/**
 * getrelock.com — page « liste d'attente » (`/waitlist/`, `/fr/waitlist/`).
 *
 * Une seule colonne, centrée, un seul geste possible : entrer une adresse.
 * Tout ce qui n'y mène pas a été retiré — pas de capture d'écran, pas de
 * seconde section, pas de deuxième bouton.
 *
 * TOUT LE MOUVEMENT DE LA PAGE VIT ICI, en CSS, sans lecteur d'animation
 * embarqué. Chaque effet est un tracé SVG ou une transformation composée
 * (`opacity`, `translate`, `scale`, `rotate`, `stroke-dashoffset`) : le GPU
 * les prend en charge, rien ne touche au layout, et le poids ajouté à la page
 * est de zéro octet de JavaScript tiers.
 *
 * Les états sont portés par des classes posées par `waitlist.js` :
 *   .is-engaged  le visiteur a touché le champ — on cesse de l'appâter
 *   .is-ready    l'adresse saisie est valide — le bouton s'anime plus fort
 *   .is-busy     envoi en cours — le libellé cède la place au rouet
 *   .is-error    refus — le champ tremble
 *   .is-done     (sur .wl__slot) le sceau remplace le formulaire
 *
 * Le chemin SANS JavaScript passe, lui, par `#joined:target` : le Worker
 * redirige vers `…/waitlist/?joined=ok#joined`, et le panneau de confirmation
 * s'affiche sur la seule foi de l'ancre. Les animations démarrent d'elles-mêmes
 * au passage en `display: block`, donc les deux chemins jouent la même scène.
 *
 * Aucune valeur en dur : les tokens viennent de `styles.css` (spec dans
 * `DESIGN.md`). Le chrome partagé (en-tête, pied, boutons) vient de `pages.css`.
 */

.wl {
  padding-block: clamp(96px, 14vw, 176px) var(--section-y);
  /* La section PORTE l'ancre `#joined`, et c'est délibéré : le navigateur
     défile jusqu'à la cible d'une ancre. Posée sur le panneau de confirmation,
     elle emmenait le visiteur au milieu de la page, hero disparu, panneau collé
     sous l'en-tête. Posée sur la section, la cible est le haut de la page — le
     défilement ne déplace donc presque rien, et le sceau reste à sa place.
     La marge dégage l'en-tête collant. */
  scroll-margin-top: 84px;
}

.wl__inner {
  max-width: 620px;
  margin-inline: auto;
  text-align: center;
}

/* ── Entrée du hero ───────────────────────────────────────────────────────
   Cascade purement CSS, jouée une fois. Les délais sont en `nth-child` et non
   en variable inline : la CSP `style-src 'self'` interdit tout attribut
   `style` dans le balisage. Le titre a sa propre courbe (`wl-title`) : il
   arrive de plus bas et se DÉFLOUTE, ce qui lui donne le poids d'une
   accroche au lieu d'un simple fondu. */

.wl__hero > * {
  animation: rl-enter var(--t-4) var(--ease) both;
}

.wl__hero > :nth-child(2) { animation: wl-title 760ms var(--ease) 60ms both; }
.wl__hero > :nth-child(3) { animation-delay: 190ms; }
.wl__hero > :nth-child(4) { animation-delay: 290ms; }
.wl__hero > :nth-child(5) { animation-delay: 390ms; }
.wl__hero > :nth-child(6) { animation-delay: 470ms; }

@keyframes wl-title {
  from {
    opacity: 0;
    translate: 0 22px;
    filter: blur(12px);
  }
  to {
    opacity: 1;
    translate: 0 0;
    filter: blur(0);
  }
}

.wl h1 {
  margin: var(--s-5) 0 0;
  font-family: var(--font-display);
  /* Le titre a fondu à trois mots : il peut donc monter en taille sans
     déborder. Une accroche courte composée petit se lit comme une légende. */
  font-size: clamp(2.7rem, 7.4vw, 4.5rem);
  font-weight: 700;
  line-height: 1.02;
  letter-spacing: -0.042em;
  text-wrap: balance;
}

