/**
 * Camboyer — pont Elementor (couche 3 du design system)
 *
 * Traduit les classes `.cb-*` de `atoms.css` (couche 2, markup sémantique
 * neutre) vers le DOM RÉEL qu'Elementor produit pour un widget qui les porte.
 * AUCUNE décision de design ici — toute couleur/taille/rayon vient d'un
 * `var(--cb-*)` déjà posé en couche 1/2. Si une valeur littérale apparaît
 * ci-dessous, c'est une valeur de STRUCTURE (une propriété de layout dont la
 * couche 2 ne dit rien parce qu'elle ne connaît pas Elementor), jamais une
 * couleur/taille de marque — cf. `design-system/AGENTS.md`.
 *
 * Ticket DS24. Chargée en dépendance de `elementor-frontend` (le plugin WP
 * l'enfile ainsi, cf. AGENTS.md § Déploiement) : elle charge donc APRÈS la
 * feuille compilée d'Elementor. À spécificité et importance égales, l'ordre
 * de source nous fait gagner (docs/elementor.md § 3.2) — mais voir plus bas :
 * sur cette réinitialisation de préprod (Elementor 4.1.4, container
 * experiment actif, Atomic Elements INACTIF), aucune règle par-widget relevée
 * pour `button`/`heading`/`uael-advanced-heading` ne porte `!important`. On
 * met quand même `!important` partout ci-dessous : c'est la riposte déjà
 * décidée (docs/elementor.md § 3.2, docs/strategie-implementation.md décision
 * 4), elle ne coûte rien si l'adversaire n'en a pas, et elle nous protège si
 * un futur widget/version/kit en ajoute une.
 *
 * ===========================================================================
 * CE QUI A ÉTÉ RELEVÉ (pas supposé) — préprod, 2026-08-10, post-A03
 * ===========================================================================
 *
 * Méthode : `curl` sur la page rendue (`https://preprod.domainedecamboyer.com/`,
 * post `8422`) pour le markup réel, `curl` sur les CSS compilés
 * (`wp-content/uploads/elementor/css/post-<id>.css`) pour les règles réelles.
 * Version : `wp plugin list` → elementor 4.1.4 ; `wp option list --search=
 * "elementor_experiment*"` → `container` = active, aucune entrée pour
 * `e_atomic_elements` (jamais activée) — confirmé aussi par le markup lui-même
 * (aucune classe hachée `e-…` de type "Atomic", uniquement le DOM "classique"
 * `.elementor-button`/`.elementor-heading-title`).
 *
 * BOUTON (widget `button`) — 3 ids réels relevés : `1f9e470`/`1947ea1`
 * (header, `_css_classes:"btn-fond-clair"`, template `8376`) et `c597b7c`
 * (header, `"btn-souligne"`), plus les pastilles homepage sans lien
 * (`f900da7`, `role="button"`, aucun `href`). DOM, identique à ce que
 * `legacy/camboyer-design-system.css` documentait déjà (toujours vrai après
 * le reset A03) :
 *   .elementor-element.elementor-element-<id>[.classes] .elementor-widget-button
 *     .elementor-widget-container
 *       .elementor-button-wrapper
 *         a.elementor-button[.elementor-button-link][.elementor-size-*]
 *           span.elementor-button-content-wrapper
 *             span.elementor-button-icon > svg   (icône, optionnelle)
 *             span.elementor-button-text
 * `_css_classes` atterrit sur `.elementor-element`, PAS sur le `<a>` réel
 * (confirmé — docs/elementor.md § 3.1). CSS compilé réel pour `1947ea1`
 * (`post-8376.css`) :
 *   .elementor-8376 .elementor-element.elementor-element-1947ea1
 *     .elementor-button{ background-color:#284634; border-style:solid;
 *     border-width:1px; border-color:var(--e-global-color-astglobalcolor2);
 *     border-radius:4px; padding:12px 15px; }
 * → spécificité RÉELLE mesurée : 4 classes (`.elementor-8376`,
 * `.elementor-element`, `.elementor-element-1947ea1`, `.elementor-button`),
 * **AUCUN `!important`**. Idem pour la pastille kit-default (`post-8422.css`,
 * id `f900da7`) : `.elementor-button{background-color:#E9EADD;font-size:14px;
 * …border-radius:50px;padding:10px;}`, sans `!important`. Balayage des 69
 * fichiers CSS compilés du site (`grep -l important *.css`) : 6 fichiers
 * seulement contiennent le mot, et AUCUNE occurrence ne porte sur
 * `.elementor-button` — les 6 portent sur le menu de nav (`margin-top:5px`),
 * un champ Gravity Forms, une checkbox. **Écart avec `docs/elementor.md`
 * § 3.2** (qui prévoit 3 classes + `!important`, décrit comme "Elementor 4
 * Atomic") : ce comportement ne s'observe PAS sur cette réinitialisation,
 * cohérent avec Atomic Elements inactif. Signalé, pas corrigé dans ce ticket
 * (rediscuter `docs/elementor.md` § 3.2 est hors mandat DS24) — la riposte
 * (spécificité doublée + `!important`) reste appliquée par précaution : elle
 * gagne dans les deux cas (l'observé ET le prédit par la doc).
 *
 * TITRE (widget `heading`) — ids réels : `776e36e` (h1, `titre-principal-
 * golfer`), `d876828` (h2, même classe). DOM :
 *   .elementor-element.elementor-element-<id>[.classes] .elementor-widget-heading
 *     .elementor-widget-container
 *       h1-h6.elementor-heading-title
 * Même piège que le bouton : la classe atterrit sur `.elementor-element`, le
 * texte stylé est le `<hX>` nested. CSS compilé réel (`post-8422.css`,
 * `776e36e`) : `.elementor-8422 .elementor-element.elementor-element-776e36e
 * .elementor-heading-title{font-size:clamp(30px, 2.6vw, 55px);font-weight:500;
 * text-transform:uppercase;line-height:1.1em;color:var(
 * --e-global-color-astglobalcolor4);}` → 4 classes, aucun `!important`.
 *
 * TEXTE (widget `text-editor`) — id réel `82351a3`. DOM :
 *   .elementor-element.elementor-element-<id> .elementor-widget-text-editor
 *     .elementor-widget-container
 *       <p>…contenu WYSIWYG, y compris un <a> posé à la main…</p>
 * Contrairement au bouton/titre, un lien posé DANS le texte n'est PAS
 * enveloppé par Elementor : la classe `.cb-link--prose` atterrit DIRECTEMENT
 * sur le `<a>` réel (l'auteur la tape dans l'éditeur HTML/avancé du lien).
 * Piège différent, relevé sur le kit (`post-8336.css`) :
 * `.elementor-kit-8336 a:hover{color:#D84D2B;}` — une règle **bare-element**
 * (spécificité 1 classe + 1 pseudo-classe + 1 élément) qui bat une règle
 * `.cb-link--on-light:hover` seule (1 classe + 1 pseudo-classe, PAS d'élément)
 * à spécificité égale sur les deux premières colonnes mais INFÉRIEURE sur la
 * troisième — **le lien du design system perdrait cette bataille sans le
 * pont**, même si ce n'est pas le scénario documenté par `docs/elementor.md`
 * § 3.2 (qui ne parle que du bouton). Trouvaille propre à ce ticket.
 *
 * IMAGE (widget `image`) — ids réels `bd28c62` (logo header, lié) et
 * `e786e0f` (photo simple, `border_radius` réglé : `.elementor-8422
 * .elementor-element.elementor-element-e786e0f img{border-radius:5px;}`,
 * aucun `!important`). DOM :
 *   .elementor-element.elementor-element-<id> .elementor-widget-image
 *     .elementor-widget-container
 *       [a >] img.attachment-*[.wp-image-<id>]
 * **DS25** : `.cb-img` existe maintenant dans `atoms.css` — traduit § 4
 * ci-dessous, sur ce même relevé (rien de nouveau relevé sur le serveur pour
 * ce ticket, le relevé DS24 ci-dessus suffisait déjà).
 *
 * `uael-advanced-heading` (widget noté par le ticket DS24, 16 occurrences
 * relevées alors sur la seule homepage — chiffre CORRIGÉ par DS26 : 53
 * occurrences réelles, sur 16 des 25 pages publiées, et 0 sur les 8
 * templates Theme Builder ; voir § 5 pour le comptage et sa commande)
 * — id réel `60b1d83` (carte
 * "Élégance") : DOM `.elementor-widget-container > div.uael-module-content
 * .uael-heading-wrapper > div.uael-sub-heading[.elementor-inline-editing]
 * (SCRIPT, ex. "Élégance") + h2.uael-heading > span.uael-heading-text
 * (TITRE)` — sur CETTE occurrence précise, l'ordre est SCRIPT PUIS TITRE,
 * l'inverse de `.cb-title-lockup` (titre puis script).
 *
 * **DS26 — mesuré, pas déduit : ce n'est PAS l'ordre du widget, c'est
 * un RÉGLAGE de cette occurrence.** Voir § 3 ci-dessous pour la règle de
 * traduction écrite (décision : ON GARDE ce widget).
 *
 * CONTENEUR (pour `.cb-title-lockup`/`.cb-title-inline`, posés sur un
 * container plutôt qu'un widget) — deux formes RÉELLES et INCOMPATIBLES
 * selon le réglage Elementor "content width" du container (relevé dans
 * `wp-content/plugins/elementor/assets/css/frontend.css`, pas un post
 * compilé — c'est la base du système de container, commune à tout le site) :
 *   - **plein-large** (`.e-con-full`) : `css_classes` atterrit sur le MÊME
 *     nœud qui porte `flex-direction`/`justify-content`/`align-items`
 *     (`.e-con-full.e-flex{flex-direction:var(--flex-direction)}`) — la
 *     classe et le flex-container sont le MÊME élément.
 *   - **boxed** (`.e-con-boxed`) : ces mêmes propriétés s'appliquent à un
 *     `<div class="e-con-inner">` ENFANT (`.e-con.e-flex > .e-con-inner{…}`),
 *     jamais au nœud qui porte `css_classes` — même piège que le bouton/
 *     titre, mais côté container.
 * Confirmé par lecture directe du DOM homepage (`elementor-element-2dedd36`,
 * `e-con-full`, ses enfants sont directement les widgets, aucun
 * `.e-con-inner` propre) vs un container boxed ancestor qui, lui, en a un.
 * Les deux formes sont couvertes ci-dessous (aucune décision : c'est une
 * traduction mécanique des deux DOM réels possibles, pas un choix de lequel
 * employer — ce choix reste à qui posera la classe sur un vrai bloc).
 */


/* ===========================================================================
 * 0. DÉPEINTE DU WRAPPER — le préalable à tout le reste du fichier (DS36)
 *
 * LE BUG QUE CE BLOC CORRIGE, et pourquoi il a fallu un retour d'Alexandre
 * pour le voir (2026-08-11) : « des composants avec doublon d'affichage
 * comme les stickers […] c'est carrément le cas sur préprod même sur des
 * boutons ». Pastille dans une pastille, bouton dans un bouton.
 *
 * Mécanisme, en trois faits déjà écrits ailleurs mais jamais rapprochés :
 *   1. `_css_classes` atterrit sur `.elementor-element`, le wrapper
 *      EXTÉRIEUR du widget (docs/elementor.md § 2.1/§ 3.1).
 *   2. Ce fichier cible donc le descendant réel (`a.elementor-button`,
 *      `img`, `ul`, `.e-con-inner`) — c'est tout son objet.
 *   3. Mais `atoms.css` cible le sélecteur NU (`.cb-tag`), qui matche aussi
 *      ce wrapper. Et les DEUX couches sont enfilées sur la même page
 *      (docs/prod.md § 15 — le plugin enfile tokens + atoms + pont).
 * Donc les deux nœuds imbriqués reçoivent la même recette.
 *
 * MESURÉ sur la page de revue avant/après (getComputedStyle, Canary 153,
 * 1440px) : **36 des 38 jumeaux de widget portaient 2 nœuds peints**
 * (`div > a`), 0 après ce bloc. Et la mesure a corrigé le diagnostic de
 * départ : le padding ne s'ADDITIONNE pas (8/20 sur les deux nœuds, pas
 * 16/40) — ce sont deux boîtes emboîtées portant chacune la même recette,
 * donc le padding du wrapper pousse le `<a>` vers l'intérieur et on voit
 * littéralement DEUX pastilles concentriques séparées de 8/20. C'est le
 * symptôme rapporté, mot pour mot : « doublon d'affichage ». Sur `--fill`,
 * deux bordures 1px concentriques séparées de 12/16px. Sur le fond
 * translucide (`--cb-tag-bg`, 10 %), les deux couches se composent dans la
 * zone commune : ~19 % au lieu de 10 %.
 *
 * `atoms.css` n'est PAS en faute : son contrat est de styler du markup
 * neutre (AGENTS.md, couche 2), et elle le fait. C'est ce fichier-ci qui
 * devait dire que sur du DOM Elementor le wrapper ne peint rien — l'en-tête
 * du § 1 l'AFFIRMAIT déjà (« la boîte extérieure n'a pas de rôle visuel
 * propre ») sans jamais l'imposer. Ce bloc l'impose.
 *
 * POURQUOI CETTE FORME DE SÉLECTEUR, et pas une autre : le sujet porte
 * `.elementor-element` EN PLUS de la classe. Le markup neutre de la page de
 * revue n'a jamais cette classe — donc cette règle est STRUCTURELLEMENT
 * incapable de l'atteindre, sans liste d'exclusions à maintenir. Même
 * raisonnement que le `main > section > h2` de review-chrome.css (DS02d).
 * `design-system/tools/lint-couches.py` ne reconnaît que cette forme, et
 * ÉCHOUE le build si une classe doublement peinte n'y figure pas.
 *
 * ON DÉPEINT LA FAMILLE ENTIÈRE (fond, bordure, rayon, padding), pas
 * seulement les propriétés que le lint signale — parce que le doublage se
 * produit aussi ENTRE DEUX CLASSES posées sur le même nœud, ce qu'un lint
 * classe par classe ne voit pas. Cas réel en préprod : un CTA porte
 * `cb-btn cb-interactive cb-interactive--tone` ; le padding vient de
 * `.cb-interactive` (atoms.css) sur le wrapper et de `.cb-btn
 * .elementor-button` (ce fichier) sur le `<a>`. Deux classes, une seule
 * boîte doublée, zéro alerte possible en raisonnant classe par classe.
 * `outline` et `box-shadow` sont volontairement HORS de la dépeinte :
 * aucune des deux n'est doublée, et `outline` porte l'anneau de focus —
 * on ne touche pas à un dispositif d'accessibilité sans motif.
 * ========================================================================= */

/* widgets — la classe est sur le `.elementor-element` du widget, le nœud
   visuel est enterré dessous (bouton/pastille/label/lien : `a.elementor-
   button` ; image : `img` ; liste : `ul`). Le wrapper ne peint rien.

   Les MODIFICATEURS sont listés en plus de leurs bases, alors que le
   `!important` de la base suffirait déjà à les couvrir (il bat une
   déclaration nue quelle que soit la spécificité). C'est délibéré : le lint
   vérifie classe par classe, sans rien déduire du `--` de nommage — donc
   cette liste est exactement l'inventaire de « ce qui peint une boîte en
   couche 2 et a un descendant peint en couche 3 », lisible et vérifié.
   Ajouter un modificateur qui peint sans l'inscrire ici fait échouer le
   build ; les modificateurs qui ne peignent PAS de boîte (`--on-light`,
   `--h3`, `--3-2`…) n'ont rien à y faire. */
.elementor-element.cb-btn,
.elementor-element.cb-btn--icon,
.elementor-element.cb-link,
.elementor-element.cb-tag,
.elementor-element.cb-tag--s,
.elementor-element.cb-tag--m,
.elementor-element.cb-tag--on-dark,
.elementor-element.cb-label,
.elementor-element.cb-interactive,
.elementor-element.cb-interactive--tone,
.elementor-element.cb-interactive--fill,
.elementor-element.cb-interactive--fill-on-dark,
.elementor-element.cb-interactive--solid-light,
.elementor-element.cb-img,
.elementor-element.cb-list{
  background: none !important;
  border: 0 !important;
  border-radius: 0 !important;
  padding: 0 !important;
}

/* container BOXED seulement — `.cb-section` est posée sur un container
   (`css_classes`), et ce fichier pose son padding sur `.e-con > .e-con-inner`
   (§ 11). Quand le container est boxed, ce `.e-con-inner` existe : le padding
   de section s'applique alors DEUX fois, 112px de gouttière au lieu de 56 en
   mobile, 224 au lieu de 112 en desktop. Quand il est `e-con-full`, le pont
   peint le MÊME nœud que la classe et il n'y a rien à dépeindre — d'où le
   `:has(> .e-con-inner)`, qui discrimine sur la présence réelle du nœud
   plutôt que sur le nom de classe `e-con-full` (déjà employé par DS26 pour
   `uael-advanced-heading`, même mécanique). */
.elementor-element.cb-section.e-con:has(> .e-con-inner){
  padding: 0 !important;
}
/* `.cb-content-hero` est posée sur LE MÊME container que `.cb-section` (relevé
   sur `11273` : `css_classes = "cb-section cb-section--light cb-univers--hotel
   cb-content-hero"`), et § 11bis lui repose un `padding-top` sur `.e-con-inner`
   en boxed. Elle a donc besoin de la même dépeinture du wrapper, pour la même
   raison, sous peine de compter son padding haut deux fois. */
.elementor-element.cb-content-hero.e-con:has(> .e-con-inner){
  padding: 0 !important;
}
.elementor-element.cb-content-hero__motion.e-con:has(> .e-con-inner){
  border-radius: 0 !important;
}
/* Même raison, un cran plus loin : la variante bandeau (P03e, § 11ter) rend son
   `padding-top` de section à `.cb-content-hero--bandeau`, et le pont le repose
   lui aussi sur `.e-con-inner` en boxed. La dépeinture ci-dessus est portée par
   `.cb-content-hero`, qui est toujours présente à côté de la variante — mais la
   déclarer ici rend la variante autonome et le lint vérifiable, plutôt que
   dépendante d'une classe voisine qu'un jour quelqu'un ne posera plus. */
.elementor-element.cb-content-hero--bandeau.e-con:has(> .e-con-inner){
  padding: 0 !important;
}
.elementor-element.cb-bandeau.e-con:has(> .e-con-inner){
  padding: 0 !important;
}


/* ===========================================================================
 * 1. FAMILLE BOUTON — .cb-btn / .cb-link / .cb-tag / .cb-label
 * Toutes portées par le widget `button` d'Elementor. La classe atterrit sur
 * `.elementor-element` (extérieur) ; l'élément visuel réel est
 * `a.elementor-button`, nesté sous `.elementor-widget-container
 * .elementor-button-wrapper`. Toute propriété visuelle cible donc
 * `.elementor-button` en DESCENDANT — jamais la classe seule (qui ne garde
 * que ce qui a un sens sur la boîte extérieure : rien, ici, la boîte
 * extérieure n'a pas de rôle visuel propre pour cette famille).
 * ========================================================================= */

/* structure du widget — layout seul (aucune couleur/taille de marque) : la
   wrapper/content-wrapper/icon/text d'Elementor doivent laisser passer le
   contenu comme le ferait notre markup neutre (inline-flex, hérite du
   parent). Commun à cb-btn ET cb-link (les deux composent .cb-interactive
   sur du DOM bouton). */
.cb-btn .elementor-button-wrapper,
.cb-link .elementor-button-wrapper{
  display: block; /* layout Elementor natif — ne pas transformer, seul le <a> à l'intérieur compte */
}
.cb-btn .elementor-button-content-wrapper,
.cb-btn .elementor-button-text,
.cb-link .elementor-button-content-wrapper,
.cb-link .elementor-button-text{
  display: inline-flex !important;
  align-items: center !important;
  color: inherit !important;
  font: inherit !important;
}
/* Le `gap` réel entre libellé et icône appartient au wrapper interne
   Elementor, pas au lien extérieur. Sans cette traduction, le widget conserve
   son 5px local malgré l'atome `link/on-light-with-icon` (12px).

   ⚠️ DS64, 2026-08-17 — CETTE RÈGLE EXISTAIT, MAIS SEULEMENT POUR `.cb-link`.
   C'est le défaut signalé par Alexandre le 2026-08-15 (« les boutons déjà
   migrés n'ont pas le bon gap entre le label et l'icône »), et sa cause exacte :
   `.cb-interactive` pose bien `gap: var(--cb-space-sm)` — mesuré 12px sur le
   porteur — mais le libellé et l'icône ne sont PAS ses enfants directs. Ils
   vivent dans `.elementor-button-content-wrapper`, qui garde son `gap: 5px`
   par défaut. Le token n'était pas faux et le widget n'avait aucun réglage
   local : c'était la 2ᵉ des trois causes possibles listées au ticket — le pont
   n'atteignait pas le bon nœud.

   Mesuré avant/après sur l'accueil : les 4 `.cb-link` rendaient déjà 12px,
   les 15 `.cb-btn` rendaient **5px**. Ajouter `.cb-btn` ici les aligne tous
   d'un coup, à la source, sans toucher un seul widget. */
.cb-btn .elementor-button-content-wrapper,
.cb-link .elementor-button-content-wrapper{
  gap: var(--cb-space-sm) !important;
}

/* RIPOSTE AUX IMPOSITIONS GLOBALES (DS35) — ce que le site force à TOUT
   `.elementor-button`, et que nos atomes ne veulent pas.

   Origine mesurée le 2026-08-11 sur le CSS SERVI par la préprod, en injectant
   le markup neutre à côté du widget réel dans la page elle-même (donc face au
   vrai adversaire, kit et thème compris — pas face à un relevé) :

   | propriété       | ce que l'atome rend | imposé | par qui                        |
   |-----------------|---------------------|--------|--------------------------------|
   | text-transform  | none                | upper  | Astra (dynamic-css) ET kit 8336 |
   | letter-spacing  | normal              | 2px    | Astra (dynamic-css) seul        |
   | text-align      | start               | center | Elementor core (frontend.min)   |

   ⚠️ `DS35` disait « le défaut du kit, qui met en capitales ». À moitié
   seulement : les capitales ont DEUX sources, et le `letter-spacing` — jamais
   suspecté — vient d'Astra uniquement. Les deux comptent autant : mesuré sur
   la page de revue, l'adversaire global élargit un bouton de 161 à 210px
   (+30 %) et une pastille de 65 à 79px (+22 %). C'est ce surcroît de largeur,
   pas seulement la casse, qui a fait déborder les pastilles de `H03` sur 3-4
   lignes.

   Pourquoi c'est arrivé maintenant : la règle du projet veut qu'un widget
   portant une classe `.cb-*` ait ses champs de style Elementor VIDES
   (AGENTS.md). Vider `text_transform` ne le met pas à `none` — ça retire la
   valeur locale et laisse gagner la globale. Vider un champ EXPOSE au défaut,
   il ne neutralise pas.

   `text-align` n'est PAS contré, volontairement : sur un `.elementor-button`
   en `inline-flex` d'une seule ligne, `center` et `start` rendent le même
   pixel. Contrer une propriété sans effet ajouterait du bruit et une règle à
   maintenir. À reprendre si un libellé passe un jour sur deux lignes. */
.cb-btn .elementor-button,
.cb-link .elementor-button,
.cb-tag .elementor-button,
.cb-label .elementor-button{
  text-transform: none !important;
  letter-spacing: normal !important;
}

/* .cb-interactive — le SYSTÈME D'ÉTAT (DS11), retargeté sur le <a> réel.
   :hover fonctionne déjà tel quel sur la classe posée à l'extérieur (:hover
   se propage aux ancêtres d'un descendant survolé) — pas besoin de le
   retargeter. :focus-visible et :active/:disabled ne se propagent PAS de la
   même façon : un `<div class="…cb-interactive…">` qui n'est lui-même jamais
   focusable/actif ne verrait jamais ces pseudo-classes se déclencher si on
   les laissait sur `.cb-interactive` seul — elles doivent cibler le `<a>`
   réel directement. C'est un fait de DOM, pas une décision de design : les
   valeurs (couleur du ring, dim, opacité) restent celles déjà posées par
   `atoms.css`, seule la CIBLE change. */
/* DS51, 2026-08-19 — LES HAMEÇONS DE DÉMO, sur le DOM d'Elementor.
   `[data-demo-state="…"]` existait en couche 2 et NULLE PART ici : le jumeau
   Elementor de la page de revue ne pouvait donc afficher aucun état forcé, et
   « une capture ne montre pas un survol » (l'argument même de DS11). Résultat :
   les sections Interaction et Boutons & liens n'avaient pas de jumeau, et la
   moitié des règles d'état du pont n'était éprouvée nulle part.
   Le hameçon est posé sur les MÊMES règles que la vraie pseudo-classe — jamais
   une règle dupliquée, qui pourrait diverger. C'est mot pour mot la doctrine
   qu'atoms.css énonce pour la couche 2, appliquée ici.
   ⚠️ NON COUVERT, et c'est un FAIT à ne pas maquiller : `--ghost-on-dark` n'a
   aucune règle de survol générique dans ce pont — il n'existe que dans le
   header (§ 11), où son survol est traité par le régime du header. Il n'a donc
   pas de hameçon, et son jumeau ne montrera pas de survol. */
.cb-btn .elementor-button:focus-visible,
.cb-link .elementor-button:focus-visible,
.cb-btn[data-demo-state="focus-visible"] .elementor-button,
.cb-link[data-demo-state="focus-visible"] .elementor-button{
  outline: var(--cb-interaction-focus-ring-width) solid var(--cb-cta) !important;
  outline-offset: var(--cb-interaction-focus-ring-offset) !important;
}
.cb-btn .elementor-button:active,
.cb-link .elementor-button:active,
.cb-btn[data-demo-state="active"] .elementor-button,
.cb-link[data-demo-state="active"] .elementor-button{
  filter: brightness(var(--cb-interaction-active-dim)) !important;
}
/* Elementor rend TOUJOURS le widget `button` en `<a>` (confirmé — même le
   "type=button" relevé ci-dessus produit `<a role="button">`, jamais un vrai
   `<button>`) : `:disabled` natif n'existe donc jamais pour ce widget, seul
   `[aria-disabled]` s'applique — cohérent avec la nuance déjà posée par
   `atoms.css` pour `.cb-link`, généralisée ici à `.cb-btn` sur ce DOM précis. */
.cb-btn[aria-disabled="true"] .elementor-button,
.cb-link[aria-disabled="true"] .elementor-button,
.cb-btn[data-demo-state="disabled"] .elementor-button,
.cb-link[data-demo-state="disabled"] .elementor-button{
  opacity: var(--cb-interaction-disabled-opacity) !important;
  cursor: not-allowed !important;
  pointer-events: none !important;
}

/* ⚠️ LE DÉPLACEMENT D'ICÔNE AU SURVOL A ÉTÉ RETIRÉ ICI LE 2026-08-12, avec son
   token et sa contrepartie couche 2. NE PAS LE RECRÉER. Décision d'Alexandre,
   après l'avoir vu sur la carte du hero : « ce truc de mouvement d'icône au
   hover des boutons on peut le wipe complètement, ça n'a pas de sens. »
   Ce bloc ciblait `.elementor-button-icon` (svg/img/i), le seul point d'ancrage
   réel côté Elementor, `_css_classes` n'ayant pas de portée sub-élément — ce
   constat-là reste vrai et resservira le jour où une icône aura besoin d'une
   règle. Il n'en a plus besoin d'une : le survol ne change que des couleurs.
   Le `@media (prefers-reduced-motion)` qui l'accompagnait part avec lui — il
   neutralisait une transition qui n'existe plus. */


/* recettes de couleur (--tone/--fill/--fill-on-dark), transition de base —
   valeurs identiques à `atoms.css`, seule la cible change (`.elementor-button`
   descendant, jamais `.cb-interactive`/`.cb-btn` seuls — DS17/DS18 déjà
   tranchées, reprises telles quelles). Doublage de classe (`.cb-btn.cb-btn--x`)
   pour égaler la spécificité déjà observée sur les règles compilées réelles
   (4 classes, cf. relevé ci-dessus) — `!important` sur les mêmes propriétés
   pour gagner même si un widget futur/une version future en ajoute. */
.cb-btn .elementor-button,
.cb-link .elementor-button{
  transition:
    background-color var(--cb-interaction-duration-base) var(--cb-interaction-ease),
    border-color var(--cb-interaction-duration-base) var(--cb-interaction-ease),
    color var(--cb-interaction-duration-base) var(--cb-interaction-ease) !important;
}

.cb-interactive.cb-interactive--tone .elementor-button,
.cb-btn.cb-interactive--tone .elementor-button{
  background: var(--cb-cta) !important;
  border-color: var(--cb-cta) !important;
  color: var(--cb-ivoire) !important;
}
/* DS63 — miroir de la couche 2 : le primaire s'approfondit au lieu de virer à
   l'encre, où `--fill` arrive déjà. Sans cette reprise ici, les 11 boutons
   `--tone` du DOM Elementor garderaient l'ancien survol et le système dirait
   deux choses différentes selon le markup. */
.cb-interactive.cb-interactive--tone .elementor-button:hover,
.cb-btn.cb-interactive--tone .elementor-button:hover,
.cb-interactive.cb-interactive--tone .elementor-button:focus,
.cb-btn.cb-interactive--tone .elementor-button:focus,
.cb-interactive.cb-interactive--tone[data-demo-state="hover"] .elementor-button,
.cb-btn.cb-interactive--tone[data-demo-state="hover"] .elementor-button{
  background: var(--cb-cta-hover) !important;
  border-color: var(--cb-cta-hover) !important;
}

.cb-interactive.cb-interactive--fill .elementor-button,
.cb-btn.cb-interactive--fill .elementor-button{
  background: transparent !important;
  border-color: var(--cb-encre) !important;
  color: var(--cb-encre) !important;
}
.cb-interactive.cb-interactive--fill .elementor-button:hover,
.cb-btn.cb-interactive--fill .elementor-button:hover,
.cb-interactive.cb-interactive--fill .elementor-button:focus,
.cb-btn.cb-interactive--fill .elementor-button:focus,
.cb-interactive.cb-interactive--fill[data-demo-state="hover"] .elementor-button,
.cb-btn.cb-interactive--fill[data-demo-state="hover"] .elementor-button{
  background: var(--cb-encre) !important;
  color: var(--cb-ivoire) !important;
}

/* --solid-light (DS28) — reprise littérale d'atoms.css, retargetée. Aucune
   décision ici : les deux couleurs sont celles mesurées sur le header réel. */
.cb-interactive.cb-interactive--solid-light .elementor-button,
.cb-btn.cb-interactive--solid-light .elementor-button{
  background: var(--cb-ivoire) !important;
  border-color: var(--cb-ivoire) !important;
  color: var(--cb-olive) !important;
}
.cb-interactive.cb-interactive--solid-light .elementor-button:hover,
.cb-btn.cb-interactive--solid-light .elementor-button:hover,
.cb-interactive.cb-interactive--solid-light .elementor-button:focus,
.cb-btn.cb-interactive--solid-light .elementor-button:focus,
.cb-interactive.cb-interactive--solid-light[data-demo-state="hover"] .elementor-button,
.cb-btn.cb-interactive--solid-light[data-demo-state="hover"] .elementor-button{
  background: var(--cb-olive) !important;
  border-color: var(--cb-olive) !important;
  color: var(--cb-ivoire) !important;
}
/* L'icône native d'Elementor est un `<svg>`/`<i>` sous `.elementor-button-icon`
   (§ 1 ci-dessus) : `fill` ne s'hérite pas comme `color`, il faut le poser. */
.cb-interactive--solid-light .elementor-button svg,
.cb-interactive--solid-light .elementor-button i{
  fill: currentColor !important;
  color: inherit !important;
}

/* Les SVG/icônes Elementor ne suivent pas toujours la couleur du lien : des
   `fill` inline peuvent gagner l'héritage. Le pictogramme est une partie du
   contrôle, il doit donc toujours suivre son texte, au repos comme au hover.
   Cette règle vise exclusivement les atomes DS (jamais les widgets legacy). */
