/* ============================================================================
   BASE — le craft appliqué. À copier tel quel, à NE PAS réécrire par site.

   Ce fichier ne contient AUCUN choix esthétique : il ne fait qu'appliquer
   mécaniquement les règles de 09-bible-web/01-fondamentaux-ui.md (grille,
   échelle, proximité, lisibilité, états) et de 09-bible-web/07 (les inviolables).

   Il ne lit que des tokens. Toute l'identité vit dans tokens.css.

   Pourquoi il existe : l'audit du 07/08/2026 a montré que chaque site
   reconstruisait sa grille, son échelle et ses états à la main — d'où des
   valeurs au hasard, des contrastes ratés et des cibles trop petites. Ici,
   le plancher de qualité est posé une fois pour toutes.
   ============================================================================ */

/* ---------------------------------------------------- 1. Remise à plat ---- */
*, *::before, *::after { box-sizing: border-box; }
/* ⛔ 19/09/2026 : `100vw` inclut la barre de defilement. Les sections pleine
   largeur debordaient donc de sa largeur sur PC, et la page se decalait
   lateralement. `clip` coupe sans creer de conteneur de defilement : le
   collant (`position: sticky`) continue de fonctionner, contrairement a
   `hidden`. Trouve par Saadou ; invisible en navigateur sans fenetre. */
