/*
 * La page d'accueil du site, et celle des nouveautés, par-dessus style.css.
 *
 * Les écrans de téléphone sont de vraies captures de l'application
 * (`site/shots/`, produites par `yarn site:screenshots`), posées dans
 * `.screen` avec un `<img class="shot">` ; une capture haute défile dans le
 * cadre à la hauteur que donne `--scroll`. Les tuiles de la grille bento en
 * montrent une fenêtre fixe (`.bento-screen`), cadrée par `object-position`.
 *
 * La page des nouveautés (`whats-new.html`, `fr/nouveautes.html`) partage ce
 * fichier : `.block`, `.block-head`, `.page-title`, `.video`, `.tools`,
 * `.tool`, `.tag`, `.cta-final`, `.cta-more` doivent lui rester utilisables.
 *
 * Les classes `.w` (un mot d'un titre), `--to` (la cible d'un chiffre),
 * `.hero-*`, `.story-*`, `.bento-*`, `.fact-value` sont les points d'accroche
 * de la couche de mouvement : ce fichier ne leur donne qu'un état statique.
 */

/* Une seule échelle pour la page : les titres de section et l'espace entre
   les sections, au lieu de tailles redites bloc par bloc */
:root {
  --home-section-space: 48px;
  --home-h2: clamp(32px, 2.2vw + 22px, 50px);
  --home-h1: clamp(46px, 3.6vw + 26px, 74px);
  --home-radius-xl: 32px;
  --home-surface-highlight: inset 0 1px 0 rgba(var(--ion-text-color-rgb), 0.05);

  /* Les durées du héros, le seul moment joué à l'horloge : tirées du jeton
     `slow` de l'app plutôt qu'inventées */
  --home-motion-title: calc(var(--app-motion-slow) * 1.6);
  --home-motion-hero: calc(var(--app-motion-slow) * 2.5);
  --home-stagger: 60ms;
}

@media (min-width: 900px) {
  :root {
    --home-section-space: 88px;
  }
}

/* L'accueil s'aligne sur `.wrap` (1 120 px) ; les pages légales gardent 1 040 px */
.topbar-inner,
.footer-inner {
  max-width: 1120px;
}

/* Pour un lecteur d'écran seulement : l'en-tête de colonne vide du
   comparatif, « Inclus » sous une coche */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ==========================================================================
   BOUTONS SECONDAIRES ET PASTILLES
   ========================================================================== */

.button-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 50px;
  padding: 0 22px;
  border-radius: var(--app-radius-md);
  border: 1px solid var(--app-border-color);
  background: var(--app-card-background);
  color: var(--ion-text-color);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.02em;
  text-decoration: none;
  transition:
    transform var(--app-transition-fast),
    border-color var(--app-transition-fast),
    background-color var(--app-transition-fast);
}

@media (hover: hover) {
  .button-ghost:hover {
    border-color: rgba(var(--ion-text-color-rgb), 0.24);
    /* Un voile sur la couleur de la carte : seul, ce fond assombrissait le
       bouton en sombre au lieu de l'éclairer */
    background-image: linear-gradient(
      rgba(var(--ion-text-color-rgb), 0.08),
      rgba(var(--ion-text-color-rgb), 0.08)
    );
  }
}

.button-ghost:active {
  transform: scale(0.98);
}

.button-ghost:focus-visible {
  outline: 2px solid var(--ion-color-primary);
  outline-offset: 3px;
}

.button .icon,
.button-ghost .icon {
  font-size: 18px;
}

/* Le bouton qui porte la page : plus haut que celui de l'app, la seule
   action qu'on attend du visiteur */
.button-lg {
  min-height: 56px;
  padding: 0 28px;
  font-size: 17px;
}

/* « Télécharger sur Google Play » passait sur deux lignes à 390 px */
@media (max-width: 559px) {
  .button-lg {
    padding: 0 20px;
    font-size: 16px;
  }
}

.topbar .button {
  min-height: 44px;
  padding: 0 16px;
  font-size: 14px;
  border-radius: var(--app-radius-sm);
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 32px;
  padding: 0 14px;
  border-radius: var(--app-radius-full);
  border: 1px solid rgba(var(--ion-color-primary-rgb), 0.22);
  background: rgba(var(--ion-color-primary-rgb), 0.1);
  color: var(--text-primary);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.eyebrow .dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

/* La pastille Pro, la même partout : dorée, comme le ProChip de l'app */
.pro {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--app-radius-full);
  background: rgba(var(--ion-color-warning-rgb), 0.14);
  color: var(--text-warning);
  font-size: 12px;
  font-weight: 700;
  line-height: 1.3;
  white-space: nowrap;
  align-self: flex-start;
}

/* ==========================================================================
   MISE EN PAGE
   ========================================================================== */

.wrap {
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 16px;
}

.block {
  padding: var(--home-section-space) 0;
}

.wrap-last {
  padding-bottom: var(--home-section-space);
}

.block-head {
  max-width: 680px;
  margin: 0 auto 40px;
  text-align: center;
}

/* Redit plus précis que `.block-head p` et `.feature-text > p`, qui lui
   donnaient sinon leur gris et leur taille de paragraphe */
