/* ==========================================================================
   LA FAME HOUSE, direction « DÉPLACEMENT »
   Un déplacement est à la fois le fait d'aller chez quelqu'un et le décalage
   d'un élément dans une composition. Le site applique le second pour dire le
   premier. Rien n'est centré, rien ne revient à son point de départ.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. POLICES, AUTO-HÉBERGÉES
   Sous-ensemble latin de Google Fonts, servi depuis le site. Le brief autorisait
   Google Fonts, il ne l'imposait pas, et l'héberger ici supprime la seule
   connexion à un tiers du site : plus aucune adresse IP de visiteur ne part chez
   Google. Ce sont des polices VARIABLES : l'axe de taille optique de Bodoni est
   conservé, et c'est lui qui fait que le symbole euro garde ses deux barres.
   Le sous-ensemble latin couvre tout le français, œ, ç, guillemets, euro et
   espace insécable compris.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: 'Bodoni Moda';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url('fonts/bodoni-moda-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url('fonts/archivo-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* --------------------------------------------------------------------------
   1. TOKENS
   Ratios mesurés au calcul WCAG, pas estimés. Plancher imposé : 3:1 sur tout
   élément porteur du concept, 4.5:1 sur tout texte.
   -------------------------------------------------------------------------- */
:root {
  /* QUATRE NOIRS. Un seul noir donne une page en carton : les plans ne se
     détachent pas. Amplitude totale 1,33:1 entre le plus profond et le plus
     clair, assez pour que l'oeil sépare les surfaces, assez peu pour que
     l'ensemble reste un noir. */
  --noir-0: #06070A;   /* fond de page, le plus profond                      */
  --noir-1: #0F1117;   /* surface principale des sections                    */
  --noir-2: #181B23;   /* plan surélevé, bandeaux                            */
  --noir-3: #22262F;   /* cartes et blocs de formulaire                      */

  --blanc:      #F4F4F2;   /* 13,76:1 sur le plus clair des quatre noirs     */
  --gris:       #8A8F99;   /*  4,67:1 sur le plus clair, texte second ET trait */
  --or:         #C9A961;   /*  8,38:1 sur --noir-1. Accent. Noir et or demandé
                              par le client : un or profond et mat, pas un jaune. */
  --or-halo:    #745A26;   /* UNIQUEMENT pour la lueur, jamais un contenu.
                              Luminance 0,11 contre 0,09 pour l'ancien halo bleu :
                              la lueur garde la même discrétion. */

  /* L'or tient sur les quatre noirs : 8,95:1 sur --noir-0, 8,38:1 sur
     --noir-1, 7,65:1 sur --noir-2, 6,73:1 sur --noir-3 au pire, toujours
     au-dessus des 4,5:1 du texte. L'ancienne règle des deux jalons par page
     venait du concept abandonné : le client veut plus d'or. Il reste un
     accent, jamais la couleur du texte courant, des titres ni des fonds :
     prix, sur-titres, filets, puces et survols, rien d'autre. */

  /* TRANSPARENCES DE BLANC. C'est ce qui fabrique la profondeur sans image.
     Décoratives : filets de séparation, fonds de carte, bordures. Tout trait
     qui PORTE une information reste en --gris, qui tient 4,67:1 au pire
     (sur --noir-3), 6,21:1 au mieux (sur --noir-0). */
  --b03: rgba(244, 244, 242, 0.03);
  --b06: rgba(244, 244, 242, 0.06);
  --b12: rgba(244, 244, 242, 0.12);
  --b25: rgba(244, 244, 242, 0.25);

  /* TRANSPARENCES D'OR. Les filets décoratifs passent du blanc transparent à
     l'or transparent, demande client : même discrétion mesurée (--o25 rendu
     #3E372A sur --noir-1, 1,6:1, contre 1,35:1 pour l'ancien --b12 ; --o12
     rendu #1D1A14 sur --noir-0, 1,16:1, contre 1,1:1 pour --b06), mais la
     température bascule côté or. Décoratif uniquement : tout trait qui PORTE
     une information reste en --gris. */
  --o12: rgba(201, 169, 97, 0.12);
  --o25: rgba(201, 169, 97, 0.25);

  /* Alias hérités, pour que les composants déjà écrits continuent de marcher */
  --noir-profond: var(--noir-1);
  --noir-veille: var(--noir-3);

  --marge: clamp(20px, 5vw, 96px);
  --colonne: 62ch;
  /* --pas et --recul n'étaient définis QUE dans la requête média téléphone.
     Hors de celle-ci ils n'existaient pas, le calc() qui les utilise devenait
     invalide et le padding retombait à 0 : sur desktop tout le texte était
     collé au bord gauche et le trait partait de left: 0. Une variable utilisée
     dans le socle doit être déclarée dans le socle. */
  --pas: 26px;
  --recul: 22px;

  /* Échelle typographique */
  --t-hero:  clamp(3rem, 11.5vw, 10.5rem);
  --t-h2:    clamp(1.75rem, 5vw, 3.25rem);
  --t-h3:    clamp(1.125rem, 2.2vw, 1.5rem);
  --t-lead:  clamp(1.0625rem, 1.6vw, 1.3125rem);
  --t-body:  1.0625rem;
  --t-micro: 0.6875rem;

  --air-section: clamp(5rem, 12vw, 11rem);
  --air-bloc: clamp(2rem, 4vw, 3.5rem);

  /* Le luxe est lent, ample et retenu. Une seule courbe pour tout le site. */
  --courbe: cubic-bezier(.16, 1, .3, 1);
  --duree: 900ms;
}

/* --------------------------------------------------------------------------
   2. RESET
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
/* PAS de scroll-behavior: smooth. Le site n'a qu'une seule ancre, le lien
   d'évitement, et un saut instantané y est préférable pour la navigation au
   clavier. En prime, smooth s'applique aussi aux défilements programmatiques
   et les fait s'annuler entre eux. */
html { -webkit-text-size-adjust: 100%; }
body, h1, h2, h3, h4, p, ul, ol, li, figure, blockquote, dl, dd { margin: 0; padding: 0; }
ul, ol { list-style: none; }
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }

body {
  background: var(--noir-0);
  color: var(--blanc);
  font-family: 'Archivo', 'Helvetica Neue', Arial, sans-serif;
  font-size: var(--t-body);
  line-height: 1.62;
  font-weight: 400;
  /* AUCUN overflow-x ici, volontairement.
     Il y avait un overflow-x: hidden. Déclarer overflow sur un seul axe force
     l'autre à « auto » : le corps devenait un conteneur de défilement et cassait
     tout position:sticky en dessous, en silence. Mesuré, la scène des six lieux
     se décollait jusqu'à top = -1809 px au lieu de rester à 0.
     Le réflexe serait de passer à overflow-x: clip, qui coupe sans créer de
     conteneur. Mais clip n'existe que depuis Safari 16, et surtout la mesure dit
     que rien ne déborde : 0 cas sur 132 combinaisons de 12 pages par 11 largeurs
     de 320 à 1920 px, sans aucune règle d'overflow. Les deux seuls éléments plus
     larges que l'écran, la piste des lieux et le bandeau défilant, sont déjà
     rognés par leur propre conteneur.
     Ne pas remettre de overflow-x ici sans avoir d'abord constaté un vrai
     débordement, et sans avoir vérifié la scène des lieux juste après. */
  /* PAS de -webkit-font-smoothing: antialiased. Vérifié au navigateur : il
     amincit les glyphes, et sur du clair sur fond sombre il efface les déliés
     de Bodoni Moda. Le symbole euro y perdait ses deux barres et se lisait
     « C », le 4 y perdait sa diagonale. Voir captures/_smooth-test.png. */
}

/* Le grain. Il donne au noir un comportement de matière et non de fond vide.
   Tuile 200x200 en feTurbulence, répétée. C'est le détail qui sépare le sobre
   du pauvre, et il ne coûte aucune requête. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  /* Monté de 0,05 à 0,10 : à 0,05 le grain existait dans le code et pas à l'écran. */
  opacity: 0.11;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* --------------------------------------------------------------------------
   3. TYPOGRAPHIE
   Bodoni Moda ne compose QUE les grands titres et les prix, à l'échelle où son
   contraste didone existe. En dessous de 24 px ses déliés disparaissent, donc
   il n'y descend jamais. Archivo fait tout le reste.
   -------------------------------------------------------------------------- */
.display {
  font-family: 'Bodoni Moda', 'Bodoni 72', Didot, Georgia, serif;
  font-weight: 400;
  /* L'axe de taille optique suit la taille RÉELLEMENT rendue, clamp() compris.
     Forcer 'opsz' 96 à la main le désactivait, et les déliés du Bodoni passaient
     sous le pixel dès qu'un titre descendait sous 40 px : le symbole euro perdait
     ses barres et se lisait « C ». C'est le même piège que sur le favicon. */
  font-optical-sizing: auto;
  letter-spacing: -0.015em;
  /* INTERLIGNE MINIMAL 1,12, ET JAMAIS SOUS 1 SUR DU TITRE.
     Un glyphe accentué de Bodoni occupe la hauteur de police ENTIÈRE : mesuré,
     « ÊÀÉÙ » à 40 px mesure 40 px de haut. Un interligne de 40 px ne laisse donc
     aucune place et l'accent de la deuxième ligne mord la première. C'était
     visible sur « DITES-MOI / OÙ VOUS ÊTES. » et « DEUX MÈTRES / CARRÉS
     SUFFISENT. ». tests/interlignes.mjs échoue si un titre repasse sous 1. */
  line-height: 1.12;
}

.micro {
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gris);
}

.lead { font-size: var(--t-lead); line-height: 1.5; }
.gris { color: var(--gris); }
/* Limitée au texte courant : sans le :not, elle fuyait dans la grille du
   formulaire et désalignait de 18,7 px les deux champs de la première ligne. */
p + p:not(.champ):not(.pot) { margin-top: 1.1em; }

/* --------------------------------------------------------------------------
   4. LA MARQUE
   Composant isolé. Le jour où un vrai logo arrive, il remplace ce bloc et
   aucune des 11 pages ne bouge.
   -------------------------------------------------------------------------- */
