/* ══════════════════════════════════════════════════════════════════════
   Le mouvement.

   Le geste et le retard de chaque objet sont écrits dans la page par
   outils/construire.py (`data-rv`, `--rvd`) ; on ne règle ici que la façon
   dont chaque geste se joue.

   Quatre règles :
   1. On guide l'œil dans l'ordre de lecture, on ne le distrait jamais.
   2. On ne fait jamais attendre : un geste dure moins d'une seconde, la
      page entière se pose en moins de quatre, et la navigation reste
      instantanée même en plein mouvement.
   3. On ne casse rien : on anime `translate`, `scale`, `rotate`, `opacity`,
      `filter` et `clip-path`, jamais `transform`, qui porte déjà
      l'inclinaison de certains objets de la maquette.
   4. Le cadre ne bouge pas. Le pied de page, commun à toutes les pages,
      n'a aucun geste : d'une page à l'autre il reste immobile, et c'est
      le contenu qui change.
   ══════════════════════════════════════════════════════════════════════ */

:root{
  --anim-page:      560ms;   /* fondu d'une page à la suivante              */
  --anim-elem:      900ms;   /* durée de référence d'un geste               */
  --anim-courbe:    cubic-bezier(.16, 1, .3, 1);     /* départ franc, fin douce */
  --anim-ressort:   cubic-bezier(.34, 1.45, .64, 1); /* un léger dépassement    */
  --anim-douce:     cubic-bezier(.45, 0, .2, 1);
}

/* Le retard s'écrit en millisecondes dans la page. En revenant en arrière,
   la page a déjà été lue : elle se pose trois fois plus vite (`--tempo`,
   réglé par js/deck.js). */
.slide{ --tempo:1; }
[data-rv]{ --d: calc(var(--rvd, 0ms) * var(--tempo)); }

/* ─────────────────────────── Le changement de page ─────────────────────
   La page qui arrive se pose par-dessus celle qui part, en fondu. Celle
   qui part ne bouge pas et reste entière dessous jusqu'au bout : rien ne
   s'assombrit au passage, et le pied de page, identique sur les deux,
   ne cille pas. */
.slide{
  z-index:0;
  transition:opacity var(--anim-page) var(--anim-courbe),
             visibility 0s linear var(--anim-page);
}
.slide.est-active{
  z-index:2;
  transition:opacity var(--anim-page) var(--anim-courbe), visibility 0s;
}
.slide.est-sortante{ z-index:1; opacity:1; visibility:visible; transition:none; }

/* ─────────────────────────── Le chargement ─────────────────────────────
   Une image de fond se télécharge même sur une page cachée : sans cette
   règle, les 22 pages partiraient ensemble à l'ouverture. Une page ne
   reçoit ses images que lorsque js/deck.js la marque `est-chargee` : la
   page affichée et ses voisines d'abord, les autres ensuite, une à une.
   Sans script (`js-anim` absent), tout se charge comme avant. */
@media screen{
  .js-anim .slide:not(.est-chargee) .o--img{ background-image:none !important; }
}

/* ─────────────────────────── L'état de départ ──────────────────────────
   Le masque ne s'applique que si le script tourne (classe posée dans
   l'en-tête) : sans lui, la présentation reste entièrement lisible. */
.js-anim .slide [data-rv]{ opacity:0; }

/* Une même animation sur la page active et sur la page qui sort : la
   seconde continue donc là où elle en était, sans redémarrer ni
   disparaître pendant le fondu. */
.js-anim :is(.est-active, .est-sortante) [data-rv]{
  animation:rv-texte calc(var(--anim-elem) * .9) var(--anim-courbe) var(--d) both;
}

/* Le titre se lève en sortant d'un léger flou : c'est la mise au point. */
.js-anim :is(.est-active, .est-sortante) [data-rv="titre"]{
  animation-name:rv-titre; animation-duration:1050ms;
}
@keyframes rv-titre{
  from{ opacity:0; translate:0 30px; filter:blur(8px); }
  60% { filter:blur(0); }
  to  { opacity:1; translate:0 0; filter:blur(0); }
}

/* Les textes montent de peu : on les lit, on ne les regarde pas arriver. */
@keyframes rv-texte{
  from{ opacity:0; translate:0 16px; }
  to  { opacity:1; translate:0 0; }
}

/* Le logo se met au point. */
.js-anim :is(.est-active, .est-sortante) [data-rv="logo"]{
  animation:rv-logo 1100ms var(--anim-courbe) var(--d) both;
}
@keyframes rv-logo{
  from{ opacity:0; scale:.86; filter:blur(10px); }
  to  { opacity:1; scale:1; filter:blur(0); }
}

