/* ==========================================================================
   KIT — Feuille de style unique de la base
   --------------------------------------------------------------------------
   Un seul fichier, en sections numérotées. Sans build step, chaque <link>
   supplémentaire coûte un aller-retour réseau : neuf fichiers de composants
   tueraient l'objectif « moins d'une seconde en 4G ».

   Les sections 0 à 4 forment le CONTRAT COMMUN : reset, éléments HTML,
   mise en page, boutons. Les composants (section 5 et suivantes) s'appuient
   dessus et ne les redéfinissent jamais.

   Aucune couleur, taille ou espacement n'est écrit en dur ici : tout passe par
   les variables de theme.css. C'est ce qui permet de rhabiller une page en
   changeant un seul fichier.
   LES TAUX DE CONTRASTE annotes dans ce fichier (« 9,1:1 », « 4,7:1 »…) valent
   pour la palette LIVREE AVEC LE KIT, et pour elle seule. Changer la palette
   les rend faux sans rien casser de visible : ils sont un point de depart, pas
   une garantie. A chaque nouveau client, les recalculer sur ses couleurs — la
   marche a suivre est dans le README, section « Contraste ».

   DEUX EXCEPTIONS, assumées et commentées sur place : le magenta des alarmes de
   brouillon, qui ne doit emprunter la couleur d'aucune marque sous peine de
   cesser d'alarmer, et les 48px de cible tactile, qui sont une contrainte
   physique et non un choix de style.

   SOMMAIRE
     0. Reset et réglages document
     1. Polices
     2. Éléments HTML
     3. Mise en page et utilitaires
     4. Boutons
     5. En-tête
     6. Accroche
     6B. Scènes animées
     7. Ce qui est inclus
     8. Tarifs
     9. Logos partenaires
    10. Réalisations (avant / après, exemples)
    11. Questions fréquentes
    12. Formulaire de contact
    13. Bouton collant mobile
    14. Pied de page
    15. Pages annexes
   ========================================================================== */


/* ==========================================================================
   0. RESET ET RÉGLAGES DOCUMENT
   ========================================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  /* Le défilement vers les ancres s'arrête sous l'en-tête, pas dessous. */
  scroll-padding-top: calc(var(--h-entete) + var(--e-4));
  -webkit-text-size-adjust: 100%;
}

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  margin: 0;
  background: var(--c-fond);
  color: var(--c-texte);
  font-family: var(--f-texte);
  font-size: var(--t-base);
  font-weight: var(--p-normal);
  line-height: var(--i-lache);
  /* Réserve la place de la barre collante mobile pour que le pied de page
     ne soit jamais masqué en fin de défilement. */
  padding-bottom: var(--h-collant);
  -webkit-font-smoothing: antialiased;
}

@media (min-width: 48em) {
  body { padding-bottom: 0; }
}

h1, h2, h3, h4, p, figure, blockquote, dl, dd {
  margin: 0;
}

ul, ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

img, picture, video, svg {
  display: block;
  max-width: 100%;
  height: auto;
}

input, button, textarea, select {
  font: inherit;
  color: inherit;
}

/* Anneau de focus unique pour tout le site. Visible au clavier uniquement,
   pour ne pas parasiter la souris. Ne jamais le supprimer sans remplaçant. */
:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

:focus:not(:focus-visible) {
  outline: none;
}

::selection {
  background: var(--c-accent);
  color: var(--c-sur-accent);
}

/* Dans les sections sombres, l'accent sert souvent DE FOND (c'est le cas dès
   qu'une charte tient en une couleur). Sélectionner un texte l'y ferait
   disparaître — accent sur accent. On inverse : fond clair, texte sombre. */
.section--sombre ::selection,
.accroche--sombre ::selection,
.pied ::selection {
  background: var(--c-fond);
  color: var(--c-texte);
}


/* Contraste renforcé. Le réglage système existe sur iOS, Android et Windows.
   On ne redessine pas la page : on épaissit les filets, qui sont la première
   chose à disparaître quand la vue baisse, et on rend la ligne de focus plus
   franche. Les couleurs de texte, déjà calculées pour AA, ne bougent pas. */
@media (prefers-contrast: more) {
  :focus-visible {
    outline-width: 3px;
  }

  .formulaire__saisie,
  .tarifs__carte,
  .formulaire__statut:not(:empty) {
    border-width: 2px;
  }

  .entete,
  .inclus__element {
    border-bottom-color: var(--c-texte-tertiaire);
  }
}


/* ==========================================================================
   1. POLICES
   --------------------------------------------------------------------------
   Deux familles au maximum, auto-hébergées dans fonts/. Aucun appel à un
   serveur tiers : c'est plus rapide, et cela évite une transmission d'adresse
   IP vers un service hors Union européenne.

   NE DÉCOMMENTEZ PAS CES BLOCS ICI. Ce fichier est RECOPIÉ depuis kit/ à
   chaque amélioration du kit : tout ce qui y est propre à un client finit
   écrasé sans bruit. C'est arrivé une fois, et le site a rendu en police de
   secours pendant plusieurs commits sans que rien ne le signale.
   Les @font-face vont dans le theme.css DU CLIENT, qui ne se recopie jamais.
   Les blocs ci-dessous sont un gabarit à copier là-bas, pas à activer ici.
   Voir sites/maison-hiret/css/theme.css pour un exemple complet et renseigné.

   font-display: swap → le texte s'affiche immédiatement avec la police de
   secours, puis bascule. Jamais de texte invisible pendant le chargement.
   ========================================================================== */

/*
@font-face {
  font-family: "Titre";
  src: url("../fonts/titre-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Texte";
  src: url("../fonts/texte-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Texte";
  src: url("../fonts/texte-600.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
*/


/* ==========================================================================
   2. ÉLÉMENTS HTML
   ========================================================================== */

h1, h2, h3, h4 {
  font-family: var(--f-titre);
  font-weight: var(--p-gras);
  line-height: var(--i-serre);
  letter-spacing: var(--l-titre);
  text-wrap: balance;   /* évite un mot orphelin sur la dernière ligne */
}

h1 { font-size: var(--t-2xl); }
h2 { font-size: var(--t-xl); }
h3 { font-size: var(--t-lg); line-height: var(--i-normal); }
h4 { font-size: var(--t-md); line-height: var(--i-normal); }

/* Un mot seul sur la dernière ligne se voit autant dans une puce que dans un
   paragraphe : les deux reçoivent la même coupure soignée. */
p,
li {
  text-wrap: pretty;
}

a {
  color: var(--c-lien);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

a:hover {
  color: var(--c-lien-survol);
}

strong, b {
  font-weight: var(--p-gras);
}

small {
  font-size: var(--t-sm);
}

hr {
  border: 0;
  border-top: 1px solid var(--c-bordure);
  margin: var(--e-6) 0;
}


/* ==========================================================================
   3. MISE EN PAGE ET UTILITAIRES
   ========================================================================== */

.conteneur {
  width: 100%;
  max-width: var(--l-page);
  margin-inline: auto;
  padding-inline: var(--e-gouttiere);
}

.conteneur--etroit {
  max-width: var(--l-etroit);
}

.section {
  padding-block: var(--e-section);
}

.section--alt     { background: var(--c-fond-alt); }
.section--teinte  { background: var(--c-fond-teinte); }

.section--sombre {
  background: var(--c-fond-sombre);
  color: var(--c-texte-inverse);
}

.section--sombre a { color: var(--c-accent); }

/* En-tête de section : surtitre, titre, chapô. Motif repris par toutes les
   sections de contenu, pour un rythme vertical homogène. */
.section__entete {
  max-width: var(--l-contenu);
  margin-bottom: var(--e-6);
}

.section__entete--centre {
  margin-inline: auto;
  text-align: center;
}

.section__surtitre {
  display: block;
  /* Interligne déclaré : sans lui l'étiquette hérite de --i-lache (1,7), prévu
     pour de la prose. Sur une capitale espacée d'une seule ligne, cela creuse un
     écart qu'on lit comme un défaut d'alignement avec le titre. */
  line-height: var(--i-normal);
  margin-bottom: var(--e-2);
  color: var(--c-primaire);
  font-size: var(--t-sm);
  font-weight: var(--p-gras);
  letter-spacing: var(--l-large);
  text-transform: uppercase;
}

.section--sombre .section__surtitre { color: var(--c-accent); }

.section__chapo {
  /* Seul paragraphe du kit qui ne s'en protégeait pas : un mot français long et
     insécable — « personnalisation », « référencement » — déborde du conteneur
     à 320px. */
  overflow-wrap: break-word;
  margin-top: var(--e-3);
  color: var(--c-texte-doux);
  font-size: var(--t-md);
}

.section--sombre .section__chapo {
  color: var(--c-texte-inverse);
  opacity: 0.9;
}

/* Grilles. Une colonne sur mobile, sans exception : c'est la règle qui
   évite l'essentiel des débordements à 375px. */
.grille {
  display: grid;
  gap: var(--e-5);
  grid-template-columns: 1fr;
}

@media (min-width: 40em) {
  .grille--2 { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 60em) {
  .grille--3 { grid-template-columns: repeat(3, 1fr); }
  .grille--2-large { grid-template-columns: repeat(2, 1fr); }
}

/* Lien d'évitement : premier élément focusable de la page, caché jusqu'à ce
   qu'il reçoive le focus clavier. */
.lien-evitement {
  position: absolute;
  left: var(--e-3);
  top: var(--e-3);
  z-index: var(--z-modale);
  padding: var(--e-3) var(--e-4);
  background: var(--c-primaire);
  color: var(--c-texte-inverse);
  border-radius: var(--r-md);
  text-decoration: none;
  transform: translateY(-150%);
  transition: transform var(--d-normal) var(--courbe);
}

.lien-evitement:focus {
  transform: translateY(0);
}

/* La cible du lien porte tabindex="-1" : sans lui, le lien déplaçait la VUE
   sans déplacer le FOCUS, et la tabulation suivante repartait de l'en-tête —
   c'est-à-dire exactement de ce qu'on venait de sauter. Le trait de focus est
   retiré sur elle seule : encadrer tout le corps de la page n'apprend rien,
   alors que le saut, lui, se voit. */
main[tabindex="-1"]:focus,
main[tabindex="-1"]:focus-visible {
  outline: none;
}

/* Groupe qui ne se coupe jamais en fin de ligne : un nom propre composé
   (« Ille-et-Vilaine »), qu'un navigateur couperait sinon à son trait d'union.
   Pas de trait d'union insécable (U+2011) à la place : les sous-ensembles de
   la police servie ne le contiennent pas, il serait dessiné dans une autre. */
.insecable {
  white-space: nowrap;
}

/* Contenu réservé aux lecteurs d'écran. */
.visuellement-cache {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


/* ==========================================================================
   4. BOUTONS
   --------------------------------------------------------------------------
   Hauteur minimale 48px : en dessous, la cible tactile devient pénible au
   pouce. Les variantes ne changent que les couleurs.
   ========================================================================== */

.bouton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--e-2);
  min-height: 48px;
  padding: var(--e-3) var(--e-5);
  border: 2px solid transparent;
  border-radius: var(--r-plein);
  font-family: var(--f-texte);
  font-size: var(--t-base);
  font-weight: var(--p-gras);
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--d-rapide) var(--courbe),
              border-color var(--d-rapide) var(--courbe),
              color var(--d-rapide) var(--courbe);
}

.bouton--primaire {
  background: var(--c-primaire);
  color: var(--c-texte-inverse);   /* 9,1:1 */
}

.bouton--primaire:hover {
  background: var(--c-primaire-sombre);
  color: var(--c-texte-inverse);
}

/* Le jaune ne porte jamais de texte : il sert de fond, avec du quasi-noir
   dessus (10,7:1). */
.bouton--accent {
  background: var(--c-accent);
  color: var(--c-sur-accent);
}

.bouton--accent:hover {
  background: var(--c-accent-sombre);
  color: var(--c-sur-accent);
}

.bouton--fantome {
  background: transparent;
  border-color: var(--c-primaire);
  color: var(--c-primaire);
}

.bouton--fantome:hover {
  background: var(--c-primaire);
  color: var(--c-texte-inverse);
}

/* Pleine largeur : le réglage par défaut sur mobile pour les appels à
   l'action principaux. */
.bouton--large {
  width: 100%;
}

@media (min-width: 40em) {
  .bouton--large { width: auto; }
}

/* État ACTIF. Sur un écran tactile le survol ne se déclenche pas : sans cet
   état, l'appui sur un bouton ne renvoie aucun retour et le visiteur appuie
   deux fois. Le retrait d'un pixel suffit à faire sentir l'enfoncement. */
.bouton:active {
  transform: translateY(1px);
}

@media (prefers-reduced-motion: reduce) {
  .bouton:active { transform: none; }
}

.bouton[aria-disabled="true"],
.bouton:disabled {
  /* PAS opacity : elle s'applique au bouton ENTIER, texte compris, et fait
     tomber le contraste du libellé d'environ 6,7:1 à 3,7:1 — « Envoi en
     cours… » devient difficile à lire au moment précis où il compte. On
     désature le fond ; le texte garde son contraste. */
  background: var(--c-texte-tertiaire);
  border-color: var(--c-texte-tertiaire);
  cursor: not-allowed;
}

/* Un bouton neutralisé ne doit plus réagir au survol : sinon il promet une
   action qu'il ne rendra pas. */
.bouton[aria-disabled="true"]:hover,
.bouton:disabled:hover {
  background: var(--c-texte-tertiaire);
  border-color: var(--c-texte-tertiaire);
}


/* ==========================================================================


/* ===== 5. EN-TÊTE ===== */
/* Barre collante en haut de page : nom commercial à gauche, appel à l'action à
   droite. Pas de menu burger — les ancres, facultatives, disparaissent sous 60em
   au lieu de se replier ; l'ombre de défilement est posée par le JS, qui lit
   directement le défilement et n'a besoin d'aucun élément repère dans le DOM. */