.marque {
  font-family: 'Bodoni Moda', 'Bodoni 72', Didot, Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-size: 1.0625rem;
  letter-spacing: 0.34em;   /* travaillé au millième : c'est l'espacement qui fait la marque */
  text-transform: uppercase;
  text-indent: 0.34em;      /* compense le blanc final pour que le bloc reste optiquement calé */
  color: var(--blanc);
  white-space: nowrap;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
/* Le monogramme ne sert que sur téléphone, où le logotype complet ne rentre pas. */
.marque__court { display: none; }

/* --------------------------------------------------------------------------
   5. LE BLOC
   Le déport et ses crans sont supprimés. Ils tenaient sur le papier et pas à
   l'écran : les filets paraissaient irréguliers, le carré bleu mal calé, et
   l'ensemble restait fade. À la place, une seule unité de composition calée à
   gauche, avec beaucoup d'air. Le rythme vient de la respiration et des
   changements de surface, pas d'un décalage.
   -------------------------------------------------------------------------- */
.bloc { position: relative; padding: var(--air-section) var(--marge); }

.bloc__inner { max-width: 1320px; margin: 0 auto; }
.bloc--surface { background: var(--noir-1); }
.bloc--haut { background: var(--noir-2); }
.bloc + .bloc { border-top: 1px solid var(--o12); }

/* ÉCHELLE ADAPTATIVE : une section brève ne porte pas le même titre qu'une
   section dense. Après le resserrage des pages locales, des sections de 25 mots
   se retrouvaient sous un titre de 68 px suivi de 180 px de vide, ce qui se lit
   comme une page inachevée. Une phrase courte sous un titre plus petit se lit
   comme une affirmation nette. Le corps du titre et la respiration de la section
   descendent ensemble : réduire l'un sans l'autre ne règle rien.
   La classe est posée sur les sections dont le CORPS fait 45 mots ou moins,
   hors introduction, bloc tarifs, FAQ et conclusion, qui ont leur propre
   gabarit. Seuil mesuré, pas choisi au jugé. */
.bloc--bref { padding-block: calc(var(--air-section) * 0.6); }
.bloc--bref .titre {
  font-size: clamp(1.5rem, 3.4vw, 2.5rem);
  line-height: 1.16;
  max-width: 26ch;
}
.bloc--bref .titre + p,
.bloc--bref .titre + h3 { margin-top: calc(var(--air-bloc) * 0.62); }
.bloc--bref h3 { margin-top: calc(var(--air-bloc) * 0.5); }

.sur {
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--or);
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-bottom: clamp(1.5rem, 3vw, 2.25rem);
}
.sur::before {
  content: '';
  width: clamp(28px, 4vw, 56px);
  height: 1px;
  background: var(--or);
  flex: none;
}

.titre {
  font-family: 'Bodoni Moda', 'Bodoni 72', Didot, Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-size: clamp(2rem, 5.6vw, 4.25rem);
  line-height: 1.12;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  max-width: 18ch;
}
.titre--large { max-width: none; }
/* Le titre d'une section est suivi soit d'un conteneur qui porte sa propre marge
   (.corps, .protocole, .faits, .tarifs), soit directement d'un paragraphe ou
   d'un sous-titre. Dans ce dernier cas rien ne les séparait, et le texte venait
   se coller sous le titre : vu sur capture après le resserrage des pages
   locales, qui a remplacé des conteneurs par des paragraphes nus. */
.titre + p, .titre + h3 { margin-top: var(--air-bloc); }
.titre .accent { color: var(--or); }

.corps { max-width: 52ch; margin-top: clamp(1.5rem, 3vw, 2.25rem); }
.corps p + p { margin-top: 1.05em; }
.corps--lead { font-size: var(--t-lead); line-height: 1.5; }

/* --------------------------------------------------------------------------
   6. ESCALIER TYPOGRAPHIQUE
   Conservé pour les titres des pages intérieures, où il donne du rythme sans
   rien prétendre. Il ne porte plus de concept, c'est une figure de mise en page.
   -------------------------------------------------------------------------- */
.escalier { display: block; }
.escalier > span { display: block; }
.escalier > span:nth-child(2) { padding-left: 1.6ch; }
.escalier > span:nth-child(3) { padding-left: 3.2ch; }
.escalier > span:nth-child(4) { padding-left: 4.8ch; }

/* --------------------------------------------------------------------------
   7. RIDEAU D'OUVERTURE
   Noir plein cadre, le logotype apparaît, tient, puis le rideau se retire vers
   le haut. Une seule fois par session. Posé par le JS et jamais écrit dans le
   HTML : sans JavaScript, il n'existe pas et le hero est immédiatement visible.
   -------------------------------------------------------------------------- */
.rideau {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: var(--noir-0);
  display: flex;
  align-items: center;
  justify-content: center;
  will-change: transform;
}
.rideau__marque {
  font-family: 'Bodoni Moda', 'Bodoni 72', Didot, Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-size: clamp(1rem, 2.4vw, 1.75rem);
  letter-spacing: 0.42em;
  text-indent: 0.42em;
  text-transform: uppercase;
  color: var(--blanc);
  opacity: 0;
}
.rideau__filet {
  position: absolute;
  left: 50%;
  bottom: 34%;
  width: 0;
  height: 1px;
  background: var(--b25);
  transform: translateX(-50%);
}

/* --------------------------------------------------------------------------
   8. EN-TÊTE RÉACTIF
   Transparent sur le hero, fond opaque et compact au défilement.
   -------------------------------------------------------------------------- */
.tete {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 70;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  padding: clamp(1.1rem, 2.4vw, 1.9rem) var(--marge);
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background 400ms var(--courbe), padding 400ms var(--courbe),
              border-color 400ms var(--courbe), backdrop-filter 400ms;
}
.tete[data-compacte='true'] {
  padding-top: 0.7rem;
  padding-bottom: 0.7rem;
  background: rgba(6, 7, 10, 0.88);
  border-bottom-color: var(--b06);
}
@supports (backdrop-filter: blur(1px)) {
  .tete[data-compacte='true'] { backdrop-filter: blur(14px); }
}
.nav { display: flex; gap: clamp(1rem, 2.4vw, 2.25rem); flex-wrap: wrap; }
.nav a {
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--gris);
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  transition: color 240ms var(--courbe);
}
.nav a:hover, .nav a:focus-visible { color: var(--or); }

/* --------------------------------------------------------------------------
   ENTRÉE « OÙ J'INTERVIENS »
   Les quatre pages de zone n'existaient qu'en pied de page. Un <details> natif
   les remonte dans la navigation principale : il s'ouvre sans JavaScript, se
   pilote au clavier sans qu'on écrive une ligne, et ne demande aucune
   autorisation à la CSP.
   Le panneau est en position absolue : dans le flux, il pousserait un en-tête
   qui est fixe, et tout le contenu sauterait à l'ouverture.
   -------------------------------------------------------------------------- */
.zonesnav { position: relative; }
.zonesnav > summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--gris);
  transition: color 200ms;
}
.zonesnav > summary::-webkit-details-marker { display: none; }
.zonesnav > summary:hover, .zonesnav > summary:focus-visible { color: var(--or); }
/* Chevron dessiné en CSS, qui bascule à l'ouverture : sans lui, rien ne dit que
   cette entrée se déplie plutôt que de mener à une page. */
.zonesnav > summary::after {
  content: '';
  width: 5px; height: 5px;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 240ms var(--courbe);
}
.zonesnav[open] > summary::after { transform: translateY(1px) rotate(-135deg); }
.zonesnav[open] > summary { color: var(--or); }

.zonesnav__panneau {
  position: absolute;
  top: calc(100% + 0.9rem);
  left: 0;
  z-index: 80;
  display: grid;
  gap: 0.15rem;
  min-width: 15rem;
  padding: 0.6rem 0;
  background: var(--noir-2);
  border: 1px solid var(--b12);
}
.zonesnav__panneau a {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0 1.1rem;
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gris);
  white-space: nowrap;
}
.zonesnav__panneau a:hover, .zonesnav__panneau a:focus-visible { color: var(--or); background: var(--b06); }
.zonesnav__panneau a[aria-current='page'] { color: var(--blanc); }

/* Le libellé court n'existe que sur téléphone. Mesuré : à 320 px il ne reste que
   37 px de libre entre le monogramme et la navigation, et « OÙ J'INTERVIENS »
   en demande une centaine. Sans ce raccourci, la navigation passait sur deux
   lignes et l'en-tête fixe grandissait d'autant. Même procédé que le
   monogramme, qui devient « F » sous cette largeur. */
.zonesnav__court { display: none; }
@media (max-width: 719px) {
  .zonesnav__long { display: none; }
  .zonesnav__court { display: inline; }
  /* Le panneau prend toute la largeur utile et s'accroche au BAS DE L'EN-TÊTE.
     Deux erreurs corrigées ici. Ancré sur le résumé, il débordait à gauche comme
     à droite depuis le milieu d'une navigation étroite. Passé en position fixe
     avec top:auto, il se plaçait à 58 px alors que l'en-tête descend à 74 : ses
     seize premiers pixels passaient dessous.
     On rend .zonesnav statique pour que le bloc conteneur devienne .tete, qui
     est fixe : top:100% vaut alors exactement le bas de l'en-tête, et suit tout
     seul son passage en version compacte. */
  .zonesnav { position: static; }
  .zonesnav__panneau {
    position: absolute;
    top: 100%;
    left: var(--marge);
    right: var(--marge);
    min-width: 0;
  }
  .zonesnav__panneau a { white-space: normal; }
}
.nav a[aria-current='page'] { color: var(--blanc); }

/* --------------------------------------------------------------------------
   9. HERO
   Typographique, sans photo : aucun fichier disponible ne supporte le plein
   écran. Le noir est traité comme une matière, pas comme un fond, par trois
   moyens qui ne coûtent aucune requête : le grain, une lueur dorée très diffuse
   et très large, et un dégradé de surface presque imperceptible.
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  padding: clamp(7rem, 14vh, 11rem) var(--marge) clamp(5rem, 10vh, 8rem);
  overflow: hidden;
  isolation: isolate;
}
/* Dégradé de surface : évite l'effet carton sur un aplat de 100 dvh. */
.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -2;
  background: linear-gradient(168deg, var(--noir-1) 0%, var(--noir-0) 46%, #040507 100%);
}
/* LA LUEUR. Large, faible, décalée à gauche pour ne pas être un halo centré.
   Aucun filtre de flou : un dégradé radial est déjà doux, et le flou sur une
   surface de cette taille coûte cher au premier rendu. */
