/* Hephira, site vitrine.
 * Transposition du projet Claude Design `ab28a64c-c543-4aaa-a920-0b1b6722af1c` :
 * tokens repris de `_ds/miso-alex-design-system-27e3bf6f/tokens/*.css`, mise en
 * page reprise de `landing/landing.css`, qui est declare "layout only".
 *
 * Ordre du fichier : tokens, @font-face, base, composants, sections.
 *
 * REGLE : le bloc de tokens delimite ci-dessous est le SEUL endroit ou une
 * valeur de couleur litterale (hexadecimale, rgb, mot-cle) est autorisee.
 * Toute regle de couleur plus bas passe par une variable. La regle est
 * verifiee mecaniquement par test/build.test.mjs, qui lit ces deux marqueurs.
 */

/* === TOKENS: DEBUT === */
:root {
  color-scheme: light dark;

  /* ---- Surfaces (clair) ---- */
  --bg-base:          #ECEAF4;
  --bg-surface:       #FFFFFF;
  --bg-surface-tint:  #F1EFF8;
  --bg-gray:          #E8E5F0;

  /* ---- Rampe lavande ----
   * Palette bleu-lavande perwinkle. La direction creme et noir a ete
   * ABANDONNEE par le design system, ne pas la reintroduire. */
  --lavender-soft:     #9DA8CF;
  --lavender-mid:      #8C9AC8;
  --lavender-primary:  #8078BC;   /* aplats, bordures, marques : contraste non textuel */
  --lavender-hover:    #6F66AB;
  /* Lavande de TEXTE. `--lavender-primary` est calibre pour des aplats : pose
     en `color:` sur les fonds clairs du site il plafonne a 3,3:1, sous le
     seuil WCAG AA. Cette valeur-ci monte a 4,7 sur le fond gris et 5,9 sur
     blanc. */
  --lavender-ink:      #645C9E;
  /* Remplissage des boutons. Le #8078BC de la maquette ne donne que 3,9:1 avec
     du blanc, sous AA : le bouton descend d'un cran dans la meme rampe. */
  --lavender-cta:      #6F66AB;
  --lavender-cta-hover:#5A5390;
  --lavender-deep:     #646B8D;
  --lavender-soft-bg:  #DDD8EC;

  /* ---- Sombre (nav et pied de page, dans les deux themes) ---- */
  --bg-dark:          #12111A;
  --bg-surface-dark:  #1E1C2A;
  /* La nav et le pied de page sont sombres quel que soit le theme : ce sont
   * des scenes, pas des surfaces de contenu. En mode nuit ils prennent la
   * surface sombre plutot que le fond sombre, sinon ils se confondraient avec
   * la page. */
  --bg-chrome:        #12111A;

  /* ---- Texte ---- */
  --text-primary:           #1F1A33;
  --text-soft:              #3D3559;
  --text-secondary:         #61628A;
  --text-tertiary:          #67657A;   /* assombri : le #9391A3 du design system ne donne que 2,6:1 sur --bg-base */
  --text-on-dark:           #FFFFFF;
  /* Encre posee SUR un aplat lavande (boutons, pilule active). En mode nuit la
     rampe lavande s'eclaircit et du blanc dessus tomberait a 2,2:1 : elle
     bascule alors sur l'encre sombre. */
  --text-on-lavender:       #FFFFFF;
  --text-on-dark-secondary: rgba(255, 255, 255, 0.72);
  --text-on-dark-tertiary:  rgba(255, 255, 255, 0.62);
  /* Encre secondaire OPAQUE des scenes sombres. Un blanc a 72 % pose sur
     `--bg-chrome` donne #BDBCBF au rendu : un gris a 1,5 % de saturation, alors
     que toute la palette de texte est teintee violet autour de 250 degres. Le
     lien de nav paraissait donc etranger a sa propre palette. Cette valeur-ci
     est la meme clarte, mais teintee : hsl(250 12% 74%).
     Elle ne remplace `--text-on-dark-secondary` QUE sur les liens de section de
     la barre. Le pied de page, les declencheurs de menu et les liens des
     panneaux gardent le blanc en opacite : leur reprise est une decision qui
     n'a pas ete demandee ici. */
  --text-on-dark-secondary-solid: #B7B5C5;

  /* ---- Bordures ---- */
  --border-subtle:         rgba(96, 99, 138, 0.10);
  --border-default:        rgba(96, 99, 138, 0.18);
  --border-on-dark:        rgba(255, 255, 255, 0.10);
  --border-on-dark-strong: rgba(255, 255, 255, 0.18);
  /* Aplat de survol des scenes sombres. Il existe parce que `--border-on-dark`
     etait employe comme REMPLISSAGE : un token de bordure pose en aplat sur
     79 x 44 px donne 1,3:1 avec la barre, trop faible pour signaler un etat et
     trop neutre pour paraitre voulu. Celui-ci est teinte de la lavande de
     marque, donc lisible comme une intention.
     REGLE : `--border-on-dark` ne sert plus jamais de `background`, uniquement
     de `border`. Un test le verifie. */
  --surface-hover-on-dark: rgba(128, 120, 188, 0.28);

  /* ---- Accents ---- */
  --accent-red:  #B03A3A;   /* annotations de l'apex UNIQUEMENT, seul accent non lavande. Assombri depuis #CC4444, qui ne donnait que 3,9:1 sur --bg-base */
  --focus-ring:  rgba(128, 120, 188, 0.60);

  /* ---- Degrade signature, confine aux cartes, jamais pleine page ---- */
  /* Degrade signature, approfondi. Les valeurs du design system
     (#9DA8CF, #8C9AC8, #8078BC) portent du texte blanc a 2,3 / 2,8 / 3,9:1,
     tres en dessous de AA sur la carte qui est la preuve produit du hero.
     Meme teinte, meme sens, trois crans plus bas : le blanc y passe a 6,0:1
     au point le plus clair. */
  --gradient-signature: linear-gradient(140deg, #635B9C 0%, #5A5390 50%, #4E477F 100%);
  /* Trames des emplacements de la maquette (NS.Slot) */
  --slot-hatch: repeating-linear-gradient(45deg, rgba(96, 99, 138, 0.03), rgba(96, 99, 138, 0.03) 10px, rgba(96, 99, 138, 0) 10px, rgba(96, 99, 138, 0) 20px);
  --slot-border: rgba(96, 99, 138, 0.25);
  /* Traits internes de la carte de progression, posee sur le degrade */
  --on-gradient-line:  rgba(255, 255, 255, 0.90);
  --on-gradient-fill:  rgba(255, 255, 255, 0.12);
  --on-gradient-rule:  rgba(255, 255, 255, 0.20);
  --on-gradient-label: rgba(255, 255, 255, 0.82);
  --on-gradient-sub:   rgba(255, 255, 255, 0.86);
  /* Fond du groupe du selecteur tarifaire */
  --toggle-track: rgba(96, 99, 138, 0.08);

  /* ---- Familles ----
   * `system-ui` est place juste apres la police latine et AVANT toute serif ou
   * sans-serif generique, volontairement : DM Sans et Playfair Display ne
   * couvrent ni le thai ni le japonais, et les @font-face ci-dessous sont
   * bornees par `unicode-range` aux sous-ensembles latin et latin-ext. Les
   * points de code thai et japonais ne matchent donc aucune webfont et
   * tombent sur la police systeme de l'appareil, toujours presente. Sans
   * `system-ui` a cette place, ils tomberaient sur Georgia ou Helvetica, qui
   * sont latines : le rendu observe cote produit etait alors du charabia
   * ("ไทย" affiche "Înʁ", signalement proprietaire du 2026-07-11). Le design
   * system met `Georgia` directement apres Playfair : c'est le seul endroit ou
   * ce site s'ecarte de ses tokens, et c'est deliberé. */
  --serif: "Playfair Display", system-ui, Georgia, "Times New Roman", serif;
  --sans:  "DM Sans", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
  --mono:  ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* ---- Tailles ---- */
  --fs-hero-h1:  clamp(44px, 7vw, 92px);
  --fs-sec-h2:   clamp(32px, 4.5vw, 56px);
  --fs-block-h3: clamp(28px, 4vw, 48px);
  --fs-apex-h3:  clamp(32px, 5vw, 64px);
  --fs-step-h3:  clamp(24px, 3vw, 36px);
  --fs-lead:     19px;
  --fs-body:     17px;
  --fs-body-sm:  16px;
  --fs-ui:       14px;
  --fs-kicker:   11px;

  /* ---- Graisses ---- */
  --fw-light:    300;
  --fw-regular:  400;
  --fw-medium:   500;
  --fw-semibold: 600;

  /* ---- Interlignes ---- */
  --lh-tight:   1.05;
  --lh-heading: 1.1;
  --lh-snug:    1.15;
  --lh-body:    1.55;
  --lh-relaxed: 1.6;

  /* ---- Approches ---- */
  --ls-hero:   -0.015em;
  --ls-h2:     -0.02em;
  --ls-tight:  -0.025em;
  --ls-kicker: 0.22em;
  --ls-micro:  0.04em;

  /* ---- Espacements, base 4 ---- */
  --space-1:  4px;
  --space-2:  8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-5:  24px;
  --space-6:  32px;
  --space-7:  48px;
  --space-8:  64px;
  --space-9:  80px;
  --space-10: 120px;

  --section-py:        96px;
  --section-py-mobile: 56px;
  --container: 1200px;
  --gutter:    40px;
  /* Hauteur EXACTE de la barre depuis que son filet bas est retire : plus rien
   * ne s'ajoute a `min-height`. Elle est consommee ailleurs, notamment par le
   * cadrage du hero et par `scroll-padding-top` : la changer deplace la ligne
   * de flottaison et l'atterrissage des ancres. */
  --nav-h:     76px;

  /* ---- Rayons ---- */
  --radius-xs:   3px;
  --radius-sm:   4px;
  --radius-md:   12px;
  --radius-lg:   16px;
  --radius-xl:   20px;
  --radius-pill: 999px;

  /* ---- Ombres ---- */
  --shadow-card:   0 4px 24px rgba(96, 99, 138, 0.08);
  --shadow-toggle: 0 1px 3px rgba(96, 99, 138, 0.10);
  /* Panneaux deroulants de la nav. Elle est sombre dans les deux themes, donc
   * cette ombre ne s'inverse pas en mode nuit, contrairement aux deux
   * au-dessus. */
  --shadow-menu:   0 12px 32px rgba(0, 0, 0, 0.36);

  /* ---- Transitions ---- */
  --t-fast: 180ms ease;
  --t-base: 240ms ease;

  /* ---- Mouvement de revelation au defilement ----
   * Amplitude volontairement faible : 14 px, pas 60. C'est ce qui separe une
   * page qui se pose d'une page qui saute.
   *
   * Il n'y a PAS de token de duree en millisecondes ici, et c'est delibere :
   * les revelations sont pilotees par `animation-timeline: view()`, ou la
   * duree n'est pas un temps mais une distance de defilement. Une valeur en
   * ms posee sur ces animations serait ignoree par le moteur, donc du code
   * mort qui donnerait l'illusion d'un reglage. L'equivalent en espace de
   * defilement est `--reveal-span`, exprime sur la plage `cover` : 20 % du
   * trajet de l'element a travers la fenetre.
   *
   * `--e-out` reste une fonction de progression, valable dans les deux
   * espaces : c'est elle qui donne la deceleration. */
  --e-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --reveal-shift: 14px;
  --reveal-span: 20%;
  /* Fondu de changement de document. Celui-la est un VRAI temps : une
   * transition de vue est chronometree, pas pilotee par le defilement. */
  --t-page: 160ms;
}

/* ---- Plafond du titre du hero, par langue ----
 *
 * Le titre est en trois lignes FIXES, separees par des `<br>` : « Tes repas. »,
 * « Ta messagerie. », « Ta progression. ». Une quatrieme ligne ne peut venir
 * que d'un retour a la ligne subi, qui casse le rythme voulu et decale tout le
 * hero. La taille maximale qui tient sur trois lignes depend donc de la
 * LONGUEUR DE LA PLUS LONGUE des trois chaines, qui varie du simple au double
 * selon la langue.
 *
 * Mesure du 2026-08-09, colonne gauche de la grille du hero, taille maximale
 * tenant sur trois lignes a 1440 px et pente maximale tenant a 901 px, largeur
 * ou la grille se scinde et ou la colonne est la plus etroite :
 *
 *   es 116px / 9.06vw    pt 105px / 8.20vw    en 106px / 8.28vw
 *   fr 100px / 7.81vw    it  98px / 7.66vw
 *   de  84px / 6.56vw    pl  78px / 6.09vw    th 70px / 5.47vw    ja 47px / 3.66vw
 *
 * Les cinq premieres passent le reglage general sans rien changer. Les quatre
 * autres recoivent leur propre plafond, avec une marge de securite d'environ
 * 2 %, les metriques de fonte n'etant pas identiques d'un moteur a l'autre.
 *
 * Le japonais est le seul cas laisse EN L'ETAT, a son reglage anterieur : il
 * ne s'ecrit pas avec des espaces, sa ligne la plus longue est un seul bloc de
 * treize caracteres, et les trois lignes lui coutent une descente a 47 px, soit
 * 38 % de moins que les autres langues. Il rendrait le hero japonais le plus
 * faible du site, exactement l'inverse de ce que ce reglage cherche. Il reste
 * donc sur quatre lignes, comme avant, et c'est une decision de design ouverte,
 * pas un oubli.
 *
 * Regle a tenir : toucher a `--fs-hero-h1` oblige a rejouer cette mesure sur
 * les 9 langues. Une valeur calibree sur le francais seul casse l'allemand, le
 * polonais et le thai. */
:root:lang(de) { --fs-hero-h1: clamp(44px, 6.4vw, 82px); }
:root:lang(pl) { --fs-hero-h1: clamp(44px, 5.9vw, 76px); }
:root:lang(th) { --fs-hero-h1: clamp(40px, 5.3vw, 68px); }
:root:lang(ja) { --fs-hero-h1: clamp(40px, 6vw, 76px); }

/* Gouttiere reduite sur telephone. Token et non valeur locale : le panneau du
 * selecteur de langue s'y aligne depuis la couche superieure, ou il n'herite
 * rien de `.wrap`. */
@media (max-width: 720px) {
  :root { --gutter: 20px; }
}

/* Mode nuit. Le design system de la maquette est clair uniquement : ce bloc
 * est une extension, imposee par la convention du projet (tout rendu se
 * controle en clair et en nuit). Il ne cree aucune couleur nouvelle, il
 * remappe les surfaces et les textes sur les valeurs sombres deja publiees
 * par `tokens/colors.css` (`--bg-dark`, `--bg-surface-dark`, `--text-on-dark*`,
 * `--border-on-dark*`), et remonte la rampe lavande d'un cran la ou le
 * contraste l'exige. */
@media (prefers-color-scheme: dark) {
  :root {
    --bg-base:         #12111A;
    --bg-surface:      #1E1C2A;
    --bg-surface-tint: #232030;
    --bg-gray:         #191722;
    --bg-chrome:       #1E1C2A;

    --lavender-primary: #9DA8CF;
    --lavender-hover:   #B3BCDD;
    --lavender-ink:     #9DA8CF;
    --lavender-cta:     #9DA8CF;
    --lavender-cta-hover:#B3BCDD;
    --lavender-soft-bg: rgba(157, 168, 207, 0.18);
    --text-on-lavender: #12111A;

    --text-primary:   #F0EFF8;
    --text-soft:      #DDD8EC;
    --text-secondary: #B7B4CC;
    --text-tertiary:  #918EA6;

    --border-subtle:  rgba(240, 239, 248, 0.10);
    --border-default: rgba(240, 239, 248, 0.20);

    --accent-red: #F08A8A;
    --focus-ring: rgba(157, 168, 207, 0.70);

    --slot-hatch: repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05) 10px, rgba(255, 255, 255, 0) 10px, rgba(255, 255, 255, 0) 20px);
    --slot-border: rgba(255, 255, 255, 0.22);
    --toggle-track: rgba(240, 239, 248, 0.08);

    --shadow-card:   0 4px 24px rgba(0, 0, 0, 0.45);
    --shadow-toggle: 0 1px 3px rgba(0, 0, 0, 0.40);
  }
}
/* === TOKENS: FIN === */

