/* ============================================================
   Garage Michel Duverger — Agent Renault, Neulise
   v5 · Blanc dominant / noir premium / jaune Renault en accent
   ------------------------------------------------------------
   LA SEULE FEUILLE MAÎTRESSE DU SITE depuis le 13 août 2026. La v4
   (direction sombre) et sa bascule ont été retirées : styles-v4.css
   n'est plus dans le dossier, il est dans « Sauvegarde du site —
   2026-08-13 (avant retrait des bascules) ». Les comparaisons qui
   suivent restent écrites parce qu'elles expliquent POURQUOI la v5
   est ce qu'elle est ; elles ne décrivent plus une alternative
   vivante.

   CE QUI CHANGE PAR RAPPORT À LA v4 :

   1. LE BLANC DEVIENT LE DÉFAUT. La v4 posait du noir partout et
      n'éclaircissait rien ; ici la page respire en blanc et en
      gris-papier, et le noir redevient ce qu'il doit être — un
      choix, réservé aux moments premium (les avis, la signature de
      fin, le pied de page) et à la TYPOGRAPHIE, qui est noire d'un
      bout à l'autre.

   2. LE JAUNE PEUT ÊTRE DE LA PEINTURE. La v4 s'interdisait l'aplat
      (« jaune = lumière, jamais peinture ») : c'est une règle qui
      tient sur fond noir, où le jaune s'allume, et qui s'effondre
      sur fond blanc, où un filet de 1 px en rgb(239 223 0) est tout
      simplement invisible. Sur clair le jaune devient donc un APLAT
      porteur d'encre noire (boutons, pastilles d'icônes) et des
      filets ÉPAIS (3 px). Sur sombre il redevient de la lumière
      (halo, liseré, lueur) — la v4 avait raison, là.
      C'est le rôle du jeton --rule : 3px sur clair, 1px sur sombre.

   3. UN SYSTÈME DE TONS. Chaque section porte data-tone="light",
      "paper" ou "dark" ; le ton REDÉFINIT les jetons de couleur pour
      tout son sous-arbre. Aucun composant ne connaît sa couleur de
      fond : ils lisent --bg, --text, --border, --card-bg… et sont
      donc corrects dans les trois tons sans une seule surcharge.
      Battement obtenu, de haut en bas :
        hero BLANC · services PAPIER · le garage BLANC ·
        rendez-vous PAPIER · avis NOIR · contact BLANC ·
        signature NOIRE (carte) · pied de page NOIR.

   Typographie NouvelR seule · révélations par masque de ligne ·
   géométrie « losange gravé ».
   ============================================================ */

/* ============================================================
   TYPOGRAPHIE — NouvelR, et rien d'autre
   ------------------------------------------------------------
   NouvelR est la typographie corporate de Renault Group. Le site
   n'emploie AUCUNE autre fonte : c'est la règle de la marque, et
   c'est aussi ce qui tient l'ensemble. Deux fichiers, deux
   graisses RÉELLES (400 et 700) — pas de troisième fonte, pas de
   graisse intermédiaire, pas d'italique. La hiérarchie ne se fait
   donc jamais par changement de caractère : elle se fait par
   graisse, par corps, par approche et par valeur de gris. C'est
   plus contraint, et c'est précisément ce qui fait la tenue.

   Fichiers servis par renault.fr, au titre de l'activité d'Agent
   Renault. 742 glyphes, français complet — accents, œ, guillemets
   français, tiret cadratin, ordinal. Une seule absence connue :
   l'étoile ★, qui n'existe dans aucune fonte latine. Elle vient du
   jeu de symboles du système, dernier maillon de la pile — et les
   trois tailles d'étoiles restent donc HORS de l'échelle ci-dessous,
   calées optiquement sur le texte qu'elles accompagnent.
   ============================================================ */
@font-face {
  font-family: "NouvelR";
  src: url("assets/fonts/nouvelr-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "NouvelR";
  src: url("assets/fonts/nouvelr-bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* ============================================================
   1 · PALETTE BRUTE — trois couleurs, et c'est tout
   ------------------------------------------------------------
   Ces jetons-là ne changent JAMAIS d'un ton à l'autre : ce sont
   les encres. Ce qui change, c'est la façon dont les tons les
   distribuent (bloc 2).
   ============================================================ */
:root {
  --white:    #ffffff;
  /* Gris-papier légèrement CHAUD (et non un gris neutre) : posé
     contre le blanc pur d'une section voisine, un gris froid vire
     au bleu et refroidit toute la page. Celui-ci garde la chaleur
     des photographies (ciel d'été, jaune du R5). */
  --paper:    #f5f4f1;
  --paper-2:  #eceae5;

  --ink:      #0a0a0b;
  --ink-2:    #141315;
  --ink-3:    #1c1b1e;

  /* Jaune Renault officiel — #EFDF00, l'identité 2021. La v4 le
     tirait vers l'or (#F5D300) parce qu'un jaune citron sur fond
     noir paraît verdir ; sur blanc c'est l'inverse, et c'est la
     valeur de marque qui est juste. */
  --yellow-rgb: 239 223 0;
  --yellow:     rgb(var(--yellow-rgb));
  /* Un cran plus sombre : bord et survol des aplats jaunes, là où
     le jaune pur, posé sur du blanc, n'a plus assez de bord. */
  --yellow-deep: #d4c400;
  /* L'encre posée SUR le jaune. Toujours noire : jaune + noir est
     la seule paire de la marque qui passe le contraste (13,5:1). */
  --yellow-ink: var(--ink);

  /* L'or des étoiles. SEULE couleur du site hors du trio, et elle
     est justifiée : #EFDF00 sur blanc tombe à 1,2:1 de contraste —
     une notation qu'on ne voit pas ne note rien. Cet or reste dans
     la famille du jaune, il ne fait que gagner le bord qui lui
     manque. Sur fond noir, le jaune de marque reprend sa place. */
  --gold: #dfb400;

  --error:      #c0392b;
  --error-soft: #e0564a;

  /* Une seule famille. --font-display et --font-body restent deux
     noms distincts non parce que les fontes diffèrent — elles sont
     identiques — mais parce que les RÔLES diffèrent : titrage et
     labeur. Le point de bascule est déjà en place si l'un des deux
     devait bouger. */
  --font:         "NouvelR", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-display: var(--font);
  --font-body:    var(--font);

  /* ---- Échelle typographique ----
     Gamme géométrique de raison 1,125 (seconde majeure), ancrée sur
     le corps de texte à 1rem. Sept paliers. HORS échelle et
     volontairement : les corps fluides en clamp() (type d'affichage,
     réglé cas par cas), le 3rem de la note Google, et les trois
     tailles d'étoiles (symboles, pas du texte). */
  --fs-3xs:  .70rem;   /* capitales micro : nav, labels, méta, tags */
  --fs-2xs:  .79rem;   /* boutons, champs, légendes, mentions */
  --fs-xs:   .89rem;   /* petit labeur, noms, gros boutons */
  --fs-sm:  1rem;      /* corps de texte, chapôs de section */
  --fs-md:  1.125rem;  /* citations, exergues */
  --fs-lg:  1.27rem;   /* sous-titres éditoriaux */
  --fs-xl:  1.6rem;    /* titres de carte */

  /* ---- Approche ----
     Elle suit le CORPS : plus la lettre est grande, plus on la
     resserre ; plus elle est petite et capitale, plus on l'ouvre.
     Six jetons, chacun pour un rôle réel. */
  --track-display:  -.03em;   /* ≥ 2rem, graisse 700 */
  --track-head:     -.02em;   /* 1,25 à 2rem */
  --track-soft:     -.01em;   /* ~1rem, et les emphases en 400 */
  --track-ui:        .03em;   /* boutons, libellés de champ */
  --track-caps:      .12em;   /* capitales : nav, labels, légendes */
  --track-caps-wide: .2em;    /* capitales d'attaque : eyebrows, tags */

  --wrap: 1240px;
  /* Gouttière de `.wrap` — sortie en variable pour que les éléments
     posés en absolu (indice de défilement) puissent s'aligner sur
     la colonne de texte sans recopier le clamp. */
  --gutter: clamp(22px, 5vw, 56px);
  /* Rayons relevés depuis la v4 (5 / 10 px) : sur fond clair, un
     angle presque vif durcit ; c'est le noir qui adoucissait. */
  --radius:    6px;
  --radius-lg: 14px;
  --ease: cubic-bezier(.22, 1, .36, 1);
  --dur: 260ms;
  /* Le RETOUR AU GESTE va plus vite que le reste. 260 ms sur le
     survol d'un bouton se sent comme un retard — l'œil est déjà sur
     l'élément, il attend. 150 ms se sent comme une réponse. La
     pression descend encore : c'est l'accusé de réception du clic,
     il doit partir dans la même image que le doigt. */
  --dur-fast:  150ms;
  --dur-press: 110ms;
  --space-section: clamp(84px, 12vw, 172px);
}

/* ============================================================
   2 · LES TONS — le cœur du système
   ------------------------------------------------------------
   Trois tons, un même jeu de jetons. Une section pose
   data-tone="…" et TOUT son sous-arbre se recolore : fonds,
   encres, filets, cartes, ombres, épaisseur des accents.

   Le ton CLAIR est aussi celui de :root — donc celui des éléments
   fixes qui vivent hors des sections (en-tête, barre d'action
   mobile, bouton retour-haut) et celui des pages légales.
   ============================================================ */
:root,
[data-tone="light"] {
  --bg:            var(--white);
  --surface:       var(--paper);
  --surface-2:     var(--paper-2);
  --card-bg:       var(--white);
  --card-bg-hover: #fffdf0;   /* blanc réchauffé d'un souffle de jaune */

  --border:        rgba(10, 10, 11, .11);
  --border-strong: rgba(10, 10, 11, .22);

  --text:          var(--ink);
  /* Contrastes mesurés sur blanc : 6,9:1 et 4,8:1. Le gris pâle de
     la v4 (#8a8782, 3,4:1) tenait sur du noir et tombait sous le
     seuil dès qu'on le posait sur du blanc — or c'est lui qui porte
     TOUS les micro-libellés en capitales (légendes de chiffres,
     méta d'avis, mentions du pied de page). Assombri jusqu'à
     repasser la barre, sans cesser d'être un gris de second plan. */
  --text-dim:      #5c5a55;
  --text-faint:    #75726d;
  /* Gris CHAUD — signature éditoriale de la 2ᵉ ligne des titres. */
  --ink-warm:      #7d7568;

  /* Épaisseur et longueur des accents jaunes. C'est LE jeton qui
     fait tenir le jaune sur blanc : un filet de 1 px disparaît,
     un filet de 3 px est une marque. */
  --rule:   3px;
  --rule-w: 28px;

  /* Le jaune ne s'allume pas sur du blanc : sur clair, les lueurs
     deviennent des OMBRES chaudes et des liserés francs. */
  --glow:
     0 0 0 1px rgb(var(--yellow-rgb) / .9),
     0 10px 26px -10px rgb(var(--yellow-rgb) / .55);
  --glow-soft:
     0 0 0 1px rgb(var(--yellow-rgb) / .6),
     0 8px 20px -12px rgb(var(--yellow-rgb) / .38);
  --glow-line: none;                 /* halo des filets de 1 px */
  --elev-1: 0 1px 2px rgba(10,10,11,.05), 0 10px 26px -14px rgba(10,10,11,.18);
  --elev-2: 0 2px 6px rgba(10,10,11,.07), 0 22px 46px -20px rgba(10,10,11,.26);
  /* --elev-3 : TROIS distances, et c'est tout l'écart entre une ombre
     de gabarit et une ombre dessinée. Une ombre réelle est la somme
     de trois choses — le contact (1 px, presque noir, dit que
     l'objet TOUCHE), la proximité (12 px, dit son épaisseur), et
     l'ambiante (60 px, très diluée, dit la pièce autour). Une seule
     couche donne toujours soit un contour dur, soit un nuage gris.
     Réservé au tirage du hero et à sa plaque : au-delà, l'effet
     s'use. */
  --elev-3: 0 1px 1px rgba(10,10,11,.055),
            0 6px 12px -6px rgba(10,10,11,.10),
            0 30px 64px -26px rgba(10,10,11,.32);

  /* Décor du hero : le panneau et la trame (cf. section 6). Ce sont
     des VALEURS DE LUMIÈRE, pas des couleurs — le panneau assombrit
     le fond de 2 à 5 %, il ne le repeint pas. La nappe jaune du hero
     continue donc de le traverser. */
  --panel-top:  rgba(10, 10, 11, .052);
  --panel-bot:  rgba(10, 10, 11, .012);
  --rule-faint: rgba(10, 10, 11, .06);

  /* Le filet de pied d'en-tête. C'est --ink-warm DILUÉ — le même gris
     chaud que « à Neulise. », la deuxième encre de la page. Le choix
     est motivé, cf. le § 5 : ni le noir du titre (il ferait un
     deuxième bloc noir à lire avant lui), ni le jaune (un filet de
     1 px de jaune sur blanc disparaît — c'est tout le sens du jeton
     --rule à 3 px). */
  --header-rule: rgba(125, 117, 104, .34);

  --wash:          rgba(10, 10, 11, .022);   /* nappe directionnelle de section */
  --losange-rgb:   10 10 11;                 /* filigrane gravé */
  --losange-op:    .055;
  --ghost-stroke:  rgba(10, 10, 11, .07);    /* chiffre fantôme */
  --star:          var(--gold);
  --star-shadow:   none;
  --glass:         rgba(255, 255, 255, .74); /* pastilles translucides */
  --glass-solid:   rgba(255, 255, 255, .93);
  --tick-off:      rgba(10, 10, 11, .16);
  --focus:         var(--ink);
  /* Encre des micro-libellés qui étaient jaunes en v4 (astérisque
     obligatoire, mentions) : sur blanc ils redeviennent noirs, et
     c'est un FILET jaune à côté qui porte l'accent. */
  --accent-text:   var(--ink);
  --link-hot:      var(--ink);
  --sheen-hot:     #7a6c00;                  /* balayage du titre */
  --photo-filter:  contrast(1.03) saturate(1.02);

  /* Cadre du hero (portage Spotlight Card, cf. bloc PORTAGE) */
  --spot-backdrop:         rgba(10, 10, 11, .035);
  --bg-spot-opacity:       .34;
  --border-light-opacity:  0;
}

[data-tone="paper"] {
  --bg:            var(--paper);
  --surface:       var(--white);
  --surface-2:     var(--paper-2);
  --card-bg:       var(--white);
  --card-bg-hover: #fffdf0;
  --border:        rgba(10, 10, 11, .12);
  --border-strong: rgba(10, 10, 11, .2);
  --wash:          rgba(255, 255, 255, .5);
}

[data-tone="dark"] {
  --bg:            var(--ink);
  --surface:       var(--ink-2);
  --surface-2:     var(--ink-3);
  --card-bg:       var(--ink);
  --card-bg-hover: #17161a;

  --border:        rgba(255, 255, 255, .1);
  --border-strong: rgba(255, 255, 255, .2);

  --text:          #f2f0ec;
  --text-dim:      #9c9992;
  --text-faint:    #67645f;
  --ink-warm:      #a89e8f;

  /* Sur noir le filet de 1 px se voit — et se voit d'autant mieux
     qu'il porte un halo. C'est la règle de la v4, conservée ici
     puisqu'elle est juste dans ce ton-là. */
  --rule:   1px;
  --rule-w: 34px;

  --glow:
     0 0 0 1px rgb(var(--yellow-rgb) / .28),
     0 0 22px -2px rgb(var(--yellow-rgb) / .28),
     0 0 64px -14px rgb(var(--yellow-rgb) / .16);
  --glow-soft:
     0 0 0 1px rgb(var(--yellow-rgb) / .18),
     0 0 30px -6px rgb(var(--yellow-rgb) / .16);
  --glow-line: 0 0 8px rgb(var(--yellow-rgb) / .7);
  --elev-1: 0 12px 30px -18px rgba(0,0,0,.9);
  --elev-2: 0 1.4rem 3rem -1rem rgba(0,0,0,.88);
  --elev-3: 0 1px 1px rgba(0,0,0,.5),
            0 8px 18px -8px rgba(0,0,0,.62),
            0 36px 72px -28px rgba(0,0,0,.92);

  /* Sur sombre le panneau ÉCLAIRE au lieu d'assombrir — même geste,
     signe inverse. */
  --panel-top:  rgba(255, 255, 255, .042);
  --panel-bot:  rgba(255, 255, 255, .006);
  --rule-faint: rgba(255, 255, 255, .055);

  --wash:          rgba(255, 255, 255, .025);
  --losange-rgb:   255 255 255;
  --losange-op:    .04;
  --ghost-stroke:  rgba(255, 255, 255, .05);
  --star:          var(--yellow);
  --star-shadow:   drop-shadow(0 0 6px rgb(var(--yellow-rgb) / .45));
  --glass:         rgba(10, 10, 11, .55);
  --glass-solid:   rgba(10, 10, 11, .88);
  --tick-off:      rgba(255, 255, 255, .18);
  --focus:         var(--yellow);
  --accent-text:   var(--yellow);
  --link-hot:      var(--yellow);
  --sheen-hot:     #fffbe9;
  --photo-filter:  contrast(1.04) saturate(.96) brightness(.9);

  --spot-backdrop:         hsl(0 0% 60% / .12);
  --bg-spot-opacity:       .1;
  --border-light-opacity:  1;
}

/* L'ENCRE DOIT SUIVRE LE TON — et cette ligne est la seule qui le
   fasse. Redéfinir --text ne repeint rien par soi-même : un titre
   qui ne déclare pas sa couleur hérite de `body`, c'est-à-dire de
   l'encre du ton CLAIR, et reste donc noir sur la section noire.
   (Symptôme observé avant correctif : « Ils nous font » invisible
   dans « Avis », « Passez nous voir. » invisible dans la carte de
   signature — seuls les mots en <em> se voyaient, parce qu'eux
   déclaraient --ink-warm.)
   Posée sur l'élément qui PORTE le ton, elle vaut pour tout son
   sous-arbre, et les couleurs explicites (--text-dim, --text-faint)
   la surchargent normalement, par ordre de source. */
[data-tone] { color: var(--text); }

/* ============================================================
   3 · BASE
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; background: var(--white); }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* font-synthesis: none — la clé de voûte du système typographique.
   NouvelR ne livre que 400 et 700 : tout ce que la page demande en
   dehors de ces deux valeurs, le navigateur le FABRIQUE en
   épaississant les contours lui-même. Cette ligne l'interdit. Une
   future graisse non chargée ne « marchera plus à peu près », elle
   retombera franchement sur la graisse réelle la plus proche —
   l'écart se voit tout de suite, au lieu de s'installer. */
body { margin: 0; font-family: var(--font-body); font-size: 16px; line-height: 1.65; color: var(--text); background: var(--bg); font-synthesis: none; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; overflow-x: hidden; }
/* line-height 1.06 : assez serré pour l'allure NouvelR, assez haut pour les
   dépassements de courbes (G, O, Q) et les descendantes (g, y, p) */
h1, h2, h3 { font-family: var(--font-display); font-weight: 700; line-height: 1.06; letter-spacing: var(--track-head); margin: 0; }
p { margin: 0; }
a { color: inherit; text-decoration: none; }
img, svg, video { display: block; max-width: 100%; }
ul { margin: 0; padding: 0; list-style: none; }

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gutter); }
.muted { color: var(--text-faint); }

/* Emphase = contraste de graisse NouvelR : 700 encre / 400 gris chaud */
.section-title em, .hero-title em { font-style: normal; font-weight: 400; color: var(--ink-warm); letter-spacing: var(--track-soft); }

/* Eyebrow : un TAB jaune + un libellé en capitales. Sur clair le
   tab fait 3 px de haut (il se voit), sur sombre 1 px avec halo
   (il s'allume). Même objet, deux physiques. */
.eyebrow, .section-num { display: flex; align-items: center; gap: 13px; font-size: var(--fs-3xs); letter-spacing: var(--track-caps-wide); text-transform: uppercase; color: var(--text-dim); }
.eyebrow-line { display: inline-block; width: var(--rule-w); height: var(--rule); border-radius: calc(var(--rule) / 2); background: var(--yellow); box-shadow: var(--glow-line); transform: scaleX(0); transform-origin: left; transition: transform .5s var(--ease) .15s; }
[data-reveal].in .eyebrow-line, .hero.in .eyebrow-line { transform: scaleX(1); }

.skip-link { position: absolute; left: 12px; top: -60px; z-index: 300; background: var(--ink); color: var(--white); padding: 10px 18px; border-radius: var(--radius); font-weight: 700; transition: top var(--dur) var(--ease); }
.skip-link:focus { top: 12px; }
/* Le focus prend la couleur du TON : encre noire sur clair (le
   jaune y perd son contraste), jaune sur sombre. Le champ de
   formulaire, lui, garde en plus son halo jaune — l'accent reste
   visible sans jamais porter seul l'indication. */
:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }

