/* ==========================================================================
   Soufiane Zaidi, page de vente B2B, refonte v4
   Palette : fond #58656A, titres dorés #D6B77D (+15px sur tous les titres),
   texte #E3E6E3. Blocs 4 (offres) et 5 (preuve) rompent le fond sombre.
   Typo : Chronicle Display / Chronicle Display Semi (titres) + Mark OT (texte)
   Animation : flou -> net + opacité, pilotée en continu par le scroll
   (scrubbing, jamais une durée fixe courte), avec des fenêtres de
   progression larges pour un rythme délibérément lent. Le Bloc 3 (texte
   qui s'écrit sur scroll retenu) applique le même principe flou+opacité,
   caractère par caractère, comme exception de granularité mais pas de
   famille d'effet.
   ========================================================================== */

@font-face{
  font-family:"Gloock";
  src:url("../assets/fonts/Gloock-Regular.ttf") format("truetype");
  font-weight:400; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"Mark OT";
  src:url("../assets/fonts/MarkOT.woff2") format("woff2");
  font-weight:400; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"Mark OT";
  src:url("../assets/fonts/MarkOT-Light.woff2") format("woff2");
  font-weight:300; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"Mark OT";
  src:url("../assets/fonts/MarkOT-Italic.woff2") format("woff2");
  font-weight:400; font-style:italic; font-display:swap;
}
@font-face{
  font-family:"Chronicle Display Light";
  src:url("../assets/fonts/ChronicleDisplay-LightItalic.woff2") format("woff2");
  font-weight:300; font-style:italic; font-display:swap;
}
@font-face{
  font-family:"Chronicle Display Roman";
  src:url("../assets/fonts/ChronicleDisplay-Roman.woff2") format("woff2");
  font-weight:400; font-style:normal; font-display:swap;
}
/* Redemandé pour la citation du Bloc 6 (voir .offers__quote) - fichier
   .otf fourni par l'utilisateur (assets/fonts/), pas de version .woff2 :
   format("opentype"), suffisant ici (chargée qu'une fois, un seul court
   texte concerné). */
@font-face{
  font-family:"Chronicle Display Bold";
  src:url("../assets/fonts/Chronicle Display-Bold-Italic.otf") format("opentype");
  font-weight:700; font-style:italic; font-display:swap;
}
@font-face{
  font-family:"Michroma";
  src:url("../assets/fonts/Michroma-Regular.ttf") format("truetype");
  font-weight:400; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"Montserrat ExtraBold";
  src:url("../assets/fonts/Montserrat-ExtraBold.ttf") format("truetype");
  font-weight:800; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"Montserrat Bold";
  src:url("../assets/fonts/Montserrat-Bold.ttf") format("truetype");
  font-weight:700; font-style:normal; font-display:swap;
}

/* ---------- Tokens ----------
   --text (#E3E6E3) on --bg (#58656A): ~4.8:1, clears WCAG AA normal text
   (4.5:1), so the off-white is kept as specified, no fallback needed.
   --gold on --bg: ~3.1:1, clears the AA LARGE-text threshold (3:1), fine
   since gold is only ever used at headline sizes on the dark background.
   Bloc 4 (--bg-light) and Bloc 5 (--white) flip the ground to light: gold
   text there would fail badly (~1.5-1.9:1), so titles/body copy in those
   two sections use --ink (= the same #58656A as --bg, reused as a dark
   foreground colour) instead, except Bloc 5's own title, which the brief
   explicitly keeps gold despite the weak contrast (flagged, not silently
   "fixed"). --title-boost adds +30px to every title's font-size (was
   +15px), layered on top of each title's own clamp() via calc(); titles
   now use Gloock (replaces Chronicle Display / Chronicle Display Semi
   everywhere, per the brief), with tightened line-heights throughout to
   keep title blocks compact at the larger size. */
:root{
  --bg:#58656A;
  --ink:#58656A;
  --bg-light:#E3E6E3;
  --gold:#D6B77D;
  --text:#E3E6E3;
  /* #FFFFFF -> #faf6f4 (redemandé, "tous les textes (titres et
     paragraphes) qui sont sur un fond #58656A, mets-les en #faf6f4",
     y compris les titres jusqu'ici en gold) : --text-strong devient LE
     token unique "texte sur fond sombre" du site - les titres/
     paragraphes qui utilisaient var(--gold)/var(--white)/#FFFFFF ou
     var(--text) directement sur une section de fond #58656A (ou une
     variante à peine teintée, var(--card)) sont redirigés vers ce même
     token (voir chaque sélecteur concerné plus bas). --gold lui-même
     N'EST PAS modifié : il reste utilisé tel quel pour tout ce qui n'est
     pas du texte de titre/paragraphe (bordures, séparateurs, icônes,
     flèches, symboles +/×, étoiles) - non concerné par la demande.
     Sections explicitement sur un autre fond (Hero : photo, pas
     #58656A ; cartes blanches/section blanches : Bio, Références,
     Portfolio, .testimonial-card, .moment__stat, .offer-card ; .bloc--4 :
     --bg-light) restent inchangées, leur texte ne touche jamais
     #58656A. */
  --text-strong:#faf6f4;
  --white:#FFFFFF;
  --card:rgba(255,255,255,.045);
  --card-border:rgba(255,255,255,.14);
  --card-border-hover:var(--gold);
  /* Passé en clamp() (audit responsive complet, bug trouvé : le Hero -
     et par construction TOUS les titres du site, ce boost étant
     partagé - débordait horizontalement sur petit écran, ex.
     "deviennent"/"d'attractivité" tronqués à 390px) : l'ajout FIXE de
     30px, jamais réduit nulle part, s'additionnait au-dessus du
     plancher déjà bas de chaque clamp() de titre, sans aucune marge de
     manoeuvre restante sur les écrans étroits. 4vw atteint son
     plafond (30px, IDENTIQUE à l'ancien comportement fixe, aucun
     changement au-delà) dès ~750px de large - seuls les écrans plus
     étroits que ça (mobile) voient ce boost réduit, jusqu'à 12px au
     plancher. Un seul token pour tous les titres du site : corrigé
     partout d'un coup, comme le reste du système --title-boost. */
  --title-boost:clamp(12px, 4vw, 30px);

  --display:"Gloock", Georgia, serif;
  --display-semi:"Gloock", Georgia, serif;
  --sans:"Mark OT", "Helvetica Neue", Arial, sans-serif;

  /* Échelle typographique uniformisée pour tous les titres de bloc.
     -30% (redemandé, "les titres sont trop gros") : min/max de la
     version précédente (2.72rem/4.4rem + --title-boost, effectif
     ~55.5px/~100.4px mesuré) multipliés par 0.7 (~38.9px/~70.3px),
     rapport min/max conservé (~1.81, proche du nombre d'or). Ne dépend
     plus de --title-boost (partagé avec .bloc3-rsvp__word/.moment__counter,
     hors du périmètre des titres uniformisés, inchangé pour eux) : un
     clamp() unique et autonome, plus simple à raisonner.
     Coefficient vw élargi (5.6vw -> 3.9vw, redemandé, "ils ne rapetissent
     pas quand la fenêtre se réduit") - bug réel trouvé : l'ancienne zone
     fluide (777px -> 1257px, calculée) restait figée au plafond sur tout
     écran desktop courant au-dessus de 1257px, donc invisible au
     redimensionnement pour la plupart des fenêtres - nouvelle zone fluide
     ~997px -> ~1800px, qui couvre la plage réelle où un utilisateur
     redimensionne une fenêtre de bureau. */
  --fs-title:clamp(2.43rem, 3.9vw, 4.39rem);
  /* --fs-kicker (nouveau, structure titre/sous-titre/paragraphe,
     redemandé) : sous-titre doré, plus petit que le titre, plus grand
     que le paragraphe courant - valeur intermédiaire entre --fs-title et
     --fs-body. */
  --fs-kicker:clamp(1.35rem, 2vw, 1.75rem);
  --fs-body:clamp(1.1rem, 1.7vw, 1.35rem);
  /* --fs-caption (nouveau, redemandé "harmonise tous les textes
     paragraphes... 2 tailles") : taille 2, plus petite que --fs-body -
     pour les cartes et descriptions sous cartes (Portfolio, Offres,
     Avis, CTA). Reprend le plancher le plus bas déjà en usage (.9rem,
     Portfolio/Avis) avec un plafond réduit dans la même proportion que
     --fs-body (1.35/1.1 ≈ 1.227). Remplace 5 valeurs jusque-là toutes
     différentes (.9rem/1rem/1.05rem/clamp(1.05,...,1.25rem)) qui
     n'avaient jamais été alignées. */
  /* -1.5px (redemandé) : 0.9rem/1.1rem -> 0.80625rem/1.00625rem (même
     écart de 1.5px = 0.09375rem retranché aux 2 bornes, la plage
     responsive reste proportionnellement identique). */
  --fs-caption:clamp(0.80625rem, 1.3vw, 1.00625rem);
  /* Interlignage taille 2 harmonisé + réduit (redemandé, "ainsi que
     l'interlignage") : remplace 3 valeurs différentes (1.5/1.55/1.6)
     jusque-là utilisées par les 5 éléments taille 2. */
  --lh-caption:1.4;

  --container:1180px;
  --gutter:clamp(1.5rem, 5vw, 4rem);

  /* Coins très légèrement arrondis (redemandé, "sur toutes les bordures
     d'images et de cartes") - remplace la convention précédente
     "border-radius:0 partout" (angles pointus, documentée à plusieurs
     endroits du fichier) : une seule valeur partagée, appliquée aux
     cartes (offer-card/cta-card/steps__grid/testimonial-card/portfolio-
     h__card/bio__card) et aux cadres photo (bloc2__media/bloc6__media/
     moment__media/portfolio-h__carousel/bio__portrait) - PAS aux
     boutons (offers__cta, hero__cta, etc., restent des pilules à angles
     droits, hors du périmètre "images et cartes" de la demande) ni à
     l'image Hero plein cadre (bord à bord avec la fenêtre, arrondir des
     coins qui touchent déjà les bords de l'écran n'aurait aucun sens
     visuel) ni aux éléments déjà circulaires (avatars, icônes rondes).
  */
  --radius:0px;

  --ease:cubic-bezier(.16,1,.3,1);
  --dur-hover:.32s;
  --dur-s:.5s;
  --dur-m:.9s;
  --dur-l:1.4s;

  /* Échelle d'espacement nombre d'or (base 1.5rem, ×φ≈1.618 par palier,
     prompt-page-pro.md "Système de proportions"), référence pour les
     marges/paddings récurrents du site. Un audit du fichier montre que le
     rythme vertical existant (beaucoup de clamp() bornés à 1.5/2.5/4/6.5/
     10/15-16rem, accumulés au fil de ~130 passes de réglages fins, chacun
     validé visuellement par le client) approche déjà cette progression
     d'assez près (écarts de quelques % à chaque palier) : pas de refonte
     systématique de ces valeurs, en particulier celles imbriquées dans des
     calc() qui les relient entre elles (--ref-gap, .bio__pin) pour garantir
     une symétrie visuelle précise réglée sur demande explicite : les
     retoucher sans pouvoir vérifier le rendu risquerait de rouvrir des
     écarts déjà corrigés. L'échelle sert de référence pour les paddings de
     section clairement indépendants (voir .bloc--4, .cta, .bloc2__text-col,
     .offers__grid plus bas) et pour tout nouvel espacement à venir. */
  /* --section-gap (captures "Ecarts-Padding-01" à "-05", demandé) : valeur
     UNIQUE pour l'écart avant/après CHAQUE séparateur de section (ligne +
     "+"), remplace l'ancien système où chaque #sepN portait sa propre
     compensation calc() ad hoc (--frame-gap, formules #sep3/.bloc6/.faq
     imbriquées) - fragile, jamais réellement uniforme d'une transition à
     l'autre (mesuré : 0/56px de part et d'autre de #sep1, 24/88px pour
     #sep3, cohérence globale absente). Reprend telle quelle la valeur déjà
     la plus fréquente du fichier (clamp(4.5rem, 9vw, 7rem), utilisée 3 fois
     avant cette passe) : le changement visuel net reste minime, seule la
     RÈGLE devient systématique. Portée par .separator lui-même
     (margin-block, un seul point de vérité) : voir son commentaire plus
     bas pour le détail par section. */
  /* Resserré (signalé, "les blocs encadrés par 2 séparateurs doivent
     avoir ceux-ci pile sur le bord haut/bas de la fenêtre" quand le bloc
     remplit l'écran - clamp(4.5rem,9vw,7rem) précédent, jusqu'à 112px,
     jugé trop grand) : reprend la valeur du padding-block propre du
     séparateur lui-même (clamp(1.5rem,3vw,2.5rem), voir .separator) -
     l'écart total autour d'un séparateur (margin + son propre padding)
     reste ainsi lisible sans être exagéré, à toutes les largeurs. */
  --section-gap:clamp(1.5rem, 3vw, 2.5rem);
  /* --separator-zone (195e passe, demandé : "le bloc FAQ doit avoir le
     même espace en haut qu'en bas... imagine que cette image [.faq-break]
     soit placée à la place d'un séparateur") : .faq-break n'a ni ligne ni
     "+", donc son propre padding ne peut égaler qu'un simple margin de
     séparateur (var(--section-gap) seul, essayé - jugé "quasiment collé",
     signalé) - la VRAIE respiration qu'un séparateur crée inclut aussi sa
     propre hauteur visible (padding-block + "+" + chute verticale), pas
     seulement sa margin. Recompose ici EXACTEMENT cette même somme :
     margin-block(2×--section-gap) + padding-block du séparateur
     (2×--section-gap, identique depuis la 192e passe) + hauteur du "+"
     (.separator__symbol, font-size 2.4rem, line-height 1) + marge avant
     la chute (.separator__drop, margin-top .5rem) + hauteur de la chute
     (.separator__drop, height clamp(1rem, 2vw, 1.6rem)). Si l'une de ces
     4 valeurs change dans .separator/.separator__symbol/.separator__drop
     plus bas, la répliquer ici. Vérifié (Playwright) : égal au pixel près
     à la distance mesurée bioFrame->bloc6 (traverse #sep3 en entier). */
  --separator-zone:calc(var(--section-gap) * 4 + 2.9rem + clamp(1rem, 2vw, 1.6rem));
  --space-1:1.5rem;
  --space-2:2.43rem;
  --space-3:3.93rem;
  --space-4:6.35rem;
  --space-5:10.28rem;
  --space-6:16.63rem;
}

/* ---------- Reset ---------- */
*,*::before,*::after{box-sizing:border-box;}
/* auto, pas smooth (changé pour Lenis, voir js/motion.js) : Lenis prend en
   charge le smooth-scroll (dont les ancres, via son option anchors:true) -
   laisser scroll-behavior:smooth actif en plus ferait lutter le lissage
   natif du navigateur et celui de Lenis sur la même position de scroll
   (à-coup/dépassement sur les liens d'ancre "#cta"), pas juste un doublon
   inoffensif. */
html{scroll-behavior:auto;}
body{
  margin:0;
  background:var(--bg);
  color:var(--text);
  font-family:var(--sans);
  font-weight:300;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img{max-width:100%;display:block;}
a{color:inherit;text-decoration:none;}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;}
ul{list-style:none;margin:0;padding:0;}
h1,h2,h3,p,blockquote,figure{margin:0;}
.container{
  max-width:var(--container);
  margin-inline:auto;
  padding-inline:var(--gutter);
}
.skip-link{
  position:absolute; left:-999px; top:0;
  background:var(--gold); color:var(--bg); padding:.8em 1.2em;
  z-index:999;
}
.skip-link:focus{left:1rem; top:1rem;}

@media (prefers-reduced-motion: reduce){
  *{animation-duration:.01ms !important; transition-duration:.01ms !important;}
}

/* ---------- Scroll progress ---------- */
.scroll-progress{
  position:fixed; top:0; left:0; right:0; height:3px; z-index:200;
  transform-origin:left center;
  transform:scaleX(0);
  background:var(--gold);
  will-change:transform;
}
/* Bouton flottant "retour en haut" (demandé, audit UX externe) : masqué
   au chargement (opacity+visibility, pas display:none - garde la
   transition disponible dès le 1er scroll), affiché une fois un certain
   seuil de scroll dépassé (voir main.js, .is-visible posée par
   backToTop).
   Carré (corrigé - était rond) + ligne blanche/fond transparent/survol
   doré (demandé, "comme le reste du design de la page... comme les
   boutons") : même recette que .hero__cta/.offers__cta/.cta-form__submit
   ailleurs sur le site (border-radius:var(--radius), border:1px solid
   var(--text), fond transparent au repos) plutôt que le rond/fond blanc
   du 1er essai. Seule différence assumée par rapport à ces 3 boutons
   (demandé explicitement) : la flèche reste BLANCHE au survol (pas
   var(--bg) comme leur texte) - le fond doré n'a donc pas besoin de
   contraster avec un texte sombre ici, juste avec la flèche claire déjà
   utilisée au repos. */
.back-to-top{
  position:fixed;
  right:clamp(1.2rem, 3vw, 2rem);
  bottom:clamp(1.2rem, 3vw, 2rem);
  z-index:50;
  width:3.1rem; height:3.1rem;
  border-radius:var(--radius);
  display:flex; align-items:center; justify-content:center;
  background:transparent;
  border:1px solid var(--text);
  color:var(--text);
  font-size:1.2rem; line-height:1;
  opacity:0; visibility:hidden;
  transform:translateY(10px);
  transition:opacity var(--dur-hover) var(--ease), transform var(--dur-hover) var(--ease), background var(--dur-hover) var(--ease), color var(--dur-hover) var(--ease), border-color var(--dur-hover) var(--ease), visibility 0s var(--dur-hover);
}
.back-to-top.is-visible{
  opacity:1; visibility:visible;
  transform:translateY(0);
  transition:opacity var(--dur-hover) var(--ease), transform var(--dur-hover) var(--ease), background var(--dur-hover) var(--ease), color var(--dur-hover) var(--ease), border-color var(--dur-hover) var(--ease), visibility 0s;
}
.back-to-top:hover{background:var(--gold); border-color:var(--gold); color:var(--white);}

/* ==========================================================================
   LOADER : "×" convergent en carré, compteur 0->100, logo, rebond puis
   expansion. Séquencé entièrement par JS (classes ajoutées une à une),
   CSS ne définit que l'état visuel de chaque étape et sa transition.
   ========================================================================== */
.loader{
  position:fixed; inset:0; z-index:9999;
  background:var(--bg);
  display:flex; align-items:center; justify-content:center;
  overflow:hidden;
  /* --loader-sq : taille du petit carré, sortie de .loader__square (voir
     plus bas) pour être aussi lisible par les "×" (calc() plus bas,
     .loader__x--tl etc) - source unique, les deux restent cohérents par
     construction plutôt que par 2 valeurs dupliquées à la main. */
  --loader-sq:min(46vw, 340px);
  /* Fondu de disparition encore allongé (.7s -> 1.1s) pour une apparition
     du Hero plus douce ; l'étirement (EXPAND) a été raccourci en
     contrepartie pour garder l'ensemble de la séquence rapide. */
  transition:opacity 1.1s var(--ease), visibility 0s 1.1s;
}
.loader.is-hidden{opacity:0; visibility:hidden; pointer-events:none;}
.loader__square{
  position:relative;
  width:var(--loader-sq); height:var(--loader-sq);
  /* Durée pilotée par main.js (--expand-dur), pas figée en dur : sinon
     l'animation visuelle du carré se termine avant que le JS ne
     déclenche la disparition du loader (EXPAND étant plus long que
     l'ancien 2s fixe), créant un vide immobile entre les deux, le bug
     de "transition manquante vers le Hero" signalé.
     Courbe volontairement LINÉAIRE ici (pas var(--ease)) : la courbe
     générale de la page, cubic-bezier(.16,1,.3,1), a ses deux points de
     contrôle à y=1, donc atteint visuellement ~99% de sa valeur finale
     très tôt puis rampe de façon quasi imperceptible sur le reste de la
     durée, donc le carré "semblait" fini bien avant que le JS ne déclenche
     réellement la disparition du loader (la vraie cause des "4 à 5
     secondes" de fond uni signalées, malgré des durées déjà synchronisées
     depuis la 37e passe). Une courbe linéaire fait coïncider la
     progression perçue et la progression réelle du minutage.
     width/height (remplace transform:scale(), voir is-expand plus bas,
     redemandé "c'est le mouvement final des × vers les coins qui pilote
     l'expansion du cadre, plutôt qu'une expansion indépendante") : le
     carré, centré par le flex de .loader, grandit symétriquement depuis
     son centre en gagnant simplement en largeur/hauteur - même effet
     visuel que l'ancien scale(), mais sans calcul JS (getBoundingClientRect/
     scaleX/scaleY) : sa cible (100vw/100vh) coïncide par construction
     avec le viewport, exactement là où les "×" atterrissent au même
     instant (is-expand, même --expand-dur), plus besoin de mesurer quoi
     que ce soit à l'exécution. */
  transition:width var(--expand-dur, 2s) linear, height var(--expand-dur, 2s) linear, opacity var(--expand-dur, 2s) linear;
}
/* Rebond avant l'expansion : le carré se compresse légèrement d'abord,
   avant de repartir dans l'expansion vers le rectangle plein écran, pas
   une expansion linéaire directe depuis sa taille de repos. Courbe
   ease-in-out (accélère d'abord, puis décélère à l'approche de la cible)
   pour un rétrécissement plus fluide, ni un dépassement/rebond sec type
   "back", ni un départ brusque façon var(--ease) (qui, elle, décélère
   fortement dès le début, sans accélération initiale). Cette règle
   déclare sa PROPRE transition (plus courte que celle du rebond
   d'expansion ci-dessous) : is-bounce est retirée par JS au moment où
   is-expand est ajoutée, donc les deux ne se chevauchent jamais et
   l'expansion récupère bien la transition longue/linéaire de base. */
.loader.is-bounce .loader__square{
  transition:transform .26s cubic-bezier(.45, 0, .55, 1);
  transform:scale(.88);
}
.loader.is-expand .loader__square{
  width:100vw; height:100vh;
  opacity:0;
}
/* Repris (demandé, voir "Animation-Loader.mov" fourni en référence) : les
   "×" ne convergent plus directement de hors-écran vers les coins du
   carré en un seul mouvement, mais en 2 temps - d'abord jusqu'au CENTRE
   exact du carré, où les 4 se rejoignent en un seul point (is-centering),
   puis s'écartent depuis ce centre vers leurs coins respectifs
   (is-converging). Une rotation continue (transform, seule propriété
   transitionnée en plus de top/left/opacity) tourne SUR TOUTES LES
   PHASES à la fois (même valeur cible, 360deg, posée par les 4 règles
   ci-dessous, is-expand incluse ; la transition ne redémarre donc jamais
   au passage de l'une à l'autre, elle continue) : avec le glyphe "×"
   existant, une rotation par paliers de 45° suffit à alterner
   visuellement entre "×" et "+" (les deux ne sont qu'une rotation l'un
   de l'autre), l'effet observé dans la référence, sans changer le
   caractère lui-même.
   Centrage posé en LAYOUT (taille fixe + marges négatives), PAS en
   transform:translate(-50%,-50%) comme avant : combiner un centrage par
   translate avec une rotation est la même incohérence de rendu
   (composition Safari) déjà rencontrée et corrigée sur d'autres "+" du
   site (portfolio-h__plus, references__plus) - réglée ici de la même
   façon, en amont, plutôt que d'attendre qu'elle se manifeste.
   Plus gros (demandé) : taille doublée (1.3rem -> 2.6rem).
   Durées pilotées par --phase-dur/--drift-dur/--expand-dur (posées par
   main.js sur #loader, héritent vers ces enfants - PLUS sur
   #loaderSquare comme avant : les "×" sont sortis de ce conteneur, voir
   plus bas et le HTML), pas figées en dur : même principe que l'ancien
   --converge-dur, pour que chaque étape ne se déclenche jamais avant que
   le mouvement de position de la précédente ne soit visuellement
   terminé. --rotate-dur retirée (la rotation n'est plus une transition
   CSS mais un angle posé image par image en JS, voir .loader__x__spin
   plus bas et main.js).
   position:fixed (remplace absolute, redemandé - "les × doivent revenir
   sur les 4 coins EXACTS de l'écran, et c'est ce mouvement qui pilote
   l'expansion du cadre") : les "×" ont quitté #loaderSquare pour devenir
   enfants directs de .loader (voir HTML) - fixed les positionne
   directement par rapport au VRAI viewport, jamais faussé par un
   transform pris par #loaderSquare en cours de route (le rebond,
   is-bounce, juste plus bas, en pose un). top/left à 50% (is-centering)
   ou 0%/100% (is-expand, coins réels de l'écran) ont donc un sens direct
   et fixe, aucune mesure JS requise pour ces 2 étapes ; seule la
   position "proche du coin du petit carré" (is-converging/is-drifting,
   juste en dessous) a besoin d'un calc() puisque le carré, lui, reste
   centré/dimensionné en unités relatives au viewport (--loader-sq, voir
   .loader).
   Bug corrigé (signalé, "les × ne reviennent pas à la fin de la vidéo
   pour étirer le cadre... ils restent au large") : is-expand manquait à
   la liste ci-dessous (opacity/transform) - au moment où is-drifting est
   retirée (voir main.js), plus aucune des classes listées ne matchait,
   l'opacity retombait donc à la valeur de base (0, voir .loader__x juste
   en dessous) et démarrait un fondu de .7s (opacity, voir sa propre
   transition) EN PARALLÈLE du déplacement vers les coins (450ms, voir
   .loader.is-expand .loader__x plus bas) : les "×" s'effaçaient sur
   place au lieu de rejoindre visiblement les coins. */
.loader__x{
  position:fixed;
  z-index:1;
  width:2.6rem; height:2.6rem;
  margin-top:-1.3rem; margin-left:-1.3rem;
  display:flex; align-items:center; justify-content:center;
  font-family:var(--display); color:var(--gold);
  font-size:2.6rem; line-height:1;
  opacity:0;
  /* transform retiré (portait un tour ponctuel 0deg->360deg à l'arrivée,
     via transition) : la rotation vit désormais exclusivement sur
     .loader__x__spin, span imbriqué piloté en JS (voir main.js et son
     propre commentaire, plus bas) - continue et liée à la progression du
     compteur, pas un simple aller ponctuel. */
  transition:top var(--phase-dur, 650ms) var(--ease), left var(--phase-dur, 650ms) var(--ease), opacity .7s var(--ease);
}
.loader__x--tl{top:-60vh; left:-60vw;}
.loader__x--tr{top:-60vh; left:calc(100% + 60vw);}
.loader__x--bl{top:calc(100% + 60vh); left:-60vw;}
.loader__x--br{top:calc(100% + 60vh); left:calc(100% + 60vw);}
.loader.is-centering .loader__x,
.loader.is-converging .loader__x,
.loader.is-drifting .loader__x,
.loader.is-expand .loader__x{
  opacity:1;
}
.loader.is-centering .loader__x{top:50%; left:50%;}
/* Décalé de 1.4rem vers l'extérieur du bord du petit carré sur chaque
   axe (demandé, "éloigne les x des coins... qu'ils ne soient pas sur les
   coins") - convergeaient jusqu'ici exactement sur le coin du carré.
   calc() plutôt qu'un simple top/left:0%/100% (comme avant, quand les
   "×" vivaient DANS #loaderSquare) : ils sont désormais fixed par
   rapport au viewport (voir plus haut), donc leur cible doit retrouver
   le coin du carré en partant du CENTRE du viewport (50%) et en
   reculant/avançant de la moitié de sa taille (--loader-sq / 2, posée
   sur .loader). */