/* ── Polices, servies depuis le domaine, jamais depuis un CDN ──
 * Six woff2 variables repris tels quels du design system : DM Sans 300 a 600,
 * Playfair Display 400 a 500 en romain et en italique, deux sous-ensembles
 * chacun (latin et latin-ext, qui couvre les diacritiques polonaises).
 * Licences OFL fournies dans /assets/fonts/. */
@font-face {
  font-family: "DM Sans";
  font-style: normal;
  font-weight: 300 600;
  font-display: swap;
  src: url("/assets/fonts/DMSans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "DM Sans";
  font-style: normal;
  font-weight: 300 600;
  font-display: swap;
  src: url("/assets/fonts/DMSans-latinext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Playfair Display";
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("/assets/fonts/PlayfairDisplay-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Playfair Display";
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("/assets/fonts/PlayfairDisplay-latinext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Playfair Display";
  font-style: italic;
  font-weight: 400 500;
  font-display: swap;
  src: url("/assets/fonts/PlayfairDisplay-Italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Playfair Display";
  font-style: italic;
  font-weight: 400 500;
  font-display: swap;
  src: url("/assets/fonts/PlayfairDisplay-Italic-latinext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ── Base ── */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  /* La barre est collante a toutes les largeurs : une ancre doit s'arreter
   * SOUS elle, avec un peu d'air, sinon le titre vise se retrouve derriere la
   * nav. `scroll-padding-top` sur `html` couvre le saut d'ancre comme
   * `scrollIntoView`. */
  scroll-padding-top: calc(var(--nav-h) + var(--space-3));
}

body {
  margin: 0;
  background: var(--bg-base);
  color: var(--text-primary);
  font-family: var(--sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
  /* Les 9 langues n'ont pas la meme longueur de mot. Sans cette regle, un
   * compose allemand (`Nutzungsbedingungen` dans le h1 des CGU) deborde la
   * colonne a 390px et fait defiler la page horizontalement. */
  overflow-wrap: break-word;
}

h1, h2, h3 {
  margin: 0;
  overflow-wrap: break-word;
  hyphens: auto;
}

p { margin: 0; }

a { color: var(--lavender-ink); }

img, svg { max-width: 100%; }

:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 3px;
  border-radius: var(--radius-xs);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* La gouttiere est un token depuis que le panneau du selecteur de langue doit
 * s'y aligner : il vit dans la couche superieure, hors du flux, et ne peut donc
 * pas heriter le calage de `.wrap`. Une seule valeur, deux usages. */
.wrap {
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

section { padding: var(--section-py) 0; position: relative; }
@media (max-width: 720px) {
  section { padding: var(--section-py-mobile) 0; }
}

.sec-base { background: var(--bg-base); }
.sec-surface { background: var(--bg-surface); }
.sec-gray { background: var(--bg-gray); }

.rule-h {
  width: 60px;
  height: 1px;
  background: var(--border-default);
  margin: 40px 0 48px;
}

.skip-link {
  position: absolute;
  left: var(--space-4);
  top: calc(-1 * var(--space-8));
  z-index: 60;
  background: var(--bg-surface);
  color: var(--text-primary);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-card);
}
.skip-link:focus { top: var(--space-3); }

/* ── NS.Button ── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 14px 22px;
  font-family: var(--sans);
  font-size: var(--fs-ui);
  font-weight: var(--fw-medium);
  border-radius: var(--radius-sm);
  border: none;
  background: var(--lavender-cta);
  color: var(--text-on-lavender);
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--t-fast), transform var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.btn::after { content: "\2192"; display: inline-block; transition: transform var(--t-fast); }
.btn:hover { background: var(--lavender-cta-hover); transform: translateY(-1px); }
.btn:hover::after { transform: translateX(3px); }
.btn:active { transform: translateY(0); }

.btn--secondary {
  padding: 13px 22px;
  background: none;
  color: var(--lavender-ink);
  border: 1px solid var(--lavender-primary);
}
.btn--secondary:hover { background: var(--lavender-cta); color: var(--text-on-lavender); }

.btn--on-dark.btn--secondary {
  color: var(--text-on-dark);
  border-color: var(--border-on-dark-strong);
}
.btn--on-dark.btn--secondary:hover { background: var(--bg-surface-dark); border-color: var(--text-on-dark-secondary); }

.btn--full { width: 100%; }

/* ── NS.LinkItalic ── */
.link-italic {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-body);
  color: var(--text-primary);
  text-decoration: none;
  padding-bottom: 3px;
  display: inline-block;
  border-bottom: 1px solid var(--lavender-primary);
  transition: border-color var(--t-base);
}
.link-italic:hover { border-bottom-color: var(--text-primary); }

/* ── Kickers mono ── */
.kicker {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--text-tertiary);
  display: inline-block;
}
.kicker__mark { color: var(--lavender-ink); margin-right: var(--space-2); }

/* ── NS.SignalHead ── */
.head { margin-bottom: var(--space-6); max-width: 920px; }
.signal-a { display: flex; align-items: center; gap: 20px; }
.signal-a .kicker { white-space: nowrap; }
.signal-a__trait { flex: 1; height: 1px; background: var(--border-default); }
.signal-head__title {
  font-family: var(--sans);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-sec-h2);
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-h2);
  margin: 20px 0 0;
  max-width: 22ch;
  color: var(--text-primary);
}
.head--centered .signal-a { justify-content: center; }
.head--centered .signal-head__title { text-align: center; margin-left: auto; margin-right: auto; }

.signal-b__kicker {
  border-left: 2px solid var(--text-primary);
  padding-left: 14px;
  display: inline-block;
  font-family: var(--serif);
  font-style: italic;
  font-size: 18px;
  color: var(--text-secondary);
}

.sec-lead {
  font-weight: var(--fw-medium);
  font-style: italic;
  font-size: var(--fs-lead);
  line-height: 1.5;
  color: var(--text-secondary);
  margin: 20px 0 0;
  max-width: 48ch;
  padding-left: var(--space-4);
  border-left: 1px solid var(--border-default);
}

/* ── NS.Slot : emplacements visuels ──
 * Boite hachuree en pointilles, deliberement pas une image. Le libelle est
 * visible, en mono : le visiteur voit une zone reservee et non une capture. */
.slot {
  background-color: var(--bg-surface-tint);
  background-image: var(--slot-hatch);
  border: 1px dashed var(--slot-border);
  border-radius: var(--radius-sm);
  padding: 28px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  text-align: center;
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
.slot--photo { aspect-ratio: 4 / 3; padding: 18px; }
.slot--wide { aspect-ratio: 16 / 10; }

/* ── Nav ── */
.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bg-chrome);
  color: var(--text-on-dark);
  /* Pas de filet bas. Un blanc a 10 % pose sur `--bg-chrome` ne monte qu'a
   * 1,3:1 avec elle : le trait n'etait pas discret, il etait invisible, et il
   * ajoutait un pixel a une hauteur de barre censee valoir exactement
   * `--nav-h`. La separation est deja portee par l'ecart considerable entre la
   * barre sombre et la page claire. */
  border-bottom: 0;
}
/* `.nav-inner` porte AUSSI la classe `.wrap`, qui pose les gouttieres de 40 px
 * partagees par toute la page. Ne jamais y ecrire `padding` en raccourci : le
 * raccourci remet les gouttieres a zero, le logotype vient alors se coller au
 * bord du viewport des que la fenetre passe sous 1200 px, et la barre parait
 * coupee alors que le reste de la page garde sa marge. `padding-block` ne
 * touche qu'aux marges verticales. */
.nav-inner {
  min-height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  /* Rien ne passe a la ligne : sous le point de rupture, les liens de section
   * partent dans le panneau du menu, ce qui laisse toujours la place. Une
   * barre qui se dedouble en hauteur decale tout le contenu ancre. */
  flex-wrap: nowrap;
  padding-block: var(--space-2);
}
.nav-logo { display: inline-flex; align-items: center; gap: 12px; flex-shrink: 0; }
.nav-logo__mark { display: block; width: 36px; height: 36px; }
/* Le logotype est ce qui grandit, pas le monogramme : c'est lui qui porte le
 * nom. Il passe de 21 a 38 px de haut, soit environ 118 px de large. */
.nav-logo__word { display: block; height: 38px; width: auto; }
.nav-links { display: flex; gap: var(--space-6); align-items: center; list-style: none; margin: 0; padding: 0; }
.nav-links a { font-size: var(--fs-ui); color: var(--text-on-dark-secondary-solid); text-decoration: none; transition: color var(--t-fast); }
.nav-links a:hover { color: var(--text-on-dark); }
/* 22 px et non 12 : a 12 px, le selecteur de langue et l'appel a l'action se
 * lisaient comme un seul objet. La POSITION du bouton ne bouge pas, son bord
 * droit reste sur la marge du conteneur, dans l'aplomb de la carte du hero. */
.nav-right { display: inline-flex; gap: 22px; align-items: center; flex-wrap: nowrap; flex-shrink: 0; }
/* L'appel a l'action de la barre est SUBORDONNE a celui du hero, qui reste le
 * primaire. Ils avaient la meme couleur, la meme graisse, le meme rayon et le
 * meme libelle a six pixels de haut pres : deux primaires qui s'annulent. Ici
 * la barre a un libelle plus court, une graisse de texte plus petite et, sur
 * pointeur fin, une hauteur reduite.
 * La hauteur de BASE reste 44 px, la cible tactile recommandee : c'est la
 * valeur sure, celle que voient aussi les moteurs qui ne repondent pas aux
 * requetes de media de pointeur. Seul un pointeur fin la reduit. */
.nav-cta { white-space: nowrap; min-height: 44px; padding: 0 18px; font-size: 13px; }

/* Etat actif du lien de section. Sans JavaScript on ne peut pas suivre le
 * defilement : ce qui est mis en avant est la section VISEE par l'ancre, pas
 * celle que le visiteur est en train de lire. C'est le maximum atteignable en
 * CSS seul, et c'est deja le cas juste apres un clic, qui est le moment ou le
 * reperage sert. Sur une page legale aucune de ces ancres n'existe, donc
 * aucune regle ne s'applique. */
body:has(#transformation:target) .nav-links a[href$="#transformation"],
body:has(#essayer:target) .nav-links a[href$="#essayer"],
body:has(#faq:target) .nav-links a[href$="#faq"] {
  color: var(--text-on-dark);
}

/* ---- Les deux menus deroulants de la barre ----
 * Toujours aucune ligne de JavaScript, mais deux mecanismes distincts, et
 * c'est deliberé.
 *
 * Le menu de section reste un <details> : il n'a que trois liens, il occupe
 * toute la barre sur telephone, et le refermer d'un second clic sur son propre
 * declencheur est un geste naturel.
 *
 * Le selecteur de langue est un POPOVER natif. `popovertarget` est un attribut
 * HTML, pas du script, et c'est le seul mecanisme declaratif qui donne la
 * fermeture au CLIC EXTERIEUR et a la touche ECHAP. Avec un <details>, le
 * panneau des neuf langues restait ouvert en travers de la page tant qu'on
 * n'avait pas retrouve son declencheur. */
.lang-switch,
.nav-menu { position: relative; flex-shrink: 0; }
.lang-switch__trigger,
.nav-menu > summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  /* Meme cible tactile que les liens des panneaux : les declencheurs ne sont
   * pas moins cliquables que ce qu'ils ouvrent. */
  min-height: 44px;
  list-style: none;
  cursor: pointer;
  /* Remise a plat du <button>, qui n'herite ni la fonte ni le fond. */
  font-family: inherit;
  font-size: inherit;
  background: none;
  padding: 0;
  border: 1px solid var(--border-on-dark-strong);
  border-radius: var(--radius-pill);
  color: var(--text-on-dark-secondary);
  transition: color var(--t-fast), background var(--t-fast), border-color var(--t-fast);
}
.nav-menu > summary::-webkit-details-marker { display: none; }
/* L'etat se signale par la BORDURE, pas par un aplat. L'aplat precedent
 * utilisait `--border-on-dark`, un token de bordure : sur 79 x 44 px il donnait
 * 1,3:1 avec la barre, trop faible pour signaler quoi que ce soit et trop
 * neutre pour paraitre voulu. Le declencheur a deja un contour en pilule, c'est
 * lui qui prend la lavande de marque. */
.lang-switch__trigger:hover,
.nav-menu > summary:hover,
.nav-menu[open] > summary,
.lang-switch:has(.lang-switch__menu:popover-open) .lang-switch__trigger {
  color: var(--text-on-dark);
  background: none;
  border-color: var(--lavender-primary);
}
.lang-switch:has(.lang-switch__menu:popover-open) .chevron { transform: rotate(180deg); }
.chevron { width: 11px; height: 11px; opacity: 0.7; transition: transform var(--t-fast); flex-shrink: 0; }

/* Les deux panneaux flottent sous la barre. Ils sortent du flux, donc ils
 * n'ajoutent jamais un pixel a la hauteur de la nav. */
.lang-switch__menu,
.nav-menu__panel {
  z-index: 60;
  margin: 0;
  padding: 6px;
  list-style: none;
  min-width: 200px;
  max-height: min(70vh, 420px);
  overflow-y: auto;
  background: var(--bg-surface-dark);
  border: 1px solid var(--border-on-dark-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-menu);
}
.nav-menu__panel {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
}
/* Un popover vit dans la couche superieure : son bloc conteneur est le bloc
 * conteneur initial, pas son parent positionne. Il ne peut donc pas etre
 * accroche au declencheur sans le positionnement par ancre, que seul un moteur
 * sur trois implemente aujourd'hui. Il est donc cale sur la GOUTTIERE DROITE du
 * conteneur, ou se termine aussi l'appel a l'action de la barre : le panneau
 * tombe dans le meme aplomb sur les trois moteurs, plutot que d'etre juste sous
 * le globe sur l'un et centre au milieu de l'ecran sur les deux autres.
 *
 * `100%` et non `100vw` : sur un element `fixed`, le pourcentage se resout sur
 * le bloc conteneur initial, qui EXCLUT la barre de defilement, alors que `vw`
 * l'inclut et decalerait le panneau d'une quinzaine de pixels. */
.lang-switch__menu {
  position: fixed;
  inset: auto;
  top: calc(var(--nav-h) + var(--space-3));
  right: max(var(--gutter), calc((100% - var(--container)) / 2 + var(--gutter)));
}
/* Sans popover natif, le declencheur n'ouvrirait rien et la liste s'afficherait
 * en clair dans la barre. Le selecteur de la barre disparait alors, et celui du
 * pied de page, toujours deplie, prend le relais : le changement de langue
 * reste possible sur tout moteur. */
@supports not selector(:popover-open) {
  .lang-switch { display: none; }
}
.lang-switch__menu a,
.nav-menu__panel a {
  /* 44 px de haut : la cible tactile recommandee. L'ancien selecteur posait
   * neuf pastilles de 30 x 25 px cote a cote. */
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0 12px;
  border-radius: var(--radius-sm);
  color: var(--text-on-dark-secondary);
  text-decoration: none;
  font-size: var(--fs-ui);
  transition: color var(--t-fast), background var(--t-fast);
}
/* Dans un panneau deroulant, l'aplat reste justifie : il materialise la zone
 * cliquable, qui est plus large que le texte. Mais il est TEINTE, et il ne
 * vient plus d'un token de bordure. */
.lang-switch__menu a:hover,
.nav-menu__panel a:hover {
  color: var(--text-on-dark);
  background: var(--surface-hover-on-dark);
}

/* ---- Selecteur de langue ----
 * La maquette le pilote en JavaScript. La spec impose 9 liens REELS, presents
 * dans le document sans JavaScript : ils le restent, panneau ouvert comme
 * ferme, seul leur rendu est plie. Le globe de la maquette est conserve comme
 * reperage visuel. */
.lang-switch__trigger { padding: 0 12px; }
.lang-switch__globe { width: 16px; height: 16px; flex-shrink: 0; }
/* Drapeaux : strictement decoratifs, `alt` vide, servis depuis le domaine.
 * Le filet interieur evite que les drapeaux a large plage blanche (Japon,
 * Pologne, Etats-Unis) ne bavent sur le panneau. `outline` et non `box-shadow`
 * interieur : sur un element remplace, une ombre interieure passe DERRIERE
 * l'image et ne se voit pas. */
.lang-flag {
  display: block;
  width: 18px;
  height: 12px;
  flex-shrink: 0;
  border-radius: 1px;
  outline: 1px solid var(--border-on-dark-strong);
  outline-offset: -1px;
}
.lang-switch__code {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  font-weight: var(--fw-semibold);
}
.lang-switch__menu { min-width: 210px; }
.lang-switch__menu a { gap: 10px; }
.lang-switch__menu .lang-code {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-micro);
  color: var(--text-on-dark-tertiary);
  margin-inline-start: auto;
  padding-inline-start: var(--space-3);
}
.lang-switch__menu a[aria-current="page"] {
  color: var(--text-on-dark);
  background: var(--border-on-dark-strong);
  font-weight: var(--fw-semibold);
}
.lang-switch__menu a[aria-current="page"] .lang-code { color: var(--text-on-dark-secondary); }

/* ---- Menu de section, sous le point de rupture ---- */
.nav-menu { display: none; }
.nav-menu > summary { padding: 0; min-width: 44px; }
.nav-menu > summary svg { width: 20px; height: 20px; display: block; }

/* Point de rupture de la barre : 1024 px.
 * Il est volontairement large. La serie de liens la plus longue est la
 * francaise ("Comment ca marche / Comment essayer / FAQ", 41 caracteres) et,
 * additionnee du logotype, du selecteur et de l'appel a l'action, elle occupe
 * un peu plus de 900 px gouttieres comprises. Descendre le point de rupture
 * ramenerait la zone morte de 770 a 1200 px signalee en recette, ou la barre
 * se dedoublait en hauteur. */
/* Centrage REEL des liens de section. En `space-between`, trois groupes de
 * largeurs inegales (logotype 107 px, liens 345 px, bloc droit 280 px) laissent
 * des espaces egaux mais posent le groupe du milieu a x=575 alors que le
 * conteneur est centre a x=662. Une grille `1fr auto 1fr` centre la colonne du
 * milieu sur le conteneur, quelles que soient les largeurs des deux autres.
 *
 * Encadre au-dessus du point de rupture : en dessous, `.nav-links` est masque
 * et il ne reste que deux groupes, ou la grille creerait une colonne vide.
 *
 * `.nav-logo` est explicitement cale a gauche : un item de grille s'etire par
 * defaut, et ce lien-la prendrait alors toute la colonne, donnant une zone
 * cliquable invisible sur la gauche de la barre. */
@media (min-width: 1025px) {
  .nav-inner {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
  }
  .nav-logo { justify-self: start; }
  .nav-links { justify-self: center; }
  .nav-right { justify-self: end; }
}

/* Cible tactile. Les hauteurs reduites de l'appel a l'action et du declencheur
 * de langue les font passer sous les 44 px recommandes : elles ne s'appliquent
 * donc qu'a un pointeur FIN, ou la precision du curseur rend la contrainte sans
 * objet et ou le minimum applicable est celui de WCAG 2.5.8, 24 px. Sur
 * pointeur grossier, et sur tout moteur qui ne repond pas a cette requete, les
 * 44 px de base restent. */
@media (pointer: fine) {
  .nav-cta { min-height: 38px; height: 38px; padding: 0 16px; }
  /* Le declencheur de langue descend d'un cran de plus que l'appel a l'action :
   * il doit rester le second de la paire. Le declencheur du menu de section,
   * lui, garde ses 44 px, il n'apparait que sous le point de rupture. */
  .lang-switch__trigger { min-height: 34px; }
}

@media (max-width: 1024px) {
  .nav-links { display: none; }
  .nav-menu { display: block; }
  /* La barre reste collante sur mobile : sans cela, une fois le hero passe, il
   * ne reste ni menu ni appel a l'action.
   * Le monogramme redescend en carre : il porte desormais `width` ET `height`,
   * et n'en surcharger qu'une l'ecraserait. */
  .nav-logo__mark { width: 32px; height: 32px; }
  .nav-logo__word { height: 32px; }
}

/* Tres petits ecrans. Ce qui cede est le LOGOTYPE, pas l'appel a l'action :
 * le monogramme suffit a identifier le site, l'intitule du bouton est ce qui
 * fait demarrer un essai. A 320 px, la largeur la plus etroite encore
 * rencontree, la barre tient alors sur une ligne, y compris avec l'intitule
 * polonais qui est le plus long des neuf. */
@media (max-width: 430px) {
  .nav-inner { gap: var(--space-3); }
  .nav-right { gap: var(--space-2); }
  .nav-logo__word { display: none; }
  .nav-cta { padding: 0 14px; font-size: var(--fs-kicker); }
  .nav-cta::after { display: none; }
  /* Ce qui cede est le GLOBE, pas le code de langue. L'etat precedent gardait
   * le globe et masquait le code : le declencheur ne disait plus dans quelle
   * langue on etait, qui est justement la seule information qu'il porte. Le
   * globe est un ornement, le code est la donnee. */
  .lang-switch__globe, .chevron { display: none; }
  .lang-switch__trigger { padding: 0; min-width: 44px; }
}

/* ── Hero ── */
.hero { padding: 72px 0 88px; }
@media (max-width: 720px) {
  .hero { padding: 40px 0 56px; }
}
/* Cadrage plein ecran. Mesure du 2026-08-09 a 1324 x 955 : le hero s'arretait a
 * 740 px et laissait voir 215 px de #transformation au chargement, kicker
 * compris et titre coupe au milieu de sa deuxieme ligne. Il occupe desormais la
 * fenetre moins la barre et moins une bande d'amorce, qui signale qu'il reste
 * du contenu sans qu'aucun texte de la section suivante ne passe la ligne de
 * flottaison. C'est l'affordance de defilement, et la seule : pas de fleche ni
 * de chevron rebondissant, le design system reste sobre.
 *
 * `--peek` doit rester SOUS les 96 px de `padding-top` de #transformation
 * (`section { padding: var(--section-py) 0 }`). Au-dela, le kicker de la
 * section suivante repasserait au-dessus de la ligne de flottaison et le
 * cadrage serait a refaire.
 *
 * `svh` et non `vh`, que la barre d'URL mobile fausse, ni `dvh`, qui
 * redimensionnerait le hero pendant le defilement.
 *
 * `grid` et non `flex` : en flex, `.hero-main` deviendrait un item dimensionne
 * sur son contenu et perdrait le `max-width: 1200px` + `margin: 0 auto` que lui
 * donne `.wrap`, ce qui ecraserait la grille 1.35fr / 1fr. En grid, l'item
 * s'etire sur toute la largeur et le centrage par marges automatiques
 * fonctionne comme en flux normal.
 *
 * Encadre : sur ecran court le hero remplit deja la fenetre, et sous 900 px
 * `.hero-main` passe en une colonne, ou une hauteur minimale enfermerait la
 * carte de progression hors champ.
 *
 * Aucune animation n'est posee ici : #hero porte le LCP. */
@media (min-width: 900px) and (min-height: 760px) {
  .hero {
    --peek: 24px;
    min-block-size: calc(100svh - var(--nav-h) - var(--peek));
    display: grid;
    /* La colonne implicite doit valoir toute la largeur. Sans elle, elle se
     * dimensionne sur le contenu de `.hero-main`, qui tombait a 1162 px au lieu
     * des 1200 px du conteneur : le hero etait alors 38 px plus etroit que
     * toutes les autres sections, ses gouttieres passaient de 40 a 59 px, et la
     * carte de progression n'atteignait plus le bord droit, donc plus l'aplomb
     * de l'appel a l'action de la barre. `minmax(0, 1fr)` et non `1fr` : le
     * minimum automatique d'une piste de grille est `auto`, ce qui laisserait un
     * contenu insecable elargir la colonne au-dela de la fenetre. */
    grid-template-columns: minmax(0, 1fr);
    align-content: center;
  }
  /* `.wrap` porte `margin: 0 auto`, et en grille des marges automatiques
   * ABSORBENT l'espace libre : elles desactivent l'etirement de l'item, qui
   * retombe alors sur sa largeur de contenu. Mesure : 1162 px au lieu de 1200,
   * gouttieres de 59 px au lieu de 40, et la carte de progression n'atteignait
   * plus l'aplomb de l'appel a l'action de la barre. `width: 100%` redonne a
   * l'item la largeur de la colonne, que `max-width` replafonne a 1200 et que
   * les marges automatiques recentrent, exactement comme en flux normal. */
  .hero > .hero-main { width: 100%; }
}
.hero-main {
  display: grid;
  grid-template-columns: 1.35fr 1fr;
  gap: 48px;
  align-items: center;
}
@media (max-width: 900px) {
  .hero-main { grid-template-columns: 1fr; gap: 40px; }
}
.hero-h1 {
  font-family: var(--serif);
  font-style: italic;
  font-weight: var(--fw-regular);
  font-size: var(--fs-hero-h1);
  line-height: var(--lh-tight);
  /* Approche resserree d'un cran par rapport a `--ls-hero`, que ce titre
   * partage avec les h3 des sections `step` et `block` : a 92 px, l'approche
   * calibree pour du 28 a 48 px laisse le titre respirer plus que de raison.
   * La surcharge est LOCALE a dessein, resserrer le token deplacerait aussi
   * des titres qui ne sont pas concernes. */
  letter-spacing: -0.02em;
  /* Encre dense, et non `--text-soft`. Le lien italique « Voir comment ca
   * marche » juste en dessous est en `--text-primary` : tant que le titre etait
   * plus clair que lui, la hierarchie visuelle etait inversee. */
  color: var(--text-primary);
}
/* Titre bicolore : les deux constats en encre dense, la promesse finale en
 * lavande de marque. `--lavender-ink` est la lavande de TEXTE, calibree pour
 * porter du texte sur les fonds clairs du site ; `--lavender-primary`, calibree
 * pour des aplats, tomberait sous le seuil AA. Aucune couleur nouvelle, aucune
 * valeur de `:root` modifiee. */
.hero-h1 .accent { color: var(--lavender-ink); }
.hero-quote {
  font-weight: var(--fw-medium);
  font-style: italic;
  font-size: 21px;
  line-height: 1.5;
  color: var(--text-soft);
  margin: 32px 0 0;
  max-width: 38ch;
  padding-left: var(--space-4);
  border-left: 1px solid var(--border-default);
}
.hero-cta { display: flex; gap: 18px; align-items: center; margin-top: 44px; flex-wrap: wrap; }
.hero-visual { display: flex; justify-content: center; }
.hero-channels { margin-top: 44px; display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.hero-channels__row { display: flex; align-items: center; gap: 11px; }
.hero-channels__row img { display: block; width: 34px; height: 34px; }
.hero-channels__name {
  font-family: var(--sans);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  letter-spacing: -0.01em;
}

/* La carte du hero est plus grande que celle de l'apex, et vient a fleur du
 * bord droit du conteneur, dans l'axe de l'appel a l'action de la barre.
 *
 * `min-width: 901px` et non 900 : la regle qui fait passer `.hero-main` en une
 * seule colonne est `max-width: 900px`, et les deux se declencheraient
 * ensemble a exactement 900 px. La carte serait alors plaquee a droite d'une
 * colonne unique, au lieu de rester centree sous le texte. Meme convention que
 * `.apex-stage`, qui bascule a 1100 et dont la variante de bureau part a 1101.
 *
 * Toutes les regles de carte sont prefixees par `#hero` : la carte de
 * progression apparait DEUX fois dans la page, et celle de l'apex ne doit pas
 * bouger d'un pixel. */
@media (min-width: 901px) {
  .hero-visual { justify-content: flex-end; }
  #hero .progression-card {
    max-width: 440px;
    padding: 30px 28px;
    gap: 26px;
    border-radius: 24px;
  }
  #hero .pc-chart { height: 92px; }
  #hero .pc-stat__value { font-size: 28px; }
  #hero .pc-label { font-size: 10.5px; }
}

/* ── NS.ProgressionCard ──
 * Le degrade signature est confine dans la carte, jamais pleine page.
 * UNE seule projection, celle du plan, ancree sur la derniere pesee : ni
 * deuxieme date, ni "a ce rythme" oppose a "theorique". La carte du design
 * system porte encore ces deux ETA, supprimees du produit en v0.92.0 : c'est
 * son unique ecart assume avec la maquette. */
.progression-card {
  background: var(--gradient-signature);
  border-radius: var(--radius-xl);
  padding: 24px 22px;
  max-width: 360px;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 20px;
  box-shadow: var(--shadow-card);
  margin: 0;
  color: var(--text-on-dark);
}
.pc-label {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--on-gradient-label);
}
.pc-stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.pc-stat__cap { display: block; margin-bottom: 6px; }
.pc-stat__value {
  display: block;
  font-family: var(--serif);
  font-style: italic;
  font-size: 22px;
  line-height: 1;
  color: var(--text-on-dark);
}
.pc-stat__sub {
  display: block;
  font-family: var(--sans);
  font-size: var(--fs-kicker);
  color: var(--on-gradient-sub);
  margin-top: 3px;
}
.pc-chart { display: block; width: 100%; height: 64px; overflow: visible; }
.pc-real { fill: none; stroke: var(--on-gradient-line); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.pc-area { fill: var(--on-gradient-fill); stroke: none; }
.pc-proj { fill: none; stroke: var(--on-gradient-line); stroke-width: 2; stroke-dasharray: 4 4; stroke-linecap: round; }
.pc-now { stroke: var(--on-gradient-rule); stroke-width: 1; }
.pc-goal { fill: var(--text-on-dark); }
.pc-foot {
  border-top: 1px solid var(--on-gradient-rule);
  padding-top: 16px;
}
.pc-foot .pc-stat__value { font-size: 18px; }
.pc-note {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: var(--ls-micro);
  color: var(--on-gradient-label);
}

/* ── Steps ── */
.step {
  display: grid;
  grid-template-columns: 1fr 1.6fr;
  gap: 56px;
  padding: 56px 0;
  border-top: 1px dashed var(--border-default);
  align-items: center;
}
.step:first-of-type { border-top: none; padding-top: var(--space-5); }
.step--right { grid-template-columns: 1.6fr 1fr; }
.step--right .step-visual { order: 1; }
.step--right .step-text { order: 2; }
@media (max-width: 900px) {
  .step, .step--right { grid-template-columns: 1fr; gap: 28px; }
  .step--right .step-visual { order: -1; }
}
.step-num { margin-bottom: var(--space-4); }
.step-text h3 {
  font-weight: var(--fw-semibold);
  font-size: var(--fs-step-h3);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-hero);
  margin: 0 0 14px;
  max-width: 16ch;
}
.step-text p { font-size: var(--fs-body-sm); line-height: var(--lh-relaxed); color: var(--text-secondary); max-width: 42ch; }
.step-photos { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4); }
@media (max-width: 600px) {
  .step-photos { grid-template-columns: 1fr; }
}

