/* BASE — reset, app shell, screen layout, typography.
   Depends on tokens.css. Brand: dark UI, Roboto, single yellow accent. */

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html, body { height: 100%; }

body {
  font-family: var(--font-family-base);
  font-weight: var(--font-weight-light);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  background: var(--color-bg);
  color: var(--color-text);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, picture, video, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; cursor: pointer; }
input, select, textarea { font: inherit; }

/* Visible keyboard focus for all interactive elements (low specificity) */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: 2px;
}

.icon { display: inline-block; width: 1em; height: 1em; flex: none; fill: currentColor; vertical-align: middle; }

/* Screen layout: 2 columns 50/50 (desktop) */
/* col1 = media, col2 = centered content; mobile: media hidden, single column. */
.screen {
  display: grid;
  grid-template-columns: var(--layout-col-split) 1fr;
  min-height: 100vh;
  min-height: 100dvh;
  /* `--app-viewport` (posée par resize.js, SIPTECS-8649) neutralise CE plancher à
     `auto` dès qu'on est en iframe, INCONDITIONNELLEMENT : c'est notre propre
     script, toujours chargé, qui pose ce défaut, jamais un message hôte. */
  min-height: var(--app-viewport, 100dvh);
}

.screen__media {
  position: relative;
  overflow: hidden;
  background: #000;
}
/* Borne la colonne média par sa PROPRE LARGEUR, jamais par une unité de fenêtre
   (SIPTECS-8649) : `.screen__media` n'ayant aucune hauteur propre, le `height:100%`
   de son image était circulaire — l'image imposait sa hauteur INTRINSÈQUE
   (1920x2160 — `introduction-media.jpg`, `profil-media.jpg`), qui fabriquait la
   hauteur de la ligne donc du document, et en iframe
   auto-dimensionnée le document EST le viewport (le zoom constaté par Justine).
   `object-fit:cover` recadre déjà : ce ratio n'a pas à matcher le fichier au pixel.
   Exclut le défilé du loader (`:not(:has(> video))`), dont la mise en page vit plus
   bas (SIPTECS-8743) ; en desktop, la media query suivante le neutralise. */
.screen__media:not(:has(> video)) {
  aspect-ratio: 1920 / 2160;
}
/* Le média REMPLIT la rangée, il ne la dimensionne JAMAIS (SIPTECS-8649) : en
   colonnes côte à côte, l'`aspect-ratio` ci-dessus fait de la hauteur une fonction
   de la seule LARGEUR (× 1,125), sans rapport avec la place réelle — mesuré sur le
   storefront INT : 793px de média pour 740 disponibles en 1440×900 (la page HÔTE se
   met à scroller), 690px dans une rangée de 1159 sur une fenêtre haute (vide noir).
   La maquette (Introduction-page_Desktop, Figma 101:1841) montre l'image occupant
   TOUTE la hauteur sous le header.
   Mécanisme : neutraliser l'`aspect-ratio` PUIS passer l'image en absolu — un enfant
   absolu ne dimensionne jamais son parent, la rangée se cale sur le `min-height` de
   `.screen` et l'étirement de grille remplit le média. Neutraliser est OBLIGATOIRE :
   un item de grille porteur d'un `aspect-ratio` ne s'étire PAS. Le repli reste hors
   media query (mobile, écran résultats — `components.css` lui donne une hauteur
   explicite —, hôte périmé), et le SCOPE media query est IMPÉRATIF : hors d'elle, la
   spécificité (0,3,1) écraserait les réglages mobiles (0,2,0) — même piège de
   cascade que celui documenté dans `components.css`. */
@media (min-width: 769px) {
  .screen:not(.screen--results) .screen__media:not(:has(> video)) {
    aspect-ratio: auto;
  }
  .screen:not(.screen--results) .screen__media:not(:has(> video)) > img {
    position: absolute;
    inset: 0;
  }
}
.screen__media > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.screen__content {
  position: relative;         /* anchor for the progress strip pinned at the bottom */
  display: flex;
  flex-direction: column;
  padding: var(--space-8);    /* Figma: 32px — c'est ce qui cale le fil d'Ariane */
}

