/* =========================================================================
   rotatestudy · systeme de design · mouvement
   Un seul geste d'objet : ce qui est pose sur la table se leve vers toi (.lift), au defilement, jamais de
   glissement lateral ni de rebond. Les titres arrivent ligne par ligne, une fois (.reveal-lines).
   Une courbe (--ease), objets 400-700 ms, textes 250 ms, survols 120 ms, rien au-dela d'1 s hors defilement.
   transform et opacity seulement.

   Convention : la regle de base est TOUJOURS la pose finale ; les @keyframes ne decrivent que le depart (from).
   Sans animation (reduire les animations, navigateur sans support, script absent ou bloque) la page est donc
   composee et lisible, rien n'y reste cache.

   Contrat avec site/motion.js (tache 4) :
   - si IntersectionObserver existe, motion.js pose la classe has-io sur <html> avant d'observer ;
   - il pose .is-in sur chaque .reveal-lines (toujours) et sur chaque .lift (seulement si
     CSS.supports("animation-timeline: view()") est faux) quand l'element entre a l'ecran, une fois ;
   - .rs1 et la route GPS sont pilotes par leurs propres regles (taches 4 a 7), sur la meme convention ;
     sans animation-timeline, motion.js pose aussi --takeoff, --rot, --route et .is-passed (html.scroll-js) ;
     l'atterrissage du pied (tache 7) n'a pas de repli : l'avion y reste pose.
   Ecran tres haut (plus de 1400 px) : tout est deja a l'ecran, on montre la pose finale.
   ========================================================================= */

/* ---- .lift : l'objet couche se releve autour de son bord bas ---- */
.lift{transform-origin:50% 100%}
@keyframes ds-lift{from{opacity:0;transform:perspective(1200px) translateY(40px) rotateX(28deg)}}

@media (prefers-reduced-motion:no-preference) and (max-height:1400px){
  /* Navigateurs recents : lie a la position, pas a la montre (on peut revenir en arriere, s'arreter au milieu). */
  @supports (animation-timeline:view()){
    .lift{animation:ds-lift linear both;animation-timeline:view();animation-range:entry 0% entry 280px}
  }
  /* Repli (Firefox aujourd'hui) : motion.js pose .is-in a l'entree, la transition joue une fois. */
  @supports not (animation-timeline:view()){
    html.has-io .lift{transition:opacity var(--t-object) var(--ease),transform var(--t-object) var(--ease)}
    html.has-io .lift:not(.is-in){opacity:0;transform:perspective(1200px) translateY(40px) rotateX(28deg)}
  }
}

/* ---- l'eventail de l'accueil (#look, components.css 17) : le .lift des trois pages, ecrit pour elles ----
   Les trois pages couchees en pile sur la table se levent et s'ouvrent en eventail, puis la page de devant livre ses
   deux pieces (.part, soulevees de --z). Lie au defilement de la scene entiere (view-timeline --fan), pas de chaque page.
   Les images cles ne decrivent que le depart : la pose de base (components.css, translate et rotate) est l'eventail
   ouvert. Le depart passe par translate, rotate et transform ; .lift{transform:none} du mouvement reduit et l'arret des
   animations laissent donc l'eventail ouvert. Repli sans animation-timeline : la transition .is-in de .lift (motion.js). */
@keyframes fan-s1{from{translate:-3% 16% -40px;rotate:-5deg;transform:rotateX(66deg)}
  40%{translate:-8% 6% -50px;rotate:-3deg;transform:rotateX(18deg)}}
@keyframes fan-s3{from{translate:3% 16% -60px;rotate:4deg;transform:rotateX(66deg)}
  40%{translate:8% 6% -70px;rotate:3deg;transform:rotateX(18deg)}}
@keyframes fan-s2{from{translate:0 14% 0;rotate:y 0deg;transform:rotateX(66deg)}
  55%{translate:0 2% 0;rotate:y 0deg;transform:rotateX(8deg)}}
@keyframes fan-part{from{transform:translateZ(0)}}
@media (prefers-reduced-motion:no-preference) and (max-height:1400px){
  @supports (animation-timeline:view()){
    .fan-stage{view-timeline-name:--fan}
    .fan .lift{animation-timeline:--fan;animation-range:cover 4% cover 46%}
    .fan .s1{animation-name:fan-s1}
    .fan .s2{animation-name:fan-s2}
    .fan .s3{animation-name:fan-s3}
    .fan .part{animation:fan-part linear both;animation-timeline:--fan;animation-range:cover 38% cover 54%}
  }
  @supports not (animation-timeline:view()){
    html.has-io .fan .lift{transition:translate var(--t-object) var(--ease),rotate var(--t-object) var(--ease),transform var(--t-object) var(--ease)}
    html.has-io .fan .s1:not(.is-in){opacity:1;translate:-3% 16% -40px;rotate:-5deg;transform:rotateX(66deg)}
    html.has-io .fan .s2:not(.is-in){opacity:1;translate:0 14% 0;rotate:y 0deg;transform:rotateX(66deg)}
    html.has-io .fan .s3:not(.is-in){opacity:1;translate:3% 16% -60px;rotate:4deg;transform:rotateX(66deg)}
  }
}