/* La seconde ligne du titre — celle qui porte la promesse. Le dégradé s'arrête
   à elle : appliqué à tout le titre, il ferait perdre en lisibilité ce qu'il
   gagne en effet. */
.wl h1 span {
  display: block;
  background: linear-gradient(180deg, var(--violet-200), var(--violet-400));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.wl__lede {
  margin: var(--s-5) auto 0;
  max-width: 47ch;
  color: var(--text-secondary);
  font-size: clamp(1.02rem, 1.6vw, 1.14rem);
  line-height: 1.62;
}

/* ── Le bloc qui bascule ──────────────────────────────────────────────────
   Formulaire et sceau de confirmation occupent la MÊME cellule de grille : le
   second ne pousse donc rien vers le bas en apparaissant, il prend la place du
   premier. Sans ça, la page sauterait au moment précis où l'on veut qu'elle
   récompense. */

.wl__slot {
  display: grid;
  margin-top: var(--s-8);
}

.wl__slot > * {
  grid-area: 1 / 1;
}

/* ── Le formulaire ────────────────────────────────────────────────────────
   Une rangée sur grand écran, deux lignes empilées sous 560 px : un champ
   e-mail sous 260 px de large tronque la moitié des adresses pendant la
   frappe, ce qui est précisément le moment où l'on veut se relire. */

.wl__form {
  display: flex;
  gap: var(--s-3);
  text-align: start;
  transition:
    opacity var(--t-3) var(--ease),
    scale var(--t-3) var(--ease);
}

.wl__slot.is-done .wl__form,
#joined:target .wl__form {
  opacity: 0;
  scale: 0.96;
  pointer-events: none;
}

.wl__field {
  position: relative;
  flex: 1;
  min-width: 0;
}

/* Le libellé n'est pas affiché — le champ est seul, son intention est évidente —
   mais il reste dans le DOM : un lecteur d'écran annonce « E-mail, zone de
   saisie », là où un `placeholder` seul n'annonce rien de fiable. */
.wl__label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.wl__input {
  width: 100%;
  min-height: 52px;
  /* La réserve de droite est la place du témoin de validité. Elle est prise
     dès le départ pour que l'apparition du crochet ne décale pas le texte
     déjà saisi. */
  padding: 0 var(--s-9) 0 var(--s-5);
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-3);
  font-family: var(--font-sans);
  font-size: 1rem;
  transition:
    border-color var(--t-2) var(--ease),
    background var(--t-2) var(--ease),
    box-shadow var(--t-2) var(--ease);
}

.wl__input::placeholder {
  color: var(--text-tertiary);
}

.wl__input:hover {
  border-color: var(--border-hover);
}

.wl__input:focus {
  outline: none;
  background: var(--surface-alt);
  border-color: var(--border-accent);
  box-shadow: 0 0 0 4px var(--accent-quiet);
}

/* Témoin de validité : le crochet se TRACE au moment où l'adresse devient
   plausible. C'est la plus petite récompense possible, et elle arrive avant
   même le clic — elle dit « c'est bon, continue ». */
.wl__valid {
  position: absolute;
  top: 50%;
  right: var(--s-4);
  translate: 0 -50%;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  color: var(--mint-300);
  opacity: 0;
  scale: 0.6;
  transition:
    opacity var(--t-2) var(--ease),
    scale var(--t-2) var(--ease);
  pointer-events: none;
}

.wl__valid svg {
  width: 16px;
  height: 16px;
}

.wl__valid path {
  stroke-dasharray: 24;
  stroke-dashoffset: 24;
  transition: stroke-dashoffset var(--t-3) var(--ease) 80ms;
}

.wl__form.is-ready .wl__valid {
  opacity: 1;
  scale: 1;
}

.wl__form.is-ready .wl__valid path {
  stroke-dashoffset: 0;
}

