/* =========================================================================
   SWISS PRO MOVING — ACCUEIL
   Ce que index.html ajoute à base.css et aux trois feuilles de marque, et
   rien d'autre. Se charge dans cet ordre :

     <link rel="stylesheet" href="base.css">                    <- + marque/tokens.css, marque/base.css
     <link rel="stylesheet" href="marque/components.css">
     <link rel="stylesheet" href="marque/rayon-Composants.css">
     <link rel="stylesheet" href="marque/rayon-Marque.css">
     <link rel="stylesheet" href="accueil.css">
     <link rel="stylesheet" href="carte.css">

   Contient : le rythme vertical, l'itinéraire vertical (la signature), le
   héros « la phrase EST le formulaire », les huit prestations, les quatre
   temps, l'aplat rouge final et son pli.
   La section « Où nous allons » vit tout entière dans carte.css : une règle,
   un seul fichier.

   AUCUN JETON N'EST DÉCLARÉ ICI. Aucune couleur écrite en dur, aucune ombre,
   aucun rayon sur un rectangle, aucune diagonale hors des deux pentes du
   logo. Les couleurs se lisent toutes dans marque/tokens.css.
   ========================================================================= */

/* ----------------------------------------------------- 1. Rythme vertical */

/* Un seul propriétaire de l'écart : margin-top. Les marges basses d'un frère
   et du suivant fusionnaient dans le flow-root de .piste, et la séquence
   voulue (inégale) se ramenait à la suite des maxima, plus régulière.
   La section « Ce qu'on nous écrit » a disparu (cahier §4.6) : la séquence
   passe de cinq à quatre temps, et le dernier ouvre sur l'aplat rouge.
   Séquence mobile  : 72 · 96 · 112 · 96.
   Séquence 1080 px : 128 · 160 · 160 · 160. */
.section-prestations { margin: 72px 0 0; }
.section-etapes      { margin: var(--e96) 0 0; }
.section-carte       { margin: 112px 0 0; }
.section-final       { margin: var(--e96) 0 0; }

@media (min-width: 1080px) {
  .section-prestations { margin-top: var(--e128); }
  .section-etapes      { margin-top: var(--e160); }
  /* Était --e200 : l'échelle de marque s'arrête à --e160, et 200 n'est pas un
     pas de cette échelle (cahier §2.3). */
  .section-carte       { margin-top: var(--e160); }
  .section-final       { margin-top: var(--e160); }
}

/* ------------------------------------------ 2. L'itinéraire (signature) */

.piste {
  position: relative;
  display: flow-root;
}

.itineraire {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: var(--z-trajet);
  pointer-events: none;
  overflow: visible;
}

/* Le liseré sombre ne vit QUE sous les bandes de photographie : c'est
   l'exception n° 2 de fondations.md §3.6, écrite pour ce cas précis. Le
   clip-path le borne aux rectangles posés dans le balisage et remesurés par
   script.js. Sur le blanc de la page un halo permanent se lirait comme une
   ombre, et ce système n'en a aucune (--ombre: none). */
.trace-lisere {
  fill: none;
  stroke: var(--voile-photo);
  stroke-width: 6;
  stroke-linecap: butt;
  stroke-linejoin: miter;
}

.trace {
  fill: none;
  stroke: var(--trajet-parcouru);
  stroke-width: var(--trajet-trait);
  stroke-linecap: butt;
  stroke-linejoin: miter;
}

/* Le même trait, en blanc, borné à l'aplat rouge final. Sans lui le dernier
   segment du trajet serait rouge sur rouge : invisible, juste là où il mène
   à l'anneau qui se ferme. */
.trace-contre {
  fill: none;
  stroke: var(--sur-marque);
  stroke-width: var(--trajet-trait);
  stroke-linecap: butt;
  stroke-linejoin: miter;
}

/* Chaque repère tire un tiret de 12 px vers le texte qu'il marque. Un
   itinéraire a des arrêts nommés ; un point seul n'est qu'une perle sur un
   fil. Valeurs de fondations.md §7.1 : départ Ø 10, arrêt Ø 7 cerné de 2,
   anneau Ø 20 au trait de 1,5. */
.tiret {
  stroke: var(--trajet-restant);
  stroke-width: 1.5;
  stroke-linecap: butt;
}

.tiret.atteint { stroke: var(--trajet-parcouru); }

/* SOUS 768 px, PAS DE TIRET. La gouttière disparaît (--retrait-rail tombe à
   0 et --rail-in à -20 px) : le repère se pose 20 px à gauche de la colonne
   de texte, et le tiret, qui mesure 12 px à partir de 10 px du centre, entre
   de 2,5 px DANS le premier mot. Relevé à la capture : « Comment ça se
   passe » s'affichait avec un trait rouge en travers du « C », c'est-à-dire
   exactement comme un texte barré. Le disque suffit à marquer l'arrêt ; le
   tiret est ce qui le relie au texte, et sur téléphone il n'y a plus de
   place entre les deux. Il s'en va, l'information reste. */
@media (max-width: 767px) {
  .tiret { display: none; }
}

.marqueur {
  r: 3.5px;
  fill: var(--fond);
  stroke: var(--trajet-parcouru);
  stroke-width: 2;
  transform-box: fill-box;
  transform-origin: center;
}

.marqueur.atteint { fill: var(--trajet-parcouru); }

.disque {
  r: 5px;
  fill: var(--trajet-parcouru);
  stroke: none;
  transform-box: fill-box;
  transform-origin: center;
}

.anneau {
  r: 10px;
  fill: none;
  stroke: var(--trajet-parcouru);
  stroke-width: var(--trajet-arrivee-trait);
}

/* Les quatre derniers éléments tombent DANS l'aplat rouge (§4.5 point 6).
   La valeur s'y inverse, terme à terme : le blanc prend la place du rouge et
   le rouge celle du fond. Même dessin, même dégagement, même sens. */
.marqueur.sur-marque {
  fill: var(--marque);
  stroke: var(--sur-marque);
}

.marqueur.sur-marque.atteint { fill: var(--sur-marque); }
.tiret.sur-marque { stroke: var(--sur-marque); }
.tiret.sur-marque.atteint { stroke: var(--sur-marque); }
.disque.sur-marque { fill: var(--sur-marque); }
.anneau.sur-marque { stroke: var(--sur-marque); }

@media (min-width: 768px) {
  .marqueur { r: 3.5px; }
  .disque { r: 5px; }
  .anneau { r: 10px; }
}

/* Tant que le script n'a pas remesuré le viewBox, celui-ci est étiré en
   hauteur par preserveAspectRatio="none" : vector-effect protège l'épaisseur
   du trait, jamais la géométrie, et les disques se rendraient en ellipses.
   Sans JavaScript la ligne reste donc intégralement tracée, mais sans
   marqueurs déformés. */
.itineraire:not([data-mesure]) .marqueur,
.itineraire:not([data-mesure]) .tiret,
.itineraire:not([data-mesure]) .disque,
.itineraire:not([data-mesure]) .anneau { visibility: hidden; }

/* État par défaut : ligne entièrement tracée, repères posés.
   Le script n'ajoute .trace-js que si le mouvement est autorisé. */
.trace-js .trace-lisere,
.trace-js .trace,
.trace-js .trace-contre { stroke-dasharray: 1000; stroke-dashoffset: 1000; }

.trace-js .marqueur,
.trace-js .disque {
  opacity: 0;
  transform: scale(.4);
  transition: opacity var(--duree-courte) var(--entree),
              transform var(--duree-moyenne) var(--trace);
}

.trace-js .tiret {
  opacity: 0;
  transition: opacity var(--duree-courte) var(--entree);
}

.trace-js .marqueur.atteint,
.trace-js .tiret.atteint,
.trace-js .disque.atteint {
  opacity: 1;
  transform: scale(1);
}

/* L'anneau est OUVERT jusqu'à l'arrivée, et il se ferme en arrivant. C'est
   le cœur de l'idée : on ne relance pas le trajet, on le termine. */
.trace-js .anneau {
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  transition: stroke-dashoffset var(--duree-longue) var(--trace) var(--duree-instant);
}

.trace-js .anneau.atteint { stroke-dashoffset: 0; }

/* Ancres invisibles : le script y accroche la ligne. Le bloc de référence est
   la boîte de remplissage de l'ancêtre positionné. Pour un h2, ce bord
   coïncide avec la colonne de texte ; pour un élément déjà en retrait du
   rail, --retrait-local le ramène dans le couloir. */
.ancre {
  position: absolute;
  top: 50%;
  left: calc(var(--rail-in) - var(--retrait-local, 0px) + var(--dx, 0) * var(--amplitude));
  width: 1px;
  height: 1px;
  pointer-events: none;
}

/* base.css ne pose plus `h2 { position: relative }` — la règle est partie
   avec le socle de titres, repris des feuilles de marque. Sans elle, les
   ancres d'un h2 se résolvent contre .piste, c'est-à-dire contre la page
   entière, et `top: 50%` les empile toutes au milieu du document.
   ⚠️ SIGNALÉ à L0 : la même dépendance existe partout où une .ancre vit dans
   un titre. Ici, elle est réparée pour l'accueil seul. */
.section > h2,
.final-texte > h2 { position: relative; }