.entete {
  position: sticky;
  top: 0;
  z-index: var(--z-entete);
  background: var(--c-fond);
  /* Filet permanent : sans JS, la barre reste détachée du contenu qui passe
     dessous. L'ombre ne fait qu'accentuer cette séparation une fois défilé. */
  border-bottom: 1px solid var(--c-bordure);
  transition: box-shadow var(--d-normal) var(--courbe);
}

.entete--defilee {
  box-shadow: var(--o-sm);
  /* Le filet s'assombrit AUSSI. Sans cette ligne, un thème qui pose
     --o-sm: none — une charte sans relief, par exemple — rend la classe
     entièrement inopérante : la barre reste identique qu'on ait défilé ou non.
     Le changement de filet, lui, marche partout. */
  border-bottom-color: var(--c-texte-tertiaire);
}

.entete__barre {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e-3);
  min-height: var(--h-entete);
}

.entete__marque {
  /* Cible tactile. Avec l'interligne serré, la boîte du lien ne ferait que 20px
     de haut : centrée dans la barre, elle serait deux fois trop petite au pouce.
     Le retrait vertical la porte au-delà de 48px sans dépasser les 60px de
     min-height de la barre — --h-entete reste donc inchangé. Le retrait à droite
     assure la même largeur minimale à un nom de trois ou quatre lettres. */
  padding-block: var(--e-4);
  padding-inline-end: var(--e-4);
  /* min-width nul : si le client a un nom plus long que prévu, c'est lui qui
     cède, jamais le bouton. Il se replie alors sur deux lignes et la barre
     s'allonge — on ne tronque pas : un nom coupé n'est plus lisible à 320px
     ni en gros caractères, alors qu'il reste complet à la synthèse vocale. */
  min-width: 0;
  overflow-wrap: break-word;   /* filet pour un nom d'un seul mot très long */
  color: var(--c-texte);
  font-family: var(--f-titre);
  font-size: var(--t-md);
  font-weight: var(--p-gras);
  letter-spacing: var(--l-titre);
  line-height: var(--i-serre);
  text-decoration: none;
  transition: color var(--d-rapide) var(--courbe);
}

.entete__marque:hover {
  color: var(--c-primaire);
}

/* Ancres facultatives : masquées tant que la largeur ne les autorise pas.
   Aucun repli en menu, la page n'a pas de navigation à replier. */
/* Poinçon facultatif devant le nom. Sans <img>, .entete__marque reste un simple
   lien texte et rien ne change : le composant ne suppose pas sa présence.
   Taille en em et non en px : le poinçon suit l'échelle du nom, y compris si le
   visiteur grossit les caractères. */
.entete__marque {
  display: inline-flex;
  align-items: center;
  gap: var(--e-2);
}

.entete__poincon {
  flex: 0 0 auto;
  width: 1.65em;
  height: 1.65em;
}

.entete__nav {
  display: none;
}

.entete__liste {
  display: flex;
  align-items: center;
  gap: var(--e-5);
}

.entete__lien {
  display: inline-block;
  padding-block: var(--e-2);
  color: var(--c-texte-doux);
  font-size: var(--t-base);
  font-weight: var(--p-moyen);
  text-decoration: none;
  transition: color var(--d-rapide) var(--courbe);
}

.entete__lien:hover,
.entete__lien:focus-visible {
  color: var(--c-primaire);
  /* La teinte seule ne suffit pas : sur une charte à une couleur, l'écart entre
     texte doux et couleur de marque se voit mal, et il ne se voit pas du tout
     pour un daltonien. Le soulignement est un second signal, de forme. */
  text-decoration: underline;
  text-underline-offset: 0.25em;
}

/* 14. La page en cours est marquée dans la navigation comme elle l'est dans le
   sélecteur de langue : même convention, même repère. */
.entete__lien[aria-current] {
  color: var(--c-primaire);
  font-weight: var(--p-gras);
}

/* Bouton resserré : à 375px, un nom de 14 caractères et un libellé court
   doivent tenir sur une ligne. On ne touche qu'au retrait horizontal — la
   cible tactile de 48px vient de .bouton et reste intacte. */
.entete__action {
  flex: 0 0 auto;
  padding-inline: var(--e-4);
}

@media (min-width: 30em) {
  .entete__action {
    padding-inline: var(--e-5);
  }
}

@media (min-width: 60em) {
  .entete__nav {
    display: block;
  }
}


/* --- Sélecteur de langue de l'en-tête ---------------------------------------
   Construit sur <details>/<summary> natifs : aucun JavaScript, navigable au
   clavier par défaut, et refermé automatiquement par le navigateur. Un <select>
   aurait exigé du script pour naviguer, donc n'aurait rien fait sans lui.

   À 375px, la barre porte déjà le nom et l'appel à l'action : le déclencheur se
   réduit au code de langue sur deux lettres, qui suffit à être compris. */

/* Parent positionné du panneau : il s'ouvre SOUS SON DÉCLENCHEUR, et c'est la
   seule place où un menu déroulant a un sens. L'ancrer sur la barre entière le
   faisait descendre à l'autre bout de l'en-tête, sous le bouton d'action — on
   cliquait sur « FR » et la liste apparaissait ailleurs. */
.entete__langues {
  position: relative;
  flex: 0 0 auto;
}

.entete__declencheur {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--e-1);
  /* 44px DANS LES DEUX SENS. Avec la seule hauteur, un code de langue de deux
     lettres et son chevron mesuraient 43px de large : une cible carree de 44
     est le minimum de la regle WCAG 2.5.5, et un pixel manquant la fait
     echouer aussi surement que dix. */
  min-height: 44px;
  min-inline-size: 44px;
  padding-inline: var(--e-2);
  cursor: pointer;
  color: var(--c-texte);
  font-size: var(--t-sm);
  font-weight: var(--p-moyen);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* Le triangle natif du summary est masqué des deux façons : les navigateurs
     ne s'accordent pas sur laquelle ils écoutent. */
  list-style: none;
}

.entete__declencheur::-webkit-details-marker {
  display: none;
}

/* Chevron dessiné en CSS : aucune image à charger, et il pivote à l'ouverture. */
.entete__declencheur::after {
  content: "";
  width: 0.4em;
  height: 0.4em;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: rotate(45deg) translate(-0.1em, -0.1em);
  transition: transform var(--d-rapide) var(--courbe);
}

.entete__langues[open] .entete__declencheur::after {
  transform: rotate(-135deg) translate(-0.1em, -0.1em);
}

.entete__menu {
  position: absolute;

  /* ALIGNÉ À GAUCHE SUR SON DÉCLENCHEUR, et non à droite. Le panneau est plus
     large que le bouton « FR » : aligné par la droite, il déborderait de
     l'écran par la gauche sur mobile — 49 px à 320 px de large, mesuré. Aligné
     par la gauche, il se déploie vers la droite, où la place ne manque jamais
     puisque le déclencheur précède le bouton d'action. Vérifié de 320 à
     1280 px : il tient dans la barre à toutes les largeurs. */
  inset-inline-start: 0;
  inset-block-start: calc(100% + var(--e-2));
  z-index: var(--z-entete);
  min-width: 11rem;
  padding: var(--e-2);
  background: var(--c-fond);
  border: 1px solid var(--c-bordure);
  border-radius: var(--r-md);
}

.entete__menu a {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding-inline: var(--e-3);
  color: var(--c-texte);
  text-decoration: none;
}

.entete__menu a:hover,
.entete__menu a:focus-visible {
  background: var(--c-fond-alt);
  color: var(--c-primaire);
}

/* La langue affichée est marquée, pas retirée : la retirer priverait le
   visiteur du repère lui disant où il se trouve. */
.entete__menu a[aria-current] {
  font-weight: var(--p-gras);
  color: var(--c-primaire);
}


/* ===== 5C. FIL D'ARIANE ===== */
/* Sur un site de deux niveaux, il ne sert pas à se repérer — on voit tout de
   suite où l'on est. Il sert à REMONTER : depuis une page légale, une page de
   remerciement ou une 404, le lien vers l'accueil est la seule sortie, et une
   sortie doit être visible sans chercher.

   <ol> et non <ul> : l'ordre porte l'information, c'est un chemin.
   role="list" parce que le reset retire les puces, ce qui supprime la
   sémantique de liste sous WebKit.
   Le séparateur est DESSINÉ, jamais écrit dans le balisage : un « / » dans le
   HTML serait lu à voix haute entre chaque étape. aria-hidden ne suffirait pas,
   il faut qu'il ne soit pas du texte du tout. */

.ariane {
  margin-bottom: var(--e-6);
}

.ariane__liste {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e-2);
  font-size: var(--t-sm);
  color: var(--c-texte-doux);
}

.ariane__element {
  display: flex;
  align-items: center;
  gap: var(--e-2);
}

/* Le chevron précède chaque étape sauf la première. */
.ariane__element + .ariane__element::before {
  content: "";
  flex: none;
  inline-size: 0.4em;
  block-size: 0.4em;
  border-top: 1px solid currentColor;
  border-right: 1px solid currentColor;
  transform: rotate(45deg);
  opacity: 0.6;
}

.ariane a {
  /* Cible tactile : sans ce retrait vertical, le lien ne ferait que 18 px de
     haut, moitié moins que le minimum au pouce. */
  display: inline-block;
  padding-block: var(--e-2);
  color: inherit;
}

/* L'étape courante n'est pas un lien : un lien vers la page qu'on regarde
   déjà occupe une tabulation pour ne rien faire. */
.ariane [aria-current="page"] {
  color: var(--c-texte);
  font-weight: var(--p-moyen);
}


/* ===== 5D. OFFRES ===== */
/* Deux propositions principales présentées côte à côte, pour un site qui vend
   deux choses distinctes plutôt qu'une seule déclinée. Le visiteur n'a pas à
   lire la page entière pour savoir laquelle le concerne.

   Une seule offre dans la liste reste correcte : la carte occupe alors toute la
   largeur, ce qui est la bonne forme pour une proposition unique. Trois offres
   tiennent aussi, mais elles retombent en une colonne sous 48em — au-delà de
   deux, mieux vaut .grille--3. */

.offres__liste {
  display: grid;
  gap: var(--e-5);
  grid-template-columns: 1fr;
}

@media (min-width: 48em) {
  .offres__liste {
    grid-template-columns: repeat(2, 1fr);
  }
}

.offre {
  display: flex;
  flex-direction: column;
  padding: var(--e-6);
  background: var(--c-fond);
  border: 1px solid var(--c-bordure);
  border-radius: var(--r-lg);
}

.offre__surtitre {
  margin-bottom: var(--e-2);
  color: var(--c-primaire);
  font-size: var(--t-sm);
  font-weight: var(--p-gras);
  letter-spacing: var(--l-large);
  line-height: var(--i-normal);
  text-transform: uppercase;
}

.offre__titre {
  margin-bottom: var(--e-3);
  font-family: var(--f-titre);
  font-size: var(--t-xl);
  font-weight: var(--p-normal);
  line-height: var(--i-titre);
}

/* Pousse le prix et le lien en bas : les deux cartes alignent leurs lignes de
   prix même quand leurs textes n'ont pas la même longueur. */
.offre__texte {
  flex: 1 1 auto;
  margin-bottom: var(--e-5);
  color: var(--c-texte-doux);
}

/* Le prix tient en DEUX LIGNES dans toutes les cartes, et c'est délibéré : le
   montant, puis la condition. En une seule ligne, « À partir de 890 € HT ·
   livré en 3 semaines » se brisait en deux là où « Sur devis » tenait sur une,
   et les deux filets de séparation se posaient à des hauteurs différentes — sur
   les deux blocs que le lecteur compare justement l'un à l'autre.
   La condition en corps réduit donne aussi au montant le poids qu'il mérite. */
.offre__prix {
  padding-top: var(--e-4);
  border-top: 1px solid var(--c-bordure);
  color: var(--c-texte);
  font-weight: var(--p-moyen);
}

.offre__condition {
  margin-top: var(--e-1);
  color: var(--c-texte-doux);
  font-size: var(--t-sm);
}

.offre__lien {
  margin-top: var(--e-3);
  font-size: var(--t-sm);
}


/* ===== 5E. CHIFFRES ===== */
/* Bande de repères chiffrés, chacun avec sa source VISIBLE et cliquable.

   LA SOURCE N'EST PAS UN ORNEMENT, c'est la moitié du composant. Un chiffre sans
   source sur le site d'une agence de référencement ne prouve rien : le lecteur
   qui vérifie est exactement le prospect qu'on veut, et celui qui ne vérifie pas
   n'aurait pas été convaincu par un chiffre de toute façon. La règle du kit est
   donc qu'on ne pose pas un .chiffre sans remplir son .chiffre__source.

   DEUX COLONNES ET PAS QUATRE. Une bande de quatre grands nombres a plus
   d'impact, mais elle écrase les lignes de source à une taille où plus personne
   ne les lit — et un chiffre dont la source est illisible retombe au rang de
   chiffre inventé. Deux colonnes laissent la place aux deux. */

.chiffres__liste {
  display: grid;
  gap: var(--e-6);
  grid-template-columns: 1fr;
}

@media (min-width: 48em) {
  .chiffres__liste {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--e-7) var(--e-8);
  }
}

.chiffre {
  padding-top: var(--e-4);
  border-top: 1px solid var(--c-bordure);
}

.chiffre__valeur {
  display: block;
  font-family: var(--f-titre);
  font-size: var(--t-3xl);
  font-weight: var(--p-normal);
  line-height: 1;
  color: var(--c-primaire);

  /* Les chiffres s'alignent d'une ligne à l'autre plutôt que de danser selon
     la largeur de chaque glyphe. */
  font-variant-numeric: tabular-nums;
}

.chiffre__enonce {
  margin-top: var(--e-3);
  color: var(--c-texte);
}

.chiffre__source {
  margin-top: var(--e-2);
  font-size: var(--t-sm);
  color: var(--c-texte-doux);
}


/* ===== 6. ACCROCHE ===== */
/* Bandeau d'ouverture : surtitre en pastille, h1, phrase de soutien, appel à
   l'action principal et ligne de réassurance. Le fond est un dégradé CSS pur —
   aucune image, donc aucune requête sur l'écran qui décide de la vitesse perçue.
   Bloc entièrement centré et autonome : il ne dépend d'aucun autre composant. */