.hero__lueur {
  position: absolute;
  z-index: -1;
  left: -22%;
  top: -14%;
  width: min(125vw, 1500px);
  height: min(125vh, 1200px);
  pointer-events: none;
  background:
    radial-gradient(closest-side,
      rgba(116, 90, 38, 0.30) 0%,
      rgba(116, 90, 38, 0.13) 38%,
      rgba(116, 90, 38, 0.04) 62%,
      transparent 78%);
}
/* LA ROSACE. Concession aux références gothiques du client : sa géométrie,
   pas son ornement. 24 axes, une bande de quadrilobes et de trilobes
   alternés, un trait de 1 px, générés par outils/rosace.py, aucun motif
   organique. Elle doit se lire comme une texture du noir, jamais comme un
   décor : si on la remarque avant de lire le titre, l'opacité est trop
   haute. Deux exemplaires sur le site : ici, et sur l'en-tête de la
   galerie, pour qu'elle devienne un élément de marque. Le nom de fichier
   porte le numéro de version (-24) : /images/* est en cache immuable. */
.hero::after {
  content: '';
  position: absolute;
  z-index: -1;
  inset: 0;
  pointer-events: none;
  background: url('images/rosace-or-24.svg') no-repeat calc(50% + 12vw) 50%;
  background-size: 940px 940px;
  /* 8, 10 et 12 % essayés sur capture 1440 : à 8 la version 24 axes redevient
     une texture timide, à 12 on la remarque avant la fin du titre. 10 % :
     elle est là, le titre passe premier. */
  opacity: 0.10;
}
@media (max-width: 899px) {
  .hero::after { background-position: 50% 44%; }
}
/* Sous 720 px le titre occupe presque toute la largeur et le noir autour
   se réduit. 7 % avaient été mesurés sur capture avec la rosace 12 axes ;
   remesuré à 390 px avec la version 24 axes, plus dense : à 7 % elle est
   une texture lisible autour du titre, qui reste premier. Inchangé. */
@media (max-width: 720px) {
  .hero::after { opacity: 0.07; }
}

.hero__inner { position: relative; width: 100%; max-width: 1320px; }

.hero__sur {
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--or);
  display: flex;
  align-items: center;
  gap: 0.9rem;
}
.hero__sur::before {
  content: '';
  width: clamp(28px, 5vw, 64px);
  height: 1px;
  background: var(--or);
  flex: none;
}

.hero__titre {
  font-family: 'Bodoni Moda', 'Bodoni 72', Didot, Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-size: var(--t-hero);
  /* 1,05 et non 1,12 : le hero n'a pas de capitale accentuée aujourd'hui, mais
     il n'y a aucune raison qu'il n'en ait jamais, et 0,92 le rendait impossible
     à modifier sans casse. 1,05 laisse la place d'un accent tout en gardant la
     masse compacte qui fait l'effet du bloc. */
  line-height: 1.05;
  letter-spacing: -0.025em;
  text-transform: uppercase;
  margin: clamp(1.75rem, 4vw, 3.25rem) 0 clamp(2.25rem, 4.5vw, 3.5rem);
}
.hero__titre .ligne { display: block; }
/* Le masque de révélation. Le padding compense la hauteur des accents et des
   jambages, sinon overflow:hidden les rogne en permanence. */
.hero__titre .mot {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  /* Le masque a besoin de sa propre hauteur de ligne : à 0,92 hérité du titre,
     la boîte est plus courte que les capitales et les rogne en permanence.
     Les marges négatives rendent au titre son interligne serré. */
  line-height: 1.16;
  padding: 0 0.05em 0.06em 0;
  margin-top: -0.12em;
  margin-bottom: -0.12em;
}
.hero__titre .mot > i { display: inline-block; font-style: normal; }
/* La troisième ligne porte l'accent, c'est la seule couleur du hero. */
.hero__titre .ligne:last-child .mot:last-child > i { color: var(--or); }

.hero__contexte {
  font-size: var(--t-lead);
  line-height: 1.5;
  color: var(--gris);
  max-width: 44ch;
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(0.9rem, 2vw, 1.75rem);
  margin-top: clamp(2rem, 4vw, 3rem);
}

/* Indicateur de défilement : un filet qui se remplit en boucle. */
.hero__defiler {
  position: absolute;
  right: var(--marge);
  bottom: clamp(1.75rem, 4vh, 3rem);
  writing-mode: vertical-rl;
  display: flex;
  align-items: center;
  gap: 0.85rem;
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--gris);
}
.hero__defiler i {
  display: block;
  width: 1px;
  height: 64px;
  background: var(--b12);
  position: relative;
  overflow: hidden;
}
.hero__defiler i::after {
  content: '';
  position: absolute;
  inset: 0 auto auto 0;
  width: 1px;
  height: 64px;
  background: var(--blanc);
  transform: translateY(-100%);
}

/* --------------------------------------------------------------------------
   9 bis. ÉTATS DE DÉPART DES ANIMATIONS
   Tout est sous .js-anim, classe posée par pre.js et retirée si GSAP n'est pas
   là ou si rien n'a pris le relais en quatre secondes. Sans JavaScript, aucune
   de ces règles ne s'applique et la page est immédiatement lisible.
   -------------------------------------------------------------------------- */
/* Le CSS ne pose QUE de l'opacité, jamais une transformation.
   Piège vérifié au navigateur : si l'état de départ est écrit ici en
   transform: translateY(108%), GSAP le lit au premier contact, le résout en
   pixels et le range dans sa propriété « y ». Sa propre animation de
   « yPercent » ne remet alors à zéro que la part en pourcentage, la part en
   pixels reste, et les mots ne remontent jamais dans leur masque.
   GSAP est donc seul propriétaire des transformations, le CSS ne fait que
   masquer le temps qu'il pose l'état de départ. */
.js-anim .hero__titre { opacity: 0; }
.js-anim [data-hero-suite] { opacity: 0; }

/* L'indicateur de défilement se remplit en boucle. CSS pur : GSAP n'a rien à
   faire pour une boucle décorative de trois lignes. */
.hero__defiler i::after { animation: defiler 2.6s var(--courbe) infinite; }
@keyframes defiler {
  0%   { transform: translateY(-100%); }
  55%  { transform: translateY(0); }
  100% { transform: translateY(100%); }
}

/* Emplacement média, prêt pour l'après-shooting.
   Passer data-media="on" sur la section hero suffit à l'activer. */
.hero__media { display: none; }
.hero[data-media='on'] .hero__media {
  display: block;
  position: absolute;
  inset: 0;
  z-index: -1;
}
.hero[data-media='on'] .hero__media img,
.hero[data-media='on'] .hero__media video { width: 100%; height: 100%; object-fit: cover; }
.hero[data-media='on'] .hero__media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, rgba(6,7,10,.92) 0%, rgba(6,7,10,.72) 45%, rgba(6,7,10,.45) 100%);
}
.hero[data-media='on'] .hero__lueur { opacity: 0.5; }

/* --------------------------------------------------------------------------
   9. BLOCS DE CONTENU
   -------------------------------------------------------------------------- */
.sec__titre { font-size: var(--t-h2); text-transform: uppercase; margin-bottom: var(--air-bloc); }
.sec__corps { max-width: var(--colonne); }

.duo { display: grid; gap: clamp(2rem, 4vw, 3.5rem); align-items: start; }
@media (min-width: 900px) { .duo { grid-template-columns: minmax(0, 1fr) 320px; } }

figure.piece { margin: 0; }
figure.piece img { width: 100%; max-width: 520px; }

.protocole { margin-top: var(--air-bloc); }
.protocole li { padding: 1.15rem 0; border-top: 1px solid var(--o25); max-width: var(--colonne); }
.protocole li:last-child { border-bottom: 1px solid var(--o25); }
.protocole b { font-weight: 500; }

.zones { display: grid; gap: clamp(2rem, 5vw, 4rem); margin-top: var(--air-bloc); }
@media (min-width: 820px) { .zones { grid-template-columns: 1fr 1fr; } }
.zone h3 { font-size: var(--t-h3); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 0.75rem; }
.zone p { color: var(--gris); }
.zone ul { margin-top: 1.1rem; display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; }
.zone ul a { font-size: var(--t-micro); font-weight: 500; letter-spacing: 0.16em; text-transform: uppercase; border-bottom: 1px solid var(--gris); min-height: 44px; display: inline-flex; align-items: center; }
.zone ul a:hover, .zone ul a:focus-visible { border-color: var(--or); }

/* --------------------------------------------------------------------------
   LISTES DE LIENS EMPILÉES, SOUS 820 px
   Sur écran large, les liens de zone tiennent sur une ligne et le pied de page
   se répartit en trois colonnes : un soulignement à la largeur du texte marche.
   Empilés sur téléphone, ces mêmes soulignements deviennent une échelle de
   traits de longueurs toutes différentes. Les trois liens de zone tombaient en
   2 + 1, avec trois longueurs qui ne s'alignaient sur rien, et le pied de page
   affichait douze traits inégaux les uns sous les autres.
   Chaque lien devient donc une ligne pleine largeur : les filets s'alignent, et
   la liste se lit comme ce qu'elle est, une liste de destinations.
   -------------------------------------------------------------------------- */
@media (max-width: 639px) {
  .zone ul { display: block; margin-top: 1.25rem; }
  .zone ul a,
  .pied ul a.lien {
    display: flex;
    width: 100%;
    align-items: center;
    min-height: 52px;
    border-bottom: 1px solid var(--b12);
    padding-bottom: 0;
  }
  /* Un chevron ferme chaque ligne. Sans lui, le filet ne souligne plus le mot,
     il sépare deux rangs : la liste se lisait comme une suite d'intitulés et
     plus comme des destinations cliquables. Dessiné en CSS, aucune requête,
     même procédé que le chevron du menu déroulant. */
  .zone ul a::after,
  .pied ul a.lien::after {
    content: '';
    flex: none;
    margin-left: auto;
    width: 6px;
    height: 6px;
    border-top: 1px solid var(--gris);
    border-right: 1px solid var(--gris);
    transform: rotate(45deg);
    transition: border-color 200ms;
  }
  .zone ul a:hover, .zone ul a:focus-visible,
  .pied ul a.lien:hover, .pied ul a.lien:focus-visible { border-bottom-color: var(--or); }
  .zone ul a:hover::after, .zone ul a:focus-visible::after,
  .pied ul a.lien:hover::after, .pied ul a.lien:focus-visible::after {
    border-color: var(--or);
  }
  /* Le dernier de chaque liste ne porte pas de filet : il fermerait le bloc sur
     un trait sans rien en dessous, ce qui se lit comme un séparateur orphelin. */
  .zone ul li:last-child a,
  .pied ul li:last-child a.lien { border-bottom: 0; }
}