.screen__inner {
  width: 100%;
  max-width: var(--content-max-width);
  margin: auto;              /* centers in both axes inside the flex column */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-6);
  text-align: center;
}
/* `margin: auto` (ligne ci-dessus) est le SEUL centrage vertical des écrans « fit »
   (`.screen__content` n'a pas de `justify-content`), et un centrage SÛR : un margin
   `auto` qui manque d'espace se résout à 0, jamais en négatif, donc `.screen__inner`
   ne peut déborder que par le BAS. À ne JAMAIS remplacer par `align-items`/
   `justify-content: center` : celles-là ne clampent pas à 0 et laisseraient la moitié
   haute du contenu hors d'atteinte du scroll (SIPTECS-8649, Justine, /s/form/).
   Le formulaire déroge délibérément : son contenu mesure 786-797px incompressibles,
   plus que n'importe quelle hauteur d'écran, et vaut mieux ferré en haut que centré
   sous le pli — en mobile aussi (735px pour 667px), pas d'exception par breakpoint. */
.screen--form .screen__inner { margin-top: 0; }
/* Sans ceci le <h1> du formulaire atterrit collé au lien "Retour" (mesuré : y=56,
   `.screen__content` n'ayant par ailleurs aucun `gap`). `.screen--form` est le seul
   écran qui cumule "Retour" visible (form ∈ content._BACK_TYPES) ET un centrage
   désactivé ci-dessus. `gap` sur `.screen__content` plutôt qu'une marge : ne joue
   que sur ses enfants EN FLUX, jamais sur `.progress`, hors flux et inchangée.
   `--space-6` : la même valeur que le `gap` interne de `.screen__inner`. */
.screen--form .screen__content { gap: var(--space-6); }

/* Progress bar: last thing in the content column, edge to edge — it ignores the
   column padding. Taken out of the flow rather than pushed down with an auto
   margin, which would compete with `.screen__inner`'s own auto margins and break
   the vertical centering. The column's bottom padding keeps them apart. */
.screen__content > .progress {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
}
.back-link {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-light);
  line-height: 24px;
  color: var(--color-text);
}
.back-link .icon { width: 24px; height: 24px; }
.back-link:hover { text-decoration: underline; }

/* Écran loader */
/* UNE SEULE COLONNE, PLEINE PAGE, À TOUTES LES LARGEURS (SIPTECS-8743, demande
   Charlotte Dunet acceptée) : « nous reprenons le comportement ISO mobile et
   n'affichons qu'une colonne en full page ». La barre de progression a été retirée
   de l'écran (SIPTECS-8777) par `content.progress()`, pas par une règle d'ici.
   Un fichier 16:9 sera contribué plus tard par le client ; la mise en page doit
   rendre juste avec les deux — l'asset livré (640×720 portrait, 960×1080 sur INT)
   comme le futur paysage. Sélecteur `.screen--loader` (posé par `base_screen.html`)
   et non `:has(> .screen__media > video)` : un moteur sans `:has()` (Chrome < 105,
   Safari < 15.4, Firefox < 121) perdait la colonne pleine largeur. Hors media query :
   le breakpoint 768/769 ne produit plus aucun saut ici. */
.screen--loader {
  grid-template-columns: 1fr;
  /* Rangée du bas vide depuis SIPTECS-8777, et `1fr auto` reste pourtant NÉCESSAIRE :
     sans cette déclaration les deux enfants tombent sur des pistes implicites `auto`
     que `align-content: normal` (donc `stretch`) étire À ÉGALITÉ dès que la grille a
     une hauteur définie (mesuré à 1440×900 : « 810px 90px » au lieu de « 900px 0px »).
     Spécificité (0,1,0), la même que `.screen` : c'est l'ORDRE qui tranche, ce bloc
     doit rester APRÈS la règle `.screen` du haut de fichier. Le
     `.screen { grid-template-columns: 1fr }` de la media query mobile passe après et
     gagne, sans conséquence — ne pas y ajouter de `grid-template-rows`. */
  grid-template-rows: 1fr auto;
}