/* ── Apex ── */
.apex { padding: 110px 0 40px; border-top: 1px dashed var(--border-default); margin-top: var(--space-7); }
@media (max-width: 720px) {
  .apex { padding: 56px 0 24px; }
}
.apex-intro { text-align: center; margin-bottom: 56px; }
.apex-intro .step-num { color: var(--lavender-ink); }
.apex-h3 {
  font-weight: var(--fw-semibold);
  font-size: var(--fs-apex-h3);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-h2);
  margin: 0 auto;
  max-width: 18ch;
}
.apex-stage {
  display: grid;
  grid-template-columns: 220px 1fr 220px;
  grid-template-rows: auto auto;
  column-gap: var(--space-6);
  row-gap: 36px;
  align-items: center;
  max-width: 1100px;
  margin: 0 auto;
}
.apex-visual { grid-column: 2; grid-row: 1; display: flex; justify-content: center; }
.apex-annot { display: flex; align-items: flex-start; gap: 10px; }
.apex-annot__mark { font-family: var(--serif); font-size: 22px; color: var(--accent-red); line-height: 1; flex-shrink: 0; }
.apex-annot p {
  font-family: var(--serif);
  font-style: italic;
  font-size: 15px;
  line-height: 1.45;
  color: var(--text-primary);
  max-width: 22ch;
}
.apex-annot--left { grid-column: 1; grid-row: 1; justify-self: end; flex-direction: row-reverse; text-align: right; }
.apex-annot--right { grid-column: 3; grid-row: 1; justify-self: start; }
.apex-annot--center { grid-column: 2; grid-row: 2; justify-self: center; text-align: center; flex-direction: column; align-items: center; gap: var(--space-2); }
@media (max-width: 1100px) {
  .apex-stage { grid-template-columns: 1fr; grid-template-rows: auto; row-gap: var(--space-5); }
  .apex-visual { grid-column: 1; grid-row: auto; order: -1; }
  .apex-annot--left, .apex-annot--right, .apex-annot--center {
    grid-column: 1;
    grid-row: auto;
    justify-self: stretch;
    flex-direction: row;
    align-items: flex-start;
    text-align: left;
  }
}
.apex-conclusion {
  max-width: 540px;
  margin: var(--space-8) auto 0;
  padding-left: var(--space-4);
  border-left: 1px solid var(--border-default);
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-lead);
  line-height: 1.5;
  color: var(--text-secondary);
}
.apex-cta { text-align: center; margin-top: 44px; }