.kicker,
.block-head .kicker,
.feature-text > .kicker,
.story-step > .kicker {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 12px;
  color: var(--text-primary);
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.block-head .kicker {
  justify-content: center;
}

.block-head h2,
.block-head .page-title,
.feature-text h2,
.story-step h2 {
  margin: 0;
  font-size: var(--home-h2);
  font-weight: 800;
  line-height: 1.06;
  letter-spacing: -0.045em;
  text-wrap: balance;
}

.block-head p,
.feature-text > p,
.story-step > p {
  margin: 16px 0 0;
  color: var(--ion-color-medium);
  font-size: 17px;
  line-height: 1.55;
  text-wrap: pretty;
}

.accent {
  background: var(--button-gradient);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

@media (min-width: 900px) {
  .block-head {
    margin-bottom: 56px;
  }

  .block-head p,
  .feature-text > p,
  .story-step > p {
    font-size: 19px;
  }
}

/* ==========================================================================
   HÉROS
   ========================================================================== */

.hero-section {
  position: relative;
  padding: 24px 0 var(--home-section-space);
}

/* Les sections se touchent : l'espace entre deux est la somme de leurs
   marges, `--home-section-space` de chaque côté */

.hero-home {
  display: grid;
  gap: 48px;
  align-items: center;
  padding: 24px 0 0;
}

.hero-copy {
  text-align: center;
}

.hero-title {
  margin: 20px 0 0;
  font-size: var(--home-h1);
  font-weight: 800;
  line-height: 0.98;
  letter-spacing: -0.055em;
}

.hero-title .accent {
  white-space: nowrap;
}

.hero-copy .lead {
  max-width: 34ch;
  margin: 20px auto 0;
  color: var(--ion-color-medium);
  font-size: 18px;
  line-height: 1.55;
  text-wrap: pretty;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: 32px;
}

.proof {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 18px;
  margin: 24px 0 0;
  padding: 0;
  list-style: none;
  color: var(--ion-color-medium);
  font-size: 14px;
  font-weight: 500;
}

.proof li {
  display: flex;
  align-items: center;
  gap: 6px;
}

.proof .icon {
  color: var(--text-success);
  font-size: 13px;
}

/* Sur téléphone, le menu est masqué : ce lien est le chemin vers les tarifs */
.proof a {
  color: inherit;
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}

@media (max-width: 559px) {
  .hero-actions {
    flex-direction: column;
    align-items: stretch;
  }
}

.hero-visual {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/*
 * La composition : le téléphone de face (l'écran Entraînement, qui défile),
 * un second derrière lui, penché, et les deux cartes chiffrées qui flottent.
 * Le second téléphone ne porte rien d'indispensable : il peut être couvert.
 */
.hero-stage {
  position: relative;
  width: min(100%, 340px);
  padding: 12px 0 16px;
}

.hero-glow {
  position: absolute;
  inset: -6% -20% 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(
    closest-side,
    rgba(var(--ion-color-primary-rgb), 0.3),
    rgba(var(--ion-color-primary-rgb), 0.08) 55%,
    transparent
  );
}

.hero-stage .phone-front {
  z-index: 1;
  width: 244px;
}

.hero-stage .phone-back {
  position: absolute;
  top: 54px;
  right: 0;
  z-index: 0;
  width: 200px;
  transform: rotate(6deg);
  opacity: 0.9;
}

/* Les chiffres des captures sont ceux d'un vrai utilisateur : le dire évite
   qu'un visiteur les lise comme une promesse faite à lui */
.hero-caption {
  margin: 6px 0 0;
  color: var(--ion-color-medium);
  font-size: 12.5px;
  text-align: center;
}

/* Les cartes qui flottent autour des téléphones */
.float {
  position: absolute;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px 10px 10px;
  border-radius: var(--app-radius-md);
  border: 1px solid var(--app-border-color);
  background: var(--app-card-background);
  box-shadow:
    0 18px 48px rgba(0, 0, 0, 0.22),
    var(--home-surface-highlight);
  -webkit-backdrop-filter: blur(18px);
  backdrop-filter: blur(18px);
  font-size: 12.5px;
  line-height: 1.3;
  white-space: nowrap;
}

.float strong {
  display: block;
  font-size: 14px;
}

.float span {
  color: var(--ion-color-medium);
}

.float .tile {
  width: 34px;
  height: 34px;
  font-size: 15px;
}

/* Posées sur ce que l'écran a de moins utile à lire : le haut du téléphone
   de derrière, la barre d'onglets du téléphone de face */
.float-a {
  top: 0;
  right: -6px;
}

.float-b {
  bottom: 52px;
  left: -6px;
}

/* Sur tablette, le héros reste sur une colonne : la composition grandit */
@media (min-width: 600px) and (max-width: 899px) {
  .hero-stage {
    width: 420px;
  }

  .hero-stage .phone-front {
    width: 280px;
    margin-left: 20px;
  }

  .hero-stage .phone-back {
    top: 70px;
    width: 230px;
  }
}

@media (min-width: 900px) {
  .hero-section {
    padding-top: 40px;
  }

  .hero-home {
    grid-template-columns: 1.02fr 0.98fr;
    gap: 40px;
    padding-top: 56px;
  }

  .hero-copy {
    text-align: left;
  }

  .hero-copy .lead {
    margin-left: 0;
    font-size: 21px;
  }

  .hero-actions,
  .proof {
    justify-content: flex-start;
  }

  .hero-stage {
    width: 480px;
    max-width: 100%;
    padding: 24px 0 36px;
  }

  .hero-stage .phone-front {
    width: 296px;
    margin-left: 32px;
  }

  .hero-stage .phone-back {
    top: 80px;
    width: 250px;
  }

  .float-a {
    top: 40px;
    right: -12px;
  }

  .float-b {
    bottom: 84px;
    left: -24px;
  }

  .float {
    font-size: 13px;
  }

  .float strong {
    font-size: 15px;
  }
}

/* ==========================================================================
   LE TÉLÉPHONE — de vraies captures de l'application (cypress/site/,
   `yarn site:screenshots`), jamais redessinées : une maquette refaite à la
   main s'écartait de l'interface dès qu'on la comparait
   ========================================================================== */

.phone {
  position: relative;
  width: 290px;
  flex: 0 0 auto;
  padding: 10px;
  border-radius: 46px;
  background: #16171a;
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.08) inset,
    0 0 0 1px rgba(0, 0, 0, 0.35),
    0 40px 80px rgba(0, 0, 0, 0.28),
    0 12px 24px rgba(0, 0, 0, 0.18);
}

/* Le rapport d'un écran de 390 × 844, celui des captures */
.screen {
  position: relative;
  aspect-ratio: 390 / 844;
  overflow: hidden;
  border-radius: 37px;
  background: #000000;
  isolation: isolate;
}

.notch {
  position: absolute;
  top: 9px;
  left: 50%;
  z-index: 3;
  width: 78px;
  height: 22px;
  margin-left: -39px;
  border-radius: 12px;
  background: #000000;
}

/* La barre d'état : sans elle, l'encoche passait sur le contenu d'un écran
   qui défile et se lisait comme une tache noire */
.screen::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 2;
  height: 7%;
  background: linear-gradient(#000000 55%, transparent);
  pointer-events: none;
}

.shot {
  display: block;
  width: 100%;
  height: auto;
}

/* Un écran qui défile : la capture est prise dans une fenêtre haute, le
   téléphone n'en montre qu'un écran à la fois */
.shot-scroll {
  position: absolute;
  inset: 0;
}

/* La barre d'onglets reste en bas pendant que l'écran défile : c'est le bas
   de la même capture, découpé */
.shot-tabbar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  height: 7.8%;
  overflow: hidden;
}

.shot-tabbar img {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: auto;
}

/* ==========================================================================
   BANDEAU DE CHIFFRES — un seul panneau, quatre cases séparées d'un filet :
   quatre cartes de plus après le héros faisaient une rangée de boutons
   ========================================================================== */

.facts-band {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  margin-top: 56px;
  border-radius: var(--app-radius-lg);
  border: 1px solid var(--app-border-color);
  background: var(--app-card-background);
  box-shadow: var(--app-shadow-md), var(--home-surface-highlight);
  /* `clip` et non `hidden` : un conteneur `hidden` est un conteneur de
     défilement, et les animations au défilement de ses enfants suivaient
     alors son défilement à lui, qui ne bouge jamais (figées à la fin) */
  overflow: clip;
}

.fact {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 24px 12px;
  text-align: center;
}

.fact:nth-child(odd) {
  border-right: 1px solid var(--tool-hairline);
}

.fact:nth-child(-n + 2) {
  border-bottom: 1px solid var(--tool-hairline);
}

.fact b {
  display: block;
  font-size: 38px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.05em;
  font-variant-numeric: tabular-nums;
}

.fact span {
  color: var(--ion-color-medium);
  font-size: 13.5px;
  line-height: 1.35;
}

@media (min-width: 900px) {
  .facts-band {
    grid-template-columns: repeat(4, 1fr);
    margin-top: 72px;
  }

  .fact {
    padding: 32px 16px;
  }

  .fact:nth-child(-n + 2) {
    border-bottom: 0;
  }

  .fact:not(:last-child) {
    border-right: 1px solid var(--tool-hairline);
  }

  .fact b {
    font-size: 56px;
  }

  .fact span {
    font-size: 14.5px;
  }
}

/* ==========================================================================
   LE BANDEAU DES NOUVEAUTÉS — une carte entière cliquable, sous les chiffres
   ========================================================================== */

.news-banner {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 16px;
  align-items: center;
  max-width: 760px;
  margin: 20px auto 0;
  padding: 16px 20px;
  border-radius: var(--app-radius-lg);
  border: 1px solid rgba(var(--ion-color-primary-rgb), 0.24);
  background:
    linear-gradient(
      100deg,
      rgba(var(--ion-color-primary-rgb), 0.1),
      transparent 60%
    ),
    var(--app-card-background);
  box-shadow: var(--app-shadow-md), var(--home-surface-highlight);
  color: var(--ion-text-color);
  text-decoration: none;
}

.news-banner-text {
  display: flex;
  flex: 1 1 200px;
  flex-direction: column;
  gap: 2px;
}

.news-banner-text b {
  font-size: 16px;
  letter-spacing: -0.01em;
}

.news-banner-text span {
  color: var(--ion-color-medium);
  font-size: 14.5px;
  line-height: 1.45;
}

.news-banner-link {
  color: var(--text-primary);
  font-size: 14px;
  font-weight: 700;
  white-space: nowrap;
}

.news-banner-link::after {
  content: ' →';
}

.news-banner:focus-visible {
  outline: 2px solid var(--ion-color-primary);
  outline-offset: 3px;
}

/* ==========================================================================
   LA VIDÉO — hébergée par le site, jamais par YouTube : un lecteur intégré
   appellerait Google, ce que la politique de confidentialité exclut.
   Deux balises plutôt qu'une <source media> : l'affiche doit changer de format
   avec la vidéo (paysage sur grand écran, portrait sur téléphone). Avec
   preload="none", rien n'est téléchargé avant le clic, la vidéo cachée non plus
   ========================================================================== */

.video {
  position: relative;
  max-width: 980px;
  margin: 0 auto;
}

/* Le halo sous la vidéo : le même rouge que celui du haut d'écran */
.video::before {
  content: '';
  position: absolute;
  inset: 8% 6% -6%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(
    closest-side,
    rgba(var(--ion-color-primary-rgb), 0.24),
    transparent
  );
  filter: blur(24px);
}

.video video {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--app-border-color);
  border-radius: var(--app-radius-lg);
  background: #000000;
  box-shadow:
    0 30px 80px rgba(0, 0, 0, 0.3),
    var(--home-surface-highlight);
}