.loader.is-converging .loader__x--tl{top:calc(50% - (var(--loader-sq) / 2) - 1.4rem); left:calc(50% - (var(--loader-sq) / 2) - 1.4rem);}
.loader.is-converging .loader__x--tr{top:calc(50% - (var(--loader-sq) / 2) - 1.4rem); left:calc(50% + (var(--loader-sq) / 2) + 1.4rem);}
.loader.is-converging .loader__x--bl{top:calc(50% + (var(--loader-sq) / 2) + 1.4rem); left:calc(50% - (var(--loader-sq) / 2) - 1.4rem);}
.loader.is-converging .loader__x--br{top:calc(50% + (var(--loader-sq) / 2) + 1.4rem); left:calc(50% + (var(--loader-sq) / 2) + 1.4rem);}
/* is-drifting : la cible top/left reste IDENTIQUE à is-converging (1.4rem,
   "sans y être collés" - léger décalage vers l'extérieur du coin du petit
   carré, redemandé) - l'ancien tremblement "s'éloignent, se rapprochent"
   qui vivait ici (sur .loader__x__breathe, span imbriqué en @keyframes)
   est retiré (redemandé, "peut-on annuler ceci ?"), remplacé par la
   rotation pilotée en JS sur .loader__x__spin (voir son commentaire plus
   bas et main.js) - aucun rapport avec le top/left, inchangé. Le
   top/left DOIT quand même être redéclaré ici (identique à
   is-converging) : cette dernière est retirée par main.js au moment où
   is-drifting est ajoutée (évite toute ambiguïté de cascade entre 2
   règles de même spécificité) - sans ça, les "×" retomberaient sur le
   repli hors-écran de .loader__x--tl/tr/bl/br (aucune classe de
   position active). */
.loader.is-drifting .loader__x{
  transition:top var(--drift-dur, 2967ms) linear, left var(--drift-dur, 2967ms) linear, opacity .7s var(--ease);
}
.loader.is-drifting .loader__x--tl{top:calc(50% - (var(--loader-sq) / 2) - 1.4rem); left:calc(50% - (var(--loader-sq) / 2) - 1.4rem);}
.loader.is-drifting .loader__x--tr{top:calc(50% - (var(--loader-sq) / 2) - 1.4rem); left:calc(50% + (var(--loader-sq) / 2) + 1.4rem);}
.loader.is-drifting .loader__x--bl{top:calc(50% + (var(--loader-sq) / 2) + 1.4rem); left:calc(50% - (var(--loader-sq) / 2) - 1.4rem);}
.loader.is-drifting .loader__x--br{top:calc(50% + (var(--loader-sq) / 2) + 1.4rem); left:calc(50% + (var(--loader-sq) / 2) + 1.4rem);}
/* .loader__x__breathe (tremblement "s'éloignent/se rapprochent") et son
   remplaçant temporaire .loader__x__spin en @keyframes/animation infinie
   RETIRÉS (demandé, "les + s'éloignent et se rapprochent - peut-on
   annuler ceci ?"). Nouvelle rotation (redemandé, description précise) :
   pilotée entièrement en JS (main.js, dans tickCounter - même boucle
   rAF que le compteur/la barre de progression, même valeur p 0-1) sur ce
   span - rapide de 0 à 70% de la progression, décélération progressive
   (vitesse angulaire décroissant linéairement jusqu'à 0) de 70 à 100%,
   pour un arrêt en douceur pile à la fin, jamais un arrêt sec. Sens
   opposé par côté (demandé, "les 2 de droite dans le sens horaire, les 2
   de gauche dans le sens anti-horaire") : géré via le signe de l'angle
   côté JS (positif à droite, négatif à gauche), cette règle CSS reste
   volontairement neutre (aucune @keyframes ici, juste display pour un
   bon comportement de `transform`). */
.loader__x__spin{display:inline-block;}
/* is-expand (redemandé, "une fois la vidéo terminée, les × reviennent
   rapidement et directement sur les 4 coins EXACTS de l'écran - et c'est
   ce mouvement final qui pilote l'expansion du cadre, plutôt qu'une
   expansion indépendante") : coins réels du viewport (0%/100%, les "×"
   étant fixed, voir plus haut) - MÊME classe, MÊME --expand-dur, MÊME
   courbe (linear) que .loader.is-expand .loader__square ci-dessus, pour
   que les "×" et le cadre arrivent voyagent et arrivent ensemble, comme
   si les "×" tiraient le cadre jusqu'aux coins plutôt que 2 animations
   indépendantes qui coïncident par coïncidence. is-drifting retirée au
   passage (voir main.js), même raison que ci-dessus. */
.loader.is-expand .loader__x{
  transition:top var(--expand-dur, 450ms) linear, left var(--expand-dur, 450ms) linear, opacity .7s var(--ease);
}
.loader.is-expand .loader__x--tl{top:0; left:0;}
.loader.is-expand .loader__x--tr{top:0; left:100%;}
.loader.is-expand .loader__x--bl{top:100%; left:0;}
.loader.is-expand .loader__x--br{top:100%; left:100%;}
/* .loader__frame : nouveau conteneur (signalé, "les lignes du carré
   doivent se former en petit et s'étendre, comme la vidéo") - avant,
   chaque bordure "se dessinait" en s'étirant sur sa propre longueur
   (scaleX/Y depuis un bord, transform-origin), le carré gardant sa
   taille finale du début à la fin ; seul le TRAIT semblait progresser,
   pas le carré lui-même. Ici, les 4 bordures restent statiques (plus
   de scaleX/Y propre) à l'intérieur d'un cadre unique qui, lui, grandit
   depuis un point quasi nul jusqu'à sa taille pleine (scale, même
   mécanique/durée que .loader__logo juste en dessous) : le carré entier
   se forme donc en miniature au centre puis s'étend, exactement comme
   la vidéo, plutôt que de sembler "se tracer" sur place. */
.loader__frame{
  position:absolute; inset:0; z-index:1;
  transform:scale(.02);
  opacity:0;
  transition:opacity var(--phase-dur, 650ms) var(--ease), transform var(--phase-dur, 650ms) var(--ease);
}
.loader.is-square .loader__frame{opacity:1; transform:scale(1);}
.loader__edge{
  position:absolute;
  background:rgba(214,183,125,.55);
}
.loader__edge--t{top:0; left:0; width:100%; height:1px;}
.loader__edge--b{bottom:0; left:0; width:100%; height:1px;}
.loader__edge--l{top:0; left:0; width:1px; height:100%;}
.loader__edge--r{top:0; right:0; width:1px; height:100%;}
/* Remplit tout le carré (demandé), plus 56% centré avec du vide autour :
   inset:0 (au lieu de left/top:50% + translate) couvre exactement la
   boîte du carré, object-fit:cover garantit un cadrage propre quel que
   soit un léger écart entre le ratio du carré (toujours 1:1) et celui
   de la vidéo source (640×640, donc déjà 1:1 - cover n'a ici presque
   rien à recadrer, une sécurité plutôt qu'un besoin strict). transform
   réduit à scale() seul pour l'effet d'entrée (plus besoin de translate
   pour centrer, inset:0 s'en charge déjà).
   Bug corrigé (constaté en vérifiant le rendu, une fois la vidéo passée
   à 100%×100% du carré) : la vidéo, dernière dans l'ordre du DOM,
   passait AU-DESSUS de la bordure (.loader__edge) et des "×"
   (.loader__x), la couvrant entièrement (vérifié par
   elementFromPoint()) - invisible tant qu'elle ne couvrait que 56% du
   centre, redevenu un vrai problème une fois étendue à tout le carré.
   z-index:0 ici, z-index:1 sur les "×"/la bordure : les garde toujours
   au-dessus, quel que soit l'ordre dans le DOM.
   scale(.02) au repos (était .6, demandé "démarre la vidéo quand le
   carré est tout petit puis étend-la jusqu'à sa taille normale") +
   transition calée sur var(--phase-dur) (était .6s fixe) : is-logo se
   déclenche désormais EN MÊME TEMPS que is-converging/is-square (voir
   main.js) - la vidéo part donc d'un point quasi nul, pile au centre où
   les "×" se rejoignent, et grandit jusqu'à scale(1) exactement au même
   rythme qu'eux et que les bordures du carré (même durée --phase-dur),
   pour arriver tous ensemble à leur taille/position finale. Taille et
   apparence finales inchangées ("comme elle l'est actuellement"). */
.loader__logo{
  position:absolute; inset:0; z-index:0;
  width:100%; height:100%;
  object-fit:cover;
  transform:scale(.02);
  opacity:0;
  transition:opacity var(--phase-dur, 650ms) var(--ease), transform var(--phase-dur, 650ms) var(--ease);
}
.loader.is-logo .loader__logo{opacity:1; transform:scale(1);}
/* Sortie en fondu seul, sans réduction (demandé, "ne la réduit pas,
   laisse la disparaître en fade out") : is-logo-out n'agit plus que sur
   opacity, transform reste à scale(1) (posé par is-logo ci-dessus)
   jusqu'à la disparition complète. Le relais vers un monogramme statique
   (crossfade vidéo -> image figée, 190e/191e passes) est retiré : la
   nouvelle vidéo (Animation-Loader-Logo.mp4, 10s) contient déjà le
   monogramme construit ET son temps d'arrêt final dans l'image, plus
   besoin de le prolonger artificiellement avec une image à part
   ("ne cale pas le logo, il est déjà dans la vidéo", demandé). */
.loader.is-logo-out .loader__logo{opacity:0;}
/* Décalage recalculé (moitié de la nouvelle taille du carré, 46vw/340px
   -> 23vw/170px), pour rester juste sous son bord bas comme avant. */
/* font-family:var(--sans) (était var(--display), demandé "changer la
   police du compteur en utilisant la police MarkOT") : var(--sans)
   pointe déjà sur "Mark OT" (voir :root), aucune nouvelle police à
   charger. */
.loader__counter{
  position:absolute; left:50%; top:calc(50% + min(23vw, 170px) + 1.8rem);
  transform:translateX(-50%);
  font-family:var(--sans); color:var(--text);
  font-size:1.15rem; letter-spacing:.1em;
  opacity:0;
  transition:opacity .5s var(--ease);
}
.loader.is-logo .loader__counter{opacity:1;}
.loader.is-logo-out .loader__counter{opacity:0;}
/* .loader__progress (nouveau, redemandé "affiche une barre fine qui se
   remplit au-dessus du compteur") : même formule top que .loader__counter
   juste au-dessus (calc(50% + min(23vw, 170px) + Xrem)), un rem plus
   petit (1.8 -> 1.1) pour se poser juste au-dessus de lui plutôt qu'à sa
   propre hauteur. Même transition opacity/mêmes classes déclencheuses
   (is-logo/is-logo-out) que le compteur : apparaît et disparaît
   exactement en même temps que lui, pas de décalage entre les deux.
   Largeur du remplissage pilotée par main.js (tickCounter, même valeur
   p que le compteur numérique juste en dessous) : une seule source de
   progression pour les deux, jamais desynchronisables. */
.loader__progress{
  position:absolute; left:50%; top:calc(50% + min(23vw, 170px) + 1.1rem);
  transform:translateX(-50%);
  width:120px; height:2px;
  background:rgba(255,255,255,.18);
  opacity:0;
  transition:opacity .5s var(--ease);
}
.loader.is-logo .loader__progress{opacity:1;}
.loader.is-logo-out .loader__progress{opacity:0;}
.loader__progress-fill{
  display:block;
  height:100%; width:0%;
  background:var(--gold);
}

/* ==========================================================================
   SÉPARATEUR : ligne fine + symbole + courte chute verticale, entre blocs
   La ligne (deux segments de part et d'autre du symbole) part de 0 et
   s'étend jusqu'à sa largeur maximale au fil du scroll (voir main.js) :
   jamais affichée déjà à sa taille finale. Construit en ligne (flex, pas
   de chevauchement/fond plaqué derrière le symbole) pour rester une fine
   ligne quel que soit le fond du bloc, sans jamais afficher de bloc plein.
   ========================================================================== */
/* margin-block:var(--section-gap) (captures "Ecarts-Padding", demandé) :
   SEUL levier de l'écart avant/après un séparateur, appliqué identiquement
   aux 4 (#sep1/#sep2/#sep3/#sep5) - pour que ça marche, chaque bloc voisin
   doit lui-même arriver à zéro écart propre sur le bord qui touche un
   séparateur (voir .bloc2/.bloc6/.faq/.bloc--4, plus bas), sans quoi leur
   propre padding/margin s'ajouterait à celui-ci et recréerait une
   incohérence. Seule exception documentée : #sep3, voir son commentaire. */
.separator{
  position:relative;
  display:flex; flex-direction:column; align-items:center;
  padding-block:clamp(1.5rem, 3vw, 2.5rem);
  margin-block:var(--section-gap);
  z-index:1;
}
.separator__row{
  display:flex; align-items:center;
  gap:.7em;
}
.separator__line{
  display:block;
  width:0;
  height:1px;
  background:rgba(214,183,125,.35);
  will-change:width;
}
/* Plus épais (demandé) : le "+" héritait de font-weight:300 (posé sur
   body pour --sans), la plus fine des graisses du site, jamais
   surchargée ici. Gloock n'existe qu'en 400 (un seul fichier) : le
   passage à 700 reste un gras synthétisé par le navigateur (même
   principe déjà utilisé pour les titres, .section-title etc en 600),
   complété par un léger -webkit-text-stroke pour un résultat plus
   fiable/constant d'un navigateur à l'autre que la synthèse seule. */
.separator__symbol{
  flex:0 0 auto;
  font-family:var(--display); font-weight:700; font-size:2.4rem; line-height:1;
  color:var(--gold);
  -webkit-text-stroke:1px var(--gold);
  display:inline-block;
  will-change:transform;
}
/* Réduit sur petit écran (audit responsive, "réduire les textes sur
   petit écran") : 2.4rem (38.4px) était fixe partout, disproportionné
   sur un écran étroit pour un simple symbole "+" isolé entre 2 traits.
   tickSeparators (main.js) mesure la largeur RÉELLE du symbole à chaque
   frame pour calculer la longueur des traits qui l'encadrent : aucun
   changement JS nécessaire, le calcul s'adapte de lui-même à cette
   nouvelle taille. */
@media (max-width:860px){
  .separator__symbol{font-size:1.7rem;}
}
.separator__drop{
  margin-top:.5rem;
  width:1px; height:clamp(1rem, 2vw, 1.6rem);
  background:rgba(214,183,125,.3);
}
/* Ancienne approche (139e passe, puis compensations #sep1/#sep2/#sep3 ad
   hoc par calc()) abandonnée (signalé, captures "Ecarts-Padding-01" à
   "-05" : toujours asymétrique localement ET incohérente d'un séparateur
   à l'autre - 0/56px de part et d'autre de #sep1, 24/88px pour #sep3,
   valeurs différentes de #sep2 à #sep5). .framed-block (Bloc 2, image
   collée des deux côtés) n'a plus besoin de --frame-gap : #sep1/#sep2
   reçoivent désormais le même margin-block:var(--section-gap) que tous
   les autres séparateurs, sans override. */
.framed-block{position:relative;}
/* #sep3 seul reste un cas particulier : .bio__pin conserve un léger
   padding-bottom propre (voir son commentaire, lié au dégradé de fond
   50/50 qui doit continuer à "straddler" bio__frame - y toucher romprait
   ce réglage, hors sujet ici), qui s'ajoute à sa gauche au margin-top de
   #sep3 - la logique originale de cette règle retranchait donc TOUT ce
   padding (clamp(1rem, 2vw, 1.5rem)) du seul margin-top, laissant
   margin-bottom hérité intact (--section-gap plein) : le total "ressenti"
   de chaque côté (padding + margin-top d'un côté, margin-bottom seul de
   l'autre) retombait bien sur --section-gap des deux côtés, mais #sep3
   lui-même (sa boîte, sa ligne/son "+" visibles) se retrouvait décalé
   tout près de .bio (8px mesurés) et loin de #bloc6 (24px) - PAS à
   égale distance des deux blocs eux-mêmes, comme demandé (audit
   responsive, "le séparateur... doit être déplacé pour se retrouver à
   égale distance entre les deux blocs, en gardant les mêmes
   espacements globaux"). Corrigé en répartissant la MOITIÉ de la
   compensation de chaque côté (au lieu de la totalité d'un seul côté) :
   la somme des deux marges reste rigoureusement identique (donc le
   total bio<->bloc6 aussi, "espacements globaux" inchangés), seule la
   position de #sep3 À L'INTÉRIEUR de cet espace se recentre. */
#sep3{
  margin-top:calc(var(--section-gap) - clamp(1rem, 2vw, 1.5rem) / 2);
  margin-bottom:calc(var(--section-gap) - clamp(1rem, 2vw, 1.5rem) / 2);
}
/* #sep6/#sep7 : l'ancien resserrement volontaire ("rapproche les
   séparateurs haut et bas" du bloc Avis) n'a plus lieu d'être - Avis a
   quitté cet emplacement (nouvel ordre de page, demandé). #sep6 a depuis
   été supprimé à son tour (redemandé, voir plus bas) ; #sep7 a reçu son
   propre traitement (voir plus bas). */
/* #sep1 : le fond clair de #momentStat qui justifiait un dégradé
   spécial (même famille que #sep2/#sep4a ci-dessous) a disparu avec la
   nouvelle simplification du bloc Chrono - #momentStat (fond blanc
   séparé) est supprimé, la phase 2 vit maintenant sur le même fond
   sombre que Bloc 2 juste après. #sep1 redevient donc un .separator
   par défaut, sans override. */
/* #sep2 : séparateur entre 2 couleurs de fond DIFFÉRENTES (.bloc2 --bg
   -> var(--white), .bloc--3 juste après) - .bloc2 est redevenu uni en
   --bg jusqu'ici (retiré, voir son commentaire : l'essai de dégradé
   --bg -> #c8cfcd sur .bloc2__media a été annulé). Même traitement que
   l'historique #sep4a (ci-dessous, désormais fond blanc uni des 2
   côtés, plus une frontière) : le "+" doit tomber pile sur la frontière
   entre les 2 couleurs, pas seulement au milieu de la boîte du
   séparateur (qui n'est pas son centre optique, à cause de
   .separator__drop sous sa rangée). Dégradé 50/50 ci-dessous en repli
   statique, remplacé par le pourcentage exact mesuré au resize
   (positionColorBoundarySeparators, main.js, regroupé avec #sep7,
   voir plus bas). margin-block:0 + padding-block
   augmenté : la marge par défaut de .separator est transparente, elle
   laisserait voir passer le fond sombre de body de part et d'autre -
   seul le padding, couvert par le fond posé ici, évite toute bande
   sombre résiduelle. Somme margin+padding inchangée (espace total
   ressenti identique à un .separator par défaut). */
#sep2{
  margin-block:0;
  padding-block:calc(var(--section-gap) + clamp(1.5rem, 3vw, 2.5rem));
  background:linear-gradient(to bottom, var(--bg) 50%, var(--white) 50%);
}
/* #sep4a : fond blanc uni - Bloc 3 (.bloc--3) et Portfolio
   (.portfolio-h) partagent tous deux var(--white), ce n'est plus une
   frontière entre 2 couleurs différentes (contrairement à #sep2
   ci-dessus) : pas besoin de mesure JS ici, juste margin-block:0 +
   padding-block augmenté, même raison que #sep2 (éviter toute bande
   sombre résiduelle dans la marge transparente par défaut). */
#sep4a{
  margin-block:0;
  padding-block:calc(var(--section-gap) + clamp(1.5rem, 3vw, 2.5rem));
  background:var(--white);
}
/* #sep7 (nouvel ordre de page, demandé) : même frontière entre 2
   couleurs différentes que #sep2 ci-dessus, même traitement
   (margin-block:0 + padding-block augmenté + dégradé 50/50 en repli
   statique, pourcentage exact mesuré au resize sur le "+",
   positionColorBoundarySeparators, main.js, #sep2 y est désormais
   regroupé avec lui).
   #sep5, #sep6, #sep8 et #sep9 tous supprimés (Offres -> Références,
   Références -> FAQ, et bloc Avis passés en fond blanc juxtaposé sans
   séparateur, voir leurs commentaires plus bas) : aucun des quatre n'en
   fait plus partie non plus. */
/* #sep7 (FAQ blanc -> CTA --bg, nouvelle frontière depuis que .faq est
   passé au blanc) : même traitement que #sep2 plus haut - measuré au
   resize (positionColorBoundarySeparators, main.js),
   dégradé 50/50 ici en repli statique. Remplace l'ancien .separator par
   défaut (margin-block:var(--section-gap) seul suffisait tant que FAQ
   et CTA partageaient le même fond sombre). */
#sep7{
  margin-block:0;
  padding-block:calc(var(--section-gap) + clamp(1.5rem, 3vw, 2.5rem));
  background:linear-gradient(to bottom, var(--white) 50%, var(--bg) 50%);
}
/* #sep8 (Portfolio -> Avis) et #sep9 (Avis -> Bio) supprimés (redemandé) :
   plus d'élément dans le DOM à ces 2 endroits, voir index.html - les 3
   blocs (Portfolio, Avis, Bio) s'enchaînent désormais directement, tous
   en fond blanc. */
/* Bloc 2 est le seul bloc image actuellement encadré par deux
   séparateurs (sep1 avant, sep2 après) : une ligne verticale fine relie
   le "+" du premier au second, le long du bord du bloc image
   (position/hauteur calculées au scroll, voir main.js). */
.frame-connector{
  position:absolute;
  width:1px;
  background:rgba(214,183,125,.35);
  transform-origin:top;
  transform:scaleY(0);
  will-change:transform;
  pointer-events:none;
}

/* ==========================================================================
   MOTIF RÉCURRENT : fines lignes et symboles "+", en écho au loader
   Purement décoratif (aria-hidden, pointer-events:none) : jamais devant le
   texte ou les images, toujours en retrait (faible opacité, z-index bas).
   ========================================================================== */
.motif{
  position:absolute;
  z-index:0;
  pointer-events:none;
  user-select:none;
  font-family:var(--display); font-weight:400;
  font-size:clamp(1.65rem, 3vw, 2.4rem);
  line-height:1;
  color:rgba(214,183,125,.32);
}
.motif--dark{color:rgba(88,101,106,.24);}
.motif--top-left{top:clamp(1.6rem, 4vw, 3rem); left:clamp(1.1rem, 3vw, 2.6rem);}
.motif--top-right{top:clamp(1.6rem, 4vw, 3rem); right:clamp(1.1rem, 3vw, 2.6rem);}
.motif--bottom-left{bottom:clamp(1.6rem, 4vw, 3rem); left:clamp(1.1rem, 3vw, 2.6rem);}
.motif--bottom-right{bottom:clamp(1.6rem, 4vw, 3rem); right:clamp(1.1rem, 3vw, 2.6rem);}
.motif--inline{position:relative; top:auto; left:auto; display:inline-block; margin-bottom:.6rem;}

/* ==========================================================================
   RÉVÉLATION AU SCROLL, flou -> net + opacité, continue (famille commune)
   ========================================================================== */
.reveal-word{display:inline-block; will-change:filter, opacity, transform;}
/* "Split by line" (redemandé, "tous les textes doivent avoir une
   animation split by line") : système générique GSAP (js/motion.js,
   splitIntoLines/initSplitLines) qui remplace la quasi-totalité des
   révélations du site (à l'exception explicite du Bloc 3, qui garde son
   "scrubbed text reveal" propre, continu, réversible au scroll - ici
   c'est un déclenchement UNIQUE par ligne, comme demandé pour
   contraster avec le Bloc 3). Chaque ligne RENDUE (mesurée après layout,
   donc valable quel que soit le point de rupture réel du texte à cette
   largeur, pas le code source) est encapsulée dans un masque
   (overflow:hidden) contenant un span qui glisse depuis le bas
   (translateY) en fondu. display:block sur les deux : chaque ligne
   devient sa propre boîte empilée verticalement (le texte source, un
   nœud continu, perd sa capacité à se ré-envelopper tout seul une fois
   découpé - re-découpage complet au resize, voir motion.js). */
/* padding-bottom + margin-bottom négatif égal (signalé : "le bas des
   lettres (ç, p, g...) est coupé") - la hauteur du masque suit line-height,
   qui ne réserve pas toujours assez de place sous la ligne de base pour
   les descendantes une fois le texte strictement clipé par overflow:
   hidden (contrairement au texte non masqué, où un léger dépassement du
   line-box ne se voit simplement pas). padding-bottom ajoute de la place
   à l'INTÉRIEUR du masque (donc dans la zone encore visible, pas coupée) ;
   margin-bottom négatif de la même valeur compense à l'extérieur pour ne
   pas élargir l'interlignage entre les lignes (déjà réglé au pixel près,
   voir plus haut). */
.reveal-line-mask{ display:block; overflow:hidden; padding-bottom:0.25em; margin-bottom:-0.25em; }
.reveal-line{ display:block; will-change:transform, opacity; }

/* Nombre d'or (prompt-page-pro.md) : hiérarchie titre/texte courant NON
   forcée à un rapport ~φ ici. Chaque titre du site (celui-ci et tous les
   autres, .hero__title, .bloc2__title, .bloc--4__title, .cta__title...)
   pèse actuellement 4 à 6× la taille du texte courant qui l'accompagne,
   un contraste large et délibéré (grand titre doré + corps de texte
   modeste), affiné sur ~130 passes validées par le client. Le ramener à
   φ (~1,6×) demanderait de réduire drastiquement CHAQUE titre du site ou
   d'agrandir CHAQUE texte courant, une refonte typographique globale non
   demandée, à haut risque sans moyen de vérifier le rendu visuellement
   dans cette session : exactement le cas où le brief demande de laisser
   primer le bon sens plutôt que de forcer le ratio. */
.section-title{
  font-family:var(--display); font-weight:600;
  /* Taille/police uniformisées sitewide (redemandé) : --fs-title, Gloock,
     identique à .hero__title/.bloc2__title/.bloc--4__title/.bloc6__title/
     .cta__title/.portfolio-h__title - plus de formule ad hoc propre à ce
     titre. Couleur : variante "fond sombre" (--title-on-dark) - ce titre
     est utilisé directement par FAQ (fond --bg sombre) ; References
     l'utilise aussi mais avec son propre override vers --ink (fond
     blanc), voir plus bas. */
  font-size:var(--fs-title);
  line-height:1;
  color:var(--text-strong);
  /* Capitales retirées, seule la 1ère lettre en majuscule (redemandé) :
     text-transform:lowercase sur tout le texte + ::first-letter en
     uppercase - la seule façon fiable en CSS pur d'obtenir "sentence
     case" indépendamment de la casse déjà présente dans le HTML source
     (text-transform:capitalize aurait mis une majuscule à CHAQUE mot,
     pas seulement le premier). Répété à l'identique pour tous les titres
     ET sous-titres du site (redemandé, "applique cette règle à tous les
     titres/sous-titres, pas seulement aux 4 blocs listés") : .hero__title,
     .moment__title, .moment__kicker, .chrono2__kicker, .bloc2__title,
     .bloc2__kicker, .bloc6__title, .bloc--4__title, .bloc--4__kicker,
     .cta__title, .cta-card__title, .portfolio-h__title. Volontairement
     EXCLUS (casse déjà différente par un choix explicite antérieur, pas
     un oubli) : .references__subtitle et .steps__title, tous deux en
     majuscules pleines (text-transform:uppercase), une décision de
     style distincte de la "sentence case" - .portfolio-h__card-title
     les a rejoints (redemandé, "en upper case", voir son propre
     commentaire plus bas). */
  text-transform:lowercase;
  text-align:center;
  margin-bottom:clamp(2.2rem, 4.6vw, 3.4rem);
}
/* Bug réel trouvé (signalé, un titre découpé sur 2 lignes - "Vous avez
   sûrement / regardé la photo" - capitalisait le 1er mot des DEUX
   lignes, pas seulement la 1ère lettre de la phrase entière) :
   `.reveal-line:first-of-type` semblait cibler LA première ligne du
   titre, mais chaque ligne est enveloppée dans son propre
   .reveal-line-mask (splitIntoLines, motion.js) - à l'intérieur de
   CHAQUE masque, son unique .reveal-line est - par définition - le
   "premier de son type" PARMI LES ENFANTS DE CE MASQUE, donc la règle
   s'appliquait à TOUTES les lignes, pas juste la 1ère. Corrigé en
   ciblant d'abord .reveal-line-mask:first-of-type (qui, lui, désigne
   sans ambiguïté LE premier masque parmi tous les masques-frères du
   même titre), puis son .reveal-line descendant - répété à l'identique
   partout où ce motif est utilisé (tous les titres/sous-titres du
   site, voir liste juste au-dessus). */