/* ── Blocs "inclus" ── */
.block {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-9);
  align-items: center;
  padding: 88px 0;
  border-top: 1px dashed var(--border-subtle);
}
.block:first-of-type { border-top: none; padding-top: var(--space-7); }
.block--right .block-visual { order: 2; }
.block--right .block-text { order: 1; }
@media (max-width: 900px) {
  .block { grid-template-columns: 1fr; gap: 28px; padding: 52px 0; }
  .block--right .block-visual { order: -1; }
}
.block-text h3 {
  font-family: var(--serif);
  font-style: italic;
  font-weight: var(--fw-regular);
  font-size: var(--fs-block-h3);
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-hero);
  color: var(--text-primary);
  margin: 0 0 18px;
}
.block-text p { font-size: var(--fs-body); line-height: var(--lh-body); color: var(--text-secondary); max-width: 38ch; }

/* ── Prix ── */
.pricing-wrap {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: stretch;
  gap: var(--space-5);
  padding-top: var(--space-4);
}
.pricing-card {
  background: var(--bg-surface-tint);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  width: 100%;
  max-width: 460px;
  padding: 40px;
}
@media (max-width: 480px) {
  .pricing-card { padding: 28px; }
}
.pricing-label { margin-bottom: 20px; }

/* NS.PricingToggle : deux boutons radio stylés, aucun JavaScript. La maquette
 * pilote le sien en JavaScript (`.ma-toggle-btn.is-active`) et affiche un
 * badge d'economie : le badge est un montant derive, interdit dans ce depot,
 * et le JavaScript est exclu par la spec. L'apparence en pilule est conservee.
 * Le conteneur [data-pricing-switch] est destine a etre retire tel quel par le
 * Worker pour la zone bresilienne, qui n'a pas de plan mensuel (paiement
 * unique Pix) : l'etat par defaut des regles ci-dessous est "annuel affiche,
 * mensuel masque", donc la page reste juste une fois le selecteur retire. */
