/*
 * MF Theme — compléments CSS.
 *
 * Tout ce qui peut être exprimé dans theme.json l'est dans theme.json.
 * Ce fichier ne contient que ce que theme.json ne sait pas faire :
 * motifs de charte, états, et quelques ajustements responsive.
 *
 * Règle de maintenance : ne jamais cibler une classe générée par WordPress
 * (.wp-container-xxxx, .wp-elements-xxxx). Uniquement des classes .wp-block-*
 * stables ou nos propres classes .mf-*.
 */

/* ------------------------------------------------------------------
   Signature de charte
   ------------------------------------------------------------------ */

/* Angles vifs pour les blocs : on annule tout arrondi hérité. */
.wp-block-image img,
.wp-block-post-featured-image img,
.wp-block-cover,
.wp-block-group.has-background,
input[type="text"],
input[type="email"],
input[type="tel"],
input[type="search"],
textarea,
select {
	border-radius: 0;
}

/* Pilule pour les actions — theme.json la pose déjà sur l'élément Bouton, on
   couvre ici les cas hérités (`button`, `input[type=submit]`) qu'il ne vise pas.
   La valeur est le jeton `settings.custom.radius.action` et non `999px` écrit en
   dur : cette feuille est chargée APRÈS les styles globaux et gagne à
   spécificité égale (0,1,0 des deux côtés), donc un `999px` littéral ici
   annulerait silencieusement tout réglage d'un thème enfant. Le jeton est la
   source de vérité unique du rayon d'action — theme.json et cette règle le
   consomment, aucun des deux ne le décide. */
.wp-element-button,
.wp-block-button__link,
button,
input[type="submit"] {
	border-radius: var(--wp--custom--radius--action);
}

/* Rayon des surfaces flottantes — la troisième valeur, et la seule.
   ------------------------------------------------------------------

   « Angles vifs, sauf pour agir » : 0 pour le contenu, 999px pour l'action,
   rien entre les deux. Certaines chartes clientes veulent pourtant une
   troisième valeur — un rayon de *surface* pour les éléments qui vivent dans
   le flux, entourés de gouttières, qu'on lit comme des objets posés sur la
   page : cartes, panneaux, médias, plans d'accès, embeds.

   **Le jeton vaut 0 par défaut : le socle garde ses angles vifs, et un site
   qui ne règle rien ne reçoit pas un pixel de différence.** Ces règles sont
   inertes tant qu'un thème enfant n'a pas dit ce qu'il voulait.

   POURQUOI C'EST ICI ET PLUS DANS UN ENFANT. Ça a vécu dans `imi-jardin`, avec
   un commentaire affirmant que c'était une décision de direction artistique
   propre à ce client, donc pas l'affaire du socle. La moitié était juste — la
   *valeur* est bien une décision de DA — et l'autre fausse : la carte des
   sélecteurs, elle, ne dit rien d'IMI. Elle dit ce qu'est une surface flottante
   dans un thème bloc, quelles exceptions la démentent, et sur quel élément
   poser le rayon quand un média est découpé au survol. C'est une connaissance
   commune, et le §1 tranche : un autre client aurait ça. Le deuxième l'a
   demandé.

   FLOTTANT VS FULL-BLEED — PAR LE RÔLE, PAS PAR LE TYPE. Ce n'est pas la balise
   qui décide, c'est la position : une même image est arrondie dans une colonne
   et vive quand elle court jusqu'au bord de l'écran. Le contexte Gutenberg dit
   déjà tout ce qu'il faut, sans classe à poser à la main :

   - le flottant est la règle, pour les surfaces de CONTENU (dans `<main>`) ;
   - le full-bleed est l'exception, marquée par le rôle : `alignfull`, ou le
     média débordant d'un hero (`.mf-hero--bleed`). Un rayon posé sur un bord
     d'écran ne se verrait pas — ou dessinerait un liseré de fond contre le
     bord ; l'angle vif est le bon rendu. Le plein-bord d'un panneau flottant
     reste, lui, un panneau : un enfant non-`alignfull` d'une bande `alignfull`
     garde son rayon ;
   - et l'exception se lit CÔTÉ PAR CÔTÉ, pas bloc par bloc : un coin n'est
     vif que si le côté qu'il termine touche le bord. Un média qui ne va au
     bord que d'un côté — la moitié image d'un `.mf-hero--bleed` — garde ses
     deux coins libres arrondis. Voir « Demi-bleed » plus bas.

   POURQUOI SCOPÉ À `<main>`. L'en-tête et le pied de page sont eux aussi des
   groupes à fond, mais ce sont du chrome, pas des surfaces flottantes : leur
   géométrie appartient au socle (la barre pleine largeur, la capsule à
   `header.floatingRadius`). Le rayon flottant ne vise donc que le contenu — et
   n'a besoin d'aucun `!important` pour cela : il est écrit après les angles
   vifs de la signature de charte, qu'il bat à spécificité égale, et le
   full-bleed repasse devant par la sienne.

   UNE SURFACE SE RECONNAÎT À CE QU'ELLE PEINT, PAS AU BLOC QUI LA PORTE. Deux
   familles se font oublier si on ne vise que `.wp-block-group.has-background` :
   le panneau qui est une COLONNE et non un groupe (un panneau posé dans une
   rangée reste un panneau, le rôle est le même, le bloc non), et le panneau
   dont la surface est dessinée par un FILET et non par un fond.

   `has-border-color` est le bon signal, et pas seulement le plus court :
   WordPress ne l'écrit que pour un bord **unifié** (`style.border.color`). Un
   filet posé sur un seul côté — le trait qui sépare deux bandes — passe par le
   chemin des côtés individuels, qui n'émet aucune classe. Une bande soulignée
   ne peut donc pas être prise pour un panneau.

   LE RAYON PORTE PARFOIS SUR L'ENVELOPPE. Un média qui zoome au survol est
   découpé par un wrapper en `overflow: hidden` : c'est ce wrapper qui doit
   porter le rayon, sinon l'image agrandie déborderait de nouveau en angles
   vifs. On arrondit donc l'enveloppe autant que le média. */

main .wp-block-image img,
main .wp-block-post-featured-image img,
main .mf-card__media img,
main .wp-block-cover,
main .wp-block-video video,
main .wp-block-embed iframe,
main .wp-block-group.has-background,
main .wp-block-group.has-border-color,
main .wp-block-column.has-background,
main .wp-block-column.has-border-color {
	border-radius: var( --wp--custom--radius--floating );
}

/* Enveloppes qui découpent un média au survol : le rayon va sur ce qui clippe. */
main .mf-card .wp-block-post-featured-image a,
main .mf-card .mf-card__media a,
main .wp-block-woocommerce-product-template li .mf-card__media a {
	border-radius: var( --wp--custom--radius--floating );
}

/* Le plan d'accès est une surface flottante comme une autre, à une réserve
   près que la section « Carte d'accès » annonçait déjà : Leaflet peint ses
   tuiles jusqu'au bord de la boîte, un rayon sans découpe laisserait donc les
   coins carrés. `overflow: hidden` est ici la condition du rayon, pas un
   ajout — d'où la règle à part. */
main .mf-map {
	overflow: hidden;
	border-radius: var( --wp--custom--radius--floating );
}

/* Full-bleed : le rôle structurel prime, l'angle vif reste. La spécificité
   (deux classes) repasse devant les règles flottantes ci-dessus, sans
   `!important`. */
main .alignfull.wp-block-cover,
main .alignfull.wp-block-group.has-background,
main .alignfull.wp-block-group.has-border-color,
main .wp-block-image.alignfull img {
	border-radius: 0;
}

/* Demi-bleed : chaque côté au bord retire ses deux coins, rien de plus.

   Le média d'un `.mf-hero--bleed` ne touche la fenêtre que d'un côté ; ses
   deux coins libres sont ceux d'une surface flottante et gardent le jeton. La
   règle n'est pas « arrondir le côté libre » mais l'inverse, et la différence
   compte : le rayon flottant est la base (posée par la carte ci-dessus), et
   CHAQUE CÔTÉ EN CONTACT RETIRE SES DEUX COINS. Trois faits, trois règles, qui
   se composent sans cas particulier — une colonne du milieu ne matche rien et
   reste ronde, une colonne unique matche les deux et devient vive, la rangée
   empilée aussi.

   LE CÔTÉ SE LIT DANS LE DOM, ET C'EST FIABLE. L'ordre flex d'une rangée suit
   le DOM et `direction` ; `core/columns` n'accepte que `core/column`. La
   première colonne est donc toujours à `inline-start`, la dernière à
   `inline-end` — en RTL comme en LTR, d'où les propriétés logiques. Que la
   rangée REMPLISSE la section jusqu'à ces côtés est l'affaire du modificateur
   lui-même (`justify-content: space-between`, section Hero) : le contact est
   garanti par la structure, pas supposé.

   EMPILÉ, CHAQUE COLONNE PREND TOUTE LA LARGEUR (`flex-basis: 100%` du cœur
   sous 782 px) : le média touche les deux bords, quatre coins vifs. Le garde
   `:not(.is-not-stacked-on-mobile)` suit le cœur à la lettre — une rangée qui
   ne s'empile pas garde ses côtés.

   Le rayon reste sur l'`img`, comme dans la carte : rien ne clippe, et la
   figure comme son groupe ne peignent rien. Les spécificités — (0,5,2) pour
   les côtés, (0,4,2) pour l'empilement — passent devant la carte (0,1,2) sans
   `!important`. Le périmètre est le média, et lui seul : une colonne peinte
   dans cette rangée a son propre rôle, le modificateur ne le redécide pas. */
main .mf-hero--bleed > .wp-block-columns > .wp-block-column:first-child .mf-hero__media img {
	border-start-start-radius: 0;
	border-end-start-radius: 0;
}

main .mf-hero--bleed > .wp-block-columns > .wp-block-column:last-child .mf-hero__media img {
	border-start-end-radius: 0;
	border-end-end-radius: 0;
}

@media (max-width: 781px) {
	main .mf-hero--bleed > .wp-block-columns:not(.is-not-stacked-on-mobile) .mf-hero__media img {
		border-radius: 0;
	}
}

/* Sur-titre — variantes enregistrées dans functions.php.
 *
 * Une seule intention de design, trois usages, et les valeurs déclarées une
 * seule fois : la taille vient du preset `eyebrow`, l'interlettrage des tokens
 * `settings.custom.eyebrow.*`. Un thème enfant qui veut un sur-titre plus ou
 * moins espacé change le token dans son theme.json, sans toucher à ce fichier
 * ni à un seul bloc.
 *
 * - `is-style-mf-eyebrow`      : le sur-titre nu — les cas centrés.
 * - `is-style-mf-eyebrow-rule` : avec le filet d'appel de 26 × 1.5 px.
 * - `is-style-mf-eyebrow-card` : filet et interlettrage resserré, dans les
 *   grilles de cartes où le libellé côtoie un titre.
 *
 * La couleur reste un réglage du bloc (`textColor`, donc un preset) : c'est le
 * seul choix qui change d'un fond clair à un fond sombre, et le seul que le
 * client doit pouvoir reprendre depuis l'éditeur.
 */
.is-style-mf-eyebrow,
.is-style-mf-eyebrow-rule,
.is-style-mf-eyebrow-card {
	font-size: var(--wp--preset--font-size--eyebrow);
	font-weight: 600;
	letter-spacing: var(--wp--custom--eyebrow--tracking);
	text-transform: uppercase;
}

.is-style-mf-eyebrow-card {
	letter-spacing: var(--wp--custom--eyebrow--tracking-card);
}

.is-style-mf-eyebrow-rule,
.is-style-mf-eyebrow-card {
	display: flex;
	align-items: center;
	gap: 10px;
}

.is-style-mf-eyebrow-rule::before,
.is-style-mf-eyebrow-card::before {
	content: "";
	inline-size: 26px;
	block-size: 1.5px;
	background: currentColor;
	flex: none;
}

/* Un sur-titre vidé ne laisse pas son filet orphelin.
 *
 * Sous `contentOnly`, le client peut vider le texte d'un sur-titre mais pas
 * supprimer le bloc : un composant qui en prévoit un doit donc supporter qu'on
 * n'en veuille pas. On neutralise le filet, pas le bloc — le masquer
 * entièrement le rendrait invisible et donc inéditable dans l'éditeur, où
 * cette feuille est aussi chargée (`add_editor_style`) et où un paragraphe
 * vide est bel et bien `:empty`. */
.is-style-mf-eyebrow-rule:empty::before,
.is-style-mf-eyebrow-card:empty::before {
	content: none;
}

/* Titre de colonne : même famille que le sur-titre, en plus appuyé, avec sa
   respiration basse. Sert aux colonnes du pied de page et à la colonne de
   filtres. */
.is-style-mf-column-title {
	font-family: var(--wp--preset--font-family--sans);
	font-size: var(--wp--preset--font-size--eyebrow);
	font-weight: 700;
	letter-spacing: var(--wp--custom--eyebrow--tracking-title);
	text-transform: uppercase;
	margin-block-end: var(--wp--preset--spacing--40);
}

/* ------------------------------------------------------------------
   Enveloppe de section — la primitive de mise en page
   ------------------------------------------------------------------

   Une bande de page respire verticalement. C'est la seule chose que dit cette
   enveloppe : elle ne porte ni fond, ni largeur, ni logique. Deux amplitudes,
   parce que deux suffisent — majeure et dense.

   Variantes enregistrées dans functions.php, sur `core/group` et sur lui seul.

   PRIORITÉ DES VALEURS (dans cet ordre, et c'est le cœur du mécanisme) :

     1. Le réglage posé par le client dans Gutenberg. Le bloc Groupe sérialise
        `style.spacing.padding` en **style en ligne** : il bat toute règle de
        cette feuille, sans `!important` d'aucune sorte. Et il le fait côté par
        côté — un client qui ne règle que le haut garde le bas de l'enveloppe,
        parce que `padding-top` (en ligne) et `padding-block-start` (ici) sont
        la même propriété calculée.
     2. À défaut, la valeur de l'enveloppe, ci-dessous.
     3. À défaut, rien : un Groupe sans enveloppe n'a pas de respiration
        verticale imposée par le thème.

   La valeur elle-même est un token (`settings.custom.section.*`) et non un
   preset écrit en dur : un thème enfant règle le rythme de ses bandes en une
   ligne de son theme.json, sans toucher à l'échelle d'espacement — qui, elle,
   sert aussi à des dizaines de marges internes qu'on ne veut pas déplacer.

   ET UNE BANDE EST `alignfull`. Ce n'est pas une préférence esthétique, c'est ce
   qui la rend correcte. Avec `useRootPaddingAwareAlignments`, WordPress pose
   `has-global-padding` sur TOUT groupe en disposition contrainte : un tel groupe
   imbriqué dans un `<main>` lui-même contraint applique donc le padding de page
   une seconde fois, et son `alignwide` se mesure dans une boîte déjà rétrécie —
   le contenu se retrouve à la largeur de texte au lieu de la largeur large.
   `alignfull` casse la contrainte du parent par marges négatives, la bande
   repart du bord de l'écran, et son propre padding est le seul qui s'applique.
   C'est aussi ce qui permet à un fond de bande d'aller au bord.

   Le symptôme, quand on l'oublie : deux `has-global-padding` dans la chaîne des
   ancêtres, et une bande deux fois trop étroite. La tentation est alors de
   remonter la variante sur `<main>` — ce qui règle la largeur en cassant tout le
   reste (§5, compensation de l'en-tête en surimpression). C'est la bande qui
   doit sortir, pas l'enveloppe qui doit se remplir.

   QUAND L'EMPLOYER — le critère, et il est volontairement étroit :
   un bloc Groupe **enfant direct de `<main>`** (ou de `post-content`), qui
   sépare une bande de page de ce qui la précède et la suit. Rien d'autre.
   Jamais l'enveloppe `<main>` elle-même, jamais un panneau encadré à
   l'intérieur d'une fiche, jamais une carte. C'est exactement ce que
   l'ancienne variante `mf-section` avait cessé de respecter avant d'être
   retirée en 0.9.0 ; voir ARCHITECTURE.md §6. */

/* WordPress traduit le `blockGap` global en `margin-block-start` sur chaque
   enfant d'un conteneur contraint. Ce rythme est utile à l'intérieur des
   contenus, mais pas entre les bandes pleine largeur qui composent une page :
   leur propre padding règle déjà l'espace vertical et la marge découvre le
   fond de page sous la forme d'un filet blanc.

   Le second sélecteur couvre les bandes saisies dans le contenu d'une page
   standard. Les marges explicites posées dans Gutenberg restent prioritaires,
   car elles sont sérialisées en style en ligne. */
main.is-layout-constrained > .alignfull,
main > .wp-block-post-content.is-layout-constrained > .alignfull {
	margin-block-start: 0;
}

/* Le footer est lui aussi un enfant direct de `.wp-site-blocks` et reçoit le
   même interligne global. Son fond doit commencer au contact du contenu. */
.wp-site-blocks > footer.wp-block-template-part {
	margin-block-start: 0;
}

.is-style-mf-section {
	padding-block: var(--wp--custom--section--padding);
}

.is-style-mf-section-compact {
	padding-block: var(--wp--custom--section--padding-compact);
}