/* ---------- Grain (matière, pas texture) ----------
   Sur blanc, le grain ne « creuse » plus comme sur du noir : il
   casse juste le plat d'un aplat trop parfait. Deux fois plus
   discret qu'en v4, sinon il salit. */
.grain { position: fixed; inset: 0; z-index: 4; pointer-events: none; opacity: .025; mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); }

/* ---------- Losange gravé (filigrane, 1px à toute échelle) ---------- */
.losange { position: absolute; z-index: 0; pointer-events: none; color: rgb(var(--losange-rgb)); opacity: var(--losange-op); display: block; height: auto; fill: none; stroke: currentColor; stroke-width: .3; }

/* ---------- Primitives des plaques photographiques ----------
   Base de la seule plaque restante — la photo d'équipe de « Le
   garage ». Déclarée ICI, tout en haut, pour rester surchargeable
   à spécificité égale par ordre de source. */
.plate { position: relative; margin: 0; }
.plate img { display: block; width: 100%; height: 100%; object-fit: cover; }
.plate { -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-size: 100% 100%; mask-size: 100% 100%; }

/* ============================================================
   4 · BOUTONS
   ------------------------------------------------------------
   Trois niveaux, et un seul jaune : le PRIMAIRE est un aplat jaune
   à encre noire, dans les trois tons. C'est le changement le plus
   visible depuis la v4 (bouton blanc à halo jaune) et c'est celui
   qui rapproche le plus le site de la marque : chez Renault, le
   geste « on y va » est jaune.
   ============================================================ */
.btn { position: relative; display: inline-flex; align-items: center; justify-content: center; gap: 9px; font-family: var(--font-display); font-weight: 700; font-size: var(--fs-2xs); line-height: 1; letter-spacing: var(--track-ui); padding: 14px 26px; border-radius: var(--radius); border: 1px solid transparent; cursor: pointer; white-space: nowrap; transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease); }
.btn svg { flex: none; }
.btn-lg { padding: 17px 32px; font-size: var(--fs-xs); }
.btn-block, .btn.block { width: 100%; }

.btn-primary { background: var(--yellow); color: var(--yellow-ink); border-color: var(--yellow); overflow: hidden; box-shadow: var(--elev-1); }

/* Secondaire : contour seul. Au survol il ne « s'allume » plus, il
   se REMPLIT d'un souffle de jaune et gagne son bord — la version
   claire de la même intention. */
.btn-outline { border-color: var(--border-strong); color: var(--text); }

/* CTA de l'en-tête : pastille noire à encre blanche, qui BASCULE en
   jaune au survol. C'est le seul endroit du site où le noir est un
   fond de bouton — et c'est voulu : dans une barre blanche, c'est
   lui qui doit peser. */
.btn-reserve { background: var(--ink); border-color: var(--ink); color: var(--white); text-transform: uppercase; letter-spacing: var(--track-caps); font-size: var(--fs-3xs); padding: 12px 20px; }

/* ---------- Aplat d'encre — la troisième variante ----------
   VARIANTE, comme .btn-primary et .btn-outline : elle dit la
   couleur, jamais la taille. Le gabarit vient à côté — et depuis le
   13 août c'est .btn-hero, LE MÊME que celui du bouton jaune.
   Écrire couleur et taille dans une seule classe est ce qui rend un
   bouton irréutilisable ailleurs.

   L'encre était DÉJÀ un fond de bouton sur ce site : .btn-reserve,
   dans l'en-tête. On ne l'invente pas ici, on l'agrandit — et les
   deux gardent le même rôle, celui du geste qui doit peser sans
   dépenser le jaune de la marque.

   IL NE BASCULE PAS EN JAUNE AU SURVOL, à la différence de
   .btn-reserve. Dans l'en-tête, le bouton noir est seul et le
   passage au jaune est sa récompense ; dans le hero il a le vrai
   bouton jaune pour VOISIN IMMÉDIAT, et deux aplats jaunes côte à
   côte effaceraient d'un coup la hiérarchie qu'on vient de poser.
   Il répond donc dans sa propre matière : l'encre s'éclaircit d'un
   ton (--ink-3), l'ombre se creuse, le bouton se relève de 2 px.
   Un noir qui s'éclaircit est un noir qui s'approche.

   overflow: hidden — pour le balayage ci-dessous, qui doit être
   coupé net par le rayon du bouton. */
.btn-ink { background: var(--ink); border-color: var(--ink); color: var(--white); overflow: hidden; }

/* CTA du hero — le point le plus dense de la page. Ce qui appelle
   l'œil est l'aplat jaune lui-même et son ombre chaude, POSÉE.
   (Une respiration infinie a été essayée puis retirée : un bouton
   qui pulse tout seul est un geste de page d'atterrissage, pas de
   marque. L'ancienne @keyframes cta-breathe est dans la sauvegarde
   « v5.0 (avant polissage hero) » si l'on veut la revoir.) */
.btn-hero { padding: 20px 38px; font-size: var(--fs-xs); letter-spacing: var(--track-ui);
  box-shadow: 0 2px 6px rgba(10,10,11,.09), 0 18px 38px -16px rgb(var(--yellow-rgb) / .75); }
.btn-hero svg { transition: transform var(--dur-fast) var(--ease); }

/* L'ENCRE DANS LE GABARIT DU HERO — « Nos véhicules ».
   Une seule déclaration, et c'est voulu : le bouton noir prend
   .btn-hero tel quel — mêmes 20px/38px, même corps, même
   interlettrage, même hauteur. Il n'a plus de gabarit à lui.

   (Il en a eu un, .btn-veh, avec 32px de garniture latérale au lieu
   de 38 : le libellé était plus court, on avait resserré. C'était
   l'erreur. Deux boutons côte à côte qui ne diffèrent QUE par
   l'encre se lisent comme un choix ; qu'on leur donne en plus des
   garnitures différentes et ils redeviennent deux objets sans
   rapport. La largeur inégale qui en résulte — 177 contre 252 px —
   n'est pas un défaut : ce sont deux libellés de longueurs
   différentes dans le même habit.)

   SEULE L'OMBRE est reprise, parce qu'elle porte une couleur.
   Celle de .btn-hero est jaune ; sous un aplat noir elle vire au
   halo brun — le défaut classique du « bouton noir premium » mal
   réglé. Même géométrie donc, exactement, mais en neutre : les deux
   boutons semblent posés sur le même plan, à la même hauteur. */
.btn-ink.btn-hero {
  box-shadow: 0 2px 6px rgba(10,10,11,.10), 0 18px 38px -16px rgba(10,10,11,.42);
}

/* Appel direct — même gabarit, en retrait : contour seul, chiffre
   lisible. Le fond translucide n'existe que parce que ce bouton
   peut se retrouver sur la photo (tablette et mobile). */
.btn-call { padding: 20px 28px; font-size: var(--fs-xs); letter-spacing: var(--track-ui);
  background: var(--glass); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); }
/* Le combiné s'incline très légèrement — un décroché, pas une
   sonnerie. Même durée et même courbe que la flèche du CTA voisin :
   les deux boutons répondent d'un seul geste. */
.btn-call svg { transition: transform var(--dur-fast) var(--ease); }

/* Les liens fléchés suivent la même règle : la flèche part dans le
   sens de la lecture, de 3px, jamais plus. Celle-ci est diagonale
   (lien externe) — elle part donc en diagonale. */
.hero-link { border-bottom: var(--rule) solid rgb(var(--yellow-rgb)); padding-bottom: 2px; font-weight: 700; font-size: var(--fs-xs); display: inline-flex; align-items: center; gap: 9px; transition: border-color var(--dur-fast) var(--ease); }
.hero-link svg { transition: transform var(--dur-fast) var(--ease); }

/* ---------- SURVOL — seulement là où un curseur existe ----------
   Sans cette barrière, le navigateur d'un téléphone SIMULE un
   :hover au toucher et l'y laisse collé jusqu'au prochain appui
   ailleurs. Concrètement : on touche « Prendre rendez-vous », la
   page défile, et le bouton reste soulevé de 2 px, ombre creusée,
   comme un état bloqué. C'est le premier bouton de la page.
   Tout ce qui suit ne répond donc qu'à une souris ou un stylet. */
@media (hover: hover) and (pointer: fine) {
  .btn-primary:hover { background: var(--yellow-deep); border-color: var(--yellow-deep); box-shadow: var(--elev-2); transform: translateY(-2px); }
  .btn-outline:hover { border-color: var(--yellow); background: rgb(var(--yellow-rgb) / .12); transform: translateY(-2px); }
  [data-tone="dark"] .btn-outline:hover { background: transparent; box-shadow: var(--glow-soft); }
  .btn-reserve:hover { background: var(--yellow); border-color: var(--yellow); color: var(--yellow-ink); }
  /* L'encre : même geste que le jaune — la couleur s'approfondit d'un
     ton, le bouton se relève de 2 px, l'ombre se creuse. Les valeurs
     de creusement sont celles de .btn-hero au chiffre près, en
     neutre. Rien de propre à ce bouton : c'est le survol du site. */
  .btn-ink:hover { background: var(--ink-3); border-color: var(--ink-3); transform: translateY(-2px); }
  .btn-ink.btn-hero:hover { box-shadow: 0 3px 8px rgba(10,10,11,.14), 0 26px 52px -18px rgba(10,10,11,.5); }
  .btn-hero:hover { box-shadow: 0 3px 8px rgba(10,10,11,.12), 0 26px 52px -18px rgb(var(--yellow-rgb) / 1); }
  .btn-hero:hover svg { transform: translateX(4px); }
  .btn-call:hover svg { transform: rotate(-9deg) scale(1.06); }
  .hero-link:hover { border-bottom-color: var(--yellow-deep); }
  .hero-link:hover svg { transform: translate(3px, -3px); }

  /* Balayage BLANC (et non jaune) : sur un aplat jaune, un balayage
     jaune ne se voit pas. C'est le même geste qu'en v4, retourné.

     LES DEUX APLATS L'ONT — même angle, même largeur de bande, même
     durée, même courbe. C'est ce que « les mêmes micro-interactions »
     veut dire : on ne peut pas donner le balayage à l'un et pas à
     l'autre sans que le second paraisse le brouillon du premier.
     Seule l'OPACITÉ diffère, et il le faut : 60 % de blanc sur du
     jaune est une lumière, les mêmes 60 % sur du noir sont un éclair.
     À 26 %, le noir reçoit un satiné — la même quantité de lumière
     perçue, pas la même quantité d'encre.

     Ce que le noir n'a PAS : le liseré jaune de motion.js, qui trace
     le périmètre du bouton au survol. Il est réservé à .btn-primary
     et doit le rester — c'est du jaune de marque, et dans le premier
     écran le jaune ne désigne qu'une chose, la prise de rendez-vous.
     Un liseré jaune sur le bouton noir mettrait les deux gestes à
     égalité au moment précis où l'on veut qu'ils ne le soient pas. */
  @media (prefers-reduced-motion: no-preference) {
    .btn-primary::after,
    .btn-ink::after { content: ""; position: absolute; inset: 0; background: linear-gradient(105deg, transparent 42%, rgba(255,255,255,.6) 50%, transparent 58%); transform: translateX(-130%); transition: transform .8s var(--ease); pointer-events: none; }
    .btn-ink::after { background: linear-gradient(105deg, transparent 42%, rgba(255,255,255,.26) 50%, transparent 58%); }
    .btn-primary:hover::after,
    .btn-ink:hover::after { transform: translateX(130%); }
  }
}

/* ---------- PRESSION — le seul retour qui existe au doigt --------
   Un téléphone n'a pas de survol : sans cet état, toucher le CTA
   du hero ne produit RIEN jusqu'au chargement de la page suivante.
   L'échelle est volontairement faible (.97) et la durée courte :
   ce n'est pas une animation, c'est un accusé de réception.
   Ces règles viennent APRÈS les survols : à spécificité égale,
   c'est l'ordre du fichier qui tranche, et la pression doit
   l'emporter sur le soulèvement. */
.btn:active,
.frame-nav:active,
.mobile-bar a:active { transform: scale(.97); transition-duration: var(--dur-press); }
/* Le lien fléché n'a pas de boîte : c'est sa flèche qui encaisse. */
.hero-link:active svg { transform: translate(1px, -1px) scale(.94); transition-duration: var(--dur-press); }
.hero-scroll:active { color: var(--text); }

/* ============================================================
   5 · EN-TÊTE — barre blanche, encre noire
   ------------------------------------------------------------
   Elle est TRANSPARENTE au repos (le hero blanc passe dessous) et
   devient un verre blanc dès le premier défilement. Elle reste
   blanche au-dessus des sections noires : c'est ce qui la tient
   comme un objet constant plutôt que comme un caméléon.
   ============================================================ */
.site-header { position: fixed; inset: 0 0 auto 0; z-index: 100; border-bottom: 1px solid transparent; transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease), backdrop-filter var(--dur) var(--ease), box-shadow var(--dur) var(--ease); }
.site-header[data-scrolled] { background: rgba(255,255,255,.86); backdrop-filter: blur(18px) saturate(1.3); -webkit-backdrop-filter: blur(18px) saturate(1.3); border-bottom-color: var(--border); box-shadow: 0 1px 0 rgba(10,10,11,.04), 0 12px 30px -24px rgba(10,10,11,.5); }
.scroll-progress { position: absolute; top: 0; left: 0; height: var(--rule); width: 0; background: var(--yellow); }
.header-inner { position: relative; display: flex; align-items: center; gap: 26px; height: 82px; }

/* ---------- Le filet de pied ----------
   L'en-tête au repos n'avait aucune assise : elle flottait sur le
   blanc du hero et ne se lisait pas comme une bande. Ce filet lui en
   donne une, et il fait un second travail, plus important.

   IL EST À LA LARGEUR DU CONTENU, PAS DE L'ÉCRAN. Un filet
   pleine-largeur est une bordure de barre d'outils — il transforme
   l'en-tête en navbar. Celui-ci part du bord gauche du losange et
   s'arrête au bord droit du bouton, c'est-à-dire EXACTEMENT sur les
   filets n° 1 et n° 7 de la trame du hero (les deux boîtes valent
   min(100vw, --wrap) - 2 × --gutter, à toute largeur). Les sept
   verticales ont enfin une horizontale à laquelle se pendre : ce
   n'est plus une bordure, c'est la ligne de référence d'un dessin.

   1 px, et un gris chaud à 34 % : assez pour que la bande existe,
   trop peu pour qu'on la regarde. Elle s'efface au défilement, où
   c'est la bordure pleine-largeur du verre blanc qui prend le
   relais — deux filets de largeurs différentes en même temps se
   verraient comme un défaut d'alignement. */