.section-title::first-letter, .section-title .reveal-line-mask:first-of-type .reveal-line::first-letter{text-transform:uppercase;}

/* ==========================================================================
   HEADER, non collant : défile normalement avec la page. Version fixed +
   fond plein essayée (audit design-taste-frontend, "plus aucun moyen
   rapide de revenir au contact sans scroller jusqu'en bas") puis retirée
   (signalé, "trop présent") : un bouton dédié sous les 3 offres
   (.offers__cta, voir Bloc 6 plus bas) couvrait ce besoin. Ce même besoin
   est désormais couvert bien plus tôt par .hero__cta (redemandé, audit UX
   externe - "capter les visiteurs déjà décidés dès l'arrivée") : le CTA
   du header redevient un simple lien de rattrapage discret, voir
   .site-header__cta plus bas.
   ========================================================================== */
.site-header{
  position:absolute; inset:0 0 auto 0; z-index:20;
  padding:1.4rem var(--gutter);
  display:flex; justify-content:flex-end;
}
.site-header__inner{
  width:100%; max-width:var(--container);
  display:flex; align-items:center; justify-content:flex-end;
}
/* .site-header__cta (lien texte "Parlons de vos images") remplacé par 3
   icônes (demandé, "remplace-le par des icônes Instagram, LinkedIn,
   mail") : mêmes couleurs/transition que l'ancien lien (var(--text) au
   repos, var(--gold) au survol) pour rester dans le même vocabulaire -
   "visibles mais discrètes" (demandé) : traits fins (stroke-width:1.5),
   pas de fond/cadre, taille modeste (19px). */
.site-header__social{
  display:flex; align-items:center; gap:1.1rem;
}
.site-header__social a{
  display:inline-flex;
  color:var(--text);
  transition:color var(--dur-hover) var(--ease);
}
.site-header__social a:hover, .site-header__social a:focus-visible{color:var(--gold);}
.site-header__social svg{width:19px; height:19px;}

/* ==========================================================================
   HERO : bloc normal, sans épinglage ni effet de sortie
   L'ancienne recherche de transition de sortie (compression, puis
   flou+remontée) est abandonnée : le Hero défile désormais comme
   n'importe quel autre bloc de la page (il disparaît simplement du haut
   de l'écran au fil du scroll). Image et texte visibles immédiatement au
   chargement (pas de séquence d'attente sur fond vide). .hero__visual
   est le SEUL élément avec overflow:hidden (pour clipper le zoom/drift
   de l'image de fond).
   ========================================================================== */
.hero{position:relative;}
/* display:flex/align-items/justify-content retirés (demandé, "le logo
   doit être au centre du premier 1/3 du bloc en hauteur, et le titre/
   sous-titre au centre du 3/4") : un flex centrait .hero__content (et,
   un temps, le logo avec lui) comme UN SEUL groupe sur l'axe vertical -
   ne permet pas de placer le logo et le titre à deux hauteurs
   INDÉPENDANTES l'une de l'autre. Remplacé par du positionnement absolu
   dédié sur chacun des deux (voir .hero__logo/.hero__content plus bas),
   .hero__visual ne servant plus que de repère (position:relative) pour
   leurs pourcentages verticaux. */
.hero__visual{
  position:relative;
  height:100svh;
  overflow:hidden;
  background:var(--bg);
}
/* Parallaxe ajoutée (redemandé, "met un paralaxe sur toutes les
   images... même le hero") : cible .hero__bg (le CONTENEUR de l'image,
   translateY continu scrubbé au scroll), PAS .hero__bg-img elle-même -
   cette dernière porte déjà sa propre animation CSS continue
   (heroDrift, scale+translate3d en boucle, indépendante du scroll) :
   lui ajouter un 2e `transform` géré par GSAP écraserait l'animation
   CSS existante (une seule propriété "transform", un seul gagnant).
   Surdimensionné (height:123%, recentré par top:-11.5%, même formule
   que les autres images du site) : donne de la marge de déplacement à
   la parallaxe sans jamais révéler les bords de .hero__visual
   (overflow:hidden, inchangé). */
.hero__bg{
  position:absolute; left:0;
  width:100%; height:123%; top:-11.5%;
}
.hero__bg-img{
  width:100%; height:100%; object-fit:cover;
  transform:scale(1.06);
  animation:heroDrift 26s ease-in-out infinite alternate;
}
@keyframes heroDrift{
  from{transform:scale(1.06) translate3d(0,0,0);}
  to{transform:scale(1.11) translate3d(-1%, -1%,0);}
}
/* Voile noir à 20 % au-dessus de l’image Hero */
.hero__scrim{
  position:absolute;
  inset:0;
  background:rgba(0, 0, 0, 0.2);
}
/* Positionné indépendamment du logo (demandé, "le titre et sous-titre au
   centre du 3/4 du bloc en hauteur") : top:75% + translate(-50%,-50%)
   place le CENTRE du groupe titre+sous-titre exactement aux 3/4 de la
   hauteur du Hero (100svh), quelle que soit sa propre hauteur - measure
   fiable, contrairement à un margin/padding qui dépendrait de la
   hauteur réelle du texte. max-width (inchangé) continue de plafonner
   la largeur en shrink-to-fit, comme avant le passage en position
   absolue (aucun effet visuel différent sur le retour à la ligne). */
/* top:61.8% (était 75%, signalé "trop bas") : point du nombre d'or
   (1/φ ≈ 0,618) de la hauteur du Hero plutôt qu'un 3/4 arbitraire -
   cohérent avec le reste du vocabulaire "ratio d'or" déjà utilisé sur
   ce bloc (--space-*, voir .hero__logo/.hero__title plus bas), et
   nettement plus haut qu'avant.
   width EXPLICITE ajoutée (était : max-width seul, qui laissait le
   navigateur calculer une largeur "shrink-to-fit") : bug réel trouvé
   (signalé, "le titre repasse sur 3 lignes au lieu de 2") - pour un
   élément en position:absolute avec seulement `left` posé (pas `right`)
   et aucune largeur explicite, l'algorithme shrink-to-fit CSS calcule sa
   largeur disponible comme la distance entre `left` et le bord DROIT du
   bloc englobant - avec left:50%, cette distance ne vaut que la MOITIÉ
   de la largeur du Hero, pas sa largeur totale comme au temps du
   centrage par flexbox (avant cette refonte) : le titre se retrouvait
   donc contraint à une largeur ~2× plus étroite que prévu, d'où le
   retour à la ligne en trop. Une largeur explicite (même valeur que
   max-width) contourne entièrement ce calcul. */
.hero__content{
  position:absolute; top:61.8%; left:50%; transform:translate(-50%, -50%);
  z-index:2; text-align:center;
  /* Élargi (96vw/1400px -> 98vw/1800px) : le titre a besoin de toute la
     largeur disponible pour tenir sur 2 lignes à cette taille de police
     (voir .hero__title plus bas). */
  width:min(98vw, 1800px);
  max-width:min(98vw, 1800px);
}
/* Logo texte (demandé, "Mon Logo Texte... en Haut du hero", puis "plus
   haut, au centre du premier 1/3 du bloc en hauteur") : sorti de
   .hero__content (n'est plus DANS le groupe titre/sous-titre, les deux
   ont désormais chacun leur propre centrage vertical indépendant, voir
   commentaire de .hero__content ci-dessus) - top:16.667% (=1/3 / 2, le
   milieu du premier tiers de 100svh) + translate(-50%,-50%) centre le
   logo pile à ce point, sur les deux axes.
   filter:brightness(0) invert(1) (demandé, "mets le en Blanc") : le
   fichier source (Logo/Text-Logo.png) est en gris ardoise (#58656A,
   couleur --ink), pas blanc - cette combinaison de filtres force
   n'importe quelle couleur opaque à blanc pur tout en préservant
   l'alpha (transparence) du PNG, sans avoir à retraiter le fichier
   lui-même. drop-shadow (même esprit que le text-shadow de
   .hero__title juste en dessous) : lisibilité sur la photo de fond,
   peut se chaîner avec les autres filtres.
   width:33vw (demandé, "1/3 de la fenêtre") - height:auto préserve le
   ratio natif du fichier (1411×208), donc pas de déformation. */
.hero__logo{
  position:absolute; top:16.667%; left:50%; transform:translate(-50%, -50%);
  z-index:2;
  /* +25% (redemandé) : 33vw -> 41.25vw. */
  width:41.25vw;
  height:auto;
  filter:brightness(0) invert(1) drop-shadow(0 4px 20px rgba(0,0,0,.3));
}
/* Un seul retour à la ligne voulu, après "attractivité," (voir index.html,
   un unique <br> à cet endroit précis), pas de retour naturel ailleurs.
   À cette taille de police (calc(clamp(2.6rem,6.6vw,5.8rem) + 30px de
   title-boost), soit ~120px+ sur desktop), une estimation en "ch" sous-
   évalue nettement la largeur réelle nécessaire pour cette police (déjà
   observé sur le titre du Bloc 4) : pas de max-width en ch ici, pour ne
   jamais forcer de retour supplémentaire avant ce point voulu. Le titre
   s'étend sur toute la largeur laissée par .hero__content (élargi
   ci-dessus), le <br> du balisage restant la seule chose qui force un
   retour à la ligne. */
.hero__title{
  display:block;
  font-family:var(--display-semi); font-weight:600; font-style:normal;
  color:var(--gold);
  text-transform:lowercase;
  /* 2x (redemandé, "doit être 2 fois plus gros") : seule exception à la
     taille uniforme --fs-title des autres titres de bloc - reste
     responsive, la mise à l'échelle suit celle de --fs-title. */
  font-size:calc(var(--fs-title) * 2);
  /* Interlignage légèrement augmenté (redemandé) : 1.02 -> 1.12, respire un
     peu plus entre les 2 lignes (titre + <br> manuel) sans desserrer le
     bloc au point de perdre l'effet "display" recherché. */
  line-height:1.12;
  letter-spacing:-.01em;
  text-shadow:0 4px 40px rgba(0,0,0,.22);
  margin-inline:auto;
}
.hero__title::first-letter, .hero__title .reveal-line-mask:first-of-type .reveal-line::first-letter{text-transform:uppercase;}
/* max-width élargi (44ch -> 84ch, demandé) : un <br> manuel après
   "Indépendants" force la 1ère ligne, mais le reste ("pour les aider...
   différenciation.", ~80 caractères) doit tenir sur la 2e ligne sans se
   recouper à son tour - 44ch était pensé pour le texte court d'origine,
   trop étroit pour ce 2e segment. .hero__content (parent) est déjà très
   large (min(98vw, 1800px)), largement assez pour cette largeur. */
.hero__subtitle{
  display:block;
  margin:clamp(1.6rem, 3.4vw, var(--space-2)) auto 0;
  max-width:84ch;
  font-family:var(--sans); font-weight:300;
  color:#FFFFFF;
  font-size:var(--fs-body);
  line-height:1.6;
  text-shadow:0 2px 20px rgba(0,0,0,.18);
}
/* .hero__cta (demandé, audit UX externe - "capter les visiteurs déjà
   décidés dès l'arrivée sur la page") : reprend le style bordé/plein au
   survol que portait .site-header__cta avant cette même passe (voir son
   commentaire, plus haut dans ce fichier) - même bouton, nouvel
   emplacement, plus prioritaire. inline-block seul suffit à le centrer
   (hérite de text-align:center posé sur .hero__content, pas besoin de
   la recette display:block+width:max-content+margin-inline:auto utilisée
   par .offers__cta, qui vit dans un contexte non centré par défaut). */
.hero__cta{
  display:inline-block;
  margin-top:clamp(1.8rem, 3.6vw, 2.6rem);
  font-size:.88rem; letter-spacing:.06em; text-transform:uppercase;
  border:1px solid var(--text); color:var(--text);
  padding:1em 1.9em; border-radius:var(--radius);
  /* white-space:nowrap (bug réel trouvé) : data-reveal-lines regroupe
     les mots par ligne VISUELLE mesurée au rendu (voir motion.js,
     splitIntoLines) - sans ça, le label se retrouvait scindé en 3
     "lignes" ("Parlons" / "de vos" / "images") au lieu d'une seule,
     l'inline-block se calant sur une largeur plus étroite que le texte
     complet avant la mesure. Une seule ligne garantie, comme pour
     n'importe quel autre bouton du site. */
  white-space:nowrap;
  transition:background var(--dur-hover) var(--ease), color var(--dur-hover) var(--ease), border-color var(--dur-hover) var(--ease);
}
.hero__cta:hover{background:var(--gold); border-color:var(--gold); color:var(--bg);}
.hero__scrollcue{
  position:absolute; left:50%; bottom:2.6rem; transform:translateX(-50%);
  z-index:2;
}
.hero__scrollcue span{
  display:block;
  width:1px; height:44px;
  background:linear-gradient(var(--gold), transparent);
  position:relative; overflow:hidden;
}
.hero__scrollcue span::after{
  content:""; position:absolute; left:0; top:-44px; width:100%; height:44px;
  background:linear-gradient(var(--text), transparent);
  animation:cueDrop 2.2s ease-in-out infinite;
}
@keyframes cueDrop{
  0%{top:-44px;} 60%{top:44px;} 100%{top:44px;}
}
/* ==========================================================================
   MOMENT (ex-Chrono) : image + texte, suit la même règle sitewide "image
   à côté d'un texte" que Bloc 2/Bloc 4 (60/40, ratio 16:9, voir plus
   bas). Épinglé (#momentPin/#momentSection, position:sticky) pour toute
   la durée de la séquence phase 1 -> phase 2 (Chrono + texte 2, fusionné
   ici depuis l'ancien #momentStat, supprimé) : les 2 phases se
   superposent dans .moment__phase-stack et s'enchaînent via l'animation
   "split by line", pilotée par un timeline dédié dans js/motion.js
   (setupMomentSequence), pas par le système générique data-reveal-lines.
   ========================================================================== */
/* Espace ajouté avec le Hero (redemandé, "elle est collée... il faut que
   ce soit comme un bloc à part") : margin-top généreux, plus que
   --section-gap (pensé pour l'espace resserré autour des séparateurs) -
   Moment n'a pas de séparateur avec le Hero, ce margin est donc la seule
   respiration entre les deux. */
/* Pin + séquence phase 1/phase 2 : SUPPRIMÉS (redemandé, "abandonne la
   mécanique actuelle - pin + transition split-by-line + chrono séparé.
   Le Chrono devient deux blocs autonomes et indépendants") - #momentPin/
   #momentSection sticky/.moment__phase-stack/.moment__phase disparaissent
   du HTML, ce bloc ("Vous avez sûrement...") redevient une section
   normale, plus de scroll-jacking ni de garde-fou nécessaire (le
   contenu suivant est maintenant un bloc à part entière, .chrono2 plus
   bas, pas un texte caché en attente derrière un pin). */
/* ==========================================================================
   MOMENT / BLOC 2 / BLOC 4 : nouvelle base commune "image d'un côté, texte
   de l'autre" (redemandé, "on repart de zéro" - remplace l'ancien système
   flex + formule de padding-inline dérivée de 1680px + breakpoints
   16:9->3:2 à 1600px + wrappers -inner/-content ajoutés pour contourner un
   bug de flex-basis:0). Un seul mécanisme, standard, pour les 3 blocs :
   - Section hôte (.moment/.bloc2/.bloc--4) : fond + overflow:hidden
     (nécessaire aux animations d'entrée), PAS de largeur contrainte -
     garde le fond plein cadre (important pour .bloc--4, --bg-light).
   - "__wrap" (nouveau, un par bloc) : le conteneur centré lui-même, même
     recette que .references__wrap (max-width:1680px + margin-inline:auto
     + padding-inline:var(--gutter), voir plus bas dans ce fichier) - un
     seul niveau, pas de formule dérivée. display:grid,
     grid-template-columns 2fr/3fr (40% texte / 60% image, conforme à la
     règle du site) DANS L'ORDRE VISUEL réel de chaque bloc (le texte n'est
     pas toujours le 1er enfant DOM - Bloc 2 a l'image en premier), un seul
     breakpoint (860px, déjà le seuil standard du site) qui repasse en
     1fr (empilé), sans palier intermédiaire.
   - Colonne image : ratio fixe 3/2 (remplace les tentatives 16:9 avec
     marge de 18% pour la parallaxe, abandonnées avec elle), overflow:
     hidden, border-radius:var(--radius) (site entier). L'image elle-même
     en position:absolute;inset:0 + object-fit:cover, opacity:0 au repos
     (état initial de l'animation d'entrée GSAP, voir motion.js -
     inchangée, seuls les sélecteurs de conteneur changent).
   - Colonne texte : plus de wrapper -inner/-content (le bug de
     flex-basis:0 qui l'imposait n'existe pas avec CSS Grid) - padding/
     gap/alignement portés directement par la classe déjà ciblée par le
     JS (.moment__text-col, .bloc2__text-col, .bloc--4__inner).
   Police, couleurs et animation "split by line" du texte : entièrement
   conservées (aucune des règles ci-dessous ne les concerne).
   ========================================================================== */
.moment{
  background:var(--bg);
  overflow:hidden;
}
.moment__wrap{
  max-width:1680px;
  margin-inline:auto;
  padding-inline:var(--gutter);
  padding-block:clamp(4rem, 8vw, 7rem);
  display:grid;
  grid-template-columns:2fr 3fr;
  align-items:center;
  gap:clamp(2rem, 4vw, 3.5rem);
}
@media (max-width:860px){
  .moment__wrap{grid-template-columns:1fr;}
}
/* Parallaxe ajoutée (redemandé, "met un paralaxe sur toutes les
   images... épargne les cartes du portfolio et l'image de la bio") :
   même principe que .bloc2__media/.bloc--4__media - le conteneur porte
   l'entrée GSAP (fondu+montée), l'<img> à l'intérieur porte la
   parallaxe scrubbée en continu (2 cibles distinctes, sinon même
   propriété `y` écrasée à chaque frame par l'un ou l'autre tween). */
.moment__media{
  position:relative;
  aspect-ratio:3/2;
  overflow:hidden;
  border-radius:var(--radius);
  background:var(--bg);
  opacity:0; /* état initial de l'entrée GSAP, voir motion.js */
}
.moment__media-img{
  /* Surdimensionnée (height:123%, recentrée par top:-11.5%) : donne de
     la marge de déplacement à la parallaxe (±10,625%, effet augmenté de
     25% - redemandé) sans jamais révéler les bords du conteneur. */
  position:absolute; left:0;
  width:100%; height:123%; top:-11.5%;
  object-fit:cover;
}
.moment__text-col{
  display:flex; flex-direction:column;
  align-items:flex-start;
  gap:1rem;
  text-align:left;
}
.moment__title{
  font-family:var(--display-semi); font-weight:600;
  color:var(--text-strong);
  text-transform:lowercase;
  font-size:var(--fs-title);
  line-height:1.1;
}
.moment__title::first-letter, .moment__title .reveal-line-mask:first-of-type .reveal-line::first-letter{text-transform:uppercase;}
/* Nouveau : structure titre/sous-titre/paragraphe (redemandé, remplace
   l'ancienne structure titre (phrase complète) + paragraphe) - le
   sous-titre est un accent doré court (MarkOT = var(--sans), déjà la
   police du site), PAS le paragraphe descriptif (voir .moment__body
   juste en dessous, ex-.moment__subtitle, renommé pour éviter la
   confusion avec ce nouvel élément). */
.moment__kicker{
  font-family:var(--sans); font-weight:500;
  color:var(--gold);
  text-transform:lowercase;
  font-size:var(--fs-kicker);
  line-height:1.3;
}
.moment__kicker::first-letter, .moment__kicker .reveal-line-mask:first-of-type .reveal-line::first-letter{text-transform:uppercase;}
.moment__body{
  font-family:var(--sans); font-weight:300;
  color:var(--text-strong);
  font-size:var(--fs-body);
  line-height:1.6;
  max-width:40ch;
}

/* ==========================================================================
   CHRONO (bloc 2 du Chrono, autonome) : "2 secondes..." - image plein
   cadre (largeur ET hauteur du bloc) avec calque sombre, texte par-dessus
   (redemandé, remplace entièrement l'ancien mécanisme pin+chrono séparé/
   compteur en overlay grid). Image tirée aléatoirement du même pool que
   le bloc précédent (assets/img/chrono/, voir main.js) - id #chronoMediaImg
   conservé (aucun changement JS nécessaire pour le tirage). Le nombre
   dans le titre (0->2) est piloté par un scrub GSAP lié à la fenêtre de
   défilement normale du bloc (voir js/motion.js) - PAS de pin, le bloc
   se comporte comme n'importe quelle autre section du site, seul un
   compteur continue de s'incrémenter tant qu'on le traverse.
   ========================================================================== */
/* Corrigé (signalé, "ne doit pas avoir une image sur toute la largeur,
   mais uniquement sur la largeur du noyau... l'image prend la largeur
   des 2 [colonnes texte+image] et le texte apparaît dedans") : l'image
   n'est plus plein cadre (100vw) - elle est contenue dans la même
   colonne centrale que tous les autres blocs (.chrono2__wrap,
   max-width:1680px + margin-inline:auto + padding-inline:var(--gutter),
   même recette que .moment__wrap/.bloc2__wrap), juste sans découpage en
   2 colonnes internes : le texte est superposé DANS le cadre de
   l'image, qui occupe toute la largeur ET hauteur de cette colonne.
   border-radius:var(--radius) (site entier) : redevient un cadre comme
   les autres images du site, plus un fond plein écran. Texte : plus de
   calque sombre plein cadre, un cadre centré à la place (.chrono2__panel
   plus bas, voir son commentaire). */
.chrono2{
  background:var(--bg);
  padding-block:clamp(3rem, 8vw, 6rem);
}
.chrono2__wrap{
  max-width:1680px;
  margin-inline:auto;
  padding-inline:var(--gutter);
}
/* Parallaxe ajoutée (redemandé, voir .moment__media pour le détail du
   principe conteneur/entrée vs <img>/parallaxe). */
.chrono2__media{
  position:relative;
  min-height:min(70vh, 900px);
  overflow:hidden;
  border-radius:var(--radius);
  background:var(--bg);
  opacity:0; /* état initial de l'entrée GSAP, voir motion.js */
}
.chrono2__media-img{
  position:absolute; left:0;
  width:100%; height:123%; top:-11.5%;
  object-fit:cover;
}
/* Calque plein cadre retiré + cadre centré à la place (redemandé, "enlève
   l'opacité et met un cadre avec opacité à 50% au centre, avec le texte
   au centre également") : plus de voile sombre uniforme sur toute la
   photo - un seul cadre (.chrono2__panel), positionné au centre de
   l'image par le conteneur .chrono2__content (display:flex, centrage
   double), qui porte lui-même l'opacité 50% (fond noir semi-transparent
   + léger contour, "cadre") et regroupe titre/sous-titre/paragraphe,
   tous centrés (text-align:center + align-items:center) - remplace
   l'ancien ancrage bas-gauche. */
.chrono2__content{
  position:absolute; inset:0;
  z-index:1;
  display:flex; align-items:center; justify-content:center;
  padding:clamp(1.5rem, 4vw, 3rem);
}
.chrono2__panel{
  /* Revenu à la taille d'origine (redemandé, "remet le cadre à sa taille
     d'avant") : max-width:640px, sans width:100% (retour au
     dimensionnement par contenu d'origine - le vrai besoin n'était pas
     un cadre plus large, mais un paragraphe qui ne renvoie plus un seul
     mot à la ligne, corrigé ci-dessous via un padding réduit plutôt
     qu'un cadre agrandi). Marges intérieures réduites (redemandé,
     "réduit les marges intérieures... pour que le texte puisse éviter
     les retours à la ligne") : clamp(2rem,5vw,3.5rem) ->
     clamp(1.25rem,3vw,2.25rem), libère de la largeur disponible pour le
     texte sans toucher au cadre lui-même. */
  background:rgba(0,0,0,.65);
  border:1px solid rgba(255,255,255,.25);
  border-radius:var(--radius);
  padding:clamp(1.25rem, 3vw, 2.25rem);
  width:min(80%, 960px);
  display:flex; flex-direction:column; align-items:center;
  gap:1rem;
  text-align:center;
}
.chrono2__title{
  font-family:var(--display-semi); font-weight:600;
  color:var(--text-strong);
  font-size:var(--fs-title);
  line-height:1.1;
  font-variant-numeric:tabular-nums;
}
.chrono2__kicker{
  font-family:var(--sans); font-weight:500;
  color:var(--gold);
  text-transform:lowercase;
  font-size:var(--fs-kicker);
  line-height:1.3;
}
.chrono2__kicker::first-letter, .chrono2__kicker .reveal-line-mask:first-of-type .reveal-line::first-letter{text-transform:uppercase;}
.chrono2__body{
  font-family:var(--sans); font-weight:300;
  color:var(--text-strong);
  font-size:var(--fs-body);
  line-height:1.6;
  max-width:40ch;
  /* Bug réel trouvé (le vrai problème derrière "élargis le cadre") :
     .chrono2__panel n'a pas de largeur propre (dimensionné par son
     contenu, voir son commentaire) - réduire le padding du panneau ne
     change donc RIEN à la largeur de renvoi à la ligne du texte
     (mesuré : largeur de retour identique, peu importe le padding).
     Le vrai réglage utile est ici : text-wrap:balance répartit les mots
     entre les lignes pour éviter qu'un seul mot ("option") ne se
     retrouve seul sur la dernière ligne - exactement le problème
     signalé, sans toucher au cadre. */
  text-wrap:balance;
}
@media (max-width:860px){
  .chrono2__media{min-height:60vh;}
}
/* ==========================================================================
   BLOC 2 : image + texte côte à côte
   Texte rapproché de l'image (petite marge intérieure) plutôt que collé
   au bord de la fenêtre (grande marge extérieure). Répartition 61,8% /
   38,2% (nombre d'or, prompt-page-pro.md) plutôt que 50/50 : l'image
   reçoit la part majoritaire (élément visuel structurant), le texte la
   part mineure, sans risque pour la lisibilité : sa largeur réelle reste
   plafonnée indépendamment par max-width:34ch (voir .bloc2__body plus
   bas), largement sous 38,2% de la largeur de fenêtre à toute taille de
   bureau usuelle. Ne s'applique qu'au-dessus de 860px : en colonne unique
   (mobile), le ratio horizontal n'a plus de sens (voir media query plus
   bas).
   ========================================================================== */
/* Pin retiré (redemandé, "enlève le pin sur la fin de ce bloc") : #bloc2
   redevient un bloc de page normal - #bloc2Pin (HTML) reste comme simple
   wrapper inerte (aucune règle CSS ni référence JS ne s'y accroche plus).
   Grille "__wrap" : voir le commentaire général au-dessus de .moment.
   Seule différence entre les 3 blocs : l'ORDRE visuel - l'image est le
   1er enfant DOM ici (texte à droite), donc grid-template-columns
   s'inverse (3fr/2fr, image/texte) plutôt que 2fr/3fr. */
