@charset "utf-8";

/* ==========================================================================
   Couche de mouvement.
   Patterns repris de l'état de l'art open source :
   - reveal en lot + stagger  → gsap ScrollTrigger.batch (greensock/gsap)
   - inView + stagger         → motiondivision/motion (.card, { delay: stagger })
   - ergonomie data-aos       → michalsnik/aos (reveal par attribut)
   - --char-index en CSS pur  → splittings/sh/Splitting.js (délai par élément)
   Rien n'est chargé depuis un CDN : tout est réimplémenté en IntersectionObserver.
   ========================================================================== */

/* État initial : appliqué seulement si le JS tourne (html.js).
   La courbe passe de cubic-bezier(.22,.61,.36,1) à (.16,1,.3,1) : même durée
   perçue, mais 80 % du déplacement est consommé dans le premier tiers du
   temps, puis l'élément se pose. C'est ce profil « arrivée lourde » qui
   distingue un mouvement tenu d'un fondu générique. Un flou de 6 px qui se
   lève sur la même durée ajoute la profondeur, sans coûter de mise en page :
   filter et transform sont composés par le GPU. */
html.js [data-motion] {
  opacity: 0;
  transform: translate3d(0, 26px, 0);
  filter: blur(6px);
  transition:
    opacity 0.82s var(--ease-sortie, cubic-bezier(0.16, 1, 0.3, 1)),
    transform 0.82s var(--ease-sortie, cubic-bezier(0.16, 1, 0.3, 1)),
    filter 0.68s var(--ease-sortie, cubic-bezier(0.16, 1, 0.3, 1));
  transition-delay: calc(var(--i, 0) * 78ms);
  will-change: opacity, transform, filter;
}
html.js [data-motion="left"] {
  transform: translate3d(-30px, 0, 0);
}
html.js [data-motion="right"] {
  transform: translate3d(30px, 0, 0);
}
html.js [data-motion="zoom"] {
  transform: scale(0.955);
}
/* La bascule de chapitre n'arrive pas par le bas : elle apparaît sur place,
   en fondu, comme un plan de coupe. */
html.js [data-motion="fade"] {
  transform: none;
  filter: blur(10px);
  transition-duration: 1.1s;
}
html.js [data-motion].is-in {
  opacity: 1;
  transform: none;
  filter: none;
  will-change: auto;
}

/* Le tiret du surtitre se tire quand la section entre dans l'écran. C'est
   tout ce qui reste de l'ouverture animée : le filet coloré qu'elle tirait
   en tête de section a été retiré, le fond en fondu le remplace. */
html.js .chapitre {
  --tiret: 0;
}
html.js .chapitre i {
  transition: transform 0.72s var(--ease-sortie, cubic-bezier(0.16, 1, 0.3, 1)) 0.18s;
}
/* .is-in seul, sans [data-motion] : l'attribut est retiré 1,4 s après la
   révélation (motion.js, settle) — accroché à lui, le tiret se serait
   rétracté tout seul une seconde après être apparu. */
html.js .is-in .chapitre,
html.js .chapitre.is-in,
html.js .chapitre-ouvert .chapitre {
  --tiret: 1;
}