.pricing-switch {
  display: inline-flex;
  gap: 2px;
  padding: var(--space-1);
  margin: 0;
  border: 0;
  min-width: 0;
  background: var(--toggle-track);
  border-radius: var(--radius-pill);
}
.pricing-switch label {
  display: inline-flex;
  align-items: center;
  padding: var(--space-2) 18px;
  border-radius: var(--radius-pill);
  font-family: var(--sans);
  font-size: 13px;
  font-weight: var(--fw-medium);
  letter-spacing: 0.01em;
  color: var(--text-secondary);
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast), box-shadow var(--t-fast);
}
.pricing-switch label:hover { color: var(--text-primary); }
.pricing-card:has(#plan-year:checked) label[for="plan-year"],
.pricing-card:has(#plan-month:checked) label[for="plan-month"] {
  background: var(--bg-surface);
  color: var(--text-primary);
  box-shadow: var(--shadow-toggle);
}
.pricing-card:has(#plan-year:focus-visible) label[for="plan-year"],
.pricing-card:has(#plan-month:focus-visible) label[for="plan-month"] {
  outline: 2px solid var(--focus-ring);
  outline-offset: 3px;
}

.pricing-display { margin: var(--space-5) 0 var(--space-2); }
.pricing-price {
  font-weight: var(--fw-semibold);
  font-size: clamp(36px, 5vw, 56px);
  line-height: 1;
  letter-spacing: var(--ls-tight);
  color: var(--text-primary);
}
.pricing-price .unit {
  font-size: 0.5em;
  font-weight: var(--fw-regular);
  color: var(--text-secondary);
  margin-left: var(--space-1);
  letter-spacing: 0;
}
.pricing-amount--month { display: none; }
.pricing-card:has(#plan-month:checked) .pricing-amount--month { display: inline; }
.pricing-card:has(#plan-month:checked) .pricing-amount--year { display: none; }

.pricing-meta { font-size: 13px; color: var(--text-secondary); margin-top: var(--space-2); }
.pricing-divider { height: 1px; background: var(--border-subtle); margin: 28px 0; }
.pricing-features { list-style: none; padding: 0; margin: 0 0 28px; display: grid; gap: var(--space-3); }
.pricing-features li {
  font-size: var(--fs-ui);
  color: var(--text-primary);
  line-height: 1.45;
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
}
.pricing-features li::before { content: "\2713"; color: var(--lavender-ink); font-weight: var(--fw-semibold); flex-shrink: 0; }
.pricing-fineprint {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  color: var(--text-tertiary);
  text-align: center;
  margin-top: var(--space-4);
  letter-spacing: var(--ls-micro);
  line-height: var(--lh-relaxed);
}

/* Panneau de demarrage WhatsApp. La maquette y place un QR code servi par
 * api.qrserver.com : appel tiers a l'execution, interdit. Le QR sera genere en
 * SVG au build a la tache 8, quand le vrai numero remplacera le placeholder de
 * lib/constants.mjs. En attendant, ce panneau n'annonce aucun QR code. */
.start-panel {
  flex: 0 0 auto;
  width: 280px;
  max-width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: 36px 28px;
}
.start-panel p { font-size: var(--fs-ui); line-height: 1.5; color: var(--text-secondary); margin: 0 0 var(--space-5); }

/* ── FAQ ── */
.faq-list { border-top: 1px solid var(--border-subtle); }
.faq-list details { border-bottom: 1px solid var(--border-subtle); }
.faq-list summary {
  cursor: pointer;
  padding: var(--space-5) 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-5);
  font-family: var(--sans);
  font-size: var(--fs-body);
  color: var(--text-primary);
  list-style: none;
  transition: color var(--t-fast);
}
.faq-list summary::-webkit-details-marker { display: none; }
.faq-list summary:hover { color: var(--lavender-ink); }
.faq-list summary::after {
  content: "+";
  font-family: var(--sans);
  font-weight: var(--fw-light);
  font-size: 22px;
  color: var(--text-tertiary);
  transition: transform var(--t-base);
  flex-shrink: 0;
  line-height: 1;
}
.faq-list details[open] summary::after { transform: rotate(45deg); color: var(--lavender-ink); }
.faq-list details p {
  font-size: var(--fs-body-sm);
  line-height: var(--lh-relaxed);
  color: var(--text-secondary);
  margin: 0 0 var(--space-5);
  max-width: 65ch;
  padding-right: 40px;
}
@media (max-width: 600px) {
  .faq-list details p { padding-right: 0; }
}

/* ── Pied de page ── */
.foot { background: var(--bg-chrome); color: var(--text-on-dark); padding: var(--space-8) 0 var(--space-7); }
.foot-grid {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-7);
  align-items: start;
  padding-bottom: var(--space-7);
  border-bottom: 1px solid var(--border-on-dark);
}
@media (max-width: 720px) {
  .foot-grid { grid-template-columns: 1fr; gap: var(--space-6); }
}
.foot-brand { display: flex; align-items: center; gap: 10px; }
.foot-brand__mark { display: block; height: 40px; width: auto; }
.foot-brand__word { display: block; height: 26px; width: auto; }
.foot-tagline {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-body);
  color: var(--text-on-dark-secondary);
  margin: var(--space-4) 0 0;
  max-width: 36ch;
  line-height: 1.5;
  padding-left: 14px;
  border-left: 1px solid var(--border-on-dark-strong);
}
.foot-cols {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-7);
  padding: var(--space-7) 0;
  border-bottom: 1px solid var(--border-on-dark);
}
/* Quatre colonnes depuis l'ajout du selecteur de langue. Deux paliers plutot
 * qu'un : a quatre colonnes sous 1100 px, les intitules legaux les plus longs
 * (allemand, polonais) passent sur trois lignes chacun. */
