/* MOUVEMENT — tout ce qui bouge, au même endroit.
   Apparitions au défilement, marquees (logos, témoignages), animations d'ambiance.
   Se charge en dernier : c'est ici que « mouvement réduit » neutralise le reste. */

/* ══ APPARITIONS ══
   Le mouvement passe par translate / scale / rotate, jamais par transform :
   ces propriétés se composent, donc les survols, le flottement de la puce
   et le défilement du bandeau continuent de fonctionner par-dessus.
   Chaque élément décrit son point de départ, .in le ramène à zéro. */
.rv, [data-rv] { opacity: 0;
	translate: var(--fx, 0px) var(--fy, 14px); scale: var(--fs, 1); rotate: var(--fr, 0deg); }

/* L'entrée est une image-clé, pas une transition. Une transition se
   déclare avec la propriété « transition », que beaucoup de composants
   redéfinissent pour leurs survols : la déclaration du composant écrasait
   alors toute l'apparition, et l'élément surgissait d'un coup — c'était
   le cas des pastilles de logos. Une animation ne peut pas être écrasée
   comme ça, et les survols gardent leur propre transition intacte. */
.rv.in, [data-rv].in { animation: rvIn var(--dur, .6s) var(--ease, var(--out)) var(--d, 0ms) both; }
@keyframes rvIn { to { opacity: 1; translate: 0 0; scale: 1; rotate: 0deg; } }

/* Un léger dépassement, réservé aux objets. Jamais sur du texte :
   une ligne de titre qui rebondit se lit comme un défaut d'affichage. */
.rv-obj  { --ease: cubic-bezier(.2,1.14,.36,1); }

/* Points de départ */
/* Les pastilles de logos ne changent pas d'échelle : un cercle qui porte
   une bordure, une ombre et un débordement masqué doit être re-tramé à
   chaque pas d'agrandissement, et ça se voit. Elles montent, c'est tout. */
.rv-bub  { --fy: 12px; --fs: .9; --dur: .56s; }
.rv-down { --fy: -13px; --fs: .62; --fr: -18deg; will-change: opacity, scale, rotate, translate; }
.rv-rise { --fy: 20px; }
/* Ces deux points de départ ne servent qu'au hero. Ils portaient une
   rotation et un décalage latéral qui, sur la première chose que l'on voit,
   se lisaient comme un tremblement plutôt que comme une entrée. Le mouvement
   y est désormais strictement vertical. */
.rv-arc  { --fy: 30px; }
.rv-slide{ --fy: 12px; }

/* La puce cumule son entrée et son flottement : l'une joue sur translate,
   l'autre sur transform, elles ne se marchent pas dessus. */
.chip-a.in { animation: rvIn var(--dur, .6s) var(--ease, var(--out)) var(--d, 0ms) both,
	bob 7s var(--soft-e) 1.5s infinite; }

/* Une fois l'entrée jouée, on retire l'animation : une animation en
   « fill: both » reste vivante indéfiniment et garde son élément sur un
   calque à part. Sur soixante éléments, ça coûtait dix-sept images par
   seconde. L'état final est simplement figé par une classe. */
.rv.done, [data-rv].done { opacity: 1; translate: 0 0; scale: 1; rotate: 0deg;
	animation: none; will-change: auto; }
/* Sauf dans le hero et le bandeau de logos, qui gardent leur calque.
   Pendant l'entrée, un élément animé est dessiné sur un calque à part ; à
   la fin, il retourne dans la page et le texte est redessiné, recalé sur la
   grille de pixels. Ces éléments tombent sur des positions fractionnaires
   (titre à y = 254,77 px, photo à x = 773,81 px) : mesuré, le titre change
   alors sur 3 000 pixels et la photo sur 4 000. C'était le sursaut d'une
   fraction de pixel, en x comme en y, à la fin de chaque entrée. Pour le
   bandeau de logos, perdre son calque obligeait en plus le navigateur à
   resynchroniser le défilement, d'où l'à-coup vers la gauche.
   Une vingtaine de calques, tous dans le premier écran : le coût est nul. */
/* « transform » et non « translate » : mesuré dans Chrome, seule la première
   valeur crée le même calque qu'une animation en cours. */
.hero [data-rv].done, .hero .rv.done, .rail.rv.done { will-change: transform; }
/* Même délai qu'à l'entrée, à la milliseconde. Le navigateur reconnaît
   l'animation « bob » par son nom et la poursuit sans la relancer : lui ôter
   son délai de 1,5 s la faisait avancer d'autant d'un coup, et la puce
   sautait de 3 px vers le haut à la fin de son entrée. */