/* ------------------------------------------------------------------
   Courbe de bande — la couture entre deux bandes de page
   ------------------------------------------------------------------

   Un filet droit sépare deux bandes ; une courbe les fait se recouvrir. C'est
   ce que font Salient ou un fond dessiné dans Illustrator, et c'est la seule
   chose qui manquait au rythme de page du socle : une bande de couleur qui
   s'arrête net sur une horizontale parfaite se lit comme un bloc empilé, pas
   comme une composition.

   QUI DESSINE LA COURBE. La bande **elle-même**, sur son propre bord, avec sa
   propre couleur — jamais la bande d'à côté. C'est ce qui rend le mécanisme
   praticable :

   - `background-color: inherit` sur le pseudo-élément reprend le fond de la
     bande, quel qu'il soit. Aucune règle n'a à connaître la palette, et un
     thème enfant qui change ses couleurs n'a rien à reprendre ici.
   - Une bande **sans fond** hérite de `transparent` : la courbe existe dans le
     rendu et ne se voit pas. Le mécanisme se désactive donc tout seul là où il
     n'aurait rien à peindre, sans condition à écrire (même logique que la
     carte sans coordonnées, ou du reveal sans JavaScript).

   CONCAVE OU CONVEXE — C'EST LE MÊME ARC, VU DES DEUX CÔTÉS. Le bombé d'une
   bande est le creux de sa voisine : il n'y a donc pas deux directions à
   déclarer, seulement à choisir **laquelle des deux bandes porte la couture**.
   `--top` la fait monter dans la bande précédente, `--bottom` descendre dans la
   suivante. Une seule dimension par option, comme les deux axes de l'en-tête
   (ARCHITECTURE §5) — et les deux se combinent sur une bande qui doit être
   courbe des deux côtés.

   POURQUOI DES CLASSES ET PAS UN STYLE DE BLOC. Un style de bloc est exclusif :
   il occuperait la place de `is-style-mf-section`, qui porte la respiration de
   la bande, et les deux bords ne pourraient pas coexister. Même raison que
   `.mf-reveal` (§5) : ce qui se combine vit en classe.

   LA COURBE EST POSÉE HORS DE LA BANDE (`inset-block-end: 100%`), pas rognée
   dedans. Deux conséquences voulues : le padding de la bande reste celui de
   `theme.json`, sans avoir à être rallongé pour éviter l'arc ; et c'est le
   padding de la bande **voisine** qui absorbe le recouvrement — ce qu'il fait
   sans effort, l'arc culminant à 72 px contre un rythme de bande à 96.

   L'AMPLITUDE EST PLAFONNÉE EN UNITÉS DE VUE. Un arc de 72 px sur 1400 px de
   large est une courbe ; le même sur 380 px est une bosse. La pente est un
   rapport entre hauteur et largeur, pas une hauteur : `min()` garde donc le
   jeton tant que l'écran est large et le remplace par une fraction de la
   largeur en dessous. C'est le seul endroit du socle où un `min()` remplace
   une media query, et il le peut parce qu'il n'exprime pas un palier mais une
   proportion continue.

   CE QU'IL FAUT SAVOIR AVANT DE LA POSER :
   - `--top` sur la **première** bande d'une page dessinerait un arc par-dessus
     l'en-tête. La couture est une transition entre deux bandes, pas une
     ouverture de page.
   - Une bande qui doit garder ses deux bords droits porte
     `.mf-edges--straight`. Elle neutralise ses propres courbes, mais aussi la
     courbe basse de la bande précédente et la courbe haute de la suivante :
     la décision voyage ainsi avec la bande si elle est déplacée.
   - Un ancêtre en `overflow: hidden` rognerait l'arc. Aucun n'en pose
     aujourd'hui ; c'est à vérifier avant d'en introduire un.
   - `z-index: 1` place la bande courbe au-dessus de sa voisine, sans quoi le
     fond de celle-ci recouvrirait l'arc. La valeur reste sous l'en-tête (100)
     et sous le retour en haut (90). */

.mf-curve {
	position: relative;
	z-index: 1;
}

.mf-curve--top::before,
.mf-curve--bottom::after {
	content: "";
	position: absolute;
	inset-inline: 0;
	display: block;
	block-size: min( var( --wp--custom--curve--height ), var( --wp--custom--curve--height-fluid ) );
	background-color: inherit;
	pointer-events: none;
}

/* Opt-out pairwise : une couture déborde depuis l'une ou l'autre des deux
   bandes qui se rencontrent. La bande droite doit donc pouvoir neutraliser
   les trois origines possibles sans obliger à modifier ses voisines. Le
   `:has()` est déjà une primitive du socle ; les commentaires entre blocs ne
   rompent pas l'adjacence CSS. */
.mf-curve--bottom:has(+ .mf-edges--straight)::after,
.mf-edges--straight.mf-curve--top::before,
.mf-edges--straight.mf-curve--bottom::after,
.mf-edges--straight + .mf-curve--top::before {
	content: none;
}

.mf-curve--ample.mf-curve--top::before,
.mf-curve--ample.mf-curve--bottom::after {
	block-size: min( var( --wp--custom--curve--height-ample ), var( --wp--custom--curve--height-fluid-ample ) );
}

/* Les rayons sont donnés en pourcentage sur les deux axes : le premier vaut la
   largeur de la bande, le second sa hauteur d'arc. L'ellipse épouse donc la
   boîte quelle que soit la largeur de l'écran — c'est ce qui garde une courbe
   *douce* au lieu d'un demi-cercle suivi d'un plat.

   LE PIXEL DE RECOUVREMENT (`100% - 1px`) N'EST PAS UN TÂTONNEMENT. Deux boîtes
   de même couleur qui se touchent exactement laissent voir un liseré clair : la
   position d'un élément absolu tombe sur une fraction de pixel dès que la page
   défile ou que l'écran a un facteur d'échelle non entier, et le navigateur
   compose alors deux bords à moitié opaques. Un pixel de chevauchement place la
   jointure *à l'intérieur* de la matière au lieu de la poser sur son bord, et
   la couture disparaît. La base plate de l'arc, elle, est de toute façon
   invisible : elle est dans la bande. */
.mf-curve--top::before {
	inset-block-end: calc( 100% - 1px );
	border-start-start-radius: 50% 100%;
	border-start-end-radius: 50% 100%;
}

.mf-curve--bottom::after {
	inset-block-start: calc( 100% - 1px );
	border-end-start-radius: 50% 100%;
	border-end-end-radius: 50% 100%;
}

/* Oblique — le même arc, mais dissymétrique : pleine amplitude d'un côté,
   nulle de l'autre. La couture ne bombe plus, elle penche. Réservé aux
   transitions où deux courbes symétriques se répondraient trop sagement (deux
   bandes bombées l'une après l'autre font un chapelet). */
.mf-curve--oblique.mf-curve--top::before {
	border-start-start-radius: 0;
	border-start-end-radius: 100% 100%;
}

.mf-curve--oblique.mf-curve--bottom::after {
	border-end-start-radius: 100% 100%;
	border-end-end-radius: 0;
}

/* ------------------------------------------------------------------
   Filet de respiration — corps de page « nu »
   ------------------------------------------------------------------

   `<main>` reste une enveloppe nue : aucun padding vertical, sur aucun
   gabarit (§6, et la section « En-tête en surimpression » plus bas, qui
   en dépend). La respiration d'une page appartient à sa première et à
   sa dernière bande, jamais à `<main>`.

   Sur une page CONSTRUITE — ouverte par un hero ou une bande
   section-intro — chaque bande porte déjà la sienne, et ce filet ne doit
   RIEN ajouter. Il n'existe que pour la page « nue » : celle dont le
   client a empilé paragraphes, titres et images sans poser de bande
   d'ouverture. Il donne alors, et alors seulement, une respiration haute
   à la première brique et basse à la dernière.

   POURQUOI PAS `:has()`. La compensation d'en-tête pose son espace sur un
   ANCÊTRE (`.wp-site-blocks`) d'après un descendant : elle ne peut donc
   qu'interroger le parent, d'où son `:has()`. Ici on pose l'espace sur la
   brique ELLE-MÊME — un sélecteur structurel (`:first-child`/`:last-child`
   + chaînes `:not()`) suffit, sans `:has()`.

   CE QU'IL NE TOUCHE JAMAIS :
   - une ouverture qui gère déjà son espace — hero (`.mf-hero`), bande
     (`.is-style-mf-section`/`-compact`), section pleine largeur
     (`.alignfull`) ou bloc à fond propre (`.has-background`) : exclus, pour
     ne pas creuser un vide au-dessus d'un fond qui doit toucher le bord ;
   - un autre type de gabarit : le scope `body.page` le réserve aux pages
     (`page.html`, `page-pleine-largeur.html`) et écarte article, archive,
     résultats, fiche produit — dont plusieurs ont aussi un `post-content`
     mais pilotent leur propre rythme (l'article, p. ex., pose déjà une
     marge sous son image à la une : un filet y ferait un double vide).

   DEUX FORMES D'OUVERTURE selon le gabarit :
   - `page.html` : le titre rendu par le thème (`post-title`), enfant
     direct de `<main>`, ouvre la page. `post-content` n'y est PAS
     `:first-child` — la règle du haut sur `post-content` ne s'y déclenche
     donc pas, et rien ne creuse de vide entre le titre et le corps.
   - `page-pleine-largeur.html` : `<main>` n'a que `post-content` ; c'est
     sa première brique interne qui ouvre la page.

   `padding`, jamais `margin` : aucune marge ne peut ainsi remonter à
   travers `<main>` jusqu'à l'en-tête. La valeur est le token de rythme de
   bande (`settings.custom.section.padding`) — un enfant le règle en une
   ligne, sans toucher à l'échelle d'espacement. */

body.page main > .wp-block-post-title:first-child,
body.page main > .wp-block-post-content:first-child > :first-child:not(.mf-hero):not(.is-style-mf-section):not(.is-style-mf-section-compact):not(.alignfull):not(.has-background) {
	padding-block-start: var(--wp--custom--section--padding);
}

body.page main > .wp-block-post-content:last-child > :last-child:not(.mf-hero):not(.is-style-mf-section):not(.is-style-mf-section-compact):not(.alignfull):not(.has-background) {
	padding-block-end: var(--wp--custom--section--padding);
}

/* ------------------------------------------------------------------
   Espace de fin de page — au gabarit, jamais au bloc réutilisable
   ------------------------------------------------------------------

   Troisième et dernier mécanisme de respiration, après l'enveloppe de bande
   et le filet ci-dessus. Il répond à un cas qu'aucun des deux ne couvre.

   Une bande `.is-style-mf-section` porte sa propre respiration, et cela suffit
   tant que le dernier élément d'un gabarit EST une bande. Mais ce dernier
   élément est souvent un bloc réutilisable — une partie de modèle, une
   composition posée telle quelle : bande de contact, rappel d'action, bandeau
   de fin. Ce bloc doit pouvoir vivre AILLEURS dans la page, où l'espace qui lui
   irait en fin de page serait de trop. Lui coller un `padding-block-end` parce
   qu'il ferme la page le figerait dans un rôle qu'il n'a pas toujours.

   Le gabarit est le seul à connaître la position du bloc : c'est donc à lui de
   porter la décision, avec un groupe enveloppant qui reçoit cette classe. Le
   bloc, lui, reste réutilisable.

   `align: full` et `layout: default` sur ce groupe, jamais `constrained` : un
   wrapper contraint autour d'un bloc pleine largeur en réduirait la largeur.

   POURQUOI CETTE RÈGLE ARRIVE APRÈS SA CONVENTION. ARCHITECTURE.md §6 décrivait
   ce wrapper depuis sa version d'origine, et trois gabarits d'imi-jardin le
   posaient déjà — mais la règle correspondante n'a jamais été écrite ici, et la
   classe portait au passage un préfixe `is-`, réservé aux variantes de bloc.
   Le manque ne se voyait pas : les blocs enveloppés jusque-là (`bloc-pieces`,
   `section-contact`) portaient tous leur propre padding interne. Il s'est vu à
   la première bande qui n'en a pas — le bandeau de réassurance, qui ferme la
   page À propos sur son seul filet. */

.mf-page-end {
	padding-block-end: var(--wp--custom--section--padding);
}

/* Hero — l'ouverture d'une page, dans ses trois formes (compositions
 * `mf-theme/hero-cover`, `hero-split`, `hero-editorial`).
 *
 * La hauteur vit ici et non sur le bloc pour deux raisons : elle a besoin d'un
 * repli mobile, que `theme.json` ne sait pas exprimer ; et une valeur posée sur
 * le bloc serait figée dans le gabarit à la première sauvegarde du client,
 * alors qu'une règle CSS continue de le corriger. La valeur elle-même est un
 * token — un thème enfant règle la hauteur de ses heroes en une ligne. */
.mf-hero {
	min-height: var(--wp--custom--hero--min-height);
}

@media (max-width: 781px) {
	.mf-hero {
		min-height: var(--wp--custom--hero--min-height-mobile);
	}
}

/* Hero pleine largeur à média débordant — la variante « split » qui va au bord.
 *
 * Générique, sans référence à un projet : une ouverture en deux moitiés où le
 * média touche le bord de la fenêtre et remplit toute la hauteur du hero, le
 * texte gardant sa gouttière. La composition — largeurs, quelle colonne porte
 * le média, padding du texte — reste au thème enfant ; le parent ne fait que
 * les trois choses qu'aucun réglage natif n'exprime.
 *
 * 1. LA HAUTEUR DESCEND AUX COLONNES. `core/columns` ne supporte pas
 *    `minHeight` (son block.json ne déclare pas `dimensions`) : la seule façon
 *    de donner aux deux colonnes la hauteur du hero — donc au média une hauteur
 *    à remplir — est cette règle. La valeur reste le token `.mf-hero`, repli
 *    mobile compris : rien de figé, un enfant règle la hauteur dans son
 *    theme.json.
 * 2. LE MÉDIA REMPLIT SA MOITIÉ, EN COVER. Un `core/image` natif se calibre par
 *    ratio, pas par remplissage. Ici l'image occupe toute sa colonne — elle-même
 *    étirée à la hauteur du hero par le flex natif de `core/columns` — et
 *    déborde proprement en `cover`. Le crochet est `.mf-hero__media`, la classe
 *    que `hero-split` pose déjà sur sa figure : sous `--bleed` seulement elle
 *    prend le remplissage ; le `hero-split` contraint garde son ratio.
 * 3. LA RANGÉE VA AUX DEUX BORDS, PAR CONSTRUCTION. Le cœur pose les colonnes
 *    en `flex-start` sans justification réglable ; si toutes les largeurs sont
 *    explicites et que leur somme n'atteint pas 100 %, le vide tombe en fin de
 *    rangée et le média n'est plus au bord — le modificateur ne tiendrait plus
 *    sa promesse. `space-between` épingle la première colonne à `inline-start`
 *    et la dernière à `inline-end`, quelles que soient les largeurs : le
 *    contact que « Demi-bleed » (section Rayon des surfaces) lit dans le DOM
 *    est vrai par structure, pas par convention. Sans effet sur une
 *    composition qui remplit déjà — 50/50, ou une largeur explicite et une
 *    colonne libre — ni sur la rangée empilée.
 *
 * Sous 782 px les colonnes s'empilent (natif) : la hauteur imposée retombe au
 * repli mobile du token, et le média reprend un ratio plutôt que de remplir une
 * hauteur qui n'existe plus. Aucun `overflow` ne masque quoi que ce soit — la
 * hauteur est réellement portée par la structure. Le ratio mobile est un token
 * local surchargeable, `--mf-hero-media-ratio`. */
.mf-hero--bleed > .wp-block-columns {
	min-block-size: var(--wp--custom--hero--min-height);
	justify-content: space-between;
}

.mf-hero--bleed .mf-hero__media,
.mf-hero--bleed .mf-hero__media .wp-block-image,
.mf-hero--bleed .mf-hero__media img {
	block-size: 100%;
}

.mf-hero--bleed .mf-hero__media img {
	inline-size: 100%;
	object-fit: cover;
}

@media (max-width: 781px) {
	.mf-hero--bleed > .wp-block-columns {
		min-block-size: var(--wp--custom--hero--min-height-mobile);
	}

	.mf-hero--bleed .mf-hero__media,
	.mf-hero--bleed .mf-hero__media .wp-block-image,
	.mf-hero--bleed .mf-hero__media img {
		block-size: auto;
	}

	.mf-hero--bleed .mf-hero__media img {
		aspect-ratio: var(--mf-hero-media-ratio, 16 / 10);
	}
}

/* Bouton secondaire : filet, pas de fond.
 *
 * Le style « outline » du cœur ne se contente pas d'un habillage : il réécrit
 * le padding du bouton (`0.667em / 1.333em`) et pose une bordure de 2px, via des
 * règles `:root :where(…)` de spécificité quasi nulle. Comme le thème ne
 * reprenait ici que la bordure et le fond, ce padding hérité passait — et un
 * bouton outline se retrouvait plus petit que le bouton plein, qui garde le
 * `14px / 24px` de theme.json. On rétablit donc le même padding.
 *
 * Le filet est dessiné en `box-shadow` INSET, pas en `border` : une bordure
 * s'ajoute à la boîte et rendrait l'outline plus haut que le plein de 2px ; un
 * `box-shadow` ne compte pas dans la mise en page, et les deux boutons ont alors
 * exactement la même hauteur. Il suit le rayon en pilule sans rien redéclarer. */
.wp-block-button.is-style-outline > .wp-block-button__link {
	padding: 14px 24px;
	border: 0;
	background: transparent;
	box-shadow: inset 0 0 0 1px var(--wp--preset--color--border-field);
}

.wp-block-button.is-style-outline > .wp-block-button__link:hover {
	background: transparent;
	color: var(--wp--preset--color--text-primary);
	box-shadow: inset 0 0 0 1px var(--wp--preset--color--text-primary);
}

/* ------------------------------------------------------------------
   Fonds sombres
   ------------------------------------------------------------------ */

.has-bg-dark-background-color a {
	color: var(--wp--preset--color--accent-primary-light);
}

.has-bg-dark-background-color a:hover {
	color: var(--wp--preset--color--text-title-dark);
}