/* Ces trois-là vivent DANS un bloc qui porte déjà le retrait du rail : leur
   boîte de référence commence à la colonne de texte, pas à la gouttière.
   --retrait-local les ramène dans le couloir. Sans lui, mesuré à 1440 px, le
   repère du dernier titre tombait à 191 px — c'est-à-dire SUR le mot « Il »
   de « Il reste à fixer la date », et la ligne traversait la phrase. */
.etape-num,
.final-texte > h2,
.final-action { --retrait-local: var(--retrait-rail); }

/* ---------------------------------------------------------------- 3. Héros */

/* La phrase EST le formulaire. « De ___ à ___ » se lit d'un trait, et les
   deux champs sont les deux blancs de la phrase. Il n'y a donc ni panneau,
   ni étiquette visible, ni bouton de soumission : le seul geste possible est
   d'écrire, puis de partir. La phrase est un <p> — des <input> dans un h1
   demandent au lecteur d'écran une porte que ce chantier ne sait pas ouvrir
   (cahier §4.1 décision 5). Le h1, longtemps masqué ici, est VISIBLE depuis
   le 24.09 en tête de « Ce que nous faisons » (§4 bis) : le premier écran
   n'avait pas une ligne de libre pour lui (mesures dans index.html). */
.hero {
  position: relative;
  min-height: 100dvh;
  padding-bottom: 0;
  row-gap: var(--e24);
  grid-template-rows: auto auto 1fr;
  align-content: start;
}

.hero > * { position: relative; z-index: var(--z-contenu); padding-left: 0; }

.hero-saisie {
  grid-row: 1;
  padding-top: var(--e48);
  padding-left: var(--retrait-rail);
  container-type: inline-size;
}

/* L'unique --t-affiche de la page (fondations.md §2.2 : une par page).
   Approche à 0 : une condensée noire est déjà serrée, et le mot-symbole du
   logo mesure +0,034 em — on ne resserre pas en dessous de lui. */
.hero-phrase {
  position: relative;
  margin: 0;
  max-width: none;
  font-family: var(--police-titre);
  font-size: var(--t-affiche);
  font-weight: var(--graisse-noire);
  line-height: var(--interligne-affiche);
  letter-spacing: var(--approche-affiche);
  color: var(--texte);
}

/* Le plafond de 7 rem de --t-affiche suppose la pleine mesure. Ici la phrase
   partage la ligne avec un champ, et au-delà de 1080 px elle n'a que 54 % de
   la colonne : mesuré au navigateur, « à 1227 Carouge » réclame 6,65 em de
   large, et à 112 px l'indication était COUPÉE aux deux largeurs de contrôle.
   La ligne ne peut donc pas dépasser largeur ÷ 6,65, soit 15,04 % — on prend
   14,6 % pour la marge. `cqi` mesure la boîte de contenu de .hero-saisie,
   c'est-à-dire exactement la ligne : plus aucun point de rupture à tenir à la
   main, et aucune largeur où la phrase déborde.
   Le @supports n'est pas une précaution de style : sans lui, un navigateur
   sans requête de conteneur invaliderait la déclaration entière. */
@supports (container-type: inline-size) {
  .hero-phrase { font-size: min(var(--t-affiche), 14.6cqi); }
}

/* Le fil qui relie la borne de départ à celle d'arrivée : deux points et un
   trait, la grammaire du trajet réduite à sa plus simple expression. Il est
   dans le couloir, jamais dans le texte. */
.hero-phrase::before {
  content: "";
  position: absolute;
  left: calc(var(--rail-in) - var(--retrait-rail));
  top: .62em;
  bottom: .62em;
  width: var(--trait-trajet);
  background: var(--trajet-restant);
}

.hero-ligne {
  position: relative;
  display: flex;
  align-items: baseline;
  gap: .28em;
}

.hero-ligne + .hero-ligne { margin-top: .12em; }

.hero-mot { flex: 0 0 auto; }

.hero-champ {
  flex: 1 1 auto;
  min-width: 0;
  display: block;
}

/* Le champ n'a ni fond, ni cadre, ni coin : il a un filet sous la ligne, qui
   est ce qu'un blanc à remplir a toujours été. La valeur saisie reprend la
   graisse du titre, donc la phrase ne change pas de voix quand on la
   complète. L'indication est --champ-indication (5,97:1 sur le fond) : une
   indication pâle serait illisible à cette échelle comme aux autres. */
.hero-champ input {
  width: 100%;
  min-width: 0;
  padding: 0 0 .06em;
  font-family: inherit;
  font-size: inherit;
  font-weight: inherit;
  line-height: inherit;
  letter-spacing: inherit;
  color: var(--champ-texte);
  background: none;
  border: 0;
  border-bottom: var(--trait-trajet) solid var(--champ-filet);
  border-radius: var(--rayon);
  box-shadow: var(--ombre);
  transition: border-color var(--duree-instant) var(--entree);
}

.hero-champ input::placeholder {
  color: var(--champ-indication);
  opacity: 1;
}

.hero-champ input:hover { border-bottom-color: var(--texte-secondaire); }
.hero-champ input:focus { border-bottom-color: var(--champ-filet-actif); }

/* `box-shadow: var(--ombre)` ci-dessus est le garde-fou contre l'ombre interne
   que certains navigateurs posent d'office sur un <input>. Mais sa
   spécificité (0,1,1) écrase le CONTRE-ANNEAU du `:focus-visible` (0,1,0) de
   marque/tokens.css : relevé au navigateur, l'anneau noir était là et son
   contre-anneau valait `none`. On le rend, au seul état où il sert. */
.hero-champ input:focus-visible {
  box-shadow: 0 0 0 calc(var(--focus-epaisseur) + var(--focus-decalage))
              var(--focus-contre);
}

/* Les deux bornes du héros. Elles n'entrent PAS dans le tracé calculé : elles
   n'ont aucun attribut de données, et script.js ne lit que [data-etape],
   [data-depart] et [data-arrivee]. */
.borne {
  --retrait-local: var(--retrait-rail);
  width: var(--trajet-depart-taille);
  height: var(--trajet-depart-taille);
  margin: calc(var(--trajet-depart-taille) / -2) 0 0 calc(var(--trajet-depart-taille) / -2);
  border-radius: var(--rayon-rond);
}

.borne-depart { background: var(--marque); }

.borne-arrivee {
  width: var(--trajet-arrivee-taille);
  height: var(--trajet-arrivee-taille);
  margin: calc(var(--trajet-arrivee-taille) / -2) 0 0 calc(var(--trajet-arrivee-taille) / -2);
  background: var(--fond);
  border: var(--trajet-arrivee-trait) solid var(--marque);
}

.hero-aide {
  margin: var(--e16) 0 0;
  max-width: var(--mesure-etroite);
}

.hero-chapeau {
  margin: var(--e24) 0 0;
  max-width: var(--mesure-etroite);
  font-size: var(--t-texte-large);
  line-height: 1.45;
  color: var(--texte-secondaire);
}

.hero-bouton { margin-top: var(--e32); }

.hero-micro { margin-top: var(--e16); }

/* Le point A du trajet de page. Il est posé dans le couloir, au bas du
   héros : la ligne part de là et descend. Son dégagement de 10 px est tenu
   par le vide de la colonne de gauche, où rien d'autre ne vit.
   Seul `top` est repris : `left` garde la formule commune de .ancre, sans
   quoi le point A se décalerait du couloir où courent les quatre repères.
   La gouttière n'est PAS var(--gouttiere-g) à l'écran : c'est un
   minmax(gouttière, 1fr), et le 1fr grandit dès que la page dépasse
   --largeur-max. Mesuré à 1440 px : 100 px, et non 86,4. */
.ancre-relais {
  top: auto;
  bottom: var(--e64);
}

.hero-photo {
  grid-row: 2;
  grid-column: pleine-d / pleine-f;
  align-self: end;
  z-index: var(--z-decor);
}

.hero-photo img {
  display: block;
  width: 100%;
  height: 46vh;
  min-height: 280px;
  object-fit: cover;
  object-position: 50% 45%;
}

@media (min-width: 1080px) {
  .hero {
    grid-template-columns:
      [pleine-d] minmax(var(--gouttiere-g), 1fr)
      [texte-d] minmax(0, calc(var(--largeur-max) * .54))
      [texte-f photo-d] minmax(0, calc(var(--largeur-max) * .46))
      [texte2-f] minmax(var(--gouttiere-d), 1fr)
      [pleine-f];
    grid-template-rows: 1fr;
    align-content: stretch;
  }

  /* Aucun column-gap : une gouttière de grille décalerait la boîte du texte,
     donc le couloir du héros, donc le point A — et la ligne d'itinéraire
     entrerait dans la page 48 px à droite de ses quatre repères. L'air entre
     le texte et la photo est un padding, qui ne déplace pas la boîte. */
  .hero-saisie {
    grid-row: 1;
    grid-column: texte-d / texte-f;
    align-self: center;
    padding-top: var(--e64);
    padding-bottom: var(--e96);
    padding-right: var(--e32);
  }

  .hero-photo {
    grid-row: 1;
    grid-column: photo-d / pleine-f;
    align-self: stretch;
  }

  .hero-photo img { height: 100%; min-height: 0; }

  .ancre-relais { bottom: var(--e96); }
}

