/* ============================================================
   SIXSKIN DESIGN — Hero TMAX piloté par le scroll
   ============================================================
   Fichier isolé, chargé UNIQUEMENT sur l'accueil (index.php, via
   $extraCss) — n'affecte aucune autre page. Toute la mise en scène
   (tailles, effets, disposition) vit ici ; les VALEURS de timing/texte
   pilotées par JS vivent dans la config en tête de assets/js/tmax-scroll.js
   — un seul endroit à modifier pour ajuster l'expérience (voir §25 du
   cahier des charges : « paramètres facilement modifiables »).

   Structure :
     .tmax-hero            → zone de scroll totale (400vh par défaut)
     .tmax-scene            → scène sticky (100vh), tout se joue dedans
       .tmax-watermark       → immense "SIXSKIN" en arrière-plan (parallaxe)
       .tmax-video-wrap       → vidéo TMAX (currentTime piloté par JS)
       .tmax-text-layer        → séquences de texte (une par étape du scroll)
       .tmax-scroll-hint        → "faites défiler" (uniquement séquence 1)
   ============================================================ */

/* Titre H1 réel (SEO/lecteurs d'écran), non visible à l'écran — la
   narration animée n'est pas un titre de document unique, donc le H1
   porte le message évergreen pendant que le texte choréographié reste
   du contenu visuel non structurant. Motif standard "visually hidden". */
.tmax-sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* ── Zone de scroll totale ── */
.tmax-hero {
  --tmax-vh: 400vh; /* 300–450vh recommandé — modifiable ici sans toucher au JS */
  position: relative;
  height: var(--tmax-vh);
  background: #000;
  /* La section démarre sous le header transparent, pas sous le padding
     global de <main> (voir règle body.page-accueil main plus bas) : elle
     doit occuper tout le haut de la fenêtre pour l'effet immersif. */
}

/* Compense le padding-top global de <main> UNIQUEMENT sur l'accueil, pour
   que le hero démarre à y=0 sous un header transparent — sans jamais
   toucher à --header-h ni à <main> pour les autres pages. */
body.page-accueil main { padding-top: 0; }