/* Les voiles posés sur un fond sombre se dérivent de la palette plutôt que
   d'une valeur figée : `color-mix` garde le filet et les fonds de champ dans
   la teinte du client, quel que soit son theme.json. */
.mf-separator--dark {
	border-color: color-mix(in srgb, var(--wp--preset--color--text-title-dark) 14%, transparent);
	opacity: 1;
}

/* Liste de liens du pied de page : sans puce, et aérée — deux lignes
   d'interlignage pour que chaque lien reste une cible confortable. La valeur
   était recopiée sur les six listes des trois thèmes ; elle appartient au
   composant. */
.mf-footer__list {
	list-style: none;
	padding-inline-start: 0;
	line-height: 2;
}

/* ------------------------------------------------------------------
   En-tête
   ------------------------------------------------------------------ */

.mf-header__phone a {
	color: inherit;
	text-decoration: none;
}

/* En-tête fixe qui se rétracte au défilement, à la manière de Salient.

   On s'accroche à l'enveloppe <header class="wp-block-template-part"> que
   WordPress génère depuis les templates (tagName:"header"), et non à une classe
   interne à la partie. Le comportement reste ainsi une propriété de la région
   « en-tête » au niveau du thème, indépendante du contenu : il tient même quand
   la partie a été personnalisée dans l'éditeur (elle est alors servie depuis la
   base et le fichier parts/header.html est ignoré).

   Les valeurs (padding, largeur de logo, seuil) vivent dans theme.json →
   settings.custom.header et arrivent en --wp--custom--header--*. La classe
   .is-stuck est posée par assets/js/header.js au-delà du seuil, seuil que le
   script relit depuis le token. */
header.wp-block-template-part {
	position: sticky;
	inset-block-start: 0;
	z-index: 100;
	transition: box-shadow var(--wp--custom--header--transition);
}

header.wp-block-template-part.is-stuck {
	box-shadow: 0 1px 0 color-mix(in srgb, var(--wp--preset--color--text-primary) 6%, transparent), 0 8px 24px -18px color-mix(in srgb, var(--wp--preset--color--text-primary) 35%, transparent);
}

/* Utilisateur connecté : l'admin bar occupe le haut du viewport. On décale le
   point de collage de sa hauteur, en suivant les paliers du core WordPress
   (32 px, 46 px sous 782 px, puis 0 sous 600 px où la barre redevient
   défilante et libère le sommet). */
.admin-bar header.wp-block-template-part {
	inset-block-start: 32px;
}

@media screen and (max-width: 782px) {
	.admin-bar header.wp-block-template-part {
		inset-block-start: 46px;
	}
}

@media screen and (max-width: 600px) {
	.admin-bar header.wp-block-template-part {
		inset-block-start: 0;
	}
}

/* La hauteur vient du padding du groupe racine de la partie, posé en inline par
   le bloc. Le !important est donc nécessaire pour le reprendre à la rétraction :
   c'est le seul moyen de battre un style inline qu'on ne maîtrise pas depuis le
   thème (il peut venir de la base). */
header.wp-block-template-part > .wp-block-group {
	/* Sans suivi JavaScript de la transition, les dimensions restent immédiates. */
	transition:
		background-color var(--wp--custom--header--transition),
		border-color var(--wp--custom--header--transition),
		box-shadow var(--wp--custom--header--transition);
}

/* L'ÉTAT DE LA BARRE, DIT UNE FOIS, LU PARTOUT.

   Chaque valeur qui change à la rétraction existe en deux jetons : `padding` et
   `paddingShrink`, `logoWidth` et `logoWidthShrink`. Sans ces deux variables,
   chaque endroit qui s'en sert devait poser SA règle `.is-stuck` pour choisir —
   la barre pour son padding, le logo pour sa largeur, le menu de recouvrement
   pour sa rangée. Trois fois la même décision, à trois endroits, qu'un jour on
   oublie de tenir d'accord. C'est arrivé : le recouvrement n'avait suivi que
   l'état rétracté, et se décalait de 10 px tant qu'on n'avait pas défilé.

   Ici, `.is-stuck` n'est lu qu'une fois. Ces deux variables portent la valeur
   **en vigueur**, et tout consommateur les lit sans savoir qu'il y a deux états :
   le padding de la barre, la largeur du logo, et — parce que le recouvrement est
   rendu à l'intérieur de l'en-tête, donc hérite de lui — la rangée du menu de
   recouvrement. Un état de plus se déclarerait ici, et nulle part ailleurs. */

header.wp-block-template-part {
	--mf-header-padding: var(--wp--custom--header--padding);
	--mf-header-logo-width: var(--wp--custom--header--logo-width);
}

header.wp-block-template-part.is-stuck {
	--mf-header-padding: var(--wp--custom--header--padding-shrink);
	--mf-header-logo-width: var(--wp--custom--header--logo-width-shrink);
}

/* La bascule mobile plus bas dans ce fichier redéfinit les deux jetons de
   largeur sur cette même région ; ces variables suivent sans le savoir, `var()`
   lisant la valeur calculée sur l'élément et non l'ordre du fichier. */

header.wp-block-template-part.is-stuck > .wp-block-group {
	padding-block: var(--mf-header-padding) !important;
}

/* Le logo suit la rétraction. Largeur de repos posée en inline par le bloc
   site-logo ; on la reprend en !important, une seule fois — la variable dit
   déjà lequel des deux jetons s'applique. */
header.wp-block-template-part .wp-block-site-logo {
	inline-size: var(--mf-header-logo-width) !important;
	/* Explicite : le garde global de mouvement réduit pose une durée minimale
	   sur tous les éléments, y compris ceux dont la propriété vaut « all ». */
	transition: none;
}

/* Le script protège le scroll jusqu'à la fin des transitions de dimensions.
   Leur durée courte est distincte de celle des fonds et des ombres. */
header.wp-block-template-part.mf-header-animate > .wp-block-group {
	transition:
		padding-block var(--wp--custom--header--shrink-transition),
		background-color var(--wp--custom--header--transition),
		border-color var(--wp--custom--header--transition),
		box-shadow var(--wp--custom--header--transition);
}

header.wp-block-template-part.mf-header-animate .wp-block-site-logo {
	transition: inline-size var(--wp--custom--header--shrink-transition);
}

@media (prefers-reduced-motion: reduce) {
	header.wp-block-template-part.mf-header-animate > .wp-block-group,
	header.wp-block-template-part.mf-header-animate .wp-block-site-logo {
		transition: none !important;
	}
}

/* ------------------------------------------------------------------
   Le point de bascule de l'en-tête — 1000 px
   ------------------------------------------------------------------

   L'en-tête n'a **qu'un seul** point de bascule, et c'est celui du menu :

   - en dessous, le menu est un **burger** et l'en-tête garde sa barre pleine
     largeur — classique ou transparente ;
   - au-dessus, le menu est **déployé**, et c'est là, et seulement là, que
     l'option « en-tête flottant » peut donner une capsule.

   Deux états, pas trois. Le même nombre commande les deux media queries : celle
   ci-dessous, qui force le burger, et celle de la section « En-tête flottant »,
   qui ouvre la capsule. Elles sont complémentaires (999.98 / 1000) et ne se
   déplacent jamais l'une sans l'autre.

   POURQUOI 1000 PX ET NON LES 600 PX DU CŒUR. À 600 px le menu se déploie, mais
   la rangée logo / menu / téléphone / action ne tient plus sur une ligne : elle
   s'enroule. Mesuré au banc — sous ~800 px pour l'en-tête du socle (4 entrées,
   bouton court), sous ~950 px pour un en-tête d'enfant plus chargé (5 entrées,
   bouton long). Un en-tête sur trois rangées n'est bon dans aucun mode, et il
   rend la capsule impossible : le rayon d'une pilule se réduit à la moitié de la
   hauteur, la capsule devient un ovale. 1000 px est le premier palier rond
   au-dessus de ces deux mesures.

   `theme.json` ne sait pas exprimer de media query (ARCHITECTURE §4) : cette
   valeur vit en CSS, ici, et nulle part ailleurs.

   La bascule est scopée à l'en-tête : un bloc Navigation posé dans le pied de
   page ou dans une composition garde le comportement natif du cœur.

   La spécificité (0,4,1) est délibérée. Elle passe devant la règle du cœur
   (0,3,0) quel que soit l'ordre d'impression des feuilles, qui n'est pas garanti
   sous `should_load_separate_block_assets`.

   Les deux garde-fous du cœur sont conservés : `:not(.hidden-by-default)` (le
   réglage « toujours afficher le menu de recouvrement ») et `:not(.is-menu-open)`
   (le menu ouvert reste ouvert). Avec « ne jamais afficher », le cœur ne rend
   aucun conteneur responsive : ces règles ne matchent alors rien, et le menu
   reste déployé — un menu masqué sans burger est impossible. */

@media (max-width: 999.98px) {

	header.wp-block-template-part .wp-block-navigation__responsive-container:not(.hidden-by-default):not(.is-menu-open) {
		display: none;
	}

	header.wp-block-template-part .wp-block-navigation__responsive-container-open:not(.always-shown) {
		display: flex;
	}
}

/* La rangée d'en-tête tient sur UNE ligne dès que le burger paraît.

   Le groupe racine de la partie « en-tête » aligne trois enfants en flex :
   le logo, le bloc Navigation (réduit au seul burger sous 1000 px) et l'îlot
   d'action (téléphone + bouton). En `flex-wrap: wrap` — le réglage natif du
   bloc — l'îlot le plus large déborde et s'enroule sous le logo, laissant le
   burger seul, coincé entre les deux. Ce n'est pas le bon ordre de lecture.

   On reprend donc, et seulement sous le point de bascule du burger, où le menu
   déployé ne réclame plus la place :

   - `nowrap` interdit l'enroulement : tout reste sur la rangée ;
   - `margin-inline-end: auto` sur le logo pousse l'îlot d'action et le burger
     vers la droite, le logo gardant le bord gauche ;
   - `order: 1` renvoie le burger APRÈS l'îlot d'action — l'ordre voulu, le
     picto tout à droite, une fois le bouton passé.

   Le logo prend sa largeur mobile propre (token `logoWidthMobile`), en
   redéfinissant les deux largeurs — repos et rétractée — sur la région : sous
   le burger la barre est déjà compacte, le logo n'a plus à rétrécir au
   défilement, il garde une taille stable. Le token vit dans `theme.json`
   (settings.custom.header) ; un enfant règle SA largeur mobile en une ligne,
   comme il le fait déjà pour `logoWidth`.

   La gouttière native (blockGap) est trop large pour trois éléments serrés
   sur un petit écran : on la resserre au token, puis d'un cran de plus sous
   600 px, où seuls logo + bouton + burger cohabitent. Là, le bouton d'action
   se compacte aussi — libellé sur une seule ligne, gouttière interne resserrée
   et pilule moins haute — pour tenir un intitulé long (« Commandez vos pièces ↗ »)
   à 360 px sans repousser le burger, et pour l'alléger face au logo et au picto.
   Un intitulé court, lui, n'y perd rien.

   Le crochet est la rangée `.alignwide` de la partie — la seule enveloppe
   flex de l'en-tête, posée par `parts/header.html` comme par une version
   servie depuis la base. */

@media (max-width: 999.98px) {
	header.wp-block-template-part {
		--wp--custom--header--logo-width: var(--wp--custom--header--logo-width-mobile);
		--wp--custom--header--logo-width-shrink: var(--wp--custom--header--logo-width-mobile);
	}

	header.wp-block-template-part .wp-block-group.alignwide {
		flex-wrap: nowrap;
		column-gap: var(--wp--preset--spacing--40);
	}

	header.wp-block-template-part .wp-block-group.alignwide > .wp-block-site-logo {
		margin-inline-end: auto;
	}

	header.wp-block-template-part .wp-block-group.alignwide > .wp-block-navigation {
		order: 1;
	}
}

@media (max-width: 600px) {
	header.wp-block-template-part .wp-block-group.alignwide {
		column-gap: var(--wp--preset--spacing--30);
	}

	header.wp-block-template-part .wp-block-buttons .wp-block-button__link {
		white-space: nowrap;
		padding-block: 0.6em;
		padding-inline: 0.95em;
		font-size: 0.78rem;
	}
}

/* Le menu de recouvrement (overlay) — sous le burger.

   TROIS RÉGLAGES, et un piège d'état corrigé côté script.

   1. TYPO PLUS GRANDE. Déployé, le menu reprend la taille du menu horizontal
      (petite, calibrée pour une barre). Plein écran, il gagne à respirer :
      chaque entrée devient une cible confortable. La taille est un token,
      `settings.custom.header.overlayFontSize` — un enfant l'ajuste en une ligne.

   2. FONDU À L'APPARITION. Le recouvrement surgissait d'un coup. Une animation
      d'opacité, à la vitesse de l'en-tête (`--wp--custom--header--transition`),
      adoucit l'ouverture. Elle ne joue qu'à l'ouverture — le cœur retire
      `is-menu-open` d'un coup à la fermeture, sans état intermédiaire à animer —
      et se tait sous `prefers-reduced-motion` (bloc global en fin de feuille).

   3. UN DRAPEAU POUR LE SCRIPT. `--mf-nav-deployed` vaut 1 au-dessus du point de
      bascule, 0 en dessous. Le nombre reste ici, en CSS, jamais recopié en JS :
      `header.js` lit le drapeau pour savoir si le menu est déployé. Il s'en sert
      pour le PIÈGE SUIVANT — le menu ouvert en burger, puis la fenêtre élargie
      au-delà du seuil : le cœur ne referme pas le recouvrement au redimensionnement.
      Il restait donc ouvert, en surimpression, verrou de défilement compris. Le
      script le referme proprement dès que le drapeau repasse à 1 (voir header.js).
      La fermeture est confiée au cœur (clic sur son bouton de fermeture), seul à
      savoir défaire le verrou de défilement, l'aria et le focus. */

:root {
	--mf-nav-deployed: 0;
}

@media (min-width: 1000px) {
	:root {
		--mf-nav-deployed: 1;
	}
}

@media (max-width: 999.98px) {
	header.wp-block-template-part .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item__content {
		font-size: var(--wp--custom--header--overlay-font-size);
	}

	header.wp-block-template-part .wp-block-navigation__responsive-container.is-menu-open {
		animation: mf-nav-overlay-in var(--wp--custom--header--transition) both;
	}
}

@keyframes mf-nav-overlay-in {
	from {
		opacity: 0;
	}

	to {
		opacity: 1;
	}
}