.cb-btn .elementor-button .elementor-button-icon,
.cb-link .elementor-button .elementor-button-icon{
  display:inline-flex !important;
  align-items:center !important;
  color:currentColor !important;
}
.cb-btn .elementor-button .elementor-button-icon :is(svg, svg *, i),
.cb-link .elementor-button .elementor-button-icon :is(svg, svg *, i){
  color:currentColor !important;
  fill:currentColor !important;
}

/* btn/icon (DS28) — le carré. Même piège que tout le reste de cette famille :
   la classe est sur le wrapper, la boîte visuelle est le `<a>`. */
/* ⚠️ DEUX CLASSES DU PORTEUR, ET C'EST UNE RÉPARATION — DS51, 2026-08-19.
   Cette règle s'écrivait `.cb-btn--icon .elementor-button` (0-2-0) et perdait
   contre `.cb-btn .elementor-button` (0-2-0 aussi, ligne ~590) sur le SEUL
   critère de l'ordre du fichier : la règle de base vient APRÈS le modificateur
   et lui reprenait son `padding` horizontal. Mesuré par le comparateur dès que
   `btn/icon` a eu un jumeau (DS51) : markup neutre **12px** de chaque côté,
   jumeau Elementor **16px** — donc un bouton à icône seule qui n'était PAS
   CARRÉ sur le DOM réel, exactement le défaut qu'`aspect-ratio` était censé
   éteindre et que `DS28` avait déjà payé une fois en couche 2.
   Le défaut ne se voyait nulle part parce que ces deux sections n'avaient pas
   de jumeau : la moitié du pont n'était éprouvée par rien.
   `.cb-btn.cb-btn--icon` pèse 0-3-0 et gagne par SPÉCIFICITÉ, pas par position —
   « une règle qui a besoin de l'ordre des sources pour être juste est fausse »
   (docs/pieges.md § 4). Aucune force ajoutée : on nomme, on ne pousse pas. */
.cb-btn.cb-btn--icon .elementor-button{
  aspect-ratio: 1 !important;
  padding: var(--cb-space-sm) !important;
  justify-content: center !important;
  gap: 0 !important;
}
.cb-btn.cb-btn--icon .elementor-button .elementor-button-icon svg,
.cb-btn.cb-btn--icon .elementor-button .elementor-button-icon img,
.cb-btn.cb-btn--icon .elementor-button .elementor-button-icon i{
  display: block !important;
  width: 1em !important;
  height: 1em !important;
}

.cb-interactive.cb-interactive--fill-on-dark .elementor-button,
.cb-btn.cb-interactive--fill-on-dark .elementor-button{
  background: transparent !important;
  border-color: var(--cb-ivoire) !important;
  color: var(--cb-ivoire) !important;
}
.cb-interactive.cb-interactive--fill-on-dark .elementor-button:hover,
.cb-btn.cb-interactive--fill-on-dark .elementor-button:hover,
.cb-interactive.cb-interactive--fill-on-dark .elementor-button:focus,
.cb-btn.cb-interactive--fill-on-dark .elementor-button:focus,
.cb-interactive.cb-interactive--fill-on-dark[data-demo-state="hover"] .elementor-button,
.cb-btn.cb-interactive--fill-on-dark[data-demo-state="hover"] .elementor-button{
  background: color-mix(in srgb, var(--cb-ivoire) 15%, transparent) !important;
  border-color: var(--cb-ivoire) !important;
}

/* .cb-btn — forme (rayon, typo, box-model), reprise littérale de `atoms.css`
   `.cb-btn`, retargetée sur `.elementor-button`. */
.cb-btn .elementor-button{
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  gap: var(--cb-space-sm) !important;
  width: auto !important;
  height: auto !important;
  font-family: var(--cb-font-body) !important;
  font-size: var(--cb-text-base) !important;
  line-height: 1 !important;
  text-decoration: none !important;
  border-radius: var(--cb-radius-sm) !important;
  border: 1px solid transparent !important;
  padding: var(--cb-space-sm) var(--cb-space-md) !important;
  cursor: pointer !important;
}

/* .cb-link — annule le box-model "bouton" (padding/bordure/fond/rayon) :
   un lien posé sur un widget `button` en mode "link" reste du texte, jamais
   une pilule — reprise littérale de `atoms.css` `.cb-link`. */
.cb-link .elementor-button{
  display: inline-flex !important;
  align-items: center !important;
  gap: var(--cb-space-sm) !important;
  width: auto !important;
  height: auto !important;
  padding: 0 !important;
  border: none !important;
  border-radius: 0 !important;
  background: none !important;
  font-family: var(--cb-font-body) !important;
  font-size: var(--cb-text-base) !important;
  line-height: 1 !important;
  text-decoration: none !important;
}

/* .cb-link--on-light/--on-dark — couleur + survol. Double cible : le `<a>`
   RÉEL peut être (a) l'élément qui porte la classe elle-même (cas
   `.cb-link--prose`, posé DIRECTEMENT sur le `<a>` tapé dans le texte d'un
   widget text-editor — aucun wrapper Elementor entre la classe et le lien)
   ou (b) un `.elementor-button` nesté sous un `.elementor-element` qui porte
   la classe (cas `.cb-link` posé sur un widget `button` en mode "link").
   Les deux sélecteurs coexistent sans jamais s'appliquer en double : chaque
   forme de DOM ne matche que la règle qui lui correspond.
   `!important` nécessaire même hors bouton : le kit pose une règle bare-
   element `.elementor-kit-8336 a:hover{color:#D84D2B}` (relevé ci-dessus,
   post-8336.css) qui bat `.cb-link--on-light:hover` seul à spécificité
   égale sur les deux premières colonnes mais supérieure sur la troisième
   (elle matche l'élément `a`, notre sélecteur ne matche qu'une classe). */
.cb-link--on-light,
.cb-link--on-light .elementor-button{
  color: var(--cb-vert) !important;
}
.cb-link--on-light:hover, .cb-link--on-light:focus,
.cb-link--on-light[data-demo-state="hover"],
.cb-link--on-light .elementor-button:hover, .cb-link--on-light .elementor-button:focus{
  color: var(--cb-encre) !important;
}
.cb-link--on-dark,
.cb-link--on-dark .elementor-button{
  color: var(--cb-ivoire) !important;
}
.cb-link--on-dark:hover, .cb-link--on-dark:focus,
.cb-link--on-dark[data-demo-state="hover"],
.cb-link--on-dark .elementor-button:hover, .cb-link--on-dark .elementor-button:focus{
  color: var(--cb-sauge) !important;
}
/* .cb-link--prose — seul le soulignement change vs le lien autonome (DS22),
   déjà correct sans retargeting : `text-decoration` s'applique directement
   à l'élément qui porte la classe, qui EST le `<a>` réel dans ce cas précis
   (contrairement au cas bouton). Aucune règle supplémentaire nécessaire ici. */


/* ===========================================================================
 * 1bis. .cb-tag / .cb-label — DÉTOURNEMENT du même widget `button`
 * (docs/composants-prod.md § 1.1/2.2 : "le button ici sert de libellé/de
 * pastille, pas de lien"). Même piège de DOM que .cb-btn (classe à
 * l'extérieur, `.elementor-button` réel à l'intérieur), mais SANS
 * `.cb-interactive` composé (décoratif, atoms.css est explicite : "aucun
 * état de survol") — donc aucune règle de `:hover`/`:focus`/`:active` à
 * retargeter ici. On neutralise quand même un éventuel survol hérité du kit
 * (relevé : certaines pastilles kit-default n'en ont aucun, d'autres oui
 * selon l'historique du widget) pour que le comportement reste bien
 * "décoratif" quel que soit l'historique du widget qui reçoit la classe.
 * ========================================================================= */
.cb-tag .elementor-button-content-wrapper,
.cb-tag .elementor-button-text,
.cb-label .elementor-button-content-wrapper,
.cb-label .elementor-button-text{
  display: inline-flex !important;
  align-items: center !important;
  color: inherit !important;
  font: inherit !important;
}

.cb-tag .elementor-button{
  display: inline-flex !important;
  align-items: center !important;
  width: auto !important;
  height: auto !important;
  border: none !important;
  border-radius: var(--cb-radius-pill) !important;
  background: var(--cb-tag-bg) !important;
  color: var(--cb-encre) !important;
  font-family: var(--cb-font-body) !important;
  font-size: var(--cb-text-sm) !important;
  line-height: 1 !important;
  text-decoration: none !important;
  cursor: default !important;
}
.cb-tag .elementor-button:hover,
.cb-tag .elementor-button:focus{
  background: var(--cb-tag-bg) !important; /* neutralise tout survol hérité du kit — décoratif, ne doit rien changer */
  color: var(--cb-encre) !important;
}
.cb-tag--m .elementor-button{ padding: var(--cb-space-xs) var(--cb-space-lg) !important; }
.cb-tag--s .elementor-button{ padding: var(--cb-space-xs) var(--cb-space-sm) !important; }

/* .cb-tag--on-dark — DS12, pastille composée dans card/overlay (atoms.css
   § .cb-tag--on-dark : fond opaque --cb-ivoire, texte --cb-encre, plutôt
   que le --cb-tag-bg translucide par défaut, jamais mesuré sur une photo).
   Même retargeting que le reste de la famille tag (§ 1bis ci-dessus) : la
   classe atterrit sur .elementor-element, la couleur réelle cible
   .elementor-button en descendant. */
.cb-tag--on-dark .elementor-button{
  background: var(--cb-ivoire) !important;
  color: var(--cb-encre) !important;
}
.cb-tag--on-dark .elementor-button:hover,
.cb-tag--on-dark .elementor-button:focus{
  background: var(--cb-ivoire) !important;
  color: var(--cb-encre) !important;
}

.cb-label .elementor-button{
  display: inline-flex !important;
  align-items: center !important;
  width: auto !important;
  height: auto !important;
  padding: 0 !important;
  border: none !important;
  border-radius: 0 !important;
  background: none !important;
  color: var(--cb-encre) !important;
  font-family: var(--cb-font-body) !important;
  font-size: var(--cb-text-sm) !important;
  line-height: 1 !important;
  text-decoration: none !important;
  cursor: default !important;
}
.cb-label .elementor-button:hover,
.cb-label .elementor-button:focus{
  background: none !important;
  color: var(--cb-encre) !important;
}

/* .cb-label posé sur un widget `heading` — DS42.
 *
 * Le détournement ci-dessus (widget `button`) était la seule forme relevée
 * quand `.cb-label` a été écrit. DS42 en a trouvé une seconde, RÉELLE et
 * répétée : les 15 cartes des 4 pages chambre contiennent chacune deux
 * sous-titres internes — « Chambre » et « Équipements » (relevé : widgets
 * 5c439c10 et 3fd09a77 du container 5023a4cb, post 11295) — qui sont des
 * widgets `heading` réglés en `header_size: h3`.
 *
 * C'est bien un LABEL et pas un titre : `Q09` a tranché que h4/h6 ne doivent
 * plus servir de petit texte titré, et que le manque se comble par les atomes
 * label/tag. Aucune décision de design ici — la couche 2 dit déjà quoi
 * peindre, cette couche dit seulement OÙ le peindre quand le porteur est un
 * `heading` : le nœud visuel est `.elementor-heading-title`, pas
 * `.elementor-button`. L'adversaire est celui du § 7 (règle `heading` du kit,
 * 4 classes sans `!important`) : spécificité doublée + `!important`, comme
 * partout ailleurs dans ce fichier.
 *
 * ⚠️ `margin` explicite : un `<h3>` réel porte les marges d'Astra, que le
 * détournement en `button` n'avait jamais à contrer. */
.cb-label .elementor-heading-title{
  display: inline-flex !important;
  align-items: center !important;
  margin: 0 !important;
  padding: 0 !important;
  border: none !important;
  background: none !important;
  color: var(--cb-encre) !important;
  font-family: var(--cb-font-body) !important;
  font-size: var(--cb-text-sm) !important;
  font-weight: 400 !important;
  line-height: 1 !important;
  text-transform: none !important; /* le kit et Astra mettent les titres en capitales — un label n'en est pas un (même riposte que DS35) */
  letter-spacing: normal !important; /* idem : Astra impose 2px aux titres, DS35 */
  text-decoration: none !important;
}


/* ---------------------------------------------------------------------------
 * 1.9 — LA CASSE DE PHRASE SUR LE DOM D'ELEMENTOR (C01)
 *
 * L'atome vit dans atoms.css (`.cb-interactive--phrase`), et il peint
 * `.cb-interactive__label`. Sur le DOM d'Elementor ce nœud s'appelle
 * `span.elementor-button-text` — c'est LUI qu'il faut peindre, pas le `<a>` :
 * le libellé est dans le span, et le `<a>` porte aussi l'icône.
 *
 * ⚠️ CETTE RÈGLE DOIT BATTRE UNE RÈGLE DE CE MÊME FICHIER, pas une règle
 * adverse : les § 1 posent `display: inline-flex !important` sur
 * `.cb-btn .elementor-button-text` (et les trois autres familles), et
 * `::first-letter` ne s'applique QU'À UNE BOÎTE DE BLOC — jamais à un conteneur
 * flex. Sans reprise du display, le modificateur mettrait tout en bas de casse
 * et ne remonterait AUCUNE majuscule : le pire des deux mondes.
 *
 * ⚠️ ET ELLE NE S'APPUIE PAS SUR L'ORDRE DES SOURCES — « une règle qui a besoin
 * de l'ordre des sources pour être juste est fausse » (docs/pieges.md § 4).
 * `.cb-btn .elementor-button-text` pèse 0-2-0 ; une règle en
 * `.cb-interactive--phrase .elementor-button-text` pèserait exactement pareil et
 * ne gagnerait que par sa position dans le fichier. D'où les DEUX classes du
 * porteur nommées ensemble (0-3-0) — la même parade que celle employée au § 13
 * pour retirer 24 `!important` sans remettre de force : on nomme, on ne pousse pas.
 *
 * ⚠️ `inline-block` et non `flex` : le span ne contient QUE le libellé (l'icône
 * est son frère, dans `.elementor-button-content-wrapper`), donc rien n'y a
 * besoin d'être aligné. Le centrage du libellé reste porté par le conteneur.
 * ------------------------------------------------------------------------- */
.cb-btn.cb-interactive--phrase .elementor-button-text,
.cb-link.cb-interactive--phrase .elementor-button-text,
.cb-tag.cb-interactive--phrase .elementor-button-text,
.cb-label.cb-interactive--phrase .elementor-button-text{
  display: inline-block !important;
  text-transform: lowercase !important;
}
.cb-btn.cb-interactive--phrase .elementor-button-text::first-letter,
.cb-link.cb-interactive--phrase .elementor-button-text::first-letter,
.cb-tag.cb-interactive--phrase .elementor-button-text::first-letter,
.cb-label.cb-interactive--phrase .elementor-button-text::first-letter{
  text-transform: uppercase !important;
}


/* ===========================================================================
 * 2. FAMILLE TITRE — .cb-title-lockup__title / __script,
 *    .cb-title-inline__title / __script
 * Widget `heading`. Même piège que le bouton : la classe atterrit sur
 * `.elementor-element`, le texte stylé réel est `.elementor-heading-title`
 * (le `<h1>`-`<h6>`) nesté sous `.elementor-widget-container`.
 * ========================================================================= */
.cb-title-lockup__title .elementor-heading-title,
.cb-title-inline__title .elementor-heading-title{
  margin: 0 !important;
  font-family: var(--cb-font-caps) !important;
  font-weight: var(--cb-weight-title) !important; /* Q27 — 200, tout le titrage, décidé le 2026-08-12 */
  /* text-transform:none — le kit met le heading par défaut en CAPITALES
     (relevé, `post-8422.css` id 776e36e : `text-transform:uppercase`), ce
     qu'aucun de nos deux titres ne veut : divergence trouvée par le jumeau
     lui-même (le script, sans casse propre, la révèle ; le titre la masque
     car Fonseca n'a pas de bas-de-casse distinct — ne pas en déduire que la
     règle ne servait à rien pour le titre). */
  text-transform: none !important;
  color: var(--cb-lockup-titre) !important;
}
.cb-title-lockup--left .cb-title-lockup__title .elementor-heading-title{
  line-height: 1.5 !important;
  font-size: var(--cb-lockup-title-size) !important;
}
.cb-title-lockup--center .cb-title-lockup__title .elementor-heading-title{
  line-height: 1.5 !important;
  font-size: var(--cb-lockup-title-size) !important;
}
.cb-title-inline__title .elementor-heading-title{
  line-height: 1.5 !important;
  font-size: var(--cb-lockup-title-size) !important;
}

.cb-title-lockup__script .elementor-heading-title{
  margin: 0 !important;
  font-family: var(--cb-font-script) !important;
  font-weight: 400 !important;
  text-transform: none !important; /* même divergence kit que __title, ci-dessus */
  line-height: 1 !important;
  color: var(--cb-terracotta) !important;
}
.cb-title-lockup--left .cb-title-lockup__script .elementor-heading-title{
  font-size: calc(var(--cb-lockup-title-size) * var(--cb-lockup-script-ratio)) !important;
}
.cb-title-lockup--center .cb-title-lockup__script .elementor-heading-title{
  font-size: calc(var(--cb-lockup-title-size) * var(--cb-lockup-script-ratio)) !important;
}
.cb-title-inline__script .elementor-heading-title{
  margin: 0 !important;
  font-family: var(--cb-font-script) !important;
  font-weight: 400 !important;
  text-transform: none !important; /* même divergence kit que __title, ci-dessus */
  font-size: calc(var(--cb-lockup-title-size) * var(--cb-lockup-script-ratio)) !important;
  line-height: 1 !important;
  color: color-mix(in srgb, var(--cb-lockup-titre) 80%, transparent) !important;
}

/* --- 2bis. `.cb-title-accent` — title/accent-italic (DS27) ----------------
 *
 * Le seul atome de titre qui se pose sur UN SEUL widget. Les deux autres
 * composent deux widgets `heading` dans un container ; celui-ci est un
 * `heading` unique dont le titre contient une balise inline — c'est ce que
 * montrent la frame (un seul nœud texte, `40000003:17426`) ET le widget réel
 * du hero (`cfa85f6`, `heading`, titre « hôtel & Événements d'Exception »
 * en une seule chaîne, relevé sur la préprod le 2026-08-12).
 *
 * Conséquence pour le ticket qui l'appliquera (`H01c`) : poser la classe ne
 * suffit pas ici — il faut aussi **enrichir la chaîne du titre** d'un
 * `<em class="cb-title-accent__mot">`. C'est un changement de contenu, pas
 * seulement de style : à faire par transformation de JSON comme le reste, et
 * `verify.py` doit rester conforme (le texte lu reste le même caractère pour
 * caractère, seul le balisage interne change). ⚠️ Et **la casse de la prod
 * gagne sur la frame** (`master-instructions.md`) : la prod écrit
 * « d'Exception », la frame « d'exception » — c'est la prod.
 *
 * `--on-dark` et `--right` ciblent le MÊME nœud peint que la base : ce sont
 * des modificateurs de la même règle, pas des couches supplémentaires.
 *
 * Le mot-accent, lui, est un vrai élément du DOM dans les DEUX markups (le
 * `<em>` est écrit dans le contenu) : il n'a pas besoin d'être retargeté,
 * seulement d'être rendu insensible aux impositions globales. Sa couleur
 * reste `inherit` — la spécification dit « même couleur que le titre », et
 * une couleur héritée ne peut pas diverger de sa source. */
.cb-title-accent .elementor-heading-title{
  margin: 0 !important;
  font-family: var(--cb-font-caps) !important;
  font-weight: var(--cb-weight-title) !important;
  font-size: var(--cb-text-title-hero) !important; /* H01 — le 32px littéral passe en token, PARTAGÉ avec .cb-heading--h1 : la frame met les deux titres du hero à la même taille */
  line-height: 1 !important;   /* H01 — CORRIGÉ avec atoms.css : la frame du hero reworké documente 100 % sur les deux segments (`40000182:18462`), là où DS27 avait dû aligner sur 1.5 faute de source */
  color: var(--cb-lockup-titre) !important;
  /* riposte aux impositions globales (DS35), même paire que la famille bouton
     § 1bis : vider le champ Elementor `typography_text_transform` du widget
     n'écrit pas `none`, ça expose au défaut d'Astra ET du kit. Le widget réel
     du hero porte justement `uppercase` aujourd'hui. */
  text-transform: none !important;
  letter-spacing: normal !important;
}
.cb-title-accent--on-dark .elementor-heading-title{ color: var(--cb-ivoire) !important; }
.cb-title-accent--right .elementor-heading-title{ text-align: right !important; }
.cb-title-accent .cb-title-accent__mot{
  font-family: var(--cb-font-accent) !important;
  font-style: italic !important;
  font-weight: 400 !important;
  font-size: calc(1em * var(--cb-ratio-accent-italic)) !important;
  color: inherit !important;
  text-transform: none !important;
  letter-spacing: normal !important;
}

/* Agencement du CONTENEUR — .cb-title-lockup(--left/--center)/.cb-title-inline
   posés sur un CONTAINER Elementor (`css_classes`, pas `_css_classes` — la
   classe atterrit ICI directement sur le nœud flex, contrairement au widget).
   Deux formes réelles possibles selon le réglage "content width" du
   container (relevé ci-dessus, frontend.css) : plein-large (flex direct sur
   le nœud) et boxed (flex sur un `.e-con-inner` enfant). Les deux sont
   couvertes — traduction mécanique des deux DOM réels, pas un choix de
   lequel un futur bloc emploiera. */
.cb-title-lockup.e-con-full,
.cb-title-lockup.e-con > .e-con-inner{
  display: flex !important;
  flex-direction: column !important;
  gap: var(--cb-flow-lockup) !important; /* 0 mesuré — docs/lockup-titres.md, DS62 */
}
.cb-title-lockup--left.e-con-full,
.cb-title-lockup--left.e-con > .e-con-inner{
  align-items: flex-start !important;
  text-align: left !important;
}
.cb-title-lockup--center.e-con-full,
.cb-title-lockup--center.e-con > .e-con-inner{
  align-items: center !important;
  text-align: center !important;
}
.cb-title-inline.e-con-full,
.cb-title-inline.e-con > .e-con-inner{
  display: flex !important;
  align-items: center !important;
  gap: var(--cb-space-gap-mobile) !important;
}
/* DS29 — même correction que atoms.css .cb-title-inline (voir son commentaire
   pour la mesure et l'arbitrage) : le jumeau doit rester indiscernable du
   markup neutre, donc le même wrap contrôlé, au même seuil. */
@media (max-width: 767px){
  .cb-title-inline.e-con-full,
  .cb-title-inline.e-con > .e-con-inner{
    flex-direction: column !important;
    align-items: flex-start !important;
  }
}


/* ===========================================================================
 * 4. FAMILLE IMAGE — .cb-img / .cb-img--3-2 / .cb-img--2-3 (DS25)
 * Widget `image`. Même piège que le bouton/titre (§ tête de fichier,
 * relevé DS24) : `_css_classes` atterrit sur `.elementor-element`
 * (l'EXTÉRIEUR, docs/elementor.md § 2.1), jamais sur l'`<img>` réel, nesté
 * sous `.elementor-widget-container` — parfois derrière un `<a>` quand
 * l'image est liée (logo `bd28c62`). Toute propriété visuelle cible donc
 * `img` en DESCENDANT ; le sélecteur traverse un `<a>` intermédiaire sans
 * avoir besoin de le nommer, comme tout descendant CSS.
 *
 * Adversaire réel relevé (DS24, `post-8422.css`, id `e786e0f`) :
 *   .elementor-8422 .elementor-element.elementor-element-e786e0f img
 *     { border-radius:5px; }
 * → 3 classes + élément, AUCUN `!important`. Notre règle gagne par
 * `!important` seul (riposte docs/elementor.md § 3.2 / strategie-
 * implementation décision 4), même à spécificité nominale plus faible
 * (1 classe + élément) — gardée par précaution même si l'adversaire réel
 * n'en a pas besoin ici (même logique que § 1/§ 2 ci-dessus).
 * ========================================================================= */
.cb-img img{
  display: block !important;
  width: 100% !important;             /* remplit sa colonne — docs/images.md §2, reprise littérale d'atoms.css */
  height: auto !important;            /* jamais une hauteur en dur — dérivée de l'aspect-ratio ci-dessous */
  object-fit: cover !important;       /* jamais fill — docs/images.md §4.2/§7 */
  object-position: center !important; /* défaut — docs/images.md §3/§4.4, réglable par image au cas où */
  border-radius: var(--cb-radius-md) !important;
}
.cb-img--3-2 img{ aspect-ratio: 3 / 2 !important; }
.cb-img--2-3 img{ aspect-ratio: 2 / 3 !important; }
.cb-img--16-9 img{ aspect-ratio: 16 / 9 !important; }
.cb-img--15-17 img{ aspect-ratio: 15 / 17 !important; }

/* ---- --duo : le média d'une section DEUX COLONNES image/texte — DS65, décision
 * d'Alexandre du 2026-08-18. Le raisonnement est dans `tokens.css` ; ici, ce qui
 * est propre à Elementor.
 *
 * ⚠️ TROIS ADVERSAIRES MESURÉS, et c'est pour ça que ce bloc est plus long que
 * ses quatre voisins :
 *
 *   1. La HAUTEUR compilée par widget. Elementor écrit
 *      `.elementor-<post> .elementor-element-<id> img{ height: 500px }` — 17
 *      hauteurs distinctes relevées sur le site. Un `height: auto !important` sur
 *      `.cb-img img` (§ 4 ci-dessus) suffisait à les neutraliser ; il faut
 *      maintenant POSER une hauteur, donc écraser la leur.
 *   2. `object-fit: fill`, servi 11 fois, qui étire la photo hors de ses
 *      proportions. `docs/images.md` l'interdit déjà — le § 4 le corrige.
 *   3. Le CARROUSEL. 37 des 145 médias sont des `media-carousel`, dont l'image
 *      n'est PAS un `img` direct du widget mais `.swiper-slide-image` sous
 *      `.swiper-slide`. La classe ne peut pas se poser sur l'image : elle se pose
 *      sur le widget, et c'est le pont qui descend. La diapositive doit recevoir
 *      la hauteur AUSSI, sinon le swiper la calcule sur son contenu et la boîte
 *      reprend la hauteur d'avant.
 *
 * ⚠️ `.swiper-wrapper` reçoit `align-items: stretch` : sans lui, une diapositive
 * plus courte que ses voisines laisse un trou — le défaut du swiper est
 * `flex-start`. C'est le même piège que REG01 § 4 (un défaut de plugin qui n'est
 * pas celui qu'on croit), pris ici en prévention et non après coup. */
/* ⚠️ DEUX PIÈGES DU SKIN `slideshow`, tous deux payés au rendu le 2026-08-18, et
 * la capture les a montrés là où les nombres ne les disaient pas.
 *
 * (a) LE WIDGET NE DOIT PAS PORTER LA HAUTEUR. `.cb-img--duo` est un atome d'IMAGE :
 *     en markup neutre il se pose sur l'`<img>`. Sous Elementor il atterrit sur le
 *     WIDGET (docs/elementor.md § 2.1) — et un widget `media-carousel` en skin
 *     `slideshow` contient DEUX blocs empilés, le grand carrousel PUIS une bande de
 *     vignettes. Lui imposer 702 px lui donne la hauteur d'UN bloc pour un contenu
 *     de DEUX : le second débordait de 712 px, par-dessus la section suivante, et
 *     masquait son titre, ses boutons et son texte. Le widget reprend donc sa
 *     hauteur naturelle ; ce sont ses boîtes intérieures qui sont réglées.
 *
 * (b) LA BANDE DE VIGNETTES N'EST PAS LE MÉDIA DE LA SECTION. Elle porte
 *     `.elementor-thumbnails-swiper` — MAIS AUSSI `.elementor-main-swiper`, les deux
 *     sur le même nœud (mesuré, pas supposé). Une simple règle de remise à zéro
 *     posée AVANT ne pouvait donc pas gagner : à spécificité égale, c'est l'ordre
 *     des sources qui tranche, et elle perdait. On ne remet plus à zéro après coup —
 *     on EXCLUT la bande dans le sujet de chaque règle de hauteur. Une règle qui a
 *     besoin de l'ordre des sources pour être juste est une règle fausse.
 *
 * La leçon commune, et c'est la troisième fois dans cette passe : un widget n'a pas
 * UN DOM, il en a autant que de skins. */
.cb-img--duo.elementor-widget-media-carousel,
.cb-img--duo.elementor-widget-image-carousel,
.cb-img--duo.elementor-widget-slides{
  block-size: auto !important;
  height: auto !important;
  min-height: 0 !important;
  max-height: none !important;
  aspect-ratio: auto !important;
}

/* ⚠️ (c) TROISIÈME PIÈGE DU MÊME WIDGET, ET IL A FALLU L'ŒIL D'ALEXANDRE
 * (`REG14`, 2026-08-20 : « un slider a hérité de notre règle de hauteur mini
 * d'image à tort »). `.cb-img--duo` décrit le média d'une section DEUX
 * COLONNES : une diapositive par vue, ratio 3/4, plancher de 420 px. Posé sur
 * un carrousel en **coverflow** — plusieurs diapositives visibles, donc étroites
 * — le plancher gagne contre le ratio : mesuré, une diapositive de **160 px de
 * large sur 588 de haut**, l'image écrasée dans une meurtrière. Retirée la
 * classe, le même carrousel reprend 1092 × 450 avec des diapositives de 346.
 *
 * La classe a été retirée du seul nœud concerné (une table de mappage), mais on
 * garde AUSSI le garde ici : c'est un mappage automatique qui l'avait posée
 * (`REG01` volet C, « les médias de section 2 colonnes »), et rien n'empêche le
 * même geste de se répéter. Un atome qui ne peut pas s'appliquer à une forme
 * doit le dire lui-même, pas compter sur la table.
 * ⚠️ `swiper-coverflow` est posée par le SWIPER au moment de l'exécution, pas
 * par Elementor : elle décrit ce que le carrousel FAIT, ce qui est exactement le
 * critère utile — un carrousel qui montre plusieurs diapositives à la fois. */
.cb-img--duo img,
.cb-img--duo .swiper:not(.elementor-thumbnails-swiper):not(.swiper-coverflow) .swiper-slide-image,
.cb-img--duo .swiper:not(.elementor-thumbnails-swiper):not(.swiper-coverflow) .swiper-slide img{
  aspect-ratio: var(--cb-img-duo-ratio-mobile) !important;
  height: auto !important;
  width: 100% !important;
  object-fit: cover !important;
  object-position: center !important;
  border-radius: var(--cb-radius-md) !important;
}
.cb-img--duo .swiper:not(.elementor-thumbnails-swiper) > .swiper-wrapper{ align-items: stretch !important; }

/* ---- --duo-dessin : le dessin garde la boîte et cesse d'être recadré (REG02).
   Même retargeting que ses voisins : la classe atterrit sur le WIDGET, la
   propriété se pose sur l'`<img>`. La hauteur n'est PAS reprise ici — elle vient
   de `.cb-img--duo`, que le widget porte aussi. Un seul mot change. */
.cb-img--duo-dessin img,
.cb-img--duo-dessin .swiper:not(.elementor-thumbnails-swiper) .swiper-slide img{
  object-fit: contain !important;
  object-position: center !important;
}
/* ⚠️ ET LA BANDE DE VIGNETTES A BESOIN QU'ON LUI RENDE SA HAUTEUR — troisième et
   dernier temps du piège (a)/(b) ci-dessus, trouvé en COMPARANT à la production et
   pas en relisant le CSS : prod sert 510×120 sur les 8 bandes du site, la préprod
   servait 510×0. Ses vignettes sont des fonds (`div.elementor-carousel-image`), donc
   sans dimension propre : dès que la chaîne de hauteur au-dessus d'elles est
   relâchée, la bande disparaît. L'exclure des règles de média était juste ; la
   laisser sans hauteur ne l'était pas. C'est le même défaut que celui de cette passe
   entière — retirer un réglage rend la main à un défaut, et ici le défaut est zéro. */
