/* ============================================================================
   motion-sultane.css — les 9 familles d'animation de la SYNTHÈSE M28,
   RÉIMPLÉMENTÉES EN CODE MAISON pour les feuilles de La Sultane.

   Source des valeurs : etudes/vistaprint/clone/SYNTHESE.md + les 6 PARTITION.md
   (mesures relevées en direct sur les pages vraies, M28).
   AUCUN fichier, script, police ou visuel Vistaprint/Wix n'entre ici : seules
   les MÉCANIQUES sont reprises (durées, courbes, propriétés qui bougent).

   Les 4 règles de conduite du lot (SYNTHESE §« 4 règles ») sont câblées :
   1. jamais deux durées identiques côte à côte  -> jeux de durées désaccordées
   2. hauteurs de déclenchement irrégulières      -> --m-seuil posé à la main
   3. le mouvement suit le geste de la lectrice   -> familles 8 et 9, réversibles
   4. un geste d'ouverture répété tient une feuille longue -> .m-ouverture

   Convention : un élément s'anime quand il porte l'attribut [data-m-vu].
   C'est motion-sultane.js qui le pose, à l'entrée dans le champ — JAMAIS au
   chargement. Sans JavaScript, tout reste visible et lisible (état final).
   ========================================================================= */

:root {
  /* Durées relevées, famille par famille (ms -> s) */
  --m-fade-1: .600s;   /* 600 ms  */
  --m-fade-2: 1.050s;  /* 1 050 ms */
  --m-fade-3: 1.200s;  /* 1 200 ms */
  --m-fade-4: 1.470s;  /* 1 470 ms */
  --m-slide:  1.200s;
  --m-arc:    1.500s;  /* bloc seul */
  --m-arc-c:  2.100s;  /* cascade de 5 */
  --m-flip:   1.200s;
  --m-glide:  1.200s;

  /* Les 3 easings relevés dans motion-breathe-1 (4 phases, un par phase) */
  --m-e1: cubic-bezier(.39, .575, .565, 1);
  --m-e2: cubic-bezier(.445, .05, .55, .95);
  --m-e3: cubic-bezier(.47, 0, .745, .715);

  /* Découpes de la révélation (relevé : --motion-clip-from / --motion-clip-to) */
  --m-clip-ferme: polygon(0% 100%, 100% 100%, 100% 100%, 0% 100%);
  --m-clip-ouvert: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
}

/* ---------------------------------------------------------------------------
   État de repos : posé UNIQUEMENT si le JS a pris la main (html.m-actif).
   Sans JS -> aucune règle ci-dessous ne s'applique -> tout est visible.
   C'est la garantie « lisible sans JavaScript » du chantier.
   ------------------------------------------------------------------------ */
.m-actif [data-m] { opacity: 0; will-change: transform, opacity; }
.m-actif [data-m][data-m-vu] { opacity: 1; }

/* --- Neutralisation de l'ANCIEN vocabulaire de mouvement ------------------
   Les gabarits M29 portent encore `.reveal` / `data-reveal` / `data-stagger`,
   hérités du moteur `kiosque/gabarits/motion.js` (M20/M21). `base.css` met
   `.reveal { opacity: 0 }` en attendant que ce moteur-là pose `.vu`.

   M30 REMPLACE ce moteur par les familles M28 et ne charge pas `motion.js` :
   sans cette neutralisation, tout élément `.reveal` que la partition ne couvre
   pas resterait INVISIBLE (constat réel : le titre et le chapô de la couverture
   avaient disparu). Le repos qui fait foi ici est `[data-m]` ci-dessus, plus
   spécifique, donc les éléments animés gardent bien leur état de départ. */
.m-actif .reveal { opacity: 1; transform: none; }

/* === 1. motion-fadeIn — l'apparition simple ==============================
   Relevé : 600/1050/1200/1470 ms, toujours linear, jouée une fois.
   « Presque toujours combinée à une autre, jamais seule sur un élément fort »
   -> ici elle sert d'entrée par défaut des blocs de texte. */
@keyframes m-fadeIn { from { opacity: 0; } to { opacity: 1; } }