/* background:var(--bg) uni (redemandé, "on revient en arrière... on
   enlève la couleur de fond #c8cfcd. On laisse la #58656A jusqu'au
   séparateur") : annule tout l'essai de dégradé --bg -> #c8cfcd (mesuré
   sur .bloc2__media, plusieurs variantes essayées - fraction fixe de
   l'image, bas du texte, bas de l'image - voir l'historique des
   commits si besoin d'y revenir). .bloc2 reste uniformément --bg
   jusqu'à #sep2 (qui redevient donc la SEULE frontière de couleur ici,
   --bg -> var(--white) porté par .bloc--3 juste après, inchangé - voir
   #sep2 plus haut). positionBloc2ColorBoundary (main.js) retiré,
   plus aucune mesure JS nécessaire pour ce bloc. */
.bloc2{overflow:hidden;}
.bloc2__wrap{
  max-width:1680px;
  margin-inline:auto;
  padding-inline:var(--gutter);
  padding-block:clamp(4rem, 8vw, 7rem);
  display:grid;
  grid-template-columns:3fr 2fr;
  align-items:center;
  gap:clamp(2rem, 4vw, 3.5rem);
}
@media (max-width:860px){
  .bloc2__wrap{grid-template-columns:1fr;}
}
/* Parallaxe rétablie (redemandé, "les images ont perdu leur animation
   parallaxe") : l'entrée GSAP (fondu+montée, motion.js) cible maintenant
   le CONTENEUR (.bloc2__media) plutôt que l'<img> - cible qui, elle,
   reçoit en plus une parallaxe scrubbée en continu (même fichier) sur sa
   propre propriété `y` ; les 2 tweens auraient sinon écrit la même
   propriété sur le même élément et se seraient écrasés l'un l'autre.
   L'image reste volontairement surdimensionnée (height:123%, recentrée
   par top:-11.5% - agrandie depuis 118%/-9%, effet de parallaxe augmenté
   de 25%, redemandé) pour donner de la marge de déplacement à la
   parallaxe sans jamais révéler les bords du conteneur. */
.bloc2__media{
  position:relative;
  aspect-ratio:3/2;
  overflow:hidden;
  border-radius:var(--radius);
  background:var(--bg);
  opacity:0; /* état initial de l'entrée GSAP, voir motion.js */
}
.bloc2__media img{
  position:absolute; left:0;
  width:100%; height:123%; top:-11.5%;
  object-fit:cover;
}
.bloc2__text-col{
  display:flex; flex-direction:column;
  gap:1.6rem;
}
.bloc2__title{
  font-family:var(--display-semi); font-weight:600;
  color:var(--text-strong);
  text-transform:lowercase;
  font-size:var(--fs-title);
  line-height:1.04;
  max-width:22ch;
}
.bloc2__title::first-letter, .bloc2__title .reveal-line-mask:first-of-type .reveal-line::first-letter{text-transform:uppercase;}
/* Nouveau : sous-titre doré (structure titre/sous-titre/paragraphe,
   redemandé) - voir .moment__kicker pour le détail, même recette. */
.bloc2__kicker{
  font-family:var(--sans); font-weight:500;
  color:var(--gold);
  text-transform:lowercase;
  font-size:var(--fs-kicker);
  line-height:1.3;
}
.bloc2__kicker::first-letter, .bloc2__kicker .reveal-line-mask:first-of-type .reveal-line::first-letter{text-transform:uppercase;}
.bloc2__body{
  font-family:var(--sans); font-weight:300;
  color:var(--text-strong);
  font-size:var(--fs-body);
  line-height:1.6;
  max-width:55ch;
}
/* "+" identique au symbole des séparateurs (.separator__symbol) : mêmes
   police/graisse/taille/épaisseur de trait (font-weight:700 synthétisé,
   faute d'un fichier Gloock 700, complété par -webkit-text-stroke pour un
   rendu de graisse fiable). Décalage vertical fin (-4px, mesuré) : ses
   métriques (2.4rem, très différentes de var(--sans) autour) tombent mal
   avec vertical-align:middle seul. */
.bloc2__plus{
  display:inline-block;
  font-family:var(--display); font-weight:700; font-size:2.4rem; line-height:1;
  color:var(--gold);
  -webkit-text-stroke:1px var(--gold);
  vertical-align:middle;
  position:relative; top:-4px;
}

/* ==========================================================================
   BLOC 3 : pin + révélation mot par mot, opacité uniquement (redemandé,
   "pas flou > net - le texte entier en faible opacité, avec un pin en
   bas du bloc, qui révèle les mots un par un au scroll") - pin
   réintroduit (#bloc3Pin, position:sticky - jamais ScrollTrigger.pin:
   true, convention du site) : la progression est mesurée sur le WRAPPER
   (#bloc3Pin), pas sur #bloc3/#bloc3Statement (formule standard du site,
   -rect.top/(rect.height-innerHeight), déjà utilisée ailleurs pour tout
   pin sticky) - couvre exactement le "rab" de scroll du pin, quelle que
   soit sa hauteur réelle.
   ========================================================================== */
.bloc3-pin{
  position:relative;
  height:calc(100vh + 200vh);
}
#bloc3{
  position:sticky;
  top:0;
  min-height:100vh;
}
.bloc--3{
  position:relative;
  min-height:100vh;
  display:flex; align-items:center; justify-content:center;
  /* var(--bg) -> var(--white) (demandé) : prolonge la zone claire
     démarrée à mi-hauteur de l'image de .bloc2 (voir son commentaire) -
     .bloc--3__statement/.bloc3__gold ci-dessous passent en #58656A pour
     rester lisibles sur ce nouveau fond clair. */
  background:var(--white);
  padding-block:var(--section-gap);
  padding-inline:var(--gutter);
}
.bloc--3__statement{
  max-width:52ch;
  text-align:center;
  /* text-transform:uppercase retiré (demandé, "on met que la 1ere
     lettre de la phrase en majuscule") : le texte HTML est déjà casé
     ainsi tel qu'écrit ("Et c'est là que se joue..." - seul le premier
     mot porte une majuscule, tout le reste en minuscules, y compris
     "vu"/"choisi") - aucune classe/span supplémentaire nécessaire, la
     casse naturelle du texte suffit une fois l'uppercase forcé retiré. */
  /* Police remise en Gloock (redemandé, "remet la police Gloock") -
     remplace Montserrat ExtraBold. var(--display-semi) + font-weight:600
     : même convention que tous les autres titres Gloock du site (un
     seul fichier Gloock, en 400 - le 600 est un gras synthétisé par le
     navigateur, déjà accepté partout ailleurs).
     Taille réduite (redemandé, "le texte est trop gros") : multiplicateur
     1.5 -> 1.3, sans toucher au clamp()/--title-boost sous-jacents
     (partagés avec d'autres titres du site). */
  font-family:var(--display-semi); font-weight:600;
  font-size:calc((clamp(2rem, 4vw, 3.2rem) + var(--title-boost)) * 1.3);
  /* var(--text-strong) -> #58656A (demandé) : ce texte vit désormais sur
     fond clair (var(--white), voir .bloc--3 ci-dessus) - var(--text-strong),
     pensée pour le fond sombre --bg du reste du site, n'a plus le
     contraste voulu ici. */
  color:#58656A;
  line-height:1.3;
}
/* Reprend la même couleur que .bloc--3__statement (aucune distinction
   visuelle entre les mots "vu"/"choisi" et le reste de la phrase dans le
   design d'origine, voir var(--text-strong) commun aux deux avant ce
   changement) - simple prolongement du même remplacement de couleur. */
