/* ============================================================
   BANC — bibliothèque de mouvement

   Toutes les @keyframes de l'application vivent ici, ainsi que les
   classes utilitaires que le JS pose sur le DOM — et rien d'autre. Le
   CSS des composants qui s'y était accumulé (déroulé pas-à-pas,
   lexique, fiches de notion, guide) est parti dans css/ecrans.css.
   Les autres modules
   n'inventent pas d'animation : ils posent une de ces classes.

   Contrat de nommage : chaque @keyframes est préfixée `banc` pour
   qu'une feuille tierce (le tuteur injecte la sienne) ne puisse pas
   redéfinir la nôtre par accident.

   Tout ce fichier se neutralise sous prefers-reduced-motion — le bloc
   général de tokens.css annule durées et délais, le bloc de fin de ce
   fichier annule en plus les états de départ.
   ============================================================ */

/* ============================================================
   1. KEYFRAMES
   ============================================================ */
@keyframes bancFade{from{opacity:0}to{opacity:1}}

@keyframes bancRise{
  from{opacity:0;transform:translateY(10px)}
  to{opacity:1;transform:none}
}

@keyframes bancPop{
  0%{opacity:0;transform:scale(.86)}
  60%{opacity:1;transform:scale(1.04)}
  100%{opacity:1;transform:scale(1)}
}

@keyframes bancShake{
  0%,100%{transform:translateX(0)}
  15%{transform:translateX(-7px)}
  30%{transform:translateX(6px)}
  45%{transform:translateX(-4px)}
  60%{transform:translateX(3px)}
  80%{transform:translateX(-1px)}
}

@keyframes bancGlowOk{
  0%{box-shadow:0 0 0 0 rgba(var(--green-rgb),.55)}
  55%{box-shadow:0 0 0 8px rgba(var(--green-rgb),0),0 0 30px -4px rgba(var(--green-rgb),.75)}
  100%{box-shadow:var(--glow-green)}
}
@keyframes bancGlowNo{
  0%{box-shadow:0 0 0 0 rgba(var(--red-rgb),.55)}
  55%{box-shadow:0 0 0 8px rgba(var(--red-rgb),0),0 0 30px -4px rgba(var(--red-rgb),.75)}
  100%{box-shadow:var(--glow-red)}
}
@keyframes bancGlowInfo{
  0%{box-shadow:0 0 0 0 rgba(var(--cyan-rgb),.5)}
  55%{box-shadow:0 0 0 8px rgba(var(--cyan-rgb),0),0 0 30px -4px rgba(var(--cyan-rgb),.7)}
  100%{box-shadow:var(--glow-cyan)}
}

/* Un compteur qui bouge : un sursaut d'échelle et un éclat de texte.
   Jamais de changement de couleur permanent — le chiffre doit rester
   lisible à la fin. */
@keyframes bancCount{
  0%{transform:scale(1)}
  35%{transform:scale(1.22);text-shadow:0 0 18px rgba(var(--amber-rgb),.9)}
  100%{transform:scale(1);text-shadow:none}
}

@keyframes bancBlink{0%,92%,100%{opacity:1}95%{opacity:.25}}

@keyframes bancSweep{
  0%{transform:translateX(-110%)}
  55%,100%{transform:translateX(110%)}
}

@keyframes bancRail{
  from{background-position:0 0}
  to{background-position:0 200%}
}

@keyframes bancSkeleton{
  0%,100%{opacity:.32}
  50%{opacity:.68}
}

@keyframes bancTestIn{
  from{opacity:0;transform:translateX(-10px)}
  to{opacity:1;transform:none}
}
@keyframes bancTick{
  0%{opacity:0;transform:scale(.3) rotate(-18deg)}
  65%{opacity:1;transform:scale(1.35) rotate(4deg)}
  100%{opacity:1;transform:scale(1) rotate(0)}
}
@keyframes bancCross{
  0%{opacity:0;transform:scale(.4)}
  40%{opacity:1;transform:scale(1.2) translateX(3px)}
  70%{transform:scale(1) translateX(-3px)}
  100%{opacity:1;transform:scale(1) translateX(0)}
}

@keyframes bancUnfold{
  from{opacity:0;transform:translateY(-8px)}
  to{opacity:1;transform:none}
}

@keyframes bancRipple{
  from{transform:scale(0);opacity:.85}
  to{transform:scale(1);opacity:0}
}

@keyframes bancBreak{
  0%{transform:scale(1) rotate(0);opacity:1}
  25%{transform:scale(1.28) rotate(-9deg)}
  55%{transform:scale(.82) rotate(7deg);opacity:.7}
  100%{transform:scale(1) rotate(0);opacity:1}
}

@keyframes bancAlert{
  0%,100%{box-shadow:0 1px 0 rgba(var(--white-rgb),.03) inset}
  50%{box-shadow:0 1px 0 rgba(var(--white-rgb),.03) inset,0 0 42px -6px rgba(var(--red-rgb),.55)}
}