.accroche {
  /* Le dégradé part de la teinte de marque et se fond dans le fond de page :
     la section suivante s'enchaîne sans couture visible. */
  background-color: var(--c-fond);
  background-image: linear-gradient(to bottom, var(--c-fond-teinte), var(--c-fond));
  padding-block: var(--e-7);
}

@media (min-width: 48em) {
  .accroche {
    padding-block: var(--e-9);
  }
}

.accroche__contenu {
  max-width: var(--l-etroit);
  margin-inline: auto;
  text-align: center;
}

/* Surtitre aligné sur .section__surtitre du contrat commun : même texte vert en
   capitales espacées que dans toutes les autres sections.
   Il était en pastille jaune ; vu sur la page entière, cela faisait un troisième
   aplat jaune sur le premier écran, à côté du bouton d'accroche et de la barre
   collante. L'accent est réservé aux appels à l'action, pas aux étiquettes. */
.accroche__surtitre {
  display: block;
  color: var(--c-primaire);       /* 9,1:1 sur fond clair */
  font-size: var(--t-sm);
  font-weight: var(--p-gras);
  line-height: var(--i-normal);
  letter-spacing: var(--l-large);
  text-transform: uppercase;
}

.accroche__titre {
  /* Taille reprise explicitement du contrat : le bloc reste juste même si la
     balise change. À 375px, --t-2xl redescend à 30px. */
  font-size: var(--t-2xl);
  /* Filet de sécurité pour un titre long ou un mot composé à 375px : on coupe
     plutôt que de laisser déborder. La césure suppose lang="fr" sur <html>. */
  overflow-wrap: break-word;
  /* Safari (macOS et tout iOS) ne connaît la césure que préfixée. */
  -webkit-hyphens: auto;
  hyphens: auto;
}

/* Marge portée par l'adjacence : sans surtitre, le titre reste collé en haut. */
.accroche__surtitre + .accroche__titre {
  margin-top: var(--e-4);
}

.accroche__texte {
  max-width: var(--l-contenu);
  margin-top: var(--e-4);
  margin-inline: auto;
  color: var(--c-texte-doux);
  font-size: var(--t-md);
  /* Une adresse ou une URL glissée ici dépasse la largeur utile à 375px. */
  overflow-wrap: break-word;
}

/* Une colonne sur mobile : les boutons s'étirent d'eux-mêmes (align-items
   stretch), y compris un éventuel bouton secondaire sans .bouton--large. */
.accroche__actions {
  display: flex;
  flex-direction: column;
  gap: var(--e-3);
  margin-top: var(--e-6);
}

@media (min-width: 40em) {
  .accroche__actions {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
  }
}

.accroche__reassurance {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--e-2);
  margin-top: var(--e-4);
  color: var(--c-texte-doux);
  /* Phrase rédigée, pas une légende : elle reste au plancher de 16px. */
  font-size: var(--t-base);
  overflow-wrap: break-word;
}

.accroche__icone {
  flex: none;
  /* Suit la taille du texte qui l'accompagne, aucune valeur fixe. */
  inline-size: 1em;
  block-size: 1em;
  color: var(--c-primaire);
}

/* Variante fond sombre — pour un client dont l'identité s'y prête.
   Sur ce fond, l'appel à l'action doit rester .bouton--accent : le vert
   .bouton--primaire n'y aurait plus assez de contraste. */
.accroche--sombre {
  background-color: var(--c-fond-sombre);
  background-image: linear-gradient(to bottom, var(--c-primaire), var(--c-fond-sombre));
  color: var(--c-texte-inverse);
}

.accroche--sombre .accroche__texte,
.accroche--sombre .accroche__reassurance {
  color: var(--c-texte-inverse);
}

.accroche--sombre .accroche__icone {
  color: var(--c-accent);
}

/* Le dégradé démarre exactement sur --c-primaire : tout ce qui est peint dans
   cette couleur y disparaît. Ce qui suit rend la garde automatique plutôt que
   documentaire. On style des descendants depuis la classe du composant, jamais
   les classes du contrat elles-mêmes. */

/* L'anneau de focus global (--c-focus) tombe à 1,4:1 sur ce fond. */
.accroche--sombre :focus-visible {
  outline-color: var(--c-accent);
}

/* :not(.bouton) est indispensable : sans lui, la spécificité écraserait la
   couleur de .bouton--accent et poserait du jaune sur du jaune. */
.accroche--sombre a:not(.bouton) {
  color: var(--c-accent);
}

.accroche--sombre .bouton--fantome {
  border-color: var(--c-accent);
  color: var(--c-accent);
}

.accroche--sombre .bouton--fantome:hover {
  background: var(--c-accent);
  color: var(--c-sur-accent);
}


/* ===== 6B. SCÈNES ANIMÉES ===== */
/* Un dessin qui se construit sous les yeux du visiteur, à la place d'une vidéo.

   POURQUOI PAS UNE VIDÉO. Une vidéo de démonstration pèse couramment plusieurs
   dizaines de mégaoctets : celle qui a inspiré ce composant en faisait 33,6,
   soit quatorze secondes de téléchargement en 4G moyenne, pour une page qui
   tient entière sous le dixième de mégaoctet. Le tracé animé montre la même
   chose, pèse le poids de son balisage, ne fait aucune requête et reste net à
   toute taille.

   TROIS ÉTATS, ET UN SEUL QUI BOUGE.
     - Sans script, ou si le visiteur a demandé moins d'animations : le dessin
       COMPLET et immobile. Le bouton est invisible mais garde sa place, pour
       que son apparition ne fasse rien bouger ; sous « animations réduites »,
       où la lecture ne démarrera jamais, il ne réserve rien.
     - Avec script : .scene--active lance la lecture, --n-scene-boucles fois,
       puis le dessin s'arrête sur son image complète.
     - En pause, ou hors de l'écran : .scene--suspendue fige l'image.

   POURQUOI UN BOUTON. Un mouvement qui démarre seul, dure plus de cinq secondes
   et côtoie du contenu doit pouvoir être arrêté (WCAG 2.2.2). La lecture n'est
   lancée que par le script qui affiche ce bouton : si l'un manque, l'autre aussi.

   CE QUE LE COMPOSANT ATTEND DU BALISAGE
     [data-scene]            le conteneur, porteur de .scene
     .scene__dessin          le dessin, aria-hidden : il répète le titre voisin
     .scene__anime           chaque groupe animé, en plus de sa classe propre
     [data-scene-fin]        l'élément animé dont la fin marque une lecture
     [data-scene-controle]   le bouton, HORS de la zone aria-hidden, porteur de
                             data-libelle-pause, -reprise et -rejouer ; s'il en
                             manque un, le script laisse le dessin fixe
     [data-scene-libelle]    dans le bouton, le texte masqué qui lui donne son nom
     [data-scene-saisie]     facultatif : un <text> tapé ; le script mesure sa
                             longueur pour arrêter le curseur au bout du mot. Il
                             doit tenir dans le champ : 160 unités environ, soit
                             à peu près 25 caractères latins ou 12 idéogrammes.
                             Au-delà, le champ le rogne.

   CHAQUE PIÈCE PART DE SON ÉTAT DE REPOS. À 0 % d'une lecture, tout est en
   place, comme sans animation ; les pièces s'effacent en 3 % de la durée, puis
   se reconstruisent. L'image au démarrage est donc celle que le visiteur voyait
   déjà ; une scène figée avant d'avoir joué montre le dessin complet ; et le
   passage d'une boucle à la suivante est un effacement bref, pas une coupure.

   LES COURBES SE POSENT SUR L'ÉLÉMENT, JAMAIS DANS LES IMAGES CLÉS. Chrome
   ignore var() dans une animation-timing-function écrite à l'intérieur d'un
   @keyframes : steps(var(--scene-lettres)) et var(--courbe) y tombaient en
   linéaire, sans erreur. Posée sur l'élément, la courbe s'applique à chaque
   intervalle entre deux images clés, ce qui revient au même : un intervalle
   sans changement ne bouge pas, quelle que soit sa courbe.

   On n'anime que transform et opacity : le navigateur les compose sans
   recalculer la page, et la lecture ne coûte rien pendant le défilement.
   Durée, nombre de lectures et largeur du dessin d'outil viennent de theme.css.

   UNE EXCEPTION À LA RÈGLE « AUCUNE VALEUR EN DUR », ASSUMÉE. Les déplacements
   des images clés (translateX, translateY en px) sont des coordonnées du
   dessin, en unités de son viewBox, au même titre que les x et y écrits dans le
   SVG. Ce ne sont pas des tailles de mise en page, et ils suivent le dessin
   quand il change d'échelle.

   UN PIÈGE DU SVG, évité ici : une transformation CSS REMPLACE l'attribut
   transform de l'élément au lieu de s'y ajouter. Les éléments animés sont donc
   toujours des groupes enveloppes, sans attribut transform ; le positionnement
   reste sur le groupe qu'ils contiennent. */

/* --scene-champ : le fond du champ de saisie ET du cache qui la révèle. Les
   deux doivent être identiques pour que le cache reste invisible, et valoir le
   fond réel derrière le dessin. theme.css le pose sur un ANCÊTRE (l'accroche,
   par exemple) ; il n'est donc volontairement pas déclaré ici : une variable
   déclarée sur .scene l'emporterait sur celle héritée du parent, et le réglage
   du thème serait ignoré. Le kit ne donne qu'une valeur de repli, au point
   d'usage. */

.scene {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--e-3);
}

.scene__dessin {
  display: flex;
  justify-content: center;
  inline-size: 100%;
}

/* Le contrôle : discret, mais une vraie cible tactile, et une bordure plutôt
   qu'un aplat pour ne pas disputer la place du bouton d'appel à l'action. */
.scene__controle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 48px;
  min-height: 48px;
  padding: 0;
  border: 1px solid currentColor;
  border-radius: var(--r-plein);
  background: transparent;
  color: inherit;
  cursor: pointer;
}

.scene__controle:hover {
  box-shadow: inset 0 0 0 1px currentColor;
}

/* Le contour de focus prend la couleur du trait : sur un fond sombre, la
   couleur de focus du thème pourrait se confondre avec le fond. */
.scene__controle:focus-visible {
  outline-color: currentColor;
}

/* Avant le script, le bouton garde sa place : l'afficher ne fait bouger ni le
   dessin ni ce qui l'entoure. visibility: hidden le retire de la vue, de la
   tabulation et de l'arbre d'accessibilité. La règle est nécessaire :
   display: inline-flex, déclaré plus haut, l'emporterait sinon sur la règle
   par défaut du navigateur pour hidden. */
.scene__controle[hidden] {
  visibility: hidden;
}

/* Là où la lecture ne démarrera jamais, aucune place à réserver. */
@media (prefers-reduced-motion: reduce) {
  .scene__controle[hidden] {
    display: none;
  }
}

/* Une icône par état ; le script tient data-etat à jour. */
.scene__icone {
  display: none;
}

.scene__controle[data-etat="lecture"] .scene__icone--pause,
.scene__controle[data-etat="pause"] .scene__icone--lecture,
.scene__controle[data-etat="fin"] .scene__icone--rejouer {
  display: block;
}


/* --- Le dessin « site » : une recherche qui aboutit à une page ------------ */

.scene-site__champ {
  fill: var(--scene-champ, var(--c-fond));
}

.scene-site__saisie {
  fill: currentColor;
  stroke: none;
  font-family: var(--f-texte);
}

/* Au repos, le cache et le curseur sont invisibles : l'image fixe montre la
   recherche entière. */
.scene-site__cache {
  fill: var(--scene-champ, var(--c-fond));
  stroke: none;
  opacity: 0;
}

/* Sur l'accroche sombre, la saisie passe en --c-texte-inverse : un champ resté
   sur --c-fond la rendrait invisible. Le repli suit donc la variante ; une
   valeur de --scene-champ posée par theme.css garde la priorité. */
.accroche--sombre .scene-site__champ,
.accroche--sombre .scene-site__cache {
  fill: var(--scene-champ, var(--c-fond-sombre));
}

.scene-site__curseur {
  opacity: 0;
}

.scene-site__titre,
.scene-site__action {
  transform-box: fill-box;
  transform-origin: 0 50%;
}


/* --- Le dessin « outil » : une semaine qui se remplit --------------------- */

.scene-outil {
  inline-size: 100%;
  max-inline-size: var(--l-scene-outil);
  block-size: auto;
  color: var(--c-texte-doux);
}

.scene-outil__actif,
.scene-outil__bloc,
.scene-outil__jauge {
  fill: var(--c-primaire);
}

.scene-outil__trait {
  stroke: var(--c-primaire);
}

.scene-outil__b1,
.scene-outil__b2,
.scene-outil__b3,
.scene-outil__b4,
.scene-outil__b5,
.scene-outil__b6,
.scene-outil__remplissage {
  transform-box: fill-box;
  transform-origin: 0 50%;
}

.scene-outil__coche {
  transform-box: fill-box;
  transform-origin: 50% 50%;
}


/* --- Un en-tête de section accompagné d'une scène ------------------------- */
/* L'en-tête garde ses propres règles, qui appartiennent au contrat commun : on
   ne fait que le ranger à côté du dessin. Sous 62em le dessin passe dessous,
   au fer à gauche comme le texte. */

.scene-entete {
  display: grid;
  gap: var(--e-5);
}

.scene-entete__scene {
  align-items: flex-start;
  margin-bottom: var(--e-6);
}

@media (min-width: 62em) {
  .scene-entete {
    grid-template-columns: minmax(0, 1fr) minmax(0, var(--l-scene-outil));
    align-items: center;
    column-gap: var(--e-9);
  }

  .scene-entete__scene {
    align-items: center;
  }
}


/* --- La lecture ----------------------------------------------------------- */
/* Tout ce qui suit est enfermé dans « screen and no-preference ». Même si le
   script posait .scene--active, rien ne bougerait pour qui a demandé moins
   d'animations ; et une page imprimée sort le dessin complet, jamais l'image
   d'un instant de la lecture. */