/* ── Scène sticky : c'est elle qui reste fixe pendant tout le scroll ── */
.tmax-scene {
  position: sticky;
  top: 0;
  height: 100vh;
  min-height: 100vh;
  overflow: hidden;
  background: #000;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Voile qui s'assombrit légèrement en fin de séquence (§13/§18) — un seul
   calque partagé plutôt qu'un effet par élément, pour rester cohérent. */
.tmax-scene::after {
  content: '';
  position: absolute;
  inset: 0;
  background: #000;
  opacity: var(--tmax-voile, 0);
  z-index: 4;
  pointer-events: none;
  will-change: opacity;
}

/* Conteneur commun watermark + vidéo : c'est LUI qui reçoit l'animation
   d'intro au chargement ET le léger effet de sortie (échelle/montée) en
   fin de séquence (§13/§18) — jamais le même élément que celui ciblé par
   les styles inline continus du JS pendant la fenêtre d'intro, pour ne
   jamais faire entrer en conflit une keyframe CSS et une écriture JS sur
   la même propriété au même instant (voir tmax-scroll.js, introEnCours). */
.tmax-stage {
  position: absolute;
  inset: 0;
  z-index: 0;
  will-change: transform;
}
.tmax-stage.tmax-intro-run {
  animation: tmax-intro-in .9s cubic-bezier(.16,.8,.3,1) both;
}
@keyframes tmax-intro-in {
  from { opacity: 0; transform: scale(1.04) translateY(10px); }
  to   { opacity: 1; transform: scale(1) translateY(0); }
}

/* ── Typo géante en filigrane, derrière la vidéo (§15) ── */
.tmax-watermark {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
  font-family: var(--fd);
  font-weight: 800;
  text-transform: uppercase;
  font-size: clamp(110px, 26vw, 420px);
  letter-spacing: .01em;
  color: var(--white);
  opacity: .045;
  white-space: nowrap;
  user-select: none;
  will-change: transform;
}

/* ── Vidéo TMAX ── */
.tmax-video-wrap {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  will-change: transform, opacity;
}
.tmax-video,
.tmax-poster-fallback {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
  pointer-events: none;
}
.tmax-poster-fallback { display: none; }
.tmax-video-wrap.tmax-video-error .tmax-video { display: none; }
.tmax-video-wrap.tmax-video-error .tmax-poster-fallback { display: block; }

/* Vignettage : assombrit légèrement les bords pour donner de la profondeur
   à la vidéo, qui remplit maintenant tout le cadre (§27bis, object-fit:cover)
   — un calque dédié plutôt qu'un filtre CSS sur la vidéo elle-même, pour ne
   jamais toucher aux couleurs du rendu. */
.tmax-video-wrap::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  background:
    radial-gradient(ellipse 62% 58% at 50% 48%, transparent 45%, #000 100%),
    linear-gradient(to bottom, rgba(0,0,0,.35) 0%, transparent 14%, transparent 82%, rgba(0,0,0,.55) 100%);
  pointer-events: none;
}

/* ── Fumée légère (§29) : ambiance pure, jamais pilotée par le scroll —
   3 nappes floutées qui dérivent lentement en boucle, mix-blend-mode:screen
   pour un rendu clair qui se fond dans la vidéo sans jamais l'assombrir.
   Volontairement discrète (opacité ~.1) : un fumigène léger, pas un écran
   de fumée. Au-dessus de la vidéo (z-index 2, après elle dans le DOM),
   en dessous du texte (.tmax-text-layer reste à z-index 3). ── */
.tmax-smoke-layer {
  position: absolute;
  inset: 0;
  z-index: 2;
  overflow: hidden;
  pointer-events: none;
}
.tmax-smoke {
  position: absolute;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(240,237,232,.9) 0%, rgba(240,237,232,0) 72%);
  filter: blur(38px);
  mix-blend-mode: screen;
  opacity: .1;
  will-change: transform, opacity;
}
.tmax-smoke-1 { width: 40vw; height: 40vw; left: 6%;  top: 58%; animation: tmax-smoke-1 24s ease-in-out infinite; }
.tmax-smoke-2 { width: 32vw; height: 32vw; right: 4%; top: 16%; animation: tmax-smoke-2 28s ease-in-out infinite; animation-delay: -7s; }
.tmax-smoke-3 { width: 26vw; height: 26vw; left: 40%; top: 66%; animation: tmax-smoke-3 32s ease-in-out infinite; animation-delay: -16s; }
@keyframes tmax-smoke-1 {
  0%, 100% { transform: translate(0,0) scale(1); opacity: .08; }
  50%      { transform: translate(4vw,-3vh) scale(1.18); opacity: .15; }
}
@keyframes tmax-smoke-2 {
  0%, 100% { transform: translate(0,0) scale(1); opacity: .07; }
  50%      { transform: translate(-3vw,3vh) scale(1.12); opacity: .13; }
}
@keyframes tmax-smoke-3 {
  0%, 100% { transform: translate(0,0) scale(1); opacity: .06; }
  50%      { transform: translate(3vw,2vh) scale(1.1); opacity: .12; }
}

/* ── Itinéraire tracé au fil du scroll (§29) : trait fin qui se dessine
   progressivement (stroke-dashoffset piloté en JS par appliquerItineraire,
   proportionnel à la progression du scroll) — évoque un parcours/une
   trajectoire plutôt qu'un simple décor statique. Au-dessus de la vidéo et
   de la fumée, en dessous du texte. ── */