/* La rangée du haut du recouvrement — celle de la barre, à l'identique.

   Un menu plein écran remplace la page : la barre d'en-tête disparaît sous lui,
   logo compris. Le visiteur n'a alors plus rien qui nomme le site — un panneau
   de liens, une croix, et c'est tout. Et le picto sur lequel il vient d'appuyer
   a sauté ailleurs, dans le coin.

   La réponse aux deux problèmes est la même : le recouvrement rouvre la rangée
   de la barre rétractée, à la même place et aux mêmes tailles. Le logo à
   gauche, le picto à droite — le burger devenu croix, sans bouger d'un pixel.
   Ouvrir le menu n'a alors déplacé personne : cela n'a fait qu'effacer ce qu'il
   y avait autour.

   LE DIALOGUE DEVIENT LA RANGÉE. `core/navigation` empile déjà dans ce cadre la
   croix et les entrées ; on le passe en flex, et les trois pièces s'y rangent
   comme dans la barre : le logo, la croix poussée au bout par une marge auto,
   les entrées renvoyées à la ligne par `flex-basis: 100%`. `align-items: center`
   centre la croix sur le logo, exactement comme la rangée de l'en-tête centre
   le burger sur le sien.

   Le centrage flex suit la hauteur du logo. Si une autre action de la barre
   est plus haute, `header.js` publie aussi sa hauteur de rangée mesurée dans
   `--mf-header-row-height` : le logo décoratif et la croix reprennent le même
   centrage que le logo et le burger d'origine, sans nouveau réglage enfant.

   IL N'Y A DONC AUCUN JETON DE TAILLE ICI, et c'est le cœur de la conception.
   Une hauteur propre au recouvrement était un second réglage pour une seule
   décision : un enfant qui change la largeur de son logo aurait dû penser à
   changer l'autre, et les deux auraient divergé au premier oubli. Un enfant
   règle sa largeur une fois, pour les deux.

   NI JETON, NI CONNAISSANCE DE L'ÉTAT. Cette rangée ne lit pas `.is-stuck` et
   ne sait pas que la barre a deux hauteurs : elle lit `--mf-header-padding` et
   `--mf-header-logo-width`, les deux variables d'état posées en tête de section
   sur la région d'en-tête, dont ce recouvrement est un descendant. Elles portent
   la valeur en vigueur — repos ou rétracté —, et la barre elle-même les lit pour
   son propre padding et son propre logo. Une décision, un endroit, trois
   consommateurs.

   C'est ce qui a manqué au premier jet : le recouvrement choisissait lui-même
   `padding-shrink`, donc ne tombait juste qu'une fois la page défilée. Le
   remède n'est pas une règle de plus pour l'autre état — c'était le deuxième
   jet, et il dupliquait le calcul — mais de ne plus faire ce choix ici.

   LA LARGEUR VIENT DU JETON, LA PROPORTION DU PHP. Le logo est piloté en
   largeur, comme celui de la barre ; sa hauteur doit donc se déduire de la
   forme du fichier, que le CSS ne sait pas lire.

   `content: url()` semblait la donner : un pseudo-élément qui porte une image
   en `content` devient un élément remplacé, et un élément remplacé calcule sa
   hauteur tout seul. Il ne le fait pas. Mesuré ici, à 128 px de large : 54,5 px
   pour un SVG sans dimensions, 103 px pour le même avec — là où un `<img>` en
   rend 51,2. `width` + `height: auto` ne gouvernent pas un `content: url()`
   comme ils gouvernent une image.

   La proportion arrive d'abord du PHP (`mf_overlay_logo_src()`), et
   `aspect-ratio` la consomme. Après chargement du logo, `header.js` publie
   ses dimensions intrinsèques réelles : la médiathèque peut annoncer 1 × 1
   pour un SVG rectangulaire. Aucun parsing SVG ni téléchargement supplémentaire.
   Sans dimensions, le repli `3 / 1` garde une hauteur approchée ; `contain`
   ne déforme jamais l'image.

   LE CALAGE VERTICAL tient sur le dialogue, et non sur ses pièces. Le cœur ouvre
   le recouvrement à la marge de page haute du site — `clamp(1rem, …, 20rem)`,
   sa formule — là où la barre pose sa rangée à son propre padding. Le décalage
   est la différence des deux ; `top` le porte, puisque le cœur pose déjà ce
   dialogue en `position: relative`. Passer par `top` et non par une marge est
   délibéré : `margin-top` est déjà pris par le cœur pour compenser la barre
   d'admin, et cette compensation doit rester intacte — l'en-tête collant se
   décale de la même hauteur ailleurs dans ce fichier, les deux disent la même
   chose.

   Le padding lu est celui **en vigueur**, `--mf-header-padding`, et c'est tout
   ce que cette règle a besoin de savoir. Le verrou de défilement du cœur fige
   `.is-stuck` à l'ouverture : l'état est bien celui du moment où l'on a appuyé,
   et rien ne bouge tant que le menu est ouvert.

   Noter que `clamp(1rem, …)` n'est pas décoratif : `--wp--style--root--padding-top`
   vaut `0px` sur un thème qui ne règle que les marges latérales — le cas des
   deux enfants du socle —, et c'est alors le plancher d'1rem du cœur qui donne
   la marge haute réelle du recouvrement. Reprendre la formule du cœur plutôt
   que la variable seule est ce qui rend le calcul vrai dans les deux cas.

   Aucune valeur n'est recopiée d'une règle à l'autre : les deux termes du calcul
   sont les jetons eux-mêmes, et la formule de la marge est celle du cœur.

   POURQUOI UN `::before` ET PAS UNE IMAGE DANS LE BALISAGE. Le recouvrement est
   construit par `core/navigation`, à l'intérieur du bloc : y insérer un nœud
   demanderait de filtrer le rendu du cœur, ou d'éditer la partie « en-tête » —
   or chaque thème enfant a la sienne, et la demande est justement que le socle
   la porte pour tous. L'adresse du fichier arrive par la variable
   `--mf-overlay-logo`, écrite par `mf_overlay_logo_css()`.

   SANS LOGO, RIEN DE TOUT CECI. La classe `.mf-has-overlay-logo` n'est posée que
   si le site en a un, et elle commande les quatre règles — y compris celle de la
   croix. Ce n'est pas un oubli : sans logo, la rangée n'a plus de hauteur à
   laquelle se centrer, et la place du burger n'a plus de repère. Le
   recouvrement reprend alors exactement le rendu du cœur, croix au coin, qui est
   le bon rendu pour un panneau sans marque.

   PAS DE MEDIA QUERY, et c'est délibéré. Les deux règles voisines (typographie,
   fondu) sont bornées au point de bascule du burger parce qu'elles corrigent
   une apparence *à cette largeur*. Celles-ci répondent à un état, pas à une
   largeur : `is-menu-open` n'existe que lorsque le recouvrement est réellement
   ouvert — le cœur rend le conteneur en liste déployée partout ailleurs. Un
   site réglé sur « toujours afficher le menu de recouvrement », qui l'ouvre
   donc en grand écran, garde ainsi sa rangée.

   Le logo est décoratif, donc muet pour les technologies d'assistance : un
   `content` de pseudo-élément n'est pas exposé, et c'est le bon résultat. Le nom
   du site est déjà annoncé par le document, et le logo ne mène nulle part ici —
   un lien vers l'accueil au milieu d'un menu qui en contient déjà un serait une
   cible de plus, pas un service. La croix, elle, ne change ni de rôle ni de nom
   accessible : on ne déplace qu'une boîte.

   Un enfant dont le recouvrement est sombre surcharge `--mf-overlay-logo` dans
   sa propre feuille, avec l'adresse de son logo clair : le socle ne peut pas
   deviner la couleur d'un fond réglé dans l'éditeur. */

.mf-has-overlay-logo header.wp-block-template-part .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-dialog {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0;
	top: calc(var(--mf-header-padding) - clamp(1rem, var(--wp--style--root--padding-top), 20rem));
}

.mf-has-overlay-logo header.wp-block-template-part .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-dialog::before {
	content: "";
	display: block;
	width: var(--mf-header-logo-width);
	aspect-ratio: var(--mf-overlay-logo-ratio, 3 / 1);
	/* Une action plus haute que le logo agrandit aussi la rangée de la barre.
	   Le fond reste centré à sa proportion réelle, comme l'image d'origine. */
	min-height: var(--mf-header-row-height, 0px);
	background: var(--mf-overlay-logo) no-repeat left center / contain;
}

/* La croix quitte le coin et reprend la place du burger : bout de rangée,
   centrée sur le logo. `position: static` défait le `absolute` du cœur — c'est
   lui, et lui seul, qui la collait en haut à droite du dialogue. */
.mf-has-overlay-logo header.wp-block-template-part .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-close {
	position: static;
	margin-inline-start: auto;
}

/* Les entrées passent à la ligne sous la rangée. La réserve du cœur n'a plus
   lieu d'être — elle ménageait `2rem + 24px` pour ne pas les passer sous la
   croix, qui n'est plus au-dessus d'elles. Restent les 2rem de respiration. */
.mf-has-overlay-logo header.wp-block-template-part .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content {
	flex-basis: 100%;
	padding-top: 2rem;
}


/* Le fond du recouvrement — translucide, et ce qui défile derrière, flouté.

   Un panneau opaque efface la page ; à 90 % elle reste devinable dessous, et le
   menu se lit comme posé par-dessus plutôt que substitué. Le flou est ce qui
   rend cette transparence lisible : sans lui, le texte de la page traverse et
   concurrence les entrées.

   Même montage que la capsule flottante, pour les mêmes raisons, et le
   commentaire de sa section vaut ici mot pour mot : `backdrop-filter` et non
   `filter`, parce que le flou porte sur ce qui est **derrière** ; les
   navigateurs qui l'ignorent rendent le panneau simplement translucide, et la
   lisibilité tient alors à l'opacité seule — c'est pourquoi elle reste haute.
   Le `!important` reprend la classe de preset que le bloc pose lui-même en
   `!important` (`has-…-background-color`), comme les quatre autres de l'en-tête.

   La couleur, elle, ne peut pas être lue : elle est choisie sur le bloc
   Navigation, dans l'éditeur, et aucune variable ne la publie. On reprend donc
   celle du socle — `bg-page`, ce que les trois en-têtes règlent aujourd'hui —,
   et un enfant qui choisit une autre couleur de recouvrement la redit une fois
   dans `--mf-overlay-bg`. */

header.wp-block-template-part .wp-block-navigation__responsive-container.is-menu-open {
	background-color: color-mix(in srgb, var(--mf-overlay-bg, var(--wp--preset--color--bg-page)) var(--wp--custom--header--overlay-opacity), transparent) !important;
	-webkit-backdrop-filter: blur(var(--wp--custom--header--overlay-blur));
	backdrop-filter: blur(var(--wp--custom--header--overlay-blur));
}

/* Le rythme vertical du menu déplié — une hiérarchie, pas une liste plate.

   Déplié en plein écran, un sous-menu n'est plus un tiroir qui s'ouvre au
   survol : c'est une liste imbriquée. Le cœur, lui, l'habille dans les deux cas
   comme un tiroir, et cela donnait une hiérarchie **inversée**. Mesuré sur un
   menu à sous-niveaux, avant :

     entre deux entrées de premier niveau …… 30,3 px
     d'une entrée à son premier enfant …….. 54,2 px
     entre deux enfants ………………………………….. 48,2 px

   Un parent était donc plus loin de ses propres enfants que de ses pairs, et
   les enfants respiraient plus large que leurs parents. Trois règles du cœur,
   trois causes, toutes légitimes pour un tiroir et fausses pour une liste :

   1. `padding: .5em 1em` sur les liens de sous-menu — la mise en forme d'un
      tiroir. Dans le recouvrement, la typo est agrandie (`overlayFontSize`), et
      ces `em` grandissent avec elle : 9 px de padding vertical par lien, donc
      18 px de pas en plus. On l'annule : ici, un lien de sous-menu est une
      entrée comme les autres.
   2. `padding-top: var(--wp--style--block-gap)` sur le conteneur de sous-menu —
      la respiration qui détache un tiroir de son parent. Elle éloigne
      précisément ce qu'il faut rapprocher. On la retire.
   3. `padding-left: 2rem` — l'indentation. Réduite au jeton, la moitié : le
      décrochement doit se voir, pas se remarquer.

   Ce qui reste tient en un seul nombre. `overlayRowGap` écarte les entrées de
   **premier niveau**, et elles seules ; les enfants n'ont aucun écart propre et
   retombent sur leur interligne. La hiérarchie en découle sans qu'on l'écrive :

     parent ↔ parent …… interligne + le jeton   (le plus grand)
     parent ↔ enfant …… interligne              (ils vont ensemble)
     enfant ↔ enfant …… interligne

   Un sous-menu vivant dans le `<li>` de son parent, l'écart entre le dernier
   enfant et l'entrée suivante est celui du premier niveau, sans rien de plus à
   déclarer.

   LA SPÉCIFICITÉ EST DÉLIBÉRÉE, comme ailleurs dans cette feuille. Les règles du
   cœur visées pèsent (0,4,0) et (0,5,0) ; les nôtres reprennent la même chaîne,
   `…container-content` compris, plus l'élément `header` — (0,4,1) et (0,5,1).
   Elles passent donc devant quel que soit l'ordre d'impression des feuilles,
   qui n'est pas garanti sous `should_load_separate_block_assets`. */

header.wp-block-template-part .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation__container {
	row-gap: var(--wp--custom--header--overlay-row-gap);
}

header.wp-block-template-part .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .has-child .wp-block-navigation__submenu-container {
	row-gap: 0;
	padding-top: 0;
	padding-inline: var(--wp--custom--header--overlay-submenu-indent) 0;
}

header.wp-block-template-part .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation__submenu-container .wp-block-navigation-item__content {
	padding: 0;
}

/* Item de navigation actif et survolé.
 *
 * Deux habillages, choisis dans theme.json → settings.custom.header.navStyle
 * et exposés en classe sur <body> (voir functions.php, la seule enveloppe
 * assez stable pour ne jamais dépendre du contenu de la partie « en-tête » —
 * règle 7) :
 * - .mf-nav-style-underline (par défaut) : filet vert sous l'item, posé en
 *   pseudo-élément absolu — jamais en bordure/padding, pour ne jamais déplacer
 *   la ligne de base du texte ni celle des items voisins.
 *
 *   LE FILET SE PLACE SUR LE TEXTE, PAS SUR LA BOÎTE. Il était posé à -6px du
 *   bas de la boîte du lien, ce qui revient à le mesurer depuis le bas de la
 *   **boîte de ligne** — laquelle contient, sous les glyphes, la moitié de
 *   l'approche que l'interligne ajoute. Dans la barre, dont l'interligne est
 *   serré, les deux se confondaient. Dans le menu de recouvrement, où la typo
 *   est agrandie et l'interligne large, cette demi-approche fait 6 px : le
 *   filet tombait 12 px sous son texte, pour 8 px seulement avant l'entrée
 *   suivante — il se lisait comme appartenant à la ligne d'en dessous.
 *
 *   L'ancrage est donc `50% + 0.5em` : la moitié de la boîte de ligne est le
 *   centre du texte quelle que soit l'interligne, et une demi-cadratine plus
 *   bas on est au pied des glyphes. Ce qui suit est le seul écart choisi,
 *   `navUnderlineOffset`, et il est en **em** : le menu se lit à 13 px dans la
 *   barre et à 18 px dans le recouvrement, où un écart fixe paraîtrait serré.
 *   Un filet se pose par rapport à la taille des lettres, pas par rapport à
 *   l'écran. La formule vaut donc telle quelle dans les trois contextes — barre,
 *   tiroir de sous-menu, recouvrement —, sans media query ni second réglage.
 *
 *   Un enfant qui veut un filet plus près ou plus loin change ce seul jeton, et
 *   peut l'écrire en pixels s'il préfère un écart optique constant.
 * - .mf-nav-style-color : pas de filet, le texte passe dans la couleur
 *   d'accent.
 *
 * Le texte du menu ne porte par ailleurs jamais le soulignement du lien
 * générique (theme.json → styles.elements.link) : l'indicateur ci-dessous
 * suffit à marquer l'état, un second trait serait parasite. */
.wp-block-navigation-item > a {
	position: relative;
	text-decoration: none;
}

.mf-nav-style-underline .wp-block-navigation-item > a::after {
	content: "";
	position: absolute;
	inset-inline: 0;
	inset-block-start: calc(50% + 0.5em + var(--wp--custom--header--nav-underline-offset));
	block-size: 1.5px;
	background: var(--wp--preset--color--accent-primary);
	opacity: 0;
	transition: opacity 0.18s ease;
}

.mf-nav-style-underline .wp-block-navigation-item > a:hover::after,
.mf-nav-style-underline .wp-block-navigation .current-menu-item > a::after {
	opacity: 1;
}

.mf-nav-style-underline .wp-block-navigation .current-menu-item > a {
	color: var(--wp--preset--color--text-primary);
}

.mf-nav-style-color .wp-block-navigation-item > a:hover,
.mf-nav-style-color .wp-block-navigation .current-menu-item > a {
	color: var(--wp--preset--color--accent-primary);
}

/* Le tiroir de sous-menu décolle d'un pixel de son parent.

   Le cœur le pose à `top: 100%`, bord à bord : le tiroir touche l'entrée qui
   l'ouvre, et les deux surfaces se lisent comme une seule. Un pixel suffit à
   les séparer — assez pour que le tiroir se donne pour un objet distinct, trop
   peu pour ouvrir un trou où le survol se perdrait : la zone reste continue,
   `mouseleave` ne se déclenche pas dans l'intervalle.

   ON NE REPREND QUE `top`. Le `left: -1px` du cœur, qui aligne la bordure du
   tiroir sur celle de l'item, reste le sien — il le passe à `0` quand le bloc
   Navigation porte un fond (`has-background`), et cet arbitrage-là lui
   appartient.

   PREMIER NIVEAU SEULEMENT. `.wp-block-navigation__container` est la liste
   racine ; les sous-listes portent `…__submenu-container`. Le combinateur
   enfant borne donc la règle aux tiroirs de premier niveau, et laisse
   intacte la règle du cœur qui envoie les niveaux suivants sur le côté
   (`left: 100%; top: -1px`) — un sous-sous-menu ne descend pas, il se déplie.

   La classe est répétée trois fois pour peser (0,5,0) : le cœur défend cette
   position à (0,3,0) et, sur un menu à fond, à (0,4,0). Égaler ne suffirait pas
   — l'ordre d'impression des feuilles n'est pas garanti sous
   `should_load_separate_block_assets`. C'est le procédé du cœur lui-même, qui
   quadruple ce même sélecteur ailleurs. */
.wp-block-navigation__container > .has-child > .wp-block-navigation__submenu-container.wp-block-navigation__submenu-container.wp-block-navigation__submenu-container {
	top: calc(100% + 1px);
}

/* CTA sombre de l'en-tête — variante de style enregistrée dans functions.php.
   Le contenu ne porte qu'une classe : aucun réglage inline à recopier, et le
   bouton se reconstruit depuis le panneau Styles de l'éditeur. */
.wp-block-button.is-style-mf-dark .wp-block-button__link {
	background-color: var(--wp--preset--color--bg-dark);
	color: var(--wp--preset--color--text-title-dark);
	padding: 12px 20px;
	font-size: 12.5px;
	font-weight: 600;
}

.wp-block-button.is-style-mf-dark .wp-block-button__link:hover,
.wp-block-button.is-style-mf-dark .wp-block-button__link:focus-visible {
	background-color: var(--wp--preset--color--accent-primary);
}

/* Logo fond sombre : masqué par défaut quand il est greffé à côté du logo
   standard pour l'en-tête transparent. Le footer l'utilise, lui, comme logo
   principal dans son bloc Site Logo natif.

   Sa largeur dans l'en-tête suit le même token que le logo standard
   (theme.json → settings.custom.header.logoWidth) : les deux versions se
   superposent à l'identique quel que soit le client. Dans le footer, c'est la
   largeur du bloc Site Logo qui commande. */
.mf-logo--dark-background {
	display: none;
	height: auto;
}

header.wp-block-template-part .mf-logo--dark-background {
	width: var(--wp--custom--header--logo-width);
}

.mf-footer__logo .mf-logo--dark-background,
.mf-footer__logo.mf-logo--dark-background {
	display: block;
	max-width: 100%;
	height: auto;
}