.header-inner::after {
  content: "";
  position: absolute; bottom: 0;
  left: var(--gutter); right: var(--gutter);
  height: 1px;
  background: var(--header-rule);
  transition: opacity var(--dur) var(--ease);
}
.site-header[data-scrolled] .header-inner::after { opacity: 0; }

/* Le losange est un SVG inline en currentColor : il suit l'encre du
   ton, donc noir sur la barre blanche. (Le JPEG blanc-sur-noir de
   la v4 n'est plus référencé nulle part — il ne pouvait pas suivre.) */
.brand { display: inline-flex; align-items: center; gap: 13px; margin-right: auto; }
.brand-logo { width: 22px; height: auto; fill: currentColor; color: var(--text); }
.brand-text { display: flex; flex-direction: column; line-height: 1.12; }
.brand-name { font-family: var(--font-display); font-weight: 700; font-size: var(--fs-sm); letter-spacing: var(--track-soft); }
.brand-tag { font-size: var(--fs-3xs); letter-spacing: var(--track-caps-wide); text-transform: uppercase; color: var(--text-faint); }

.nav { display: flex; gap: 4px; }
.nav a { position: relative; padding: 9px 16px; font-size: var(--fs-3xs); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--text-dim); transition: color var(--dur) var(--ease); }
/* Le soulignage entre de gauche à droite et SORT de gauche à droite
   (bascule d'origine au survol). 3 px sur clair : c'est un onglet
   jaune, pas un cheveu. */
.nav a::after { content: ""; position: absolute; left: 16px; right: 16px; bottom: 4px; height: var(--rule); border-radius: calc(var(--rule) / 2); background: var(--yellow); box-shadow: var(--glow-line); transform: scaleX(0); transform-origin: right; transition: transform var(--dur) var(--ease), opacity var(--dur) var(--ease); }
.nav a:hover { color: var(--text); }
.nav a:hover::after { transform: scaleX(1); transform-origin: left; }
/* Scrollspy : la section courante garde son onglet, en demi-teinte */
.nav a[data-active] { color: var(--text); }
.nav a[data-active]::after { transform: scaleX(1); transform-origin: left; opacity: .5; }
.nav a[data-active]:hover::after { opacity: 1; }

.header-actions { display: flex; align-items: center; gap: 18px; }
.header-loc { display: inline-flex; align-items: center; gap: 7px; font-size: var(--fs-3xs); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--text-dim); transition: color var(--dur) var(--ease); }
.header-loc:hover { color: var(--text); }

.menu-toggle { display: none; width: 46px; height: 46px; border: 1px solid var(--border-strong); border-radius: var(--radius); background: transparent; cursor: pointer; padding: 0; flex-direction: column; align-items: center; justify-content: center; gap: 6px; }
.menu-toggle span { width: 20px; height: 1.6px; background: var(--text); transition: transform var(--dur) var(--ease); }
.menu-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(3.8px) rotate(45deg); }
.menu-toggle[aria-expanded="true"] span:nth-child(2) { transform: translateY(-3.8px) rotate(-45deg); }

.mobile-menu { display: flex; flex-direction: column; gap: 4px; padding: 14px clamp(22px,5vw,56px) 26px; background: rgba(255,255,255,.98); backdrop-filter: blur(16px); border-bottom: 1px solid var(--border); box-shadow: 0 20px 40px -30px rgba(10,10,11,.6); }
.mobile-menu[hidden] { display: none; }
.mobile-menu a:not(.btn) { padding: 15px 6px; font-size: var(--fs-2xs); letter-spacing: var(--track-caps); text-transform: uppercase; border-bottom: 1px solid var(--border); color: var(--text-dim); }
.mobile-menu .btn { margin-top: 10px; }

/* ---------- Décalage du contenu sous l'en-tête fixe ---------- */
#contenu { padding-top: 82px; }

/* Les étoiles sont le seul endroit du site qui ne soit PAS en
   NouvelR, et ce n'est pas une entorse : ★ (U+2605) n'existe dans
   aucune fonte latine. Le glyphe vient du jeu de symboles du
   système, tout au bout de la pile de repli. Ses trois tailles
   échappent pour la même raison à l'échelle typographique : elles
   sont calées à l'œil, car la chasse du symbole varie d'un système
   à l'autre. L'approche de 2px est de l'espacement de symboles. */
.stars { color: var(--star); letter-spacing: 2px; line-height: 1; filter: var(--star-shadow); }
.star-half { position: relative; display: inline-block; color: var(--text-faint); }
.star-half::before { content: "★"; position: absolute; left: 0; top: 0; width: 50%; overflow: hidden; color: var(--star); }

/* ============================================================
   6 · HERO — ouverture claire (typographie + tirage photo)
   ------------------------------------------------------------
   Le hero remonte SOUS l'en-tête (transparente tant qu'on n'a pas
   défilé) : la composition part du tout premier pixel. Son padding
   haut rend au texte les 82 px avalés. min-height et non height :
   si la typographie déborde, le hero grandit au lieu de rogner.

   Le fond descend du blanc pur vers le gris-papier de la section
   suivante : la couture entre le hero et « Nos prestations » ne se
   voit pas, elle se traverse.

   LES MARGES SONT DES JETONS, et ce n'est pas de la coquetterie.
   Elles étaient écrites en dur dans le padding, pendant que la
   hauteur du cadre en devinait le total avec un « - 260px »
   approximatif. Résultat mesuré en 1440 × 900 : le hero faisait
   967 px de haut pour 900 px d'écran, et l'indice « Découvrir le
   garage » — posé en absolu à 58 px du bas — tombait 19 px SOUS la
   ligne de flottaison. Invisible. Un hero qui annonce 100svh doit
   tenir dans 100svh : le cadre déduit maintenant sa hauteur des
   mêmes jetons que le padding, l'arithmétique se referme.
   ============================================================ */
.hero {
  --hero-pad-top: clamp(22px, 4.6vh, 64px);
  --hero-pad-bot: clamp(88px, 11vh, 142px);
  position: relative;
  margin-top: -82px;
  min-height: 100svh;
  display: grid; align-items: center;
  padding-block: calc(82px + var(--hero-pad-top)) var(--hero-pad-bot);
  overflow: hidden;
  isolation: isolate;
  background:
    radial-gradient(120% 78% at 88% -16%, rgb(var(--yellow-rgb) / .22), transparent 58%),
    radial-gradient(80% 60% at 4% 104%, rgba(10,10,11,.05), transparent 62%),
    linear-gradient(180deg, var(--white) 0%, var(--white) 58%, var(--paper) 100%);
}

/* ============================================================
   6a · LE DÉCOR DU HERO — trame et panneau
   ------------------------------------------------------------
   Le fond était vide. Pas sobre : vide. Sur du blanc, « sobre »
   demande au moins une structure qu'on ne voit pas mais qui tient
   tout le reste — c'est ce que fait une trame sur une planche
   d'architecte.

   DEUX ÉLÉMENTS, ET PAS UN DE PLUS :

   1. La TRAME — sept filets à 6 % d'encre, alignés sur les sixièmes
      de la colonne de texte. Ils ne se voient pas ; ils se
      RESSENTENT. Leur travail est de rendre l'asymétrie lisible
      comme un choix : le panneau démarre exactement sur le
      quatrième filet, le texte s'aligne exactement sur le premier.
      Sans eux, un panneau décalé est un accident ; avec eux, c'est
      une décision.

   2. Le PANNEAU — une plaque qui part du quatrième filet et fuit
      hors de l'écran à droite. C'est LUI qui casse la symétrie
      55/45, et non le cadre : le cadre reste entier (décision
      documentée) tandis que le panneau, lui, peut être coupé par le
      bord sans rien perdre. Le tirage se pose dessus en débordant à
      gauche et en retrait à droite — ce double décalage est tout ce
      qui sépare « une photo dans un rectangle » de « un tirage
      épinglé sur un mur ».

   AUCUN LOSANGE ici : le hero porte une photographie de l'enseigne,
   et le fil losange de motion.js traverse déjà la page. Un
   troisième losange serait une redite, pas un détail.

   INTERDIT ABSOLU — ce bloc est un FRÈRE de .hero-inner, jamais un
   ancêtre. Le cadre du hero vit de background-attachment: fixed,
   qui meurt si un ancêtre porte transform/filter/will-change (cf.
   bloc PORTAGE). Le décor peut donc bouger ; le cadre, non.
   ============================================================ */