/* Fond de page aligné sur celui de la vidéo (`--color-bg`, dont le défaut `#1A1A1A`
   est celui du fichier au niveau près) plutôt que le noir pur de `.screen__media`,
   qui dessinait un rectangle plus clair autour du défilé ; en plein cadre, ce fond
   remplit les bandes que laisse un défilé portrait en page paysage.
   CASCADE, le piège du raccourci : `background` remet AUSSI `background-image` à
   `none`, la propriété dont la règle `prefers-reduced-motion` plus bas se sert pour
   peindre l'affiche. Elle tient parce que `:has()`/`:not()` portent la spécificité de
   leur argument — (0,2,1) contre (0,2,0) ici, mesuré ; idem pour son `display: none`
   (0,2,2) sur la vidéo.
   `max-height` : la hauteur disponible chez l'hôte, relayée par embed.js dans
   `--app-viewport`. Il borne la CONTRIBUTION intrinsèque de la colonne à la rangée
   `1fr` (mesuré : 1092 puis 900 à 1440×900). Règle inchangée depuis SIPTECS-8777 : le
   plafond de la rangée média doit RETIRER la rangée du bas — nulle aujourd'hui, d'où
   `var(--app-viewport)` sans `calc` —, sinon l'hôte applique `Math.max` sur un total
   trop haut et l'iframe grandit à chaque tour. Il vit SUR LE PARENT et son
   repli sur la réservation (`.screen--loader .screen__media::before`) parce que
   `--app-viewport` vaut `auto` tant qu'aucun hôte n'a relayé de VIEWPORT (resize.js
   le pose INCONDITIONNELLEMENT, et POUR TOUJOURS chez un hôte muet) : `max-height:
   auto` est invalide À LA VALEUR CALCULÉE et retombe à `none`, et empiler les deux
   sur le même élément ne fabrique aucun repli — l'échec de substitution se résout
   APRÈS la cascade, la propriété prend sa valeur INITIALE. */
.screen--loader .screen__media {
  display: grid;
  place-items: center;
  background: var(--color-bg);
  max-height: var(--app-viewport, 100dvh);
}

/* La vidéo REMPLIT la boîte que la grille lui donne, elle n'en fabrique aucune :
   `object-fit: contain` (jamais `cover`) la garde ENTIÈRE — seul cadrage acceptable
   pour un défilé de packshots — et le fond `--color-bg` du parent remplit ce que son
   ratio laisse libre (aire peinte : 1440×900 -> 789,33×888 à x=325,33 ; 375×812 ->
   375×421,88, le mobile d'avant ce ticket AU PIXEL). C'est le MOTIF ABSOLU des images
   plus haut, pour la même raison : un enfant absolu ne dimensionne jamais son parent.
   Deux écritures EN FLUX, mesurées et abandonnées : `width: 100%` sur une source
   portrait fabrique une rangée de 1620px pour 900px de fenêtre — resize.js postait
   1632 et la page HÔTE se mettait à scroller, la régression exacte de SIPTECS-8649 ;
   `height: 100%` + `aspect-ratio` fait partager la même cellule à la vidéo et à la
   réservation, la PLUS HAUTE la fixe et l'`overflow: hidden` du parent rognait la
   vidéo de 16px (720 suivis pour 688 de colonne). En absolu, la vidéo suit la BOÎTE
   du parent, jamais la cellule. Le ratio n'a plus qu'un métier ici : décrire la RÉSERVATION. */
.screen--loader .screen__media > video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* RÉSERVATION de la rangée média — le seul rôle qui reste à `--loader-video-width`
   (SIPTECS-8632), mais un rôle RÉEL : sans elle la rangée `1fr` vaut ZÉRO tant que
   la vidéo n'a aucune taille naturelle (affiche pas encore décodée), et sous
   `prefers-reduced-motion` la vidéo est en `display: none` (règle plus bas) : elle
   n'a alors jamais de boîte et l'affiche serait peinte dans un fond de hauteur nulle.
   Mesuré en iframe réelle : premier RESIZE à 0px (12px avant SIPTECS-8777) — le temps
   d'un aller-retour chez un hôte coopératif, DÉFINITIVEMENT chez un hôte muet.
   Un pseudo-élément et non la vidéo, hors flux et qui ne contribue rien : il faut une
   seconde boîte EN FLUX qui ne peint rien, seul enfant en flux de la colonne.
   La réservation vaut ce que la vidéo VALAIT avant ce ticket (640px -> 720px de
   rangée en desktop, 375px -> 421,875px en mobile) : elle ne sature jamais son
   plafond, donc ne peut pas verrouiller l'iframe sur sa hauteur courante. La variante
   `width: 100%`, mesurée, poste TOUJOURS la hauteur de l'iframe — 1632 pour 900
   disponibles, dont l'hôte ne redescendait JAMAIS : le cliquet que SIPTECS-8649 a
   coûté un ticket à supprimer. `max-width: 100vw` et non `100%` : un pourcentage est
   cyclique pendant le dimensionnement intrinsèque, donc IGNORÉ (mesuré : pseudo resté
   à 640px sur une fenêtre de 375, 720px réservés au lieu de 421,875px). */