.tmax-route {
  position: absolute;
  inset: 0;
  z-index: 3;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.tmax-route path {
  fill: none;
  stroke: var(--orange);
  stroke-width: 2;
  stroke-linecap: round;
  opacity: .5;
  vector-effect: non-scaling-stroke;
}

/* ── Indicateur de scroll (séquence 1 uniquement) ── */
.tmax-scroll-hint {
  position: absolute;
  left: 50%;
  bottom: 36px;
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  opacity: 0;
  will-change: opacity;
}
.tmax-scroll-hint span:first-child {
  font-family: var(--fd);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--muted);
}
.tmax-scroll-line {
  width: 1px;
  height: 32px;
  background: linear-gradient(to bottom, var(--orange), transparent);
  animation: tmax-hint-glisse 1.6s ease-in-out infinite;
}
@keyframes tmax-hint-glisse {
  0%   { transform: scaleY(.3); transform-origin: top; opacity: .4; }
  50%  { transform: scaleY(1);  transform-origin: top; opacity: 1; }
  100% { transform: scaleY(.3); transform-origin: bottom; opacity: .4; }
}

/* ── Séquences de texte ── */
.tmax-text-layer {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  padding: 0 6vw;
}
.tmax-seq {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  opacity: 0;
  will-change: opacity, transform, filter;
}

.tmax-eyebrow {
  font-family: var(--fd);
  font-size: clamp(14px, 1.8vw, 20px);
  font-weight: 800;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--orange);
  margin-bottom: 18px;
}
.tmax-title-lg {
  position: relative;
  font-family: var(--fd);
  font-weight: 800;
  text-transform: uppercase;
  line-height: .92;
  letter-spacing: -.01em;
  font-size: clamp(52px, 9vw, 150px);
  margin: 0;
}
.tmax-title-md {
  position: relative;
  font-family: var(--fd);
  font-weight: 800;
  text-transform: uppercase;
  line-height: .95;
  font-size: clamp(38px, 6vw, 84px);
  margin: 0;
}

/* Trace de pneu légère (§29) sous les grands titres scrubbés : largeur
   pilotée par --tmax-o, la même valeur d'opacité 0–1 déjà calculée par
   séquence dans appliquerSequences() (tmax-scroll.js) — pas de nouvel état
   à maintenir, le trait suit simplement l'apparition/disparition du texte
   qu'il accompagne. Repli à 0 (invisible) si --tmax-o n'est jamais définie
   (texte hors scène scrubbée, ex. le bloc .tmax-landing qui réutilise ces
   mêmes classes typographiques plus bas dans la page). */
.tmax-scene .tmax-text-layer .tmax-title-lg::after,
.tmax-scene .tmax-text-layer .tmax-title-md::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: -12px;
  width: min(240px, 70%);
  height: 7px;
  transform: translateX(-50%) scaleX(var(--tmax-o, 0));
  transform-origin: center;
  background-image: repeating-linear-gradient(
    76deg,
    rgba(240, 237, 232, .55) 0px, rgba(240, 237, 232, .55) 5px,
    transparent 5px, transparent 13px
  );
  opacity: .6;
  pointer-events: none;
}
.tmax-subtitle {
  font-family: var(--fd);
  font-size: clamp(16px, 2.2vw, 24px);
  font-weight: 800;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--white);
  margin-top: 16px;
}
.tmax-kicker {
  font-family: var(--fd);
  font-size: clamp(13px, 1.7vw, 18px);
  font-weight: 800;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--white);
  margin-bottom: 20px;
}
.tmax-features {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  display: flex;
  gap: 24px;
  flex-wrap: wrap;
  justify-content: center;
  font-size: clamp(15px, 1.8vw, 19px);
  font-weight: 800;
  color: var(--white);
}
.tmax-features li { position: relative; padding-left: 16px; }
.tmax-features li::before {
  content: ''; position: absolute; left: 0; top: 8px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--orange);
}

/* Séquence 4 : "Pensez-le." / "Nous le créons." de part et d'autre */
.tmax-seq-4 { flex-direction: row; justify-content: space-between; align-items: center; }
.tmax-seq-4 .tmax-title-md { max-width: 46%; }
.tmax-pos-left  { text-align: left; }
.tmax-pos-right { text-align: right; color: var(--orange); }