.bloc3__gold{ color:#58656A; }

@media (prefers-reduced-motion: reduce), (max-width: 860px){
  /* Pin de #bloc3 désactivé (comme les autres pins du site sur ce
     breakpoint) : plus de "rab" de scroll, le bloc redevient un bloc de
     page normal qui défile à son propre rythme. */
  .bloc3-pin{height:auto;}
  #bloc3{position:static; min-height:0;}
  .bloc--3{min-height:0;}
  /* Taille réduite (redemandé, "le texte est trop gros sur mobile") :
     la formule desktop ((clamp(2rem,4vw,3.2rem) + --title-boost) * 1.5)
     est pensée pour un texte qui défile HORIZONTALEMENT plein écran
     (voir motion.js, désactivé ici avec le pin juste au-dessus) - sur
     mobile, statique et empilé normalement, elle produit ~70px à 375px
     de large, un texte majuscule qui ne tient que quelques lettres par
     ligne. Valeur dédiée, sans le multiplicateur ni --title-boost
     (pensés pour le défilement plein écran, hors sujet ici). */
  .bloc--3__statement{font-size:clamp(1.6rem, 7vw, 2.2rem);}
}

/* ==========================================================================
   PORTFOLIO : simplification majeure (demandée) - titre en haut dans une
   mise en page classique (comme Bloc 5/Bloc 6), puis 3 cartes de
   catégorie qui apparaissent au scroll selon EXACTEMENT la même logique
   d'entrée que les cartes d'offres (voir tickPortfolioCards, main.js -
   copie de tickOffersCards : la carte centrale monte en premier, puis
   les 2 latérales glissent depuis derrière elle), puis un panneau de
   clôture simple. Annule tout le système précédent : plus de ruban
   horizontal pinné, plus de ligne dorée entre les cartes, plus de
   symbole "+" animé ici (le "+" statique vit désormais dans #sep4a,
   avant ce bloc - voir HTML ; le séparateur qui suivait ce bloc,
   #sep4b, a depuis été retiré). Fond blanc, comme le Bloc 3 qui
   précède désormais (voir .bloc--3) - #sep4a n'est plus une frontière
   entre 2 couleurs. */
/* Changement de fond var(--white) -> #e3e6e3 (redemandé - var(--bg)
   #58656A essayé d'abord, "remplace #58656A par #e3e6e3") - démarre
   PILE au centre vertical de .portfolio-h__card ("la frontière
   doit être au niveau du centre des cartes" : essayé d'abord au bas de
   .portfolio-h__carousel seul, plus bas que le vrai centre de la carte
   entière - carousel + légende ; les 3 cartes sont identiques, une
   seule mesurée suffit). .portfolio-h__card (le "carton" blanc sous
   chaque carousel, voir plus bas) devient transparent en conséquence :
   la légende (titre/texte) doit désormais se détacher sur ce nouveau
   fond, pas sur son propre fond blanc - .portfolio-h__card-title/
   -tagline repassés sur var(--ink) (voir leurs commentaires) : #f9f5f2/
   var(--gold), pensés pour l'ancien fond sombre var(--bg), n'ont plus
   le contraste voulu sur ce fond clair. Le centre de la carte tombant à
   l'intérieur même de l'image (opaque, insensible à la couleur de fond
   derrière elle), la frontière ne devient visuellement perceptible que
   dans les espaces NON couverts par une photo - marges entre les 3
   cartes, espace au-dessus/en dessous d'elles.
   Mesuré en JS (positionPortfolioColorBoundary, main.js) via la chaîne
   offsetTop/offsetParent plutôt que getBoundingClientRect() : les
   cartes portent un scale()+opacity transitoires pendant leur entrée
   (voir .portfolio-h__card plus bas, tickPortfolioCards) -
   getBoundingClientRect() aurait inclus ce transform, faussant la
   frontière tant que l'entrée n'est pas terminée ; offsetTop/
   offsetHeight restent, eux, purement liés à la mise en page, jamais
   affectés par un transform CSS. Repli statique en dur ci-dessous. */
.portfolio-h{
  position:relative;
  background:linear-gradient(to bottom, var(--white) 60%, #e3e6e3 60%);
  padding-block:var(--section-gap);
}
/* #portfolio (override ID, scopé à cette seule instance - PAS un
   changement de .portfolio-h, qui n'a qu'un usage sur le site) :
   padding-bottom doublé, redemandé - "même espace qu'entre un
   séparateur et son titre, même si le séparateur n'est plus là".
   #sep4b (Portfolio -> Références) a été entièrement retiré (demandé
   précédemment) ; .references garde volontairement son padding-top à
   var(--section-gap) SEUL (voir son commentaire, "LA règle est
   l'égalité stricte partout" avec les autres séparateurs du site,
   décision explicite d'une passe précédente - non touchée ici). Le
   supplément demandé est donc ajouté ici, côté Portfolio, plutôt que
   de rouvrir cette règle générale. */
#portfolio{
  padding-bottom:calc(var(--section-gap) * 2);
}
/* max-width:1700px (au lieu de 1600px comme les autres conteneurs du
   site) + padding-inline propre, plus étroit que --gutter : élargit au
   maximum les marges disponibles pour que "Ce que ça donne,
   concrètement" (white-space:nowrap ci-dessous) tienne sur une seule
   ligne en desktop (demandé) - le --gutter standard (jusqu'à 4rem de
   chaque côté) était trop généreux pour cette exigence précise. */
.portfolio-h__text-col{
  width:100%; max-width:1700px;
  margin-inline:auto;
  padding-inline:clamp(1rem, 3vw, 2rem);
  text-align:center;
}
/* color:#58656A (PAS --gold) : ce titre est sur fond BLANC (contrairement
   à la plupart des autres grands titres du site, sur fond --bg foncé, où
   le gold a un contraste correct) - un texte gold sur blanc serait bien
   trop clair pour rester lisible confortablement. font-size plafonné à
   3.6rem (pas 4.4rem comme les autres grands titres) : au-delà, le texte
   (plus long que les autres titres du site) ne tenait plus sur une seule
   ligne même marges élargies au maximum - voir .portfolio-h__text-col. */
/* Taille unifiée (redemandé, "tous les titres de blocs... absolument
   tous", référence .bloc6__title) - remplace l'ancien plafond réduit
   (3.6rem, au lieu de 4.4rem) qui avait été spécifiquement abaissé pour
   tenir sur une seule ligne (white-space:nowrap, demandé à l'époque) :
   à la taille de référence, le texte ne tient plus sur une ligne à
   toutes les largeurs de bureau - nowrap est retiré (règle mobile
   #sep... .portfolio-h__title{white-space:normal} devenue inutile,
   retirée aussi) : ce titre revient à un retour à la ligne normal, comme
   tous les autres titres du site, plutôt que de risquer un débordement
   visuel hors de son conteneur. */
.portfolio-h__title{
  font-family:var(--display-semi); font-weight:600;
  color:var(--ink);
  /* Taille/police uniformisées sitewide (redemandé) : --fs-title -
     remplace l'ancienne formule identique en apparence mais SANS le
     "* 0.7" que la plupart des autres titres avaient reçu (cause de
     l'écart signalé, "une sacrée différence sur la taille de la
     police"). */
  font-size:var(--fs-title);
  line-height:1;
  text-transform:lowercase;
  margin-bottom:clamp(2.2rem, 4.6vw, 3.4rem);
}
.portfolio-h__title::first-letter, .portfolio-h__title .reveal-line-mask:first-of-type .reveal-line::first-letter{text-transform:uppercase;}
.portfolio-h__container{width:100%; max-width:1600px; padding-inline:var(--gutter); margin-inline:auto;}
.portfolio-h__grid{
  display:grid;
  grid-template-columns:repeat(3, 1fr);
  gap:clamp(1.4rem, 2.4vw, 2rem);
}
/* "Stacked cards reveal" (redemandé, GSAP ScrollTrigger + scrub + léger
   stagger, voir js/motion.js) : les 3 cartes démarrent empilées/réduites
   près de l'axe central de la grille (là où vit la carte centrale, pas
   de ligne verticale dessinée - confirmé, sobriété), puis se séparent en
   grandissant vers leur position finale côte à côte. État de repos posé
   en CSS (avant le 1er rendu GSAP, qui l'écrase aussitôt dès le premier
   scroll) pour éviter un flash des 3 cartes déjà en place - simple repli
   générique (échelle réduite + faible opacité), le décalage empilé exact
   par carte (calculé via offsetLeft, comme l'ancien tickPortfolioCards)
   n'a de sens qu'au runtime. z-index : centre au-dessus, cohérent avec
   l'empilement (les latérales semblent glisser de derrière elle). */
/* background:var(--white) retiré (demandé, le fond de .portfolio-h
   descend désormais au milieu des cartes, voir son commentaire) : ce
   fond ne couvrait de toute façon jamais .portfolio-h__carousel
   (l'image, son propre overflow:hidden/border-radius suffisent seuls,
   voir plus bas) - seule .portfolio-h__caption (titre/texte) en
   dessous s'appuyait dessus. border-radius devient sans effet (rien à
   arrondir sur un fond transparent) mais reste inoffensif, inutile de
   le retirer. */
.portfolio-h__card{
  position:relative;
  border-radius:var(--radius);
  display:flex; flex-direction:column;
  opacity:.15;
  transform:scale(.85);
  /* container-type:inline-size (redemandé, voir .portfolio-h__card-title
     plus bas) : permet à la taille du titre de suivre la largeur RÉELLE
     de la carte (cqw) plutôt que celle du viewport (vw) - nécessaire ici
     car la largeur de carte n'est pas monotone avec la largeur d'écran
     (elle CHUTE juste après le passage en grille 3 colonnes à 860px,
     avant de remonter en pile 1 colonne en dessous). Aucun changement
     visuel : la largeur de la carte reste dictée par la grille, pas par
     son contenu. */
  container-type:inline-size;
}
.portfolio-h__card--center{z-index:2;}
.portfolio-h__card--left, .portfolio-h__card--right{z-index:1;}
.portfolio-h__carousel{
  position:relative;
  width:100%;
  aspect-ratio:2 / 3;
  flex:0 0 auto;
  overflow:hidden;
  border-radius:var(--radius);
}
.portfolio-h__carousel img{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:cover;
  opacity:0;
  transition:opacity 1.2s var(--ease);
}
.portfolio-h__carousel img.is-active{opacity:1;}
.portfolio-h__caption{
  flex:0 0 auto;
  margin-top:clamp(1rem, 2vw, 1.5rem);
  text-align:center;
}
/* Chronicle Display Roman, en capitales (redemandé, remplace Montserrat
   Bold + son ancien traitement "sentence case" text-transform:lowercase
   + ::first-letter:uppercase - la police ET la casse changent ensemble,
   la règle ::first-letter ci-dessous devient donc obsolète et est
   retirée). Taille : clamp() en cqw (container query units), PAS vw -
   .portfolio-h__card (voir son commentaire container-type juste au-
   dessus) a une largeur qui n'est pas monotone avec la largeur d'écran
   (elle chute d'environ 470px à environ 240px juste après le passage de
   3 colonnes à 1 colonne à 860px, avant de remonter en pile). Un clamp
   en vw ne peut pas suivre cette chute ; en cqw, la taille reste
   proportionnelle à la largeur RÉELLE de la carte à tout moment, donc le
   titre le plus long du bloc ("Indépendants & Services") tient sur une
   seule ligne quelle que soit la largeur d'écran (vérifié : le point le
   plus étroit, ~240px juste après 860px, est le cas le plus contraignant
   - plus étroit que la plupart des cartes empilées en mobile). Plafond à
   1.5rem ("pas trop grande", redemandé) : au-delà, sur les cartes larges
   de bureau, le titre reste petit par choix plutôt que de grossir avec
   l'espace disponible. */
.portfolio-h__card-title{
  font-family:"Chronicle Display Roman", var(--sans); font-weight:400;
  color:var(--ink);
  font-size:clamp(0.85rem, 6.3cqw, 1.35rem);
  line-height:1.2;
  text-transform:uppercase;
  letter-spacing:.02em;
  white-space:nowrap;
}
.portfolio-h__card-tagline{
  margin-top:.5rem;
  font-family:var(--sans); font-weight:300;
  /* var(--gold) -> var(--ink) (redemandé, fond de .portfolio-h passé de
     var(--bg) à #e3e6e3 - voir son commentaire) : var(--gold) (doré),
     lisible sur l'ancien fond sombre, manque de contraste sur ce fond
     clair (2 couleurs proches en luminosité) - var(--ink) redevient le
     bon token, comme avant le premier essai de fond sombre. */
  color:var(--ink);
  opacity:.75;
  /* Harmonisé sitewide (redemandé) : taille 2 (cartes/descriptions),
     voir --fs-caption. */
  font-size:var(--fs-caption);
  line-height:var(--lh-caption);
  margin-inline:auto;
  max-width:30ch;
}
/* "+" doré (redemandé, "exactement comme pour le + déjà traité de cette
   façon dans... Recadrage") : même couleur EXACTE que .bloc2__plus
   (var(--gold) = #D6B77D) - pas la même taille/police en revanche
   (2.4rem, pensé pour un paragraphe ~17.6px, y serait totalement
   disproportionné dans cette légende ~13.3px) : hérite donc de la
   taille/police du texte autour, seules la couleur ET le poids
   changent - font-weight:700 (redemandé, "mets en gras le +") sur ce
   même texte par ailleurs en weight:300 (voir .portfolio-h__card-tagline
   plus haut). */
.portfolio-h__card-tagline__plus{color:var(--gold); font-weight:700;}
/* Panneau de clôture ("Découvrez toutes les collections" + bouton)
   retiré entièrement (redemandé) : plus d'élément de clôture du tout
   sous les 3 cartes. */
/* Simplification majeure (demandée) ayant retiré tout le pin horizontal :
   ce fallback mobile n'a plus qu'à empiler les cartes (Portfolio n'est
   déjà plus qu'un bloc normal, comme le reste de la page). */
@media (prefers-reduced-motion: reduce), (max-width: 860px){
  /* Bloc 3 déjà un bloc de page normal, sans pin, à toute largeur depuis
     le retrait du défilement horizontal (voir .bloc--3 plus haut) - plus
     de règle mobile dédiée nécessaire ici. */
  /* Portfolio en 1 colonne (comme .offers__grid sur mobile, même
     principe) : les 3 cartes de catégorie s'empilent au lieu de
     rester sur 3 colonnes étroites. */
  .portfolio-h__grid{grid-template-columns:1fr;}
}
/* BLOC 4 ("Une approche qui fait la différence") : retiré entièrement du
   site (demandé, "supprime complètement ce bloc" - texte, image, fond
   --bg-light et son séparateur de sortie sep4c, voir index.html). Les
   règles .bloc--4* qui vivaient ici ont été supprimées avec lui. */
/* Grille des 3 cartes d'offre, largeur raisonnable plafonnée (1400px),
   centrée : width:100% explicite car .offers__grid (1fr × 3) a besoin
   d'une largeur définie sur son conteneur pour résoudre ses colonnes
   correctement. Cette grille vit désormais dans #bloc6 (contenu
   inversé, demandé) - règle inchangée, générique, ne dépend pas de sa
   section hôte. */
/* max-width 1400px -> 1600px : élargi en même temps que .bloc6__text-col
   (voir son commentaire, "3 façons de travailler ensemble" tient
   désormais sur 1 ligne mais a besoin d'un peu plus de 1400px avec le
   gutter agrandi) - les deux valent la MÊME largeur pour rester alignés
   (titre/corps au-dessus, grille des 3 cartes en dessous), comme avant. */
.offers__container{width:100%; max-width:1600px; padding-inline:var(--gutter);}

.offers__grid{
  position:relative;
  margin-top:clamp(1.5rem, 3vw, 2.5rem);
  display:grid;
  grid-template-columns:repeat(3, 1fr);
  /* Resserré (le cadre lui-même est maintenant bien plus proche de la
     carte, inset:-8px au lieu de ±20px sur des corners/edges séparés,
     voir .offer-card__frame - le gap élargi à la passe précédente pour
     "faire de la place au cadre" n'a plus besoin d'être aussi généreux)
     - toujours un peu plus large que l'origine (0,9-1,4rem) pour garder
     un peu d'air autour du cadre resserré.
     --offers-gap (bug réel trouvé et corrigé, signalé "lié à la
     dimension de la fenêtre" - le gap réel dépend de la largeur via vw,
     motion.js le LISAIT en px UNE SEULE FOIS pour caler le décalage de
     fusion des cartes ; toute variation de largeur après coup - y
     compris un simple redimensionnement de fenêtre, jamais rattrapé de
     façon fiable malgré plusieurs filets de sécurité JS essayés -
     rendait ce décalage figé incohérent avec le vrai gap, cartes déjà
     visiblement séparées). Reproduit la valeur ci-dessous dans une
     variable exposée aux enfants (.offer-card, voir plus bas) : le
     décalage de fusion est désormais calculé PAR LE NAVIGATEUR via
     calc(), recalculé nativement à chaque rendu comme le gap lui-même -
     plus aucune valeur numérique à synchroniser depuis JS, donc plus
     aucune fenêtre de désynchronisation possible, quelle qu'en soit la
     cause. */
  --offers-gap:clamp(1.4rem, 2.4vw, 2rem);
  gap:var(--offers-gap);
}
/* Citation affichée le temps de la phase 1 (redemandé, nouvelle
   séquence d'entrée du Bloc 6 - voir motion.js) : superposée en
   position:absolute sur toute la grille (donc sur la largeur combinée
   des 3 cartes, sans les espaces entre elles tant que gap:0) - .offers__
   grid porte position:relative ci-dessus spécifiquement pour cet
   ancrage. pointer-events:none : jamais interactive, purement
   décorative, ne doit surtout pas intercepter les clics/survols des
   cartes une fois superposée puis masquée (autoAlpha, pas juste
   opacity, gère aussi visibility - voir motion.js). Chronicle Display
   Bold Italic (redemandé, remplace Light Italic) : @font-face déjà
   chargée (voir plus haut) mais orpheline jusqu'ici (titres passés à
   Gloock) - seul usage du site pour cette police. Taille alignée sur
   var(--fs-title) (redemandé, "la même taille que les titres de bloc") -
   même variable que .bloc2__title/.bloc--4__title/.bloc6__title, voir
   plus haut/bas. Masquée en dur au chargement/reduced-motion/mobile
   (voir media query plus bas) : élément purement transitoire de la
   séquence pinnée, n'a pas de sens dans un layout statique. */
/* display:flex retiré (bug réel trouvé, signalé - le retour à la ligne
   demandé ne s'appliquait jamais, tout le texte restait sur 1 seule
   ligne) : le split-by-line (splitIntoLines, motion.js) enveloppe
   chaque mot dans un <span>, mais dans un conteneur display:flex ces
   <span> deviennent des ITEMS flex - toujours sur une seule rangée par
   défaut (flex-wrap:nowrap implicite), ignorant totalement le <br> et
   la largeur du conteneur. Aucun autre texte split-by-line du site
   n'est dans ce cas (tous restent des éléments de bloc/inline normaux).
   Centrage vertical repris via top:50% + yPercent:-50 posé par
   motion.js (au lieu d'align-items:center) - centrage horizontal
   inchangé, déjà porté par text-align:center juste en dessous. */
.offers__quote{
  position:absolute; left:0; right:0; top:50%;
  text-align:center;
  padding-inline:clamp(1.5rem, 4vw, 3rem);
  font-family:"Chronicle Display Bold"; font-style:italic; font-weight:700;
  font-size:var(--fs-title);
  line-height:1.15;
  /* Bug réel trouvé (signalé, "quasiment invisible") : var(--text-strong)
     est un blanc cassé (#faf6f4), pensé pour du texte sur le fond SOMBRE
     du reste du bloc - or cette citation est posée sur le rectangle
     BLANC des cartes fusionnées. var(--ink) (#58656A), déjà utilisé pour
     .offer-card__text sur ce même fond blanc, est le bon choix ici. */
  color:var(--ink);
  pointer-events:none;
  z-index:5;
}
/* Bouton sous les 3 offres (demandé) : reprend le style du bouton de
   contact du header (même bordure/texte var(--text), même transition au
   survol) plutôt que d'inventer un second langage de bouton - ce bloc
   est sur le même fond sombre --bg que le header, donc le même contraste
   déjà documenté (~4.8:1) s'applique tel quel, sans calcul supplémentaire. */
.offers__cta{
  display:block;
  width:max-content;
  margin:clamp(2.2rem, 4.6vw, 3.4rem) auto 0;
  /* Même agrandissement + coins arrondis que .hero__cta (même bouton,
     même texte, redemandé). */
  font-size:.88rem; letter-spacing:.06em; text-transform:uppercase;
  border:1px solid var(--text); color:var(--text);
  padding:1em 1.9em; border-radius:var(--radius);
  transition:background var(--dur-hover) var(--ease), color var(--dur-hover) var(--ease), border-color var(--dur-hover) var(--ease);
}
.offers__cta:hover{background:var(--gold); border-color:var(--gold); color:var(--bg);}

/* Mention "sur mesure" (demandée), entre la grille de cartes et les
   étapes numérotées - taille discrète (--fs-caption, déjà le token du
   site pour du texte secondaire), cohérente avec le reste du bloc. */
.offers__custom-note{
  font-family:var(--sans); font-weight:300;
  color:var(--text);
  font-size:var(--fs-caption);
  line-height:var(--lh-caption);
  text-align:center;
  margin-top:clamp(1.5rem, 3vw, 2.25rem);
}

/* ==========================================================================
   ÉTAPES DU PROCESS (redemandé, "ajoute un bloc juste en dessous des 3
   cartes d'offres, sans titre de section, comme un prolongement du même
   bloc") : reste dans #bloc6 (même fond sombre --bg, aucune section/
   séparateur propre), même .offers__container que la grille d'offres
   au-dessus pour un alignement identique. Un seul cadre bordé (--card/
   --card-border, déjà le token du site pour un panneau discret posé sur
   fond sombre - voir .cta-card) contenant 3 colonnes séparées par de
   fines lignes verticales, plutôt que 3 cartes séparées comme la capture
   de référence fournie (mise en page reprise, pas ses couleurs - demandé
   explicitement). border-radius:0 (convention du site, jamais de coin
   arrondi sur un composant carte - offer-card/cta-card - contrairement à
   la capture, qui n'est qu'une inspiration de structure).
   ========================================================================== */
.steps{
  /* Rendu à sa valeur d'origine (redemandé, "descendre le tableau des 3
     étapes pour qu'il y ait un peu plus d'espace entre les cartes et le
     tableau" - le resserrement précédent visait à faire rentrer #bloc6
     sous 900px de hauteur d'écran, objectif abandonné en même temps que
     le retour des cartes au format 4:5, voir leur commentaire). */
  margin-top:clamp(2.4rem, 5vw, 3.6rem);
}
.steps__grid{
  display:grid;
  grid-template-columns:repeat(3, 1fr);
  background:var(--card);
  border:1px solid var(--card-border);
  border-radius:var(--radius);
}
.steps__item{
  position:relative;
  padding:clamp(2rem, 3.4vw, 2.6rem) clamp(1.8rem, 3vw, 2.4rem);
  display:flex; flex-direction:column; gap:.85rem;
  /* Lueur au survol (redemandé, "une petite animation de couche blanche
     à faible opacité qui illumine le bloc sélectionné, sans que le bloc
     ne bouge") : simple transition de background-color sur l'item
     lui-même (pas de transform/translate, donc aucun mouvement) - chaque
     colonne s'illumine indépendamment des 2 autres. Fade lent (redemandé,
     "en in et en out") : --dur-hover (.32s, pensé pour un survol de bouton
     réactif) -> --dur-l (1.4s, déjà l'échelle de durée "lente" du site) -
     une seule transition sur .steps__item s'applique symétriquement à
     l'entrée ET à la sortie du survol, donc les deux sens sont ralentis
     ensemble sans réglage séparé. */
  transition:background-color var(--dur-l) var(--ease);
}
.steps__item:hover{
  background-color:rgba(255,255,255,.05);
}
.steps__item:not(:first-child){
  border-left:1px solid var(--card-border);
}
/* Titre à droite du numéro (redemandé) : rangée dédiée, alignée sur la
   ligne de base commune (nombres/lettres de tailles différentes,
   baseline reste l'alignement le plus naturel pour ce genre de paire
   "repère + titre"). */
.steps__head{
  display:flex; align-items:baseline;
  gap:clamp(.8rem, 1.6vw, 1.2rem);
}
/* MarkOT (redemandé, remplace Michroma) : var(--sans) est déjà "Mark OT"
   en 1ère police de la pile - plus de contraste de police volontaire
   avec le texte courant, juste un poids/une taille différents. */
.steps__number{
  /* Corrigé (signalé, "pas la même police que le titre, pas en gras...
     mettre les chiffres en doré") : même police/graisse que
     .steps__title (Montserrat Bold, déjà chargée), couleur dorée -
     jusque-là var(--sans)/400/var(--text-strong), sans lien avec le
     titre juste à côté. */
  font-family:"Montserrat Bold", var(--sans); font-weight:700;
  font-variant-numeric:tabular-nums;
  color:var(--gold);
  /* Réduit (redemandé, "à peu près la taille des titres") : reprend
     exactement le clamp de .steps__title (juste en dessous) - jusqu'ici
     nettement plus grand (1.6-2.1rem) que le titre à côté, un écart plus
     justifié depuis que .steps__title est passé à une police plus fine
     (Chronicle Display Roman, remplace Montserrat Bold). */
  font-size:clamp(1.4rem, 2.2vw, 1.7rem);
  line-height:1;
}
.steps__title{
  /* Chronicle Display Roman (redemandé, remplace Montserrat Bold) +
     capitales (inchangé, déjà demandé précédemment). NON répercuté sur
     .steps__number juste au-dessus, qui reste en Montserrat Bold malgré
     son commentaire ("même police que .steps__title") : demande limitée
     au titre cette fois-ci, pas aux chiffres - à revoir si besoin de
     réharmoniser les deux. */
  font-family:"Chronicle Display Roman", var(--sans); font-weight:400;
  text-transform:uppercase;
  color:var(--text-strong);
  /* Agrandi (redemandé, "une taille un peu plus grande") : 1.2-1.4rem ->
     1.4-1.7rem. */
  font-size:clamp(1.4rem, 2.2vw, 1.7rem);
}
.steps__text{
  font-family:var(--sans); font-weight:300;
  /* Harmonisé sitewide (redemandé) : taille 2, voir --fs-caption. */
  color:var(--text); font-size:var(--fs-caption); line-height:var(--lh-caption);
}
/* Bascule au survol (rotateY) abandonnée, remplacée par un effet
   "tiroir" : pas de rotation, la carte reste plate. Le nom de l'offre
   (mark-box) remonte vers le haut de la carte au survol/tap, ce qui
   révèle le texte descriptif juste en-dessous (il glisse depuis le bas +
   fondu), comme si le nom coulissait pour laisser apparaître un tiroir.
   Ratio 4:5 (largeur:hauteur) imposé via aspect-ratio, plus une
   min-height (32vw) pour rester lisible avant que le ratio ne prenne le
   dessus sur les très petits écrans. Nombre d'or (prompt-page-pro.md) :
   4:5 gardé plutôt qu'un ratio ~1:1,6 (cartes ~28% plus hautes) : le
   brief autorise explicitement à garder 4:5 si l'ajustement n'améliore
   pas clairement le rendu ; la distance de remontée du "tiroir" au survol
   (translateY(-140%) sur .offer-card__mark-box) dépend de ces proportions
   déjà réglées, et sans moyen de vérifier visuellement le rendu dans cette
   session, le risque de régression dépasse le bénéfice incertain.
   max-height ajouté (signalé, bloc "trop haut") : le Bloc 4 vit désormais
   dans le pin partagé #act3Pin, contraint à 100svh (voir .bloc--4) ; sans
   plafond, le ratio 4:5 pouvait pousser les cartes bien au-delà de la
   hauteur disponible une fois titre/sous-titre déduits. 56svh borne la
   hauteur réelle sans casser le ratio tant qu'il tient dans ce plafond
   (cas courant, viewports larges, cartes quasi à leur taille d'origine :
   un premier essai à 46svh les avait trop réduites, signalé, "trop
   petites"), le contenu (juste un nom centré) reste lisible même
   réduit sur les viewports plus courts où le plafond joue vraiment. */
/* Cadre repris de l'animation du loader (× convergent vers les 4
   coins + bordure qui se dessine, voir .loader__x/.loader__edge et
   .cta-card__mark/__edge, déjà réutilisé une 1ère fois là), demandé
   explicitement pour ces 3 cartes aussi. Différence avec .cta-card :
   ce cadre reste piloté par .is-flipped (survol/tap), PAS par le
   scroll - l'arrivée au scroll de la carte ENTIÈRE, elle, est
   désormais confiée à une chorégraphie dédiée en 2 temps (demandée,
   voir tickOffersCards, main.js, et .offer-card--left/--center/--right
   plus bas) plutôt qu'au système générique data-reveal-block (retiré
   du HTML) : les deux animations restent indépendantes, l'une sur la
   carte entière, l'autre sur son contenu interne. Déclenché en
   CSS pur par .is-flipped à la place (déjà
   posée au survol/tap par le "tiroir" existant, voir main.js) :
   --mark-off/--mark-opacity/--edge-scale/--edge-opacity passent de
   leur valeur de repos à leur valeur "révélée" en même temps que le
   nom remonte, avec transition sur les propriétés qui les consomment
   (.offer-card__corner/.offer-card__edge, plus bas) puisque des
   custom properties ne s'animent pas nativement sans @property.
   Décalage de départ des "×" réduit (10px, pas 22px comme sur
   .cta-card) : cette carte garde overflow:hidden (nécessaire au
   tiroir existant, mark-box qui remonte de 140% de sa hauteur) - un
   décalage plus grand les ferait démarrer hors du cadre et donc
   rognés le temps de la transition, contrairement à .cta-card qui n'a
   pas cette contrainte. Angles POINTUS (border-radius:0, au lieu de
   14px) : demandé explicitement, comme pour .cta-card. */
/* overflow:visible (était hidden) : demandé, "encadrement doré extérieur
   et non intérieur pour les cartes des offres au survol" - le cadre
   (corners/edges, plus bas) doit désormais déborder hors de la carte,
   qu'un overflow:hidden ICI clipperait entièrement quelle que soit sa
   propre position (négative ou non). Le clip nécessaire au "tiroir"
   (mark-box qui remonte de 140% de sa hauteur) est déplacé sur un
   nouveau conteneur dédié, .offer-card__viewport (voir plus bas, et
   HTML) : lui seul reste en overflow:hidden, corners/edges restant
   enfants directs de .offer-card n'y sont plus soumis. */
.offer-card{
  --edge-scale:0;
  --edge-opacity:0;
  /* --offer-border-rest : couleur finale, jamais modifiée (contrairement
     à --offer-border juste en dessous) - existe uniquement pour que
     motion.js puisse la LIRE (getComputedStyle) au chargement, sans
     dépendre de la valeur (transitoire) de --offer-border à cet instant. */
  --offer-border-rest:rgba(88,101,106,.16);
  /* --offer-border (redemandé, bug réel trouvé) : indirection nécessaire
     pour que motion.js puisse animer la couleur de bordure PENDANT la
     phase de fusion (voir plus bas) sans jamais écrire border-color en
     inline - un style inline gagnerait toujours face à .is-flipped {
     border-color:var(--gold) } (survol), quelle que soit sa spécificité
     CSS, bloquant le survol doré pour de bon. En passant par cette
     variable, .is-flipped reste une déclaration border-color DIRECTE,
     indépendante de --offer-border, qui continue de fonctionner
     normalement. Répare au passage la réversibilité (signalé, "les
     lignes restent apparentes en remontant") : une variable tweenée
     normalement (contrairement à un clearProps, one-shot et non
     réversible) revient correctement à sa valeur de départ quand on
     remonte le scroll.
     Valeur de repos transparent (PAS la couleur finale rgba, était
     rgba(88,101,106,.16) - bug réel trouvé, signalé "au tout premier
     chargement de la page, les lignes de séparation sont visibles") :
     l'état "fusionné" (bordure invisible) est désormais le défaut CSS
     lui-même, plus un gsap.set() au chargement - motion.js ne fait plus
     que l'ANIMER vers sa vraie valeur pendant la phase 3 (voir plus bas),
     il n'a plus besoin de la poser une 1ère fois lui-même. Élimine toute
     dépendance à l'ordre d'exécution JS/rendu initial pour ce premier
     état (même logique que .offer-card__mark-box/__text : masqué "pour
     de vrai" en CSS, jamais par un aller simple en JS). border-radius
     suit le même principe juste en dessous (0, pas var(--radius)) - le
     rendu final de ces 2 propriétés est restauré sous reduced-motion/
     mobile (media query plus bas, motion.js n'y tourne jamais). */
  --offer-border:transparent;
  position:relative;
  overflow:visible;
  /* width:100% explicite (bug réel trouvé, signalé "lié à la dimension
     de la fenêtre", persistant après le correctif --offers-gap/--merge
     ci-dessous) : sur un viewport assez large mais PEU HAUT (fenêtre de
     laptop 13-15", jamais reproduit sur un grand écran 32" sauf en
     rétrécissant sa fenêtre), max-height:56svh limitait la hauteur AVANT
     que aspect-ratio ait fini de dériver ses dimensions depuis la
     largeur de piste de grille (stretch implicite) - le navigateur
     recalculait alors la largeur à partir de cette hauteur plafonnée
     pour respecter 4:5, laissant chaque carte plus étroite que sa piste
     de grille réelle. Résultat : un espace résiduel entre les cartes
     que RIEN ne pouvait combler, même une fois --merge/--offers-gap
     parfaitement synchronisés (le transform ne fait que déplacer une
     boîte, pas l'élargir) - d'où le "rectangle divisé en 3" observé.
     Une largeur EXPLICITE (valeur déclarée, pas un stretch implicite)
     prime sur aspect-ratio dans l'algorithme de résolution des tailles :
     la carte remplit désormais toujours sa piste de grille, quitte à
     s'écarter légèrement du ratio 4:5 une fois max-height atteint
     (dégradation gracieuse déjà voulue par ce plafond, voir plus haut). */
  width:100%;
  aspect-ratio:4 / 5;
  min-height:clamp(280px, 32vw, 380px);
  max-height:56svh;
  background:var(--white);
  border:1px solid var(--offer-border);
  border-radius:0;
  padding:clamp(2rem, 3.4vw, 2.8rem) clamp(1.8rem, 3vw, 2.4rem);
  box-shadow:0 18px 40px -30px rgba(88,101,106,.4);
  --offer-mark-h:clamp(48px, 6vw, 64px);
  /* --offer-hover-dur (dédié - remplace var(--dur-hover) sur ce survol
     précis) : redemandé, "l'animation de survol des cartes est trop
     rapide, peut-on faire un fade plus lent" - var(--dur-hover) (.32s)
     est utilisée par la quasi-totalité des survols du site (boutons,
     liens...), l'allonger globalement aurait ralenti tout le reste avec
     elle. Scopée à .offer-card (comme --offer-mark-h juste au-dessus) :
     hérite vers .offer-card__edge/__mark-box/__details/.is-entered plus
     bas, seul ce survol-ci est concerné.
     .35s -> 1.4s (corrigé - le passage précédent, "réduit de moitié",
     était allé dans le mauvais sens - reconfirmé explicitement, "le
     titre se déplace vers le haut et laisse apparaître le descriptif...
     c'est cette animation qui doit être drastiquement ralentie") : 1.4s,
     4x la valeur précédente, pour un mouvement nettement plus lent et
     posé plutôt qu'un simple ajustement fin. */
  --offer-hover-dur:1.4s;
  /* --merge (bug réel trouvé et corrigé - voir --offers-gap ci-dessus
     pour le détail complet) : 1 = fusionnée (bord collé à la carte
     centrale), 0 = séparée (position naturelle de la grille). Piloté
     par motion.js (gsap.set/fromTo sur cette seule variable, jamais un
     x/y en pixels) - .offer-card--left/--right, plus bas, calculent
     eux-mêmes le déplacement réel via calc(), toujours avec le
     --offers-gap ACTUEL au moment du rendu. */
  --merge:1;
}
/* Décale les 2 cartes extérieures vers la carte centrale (immobile,
   jamais de transform) : illusion du rectangle fusionné, obtenue par
   transform pur (jamais le layout - gap CSS toujours intact en
   dessous). calc(var(--merge) * var(--offers-gap)) plutôt qu'une valeur
   figée en JS : recalculé par le NAVIGATEUR à chaque rendu, donc
   toujours cohérent avec le gap réel quelle que soit la largeur de
   fenêtre au moment considéré - élimine à la racine le bug corrigé
   ci-dessus (--offers-gap). */
.offer-card--left{ transform:translateX(calc(var(--merge) * var(--offers-gap))); }
.offer-card--right{ transform:translateX(calc(var(--merge) * -1 * var(--offers-gap))); }
/* transition:border-color déplacée ici, scopée à .is-entered (bug réel
   trouvé en creusant le signalement des lignes visibles) : posée sur
   .offer-card elle-même, cette transition s'appliquait aussi PENDANT le
   scrub (motion.js change --offer-border - donc border-color - à un
   instant précis, duration:0, pour rester parfaitement synchronisé au
   scroll comme border-radius juste à côté) - le navigateur lissait alors
   ce changement en 0,32s RÉELLES, désynchronisées du scroll, un
   décalage visible en scrollant vite. .is-entered n'est posée qu'une
   fois la séquence terminée (voir motion.js) : la transition ne
   s'applique donc plus qu'au survol (.is-flipped, seul déclencheur
   possible une fois .is-entered actif), son usage prévu à l'origine. */
.offer-card.is-entered{
  transition:border-color var(--offer-hover-dur) var(--ease);
}
/* Reprend le rôle d'overflow:hidden retiré de .offer-card ci-dessus,
   scopé au seul contenu qui en a besoin (le "tiroir" mark-box/text) :
   position:absolute;inset:0 reproduit exactement la même boîte que
   .offer-card avait pour ces enfants (percentages de position absolue
   résolus contre la padding-box du plus proche ancêtre positionné,
   inchangé par construction), donc aucune différence visuelle pour
   eux - seuls corners/edges (restés enfants directs de .offer-card,
   plus soumis à AUCUN overflow) peuvent désormais déborder. */
.offer-card__viewport{
  position:absolute; inset:0;
  overflow:hidden;
}
/* Ancienne entrée en 2 temps (tickOffersCards, translateX/Y + z-index
   superposé) retirée (redemandée, nouvelle séquence en 5 phases - voir
   motion.js) : la "fusion" des 3 cartes en un seul rectangle (phase 1-2)
   et leur "séparation" (phase 3) sont désormais entièrement portées par
   .offers__grid { gap }, animé par GSAP de 0 à sa valeur de repos - les
   3 cartes restent chacune dans sa propre piste de grille du début à la
   fin, aucun transform/z-index de positionnement n'est plus nécessaire
   ici (voir aussi .offer-card, borderRadius/borderColor désormais posés
   par motion.js pour le même effet de fusion, plutôt que par une classe
   CSS dédiée). */
/* Pas de soulèvement (translateY) ni d'ombre accentuée au survol
   (signalé : essayé à la passe précédente, refusé explicitement,
   "je ne veux pas lever les cartes") : seuls le contour doré et le
   cadre (.offer-card__edge, plus bas) doivent s'animer au survol. */
.offer-card.is-flipped{
  border-color:var(--gold);
  --edge-scale:1;
  --edge-opacity:1;
}
/* Cadre reparti des COINS, pas du centre (demandé : le précédent
   .offer-card__frame - un seul conteneur qui grandissait depuis son
   propre centre via scale() - donnait l'impression que le cadre
   "sortait du milieu" de la carte plutôt que d'émaner de ses coins).
   Repris des 4 traits indépendants (.offer-card__edge), chacun ancré
   par transform-origin sur UN SEUL de ses 2 coins plutôt que centré -
   les 4 origines s'enchaînent (haut:gauche -> droite:haut ->
   bas:droite -> gauche:bas) pour former un tracé continu qui semble
   partir de chaque coin et parcourir tout le rectangle, plutôt qu'un
   simple agrandissement uniforme. -8px sur chaque trait (inchangé) :
   même écart resserré par rapport à la carte. */
.offer-card__edge{
  position:absolute; background:var(--gold);
  opacity:var(--edge-opacity);
  pointer-events:none;
  transition:opacity var(--offer-hover-dur) var(--ease), transform var(--offer-hover-dur) var(--ease);
}
.offer-card__edge--t{top:-8px; left:-8px; width:calc(100% + 16px); height:1px; transform-origin:left; transform:scaleX(var(--edge-scale));}
.offer-card__edge--r{top:-8px; right:-8px; width:1px; height:calc(100% + 16px); transform-origin:top; transform:scaleY(var(--edge-scale));}
.offer-card__edge--b{bottom:-8px; left:-8px; width:calc(100% + 16px); height:1px; transform-origin:right; transform:scaleX(var(--edge-scale));}
.offer-card__edge--l{top:-8px; left:-8px; width:1px; height:calc(100% + 16px); transform-origin:bottom; transform:scaleY(var(--edge-scale));}
/* Bug corrigé : le nom de l'offre n'était pas au vrai milieu de la carte
   (display:flex + justify-content:center centrait le GROUPE nom+texte,
   or le texte occupait toujours sa place en layout même invisible à
   opacity:0, ce qui poussait le nom au-dessus du centre réel). Le nom et
   le texte sont maintenant tous les deux en position:absolute, centrés
   indépendamment l'un de l'autre sur les mêmes coordonnées (top/left:50%
   + translate(-50%,-50%)) : le nom est donc exactement au milieu de la
   carte par construction, quelle que soit la présence du texte. */
/* Position "remontée" au survol de nouveau, PAS permanente (corrigé -
   redemandé, "au survol, le titre se déplace vers le haut, révélant le
   texte descriptif en dessous") : le nom repart du vrai centre de la
   carte au repos (translate(-50%,-50%) seul, sans translateY
   supplémentaire) - le "tiroir" ouvert au survol, un temps abandonné
   (voir l'historique de ce commentaire), est donc réintroduit, avec en
   plus la mention prix/CTA ajoutée ci-dessous (.offer-card__details).
   transition ajoutée (absente jusqu'ici, la valeur étant fixe) : le
   déplacement doit maintenant s'animer, comme le reste du survol
   (.offer-card__edge, même durée). */
.offer-card__mark-box{
  position:absolute; left:50%; top:50%;
  transform:translate(-50%, -50%);
  display:flex; align-items:center; justify-content:center;
  width:calc(100% - clamp(3.6rem, 6vw, 4.8rem)); height:var(--offer-mark-h);
  transition:transform var(--offer-hover-dur) var(--ease);
}
.offer-card.is-flipped .offer-card__mark-box{
  transform:translate(-50%, -50%) translateY(-140%);
}
/* Bug réel trouvé (signalé, "PARTENAIRE est trop gros, il faut un
   entre 2") : l'ancien réglage (max-height:56px/max-width:80% pour
   PRO/PRO+, max-height:138px/max-width:96% pour PARTENAIRE seul)
   semblait viser des hauteurs égales, mais max-width étant LA
   contrainte qui bloque en pratique pour les 3 (mesuré : PRO/PRO+
   ~41px, PARTENAIRE ~49px, jamais 56 ni 138) - le mot "PARTENAIRE"
   (plus long) a besoin de moins de hauteur pour atteindre la même
   largeur relative que "PRO"/"PRO+", d'où l'écart. Unifié sur les 3
   cartes avec la MÊME valeur pour les deux contraintes plutôt qu'un
   override scopé à une seule carte (déjà la même correction que sur
   mobile, voir plus bas) - max-height choisi ici comme la valeur qui
   compte réellement puisque max-width (88%, une valeur "entre deux"
   de 80/96%) reste assez généreux pour ne jamais la devancer. */
.offer-card__mark{
  height:auto; max-height:45px; width:auto; max-width:88%;
  object-fit:contain; object-position:center center;
}
/* .offer-card__details (nouveau conteneur, remplace le positionnement
   absolute individuel de .offer-card__text) : regroupe le texte
   descriptif ET la nouvelle mention prix/CTA (.offer-card__detail,
   ajoutée ci-dessous) dans un flux normal (flex column) plutôt que 2
   éléments positionnés en absolu indépendamment - la hauteur du groupe
   n'est donc plus à deviner via un offset fixe pour empiler proprement
   les deux. Caché par défaut, révélé au survol (.is-flipped, comme
   .offer-card__mark-box ci-dessus) : redemandé, "au survol... révélant
   le texte descriptif en dessous" - le texte n'est donc plus visible en
   permanence (contrairement à la passe précédente). Positionné juste
   sous le bord bas RÉEL du nom une fois remonté au survol (centre - 0,9
   * sa hauteur, même calcul que la remontée du nom ci-dessus) plus un
   petit espacement fixe. */
.offer-card__details{
  position:absolute; left:clamp(2.4rem, 5vw, 3.4rem); right:clamp(2.4rem, 5vw, 3.4rem);
  top:calc(50% - .9 * var(--offer-mark-h) + 14px);
  display:flex; flex-direction:column; align-items:center; gap:.6em;
  opacity:0;
  transition:opacity var(--offer-hover-dur) var(--ease);
}
.offer-card.is-flipped .offer-card__details{opacity:1;}
/* Le masquage/la révélation au scroll (phase 4 de la séquence
   d'entrée, motion.js) continue de se faire sur les LIGNES issues du
   split-by-line (.reveal-line, générées par splitIntoLines()), pas sur
   ce conteneur - même convention que tout autre texte split-by-line du
   site - désormais imbriquée dans le masquage/la révélation au SURVOL
   du groupe .offer-card__details ci-dessus (2 conditions cumulatives :
   la séquence d'entrée doit être terminée ET la carte survolée). */
.offer-card__text{
  font-family:var(--sans); font-weight:300;
  /* Harmonisé sitewide (redemandé) : taille 2, voir --fs-caption. */
  font-size:var(--fs-caption); line-height:var(--lh-caption); color:var(--ink);
  text-align:center;
}
/* .offer-card__detail (nouveau, demandé) : mention courte sous le texte
   descriptif, révélée en même temps que lui (même conteneur
   .offer-card__details, même transition opacity). --price (carte PRO
   seule) : "À partir de 400€" en doré, gras (redemandé, "en doré") - les
   2 autres cartes (PRO+/PARTENAIRE, pas de prix) affichent "Plus
   d'informations" sans ce modificateur, même couleur/poids que le texte
   descriptif juste au-dessus. --price traitée à l'identique des 2 autres
   depuis (redemandé, "comme pour les 2 autres cartes... le descendre, le
   faire pointer vers le formulaire et le rendre survolable") : même
   balise <a href="#ctaForm"> (voir HTML), plus de traitement à part. */
.offer-card__detail{
  font-family:var(--sans); font-weight:300;
  font-size:var(--fs-caption); line-height:var(--lh-caption);
  /* var(--ink) -> var(--gold) (demandé, "mets 'Plus d'informations' en
     doré") : les 2 cartes sans prix reprennent donc la même couleur que
     la mention prix de la carte PRO - --price (juste en dessous) n'a
     donc plus besoin de redéclarer sa propre couleur, seul son poids
     (600, gras) continue de la distinguer. */
  color:var(--gold);
  text-align:center;
}
/* "Plus d'informations" (PRO+/PARTENAIRE) ET "À partir de 400€" (PRO,
   rejointe depuis) pointent vers le formulaire (demandé, "les faire
   pointer vers le formulaire") : toutes deux des <a href="#ctaForm">
   (voir HTML) - qualifié par le tag (a.offer-card__detail), donc
   appliqué automatiquement aux 3 cartes sans distinction.
   Traité comme un petit bouton (redemandé, "descendre un peu 'plus
   d'informations' pour qu'au survol, un cadre doré apparaisse... comme
   un bouton. Cela laissera assez d'espace entre la description de
   l'offre et 'plus d'informations' pour respirer", remplace le
   soulignement du 1er essai) : margin-top propre (en plus du gap déjà
   fourni par .offer-card__details) pour l'espace de respiration demandé
   - padding + border TRANSPARENTE au repos (même épaisseur qu'au
   survol, pour qu'aucun décalage de taille/reflow ne survienne quand la
   couleur apparaît). */
a.offer-card__detail{
  display:inline-block;
  margin-top:.9em;
  padding:.5em 1.1em;
  border:1px solid transparent;
  border-radius:var(--radius);
  transition:border-color var(--offer-hover-dur) var(--ease);
}
a.offer-card__detail:hover, a.offer-card__detail:focus-visible{
  border-color:var(--gold);
}
.offer-card__detail--price{font-weight:600;}

/* Breakpoint harmonisé sur 860px (audit demandé). */
@media (max-width:860px){
  /* --offers-gap resynchronisée avec le gap réel de cette media query
     (1.2rem, pas le clamp() desktop) - voir son commentaire plus haut :
     .offer-card--left/--right lisent cette même variable pour calculer
     leur décalage de fusion, doit donc toujours refléter la valeur
     RÉELLEMENT appliquée à ce breakpoint. */
  .offers__grid{--offers-gap:1.2rem; grid-template-columns:1fr; gap:var(--offers-gap);}
  /* Fusion verticale sur mobile (redemandé, "3 cartes... empilées
     verticalement") : .offers__grid passe en 1 seule colonne ci-dessus,
     les 3 cartes s'y empilent donc en 3 RANGÉES - translateY plutôt que
     translateX, même formule calc(var(--merge) * var(--offers-gap))
     sinon (voir la règle desktop, plus haut, pour le détail complet). */
  .offer-card--left{ transform:translateY(calc(var(--merge) * var(--offers-gap))); }
  .offer-card--right{ transform:translateY(calc(var(--merge) * -1 * var(--offers-gap))); }
  /* Hauteur réduite (220px -> 148px, redemandé, "repense la mécanique...
     chaque carte doit avoir une hauteur réduite") : les 3 cartes sont
     désormais empilées PLEINE LARGEUR (1 colonne, ci-dessus) au lieu de
     3 colonnes côte à côte - à cette hauteur desktop inchangée, la
     citation (qui recouvre toute la grille, .offers__quote) aurait
     occupé un rectangle bien plus haut que large une fois empilé
     verticalement, disproportionné (signalé) et ne tenant pas dans
     #bloc6 { min-height:100vh } avec le reste du contenu de la
     séquence (titre/sous-titre, étapes, CTA). Padding resserré en même
     temps (clamp desktop -> valeurs fixes plus petites) pour que
     nom + texte tiennent dans cette hauteur réduite. */
  /* --offer-mark-h : 48-64px -> 26px (redemandé, "titres des cartes...
     taille réduite pour que le contenu tienne sans déborder") - le nom
     PRO/PRO+/PARTENAIRE (image, voir .offer-card__mark plus bas) et la
     position du texte sous lui (calc(50% - .9 * var(--offer-mark-h)...),
     voir .offer-card__text) dépendent tous les deux de cette seule
     variable, réduite ici une fois pour les deux.
     min-height 148px -> 184px (bug réel trouvé, testé au survol sur
     mobile) : 148px avait été calé sur nom + texte descriptif SEULS -
     la nouvelle mention prix/CTA (.offer-card__detail, ajoutée sous le
     texte dans .offer-card__details) dépassait le bas de
     .offer-card__viewport (overflow:hidden), donc rognée à mi-hauteur
     au lieu de rester lisible ou proprement masquée. */
  .offer-card{aspect-ratio:auto; min-height:184px; padding:1.3rem 1.5rem; --offer-mark-h:26px;}
  /* Bug corrigé (signalé, "réduire l'illustration PARTENAIRE sur
     mobile, elle est trop grande") : max-height:138px (voir plus haut)
     est pensé pour la carte desktop, bien plus haute (min-height
     280-380px) - beaucoup trop généreux ici une fois la carte réduite.
     Ramené à une taille plus proche de celle de PRO/PRO+ (max-height
     56px, la valeur de base commune), encore réduite (56px -> 40px)
     pour la nouvelle hauteur de carte plus compacte (148px, contre
     220px avant ce même correctif).
     2e bug corrigé (signalé, capture à l'appui - "PARTENAIRE" encore
     visiblement plus petit que PRO/PRO+") : donner un max-width propre
     à .offer-card--partenaire seul ne suffisait pas - restait PLUS
     PETIT que PRO/PRO+ (mesuré : ~34px de haut contre 40px visé), pas
     plus gros comme le 1er correctif le supposait. Cause réelle : à
     cette largeur de carte réduite, c'est désormais PRO/PRO+ eux-mêmes
     qui buttent sur leur propre max-width:80% HÉRITÉ (jamais dédié à
     un override mobile) avant d'atteindre max-height:56px - la
     comparaison "PARTENAIRE vs PRO/PRO+" avait changé de sens en cours
     de route. Corrigé en unifiant les 3 sur LA MÊME règle mobile
     (max-height ET max-width identiques pour les 3), plutôt qu'un
     override scopé à une seule carte qui recréait un écart à chaque
     fois que l'autre bute sur une contrainte différente. */
  .offer-card__mark{max-height:40px; max-width:92%;}
  .offer-card__text{font-size:.76rem; line-height:1.35;}
  /* Étapes du process : colonnes empilées, les lignes verticales
     n'auraient plus de sens - remplacées par des lignes horizontales
     entre les items empilés. */
  .steps__grid{grid-template-columns:1fr;}
  .steps__item:not(:first-child){border-left:none; border-top:1px solid var(--card-border);}
  /* Citation : taille de police ramenée d'un titre de section
     (var(--fs-title), jusqu'à 4.39rem) à une taille adaptée au
     rectangle réduit décrit ci-dessus (redemandé, "hauteur réduite...
     citation affichée en plus petite taille de police, adaptée à cet
     espace réduit"). */
  .offers__quote{font-size:clamp(1.05rem, 5vw, 1.3rem); line-height:1.3;}
  /* .bloc6-pin : "rab" de scroll propre au mobile (pas le 130vh du
     desktop, pensé pour une mécanique plus complexe côte-à-côte) -
     la nouvelle mécanique verticale (voir initOffersSequence, motion.js)
     a une trajectoire plus courte (translateY sur 1 seul axe, pas de
     grille à réorganiser) et un contenu déjà compact (cartes réduites
     ci-dessus) : moins de scroll suffit à dérouler les 5 phases. */
  .bloc6-pin{height:calc(100vh + 90vh);}
}

/* ==========================================================================
   BLOC 5 : RÉFÉRENCES & PUBLICATIONS
   Refonte, 2e passe (signalée à la capture, "Bloc-Preuves-Actuel.png" ne
   correspondait pas à "Bloc-Preuves-Souhaité.png", cette 2e référence
   fournie faisant à la fois foi sur des points non couverts par la 1ère
   passe ET la corrigeant sur un point contraire : la ligne verticale,
   demandée à DROITE lors de la 1ère passe, est ici clairement à GAUCHE,
   juste après le titre (comme dans "inspiration-bloc-preuves.png"
   d'origine, jamais inversée en réalité - correction assumée) :
   - Titre sur 2 lignes ("Références &" / "Publications", <br> explicite
     dans le HTML plutôt qu'un retour dépendant de la largeur du texte,
     pour un résultat fiable quel que soit le rendu de police), taille
     nettement réduite (variante réduite de .section-title, comme
     .bloc6__title, pas la taille pleine des titres de section centrés).
   - .references__frame : conteneur plein-bleed (100vw, même technique
     que l'ancienne .references__subtitle-row) qui porte hline (pleine
     largeur), vline (voir passes suivantes pour sa hauteur/position
     exactes - a bougé plusieurs fois depuis : d'abord courte, puis
     étirée jusqu'en bas ; d'abord alignée au bord droit de la colonne
     du titre, puis à son centre, puis revenue à son bord GAUCHE),
     "+" à leur intersection, et les 2 étages de logos, alignés sur la
     même abscisse que la verticale (--ref-left-inset, demandé), et
     masqués (overflow:hidden) exactement à cette même abscisse à
     gauche (voir .references__row plus bas pour ce dernier point,
     corrigé après-coup). Logos plus grands et mieux espacés.
   - "+" : corrigé un bug qui le rendait invisible ("actuellement absent
     du rendu", signalé) - centré exactement SUR le coin haut-gauche du
     cadre (top:0 du frame, en overflow:hidden) avec une marge négative
     de la moitié de sa taille, il se retrouvait pour moitié hors de la
     zone visible du cadre (au-dessus de top:0), donc rogné. Corrigé en
     réservant explicitement de la place en haut du cadre (--ref-plus-
     inset) : hline/vline/plus démarrent tous les trois à ce même y,
     jamais à 0 littéralement. 2e bug du même ordre trouvé et corrigé au
     passage (voir le commentaire de --ref-plus-inset, plus bas) : une
     fois tourné, le "+" dépassait encore par intermittence (le rayon
     balayé par sa rotation est plus grand que sa demi-hauteur au repos).
   ========================================================================== */
.references{
  background:var(--white);
  /* padding-top doublé (corrigé - même traitement que .testimonials plus
     bas dans ce fichier, "répète l'espace mis au-dessus de la ligne
     haute du bandeau des avis, pour la ligne haute de Références") :
     l'ancien padding-block:var(--section-gap) uniforme laissait la ligne
     du haut (+ le "+") trop collée au bloc Offres qui précède, #sep5
     (Offres -> Références) ayant été retiré (redemandé) sans que son
     espace soit compensé jusqu'ici. padding-bottom reste à
     var(--section-gap) (PAS de compensation ici, contrairement à
     .testimonials - précisé après coup, "il faut descendre en
     conséquence ce qu'il y a en dessous, le titre de la FAQ etc.") : le
     bloc FAQ qui suit doit bien se décaler d'autant plus bas. */
  padding-top:calc(var(--section-gap) * 2);
  padding-bottom:var(--section-gap);
  /* --ref-plus-size (taille visuelle, "+" au repos, non tourné) et
     --ref-plus-inset (espace réservé en haut du cadre, où hline/vline/
     "+" démarrent tous les trois) sont volontairement 2 valeurs
     distinctes : bug corrigé (mesuré, "+" rogné par intermittence par
     l'overflow:hidden du cadre) - une fois tourné, la boîte carrée du
     "+" balaie un cercle de rayon (taille/2)*racine(2) autour de son
     centre (l'ANGLE de rotation change en permanence, l'AABB mesurée
     par le navigateur suit ce rayon, pas la demi-hauteur immobile de la
     boîte) : réserver seulement (taille/2) laissait la pointe du carré
     dépasser au-dessus de y=0 (donc rognée) à certains angles
     (proche de 45°/135°/...). --ref-plus-inset couvre ce rayon complet
     avec une petite marge. */
  --ref-plus-size:16px;
  --ref-plus-inset:13px;
  /* Distance ligne-du-haut -> 1er bandeau, réutilisée à l'identique pour
     2e bandeau -> ligne-du-bas (redemandé, "à distance égale par rapport
     à la ligne du haut") : même variable des deux côtés, garantit
     l'égalité par construction plutôt qu'une valeur recalculée à la
     main. */
  --ref-line-gap:clamp(2.5rem, 5vw, 3.5rem);
}
.references__wrap{max-width:1680px; margin-inline:auto; padding-inline:var(--gutter);}
/* Grand titre centré retiré (redemandé, même traitement que le bloc
   Avis) : remplacé par .references__eyebrow ci-dessous, posée sur
   .references__hline. */
/* .references__eyebrow : même principe que .testimonials__eyebrow
   (voir son commentaire, plus bas dans ce fichier, pour le détail) -
   .references__frame est plein cadre (100vw) comme .testimonials__frame,
   même calc() pour retrouver le bord gauche du "tronc" central de la
   page à partir du bord du viewport. Fond var(--white) opaque : creuse
   la ligne par recouvrement local, comme .references__plus la creuse en
   son centre. */
.references__eyebrow{
  position:absolute;
  top:var(--ref-plus-inset);
  left:calc(max(0px, (100vw - 1680px) / 2) + var(--gutter));
  transform:translateY(-50%);
  z-index:1;
  background:var(--white);
  padding-inline:1rem;
  font-family:var(--sans); font-weight:300;
  text-transform:uppercase;
  letter-spacing:.04em;
  color:var(--ink);
  font-size:var(--fs-caption); line-height:var(--lh-caption);
  white-space:nowrap;
}
.references__frame{
  position:relative;
  width:100vw; margin-left:calc(50% - 50vw);
  padding-top:calc(var(--ref-line-gap) + var(--ref-plus-inset));
  /* Ligne du bas ajoutée (redemandé) : ce padding-bottom (= --ref-line-gap,
     identique au gap ligne-du-haut -> 1er bandeau) crée l'espace entre le
     2e bandeau et .references__hline-bottom, posée à bottom:0 de ce
     padding - donc à distance égale de la ligne du haut, par construction. */
  padding-bottom:var(--ref-line-gap);
  overflow:hidden;
}
/* Bug corrigé (signalé, capture "Bug-Preuves-02" : la ligne verticale
   "s'éclaircit et disparaît presque" au passage des logos) : vérifié
   par elementFromPoint() qu'elle peint déjà AU-DESSUS des logos (pas un
   bug d'ordre d'empilement - un élément positionné peint par défaut
   au-dessus du contenu statique, indépendamment de l'ordre dans le
   DOM), donc le souci est ailleurs : à opacité .35 (le ton discret déjà
   utilisé par les séparateurs/frame-connector ailleurs sur le site,
   pensé pour un fond blanc fixe), le mélange avec les pixels gris des
   logos qui défilent juste derrière/à proximité change sensiblement le
   résultat perçu (un mélange à 35% laisse largement dominer ce qu'il y
   a derrière), donnant l'impression que la ligne "s'efface". Remonté à
   .85 : le résultat visuel reste quasiment identique quel que soit le
   fond (blanc ou logo gris clair) derrière, donc redevient stable.
   Même traitement sur hline (elle aussi longée en permanence par les
   logos). Le "+" (déjà en background:var(--gold) plein, pas de rgba)
   n'était pas concerné. */
/* Espace creusé pour le "+" (signalé, "comme pour les séparateurs") :
   hline coupée en 2 segments (gauche/droite) de part et d'autre de
   --ref-left-inset, avec un espace entre eux (--ref-plus-inset,
   réutilisée : même rayon de sécurité déjà calculé pour la rotation du
   "+", donc cohérent). vline, elle, n'a besoin que d'un espace en HAUT
   (top décalé du même rayon) : elle commence pile là où le "+" est
   posé (croisement en coin, pas un croisement en plein milieu comme
   pour hline), inutile de la couper en 2. */
/* Ligne verticale retirée entièrement (redemandé). Le "+" revient au
   CENTRE de la ligne horizontale du haut (redemandé, "le + revient au
   centre de la ligne horizontale du haut") : hline/hline--r se coupent
   désormais à 50% (plus --ref-left-inset, qui reste utilisé ailleurs -
   largeur du titre, masquage des rangées de logos - inchangé pour ça). */
.references__hline{
  position:absolute; top:var(--ref-plus-inset);
  left:0; width:calc(50% - var(--ref-plus-inset));
  height:1px;
  background:rgba(214,183,125,.85);
}
.references__hline--r{
  position:absolute; top:var(--ref-plus-inset);
  left:calc(50% + var(--ref-plus-inset)); right:0;
  height:1px;
  background:rgba(214,183,125,.85);
}
/* Nouvelle ligne horizontale, sous le 2e bandeau (redemandé) : posée à
   bottom:0 du padding-bottom de .references__frame (= --ref-line-gap,
   voir son commentaire) - même distance que celle qui sépare la ligne du
   haut du 1er bandeau. Pleine largeur du cadre (100vw), pas de "+" ni de
   coupure - demande explicite portait uniquement sur "une seconde ligne
   horizontale", pas de croisement supplémentaire. */
.references__hline-bottom{
  position:absolute; left:0; right:0; bottom:0;
  height:1px;
  background:rgba(214,183,125,.85);
}
/* "+" à l'intersection des deux segments de la ligne du haut : dessiné en
   CSS pur (deux barres, ::before/::after, dans une boîte de taille fixe)
   plutôt qu'en glyphe de texte - même motif, et même raison, que
   .portfolio-h__plus (voir plus haut dans ce fichier) : le croisement des
   deux barres tombe alors, par construction géométrique, exactement au
   centre de la boîte, donc exactement sur le croisement des deux lignes,
   quel que soit le moteur de rendu. Petit et discret (demandé). Rotation
   en boucle infinie, CSS pur (@keyframes), puisqu'elle doit tourner en
   continu tant que les logos défilent (eux-mêmes toujours en mouvement,
   sans dépendance au scroll) plutôt que suivre un accumulateur piloté au
   scroll comme les autres "+" du site - "continue de tourner au rythme
   des logos" (redemandé) : l'animation continue, en boucle, sans jamais
   s'arrêter ni se resynchroniser sur le marquee, exactement comme avant. */
.references__plus{
  position:absolute;
  top:var(--ref-plus-inset); left:50%;
  width:var(--ref-plus-size); height:var(--ref-plus-size);
  margin-top:calc(var(--ref-plus-size) / -2); margin-left:calc(var(--ref-plus-size) / -2);
  animation:referencesPlusSpin 7s linear infinite;
}
.references__plus::before,
.references__plus::after{
  content:"";
  position:absolute;
  background:var(--gold);
}
.references__plus::before{
  top:0; bottom:0; left:50%;
  width:1.5px;
  margin-left:-.75px;
}
.references__plus::after{
  left:0; right:0; top:50%;
  height:1.5px;
  margin-top:-.75px;
}
@keyframes referencesPlusSpin{
  from{transform:rotate(0deg);}
  to{transform:rotate(360deg);}
}

/* Bug corrigé (signalé : les logos "ne disparaissent pas une fois
   passé la ligne verticale... ne doivent pas aller au-delà de la ligne
   verticale ni arriver de la gauche de cette ligne") : le masquage
   (overflow:hidden) ne se faisait jusqu'ici qu'aux bords DU CADRE
   (.references__frame, plein-bleed, donc les bords réels de la
   fenêtre), alors que le point de départ visuel des logos
   (padding-left:var(--ref-left-inset)) était plus loin à droite que
   ça : un logo restait donc visible dans toute la zone ENTRE le bord
   du cadre et la ligne verticale, aussi bien en l'approchant par la
   gauche (piste "presse") qu'en la dépassant vers la gauche (piste
   "clients", qui s'en éloigne). Corrigé en donnant à CHAQUE rangée son
   propre contexte de masquage (overflow:hidden), dont le bord GAUCHE
   tombe exactement sur --ref-left-inset (margin-left remplace l'ancien
   padding-left, seul moyen d'obtenir un bord de rognage à cette
   abscisse précise plutôt qu'au bord du cadre) : rien n'est plus jamais
   visible avant ce point, quelle que soit la piste. Le bord droit reste
   celui du cadre (largeur automatique d'un bloc, inchangé), les logos
   continuent de se masquer/rogner au bord réel de la fenêtre là-bas,
   comme prévu. */
/* display:flex ajouté (196e passe) : la piste "clients" est désormais 2
   <ul class="references__track"> consécutifs (voir le commentaire HTML,
   bug d'affichage WebKit lié à un calque de piste trop large) - sans
   flex, 2 <ul> (block par défaut) s'empileraient verticalement au lieu
   de se juxtaposer horizontalement.
   Bug corrigé dans la foulée (mesuré, largeur de piste retombée à
   2560px au lieu des ~6300px attendus - la piste "presse" ELLE AUSSI
   touchée, alors qu'elle n'a pourtant qu'un seul <ul>, pas 2) :
   flex-shrink vaut 1 par défaut sur tout enfant flex, y compris un
   .references__track déjà bien plus large (width:max-content) que
   .references__row (~1440px, contrainte par la largeur de la fenêtre) -
   dès que .references__row devient flex (ligne ci-dessus), CHAQUE
   .references__track existant (clients ET presse) se voit donc comprimé
   pour tenter de tenir dans cette largeur, écrasant leur width:max-
   content voulu. flex-shrink:0 sur .references__track (plus bas) annule
   ça : chaque piste garde sa largeur intrinsèque complète, quel que soit
   l'espace dispo dans .references__row (qui la rogne de toute façon via
   overflow:hidden, comme avant). */
/* Rognage à --ref-left-inset retiré (redemandé, "les logos disparaissent
   toujours derrière une ligne verticale qui n'existe plus... qu'ils
   défilent sur toute la largeur") - cette marge reproduisait la position
   de l'ancienne ligne verticale (retirée il y a 2 passes, masquage
   jamais mis à jour en conséquence). Sans margin-left, la rangée occupe
   toute la largeur de .references__frame (100vw) : le rognage
   (overflow:hidden, inchangé) n'intervient plus qu'aux bords réels de la
   fenêtre. */
.references__row{
  display:flex; align-items:center;
  overflow:hidden;
}
.references__row + .references__row{margin-top:clamp(1.5rem, 3vw, 2.25rem);}
.references__track{
  display:flex; align-items:center;
  flex-shrink:0;
  width:max-content;
  will-change:transform;
}
/* Logos agrandis (encore, demandé une 2e fois : "les logos doivent être
   plus grands") et mieux espacés. */
.references__track li{padding:0 clamp(2.6rem, 5vw, 4rem); flex:0 0 auto;}
/* Bug corrigé (signalé : "certains logos changent de couleur sans
   survol, aléatoirement") : :hover se déclenche sur la position RÉELLE
   à l'écran, qui bouge en permanence ici (défilement continu) - un logo
   qui passe simplement sous le curseur, resté immobile, déclenchait
   donc un vrai :hover, sans mouvement de souris perceptible par
   l'utilisateur, d'où l'impression de changement aléatoire. Un effet de
   survol n'a de toute façon pas de sens sur un contenu qui ne reste
   jamais en place : pointer-events:none désactive toute interaction
   sur la piste (jamais de :hover ni de clic), plutôt que de garder un
   effet qui ne peut plus se déclencher intentionnellement. */
.references__track{pointer-events:none;}
.references__track img{
  height:clamp(130px, 13vw, 190px); width:auto;
  filter:grayscale(1) opacity(.62);
}
/* Étage du haut (clients) : défile vers la gauche (translateX 0 -> -50%,
   demandé). Vitesse ralentie (signalé, "sur une échelle de 0 à 10... à
   3" - 60s jugé encore trop rapide) : durée x2,5 (60s -> 150s), même
   ratio conservé avec la piste "presse" (150:110 = 60:44).
   150s -> 75s (196e passe, piste scindée en 2 <ul> de largeur moitié
   chacun, voir le commentaire HTML/de .references__row) : la vitesse de
   ce défilement est (largeur parcourue)/(durée) - diviser la largeur par
   2 sans toucher la durée aurait donc AUSSI divisé la vitesse par 2 (un
   défilement 2x plus lent qu'avant, non demandé). Durée divisée par 2 à
   son tour pour compenser exactement et retrouver le même px/s qu'avant
   le scindage, donc la même vitesse "3/10" déjà réglée sur demande
   explicite. */
/* Animation CSS @keyframes remplacée par un tween GSAP (motion.js, voir
   "Bandeau Références") : signalé, "parfois il y a des flashs, une micro
   coupure" sur le bouclage `animation:infinite`, et nécessaire de toute
   façon pour la pause/reprise douce au survol (une reprise PROGRESSIVE
   de vitesse n'est pas possible en CSS pur, seul un vrai contrôle JS du
   temps de l'animation le permet). Sous reduceMotion, motion.js ne
   s'exécute pas du tout (garde en tête de fichier) : la piste reste
   simplement immobile, contenu toujours visible. */

/* .references__subtitle-row / -line / -subtitle : ne servent plus à rien
   dans Bloc 5 lui-même depuis sa refonte ci-dessus (ancien système de ligne
   de sous-titre tracée au scroll, remplacé par .references__frame), mais
   restent utilisées telles quelles par Bio ("Qui je suis", #bioSubtitleRow,
   voir plus bas) : conservées ici pour cette seule raison, inchangées, hors
   du périmètre de cette refonte. */

/* .references__subtitle-row / -line / -subtitle : ne servent plus à rien
   dans Bloc 5 lui-même depuis sa refonte ci-dessus (ancien système de ligne
   de sous-titre tracée au scroll, remplacé par .references__frame), mais
   restent utilisées telles quelles par Bio ("Qui je suis", #bioSubtitleRow,
   voir plus bas) : conservées ici pour cette seule raison, hors du
   périmètre de cette refonte - sauf la couleur de la ligne, repassée au
   doré (demandé, "mets la ligne animée du titre avec la couleur
   dorée"), seule utilisatrice restante de cette classe. */
.references__subtitle-row{
  position:relative;
  min-height:2.2rem;
  margin-bottom:clamp(2rem, 4vw, 2.8rem);
  width:100vw;
  margin-left:calc(50% - 50vw);
}
.references__subtitle{
  display:block;
  font-family:var(--sans); font-weight:500;
  color:var(--ink);
  text-transform:uppercase;
  letter-spacing:.08em;
  font-size:clamp(.85rem, 1.3vw, 1.05rem);
  line-height:1;
  will-change:opacity, transform;
}

/* ==========================================================================
   BLOC 6 : texte + image, bouclage, retour au fond sombre
   Miroir du Bloc 2 : mêmes proportions 61,8% image / 38,2% texte (nombre
   d'or, comme le Bloc 2, voir son commentaire), ordre inversé (Bloc 2 =
   image puis texte, à gauche ; Bloc 6 = texte puis image, image à
   droite). Texte collé directement contre l'image, sans espace entre
   les deux. Marge basse du fond #58656A rendue égale à la marge haute
   (padding-block sur la section elle-même, pas seulement sur la
   colonne de texte, pour que l'image en hérite aussi) ; marge gauche du
   texte augmentée (bord de fenêtre trop proche du texte).
   ========================================================================== */
/* padding-top réduit (au lieu du même clamp que le bas) : espace jugé
   trop grand entre .bio et .bloc6 (.bio__pin padding-bottom + #sep3 +
   ce padding-top s'additionnaient), réduit une 2e fois (encore jugé trop
   grand après la 80e passe). padding-bottom (vers la FAQ) inchangé. */
/* min-height alignée sur .bloc2 (80vh) : à 100vh, l'image (stretch sur
   toute la hauteur de la section, comme .bloc2__media) rendait
   nettement plus haute que celle du Bloc 2, incohérent maintenant que
   les deux blocs sont conçus comme miroirs l'un de l'autre (93e-94e
   passes). */
/* padding-block -> margin-block (demandé : l'image du Bloc 6 paraissait
   moins haute que celle du Bloc 2). Cause : box-sizing:border-box global,
   donc min-height:80vh inclut le padding : un padding-block ICI réduisait
   d'autant la hauteur de contenu réellement disponible pour les enfants
   flex (stretch), donc l'image (et le texte) du Bloc 6 se retrouvaient
   plus bas que 80vh, contrairement à .bloc2 (aucun padding sur la
   section, image à 80vh plein). Le margin, lui, s'ajoute EN DEHORS de la
   boîte (n'entame pas les 80vh de contenu) : mêmes 80vh pleins pour
   l'image des deux blocs, tout en gardant l'espacement haut/bas voulu
   autour de la section (identique visuellement, même couleur de fond
   uniforme #58656A des deux côtés). */
/* Contenu inversé avec l'ex-Bloc 4 (demandé, "inverse le contenu... pas
   les couleurs de fond") : affiche désormais "Le bon cadre, pour vous."
   + son sous-titre + la grille des 3 offres (l'ex-Bloc 4, "Une approche
   qui fait la différence", qui occupait cette place avant l'inversion,
   a depuis été entièrement retiré du site). Mise en page entièrement
   column/centrée (remplace l'ancien split 56/44 texte-à-gauche/image-à-
   droite, pensé pour un texte+une photo, pas pour un titre+une grille de
   3 cartes) : titre+sous-titre centrés en haut (.bloc6__text-col),
   grille pleine largeur en dessous (.offers__container/#offersGrid).
   margin-block:0 (était
   clamp(1rem,2vw,1.5rem) clamp(4.5rem,9vw,7rem), captures
   "Ecarts-Padding") : cet écart propre s'ajoutait à celui de #sep3/#sep5
   de part et d'autre, sans jamais l'égaler - .bloc6 ne fournit plus
   aucun espace lui-même côté séparateurs, padding-block gère l'espace
   interne haut/bas du nouveau contenu column. */
/* padding-top < padding-bottom (demandé, "remonte un peu tout le bloc
   car il n'a pas l'air centré verticalement") : le groupe (titre +
   corps + grille) est déjà géométriquement centré par
   justify-content:center sur une hauteur min-height symétrique - l'effet
   "pas centré" est optique, pas un vrai décalage (le "+" décoratif
   au-dessus du titre, .motif ci-dessus, est en position:absolute donc
   ne compte dans aucun calcul de centrage, mais ajoute du poids visuel
   côté haut sans équivalent côté bas). Un padding-top légèrement réduit
   (même formule que #sep3 plus haut dans ce fichier, déjà utilisée pour
   ce même type d'ajustement) compense en remontant tout le groupe d'un
   cran. */
/* Pin de la séquence d'entrée à 5 phases (redemandé, voir motion.js) :
   position:sticky sur #bloc6 lui-même (jamais ScrollTrigger.pin:true,
   convention du site - même formule que .bloc3-pin/#bloc3 plus haut),
   .bloc6-pin fournit le "rab" de scroll sur lequel la timeline GSAP
   scrubbe ses 5 phases. min-height:100vh sur l'ID l'emporte sur le 80vh
   de la classe .bloc6 juste en dessous (même mécanique déjà utilisée
   par #bloc3/.bloc--3).
   250vh -> 130vh (redemandé, "on doit scroller beaucoup ou très vite
   pour terminer l'animation") : réduit d'autant le scroll nécessaire
   pour boucler TOUTE la séquence (le mapping scrub reste 0->1 sur
   n'importe quelle hauteur donnée ici, quels que soient les timings
   relatifs internes de la timeline - c'est cette hauteur, pas la durée
   GSAP elle-même, qui détermine le scroll total requis). */
.bloc6-pin{
  position:relative;
  height:calc(100vh + 130vh);
}
#bloc6{
  position:sticky;
  top:0;
  min-height:100vh;
}
/* reduceMotion SEUL désormais (retiré de la media query mobile ci-dessous
   - redemandé, "repense la mécanique du bloc citation + offres pour
   mobile, plutôt que de désactiver l'animation") : sous reduceMotion,
   motion.js ne tourne jamais du tout (garde en tête de fichier) - pin
   désactivé, contenu final déjà visible par défaut, la citation
   (transitoire, sans place dans un layout statique) est retirée de
   l'affichage. Le mobile, lui, garde désormais SA PROPRE mécanique
   (voir .bloc6-pin/.offer-card plus bas, et initOffersSequence,
   motion.js) - un pin plus court, une fusion/séparation VERTICALE des
   cartes plutôt qu'horizontale. */