.screen--loader .screen__media::before {
  content: "";
  width: var(--loader-video-width, 640px);
  max-width: 100vw;
  aspect-ratio: var(--loader-video-ratio, 640 / 720);
  max-height: 100dvh;
}

/* Rangée du bas vide depuis SIPTECS-8777 : plus de barre, pas de copie, pas de
   « Retour » (`_BACK_TYPES` exclut le loader), le lien de secours étant hors flux.
   `padding: 0` RESTE : sans lui les 32px de `.screen__content` (16px en mobile) en
   font une rangée de 64px qui vole 52px au défilé (mesuré à 1440×900 : rangée média
   836px au lieu de 888). */
.screen--loader .screen__content {
  padding: 0;
}
/* `text-align` et NON `align-self` (SIPTECS-8743) : sorti du flux et tendu d'un
   bord à l'autre par `inset-inline: 0`, ce lien n'est plus aligné par son parent
   flex. L'`align-self: center` qui vivait plus bas était donc une déclaration morte
   qui mentait — mesuré, le lien démarrait à x=0 et faisait 51,08px de large sur une
   fenêtre de 1440, là où le desktop d'avant le centrait à x=1079,99. */
.screen--loader .loader__continue {
  position: absolute;
  inset-inline: 0;
  bottom: var(--space-10);
  text-align: center;
}

/* Sans animation, la vidéo cède la place à son affiche — sa première image, donc
   rien ne manque ; la mettre en pause demanderait du JS et cet écran n'en charge
   aucun pour son rendu. `display: none` n'est possible que parce que la RÉSERVATION
   du bloc précédent tient la rangée toute seule (sans elle, mesuré : rangée 0,
   affiche invisible). L'affiche suit EXACTEMENT le cadrage de la vidéo, donc
   `background-size: contain` à toutes les largeurs (SIPTECS-8743) : en `auto`, elle
   était peinte à sa taille NATURELLE (640×720) et rognée dès que la colonne média —
   50 % de la fenêtre — passait sous 640px, donc sous 1280px de fenêtre (127,75px de
   chaque côté sur une colonne de 384,5px). Des longhands, pas le raccourci
   `background` : il réinitialiserait la couleur de fond posée plus haut.
   `--loader-poster` vient du gabarit ; l'URL en dur reste le repli sur l'asset livré.
   `:not(.screen__video--no-poster)` (SIPTECS-8632, D32) : sans affiche,
   `--loader-poster` vaut `none` et masquer la vidéo ouvrirait un rectangle vide, CSS
   ne sachant pas tester la valeur d'une custom property ; la vidéo reste alors dans
   le flux, gelée par `loader-video.js`. Les DEUX moitiés (masquer / peindre) portent
   le MÊME `:has()`, jamais l'une sans l'autre : il n'existe que depuis Chrome 105 /
   Safari 15.4 / Firefox 121, et un moteur qui ne l'a pas doit perdre les deux règles
   ensemble plutôt que garder le masquage sans le repeint. */
@media (prefers-reduced-motion: reduce) {
  .screen__media:has(> video:not(.screen__video--no-poster)) > video { display: none; }
  .screen__media:has(> video:not(.screen__video--no-poster)) {
    background-image: var(--loader-poster, url("../img/loader-produits.42200fa42530.jpg"));
    background-position: center;
    background-repeat: no-repeat;
    background-size: contain;
  }
}

/* Filet de sécurité, pas un bouton du parcours : le loader avance seul (meta refresh,
   délai = durée de la vidéo contribuée, 5 s par défaut) ; si le navigateur le bloque,
   le visiteur resterait coincé là. Le lien ne se montre donc qu'UNE SECONDE APRÈS le
   délai (`--loader-escape-delay`, posé par le gabarit = `seconds + 1`). `visibility`
   et non `opacity` : ni cliquable ni annoncé tant qu'il est caché. */
.loader__continue {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-decoration: underline;
  visibility: hidden;
  animation: loader-escape-hatch 0s linear var(--loader-escape-delay, 6s) forwards;
}
@keyframes loader-escape-hatch { to { visibility: visible; } }