/* Liens de texte : soulignement à 1 px en gris, qui passe à l'or à l'appui. */
.lien {
  border-bottom: 1px solid var(--gris);
  padding-bottom: 1px;
  transition: border-color 200ms, color 200ms;
}
.lien:hover, .lien:focus-visible { border-color: var(--or); }

/* --------------------------------------------------------------------------
   10. ARRIVÉE
   La section reste sur --noir-profond ; l'or y tient 8,38:1. C'est le bloc
   du formulaire, à l'intérieur, qui porte --noir-veille et fabrique la
   profondeur.
   -------------------------------------------------------------------------- */
.arrivee .sec__titre { margin-bottom: 1.25rem; }

/* --------------------------------------------------------------------------
   11. BOUTONS ET FORMULAIRE
   -------------------------------------------------------------------------- */
.bouton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 52px;
  padding: 0.9rem 1.9rem;
  background: var(--blanc);
  color: var(--noir-profond);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  border: 1px solid var(--blanc);
  cursor: pointer;
  transition: background 200ms, color 200ms, border-color 200ms;
}
.bouton:hover, .bouton:focus-visible { background: transparent; color: var(--or); border-color: var(--or); }
.bouton--nu {
  background: transparent;
  color: var(--blanc);
  border-color: var(--b25);
  padding-left: 0;
  padding-right: 0;
  border-width: 0 0 1px 0;
  letter-spacing: 0.2em;
}
.bouton--nu:hover, .bouton--nu:focus-visible { background: transparent; color: var(--or); border-bottom-color: var(--or); }
.bouton--creux { background: transparent; color: var(--blanc); border-color: var(--gris); }
.bouton--creux:hover, .bouton--creux:focus-visible { background: transparent; color: var(--or); border-color: var(--or); }

/* Le placeholder est explicitement en --gris : la valeur par défaut du
   navigateur est un gris non maîtrisé qui échoue au contraste sur fond sombre.
   Ici 4,67:1 sur --noir-veille. */
.champ ::placeholder { color: var(--gris); opacity: 1; }
/* Chevron dessiné en CSS pur, aucune image, aucune requête : appearance:none
   avait retiré au select obligatoire tout repère de liste déroulante. */
.champ select {
  background-image: linear-gradient(45deg, transparent 50%, var(--gris) 50%),
                    linear-gradient(135deg, var(--gris) 50%, transparent 50%);
  background-position: right 13px center, right 7px center;
  background-size: 7px 7px, 7px 7px;
  background-repeat: no-repeat;
  padding-right: 34px;
}
.champ option { background: var(--noir-veille); color: var(--blanc); }
/* On NE remet PAS outline:none ici. La règle :focus l'emportait sur
   :focus-visible par spécificité et annulait l'anneau sur les 16 champs des
   deux formulaires, ne laissant qu'un changement de bordure à 2,89:1, sous le
   plancher de 3:1 que cette feuille s'impose. Mesuré au navigateur. */