.m-actif [data-m="fade"][data-m-vu] {
  animation: m-fadeIn var(--m-d, var(--m-fade-3)) linear both;
}

/* === 2. motion-slideIn — l'ouverture de feuille ==========================
   Relevé : 1 200 ms linear. Trois choses à la fois — ça glisse, ça se dévoile
   par une DÉCOUPE, et ça apparaît. C'est le signal « nouvelle partie » ;
   rejoué 11 fois sur une feuille de 21 538 px (règle de conduite 4). */
@keyframes m-slideIn {
  0.0001% {
    opacity: 1;
    transform: translate(var(--m-tx, -100%), var(--m-ty, 0%));
    clip-path: var(--m-clip-start, polygon(0% 0%, 0% 0%, 0% 100%, 0% 100%));
  }
  100% {
    opacity: 1;
    transform: translate(0, 0);
    clip-path: var(--m-clip-ouvert);
  }
}

.m-actif [data-m="slide"][data-m-vu],
.m-actif .m-ouverture[data-m-vu] {
  animation: m-slideIn var(--m-d, var(--m-slide)) linear both;
}

/* === 3. motion-arcIn — l'arrivée en arc ==================================
   Relevé : perspective(800px) + rotation jusqu'à 80° AUTOUR D'UN AXE DÉPORTÉ.
   Le déport s'obtient par un translateZ avant la rotation et son inverse
   après — c'est ce qui fait pivoter l'élément autour d'un point éloigné,
   donc en arc et non sur lui-même. 1 500 ms seul, 2 100 ms en cascade de 5.
   « Le geste le plus spectaculaire du lot » -> à doser (sommaire, rubriques). */
@keyframes m-arcIn {
  0.0001% {
    opacity: 0;
    transform:
      perspective(800px)
      translateZ(var(--m-axe, -420px))
      rotateX(var(--m-rx, 80deg))
      rotateY(var(--m-ry, 0deg))
      translateZ(calc(var(--m-axe, -420px) * -1));
  }
  100% {
    opacity: 1;
    transform:
      perspective(800px)
      translateZ(var(--m-axe, -420px))
      rotateX(0deg) rotateY(0deg)
      translateZ(calc(var(--m-axe, -420px) * -1));
  }
}

.m-actif [data-m="arc"][data-m-vu] {
  animation: m-arcIn var(--m-d, var(--m-arc)) var(--m-e1) both;
  transform-origin: 50% 100%;
}

/* === 4. motion-flipIn — la carte qui se retourne =========================
   Relevé : 1 200 ms, rotateY -90° -> 0, TOUJOURS doublé d'un fadeIn de même
   durée (sinon la tranche à -90° est un trait invisible). Le doublage est
   intégré ici : l'opacité monte dans la même keyframe. */
@keyframes m-flipIn {
  0.0001% { opacity: 0; transform: perspective(800px) rotateY(var(--m-ry, -90deg)); }
  100%    { opacity: 1; transform: perspective(800px) rotateX(0deg) rotateY(0deg); }
}

.m-actif [data-m="flip"][data-m-vu] {
  animation: m-flipIn var(--m-d, var(--m-flip)) linear both;
  transform-origin: 50% 50%;
}

/* === 5. motion-glideIn — la cascade nette ================================
   Relevé : 1 200 ms. LE détail qui compte : l'opacité est en `step-end`, elle
   bascule d'un coup au lieu de fondre. Le texte n'est JAMAIS lu à
   mi-transparence — il glisse déjà lisible. Vu jusqu'à 24 fois sur une page,
   aux hauteurs irrégulières (197, 221, 256, 366, 391 px).
   C'est notre cascade par défaut : listes, chiffres, réponses. */
@keyframes m-glideIn {
  0% {
    opacity: 0;
    animation-timing-function: step-end;
    transform: translate(var(--m-tx, 0), var(--m-ty, 2.2em));
  }
  0.0001% {
    opacity: 1;
    transform: translate(var(--m-tx, 0), var(--m-ty, 2.2em));
  }
  100% { opacity: 1; transform: translate(0, 0); }
}