/* ------------------------------------ 4 bis. Le titre de la page (24.09) --
   Carnet SEO pièce 06. Le h1 et sa réponse directe ouvrent la première
   section, au-dessus de « Ce que nous faisons ». Le h1 est une SURLIGNE :
   famille de texte, 700, au corps d'un titre de bloc (--t-h3), comme les
   titres de la famille de texte (fondations.md §2.3). La condensée noire
   au corps --t-h1 aurait mis un second titre d'affiche entre le « De … à … »
   du héros et le h2 de la section. La réponse directe est le chapeau
   d'entrée de la page (.spm-chapeau, marque/base.css) : --t-texte-large,
   --texte-secondaire. Elle prend la mesure pleine (68ch) et non l'étroite :
   à 46ch ses 55 mots tenaient sur sept lignes à 1440. */
.accueil-titre {
  font-family: var(--police-texte);
  font-weight: var(--graisse-gras);
  font-size: var(--t-h3);
  line-height: var(--interligne-h3);
  letter-spacing: -.005em;
  color: var(--texte);
  margin: 0 0 var(--e12);
}

.reponse-directe {
  max-width: var(--mesure);
  margin-bottom: var(--e48);
}

@media (min-width: 1080px) {
  .reponse-directe { margin-bottom: var(--e64); }
}

/* ------------------------------------------------- 4. Ce que nous faisons */

/* Deux colonnes de quatre, la photo sur la troisième, au-delà de 900 px.
   `display: contents` sur le <ul> fait des huit <li> des enfants DIRECTS de
   la grille : sans lui, le <ul> est la seule case et les colonnes ne disent
   rien. Le `role="list"` du balisage est ce qui garde la sémantique de liste
   malgré `display: contents` — il n'est donc pas redondant, il est
   obligatoire.
   Colonne 1 : les quatre déménagements. Colonne 2 : les quatre prestations
   d'appoint. La grille dit donc quelque chose, au lieu de ranger huit blocs
   dans l'ordre d'arrivée. */
.prestations-corps { margin-top: var(--e32); }

/* La liste garde sa sémantique et perd ses puces : le marqueur d'un <li>
   survit à `display: contents`, et huit points gris devant huit titres ne
   disent rien. */
.prestations-liste { margin: 0; padding-left: 0; list-style: none; }

/* HUIT BLOCS AU FILET, PAS HUIT BOÎTES. Le balisage garde `spm-carte
   spm-carte--nue` — c'est la classe que le cahier compte (§7, L1, contrôle 3)
   et ce qui rend ces huit <li> comparables entre eux. Mais le rendu de la
   carte cernée ne tient pas ici : huit cadres gris sur deux colonnes, sur
   toute la hauteur de la section, sans une seule couleur, et la section la
   plus générique du site. Le livret construit ses blocs AU FILET et à
   l'aplat, pas à la boîte encadrée (langage-graphique.md §5.2 : « Le
   séparateur reste un filet ») — c'est aussi la forme de ses tableaux.
   Le cadre part, le filet haut reste, et les huit blocs se lisent comme les
   huit lignes qu'ils sont. Aucun des huit ne passe en aplat rouge : la page
   n'a droit qu'à UN grand aplat, et c'est « Il reste à fixer la date »
   (§1.5). */
.prestation {
  list-style: none;
  max-width: none;
  padding: var(--e16) 0 0;
  background-color: transparent;
  border: 0;
  border-top: var(--filet-epaisseur) solid var(--neutre-200);
  border-radius: 0;
}

.prestation + .prestation { margin-top: var(--gouttiere); }
.prestation .spm-carte__corps { color: var(--texte-secondaire); max-width: none; }

/* Les huit titres sont des liens (24.09, carnet SEO pièce 09). Ils gardent
   l'encre du titre : le rouge de texte ne va « jamais à un intertitre »
   (fondations.md §1.5), et huit titres rouges feraient de cette section la
   plus rouge de la page. Le lien se dit par son soulignement, gris
   au repos (--filet-fort, 3,88:1) et rouge au survol et au focus — la
   grammaire des libellés modifiables du récapitulatif (base.css). */
.prestation .spm-carte__titre a {
  color: inherit;
  text-decoration-color: var(--filet-fort);
  text-underline-offset: .22em;
}

.prestation .spm-carte__titre a:hover,
.prestation .spm-carte__titre a:focus-visible {
  text-decoration-color: var(--marque);
}

/* Les quatre photographies de la page sont enveloppées d'un <picture>, pour
   proposer le WebP avant le JPEG (21.09). Or `picture` est EN LIGNE par
   défaut : laissé tel quel il ouvre une ligne de texte, et l'espace sous la
   ligne de base décolle la photo du bas de son cadre. Il prend donc le même
   `display: block` que l'`img` qu'il enveloppe. Une seule ligne, sur la classe
   partagée par les quatre figures — `.hero-photo picture` (§8.3) lui ajoute en
   plus une hauteur, et n'est pas remplacée par celle-ci. */
.spm-photo picture { display: block; }

.prestations-photo { margin: var(--e32) 0 0 auto; width: 78%; }

.prestations-photo img {
  display: block;
  width: 100%;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  /* La planche est déjà composée pour cette colonne (recadrée 2/3 autour des
     deux porteurs). Le réglage vertical ne sert qu'au-delà de 900 px, où la
     boîte s'étire à la hauteur de la liste : 40 % garde les deux visages et
     sacrifie les chaussures. */
  object-position: 50% 40%;
}

@media (min-width: 900px) {
  /* Deux colonnes de quatre, la photo sur la troisième.
     `display: contents` sur le <ul> fait des huit <li> des enfants DIRECTS de
     la grille : sans lui, le <ul> est la seule case et les colonnes ne disent
     rien. Le `role="list"` du balisage est ce qui garde la sémantique de
     liste malgré `display: contents` — il n'est donc pas redondant.
     ⚠️ Les rangées et les colonnes sont posées EXPLICITEMENT, une par carte.
     Le cahier §4.2 se contente de `nth-child(-n+4) { grid-column: 1 }` et
     laisse le placement automatique trouver la rangée : mesuré au navigateur,
     le curseur de placement ne revient jamais en arrière, la cinquième carte
     tombe donc en rangée 4 et la section s'étire sur SEPT rangées avec deux
     trous de 400 px. Et `grid-row: 1 / -1` sur la photo ne couvre qu'UNE
     rangée tant que la grille explicite n'en déclare aucune.
     Colonne 1 : les quatre déménagements. Colonne 2 : les quatre prestations
     d'appoint. La grille dit donc quelque chose, au lieu de ranger huit blocs
     dans l'ordre d'arrivée. */
  .section-prestations .prestations-corps {
    display: grid;
    grid-template-columns: 1fr 1fr minmax(0, .8fr);
    grid-template-rows: repeat(4, auto);
    gap: var(--gouttiere);
    align-content: start;
    margin-top: var(--e48);
  }

  .prestations-liste { display: contents; }
  .prestation + .prestation { margin-top: 0; }

  .prestation:nth-child(1) { grid-area: 1 / 1; }
  .prestation:nth-child(2) { grid-area: 2 / 1; }
  .prestation:nth-child(3) { grid-area: 3 / 1; }
  .prestation:nth-child(4) { grid-area: 4 / 1; }
  .prestation:nth-child(5) { grid-area: 1 / 2; }
  .prestation:nth-child(6) { grid-area: 2 / 2; }
  .prestation:nth-child(7) { grid-area: 3 / 2; }
  .prestation:nth-child(8) { grid-area: 4 / 2; }

  /* La photo prend la hauteur de la grille, elle ne la dicte pas : une
     verticale 1024 × 1536 dans une colonne de 270 px réclamerait 405 px
     d'intrinsèque et tirerait les quatre rangées avec elle. L'image sort donc
     du flux, et la boîte s'étire sur les quatre rangées. */
  .prestations-photo {
    position: relative;
    grid-area: 1 / 3 / 5 / 4;
    width: auto;
    margin: 0;
    align-self: stretch;
    min-height: 320px;
  }

  .prestations-photo img {
    position: absolute;
    inset: 0;
    height: 100%;
    aspect-ratio: auto;
  }
}

/* ------------------------------------------------- 5. Comment ça se passe */

/* padding-left reprend --retrait-rail, il ne le met pas à zéro : la liste est
   un enfant DIRECT de .bande, et c'est ce retrait qui la tient à droite du
   couloir. Mesuré à 1440 px avec `padding-left: 0` : le texte commençait à
   100 px et la ligne d'itinéraire, qui court à 116 px, le traversait. */
.etapes-liste {
  display: grid;
  gap: var(--e48);
  margin: var(--e32) 0 0;
  padding-left: var(--retrait-rail);
  list-style: none;
}

.etapes-liste > li + li { margin-top: 0; }

.etape { position: relative; max-width: none; }

/* Le chiffre garde .chiffre, donc la DIN, et il a perdu son data-etape : le
   trajet ne compte plus que quatre arrêts, un par titre de section. */
.etape-num {
  position: relative;
  margin: 0 0 var(--e12);
  font-size: var(--t-chiffre);
  line-height: 1;
  color: var(--marque);
}

.etape h3 { margin-bottom: var(--e8); }