@media (max-width: 768px) {
  .screen { grid-template-columns: 1fr; }
  .screen__media { display: none; }
  /* Figma cale le contenu mobile à 345px dans 375 (15px de marge) ; 16px reste sur
     l'échelle de 4px pour 2px d'écart, invisibles. */
  .screen__content { padding: var(--space-4); }

  /* Introduction (Figma 67:661) et profil (Figma 351:5068) : seuls écrans dont la
     maquette mobile garde les deux colonnes, empilées — le `display: none` ci-dessus
     les priverait de leur image ; le média est déjà avant le contenu dans le DOM
     (base_screen.html). SIPTECS-8737/8739 : la maquette dit les deux rangées en FILL
     (layoutGrow:1 sur Col-1 ET Col-2), jamais une hauteur d'image fonction de la
     largeur. `height: var(--app-viewport, auto)` rend la hauteur DÉFINIE quand l'hôte
     la relaie : en grille à hauteur indéfinie, une piste `fr` se cale sur sa
     contribution max-content et ne COMPRESSE jamais (mesuré : image figée à 425px) ;
     hôte périmé ou accès direct, repli `auto` et le `min-height` de `.screen` pilote.
     Rangée média `minmax(160px, 1fr)` : c'est L'IMAGE qui absorbe le surplus quand
     l'écran est trop petit (crop acté par le design, priorité au CTA). Rangée contenu
     `minmax(50%, auto)` — au plus son contenu, titre et description variant par rituel
     et par langue sans se compresser —, SURTOUT PAS `1fr` : en hauteur indéfinie
     DEUX pistes flexibles s'égalisent sur la plus haute (mesuré : contenu gonflé à 425px, écran à
     851px), alors qu'un pourcentage de piste dégrade proprement en `auto`. */
  .screen--introduction,
  .screen--profile {
    grid-template-rows: minmax(160px, 1fr) minmax(50%, auto);
    height: var(--app-viewport, auto);
  }
  .screen--introduction .screen__media,
  .screen--profile .screen__media {
    display: block;
    width: 100%;
    /* 375x406 sur la maquette : un ratio, pas une hauteur fixe ni un `vh` — l'app vit
       dans une iframe redimensionnée sur son contenu par resize.js, et un `vh` y
       bouclerait. Depuis SIPTECS-8737/8739 ce ratio n'est plus que la CONTRIBUTION
       INTRINSÈQUE de la rangée média ; `height: 100%` cale ensuite le média sur sa
       rangée (un pourcentage prime sur l'aspect-ratio). `min-height: 0` : sans lui, la
       taille minimale automatique d'un item de grille interdirait cette compression. */
    aspect-ratio: 375 / 406;
    height: 100%;
    min-height: 0;
  }
  .screen--introduction .screen__content,
  .screen--profile .screen__content {
    /* 24px haut/bas (--space-6) ; côtés : la maquette dit 15px, on garde 16px
       (--space-4) pour rester sur l'échelle de 4px — même arbitrage que plus haut. */
    padding: var(--space-6) var(--space-4);
  }
}

/* Typography — sizes from Confluence spec, authoritative */
/* Intro / profile title */
.title-lead {
  font-size: 28px;
  font-weight: var(--font-weight-medium);
  line-height: 40px;
}
/* Question title — Figma: Roboto 300 / 28px / 40px, l'emphase portée par un
   `<span class="accent">` et non par le poids du titre entier. */
.title-question {
  font-size: 28px;
  font-weight: var(--font-weight-light);
  line-height: 40px;
}
.text-body {
  font-size: var(--text-sm);            /* 14px */
  font-weight: var(--font-weight-light);
  line-height: 26px;
}
.text-legal {
  font-size: var(--text-xs);            /* 12px */
  font-weight: var(--font-weight-light);
  line-height: 24px;
  color: var(--color-text-muted);
}
.accent { color: var(--color-accent); }
.title-question .accent { font-weight: var(--font-weight-regular); }
.error { color: var(--color-error); }  /* mots en alerte (éditeur riche du BO) */

/* Tailles de l'éditeur riche du BO — en em : justes quel que soit le contexte de rendu */
.rt-small { font-size: 0.85em; }
.rt-large { font-size: 1.25em; }

@media (max-width: 768px) {
  .title-lead { font-size: 24px; font-weight: var(--font-weight-regular); line-height: 34px; }
  .title-question { font-size: 20px; font-weight: var(--font-weight-light); line-height: 28px; }
}