/* Le piège à robots. `display: none` est reconnu par les remplisseurs
   automatiques modernes, qui sautent alors le champ — donc ne se trahissent
   pas. On le sort de l'écran en le laissant techniquement visible, et on le
   retire de l'ordre de tabulation et de l'arbre d'accessibilité côté HTML. */
.wl__trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

/* ── Le bouton, et son travail d'appel ────────────────────────────────────
   Deux effets superposés, tous deux en boucle lente tant que le visiteur n'a
   pas touché le champ :

   1. un HALO qui respire, posé sur l'enveloppe (`.wl__cta::before`) donc à
      l'EXTÉRIEUR du bouton — c'est ce qui le décolle du fond ;
   2. un ÉCLAT lavande qui traverse la face (`.rl-button::after`), clippé par
      le bouton lui-même.

   Le halo est violet et non blanc : la face du bouton EST blanche, un reflet
   blanc n'y aurait aucun contraste. Les deux s'arrêtent dès `.is-engaged` —
   appâter quelqu'un qui est déjà en train d'écrire, c'est du bruit. */

.wl__cta {
  position: relative;
  flex: 0 0 auto;
  display: grid;
}

.wl__cta::before {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: calc(var(--r-3) + 1px);
  box-shadow: 0 0 0 0 rgb(var(--accent-rgb) / 45%);
  animation: wl-halo 3.6s var(--ease-soft) 1.2s infinite;
  pointer-events: none;
}

@keyframes wl-halo {
  0% {
    box-shadow: 0 0 0 0 rgb(var(--accent-rgb) / 45%);
  }
  60%,
  100% {
    box-shadow: 0 0 0 18px rgb(var(--accent-rgb) / 0%);
  }
}

.wl__submit {
  position: relative;
  width: 100%;
  overflow: hidden;
}

.wl__submit::after {
  content: "";
  position: absolute;
  inset: -30% -60%;
  background: linear-gradient(
    102deg,
    transparent 38%,
    rgb(var(--accent-rgb) / 36%) 50%,
    transparent 62%
  );
  translate: -70% 0;
  animation: wl-sheen 5.4s var(--ease-soft) 2s infinite;
  pointer-events: none;
}

@keyframes wl-sheen {
  0% {
    translate: -70% 0;
  }
  24%,
  100% {
    translate: 70% 0;
  }
}

/* Adresse valide saisie : le rythme s'accélère et le halo se resserre. Le
   bouton devient la seule chose vivante de la page au moment exact où il ne
   reste plus qu'à cliquer. */
.wl__form.is-ready .wl__cta::before {
  animation-duration: 1.9s;
  animation-delay: 0s;
}

.wl__form.is-ready .wl__submit::after {
  animation-duration: 2.6s;
  animation-delay: 0s;
}

/* Le visiteur écrit, ou a cliqué ailleurs dans le formulaire : on se tait. */
.wl__form.is-engaged:not(.is-ready) .wl__cta::before,
.wl__form.is-engaged:not(.is-ready) .wl__submit::after,
.wl__form.is-busy .wl__cta::before,
.wl__form.is-busy .wl__submit::after {
  animation: none;
}

.wl__submit-label {
  transition:
    opacity var(--t-2) var(--ease),
    translate var(--t-2) var(--ease);
}

/* ── Envoi en cours ───────────────────────────────────────────────────────
   Le libellé s'échappe par le haut, le rouet entre par le bas, et la largeur
   du bouton ne bouge pas d'un pixel : c'est le libellé, toujours en place mais
   transparent, qui la tient. Un bouton qui rétrécit pendant l'attente fait
   sauter la ligne entière. */

.wl__form.is-busy .wl__submit-label {
  opacity: 0;
  translate: 0 -10px;
}

.wl__form.is-busy .wl__submit {
  pointer-events: none;
}

.wl__spinner {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 22px;
  height: 22px;
  margin: -11px 0 0 -11px;
  opacity: 0;
  translate: 0 10px;
  transition:
    opacity var(--t-2) var(--ease),
    translate var(--t-2) var(--ease);
}