@media screen and (prefers-reduced-motion: no-preference) {

  .scene--active .scene__anime {
    animation-duration: var(--d-scene);
    animation-iteration-count: var(--n-scene-boucles);
    animation-timing-function: var(--courbe);
    animation-fill-mode: both;
  }

  /* Les courbes propres à une pièce se posent ici, sur l'élément (voir l'en-tête
     de la section) : même spécificité que la règle commune, et placées après. */
  .scene--active .scene-site__frappe  { animation-name: scene-site-frappe; animation-timing-function: steps(var(--scene-lettres, 12), end); }
  .scene--active .scene-site__cache   { animation-name: scene-site-cache; }
  .scene--active .scene-site__curseur { animation-name: scene-site-curseur; animation-timing-function: step-end; }
  .scene--active .scene-site__lien    { animation-name: scene-site-lien; }
  .scene--active .scene-site__page    { animation-name: scene-site-page; }
  .scene--active .scene-site__entete  { animation-name: scene-site-entete; }
  .scene--active .scene-site__titre   { animation-name: scene-site-titre; }
  .scene--active .scene-site__texte   { animation-name: scene-site-texte; }
  .scene--active .scene-site__action  { animation-name: scene-site-action; }
  .scene--active .scene-site__infos   { animation-name: scene-site-infos; }
  .scene--active .scene-site__repere  { animation-name: scene-site-repere; }

  .scene--active .scene-outil__b1           { animation-name: scene-outil-b1; }
  .scene--active .scene-outil__b2           { animation-name: scene-outil-b2; }
  .scene--active .scene-outil__b3           { animation-name: scene-outil-b3; }
  .scene--active .scene-outil__b4           { animation-name: scene-outil-b4; }
  .scene--active .scene-outil__b5           { animation-name: scene-outil-b5; }
  .scene--active .scene-outil__b6           { animation-name: scene-outil-b6; }
  .scene--active .scene-outil__remplissage  { animation-name: scene-outil-remplissage; }
  .scene--active .scene-outil__coche        { animation-name: scene-outil-coche; }

  /* En dernier : la pause ne touche qu'une propriété que rien d'autre ne pose. */
  .scene--suspendue .scene__anime {
    animation-play-state: paused;
  }
}

/* À l'impression, pas de bouton sur le papier. */
@media print {
  .scene__controle {
    display: none;
  }
}

/* Une lecture, en pourcentages d'une même durée : toutes les pièces restent
   synchrones d'une boucle à l'autre. Un animation-delay ne s'appliquerait qu'à
   la première boucle et désynchroniserait les suivantes.
   Chaque image clé 0 % est l'état de repos (voir l'en-tête de la section). */

/* Le cache glisse d'un caractère à la fois, sur la distance que le script a
   mesurée. Il ne bouge qu'entre 4 % et 34 % : il est invisible au-delà. */
@keyframes scene-site-frappe {
  0%, 4%    { transform: translateX(0); }
  34%, 100% { transform: translateX(var(--scene-frappe, 170px)); }
}

@keyframes scene-site-cache {
  0%      { opacity: 0; }
  3%, 99% { opacity: 1; }
  100%    { opacity: 0; }
}

/* Sous step-end, chaque valeur tient jusqu'à l'image clé suivante : le curseur
   clignote franchement au lieu de se fondre. */
@keyframes scene-site-curseur {
  0%        { opacity: 0; }
  3%, 34%   { opacity: 1; }
  40%       { opacity: 0; }
  46%       { opacity: 1; }
  52%       { opacity: 0; }
  58%       { opacity: 1; }
  64%, 100% { opacity: 0; }
}

@keyframes scene-site-lien {
  0%        { opacity: 1; }
  3%, 34%   { opacity: 0; }
  42%, 100% { opacity: 1; }
}

@keyframes scene-site-entete {
  0%        { opacity: 1; }
  3%, 42%   { opacity: 0; }
  48%, 100% { opacity: 1; }
}

@keyframes scene-site-titre {
  0%        { transform: scaleX(1); }
  3%, 46%   { transform: scaleX(0); }
  56%, 100% { transform: scaleX(1); }
}

@keyframes scene-site-texte {
  0%        { opacity: 1; }
  3%, 54%   { opacity: 0; }
  62%, 100% { opacity: 1; }
}

@keyframes scene-site-action {
  0%        { transform: scaleX(1); }
  3%, 60%   { transform: scaleX(0); }
  68%, 100% { transform: scaleX(1); }
}

@keyframes scene-site-infos {
  0%        { opacity: 1; }
  3%, 66%   { opacity: 0; }
  74%, 100% { opacity: 1; }
}

/* La page défile puis revient : l'image finale est celle du haut de page. */
@keyframes scene-site-page {
  0%, 76%   { transform: translateY(0); }
  84%, 88%  { transform: translateY(-16px); }
  96%, 100% { transform: translateY(0); }
}

@keyframes scene-site-repere {
  0%        { opacity: 1; transform: translateY(0); }
  3%        { opacity: 0; transform: translateY(0); }
  72%       { opacity: 0; transform: translateY(-28px); }
  80%       { opacity: 1; transform: translateY(3px); }
  84%, 100% { opacity: 1; transform: translateY(0); }
}

@keyframes scene-outil-b1 {
  0%        { transform: scaleX(1); }
  3%, 10%   { transform: scaleX(0); }
  18%, 100% { transform: scaleX(1); }
}

@keyframes scene-outil-b2 {
  0%        { transform: scaleX(1); }
  3%, 18%   { transform: scaleX(0); }
  26%, 100% { transform: scaleX(1); }
}

@keyframes scene-outil-b3 {
  0%        { transform: scaleX(1); }
  3%, 26%   { transform: scaleX(0); }
  34%, 100% { transform: scaleX(1); }
}

@keyframes scene-outil-b4 {
  0%        { transform: scaleX(1); }
  3%, 34%   { transform: scaleX(0); }
  42%, 100% { transform: scaleX(1); }
}

@keyframes scene-outil-b5 {
  0%        { transform: scaleX(1); }
  3%, 42%   { transform: scaleX(0); }
  50%, 100% { transform: scaleX(1); }
}

@keyframes scene-outil-b6 {
  0%        { transform: scaleX(1); }
  3%, 50%   { transform: scaleX(0); }
  60%, 100% { transform: scaleX(1); }
}

@keyframes scene-outil-remplissage {
  0%        { transform: scaleX(1); }
  3%, 62%   { transform: scaleX(0); }
  78%, 100% { transform: scaleX(1); }
}

@keyframes scene-outil-coche {
  0%        { opacity: 1; transform: scale(1); }
  3%, 80%   { opacity: 0; transform: scale(0.4); }
  86%       { opacity: 1; transform: scale(1.08); }
  90%, 100% { opacity: 1; transform: scale(1); }
}


/* ===== 7. CE QUI EST INCLUS ===== */
/* Liste de ce que comprend la prestation : une colonne à 375px, deux au-delà de
   40em via .grille et .grille--2 du contrat commun. La coche est entièrement
   dessinée en CSS avec deux pseudo-éléments décoratifs — aucun fichier à
   charger, et aucun contenu généré qu'un lecteur d'écran pourrait annoncer.
   La classe racine .inclus ne sert que de point d'accroche : le fond de la
   section reste au choix de la page (.section, .section--alt, .section--teinte). */

.inclus__element {
  position: relative;
  /* Gouttière réservée à la pastille : son diamètre, plus la respiration. */
  padding-inline-start: calc(var(--e-6) + var(--e-4));
}

/* Pastille : le disque coloré. Le jaune est ici un fond, jamais un texte. */
.inclus__element::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  width: var(--e-6);
  height: var(--e-6);
  border-radius: var(--r-plein);
  background: var(--c-accent);
}

/* Coche : un rectangle dont on ne garde que deux bordures, pivoté à 45 degrés.
   Le décalage vertical du translate compense le centre optique de la coche,
   qui tombe plus bas que le centre géométrique de son rectangle. */
.inclus__element::after {
  content: "";
  position: absolute;
  inset-block-start: calc(var(--e-6) / 2);
  inset-inline-start: calc(var(--e-6) / 2);
  width: var(--e-2);
  height: var(--e-4);
  border-inline-end: calc(var(--e-1) / 2) solid var(--c-sur-accent);
  border-block-end: calc(var(--e-1) / 2) solid var(--c-sur-accent);
  transform: translate(-50%, calc(-50% - var(--e-1) / 2)) rotate(45deg);
}

.inclus__texte {
  /* Marge portée par le paragraphe : un élément sans phrase d'explication ne
     laisse ainsi aucun blanc sous son intitulé. */
  margin-block-start: var(--e-2);
  max-width: var(--l-contenu);
  color: var(--c-texte-doux);
}

/* Encart de fin de section : le délai ou l'engagement à retenir.
   Volontairement pas en flex : l'intitulé-clé et le détail forment une seule
   phrase, et le retour à la ligne qui les sépare dans la source deviendrait un
   élément flex anonyme vide, non rendu — donc un mot collé au suivant pour une
   synthèse vocale comme pour un copier-coller. En flux inline, cet espace reste
   un vrai espace et l'alignement des lignes de base est natif. */
.inclus__mention {
  margin-block-start: var(--e-7);
  padding: var(--e-5);
  border-radius: var(--r-lg);
  background: var(--c-fond-teinte);
  color: var(--c-texte);
  text-align: center;
}

.inclus__mention-cle {
  color: var(--c-primaire);
  font-family: var(--f-titre);
  font-size: var(--t-md);
}

.inclus__mention-detail {
  color: var(--c-texte-doux);
  font-size: var(--t-base);
}

/* Sur une section sombre, le paragraphe doit repasser en texte clair ; l'encart,
   lui, fixe déjà ses propres couleurs et n'hérite de rien. Seuls ses liens sont
   à reprendre : .section--sombre a les repeindrait en jaune, illisible sur le
   fond clair de l'encart. */
.section--sombre .inclus__texte {
  color: var(--c-texte-inverse);
}

.section--sombre .inclus__mention a {
  color: var(--c-lien);
}

.section--sombre .inclus__mention a:hover {
  color: var(--c-lien-survol);
}

@media (min-width: 40em) {
  .inclus__mention {
    padding: var(--e-5) var(--e-6);
  }
}


/* ===== 8. TARIFS ===== */
/* Cartes de prix qui tiennent de une à trois offres sans changer de balisage :
   les cartes s'étalent en flex et se recentrent quand il n'y en a qu'une seule.
   Le prix est une donnée, pas un titre : il vit dans un <p>, jamais dans un hN. */

/* La classe racine .tarifs ne porte aucun style : elle sert de repère de bloc
   BEM et de point d'accroche si une page cliente doit habiller la section. */

/* Les cartes ne sont pas posées dans .grille : une grille à colonnes fixes
   étirerait une offre unique sur toute la largeur. Ici le flex-wrap laisse
   deux ou trois offres se partager la ligne, et borne une offre seule. */
.tarifs__liste {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--e-5);
}

/* Carte pleine largeur sur mobile : à 375px, deux colonnes casseraient le
   prix affiché en --t-2xl. */
.tarifs__carte {
  display: flex;
  flex-direction: column;
  width: 100%;
  padding: var(--e-5);
  background: var(--c-fond);
  border: 1px solid var(--c-bordure);
  border-radius: var(--r-lg);
  box-shadow: var(--o-sm);
  /* Recadre le bandeau, qui déborde jusqu'aux bords de la carte. */
  overflow: hidden;
}

/* Offre recommandée. La bordure reste fine : c'est la couleur, l'ombre portée
   et le bandeau qui font la différence, pas l'épaisseur du trait. */
.tarifs__carte--mise-en-avant {
  border-color: var(--c-primaire);
  box-shadow: var(--o-lg);
}

/* Bandeau d'accent, pleine largeur en haut de carte. Les marges négatives
   annulent le padding de la carte : elles suivent donc la même variable,
   ici comme au changement de palier. */
.tarifs__bandeau {
  margin: calc(var(--e-5) * -1) calc(var(--e-5) * -1) var(--e-5);
  padding: var(--e-2) var(--e-4);
  background: var(--c-accent);
  color: var(--c-sur-accent);
  font-size: var(--t-sm);
  font-weight: var(--p-gras);
  letter-spacing: var(--l-large);
  line-height: var(--i-normal);
  text-align: center;
  text-transform: uppercase;
}

/* La carte est en overflow: hidden : sans césure, un nom d'offre saisi en un
   seul mot long serait tronqué à 375px, sans le moindre indice visuel. */
.tarifs__nom {
  color: var(--c-texte);
  overflow-wrap: break-word;
}

.tarifs__prix {
  margin-top: var(--e-4);
}

/* Le montant et l'unité restent en flux inline : une mise en flex supprimerait
   le blanc entre les deux et la synthèse vocale lirait « 890 €HT ». */
.tarifs__prefixe {
  display: block;
  color: var(--c-texte-doux);
  font-size: var(--t-sm);
  line-height: var(--i-normal);
}

.tarifs__montant {
  font-family: var(--f-titre);
  font-size: var(--t-2xl);
  font-weight: var(--p-gras);
  letter-spacing: var(--l-titre);
  line-height: var(--i-serre);
}

.tarifs__unite {
  color: var(--c-texte-doux);
  font-size: var(--t-base);
  font-weight: var(--p-moyen);
}

.tarifs__modalites {
  margin-top: var(--e-2);
  color: var(--c-texte-doux);
  font-size: var(--t-sm);
  line-height: var(--i-normal);
}

.tarifs__inclus {
  margin-top: var(--e-5);
  padding-top: var(--e-5);
  border-top: 1px solid var(--c-bordure);
}

/* « anywhere » plutôt que « break-word » : une ligne incluse peut contenir une
   adresse électronique, insécable et sans tiret où couper. */
.tarifs__ligne {
  position: relative;
  padding-left: var(--e-5);
  overflow-wrap: anywhere;
}

