/* ==========================================================================
   Capri · mouvement
   Une seule idée matérielle : la tête de lecture. Le chargement passe comme
   une coupe de gauche à droite, les lignes se révèlent comme un balayage,
   les écrans s'ouvrent à mesure qu'on défile. Tout est visible par défaut :
   si un script échoue, rien ne reste caché.
   ========================================================================== */

@property --scan { syntax: '<percentage>'; inherits: true; initial-value: 130%; }

@keyframes lnRise   { from { transform: translateY(112%); } to { transform: none; } }
@keyframes softIn   { from { opacity: 0; transform: translateY(16px); filter: blur(8px); } to { opacity: 1; transform: none; filter: none; } }
@keyframes fadeIn   { from { opacity: 0; } to { opacity: 1; } }
@keyframes aperture { from { clip-path: inset(10% 10% 10% 10%); } to { clip-path: inset(0 0 0 0); } }
@keyframes settle   { from { transform: scale(1.18); } to { transform: none; } }
@keyframes feedIn   { from { opacity: 0; transform: translateX(22px); } to { opacity: 1; transform: none; } }
@keyframes feedOut  { to { opacity: 0; transform: translateX(-14px); } }
@keyframes pulse    { 50% { opacity: .35; } }

/* ================================================================ chargement
   L'écran couvre la page au premier affichage (script en tête de page),
   la timeline reste visible dessous et sert de barre de progression.
   Filet de sécurité : l'écran s'efface tout seul par animation CSS au bout
   de 3,4 s, même si aucun script ne tourne. */
.loader, .loader-edge { display: none; }
html[data-load] .loader, .loader[data-state] { display: grid; }
.loader {
  position: fixed; inset: 0 0 44px 0; z-index: 52;
  align-content: end; padding: 0 var(--gutter) clamp(44px, 9vh, 96px);
  background: var(--ground);
}
.loader-mask { overflow: hidden; padding: .14em .04em .06em; margin: -.14em -.04em -.06em; }
.loader-title {
  display: block; font-weight: 850; font-stretch: 64%; text-transform: uppercase;
  font-size: clamp(3.4rem, 12vw, 11rem); line-height: .88; letter-spacing: -0.012em; color: var(--bone);
}
.loader-title::before { content: attr(data-fr); }
html[lang="en"] .loader-title::before { content: attr(data-en); }
.loader-status { margin-top: 20px; display: flex; align-items: center; gap: 12px; font-family: var(--mono); font-size: .8rem; letter-spacing: .1em; text-transform: uppercase; color: var(--bone-3); }
.loader-status img { width: 22px; height: 22px; }
.loader-status .num { color: var(--bone); }
.loader-label::before { content: 'Chargement'; }
html[lang="en"] .loader-label::before { content: 'Loading'; }

/* La tête de lecture qui accompagne chaque coupe. */
.loader-edge { position: fixed; z-index: 53; top: 0; bottom: 44px; left: 0; width: 2px; background: var(--bone); pointer-events: none; transform: translateX(-4px); }
.loader-edge[data-run] { display: block; animation: edgeSweep var(--sweep, .9s) var(--ease-io) forwards; }
@keyframes edgeSweep { from { transform: translateX(-4px); } to { transform: translateX(calc(100vw + 4px)); } }