/* CTA du bloc final (.tmax-landing, plus bas dans ce fichier) — pointer-events:auto
   n'est plus indispensable ici (ce bloc n'est plus sous un calque
   pointer-events:none), mais reste inoffensif et garantit que les liens
   restent cliquables quelle que soit sa position future. */
.tmax-ctas {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 36px;
  pointer-events: auto;
}
.tmax-ctas .btn-outline { border-color: rgba(240,237,232,.3); color: var(--white); }
.tmax-ctas .btn-outline:hover { border-color: var(--orange); }

/* ── Bloc final statique et persistant (§29), juste après .tmax-hero dans
   le DOM — voir le docblock de accueil_bloc_hero() dans blocs-accueil.php.
   Fond = dernière image de la vidéo (poster), assombrie pour la lisibilité
   du texte, dans le même esprit que le vignettage du hero ci-dessus. Un
   bloc de page tout à fait normal : pas de position sticky, pas de scrub —
   il apparaît une fois (voir le système générique de révélation au
   défilement, tout en bas de ce fichier) puis reste affiché normalement. ── */
.tmax-landing {
  position: relative;
  min-height: 72vh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: #000;
  padding: 90px 5vw;
}
.tmax-landing-bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center 30%;
  z-index: 0;
}
.tmax-landing-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 70% 65% at 50% 45%, rgba(0,0,0,.35) 0%, rgba(0,0,0,.82) 100%),
    linear-gradient(to bottom, rgba(0,0,0,.55) 0%, rgba(0,0,0,.22) 30%, rgba(0,0,0,.6) 100%);
}
.tmax-landing-inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}
.tmax-landing .tmax-eyebrow { margin-bottom: 18px; }
.tmax-landing .tmax-title-lg { font-size: clamp(40px, 7vw, 110px); }
.tmax-landing .tmax-ctas { margin-top: 36px; }
@media (max-width: 640px) {
  .tmax-landing { min-height: 58vh; padding: 64px 6vw; }
  .tmax-landing .tmax-ctas { flex-direction: column; width: 100%; max-width: 320px; }
  .tmax-landing .tmax-ctas .btn { width: 100%; justify-content: center; }
}

/* ── En-tête transparent au-dessus du hero (basculé en JS) ── */
body.page-accueil #header.tmax-header-transparent {
  background: transparent;
  backdrop-filter: none;
  border-bottom-color: transparent;
}
body.page-accueil #header {
  transition: background .3s ease, backdrop-filter .3s ease, border-color .3s ease;
}

/* ── Micro-interaction CTA (léger effet magnétique desktop, §17) ── */
@media (hover: hover) and (pointer: fine) {
  .tmax-ctas .btn { transition: transform .18s ease, background .2s, border-color .2s; }
  .tmax-ctas .btn:hover { transform: translateY(-2px) scale(1.02); }
}

/* ── Responsive : tablette ── */
@media (max-width: 1024px) {
  .tmax-watermark { font-size: clamp(90px, 30vw, 300px); }
  .tmax-seq-4 .tmax-title-md { max-width: 48%; }
}

/* ── Responsive : mobile — vraie recomposition, pas juste une réduction (§20) ── */
@media (max-width: 640px) {
  .tmax-hero { --tmax-vh: 340vh; }
  .tmax-watermark { font-size: 78px; opacity: .05; }
  .tmax-text-layer { padding: 0 7vw; }
  .tmax-seq { justify-content: flex-end; padding-bottom: 14vh; }
  .tmax-title-lg { font-size: clamp(40px, 13vw, 64px); }
  .tmax-title-md { font-size: clamp(30px, 10vw, 46px); }
  /* Séquence 4 : empilée plutôt que gauche/droite, illisible en colonne étroite */
  .tmax-seq-4 { flex-direction: column; gap: 14px; justify-content: flex-end; }
  .tmax-seq-4 .tmax-title-md { max-width: 100%; text-align: center !important; }
  .tmax-features { flex-direction: column; gap: 8px; align-items: center; }
  .tmax-scroll-hint { bottom: 20px; }
  .tmax-ctas { flex-direction: column; width: 100%; max-width: 320px; }
  .tmax-ctas .btn { width: 100%; justify-content: center; }
}