.hero-canvas {
  /* La largeur utile de .wrap, gouttières déduites — exactement la
     boîte de contenu du texte. Trame et panneau s'y réfèrent tous
     les deux : c'est ce qui garantit que le bord du panneau tombe
     sur un filet, et non « à peu près » dessus. */
  --content-w: min(100% - 2 * var(--gutter), var(--wrap) - 2 * var(--gutter));
  --content-x: calc((100% - var(--content-w)) / 2);
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

/* ---------- La trame ---------- */
.hero-rules {
  position: absolute; top: 0; bottom: 0;
  left: var(--content-x);
  width: var(--content-w);
  /* Elle naît SOUS LE FILET DE L'EN-TÊTE — 82 px, la hauteur de la
     barre, en dur et non en pourcentage : c'est ce filet-là qu'elle
     doit toucher, pas une fraction du hero. Les verticales partent
     donc de l'horizontale, comme sur une planche. Elle s'éteint
     avant le bas : une trame qui touche les deux bords devient un
     tableau. */
  -webkit-mask-image: linear-gradient(180deg, transparent 82px, #000 150px, #000 58%, transparent 96%);
          mask-image: linear-gradient(180deg, transparent 82px, #000 150px, #000 58%, transparent 96%);
}
.hero-rules i {
  position: absolute; top: 0; bottom: 0; width: 1px;
  background: var(--rule-faint);
}
.hero-rules i:nth-child(1) { left: 0; }
.hero-rules i:nth-child(2) { left: calc(100% / 6); }
.hero-rules i:nth-child(3) { left: calc(100% / 3); }
.hero-rules i:nth-child(4) { left: 50%; }
.hero-rules i:nth-child(5) { left: calc(200% / 3); }
.hero-rules i:nth-child(6) { left: calc(500% / 6); }
.hero-rules i:nth-child(7) { left: 100%; }

/* ---------- Le panneau ----------
   Son bord gauche tombe sur le filet n° 5 (4/6 de la colonne de
   texte) ; à droite il n'a pas de bord, il sort de l'écran. Angles
   vifs : un panneau d'architecture n'a pas de coins arrondis, et
   c'est précisément ce qui l'empêche de ressembler à une carte. */
.hero-panel {
  position: absolute;
  left: calc(var(--content-x) + var(--content-w) * 2 / 3);
  right: 0;
  /* Descendu (8,5vh → 11,5vh, plancher 58 → 98 px) DEPUIS LE FILET
     DE PIED : le repère de coupe supérieur occupe [top-1, top+15] et
     tombait exactement sur les 82 px du filet. Deux marques qui se
     croisent se lisent comme une erreur d'impression. Le plancher
     garantit 15 px d'air sur l'écran le plus bas. */
  top: clamp(98px, 11.5vh, 132px);
  bottom: clamp(48px, 7vh, 92px);
  border-left: 1px solid var(--border);
  background: linear-gradient(180deg, var(--panel-top) 0%, var(--panel-bot) 100%);
}

/* Repères de coupe : deux équerres de 14 px sur les seuls angles
   VISIBLES du panneau. Une marque d'imprimeur, la même famille de
   signes que le « tirage sous passe-partout » — et deux angles au
   lieu de quatre, parce que le panneau est coupé à droite : poser
   des repères sur un bord qui n'existe pas serait du décor. */
.crop { position: absolute; width: 16px; height: 16px; }
.crop::before, .crop::after {
  content: ""; position: absolute; background: var(--border-strong);
}
.crop::before { width: 100%; height: 1px; }
.crop::after  { width: 1px; height: 100%; }
.crop-tl { left: -1px; top: -1px; }
.crop-tl::before { top: 0; left: 0; }
.crop-tl::after  { top: 0; left: 0; }
.crop-bl { left: -1px; bottom: -1px; }
.crop-bl::before { bottom: 0; left: 0; }
.crop-bl::after  { bottom: 0; left: 0; }

/* ============================================================
   PORTAGE — « Spotlight Card » · 21st.dev / @easemize
   ------------------------------------------------------------
   Portage fidèle, pas réinterprétation. Les variables, les
   dégradés, les pseudo-éléments, la couche de flou et le suivi du
   pointeur sont repris tels quels. Ce qui était classe Tailwind
   est simplement écrit en CSS : rounded-2xl → 1rem, p-4 → 1rem,
   gap-4 → 1rem, grid-rows-[1fr_auto], backdrop-blur-[5px].

   Le découpage reproduit celui du composant React :
   · getInlineStyles()  → .hero-frame        (style={} sur cardRef,
                                              donc l'élément PORTEUR seul)
   · beforeAfterStyles  → [data-spotlight]…  (feuille globale, donc
                                              porteur ET enfant)
   Ne pas fondre les deux : dans l'original l'enfant de nimbe ne
   reçoit PAS les styles inline.

   SIX ÉCARTS, tous techniquement nécessaires :

   1. data-glow → data-spotlight. Le site utilisait DÉJÀ data-glow
      (lueur jaune des cartes). Les sélecteurs du composant sont
      des sélecteurs d'attribut globaux : les conserver aurait
      repeint les six cartes de « Nos prestations ». Simple
      renommage — la forme des sélecteurs est inchangée.

   2. touch-action: none → pan-y. Sur la carte d'origine
      (256 × 320 px) l'interdiction est sans conséquence ; ici le
      cadre occupe presque tout le hero au téléphone et `none`
      empêcherait de faire défiler la page. `pan-y` rend le
      défilement vertical au navigateur.

   3. --base: 220 → 52 et --spread: 200 → 0. Le jaune Renault
      (rgb 239 223 0) vaut hsl(56 100% 47%) ; on garde 52 —
      légèrement plus chaud que le jaune pur, la lueur reste dans
      la famille sans virer au citron — et l'écart de teinte tombe
      à 0 pour que la lueur reste jaune où que soit le curseur.

   4. -webkit-backdrop-filter ajouté à côté de backdrop-filter.
      Préfixe de compatibilité, aucun effet sur le rendu.

   5. NOUVEAU EN v5 — les quatre valeurs de LUMIÈRE du composant
      passent en jetons de ton (--spot-backdrop, --bg-spot-opacity,
      --border-light-opacity, ombre). Motif : le composant est
      dessiné pour un fond noir, où une nimbe blanche à 100 %
      creuse le cadre. Sur blanc, cette même nimbe est invisible et
      la lueur jaune à 10 % n'existe pas. Sur clair on coupe donc
      la nimbe blanche (--border-light-opacity: 0) et on monte la
      lueur jaune à 34 % ; sur sombre, les valeurs d'origine sont
      restituées à l'identique. La GÉOMÉTRIE, elle, est intacte.

   6. `will-change: filter` retiré de l'enfant de nimbe. Dans le
      composant d'origine le flou ne s'anime jamais non plus — la
      déclaration y était déjà sans objet. Ici elle coûte : elle
      promeut en permanence une couche de la taille du cadre,
      floutée à 30 px, sur le tout premier écran, et c'est
      précisément là que le budget d'un téléphone est le plus
      serré. `will-change` est une réservation pour un changement à
      VENIR ; sans changement, c'est une réservation qu'on ne libère
      jamais. Aucun effet sur le rendu.

   NOTE — un affinage de l'encadrement (anneau 1px, passe-partout
   10px) a été essayé puis ABANDONNÉ sur retour : la bordure
   épaisse d'origine est préférée. Ne pas la réaffiner sans
   demande explicite.

   ATTENTION — `background-attachment: fixed` est le cœur de
   l'effet : c'est lui qui place le dégradé dans l'espace du
   VIEWPORT, donc pile sous le curseur. Il cesse de fonctionner si
   un ANCÊTRE du cadre porte transform, filter, perspective,
   backdrop-filter ou will-change. D'où : aucune parallaxe sur le
   cadre, et une ouverture animée en opacité / clip-path
   uniquement, jamais en transform.
   ============================================================ */

/* --- getInlineStyles() : l'élément porteur, et lui seul --- */
.hero-frame {
  --base: 52;
  --spread: 0;
  --radius: 14;
  --border: 3;
  --backdrop: var(--spot-backdrop);
  --backup-border: var(--backdrop);
  --size: 200;
  --outer: 1;
  --border-size: calc(var(--border, 2) * 1px);
  --spotlight-size: calc(var(--size, 150) * 1px);
  --hue: calc(var(--base) + (var(--xp, 0) * var(--spread, 0)));

  background-image: radial-gradient(
    var(--spotlight-size) var(--spotlight-size) at
    calc(var(--x, 0) * 1px)
    calc(var(--y, 0) * 1px),
    hsl(var(--hue, 210) calc(var(--saturation, 100) * 1%) calc(var(--lightness, 70) * 1%) / var(--bg-spot-opacity, 0.1)), transparent
  );
  background-color: var(--backdrop, transparent);
  background-size: calc(100% + (2 * var(--border-size))) calc(100% + (2 * var(--border-size)));
  background-position: 50% 50%;
  background-attachment: fixed;
  border: var(--border-size) solid var(--backup-border);
  position: relative;
  touch-action: pan-y; /* écart n° 2 */

  /* classes Tailwind du composant, résolues */
  border-radius: 1rem;
  display: grid;
  grid-template-rows: 1fr auto;
  gap: 1rem;
  padding: 1rem;
  box-shadow: var(--elev-3);
  -webkit-backdrop-filter: blur(5px);
          backdrop-filter: blur(5px);
  transition: box-shadow .45s var(--ease), border-color .45s var(--ease);
}

/* Survol du cadre : l'ombre s'approfondit et l'anneau se teinte.
   Sur clair le jaune est franc (55 %) — un anneau à 18 % sur du
   blanc ne se voit pas. Au doigt, rien de tout cela : sans la
   barrière, l'anneau jaune restait allumé après un simple
   glissement de photo, et ne s'éteignait plus. */
@media (hover: hover) and (pointer: fine) {
  .hero-frame:hover {
    --backup-border: rgb(var(--yellow-rgb) / .55);
    box-shadow: var(--elev-3), 0 0 34px -10px rgb(var(--yellow-rgb) / .5);
  }
  [data-tone="dark"] .hero-frame:hover { --backup-border: rgb(var(--yellow-rgb) / .18); }
}

/* --- beforeAfterStyles : feuille globale, à l'identique --- */
[data-spotlight]::before,
[data-spotlight]::after {
  pointer-events: none;
  content: "";
  position: absolute;
  inset: calc(var(--border-size) * -1);
  border: var(--border-size) solid transparent;
  border-radius: calc(var(--radius) * 1px);
  background-attachment: fixed;
  background-size: calc(100% + (2 * var(--border-size))) calc(100% + (2 * var(--border-size)));
  background-repeat: no-repeat;
  background-position: 50% 50%;
  mask: linear-gradient(transparent, transparent), linear-gradient(white, white);
  mask-clip: padding-box, border-box;
  mask-composite: intersect;
}

[data-spotlight]::before {
  background-image: radial-gradient(
    calc(var(--spotlight-size) * 0.75) calc(var(--spotlight-size) * 0.75) at
    calc(var(--x, 0) * 1px)
    calc(var(--y, 0) * 1px),
    hsl(var(--hue, 210) calc(var(--saturation, 100) * 1%) calc(var(--lightness, 50) * 1%) / var(--border-spot-opacity, 1)), transparent 100%
  );
  filter: brightness(2);
}

[data-spotlight]::after {
  background-image: radial-gradient(
    calc(var(--spotlight-size) * 0.5) calc(var(--spotlight-size) * 0.5) at
    calc(var(--x, 0) * 1px)
    calc(var(--y, 0) * 1px),
    hsl(0 100% 100% / var(--border-light-opacity, 1)), transparent 100%
  );
}

[data-spotlight] [data-spotlight] {
  position: absolute;
  inset: 0;
  /* will-change: filter — RETIRÉ, cf. écart n° 6 du bloc PORTAGE. */
  opacity: var(--outer, 1);
  border-radius: calc(var(--radius) * 1px);
  border-width: calc(var(--border-size) * 20);
  filter: blur(calc(var(--border-size) * 10));
  background: none;
  pointer-events: none;
  border: none;
}

[data-spotlight] > [data-spotlight]::before {
  inset: -10px;
  border-width: 10px;
}

/* ============================================================
   Le hero : 55 % de typographie, 45 % de photographie
   ============================================================ */
.hero-inner {
  position: relative; z-index: 2;
  display: grid; grid-template-columns: 55fr 45fr;
  gap: clamp(28px, 4.4vw, 72px);
  align-items: center;
}

/* La photographie déborde sur la marge de droite, et s'y aligne PAR
   LA DROITE. C'est ce débord qui l'agrandit et l'écarte du texte
   sans rien prendre à la colonne de gauche.

   Le débord est borné par la place réellement libre : une
   demi-gouttière, plus le jeu de centrage s'il existe, le tout
   plafonné à une gouttière. Le max() interne n'est pas décoratif :
   sous --wrap le jeu de centrage devient NÉGATIF et, sans plancher
   propre, il annulait la demi-gouttière. Chaque terme a donc son
   propre plancher.

   Le tirage descend en outre de --stage-drop sous le centre optique.
   Deux colonnes centrées l'une sur l'autre partagent le même axe :
   c'est la mise en page la plus courante du web, et elle ne produit
   aucune tension. Vingt-cinq pixels suffisent à créer une DIAGONALE
   de lecture — exergue en haut à gauche, titre, bouton en bas à
   gauche, tirage à droite et plus bas — et à dégager le haut du
   cadre de l'en-tête, contre laquelle il était collé. */
.hero-stage {
  /* Plafond porté d'une gouttière à 1,4 : sur un grand écran il
     restait 340 px de marge libre à droite et le tirage n'y touchait
     pas. Il sort maintenant franchement de la grille de texte —
     c'est le débord qui dit que la mise en page a été DÉCIDÉE. Sous
     --wrap le calcul est inchangé : le plafond n'y est jamais
     atteint, c'est la place réellement libre qui commande. */
  --frame-shift: min(max(0px, (100vw - var(--wrap)) / 2) + var(--gutter) / 2, var(--gutter) * 1.4);
  --stage-drop: clamp(0px, 2.6vh, 30px);
  /* PIÈGE — .hero-frame RÉASSIGNE --radius et --border pour le
     portage Spotlight, et leur donne des nombres SANS UNITÉ (14 et
     3, tels quels dans le composant d'origine). Tout descendant du
     cadre qui écrit `border-radius: var(--radius)` reçoit donc « 14 »
     — invalide, donc 0 — et `1px solid var(--border)` devient
     « 1px solid 3 », déclaration invalide, donc pas de bordure du
     tout. On capture ici, en AMONT de la réassignation, les vraies
     valeurs de ton, sous des noms que le cadre ne touche pas. */
  --plate-radius: var(--radius);
  --plate-line: var(--border);
  position: relative; display: flex; justify-content: flex-end;
  margin-right: calc(-1 * var(--frame-shift));
  margin-top: var(--stage-drop);
}

/* La photographie est montrée ENTIÈRE : le cadre est donc esclave du
   rapport 3/4 de la source. On borne d'abord la hauteur au viewport,
   puis on en DÉDUIT la largeur — l'inverse déborderait sur un écran
   bas. --frame-chrome = les deux passe-partout (2 × 1rem) plus les
   deux bordures. */
.hero-frame {
  --frame-chrome: calc(2rem + 2 * var(--border-size));
  /* La hauteur disponible, sans approximation : l'écran, moins
     l'en-tête, moins les deux marges du hero, moins la descente du
     tirage. Les mêmes jetons que le padding de .hero — c'est ce qui
     referme l'arithmétique et fait tenir l'ouverture dans un écran.
     Le plafond de 780 px empêche le tirage de devenir un mur sur un
     grand moniteur. */
  --frame-max-h: min(
    100svh - 82px - var(--hero-pad-top) - var(--hero-pad-bot) - var(--stage-drop),
    780px
  );
  width: min(100%, calc((var(--frame-max-h) - var(--frame-chrome)) * 3 / 4 + var(--frame-chrome)));
}
/* Au bureau la deuxième rangée du composant (grid-rows-[1fr_auto])
   reste vide — les flèches flottent au-dessus de la photographie. On
   la replie pour que le passe-partout reste d'épaisseur égale sur
   les quatre côtés. Elle reprend du service au doigt. */
@media (min-width: 861px) {
  .hero-frame { grid-template-rows: 1fr; gap: 0; }
}

/* ---------- La fenêtre et le film des trois photographies ---------- */
.frame-window {
  position: relative; overflow: hidden;
  aspect-ratio: 3 / 4;
  /* Rayons concentriques : rayon extérieur (14) moins une part du
     passe-partout. Les deux arrondis restent parallèles. */
  border-radius: calc(var(--radius) * 1px - 6px);
  background: var(--paper-2);
}
/* Le cadre ne bouge jamais : c'est le film qui coulisse dedans.
   Glissement franc, aucun fondu.

   --drag est la course du DOIGT en cours (script.js), ajoutée à la
   position de repos. C'est ce terme qui fait que le film suit la
   main au lieu d'attendre qu'on la relâche : pendant le geste, le
   film est sous le doigt, pas après lui. */
.frame-track {
  display: flex; width: 100%; height: 100%;
  transform: translate3d(calc(var(--i, 0) * -100% + var(--drag, 0px)), 0, 0);
}
.has-motion .frame-track { transition: transform 820ms var(--ease); }
/* Pendant le geste, aucune transition : sinon le film traîne
   derrière le doigt de 820 ms — la sensation exacte du plastique.
   Au relâché, deux temps DIFFÉRENTS : si la photo change, elle
   prend les 820 ms du glissement ; si elle revient en place, elle
   claque en 320 ms. Le système répond toujours plus vite qu'il ne
   raconte. */
.hero-frame.is-dragging .frame-track { transition: none; }
.hero-frame.is-settling .frame-track { transition: transform 320ms var(--ease); }
/* Chaque photogramme ROGNE son propre contenu : sans cela, les
   mises à l'échelle s'appliquent aussi aux photogrammes VOISINS et
   laissent voir un bandeau de la photo suivante. L'overflow de
   .frame-window n'y peut rien — ces pixels sont à l'INTÉRIEUR de la
   fenêtre. */
.frame-shot { flex: 0 0 100%; height: 100%; margin: 0; overflow: hidden; }
.frame-shot img {
  width: 100%; height: 100%; object-fit: cover;
  /* Plus de brightness(.9) : la v4 assombrissait les clichés pour
     les faire tenir dans une page noire. Une page claire n'a plus
     à les éteindre — le ciel d'été et le jaune du R5 sont
     exactement ce qui rend l'ouverture accueillante. */
  filter: var(--photo-filter);
  /* Les DEUX mises à l'échelle vivent ici, dans l'élément rogné —
     un rogneur ne peut pas se rogner lui-même. Elles restent
     indépendantes car elles empruntent deux propriétés distinctes :
     `transform` pour l'ouverture (2,4 s), `scale` pour le survol
     (0,8 s). Chacune sa transition. */
  transition: transform 1.7s var(--ease), scale .8s var(--ease),
              filter .8s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .hero-frame:hover .frame-shot img { scale: 1.03; }
}

/* ---------- Repères de navigation ----------
   Invisibles au repos. L'opacité est pilotée par la PROXIMITÉ du
   curseur au bord correspondant (--nav-prev / --nav-next, posées
   par script.js) : la flèche se découvre en approchant. Jamais
   annoncée, toujours trouvable.

   La pastille est claire et le chevron NOIR : sur une photographie
   dont le haut est un ciel et le bas un bitume, un glyphe blanc
   disparaît une fois sur deux. Le contraste est porté par la
   pastille, pas par la photo. */
.frame-nav {
  position: absolute; top: 50%; z-index: 2;
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, .9);
  color: var(--ink); cursor: pointer;
  box-shadow: 0 2px 10px -4px rgba(0, 0, 0, .45);
  transition: opacity .32s var(--ease), translate .32s var(--ease),
              background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
/* .hero-frame étant une grille, l'enfant absolu se réfère à sa
   PREMIÈRE ZONE DE GRILLE — soit exactement la fenêtre photo. Ces
   12px sont donc 12px dans la photographie, passe-partout exclu. */
.frame-nav-prev { left: 12px; opacity: var(--nav-prev, 0); translate: calc((1 - var(--nav-prev, 0)) * -8px) -50%; }
.frame-nav-next { right: 12px; opacity: var(--nav-next, 0); translate: calc((1 - var(--nav-next, 0)) * 8px) -50%; }
.frame-nav svg { transition: transform var(--dur-fast) var(--ease); }
@media (hover: hover) and (pointer: fine) {
  .frame-nav:hover { background: var(--yellow); box-shadow: 0 4px 18px -4px rgba(0, 0, 0, .5); }
  .frame-nav-next:hover svg { transform: translateX(1.5px); }
  .frame-nav-prev:hover svg { transform: translateX(-1.5px); }
}
/* Au clavier la flèche doit exister : le focus la révèle
   entièrement, glissement compris. */
.frame-nav-prev:focus-visible,
.frame-nav-next:focus-visible { opacity: 1; translate: 0 -50%; }

/* Deuxième rangée : repères permanents, au doigt seulement */
.frame-ticks { display: none; justify-content: center; gap: 7px; }
.frame-tick { display: block; width: 24px; height: 3px; border-radius: 3px; background: var(--tick-off); transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease); }
.frame-tick.is-on { background: var(--yellow); box-shadow: var(--glow-line); }

/* ---------- La plaque : la note Google, à cheval sur le cadre ----------
   Le CHEVAUCHEMENT est tout le sujet. Une plaque posée à côté du
   cadre serait un quatrième bloc à lire ; à cheval sur son angle,
   elle devient une COUTURE : elle prouve que le tirage et la
   colonne de texte appartiennent à la même page, et elle donne au
   cadre une profondeur qu'aucune ombre ne donne — un objet qui en
   recouvre un autre est devant lui, l'œil n'a pas besoin qu'on le
   lui dise.

   Elle sort par l'angle INFÉRIEUR GAUCHE, choisi et non par
   défaut : c'est l'angle qui regarde le bouton. Et sur les trois
   photographies, le bas-gauche est du sol — bitume ou bordure.
   Jamais un visage, jamais l'enseigne. */
.frame-plate {
  position: absolute; z-index: 3;
  left: -30px; bottom: -26px;
  display: inline-flex; align-items: center; gap: 12px;
  padding: 12px 18px;
  border-radius: var(--plate-radius);   /* cf. le PIÈGE, sur .hero-stage */
  background: var(--glass-solid);
  -webkit-backdrop-filter: blur(6px);
          backdrop-filter: blur(6px);
  border: 1px solid var(--plate-line);
  box-shadow: var(--elev-3);
  white-space: nowrap;
  /* Elle ne se clique pas, elle ne se survole pas, elle ne prend
     rien. Sans cela : la lueur du Spotlight se figeait dès que le
     curseur passait dessus, et au doigt le geste de glissement
     mourait sur elle (touch-action ne s'hérite pas — la plaque
     serait repassée en `auto` au milieu du cadre). Aucun effet sur
     les lecteurs d'écran, qui lisent l'arbre, pas les pointeurs. */
  pointer-events: none;
}
.frame-plate .stars { font-size: .82rem; }
.frame-plate-note { display: inline-flex; align-items: baseline; gap: 7px; font-size: var(--fs-3xs); color: var(--text-faint); }
.frame-plate-note strong { font-size: var(--fs-sm); font-weight: 700; color: var(--text); letter-spacing: var(--track-soft); }

/* ---------- Ouverture : le cadre se pose, puis la photo ----------
   Aucun transform sur .hero-frame ni sur ses ancêtres (cf.
   l'avertissement du bloc PORTAGE) : le corps du cadre monte en
   opacité, le liseré se trace du haut vers le bas, la photographie
   se dévoile derrière lui et se pose enfin en échelle. Le repos des
   clip-path est large (-24px) pour ne jamais rogner le débord de
   3 px du liseré.
   La liste de transitions REMPLACE celle de la règle de base : les
   micro-interactions (ombre, anneau) doivent y être reconduites,
   sinon elles deviennent instantanées dès que .has-motion est
   posée. */
.has-motion .hero-frame {
  opacity: 0;
  transition: opacity .68s var(--ease) .08s,
              box-shadow .45s var(--ease), border-color .45s var(--ease);
}
.has-motion .hero.in .hero-frame { opacity: 1; }
.has-motion .hero-frame::before,
.has-motion .hero-frame::after {
  clip-path: inset(-24px -24px 100% -24px);
  transition: clip-path .88s var(--ease) .2s;
}
.has-motion .hero.in .hero-frame::before,
.has-motion .hero.in .hero-frame::after { clip-path: inset(-24px); }
/* La photographie est l'élément le plus lourd du premier écran :
   elle finissait de se dévoiler à 1,55 s, soit bien après que
   l'œil l'ait cherchée. Elle est entière à 1,15 s. */
.has-motion .frame-window { clip-path: inset(0 0 100% 0); transition: clip-path .85s var(--ease) .3s; }
.has-motion .hero.in .frame-window { clip-path: inset(0); }
/* L'échelle d'ouverture porte sur l'IMAGE, pas sur le photogramme :
   celui-ci rogne, il ne bouge plus. La transition n'est pas
   redéclarée ici — elle est déjà dans la règle de base de
   .frame-shot img, et la redéclarer REMPLACERAIT la liste,
   emportant avec elle le survol et le filtre. */
.has-motion .frame-shot img { transform: scale(1.06); }
.has-motion .hero.in .frame-shot img { transform: scale(1); }

/* Hiérarchie : titre 700 noir −.03em, « à Neulise. » en 400 gris
   chaud (em), sous-titre 44ch.
   PAS de line-height propre : le titre reprend le 1.06 de h1/h2/h3,
   l'interligne d'affichage du site. Il a longtemps porté un 1.14,
   posé pour « donner de l'air sous la descendante du g d'agent ».
   Le diagnostic était faux : ce qui écartait « à Neulise. » n'était
   pas l'interligne mais la fusion des marges du masque (cf. § 8) —
   l'aération était donc payée sur TOUTES les lignes pour corriger un
   défaut qui n'existait qu'à une seule. La fusion réglée, le 1.14
   n'avait plus d'objet : il ne faisait plus que délier la phrase.
   Mesuré à 1440 px : le blanc sous le « g » vaut encore 13,5 px, et
   le masque garde 32 px de réserve — rien ne se touche, rien ne se
   rogne.
   Le titre tient la moitié gauche : sa borne haute (5,2rem) est
   calée pour que « Votre agent Renault » s'arrête vers 60 % de la
   largeur. Le min() ajoute un plafond en vh : sur un écran bas,
   c'est la hauteur et non la largeur qui commande. */
.hero-title { font-size: min(clamp(2.6rem, 5.2vw, 5.2rem), 12.4vh); font-weight: 700; letter-spacing: var(--track-display); margin-top: clamp(12px, 2.4vh, 26px); }

/* ---------- « à Neulise. » : la signature éditoriale ----------
   Composée avec les seuls moyens de NouvelR :
     • graisse 400 contre le 700 de la ligne 1 — le contraste que le
       site emploie DÉJÀ pour toutes ses emphases (.section-title em).
     • --ink-warm — gris chaud, pas le gris neutre du corps de texte.
     • .88em : le seul écart voulu, celui qui fait peser la ligne 2
       moins lourd.
     • approche --track-soft : une droite n'a pas besoin d'être
       desserrée comme une italique.

   LE .88em EST PORTÉ PAR LA LIGNE, PAS PAR L'EM. Sur l'em, il ne
   réduisait que les lettres : la boîte de ligne, elle, restait à la
   taille pleine du titre, parce que c'est la plus grande hauteur en
   présence qui la fixe. « à Neulise. » flottait donc au fond d'une
   ligne trop haute pour lui, décalé vers le bas de 9 px — l'exact
   complément des 12 px de marges fusionnées, et la seconde moitié de
   l'écart que l'œil lisait. Sur la ligne, la boîte suit les lettres.
   Bénéfice de surcroît : le masque .line, dont les réserves sont en
   em, se met lui aussi à l'échelle des glyphes qu'il rogne. */
.hero-title .line:nth-child(2) { font-size: .88em; }
.hero-title em {
  font-weight: 400;
  letter-spacing: var(--track-soft);
  color: var(--ink-warm);
}

/* Lueur qui traverse les lettres au survol du titre : dégradé
   découpé dans le texte (background-clip: text), balayage unique
   par ligne, 2e ligne décalée. Sur clair, le balayage ne peut plus
   ÉCLAIRCIR (l'encre est déjà noire sur blanc) : il passe par un or
   sombre — un reflet métallique sur du noir, exactement l'inverse
   du geste v4, et la même sensation. */
@supports (-webkit-background-clip: text) or (background-clip: text) {
  .hero-title .line:nth-child(1) .line-inner { --sheen-from: var(--text); --sheen-to: var(--sheen-hot); }
  .hero-title .line:nth-child(2) .line-inner { --sheen-from: var(--ink-warm); --sheen-to: var(--sheen-hot); }
  .hero-title .line-inner {
    color: transparent;
    background-image: linear-gradient(105deg, var(--sheen-from) 0 41%, var(--sheen-to) 50%, var(--sheen-from) 59% 100%);
    /* 300 % et non 220 %. À 220 %, la fenêtre visible au repos
       (0 → 45 % du dégradé) mordait déjà sur la bande chaude
       (41 → 59 %) : les dernières lettres du titre étaient teintées
       d'or EN PERMANENCE, sans que personne survole quoi que ce soit.
       Visible surtout quand la ligne 1 tient sur toute la largeur.
       À 300 %, le repos montre 0 → 33 %, la fin montre 67 → 100 % :
       la bande chaude n'est visible QUE pendant le balayage. */
    background-size: 300% 100%; background-position: 0% 0; background-repeat: no-repeat;
    -webkit-background-clip: text; background-clip: text;
  }
  .hero-title em { color: inherit; }
  @media (hover: hover) and (pointer: fine) {
    .hero-title:hover .line-inner { animation: hero-sheen 1.5s var(--ease) both; }
    .hero-title:hover .line:nth-child(2) .line-inner { animation-delay: .12s; }
  }
  @keyframes hero-sheen { from { background-position: 0% 0; } to { background-position: 100% 0; } }
}
/* Le paragraphe remonte (2,6vh → 1,9vh). Ce n'est pas un réglage de
   confort : c'est la conséquence obligée du titre resserré. Le rythme
   d'un bloc se lit en ÉCARTS RELATIFS, jamais en valeurs absolues —
   un intervalle inchangé à côté d'un intervalle réduit de moitié se
   met à peser le double. Blancs optiques mesurés à 1440 × 900, du
   plus serré au plus large : 13,5 et 25,4 px dans la phrase, 35,8 px
   jusqu'au paragraphe, 46,9 px depuis l'exergue. L'échelle est donc
   strictement croissante — la phrase se tient, le paragraphe s'y
   rattache, l'exergue reste à part. C'est la seule chose à vérifier
   si l'un de ces trois réglages bouge un jour. */
.hero-sub { margin-top: clamp(12px, 1.9vh, 22px); font-size: clamp(1.02rem, 1.4vw, 1.14rem); line-height: 1.7; color: var(--text-dim); max-width: 46ch; }
/* Une phrase par ligne : le métier d'abord, l'ancrage ensuite.
   Deux BLOCS et non un <br /> — la coupure appartient alors à la
   structure de la phrase, pas à une largeur d'écran donnée, et
   chaque phrase garde le droit de se replier pour son compte sur
   un téléphone. `text-wrap: pretty` est hérité de .hero-sub : les
   deux lignes sont protégées des orphelines sans être redéclarées.
   46ch et non 44 : c'est la mesure qui fait tenir « Renault et
   toutes marques, au 661 Rue de la République. » en deux lignes
   franches au lieu de trois avec « République. » seule au bas.

   `balance` ET NON `pretty` — inerte au bureau, où chaque phrase
   tient sur une ligne, mais décisif au téléphone. `pretty`, hérité
   de .hero-sub, ne défend que contre l'orpheline d'UN mot : à
   390 px il laissait passer « … au 661 Rue de / la République. »,
   deux mots au bas d'une ligne quasi vide. `balance` répartit les
   deux lignes à longueur égale — « Renault et toutes marques, /
   au 661 Rue de la République. » — et l'adresse cesse d'être
   coupée en plein milieu. C'est possible ICI et pas ailleurs
   parce que la propriété s'applique à un bloc court : deux ou
   trois lignes, ce que les navigateurs équilibrent. */
.hero-sub > span { display: block; text-wrap: balance; }

/* ---------- Bandeau de confiance ----------
   Il était une énumération : trois éléments de tailles inégales
   séparés par des pastilles, jetés sous les boutons — le genre de
   ligne qu'on ajoute à la fin et qui se voit. Il devient une BANDE :
   un filet la déclare, trois cellules égales lui donnent son rythme,
   des filets verticaux la découpent, un pictogramme ouvre chaque
   cellule. Trois colonnes exactement égales alors que les libellés
   ne le sont pas : c'est la grille qui fait la régularité, jamais le
   texte.

   ET IL PASSE AVANT LES BOUTONS. On donne les raisons d'y croire,
   puis le geste — jamais l'inverse. Bénéfice de mise en page en
   prime : l'élément le plus lourd de la colonne, le bouton jaune,
   descend au point bas, là où le poids doit se trouver.

   DISCIPLINE DU JAUNE — les séparateurs étaient des pastilles
   jaunes. Ils ne le sont plus, et les pictogrammes ne le sont pas
   non plus. Dans le premier écran, le jaune saturé doit désigner UNE
   chose : le bouton. Un séparateur est un signe de ponctuation, il
   ne veut rien dire ; le peindre en jaune volait de l'attention au
   seul geste qui compte. Le filet de l'exergue (repère de section,
   système global), le rail de défilement (il indique) et le repère
   de photo active (il informe) restent jaunes : chacun DIT quelque
   chose. C'est la lecture Renault de l'accent — rare, donc lu. */
.hero-trust {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: clamp(24px, 3.8vh, 42px);
  padding-top: clamp(15px, 2.2vh, 21px);
  border-top: 1px solid var(--border);
  font-size: var(--fs-2xs); color: var(--text-dim);
  max-width: 600px;
}
.hero-trust li { display: flex; align-items: center; gap: 9px; min-width: 0; padding-inline: 17px; line-height: 1.35; }
.hero-trust li:first-child { padding-inline-start: 0; }
.hero-trust li + li { border-left: 1px solid var(--border); }
.hero-trust svg { flex: none; color: var(--text-faint); }

/* ---------- Pied de colonne : la preuve, puis les gestes ----------
   Une enveloppe sans dessin. `display: block` au bureau la rend
   RIGOUREUSEMENT invisible en mise en page : sans bordure ni
   garniture, la marge haute de .hero-trust la traverse par fusion
   et s'applique exactement où elle s'appliquait avant. Elle
   n'existe que pour la permutation du téléphone (§ responsive) —
   et pour épargner à .hero-body de devenir un contexte de
   formatage indépendant, ce qui aurait décalé tout le bloc de
   20,2 px en libérant les marges négatives des masques du titre. */
.hero-foot { display: block; }

/* TROIS boutons de même hauteur, et un ordre qui vaut hiérarchie :
   « Nos véhicules », « Prendre rendez-vous », « Appeler ». Ils
   tiennent en deux rangées au bureau — la paire des deux gestes
   d'entrée, puis le numéro en dessous, à sa place de recours.
   C'est le repli naturel de la boîte flexible, pas une grille
   fabriquée : la colonne fait 589 px, les deux premiers 460, le
   troisième n'y entre pas et va à la ligne. Le jour où un libellé
   change, la rangée se recompose toute seule.

   L'écart au bandeau de preuve est plus grand que l'écart INTERNE
   au bandeau : le rythme vertical se lit par contraste d'espaces,
   pas par une suite d'intervalles voisins. */
.hero-actions { display: flex; align-items: center; gap: 16px; margin-top: clamp(26px, 4.4vh, 46px); flex-wrap: wrap; }

/* UNE SEULE HAUTEUR POUR LES TROIS. Elle ne s'obtenait pas toute
   seule : la hauteur d'un `.btn` est celle de son plus grand
   enfant, et les trois pictogrammes ne font pas la même taille
   (voiture 18, flèche 17, combiné 16). Résultat mesuré avant
   correction : 60, 59 et 58 px — trois boutons presque alignés,
   c'est-à-dire un défaut que l'œil sent sans pouvoir le nommer.
   À deux boutons l'écart d'un pixel passait ; à trois, sur deux
   rangées, il ne passe plus.
   Le plancher est posé sur la RANGÉE et non sur chaque gabarit :
   c'est le fait d'être ensemble qui crée l'obligation, et les
   pictogrammes gardent chacun la taille à laquelle ils ont été
   dessinés. */
.hero-actions .btn { min-height: 60px; }

/* ---------- Indice de défilement (bas à gauche, sous le texte) ---------- */
.hero-scroll {
  position: absolute; z-index: 3; bottom: clamp(30px, 5.4vh, 58px);
  left: max(var(--gutter), calc(50% - var(--wrap) / 2 + var(--gutter)));
  display: inline-flex; align-items: center; gap: 16px;
  font-size: var(--fs-3xs); letter-spacing: var(--track-caps-wide); text-transform: uppercase;
  color: var(--text-faint); transition: color var(--dur) var(--ease);
}
.hero-scroll:hover { color: var(--text); }
.hero-scroll-rail { position: relative; display: block; width: 56px; height: var(--rule); border-radius: calc(var(--rule) / 2); background: var(--border); overflow: hidden; }
.hero-scroll-rail span { position: absolute; inset: 0; background: var(--yellow); box-shadow: var(--glow-line); }
/* Une lumière traverse le filet vers la droite, puis marque un
   temps : même langage que `.eyebrow-line`, en mouvement. */
.has-motion .hero-scroll-rail span { animation: rail-run 2.8s var(--ease) infinite; }
@keyframes rail-run {
  0%       { transform: translateX(-100%); }
  55%,100% { transform: translateX(100%); }
}

/* ---------- Chorégraphie d'ouverture (.hero.in, posée par script.js) ----------
   La photographie arrive la première (elle est le sujet), la
   typographie se pose dessus, les commandes ferment la marche.

   RESSERRÉE — la même chorégraphie, dans les temps. Elle se posait
   en 1,86 s (dernier retardement 0,96 s + 0,9 s de course) ; elle
   se pose maintenant en 1,32 s. L'ordre, les écarts relatifs et le
   sens de lecture sont intacts : on a seulement cessé de faire
   attendre. Une ouverture lente ne se lit pas comme du soin, elle
   se lit comme un site qui charge — et c'est le tout premier écran
   qui donne le tempo de tout le reste. */
.hero .section-num, .hero-sub, .hero-actions, .hero-trust, .hero-scroll {
  opacity: 0; transform: translateY(16px);
  transition: opacity .62s var(--ease), transform .62s var(--ease);
}
.hero.in .section-num { opacity: 1; transform: none; transition-delay: .18s; }
.hero.in .hero-sub    { opacity: 1; transform: none; transition-delay: .38s; }
.hero.in .hero-trust  { opacity: 1; transform: none; transition-delay: .48s; }
.hero.in .hero-actions{ opacity: 1; transform: none; transition-delay: .58s; }
.hero.in .hero-scroll { opacity: 1; transform: none; transition-delay: .70s; }
.hero .line-inner { transition-delay: .24s; }
.hero .line:nth-child(2) .line-inner { transition-delay: .32s; }

/* ---------- Le décor entre le premier, et par en dessous ----------
   Le panneau se déroule du haut (clip-path), les filets se tracent
   du haut, décalés de 30 ms — assez pour qu'on sente un sens de
   lecture, trop peu pour qu'on compte les traits. Ils PRÉCÈDENT
   tout le reste : la scène se dresse d'abord, les objets s'y posent
   ensuite. C'est l'ordre d'un plateau, pas d'une liste.

   La plaque ferme la marche à 1,33 s, après que le tirage est
   entier : un objet ne peut pas chevaucher un cadre qui n'est pas
   encore là. */
.has-motion .hero-panel { clip-path: inset(0 0 100% 0); transition: clip-path .92s var(--ease) .06s; }
.has-motion .hero.in .hero-panel { clip-path: inset(0); }
.has-motion .hero-rules i { transform: scaleY(0); transform-origin: 50% 0; transition: transform .8s var(--ease); }
.has-motion .hero.in .hero-rules i { transform: none; }
.has-motion .hero.in .hero-rules i:nth-child(1) { transition-delay: .10s; }
.has-motion .hero.in .hero-rules i:nth-child(2) { transition-delay: .13s; }
.has-motion .hero.in .hero-rules i:nth-child(3) { transition-delay: .16s; }
.has-motion .hero.in .hero-rules i:nth-child(4) { transition-delay: .19s; }
.has-motion .hero.in .hero-rules i:nth-child(5) { transition-delay: .22s; }
.has-motion .hero.in .hero-rules i:nth-child(6) { transition-delay: .25s; }
.has-motion .hero.in .hero-rules i:nth-child(7) { transition-delay: .28s; }
.has-motion .frame-plate {
  opacity: 0; transform: translateY(10px);
  transition: opacity .55s var(--ease), transform .55s var(--ease);
}
.has-motion .hero.in .frame-plate { opacity: 1; transform: none; transition-delay: .78s; }

/* ---------- Dérive du panneau au défilement ----------
   Une profondeur ne se démontre pas, elle se vérifie : deux plans
   qui se décalent l'un par rapport à l'autre quand on défile sont
   perçus comme deux distances. Trois pour cent, aucune image ne
   bouge, et le cadre encore moins — LE PANNEAU EST UN FRÈRE, jamais
   un ancêtre (bloc PORTAGE). Pilotée par la timeline de défilement
   du navigateur : aucun JavaScript, aucun écouteur de scroll, et
   rien du tout là où ce n'est pas implémenté. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .has-motion .hero-panel {
      animation: panel-drift linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
    @keyframes panel-drift {
      from { translate: 0 -2.4%; }
      to   { translate: 0  3.2%; }
    }
  }
}

/* ============================================================
   7 · COQUE DE SECTION
   ------------------------------------------------------------
   Le fond vient du TON, pas de la section : `.garage, .avis {
   background-color: var(--bg-2) }` de la v4 disparaît. Changer le
   battement se fait désormais dans le HTML, en changeant un
   data-tone — plus jamais en cherchant quelle règle CSS nommait
   quelle section.
   ============================================================ */
.section { position: relative; padding-block: var(--space-section); overflow: hidden; background-color: var(--bg); }
.section > .wrap { position: relative; z-index: 1; }
/* Nappe directionnelle très faible ; la position en x alterne, la
   lumière voyage donc à mesure qu'on défile. */
.section { background-image: radial-gradient(120% 90% at 72% -10%, var(--wash), transparent 55%); }
.section:nth-of-type(even) { background-image: radial-gradient(120% 90% at 28% -10%, var(--wash), transparent 55%); }
/* Filet de séparation : un cheveu du ton + un point jaune au
   centre. Entre deux sections de tons différents il ne sert à rien
   — mais il ne gêne pas non plus, et il tient les coutures
   clair/clair, qui sont désormais les plus nombreuses. */
.section::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px; background: linear-gradient(90deg, transparent, var(--border) 20%, rgb(var(--yellow-rgb) / .7) 50%, var(--border) 80%, transparent); }
.hero + .section::before { opacity: 0; }
/* Le filet s'allume depuis le centre à l'arrivée dans la section :
   la transition entre sections appartient au système de lumière
   (motion.js pose .lit ; statique sans .has-motion). */
.has-motion .section::before { transform: scaleX(0); transition: transform 1.2s var(--ease); }
.has-motion .section.lit::before { transform: scaleX(1); }

.section-head { max-width: 760px; margin-bottom: clamp(48px, 6vw, 76px); }
.section-head.center { margin-inline: auto; text-align: center; }
.section-num { margin-bottom: 22px; }
.section-head.center .section-num { justify-content: center; }
.section-title { font-size: clamp(2.2rem, 5vw, 3.8rem); font-weight: 700; letter-spacing: var(--track-display); }
/* text-wrap pretty : évite les lignes orphelines de un-deux mots */
.section-lead { margin-top: 22px; font-size: var(--fs-sm); color: var(--text-dim); max-width: 62ch; text-wrap: pretty; }
.hero-sub, .card p, .review blockquote, .services-note { text-wrap: pretty; }
.section-head.center .section-lead { margin-inline: auto; }
.section-lead strong { color: var(--text); font-weight: 700; }

/* Chiffre fantôme géant : gravé au trait, jamais rempli */
[data-ghost]::after {
  content: attr(data-ghost); position: absolute; top: clamp(20px, 4vw, 60px);
  font-family: var(--font-display); font-weight: 400; font-size: clamp(9rem, 26vw, 24rem);
  line-height: .8; z-index: 0; pointer-events: none; letter-spacing: var(--track-display);
  inset: auto clamp(-10px, 2vw, 40px) auto auto;
  color: var(--ghost-stroke);
}
@supports (-webkit-text-stroke: 1px black) {
  [data-ghost]::after { color: transparent; -webkit-text-stroke: 1px var(--ghost-stroke); }
}

/* ============================================================
   8 · RÉVÉLATIONS — masque de ligne (titres) + fondus (labeur)
   ============================================================ */
/* Le masque rogne au bord du padding. Métriques NouvelR mesurées :
   descendante (g/y/p/j/q) = .19em sous la ligne de base, accents
   (À/É) = .84em au-dessus. On ouvre l'air asymétrique — plus bas
   pour les descendantes — et on reprend la même valeur en marge
   négative (rythme vertical préservé). */
.line { display: block; overflow: hidden; padding-block: .16em .3em; margin-block: -.16em -.3em; }

/* ...SAUF QUE LA MARGE NÉGATIVE NE SE REPRENAIT PAS. Deux .line qui se
   suivent sont des frères de bloc : leurs marges verticales FUSIONNENT.
   Le navigateur ne retenait donc PAS -.16em puis -.3em, mais une seule
   valeur, la plus négative — soit -.3em. Les .16em du padding haut de
   la ligne suivante n'étaient jamais annulés et fuyaient dans la mise
   en page : 12 px de trop À CHAQUE PASSAGE À LA LIGNE, et là seulement.
   D'où un titre dont les interlignes n'étaient pas égaux — 85 px entre
   deux lignes d'un même .line, 97 px entre deux .line. Le compte est
   exactement celui du padding non annulé.

   Les marges des éléments flex ne fusionnent jamais. Une colonne flex
   suffit donc à rendre à chaque .line ses DEUX marges, et l'interligne
   redevient l'interligne partout. Rien d'autre ne bouge : les .line
   s'étirent déjà sur toute la largeur (align-items: stretch), donc le
   centrage des titres de section par text-align tient toujours. */
.hero-title, .section-title, .signature-title { display: flex; flex-direction: column; }
.line-inner { display: block; transform: translateY(150%); transition: transform .7s var(--ease); }
.line:nth-child(2) .line-inner { transition-delay: 80ms; }
.in .line-inner, .hero.in .line-inner { transform: none; }

/* Révélation par défaut : opacité seule. */
[data-reveal] { opacity: 0; transition: opacity .7s var(--ease); }
.team-photo[data-reveal], .rdv-form[data-reveal], .contact-map[data-reveal] { transform: translateY(14px); transition: opacity .7s var(--ease), transform .7s var(--ease); }
[data-reveal].in { opacity: 1; transform: none; }

/* Révélation « gravure » (cartes, avis, infos) : aucun déplacement
   — un filet jaune balaie le bord haut pendant que la carte
   apparaît, comme un trait qui s'incise. Son épaisseur suit --rule :
   3 px sur clair, 1 px sur sombre. Le stagger (script.js) fait la
   cascade. */
.card[data-reveal], .review[data-reveal], .info-card[data-reveal] {
  background-image: linear-gradient(90deg, rgb(var(--yellow-rgb)), rgb(var(--yellow-rgb) / .15));
  background-size: 0% var(--rule); background-position: top left; background-repeat: no-repeat;
  transition: opacity .7s var(--ease), background-size .9s var(--ease);
}
.card[data-reveal].in, .review[data-reveal].in, .info-card[data-reveal].in { background-size: 100% var(--rule); }
/* Stat : le tick jaune existant s'étire au lieu que le bloc ne monte */
.stat[data-reveal]::before { width: 0; transition: width .9s var(--ease) .15s; }
.stat[data-reveal].in::before { width: var(--rule-w); }
/* Les titres se révèlent par le masque, pas par le fondu */
.section-title[data-reveal] { opacity: 1; transform: none; }

/* ============================================================
   9 · SERVICES — six prestations, une grille de traits
   ------------------------------------------------------------
   Section PAPIER, cartes BLANCHES : le gap de 1px laisse voir le
   fond de la grille et dessine un quadrillage net. C'est la
   composition qui structure, pas l'ombre.
   ============================================================ */
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; }
/* background-color (jamais le raccourci) : le filet de gravure vit
   en background-image sur la même carte. */
