/* ════════════════════════════════════════════════════════════════
   LES ÉTAPES SOUS LE PRODUIT — L'HABILLAGE.

   POURQUOI CE BLOC EXISTE : sur une précommande, le client paie avant
   d'avoir l'objet. Ces trois boutons lui montrent le geste qu'il fera
   quand le colis arrivera. C'est la seule chose qu'ils racontent.

   POURQUOI UNE FEUILLE À PART : ce bloc se pose sur DEUX pages qui
   n'ont pas les mêmes feuilles — l'accueil (socle.css + accueil.css)
   et les 45 fiches (socle.css + fiche.css). Le mettre dans l'une des
   deux l'aurait rendu invisible sur l'autre.

   CE QU'ELLE EMPRUNTE, ET NE REDÉFINIT JAMAIS :
   · --u1 --u2 --u3 --u2f : les quatre couleurs du rayon, posées par
     body[data-univers] dans socle.css. Les valeurs de repli servent
     seulement si le bloc est posé hors de tout univers ;
   · --b5 --b10 --b20 --b50 : les blancs transparents du socle ;
   · --f-titre --f-corps --f-mono, --t-* et --e-* : polices, durées et
     courbes du socle.

   LA PERFORMANCE N'EST PAS UNE OPTION — CE QUI EST ANIMÉ ICI :
   `transform` et `opacity`, RIEN D'AUTRE. Pas un `left`, pas un
   `width`, pas un `margin`, pas une `box-shadow` en mouvement : ces
   propriétés-là refont la mise en page à chaque image et le téléphone
   d'entrée de gamme tombe à 20 images par seconde.
     · la pastille active est un pseudo-élément qui GLISSE
       (translateX/translateY) : sa largeur ne bouge jamais ;
     · les phrases sont TOUTES empilées dans la même case de grille :
       le bloc a donc une hauteur fixe dès le premier rendu, et passer
       d'une étape à l'autre ne coûte qu'un fondu — aucune reprise de
       mise en page, aucun saut de la page sous le doigt ;
     · la jauge est un scaleX, jouée par l'API Web Animations, donc
       tenue par le compositeur et mise en pause hors écran.

   LE PIÈGE DU SURVOL : le mode téléphone du navigateur de vérification
   pilote une SOURIS. Tout effet de survol est donc enfermé dans
   @media (hover:hover) and (pointer:fine), sinon il se déclenche au
   doigt et l'étape paraît sélectionnée alors qu'elle ne l'est pas.
   ════════════════════════════════════════════════════════════════ */

/* ══════════ L'HÔTE ══════════
   L'élément que la page donne à MLO_ETAPES_VUE.poser(). Deux services,
   et rien d'autre — la page reste maîtresse de l'endroit :
   · la largeur de lecture et la gouttière du site, pour que le bloc
     s'aligne sur le reste de la page sans que l'appelant ait à le
     recopier (5vw : la même que #hero dans socle.css) ;
   · le masquage. Quand le produit n'a pas d'étapes, le JS pose
     `hidden` ET `display:none` en ligne ; ce `!important` est la
     troisième ceinture, pour le cas où l'hôte hérite d'un `display`
     d'une autre feuille. Un cadre vide sous un produit vaut moins que
     rien : il promet un contenu qui n'existe pas. */
.etv-hote{display:block;width:100%;max-width:1240px;
  margin-inline:auto;padding-inline:5vw}
.etv-hote[hidden]{display:none!important}