/* ---- .reveal-lines : un titre dont chaque ligne est un <span class="l">, revele ligne par ligne, une fois ----
   <h2 class="reveal-lines"><span class="l">Thirteen exams.</span><span class="l">Then rotation.</span></h2>
   Jamais sur le h1 de l'accueil : il est la au premier rendu. */
.reveal-lines .l{display:block}
@media (prefers-reduced-motion:no-preference){
  html.has-io .reveal-lines .l{transition:opacity var(--t-text) var(--ease),transform var(--t-text) var(--ease)}
  html.has-io .reveal-lines .l:nth-child(2){transition-delay:80ms}
  html.has-io .reveal-lines .l:nth-child(3){transition-delay:160ms}
  html.has-io .reveal-lines .l:nth-child(4){transition-delay:240ms}
  html.has-io .reveal-lines:not(.is-in) .l{opacity:0;transform:translateY(.4em)}
}

/* ---- petits textes qui s'ouvrent : explication de la question, menu telephone ---- */
@keyframes ds-rise{from{opacity:0;transform:translateY(8px)}}
@keyframes ds-fade{from{opacity:0}}
@media (prefers-reduced-motion:no-preference){
  .qf-e[open]>p{animation:ds-rise var(--t-text) var(--ease)}
  .mnav[open] .mnav-panel{animation:ds-fade var(--t-text) var(--ease)}
}

/* ---- decollage de l'accueil (.takeoff, components.css 16) ----
   Au premier geste de defilement, le RS-1 aligne roule en accelerant, cabre a 12 degres sur le train principal, decolle
   a 15 degres, rentre son train et sort en haut a droite. Lie au defilement de la page (scroll(root)), sur --range.
   Convention de ce fichier : les images cles ne decrivent que le depart. Le geste est donc ecrit a rebours (from = la
   sortie) et joue en reverse : au repos, et sans animation du tout, l'avion est dans sa pose de base, aligne, plein.
   Seules les proprietes translate et rotate deplacent l'avion (transform reste libre) ; le roulage accelere en quatre
   segments (distance au carre du temps) ; seul le groupe .gear change d'opacite, jamais la cellule. */
@keyframes rs1-takeoff{from{translate:calc(var(--exit) - var(--x0)) calc((var(--exit) - var(--xl) * 100cqw) * var(--gam) * -1 - 2px);rotate:-15deg}
  44%{translate:calc(var(--xl) * 100cqw - var(--x0)) -2px;rotate:-14deg}
  50%{translate:calc(var(--xr2) * 100cqw - var(--x0)) 0px;rotate:-12deg}
  62%{translate:calc(var(--xr1) * 100cqw - var(--x0)) 0px;rotate:0deg}
  71.5%{translate:calc((var(--xr1) * 100cqw - var(--x0)) * .5625) 0px;rotate:0deg}
  81%{translate:calc((var(--xr1) * 100cqw - var(--x0)) * .25) 0px;rotate:0deg}
  90.5%{translate:calc((var(--xr1) * 100cqw - var(--x0)) * .0625) 0px;rotate:0deg}}
@keyframes rs1-gear{from,24%{opacity:0;translate:0 -48px}38%{opacity:1;translate:0 0}}
@media (prefers-reduced-motion:no-preference){
  @supports (animation-timeline:scroll()){
    .takeoff .rs1{animation:rs1-takeoff linear both reverse;animation-timeline:scroll(root);animation-range:0px var(--range)}
    .takeoff .gear{animation:rs1-gear linear both reverse;animation-timeline:scroll(root);animation-range:0px var(--range)}
  }
  /* Repli : motion.js pose html.scroll-js et --takeoff (0 a 1) ; l'animation reste en pause, le defilement la place. */
  @supports not (animation-timeline:scroll()){
    html.scroll-js .takeoff .rs1{animation:rs1-takeoff 1s linear both reverse paused;animation-timeline:auto;animation-delay:calc(var(--takeoff,0) * -1s)}
    html.scroll-js .takeoff .gear{animation:rs1-gear 1s linear both reverse paused;animation-timeline:auto;animation-delay:calc(var(--takeoff,0) * -1s)}
  }
}