.card { position: relative; padding: 42px 34px 46px; background-color: var(--card-bg); overflow: hidden; transition: background-color var(--dur) var(--ease); }
.card::before { content: ""; position: absolute; width: 340px; height: 340px; left: var(--mx, 50%); top: var(--my, 50%); translate: -50% -50%; background: radial-gradient(circle, rgb(var(--yellow-rgb) / .3), transparent 62%); opacity: 0; transition: opacity var(--dur) var(--ease); pointer-events: none; }
.card:hover { background-color: var(--card-bg-hover); box-shadow: inset 0 0 0 1px rgb(var(--yellow-rgb) / .55); }
.card:hover::before { opacity: 1; }
[data-tone="dark"] .card::before { background: radial-gradient(circle, rgb(var(--yellow-rgb) / .14), transparent 62%); }
[data-tone="dark"] .card:hover { box-shadow: inset 0 0 0 1px rgb(var(--yellow-rgb) / .16); }

/* Le numéro « s'allume » au survol. Sur clair, s'allumer ne veut
   plus dire devenir jaune (illisible) : il redevient NOIR et un
   surligneur jaune se déroule derrière — le geste du marqueur,
   franc et lisible. Sur sombre, la lueur v4 est conservée.

   Le surligneur est le BACKGROUND du numéro lui-même, et non un
   ::before en z-index négatif : un enfant en z-index -1 se
   glisserait derrière le fond de la carte (le premier contexte
   d'empilement au-dessus est `.section > .wrap`, pas `.card`) et
   ne se verrait jamais. Un background, lui, peint sous le texte de
   son propre élément, par construction. */