@keyframes bancLevelup{
  0%{opacity:0;transform:translate(-50%,-50%) scale(.82)}
  14%{opacity:1;transform:translate(-50%,-50%) scale(1.03)}
  24%{transform:translate(-50%,-50%) scale(1)}
  76%{opacity:1;transform:translate(-50%,-50%) scale(1)}
  100%{opacity:0;transform:translate(-50%,-56%) scale(1.04)}
}

/* Respiration d'une notion échue : 0,4 Hz, très loin du seuil de
   photosensibilité, et sans extinction totale pour rester lisible. */
@keyframes bancDue{
  0%,100%{transform:scale(1);opacity:1}
  50%{transform:scale(1.45);opacity:.55}
}

@keyframes bancScan{
  0%{transform:translateY(-100%)}
  100%{transform:translateY(400%)}
}

/* ============================================================
   2. UTILITAIRES D'ENTRÉE
   ============================================================ */

/* Cascade : les enfants directs entrent l'un après l'autre.
   Rien en JS — le décalage vient de :nth-child. Au-delà de 14 enfants
   on plafonne le délai : une liste longue ne doit pas mettre trois
   secondes à s'afficher. */
.stagger>*{animation:bancRise .44s var(--ease-out) backwards}
.stagger>*:nth-child(1){animation-delay:0ms}
.stagger>*:nth-child(2){animation-delay:45ms}
.stagger>*:nth-child(3){animation-delay:90ms}
.stagger>*:nth-child(4){animation-delay:135ms}
.stagger>*:nth-child(5){animation-delay:180ms}
.stagger>*:nth-child(6){animation-delay:225ms}
.stagger>*:nth-child(7){animation-delay:270ms}
.stagger>*:nth-child(8){animation-delay:315ms}
.stagger>*:nth-child(9){animation-delay:360ms}
.stagger>*:nth-child(10){animation-delay:405ms}
.stagger>*:nth-child(11){animation-delay:450ms}
.stagger>*:nth-child(12){animation-delay:495ms}
.stagger>*:nth-child(13){animation-delay:540ms}
.stagger>*:nth-child(n+14){animation-delay:585ms}

/* Le contenu d'un écran de leçon est reconstruit à chaque rendu :
   la cascade se rejoue donc toute seule. Elle est plus serrée que
   `.stagger` parce qu'on la voit à chaque écran. */
.screen>*{animation:bancRise .38s var(--ease-out) backwards}
.screen>*:nth-child(1){animation-delay:0ms}
.screen>*:nth-child(2){animation-delay:34ms}
.screen>*:nth-child(3){animation-delay:68ms}
.screen>*:nth-child(4){animation-delay:102ms}
.screen>*:nth-child(5){animation-delay:136ms}
.screen>*:nth-child(6){animation-delay:170ms}
.screen>*:nth-child(n+7){animation-delay:204ms}

.pop{animation:bancPop .34s var(--ease-spring) backwards}
.slide-up{animation:bancRise .42s var(--ease-out) backwards}
.fade-in{animation:bancFade .3s ease backwards}
.shake{animation:bancShake .4s var(--ease-in-out) 1}
.glow-ok{animation:bancGlowOk .9s var(--ease-out) 1}
.glow-no{animation:bancGlowNo .9s var(--ease-out) 1}
.glow-info{animation:bancGlowInfo .9s var(--ease-out) 1}
.counting{animation:bancCount .5s var(--ease-spring) 1;display:inline-block}

.skeleton{
  border-radius:var(--r);min-height:14px;
  background:linear-gradient(90deg,var(--panel2),var(--panel2-hi),var(--panel2));
  animation:bancSkeleton 1.5s var(--ease-in-out) infinite
}

/* Cœur qui vient de se vider, et rail à sec. */
.hearts span.broke{animation:bancBreak .5s var(--ease-out) 1}
.rail.rail-alert{animation:bancAlert 1.1s var(--ease-in-out) 2}
/* La flamme de série : c'est le pictogramme qui réagit, pas le chiffre. */
.stat.streak-up::before{animation:bancCount .6s var(--ease-spring) 1}

/* La carte des mondes est reconstruite à chaque retour à l'accueil :
   ses vignettes arrivent donc en cascade sans qu'aucun JS s'en mêle. */
.grid>.card{animation:bancRise .4s var(--ease-out) backwards}
.grid>.card:nth-child(1){animation-delay:0ms}
.grid>.card:nth-child(2){animation-delay:32ms}
.grid>.card:nth-child(3){animation-delay:64ms}
.grid>.card:nth-child(4){animation-delay:96ms}
.grid>.card:nth-child(5){animation-delay:128ms}
.grid>.card:nth-child(6){animation-delay:160ms}
.grid>.card:nth-child(n+7){animation-delay:192ms}