/* ---- « Thirteen exams. Then rotation. » (#rotation, components.css 18) ----
   Le meme geste que le decollage, en grand, lie a la traversee de la scene a l'ecran (view-timeline --rot), de --ra a --rb.
   La pose de base est l'avion en rotation au milieu de la piste (12 degres, train sorti) : le geste se joue donc en deux
   temps autour d'elle, chacun ecrit depuis son depart (convention de ce fichier) :
   - rs1-rot-roll (de --ra a --rm, en avant) : du seuil (from, la queue contre le dos du livre), roulage qui accelere en
     quatre segments (distance au carre du temps) jusqu'a --xr1, puis cabre jusqu'a la pose de base ; remplissage backwards ;
   - rs1-rot-climb (de --rm a --rb, joue en reverse) : ecrit depuis la sortie en haut a droite (from), decolle a --xl a
     14 degres, monte a 15 degres ; remplissage forwards, l'avion reste sorti.
   Le train (rs1-gear, celui du decollage) se replie de 62 a 76 % du geste : seul .gear change d'opacite, jamais la cellule.
   « Then rotation. » (.rot-on) s'allume pendant la rotation, de --ri a --rm. translate et rotate seulement.
   Repli sans animation-timeline : motion.js pose --rot (0 a 1 sur le geste) ; memes images cles, en pause, placees par
   animation-delay (un delai positif laisse l'animation avant son debut, sans effet). */
@keyframes rs1-rot-roll{from{translate:calc(var(--x0) - var(--xr2) * 100cqw) 0px;rotate:0deg}
  19%{translate:calc(var(--x0) + (var(--xr1) * 100cqw - var(--x0)) * .0625 - var(--xr2) * 100cqw) 0px;rotate:0deg}
  38%{translate:calc(var(--x0) + (var(--xr1) * 100cqw - var(--x0)) * .25 - var(--xr2) * 100cqw) 0px;rotate:0deg}
  57%{translate:calc(var(--x0) + (var(--xr1) * 100cqw - var(--x0)) * .5625 - var(--xr2) * 100cqw) 0px;rotate:0deg}
  76%{translate:calc((var(--xr1) - var(--xr2)) * 100cqw) 0px;rotate:0deg}}
@keyframes rs1-rot-climb{from{translate:calc(var(--exit) - var(--xr2) * 100cqw) calc((var(--exit) - var(--xl) * 100cqw) * var(--gam) * -1 - 2px);rotate:-15deg}
  88%{translate:calc((var(--xl) - var(--xr2)) * 100cqw) -2px;rotate:-14deg}}
@keyframes rot-ignite{from{opacity:.16}}

/* ---- la route GPS des matieres (#route, components.css 18) ----
   Le symbole reste au centre de l'ecran (position collante) : la route defile sous lui. Le trace vole (.fpl-flown) grandit
   de la premiere balise au bas de la route pendant que la piste (view-timeline --route) passe sous le centre de l'ecran ;
   chaque balise (.wp::after) se remplit quand son centre (--wp-y sous le haut de sa ligne, view-timeline --leg) y arrive.
   Les deux sont lus sur la meme geometrie que la position collante : toujours synchrones, quelle que soit la hauteur
   des lignes. Pose de base : symbole sur la premiere balise, rien de vole (images cles ecrites depuis la fin, en reverse).
   Repli : motion.js pose --route et .is-passed ; la position collante ne s'active qu'avec l'un des deux chemins. */
@keyframes fpl-flown{from{scale:1 1}}
/* Balise passee, posee par motion.js en repli (et montree telle quelle dans la liste de ds/design.html) : jamais en
   mouvement reduit, ou la route reste au repos. */