html { overflow-x: clip; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body, h1, h2, h3, h4, p, figure, blockquote, dl, dd { margin: 0; }
ul[class], ol[class] { list-style: none; padding: 0; margin: 0; }
img, picture, video, canvas, svg { display: block; max-width: 100%; }
/* ⚠️ `height: auto` est OBLIGATOIRE : sans lui, les attributs width/height du HTML
   (1200x900) restent la hauteur rendue et l'image est ÉTIRÉE dès que la colonne est
   plus étroite. Mesuré le 17/09/2026 sur le hero de Porteclose : 600x900 affichés pour
   une photo de ratio 1,47. Un bloc qui veut un cadrage pose son aspect-ratio + object-fit. */
img, video, canvas { height: auto; }
input, button, textarea, select { font: inherit; color: inherit; max-width: 100%; }
button { background: none; border: 0; cursor: pointer; }

/* ⛔ INVIOLABLE I05 — le garde-fou mouvement. Toujours en haut, jamais retiré. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  html { scroll-behavior: auto; }
}

/* ------------------------------------------------------- 2. Le corps ------ */
body {
  background: var(--fond);
  color: var(--encre);
  font-family: var(--f-texte);
  font-size: var(--fs-body);          /* ⛔ I01 — plancher 19px                */
  line-height: var(--lh-body);
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

/* ------------------------------------------------- 3. La grille ----------- */
/* 4 colonnes mobile · 8 tablette · 12 desktop. Rien ne sort de là sans
   décision écrite (09-bible-web/07, règle F01).                              */
.wrap {
  width: 100%;
  max-width: var(--w-max);
  margin-inline: auto;
  padding-inline: var(--e-5);                       /* 24px : la marge mobile  */
}
@media (min-width: 768px)  { .wrap { padding-inline: var(--e-6); } }
@media (min-width: 1024px) { .wrap { padding-inline: var(--e-8); } }

/* ⚠️ `minmax(0, 1fr)` et PAS `1fr`. C'est LE piège de CSS Grid : `1fr` vaut
   `minmax(auto, 1fr)`, donc une colonne ne descend jamais sous la largeur de son
   contenu — un mot long, une carte dense, et les colonnes se chevauchent au lieu
   de se partager la largeur. Constaté sur le premier site généré par le socle
   (07/08/2026) : les 3 cartes de services sortaient à des largeurs différentes.
   Le `min-width: 0` sur les enfants couvre le même problème en flex. */
.grid { display: grid; gap: var(--gap-grille-m); grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (min-width: 768px)  { .grid { grid-template-columns: repeat(8, minmax(0, 1fr));  gap: var(--gap-grille); } }
@media (min-width: 1024px) { .grid { grid-template-columns: repeat(12, minmax(0, 1fr)); gap: var(--gap-grille); } }
.grid > * { min-width: 0; }

.col-2 { grid-column: span 2; } .col-3 { grid-column: span 3; }
.col-4 { grid-column: span 4; } .col-5 { grid-column: span 5; }
.col-6 { grid-column: span 6; } .col-7 { grid-column: span 7; }
.col-8 { grid-column: span 8; } .col-12{ grid-column: 1 / -1; }
@media (max-width: 767px) { [class*="col-"] { grid-column: 1 / -1; } }

/* Sortir de la grille : un choix assumé, jamais un accident. */
.full-bleed { width: 100vw; margin-left: 50%; transform: translateX(-50%); }

/* ------------------------------------------------- 4. Les sections -------- */
.section { padding-block: var(--pad-section); }
.section--sombre { background: var(--sombre); color: #fff; }
.section--sombre .lead,
.section--sombre p { color: rgb(255 255 255 / .82); }
.section--surface { background: var(--surface); }
.section + .section { border-top: 1px solid var(--ligne); }
.section--sombre + .section,
.section--sombre { border-top-color: var(--ligne-nuit); }

/* ------------------------------------------------- 5. La typographie ------ */
/* ⚠️ `overflow-wrap: break-word` : le plancher de `--fs-titre` est 52 px, et sur un
   écran de 320 px il ne reste que 272 px utiles. Un mot long (« aujourd'hui, »)
   sortait de sa boîte et poussait TOUTE la page à 336 px de large — un défilement
   horizontal sur chaque page, mesuré le 09/09/2026. Un mot coupé est laid ; une
   page qui glisse latéralement sur un téléphone est cassée. */
h1, h2, h3 { font-family: var(--f-titre); font-weight: 700; text-wrap: balance;
             overflow-wrap: break-word; }
h1 { font-size: var(--fs-titre); line-height: var(--lh-titre); letter-spacing: var(--ls-titre); }
/* ⚠️ 09/09/2026 : c'était `var(--fs-h1)` — une variable qui n'existe dans AUCUN
   fichier de tokens. Le H1 de tous les sites générés depuis le 07/08 se rendait
   donc à 19 px (la taille du corps), mesuré au navigateur. Le gate ne le voyait
   pas : il lit les DÉCLARATIONS de tokens.css, pas le rendu. Toute variable
   utilisée sans valeur de repli doit exister — sinon la règle entière est muette. */
h2 { font-size: var(--fs-h2); line-height: 1.12; letter-spacing: -.015em; }
h3 { font-size: var(--fs-h3); line-height: 1.2;  letter-spacing: -.01em; }

/* La règle de proximité : beaucoup avant un titre, peu après.
   Un titre appartient à ce qui le SUIT (09-bible-web/01 §2.2).               */
h2 { margin-block: var(--e-9) var(--e-4); }
h3 { margin-block: var(--e-6) var(--e-2); }
h2:first-child, h3:first-child { margin-top: 0; }

p, li { max-width: var(--w-texte); text-wrap: pretty; }
p + p { margin-top: var(--e-4); }
.lead { font-size: clamp(18px, 2vw, 21px); color: var(--encre-2); }

/* Le label en capitales : letter-spacing OBLIGATOIRE, sinon c'est illisible. */
.label {
  font-size: var(--fs-label); font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--ls-label);
  color: var(--encre-2);
}

a { color: inherit; text-underline-offset: 3px; text-decoration-thickness: 1px; }
strong, b { font-weight: 700; }

/* Les listes à puce du contenu (celles qui n'ont pas de classe) */
:where(ul:not([class]), ol:not([class])) { padding-left: 1.2em; margin-block: 0 var(--e-4); }
:where(ul:not([class]), ol:not([class])) li + li { margin-top: var(--e-2); }

/* ------------------------------------------------- 6. L'accessibilité ----- */
/* ⛔ I06 — un focus VISIBLE sur tout ce qui se navigue au clavier.           */
:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
  border-radius: 3px;
}
:focus:not(:focus-visible) { outline: none; }

/* Le lien d'évitement : première chose dans le <body>, invisible jusqu'au Tab */
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 999;
  padding: var(--e-3) var(--e-5); background: var(--marque); color: #fff;
}
.skip:focus { left: var(--e-3); top: var(--e-3); }

/* Masquer visuellement sans masquer pour un lecteur d'écran */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* ------------------------------------------------- 7. Les boutons --------- */
/* ⛔ I03 — 48px de haut minimum. Les 6 états sont tous définis : sans ça,
   un composant n'est pas livrable (09-bible-web/02 §7.3).                    */
.btn {
  position: relative; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--e-2);
  min-height: var(--min-cible);
  padding: var(--e-3) var(--e-6);
  border-radius: var(--r-cta);
  font-weight: 650; text-decoration: none; text-align: center;
  transition: transform var(--d-micro) var(--ease-sortie),
              box-shadow var(--d-micro) var(--ease-sortie),
              background  var(--d-micro),
              border-color var(--d-micro);
}
.btn:hover  { transform: translateY(-2px); box-shadow: var(--sh-2); }
.btn:active { transform: translateY(0);    box-shadow: var(--sh-1); transition-duration: 60ms; }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .5; pointer-events: none; }
.btn[data-loading] { pointer-events: none; }
.btn[data-loading]::after {
  content: ""; width: 15px; height: 15px; border-radius: 50%;
  border: 2px solid currentColor; border-top-color: transparent;
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.btn--primaire  { background: var(--marque); color: #fff; }
.btn--secondaire{ background: transparent; color: var(--encre);
                  border: 2px solid var(--encre); }
.btn--sombre    { background: #fff; color: var(--sombre); }

/* La flèche qui avance : le détail qui coûte 2 lignes et se voit. */
.btn svg { transition: transform var(--d-etat) var(--ease-retour); }
.btn:hover svg { transform: translateX(4px); }

/* ------------------------------------------------- 8. Les cartes ---------- */
.card {
  position: relative;                       /* pour un badge en absolu       */
  display: flex; flex-direction: column; gap: var(--e-4);
  padding: var(--e-5);
  background: var(--surface);
  border: 1px solid var(--ligne);
  border-radius: var(--r-bloc);
  overflow: hidden;                         /* le rayon s'applique à l'image */
}
@media (min-width: 768px) { .card { padding: var(--e-6); } }
.card__media { width: 100%; aspect-ratio: 4/3; object-fit: cover; }
.card__body  { flex: 1 1 auto; }            /* pousse le CTA en bas          */
.card__badge { position: absolute; top: var(--e-3); right: var(--e-3); }
.card--lien:hover { border-color: var(--marque); }

/* ------------------------------------------------- 9. Les formulaires ----- */
.field { display: flex; flex-direction: column; gap: var(--e-2); margin-bottom: var(--e-5); }
.field > label { font-weight: 600; font-size: var(--fs-small); }
.field input, .field textarea, .field select {
  min-height: var(--min-cible);
  padding: var(--e-3) var(--e-4);
  background: #fff; color: var(--encre);
  border: 2px solid var(--ligne); border-radius: var(--r-cta);
  transition: border-color var(--d-micro);
}
.field input:focus, .field textarea:focus { border-color: var(--marque); }
.field .aide { font-size: var(--fs-small); color: var(--encre-2); }

/* ⛔ I07 — l'erreur ne passe JAMAIS par la couleur seule : icône + texte.    */
.field.is-error input, .field.is-error textarea { border-color: var(--danger); }
.field.is-error .msg { color: var(--danger); font-size: var(--fs-small); font-weight: 600; }
.field.is-error .msg::before { content: "⚠ "; }
.field.is-error { animation: shake .35s both; }
@keyframes shake {
  10%,90% { transform: translateX(-2px) } 30%,70% { transform: translateX(4px) }
  50%     { transform: translateX(-4px) }
}

/* ------------------------------------------------- 10. La barre d'appel --- */
/* ⛔ SUPPRIMÉE le 20/09/2026. Saadou : « le truc qui reste tout le temps en bas de
   l'écran tu me l'enlèves, ON A DÉJÀ LE CTA DANS LA BARRE EN HAUT, PK T'EN METTRAIS
   UN EN BAS AUSSI ». Il a raison : la barre du haut est `position: sticky`, son
   bouton d'appel ne quitte jamais l'écran. Deux boutons d'appel permanents, c'est
   un bouton de trop et 84 px de page mangés en permanence.
   La doctrine (04 §6) demande UN CTA visible pendant tout le scroll : c'est tenu,
   par le haut. Ce qui reste interdit, c'est de n'en avoir aucun.
   Sur le gabarit Pro, les actions de la barre reviennent donc sur téléphone. */
.callbar { display: none !important; }

/* ------------------------------------------------- 11. Le reveal ---------- */
/* ⛔ I04 — la classe n'est JAMAIS dans le markup : c'est reveal.js qui pose
   `.js` sur <html>. Sans JS, rien n'est masqué. Failsafe à 1400 ms.          */
.js [data-reveal-item] {
  opacity: 0; transform: translateY(18px);
  transition: opacity var(--d-entree) var(--ease-expo),
              transform var(--d-entree) var(--ease-expo);
  transition-delay: var(--d, 0ms);
}
.js [data-reveal].is-in [data-reveal-item] { opacity: 1; transform: none; }

/* Variantes — 09-bible-web/02 §4. Une seule par section.                     */
.js [data-reveal-item].rv-scale  { transform: scale(.94); }
.js [data-reveal-item].rv-gauche { transform: translateX(-28px); }
.js [data-reveal-item].rv-droite { transform: translateX(28px); }
.js [data-reveal-item].rv-rideau { opacity: 1; clip-path: inset(0 0 100% 0);
                                   transition: clip-path .9s var(--ease-expo); }
.js [data-reveal].is-in [data-reveal-item].rv-rideau { clip-path: inset(0); }
.js [data-reveal-item].rv-trait  { opacity: 1; transform: scaleX(0); transform-origin: left; }
.js [data-reveal].is-in [data-reveal-item].rv-trait { transform: scaleX(1); }

/* ------------------------------- 11 bis. Les composants du catalogue ------ */
/* Tout ce qu'utilise sections/catalogue.html doit être stylé ICI. Un bloc du
   catalogue qui s'appuie sur une classe absente produit un site bancal dès la
   première génération — constaté au premier essai (nav sans espacement,
   paragraphes collés). */

/* Nav */
.nav { position: sticky; top: 0; z-index: 50; padding-block: var(--e-3);
       background: color-mix(in srgb, var(--fond) 88%, transparent);
       backdrop-filter: blur(10px); border-bottom: 1px solid var(--ligne); }
.marque { display: inline-flex; align-items: center; gap: var(--e-2);
          font-family: var(--f-titre); font-weight: 700; font-size: 20px;
          text-decoration: none; }
.nav__liens { display: flex; align-items: center; gap: var(--e-5); flex-wrap: wrap; }
.nav__liens a { text-decoration: none; font-weight: 600; font-size: var(--fs-small);
                padding-block: var(--e-2); }
.nav__liens a:hover { color: var(--marque); }
@media (max-width: 900px) { .nav { position: static; } .nav__liens { display: none; } }

/* Hero */
.hero { padding-bottom: var(--pad-section); }
.hero .label { display: block; margin-bottom: var(--e-3); }
.hero h1 { margin-bottom: var(--e-4); }

/* Espacements de texte courants — le « lead » respire, la mention légère aussi */
.lead { margin-bottom: var(--e-5); }
.aide { font-size: var(--fs-small); color: var(--encre-2); margin-top: var(--e-3); }
.section--sombre .aide { color: rgb(255 255 255 / .7); }

/* Citation */
blockquote { margin: 0 0 var(--e-4); padding: var(--e-4) var(--e-5);
             background: var(--surface); color: var(--encre);
             border-left: 4px solid var(--accent); border-radius: 0 var(--r-bloc) var(--r-bloc) 0; }

/* Figure / vidéo */
figure { margin: 0; }
/* ⛔ Le fond d'une figure n'est PAS noir. Tant que l'image ne s'est pas affichée — le temps
   du chargement, ou pour toujours si le fichier manque — c'est ce fond qu'on voit. Sur la
   grille des chantiers, huit carrés noirs à la place des huit preuves du travail : un visiteur
   en conclut que le site n'est pas fini. (Vu par un prospect le 17/09/2026.) */
figure video, figure img { width: 100%; border-radius: var(--r-img); background: var(--surface); }
figcaption { margin-top: var(--e-3); font-size: var(--fs-small); color: var(--encre-2); }
.section--sombre figcaption { color: rgb(255 255 255 / .7); }

/* FAQ — <details> natif : accessible, indexable, zéro JS */
.faq__item { border-bottom: 1px solid var(--ligne); }
.faq__item summary { display: flex; align-items: center; justify-content: space-between;
                     gap: var(--e-4); min-height: var(--min-cible);
                     padding-block: var(--e-3); cursor: pointer;
                     font-weight: 650; list-style: none; }
.faq__item summary::-webkit-details-marker { display: none; }
/* ═══════════════════════════════════════════════════════════════════════════
   LA FAQ QUI S'OUVRE ET QUI SE REFERME
   ---------------------------------------------------------------------------
   Saadou, 19/09 : « le texte est trop a gauche », « t'as pas fait une animation
   de retour ». Les deux etaient des fautes, plus une troisieme que j'ai vue en
   regardant : le « + » ouvert devenait une BARRE VERTICALE au lieu d'un moins.

   · L'ALIGNEMENT : la question se decalait de 16 px a l'ouverture, pas la
     reponse — qui restait collee au filet d'accent. Les deux bougent ensemble.
   · LE RETOUR : le CSS ouvrait sur [open] ET sur .is-ouvert. Quand le JS
     retirait .is-ouvert pour fermer en douceur, [open] tenait encore le volet
     ouvert : rien ne se fermait. Avec JS, c'est .is-ouvert qui decide, seule.
     Sans JS, [open] reprend la main et la FAQ s'ouvre d'un coup — elle marche.
   · LA CROIX : on tourne d'un DEMI-tour (180°), pas d'un quart. Une barre
     horizontale tournee de 90° devient verticale ; tournee de 180° elle reste
     horizontale. La barre verticale, elle, se retracte. On finit sur un moins.
   ═══════════════════════════════════════════════════════════════════════════ */
.faq__item { position: relative; max-width: 68ch;
             transition: background var(--d-etat) var(--ease-sortie); }
.faq__item::before { content: ""; position: absolute; left: 0; top: 0; width: 2px; height: 0;
                     background: var(--accent);
                     transition: height .45s var(--ease-expo); }

/* le decalage : question ET reponse se deplacent du meme pas */
.faq__item summary,
.faq__item .faq__body { padding-inline: 0; transition: padding-inline .45s var(--ease-expo); }

/* la croix : deux barres, pas un caractere « + » (qui saute selon la police) */
.faq__item summary::after { content: ""; flex: none; width: 15px; height: 15px;
                            background:
                              linear-gradient(currentColor 0 0) center/100% 2px no-repeat,
                              linear-gradient(currentColor 0 0) center/2px 100% no-repeat;
                            transition: transform .45s var(--ease-expo),
                                        background-size .45s var(--ease-expo); }

/* l'ouverture en hauteur : grid 0fr -> 1fr, la seule facon d'animer une hauteur
   « auto » sans mesurer au JS. */
.faq__body { display: grid; grid-template-rows: 0fr;
             /* FERMETURE : la hauteur attend que le texte soit parti (.14s) */
             transition: grid-template-rows .42s var(--ease-expo) .14s,
                         padding-inline .42s var(--ease-expo) .14s; }
.faq__body > * { overflow: hidden; min-height: 0;
                 transform: translateY(6px); opacity: 0;
                 /* FERMETURE : le texte part tout de suite, et vite */
                 transition: transform .18s ease-in, opacity .16s ease-in; }
.faq__body p { padding-bottom: var(--e-4); margin: 0; }

/* ── SANS JS : [open] fait tout, d'un coup ─────────────────────────────── */
.faq__item[open] .faq__body { grid-template-rows: 1fr; }
.faq__item[open] .faq__body > * { transform: none; opacity: 1; }
.faq__item[open]::before { height: 100%; }

/* ── AVEC JS : .is-ouvert decide, pour que la FERMETURE ait le temps de jouer ─ */
.js .faq__item[open] .faq__body { grid-template-rows: 0fr; }
.js .faq__item[open] .faq__body > * { transform: translateY(8px); opacity: 0; }
.js .faq__item[open]::before { height: 0; }

.js .faq__item.is-ouvert .faq__body {
  grid-template-rows: 1fr;
  transition: grid-template-rows .42s var(--ease-expo),
              padding-inline .42s var(--ease-expo); }
.js .faq__item.is-ouvert .faq__body > * {
  transform: none; opacity: 1;
  transition: transform .5s var(--ease-expo) .16s, opacity .34s ease .18s; }
.js .faq__item.is-ouvert::before { height: 100%; }
.js .faq__item.is-ouvert summary,
.js .faq__item.is-ouvert .faq__body,
.faq__item[open] summary, .faq__item[open] .faq__body,
.faq__item summary:hover { padding-inline: var(--e-4) 0; }
.js .faq__item[open]:not(.is-ouvert) summary,
.js .faq__item[open]:not(.is-ouvert) .faq__body { padding-inline: 0; }

/* la croix devient un moins : demi-tour, et la barre verticale se retracte */
.js .faq__item.is-ouvert summary::after,
.faq__item[open] summary::after {
  transform: rotate(180deg);
  background: linear-gradient(currentColor 0 0) center/100% 2px no-repeat,
              linear-gradient(currentColor 0 0) center/2px 0 no-repeat;
}
.js .faq__item[open]:not(.is-ouvert) summary::after {
  transform: none;
  background: linear-gradient(currentColor 0 0) center/100% 2px no-repeat,
              linear-gradient(currentColor 0 0) center/2px 100% no-repeat;
}

@media (prefers-reduced-motion: reduce) {
  .faq__item::before, .faq__item summary, .faq__item summary::after,
  .faq__body, .faq__body > * { transition: none !important; }
  .faq__body > * { transform: none; opacity: 1; }
}

/* Process — la liste ordonnée sans puces */
ol.grid { list-style: none; padding: 0; counter-reset: etape; }
ol.grid > li { max-width: none; }

/* Zone d'intervention — la liste de villes EST ce qui référence */
.section ul.row { list-style: none; padding: 0; }
.section ul.row li { max-width: none; padding: var(--e-2) var(--e-4);
                     border: 1px solid var(--ligne); border-radius: var(--r-cta);
                     font-size: var(--fs-small); font-weight: 600; }

/* Tableaux (pages légales) */
table { width: 100%; border-collapse: collapse; margin-bottom: var(--e-5);
        font-size: var(--fs-small); font-variant-numeric: tabular-nums; }
th, td { text-align: left; padding: var(--e-3); border-bottom: 1px solid var(--ligne);
         vertical-align: top; }
th { font-weight: 700; }

/* Le bloc de contenu propre à la page : ses paragraphes gardent la largeur de lecture */
.section > .wrap > div > p { margin-bottom: var(--e-4); }

/* ------------------------------------------------- 12. Utilitaires -------- */
.stack > * + * { margin-top: var(--e-4); }
.stack-l > * + * { margin-top: var(--e-6); }
.row { display: flex; align-items: center; gap: var(--e-4); flex-wrap: wrap; }
.between { display: flex; align-items: center; justify-content: space-between;
           gap: var(--e-4); flex-wrap: wrap; }
.center { text-align: center; margin-inline: auto; }
.nums { font-variant-numeric: tabular-nums; font-family: var(--f-chiffre); }
.hr { border: 0; border-top: 1px solid var(--ligne); margin-block: var(--e-8); }

/* Impression : on retire le décor, on garde le contenu et les URL. */
@media print {
  .callbar, .nav, nav, video, canvas { display: none !important; }
  body { font-size: 12pt; color: #000; background: #fff; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 10pt; }
}


/* ============================================================================
   BLOC C-QUATER — LES COMPOSANTS DE LA REFONTE DU 17/08/2026
   Ils remplacent trois blocs qui étaient des grilles de colonnes égales :
   `services` (3 cartes), `reassurance` (4 colonnes), `process` (3 colonnes).
   Motif : les cartes égales alignées sont le marqueur générique n°1, et le gate
   C52 les refuse désormais. Aucun ne perd d'information — même densité,
   hiérarchie en plus.
   ============================================================================ */

/* ── LA LISTE DES SERVICES — remplace les trois cartes ────────────────────── */
.liste-services { margin-top: clamp(46px, 7vw, 86px); border-top: 1px solid var(--ligne); }

.ls-item {
  display: grid; grid-template-columns: auto 1fr auto;
  gap: clamp(16px, 3vw, 40px); align-items: baseline;
  border-bottom: 1px solid var(--ligne);
  padding-block: clamp(24px, 3.4vw, 42px);
  text-decoration: none; color: inherit;
  position: relative; isolation: isolate;
}
.ls-num {
  font-size: var(--fs-label); letter-spacing: var(--ls-label); color: var(--encre-2);
  font-variant-numeric: tabular-nums; padding-top: .9em;
}
.ls-titre {
  font-family: var(--f-titre); font-size: var(--fs-h3);
  line-height: 1; letter-spacing: var(--ls-h2); margin: 0;
}
.ls-benef { color: var(--encre-2); font-size: var(--fs-small); margin: 11px 0 0; max-width: 44ch; }
.ls-prix  { font-size: var(--fs-small); color: var(--encre-2);
            white-space: nowrap; font-variant-numeric: tabular-nums; padding-top: .7em; }

/* Le voile sombre au survol : peint une fois sur un pseudo-élément, on n'anime
   QUE son opacity. Travail compositeur, aucun repeint. (02-animations §15) */
.ls-item::before {
  content: ""; position: absolute; inset-block: 0; inset-inline: -3vw; z-index: -1;
  background: var(--sombre); opacity: 0;
  transition: opacity var(--d-micro) var(--ease-sortie);
}
.ls-item .ls-titre, .ls-item .ls-benef,
.ls-item .ls-prix, .ls-item .ls-num { transition: color var(--d-micro); }
/* La flèche est une AFFORDANCE, pas un ornement : elle apparaît sur l'item qu'on
   survole, jamais sur les dix à la fois (règle D3, appliquée le 17/09/2026). Elle est
   dessinée ici, en pseudo-élément, pour qu'aucune flèche ne traîne dans le HTML. */
.ls-titre::after { content: ""; display: inline-block; width: 0; margin-left: 0;
                   color: var(--accent); opacity: 0;
                   transition: opacity var(--d-micro), transform var(--d-micro); }

@media (hover: hover) {
  .ls-item:hover::before { opacity: 1; }
  a.ls-item:hover .ls-titre { color: #fff; }
  a.ls-item:hover .ls-benef,
  a.ls-item:hover .ls-prix,
  a.ls-item:hover .ls-num { color: rgb(255 255 255 / .72); }
  a.ls-item:hover .ls-titre::after, .ls-item:focus-visible .ls-titre::after {
    content: "\2192"; width: auto; margin-left: .4em; opacity: 1; transform: translateX(6px); }
}

/* Sur mobile il n'y a pas de survol : la preuve du chantier passe DANS la rangée.
   On ne perd jamais la photo. */
.ls-vignette { display: none; }
@media (max-width: 760px) {
  .ls-item { grid-template-columns: 1fr auto; gap: 14px; margin-left: 0 !important; }
  .ls-num { display: none; }
  .ls-prix { grid-column: 2; grid-row: 1; }
  .ls-vignette { display: block; grid-column: 1 / -1; width: 100%;
                 aspect-ratio: 16/9; object-fit: cover; margin-top: var(--e-4);
                 border-radius: var(--r-img); }
}

/* ── LA BANDE DE FAITS — remplace les 4 colonnes égales ───────────────────── */
/* Pleine largeur : c'est elle qui casse la grille au moins une fois (gate C51). */
.bande-faits {
  background: var(--sombre); color: #fff;
  padding-block: var(--e-7);
  margin-inline: calc(50% - 50vw); width: 100vw;
}
.bf-piste {
  max-width: var(--w-max); margin-inline: auto; padding-inline: var(--marge-bord);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--e-5) 0;
}
/* Les quatre faits n'ont PAS la même largeur : le premier est le plus fort. */
.bf-fait {
  margin: 0; padding-inline: var(--e-5); flex: 1 1 200px;
  font-size: var(--fs-small); color: rgb(255 255 255 / .78);
  border-left: 1px solid var(--ligne-nuit);
}
.bf-fait:first-child { padding-left: 0; border-left: 0; }
.bf-fait--fort { flex: 1.6 1 260px; font-size: var(--fs-body); color: #fff; }
.bf-fait--fort b { color: var(--accent); }
/* le picto : vif et petit, jamais plus gros que la ligne qu'il illustre */
.bf-ico { display: block; width: 19px; height: 19px; margin-bottom: 7px;
          fill: var(--accent); flex: none; }
.bf-fait--fort .bf-ico { width: 22px; height: 22px; }

/* ⭐ 20/09/2026, Saadou : « sur telephone, cette partie-la je l'aimerais plus EN
   LIGNE qu'en colonne ; t'as qu'a reduire les distances avec les cotes ».
   Empiles, les quatre faits mangeaient 357 px, un fait par ligne. Deux colonnes,
   marges resserrees : la bande se lit d'un coup, comme une plaque de chiffres. */
/* le conteneur des tableaux legaux : ils defilent, la page non */
/* ⛔ MESURE du 20/09 : une URL ecrite en entier sortait la page de mentions
   legales a 408 px sur un ecran de 320. Une adresse n a pas de coupure
   naturelle : il faut l autoriser explicitement. */
main a { overflow-wrap: anywhere; }
.tab-legale { max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.tab-legale table { min-width: 34rem; }

/* ⭐ 20/09/2026, Saadou : « sur telephone, mets du 2 par 2, dans des petits carres,
   le truc en dessous de la section 1 ». Quatre cases egales, pas une vedette :
   c'est une plaque de chiffres, chacun porte le meme poids. */
@media (max-width: 760px) {
  .bf-piste { display: grid; grid-template-columns: 1fr 1fr;
              gap: 10px; padding-inline: var(--e-4); align-items: stretch; }
  /* ⛔ MESURE du 20/09 : en `display: flex` + `column`, chaque morceau de contenu
     de la ligne devenait un item a part et le texte sortait un mot par ligne
     (« Visite et devis : » / « 0 » / « € »). Une carte de fait est un BLOC de
     texte, pas une colonne flex. */
  .bf-fait, .bf-fait--fort { flex: none; grid-column: auto; border: 0;
                             display: block;
                             min-height: 92px; padding: 12px 13px;
                             border-radius: var(--r-img);
                             background: rgb(255 255 255 / .07);
                             font-size: 14px; line-height: 1.26; color: #fff; }
  .bf-fait--fort b { color: var(--accent); }
  .bf-ico { width: 18px; height: 18px; margin-bottom: 7px; }
}

/* ── LA CHAÎNE DU PROCESS — remplace les 3 colonnes ───────────────────────── */
/* Un process est SÉQUENTIEL. Trois cases côte à côte disent « trois choses
   parallèles » : c'est un contresens, pas seulement une question de goût. */
/* ⚠️ `ol.chaine` et non `.chaine` : la ligne 20 pose `ul[class], ol[class]
   { margin: 0 }` — spécificité 0,1,1. Une simple classe (0,1,0) perdait, et
   la marge n'était jamais appliquée (constaté à la mesure, 17/08). */
ol.chaine { list-style: none; margin: clamp(56px, 8vw, 100px) 0 0; padding: 0; }
.ch-etape {
  display: grid; grid-template-columns: auto 1fr; gap: clamp(20px, 3vw, 44px);
  padding-bottom: clamp(28px, 4vw, 56px); position: relative;
}
.ch-num {
  font-family: var(--f-chiffre); font-size: var(--fs-h3); line-height: 1;
  letter-spacing: var(--ls-h2); color: var(--accent);
  font-variant-numeric: tabular-nums;
  /* ⚠️ PAS la taille du H2 : un interligne serré sur un si grand corps
     déborde de sa boîte et percute le titre de section (vu au rendu 17/08). */
}
/* le filet qui relie une étape à la suivante — c'est lui qui dit « chaîne » */
.ch-etape::before {
  content: ""; position: absolute; left: .62em; top: 1.9em; bottom: 0;
  width: 1px; background: var(--ligne);
}
.ch-etape:last-child::before { display: none; }
.ch-corps h3 { margin: 0 0 var(--e-2); }
.ch-corps p  { margin: 0; color: var(--encre-2); max-width: 52ch; }
.ch-delai    { margin-top: var(--e-2) !important; font-size: var(--fs-label);
               letter-spacing: var(--ls-label); text-transform: uppercase;
               color: var(--encre) !important; }

/* ============================================================================
   13. LES COMPOSANTS DE LA DÉMO PRO v4        — entrés dans le socle le 09/09/2026
   ----------------------------------------------------------------------------
   Validés par Saadou le 08/09/2026 (« les règles ne sont plus des hypothèses
   mais validées »), en production sur krescia.com/demos/plombier-pro/.
   Comme tout le reste de ce fichier : AUCUN choix esthétique ici, uniquement
   des tokens. Un site change d'allure en changeant `tokens.css`, jamais celui-ci.
   ============================================================================ */

/* ── LA BARRE DU HAUT — menu à gauche, marque au centre, actions à droite ─── */
/* Disposition arrêtée le 09/09 après trois essais. Ce qui l'a emporté : les deux
   CTA restent à droite quel que soit le nombre de pages, parce que le menu est
   UN bouton et non une rangée de liens qui pousse tout dehors. */
.barre { position: sticky; top: 0; z-index: 50;
         background: color-mix(in srgb, var(--fond) 92%, transparent);
         backdrop-filter: blur(10px); border-bottom: 1px solid var(--ligne); }
.barre__rang { display: grid; grid-template-columns: 1fr auto 1fr;
               align-items: center; gap: var(--e-4); padding-block: var(--e-3); }
.barre__menu { justify-self: start; display: inline-flex; align-items: center;
               gap: var(--e-2); min-height: var(--min-cible);
               padding: 0 var(--e-3); background: none; border: 1px solid var(--ligne);
               border-radius: var(--r-cta); cursor: pointer; color: inherit;
               font: inherit; font-size: var(--fs-small); font-weight: 650; }
.barre__menu:hover { border-color: var(--marque); }
.barre__traits { display: block; width: 16px; height: 8px;
                 border-top: 2px solid currentColor; border-bottom: 2px solid currentColor; }
.barre__marque { justify-self: center; display: inline-flex; align-items: center;
                 gap: var(--e-3); text-decoration: none; text-align: left; }
.barre__marque b { display: block; font-family: var(--f-titre); font-size: 20px;
                   line-height: 1.1; letter-spacing: var(--ls-h2); }
.barre__marque small { display: block; font-size: var(--fs-label);
                       letter-spacing: var(--ls-label); text-transform: uppercase;
                       color: var(--encre-2); }
.barre__actions { justify-self: end; display: flex; align-items: center; gap: var(--e-3); }
/* Un numéro de téléphone ne se coupe pas en deux lignes : c'est LE geste de la page.
   (Vu au rendu le 17/09/2026 sur Porteclose, à 1440.) */
.barre__actions .btn { white-space: nowrap; }
/* ⛔ Le panneau est OUVERT par défaut : sans JS, les six liens restent dans la
   page, cliquables et suivis. C'est `reveal.js` qui pose `.est-replie`. */
.barre__panneau { border-top: 1px solid var(--ligne); }
.barre__panneau > .wrap { display: grid; gap: var(--e-3);
                          grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
                          padding-block: var(--e-5); }
.barre__panneau a { text-decoration: none; font-weight: 650;
                    padding: var(--e-3); border: 1px solid var(--ligne);
                    border-radius: var(--r-bloc);
                    transition: border-color var(--d-micro) var(--ease-sortie); }
.barre__panneau a:hover { border-color: var(--marque); }
.barre__panneau small { display: block; margin-top: var(--e-1);
                        font-weight: 400; font-size: var(--fs-small); color: var(--encre-2); }
.barre__panneau.est-replie { display: none; }
/* ⛔ SUR TÉLÉPHONE, PAS DE MENU (décision Saadou, 09/09/2026).
   La barre du haut se réduit au blason et au nom. Le bouton Menu et son panneau
   disparaissent : sur un écran de 390 px, « Menu » + la marque + les deux CTA se
   marchaient dessus et la marque passait sur deux lignes (mesuré le 09/09).
   Ce qu'ils portaient est repris ailleurs, sans rien perdre :
     · l'ACTION  → les boutons restent dans la barre du haut, qui est collante ;
     · la NAVIGATION → le pied de page, qui liste les mêmes pages.
   Un visiteur sur téléphone qui a une fuite ne cherche pas un menu : il appelle. */
@media (max-width: 900px) {
  .barre { position: static; }
  .barre__rang { grid-template-columns: 1fr; justify-items: start; }
  .barre__menu,
  .barre__panneau { display: none; }
  /* 20/09/2026 : la barre d'appel du bas n'existe plus, les actions reviennent ici. */
  .barre__actions { display: flex; gap: var(--e-3); }
  .barre__actions .btn { min-height: 44px; }
  .barre__marque { justify-self: start; }
}

/* ── LE HERO À DEUX PORTES ────────────────────────────────────────────────── */
.hero-p { padding-block: var(--pad-section) 0; }
.hero-p__haut { display: grid; gap: clamp(24px, 4vw, 64px);
                grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
                align-items: end; }
.estampille { display: inline-block; margin-bottom: var(--e-4);
              font-size: var(--fs-label); letter-spacing: var(--ls-label);
              text-transform: uppercase; color: var(--encre-2);
              border: 1px solid var(--ligne); border-radius: 999px;
              padding: var(--e-2) var(--e-4); }
/* Le H1 dit le BÉNÉFICE ; le métier et la ville descendent d'un cran (règle du
   07/09). Ils restent dans le <h1> : Google lit la ligne entière. */
/* Le H1 du hero à deux portes n'est PAS le H1 pleine largeur : il partage sa
   ligne avec une photo, dans une colonne d'environ 570 px. À `--fs-titre`
   (168 px) il fait 997 px de haut et écrase tout (mesuré le 09/09). Il reste
   le plus grand texte de la page — juste au-dessus du H2 de section (76 px). */
.hero-p h1 { font-size: clamp(40px, 5.4vw, 78px); margin-bottom: var(--e-4); }
.hero-p__l2 { display: block; margin-top: var(--e-3);
              font-size: var(--fs-h3); line-height: 1.15;
              letter-spacing: var(--ls-h2); color: var(--encre-2); }
.hero-p__preuve { display: flex; flex-wrap: wrap; align-items: center;
                  gap: var(--e-3) var(--e-5); margin: var(--e-4) 0 0;
                  font-size: var(--fs-small); }
.hero-p__preuve a { text-decoration: none; }
.hero-p__preuve b { font-variant-numeric: tabular-nums; }
.etoiles { color: var(--accent); letter-spacing: .08em; }
.hero-p__photo { margin: 0; }
/* ⚠️ `height: auto` OBLIGATOIRE avec `aspect-ratio` : les attributs width/height
   de l'<img> (qu'on met pour réserver la place et éviter le décalage au chargement)
   sont des hints CSS et une hauteur explicite bat l'aspect-ratio. Sans cette ligne,
   la photo du hero sortait à 516x800 au lieu de 516x344 (mesuré le 09/09/2026). */
.hero-p__photo img { width: 100%; height: auto; aspect-ratio: 3/2; object-fit: cover;
                     border-radius: var(--r-img); }

/* Les deux portes. Elles n'ont PAS la même surface : l'urgence est la plus
   sombre et la plus lourde, parce que c'est elle qui rapporte le jour même. */
.portes { display: grid; gap: var(--e-4); margin-block: clamp(28px, 4vw, 56px);
          grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); }
/* ⚠️ `a.porte`, pas `.porte` : le gate refuse tout survol dont le sélecteur ne dit
   pas que la cible est cliquable (I36 / C40). Ce n'est pas une coquetterie du gate —
   un survol sur un bloc non cliquable promet un clic qui n'existe pas. Ici la porte
   EST un lien : on l'écrit. */
a.porte { display: block; padding: clamp(20px, 2.4vw, 32px);
          border-radius: var(--r-bloc); text-decoration: none;
          min-height: var(--min-cible);
          transition: transform var(--d-micro) var(--ease-sortie); }
a.porte:hover { transform: translateY(-2px); }
.porte b { display: block; font-family: var(--f-titre); font-size: var(--fs-h3);
           line-height: 1.1; letter-spacing: var(--ls-h2); margin-bottom: var(--e-2); }
.porte span { display: block; font-size: var(--fs-small); }
.porte--urgence { background: var(--sombre); color: #fff; }
.porte--urgence span { color: rgb(255 255 255 / .74); }
.porte--projet { background: var(--surface); color: var(--encre);
                 border: 1px solid var(--ligne); }
.porte--projet span { color: var(--encre-2); }
@media (max-width: 900px) {
  .hero-p__haut, .portes { grid-template-columns: 1fr; }
}

/* ── LA BANDE DE LOGOS QUI DÉFILE ─────────────────────────────────────────── */
/* Pleine largeur : elle participe à la casse de grille (gate C51). */
.logos { padding-block: var(--e-7); border-block: 1px solid var(--ligne);
         margin-inline: calc(50% - 50vw); width: 100vw; overflow: hidden; }
.logos__titre { text-align: center; color: var(--encre-2); margin-bottom: var(--e-5); }
.logos__piste { display: flex; width: max-content; }
.logos__rail { display: flex; align-items: center; gap: clamp(40px, 6vw, 88px);
               padding-inline: clamp(20px, 3vw, 44px);
               animation: logos-defile 38s linear infinite; }
.logos__rail img { height: 40px; width: auto; opacity: .58; filter: grayscale(1); }
/* ⛔ AUCUN survol sur les logos : ils ne sont pas cliquables (I36, gate C40).
   Un élément qui réagit au survol promet un clic ; ici il n'y en a pas.
   Le seul survol admis est la PAUSE du défilement, qui n'est pas un affordance
   de clic mais le droit de lire (WCAG 2.2.2 : toute animation en boucle doit
   pouvoir s'arrêter). */
.logos__piste:hover .logos__rail { animation-play-state: paused; }
@keyframes logos-defile { to { transform: translateX(-50%); } }
/* I05 : le garde-fou global coupe déjà les animations en reduced-motion, mais
   une boucle infinie mérite d'être coupée explicitement — elle ne s'arrête jamais. */
@media (prefers-reduced-motion: reduce) { .logos__rail { animation: none; } }

/* ── LA PHOTO PLEIN CADRE ─────────────────────────────────────────────────── */
/* Mesure du 05/09 : les sites qu'on a retenus donnent 45 à 90 % d'un écran à
   leur plus grande photo. `min-height: 78vh` est le plancher qui tient ça. */
.plein { position: relative; margin-inline: calc(50% - 50vw); width: 100vw;
         min-height: 78vh; display: grid; overflow: hidden;
         margin-block: var(--pad-section); }
.plein > img { grid-area: 1/1; width: 100%; height: 100%; object-fit: cover; }
.plein__texte { grid-area: 1/1; position: relative; display: flex; align-items: end;
                padding-block: clamp(32px, 5vw, 72px);
                background: linear-gradient(to top,
                            rgb(0 0 0 / .62) 0%, rgb(0 0 0 / .22) 42%, rgb(0 0 0 / 0) 72%); }
/* Le voile tombait à 74 % de noir : sur une photo déjà sombre, on ne voyait plus le travail,
   et c'est justement l'image qui doit faire envie. Ramené à 62 % le 17/09/2026 ; le texte
   posé dessus reste au-dessus de 7:1, vérifié. */
.plein__texte > .wrap { display: grid; gap: var(--e-5);
                        grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
                        align-items: end; width: 100%; }
.plein__texte h2 { color: #fff; margin-bottom: var(--e-4); }
.plein__chiffres { display: flex; flex-wrap: wrap; gap: var(--e-5) var(--e-7); }
.plein__chiffres b { display: block; font-family: var(--f-chiffre);
                     font-size: var(--fs-h3); line-height: 1; color: #fff;
                     font-variant-numeric: tabular-nums; }
.plein__chiffres span { display: block; margin-top: var(--e-2);
                        font-size: var(--fs-label); letter-spacing: var(--ls-label);
                        text-transform: uppercase; color: rgb(255 255 255 / .74); }
@media (max-width: 900px) { .plein__texte > .wrap { grid-template-columns: 1fr; } }

/* Le dé-zoom à la révélation. ⚠️ PIÈGE DU 05/09 : la transformation se pose sur
   l'IMAGE, l'observateur regarde la SECTION. Une image rognée à 100 % n'entre
   jamais en intersection — les photos plein cadre de la v3 sont restées
   invisibles pour toujours. D'où `[data-reveal]` parent, jamais l'image seule. */
.js [data-reveal-item].rv-dezoom { opacity: 1; transform: scale(1.08);
                                   transition: transform 1.2s var(--ease-expo); }
.js [data-reveal].is-in [data-reveal-item].rv-dezoom { transform: scale(1); }

/* Le bouton clair — la seule variante posée SUR une photo. */
.btn--clair { background: var(--fond); color: var(--encre); border: 0; }
.btn--clair:hover { background: #fff; }

/* ── LES CHANTIERS DATÉS ET SITUÉS ────────────────────────────────────────── */
/* Volontairement PAS des cartes égales (gate C52) : pas de fond, pas de bordure,
   la photo et deux lignes. Le lieu est en gras — c'est lui la preuve. */
/* LA MOSAÏQUE, PAS HUIT CADRES IDENTIQUES. Huit tuiles 4:3 alignées sont le marqueur de
   gabarit que la règle D4 vise, et la wireframe Porteclose du 17/09 demandait explicitement
   une « mosaïque irrégulière ». Le premier chantier prend deux colonnes et deux rangs : c'est
   la preuve principale, elle a la place de l'être. Les autres alternent portrait et paysage. */
.chantiers { display: grid; gap: var(--e-5); margin-top: var(--e-7);
             grid-template-columns: repeat(4, minmax(0, 1fr)); grid-auto-rows: auto;
             grid-auto-flow: dense; align-items: start; }
.chantiers figure img { height: auto; aspect-ratio: 4/3; object-fit: cover; }
.chantiers > figure:first-child { grid-column: span 2; grid-row: span 2; }
.chantiers > figure:first-child img { aspect-ratio: 1 / 1; }
.chantiers > figure:nth-child(4) img,
.chantiers > figure:nth-child(7) img { aspect-ratio: 3 / 4; }
/* Le dernier prend la place qui reste : une mosaique ne finit pas sur un trou. */
.chantiers > figure:last-child { grid-column: span 2; }
.chantiers > figure:last-child img { aspect-ratio: 16 / 9; }
@media (max-width: 1100px) { .chantiers { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.chantiers figcaption { display: flex; flex-wrap: wrap; gap: var(--e-2);
                        justify-content: space-between; }
.chantiers figcaption b { color: var(--encre); }

/* ── LA RÈGLE DE LA MAISON — les engagements ──────────────────────────────── */
/* Une chaîne de gestes, pas quatre colonnes. Le mot-clé sort dans la marge :
   c'est ce qui rend la liste lisible en diagonale. */
.engage { margin-top: var(--e-7); border-top: 1px solid var(--ligne); }
.engage > div { display: grid; grid-template-columns: minmax(0, 148px) minmax(0, 1fr);
                gap: var(--e-3) var(--e-6);
                padding-block: clamp(20px, 2.6vw, 36px);
                border-bottom: 1px solid var(--ligne); }
.engage > div > b { font-size: var(--fs-label); letter-spacing: var(--ls-label);
                    text-transform: uppercase; color: var(--accent);
                    padding-top: var(--e-2); }
.engage h3 { margin: 0 0 var(--e-2); }
.engage p { margin: 0; color: var(--encre-2); max-width: 62ch; }
@media (max-width: 760px) { .engage > div { grid-template-columns: 1fr; } }

/* ── L'HÉRITAGE, EN RÉCIT ─────────────────────────────────────────────────── */
.heritage { display: grid; gap: clamp(24px, 4vw, 64px);
            grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr); align-items: center; }
.heritage__photo { position: relative; overflow: hidden; border-radius: var(--r-img); }
.heritage__photo img { height: auto; aspect-ratio: 3/2; object-fit: cover; }
.heritage__badge { position: absolute; left: var(--e-4); bottom: var(--e-4);
                   margin: 0; padding: var(--e-2) var(--e-4);
                   background: var(--sombre); color: #fff;
                   font-size: var(--fs-label); letter-spacing: var(--ls-label);
                   text-transform: uppercase; border-radius: var(--r-bloc); }
.heritage p { max-width: 62ch; margin-bottom: var(--e-4); }
@media (max-width: 900px) { .heritage { grid-template-columns: 1fr; } }

/* ── LA ZONE — vraie carte, rayon dessiné ─────────────────────────────────── */
.zone-c { display: grid; gap: clamp(24px, 4vw, 64px);
          grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: center; }
.zone-c__carte { position: relative; aspect-ratio: 1; border-radius: var(--r-img);
                 overflow: hidden; border: 1px solid var(--ligne); }
.zone-c__carte > img { width: 100%; height: 100%; object-fit: cover; }
.zone-c__carte > svg { position: absolute; inset: 0; width: 100%; height: 100%; }
/* ⭐ 20/09/2026, Saadou : « fais une animation avec la zone d'intervention, genre le
   rayon qui grossit jusqu'à atteindre ces 30 km ».
   Le cercle part du point de Civry et s'ouvre au fil du scroll ; le compteur suit
   en kilomètres ; et chaque commune de la liste s'allume quand le rayon l'a
   rejointe. C'est le seul mouvement de la section, et il dit exactement ce que la
   section raconte : jusqu'où il se déplace.
   ⚠️ `--rayon` est posé par reveal.js entre 0 et 1. Sans JS il vaut 1 : le cercle
   est dessiné en entier, la carte reste juste. */
.zone-c__rayon { fill: color-mix(in srgb, var(--accent) 12%, transparent);
                 stroke: var(--accent); stroke-width: 3; vector-effect: non-scaling-stroke;
                 stroke-dasharray: 14 9; stroke-linecap: round;
                 transform-origin: center; transform-box: fill-box; }
.js .zone-c__rayon { transform: scale(var(--rayon, 1)); opacity: calc(.15 + var(--rayon, 1)); }
/* le compteur, posé sur la carte */
.zone-c__km { position: absolute; z-index: 2; left: 50%; top: 4.6%;
              transform: translateX(-50%); margin: 0;
              display: inline-flex; align-items: baseline; gap: .34em;
              padding: 7px 17px; border-radius: 999px;
              /* ⛔ gate C21 : `--accent` peut etre un bleu CLAIR (chez Porteclose
                 #7CC4F5, soit 1,90:1 avec du blanc). Une pastille porte du texte :
                 elle prend la couleur de MARQUE, pas l'accent. */
              background: var(--marque); color: #fff; white-space: nowrap;
              font-size: var(--fs-small); font-weight: 650; line-height: 1.1;
              box-shadow: 0 2px 10px rgb(0 0 0 / .18); }
.zone-c__km b { font-family: var(--f-chiffre); font-variant-numeric: tabular-nums;
                font-size: 1.22em; font-weight: 800; }
/* les communes s'allument quand le rayon les a rejointes. `--part` est la part du
   temps de route le plus long : c'est donc l'ordre dans lequel le cercle les touche. */
/* ⚠️ MESURE du 20/09 : la commune la plus lointaine a `--part: 1`. Avec une
   difference nue, elle restait a 0,34 meme rayon plein. On ajoute la rampe
   (4 %) DANS la soustraction : la commune s'allume pile quand le rayon l'atteint. */
.js .zone-c__temps li { opacity: clamp(.34, calc(.34 + (var(--rayon, 1) - var(--part, 0) + .04) * 18), 1);
                        transition: opacity .28s linear; }
@media (prefers-reduced-motion: reduce) {
  .js .zone-c__rayon { transform: none; opacity: 1; }
  .js .zone-c__temps li { opacity: 1; }
}
/* Les communes : une pastille + son nom, posées en pourcentages de la carte. */
.zone-c__pt { position: absolute; transform: translate(-50%, -50%);
              display: flex; align-items: center; gap: var(--e-2);
              font-size: var(--fs-small); font-weight: 650; white-space: nowrap; }
.zone-c__pt::before { content: ""; width: 8px; height: 8px; border-radius: 50%;
                      background: var(--encre); box-shadow: 0 0 0 3px var(--fond); }
.zone-c__pt--hub::before { width: 12px; height: 12px; background: var(--accent); }
/* Une carte de 370 px ne porte pas neuf etiquettes lisibles. Sous 760 px on ne garde que
   le point de depart et les extremes ; ecrire plus petit tomberait sous le plancher de
   lisibilite (gate C20). Reglee le 17/09/2026. */
@media (max-width: 760px) { .zone-c__pt--large { display: none; } }
.zone-c__temps { list-style: none; padding: 0; margin: var(--e-5) 0 0;
                 display: grid; gap: 0; }
.zone-c__temps li { display: flex; justify-content: space-between; gap: var(--e-4);
                    padding-block: var(--e-3); border-bottom: 1px solid var(--ligne);
                    max-width: none; font-size: var(--fs-small); }
.zone-c__temps b { font-variant-numeric: tabular-nums; }
@media (max-width: 900px) { .zone-c { grid-template-columns: 1fr; } }

/* ── LE SIMULATEUR D'AIDES ────────────────────────────────────────────────── */
.simu { margin-top: var(--e-7); padding: clamp(20px, 3vw, 40px);
        background: var(--surface); border: 1px solid var(--ligne);
        border-radius: var(--r-bloc); }
.simu__choix { display: grid; gap: var(--e-5);
               grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); }
/* ⚠️ Un <select> se dimensionne sur son option la plus longue et IGNORE la largeur
   de son parent : « Très modestes (jusqu'à 25 000 € pour 2 personnes) » sortait la
   page à 336 px sur un écran de 320 (mesuré le 09/09/2026). `width: 100%` + `min-width: 0`
   sur le parent flex/grid, sinon le contrôle reste plus large que sa colonne. */
.simu__choix p { margin: 0; min-width: 0; }
.simu__choix select, .simu__choix label { display: block; width: 100%; max-width: 100%; }
.simu__res { display: grid; gap: var(--e-5); margin-top: var(--e-6);
             padding-top: var(--e-6); border-top: 1px solid var(--ligne);
             grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); }
.simu__res p { margin: 0; }
.simu__res b { display: block; font-family: var(--f-chiffre); font-size: var(--fs-h3);
               line-height: 1.05; letter-spacing: var(--ls-h2);
               font-variant-numeric: tabular-nums; }
.simu__res .simu__aide { color: var(--accent); }
.simu__res span { display: block; margin-top: var(--e-2); font-size: var(--fs-small);
                  color: var(--encre-2); }

/* ── CE QUI MANQUE ────────────────────────────────────────────────────────────
   Un visuel qu'on n'a pas encore ne devient jamais une image de banque : il devient
   ce cadre. Il est visible, il se compte au gate, et il ne peut pas partir en prod
   sans qu'on le voie. Règle du chantier Porteclose, 17/09/2026. */
.manque { display: grid; place-content: center; gap: var(--e-2); text-align: center;
          aspect-ratio: 3 / 2; margin: 0; padding: var(--e-5);
          border: 2px dashed #C0392B; border-radius: var(--r-img);
          background: repeating-linear-gradient(135deg, transparent 0 14px,
                      color-mix(in srgb, #C0392B 7%, transparent) 14px 28px); }
.manque b { color: #C0392B; font-size: var(--fs-small); letter-spacing: .08em;
            text-transform: uppercase; }
.manque span { color: var(--encre-2); font-size: var(--fs-small); max-width: 36ch; }
/* Sur la bande sombre, le rouge du papier tombe a 1,6:1 : on passe au rouge clair. */
.section--sombre .manque { border-color: #FF8A7A;
  background: repeating-linear-gradient(135deg, transparent 0 14px,
              rgb(255 138 122 / .12) 14px 28px); }
.section--sombre .manque b { color: #FF8A7A; }
.section--sombre .manque span { color: rgb(255 255 255 / .8); }

/* ── LA PARALLAXE (M5) ────────────────────────────────────────────────────────
   Deux photos se déplacent un peu moins vite que la page : c'est ce décalage,
   et rien d'autre, qui donne à une page plate une profondeur. Natif, zéro ko,
   zéro JS — `animation-timeline: view()` suit la position de l'élément dans la
   fenêtre. Ajouté le 17/09/2026 (gate C54 : le mécanisme manquait au socle).
   ⛔ Jamais sous 900 px : sur un téléphone le scroll est tactile, et un élément
   qui traîne sous le doigt se lit comme une saccade, pas comme de la profondeur. */
@supports (animation-timeline: view()) {
  @media (min-width: 900px) and (prefers-reduced-motion: no-preference) {
    @keyframes parallaxe-photo { from { translate: 0 -3.5%; } to { translate: 0 3.5%; } }
    .hero .col-6 > img, .hero .col-6 > .manque,
    .heritage__photo > img, .heritage__photo > .manque {
      animation: parallaxe-photo linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
      will-change: translate;
    }
  }
}

/* ── LES CHANTIERS SUR TÉLÉPHONE : UN RAIL, PAS UNE PILE ──────────────────────
   Huit photos empilées prenaient 3,5 écrans sur 16,7 — un tiers de la page pour une
   seule section, et le visiteur abandonne avant la fin. Le rail aimanté montre une
   photo à 88 % de la largeur, la suivante dépasse pour dire qu'il y en a d'autres,
   et la section retombe à moins d'un écran. Demandé par la wireframe Porteclose du
   17/09/2026 ; le geste est le même que sur une galerie de téléphone. */
@media (max-width: 760px) {
  .chantiers { grid-auto-flow: column; grid-auto-columns: 88%; grid-template-columns: none;
               overflow-x: auto; scroll-snap-type: x mandatory; gap: var(--e-4);
               padding-bottom: var(--e-4); margin-inline: calc(var(--marge-bord) * -1);
               padding-inline: var(--marge-bord); scrollbar-width: thin;
               overscroll-behavior-x: contain; }
  /* Le rail annule les span de la mosaique : sur un rail, toutes les vignettes ont la
     meme largeur, c'est le defilement qui fait la variete. */
  .chantiers > figure { scroll-snap-align: start; }
  /* Meme specificite que les regles de la mosaique, sinon elles gagnent : une media query
     n'ajoute aucune specificite. (Piege verifie au navigateur le 17/09/2026.) */
  .chantiers > figure:first-child,
  .chantiers > figure:last-child { grid-column: auto; grid-row: auto; }
  .chantiers > figure img,
  .chantiers > figure:first-child img,
  .chantiers > figure:last-child img { aspect-ratio: 4 / 3; }
}

/* ── LE LOGO ENTIER DANS LA BARRE ET AU PIED (17/09/2026) ─────────────────────
   Un artisan paie son logo ; le retaper en texte à côté d'un pictogramme, c'est
   ne pas le servir. Quand le client a un logo horizontal, il passe en entier, et
   la ligne « métier · commune » reste en dessous. */
.barre__marque img { height: 34px; width: auto; max-width: none; }
.pied-logo { height: 40px; width: auto; max-width: none; margin-bottom: var(--e-4); }
@media (max-width: 760px) {
  .barre__marque img { height: 26px; }
  /* Le metier et la commune passent sur trois lignes a 390 px et font grossir la barre de
     moitie, pour redire ce que le H1 dit juste en dessous. Sur telephone, le logo suffit. */
  .barre__marque small { display: none; }
}


/* ════════════════════════════════════════════════════════════════════════════
   LES COLORWAYS DE SECTION — le rythme de la page (transplanté de lionheart)
   ════════════════════════════════════════════════════════════════════════════
   Lu dans `app.min.css` de lionheart-education.com, verdict de Saadou :
   « l'alternance entre des parties colorées et le fond crème rend très bien ».

   Le mécanisme, et il tient en trois idées :
   ① chaque section porte UNE classe de fond ;
   ② le texte, lui, ne change JAMAIS de couleur dans les fonds clairs — c'est
      l'unité. Seul le fond change — c'est le rythme ;
   ③ ⭐ deux sections du MÊME colorway qui se suivent FUSIONNENT : ni filet, ni
      padding entre elles. C'est ce détail qui empêche la page de ressembler à
      une pile de blocs, et c'est lui qu'on ne voit sur aucune capture.

   ⛔ Sans ça, neuf sections identiques : le défaut mesuré sur Porteclose le 17/09.
   ============================================================================ */
.cw-papier  { background: var(--fond);    color: var(--encre); }
.cw-blanc   { background: var(--surface); color: var(--encre); }
.cw-navy    { background: var(--sombre);  color: #fff; }
/* Le 4e colorway n'est pas dans le socle : chaque client pose le sien dans SON tokens.css,
   avec une valeur calculee, jamais un color-mix de deux variables (le gate ne sait pas le lire). */
.cw-bleu    { background: var(--fond-2, var(--fond)); color: var(--encre); }
.cw-papier + .cw-papier, .cw-blanc + .cw-blanc,
.cw-navy + .cw-navy, .cw-bleu + .cw-bleu { padding-top: 0; border-top: 0; }
.cw-navy .lead, .cw-navy p  { color: rgb(255 255 255 / .82); }
.cw-navy .aide              { color: rgb(255 255 255 / .7); }
.cw-navy { --accent: #7CC4F5; --ligne: var(--ligne-nuit); }

/* ════════════════════════════════════════════════════════════════════════════
   HERO-MOT — la photo prend l'écran, le mot se pose dessus
   ════════════════════════════════════════════════════════════════════════════
   lionheart `flagship-banner` : min-height 100vh, voile en color-mix de noir,
   contenu poussé en bas par `margin-top:auto`, le mot ajusté à la largeur.
   Le voile passe de 30 % à 46 % : un artisan n'a pas la lumière d'un studio. */
.hm { position: relative; display: flex; overflow: hidden;
      min-height: min(86svh, 800px); padding-block: var(--e-8) var(--e-7);
      isolation: isolate; }
.hm__img { position: absolute; inset: -6% 0; z-index: -1;
           transform: translate3d(0, var(--parallaxe, 0%), 0); will-change: transform; }
.hm__img img, .hm__img .manque { width: 100%; height: 100%; object-fit: cover; margin: 0;
                                 border-radius: 0; aspect-ratio: auto; }
.hm::before { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
              background: linear-gradient(to top,
                          color-mix(in srgb, #000 72%, transparent) 0%,
                          color-mix(in srgb, #000 46%, transparent) 42%,
                          color-mix(in srgb, #000 24%, transparent) 100%); }
.hm__inner { position: relative; z-index: 1; display: flex; flex-direction: column;
             width: 100%; color: #fff; }
.hm__haut { font-size: var(--fs-label); letter-spacing: var(--ls-label);
            text-transform: uppercase; color: rgb(255 255 255 / .82); }
/* ⭐ Le contenu est POUSSÉ EN BAS, il ne flotte pas au centre. */
.hm__bas { margin-top: auto; }
.hm__titre { margin: 0 0 var(--e-5); display: block; }
/* Le mot ajusté à la largeur : pas de JS, `cqi` fait le calcul. 1cqi = 1 % de la
   largeur du conteneur, donc un corps en cqi remplit toujours la ligne. */
.hm__inner { container-type: inline-size; }
.hm__mot { display: block; font-family: var(--f-titre); font-weight: 700;
           font-size: clamp(56px, 19cqi, 260px); line-height: .82;
           letter-spacing: -.045em; text-transform: uppercase; color: #fff; }
.hm__l2 { display: block; margin-top: var(--e-3); max-width: 24ch;
          font-family: var(--f-titre); font-weight: 700;
          font-size: clamp(22px, 3.2cqi, 46px); line-height: 1.05;
          letter-spacing: var(--ls-h2); color: #fff; }
.hm__cols { display: grid; gap: var(--e-6) var(--e-9);
            grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr); align-items: end; }
.hm__lead { font-size: var(--fs-lead); line-height: 1.45; max-width: 46ch;
            color: rgb(255 255 255 / .92); margin: 0; }
.hm__act { display: flex; flex-wrap: wrap; gap: var(--e-4); align-items: center; }
.hm__aide { flex: 1 1 100%; margin: 0; font-size: var(--fs-small);
            color: rgb(255 255 255 / .78); }
@media (max-width: 900px) {
  .hm { min-height: min(88svh, 700px); padding-block: var(--e-7) var(--e-6); }
  .hm__cols { grid-template-columns: 1fr; gap: var(--e-5); }
  .hm__act .btn { flex: 1 1 100%; text-align: center; }
}

/* ════════════════════════════════════════════════════════════════════════════
   BANDES-PHOTOS — deux bandes qui dérivent en sens inverse
   ════════════════════════════════════════════════════════════════════════════
   kozowood `[data-component=us]`, relevé au CSS :
     .line-container { align-items: flex-end; width: 160% }
     .line-container:last-of-type { align-self: flex-end }
     .line-container:last-of-type .image-container { align-self: flex-start }
     .line-container { transition: transform .25s linear }
   ⭐ Ce n'est pas le défilement qui fait l'effet : c'est que les deux bandes
   s'alignent l'une VERS l'autre, sur des photos de hauteurs différentes. */
.bandes__piste { display: flex; flex-direction: column; gap: clamp(16px, 1.6vw, 28px);
                 overflow: hidden; width: 100%; margin-top: var(--e-7); }
.bandes__ligne { display: flex; align-items: flex-end; gap: clamp(14px, 1.4vw, 26px);
                 width: max-content; min-width: 100%;
                 transition: transform .25s linear; will-change: transform; }
.bandes__ligne:last-of-type { align-self: flex-end; }
.bandes__ligne:last-of-type figure { align-self: flex-start; }
.bandes__ligne figure { flex: 0 0 auto; margin: 0; }
.bandes__ligne img { display: block; height: var(--bh, 340px); width: auto; max-width: none;
                     object-fit: cover; border-radius: var(--r-img); background: var(--surface); }
@media (max-width: 900px) { .bandes__ligne img { height: calc(var(--bh, 340px) * .62); } }
.bandes__ligne figcaption { margin-top: var(--e-3); font-size: var(--fs-small);
                            color: var(--encre-2); max-width: 30ch; }
.bandes__ligne figcaption b { display: block; color: var(--encre); }
.cw-navy .bandes__ligne figcaption   { color: rgb(255 255 255 / .7); }
.cw-navy .bandes__ligne figcaption b { color: #fff; }

@media (prefers-reduced-motion: reduce) { .bandes__ligne { transform: none !important; } }

/* ════════════════════════════════════════════════════════════════════════════
   CTA-COLLE — l'image reste, le formulaire défile
   ════════════════════════════════════════════════════════════════════════════
   lionheart « Make an inquiry » : `position: sticky; top: 120px`. Zéro JS.
   Saadou : « l'image bouge pas, c'est ce qui est à droite qui bouge. » */
.cc__grille { display: grid; gap: clamp(28px, 4vw, 72px);
              grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
.cc__colle { position: sticky; top: 104px; }
.cc__colle img, .cc__colle .manque { width: 100%; aspect-ratio: 4 / 5; object-fit: cover;
                                     border-radius: var(--r-img); }
.cc__mots { margin-top: var(--e-6); }
.cc__mots h2 { margin-top: 0; }
.cc__form .field:first-child { margin-top: 0; }
@media (max-width: 900px) {
  .cc__grille { grid-template-columns: 1fr; }
  .cc__colle { position: static; }
  .cc__colle img, .cc__colle .manque { aspect-ratio: 16 / 10; }
}

/* ════════════════════════════════════════════════════════════════════════════
   PRESTATIONS-VUES — le fluage lent, et le numéro qui devient un repère
   ════════════════════════════════════════════════════════════════════════════
   kozowood `[data-component=category-articles]` : transition de DIX SECONDES
   vers scale(1.06). Saadou : « elle est superbe ». Le numéro passe en display :
   il indexe, il ne double pas le titre (défaut mesuré le 17/09 : .ch-num était
   à la taille exacte du <h3> qu'il numérote). */
.pv__liste { margin-top: var(--e-7); display: grid; gap: 0;
             border-top: 1px solid var(--ligne); }
.pv__item { position: relative; display: grid; align-items: start;
            grid-template-columns: minmax(0, auto) minmax(0, 1fr);
            gap: var(--e-4) clamp(20px, 3vw, 48px); padding-block: clamp(20px, 2.4vw, 34px);
            border-bottom: 1px solid var(--ligne); color: inherit; text-decoration: none; }
/* La colonne d'image n'existe que s'il y a une image : sinon 15 rem de vide à droite
   de chaque rangée, sur dix rangées. */
.pv__item:has(.pv__vue) { grid-template-columns: minmax(0, auto) minmax(0, 1fr) minmax(0, 14rem); }
.pv__corps { display: block; min-width: 0; }
.pv__titre { display: block; }
.pv__benef { display: block; }
.pv__num { display: block; min-width: 2.1ch; text-align: right;
           font-family: var(--f-chiffre); font-size: var(--fs-num); line-height: .9;
           letter-spacing: -.04em; color: var(--accent); font-variant-numeric: tabular-nums; }
.pv__titre { margin: 0 0 var(--e-2); font-family: var(--f-titre); font-weight: 700;
             font-size: var(--fs-h3); line-height: 1.05; letter-spacing: var(--ls-h2);
             transition: color var(--d-micro); }
.pv__benef { margin: 0; color: var(--encre-2); max-width: 54ch; font-size: var(--fs-small); }
.cw-navy .pv__benef { color: rgb(255 255 255 / .74); }
.pv__vue { overflow: hidden; border-radius: var(--r-img); aspect-ratio: 4 / 3;
           background: var(--surface); }
.pv__vue img { width: 100%; height: 100%; object-fit: cover;
               transition: transform 10s cubic-bezier(.19, 1, .22, 1); }
a.pv__item:hover .pv__vue img, a.pv__item:focus-visible .pv__vue img { transform: scale(1.06); }
a.pv__item:hover .pv__titre, a.pv__item:focus-visible .pv__titre { color: var(--accent); }
@media (max-width: 900px) {
  .pv__item { grid-template-columns: minmax(0, 3.2rem) minmax(0, 1fr); }
  .pv__vue { grid-column: 1 / -1; aspect-ratio: 16 / 9; }
}

/* ── LE CARTON QU'ON RELÈVE (kozowood `contact-card`) ─────────────────────────
   rotateX 4° sous une perspective de 1000px. ⭐ Ce n'est PAS un agrandissement :
   un scale se lit comme un zoom, l'inclinaison se lit comme un objet qui ARRIVE.
   Sans la perspective, la rotation ne produit rien. */
@media (min-width: 900px) and (prefers-reduced-motion: no-preference) {
  .js [data-reveal].rv-carton { perspective: 1000px; }
  .js [data-reveal].rv-carton [data-reveal-item] {
    transform: translateY(40px) scale(.94) rotateX(4deg);
    transition: transform .9s var(--ease-expo), opacity .6s var(--ease-sortie); }
  .js [data-reveal].rv-carton.is-in [data-reveal-item] {
    transform: translateY(0) scale(1) rotateX(0deg); }
}


/* ████████████████████████████████████████████████████████████████████████████
   LE CONCEPT : OUVRIR
   ████████████████████████████████████████████████████████████████████████████
   Porte / close. Le métier du client, c'est ce qui s'ouvre et se ferme : portails,
   portes, fenêtres, volets. La page fait le geste du métier. Ce n'est pas une
   décoration posée dessus, c'est le fil qui tient les six sections :
     ① le premier écran s'ouvre comme un portail à deux vantaux ;
     ② le passage vers la preuve se fait par un rond qui s'ouvre ;
     ③ les étapes s'empilent en se fixant ;
     ④ les tuiles respirent au survol, dix secondes durant.
   ============================================================================ */

/* ── ① LA BARRE D'UN ESSENTIEL ─────────────────────────────────────────────── */
.bs { position: sticky; top: 0; z-index: 50; width: 100%;
      background: color-mix(in srgb, var(--fond) 88%, transparent);
      backdrop-filter: blur(14px) saturate(1.2);
      border-bottom: 1px solid color-mix(in srgb, var(--encre) 8%, transparent); }
.bs__rang { display: flex; align-items: center; justify-content: space-between;
            gap: var(--e-5); padding: 6px var(--marge-bord); }
/* 19/09/2026, Saadou : le picto EN GROS a gauche ; a sa droite, le nom avec sa
   baseline collee juste dessous. Donc une RANGEE de deux colonnes, plus une pile. */
.bs__marque { display: flex; flex-direction: row; align-items: center;
              gap: clamp(9px, 1vw, 14px); text-decoration: none; }
.bs__picto  { display: block; height: 48px; width: auto; max-width: none; flex: none; }
.bs__bloc   { display: flex; flex-direction: column; align-items: flex-start;
              gap: 1px; min-width: 0; }
.bs__mot    { display: block; height: 23px; width: auto; max-width: none; }
/* la barre garde sa pile quand le client n'a qu'un logo entier */
.bs__marque:not(:has(.bs__picto)) { flex-direction: column; align-items: flex-start; gap: 2px; }
/* ⛔ JAMAIS width:auto + max-height sur une image qui porte ses attributs : le
   navigateur garde la largeur de l'attribut et ecrase la hauteur, donc il ETIRE.
   On fixe la hauteur, la largeur suit. (Bug introduit puis vu par Saadou le 18/09.) */
/* ⚠️ `:not(.bs__mot-h)` ajoute le 20/09 : cette regle-ci a une specificite de
   0,3,1 et ecrasait la hauteur des deux mots empiles (ils ressortaient a 28 px
   au lieu de 16, mesure au navigateur). Une media query n'ajoute pas de
   specificite : il fallait exclure la classe ici. */
.bs__marque img:not(.bs__picto):not(.bs__mot):not(.bs__mot-h) { height: 28px; width: auto; max-width: none; }
.bs__marque small { font-size: 12.2px; letter-spacing: .14em; line-height: 1.2; text-transform: uppercase;
                    color: var(--encre-2); }
.bs__tel { white-space: nowrap; font-size: clamp(16px, 1.15vw, 18px);
           letter-spacing: .005em; }
/* 19/09/2026 — MESURE a 390 px : marque 239,7 + bouton 150 + marges 44 = 434 px
   pour 390 disponibles. Le logo ne rapetisse pas (Saadou vient de demander l'inverse),
   et la cible reste >= 48 px : c'est donc le LIBELLE qui s'adapte. Sous 460 px le
   bouton dit « Appeler » — le numero entier reste lisible dans la barre d'appel fixe
   du bas, qui ne quitte jamais l'ecran sur telephone. */
.bs__tel-court { display: none; }
/* Le combine, dessine une fois, en courant de la couleur du bouton. */
.bs__tel-ico { display: none; width: 19px; height: 19px; flex: none; fill: currentColor; }
/* ⭐ 20/09/2026, Saadou, sur telephone : « enleve le wordmark et ce qu'il y a en
   dessous » · « a droite le CTA, met juste un logo telephone dans le bouton avec
   marque Appelez ». Sur 390 px, le picto seul dit deja la marque : le nom ecrit et
   sa baseline ne font que remplir la barre et rapetisser tout le reste. */
/* le nom coupe en deux mots : ne sert que sur telephone */
.bs__pile { display: none; }
@media (max-width: 767px) {
  .bs__rang  { padding: 5px var(--e-4); gap: var(--e-3); }
  /* ⭐ 20/09/2026, Saadou : « remets le wordmark, mais PORTE au-dessus de CLOSE ».
     Sur une ligne, le nom forcait a descendre sous le plancher de lisibilite ;
     empile, chaque mot garde sa hauteur de capitale d'origine (17 px de rendu). */
  .bs__bloc  { display: flex; align-items: center; }
  .bs__mot   { display: none; }          /* la version une ligne */
  /* ⛔ 20/09/2026, Saadou : « c'est beaucoup trop gros, fais que le haut et le bas
     fit avec le logo carre ». MESURE de la faute : picto 44 px, pile 36 px, barre
     73 px. Les deux blocs ne s'alignaient ni en haut ni en bas, et l'ensemble
     mangeait un dixieme du premier ecran.
     La pile prend donc EXACTEMENT la hauteur du picto, et chaque mot vaut la
     moitie de ce qui reste une fois l'interligne retire : haut et bas a fleur. */
  /* ⭐ 20/09/2026, Saadou : « baisse la taille de la navbar en baissant la taille
     de ce qui s'y trouve ». MESURE de la faute : la barre faisait 60 px, et ce
     n'etait PAS le logo qui la tenait (34 px) mais le bouton (47,3 px). On baisse
     les deux : blason 28, bouton 36. La barre tombe a 46 px. */
  .bs__picto { height: var(--h-blason, 28px); }
  .bs__pile  { display: flex; flex-direction: column; justify-content: space-between;
               align-items: flex-start; gap: 2px; height: var(--h-blason, 28px); }
  /* ⚠️ pas de POURCENTAGE ici : sur un conteneur flex en colonne, une hauteur en %
     retombe sur `auto` et l'image reprend la hauteur de son attribut (mesure du
     20/09 : la pile ressortait a 150 px de large au lieu de 86). On calcule depuis
     la meme variable que le picto, en pixels. */
  .bs__mot-h { display: block; height: calc((var(--h-blason, 28px) - 2px) / 2);
               width: auto; max-width: none; }
  .bs__marque small { display: none; }   /* la baseline reste dehors sur telephone */
  .bs__marque img:not(.bs__picto):not(.bs__mot):not(.bs__mot-h) { height: 30px; }
  .bs__tel-long  { display: none; }
  .bs__tel-court { display: inline; }
  .bs__tel-ico   { display: block; }
  /* 20/09 : « fais le bouton moins wide, genre un peu plus slim ». La cible reste
     a 44 px de haut (plancher tactile) ; c'est la largeur qui se resserre. */
  /* ⛔ Le bouton DESCEND a 36 px mais reste TAPABLE sur 46 : la zone de clic est
     etendue par un pseudo-element qui deborde. Le plancher tactile de 44 px
     (WCAG 2.5.5) tient, sans que la barre le paie en hauteur. */
  /* ⚠️ `min-height` ne suffit pas : `.btn` garde son `padding` vertical, qui pousse
     la boite a 45,7 px (mesure du 20/09). Il faut aussi baisser le padding.
     Et `.btn` porte `overflow: hidden` : sans `overflow: visible` ici, la zone de
     clic etendue serait rognee et le bouton deviendrait vraiment plus petit. */
  .bs__tel { position: relative; overflow: visible;
             display: inline-flex; align-items: center; gap: 6px;
             padding: 6px 11px; min-height: 36px;
             font-size: 14px; letter-spacing: 0; }
  .bs__tel::after { content: ""; position: absolute; inset: -5px -6px; }
  .bs__tel-ico { width: 15px; height: 15px; }
}

/* ── ② LE PREMIER ÉCRAN QUI S'OUVRE ────────────────────────────────────────── */
.ho { position: relative; display: flex; overflow: hidden; isolation: isolate;
      min-height: min(90svh, 820px); padding-block: var(--e-8) var(--e-7); }
.ho__img { position: absolute; inset: -7% 0; z-index: -2;
           transform: translate3d(0, var(--parallaxe, 0%), 0); will-change: transform; }
.ho__img img, .ho__img .manque { width: 100%; height: 100%; object-fit: cover;
                                 margin: 0; border-radius: 0; aspect-ratio: auto; }
.ho::before { content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
              background: linear-gradient(to top,
                          color-mix(in srgb, var(--sombre) 86%, transparent) 0%,
                          color-mix(in srgb, var(--sombre) 60%, transparent) 30%,
                          color-mix(in srgb, var(--sombre) 18%, transparent) 62%,
                          transparent 100%); }
/* ⭐ LES DEUX VANTAUX. Ils couvrent la photo, puis s'écartent depuis le centre.
   C'est le geste du métier, fait par la page. */
.ho__vantail { position: absolute; top: 0; bottom: 0; width: 50.5%; z-index: 2;
               background: var(--sombre); pointer-events: none;
               transition: transform 1.15s var(--ease-expo); }
.ho__vantail--g { left: 0;  transform: translateX(-101%); }
.ho__vantail--d { right: 0; transform: translateX(101%); }
.js .ho__vantail--g { transform: translateX(0); }
.js .ho__vantail--d { transform: translateX(0); }
.js .ho.is-in .ho__vantail--g { transform: translateX(-101%); }
.js .ho.is-in .ho__vantail--d { transform: translateX(101%); }
@media (prefers-reduced-motion: reduce) { .ho__vantail { display: none; } }

.ho__inner { position: relative; z-index: 3; display: flex; flex-direction: column;
             width: 100%; color: #fff; }
.ho__bas { margin-top: auto; max-width: 62ch; }
.ho__titre { hyphens: none; -webkit-hyphens: none; text-wrap: balance; margin: 0 0 var(--e-3); color: #fff;
             font-size: clamp(38px, 5.6vw, 78px); line-height: .98;
             letter-spacing: -.035em; text-wrap: balance; }
/* la seconde phrase du titre : le metier, la ville, les prestations. Plus petite
   que le benefice, mais toujours dans le H1 (SEO et test des 5 secondes). */
/* ⚠️ `:not(.ho__mot)` : le decoupeur du titre enveloppe CHAQUE MOT dans un
   <span class="ho__mot">. Sans l'exclusion, chaque mot passait en bloc et le
   titre sortait a un mot par ligne (mesure du 20/09 : 462 px de haut). */
.ho__titre > span:not(.ho__mot) { display: block; margin-top: .44em; font-size: .46em;
                  line-height: 1.2; font-weight: 600; letter-spacing: -.012em;
                  color: rgb(255 255 255 / .9); text-wrap: pretty; }
.ho__l2 { margin: 0 0 var(--e-6); max-width: 48ch; color: rgb(255 255 255 / .9);
          font-size: var(--fs-lead); line-height: 1.4; }
.ho__act { display: flex; flex-wrap: wrap; gap: var(--e-4); }
/* la preuve, dans le premier écran */
.preuve-avis { display: inline-flex; align-items: center; gap: var(--e-3);
               margin-bottom: var(--e-5); padding: 7px 15px 7px 11px; border-radius: 999px;
               background: rgb(255 255 255 / .13); border: 1px solid rgb(255 255 255 / .22);
               backdrop-filter: blur(8px); color: #fff; text-decoration: none;
               font-size: var(--fs-small); transition: background var(--d-micro); }
a.preuve-avis:hover { background: rgb(255 255 255 / .22); }
.preuve-avis .etoiles { color: #FFC64B; letter-spacing: .06em; font-size: 14px; }
.preuve-avis b { font-weight: 700; }
.preuve-avis span { color: rgb(255 255 255 / .8); }
@media (max-width: 900px) {
  .ho { min-height: min(92svh, 720px); padding-block: var(--e-7) var(--e-6); }
  .ho__act .btn { flex: 1 1 100%; text-align: center; }
}

/* ── ③ LES TUILES QU'ON VOIT ───────────────────────────────────────────────── */
/* ⛔ 18/09/2026, Saadou : « ton mur de photos est moche ». Il l'etait, et voici pourquoi :
   la grille de 4 colonnes laissait la 5e tuile ORPHELINE en bas a gauche, en portrait,
   au milieu de tuiles en paysage, avec sa legende qui debordait sur trois lignes.
   Une mosaique se dessine pour un NOMBRE de tuiles, elle ne se subit pas.
   Ici : 6 colonnes. Rang 1 = une grande (4) + une haute (2). Rang 2 = trois egales (2+2+2).
   Cinq tuiles, deux rangs pleins, zero orpheline. La tuile 2 n'a pas de ratio : elle
   s'etire a la hauteur du rang, donc les deux rangs s'alignent toujours. */
.tp__grille { margin-top: var(--e-7); display: grid; gap: clamp(14px, 1.4vw, 22px);
              grid-template-columns: repeat(6, minmax(0, 1fr)); }
.tp__tuile { position: relative; display: block; overflow: hidden; text-decoration: none;
             border-radius: var(--r-img); background: var(--sombre);
             grid-column: span 2; aspect-ratio: 4 / 3; }
/* ── LA MOSAIQUE SE DESSINE POUR UN NOMBRE ─────────────────────────────────
   Une grille « auto » laisse toujours un rang bancal des que le compte change :
   5 tuiles donnaient une orpheline (18/09), 4 laissaient deux colonnes vides
   (19/09). On dessine donc chaque cas, sur six colonnes. */

/* 3 : trois de front */
.tp__grille--3 .tp__tuile { grid-column: span 2; aspect-ratio: 4 / 3; }

/* 4 : deux rangs de deux, et le premier respire plus large */
.tp__grille--4 .tp__tuile { grid-column: span 3; aspect-ratio: 4 / 3; }

/* 5 : une grande + une haute, puis trois de front */
.tp__grille--5 .tp__tuile:nth-child(1) { grid-column: span 4; aspect-ratio: 16 / 9; }
.tp__grille--5 .tp__tuile:nth-child(2) { grid-column: span 2; aspect-ratio: auto; }

/* 6 : deux rangs de trois */
.tp__grille--6 .tp__tuile { grid-column: span 2; aspect-ratio: 4 / 3; }

/* 7 : trois de front encadrees par deux rangs de deux */
.tp__grille--7 .tp__tuile { grid-column: span 2; aspect-ratio: 4 / 3; }
.tp__grille--7 .tp__tuile:nth-child(-n+2),
.tp__grille--7 .tp__tuile:nth-child(n+6) { grid-column: span 3; aspect-ratio: 3 / 2; }

/* 8 : quatre rangs de deux */
.tp__grille--8 .tp__tuile { grid-column: span 3; aspect-ratio: 4 / 3; }

/* 9 : trois rangs de trois */
.tp__grille--9 .tp__tuile { grid-column: span 2; aspect-ratio: 4 / 3; }

/* 10 : cinq rangs de deux */
.tp__grille--10 .tp__tuile { grid-column: span 3; aspect-ratio: 4 / 3; }

/* le defaut, quand le nombre n'est pas dessine : une grande + le reste par deux */
.tp__grille:not([class*="tp__grille--"]) .tp__tuile:nth-child(1) { grid-column: span 4; aspect-ratio: 16 / 9; }
.tp__grille:not([class*="tp__grille--"]) .tp__tuile:nth-child(2) { grid-column: span 2; aspect-ratio: auto; }
/* ⛔ plus aucun survol ici : la tuile ne mene nulle part, elle ne doit donc rien
   promettre. L'opacite pleine, une bonne fois : l'image est le sujet, pas un fond. */
.tp__tuile img { width: 100%; height: 100%; object-fit: cover; opacity: 1; }
.tp__tuile { margin: 0; }
.tp__tuile::after { content: ""; position: absolute; inset: 0; pointer-events: none;
                    background: linear-gradient(to top,
                                color-mix(in srgb, var(--sombre) 86%, transparent) 0%,
                                transparent 58%); }
.tp__mots { position: absolute; inset: auto 0 0 0; z-index: 1;
            padding: clamp(14px, 1.8vw, 26px); color: #fff; }
.tp__mots b { display: block; font-family: var(--f-titre); font-weight: 700;
              font-size: clamp(17px, 1.5vw, 24px); line-height: 1.08;
              letter-spacing: var(--ls-h2); }
.tp__mots span { display: block; margin-top: 4px; font-size: var(--fs-small);
                 color: rgb(255 255 255 / .76); }
.tp__reste { margin-top: var(--e-6); color: var(--encre-2); max-width: 74ch; }
@media (max-width: 900px) {
  .tp__grille { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tp__tuile, .tp__tuile:nth-child(1), .tp__tuile:nth-child(4) {
    grid-column: span 2; grid-row: auto; aspect-ratio: 4 / 3; }
}

/* ── LE RAIL : CE QUI S'EMPILE SUR PC SE FAIT DÉFILER SUR TÉLÉPHONE ─────────
   ⭐ 20/09/2026, Saadou, mesure à l'appui : « devoir scroll pour voir tout ce que
   le client propose c'est n'importe quoi, ça prend littéralement 20 % de la page,
   et le tout sans aucune animation on finit par s'ennuyer vite ».
   MESURÉ à 390 px avant correction : les neuf prestations empilées faisaient
   2 923 px, soit 23,1 % d'une page de 12 634 px, et 3,5 écrans de téléphone rien
   que pour la liste.
   Le rail les couche sur une ligne qui se fait glisser : la section retombe sous
   500 px, le geste horizontal est lui-même le mouvement, et la tuile coupée au bord
   droit dit qu'il y en a d'autres sans qu'on ait à l'écrire. */
/* ⚠️ MESURE du 20/09 : ces règles posées SANS media query débordaient de 4 px à
   900 px (la marge négative s'appliquait alors que la grille était restée en
   `grid`). Le rail n'existe QUE sur téléphone ; au-dessus, la mosaïque reprend. */
@media (max-width: 760px) {
  .rail { display: flex; flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
          scroll-snap-type: x mandatory; overscroll-behavior-x: contain;
          -webkit-overflow-scrolling: touch;
          scrollbar-width: none; -ms-overflow-style: none;
          /* le rail file jusqu'aux deux bords de l'écran, puis reprend la marge dedans */
          margin-inline: calc(var(--marge-bord) * -1);
          padding-inline: var(--marge-bord);
          scroll-padding-inline: var(--marge-bord); }
  .rail::-webkit-scrollbar { display: none; }
  .rail > * { scroll-snap-align: start; flex: 0 0 auto; }
}
/* la jauge : elle dit où on en est dans la file, et combien il en reste */
.rail-jauge { position: relative; height: 3px; border-radius: 3px; margin-top: var(--e-5);
              background: color-mix(in srgb, var(--encre) 12%, transparent); }
.rail-jauge::after { content: ""; position: absolute; inset: 0 auto 0 0;
                     width: calc(var(--part-vue, .34) * 100%);
                     transform: translateX(calc(var(--progres, 0) * (100% * (1 / max(var(--part-vue, .34), .01)) - 100%)));
                     border-radius: 3px; background: var(--accent);
                     transition: transform .12s linear; }
.cw-navy .rail-jauge, .section--sombre .rail-jauge { background: rgb(255 255 255 / .18); }

@media (max-width: 760px) {
  /* ⛔ 20/09/2026, Saadou : « l'idee que t'as trouvee est nulle pour regler le
     probleme de la section 2 ». Il a raison, et voici pourquoi : le rail ne reglait
     rien, il DEPLACAIT le defilement sur le cote. Sa phrase d'origine etait
     « devoir scroll pour voir tout ce que le client propose, c'est n'importe quoi ».
     La bonne reponse n'est pas un meilleur defilement : c'est de n'en avoir AUCUN.
     Neuf vignettes carrees en trois colonnes, le nom dessous. Tout ce qu'il vend
     tient dans un demi-ecran, d'un seul coup d'oeil, sans un geste.
     MESURE : 2 923 px empilees -> 701 px en rail -> ~440 px ici, et rien a faire
     defiler. Le detail de chaque prestation descend dans la ligne du dessous. */
  .tp__grille { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
                gap: 11px 8px; margin: var(--e-6) 0 0; padding: 0;
                overflow: visible; scroll-snap-type: none; }
  /* ⚠️ `.tp__grille` DOIT etre dans le selecteur : les mosaiques ci-dessus
     (`.tp__grille--9 .tp__tuile`) pesent 0,2,0 et ecrasaient un `.tp__tuile` nu.
     Mesure de la faute : la section ressortait a 1 906 px au lieu de 440. */
  .tp__grille .tp__tuile { grid-column: auto; grid-row: auto; flex: none;
                           display: block; overflow: visible; aspect-ratio: auto;
                           background: none; border-radius: 0; }
  .tp__grille .tp__tuile img { aspect-ratio: 1; height: auto;
                               border-radius: var(--r-img); }
  /* ⛔ MESURE du 20/09 : `.tp__voir { height: 100% }` (necessaire sur PC, ou la
     tuile a un ratio fixe) prenait ici la hauteur de la RANGEE de grille, soit
     205 px — le bouton recouvrait donc la legende, invisible sous lui.
     `elementFromPoint` sur le nom renvoyait BUTTON.tp__voir. Ici, hauteur libre. */
  .tp__grille .tp__voir { height: auto; }
  .tp__tuile::after { display: none; }   /* le nom n'est plus SUR la photo */
  .tp__mots { position: static; padding: 6px 0 0; }
  /* ⛔ 20/09/2026, Saadou : « PK T'AS SUPPRIME LE TEXTE DANS SECTION 2 QUI ETAIT
     SUR LES PHOTOS ??? ». J'avais masque le detail de chaque prestation parce
     qu'il ne tenait pas SUR une vignette de 109 px. C'etait la mauvaise conclusion :
     ce qui ne tenait pas, c'etait de l'ecrire PAR-DESSUS la photo. Dessous, il
     tient, et c'est lui qui dit la matiere et le cas d'emploi. Il revient. */
  .tp__mots b { display: block; font-size: 12.5px; line-height: 1.2;
                letter-spacing: -.005em; color: var(--encre);
                overflow-wrap: break-word; hyphens: none; }
  .tp__mots span { display: block; margin-top: 2px; font-size: 12px;
                   line-height: 1.28; color: var(--encre-2);
                   overflow-wrap: break-word; hyphens: none; }
  .cw-navy .tp__mots span, .section--sombre .tp__mots span { color: rgb(255 255 255 / .74); }
  .cw-navy .tp__mots b, .section--sombre .tp__mots b { color: #fff; }
  .tp__reste { margin-top: var(--e-5); }
}
@media (min-width: 761px) { .rail-jauge { display: none; } }

/* L'ENTRÉE DU RAIL. Les tuiles arrivent de la droite, l'une après l'autre : le
   mouvement raconte la file qu'on va faire glisser. `--rang` est posé par build.py. */
@media (max-width: 760px) {
  .js .rail[data-reveal-rail] > * { opacity: 0; transform: translate3d(26px, 0, 0); }
  .js [data-reveal].is-in .rail[data-reveal-rail] > * {
    opacity: 1; transform: none;
    transition: opacity .5s var(--ease-sortie) calc(var(--rang, 0) * 70ms),
                transform .62s var(--ease-expo) calc(var(--rang, 0) * 70ms); }
}
@media (prefers-reduced-motion: reduce) {
  .js .rail[data-reveal-rail] > * { opacity: 1; transform: none; transition: none; }
  .rail { scroll-snap-type: none; }
}

/* ── LA PHOTO QU'ON AGRANDIT ────────────────────────────────────────────────
   ⭐ 20/09/2026, Saadou : « pour la section 2, tres bonne alternative, il faut
   juste que quand on clique sur la photo elle s'agrandit pour qu'on la voie bien ».
   En trois colonnes, une vignette fait 109 px : on reconnait la prestation, on ne
   voit pas l'ouvrage. Le clic ouvre la photo en grand.
   ⚠️ Le clic FAIT desormais quelque chose : le curseur a le droit de le dire.
   C'etait l'interdit du 19/09 (« le truc qui fait IA comme si on pouvait cliquer,
   or ca nous emmene pas ailleurs ») — il ne tient plus, puisque ca mene quelque part. */
.tp__voir { display: block; width: 100%; height: 100%; padding: 0; margin: 0;
            border: 0; background: none; cursor: zoom-in; color: inherit;
            font: inherit; -webkit-tap-highlight-color: transparent; }
.tp__voir:focus-visible { outline: 3px solid var(--marque); outline-offset: 3px; }
.zoom { width: min(94vw, 1200px); max-width: none; max-height: 92vh; padding: 0;
        border: 0; border-radius: var(--r-img); background: var(--sombre);
        color: #fff; overflow: hidden; }
.zoom::backdrop { background: rgb(8 14 20 / .82); backdrop-filter: blur(3px); }
.zoom img { display: block; width: 100%; height: auto; max-height: 82vh;
            object-fit: contain; background: var(--sombre); }
/* ⛔ 20/09/2026, Saadou : « le texte est mal arrange dans le truc quand on clique ».
   Il l'etait : un flex `space-between` collait le nom a gauche et poussait une
   phrase entiere a droite, sur quatre lignes etroites. Un nom et sa legende se
   lisent l'un SOUS l'autre, alignes a gauche, pas cote a cote. */
.zoom__bas { display: block; margin: 0; padding: var(--e-4) var(--e-5) var(--e-5);
             max-width: 72ch; }
.zoom__bas b { display: block; font-family: var(--f-titre);
               font-size: clamp(19px, 2.4vw, 26px); line-height: 1.12;
               letter-spacing: var(--ls-h2); }
.zoom__bas span { display: block; margin-top: 6px; font-size: var(--fs-small);
                  line-height: 1.45; color: rgb(255 255 255 / .74); }
.zoom__fermer { position: absolute; top: 10px; right: 10px; z-index: 2;
                width: 44px; height: 44px; border: 0; border-radius: 50%;
                background: rgb(8 14 20 / .62); color: #fff; font-size: 22px;
                line-height: 1; cursor: pointer; }
.zoom__fermer:hover { background: rgb(8 14 20 / .86); }
/* le dialogue donne le focus a ce bouton en s'ouvrant : l'anneau doit se lire
   sur une photo, donc blanc, pas la couleur de marque. */
.zoom__fermer:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
@media (prefers-reduced-motion: no-preference) {
  .zoom[open] { animation: zoom-entre .26s var(--ease-sortie); }
  @keyframes zoom-entre { from { opacity: 0; transform: scale(.97); } }
}

/* ── LA CASSURE EN VAGUE ────────────────────────────────────────────────────
   ⭐ 20/09/2026, Saadou : « pour la cassure entre la FAQ et le footer, fais un
   truc en vague ». Une arete droite entre deux aplats coupe la page en deux ;
   une vague la fait couler. Le trace est posé EN HAUT du pied, rempli de la
   couleur de la section du dessus : c'est le pied qui se laisse mordre. */
.vague { display: block; width: 100%; height: var(--h-vague, clamp(38px, 6vw, 78px));
         pointer-events: none; }
.vague path { fill: var(--vague-fond, var(--surface)); }
/* ⚠️ MESURE du 20/09 : laissee dans le flux, la vague se posait SOUS les 64 px de
   retrait du pied — on voyait une bande sombre au-dessus d'elle, donc deux
   cassures au lieu de zero. Elle est donc collee au bord haut, en absolu, et la
   section lui fait sa place en retrait. */
.section:has(> .vague:first-child) { position: relative;
                                     padding-top: calc(var(--e-7) + var(--h-vague, clamp(38px, 6vw, 78px))); }
.vague:first-child { position: absolute; inset: 0 0 auto 0; }

/* ── ④ LE ROND QUI S'OUVRE ─────────────────────────────────────────────────── */
.pr { position: relative; margin-inline: calc(50% - 50vw); width: 100vw;
      min-height: 84svh; display: grid; overflow: hidden; isolation: isolate; }
.pr__cadre { grid-area: 1/1; overflow: hidden;
             /* ⭐ l'image est PLEINE dès le départ : c'est la fenêtre qui grandit */
             clip-path: circle(var(--ouv, 100%) at 50% 50%); }
.js [data-rond] .pr__cadre { --ouv: 16%; transition: clip-path 1.1s var(--ease-expo); }
.js [data-rond].is-in .pr__cadre { --ouv: 78%; }
.pr__cadre img, .pr__cadre .manque { width: 100%; height: 100%; object-fit: cover;
                                     margin: 0; border-radius: 0; aspect-ratio: auto; }
.pr__mots { grid-area: 1/1; position: relative; display: flex; align-items: flex-end;
            padding-block: clamp(32px, 5vw, 76px);
            background: linear-gradient(to top,
                        color-mix(in srgb, var(--sombre) 74%, transparent) 0%,
                        transparent 52%); }
.pr__mots h2 { color: #fff; margin: 0 0 var(--e-5); max-width: 18ch; text-wrap: balance; }
@media (prefers-reduced-motion: reduce) { .js [data-rond] .pr__cadre { --ouv: 78%; } }
@media (max-width: 760px) { .pr { min-height: 66svh; } }

/* ── ⑤ LA PILE QUI SE FIXE ─────────────────────────────────────────────────── */
.pp__pile { list-style: none; margin: var(--e-7) 0 0; padding: 0; max-width: none; }
/* Le socle bride tout <li> a 65ch : une carte de process n'est pas un paragraphe. */
.pp__pile > li { max-width: none; }
.pp__carte { position: sticky; top: calc(84px + var(--rang, 0) * 16px);
             display: grid; grid-template-columns: minmax(0, auto) minmax(0, 1fr);
             gap: var(--e-4) clamp(20px, 3vw, 48px); align-items: start;
             margin-bottom: var(--e-5); padding: clamp(22px, 3vw, 40px);
             border-radius: var(--r-bloc); border: 1px solid var(--ligne);
             background: var(--surface); box-shadow: var(--sh-2); }
/* ⚠️ La dernière ne chevauche pas : elle se pose. Correction demandée sur enerblock. */
.pp__carte:last-child { position: static; margin-bottom: 0; }
.pp__num { font-family: var(--f-chiffre); font-size: var(--fs-num); line-height: .86;
           letter-spacing: -.045em; color: var(--accent);
           font-variant-numeric: tabular-nums; }
.pp__carte h3 { margin: 0 0 var(--e-2); font-size: var(--fs-h3); line-height: 1.06;
                letter-spacing: var(--ls-h2); }
.pp__carte p { margin: 0; color: var(--encre-2); max-width: 58ch; }
.pp__delai { display: inline-block; margin-top: var(--e-3); padding: 4px 11px;
             border-radius: 999px; font-size: var(--fs-label);
             letter-spacing: var(--ls-label); text-transform: uppercase;
             background: var(--sombre); color: #fff; }
@media (max-width: 760px) {
  .pp__carte { grid-template-columns: 1fr; top: calc(68px + var(--rang, 0) * 10px); }
}

/* ── ⑥ LES AVIS ────────────────────────────────────────────────────────────── */
.av__haut { display: flex; flex-wrap: wrap; align-items: baseline;
            justify-content: space-between; gap: var(--e-4); }
.av__haut h2 { margin: 0; }
.note-avis { display: inline-flex; align-items: center; gap: var(--e-3);
             text-decoration: none; color: inherit; font-size: var(--fs-small); }
.note-avis .etoiles { color: #E8A317; letter-spacing: .06em; }
.note-avis b { font-family: var(--f-chiffre); font-size: var(--fs-h3); line-height: 1; }
.av__grille { margin-top: var(--e-7); display: grid; gap: clamp(16px, 1.8vw, 28px);
              grid-template-columns: 1fr; }
/* ⛔ 19/09/2026 — mesure : entre 720 et 980 px, auto-fit donnait DEUX colonnes,
   donc « 2 + 1 » et un avis orphelin au dernier rang. On passe directement de une
   colonne a trois : la largeur intermediaire n'existe plus. */
@media (min-width: 980px) {
  .av__grille { grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }
}
/* ⭐ 20/09/2026, Saadou : « les 3 avis, mets-les plutôt en ligne avec une
   possibilité de scroll horizontal ». Empilés, ils faisaient 1 300 px sur un
   téléphone, soit un écran et demi pour trois cartes qui disent la même chose.
   ⚠️ Cette règle doit rester APRÈS la définition de `.av__grille` : posée avant,
   elle se faisait écraser par le `display: grid` de la règle de base. */
@media (max-width: 760px) {
  /* ⭐ 20/09/2026, Saadou : « que le cadre des avis soit plus wide, et mets l'avis
     du milieu en avant, pas celui de gauche, de sorte qu'on puisse scroller a
     gauche OU a droite ».
     Le cadre passe a 90 % de l'ecran, et le calage se fait au CENTRE : au repos
     c'est l'avis du milieu qui est sous les yeux, avec un bord de chaque cote.
     Le geste devient reversible, et on voit tout de suite qu'il y en a d'autres
     des DEUX cotes. Le centrage initial est pose par reveal.js. */
  .av__grille { display: flex; grid-template-columns: none; gap: 10px;
                margin-top: var(--e-6); align-items: stretch;
                padding-inline: 5%; scroll-padding-inline: 5%;
                margin-inline: calc(var(--marge-bord) * -1); }
  .av__carte { flex: 0 0 90%; padding: var(--e-5); scroll-snap-align: center; }
  .av__carte blockquote { font-size: var(--fs-small); }
}
.av__carte { display: flex; flex-direction: column; gap: var(--e-4);
             padding: clamp(22px, 2.6vw, 34px); border-radius: var(--r-bloc);
             background: var(--surface); border: 1px solid var(--ligne); }
.cw-navy .av__carte { background: color-mix(in srgb, #fff 7%, transparent);
                      border-color: var(--ligne-nuit); }
.av__carte .etoiles { color: #E8A317; letter-spacing: .08em; font-size: 15px; }

/* 19/09/2026 — LES AVIS, calques sur la capture de reference fournie par Saadou.
   Rang 1 : avatar rond | nom + date | le G de Google, en haut a DROITE de la carte.
   Rang 2 : les etoiles sur leur propre ligne, grandes | la pastille bleue de verification.
   Rang 3 : le texte, mot pour mot.
   La pastille est un LIEN vers la fiche : « verifie » doit pouvoir se verifier. */
.av__carte { position: relative; gap: var(--e-3);
             /* 19/09 : la carte ne doit pas se confondre avec la section.
                Une pointe d'accent dans le fond, pas une couleur : on reste
                dans la palette, la carte se detache juste assez pour exister. */
             background: color-mix(in srgb, var(--accent) 5%, var(--fond));
             border: 1px solid color-mix(in srgb, var(--accent) 13%, transparent); }
.section--sombre .av__carte {
             background: color-mix(in srgb, var(--accent) 16%, transparent);
             border-color: color-mix(in srgb, #fff 12%, transparent); }
.av__tete { display: flex; align-items: flex-start; gap: var(--e-3); }
.av__ava { flex: none; width: 48px; height: 48px; border-radius: 50%;
           display: grid; place-items: center; overflow: hidden; }
.av__ava img { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; display: block; }
.av__ava--txt { background: hsl(var(--t, 210) 42% 40%); color: #fff;
                font-family: var(--f-titre); font-weight: 700; font-size: 20px; }
.av__id { display: flex; flex-direction: column; min-width: 0; padding-top: 2px; }
.av__id b { font-size: var(--fs-body); line-height: 1.25; }
.av__id small { font-size: var(--fs-small); color: var(--encre-2); }
.section--sombre .av__id small { color: rgb(255 255 255 / .66); }
/* le G : en haut a droite de la CARTE, pas sur l'avatar */
.av__g { flex: none; margin-left: auto; width: 24px; height: 24px; }
/* le rang des etoiles */
.av__note { display: flex; align-items: center; gap: var(--e-2); }
.av__carte .av__note .etoiles { font-size: 21px; letter-spacing: .1em; color: #E8A317; }
.av__verif { width: 19px; height: 19px; display: block; }
.av__lien { display: block; line-height: 0; border-radius: 50%; }
.av__lien:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.av__carte blockquote { margin: 0; padding: 0; border: 0; background: none;
                        font-size: var(--fs-lead); line-height: 1.42; }
.av__qui { margin-top: auto; font-size: var(--fs-small); color: var(--encre-2); }
.cw-navy .av__qui { color: rgb(255 255 255 / .7); }
.av__qui b { display: block; color: var(--encre); font-size: var(--fs-body); }
.cw-navy .av__qui b { color: #fff; }


/* ═══════════════════════════════════════════════════════════════════════════
   LA COUTURE — l'animation qui tient la page entière
   ---------------------------------------------------------------------------
   Saadou, 18/09/2026 : « le site n'a pas d'animation structurante ».
   La faute d'avant : trois effets posés sur trois sections (vantaux, rond,
   parallaxe). Trois idées, aucune structure.

   Ici, UNE seule idée, déclinée : ce métier vend des OUVERTURES. Toute la page
   s'ouvre depuis un joint vertical, comme deux vantaux qui s'écartent. Le hero
   le fait en grand avec ses vantaux pleins ; chaque section suivante le rejoue
   en petit, sur sa propre matière, jamais de la même façon deux fois de suite.

   (Une barre de progression verticale courait aussi sur le bord gauche : elle est
   SUPPRIMÉE le 19/09/2026 sur demande de Saadou. Ne pas la remettre.)

   ⛔ On n'anime que clip-path, transform et opacity : composés par le GPU.
   ⛔ Tout est sous prefers-reduced-motion.
   ═══════════════════════════════════════════════════════════════════════════ */

.js .ouvre           { clip-path: inset(0 50% 0 50%); }
.js .is-in .ouvre,
.js .ouvre.is-in     { clip-path: inset(0 0 0 0);
                       transition: clip-path 1000ms var(--ease-expo); }

/* la même idée, mais par le haut et le bas : pour ne pas rejouer deux fois de
   suite le même geste sur deux sections voisines */
.js .ouvre-v         { clip-path: inset(50% 0 50% 0); }
.js .is-in .ouvre-v,
.js .ouvre-v.is-in   { clip-path: inset(0 0 0 0);
                       transition: clip-path 1000ms var(--ease-expo); }

/* le décalage : les enfants s'ouvrent l'un après l'autre, pas tous ensemble */
.js .is-in .ouvre:nth-child(2)  { transition-delay:  90ms; }
.js .is-in .ouvre:nth-child(3)  { transition-delay: 180ms; }
.js .is-in .ouvre:nth-child(4)  { transition-delay: 270ms; }
.js .is-in .ouvre:nth-child(5)  { transition-delay: 360ms; }

@media (prefers-reduced-motion: reduce) {
  .js .ouvre, .js .ouvre-v { clip-path: none !important; transition: none !important; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   LA LARGEUR DE LECTURE — garde-fou mesure le 19/09/2026
   ---------------------------------------------------------------------------
   Le balayage large a trouve des lignes de 100 a 105 signes autour de 900 px :
   un oeil perd sa ligne au-dela de ~75. Ce n'est pas un defaut de section, c'est
   un defaut de SOCLE : aucun plancher n'existait pour le texte courant.
   On le pose ici, en dernier, pour qu'il gagne sur les blocs qui n'en ont pas —
   et on epargne ce qui doit rester large : les titres, les grilles, les champs.
   ═══════════════════════════════════════════════════════════════════════════ */
p, li, .aide, label, figcaption, blockquote {
  max-width: 72ch;
}
/* ce qui n'a pas de largeur de lecture : une ligne de faits, une etiquette posee
   sur une image, une cellule de tableau, un champ de formulaire */
.bande-faits p, .bande-faits li,
.tp__mots span, .pr__mots p, .ho__l2,
td p, th p, .av__carte blockquote,
.cc__form label {
  max-width: none;
}


/* ═══════════════════════════════════════════════════════════════════════════
   LA PORTÉE QUI SE DESSINE — l'animation de la section « zone »
   ---------------------------------------------------------------------------
   C'est la section où le site dit « on vient jusque chez vous ». Elle n'avait
   aucun mouvement, et le cercle prévu sortait avec r="0" — invisible.
   Ici, chaque commune porte une barre longue comme son temps de route, et les
   barres poussent de la plus proche à la plus lointaine. Le rayon s'étend sous
   l'œil, avec les vraies données. On n'anime que transform et opacity.
   ═══════════════════════════════════════════════════════════════════════════ */
.zone-c__temps li { position: relative; align-items: baseline; }
.zone-c__temps li::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
  background: color-mix(in srgb, var(--accent) 55%, transparent);
  transform: scaleX(var(--part, 1)); transform-origin: left;
}
.js [data-reveal] .zone-c__temps li::after { transform: scaleX(0); }
.js [data-reveal].is-in .zone-c__temps li::after {
  transform: scaleX(var(--part, 1));
  transition: transform .8s var(--ease-expo) calc(var(--rang, 0) * 110ms);
}
.js [data-reveal] .zone-c__temps li > * { opacity: 0; transform: translateX(-6px); }
.js [data-reveal].is-in .zone-c__temps li > * {
  opacity: 1; transform: none;
  transition: opacity .45s ease calc(var(--rang, 0) * 110ms + 80ms),
              transform .55s var(--ease-expo) calc(var(--rang, 0) * 110ms + 80ms);
}
@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] .zone-c__temps li::after,
  .js [data-reveal] .zone-c__temps li > * { transform: none; opacity: 1; transition: none; }
  .zone-c__temps li::after { transform: scaleX(var(--part, 1)); }
}


/* LE TITRE QUI MONTE — un mot ne peut se lever que si son enveloppe le coupe.
   `overflow: clip` plutot que `hidden` : il ne cree aucun contexte de defilement,
   donc il n'attrape pas la page. Le decalage par mot est pose par reveal.js. */
.ho__mot { display: inline-block; overflow: clip; vertical-align: bottom;
           padding-bottom: .04em; }
.ho__mot > i { display: inline-block; font-style: inherit;
               transform: translateY(105%);
               transition: transform .9s var(--ease-expo); }
.ho__titre.is-leve .ho__mot > i { transform: none; }
@media (prefers-reduced-motion: reduce) {
  .ho__mot > i { transform: none; transition: none; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   DEUX SECTIONS QUI N'AVAIENT AUCUN MOUVEMENT — 19/09/2026
   ---------------------------------------------------------------------------
   Le skill demande « une animation ostentatoire minimum toutes les deux
   sections ». L'heritage et la bande de faits n'en avaient aucune.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── LE FLUAGE DE LA PHOTO D'HERITAGE ──────────────────────────────────────
   Un zoom tres lent et permanent (mecanisme releve dans le CSS de kozowood :
   `transition: transform 10s`). Douze secondes pour 4 % : on ne le voit pas
   bouger, on sent que ce n'est pas une image morte. GPU seul. */
.heritage__photo img { transform: scale(1); transition: transform 12s linear; }
.js [data-reveal].is-in .heritage__photo img { transform: scale(1.04); }

/* le texte d'heritage arrive par la gauche, un bloc apres l'autre */
.js [data-reveal] .heritage__texte > * { opacity: 0; transform: translateX(-14px); }
.js [data-reveal].is-in .heritage__texte > * {
  opacity: 1; transform: none;
  transition: opacity .5s ease var(--d, 0ms), transform .7s var(--ease-expo) var(--d, 0ms);
}
.js .heritage__texte > *:nth-child(1) { --d: 60ms; }
.js .heritage__texte > *:nth-child(2) { --d: 170ms; }
.js .heritage__texte > *:nth-child(3) { --d: 280ms; }
.js .heritage__texte > *:nth-child(4) { --d: 390ms; }

/* ── LES CHIFFRES QUI COMPTENT ─────────────────────────────────────────────
   La bande de preuve, juste sous le premier ecran. Les nombres montent jusqu'a
   leur valeur quand la bande entre a l'ecran. `tabular-nums` fige la largeur des
   chiffres : sans lui, la ligne tremble a chaque increment. */
.bf-nb { font-variant-numeric: tabular-nums; }

@media (prefers-reduced-motion: reduce) {
  .heritage__photo img { transition: none; transform: none; }
  .js [data-reveal] .heritage__texte > * { opacity: 1; transform: none; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   LA SIGNATURE DU PIED DE PAGE — le code de hildenkaira.fi, repris
   ---------------------------------------------------------------------------
   Leur CSS, relevé dans `3-copies/hildenkaira.fi/copie` :
       .footer-logo      { display:flex; justify-content:center;
                           align-items:flex-start; width:100%; height:24em;
                           position:relative; z-index:1; pointer-events:none }
       .footer-logo_item { height:100% }
   Leur animation (timeline t-41490f8e) : chaque lettre `y: 30vh → 0vh`,
   DU CENTRE VERS L'EXTÉRIEUR, décalage 0.02 entre chaque paire, scrubée par le
   scroll sur le pied de page (start « 10% bottom », end « bottom top »).
   Le scrub est piloté par reveal.js, qui écrit --p ; le reste est ici.

   ⛔ La hauteur ne se fixe pas en `em` comme chez eux : notre mot n'a pas le même
      nombre de lettres. `--rapport` vient du build (largeur totale ÷ hauteur), donc
      le mot remplit toujours exactement la largeur, de 320 px à 1920.
   ═══════════════════════════════════════════════════════════════════════════ */
/* 19/09, Saadou : « enleve porteclose du haut du footer, mets le truc a la
   hildenkaira a la place, et fais-moi le VRAI logo ». Donc : la signature prend
   la place du petit logo, en tete du pied, et a pleine opacite — c'est le logo,
   pas un filigrane. */
.pied-signe { display: flex; justify-content: center; align-items: flex-start;
              /* ⛔ le retrait passe en MARGE, pas en padding : avec border-box,
                 `aspect-ratio` se calcule sur la boite entiere, et les lettres
                 (hautes de 100 % du CONTENU) se faisaient couper sur les cotes. */
              /* ⛔ PLEINE LARGEUR, bord à bord (Saadou, 20/09 : « t'as laissé un
                 petit écart à droite »). La signature du pied est le mot de la
                 marque posé au bas de la page : un retrait la ferait passer pour
                 une image dans une colonne. `width: 100%` et non `100vw` — la
                 barre de defilement compte dans `vw`. */
              width: 100%; aspect-ratio: var(--rapport, 10);
              position: relative; z-index: 1; pointer-events: none;
              overflow: clip;                /* les lettres montent de sous le bord */
              margin: 0 0 clamp(34px, 4.5vw, 64px);
              max-width: none; }
.pied-signe__l { height: 100%; width: auto; max-width: none; display: block; }

/* chaque lettre monte depuis 30vh, exactement comme chez eux. `--y` est calculé
   par reveal.js à partir de la progression du scroll et du rang de la lettre. */
.js .pied-signe__l { transform: translate3d(0, var(--y, 30vh), 0);
                     will-change: transform; }

@media (prefers-reduced-motion: reduce) {
  .js .pied-signe__l { transform: none; }
}