.wl__form.is-busy .wl__spinner {
  opacity: 1;
  translate: 0 0;
  animation: wl-spin 900ms linear infinite;
}

.wl__spinner circle {
  fill: none;
  stroke: currentColor;
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-dasharray: 58;
  stroke-dashoffset: 44;
}

/* Le trait respire pendant que le cercle tourne : deux périodes premières
   entre elles, donc un motif qui ne se répète jamais à l'identique. C'est ce
   qui distingue une attente vivante d'un GIF en boucle. */
.wl__form.is-busy .wl__spinner circle {
  animation: wl-dash 1.5s var(--ease-soft) infinite;
}

@keyframes wl-spin {
  to {
    rotate: 360deg;
  }
}

@keyframes wl-dash {
  0% {
    stroke-dashoffset: 52;
  }
  50% {
    stroke-dashoffset: 16;
  }
  100% {
    stroke-dashoffset: 52;
  }
}

/* ── Refus ────────────────────────────────────────────────────────────────
   Un tremblement court et amorti. Il ne remplace pas le message écrit, il le
   DÉSIGNE : l'œil part vers le champ avant même d'avoir lu la phrase. */

.wl__form.is-error .wl__input {
  border-color: var(--rose-400);
  animation: wl-shake 440ms var(--ease);
}

@keyframes wl-shake {
  12% { translate: -6px; }
  32% { translate: 5px; }
  52% { translate: -3px; }
  72% { translate: 2px; }
  100% { translate: 0; }
}

/* ── Retours écrits ───────────────────────────────────────────────────────
   Le message vit sous le formulaire et ne déplace rien en apparaissant : la
   hauteur est réservée dès le premier rendu. */

.wl__status {
  min-height: 1.5rem;
  margin: var(--s-4) 0 0;
  font-size: 0.92rem;
  line-height: 1.5;
}

.wl__status:empty {
  margin-top: 0;
  min-height: 0;
}

.wl__status:not(:empty) {
  animation: rl-enter var(--t-3) var(--ease) both;
}

.wl__status[data-tone="ok"] {
  color: var(--mint-300);
}

.wl__status[data-tone="error"] {
  color: var(--rose-400);
}

/* ── Le sceau ─────────────────────────────────────────────────────────────
   La récompense. Elle se joue en quatre temps qui se recouvrent — anneau
   tracé, crochet tracé, ondes, éclats — parce qu'un seul temps se lit comme
   une icône qui apparaît, alors que quatre se lisent comme quelque chose qui
   VIENT DE SE PRODUIRE.

   Les animations sont déclarées sans condition sur les descendants : elles
   démarrent toutes seules au moment où le panneau passe de `display: none` à
   `display: block`. C'est ce qui permet aux deux chemins — celui de
   `waitlist.js` et celui de `#joined:target`, sans JavaScript — de jouer
   exactement la même scène sans une ligne de code en double. */

.wl__done {
  display: none;
  place-items: center;
  text-align: center;
}

.wl__slot.is-done .wl__done,
#joined:target .wl__done {
  display: grid;
}

/* Le panneau est focalisable (`tabindex="-1"`) pour que `waitlist.js` puisse y
   emmener un lecteur d'écran. Chrome donne AUSSI le focus à la cible d'une
   ancre quand elle est focalisable : sans cette règle, arriver par
   `#joined` dessinait un liseré de focus autour de tout le panneau. On le
   retire ici seulement — c'est une région de statut, pas une commande, et sa
   présence est déjà signalée par le sceau. */
.wl__done:focus {
  outline: none;
}

.wl__seal {
  position: relative;
  display: grid;
  place-items: center;
  width: 96px;
  height: 96px;
}

.wl__seal-mark {
  width: 68px;
  height: 68px;
  overflow: visible;
}