.tarifs__ligne + .tarifs__ligne {
  margin-top: var(--e-3);
}

/* Coche décorative : le texte de la ligne se suffit à lui-même, la puce
   n'ajoute aucune information — elle ne doit donc pas être vocalisée.
   Deux déclarations en cascade : la seconde donne un texte alternatif vide,
   la première reste le repli des navigateurs qui ne parsent pas la syntaxe
   « content / alt » et qui, sans elle, perdraient la coche. */
.tarifs__ligne::before {
  content: "\2713";
  content: "\2713" / "";
  position: absolute;
  left: 0;
  color: var(--c-primaire);
  font-weight: var(--p-gras);
}

/* margin-top: auto colle le pied en bas de carte : les boutons restent alignés
   d'une carte à l'autre même si les listes n'ont pas la même longueur. Le
   padding garantit l'écart minimum quand la carte est la plus haute. */
.tarifs__pied {
  margin-top: auto;
  padding-top: var(--e-5);
}

/* Bouton toujours pleine largeur dans la carte, y compris sur grand écran :
   on ne passe donc pas par .bouton--large, qui repasse en largeur auto. */
.tarifs__action {
  width: 100%;
}

/* Mentions communes aux offres (TVA, validité du devis, conditions d'acompte).
   Pas --c-texte-tertiaire : le 4,7:1 annoncé vaut sur blanc, or le fragment
   pose la section en .section--alt, donc sur --c-fond-alt, où il retombe à
   4,26:1 — sous le seuil AA pour du texte en --t-sm. --c-texte-doux y donne
   8,4:1. */
.tarifs__mention {
  max-width: var(--l-contenu);
  margin-top: var(--e-6);
  margin-inline: auto;
  color: var(--c-texte-doux);
  font-size: var(--t-sm);
  line-height: var(--i-normal);
  text-align: center;
}

@media (min-width: 48em) {
  .tarifs__carte {
    /* Base d'un quart de page : trois offres tiennent sur une ligne dès
       l'écran d'ordinateur, deux se partagent la largeur, et une offre seule
       est bornée à la demi-page pour ne pas s'étirer. */
    flex: 1 1 calc(var(--l-page) / 4);
    max-width: calc(var(--l-page) / 2);
    padding: var(--e-6);
  }

  .tarifs__bandeau {
    margin: calc(var(--e-6) * -1) calc(var(--e-6) * -1) var(--e-5);
  }
}


/* --- Options chiffrées -----------------------------------------------------
   Un tarif unique ne convient jamais à tous les projets. Plutôt que de
   multiplier les paliers — dont le troisième est presque toujours un leurre que
   personne n'achète —, on CHIFFRE ce qui s'ajoute. Le client garde un prix de
   référence lisible et connaît le coût d'un supplément AVANT de le demander :
   c'est ce qui permet de tenir la promesse « sans surprise » tout en facturant
   le travail réel quand il est plus lourd.

   Composé en liste réglée : un filet par ligne, le nom au fer à gauche, le prix
   au fer à droite. Sur un écran étroit le prix passe sous le nom plutôt que de
   comprimer les deux. */

.tarifs__options {
  margin-top: var(--e-7);
  max-width: var(--l-contenu);
}

/* Plusieurs blocs peuvent se suivre — options ponctuelles, spécifiques à un
   métier, services annuels. Sans cet écart, le filet d'ouverture du bloc suivant
   colle à la dernière ligne du précédent et les deux listes n'en font plus
   qu'une à l'œil. */
.tarifs__options + .tarifs__options {
  margin-top: var(--e-8);
}

.tarifs__options-titre {
  margin-bottom: var(--e-2);
}

.tarifs__options-chapo {
  margin-bottom: var(--e-5);
  color: var(--c-texte-doux);
}

.tarifs__option {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--e-2) var(--e-5);
  padding-block: var(--e-4);
  border-top: 1px solid var(--c-bordure);
}

.tarifs__option:last-child {
  border-bottom: 1px solid var(--c-bordure);
}

.tarifs__option-nom {
  flex: 1 1 14rem;
  font-weight: var(--p-moyen);
}

/* Le détail explique ce que l'option comprend : sans lui, un prix seul invite à
   la négociation plutôt qu'à la décision. */
.tarifs__option-detail {
  display: block;
  margin-top: var(--e-1);
  color: var(--c-texte-doux);
  font-size: var(--t-sm);
  font-weight: var(--p-normal);
}

.tarifs__option-prix {
  flex: 0 0 auto;
  /* Chiffres à chasse fixe : les prix s'alignent en colonne d'une ligne à
     l'autre, ce qui les rend comparables d'un coup d'œil. */
  font-variant-numeric: tabular-nums;
  font-weight: var(--p-gras);
  white-space: nowrap;
  /* Reste au fer à droite MÊME quand il passe à la ligne. Sans cette marge
     automatique, un nom long fait basculer le prix sous le texte où il
     s'aligne à gauche : deux alignements dans une même liste. */
  margin-inline-start: auto;
}


/* ===== 8B. DÉMARCHE — les étapes d'une prestation =====
   Une liste NUMÉROTÉE, et le numéro n'est pas décoratif : c'est une vraie
   séquence, les étapes se suivent dans cet ordre et le client a besoin de le
   savoir. On ne numérote jamais ce qui n'est pas ordonné.

   Le numéro est en police de titrage et en grand corps : il sert de repère
   visuel dans une page longue, là où un simple filet ne suffit plus. */

.demarche__liste {
  display: grid;
  gap: 0;
  counter-reset: etape;
}

.demarche__etape {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--e-2) var(--e-5);
  align-items: baseline;
  padding-block: var(--e-5);
  border-top: 1px solid var(--c-bordure);
  counter-increment: etape;
}

.demarche__etape:last-child {
  border-bottom: 1px solid var(--c-bordure);
}

/* Le numéro est généré par la CSS : il n'a pas à être écrit dans le HTML, où
   il ferait doublon à la synthèse vocale avec la structure de liste. */
.demarche__etape::before {
  content: counter(etape);
  grid-row: span 2;
  font-family: var(--f-titre);
  font-size: var(--t-xl);
  line-height: 1;
  color: var(--c-primaire);
  font-variant-numeric: tabular-nums;
}

.demarche__titre {
  margin: 0;
  font-family: var(--f-titre);
  font-size: var(--t-lg);
  font-weight: var(--p-normal);
}

.demarche__texte {
  grid-column: 2;
  color: var(--c-texte-doux);
}

/* Le délai de l'étape, aligné à droite quand la place le permet. */
.demarche__delai {
  display: block;
  margin-top: var(--e-1);
  color: var(--c-texte-tertiaire);
  font-size: var(--t-sm);
  letter-spacing: var(--l-large);
  text-transform: uppercase;
}


/* ===== 9. LOGOS PARTENAIRES ===== */
/* Bandeau de logos de partenaires, clients ou sponsors. Grille auto-fit plutôt
   qu'un nombre de colonnes figé : la mise en page reste équilibrée avec 2 logos
   comme avec 8. Hauteur normalisée et object-fit: contain pour aligner des
   fichiers de proportions très différentes sans les déformer. */

.logos {
  /* Trois réglages pilotent tout le composant. Les valeurs sont dérivées de
     l'échelle d'espacement : aucune mesure n'est inventée ici.
     ATTENTION : c'est --logos-max, PAS --logos-min, qui décide du nombre de
     colonnes. Avec repeat(auto-fit, minmax(min, max)), le navigateur compte les
     pistes d'après la fonction de dimensionnement maximale dès qu'elle est
     définie. À 375px il reste 343px une fois la gouttière de page retirée :
     deux pistes de 9rem plus l'écart de 1,5rem font 312px et tiennent, trois
     en demanderaient 480px et ne tiennent jamais. Le seuil réel des deux
     colonnes est 344px de viewport ; en dessous (320px) on retombe à une seule.
     Ne pas remonter ce plafond sans refaire ce calcul : un logo par ligne fait
     vide, c'est précisément ce que ce composant doit éviter. */
  --logos-min: calc(var(--e-9) + var(--e-6));   /*  8rem — largeur mini d'une piste */
  --logos-max: calc(var(--e-9) + var(--e-7));   /*  9rem — largeur maxi d'une piste */
  --logos-hauteur: var(--e-7);                  /*  3rem — hauteur normalisée */
}

/* Titre volontairement discret : ce bandeau est une preuve sociale, pas un
   argument de vente. D'où la famille de texte et le corps de légende plutôt
   que le traitement habituel d'un h2. */
.logos__titre {
  margin-bottom: var(--e-6);
  color: var(--c-texte-doux);
  font-family: var(--f-texte);
  font-size: var(--t-sm);
  font-weight: var(--p-gras);
  line-height: var(--i-normal);
  letter-spacing: var(--l-large);
  text-align: center;
  text-transform: uppercase;
}

.logos__liste {
  display: grid;
  /* auto-fit + minmax : le navigateur pose autant de colonnes que la largeur
     le permet — le compte se fait sur --logos-max, voir la note plus haut — et
     les pistes vides se réduisent à zéro. Le plafond --logos-max évite qu'avec
     deux logos chacun n'occupe une demi-page.
     min(..., 100%) protège des viewports plus étroits que --logos-min. */
  grid-template-columns: repeat(auto-fit, minmax(min(var(--logos-min), 100%), var(--logos-max)));
  gap: var(--e-5);
  justify-content: center;
  align-items: center;
  margin: 0;
  padding: 0;
  list-style: none;
}

.logos__element {
  display: flex;
  flex-direction: column;   /* colonne d'office : le nom visible, s'il existe, se pose sous le logo */
  align-items: center;
  justify-content: center;
  gap: var(--e-2);
  min-width: 0;             /* sans quoi une image large force la piste à déborder */
}

/* Le lien est facultatif : un logo sans site à pointer reste une simple image. */
.logos__lien {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--e-2);
  width: 100%;
  min-height: 48px;         /* cible tactile confortable au pouce */
  padding: var(--e-2);
  border-radius: var(--r-md);
  color: var(--c-texte-doux);
  text-decoration: none;
  transition: transform var(--d-normal) var(--courbe);
}

.logos__image {
  width: 100%;
  /* Hauteur imposée + contain : des logos carrés, larges ou étroits s'alignent
     sur la même ligne optique sans être étirés. Les attributs width/height du
     HTML restent utiles avant que cette feuille ne soit appliquée. */
  height: var(--logos-hauteur);
  object-fit: contain;
  object-position: center;
  transition: filter var(--d-normal) var(--courbe),
              opacity var(--d-normal) var(--courbe);
}

/* Nom écrit sous le logo. À n'utiliser que si le logo porte alt="" (voir le
   commentaire du fragment HTML), sinon l'information est annoncée deux fois. */
.logos__nom {
  /* La piste étant plafonnée, un nom long d'un seul tenant déborderait sur le
     logo voisin. Les deux déclarations vont ensemble : le span est un élément
     flex dimensionné sur son contenu, et overflow-wrap ne réduit pas cette
     largeur intrinsèque — sans max-width la boîte s'élargit et le mot n'a
     jamais l'occasion de se couper. */
  max-width: 100%;
  overflow-wrap: break-word;
  /* --c-texte-doux et non --c-texte-tertiaire : à 13px en graisse normale le
     seuil AA est 4,5:1, or le gris tertiaire ne donne que 4,18:1 sur le fond
     alterné livré par le fragment. Ici 8,42:1, quel que soit le fond retenu. */
  color: var(--c-texte-doux);
  font-size: var(--t-xs);
  line-height: var(--i-normal);
  letter-spacing: var(--l-normal);
  text-align: center;
  text-transform: none;
}

/* Désaturation au repos, couleur au survol.
   Enfermée dans (hover: hover) à dessein : sur un écran tactile il n'y a pas
   de survol, la couleur ne reviendrait donc jamais et les logos resteraient
   gris à vie. Sur tactile, ils s'affichent d'emblée en couleur.
   SI LE CLIENT TIENT À SES COULEURS DE MARQUE : supprimer ce bloc entier,
   rien d'autre n'en dépend. */
@media (hover: hover) {
  .logos__image {
    filter: grayscale(1);
    opacity: 0.75;
  }

  .logos__lien:hover .logos__image,
  .logos__lien:focus-visible .logos__image,
  .logos__element:hover .logos__image {
    filter: none;
    opacity: 1;
  }

  .logos__lien:hover {
    transform: translateY(calc(var(--e-1) * -0.5));
  }
}

/* Tablette et au-delà : logos un peu plus généreux et respiration accrue. */
@media (min-width: 48em) {
  .logos {
    --logos-max: calc(var(--e-9) + var(--e-9));      /* 12rem */
    --logos-hauteur: calc(var(--e-7) + var(--e-2));  /* 3,5rem */
  }

  .logos__liste {
    gap: var(--e-6);
  }
}


/* ===== 10. RÉALISATIONS (AVANT / APRÈS) ===== */
/* Blocs portfolio : pour chaque réalisation, un contexte court puis deux
   captures étiquetées « Avant » et « Après ». L'étiquette est un <figcaption>
   placé AVANT l'image, seule position qui reste lisible une fois les captures
   empilées à 375px ; elle associe un libellé écrit et un repère de forme
   (cercle creux / cercle plein) pour ne jamais dépendre de la seule couleur.
   Aucun JavaScript, aucune image de fond : la section est utile dès le HTML. */

.realisations__liste {
  display: grid;
  gap: var(--e-6);
}

/* Chaque réalisation est une carte autonome : le cadre sépare visuellement
   deux comparaisons successives sans avoir à compter sur l'ordre des blocs. */
.realisations__bloc {
  padding: var(--e-5);
  background: var(--c-fond);
  border: 1px solid var(--c-bordure);
  border-radius: var(--r-lg);
}

@media (min-width: 48em) {
  .realisations__bloc {
    padding: var(--e-6);
  }
}

.realisations__entete {
  margin-bottom: var(--e-5);
  max-width: var(--l-contenu);
}

.realisations__contexte {
  margin-top: var(--e-2);
  color: var(--c-texte-doux);
}