.cb-img--duo .elementor-thumbnails-swiper,
.cb-img--duo .elementor-thumbnails-swiper > .swiper-wrapper > .swiper-slide,
.cb-img--duo .elementor-thumbnails-swiper .elementor-carousel-image{
  height: var(--cb-img-duo-vignette-h) !important;
}
/* ⚠️ MIROIR DE LA CORRECTION DU 2026-08-19 (voir atoms.css § .cb-img--duo) :
   la hauteur ne dépend plus de la HAUTEUR DE FENÊTRE. `--cb-img-duo-h` valait
   `clamp(420px, 78vh, 800px)` — la même image faisait 702 px en fenêtre haute et
   420 en fenêtre basse, à largeur identique. C'est le ratio, dérivé de la
   LARGEUR, qui donne la hauteur ; les deux bornes n'empêchent que l'absurde.
   ⚠️ LE SWIPER, LUI, A BESOIN D'UNE HAUTEUR RÉSOLUE : ses diapositives sont en
   position absolue et ne se dimensionnent pas sur leur contenu. On lui donne
   donc `aspect-ratio` AUSSI, plutôt qu'une hauteur — même source, même résultat,
   sans dépendance au viewport. */
@media (min-width: 1025px){
  .cb-img--duo img,
  .cb-img--duo .swiper:not(.elementor-thumbnails-swiper):not(.swiper-coverflow) .swiper-slide-image,
  .cb-img--duo .swiper:not(.elementor-thumbnails-swiper):not(.swiper-coverflow) .swiper-slide img{
    aspect-ratio: var(--cb-img-duo-ratio) !important;
    height: auto !important;
    min-height: var(--cb-img-duo-h-min) !important;
    max-height: var(--cb-img-duo-h-max) !important;
  }
  /* La diapositive et son conteneur suivent la même forme, sinon le swiper
     garde la sienne et l'image débordait ou se recadrait deux fois. */
  .cb-img--duo .swiper:not(.elementor-thumbnails-swiper):not(.swiper-coverflow),
  .cb-img--duo .swiper:not(.elementor-thumbnails-swiper):not(.swiper-coverflow) > .swiper-wrapper > .swiper-slide{
    aspect-ratio: var(--cb-img-duo-ratio) !important;
    height: auto !important;
    min-height: var(--cb-img-duo-h-min) !important;
    max-height: var(--cb-img-duo-h-max) !important;
  }
}

/* H02d/H03b — le markup reste celui des widgets Elementor, les compositions
   vivent dans les atomes. Ces règles ne font que traverser les wrappers réels. */
.cb-intro > .e-con-inner{ width:min(100%,var(--cb-container-max)) !important; max-width:var(--cb-container-max) !important; }
.cb-rooms-head > .e-con-inner,
.cb-rooms > .e-con-inner{ width:100% !important; max-width:none !important; margin-inline:0 !important; align-self:stretch !important; }
.cb-intro__lockup,.cb-intro__details,.cb-intro__brochure,.cb-rooms-head .elementor-widget{ width:100%; }
.cb-intro__title .elementor-heading-title,.cb-rooms-head__title .elementor-heading-title{ color:inherit !important; font:inherit !important; text-transform:inherit !important; text-align:inherit !important; }
.cb-intro__script .elementor-heading-title,.cb-rooms-head__script .elementor-heading-title{ color:inherit !important; font:inherit !important; text-transform:inherit !important; text-align:inherit !important; }
.cb-intro__image .elementor-widget-container{ width:100%; }
.cb-intro__tags,.cb-intro__actions{ width:100% !important; }
.cb-intro__tags > .elementor-element,.cb-intro__actions > .elementor-element,.cb-rooms__actions > .elementor-element{ width:auto !important; }
/* centré — sa demande du 2026-08-20, qui revient sur celle du 2026-08-18 pour ce
   bloc précis. Les deux arbitrages et ce qui les réconcilie sont écrits en
   couche 2 : `atoms.css`, § `.cb-intro__actions`. */
.cb-intro__actions.e-con-full,.cb-intro__actions > .e-con-inner{ --justify-content:center; display:flex !important; flex-direction:row !important; flex-wrap:wrap !important; justify-content:center !important; gap:var(--cb-flow-actions) !important; }
.cb-intro__actions > .e-con-inner > .elementor-element{ width:auto !important; }
.cb-intro__brochure > .e-con-inner{ position:relative !important; z-index:1 !important; min-height:inherit; display:flex; align-items:end; justify-content:center; }
.elementor-element.cb-rooms{ padding:0 !important; }
/* `flex:1` n'est pas une décision de mise en page : c'est ce qui fait
   descendre la hauteur réservée par l'atome (`min-height` sur `.cb-rooms`)
   jusqu'au wrapper qui porte réellement la colonne. Sans lui, le conteneur
   Elementor garde sa hauteur de contenu et les CTA remontent à chaque
   ouverture de chambre. */
.cb-rooms > .e-con-inner{ display:flex !important; flex-direction:column !important; flex:1 1 auto !important; gap:0 !important; padding:var(--cb-rooms-padding) !important; }
.cb-rooms__item > .e-con-inner{ display:block; width:100%; }
/* Les images de chambres restent éditables dans les fonds Elementor de chaque
   item, puis l'explorateur lit cette source pour la projeter une seule fois
   dans le panneau média de droite. Un voile opaque masque donc l'ancien fond
   de la carte sans le supprimer : le JS garde accès à l'asset éditorial. */
.elementor-element.cb-rooms__item{ background-size:0 0 !important; background-repeat:no-repeat !important; }
.cb-rooms__item::before{ content:""; position:absolute; inset:0; z-index:0; pointer-events:none; background:var(--cb-ivoire); }
.cb-rooms__item > *{ position:relative; z-index:1; }
.cb-rooms__item .uael-heading-wrapper{ margin:0 !important; }
.cb-rooms__item .uael-sub-heading{ display:none !important; }
.cb-rooms__item .uael-heading-text{ margin:0 !important; }
.cb-rooms__item .cb-rooms__label{ display:flex; }
.cb-rooms__item .cb-rooms__details{ width:100%; }
.cb-rooms__item .cb-rooms__details p{ margin:0; color:var(--cb-encre) !important; }
.cb-rooms__item .elementor-widget-button{ width:auto; }
.cb-rooms__item .elementor-button-wrapper{ text-align:left; }
.cb-rooms__item .elementor-button{ width:auto !important; }

/* DS62 — les classes restent sur les wrappers Elementor ; le pont ne crée pas
   de rythme mais propage la recette neutre jusqu'aux nœuds réellement peints.
   Elementor rend indifféremment un container « full » ou son `.e-con-inner` :
   les deux formes sont couvertes pour que la composition ne dépende pas du
   réglage historique du nœud. */
.cb-events.e-con-full,.cb-events > .e-con-inner,
.cb-events__content.e-con-full,.cb-events__content > .e-con-inner,
.cb-events__lead.e-con-full,.cb-events__lead > .e-con-inner{ width:100% !important; max-width:none !important; }
.cb-events__lockup.e-con-full,.cb-events__lockup > .e-con-inner{ display:flex !important; flex-direction:column !important; align-items:center !important; gap:var(--cb-flow-lockup) !important; }
.cb-events__actions.e-con-full,.cb-events__actions > .e-con-inner{ display:flex !important; flex-direction:row !important; flex-wrap:wrap !important; justify-content:center !important; gap:var(--cb-flow-actions) !important; }
/* Miroir de l'écart boutons → paragraphe (`DS71`). La cause était une collision
   d'ordre en couche 2 (`.cb-paragraph`, déclaré plus bas, remettait `margin-top` à 0
   par son raccourci) et elle est corrigée là-bas en qualifiant la composition. On la
   garantit ici parce que c'est le pont qui répond du DOM Elementor : rien dans
   Elementor ne pose de marge sur ce widget aujourd'hui (vérifié sur `post-8422.css`,
   qui ne compile qu'un `padding` sur son `.elementor-widget-container`), mais c'est
   une absence constatée, pas une garantie. */
.cb-paragraph.cb-events__copy{ margin-top: var(--cb-flow-group) !important; }

/* .cb-tag-row dans le DOM Elementor (DS69). Le container porte la classe, mais
   c'est son `.e-con-inner` qui est le vrai parent flex quand il est *boxed* — et
   Elementor compile pour chaque container un `--justify-content` en variable, qu'une
   simple déclaration ne bat pas. On écrase donc la variable ET la propriété. */
/* ⚠️ `flex-direction: row` N'EST PAS DÉCORATIF ICI, et son absence a cassé le site
   le 2026-08-18 : la migration vide `flex_direction` du container (c'est un réglage
   local sur un porteur `.cb-*`), et **le défaut d'Elementor pour un container est
   `column`**. Résultat mesuré sur `/spa/` : 4 rangées de pastilles sur 5 empilées
   verticalement, une pastille par ligne. C'est le piège de `docs/pieges.md` § 7 pris
   une troisième fois — vider un champ rend la main à un défaut, et ce défaut peut
   être le contraire de ce qu'on veut. La règle doit donc AFFIRMER la direction, pas
   la supposer. On écrase la variable ET la propriété : Elementor compile la mise en
   page de chaque container en variable CSS. */
/* ⚠️ ET LA MÊME OMISSION VIVAIT DANS SIX AUTRES ATOMES DE RANGÉE, découverte le
   2026-08-18 par le recensement REG01 : `.cb-intro__tags` (la section
   d'introduction de l'accueil, celle qu'Alexandre a montrée) et `.cb-card__tags`
   ×6 s'étaient empilées pour exactement la même raison. Elles sont ajoutées au
   sujet ci-dessous plutôt que traitées à part : c'est UN défaut, pas sept. Les
   atomes de couche 2 affirment `flex-direction: row` de leur côté — ici on écrase
   en plus la VARIABLE, ce que la couche 2 ne peut pas faire. */
.cb-intro__tags.e-con,
.cb-intro__tags > .e-con-inner,
.cb-card__tags.e-con,
.cb-card__tags > .e-con-inner,
.cb-content-hero__tags.e-con,
.cb-content-hero__tags > .e-con-inner,
.cb-tag-row.e-con,
.cb-tag-row > .e-con-inner{
  --flex-direction: row;
  --justify-content: flex-start;
  --flex-wrap: wrap;
  display: flex !important;
  flex-direction: row !important;
  flex-wrap: wrap !important;
  justify-content: flex-start !important;
  align-content: start !important;
  gap: var(--cb-flow-inline) !important;
}

/* .cb-flow--group dans le DOM Elementor (`REG11`). On ne pose QUE le `gap` :
   forcer `flex-direction` casserait les conteneurs qui passent en ligne à partir
   d'un breakpoint, et le sujet du ticket est l'écart, pas l'agencement.
   Le `!important` est nécessaire — Elementor compile le `flex_gap` du conteneur
   en `--gap` sur `.e-con`, et une déclaration sans `!important` perdrait contre
   lui tant qu'un réglage local subsiste quelque part. */
.cb-flow--group.e-con,
.cb-flow--group.e-con-full,
.cb-flow--group.e-con > .e-con-inner{
  gap: var(--cb-flow-group) !important;
}
/* même nuance qu'en couche 2, et il faut viser les DEUX formes de conteneur :
   `e-con-boxed` range ses enfants sous `.e-con-inner`, `e-con-full` non. */
.cb-flow--group > .cb-heading + *:not(.cb-tag-row):not(.cb-flow--lockup),
.cb-flow--group > .e-con-inner > .cb-heading + *:not(.cb-tag-row):not(.cb-flow--lockup){
  margin-top: calc(var(--cb-flow-text) - var(--cb-flow-group)) !important;
}

/* .cb-flow--lockup dans le DOM Elementor (`REG13`) — raisonnement en couche 2.
   Les deux formes de container, comme au-dessus : `e-con-full` porte ses enfants
   directement, `e-con-boxed` les range sous `.e-con-inner`. */
.cb-flow--group > .cb-flow--lockup,
.cb-flow--group > .e-con-inner > .cb-flow--lockup{
  margin-top: calc(var(--cb-flow-lockup) - var(--cb-flow-group)) !important;
}
.cb-flow--group > .cb-flow--lockup + *:not(.cb-tag-row),
.cb-flow--group > .e-con-inner > .cb-flow--lockup + *:not(.cb-tag-row){
  margin-top: calc(var(--cb-flow-text) - var(--cb-flow-group)) !important;
}

/* .cb-flow--actions dans le DOM Elementor (`REG13`). Ici, contrairement à
   `.cb-flow--group`, on DOIT forcer `flex-direction` : le défaut d'un container
   Elementor est `column`, et c'est très exactement le défaut à réparer — deux
   CTA empilés au lieu d'être côte à côte. Les deux formes de container sont
   visées, `e-con-full` porte ses enfants, `e-con-boxed` les range sous
   `.e-con-inner`. `--flex-direction` est reposée en plus de la propriété :
   Elementor compile ses réglages responsive dans cette variable, et la laisser
   à `column` ferait revenir l'empilement au premier breakpoint. */
.cb-flow--actions.e-con-full,
.cb-flow--actions.e-con > .e-con-inner{
  --flex-direction: row;
  --flex-wrap: wrap;
  --align-items: center;
  display: flex !important;
  flex-direction: row !important;
  flex-wrap: wrap !important;
  align-items: center !important;
  gap: var(--cb-flow-actions) !important;
}
.cb-align--center .cb-flow--actions.e-con-full,
.cb-align--center .cb-flow--actions.e-con > .e-con-inner{
  --justify-content: center;
  justify-content: center !important;
}

/* La rangée CENTRÉE — pour les sections qui présentent des arguments autour d'un
   axe (demande d'Alexandre, 2026-08-18 : « on doit laisser centré dans ces cas là
   les titres paragraphes boutons et stickers »). Un enfant ne peut pas hériter en
   CSS de l'alignement de son parent : il faut donc un modificateur explicite. */
.cb-tag-row--center.e-con,
.cb-tag-row--center > .e-con-inner{
  --justify-content: center;
  justify-content: center !important;
}

/* .cb-align--center dans le DOM Elementor. Une seule classe sur le container de
   section suffit pour les titres et la prose (`text-align` hérite) ; les rangées en
   flex doivent être nommées, `justify-content` n'héritant pas. Le `!important` est
   nécessaire : Elementor compile un `text-align` par widget dès qu'un `align` est
   réglé, et certains widgets non migrés en gardent un. */
.cb-align--center,
.cb-align--center > .e-con-inner{
  text-align: center !important;
}
/* ⚠️ Il faut viser le WIDGET, pas seulement son conteneur interne : Elementor
   compile son `text-align` sur `.elementor-element` (le div du widget) dès qu'un
   `align` est réglé — et les widgets NON migrés en gardent un. Mesuré sur `/hotel/`
   après une première tentative : 14 sections marquées centrées dont le titre, la
   prose ou le bouton restaient à `start`, parce que la règle ne descendait qu'au
   `.elementor-widget-container`. */
.cb-align--center .elementor-element,
.cb-align--center .elementor-widget,
.cb-align--center .elementor-widget-container,
.cb-align--center .elementor-heading-title,
.cb-align--center .uael-heading,
.cb-align--center .uael-heading-text,
.cb-align--center .cb-heading,
.cb-align--center .cb-paragraph,
.cb-align--center .cb-list,
.cb-align--center .elementor-button-wrapper{
  text-align: center !important;
}
/* la prose bornée par la mesure se recentre dans sa colonne, sinon elle reste
   collée à gauche malgré le `text-align` (DS71 lui a rendu son `max-width`) */
.cb-align--center .cb-paragraph,
.cb-align--center .cb-list{
  margin-inline: auto !important;
}
.cb-align--center .cb-tag-row.e-con,
.cb-align--center .cb-tag-row > .e-con-inner,
.cb-align--center .cb-intro__tags.e-con,
.cb-align--center .cb-intro__tags > .e-con-inner,
.cb-align--center .cb-card__tags.e-con,
.cb-align--center .cb-card__tags > .e-con-inner,
.cb-align--center .cb-content-hero__tags.e-con,
.cb-align--center .cb-content-hero__tags > .e-con-inner{
  /* ⚠️ Obligatoire dès que les six atomes de rangée ont rejoint le sujet du § 4
     (affirmation de `row`) : cette affirmation porte `justify-content: flex-start
     !important`, qui battrait le centrage de la couche 2. Une règle qui gagne doit
     prévoir son exception, sinon elle en crée une. */
  --justify-content: center;
  justify-content: center !important;
}
/* ⚠️ ET LA FRATRIE DOIT SUIVRE — REG02 § F, trouvé le 2026-08-18 sur ses captures
   de `/seminaire/team-building/`, `/seminaire/salles-de-reunion/` et
   `/restauration/`. Mesuré, dans une colonne dont les blocs sont centrés :

       7363b8d   cb-align--center   textAlign=center
       17bd7008  cb-tag-row         textAlign=start     ← FRÈRE, pas descendant
       38a40044  cb-align--center   textAlign=center

   La rangée de pastilles n'est pas SOUS un bloc centré, elle est À CÔTÉ. Aucune
   règle en `.cb-align--center .cb-tag-row` ne pouvait donc l'atteindre : six
   rangées dans ce cas sur team-building, d'autres ailleurs. C'est le défaut que
   le commentaire de `atoms.css` annonçait sans le corriger — « LE CENTRAGE EST
   UNE DÉCISION DE SECTION » — parce que le générateur pose la classe sur les
   blocs de texte, pas sur leur parent commun.

   `:has()` lit la fratrie sans qu'on ait à réécrire quoi que ce soit dans le
   JSON : un conteneur qui abrite un bloc centré centre aussi ses rangées et ses
   contrôles. Le pont emploie déjà `:has()` au § 5 pour l'ordre du lockup — ce
   n'est pas un mécanisme neuf ici.

   ⚠️ Portée volontairement étroite : les RANGÉES et les WIDGETS DE CONTRÔLE,
   pas tout ce qui traîne. Centrer une image ou une colonne parce qu'un titre
   voisin est centré déborderait du défaut. */
.e-con:has(> .cb-align--center) > .cb-tag-row.e-con,
.e-con:has(> .cb-align--center) > .cb-tag-row > .e-con-inner,
.e-con-inner:has(> .cb-align--center) > .cb-tag-row.e-con,
.e-con-inner:has(> .cb-align--center) > .cb-tag-row > .e-con-inner{
  --justify-content: center;
  justify-content: center !important;
}
.e-con:has(> .cb-align--center) > .elementor-widget-button,
.e-con-inner:has(> .cb-align--center) > .elementor-widget-button{
  justify-content: center !important;
  text-align: center !important;
}

/* ⚠️ ET IL FAUT LE DIRE AUX WIDGETS EUX-MÊMES, pas seulement aux rangées — 118
   pastilles décentrées mesurées le 2026-08-18, sur 20 pages. Notre § 2 rend le
   wrapper d'un widget bouton `display: flex` (c'est ce qui permet à `.cb-tag` de
   centrer son libellé verticalement) ; or sur un conteneur flex, `text-align` ne
   positionne RIEN. La production laisse ces wrappers en `display: block`, où
   l'ancre `inline-block` est centrée par `text-align` — d'où l'écart, invisible
   jusqu'à ce qu'on mesure l'ANCRE et non le wrapper (qui, lui, occupe toute la
   colonne : son centre est toujours celui de la colonne).

   `justify-content` seul, jamais `align-items` : l'axe transversal porte le
   centrage vertical du libellé, y toucher casserait `.cb-tag`. */
.cb-align--center .elementor-widget,
.cb-align--center .elementor-widget > .elementor-widget-container,
.cb-align--center .elementor-button-wrapper{
  justify-content: center !important;
}

.cb-events__title .elementor-heading-title,.cb-events__script .elementor-heading-title{ margin:0 !important; color:inherit !important; font:inherit !important; text-transform:inherit !important; text-align:inherit !important; }


/* ===========================================================================
 * 5. FAMILLE TITRE — `.cb-title-lockup` sur `uael-advanced-heading` (DS26)
 *
 * TICKET DS26 : décider si on garde ce widget (plugin Ultimate Elementor,
 * `ultimate-elementor` 1.45.0, actif) pour composer un lockup titre+script,
 * malgré l'ordre DOM signalé inversé par DS24 sur UNE occurrence.
 *
 * RELEVÉ (mesuré, pas déduit — préprod, 2026-08-10) :
 * - **53 occurrences réelles**, pas 8 (chiffre homepage seul de DS24) : sur
 *   16 des 25 pages publiées + 0 sur les 8 templates Theme Builder (header
 *   8376, footer 8372, 2 popups 18601/8344, archive 8363, single 15876/8338,
 *   floating_buttons 5390). Compté par
 *   `wp post meta get <id> _elementor_data --format=json | grep -o
 *   "uael-advanced-heading" | wc -l` sur chaque id — vérifié égal au compte
 *   de nœuds `widgetType` réel après double `json.loads()` (8/8 sur la
 *   homepage, DS24).
 * - **L'ordre N'EST PAS figé dans le widget : c'est un réglage par
 *   occurrence**, le contrôle `subheading_position` (`SELECT`, options
 *   `top`="Above Heading" / `bottom`="Below Heading", **défaut `bottom`**
 *   — relevé dans `wp-content/plugins/ultimate-elementor/modules/headings/
 *   widgets/advanced-heading.php:167-181`). Le PHP de rendu (méthode
 *   `render()`, mêmes fichier, lignes ~1702-1716) appelle
 *   `render_subheading('top', …)` AVANT le `<hX>` et `render_subheading(
 *   'bottom', …)` APRÈS — chacune ne produit du DOM que si `$pos ===
 *   $settings['subheading_position']` : l'ordre bouge VRAIMENT dans le
 *   markup source, pas par un habillage CSS du plugin.
 * - **Confirmé au DOM réel** (`curl` sur la page rendue, pas la doc du
 *   plugin) : id `60b1d83` (accueil `8422`, `subheading_position:"top"`)
 *   → `.uael-heading-wrapper` contient `.uael-sub-heading` PUIS `h2.uael-
 *   heading` ; id `0451aff` (page `15972`, `subheading_position` absent =
 *   défaut `bottom`) → `h2.uael-heading` PUIS `.uael-sub-heading`. Les deux
 *   DOM sont réels, aucun n'est supposé.
 * - **Sur les 45 occurrences où titre ET script sont non vides** (donc où
 *   l'ordre est visible) : 18 en `bottom` (= ordre de `.cb-title-lockup`,
 *   déjà identique) et 27 en `top` (= inversé). ⚠️ La majorité RÉELLE est
 *   `top`, pas `bottom` — ne pas assumer que le défaut du contrôle domine
 *   l'usage réel.
 *
 * CONSÉQUENCE CSS DE L'ORDRE INVERSÉ — vérifiée AU RENDU (Chrome Canary,
 * `--user-data-dir=/tmp/cdp-canary-ds26`, port 9493 ; les deux DOM réels
 * ci-dessus rejoués tels quels, pas un DOM supposé) :
 * - `flex-direction: column-reverse`, déclenché seulement quand le sélecteur
 *   structurel `:has(> .uael-sub-heading + .uael-heading)` matche (donc
 *   seulement sur le DOM `top`, jamais sur `bottom`), suffit à remettre le
 *   titre visuellement AU-DESSUS du script dans les DEUX cas — AUCUNE classe
 *   ajoutée par ce pont ne dépend du réglage `subheading_position`, qui
 *   n'est de toute façon lisible par aucun sélecteur CSS (rien dans le DOM
 *   ne le reflète). `:has()` imbriqué (`:has(...:has(...))`) est INVALIDE en
 *   CSS (`SyntaxError`, testé) — d'où le sélecteur adjacent `+` retenu,
 *   robuste même si un séparateur précède le script (testé, cas
 *   hypothétique : aucune des 53 occurrences réelles n'a
 *   `heading_separator_position:"top"`).
 * - **Limite trouvée au rendu, pas anticipée en théorie** : 48 des 53
 *   occurrences ont `heading_separator_style:"line"` (position `center` par
 *   défaut — jamais `top` dans le relevé). Un séparateur est un 3ᵉ enfant du
 *   même conteneur flex. Quand `column-reverse` se déclenche (cas `top`),
 *   il inverse TOUT le conteneur, pas seulement la paire titre/script : le
 *   séparateur atterrit visuellement EN HAUT du bloc au lieu d'après le
 *   titre — testé (26 des 45 occurrences "les deux textes remplis" combinent
 *   `top` + séparateur `line`, donc ce n'est pas un cas rare). Ce n'est
 *   PAS un coût supplémentaire de cette règle : la classe `.cb-*` exige déjà
 *   par ailleurs des champs Elementor vides (règle non négociable,
 *   `docs/elementor.md` § 3.3) — un widget qui adopte `.cb-title-lockup`
 *   doit désactiver son séparateur (`heading_separator_style:"none"`) de
 *   toute façon, l'atome n'en spécifie aucun (`docs/lockup-titres.md`).
 *   Sans séparateur ni description (52/53 occurrences ont déjà
 *   `show_description:"no"`), aucun 3ᵉ enfant ne reste à déplacer.
 *
 * DÉCISION : ON GARDE ce widget pour `.cb-title-lockup` (pas `.cb-title-
 * inline`, hors mandat DS26). 53 occurrences réelles rendent le
 * remplacement par un `heading` standard coûteux (restructurer l'arbre de
 * 53 widgets, pas un JSON transformable en masse — H02 a déjà noté que
 * `A02`/décision 5 autorise "poser une classe + vider des champs", pas
 * "ajouter des nœuds") pour un gain qui n'existait déjà plus une fois
 * l'ordre confirmé configurable et rattrapable en CSS pur, sans toucher au
 * markup.
 * ========================================================================= */
.cb-title-lockup .uael-heading-wrapper{
  display: flex !important;
  flex-direction: column !important;
  gap: var(--cb-flow-lockup) !important; /* 0 mesuré — même relation que atoms.css, DS62 */
}
/* le sélecteur structurel ci-dessus, PAS une classe, détecte l'ordre RÉEL du
   DOM (`subheading_position` n'est reflété par aucune classe/attribut) —
   vérifié au rendu, voir tête de section. */
.cb-title-lockup .uael-heading-wrapper:has(> .uael-sub-heading + .uael-heading){
  flex-direction: column-reverse !important;
}
.cb-title-lockup--left .uael-heading-wrapper{
  align-items: flex-start !important;
  text-align: left !important;
}
.cb-title-lockup--center .uael-heading-wrapper{
  align-items: center !important;
  text-align: center !important;
}

.cb-title-lockup .uael-heading{
  margin: 0 !important;
}
.cb-title-lockup .uael-heading-text{
  font-family: var(--cb-font-caps) !important;
  font-weight: var(--cb-weight-title) !important; /* Q27 — 200, tout le titrage, décidé le 2026-08-12 */
  text-transform: none !important; /* aucune règle Astra/kit relevée ne mettait .uael-heading en capitales (contrairement à .elementor-heading-title, DS24 § tête de fichier) — neutralisé quand même par précaution, même logique que le reste du pont */
  color: var(--cb-lockup-titre) !important;
  line-height: 1.5 !important;
}
.cb-title-lockup--left .uael-heading-text,
.cb-title-lockup--center .uael-heading-text{ font-size: var(--cb-lockup-title-size) !important; }

.cb-title-lockup .uael-sub-heading{
  margin: 0 !important;
  font-family: var(--cb-font-script) !important;
  font-weight: 400 !important;
  text-transform: none !important;
  line-height: 1 !important;
  color: var(--cb-terracotta) !important; /* accent EMPILÉ = terracotta — docs/lockup-titres.md § 3, même parti pris que .cb-title-lockup__script */
}
.cb-title-lockup--left .uael-sub-heading,
.cb-title-lockup--center .uael-sub-heading{ font-size: calc(var(--cb-lockup-title-size) * var(--cb-lockup-script-ratio)) !important; }

/* ===========================================================================
 * 7. FAMILLE TITRE AUTONOME — .cb-heading (DS09)
 * Widget `heading`, HORS lockup (H02 § « Hors périmètre déclaré » : le
 * widget d876828, « BIENVENUE AU DOMAINE DE CAMBOYER », resté sans atome).
 * Même piège que § 2 : `_css_classes` atterrit sur `.elementor-element`,
 * le texte réel est `.elementor-heading-title` nesté sous
 * `.elementor-widget-container`. Adversaire réel identique à celui déjà
 * relevé pour `776e36e`/`d876828` (tête de fichier, § TITRE) :
 *   .elementor-8422 .elementor-element.elementor-element-776e36e
 *     .elementor-heading-title{font-size:clamp(30px, 2.6vw, 55px);
 *     font-weight:500;text-transform:uppercase;line-height:1.1em;
 *     color:var(--e-global-color-astglobalcolor4);}
 * → 4 classes, aucun `!important`. Notre riposte (déjà décidée § 2/tête de
 * fichier) : spécificité doublée + `!important` sur toutes les propriétés.
 * ========================================================================= */
.cb-heading .elementor-heading-title{
  margin: 0 !important;
  font-family: var(--cb-font-caps) !important;
  font-weight: var(--cb-weight-title) !important; /* Q27 — 200, tout le titrage, décidé le 2026-08-12 */
  text-transform: uppercase !important;
  line-height: 1.5 !important;
  color: var(--cb-encre) !important;
}
.cb-heading--h2 .elementor-heading-title{ font-size: var(--cb-text-h2) !important; }
.cb-heading--h3 .elementor-heading-title{ font-size: var(--cb-text-h3) !important; }

/* .cb-heading--h1 — H01. TROIS propriétés à reprendre et non une : le palier h1
   ne change pas seulement de taille, il change de GRAISSE et d'INTERLIGNE, donc
   les trois valeurs posées par `.cb-heading` juste au-dessus doivent être
   écrasées ici. Sans ces règles, le jumeau Elementor du h1 retombe sur le kit —
   mesuré par `compare-jumeaux.py` avant correction : 37,44px / 300 / 56,16px là
   où l'atome dit 32px / 200 / 38,4px. C'est exactement la dette que le
   comparateur existe pour attraper. */
.cb-heading--h1 .elementor-heading-title{
  font-weight: var(--cb-weight-display) !important;
  font-size: var(--cb-text-title-hero) !important;
  line-height: 1.2 !important;
}
.cb-heading--h1 .uael-heading-text{
  font-weight: var(--cb-weight-display) !important;
  font-size: var(--cb-text-title-hero) !important;
  line-height: 1.2 !important;
}

/* .cb-heading--on-dark — DS12, même retargeting que ci-dessus. */
.cb-heading--on-dark .elementor-heading-title{ color: var(--cb-ivoire) !important; }

/* ---------------------------------------------------------------------------
 * DS34 — le MÊME atome, posé sur un `uael-advanced-heading`.
 *
 * `DS26` (§ 5) n'avait bridgé ce widget que pour `.cb-title-lockup`, c'est-à-
 * dire pour le cas « titre ET script dans le même widget ». `H03` a buté sur
 * l'autre cas : un `uael-advanced-heading` qui ne porte QU'UN titre. Sa classe
 * `.cb-heading` atterrissait sur `.elementor-element` et n'atteignait rien —
 * le texte réel n'est pas `.elementor-heading-title` mais
 * `span.uael-heading-text`, nesté sous `h2.uael-heading`.
 *
 * MESURÉ sur la préprod (`curl` sur la page rendue + `post-8422.css`,
 * 2026-08-12), pas déduit — les 8 `uael-advanced-heading` de l'accueil se
 * répartissent en DEUX familles disjointes, et aucune n'est un lockup :
 *   - 4 ne remplissent QUE `.uael-sub-heading` (`35b9ca8` "Charme",
 *     `c42024b` "Prestige", `60b1d83` "Élégance", `5d82d23` "Camboyer") —
 *     `.uael-heading-text` y est VIDE. C'est le nom de chambre, en script.
 *     → couvert plus bas par `.cb-section__accent` (§ 9).
 *   - 4 ne remplissent QUE `.uael-heading-text` (`429e185` "MARIAGES &
 *     FÊTES", `758db05`, `cfec2c9`, `9ebb332`), sans sous-titre.
 *     → c'est ce bloc-ci.
 * Donc la répartition est nette : lockup → `.cb-title-lockup` (§ 5), titre
 * seul → `.cb-heading`, script seul → `.cb-section__accent`. Aucun atome
 * nouveau, aucune décision de design : les mêmes recettes, un autre DOM.
 *
 * ADVERSAIRE RÉEL, relevé dans `post-8422.css` :
 *   .elementor-8422 .elementor-element.elementor-element-35b9ca8
 *     .uael-heading-text{ color:var(--e-global-color-astglobalcolor5); }
 *   … .elementor-element-429e185 .uael-heading{ font-size:30px;
 *     font-weight:500; }   (+ 25px et 20px aux paliers responsive)
 * → 4 classes, AUCUN `!important` : le `!important` de nos règles suffit,
 * même riposte que partout ailleurs dans ce fichier.
 *
 * ⚠️ La taille est posée sur `.uael-heading-text` et non sur `.uael-heading`
 * — c'est le nœud que l'adversaire vise en dernier ressort, et c'est déjà le
 * choix de `.cb-title-lockup` (§ 5). `.uael-heading` ne reçoit que sa marge,
 * qu'aucune couche 2 ne décrit (c'est du bruit de widget, pas du design).
 * ------------------------------------------------------------------------ */