.video .video-wide {
  display: none;
}

.video .video-tall {
  max-width: 360px;
  margin: 0 auto;
}

@media (min-width: 640px) {
  .video .video-wide {
    display: block;
  }

  .video .video-tall {
    display: none;
  }
}

/* ==========================================================================
   LISTES À PASTILLES — sous un titre de fonction ou de la vie privée
   ========================================================================== */

.points {
  display: grid;
  gap: 4px;
  margin: 28px 0 0;
  padding: 0;
  list-style: none;
}

.points li {
  display: flex;
  gap: 14px;
  padding: 10px 0;
}

.points .tile {
  width: 38px;
  height: 38px;
  font-size: 15px;
}

.points b {
  display: block;
  font-size: 16px;
  letter-spacing: -0.01em;
}

.points span {
  color: var(--ion-color-medium);
  font-size: 15px;
  line-height: 1.5;
}

.feature-text .pro,
.story-step .pro {
  margin-top: 16px;
}

/* ==========================================================================
   L'HISTOIRE — trois étapes, trois écrans.
   Par défaut (téléphone, mouvement réduit, navigateur sans animation au
   défilement) : chaque étape suivie de son téléphone, `display: contents`
   laissant la grille les entrelacer. Sur un écran large qui sait animer au
   défilement : un seul téléphone tenu (sticky) pendant que les étapes passent,
   les trois écrans empilés dans `.story-stage` ; c'est la couche de mouvement
   qui fait passer de l'un à l'autre. Aucun parent du sticky ne doit couper
   son débordement (`main` est en `overflow-x: clip`, qui le permet)
   ========================================================================== */

.story-grid {
  display: grid;
  gap: 40px;
  align-items: center;
}

.story-steps,
.story-stage {
  display: contents;
}

.story-glow {
  display: none;
}

.story-step {
  max-width: 540px;
}

.story-index {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  height: 22px;
  padding: 0 7px;
  border-radius: var(--app-radius-full);
  background: rgba(var(--ion-color-primary-rgb), 0.12);
  color: var(--text-primary);
  font-size: 11.5px;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
}

.story-shot {
  justify-self: center;
  width: 248px;
}

/* L'ordre d'entrelacement : étape, son écran, étape suivante… Les écrans
   comptent depuis 2, `.story-glow` étant le premier enfant de la scène */
.story-step:nth-child(1) {
  order: 1;
}

.story-shot:nth-child(2) {
  order: 2;
}

.story-step:nth-child(2) {
  order: 3;
}

.story-shot:nth-child(3) {
  order: 4;
}

.story-step:nth-child(3) {
  order: 5;
}

.story-shot:nth-child(4) {
  order: 6;
}

/* Sur un écran large sans le mode tenu : texte à gauche, écran à droite,
   ligne par ligne */
@media (min-width: 900px) {
  .story-grid {
    grid-template-columns: 1fr 1fr;
    column-gap: 80px;
    row-gap: 96px;
  }

  .story-shot {
    width: 290px;
  }
}

/* `timeline-scope` est exigé avec : c'est lui qui laisse les écrans, dans la
   scène, suivre les étapes, qui sont dans une autre colonne. Sans lui, le
   téléphone tenu resterait sur le premier écran pendant toute l'histoire */
@supports (animation-timeline: view()) and (timeline-scope: none) {
  @media (min-width: 900px) and (prefers-reduced-motion: no-preference) {
    .story-grid {
      align-items: start;
      row-gap: 0;
    }

    .story-steps {
      display: block;
    }

    .story-step {
      display: flex;
      flex-direction: column;
      justify-content: center;
      min-height: 82vh;
      padding: 48px 0;
    }

    .story-stage {
      position: sticky;
      top: max(80px, calc(50vh - 290px));
      display: grid;
      align-self: start;
      place-items: center;
      order: 2;
    }

    .story-glow {
      display: block;
      grid-area: 1 / 1;
      width: 560px;
      height: 560px;
      pointer-events: none;
      background: radial-gradient(
        closest-side,
        rgba(var(--ion-color-primary-rgb), 0.22),
        transparent
      );
    }

    /* Empilés, le premier dessus : sans la couche de mouvement, c'est lui
       qu'on voit */
    .story-shot {
      grid-area: 1 / 1;
      order: 0;
    }

    .story-shot:nth-child(2) {
      z-index: 3;
    }

    .story-shot:nth-child(3) {
      z-index: 2;
    }

    .story-shot:nth-child(4) {
      z-index: 1;
    }
  }
}

/* ==========================================================================
   LA GRILLE BENTO — le reste des fonctions, chacune avec une fenêtre sur son
   vrai écran. Une tuile large, puis des tuiles simples ; les tuiles sans
   capture (`.bento-text`) rythment la grille
   ========================================================================== */

.bento {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}

.bento-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 24px 24px 0;
  /* `clip` et non `hidden` : voir `.facts-band` */
  overflow: clip;
  border-radius: var(--home-radius-xl);
  border: 1px solid var(--app-border-color);
  background:
    radial-gradient(
      120% 70% at 100% 0%,
      rgba(var(--ion-color-primary-rgb), 0.07),
      transparent 60%
    ),
    var(--app-card-background);
  box-shadow: var(--app-shadow-md), var(--home-surface-highlight);
}

.bento-text {
  padding-bottom: 24px;
}

.bento-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.bento-tile h3 {
  margin: 18px 0 0;
  font-size: 21px;
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: -0.035em;
  text-wrap: balance;
}

.bento-tile p {
  margin: 8px 0 0;
  color: var(--ion-color-medium);
  font-size: 15px;
  line-height: 1.55;
}

.bento-tile .bento-note {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--tool-hairline);
  font-size: 14px;
}

/* La capture sort par le bas de la tuile, comme un écran qu'on glisse dans
   une poche, et s'y fond */
.bento-shot {
  margin-top: auto;
  padding-top: 24px;
}