.m-actif [data-m="glide"][data-m-vu] {
  animation: m-glideIn var(--m-d, var(--m-glide)) var(--m-e1) both;
}

/* Cascade : les retards sont IRRÉGULIERS (règle 2), pas une grille régulière.
   Suite relevée sur la page vraie : 197, 221, 256, 366, 391… -> ramenée en ms
   et étalée sur 9 rangs, jamais deux écarts identiques. */
.m-actif [data-m-cascade] > * { --m-r: 0ms; }
.m-actif [data-m-cascade] > :nth-child(1) { animation-delay: 0ms; }
.m-actif [data-m-cascade] > :nth-child(2) { animation-delay: 90ms; }
.m-actif [data-m-cascade] > :nth-child(3) { animation-delay: 205ms; }
.m-actif [data-m-cascade] > :nth-child(4) { animation-delay: 265ms; }
.m-actif [data-m-cascade] > :nth-child(5) { animation-delay: 400ms; }
.m-actif [data-m-cascade] > :nth-child(6) { animation-delay: 445ms; }
.m-actif [data-m-cascade] > :nth-child(7) { animation-delay: 585ms; }
.m-actif [data-m-cascade] > :nth-child(8) { animation-delay: 640ms; }
.m-actif [data-m-cascade] > :nth-child(n+9) { animation-delay: 760ms; }

/* === 6. motion-breathe-1 — le mouvement qui ne s'arrête jamais ===========
   Relevé : boucle infinie, translate3d, 4 PHASES À EASING DIFFÉRENT.
   Durées relevées sur un même bloc de chiffres : 2 500 · 5 000 · 6 000 · 6 700 ms
   — TOUTES DIFFÉRENTES. « Rien ne pulse en cœur » (règle de conduite 1).
   Les 4 classes ci-dessous sont à poser dans l'ordre sur des voisins. */
@keyframes m-breathe {
  0%   { transform: translate3d(0, 0, 0);                        animation-timing-function: var(--m-e1); }
  25%  { transform: translate3d(0, calc(var(--m-amp, 6px) * -1), 0); animation-timing-function: var(--m-e2); }
  50%  { transform: translate3d(0, 0, 0);                        animation-timing-function: var(--m-e3); }
  75%  { transform: translate3d(0, var(--m-amp, 6px), 0);        animation-timing-function: var(--m-e1); }
  100% { transform: translate3d(0, 0, 0); }
}

.m-actif [data-m-souffle][data-m-vu] {
  animation: m-breathe var(--m-souffle, 5s) infinite both;
}
/* Les 4 durées désaccordées, à distribuer sur des voisins */
.m-actif [data-m-souffle="1"] { --m-souffle: 2.5s;  --m-amp: 5px; }
.m-actif [data-m-souffle="2"] { --m-souffle: 5s;    --m-amp: 7px; }
.m-actif [data-m-souffle="3"] { --m-souffle: 6s;    --m-amp: 6px; }
.m-actif [data-m-souffle="4"] { --m-souffle: 6.7s;  --m-amp: 8px; }

/* === 7. Le bandeau défilant ==============================================
   Relevé : translate(-100%) linear infini, TOUJOURS 2 instances (le contenu
   + sa copie) pour une boucle sans couture. Durée signature du pied de page :
   16 725 ms sur les 6 pages. Ailleurs : 22 250 / 23 000 / 23 875 / 24 556 /
   25 333 / 25 764 / 26 250 ms. Usage remarquable à reprendre : le TRIPTYQUE
   dont celui du milieu défile À L'ENVERS (effet de tissage). */
@keyframes m-bandeau-g { to   { transform: translate(-100%); } }
@keyframes m-bandeau-d { from { transform: translate(-100%); } }