@media (prefers-reduced-motion:reduce){
  .bloc6-pin{height:auto;}
  #bloc6{position:static; min-height:0;}
  .offers__quote{display:none;}
  /* .offer-card : restaure le rendu final (bordure/coins arrondis) -
     motion.js ne tourne jamais ici, leur valeur de repos par défaut
     (transparent/0, voir .offer-card plus haut) resterait donc sinon
     affichée en permanence. */
  .offer-card{ --offer-border:var(--offer-border-rest); border-radius:var(--radius); }
}
.bloc6{
  position:relative;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  min-height:80vh;
  margin-block:0;
  padding-top:calc(var(--section-gap) - clamp(1rem, 2vw, 1.5rem));
  padding-bottom:var(--section-gap);
}
/* padding-inline:var(--gutter) (demandé, "laisse plus d'espace sur
   chaque côté") : était clamp(.75rem, 1.5vw, 1.25rem), un espace
   nettement plus étroit (12-20px) que le gutter standard du site
   (clamp(1.5rem, 5vw, 4rem), déjà utilisé par .offers__container juste
   en dessous, CSS - incohérent que le titre/corps au-dessus en aient
   moins que la grille sous eux).
   max-width 1400px -> 1600px (même valeur que .offers__container, pour
   rester alignés) : nécessaire pour "3 façons de travailler ensemble"
   en white-space:nowrap (voir .bloc6__title plus bas) - à la taille de
   police pleine (.section-title), ce titre mesure ~1409px, plus que
   1400px UNE FOIS le gutter agrandi ci-dessus déduit (box-sizing:
   border-box global : le padding mange sur le max-width, pas en plus). */
