/* Hallmark · mouvement
 *
 * Trois primitives, pas une de plus :
 *   1. reveal, fade-up en cascade à l'entrée dans le viewport
 *   2. media-settle l'image se pose (scale 1.06 → 1) pendant que le cadre s'ouvre
 *   3. tick, les chiffres montent jusqu'à leur valeur réelle
 *
 * On n'anime que transform et opacity. Aucune propriété de layout.
 * Aucun rebond, aucun overshoot sur du changement d'état UI.
 * Rien ne bouge tant que JS n'a pas armé le système (.js-motion sur <html>),
 * pour qu'une page sans JS reste entièrement lisible.
 */

/* ═══════════════════════════ 1 · REVEAL ══════════════════════════════ */

.js-motion [data-reveal] {
  opacity: 0;
  transform: translate3d(0, var(--reveal-shift), 0);
  transition:
    opacity   var(--dur-reveal) var(--ease-out),
    transform var(--dur-reveal) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: transform, opacity;
}
.js-motion [data-reveal].is-revealed {
  opacity: 1;
  transform: translate3d(0, 0, 0);
  will-change: auto;
}

/* Variantes directionnelles, pour les diptyques, où le texte et l'image
   n'arrivent pas du même côté. */
.js-motion [data-reveal="left"]  { transform: translate3d(calc(var(--reveal-shift) * -1.4), 0, 0); }
.js-motion [data-reveal="right"] { transform: translate3d(calc(var(--reveal-shift) *  1.4), 0, 0); }
.js-motion [data-reveal="scale"] { transform: scale(0.965); }
.js-motion [data-reveal="left"].is-revealed,
.js-motion [data-reveal="right"].is-revealed { transform: translate3d(0, 0, 0); }
.js-motion [data-reveal="scale"].is-revealed { transform: scale(1); }

/* Cascade : chaque enfant part var(--reveal-step) après le précédent.
   Plafonnée à 8, au-delà, l'attente devient perceptible et agaçante. */
.js-motion [data-reveal-group] > * {
  opacity: 0;
  transform: translate3d(0, var(--reveal-shift), 0);
  transition:
    opacity   var(--dur-reveal) var(--ease-out),
    transform var(--dur-reveal) var(--ease-out);
  will-change: transform, opacity;
}
.js-motion [data-reveal-group].is-revealed > * {
  opacity: 1;
  transform: translate3d(0, 0, 0);
  will-change: auto;
}
.js-motion [data-reveal-group].is-revealed > *:nth-child(1) { transition-delay: calc(var(--reveal-step) * 0); }
.js-motion [data-reveal-group].is-revealed > *:nth-child(2) { transition-delay: calc(var(--reveal-step) * 1); }
.js-motion [data-reveal-group].is-revealed > *:nth-child(3) { transition-delay: calc(var(--reveal-step) * 2); }
.js-motion [data-reveal-group].is-revealed > *:nth-child(4) { transition-delay: calc(var(--reveal-step) * 3); }
.js-motion [data-reveal-group].is-revealed > *:nth-child(5) { transition-delay: calc(var(--reveal-step) * 4); }
.js-motion [data-reveal-group].is-revealed > *:nth-child(6) { transition-delay: calc(var(--reveal-step) * 5); }
.js-motion [data-reveal-group].is-revealed > *:nth-child(7) { transition-delay: calc(var(--reveal-step) * 6); }
.js-motion [data-reveal-group].is-revealed > *:nth-child(n+8) { transition-delay: calc(var(--reveal-step) * 7); }

/* ═══════════════════════ 2 · MEDIA SETTLE ════════════════════════════
   La transition entre sections. Le cadre monte, l'image se dézoome
   légèrement, l'effet « la photo se pose » de la référence. */

.js-motion [data-media-settle] { overflow: hidden; }
.js-motion [data-media-settle] > img,
.js-motion [data-media-settle] > picture > img {
  transform: scale(1.06);
  opacity: 0;
  transition:
    transform 1100ms var(--ease-out),
    opacity   var(--dur-slow) var(--ease-out);
  will-change: transform, opacity;
}
.js-motion [data-media-settle].is-revealed > img,
.js-motion [data-media-settle].is-revealed > picture > img {
  transform: scale(1);
  opacity: 1;
  will-change: auto;
}