/* La règle de couleur du texte courant ne vise que le paragraphe de contenu,
   qui est le seul <p> à suivre un <h3> : écrite « .etape p », de spécificité
   (0,1,1), elle écraserait « .etape-num » (0,1,0) déclaré plus haut. */
.etape > h3 + p { color: var(--texte-secondaire); }

.etapes-photo { margin: var(--e48) 0 0 auto; width: 78%; }

.etapes-photo img {
  display: block;
  width: 100%;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  object-position: 50% 40%;
}

/* La seule vue plongeante de la page : le rappel du point de vue du logo, un
   carton vu d'en haut. Elle porte la seconde et dernière coupe en rabat, au
   même coin que celle du héros — un seul coin pour toute la page. */
.photo-large { margin-top: var(--e64); }

.photo-large img {
  display: block;
  width: 100%;
  height: 38vh;
  min-height: 220px;
  object-fit: cover;
  /* Le bandeau ne montre que 35 % de la hauteur de la planche : le camion,
     lui, en occupe 30 % (toit à 37,5 %, contact des roues à 67,5 %). 54 %
     est le centre de la fourchette [49,5 % ; 58,3 %] qui le garde ENTIER,
     roues comprises, à 1440 × 900 comme à 1920 × 1080. */
  object-position: 50% 54%;
}

@media (min-width: 900px) {
  .section-etapes {
    grid-template-columns:
      [pleine-d] minmax(var(--gouttiere-g), 1fr)
      [texte-d] minmax(0, calc(var(--largeur-max) * .62))
      [texte-f photo-d] minmax(0, calc(var(--largeur-max) * .38))
      [texte2-f] minmax(var(--gouttiere-d), 1fr)
      [pleine-f];
  }

  .section-etapes > h2 { grid-column: texte-d / texte2-f; grid-row: 1; }

  .etapes-liste {
    grid-column: texte-d / texte-f;
    grid-row: 2;
    grid-template-columns: 1fr 1fr;
    gap: var(--e48) var(--gouttiere);
    align-content: start;
    margin-top: var(--e48);
  }

  /* Même raison qu'aux prestations : l'image sort du flux pour que la rangée
     soit dictée par les quatre temps, et non par les 645 px d'intrinsèque
     d'une verticale de 1024 × 1536. */
  .etapes-photo {
    position: relative;
    grid-column: photo-d / texte2-f;
    grid-row: 2;
    width: auto;
    margin: var(--e48) 0 0 var(--gouttiere);
    padding-left: 0;
    min-height: 360px;
  }

  .etapes-photo img {
    position: absolute;
    inset: 0;
    height: 100%;
    aspect-ratio: auto;
  }

  .photo-large { grid-row: 3; margin-top: var(--e96); }
  .photo-large img { height: 56vh; }
}

/* --------------------------------------- 6. Il reste à fixer la date */

/* L'unique grand aplat rouge de la page, et l'unique pli. Le pli descend vers
   la droite, course min(largeur ÷ 6, 240px), montée = course ÷ 2 : la classe
   .pli de marque/tokens.css le fait, on ne la recopie pas.
   Le bloc porte trois choses — un titre, une phrase, une action — plus deux
   lignes de contact, qui sont deux <li> courts et non un quatrième objet. */
.section-final {
  display: grid;
  grid-template-columns:
    [pleine-d] minmax(var(--gouttiere-g), 1fr)
    [texte-d] minmax(0, var(--largeur-max))
    [texte-f] minmax(var(--gouttiere-d), 1fr)
    [pleine-f];
}

.final-texte {
  grid-column: texte-d / texte-f;
  padding-left: var(--retrait-rail);
}

.final-texte > h2 { max-width: var(--mesure-etroite); }

.final-texte p { margin-top: var(--e16); }

.final-texte .final-action {
  position: relative;
  margin-top: var(--e32);
  max-width: none;
}

.contact { margin: var(--e32) 0 0; }

.contact li { font-size: var(--t-texte); }
.contact li + li { margin-top: var(--e8); }

/* Sur l'aplat, la mention de marque garderait --texte-faible : un gris à
   1,9:1 sur le rouge, illisible. Elle reprend la couleur du bloc.
   ⚠️ SIGNALÉ au design system : components.css §10 remet h1, h2, h3 et p en
   `inherit` dans .spm-bloc-marque, mais pas .spm-mention. La correction vit
   en amont ; ici elle n'est que locale, et elle ne sert qu'à cette page. */
.spm-bloc-marque .spm-mention { color: inherit; }

/* Le seul bouton du site qui vit SUR l'aplat de marque. components.css ne le
   porte pas : le paquet n'a pas de variante « sur aplat », et l'inventer
   là-bas obligerait à la contrôler sur les 29 cartes. Blanc plein, texte
   --marque-texte : 6,52:1 sur le blanc, et la plaque blanche mesure 4,87:1
   contre l'aplat. Aucune autre page ne l'emploie. */
/* ⚠️ Le sélecteur porte DEUX classes, et ce n'est pas un ornement :
   components.css §10 écrit `.spm-bloc-marque a { color: var(--sur-marque) }`,
   de spécificité (0,2,0). Un `.btn-sur-marque` simple (0,1,0) perdait, et le
   libellé du bouton s'affichait en BLANC SUR BLANC — relevé à la capture, pas
   déduit. Le bouton était là, cliquable, et vide. */
.spm-bloc-marque .btn-sur-marque {
  background-color: var(--sur-marque);
  color: var(--marque-texte);
  border-color: transparent;
}

.spm-bloc-marque .btn-sur-marque:hover  { background-color: var(--marque-voile); }
.spm-bloc-marque .btn-sur-marque:active { background-color: var(--rouge-100); }

@media (min-width: 900px) {
  .final-texte { padding-right: var(--e48); }
  .contact { display: flex; flex-wrap: wrap; gap: var(--e16) var(--e48); }
  .contact li + li { margin-top: 0; }
}

/* ----------------------------------------------------- 7. Mouvement réduit */

/* Le repli commun vient de marque/tokens.css §4. Ici, celui de la signature :
   ligne entièrement tracée, repères et tirets posés, anneau fermé, aucune
   boucle d'animation. On supprime le mouvement, pas l'information. */