/* ------------------------------------------------------------------
   En-tête en surimpression
   ------------------------------------------------------------------

   `.mf-header-overlay` est posée sur <body> dès que l'en-tête est transparent
   **ou** flottant (voir functions.php). Les deux modes ont en commun un seul
   fait, et c'est celui qui change la mise en page : l'en-tête **sort du flux**.
   Une classe pour ce fait, plutôt que `position: fixed` répété dans chacun des
   deux modes — et la compensation de la section d'ouverture n'a plus qu'un seul
   état à connaître au lieu de quatre combinaisons.

   Ce qu'on obtient, et c'est le comportement d'un en-tête de surimpression :
   la page démarre à y = 0, l'en-tête passe par-dessus, et c'est la section
   d'ouverture qui dégage l'espace nécessaire **à l'intérieur d'elle-même**.

   LA HAUTEUR. `--mf-header-height` ne peut pas être un token : elle dépend du
   logo du client (proportion inconnue du thème), du nombre d'entrées du menu et
   de la largeur de l'écran. `assets/js/header.js` la mesure au repos et la
   publie ; la valeur déclarée juste en dessous est le repli utilisé avant que le
   script ait tourné, et pour lui seul — la mesure, posée en style en ligne sur
   <html>, la remplace dès qu'elle arrive. Aucune media query n'est donc
   nécessaire pour suivre les paliers : la mesure les suit d'elle-même.

   POURQUOI PAS `main > :first-child`. `.mf-hero` existe déjà sur les trois
   compositions d'ouverture et dit précisément ce qu'on cherche : « la bande qui
   ouvre la page ». Deux chemins la portent selon le gabarit — enfant direct de
   <main> (`front-page.html`), ou enfant de `post-content`
   (`page-pleine-largeur.html`) — et les deux sont écrits. Une page qui n'ouvre
   pas sur un hero n'est pas laissée sous l'en-tête pour autant : elle reçoit le
   filet de sécurité ci-dessous. */

:root {
	--mf-header-height: var(--wp--custom--header--height);
}

.mf-header-overlay header.wp-block-template-part {
	position: fixed;
	inset-inline: 0;
}

/* L'en-tête n'étant plus dans le flux, <main> en est le premier élément : il ne
   doit pas hériter de l'interligne de bloc que le cœur pose entre frères
   (`:where(.wp-site-blocks) > * { margin-block-start: … }`, 24 px ici). Sans
   cette ligne, « démarrer à y = 0 » serait faux de 24 px. */
.mf-header-overlay .wp-site-blocks > main {
	margin-block-start: 0;
}

/* Le filet de sécurité — pour toutes les pages qui n'ouvrent pas sur un hero :
   fiche produit, archive, résultats de recherche, article. Elles n'ont aucune
   section prévue pour accueillir la surimpression, et leur premier bloc
   passerait sous l'en-tête. On leur rend l'espace au niveau de l'enveloppe de
   page — jamais sur <main>, dont plusieurs gabarits posent le padding haut en
   style en ligne, qu'une feuille de style ne peut pas reprendre.

   C'est le seul endroit où la surimpression réserve de la place. Elle le fait
   quand il n'y a rien d'autre à faire, et le `:has()` s'efface dès qu'une
   section d'ouverture prend le relais. */
.mf-header-overlay .wp-site-blocks:not(:has(main > .mf-hero:first-child, main > .wp-block-post-content > .mf-hero:first-child)) {
	padding-block-start: var(--mf-header-height);
}

/* La section d'ouverture, elle, démarre bien à y = 0 et dégage la hauteur de
   l'en-tête en interne : son fond, son image et son voile occupent tout le haut
   de la page, seul son contenu descend. */
.mf-header-overlay main > .mf-hero:first-child,
.mf-header-overlay main > .wp-block-post-content > .mf-hero:first-child {
	padding-block-start: var(--mf-header-height);
}

/* Un hero qui porte aussi l'enveloppe de bande (hero-split, hero-editorial)
   garde sa respiration : la hauteur de l'en-tête s'y **ajoute**, elle ne la
   remplace pas. Sans ce cas, le titre remonterait de la valeur de la bande au
   moment où l'on active la surimpression. */
.mf-header-overlay main > .mf-hero.is-style-mf-section:first-child,
.mf-header-overlay main > .wp-block-post-content > .mf-hero.is-style-mf-section:first-child {
	padding-block-start: calc(var(--wp--custom--section--padding) + var(--mf-header-height));
}

/* ------------------------------------------------------------------
   En-tête transparent (façon Salient)
   ------------------------------------------------------------------

   La classe .mf-header-transparent n'apparaît sur <body> (voir
   functions.php) que si le réglage est actif ET que le logo fond sombre a été
   fourni depuis Apparence → Réglages du thème. Sans elle, aucune règle
   ci-dessous ne s'applique et l'en-tête garde son comportement par défaut.

   Le mode transparent implique la surimpression : `.mf-header-overlay`
   l'accompagne toujours, et c'est elle qui sort l'en-tête du flux et fait
   dégager la hauteur nécessaire à la section d'ouverture (section précédente).
   Cette section-ci ne s'occupe donc que de la **peinture**. Réglage global : il
   s'applique à tout le site, pas page par page.

   Le fond et le filet bas viennent eux aussi d'une classe et d'un style
   inline posés par le bloc racine de la partie (`has-bg-page-background-color`,
   `style="border-bottom-color:…"`) : même chose, !important pour les reprendre. */

.mf-header-transparent header.wp-block-template-part:not(.is-stuck) > .wp-block-group {
	background-color: transparent !important;
	border-bottom-color: transparent !important;
}

.mf-header-transparent header.wp-block-template-part:not(.is-stuck) .wp-block-navigation-item > a,
.mf-header-transparent header.wp-block-template-part:not(.is-stuck) .mf-header__phone {
	color: var(--wp--preset--color--text-title-dark) !important;
}

.mf-header-transparent header.wp-block-template-part:not(.is-stuck) .mf-logo--dark-background {
	display: block;
}

.mf-header-transparent header.wp-block-template-part:not(.is-stuck) .mf-logo--standard {
	display: none;
}

/* ------------------------------------------------------------------
   En-tête flottant — la capsule
   ------------------------------------------------------------------

   La classe .mf-header-floating vient de <body> (voir functions.php), comme
   .mf-header-transparent. Les deux réglages sont indépendants et se combinent :
   « transparent » gouverne la **peinture** (fond, filet, couleur du texte, quelle
   version du logo), « flottant » gouverne la **géométrie** (largeur, écart au
   haut, rayon). C'est ce qui rend les quatre combinaisons cohérentes sans qu'une
   seule règle ait à les énumérer.

   Rien ici ne touche à `position` ni à `z-index` : le collage reste celui du
   mode courant — `sticky` en temps normal, `fixed` sous .mf-header-transparent.

   Conséquence, et c'est le point le plus important : **le mode flottant seul
   reste dans le flux**. L'écart au haut du viewport est un `padding-block-start`
   posé sur l'enveloppe <header>, donc *à l'intérieur* de la boîte collante. La
   page n'est décalée par rien, aucun gabarit n'a à compenser quoi que ce soit,
   et les décalages d'admin bar (inset-block-start : 32 / 46 / 0 px) continuent
   de s'appliquer tels quels.

   La largeur ne vient pas d'un token mais de la colonne large du site
   (--wp--style--global--wide-size, écrite par WordPress depuis theme.json →
   settings.layout) : la capsule est exactement aussi large que le contenu le
   plus large. Un thème enfant qui change sa `wideSize` obtient une capsule
   cohérente sans avoir rien à régler.

   Le rayon est celui de la charte : 999px, la pilule des actions. Une capsule
   flottante est un objet d'interface, pas un bloc de contenu — le rayon binaire
   du thème (0 ou 999px, README) la range du bon côté, et aucun troisième rayon
   n'entre dans la charte. */

/* La capsule n'existe qu'à l'état « menu déployé », c'est-à-dire au-dessus du
   point de bascule de l'en-tête — voir la section du même nom plus haut, qui
   porte la décision et sa justification. Ce `min-width` et le `max-width` du
   burger sont une seule et même décision, écrite en deux media queries
   complémentaires.

   En dessous, le mode flottant ne s'applique **pas du tout** : l'en-tête reprend
   exactement son rendu classique ou transparent, sans qu'aucune règle n'ait à
   être défaite. Se replier est le bon échec ; dégrader n'en est pas un. */
@media (min-width: 1000px) {

	.mf-header-floating header.wp-block-template-part {
		padding-block-start: var(--wp--custom--header--floating-gap);
		padding-inline: var(--wp--custom--header--floating-gap);

		/* La bande au-dessus de la capsule et les gouttières latérales sont
		   transparentes : elles ne doivent pas davantage intercepter les clics du
		   contenu qui défile dessous. La capsule, elle, les reprend. */
		pointer-events: none;
	}

	.mf-header-floating header.wp-block-template-part > .wp-block-group {
		max-inline-size: var(--wp--style--global--wide-size);
		margin-inline: auto;
		border-radius: var(--wp--custom--header--floating-radius);
		box-shadow: 0 10px 30px -12px color-mix(in srgb, var(--wp--preset--color--text-primary) 22%, transparent);
		pointer-events: auto;

		/* Le padding latéral du groupe vient de `.has-global-padding`, posé par
		   WordPress parce que la partie a une mise en page « contenu contraint » : il
		   vaut la gouttière de page, trop lâche pour une capsule déjà encastrée. On
		   le reprend avec un preset d'espacement existant — sans !important, la
		   règle du cœur ne pesant qu'une seule classe. */
		padding-inline: var(--wp--preset--spacing--60);

		/* Le filet bas est posé en style inline par le bloc. Sous une capsule, un
		   trait unilatéral ne veut plus rien dire, et un contour complet
		   contredirait la charte (« des filets, jamais des cadres »). Même technique
		   et même raison que les !important du mode transparent. On neutralise la
		   couleur et non la largeur, pour que la hauteur de la capsule ne bouge pas
		   d'un pixel entre les états. */
		border-bottom-color: transparent !important;
	}

	/* Verre dépoli — optionnel, réglé depuis Apparence → Réglages du thème
	   (couleur de fond, opacité de 80 à 100 %, flou d'arrière-plan de 0 à 20 px).

	   La classe `.mf-header-glass` n'est posée que si l'en-tête flottant est actif
	   et qu'un des trois réglages s'écarte de sa valeur par défaut ; les trois
	   variables sont alors écrites par `mf_header_glass_css()`. Aux valeurs par
	   défaut, ni classe ni variables : la capsule garde le fond réglé dans
	   l'éditeur et cette règle n'existe pour personne.

	   C'est bien `backdrop-filter` et non `filter` : le flou porte sur ce qui
	   défile **derrière** la capsule. `filter` flouterait le logo et le menu
	   eux-mêmes. Les navigateurs qui ne le connaissent pas ignorent la
	   déclaration et rendent la capsule simplement translucide — la lisibilité
	   tient à l'opacité, jamais au flou, et c'est ce que garantit la borne à 80 %.

	   Le `!important` reprend la classe de preset posée par le bloc
	   (`has-…-background-color`), que le cœur écrit lui-même en `!important` :
	   même raison que les trois autres de l'en-tête.

	   Une seule classe dans le sélecteur, et c'est délibéré : à deux, il
	   pèserait autant que la règle du mode transparent et la battrait par l'ordre
	   du fichier — la capsule serait peinte en haut d'un hero, où elle ne doit
	   pas l'être. */
	.mf-header-glass header.wp-block-template-part > .wp-block-group {
		background-color: color-mix(in srgb, var(--mf-header-glass-bg) var(--mf-header-glass-opacity), transparent) !important;
		-webkit-backdrop-filter: blur(var(--mf-header-glass-blur));
		backdrop-filter: blur(var(--mf-header-glass-blur));
	}

	/* L'ombre pleine largeur de la rétraction n'a plus de sens : c'est la capsule
	   qui porte la sienne, et elle la porte en permanence — un objet qui flotte
	   sans ombre ne flotte pas. */
	.mf-header-floating header.wp-block-template-part.is-stuck {
		box-shadow: none;
	}

	/* Transparent + flottant. En haut de page, la capsule n'est tout simplement pas
	   peinte : le mode transparent lui retire déjà fond et filet, on lui retire
	   l'ombre. Il ne reste que le logo fond sombre et le menu clair, flottant
	   au-dessus du hero. La capsule se matérialise au premier défilement, avec la
	   transition du groupe. */
	.mf-header-transparent.mf-header-floating header.wp-block-template-part:not(.is-stuck) > .wp-block-group {
		box-shadow: none;

		/* Le flou aussi : `backdrop-filter` s'applique même sur un fond
		   transparent, et une bande floutée au-dessus du hero serait de la
		   peinture — exactement ce que le mode transparent retire ici. */
		-webkit-backdrop-filter: none;
		backdrop-filter: none;
	}

}

/* ------------------------------------------------------------------
   Retour en haut
   ------------------------------------------------------------------

   Un bouton rond, fixe en bas à droite, qui apparaît au-delà d'un seuil de
   défilement et ramène en haut de page. Le balisage est posé par PHP sur
   `wp_footer` (functions.php) : il vit donc **hors de `.wp-site-blocks`**, hors
   de tout gabarit et de toute partie de modèle. Aucun contenu éditable ne peut
   le faire disparaître, et il est présent sur les pages créées après la
   livraison (règle 7, prise par le seul bout où elle ne peut pas être détendue).

   Le comportement — poser `.is-visible` au-delà du seuil — vit dans
   assets/js/back-to-top.js. Le seuil, la taille et l'écart aux bords vivent
   dans theme.json → settings.custom.backToTop.

   ÉTAT CACHÉ PAR DÉFAUT, ET C'EST L'INVERSE DE LA RÉVÉLATION. Pour du contenu,
   la règle est « on ne cache jamais ce qu'on ne saurait pas révéler » : d'où la
   classe `.mf-reveal-ready`, sans laquelle rien n'est caché. Pour un contrôle,
   la règle s'inverse — un bouton qui ne peut pas agir ne doit pas s'afficher.
   Sans JavaScript, ou si le script échoue, il reste donc simplement invisible.

   `visibility` plutôt que `display` : la transition d'opacité a besoin que la
   boîte existe. Et `visibility: hidden` fait plus que masquer — il **sort
   l'élément de l'ordre de tabulation**. Un utilisateur clavier ne tombe jamais
   sur un bouton qu'il ne voit pas ; `pointer-events` fait le même office pour
   la souris. Le trio est nécessaire, aucun des trois n'est décoratif.

   Le rayon n'est pas redéclaré : la règle de charte en tête de fichier donne
   déjà 999px à tout `button`, et sur une boîte carrée c'est un cercle. Un
   bouton est une action, la charte lui donne la pilule.

   Le `z-index` reste sous celui de l'en-tête (100, seule autre valeur du
   thème) : si les deux se croisent, c'est la capsule flottante qui passe
   devant, et le menu de recouvrement du cœur — bien plus haut — couvre le
   bouton en entier. Aucun autre élément fixe n'existe dans le thème, et
   l'admin bar occupe le haut : le coin bas est libre.

   L'apparition emprunte la transition de l'en-tête plutôt que d'en déclarer une
   quatrième valeur : deux objets d'interface qui réagissent au même défilement
   doivent réagir à la même vitesse. Le bloc `prefers-reduced-motion` global (fin
   de fichier) la neutralise déjà — le défilement animé, lui, se règle dans le
   script, un `behavior: "smooth"` explicite l'emportant sur le CSS. */

.mf-back-to-top {
	position: fixed;
	inset-block-end: var(--wp--custom--back-to-top--gap);
	inset-inline-end: var(--wp--custom--back-to-top--gap);
	z-index: 90;

	display: flex;
	align-items: center;
	justify-content: center;
	inline-size: var(--wp--custom--back-to-top--size);
	block-size: var(--wp--custom--back-to-top--size);
	padding: 0;
	border: 0;

	background-color: var(--wp--preset--color--text-primary);
	color: var(--wp--preset--color--bg-page);
	box-shadow: 0 10px 30px -12px color-mix(in srgb, var(--wp--preset--color--text-primary) 45%, transparent);
	cursor: pointer;

	opacity: 0;
	visibility: hidden;
	pointer-events: none;
	transition:
		opacity var(--wp--custom--header--transition),
		visibility var(--wp--custom--header--transition),
		background-color var(--wp--custom--header--transition);
}

.mf-back-to-top.is-visible {
	opacity: 1;
	visibility: visible;
	pointer-events: auto;
}

/* Encre au repos, accent au survol. Le bouton ne rivalise ainsi jamais avec un
   appel à l'action de la page, qui porte l'accent en permanence. Le
   `:focus-visible` vient de la règle globale (section Accessibilité) : un vrai
   <button> en hérite, il n'y a rien à écrire ici. */
.mf-back-to-top:hover {
	background-color: var(--wp--preset--color--accent-primary);
}

/* La flèche est dimensionnée en proportion du bouton : un thème enfant qui
   change `size` obtient une icône à l'échelle, sans second réglage. */
.mf-back-to-top__icon {
	inline-size: 40%;
	block-size: auto;
}

/* Un élément fixe n'a aucun sens sur une feuille imprimée. */
@media print {
	.mf-back-to-top {
		display: none;
	}
}

/* ------------------------------------------------------------------
   Révélation au défilement
   ------------------------------------------------------------------

   Un bloc apparaît (fondu + léger déplacement) en entrant dans le viewport,
   façon Salient. On s'y inscrit par une classe posée dans le champ natif
   « Classe(s) CSS additionnelle(s) » de l'éditeur (règle 5) :
   - `mf-reveal`          → l'élément apparaît ;
   - `mf-reveal--down/left/right/zoom/fade` → sens de l'apparition (défaut :
     depuis le bas) ;
   - `mf-reveal-stagger`  → sur un conteneur (Groupe, Colonnes…), ses enfants
     directs apparaissent en cascade.

   L'état caché n'existe que sous `.mf-reveal-ready`, classe posée par
   assets/js/reveal.js et par lui seul (jamais dans l'éditeur, jamais sans JS) :
   ce qu'on cache, on sait toujours le révéler. La classe `.is-visible` est
   ensuite ajoutée par le script quand le bloc atteint l'écran.

   Durée, distance, easing et décalage de cascade : theme.json →
   settings.custom.reveal, en --wp--custom--reveal--*. */