.chip-a.done { animation: bob 7s var(--soft-e) 1.5s infinite; }

/* Mouvement réduit, version enrichie par main.js : une seule voie, chaque
   témoignage une seule fois. Sans JavaScript, la règle du bloc ci-dessous
   suffit à tout déplier — au prix des doublons de la boucle. */
.stream.still { height: auto; grid-template-columns: 1fr; grid-template-rows: auto;
	-webkit-mask-image: none; mask-image: none; }
.stream.still .lane + .lane { display: none; }
.stream.still .lane-track { animation: none; }

/* Hors écran, plus rien ne tourne : deux animations continues qui
   défilent dans le vide, c'est de la batterie pour personne.
   La classe est posée par main.js, au passage d'un observateur. */
.rail.dormant .rail-track,
.stream.dormant .lane-track,
.chip-a.dormant,
.hero-note.dormant .dot::after { animation-play-state: paused; }

/* Sous le rideau de chargement, seule la photo du hero fait son entrée : quand
   le rideau monte, on découvre une page déjà habitée, pas un fond vide. Les
   textes, les boutons et les deux badges posés sur la photo attendent, figés à
   leur première image, et jouent leur entrée quand le rideau passe sur eux.
   Le surlignage du titre est une transition, qu'on ne peut pas figer : on le
   retient à zéro, il part au même moment. */
.is-loading .hero-left [data-rv].in, .is-loading .shot > [data-rv].in { animation-play-state: paused; }
.is-loading .hero .hl { transition: none; background-size: 0% 100%; }

/* ══ MARQUEE — bandeau de logos ══ */
.rail-track { display: flex; align-items: center; gap: 56px; width: max-content;
	will-change: transform; backface-visibility: hidden;
	animation: slide 54s linear infinite; }
.rail:hover .rail-track { animation-play-state: paused; }
/* Chaque logo porte sa propre hauteur, posée dans le HTML : à hauteur égale,
   un logo carré occupe six fois moins de surface qu'un logotype allongé, et le
   bandeau paraît irrégulier. La hauteur décroît avec l'allongement, de façon à
   ce que tous pèsent visuellement la même chose. */
.rail-track img { height: var(--lh, 34px); width: auto; object-fit: contain; filter: grayscale(1); opacity: .48;
	transition: opacity .2s var(--soft-e), filter .2s var(--soft-e); }
@media (hover: hover) and (pointer: fine) {
	.rail-track img:hover { filter: grayscale(0); opacity: 1; }
}
@keyframes slide { from { transform: translate3d(0,0,0); } to { transform: translate3d(-50%,0,0); } }

/* ══ MARQUEE — colonnes de témoignages ══ */
.lane-track { display: flex; flex-direction: column; gap: 16px; will-change: transform;
	animation: rollUp var(--dur, 58s) linear infinite; }
.lane:nth-child(2) .lane-track { animation-name: rollDown; --dur: 68s; }
.lane:nth-child(3) .lane-track { --dur: 62s; }
@keyframes rollUp   { from { transform: translate3d(0,0,0); }      to { transform: translate3d(0,-50%,0); } }
@keyframes rollDown { from { transform: translate3d(0,-50%,0); }   to { transform: translate3d(0,0,0); } }
.stream:hover .lane-track { animation-play-state: paused; }

/* ══ ANIMATIONS D'AMBIANCE ══ */
@keyframes halo { 0% { transform: scale(.7); opacity: .8; } 70%,100% { transform: scale(1.6); opacity: 0; } }
@keyframes bob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }

@media (prefers-reduced-motion: reduce) {
	.rv, [data-rv] { opacity: 1; translate: none; scale: none; rotate: none; }
	.rv.in, [data-rv].in { animation: none; }
	.rail-track, .chip-a, .hero-note .dot::after, .lane-track { animation: none !important; }
	/* Le flux de témoignages ne défile plus : il se déplie et se lit
	   normalement, au lieu de rester coupé net à hauteur fixe. */
	.stream { height: auto; -webkit-mask-image: none; mask-image: none; }
	.hl { background-size: 100% 100%; transition: none; }
	html { scroll-behavior: auto; }
	/* Trois transitions dépassaient encore la demi-seconde : le trait qui se
	   trace entre les fiches de la méthode, et les deux zooms de photo au survol.
	   Elles s'appliquent maintenant d'un coup, comme le reste. */
	.tie .ln, .tie .hd, .who .pic img, .tile-dark .cover { transition: none; }
}