.card-num { display: inline-block; font-family: var(--font-display); font-weight: 700; font-size: var(--fs-2xs); letter-spacing: var(--track-caps); color: var(--text-faint);
  padding-inline: 4px; margin-inline: -4px;
  background-image: linear-gradient(rgb(var(--yellow-rgb)), rgb(var(--yellow-rgb)));
  background-repeat: no-repeat; background-position: 0 94%; background-size: 0% .62em;
  transition: color var(--dur) var(--ease), background-size .38s var(--ease), text-shadow var(--dur) var(--ease); }
.card:hover .card-num { color: var(--text); background-size: 100% .62em; }
[data-tone="dark"] .card-num { background-image: none; }
[data-tone="dark"] .card:hover .card-num { color: var(--yellow); text-shadow: 0 0 12px rgb(var(--yellow-rgb) / .5); }

.card h3 { font-size: var(--fs-xl); font-weight: 700; margin: 18px 0 12px; letter-spacing: var(--track-head); }
.card p { color: var(--text-dim); font-size: var(--fs-sm); }
.services-note { margin-top: 40px; color: var(--text-dim); }
.services-note.center { text-align: center; }
/* Le lien souligné de jaune : 3 px sur clair, il devient un vrai
   surligneur ; le texte reste noir, donc lisible. */
.services-note a { color: var(--text); font-weight: 700; border-bottom: var(--rule) solid rgb(var(--yellow-rgb)); padding-bottom: 1px; transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.services-note a:hover { background: rgb(var(--yellow-rgb) / .3); border-bottom-color: var(--yellow-deep); }
[data-tone="dark"] .services-note a:hover { background: transparent; box-shadow: 0 2px 12px -2px rgb(var(--yellow-rgb) / .6); }

/* ============================================================
   10 · LE GARAGE — photo d'équipe, chiffres, promesses
   ============================================================ */
.garage-grid { display: grid; grid-template-columns: .95fr 1.05fr; gap: clamp(36px, 6vw, 84px); align-items: center; }
.losange-garage { top: 50%; right: -14vw; transform: translateY(calc(-50% + var(--py, 0px))); width: clamp(280px, 38vw, 560px); will-change: transform; }

/* Photo d'équipe — seule image en paysage du site, et c'est
   délibéré : c'est le seul cliché de personnes, sa forme le
   distingue.
   CHANGEMENT v5 — la v4 la faisait se DISSOUDRE dans le noir de la
   page (masque en dégradé + voile sombre + légende gravée dessus).
   Sur blanc, une photo qui se dissout est une photo qui se salit :
   elle redevient donc une plaque franche — bord net, coin arrondi,
   ombre douce — et sa légende passe DESSOUS, en capitales grises.
   On y gagne aussi le bas du cliché, que le voile mangeait. */
.team-photo {
  position: relative; margin: 0 0 clamp(24px, 4vw, 56px);
  border-radius: var(--radius-lg); overflow: hidden;
  box-shadow: var(--elev-1);
}
.team-photo img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 4 / 3; filter: var(--photo-filter); }
.team-photo figcaption { position: absolute; left: 14px; bottom: 12px; z-index: 2; padding: 7px 12px; border-radius: var(--radius); background: var(--glass-solid); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); font-size: var(--fs-3xs); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--text); }

.stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 30px 40px; margin-top: 44px; }
.stat { border-top: 1px solid var(--border); padding-top: 18px; position: relative; }
.stat::before { content: ""; position: absolute; top: -1px; left: 0; width: var(--rule-w); height: var(--rule); border-radius: calc(var(--rule) / 2); background: var(--yellow); box-shadow: var(--glow-line); }
.stat-num { display: block; font-family: var(--font-display); font-weight: 700; font-size: clamp(2.3rem, 4.5vw, 3.2rem); color: var(--text); line-height: 1; font-variant-numeric: tabular-nums; letter-spacing: var(--track-display); }
.stat-unit { color: inherit; }
.stat-label { display: block; margin-top: 10px; font-size: var(--fs-3xs); color: var(--text-faint); text-transform: uppercase; letter-spacing: var(--track-caps); }

/* Les coches : PASTILLE jaune, glyphe noir. C'est l'usage le plus
   littéral du jaune-peinture, et le plus efficace sur blanc — un
   trait jaune de 1,8 px sur du blanc n'existe pas. Aucun balisage
   ajouté : le disque est le fond du <svg> lui-même. */
.promises { margin-top: 40px; display: grid; gap: 15px; }
.promises li { display: flex; align-items: center; gap: 14px; color: var(--text-dim); }
.promises svg, .rdv-contact svg {
  color: var(--yellow-ink); background: var(--yellow); border-radius: 50%;
  padding: 6px; box-sizing: content-box; flex: none;
}
[data-tone="dark"] .promises svg, [data-tone="dark"] .rdv-contact svg {
  color: var(--yellow); background: none; padding: 0;
  filter: drop-shadow(0 0 6px rgb(var(--yellow-rgb) / .4));
}
.garage-body .btn { margin-top: 40px; }

/* ============================================================
   11 · AVIS — la section noire
   ------------------------------------------------------------
   C'est ici que le noir revient, et c'est le seul endroit du corps
   de page où il occupe toute la largeur. Motif : la grille inclinée
   a besoin d'une profondeur (les tuiles sortent du noir par
   brightness), et une respiration sombre au milieu d'une page
   claire vaut mieux qu'une page qui n'en a aucune.
   ============================================================ */
.rating-summary { display: inline-flex; align-items: center; gap: 15px; margin-top: 26px; }
/* 3rem hors échelle : c'est du type d'AFFICHAGE, réglé pour
   équilibrer la rangée d'étoiles à côté.
   tabular-nums : la note est réécrite au chargement depuis la fiche
   Google (script.js). En chiffres proportionnels, un « 4,5 »
   remplacé par un « 4,7 » change de largeur et fait sauter les
   étoiles d'un demi-pixel. */
.rating-big { font-family: var(--font-display); font-weight: 700; font-size: 3rem; color: var(--text); line-height: 1; letter-spacing: var(--track-display); font-variant-numeric: tabular-nums; }
.stars-lg { font-size: 1.35rem; }
.rating-count { color: var(--text-faint); font-size: var(--fs-xs); }
.star-empty { color: var(--text-faint); }

/* Grille inclinée au scroll — portage fidèle de ScrollTiltedGrid
   (21st.dev, framer-motion). Structure identique à l'original :
   wrapper (perspective 900px) → carte (masque overflow + radius,
   filtres et transforms 3D) → contenu (scaleY animé, backface
   caché). La cinématique vit dans motion.js §4 — sans JS ou en
   reduced-motion cette grille est simplement statique. */
.tilt-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; width: 100%; max-width: 768px; margin: 5vh auto 0; padding: 9vh 24px 11vh; }
.tilt-tile { position: relative; z-index: 1; }
html.has-motion .tilt-tile { perspective: 900px; will-change: transform; }
/* Fond opaque obligatoire : brightness(0→1) doit lire la tuile
   comme une photo qui sort du noir, pas comme du texte sur fond
   transparent. */
.tilt-card { position: relative; overflow: hidden; aspect-ratio: 3 / 4; min-height: max-content; border-radius: var(--radius-lg); background: var(--surface); border: 1px solid var(--border); }
html.has-motion .tilt-card { will-change: filter, transform; }
/* Rotation auto (script.js) : fondu du contenu toutes les 10 s —
   opacity seule, jamais transform/filter (réservés à la cinématique
   de scroll). */
.tilt-card .review { height: 100%; border: none; border-radius: 0; backface-visibility: hidden; transition: opacity .45s var(--ease); }
.tilt-card .review.swapping { opacity: 0; }

.review { position: relative; margin: 0; padding: 36px 32px; border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; transition: border-color var(--dur) var(--ease); display: flex; flex-direction: column; }
.review::before { content: ""; position: absolute; width: 300px; height: 300px; left: var(--mx, 50%); top: var(--my, 50%); translate: -50% -50%; background: radial-gradient(circle, rgb(var(--yellow-rgb) / .12), transparent 62%); opacity: 0; transition: opacity var(--dur) var(--ease); pointer-events: none; }
.review:hover { border-color: rgb(var(--yellow-rgb) / .35); }
.review:hover::before { opacity: 1; }
.review .stars { font-size: .95rem; }
/* Ce qui distingue un avis du reste du texte n'est pas la fonte
   mais la MISE EN PAGE : un cran de corps au-dessus du labeur
   (--fs-md), l'approche resserrée des grands corps, un interlignage
   plus généreux (1.58) et l'encre pleine contre le gris des
   légendes. Les guillemets français font le reste du travail. */
.review blockquote { position: relative; z-index: 1; margin: 18px 0 24px; font-weight: 400; font-size: var(--fs-md); line-height: 1.58; letter-spacing: var(--track-soft); color: var(--text); flex: 1; }
.review figcaption { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 3px; border-top: 1px solid var(--border); padding-top: 16px; }
.review-name { font-weight: 700; font-size: var(--fs-xs); }
.review-meta { font-size: var(--fs-3xs); color: var(--text-faint); letter-spacing: var(--track-caps); text-transform: uppercase; }

/* ============================================================
   12 · RENDEZ-VOUS — le formulaire, carte blanche sur papier
   ============================================================ */
.rdv-grid { display: grid; grid-template-columns: 1fr 1.1fr; gap: clamp(36px, 6vw, 84px); align-items: start; }
.rdv-intro { position: sticky; top: 108px; }
.rdv-contact { margin-top: 42px; display: grid; gap: 20px; }
.rdv-contact li { display: flex; align-items: center; gap: 15px; }
.rdv-contact-label { display: block; font-size: var(--fs-3xs); text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--text-faint); margin-bottom: 2px; }
.rdv-contact a { font-weight: 700; border-bottom: 2px solid transparent; transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease); }
.rdv-contact a:hover { color: var(--link-hot); border-bottom-color: rgb(var(--yellow-rgb)); }

.rdv-form { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: clamp(26px, 3vw, 42px); box-shadow: var(--elev-1); }
.field { display: flex; flex-direction: column; gap: 9px; margin-bottom: 20px; position: relative; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.field label { font-size: var(--fs-2xs); letter-spacing: var(--track-ui); color: var(--text-dim); transition: color 180ms var(--ease); }
.field:focus-within label { color: var(--text); }
.req { color: var(--accent-text); font-weight: 700; }
.opt { color: var(--text-faint); }
.field input, .field select, .field textarea { font-family: var(--font-body); font-size: var(--fs-sm); color: var(--text); background: var(--bg); border: 1px solid var(--border-strong); border-radius: var(--radius); padding: 14px 16px; width: 100%; transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease); }
.field textarea { resize: vertical; min-height: 100px; }
.field input::placeholder, .field textarea::placeholder { color: var(--text-faint); }
/* Focus : DEUX signaux, jamais un seul. Le bord passe à l'encre du
   ton (c'est lui qui porte le contraste, y compris pour un œil qui
   ne distingue pas le jaune) et un halo jaune de 3 px l'entoure
   (c'est lui qui porte la marque). Sur fond noir, le bord devient
   jaune : il y a alors assez de contraste pour qu'il suffise. */
