/* base.css : structure commune à tous les thèmes.
   Les thèmes fournissent les variables (--bg, --ink, --accent, --font-display…)
   et peuvent surcharger librement les composants. */

* { box-sizing: border-box; margin: 0; padding: 0; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.65;
}

/* height: auto est OBLIGATOIRE ici. Les images de galerie portent width="" et height=""
   en attributs HTML (le navigateur réserve la place avant le chargement, la page ne saute
   plus quand les photos arrivent). Ces attributs valent une hauteur CSS : sans height: auto,
   une photo 1536x2048 posée dans une colonne de 360 px garde 2048 px de haut et se retrouve
   étirée en object-fit: fill. Panne du 03/09/2026 sur Lo Plomberie : mosaïque étirée quatre
   fois en hauteur. Toute règle qui donne une largeur à une image doit donner sa hauteur ou
   son aspect-ratio ; outils/controle.js le vérifie. */
img { display: block; max-width: 100%; height: auto; }
a { color: inherit; }

h1, h2, h3 { font-family: var(--font-display); line-height: 1.12; text-wrap: balance; }
h1 { font-size: clamp(2.2rem, 5.4vw, 3.6rem); font-weight: var(--poids-titre, 700); }
h2 { font-size: clamp(1.6rem, 3.2vw, 2.3rem); font-weight: var(--poids-titre, 700); margin-bottom: 0.9em; }
h3 { font-size: 1.18rem; margin-bottom: 0.35em; }

.section-inner { max-width: 1160px; margin: 0 auto; padding: clamp(64px, 9vw, 110px) 24px; }