.mf-reveal-ready .mf-reveal,
.mf-reveal-ready .mf-reveal-stagger > * {
	opacity: 0;
	transform: translateY(var(--wp--custom--reveal--distance));
	transition:
		opacity var(--wp--custom--reveal--duration) var(--wp--custom--reveal--easing),
		transform var(--wp--custom--reveal--duration) var(--wp--custom--reveal--easing);
}

/* Sens de l'apparition — même contexte que l'état caché pour le battre, alors
   que `.is-visible` (plus spécifique) rétablit la position finale. */
.mf-reveal-ready .mf-reveal--down {
	transform: translateY(calc(-1 * var(--wp--custom--reveal--distance)));
}

.mf-reveal-ready .mf-reveal--left {
	transform: translateX(var(--wp--custom--reveal--distance));
}

.mf-reveal-ready .mf-reveal--right {
	transform: translateX(calc(-1 * var(--wp--custom--reveal--distance)));
}

.mf-reveal-ready .mf-reveal--zoom {
	transform: scale(0.94);
}

.mf-reveal-ready .mf-reveal--fade {
	transform: none;
}

.mf-reveal-ready .mf-reveal.is-visible,
.mf-reveal-ready .mf-reveal-stagger > .is-visible {
	opacity: 1;
	transform: none;
}

/* Mouvement réduit : on affiche d'emblée, sans attendre l'observateur ni jouer
   la transition (le bloc global d'accessibilité neutralise déjà les durées). */
@media (prefers-reduced-motion: reduce) {
	.mf-reveal-ready .mf-reveal,
	.mf-reveal-ready .mf-reveal-stagger > * {
		opacity: 1;
		transform: none;
	}
}

/* ------------------------------------------------------------------
   Parallaxe au défilement
   ------------------------------------------------------------------

   Un bloc — ou l'image de fond d'un bloc — dérive un peu moins vite que la
   page, pour donner de la profondeur sans agiter. Même inscription que la
   révélation : une classe dans « Classe(s) CSS additionnelle(s) » (règle 5).
   Deux dimensions, chacune gouvernée par ses seuls modificateurs (ARCHITECTURE
   §5, « deux axes plutôt qu'un troisième mode ») :
   - la CIBLE — `mf-parallax` seul : l'élément dérive ; `mf-parallax--bg` :
     son image de fond dérive, lui reste en place ; `mf-parallax--custom` : le
     socle ne rend rien, il mesure et fournit les variables, l'enfant place ;
   - l'AMPLITUDE — `mf-parallax--slow` / `--fast` : moitié, double.
   Toutes les combinaisons sont légitimes, y compris avec `mf-reveal`.

   LE SCRIPT MESURE, LE CSS DÉCIDE. assets/js/parallax.js n'écrit qu'une
   grandeur géométrique sur l'élément, `--mf-parallax-offset` : la distance du
   centre du viewport au centre de l'élément. Tout le reste est calculé ici :
   le décalage `--mf-parallax-shift` vaut cette distance multipliée par la
   vitesse, plafonnée — un élément loin du centre n'est jamais projeté loin.
   Vitesse et plafond sont des jetons (theme.json → settings.custom.parallax) ;
   la vitesse est aussi une variable, `--mf-parallax-speed`, qu'un enfant peut
   poser sous une classe à lui (une valeur négative inverse le sens). Les
   variables sont définies hors garde : sans script elles valent zéro, et un
   enfant les consomme sans condition — c'est tout le mode `--custom`.

   LE MODE FOND NE PEUT PAS DÉCOUVRIR UN BORD. En pourcentage, la position
   d'une image de fond *aligne* ses bords sur ceux de la boîte : entre 0 % et
   100 %, elle ne les dépasse jamais. `--mf-parallax-bg-y` est donc borné par
   ces deux valeurs — mais par `min(0%, 100%)` et `max(0%, 100%)`, pas par
   `0%` et `100%` dans cet ordre : sous `cover`, l'image dépasse la boîte, et
   100 % se résout alors en une longueur *négative*, sous 0 %. Un `clamp()`
   écrit dans l'ordre naïf aurait ses bornes inversées et s'effondrerait sur la
   première (image calée en haut, immobile). Avec de la marge (un portrait
   recadré dans une bande large), le décalage joue dedans ; sans marge (mobile
   étroit, où `cover` tombe juste), il ne joue pas — le fond reste immobile
   plutôt que de laisser affleurer une bande de couleur. Se replier vaut mieux
   que dégrader (ARCHITECTURE §5) : là où l'option ne peut pas être juste, elle
   ne s'applique pas, et pas *à moitié*. Repos à 50 % : le mode suppose une
   image centrée verticalement.

   `translate`, PAS `transform`. La propriété individuelle se compose avec un
   `transform` déjà porté par l'élément — celui de `.mf-reveal`, par exemple,
   qu'elle ne remplace pas. Un `transform` ici clobberait l'apparition.

   Le rendu n'existe que sous `.mf-parallax-ready`, posée par parallax.js et
   par lui seul (jamais dans l'éditeur, jamais sans JS) : on ne déplace rien
   qu'on ne saurait pas piloter. La garde est écrite en `:where()`, à poids
   nul — elle conditionne, elle ne pèse pas. */

.mf-parallax {
	--mf-parallax-speed: var(--wp--custom--parallax--speed);
	--mf-parallax-max: var(--wp--custom--parallax--max);
	--mf-parallax-shift: clamp(
		calc(-1 * var(--mf-parallax-max)),
		calc(var(--mf-parallax-offset, 0px) * var(--mf-parallax-speed)),
		var(--mf-parallax-max)
	);
	--mf-parallax-bg-y: clamp(min(0%, 100%), calc(50% + var(--mf-parallax-shift)), max(0%, 100%));
}

.mf-parallax--slow {
	--mf-parallax-speed: calc(var(--wp--custom--parallax--speed) / 2);
}

.mf-parallax--fast {
	--mf-parallax-speed: calc(var(--wp--custom--parallax--speed) * 2);
}

:where(.mf-parallax-ready) .mf-parallax:not(.mf-parallax--bg):not(.mf-parallax--custom) {
	translate: 0 var(--mf-parallax-shift);
	will-change: transform;
}

/* `!important`, commenté comme §5 l'exige. Le cœur écrit le point focal du
   bloc EN LIGNE (`style="background-position: …"`, block-supports/background),
   et une feuille de style ne répond jamais à une déclaration en ligne autrement.
   Rien d'autre n'était ouvert : ni spécificité, ni ordre. Mais le réglage du
   client n'est pas confisqué, il est *cédé* — c'est lui qui a posé
   `mf-parallax--bg`, décision plus tardive et plus précise sur cet axe que son
   point focal, et seul `-y` est pris : l'axe horizontal reste au point focal.
   Un fond à plusieurs couches ne passe pas par ici (le socle les déplacerait
   toutes) : c'est `--custom`, et l'enfant place `--mf-parallax-bg-y` sur la
   seule couche qui doit bouger. */
:where(.mf-parallax-ready) .mf-parallax--bg {
	background-position-y: var(--mf-parallax-bg-y) !important;
}

/* Mouvement réduit : le décalage vaut zéro, dans les deux modes et chez tout
   enfant qui consomme la variable. Le bloc global d'accessibilité ne suffit pas
   ici — il neutralise durées et transitions, pas une valeur qu'un script écrit
   à chaque image. Et c'est cette règle, réévaluée en direct par le navigateur,
   qui couvre un réglage système changé en cours de visite ; le script, lui,
   ne relit le réglage qu'au chargement, pour s'épargner le travail. */
@media (prefers-reduced-motion: reduce) {
	.mf-parallax {
		--mf-parallax-shift: 0px;
	}
}

/* ------------------------------------------------------------------
   Contenu éditorial (articles de fond)
   ------------------------------------------------------------------ */

.wp-block-post-content > * + * {
	margin-block-start: var(--wp--preset--spacing--50);
}

.wp-block-post-content h2 {
	margin-block-start: var(--wp--preset--spacing--80);
}

.wp-block-post-content h3 {
	margin-block-start: var(--wp--preset--spacing--70);
}

.wp-block-table table {
	border-collapse: collapse;
}

.wp-block-table td,
.wp-block-table th {
	border-color: var(--wp--preset--color--border-rule);
	padding: 12px 14px;
}

.wp-block-table th {
	font-weight: 600;
	text-align: start;
}

/* ------------------------------------------------------------------
   WooCommerce — mode catalogue
   ------------------------------------------------------------------ */

/* Ceinture et bretelles : les filtres PHP retirent déjà prix et panier,
   ce sélecteur couvre les rendus que WooCommerce produit hors de nos gabarits. */
.woocommerce .price,
.woocommerce .add_to_cart_button,
.woocommerce .single_add_to_cart_button,
.wp-block-woocommerce-product-price,
.wp-block-woocommerce-product-button {
	display: none;
}

/* Carte produit : filet plutôt que boîte, CTA révélé au survol sur pointeur fin. */
.wp-block-woocommerce-product-template li {
	display: flex;
	flex-direction: column;
	padding-block-end: var(--wp--preset--spacing--50);
	border-block-end: 1px solid var(--wp--preset--color--border-rule);
}

/* ------------------------------------------------------------------
   Zoom au survol — option des blocs Bannière et Image mise en avant
   ------------------------------------------------------------------

   `is-style-mf-hover-zoom` anime uniquement l’image du bloc marqué.
   Le clipping est natif pour Cover et posé sur le lien des images mises en avant.
   Ni texte, ni voile, ni lien,
   ni mise en page ne sont modifiés ; `.mf-card` garde son propre contrat.
   Les fonds fixes/répétés (un <div>) et vidéos restent natifs sans zoom.
   `scale` compose avec les autres transformations, sans les remplacer.
   L'effet est absent sur écran tactile et sous mouvement réduit, y compris
   au focus. Les tokens hoverZoom peuvent être surchargés par un enfant. */

/* Le lien reste hors d’un conteneur découpé : son focus externe reste visible.
   Sans lien, le figure découpe lui-même son image. */
.wp-block-post-featured-image.is-style-mf-hover-zoom > a,
.wp-block-post-featured-image.is-style-mf-hover-zoom:not(:has(> a)) {
	overflow: hidden;
	border-radius: var(--wp--custom--radius--floating);
}

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
	.wp-block-cover.is-style-mf-hover-zoom > img.wp-block-cover__image-background,
	.wp-block-post-featured-image.is-style-mf-hover-zoom img {
		transition: scale var(--wp--custom--hover-zoom--duration, 0.2s) ease;
	}

	.wp-block-cover.is-style-mf-hover-zoom:is(:hover, :focus-within) > img.wp-block-cover__image-background,
	.wp-block-post-featured-image.is-style-mf-hover-zoom:is(:hover, :focus-within) img {
		scale: var(--wp--custom--hover-zoom--scale, 1.03);
	}
}

/* Carte dans une grille — le comportement, pas la structure.
 *
 * Les cartes des différents contenus (article, coup de cœur, événement, revue
 * de presse, produit) n'ont **pas** la même structure : ni la même image, ni la
 * même méta, ni le même appel à l'action, ni même le même bloc image côté
 * WooCommerce. Les fondre dans une composition unique demanderait trois axes de
 * conditions — on garde donc une composition par contenu.
 *
 * Ce qu'elles partagent réellement, c'est un comportement de conteneur : tenir
 * toute la hauteur de sa cellule de grille, et animer légèrement son visuel au
 * survol du lien. C'est mutualisable, et c'est du CSS.
 *
 * Une carte porte donc `mf-card` plus son propre modificateur : `mf-card
 * ev-book`, `mf-card imi-piece`… */
.mf-card {
	block-size: 100%;
}

/* Deux ancrages, une seule intention.
 *
 * `.mf-card` est posée par le thème sur le Groupe d'une carte. Les cartes
 * produit, elles, sont des `<li>` fabriqués par `woocommerce/product-template`
 * — on ne peut pas leur poser de classe depuis le balisage, le `className` du
 * bloc atterrit sur le `<ul>`. Le sélecteur WooCommerce est donc l'équivalent
 * imposé de `.mf-card`, et les deux sont déclarés ensemble à chaque règle pour
 * qu'ils ne puissent pas diverger. Ils décrivent la même carte.
 *
 * Le visuel change de bloc selon le contenu : `post-featured-image` côté
 * éditorial, `mf-card__media` côté WooCommerce ou sur une image posée à la
 * main. Le zoom vise les trois.
 *
 * `.mf-card__media` n'a longtemps été reconnue que **sous** une boucle
 * WooCommerce, alors que son nom ne le dit pas : la même classe posée sur
 * l'enveloppe d'un `core/image` dans une carte écrite à la main ne produisait
 * rien, sans erreur ni trace. Elle vaut désormais dans les deux contextes —
 * c'est ce que son nom promettait.
 *
 * L'enveloppe qui découpe n'est pas toujours un lien : une carte dont seul le
 * titre est cliquable pose `mf-card__media` sur un Groupe. La règle vise donc
 * la classe elle-même autant que le lien qu'elle peut contenir. */
.mf-card .wp-block-post-featured-image a,
.mf-card .mf-card__media,
.mf-card .mf-card__media a,
.wp-block-woocommerce-product-template li .mf-card__media a {
	display: block;
	overflow: hidden;
}

.mf-card .wp-block-post-featured-image img,
.mf-card .mf-card__media img,
.wp-block-woocommerce-product-template li .mf-card__media img {
	transition: transform 0.5s var(--wp--custom--reveal--easing, ease);
}

.mf-card:hover .wp-block-post-featured-image img,
.mf-card:hover .mf-card__media img,
.wp-block-woocommerce-product-template li:hover .mf-card__media img {
	transform: scale(1.03);
}

/* Un visuel qui ne remplit pas sa boîte ne laisse pas un trou blanc — une
   image transparente, un PNG de logo, le temps d'un chargement.

   LA TEINTE VA SUR L'IMAGE, JAMAIS SUR SON ENVELOPPE. La règle posait le fond
   sur le `<figure>` de `post-featured-image` d'un côté, sur l'`img` de
   `mf-card__media` de l'autre : deux éléments différents pour une seule
   intention. Tant que tout était à angles vifs, la différence ne se voyait
   pas. Depuis que le rayon de charte arrondit l'image et son lien — et **pas**
   le `figure` —, l'enveloppe dépasse aux quatre coins et dessine des triangles
   gris derrière une image arrondie.

   La règle est donc alignée sur les trois voisines, qui visent toutes l'`img` :
   la teinte est co-localisée avec le rayon et avec la découpe, et ne peut plus
   déborder de ce qu'elle est censée doubler.

   `post-featured-image` SORT DE LA LISTE, et pas seulement pour les coins. Un
   contenu sans image à la une ne rend **aucun élément** : le bloc renvoie une
   chaîne vide. La teinte n'y a donc jamais pu remplir le trou blanc que son
   nom promettait — elle ne s'affichait que là où une image existait déjà, ce
   qui est exactement le cas où elle ne sert à rien. Un thème enfant qui veut
   un visuel de repli le fait avec une image, pas avec un aplat (voir
   `lespritvif/inc/maquette.php`). */
.mf-card .mf-card__media img,
.wp-block-woocommerce-product-template li .mf-card__media img {
	background: var(--wp--preset--color--bg-placeholder);
}

/* Un visuel de carte est RECADRÉ, jamais mis en boîte.
 *
 * Une grille de cartes n'a qu'une seule façon d'être lisible : des vignettes
 * de même format, alignées. Un visuel « contenu » (`object-fit: contain`)
 * respecte les proportions du fichier et paie ce respect au prix fort — deux
 * bandes de fond de part et d'autre, dont la largeur change à chaque produit.
 * La grille se met alors à respirer de travers, et le fond de repli, prévu
 * pour signaler une image *absente*, devient la moitié de l'image présente.
 *
 * La règle vaut pour les trois blocs qui peuvent porter le visuel d'une carte,
 * déclarés ensemble ici comme partout ailleurs pour qu'ils ne divergent pas.
 *
 * ELLE NE SUFFIT PAS SEULE, et c'est ce qu'il faut retenir :
 * `woocommerce/product-image` écrit `object-fit` **en style en ligne** depuis
 * son attribut `scale` (ProductImage::render_image) — un style en ligne qu'une
 * feuille ne reprend pas, et sur lequel un `!important` est proscrit puisque
 * l'éditeur expose le réglage. Toute composition qui pose ce bloc doit donc
 * déclarer `"scale":"cover"`. La règle ci-dessous est la valeur par défaut
 * pour tous les autres cas — `core/image` posé à la main, visuel sans réglage
 * — et la trace écrite de l'intention. */
.mf-card .wp-block-post-featured-image img,
.mf-card .mf-card__media img,
.wp-block-woocommerce-product-template li .mf-card__media img {
	object-fit: cover;
}

.mf-card__cta {
	margin-block-start: auto;
}

/* Le CTA révélé au survol, sur pointeur fin.
 *
 * Ce qui cache et ce qui révèle sont volontairement scopés au **même**
 * contexte. Auparavant, `.mf-card__cta` était masquée toute seule et n'était
 * révélée que sous le sélecteur WooCommerce : la même classe, posée sur une
 * carte éditoriale, donnait un CTA définitivement invisible au-dessus de
 * 782 px — sans erreur, sans trace. Une carte hors de ces deux contextes n'est
 * désormais tout simplement jamais masquée. */
@media (hover: hover) and (min-width: 782px) {
	.mf-card .mf-card__cta,
	.wp-block-woocommerce-product-template li .mf-card__cta {
		opacity: 0;
		transition: opacity 0.18s ease;
	}

	.mf-card:hover .mf-card__cta,
	.mf-card:focus-within .mf-card__cta,
	.wp-block-woocommerce-product-template li:hover .mf-card__cta,
	.wp-block-woocommerce-product-template li:focus-within .mf-card__cta {
		opacity: 1;
	}
}