/* Barre de progression de lecture */
.scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 3px;
  width: 100%;
  z-index: 120;
  pointer-events: none;
  background: transparent;
}
.scroll-progress span {
  display: block;
  height: 100%;
  width: 100%;
  background: linear-gradient(90deg, var(--sky, #60a3fb), var(--accent, #3467cd));
  border-radius: 0 3px 3px 0;
  transform: scaleX(0);
  transform-origin: 0 50%;
  will-change: transform;
}

/* En-tête condensé au défilement */
.header.is-scrolled {
  box-shadow: 0 10px 30px -22px rgba(21, 24, 32, 0.6);
}
.header.is-scrolled .header__inner {
  min-height: 3.9rem;
  transition: min-height 0.25s ease;
}

/* Zoom doux des visuels au survol */
.bien__media svg {
  transition: transform 0.6s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.bien:hover .bien__media svg {
  transform: scale(1.045);
}

/* Flèche des pilules : rotation déjà en CSS, on ajoute un décalage magnétique.
   Le will-change reste au survol : posé en permanence, il gardait chaque
   bouton de chaque page sur sa propre couche de composition pour rien. */
.pill-arrow i {
  transition: background 0.2s, transform 0.35s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.pill-arrow:hover i,
.pill-arrow:focus-visible i {
  will-change: transform;
}

/* Compteur : chiffres tabulaires pendant l'animation. L'attribut lu par le JS
   est data-compteur depuis la refonte ; la règle visait encore data-count et
   ne s'appliquait donc à rien — c'est elle qui devait empêcher la largeur des
   chiffres de sauter pendant le décompte. */
[data-compteur] {
  font-variant-numeric: tabular-nums;
}

/* Bandeau défilant (partenaires / labels) */
.marquee {
  overflow: hidden;
  background: var(--bg-2);
  padding-block: 1rem;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee__track {
  display: flex;
  align-items: center;
  gap: 3.5rem;
  width: max-content;
  /* 34 s pour parcourir la moitié de la piste, soit huit villes : les noms
     défilaient plus vite qu'on ne les lit. 75 s laisse le temps de lire
     chacun, et le bandeau redevient un fond plutôt qu'un mouvement qui
     attire l'œil hors du texte. */
  animation: marquee-scroll 75s linear infinite;
}
.marquee:hover .marquee__track {
  animation-play-state: paused;
}
.marquee span {
  font-size: var(--t-m);
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink-2);
  white-space: nowrap;
}
.marquee span::before {
  content: "◆";
  color: var(--accent, #2a6a4e);
  margin-right: 3.5rem;
  font-size: 0.6em;
  vertical-align: middle;
}
@keyframes marquee-scroll {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(-50%, 0, 0);
  }
}

/* Parallaxe du hero. Pas de transition : --py est réécrit à chaque image de
   défilement, et une transition de 0,15 s sur la même propriété retardait
   chaque mise à jour au lieu de l'adoucir — d'où le flottement au scroll. */
.hero__fond {
  transform: translate3d(0, var(--py, 0px), 0) scale(1.08);
  will-change: transform;
}

/* ------------------------------------------------------------------ Mobile */
/* Le mouvement ci-dessus est réglé pour un grand écran, où les éléments
   arrivent côte à côte. Sur un téléphone tout s'empile : la même animation
   fait apparaître des pans entiers d'un coup, et le lecteur perd le fil.

   Trois choses changent, et une seule compte vraiment — le décalage.
   Avec 78 ms par rang, une liste de huit éléments met plus d'une seconde et
   demie à se poser : c'est ce qui donne l'impression d'un gros bloc qui
   bascule. On le ramène à 20 ms et on le plafonne au quatrième rang, pour
   qu'une longue liste ne cascade pas indéfiniment.

   Le déplacement latéral est annulé : sur une colonne étroite, un texte qui
   entre par le côté sort de l'axe de lecture. Et le flou passe de 6 px à
   2 px : c'est lui qui rend le texte illisible pendant la transition. */
@media (max-width: 56rem) {
  html.js [data-motion] {
    transform: translate3d(0, 10px, 0);
    filter: blur(2px);
    transition-duration: 0.38s, 0.38s, 0.3s;
    transition-delay: calc(min(var(--i, 0), 4) * 20ms);
  }
  html.js [data-motion="left"],
  html.js [data-motion="right"] {
    transform: translate3d(0, 10px, 0);
  }
  html.js [data-motion="zoom"] {
    transform: scale(0.985);
  }
  html.js [data-motion="fade"] {
    transform: none;
    filter: blur(3px);
    transition-duration: 0.42s;
  }
  html.js .chapitre i {
    transition-duration: 0.36s;
    transition-delay: 0.06s;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero__fond {
    transform: none;
  }
}

/* Changement d'étape dans les formulaires : app.js bascule l'attribut hidden,
   le fieldset qui prend la main entre par la droite. */
@keyframes etape-entre {
  from {
    opacity: 0;
    transform: translateX(14px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
html.js .stepper fieldset:not([hidden]),
html.js .stepper__done:not([hidden]) {
  animation: etape-entre 0.34s cubic-bezier(0.22, 0.61, 0.36, 1) both;
}

@media (prefers-reduced-motion: reduce) {
  html.js [data-motion] {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    transition: none !important;
  }
  html.js .chapitre {
    --tiret: 1;
  }
  html.js .chapitre i {
    transition: none;
  }
  .marquee__track {
    animation: none;
  }
  .scroll-progress {
    display: none;
  }
  .bien__media svg {
    transition: none;
  }
  html.js .stepper fieldset:not([hidden]),
  html.js .stepper__done:not([hidden]) {
    animation: none;
  }
}

@media print {
  html.js [data-motion] {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }
  html.js .chapitre {
    --tiret: 1;
  }
  .scroll-progress {
    display: none !important;
  }
  html.js .stepper fieldset:not([hidden]),
  html.js .stepper__done:not([hidden]) {
    animation: none !important;
  }
}

/* La parallaxe agrandit légèrement : le cadre doit rogner,
.metier__visuel,
.galerie__principale {
  overflow: hidden;
}
/* Deux variables composées en un seul transform : --par porte le décalage de
   parallaxe écrit par motion.js, --zoom le grossissement au survol. Avant,
   le JS écrivait transform en style inline et effaçait le survol. */
.shot img,
.metier__visuel img,
.galerie__principale img {
  --par: 0px;
  --zoom: 1.06;
  transform: translate3d(0, var(--par), 0) scale(var(--zoom));
  transition: transform 0.7s var(--ease, cubic-bezier(0.22, 0.61, 0.36, 1));
  will-change: transform;
}
.metier:hover .metier__visuel img,
.metier:focus-within .metier__visuel img,
.shot:hover img {
  --zoom: 1.11;
}

/* Relief de la bascule : le texte monte moins vite que l'image. */
.videoband__text {
  transform: translate3d(0, var(--releve, 0px), 0);
  will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
  .shot img,
  .metier__visuel img,
  .galerie__principale img {
    transform: none !important;
    transition: none;
    will-change: auto;
  }
  .videoband__text {
    transform: none;
  }
}

/* ---------- Mécanisme de la reprise ---------- */

/* Deux secondes et demie par dent, sur les quatre roues. Le confrère dont
   vient le motif fait tourner les siennes en dix secondes, ce qui se voit
   tourner ; ici la période vaut le nombre de dents multiplié par le même pas
   de temps, si bien que les dents qui se font face arrivent toujours en
   quinconce et ne se croisent jamais. Une roue tourne à l'envers de celle qui
   l'entraîne : la grande mène les roues 2 et 3, la roue 2 mène la petite, d'où
   l'alternance des sens. transform-box est écrit en toutes lettres, sinon
   l'origine se calcule sur le cadre du dessin et la roue décrit un cercle au
   lieu de tourner sur elle-même. */
.mecanisme__roue {
  transform-box: fill-box;
  transform-origin: center;
  animation: mecanisme-tour linear infinite;
  will-change: transform;
}
.mecanisme__roue--1 {
  animation-duration: 40s;              /* 16 dents */
}
.mecanisme__roue--2 {
  animation-duration: 25s;              /* 10 dents */
  animation-direction: reverse;
}
.mecanisme__roue--3 {
  animation-duration: 30s;              /* 12 dents */
  animation-direction: reverse;
}
.mecanisme__roue--4 {
  animation-duration: 12.5s;            /*  5 dents */
}
@keyframes mecanisme-tour {
  to {
    transform: rotate(1turn);
  }
}
@media (prefers-reduced-motion: reduce) {
  .mecanisme__roue {
    animation: none;
    will-change: auto;
  }
}