.champ input:focus-visible, .champ select:focus-visible, .champ textarea:focus-visible {
  border-bottom-color: var(--blanc);
}
.champ input:focus, .champ select:focus, .champ textarea:focus { border-bottom-color: var(--blanc); }
.pot { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.message { margin-top: 1.5rem; font-size: var(--t-lead); }
.champ input[aria-invalid='true'], .champ select[aria-invalid='true'] { border-bottom-color: var(--blanc); border-bottom-width: 2px; }
/* Le filet d'erreur est en --blanc et non en --or. Deux raisons :
   l'or ne doit exister que sur les deux jalons, et un filet de 2 px est un
   objet fin, où la seule conformité au ratio ne suffit pas à le rendre visible.
   La couleur n'est de toute façon pas le seul indicateur : le texte porte le sens. */
.message[data-etat='erreur'] { color: var(--blanc); border-left: 2px solid var(--blanc); padding-left: 1rem; }
/* PAS de display:none : une région live retirée puis remise dans l'arbre au
   moment exact où elle devrait être annoncée ne l'est pas par tous les lecteurs
   d'écran. Vide, elle ne prend simplement plus de marge. */
.message:empty { margin-top: 0; }

/* Anneau de focus : blanc sur les quatre noirs. 18,29:1, 17,14:1, 15,63:1,
   13,76:1. Aucun élément focusable ne repose sur un fond or. */
:focus-visible { outline: 2px solid var(--blanc); outline-offset: 3px; }

/* --------------------------------------------------------------------------
   12. PIED DE PAGE
   -------------------------------------------------------------------------- */
.pied {
  border-top: 1px solid var(--gris);
  padding: clamp(2.5rem, 5vw, 4rem) var(--marge);
  display: grid;
  gap: 2rem;
  color: var(--gris);
  font-size: 0.9375rem;
}
@media (min-width: 820px) { .pied { grid-template-columns: 1fr 1fr 1fr; } }
.pied a { display: inline-flex; align-items: center; min-height: 44px; }
.pied a:hover, .pied a:focus-visible { color: var(--or); }
.pied h2 { font-size: var(--t-micro); font-weight: 500; letter-spacing: 0.22em; text-transform: uppercase; color: var(--blanc); margin-bottom: 0.9rem; }
.pied li + li { margin-top: 0; }

/* --------------------------------------------------------------------------
   13. ANIMATIONS
   Fade-in et slide-up en CSS pur, déclenchés par IntersectionObserver.
   FILET DE SÉCURITÉ : sans JS, .anim n'existe jamais et tout reste visible.
   Une section invisible est un bug bloquant, pas un effet.
   -------------------------------------------------------------------------- */
.anim { opacity: 0; transform: translateY(18px); }
.anim.vu { opacity: 1; transform: none; transition: opacity var(--duree) var(--courbe), transform var(--duree) var(--courbe); }

/* MOUVEMENT RÉDUIT : on coupe le mouvement, pas les fondus.
   Le critère WCAG 2.3.3 vise le DÉPLACEMENT. Une opacité qui monte ne déplace
   rien et ne déclenche rien chez personne. La version précédente écrasait tout
   avec un « * { animation-duration: 0.01ms } » et un « opacity: 1 !important »
   sur le titre : le site devenait entièrement immobile, ce qui n'est pas un site
   accessible, c'est un site mort. Les états de départ en opacité restent donc
   actifs et c'est GSAP qui les lève, en fondu seul. Voir pre.js et anim.js. */
@media (prefers-reduced-motion: reduce) {
  .anim, .anim.vu { transform: none; transition: opacity var(--duree) var(--courbe); }
  .hero__defiler i::after { animation: none; transform: translateY(0); }
  .rideau { display: none; }
}

/* --------------------------------------------------------------------------
   14. TÉLÉPHONE
   Le pas tombe à 7 px : la colonne n'a pas la place de se décaler de 26 px.
   Le trait continue donc de dériver, visiblement, et la charge du concept
   passe surtout dans la typographie, qui elle a toute la largeur.
   -------------------------------------------------------------------------- */
@media (max-width: 719px) {
  :root { --pas: 7px; --recul: 14px; --marge: 24px; --colonne: 100%; }

  /* En-tête compact. Le logotype complet et la navigation font 373 px pour
     342 px utiles : la nav se repliait sur deux lignes et l'en-tête montait à
     135 px. Sur téléphone, le logotype cède donc la place au monogramme,
     c'est précisément ce à quoi sert un monogramme. */
  .tete { padding-top: 0.9rem; padding-bottom: 0.9rem; }
  .marque__long { display: none; }
  .marque__court { display: block; font-size: 1.5rem; letter-spacing: 0; text-indent: 0; line-height: 1; }
  .marque { letter-spacing: 0; text-indent: 0; }
  /* Quatre entrées au lieu de trois : il faut reprendre de la place, sinon la
     navigation passe sur deux rangs et l'en-tête FIXE double de hauteur, de 67
     à 135 px, sur tous les téléphones. Mesuré. On resserre les gouttières et
     l'interlettrage plutôt que de rapetisser le texte. */
  .tete { gap: 1rem; }
  .nav { gap: 0.7rem; }
  .nav a, .zonesnav > summary { font-size: 0.625rem; letter-spacing: 0.06em; }
  .zonesnav > summary { gap: 0.35rem; }
}

/* Sous 360 px, quatre entrées ne tiennent toujours pas et l'en-tête repassait à
   deux rangs, 129 px de haut. On resserre encore, uniquement là : ce sont les
   écrans de 320 px, pas la cible principale, mais un en-tête fixe qui mange un
   sixième de l'écran n'est acceptable nulle part. */
@media (max-width: 359px) {
  .nav { gap: 0.5rem; }
  .nav a, .zonesnav > summary { letter-spacing: 0.02em; }

  /* L'indicateur de défilement RESTE, dans une forme adaptée.
     Il était masqué au motif que la barre d'action fixe occupe le bas de
     l'écran. C'était faux : cette barre n'apparaît qu'une fois le hero franchi,
     donc jamais pendant qu'on regarde le hero. Résultat, l'accueil se terminait
     sur 212 px de noir sans rien qui invite à descendre, sur l'écran même où le
     site sera le plus vu.
     Forme adaptée : le texte vertical à droite est un usage de grand écran. Ici
     il passe à l'horizontale, ancré en bas à gauche, sur la marge du reste de
     la page, filet à gauche du mot.
     Il reste en position absolue. Première tentative : le repasser dans le
     flux. Le hero est un conteneur flex, l'indice y est donc devenu un élément
     flex à part entière, a pris sa part de largeur et a comprimé le titre de
     trois à cinq lignes. Vérifié sur capture. Il reste donc hors du flux, on ne
     change que son ancrage et son sens de lecture. */
  .hero__defiler {
    display: flex;
    left: var(--marge);
    right: auto;
    bottom: clamp(2rem, 6vh, 3.5rem);
    writing-mode: horizontal-tb;
    flex-direction: row;
    align-items: center;
    gap: 0.85rem;
  }
  .hero__defiler i, .hero__defiler i::after { height: 40px; }

  .hero { padding-top: clamp(5.5rem, 16vh, 8rem); padding-bottom: clamp(6rem, 14vh, 8rem); }
  body { padding-bottom: 64px; }   /* place pour la barre de contact fixe */
  /* Sans ça, un utilisateur au clavier qui tabule vers un élément situé juste
     sous le pli le voit apparaître DERRIÈRE la barre fixe. */
  html { scroll-padding-bottom: 80px; }
}

/* --------------------------------------------------------------------------
   BARRE D'ACTION FIXE, TÉLÉPHONE UNIQUEMENT
   Les deux gestes qui comptent, appeler et écrire. Elle n'apparaît qu'après le
   hero, pour ne pas encombrer la première impression, et se retire quand on
   descend pour rendre l'écran au contenu. Posée sur --noir-3.
   -------------------------------------------------------------------------- */
.barre {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 60;
  display: none;
  background: rgba(34, 38, 47, 0.94);
  border-top: 1px solid var(--b12);
  transform: translateY(100%);
  transition: transform 520ms var(--courbe);
  padding-bottom: env(safe-area-inset-bottom);
}
@supports (backdrop-filter: blur(1px)) { .barre { backdrop-filter: blur(16px); } }
.barre[data-visible='true'] { transform: translateY(0); }
.barre a {
  flex: 1;
  min-height: 58px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  transition: background 240ms var(--courbe);
}
.barre a:active { background: var(--b06); }
.barre a + a { border-left: 1px solid var(--b12); }
.barre svg { width: 15px; height: 15px; flex: none; }
@media (max-width: 719px) { .barre { display: flex; } }

/* Utilitaire d'accessibilité */
.saut {
  position: absolute; left: -9999px;
  background: var(--blanc); color: var(--noir-profond);
  padding: 0.75rem 1.25rem; z-index: 99;
}
.saut:focus { left: var(--marge); top: 0.5rem; }

/* --------------------------------------------------------------------------
   15. NOTES SOUS LA GRILLE DE TARIFS
   -------------------------------------------------------------------------- */
.tarifs__note { margin-top: 1.5rem; color: var(--gris); max-width: 44rem; }
.cta { margin-top: clamp(1.75rem, 3vw, 2.25rem); }

/* Le second registre des prestations : par lieu et non par geste. */
.registre { margin-top: var(--air-bloc); }
.registre li { padding: 1.15rem 0; border-top: 1px solid var(--gris); max-width: var(--colonne); }
.registre li:last-child { border-bottom: 1px solid var(--gris); }
.registre b { font-weight: 500; }

/* --------------------------------------------------------------------------
   16. GALERIE
   Chaque image plafonne à 520 px d'affichage, contrainte physique : aucun
   fichier disponible ne dépasse 1040 px de large.
   -------------------------------------------------------------------------- */
.galerie {
  margin-top: var(--air-bloc);
  display: grid;
  gap: clamp(2rem, 4vw, 3.5rem);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  max-width: 1100px;
}
.galerie figure { margin: 0; }
.galerie img { width: 100%; max-width: 520px; }

/* --------------------------------------------------------------------------
   17. PAGES LÉGALES ET TEXTE LONG
   -------------------------------------------------------------------------- */
.texte { max-width: var(--colonne); }
.texte h2 {
  font-size: var(--t-h3);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-top: var(--air-bloc);
  margin-bottom: 0.85rem;
}
.texte h2:first-child { margin-top: 0; }
.texte ul { margin-top: 0.6rem; }
/* Sans ça, un paragraphe qui suit une liste vient se coller au dernier point :
   la règle « p + p » ne couvre pas « ul + p ». Visible sur la page 404, où la
   ligne du téléphone touchait le dernier lien de la liste. */
.texte ul + p, .texte ol + p { margin-top: 1.4rem; }
.texte ul li { padding-left: 1.1rem; position: relative; }
.texte ul li::before { content: ''; position: absolute; left: 0; top: 0.72em; width: 5px; height: 1px; background: var(--or); }
.texte li + li { margin-top: 0.35rem; }
/* Marqueur de donnée manquante. Discret : une note d'atelier, pas une alerte.
   Il doit se voir quand on le cherche et s'oublier quand on lit. */
.acompleter {
  color: var(--gris);
  font-size: 0.9em;
  border-bottom: 1px dotted var(--b25);
  padding-bottom: 1px;
}

/* --------------------------------------------------------------------------
   18. TITRE DE PAGE INTÉRIEURE
   -------------------------------------------------------------------------- */
.titre-page {
  font-size: clamp(2.25rem, 7vw, 4.5rem);
  text-transform: uppercase;
  line-height: 1.12;
  margin: clamp(1.25rem, 2.5vw, 2rem) 0 clamp(1.75rem, 3vw, 2.5rem);
}
.titre-page .escalier > span:nth-child(2) { padding-left: 1.6ch; }
.titre-page .escalier > span:nth-child(3) { padding-left: 3.2ch; }

/* --------------------------------------------------------------------------
   19. LA GRILLE DE TARIFS
   C'est la page où se décide l'achat, elle doit être composée et pas exportée.
   Le nom de la prestation passe en display, la durée s'efface en micro-capitale
   sous elle, et les deux prix se lisent d'un coup à droite. Un seul filet
   horizontal entre les lignes, un seul filet vertical entre les deux zones :
   ça sépare sans enfermer. L'élément reste un <table>, parce que c'est de la
   donnée tabulaire et qu'un lecteur d'écran doit pouvoir la parcourir.
   -------------------------------------------------------------------------- */
/* border-collapse: SEPARATE et non collapse. Un position:sticky sur un <th> ne
   fonctionne pas avec collapse dans Chromium : l'en-tête de colonnes défilait et
   sortait de l'écran malgré la règle. Vérifié, il était à y = -134 px après
   1 200 px de défilement. En contrepartie, les bordures posées sur un <tr> ne
   sont plus peintes du tout en mode separate : les filets de ligne passent donc
   sur les cellules, juste en dessous. */
.grille { width: 100%; max-width: 68rem; border-collapse: separate; border-spacing: 0; margin-top: var(--air-bloc); }
/* PAS de display:block : la légende quitte alors son emplacement de légende et
   se rend dans l'ordre du DOM, c'est-à-dire SOUS la ligne d'en-tête.
   caption-side: top la remet où elle doit être. */
.grille caption { caption-side: top; text-align: left; color: var(--gris); padding-bottom: 1.9rem; max-width: 46rem; }
.grille th, .grille td { text-align: left; vertical-align: baseline; }

/* L'EN-TÊTE DE COLONNES RESTE COLLANTE.
   Depuis que les deux prix sont blancs et du même corps, seule cette ligne dit
   lequel est l'Allier et lequel est Paris. Sur treize lignes elle sortait de
   l'écran au bout de la troisième, et on ne savait plus lire le tableau.
   67 px : hauteur mesurée de l'en-tête du site en version compacte, qui est son
   état dès qu'on a descendu de quelques pixels, donc toujours quand ce tableau
   est à l'écran. */
.grille thead th {
  position: sticky;
  top: 67px;
  z-index: 5;
  background: var(--noir-1);
  padding: 0.9rem 0 1.1rem;
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--gris);
  border-bottom: 1px solid var(--gris);
}
/* Étiquettes courtes et insécables : « Moulins et l'Allier » sur une colonne
   étroite se repliait sur cinq lignes. La légende au dessus dit le reste. */
.grille thead .zone-nom { color: var(--blanc); text-align: right; white-space: nowrap; }
/* L'en-tête « Durée » est INVISIBLE mais RESTE DANS LE FLUX. Il était sorti du
   champ par position:absolute, ce qui retirait sa cellule de la colonne : depuis
   que la ligne d'en-tête est collante, le fond ne couvrait plus cette colonne et
   on voyait défiler les lignes du tableau au travers. Vérifié sur capture.
   color: transparent garde la cellule, garde son fond, et laisse le mot
   « Durée » disponible pour les lecteurs d'écran. */
.grille thead .col-duree { color: transparent; }

.grille tbody th, .grille tbody td { border-bottom: 1px solid var(--o25); }
.grille tbody tr:last-child th, .grille tbody tr:last-child td { border-bottom: 0; }

.grille tbody th {
  font-family: 'Bodoni Moda', 'Bodoni 72', Didot, Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-size: clamp(1.0625rem, 1.7vw, 1.5rem);
  line-height: 1.18;
  letter-spacing: -0.005em;
  padding: 1.5rem clamp(1.5rem, 4vw, 3.5rem) 1.5rem 0;
}
.grille .duree-cell {
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--gris);
  white-space: nowrap;
  padding: 1.5rem clamp(1rem, 3vw, 2.5rem) 1.5rem 0;
}
/* LE PRIX est un composant à part entière, plus une cellule de tableau. Il sert
   dans la grille complète de prestations.html ET dans le bloc tarifs des quatre
   pages locales. Le traitement typographique est ici, ce qui relève du tableau
   est dans la règle suivante. */
.prix {
  font-family: 'Bodoni Moda', 'Bodoni 72', Didot, Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  font-size: clamp(1.375rem, 2.4vw, 2rem);
  line-height: 1;
  white-space: nowrap;
  color: var(--or);
}
.grille .prix {
  min-width: 4.5em;
  text-align: right;
  padding: 1.4rem 0;
  width: 1%;
}
/* Un seul filet vertical, celui qui sépare les deux zones. */
.grille .prix--paris, .grille thead .th--paris {
  border-left: 1px solid var(--o25);
  padding-left: clamp(1.25rem, 3vw, 2.5rem);
}
/* LES DEUX PRIX EN OR, même corps, même couleur : deux offres égales sur deux
   territoires. Le prix est l'information la plus cherchée de la page, l'or
   l'attrape en premier (8,38:1 sur --noir-1, 6,73:1 au pire des noirs). Ce qui
   distingue les zones, c'est l'étiquette au-dessus sur téléphone et l'en-tête
   de colonne sur écran large. Un gris sur l'un des deux se lirait « barré ». */
.grille tbody .prix, .grille tbody .prix--paris { color: var(--or); }