@media (prefers-reduced-motion: reduce) {
  .trace-js .trace-lisere,
  .trace-js .trace,
  .trace-js .trace-contre { stroke-dasharray: none; stroke-dashoffset: 0; }

  .trace-js .marqueur,
  .trace-js .tiret,
  .trace-js .disque {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .trace-js .anneau {
    stroke-dasharray: none;
    stroke-dashoffset: 0;
    transition: none;
  }

  .hero-champ input { transition: none; }
}

/* =========================================================================
   8. LE PREMIER ÉCRAN — « LA ROUTE EST LA LIGNE »
   L'accueil, et lui seul.

   DÉCISION CLIENT du 20.09 21h19, mot pour mot, devant la variante HC :
   « Je préfère celui-ci mais il faut mieux cadrer la version mobile ».
   La variante retenue remplace donc l'ancien §8 « LE TITRE SUR ROUGE », et
   l'aplat rouge du héros est RETIRÉ : le premier écran est UNE photographie à
   fond perdu, et la surface qui porte le titre-formulaire est un objet réel,
   la chaussée genevoise, sur laquelle roule le camion de l'entreprise.

   Ce que l'image apporte, et que le CSS ne sait pas faire :
     · la ligne blanche peinte au sol, qui court à plat puis s'infléchit une
       fois vers le camion : le motif d'itinéraire de la marque, en vrai ;
     · le rouge, qui n'est plus un aplat posé mais trois choses qui existent
       ou qui agissent — le bandeau bas de caisse du camion, la borne de
       départ, le bouton ;
     · Genève nommée sans légende : la rade, le Jet d'eau, la balustrade du
       quai, et le patron au volant.

   CE QUI A CHANGÉ LE 20.09 AU SOIR, et pourquoi (retour de Jhonno) :
   sur téléphone le camion était coupé au bas de l'écran — on n'en voyait que
   le toit. Trois causes, toutes corrigées ici :
     1. la planche téléphone servie était rognée à 1100 × 1402, c'est-à-dire
        aux 85 % HAUTS d'une photo qui en mesure 1650 : le camion, qui vit
        entre 49 % et 85,5 % de la hauteur, perdait ses roues AVANT même
        d'arriver au navigateur. La série est refaite sur la planche entière ;
     2. la boîte du héros dépassait le pli de 24 vh, volontairement, pour
        gagner de l'asphalte sous le bloc de texte ; elle tient désormais
        DANS le premier écran ;
     3. les deux dernières phrases du bloc, qui réclamaient cette place, sont
        descendues sous la photographie, sur le fond de page (§8.5).

   Ce qui ne bouge pas : la copie, le balisage du formulaire et ses
   identifiants, script.js, l'en-tête, l'itinéraire de page. Aucun jeton de
   couleur n'est déclaré ici : le voile se compose à partir de --voile-photo,
   qui existe déjà dans marque/tokens.css. Aucune requête externe, aucune
   police ajoutée, aucun script.

   ⚠️ CE QUI A ÉTÉ RETIRÉ. `.hero-aplat` n'existe plus dans le balisage, donc
   l'aplat, sa hauteur et sa jonction en rabat avec la photo n'ont plus
   d'objet et ne sont pas repris. La classe `spm-bloc-marque` qu'il portait
   servait de cible de découpe au contre-tracé BLANC de l'itinéraire ; il n'y
   a plus de rouge sur rouge à compenser dans le héros, le contre-tracé reste
   borné au bloc de marque du bas de page, et script.js n'est pas modifié
   d'une ligne.
   ========================================================================= */

/* --- 8.1 La seule longueur que le CSS ne sait pas lire --------------------
   La hauteur de l'en-tête (frise + bande blanche). Elle ne se déduit pas :
   elle dépend de ce qui tient sur une ligne. RELEVÉE AU NAVIGATEUR le 20.09
   sur cette page, par balayage de 320 à 1920 px (bas de la boîte de
   `.spm-entete`, frise comprise) :

       320 → 367 px : 167,7 px   ⚠️ le logo et « Estimer » passent à la ligne
       368 → 599 px : 106,5 px
       600 → 699 px : 250,6 px   (la navigation apparaît, sur deux lignes)
       700 → 1022 px : 200,4 px
      1023 → 1199 px : 122,5 px
      1200 → 1920 px : 126,4 → 137,3 px (croît avec la largeur)

   Chaque palier prend la valeur HAUTE de sa plage : une valeur trop grande
   raccourcit le héros de quelques pixels — on voit alors poindre la section
   suivante, ce qui ne coûte rien ; une valeur trop petite pousse le bouton
   SOUS le pli, ce qui coûte la conversion. On se trompe donc du bon côté.

   ⚠️ RÉSERVE INSCRITE : sous 368 px, l'en-tête mesure 61 px de PLUS que sur
   un téléphone courant, parce que le logo et le lien « Estimer » ne tiennent
   plus sur une ligne. Ce n'est pas un défaut du héros, c'est l'en-tête ; il
   vit dans base.css, partagé par les quatre pages, hors du périmètre de ce
   chantier. Le héros s'y adapte (§8.4 ter) au lieu de le corriger. */
.hero {
  --entete-h: 106.5px;
  /* Ce qui, sur téléphone, est réservé SOUS la photographie pour les deux
     dernières phrases (§8.5). Nul partout ailleurs. */
  --queue-h: 0px;
  --hero-h: calc(100dvh - var(--entete-h));
}

@media (max-width: 367px)                          { .hero { --entete-h: 168px;   } }
@media (min-width: 600px) and (max-width: 699px)   { .hero { --entete-h: 251px;   } }
@media (min-width: 700px) and (max-width: 1022px)  { .hero { --entete-h: 201px;   } }
@media (min-width: 1023px) and (max-width: 1199px) { .hero { --entete-h: 122.5px; } }
@media (min-width: 1200px)                         { .hero { --entete-h: 137.2px; } }

/* --- 8.2 La boîte du premier écran --------------------------------------- */

.hero {
  /* Une seule rangée : l'image et le formulaire occupent la MÊME case. Le
     formulaire n'est plus à côté de l'image, il est DEDANS. */
  grid-template-rows: 1fr;
  align-content: stretch;
  row-gap: 0;
  min-height: var(--hero-h);
  /* L'empilement du héros se règle dans le héros. */
  isolation: isolate;
}

/* --- 8.3 L'image, à fond perdu ------------------------------------------- */

/* La figure n'est PAS une case de la grille : elle est une COUCHE, posée en
   absolu. C'est ce qui la rend à fond perdu sans discuter, et c'est surtout
   ce qui lui donne une hauteur DÉFINIE — donc ce qui permet à `height: 100%`
   de se résoudre sur l'image.
   ⚠️ Mesuré, et non supposé : avec la figure en case de grille étirée
   (`align-self: stretch` dans une rangée `1fr`), la hauteur restait
   indéfinie, `height: 100%` ne résolvait rien, et l'image se posait à sa
   hauteur INTRINSÈQUE — 961 px relevés à 1440 × 900 au lieu des 762,8 px du
   premier écran, ce qui poussait le bouton 121 px sous le pli.

   ⚠️ La hauteur n'est plus `inset: 0` mais une hauteur ÉCRITE. Deux raisons :
   le héros peut grandir au-delà de --hero-h (texte plus gros, écran très
   court) et la photo, elle, ne doit pas suivre ; et sur téléphone elle doit
   s'arrêter AU-DESSUS de la queue de texte (§8.5). */
.hero-photo {
  position: absolute;
  top: 0;
  bottom: auto;
  left: 0;
  right: 0;
  height: calc(var(--hero-h) - var(--queue-h));
  /* ⚠️ Le placement de grille N'EST PAS décoratif ici, et il ne peut pas être
     omis : pour un enfant ABSOLU d'un conteneur de grille, le bloc conteneur
     n'est pas la boîte de remplissage du parent, c'est SA CASE de grille.
     Relevé au navigateur : sans ces deux lignes, la figure héritait de la
     case posée au §3 (`photo-d / pleine-f`, rangée 2) et se bornait à la
     moitié droite sur ordinateur. On lui donne donc la case pleine. */
  grid-row: 1;
  grid-column: pleine-d / pleine-f;
  /* Et pour la même raison, l'alignement de la case doit être remis à plat :
     le §3 pose `align-self: end` sur cette figure. Sur un enfant absolu d'une
     grille, Chrome l'applique quand même — relevé : hauteur 478 px (la
     hauteur intrinsèque de l'image du téléphone) calée sur le bas de la case
     au lieu de la hauteur du premier écran. */
  align-self: stretch;
  justify-self: stretch;
  margin: 0;
  z-index: var(--z-decor);
  padding-left: 0;
  /* Le fond rouge de la <figure> servait à montrer le rouge dans l'encoche de
     la coupe en rabat. Il n'y a plus ni encoche ni aplat : la figure ne peint
     plus rien, l'image la remplit. */
  background-color: transparent;
}

.hero-photo picture { display: block; height: 100%; }

.hero-photo img {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 0;
  object-fit: cover;
  /* TÉLÉPHONE ET TABLETTE — le cadrage refait le 20.09 au soir.
     La planche garde le rapport 2:3 de sa source (1280 × 1920 depuis le
     réexport du 21.09, 1100 × 1650 auparavant : les proportions ci-dessous
     n'en dépendent pas, elles sont en POUR-CENT de la planche et c'est
     pourquoi elles survivent à un changement de définition).
     Relevé sur les pixels : le camion occupe la
     largeur de 28,6 % à 94,7 % et la hauteur de 49,2 % (le toit) à 85,5 %
     (le contact des roues). La boîte du héros est plus ÉTROITE que la photo
     une fois la hauteur couverte : c'est donc la largeur qui est rognée, et
     `96 %` cale le camion à une vingtaine de pixels du bord droit à 360, 375
     et 390 px, en sacrifiant l'asphalte de GAUCHE, qui ne porte rien.
     `0 %` en vertical : partout où la hauteur déborde aussi (360 px, et la
     tablette), ce qui part est le BAS de l'image — l'asphalte sous les roues,
     jamais la rade ni le camion. */
  object-position: 96% 0%;
}

/* --- 8.3 bis Le voile, et pourquoi il existe ------------------------------
   MESURÉ, pas supposé. Sur la photo d'ordinateur, l'asphalte du bas-gauche
   monte par endroits à un gris moyen de 140 à 149 : un blanc plein y vaut
   3,0 à 4,1:1, c'est-à-dire SOUS le seuil de 4,5:1 pour le texte courant.
   La chaussée du téléphone, elle, est plus sombre et n'a presque besoin de
   rien.

   Le voile n'a aucun bord, et il est construit comme une ombre de fin
   d'après-midi qui vient du bas et de la gauche — c'est-à-dire du côté où les
   platanes du quai portent déjà leur ombre sur la chaussée dans la photo
   elle-même. Il s'éteint avant le camion. Sa couleur n'est pas écrite ici :
   c'est --voile-photo, le jeton que la marque utilise déjà sous le tracé sur
   photo, dont on module la force par l'opacité du calque. */
.hero-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  /* Téléphone et tablette : la chaussée y est déjà sombre. Il ne reste qu'un
     souffle, ancré au bord gauche et éteint avant le camion. */
  background:
    linear-gradient(to right, var(--voile-photo) 0%,
                              var(--voile-photo) 30%, transparent 78%);
  opacity: .36;
}

/* --- 8.4 Le titre-formulaire, DANS l'image ------------------------------- */

.hero-saisie {
  grid-row: 1;
  grid-column: texte-d / texte-f;
  z-index: var(--z-contenu);
  align-self: start;
  /* Le haut du bloc tombe sur l'asphalte, sous la bande de rade et le
     trottoir : la chaussée commence à 15,8 % de la hauteur de la photo. */
  padding-top: calc(var(--hero-h) * .185);
  padding-bottom: var(--e32);
}