.cb-heading .uael-heading{
  margin: 0 !important;
}
.cb-heading .uael-heading-text{
  margin: 0 !important;
  font-family: var(--cb-font-caps) !important;
  font-weight: var(--cb-weight-title) !important; /* Q27 — 200, tout le titrage, décidé le 2026-08-12 */
  text-transform: uppercase !important;
  line-height: 1.5 !important;
  color: var(--cb-encre) !important;
}
.cb-heading--h2 .uael-heading-text{ font-size: var(--cb-text-h2) !important; }
.cb-heading--h3 .uael-heading-text{ font-size: var(--cb-text-h3) !important; }
.cb-heading--on-dark .uael-heading-text{ color: var(--cb-ivoire) !important; }

/* ⚠️ ET LE FRÈRE SCRIPT DOIT ÊTRE RENDU À LUI-MÊME — REG01 § 2, 62 occurrences.
 *
 * Le relevé de `DS34` ci-dessus décrivait deux familles DISJOINTES : soit le
 * widget porte un titre seul, soit un script seul. Il décrivait l'accueil, et
 * l'accueil seulement. Les pages chambres ont une TROISIÈME famille, que la
 * migration du 2026-08-18 a rencontrée : les deux champs remplis en même temps —
 * `.uael-sub-heading` = « Charme » en DianaWebber Script, et `.uael-heading-text`
 * = « Les Chambres » en Fonseca, sous le même widget.
 *
 * `.cb-heading` posée sur le widget entier descend alors par HÉRITAGE sur
 * l'accent script : capitales et graisse 200. Mesuré, prod contre préprod, sur
 * `256cd6c` / `5b724ad4` / `dd802b6` :
 *
 *     production      DianaWebber Script  80px  400  text-transform: none
 *     préprod avant   DianaWebber Script  80px  200  text-transform: UPPERCASE
 *
 * ⚠️ LA TAILLE N'AVAIT PAS BOUGÉ — 80px des deux côtés. Ce qu'Alexandre a vu
 * comme « immense » EST la mise en capitales : les capitales d'une anglaise sont
 * bien plus hautes et plus larges que ses bas-de-casse. Diagnostiquer « la taille
 * a changé » aurait envoyé le correctif au mauvais endroit.
 *
 * C'est sa règle du 2026-08-18 prise à la lettre : « la Fonseca est une font qui
 * est en permanence en uppercase ; pour tout autre titre du site qui n'est pas en
 * Fonseca → on ne touche pas. » On ne POSE donc rien ici — on REND : la casse
 * naturelle et le poids de fonte. La famille et la couleur ne sont pas touchées,
 * le widget les porte et elles sont justes (mesuré identique des deux côtés).
 *
 * Le § 5 tient déjà exactement cette ligne pour `.cb-title-lockup`
 * (`text-transform: none !important` sur `.uael-heading-text`, plus haut dans ce
 * fichier) : ce n'est pas une exception neuve, c'est la même règle sur l'autre
 * moitié du widget. */
.cb-heading .uael-sub-heading{
  text-transform: none !important;
  font-weight: var(--cb-weight-script) !important;
  /* ⚠️ ET SON ÉCART AU TITRE ÉTAIT FAUX — signalé par Alexandre le 2026-08-18 sur
     le bloc « Premium / Vos services » : « on a la typo script avant la typo de
     titre, du coup l'écart est trop grand ». Mesuré sur `526286e` : le script est
     en `font-size: 60px` avec un `line-height: 90px` et une `margin: 15px 0`. Sa
     boîte fait donc 90 px pour un glyphe d'environ 47 — 43 px de blanc que
     personne n'a demandés — plus 15 px de marge par-dessus.
     Le système a déjà tranché ce couple : `--cb-flow-lockup` vaut **0**, mesuré
     sur deux frames (`40000245:1362`, `40000253:1436`), et
     `.cb-title-lockup__script` est en `line-height: 1` — « l'espace perçu relève
     de l'interligne, pas d'une marge locale » (tokens.css). Cette règle-là ne
     valait que pour `.cb-title-lockup` ; le même script sous `.cb-heading` n'était
     couvert par rien. On lui applique la même loi, pas une valeur neuve. */
  line-height: 1 !important;
  margin: 0 !important;
}


/* ===========================================================================
 * 8. FAMILLE PROSE — .cb-paragraph / .cb-list (DS09)
 * Widget `text-editor`. Piège DIFFÉRENT du bouton/titre/image, déjà noté en
 * tête de fichier (§ TEXTE) et RE-VÉRIFIÉ pour ce ticket (lecture seule,
 * préprod, 2026-08-10, `post-8422.css` + les CSS dynamiques Astra) :
 *
 * - `_css_classes` atterrit bien sur `.elementor-element` (comme les autres
 *   widgets), MAIS Elementor y pose directement les réglages de typo LOCAUX
 *   du widget (couleur, interlignage, alignement) — relevé verbatim :
 *     .elementor-element-74814fbe{text-align:start;line-height:1.4em;}
 *     .elementor-element-82351a3{color:var(--e-global-color-astglobalcolor4);}
 *   Ces propriétés sont HÉRITÉES par le/les `<p>` réels (nested sous
 *   `.elementor-widget-container`, sans wrapper intermédiaire) : pas besoin
 *   de retargeter un descendant pour elles, contrairement au bouton/titre.
 * - Sans réglage local (cas des deux widgets ci-dessus pour la police et la
 *   taille), le texte hérite du thème : Astra applique globalement
 *     body,button,input,select,textarea,.ast-button,.ast-custom-button{
 *       font-family:'Poppins',sans-serif;font-weight:inherit;
 *       font-size:16px;line-height:var(--ast-body-line-height,1.5em);}
 *   (relevé `astra-theme-dynamic-css-*.css`, sélecteur `body`, AUCUN
 *   `!important` — spécificité 1 élément, facile à battre) — et pose une
 *   marge de paragraphe générique `p{margin-bottom:0.5em;}` (bare-element,
 *   même fichier) que notre pont doit battre EXPLICITEMENT sur le `<p>` lui
 *   même (`margin` ne s'hérite pas, contrairement à `color`/`font-size`).
 * - Aucune règle Astra/kit dédiée trouvée pour `<ul>`/`<li>` dans un
 *   contexte `text-editor` (grep ciblé sur `entry-content`/`dynamic-css`,
 *   2026-08-10) : l'adversaire réel pour `.cb-list` est la feuille de style
 *   UA par défaut du navigateur (marge/indentation natives), pas une règle
 *   Astra propre — signalé tel quel, pas fabriqué.
 *
 * Conséquence pour le pont : couleur/police/taille/interlignage posés sur
 * `.cb-paragraph`/`.cb-list` eux-mêmes (l'élément qui porte la classe,
 * hérité par le contenu WYSIWYG) ; margin/padding — non hérités —
 * retargetés explicitement sur `.elementor-widget-container p`/`ul`/`li`.
 * ========================================================================= */
.cb-paragraph{
  font-family: var(--cb-font-body) !important;
  font-size: var(--cb-text-base) !important;
  line-height: 1.6 !important;
  color: var(--cb-encre) !important;
}
.cb-paragraph--lead{
  font-size: var(--cb-text-lg) !important;
}
/* ⚠️ CETTE RÈGLE MANQUAIT, et son absence se voyait à l'œil sur la carte du hero :
   le paragraphe posé sur la vidéo rendait en VERT FONCÉ (`--cb-encre`, mesuré
   `rgb(33,39,33)`), signalé par Alexandre. La cause est propre à cette famille :
   contrairement aux titres, dont le pont peint un ENFANT
   (`.elementor-heading-title`), la prose doit être peinte sur l'élément qui
   PORTE la classe — c'est ainsi que le contenu WYSIWYG en hérite. Le
   `color: !important` de `.cb-paragraph` ci-dessus atteint donc aussi le markup
   neutre de la couche 2, où `.cb-paragraph--on-dark` n'a pas de `!important` et
   perdait. Le modificateur doit être doublé ici, exactement comme
   `.cb-heading--on-dark` l'est § 7.
   Leçon à ne pas perdre : quand le pont peint la classe elle-même, TOUT
   modificateur de cette classe doit être doublé dans le pont — sinon il est
   sans effet des deux côtés. */
.cb-paragraph--on-dark{ color: var(--cb-ivoire) !important; }
/* ⚠️ `max-width` — RETIRÉE ici le 2026-08-17, REMISE le 2026-08-18 par `DS71`
   (décision d'Alexandre : la remettre, mais plus large — le token passe à 80ch).
   **Et elle ne peut vivre qu'ICI pour un widget Elementor.** Mesuré au rendu sur
   `/` à 1440 (`docs/preuves/DS71/`) : la seule règle qui gagnait sur
   `.cb-events__copy{max-width:var(--cb-measure)}` était
   `.e-con.e-con > .e-con-inner > .elementor-widget, .elementor.elementor .e-con >
   .elementor-widget{max-width:100%}` de `frontend.min.css` — **spécificité 0-4-0**
   contre 0-1-0. Toute classe simple de couche 2 est donc morte sur la largeur d'un
   widget en container, sans exception : c'est le motif de `docs/pieges.md` § 14, et
   il ne concerne pas que le `!important` mais aussi la simple spécificité. */
.cb-paragraph,
.cb-list{
  max-width: var(--cb-measure) !important;
}
/* La marge, elle, a une raison propre au pont : battre `p{margin-bottom:0.5em}`
   d'Astra (non hérité). */
.cb-paragraph .elementor-widget-container p{
  margin: 0 0 var(--cb-space-sm) 0 !important; /* bat p{margin-bottom:0.5em} d'Astra (non hérité) */
}
.cb-paragraph .elementor-widget-container p:last-child{ margin-bottom: 0 !important; }

.cb-list{
  font-family: var(--cb-font-body) !important;
  font-size: var(--cb-text-base) !important;
  line-height: 1.6 !important;
  color: var(--cb-encre) !important;
}
.cb-list .elementor-widget-container ul{
  margin: 0 !important;
  padding-left: var(--cb-space-lg) !important; /* la mesure est portée par `.cb-list` lui-même ci-dessus (DS71), pas par le `ul` */
}
.cb-list .elementor-widget-container li + li{
  margin-top: var(--cb-space-sm) !important;
}


/* ===========================================================================
 * 10. FAMILLE CARTE — .cb-card / --overlay / --split / --stacked /
 *     __media / __body / __tags / __cta (DS12)
 *
 * `.cb-card` et ses 4 classes de composition (atoms.css § .cb-card) sont
 * posées sur des CONTAINERS Elementor, jamais sur un widget — même famille
 * de piège que `.cb-title-lockup`/`.cb-title-inline` (§ 2 ci-dessus,
 * `css_classes` pas `_css_classes`) : deux formes réelles selon le réglage
 * "content width" du container (plein-large : la classe et le nœud flex
 * sont le MÊME élément ; boxed : le nœud flex est un `.e-con-inner`
 * enfant). Traduction mécanique des deux DOM réels, reprise à l'identique
 * du motif déjà établi § 2 — aucune décision nouvelle ici, quatre classes
 * de plus à couvrir avec la même mécanique.
 *
 * Les briques COMPOSÉES à l'intérieur (`.cb-img`, `.cb-heading` et son
 * `--on-dark`, `.cb-title-lockup`, `.cb-tag` et son `--on-dark`,
 * `.cb-paragraph`, `.cb-btn`/`.cb-link` — §§ 1bis/2/5/7 ci-dessus) sont
 * DÉJÀ traduites plus haut dans ce fichier : aucune règle supplémentaire
 * n'est nécessaire pour elles, la carte ne fait que positionner les boîtes
 * qui les contiennent. (`.cb-title-lockup--on-dark` n'existe volontairement
 * pas — voir atoms.css § .cb-title-lockup pour pourquoi.)
 * ========================================================================= */
.cb-card.e-con-full,
.cb-card.e-con > .e-con-inner{
  display: flex !important;
  /* pas de `width` — miroir d'atoms.css (DS56). C'est ce `!important` qui
     écrasait le `width:50%` des cartes de la grille réception. */
}
.cb-card__media.e-con-full,
.cb-card__media.e-con > .e-con-inner{
  display: block !important;
}
/* flux du corps de carte (DS55) — miroir strict d'atoms.css : gap à 0, tout
   se joue en marges, une règle par relation. Les enfants réels d'un container
   Elementor sont des `.elementor-element` (widgets ou containers), d'où le
   `> .elementor-element` là où le markup neutre a `> *`. */
.cb-card__body.e-con-full,
.cb-card__body.e-con > .e-con-inner{
  display: flex !important;
  flex-direction: column !important;
  gap: 0 !important;
  min-width: 0 !important;
}
.cb-card__body.e-con-full > .elementor-element + .elementor-element,
.cb-card__body.e-con > .e-con-inner > .elementor-element + .elementor-element{
  margin-top: var(--cb-flow-text) !important;
}
.cb-card__body.e-con-full > .elementor-element + .cb-card__tags,
.cb-card__body.e-con-full > .elementor-element + .cb-card__cta,
.cb-card__body.e-con-full > .cb-card__tags + .elementor-element,
.cb-card__body.e-con-full > .cb-card__cta + .elementor-element,
.cb-card__body.e-con > .e-con-inner > .elementor-element + .cb-card__tags,
.cb-card__body.e-con > .e-con-inner > .elementor-element + .cb-card__cta,
.cb-card__body.e-con > .e-con-inner > .cb-card__tags + .elementor-element,
.cb-card__body.e-con > .e-con-inner > .cb-card__cta + .elementor-element{
  margin-top: var(--cb-flow-group) !important;
}
/* même raison qu'en couche 2 : aucune marge basse, le flux est en marge-haute
   seule. Sur le DOM Elementor la marge vient du `<p>` INTERNE au widget, donc
   elle gonfle la boîte du widget — on la neutralise là où elle naît. */
.cb-card__body.e-con-full > .elementor-element,
.cb-card__body.e-con > .e-con-inner > .elementor-element{
  margin-bottom: 0 !important;
}
.cb-card__body .cb-paragraph .elementor-widget-container p:last-child,
.cb-card__body .cb-list .elementor-widget-container ul:last-child{
  margin-bottom: 0 !important;
}
.cb-card__tags.e-con-full,
.cb-card__tags.e-con > .e-con-inner{
  display: flex !important;
  flex-wrap: wrap !important;
  gap: var(--cb-flow-inline) !important; /* DS55 — mesuré sur Figma, fixe : un écart entre objets ne double pas avec l'écran */
}
.cb-card__cta.e-con-full,
.cb-card__cta.e-con > .e-con-inner{
  display: flex !important;
  flex-wrap: wrap !important;
  gap: var(--cb-flow-actions) !important; /* DS55 — mesuré sur Figma, fixe : un écart entre objets ne double pas avec l'écran */
}

/* card/stacked — reprise littérale d'atoms.css .cb-card--stacked (DS55). */
.cb-card--stacked.e-con-full,
.cb-card--stacked.e-con > .e-con-inner{
  flex-direction: column !important;
  gap: var(--cb-flow-group) !important;
}

/* card/split — reprise littérale d'atoms.css .cb-card--split. */
.cb-card--split.e-con-full,
.cb-card--split.e-con > .e-con-inner{
  flex-direction: column !important;
  gap: var(--cb-flow-group) !important; /* DS55 */
}
@media (min-width: 768px){
  .cb-card--split.e-con-full,
  .cb-card--split.e-con > .e-con-inner{
    flex-direction: row !important;
    align-items: stretch !important;
    gap: var(--cb-space-gap-desktop) !important;
  }
  .cb-card--split.e-con-full > .elementor-element:nth-child(1),
  .cb-card--split.e-con > .e-con-inner > .elementor-element:nth-child(1),
  .cb-card--split.e-con-full > .elementor-element:nth-child(2),
  .cb-card--split.e-con > .e-con-inner > .elementor-element:nth-child(2){
    /* Les 2 containers enfants (média, corps). Le ciblage porte sur leur NOMBRE,
       jamais sur leur rôle : l'ordre n'est PAS constant — 22 cartes sur 39 ont le
       média en 1er, 17 sont en miroir (DS42, docs/composants-prod.md § 1.1, corrigé
       le 2026-08-12 par DS58 ; la version d'avant affirmait un ordre unique).
       Les deux reçoivent la même règle, donc les deux agencements passent. */
    flex: 1 1 0 !important;
    min-width: 0 !important;
  }
}

/* card/overlay — RETIRÉ le 2026-08-11 (DS37). Voir atoms.css § card/overlay
   pour la décision et les mesures qui l'ont motivée. Rien à traduire : plus
   d'atome à traduire. Ce retrait fait disparaître le seul `linear-gradient`
   du pont — s'il en réapparaît un, c'est un accident. */


/* ===========================================================================
 * 10quater. LE TIROIR MOBILE — réparer ce que NOTRE header y a cassé
 * (retour d'Alexandre du 2026-08-19, capture à 500 px)
 *
 * Le menu mobile n'a jamais été au devis. La consigne est donc stricte : « on
 * répare ce que notre header a cassé, on ne refait pas le menu mobile ». Ces
 * règles ne redessinent rien — elles annulent une fuite et rendent lisible ce
 * que le défaut d'ElementsKit rendait illisible.
 *
 * TROIS CAUSES, relevées au rendu sur le tiroir ouvert :
 *
 * (1) LE PANNEAU DE SOUS-MENU EST GRIS CLAIR SOUS DU TEXTE IVOIRE. Mesuré :
 *     fond `rgb(244,244,244)` (le défaut d'ElementsKit, `widget-styles.css`) et
 *     couleur héritée `rgb(255,253,240)` — soit de l'ivoire sur du gris clair,
 *     illisible. C'est la bande « ENGLISH » de sa capture. Le tiroir, lui, est
 *     bien en `--cb-encre` : c'est le PANNEAU qui n'a pas suivi.
 *
 * (2) NOS ITEMS DE NAV FUITENT DANS LE TIROIR. `.cb-nav .elementskit-navbar-nav
 *     > li > a` donne `padding: 12px 16px` et `border-radius: 4px` — une forme
 *     pensée pour une nav HORIZONTALE de desktop. Empilée dans un tiroir, elle
 *     fabrique des pastilles contournées.
 *
 * (3) LE CHEVRON ET LA FERMETURE SONT DES BOÎTES CONTOURNÉES, pour la même
 *     raison : ils héritent d'un traitement de contrôle qui n'a de sens qu'en
 *     desktop.
 *
 * ⚠️ TOUT EST SOUS 1025 px, le seuil d'ElementsKit lui-même (mesuré : le
 * hamburger existe jusqu'à 1024 inclus, boîte 0×0 à 1025). Rien de ceci ne doit
 * toucher au desktop, qui est validé.
 * ========================================================================= */
@media (max-width: 1024px){
  /* (1) le panneau déplié prend le fond du tiroir, pas le gris d'ElementsKit */
  .cb-nav .elementskit-menu-container .elementskit-submenu-panel,
  .cb-nav .elementskit-menu-container .elementskit-dropdown{
    background-color: var(--cb-encre) !important;
    border: 0 !important;
    box-shadow: none !important;
  }
  .cb-nav .elementskit-menu-container .elementskit-submenu-panel > li > a{
    color: var(--cb-ivoire) !important;
    background-color: transparent !important;
  }

  /* (2) l'item de nav redevient une LIGNE de liste, pas une pastille */
  .cb-nav .elementskit-navbar-nav > li > a,
  .cb-nav .elementskit-submenu-panel > li > a{
    border-radius: 0 !important;
    border: 0 !important;
    background-color: transparent !important;
    padding-inline: var(--cb-page-inset) !important;
  }

  /* (3) le chevron et la fermeture cessent d'être des boîtes contournées */
  .cb-nav .elementskit-submenu-indicator,
  .cb-nav button.elementskit-menu-close{
    border: 0 !important;
    background-color: transparent !important;
    border-radius: 0 !important;
    color: var(--cb-ivoire) !important;
  }
}


/* ===========================================================================
 * 10ter. L'ITEM DE LANGUE INJECTÉ PAR WPML — masqué en desktop (REG04, 2026-08-19)
 *
 * WPML sait injecter un sélecteur de langue DANS un menu (option
 * `wpml_language_switcher['menus']`). Le menu 62 sert LES DEUX PALIERS : le
 * tiroir mobile et la nav desktop. Rallumer l'injection pour rendre la langue
 * au burger la remet donc aussi dans la nav desktop.
 *
 * ⚠️ ET CE SERAIT ROUVRIR UN DÉFAUT FERMÉ : « Français » est exactement l'item
 * qui faisait passer la nav desktop à la ligne — `P05b` l'a mesuré, 8 items =
 * 1098px dans une nav de 1033. En desktop, le contrôle de langue est celui de
 * la RANGÉE 1 (le widget shortcode `cb0e005`), et il y reste.
 *
 * ⚠️ CETTE RÈGLE EST POSÉE AVANT DE RALLUMER L'OPTION, pas après : dans l'autre
 * ordre, la nav desktop passe à la ligne pendant l'intervalle. Deux gestes, deux
 * mesures (WORKFLOW.md § 3, « une seule cause à la fois »).
 *
 * ⚠️ SCOPÉ À `.cb-nav`, jamais à `.wpml-ls-item` seul : le sélecteur de la
 * rangée 1 ne vit PAS dans le widget de menu, il ne doit surtout pas tomber
 * avec — c'est lui le contrôle desktop.
 * ========================================================================= */
@media (min-width: 1025px){
  .cb-nav .wpml-ls-item{
    display: none !important;
  }
}


/* ===========================================================================
 * 10bis. LA GRILLE DE CARTES — .cb-cards-grid (C03, 2026-08-19)
 *
 * L'atome vit dans atoms.css. Ici, seulement ce qu'Elementor impose et qu'il
 * faut battre :
 *
 * ⚠️ ELEMENTOR COMPILE LA MISE EN PAGE EN VARIABLES CSS (`--flex-direction`,
 * `--flex-wrap`, `--gap`, `--column-gap`…) autant qu'en propriétés
 * (docs/pieges.md § 3) : écraser la propriété seule laisse la variable décider.
 * On écrase donc LES DEUX, comme partout ailleurs dans ce fichier.
 *
 * ⚠️ LES ENFANTS SONT DES CONTAINERS, PAS DES WIDGETS : la règle en
 * spécificité 0-4-0 de `frontend.min.css` sur `.e-con > .e-con-inner >
 * .elementor-widget` (piège § 2) ne les concerne pas — leur largeur vient de
 * leur PROPRE réglage `width`, qui est vidé par le mapping de C03. C'est ce
 * qui rend cette règle-ci suffisante ; si un jour un `width` local revenait sur
 * l'un d'eux, il gagnerait et la grille se casserait en silence.
 *
 * ⚠️ LA GOUTTIÈRE ET LA LARGEUR VIENNENT DU MÊME TOKEN — ne jamais en régler
 * une sans l'autre, sinon les colonnes débordent ou passent à la ligne. Le
 * défaut est brutal et immédiatement visible : 4 cartes sur 4 rangées.
 * ========================================================================= */
.cb-cards-grid.e-con-full,
.cb-cards-grid.e-con > .e-con-inner{
  --display: flex;
  --flex-direction: row;
  --flex-wrap: wrap;
  --column-gap: var(--cb-space-gap-mobile);
  --row-gap: var(--cb-space-gap-mobile);
  display: flex !important;
  flex-direction: row !important;
  flex-wrap: wrap !important;
  column-gap: var(--cb-space-gap-mobile) !important;
  row-gap: var(--cb-space-gap-mobile) !important;
}
.cb-cards-grid.e-con-full > .e-con,
.cb-cards-grid.e-con > .e-con-inner > .e-con{
  width: 100% !important;
}
@media (min-width: 768px){
  .cb-cards-grid.e-con-full > .e-con,
  .cb-cards-grid.e-con > .e-con-inner > .e-con{
    width: calc(50% - var(--cb-space-gap-mobile) / 2) !important;
  }
}
@media (min-width: 1025px){
  .cb-cards-grid.e-con-full,
  .cb-cards-grid.e-con > .e-con-inner{
    --column-gap: var(--cb-space-gap-desktop);
    --row-gap: var(--cb-space-gap-desktop);
    column-gap: var(--cb-space-gap-desktop) !important;
    row-gap: var(--cb-space-gap-desktop) !important;
  }
  .cb-cards-grid.e-con-full > .e-con,
  .cb-cards-grid.e-con > .e-con-inner > .e-con{
    width: calc(50% - var(--cb-space-gap-desktop) / 2) !important;
  }
}


/* ===========================================================================
 * 11. FAMILLE SECTION — .cb-section / .cb-section--light / --dark /
 *     .cb-univers--* / .cb-section__accent (DS31, décision Q14)
 *
 * `.cb-section`/`.cb-section--light`/`--dark` sont posées sur un CONTAINER
 * Elementor — même famille de piège que `.cb-title-lockup`/`.cb-card` (§ 2/
 * § 10 ci-dessus) : deux DOM réels possibles selon "content width" (plein-
 * large, la classe et le nœud flex sont le même élément ; boxed, le nœud
 * flex est un `.e-con-inner` enfant). Traduction mécanique des deux formes,
 * rien de neuf ici.
 *
 * CE QUI NE DEMANDE AUCUNE traduction ci-dessous, et pourquoi : les
 * propriétés personnalisées (`--cb-section-accent-light`, `--cb-section-
 * accent`) posées par `.cb-section--light`/`--dark`/`.cb-univers--*`
 * (atoms.css) sont de simples déclarations de variable — elles héritent
 * par le DOM normal, à travers n'importe quel `.e-con-inner` intermédiaire,
 * sans qu'aucune règle Elementor ne les concurrence (le kit ne déclare
 * jamais `--cb-*`). Seules les propriétés VISUELLES qui doivent battre un
 * adversaire réel (couleur, fond) ont besoin d'être retargetées ci-dessous,
 * sur le même motif que le reste de ce fichier.
 *
 * `.cb-section__accent` — widget `heading`, même piège que `.cb-heading`/
 * `.cb-title-lockup__script` (§ 2/§ 7) : la classe atterrit sur
 * `.elementor-element`, le texte réel est `.elementor-heading-title` nesté.
 * Adversaire identique à celui déjà relevé pour ce widget (tête de fichier,
 * § TITRE) : 4 classes, aucun `!important` — notre riposte habituelle
 * (spécificité doublée + `!important`) suffit.
 * ========================================================================= */
.cb-section.e-con-full,
.cb-section.e-con > .e-con-inner{
  display: flex !important;
  flex-direction: column !important;
  gap: 0 !important; /* DS55 — miroir d'atoms.css : flux en marges, pas en gap */
  padding: var(--cb-space-section-mobile) var(--cb-page-inset) !important; /* DS66 — miroir d'atoms.css : retrait de page, pas inset de bloc */
}
.cb-section.e-con-full > .elementor-element + .elementor-element,
.cb-section.e-con > .e-con-inner > .elementor-element + .elementor-element{
  margin-top: var(--cb-flow-group) !important;
}
.cb-section.e-con-full > .cb-heading + .cb-section__accent,
.cb-section.e-con > .e-con-inner > .cb-heading + .cb-section__accent{
  margin-top: 0 !important; /* titre + accent = un lockup, ils se touchent */
}
.cb-section.e-con-full > .elementor-element,
.cb-section.e-con > .e-con-inner > .elementor-element{
  margin-bottom: 0 !important;
}
@media (min-width: 768px){
  .cb-section.e-con-full,
  .cb-section.e-con > .e-con-inner{
    /* DS66 — miroir d'atoms.css. Le rail DOIT être repris ici : ce padding-là
       porte `!important` et c'est lui qui gagne sur le DOM Elementor ; la
       recette de la couche 2 ne l'atteindrait jamais. `--cb-rail-gutter` est
       déclarée sur `.cb-section`, donc elle hérite jusqu'au `.e-con-inner`,
       et son `100%` s'y lit sur le container de section — la même largeur
       qu'en couche 2. */
    padding: var(--cb-space-section-desktop) calc(var(--cb-rail-gutter) + var(--cb-page-inset)) !important;
  }
}

.cb-section__accent .elementor-heading-title{
  margin: 0 !important;
  font-family: var(--cb-font-script) !important;
  font-weight: 400 !important;
  text-transform: none !important; /* même divergence kit que .cb-heading/.cb-title-lockup__script, § 2/§ 7 */
  font-size: var(--cb-text-section-accent) !important;
  line-height: 1 !important;
  color: var(--cb-section-accent) !important;
}

/* DS34 — le même accent posé sur un `uael-advanced-heading` dont SEUL le
   sous-titre est rempli. C'est le cas des 4 noms de chambre de l'accueil
   (« Charme », « Prestige », « Élégance », « Camboyer ») : mesuré,
   `.uael-heading-text` y est vide et le nom vit dans `.uael-sub-heading`
   (relevé complet en § 7, bloc DS34). Sans cette règle, la promesse de
   `Q14`/`DS31` — « chaque chambre a sa couleur » — n'a AUCUN élément sur
   lequel s'exprimer dans ce bloc : c'est ce que `H03` avait constaté.
   Adversaire relevé (`post-8422.css`) :
     … .elementor-element-35b9ca8 .uael-sub-heading{
       font-family:"DianaWebber Script",Sans-serif; font-size:80px;
       color:#9FD37D; margin:5px 0 0 0; }   (75px / 60px en responsive)
   4 classes, aucun `!important`. DS32 reprend explicitement la TAILLE et
   l'interligne issus de `.cb-section__accent` : `h3 × 1,8`, puis 1. Le pont
   ne décide rien, il applique le même contrat à ce DOM UAEL. La couleur est
   reprise du même token que le jumeau `heading` : `--cb-section-accent`,
   piloté par `.cb-univers--*`. */
.cb-section__accent .uael-sub-heading{
  margin: 0 !important;
  font-family: var(--cb-font-script) !important;
  font-weight: 400 !important;
  text-transform: none !important;
  font-size: var(--cb-text-section-accent) !important;
  line-height: 1 !important;
  color: var(--cb-section-accent) !important;
}

/* Titre / texte / liste / label — reprise littérale des règles atoms.css
   § .cb-section, retargetées sur l'élément réel de chaque widget (même
   motif que § 7/§ 8 pour .cb-heading/.cb-paragraph/.cb-list). */
.cb-section--dark .cb-heading .elementor-heading-title,
.cb-section--dark .cb-heading .uael-heading-text{ /* DS34 — même règle, DOM uael */
  color: var(--cb-ivoire) !important;
}
.cb-section--dark .cb-paragraph,
.cb-section--dark .cb-list{
  color: var(--cb-ivoire) !important; /* hérité par le(s) <p>/<ul> réels, non retargeté — même logique que § 8 */
}
.cb-section--dark .cb-label .elementor-button{
  color: var(--cb-ivoire) !important;
}
.cb-section--dark .cb-label .elementor-button:hover,
.cb-section--dark .cb-label .elementor-button:focus{
  color: var(--cb-ivoire) !important;
}