/* Panneau égalisé — la carte de mise en page, sœur découplée de `.mf-card`.
 *
 * `.mf-card` tient déjà toute la hauteur de sa cellule, mais il porte le
 * comportement des cartes de CONTENU : zoom du média au survol, et CTA révélé
 * au survol (donc masqué au repos au-dessus de 782 px). Sur une carte de mise
 * en page — deux « portes » d'accueil, un triptyque d'offres, une grille de
 * fonctionnalités — ce CTA masqué serait un piège.
 *
 * Ce style de bloc (enregistré dans functions.php, sur `core/group`) ne garde
 * donc que la mise en page, sans aucun survol : le groupe prend toute la
 * hauteur de sa colonne — les colonnes s'égalisent nativement, la plus haute
 * impose la hauteur — et s'organise en pile verticale. Un enfant y pose son
 * habillage (`ev-*`, `imi-*`) et repousse l'action en bas avec la classe
 * générique `.mf-card__cta` (`margin-block-start: auto`), qui hors d'un
 * `.mf-card` ne fait que cela, sans jamais masquer.
 *
 * Aucune hauteur fixe : tout vient du flex. Poser aussi la mise en page flex
 * du groupe côté éditeur (Groupe → orientation verticale) fait porter l'écart
 * entre blocs par `gap` plutôt que par des marges, et l'écart au-dessus du CTA
 * repoussé est alors préservé. */
.is-style-mf-panel {
	display: flex;
	flex-direction: column;
	block-size: 100%;
}

/* Ligne de caractéristique : libellé à gauche, valeur à droite, filet entre. */
.mf-spec {
	padding-block: 10px;
	border-block-end: 1px solid var(--wp--preset--color--border-alt);
	gap: var(--wp--preset--spacing--40);
}

.mf-spec p {
	margin: 0;
}

/* Une caractéristique non renseignée ne doit pas laisser une ligne vide. */
.mf-spec:has(p:last-child:empty),
.mf-arguments p:empty,
.mf-card__specs p:empty {
	display: none;
}

/* Colonne de filtres. */
.mf-filters__title {
	padding-block-end: 12px;
	border-block-end: 1px solid var(--wp--preset--color--border-rule);
}

/* Intitulé d'un groupe de filtres — marque, famille. Même famille visuelle que
   le titre de colonne, mais sur la taille `meta` : c'est un intertitre de
   liste, pas un en-tête de colonne. Sa typographie vit ici plutôt qu'en
   variante enregistrée — un seul composant, un seul endroit. */
.mf-filters__subtitle {
	margin-block: 0 0.625rem;
	color: var(--wp--preset--color--text-nav);
	font-family: var(--wp--preset--font-family--sans);
	font-weight: 700;
	letter-spacing: 1.2px;
	text-transform: uppercase;
}

/* Les filtres actifs n'occupent aucune place tant que rien n'est coché :
   WooCommerce pose `hidden` et `wc-block-product-filter--hidden` sur les
   blocs vides, mais l'espacement du conteneur, lui, subsiste. */
.wp-block-woocommerce-product-filters > [hidden] {
	display: none;
}

/* Liste de familles, rendue par core/categories (product_cat). */
.mf-familles ul {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
	gap: 0 var(--wp--preset--spacing--50);
	list-style: none;
	padding-inline-start: 0;
}

.mf-familles li {
	padding-block: 14px;
	border-block-end: 1px solid var(--wp--preset--color--border-alt);
}

/* ------------------------------------------------------------------
   Details — animation opt-in
   ------------------------------------------------------------------ */

/* État transitoire posé uniquement par details.js sur une cible opt-in.
   Sans JS, sans mf-details ou au repos : aucune modification de rendu. */
details.mf-details.mf-details-animating {
	overflow: hidden;
}

/* ------------------------------------------------------------------
   FAQ — l'accordéon natif
   ------------------------------------------------------------------

   Une liste de `core/details` séparés par des filets. Le filet ouvre chaque
   rangée et un dernier ferme la liste : c'est le même dessin partout — bande
   FAQ d'une page, FAQ d'une fiche produit —, et c'est précisément pourquoi il
   vit ici plutôt que dans les attributs de chaque bloc Détails.

   Ces rangées sont sous `templateLock: contentOnly` : le client y écrit ses
   questions, il n'en règle ni la respiration ni les filets. Les laisser en
   attributs de bloc ne rendait donc rien réglable — cela recopiait seulement
   quatre déclarations identiques sur chaque question, à tenir à jour une par
   une. Le `blockGap: 0` du groupe, lui, reste sur le bloc : c'est une
   disposition, et l'éditeur la montre. */

.mf-faq {
	border-block-end: 1px solid var(--wp--preset--color--border-alt);
}

.mf-faq__row {
	padding-block: var(--wp--preset--spacing--50);
	border-block-start: 1px solid var(--wp--preset--color--border-alt);
}

.mf-faq__row summary {
	cursor: pointer;
	font-size: var(--wp--preset--font-size--lead);
	font-weight: 500;
	list-style: none;
}

.mf-faq__row summary::-webkit-details-marker {
	display: none;
}

.mf-faq__row summary::after {
	content: "+";
	float: inline-end;
	color: var(--wp--preset--color--accent-primary);
	font-size: 20px;
	line-height: 1;
}

.mf-faq__row[open] summary::after {
	content: "–";
}

/* ------------------------------------------------------------------
   Contact Form 7
   ------------------------------------------------------------------

   Le balisage de CF7 n'est pas modifiable depuis le thème : il empile
   des <p>, des <br> et des <span> autour des champs. On neutralise donc
   cette structure (les <br> deviennent inutiles dès que libellés et
   enveloppes de champ passent en bloc) plutôt que de la combattre au cas
   par cas. Seules les classes stables de CF7 sont ciblées.

   Faute de couleur d'erreur dans la palette, l'orange éditorial sert
   d'alerte et le vert d'action sert de confirmation. */

.wpcf7 {
	--mf-form-gap: var(--wp--preset--spacing--40);
}

/* La réponse pour lecteurs d'écran ne doit rien occuper visuellement. */
.wpcf7 .screen-reader-response {
	position: absolute;
	inline-size: 1px;
	block-size: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

/* Structure — on remplace les sauts de ligne par des marges. */

.wpcf7-form br {
	display: none;
}

.wpcf7-form > p,
.wpcf7-form .col > p {
	margin-block: 0 var(--wp--preset--spacing--50);
}

.wpcf7-form > p:last-of-type,
.wpcf7-form .col > p:last-child {
	margin-block-end: 0;
}

/* Colonnes du formulaire — deux champs côte à côte sur large écran. */
.wpcf7-form .cols {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 0 var(--wp--preset--spacing--50);
	margin-block-end: var(--wp--preset--spacing--50);
}

.wpcf7-form .col {
	min-inline-size: 0;
}

/* Libellés et champs */

.wpcf7-form label {
	display: block;
	margin-block-end: var(--wp--preset--spacing--50);
	color: var(--wp--preset--color--text-primary);
	font-family: var(--wp--preset--font-family--sans);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	line-height: 1.4;
}

.wpcf7-form .col > p > label:last-child,
.wpcf7-form > p > label:last-child {
	margin-block-end: 0;
}

.wpcf7-form-control-wrap {
	display: block;
	margin-block-start: var(--mf-form-gap);
}

.wpcf7-form input[type="text"],
.wpcf7-form input[type="email"],
.wpcf7-form input[type="tel"],
.wpcf7-form input[type="url"],
.wpcf7-form input[type="date"],
.wpcf7-form input[type="number"],
.wpcf7-form select,
.wpcf7-form textarea {
	inline-size: 100%;
	box-sizing: border-box;
	max-inline-size: 100%;
	padding: 13px 15px;
	border: 1px solid var(--wp--preset--color--border-field);
	background-color: var(--wp--preset--color--bg-surface);
	color: var(--wp--preset--color--text-primary);
	font-family: var(--wp--preset--font-family--sans);
	/* 16px minimum : en dessous, iOS zoome à la mise au point. */
	font-size: var(--wp--preset--font-size--body);
	font-weight: 400;
	line-height: 1.5;
	transition: border-color 0.18s ease, background-color 0.18s ease;
}

.wpcf7-form textarea {
	min-block-size: 180px;
	resize: vertical;
}

.wpcf7-form ::placeholder {
	color: var(--wp--preset--color--text-subtle);
	opacity: 1;
}

.wpcf7-form input:hover,
.wpcf7-form select:hover,
.wpcf7-form textarea:hover {
	border-color: var(--wp--preset--color--text-subtle);
}

.wpcf7-form input:focus,
.wpcf7-form select:focus,
.wpcf7-form textarea:focus {
	border-color: var(--wp--preset--color--accent-primary);
	background-color: var(--wp--preset--color--bg-page);
	outline: none;
}

/* Case à cocher RGPD — mention longue, donc alignée en haut. */

.wpcf7-checkbox .wpcf7-list-item,
.wpcf7-radio .wpcf7-list-item,
.wpcf7-acceptance .wpcf7-list-item {
	display: block;
	margin: 0;
}

.wpcf7-checkbox .wpcf7-list-item + .wpcf7-list-item,
.wpcf7-radio .wpcf7-list-item + .wpcf7-list-item {
	margin-block-start: var(--wp--preset--spacing--30);
}

.wpcf7-checkbox label,
.wpcf7-radio label,
.wpcf7-acceptance label {
	display: flex;
	align-items: flex-start;
	gap: 10px;
	margin-block-end: 0;
	color: var(--wp--preset--color--text-body);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 400;
	cursor: pointer;
}

.wpcf7-form input[type="checkbox"],
.wpcf7-form input[type="radio"] {
	flex: none;
	inline-size: 17px;
	block-size: 17px;
	margin: 2px 0 0;
	accent-color: var(--wp--preset--color--accent-primary);
	cursor: pointer;
}

.wpcf7-list-item-label {
	display: block;
}

/* Envoi */

.wpcf7-form input[type="submit"] {
	padding: 14px 30px;
	border: none;
	background-color: var(--wp--preset--color--accent-primary);
	color: var(--wp--preset--color--text-title-dark);
	font-family: var(--wp--preset--font-family--sans);
	font-size: var(--wp--preset--font-size--action);
	font-weight: 600;
	cursor: pointer;
	transition: background-color 0.18s ease;
}

.wpcf7-form input[type="submit"]:hover,
.wpcf7-form input[type="submit"]:focus-visible {
	background-color: var(--wp--preset--color--text-primary);
}

/* Pendant l'envoi, CF7 désactive le bouton : il doit le montrer. */
.wpcf7-form input[type="submit"]:disabled,
.wpcf7-form.submitting input[type="submit"] {
	opacity: 0.55;
	cursor: progress;
}

.wpcf7-spinner {
	margin-inline-start: var(--wp--preset--spacing--30);
	background-color: var(--wp--preset--color--border-field);
}

/* Validation et messages de retour */

.wpcf7-form .wpcf7-not-valid {
	border-color: var(--wp--preset--color--accent-secondary);
}

.wpcf7-not-valid-tip {
	display: block;
	margin-block-start: var(--wp--preset--spacing--20);
	color: var(--wp--preset--color--accent-secondary);
	font-size: var(--wp--preset--font-size--meta);
	font-weight: 400;
}

.wpcf7-response-output {
	margin: var(--wp--preset--spacing--50) 0 0;
	padding: 14px 16px;
	border: 1px solid var(--wp--preset--color--border-rule);
	border-inline-start-width: 3px;
	background-color: var(--wp--preset--color--bg-alt);
	color: var(--wp--preset--color--text-body);
	font-size: var(--wp--preset--font-size--small);
	line-height: 1.5;
}

.wpcf7-form.sent .wpcf7-response-output {
	border-color: var(--wp--preset--color--border-rule);
	border-inline-start-color: var(--wp--preset--color--accent-primary);
}

.wpcf7-form.invalid .wpcf7-response-output,
.wpcf7-form.unaccepted .wpcf7-response-output,
.wpcf7-form.payment-required .wpcf7-response-output,
.wpcf7-form.failed .wpcf7-response-output,
.wpcf7-form.aborted .wpcf7-response-output,
.wpcf7-form.spam .wpcf7-response-output {
	border-inline-start-color: var(--wp--preset--color--accent-secondary);
}

/* Formulaire posé sur un fond sombre */

.has-bg-dark-background-color .wpcf7-form label {
	color: var(--wp--preset--color--text-title-dark);
}

.has-bg-dark-background-color .wpcf7-checkbox label,
.has-bg-dark-background-color .wpcf7-acceptance label {
	color: var(--wp--preset--color--text-list-dark);
}

.has-bg-dark-background-color .wpcf7-form input[type="text"],
.has-bg-dark-background-color .wpcf7-form input[type="email"],
.has-bg-dark-background-color .wpcf7-form input[type="tel"],
.has-bg-dark-background-color .wpcf7-form textarea,
.has-bg-dark-background-color .wpcf7-form select {
	border-color: color-mix(in srgb, var(--wp--preset--color--text-title-dark) 24%, transparent);
	background-color: color-mix(in srgb, var(--wp--preset--color--text-title-dark) 6%, transparent);
	color: var(--wp--preset--color--text-title-dark);
}

.has-bg-dark-background-color .wpcf7-form input:focus,
.has-bg-dark-background-color .wpcf7-form textarea:focus,
.has-bg-dark-background-color .wpcf7-form select:focus {
	border-color: var(--wp--preset--color--accent-primary-light);
	background-color: color-mix(in srgb, var(--wp--preset--color--text-title-dark) 10%, transparent);
}

.has-bg-dark-background-color .wpcf7-response-output {
	border-color: color-mix(in srgb, var(--wp--preset--color--text-title-dark) 14%, transparent);
	background-color: color-mix(in srgb, var(--wp--preset--color--text-title-dark) 6%, transparent);
	color: var(--wp--preset--color--text-list-dark);
}

/* ------------------------------------------------------------------
   Carte d'accès — le conteneur Leaflet
   ------------------------------------------------------------------

   Le plan d'accès d'une page contact. Le comportement vit dans
   `assets/js/map.js`, qui dit aussi pourquoi c'est Leaflet et pas un embed
   Google. Ne reste ici que ce que le JavaScript n'a pas à connaître.

   POURQUOI UN CONTEXTE D'EMPILEMENT. Leaflet range ses calques de 200 à 800 —
   les contrôles de zoom sont à 800. L'en-tête du socle est à 100, le retour en
   haut à 90 : sans contexte, les deux boutons + / − de la carte passeraient
   par-dessus l'en-tête collé au défilement. `z-index: 0` sur un élément
   positionné enferme toute l'échelle de Leaflet dans la boîte, et aucune de
   ses valeurs n'a besoin d'être reprise une à une.

   LA HAUTEUR EST UN JETON. Une carte n'a pas de hauteur intrinsèque — sans
   valeur explicite, le conteneur est plat et Leaflet ne s'y initialise pas
   visiblement. Un thème enfant peut légitimement vouloir la sienne, avec un
   effet prévisible : c'est le test du §4, elle vit donc dans theme.json.

   LE FOND SE VOIT AVANT LES TUILES et si le serveur de tuiles ne répond pas.
   Un aplat de charte vaut mieux qu'un rectangle blanc, qui se lirait comme un
   trou dans la page.

   L'ATTRIBUTION NE SE MASQUE PAS. La licence des données OpenStreetMap exige
   qu'elle reste visible ; on l'aligne sur la typographie du site, on ne la
   cache pas. C'est aussi la contrepartie d'un service rendu gratuitement.

   ANGLES VIFS PAR DÉFAUT, comme tout le contenu du socle — mais la carte est
   une surface flottante, et le rayon de charte l'attrape désormais avec le
   `overflow: hidden` qui va avec (voir « Rayon des surfaces flottantes »). Un
   thème enfant n'a donc plus rien à écrire : il règle
   `settings.custom.radius.floating` et la carte suit. */

.mf-map {
	position: relative;
	z-index: 0;
	inline-size: 100%;
	block-size: var(--wp--custom--map--height);
	background: var(--wp--preset--color--bg-alt);
}

.mf-map .leaflet-container {
	font-family: inherit;
}

.mf-map .leaflet-control-attribution {
	font-size: var(--wp--preset--font-size--meta);
}

/* Le fond sobre — modificateur, pas règle par défaut.

   Un fond de carte complet dessine des dizaines de couleurs qui n'apprennent
   rien sur un plan d'accès : le sujet y est l'adresse, pas la géographie. Le
   filtre les ramène à une trame neutre, et le marqueur — qui vit dans un autre
   calque — reste le seul point vif de l'image.

   POURQUOI UN FILTRE ET PAS UN AUTRE FOURNISSEUR. Les fonds « clairs » tout
   faits existent, mais chacun vient avec son opérateur, ses conditions d'usage
   et une attribution de plus à porter. Un filtre CSS ne change ni le tiers ni
   la licence : il ne change que le rendu, et se retire en enlevant une classe.

   La valeur est un jeton : « sobre » est une intention, son dosage est une
   décision de charte qu'un thème enfant peut vouloir reprendre. */
.mf-map--sobre .leaflet-tile-pane {
	filter: var(--wp--custom--map--filter);
}

/* L'étiquette du marqueur — le nom du lieu, lisible sans clic.

   Leaflet dessine ses infobulles en boîte blanche à ombre portée et flèche.
   On garde la flèche, qui rattache l'étiquette au point, et on aligne le reste
   sur la typographie du site plutôt que sur celle de la bibliothèque. */
.mf-map .mf-map__label {
	padding: 4px 10px;
	border: 0;
	font-size: var(--wp--preset--font-size--meta);
	font-weight: 600;
	color: var(--wp--preset--color--text-primary);
	box-shadow: 0 1px 4px rgb(0 0 0 / 0.18);
}

/* ------------------------------------------------------------------
   Défilement vers une ancre
   ------------------------------------------------------------------

   Un lien vers une ancre de la page — sommaire d'article, « aller à la
   section » — fait défiler en douceur au lieu de sauter : le lecteur voit
   qu'il reste sur la même page et dans quel sens il se déplace. Défaut du
   socle, actif sur tous les sites ; un enfant qui n'en veut pas écrit
   `html { scroll-behavior: auto; }` dans sa feuille, chargée après celle-ci.

   La propriété ne vaut que pour les défilements dont l'allure n'est pas
   précisée : clic sur une ancre, `scrollIntoView()` ou `scrollTo()` sans
   `behavior`. Un script qui doit sauter — une ancre rejouée au chargement,
   une position restaurée — passe `behavior: "instant"`. Sous mouvement
   réduit, le garde-fou ci-dessous la ramène à `auto` : ici il porte, puisque
   c'est précisément la propriété qu'il neutralise. */

html {
	scroll-behavior: smooth;
}

/* ------------------------------------------------------------------
   Accessibilité
   ------------------------------------------------------------------ */

:where(a, button, input, select, textarea, summary):focus-visible {
	outline: 2px solid var(--wp--preset--color--accent-primary);
	outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}

/* ------------------------------------------------------------------
   Responsive — 768 et 375, conformément à la charte
   ------------------------------------------------------------------ */

@media (max-width: 781px) {
	/* La colonne de filtres passe au-dessus de la grille, repliée. */
	.mf-filters {
		order: -1;
	}

	/* Le raccourci téléphone ne doit jamais être enfoui dans le menu. */
	.mf-header__phone {
		display: none;
	}

	/* Les deux colonnes du formulaire s'empilent sous 781 px. */
	.wpcf7-form .cols {
		grid-template-columns: 1fr;
		gap: 0;
	}
}

/* ------------------------------------------------------------------
   Carrousel natif — opt-in core/group, centrage JS après les gestes.
   La largeur suit le conteneur ; pas de débordement de la page.
   ------------------------------------------------------------------ */
.is-style-mf-carousel {
	container-type: inline-size;
}

.is-style-mf-carousel:not(.is-ready),
.is-style-mf-carousel > .mf-carousel__track {
	display: grid;
	grid-auto-columns: min(40cqw, 560px);
	grid-auto-flow: column;
	gap: 36px;
	overflow-x: auto;
	padding: 18px max(20px, calc((100cqw - min(40cqw, 560px)) / 2)) 56px;
	scrollbar-width: none;
	/* Le JS centre après l'inertie ; aucun snap CSS concurrent. */
}

.is-style-mf-carousel:not(.is-ready)::-webkit-scrollbar,
.mf-carousel__track::-webkit-scrollbar {
	display: none;
}

.is-style-mf-carousel:not(.is-ready) > .wp-block-group,
.mf-carousel__track > .wp-block-group {
	margin-block: 0;
	transition: opacity 220ms ease;
}

.is-style-mf-carousel.is-ready > .mf-carousel__track {
	margin-block: 0;
	cursor: grab;
}

.mf-carousel__track > .wp-block-group { opacity: 0.2; }
.mf-carousel__track > .is-active { opacity: 1; }
.mf-carousel__track.is-rebasing > .wp-block-group { transition: none; }
.mf-carousel__track.is-dragging { cursor: grabbing; user-select: none; }
.mf-carousel__track:focus-visible {
	outline: 2px solid var(--wp--preset--color--accent-primary);
	outline-offset: -2px;
}

.mf-carousel__status {
	clip-path: inset(50%);
	height: 1px;
	overflow: hidden;
	position: absolute;
	white-space: nowrap;
	width: 1px;
}

.mf-carousel__controls { display: flex; gap: 22px; justify-content: center; }
.mf-carousel__controls button {
	align-items: center;
	background: transparent;
	border: 0;
	border-radius: var(--wp--custom--radius--action);
	color: var(--wp--preset--color--text-content);
	cursor: pointer;
	display: inline-flex;
	font: inherit;
	font-size: 24px;
	height: 44px;
	justify-content: center;
	padding: 0;
	width: 44px;
}
.mf-carousel__controls button:hover { background: var(--wp--preset--color--bg-alt); }

/* Témoignage : trois blocs natifs. Le paragraphe avec fond est la citation ;
   couleurs, padding et typographie restent des réglages Gutenberg.
   Le rayon est un contrat de charte avec l'enfant, pas quatre styles inline. */
.is-style-mf-testimonial { display: flex; flex-direction: column; }
.is-style-mf-testimonial p.has-background {
	flex: 1;
	border-radius: var(--wp--custom--testimonial--radius);
	box-shadow: 40px 60px 60px -20px rgb(0 0 0 / 18%);
	min-height: 420px;
	position: relative;
}
.is-style-mf-testimonial p.has-background::before {
	color: rgb(255 255 255 / 22%);
	content: "“";
	font-size: 90px;
	font-weight: 600;
	left: 18px;
	line-height: 1;
	position: absolute;
	top: 24px;
}
.is-style-mf-testimonial p.has-background::after {
	background: inherit;
	clip-path: polygon(0 0, 100% 0, 50% 100%);
	bottom: -18px;
	content: "";
	height: 18px;
	left: calc(50% - 18px);
	position: absolute;
	width: 36px;
}

@container (max-width: 900px) {
	.is-style-mf-carousel > .mf-carousel__track {
		grid-auto-columns: min(72cqw, 540px);
		padding-inline: max(20px, calc((100cqw - min(72cqw, 540px)) / 2));
	}
}
@container (max-width: 520px) {
	.is-style-mf-carousel > .mf-carousel__track {
		grid-auto-columns: calc(100cqw - 44px);
		gap: 18px;
		padding-inline: 22px;
	}
}
/* La piste statique (sans JS) et l'éditeur restent lisibles sur mobile. */
@media (max-width: 900px) {
	.is-style-mf-carousel:not(.is-ready) {
		grid-auto-columns: min(72vw, 540px);
		padding-inline: max(20px, calc((100vw - min(72vw, 540px)) / 2));
	}
}
@media (max-width: 781px) {
	.is-style-mf-testimonial p.has-background { min-height: 0; }
}
@media (max-width: 520px) {
	.is-style-mf-carousel:not(.is-ready) {
		grid-auto-columns: calc(100vw - 44px);
		gap: 18px;
		padding-inline: 22px;
	}
}

/* Pas de clones ni de JS dans Gutenberg : tous les témoignages sont visibles.
   Les wrappers de l'éditeur exigent leur propre cible de mise en page. */
.editor-styles-wrapper .is-style-mf-carousel:not(.is-ready) {
	display: block;
	overflow: visible;
	padding: 0;
}
.editor-styles-wrapper .is-style-mf-carousel .is-style-mf-testimonial { margin-block: 0 32px; }
.editor-styles-wrapper .is-style-mf-testimonial > .block-editor-inner-blocks > .block-editor-block-list__layout {
	display: flex;
	flex-direction: column;
}

/* ------------------------------------------------------------------
   Fiche d'équipe — carte cliquable, biographie en plein écran
   ------------------------------------------------------------------

   Opt-in : style « Membre d'équipe » (`is-style-mf-team-member`) sur un
   Groupe qui contient un portrait (bloc Image), des textes (fonction, nom)
   et la biographie dans un bloc Details. Tout reste natif et éditable.

   Sans JavaScript, le Details s'ouvre sur place : rien n'est caché. Avec,
   `assets/js/team.js` pose `.is-ready`, rend le Details inerte (son résumé
   reste la flèche de la carte) et ajoute un bouton qui couvre la carte. Le
   survol et le zoom du portrait n'existent qu'à partir de là : ils
   annoncent un clic qui fonctionne.

   La biographie s'ouvre dans un <dialog> unique, rempli à chaque ouverture :
   texte à gauche, portrait à droite. Recette reprise de la fiche équipe
   « plein écran » de Salient : entrée par la droite, textes qui montent en
   cascade, rideau qui découvre le portrait, puis lent zoom arrière (jetons
   `settings.custom.team`). Sur un pointeur fin, un cercle « fermer » suit
   la souris ; le bouton Fermer n'apparaît alors qu'au clavier. */

.is-style-mf-team-member {
	position: relative;
}

/* Le résumé est l'appel à l'action de la carte, pas un triangle de
   disclosure : il garde son texte, sans marqueur. */
.is-style-mf-team-member > details > summary {
	cursor: pointer;
	list-style: none;
}

.is-style-mf-team-member > details > summary::-webkit-details-marker {
	display: none;
}

.mf-team-member__open {
	appearance: none;
	background: transparent;
	border: 0;
	cursor: pointer;
	inset: 0;
	margin: 0;
	padding: 0;
	position: absolute;
	z-index: 1;
}

.mf-team-member__open:focus-visible {
	outline-offset: 6px;
}

/* Le portrait est découpé par son bloc : c'est lui qui porte le rayon, sinon
   l'image agrandie déborderait en angles vifs (voir « Rayon des surfaces »). */
.is-style-mf-team-member.is-ready > .wp-block-image {
	border-radius: var(--wp--custom--radius--floating);
	overflow: hidden;
}

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
	.is-style-mf-team-member.is-ready > .wp-block-image,
	.is-style-mf-team-member.is-ready > .wp-block-image img {
		transition: scale 0.8s cubic-bezier(0.05, 0.2, 0.1, 1);
	}

	.is-style-mf-team-member.is-ready > details > summary {
		transition: translate 0.4s cubic-bezier(0.05, 0.2, 0.1, 1);
	}

	/* Le cadre recule pendant que l'image avance : la photo semble aspirée. */
	.is-style-mf-team-member.is-ready:is(:hover, :focus-within) > .wp-block-image {
		scale: 0.95;
	}

	.is-style-mf-team-member.is-ready:is(:hover, :focus-within) > .wp-block-image img {
		scale: 1.15;
	}

	.is-style-mf-team-member.is-ready:is(:hover, :focus-within) > details > summary {
		translate: 6px 0;
	}
}