/* Une colonne à 375px, sans exception. Les deux captures ne passent côte à
   côte qu'à partir de 40em, seuil retenu par les grilles du kit. */
.realisations__captures {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--e-5);
}

@media (min-width: 40em) {
  .realisations__captures {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--e-4);
  }
}

.realisations__capture {
  display: flex;
  flex-direction: column;
  gap: var(--e-2);
}

/* La classe de base ne porte que la structure de la pastille ; chacun des deux
   états déclare ses couleurs. Aucune n'est donc décorative à vide. */
.realisations__etiquette {
  display: inline-flex;
  align-items: center;
  gap: var(--e-2);
  align-self: start;   /* la pastille se règle sur son texte, pas sur la colonne */
  padding: var(--e-1) var(--e-3);
  border-width: 1px;
  border-style: solid;
  border-radius: var(--r-plein);
  font-size: var(--t-sm);
  font-weight: var(--p-gras);
  line-height: var(--i-normal);
  letter-spacing: var(--l-large);
  text-transform: uppercase;
}

/* Repère de forme, creux avant / plein après : la distinction tient encore si
   la couleur n'est pas perçue. content:"" n'ajoute rien à la restitution
   vocale, le sens reste porté par les mots « Avant » et « Après ». */
.realisations__etiquette::before {
  content: "";
  flex: none;
  width: var(--e-3);
  height: var(--e-3);
  border: 1px solid currentColor;
  border-radius: var(--r-plein);
}

.realisations__etiquette--avant {
  background: var(--c-fond-alt);
  color: var(--c-texte);
  border-color: var(--c-bordure);
}

.realisations__etiquette--apres {
  background: var(--c-accent);           /* le jaune est un fond, jamais un texte */
  color: var(--c-sur-accent);            /* 10,7:1 */
  border-color: var(--c-accent-sombre);
}

.realisations__etiquette--apres::before {
  background: currentColor;
}

/* width/height sont portés par les attributs HTML : le navigateur en déduit le
   rapport et réserve la place avant le chargement, donc aucun saut de page. */
.realisations__image {
  width: 100%;
  height: auto;
  background: var(--c-fond-alt);
  border: 1px solid var(--c-bordure);
  border-radius: var(--r-md);
}

.realisations__resultat {
  margin-top: var(--e-5);
  padding-top: var(--e-4);
  border-top: 1px solid var(--c-bordure);
  color: var(--c-texte-doux);
}

.realisations__chiffre {
  display: inline-block;
  margin-inline-end: var(--e-2);
  color: var(--c-primaire);
  font-family: var(--f-titre);
  font-size: var(--t-lg);
  font-weight: var(--p-gras);
  letter-spacing: var(--l-titre);
}


/* --- Variante « exemple » : une maquette, ce qui est livré, le prix ------- */
/* Pour montrer le travail avant d'avoir un client à citer : une seule image,
   maquette ou écran de démonstration, puis ce qu'un tel projet comprend et ce
   qu'il coûte.

   UN EXEMPLE FICTIF SE DIT COMME TEL, à trois endroits : .realisations__mention
   sur chaque projet, le chapô de la section, et des verbes au présent (« ce qui
   est livré », jamais « ce qui a été livré »). Présenter une maquette comme un
   travail réel pour un client serait une pratique commerciale trompeuse.

   MISE EN PAGE. Sous 62em tout s'empile, le titre AVANT l'image, pour qu'on
   sache ce qu'on regarde. Au-delà, l'image à gauche, le texte à droite.
   L'ordre du balisage reste celui de la lecture : titre, image, détail. */

.realisations__bloc--exemple {
  display: grid;
  gap: var(--e-5);
  grid-template-areas:
    "entete"
    "apercu"
    "corps";
}

@media (min-width: 62em) {
  .realisations__bloc--exemple {
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
    grid-template-rows: auto 1fr;
    grid-template-areas:
      "apercu entete"
      "apercu corps";
    align-items: start;
    column-gap: var(--e-7);
  }
}

.realisations__bloc--exemple .realisations__entete {
  grid-area: entete;
  margin-bottom: 0;
}

.realisations__apercu {
  grid-area: apercu;
  margin: 0;
}

/* Sur écran large, l'image accompagne la lecture : elle reste à l'écran tant
   que le texte de son projet défile, au lieu de laisser un blanc sous elle.
   La grille l'aligne en haut de sa zone, qui couvre toute la hauteur du bloc. */
@media (min-width: 62em) {
  .realisations__apercu {
    position: sticky;
    top: calc(var(--h-entete) + var(--e-4));
  }
}

.realisations__corps {
  grid-area: corps;
}

/* La mention n'est pas un ornement : c'est elle qui dit que le projet est une
   démonstration. Elle précède le nom, et se lit avant lui. */
.realisations__mention {
  display: inline-block;
  margin-bottom: var(--e-3);
  padding: var(--e-1) var(--e-2);
  border: 1px solid var(--c-bordure);
  border-radius: var(--r-sm);
  color: var(--c-texte-doux);
  font-size: var(--t-xs);
  font-weight: var(--p-gras);
  line-height: var(--i-normal);
  letter-spacing: var(--l-large);
  text-transform: uppercase;
}

/* Sous 360 px, la mention ne tient plus sur une ligne dans les langues à
   alphabet latin (mesuré à 320 px : 274 px en français pour 238 disponibles,
   et encore 242 px avec l'interlettrage réduit de moitié). Plutôt qu'un cadre
   pleine largeur à moitié vide, elle passe franchement sur deux lignes et le
   cadre épouse le mot le plus long. keep-all : sans lui, une écriture sans
   espaces (le chinois) s'empilerait caractère par caractère.
   22.49em : même convention que les autres seuils du fichier. */
@media (max-width: 22.49em) {
  .realisations__mention {
    width: min-content;
    word-break: keep-all;
  }
}

.realisations__intertitre {
  margin-top: var(--e-5);
  margin-bottom: var(--e-2);
  color: var(--c-primaire);
  font-family: var(--f-texte);
  font-size: var(--t-sm);
  font-weight: var(--p-gras);
  line-height: var(--i-normal);
  letter-spacing: var(--l-large);
  text-transform: uppercase;
}

.realisations__corps > .realisations__intertitre:first-child {
  margin-top: 0;
}

.realisations__livre {
  display: grid;
  gap: var(--e-2);
}

/* Un petit carré en guise de puce, et pas un trait : un trait court se lit
   comme un tiret, et le site n'en emploie aucun. La liste reste une liste pour
   les lecteurs d'écran (role="list"), et le repère ne dépend d'aucune police.
   Il se centre sur la première ligne, calculée sur l'interligne du texte. */
.realisations__livre li {
  position: relative;
  padding-inline-start: var(--e-4);
}

.realisations__livre li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: calc(var(--i-lache) * 0.5em - var(--e-1) / 2);
  width: var(--e-1);
  height: var(--e-1);
  background: var(--c-primaire);
}

.realisations__parti {
  color: var(--c-texte-doux);
}

/* Le prix, en tableau réglé : un libellé à gauche, un montant à droite, en
   chiffres de même chasse pour que les montants s'alignent d'un projet à
   l'autre. Quand les deux ne tiennent pas côte à côte (téléphone étroit,
   langue plus longue), le montant passe dessous, toujours calé à droite,
   au lieu d'écraser le libellé sur trois lignes. */
.realisations__tarif {
  margin-top: var(--e-5);
  border-top: 1px solid var(--c-bordure);
}

.realisations__ligne {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--e-1) var(--e-4);
  padding-block: var(--e-2);
  border-bottom: 1px solid var(--c-bordure);
}

.realisations__ligne dt,
.realisations__ligne dd {
  margin: 0;
}

.realisations__ligne dd {
  margin-inline-start: auto;
  font-variant-numeric: tabular-nums;
  font-weight: var(--p-gras);
  text-align: end;
  white-space: nowrap;
}

.realisations__note {
  margin-top: var(--e-3);
  color: var(--c-texte-doux);
  font-size: var(--t-sm);
}


/* ===== 11. QUESTIONS FRÉQUENTES ===== */
/* Accordéon bâti sur <details>/<summary> natifs : il s'ouvre, se navigue au
   clavier et s'indexe sans une ligne de JavaScript. Le triangle du navigateur
   est masqué au profit d'un chevron tracé en CSS, qui pivote à l'ouverture ;
   le JS ne sert qu'au confort (un seul panneau ouvert à la fois). */

.faq__liste {
  display: grid;
  gap: var(--e-3);
}

.faq__item {
  background: var(--c-fond);
  border: 1px solid var(--c-bordure);
  border-radius: var(--r-md);
  /* Contient le fond de survol du <summary> dans les angles arrondis. */
  overflow: hidden;
  transition: border-color var(--d-rapide) var(--courbe);
}

.faq__item[open] {
  border-color: var(--c-primaire);
  box-shadow: var(--o-sm);
}

/* --- La question, c'est-à-dire la zone cliquable --- */

.faq__question {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--e-4);
  min-height: 48px;          /* cible tactile confortable au pouce */
  padding: var(--e-4);
  cursor: pointer;
  /* Trois écritures pour un seul résultat : faire disparaître le marqueur
     natif, dont la forme et la position varient d'un navigateur à l'autre. */
  list-style: none;
  -webkit-tap-highlight-color: transparent;
}

.faq__question::-webkit-details-marker {
  display: none;
}

.faq__question::marker {
  content: "";
}

.faq__question:hover {
  background: var(--c-fond-teinte);
}

/* L'anneau de focus global est dessiné vers l'extérieur : ici il serait rogné
   par l'overflow de la carte. On le rentre à l'intérieur et on double le
   repère par un aplat, pour qu'il reste franchement visible au clavier. */
.faq__question:focus-visible {
  outline-offset: calc(var(--e-1) * -1);
  background: var(--c-fond-teinte);
}

.faq__intitule {
  font-size: var(--t-md);
  line-height: var(--i-normal);
}

/* --- Le chevron --- */

.faq__chevron {
  flex: none;
  width: var(--e-3);
  height: var(--e-3);
  /* Aligne la pointe sur la première ligne de la question, quel que soit
     le nombre de lignes de celle-ci. */
  margin-top: var(--e-2);
  border-right: 1px solid var(--c-primaire);
  border-bottom: 1px solid var(--c-primaire);
  transform: rotate(45deg);
  transition: transform var(--d-normal) var(--courbe);
}

.faq__item[open] .faq__chevron {
  transform: rotate(-135deg);
}

/* --- La réponse --- */

.faq__reponse {
  padding: 0 var(--e-4) var(--e-4);
  max-width: var(--l-contenu);
  color: var(--c-texte-doux);
}

.faq__reponse p + p {
  margin-top: var(--e-3);
}

/* Apparition en fondu à l'ouverture. Le réglage système « réduire les
   animations » ramène --d-normal à 0ms : l'animation devient instantanée
   sans qu'il faille l'annuler ici. */
.faq__item[open] .faq__reponse {
  animation: faq-apparition var(--d-normal) var(--courbe);
}

@keyframes faq-apparition {
  from {
    opacity: 0;
    transform: translateY(calc(var(--e-1) * -1));
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* --- Relance sous la liste --- */

/* Phrase de relance, pas une légende : elle porte le second chemin de
   conversion (le lien mailto), donc taille du texte courant. La césure évite
   qu'une adresse électronique réelle, insécable, ne déborde à 375px. */
.faq__pied {
  margin-top: var(--e-5);
  color: var(--c-texte-doux);
  font-size: var(--t-base);
  text-align: center;
  overflow-wrap: break-word;
}

/* --- À partir de la tablette : on aère --- */

@media (min-width: 40em) {
  .faq__question {
    padding: var(--e-5);
  }

  .faq__reponse {
    padding: 0 var(--e-5) var(--e-5);
  }
}


/* ===== 12. FORMULAIRE DE CONTACT ===== */
/* Le point de conversion de la page : champs pleine largeur, cibles tactiles
   confortables, et une case de consentement traitée comme un élément de premier
   plan plutôt que reléguée en petits caractères de bas de page.
   Parti pris : l'état d'erreur est piloté par l'attribut aria-invalid, jamais par
   une classe, pour que l'apparence et l'accessibilité ne puissent pas diverger. */

.formulaire {
  display: grid;
  gap: var(--e-5);
  max-width: var(--l-etroit);
}

.formulaire__consigne {
  color: var(--c-texte-doux);
  font-size: var(--t-sm);
}

/* Sur mobile, une colonne : deux champs côte à côte à 375px deviennent
   illisibles et font déborder les étiquettes. */
.formulaire__rangee {
  display: grid;
  gap: var(--e-5);
}

/* Mention « facultatif » accolée à une étiquette. En corps réduit et en gris
   doux : elle informe sans concurrencer l'étiquette, et surtout elle DIT
   POURQUOI le champ existe. Une donnée demandée sans raison visible est une
   donnée qu'on ne donne pas — et un champ facultatif silencieux est lu comme
   une collecte opportuniste. */
.formulaire__facultatif {
  font-weight: var(--p-normal);
  font-size: var(--t-sm);
  color: var(--c-texte-doux);
}

/* Un champ = étiquette, saisie, message d'erreur. */
.formulaire__champ {
  display: grid;
  gap: var(--e-2);
}

.formulaire__etiquette {
  font-weight: var(--p-moyen);
  line-height: var(--i-normal);
}

.formulaire__requis {
  /* PAS --erreur : une étoile d'obligation n'est pas une alarme. Peindre en
     rouge un champ que le visiteur n'a pas encore touché le met en faute avant
     qu'il ait rien fait. Le rouge reste pour l'erreur réelle. */
  color: var(--c-texte);
  font-weight: var(--p-gras);
}


/* --- Champs de saisie : repos, survol, focus, erreur --- */

.formulaire__saisie {
  width: 100%;
  min-height: 48px;                 /* cible tactile */
  padding: var(--e-3) var(--e-4);
  background: var(--c-fond);
  color: var(--c-texte);
  /* --c-bordure ne contraste qu'à 1,4:1 avec le blanc du champ : sur une section
     alternée, où le remplissage lui-même ne se détache presque pas, la bordure
     est le seul contour du champ. --c-texte-tertiaire tient le 3:1 exigé par le
     critère WCAG 1.4.11 (4,7:1 sur blanc). */
  border: 1px solid var(--c-texte-tertiaire);
  border-radius: var(--r-md);
  font-family: var(--f-texte);
  /* Ne jamais descendre sous --t-base : en dessous de 16px, iOS zoome sur le
     champ au focus et casse la mise en page. */
  font-size: var(--t-base);
  line-height: var(--i-normal);
  transition: border-color var(--d-rapide) var(--courbe),
              box-shadow var(--d-rapide) var(--courbe);
}

.formulaire__saisie--zone {
  /* La zone de message contient de la PROSE : elle prend l'interligne de la
     prose, pas celui d'un champ d'une seule ligne. */
  line-height: var(--i-lache);
  min-height: var(--e-9);
  resize: vertical;                 /* jamais horizontal : ferait déborder la grille */
}

/* Plus soutenu que le repos, sinon le survol ne se distinguerait plus. */
.formulaire__saisie:hover {
  /* PAS --c-texte-doux : au repos la bordure est en --c-texte-tertiaire, et les
     deux gris sont voisins dans toute charte sobre — le survol ne produisait
     RIEN de visible. La couleur principale tranche par construction, quelle que
     soit la palette du client. */
  border-color: var(--c-primaire);
}

/* L'anneau de focus global du kit reste en place ; on renforce seulement la
   bordure pour que le champ actif se repère aussi à la souris. */
.formulaire__saisie:focus {
  border-color: var(--c-focus);
}

.formulaire__saisie[aria-invalid="true"] {
  border-color: var(--erreur);
  /* Double la bordure pour que l'erreur se voie aussi sans la couleur. Le 1px
     est la seule largeur de trait tolérée par le contrat, et box-shadow plutôt
     qu'outline : outline écraserait l'anneau de focus du kit, dont le sélecteur
     :focus-visible a une spécificité plus faible. Il n'existe pas de jeton
     --o-* pour ce trait ; en ajouter un touche theme.css, hors périmètre. */
  box-shadow: 0 0 0 1px var(--erreur);
}

/* Remplissage automatique. Les navigateurs repeignent le champ en bleu système,
   ce qui écrase la charte du client et casse le contraste calculé. La teinte
   interne de l'ombre est le seul moyen de reprendre la main sur ce fond. */
.formulaire__saisie:-webkit-autofill,
.formulaire__saisie:-webkit-autofill:hover,
.formulaire__saisie:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--c-texte);
  box-shadow: 0 0 0 100px var(--c-fond) inset;
}