.bento-screen {
  position: relative;
  aspect-ratio: 780 / 600;
  /* `clip` et non `hidden` : voir `.facts-band` */
  overflow: clip;
  border: 1px solid var(--app-border-color);
  border-bottom: 0;
  border-radius: 20px 20px 0 0;
  background: #000000;
  -webkit-mask-image: linear-gradient(#000000 62%, transparent);
  mask-image: linear-gradient(#000000 62%, transparent);
}

/* La fenêtre de la tuile large est plus haute : `object-position` est
   calculé pour ce rapport (780 × 660 px de capture) */
.bento-screen-wide {
  aspect-ratio: 780 / 660;
}

.bento-screen img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

@media (min-width: 640px) {
  .bento {
    grid-template-columns: repeat(2, 1fr);
  }

  .bento-wide {
    grid-column: span 2;
  }
}

/* Sur deux colonnes, la dernière tuile serait seule sur sa ligne */
@media (min-width: 640px) and (max-width: 959px) {
  .bento-tile:last-child {
    grid-column: span 2;
  }
}

@media (min-width: 960px) {
  .bento {
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
  }

  .bento-tile {
    padding: 32px 32px 0;
  }

  .bento-text {
    padding-bottom: 32px;
  }

  /* La tuile large : le texte à gauche, l'écran à droite, posé sur le bas */
  .bento-wide {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 32px;
  }

  .bento-wide .bento-copy {
    padding-bottom: 32px;
  }

  .bento-wide .bento-shot {
    align-self: end;
  }

  .bento-tile h3 {
    font-size: 23px;
  }
}

/* ==========================================================================
   SCHÉMAS
   ========================================================================== */

.diagram {
  width: 100%;
  max-width: 520px;
  height: auto;
  margin: 0 auto;
  display: block;
}

.diagram .ring-path {
  fill: none;
  stroke: var(--tool-hairline);
  stroke-width: 2;
  stroke-dasharray: 4 8;
}

.diagram .arc {
  fill: none;
  stroke: var(--ion-color-primary);
  stroke-width: 3;
  stroke-linecap: round;
}

.diagram .node {
  fill: var(--app-card-background);
  stroke: var(--app-border-color);
}

.diagram .node-text {
  fill: var(--ion-text-color);
  font-size: 17px;
  font-weight: 700;
  text-anchor: middle;
}

.diagram .node-sub {
  fill: var(--ion-color-medium);
  font-size: 12.5px;
  text-anchor: middle;
}

.diagram .center-text {
  fill: var(--ion-text-color);
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -0.04em;
  text-anchor: middle;
}

.diagram .center-sub {
  fill: var(--ion-color-medium);
  font-size: 12px;
  text-anchor: middle;
}

.loop-grid {
  display: grid;
  gap: 40px;
  align-items: center;
}

@media (min-width: 900px) {
  .loop-grid {
    grid-template-columns: 1fr 1fr;
    gap: 64px;
  }
}

.steps {
  display: grid;
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: step;
}

.steps li {
  display: flex;
  gap: 14px;
  padding: 18px 20px;
  border-radius: var(--app-radius-lg);
  border: 1px solid var(--app-border-color);
  background: var(--app-card-background);
  box-shadow: var(--app-shadow-md), var(--home-surface-highlight);
}

.steps li::before {
  counter-increment: step;
  content: counter(step);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  border-radius: var(--app-radius-xs);
  background: rgba(var(--ion-color-primary-rgb), 0.14);
  color: var(--text-primary);
  font-weight: 800;
}

.steps b {
  display: block;
  font-size: 16px;
}

.steps span {
  color: var(--ion-color-medium);
  font-size: 15px;
}

/* ==========================================================================
   VIE PRIVÉE — un panneau à part, teinté du vert de « hors ligne » : la seule
   section qui parle de ce que Liftz ne fait pas
   ========================================================================== */

.privacy-panel {
  padding: 32px 20px;
  border-radius: var(--home-radius-xl);
  border: 1px solid var(--app-border-color);
  background:
    radial-gradient(
      80% 60% at 85% 50%,
      rgba(var(--ion-color-success-rgb), 0.08),
      transparent 70%
    ),
    var(--app-card-background);
  box-shadow: var(--app-shadow-md), var(--home-surface-highlight);
}

.privacy-grid {
  display: grid;
  gap: 32px;
  align-items: center;
}

.feature-text h2 {
  margin: 0;
}

@media (min-width: 900px) {
  .privacy-panel {
    padding: 64px;
  }

  .privacy-grid {
    grid-template-columns: 1fr 1fr;
    gap: 64px;
  }
}

.privacy-diagram .device {
  fill: var(--app-card-background);
  stroke: var(--ion-color-success);
  stroke-width: 2;
}

.privacy-diagram .cloud-box {
  fill: var(--app-card-background);
  stroke: var(--app-border-color);
}

.privacy-diagram .link {
  fill: none;
  stroke: var(--ion-color-secondary);
  stroke-width: 2;
  stroke-dasharray: 5 6;
}

.privacy-diagram .blocked {
  fill: none;
  stroke: var(--ion-color-medium);
  stroke-opacity: 0.35;
  stroke-width: 2;
}

.privacy-diagram .cross {
  stroke: var(--ion-color-danger);
  stroke-width: 3;
  stroke-linecap: round;
}

.privacy-diagram .label {
  fill: var(--ion-text-color);
  font-size: 13px;
  font-weight: 700;
  text-anchor: middle;
}

.privacy-diagram .sub {
  fill: var(--ion-color-medium);
  font-size: 11px;
  text-anchor: middle;
}

.privacy-diagram .strike {
  fill: var(--ion-color-medium);
  font-size: 12px;
  font-weight: 600;
  text-anchor: middle;
  text-decoration: line-through;
}

/*
 * Sur téléphone, les schémas sont réduits aux deux tiers : leurs petits textes
 * tombaient sous 9 px. Les sous-titres des étapes disparaissent (la liste
 * `.steps`, juste dessous, les répète), les autres grossissent d'autant.
 */
@media (max-width: 899px) {
  .diagram .node-sub {
    display: none;
  }

  .diagram .center-sub {
    font-size: 17px;
  }

  .privacy-diagram .sub {
    font-size: 16px;
  }

  .privacy-diagram .strike,
  .privacy-diagram .label {
    font-size: 17px;
  }
}

/* ==========================================================================
   OUTILS — aussi les listes de la page des nouveautés
   ========================================================================== */

.tools {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

.tool {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 18px;
  border-radius: var(--app-radius-lg);
  border: 1px solid var(--app-border-color);
  background: var(--app-card-background);
  box-shadow: var(--app-shadow-md), var(--home-surface-highlight);
}

.tool b {
  display: block;
  font-size: 15.5px;
  letter-spacing: -0.01em;
}

.tool span {
  color: var(--ion-color-medium);
  font-size: 14px;
  line-height: 1.45;
}

.tool .tag {
  align-self: flex-start;
  margin-left: auto;
  padding: 3px 9px;
  border-radius: var(--app-radius-full);
  background: rgba(var(--ion-color-warning-rgb), 0.14);
  color: var(--text-warning);
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
}

.tool .tag.free {
  background: rgba(var(--ion-color-success-rgb), 0.14);
  color: var(--text-success);
}

@media (min-width: 640px) {
  .tools {
    grid-template-columns: 1fr 1fr;
  }
}

@media (min-width: 1000px) {
  .tools {
    grid-template-columns: repeat(3, 1fr);
    gap: 16px;
  }
}

/* ==========================================================================
   TARIFS
   ========================================================================== */

/*
 * Le gratuit d'un côté, Pro de l'autre, chacun en quelques mots ; ce que
 * chacun contient est écrit une seule fois, dans le comparatif dessous. Les
 * deux listes à puces côte à côte se lisaient mal : il fallait deviner, ligne
 * par ligne, ce qui manquait au gratuit.
 */
.pricing {
  display: grid;
  gap: 24px;
  align-items: stretch;
  max-width: 1000px;
  margin: 0 auto;
}

.plan {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 28px 24px 24px;
  border-radius: var(--home-radius-xl);
  border: 1px solid var(--app-border-color);
  background: var(--app-card-background);
  box-shadow: var(--app-shadow-md), var(--home-surface-highlight);
}

.plan.featured {
  border: 2px solid var(--ion-color-primary);
  background:
    radial-gradient(
      90% 60% at 50% 0%,
      rgba(var(--ion-color-primary-rgb), 0.1),
      transparent 70%
    ),
    var(--app-card-background);
  box-shadow: 0 24px 60px rgba(var(--ion-color-primary-rgb), 0.2);
}

.plan-badge {
  position: absolute;
  top: -14px;
  left: 50%;
  transform: translateX(-50%);
  padding: 4px 12px;
  border-radius: var(--app-radius-full);
  background: var(--ion-color-primary);
  color: #ffffff;
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
}

.plan h3 {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
}

.plan .price {
  margin: 14px 0 0;
  font-size: 44px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.045em;
}

.plan .price small {
  color: var(--ion-color-medium);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0;
}

/* Sur téléphone, la période passe sous le prix : en ligne, « facturé à
   l'année » se coupait au milieu */
@media (max-width: 559px) {
  .plan .price small {
    display: block;
    margin-top: 8px;
  }
}

.plan .per {
  margin: 10px 0 0;
  color: var(--ion-color-medium);
  font-size: 15px;
}

.plan-summary {
  flex: 1;
  margin: 18px 0 24px;
  padding-top: 18px;
  border-top: 1px solid var(--tool-hairline);
  color: var(--ion-color-medium);
  font-size: 15px;
  line-height: 1.55;
}

.plan .button,
.plan .button-ghost {
  width: 100%;
}

.plans-note {
  margin: 24px auto 0;
  max-width: 720px;
  color: var(--ion-color-medium);
  font-size: 13px;
  text-align: center;
}

/* L'annuel vient en premier dans le document, donc sur téléphone ; sur un
   écran large il reprend la place du milieu, entre le mensuel et le à vie */
.offers {
  display: grid;
  gap: 10px;
  margin: 22px 0;
}

.offer {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 14px 16px;
  border-radius: var(--app-radius-md);
  border: 1px solid var(--app-border-color);
  background: var(--tool-fill);
}

.offer.yearly {
  border: 2px solid var(--ion-color-primary);
  background: rgba(var(--ion-color-primary-rgb), 0.07);
}

.offer-name {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 14px;
  font-weight: 700;
}

.offer-save {
  padding: 2px 8px;
  border-radius: var(--app-radius-full);
  background: rgba(var(--ion-color-success-rgb), 0.14);
  color: var(--text-success);
  font-size: 12px;
  white-space: nowrap;
}

.offer-price {
  font-size: 26px;
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.03em;
}

.offer-price small {
  color: var(--ion-color-medium);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0;
}

.offer-hint {
  color: var(--ion-color-medium);
  font-size: 13px;
}

.offer-note {
  margin: 12px 0 0;
  color: var(--ion-color-medium);
  font-size: 13px;
  text-align: center;
}

@media (min-width: 640px) {
  .offers {
    grid-template-columns: repeat(3, 1fr);
  }

  .offer.monthly {
    order: -1;
  }
}

@media (min-width: 900px) {
  .pricing {
    grid-template-columns: 1fr 1.8fr;
  }

  .plan {
    padding: 36px 32px 32px;
  }
}

/* Le comparatif : une vraie table, lue ligne par ligne ; la colonne Pro
   garde un voile de la couleur de sa carte */
.compare {
  max-width: 1000px;
  margin: 32px auto 0;
  overflow: hidden;
  border-radius: var(--home-radius-xl);
  border: 1px solid var(--app-border-color);
  background: var(--app-card-background);
  box-shadow: var(--app-shadow-md), var(--home-surface-highlight);
}

.compare-table {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
  font-size: 14.5px;
  line-height: 1.4;
}

.compare-table caption {
  padding: 22px 20px 6px;
  font-size: 18px;
  font-weight: 800;
  letter-spacing: -0.02em;
  text-align: left;
}

.compare-table thead th {
  padding: 10px 8px;
  color: var(--ion-color-medium);
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-align: center;
}

.compare-table thead .compare-pro {
  color: var(--text-primary);
}

.compare-table tbody th[scope='row'] {
  padding: 12px 12px 12px 20px;
  font-weight: 500;
  text-align: left;
}

.compare-table th small {
  display: block;
  color: var(--ion-color-medium);
  font-size: 12.5px;
}

.compare-table td,
.compare-table thead th:not(:first-child) {
  width: 76px;
}

.compare-table td {
  padding: 12px 6px;
  color: var(--ion-text-color);
  font-size: 13.5px;
  font-weight: 600;
  text-align: center;
}

.compare-table td:last-child,
.compare-table th.compare-pro {
  background: rgba(var(--ion-color-primary-rgb), 0.06);
}

.compare-table tbody tr {
  border-top: 1px solid var(--tool-hairline);
}

.compare-table .compare-group th {
  padding: 22px 20px 8px;
  color: var(--text-primary);
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  text-align: left;
  background: none;
}

.compare-table .yes {
  color: var(--text-success);
  font-size: 15px;
  vertical-align: middle;
}

.compare-table .no {
  color: var(--ion-color-medium);
}

@media (min-width: 640px) {
  .compare-table td,
  .compare-table thead th:not(:first-child) {
    width: 22%;
  }

  .compare-table td {
    font-size: 14.5px;
  }

  .compare-table caption {
    padding: 28px 28px 8px;
  }

  .compare-table tbody th[scope='row'],
  .compare-table .compare-group th {
    padding-left: 28px;
  }
}

/* ==========================================================================
   FAQ — sur un écran large, le titre reste à gauche pendant que les
   questions défilent
   ========================================================================== */

.faq-layout {
  display: grid;
  gap: 8px;
}

.faq-head a {
  color: var(--text-primary);
  font-weight: 600;
}

@media (min-width: 900px) {
  .faq-layout {
    grid-template-columns: 0.8fr 1.4fr;
    gap: 64px;
    align-items: start;
  }

  .faq-head {
    position: sticky;
    top: 96px;
    margin: 0;
    text-align: left;
  }

  .faq-head .kicker {
    justify-content: flex-start;
  }
}

.faq {
  width: 100%;
  max-width: 760px;
  margin: 0 auto;
}

.faq details {
  border-radius: var(--app-radius-lg);
  border: 1px solid var(--app-border-color);
  background: var(--app-card-background);
  box-shadow: var(--home-surface-highlight);
}

.faq details + details {
  margin-top: 10px;
}

.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 60px;
  padding: 14px 20px;
  cursor: pointer;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  list-style: none;
}

.faq summary:focus-visible {
  outline: 2px solid var(--ion-color-primary);
  outline-offset: -2px;
  border-radius: var(--app-radius-lg);
}

.faq summary::-webkit-details-marker {
  display: none;
}

.faq summary::after {
  content: '+';
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--tool-fill);
  color: var(--ion-color-medium);
  font-size: 20px;
  font-weight: 400;
  line-height: 1;
}

.faq details[open] summary::after {
  content: '+';
  transform: rotate(45deg);
}

/* La taille est redite : celle des pages testeurs (`.faq p`, style.css) l'emportait */
.faq details p {
  margin: 0;
  padding: 0 20px 20px;
  color: var(--ion-color-medium);
  font-size: 15px;
  line-height: 1.6;
}

/* ==========================================================================
   QUI FAIT LIFTZ
   ========================================================================== */

.about {
  display: flex;
  gap: 16px;
  align-items: flex-start;
  max-width: 760px;
  margin: 0 auto 32px;
  padding: 24px;
  border-radius: var(--app-radius-lg);
  border: 1px solid var(--app-border-color);
  background: var(--app-card-background);
  box-shadow: var(--app-shadow-md), var(--home-surface-highlight);
}

.about h2 {
  margin: 0 0 6px;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.about p {
  margin: 0;
  color: var(--ion-color-medium);
  font-size: 15px;
}

/* Le contact est une action, pas une phrase de plus : il se lit à part */
.about-contact {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
}

/* ==========================================================================
   APPEL FINAL
   ========================================================================== */

.cta-final {
  position: relative;
  /* `clip` et non `hidden` : voir `.facts-band` */
  overflow: clip;
  padding: 56px 24px;
  border-radius: var(--home-radius-xl);
  border: 1px solid var(--app-border-color);
  background: var(--app-card-background);
  box-shadow: var(--app-shadow-md), var(--home-surface-highlight);
  text-align: center;
}

.cta-final::before {
  content: '';
  position: absolute;
  inset: -40% -20% auto;
  height: 130%;
  pointer-events: none;
  background: radial-gradient(
    60% 60% at 50% 0%,
    rgba(var(--ion-color-primary-rgb), 0.24),
    transparent 70%
  );
}

.cta-final > * {
  position: relative;
}

.cta-final img {
  width: 76px;
  height: 76px;
  border-radius: var(--app-radius-md);
  border: 1px solid var(--app-border-color);
  box-shadow: 0 16px 40px rgba(var(--ion-color-primary-rgb), 0.3);
}

.cta-final h2 {
  max-width: 18ch;
  margin: 22px auto 0;
  font-size: var(--home-h2);
  font-weight: 800;
  line-height: 1.06;
  letter-spacing: -0.045em;
  text-wrap: balance;
}

.cta-final p {
  max-width: 44ch;
  margin: 14px auto 28px;
  text-wrap: balance;
  color: var(--ion-color-medium);
  font-size: 17px;
}

.cta-points {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px 20px;
  margin: 20px 0 0;
  padding: 0;
  list-style: none;
  color: var(--ion-color-medium);
  font-size: 13.5px;
  font-weight: 600;
}

.cta-points li + li::before {
  content: '·';
  margin-right: 20px;
  margin-left: -12px;
}

/* Le lien de retour de la page des nouveautés, sous le bouton */
.cta-final .cta-more {
  margin: 20px auto 0;
  font-size: 15px;
}

.cta-final .cta-more a {
  color: var(--text-primary);
  font-weight: 600;
}

@media (min-width: 900px) {
  .cta-final {
    padding: 96px 40px;
  }

  .cta-final h2 {
    font-size: 56px;
  }
}

/* ==========================================================================
   MOUVEMENT

   Le langage de l'app (jetons de style.css, recopiés de variables.css) et
   celui des vidéos que l'utilisateur a validées : un film produit, pas de
   rebond, pas de paillettes. Chaque sorte de chose a un seul geste :
   - un titre monte mot à mot de sous sa ligne (le masque des vidéos) ;
   - un écran s'ouvre comme une fenêtre ;
   - une carte se pose, en vague quand elles sont plusieurs côte à côte ;
   - un chiffre compte ;
   - un paragraphe ne bouge jamais seul.

   Seul le héros est joué à l'horloge, une fois, au chargement. Tout le reste
   suit le défilement (`animation-timeline`) : à mi-course l'élément est à
   mi-chemin, remonter rejoue en sens inverse, et ce qui est déjà à l'écran à
   l'ouverture est déjà en place — la règle de l'app : rien ne s'anime quand
   un contenu déjà là s'affiche.

   Sans `prefers-reduced-motion: no-preference`, rien de ce qui suit ne
   s'applique : la page est complète, immobile. Un navigateur sans animation
   au défilement (Firefox, Safari avant 26) garde tout visible : l'état caché
   n'existe que dans les images clés, jamais dans une règle ordinaire.

   Les canaux sont séparés pour que deux animations d'un même élément ne
   s'écrasent pas : `transform` pour l'entrée, `translate` pour la profondeur
   et le survol, `scale` pour ce qui s'avance. `animation-timeline` s'écrit
   toujours après le raccourci `animation`, qui le remet à zéro.
   ========================================================================== */

/* Déclarées : une variable non déclarée s'anime par paliers, pas en nombre */
@property --fact-n {
  syntax: '<integer>';
  inherits: false;
  initial-value: 0;
}

@property --fact-ink {
  syntax: '<color>';
  inherits: true;
  initial-value: transparent;
}

@media (prefers-reduced-motion: no-preference) {
  /* ---- Les mots d'un titre ---------------------------------------------- */

  /*
   * Le rang de chaque mot dans son titre, pour la vague. Un mot dans `.accent`
   * prend le rang de l'accent plus le sien : « Devinez moins. » suit
   * « Soulevez plus. » au lieu de partir avec lui.
   */
  .hero-title,
  h2 {
    --wa: 0;
  }

  .w {
    --wi: 0;
  }

  .w:nth-child(2) {
    --wi: 1;
  }

  .w:nth-child(3) {
    --wi: 2;
  }

  .w:nth-child(4) {
    --wi: 3;
  }

  .w:nth-child(5) {
    --wi: 4;
  }

  .w:nth-child(6) {
    --wi: 5;
  }

  .w:nth-child(7) {
    --wi: 6;
  }

  .w:nth-child(8) {
    --wi: 7;
  }

  .w:nth-child(9) {
    --wi: 8;
  }

  .w:nth-child(n + 10) {
    --wi: 9;
  }

  .accent:nth-child(2) {
    --wa: 1;
  }

  .accent:nth-child(3) {
    --wa: 2;
  }

  .accent:nth-child(4) {
    --wa: 3;
  }

  .accent:nth-child(5) {
    --wa: 4;
  }

  .accent:nth-child(n + 6) {
    --wa: 5;
  }

  /*
   * Un mot qui bouge porte lui-même le dégradé : posé sur `.accent` avec
   * `background-clip: text`, il restait peint à la place d'origine du mot
   * pendant que le mot, transparent, montait ailleurs.
   */
  .accent:has(.w) {
    background: none;
  }

  .accent .w {
    background: var(--button-gradient);
    -webkit-background-clip: text;
    background-clip: text;
  }

  /*
   * Le masque sans élément de plus : le mot descend de 110 % de sa hauteur et
   * son `clip-path` rogne d'autant son bas, sur la même courbe. Ce qui reste
   * visible s'arrête donc toujours au bas de sa ligne : le mot sort de sous
   * elle. Les marges négatives de la fin rendent les jambages et les accents.
   */
  @keyframes word-rise {
    from {
      transform: translateY(110%);
      clip-path: inset(-0.35em -0.2em 110% -0.2em);
    }

    to {
      transform: none;
      clip-path: inset(-0.35em -0.2em -0.35em -0.2em);
    }
  }

  /* ---- Le héros : il se monte une fois, à l'horloge (≈ 1,1 s) ----------- */

  .hero-copy > .eyebrow,
  .hero-copy > .lead,
  .hero-actions,
  .proof {
    animation: hero-rise var(--app-motion-slow) var(--app-ease-out) both;
  }

  .hero-copy > .lead {
    animation-duration: calc(var(--app-motion-slow) * 1.4);
    animation-delay: 200ms;
  }

  /* Cliquable dès la première image : l'opacité n'arrête pas le doigt */
  .hero-actions {
    animation-delay: 280ms;
  }

  .proof {
    animation-delay: 360ms;
  }

  @keyframes hero-rise {
    from {
      opacity: 0;
      transform: translateY(10px);
    }
  }

  .hero-title .w {
    display: inline-block;
    animation: word-rise var(--home-motion-title) var(--app-ease-out) both;
    animation-delay: calc(60ms + (var(--wa) + var(--wi)) * var(--home-stagger));
  }

  /* Le mot-clé se souligne d'un trait qui se déroule, comme dans les vidéos */
  .hero-title .accent {
    position: relative;
  }

  .hero-title .accent::after {
    content: '';
    position: absolute;
    left: 0.02em;
    right: 0.04em;
    bottom: -0.02em;
    height: 0.06em;
    border-radius: var(--app-radius-full);
    background: var(--button-gradient);
    transform-origin: left center;
    animation: underline-draw var(--app-motion-slow) var(--app-ease-out) 560ms
      both;
  }

  @keyframes underline-draw {
    from {
      transform: scaleX(0);
    }
  }

  /* La pastille « Hors ligne » : un voyant qui émet, sur un calque à lui
     (l'ancienne ombre portée repeignait la pastille à chaque image) */
  .eyebrow .dot {
    position: relative;
  }

  .eyebrow .dot::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: currentColor;
    opacity: 0;
    animation: dot-ping 2.4s var(--app-ease-out) 1.2s infinite;
  }

  @keyframes dot-ping {
    from {
      opacity: 0.55;
      transform: scale(1);
    }

    to {
      opacity: 0;
      transform: scale(3.2);
    }
  }

  /*
   * Les téléphones arrivent de loin, penchés en arrière, et se redressent :
   * celui de face d'abord, puis celui de derrière, puis les deux cartes.
   * Celui de derrière garde sa rotation de 6° : l'image de fin est laissée
   * implicite, c'est donc sa valeur à lui.
   * Au défilement, la profondeur : sur le premier écran parcouru, celui de
   * derrière descend plus que celui de face (il est plus loin), les cartes
   * montent (elles sont plus près). Verticalement seulement : un décalage de
   * côté faisait déborder l'écran du téléphone.
   */
  .hero-section {
    --hero-depth: 0.5;
  }

  .hero-glow {
    animation: hero-glow-in var(--home-motion-hero) var(--app-ease-out) both;
  }

  @keyframes hero-glow-in {
    from {
      opacity: 0;
      transform: scale(0.7);
    }
  }

  .hero-stage .phone-front,
  .hero-stage .phone-back,
  .hero-stage .float {
    animation:
      phone-arrive var(--home-motion-hero) var(--app-ease-out) 120ms both,
      hero-depth linear both;
    animation-timeline: auto, scroll(root);
    animation-range:
      normal,
      0 100vh;
  }

  .hero-stage .phone-front {
    --depth: calc(24px * var(--hero-depth));
  }

  .hero-stage .phone-back {
    --depth: calc(64px * var(--hero-depth));
    animation-name: phone-back-arrive, hero-depth;
    animation-delay: 260ms, 0s;
  }

  /* Les cartes se posent au ressort `gentle` de l'app, qui ne dépasse pas */
  .hero-stage .float {
    --depth: calc(-40px * var(--hero-depth));
    animation:
      float-arrive var(--app-spring-gentle) 640ms both,
      hero-depth linear both;
    animation-timeline: auto, scroll(root);
    animation-range:
      normal,
      0 100vh;
  }

  .hero-stage .float-b {
    --depth: calc(-24px * var(--hero-depth));
    animation-delay: 730ms, 0s;
  }

  @keyframes phone-arrive {
    from {
      opacity: 0;
      transform: perspective(1400px) translateY(56px) rotateX(16deg) scale(0.94);
    }
  }

  @keyframes phone-back-arrive {
    from {
      opacity: 0;
      transform: translateY(72px) rotate(10deg) scale(0.92);
    }
  }

  @keyframes float-arrive {
    from {
      opacity: 0;
      transform: translateY(14px) scale(0.92);
    }
  }

  @keyframes hero-depth {
    to {
      translate: 0 var(--depth);
    }
  }

  /* L'écran de face se met au point en même temps que le téléphone arrive */
  .hero-stage .phone-front .screen {
    animation: screen-focus var(--home-motion-hero) var(--app-ease-out) 120ms
      both;
  }

  @keyframes screen-focus {
    from {
      transform: scale(1.06);
    }
  }

  .hero-caption {
    animation: hero-fade var(--app-motion-slow) var(--app-ease-out) 900ms both;
  }

  @keyframes hero-fade {
    from {
      opacity: 0;
    }
  }

  @media (min-width: 900px) {
    .hero-section {
      --hero-depth: 1;
    }
  }

  /* ---- En continu : ce qui reste vivant --------------------------------- */

  /* L'écran défile tout seul, s'arrête en haut et en bas, puis remonte : c'est
     le contenu, pas une décoration */
  .shot-scroll .shot {
    animation: screen-scroll 22s var(--app-ease-in-out) infinite;
  }

  @keyframes screen-scroll {
    0%,
    12% {
      transform: translateY(0);
    }

    46%,
    60% {
      transform: translateY(calc(-1 * var(--scroll)));
    }

    94%,
    100% {
      transform: translateY(0);
    }
  }

  /* La lueur de l'appel final respire, et un halo vit sous son bouton : le
     seul appel de la page qui bouge seul */
  .cta-final {
    isolation: isolate;
  }

  .cta-final::before {
    animation: cta-glow-breathe 10s var(--app-ease-in-out) infinite;
  }

  @keyframes cta-glow-breathe {
    0%,
    100% {
      opacity: 0.75;
      transform: scale(1);
    }

    50% {
      opacity: 1;
      transform: scale(1.08) translateY(2%);
    }
  }

  .cta-final .button::before {
    content: '';
    position: absolute;
    inset: -16px -28px;
    z-index: -1;
    pointer-events: none;
    border-radius: var(--app-radius-full);
    background: radial-gradient(
      closest-side,
      rgba(var(--ion-color-primary-rgb), 0.42),
      transparent
    );
    animation: cta-halo 2.6s var(--app-ease-in-out) infinite alternate;
  }

  @keyframes cta-halo {
    from {
      opacity: 0.35;
      transform: scale(0.9);
    }

    to {
      opacity: 1;
      transform: scale(1.06);
    }
  }

  /* ---- Les réponses de la FAQ s'ouvrent au lieu de sauter --------------- */

  .faq summary::after {
    transition: transform var(--app-transition-fast);
  }

  /*
   * La hauteur est une propriété de mise en page : la seule animée ici, comme
   * dans l'app (docs/motion, décision 6) — une réponse, à l'instant où on
   * l'ouvre. Un navigateur sans `interpolate-size` l'ouvre d'un coup.
   */
  @supports (interpolate-size: allow-keywords) {
    .faq details {
      interpolate-size: allow-keywords;
    }

    .faq details::details-content {
      block-size: 0;
      overflow-y: clip;
      opacity: 0;
      transition:
        block-size var(--app-transition),
        opacity var(--app-transition),
        content-visibility var(--app-motion-base) allow-discrete;
    }

    .faq details[open]::details-content {
      block-size: auto;
      opacity: 1;
    }
  }

  /* ---- Au défilement ---------------------------------------------------- */

  @supports (animation-timeline: view()) {
    /*
     * Se poser : un peu de hauteur et d'échelle, moins que les 32 px d'avant,
     * qui faisaient de chaque carte un effet. En place avant la moitié de son
     * entrée : on ne lit pas un bloc qui bouge encore. `--si` décale les
     * voisins d'une même rangée, pour la vague.
     */
    .reveal {
      animation: settle var(--app-ease-out) both;
      animation-timeline: view();
      animation-range: entry calc(var(--si, 0) * 7%) entry
        calc(50% + var(--si, 0) * 7%);
    }

    @keyframes settle {
      from {
        opacity: 0;
        transform: translateY(20px) scale(0.985);
      }

      to {
        opacity: 1;
        transform: none;
      }
    }

    .facts-band .fact:nth-child(2n) {
      --si: 1;
    }

    @media (min-width: 640px) and (max-width: 999px) {
      .tools .tool:nth-child(2n) {
        --si: 1;
      }
    }

    @media (min-width: 640px) and (max-width: 959px) {
      .bento-tile:nth-child(2n + 3) {
        --si: 1;
      }
    }

    @media (min-width: 900px) {
      .facts-band .fact:nth-child(3) {
        --si: 2;
      }

      .facts-band .fact:nth-child(4) {
        --si: 3;
      }

      .pricing .plan:nth-child(2) {
        --si: 1;
      }
    }

    @media (min-width: 960px) {
      /* La tuile large prend deux colonnes : sa voisine est la troisième de
         la rangée, et la large, elle, part la première */
      .bento-tile:nth-child(3n + 1) {
        --si: 1;
      }

      .bento-tile:nth-child(3n + 2) {
        --si: 2;
      }

      .bento-tile:nth-child(3n),
      .bento-tile:first-child {
        --si: 0;
      }
    }

    @media (min-width: 1000px) {
      .tools .tool:nth-child(3n + 2) {
        --si: 1;
      }

      .tools .tool:nth-child(3n) {
        --si: 2;
      }
    }

    /* Les titres de section, mot à mot. Chaque mot suit sa propre position :
       la seconde ligne d'un titre vient donc d'elle-même après la première.
       Tôt : un titre est entier avant le dernier quart bas de l'écran (au
       plus 20 % du parcours), jamais coupé là où l'on s'arrête pour lire */
    h2 .w {
      display: inline-block;
      animation: word-rise var(--app-ease-out) both;
      animation-timeline: view();
      animation-range: cover calc((var(--wa) + var(--wi)) * 0.8%) cover
        calc(12% + (var(--wa) + var(--wi)) * 0.8%);
    }

    /*
     * Les chiffres comptent. Le vrai nombre reste dans la page (lecteur
     * d'écran, recherche, `yarn docs:facts`), transparent pendant le compte ;
     * le compteur est un `::after` muet (`/ ''`) posé par-dessus. À la fin, le
     * vrai nombre reprend sa couleur, avec son séparateur de milliers que le
     * compteur n'a pas, et le compteur s'efface — d'un pas, sans fondu entre
     * deux écritures.
     */
    @supports (content: 'a' / '') {
      .fact-value {
        position: relative;
        counter-reset: fact var(--fact-n);
        animation: fact-count var(--app-ease-out) both;
        animation-timeline: view();
        /* Le compte tient dans l'entrée du chiffre lui-même : un chiffre
           entier à l'écran est toujours le vrai. Avec une course plus longue,
           un visiteur arrêté là lisait « 728 exercices » au bas de l'écran */
        animation-range: entry 0% entry 100%;
      }

      .fact-value::after {
        content: counter(fact) / '';
        position: absolute;
        inset: 0;
        color: var(--fact-ink);
        white-space: nowrap;
      }

      @keyframes fact-count {
        0% {
          --fact-n: 0;
          --fact-ink: var(--ion-text-color);
          color: transparent;
        }

        97% {
          --fact-n: var(--to);
          --fact-ink: var(--ion-text-color);
          color: transparent;
          animation-timing-function: step-end;
        }

        100% {
          --fact-n: var(--to);
          --fact-ink: transparent;
          color: var(--ion-text-color);
        }
      }
    }

    /* La vidéo s'agrandit jusqu'à sa place en entrant, au lieu de monter */
    .video.reveal {
      animation-name: video-grow;
      animation-range: entry 0% cover 45%;
    }

    @keyframes video-grow {
      from {
        opacity: 0.3;
        transform: scale(0.9);
      }

      to {
        opacity: 1;
        transform: none;
      }
    }

    /*
     * Les écrans de la grille s'ouvrent comme une fenêtre, et la capture se
     * met au point dedans. Le `clip-path` ne coupe que la capture, jamais
     * l'ombre ni la tuile.
     */
    .bento-shot {
      animation: shot-open var(--app-ease-out) both;
      animation-timeline: view();
      animation-range: entry 0% cover 40%;
    }

    .bento-screen img {
      animation: shot-focus var(--app-ease-out) both;
      animation-timeline: view();
      animation-range: entry 0% cover 50%;
    }

    @keyframes shot-open {
      from {
        clip-path: inset(18% 10% 0 10% round 20px 20px 0 0);
      }

      to {
        clip-path: inset(0 round 20px 20px 0 0);
      }
    }

    @keyframes shot-focus {
      from {
        transform: scale(1.12);
      }

      to {
        transform: none;
      }
    }

    /* Les schémas : les flèches du cycle et le lien de la sauvegarde coulent
       quand on fait défiler, et s'arrêtent avec la page (avant : en boucle,
       un trait repeint à chaque image tant que la page était ouverte) */
    .diagram {
      view-timeline: --diagram block;
    }

    .diagram .arc {
      stroke-dasharray: 6 6;
    }

    .diagram .arc,
    .privacy-diagram .link {
      animation: dash-scrub linear both;
      animation-timeline: --diagram;
    }

    @keyframes dash-scrub {
      from {
        stroke-dashoffset: 60;
      }

      to {
        stroke-dashoffset: -60;
      }
    }

    /* Sur téléphone, l'histoire est une pile : chaque écran monte et se
       redresse en entrant, comme ceux du héros */
    @media (max-width: 899px) {
      .story-shot {
        animation: phone-rise var(--app-ease-out) both;
        animation-timeline: view();
        animation-range: entry 0% cover 35%;
      }
    }

    @keyframes phone-rise {
      from {
        opacity: 0;
        transform: translateY(48px) rotate(-3deg) scale(0.94);
      }

      to {
        opacity: 1;
        transform: none;
      }
    }
  }

  /*
   * Ce qui lit plusieurs lignes de temps à la fois : `timeline-scope` remonte
   * le nom d'une ligne de temps jusqu'à un parent commun, pour qu'un élément
   * d'ailleurs la suive.
   */
  @supports (animation-timeline: view()) and (timeline-scope: none) {
    /*
     * Le bouton de la barre attend que celui du héros soit passé sous elle :
     * deux fois « Télécharger » à l'écran, c'était un de trop. `visibility`
     * le retire aussi du clavier tant qu'il est caché. Sur la page des
     * nouveautés, qui n'a pas de héros, la ligne de temps n'existe pas et le
     * bouton reste là.
     */
    body {
      timeline-scope: --hero-cta;
    }

    .hero-cta {
      view-timeline: --hero-cta block;
    }

    .topbar-cta {
      animation: topbar-cta-in var(--app-ease-out) both;
      animation-timeline: --hero-cta;
      animation-range: exit -72px exit 0%;
    }

    @keyframes topbar-cta-in {
      from {
        visibility: hidden;
        opacity: 0;
        translate: 0 -6px;
        scale: 0.94;
      }

      to {
        visibility: visible;
        opacity: 1;
        translate: 0 0;
        scale: 1;
      }
    }

    /*
     * L'histoire, téléphone tenu (écran large, voir L'HISTOIRE plus haut) :
     * chaque étape porte sa ligne de temps, et l'écran change à la frontière
     * entre deux étapes. L'étape lue est pleine, les autres s'estompent.
     */
    @media (min-width: 900px) {
      .story-grid {
        timeline-scope: --story-1, --story-2, --story-3;
      }

      .story-step:nth-child(1) {
        view-timeline: --story-1 block;
      }

      .story-step:nth-child(2) {
        view-timeline: --story-2 block;
      }

      .story-step:nth-child(3) {
        view-timeline: --story-3 block;
      }

      .story-step {
        animation: step-focus linear both;
        animation-timeline: view();
        animation-range: cover 0% cover 100%;
      }

      /* La première étape est lue dès qu'on arrive, la dernière jusqu'au
         bout : elles ne s'estompent que du côté où une autre les remplace */
      .story-step:first-child {
        animation-name: step-focus-out;
      }

      .story-step:last-child {
        animation-name: step-focus-in;
      }

      @keyframes step-focus {
        0% {
          opacity: 0.28;
        }

        34%,
        66% {
          opacity: 1;
        }

        100% {
          opacity: 0.28;
        }
      }

      @keyframes step-focus-out {
        0%,
        66% {
          opacity: 1;
        }

        100% {
          opacity: 0.28;
        }
      }

      @keyframes step-focus-in {
        0% {
          opacity: 0.28;
        }

        34%,
        100% {
          opacity: 1;
        }
      }

      /*
       * Les écrans sont les enfants 2 à 4 de la scène (`.story-glow` est le
       * premier) ; le premier est dessus. À la frontière, celui du dessus est
       * essuyé de bas en haut, comme poussé par le suivant qui monte avec la
       * page, et s'assombrit en partant. Jamais de fondu : deux captures
       * denses mêlées à moitié ne se lisaient plus ni l'une ni l'autre. Le
       * cadre, l'ombre et la place des trois téléphones sont les mêmes : la
       * coupe ne se voit que sur l'écran.
       */
      .story-shot:nth-child(2) {
        animation: story-wipe var(--app-ease-in-out) both;
        animation-timeline: --story-2;
        animation-range: cover 16% cover 30%;
      }

      .story-shot:nth-child(3) {
        animation: story-wipe var(--app-ease-in-out) both;
        animation-timeline: --story-3;
        animation-range: cover 16% cover 30%;
      }

      @keyframes story-wipe {
        from {
          clip-path: inset(0 0 0 0);
          filter: brightness(1);
        }

        to {
          clip-path: inset(0 0 100% 0);
          filter: brightness(0.55);
        }
      }
    }
  }
}

/* Le point qui fait le tour du cycle est une animation SVG (animateMotion),
   que la media query de mouvement réduit ne suspend pas : on le retire */
@media (prefers-reduced-motion: reduce) {
  .orbit {
    display: none;
  }
}

/* Les intitulés gardent la couleur du texte : les règles « span » ci-dessus
   teignent aussi le span qui les enveloppe, et le gras en héritait le gris */
.points b,
.steps b,
.tool b,
.float strong {
  color: var(--ion-text-color);
}

/* Le survol passe par `translate` : `transform` appartient à l'animation
   d'entrée, qui, remplie jusqu'au bout, l'aurait emporté sur lui */
.tool,
.steps li,
.news-banner,
.bento-tile {
  transition:
    translate var(--app-transition-fast),
    box-shadow var(--app-transition-fast),
    border-color var(--app-transition-fast);
}

@media (hover: hover) {
  .tool:hover,
  .steps li:hover,
  .news-banner:hover {
    translate: 0 -3px;
    box-shadow: 0 16px 36px rgba(0, 0, 0, 0.16);
  }

  .bento-tile:hover {
    border-color: rgba(var(--ion-text-color-rgb), 0.16);
  }
}
