/* ============================================================
   anim.css - mouvements, arrivees, celebrations
   Regle : rien ne clignote en rouge, rien ne secoue, rien ne
   signale un manque. On anime la reussite, jamais l'absence.
   ============================================================ */

@keyframes monte{
  from{opacity:0;transform:translateY(16px)}
  to{opacity:1;transform:none}
}
@keyframes pop{
  0%{transform:scale(.7);opacity:0}
  60%{transform:scale(1.08)}
  100%{transform:scale(1);opacity:1}
}
@keyframes tampon{
  0%{transform:scale(2.4) rotate(-18deg);opacity:0}
  55%{transform:scale(.88) rotate(4deg);opacity:1}
  100%{transform:scale(1) rotate(0);opacity:1}
}
@keyframes respire{
  0%,100%{transform:scale(1)}
  50%{transform:scale(1.045)}
}
@keyframes flotte{
  0%,100%{transform:translateY(0)}
  50%{transform:translateY(-7px)}
}
/* Onde composee par le GPU. L'ancienne version animait un box-shadow,
   donc un repaint par image, sur un element d'une grille de 36. */
@keyframes onde{
  0%{transform:scale(1);opacity:.5}
  70%{transform:scale(1.5);opacity:0}
  100%{transform:scale(1.5);opacity:0}
}
@keyframes glisse{
  from{opacity:0;transform:translateX(22px)}
  to{opacity:1;transform:none}
}
@keyframes tourne{to{transform:rotate(360deg)}}
@keyframes ouvre-couvercle{
  0%{transform:rotate(0)}
  30%{transform:rotate(-8deg)}
  100%{transform:rotate(-52deg) translateY(-6px)}
}
@keyframes etincelle{
  0%{transform:scale(0) rotate(0);opacity:0}
  40%{transform:scale(1.2) rotate(90deg);opacity:1}
  100%{transform:scale(0) rotate(180deg);opacity:0}
}

/* ---------- arrivees en cascade ---------- */
/* Une carte qui arrive grandit tres legerement en plus de monter :
   c'est ce detail qui la fait « se poser » au lieu de « glisser ». */
@keyframes pose{
  from{opacity:0;transform:translateY(16px) scale(.985)}
  to{opacity:1;transform:none}
}
.arrive > *{animation:pose .5s var(--doux) both}
.arrive > *:nth-child(1){animation-delay:.02s}
.arrive > *:nth-child(2){animation-delay:.08s}
.arrive > *:nth-child(3){animation-delay:.14s}
.arrive > *:nth-child(4){animation-delay:.2s}
.arrive > *:nth-child(5){animation-delay:.26s}
.arrive > *:nth-child(6){animation-delay:.32s}
.arrive > *:nth-child(n+7){animation-delay:.38s}

/* Un cran de profondeur en plus, sur les ecrans qui sont des listes :
   les cartes s'y posaient toutes ensemble. Plafonne a quatre - au dela
   l'attente cesse d'etre un plaisir. */
.arrive .coffre-carte,
.arrive .saison-bloc,
.arrive .pliant{animation:pose .5s var(--doux) both}
.arrive .coffre-carte:nth-of-type(1),
.arrive .saison-bloc:nth-of-type(1),
.arrive .pliant:nth-of-type(1){animation-delay:.06s}
.arrive .coffre-carte:nth-of-type(2),
.arrive .saison-bloc:nth-of-type(2),
.arrive .pliant:nth-of-type(2){animation-delay:.13s}
.arrive .coffre-carte:nth-of-type(3),
.arrive .saison-bloc:nth-of-type(3),
.arrive .pliant:nth-of-type(3){animation-delay:.2s}
.arrive .coffre-carte:nth-of-type(n+4),
.arrive .saison-bloc:nth-of-type(n+4),
.arrive .pliant:nth-of-type(n+4){animation-delay:.27s}

/* ---------- elements vivants ---------- */
.arrive .carte-defi .numero{animation:glisse .5s .15s var(--doux) both}

/* La semaine qui arrive apres un glissement - la carte et le rituel du
   soir -, du cote d'ou elle vient (7.0.0, voir brancherGlissement dans
   ui/accueil.js). A plat, sans inclinaison : deux blocs penches chacun
   autour de son centre se lisaient comme deux cartes qui se separent.
   `backwards` et SURTOUT PAS `both` : une animation qui garde son etat
   final l'emporte sur le style inline, et la semaine ne suivrait plus
   le doigt au glissement d'apres - figee sur `transform:none`. */
@keyframes glisse-entre-droite{
  from{opacity:0;transform:translateX(38%)}
  to{opacity:1;transform:none}
}
@keyframes glisse-entre-gauche{
  from{opacity:0;transform:translateX(-38%)}
  to{opacity:1;transform:none}
}
.ecran-jeu .glisse-entre-droite{animation:glisse-entre-droite .26s var(--doux) backwards}
.ecran-jeu .glisse-entre-gauche{animation:glisse-entre-gauche .26s var(--doux) backwards}

.soir.vient-de-cocher{animation:pop .42s var(--ressort)}
.soir.vient-de-cocher .etat{animation:pop .34s .06s var(--ressort)}

/* ---------- la ligne de lanternes ---------- */
@keyframes filet-lumiere{
  from{background-position:-60% 0;opacity:0}
  22%{opacity:1}
  to{background-position:160% 0;opacity:0}
}
@keyframes flamme-monte{
  0%{transform:scale(1)}
  45%{transform:scale(1.22) translateY(-2px)}
  100%{transform:scale(1)}
}
/* la flamme vacille, tres legerement, comme une vraie */
@keyframes flamme-vacille{
  0%,100%{transform:scale(1) rotate(0)}
  35%{transform:scale(1.04,.97) rotate(-2.5deg)}
  70%{transform:scale(.98,1.03) rotate(2deg)}
}
.soir.coche .flamme{
  transform-origin:center bottom;
  animation:flamme-vacille 2.6s ease-in-out infinite;
  animation-delay:calc(var(--i, 0) * 320ms);
}

.ruban-fait{animation:pop .5s var(--ressort) both}
.ruban-fait .coche{animation:tampon .6s .06s var(--ressort) both}

.arrive .case-plateau .sceau{animation:tampon .55s var(--ressort) both}
.case-plateau.courante{position:relative}
.case-plateau.courante::after{
  content:"";position:absolute;inset:-3px;border-radius:inherit;
  border:2.5px solid color-mix(in srgb,var(--t) 55%,transparent);
  pointer-events:none;
  animation:onde 2.6s ease-in-out infinite;
}

.arrive .jalon.ouvert .rond{animation:pop .5s var(--ressort) both}

/* Trois respirations, pas l'infini. Le bouton se signale a l'arrivee
   sur l'ecran ; passe ce moment, une pulsation permanente cesse d'etre
   une invitation et devient une sollicitation. */
.bouton--grand.appelle{animation:respire 2.4s ease-in-out 3}


.arrive .chiffre b{animation:pop .5s var(--ressort) both}