.formulaire__erreur {
  color: var(--erreur);
  font-size: var(--t-sm);
  line-height: var(--i-normal);
}

/* Vide, le conteneur d'erreur ne doit ni occuper de place ni être annoncé. */
.formulaire__erreur:empty {
  display: none;
}

/* Le symbole double l'information portée par la couleur : l'erreur reste
   identifiable en niveaux de gris ou en cas de daltonisme. */
.formulaire__erreur:not(:empty)::before {
  /* \FE0E impose la variante TEXTE. Sans lui, iOS et Android rendent ce
     caractère en emoji couleur : un triangle orange vif au milieu d'une charte
     qui tient en deux tons. */
  content: "\26A0\FE0E";
  margin-right: var(--e-1);
}


/* --- Consentement RGPD --- */

.formulaire__consentement {
  display: flex;
  align-items: flex-start;
  gap: var(--e-3);
  min-height: 48px;                 /* la ligne entière est une cible tactile */
  padding: var(--e-3);
  background: var(--c-fond-teinte);
  border-radius: var(--r-md);
  cursor: pointer;
}

.formulaire__case {
  /* Décalage optique : la case fait 24px de haut, la première ligne du libellé
     en occupe 27,2. Sans ce retrait, la case flotte au-dessus du texte qu'elle
     accompagne. */
  margin-block-start: 2px;
  flex: none;
  width: var(--e-5);
  height: var(--e-5);
  margin: 0;
  accent-color: var(--c-primaire);
  cursor: pointer;
}

.formulaire__case[aria-invalid="true"] {
  box-shadow: 0 0 0 1px var(--erreur);
  border-radius: var(--r-sm);
}

/* Signale l'encart entier quand la case manque. Amélioration progressive :
   sans prise en charge de :has(), le message d'erreur reste seul, ce qui suffit. */
.formulaire__consentement:has(.formulaire__case[aria-invalid="true"]) {
  background: var(--c-fond);
  outline: 1px solid var(--erreur);
}

/* Texte de lecture, pas une légende : il énonce la finalité du traitement et
   doit être lu avant de cocher. Jamais sous --t-base. */
.formulaire__consentement-texte {
  font-size: var(--t-base);
  line-height: var(--i-lache);
}

/* Le lien de la politique est volontairement HORS du <label> : à l'intérieur,
   sa boîte cliquable jouxtait sans écart celle de la case, et un appui
   approximatif quittait la page en perdant le message saisi. Sorti, il garde
   sa hauteur de cible tactile et le gap de .formulaire__champ l'en sépare.
   justify-self empêche qu'en élément de grille il ne s'étire sur toute la
   ligne, ce qui ferait une cible bien plus large que son texte. */
.formulaire__lien-politique {
  display: inline-flex;
  align-items: center;
  justify-self: start;
  min-height: 48px;
}


/* --- Pied du formulaire : bouton puis zone d'annonce --- */

/* Bloc simple et non pas grille : une zone de statut vide doit rester dans le
   DOM pour role="status", sans créer d'espace ni décaler le bouton. Elle est
   placée APRÈS le bouton dans le fragment : dès que le script y écrit, l'encart
   prend près de 5rem de haut, et au-dessus du bouton il l'aurait fait descendre
   sous le pouce à l'instant même où l'utilisateur le vise. */
.formulaire__statut {
  font-weight: var(--p-moyen);
}

.formulaire__statut:not(:empty) {
  margin-top: var(--e-4);
  padding: var(--e-4);
  background: var(--c-fond);
  border: 1px solid var(--c-bordure);
  border-radius: var(--r-md);
}

.formulaire__statut--attente:not(:empty) {
  color: var(--c-texte-doux);
}

/* Succès et erreur portent un PICTOGRAMME en plus de la teinte. Séparés par la
   seule couleur, ils sont indiscernables pour les 8 % d'hommes qui confondent
   le rouge et le vert — et c'est précisément ici, au moment où l'on apprend si
   son message est parti, qu'il ne faut pas avoir à deviner.
   \FE0E impose la variante texte : sans lui, les deux signes virent à l'emoji
   couleur sur mobile. */
.formulaire__statut--succes:not(:empty) {
  background: var(--c-fond-teinte);
  /* --succes et non --c-primaire : sur une charte dont la couleur de marque est
     rouge ou orange, un message de reussite encadre de cette couleur dit le
     contraire de ce qu'il annonce. */
  border-color: var(--succes);
  color: var(--c-texte);
}

.formulaire__statut--succes:not(:empty)::before {
  content: "\2713\FE0E";
  margin-right: var(--e-2);
  font-weight: var(--p-gras);
}

.formulaire__statut--erreur:not(:empty) {
  border-color: var(--erreur);
  color: var(--erreur);
}

.formulaire__statut--erreur:not(:empty)::before {
  content: "\26A0\FE0E";
  margin-right: var(--e-2);
}

/* Point clignotant pendant l'envoi. La durée dérive de --d-normal : sous le
   réglage « réduire les animations », le thème la ramène à 0 et le point se fige. */
.formulaire__statut--attente:not(:empty)::before {
  content: "";
  display: inline-block;
  width: var(--e-2);
  height: var(--e-2);
  margin-right: var(--e-2);
  border-radius: var(--r-plein);
  background: var(--c-primaire);
  animation: formulaire-pulsation calc(var(--d-normal) * 5) var(--courbe) infinite alternate;
}

@keyframes formulaire-pulsation {
  from { opacity: 1; }
  to   { opacity: 0.2; }
}

/* Arrêt EXPLICITE, et non une durée ramenée à zéro. Une animation de durée
   nulle en alternate infinite ne se fige pas à coup sûr sur son image de
   départ : le point pouvait rester à 0.2 d'opacité, c'est-à-dire invisible,
   alors qu'il est le seul signe que l'envoi est en cours. */
@media (prefers-reduced-motion: reduce) {
  .formulaire__statut--attente:not(:empty)::before {
    animation: none;
    opacity: 1;
  }
}

/* Envoi en cours : les champs se retirent, seul le bouton parle. Le retrait
   n'est ici que visuel — c'est le script qui verrouille réellement les champs
   (readOnly, et disabled sur la case). pointer-events serait un faux ami : il
   ne concerne pas le clavier, donc il promettrait un verrou inexistant, et il
   empêcherait au passage de sélectionner le texte déjà saisi. */
.formulaire[aria-busy="true"] {
  cursor: progress;
}

.formulaire[aria-busy="true"] .formulaire__rangee,
.formulaire[aria-busy="true"] .formulaire__champ,
.formulaire[aria-busy="true"] .formulaire__consentement {
  opacity: 0.6;
}


/* --- Mention légale sous le formulaire --- */

.formulaire__mention {
  /* Sans mesure de lecture, ce paragraphe atteignait 540 signes par ligne sur un
     grand écran, en corps 13 : illisible, et c'est précisément le texte que le
     visiteur doit pouvoir lire avant de cocher. */
  max-width: var(--l-contenu);
  color: var(--c-texte-doux);       /* 9,2:1 : une mention légale se lit */
  font-size: var(--t-xs);
  line-height: var(--i-lache);
  /* L'adresse RGPD est insécable et souvent longue. anywhere et non break-word :
     seul anywhere entre dans le calcul de la taille min-content, donc seul lui
     empêche ce paragraphe d'élargir la piste du formulaire, qui est une grille,
     et de faire déborder la page entière à 375px. */
  overflow-wrap: anywhere;
}


/* --- À partir de 40em : nom et e-mail partagent une ligne --- */

@media (min-width: 40em) {
  .formulaire__rangee {
    grid-template-columns: repeat(2, 1fr);
  }
}


/* ===== 13. BOUTON COLLANT MOBILE ===== */
/* Barre fixe en bas d'écran, sous 48em uniquement : au-dessus, l'en-tête collant
   suffit et la barre disparaît complètement. Elle est VISIBLE par défaut ; c'est
   le JS qui installe l'état masqué (classe --js), pour qu'une page sans
   JavaScript garde l'accès au contact. Le texte se tronque, jamais le bouton.

   Zone sûre iOS : les deux règles env(safe-area-inset-bottom) de ce fichier (le
   padding de la barre et la réserve du body) ne prennent effet que si le
   <meta name="viewport"> de la page porte viewport-fit=cover. Sans lui elles
   valent 0 et iOS réduit lui-même le viewport, il n'y a rien à faire. Avec lui,
   elles vont par paire : retirer la seconde ferait passer le pied de page sous
   la barre, sans moyen de le faire remonter. */

.cta-collant {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: var(--z-collant);
  display: flex;
  align-items: center;
  gap: var(--e-3);
  min-height: var(--h-collant);
  padding-block: var(--e-2);
  padding-inline: var(--e-4);
  /* Zone sûre iPhone : la barre d'accueil ne doit pas recouvrir le bouton.
     Le 0px n'est qu'un repli pour les navigateurs sans env(). */
  padding-bottom: calc(var(--e-2) + env(safe-area-inset-bottom, 0px));
  background: var(--c-fond);
  border-top: 1px solid var(--c-bordure);
  box-shadow: var(--o-collant);
}

/* Le body réserve déjà --h-collant en bas de page (section 0 de kit.css). Seule
   la zone sûre doit s'y ajouter : avec viewport-fit=cover la barre grandit de
   l'encoche, la réserve doit suivre. Inerte sans viewport-fit=cover. */
@media (max-width: 47.99em) {
  body {
    padding-bottom: calc(var(--h-collant) + env(safe-area-inset-bottom, 0px));
  }
}

/* La barre recouvre en permanence le bas de la fenêtre. Sans cette réserve, un
   élément atteint au clavier ou par une ancre s'arrête juste derrière elle et
   reste invisible (WCAG 2.4.11). Le pendant haut, scroll-padding-top, est déjà
   posé en section 0 de kit.css. */
html {
  /* --e-4 de marge en plus : à la hauteur exacte de la barre, une ancre
     atteinte au clavier vient se coller contre elle sans respiration. La
     réserve du haut s'accorde déjà la même. */
  scroll-padding-bottom: calc(var(--h-collant) + var(--e-4));
}

/* Rappel de prix ou de promesse. min-width: 0 est ce qui autorise la troncature
   au lieu de laisser le texte pousser le bouton hors de l'écran à 375px. */