@media (max-width: 1100px) {
  .foot-cols { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 720px) {
  .foot-cols { grid-template-columns: 1fr; gap: var(--space-6); }
}
/* Neuf langues sur une colonne rendraient le pied de page deux fois plus haut
 * que le reste. Deux sous-colonnes, dans l'ordre des locales. */
.foot-langs { grid-template-columns: repeat(2, 1fr); column-gap: var(--space-4); }
.foot-langs a { display: flex; align-items: center; gap: var(--space-2); min-height: 32px; }
.foot-langs .lang-code { display: none; }
.foot-langs a[aria-current="page"] { color: var(--text-on-dark); font-weight: var(--fw-semibold); }
.foot-col h2 {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  font-weight: var(--fw-regular);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--text-on-dark-tertiary);
  margin: 0 0 var(--space-4);
}
.foot-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }
.foot-col a { color: var(--text-on-dark-secondary); font-size: var(--fs-ui); text-decoration: none; }
.foot-col a:hover { color: var(--text-on-dark); text-decoration: underline; }
.foot-meta {
  display: grid;
  gap: var(--space-1);
  padding-top: var(--space-6);
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-micro);
  line-height: var(--lh-relaxed);
  color: var(--text-on-dark-tertiary);
}
.foot-disclaimer {
  margin-top: var(--space-5);
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  color: var(--text-on-dark-tertiary);
  letter-spacing: var(--ls-micro);
  text-align: center;
}