/* La page derrière ne défile pas ; la gouttière évite un saut de mise en page
   là où la barre de défilement occupe de la place. */
html.mf-team-modal-open {
	overflow: hidden;
	scrollbar-gutter: stable;
}

.mf-team-modal {
	--mf-team-ease: cubic-bezier(0.2, 1, 0.2, 1);
	background: var(--wp--preset--color--bg-alt);
	border: 0;
	box-sizing: border-box;
	color: inherit;
	height: 100%;
	inset: 0;
	margin: 0;
	max-height: none;
	max-width: none;
	overflow: hidden;
	padding: 0;
	translate: 100% 0;
	transition: translate 0.8s var(--mf-team-ease);
	width: 100%;
}

/* `display` seulement à l'ouverture : la règle du navigateur qui masque un
   dialogue fermé doit continuer de s'appliquer. */
.mf-team-modal[open] {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
}

.mf-team-modal.has-media[open] {
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

.mf-team-modal::backdrop {
	background: transparent;
}

.mf-team-modal.is-open {
	translate: 0 0;
}

.mf-team-modal.is-closing {
	transition: translate 0.6s cubic-bezier(0.7, 0, 0.84, 0);
}

/* La colonne défile sans barre visible, comme la fiche Salient : molette,
   clavier et toucher restent actifs, et le texte coupé au bas de l'écran
   annonce la suite. */
.mf-team-modal__text {
	display: flex;
	flex-direction: column;
	min-height: 0;
	overflow-y: auto;
	overscroll-behavior: contain;
	padding: clamp(32px, 9vh, 112px) clamp(20px, 8vw, 128px);
	position: relative;
	scrollbar-width: none;
}

.mf-team-modal__text::-webkit-scrollbar {
	display: none;
}

/* Marges automatiques : centrée quand elle tient, en haut quand elle défile. */
.mf-team-modal__body {
	margin-block: auto;
	max-width: 36rem;
	width: 100%;
}

.mf-team-modal__body > *,
.mf-team-modal__bio > * {
	margin-block: 0;
}

.mf-team-modal__body > * + * {
	margin-block-start: var(--wp--preset--spacing--30);
}

.mf-team-modal__body > .mf-team-modal__bio {
	margin-block-start: var(--wp--preset--spacing--50);
}

.mf-team-modal__bio > * + * {
	margin-block-start: 1em;
}

/* Icônes sociales de la biographie (LinkedIn…) avec libellé visible : le cœur
   le réduit à 0.65em de l'icône, illisible à côté d'une petite icône. Il suit
   ici la taille de l'icône, celle du texte avec « Petite ». La spécificité
   reprend la règle du cœur (0,3,1) et la dépasse. */
:is(.is-style-mf-team-member, .mf-team-modal__bio) .wp-block-social-links.has-visible-labels .wp-social-link span:not(.screen-reader-text) {
	font-size: 1em;
}

/* Entrée en cascade : fonction, nom, puis biographie. Pendant la fermeture
   les textes restent en place et partent avec le panneau. */
.mf-team-modal__body > * {
	opacity: 0;
	transition: opacity 1.1s var(--mf-team-ease), translate 1.1s var(--mf-team-ease);
	transition-delay: calc(0.4s + min(var(--mf-team-index, 0), 3) * 0.15s);
	translate: 0 40px;
}

.mf-team-modal:is(.is-open, .is-closing) .mf-team-modal__body > * {
	opacity: 1;
	translate: 0 0;
}

.mf-team-modal__media {
	background: var(--wp--preset--color--bg-placeholder);
	position: relative;
}

/* Le cadre découpe la photo agrandie ; le rideau reste en dehors (voir
   plus bas). */
.mf-team-modal__frame {
	inset: 0;
	overflow: hidden;
	position: absolute;
}

.mf-team-modal__photo {
	display: block;
	height: 100%;
	object-fit: cover;
	object-position: 50% 25%;
	opacity: 0;
	scale: var(--wp--custom--team--zoom, 1.11);
	transition:
		opacity 0.3s ease,
		scale var(--wp--custom--team--zoom-duration, 15s) cubic-bezier(0.15, 0, 0.7, 1);
	width: 100%;
}

.mf-team-modal__photo.is-loaded {
	opacity: 1;
}

.mf-team-modal.is-open .mf-team-modal__photo {
	scale: 1;
}

/* Le rideau a la couleur du panneau : il se retire vers le texte et
   découvre le portrait de droite à gauche.

   IL DÉBORDE DE 4 PX SUR LE TEXTE, HORS DU CADRE DE LA PHOTO. Le bord entre
   texte et portrait tombe souvent entre deux pixels (demi-largeur impaire,
   panneau en mouvement). Découpés au même bord, rideau et photo y étaient
   tous deux adoucis à moitié : la photo sombre transparaissait en un trait
   vertical qui clignotait pendant l'entrée. Ici, seul le bord gauche du
   rideau est adouci, sur un fond de sa propre couleur. */
.mf-team-modal__curtain {
	background: var(--wp--preset--color--bg-alt);
	inset: 0 0 0 -4px;
	pointer-events: none;
	position: absolute;
	transform-origin: left center;
	transition: scale 1s cubic-bezier(0.25, 1, 0.2, 1);
	z-index: 1;
}

.mf-team-modal.is-open .mf-team-modal__curtain {
	scale: 0 1;
}

.mf-team-modal__close {
	align-items: center;
	align-self: flex-start;
	appearance: none;
	background: var(--wp--preset--color--bg-alt);
	border: 1.5px solid currentColor;
	border-radius: 50%;
	color: inherit;
	cursor: pointer;
	display: inline-flex;
	flex: none;
	height: 44px;
	justify-content: center;
	margin: 0 0 var(--wp--preset--spacing--40);
	padding: 0;
	position: sticky;
	top: 0;
	width: 44px;
	z-index: 2;
}

.mf-team-modal__cursor {
	display: none;
}

@media (hover: hover) and (pointer: fine) {
	/* Le cercle remplace le bouton pour la souris ; le bouton reste le
	   premier focus et réapparaît dès qu'on navigue au clavier. Placé hors
	   du flux, il n'entraîne aucun décalage en apparaissant. */
	.mf-team-modal__close {
		inset-block-start: 24px;
		inset-inline-start: 24px;
		margin: 0;
		position: absolute;
	}

	.mf-team-modal__close:not(:focus-visible) {
		opacity: 0;
		pointer-events: none;
	}

	/* Blanc en mode « différence » : sombre sur le panneau clair, inversé sur
	   la photo. C'est le mode de fusion qui donne la couleur, d'où le blanc
	   en dur plutôt qu'un rôle de la palette. */
	.mf-team-modal__cursor {
		align-items: center;
		border: 1.5px solid #fff;
		border-radius: 50%;
		box-sizing: border-box;
		color: #fff;
		display: flex;
		height: 84px;
		justify-content: center;
		left: -42px;
		mix-blend-mode: difference;
		opacity: 0;
		pointer-events: none;
		position: absolute;
		top: -42px;
		transition: opacity 0.3s ease;
		width: 84px;
		z-index: 3;
	}

	.mf-team-modal__cursor.is-visible {
		opacity: 1;
	}
}

@media (max-width: 999.98px) {
	.mf-team-modal.has-media[open] {
		grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
	}
}

/* Sous l'empilement des colonnes, le texte prend tout l'écran : le portrait
   vient d'être vu sur la carte. */
@media (max-width: 781px) {
	.mf-team-modal.has-media[open] {
		grid-template-columns: minmax(0, 1fr);
	}

	.mf-team-modal__media {
		display: none;
	}
}