/* ============================================================
   7. COUCHE D'EFFETS — alimentée par js/fx.js
   ============================================================ */
.fx-layer{
  position:fixed;inset:0;z-index:var(--z-fx);pointer-events:none;overflow:hidden;
  contain:layout paint
}

.confetti{
  position:absolute;top:0;left:0;display:block;
  width:7px;height:7px;border-radius:2px;
  will-change:transform,opacity
}
.confetti.round{border-radius:50%}
.confetti.bar{width:3px;height:11px;border-radius:1px}

.xp-fly{
  position:absolute;top:0;left:0;
  font-family:var(--mono);font-size:var(--fs-2);font-weight:500;letter-spacing:.06em;
  color:var(--on-accent);
  background:linear-gradient(180deg,var(--amber-hi),var(--amber));
  padding:4px 11px;border-radius:99px;white-space:nowrap;
  box-shadow:var(--e2),var(--glow-amber);
  will-change:transform,opacity
}
.xp-fly.neg{
  color:var(--paper);
  background:linear-gradient(180deg,rgba(var(--red-rgb),.9),rgba(var(--red-rgb),.7));
  box-shadow:var(--e2),var(--glow-red)
}

.levelup{
  position:absolute;left:50%;top:38%;overflow:hidden;
  transform:translate(-50%,-50%);
  display:flex;flex-direction:column;align-items:center;gap:6px;
  padding:22px 46px;border-radius:var(--r-xl);text-align:center;
  background:var(--glass);backdrop-filter:blur(16px) saturate(1.5);
  -webkit-backdrop-filter:blur(16px) saturate(1.5);
  border:1px solid rgba(var(--amber-rgb),.55);
  box-shadow:var(--e3),var(--glow-amber);
  animation:bancLevelup 2.4s var(--ease-out) both
}
.levelup .k{
  font-family:var(--mono);font-size:var(--fs-1);letter-spacing:.3em;text-transform:uppercase;
  color:var(--amber)
}
.levelup .n{
  /* Hors échelle à dessein : ce chiffre est un événement, pas du
     texte. Le poser sur --fs-7 le ferait maigrir sur téléphone,
     là où il doit justement frapper. */
  font-family:var(--display);font-weight:700;font-size:46px;line-height:1;color:var(--paper);
  text-shadow:0 0 44px rgba(var(--amber-rgb),.65)
}
/* Balayage lumineux derrière le bandeau : la lumière traverse, elle ne clignote pas. */
.levelup::after{
  content:'';position:absolute;inset:0;border-radius:var(--r-xl);overflow:hidden;pointer-events:none;
  background:linear-gradient(180deg,transparent,rgba(var(--white-rgb),.12),transparent);
  height:26%;animation:bancScan 2.4s var(--ease-in-out) 1
}

/* Ondulation de clic. `z-index:-1` la place au-dessus du fond du bouton
   mais sous son libellé : les hôtes sont en `isolation:isolate`. */
.ripple{
  position:absolute;border-radius:50%;pointer-events:none;z-index:-1;
  background:radial-gradient(circle,rgba(var(--white-rgb),.32),rgba(var(--white-rgb),.07) 58%,transparent 72%);
  transform:scale(0);
  animation:bancRipple .62s var(--ease-out) forwards
}

/* ============================================================
   8. ACCESSIBILITÉ
   Le bloc de tokens.css réduit déjà durées et délais à zéro. On
   supprime ici les états de départ (opacité nulle, décalage) pour
   qu'aucun élément ne puisse rester invisible, et on coupe les
   boucles infinies qu'une durée quasi nulle rendrait frénétiques.
   ============================================================ */
@media (prefers-reduced-motion:reduce){
  .stagger>*,.screen>*,.grid>.card,.pop,.slide-up,.fade-in,.shake,
  .glow-ok,.glow-no,.glow-info,.counting,
  .test,.test .m,.verdict.on,.sheet-in,.opt.good,.opt.bad,.opt-why,
  .notion-card,.guide-pop,.levelup,.ripple,
  details.deep>:not(summary),.tvar.changed,.tvar.changed .val,
  .hearts span.broke,.rail.rail-alert{
    animation:none!important;opacity:1!important;transform:none!important
  }
  .levelup{transform:translate(-50%,-50%)!important}
  .levelup::after{display:none}
  .ripple{display:none}
  .skeleton{animation:none;opacity:.45}
  .bar i::after,.led,.led.busy,.console-hd .dot.run,.notion-chip.due::before,
  .card.open:hover::before,.card.partial:hover::before,.card.done:hover::before{
    animation:none!important
  }
  /* La notion échue ne clignote plus : son contour plein la signale déjà. */
  .notion-chip.due::before{transform:none;opacity:1}
  .trace-line,.ed-band,.ed-gut i{transition:none!important}
  .fx-layer{display:none}
}