/* Tag — même retargeting que § 1bis (.cb-tag--on-dark), appliqué ici par
   la section plutôt que par une classe posée sur chaque pastille. */
.cb-section--dark .cb-tag .elementor-button{
  background: var(--cb-ivoire) !important;
  color: var(--cb-encre) !important;
}
.cb-section--dark .cb-tag .elementor-button:hover,
.cb-section--dark .cb-tag .elementor-button:focus{
  background: var(--cb-ivoire) !important;
  color: var(--cb-encre) !important;
}

/* ⚠️ ET LE BOUTON CONTOURNÉ N'ÉTAIT PAS COUVERT — REG01 § 8, trouvé en REGARDANT
   la section réparée et non en relisant le CSS. `.cb-interactive--fill` pose un
   contour et du texte `--cb-encre` : c'est juste sur fond clair, et sur une section
   sombre il n'y a plus de bouton. Mesuré au contraste sur les 24 pages : **144
   contrôles sous le seuil AA de 4,5:1, dont 133 à 1,00** — c'est-à-dire encre sur
   encre, littéralement invisibles. Ils tiennent dans **trois sections** seulement
   (`2a7507b` du bloc global, `2962edc0` région, `2df749b6` gîte), ce qui est
   rassurant : le défaut est concentré, pas diffus.
   La production, elle, les servait pleins (`rgb(77,82,74)`, texte ivoire) — c'est
   donc bien un régime « bouton secondaire sur fond sombre », et le système a déjà
   sa recette pour ça : `--fill-on-dark` (contour ivoire, texte ivoire, fond
   transparent, `atoms.css:373`). On la reprend ici, appliquée PAR LA SECTION plutôt
   que par une classe posée sur chacun des 144 boutons — exactement le choix déjà
   fait juste au-dessus pour les pastilles. Le survol suit la même destination que
   partout ailleurs sur fond sombre : ivoire à 15 % (DS18). */
.cb-section--dark .cb-interactive--fill .elementor-button{
  background-color: transparent !important;
  border-color: var(--cb-ivoire) !important;
  color: var(--cb-ivoire) !important;
  fill: var(--cb-ivoire) !important;
}
.cb-section--dark .cb-interactive--fill .elementor-button:hover,
.cb-section--dark .cb-interactive--fill .elementor-button:focus-visible{
  background-color: color-mix(in srgb, var(--cb-ivoire) 15%, transparent) !important;
  border-color: var(--cb-ivoire) !important;
  color: var(--cb-ivoire) !important;
}

/* Lien nu (.cb-link sans --on-light/--on-dark) — double cible, même
   raison qu'au § 1 (le <a> réel peut être l'élément qui porte la classe,
   cas prose, ou un `.elementor-button` nesté, cas bouton en mode lien). */
.cb-section--light .cb-link,
.cb-section--light .cb-link .elementor-button{
  color: var(--cb-vert) !important;
}
.cb-section--light .cb-link:hover, .cb-section--light .cb-link:focus,
.cb-section--light .cb-link[data-demo-state="hover"],
.cb-section--light .cb-link .elementor-button:hover, .cb-section--light .cb-link .elementor-button:focus{
  color: var(--cb-encre) !important;
}
.cb-section--dark .cb-link,
.cb-section--dark .cb-link .elementor-button{
  color: var(--cb-ivoire) !important;
}
.cb-section--dark .cb-link:hover, .cb-section--dark .cb-link:focus,
.cb-section--dark .cb-link[data-demo-state="hover"],
.cb-section--dark .cb-link .elementor-button:hover, .cb-section--dark .cb-link .elementor-button:focus{
  color: var(--cb-sauge) !important;
}

/* Bouton secondaire (.cb-interactive--fill) sous une section sombre —
   même recette que .cb-interactive--fill-on-dark (§ 1), appliquée par la
   section plutôt que choisie sur le widget. */
.cb-section--dark .cb-interactive--fill .elementor-button{
  border-color: var(--cb-ivoire) !important;
  color: var(--cb-ivoire) !important;
}
.cb-section--dark .cb-interactive--fill .elementor-button:hover,
.cb-section--dark .cb-interactive--fill .elementor-button:focus{
  background: color-mix(in srgb, var(--cb-ivoire) 15%, transparent) !important;
  border-color: var(--cb-ivoire) !important;
  color: var(--cb-ivoire) !important;
}

/* ===========================================================================
 * 11bis. HERO ÉDITORIAL — .cb-content-hero (DS13 / poste 2)
 *
 * Traduction mécanique de la composition neutre vers les containers Elementor.
 * Les décisions visuelles sont toutes en couche 2 ; ici, on ne fait que viser
 * le nœud flex réel (porteur en full, `.e-con-inner` en boxed) et la vidéo de
 * fond qu'Elementor injecte lui-même.
 * ========================================================================= */
/* ⚠️ LE PADDING HAUT DU HERO DOIT ÊTRE REPRIS ICI, et ce n'est pas une
   redondance : `.cb-content-hero{padding-top}` en couche 2 ne pèse que (0,1,0)
   sans `!important`, alors que § 8 pose `.cb-section.e-con-full{padding: …
   !important}` à (0,2,0). Sur le DOM Elementor, la section gagnait donc
   toujours — mesuré le 2026-08-17 : le token `--cb-space-hero-top` était bien
   servi à 32px et le rendu sortait quand même à 112px. Même spécificité que
   son adversaire, `!important` comme lui, et APRÈS lui dans le fichier : c'est
   l'ordre source qui tranche, pas un cran de spécificité en plus. */
.cb-content-hero.e-con-full,
.cb-content-hero.e-con > .e-con-inner{
  padding-top: var(--cb-space-hero-top) !important;
}
.cb-content-hero__intro.e-con-full,
.cb-content-hero__intro.e-con > .e-con-inner{
  display: grid !important;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) !important;
  align-items: start !important; /* miroir d'atoms.css — ferrage haut, frame 40000182:17715 */
  gap: var(--cb-space-split-desktop) !important;
  width: 100% !important;
  padding: 0 !important;
}
/* `.cb-content-hero__media` retiré le 2026-08-18 (`P03g`) — la classe est sur
   0 post depuis que les 15 pages sont en bandeau. Voir couche 2. */
.cb-content-hero__body.e-con-full,
.cb-content-hero__body.e-con > .e-con-inner,
.cb-content-hero__tags.e-con-full,
.cb-content-hero__tags.e-con > .e-con-inner{
  min-width: 0 !important;
  width: 100% !important;
  padding: 0 !important;
}
.cb-content-hero__body.e-con-full,
.cb-content-hero__body.e-con > .e-con-inner{
  display: flex !important;
  flex-direction: column !important;
  gap: 0 !important;
}
.cb-content-hero__body.e-con-full > .elementor-element + .elementor-element,
.cb-content-hero__body.e-con > .e-con-inner > .elementor-element + .elementor-element{
  margin-top: var(--cb-flow-group) !important;
}
.cb-content-hero__body.e-con-full > .cb-heading + .cb-section__accent,
.cb-content-hero__body.e-con > .e-con-inner > .cb-heading + .cb-section__accent{
  margin-top: 0 !important;
}
.cb-content-hero__body.e-con-full > .elementor-element,
.cb-content-hero__body.e-con > .e-con-inner > .elementor-element{
  margin-bottom: 0 !important;
}
.cb-content-hero__tags.e-con-full,
.cb-content-hero__tags.e-con > .e-con-inner{
  display: flex !important;
  flex-direction: row !important;
  flex-wrap: wrap !important;
  align-items: flex-start !important;
  gap: var(--cb-flow-inline) !important;
}
.cb-content-hero__tags.e-con-full > .elementor-element,
.cb-content-hero__tags.e-con > .e-con-inner > .elementor-element{
  width: auto !important;
}
.cb-content-hero__motion.e-con-full,
.cb-content-hero__motion.e-con > .e-con-inner{
  position: relative !important;
  width: 100% !important;
  aspect-ratio: 16 / 9;
  overflow: hidden !important;
  border-radius: var(--cb-radius-md) !important;
  background-position: center !important;
  background-size: cover !important;
  padding: 0 !important;
}
.cb-content-hero__motion .elementor-background-video-container,
.cb-content-hero__motion .elementor-background-video-container video{
  position: absolute !important;
  inset: 0 !important;
  width: 100% !important;
  height: 100% !important;
  object-fit: cover !important;
  /* ⚠️ `transform: none` N'EST PAS DE LA PRÉCAUTION — mesuré sur la première
     page vidéo réellement migrée (`/hotel/chambres/`, P03e, 2026-08-17). Le
     pilote `/hotel/` est une page IMAGE : ce chemin n'avait donc jamais été
     éprouvé ailleurs que sur la page de revue, où Elementor ne tourne pas.
     Elementor centre sa vidéo de fond avec `left:50%; top:50%` PLUS
     `transform: translate(-50%,-50%)`. `inset: 0 !important` gagne bien sur
     `left`/`top`, et rien sur le `transform` : la vidéo restait déplacée de
     (-688, -387) sur un cadre de 1376 × 774, soit exactement la moitié de sa
     taille — mesuré, pas déduit. Sur la capture, ça ressemblait à un collage
     de trois photos, pas à un bug de positionnement. → docs/pieges.md § 16. */
  transform: none !important;
}
@media (max-width: 767px){
  .cb-content-hero__intro.e-con-full,
  .cb-content-hero__intro.e-con > .e-con-inner{
    grid-template-columns: minmax(0, 1fr) !important;
    gap: var(--cb-flow-block) !important;
  }
}

/* ===========================================================================
 * 11ter. VARIANTE BANDEAU — .cb-bandeau et .cb-content-hero--bandeau (P03e)
 *
 * Le bandeau est un container Elementor SANS ENFANT : son média est le
 * `background_image` du container, donc la boîte doit tenir sa hauteur toute
 * seule — c'est l'`aspect-ratio` qui la donne, jamais un `min_height` local.
 *
 * `padding: 0` n'est pas un combat gagné contre une règle connue : le kit `8336`
 * pose déjà `container_padding: 0` (docs/prod.md § 3.1) et la transformation vide
 * le champ local. Il tient le contrat « un container qui porte une classe `.cb-*`
 * n'a plus de réglage de style local qui la contredise », y compris si quelqu'un
 * repose un padding dans l'admin plus tard.
 *
 * ⚠️ ET LA GRILLE DE LA VARIANTE EST SCOPÉE `min-width: 768px`, pas globale :
 * § 11bis pose l'empilement mobile à (0,2,0) DANS une media query, et cette
 * règle-ci pèse (0,3,0). Non scopée, elle gagnerait aussi en mobile et y
 * remettrait deux colonnes de texte sur 390px.
 * ========================================================================= */
.cb-bandeau.e-con-full,
.cb-bandeau.e-con > .e-con-inner{
  padding: 0 !important;
}
.cb-bandeau.e-con-full{
  position: relative !important;
  width: 100% !important;
  aspect-ratio: 1440 / 609;
  /* Le plafond de hauteur est repris ici parce qu'Elementor pose `min-height` et
     `height` par variables sur `.e-con` : à valeur égale de spécificité, mieux
     vaut que le plafond du système soit au même niveau d'insistance que le reste
     de cette règle. Raisonnement complet : atoms.css § .cb-bandeau. */
  max-height: calc(100svh - var(--cb-space-hero-intro) - var(--cb-bandeau-lockup) - var(--cb-flow-group)) !important;
  overflow: hidden !important;
  /* LONGHANDS — le raccourci `background` effacerait le `background-image`
     compilé par Elementor. Voir atoms.css § .cb-bandeau. */
  background-position: center !important;
  background-size: cover !important;
  background-repeat: no-repeat !important;
  /* ⚠️ LA MARGE NÉGATIVE VIT ICI, ET PAS DANS L'ATOME NI DANS LE JSON.
     Ajoutée par `P03b` le 2026-08-17, en préparation du déploiement des 14 pages.

     PAS DANS L'ATOME : elle n'est pas une propriété du composant. Elle n'existe
     que parce que le header RÉEL occupe le flux — le bandeau doit remonter
     d'exactement sa hauteur pour passer dessous. La page de revue n'a pas de
     header au-dessus de ses cartes : l'y poser ferait chevaucher la carte
     précédente. C'est la définition même de la couche 3.

     PAS DANS LE JSON : le transformateur l'y écrivait en dur (134/124/57), soit
     trois constantes recopiées sur 15 pages pour une hauteur qui, elle, bouge —
     la cliente peut ajouter une entrée de menu. C'était aussi un champ de style
     local sur un porteur `.cb-*`, ce que la règle du projet interdit.

     ⚠️ `--cb-header-h-repos` ET NON `--cb-header-h` : la sentinelle publie les
     deux, et elles diffèrent (134 au repos contre 68 collé, à 1440). La courante
     ferait sauter le bandeau de 66px sous le header au premier pixel de
     défilement. Voir tokens.css § « hauteur du header au repos ».

     ⚠️ Le repli sans JS est par palier, pas un maximum : trop remonter découvre
     du fond ivoire entre le header et la photo — le défaut que `P03e` a corrigé
     sur le -120px du hero legacy. */
  margin-top: calc(-1 * var(--cb-header-h-repos, var(--cb-header-repos-mobile))) !important;
}
@media (min-width: 768px){
  .cb-bandeau.e-con-full{
    margin-top: calc(-1 * var(--cb-header-h-repos, var(--cb-header-repos-tablet))) !important;
  }
}
@media (min-width: 1025px){
  .cb-bandeau.e-con-full{
    margin-top: calc(-1 * var(--cb-header-h-repos, var(--cb-header-repos-desktop))) !important;
  }
}
@media (max-width: 767px){
  .cb-bandeau.e-con-full{ aspect-ratio: 3 / 2; }
}
.cb-content-hero--bandeau.e-con-full,
.cb-content-hero--bandeau.e-con > .e-con-inner{
  padding-top: var(--cb-space-section-mobile) !important;
}
/* ⚠️ L'ÉCART TEXTE → MÉDIA SE REPREND ICI, ET LE SÉLECTEUR DOIT PESER AUTANT QUE
   SON ADVERSAIRE : § 11 pose le flux de section avec
   `.cb-section.e-con-full > .elementor-element + .elementor-element` — QUATRE
   classes (0,4,0). Une règle en trois classes perdrait, et le média resterait à
   32px du texte. D'où le `.elementor-element` explicite : même poids, plus bas
   dans le fichier, c'est l'ordre source qui tranche. Même mécanique que § 11bis
   pour le padding haut du hero (docs/pieges.md § 14). */
.cb-content-hero.e-con-full > .elementor-element.cb-content-hero__motion,
.cb-content-hero.e-con > .e-con-inner > .elementor-element.cb-content-hero__motion{
  margin-top: var(--cb-flow-block) !important;
}
/* --- LE DÔME sur le DOM réel (P03f) ---------------------------------------
 * ⚠️ IL N'Y A PRESQUE RIEN À TRADUIRE, ET C'EST LE POINT : la couche 2 pose le
 * `clip-path` sur la BOÎTE (`.cb-content-hero__motion`), un nœud qui existe des
 * deux côtés à l'identique. Rien à re-cibler, aucun `!important` à opposer —
 * personne ne dispute `clip-path` sur ce nœud.
 *
 * Une seule reprise est nécessaire, et elle est de spécificité : en Elementor la
 * classe atterrit sur `.elementor-element`, et la boîte VISUELLE est ce même
 * nœud en `e-con-full` (ou `.e-con-inner` en boxed). La règle de couche 2 pèse
 * (0,1,1)/(0,1,0) ; celle-ci reprend l'état FORCÉ de la revue à la spécificité
 * du DOM Elementor pour qu'il tienne aussi là où le jumeau est rendu.
 *
 * ⚠️ ET UN PIÈGE ÉVITÉ, à garder en mémoire : le premier essai animait l'`inset`
 * du conteneur vidéo d'Elementor. Impossible à tenir — `!important` d'auteur bat
 * les ANIMATIONS dans le cascade, donc l'`inset: 0 !important` de § 11bis aurait
 * gagné contre les keyframes, en silence. → docs/pieges.md § 17. */
/* Le plafond de hauteur du média — 32px de marge haut et bas, plein cadre
   conservé, recadrage assumé (voir atoms.css § dôme). */
.cb-content-hero--bandeau .cb-content-hero__motion.e-con-full,
.cb-content-hero--bandeau .cb-content-hero__motion.e-con > .e-con-inner{
  --cb-dome-sous-header: var(--cb-header-h, var(--cb-header-colle-mobile));
  --cb-dome-bande: calc(100svh - var(--cb-dome-sous-header) - 2 * var(--cb-page-inset));
  --cb-dome-boite-h: min(var(--cb-dome-boite-h-max), var(--cb-dome-bande));
  max-height: var(--cb-dome-bande) !important;
}
@media (min-width: 768px){
  .cb-content-hero--bandeau .cb-content-hero__motion.e-con-full,
  .cb-content-hero--bandeau .cb-content-hero__motion.e-con > .e-con-inner{
    --cb-dome-sous-header: var(--cb-header-h, var(--cb-header-colle-desktop));
  }
}
.cb-content-hero__motion.e-con-full[data-demo-state],
.cb-content-hero__motion.e-con[data-demo-state] > .e-con-inner{
  animation: none; /* une animation bat une déclaration normale — voir couche 2 */
  position: static !important; /* bat le `position: relative !important` de § 11bis */
}
.cb-content-hero__motion.e-con-full[data-demo-state="dome"],
.cb-content-hero__motion.e-con[data-demo-state="dome"] > .e-con-inner{
  clip-path: inset(var(--cb-dome-inset) var(--cb-dome-inset)
    round var(--cb-dome-radius) var(--cb-dome-radius) var(--cb-radius-md) var(--cb-radius-md)
    / var(--cb-dome-radius) var(--cb-dome-radius) var(--cb-radius-md) var(--cb-radius-md));
}
/* ⚠️ LA RETENUE A BESOIN D'UN `position` QUI GAGNE. § 11bis pose
   `position: relative !important` sur cette boîte (elle porte le voile et la
   couche vidéo en absolu). `position: sticky` en couche 2 ne pèse que (0,1,0) :
   il perdait, sans bruit — le média ne s'épinglait pas. Repris ici à la même
   insistance. ⚠️ `sticky` reste compatible avec le rôle de `relative` : c'est
   toujours un contexte de positionnement pour ses enfants absolus. */
@media (prefers-reduced-motion: no-preference){
  @supports (animation-timeline: view()){
    .cb-content-hero--bandeau .cb-content-hero__motion.e-con-full:not([data-demo-state]),
    .cb-content-hero--bandeau .cb-content-hero__motion.e-con:not([data-demo-state]) > .e-con-inner{
      position: sticky !important;
      /* Même formule qu'en couche 2 : centré dans l'écran MOINS le header collé,
         et jamais au-dessus du bas de ce header. Voir atoms.css. */
      --cb-dome-sous-header: var(--cb-header-h, var(--cb-header-colle-mobile));
      /* ⚠️ Une ligne, `max(var(), var())` — même contrainte de minifieur qu'en
         couche 2 (`REG13`). `--cb-dome-top-centre` est résolue sur le container
         `.cb-content-hero__motion` et hérite ici déjà calculée : c'est la même
         chaîne de variables que celle dont dépendait la formule développée. */
      top: max(var(--cb-dome-sous-header), var(--cb-dome-top-centre));
    }
    @media (min-width: 768px){
      .cb-content-hero--bandeau .cb-content-hero__motion.e-con-full:not([data-demo-state]),
      .cb-content-hero--bandeau .cb-content-hero__motion.e-con:not([data-demo-state]) > .e-con-inner{
        --cb-dome-sous-header: var(--cb-header-h, var(--cb-header-colle-desktop));
      }
    }
    /* La course de retenue, en frère et non en padding (raisonnement complet en
       couche 2). ⚠️ `::after` est libre sur ce nœud AUJOURD'HUI : Elementor s'en
       sert pour la « superposition de fond » d'un container, et cette section
       n'en a pas (vérifié sur le CSS compilé du post). Si quelqu'un en ajoute
       une dans l'admin, c'est ici que ça se verra. */
    /* ⚠️ `:has(…__motion)` — PAS DE MÉDIA, PAS DE COURSE (2026-08-18).
       La condition ne testait que l'état figé de la revue, jamais l'existence
       d'une vidéo. Sur `/hotel/`, page IMAGE migrée par `P03b`, ça posait 800px
       d'ivoire vide : 0 `<video>`, aucun `__motion`, et pourtant la course.
       Signalé par Alexandre au rendu, puis mesuré. Concerne 9 des 15 pages du
       poste 2. Raisonnement complet en couche 2. */
    .cb-content-hero--bandeau.e-con-full:has(.cb-content-hero__motion):not(:has(.cb-content-hero__motion[data-demo-state]))::after,
    .cb-content-hero--bandeau.e-con:has(.cb-content-hero__motion):not(:has(.cb-content-hero__motion[data-demo-state])) > .e-con-inner::after{
      content: "" !important;
      display: block !important;
      flex: 0 0 auto !important;
      width: 100% !important;
      height: var(--cb-dome-course-mobile) !important;
      background: none !important;
    }
    @media (min-width: 768px){
      /* Même garde qu'au-dessus : pas de média, pas de course. */
      .cb-content-hero--bandeau.e-con-full:has(.cb-content-hero__motion):not(:has(.cb-content-hero__motion[data-demo-state]))::after,
      .cb-content-hero--bandeau.e-con:has(.cb-content-hero__motion):not(:has(.cb-content-hero__motion[data-demo-state])) > .e-con-inner::after{
        height: var(--cb-dome-course-desktop) !important;
      }
    }
  }
}
@media (min-width: 768px){
  .cb-content-hero--bandeau.e-con-full,
  .cb-content-hero--bandeau.e-con > .e-con-inner{
    padding-block: var(--cb-space-hero-intro) !important;
  }
  .cb-content-hero--bandeau .cb-content-hero__intro.e-con-full,
  .cb-content-hero--bandeau .cb-content-hero__intro.e-con > .e-con-inner{
    grid-template-columns: minmax(0, 7fr) minmax(0, 8fr) !important;
  }
}


/* ===========================================================================
 * 9. CE QUE CE PONT NE TRADUIT PAS ENCORE
 * - `.cb-title-inline` sur `uael-advanced-heading` : DS26 n'a tranché que
 *   `.cb-title-lockup` (l'ordre empilé, seul point soulevé par DS24). La
 *   composition "en ligne" reste une décision d'emploi séparée.
 * - Le kit Elementor global (`8336`) lui-même : hors mandat DS24/DS25/DS26
 *   (`A02` a tranché son contenu, l'application est un autre ticket).
 * - Le script DianaWebber « 4d04cb0d » du bloc Bienvenue (H02) : reste
 *   bloqué par la restructuration d'arbre nécessaire (image intercalée),
 *   pas par un atome manquant — `.cb-heading` ne le couvre pas et ne doit
 *   pas le couvrir (DianaWebber n'est jamais autonome, docs/typographie.md
 *   § 3) — hors mandat DS09, signalé pour mémoire.
 * - `.cb-card--split` en boxed (`.e-con > .e-con-inner`) : le ciblage des
 *   2 colonnes (`> .elementor-element:nth-child(1/2)`) suppose que les
 *   containers média/corps sont les seuls enfants directs du nœud flex —
 *   vrai sur tout le motif relevé (docs/composants-prod.md § 1.1, ordre
 *   d'enfants constant), pas vérifié sur un cas boxed réel spécifiquement
 *   (DS12 n'a rien appliqué à un bloc, § « rayon d'action » du journal).
 * - La grille de l'agencement (C) elle-même (plusieurs `.cb-card--stacked`
 *   côte à côte, réception/mariages) : délibérément hors de cette famille —
 *   c'est une composition de BLOC (gap + display flex/grid sur un
 *   container parent), pas une classe de carte. Voir la décision dans
 *   atoms.css § .cb-card, pied de section, et tickets/journal/DS12.md.
 * ========================================================================= */


/* ===========================================================================
 * 10. CORRECTIF DE PLUGIN — le débordement horizontal de 281 px du site
 * entier (P05b, poste 10 du devis)
 *
 * ⚠️ CE BLOC N'EST PAS UNE TRADUCTION D'ATOME. C'est la seule règle de ce
 * fichier qui ne sert aucune classe `.cb-*`, et c'est un écart assumé à
 * `AGENTS.md` (couche 3 = « la traduction vers le DOM réel d'Elementor »).
 * Raison : c'est la seule feuille que nous déployons, et créer une 4ᵉ couche
 * pour une règle coûterait un fichier, une entrée dans
 * `deploy-design-system.sh` et une ligne dans le plugin PHP. *(parti pris —
 * si un 2ᵉ correctif de plugin arrive, la 4ᵉ couche devient le bon choix.)*
 *
 * LE DÉFAUT, mesuré au CHARGEMENT et sans interaction, sur les 25 pages :
 *   document.scrollWidth = 1721 pour clientWidth = 1440 → 281 px de bande
 *   vide à droite. `P01` l'avait vu, `P05` l'avait localisé.
 *
 * LA CAUSE RÉELLE, et elle est plus profonde que « un panneau mal placé » :
 * **le container de header `83c0419` est rendu DEUX FOIS** dans
 * `header.elementor-8376` (mesuré : `:nth-child(1)` et `:nth-child(2)`,
 * mêmes 1440×101, mêmes 4 images, mêmes 4 panneaux de méga-menu chacun).
 *   - la copie 1 est `position: fixed`, `z-index: 99` — c'est le header
 *     qu'on voit, rendu collant par le snippet `header-scroll-effect` ;
 *   - la copie 2 est `position: relative` — elle ne se voit pas, elle
 *     RÉSERVE les 101 px de flux sous un header sorti du flux. C'est une
 *     cale, pas un doublon inutile : la masquer remonterait toute la page
 *     de 101 px sous le header fixe (vérifié).
 * Le JS d'ElementsKit écrit en inline `left:-281px` sur chaque panneau pour
 * le ramener à x=0 ; sur les 8 panneaux des deux copies, il en sert 7 et
 * oublie le dernier, qui reste à x=281 avec width 1440 → right = 1721.
 * Il se corrige au premier `resize`, jamais au chargement.
 *
 * POURQUOI ON CLIPPE LA CALE, ET RIEN D'AUTRE : ses panneaux sont
 * inatteignables (la copie 1, `fixed` et `z-index:99`, reçoit tous les
 * survols au même endroit de l'écran), donc les clipper ne retire aucune
 * fonction. Mesuré après la règle : `scrollWidth` = 1440, la cale fait
 * toujours ses 101 px, et le vrai header garde `overflow: visible` — son
 * panneau sort toujours en 1440 à x=0.
 *
 * ⚠️ CE QU'ON NE FAIT PAS, ET POURQUOI :
 *   - `body{overflow-x:hidden}` : rayon bien plus large, et il casse tout
 *     `position:sticky` descendant. On clippe un élément nommé, pas la page.
 *   - viser le panneau fautif par `:not([style*="left"])` — ESSAYÉ, DÉPLOYÉ,
 *     et **sans aucun effet** : la règle est bien dans la feuille servie et
 *     `querySelectorAll` la fait matcher 1 élément, mais le `display`
 *     calculé reste `block`. La même règle injectée à l'exécution, elle,
 *     fonctionne. Cause probable : Blink ne réinvalide pas les sélecteurs
 *     d'attribut `[style]` quand le JS réécrit le style inline —
 *     *(à vérifier)*. Retenir surtout la leçon : **ne pas indexer une règle
 *     CSS sur l'attribut `style`.**
 *   - supprimer la copie 2 : c'est la cale. Le vrai remède est que le
 *     snippet réserve la hauteur avec une div vide au lieu de cloner tout
 *     le header — ce qui supprimerait aussi la moitié des 3,09 Mo de photos
 *     de panneaux. C'est du ressort de `H01b` (édition de snippet), pas
 *     d'une feuille de style.
 *
 * ✅ ═══ RÈGLE RETIRÉE LE 2026-08-13 PAR `H01e` — LA CAUSE EST MORTE ═══
 *
 * Tout ce qui précède reste vrai comme HISTOIRE, et c'est pour ça qu'on le
 * garde. Mais le diagnostic « c'est du ressort de H01b, édition de snippet »
 * était FAUX, et il valait la peine d'être corrigé : la cale ne venait d'aucun
 * snippet. `21-header-scroll-effect.php` est du CSS pur, sans une ligne de
 * logique collante. La copie 2 était le **`.elementor-sticky__spacer`
 * d'Elementor Pro** — le clone que sa fonction *Sticky* fabrique en JS, déclaré
 * par le seul réglage `sticky: "top"` sur `83c0419`.
 *
 * Ce réglage a été supprimé (`restructure-h01e.py`) et remplacé par le
 * `position: sticky` natif du § 11. Mesuré après coup, sur la page servie :
 * `document.querySelectorAll('.elementor-sticky__spacer').length` = **0**, et
 * `document.documentElement.scrollWidth` = **1440** pour un viewport de 1440.
 * Plus de clone, donc plus de panneau fautif à clipper : la règle n'avait plus
 * rien à viser. La laisser en double filet aurait masqué la réparation.
 *
 * Et ce n'est pas que le débordement qui tombe : le clone portait 4 `<img>`,
 * 14 nœuds à fond CSS et **39 liens tabulables** — c'est-à-dire le doublement
 * de poids de `P05b`/`P05d` et le double parcours clavier de `P05c`. Trois
 * défauts, une cause, un remède.
 * ========================================================================= */

/* ===========================================================================
 * 11. `.cb-headerbar` / `.cb-nav` — le header à deux rangées (H01e, poste 4)
 *
 * ⚠️ TOUT CE BLOC EST ÉCRIT SUR LE DOM RÉEL RELEVÉ, pas sur un DOM supposé
 * (`DS24`). Le template `8376` a été restructuré le 2026-08-13 ; les valeurs
 * ci-dessous sont celles mesurées à 1440 px APRÈS restructuration, avec
 * `mesurer-au-rendu.py` — l'outil qui pose la largeur, parce que `cdp.py` ne la
 * pose pas et mesurait en réalité à 756 px.
 *
 * --- 11.1 Les groupes doivent se dimensionner sur leur contenu -------------
 *
 * LA DIVERGENCE, et c'est LA raison d'être de ce bloc. En markup neutre, un
 * `.cb-headerbar__group` est une boîte qui se dimensionne sur son contenu :
 * les deux groupes de la rangée utilitaire sont donc étroits, et le
 * `justify-content: space-between` de la rangée les repousse aux deux bords.
 * En Elementor, TOUT container reçoit `width: var(--width)` avec `--width:
 * 100%` — les deux groupes prennent alors **704 px chacun** (mesuré), la
 * rangée n'a plus un pixel de jeu, et `space-between` ne fait plus rien : le
 * « RÉSERVER » se retrouve à **x = 769** au lieu du bord droit.
 *
 * `width: auto` rend aux groupes le comportement que l'atome suppose. C'est le
 * cas d'école de la couche 3 : la couche 2 décrit une intention, Elementor
 * impose une largeur, le pont réconcilie les deux.
 * ========================================================================= */
.elementor .cb-headerbar__group{
  width: auto;
}

/* La famille se déclare sur la barre, pas seulement sur chaque libellé. Sans
   ça, tout ce qui n'a pas sa propre déclaration hérite du `body` — Teachers
   dans la page de revue, **Poppins** sur la préprod. Six divergences d'un coup
   dans la comparaison, toutes du même héritage. */
.cb-headerbar{
  font-family: var(--cb-font-body);
}

/* --- 11.4bis Les descendants peints d'Elementor doivent reprendre la forme
 * des atomes que porte le wrapper. Relevé par `comparer-header.py` : la classe
 * `.cb-nav__item` posée sur un widget bouton ne descend pas jusqu'au `<a>`, qui
 * gardait le `gap: 12px` de `.cb-interactive` au lieu des 8 de l'atome de nav,
 * et le centrage imposé par Elementor. */