.field input:focus, .field select:focus, .field textarea:focus { outline: none; border-color: var(--text); box-shadow: 0 0 0 3px rgb(var(--yellow-rgb) / .5); }
[data-tone="dark"] .field input:focus, [data-tone="dark"] .field select:focus, [data-tone="dark"] .field textarea:focus { border-color: var(--yellow); box-shadow: var(--glow-soft); }
.field select { appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%235c5a55' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 15px center; padding-right: 44px; }
[data-tone="dark"] .field select { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%239c9992' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); }
.field input[type="date"] { color-scheme: light; }
[data-tone="dark"] .field input[type="date"] { color-scheme: dark; }
/* L'erreur est le seul rouge du site. Sur blanc il faut un rouge
   qui passe le contraste du texte (#c0392b, 5,2:1), pas le corail
   de la v4 qui n'était lisible que sur noir. */
.field.invalid input, .field.invalid select { border-color: var(--error); box-shadow: 0 0 0 3px rgb(192 57 43 / .18); }
.field-error { color: var(--error); font-size: var(--fs-2xs); font-weight: 700; }
[data-tone="dark"] .field-error { color: var(--error-soft); font-weight: 400; }
.field-consent { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 12px; }
.field-consent input { width: 20px; height: 20px; margin-top: 2px; accent-color: var(--yellow); flex: none; }
.field-consent label { font-size: var(--fs-2xs); color: var(--text-dim); }
.btn-spinner { display: none; width: 16px; height: 16px; border: 2px solid rgb(10 10 11 / .25); border-top-color: var(--yellow-ink); border-radius: 50%; animation: spin .7s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
[data-submit].loading .btn-label { opacity: .55; }
[data-submit].loading .btn-spinner { display: inline-block; }
.form-hint { margin-top: 16px; font-size: var(--fs-2xs); color: var(--text-faint); text-align: center; }
/* Le panneau de confirmation : aplat jaune très pâle, bord jaune
   franc, encre noire. Le jaune y est enfin ce qu'il doit être sur
   du clair — un fond, pas une lueur. */
.form-success { display: flex; align-items: center; gap: 16px; margin-top: 24px; padding: 22px; border-radius: var(--radius-lg); background: rgb(var(--yellow-rgb) / .18); border: 1px solid rgb(var(--yellow-rgb) / .7); color: var(--text); }
.form-success p { color: var(--text-dim); font-size: var(--fs-xs); margin-top: 4px; }
.form-success strong { color: var(--text); font-family: var(--font-display); font-size: var(--fs-md); }
.form-success a { font-weight: 700; border-bottom: 2px solid rgb(var(--yellow-rgb)); }
[data-tone="dark"] .form-success { background: rgb(var(--yellow-rgb) / .05); border-color: transparent; box-shadow: var(--glow-soft); color: var(--yellow); }
/* Le repli — même forme que la confirmation, mais un rouge sourd :
   c'est une consigne à lire, pas une alarme. */
.form-fallback { display: flex; align-items: center; gap: 16px; margin-top: 24px; padding: 22px; border-radius: var(--radius-lg); background: rgb(180 54 47 / .08); border: 1px solid rgb(180 54 47 / .5); color: var(--text); }
.form-fallback p { color: var(--text-dim); font-size: var(--fs-xs); margin-top: 4px; }
.form-fallback strong { color: var(--text); font-family: var(--font-display); font-size: var(--fs-md); }
.form-fallback a { font-weight: 700; border-bottom: 2px solid rgb(180 54 47 / .6); }
/* Le pot de miel : hors de l'écran, hors du parcours clavier, hors
   des lecteurs d'écran — mais bien présent dans le DOM du robot. */
.rdv-piege { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

/* ============================================================
   13 · CONTACT
   ============================================================ */
.losange-contact { bottom: -10vw; left: -8vw; width: clamp(300px, 40vw, 580px); transform: rotate(12deg); }
.contact-grid { display: grid; grid-template-columns: 1fr 1.15fr; gap: 24px; }
.contact-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-content: start; }
/* Tuiles PAPIER sur fond blanc (et --surface sombre sur fond noir) :
   le même jeton fait les deux, la tuile se détache toujours. */
.info-card { position: relative; padding: 28px 26px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease); }
.info-card::before { content: ""; position: absolute; width: 280px; height: 280px; left: var(--mx, 50%); top: var(--my, 50%); translate: -50% -50%; background: radial-gradient(circle, rgb(var(--yellow-rgb) / .22), transparent 62%); opacity: 0; transition: opacity var(--dur) var(--ease); pointer-events: none; }
.info-card:hover { border-color: rgb(var(--yellow-rgb) / .6); box-shadow: var(--elev-1); }
.info-card:hover::before { opacity: 1; }
.info-card > * { position: relative; z-index: 1; }
[data-tone="dark"] .info-card::before { background: radial-gradient(circle, rgb(var(--yellow-rgb) / .12), transparent 62%); }
[data-tone="dark"] .info-card:hover { border-color: rgb(var(--yellow-rgb) / .32); box-shadow: none; }
/* Le libellé était JAUNE en v4 : sur blanc il devient illisible. Il
   repasse donc à l'encre, et c'est un onglet jaune posé à sa gauche
   qui porte l'accent — même information, même couleur, autre
   support. */
.info-label { display: inline-block; font-size: var(--fs-3xs); text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--text); font-weight: 700; border-left: var(--rule) solid rgb(var(--yellow-rgb)); padding-left: 10px; }
[data-tone="dark"] .info-label { color: var(--yellow); border-left: 0; padding-left: 0; }
.info-value { margin-top: 14px; font-size: var(--fs-sm); color: var(--text); line-height: 1.55; }
.info-value a { font-weight: 700; border-bottom: 2px solid transparent; transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease); }
.info-value a:hover { color: var(--link-hot); border-bottom-color: rgb(var(--yellow-rgb)); }
.info-link { display: inline-block; margin-top: 15px; font-size: var(--fs-2xs); color: var(--text-dim); border-bottom: 2px solid transparent; transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.info-link:hover { color: var(--link-hot); border-bottom-color: rgb(var(--yellow-rgb)); }

/* ---------- Ligne directe (fiche « Dépannage ») ----------
   Un numéro de SECOURS dans une fiche d'information. Deux choses
   à tenir en même temps, et elles tirent en sens contraire : il
   doit être trouvable en une seconde par qui est au bord de la
   route, et rester derrière le rendez-vous pour tous les autres.

   La réponse n'est pas la couleur, c'est le FILET. Un trait
   horizontal détache le numéro du corps de la fiche — l'œil qui
   balaie la page voit un second niveau, donc un second sujet,
   sans qu'aucun pixel de jaune ait été dépensé. Le libellé en
   petites capitales grises le nomme, le numéro passe en 700 :
   dans la fiche il est ce qu'il y a de plus lourd, dans la page
   il ne pèse rien à côté du bouton. C'est exactement le rapport
   demandé.

   Le filet est en marge NÉGATIVE de la valeur du padding de la
   fiche : il traverse toute sa largeur, bord à bord, au lieu de
   flotter entre deux blancs. Un séparateur qui ne touche pas les
   bords ne sépare pas, il décore. */
.info-direct {
  margin-top: 18px; padding-top: 15px;
  margin-inline: -26px; padding-inline: 26px;
  border-top: 1px solid var(--border);
}
.info-direct-label { display: block; font-size: var(--fs-3xs); text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--text-faint); margin-bottom: 5px; }
.info-direct a { font-size: var(--fs-sm); font-weight: 700; color: var(--text); letter-spacing: var(--track-soft); border-bottom: 2px solid transparent; transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.info-direct a:hover { color: var(--link-hot); border-bottom-color: rgb(var(--yellow-rgb)); }
/* Pastille d'état : le point est jaune (ouvert) ou rouge (fermé),
   le texte reste à l'encre. En v4 les deux étaient de la même
   couleur — sur blanc, un « Ouvert » jaune ne se lit pas. */
.hours-live { margin-top: 14px; font-size: var(--fs-2xs); font-weight: 700; display: inline-flex; align-items: center; gap: 8px; color: var(--text); }
.hours-live::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--dot, currentColor); box-shadow: 0 0 8px var(--dot, currentColor); }
.hours-live.open { --dot: var(--yellow); }
.hours-live.closed { --dot: var(--error); }
[data-tone="dark"] .hours-live.open { color: var(--yellow); }
[data-tone="dark"] .hours-live.closed { color: var(--error-soft); --dot: var(--error-soft); }

.contact-map { position: relative; border-radius: var(--radius-lg); overflow: hidden; border: 1px solid var(--border); min-height: 400px; box-shadow: var(--elev-1); }
/* La v4 assombrissait le plan (brightness .8) pour le faire tenir
   dans une page noire. Ici on le désature seulement, et le survol
   lui rend ses couleurs. */
.contact-map iframe { width: 100%; height: 100%; min-height: 400px; border: 0; filter: grayscale(.9) contrast(1.02); transition: filter var(--dur) var(--ease); }
.contact-map:hover iframe { filter: grayscale(0) contrast(1.02); }
/* --track-caps et non --track-ui : le libellé est en CAPITALES, et
   dans ce système l'approche suit la casse. Des capitales serrées à
   11 px se lisent mal. */
.map-fs { position: absolute; right: 16px; bottom: 16px; background: var(--glass-solid); backdrop-filter: blur(8px); border: 1px solid var(--border-strong); color: var(--text); padding: 10px 16px; border-radius: var(--radius); font-size: var(--fs-3xs); font-weight: 700; letter-spacing: var(--track-caps); text-transform: uppercase; transition: color var(--dur) var(--ease), background var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.map-fs:hover { background: var(--yellow); border-color: var(--yellow); color: var(--yellow-ink); }

/* ---------- Signature de fin ----------
   La page se referme sur une CARTE NOIRE : dernier moment premium
   avant le pied de page, et seule respiration sombre de la section
   contact. Elle porte son propre data-tone dans le HTML — c'est le
   ton qui fait le fond, pas une règle nommée. */
.signature {
  position: relative; z-index: 1;
  margin-top: clamp(76px, 10vw, 150px);
  background: var(--bg);
  border-radius: clamp(var(--radius-lg), 2.4vw, 26px);
  padding-block: clamp(46px, 7vw, 86px);
  overflow: hidden;
}
/* Un filet jaune plein le long du bord haut : la même marque que
   les eyebrows, à l'échelle d'un bloc. */
.signature::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: var(--yellow); }
.signature-body { padding-bottom: clamp(6px, 2.5vh, 36px); }
.signature .section-num { margin-bottom: 20px; }
.signature-title { font-family: var(--font-display); font-weight: 700; letter-spacing: var(--track-display); font-size: clamp(2rem, 4.2vw, 3.4rem); line-height: 1.1; }
.signature-lead { margin-top: 18px; max-width: 44ch; color: var(--text-dim); font-size: var(--fs-sm); line-height: 1.7; text-wrap: pretty; }
.signature-actions { display: flex; align-items: center; gap: 26px; margin-top: 34px; flex-wrap: wrap; }

/* ============================================================
   14 · PIED DE PAGE — le dernier bloc noir
   ============================================================ */
.site-footer { position: relative; background: var(--bg); color: var(--text); }
/* Le liseré jaune plein largeur : la signature de marque qui ferme
   la page, écho du filet haut de la carte « Passez nous voir ». */
.site-footer::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: var(--yellow); }
.footer-inner { display: grid; grid-template-columns: 1.4fr 1fr 1.2fr; gap: 34px; padding-block: 64px 36px; align-items: start; }
.footer-brand { display: flex; align-items: center; gap: 14px; }
.footer-brand .brand-logo { width: 30px; }
.footer-name { font-family: var(--font-display); font-weight: 700; font-size: var(--fs-md); }
.footer-tag { font-size: var(--fs-3xs); text-transform: uppercase; letter-spacing: var(--track-caps-wide); color: var(--text-faint); }
.footer-nav { display: flex; flex-wrap: wrap; gap: 10px 22px; }
.footer-nav a { color: var(--text-dim); font-size: var(--fs-3xs); text-transform: uppercase; letter-spacing: var(--track-caps); transition: color var(--dur) var(--ease); }
.footer-nav a:hover { color: var(--yellow); }
.footer-contact { display: flex; flex-direction: column; gap: 7px; color: var(--text-dim); font-size: var(--fs-xs); }
.footer-contact a:hover { color: var(--yellow); }
.footer-bottom { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 10px; padding-block: 24px; border-top: 1px solid var(--border); color: var(--text-faint); font-size: var(--fs-2xs); }
.footer-legal { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.footer-legal a:hover { color: var(--text-dim); }
.footer-note { width: 100%; opacity: .7; margin-top: 4px; }

/* L'ENTRÉE DE L'ESPACE PRO — la porte de service.
   Elle est dans le pied de page, en clair, et c'est volontaire :
   la protection est le mot de passe, jamais le secret de
   l'adresse. Une porte qu'on cache mal n'est pas fermée, elle est
   seulement difficile à trouver pour Olivier.
   Ce qu'on lui demande, c'est de ne pas EXISTER pour le visiteur :
   même graisse, même corps que le reste de la ligne, mais une
   encre plus pâle qui la fait passer derrière « Mentions légales »
   dans l'ordre de lecture. Elle reprend sa couleur au survol,
   comme les autres. */
.footer-admin { opacity: .45; transition: opacity var(--dur-fast) var(--ease); }
.footer-admin:hover, .footer-admin:focus-visible { opacity: 1; }

/* Le bouton retour-haut est jaune : posé au-dessus de sections
   tantôt blanches tantôt noires, c'est la seule couleur qui se
   détache des deux. */
.to-top { position: fixed; right: 24px; bottom: 24px; z-index: 90; width: 48px; height: 48px; border-radius: 50%; background: var(--yellow); color: var(--yellow-ink); border: none; cursor: pointer; display: grid; place-items: center; opacity: 0; pointer-events: none; transform: translateY(14px); box-shadow: 0 6px 20px -8px rgba(10,10,11,.5); transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease), background var(--dur) var(--ease); }
.to-top[data-visible] { opacity: 1; pointer-events: auto; transform: translateY(0); }
.to-top:hover { background: var(--yellow-deep); box-shadow: 0 10px 26px -8px rgba(10,10,11,.55); }

/* ============================================================
   15 · PAGES LÉGALES (ton clair hérité de :root)
   ============================================================ */
.legal-main { max-width: 760px; margin-inline: auto; padding: 140px clamp(22px, 5vw, 56px) 100px; }
.legal-main h1 { font-size: clamp(2rem, 4.5vw, 3rem); letter-spacing: var(--track-display); margin-bottom: 12px; }
.legal-main h2 { font-size: var(--fs-lg); margin: 44px 0 12px; }
.legal-main p, .legal-main li { color: var(--text-dim); font-size: var(--fs-sm); }
.legal-main ul { list-style: disc; padding-left: 20px; display: grid; gap: 6px; }
.legal-main a { color: var(--text); font-weight: 700; border-bottom: var(--rule) solid rgb(var(--yellow-rgb)); }
.legal-main a:hover { background: rgb(var(--yellow-rgb) / .3); }
.legal-updated { font-size: var(--fs-2xs); color: var(--text-faint); letter-spacing: var(--track-caps); text-transform: uppercase; }
.legal-todo { color: var(--accent-text); font-weight: 700; font-size: var(--fs-xs); }

/* ============================================================
   16 · BARRE D'ACTION MOBILE (zone du pouce)
   ============================================================ */
.mobile-bar { position: fixed; left: 12px; right: 12px; bottom: 12px; z-index: 95; display: none; align-items: stretch; gap: 8px; padding: 8px; border-radius: 16px; background: rgba(255,255,255,.92); backdrop-filter: blur(18px) saturate(1.3); -webkit-backdrop-filter: blur(18px) saturate(1.3); border: 1px solid var(--border-strong); box-shadow: 0 -2px 0 rgb(var(--yellow-rgb)), 0 20px 50px -18px rgba(10,10,11,.45); }
/* Capitales, donc --track-caps comme la nav. C'est le poste le plus
   contraint du site : trois cases égales de 110 px sur un écran de
   390, et « Rendez-vous » est le libellé le plus long. Mesuré à
   .12em — l'encre fait 91,87 px pour 98,67 px utiles hors padding.
   Ça passe, mais c'est la limite. */
.mobile-bar a { flex: 1; display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; padding: 9px 6px; font-size: var(--fs-3xs); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--text-dim); border-radius: 10px; transition: transform var(--dur-fast) var(--ease); }
.mobile-bar a svg { color: var(--text); }
.mobile-bar-primary { background: var(--yellow); color: var(--yellow-ink) !important; font-weight: 700; }
.mobile-bar-primary svg { color: var(--yellow-ink) !important; }

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

/* ============================================================
   17 · (libre)
   ------------------------------------------------------------
   Ce chapitre habillait les deux sélecteurs d'arbitrage du pied de
   page — « Design du site » et « Section Avis ». Les deux ont été
   retirés le 13 août : le site est clair, la section « Avis » est
   claire, il n'y a plus de choix à offrir. La classe .version-switch
   n'existe plus dans aucune page ; ses règles sont parties avec elle
   plutôt que de rester à attendre un HTML qui ne reviendra pas.
   Le numéro de chapitre est conservé pour ne pas décaler les
   renvois du README.
   ============================================================ */

/* ============================================================
   18 · RESPONSIVE
   ============================================================ */
