/* ============================================================================
   Couche motion.
   Amélioration progressive : l'état FINAL est l'état par défaut. Les états
   initiaux ne sont posés que si <html> porte la classe `js` (script inline
   synchrone dans le <head>). Si le JS échoue, tout reste visible et lisible.
   Seuls `transform`, `opacity` et — pour les barres de Gantt — `clip-path`
   sont animés.
   ========================================================================= */

.js [data-reveal] {
  will-change: transform, opacity;
}

.js [data-reveal].is-settled {
  will-change: auto;
}

/* ── Entrée simple : montée + apparition ───────────────────────────────── */
.js [data-reveal="up"] {
  opacity: 0;
  transform: translate3d(0, 20px, 0);
  transition:
    opacity var(--d-enter) var(--e-out),
    transform var(--d-enter) var(--e-out);
  transition-delay: calc(var(--i, 0) * var(--stagger));
}

.js [data-reveal="up"].is-in {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

/* ── Cartes d'offre ────────────────────────────────────────────────────
   L'entrée porte sur la LISTE, pas sur chaque carte : un seul observateur
   déclenche la cascade, et les trois cartes gardent leur ordre quel que soit
   celui dans lequel elles franchissent le seuil. */
.js [data-reveal="cards"] .offer {
  opacity: 0;
  transform: translate3d(0, var(--card-lift), 0);
  transition:
    opacity var(--d-line) var(--e-out),
    transform var(--d-line) var(--e-out);
  transition-delay: calc(var(--i, 0) * 90ms);
}

.js [data-reveal="cards"].is-in .offer {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

/* ── Titres révélés ligne par ligne ────────────────────────────────────
   Les <span> sont produits par js/split-lines.js.

   Deux contraintes se croisent ici :
   1. `overflow: clip` rognerait les hampes et les accents, puisque
      l'interligne (0.9) est plus court que la fonte. On dégage donc le
      masque par un padding, annulé par une marge négative de même valeur.
   2. Une fois le texte éclaté en blocs, le conteneur n'a plus de ligne à
      lui : `text-box-trim` ne s'applique plus. Le rognage optique est
      reporté sur la première et la dernière ligne — valeurs identiques à
      celles calculées dans tokens.css, vérifiées contre l'implémentation
      native (H1 non découpé : 90,27 px, comme dans Figma).

   3. Le letter-spacing négatif des titres rogne l'avance du dernier glyphe :
      « ? » ou « s » dépassent alors de la boîte de ligne. On dégage aussi
      le masque sur les côtés (`--line-bleed-i`), compensé par une marge.

   Le conteneur passe en colonne flex : les marges négatives de lignes
   voisines ne doivent pas fusionner, sinon on perd 2,8 px par interligne. */
.line {
  --line-bleed-t: 0.05em;
  --line-bleed-b: 0.22em;
  --line-bleed-i: 0.12em;

  display: block;
  overflow: clip;
  padding-block: var(--line-bleed-t) var(--line-bleed-b);
  padding-inline: var(--line-bleed-i);
  margin-block: calc(-1 * var(--line-bleed-t)) calc(-1 * var(--line-bleed-b));
  margin-inline: calc(-1 * var(--line-bleed-i));
}

.line__in {
  display: block;
}

.is-split {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.is-split > .line:first-child {
  margin-block-start: calc(-1 * (var(--line-bleed-t) + var(--trim-cap-lh09)));
}

.is-split > .line:last-child {
  margin-block-end: calc(-1 * (var(--line-bleed-b) + var(--trim-base-lh09)));
}

.js [data-reveal="lines"] .line__in {
  transform: translate3d(0, 112%, 0);
  transition: transform var(--d-line) var(--e-out);
  transition-delay: calc(var(--line-i, 0) * 90ms);
}

.js [data-reveal="lines"].is-in .line__in {
  transform: translate3d(0, 0, 0);
}

/* ── Timeline : les graduations se tracent de haut en bas ──────────────── */
.js [data-reveal="grid"] .timeline__line {
  transform: scaleY(0);
  transform-origin: 50% 0;
  transition: transform var(--d-enter) var(--e-out);
  transition-delay: calc(var(--i, 0) * 40ms);
}

.js [data-reveal="grid"].is-in .timeline__line {
  transform: scaleY(1);
}

/* ── Barres de Gantt : balayage depuis la gauche, en cascade ───────────
   Exception assumée à la règle « transform/opacity seuls » : un masque
   par transform ferait glisser le texte avec la barre, or la barre doit
   pousser sous un contenu fixe. 3 éléments, repeint négligeable — mesuré. */
.js [data-reveal="gantt"] .step {
  clip-path: inset(0 100% 0 0 round var(--r-sm));
  transition: clip-path var(--d-narrative) var(--e-out);
  transition-delay: calc(var(--i, 0) * 140ms);
  will-change: clip-path;
}

.js [data-reveal="gantt"].is-in .step {
  clip-path: inset(0 0 0 0 round var(--r-sm));
}

/* Une fois l'entrée jouée, on retire le `clip-path` : le rayon est déjà porté
   par `border-radius`, garder les deux revient à découper deux fois et à
   maintenir une couche de composition pour rien. */
.js [data-reveal="gantt"].is-settled .step {
  clip-path: none;
  will-change: auto;
}

/* ── Mouvement réduit ──────────────────────────────────────────────────
   Le contenu reste intégralement accessible : on ne conserve qu'un fondu
   court, et toute translation / découpe disparaît. Le glisser du carrousel
   reste actif — c'est une interaction, pas une décoration. */
@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] {
    will-change: auto;
  }

  .js [data-reveal="up"],
  .js [data-reveal="cards"] .offer {
    transform: none;
    transition:
      opacity 200ms linear,
      transform 0ms;
  }

  .js [data-reveal="lines"] .line__in,
  .js [data-reveal="grid"] .timeline__line {
    transform: none;
    transition: none;
  }

  .js [data-reveal="gantt"] .step {
    clip-path: none;
    transition: none;
  }

  .line {
    overflow: visible;
  }

  .btn,
  .btn__label,
  .cta .avatar,
  .cta .avatar::after,
  .avatar img,
  .footer__avatar img,
  .nav__portrait img,
  .acc__state span,
  .acc__panel {
    transition-duration: 120ms;
  }

  html {
    scroll-behavior: auto;
  }
}
