/* ==========================================================================
   Les apparitions : Pulsor pour le texte, Gemeos pour les éléments.

   16 septembre 2026, au soir, sur demande. Relevé sur pulsor.agency (script
   slater 17824/50442.js et feuilles) et sur gemeosagency.com (feuilles
   inline et paquet @gemeosagency/gemeos-v2), courbes et durées comprises.
   Jetons dans tokens.css, table de qui entre et comment dans apparitions.js.

   Le texte, Pulsor :
   - H1 : data-load-heading, mot par mot, avec flou (site.js et styles.css) ;
   - sous-titre et étiquette du hero : data-load-sub et data-load-tag ;
   - en-tête : il descend, yPercent -125, expo.out ;
   - H2 et chapôs : data-reveal, lignes masquées ;
   - étiquettes de section : data-scramble, texte brouillé ;
   - boutons et liens de navigation : data-stagger-char (styles.css) ;
   - menu déplié : data-menu-reveal.

   Les éléments, Gemeos :
   - « monte » (sticker-move) : cartes, constats, conditions, membres,
     chiffres, boutons et prix du hero, bandeau de consentement ;
   - « glisse » (hand-move) : les tuiles d'icônes arrivent de côté en roulant ;
   - « popcorn » (popcorn-in) : les chiffres du cas éclatent lettre par lettre ;
   - panneau (tabs3-pane) : les vues de Signal ;
   - déplie : la FAQ.

   Trois garde-fous ne se retirent pas.

   1. Rien n'est masqué sans script : tout attend html[data-apparitions],
      que apparitions.js pose seulement s'il peut tenir sa promesse.
   2. L'état d'attente n'est qu'une opacité. Le point de départ du mouvement
      n'existe que dans les keyframes, en remplissage « backwards ».
   3. Sous prefers-reduced-motion, rien ne se découpe et rien ne bouge.
   ========================================================================== */

/* --------------------------------------------------------------------------
   L'état d'attente. Le texte découpé attend morceau par morceau : son
   porteur reste visible, c'est ce qu'il contient qui entre.
   -------------------------------------------------------------------------- */
html[data-apparitions] [data-apparition]:not([data-apparu]) { opacity: 0; }

html[data-apparitions] [data-apparition="lignes"]:not([data-apparu]),
html[data-apparitions] [data-apparition="titre"]:not([data-apparu]),
html[data-apparitions] [data-apparition="frappe"]:not([data-apparu]),
html[data-apparitions] [data-apparition="brouille"]:not([data-apparu]),
html[data-apparitions] [data-apparition="popcorn"]:not([data-apparu]) { opacity: 1; }

html[data-apparitions] [data-apparition="lignes"]:not([data-apparu]) .lg-mot,
html[data-apparitions] [data-apparition="titre"]:not([data-apparu]) .lg-mot,
html[data-apparitions] [data-apparition="frappe"]:not([data-apparu]) .frappe,
html[data-apparitions] [data-apparition="popcorn"]:not([data-apparu]) .pc-lettre,
html[data-apparitions] [data-apparition="brouille"]:not([data-apparu]) .br-vrai { opacity: 0; }

/* ==========================================================================
   Pulsor : le texte
   ========================================================================== */

/* --------------------------------------------------------------------------
   L'en-tête descend : yPercent -125, 1 s en expo.out, à 0,25 s. Il ne passe
   pas par l'opacité, chez eux non plus.
   -------------------------------------------------------------------------- */
html[data-apparitions] [data-apparition="entete"]:not([data-apparu]) {
  opacity: 1;
  translate: 0 var(--entete-montee);
}

[data-apparu][data-apparition="entete"] {
  animation: app-entete var(--pulsor-xl) var(--pulsor-expo-out) backwards;
  animation-delay: var(--entete-retard);
}

@keyframes app-entete {
  from { translate: 0 var(--entete-montee); }
  to   { translate: 0 0; }
}

/* --------------------------------------------------------------------------
   L'étiquette : data-load-tag, flou de 10 px, yPercent 60, 1,2 s en expo.out.
   -------------------------------------------------------------------------- */
[data-apparu][data-apparition="etiquette"] {
  animation: app-etiquette var(--sous-titre-duree) var(--sous-titre-courbe) backwards;
  animation-delay: var(--app-retard, 0ms);
}

@keyframes app-etiquette {
  from { opacity: 0; translate: 0 var(--sous-titre-montee); filter: blur(var(--texte-flou)); }
  to   { opacity: 1; translate: 0 0; filter: blur(0); }
}