/* Le hero se joue au chargement, pas au défilement, il est déjà visible.
 *
 * Remplissage `backwards` et NON `forwards`, avec l'état invisible déclaré
 * dans le `from` de la keyframe plutôt que sur l'élément.
 *
 * La version précédente posait `opacity: 0` sur l'élément lui-même et comptait
 * sur l'animation pour le révéler. Si l'animation ne partait pas (onglet en
 * arrière-plan au chargement, animation interrompue, moteur qui ne compose
 * pas), le titre du hero restait invisible pour toujours. Le contenu le plus
 * important du site ne doit pas dépendre d'une animation pour exister.
 *
 * Ici, l'état naturel de l'élément est visible. `backwards` lui fait tenir
 * l'état `from` pendant le délai, puis l'animation le ramène à son état
 * naturel. Pas d'animation, pas de problème : le contenu s'affiche.
 */
.js-motion .hero__frame > .hero__media img {
  animation: hero-settle 1400ms var(--ease-out) 120ms backwards;
}
@keyframes hero-settle {
  from { transform: scale(1.08); opacity: 0; }
}

.js-motion .hero__content > *,
.js-motion .hero__social,
.js-motion .hero__slider {
  animation: hero-rise var(--dur-reveal) var(--ease-out) backwards;
}
.js-motion .hero__content > *:nth-child(1) { animation-delay: 320ms; }
.js-motion .hero__content > *:nth-child(2) { animation-delay: 400ms; }
.js-motion .hero__content > *:nth-child(3) { animation-delay: 480ms; }
.js-motion .hero__content > *:nth-child(4) { animation-delay: 560ms; }
.js-motion .hero__social { animation-delay: 640ms; }
.js-motion .hero__slider { animation-delay: 720ms; }

@keyframes hero-rise {
  from { opacity: 0; transform: translate3d(0, 18px, 0); }
}

/* ═══════════════════════════ 3 · TICK ════════════════════════════════
   Les compteurs montent vers leur valeur réelle. Le JS écrit le nombre ;
   le CSS ne fait que réserver la place pour éviter tout décalage. */

[data-tick] {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

/* ══════════════════════ Transitions entre sections ═══════════════════
   Un liseré qui se déploie de la gauche vers la droite au passage
   d'une section à l'autre. Transform uniquement, scaleX sur une ligne. */

.section-rule {
  height: var(--rule-hairline);
  background: var(--color-rule);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 900ms var(--ease-out);
}
.js-motion .section-rule.is-revealed { transform: scaleX(1); }
html:not(.js-motion) .section-rule { transform: scaleX(1); }

/* ══════════════════════════ Mouvement réduit ═════════════════════════
   Le mouvement spatial disparaît. Il ne reste qu'un fondu court 
   la page conserve son rythme sans jamais déplacer quoi que ce soit. */

@media (prefers-reduced-motion: reduce) {
  .js-motion [data-reveal],
  .js-motion [data-reveal-group] > *,
  .js-motion [data-media-settle] > img,
  .js-motion [data-media-settle] > picture > img {
    transform: none !important;
    transition: opacity 150ms linear !important;
    transition-delay: 0ms !important;
  }
  .js-motion .hero__frame > .hero__media img,
  .js-motion .hero__content > *,
  .js-motion .hero__slider,
  .js-motion .hero__social {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .section-rule { transform: scaleX(1) !important; transition: none !important; }
  .gauge__fill  { transition: none !important; }
}

/* ── Entrées latérales : verticales dès que la mise en page se replie ───
   `data-reveal="left|right"` translate l'élément de 30 px horizontalement
   tant qu'il n'est pas révélé. Sur un diptyque replié en colonne unique,
   l'élément fait déjà toute la largeur : ces 30 px le poussent hors du
   cadre. Le débordement est masqué par `overflow-x: clip`, mais le contenu
   est bel et bien rogné à droite.
   Et une entrée « par la gauche » ne veut plus rien dire quand il n'y a
   plus de colonne gauche. */
@media (max-width: 53.75rem) {
  .js-motion [data-reveal="left"],
  .js-motion [data-reveal="right"] {
    transform: translate3d(0, var(--reveal-shift), 0);
  }
}