/* Le jalon d'un coffre qui vient de s'ouvrir : un tampon, puis une
   onde unique. Jamais en boucle - c'est un evenement, pas un etat.

   Les keyframes reprennent le translate(-50%,-50%) qui place le jalon
   sur la barre : `tampon` ne le portait pas, et comme il est joue en
   `both`, le coffre ouvert restait pose en bas a gauche de sa place
   pour le reste de la session.

   Et il ne repasse pas non plus en position:relative : c'etait la
   deuxieme moitie du meme decalage - le jalon quittait alors le
   placement absolu qui le pose sur sa semaine et retombait dans le
   flux, sous la barre. Le absolu de app.css suffit a ::after. */
@keyframes jalon-tampon{
  0%{transform:translate(-50%,-50%) scale(2.4) rotate(-18deg);opacity:0}
  55%{transform:translate(-50%,-50%) scale(.88) rotate(4deg);opacity:1}
  100%{transform:translate(-50%,-50%) scale(1) rotate(0);opacity:1}
}
.arrive .jalon-barre.ouvert:not(.sautille){animation:jalon-tampon .6s var(--ressort) both}
.arrive .jalon-barre.ouvert:not(.sautille)::after{
  content:"";position:absolute;inset:-2px;border-radius:50%;
  border:2px solid color-mix(in srgb,var(--coffre) 60%,transparent);
  animation:onde .62s .18s var(--doux) both;
}

/* ---------- un coffre gagné, pas encore vu ----------
   Il sautille tant qu'on n'est pas allé le voir, puis il se pose. Le
   rebond est court et la pause longue : c'est un appel du coin de
   l'œil, pas un clignotant. */
@keyframes coffre-sautille{
  0%,58%,100%{transform:translate(-50%,-50%) translateY(0) scale(1)}
  8%         {transform:translate(-50%,-50%) translateY(0) scale(1.08,.88)}
  22%        {transform:translate(-50%,-50%) translateY(-7px) scale(.95,1.1)}
  36%        {transform:translate(-50%,-50%) translateY(0) scale(1.1,.9)}
  46%        {transform:translate(-50%,-50%) translateY(-2px) scale(1)}
}
@keyframes coffre-halo{
  0%,58%,100%{opacity:.25;transform:scale(.88)}
  24%        {opacity:.85;transform:scale(1.18)}
}

/* ---------- la barre de l'année, vivante ----------
   Un reflet qui remonte le chemin parcouru, et la couleur qui
   respire. Deux mouvements très lents, aucun compteur. */
/* La bande de lumiere traverse la barre, par transform : c'est le
   compositeur qui la deplace, sans repeindre. Elle part de -60 % (voir
   .barre span::after) et 370 % de sa largeur la sortent par la droite,
   quelle que soit la largeur de la barre. */
@keyframes barre-reflet{
  0%{transform:translateX(0)}
  100%{transform:translateX(370%)}
}
@keyframes barre-respire{
  0%,100%{filter:saturate(1) brightness(1)}
  50%{filter:saturate(1.2) brightness(1.07)}
}

.arrive .pastille{animation:pop .4s var(--ressort) both}

/* ---------- rotation d'ecran ---------- */
#vue.change{animation:monte .34s var(--doux)}

/* ---------- chargement ---------- */
.roue{
  width:44px;height:44px;border-radius:50%;
  border:4px solid var(--trait);border-top-color:var(--automne);
  animation:tourne .9s linear infinite;
}

/* ---------- celebration plein ecran ---------- */
#fete{
  position:fixed;inset:0;z-index:80;display:flex;
  flex-direction:column;align-items:center;justify-content:center;gap:14px;
  padding:32px;text-align:center;
  background:color-mix(in srgb,var(--fond) 94%,transparent);
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
  animation:monte .3s var(--doux);
}
/* ---------- le sceau de cire ---------- */
/* Le placement et l'echelle vivent sur le conteneur, l'ecrasement de
   la cire sur le groupe interieur : une animation qui pose un
   transform ecraserait l'attribut transform d'un element SVG. */
.sceau{
  width:var(--c,132px);height:var(--c,132px);
  display:flex;align-items:center;justify-content:center;
  animation:tampon .7s var(--ressort) both;
}
.sceau svg{display:block;overflow:visible}
/* la cire s'aplatit en touchant le papier, puis reprend son galbe */
@keyframes cire-ecrase{
  0%{transform:scale(1.06,.9)}
  45%{transform:scale(.98,1.04)}
  100%{transform:scale(1,1)}
}
.sceau-galette{
  transform-box:fill-box;transform-origin:center;
  animation:cire-ecrase .38s .32s var(--doux) both;
}
/* l'empreinte se creuse un instant apres la cire */
@keyframes empreinte-creuse{
  0%{opacity:0;transform:scale(1.3)}
  100%{opacity:1;transform:scale(1)}
}
.sceau-empreinte{
  transform-box:fill-box;transform-origin:center;
  animation:empreinte-creuse .34s .46s var(--doux) both;
}

#fete .medaille{
  width:132px;height:132px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;font-size:4rem;
  background:var(--saison-clair);
  box-shadow:0 0 0 10px color-mix(in srgb,var(--saison) 14%,transparent);
  animation:tampon .7s var(--ressort) both;
}
#fete h2{font-size:2rem;animation:monte .5s .18s var(--doux) both}
#fete p{color:var(--encre-douce);max-width:340px;animation:monte .5s .28s var(--doux) both}
#fete .bouton{margin-top:12px;animation:monte .5s .4s var(--doux) both}

/* coffre qui s'ouvre */
/* ============================================================
   Le coffre qui s'ouvre
   ------------------------------------------------------------
   Il s'ouvrait posément : le couvercle pivotait, une lueur
   apparaissait. C'était juste, et sans effet. Un coffre plein à
   craquer, lui, ne s'ouvre pas — il cède.

   Trois temps, en une seconde et demie :
     0 → .85s   il tremble, de plus en plus fort, comme si ce qu'il y
                a dedans poussait pour sortir ;
     .85s       le couvercle part d'un coup, en arrière et en l'air ;
     .9s        la lumière jaillit, et les pièces avec (le jet de
                pièces est tiré par anim.js, sur la toile plein écran).

   Le tremblement monte en amplitude au lieu d'être régulier : c'est ce
   qui fait attendre quelque chose, et donc ce qui fait sursauter
   quand ça arrive.
   ============================================================ */
@keyframes coffre-tremble{
  0%   {transform:translate(0,0) rotate(0)}
  12%  {transform:translate(-1px,0) rotate(-.6deg)}
  24%  {transform:translate(1px,-1px) rotate(.7deg)}
  36%  {transform:translate(-2px,0) rotate(-1.2deg)}
  48%  {transform:translate(2px,-1px) rotate(1.3deg)}
  60%  {transform:translate(-3px,1px) rotate(-2deg)}
  70%  {transform:translate(3px,-2px) rotate(2.2deg)}
  78%  {transform:translate(-4px,1px) rotate(-2.8deg)}
  86%  {transform:translate(4px,-2px) rotate(3deg)}
  93%  {transform:translate(-2px,2px) rotate(-1.4deg) scale(1.04)}
  100% {transform:translate(0,0) rotate(0) scale(1)}
}