@media (prefers-reduced-motion:no-preference){
  :is(html.scroll-js,.ds-list) .subject-row.is-passed .wp::after{opacity:1}
}
@keyframes fpl-pass{from{opacity:1}}
@media (prefers-reduced-motion:no-preference){
  @supports (animation-timeline:view()){
    .rot-scene{view-timeline-name:--rot}
    .rot-scene .rs1{animation:rs1-rot-roll linear backwards,rs1-rot-climb linear forwards reverse;animation-timeline:--rot,--rot;animation-range:cover var(--ra) cover var(--rm),cover var(--rm) cover var(--rb)}
    .rot-scene .gear{animation:rs1-gear linear both reverse;animation-timeline:--rot;animation-range:cover var(--ra) cover var(--rb)}
    .rot-on{animation:rot-ignite linear both;animation-timeline:--rot;animation-range:cover var(--ri) cover var(--rm)}
    .fpl-track{view-timeline-name:--route}
    .route .subject-row{view-timeline-name:--leg}
    .fpl-ship{position:sticky;top:50vh}
    .fpl-flown{animation:fpl-flown linear both reverse;animation-timeline:--route;animation-range:cover 50vh cover calc(100% - 50vh)}
    .route .wp::after{animation:fpl-pass linear both reverse;animation-timeline:--leg;animation-range:cover calc(50vh + var(--wp-y) - 1px) cover calc(50vh + var(--wp-y) + 1px)}
  }
  @supports not (animation-timeline:view()){
    html.scroll-js .rot-scene .rs1{animation:rs1-rot-roll 1s linear backwards paused,rs1-rot-climb 1s linear forwards reverse paused;animation-timeline:auto;animation-delay:calc(var(--rot,0) / .5 * -1s),calc((.5 - var(--rot,0)) / .5 * 1s)}
    html.scroll-js .rot-scene .gear{animation:rs1-gear 1s linear both reverse paused;animation-timeline:auto;animation-delay:calc(var(--rot,0) * -1s)}
    html.scroll-js .rot-on{animation:rot-ignite 1s linear both paused;animation-timeline:auto;animation-delay:calc((.38 - var(--rot,0)) / .12 * 1s)}
    html.scroll-js .fpl-ship{position:sticky;top:50vh}
    html.scroll-js .fpl-flown{animation:fpl-flown 1s linear both reverse paused;animation-timeline:auto;animation-delay:calc(var(--route,0) * -1s)}
  }
}

/* ---- l'atterrissage (pied de chaque page, components.css 19) ----
   Le troisieme et dernier moment d'avion : le RS-1 arrive de la gauche (hors de l'ecran) en approche sur un plan a
   3 degres, assiette 3 degres, train rentre ; le train sort pendant l'approche (rs1-gear, celui du decollage, joue a
   l'endroit : seul .gear change d'opacite, jamais la cellule) ; arrondi a 6 degres et toucher sur le train principal (le
   pivot) a --xt ; le nez se pose a --xn ; roulage qui ralentit (quatre segments, distance restante au carre du temps)
   jusqu'a l'arret a --xs, la pose de base. Lie a la traversee de la scene (view-timeline --land), de son entree en bas de
   l'ecran jusqu'a ce que son bas soit a 360 px au-dessus du bas de l'ecran. Cette fin ne depend pas de la hauteur de la
   fenetre : le pied, sous la scene, mesure toujours plus de 360 px, le geste se termine donc en bas de chaque page, sur
   toutes les fenetres (revue tache 7 : une fin au centre de l'ecran n'etait pas atteinte sur une fenetre de 1400 px).
   Convention de ce fichier : from = le depart (l'approche), la pose de base = l'avion arrete ; pas de to.
   Pas de repli par script : sans animation-timeline (Firefox), comme en mouvement reduit, l'avion est pose. */
@keyframes rs1-land{from{translate:calc(50cqw - 50vw - var(--rs1-size) * .5 - var(--xs) * 100cqw) calc((var(--xt) * 100cqw - 50cqw + 50vw + var(--rs1-size) * .5) * -.0524);rotate:-3deg}
  64%{translate:calc((var(--xa) - var(--xs)) * 100cqw) calc((var(--xt) - var(--xa)) * 100cqw * -.0524);rotate:-3deg}
  74%{translate:calc((var(--xt) - var(--xs)) * 100cqw) 0px;rotate:-6deg}
  84%{translate:calc((var(--xn) - var(--xs)) * 100cqw) 0px;rotate:0deg}
  88%{translate:calc((var(--xn) - var(--xs)) * 100cqw * .5625) 0px;rotate:0deg}
  92%{translate:calc((var(--xn) - var(--xs)) * 100cqw * .25) 0px;rotate:0deg}
  96%{translate:calc((var(--xn) - var(--xs)) * 100cqw * .0625) 0px;rotate:0deg}}
@media (prefers-reduced-motion:no-preference){
  @supports (animation-timeline:view()){
    .land-scene{view-timeline-name:--land}
    .landing .rs1{animation:rs1-land linear both;animation-timeline:--land;animation-range:cover 0% entry calc(100% + 360px)}
    .landing .gear{animation:rs1-gear linear both;animation-timeline:--land;animation-range:cover 0% entry calc(100% + 360px)}
  }
  .qa details[open]>p{animation:ds-rise var(--t-text) var(--ease)}
}

/* ---- reduire les animations : chaque element dans sa pose finale ----
   L'avion reste visible dans l'attitude de sa classe (.ground, .rotate…), qui passe par la propriete rotate
   et non par transform : la pose est gardee, le mouvement est retire. */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation:none!important;transition-duration:.01ms!important;transition-delay:0s!important}
  .lift,.reveal-lines .l{opacity:1!important;transform:none!important}
  .rs1{opacity:1!important;transform:none!important;animation:none!important}
}