/* --- 8.4 bis Téléphone et tablette : le rythme se resserre ---------------
   LE COMPTE, MESURÉ ET NON SUPPOSÉ. La planche montre la chaussée libre
   entre 18 % et 49,2 % de sa hauteur : la bande d'asphalte vaut donc 31,2 %
   de la photo, et pas un pixel de plus. Relevé au navigateur, une ligne de
   titre occupe 2,78 fois son corps (le champ de saisie, qui partage la
   ligne, est plus haut que le texte) ; l'indication coûte 29 px avec sa
   marge, le bouton 57 px avec la sienne. Il reste donc, pour deux lignes de
   titre : bande − 86 px.

       375 × 812 : bande 206 px → 111 px de titre → 39,9 px de corps
       390 × 844 : bande 216 px → 121 px de titre → 41,8 px de corps
       360 × 740 : bande 167 px →  77 px de titre → 27,4 px de corps (§8.5 bis)

   12,5 cqi donne exactement ces valeurs sur téléphone et se fait plafonner
   par --t-affiche dès la tablette, où §8.8 reprend la main. */
@media (max-width: 1079px) {
  @supports (container-type: inline-size) {
    .hero-phrase { font-size: min(var(--t-affiche), 12.5cqi); }
  }

  .hero-aide    { margin-top: var(--e12); }
  .hero-chapeau { margin-top: var(--e16); line-height: 1.35; }
  .hero-bouton  { margin-top: var(--e16); }
  .hero-micro   { margin-top: var(--e12); }
}

/* --- 8.5 TÉLÉPHONE : la composition entière tient dans le premier écran ---
   C'est la demande du 20.09 au soir. Ce que l'écran doit montrer SANS
   défiler : la rade en haut, le titre-formulaire et le bouton sur la bande
   d'asphalte, et SOUS eux le camion entier avec le patron au volant.

   Le compte est serré et il se fait à la main. À 375 × 812, la boîte mesure
   705,5 px ; la photo y est calée sur la largeur, donc le toit du camion
   tombe à 49,2 % et la bande d'asphalte libre — du trottoir au toit — ne
   vaut qu'environ 230 px. Le titre, l'indication et le bouton la remplissent
   déjà. Les deux dernières phrases n'y tiennent pas.

   POURQUOI ELLES NE SONT PAS POSÉES SUR LA ROUTE, SOUS LE CAMION.
   Mesuré sur les pixels du recadrage réel (`object-fit: cover`, 96 % 0 %),
   dans la bande située sous les roues, pour du blanc plein :

       360 × 740, sur 58 % de la largeur : pire pixel 3,36:1
       375 × 812, sur 58 % de la largeur : pire pixel 2,99:1
       390 × 844, sur 58 % de la largeur : pire pixel 2,96:1
       les trois, sur toute la largeur   : pire pixel 1,10 à 1,13:1

   La cause est dans l'image : le marquage BLANC peint au sol traverse cette
   bande, et le feuillage y porte des reflets clairs. Aucun voile discret ne
   rattrape 1,1:1 ; il faudrait un panneau noir, c'est-à-dire tuer la photo.
   Les deux phrases descendent donc SOUS la photographie, sur le fond de
   page, où elles valent 9,09:1 sans rien demander à personne. La photo
   s'arrête à --queue-h du bas : le camion garde ses roues, et le pli tombe
   sous la dernière ligne.

   La mécanique, sans nombre magique : le formulaire devient une colonne
   flexible haute d'exactement le premier écran, les trois premiers éléments
   se rangent en haut, et `margin-top: auto` sur le chapeau colle les deux
   derniers au bas de cette colonne — c'est-à-dire exactement dans la bande
   que la photo a libérée.

   ⚠️ L'ORDRE VISUEL N'EST PAS L'ORDRE DU CODE, et c'est voulu : le bouton
   remonte devant les deux phrases. L'ordre de TABULATION ne bouge pas (les
   deux champs, puis le bouton : les phrases ne sont pas focalisables), et
   l'ordre de lecture d'un lecteur d'écran suit le code, où la phrase
   « Le canton de Genève… » précède toujours le bouton. */
@media (max-width: 767px) {
  .hero {
    /* ⚠️ --queue-h n'est PAS la hauteur de la queue de texte : c'est ce
       qu'on retire à la photo. La queue, elle, est collée au bas du héros et
       recouvre la photo sur sa propre hauteur (109 px relevés) ; elle ne
       laisse donc jamais de bande vide, quel que soit le corps du visiteur.
       40 px est le réglage qui, aux trois formats de contrôle, garde à la
       fois une bande d'asphalte utile EN HAUT et un dégagement sous les
       roues EN BAS :
           375 × 812 : bas du camion à 675, haut de la queue à 704 (+29)
           390 × 844 : bas du camion à 702, haut de la queue à 736 (+34)
           360 × 740 : bas du camion à 628, haut de la queue à 631 (+3) */
    --queue-h: 40px;
  }

  .hero-saisie {
    display: flex;
    flex-direction: column;
    min-height: var(--hero-h);
    /* 17,2 % : le haut de la boîte du titre tombe sur les premiers pixels
       d'asphalte (18 % de la photo), et pas sur le trottoir. Relevé sans ce
       réglage : le pire pixel sous la boîte du titre valait 2,72:1 à 375 et
       1,59:1 à 360, uniquement dans la PREMIÈRE ligne de la boîte, sur la
       bordure de trottoir. Une fois le bloc descendu, le pire pixel de
       chaque ligne suivante est au-dessus de 4,6:1. */
    padding-top: calc(var(--hero-h) * .172);
    padding-bottom: 0;
  }

  /* Le bouton garde sa cible de 45 px (seuil 44) et rend 8 px à la bande. */
  .hero-aide   { margin-top: var(--e8); }
  .hero-bouton { margin-top: var(--e12); padding-block: var(--e12); }

  .hero-phrase  { order: 1; }
  .hero-aide    { order: 2; }
  .hero-bouton  { order: 3; }
  .hero-chapeau { order: 4; }
  .hero-micro   { order: 5; }
  .hero-saisie > [data-trajet-dit] { order: 9; }

  /* La queue : deux phrases sur le fond de page, à fond perdu de bord à
     bord. Le fond est peint par les paragraphes eux-mêmes et non par une
     bande à part : si le texte grandit (réglage de corps du visiteur), il
     mange un peu de route au lieu de devenir illisible dessus.
     ⚠️ LE SÉLECTEUR PORTE DEUX CLASSES, ET CE N'EST PAS UN ORNEMENT. Le §8.6
     repeint `.hero-chapeau` et `.hero-micro` en --sur-marque (blanc), il est
     PLUS BAS dans la feuille, et une requête de média n'ajoute pas de
     spécificité : avec un sélecteur à une classe, ces deux phrases seraient
     blanches sur le fond blanc de la queue, c'est-à-dire absentes. */
  .hero-saisie .hero-chapeau,
  .hero-saisie .hero-micro {
    max-width: none;
    margin-inline: calc(-1 * var(--gouttiere-g)) calc(-1 * var(--gouttiere-d));
    padding-inline: var(--gouttiere-g) var(--gouttiere-d);
    background: var(--fond);
    color: var(--texte-secondaire);   /* 9,09:1 sur --fond */
  }

  .hero-saisie .hero-chapeau {
    margin-top: auto;
    padding-top: var(--e12);
    font-size: var(--t-petit);
    line-height: 1.3;
  }

  /* « treize ans d’escaliers » (fait du patron, 24.09 ; formulation du
     jury, 24.09) : la seule preuve chiffrée du site, au cœur du chapeau
     « Le patron a treize ans d’escaliers dans les jambes. ». Sur la queue
     blanche le passage en gras passe du gris secondaire au noir du texte
     (17,65:1) : on le lit avant le reste de la phrase, sans ajouter une
     ligne ni un élément au héros. À 390 px la phrase tient désormais sur
     UNE ligne (325 px pour 334) : la queue perd une ligne, la photo la
     reprend. */
  .hero-saisie .hero-chapeau .hero-preuve { color: var(--texte); }

  .hero-saisie .hero-micro {
    margin-top: 0;
    padding-top: var(--e8);
    padding-bottom: var(--e16);
  }
}

/* Au-dessus de 367 px, la bande d'asphalte passe de 167 à 206 px : le bloc
   descend franchement dans la chaussée (18,8 % au lieu de 17,2 %) et
   l'indication se colle aux champs. Relevé avant ce réglage : le pire pixel
   sous la boîte du titre valait 2,99:1 à 375 — la bordure de trottoir, dans
   la première ligne de la boîte. Après : 4,7:1. */
@media (min-width: 368px) and (max-width: 767px) {
  @supports (container-type: inline-size) {
    .hero-phrase { font-size: min(var(--t-affiche), 11.8cqi); }
  }

  .hero-saisie { padding-top: calc(var(--hero-h) * .188); }
  .hero-aide   { margin-top: 0; }
}

/* --- 8.5 bis Sous 368 px : l'en-tête prend 61 px, le titre les paie ------
   Voir la réserve du §8.1. À 360 × 740 l'en-tête mesure 167,8 px au lieu de
   106,5 : la boîte du héros tombe à 572 px, la photo est calée sur la
   LARGEUR (elle ne peut plus grandir), et la bande d'asphalte se fige à
   167 px — contre 206 px à 375 px de large, pour 15 px d'écart seulement.

   Il reste 81 px pour deux lignes de titre, soit 27,4 px de corps. C'est
   sous la fourchette demandée, et c'est assumé : l'alternative était de
   poser le bouton sur le toit BLANC du camion, ou de couper les roues.
   Aucune des deux n'est acceptable. Le jour où l'en-tête cessera de passer à
   la ligne sous 368 px, cette règle pourra disparaître telle quelle. */