/* ── Pages legales ── */
.legal { padding: var(--space-8) 0 var(--space-9); background: var(--bg-base); }
.legal-inner { max-width: 46rem; margin: 0 auto; }
.legal h1 {
  font-family: var(--sans);
  font-weight: var(--fw-semibold);
  font-size: clamp(28px, 4vw, 44px);
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-h2);
  margin-bottom: var(--space-7);
}
.legal-body h2 {
  font-family: var(--sans);
  font-weight: var(--fw-semibold);
  font-size: clamp(20px, 2.4vw, 26px);
  line-height: var(--lh-snug);
  margin: var(--space-7) 0 var(--space-3);
}
.legal-body p { margin-bottom: var(--space-4); color: var(--text-secondary); font-size: var(--fs-body-sm); }
.legal-body ul { margin: 0 0 var(--space-4) var(--space-5); padding: 0; color: var(--text-secondary); font-size: var(--fs-body-sm); }
.legal-body li { margin-bottom: var(--space-2); }
.legal-body :target { scroll-margin-top: calc(var(--nav-h) + var(--space-4)); }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}

/* ===== Revelations au defilement : DEBUT ===== */
/*
 * Le contenu se pose au fur et a mesure du defilement. Tout est en CSS natif,
 * pilote par `animation-timeline: view()` : aucune ligne de JavaScript n'est
 * ajoutee, le site n'en sert toujours aucune. Bloc volontairement isole en fin
 * de fichier et delimite par deux marqueurs, pour qu'un retour en arriere soit
 * une suppression de bloc et rien d'autre.
 *
 * ── Pourquoi DEUX gardes, et pourquoi tout est dedans ──
 *
 * L'etat masque (`opacity: 0`) est le seul vrai risque de cette fonctionnalite :
 * un texte invisible est pire qu'une page sans animation. Il n'est donc jamais
 * neutralise apres coup, il n'EXISTE pas hors des deux conditions.
 *
 * 1. `@supports (animation-timeline: view())`. Sur un moteur sans animations
 *    pilotees par le defilement, aucune de ces regles n'est parsee et la page
 *    s'affiche exactement comme avant ce bloc, contenu entierement visible.
 *
 * 2. `@media (prefers-reduced-motion: no-preference)`. Le bloc
 *    `prefers-reduced-motion: reduce` juste au-dessus ne suffirait PAS : il
 *    ecrase `animation-duration`, ce qui ne veut rien dire pour une animation
 *    dont la duree est une distance de defilement, et un element qui n'a pas
 *    encore atteint sa plage resterait peint a `opacity: 0` par le remplissage
 *    `backwards`. Formule en positif, la condition ne produit aucune regle des
 *    que le visiteur demande moins de mouvement.
 *
 * Un test de `test/build.test.mjs` verrouille les deux gardes et refuse tout
 * etat masque hors de ce bloc.
 *
 * ── Trois autres pieges de cette API ──
 *
 * - `animation-delay` est IGNORE sur une timeline de progression. La cascade
 *   interne d'un bloc se fait en decalant le DEBUT de la plage
 *   (`--reveal-at`), jamais en millisecondes, sinon elle ne se voit pas.
 * - Le raccourci `animation` remet `animation-timeline` et `animation-range` a
 *   leur valeur initiale. Les longhands sont donc ecrits seuls, jamais apres un
 *   raccourci.
 * - La plage est exprimee en `cover`, pas en `entry`. `entry 100 %` n'est
 *   atteint qu'une fois le bord BAS de l'element arrive en bas de la fenetre :
 *   sur un element plus haut que la fenetre, comme la carte tarifaire, la
 *   revelation ne se terminerait jamais au bon moment. `cover` se comporte de
 *   la meme facon sur un element de 60 px et sur un element de 800 px.
 *
 * ── Ce qui n'est jamais anime ──
 *
 * `#hero` en entier : il porte le LCP, titre et carte de progression comprises.
 * Aucun selecteur de ce bloc ne l'atteint. Les pages legales non plus : elles
 * n'ont ni `.head` ni `.step`, un document opposable se lit, il ne se met pas
 * en scene.
 *
 * Seules `opacity` et `transform` sont animees, jamais une propriete de mise en
 * page : le CLS reste nul. Aucune translation horizontale, donc aucune barre de
 * defilement horizontale.
 */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {

    /* Montee de 14 px avec fondu. Pas de keyframe `to` : l'etat final est le
     * style propre de l'element, ce qui evite de figer une valeur de
     * `transform` qu'une regle plus specifique voudrait poser. */
    @keyframes rise {
      from {
        opacity: 0;
        transform: translateY(var(--reveal-shift));
      }
    }

    /* Les filets de section se tracent au lieu de monter : ce sont des traits,
     * pas des blocs de contenu. */
    @keyframes draw {
      from {
        opacity: 0;
        transform: scaleX(0);
      }
    }

    /* Cible de revelation. `--reveal-at` decale le debut de la plage sur la
     * course de l'element a travers la fenetre : c'est le seul levier de
     * cascade. */
    #transformation .head > *,
    #transformation .step-text > *,
    #transformation .step-photos > .slot,
    #transformation .step-visual,
    #inclus .head > *,
    #inclus .block-text > *,
    #inclus .block-visual,
    #essayer .head > *,
    #essayer .pricing-card,
    #essayer .start-panel,
    #faq .head > *,
    #faq .faq-list > details {
      animation-name: rise;
      animation-fill-mode: both;
      animation-timing-function: var(--e-out);
      animation-timeline: view();
      animation-range: cover var(--reveal-at, 2%) cover calc(var(--reveal-at, 2%) + var(--reveal-span));
    }

    #transformation .rule-h,
    #inclus .rule-h,
    #faq .rule-h {
      transform-origin: left center;
      animation-name: draw;
      animation-fill-mode: both;
      animation-timing-function: var(--e-out);
      animation-timeline: view();
      animation-range: cover var(--reveal-at, 2%) cover calc(var(--reveal-at, 2%) + var(--reveal-span));
    }

    /* ── Cascade interne ──
     * Trois crans de 3 % de la course, soit l'equivalent d'une soixantaine de
     * millisecondes a vitesse de defilement ordinaire. Au-dela le bloc se
     * delite au lieu de se poser. */
    .head > *:nth-child(2)          { --reveal-at: 5%; }
    .head > *:nth-child(3)          { --reveal-at: 8%; }
    .rule-h                         { --reveal-at: 5%; }
    .step-text > *:nth-child(2)     { --reveal-at: 5%; }
    .step-text > *:nth-child(3)     { --reveal-at: 8%; }
    .block-text > *:nth-child(2)    { --reveal-at: 5%; }
    .step-photos > .slot:nth-child(2) { --reveal-at: 5%; }
    .step-photos > .slot:nth-child(3) { --reveal-at: 8%; }
    /* Le visuel arrive apres son texte, dans les deux sens de lecture de la
     * grille : c'est le texte qui annonce, le visuel qui confirme. */
    .step-visual                    { --reveal-at: 5%; }
    .block-visual                   { --reveal-at: 5%; }
    /* Le panneau de demarrage suit la carte tarifaire, qui porte le prix. */
    .start-panel                    { --reveal-at: 6%; }

    /* Six questions : 2 % par cran, sinon la derniere attendrait un tiers de sa
     * course pour apparaitre. */
    .faq-list > details:nth-child(2) { --reveal-at: 4%; }
    .faq-list > details:nth-child(3) { --reveal-at: 6%; }
    .faq-list > details:nth-child(4) { --reveal-at: 8%; }
    .faq-list > details:nth-child(5) { --reveal-at: 10%; }
    .faq-list > details:nth-child(6) { --reveal-at: 12%; }

    /* ── Mise en scene de l'apex ──
     *
     * La section raconte une progression : ce qui a ete mesure, ou l'on en est,
     * ou le plan mene. Les trois annotations doivent donc apparaitre dans
     * l'ordre ① ② ③, et la courbe se tracer dans le meme sens.
     *
     * Toutes ces animations partagent UNE timeline nommee, celle de
     * `.apex-stage`, au lieu d'avoir chacune la sienne. Sans cela l'ordre
     * demande est impossible a obtenir : ① et ③ sont dans la premiere ligne de
     * la grille, ② dans la seconde, 367 px plus bas. Sur des timelines
     * separees, chaque element mesure SA propre traversee de la fenetre, donc ②
     * commencerait la sienne 367 px de defilement apres les deux autres et
     * arriverait bon dernier. Une timeline commune ramene les trois sur la
     * meme regle graduee, et `--reveal-at` redevient un vrai rang.
     *
     * Une timeline nommee sur un ancetre est visible par ses descendants : les
     * annotations, le visuel et le trace de la courbe la referencent tous. */
    /* La declaration du nom reste HORS de la requete de media : le trace de la
     * courbe s'y accroche a toutes les largeurs, et une timeline nommee
     * introuvable est une timeline inactive, qui fige son animation a l'etat de
     * depart. Sur un trace, cela voudrait dire une courbe jamais dessinee. */
    .apex-stage { view-timeline-name: --apex-stage; }

    /* Sous 1100 px, la grille de l'apex passe en une colonne et les quatre
     * elements s'empilent : chacun traverse la fenetre a son tour, et la
     * revelation ordinaire suffit. Une timeline commune les tasserait, et
     * l'ordre ① ② ③ y serait faux, puisque l'empilement suit l'ordre du
     * document, ou ③ precede ②. */
    .apex-visual,
    .apex-annot {
      animation-name: rise;
      animation-fill-mode: both;
      animation-timing-function: var(--e-out);
      animation-timeline: view();
      animation-range: cover var(--reveal-at, 2%) cover calc(var(--reveal-at, 2%) + var(--reveal-span));
    }
    .apex-annot--right  { --reveal-at: 5%; }
    .apex-annot--center { --reveal-at: 8%; }

    /* Meme point de rupture que la grille de `.apex-stage` plus haut : c'est
     * au-dessus de 1100 px seulement que les trois annotations entourent la
     * carte et que leur ordre d'apparition raconte quelque chose. */
    @media (min-width: 1101px) {
      .apex-visual,
      .apex-annot {
        animation-timeline: --apex-stage;
        /* Course plus serree que les revelations ordinaires : quatre elements
         * se succedent sur la meme regle, un empan de 20 % les ferait se
         * chevaucher au point d'annuler la sequence. */
        animation-range: cover var(--reveal-at) cover calc(var(--reveal-at) + 12%);
      }
      /* La carte d'abord : c'est le sujet, les annotations la commentent. */
      .apex-visual        { --reveal-at: 8%; }
      .apex-annot--left   { --reveal-at: 20%; }  /* ① aujourd'hui */
      .apex-annot--center { --reveal-at: 26%; }  /* ② maintenant */
      .apex-annot--right  { --reveal-at: 32%; }  /* ③ dans 4 semaines */
    }

    .apex-intro > *,
    .apex-conclusion,
    .apex-cta {
      animation-name: rise;
      animation-fill-mode: both;
      animation-timing-function: var(--e-out);
      animation-timeline: view();
      animation-range: cover var(--reveal-at, 2%) cover calc(var(--reveal-at, 2%) + var(--reveal-span));
    }
    .apex-intro > *:nth-child(2) { --reveal-at: 5%; }

    /* ── Trace de la courbe ──
     *
     * Uniquement l'occurrence de l'apex. La carte de progression apparait DEUX
     * fois dans la page, et celle du hero porte le LCP : tous les selecteurs
     * ci-dessous sont prefixes par la section, jamais nus.
     *
     * Le trait plein se trace en `stroke-dashoffset`, la facon canonique. Elle
     * ne marche que parce que `.pc-real` porte `pathLength="1"` : la longueur
     * du trace n'est donc pas recopiee ici, ou elle deviendrait fausse a la
     * premiere retouche du chemin.
     *
     * La projection, elle, est DEJA en pointilles (`stroke-dasharray: 4 4`).
     * Lui appliquer la meme technique reviendrait a ecraser ce motif : le trait
     * apparaitrait continu puis se remettrait a pointiller, ou pire, les
     * pointilles grandiraient. Elle est donc devoilee par un `clip-path`
     * anime, qui ne touche pas au trace du tout et se contente de decouvrir la
     * zone de gauche a droite. Les inserts de fin sont NEGATIFS a dessein : sur
     * un element SVG, les pourcentages d'un `inset()` se resolvent sur la boite
     * englobante du CHEMIN, qui ignore l'epaisseur du trait ; a zero, le
     * decoupage rognerait la moitie de l'epaisseur sur les quatre bords. */
    @keyframes trace { from { stroke-dashoffset: 1; } }
    @keyframes unveil { from { clip-path: inset(-25% 100% -25% -25%); } }
    @keyframes fade { from { opacity: 0; } }
    @keyframes pop { from { opacity: 0; transform: scale(0.4); } }

    #transformation .pc-area,
    #transformation .pc-real,
    #transformation .pc-now,
    #transformation .pc-proj,
    #transformation .pc-goal {
      animation-fill-mode: both;
      animation-timing-function: var(--e-out);
      animation-timeline: --apex-stage;
      animation-range: cover var(--reveal-at) cover calc(var(--reveal-at) + var(--trace-span, 14%));
    }
    /* Le passe mesure se trace, puis le reperage du jour, puis la projection,
     * puis le point d'arrivee. C'est la phrase de la section, dans l'ordre. */
    #transformation .pc-real  { stroke-dasharray: 1; animation-name: trace;  --reveal-at: 12%; --trace-span: 20%; }
    #transformation .pc-area  { animation-name: fade;   --reveal-at: 14%; }
    #transformation .pc-now   { animation-name: fade;   --reveal-at: 30%; --trace-span: 8%; }
    /* `clip-path` est pose ici AUSSI, et pas seulement dans le keyframe : sans
     * valeur de base, l'etat d'arrivee serait `none`, avec lequel un `inset()`
     * n'est pas interpolable. Le moteur passerait alors en interpolation
     * discrete et la projection apparaitrait d'un coup a mi-course au lieu de
     * se devoiler. Mesure en recette, le defaut ne se voit pas dans le code.
     * `inset(-25%)` deborde la boite englobante de tous les cotes : c'est un
     * decoupage qui ne decoupe rien. */
    #transformation .pc-proj  { clip-path: inset(-25%); animation-name: unveil; --reveal-at: 34%; }
    #transformation .pc-goal  { animation-name: pop;    --reveal-at: 46%; --trace-span: 8%; transform-box: fill-box; transform-origin: center; }
  }
}
/* ===== Revelations au defilement : FIN ===== */