.wl__seal-mark circle,
.wl__seal-mark path {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.wl__seal-ring {
  stroke: var(--accent-text);
  stroke-width: 2;
  stroke-dasharray: 170;
  stroke-dashoffset: 170;
  /* Le tracé part de midi et non de trois heures : `rotate` sur l'élément,
     pas sur le conteneur, pour ne pas emmener le crochet avec lui. */
  rotate: -90deg;
  transform-origin: 50% 50%;
  animation: wl-draw 620ms var(--ease) 120ms forwards;
}

.wl__seal-check {
  stroke: var(--mint-300);
  stroke-width: 3.2;
  stroke-dasharray: 40;
  stroke-dashoffset: 40;
  animation: wl-draw 380ms var(--ease) 560ms forwards;
}

@keyframes wl-draw {
  to {
    stroke-dashoffset: 0;
  }
}

/* Deux ondes concentriques, décalées : une seule se lit comme un cercle qui
   grandit, deux comme une onde de choc. */
.wl__pulse {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 64px;
  height: 64px;
  margin: -32px 0 0 -32px;
  border: 1px solid rgb(var(--accent-rgb) / 55%);
  border-radius: var(--r-full);
  opacity: 0;
  animation: wl-pulse 1200ms var(--ease) 520ms both;
}

.wl__pulse:nth-of-type(2) {
  animation-delay: 700ms;
  border-color: rgb(var(--mint-rgb) / 45%);
}

@keyframes wl-pulse {
  0% {
    opacity: 0.85;
    scale: 0.45;
  }
  100% {
    opacity: 0;
    scale: 2.6;
  }
}

/* Les éclats. Angles, distances et teintes sont posés ici, en `nth-of-type`,
   et non par JavaScript : le panneau doit pouvoir se jouer entièrement sur le
   chemin `#joined:target`, où aucun script ne tourne. Douze suffisent — au
   delà, ça cesse d'être une étincelle pour devenir un feu d'artifice, et la
   page n'a rien gagné. */

.wl__spark {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  border-radius: var(--r-full);
  background: rgb(var(--spark, var(--violet-300-rgb)));
  opacity: 0;
  animation: wl-spark 900ms var(--ease) 540ms both;
}

@keyframes wl-spark {
  0% {
    opacity: 0;
    transform: rotate(var(--a)) translateX(6px) scale(0.3);
  }
  22% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: rotate(var(--a)) translateX(var(--d)) scale(0.55);
  }
}

.wl__spark:nth-of-type(1)  { --a:   8deg; --d: 74px; animation-delay: 520ms; }
.wl__spark:nth-of-type(2)  { --a:  38deg; --d: 58px; animation-delay: 600ms; --spark: var(--cyan-300-rgb); }
.wl__spark:nth-of-type(3)  { --a:  67deg; --d: 82px; animation-delay: 545ms; }
.wl__spark:nth-of-type(4)  { --a:  96deg; --d: 62px; animation-delay: 640ms; --spark: var(--mint-rgb); }
.wl__spark:nth-of-type(5)  { --a: 124deg; --d: 78px; animation-delay: 560ms; }
.wl__spark:nth-of-type(6)  { --a: 152deg; --d: 56px; animation-delay: 660ms; --spark: var(--cyan-300-rgb); }
.wl__spark:nth-of-type(7)  { --a: 187deg; --d: 80px; animation-delay: 530ms; }
.wl__spark:nth-of-type(8)  { --a: 214deg; --d: 60px; animation-delay: 620ms; --spark: var(--mint-rgb); }
.wl__spark:nth-of-type(9)  { --a: 243deg; --d: 76px; animation-delay: 575ms; }
.wl__spark:nth-of-type(10) { --a: 271deg; --d: 54px; animation-delay: 655ms; --spark: var(--cyan-300-rgb); }
.wl__spark:nth-of-type(11) { --a: 302deg; --d: 84px; animation-delay: 540ms; }
.wl__spark:nth-of-type(12) { --a: 332deg; --d: 64px; animation-delay: 630ms; --spark: var(--mint-rgb); }

.wl__done-title {
  margin: var(--s-4) 0 0;
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3.4vw, 2.1rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  animation: rl-enter var(--t-3) var(--ease) 760ms both;
}