/* ============================================================
   Accessibilité — prefers-reduced-motion (§22)
   Le scroll ne pilote plus rien : présentation statique, image poster,
   titre et CTA toujours visibles. Le moteur JS ne démarre pas du tout
   dans ce mode (voir tmax-scroll.js).
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .tmax-hero { height: auto; }
  body.page-accueil main { padding-top: calc(var(--header-h) + var(--banniere-h, 0px)); }
  .tmax-scene {
    position: relative;
    height: 90vh;
    min-height: 560px;
  }
  .tmax-watermark { display: none; }
  .tmax-video { display: none; }
  .tmax-poster-fallback { display: block; }
  .tmax-video-wrap::before { background: linear-gradient(rgba(0,0,0,.35), rgba(0,0,0,.65)); }
  .tmax-scroll-hint { display: none; }
  .tmax-seq { position: relative; opacity: 0 !important; }
  .tmax-scene::after { display: none; }
  /* Décors purement animés (§29), sans intérêt statique : masqués plutôt
     que figés dans un état arbitraire. Le bloc .tmax-landing, lui, n'a
     jamais dépendu du scroll ni de ce mode — il reste affiché normalement. */
  .tmax-smoke-layer, .tmax-route { display: none; }
}

/* ── Filet de sécurité sans JavaScript : la vidéo affiche son poster
   (attribut natif), rien ne dépend du scroll. Le bloc .tmax-landing est un
   élément de page normal (voir accueil_bloc_hero()) : il reste affiché
   sans aucune règle particulière ici. ── */
.no-js .tmax-hero { height: auto; }
.no-js .tmax-scene { position: relative; height: 90vh; min-height: 560px; }
.no-js .tmax-seq { position: relative; opacity: 0; }
.no-js .tmax-scroll-hint { display: none; }
.no-js .tmax-smoke-layer, .no-js .tmax-route { display: none; }

/* ── Léger polish hover de la grille de catégories juste en dessous du
   hero (§19) — additif uniquement, n'affecte pas .cat-card ailleurs sur
   le site puisque ce fichier ne charge que sur l'accueil. ── */
body.page-accueil .cat-card {
  position: relative;
  overflow: hidden;
  transition: border-color .25s, transform .25s, background .25s;
}
body.page-accueil .cat-card.has-img .cat-img {
  transition: transform .4s ease, filter .4s ease;
}
body.page-accueil .cat-card:hover.has-img .cat-img {
  transform: scale(1.06);
}
body.page-accueil .cat-card .cat-reveal {
  display: block;
  font-family: var(--fd);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--orange);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .25s, transform .25s;
  margin-top: 6px;
}
body.page-accueil .cat-card:hover .cat-reveal,
body.page-accueil .cat-card:focus-visible .cat-reveal {
  opacity: 1;
  transform: translateY(0);
}