/* ------------------------------------------------------------
   Le coffre de la fête
   ------------------------------------------------------------
   C'était un coffre VIOLET dessiné ici en CSS : trois rectangles et
   un rond, qui n'existait nulle part ailleurs dans le carnet. Un
   enfant qui gagnait un coffre en voyait donc un qu'il ne
   retrouverait jamais dans l'onglet Coffres.

   C'est maintenant le coffre du carnet — celui de ui/icones.js, avec
   ses ferrures, sa serrure, ses pièces et ses deux gemmes. Il arrive
   FERMÉ : il tremble, puis anim.js lui pose .ouvert à la fin du
   tremblement, et c'est .coffre-illu.ouvert (plus bas) qui ouvre le
   couvercle, fait sortir la lumière et monter le trésor.
   ------------------------------------------------------------ */
.coffre-fete{
  width:214px;max-width:62vw;margin-bottom:2px;
  animation:coffre-tremble .85s ease-in-out both;
}
.coffre-fete .coffre-illu{width:100%;height:auto;display:block;overflow:visible}

/* ============================================================
   Les compagnons sont vivants
   ------------------------------------------------------------
   Un dessin qui cligne des yeux vous regarde ; un dessin fixe est
   regardé. C'est toute la différence, et elle coûte deux keyframes.

   Le clignement est volontairement irrégulier d'un compagnon à
   l'autre : six paupières qui tombent en même temps feraient une
   horloge. Le décalage vient de --cligne, posé par figurines.js.
   ============================================================ */
@keyframes cligne{
  0%, 92%, 100%{transform:scaleY(0)}
  95%, 97%{transform:scaleY(1)}
}
.fig-paupiere{
  fill:currentColor;
  transform-box:fill-box;transform-origin:center top;
  transform:scaleY(0);
  animation:cligne 5.4s var(--cligne,0s) ease-in-out infinite;
}
/* La paupière prend la couleur de la tête : posée dans le <svg>, elle
   hérite du `color` que figurines.js met sur la figurine entière. */