.m-bandeau { overflow: hidden; display: flex; white-space: nowrap; }
.m-bandeau > * {
  flex: 0 0 auto;
  animation: m-bandeau-g var(--m-bd, 16.725s) linear infinite;
}
.m-bandeau[data-sens="inverse"] > * { animation-name: m-bandeau-d; }
/* Le triptyque : 3 durées différentes, celle du milieu à l'envers */
.m-triptyque > :nth-child(1) .m-bandeau > * { --m-bd: 23.000s; }
.m-triptyque > :nth-child(2) .m-bandeau > * { --m-bd: 25.764s; }
.m-triptyque > :nth-child(3) .m-bandeau > * { --m-bd: 23.875s; }

/* === 8. Le tracé qui se dessine au défilement ============================
   L'équivalent MAISON du « Lottie scrubbé » — la signature de la référence.
   Chez eux : 32 fichiers JSON, un lecteur tiers, une frame fonction de la
   position dans l'écran (`sync:"Scroll"`, `cursorSyncDirection:"Both"`).
   Chez nous : du SVG à chemins, `stroke-dashoffset` piloté par la MÊME
   fonction (progression de visibilité -> avancement du dessin), RÉVERSIBLE
   comme leur `Both`. Zéro Lottie, zéro lecteur tiers, zéro requête externe.
   La progression est écrite dans --m-p (0 -> 1) par motion-sultane.js. */
.m-trace path,
.m-trace line,
.m-trace circle,
.m-trace polyline {
  stroke-dasharray: var(--m-long, 1000);
  stroke-dashoffset: calc(var(--m-long, 1000) * (1 - var(--m-p, 1)));
}

/* === 9. Les animations liées au défilement (ViewTimeline natif) ==========
   Relevé sur la couverture : le bloc de titre `scale 0.3 -> 1`, les 5 cartes
   de chapitre `scale 0.80 -> 0.25` en traversant l'écran. API NATIVE, aucune
   librairie.

   DÉCISION assumée (M30) : la référence emploie `animation-timeline: view()`.
   Nous reprenons la MÉCANIQUE (le scale est fonction de la progression de
   visibilité, pas de l'horloge) mais nous la pilotons dans NOTRE boucle, pas
   par la timeline native. Deux raisons, dites franchement :
     1. une animation liée au défilement ne se recalcule pas dans un onglet
        caché — donc elle serait INVÉRIFIABLE à la sonde, et on livrerait une
        mécanique sur parole (mesuré : scale figé à 0,55 quel que soit le
        défilement) ;
     2. le pilotage maison marche aussi sur les navigateurs sans ViewTimeline,
        sans avoir à maintenir deux chemins de code.
   Le résultat visuel est le même ; c'est la source de la progression qui change.
   Voir motion-sultane.js -> majDescentes(). */
.m-actif .m-descente-titre,
.m-actif .m-descente-carte { will-change: transform; }

/* === En prime : la révélation par découpe ================================
   Relevé : le bloc se dévoile du bas vers le haut SANS BOUGER.
   « Plus élégant qu'un fondu pour une image. » */
@keyframes m-clipUp {
  from { clip-path: var(--m-clip-ferme); opacity: 1; }
  to   { clip-path: var(--m-clip-ouvert); opacity: 1; }
}
.m-actif [data-m="clip"][data-m-vu] {
  animation: m-clipUp var(--m-d, var(--m-fade-4)) var(--m-e1) both;
}

/* ---------------------------------------------------------------------------
   prefers-reduced-motion : ÉTAT FINAL POSÉ, AUCUN MOUVEMENT.
   Règle transverse du PLAN LISEUSE. On ne se contente pas de couper les
   animations (ce qui laisserait les états de repos à opacity:0 = page vide) :
   on force l'état d'arrivée de chaque famille.
   ------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .m-actif [data-m],
  .m-actif [data-m][data-m-vu],
  .m-actif [data-m-souffle],
  .m-actif .m-ouverture,
  .m-actif .m-descente-titre,
  .m-actif .m-descente-carte,
  .m-bandeau > * {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
    clip-path: var(--m-clip-ouvert) !important;
  }
  /* le tracé est montré ENTIER, pas à moitié dessiné */
  .m-trace path,
  .m-trace line,
  .m-trace circle,
  .m-trace polyline {
    stroke-dashoffset: 0 !important;
  }
}