/* ============================================================
   Révélation générique au défilement — page d'accueil (§29, enrichi §30)
   ============================================================
   Chaque bloc de l'accueil (catégories, comment ça marche, promos,
   bandeau de réassurance, fidélité, créations, Instagram, avis, blocs
   libres, et le nouveau .tmax-landing) apparaît quand il entre dans
   l'écran, au lieu d'être simplement déjà là. Ciblage et bascule des
   classes : voir la seconde IIFE en fin de tmax-scroll.js.

   Sécurité : la classe .reveal-bloc (qui démarre à opacity:0) n'est JAMAIS
   présente dans le HTML généré par PHP — elle n'est ajoutée que par ce
   JS. Résultat : si JavaScript ne s'exécute pas du tout (no-js), aucun
   bloc n'est jamais masqué — tout reste visible par défaut, sans règle
   .no-js particulière à écrire ici (repli sûr « progressive enhancement »,
   même principe que le filet de sécurité du hero juste au-dessus).

   Règle d'or (déjà apprise à la dure, §29) : le bloc .reveal-bloc
   LUI-MÊME — l'élément qui porte l'id (#categories, #nos-creations…) et
   qui peut donc être la cible d'un défilement natif vers ancre — ne doit
   JAMAIS recevoir de transform pendant sa transition : getBoundingClientRect
   suit les transforms, et un défilement vers ancre déclenché pendant cette
   fenêtre serait refaussé. Le bloc lui-même reste donc strictement
   opacity-only. En revanche, un ENFANT du bloc peut utiliser transform
   librement : le transform d'un enfant n'affecte jamais le
   getBoundingClientRect() de son parent — c'est ce que le "fondu
   explosif" (§30) exploite ci-dessous pour rester à la fois énergique et
   sûr pour le défilement vers ancre.
   ============================================================ */
.reveal-bloc {
  /* Pas de transition ici : cette règle ne doit s'appliquer qu'une seule
     fois, au moment où le JS ajoute la classe (bloc encore hors écran, la
     plupart du temps). Si "transition" était déclaré sur cette règle de
     base, le passage implicite opacity:1 (état par défaut avant l'ajout de
     la classe) → opacity:0 serait lui-même animé — un fondu de sortie
     parasite au chargement, avant même toute apparition. */
  opacity: 0;
}
.reveal-bloc.reveal-visible {
  opacity: 1;
}
/* #tmax-landing (conclusion statique du hero, voir §29) : garde son fondu
   simple déjà en place sur le bloc lui-même — ce n'est pas "un bloc après
   la vidéo" au sens de la demande §30, mais la conclusion de la vidéo. Pas
   de "pop" d'enfants pour lui (juste en dessous), donc il a besoin de
   porter lui-même une transition visible. */
#tmax-landing.reveal-visible {
  transition: opacity .7s ease-out;
}

/* "Fondu explosif" (§30, demande d'Adrien) : les enfants directs de
   chaque AUTRE bloc révélé entrent avec un sursaut d'échelle (léger
   dépassement puis stabilisation), un flou qui se dissipe et un
   glissement vers le haut — plus énergique qu'un simple fondu. Porté
   entièrement par les ENFANTS (jamais par .reveal-bloc lui-même, voir
   plus haut) : le bloc parent passe à opacity:1 instantanément (aucune
   transition sur lui — sinon son propre fondu se cumulerait avec celui,
   déjà présent, de ses enfants, et donnerait un rendu flou/plus lent que
   prévu au lieu d'un seul fondu net) et ce sont ses enfants qui portent
   tout le mouvement visible. */
.reveal-bloc.reveal-visible:not(#tmax-landing) > * {
  animation: reveal-pop .75s cubic-bezier(.16, 1, .3, 1) both;
}
.reveal-bloc.reveal-visible:not(#tmax-landing) > *:nth-child(2) { animation-delay: .08s; }
.reveal-bloc.reveal-visible:not(#tmax-landing) > *:nth-child(3) { animation-delay: .16s; }
@keyframes reveal-pop {
  0%   { opacity: 0; transform: scale(.85) translateY(28px); filter: blur(7px); }
  60%  { opacity: 1; transform: scale(1.025) translateY(0); filter: blur(0); }
  100% { opacity: 1; transform: scale(1) translateY(0); filter: blur(0); }
}
@media (prefers-reduced-motion: reduce) {
  .reveal-bloc { opacity: 1; transition: none; }
  .reveal-bloc.reveal-visible > * { animation: none; opacity: 1; transform: none; filter: none; }
}