.figurine{color:#8FD0A0}

/* ============================================================
   Le compagnon de l'entête, dans sa bulle
   ------------------------------------------------------------
   Il est visible depuis TOUS les écrans : c'est le seul dessin que
   l'enfant a constamment sous les yeux. Il se contentait de respirer.

   Il a maintenant trois petites bêtises, prises dans un cycle de
   quinze secondes dont les deux tiers restent immobiles — sans quoi
   une figurine qui s'agite en permanence dans un coin devient un
   clignotant qu'on finit par ne plus voir :

     1. le coup d'œil — il penche la tête à gauche, puis à droite,
        comme s'il regardait ce qui se passe autour ;
     2. les trois rebonds — trois bonds de plus en plus petits, avec
        l'écrasement à la réception qui donne le poids ;
     3. l'approche — il s'avance vers la vitre, nous regarde de près
        en penchant la tête, puis recule.

   Le troisième gag était une plongée : il sortait par le bas de la
   bulle et disparaissait une seconde. Ça marchait techniquement (la
   bulle est en overflow:hidden, elle l'avalait pour de vrai) mais ça
   attirait trop l'œil : un dessin qui disparaît d'un écran fait lever
   la tête, alors que ce coin doit rester du décor. L'approche occupe
   le même temps sans jamais quitter le cadre — les pieds seuls
   débordent en bas, ce qui suffit à donner la proximité.

   La montée d'échelle plafonne à 1.22, mesurée sur les six
   compagnons dans la bulle de 46 px : au-delà, les oreilles du cheval
   touchent le haut du cercle et les roues de la voiture sortent par le
   bas — un véhicule dont les roues sont coupées se lit comme un bug,
   pas comme une approche.

   Aucun n'est celui du plateau (dandinement + pirouette) : deux
   dessins identiques qui font le même geste dans le même écran se
   lisent comme une erreur.

   Le pivot est aux pieds : un personnage debout se penche depuis le
   sol, pas depuis le nombril.
   ============================================================ */
@keyframes compagnon-bulle{
  /* il respire */
  0%   {transform:translateY(0)      rotate(0)      scale(1,1)}
  6%   {transform:translateY(-1.5px) rotate(0)      scale(1,1.012)}
  12%  {transform:translateY(0)      rotate(0)      scale(1,1)}
  18%  {transform:translateY(-1.5px) rotate(0)      scale(1,1.012)}
  24%  {transform:translateY(0)      rotate(0)      scale(1,1)}

  /* 1. le coup d'œil */
  27%  {transform:translateY(0)      rotate(-12deg) scale(1,1)}
  30.5%{transform:translateY(0)      rotate(-12deg) scale(1,1)}
  33.5%{transform:translateY(0)      rotate(11deg)  scale(1,1)}
  37%  {transform:translateY(0)      rotate(11deg)  scale(1,1)}
  39%  {transform:translateY(0)      rotate(0)      scale(1,1)}

  /* il respire */
  45%  {transform:translateY(-1.5px) rotate(0)      scale(1,1.012)}
  51%  {transform:translateY(0)      rotate(0)      scale(1,1)}

  /* 2. les trois rebonds */
  53%  {transform:translateY(2px)    rotate(0)      scale(1.1,.9)}
  56%  {transform:translateY(-8px)   rotate(0)      scale(.94,1.06)}
  58%  {transform:translateY(1px)    rotate(0)      scale(1.08,.92)}
  60%  {transform:translateY(-5px)   rotate(0)      scale(.96,1.04)}
  62%  {transform:translateY(1px)    rotate(0)      scale(1.06,.94)}
  63.5%{transform:translateY(-3px)   rotate(0)      scale(.98,1.02)}
  65%  {transform:translateY(0)      rotate(0)      scale(1,1)}

  /* il respire */
  71%  {transform:translateY(-1.5px) rotate(0)      scale(1,1.012)}
  77%  {transform:translateY(0)      rotate(0)      scale(1,1)}

  /* 3. l'approche : il se recule d'un cheveu pour prendre appui,
     s'avance jusqu'à la vitre, nous dévisage en penchant la tête
     d'un côté puis de l'autre, et repart en arrière. */
  79%  {transform:translateY(1px)    rotate(0)      scale(.97)}
  84%  {transform:translateY(3px)    rotate(0)      scale(1.22)}
  87.5%{transform:translateY(3px)    rotate(-5deg)  scale(1.22)}
  91%  {transform:translateY(3px)    rotate(4deg)   scale(1.22)}
  94%  {transform:translateY(3px)    rotate(0)      scale(1.22)}
  98%  {transform:translateY(-1px)   rotate(0)      scale(.98,1.02)}
  100% {transform:translateY(0)      rotate(0)      scale(1,1)}
}
.figurine--vivante{
  animation:compagnon-bulle 15s ease-in-out infinite;
  transform-origin:50% 100%;
}

/* ---------- le pion du plateau ----------
   Sur le plateau, le compagnon marque la case où l'on en est. Il ne
   reste pas planté : il se dandine, puis il saute en faisant un tour
   sur lui-même, puis il se repose - et ça recommence, tranquillement.

   Les deux tiers du cycle sont immobiles, à dessein : une figurine qui
   bouge sans arrêt dans une grille de 36 cases devient un clignotant.
   La pirouette tourne autour de l'axe vertical (rotateY) : le
   personnage reste debout, alors qu'un rotate le mettrait la tête en
   bas au milieu du tour.

   Le cycle n'a plus de temps mort : il y avait deux tiers d'immobilité
   entre deux pirouettes, et c'était une prudence excessive. Un seul
   pion bouge sur le plateau - celui de la semaine en cours - donc ça
   ne fait pas un clignotant, ça fait un compagnon qui attend. Le
   mouvement change en revanche de nature tout du long : dandinement,
   saut, réception, balancement. Une boucle où l'on reconnaît le même
   geste toutes les deux secondes fatiguerait bien plus vite. */
@keyframes pion-danse{
  /* il se dandine, d'un pied sur l'autre, sans jamais s'arrêter */
  0%   {transform:perspective(240px) translateY(0) rotate(0) rotateY(0)}
  6%   {transform:perspective(240px) translateY(-4px) rotate(-7deg)}
  12%  {transform:perspective(240px) translateY(0) rotate(6deg) scale(1.04,.96)}
  18%  {transform:perspective(240px) translateY(-4px) rotate(-6deg)}
  24%  {transform:perspective(240px) translateY(0) rotate(5deg) scale(1.04,.96)}
  30%  {transform:perspective(240px) translateY(-3px) rotate(-4deg)}
  /* il s'accroupit, saute, fait son tour sur lui-même, se reçoit */
  36%  {transform:perspective(240px) translateY(3px) rotate(0) scale(1.12,.88)}
  45%  {transform:perspective(240px) translateY(-15px) rotateY(180deg) scale(.94,1.08)}
  54%  {transform:perspective(240px) translateY(-6px) rotateY(340deg)}
  60%  {transform:perspective(240px) translateY(2px) rotateY(360deg) scale(1.14,.86)}
  /* rebond de réception, puis il repart en balançant */
  66%  {transform:perspective(240px) translateY(-6px) rotateY(360deg) rotate(3deg)}
  72%  {transform:perspective(240px) translateY(0) rotateY(360deg) rotate(-5deg)}
  78%  {transform:perspective(240px) translateY(-4px) rotateY(360deg) rotate(5deg)}
  84%  {transform:perspective(240px) translateY(0) rotateY(360deg) rotate(-4deg) scale(1.03,.97)}
  90%  {transform:perspective(240px) translateY(-3px) rotateY(360deg) rotate(3deg)}
  95%  {transform:perspective(240px) translateY(0) rotateY(360deg) rotate(-2deg)}
  100% {transform:perspective(240px) translateY(0) rotateY(360deg) rotate(0)}
}

/* Et il saute quand l'enfant coche un soir : la seule récompense
   qu'il donne, c'est son contentement. */
@keyframes fig-saute{
  0%{transform:translateY(0) scale(1)}
  22%{transform:translateY(-9px) scale(1.07,.95)}
  46%{transform:translateY(-11px) scale(1.02,1.02)}
  74%{transform:translateY(1px) scale(.96,1.05)}
  100%{transform:translateY(0) scale(1)}
}
.figurine--content{animation:fig-saute .72s var(--ressort)}

/* La paire trouvee : un petit sursaut du motif, une fois. Pas de
   halo qui pulse - la carte reste lisible, c'est encore un plateau
   de jeu ou l'on cherche. */
/* La puce d'un mot trouve : elle se raye ET elle tressaille. La
   rature seule se voit mal dans une liste de dix. */
@keyframes mots-trouve{
  0%{transform:scale(1)}
  40%{transform:scale(1.14)}
  100%{transform:scale(1)}
}
@keyframes memo-paire{
  0%{transform:scale(1)}
  45%{transform:scale(1.22)}
  100%{transform:scale(1)}
}

/* ---------- accessibilite ---------- */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important;animation-iteration-count:1 !important;
    transition-duration:.01ms !important;
  }
  /* La regle ci-dessus fige les animations ; elle ne retire pas ce
     qu'elles animaient. Sept feuilles figees a leur premiere image
     resteraient posees en haut de la carte comme des taches, et
     l'eclat de l'Ete garderait son opacite de scintillement. La vie
     de la saison est du mouvement et rien d'autre : sans mouvement,
     elle n'a plus de raison d'etre affichee. */
  .saison-vie{display:none}
}

/* ------------------------------------------------------------
   La vie de la saison : chute, montée, scintillement
   ------------------------------------------------------------
   UNE particule n'est visible que 40 % de son cycle. Le reste du
   temps elle attend, immobile et transparente, en bas de la carte —
   d'où les deux dernières étapes identiques, qui la figent au lieu
   de la faire remonter en glissant.

   C'est ce qui donne « quelques feuilles » : sept particules aux
   phases étalées, deux ou trois à l'écran à la fois. Sans cette
   pause, les sept tombaient en permanence et la carte devenait un
   rideau — exactement ce qu'on ne veut pas d'un rappel.

   `100cqh` est la hauteur de la couche (cf. `container-type:size`
   dans app.css), donc la hauteur de la carte : une feuille traverse
   toujours tout le défi, qu'il tienne en deux lignes ou en six.

   La dérive `--dr` change de signe entre le début et la fin : la
   feuille part d'un côté et arrive de l'autre. C'est ce demi-tour,
   avec la rotation, qui fait la différence entre une feuille qui
   descend et un objet qui tombe.
   ------------------------------------------------------------ */
@keyframes sv-chute{
  0%      {opacity:0;
           transform:translate(calc(-50% - var(--dr)), -18px) rotate(0) scale(var(--s))}
  5%      {opacity:var(--op)}
  33%     {opacity:var(--op)}
  40%,100%{opacity:0;
           transform:translate(calc(-50% + var(--dr)), 100cqh) rotate(var(--t)) scale(var(--s))}
}
@keyframes sv-montee{
  0%      {opacity:0;
           transform:translate(calc(-50% + var(--dr)), 100cqh) rotate(0) scale(calc(var(--s) * .7))}
  7%      {opacity:var(--op)}
  31%     {opacity:var(--op)}
  40%,100%{opacity:0;
           transform:translate(calc(-50% - var(--dr)), -14px) rotate(var(--t)) scale(var(--s))}
}
/* Le creux du scintillement ne descend pas plus bas que .62, et
   c'est mesure : les deux opacites se multiplient, donc un éclat à
   .44 tombait à .18 en bas de course — c'est-à-dire qu'il
   disparaissait complètement une seconde sur deux, au lieu de
   respirer. */