.bloc6__text-col{
  position:relative;
  width:100%; max-width:1600px;
  display:flex; flex-direction:column; align-items:center;
  gap:1.4rem;
  padding-inline:var(--gutter);
  text-align:center;
}
/* Bug corrigé (signalé, "plus d'espace entre #sep3 et le titre
   qu'entre le bas des cartes et #sep5") : .motif--inline (partagée
   avec .bloc2 etc., voir plus haut) est un élément DE FLUX (display:
   inline-block, margin-bottom) - sa propre hauteur s'ajoutait donc
   au-dessus du titre dans .bloc6__text-col, gonflant le groupe centré
   (justify-content:center sur .bloc6, voir plus bas) côté HAUT
   uniquement (rien d'équivalent sous les cartes) : le titre se
   retrouvait poussé plus bas que la vraie moitié de l'espace
   disponible, d'où un écart #sep3->titre visuellement plus grand que
   cartes->#sep5, alors que .bloc6.padding-block (haut/bas identiques)
   aurait dû les rendre égaux. Sorti du flux ICI SEULEMENT (scopé à
   .bloc6__text-col, la classe partagée .motif--inline n'est pas
   touchée, .bloc2 etc. continuent de l'utiliser en flux normal, qui
   n'a pas ce problème de symétrie haut/bas) : bottom:100% place son
   propre bord bas exactement au bord haut de .bloc6__text-col (donc
   entièrement AU-DESSUS, jamais superposé au titre), sans plus
   consommer de hauteur dans le calcul du groupe centré - les deux
   écarts redeviennent strictement égaux, comme pour les autres blocs
   de la page (demandé). */
.bloc6__text-col .motif{
  position:absolute;
  bottom:calc(100% + .6rem); left:50%;
  transform:translateX(-50%);
  margin-bottom:0;
}
/* font-size aligné sur .section-title (2.6-4.4rem, pas 2-3.1rem comme
   .bloc2__title/.bloc--4__title) : vérifié suite au signalement "le
   titre... a l'air plus petit que les autres titres" - confirmé,
   .bloc6__title reprenait par erreur la taille des titres "de contenu"
   pairés à un petit paragraphe (Bloc 2/Bloc 4), alors que ce titre fait
   fonctionnellement office d'en-tête de SECTION à part entière (comme
   "Ce que ça donne, concrètement.", "Références & Publications" ou
   "Questions fréquentes", tous en .section-title) - pas un titre de
   module secondaire. line-height repris à 1 (comme .section-title, au
   lieu de 1.04) pour la cohérence complète. */
/* white-space:nowrap (demandé, "doit être sur 1 ligne") : même
   traitement que .cta__title, qui utilise déjà exactement la même
   taille de police (voir passe précédente, alignée sur .section-title)
   et tient sur 1 ligne avec cette même propriété - .bloc6__text-col
   (1400px de large, plus que le --container de 1180px du CTA) laisse
   assez de place pour "3 façons de travailler ensemble" quel que soit
   le viewport où le CTA lui-même tient déjà. */
.bloc6__title{
  font-family:var(--display-semi); font-weight:600;
  color:var(--text-strong);
  text-transform:lowercase;
  /* Taille/police uniformisées sitewide (redemandé) : --fs-title - measuré
     en direct (Playwright), "3 façons de travailler ensemble" déborde
     largement (1785px) de son conteneur (1440px) à cette taille agrandie,
     provoquant un débordement horizontal de toute la page : white-space:
     nowrap retiré, même correction déjà appliquée à .portfolio-h__title
     pour exactement la même raison lors d'une passe précédente. */
  font-size:var(--fs-title);
  line-height:1.04;
  margin-inline:auto;
  max-width:22ch;
}
.bloc6__title::first-letter, .bloc6__title .reveal-line-mask:first-of-type .reveal-line::first-letter{text-transform:uppercase;}
/* color explicite #FFFFFF (pas var(--text), #E3E6E3 - légèrement moins
   blanc) : demandé, "texte en blanc (#FFFFFF)" pour ce bloc une fois
   qu'il héberge le contenu des Offres. */
.bloc6__body{
  font-family:var(--sans); font-weight:300;
  color:var(--text-strong);
  font-size:var(--fs-body);
  line-height:1.6;
  max-width:58ch;
  margin-inline:auto;
}

/* ==========================================================================
   MICRO-BIO : Qui je suis, FOND BLANC
   3 éléments (grand rectangle, petit rectangle/texte, photo) arrivent
   simultanément depuis 3 directions différentes au scroll (gauche / bas /
   droite, voir main.js, tickBio), pas via le système générique
   flou->net (qui ne gère qu'une montée verticale). Animation nettement
   ralentie (fenêtre elementProgress élargie, voir main.js, tickBio).
   Nouvelle palette : fond blanc, grand rectangle #58656A, petit
   rectangle #C8CFCD (couleur hors charte 4 couleurs, valeur corrigée
   cette passe par rapport à la précédente), texte #E3E6E3.
   ⚠️ Contraste texte/fond très insuffisant dans ce petit rectangle :
   #E3E6E3 sur #C8CFCD ≈ 1,26:1 (luminance relative WCAG), très loin du
   seuil AA (4,5:1), quasiment illisible, un problème déjà signalé pour
   la couleur précédente et qui persiste avec celle-ci. Implémenté tel
   quel car explicitement demandé (correction de teinte, pas de
   contraste), mais fortement déconseillé en l'état.
   ========================================================================== */
/* Épinglage (position:sticky) abandonné : la sortie de section n'était
   pas fluide (le scroll s'arrêtait nettement avant de reprendre).
   L'entrée (ligne du titre + arrivée de bio__frame) reste pilotée par
   bioPinProgress (voir main.js), basée sur elementProgress comme le
   reste du site : elle ne dépendait déjà pas de l'épinglage, donc
   continue de fonctionner à l'identique. */
.bio{background:var(--white);}
/* Bug corrigé (signalé, "réduit l'espace... le titre doit arriver juste
   en dessous") : le titre "Qui je suis" vivait jusqu'ici DANS
   .bio__pin, un bloc de 100svh (pleine hauteur d'écran) en
   display:flex;align-items:center - il se retrouvait donc centré
   verticalement sur TOUTE la hauteur de l'écran, avec bio__frame, très
   loin sous le bloc Références selon la hauteur d'écran, quel que soit
   le padding posé par ailleurs (un padding ne peut pas corriger un
   centrage vertical sur 100svh, la cause était structurelle). Le titre
   vit maintenant dans .bio__subtitle-wrap, un bloc à part, AVANT
   bio__pin, hors de ce centrage. .bio__pin (inchangé sinon) garde son
   centrage vertical sur 100svh pour bio__frame (carte + portrait) seul,
   qui n'a pas besoin de bouger. */
.bio__subtitle-wrap{
  background:var(--white);
  /* padding-top:var(--section-gap) (revenu à la valeur standard, nouvel
     ordre de page - demandé) : l'ancienne moitié réduite n'avait de sens
     que couplée au padding-bottom, lui aussi réduit, de .references
     (--ref-gap, retiré) quand ce bloc précédait directement Bio -
     Références vit désormais après le bloc Offres (voir style.css/
     .references), Bio est précédé directement par le bloc Avis, sans
     séparateur entre les deux (#sep9 supprimé, redemandé - les 2 blocs
     étant en fond blanc, plus rien à marquer ici). */
  padding-top:var(--section-gap);
}
/* Bug corrigé (signalé, "supprime l'espace entre le titre et le reste
   de la page... la bio (rectangle et photo) est trop loin du titre") :
   height:100svh (pleine hauteur d'écran) + align-items:center centrait
   bio__frame verticalement sur TOUTE cette hauteur, laissant un grand
   vide invisible (ni padding ni margin, de la place "en trop" dans la
   boîte) entre le haut de .bio__pin (juste après le titre) et le haut
   réel de bio__frame, dont la hauteur propre est nettement inférieure à
   100svh. Remplacé par une hauteur naturelle (retire height/display/
   align-items) : .bio__pin ne prend plus que la place dont bio__frame a
   réellement besoin, plus son padding-block ci-dessous - la mise en
   page se resserre donc AUTOMATIQUEMENT, tout ce qui suit (Bloc 6, FAQ,
   etc.) remonte d'autant, sans déplacer ce vide plus bas (demandé,
   "il ne faut pas que cet espace soit décalé vers le bas").
   Fond coupé en deux (moitié haute blanche, moitié basse #58656A) :
   reste à 50%/50% de la boîte, désormais property à la hauteur réelle
   du contenu plutôt qu'à la hauteur de l'écran - toujours cohérent avec
   bio__frame (aligné en bas de grille, voir .bio__frame) qui straddle
   ce point médian. Le bloc suivant (.bloc6) est déjà sur fond sombre
   par défaut (voir plus bas) : la moitié basse sombre d'ici se prolonge
   donc sans coupure visible une fois #sep3 (ligne dorée) passé. */