.wl__done-sub {
  margin: var(--s-3) auto 0;
  max-width: 40ch;
  color: var(--text-secondary);
  font-size: 0.98rem;
  line-height: 1.6;
  animation: rl-enter var(--t-3) var(--ease) 880ms both;
}

/* ── Mentions ─────────────────────────────────────────────────────────────── */

.wl__note {
  margin: var(--s-6) 0 0;
  color: var(--text-tertiary);
  font-size: 0.85rem;
  line-height: 1.6;
  transition: opacity var(--t-3) var(--ease);
}

.wl__slot.is-done ~ .wl__note {
  opacity: 0.35;
}

.wl__note a {
  color: var(--text-secondary);
  text-decoration-color: var(--border-hover);
  text-underline-offset: 3px;
}

.wl__note a:hover {
  color: var(--text);
}

/* ── Ce qu'on obtient en s'inscrivant ────────────────────────────────────
   Trois lignes, pas un tableau de fonctionnalités : la page ne vend pas le
   produit, elle explique ce que l'adresse déclenche.

   Elles utilisent le système de révélation du site (`data-reveal`, cf.
   `motion.js`) plutôt qu'une cascade d'entrée : sur téléphone elles sont sous
   la ligne de flottaison, et une animation jouée avant d'être vue est une
   animation perdue. */

.wl__list {
  display: grid;
  gap: var(--s-3);
  margin: var(--s-10) 0 0;
  padding: 0;
  list-style: none;
  text-align: start;
}

.wl__list li {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-3);
  align-items: start;
  padding: var(--s-4) var(--s-5);
  color: var(--text-secondary);
  background: var(--surface-glass);
  border: 1px solid var(--border);
  border-radius: var(--r-3);
  font-size: 0.95rem;
  line-height: 1.55;
  transition:
    border-color var(--t-2) var(--ease),
    background var(--t-2) var(--ease),
    translate var(--t-2) var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .wl__list li:hover {
    background: var(--surface-hover);
    border-color: var(--border-accent);
    translate: 0 -2px;
  }
}

.wl__list svg {
  width: 18px;
  height: 18px;
  margin-top: 2px;
  color: var(--accent-text);
}

/* Le crochet se trace au lieu d'apparaître, en suivant la cascade du groupe
   (`--i`, posé par motion.js). */
.wl__list svg path {
  stroke-dasharray: 26;
  stroke-dashoffset: 26;
  transition: stroke-dashoffset var(--t-4) var(--ease);
  transition-delay: calc(var(--i, 0) * 70ms + 260ms);
}

.wl__list li.is-in svg path,
.no-js .wl__list svg path {
  stroke-dashoffset: 0;
}

.wl__list strong {
  color: var(--text);
  font-weight: 600;
}

@media (max-width: 560px) {
  .wl__form {
    flex-direction: column;
  }

  .wl__cta {
    width: 100%;
  }
}

/* ── Moins de mouvement ───────────────────────────────────────────────────
   On coupe tout ce qui BOUCLE ou se déplace — l'appât du bouton, les ondes,
   les éclats, le tremblement, le flou du titre. On garde les fondus et le
   tracé du crochet : ce sont des changements d'état, pas de la décoration, et
   les supprimer priverait le visiteur du retour, pas de l'effet. */

@media (prefers-reduced-motion: reduce) {
  .wl__cta::before,
  .wl__submit::after,
  .wl__pulse,
  .wl__spark,
  .wl__form.is-error .wl__input {
    animation: none;
  }

  .wl__pulse,
  .wl__spark {
    display: none;
  }

  .wl__hero > :nth-child(2) {
    animation: none;
  }

  .wl__form.is-busy .wl__spinner {
    /* Le rouet reste : sans lui, rien n'indiquerait que l'envoi est en cours.
       C'est de l'information, pas de l'ornement. */
    animation: wl-spin 1.4s linear infinite;
  }
}