/* ===== Transition de document : DEBUT ===== */
/*
 * Le changement de langue est le seul vrai changement de document de ce site :
 * les liens de section de la barre sont des ancres INTERNES, qui ne provoquent
 * aucune navigation et donc aucun fondu. C'est voulu : un fondu au clic sur une
 * ancre serait un mensonge visuel, la page ne change pas.
 *
 * Portee reelle, plus large que le seul selecteur de langue, et assumee : la
 * regle vaut pour toute navigation de meme origine, donc aussi les liens du
 * pied de page vers les pages legales et le retour par le logotype. C'est
 * coherent, ce sont les memes gabarits et la meme feuille de style, mais il
 * faut le savoir plutot que de le decouvrir.
 *
 * Un fondu court, sans glissement directionnel : rien n'indique au visiteur
 * dans quel sens il se deplace entre neuf langues, une direction serait
 * arbitraire.
 *
 * Deux conditions que ce depot ne controle pas entierement :
 * - les deux documents doivent etre de MEME ORIGINE. C'est le cas : tous les
 *   liens du site sont relatifs a la racine (`/en/`, `/fr/legal/terms`). Les
 *   seules URL absolues vers hephira.com sont la canonique et les alternats
 *   `hreflang` du `<head>`, qui sont des metadonnees et ne se cliquent pas ;
 * - le moteur doit supporter les transitions de vue entre documents. Sinon la
 *   navigation est simplement instantanee, comme aujourd'hui.
 *
 * La garde `no-preference` est indispensable ici aussi : le bloc
 * `prefers-reduced-motion: reduce` plus haut cible `*`, et un selecteur
 * universel n'atteint PAS les pseudo-elements de transition de vue.
 */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }

  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: var(--t-page);
    animation-timing-function: ease;
  }
}
/* ===== Transition de document : FIN ===== */