.etv{
  /* Les quatre couleurs du rayon, avec un repli neutre : si un jour le
     bloc est posé sur une page sans data-univers, il reste lisible au
     lieu de disparaître dans le noir. */
  --etv-u1:var(--u1,#181110);
  --etv-u2:var(--u2,#C9A227);
  --etv-u3:var(--u3,#F0E2B8);
  --etv-u2f:var(--u2f,#8A6E12);

  /* L'ENCRE POSÉE SUR LA PASTILLE — ET POURQUOI CE N'EST PAS --u1.
     Le libellé de l'étape active s'écrit en sombre sur la pastille
     claire du rayon. On avait pris --u1 tel quel, en supposant les
     cinq couples contrastés. MESURÉ, ils ne le sont pas tous :
       huiles  5,18:1 · livres 5,17:1 · zamzam 4,91:1  — bon
       enfants 4,46:1 · priere 4,12:1                  — SOUS 4,5:1
     Or le libellé fait .82rem gras, soit ~13 px : c'est du texte
     normal au sens des règles d'accessibilité, donc 4,5:1 est le
     minimum, pas 3:1. Deux rayons sur cinq étaient donc illisibles
     pour qui voit mal — sur le bouton qui doit justement se
     distinguer nettement des autres.
     On assombrit --u1 de 30 % vers le noir : la teinte du rayon reste
     reconnaissable (on ne tombe pas dans le noir universel, qui aurait
     effacé la différence entre les cinq univers) et les cinq passent —
     mesuré : priere 4,96 · enfants 5,53 · livres 5,80 · zamzam 5,88 ·
     huiles 6,92. Le repli #101010 sert aux navigateurs sans color-mix :
     il donne 6,4:1 au pire, en perdant seulement la teinte. */
  --etv-encre:#101010;

  --etv-gap:8px;          /* l'écart entre deux boutons */
  --etv-pad:6px;          /* la marge intérieure du rang */
  --etv-i:0;              /* l'indice de l'étape active — posé par le JS */
  --etv-n:3;              /* le nombre d'étapes — posé par le JS */

  display:block;
  margin:clamp(18px,2.6vw,30px) 0 0;
  padding:clamp(15px,2.2vw,24px);
  border:1px solid var(--b10,rgba(var(--laine),.10));
  border-radius:22px;
  background:linear-gradient(180deg,var(--b5,rgba(var(--laine),.05)),transparent 70%);
  /* La couleur du rayon change au défilement sur l'accueil : on suit le
     même mouvement que le reste de la page, sans le devancer. */
  color:var(--blanc);
}
/* En sRGB, pas en oklab : les rapports ci-dessus ont été calculés sur
   un mélange sRGB, et oklab ne donne pas les mêmes valeurs. */
@supports (color:color-mix(in srgb,#000 50%,#fff)){
  .etv{--etv-encre:color-mix(in srgb,var(--etv-u1) 70%,#000)}
}

/* ══════════ LA TÊTE ══════════ */
.etv__tete{margin:0 0 clamp(12px,1.6vw,18px)}
.etv__sur{
  margin:0 0 .35em;
  font-family:var(--f-mono,ui-monospace,Consolas,monospace);
  font-size:.68rem;letter-spacing:.2em;text-transform:uppercase;
  color:var(--etv-u3);opacity:.85;
}
.etv__titre{
  margin:0;
  font-family:var(--f-titre,Palatino,serif);font-weight:400;
  font-size:clamp(1.25rem,2.6vw,1.75rem);line-height:1.15;
  letter-spacing:-.01em;
}

/* ══════════ LE RANG DE BOUTONS ══════════
   Une grille à colonnes implicites égales (grid-auto-columns:1fr) :
   pas de repeat(var(--n)) — la substitution d'une variable dans le
   compteur de repeat() n'est pas fiable partout, et une grille qui
   retombe sur une seule colonne ferait sauter toute la page. */
.etv__rang{
  position:relative;
  display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);
  gap:var(--etv-gap);padding:var(--etv-pad);
  border-radius:16px;
  background:rgba(0,0,0,.28);
  box-shadow:inset 0 0 0 1px var(--b10,rgba(var(--laine),.10));
}

/* LA PASTILLE ACTIVE — un pseudo-élément, donc totalement absent de
   l'arbre d'accessibilité : le lecteur d'écran n'annonce pas un objet
   décoratif, et le rôle « tablist » ne reçoit aucun enfant illégitime.

   Sa largeur est celle d'une colonne, calculée une fois ; ensuite elle
   ne fait que GLISSER. Le 100% d'un translate se mesure sur l'élément
   lui-même : un cran = sa propre largeur + l'écart. */
.etv__rang::before{
  content:'';position:absolute;z-index:0;
  top:var(--etv-pad);bottom:var(--etv-pad);left:var(--etv-pad);
  width:calc((100% - 2 * var(--etv-pad) - (var(--etv-n) - 1) * var(--etv-gap)) / var(--etv-n));
  border-radius:12px;
  background:var(--etv-u2);
  /* Ombre FIXE : elle ne participe pas à la transition, elle est juste
     là. Une ombre qui s'anime repeint tout le rang à chaque image. */
  box-shadow:0 6px 20px -10px var(--etv-u2f);
  transform:translateX(calc(var(--etv-i) * (100% + var(--etv-gap))));
  transition:transform var(--t-transit,420ms) var(--e-sortie,cubic-bezier(.22,1,.36,1));
}
/* `will-change` seulement pendant le glissement : le JS pose
   data-bouge le temps de la transition, puis l'enlève. Laissé en
   permanence, il réserve une couche mémoire pour rien sur un
   téléphone qui n'en a pas de trop. */
.etv[data-bouge="1"] .etv__rang::before,
.etv[data-bouge="1"] .etv__panneau{will-change:transform,opacity}

/* ══════════ UN BOUTON-ÉTAPE ══════════ */
.etv__bouton{
  position:relative;z-index:1;      /* au-dessus de la pastille */
  display:flex;flex-direction:column;gap:.28em;
  align-items:flex-start;justify-content:center;
  /* 56 px : au-delà des 44 px exigés au doigt, et de quoi loger deux
     lignes de libellé sans que le texte touche le bord. */
  min-height:56px;
  padding:.6rem .7rem;
  margin:0;border:0;border-radius:12px;
  background:transparent;
  font-family:var(--f-corps,system-ui,sans-serif);
  color:rgba(var(--laine),.68);
  text-align:left;cursor:pointer;
  -webkit-tap-highlight-color:transparent;
  touch-action:manipulation;        /* pas d'attente de double-tape */
  transition:color var(--t-survol,220ms) var(--e-doux,cubic-bezier(.4,0,.2,1));
}
.etv__num{
  font-family:var(--f-mono,ui-monospace,Consolas,monospace);
  font-size:.62rem;letter-spacing:.16em;
  opacity:.6;
}
.etv__geste{
  font-size:.82rem;font-weight:700;line-height:1.25;
  letter-spacing:-.005em;
  /* Le plus long libellé du fichier fait 33 caractères, et « Prosternez-
     vous dessus » ne se coupe qu'au trait d'union. Dans une colonne de
     grille en minmax(0,1fr), un mot plus large que sa colonne DÉBORDE
     sur la pastille voisine au lieu d'élargir la sienne : la césure
     forcée est le seul garde-fou. */
  overflow-wrap:anywhere;
}

/* L'ÉTAPE ACTIVE : texte sombre sur la pastille claire du rayon —
   avec l'encre assombrie, pas --u1 brut (voir --etv-encre plus haut,
   et les rapports mesurés qui l'expliquent). */
.etv__bouton[aria-selected="true"]{color:var(--etv-encre)}
/* Le numéro passe à pleine opacité une fois l'étape active : à .75 il
   tombait entre 3,39:1 (priere) et 4,12:1 (huiles) sur la pastille.
   La hiérarchie tient déjà par le corps (.62rem contre .82rem) et par
   l'interlettrage — elle n'a pas besoin de coûter de la lisibilité. */
.etv__bouton[aria-selected="true"] .etv__num{opacity:1}

/* Le survol, enfermé : au doigt il ne doit rien se passer. */
@media (hover:hover) and (pointer:fine){
  .etv__bouton:hover{color:var(--blanc)}
  .etv__bouton[aria-selected="true"]:hover{color:var(--etv-encre)}
}
/* L'appui : un enfoncement de 1 px, en transform — jamais en margin. */
.etv__bouton:active{transform:translateY(1px);
  transition-duration:var(--t-micro,120ms)}

.etv__bouton:focus-visible{
  outline:2px solid var(--etv-u3);outline-offset:2px;
}

/* ══════════ LES PHRASES ══════════
   Toutes empilées dans LA MÊME case de grille (grid-area:1/1). La
   hauteur du bloc est donc celle de la plus longue, arrêtée une fois
   pour toutes : changer d'étape ne déplace plus rien sur la page.
   C'est ce qui rend le passage gratuit — un fondu, rien de plus. */
.etv__panneaux{display:grid;margin-top:clamp(12px,1.6vw,18px)}
.etv__panneau{
  grid-area:1/1;
  opacity:0;transform:translateY(10px);
  visibility:hidden;                 /* retiré de la tabulation clavier */
  transition:opacity var(--t-transit,420ms) var(--e-doux,cubic-bezier(.4,0,.2,1)),
             transform var(--t-transit,420ms) var(--e-sortie,cubic-bezier(.22,1,.36,1)),
             visibility 0s linear var(--t-transit,420ms);
}
.etv__panneau[data-actif="1"]{
  opacity:1;transform:none;visibility:visible;
  transition-delay:0s,0s,0s;
}
.etv__panneau:focus-visible{outline:2px solid var(--etv-u3);outline-offset:6px;
  border-radius:8px}
.etv__dire{
  margin:0;
  font-family:var(--f-titre,Palatino,serif);
  font-size:clamp(1rem,1.5vw,1.14rem);line-height:1.7;
  color:rgba(var(--laine),.9);
  max-width:52ch;
}

/* ══════════ LA JAUGE ══════════
   Elle dit pourquoi ça avance tout seul. Dès qu'on prend la main, le
   JS la retire : plus rien n'avance, plus rien ne le laisse croire. */
.etv__jauge{
  height:2px;margin-top:clamp(12px,1.6vw,18px);
  border-radius:2px;overflow:hidden;
  background:var(--b10,rgba(var(--laine),.10));
}
.etv__jauge i{
  display:block;height:100%;
  background:var(--etv-u2);
  transform-origin:left center;transform:scaleX(0);
}
.etv[data-auto="0"] .etv__jauge{display:none}

/* ══════════ LE RAPPEL DE PRÉCOMMANDE (facultatif) ══════════
   Rendu SEULEMENT si l'appelant fournit une action. Par défaut il
   n'existe pas : sur une fiche, le bouton de précommande est déjà
   dans la colonne d'achat et dans la barre collante — un troisième
   sèmerait le doute sur ce qu'on est en train de commander. */
.etv__faire{
  display:inline-flex;align-items:center;gap:.5em;
  min-height:48px;margin-top:clamp(12px,1.6vw,18px);
  padding:.7rem 1.25rem;
  border:0;border-radius:999px;
  /* Même pastille, même encre, même raison : .88rem gras reste du
     texte normal, donc 4,5:1 exigés. */
  background:var(--etv-u2);color:var(--etv-encre);
  font-family:var(--f-corps,system-ui,sans-serif);
  font-size:.88rem;font-weight:700;line-height:1;
  cursor:pointer;-webkit-tap-highlight-color:transparent;
  touch-action:manipulation;
  box-shadow:0 5px 0 -1px var(--etv-u2f);
  transition:transform var(--t-survol,220ms) var(--e-sortie,cubic-bezier(.22,1,.36,1));
}
@media (hover:hover) and (pointer:fine){
  .etv__faire:hover{transform:translateY(-2px)}
}
.etv__faire:active{transform:translateY(3px);
  transition-duration:var(--t-micro,120ms)}
.etv__faire:focus-visible{outline:2px solid var(--etv-u3);outline-offset:3px}

/* ══════════ LA GARDE SOUS LA BARRE COLLANTE ══════════
   LE PIÈGE, DÉJÀ PAYÉ SUR CE PROJET : .barre-achat est en
   position:fixed, bottom:0, z-index:60, et RIEN ne réserve sa hauteur
   au bas du corps de page. Un bouton posé tout en bas se retrouve donc
   dessous : on le voit à moitié, et le doigt tape la barre.

   Ce bloc reste en flux normal et ne prend AUCUN z-index : il ne peut
   donc jamais passer par-dessus le bouton de précommande. Reste
   l'inverse — être recouvert. D'où cette garde, posée uniquement quand
   DEUX choses sont vraies (le JS s'en charge, voir « LA GARDE » dans
   etapes-vue.js) : la page porte vraiment une barre, ET rien ne suit le
   bloc. Sur les 45 fiches, le bloc est au milieu de la page : la garde
   n'y est pas posée, sinon elle creuserait 82 px de vide entre les
   étapes et la description.
   Et seulement là où la barre s'affiche : socle.css l'éteint au-dessus
   de 900 px.
   Hauteur mesurée dans socle.css : 10 + 48 + 10 = 68 px, plus l'encoche
   du téléphone, plus 14 px pour respirer. */
@media (max-width:900px){
  .etv[data-barre="1"]{
    margin-bottom:calc(82px + env(safe-area-inset-bottom,0px));
    scroll-margin-bottom:calc(82px + env(safe-area-inset-bottom,0px));
  }
}

/* ══════════ EN COLONNE, QUAND C'EST TROP ÉTROIT ══════════
   Cinq boutons sur 360 px de large, c'est 60 px chacun : le libellé y
   part en cinq lignes et la cible au doigt devient un timbre. On passe
   alors en colonne — et la pastille glisse en Y au lieu de X.

   C'est le JS qui pose data-colonne, parce que le seuil dépend du
   NOMBRE d'étapes (3 tiennent là où 5 ne tiennent pas) et qu'une
   requête média ne sait pas lire ce nombre. Il écoute matchMedia, qui
   ne se réveille qu'au franchissement du seuil : aucun coût au
   défilement, aucun écouteur de redimensionnement. */
.etv[data-colonne="1"] .etv__rang{
  grid-auto-flow:row;
  /* Une seule colonne, pleine largeur, et des lignes toutes égales :
     c'est cette égalité qui rend le calcul de la pastille juste. */
  grid-template-columns:minmax(0,1fr);
  grid-auto-columns:minmax(0,1fr);
  grid-auto-rows:minmax(52px,1fr);
}
.etv[data-colonne="1"] .etv__rang::before{
  left:var(--etv-pad);right:var(--etv-pad);width:auto;
  top:var(--etv-pad);bottom:auto;
  height:calc((100% - 2 * var(--etv-pad) - (var(--etv-n) - 1) * var(--etv-gap)) / var(--etv-n));
  transform:translateY(calc(var(--etv-i) * (100% + var(--etv-gap))));
}
.etv[data-colonne="1"] .etv__bouton{
  flex-direction:row;align-items:center;gap:.75em;
  min-height:52px;
}
.etv[data-colonne="1"] .etv__num{font-size:.66rem;opacity:.7}

/* ══════════ MOUVEMENT RÉDUIT ══════════
   On coupe le MOUVEMENT, jamais le CONTENU : les cinq étapes restent
   TOUTES là, lisibles, cliquables, atteignables au clavier. Ce qui
   disparaît, c'est uniquement ce qui bougeait tout seul — l'avance
   automatique (coupée côté JS), la jauge qui l'annonçait, et le
   glissement d'une étape à l'autre. La page devient immobile, pas
   plus courte.

   ⚠ CE BLOC NE DÉLÈGUE PLUS RIEN, ET C'EST UNE CORRECTION.
   Il portait, jusqu'au 11/08 : « socle.css ramène déjà toutes les
   transitions à 200 ms sous prefers-reduced-motion ». MESURÉ dans la
   page, avec le réglage actif : c'était FAUX. La règle est bien écrite
   dans socle.css, mais le commentaire qui la précède s'ouvre par `\*`
   au lieu de `/*` ; le sélecteur avalé court jusqu'à l'accolade
   suivante et la règle est jetée au parsage. Elle est introuvable dans
   document.styleSheets, et la pastille se mesurait encore à 0,42 s.
   La règle voisine html[data-calme="1"] *, elle, survit — mesurée à
   0 s. On ne pouvait donc pas deviner laquelle tenait : il fallait
   mesurer.
   La leçon, et la raison de ce qui suit : une protection
   d'accessibilité ne s'hérite pas, elle se pose chez soi. Réparer
   socle.css appartient à une autre main, et ce bloc n'a plus besoin
   qu'elle le soit.

   Ce qu'on NE coupe pas : l'enfoncement de 1 px sous le doigt. Ce
   n'est pas du mouvement autonome, c'est la réponse au geste — la
   supprimer rendrait le bouton muet sans rien apaiser. */
@media (prefers-reduced-motion:reduce) and (min-width:999999px) /* desarme 17/08 : data-calme est le seul interrupteur */{
  .etv__jauge{display:none}
  /* La pastille ne glisse plus : elle SAUTE à l'étape choisie. */
  .etv__rang::before{transition:none}
  /* La phrase ne monte plus de 10 px. Elle se substitue en fondu
     court : un fondu ne déplace rien à l'écran, et il évite l'à-coup
     d'une phrase qui change d'un coup sous l'œil. */
  .etv__panneau{transform:none;
    transition:opacity 200ms linear,visibility 0s linear 200ms}
  .etv__faire{transition:none}
  .etv__faire:hover{transform:none}
  /* Plus rien ne s'anime : `will-change` réserverait une couche
     mémoire pour un mouvement qui n'aura pas lieu. */
  .etv[data-bouge="1"] .etv__rang::before,
  .etv[data-bouge="1"] .etv__panneau{will-change:auto}
}

/* L'interrupteur du site : un choix explicite, donc on coupe tout,
   sans même le fondu. Les mêmes règles que ci-dessus, posées ici aussi
   — socle.css les couvre aujourd'hui (mesuré à 0 s), mais c'est
   exactement ce qu'on croyait du réglage système. */
:root[data-calme="1"] .etv__jauge{display:none}
:root[data-calme="1"] .etv__rang::before,
:root[data-calme="1"] .etv__panneau,
:root[data-calme="1"] .etv__faire{transition:none}
:root[data-calme="1"] .etv__panneau{transform:none}
:root[data-calme="1"] .etv__faire:hover{transform:none}
:root[data-calme="1"] .etv[data-bouge="1"] .etv__rang::before,
:root[data-calme="1"] .etv[data-bouge="1"] .etv__panneau{will-change:auto}