@media (max-width: 639px) {
  /* LA LIGNE TIENT EN DEUX TEMPS : le nom et la durée sur une ligne, les deux
     prix alignés à droite sur la suivante.
     La version précédente empilait nom, puis durée, puis prix, chacun avec sa
     propre respiration : 237 px par ligne, 3 619 px pour treize prix, soit
     4,3 écrans de défilement. La durée flottait seule au milieu du vide et le
     nom se retrouvait à plus de 100 px de son prix : ils cessaient de se lire
     comme une même ligne. */
  .grille, .grille tbody { display: block; }
  /* La légende doit devenir un bloc ICI, et seulement ici. Restée en
     table-caption dans un conteneur passé en display:block, elle se retrouvait
     dans une table anonyme et sa largeur tombait à quelques mots : mesurée à
     498 px de haut pour deux phrases, un mot par ligne. Sur écran large elle
     reste une vraie légende, voir le commentaire plus haut : display:block la
     ferait alors passer sous la ligne d'en-tête. */
  .grille caption { display: block; width: 100%; }
  .grille thead { position: absolute; left: -9999px; }
  .grille tbody tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    align-items: baseline;
    column-gap: clamp(1.25rem, 5vw, 2.25rem);
    row-gap: 0.5rem;
    padding: 0.95rem 0;
    border-bottom: 1px solid var(--o25);
  }
  /* Le nom occupe les deux premières colonnes, la durée ferme la ligne à
     droite. Les deux prix tombent en dessous, dans les colonnes 2 et 3 : la
     colonne 1 reste vide, ce qui les cale à droite sans alignement à la main. */
  .grille tbody th { grid-column: 1 / 3; padding: 0; }
  .grille .duree-cell { grid-column: 3; justify-self: end; padding: 0; margin: 0; }
  .grille .prix { grid-column: 2; }
  .grille .prix--paris { grid-column: 3; }
  .grille .prix, .grille .prix--paris {
    min-width: 0;
    width: auto;
    padding: 0;
    text-align: right;
    border-left: 0;
    padding-left: 0;
  }
  .grille .prix::before {
    content: attr(data-zone);
    display: block;
    font-family: 'Archivo', sans-serif;
    font-size: 0.5625rem;
    font-weight: 500;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--gris);
    margin-bottom: 0.3rem;
    white-space: nowrap;
  }
  /* « Moulins et l'Allier » et « Paris et Hauts-de-Seine » en entier sous
     chaque prix ne tiennent pas côte à côte à 390 px. La légende du tableau,
     juste au-dessus, dit déjà ce que couvre chaque zone. */
  .grille .prix[data-zone]::before { content: 'Allier'; }
  .grille .prix--paris[data-zone]::before { content: 'Paris'; }
}

/* Révélation ligne par ligne : posée par le JS, donc sans effet sans lui. */
.js-anim .grille tbody tr { opacity: 0; }

/* --------------------------------------------------------------------------
   20. TRÈS PETITS ÉCRANS
   Sous 375 px, un h1 de page intérieure contenant un mot long et indivisible
   (« Confidentialité », « commentaire. ») déborde de sa colonne, même si tous
   les autres titres passent. Mesuré à 320 et 360 px : 337 px de texte pour
   272 px utiles. On réduit le corps ET le pas de l'escalier, uniquement ici,
   pour ne pas rapetisser les titres sur les téléphones courants.
   -------------------------------------------------------------------------- */
@media (max-width: 374px) {
  .titre-page { font-size: 1.6875rem; }
  .titre-page .escalier > span:nth-child(2) { padding-left: 1.2ch; }
  .titre-page .escalier > span:nth-child(3) { padding-left: 2.4ch; }
}

/* ==========================================================================
   COMPOSANTS DE LA REFONTE
   ========================================================================== */

/* --------------------------------------------------------------------------
   21. LES SIX LIEUX, EN DÉFILEMENT HORIZONTAL
   C'est le composant qui porte le concept : pendant qu'on descend, les lieux
   défilent latéralement. On se déplace.
   Sur grand écran, ScrollTrigger épingle la section et pilote la piste.
   Sur téléphone, PAS de défilement piloté au scroll : c'est le piège iOS
   classique. La piste devient un défilement natif au doigt, avec accrochage.
   -------------------------------------------------------------------------- */
.lieux { position: relative; padding: 0; overflow: hidden; }
.lieux__cadre {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  /* Partition éditoriale plutôt que centrage : le titre tient le haut, la
     piste tient le bas. Centré, la section laissait 300 px de vide au dessus
     ET en dessous, et l'écran paraissait creux. */
  justify-content: space-between;
  gap: clamp(2rem, 5vh, 4rem);
  padding: clamp(8rem, 20vh, 13rem) 0 clamp(7rem, 18vh, 11rem);
}
.lieux__tete { padding: 0 var(--marge); }
/* Par défaut la liste ENVELOPPE : sans JavaScript, rien ne pilote le
   défilement horizontal et les quatre derniers lieux seraient hors cadre,
   donc inatteignables. Vérifié au navigateur, JS désactivé.
   La piste ne devient une vraie piste que sous .js-anim. */
.lieux__piste {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: clamp(1.5rem, 4vw, 3rem) clamp(3rem, 7vw, 9rem);
  padding: 0 var(--marge);
}
.js-anim .lieux__piste {
  flex-wrap: nowrap;
  width: max-content;
  will-change: transform;
}
.lieux__item { flex: none; display: flex; align-items: baseline; gap: clamp(0.9rem, 2vw, 1.6rem); }
.lieux__num {
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.2em;
  color: var(--gris);
  align-self: flex-start;
  padding-top: 0.6em;
}
.lieux__mot {
  font-family: 'Bodoni Moda', 'Bodoni 72', Didot, Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-size: clamp(2.75rem, 9vw, 8rem);
  /* « EN ÉVÉNEMENT » passe sur deux lignes dans la scène : le É de la seconde
     mordait la première à interligne 1. */
  line-height: 1.12;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}
.lieux__item:last-child .lieux__mot { color: var(--or); }

@media (max-width: 899px) {
  /* Défilement natif au doigt : jamais de currentTime ni de translate piloté
     au scroll sur iOS, c'est le piège documenté. */
  /* Sur téléphone la section n'est plus épinglée, mais elle doit garder de la
     présence : sans hauteur minimale elle tombait à 530 px et se lisait comme
     un simple paragraphe entre deux blocs. */
  .lieux__cadre { min-height: 78vh; min-height: 78dvh; padding: clamp(4rem, 10vh, 6rem) 0; }
  .js-anim .lieux__piste {
    width: auto;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    /* Sans ça, l'accrochage cale le premier élément sur le bord du conteneur
       en ignorant son padding : le « 01 » se retrouvait coupé au bord gauche.
       Mesuré scrollLeft à 24 px, exactement la marge annulée. */
    scroll-padding-left: var(--marge);
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 1rem;
  }
  .lieux__piste::-webkit-scrollbar { display: none; }
  .lieux__item { scroll-snap-align: start; }

  /* ------------------------------------------------------------------------
     LA SCÈNE, MOMENT FORT DU TÉLÉPHONE
     Sur ordinateur, la section s'épingle et les six lieux défilent
     horizontalement. Sous 900 px ce mécanisme était coupé, et le meilleur
     moment du site disparaissait de l'écran sur lequel il sera le plus vu.

     Ici les six lieux prennent l'écran entier à tour de rôle. L'épinglage est
     un position:sticky NATIF, pas un pin GSAP : c'est précisément l'épinglage
     JavaScript qui se comporte mal sur iOS. GSAP ne pilote que de l'opacité et
     un déplacement de quelques pixels, deux propriétés que le compositeur
     traite sans recalcul de mise en page.

     data-scene est posé par anim.js, et seulement sous 900 px et hors mouvement
     réduit. Sans JavaScript, sans GSAP ou en mouvement réduit, l'attribut
     n'existe pas et on retombe sur la piste au doigt ci-dessus, qui reste
     parfaitement utilisable.
     ------------------------------------------------------------------------ */
  .js-anim [data-lieux][data-scene] {
    /* Un écran pour le premier lieu, puis une demi-hauteur par transition.
       C'est cette course de défilement qui donne son temps à la scène. */
    height: calc(100svh + 5 * 48vh);
    padding: 0;
    /* La section porte overflow:hidden pour rogner la piste horizontale du
       bureau. Ici il n'y a plus de piste, et cet overflow ferait de la section
       le conteneur de défilement de son propre enfant collant : le sticky ne
       collerait à rien. */
    overflow: visible;
  }
  .js-anim [data-lieux][data-scene] .lieux__cadre {
    position: sticky;
    top: 0;
    /* svh et non dvh : la hauteur la plus PETITE, celle qu'on a barre d'adresse
       déployée. La scène tient donc toujours à l'écran, y compris au moment où
       iOS replie sa barre, au lieu de déborder d'une centaine de pixels. */
    height: 100svh;
    min-height: 0;
    /* Le haut dégage l'en-tête, qui est en position fixed et mesure 67 px :
       mesuré, « OÙ ÇA SE PASSE » démarrait à 57 px et passait dessous.
       Le bas dégage la barre d'action fixe (58 px plus la zone sûre), sinon la
       jauge passe dessous et devient invisible au moment où elle sert le plus. */
    padding: clamp(5.5rem, 11vh, 6.5rem) 0 calc(58px + 1.25rem + env(safe-area-inset-bottom));
    justify-content: space-between;
    gap: clamp(1.5rem, 4vh, 2.5rem);
    overflow: hidden;
  }
  .js-anim [data-lieux][data-scene] .lieux__piste {
    /* Les six lieux occupent la MÊME cellule de grille : ils se superposent
       sans position absolue, et le conteneur se dimensionne sur le plus haut. */
    display: grid;
    width: auto;
    overflow: visible;
    flex: 1;
    align-content: center;
    padding: 0 var(--marge);
    scroll-snap-type: none;
  }
  .js-anim [data-lieux][data-scene] .lieux__item {
    grid-area: 1 / 1;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: 0;
  }
  /* Le numéro est POSÉ AU DESSUS du mot, pas derrière lui. Première version
     essayée : un numéro géant en fond, centré sur le mot. Le rendu montrait la
     hampe du « 1 » traversant CHEZ VOUS entre les deux mots, au même poids
     optique que la didone : ça se lisait comme un défaut d'affichage, pas
     comme une intention. Ici il occupe le haut du panneau, le mot se glisse
     sous lui grâce à la marge négative, et rien ne se croise. */
  .js-anim [data-lieux][data-scene] .lieux__num {
    position: static;
    align-self: flex-start;
    padding-top: 0;
    font-family: 'Bodoni Moda', 'Bodoni 72', Didot, Georgia, serif;
    font-optical-sizing: auto;
    /* Agrandi : le bloc n'occupait que 181 px d'une bande de 413. Le numéro
       porte l'échelle, c'est lui qui doit grandir, pas le mot. */
    font-size: min(42vw, 13rem);
    font-weight: 400;
    letter-spacing: -0.04em;
    /* 0.86 et non 0.76 : sous 0.8 le glyphe déborde de sa propre boîte et le
       mot venait chevaucher le chiffre, accents compris. Mesuré sur « 06 » et
       « EN ÉVÉNEMENT », qui est le pire cas du lot. */
    line-height: 0.86;
    margin-left: -0.04em;
    margin-bottom: 0.02em;
    color: var(--noir-3);
  }
  .js-anim [data-lieux][data-scene] .lieux__mot {
    white-space: normal;
    /* 11,2vw et non 13. Mesuré au canvas : à 13vw « EN ÉVÉNEMENT » fait 385 px
       pour 342 px disponibles, et se coupait en « EN » seul puis « ÉVÉNEMENT »
       pleine largeur. text-wrap: balance ne peut rien avec deux mots aussi
       inégaux. À 11,2vw il fait 332 px et tient sur une ligne, comme trois
       autres lieux. Les deux plus longs, « DANS VOS BUREAUX » et « SUR UN
       TOURNAGE », se coupent encore mais en deux moitiés équilibrées. */
    font-size: clamp(2.4rem, 11.2vw, 4.5rem);
    text-wrap: balance;
  }
  /* Le dernier lieu est en or dans la piste. Dans la scène chaque lieu est
     seul à l'écran : l'or marque la fin du compte, pas un élément parmi six
     visibles. Il reste donc sur le sixième, et sur lui seul. */
  .js-anim [data-lieux][data-scene] .lieux__item:last-child .lieux__mot { color: var(--or); }

  /* Jauge d'avancement, créée par anim.js donc inexistante sans JavaScript.
     Sans elle on ne sait pas combien de lieux il reste, et une scène dont on
     ne voit pas la fin se lit comme un blocage du défilement. */
  .lieux__jauge {
    margin: 0 var(--marge);
    height: 1px;
    background: var(--b12);
    position: relative;
    flex: none;
  }
  .lieux__jauge i {
    position: absolute;
    inset: 0 auto 0 0;
    width: 100%;
    background: var(--or);
    transform: scaleX(0);
    transform-origin: 0 50%;
  }
}