@media (max-width: 367px) {
  @supports (container-type: inline-size) {
    .hero-phrase { font-size: min(var(--t-affiche), 9cqi); }
  }
}

/* --- 8.6 Ce qui se lit sur l'asphalte ------------------------------------ */

/* Blanc plein, comme sur l'aplat : un blanc affaibli sur une photographie
   est un blanc sale, et un gris sur du gris n'est plus du texte. */
.hero-phrase,
.hero-aide,
.hero-chapeau,
.hero-micro { color: var(--sur-marque); }

/* Le fil qui relie les deux bornes reste blanc — il rime avec la ligne
   peinte de la photo : même couleur, même rôle. On ne cherche PAS à les
   raccorder au pixel : la photo est rognée différemment à chaque largeur, et
   une jonction ratée se verrait. La rime suffit ; le raccord exact serait le
   collage qu'on fuit.
   La borne de DÉPART reprend le rouge de marque : c'est le point A du
   trajet, et c'est l'un des trois rouges du premier écran avec le bandeau du
   camion et le bouton. La borne d'arrivée reste un anneau blanc creux. */
.hero-phrase::before { background: var(--sur-marque); }
.borne-depart { background: var(--marque); }
.borne-arrivee {
  background: transparent;
  border-color: var(--sur-marque);
}

/* Les deux blancs de la phrase : un filet blanc sous la ligne, et rien
   d'autre. L'indication est en blanc plein — c'est du texte, et du texte sur
   l'asphalte est blanc. */
.hero-champ input {
  color: var(--sur-marque);
  border-bottom-color: var(--sur-marque);
  caret-color: var(--sur-marque);
}

.hero-champ input::placeholder { color: var(--sur-marque); }

/* Le survol ne peut plus assombrir le filet : il l'ÉPAISSIT, par box-shadow
   et non par border-width, pour ne pas déplacer la ligne de base au passage
   de la souris. */
.hero-champ input:hover,
.hero-champ input:focus {
  border-bottom-color: var(--sur-marque);
  box-shadow: 0 var(--trait-trajet) 0 var(--sur-marque);
}

/* Le focus garde l'anneau du socle : 2 px noirs doublés de 2 px blancs. Sur
   une photographie, l'un des deux tient toujours, quel que soit le pixel. */
.hero-champ input:focus-visible {
  box-shadow: 0 0 0 calc(var(--focus-epaisseur) + var(--focus-decalage))
              var(--focus-contre);
}

/* --- 8.7 Le bouton : la plaque rouge, cernée de blanc ---------------------
   Le rouge du premier écran vient de trois choses réelles : le bandeau du
   camion, la borne de départ, et ce bouton. Il reste donc la plaque ROUGE
   PLEINE de la forme primaire (blanc sur --marque : 4,87:1), et non la
   plaque blanche inversée de l'ancien aplat.
   Le filet blanc de 1 px n'est pas un ornement : sur l'asphalte, la plaque
   rouge ne se détache que de 1,5:1 environ, ce qui ne suffit pas à dire
   « ceci est un bouton » (WCAG 1.4.11). Le filet blanc, lui, se détache
   exactement autant que le texte qu'il entoure. `.spm-btn` porte déjà une
   bordure transparente : on ne fait que lui donner une couleur, rien ne
   bouge d'un pixel. */
.hero-bouton.spm-btn--primaire {
  background-color: var(--marque);
  color: var(--sur-marque);
  border-color: var(--sur-marque);
}

.hero-bouton.spm-btn--primaire:hover  { background-color: var(--rouge-700); }
.hero-bouton.spm-btn--primaire:active { background-color: var(--rouge-800); }

/* L'en-tête reste blanc, logo couleur. Son filet du bas s'en va : entre une
   bande blanche et une photographie, une ligne grise de 1 px n'est plus une
   limite, c'est un reste. */
.spm-entete { border-bottom-color: transparent; }

/* --- 8.8 Tablette : le patron doit être DANS le cadre --------------------
   Relevé le 20.09 au soir sur l'ancien cadrage à 768 × 1024 : on ne voyait
   que le toit du camion, et pas le conducteur. La cause était la même que
   sur téléphone (planche rognée à 1402 px, boîte débordant le pli de 24 vh).

   Ici la largeur suffit et c'est la HAUTEUR qui déborde : la photo est calée
   sur la largeur, `0 %` garde donc la rade et le Jet d'eau en haut, et ce
   qui passe sous le pli est l'asphalte du bas. Mesuré à 768 × 1024 :
   l'en-tête prend 200,4 px, la boîte 823 px, le toit du camion tombe à
   567 px et la tête du conducteur à 726 px — 97 px au-dessus du pli.
   Le bloc de texte ne change pas de forme — pas de queue ici, les CINQ
   éléments tiennent sur la chaussée — mais il descend, et le titre rend un
   cran. Mesuré à 768 × 1024 : la bande d'asphalte vaut 359 px, le bloc au
   plafond de --t-affiche (77,7 px de titre) en réclamait 394, et la mention
   du bas retombait sur le toit BLANC du camion (1,28:1 relevé). À 9,5 cqi
   le titre mesure 60 px, le bloc 345 px, et il reste 15 px de dégagement
   sous la mention. */
@media (min-width: 768px) and (max-width: 1079px) {
  @supports (container-type: inline-size) {
    .hero-phrase { font-size: min(var(--t-affiche), 9.5cqi); }
  }

  .hero-saisie { padding-top: calc(var(--hero-h) * .262); }
}

/* --- 8.9 Ordinateur : la moitié gauche, et le bas ------------------------ */

@media (min-width: 1080px) {
  /* Sur ordinateur la boîte tient EXACTEMENT le premier écran : le pli tombe
     sur le bord bas de la photo. Pas de queue ici — le cadrage large laisse
     assez d'asphalte pour tout le bloc. */

  /* Cadrage centré. Balayé au navigateur de 45 % à 78 % par pas de 4 sur
     sept formats (1920×1080, 1680×1050, 1440×900, 1366×768, 1280×800,
     1100×900, 1080×1024) : 50 % est la seule valeur qui garde la rade et le
     Jet d'eau visibles en haut À TOUTES ces hauteurs, et qui ne fait
     retomber le titre sur le marquage peint à AUCUNE. */
  .hero-photo img { object-position: 50% 50%; }

  /* Le bloc descend dans le bas-gauche, là où la chaussée est large et
     calme. Il n'est plus centré : une photographie a un sol, et le texte s'y
     pose. */
  .hero-saisie {
    align-self: end;
    padding-top: var(--e32);
    padding-bottom: calc(var(--hero-h) * .055);
    padding-right: var(--e32);
  }

  /* Le voile, uniquement ici (§8.3 bis). Deux nappes sans bord : l'une monte
     du sol, l'autre vient du bord gauche. Là où elles se croisent — sous le
     bloc de texte — elles valent ensemble un peu moins de 45 % ; sur le
     camion, à droite et en haut, elles ne valent plus rien.
     ⚠️ La nappe du bas monte à 78 % et l'ensemble passe de 0,76 à 0,84.
     Ce n'est pas un réglage d'humeur : le MARQUAGE BLANC peint au sol
     traverse la deuxième ligne du titre, et c'est lui, et lui seul, qui
     tenait le pire pixel à 2,93:1 sous la barre des 3:1. Les nappes ne
     changent ni de forme ni de direction ; elles gagnent le demi-ton qui
     manquait à cet endroit-là. */
  .hero-photo::after {
    background:
      linear-gradient(to top,   var(--voile-photo) 0%, transparent 78%),
      linear-gradient(to right, var(--voile-photo) 0%,
                                var(--voile-photo) 44%, transparent 74%);
    opacity: .84;
  }
}

/* --- 8.10 Le chapeau et les treize ans du patron (24.09) -----------------
   Le chapeau porte « Le patron a treize ans d’escaliers dans les jambes. »,
   « treize ans d’escaliers » en gras (fait du patron, donné le 24.09 ;
   formulation du jury du même jour, qui remplace « 13 ans de métier. »).
   La phrase mesure 464,5 px à 1440 (l'ancienne ~530) : la mesure étroite
   (512 px) cassait l'ancienne en deux lignes. Sur ordinateur le bloc
   est calé sur le BAS, et la ligne de plus faisait monter le titre de 29 px
   vers la bordure du trottoir ; sur tablette elle poussait le bouton de
   25 px. La colonne du titre-formulaire est plus large que 512 px (574 px
   relevés à 1440, 632 à 768) : le chapeau la suit, et le héros garde au
   pixel près la géométrie d'avant (relevé à 768, 1080, 1280, 1366, 1440 et
   1920). Sous 768 px la queue blanche règle déjà sa largeur (§8.5). */
@media (min-width: 768px) {
  .hero-chapeau { max-width: none; }
}