@media (max-width: 1024px) {
  .garage-grid, .rdv-grid, .contact-grid { grid-template-columns: 1fr; }
  .rdv-intro { position: static; }
  /* Trois colonnes de texte deviennent trop étroites pour des
     titres de deux mots : on repasse à deux, soit trois rangées. */
  .cards { grid-template-columns: repeat(2, 1fr); }
  .contact-map { min-height: 340px; order: -1; }
  .team-photo img { aspect-ratio: 16 / 10; }
}

@media (max-width: 1080px) {
  .hero-sub { max-width: 40ch; }
}

@media (max-width: 860px) {
  .nav, .header-actions { display: none; }
  .menu-toggle { display: flex; }
  .mobile-bar { display: flex; }
  body { padding-bottom: 84px; }
  /* 104 et non 92 : la pastille est passée de 46 à 48 px, et elle
     mordait sur le bord arrondi de la barre d'action. */
  .to-top { bottom: 104px; }
  /* La barre d'action fixe mange 84 px en bas : le hero s'arrête
     au-dessus d'elle plutôt que de passer dessous. */
  .hero {
    min-height: calc(100svh - 84px);
    padding-block: calc(82px + clamp(24px, 5vh, 56px)) clamp(48px, 9vh, 92px);
  }
  .hero-inner { grid-template-columns: 1fr; gap: clamp(28px, 6vw, 46px); }
  /* Une seule colonne : le débord sur la marge de droite n'a plus
     de sens — il n'y a plus de texte à côté à dégager. Ni la
     descente : il n'y a plus de centre optique à décaler. */
  .hero-stage { justify-content: center; margin-right: 0; --stage-drop: 0px; margin-top: 0; }
  /* Le cadre reprend toute la largeur : c'est la largeur, et non
     plus la hauteur du viewport, qui le dimensionne. */
  .hero-frame { width: 100%; }
  /* Le décor s'efface — et ce n'est pas une reculade. Trame et
     panneau existent pour organiser DEUX colonnes ; il n'y en a
     plus qu'une, et le cadre en occupe toute la largeur. Un panneau
     sous une image aussi large que lui ne se voit pas, une trame
     derrière elle non plus : il ne resterait que le coût de peinture,
     sur l'appareil qui l'encaisse le plus mal. */
  .hero-canvas { display: none; }
  /* La plaque rentre DANS le cadre : dehors, elle chevaucherait la
     gouttière et non plus le tirage. */
  .frame-plate { left: -10px; bottom: 42px; padding: 10px 15px; gap: 10px; }
  /* Pas de survol au doigt : le repère ne peut pas se « découvrir ».
     Les flèches restent donc là, en retrait, et les trois traits de
     la deuxième rangée disent où l'on en est. */
  .frame-nav-prev, .frame-nav-next { opacity: .72; translate: 0 -50%; }
  .frame-ticks { display: flex; }
}

@media (max-width: 680px) {
  .cards, .contact-cards, .stats { grid-template-columns: 1fr; }
  .field-row { grid-template-columns: 1fr; gap: 0; }
  .footer-inner { grid-template-columns: 1fr; gap: 30px; }
  /* Deux colonnes de texte deviennent illisibles : une seule tuile
     par rangée, l'alternance L/R garde la dérive latérale. */
  .tilt-grid { grid-template-columns: 1fr; max-width: 430px; gap: 34px; }
  .review { padding: 30px 26px; }
  /* ============================================================
     LES TROIS GESTES, AU TÉLÉPHONE
     ------------------------------------------------------------
     Ce n'est pas la mise en page du bureau rétrécie : c'est une
     COMPOSITION PROPRE, avec le même vocabulaire.

     LA PERMUTATION. Le bloc de pied s'inverse — les boutons
     passent AVANT la bande de preuve. Mesures relevées dans le
     navigateur à 390 × 844, barre d'action du pouce comprise
     (elle occupe 756 → 832) :

                          avant         après
       bande de preuve    423 → 513     682 → 772
       Nos véhicules      550 → 610     437 → 497
       Prendre rdv        622 → 682     507 → 567
       Appeler            694 → 754     583 → 643

     Avant, le troisième bouton finissait à 754 px pour une barre
     qui commence à 756 : deux pixels d'air. Ce n'était pas hors
     champ, c'était pire — c'était collé. Après, les trois gestes
     tiennent entre 437 et 643 px, et il reste 113 px sous eux.

     La bande de preuve, elle, passe désormais SOUS la barre du
     pouce (772 contre 756) : sa dernière ligne est rognée au
     repos. C'est assumé — un bloc coupé au bord du champ est ce
     qui dit le mieux qu'il y a une suite, et ce qu'on rogne ici
     est un argument, jamais un geste.

     L'ORDRE DES ÉCARTS DIT LA HIÉRARCHIE. 10 px entre les deux
     premiers, 16 px avant le numéro : « Nos véhicules » et
     « Prendre rendez-vous » forment une paire — deux façons
     d'entrer — et le téléphone se tient à part, comme le recours
     qu'il est. Trois boutons à écarts égaux auraient fait une
     liste ; deux plus un font une phrase. C'est le même principe
     de contraste d'espaces que le reste de la colonne.

     LES TROIS ONT LA MÊME HAUTEUR, et elle suit l'écran. Sur une
     pile pleine largeur, un écart d'un pixel entre deux boutons
     se voit d'un coup, là où il passait inaperçu sur une rangée :
     la hauteur est donc posée une fois, pour les trois.
     Elle est en vh parce que TOUT le hero est en vh — le retrait
     de tête, le corps du titre, les intervalles. Le bouton était
     le seul élément à hauteur fixe du premier écran, et c'est
     exactement lui qui débordait sur les téléphones courts.
     60 px sur un grand écran, 54 sur un petit : le plancher reste
     très au-dessus des 44 pt recommandés au doigt.
     ============================================================ */
  .hero-foot { display: flex; flex-direction: column; }
  .hero-actions { order: 1; margin-top: clamp(22px, 3.6vh, 34px); gap: 10px; }
  .hero-trust   { order: 2; margin-top: clamp(30px, 4.6vh, 40px); }
  /* Pleine largeur : cible au pouce, et une hiérarchie qui reste
     nette même sur trois lignes de titre. */
  .hero-actions .btn { width: 100%; min-height: clamp(54px, 7.2vh, 60px); }
  /* La garniture verticale tombe à 14 px pour LAISSER COMMANDER la
     hauteur ci-dessus. À 19 px elle imposait 58 px à elle seule
     (19 + 19 + 18 de pictogramme + 2 de filet) : le clamp n'aurait
     jamais pu descendre en dessous, et la règle se serait tue sans
     rien dire. Deux mécanismes qui fixent la même dimension, c'est
     toujours le plus bavard qui gagne — ici on n'en laisse qu'un.
     Une seule ligne pour les deux premiers boutons depuis qu'ils
     partagent .btn-hero. */
  .btn-hero { padding: 14px 26px; }
  /* Le numéro se détache de la paire qui le précède. La marge est
     posée sur LUI et non en `gap` : un gap est uniforme par
     construction, et c'est justement l'uniformité qu'on refuse. */
  .btn-call { padding: 14px 26px; margin-top: 6px; }
  /* Trois cellules de 110 px ne sont plus une bande, ce sont trois
     mots coupés. La bande se redresse : une preuve par ligne, les
     pictogrammes en colonne font le filet à eux seuls. */
  .hero-trust { grid-template-columns: 1fr; gap: 11px; max-width: 340px; padding-top: 16px; }
  /* La chorégraphie suit la nouvelle lecture. Les retardements du
     bureau (preuve .48s, gestes .58s) joueraient à l'envers de ce
     qu'on voit : la bande s'allumerait sous des boutons déjà là.
     On échange les deux — ce qui est en haut arrive en premier,
     règle valable partout ailleurs sur ce site. */
  .hero.in .hero-actions { transition-delay: .48s; }
  .hero.in .hero-trust   { transition-delay: .58s; }

  /* ---------- Écrans COURTS (≤ 780 px de haut) ----------
     Une requête sur la HAUTEUR, et c'est bien elle le sujet :
     à 360 × 740 le troisième bouton finissait à 644 px pour une
     barre du pouce qui commence à 652. Huit pixels. Aucun
     chevauchement, et pourtant exactement ce qu'on nous demande
     d'éviter — un bouton posé sur une barre n'a pas l'air à
     l'étroit, il a l'air d'une erreur.

     C'est la RÉSERVE DE TÊTE qu'on reprend, pas le rythme du
     bloc. Le retrait haut du hero vaut 82 px d'en-tête plus une
     respiration en vh : cette respiration est le seul poste du
     premier écran qui ne porte ni texte ni geste, donc le seul
     qu'on puisse rendre sans rien perdre. Elle passe de 5vh à
     3vh — 37 px à 22 px sur un écran de 740 —, les trois boutons
     remontent d'autant, et l'air sous le dernier passe de 8 à
     23 px. Rien d'autre n'a bougé : ni les hauteurs de bouton,
     ni les écarts qui portent la hiérarchie. */
  @media (max-height: 780px) {
    .hero { padding-block: calc(82px + clamp(14px, 3vh, 34px)) clamp(40px, 7vh, 72px); }
    .hero-title { margin-top: clamp(8px, 1.6vh, 20px); }
    .hero-actions { margin-top: clamp(16px, 2.8vh, 26px); }

    /* LE TITRE PASSE ENFIN SOUS COMMANDE DE LA HAUTEUR.
       La règle de base le prévoyait déjà — `min(clamp(...), 12.4vh)`,
       « sur un écran bas, c'est la hauteur et non la largeur qui
       commande ». L'intention était juste, le seuil beaucoup trop
       bas : 12,4vh ne devient contraignant qu'en dessous de 336 px
       de haut, c'est-à-dire jamais. Sur un iPhone SE (375 × 667) le
       titre gardait donc ses 41,6 px de plancher, tenait sur trois
       lignes et coûtait 127 px — et le dernier bouton finissait à
       2 px de la barre du pouce.

       5,4vh raccorde exactement au plancher de 2,6rem à la borne
       des 780 px (5,4 × 7,8 = 42,1, ramené à 41,6) : aucun saut au
       passage de la requête. En dessous, le titre suit l'écran —
       36 px sur un SE, où trois lignes ne coûtent plus que 110 px.
       Dix-sept pixels rendus au geste, pris sur le seul élément qui
       en avait de trop. */
    .hero-title { font-size: clamp(2.15rem, 5.4vh, 2.6rem); }
  }
  /* li + li et non li : le filet est posé par `.hero-trust li + li`,
     qui l'emporte en spécificité sur `.hero-trust li`. Même
     sélecteur, ou la règle est écrite pour rien. */
  .hero-trust li,
  .hero-trust li + li { padding-inline: 0; border-left: 0; }
  /* Plus la place pour le repère en bas : l'indice de défilement
     s'efface, la barre d'action tient déjà ce rôle. */
  .hero-scroll { display: none; }
  .signature { border-radius: var(--radius-lg); }
  .signature-actions { gap: 18px; margin-top: 26px; }
  .signature-actions .btn { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; transition-delay: 0ms !important; }
  [data-reveal], .hero .section-num, .hero-sub, .hero-actions, .hero-trust, .hero-scroll, .frame-plate { opacity: 1 !important; transform: none !important; }
  /* Le décor est en place d'emblée : le panneau entier, les filets
     à pleine hauteur, et aucune dérive au défilement. */
  .hero-panel { clip-path: none !important; animation: none !important; translate: none !important; }
  .hero-rules i { transform: none !important; }
  .line-inner, .eyebrow-line { transform: none !important; }
  .hero-title .line-inner { animation: none !important; }
  .losange-thread { display: none !important; }
  /* Les plaques restent en place, masques compris : le cadrage est
     de la mise en page, pas du mouvement. */
  .plate, .plate img { transform: none !important; }
  /* Le cadre du hero : la lueur continue de suivre le curseur —
     elle répond au geste du visiteur, ce n'est pas du mouvement
     subi — mais l'ouverture et le glissement sont instantanés. */
  .hero-frame { opacity: 1 !important; }
  .hero-frame::before, .hero-frame::after, .frame-window { clip-path: none !important; }
  /* Ni l'échelle d'ouverture (transform) ni le micro-zoom de survol
     (scale) : une image qui grossit sous le curseur reste du
     mouvement. Les deux propriétés portent chacune un mouvement
     distinct sur le même élément. */
  .frame-shot img { transform: none !important; scale: none !important; }
}

/* ============================================================
   19 · Système de mouvement « fil losange » (motion.js)
   Tout est inerte tant que .has-motion / .has-thread ne sont pas
   posées sur <html> — le fallback statique reste le défaut.
   ============================================================ */

/* ---------- Le fil : un losange fixe traverse la page ----------
   IMPORTANT — cet élément est en position:fixed, donc HORS de
   toute section : il ne peut pas hériter du ton de ce qu'il
   survole, et il traverse successivement du blanc et du noir. Sa
   gravure est donc en GRIS MOYEN, la seule valeur qui se voit sur
   les deux (elle était blanche en v4, quand tout était noir). */
.losange-thread {
  /* z-index 1 : au-dessus des fonds de section opaques, sous le
     contenu (.section > .wrap est aussi z-index 1 mais plus tard
     dans le DOM). */
  position: fixed; left: 0; top: 0; z-index: 1;
  /* Taille modérée : le fil est une gravure, jamais un sujet. */
  width: clamp(160px, 17vw, 280px); height: auto;
  color: #8d8b86; opacity: 0; pointer-events: none;
  transform-origin: 50% 50%; will-change: transform, opacity;
  display: none;
}
.has-thread .losange-thread { display: block; }
/* Le fil remplace les losanges gravés par section (doublon visuel
   sinon). Le hero n'en a plus : il porte une photographie. */
.has-thread .losange-garage, .has-thread .losange-contact { display: none; }

/* Beam (motion.js buildBeam) : ligne lumière eyebrow→CTA, sous le
   contenu. .hero-inner = z 2 ; le beam se pose à z 0, en retrait. */
.beam { position: absolute; left: 0; top: 0; z-index: 0; pointer-events: none; overflow: visible; }

.losange-thread { overflow: visible; }
/* Trois couches : gravure (traits), remplissage jaune (niveau monté
   par JS), halo (lumière) */
.losange-thread .thread-line { fill: none; stroke: currentColor; stroke-width: 1; transition: stroke 1.2s var(--ease); }
/* Remplissage : suit le contour — le centre ne se remplit jamais (le
   vide central n'appartient pas aux bandes du logo).
   fill-opacity .38 et halo coupé : sur le noir de la v4 un jaune
   plein était une lueur ; sur le blanc de la v5 c'est un losange
   JAUNE VIF de 280 px posé en travers de la page, qui passait
   devant les titres de « Contact ». Le fil doit rester une gravure
   qu'on remarque à peine — il n'a jamais eu vocation à être lu. */
.losange-thread .thread-fill path { fill: rgb(var(--yellow-rgb)); fill-opacity: .25; stroke: none; }
/* Le halo se règle par ses ARRÊTS DE DÉGRADÉ, dans index.html, et
   non ici : motion.js pilote son opacité par attribut de
   présentation (setAttribute("opacity", …)), qu'une règle CSS
   d'opacité écraserait — le halo resterait figé à sa valeur au lieu
   de se charger avec le fil. */
.losange-thread .thread-halo { fill: url(#thread-halo-grad); }

/* Signature d'APPARITION : une ligne JAUNE trace le losange (1,2 s)
   au moment où il se lève, section 01, pendant que le hero sort du
   champ. Puis le trait redevient gravure et devient le fil de
   scroll. */
.losange-thread.tracing .thread-line { stroke: var(--yellow); transition: stroke-dashoffset 1.2s var(--ease); }
.losange-thread.tracing .thread-line + .thread-line { transition-delay: .2s; }

/* ---------- Liseré CTA : périmètre tracé, coins aux angles losange ---------- */
.btn-line { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.btn-line path {
  fill: none; stroke: var(--yellow-deep); stroke-width: 1.5;
  stroke-dasharray: 100 100; stroke-dashoffset: 100;
  transition: stroke-dashoffset .8s var(--ease), stroke-dasharray .7s var(--ease);
}

/* ---------- Révélation diagonale des images (angle du losange ≈ 54°) ---------- */
.has-motion .diag-reveal[data-reveal] {
  opacity: 1; transform: none;
  clip-path: polygon(-200% 0, 0% 0, -55% 100%, -200% 100%);
  transition: clip-path 1.05s var(--ease);
}
.has-motion .diag-reveal[data-reveal].in {
  clip-path: polygon(-200% 0, 155% 0, 100% 100%, -200% 100%);
}
.has-motion .diag-reveal img { transform: scale(1.06); transition: filter .6s var(--ease), transform 1.25s var(--ease); }
.has-motion .diag-reveal.in img { transform: scale(1); }

/* ---------- Spotlight de bordure (cartes) : le liseré s'éclaire
   près du curseur. Sur clair, « s'éclairer » veut dire devenir
   franchement jaune — d'où les opacités relevées. ---------- */
.card::after, .review::after, .info-card::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  border: 1px solid transparent;
  background: radial-gradient(200px circle at var(--mx, 50%) var(--my, 50%),
      rgb(var(--yellow-rgb) / .9), rgb(var(--yellow-rgb) / .2) 55%, transparent 78%) border-box;
  -webkit-mask: linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0; transition: opacity var(--dur) var(--ease);
  pointer-events: none;
}
.card:hover::after, .review:hover::after, .info-card:hover::after { opacity: 1; }
[data-tone="dark"] .card::after, [data-tone="dark"] .review::after, [data-tone="dark"] .info-card::after {
  background: radial-gradient(200px circle at var(--mx, 50%) var(--my, 50%),
      rgb(var(--yellow-rgb) / .5), rgb(var(--yellow-rgb) / .07) 55%, transparent 78%) border-box;
}