/* --------------------------------------------------------------------------
   22. BANDEAU DÉFILANT
   Codes du secteur, très efficace en noir. Animation CSS pure, en boucle.
   La piste est dupliquée pour que la boucle soit invisible.
   -------------------------------------------------------------------------- */
.bandeau {
  overflow: hidden;
  border-block: 1px solid var(--b12);
  padding: clamp(1.1rem, 2.6vw, 1.9rem) 0;
  background: var(--noir-1);
}
.bandeau__piste { display: flex; width: max-content; animation: bandeau 46s linear infinite; }
.bandeau__lot { display: flex; align-items: center; gap: clamp(2rem, 5vw, 4.5rem); padding-right: clamp(2rem, 5vw, 4.5rem); }
.bandeau__mot {
  font-family: 'Bodoni Moda', 'Bodoni 72', Didot, Georgia, serif;
  font-optical-sizing: auto;
  font-size: clamp(1.125rem, 2.2vw, 1.75rem);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  white-space: nowrap;
  color: var(--gris);
}
.bandeau__point { width: 5px; height: 5px; background: var(--or); flex: none; }
@keyframes bandeau { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* --------------------------------------------------------------------------
   23. RÉVÉLATIONS AU DÉFILEMENT
   État de départ sous .js-anim, donc inexistant sans JavaScript.
   -------------------------------------------------------------------------- */
.js-anim [data-reveal] { opacity: 0; }

/* --------------------------------------------------------------------------
   24. TRANSITION ENTRE PAGES
   Fondu au noir au clic sur un lien interne, puis apparition de la suivante.
   C'est ce qui donne la sensation d'application plutôt que de site.
   Le voile est créé par le JS : sans JavaScript, les liens sont des liens.
   -------------------------------------------------------------------------- */
.voile {
  position: fixed;
  inset: 0;
  z-index: 95;
  background: var(--noir-0);
  opacity: 0;
  pointer-events: none;
}

/* L'ENTRÉE est en CSS pur, pas en JavaScript. Le voile de sortie est créé par
   anim.js, qui est différé : entre le premier rendu et son exécution, la page
   s'affichait puis s'assombrissait. Ici le noir est là dès le premier pixel et
   s'efface tout seul. Sous .js-anim, donc inexistant sans JavaScript. */
.js-anim body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 94;
  background: var(--noir-0);
  pointer-events: none;
  animation: entree 620ms var(--courbe) forwards;
}
@keyframes entree {
  from { opacity: 1; }
  to   { opacity: 0; visibility: hidden; }
}

/* --------------------------------------------------------------------------
   25. LISTE DU PROTOCOLE
   -------------------------------------------------------------------------- */
.protocole { margin-top: var(--air-bloc); max-width: 60ch; }
.protocole li { padding: 1.15rem 0; border-top: 1px solid var(--o25); }
.protocole li:last-child { border-bottom: 1px solid var(--o25); }
.protocole b { font-weight: 500; color: var(--blanc); }
.protocole span { color: var(--gris); }

/* --------------------------------------------------------------------------
   26. DUO TEXTE ET IMAGE
   -------------------------------------------------------------------------- */
.duo { display: grid; gap: clamp(2rem, 5vw, 4.5rem); align-items: center; margin-top: var(--air-bloc); }
@media (min-width: 900px) { .duo { grid-template-columns: minmax(0, 1fr) 380px; } }
.duo figure { margin: 0; }
.duo img { width: 100%; max-width: 520px; }
/* Révélation par masque sur la photo : elle se découvre par le bas. */
.js-anim [data-masque] { clip-path: inset(100% 0 0 0); }

@media (prefers-reduced-motion: reduce) {
  /* Le bandeau défile en boucle sans fin : mouvement continu, il s'arrête. */
  .bandeau__piste { animation: none; }

  /* Le voile d'entrée reste : c'est un fondu d'opacité pur, il ne bouge pas.

     La photo, elle, ne se découvre plus par un volet qui remonte. Il faut
     remplacer son état de départ, sinon le clip-path la garderait invisible
     puisque anim.js n'anime plus que son opacité dans ce mode. */
  .js-anim.mouvement-reduit [data-masque] { clip-path: none; opacity: 0; }

  /* La barre d'action ne glisse plus depuis le bas, elle apparaît sur place. */
  .barre { transform: none; opacity: 0; visibility: hidden;
           transition: opacity 320ms var(--courbe), visibility 320ms; }
  .barre[data-visible='true'] { transform: none; opacity: 1; visibility: visible; }
}


/* --------------------------------------------------------------------------
   27. EN-TÊTE DE PAGE INTÉRIEURE
   Pas un hero plein écran : les pages intérieures s'ouvrent sur une déclaration
   haute, avec la même lueur que l'accueil mais plus discrète, puis entrent
   directement dans le contenu.
   -------------------------------------------------------------------------- */
.tete-page {
  position: relative;
  overflow: hidden;
  padding: clamp(9rem, 22vh, 15rem) var(--marge) clamp(4rem, 9vh, 7rem);
  isolation: isolate;
}
.tete-page::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -2;
  background: linear-gradient(168deg, var(--noir-1) 0%, var(--noir-0) 60%);
}
.tete-page__lueur {
  position: absolute;
  z-index: -1;
  left: -18%;
  top: -35%;
  width: min(105vw, 1200px);
  height: min(105vh, 900px);
  pointer-events: none;
  background: radial-gradient(closest-side,
    rgba(116, 90, 38, 0.22) 0%, rgba(116, 90, 38, 0.09) 40%, transparent 74%);
}
/* Sur la galerie, la rosace revient : élément de marque, plus un accident
   de la page d'accueil. Même fichier (une seule requête, déjà en cache après
   l'accueil), plus petite et remontée pour vivre dans le noir au-dessus du
   titre. Mêmes règles que sur le hero : une texture, jamais un décor. */
.tete-page--rosace::after {
  content: '';
  position: absolute;
  z-index: -1;
  inset: 0;
  pointer-events: none;
  background: url('images/rosace-or-24.svg') no-repeat calc(50% + 18vw) 34%;
  background-size: 760px 760px;
  opacity: 0.10;
}
@media (max-width: 899px) {
  .tete-page--rosace::after { background-position: 50% 20%; background-size: 640px 640px; opacity: 0.07; }
}
.tete-page__inner { position: relative; max-width: 1320px; margin: 0 auto; }
.tete-page h1 {
  font-family: 'Bodoni Moda', 'Bodoni 72', Didot, Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-size: clamp(2.5rem, 7.5vw, 6rem);
  line-height: 1.12;
  letter-spacing: -0.025em;
  text-transform: uppercase;
  max-width: 16ch;
}
.tete-page h1 .accent { color: var(--or); }
.tete-page .corps { margin-top: clamp(1.75rem, 3.5vw, 2.75rem); }

/* --------------------------------------------------------------------------
   28. INTERTITRES DANS UN TEXTE LONG
   Les pages locales gardent leurs 400 à 500 mots, mais aérés : un intertitre,
   des phrases courtes, une idée par paragraphe.
   -------------------------------------------------------------------------- */
.corps h3 {
  font-size: var(--t-h3);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: clamp(2.25rem, 4.5vw, 3.25rem) 0 0.9rem;
  color: var(--blanc);
}
.corps h3:first-child { margin-top: 0; }
.corps--gris p { color: var(--gris); }
.corps--gris h3 { color: var(--blanc); }

/* Une ligne de faits, sobre, pour remplacer un paragraphe descriptif. */
.faits { margin-top: var(--air-bloc); display: grid; gap: 1px; background: var(--b12); max-width: 60ch; }
.faits > div { background: var(--noir-1); padding: 1.15rem 0 1.15rem 0; }
.faits dt { font-size: var(--t-micro); font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--gris); }
.faits dd { margin: 0.45rem 0 0; }
.bloc--surface .faits > div { background: var(--noir-1); }