/* --------------------------------------------------------------------------
   Les lignes masquées : data-split + data-reveal. Chaque ligne monte de
   110 % derrière son masque, 0,8 s en smooth-ease, 75 ms par ligne, au
   passage du haut du bloc à 90 % de l'écran. Chez Pulsor, les H2 et les
   paragraphes portent cette entrée.

   Le masque est posé mot par mot : les mots d'une même ligne partent
   ensemble, l'effet est le même, et la ligne reste libre de se recomposer.
   -------------------------------------------------------------------------- */
.app-texte { display: inline; }

.lg-masque {
  display: inline-block;
  overflow: clip;
  overflow-clip-margin: .2em;
  vertical-align: top;
}

.lg-mot { display: inline-block; }

[data-apparu][data-apparition="lignes"] .lg-mot,
[data-apparu][data-apparition="titre"] .lg-mot {
  animation: app-ligne var(--lignes-duree) var(--lignes-courbe) backwards;
  animation-delay: calc(var(--app-retard, 0ms) + var(--ligne, 0) * var(--lignes-pas));
}

@keyframes app-ligne {
  from { opacity: 1; translate: 0 var(--lignes-montee); }
  to   { opacity: 1; translate: 0 0; }
}

/* ⚠️ **Un mot qui bouge sort du découpage du dégradé de son titre**, la panne
   muette du 14 septembre : chaque mot d'un H2 porte le dégradé de son titre,
   relevé par site.js (--h2-fond) à la taille de l'emprise du texte et décalé
   de sa place. La garniture couvre les jambages, la marge du masque les
   laisse voir. */