/* Un visuel détouré (personnage, objet) se lève et se pose. */
.js-anim :is(.est-active, .est-sortante) [data-rv="visuel"]{
  animation:rv-visuel 1000ms var(--anim-courbe) var(--d) both;
}
@keyframes rv-visuel{
  from{ opacity:0; translate:0 26px; scale:.97; }
  to  { opacity:1; translate:0 0; scale:1; }
}

/* Une photo s'ouvre depuis son centre, comme un diaphragme. */
.js-anim :is(.est-active, .est-sortante) [data-rv="photo"]{
  animation:rv-photo 1100ms var(--anim-courbe) var(--d) both;
}
@keyframes rv-photo{
  from{ opacity:0; clip-path:inset(12% 12% 12% 12% round 24px); scale:1.04; }
  35% { opacity:1; }
  to  { opacity:1; clip-path:inset(0 0 0 0 round 0); scale:1; }
}

/* Ce qui habite un bord de la page entre par ce bord. */
.js-anim :is(.est-active, .est-sortante) [data-rv="gauche"]{
  animation:rv-gauche 1250ms var(--anim-courbe) var(--d) both;
}
@keyframes rv-gauche{ from{ opacity:0; translate:-70px 0; } to{ opacity:1; translate:0 0; } }
.js-anim :is(.est-active, .est-sortante) [data-rv="droite"]{
  animation:rv-droite 1250ms var(--anim-courbe) var(--d) both;
}
@keyframes rv-droite{ from{ opacity:0; translate:70px 0; } to{ opacity:1; translate:0 0; } }
.js-anim :is(.est-active, .est-sortante) [data-rv="bas"]{
  animation:rv-bas 1250ms var(--anim-courbe) var(--d) both;
}
@keyframes rv-bas{ from{ opacity:0; translate:0 90px; } to{ opacity:1; translate:0 0; } }

/* Une carte monte d'un bloc, avec ce qu'elle porte. */
.js-anim :is(.est-active, .est-sortante) [data-rv="carte"]{
  animation:rv-carte 950ms var(--anim-courbe) var(--d) both;
}
@keyframes rv-carte{
  from{ opacity:0; translate:0 34px; scale:.975; }
  to  { opacity:1; translate:0 0; scale:1; }
}

/* Une pastille éclot : un prix, une coche, une étoile, une icône. */
.js-anim :is(.est-active, .est-sortante) :is([data-rv="pastille"], [data-rv="chiffre"]){
  animation:rv-pastille 700ms var(--anim-ressort) var(--d) both;
}
@keyframes rv-pastille{
  from{ opacity:0; scale:.45; }
  45% { opacity:1; }
  to  { opacity:1; scale:1; }
}

/* Un macaron en étoile tourne en éclatant, puis respire. */
.js-anim :is(.est-active, .est-sortante) [data-rv="eclat"]{
  animation:rv-eclat 900ms var(--anim-ressort) var(--d) both,
            eclat-respire 2800ms var(--anim-douce) calc(var(--d) + 900ms) infinite alternate;
}
@keyframes rv-eclat{
  from{ opacity:0; scale:.35; rotate:-16deg; }
  50% { opacity:1; }
  to  { opacity:1; scale:1; rotate:0deg; }
}
@keyframes eclat-respire{ from{ scale:1; } to{ scale:1.045; } }

/* Une bulle de parole gonfle. */
.js-anim :is(.est-active, .est-sortante) [data-rv="bulle"]{
  animation:rv-bulle 800ms var(--anim-ressort) var(--d) both;
}
@keyframes rv-bulle{
  from{ opacity:0; scale:.6; translate:0 14px; }
  50% { opacity:1; }
  to  { opacity:1; scale:1; translate:0 0; }
}

/* Les filets se tracent, les flèches se dessinent dans leur sens.
   On découpe plutôt qu'on étire : le dessin ne se déforme pas, et un
   objet incliné garde son centre de rotation. Le découpage final déborde
   de l'objet, sinon il rognerait un filet dessiné par un contour. */
.js-anim :is(.est-active, .est-sortante) :is([data-rv="trait"], [data-rv="trait-titre"], [data-rv="fleche"]){
  animation:rv-trace 900ms var(--anim-douce) var(--d) both;
}
@keyframes rv-trace{
  from{ opacity:1; clip-path:inset(-20px 100% -20px -20px); }
  to  { opacity:1; clip-path:inset(-20px -20px -20px -20px); }
}
.js-anim :is(.est-active, .est-sortante) [data-rv="trait-titre"]{ animation-duration:1100ms; }
.js-anim :is(.est-active, .est-sortante) [data-rv="trait-v"]{
  animation:rv-trace-v 1300ms var(--anim-douce) var(--d) both;
}
@keyframes rv-trace-v{
  from{ opacity:1; clip-path:inset(-20px -20px 100% -20px); }
  to  { opacity:1; clip-path:inset(-20px -20px -20px -20px); }
}