.cta-collant__rappel {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.cta-collant__prix {
  overflow: hidden;
  color: var(--c-texte);
  font-size: var(--t-base);
  font-weight: var(--p-gras);
  line-height: var(--i-serre);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.cta-collant__mention {
  overflow: hidden;
  color: var(--c-texte-doux);
  font-size: var(--t-xs);
  line-height: var(--i-normal);
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* En dessous de 22.5em de large, il ne resterait au rappel que la place d'un
   fragment : « À partir de 6… » afficherait un montant faux plutôt qu'aucun
   montant. On le retire, et la barre laisse toute sa largeur au bouton.

   LE SEUIL VIENT D'UNE MESURE, PAS D'UNE ESTIMATION. Le rappel porte désormais
   le prix ET le nom des deux offres ; le plus long des cinq libellés cesse de
   tenir en dessous de 360 px, soit 22.5em. Un seuil plus bas laisserait passer
   un prix tronqué, c'est-à-dire un montant faux, ce qui est pire que pas de
   prix du tout. Refaire la mesure si un libellé change.

   Le seuil est en em, donc il suit aussi l'agrandissement du texte par
   l'utilisateur : à 200 %, tous les téléphones passent en dessous, et c'est
   voulu.

   22.49em et non 22.5em : la convention du fichier (47.99em plus haut), pour
   qu'à 360 px pile le rappel s'affiche. */
@media (max-width: 22.49em) {
  .cta-collant__rappel {
    display: none;
  }
}

/* Le bouton est prioritaire : il ne rétrécit pas, c'est le rappel qui se tronque.
   Padding latéral resserré par rapport à .bouton, l'espace est compté à 375px.
   max-width borne le seul cas où il ne tiendrait pas malgré tout (texte agrandi
   à 200 % sur écran étroit) : le libellé se replie alors sur deux lignes et la
   barre grandit vers le haut, au lieu d'être coupée hors écran — la barre étant
   en position fixe, la partie débordante serait définitivement inatteignable.
   min-height rappelé ici pour que la cible tactile tienne même si l'élément
   perdait la classe .bouton. */
.cta-collant__bouton {
  flex: 0 0 auto;
  max-width: 100%;
  min-height: 48px;
  padding-inline: var(--e-4);
}

/* État masqué. Ajouté uniquement par le JS, via --js : sans lui, aucune de ces
   règles ne s'applique et la barre reste affichée.
   La visibilité bascule en 0s, retardée de --d-normal, pour laisser le
   glissement se terminer avant de sortir la barre de l'ordre de tabulation. */
.cta-collant--js {
  transform: translateY(100%);
  visibility: hidden;
  transition: transform var(--d-normal) var(--courbe),
              visibility 0s linear var(--d-normal);
}

.cta-collant--js.cta-collant--visible {
  transform: translateY(0);
  visibility: visible;
  transition: transform var(--d-normal) var(--courbe),
              visibility 0s linear;
}

/* Pose de l'état initial au chargement, sans glissement fantôme.
   Le JS retire cette classe une fois la première peinture passée.
   Classe doublée pour passer devant .cta-collant--js.cta-collant--visible
   ci-dessus, qui redéclare transition et l'emporterait sinon. */
.cta-collant--immediat.cta-collant--immediat {
  transition: none;
}

@media (prefers-reduced-motion: reduce) {
  .cta-collant--js,
  .cta-collant--js.cta-collant--visible {
    transition: none;
  }
}

/* Au-delà de 48em, l'en-tête collant prend le relais : la barre ne doit occuper
   aucune place, d'où display: none plutôt qu'un simple masquage visuel. */
@media (min-width: 48em) {
  .cta-collant {
    display: none;
  }

  /* Plus de barre, donc plus rien à réserver en bas pour les ancres. */
  html {
    scroll-padding-bottom: 0;
  }
}


/* ===== 14. PIED DE PAGE ===== */
/* Bloc de clôture posé sur .section--sombre : marque, contact, liens légaux,
   réseaux facultatifs et mentions obligatoires (forme juridique, SIREN).
   Partis pris : les liens portent leur couleur sur leur propre classe plutôt
   que d'hériter de .section--sombre, pour que le survol reste lisible même si
   le composant est déplacé ; et l'anneau de focus repasse en jaune, le vert
   par défaut ne ressortant pas sur ce fond (1,9:1). */


/* Le composant attend un fond sombre : c'est la classe .section--sombre du
   contrat commun qui le fournit, sur le même <footer>. */
.pied {
  font-size: var(--t-base);
}

/* Haut : marque + colonnes. Une seule colonne à 375px, sans exception. */
.pied__haut {
  display: flex;
  flex-direction: column;
  gap: var(--e-6);
}

.pied__nom {
  font-family: var(--f-titre);
  font-size: var(--t-lg);
  font-weight: var(--p-gras);
  line-height: var(--i-serre);
  letter-spacing: var(--l-titre);
  color: var(--c-texte-inverse);   /* 12,8:1 */
}

/* La largeur de lecture se limite ici, sur le paragraphe, et non sur
   .pied__marque : plafonner le bloc l'empêcherait de prendre toute la ligne
   flex, et les colonnes voisines se retrouveraient écrasées entre 40 et 60em. */
.pied__description {
  max-width: var(--l-contenu);
  margin-top: var(--e-3);
  color: var(--c-texte-inverse);
  line-height: var(--i-lache);
  opacity: 0.9;                    /* 10,6:1 : la hiérarchie ne coûte pas la lisibilité */
}

/* Une colonne de liens. Reste autonome : si un bloc est retiré, les autres
   se répartissent la largeur restante. */
.pied__bloc {
  min-width: 0;
}

.pied__titre {
  margin-bottom: var(--e-2);
  font-family: var(--f-texte);
  font-size: var(--t-sm);
  font-weight: var(--p-gras);
  line-height: var(--i-normal);
  letter-spacing: var(--l-large);
  text-transform: uppercase;
  color: var(--c-accent);          /* 7,8:1 */
}

/* gap plutôt que des marges : les liens sont écartés d'au moins --e-3 sans
   règle de dernier enfant, et l'écart tient si on ajoute ou retire une ligne.
   Au pouce, deux cibles jointives se confondent. */
.pied__liste {
  display: flex;
  flex-direction: column;
  gap: var(--e-3);
}

/* <address> passe en italique par défaut dans les navigateurs. */
.pied__contact {
  display: flex;
  flex-direction: column;
  gap: var(--e-3);
  font-style: normal;
}

.pied__adresse {
  color: var(--c-texte-inverse);   /* 12,8:1 */
  line-height: var(--i-lache);
}

/* Cible tactile de 48px de haut, quelle que soit la longueur du libellé.
   overflow-wrap évite qu'une adresse électronique longue ne déborde à 375px. */
.pied__lien {
  display: inline-flex;
  align-items: center;
  min-height: 48px;
  padding-block: var(--e-1);
  color: var(--c-accent);          /* 7,8:1 sur --c-fond-sombre */
  overflow-wrap: anywhere;
  transition: color var(--d-rapide) var(--courbe);
}

/* Indispensable : sans cette règle, le a:hover du contrat commun repeindrait
   le lien en --c-lien-survol, c'est-à-dire la couleur du fond. */
.pied__lien:hover {
  color: var(--c-texte-inverse);   /* 12,8:1 */
}

/* Réseaux sociaux : bloc facultatif. En ligne dès qu'il y a la place, mais
   toujours avec le même écart minimal entre deux cibles. */
.pied__reseaux {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e-3) var(--e-5);
}

/* Un nom de réseau peut tenir en une lettre (« X ») : sans plancher, la cible
   tomberait à 9px de large. Même exception que le min-height de 48px.
   Portée limitée au bloc réseaux : ailleurs, .pied__contact étire ses liens sur
   toute la colonne, et un centrage y décalerait l'adresse électronique. */
.pied__reseaux .pied__lien {
  min-width: 48px;
  justify-content: center;
}

/* Bas : filet de séparation puis mentions légales. Le filet est décoratif,
   d'où un vert volontairement discret sur le fond sombre. */
.pied__bas {
  display: flex;
  flex-direction: column;
  gap: var(--e-2);
  margin-top: var(--e-7);
  padding-top: var(--e-5);
  border-top: 1px solid var(--c-primaire);
}

.pied__mentions,
.pied__droits {
  font-size: var(--t-xs);
  line-height: var(--i-normal);
  color: var(--c-texte-inverse);
  opacity: 0.9;                    /* 10,6:1 */
}

/* L'anneau de focus du contrat est vert (--c-focus) : sur --c-fond-sombre il
   tombe à 1,9:1, sous les 3:1 exigés pour un indicateur. Le jaune y monte à
   7,8:1. On ne change que la couleur, l'épaisseur et l'écart restent communs. */
.pied :focus-visible {
  outline-color: var(--c-accent);
}


/* À partir de 40em, la marque prend toute la largeur et les colonnes se
   partagent la ligne suivante. flex-basis: 0 → colonnes égales quel que soit
   leur nombre, y compris si le bloc réseaux sociaux est supprimé. */
@media (min-width: 40em) {
  .pied__haut {
    flex-direction: row;
    flex-wrap: wrap;
    column-gap: var(--e-7);
  }

  .pied__marque {
    flex: 1 1 100%;
  }

  .pied__bloc {
    flex: 1 1 0;
  }

  .pied__bas {
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--e-3) var(--e-5);
  }
}

/* À partir de 60em, tout tient sur une seule ligne, la marque en tête. */
@media (min-width: 60em) {
  .pied__marque {
    flex: 2 1 0;
  }
}


/* ===== 15. PAGES ANNEXES ===== */
/* Mentions légales et politique de confidentialité. Pages de texte long, sans
   fioriture : une seule colonne étroite, un rythme vertical marqué pour que la
   lecture reste possible sur mobile.
   Ces pages ne sont pas décoratives, elles sont opposables — leur mise en forme
   doit servir la lisibilité, rien d'autre. */

.page-legale {
  padding-block: var(--e-7) var(--e-8);
}

.page-legale__entete {
  margin-bottom: var(--e-6);
  padding-bottom: var(--e-5);
  border-bottom: 1px solid var(--c-bordure);
}

.page-legale__maj {
  display: block;
  margin-top: var(--e-3);
  color: var(--c-texte-doux);
  font-size: var(--t-sm);
}

/* Sommaire. Utile dès que la page dépasse deux écrans, ce qui est toujours
   le cas d'une politique de confidentialité honnête. */
.page-legale__sommaire {
  margin-bottom: var(--e-7);
  padding: var(--e-4) var(--e-5);
  background: var(--c-fond-alt);
  border-radius: var(--r-lg);
}

.page-legale__sommaire h2 {
  font-size: var(--t-md);
  margin-bottom: var(--e-3);
}

.page-legale__sommaire ol {
  display: grid;
  /* --e-2 et non --e-1 : à 4px d'écart, deux cibles tactiles de 44px voisines se
     confondent au pouce et le visiteur ouvre la mauvaise section. */
  gap: var(--e-2);
  counter-reset: sommaire;
}

.page-legale__sommaire li {
  counter-increment: sommaire;
}

.page-legale__sommaire a {
  /* Cible tactile pleine largeur : au doigt, on vise mal une ligne de texte. */
  display: block;
  min-height: 44px;
  padding-block: var(--e-2);
  text-decoration: none;
}

.page-legale__sommaire a::before {
  content: counter(sommaire) ". ";
  color: var(--c-texte-tertiaire);
}

.page-legale__sommaire a:hover,
.page-legale__sommaire a:focus-visible {
  text-decoration: underline;
}

/* Corps de texte. Largeur de lecture bornée : au-delà, l'œil perd la ligne. */
.page-legale__corps {
  max-width: var(--l-contenu);
}

.page-legale__corps h2 {
  margin-top: var(--e-7);
  margin-bottom: var(--e-3);
  font-size: var(--t-lg);
}

.page-legale__corps h3 {
  margin-top: var(--e-5);
  margin-bottom: var(--e-2);
  font-size: var(--t-md);
}

.page-legale__corps p,
.page-legale__corps ul,
.page-legale__corps dl {
  margin-bottom: var(--e-4);
}

.page-legale__corps li {
  position: relative;
  margin-bottom: var(--e-2);
  padding-left: var(--e-5);
}

.page-legale__corps li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: var(--e-2);
  height: 2px;
  background: var(--c-primaire);
}

/* Listes de définitions : le bon élément pour « Responsable : X », « Durée : Y ».
   Sur mobile, terme au-dessus de la définition ; deux colonnes ensuite. */
.page-legale__corps dt {
  font-weight: var(--p-gras);
}

.page-legale__corps dd {
  margin: 0 0 var(--e-3) 0;
  color: var(--c-texte-doux);
}

@media (min-width: 40em) {
  .page-legale__corps dl {
    display: grid;
    grid-template-columns: minmax(auto, 14rem) 1fr;
    gap: var(--e-2) var(--e-5);
  }

  .page-legale__corps dd {
    margin-bottom: 0;
  }
}

/* Les adresses email et URLs longues doivent pouvoir être coupées, sinon elles
   débordent de l'écran à 375px. */
.page-legale__corps a {
  overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------------------
   Champs à compléter
   Surligne en jaune tout ce qui reste à renseigner. Le but est qu'il soit
   IMPOSSIBLE de mettre une page en ligne avec des trous sans les voir.
   Le jaune est ici un fond, avec du quasi-noir dessus : 10,7:1.
   Une fois la page renseignée, il ne doit plus rester un seul .a-completer.
   --------------------------------------------------------------------------- */
.a-completer {
  padding: 0 var(--e-1);
  /* VALEURS EN DUR, ET C'EST VOULU — seule exception du kit.
     Ce surlignage n'appartient à aucune identité : c'est une alarme de
     brouillon. S'il empruntait --c-accent, il prendrait la couleur de la marque
     et se fondrait dans la page — exactement ce qui s'est produit sur une charte
     verte, où l'alarme est devenue invisible. Un magenta franc jure avec toutes
     les chartes, ce qui est précisément sa fonction.
     Blanc dessus : 5,8:1. Il ne doit plus en rester un seul à la mise en ligne. */
  background: #C2007E;
  color: #FFFFFF;
  border-radius: var(--r-sm);
  font-weight: var(--p-moyen);
  /* Pas seulement la couleur : le soulignement pointillé porte l'information
     pour qui ne distingue pas le magenta. */
  text-decoration: underline dotted currentColor;
  text-underline-offset: 0.15em;
}

/* Rappel en haut de page tant qu'il reste des champs vides. À retirer, comme
   les .a-completer, une fois la page complète. */
.avis-brouillon {
  margin-bottom: var(--e-6);
  padding: var(--e-4);
  /* Même raison que .a-completer : une alarme ne se teint pas aux couleurs
     de la marque, sinon elle cesse d'alarmer. */
  background: #C2007E;
  color: #FFFFFF;
  border-radius: var(--r-md);
  font-weight: var(--p-moyen);
}

.avis-brouillon p {
  margin: 0;
}

@media print {
  .avis-brouillon,
  .page-legale__sommaire {
    display: none;
  }

  .a-completer {
    background: none;
    color: inherit;
  }
}