.cb-headerbar .cb-nav__item .elementor-button{
  gap: var(--cb-space-xs) !important;
  justify-content: normal !important;
}
/* ⚠️ ET LE TÉLÉPHONE N'AVAIT PAS DE BOÎTE DU TOUT — c'est le point 1 de `H01n`,
   celui qu'Alexandre décrit comme « des incohérences d'écarts sur le premier row
   entre les différents items ». Mesuré sur `/hotel/` à 1440, hauteur des cinq
   items de la rangée utilitaire :

       Français 42   téléphone 16   Contact 42   cadeau 42   Réserver 42

   La cause n'est pas un écart, c'est une BOÎTE MANQUANTE. Le téléphone est un
   widget `icon-list` : il porte bien `.cb-interactive .cb-btn .cb-nav__item`,
   mais toute la forme du contrôle (padding 12/16, bordure, rayon) est posée par
   le pont sur `.elementor-button` — un nœud que l'icon-list n'a pas. Son `<li>`
   ne recevait rien, d'où 16 px de haut au lieu de 42, une ligne optique
   différente de ses voisins, et aucune surface de survol.

   ⚠️ ET LES ÉCARTS, EUX, SONT JUSTES — vérifié avant de les toucher : le groupe
   de gauche a `gap: 16px` (`40000182:18606`) et celui de droite `gap: 8px`
   (`40000182:18619`). Les deux sont MESURÉS sur la frame validée, et
   `atoms.css:2347` le dit déjà : « deux écarts différents, deux mesures ». Les
   uniformiser aurait remplacé une source par un parti pris. On n'y touche pas.

   Le `<li>` est le bon porteur : c'est la ligne cliquable, et c'est déjà lui que
   visent les règles de survol des deux régimes (§ 11.5). */
.cb-headerbar .cb-nav__item .elementor-icon-list-item{
  display: inline-flex !important;
  align-items: center !important;
  gap: var(--cb-space-xs) !important;
  padding: var(--cb-space-sm) var(--cb-space-md) !important;
  border: 1px solid transparent !important;
  border-radius: var(--cb-radius-sm) !important;
}
/* Le `<span>` du libellé porte un `padding-left: 5px` d'ElementsKit qui double
   le gap qu'on vient de poser — 5 px n'est aucun token du système. */
.cb-headerbar .cb-nav__item .elementor-icon-list-text{
  padding-inline-start: 0 !important;
}
.cb-headerbar .cb-interactive--solid-light .elementor-button{
  justify-content: normal !important;
}
/* Le bouton icône : `btn/icon` est carré par construction — largeur d'un
   cadratin en `content-box`, padding `--cb-space-sm` sur les quatre côtés, pas
   de gap puisqu'il n'y a pas de texte à séparer. Elementor lui posait 16px de
   padding horizontal et un gap de 12, d'où une boîte de 50 au lieu de 42. */
.cb-headerbar .cb-btn--icon .elementor-button{
  box-sizing: content-box !important;
  width: 1em !important;
  padding: var(--cb-space-sm) !important;
  gap: 0 !important;
  justify-content: center !important;
}

/* --- 11.2 La navigation ne s'enroule pas ---------------------------------
 *
 * ElementsKit rend le menu en `<ul class="elementskit-navbar-nav">` avec
 * `flex-wrap: wrap` (relevé). Mesuré avant correctif : la nav enroulait sur
 * **deux lignes** dès 1440, ce qui n'est pas un défaut de largeur mais un
 * défaut de réglage — la frame pose `nowrap` sur les 12 libellés de contrôle,
 * et le journal de `H01` étape 1 note que « ce n'était pas une précaution,
 * c'était la spécification, et elle manquait ».
 *
 * ⚠️ On vise le `<ul>` interne, PAS `.cb-nav` : la classe atterrit sur le
 * `.elementor-element` extérieur du widget, jamais sur le markup rendu par le
 * plugin — c'est la règle générale de ce fichier (`docs/elementor.md`). */
.cb-nav .elementskit-navbar-nav{
  flex-wrap: nowrap;
}
/* ⚠️ ElementsKit pose une HAUTEUR FIXE de 30px sur son conteneur de menu
   (`.elementskit-menu-container{height:30px}` dans le CSS compilé du widget).
   Elle écrasait la rangée : items de 42px dans un conteneur de 30, rangée à
   **57px** au lieu des 67 de la revue. `auto` rend la hauteur au contenu.

   ⚠️⚠️ ET CETTE RÈGLE EST DESKTOP-SEULEMENT, depuis le 2026-08-19 — sans le
   `@media`, elle CASSAIT LE TIROIR MOBILE, et c'était la régression qu'Alexandre
   signalait (« toujours pas en full height de l'écran, toujours pas scrollable »).
   Le MÊME sélecteur désigne deux choses selon la largeur : au-dessus de 1025 px
   c'est la rangée de nav du header (le défaut des 30 px) ; en dessous c'est le
   TIROIR hors-écran, à qui ElementsKit donne légitimement `height: 100%`.
   Notre `auto !important` battait ce `100%` : mesuré à 390 sur `/hotel/`, le
   tiroir faisait **422 px sur un écran de 844** une fois ouvert, et **2042 px**
   une fois quatre sous-menus dépliés — un conteneur `position: fixed` plus haut
   que l'écran, donc dont le bas est INATTEIGNABLE. La production, elle, sert
   **844 px dans les trois états** et laisse son contenu défiler à l'intérieur.
   Le défaut se voyait surtout page défilée et sous-menus ouverts, exactement
   comme il l'a décrit.
   La leçon est celle du § 13 de DS46, une fois de plus : un widget n'a pas UN
   DOM, il en a autant que de paliers. */
@media (min-width: 1025px){
  .cb-nav .elementskit-menu-container{
    height: auto !important;
  }
}

/* --- 11.3 Le header colle, en natif -------------------------------------
 *
 * Décision d'Alexandre du 2026-08-12 : on abandonne le *Sticky* d'Elementor Pro
 * au profit du natif. Le réglage `sticky: "top"` est retiré de `83c0419` par
 * `restructure-h01e.py` ; c'est ici que le comportement revient.
 *
 * ⚠️ POURQUOI SUR LE `<header>` ET PAS SUR LE CONTAINER : un élément `sticky`
 * ne voyage que dans les limites de son bloc conteneur. Posé sur `83c0419`, il
 * n'aurait nulle part où coller — `<header>` ne fait que la hauteur du header.
 * C'est le `<header>`, enfant direct de `div.hfeed`, qui a toute la page devant
 * lui. Éprouvé au rendu avant d'être écrit (témoin injecté puis `scrollTo`,
 * `docs/prod.md` § 4.6).
 *
 * ⚠️ ET C'EST CE QUI DONNE LE COMPORTEMENT DEMANDÉ, sans une ligne de JS : la
 * rangée 1 porte déjà `.cache-boutons-scroll`, la classe du snippet
 * `20-smart-header-cache-boutons-scroll.php`, qui l'effondre à la descente et
 * la rend à la remontée. Header collant + rangée 1 escamotable = « seule la 2ᵉ
 * rangée reste collée ». Le mécanisme existait, il tombait déjà sur la bonne
 * rangée.
 *
 * 🚨 CE QUI RESTE DÛ, ET QUI N'EST PAS ICI : sans le Sticky d'Elementor, la
 * classe `.elementor-sticky--effects` n'apparaît plus. Or
 * `21-header-scroll-effect.php` et `19-immersive-mode-hero.php` gardent TOUTES
 * leurs règles derrière un `:not(.elementor-sticky--effects)` — leur `:not()`
 * est donc toujours vrai, et le régime « header transparent » de l'accueil ne
 * se coupe plus au défilement. Il faut une sentinelle qui repose un état
 * équivalent. Détail et parade : `docs/prod.md` § 4.6. */
/* ⚠️ 11.3 bis — LA HAUTEUR DU REPOS EST RÉSERVÉE (V16, 2026-08-19).
 *
 * Le défaut : `20-smart-header-cache-boutons-scroll.php` effondre la rangée
 * utilitaire (66 px) au-delà de 600 px de défilement. Le header étant `sticky`,
 * il OCCUPE LE FLUX — les 66 px libérés étaient récupérés par tout ce qui suit,
 * et la page remontait d'un coup. Mesuré : repère du document 0 → −66.
 *
 * Réserver la hauteur du repos éteint le saut. Mais seul, ce geste en crée deux
 * autres, tous deux mesurés le 2026-08-19 — d'où les DEUX contreparties qui
 * l'accompagnent, et sans lesquelles il ne faut pas le poser :
 *
 *   · le dôme de `P03f` se figerait (il lit `--cb-header-h`, que la sentinelle
 *     mesurait sur la BOÎTE du header) → `sentinelle-collant.js` mesure
 *     désormais la BARRE PEINTE, pas la boîte ;
 *   · la bande réservée mangerait les clics sur les 25 pages (le fond du
 *     `<header>` est transparent : `elementFromPoint` y renvoyait le header au
 *     lieu du contenu) → `pointer-events` est rendu au contenu ci-dessous.
 * ------------------------------------------------------------------------- */
header.elementor-location-header{
  min-height: var(--cb-header-repos-mobile);
  /* ⚠️ LE HEADER LAISSE PASSER LES CLICS, SES RANGÉES LES REPRENNENT. Sans ça,
     les 66 px réservés mais non peints interceptent tout ce qui passe dessous. */
  pointer-events: none;
}
header.elementor-location-header .cb-headerbar__row,
header.elementor-location-header .elementskit-menu-container,
header.elementor-location-header button.elementskit-menu-hamburger{
  pointer-events: auto;
}
@media (min-width: 768px){
  header.elementor-location-header{ min-height: var(--cb-header-repos-tablet); }
}
@media (min-width: 1025px){
  header.elementor-location-header{ min-height: var(--cb-header-repos-desktop); }
}
header.elementor-location-header{
  position: sticky;
  top: 0;
  /* Au-dessus du contenu, sous les overlays du site (le popup Day Pass et le
     bandeau cookies vivent bien plus haut). 99 est la valeur qu'Elementor
     posait lui-même sur la copie fixe — on ne change pas l'empilement, on le
     reprend. */
  z-index: 99;
}

/* --- 11.4 L'état « collé » est reposé par la sentinelle -------------------
 *
 * Il y a eu ici, pendant une heure le 2026-08-13, une règle qui repassait le
 * header en `position: static` sur `body.home` et
 * `body.ast-theme-transparent-header`. C'était une mesure de sûreté prise
 * après avoir MESURÉ la régression annoncée : nav en `rgb(255,255,255)` devant
 * un fond `rgb(255,253,240)` sur l'accueil défilée.
 *
 * Elle a été retirée, et il faut savoir pourquoi elle ne pouvait pas rester :
 * elle coûtait le collant sur **31 pages** (celles à en-tête transparent Astra)
 * plus l'accueil — et surtout, combinée à `19-immersive-mode-hero.php` qui
 * escamote le header après 4 s d'inactivité, elle laissait l'accueil **sans
 * aucun menu visible** dès qu'on défilait. Constaté par Alexandre, puis mesuré :
 * à t=3 s le header rendait `opacity: 0; visibility: hidden`, `body` portant
 * `mode-immersif`.
 *
 * Le remède est `design-system/src/sentinelle-collant.js` : il repose
 * `elementor-sticky--effects` sur le `<header>` dès qu'il colle, ce qui rend
 * leur garde aux deux snippets. Rien à écrire ici — cette note est la trace de
 * ce qui a été essayé, pour qu'on ne le réessaie pas. */

/* ===========================================================================
 * 11.5 LE RÉGIME CLAIR / SOMBRE DU HEADER — le commutateur, enfin
 *
 * LE PROBLÈME, posé depuis la 1ʳᵉ passe : les recettes `--on-dark` (ivoire sur
 * média) sont justes sur les **31 pages** à en-tête transparent Astra et sur
 * l'accueil, et FAUSSES sur les autres, où le header est un aplat ivoire. Une
 * classe posée dans le JSON est statique : elle ne sait pas basculer par page,
 * encore moins au défilement.
 *
 * CE QUI DÉBLOQUE : la sentinelle repose `elementor-sticky--effects` dès que le
 * header colle. Le pont dispose donc du même commutateur que les snippets, et
 * peut dire exactement la même chose qu'eux :
 *
 *     régime sombre  ⟺  page à en-tête transparent  ET  header pas encore collé
 *
 * En dehors de ça — page ordinaire, ou header collé sur du contenu — le régime
 * est clair, et les modificateurs `--on-dark` posés dans le JSON doivent être
 * ÉTEINTS. C'est ce que fait ce bloc : il ne peint pas le régime sombre (les
 * atomes le font déjà), il **révoque** les recettes sombres là où elles mentent.
 *
 * ⚠️ POURQUOI `!important` : `21-header-scroll-effect.php` sature ses propres
 * déclarations d'`!important` sur des sélecteurs à 6 classes chaînées. On ne
 * cherche pas à le battre partout — on couvre le cas qu'il ne traite pas (le
 * header collé sur du contenu clair), et il faut y peser autant que lui.
 * ========================================================================= */
/* ⚠️ ON VISE LE DESCENDANT PEINT, PAS LE PORTEUR DE LA CLASSE — c'est la règle
   générale de ce fichier et elle a été payée une fois de plus ici : peindre le
   seul `.elementor-element` laissait le `<a>` intérieur à `rgb(255,253,240)`,
   c'est-à-dire de l'ivoire sur de l'ivoire. Mesuré, pas déduit. Les deux
   familles de descendants sont couvertes : `.elementor-button` pour un widget
   bouton, `.elementor-icon-list-text` / `-icon` pour l'icon-list du téléphone. */
.cb-headerbar.elementor-sticky--effects .cb-interactive--ghost-on-dark,
.cb-headerbar.elementor-sticky--effects .cb-interactive--ghost-on-dark .elementor-button,
.cb-headerbar.elementor-sticky--effects .cb-interactive--ghost-on-dark .elementor-icon-list-text,
.cb-headerbar.elementor-sticky--effects .cb-interactive--ghost-on-dark .elementor-icon-list-icon i,
.cb-headerbar.elementor-sticky--effects .cb-interactive--ghost-on-dark :is(svg, path),
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-interactive--ghost-on-dark :is(svg, path),
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-interactive--ghost-on-dark,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-interactive--ghost-on-dark .elementor-button,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-interactive--ghost-on-dark .elementor-icon-list-text,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-interactive--ghost-on-dark .elementor-icon-list-icon i{
  color: var(--cb-vert) !important;
  fill: currentColor !important;
  /* ⚠️ `--cb-vert` ET PAS `--cb-encre` DEPUIS LE 2026-08-18, et c'est une DÉCISION,
     pas un réglage — c'est le point 3 de `H01n`, resignalé par Alexandre : « toujours
     pas de cohérence entre les menu items de la row 1 (contact, numéro de tel etc) et
     de la row 2 ». Mesuré, header collé à 1440 : rangée 1 à `rgb(33,39,33)` (encre),
     rangée 2 à `rgb(75,97,70)` (olive). Deux couleurs pour un seul rôle — un libellé
     de contrôle du header.
     `(parti pris)` : les DEUX rangées passent sur `--cb-vert`. Il demande de la
     cohérence sans dire laquelle gagne, et le tri se fait sur la PROVENANCE des deux
     candidats — `--cb-olive` est la couleur « primary » héritée de la configuration
     Astra (`tokens.css:33`), donc un accident de configuration ; `--cb-vert` est la
     dominante n°1 du brand book. Entre un accident et une source, on garde la source.
     Le même token peint le CTA « Réserver » juste en dessous : le header entier parle
     alors d'une seule couleur en régime clair. À confirmer à la revue. */
  /* ⚠️ PAS de `border-color` ici — le lint des couches l'a refusé, et il avait
     raison : `.cb-interactive--ghost-on-dark` peint déjà sa bordure sur le
     porteur de la classe (couche 2). La repeindre sur le descendant faisait
     « une bordure dans une bordure » sur du DOM Elementor, exactement le
     défaut de `DS36`. La recette de repos est transparente de toute façon :
     la déclaration était redondante autant que fautive. */
}
/* ⚠️ LE SURVOL NE TOUCHAIT QU'UN DESCENDANT SUR TROIS, et Alexandre l'a vu
   exactement comme ça le 2026-08-18 : « on a aussi des inconsistances d'état hover
   (contact passe en orange dark niquel mais pas le num de tel, pas Français) ».
   Le diagnostic est mécanique : « Contact » est un widget bouton, donc
   `.elementor-button` — la règle l'attrapait. Le téléphone est un `icon-list`
   (`.elementor-icon-list-text` + `.elementor-icon-list-icon i`) et « Français » est
   un lien WPML (`.wpml-ls-link`) : ni l'un ni l'autre n'a d'`.elementor-button`, donc
   ni l'un ni l'autre ne s'allumait. C'est la faute décrite en tête du § 11.5 lui-même
   (« on vise le descendant peint »), commise sur le survol après avoir été corrigée
   sur le repos — les DEUX états ont besoin des TROIS familles de descendants. */
.cb-headerbar.elementor-sticky--effects .cb-interactive--ghost-on-dark:hover .elementor-button,
.cb-headerbar.elementor-sticky--effects .cb-interactive--ghost-on-dark:hover .elementor-icon-list-text,
.cb-headerbar.elementor-sticky--effects .cb-interactive--ghost-on-dark:hover .elementor-icon-list-icon i,
.cb-headerbar.elementor-sticky--effects .cb-interactive--ghost-on-dark:hover .wpml-ls-link,
.cb-headerbar.elementor-sticky--effects .cb-interactive--ghost-on-dark:hover :is(svg, path),
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-interactive--ghost-on-dark:hover .elementor-button,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-interactive--ghost-on-dark:hover .elementor-icon-list-text,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-interactive--ghost-on-dark:hover .elementor-icon-list-icon i,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-interactive--ghost-on-dark:hover .wpml-ls-link,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-interactive--ghost-on-dark:hover :is(svg, path){
  color: var(--cb-cta) !important;
  fill: currentColor !important;
}
/* Le bouton icône : plein ivoire sur média, contour d'encre sur fond clair. */
.cb-headerbar.elementor-sticky--effects .cb-interactive--fill-on-dark .elementor-button,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-interactive--fill-on-dark .elementor-button{
  background-color: transparent !important;
  color: var(--cb-encre) !important;
  fill: var(--cb-encre) !important;
  border: 1px solid var(--cb-encre) !important;
}
/* ⚠️ ET IL LUI MANQUAIT SON SURVOL — signalé par Alexandre le 2026-08-18 : « état
   hover du bouton cadeau : il est en full encre de base quand en sticky et au hover
   ça ne change pas de couleur vers un état hover cohérent alors que ça devrait ».
   Il a raison, et la cause est nette : la recette de l'atome s'allume en posant de
   l'ivoire à 15 % (`atoms.css:409`), ce qui est juste sur une photo sombre et
   INVISIBLE sur un fond ivoire. Un état de survol qui ne se voit pas n'existe pas.
   Le régime clair inverse donc, comme le fait `--fill` partout ailleurs dans le
   système : l'aplat prend la couleur du contour, le contenu passe en ivoire. Même
   vert que le CTA plein juste au-dessus — deux boutons voisins qui s'allument de la
   même façon, c'est la cohérence qu'il demande, pas deux inventions. */
.cb-headerbar.elementor-sticky--effects .cb-interactive--fill-on-dark .elementor-button:hover,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-interactive--fill-on-dark .elementor-button:hover{
  background-color: var(--cb-vert) !important;
  border-color: var(--cb-vert) !important;
  color: var(--cb-ivoire) !important;
  fill: var(--cb-ivoire) !important;
}
/* Le CTA plein : ivoire sur média (l'atome), VERT FONCÉ sur fond clair.
   ⚠️ C'ÉTAIT `--cb-encre`, et Alexandre l'a refusé deux fois (2026-08-17 puis
   2026-08-18 : « bouton principal Réserver toujours en fond dark encre, à corriger
   dans le design system si besoin pour remplacer par green dark »). Mesuré avant
   correction, header collé à 1440 : `background: rgb(33,39,33)`.
   Le motif de l'encre était la lisibilité, et il tenait ; ce qu'il ne tenait pas,
   c'est l'IDENTITÉ — l'encre est le neutre du brand book (texte et contours), le
   bouton principal du site n'a aucune raison d'être peint avec le neutre. Le vert
   profond est la dominante n°1 (`charte.md` § 2.2) et son contraste sur l'ivoire
   reste largement au-dessus du seuil. */
.cb-headerbar.elementor-sticky--effects .cb-interactive--solid-light .elementor-button,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-interactive--solid-light .elementor-button{
  background-color: var(--cb-vert) !important;
  color: var(--cb-ivoire) !important;
  fill: var(--cb-ivoire) !important;
  border-color: var(--cb-vert) !important;
}
/* Et son survol, qui n'était pas décrit en régime clair : l'atome inverse vers
   l'olive (sa recette de régime sombre), ce qui sur un aplat vert ne se lit pas.
   Le survol reprend donc `--cb-cta-hover`, la SEULE destination de survol que le
   système emploie pour un aplat plein — c'est l'argument de convergence de DS18,
   et ça répond au point 5 de `H01n` (« au hover Réserver passe en vert foncé sur
   fond vert normal, je crois pas que c'est ce que prévoit le design system »). */
.cb-headerbar.elementor-sticky--effects .cb-interactive--solid-light .elementor-button:hover,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-interactive--solid-light .elementor-button:hover{
  background-color: var(--cb-cta) !important;
  border-color: var(--cb-cta) !important;
  color: var(--cb-ivoire) !important;
}
/* Les filets et les logos suivent la même bascule : ivoire sur média, encre
   ailleurs. Le filet de l'atome est déclaré en `--cb-ivoire` (la frame). */
.cb-headerbar.elementor-sticky--effects .cb-hairline--under,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-hairline--under{
  border-bottom-color: color-mix(in srgb, var(--cb-encre) 15%, transparent);
}

/* Elementor enferme les rangées dans `.e-con-inner` (1490 px à 2560 px de
   viewport) : leur bordure s'arrêtait avec le contenu, alors que la barre
   couvre tout l'écran. Le contenu reste volontairement borné ; seul le filet
   est projeté sur la largeur du viewport. Le pseudo-élément ne participe pas
   au flux.

   ⚠️ CORRECTIF `V12`, 2026-08-17 — et la phrase qui était ici (« la mesure à
   2560 px confirme qu'il ne crée pas de débordement ») ÉTAIT FAUSSE. Elle
   reposait sur le raccourci que `V12` dénonce : comparer `scrollWidth` à la
   largeur d'ÉMULATION au lieu de `scrollWidth - clientWidth`. Les 15 px de
   barre de défilement masquaient l'écart.

   La cause exacte, mesurée : `width: 100vw` **inclut la barre de défilement**
   (1440) alors que le viewport utile n'en fait que 1425. Le filet, centré,
   débordait donc de (1440−1425)/2 ≈ **8 px à droite** — exactement le
   défilement horizontal constaté sur TOUTES les pages.

   Le correctif est le `overflow-x: clip` posé sur le `<header>` ci-dessous, et
   PAS un changement de largeur du filet : passer le pseudo-élément en
   `width: 100%` supprime bien le débordement, mais ramène le filet à 1490 px
   au-delà de 1490 de viewport — il cesse d'atteindre les bords, ce qui est
   précisément ce que `H01i` avait livré. Mesuré à 1440 / 2000 / 2560 avant de
   choisir. */
/* `V12` — le clip qui rend le filet plein écran sans rendre la PAGE défilable.
   Posé sur le `<header>` et pas plus haut : `html{overflow-x:clip}` comme
   `html{overflow-x:hidden}` CASSENT le header collant (mesuré : à 300 px de
   défilement le header remonte à −300 au lieu de rester à 0). `clip` sur
   l'élément lui-même ne casse pas sa propre adhérence.
   `overflow-x` et non `overflow` : `clip` en x laisse `overflow-y: visible`
   (vérifié au rendu), donc les panneaux du méga-menu descendent toujours hors
   du header. Un `hidden` les aurait rognés.
   Le `<header>` fait exactement la largeur utile du viewport à toutes les
   largeurs mesurées (1425/1985/2545 pour 1440/2000/2560) : le filet continue
   donc d'atteindre les deux bords de l'écran, il perd seulement le dépassement
   invisible qui rendait la page défilable. */
header.elementor-location-header{
  overflow-x: clip;
}
.cb-headerbar{
  --cb-headerbar-hairline-color: var(--cb-ivoire);
}
.cb-headerbar .cb-headerbar__row.cb-hairline--under{
  position: relative;
  border-bottom-color: transparent;
}
.cb-headerbar .cb-headerbar__row.cb-hairline--under::after{
  content: "";
  position: absolute;
  inset-inline-start: 50%;
  inset-block-end: calc(var(--cb-hairline) * -1);
  width: 100vw;
  border-block-end: var(--cb-hairline) solid var(--cb-headerbar-hairline-color);
  pointer-events: none;
  transform: translateX(-50%);
}
.cb-headerbar.elementor-sticky--effects .cb-headerbar__row,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-headerbar__row{
  --cb-headerbar-hairline-color: color-mix(in srgb, var(--cb-encre) 15%, transparent);
}

/* ⚠️ ET LE PENDANT SOMBRE, qui manquait — relevé par la comparaison avec la
   page de revue : la barre y porte `.cb-headerbar--on-dark`, qui pose l'ivoire
   sur TOUT ce qu'elle contient, logos compris. Côté préprod cette classe ne
   peut pas être posée dans le JSON : elle serait fausse sur les pages claires.
   C'est donc ici qu'elle s'applique, sous la même condition que les snippets.
   Sans ça, la barre rendait `rgb(64,64,64)` — la couleur par défaut — au lieu
   de l'ivoire, et les deux logos avec elle. */
/* La barre porte désormais `--on-dark` dans le JSON (le contrat l'exige, porte A
   de `conformite-composant.py`). C'est donc ici que le régime CLAIR la révoque —
   une classe révoquée est lisible, une classe absente est un manque. */
body:is(.home, .ast-theme-transparent-header) .elementor-location-header .cb-headerbar{
  transition: background-color var(--cb-interaction-duration-base) var(--cb-interaction-ease);
}
body:is(.home, .ast-theme-transparent-header) .elementor-location-header .cb-headerbar.elementor-sticky--effects{
  background-color: var(--cb-ivoire);
}
.elementor-location-header .cb-headerbar.elementor-sticky--effects,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-headerbar{
  color: var(--cb-encre);
}
/* ⚠️ LE FOND DES PAGES CLAIRES — et il ne peut plus être omis depuis `H01k`.
   Jusqu'ici, sur une page SANS en-tête transparent, la barre n'avait aucun fond
   propre : ce qui la rendait opaque au défilement était une couche
   `motion-fx` d'Elementor dont l'opacité suivait la POSITION DE SCROLL
   (mesuré : 0 au repos, 0,15 à 60px, 0,90 à 400px, sur 3 % de la hauteur de
   page). C'est ce que voyait Alexandre — un « passage en mode solid » qui se
   rejoue à chaque défilement, au lieu d'un état. La couche est retirée du JSON
   par `H01k` ; sans la règle ci-dessous, ces pages se retrouveraient avec un
   header DÉFINITIVEMENT transparent, du texte sur du contenu défilant.
   PAS de transition ici, volontairement : sur une page claire le header est
   opaque **par nature**, il ne bascule pas d'un régime à l'autre. La transition
   de 250 ms de `H01f` reste réservée aux pages à en-tête transparent, qui, elles,
   ont bien deux états. C'est aussi ce que montre la frame 40000182:17715 :
   header ivoire plein, filet compris, dès le premier pixel. */
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-headerbar{
  background-color: var(--cb-ivoire);
}
/* --- 11.6bis Le sélecteur de langue WPML (H01j) -------------------------
 *
 * WPML sert sa propre feuille et y pose `.wpml-ls-legacy-dropdown a{color:#444}`
 * — un gris qui n'est dans aucune palette du projet. Le lien n'hérite pas de la
 * barre : une couleur héritée perd toujours contre une règle qui matche.
 *
 * ⚠️ LE DÉFAUT EST PLUS LARGE QU'IL N'EN AVAIT L'AIR, et c'est mesuré : il ne
 * touche pas seulement les pages claires. Le snippet 21 force l'ivoire tant que
 * le header n'est PAS collé ; dès qu'il colle, il cesse de forcer et le `#444`
 * ressort — y compris sur l'accueil. Relevé le 2026-08-17 :
 *
 *     accueil, au repos     wpml ivoire   contact ivoire   ✔
 *     accueil, collé 500px  wpml #444     contact encre    ✘
 *     /hotel/, au repos     wpml #444     contact encre    ✘
 *
 * Il était simplement invisible tant que toutes les pages étaient transparentes.
 * La condition est donc EXACTEMENT celle du régime clair de § 11.5 — même
 * couple de sélecteurs, pour que les deux ne puissent pas diverger.
 * Pas d'`!important` : `.wpml-ls-legacy-dropdown a` ne pèse que (0,1,1), nos
 * deux sujets montent bien au-dessus sans avoir à saturer.
 * ===================================================================== */
.elementor-location-header .cb-headerbar.elementor-sticky--effects .wpml-ls-legacy-dropdown a,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .wpml-ls-legacy-dropdown a{
  color: var(--cb-encre);
}
/* ⚠️ ET CETTE RÈGLE-LÀ ALLAIT TROP LOIN — c'est le point 4 de `H01n`, resignalé par
   Alexandre le 2026-08-18 : « le dropdown de header est en encre sur fond encre :
   illisible ». Il a raison, et le correctif d'hier est le coupable.
   Mesuré, header collé à 1440 : `.wpml-ls-sub-menu` — le PANNEAU qui s'ouvre sous
   « Français » — a un fond `rgb(33,39,33)`, c'est-à-dire de l'encre, et il le tient
   de son propre style, indépendamment du régime du header. Le sélecteur ci-dessus
   dit `.wpml-ls-legacy-dropdown a`, ce qui attrape le déclencheur ET les liens du
   panneau : les liens du panneau sont donc passés en encre sur de l'encre.
   La distinction à faire n'est pas « quel régime » mais « quel FOND » : le
   déclencheur est sur le fond du header, les items sont sur le fond du panneau. Le
   panneau reste sombre, ses liens restent ivoire. `docs/pieges.md` § 5 en une ligne :
   un correctif se vérifie sur tous les nœuds que son sélecteur attrape, pas
   seulement sur celui qu'on regardait. */
.elementor-location-header .wpml-ls-sub-menu,
.elementor-location-header .wpml-ls-sub-menu a,
.elementor-location-header .cb-headerbar.elementor-sticky--effects .wpml-ls-sub-menu,
.elementor-location-header .cb-headerbar.elementor-sticky--effects .wpml-ls-sub-menu a,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .wpml-ls-sub-menu,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .wpml-ls-sub-menu a{
  /* Le PANNEAU lui-même est visé en plus de ses liens : mesuré après le premier
     correctif, le conteneur héritait encore du vert de la rangée. Aucun texte n'y
     vit hors des `<a>` aujourd'hui — mais une couleur d'héritage fausse est une
     mine posée pour le prochain item que WPML y ajoutera. */
  color: var(--cb-ivoire);
}
@media (prefers-reduced-motion: reduce){
  body:is(.home, .ast-theme-transparent-header) .elementor-location-header .cb-headerbar{
    transition-duration: var(--cb-interaction-duration-reduced);
  }
}
/* ⚠️ ET IL FAUT LE DIRE AUX ITEMS EUX-MÊMES : `21-header-scroll-effect.php`
   force `color: #ffffff !important` sur `.ekit-menu-nav-link`. Ce n'est pas
   l'ivoire de la charte (`#FFFDF0`) — c'est du blanc pur, une couleur qui
   n'existe pas dans la palette. L'exclusion `:not(.cb-btn)` posée dans le
   snippet ne l'attrape pas : cette règle-là vise la classe d'ElementsKit, pas
   un widget bouton. On la reprend ici plutôt que d'élargir encore le snippet —
   une exclusion de plus y aurait été une 7ᵉ classe chaînée. */