/* =========================================================================
   9. CORRECTIF MOBILE DU 21.09 (après-midi)
   Retour de Jhonno sur son iPhone, mot pour mot : « Les images prennent pas
   mal de temps à charger (tu peux rajouter un squelette), c'est pas assez
   visuel (beaucoup de texte et pas d'images) […] ça me donne le choix
   d'écrire sur l'accueil, mais pas vraiment. C'est impossible à modifier. »
   Les défauts sont numérotés comme dans
   captures/mobile-21-09/AUDIT-MOBILE.md. Aucune couleur écrite ici : les
   jetons seulement. Le bureau (≥ 900 px) ne change pas de forme.
   ========================================================================= */

/* --- 9.1 La hauteur du héros ne danse plus avec les barres de Safari (P2-10)
   `dvh` suit les barres d'iOS qui se replient au défilement : le héros se
   redimensionnait sous le doigt. `svh` est la petite fenêtre, stable. */
@supports (height: 100svh) {
  .hero { --hero-h: calc(100svh - var(--entete-h)); }
}

/* --- 9.2 Le squelette des photos (P1-1, P1-2) ------------------------------
   Chaque figure reçoit, en style en ligne, --apercu : sa miniature floue de
   32 px (injecter_squelettes.py, depuis la chaîne d'images). Elle est peinte
   en fond du <picture>, cadrée comme l'image (même object-position), sur un
   neutre de la marque : aucune zone d'image ne reste blanche pendant le
   chargement. Le fond est sur le <picture> et non sur la <figure> : la
   figure du bandeau porte une coupe en rabat, et son encoche doit rester
   vide. Le fondu d'arrivée n'existe que si le script du <head> a posé
   .fondu-photos ; sans lui, les photos sont visibles d'office. */
.spm-photo picture,
.imagette picture {
  background-color: var(--neutre-200);
  background-image: var(--apercu, none);
  background-size: cover;
  background-position: 50% 50%;
  background-repeat: no-repeat;
}

.prestations-photo picture,
.etapes-photo picture { background-position: 50% 40%; }
.photo-large picture {
  background-position: 50% 54%;
  clip-path: polygon(0 var(--coupe-rabat), calc(var(--coupe-rabat) * 2) 0,
                     100% 0, 100% 100%, 0 100%);
}

/* Le héros : un neutre SOMBRE sous la miniature, pour que la phrase blanche
   se lise dès la première peinture (9,2:1 sur --neutre-700), et la miniature
   du bon cadrage — téléphone, ou ordinateur au-delà de 1080 px. */
.hero-photo picture {
  background-color: var(--neutre-700);
  background-position: 96% 0%;
}

@media (min-width: 1080px) {
  .hero-photo picture {
    background-image: var(--apercu-ordi, var(--apercu, none));
    background-position: 50% 50%;
  }
}

/* Au-delà de 900 px, ces deux photos sortent du flux (§4, §5) : le <picture>
   doit couvrir la boîte, sinon il mesure 0 px et le squelette ne se voit pas. */
@media (min-width: 900px) {
  .prestations-photo picture,
  .etapes-photo picture { position: absolute; inset: 0; }
}

.fondu-photos .spm-photo img,
.fondu-photos .imagette img {
  opacity: 0;
  transition: opacity var(--duree-moyenne) var(--entree);
}

.fondu-photos .spm-photo img.arrivee,
.fondu-photos .imagette img.arrivee { opacity: 1; }

/* Le héros d'abord : tant que sa photo n'est pas arrivée, les photos
   différées ne sont pas affichées, donc pas demandées (une image « lazy » en
   display: none ne part pas). Leur boîte est tenue par la figure ou le
   <picture> (ci-dessous et §9.4), et le squelette y est déjà : rien ne
   bouge quand elles apparaissent. Sans JavaScript, rien de tout cela. */
.fondu-photos:not(.heros-pret) main img[loading="lazy"] { display: none; }

.photo-large picture { min-height: max(220px, 38vh); }

@media (min-width: 900px) {
  .photo-large picture { min-height: 56vh; }
}

@media (prefers-reduced-motion: reduce) {
  .fondu-photos .spm-photo img,
  .fondu-photos .imagette img { transition: none; }
}

/* --- 9.3 Les deux blancs du héros se lisent comme des blancs (P0-1) -------
   L'exemple était peint comme une réponse : même blanc, même corps de 39 px,
   même graisse noire. On effaçait « 1201 Genève » lettre par lettre, et rien
   ne partait : il n'y avait rien à effacer. L'exemple garde le blanc plein
   (le contraste sur l'asphalte ne se négocie pas), mais il perd le corps et la
   graisse : une indication, pas une valeur. Ce qu'on tape, lui, garde la voix
   du titre. Le filet du blanc s'épaissit déjà au focus (§8.6). */
.hero-champ input::placeholder {
  font-family: var(--police-texte);
  font-size: .5em;
  font-weight: var(--graisse-normale);
  letter-spacing: 0;
}

/* --- 9.4 Téléphone et tablette : plus visuel (P1-3, P1-7) ----------------- */

.prestation-vignette,
.etape-vignette,
.action-relais,
.photo-equipe { display: none; }

@media (max-width: 899px) {

  /* Les huit prestations défilent de côté, au doigt, sans script. La carte
     suivante dépasse du bord : c'est ce qui dit qu'il y a une suite. La liste
     reste dans la colonne du texte : à gauche le tracé d'itinéraire (dans la
     gouttière) ne croise aucune photo, à droite l'anneau de focus de la liste
     (+4 px) tient dans l'écran (banc C7, relevé le 21.09 quand elle allait
     jusqu'au bord). */
  .prestations-liste {
    display: flex;
    gap: var(--e12);
    padding: 0 0 var(--e8);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    /* Depuis que les titres sont des liens (24.09, pièce 09), la tabulation
       entre DANS la liste. Relevé à 375 × 812 : le lien de la carte qui
       dépasse du bord (36 px visibles) recevait le focus sans que la liste
       défile — Chrome le jugeait visible — et l'anneau sortait de l'écran
       (banc C7, 4 anneaux rognés). La bande de dépassement est retirée de la
       zone utile : le lien y est alors « caché », la liste défile, et
       l'accroche au début de carte le pose en entier. Aucun effet au doigt :
       les cartes s'accrochent par leur DÉBUT, que ce retrait ne touche pas. */
    scroll-padding-inline-end: var(--e48);
  }

  .prestations-liste::-webkit-scrollbar { display: none; }

  .prestation {
    flex: 0 0 min(calc(100% - var(--e48)), 320px);
    padding-top: 0;
    border-top: 0;
    scroll-snap-align: start;
  }

  .prestation + .prestation { margin-top: 0; }

  .prestation-vignette {
    display: block;
    margin: 0 0 var(--e12);
    aspect-ratio: 3 / 2;
  }

  .prestation-vignette picture,
  .etape-vignette picture { height: 100%; }

  .prestation-vignette img,
  .etape-vignette img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .prestation .spm-carte__titre { margin-bottom: var(--e8); }

  /* La photo du fauteuil prend toute la colonne (elle en prenait 78 %, une
     colonne blanche à gauche) ; en 4:5, les visages et le fauteuil restent. */
  .prestations-photo { width: 100%; margin-top: var(--e32); }
  .prestations-photo picture,
  .prestations-photo img { aspect-ratio: 4 / 5; }

  /* Une action à portée de pouce après les cartes, puis après les étapes : il
     y avait sept écrans sans bouton entre le héros et l'aplat final. */
  .action-relais { display: block; margin: var(--e24) 0 0; }
  .action-relais .spm-btn { width: 100%; }

  /* Chaque étape porte sa vignette, à gauche du texte. La rangée de fin
     (1fr) absorbe la hauteur de la vignette : le numéro, le titre et le texte
     restent serrés en haut, au lieu de s'écarter pour la remplir. */
  .etapes-liste { gap: var(--e32); }

  .etape {
    display: grid;
    grid-template-columns: 112px minmax(0, 1fr);
    grid-template-rows: auto auto auto 1fr;
    column-gap: var(--e16);
    align-items: start;
  }

  .etape > * { grid-column: 2; }

  .etape-vignette {
    display: block;
    grid-column: 1;
    grid-row: 1 / -1;
    width: 112px;
    margin: 0;
    aspect-ratio: 4 / 5;
  }

  .etape .etape-num { margin-bottom: var(--e4); }

  /* L'étape 01 montre le calculateur avant qu'on y entre : quatre objets de
     son inventaire, sur le blanc qui est aussi le leur. */
  .etape-objets {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-content: center;
    gap: var(--e4);
    padding: var(--e8);
    background-color: var(--fond);
    border: var(--filet-epaisseur) solid var(--neutre-200);
  }

  .etape-objets img { width: 100%; height: auto; aspect-ratio: 1; object-fit: contain; }

  /* La photo du repérage est devenue la vignette de l'étape 02. */
  .etapes-photo { display: none; }

  /* L'équipe, juste après l'aplat final, dans la colonne du texte : la
     dernière image avant le pied. */
  .photo-equipe { display: grid; margin: var(--e16) 0 var(--e48); }

  /* Entre la carte et l'équipe, l'aplat rouge et ses marges faisaient 740 px
     sans une image (relevé à 390 × 664) : plus qu'un écran. Sur téléphone
     l'aplat se rapproche de la carte. */
  .section-final { margin-top: var(--e32); }
  .photo-equipe picture { aspect-ratio: 3 / 2; }
  .photo-equipe img { display: block; width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover; }
}