.bio__pin{
  padding-block:0 clamp(1rem, 2vw, 1.5rem);
  background:linear-gradient(to bottom, #FFFFFF 50%, var(--bg) 50%);
}
.bio__pin .container{width:100%;}
/* "Qui je suis" reprend le style et l'animation des sous-titres de Bloc 5
   ("Ils me font confiance" / "Publications & presse") : ligne fine qui se
   trace au scroll + texte qui apparaît, arrêtée pile à la même abscisse
   que ces deux sous-titres (mesurée au runtime sur le titre "Références
   & Publications", voir main.js, tickBioSubtitle, qui réutilise
   exactement le même calcul que tickSubtitleLines). Largeur de la ligne
   et margin-left du texte ne sont donc plus fixes en CSS : entièrement
   pilotées par JS, comme pour les deux autres sous-titres. */
/* Espace titre -> bio__frame réduit au minimum (signalé, "la bio... est
   trop loin du titre") : ce margin-bottom est désormais le SEUL levier
   de cet espace (bio__pin et bio__frame, plus bas, ne rajoutent plus
   chacun leur propre padding-top redondant par-dessus). */
/* Ligne dorée retirée entièrement (redemandé, "enlève la ligne dorée,
   garde uniquement 'Qui je suis'") : #bioSubtitleLine supprimée du HTML.
   Le titre reste positionné au-dessus du bloc texte (#bioCard, fond
   #C8CFCD), aligné sur son bord gauche - voir .bio__subtitle-row plus
   bas pour comment cet alignement est obtenu. */
.bio__subtitle-wrap .container{width:100%;}
/* width:max-content + margin-left calculé abandonné (bug réel trouvé,
   nouveau texte du titre bien plus long - "Qui je suis, et pourquoi mon
   approche fait la différence.") : max-content laissait le texte
   déborder du viewport au lieu de passer à la ligne, la formule de
   centrage n'ayant plus de sens pour un titre de plusieurs lignes.
   Repris sur le MÊME principe que .bio__frame juste en dessous
   (max-width + margin-inline:auto) : les deux partagent maintenant
   littéralement la même valeur de max-width, garantissant un bord
   gauche identique quelle que soit la longueur du texte, tout en
   permettant un retour à la ligne normal. */
.bio__subtitle-row{
  width:100%;
  max-width:1120px;
  margin-inline:auto;
  /* Quasiment collé au bloc texte (redemandé) : marge minime, juste assez
     pour ne pas toucher visuellement le bloc. */
  margin-bottom:0.4rem;
}
/* max-width 1000px -> 1120px + colonnes 1fr/1fr -> 1.35fr/1fr (redemandé,
   "élargis le container de texte" pour le nouveau texte plus long, 3
   paragraphes au lieu d'1) : élargit spécifiquement la colonne texte
   (.bio__card, ~+30% de largeur utile) plutôt que les deux colonnes à
   égalité, pour limiter la croissance en hauteur du texte sans réduire
   le portrait à l'excès. Padding de .bio__card (juste en dessous)
   inchangé, comme demandé. */
.bio__frame{
  position:relative;
  display:grid; grid-template-columns:1.35fr 1fr;
  align-items:end;
  gap:clamp(1.5rem, 3vw, 2.5rem);
  max-width:1120px; margin-inline:auto;
}
/* display:flex column + gap (nouveau) : le texte est passé de 1 à 3
   paragraphes (<p class="bio__text"> répétée, voir HTML) - sans cet
   espacement entre eux, ils se suivraient collés sans aucune séparation
   visuelle (aucune marge propre sur .bio__text par ailleurs). */
.bio__card{
  position:relative; z-index:2;
  /* #C8CFCD -> #e3e6e3 (demandé). */
  background:#e3e6e3;
  border-radius:var(--radius);
  padding:clamp(2.2rem, 4.5vw, 3.2rem);
  display:flex; flex-direction:column; gap:1.1em;
  align-self:center;
  will-change:transform, opacity;
}
/* Couleur corrigée (redemandé, règle sitewide "fond blanc/clair -> titre
   ET texte en #58656A") - remplace le blanc pur précédent, dont le
   contraste sur ce fond clair #C8CFCD était déjà signalé comme
   insuffisant dans cette même règle avant cette passe. Taille/police
   uniformisées avec le reste du site (--fs-body). */
.bio__text{
  font-family:var(--sans); font-weight:300;
  /* var(--fs-body) -> var(--fs-caption) (demandé, "même taille que les
     textes de cartes d'avis") : même taille/interligne que
     .testimonial-card__text, voir son commentaire. */
  font-size:var(--fs-caption);
  line-height:var(--lh-caption);
  color:#58656A;
  text-align:left;
}
.bio__portrait{
  position:relative; z-index:1;
  aspect-ratio:3/4;
  overflow:hidden;
  border-radius:var(--radius);
  will-change:transform, opacity;
}
/* object-position 50% 15% (0% -> 15%, redemandé, "remonte légèrement
   la position de la photo dans le cadre") : par défaut (50% 50%,
   centré), le cadre (3/4 desktop, 16/9 mobile - voir plus bas, très
   différent des proportions natives du portrait, 709x900) rognait le
   haut et le bas de façon égale autour du centre géométrique de la
   photo, pas du sujet - le visage, situé dans le tiers HAUT de la
   photo source (vérifié), en pâtissait. "Remonter la photo dans son
   cadre" = faire glisser l'image vers le HAUT derrière une fenêtre
   fixe (sens inverse du "descendre" du 1er correctif, poussé lui à
   0%, l'extrême opposé) - révèle légèrement plus sa partie BASSE (un
   peu de buste en plus) et rogne un peu plus le tout petit espace vide
   au-dessus de la tête que 0% laissait. Seul le cadre 16/9 (mobile) est
   concerné en pratique : le cadre 3/4 (desktop) rogne déjà uniquement
   sur les côtés, jamais en hauteur (photo source proportionnellement
   plus large que ce cadre), donc object-position vertical n'y change
   rien. */
.bio__portrait img{width:100%; height:100%; object-fit:cover; object-position:50% 15%;}

/* Breakpoint harmonisé sur 860px (audit demandé). */
@media (max-width:860px){
  .bio__frame{grid-template-columns:1fr; align-items:stretch;}
  /* Rectangle réduit (signalé, "réduire le rectangle photo du bloc
     Bio") : 4/3 (mesuré 342×256px sur 390px de large) prenait une
     bonne partie de l'écran avant même le texte. 16/9 (342×192px) le
     réduit sensiblement en hauteur en gardant la même largeur. */
  .bio__portrait{order:-1; aspect-ratio:16/9;}
  /* .bio__frame passe en 1 colonne pleine largeur ci-dessus (carte et
     photo empilées) : "Qui je suis" reprend donc, lui aussi, l'abscisse
     gauche pleine largeur plutôt que celle calculée pour le desktop
     (2 colonnes). */
  .bio__subtitle-row{margin-left:0;}
}

/* ==========================================================================
   FAQ
   ========================================================================== */
/* padding-block:0 (padding-bottom repassé à 0, était var(--separator-zone)
   - demandé, "ajoute un séparateur au-dessus et en dessous du bloc des
   avis, aux mêmes réglages que les autres blocs") : le padding-bottom
   compensait l'ABSENCE d'un vrai séparateur entre le FAQ et .faq-break
   (voir #sep6, HTML, désormais ajouté juste après ce bloc) en
   reproduisant artificiellement la place qu'il aurait occupée - devenu
   redondant (doublerait l'écart) maintenant qu'un vrai séparateur existe
   à cet endroit, avec son propre margin-block:var(--section-gap)
   (.separator, plus haut) qui suffit seul, comme pour #sep1/#sep2/#sep3/
   #sep5. */
/* Fond blanc (demandé) : .faq vivait jusqu'ici sur le même fond sombre
   --bg que le reste du site (hérité de body, aucune déclaration propre
   ici) - #sep7 (FAQ -> CTA) ajusté en conséquence (voir son commentaire,
   plus haut). #sep6 (Références -> FAQ), qui avait lui aussi été ajusté
   à l'époque, a depuis été supprimé à son tour (redemandé). */
.faq{padding-block:0; background:var(--white);}
/* color:var(--ink) (demandé, "le texte en #58656A") : .section-title
   est pensé par défaut pour un fond sombre (voir son commentaire) - même
   override que .references pour la même raison (fond blanc). */
.faq__title{margin-bottom:clamp(var(--space-2), 5vw, var(--space-3)); color:var(--ink);}
.faq__list{max-width:840px; margin-inline:auto;}
/* var(--card-border) (rgba blanc, pensée pour un fond sombre) ->
   rgba(88,101,106,.16) (demandé, fond blanc) : même valeur/même
   principe que --offer-border-rest (.offer-card, plus haut), une carte
   blanche sur fond sombre - ici l'inverse (fond blanc), la bordure doit
   donc être sombre pour rester visible. */
.faq__item{border-bottom:1px solid rgba(88,101,106,.16);}
.faq__item:first-child{border-top:1px solid rgba(88,101,106,.16);}
.faq__question{
  width:100%;
  /* justify-content:space-between -> flex-start (demandé, "passer les +
     de chaque question à gauche") : .faq__icon (déjà en 2e position dans
     le HTML) passe en order:-1 pour s'afficher AVANT le texte sans
     changer l'ordre du DOM (aria-hidden, sans incidence pour un lecteur
     d'écran) - space-between aurait alors poussé le texte tout à droite
     au lieu de le garder collé à l'icône. */
  display:flex; align-items:center; justify-content:flex-start; gap:1.4rem;
  padding-block:clamp(1.5rem, 3vw, 2rem);
  text-align:left;
  /* Mark OT Light (demandé) : var(--sans), déjà "Mark OT" (voir tête de
     fichier), avec le poids 300 (fichier MarkOT-Light.woff2, déjà chargé
     - voir @font-face) - remplace Montserrat Bold. */
  font-family:var(--sans); font-weight:300;
  font-size:clamp(1.25rem, 2.1vw, 1.7rem);
  color:var(--ink);
}
.faq__icon{
  position:relative; flex:none;
  order:-1;
  width:22px; height:22px;
}
.faq__icon::before, .faq__icon::after{
  content:""; position:absolute; top:50%; left:50%;
  background:var(--gold);
  transition:transform var(--dur-hover) var(--ease);
}
.faq__icon::before{width:16px; height:1.5px; transform:translate(-50%,-50%);}
.faq__icon::after{width:1.5px; height:16px; transform:translate(-50%,-50%);}
.faq__question[aria-expanded="true"] .faq__icon::after{transform:translate(-50%,-50%) rotate(90deg) scaleY(0);}
.faq__question[aria-expanded="true"] .faq__icon::before{transform:translate(-50%,-50%) rotate(180deg);}

.faq__answer{
  display:grid;
  grid-template-rows:0fr;
  transition:grid-template-rows var(--dur-m) var(--ease);
}
.faq__question[aria-expanded="true"] + .faq__answer{grid-template-rows:1fr;}
.faq__answer-inner{overflow:hidden;}
.faq__answer p, .faq__pricing{
  padding-bottom:clamp(1.4rem, 3vw, 1.9rem);
  font-family:var(--sans); font-weight:400;
  /* Harmonisé sitewide (redemandé) : taille 1 (paragraphe de bloc,
     comme Moment/Recadrage/Une approche/Bio/CTA), voir --fs-body -
     était en dur (1.08rem), quasi identique au plancher de --fs-body
     mais pas la même variable. color:var(--ink) (demandé, "le texte en
     #58656A") : var(--text-strong), pensée pour le fond sombre --bg,
     n'a plus le contraste voulu sur le fond blanc de .faq. */
  font-size:var(--fs-body); line-height:1.68; color:var(--ink);
  max-width:64ch;
}
.faq__pricing li{margin-bottom:.6rem;}
.faq__pricing strong{color:var(--ink); font-weight:600;}

/* ==========================================================================
   AVIS CLIENTS : bandeau de cartes en défilement continu (redemandé,
   "remplace le système actuel du bloc Preuve/avis par un bandeau
   défilant de petites cartes d'avis, en reprenant le même mécanisme
   technique déjà utilisé pour le bandeau de logos Références &
   Publications") - remplace intégralement l'ancien .faq-break (photo de
   fond plein écran par avis, rideau, minuteur JS) + .proof (carousel
   autoplay un avis à la fois), supprimés (demandé explicitement,
   "supprime l'ancien système... qui n'est plus nécessaire").
   Mécanisme IDENTIQUE à .references__track (voir plus haut, aucune
   différence de principe) : la liste de cartes est dupliquée UNE FOIS
   dans .testimonials__track (2e moitié aria-hidden), width:max-content +
   flex-shrink:0, puis animation linéaire translateX(0) -> translateX(-50%)
   en boucle infinie - AUCUN JS, le défilement continu est piloté à 100%
   par le compositeur CSS. Couvert automatiquement par la règle globale
   prefers-reduced-motion (tout en haut du fichier, animation-duration
   forcée à .01ms) : pas de media query dédiée nécessaire ici, comme pour
   .references__track.
   Cartes : var(--card)/var(--card-border) (déjà le token du site pour un
   panneau discret posé sur fond sombre - .cta-card/.steps__item),
   border-radius:0 (convention du site, jamais de coin arrondi sur un
   composant carte - contrairement à la capture de référence, qui n'est
   qu'une inspiration de mise en page : ses avatars multicolores ne sont
   PAS repris, un seul avatar gold/fond sombre cohérent avec l'accent
   unique déjà en place partout ailleurs sur le site).
   ========================================================================== */
/* ==========================================================================
   AVIS CLIENTS : déplacé après Portfolio (demandé, nouvel ordre de page) -
   reprend la structure visuelle du bloc Références & Publications
   (.references__wrap/.references__frame/hline/hline--r/plus/
   hline-bottom, voir plus bas) sous des classes .testimonials__* dédiées
   (mêmes règles, section différente - convention du site, jamais de
   classe partagée entre 2 sections, voir par ex. .chrono2__kicker/
   .moment__kicker/.bloc2__kicker, déjà séparées malgré un style quasi
   identique). Fond passé au blanc + grand titre retiré (redemandé) : le
   texte du titre devient une étiquette "eyebrow" posée sur la ligne du
   haut (.testimonials__eyebrow plus bas), .testimonials__wrap ne contient
   donc plus que .testimonials__frame. --test-plus-size/--test-plus-inset/
   --test-line-gap : mêmes valeurs que --ref-plus-size/--ref-plus-inset/
   --ref-line-gap, voir leurs commentaires respectifs pour le détail
   (rayon de sécurité de rotation du "+", distance ligne-du-haut ->
   bandeau). */
.testimonials{
  --test-plus-size:16px;
  --test-plus-inset:13px;
  --test-line-gap:clamp(2.5rem, 5vw, 3.5rem);
  background:var(--white);
  /* padding-top doublé (corrigé - un 1er essai avait ajouté cet espace
     entre la ligne du haut et les CARTES, "je voulais que l'espace soit
     entre la ligne du haut et le bloc précédent, avec la frontière de
     fond") : c'est ICI, avant même .testimonials__frame/la ligne du
     haut, que l'espace supplémentaire doit vivre - entre la fin du bloc
     Portfolio (sa propre frontière de fond blanc/#e3e6e3) et la ligne du
     haut d'Avis. Même principe que #portfolio plus haut dans ce fichier
     ("même espace qu'entre un séparateur et son titre, même si le
     séparateur n'est plus là") : #sep8 (Portfolio -> Avis) a été retiré
     dans une passe précédente, cet espace compense son absence.
     padding-bottom passe à 0 (au lieu de var(--section-gap)) : le bloc
     Bio qui suit ne doit pas se retrouver décalé plus bas (demandé,
     "resserrer l'ensemble... pour qu'ils restent visuellement proches,
     dans un même souffle de scroll") - la hauteur totale ajoutée en haut
     est exactement retirée en bas de la section, la position de Bio
     dans la page ne bouge donc pas. */
  padding-top:calc(var(--section-gap) * 2);
  padding-bottom:0;
  overflow:hidden;
}
.testimonials__wrap{max-width:1680px; margin-inline:auto; padding-inline:var(--gutter);}
.testimonials__frame{
  position:relative;
  width:100vw; margin-left:calc(50% - 50vw);
  padding-top:calc(var(--test-line-gap) + var(--test-plus-inset));
  padding-bottom:var(--test-line-gap);
  overflow:hidden;
}
/* .testimonials__eyebrow (redemandé, remplace le grand titre centré) :
   posée sur .testimonials__hline, alignée à gauche sur le bord du
   "tronc" central de la page - .testimonials__frame est plein cadre
   (100vw, voir ci-dessus) donc son propre left:0 correspond au bord du
   VIEWPORT, pas au bord du contenu : l'étiquette recalcule cet écart en
   CSS pur avec le même calc() que .testimonials__wrap (max-width:1680px,
   centré + padding-inline:var(--gutter)), pas besoin de mesure JS ici
   (contrairement à positionColorBoundarySeparators/
   positionPortfolioColorBoundary, main.js, qui mesurent des positions
   sans formule reproductible en CSS). Fond var(--white) opaque + un peu
   de padding latéral : "creuse" la ligne en la recouvrant localement, sur
   le même principe visuel que .testimonials__plus qui la creuse en son
   centre (mais ici par recouvrement plutôt que par un vrai trou dans la
   ligne, la largeur du texte n'étant pas fixe). */
.testimonials__eyebrow{
  position:absolute;
  top:var(--test-plus-inset);
  left:calc(max(0px, (100vw - 1680px) / 2) + var(--gutter));
  transform:translateY(-50%);
  z-index:1;
  background:var(--white);
  padding-inline:1rem;
  font-family:var(--sans); font-weight:300;
  text-transform:uppercase;
  letter-spacing:.04em;
  color:var(--ink);
  font-size:var(--fs-caption); line-height:var(--lh-caption);
  white-space:nowrap;
}
.testimonials__hline{
  position:absolute; top:var(--test-plus-inset);
  left:0; width:calc(50% - var(--test-plus-inset));
  height:1px;
  background:rgba(214,183,125,.85);
}
.testimonials__hline--r{
  position:absolute; top:var(--test-plus-inset);
  left:calc(50% + var(--test-plus-inset)); right:0;
  height:1px;
  background:rgba(214,183,125,.85);
}
.testimonials__hline-bottom{
  position:absolute; left:0; right:0; bottom:0;
  height:1px;
  background:rgba(214,183,125,.85);
}
.testimonials__plus{
  position:absolute;
  top:var(--test-plus-inset); left:50%;
  width:var(--test-plus-size); height:var(--test-plus-size);
  margin-top:calc(var(--test-plus-size) / -2); margin-left:calc(var(--test-plus-size) / -2);
  /* @keyframes referencesPlusSpin réutilisée telle quelle (mécanique
     générique, sans dépendance à la section - voir son commentaire
     plus haut) : pas besoin d'une 2e déclaration @keyframes identique. */
  animation:referencesPlusSpin 7s linear infinite;
}
.testimonials__plus::before,
.testimonials__plus::after{
  content:"";
  position:absolute;
  background:var(--gold);
}
.testimonials__plus::before{
  top:0; bottom:0; left:50%;
  width:1.5px;
  margin-left:-.75px;
}
.testimonials__plus::after{
  left:0; right:0; top:50%;
  height:1.5px;
  margin-top:-.75px;
}
.testimonials__row{
  display:flex; align-items:flex-start;
}
.testimonials__track{
  /* align-items:stretch (redemandé, "taille (hauteur) toutes identiques,
     prends la plus grande comme référence") : chaque carte, en flex-item
     d'une même rangée non wrappée, s'étire automatiquement à la hauteur
     de la plus haute d'entre elles - pas de hauteur fixe à deviner/
     recalculer si un avis change de longueur plus tard. */
  display:flex; align-items:stretch;
  flex-shrink:0;
  width:max-content;
  will-change:transform;
  pointer-events:none;
  /* Animation CSS @keyframes retirée (signalé, "les cartes des avis ne
     s'arrêtent pas au passage de la souris") : remplacée par un tween
     GSAP dans motion.js, même mécanisme et même raison que
     .references__track juste au-dessus (pause/reprise douce
     impossible en CSS pur) - durée 55s conservée (plus lent que les 75s/
     logos - moins d'items, mais du texte à lire au passage plutôt qu'un
     simple pictogramme, donc un rythme plus posé). */
}
.testimonial-card{
  flex:0 0 auto;
  width:clamp(260px, 24vw, 320px);
  margin-inline:clamp(.6rem, 1.1vw, .9rem);
  padding:clamp(1.5rem, 2.4vw, 1.9rem);
  /* Fond blanc/texte foncé/bordure dorée (redemandé) : remplace le
     panneau translucide --card/--card-border pensé pour un fond sombre -
     même logique que .offer-card (carte blanche posée sur le fond sombre
     de sa section), pas une nouvelle convention. */
  background:var(--white);
  border:1px solid var(--gold);
  border-radius:var(--radius);
  /* Ombre ajoutée (non demandé, correction proactive) : carte blanche sur
     fond de section désormais blanc lui aussi (voir .testimonials
     plus haut) - sans elle, seule la fine bordure dorée distinguait
     encore la carte de son fond, même défaut déjà rencontré et corrigé
     pour .faq__item/.offers__cta plus tôt sur ce site. */
  box-shadow:0 20px 45px -32px rgba(88,101,106,.35);
  display:flex; flex-direction:column; gap:.85rem;
}
.testimonial-card__head{
  display:flex; align-items:center; gap:.75rem;
}
.testimonial-card__avatar{
  flex:0 0 auto;
  width:2.3rem; height:2.3rem;
  border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:var(--gold);
  color:var(--bg);
  font-family:var(--sans); font-weight:600;
  font-size:.95rem;
}
.testimonial-card__name{
  font-family:var(--sans); font-weight:500;
  /* Texte foncé (redemandé) : var(--text-strong)/var(--text) étaient
     pensés pour du texte clair sur fond sombre - var(--ink), déjà la
     couleur "texte sur fond blanc" utilisée ailleurs sur ce site (ex.
     .references__wrap .section-title). */
  color:var(--ink);
  font-size:.92rem; line-height:1.3;
}
.testimonial-card__stars{
  color:var(--gold);
  font-size:.8rem;
  letter-spacing:.2em;
}
.testimonial-card__text{
  font-family:var(--sans); font-weight:300;
  color:var(--ink);
  /* Harmonisé sitewide (redemandé) : taille 2, voir --fs-caption. */
  font-size:var(--fs-caption); line-height:var(--lh-caption);
}
@media (max-width:860px){
  .testimonial-card{width:clamp(220px, 72vw, 280px);}
}

/* ==========================================================================
   CTA
   ========================================================================== */
/* padding-top:0 (était var(--separator-zone)) : même raison que le
   padding-bottom de .faq (voir son commentaire) - compensait l'absence
   d'un vrai séparateur entre .faq-break et ce bloc, désormais fourni par
   #sep7 (HTML, voir son propre commentaire, style.css, pour son
   margin/padding) - redondant sinon. padding-bottom (-> footer)
   inchangé, hors sujet ici. */
.cta{position:relative; padding-block:0 clamp(5.5rem, 13vw, 10rem);}
/* .cta__layout (nouveau, redemandé "le texte à gauche et le formulaire à
   droite au lieu de l'un au-dessus de l'autre") : même recette que
   .moment__wrap ("même schéma que le bloc texte à gauche/image à
   droite", redemandé - .moment est le seul bloc du site dans cet ordre
   précis, texte AVANT image ; .bloc2__wrap fait l'inverse, image avant
   texte) - grid 3fr/2fr (align-items:start plutôt que :center comme
   .moment__wrap : le titre du formulaire doit démarrer au même niveau
   que "Parlons de vos images", pas être centré verticalement face à un
   texte bien plus haut que lui), colonne unique sous 860px (voir media
   query plus bas), même breakpoint que le reste du site. max-width
   (juste en dessous) : repris de .moment__wrap/.bloc2__wrap (redemandé,
   "avec la même largeur") - remplace le max-width plus étroit hérité de
   .container (1180px, pensé pour du texte seul centré) par celui, plus
   large, des blocs image+texte du site. Le texte (3fr, 2 paragraphes)
   garde la part la plus généreuse, le formulaire (2fr) la plus étroite
   des 2 (demandé explicitement lors de la 1ère passe, "si tu dois
   réduire la largeur de l'un des 2, réduis celle du formulaire en
   priorité" - non remis en cause par cette passe-ci, qui ne portait que
   sur la largeur totale et le schéma, pas sur ce ratio). */
.cta__layout{
  max-width:1680px;
  margin-inline:auto;
  display:grid;
  grid-template-columns:3fr 2fr;
  align-items:start;
  gap:clamp(2rem, 4vw, 3.5rem);
}
.cta__text-col{
  /* Alignement à gauche (remplace le centrage d'avant, pensé pour une
     colonne seule pleine largeur) : même convention que .bloc2__text-col/
     .bloc--4__text-col à côté d'un élément voisin, sur ce site. */
  text-align:left;
  /* min-width:0 (bug réel trouvé en vérifiant le rendu, pas visible dans
     le code seul) : par défaut, une piste de grille en fr respecte le
     min-content de son contenu avant de suivre le ratio 3fr/2fr demandé
     - .cta-card--form (2fr, voir plus bas) contient des champs de
     formulaire dont le min-content dépasse sa part "légitime", ce qui
     forçait la grille à LUI donner plus de place qu'au texte (3fr),
     inversant le rapport voulu ("réduis le formulaire en priorité").
     min-width:0 sur les 2 pistes (celle-ci + .cta-card--form) neutralise
     cette contrainte implicite, le ratio 3fr/2fr s'applique enfin tel
     quel. */
  min-width:0;
}
.cta__title{
  font-family:var(--display); font-weight:600;
  color:var(--text-strong);
  text-transform:lowercase;
  /* Taille/police uniformisées sitewide (redemandé) : --fs-title. max-
     width/margin-inline:auto retirés (pensés pour un titre centré seul
     sur toute la largeur de .container ; la largeur est désormais celle,
     déjà réduite, de .cta__text-col - voir .cta__layout) - white-space:
     nowrap déjà retiré avant ça (mesuré, "Parlons de vos images."
     déborde de son conteneur à cette taille agrandie), même correction
     que .bloc6__title juste au-dessus, pour la même raison. */
  font-size:var(--fs-title);
  line-height:1.04;
}
.cta__title::first-letter, .cta__title .reveal-line-mask:first-of-type .reveal-line::first-letter{text-transform:uppercase;}
.cta__intro{
  margin:1.8rem 0 0;
  max-width:100%;
  font-family:var(--sans);
  font-weight:300;
  font-size:var(--fs-body);
  line-height:1.6;
  color:var(--text-strong);
}

.cta__intro span{
  display:block;
}

.cta__intro span + span{
  margin-top:1.4rem;
}
@media (max-width:860px){
  /* Repasse en 1 colonne (breakpoint harmonisé sur 860px, comme le reste
     du site) : texte au-dessus, formulaire en dessous - même ordre que
     le HTML (voir .cta__layout, texte avant formulaire), pas d'inversion
     à gérer. */
  .cta__layout{grid-template-columns:1fr;}
  /* padding-inline restauré (perdu avec .container une fois passé en
     grid, dont le padding-inline reste inchangé - cette règle ne
     concerne QUE le texte, plus large que son gutter naturel une fois
     empilé pleine largeur ; inutile en 2 colonnes, où .cta__text-col est
     déjà bien plus étroit que .container). */
  .cta__intro{padding-inline:0;}
}
/* .cta__grid (ancienne grille à 2 colonnes avec le cadre "Décrire mon
   projet") supprimée (demandé, "enlever le cadre 'Décrire mon projet' et
   ne garder que le formulaire qui prend la place des 2 cadres") - sans
   rapport avec .cta__layout (plus haut), qui organise désormais texte et
   formulaire côte à côte. */
.cta-card--form{
  /* margin-top/auto retirés (pensés pour un formulaire seul, centré,
     SOUS le texte) : .cta-card--form est désormais une colonne de grille
     à côté du texte (voir .cta__layout plus haut), le "auto" horizontal
     n'a plus de sens et le margin-top est repris sous 860px seulement
     (texte et formulaire s'empilent de nouveau à ce breakpoint, voir
     media query plus bas). max-width:100% inchangé : remplit toute sa
     colonne de grille (2fr), au lieu de tout .container comme avant. */
  margin:0;
  max-width:100%;
  min-width:0; /* voir commentaire détaillé sur .cta__text-col plus haut */
}
@media (max-width:860px){
  .cta-card--form{margin-top:clamp(3rem, 6vw, 4.5rem);}
}
/* Cadre repris de l'animation du loader (×convergent + bordure qui se
   dessine, voir .loader__x/.loader__edge), demandé explicitement pour ces
   2 cartes : les "×" convergent vers les 4 coins, puis un cadre se
   dessine (scaleX/Y), pilotés au scroll (main.js, tickCtaCards,
   elementProgress sur les mêmes data-start/data-end que le fondu du
   contenu, déjà présents ici). Angles POINTUS (pas de border-radius,
   contrairement à l'ancien cadre arrondi 14px) : demandé explicitement.
   Ici le cadre reste une simple bordure fine à l'arrivée (pas de forme
   remplie ni d'expansion plein écran comme la fin de l'animation du
   loader, non reprise). --edge-scale : état de départ (non révélé) par
   défaut, piloté par JS à chaque frame ; état final statique si JS ne
   tourne pas (prefers-reduced-motion, voir plus bas). */
.cta-card{
  /* --mark-off/--mark-opacity retirés (redemandé, "enlève les × des
     bords façon loader") avec .cta-card__mark et ses 4 variantes plus
     bas. --edge-opacity retiré à son tour (redemandé, "l'animation doit
     remplir toute la bordure, même les coins arrondis") : les 4 barres
     droites (une par côté) ne pouvaient pas, par construction, couvrir
     un arc de cercle, laissant les 4 coins nus - remplacées par un seul
     cadre bordé + masque conique (voir .cta-card__frame plus bas), pour
     lequel --edge-scale seul suffit (à 0, le masque ne révèle déjà
     rien). */
  --edge-scale:0;
  position:relative;
  background:var(--card);
  border-radius:var(--radius);
  padding:clamp(2.2rem, 4.5vw, 2.8rem);
  display:flex; flex-direction:column; gap:1rem;
  transition:transform var(--dur-hover) var(--ease), background var(--dur-hover) var(--ease);
}
.cta-card:hover, .cta-card:focus-visible{
  transform:translateY(-8px);
  background:rgba(255,255,255,.08);
}
/* Cadre à coins arrondis (redemandé, "les coins n'ont pas de bordure,
   l'animation doit remplir toute la bordure, mêmes les coins arrondis") :
   les 4 barres droites précédentes (une par côté) laissaient les 4
   coins totalement nus, quel que soit leur inset - une barre droite ne
   peut pas, par construction, couvrir un arc de cercle. Remplacées par
   UN SEUL élément avec un vrai border+border-radius (même --radius que
   le fond de la carte, donc un contour parfaitement superposé, coins
   compris) - "dessiné" progressivement via un masque conique
   (conic-gradient) plutôt que 4 scaleX/Y indépendants : le masque
   balaie tout le pourtour à 360° (calc(var(--edge-scale) * 1turn)),
   révélant le contour - lignes ET coins arrondis - au fur et à mesure,
   sans jamais laisser de coin vide. --edge-opacity n'est plus
   nécessaire (à edge-scale:0, le masque ne révèle rien du tout, pas
   besoin d'un opacity:0 séparé pour éviter un résidu). */
.cta-card__frame{
  position:absolute; inset:0;
  border:1px solid var(--gold);
  border-radius:var(--radius);
  pointer-events:none;
  -webkit-mask-image:conic-gradient(from 0deg, #000 calc(var(--edge-scale) * 1turn), transparent calc(var(--edge-scale) * 1turn));
  mask-image:conic-gradient(from 0deg, #000 calc(var(--edge-scale) * 1turn), transparent calc(var(--edge-scale) * 1turn));
}
/* .cta-card__icon (petit logo rond "◐") supprimée (demandé, "enlever le
   petit logo rond du formulaire") - seul usage sur le site. */
.cta-card__title{
  font-family:var(--display); font-weight:600;
  color:var(--text-strong);
  font-size:clamp(1.5rem, 2.6vw, 1.95rem);
  text-transform:lowercase;
}
.cta-card__title::first-letter, .cta-card__title .reveal-line-mask:first-of-type .reveal-line::first-letter{text-transform:uppercase;}
/* .cta-card__text/.cta-card__arrow supprimées (mort code - demandé,
   "enlever le cadre 'Décrire mon projet'" : c'était le seul autre
   usager de .cta-card__text, et le seul usager de .cta-card__arrow -
   la carte formulaire restante n'utilise ni l'un ni l'autre, son propre
   texte descriptif étant devenu le sous-titre .cta__intro). */
/* .cta-card--form (nouveau, demandé - remplace l'ancien lien mailto
   "Prendre rendez-vous" par un vrai formulaire, Formspree) : le
   soulèvement au survol (.cta-card:hover, plus haut) a du sens pour une
   carte-lien entière cliquable - plus pour une carte contenant des
   champs de saisie, où on veut au contraire que tout reste stable
   pendant qu'on remplit le formulaire. :focus-within (pas seulement
   :hover) : un champ qui garde le focus (ex. en tapant un message long)
   doit lui aussi empêcher tout mouvement, pas seulement le survol
   souris. */
.cta-card--form:hover, .cta-card--form:focus-within{
  transform:none;
  background:var(--card);
}
.cta-form{
  margin-top:.4rem;
  display:flex; flex-direction:column; gap:1.1rem;
}
/* .cta-form__row (nouveau, demandé) : regroupe Nom+Prénom, puis
   Mail+Téléphone, côte à côte sur une même ligne - flex:1 1 0 sur
   chaque .cta-form__field (voir plus bas) partage l'espace à parts
   égales, quelle que soit la longueur du contenu déjà tapé. Empilé en
   colonne sous 860px (seuil standard du site, voir la media query plus
   bas) : 2 champs cote à côte deviendraient trop étroits sur mobile. */
.cta-form__row{
  display:flex; gap:1.1rem;
}
/* min-width:0 (bug réel trouvé au rendu - "les champs dépassent du
   cadre") : flex:1 1 0 seul ne suffit pas à faire partager l'espace à
   parts égales - un item flex refuse par défaut de rétrécir sous le
   min-content de son contenu (même mécanisme que le bug de grille
   corrigé sur .cta__layout plus haut), et un <input> sans width déclarée
   a un min-content proche de sa largeur intrinsèque (~20 caractères) :
   largement plus que la moitié de la carte une fois celle-ci réduite à
   sa colonne de grille (2fr, voir .cta__layout). min-width:0 neutralise
   cette contrainte, width:100% sur l'input/textarea (voir plus bas) fait
   le reste. */
.cta-form__row .cta-form__field{flex:1 1 0; min-width:0;}
.cta-form__field{
  display:flex; flex-direction:column; gap:.45rem;
}
/* Labels en MarkOT (demandé, "stylé selon la charte du site - MarkOT
   pour les labels") : var(--sans) pointe déjà sur "Mark OT" (voir
   :root) - majuscules + letter-spacing, même vocabulaire typographique
   que les autres micro-libellés du site (ex. .offer-card__detail,
   .testimonials__eyebrow). */
.cta-form__field label{
  font-family:var(--sans); font-weight:300;
  text-transform:uppercase; letter-spacing:.05em;
  font-size:.78rem;
  color:var(--text);
}
.cta-form__field input,
.cta-form__field textarea{
  /* width:100% (bug réel trouvé au rendu, voir commentaire sur
     .cta-form__row .cta-form__field plus haut) : sans elle, un <input>
     garde sa largeur intrinsèque au lieu de remplir son champ - c'est
     cette largeur intrinsèque, jamais contrainte, qui débordait du
     cadre. */
  width:100%;
  font-family:var(--sans); font-weight:300;
  font-size:var(--fs-caption);
  color:var(--text-strong);
  background:rgba(255,255,255,.06);
  border:1px solid rgba(255,255,255,.18);
  border-radius:var(--radius);
  padding:.7em .9em;
  transition:border-color var(--dur-hover) var(--ease), background var(--dur-hover) var(--ease);
}
.cta-form__field textarea{resize:vertical; min-height:5em;}
.cta-form__field input:focus,
.cta-form__field textarea:focus{
  outline:none;
  border-color:var(--gold);
  background:rgba(255,255,255,.1);
}
/* Même bouton bordé/plein-au-survol que .hero__cta/.offers__cta
   ailleurs sur le site (voir leurs commentaires respectifs) - même
   vocabulaire visuel pour toute action "principale" du site, ce
   formulaire y compris. */
.cta-form__submit{
  align-self:flex-start;
  margin-top:.2rem;
  font-family:var(--sans);
  font-size:.88rem; letter-spacing:.06em; text-transform:uppercase;
  border:1px solid var(--gold); color:var(--gold);
  background:transparent;
  padding:.85em 1.8em; border-radius:var(--radius);
  cursor:pointer;
  transition:background var(--dur-hover) var(--ease), color var(--dur-hover) var(--ease);
}
.cta-form__submit:hover{background:var(--gold); color:var(--bg);}
/* État "Envoi en cours..." (demandé, "le bouton doit se désactiver
   pendant l'envoi... éviter les doubles soumissions", voir main.js) :
   :hover neutralisé pendant ce temps (le survol du curseur pendant
   l'attente ne doit pas déclencher le style plein doré, réservé à une
   action réellement possible) - même opacité réduite que le reste des
   états "inactifs" du site (ex. --mark-opacity au repos des offres). */
.cta-form__submit:disabled{
  opacity:.55;
  cursor:not-allowed;
}
.cta-form__submit:disabled:hover{background:transparent; color:var(--gold);}
/* .cta-form__success/.cta-form__error (nouveau, demandé - confirmation/
   erreur de soumission, voir main.js pour la logique fetch) : même
   vocabulaire typographique que le reste du formulaire (MarkOT, même
   famille que .cta-form__field label) plutôt qu'une bannière/un encart
   coloré étranger à la charte du site. --success en doré (seule couleur
   d'accent du site, déjà utilisée pour signaler une action réussie
   ailleurs - ex. bordure au survol) ; --error dans le même doré mais
   SANS gras (demandé "discret", pas d'alerte rouge agressive, hors
   palette du site) - seule la taille de police (légèrement réduite,
   proche d'un texte d'aide) le distingue visuellement du message de
   succès, plus affirmatif. */
.cta-form__success{
  font-family:var(--sans); font-weight:300;
  font-size:var(--fs-body);
  line-height:1.6;
  color:var(--gold);
}
.cta-form__error{
  margin-top:.9rem;
  font-family:var(--sans); font-weight:300;
  font-size:var(--fs-caption); line-height:var(--lh-caption);
  color:var(--gold);
}
/* Breakpoint harmonisé sur 860px (comme le reste du site) : Nom/Prénom
   et Mail/Téléphone repassent en colonne, chacun sur sa propre ligne. */
@media (max-width:860px){
  .cta-form__row{flex-direction:column;}
}

/* ==========================================================================
   FOOTER
   ========================================================================== */
.site-footer{padding-block:2.4rem;}
/* Passé de 1 à 3 lignes de texte (demandé, mentions TVA/SIREN + copyright
   détaillé en plus du copyright déjà présent) : .site-footer__inner
   repasse en colonne (logo au-dessus, texte en dessous) plutôt qu'une
   seule rangée horizontale - resterait illisible/trop large sur une
   seule ligne à 3 mentions. */
.site-footer__inner{
  display:flex; flex-direction:column; align-items:center; gap:.8rem;
  text-align:center;
}
.site-footer__mark{width:20px; height:20px; opacity:.7; filter:brightness(0) invert(1);}
.site-footer__text{display:flex; flex-direction:column; gap:.4rem;}
.site-footer p{font-size:.8rem; color:var(--text); opacity:.65; letter-spacing:.02em;}
/* Lien "Mentions Légales" (demandé) : soulignement (a{text-decoration:
   none} par défaut sitewide, voir règle générique) + doré au survol,
   même vocabulaire que les autres liens discrets du site. */
.site-footer p a{text-decoration:underline; text-underline-offset:2px; transition:color var(--dur-hover) var(--ease);}
.site-footer p a:hover, .site-footer p a:focus-visible{color:var(--gold);}

/* ==========================================================================
   PAGE MENTIONS LÉGALES (mentions-legales.html, demandé) : page statique,
   sans animation (demandé, "pas besoin d'animation particulière") - ni
   motion.js ni main.js ne sont chargés sur cette page, aucune règle
   [data-reveal-*]/GSAP à prévoir ici. Charte reprise telle quelle : fond
   --bg/texte --text hérités de body (aucune règle propre nécessaire),
   même vocabulaire typographique que le reste du site (titre en
   var(--display) façon .section-title, corps en MarkOT). .legal-main
   padding-top généreux : .site-header reste position:absolute (comme sur
   l'accueil), cette marge l'empêche de chevaucher le titre de page. */
.legal-main{
  padding-block:clamp(6rem, 12vw, 8rem) var(--section-gap);
}
.legal__title{
  font-family:var(--display); font-weight:600;
  color:var(--text-strong);
  text-transform:lowercase;
  font-size:var(--fs-title);
  line-height:1.04;
  margin-bottom:clamp(2.2rem, 4.6vw, 3.4rem);
}
.legal__title::first-letter{text-transform:uppercase;}
.legal__section{max-width:70ch;}
.legal__section + .legal__section{margin-top:clamp(2rem, 4vw, 3rem);}
.legal__section h2{
  font-family:var(--sans); font-weight:600;
  text-transform:uppercase; letter-spacing:.04em;
  font-size:var(--fs-caption);
  color:var(--gold);
  margin-bottom:.7rem;
}
.legal__section p{
  font-family:var(--sans); font-weight:300;
  font-size:var(--fs-body);
  line-height:1.6;
  color:var(--text);
}
.legal__section p + p{margin-top:.9rem;}
.legal__section a{text-decoration:underline; text-underline-offset:2px; transition:color var(--dur-hover) var(--ease);}
.legal__section a:hover, .legal__section a:focus-visible{color:var(--gold);}