h2 .lg-mot,
h3 .lg-mot {
  padding-block: .26em;
  margin-block: -.26em;
  /* ⚠️ Même panne sur les côtés, 17 septembre 2026 : à l'interlettrage
     serré des titres, le glyphe déborde de la boîte de sa lettre (le V de
     « Vos » était tranché à gauche) et rien ne se peint hors de la boîte.
     La garniture latérale agrandit la boîte peinte, la marge négative rend
     la place : l'approche ne bouge pas d'un pixel. */
  padding-inline: var(--garniture-glyphe);
  margin-inline: calc(-1 * var(--garniture-glyphe));
}
h2 .lg-masque { overflow-clip-margin: .3em; }
h2[style*="--h2-fond"] .lg-mot,
h3[style*="--h2-fond"] .lg-mot {
  background-image: var(--h2-fond);
  background-size: var(--fond-l) var(--fond-h);
  background-position: var(--fond-x, 0) var(--fond-y, 0);
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Les segments surlignés (constat, chatbot) gardent leur encre pleine, et
   leur fond se découvre avec leur ligne, par le bas, sur la même courbe : un
   aplat posé au-dessus de mots encore masqués se lisait comme un trou. */
h2 mark .lg-mot { background-image: none; color: inherit; }
html[data-apparitions] [data-apparition="titre"]:not([data-apparu]) mark { clip-path: inset(100% 0 0 0); }
[data-apparu][data-apparition="titre"] mark {
  animation: app-surligne var(--lignes-duree) var(--lignes-courbe) backwards;
  animation-delay: calc(var(--app-retard, 0ms) + var(--ligne, 0) * var(--lignes-pas));
}
@keyframes app-surligne {
  from { clip-path: inset(100% 0 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* --------------------------------------------------------------------------
   La frappe : les H2 et les H3, 17 septembre 2026, sur demande. Chaque lettre
   sort du flou et du transparent, l'une après l'autre (--k et --frappe-pas,
   posés par apparitions.js). Les lettres ne bougent pas : chacune porte le
   dégradé de son titre, et un décalage fausserait sa mesure.

   ⚠️ **Le dégradé du parent est découpé sur tout son texte, lettres
   transparentes comprises** : il montrerait la phrase entière avant qu'elle
   soit écrite. Il prend une taille nulle, et reste lisible par site.js.
   -------------------------------------------------------------------------- */
.lg-mot.frappe { display: inline; }
.frappe-mot { white-space: nowrap; }

[data-apparition="frappe"][style*="--h2-fond"] { background-size: 0 0 !important; }

[data-apparu][data-apparition="frappe"] .frappe {
  animation: app-frappe var(--frappe-duree) var(--frappe-courbe) backwards;
  animation-delay: calc(var(--app-retard, 0ms) + var(--k, 0) * var(--frappe-pas, 30ms));
}

@keyframes app-frappe {
  from { opacity: 0; filter: blur(var(--texte-flou)); }
  to   { opacity: 1; filter: blur(0); }
}

/* Un segment surligné se découvre de gauche à droite, au rythme de ses
   lettres. */
html[data-apparitions] [data-apparition="frappe"]:not([data-apparu]) mark { clip-path: inset(0 100% 0 0); }
[data-apparu][data-apparition="frappe"] mark {
  animation: app-surligne-frappe calc(var(--mk-n, 1) * var(--frappe-pas, 30ms) + var(--frappe-duree)) linear backwards;
  animation-delay: calc(var(--app-retard, 0ms) + var(--mk-k, 0) * var(--frappe-pas, 30ms));
}
@keyframes app-surligne-frappe {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* --------------------------------------------------------------------------
   Le texte brouillé : data-scramble. Par signe, décalé de 50 ms : un faux
   signe 80 ms, un second 80 ms puis écrasé en 160 ms, et le vrai qui se
   révèle en 300 ms, 150 ms avant la fin du second. Courbe power4.inOut.
   Les faux signes sont en négatif : fond à l'encre du texte.
   -------------------------------------------------------------------------- */
.br-mot { display: inline-block; white-space: nowrap; }
.br-lettre { display: inline-block; position: relative; }
.br-faux {
  position: absolute;
  inset: 0 auto auto 0;
  opacity: 0;
  pointer-events: none;
  background: currentColor;
  transform-origin: left center;
}
.br-faux::before { content: attr(data-c); color: var(--brouille-encre, var(--blanc)); }
.fond-sombre .br-faux { --brouille-encre: #111111; }

[data-apparu][data-apparition="brouille"] .br-faux-1 {
  animation: app-brouille-flash var(--brouille-faux) linear;
  animation-delay: calc(var(--app-retard, 0ms) + var(--k, 0) * var(--brouille-pas));
}
[data-apparu][data-apparition="brouille"] .br-faux-2 {
  animation: app-brouille-ecrase calc(var(--brouille-faux) + var(--brouille-sortie)) linear;
  animation-delay: calc(var(--app-retard, 0ms) + var(--k, 0) * var(--brouille-pas) + var(--brouille-faux));
}
[data-apparu][data-apparition="brouille"] .br-vrai {
  animation: app-brouille-vrai var(--brouille-revele) var(--pulsor-power4-inout) backwards;
  animation-delay: calc(var(--app-retard, 0ms) + var(--k, 0) * var(--brouille-pas) + var(--brouille-faux) * 2 + var(--brouille-sortie) - 150ms);
}
@keyframes app-brouille-flash { from, to { opacity: 1; transform: scaleX(1); } }
@keyframes app-brouille-ecrase {
  0%      { opacity: 1; transform: scaleX(1); animation-timing-function: linear; }
  33.333% { opacity: 1; transform: scaleX(1); animation-timing-function: var(--pulsor-power4-inout); }
  100%    { opacity: 0; transform: scaleX(0); }
}
@keyframes app-brouille-vrai { from { opacity: 0; } to { opacity: 1; } }

/* --------------------------------------------------------------------------
   Le menu déplié : data-menu-reveal. Chaque lien descend d'un em et se
   révèle, 0,45 s en smooth-ease, 40 ms par rang après 0,25 s. Le menu passe
   de « display: none » à « flex », donc l'entrée rejoue à chaque ouverture.
   -------------------------------------------------------------------------- */
html[data-apparitions] .entete-nav[data-ouvert="oui"] > * {
  animation: app-menu var(--menu-duree) var(--pulsor-smooth) backwards;
  animation-delay: calc(var(--menu-retard) + var(--rang-menu, 0) * var(--menu-pas));
}
.entete-nav > :nth-child(2) { --rang-menu: 1; }
.entete-nav > :nth-child(3) { --rang-menu: 2; }
.entete-nav > :nth-child(4) { --rang-menu: 3; }
.entete-nav > :nth-child(5) { --rang-menu: 4; }

@keyframes app-menu {
  from { opacity: 0; translate: 0 var(--menu-montee); }
  to   { opacity: 1; translate: 0 0; }
}

/* ==========================================================================
   Gemeos : les éléments
   ========================================================================== */

/* --------------------------------------------------------------------------
   « monte » : sticker-move. opacity 0 et translate(0, 120%), 1,1 s,
   cubic-bezier(.22, 1, .36, 1). Chez eux 200, 400 puis 600 ms : le pas est
   de 200 ms.
   -------------------------------------------------------------------------- */
[data-apparu][data-apparition="monte"] {
  animation: app-monte var(--monte-duree) var(--monte-courbe) backwards;
  animation-delay: calc(var(--app-retard, 0ms) + var(--rang, 0) * var(--monte-pas));
}

@keyframes app-monte {
  from { opacity: 0; translate: 0 var(--monte-distance); }
  to   { opacity: 1; translate: 0 0; }
}

/* --------------------------------------------------------------------------
   « glisse » : hand-move. translate(120%) vers translate(0) rotate(-360deg),
   600 ms, cubic-bezier(.175, .885, .32, 1.275), à 400 ms. L'icône arrive de
   côté en roulant. Le tour complet finit à l'identique du repos : rien ne
   saute quand l'animation se retire.
   -------------------------------------------------------------------------- */
[data-apparu][data-apparition="glisse"] {
  animation: app-glisse var(--glisse-duree) var(--glisse-courbe) backwards;
  animation-delay: calc(var(--app-retard, var(--glisse-retard)) + var(--rang, 0) * var(--monte-pas));
}

@keyframes app-glisse {
  from { opacity: 0; translate: var(--glisse-x) 0; rotate: 0deg; }
  to   { opacity: 1; translate: 0 0; rotate: -360deg; }
}

/* --------------------------------------------------------------------------
   « popcorn » : popcorn-in. Chaque lettre part de translateY(30px) scale(0),
   400 ms, cubic-bezier(.34, 1.56, .64, 1). L'ordre est tiré au hasard, le pas
   vaut min(40 ms, 600 ms / nombre de lettres).
   -------------------------------------------------------------------------- */
.pc-mot { display: inline-block; white-space: nowrap; }
.pc-lettre { display: inline-block; }

[data-apparu][data-apparition="popcorn"] .pc-lettre {
  animation: app-popcorn var(--popcorn-duree) var(--popcorn-courbe) backwards;
  animation-delay: calc(var(--app-retard, 0ms) + var(--pc-retard, 0ms));
}

@keyframes app-popcorn {
  from { opacity: 1; translate: 0 var(--popcorn-montee); scale: 0; }
  to   { opacity: 1; translate: 0 0; scale: 1; }
}

/* --------------------------------------------------------------------------
   Le panneau : tabs3-pane-in et tabs3-pane-out, sur les vues de Signal. La
   vue qui arrive glisse de 150 % en 1,1 s, son contenu se révèle en 400 ms
   après 600 ms ; celle qui part glisse de -150 % et son contenu s'éteint en
   200 ms. Armé au premier changement de vue seulement.
   -------------------------------------------------------------------------- */
html[data-apparitions] .signal-vues[data-bascule] .signal-scene { overflow: clip; }

html[data-apparitions] .signal-vues[data-bascule] .signal-vue {
  animation: app-panneau-sort var(--panneau-duree) var(--panneau-courbe) forwards;
  transition:
    opacity 0s linear var(--panneau-duree),
    visibility 0s linear var(--panneau-duree);
}

html[data-apparitions] .signal-vues[data-bascule] .signal-vue > * {
  opacity: 0;
  transition: opacity var(--panneau-sortie-duree) linear;
}

html[data-apparitions] .signal-vues[data-bascule] .signal-vue[data-actif] {
  animation-name: app-panneau-entre;
  transition-delay: 0s;
}

html[data-apparitions] .signal-vues[data-bascule] .signal-vue[data-actif] > * {
  opacity: 1;
  transition: opacity var(--panneau-contenu-duree) linear var(--panneau-contenu-retard);
}

@keyframes app-panneau-entre {
  from { translate: var(--panneau-distance) 0; }
  to   { translate: 0 0; }
}

@keyframes app-panneau-sort {
  from { translate: 0 0; }
  to   { translate: calc(var(--panneau-distance) * -1) 0; }
}

/* --------------------------------------------------------------------------
   « déplie » : la FAQ, grid-template-rows chez eux, 600 ms,
   cubic-bezier(.215, .61, .355, 1). Le texte reste dans le HTML replié, et un
   navigateur qui ne sait pas animer la hauteur ouvre d'un coup.
   -------------------------------------------------------------------------- */
@supports (interpolate-size: allow-keywords) {
  html[data-apparitions] .faq-item,
  html[data-apparitions] .cas3-volet { interpolate-size: allow-keywords; }

  html[data-apparitions] .faq-item::details-content,
  html[data-apparitions] .cas3-volet::details-content {
    block-size: 0;
    overflow: clip;
    transition:
      block-size var(--deplie-duree) var(--deplie-courbe),
      content-visibility var(--deplie-duree) allow-discrete;
  }

  html[data-apparitions] .faq-item[open]::details-content,
  html[data-apparitions] .cas3-volet[open]::details-content { block-size: auto; }
}

/* Le bandeau de consentement monte à chaque affichage. */
html[data-apparitions] .consentement:not([hidden]) {
  animation: app-monte var(--monte-duree) var(--monte-courbe) backwards;
}

@media (prefers-reduced-motion: reduce) {
  [data-apparition], [data-apparition] *,
  .consentement, .signal-vue, .faq-item::details-content, .cas3-volet::details-content,
  .entete-nav > *, h2 mark { animation: none !important; }
}