/* ---------- header ---------- */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: var(--header-bg, var(--bg));
  border-bottom: 1px solid var(--line);
}
.header-inner {
  max-width: 1160px; margin: 0 auto; padding: 14px 24px;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.brand { text-decoration: none; display: flex; align-items: center; gap: 10px; min-width: 0; }
/* Les logos arrivent dans tous les formats : bandeau horizontal, carré, empilé.
   On borne la hauteur ET la largeur pour qu'aucun ne domine ni ne devienne illisible. */
.brand-logo {
  height: clamp(40px, 4.6vw, 50px); width: auto;
  max-width: min(180px, 34vw); object-fit: contain;
  flex: 0 0 auto;
}
.brand-name {
  font-family: var(--font-display); font-weight: var(--poids-titre, 700);
  font-size: 1.25rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  color: var(--ink, inherit);
}
.header-tel {
  font-weight: 600; text-decoration: none; white-space: nowrap;
  color: var(--accent-fonce, var(--accent));
}

/* ---------- boutons ---------- */
.btn {
  display: inline-block; text-decoration: none; text-align: center;
  font-family: var(--font-body); font-weight: 600; font-size: 1rem;
  padding: 14px 26px; border-radius: var(--radius, 4px);
  border: 2px solid transparent; cursor: pointer;
  transition: filter 0.15s ease, background 0.15s ease;
}
.btn-plein { background: var(--accent); color: var(--sur-accent, #fff); }
.btn-plein:hover { filter: brightness(1.08); }
.btn-contour { border-color: currentColor; background: transparent; }
.btn:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

/* ---------- héro : plein ---------- */
.hero-plein { position: relative; min-height: min(88vh, 780px); display: flex; align-items: flex-end; }
.hero-plein .hero-media { position: absolute; inset: 0; overflow: hidden; }
.hero-plein .hero-media img {
  width: 100%; height: 100%; object-fit: cover;
  object-position: var(--hero-cadrage-x, 50%) var(--hero-cadrage, 50%);
}
.hero-plein .hero-voile {
  position: absolute; inset: 0;
  background: var(--hero-voile, linear-gradient(to top, rgba(10,10,10,0.72) 0%, rgba(10,10,10,0.25) 45%, rgba(10,10,10,0.05) 100%));
}
.hero-plein .hero-contenu {
  position: relative; max-width: 1160px; width: 100%; margin: 0 auto;
  padding: 72px 24px 64px; color: var(--hero-ink, #fff);
}
.hero-plein h1 { max-width: 15ch; }
.hero-plein .hero-sous { margin-top: 14px; max-width: 52ch; font-size: 1.12rem; opacity: 0.94; }

.hero-metier {
  font-size: 0.85rem; letter-spacing: 0.14em; text-transform: uppercase;
  font-weight: 600; margin-bottom: 16px; color: var(--metier-ink, inherit);
}
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 28px; }

/* ---------- héro : scindé ----------
   La hauteur est fixée par la colonne de texte, JAMAIS par la photo. La photo est
   détachée du flux (position absolue) puis recadrée : sans cela, une photo verticale
   (portrait du dirigeant) étirerait le héro sur 1500 px et repousserait tout le contenu. */
.hero-scinde {
  display: grid; grid-template-columns: 1.05fr 1fr;
  min-height: min(78vh, 660px);
  max-height: 760px;
}
.hero-scinde .hero-texte {
  display: flex; flex-direction: column; justify-content: center;
  padding: clamp(40px, 5.5vw, 80px);
  min-width: 0;
}
/* colonne deux fois plus étroite que la page : le titre se cale dessus */
.hero-scinde h1 { font-size: clamp(1.9rem, 3.3vw, 3rem); }
.hero-scinde .hero-sous { margin-top: 16px; max-width: 48ch; font-size: 1.1rem; color: var(--muted); }
/* La photo ne mord plus l'angle de la page. Collée au bord haut et au bord droit elle
   fait bandeau de publicité ; détachée, avec de l'air autour, elle fait mise en page. */
.hero-scinde .hero-photo {
  position: relative; overflow: hidden; min-height: 340px;
  margin: var(--hero-air, clamp(16px, 1.8vw, 28px));
  margin-left: 0;
  border-radius: var(--radius-photo, 22px);
}
.hero-scinde .hero-photo img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  object-position: var(--hero-cadrage-x, 50%) var(--hero-cadrage, 20%);
}

/* ---------- héro : photo montrée en entier ----------
   Classe posée par build.js dans deux cas : photo plus haute que large, ou photo de
   l'artisan marquée à la planche contact (quel que soit son format).
   Deux pièges, évités ici : la recadrer dans un panneau paysage lui coupe la tête ou
   le buste ; la poser en entier DANS ce panneau laisse deux bandes vides autour
   d'elle. La solution n'est pas de corriger la photo, c'est de changer de contenant :
   le héro devient un bloc centré comme le reste de la page, et la photo devient une
   carte au format exact du fichier (--hero-ratio). Rien n'est coupé, rien n'est vide.
   Le format du contenant suit la photo, jamais l'inverse. */
.hero-scinde.hero-photo-entiere {
  display: flex; align-items: center;
  gap: clamp(32px, 4.5vw, 72px);
  max-width: 1160px; margin: 0 auto;
  padding: clamp(44px, 6vw, 88px) 24px;
  min-height: 0; max-height: none;
}
.hero-scinde.hero-photo-entiere .hero-texte { flex: 1 1 auto; min-width: 0; padding: 0; }
/* Face à une carte verticale, un titre en pavé étroit et haut fait bloc avec elle ;
   étalé sur toute la colonne il laisserait un trou blanc au milieu du héro. */
.hero-scinde.hero-photo-entiere h1 { max-width: 14ch; }
.hero-scinde.hero-photo-entiere .hero-sous { max-width: 34ch; }
/* La hauteur de la carte est décidée ici, jamais par les pixels du fichier : sans ce
   plafond une photo de 1700 px de haut repousserait les boutons d'appel sous la ligne
   de flottaison. La largeur en découle par le ratio, donc aucun recadrage. */
.hero-photo-entiere .hero-photo {
  flex: 0 0 auto;
  margin: 0;
  position: relative; overflow: hidden;
  min-height: 0;
  height: clamp(360px, 66vh, 600px);
  aspect-ratio: var(--hero-ratio, 0.7);
  width: auto;
  border-radius: var(--radius-photo, 22px);
  box-shadow: 0 10px 30px rgba(16, 20, 26, 0.11);
}
.hero-photo-entiere .hero-photo img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: center;
}
/* Photo entière PLUS LARGE QUE HAUTE (artisan devant sa camionnette, équipe en pied).
   Elle se pilote par sa largeur : réglée en hauteur comme une verticale, la carte
   s'étalerait sur 900 px et écraserait la colonne de texte contre le bord. */
.hero-photo-entiere.hero-photo-large .hero-photo {
  height: auto;
  width: min(52%, 620px);
}
.hero-scinde.hero-photo-entiere.hero-photo-large h1 { max-width: 18ch; }
.hero-scinde.hero-photo-entiere.hero-photo-large .hero-sous { max-width: 42ch; }
/* Le nom de l'artisan posé sur sa photo : c'est ce qui transforme une image en personne
   à qui l'on parle. Écrit seulement si le client a donné son prénom (hero.personne). */
.hero-photo .hero-personne {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 1;
  margin: 0; padding: 42px 20px 16px;
  background: linear-gradient(to top, rgba(12, 14, 17, 0.74) 0%, rgba(12, 14, 17, 0.36) 55%, rgba(12, 14, 17, 0) 100%);
  color: #fff; font-size: 0.95rem; font-weight: 600; letter-spacing: 0.01em;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}

/* ---------- héro : bandeau ---------- */
.hero-bandeau .hero-texte { max-width: 1160px; margin: 0 auto; padding: clamp(56px, 7vw, 96px) 24px clamp(36px, 4vw, 56px); }
.hero-bandeau h1 { max-width: 22ch; }
.hero-bandeau .hero-ligne {
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between;
  gap: 24px; margin-top: 20px;
}
.hero-bandeau .hero-sous { max-width: 46ch; font-size: 1.1rem; color: var(--muted); }
.hero-bandeau .hero-actions { margin-top: 0; }
.hero-bandeau .hero-photo {
  position: relative; overflow: hidden; height: min(56vh, 520px);
  width: min(100% - 48px, 1160px);
  margin: 0 auto var(--hero-air, clamp(16px, 1.8vw, 28px));
  border-radius: var(--radius-photo, 22px);
}
.hero-bandeau .hero-photo img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  object-position: var(--hero-cadrage-x, 50%) var(--hero-cadrage, 20%);
}

/* ---------- héro : sobre (aucune photo) ----------
   Pour les clients sans photo de chantier. Le fond profond et la typographie tiennent
   la page : c'est un parti pris, pas un manque. */
/* Sélecteur DOUBLE (.hero.hero-sobre) et non .hero-sobre : les classes bande-a / bande-b
   sont posées en fin de fichier et gagneraient à égalité de spécificité, ce qui repeindrait
   ce héro en clair. Toute règle de héro qui pose un fond dans base.css doit s'écrire ainsi. */
.hero.hero-sobre {
  background: var(--sobre-bg, var(--ink));
  color: var(--sobre-ink, var(--bg));
  position: relative;
  overflow: hidden;
}
.hero-sobre::after {
  content: ""; position: absolute; right: -12%; top: -35%;
  width: 70%; aspect-ratio: 1; border-radius: 50%;
  background: radial-gradient(circle, color-mix(in srgb, var(--accent) 26%, transparent) 0%, transparent 68%);
  pointer-events: none;
}
.hero-sobre .hero-contenu {
  position: relative; z-index: 1;
  max-width: 1160px; margin: 0 auto;
  padding: clamp(72px, 12vw, 150px) 24px clamp(56px, 9vw, 110px);
}
.hero-sobre h1 { max-width: 17ch; }
.hero-sobre .hero-metier { color: var(--sobre-accent, var(--accent)); }
.hero-sobre .hero-sous {
  margin-top: 18px; max-width: 54ch; font-size: 1.15rem;
  color: color-mix(in srgb, var(--sobre-ink, var(--bg)) 78%, transparent);
}
.hero-sobre .btn-contour { color: var(--sobre-ink, var(--bg)); }
.hero-zone {
  display: flex; flex-wrap: wrap; gap: 8px 0;
  margin-top: clamp(36px, 6vw, 60px);
  padding-top: 22px;
  border-top: 1px solid color-mix(in srgb, var(--sobre-ink, var(--bg)) 22%, transparent);
  font-size: 0.9rem;
  color: color-mix(in srgb, var(--sobre-ink, var(--bg)) 68%, transparent);
}
.hero-zone span + span::before {
  content: "·"; margin: 0 12px; color: var(--sobre-accent, var(--accent));
}

/* ---------- héro : marque (aucune photo, un logo) ----------
   Le client n'a rien à montrer que son identité : conciergerie, conseil, services. Le héro
   sobre reçoit alors le logo en grand, posé sur sa propre carte (fond du logo lui-même :
   un JPEG à fond blanc garde son blanc, un PNG détouré flotte sur le marine). Le fond du
   héro et l'accent sont tirés des couleurs mesurées sur le logo (build.js), donc la page
   est visiblement celle de cette entreprise, pas un gabarit. */
.hero-marque .hero-contenu {
  display: grid; grid-template-columns: 1.15fr 0.85fr;
  gap: clamp(32px, 5vw, 80px); align-items: center;
  padding-top: clamp(56px, 8vw, 110px); padding-bottom: clamp(56px, 8vw, 110px);
}
.hero-marque .hero-texte { min-width: 0; }
.hero-marque .hero-logo { display: flex; justify-content: center; align-items: center; }
.hero-marque .hero-logo img {
  width: min(100%, 440px); height: auto; max-height: 380px; object-fit: contain;
  border-radius: var(--radius-photo, 22px);
}
.hero-marque .hero-logo.hero-logo-carte img {
  background: var(--marque-fond, #ffffff);
  padding: clamp(22px, 3vw, 44px);
  box-shadow: 0 16px 44px rgba(0, 0, 0, 0.28);
}

/* ---------- engagements (sans image) ---------- */
.engagements-liste {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: clamp(24px, 4vw, 48px);
}
/* Marge intérieure écrite en RACCOURCI, pas en padding-top : un thème qui donne un fond à
   la carte doit redéclarer « padding », sinon le texte touche les bords et déborde par le bas
   (constaté sur SUD Rénovation le 12/09/2026, thème ardoise). outils/controle.js refuse
   maintenant toute règle de thème qui pose un fond de carte sans marge intérieure. */
.engagement { padding: 18px 0 0; border-top: 2px solid var(--accent); }
.engagement h3 { margin-bottom: 8px; }
.engagement p { color: var(--muted); }

/* ---------- services : version sans photo ----------
   Numérotation discrète pour donner du rythme quand aucune image ne le fait. */
.services-sans-photo .service {
  grid-template-columns: 64px 1fr;
  align-items: start;
  padding: clamp(24px, 3vw, 34px) 0;
}
.services-sans-photo .service::before {
  content: counter(service, decimal-leading-zero);
  font-family: var(--font-display); font-size: 1rem; font-weight: var(--poids-titre, 700);
  color: var(--accent); padding-top: 3px;
}
.services-sans-photo .services-liste { counter-reset: service; }
.services-sans-photo .service { counter-increment: service; }
.services-sans-photo .service-texte p { max-width: 62ch; }

/* ---------- services ---------- */
.services-liste { display: grid; gap: 0; }
.service {
  display: grid; grid-template-columns: 300px 1fr; gap: clamp(20px, 4vw, 48px);
  padding: 30px 0; border-top: 1px solid var(--line); align-items: center;
}
.service:last-child { border-bottom: 1px solid var(--line); }
.service-photo img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; border-radius: var(--radius-img, 0); }
.service:not(:has(.service-photo)) { grid-template-columns: 1fr; }
.service-texte p { color: var(--muted); max-width: 68ch; }

/* ---------- galerie mosaïque ---------- */
.mosaique { columns: 3 300px; column-gap: 18px; }
.mosaique figure { break-inside: avoid; margin-bottom: 18px; }
.mosaique img { width: 100%; height: auto; border-radius: var(--radius-img, 0); cursor: zoom-in; }

/* ---------- galerie rangées ---------- */
.rangees { display: flex; flex-wrap: wrap; gap: 14px; }
.rangees figure {
  flex-grow: var(--ratio, 1); flex-basis: calc(var(--ratio, 1) * 240px);
  min-width: 200px; max-width: calc(var(--ratio, 1) * 460px);
}
.rangees img { width: 100%; aspect-ratio: var(--ratio, 4/3); object-fit: cover; border-radius: var(--radius-img, 0); cursor: zoom-in; }

figcaption { font-size: 0.85rem; color: var(--muted); padding-top: 7px; }

/* ---------- avant / après ---------- */
/* Deux photos du même chantier, côte à côte et à la même taille : c'est le rapprochement
   qui vend, pas la photo. Elles restent côte à côte sur téléphone aussi, les empiler
   obligerait à faire défiler entre les deux et la comparaison serait perdue. */
/* colonnes plutôt que grille : les paires n'ont pas toutes le même format (une véranda
   en 3:2 à côté d'un couloir en 9:16), et une grille aligne les rangées sur la plus haute,
   ce qui creuse un trou blanc à côté de chaque paire courte. */
.duos { columns: 2 320px; column-gap: clamp(18px, 2.4vw, 30px); }
.duos .duo { break-inside: avoid; margin: 0 0 clamp(26px, 3.2vw, 40px); }
.duo-paire { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
.duo-vue { position: relative; overflow: hidden; }
.duo-vue img {
  width: 100%; aspect-ratio: var(--ratio, 0.75); object-fit: cover;
  border-radius: var(--radius-img, 0); cursor: zoom-in;
}
.duo-etiquette {
  position: absolute; top: 0; left: 0;
  font-family: var(--font-display); font-weight: var(--poids-titre, 700);
  font-size: 0.7rem; letter-spacing: 0.09em; text-transform: uppercase;
  padding: 5px 11px; background: rgba(18, 20, 23, 0.86); color: #fff;
}
.duo-vue-apres .duo-etiquette { background: var(--accent); color: var(--sur-accent, #fff); }
.duo figcaption { padding-top: 10px; }

/* PHOTO COMPOSITE : le client a envoyé l'avant et l'après déjà montés dans une seule image,
   avec les mots « avant » et « après » incrustés. Rien à comparer par glissement, rien à
   étiqueter : on montre l'image entière. Elle traverse les deux colonnes parce qu'elle porte
   déjà deux vues côte à côte : dans une colonne de 320px, chaque moitié tomberait à 160px et
   le texte incrusté par le client deviendrait illisible. */
.duos .duo-seule { column-span: all; margin-inline: auto; max-width: min(100%, var(--large, 860px)); }
.duo-composite img {
  display: block; width: 100%; aspect-ratio: var(--ratio, 1.6); object-fit: cover;
  border-radius: var(--radius-img, 0); cursor: zoom-in;
}

/* Comparateur : site.js superpose les deux vues et pose une poignée. On glisse pour faire
   passer le chantier de l'avant à l'après, geste que tout le monde connaît. Sans JS la
   classe n'arrive jamais et les deux photos restent côte à côte : rien n'est perdu. */
.duo-glisse {
  position: relative; display: block; touch-action: pan-y; cursor: ew-resize;
  /* Sans ça, glisser sur une image la SÉLECTIONNE : voile bleu sur toute la photo, et sur
     mobile la poignée de sélection de texte s'invite. C'est un geste de manipulation, pas
     de lecture, il ne doit rien sélectionner ni rien déposer. */
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
}
.duo-glisse img { -webkit-user-drag: none; user-select: none; pointer-events: auto; }
.duo-glisse::selection, .duo-glisse *::selection { background: transparent; }
.duo-glisse .duo-vue { position: absolute; inset: 0; }
/* la vue « avant » reste dans le flux : c'est elle qui donne sa hauteur à la boîte */
.duo-glisse .duo-vue-avant { position: relative; }
.duo-glisse .duo-vue-apres { clip-path: inset(0 0 0 var(--curseur, 50%)); }
.duo-glisse .duo-vue-apres .duo-etiquette { left: auto; right: 0; }
.duo-glisse img { cursor: ew-resize; }
/* le zoom au survol décalerait un seul des deux côtés : la comparaison ne vaudrait plus rien */
.duo-glisse .duo-vue:hover img { transform: none; }
.duo-poignee {
  position: absolute; top: 0; bottom: 0; left: var(--curseur, 50%);
  width: 2px; margin-left: -1px; background: #fff;
  box-shadow: 0 0 0 1px rgba(18, 20, 23, 0.28); cursor: ew-resize;
}
.duo-poignee::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 40px; height: 40px; margin: -20px 0 0 -20px; border-radius: 50%;
  background: #fff; box-shadow: 0 2px 12px rgba(18, 20, 23, 0.35);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='12' viewBox='0 0 26 12'%3E%3Cpath d='M9.5 1 4 6l5.5 5M16.5 1 22 6l-5.5 5' fill='none' stroke='%2312141a' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
}
.duo-poignee:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
/* Mode d'emploi : consigne sous le titre + bulle sur la photo, effacée au premier geste */
.duo-consigne {
  display: flex; align-items: center; gap: 10px;
  margin: -6px 0 clamp(20px, 2.6vw, 30px); font-size: 0.95rem; opacity: 0.8;
}
.duo-consigne::before {
  content: ""; flex: none; width: 26px; height: 12px; background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='12' viewBox='0 0 26 12'%3E%3Cpath d='M9.5 1 4 6l5.5 5M16.5 1 22 6l-5.5 5' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='12' viewBox='0 0 26 12'%3E%3Cpath d='M9.5 1 4 6l5.5 5M16.5 1 22 6l-5.5 5' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
.duo-astuce {
  position: absolute; left: var(--curseur, 50%); top: calc(50% + 30px); transform: translateX(-50%);
  padding: 5px 12px; border-radius: 999px; white-space: nowrap; pointer-events: none;
  font-size: 0.75rem; font-weight: 600; background: rgba(18, 20, 23, 0.86); color: #fff;
  transition: opacity 0.35s ease;
}
.duo-manipule .duo-astuce { opacity: 0; }

/* ---------- à propos ---------- */
.apropos-inner { display: grid; grid-template-columns: 380px 1fr; gap: clamp(32px, 6vw, 80px); align-items: center; }
.apropos-photo-droite .apropos-inner { grid-template-columns: 1fr 380px; }
.apropos-photo-droite .apropos-photo { order: 2; }
.apropos-photo img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; border-radius: var(--radius-img, 0); }
.apropos-texte p { margin-bottom: 1em; max-width: 60ch; }
.apropos-inner:not(:has(.apropos-photo)) { grid-template-columns: 1fr; }

/* ---------- zone ---------- */
.zone .section-inner { padding-top: clamp(48px, 6vw, 80px); padding-bottom: clamp(48px, 6vw, 80px); }
.zone-phrase { color: var(--muted); max-width: 62ch; margin-bottom: 20px; }
.zone-villes { list-style: none; display: flex; flex-wrap: wrap; gap: 8px 0; font-weight: 600; }
.zone-villes li { display: flex; align-items: center; }
.zone-villes li + li::before { content: "·"; margin: 0 14px; color: var(--accent); font-weight: 700; }

/* ---------- avis ---------- */
.avis-liste { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 28px; }
.avis-item { border-left: 3px solid var(--accent); padding-left: 20px; }
.avis-item p { font-size: 1.05rem; margin-bottom: 10px; }
.avis-item cite { font-style: normal; color: var(--muted); font-size: 0.9rem; }
/* Avis Google : les étoiles et la mention de la source font toute la valeur du bloc. Un
   témoignage anonyme sur un site d'artisan ne prouve rien, le même avis marqué Google et
   vérifiable d'un clic prouve beaucoup. Or jaune de Google pour les étoiles : c'est le
   signal que l'œil reconnaît sans lire. Aucun script tiers, aucun cookie, aucun compte. */
.avis-source { color: var(--muted); font-size: 0.92rem; margin: -14px 0 26px; }
.avis-etoiles { display: flex; gap: 2px; margin-bottom: 10px; }
.avis-etoiles svg { width: 16px; height: 16px; fill: #FBBC04; }
.avis-lien { margin-top: 32px; }

/* ---------- contact ---------- */
.contact-inner { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(36px, 6vw, 80px); }
.contact-accroche { color: var(--muted); max-width: 40ch; }
.contact-tel {
  display: inline-block; margin: 22px 0 6px;
  font-family: var(--font-display); font-weight: var(--poids-titre, 700);
  font-size: clamp(1.7rem, 4vw, 2.6rem); text-decoration: none;
  color: var(--accent-fonce, var(--accent));
}
.contact-email a { color: var(--muted); }
.contact-horaires, .contact-adresse { color: var(--muted); margin-top: 10px; font-size: 0.95rem; }

.contact-form { display: grid; gap: 14px; align-content: start; }
.contact-form label { display: grid; gap: 6px; font-weight: 600; font-size: 0.92rem; }
.contact-form input, .contact-form textarea {
  font: inherit; padding: 12px 14px; width: 100%;
  border: 1px solid var(--line); border-radius: var(--radius, 4px);
  background: var(--champ-bg, var(--bg)); color: var(--ink);
}
.contact-form input:focus, .contact-form textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.contact-form .hp { position: absolute; left: -9999px; }
.champ-option { font-weight: 400; color: var(--muted); font-size: 0.85em; }
.form-etat { font-weight: 600; }
.form-rgpd { font-size: 0.78rem; color: var(--muted); }

/* ---------- footer ---------- */
.site-footer { border-top: 1px solid var(--line); background: var(--footer-bg, transparent); }
.footer-inner {
  max-width: 1160px; margin: 0 auto; padding: 36px 24px;
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: 20px;
  font-size: 0.9rem; color: var(--muted);
}
.footer-nap { display: grid; gap: 4px; }
.footer-nap strong { color: var(--ink); }
.footer-nap a { color: inherit; }
.footer-liens { display: grid; gap: 6px; align-content: start; text-align: right; }
.footer-liens a { color: inherit; }
.footer-reseaux { display: flex; gap: 12px; justify-content: flex-end; margin-bottom: 4px; }
.footer-reseaux a { opacity: 0.8; transition: opacity 0.2s ease; }
.footer-reseaux a:hover { opacity: 1; }
.footer-credit { font-size: 0.82rem; opacity: 0.75; transition: opacity 0.2s ease, color 0.2s ease; }
.footer-credit:hover { opacity: 1; color: var(--accent-fonce, var(--accent)); }

@media (max-width: 560px) {
  .footer-liens { text-align: left; }
  .footer-reseaux { justify-content: flex-start; }
}

/* ---------- galerie bandeau défilant ---------- */
.galerie-bandeau .galerie-bandeau-titre { padding-bottom: 30px; }
.bandeau { overflow: hidden; padding-bottom: clamp(56px, 8vw, 100px); }
.bandeau-piste { display: flex; width: max-content; padding-left: 24px; }
.bandeau figure { flex: none; margin-right: 18px; }
.bandeau img {
  height: clamp(220px, 30vw, 320px); width: auto;
  border-radius: var(--radius-img, 0); cursor: zoom-in;
}
.bandeau-piste.anime { animation: defile linear infinite; }
.bandeau:hover .bandeau-piste.anime,
.bandeau:focus-within .bandeau-piste.anime { animation-play-state: paused; }
@keyframes defile {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .bandeau { overflow-x: auto; }
  .bandeau-piste.anime { animation: none; }
}

/* ---------- mouvement : entrée du héro (CSS pur, une seule fois) ---------- */
@media (prefers-reduced-motion: no-preference) {
  .hero .hero-metier, .hero h1, .hero .hero-sous, .hero .hero-actions {
    animation: hero-entre 0.85s cubic-bezier(0.2, 0.65, 0.25, 1) both;
  }
  .hero h1 { animation-delay: 0.08s; }
  .hero .hero-sous { animation-delay: 0.16s; }
  .hero .hero-actions { animation-delay: 0.26s; }
  .hero-plein .hero-media img, .hero-scinde .hero-photo img, .hero-bandeau .hero-photo img {
    animation: hero-zoom 1.5s ease-out both;
  }
}
@keyframes hero-entre {
  from { opacity: 0; transform: translateY(18px); }
  to { opacity: 1; transform: none; }
}
@keyframes hero-zoom {
  from { transform: scale(1.05); }
  to { transform: none; }
}

/* ---------- mouvement : apparitions au défilement (classes posées par site.js) ---------- */
.rv {
  opacity: 0; transform: translateY(16px);
  transition: opacity var(--vitesse-reveal, 0.6s) ease, transform var(--vitesse-reveal, 0.6s) ease;
}
.rv-visible { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .rv { opacity: 1; transform: none; transition: none; }
}

/* ---------- micro-interactions ---------- */
.site-header { transition: box-shadow 0.25s ease; }
.site-header.scrolle { box-shadow: 0 2px 16px rgba(20, 15, 8, 0.09); }
.btn { transition: filter 0.15s ease, transform 0.2s ease, box-shadow 0.2s ease; }
.btn:hover { transform: translateY(-2px); box-shadow: 0 6px 16px rgba(20, 15, 8, 0.14); }
.mosaique img, .rangees img, .bandeau img, .service-photo img, .duo-vue img {
  transition: transform 0.5s ease;
}
.mosaique figure:hover img, .rangees figure:hover img,
.bandeau figure:hover img, .service:hover .service-photo img,
.duo-vue:hover img {
  transform: scale(1.02);
}
@media (prefers-reduced-motion: reduce) {
  .btn:hover { transform: none; }
  .mosaique figure:hover img, .rangees figure:hover img,
  .bandeau figure:hover img, .service:hover .service-photo img,
  .duo-vue:hover img { transform: none; }
}

/* ---------- lightbox ---------- */
.lightbox {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(8, 8, 8, 0.92);
  display: flex; align-items: center; justify-content: center;
}
.lightbox[hidden] { display: none; }
.lightbox img { max-width: 92vw; max-height: 90vh; }
.lightbox-fermer {
  position: absolute; top: 14px; right: 20px;
  font-size: 2.2rem; line-height: 1; color: #fff;
  background: none; border: none; cursor: pointer;
}

/* ---------- responsive ---------- */
/* RÈGLE MOBILE INTANGIBLE : à l'ouverture, le visiteur doit voir le titre ET le bouton
   d'appel sans faire défiler. Une photo de héro ne passe donc jamais avant le texte,
   et sa hauteur est toujours plafonnée. Ne pas assouplir sans revérifier sur 375x812. */
@media (max-width: 960px) {
  /* Ordre mobile : accroche, titre, sous-titre, PHOTO, puis boutons d'appel.
     La photo se voit immédiatement, et le bouton d'appel reste le dernier élément lu,
     juste avant le geste d'appeler. `display: contents` fait remonter les enfants de la
     colonne de texte au niveau du héro, seule façon d'intercaler la photo entre eux. */
  .hero-scinde { display: flex; flex-direction: column; min-height: 0; max-height: none; }
  .hero-scinde .hero-texte { display: contents; }
  .hero-scinde .hero-metier { order: 1; padding: clamp(22px, 5vw, 36px) 24px 0; }
  .hero-scinde h1 { order: 2; padding: 0 24px; }
  .hero-scinde .hero-sous { order: 3; padding: 0 24px; }
  .hero-scinde .hero-photo {
    order: 4; min-height: 0; height: 28vh; max-height: 250px;
    /* mêmes gouttières que le texte : une bande qui court d'un bord à l'autre pendant
       que tout le reste est en retrait fait bandeau collé, pas mise en page */
    margin: clamp(18px, 4vw, 28px) 24px 0;
    border-radius: var(--radius-photo, 22px);
  }
  /* La bande mobile est bien plus courte que le panneau de bureau : le même point
     d'ancrage y couperait le visage. On vise le centre haut, là où sont les visages. */
  .hero-scinde .hero-photo img, .hero-bandeau .hero-photo img {
    object-position: var(--hero-cadrage-x, 50%) var(--hero-cadrage-mobile, 32%);
  }
  .hero-scinde .hero-actions {
    order: 5; padding: 0 24px clamp(24px, 5vw, 36px);
    margin-top: clamp(18px, 4vw, 26px);
  }

  /* Verticale sur téléphone : la carte prend toute la largeur utile. Le portrait du
     dirigeant est l'argument de confiance no 1, il passe donc avant le fait de voir le
     bouton d'appel sans faire défiler : on l'atteint d'un pouce, il n'a pas à être
     visible d'emblée. Seul garde-fou, la hauteur plafonne à 66vh pour qu'une photo
     très étroite ne remplisse pas l'écran entier. */
  .hero-scinde.hero-photo-entiere {
    align-items: stretch; gap: 0;
    max-width: none; margin: 0; padding: 0;
  }
  /* le pavé étroit n'a plus de sens dans une colonne unique : il ajouterait une ligne
     de titre, donc 70 px de moins pour la photo et le bouton d'appel */
  .hero-scinde.hero-photo-entiere h1 { max-width: none; }
  .hero-scinde.hero-photo-entiere .hero-photo {
    align-self: center;
    width: min(100% - 48px, calc(66vh * var(--hero-ratio, 0.7)));
    height: auto; max-height: none;
    margin: clamp(18px, 4vw, 28px) 0 0;
  }

  /* Marque sur téléphone : accroche, titre, sous-titre, LOGO (petit, il ne pousse pas le
     bouton d'appel sous le pli), boutons, zone. Même mécanique que le héro scindé. */
  .hero-marque .hero-contenu { display: flex; flex-direction: column; align-items: stretch; gap: 0; }
  .hero-marque .hero-texte { display: contents; }
  .hero-marque .hero-metier { order: 1; }
  .hero-marque h1 { order: 2; }
  .hero-marque .hero-sous { order: 3; }
  .hero-marque .hero-logo { order: 4; margin-top: clamp(18px, 4vw, 26px); justify-content: flex-start; }
  .hero-marque .hero-logo img { width: auto; height: auto; max-width: min(70%, 260px); max-height: 130px; object-fit: contain; }
  .hero-marque .hero-logo.hero-logo-carte img { padding: 12px 16px; }
  .hero-marque .hero-actions { order: 5; margin-top: clamp(18px, 4vw, 26px); }
  .hero-marque .hero-zone { order: 6; }

  .hero-bandeau .hero-photo { height: min(34vh, 280px); width: 100%; margin: 0; border-radius: 0; }
  .apropos-inner, .apropos-photo-droite .apropos-inner { grid-template-columns: 1fr; }
  .apropos-photo-droite .apropos-photo { order: 0; }
  .apropos-photo img { aspect-ratio: 16 / 10; }
  .contact-inner { grid-template-columns: 1fr; }
}
@media (max-width: 720px) {
  .service, .service:not(:has(.service-photo)) { grid-template-columns: 1fr; gap: 14px; }
  .services-sans-photo .service { grid-template-columns: 1fr; gap: 6px; }
  .service-photo img { aspect-ratio: 16 / 9; }
  .mosaique { columns: 2 160px; column-gap: 12px; }
  .mosaique figure { margin-bottom: 12px; }
  .header-tel span { display: none; }
  .brand { gap: 8px; }
  .brand-logo { height: 36px; max-width: 84px; }
  .brand-name {
    white-space: normal; overflow: visible;
    font-size: 0.95rem; line-height: 1.12;
    /* sans ça la coupure tombe sur le trait d'union : « JD Rénovation Multi- / Services » */
    text-wrap: balance;
  }
}
@media (max-width: 520px) {
  body { font-size: 16px; }

  /* Budget du premier écran sur téléphone : titre, sous-titre, boutons ET photo
     doivent tenir ensemble. C'est pour ça que le titre est plafonné ici, quel que
     soit le thème, et que les marges sont resserrées. */
  .hero h1 { font-size: clamp(1.75rem, 8.2vw, 2.15rem); line-height: 1.14; }
  .hero .hero-metier { font-size: 0.78rem; margin-bottom: 10px; }
  .hero .hero-sous { font-size: 1rem; margin-top: 12px; }
  .hero-actions { flex-direction: column; align-items: stretch; gap: 9px; }
  .hero-plein .hero-actions, .hero-bandeau .hero-actions, .hero-sobre .hero-actions { margin-top: 20px; }
  .btn { padding: 13px 20px; font-size: 0.95rem; }

  /* héro plein écran : le texte est superposé, on réduit la hauteur pour que les
     boutons d'appel restent dans le premier écran */
  .hero-plein { min-height: min(70vh, 560px); }
  .hero-plein .hero-contenu { padding: 44px 24px 32px; }
  .hero-scinde .hero-photo { height: 26vh; max-height: 220px; }
  .hero-bandeau .hero-texte { padding-top: 32px; padding-bottom: 24px; }
  .header-inner { padding: 9px 20px; }
}

/* ---------- services : variante « cartes » (variantes.services = cartes) ----------
   Grille de cartes, photo en bandeau au-dessus du texte. Ajoutée le 03/09/2026 pour que
   deux sites du même thème ne se ressemblent pas : la liste (par défaut) reste inchangée. */
.services-cartes .services-liste {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); gap: 22px; align-items: stretch;
}
.services-cartes .service, .services-cartes .service:not(:has(.service-photo)) {
  grid-template-columns: 1fr; gap: 0; padding: 0; margin: 0; align-items: start;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-photo, 10px);
  overflow: hidden; box-shadow: 0 10px 28px rgba(20, 15, 8, 0.06);
}
.services-cartes .service:last-child { border-bottom: 1px solid var(--line); }
.services-cartes .service-photo img { aspect-ratio: 16 / 10; border-radius: 0; }
.services-cartes .service-texte { padding: 22px 24px 26px; }
.services-cartes .service-texte p { max-width: none; }
/* Le mode « sans photo » se marque sur <main>, pas sur la section : le sélecteur doit donc
   descendre, sinon la carte garde son padding à zéro et le numéro se colle au bord.
   Même faute de famille que le bloc engagements du thème ardoise (12/09/2026). */
.services-sans-photo .services-cartes .service { grid-template-columns: 1fr; padding: 26px 24px; }
.services-sans-photo .services-cartes .service::before { margin-bottom: 10px; }
@media (max-width: 720px) {
  .services-cartes .services-liste { gap: 16px; }
  .services-cartes .service-photo img { aspect-ratio: 16 / 9; }
}

/* Section rénovations en cours avec onglets par ville */
.renos-onglets { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1.5rem; }
.renos-onglet {
  padding: .5rem 1.25rem; border: 2px solid var(--accent); border-radius: var(--radius, 4px);
  background: transparent; color: var(--accent); cursor: pointer; font: inherit; font-size: .95rem;
  font-weight: 600; transition: background .18s, color .18s;
}
.renos-onglet.actif, .renos-onglet:hover { background: var(--accent); color: var(--sur-accent, #fff); }
.renos-paneau { display: none; }
.renos-paneau.actif { display: block; }

/* ---------- galerie bandeau sur deux rangs (12/09/2026) ----------
   Au dela de 12 photos, build.js coupe la galerie en deux rubans : le haut file vers la
   droite, le bas vers la gauche. Deux sens contraires se lisent comme un mouvement voulu,
   et 48 photos passent sans ajouter un pixel de hauteur au-dela du second rang. */
.bandeau.bandeau-haut { padding-bottom: 14px; }
.bandeau-piste.anime.vers-droite { animation-direction: reverse; }

/* ---------- section vidéo (14/09/2026) ----------
   RIEN N'EST CHARGÉ AVANT LE CLIC, c'est toute la raison pour laquelle une vidéo est
   désormais permise sur ces sites. Fichier local : preload="none" + poster, donc le
   visiteur paie une image tant qu'il ne lance rien. YouTube : une façade (l'affiche et
   un bouton), l'iframe n'est posée qu'au clic, ce qui évite aussi de déposer les cookies
   de Google chez un visiteur qui ne regarde pas (site.js).
   Le contenant prend le format EXACT du fichier (--ratio, mesuré par outils/videos.js) :
   une verticale de téléphone ne se retrouve pas en bande noire dans un cadre 16/9. */
.video-intro { color: var(--muted); max-width: 62ch; margin-bottom: 28px; }
.video-liste { display: grid; gap: clamp(20px, 3vw, 34px); grid-template-columns: 1fr; justify-items: center; }
.video-liste:has(.video-item + .video-item) { grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); }
.video-item {
  margin: 0; width: 100%;
  /* La HAUTEUR est le vrai plafond, pas la largeur : une vidéo verticale de téléphone
     (9/16) dans une colonne de 560 px ferait 1000 px de haut, et le visiteur ne verrait
     plus rien d'autre. On borne donc la LARGEUR du contenant par la hauteur qu'on
     s'autorise, ratio compris (--ratio-num = largeur / hauteur, posé par le build).
     Le cadre garde le format exact du fichier : jamais de bande noire, et la légende
     reste collée sous la vidéo au lieu de flotter à 200 px. */
  max-width: min(860px, calc(74vh * var(--ratio-num, 1.78)));
}
.video-item video, .video-facade {
  display: block; width: 100%; aspect-ratio: var(--ratio, 16 / 9);
  background: #000; border-radius: var(--radius-photo, 10px); overflow: hidden;
}
.video-item video { object-fit: cover; }
.video-item figcaption { color: var(--muted); font-size: 0.92rem; padding-top: 10px; }

.video-facade { position: relative; padding: 0; border: 0; cursor: pointer; }
.video-facade img { width: 100%; height: 100%; object-fit: cover; display: block; }
.video-facade::after {
  content: ""; position: absolute; inset: 0; background: rgba(0, 0, 0, 0.18);
  transition: background .18s;
}
.video-facade:hover::after, .video-facade:focus-visible::after { background: rgba(0, 0, 0, 0.05); }
.video-lecture {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 1;
  width: 74px; height: 52px; border-radius: 14px; background: rgba(17, 17, 17, 0.78);
  transition: background .18s, transform .18s;
}
.video-lecture::before {
  content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-42%, -50%);
  border-style: solid; border-width: 11px 0 11px 18px; border-color: transparent transparent transparent #fff;
}
.video-facade:hover .video-lecture, .video-facade:focus-visible .video-lecture {
  background: var(--accent); transform: translate(-50%, -50%) scale(1.06);
}
.video-item iframe { display: block; width: 100%; aspect-ratio: var(--ratio, 16 / 9); border: 0; border-radius: var(--radius-photo, 10px); }
@media (prefers-reduced-motion: reduce) {
  .video-facade::after, .video-lecture { transition: none; }
  .video-facade:hover .video-lecture, .video-facade:focus-visible .video-lecture { transform: translate(-50%, -50%); }
}
@media (max-width: 720px) {
  .video-item { max-width: min(860px, calc(68vh * var(--ratio-num, 1.78))); }
  .video-lecture { width: 62px; height: 44px; }
}

/* ---------- alternance des fonds, par POSITION (14/09/2026) ----------
   build.js pose bande-a / bande-b sur chaque section de <main>, en alternance, dans
   l'ordre où elles sortent. La couleur d'une section ne dépend donc plus de son nom
   (.services, .apropos…) mais de sa PLACE : déplacer une section dans le cockpit suffit,
   elle prend la couleur de l'emplacement où elle arrive, et deux bandes de même couleur
   ne peuvent plus se retrouver côte à côte.
   Ces deux règles sont VOLONTAIREMENT à la fin du fichier : à égalité de spécificité, la
   dernière gagne, donc elles passent devant les .services / .apropos plus haut. Elles
   perdent en revanche face à theme.css et au style.css du client, tous deux chargés après :
   épingler une section reste possible, ça reste l'exception.
   Un thème sombre n'a rien de plus à faire que redéfinir --bande-a et --bande-b ; un client
   qui veut sa propre alternance écrit .bande-a / .bande-b dans style.css, jamais .services.
   Le HÉRO porte lui aussi une bande (bande-a, rang 1) depuis le 14/09/2026 : les huit thèmes
   posent --bande-a égal à --bg, donc un héro sans fond propre (scindé, bandeau) faisait bloc
   avec la première section. Un héro qui peint son fond doit s'épingler en DOUBLE classe
   (.hero.hero-sobre), sinon ces deux règles-ci, plus bas dans le fichier, le repeignent.
   JAMAIS de fond attaché à un NOM de section (.apropos, .services…) : ni ici, ni dans un
   thème, ni dans le style.css d'un client. Le nom ne dit pas la place, et la couleur suit la
   place. Pour changer l'alternance : --bande-a / --bande-b. */
.bande-a { background: var(--bande-a, var(--bg)); }
.bande-b { background: var(--bande-b, var(--surface-2, var(--surface))); }