/* Utilities */
.stack { display: flex; flex-direction: column; gap: var(--space-4); }
.cluster { display: flex; flex-wrap: wrap; gap: var(--space-2); justify-content: center; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Passage d'un écran à l'autre */
/* La maquette anime le passage d'une question à l'autre en Smart Animate 300ms
   ease-out : le bloc question glisse vers le haut, le suivant arrive par le bas,
   pendant que le visuel, le fil d'Ariane et la barre restent en place. Les View
   Transitions rendent ça sans une ligne de JS : chaque réponse reste un lien, le
   serveur sert une page entière, et un navigateur qui ne les connaît pas navigue
   comme avant. */
@view-transition { navigation: auto; }

/* Nommer un élément le fait **persister** d'une page à l'autre au lieu de partir
   dans le fondu global : c'est ce qui immobilise le visuel et fait *filer* la barre.
   Chaque nom doit être unique dans la page. */
.screen__media { view-transition-name: ecran-media; }
.back-link { view-transition-name: ecran-retour; }
.progress { view-transition-name: progression-rail; }
/* nommé séparément du rail : c'est sa largeur qui doit s'interpoler */
.progress__fill { view-transition-name: progression-remplissage; }
.screen__inner { view-transition-name: ecran-question; }

::view-transition-group(*) { animation-duration: 300ms; animation-timing-function: ease-out; }

/* Le bloc question est le seul à se déplacer, et il fait **tout le trajet** : Smart
   Animate ne fond pas un calque apparié, il le déplace (dans la maquette, une seule
   pile que chaque écran translate — 586px entre Q1 et Q2). `50vh + 50%` = la moitié
   de la fenêtre plus la moitié du bloc, le compte exact pour qu'un bloc centré sorte
   entièrement du champ quelle que soit sa hauteur. L'opacité ne lâche que sur la fin
   du trajet : la maquette découpe sa pile dans une fenêtre, nous n'en avons pas —
   sans ce fondu la question sortante passerait par-dessus le fil d'Ariane. */
::view-transition-old(ecran-question) { animation: question-sort 300ms ease-out both; }
::view-transition-new(ecran-question) { animation: question-entre 300ms ease-out both; }
/* La sortante s'efface sur la moitié haute de son trajet, là où elle croise le fil
   d'Ariane ; l'entrante monte depuis le bas, où elle ne croise rien. Le déplacement
   court sur toute la durée dans les deux cas. */
@keyframes question-sort {
  0%   { transform: translateY(0); opacity: 1; }
  20%  { opacity: 1; }
  65%  { opacity: 0; }
  100% { transform: translateY(calc(-50vh - 50%)); opacity: 0; }
}
@keyframes question-entre {
  0%   { transform: translateY(calc(50vh + 50%)); opacity: 0; }
  35%  { opacity: 1; }
  100% { transform: translateY(0); opacity: 1; }
}

/* Reculer, c'est le même geste à l'envers. Le CSS ne peut pas deviner le sens —
   « Retour » est un lien comme un autre, donc une navigation « avant » pour le
   navigateur ; c'est `js/screen-transition.js` qui pose l'attribut avant que la
   transition ne capture quoi que ce soit. Le fondu suit le bloc qui croise le fil
   d'Ariane et change donc de camp : les deux keyframes gardent leur profil
   d'opacité. */
/* Pas d'espace avant le pseudo-élément : il s'attache à la racine, il n'en est
   pas un descendant. Avec l'espace le sélecteur ne matche jamais — en silence. */
:root[data-sens="arriere"]::view-transition-old(ecran-question) {
  animation-name: question-sort-arriere;
}
:root[data-sens="arriere"]::view-transition-new(ecran-question) {
  animation-name: question-entre-arriere;
}
@keyframes question-sort-arriere {
  0%   { transform: translateY(0); opacity: 1; }
  20%  { opacity: 1; }
  65%  { opacity: 0; }
  100% { transform: translateY(calc(50vh + 50%)); opacity: 0; }
}
@keyframes question-entre-arriere {
  0%   { transform: translateY(calc(-50vh - 50%)); opacity: 0; }
  35%  { opacity: 1; }
  100% { transform: translateY(0); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  /* la règle ci-dessus ne porte pas sur les pseudo-éléments de transition */
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}