body:is(.home, .ast-theme-transparent-header) .cb-headerbar:not(.elementor-sticky--effects) .elementskit-navbar-nav > li > a,
body:is(.home, .ast-theme-transparent-header) .cb-headerbar:not(.elementor-sticky--effects) .cb-headerbar__logo a{
  color: var(--cb-ivoire) !important;
}

/* ⚠️ ET LE RÉGIME SOMBRE N'AVAIT AUCUN SURVOL SUR LES LIBELLÉS FANTÔMES — trouvé
   en vérifiant le correctif de régime clair, avec une VRAIE souris
   (`Input.dispatchMouseEvent`, la seule méthode que ce projet n'ait pas écartée) :
   « Contact » et le téléphone ne changeaient RIEN au survol, ni sur le widget ni
   sur le bouton. L'atome décrit bien l'état (`atoms.css:405`, ivoire à 15 %) mais
   il le peint sur le PORTEUR de la classe, en couche 2 et sans `!important` ; sur
   le DOM d'Elementor la classe atterrit sur le widget, dont le fond est déjà
   compilé par le widget lui-même. Un état de survol qui ne se voit pas n'existe
   pas — c'est le même constat que pour le bouton cadeau en régime clair, et c'est
   ce qu'Alexandre décrit quand il dit qu'il y a « des inconsistances d'état hover ».
   Même destination que partout ailleurs sur fond sombre : une seule façon de
   s'allumer dans tout le système (DS18). */
body:is(.home, .ast-theme-transparent-header) .cb-headerbar:not(.elementor-sticky--effects) .cb-interactive--ghost-on-dark:hover .elementor-button,
body:is(.home, .ast-theme-transparent-header) .cb-headerbar:not(.elementor-sticky--effects) .cb-interactive--ghost-on-dark:hover .elementor-icon-list-item,
body:is(.home, .ast-theme-transparent-header) .cb-headerbar:not(.elementor-sticky--effects) .cb-interactive--ghost-on-dark:hover .wpml-ls-legacy-dropdown > a{
  background-color: color-mix(in srgb, var(--cb-ivoire) 15%, transparent) !important;
  border-radius: var(--cb-radius-sm) !important;
}

/* ⚠️ ET LE RÉGIME CLAIR N'ÉTAIT PAS DÉCRIT DU TOUT POUR CES ITEMS — c'est la
   deuxième moitié du point 3 de `H01n`. Le snippet ne parle que du régime sombre,
   et le pont non plus : dès que le header colle, les 7 items de nav retombent sur
   la recette d'ElementsKit, qui sert `rgb(75,97,70)` — l'olive « primary » d'Astra.
   Pendant ce temps la rangée utilitaire juste au-dessus était peinte en encre par
   le § 11.5. Deux couleurs, aucune décision derrière : c'est exactement le défaut
   qu'Alexandre décrit.
   Les deux rangées disent maintenant la même chose, et c'est le même token qu'au
   § 11.5 — pas une valeur recopiée, la même source. Le survol reprend `--cb-cta`,
   comme les libellés de la rangée 1 : une seule façon de s'allumer sur fond clair
   dans tout le header. */
.cb-headerbar.elementor-sticky--effects .cb-nav .elementskit-navbar-nav > li > a,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-nav .elementskit-navbar-nav > li > a{
  color: var(--cb-vert) !important;
}
.cb-headerbar.elementor-sticky--effects .cb-nav .elementskit-navbar-nav > li > a:hover,
.cb-headerbar.elementor-sticky--effects .cb-nav .elementskit-navbar-nav > li > a:focus-visible,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-nav .elementskit-navbar-nav > li > a:hover,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-nav .elementskit-navbar-nav > li > a:focus-visible{
  color: var(--cb-cta) !important;
}

/* --- 11.6 Les items de nav portent la recette d'ElementsKit --------------
 *
 * Les 7 items ne peuvent pas recevoir de classe depuis Elementor : ElementsKit
 * les rend à partir du menu 62. C'est donc ici qu'on leur donne la forme de
 * l'atome `nav/item` — 16px, casse normale, pas d'interlettre, `nowrap`, que la
 * frame prescrit et qu'Alexandre a confirmés (« 16px, plus de letter spacing,
 * casse normale, c'est assumé »). Le CSS compilé du widget pose aujourd'hui
 * 13px, `uppercase` et 1,5px d'interlettre : on écrit plus spécifique que lui. */
/* ⚠️ IL FAUT LA FORME ENTIÈRE, PAS SEULEMENT LA TYPO — et c'est la comparaison
   propriété par propriété avec la page de revue qui l'a établi
   (`docs/preuves/H01e/comparer-header.py`) : sur 13 propriétés comparées,
   l'item de nav de la préprod en manquait 13. Dans le markup neutre un item
   compose `.cb-interactive` + `.cb-btn` + `.cb-nav__item` — il hérite donc du
   padding 12/16, du rayon 4px, de l'interligne 1 et du gap de 8px. Côté
   ElementsKit le `<a>` n'a rien de tout ça : il rendait **30,8px de haut,
   9px de padding, 0 de rayon, interligne 28,8px**. On reprend ici les
   déclarations des trois atomes, littéralement. */
.cb-nav .elementskit-navbar-nav > li > a{
  display: inline-flex !important;
  align-items: center !important;
  gap: var(--cb-space-xs) !important;
  font-family: var(--cb-font-body) !important;
  font-size: var(--cb-text-base) !important;
  font-weight: var(--cb-weight-control) !important;
  line-height: 1 !important;
  text-transform: none !important;
  letter-spacing: normal !important;
  white-space: nowrap !important;
  padding: var(--cb-space-sm) var(--cb-space-md) !important;
  /* ⚠️ `transparent`, PAS `none` — `.cb-interactive` pose `border: 1px solid
     transparent`, et cette bordure invisible fait partie de la hauteur : sans
     elle l'item rendait **40px** au lieu de 42, mesuré. Une bordure
     transparente n'est pas une décoration, c'est de la place réservée pour
     l'état de survol. */
  border: 1px solid transparent !important;
  border-radius: var(--cb-radius-sm) !important;
}
/* ⚠️ `!important` OBLIGATOIRE ICI, et mesuré : Elementor recompile pour ce
   widget `.elementor-8376 .elementor-element.elementor-element-dbe02d6
   .elementskit-navbar-nav > li > a` — quatre classes, contre nos trois. Sans
   `!important`, la nav restait à **13px, `uppercase`, 1,5px d'interlettre**
   alors que la règle était bien servie. C'est le cas d'école de
   `docs/elementor.md` : égaler la spécificité d'Elementor ne suffit pas, il
   faut peser autant que lui. */

/* --- 11.7 Les logos : la classe est sur le widget, l'image est dedans -----
 *
 * L'atome `.cb-headerbar__logo` est écrit pour être posé DIRECTEMENT sur un
 * `<img>` (c'est dit dans `atoms.css`). En Elementor la classe atterrit sur le
 * `.elementor-element` extérieur : la hauteur s'appliquait donc au conteneur
 * pendant que l'image gardait la sienne. Mesuré : conteneur **48 × 32**, image
 * **48 × 59** — elle débordait de 27 px sous la rangée, et le logo partenaire
 * sortait écrasé à **48 × 12** au lieu de 24 de haut.
 *
 * Les deux hauteurs viennent de la frame, pas d'une échelle : monogramme 32 px
 * (`40000182:18626`), partenaire 24 px (`40000182:18635`). `width: auto` pour
 * que le rapport du tracé décide de la largeur — jamais la boîte. */
.cb-headerbar__logo img{
  height: 32px !important;
  width: auto !important;
  max-width: none;
  object-fit: contain;
}
.cb-headerbar__logo--partenaire img{
  height: 24px !important;
}

/* --- 11.8 Les filets verticaux, rendus par un widget `divider` -------------
 *
 * Le contrat pose `<span class="cb-hairline--between">` : une boîte de 0 de
 * large et 24 de haut dont la BORDURE GAUCHE est le trait. Côté Elementor, le
 * widget `html` — qui aurait reproduit ce markup à l'identique — **n'est pas
 * enregistré sur cette installation** (relevé au `widgets_manager`, et le nœud
 * était sauté SANS AUCUNE ERREUR). C'est donc un `divider` qui porte la classe.
 *
 * Deux choses à faire, et une seule serait insuffisante :
 *   1. neutraliser le tracé PROPRE du divider, sinon on aurait deux filets dont
 *      un horizontal ;
 *   2. rendre au porteur sa largeur de 0 — Elementor étire ses widgets, et une
 *      boîte de 0 est précisément ce que l'atome demande. */
.cb-headerbar .cb-hairline--between{
  width: 0 !important;
  flex: 0 0 auto;
}
.cb-headerbar .cb-hairline--between .elementor-widget-container,
.cb-headerbar .cb-hairline--between .elementor-divider{
  display: none;
}
/* ⚠️ ET LE FILET DOIT CHANGER DE COULEUR AVEC LE RÉGIME. L'atome le déclare en
   `--cb-ivoire` (la frame ne montre que le régime sombre) : sur une page claire,
   c'était donc de l'ivoire sur de l'ivoire — invisible. Constaté au zoom sur
   `/mentions-legales/`, pas au calcul. Même bascule que `--under` : le § 11.5
   traitait déjà le filet horizontal et avait oublié le vertical. */
.elementor-location-header .cb-headerbar.elementor-sticky--effects .cb-hairline--between,
body:not(.home):not(.ast-theme-transparent-header) .elementor-location-header .cb-hairline--between{
  border-inline-start-color: color-mix(in srgb, var(--cb-encre) 25%, transparent);
}

/* ===========================================================================
 * 11.9 Les quatre défauts relevés par Alexandre le 2026-08-13
 *
 * Tous les quatre ont la même famille de cause : **une classe posée sur un
 * widget peint son porteur, pas les descendants qu'Elementor fabrique dedans.**
 * L'atome dit vrai, le DOM réel a une couche de plus. C'est exactement le rôle
 * de cette couche-ci, et c'est le troisième ticket où le même piège se referme.
 * ========================================================================= */

/* --- 11.9a Le lien du téléphone : orange et vert au lieu d'ivoire ---------
 *
 * Mesuré : le WIDGET est bien à `rgb(255,253,240)` — `--on-dark` fonctionne —
 * mais le `<a>` sort à `rgb(223,106,46)` (l'accent orange d'Astra) et le texte
 * comme le tracé du SVG à `rgb(75,97,70)` (le vert olive du kit). Vider
 * `text_color` sur le widget ne suffisait donc pas : il restait des règles pour
 * `.elementor-icon-list-text` et la couleur de lien du thème.
 *
 * Le SVG porte `fill="currentColor"` : lui donner la bonne `color` suffit, mais
 * on double par `fill` parce qu'Elementor pose parfois un `fill` explicite. */
body:is(.home, .ast-theme-transparent-header) .cb-headerbar:not(.elementor-sticky--effects) :is(.cb-nav__item, .cb-btn) :is(a, .elementor-button, .elementor-icon-list-text, .elementor-icon-list-icon, .wpml-ls-link),
body:is(.home, .ast-theme-transparent-header) .cb-headerbar:not(.elementor-sticky--effects) :is(.cb-nav__item, .cb-btn) :is(svg, path){
  color: var(--cb-ivoire) !important;
  fill: currentColor !important;
}
/* Le CTA plein est l'exception : fond ivoire, donc texte sombre. */
body:is(.home, .ast-theme-transparent-header) .cb-headerbar:not(.elementor-sticky--effects) .cb-interactive--solid-light :is(.elementor-button, svg, path){
  color: var(--cb-vert) !important;
  fill: currentColor !important;
}

/* ⚠️ ET SON SURVOL DIVERGEAIT ENTRE LES DEUX RÉGIMES — point 5 de `H01n`,
   qu'Alexandre a resignalé le 2026-08-18 : « au hover sur Réserver le bouton
   passe en vert foncé sur fond vert normal, je crois pas que c'est ce que
   prévoit le design system ». Mesuré à la vraie souris : en régime sombre il va
   vers l'olive (la recette d'atome `--solid-light:hover`, qui inverse la paire),
   alors que le régime clair, refait la veille, va vers `--cb-cta`. Un seul
   bouton, deux destinations selon l'endroit de la page où on se trouve.
   Le régime sombre rejoint donc le clair. C'est l'argument de convergence de
   DS18 tenu jusqu'au bout : une seule façon de s'allumer pour ce bouton. */
body:is(.home, .ast-theme-transparent-header) .cb-headerbar:not(.elementor-sticky--effects) .cb-interactive--solid-light .elementor-button:hover{
  background-color: var(--cb-cta) !important;
  border-color: var(--cb-cta) !important;
}
body:is(.home, .ast-theme-transparent-header) .cb-headerbar:not(.elementor-sticky--effects) .cb-interactive--solid-light .elementor-button:hover :is(span, svg, path){
  color: var(--cb-ivoire) !important;
  fill: var(--cb-ivoire) !important;
}

/* --- 11.9b Le bouton cadeau était écrasé à 16 px de large ----------------
 *
 * Mesuré **16 × 42** au lieu de 42 × 42 : la règle `width: 1em` en
 * `content-box` était posée sur le `<a>` intérieur, mais le WIDGET extérieur —
 * celui qu'Elementor dimensionne — restait sur la largeur de son contenu brut.
 * D'où l'impression de « boutons collés » : l'écart de 8 px était juste, c'est
 * le bouton qui manquait de 26 px. */
.cb-headerbar .cb-btn--icon{
  width: auto !important;
  flex: 0 0 auto;
}
.cb-headerbar .cb-btn--icon .elementor-button{
  display: inline-flex !important;
  align-items: center;
}

/* --- 11.9c Le déroulant de langue était COUPÉ, pas masqué ----------------
 *
 * Le panneau est en `position: absolute; z-index: 101` — il est donc bien
 * au-dessus. Ce qui le tronquait, c'est l'**`overflow: hidden !important`** que
 * `20-smart-header-cache-boutons-scroll.php` pose sur `.cache-boutons-scroll`,
 * c'est-à-dire sur notre rangée 1.
 *
 * ⚠️ CE DÉFAUT ÉTAIT ÉCRIT AVANT D'ARRIVER — `docs/prod.md` § 4.6, au moment de
 * décider qu'on réemploierait ce snippet : « `overflow: hidden` sur la rangée →
 * tout ce qui déborde est coupé : un menu déroulant de sélecteur de langue, ou
 * un anneau de focus, y disparaîtrait. » Le noter ne l'a pas empêché.
 *
 * On ne peut pas simplement retirer l'`overflow` : c'est lui qui fait que le
 * `max-height: 0` du snippet ESCAMOTE la rangée au lieu de la laisser dépasser.
 * On le rend donc conditionnel — visible au repos, caché seulement pendant
 * l'effondrement, exactement le moment où il sert. */
/* ⚠️ DEUX CLASSES, PAS UNE — et c'est mesuré, pas prudentiel. Écrit d'abord
   `.cb-headerbar__row--utility { overflow: visible !important }` : la rangée
   rendait TOUJOURS `overflow: hidden`. Le snippet écrit
   `.cache-boutons-scroll { overflow: hidden !important }` — une classe, un
   `!important`, exactement comme nous. À spécificité et importance égales,
   c'est l'ORDRE DES SOURCES qui tranche, et son `<style>` de `wp_head` passe
   après notre feuille. Il faut donc peser plus lourd, pas aussi lourd. */
.cb-headerbar .cb-headerbar__row--utility{
  overflow: visible !important;
}
body.scroll-vers-le-bas .cb-headerbar .cb-headerbar__row--utility{
  overflow: hidden !important;
}

/* --- 11.9d Le sélecteur de langue prend le dessin de l'atome -------------
 *
 * Décision d'Alexandre : « pourquoi on utiliserait pas du css pour obtenir le
 * look and feel qu'on veut ? ». C'est la bonne voie — elle ne dépend pas de ce
 * que WPML voudra bien produire, là où changer de gabarit nous laisserait à la
 * merci du prochain. WPML rend une boîte de formulaire encadrée de 248 px ; le
 * contrat veut « Français ▾ », un item de contrôle comme les autres. */
.cb-headerbar .wpml-ls,
.cb-headerbar .wpml-ls > ul,
.cb-headerbar .wpml-ls-statics-shortcode_actions{
  width: auto !important;
  margin: 0 !important;
  padding: 0 !important;
  border: none !important;
  background: none !important;
  list-style: none;
}
.cb-headerbar .wpml-ls .wpml-ls-item{
  position: relative;
  list-style: none;
  margin: 0;
}
/* Le lien courant devient l'item de contrôle, avec le chevron en pseudo-élément
   — WPML ne fournit aucun nœud pour lui, c'est pourquoi la porte A en compte 7
   sur 8 et pourquoi cette absence est déclarée plutôt que comblée. */
/* ⚠️ DEUX CLASSES DIFFÉRENTES POUR DEUX RÔLES, et viser la mauvaise ne se voit
   qu'au rendu : le lien CLIQUABLE de la langue courante est
   `.wpml-ls-item-toggle` (avec son jumeau `js-`), tandis que `.wpml-ls-link`
   ne désigne que les entrées du panneau. Ma première règle ne visait que la
   seconde — le toggle gardait donc le **fond blanc** que WPML lui pose, sous un
   texte ivoire : un rectangle vide à l'écran. Relevé nœud par nœud. */
.cb-headerbar .wpml-ls .wpml-ls-item-toggle,
.cb-headerbar .wpml-ls .wpml-ls-link{
  display: inline-flex !important;
  align-items: center;
  gap: var(--cb-space-xs);
  padding: var(--cb-space-sm) var(--cb-space-md) !important;
  border: 1px solid transparent;
  border-radius: var(--cb-radius-sm);
  font-family: var(--cb-font-body);
  font-size: var(--cb-text-base);
  font-weight: var(--cb-weight-control);
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  background: none !important;
  border-color: transparent !important;
  box-shadow: none !important;
}
.cb-headerbar .wpml-ls .wpml-ls-item-toggle::after{
  /* ⚠️ IL FAUT TOUT REMETTRE À ZÉRO AVANT DE DESSINER. WPML trace déjà sa propre
     flèche ici, en triangle CSS : `border-top: 8px currentColor` +
     `border-left: 5px transparent`, avec sa propre rotation (relevé au
     `getComputedStyle(el, '::after')`). Mes deux bords s'y AJOUTAIENT — d'où le
     « gros paquet de mouches » qu'Alexandre a vu, et non un chevron.

     Et plutôt que de bricoler des bordures pour imiter un chevron, on emploie
     LE chevron du design system : `assets/icones/chevron-down.svg`, le fichier
     même que le contrat inline. En masque plutôt qu'en image, pour que
     `currentColor` continue de décider de la couleur — c'est la règle de
     `.cb-interactive__icon svg` (« l'icône suit la recette sans qu'aucune règle
     de couleur ne soit écrite pour elle »).

     `1em`, comme toutes les icônes du système : elle suit le palier de texte du
     libellé, donc elle reste juste si ce palier change. */
  content: "\e994";
  border: 0 !important;
  /* ⚠️ `static`, ET C'EST MESURÉ : WPML pose son `::after` en
     `position: absolute; right: 10px; top: 17px`. Le chevron se retrouvait donc
     POSÉ SUR le libellé — visible au zoom, il chevauchait le « s » de
     « Français ». Un item de contrôle range son icône comme les autres : en
     flux, après le texte, aligné par le `align-items: center` du parent. */
  position: static !important;
  inset: auto !important;
  flex: 0 0 auto;
  width: var(--cb-header-chevron-size);
  height: var(--cb-header-chevron-size);
  font-family: elementskit !important;
  font-size: var(--cb-header-chevron-size);
  line-height: 1;
  transform: none;
  background: none;
  -webkit-mask: none;
  mask: none;
}
/* ⚠️ ET IL FAUT LE FERMER — défaut que j'ai INTRODUIT en écrivant la règle
   ci-dessous : lui donner un fond opaque et un `z-index` sans lui dire de se
   masquer au repos l'a laissé ouvert EN PERMANENCE, par-dessus le libellé.
   Sur l'image côte à côte, le sélecteur n'était plus qu'un rectangle gris vide.
   Un panneau déroulant se ferme par défaut ; c'est le survol et le focus qui
   l'ouvrent. Le `:focus-within` n'est pas un supplément : sans lui, le panneau
   est inatteignable au clavier — le défaut que `P05c` a mesuré sur le
   méga-menu, qu'on ne va pas reproduire ici. */
.cb-headerbar .wpml-ls .wpml-ls-sub-menu{
  display: none;
}
.cb-headerbar .wpml-ls .wpml-ls-item:hover > .wpml-ls-sub-menu,
.cb-headerbar .wpml-ls .wpml-ls-item:focus-within > .wpml-ls-sub-menu{
  display: block;
}
/* Le panneau : posé sous l'item, opaque, au-dessus de la rangée suivante. */
.cb-headerbar .wpml-ls .wpml-ls-sub-menu{
  position: absolute;
  inset-inline-start: 0;
  top: 100%;
  min-width: 100%;
  margin: 0;
  padding: var(--cb-space-xs) 0;
  background: var(--cb-encre);
  border-radius: var(--cb-radius-sm);
  z-index: 120;
}

/* --- 11.9e Le panneau reste CLIQUABLE pendant l'appui (REG03) ------------
 *
 * MESURÉ, PAS DÉDUIT — souris réelle (`Input.dispatchMouseEvent`) sur
 * « English » : `mousedown` atteint bien `span.wpml-ls-native`, mais `mouseup`
 * atteint `img` — LE LOGO, qui est sous le panneau — et le `click` retombe
 * donc sur leur ancêtre commun, `div.e-con-inner`. Aucun lien n'est activé :
 * la langue ne change pas, et le panneau n'a pourtant NI bougé NI disparu.
 *
 * La cause est le pressé du design system. `.cb-interactive:active` pose
 * `filter: brightness(var(--cb-interaction-active-dim))` (atoms.css
 * § .cb-interactive) sur le conteneur du widget WPML, et un `filter` non nul
 * CRÉE UN CONTEXTE D'EMPILEMENT : le `z-index: 120` du panneau devient local à
 * ce conteneur, qui n'a pas de `z-index` propre. Le panneau retombe alors sous
 * la rangée 2 — le conteneur du logo et de la nav, `z-index: 10` en CSS
 * par-widget Elementor — pendant TOUTE la durée de l'appui, puis remonte au
 * relâchement. Relevé de la pile au point du curseur (`elementsFromPoint`) :
 *   au repos  : SPAN.wpml-ls-native > A.wpml-ls-link > UL.wpml-ls-sub-menu(z=120) > IMG
 *   à l'appui : IMG > … > elementor-element-2d…(z=10) > SPAN.wpml-ls-native
 *
 * On ne retire pas le `filter` — c'est la recette de pressé du système, et
 * l'interdiction de déplacer ou de mettre à l'échelle un composant la rend non
 * négociable. On classe son porteur au-dessus de la rangée 2 : le contexte
 * d'empilement existe alors EN PERMANENCE et l'ordre ne peut plus changer entre
 * repos et appui.
 *
 * `:has()` plutôt qu'une règle sur les 5 `.cb-interactive` du header : seul le
 * porteur d'un panneau a besoin de ce rang, et le sélecteur dit pourquoi.
 * Spécificité (0,3,0) contre (0,2,0) pour `.cb-interactive:active` — aucun
 * `!important`. */
.cb-headerbar .cb-interactive:has(.wpml-ls-sub-menu){
  /* `relative` est déjà servi par Elementor sur ce conteneur (mesuré) ; déclaré
     pour que le rang ne devienne pas un no-op si ce défaut changeait. */
  position: relative;
  z-index: 11; /* juste au-dessus du 10 mesuré sur la rangée 2 */
}

/* =====================================================================
 * .cb-hero — le corps du hero, cas RÉEL (H01c)
 *
 * TROUVÉ AU RENDU, PAS DANS LE JSON : `.cb-hero__body{padding: var(
 * --cb-flow-group) …}` (atoms.css, 32px) suppose que le corps du hero
 * commence à la vraie limite supérieure de l'écran. C'est faux ici : le hero
 * réel remonte SOUS le header sticky (`margin-top` négatif sur `0ce0045`,
 * exprès — c'est ce qui laisse voir la vidéo À TRAVERS le header transparent
 * du régime immersif, pas un défaut). Avec seulement 32px de retrait, le
 * titre rendait DERRIÈRE la 2ᵉ rangée du header (nav), pas en dessous —
 * signalé par Alexandre (« les éléments sont à moitié sous le header »).
 *
 * Le retrait horizontal (`--cb-page-inset`) reste juste — c'est le
 * VERTICAL qui doit compenser la hauteur du header, RÉELLE et RESPONSIVE
 * (mesurée : 133px à 1440, 123px dès le palier tablette Elementor) — pas une
 * valeur du design system générique, un fait de CETTE installation. D'où la
 * portée étroite : seul le hero de l'accueil (`.home`) est concerné, jamais
 * la page de revue (qui n'a pas ce header sticky-remonté).
 * ================================================================== */
.home .cb-hero__body{
  padding-top: calc(133px + var(--cb-flow-group));
}
@media (max-width: 1024px){
  .home .cb-hero__body{
    padding-top: calc(123px + var(--cb-flow-group));
  }
}

/* =====================================================================
 * § 13 — LE MÉGA-MENU DESKTOP (DS46b, frame Figma 40000272:1939)
 *
 * Pourquoi ce bloc est ICI et pas dans un atome : les 4 panneaux sont des
 * templates `elementskit_content` rendus par ElementsKit, et les 26 cartes
 * sont des widgets Elementor Pro « Call to Action » skin `cover`. Aucune
 * classe `.cb-*` ne peut être posée dessus depuis Elementor — c'est le même
 * cas que les items de nav (§ 11.6) : le markup est produit par le plugin.
 *
 * ⚠️ CE QUI RESTE DÛ, ET IL FAUT LE DIRE : ce bloc habille un composant qui
 * n'a PAS d'atome. C'est un écart assumé à « le design system d'abord »,
 * borné à cette passe d'essai, parce qu'Alexandre veut juger la proposition
 * sur la préprod avant qu'on grave un atome de carte-sur-photo — et parce
 * que `DS37` a explicitement INTERDIT de reconstruire `card/overlay` sans
 * refaire la mesure de contraste sur les photos concernées. La mesure est
 * faite (voir `tokens.css` § méga-menu) ; l'atome et le nettoyage des champs
 * de style des 26 widgets restent à faire → `DS46`.
 *
 * ✅ LES `!important` DES CARTES SONT TOMBÉS (DS46, 2026-08-18). Ils
 * existaient parce qu'Elementor recompilait pour CHAQUE widget un sélecteur à
 * 4 classes (`.elementor-15586 .elementor-element .elementor-element-279dd79d
 * .elementor-cta__content`) là où nous en avons 2 : égaler sa spécificité ne
 * suffisait pas, il fallait peser autant que lui. La sortie n'était pas de
 * surenchérir mais de vider les champs de style des 26 widgets — c'est fait.
 * Le CSS par-widget ne contient plus AUCUNE media query desktop, et les
 * blocs 13.3 à 13.6 sont désormais des règles ordinaires.
 *
 * ⚠️ TROIS `!important` SURVIVENT dans ces blocs, et c'est mesuré : le CSS
 * par-widget déclare encore `transition-duration: 1500ms` sur
 * `.elementor-cta__bg` et `.elementor-cta__bg-overlay`. Cette valeur n'est
 * écrite dans aucun JSON — c'est un DÉFAUT du contrôle, comme `transformation`
 * et `content_animation` (`docs/pieges.md` § 7). Tant qu'on ne l'a pas
 * contredite à la source, notre `transition` doit peser autant qu'elle.
 *
 * ⚠️ Les `!important` des blocs 13.1, 13.2 et 13.7 à 13.10 RESTENT : ils ne
 * combattaient pas le CSS par-widget mais celui d'ElementsKit et du snippet
 * d'animation. Les retirer serait une autre passe, avec sa propre mesure.
 *
 * ⚠️ DESKTOP SEULEMENT. La garde `min-width: 1025px` est celle du snippet
 * `18-animation-fluide-mega-menu.php`, qui anime ces mêmes panneaux : sous
 * 1025px ElementsKit ne rend pas de panneau mais un tiroir hors-canevas, et
 * aucune de ces règles n'aurait de sens dessus.
 *
 * Rayon d'action : le header `8376` est un template `include/general`, donc
 * **les 25 pages publiées**, FR et EN (les 8 templates de panneaux ont la
 * même structure à trois étages — `tickets/journal/P05-DS46-releve.md` § 1.3).
 * ================================================================== */