@keyframes sv-scintille{
  0%,100% {opacity:.62;transform:scale(.86)}
  50%     {opacity:1;transform:scale(1.12)}
}

/* ---------- scenes de l'intro ---------- */
@keyframes case-allume{
  0%,6%{opacity:.22;transform:scale(.86)}
  14%{opacity:1;transform:scale(1.14)}
  20%,74%{opacity:1;transform:scale(1)}
  90%,100%{opacity:.22;transform:scale(.86)}
}
@keyframes soir-anime{
  0%,8%{background:var(--papier);border-color:var(--trait);color:var(--gris);transform:scale(1)}
  16%{background:var(--printemps);border-color:var(--printemps);color:#fff;transform:scale(1.1)}
  22%,76%{background:var(--printemps);border-color:var(--printemps);color:#fff;transform:scale(1)}
  90%,100%{background:var(--papier);border-color:var(--trait);color:var(--gris);transform:scale(1)}
}
@keyframes coche-anime{
  0%,10%{opacity:0;transform:scale(.4)}
  18%{opacity:1;transform:scale(1.2)}
  24%,76%{opacity:1;transform:scale(1)}
  88%,100%{opacity:0;transform:scale(.4)}
}

/* ------------------------------------------------------------
   Le geste qui pose le carnet sur l'écran d'accueil
   ------------------------------------------------------------
   UN cycle de 5 s, partagé par les cinq animations, et l'ordre est
   tout. TROIS temps, pas deux :

     0 → 26 %   le halo bat sur le bouton à toucher
     28 → 63 %  le menu s'ouvre, la bonne ligne se surligne
     63 → 99 %  l'écran d'accueil, et l'icône qui s'y pose

   Le troisième a été ajouté après coup, demandé par Xavier : la scène
   s'arrêtait sur le dernier GESTE et ne montrait jamais le RÉSULTAT.
   Une consigne qui s'arrête avant son résultat demande de faire
   confiance ; celle-ci montre l'icône qu'on ira chercher du doigt le
   lendemain soir.

   LE CYCLE FAIT 5 s, et il en a fait 9 pendant une version : trop
   long, tranché par Xavier après l'avoir regardé tourner. Une boucle
   qu'on découvre au milieu d'un temps mort donne l'impression d'un
   dessin figé, et personne n'attend neuf secondes devant une
   illustration d'onboarding.

   Les proportions n'ont PAS été divisées à l'identique en
   raccourcissant : le halo a cédé du terrain (34 % → 26 %) au profit
   des deux temps qui portent du TEXTE à lire, le menu et le bureau.
   Un halo se comprend en un battement, un libellé demande le temps
   d'être lu. C'est la règle à garder si l'on retouche encore la
   durée : ce qui se lit ne se raccourcit pas comme ce qui se voit.

   Montrer deux temps EN MÊME TEMPS a été essayé et ne racontait rien :
   deux choses clignotaient, on ne savait pas laquelle était la
   première. Un geste se montre dans son ordre, sinon c'est un schéma.

   Le halo bat deux fois plutôt qu'une : une pulsation isolée se
   confond avec une transition d'arrivée.
   ------------------------------------------------------------ */
@keyframes ph-halo{
  0%,1%    {opacity:0;transform:scale(.55)}
  6%       {opacity:.9;transform:scale(.78)}
  14%      {opacity:0;transform:scale(1.3)}
  18%      {opacity:.9;transform:scale(.78)}
  26%,100% {opacity:0;transform:scale(1.3)}
}
@keyframes ph-menu-bas{
  0%,28%   {opacity:0;transform:translateY(112%)}
  34%      {opacity:1;transform:translateY(0)}
  58%      {opacity:1;transform:translateY(0)}
  63%,100% {opacity:0;transform:translateY(112%)}
}
@keyframes ph-menu-haut{
  0%,28%   {opacity:0;transform:translateY(-10px) scale(.92)}
  34%      {opacity:1;transform:translateY(0) scale(1)}
  58%      {opacity:1;transform:translateY(0) scale(1)}
  63%,100% {opacity:0;transform:translateY(-10px) scale(.92)}
}
/* La ligne du menu se souligne APRÈS que le menu soit arrivé : le
   surlignage posé pendant la montée passait inaperçu, l'œil suivant
   le mouvement et non la couleur. */
@keyframes ph-surligne{
  0%,36%   {box-shadow:0 0 0 0 transparent}
  41%      {box-shadow:0 0 0 3px var(--saison-fonce)}
  46%      {box-shadow:0 0 0 2px color-mix(in srgb, var(--saison-fonce) 30%, transparent)}
  51%      {box-shadow:0 0 0 3px var(--saison-fonce)}
  63%,100% {box-shadow:0 0 0 2px color-mix(in srgb, var(--saison-fonce) 30%, transparent)}
}
/* Le bureau recouvre l'écran entier : on a quitté le navigateur, c'est
   tout l'intérêt de la manœuvre. Il arrive d'un demi-pas en arrière
   plutôt qu'en fondu pur — un fondu seul se lit comme un chargement. */
@keyframes ph-bureau{
  0%,63%   {opacity:0;transform:scale(1.05)}
  70%      {opacity:1;transform:scale(1)}
  94%      {opacity:1;transform:scale(1)}
  99%,100% {opacity:0;transform:scale(1.03)}
}
/* L'icône se pose EN DERNIER, après que le bureau soit là. Elle
   dépasse sa taille avant de s'y ranger : c'est ce petit excès qui la
   fait « tomber » sur l'écran au lieu d'y apparaître. */
@keyframes ph-pose{
  0%,70%   {opacity:0;transform:scale(.3) translateY(-9px)}
  78%      {opacity:1;transform:scale(1.16) translateY(0)}
  84%,94%  {opacity:1;transform:scale(1) translateY(0)}
  99%,100% {opacity:0;transform:scale(.72) translateY(0)}
}

/* ------------------------------------------------------------
   Le coffre de l'explication : la même ouverture, mais en boucle
   ------------------------------------------------------------
   Le coffre du carnet s'ouvre UNE fois : c'est un événement, et ses
   animations sont en `forwards`. Sur l'écran d'explication, il doit
   respirer — s'ouvrir, montrer ce qu'il a dedans, se refermer, et
   recommencer. Mêmes pièces, mêmes gemmes, même couvercle ; seul le
   minutage change.

   Le couvercle tremble d'abord (0 → 16 %), comme dans la fête : sans
   ce petit sursaut, une rotation qui démarre à froid ressemble à une
   porte de placard.
   ------------------------------------------------------------ */
@keyframes couvercle-boucle{
  0%,12%   {transform:rotate(0)}
  17%      {transform:rotate(5deg)}
  30%,70%  {transform:rotate(-52deg)}
  84%,100% {transform:rotate(0)}
}
@keyframes dedans-boucle{
  0%,16%   {opacity:0}
  24%,74%  {opacity:1}
  84%,100% {opacity:0}
}
@keyframes lueur-boucle{
  0%,22%   {opacity:0;transform:scale(.5)}
  40%,70%  {opacity:.9;transform:scale(1)}
  82%,100% {opacity:0;transform:scale(.6)}
}
@keyframes tresor-boucle{
  0%,24%   {opacity:0;transform:translateY(14px) scale(.7)}
  38%      {opacity:1;transform:translateY(-3px) scale(1.06)}
  46%,72%  {opacity:1;transform:translateY(0) scale(1)}
  82%,100% {opacity:0;transform:translateY(10px) scale(.8)}
}
@keyframes etincelle-boucle{
  0%,30%   {opacity:0;transform:scale(0) rotate(-30deg)}
  42%      {opacity:1;transform:scale(1.2) rotate(10deg)}
  52%,70%  {opacity:.9;transform:scale(1) rotate(0)}
  80%,100% {opacity:0;transform:scale(.4) rotate(0)}
}
.coffre-illu.boucle .couvercle{animation:couvercle-boucle 4.8s var(--doux) infinite}
.coffre-illu.boucle .dedans{animation:dedans-boucle 4.8s linear infinite}
.coffre-illu.boucle .lueur{animation:lueur-boucle 4.8s var(--doux) infinite}
.coffre-illu.boucle .tresor{animation:tresor-boucle 4.8s var(--ressort) infinite}
.coffre-illu.boucle .e1{animation:etincelle-boucle 4.8s var(--ressort) infinite}
.coffre-illu.boucle .e2{animation:etincelle-boucle 4.8s .14s var(--ressort) infinite}
.coffre-illu.boucle .e3{animation:etincelle-boucle 4.8s .28s var(--ressort) infinite}

/* ---------- celebration de fin de semaine ---------- */
@keyframes pion-saute{
  0%{transform:translateY(0) scale(1)}
  25%{transform:translateY(-16px) scale(1.14)}
  55%{transform:translateY(-20px) scale(1.08)}
  80%{transform:translateY(2px) scale(.94)}
  100%{transform:translateY(0) scale(1)}
}
@keyframes case-atteinte{
  0%{transform:scale(1)}
  40%{transform:scale(1.5)}
  100%{transform:scale(1)}
}

/* ---------- ouverture d'un coffre ---------- */
.coffre-illu .couvercle{transform-box:fill-box;transform-origin:6% 88%}
.coffre-illu .lueur,
.coffre-illu .tresor,
.coffre-illu .dedans,
.coffre-illu .etincelles path{opacity:0}

.coffre-illu.ouvert .couvercle{animation:couvercle-ouvre .95s var(--ressort) forwards}
/* l'interieur apparait des que le couvercle bascule : c'est lui qui
   fait de la rotation une ouverture, et pas un simple pivotement */
.coffre-illu.ouvert .dedans{animation:pop .3s .16s var(--doux) forwards}
.coffre-illu.ouvert .lueur{animation:lueur-sort 1.1s .3s var(--doux) forwards}
.coffre-illu.ouvert .tresor{animation:tresor-monte .7s .38s var(--ressort) forwards}
.coffre-illu.ouvert .e1{animation:etincelle-pop 1.1s .45s var(--ressort) forwards}
.coffre-illu.ouvert .e2{animation:etincelle-pop 1.1s .6s var(--ressort) forwards}
.coffre-illu.ouvert .e3{animation:etincelle-pop 1.1s .75s var(--ressort) forwards}

@keyframes couvercle-ouvre{
  0%{transform:rotate(0)}
  22%{transform:rotate(6deg)}
  70%{transform:rotate(-58deg)}
  100%{transform:rotate(-50deg)}
}
@keyframes lueur-sort{
  0%{opacity:0;transform:scale(.5)}
  60%{opacity:1;transform:scale(1.05)}
  100%{opacity:.85;transform:scale(1)}
}
@keyframes tresor-monte{
  0%{opacity:0;transform:translateY(14px) scale(.7)}
  70%{opacity:1;transform:translateY(-3px) scale(1.06)}
  100%{opacity:1;transform:translateY(0) scale(1)}
}
@keyframes etincelle-pop{
  0%{opacity:0;transform:scale(0) rotate(-30deg)}
  45%{opacity:1;transform:scale(1.2) rotate(10deg)}
  100%{opacity:.9;transform:scale(1) rotate(0)}
}
.coffre-illu .tresor,.coffre-illu .etincelles path{transform-box:fill-box;transform-origin:center}

/* ---------- ticket a gratter ---------- */
@keyframes reflet{
  0%{background-position:120% 0}
  55%,100%{background-position:-60% 0}
}
@keyframes tapote{
  0%,100%{transform:translateY(0) rotate(0)}
  35%{transform:translateY(-5px) rotate(-9deg)}
  60%{transform:translateY(1px) rotate(3deg)}
}

/* ---------- scène des jetons Seconde Chance ---------- */
@keyframes jeton-arrive{
  0%,4%{opacity:0}
  14%{opacity:1}
  74%{opacity:1}
  90%,100%{opacity:0}
}

/* ---------- illustration d'accueil ---------- */
@keyframes perso-flotte{
  0%,100%{transform:translateY(0)}
  50%{transform:translateY(-7px)}
}
@keyframes nuage-derive{
  0%{transform:translateX(-70px)}
  100%{transform:translateX(430px)}
}
@keyframes chemin-avance{
  0%{stroke-dashoffset:0}
  100%{stroke-dashoffset:-30}
}
@keyframes etincelle-lueur{
  0%,100%{opacity:.15;transform:scale(.6)}
  50%{opacity:1;transform:scale(1.25)}
}

/* ============================================================
   Le passage de frontière
   ------------------------------------------------------------
   Une scène, un titre, un bouton. Tout est arrivé à 1,5 s, et
   rien ne boucle plus de trois secondes : le passage tombe dans
   une séquence déjà chargée (validation, fête, plateau, ticket),
   au-delà ce serait un péage.
   ============================================================ */
@keyframes fr-monte-crete{
  0%{transform:translate(-26px,16px) scale(.82);opacity:0}
  55%{opacity:1}
  100%{transform:translate(0,0) scale(1);opacity:1}
}
@keyframes fr-astre-lever{
  0%{transform:translateY(26px) scale(.6);opacity:0}
  100%{transform:translateY(0) scale(1);opacity:1}
}
@keyframes fr-grain-passe{
  0%{transform:translate(0,0);opacity:0}
  18%{opacity:.95}
  100%{transform:translate(-46px,58px);opacity:0}
}
@keyframes fr-chemin-avance{
  0%{stroke-dashoffset:0}
  100%{stroke-dashoffset:-30}
}
/* La terre qu'on quitte se retire. Elle tient les 250 premières ms
   pour qu'on la voie, puis s'efface en 900 ms : le temps que le
   compagnon finisse de monter sur la crête. */
@keyframes fr-saison-passe{
  0%, 18%{opacity:1}
  100%{opacity:0}
}

/* La scène ne dépasse jamais la moitié de l'écran : le titre est
   ce qu'on doit lire, le décor n'est là que pour le porter. */
.fr-scene{
  width:min(340px,86vw);border-radius:var(--r-xl);overflow:hidden;
  box-shadow:var(--ombre-l);background:var(--papier);
  animation:pop .6s var(--ressort) both;
}
.fr-scene svg{display:block;width:100%;height:auto}

/* le placement vit sur le groupe extérieur, l'animation sur l'intérieur */
.fr-compagnon{transform-box:fill-box;transform-origin:center bottom}
.fr-compagnon > svg{animation:fr-monte-crete .9s .3s var(--doux) both}
.fr-astre{transform-box:fill-box;transform-origin:center;
  animation:fr-astre-lever 1s .15s var(--doux) both}
.fr-tirets{animation:fr-chemin-avance 2.4s linear infinite}
.fr-grain{transform-box:fill-box;transform-origin:center;
  animation:fr-grain-passe 3.4s var(--d,0s) ease-in infinite}
/* le décor précédent n'intercepte rien, il ne fait que s'effacer */
.fr-avant{pointer-events:none;animation:fr-saison-passe 1.15s .25s var(--doux) both}

/* Le titre passe devant : sur un écran court, la scène se réduit
   plutôt que de pousser le bouton hors de vue. */
#fete.fete-frontiere{gap:10px}
#fete.fete-frontiere h2{font-size:1.7rem;max-width:340px}
@media (max-height:700px){
  #fete.fete-frontiere .fr-scene{width:min(300px,80vw)}
  #fete.fete-frontiere h2{font-size:1.45rem}
}

/* ============================================================
   La scene du tresor : la derniere cote
   ------------------------------------------------------------
   Le compagnon part du bas du chemin et monte jusqu'au coffre en
   1,6 s. C'est exactement le `delaiCoffre` passe a anim.fete() : le
   tremblement du coffre commence quand le compagnon arrive. Les deux
   durees se suivent, elles ne se reglent pas separement - sinon
   l'une derive et le coffre s'ouvre devant un compagnon encore en
   route.

   Deux petits rebonds pendant la montee, pas un glissement : un
   personnage qui translate sans rythme a l'air tire par une ficelle.
   ============================================================ */
@keyframes tr-monte{
  0%{transform:translate(-130px,86px) scale(.72);opacity:0}
  14%{opacity:1}
  32%{transform:translate(-96px,62px) scale(.79)}
  40%{transform:translate(-84px,58px) scale(.8)}
  62%{transform:translate(-48px,30px) scale(.89)}
  70%{transform:translate(-36px,26px) scale(.91)}
  100%{transform:translate(0,0) scale(1);opacity:1}
}
/* le sommet s'allume a mesure qu'on s'en approche */
@keyframes tr-halo-monte{
  0%{transform:scale(.3);opacity:.15}
  70%{transform:scale(.85);opacity:.6}
  100%{transform:scale(1);opacity:1}
}
.tr-scene{
  position:relative;
  width:min(340px,86vw);border-radius:var(--r-xl);overflow:hidden;
  box-shadow:var(--ombre-l);background:var(--papier);
  animation:pop .6s var(--ressort) both;
}
.tr-scene svg{display:block;width:100%;height:auto}
.tr-compagnon{transform-box:fill-box;transform-origin:center bottom}
.tr-compagnon > svg{animation:tr-monte 1.6s var(--doux) both}
.tr-halo{transform-box:fill-box;transform-origin:center;
  animation:tr-halo-monte 1.6s var(--doux) both}

/* Le coffre est pose sur la crete, en pourcentage de la scene : il
   suit donc la largeur de l'ecran sans second reglage. Il attend la
   montee, immobile, puis anim.fete() lui pose `.ouvert`. */
.tr-coffre{
  position:absolute;left:64.5%;top:12%;width:31%;
  animation:coffre-tremble .85s 1.6s ease-in-out both;
}
.tr-coffre .coffre-illu{width:100%;height:auto;display:block;overflow:visible}

/* Comme la frontiere : la scene se resserre plutot que de pousser le
   bouton hors de l'ecran. */
#fete.fete-tresor{gap:10px}
#fete.fete-tresor h2{font-size:1.7rem;max-width:340px}
@media (max-height:700px){
  #fete.fete-tresor .tr-scene{width:min(300px,80vw)}
  #fete.fete-tresor h2{font-size:1.45rem}
}

/* ============================================================
   Les bandeaux de terre : ce qui bouge dans le paysage
   ------------------------------------------------------------
   Des trajets longs et lents, jamais synchronisés : trois feuilles
   qui tombent en même temps feraient une horloge. Chaque élément
   porte son propre --d, posé par icones.js.
   ============================================================ */
@keyframes bt-tombe{
  0%{transform:translate(0,-6px) rotate(0);opacity:0}
  14%{opacity:.95}
  100%{transform:translate(-26px,42px) rotate(160deg);opacity:0}
}
@keyframes bt-descend{
  0%{transform:translate(0,-6px);opacity:0}
  18%{opacity:1}
  100%{transform:translate(-14px,44px);opacity:0}
}
@keyframes bt-derive{
  0%{transform:translate(0,0);opacity:0}
  20%{opacity:.95}
  100%{transform:translate(-20px,38px);opacity:0}
}
@keyframes bt-scintille{
  0%,100%{transform:translate(0,0) scale(.7);opacity:.25}
  50%{transform:translate(6px,-4px) scale(1.15);opacity:1}
}
.bt-feuille{transform-box:fill-box;transform-origin:center;
  animation:bt-tombe 7s var(--d,0s) linear infinite}
.bt-flocon{transform-box:fill-box;transform-origin:center;
  animation:bt-descend 8.5s var(--d,0s) linear infinite}
.bt-petale{transform-box:fill-box;transform-origin:center;
  animation:bt-derive 7.5s var(--d,0s) linear infinite}
.bt-luciole{transform-box:fill-box;transform-origin:center;
  animation:bt-scintille 3.4s var(--d,0s) ease-in-out infinite}

/* ---------- l'écran de chargement ---------- */
/* UNE ANNEE EN 7 SECONDES.
   ------------------------------------------------------------
   Le ciel et le sol portent DEUX annees bout a bout (2 x 1680 px) et
   glissent d'exactement 1680 : arrivee au bout, la bande est au pixel
   pres dans l'etat ou elle a commence, donc la boucle ne se voit pas.
   Les ondulations du sol ont des periodes qui divisent 1680 (420 et
   560) pour la meme raison.

   LES SIX ANIMATIONS CI-DESSOUS SONT SOLIDAIRES : le decor, le halo
   et les quatre meteos partagent la meme duree. En changer une seule
   ferait tomber des feuilles en plein hiver. */
/* C'est la POSITION DU FOND qui avance, pas l'element : voir le
   commentaire de .ch-ciel dans app.css. Une annee par tour. */
@keyframes ch-defile{
  0%{background-position-x:0}
  100%{background-position-x:-1680px}
}
/* le halo derriere la marque prend la couleur de la terre traversee */
@keyframes ch-halo{
  0%,  6% {background-color:#E0561F}
  26%,32% {background-color:#1F76A0}
  51%,57% {background-color:#3F8F4F}
  76%,82% {background-color:#D99512}
  100%    {background-color:#E0561F}
}
/* chaque meteo n'est visible que pendant son quart de cycle */
@keyframes ch-w1{0%{opacity:1}20%{opacity:1}26%{opacity:0}94%{opacity:0}100%{opacity:1}}
@keyframes ch-w2{0%{opacity:0}20%{opacity:0}26%{opacity:1}45%{opacity:1}51%{opacity:0}100%{opacity:0}}
@keyframes ch-w3{0%{opacity:0}45%{opacity:0}51%{opacity:1}70%{opacity:1}76%{opacity:0}100%{opacity:0}}
@keyframes ch-w4{0%{opacity:0}70%{opacity:0}76%{opacity:1}94%{opacity:1}100%{opacity:0}}

/* en `vh` et non en px : la chute doit traverser n'importe quel ecran */
@keyframes ch-chute{
  0%  {transform:translate(0,-70px) rotate(0deg);      opacity:0}
  12% {opacity:.95}
  88% {opacity:.95}
  100%{transform:translate(-40px,110vh) rotate(300deg);opacity:0}
}
@keyframes ch-envol{
  0%  {transform:translate(0,80px) scale(.5);      opacity:0}
  18% {opacity:1}
  82% {opacity:1}
  100%{transform:translate(-30px,-80vh) scale(1.1);opacity:0}
}

/* Le pas : ils avancent SUR PLACE, c'est le monde qui passe. */
@keyframes ch-pas{
  0%,100%{transform:translateY(0)    rotate(-4deg)}
  50%    {transform:translateY(-9px) rotate(4deg)}
}
/* Le dragon vole : plus ample, plus lent, et sans bascule. Son rythme
   n'a rien a voir avec celui des saisons - ne pas le recaler dessus. */
@keyframes ch-vol{
  0%,100%{transform:translateY(0)}
  50%    {transform:translateY(-14px)}
}
/* L'ombre reste au sol et se resserre quand le compagnon monte : la
   meme regle que dans ui/course.js - c'est l'ombre qui respire. */
@keyframes ch-ombre{
  0%,100%{transform:scaleX(1);   opacity:.22}
  50%    {transform:scaleX(.74); opacity:.12}
}
@keyframes ch-pop{
  0%  {transform:scale(.4);  opacity:0}
  62% {transform:scale(1.08);opacity:1}
  100%{transform:scale(1);   opacity:1}
}
@keyframes ch-monte{
  0%{transform:translateY(16px);opacity:0}
  100%{transform:translateY(0);opacity:1}
}
@keyframes ch-point{0%,100%{opacity:.25}50%{opacity:1}}

.ch-ciel,.ch-sol{animation:ch-defile 7s linear infinite}
.ch-halo{animation:ch-halo 7s linear infinite}
.ch-w1{animation:ch-w1 7s linear infinite}
.ch-w2{animation:ch-w2 7s linear infinite}
.ch-w3{animation:ch-w3 7s linear infinite}
.ch-w4{animation:ch-w4 7s linear infinite}

.ch-p{animation:ch-chute var(--d) linear infinite;animation-delay:var(--delai)}
.ch-w4 .ch-p{animation-name:ch-envol}

.ch-marche,.ch-marche-b,.ch-vole,
.ch-ombre,.ch-ombre-b,.ch-ombre-c,.ch-logo{
  transform-box:fill-box;transform-origin:center;
}
.ch-marche{animation:ch-pas .62s ease-in-out infinite}
.ch-marche-b{animation:ch-pas .62s .1s ease-in-out infinite}
.ch-vole{animation:ch-vol 1.9s ease-in-out infinite}
.ch-ombre{animation:ch-ombre .62s ease-in-out infinite}
.ch-ombre-b{animation:ch-ombre .62s .1s ease-in-out infinite}
.ch-ombre-c{animation:ch-ombre .62s .2s ease-in-out infinite}

.ch-logo{animation:ch-pop .8s var(--ressort) both}
.ch-souffle{animation:respire 4.2s 1s ease-in-out infinite}
.ch-nom{animation:ch-monte .6s .3s var(--doux) both}
.ch-attente{animation:ch-monte .6s .55s var(--doux) both}
.ch-attente i{animation:ch-point 1.4s ease-in-out infinite}
.ch-attente i:nth-of-type(2){animation-delay:.15s}
.ch-attente i:nth-of-type(3){animation-delay:.3s}

/* ------------------------------------------------------------
   La galipette du compagnon
   ------------------------------------------------------------
   Le seul temps d'attente du parcours : pendant que le carnet se
   prépare, le compagnon fait la roue. Une vraie galipette, pas une
   rotation : il s'accroupit, il décolle, il tourne, il se reçoit et
   il rebondit. Ce sont les deux écrasements — avant l'appel et à la
   réception — qui donnent le poids ; sans eux, un personnage qui
   pivote a l'air d'être sur un tourne-disque.

   Posée sur la boîte extérieure, jamais sur le SVG : le compagnon a
   déjà son animation d'arrivée dessus, et les deux se composent.
   ------------------------------------------------------------ */
@keyframes galipette{
  0%   {transform:translateY(0)     rotate(0turn)    scaleX(1)   scaleY(1)}
  12%  {transform:translateY(7px)   rotate(0turn)    scaleX(1.1) scaleY(.88)}
  26%  {transform:translateY(-34px) rotate(-.22turn) scaleX(.94) scaleY(1.08)}
  46%  {transform:translateY(-52px) rotate(-.5turn)  scaleX(1)   scaleY(1)}
  66%  {transform:translateY(-30px) rotate(-.78turn) scaleX(.96) scaleY(1.06)}
  84%  {transform:translateY(4px)   rotate(-1turn)   scaleX(1.12) scaleY(.86)}
  92%  {transform:translateY(-6px)  rotate(-1turn)   scaleX(.98) scaleY(1.04)}
  100% {transform:translateY(0)     rotate(-1turn)   scaleX(1)   scaleY(1)}
}
.recap-figurine.galipette{
  animation:galipette 1.2s cubic-bezier(.36,.05,.42,1) infinite;
  /* le pivot aux pieds : une galipette part du sol, pas du nombril */
  transform-origin:50% 82%;
  will-change:transform;
}
/* l'animation d'arrivée du compagnon ne doit pas se rejouer par-dessus */
.recap-figurine.galipette .figurine{animation:none}

/* L'encre des morceaux du pendu : ils arrivent en se posant, jamais
   d'un coup - un trait qui surgit sans transition se lit comme un
   defaut d'affichage plutot que comme une erreur de plus. */
@keyframes pendu-encre{
  from{opacity:0;transform:scale(.86)}
  to{opacity:1;transform:scale(1)}
}