@keyframes ldIn  { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes ldOut { from { clip-path: inset(0 0 0 0); visibility: visible; } to { clip-path: inset(0 0 0 100%); visibility: hidden; } }
/* Même geste sous un autre nom : si le filet et la vraie sortie partageaient
   un nom, changer le délai ne relancerait pas l'animation, elle sauterait
   directement à la fin. */
@keyframes ldOutSafe { from { clip-path: inset(0 0 0 0); visibility: visible; } to { clip-path: inset(0 0 0 100%); visibility: hidden; } }
@keyframes ldFadeIn  { from { opacity: 0; } to { opacity: 1; } }
@keyframes ldFadeOut { from { opacity: 1; visibility: visible; } to { opacity: 0; visibility: hidden; } }
@keyframes ldFadeOutSafe { from { opacity: 1; visibility: visible; } to { opacity: 0; visibility: hidden; } }

html[data-load] .loader { animation: ldOutSafe .62s var(--ease-io) 3.4s forwards; }
.loader[data-state="cover"] { animation: ldIn .42s var(--ease-io) both; }
html[data-reveal] .loader { animation: ldOut .62s var(--ease-io) forwards; }

/* Le titre monte sous son masque au premier chargement et à chaque coupe ;
   à l'arrivée d'une autre page, il est déjà là : c'est le même plan. */
html[data-load="boot"] .loader-title, html[data-load="quick"] .loader-title { animation: lnRise .8s var(--ease) .05s both; }
.loader[data-state="cover"] .loader-title { animation: lnRise .7s var(--ease) .12s both; }

/* Progression dans la timeline pendant le chargement. */
.timeline-load { position: absolute; z-index: 2; top: 0; bottom: 0; left: 0; width: calc(var(--p, 0) * 100%); background: color-mix(in oklab, var(--ally) 20%, var(--ground)); border-right: 2px solid var(--ally); pointer-events: none; transition: opacity .45s; }
.timeline-load[data-done] { opacity: 0; }

/* ================================================================ ouverture
   Le moment signé : quand l'écran de chargement passe, les lignes du titre
   montent sous leur masque, l'écran du joueur s'ouvre, le fil démarre. */
.ln { overflow: hidden; padding: .14em .04em .06em; margin: -.14em -.04em -.06em; }
.ln > span { display: block; }
.ln:nth-child(2) > span { --i: 1; }
.ln:nth-child(3) > span { --i: 2; }

@media (prefers-reduced-motion: no-preference) {
  .ln > span { animation: lnRise 1s var(--ease) both; animation-delay: calc(var(--i, 0) * 95ms + 60ms); }
  .intro-soft { animation: softIn 1s var(--ease) both; animation-delay: var(--d, .38s); }
  .intro-frame { animation: aperture 1.3s var(--ease) both .12s; }
  .intro-frame > video, .intro-frame > img { animation: settle 1.6s var(--ease) both .12s; }
  .feed li { animation: feedIn .5s var(--ease) both; animation-delay: var(--fd, 0s); }
  .feed li.out { animation: feedOut .35s var(--ease) forwards; }

  /* Tant que l'écran de chargement est là, l'ouverture attend. */
  html[data-load]:not([data-intro]) :is(.ln > span, .intro-soft, .intro-frame, .intro-frame > video, .intro-frame > img, .feed li) { animation-play-state: paused; }
}
@media (prefers-reduced-motion: reduce) {
  .intro-soft, .intro-frame, .ln > span { animation: fadeIn .4s linear both; }
  html[data-load]:not([data-intro]) :is(.ln > span, .intro-soft, .intro-frame) { animation-play-state: paused; }
}

/* ================================================================ apparitions au défilement
   Posées par mouvement.js seulement (classe .rv-on) : sans lui, tout est
   déjà affiché. Quatre gestes, un par nature de contenu. */

/* 1. Titres : chaque mot monte sous son propre masque. */
.rv-title .w { display: inline-block; overflow: hidden; vertical-align: top; padding: .14em .03em .12em; margin: -.14em -.03em -.12em; }
.rv-title .w > span { display: inline-block; }
.rv-on .rv-title .w > span { transition: transform 1.05s var(--ease); transition-delay: calc(var(--wi, 0) * 65ms); }
.rv-on .rv-title:not([data-in]) .w > span { transform: translateY(115%); }

/* 2. Textes : ils sortent du flou, juste après leur titre. */
.rv-on .rv-text { transition: opacity .9s var(--ease), transform 1s var(--ease), filter .9s var(--ease); transition-delay: var(--rd, .14s); }
.rv-on .rv-text:not([data-in]) { opacity: 0; transform: translateY(18px); filter: blur(6px); }

/* 3. Lignes (listes, tableau) : un balayage de gauche à droite, comme la
   tête de lecture qui passe sur un clip. */
.rv-on .rv-row { transition: --scan 1.25s var(--ease), opacity .45s linear; transition-delay: var(--rd, 0s); }
.rv-on .rv-row:not([data-in]) { --scan: -30%; opacity: 0; }
.rv-on .rv-row:not(tr):not([data-done]),
.rv-on tr.rv-row:not([data-done]) > * {
  -webkit-mask-image: linear-gradient(90deg, #000 calc(var(--scan) - 30%), transparent var(--scan));
          mask-image: linear-gradient(90deg, #000 calc(var(--scan) - 30%), transparent var(--scan));
}

/* 4. Écrans (vidéos, captures) : la fenêtre s'ouvre et l'image se pose à
   mesure qu'on défile. --ap va de 0 à 1, calculé par mouvement.js. */
.rv-on .rv-media { clip-path: inset(calc((1 - var(--ap, 1)) * 9%) calc((1 - var(--ap, 1)) * 9%)); }
.rv-on .rv-media > img, .rv-on .rv-media > video { scale: calc(1 + (1 - var(--ap, 1)) * .16); }

/* Le formulaire s'assemble : le panneau se découpe, puis les champs
   arrivent un par un, le bouton d'envoi en dernier. */
.rv-on .brief-grid .form { clip-path: inset(0 0 0 0); transition: clip-path 1s var(--ease-io) .1s; }
.rv-on .brief-grid:not([data-in]) .form { clip-path: inset(0 100% 0 0); }
.rv-on .brief-grid .form > * { transition: opacity .6s var(--ease), transform .8s var(--ease); transition-delay: calc(.55s + var(--fi, 0) * 55ms); }
.rv-on .brief-grid:not([data-in]) .form > * { opacity: 0; transform: translateY(18px); }

/* Désactive les transitions le temps de poser l'état de départ. */
.rv-init .rv-title .w > span, .rv-init .rv-text, .rv-init .rv-row, .rv-init .brief-grid .form, .rv-init .brief-grid .form > * { transition: none !important; }

/* ================================================================ déroulé épinglé
   La section reste à l'écran ; le défilement fait avancer la piste et
   apparaître les étapes une à une. Désactivé si l'écran est trop bas. */
.pin-on { height: calc(100vh + 190vh); height: calc(100svh + 190svh); }
.pin-on .pin-stage {
  position: sticky; top: 0; height: 100vh; height: 100svh;
  display: grid; align-content: center;
  padding-top: calc(var(--bar) + 36px); padding-bottom: 60px;
}
.pin-on .track li { transition: opacity .8s var(--ease), transform .9s var(--ease), filter .8s var(--ease); }
.pin-on .track li:not([data-on]) { opacity: 0; transform: translateY(18px); filter: blur(6px); }
.pin-on .track li:not([data-on])::before { background: var(--ground); border-color: var(--line-2); }

/* ================================================================ retours d'action */
/* Marqueur d'impact : le clic sur l'objectif touche, comme en jeu. */
.hit { position: fixed; z-index: 70; width: 28px; height: 28px; margin: -14px 0 0 -14px; pointer-events: none; color: var(--goal-ink); animation: hit .34s var(--ease) forwards; }
.hit svg { width: 100%; height: 100%; }
@keyframes hit { from { opacity: 1; transform: scale(.55); } to { opacity: 0; transform: scale(1.2); } }

/* Menu déroulant (écrans moyens et téléphones). */
@media (max-width: 1179px) and (prefers-reduced-motion: no-preference) {
  .hud[data-menu="open"] .sections { animation: menuIn .45s var(--ease) both; }
  .hud[data-menu="open"] .keys li { animation: softIn .5s var(--ease) both; animation-delay: calc(var(--k, 0) * 35ms + 60ms); }
  .keys li:nth-child(2) { --k: 1; } .keys li:nth-child(3) { --k: 2; } .keys li:nth-child(4) { --k: 3; }
  .keys li:nth-child(5) { --k: 4; } .keys li:nth-child(6) { --k: 5; }
}
@keyframes menuIn { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0 0 0 0); } }

@media (prefers-reduced-motion: no-preference) {
  .tally[data-state="live"] .dot, .onair .dot { animation: pulse 1.6s ease-in-out infinite; }
  .nosignal[data-run]::before { animation: snow .8s steps(5) infinite; }
  .live[data-state="live"] .live-trace rect { animation: traceRun 9s linear infinite; }
  .tube[data-skeleton] .frame { animation: shimmer 1.4s linear infinite; }
}
@keyframes snow { 0% { transform: translate(0, 0); } 25% { transform: translate(-12%, 8%); } 50% { transform: translate(9%, -11%); } 75% { transform: translate(-6%, -4%); } 100% { transform: translate(4%, 10%); } }
@keyframes traceRun { to { stroke-dashoffset: -100; } }
@keyframes shimmer { from { background-position: 100% 0; } to { background-position: 0 0; } }

/* ================================================================ mouvement réduit
   Moins d'animations, plus douces : fondus courts, pas de déplacement. */
@media (prefers-reduced-motion: reduce) {
  .loader[data-state="cover"] { animation: ldFadeIn .2s linear both; }
  html[data-load] .loader { animation: ldFadeOutSafe .3s linear 3.4s forwards; }
  html[data-reveal] .loader { animation: ldFadeOut .3s linear forwards; }
  .loader-title { animation: none !important; }
  .loader-edge { display: none !important; }
}