/* --------------------------------------------------------------------------
   29. VIDÉO EN BOUCLE
   Règle absolue : JAMAIS en plein cadre sur desktop. Les sources plafonnent à
   1024 px de large, les agrandir les ferait éclater. La largeur d'affichage
   est donc plafonnée par classe, selon la définition de la source.
   Sur téléphone, la pleine largeur est autorisée : 464 px de source pour
   390 px d'écran, la densité reste correcte.
   -------------------------------------------------------------------------- */
.video { margin: 0; position: relative; }
.video video {
  display: block;
  width: 100%;
  height: auto;
  background: var(--noir-2);
}
.video--440 video { max-width: 440px; }
.video--620 video { max-width: 620px; }
.video--900 video { max-width: 900px; }
@media (max-width: 719px) {
  .video--440 video, .video--620 video, .video--900 video { max-width: 100%; }
}
/* Le filet qui encadre la vidéo la pose comme une pièce et non comme un trou. */
.video video { border: 1px solid var(--b12); }

/* --------------------------------------------------------------------------
   30. GALERIE ÉDITORIALE
   Formats inégaux et respiration : une galerie sur fond noir vit par le rythme
   des vides, pas par la régularité d'une grille.
   -------------------------------------------------------------------------- */
.editoriale { margin-top: var(--air-bloc); display: grid; gap: clamp(2.5rem, 6vw, 6rem); }
/* Quand la grille de médias ouvre la section sans titre au-dessus (choix
   client : aucun texte autour des photos), la marge haute n'a plus d'objet. */
.bloc__inner > .editoriale:first-child { margin-top: 0; }
@media (min-width: 900px) {
  .editoriale { grid-template-columns: repeat(12, 1fr); align-items: end; }
  .ed--gauche  { grid-column: 1 / span 5; }
  .ed--droite  { grid-column: 8 / span 5; }
  .ed--large   { grid-column: 3 / span 8; }
  .ed--etroit  { grid-column: 2 / span 4; }
  .ed--fin     { grid-column: 7 / span 4; }
  .ed--pleine  { grid-column: 1 / -1; }
  .ed--decale  { grid-column: 6 / span 6; }
  .ed--petite  { grid-column: 9 / span 3; }
}
/* Petit format, y compris sur téléphone où la grille retombe à une colonne et
   où tout serait sinon pleine largeur. Sert aux images qui documentent sans
   porter la démonstration : ici la seule photo claire du lot, descendue de
   l'accueil parce qu'elle contredisait la direction sombre. */
.ed--petite img { max-width: 62%; }
@media (min-width: 900px) { .ed--petite img { max-width: 100%; } }
.editoriale figure { margin: 0; }
.editoriale img, .editoriale video { width: 100%; height: auto; display: block; }


/* --------------------------------------------------------------------------
   BLOC TARIFS DES PAGES LOCALES
   Trois prestations, pas une de plus. Les prix étaient noyés dans une liste de
   six lignes hétérogènes qui mêlait logistique et tarifs, et écrits en toutes
   lettres au fil d'une phrase : « 40 € quand la repousse fait moins de deux
   mois, 55 € au-delà. Quarante minutes. » Dix-huit mots pour deux chiffres.
   Ici le prix est l'élément le plus visible du bloc, la durée passe en gris et
   en micro-capitale, et quand deux prix coexistent ils sont côte à côte avec
   leur condition au-dessus, comme les deux zones sur la page prestations.
   -------------------------------------------------------------------------- */
.tarifs { margin-top: var(--air-bloc); max-width: 44rem; }
.tarifs li { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: 0.4rem 1.5rem; padding: 1.15rem 0; border-bottom: 1px solid var(--o25); }
.tarifs li:last-child { border-bottom: 0; }
.tarif__nom { font-family: 'Bodoni Moda', 'Bodoni 72', Didot, Georgia, serif; font-optical-sizing: auto; font-size: clamp(1.0625rem, 1.7vw, 1.5rem); line-height: 1.18; }
.tarif__duree { display: block; margin-top: 0.35rem; font-family: 'Archivo', sans-serif; font-size: 0.625rem; font-weight: 500; letter-spacing: 0.2em; text-transform: uppercase; color: var(--gris); }
.tarif__prix { display: flex; gap: clamp(1.25rem, 3vw, 2.25rem); align-items: flex-end; }
.tarif__prix > span { text-align: right; }
/* La condition, en micro-capitale grise au-dessus du chiffre. Jamais dans une
   phrase : c'est ce qui permet de lire les deux prix d'un seul regard.

   ELLE EST DANS LE HTML, PAS DANS LE CSS. Première version : content:
   attr(data-cond) sur un ::before. Le rendu était le bon, mais le libellé
   n'existait pas dans la couche texte : une machine lisait « 40 €55 € » sans
   savoir ce qui distinguait les deux prix. C'est le même piège que le FAQPage,
   qui exige du texte réellement affiché. Le rendu est identique, le sens est
   maintenant lisible par tout le monde. */
.prix__cond {
  display: block;
  font-family: 'Archivo', sans-serif;
  font-size: 0.5625rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--gris);
  margin-bottom: 0.35rem;
  white-space: nowrap;
}
/* Empilé, tout se cale à gauche. Aligné à droite, un prix étiqueté se retrouvait
   décalé sous son étiquette pendant qu'un prix sans étiquette restait au bord :
   deux points de départ dans le même bloc. */
@media (max-width: 479px) {
  .tarifs li { grid-template-columns: 1fr; }
  .tarifs .tarif__prix { justify-content: flex-start; }
  .tarifs .tarif__prix > span { text-align: left; }
}

/* Infos pratiques : ce qui reste quand les prix sont sortis. */
.pratique { margin-top: var(--air-bloc); }

/* --------------------------------------------------------------------------
   FAQ VISIBLE DES PAGES LOCALES
   Le schema FAQPage exige que la donnée structurée corresponde à du texte
   réellement affiché. Ces questions ne sont donc pas un habillage du balisage,
   c'est l'inverse : le contenu existe, le balisage le décrit.
   Pas de <details> ici : replié, le texte reste dans le DOM et Google
   l'accepte, mais un visiteur qui cherche un prix ne déplie pas quatre blocs.
   -------------------------------------------------------------------------- */
.faq { margin-top: var(--air-bloc); display: grid; gap: clamp(1.75rem, 3.5vw, 2.75rem); max-width: 62ch; }
@media (min-width: 900px) { .faq { grid-template-columns: 1fr 1fr; max-width: none; gap: clamp(2rem, 3vw, 3.25rem) clamp(2.5rem, 5vw, 5rem); } }
.qr h3 {
  font-size: var(--t-h3);
  font-weight: 500;
  letter-spacing: 0.01em;
  line-height: 1.35;
  margin-bottom: 0.6rem;
}
.qr p { color: var(--gris); }
/* Un filet fin au-dessus de chaque question : il sépare sans peser, et sur deux
   colonnes il donne le rythme que la seule gouttière ne donne pas. */
.qr { border-top: 1px solid var(--o25); padding-top: clamp(1.1rem, 2vw, 1.5rem); }

/* --------------------------------------------------------------------------
   31. BOUTON MAGNÉTIQUE
   Le bouton suit légèrement le curseur quand on l'approche. Uniquement au
   pointeur fin : sur un écran tactile il n'y a pas de survol, et l'effet
   n'aurait aucun sens.
   -------------------------------------------------------------------------- */
@media (hover: hover) and (pointer: fine) {
  .bouton[data-magnetique] { will-change: transform; }
}

/* --------------------------------------------------------------------------
   32. LE FORMULAIRE, VERSION ARRIVÉE
   Il était serré et lisait comme un pied de page. Il devient un bloc à part
   entière : plus de respiration, des champs plus hauts, et un fond qui le
   pose sur la page au lieu de le laisser flotter.
   -------------------------------------------------------------------------- */
.bloc-veille {
  background: var(--noir-3);
  padding: clamp(2rem, 5vw, 4rem);
  margin-top: var(--air-bloc);
  border: 1px solid var(--b06);
  /* Le panneau se cale sur son contenu. Laissé à 1320 px, il encadrait
     450 px de champs et paraissait vide aux trois quarts. */
  max-width: 62rem;
}
/* Dans le panneau, les champs occupent toute la largeur disponible. */
.bloc-veille .champs { max-width: none; }
.champs { display: grid; gap: clamp(1.75rem, 3.5vw, 2.5rem); max-width: 68ch; }
@media (min-width: 760px) { .champs { grid-template-columns: 1fr 1fr; } .champ--large { grid-column: 1 / -1; } }
.champ label {
  display: block;
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--gris);
  margin-bottom: 0.75rem;
}
.champ input, .champ select, .champ textarea {
  width: 100%;
  /* 52 px et non 60. Un champ vide de 60 px surmonté de son étiquette se lisait
     comme un trou : sur neuf champs, le formulaire paraissait inachevé. 52 px
     reste très au-dessus du plancher tactile de 44 px, et chaque champ porte
     désormais un texte indicatif, ce qui règle le vrai problème : un champ vide
     ne montrait rien du tout. */
  min-height: 52px;
  font-size: 1.0625rem;
  background: transparent;
  color: var(--blanc);
  border: 0;
  border-bottom: 1px solid var(--gris);
  padding: 0.75rem 0;
  border-radius: 0;
  -webkit-appearance: none;
  appearance: none;
  transition: border-color 240ms var(--courbe);
}
.champ textarea { min-height: 150px; resize: vertical; }

/* --------------------------------------------------------------------------
   33. IMPRESSION
   Tout ce que le JS cache en attendant une révélation au défilement doit
   exister sur papier et dans un PDF : une grille de tarifs absente d'une
   impression est un bug bloquant, pas un effet. Le !important est
   nécessaire : GSAP pose l'état de départ (opacity:0) en style en ligne.
   Les voiles plein écran, eux, n'ont rien à faire sur une page imprimée.
   -------------------------------------------------------------------------- */
@media print {
  .js-anim .grille tbody tr,
  .js-anim .hero__titre,
  .js-anim .hero__titre .mot > i,
  .js-anim [data-hero-suite],
  .js-anim [data-reveal],
  .js-anim [data-reveal] *,
  .anim {
    opacity: 1 !important;
    transform: none !important;
  }
  .js-anim [data-masque] { clip-path: none !important; }
  .js-anim body::before { content: none !important; }
  .rideau, .voile { display: none !important; }
}