@media (min-width: 1025px){

  /* --- 13.1 Le panneau reçoit un sol -------------------------------------
   *
   * Aujourd'hui il n'en a AUCUN : `background-color: rgba(0,0,0,0)` mesuré,
   * et le `::before` que `P05` avait relevé est éteint depuis. Résultat
   * visible sur la préprod : le contenu de la page traverse entre les cartes
   * (un titre de section, des pastilles). La frame pose du verre dépoli.
   *
   * 🚨 LE DÉCALAGE SE FAIT EN `padding`, JAMAIS EN `margin` — et c'est une
   * régression payée le 2026-08-17, signalée par Alexandre : « en bougeant la
   * souris ça referme le méga-menu, je ne peux donc jamais y accéder ».
   *
   * Le panneau s'ouvre sur `li:hover`, et il est ENFANT du `li` : tant que le
   * curseur reste dans le sous-arbre, le panneau tient. Sa boîte démarre
   * exactement au bas de l'item (mesuré : item `bas = 121`, panneau `y = 121`)
   * — les deux se touchent, donc la souris passe de l'un à l'autre sans
   * jamais quitter le `li`. Un `margin-top: 13px` déplaçait la BOÎTE : il
   * ouvrait une bande de 13px n'appartenant ni à l'item ni au panneau, où le
   * survol se perdait et le menu se refermait. Le panneau devenait
   * inatteignable, c'est-à-dire cassé.
   *
   * Un `padding-top` fait le contraire : la boîte reste jointive à l'item —
   * donc la continuité du survol est préservée — et seul le CONTENU descend.
   * Le sol est alors peint sur le wrapper de template (§ 13.1bis), pour que
   * les 13px du haut restent transparents. Ils sont survolables et invisibles :
   * exactement ce qu'il faut. */
  .elementskit-megamenu-panel{
    padding-top: var(--cb-megamenu-decalage);
    /* ⚠️ Ni fond ni flou ICI : ils descendent d'un cran (§ 13.1bis), sinon le
       verre teinterait la bande de raccord et donc le bas du header. */
  }

  /* --- 13.1bis Le sol est peint sur le wrapper de template ---------------
   *
   * Elementor enveloppe toujours un template dans `div.elementor.elementor-<id>`
   * (ici `.elementor-15586` et ses 7 jumeaux). C'est le seul nœud qui commence
   * SOUS la bande de raccord et couvre toute la largeur : le sol lui revient. */
  .elementskit-megamenu-panel > .elementor{
    background-color: color-mix(in srgb, var(--cb-ivoire) calc(var(--cb-megamenu-fond) * 100%), transparent) !important;
    -webkit-backdrop-filter: blur(var(--cb-megamenu-flou));
    backdrop-filter: blur(var(--cb-megamenu-flou));
  }

  /* --- 13.1ter Le flou n'apparaît plus « d'un coup » ---------------------
   *
   * Signalé par Alexandre : « le backdrop filter blur apparaît d'un coup une
   * fois l'animation terminée ». Ce n'était pas une transition mal réglée,
   * c'était une bascule BINAIRE, et la cause est dans la spec Filter Effects :
   * un ancêtre dont l'`opacity` est < 1 crée un **BACKDROP ROOT**. Sous une
   * telle racine, `backdrop-filter` n'a plus aucun fond à échantillonner —
   * il ne peint pas « un peu », il ne peint RIEN. Le panneau s'ouvrant par un
   * fondu d'opacité (ElementsKit + `18-animation-fluide-mega-menu.php`), le
   * flou restait donc éteint pendant les 400 ms, puis s'allumait net à
   * `opacity: 1`.
   *
   * MESURÉ, par paires, à opacité constante 0,5, sur `/hotel/` (hero photo,
   * fond fixe et détaillé — voir la note d'instrument ci-dessous) :
   *   - flou sur un descendant, ancêtre à 0,5 → **0 pixel d'écart** avec la
   *     même capture SANS flou, écart max 1/255. Le flou est mort.
   *   - flou porté par l'élément qui porte l'opacité → 72 % des pixels
   *     diffèrent, écart max 95. Le flou vit.
   *   - opacité laissée à 1 + révélation par `clip-path` → **88 % des pixels
   *     diffèrent à 25, 50, 75 et 100 % de la course**, écart max 172. Le flou
   *     vit d'un bout à l'autre.
   *
   * Des deux remèdes possibles, on prend le second. Porter le flou sur le
   * panneau lui-même (le porteur de l'opacité) marcherait, mais son border-box
   * inclut les 13px de raccord : le flou déborderait sur le bas du header et
   * emporterait le filet de l'item survolé (§ 13.7), qui est peint EN DESSOUS
   * du panneau. On garde donc le flou où il est et on change la façon de
   * révéler : plus de fondu d'opacité, un rideau.
   *
   * ⚠️ (parti pris, à confirmer à la revue) : cela change la nature du
   * mouvement — le panneau ne fond plus, il se déroule du haut vers le bas. Le
   * fondu et le flou sont incompatibles par construction, pas par réglage : il
   * fallait choisir lequel des deux garder. Le sobre est de garder le verre,
   * qui est la proposition, et de changer la mécanique d'apparition.
   *
   * ⚠️ Note d'instrument, parce que deux bancs sur trois ont menti : la mesure
   * exige un fond FIXE **et** CHARGÉ. `/spa/` bouge derrière le panneau (deux
   * captures ne comparent pas le même fond) et `/mentions-legales/` est un
   * aplat ivoire (rien à flouter, donc « avec » et « sans » sont identiques
   * même quand le flou marche). Détail dans `docs/pieges.md`. */
  .elementskit-navbar-nav > li > .elementskit-megamenu-panel{
    /* on ne touche PAS `transition` : la `visibility` d'ElementsKit doit garder
       la sienne (elle bascule à l'ouverture et attend la fin à la fermeture).
       Neutraliser opacité et translation suffit — il n'y a plus rien à
       interpoler dessus, donc plus de racine de fond. */
    opacity: 1 !important;
    transform: none !important;
  }
  .elementskit-megamenu-panel > .elementor{
    clip-path: inset(0 0 100% 0);
    transition: clip-path var(--cb-megamenu-revelation) var(--cb-interaction-ease);
  }
  .elementskit-navbar-nav > li:hover > .elementskit-megamenu-panel > .elementor{
    clip-path: inset(0 0 0 0);
  }
  @media (prefers-reduced-motion: reduce){
    .elementskit-megamenu-panel > .elementor{
      transition-duration: var(--cb-interaction-duration-reduced);
    }
  }

  /* --- 13.2 Les cartes vont d'un bord à l'autre --------------------------
   *
   * Les 20px de gouttière d'aujourd'hui viennent de DEUX couches empilées,
   * et c'est pour ça qu'on ne les corrige pas en un seul endroit : 10px du
   * container boxed (`e-con-boxed`, padding `0 10px`) et 10px de la rangée
   * (`padding: 20px 10px 15px`). La frame veut 12px, une seule fois, sur les
   * quatre côtés du panneau. */
  .elementskit-megamenu-panel .e-con-boxed{
    padding-left: 0 !important;
    padding-right: 0 !important;
  }
  .elementskit-megamenu-panel .e-con-inner{
    max-width: none !important;
    width: 100% !important;
  }
  .elementskit-megamenu-panel .e-con-inner > .e-con{
    padding: var(--cb-space-sm) !important;
    gap: var(--cb-space-sm) !important;
  }
  /* ⚠️ `width: 100%` sur la grille, et c'est un CHOIX qui se voit sur deux
     panneaux : celui du Spa portait `width: 75%` (grille de 1050px centrée,
     cartes de 515) et celui des Séminaires n'a aucun réglage de colonnes
     (donc 3 par défaut). Plein-bord, ils rendent 2 × 702 et 3 × 464 au lieu
     de 4 × 345. La frame ne montre que le cas à 4 cartes : les deux autres
     sont une extrapolation cohérente, à confirmer à la revue. */
  .elementskit-megamenu-panel .e-grid{
    width: 100% !important;
    gap: var(--cb-space-sm) !important;
  }

  /* --- 13.3 La carte : plus basse, texte ferré en pied -------------------
   *
   * Le rayon est porté par `.elementor-widget-container`, et c'est bien lui
   * qu'il faut viser : c'est ce nœud qui a l'`overflow: hidden`, donc c'est
   * son rayon qui clippe réellement la photo (relevé `P05`). Le rayon posé
   * sur `.elementor-cta` ne clipperait rien. */
  .elementskit-megamenu-panel .elementor-widget-call-to-action .elementor-widget-container{
    border-radius: var(--cb-radius-sm);
  }
  .elementskit-megamenu-panel .elementor-widget-call-to-action .elementor-cta__content{
    min-height: var(--cb-megamenu-carte-hauteur);
    padding: var(--cb-megamenu-carte-inset);
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
  }

  /* --- 13.4 Le voile en DEUX couches, et c'est le cœur de la proposition --
   *
   * Aujourd'hui : un aplat `rgba(27,24,18,.5)` en `mix-blend-mode: multiply`,
   * uniforme du haut en bas de la carte. Il tient la moyenne et perd sur les
   * pires pixels — 21 boîtes de texte sur 26 sous 4,5:1, mesurées.
   *
   * La frame superpose un aplat de 40 % ET un dégradé qui monte à 60 % à
   * hauteur du texte, soit ~76 % d'opacité composée là où les glyphes se
   * posent. Le `multiply` part avec : deux couches en `multiply` sur une
   * photo claire ne composent pas ce que la frame dessine.
   *
   * Mesuré au pire pixel sur les 26 boîtes des 4 panneaux, fond seul (texte
   * masqué, sinon les glyphes ivoire entrent dans la mesure) : minimum
   * **5,64:1**, aucune boîte sous 4,5:1. */
  .elementskit-megamenu-panel .elementor-widget-call-to-action .elementor-cta__bg-overlay{
    mix-blend-mode: normal;
    background-color: color-mix(in srgb, var(--cb-scrim-color) calc(var(--cb-megamenu-voile-repos) * 100%), transparent);
    background-image: linear-gradient(
      180deg,
      transparent var(--cb-megamenu-voile-bas-depart),
      color-mix(in srgb, var(--cb-scrim-color) calc(var(--cb-megamenu-voile-bas) * 100%), transparent) var(--cb-megamenu-voile-bas-fin)
    );
    /* L'aplat suit la photo au MÊME tempo qu'elle (§ 13.5), et c'est le point
       de la retouche du 2026-08-17 : les deux couches sont UN seul geste — la
       photo respire pendant que le voile se lève. À 250 ms le voile aurait fini
       avant que le zoom ait commencé à se voir, et le survol se lirait comme
       deux effets empilés. On ne transite QUE `background-color` : le
       `background-image` ci-dessus est identique dans les deux frames, et un
       `transition: all` ferait interpoler un dégradé pour rien. */
    transition: background-color var(--cb-megamenu-zoom-duree) var(--cb-interaction-ease) !important;
  }

  /* --- 13.5 Le survol : un frémissement lent, et un voile qui S'ÉCLAIRCIT --
   *
   * 🚨 CE PARAGRAPHE A CHANGÉ DE SENS DEUX FOIS DANS LA MÊME JOURNÉE, et les
   * deux versions sont des décisions d'Alexandre, pas des hésitations de notre
   * part. Il faut lire les trois états pour comprendre le CSS ci-dessous.
   *
   * 1. CE QUE LE THÈME FAISAIT (état de départ, mesuré au survol réel) : la
   *    photo en `scale(1.2)` sur 1 500 ms, le titre ET la description en
   *    `scale(0.85)` — du texte redimensionné en transform — et le voile qui
   *    s'allégeait de 50 % à 30 %. Pire pixel : 2,33 à 2,68:1. L'état le moins
   *    lisible du site.
   * 2. 2026-08-17, matin : « je ne veux plus d'effet de zoom au survol. au
   *    survol on assombrit juste un peu plus l'overlay » — puis « tu peux
   *    aussi remettre un léger scale in des images au hover mais vraiment
   *    léger et snappy ». D'où `scale(1.03)` / 250 ms, et voile 40 % → 52 %.
   * 3. 2026-08-17, après-midi, frames `40000276:2360` et `40000276:2370` :
   *    « garde le zoom mais ralentis-le et surtout éclaircis l'overlay au lieu
   *    de l'assombrir ». C'est l'état ci-dessous.
   *
   * Ce qui SURVIT du point 2, et ce n'est pas un détail : le texte ne bouge
   * toujours pas. Le `scale(0.85)` du titre et de la description était du
   * texte redimensionné en transform, il ne revient dans aucune version. Q08
   * (« jamais un scale du composant entier ») reste respecté : la couche photo
   * bouge à l'intérieur d'un cadre `overflow: hidden`, la carte ne bouge pas
   * d'un pixel et rien ne se déplace autour.
   *
   * ⚠️ Le zoom du thème ne vient d'AUCUN réglage des 8 JSON : `elementor-bg-
   * transform-zoom-in` est la valeur PAR DÉFAUT du contrôle `transformation`
   * d'Elementor Pro (`docs/pieges.md` § 7). On ne peut donc pas « retirer le
   * réglage » ; on neutralise sa règle — c'est ce que fait le `transition` +
   * `transform` ci-dessous, qui réécrit les deux au lieu de les supprimer.
   * Idem `content_animation: shrink`, qui pose `.elementor-animated-item--
   * shrink` sur les deux textes.
   *
   * 🚨 ET LE PLANCHER DE CONTRASTE A CHANGÉ DE CÔTÉ. Tant que le survol
   * densifiait, l'état survolé était le plus lisible et il suffisait de tenir
   * le repos. Maintenant qu'il éclaircit, c'est le SURVOL qui est le pire cas
   * des deux : c'est lui qu'il faut mesurer en premier quand on retouche une
   * photo, une hauteur de carte ou le dégradé de pied. Le dégradé de § 13.4,
   * lui, est identique dans les deux frames — il porte seul la lisibilité du
   * texte, l'aplat ne porte plus que l'ambiance. */
  .elementskit-megamenu-panel .elementor-widget-call-to-action .elementor-cta__bg{
    transition: transform var(--cb-megamenu-zoom-duree) var(--cb-interaction-ease) !important;
  }
  .elementskit-megamenu-panel .elementor-widget-call-to-action .elementor-cta:hover .elementor-cta__bg{
    transform: scale(var(--cb-megamenu-zoom-survol));
  }
  /* Le texte, lui, ne bouge toujours pas. */
  /* ⚠️ La règle qui neutralisait `elementor-animated-item--shrink` a été RETIRÉE
     par `DS46` : `content_animation` vaut désormais la chaîne vide sur les 26
     widgets, et le HTML servi n'émet plus AUCUNE classe `elementor-animated-item--*`
     (vérifié sur `/`, `/spa/`, `/en/spa/`). Ne pas la remettre « au cas où » :
     une règle sans objet cache le jour où l'objet revient.
     🚨 ET SURTOUT, NE PAS SUPPRIMER LA CLÉ pour « faire propre » : la vider est
     un geste DIFFÉRENT de la supprimer. Supprimée, Elementor rend la main à son
     défaut — qui est `grow`, c'est-à-dire le même zoom dans l'autre sens. C'est
     arrivé pendant cette passe et le HTML l'a montré (26 × `--grow`).
     Voir `docs/pieges.md` § 7. */
  .elementskit-megamenu-panel .elementor-widget-call-to-action .elementor-cta:hover .elementor-cta__bg-overlay{
    background-color: color-mix(in srgb, var(--cb-scrim-color) calc(var(--cb-megamenu-voile-survol) * 100%), transparent);
  }
  @media (prefers-reduced-motion: reduce){
    .elementskit-megamenu-panel .elementor-widget-call-to-action .elementor-cta__bg,
    .elementskit-megamenu-panel .elementor-widget-call-to-action .elementor-cta__bg-overlay{
      transition-duration: var(--cb-interaction-duration-reduced) !important;
    }
    .elementskit-megamenu-panel .elementor-widget-call-to-action .elementor-cta:hover .elementor-cta__bg{
      transform: none;   /* un zoom, même léger, reste du mouvement */
    }
    /* ⚠️ Le VOILE, lui, change toujours — on ne coupe que sa transition. C'est
       un état (la carte survolée se distingue), pas un mouvement : le supprimer
       priverait d'affordance ceux qui ont désactivé les animations, alors qu'il
       ne leur coûte rien puisqu'il devient instantané. */
  }

  /* --- 13.6 La typo des cartes ------------------------------------------
   *
   * La frame : titre Fonseca Light 16px/1,5 ; description Teachers Regular
   * 16px/1,4 ; les deux en ivoire. Servi aujourd'hui : titre 20px poids 300,
   * description 16px poids 300, et les deux en **blanc pur**
   * (`rgb(255,255,255)`) — une couleur qui n'existe pas dans la palette,
   * exactement le défaut que `H01e` avait déjà eu à retirer de cette nav. */
  .elementskit-megamenu-panel .elementor-widget-call-to-action .elementor-cta__title{
    font-family: var(--cb-font-caps);   /* Fonseca — titrage, la police du titre sur la frame */
    font-size: var(--cb-text-base);
    font-weight: 300;
    line-height: 1.5;
    color: var(--cb-ivoire);
    margin-bottom: var(--cb-megamenu-carte-gap);
  }
  .elementskit-megamenu-panel .elementor-widget-call-to-action .elementor-cta__description{
    font-family: var(--cb-font-body);
    font-size: var(--cb-text-base);
    font-weight: 400;
    line-height: 1.4;
    color: var(--cb-ivoire);
  }

  /* --- 13.6bis L'anneau de focus des cartes (P05e, marche 2) --------------
   *
   * Mesuré avant : `outline-style: none` sur les 26 liens de carte. Un
   * utilisateur au clavier ne voyait donc RIEN quand le focus s'y posait —
   * `P05c` avait relevé le trou, il se ferme ici parce que c'est la même passe
   * et les mêmes widgets (règle de rayon : on n'écrit qu'une fois dessus).
   *
   * ⚠️ L'ANNEAU EST INTÉRIEUR, ET C'EST LA GÉOMÉTRIE QUI L'IMPOSE. La recette
   * du système (`.cb-interactive`, atoms.css) pose l'anneau à
   * `outline-offset: +2px`, donc DEHORS. Ici les cartes sont plein-bord dans un
   * panneau, et leur enveloppe porte `overflow: hidden` (mesuré) : un anneau
   * extérieur serait rogné sur les bords qui touchent le panneau — c'est-à-dire
   * exactement là où il doit se voir. On garde donc la largeur et la couleur du
   * système, et on inverse le signe de l'offset.
   *
   * `:focus-visible` et non `:focus` : le clic souris ne doit pas dessiner
   * d'anneau, seule la navigation au clavier le mérite.
   *
   * ⚠️ Ceci ne rend PAS le méga-menu opérable au clavier — les panneaux ne
   * s'ouvrent toujours qu'au survol. C'est la marche 2 de `P05e` sur 4 ; les
   * marches 1 (route hors méga-menu) et 4 (ouverture au clavier) restent dues. */
  .elementskit-megamenu-panel .elementor-widget-call-to-action a.elementor-cta:focus-visible{
    outline: var(--cb-interaction-focus-ring-width) solid var(--cb-cta);
    outline-offset: calc(-1 * var(--cb-interaction-focus-ring-offset));
  }

  /* --- 13.7 L'item de nav ouvert se souligne ----------------------------
   *
   * La frame pose un filet plein sous l'item dont le panneau est ouvert
   * (relevé au pixel sur la frame : blanc, ~1,5px, la largeur de l'item).
   *
   * ⚠️ ET IL RÉPARE UN DÉFAUT QU'ON AVAIT FABRIQUÉ NOUS-MÊMES : § 11.6 pose
   * `border: 1px solid transparent !important` sur les items pour réserver
   * la place de l'état de survol — ce qui neutralise du même coup le
   * soulignement terracotta d'ElementsKit. Mesuré aujourd'hui au survol
   * réel : bordure `1px solid rgba(0,0,0,0)` inchangée, et la couleur passe
   * de `rgb(255,253,240)` à `rgb(255,255,255)`. Deux unités sur 255 : le
   * survol d'un item de nav est **invisible**. Le filet le rend visible.
   *
   * `currentColor` et non l'ivoire en dur : le header a deux régimes (§ 11.5)
   * et un filet ivoire serait invisible sur le régime clair. Le filet prend
   * la couleur du libellé, quel que soit le régime — c'est aussi ce que la
   * frame montre (filet et texte de la même couleur).
   *
   * 🚨 ET SURTOUT PAS UNE `border-bottom` — deux défauts signalés par
   * Alexandre le 2026-08-17, tous deux inhérents à la bordure :
   *   1. elle hérite du `border-radius: 4px` de § 11.6, donc le filet avait
   *      des extrémités arrondies là où la maquette montre un trait net ;
   *   2. elle est collée au bas de l'ITEM (`bas = 121`), pas au bas du
   *      HEADER (134) où la maquette la pose (`Line 1` à `y = 131` sous un
   *      header de 132). 13px d'écart, et le filet flottait au milieu de la
   *      barre au lieu de la fermer.
   *
   * Un pseudo-élément règle les deux : aucun rayon à hériter, et on le pose
   * au bas du header avec le MÊME token que le raccord du panneau (§ 13.1) —
   * ce n'est pas une coïncidence, c'est la même distance, mesurée une fois.
   * Le filet devient donc l'arête haute du panneau ouvert : exactement ce que
   * la maquette dessine. */
  .cb-nav .elementskit-navbar-nav > li > a{
    position: relative;   /* porteur du filet — l'item n'en avait pas besoin jusqu'ici */
  }
  .cb-nav .elementskit-navbar-nav > li:hover > a::after{
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    /* `calc(-1 * …)` et non une valeur en dur : le filet descend jusqu'au bas
       du header, d'où la bordure de 1px de § 11.6 qu'il faut franchir aussi. */
    bottom: calc(-1 * (var(--cb-megamenu-decalage) + 1px));
    height: var(--cb-header-filet-nav);
    background-color: currentColor;
  }

  /* --- 13.8 Les items à chevron ne sont pas symétriques, et c'est ElementsKit
   *
   * Signalé par Alexandre (« trop de padding right sur les menu items »), et
   * ce n'était PAS notre padding : § 11.6 pose bien 16px des deux côtés. Le
   * coupable est le chevron, mesuré sur « Mariages » :
   *
   *   .elementskit-submenu-indicator{ margin: 0 6px }   ← CSS d'ElementsKit
   *
   * Conséquence mesurée : **17px de vide à gauche du libellé, 23px à droite du
   * chevron** — les 6px de sa marge droite s'ajoutent à notre padding. Et le
   * défaut ne se voyait que dans l'état survolé, parce qu'avant le filet rien
   * ne dessinait le bord de l'item. Les 3 items sans chevron, eux, étaient
   * déjà à 17/17.
   *
   * ⚠️ On retire la marge au lieu de rogner le padding : le gap libellé →
   * chevron redevient celui de l'atome (`--cb-space-xs`, 8px, posé par
   * `.cb-interactive`) au lieu de 14px (8 + 6), et les deux côtés retombent à
   * 17px SANS toucher à la recette de l'item.
   *
   * ✅ Et la maquette le confirme au dixième de pixel : ainsi corrigé, l'item
   * « Hôtel » mesure **85,3px**, quand le filet de la frame (`Line 1`) en
   * mesure **85**. Les 6px n'étaient donc pas voulus côté design non plus. */
  .cb-nav .elementskit-navbar-nav > li > a .elementskit-submenu-indicator{
    margin: 0 !important;
  }

  /* --- 13.9 Le triangle de sécurité, côté CSS ----------------------------
   *
   * Signalé par Alexandre : « sous Hôtel, si je veux aller à Restauration ou à
   * Coffrets cadeaux, ça referme le dropdown ». C'est de la géométrie : le
   * trajet naturel de l'item vers une carte de droite coupe le bas de la
   * rangée vers x ≈ 359, en plein sur l'item voisin — le survol CSS bascule
   * alors instantanément de panneau.
   *
   * `:hover` ne peut pas régler ça : c'est un état du présent, sans mémoire ni
   * direction, et le triangle a besoin des deux. La décision vit donc dans
   * `megamenu-intent.js`, qui pose deux classes ; ce bloc ne fait que les
   * honorer. Le JS n'OUVRE rien de neuf — il RETIENT une fermeture.
   *
   * `.cb-mm-open` sur le `<li>` : garde son panneau ouvert même quand le
   * curseur n'est plus dessus, tant qu'il se dirige vers lui. */
  .elementskit-navbar-nav > li.cb-mm-open > .elementskit-megamenu-panel{
    visibility: visible !important;
    /* 🚨 ET `pointer-events`, SANS QUOI TOUT LE RESTE EST INUTILE. Mesuré le
     * 2026-08-17 : dès que le curseur quitte l'item, le panneau passe de
     * `pointer-events: auto` à **`none`** — ElementsKit ne le rend cliquable
     * que sous `li:hover`. Il reste donc visible mais devient TRANSPARENT à la
     * souris : le test de survol traverse et touche la page derrière.
     * Conséquence exacte, tracée pas à pas : le triangle retenait bien le
     * panneau, puis `elementFromPoint` renvoyait un `DIV.elementor-element` de
     * la page, le script en déduisait « on est sorti du menu » et fermait —
     * à 130 ms du but. Le symptôme ressemblait à un triangle mal calculé ;
     * c'était un panneau devenu intangible. */
    pointer-events: auto !important;
  }
  .elementskit-navbar-nav > li.cb-mm-open > .elementskit-megamenu-panel > .elementor{
    clip-path: inset(0 0 0 0);
  }

  /* `.cb-mm-lock` sur le `<ul>` : pendant qu'un panneau est retenu, aucun
   * AUTRE ne s'ouvre au survol — sinon l'item traversé en chemin volerait
   * l'ouverture et on aurait deux panneaux à l'écran. (0,4,0) + `important`
   * pour battre le `li:hover` du snippet 18, qui pèse (0,3,0) + `important`. */
  .elementskit-navbar-nav.cb-mm-lock > li:not(.cb-mm-open) > .elementskit-megamenu-panel{
    visibility: hidden !important;
  }
  .elementskit-navbar-nav.cb-mm-lock > li:not(.cb-mm-open) > .elementskit-megamenu-panel > .elementor{
    clip-path: inset(0 0 100% 0) !important;
  }

  /* --- 13.10 Passer d'un panneau à l'autre ne rejoue PAS le rideau ---------
   *
   * Signalé par Alexandre : « il faut animer avec plus d'élégance le background
   * du dropdown qui change de sens à chaque fois et s'est un peu éloigné d'un
   * look and feel zen ».
   *
   * MESURÉ, image par image, sur une bascule Hôtel → Mariages : les deux
   * panneaux restent visibles **ensemble pendant 384 ms**, avec des `clip-path`
   * exactement complémentaires — le sortant se rembobine vers le HAUT
   * (0 → 100 %) pendant que l'entrant se déroule vers le BAS (100 → 0 %).
   * Superposés au pixel près, ils ne se lisent pas comme deux panneaux : ils
   * fabriquent une COUTURE horizontale qui traverse l'écran, avec les cartes du
   * sortant au-dessus et celles de l'entrant en dessous. D'où « ça change de
   * sens » — les deux sens sont là, dans le même geste.
   *
   * Le remède ne demande aucune animation nouvelle, il demande d'en RETIRER
   * une. Les deux panneaux ont exactement le même sol : même verre, même flou,
   * même boîte, à la même place. Le faire rouler pour le remplacer par son
   * sosie est un mouvement qui ne transporte aucune information. On l'enlève :
   * à la bascule le cadre ne bouge plus d'un pixel, et seul le contenu change.
   *
   * Le rideau, lui, garde tout son sens là où il en a un — la première
   * ouverture (le panneau n'existait pas) et la fermeture franche (il
   * disparaît). `megamenu-intent.js` distingue les deux cas en posant
   * `.cb-mm-bascule` sur la nav, et en la retirant à la fermeture.
   *
   * 🚨 ET LA BASCULE EST INSTANTANÉE — décision d'Alexandre, 2026-08-18 :
   * « montre-moi une transition instantanée (quand on passe d'un dropdown à un
   * autre, pas au premier affichage). ça sera sûrement plus simple. »
   *
   * Il y avait ici un fondu de contenu, passé de 260 à 600 ms pour être plus
   * calme. Il est RETIRÉ, pas mis à zéro : un fondu de durée nulle laisserait
   * des keyframes qui ne jouent rien et un token que personne ne lit. Ce qui
   * reste tient en une déclaration — et c'est la version la plus honnête de ce
   * paragraphe, parce que le raisonnement du dessus la portait déjà : si le
   * cadre est le même et ne doit pas bouger, il n'y a rien à animer du tout.
   * On ne change pas de panneau, on change ce qu'il y a dedans.
   *
   * ⚠️ CE QUE ÇA COÛTE, et il faut le dire : rien ne relie plus visuellement
   * l'ancien contenu au nouveau. La bascule est un remplacement sec, et sur des
   * photos très différentes d'un panneau à l'autre elle peut se lire comme un
   * clignotement. C'est le prix assumé de la simplicité demandée — si ça pique
   * à l'usage, le fondu revient en trois lignes (`git show` de cette date).
   *
   * ⚠️ Et une conséquence qui se voit : l'apparition des cartes a maintenant
   * DEUX régimes — le rideau de 400 ms à la première ouverture, rien du tout
   * quand on circule. C'est voulu (« pas au premier affichage »), pas un oubli. */
  /* 🚨 LES DEUX ÉTAGES, ET LE SECOND EST LA CORRECTION DU FLASH (2026-08-18).
   *
   * Signalé par Alexandre : « c'est pas tout à fait instantané. y'a un temps
   * très court où les cards apparaissent soudainement dans un flash, notamment
   * en passant de hôtel à spa & bien-être. »
   *
   * MESURÉ, à la trame : à t = 200 ms le panneau SORTANT est déjà entièrement
   * clippé (`inset(0 0 100%)`) pendant que l'ENTRANT a bien son clip plein mais
   * reste `visibility: hidden` — il ne repasse `visible` qu'à t = 217, une
   * trame plus tard. Pendant cette trame **aucun des deux panneaux ne peint** :
   * la page passe au travers, puis le nouveau panneau claque. Ce n'était donc
   * pas une animation trop rapide, c'était un TROU.
   *
   * La cause : `visibility` porte la transition d'ElementsKit, et § 13.1ter
   * l'avait laissée intacte à dessein (« elle bascule à l'ouverture et attend
   * la fin à la fermeture »). Ce raisonnement vaut pour une fermeture — il ne
   * vaut pas pour une bascule, où il n'y a rien à attendre. On la neutralise
   * donc, mais UNIQUEMENT sous `.cb-mm-bascule` : la classe n'existe que
   * pendant qu'on circule dans le menu, et `fermer()` la retire. La fermeture
   * franche garde donc la transition d'ElementsKit, intacte.
   *
   * `!important` parce qu'ElementsKit pose la sienne avec — c'est le même cas
   * qu'au § 11.6 : égaler la spécificité ne suffit pas, il faut peser autant. */
  .elementskit-navbar-nav.cb-mm-bascule > li > .elementskit-megamenu-panel{
    transition: none !important;   /* la visibilité bascule dans LA MÊME trame */
  }
  .elementskit-navbar-nav.cb-mm-bascule > li > .elementskit-megamenu-panel > .elementor{
    transition: none;   /* le sol ne roule plus, et le contenu ne fond plus */
  }
}

/* --- 13.11 Le tiroir sous 1025px : ce que le vidage des champs aurait EMPORTÉ
 *
 * 🔍 CE BLOC EXISTE À CAUSE D'UNE MESURE, pas d'une intention de design.
 *
 * `DS46` demande de vider les champs de style des 26 widgets, pour que le CSS
 * par-widget d'Elementor disparaisse et que le § 13 puisse enfin lâcher ses
 * `!important`. En préparant ce vidage, le relevé a montré ce que personne
 * n'avait vérifié : **les cartes du méga-menu sont RENDUES sous 1025px**, dans
 * le tiroir hors-canevas d'ElementsKit — 13 cartes visibles à 292×100 (390) et
 * 612×100 (767), mesurées hamburger ouvert.
 *
 * Or tout le § 13 est sous `min-width: 1025px`. Le tiroir n'était donc habillé
 * QUE par les champs de widget. Les vider sans rien mettre à la place aurait
 * dépouillé le tiroir : plus de voile (texte blanc sur photo nue), plus de
 * graisse ni d'interligne de titre. C'est la règle « vérifier au rendu, pas à
 * la lecture » qui a payé ici — à la lecture du § 13, le tiroir n'existait pas.
 *
 * ⚠️ CE BLOC NE REPREND QUE LES PROPRIÉTÉS DE BASE, celles qu'Elementor écrit
 * SANS suffixe de largeur et qui servaient donc les deux régimes. Les réglages
 * proprement mobiles du widget (`min-height_mobile` 100, `min-height_tablet`
 * 135, `padding_mobile`, `title_spacing_mobile`, les corps de texte `_tablet`
 * et `_mobile`) sont **délibérément CONSERVÉS dans le widget** : rien ici ne
 * les combat, et les transférer aurait figé dans le design system une
 * composition que personne n'a dessinée.
 *
 * ⚠️ AUCUN `!important` ICI, et c'est voulu. Tant que les champs sont encore
 * remplis, le CSS par-widget d'Elementor gagne — avec les MÊMES valeurs, donc
 * rien ne bouge. Une fois les champs vidés, il disparaît et ces règles
 * prennent la main. Le déploiement est neutre dans les deux sens, ce qui rend
 * l'ordre CSS-puis-JSON sans risque.
 *
 * `(parti pris)` — DEUX écarts assumés au legacy, tous deux vers le système :
 *   1. le voile passe de `#1B1812` à `--cb-scrim-color` (#000). `#1B1812`
 *      n'appartient PAS à la palette (`Q05`) : c'était une couleur hors
 *      système. À 50 % en `multiply`, l'écart plafonne à ~5 % par canal sur
 *      les pixels clairs et il va dans le sens de la lisibilité.
 *   2. le rayon passe de 5px à `--cb-radius-sm` (4px), le même qu'en desktop.
 *
 * ⚠️ CE QUI RESTE DÛ : le tiroir mobile n'a **jamais été dessiné**. Il montre
 * aujourd'hui des cartes de 100px de haut portant une photo de 1600px de
 * large, avec un titre en 18px sur une photo pleine. Ce bloc le PRÉSERVE, il
 * ne le corrige pas → ticket dédié.
 * ------------------------------------------------------------------------ */
@media (max-width: 1024px){

  .elementskit-megamenu-panel .elementor-widget-container{
    border-radius: var(--cb-radius-sm);
  }

  /* Le voile : c'est lui qui rend le titre lisible sur la photo. Sans cette
     règle, le vidage de `overlay_color` laissait du blanc sur photo nue. */
  .elementskit-megamenu-panel .elementor-cta__bg-overlay{
    background-color: color-mix(in srgb,
      var(--cb-scrim-color) calc(var(--cb-megamenu-tiroir-voile) * 100%), transparent);
    mix-blend-mode: multiply;
  }

  /* Graisse et interligne seulement : le CORPS du texte reste au widget, qui
     le décline par largeur (18px/12px en mobile, 25px/14px en tablette). */
  .elementskit-megamenu-panel .elementor-cta__title{
    font-weight: 300;
    line-height: 1.2;
  }
  .elementskit-megamenu-panel .elementor-cta__description{
    font-weight: 300;
  }
}