/* Les taches de couleur se posent lentement, puis respirent. Elles
   habillent la page sans jamais attirer l'œil. */
.js-anim :is(.est-active, .est-sortante) [data-rv="halo"]{
  animation:rv-halo 1800ms ease-out var(--d) both,
            halo-respire 9s var(--anim-douce) 1800ms infinite alternate;
}
@keyframes rv-halo{ from{ opacity:0; scale:.9; } to{ opacity:1; scale:1; } }
@keyframes halo-respire{ from{ scale:1; } to{ scale:1.07; } }

/* ─────────────────────────────── Les boutons ───────────────────────────
   Le bouton arrive en dernier, une fois lu ce qu'il propose. Il bat deux
   fois, au moment où la main le touche ; puis il respire lentement tant
   que la page est à l'écran, pour qu'on comprenne qu'il se clique. */
.js-anim :is(.est-active, .est-sortante) [data-rv="bouton"]{
  animation:rv-bouton 850ms var(--anim-courbe) var(--d) both,
            bouton-bat 1100ms var(--anim-douce) calc(var(--d) + 850ms) 2,
            bouton-respire 2600ms var(--anim-douce) calc(var(--d) + 3100ms) infinite;
}
@keyframes rv-bouton{
  from{ opacity:0; translate:0 22px; }
  to  { opacity:1; translate:0 0; }
}
@keyframes bouton-bat{
  0%, 40%, 100%{ scale:1; }
  20%          { scale:1.06; }
}
@keyframes bouton-respire{
  0%, 100%{ filter:drop-shadow(0 3px 6px rgba(20, 40, 60, .12)); }
  50%     { filter:drop-shadow(0 9px 18px rgba(20, 40, 60, .26)); }
}

/* La main qui pointe le bouton arrive de biais, puis appuie à chaque
   battement : la cause précède l'effet d'un souffle. */
.js-anim :is(.est-active, .est-sortante) [data-rv="clic"]{
  animation:rv-clic 750ms var(--anim-courbe) var(--d) both,
            clic-appui 1100ms var(--anim-douce) calc(var(--d) + 690ms) 2;
}
@keyframes rv-clic{
  from{ opacity:0; translate:34px 40px; }
  to  { opacity:1; translate:0 0; }
}
@keyframes clic-appui{
  0%, 36%, 100%{ scale:1; }
  16%          { scale:.84; }
}

/* Au survol, le bouton se soulève. Le libellé porte le lien et couvre tout
   le bouton : c'est lui qu'on survole, et toute la page sait qu'il l'est. */
.cta{ cursor:pointer; text-decoration:none; }
.cta:focus-visible{ outline:3px solid #d20074; outline-offset:6px; border-radius:14px; }
[data-bouton]{ transition:scale .3s var(--anim-ressort); }
.slide:has(.cta:hover) [data-bouton]{ scale:1.05; }
.slide:has(.cta:active) [data-bouton]{ scale:.97; transition-duration:.08s; }

/* Les coordonnées des pages de clôture se cliquent. */
.lien-texte{ cursor:pointer; text-decoration:none; }
.lien-texte span{ transition:color .2s; text-decoration-thickness:1.5px; text-underline-offset:3px; }
.lien-texte:hover span{ text-decoration-line:underline; }
.lien-texte:focus-visible{ outline:2px solid #d20074; outline-offset:3px; border-radius:4px; }

/* ─────────────────────────────── Les compteurs ─────────────────────────
   Le chiffre se pose sur sa valeur : un chiffre qui s'arrête se retient
   mieux qu'un chiffre posé. */
[data-count]{ font-variant-numeric:tabular-nums; }

/* ─────────────────────── Préférences et impression ───────────────────── */
@media (prefers-reduced-motion:reduce){
  :root{ --anim-page:180ms; }
  .js-anim :is(.est-active, .est-sortante) [data-rv]{
    animation:rv-fondu 220ms linear calc(var(--d) * .25) both !important;
  }
  [data-bouton]{ transition:none; }
}
@keyframes rv-fondu{ from{ opacity:0; } to{ opacity:1; } }

@media print{
  .js-anim .slide [data-rv]{ opacity:1 !important; animation:none !important; }
}
