@import url("fonts.css");
@import url("tokens.css");

/* ============ Base ============ */

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

html {
  scroll-behavior: smooth;
  scroll-padding-top: 5rem;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg-page);
  color: var(--text-body);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  /* Même filet que sur les titres : n'agit qu'au zoom, quand un mot ne tient plus. */
  overflow-wrap: break-word;
}

h1,
h2,
h3 {
  font-family: var(--font-display);
  color: var(--text-strong);
  line-height: var(--lh-snug);
  margin: 0 0 var(--sp-4);
  letter-spacing: var(--display-tracking);
  /*
    Filet de sécurité pour le zoom texte. Ne se déclenche JAMAIS aux tailles
    courantes — un mot ne se coupe que s'il ne peut pas tenir autrement. À 200 %,
    en revanche, « 21,097 » à 72 px dans une colonne de 240 px poussait la page
    entière en débordement horizontal (WCAG 1.4.4). Le titre se coupe désormais
    au lieu d'élargir le document.
  */
  overflow-wrap: break-word;
}

/* Titres façon DrukWide : capitales italiques serrées. Voir docs/design-system.md. */
h1,
h2 {
  text-transform: var(--display-transform);
  font-weight: var(--display-weight);
  /*
    L'axe `wdth` fait ce que le repli « Arial Narrow » promettait sans jamais le
    tenir : les capitales de titre sont étroites, comme le lettrage de la charte.
  */
  font-stretch: var(--display-width);
}

h2 {
  font-size: var(--fs-h2);
  line-height: var(--lh-tight);
}

h3 {
  font-size: var(--fs-h3);
  font-weight: 700;
}

p {
  margin: 0 0 var(--sp-4);
  max-width: var(--measure);
}

img {
  max-width: 100%;
  display: block;
}

a {
  color: inherit;
}

/* Les grilles sont des listes : jamais de puces. */
ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

/*
  Anneau de focus unique pour tout le site. `outline` et non `box-shadow` : voir
  le commentaire de --focus-ring dans tokens.css (contrastes forcés + 1.4.11).
  Pas de `border-radius` ici : l'outline suit déjà la forme de l'élément, et en
  poser un écrasait celle des pilules.
*/
:focus-visible {
  outline: var(--focus-w) solid var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/*
  Bascule de l'anneau sur les surfaces sombres : le bleu accessible n'y fait que
  4,6:1 face au noir, le blanc y tient à 21:1. Une seule déclaration par section
  sombre suffit — les custom properties héritent jusqu'aux éléments focusables.
*/
.section--accent-deep,
.assos-featured,
.cta-final,
.footer,
.pricing-bar,
.platform-band,
.notice {
  --focus-ring: var(--c-white);
}

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

/* ============ Layout ============ */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section {
  padding-block: var(--section-y);
}

.section--warm {
  background: var(--bg-warm);
}

.section--accent-deep {
  background: var(--bg-accent-deep);
  color: var(--text-on-dark-muted);
}

.section--accent-deep h2,
.section--accent-deep h3 {
  color: var(--text-on-dark);
}

/*
  ============ Plancher des pistes de grille ============

  Un item de grille (comme un item de flex) a `min-width: auto`, et cet `auto` vaut
  sa taille MIN-CONTENT : la piste ne peut donc pas descendre sous le plus long
  fragment insécable de son contenu. `overflow-wrap: break-word`, posé sur le
  `body`, ne change RIEN à ce calcul — c'est précisément ce qui distingue
  `break-word` de `anywhere`, qui lui compte dans la taille intrinsèque.

  Conséquence, mesurée à 200 % de zoom texte dans une fenêtre de 320 px : les
  cartes d'étape, la photo du témoignage et la barre des tarifs réclamaient
  jusqu'à 60 px de plus que la page, et le document partait en défilement
  horizontal (WCAG 1.4.4 et 1.4.10).

  `min-width: 0` retire ce plancher : la piste se cale sur la place disponible, le
  texte déborde alors sa ligne, et c'est LÀ que `break-word` reprend la main et
  coupe le mot. Aucun effet aux tailles courantes, où rien ne réclame plus que sa
  piste.

  ⚠️ La correction ne va pas sur `body { overflow-wrap: anywhere }` : `anywhere`
  ferait tomber la taille min-content de TOUT le site à la largeur d'un caractère,
  et les pistes `auto` (le pied de footer, la puce numérotée des confirmations)
  perdraient le dimensionnement au contenu qu'on attend d'elles.
*/
.figures__grid > *,
.steps__grid > *,
.assos__grid > *,
.pricing-bar__grid > *,
.testimonial__card > *,
.footer__grid > *,
.confirmation__step > * {
  min-width: 0;
}

/*
  ============ Motif « parcours » ============
  Le plan de rues de la charte, en fond de section sombre. Voir
  partials/motif-parcours.njk.

  Il est ancré en bas à droite et n'occupe qu'une partie de la surface : l'aplat
  noir doit garder de la place vide. `opacity` le maintient sous le texte : à pleine
  intensité, le rose du motif passe devant le blanc du contenu.
*/
.has-motif {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

/*
  Le motif doit tenir en ENTIER dans son cadre. Agrandi jusqu'à déborder, il perd
  ce qui en fait un signe : on ne voit plus un plan, seulement des diagonales qui
  traversent le texte. D'où une largeur qui reste sous 100 % et un ancrage en bas
  à droite, la zone que le contenu laisse libre.
*/
.motif {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: -1;
  width: min(440px, 88%);
  height: auto;
  opacity: 0.42;
  pointer-events: none;
}

@media (min-width: 62em) {
  .motif {
    width: min(680px, 52%);
    opacity: 0.5;
  }
}

.section__head {
  margin-bottom: var(--sp-8);
}

/*
  Le titre de tête de section peut être un h1 (page listing) ou un h2 (sections de
  l'accueil). Le niveau est une question de structure, pas de graisse : il prend la
  même taille dans les deux cas, sinon corriger la hiérarchie déplacerait le design.
*/
.section__head h1 {
  font-size: var(--fs-h2);
  line-height: var(--lh-tight);
}

/*
  `:not(.eyebrow)` n'est pas une précaution de style, il corrige un vrai défaut :
  le surtitre EST un <p>, donc `.section__head p` (0,1,1) battait `.eyebrow`
  (0,1,0) et lui imposait le corps du chapô. Résultat, « COMMENT ÇA MARCHE »
  sortait à 18 px en capitales interlettrées — à deux doigts du h2 qu'il est censé
  annoncer, au lieu des 12 px du surtitre du hero.
*/
.section__head p:not(.eyebrow) {
  font-size: var(--fs-lead);
}

.eyebrow {
  font-family: var(--font-display);
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-deep);
  margin-bottom: var(--sp-3);
}

.eyebrow--on-dark {
  color: var(--text-highlight-on-dark);
}

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

.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: -100px;
  z-index: 100;
  background: var(--bg-surface);
  color: var(--text-strong);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius-sm);
}

.skip-link:focus {
  top: var(--sp-4);
}

/* ============ Boutons ============ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 48px; /* cible tactile */
  /*
    Filet de zoom texte (WCAG 1.4.4). Un bouton est en `inline-flex` : sa largeur
    est celle de son libellé PLUS 2 x `--sp-6` de gouttière, et les deux grandissent
    avec la taille de texte de l'utilisateur. Mesuré à 200 % : « Voir toutes les
    associations » sortait à 427 px de large dans une fenêtre de 390 px et poussait
    toute la page en débordement horizontal. Le plafond force le libellé à passer à
    la ligne au lieu d'élargir le document. Sans effet aux tailles courantes.
  */
  max-width: 100%;
  padding: var(--sp-3) var(--sp-6);
  border: 2px solid transparent;
  /* Pilule : la forme des boutons de la charte 2027 (planche « landing page »). */
  border-radius: var(--radius-pill);
  font-family: var(--font-display);
  font-size: var(--fs-small);
  font-weight: 700;
  /*
    Le libellé penche, comme sur les boutons de la charte. C'était une oblique
    SYNTHÉTISÉE par le navigateur, faute d'italique dessiné dans Manrope ; l'axe
    `slnt` la rend réelle. Et toujours pas de `skewX` ici : un skew déformerait
    la pilule elle-même, pas seulement son libellé.
  */
  font-style: var(--display-slant);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: transform var(--dur) var(--ease), background-color var(--dur) var(--ease),
    color var(--dur) var(--ease);
}

.btn:hover {
  transform: translateY(-1px);
}

/*
  Vert acide, texte noir : le CTA. La charte réserve une planche entière au fond
  des CTA, et c'est cette couleur — d'où des tokens à part plutôt que `--accent`.
  Le vert marche sur les DEUX fonds du site (18,3:1 avec du noir), donc contrairement
  au site du Marathon il n'y a aucune variante de bouton primaire à redéclarer
  pour les sections sombres.
*/
.btn--primary {
  background: var(--btn-primary-bg);
  color: var(--btn-primary-fg);
}

.btn--primary:hover {
  background: var(--btn-primary-bg-hover);
  color: var(--btn-primary-fg);
}

/* Noir : boutons posés sur photo. */
.btn--deep {
  background: var(--accent-deep);
  color: var(--c-white);
}

.btn--deep:hover {
  background: var(--c-black);
}

.btn--ghost {
  background: transparent;
  border-color: var(--border-strong);
  color: var(--text-strong);
}

.btn--ghost:hover {
  border-color: var(--accent-deep);
  color: var(--accent-deep);
}

.btn--white {
  background: var(--c-white);
  color: var(--text-strong);
}

.btn--lg {
  min-height: 56px;
  padding-inline: var(--sp-8);
  font-size: var(--fs-body);
}

/* Pied de carte : le libellé doit tenir sur une ligne, la carte est étroite. */
.btn--sm {
  padding-inline: var(--sp-4);
  font-size: var(--fs-eyebrow);
  white-space: nowrap;
}

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

@media (min-width: 40em) {
  .btn--block {
    width: auto;
  }
}

/*
  Bouton désactivé — aujourd'hui, uniquement le CTA d'une association que la plateforme
  ne connaît pas encore : son formulaire n'aurait aucune destination. Il garde sa place
  pour que les pieds de carte restent alignés d'une carte à l'autre, mais il perd son
  `href` (participations.js), donc il n'est ni cliquable ni focusable.

  `:hover` est répété : `.btn--primary:hover` est plus spécifique et reprendrait sinon
  l'aplat bleu au survol.
*/
.btn--disabled,
.btn--disabled:hover {
  background: var(--c-grey-100);
  border-color: var(--c-grey-100);
  color: var(--c-grey-500);
  cursor: not-allowed;
  transform: none;
}

/*
  Note posée sous un bouton SORTANT, pour dire où il mène. Les liens « Voir les
  collecteurs » quittent le site pour la plateforme : un changement de domaine
  non annoncé se lit comme un piège, et la note coûte une ligne.

  Sur fond clair uniquement (pages de confirmation). Sur sombre, il faudrait
  `--text-on-dark-muted` — pas d'usage aujourd'hui, donc pas de variante.
*/
.btn-hint {
  margin: var(--sp-2) 0 0;
  font-size: var(--fs-small);
  color: var(--text-body);
}

/* ============ Header ============ */
/* Blanc, comme le site officiel de l'épreuve — pas de bandeau sombre. */

.header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--c-white);
  color: var(--text-strong);
  border-bottom: 1px solid var(--border-light);
}

/*
  Le filet de dégradé en tête de page. C'est le seul endroit où toute la rampe est
  visible d'un coup, et il suffit à situer l'édition avant même le logo.
*/
.header::before {
  content: "";
  display: block;
  height: var(--rule-h);
  background: var(--rule-brand);
}

.header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  min-height: 64px;
  /*
    Sans JS, la nav passe à la ligne sous le header au lieu d'écraser la marque.

    ⚠️ Et le `wrap` vaut AUSSI en desktop, où un `flex-wrap: nowrap` figurait
    jusqu'ici sous `@media (min-width: 70em)`. Les deux valeurs donnent le même
    rendu tant que la barre tient sur une ligne — c'est le cas dès 1120 px, avec
    26 px de marge (voir la note du point de rupture plus bas). Elles ne
    divergent QUE lorsqu'elle ne tient plus, et alors :

      nowrap  les items ne peuvent plus se replier, et comme `.header__nav-list a`
              et `.header__cta` portent `white-space: nowrap`, ils ne rétrécissent
              pas non plus : ils SORTENT. Mesuré à 200 % de zoom TEXTE (le zoom
              texte seul de Firefox, ou une taille de police système élevée — le
              zoom de PAGE, lui, réduit la largeur en px CSS et rebascule sur le
              hamburger, donc il ne montrait rien) : le CTA du header se posait à
              1709 px dans une fenêtre de 1440, soit 269 px de débordement
              horizontal, sur toutes les pages. Échec de WCAG 1.4.4 et 1.4.10.
      wrap    le CTA passe sous la barre, le lockup reste entier, et la page ne
              défile pas latéralement.

    Le `nowrap` des libellés reste, lui : il porte l'intention « un libellé trop
    long doit se voir ». Elle tient toujours — un libellé rallongé fait désormais
    tomber le CTA sur une seconde ligne au lieu de se replier en silence.
  */
  flex-wrap: wrap;
}

/*
  ⚠️ Le passage en barre horizontale se fait à 70em (1120 px), pas à 62em (992 px).

  Mesuré : le contenu du header réclame 999 px, et la largeur utile vaut
  `largeur de fenêtre - l'ascenseur - 2 x 40 px de gouttière`. Il faut donc
  1095 px de fenêtre pour que la barre tienne. Entre 992 et 1120, elle ne tenait
  pas — et comme la nav du Semi est en `white-space: nowrap`, elle ne se replie
  pas : elle ÉCRASE la marque. À 992 px, « DOSSARDS ENGAGÉS » se superposait
  littéralement à « LE PROGRAMME », le lockup étant comprimé de 232 à 119 px.

  Dans cette plage, c'est le panneau au hamburger qui sert. 70em laisse 26 px de
  marge à 1120 px.

  ⚠️ La valeur diffère de celle du dépôt du Marathon (75em) et c'est normal : la
  barre y porte CINQ entrées, ici quatre — « Devenir association bénéficiaire »
  vit dans le créneau secondaire du panneau, pas dans `site.nav`.

  La bascule elle-même est plus bas, dans le grand `@media (min-width: 70em)` de la
  section « Navigation » ; elle est solidaire de `.sticky-cta`, du `padding-bottom`
  du `body` et de `DESKTOP` dans nav.js. Cette note est ici parce que c'est ici que
  se règle ce que fait le header quand il ne tient PAS.
*/
/*
  `min-width: 0` + `flex-wrap` : à 200 % de zoom texte, « DOSSARDS ENGAGÉS » posé à
  côté du lockup dépassait de la fenêtre. Il passe désormais sous le logo au lieu
  de pousser la page. Aucun effet aux tailles courantes.
*/
.header__brand {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  min-width: 0;
  text-decoration: none;
  color: inherit;
}

.header__logo {
  width: auto;
  height: 44px;
}

/* Le lockup empile « HOKA » et « SEMI DE PARIS » : sous 48px, la seconde ligne devient illisible. */
@media (min-width: 48em) {
  .header__logo {
    height: 54px;
  }
}

/* Séparateur : le logo est la marque de l'épreuve, le texte est le nom du programme. */
.header__brand-text {
  padding-left: var(--sp-3);
  border-left: 1px solid var(--border);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-small);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  line-height: 1.15;
}

.header__brand-text strong {
  display: block;
  color: var(--accent-deep);
}

/* --- Navigation ---
   Un seul markup pour deux affichages : panneau plein écran en mobile,
   barre horizontale en desktop. Voir header.njk et assets/js/nav.js.
*/

/* Sans JS, le panneau reste une liste pleine largeur sous le header :
   navigable, jamais piégée. Avec JS (.has-js), il devient un panneau
   qu'on ouvre au hamburger. */
.header__nav {
  flex: 1 0 100%;
  padding-bottom: var(--sp-4);
}

.has-js .header__nav {
  flex: 0 1 auto;
  padding-bottom: 0;
  position: fixed;
  inset: 64px 0 0; /* sous le header, qui reste cliquable */
  z-index: 45;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-6) var(--gutter) calc(var(--sp-8) + env(safe-area-inset-bottom));
  background: var(--c-white);
  overflow-y: auto;
  overscroll-behavior: contain;
  transform: translateX(100%);
  visibility: hidden;
  transition: transform 220ms var(--ease), visibility 220ms var(--ease);
}

.has-js .header__nav[data-open] {
  transform: translateX(0);
  visibility: visible;
}

@media (prefers-reduced-motion: reduce) {
  .has-js .header__nav {
    transition: none;
  }
}

.header__nav-list {
  display: flex;
  flex-direction: column;
}

.header__nav a {
  display: block;
  text-decoration: none;
  color: var(--text-strong);
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: -0.01em;
}

/* Mobile : liens pleine largeur, gros, séparés. Cible tactile confortable. */
.header__nav-list a {
  padding: var(--sp-4) 0;
  border-bottom: 1px solid var(--border);
  font-size: 1.5rem;
}

.header__nav-list a:hover,
.header__nav-list a:focus-visible {
  color: var(--accent-deep);
}

/*
  Le CTA du panneau mobile.

  ⚠️ Le sélecteur est préfixé par `.header__nav` pour repasser devant
  `.header__nav a` (0,1,1), qui bat `.btn--primary` (0,1,0) et imposait
  `--text-strong`. On rend donc au bouton la couleur du primaire, `--btn-primary-fg`.
  C'était visible uniquement dans le panneau mobile et dans le repli sans JS, ce
  qui explique que ça ait tenu si longtemps. Même piège que pour le `display`
  plus bas.

  ⚠️ `--text-on-accent`, ici, serait FAUX : ce token est le texte sur l'aplat
  NOIR (donc blanc), et le fond de ce bouton est le vert de marque — blanc sur
  `#DAFF00`, c'est 1,1:1, illisible. Le vert porte toujours du noir. La couleur
  du CTA du panneau doit être la même que celle de tous les autres
  `.btn--primary` du site, et c'est `--btn-primary-fg`.
*/
.header__nav .header__nav-cta {
  margin-top: var(--sp-6);
  color: var(--btn-primary-fg);
}

/*
  Les deux liens du bas du panneau mobile. `-external` sort du site,
  `-secondary` mène à /devenir-association-beneficiaire/ — le second public. Traitement discret
  dans les deux cas : ce ne sont pas des étapes du funnel coureur.

  ⚠️ SÉLECTEURS PRÉFIXÉS PAR `.header__nav`, et c'est le seul moyen de les rendre
  effectifs : `.header__nav a` (0,1,1) bat une classe nue (0,1,0) et réimposait sa
  fonte d'affichage, son gras, ses capitales et sa couleur. Non préfixées, ces
  déclarations étaient INERTES — les deux liens sortaient en The Future HOKA 700
  capitales noires, c'est-à-dire en petits liens de nav, alors qu'ils doivent se
  lire comme des notes de bas de panneau. Même piège, et même correctif, que pour
  `.header__nav-cta` juste au-dessus et pour le `display` du bloc desktop.

  Le contraste typographique est le fond de l'affaire : les entrées du funnel sont
  en fonte d'AFFICHAGE, capitales, 24 px ; celle-ci en fonte de TEXTE,
  bas-de-casse, 16 px. C'est ce qui dit qu'elle n'est pas du même ordre.
*/
.header__nav .header__nav-external {
  /* 22 px de haut : sous le minimum de 24 px de WCAG 2.5.8. */
  display: flex;
  align-items: center;
  min-height: 24px;
  margin-top: var(--sp-4);
  font-size: var(--fs-small);
  color: var(--text-body);
  text-transform: none;
  font-family: var(--font-body);
  font-weight: 400;
}

/*
  Convention maison, reprise de `.footer__links a` : les listes de liens ne sont
  pas soulignées au repos, elles le sont au survol. Sans ce bloc, `.header__nav a`
  (`text-decoration: none`) laisserait deux lignes de texte gris sans aucun retour
  d'interaction — le prix à payer d'être passé en fonte de texte.
*/
.header__nav .header__nav-external:hover,
.header__nav .header__nav-external:focus-visible {
  text-decoration: underline;
}

/* Bouton hamburger : à droite, mobile seulement.
   Masqué sans JS : il n'ouvrirait rien, et un bouton mort est pire que pas de bouton. */
.nav-toggle {
  display: none;
}

.has-js .nav-toggle {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  margin-right: calc(var(--sp-2) * -1);
  padding: 0;
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  color: var(--text-strong);
}

.nav-toggle__bars {
  position: relative; /* repère des deux autres barres */
}

.nav-toggle__bars,
.nav-toggle__bars::before,
.nav-toggle__bars::after {
  display: block;
  width: 24px;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
  transition: transform 200ms var(--ease), opacity 200ms var(--ease);
}

.nav-toggle__bars::before,
.nav-toggle__bars::after {
  content: "";
  position: absolute;
}

.nav-toggle__bars::before {
  transform: translateY(-7px);
}

.nav-toggle__bars::after {
  transform: translateY(7px);
}

/* Ouvert : les barres deviennent une croix. */
.nav-toggle[aria-expanded="true"] .nav-toggle__bars {
  background: transparent;
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bars::before {
  transform: translateY(0) rotate(45deg);
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bars::after {
  transform: translateY(0) rotate(-45deg);
}

@media (prefers-reduced-motion: reduce) {
  .nav-toggle__bars,
  .nav-toggle__bars::before,
  .nav-toggle__bars::after {
    transition: none;
  }
}

/* Panneau ouvert : le fond ne défile plus derrière. */
body.nav-open {
  overflow: hidden;
}

.header__cta {
  display: none;
}

@media (min-width: 70em) {
  /* Desktop : barre horizontale classique, pas de hamburger ni de panneau. */
  .has-js .header__nav,
  .header__nav {
    /*
      `flex` DOIT être remis ici, et c'est la seule déclaration que ce bloc
      oubliait. Sans JS, la nav garde sinon le `flex: 1 0 100%` du repli mobile
      (la liste passe sous le header) : elle réclame toute la ligne d'un
      `.header__inner` qui est en `nowrap` à partir de 70em, et pousse le CTA
      hors du conteneur — 36 px de débordement à 1440, 156 px à 1024.
      Avec JS le défaut ne se voyait pas : `.has-js .header__nav` (0,2,0) posait
      déjà `flex: 0 1 auto`.
    */
    flex: 0 1 auto;
    position: static;
    inset: auto;
    z-index: auto;
    flex-direction: row;
    align-items: center;
    gap: var(--sp-6);
    padding: 0;
    background: none;
    overflow: visible;
    transform: none;
    visibility: visible;
    /*
      La barre n'est pas un panneau qui coulisse : elle n'a rien à animer. Sans
      ce `none`, elle héritait de la transition `transform`/`visibility` du
      panneau mobile, et repasser du panneau à la barre en élargissant la fenêtre
      la faisait glisser en place.
    */
    transition: none;
  }

  .header__nav-list {
    flex-direction: row;
    /*
      Gouttière resserrée (16 px au lieu de 24) : voir la note de taille ci-dessous.
      C'est le même problème de largeur, réglé sur l'autre levier.
    */
    gap: var(--sp-4);
  }

  /*
    ⚠️ La barre de nav est calée à 14 px et non sur `--fs-small`, et c'est une
    contrainte de LARGEUR propre à cette charte, pas un choix esthétique.

    The Future HOKA est une géométrique large : à corps égal, « COMMENT ÇA MARCHE »
    y occupe sensiblement plus de place que dans la fonte du site du Marathon. Or
    `--fs-small` a par ailleurs MONTÉ de 7 % ici, pour compenser la petite hauteur
    d'x de Futura dans le texte courant (voir tokens.css). Les deux effets se
    cumulent exactement là où il y a le moins de place.

    Résultat à 1440 px avec `--fs-small` : les quatre libellés ET le CTA passaient
    tous sur deux lignes, et « DOSSARDS ENGAGÉS » tombait sous le lockup au lieu de
    se poser à côté. Le header entier se dépliait en trois étages.

    `white-space: nowrap` verrouille l'intention : si un libellé devient trop long,
    on veut le voir déborder et le corriger, pas le voir se replier en silence.
  */
  .header__nav-list a {
    padding-block: var(--sp-2);
    border-bottom: 2px solid transparent;
    font-size: var(--fs-nav);
    letter-spacing: 0.02em;
    white-space: nowrap;
  }

  /*
    Séparateurs entre les entrées du menu. Même filet décoratif que celui qui
    sépare le lockup du nom du programme : `--border`, le token prévu pour les
    traits qui ne délimitent rien.

    ⚠️ En pseudo-élément ABSOLU posé au milieu de la gouttière, et pas en
    `border-left` + `padding` : le trait ne coûte alors AUCUNE largeur, et la
    barre n'a que 26 px de marge à 1120 px. Un `border-left` avec ne serait-ce
    que 8 px de part et d'autre ajouterait 3 x 17 = 51 px et repousserait le
    point de rupture de 1120 à 1152 px.

    `li + li` : un séparateur ENTRE les entrées, donc jamais avant la première.
  */
  .header__nav-list li + li {
    position: relative;
  }

  /*
    Entrées réservées au PANNEAU mobile. La place utile de la barre est plafonnée
    par `--container` (1105 px au maximum, quelle que soit la fenêtre) : avec les
    intitulés d'audience elle ne tient que trois entrées. Mesuré à 1200 px, CTA
    post-lancement — 5 entrées : Marathon 0 px de marge et header à 99 px, Semi
    −176 px et header à 125 px ; 3 entrées : +242 px et +138 px, header à 69 px.
    Réduire la typo ne suffit pas (le Semi reste à −92 px au plancher), et la
    fonte de texte est PLUS large que la fonte d'affichage : +48 px sur le
    Marathon, +61 px sur le Semi.

    Le panneau, plein écran, n'a pas cette contrainte : rien n'est perdu en
    mobile. Le drapeau est `panelOnly` dans `site.json` → Réglages.
  */
  .header__nav-list li.header__nav-panel-only {
    display: none;
  }

  /*
    ⚠️ Le séparateur est en `li + li`, et `+` matche les frères du DOM, masqués
    compris : la première entrée VISIBLE de la barre héritait donc d'un filet
    flottant à sa gauche. On le retire derrière toute entrée masquée.
  */
  .header__nav-list li.header__nav-panel-only + li::before {
    content: none;
  }

  .header__nav-list li + li::before {
    content: "";
    position: absolute;
    /* Le milieu exact de la gouttière de `--sp-4`. */
    left: calc(var(--sp-4) / -2);
    top: 50%;
    width: 1px;
    height: 18px;
    transform: translateY(-50%);
    background: var(--border);
  }

  /*
    Même raison que les libellés : le CTA du header ne se replie pas sur deux
    lignes. Gouttières internes à `--sp-4` (au lieu de `--sp-6`) et interlettrage
    à 0,03em (au lieu du 0,06em de `.btn`) : 26 px gagnés, ce qui est exactement
    ce qui descend le point de rupture de 1152 à 1120 px.
  */
  .header__cta {
    white-space: nowrap;
    padding-inline: var(--sp-4);
    font-size: var(--fs-nav);
    letter-spacing: 0.03em;
  }

  /* Le nom du programme se pose à CÔTÉ du lockup, pas dessous. */
  .header__brand {
    flex-wrap: nowrap;
  }

  .header__nav-list a:hover {
    border-bottom-color: var(--accent);
    color: var(--text-strong);
  }

  /*
    Doublons du panneau mobile : le header a déjà son CTA et le footer le lien.
    Sélecteurs préfixés par .header__nav pour passer devant `.header__nav a`
    (0,1,1), sinon le `display: block` des liens gagne et le CTA s'affiche deux fois.
  */
  .header__nav .header__nav-cta,
  .header__nav .header__nav-external,
  .has-js .nav-toggle,
  .nav-toggle {
    display: none;
  }

  .header__cta {
    display: inline-flex;
  }
}

/* ============ Hero ============ */
/*
  Charte 2027 : la typo tient sur un aplat clair, la photo vient DESSOUS en pleine
  largeur. C'est la structure de la planche de référence, et elle règle au passage
  le vieux problème du hero précédent — du texte blanc sur une photo, dont la
  lisibilité dépendait d'un voile de plus en plus opaque.
*/

.hero {
  position: relative;
  isolation: isolate;
  background: var(--bg-hero);
  color: var(--text-body);
  padding-top: clamp(2.5rem, 8vw, 4.5rem);
}

.hero__inner {
  position: relative;
  padding-bottom: clamp(2.5rem, 8vw, 4.5rem);
}

.hero h1 {
  font-size: var(--fs-hero);
  line-height: var(--lh-snug);
  color: var(--text-strong);
  max-width: 18ch;
  text-wrap: balance;
  font-style: var(--display-slant);
  margin-bottom: var(--sp-6);
}

/*
  Le mot mis en valeur passe en dégradé — c'est le procédé de la charte, et il
  n'était pas disponible avant : la palette 2026 était monochrome, un accent bleu
  sur bleu tombait à 2,6:1 et il fallait inverser en bloc blanc.
  Sur ce fond clair c'est la moitié FROIDE de la rampe qui s'applique (voir
  `.gradient-text` plus haut) : la moitié chaude y serait à 2,6:1.
*/
.hero h1 em {
  /*
    Le mot en dégradé penchait avec le reste du titre tant que la pente était un
    `skewX` posé sur le bloc. Maintenant qu'elle est portée par la fonte, il faut
    la faire suivre explicitement : sans ça, le `font-style: italic` par défaut du
    <em> serait bien remplacé, mais par du droit — le mot se redresserait tout seul
    au milieu d'un titre penché.
  */
  font-style: inherit;
}

.hero__sub {
  font-size: var(--fs-lead);
  color: var(--text-strong);
  max-width: 44ch;
  margin-bottom: var(--sp-8);
}

/*
  Bandeau de dégradé en pied de hero, pleine largeur. Il remplace la photo, qui
  n'avait rien d'officiel (une vue du MARATHON reprise de Wikimedia) : le lot
  d'assets du Semi donne un aplat de dégradé à cette place.

  C'est `--gradient-brand`, la rampe VIVE, et c'est correct ici : l'emploi est
  décoratif, aucun texte ne s'y pose. La règle « la rampe vive va sur le noir »
  ne concerne que le dégradé EN TEXTE.

  Hauteur en `clamp` et non en `aspect-ratio` : la photo avait un cadrage à
  préserver (4/3 mobile, 21/8 au-delà), un dégradé n'a rien à cadrer. Un 4/3
  peindrait ici un tiers d'écran de mobile pour zéro information.
*/
.hero__band {
  height: clamp(5rem, 10vw, 9rem);
  background: var(--gradient-brand);
}

.hero__actions {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin-bottom: var(--sp-6);
}

@media (min-width: 40em) {
  .hero__actions {
    flex-direction: row;
    align-items: center;
  }

  /*
    `.btn` est déjà plafonné à `max-width: 100%`, mais 100 % vaut la largeur du
    PARENT et non la moitié : deux boutons tiennent chacun sous le plafond et
    débordent quand même à deux. Ce qui les empêchait de se rétrécir davantage,
    c'est le plancher `min-width: auto` des items de flex — leur plus long mot.
    Mesuré à 200 % de zoom texte dans une fenêtre de 768 px : 104 px de
    débordement. `min-width: 0` lève le plancher et `overflow-wrap: break-word`
    (hérité du body) coupe le mot.

    ⚠️ Et pas `flex-wrap: wrap`, qui réglait le débordement mais empilait aussi
    les deux boutons dès 640 px À TAILLE NORMALE — ils n'y tiennent déjà pas côte
    à côte sans rétrécir. Corriger un défaut de zoom ne doit pas déplacer la mise
    en page de tout le monde. Même lever que pour les grilles, voir sa note.
  */
  .hero__actions > .btn {
    min-width: 0;
  }
}

.hero__note {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-small);
  color: var(--text-strong);
}

/*
  Pastille rose. Elle ne PORTE pas l'information (le texte à côté le fait), mais
  elle doit se VOIR — et c'est pour ça qu'elle n'est pas verte : sur l'aplat clair
  du hero, le vert de marque tombe à 1,1:1, un point vert cerclé d'un halo vert y
  serait purement invisible. Le rose y tient à 3,8:1.
*/
.hero__note::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent-pink);
  box-shadow: 0 0 0 4px var(--glow-pink);
}

/* Bandeau tarifs — bleu foncé, sous le hero */

/*
  Plus de filet en tête : il marquait la couture entre la PHOTO du hero et
  l'aplat noir. Le hero finit maintenant sur le bandeau de dégradé lui-même —
  un filet du même dégradé juste dessous se lisait comme une bavure de rendu.
*/
.pricing-bar {
  background-color: var(--bg-accent-deep);
  color: var(--text-on-dark-muted);
  padding-block: var(--sp-8) var(--sp-6);
}

.pricing-bar__grid {
  display: grid;
  gap: var(--sp-6);
}

@media (min-width: 48em) {
  .pricing-bar__grid {
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-12);
  }
}

.pricing-bar dt {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 700;
  text-transform: uppercase;
  color: var(--text-on-dark);
  margin-bottom: var(--sp-1);
}

.pricing-bar dd {
  margin: 0;
  font-size: var(--fs-small);
  max-width: 52ch;
}

/*
  Le montant. Rose accessible sur le noir : 4,6:1, donc lisible même si le lecteur
  ne distingue pas la couleur. C'est le seul chiffre coloré de la bande.
*/
.pricing-bar dt span {
  color: var(--accent-pink);
}

/* ============ Chiffres clés ============ */

/*
  Les chiffres clés sont la SEULE section posée sur l'aplat cyan de la charte.
  C'est le rôle qu'on lui a donné, et il tient à trois choses : la bande est
  courte et close, elle ne contient aucun CTA (le vert n'a donc rien à y perdre),
  et elle tombe entre la barre de conditions et le bandeau photo — deux surfaces
  sombres qu'elle sépare. Le reste du site garde le couple blanc / noir.

  ⚠️ Tout y est en noir, y compris les libellés : voir --text-on-vivid. Le
  `border-bottom` saute, l'aplat borne la section tout seul.
*/
.figures {
  background: var(--bg-vivid);
  color: var(--text-on-vivid);
  padding-block: var(--sp-12);
}

/*
  L'anneau de focus par défaut est le bleu accessible : sur le cyan il ne fait que
  1,6:1. Le noir y tient à 7,5:1, comme le texte.
*/
.figures {
  --focus-ring: var(--c-black);
}

/*
  ⚠️ `minmax(0, 1fr)` et non `1fr`. `1fr` est un raccourci de `minmax(auto, 1fr)`,
  et ce `auto` vaut la taille MIN-CONTENT de la colonne : une piste ne peut pas
  descendre sous le plus long fragment insécable qu'elle contient. Or les chiffres
  clés sont écrits avec des espaces insécables (« 1 174 906 € » est un seul
  fragment) et `overflow-wrap: break-word`, posé sur le `body`, ne compte PAS dans
  le calcul de min-content — c'est la différence entre `break-word` et `anywhere`.
  À 200 % de zoom texte dans une fenêtre de 320 px, la grille réclamait donc 62 px
  de plus que la page. Le plancher à 0 rend la main à `break-word`, qui coupe le
  nombre au lieu d'élargir le document.
*/
.figures__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-8) var(--sp-4);
  text-align: center;
}

@media (min-width: 48em) {
  .figures__grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.figure__value {
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 5vw, 2.75rem);
  font-weight: 700;
  /* Noir plein, pas --accent-deep : le carbone ne fait que 6,1:1 sur le cyan. */
  color: var(--text-on-vivid);
  line-height: 1;
  letter-spacing: -0.03em;
  margin-bottom: var(--sp-2);
}

/*
  Court filet au-dessus de chaque chiffre. Décoratif — l'information est dans le
  chiffre.

  ⚠️ Les quatre sont NOIRS, et c'est le passage de la section sur l'aplat cyan qui
  l'impose. Ils parcouraient la rampe auparavant (bleu, violet, rose, puis la rampe
  entière), ce qui marchait sur le blanc. Sur le cyan, deux des quatre s'effacent :
  --accent-bright est un bleu voisin, et la rampe complète part de #009DFF, à un
  cheveu du fond. Un filet décoratif qui ne se voit qu'une fois sur deux ne décore
  rien. Le noir, lui, tient à 7,5:1 partout dans la bande.
*/
.figure::before {
  content: "";
  display: block;
  width: 32px;
  height: 3px;
  margin: 0 auto var(--sp-3);
  background: var(--text-on-vivid);
}

.figure__label {
  font-size: var(--fs-small);
  /* --text-body (gris 700) tomberait à 3,2:1 sur le cyan. */
  color: var(--text-on-vivid);
}

/* ============ Bandeau photo ============ */

.photo-band {
  height: clamp(140px, 22vw, 240px);
  background-image: url("/assets/img/band-coureurs-2027.jpg");
  background-size: cover;
  background-position: center;
}

/*
  ============ Bandeau plateforme ============

  L'aplat le plus court du site : une phrase, le logotype Alvarum, et rien
  d'autre. Même aplat que `.notice` (`--bg-accent`, texte blanc) parce que c'est
  le même geste — une information de service posée sur le chemin du coureur, pas
  un moment de marque.

  Pas de `--section-y` : c'est une barre, pas une section de contenu. Le padding
  court (~74 px de haut sur une ligne) la fait lire comme une transition entre le
  bandeau photo et les étapes, là où `--section-y` en ferait un bloc à part
  entière et casserait l'enchaînement.
*/
.platform-band {
  background: var(--bg-accent);
  color: var(--text-on-accent);
  padding-block: var(--sp-6);
}

.platform-band__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-4);
  text-align: center;
}

/*
  ⚠️ Blanc, et PAS `--text-highlight-on-dark` comme `.notice__body` : sur cette
  charte le surlignage sur fond sombre est le vert acide, et une phrase de trois
  lignes en vert sur noir hurle — elle concurrencerait le CTA collant, qui est
  vert lui aussi. Ici le vert n'a rien à surligner : c'est une information de
  service, pas un avertissement. Le bandeau reste donc monochrome, du texte au
  logotype. (Le jumeau du Marathon utilise bien son tint : sur bleu nuit, ce
  n'est qu'un blanc refroidi.)
*/
.platform-band__text {
  max-width: var(--measure);
  margin: 0;
  font-size: var(--fs-small);
  color: var(--text-on-dark);
}

.platform-band__link {
  display: inline-flex;
  /* Cible tactile : WCAG 2.5.8 demande 24 px. */
  min-height: 24px;
}

/*
  26 px, soit 4 de plus que dans le footer : ici le logotype PORTE l'information
  (il est le seul endroit où le nom est écrit), là il ne fait que créditer. Le SVG
  n'a pas de `viewBox` — son rapport vient du couple width/height intrinsèque,
  donc une seule dimension est pilotée. Voir docs/design-system.md.
*/
.platform-band__logo {
  display: block;
  width: auto;
  height: 26px;
}

/* Même convention que `.footer__powered` : l'opacité, seul retour possible sur
   une image — un `text-decoration` ne ferait rien. */
.platform-band__link:hover .platform-band__logo,
.platform-band__link:focus-visible .platform-band__logo {
  opacity: 0.8;
}

@media (min-width: 48em) {
  .platform-band__inner {
    flex-direction: row;
    justify-content: center;
  }

  /* La mesure ne sert qu'à casser la ligne en colonne ; en ligne, elle
     provoquerait un retour à la ligne avant le logotype. */
  .platform-band__text {
    max-width: none;
  }
}

/* ============ Étapes ============ */

.steps__grid {
  display: grid;
  gap: var(--sp-6);
  counter-reset: step;
}

@media (min-width: 48em) {
  .steps__grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 68em) {
  .steps__grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

.step {
  background: var(--bg-surface);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-md);
  padding: var(--sp-6);
  box-shadow: var(--shadow-sm);
}

/*
  Les quatre pastilles parcourent la palette : bleu -> violet -> rose -> VERT. La
  progression dit la progression du parcours, et elle finit sur le vert de marque —
  la 4e étape est celle où l'on court, donc celle qui porte la couleur du CTA.

  ⚠️ La couleur du chiffre change avec la pastille, elle ne peut pas être fixe :
  le vert est très clair et ne porte que du noir, là où les trois autres portent du
  blanc. Chaque paire vient de tokens.css, où les contrastes sont notés — et sur
  cette charte les quatre paires passent AA à n'importe quel corps (4,6:1 pour la
  plus juste), contrairement au site du Marathon où le violet imposait du grand
  texte. Les 40 px et le gras restent, mais par cohérence, plus par contrainte.
*/
.step::before {
  counter-increment: step;
  content: counter(step);
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-pill);
  background: var(--accent-bright);
  color: var(--text-on-bright);
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 700;
  font-style: var(--display-slant);
  margin-bottom: var(--sp-4);
}

.step:nth-child(2)::before {
  background: var(--accent-violet);
  color: var(--text-on-violet);
}

.step:nth-child(3)::before {
  background: var(--accent-pink);
  color: var(--text-on-pink);
}

.step:nth-child(4)::before {
  background: var(--accent-hot);
  color: var(--text-on-hot);
}

.step p {
  margin-bottom: 0;
  font-size: var(--fs-small);
}

/* ============ Témoignage ============ */
/* Noir + photo + blanc. */

.testimonial__card {
  display: grid;
  gap: var(--sp-8);
  margin: 0;
}

@media (min-width: 62em) {
  .testimonial__card {
    grid-template-columns: 360px 1fr;
    align-items: center;
    gap: var(--sp-12);
  }
}

.testimonial__media {
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--c-black);
}

@media (min-width: 62em) {
  .testimonial__media {
    aspect-ratio: 4 / 5;
  }
}

.testimonial__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 62%; /* cadre les coureurs, pas le ciel ni les jambes seules */
}

.testimonial__quote {
  font-family: var(--font-display);
  font-size: clamp(1.125rem, 2.4vw, 1.75rem);
  font-weight: 700;
  line-height: 1.35;
  color: var(--c-white);
  margin: 0 0 var(--sp-6);
  max-width: 44ch;
  text-wrap: pretty;
}

.testimonial__author {
  font-weight: 700;
  color: var(--c-white);
}

.testimonial__meta {
  font-size: var(--fs-small);
  margin-bottom: var(--sp-6);
}

.testimonial__raised {
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-2);
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid var(--border-on-dark);
  border-radius: var(--radius-sm);
  padding: var(--sp-2) var(--sp-4);
  margin-bottom: var(--sp-6);
}

/*
  Le montant collecté, en dégradé chaud (voir le @supports de `.gradient-text`).
  `--text-highlight-on-dark` reste le repli : sans background-clip, c'est lui qui
  s'affiche, et il est déjà lisible.
*/
.testimonial__raised strong {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-style: var(--display-slant);
  color: var(--text-highlight-on-dark);
}

.testimonial__raised span {
  font-size: var(--fs-small);
}

/* ============ Associations ============ */

/* ============ Bande « À la une » ============ */
/*
  Pleine largeur : elle sort du container pour toucher les deux bords et
  trancher sur le blanc de la section. Le noir est le seul fond sombre de la
  charte, donc texte blanc (voir docs/design-system.md).
*/

/*
  Filet de dégradé en haut et en bas : la bande devient un objet de la charte.
  Posé en couches de `background` plutôt qu'en `border-image` — le filet ne coûte
  alors rien en layout, et le dégradé horizontal n'a pas à survivre au découpage
  en neuf régions d'un border-image.
*/
.assos-featured {
  background-color: var(--bg-accent);
  /*
    Trois couches : les deux filets, puis le fond « nuit » de la charte. Le plat
    de bleu ne se distinguait pas assez du bleu de la section témoignage juste
    au-dessus ; la remontée de bleu vif au centre bas donne à la bande son relief
    et pousse les cartes en avant. Les filets sont déclarés d'abord : la première
    couche de `background-image` est celle du dessus.
  */
  background-image: var(--rule-brand), var(--rule-brand), var(--gradient-night);
  background-repeat: no-repeat;
  /*
    La couche « nuit » est dessinée sur 125% de la hauteur, ancrée en haut : le
    cœur de la lueur (à 118% dans le radial, voir tokens.css) tombe alors SOUS la
    bande et il n'en reste que la frange. À `cover`, ce cœur atterrit dans la
    bande et son bleu vif vient manger le filet du bas.
  */
  background-size: 100% var(--rule-h), 100% var(--rule-h), 100% 125%;
  background-position: top left, bottom left, top center;
  padding-block: var(--sp-12);
  margin-block: var(--sp-8) var(--sp-12);
}

.assos-featured__head {
  margin-bottom: var(--sp-8);
}

/*
  Le titre était un libellé (fs-h3, blanc, discret) au milieu d'une bande large :
  il ne pesait pas plus que le nom d'une association. Il passe au régime des h2
  de la page — capitale italique serrée — pour annoncer une SECTION.
*/
.assos-featured__title {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  /* « Régime des h2 » : donc les mêmes axes qu'eux, pas seulement la même taille. */
  font-weight: var(--display-weight);
  font-stretch: var(--display-width);
  line-height: var(--lh-tight);
  text-transform: var(--display-transform);
  letter-spacing: var(--display-tracking);
  color: var(--text-on-accent);
  font-style: var(--display-slant);
  margin: 0 0 var(--sp-3);
}

.assos-featured__intro {
  margin: 0;
  font-size: var(--fs-lead);
  /* Tint froid plutôt que blanc transparent : l'intro doit tenir face au titre. */
  color: var(--text-highlight-on-dark);
}

/*
  Nombre d'associations à la une volontairement libre (2 aujourd'hui) :
  auto-fit gère 1, 2, 3… sans toucher au template.
  Sélecteur doublé (0,2,0) pour passer devant les media queries de `.assos__grid`,
  qui sont plus bas dans le fichier et forceraient 2 puis 3 colonnes.
*/
.assos__grid.assos__grid--featured {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
}

.asso-card--featured {
  --logo-w: 112px;
  --logo-h: 68px;

  border-color: transparent;
  /*
    Ombre portée + halo chaud : le procédé de la planche « landing page », et la
    seule façon de faire ressortir une carte blanche d'un fond bleu sans lui
    poser une bordure de couleur (le chaud ne tient pas en trait fin ici).
  */
  box-shadow: var(--shadow-lg), 0 0 56px -14px var(--glow-hot);
  padding: var(--sp-6);
  /*
    Plafond : sans lui, une association seule à la une s'étire sur toute la
    largeur et sa moitié droite sonne creux. Ne mord qu'à une seule carte —
    à deux (552px) ou trois (362px), on est déjà sous ce seuil.
  */
  max-width: 560px;
}

.asso-card--featured:hover {
  border-color: transparent;
  box-shadow: var(--shadow-lg), 0 0 72px -10px var(--glow-hot);
  transform: translateY(-3px);
}

/*
  Pastille de mise en avant. Aplat rose et texte BLANC — l'inverse du site du
  Marathon, où le rose portait du bleu nuit. Et c'est le rose ACCESSIBLE (#E9005E)
  qui sert d'aplat, pas le rose vif : à 12 px gras, le blanc sur rose vif ne fait
  que 3,8:1. Sur le rose accessible il passe à 4,5:1. Voir tokens.css.
*/
.asso-card__flag {
  align-self: flex-start;
  margin: 0 0 var(--sp-4);
  padding: 0.2em 0.75em;
  border-radius: var(--radius-pill);
  background: var(--accent-pink);
  color: var(--text-on-pink);
  font-family: var(--font-display);
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.asso-card--featured .asso-card__name {
  font-size: var(--fs-h3);
}

.asso-card--featured .asso-card__desc {
  font-size: var(--fs-body);
}

@media (min-width: 48em) {
  .asso-card--featured {
    padding: var(--sp-8);
  }
}

.assos__grid {
  display: grid;
  gap: var(--sp-4);
}

@media (min-width: 40em) {
  .assos__grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 68em) {
  .assos__grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/*
  `[hidden]` vient de la feuille du navigateur, et N'IMPORTE QUEL `display` auteur
  le bat, quelle que soit la spécificité. Sans ce garde-fou, le `display: flex`
  ci-dessous rendait les cartes filtrées visibles quand même (même piège que
  `.field__error[hidden]`).
*/
.asso-card[hidden] {
  display: none;
}

.asso-card {
  display: flex;
  flex-direction: column;
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  padding: var(--sp-6);
  transition: box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

.asso-card:hover {
  box-shadow: var(--shadow-md);
  border-color: var(--accent);
  transform: translateY(-2px);
}

/*
  La carte est un item de grille : sa piste `1fr` se dimensionnait sur son
  min-content, et la tuile de logo (88 px non compressibles) plus le nom de
  l'association élargissaient toute la grille au zoom texte, jusqu'à faire déborder
  la page. `min-width: 0` autorise la piste à descendre sous ce min-content.

  À noter : `overflow-wrap: break-word` ne suffit PAS ici — il autorise la coupure
  à l'affichage mais n'entre pas dans le calcul du min-content.

  ⚠️ PAS de `flex-wrap` sur `.asso-card__top`. Essayé : à taille normale, les noms
  longs (« Mécénat Chirurgie Cardiaque », « Association Petits Princes ») passaient
  sous le logo alors que les noms courts restaient à côté — la grille devenait
  irrégulière d'une carte à l'autre. C'est `min-width: 0` sur le bloc de texte qui
  fait le travail : le nom se resserre, le logo garde sa place.
*/
.asso-card {
  min-width: 0;
}

.asso-card__top {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  margin-bottom: var(--sp-4);
}

/* Le bloc nom + cause doit pouvoir se resserrer, sinon il refuse de passer sous
   son min-content et c'est la carte entière qui s'élargit. */
.asso-card__top > div {
  min-width: 0;
}

/*
  Tuile paysage : les logos d'associations sont majoritairement horizontaux
  (jusqu'à 787×317). Un carré les réduirait à un timbre-poste.
  Fond blanc et non gris : beaucoup de logos sont fournis détourés sur blanc.
*/
.asso-card__logo {
  --logo-w: 88px;
  --logo-h: 56px;
  --logo-pad: 5px;

  flex: 0 0 var(--logo-w);
  width: var(--logo-w);
  height: var(--logo-h);
  padding: var(--logo-pad);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--c-white);
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.25rem;
  color: var(--accent-deep);
}

/*
  Bornage en valeurs calculées, pas en pourcentages : dans cette tuile (grille +
  place-items: center), ni height:100% ni max-height:100% ne se résolvent, et les
  logos hauts (Curie, SPA, Croix-Rouge, Restos) débordaient. Les logos d'assos ont
  des ratios très variables — de 787×317 à 2047×2267 — donc la contrainte doit
  porter sur les deux axes.
*/
.asso-card__logo img {
  max-width: calc(var(--logo-w) - 2 * var(--logo-pad));
  max-height: calc(var(--logo-h) - 2 * var(--logo-pad));
  width: auto;
  height: auto;
}

/*
  Ces titres sont des h1/h2 par la STRUCTURE, pas par le rang visuel : tous sont
  calés sur --fs-h3 ou moins. À ces corps-là, la condensation et la graisse 800
  du régime de titre ne se lisent pas comme un parti pris mais comme un accident
  de rendu — et elles font diverger des composants censés être identiques.
  Ils reviennent donc à la largeur et à la graisse du texte.
*/
.asso-card__name,
.sponsors__title,
.notice__title,
.error-summary__title,
.confirmation__step-title {
  font-stretch: normal;
  font-weight: 700;
}

/*
  Le nom de l'association est un h2 sur /associations/ et un h3/h4 sur l'accueil —
  le niveau suit la structure de chaque page. La macro asso-card promet que « le
  niveau ne change rien au rendu » ; sans ce `text-transform`, la promesse était
  fausse, et « Gustave Roussy » sortait en capitales sur le listing et en bas-de-
  casse sur l'accueil. Un nom propre d'association se compose en bas-de-casse.
*/
.asso-card__name {
  margin: 0;
  font-size: 1.0625rem;
  /*
    Tout est redit ici, et c'est le but : la carte est rendue en h2, h3 ou h4 selon
    la page, et seuls h1..h3 reçoivent le régime de titre. En h4, le nom tombait
    donc dans le texte courant — gris au lieu de noir, interligné à 1,6 — si
    bien que « Utopia 56 » dans la bande « À la une » et « Institut Curie » dans la
    grille juste en dessous n'avaient ni la même couleur ni le même interlignage.
    Le composant ne dépend plus du niveau de titre choisi par la page.
  */
  font-family: var(--font-display);
  color: var(--text-strong);
  line-height: var(--lh-snug);
  text-transform: none;
}

.tag {
  display: inline-block;
  /*
    Même filet de zoom texte que sur `.btn`. La pastille de cause vit dans une
    colonne de grille étroite : à 200 %, « ENVIRONNEMENT » et ses deux gouttières
    de `--sp-3` sortaient à 299 px dans une fenêtre de 390 px. Plafonnée, elle se
    replie sur deux lignes au lieu d'élargir la page.
  */
  max-width: 100%;
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent-deep);
  background: var(--accent-soft);
  border-radius: var(--radius-pill);
  padding: 2px var(--sp-3);
  margin-top: var(--sp-1);
}

.asso-card__desc {
  font-size: var(--fs-small);
  flex: 1;
}

/*
  `margin-top: auto` et non une marge fixe : la description est optionnelle
  (seules les assos mises en avant en ont une). Sans elle, `flex: 1` sur la
  description ne pousse plus rien et le pied remontait coller au logo, ce qui
  désalignait les cartes d'une même ligne de la grille.
*/
/*
  `flex-wrap: wrap` ne change RIEN à la taille de texte par défaut — il y a
  largement la place sur une ligne. Il sert au zoom texte : à 200 %, le libellé
  « Je choisis » (en `white-space: nowrap`) et le statut en capitales imposaient au
  pied une largeur minimale de ~430 px, qui gonflait la piste de grille `1fr` et
  faisait déborder la page entière (WCAG 1.4.4). Autoriser le retour à la ligne
  ramène la largeur minimale à celle du plus large des deux éléments.
*/
.asso-card__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border-light);
  margin-top: auto;
}

/*
  Disponibilité du dossard. Trois états, et un seul est bavard : « places
  restantes » est la normale, donc elle reste en texte discret avec une puce ;
  les deux exceptions prennent un aplat pour sauter aux yeux.
*/
/*
  Tag de statut — surtout PAS un bouton.

  Le vocabulaire des boutons de la charte est : pilule, aplat saturé, majuscules
  italiques, cible tactile de 48px. Un tag qui reprend ce vocabulaire se fait
  cliquer. Il s'en écarte donc sur quatre axes simultanés :

    forme    coin franc (`--radius-tag`) contre la pilule
    aplat    tint pâle contre l'aplat saturé
    italique aucun — les boutons penchent, pas les tags
    taille   pas de hauteur minimale, padding serré

  La teinte de l'état vit dans le liseré et l'icône, tous deux décoratifs : les
  teintes vives de la charte ne tiennent pas en TEXTE sur fond clair, mais elles
  tiennent en aplat et en pictogramme (>= 3:1, mesuré par `npm run palette`). Le
  texte reste NOIR dans les quatre états teintés.
*/
.asso-card__status {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  font-family: var(--font-display);
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border-radius: var(--radius-tag);
  padding: 0.35em 0.6em;
  background: var(--status-tint);
  color: var(--text-strong);
  /* Liseré plutôt qu'une bordure : garde le coin propre et ne décale pas le texte. */
  box-shadow: inset 3px 0 0 var(--status-hue);
  padding-left: calc(0.6em + 3px);
}

/*
  L'icône est un masque appliqué à la teinte de l'état : elle porte la couleur sans
  que celle-ci ait à passer le contraste d'un texte. Le JS n'a qu'à changer la classe.
*/
.asso-card__status::before {
  content: "";
  width: 1.05em;
  height: 1.05em;
  flex: 0 0 auto;
  background: var(--status-hue);
  -webkit-mask: center / contain no-repeat var(--status-icon);
  mask: center / contain no-repeat var(--status-icon);
}

/*
  Liste d'attente : le ROSE porte la rareté. La charte du Semi n'a pas d'orange,
  et le vert de marque ne peut pas tenir ce rôle — il est illisible sur un aplat
  clair. C'est le rose accessible qui s'en charge (3,7:1 sur son propre tint).
*/
.asso-card--waiting-list .asso-card__status {
  --status-tint: var(--tint-pink);
  --status-hue: var(--c-pink-acc);
  --status-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm1 11V6h-2v9h7v-2z'/%3E%3C/svg%3E");
}

/* Sold out : gris, pas rouge. C'est une fin de stock, pas une erreur. */
.asso-card--sold-out .asso-card__status {
  --status-tint: var(--c-grey-100);
  --status-hue: var(--c-grey-500);
  --status-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zM4 12a8 8 0 0 1 12.9-6.3L5.7 16.9A8 8 0 0 1 4 12zm8 8a8 8 0 0 1-4.9-1.7L18.3 7.1A8 8 0 0 1 12 20z'/%3E%3C/svg%3E");
}

/*
  Bientôt : l'association n'est pas encore en ligne sur la plateforme. Sans
  aplat, le plus effacé des trois — c'est une attente, pas une fermeture. Icône
  horloge, distincte du sablier de la liste d'attente par son cadran plein.
*/
.asso-card--soon .asso-card__status {
  --status-tint: transparent;
  /*
    ⚠️ `--c-grey-500` et non `--c-grey-300`. La règle des tags (voir plus haut) est
    que l'icône est un GRAPHIQUE porteur d'information : WCAG 1.4.11 lui demande
    3:1 sur son propre fond, et les trois autres états y sont (3,9 / 3,7 / 5,6:1).
    Celui-ci ne l'était pas : sans aplat, le liseré et l'horloge se posaient sur le
    blanc de la carte, où `--c-grey-300` ne fait que 1,7:1 — le token est réservé
    aux filets DÉCORATIFS, qui ne délimitent rien (voir tokens.css). `--c-grey-500`
    monte à 5,3:1 et c'est déjà la couleur du libellé juste à côté, donc l'état
    reste bien le plus effacé des quatre : c'est l'absence d'aplat qui le dit, pas
    un trait qu'on ne voit pas. Paire vérifiée par `npm run palette`.
  */
  --status-hue: var(--c-grey-500);
  --status-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm4 14.2-5-3V7h1.5v5.4l4.3 2.5z'/%3E%3C/svg%3E");
  color: var(--c-grey-500);
}

/*
  Carte épuisée ou fermée : le logo passe en retrait pour que l'œil la saute en
  balayant la grille.
*/
.asso-card--sold-out:not(.asso-card--featured) .asso-card__logo,
.asso-card--soon:not(.asso-card--featured) .asso-card__logo {
  opacity: 0.6;
}

/* ============ Recherche et filtre par cause ============ */
/*
  `hidden` en HTML, retiré par assos-filter.js : sans JS la barre n'apparaît pas.
  Le garde-fou est obligatoire — le `display: flex` juste en dessous battrait le
  `[hidden]` de la feuille du navigateur et la barre s'afficherait sans JS,
  inerte. Même piège que `.asso-card[hidden]` et `.field__error[hidden]`.
*/
.assos-filter[hidden] {
  display: none;
}

.assos-filter {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  margin-bottom: var(--sp-8);
}

.assos-filter__input {
  /* Le champ de recherche vient de la palette du formulaire (`.field__control`). */
  max-width: 28rem;
}

/*
  Button group scrollable sur mobile plutôt qu'en plusieurs lignes : neuf causes
  sur 390px feraient trois rangées et repousseraient les cartes sous la ligne de
  flottaison. Sur desktop, on repasse en `wrap`.
*/
/*
  Filtre sur l'état réel (« formulaire de demande en ligne »). Séparé des causes :
  ce n'est pas une facette du contenu mais une contrainte de disponibilité, et il
  n'apparaît qu'une fois la plateforme interrogée.
*/
.assos-filter__toggles {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

/*
  Case à cocher du filtre. Volontairement discrète et SANS le vocabulaire des
  pastilles de cause : pas de pilule, pas de majuscules, pas de bordure. Un chip
  se lit comme une facette exclusive du même groupe que les causes, alors que
  celui-ci se combine avec elles — la case dit tout de suite qu'il s'additionne.
*/
.filter-check {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  /*
    Même plancher que `.footer__links a` et `.header__nav-external` : 24 px, le
    minimum de WCAG 2.5.8. Le libellé EST la cible (le <label> englobe la case),
    et il ne mesurait 26 px que par l'effet de son interligne — une valeur qui
    tombe si le corps du texte change. Le plancher la verrouille.
  */
  min-height: 24px;
  font-size: var(--fs-small);
  color: var(--text-body);
  cursor: pointer;
}

/*
  20 px, comme les cases et les radios du formulaire de demande (`.radio input`,
  `.consent input`) : c'était la seule case du site à 16 px, et rien ne le
  justifiait — les cases par défaut du navigateur sont la plus petite cible de
  toute la page.
*/
.filter-check input {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  accent-color: var(--accent-deep);
}

.assos-filter__causes {
  display: flex;
  gap: var(--sp-2);
  overflow-x: auto;
  /*
    Les pastilles ont un anneau de focus : sans marge, il est rogné par l'overflow.
    Il faut la largeur de l'anneau PLUS son offset (voir tokens.css), sinon le
    passage à un anneau conforme le fait réapparaître tronqué.
  */
  padding: calc(var(--focus-w) + var(--focus-offset));
  margin: calc((var(--focus-w) + var(--focus-offset)) * -1);
  /*
    Ombre de bord droit : le rail défile, et rien ne le disait. La barre de
    défilement est masquée (elle traverserait les pastilles sur 40 px de haut), donc
    c'est ce dégradé qui porte l'affordance — il ne s'affiche que s'il reste
    quelque chose à faire défiler, `animation-timeline: scroll()` lisant la
    position du rail lui-même.
  */
  scrollbar-width: none;
  background: linear-gradient(to left, var(--bg-page), rgba(255, 255, 255, 0)) no-repeat
    right center / 2.5rem 100%;
}

.assos-filter__causes::-webkit-scrollbar {
  display: none;
}

/*
  Le voile s'efface quand on atteint le bout du rail. Sans scroll-timeline il
  reste affiché en permanence : un indice de trop vaut mieux qu'un rail muet.
*/
@supports (animation-timeline: scroll(self inline)) {
  .assos-filter__causes {
    animation: causes-fade-out linear both;
    animation-timeline: scroll(self inline);
    animation-range: 85% 100%;
  }

  @keyframes causes-fade-out {
    to {
      background-size: 0 100%;
    }
  }
}

@media (min-width: 48em) {
  .assos-filter__causes {
    flex-wrap: wrap;
    overflow-x: visible;
    /* Plus de rail : plus de voile, et plus d'animation à piloter. */
    background: none;
    animation: none;
  }
}

.chip {
  flex: 0 0 auto;
  min-height: 40px;
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--bg-surface);
  font-family: var(--font-display);
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-strong);
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

.chip:hover {
  border-color: var(--accent-deep);
  color: var(--accent-deep);
}

/*
  Pas de règle de focus propre à la pastille : l'anneau global de `:focus-visible`
  s'applique et suit sa forme en pilule. L'ancienne surcharge ne servait qu'à
  neutraliser l'outline pour le remplacer par une ombre — ce n'est plus le procédé.
*/

/*
  L'état actif s'accroche à `aria-pressed`, pas à une classe : une seule source de
  vérité pour le visuel et pour le lecteur d'écran, impossible de les désynchroniser.
  Noir -> texte blanc, jamais l'inverse.
*/
.chip[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-on-accent);
}

.chip[aria-pressed="true"]:hover {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
  color: var(--c-white);
}

.assos-filter__count {
  margin: 0;
  font-size: var(--fs-small);
  color: var(--text-body);
}

.assos-filter__empty {
  margin: var(--sp-8) 0 0;
  text-align: center;
  font-size: var(--fs-lead);
}

.assos__more {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  margin-top: var(--sp-8);
  text-align: center;
}

.assos__more p {
  margin: 0;
  font-size: var(--fs-small);
}

/*
  ============ Passerelle « association » ============
  Fin de la section « associations » de l'accueil : le seul endroit du parcours
  coureur où une association candidate se reconnaît. Volontairement DISCRET —
  aplat clair, bouton fantôme, titre en h3 : la page reste un funnel coureur, ce
  bloc est une sortie de route assumée, pas un second CTA en concurrence.

  Pas de geste de charte (ni filet, ni motif, ni texte en dégradé) : la section
  en a déjà un plus haut.
*/
.assos-partner {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  margin-top: var(--sp-12);
  padding: var(--sp-6);
  border-radius: var(--radius-lg);
  background: var(--bg-warm);
}

.assos-partner h3 {
  margin: 0 0 var(--sp-2);
}

.assos-partner p {
  margin: 0;
  font-size: var(--fs-small);
}

/* Le bouton se met à droite du texte dès qu'il y a la place pour les deux. */
@media (min-width: 48em) {
  .assos-partner {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-8);
    padding: var(--sp-8);
  }

  .assos-partner .btn {
    flex: 0 0 auto;
  }
}

/* ============ Bande sponsors ============ */
/*
  Marquee sans JS : deux pistes identiques côte à côte, chacune translatée de sa
  propre largeur. Quand la première sort, la seconde est exactement à sa place —
  la boucle ne se voit pas.
*/

.sponsors {
  padding-block: var(--sp-12);
  background: var(--bg-alt);
  border-block: 1px solid var(--border);
  overflow: hidden; /* la piste dépasse : on la coupe aux bords */
}

/* Le titre et le bouton pause se partagent la ligne, au-dessus du rail. */
.sponsors__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  /*
    Le titre et l'interrupteur de défilement se posent aux deux bouts d'une ligne
    qui n'a que 390 px sur mobile. À 200 % de zoom texte, « Ils soutiennent le
    Semi » et « Pause » ne tiennent plus à deux : mesuré, l'interrupteur sortait
    de 24 px. `wrap` le renvoie sous le titre.
  */
  flex-wrap: wrap;
  margin-bottom: var(--sp-8);
}

.sponsors__title {
  font-family: var(--font-display);
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-body);
  margin-bottom: 0;
}

/*
  ============ Pause du défilement ============

  WCAG 2.2.2 : tout mouvement automatique de plus de 5 s doit pouvoir être arrêté.
  La bande tournait 60 s en boucle, et la pause n'existait que sur `:hover` /
  `:focus-within` — or la piste ne contient que des <img>, donc AUCUN élément
  focusable : `:focus-within` ne se déclenchait jamais, et il n'y a pas de survol
  au doigt. Au clavier comme sur mobile, le mouvement était impossible à arrêter.

  Case à cocher plutôt que bouton + JS : le défilement est une animation CSS, donc
  son interrupteur peut l'être aussi. Ça garde la pause opérante dans le repli sans
  JS — là où, précisément, un bouton piloté par script ne servirait à rien — et
  n'ajoute pas un cinquième script au projet. L'état coché porte l'information,
  c'est le rôle natif d'une case.
*/
.sponsors:has(.marquee-pause__input:checked) .marquee__track {
  animation-play-state: paused;
}

.marquee-pause__label {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  flex: 0 0 auto;
  min-height: 32px;
  padding: var(--sp-1) var(--sp-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--bg-surface);
  font-family: var(--font-display);
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-strong);
  cursor: pointer;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

.marquee-pause__label:hover {
  border-color: var(--accent-deep);
  color: var(--accent-deep);
}

/* L'input est en sr-only : l'anneau de focus doit donc se poser sur le libellé. */
.marquee-pause__input:focus-visible + .marquee-pause__label {
  outline: var(--focus-w) solid var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* En pause : aplat bleu, texte blanc — le même code visuel que `.chip` actif. */
.marquee-pause__input:checked + .marquee-pause__label {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-on-accent);
}

/*
  Glyphe : deux barres (en marche -> « mettre en pause »), qui deviennent un
  triangle (en pause -> « reprendre »). Décoratif : le libellé à côté dit la même
  chose, et l'état coché de la case le dit aux technologies d'assistance.

  Les deux barres passent par un `linear-gradient` et non un `clip-path` : un
  polygone à trou est un tracé auto-sécant, que la règle de remplissage non nul
  rend presque plein.
*/
.marquee-pause__icon {
  flex: 0 0 auto;
  width: 9px;
  height: 10px;
  background: linear-gradient(
    to right,
    currentColor 0 35%,
    transparent 35% 65%,
    currentColor 65% 100%
  );
}

.marquee-pause__input:checked + .marquee-pause__label .marquee-pause__icon {
  background: currentColor;
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}

/*
  Le libellé suit l'état, et il change par `display` : le mot masqué sort aussi de
  l'arbre d'accessibilité, donc le nom accessible de la case reste toujours celui
  de l'action qu'elle déclenche — jamais « Pause » sur un bouton qui reprend.
*/
.marquee-pause__input:checked + .marquee-pause__label .marquee-pause__on,
.marquee-pause__label .marquee-pause__off {
  display: none;
}

.marquee-pause__input:checked + .marquee-pause__label .marquee-pause__off {
  display: inline;
}

.marquee {
  --marquee-gap: clamp(2rem, 5vw, 4rem);
  --marquee-duration: 60s;

  display: flex;
  gap: var(--marquee-gap);
  width: max-content;
}

.marquee__track {
  display: flex;
  align-items: center;
  gap: var(--marquee-gap);
  flex: 0 0 auto;
  animation: marquee var(--marquee-duration) linear infinite;
}

@keyframes marquee {
  to {
    /* La piste sort entièrement, gap compris : la copie tombe pile derrière. */
    transform: translateX(calc(-100% - var(--marquee-gap)));
  }
}

/* Laisser le temps de lire un logo. */
.marquee:hover .marquee__track,
.marquee:focus-within .marquee__track {
  animation-play-state: paused;
}

.marquee__item {
  flex: 0 0 auto;
}

.marquee__item img {
  height: clamp(28px, 5vw, 40px);
  width: auto;
  max-width: 140px;
  object-fit: contain;
  /* Logos de marques très diverses : le gris les met au même niveau et évite
     l'arc-en-ciel à côté du bleu de la charte. Couleur au survol. */
  filter: grayscale(1);
  /*
    0,65 rendait les logos difficiles à identifier en basse vision — et la couleur
    ne revenant qu'au survol, elle ne revenait jamais au doigt ni au clavier.
    0,8 garde la mise à plat voulue sans effacer la marque.
  */
  opacity: 0.8;
  transition: filter var(--dur) var(--ease), opacity var(--dur) var(--ease);
}

.marquee:hover .marquee__item img {
  filter: grayscale(0);
  opacity: 1;
}

/*
  Mouvement réduit : plus d'animation. La bande devient un rail qu'on fait
  défiler à la main, et le duplicata n'a plus lieu d'être.
*/
@media (prefers-reduced-motion: reduce) {
  .sponsors {
    overflow: visible;
  }

  /* Plus d'animation, donc plus rien à mettre en pause : le bouton disparaît. */
  .marquee-pause {
    display: none;
  }

  .marquee {
    width: auto;
    overflow-x: auto;
    padding-inline: var(--gutter);
    scroll-snap-type: x proximity;
  }

  .marquee__track {
    animation: none;
  }

  .marquee__track[aria-hidden="true"] {
    display: none;
  }

  .marquee__item img {
    filter: grayscale(0);
    opacity: 1;
  }
}

/* ============ CTA final ============ */

/*
  Le moment de marque de la page : le fond « nuit » de la charte (bleu qui remonte
  au noir), le monogramme A27 en filigrane et le motif du parcours. C'est le seul
  endroit où les trois éléments de la charte jouent ensemble — ailleurs, un seul
  suffit.
*/
.cta-final {
  background: var(--bg-night);
  color: var(--text-on-accent);
  text-align: center;
}

/*
  Monogramme en filigrane. Très peu opaque : il doit se lire comme une texture,
  pas entrer en concurrence avec le titre posé par-dessus.
*/
.cta-final__monogram {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: -1;
  width: min(560px, 88%);
  /* `height: auto` est obligatoire : sinon l'attribut height="213" du HTML tient
     et le monogramme est écrasé. */
  height: auto;
  translate: -50% -50%;
  opacity: 0.15;
  pointer-events: none;
}

.cta-final h2 {
  color: var(--text-on-accent);
  max-width: 18ch;
  margin-inline: auto;
  font-style: var(--display-slant);
  text-wrap: balance;
}

.cta-final p {
  color: var(--text-on-dark-muted);
  margin-inline: auto;
  margin-bottom: var(--sp-8);
  max-width: 46ch;
  font-size: var(--fs-lead);
}

/*
  Pas d'inversion du bouton principal ici, contrairement au site du Marathon : là-bas
  le CTA était bleu sur un aplat bleu et disparaissait. Le vert acide sur du noir
  est au contraire le contraste le plus fort de toute la charte (18,3:1) — c'est
  précisément l'endroit où il doit rester tel quel.
*/

/* ============ FAQ ============ */

.faq__list {
  max-width: 780px;
  border-top: 1px solid var(--border);
}

.faq__item {
  border-bottom: 1px solid var(--border);
}

.faq__item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-6) 0;
  cursor: pointer;
  /*
    ⚠️ `anywhere` ici, alors que le plancher des grilles plus haut se règle avec
    `min-width: 0`. La raison est que la question n'a PAS de boîte à elle : le
    <summary> est un conteneur flex, et son texte forme un item ANONYME, qu'aucun
    sélecteur ne peut atteindre. Il garde donc son `min-width: auto`, c'est-à-dire
    sa taille min-content — et à 200 % de zoom texte dans une fenêtre de 320 px,
    « désengagement » suffisait à pousser la page de 42 px.

    `anywhere` est la seule des deux valeurs d'`overflow-wrap` qui compte dans la
    taille intrinsèque : elle fait tomber ce plancher sans rien changer au rendu
    (comme `break-word`, elle ne coupe un mot que s'il ne tient pas autrement).
    Portée volontairement limitée à ce sélecteur — sur tout le site, elle
    déréglerait les pistes `auto` (voir la note du plancher des grilles).

    L'alternative serait d'envelopper la question dans un <span> pour lui donner
    `min-width: 0` ; elle coûte un élément de markup pour le même résultat.
  */
  overflow-wrap: anywhere;
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--text-strong);
  list-style: none;
}

.faq__item summary::-webkit-details-marker {
  display: none;
}

/*
  Le chevron est du contenu généré, donc vocalisé par certains lecteurs d'écran
  après la question (« … impôts ? plus »). La syntaxe `content: valeur / texte-alt`
  lui donne un texte alternatif VIDE : le signe reste dessiné, il sort de l'arbre
  d'accessibilité. Un pseudo-élément ne pouvant pas porter `aria-hidden`, c'est la
  seule façon de le faire sans passer par un SVG dans le markup.
*/
.faq__item summary::after {
  content: "+" / "";
  flex: 0 0 auto;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--accent-deep);
  transition: transform var(--dur) var(--ease);
}

.faq__item[open] summary::after {
  transform: rotate(45deg);
}

/*
  La réponse a sa propre boîte : elle porte le retrait du bas, que les paragraphes
  portaient quand il n'y en avait qu'un. Sans elle, une réponse en trois
  paragraphes cumulait trois fois `--sp-6`, et un CTA en pied de réponse se
  retrouvait décollé du texte par le retrait du dernier paragraphe.
*/
.faq__answer {
  padding-bottom: var(--sp-6);
}

.faq__item p {
  margin: 0;
  font-size: var(--fs-small);
}

/*
  ⚠️ Sélecteurs volontairement plus spécifiques que `.faq__item p` ci-dessus, qui
  remet les marges à zéro : un `.faq__answer > * + *` (0-1-0) perdait contre lui
  (0-1-1) et les paragraphes se retrouvaient collés. Ne pas « simplifier » en
  sélecteur universel sans vérifier l'écart entre deux paragraphes.
*/
.faq__answer p + p,
.faq__answer p + .btn {
  margin-top: var(--sp-4);
}

/* ============ Footer ============ */
/* Noir + filet de dégradé en tête. */

.footer {
  background-color: var(--bg-accent);
  background-image: var(--rule-brand);
  background-repeat: no-repeat;
  background-size: 100% var(--rule-h);
  background-position: top left;
  color: var(--text-on-accent);
  padding-block: var(--sp-12) 0;
}

.footer__logo {
  width: auto;
  height: 52px;
  margin-bottom: var(--sp-4);
}

/*
  La signature verbale de l'édition, en trois lignes décalées. Le mot en dégradé
  passe par le <em>, pas par une classe : le bloc est éditable (voir le @supports
  en fin de fichier).

  ⚠️ Le texte actuel est un SUBSTITUT — la charte du Semi ne fournit pas de
  signature. Voir partials/footer.njk. Ne pas y remettre celle du Marathon.
*/
.tagline {
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 3.4vw, 1.75rem);
  font-weight: 700;
  font-style: var(--display-slant);
  text-transform: uppercase;
  line-height: 1.05;
  letter-spacing: 0.01em;
  color: var(--text-on-accent);
  margin: 0 0 var(--sp-6);
  max-width: none;
}

.tagline span {
  display: block;
}

/* Décalage progressif : la ligne du milieu avance, la dernière revient. */
.tagline span:nth-child(2) {
  padding-left: 1.6em;
}

.tagline span:nth-child(3) {
  padding-left: 0.7em;
}

.footer__grid {
  display: grid;
  gap: var(--sp-8);
}

@media (min-width: 48em) {
  .footer__grid {
    grid-template-columns: 1fr auto;
    align-items: start;
  }
}

.footer__brand {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  color: var(--text-on-accent);
  margin-bottom: var(--sp-2);
}

.footer p {
  color: var(--text-on-dark-muted);
}

/*
  `min-height` et non un simple padding : ces liens faisaient 22 px de haut, sous
  le minimum de 24 px de WCAG 2.5.8. La grille garde son `gap`, donc l'espacement
  visuel ne bouge pas — seule la zone cliquable grandit.
*/
/*
  « Propulsé par Alvarum ». Le programme est une offre de Cadence Running United,
  mais tout le processus — inscription des associations, demandes de dossard,
  pré-inscription des coureurs, collecte — est opéré sur la plateforme Alvarum.
  Le bloc est donc un crédit de marque TIERS, présent sur toutes les pages.

  ⚠️ Le nom prend `--font-body` et NON `--font-display`, et il n'est ni en
  capitales ni penché. C'est délibéré : la fonte d'affichage, l'oblique et les
  capitales sont l'identité de l'ÉPREUVE. Les appliquer au nom d'un partenaire le
  ferait passer pour un élément de la charte de la course, ce qu'il n'est pas.
  « Alvarum » est un nom propre : il se compose en bas-de-casse.

  ⚠️ Aucun logo Alvarum n'est fourni — d'où un nom en texte. À remplacer par
  l'asset réel quand il arrive.
*/
.footer__powered {
  display: inline-flex;
  flex-direction: column;
  gap: 2px;
  /* Cible tactile : WCAG 2.5.8 demande 24 px. */
  min-height: 24px;
  margin-top: var(--sp-6);
  text-decoration: none;
}

.footer__powered-label {
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-highlight-on-dark);
}

/*
  Le logotype officiel, version blanche. Hauteur fixe et largeur `auto` : le SVG
  n'a pas de `viewBox`, c'est son couple width/height intrinsèque qui donne le
  rapport, et il faut donc en piloter une seule dimension.

  22 px : la même hauteur d'x apparente que le texte du footer autour. Plus grand,
  le logotype d'un prestataire concurrencerait le lockup de l'épreuve juste
  au-dessus ; plus petit, ses fûts très fins commencent à se rompre.
*/
.footer__powered-logo {
  display: block;
  width: auto;
  height: 22px;
}

/*
  Convention maison : retour visuel au survol et au focus. Un `text-decoration` ne
  ferait rien sur une image — d'où l'opacité, seul levier disponible ici.
*/
.footer__powered:hover .footer__powered-logo,
.footer__powered:focus-visible .footer__powered-logo {
  opacity: 0.8;
}

.footer__links {
  display: grid;
  gap: var(--sp-3);
  font-size: var(--fs-small);
  font-weight: 700;
}

.footer__links a {
  display: flex;
  align-items: center;
  min-height: 24px;
}

.footer__links a {
  text-decoration: none;
}

.footer__links a:hover {
  text-decoration: underline;
}

.footer__legal {
  margin-top: var(--sp-12);
  background: var(--bg-accent-deep);
  color: rgba(255, 255, 255, 0.8);
  font-size: var(--fs-eyebrow);
  padding-block: var(--sp-4);
}

.footer__legal p {
  margin: 0;
  color: inherit;
  max-width: none;
}

/* Crédit prestataire : une ligne sous le copyright, volontairement discrète. */
.footer__legal p + p {
  margin-top: var(--sp-1);
  opacity: 0.75;
}

/* ============ Formulaire de demande ============ */

.container--narrow {
  max-width: 720px;
}

.form-page__title {
  margin-bottom: var(--sp-6);
}

.form-page__intro {
  font-size: var(--fs-lead);
  color: var(--text-body);
  margin-bottom: var(--sp-6);
}

/*
  Bandeau « formulaire fermé », en tête de la page de demande. Il remplace l'intro
  quand l'association est complète.

  Aplat noir + texte blanc : c'est le traitement le plus fort de la charte qui ne
  soit pas le rouge fonctionnel — et il ne faut pas qu'un coureur remplisse vingt
  champs pour rien. Ni vert ni rose en aplat ici : un aplat vif de cette taille
  hurlerait, et le vert ne porte que du noir. Le bouton reste en `btn--white` : sur
  ce bandeau, le vert du `btn--primary` dirait « continue » alors que le bandeau dit
  l'inverse.
*/
.notice {
  background: var(--bg-accent);
  color: var(--text-on-accent);
  border-radius: var(--radius-md);
  padding: var(--sp-6);
  margin-bottom: var(--sp-8);
}

.notice__title {
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-h3);
  color: var(--text-on-accent);
}

.notice__body {
  /*
    Vert acide plutôt que blanc transparent : le texte doit rester net, et sur
    cette charte le surlignage sur fond sombre est le vert (18,3:1).
  */
  color: var(--text-highlight-on-dark);
  margin: 0 0 var(--sp-6);
}

/* Là non plus, pas d'inversion : le vert tient sur le noir du bandeau. */

@media (min-width: 48em) {
  .notice {
    padding: var(--sp-8);
  }
}

/*
  Formulaire fermé. Les fieldsets portent `disabled` (c'est LUI qui bloque l'envoi
  sans JS : un fieldset désactivé sort ses champs de la soumission et du focus) —
  encore faut-il que ça se voie, sinon la page invite à remplir.
  Le contraste bas est assumé : c'est du contenu désactivé, et le message qui
  compte est dans le bandeau au-dessus.
*/
.form--closed .form__legend,
.form--closed .field__label {
  color: var(--text-body);
}

/*
  Les astérisques disparaissent avec le formulaire : la note qui les explique
  (`form__required`) n'est plus rendue quand il est fermé, et du rouge sur un
  formulaire mort ne demande plus rien à personne.
*/
.form--closed .field__req {
  display: none;
}

.form--closed .field__control:disabled {
  background: var(--bg-warm);
  color: var(--text-body);
  border-color: var(--border-light);
  cursor: not-allowed;
}

.form--closed fieldset:disabled .radio,
.form--closed fieldset:disabled .consent {
  border-color: var(--border-light);
  color: var(--text-body);
  cursor: not-allowed;
}

/* Sans ça, survoler un champ mort le fait réagir comme s'il était vivant. */
.form--closed fieldset:disabled .radio:hover,
.form--closed fieldset:disabled .consent:hover {
  border-color: var(--border-light);
  background: transparent;
}

/* Rappel de l'association choisie, en haut du formulaire */
/* Même raison que `.asso-card__foot` : à 200 % de zoom texte, le lien « Changer »
   sortait du cadre. Sans effet à taille normale. */
.form-asso {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4);
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  margin-bottom: var(--sp-6);
}

.form-asso__label {
  margin: 0;
  font-size: var(--fs-eyebrow);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-body);
}

.form-asso__name {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-h3);
  color: var(--text-strong);
}

.form-asso__change {
  /* 22 px de haut : sous le minimum de 24 px de WCAG 2.5.8. */
  display: flex;
  align-items: center;
  min-height: 24px;
  margin-left: auto;
  font-size: var(--fs-small);
  font-weight: 700;
  color: var(--accent-deep);
  white-space: nowrap;
}

/* Récapitulatif d'erreurs, en tête de formulaire */
.error-summary {
  padding: var(--sp-4) var(--sp-6);
  margin-bottom: var(--sp-8);
  background: var(--c-white);
  border: 2px solid var(--accent-error);
  border-radius: var(--radius-md);
}

/* Le récapitulatif prend le focus au clic sur « Envoyer » : anneau rouge opaque,
   pour rester cohérent avec le rouge fonctionnel de la bordure. */
.error-summary:focus-visible {
  outline: var(--focus-w) solid var(--accent-error);
  outline-offset: var(--focus-offset);
}

.error-summary__title {
  font-size: var(--fs-h3);
  color: var(--accent-error);
  margin-bottom: var(--sp-2);
}

.error-summary__intro {
  font-size: var(--fs-small);
  margin-bottom: var(--sp-3);
}

.error-summary__list {
  list-style: disc;
  padding-left: var(--sp-6);
  font-size: var(--fs-small);
}

.error-summary__list li {
  margin-bottom: var(--sp-2);
}

.error-summary__list a {
  color: var(--text-strong);
}

.form__required {
  font-size: var(--fs-small);
  color: var(--text-body);
  margin-bottom: var(--sp-6);
}

/*
  `min-width: 0` est indispensable sur un `fieldset` : la feuille du navigateur lui
  pose un `min-width: min-content` que rien d'autre ne défait, et il refusait donc
  de descendre sous la largeur de son contenu au zoom texte — d'où un débordement
  horizontal de tout le formulaire.
*/
.form__section {
  border: none;
  border-top: 1px solid var(--border);
  padding: var(--sp-8) 0 0;
  margin: 0 0 var(--sp-8);
  min-width: 0;
}

.form__legend {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-h3);
  text-transform: uppercase;
  letter-spacing: -0.01em;
  color: var(--text-strong);
  padding: 0;
  /*
    Un <legend> se dimensionne comme une boîte flottante : il prend sa largeur
    MAX-CONTENT et ignore celle du <fieldset>. « Ta demande de dossard engagé »
    sortait ainsi de 118 px à 200 % de zoom texte dans une fenêtre de 320 px. Le
    plafond le ramène dans la colonne ; `overflow-wrap: break-word` (posé sur le
    body) fait le reste.
  */
  max-width: 100%;
  margin-bottom: var(--sp-6);
}

.field {
  margin-bottom: var(--sp-6);
}

.field:last-child {
  margin-bottom: 0;
}

.field__label {
  display: block;
  font-weight: 700;
  color: var(--text-strong);
  margin-bottom: var(--sp-2);
}

/*
  L'astérisque est collé au libellé par une espace INSÉCABLE dans form-field.njk :
  sinon il passe seul à la ligne dès que le libellé remplit la sienne, et un
  astérisque rouge orphelin sous un champ se lit comme un message d'erreur.
  « As-tu déjà un dossard pour cette course ? » le faisait à 390 px.
*/
.field__req {
  color: var(--accent-error);
}

/*
  L'inverse de l'astérisque : sur un formulaire où presque tout est requis, un
  champ facultatif ne se signale pas par l'ABSENCE de marque — personne ne compte
  les astérisques. Il le dit en toutes lettres, en retrait typographique pour
  rester subordonné au libellé. Même espace insécable que l'astérisque, et pour la
  même raison : la mention ne doit pas tomber seule à la ligne.
*/
.field__optional {
  font-size: var(--fs-small);
  font-weight: 400;
  color: var(--text-body);
}

.field__control {
  width: 100%;
  min-height: 48px;
  padding: var(--sp-3) var(--sp-4);
  font-family: inherit;
  font-size: var(--fs-body);
  color: var(--text-strong);
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

.field__control:hover {
  border-color: var(--text-body);
}

.field__control:focus {
  outline: var(--focus-w) solid var(--focus-ring);
  outline-offset: var(--focus-offset);
  border-color: var(--accent-deep);
}

textarea.field__control {
  min-height: unset;
  resize: vertical;
  line-height: var(--lh-body);
}

/*
  Champ invalide. `:user-invalid` couvre le cas sans JS (pas de rouge au chargement) ;
  `.field--invalid` est posé par form.js quand il affiche le message.
*/
.field__control:user-invalid,
.field--invalid .field__control {
  border-color: var(--accent-error);
}

.field--invalid .field__amount {
  border-color: var(--accent-error);
}

/*
  `[hidden]` vient de la feuille du navigateur : n'importe quel `display` auteur
  le bat, quelle que soit la spécificité. Sans ce garde-fou, le message et sa
  pastille rose s'affichaient en permanence, erreur ou pas.
*/
.field__error[hidden] {
  display: none;
}

.field__error {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  margin: var(--sp-2) 0 0;
  font-size: var(--fs-small);
  font-weight: 700;
  color: var(--accent-error);
  max-width: none;
}

.field__error::before {
  content: "!";
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  margin-top: 2px;
  border-radius: 50%;
  background: var(--accent-error);
  color: var(--c-white);
  font-size: 0.6875rem;
}

.field__counter {
  margin: var(--sp-2) 0 0;
  font-size: var(--fs-eyebrow);
  color: var(--text-body);
  text-align: right;
  max-width: none;
}

.field__static {
  margin: 0;
  padding: var(--sp-3) var(--sp-4);
  background: var(--bg-alt);
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-body);
}

.field__help {
  margin: var(--sp-2) 0 0;
  font-size: var(--fs-small);
  color: var(--text-body);
  max-width: none;
}

/* Montant : symbole devise collé au champ */
.field__amount {
  display: flex;
  align-items: stretch;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.field__amount:focus-within {
  outline: var(--focus-w) solid var(--focus-ring);
  outline-offset: var(--focus-offset);
  border-color: var(--accent-deep);
}

.field__currency {
  display: grid;
  place-items: center;
  padding-inline: var(--sp-4);
  background: var(--bg-alt);
  border-right: 1px solid var(--border);
  font-weight: 700;
  color: var(--text-strong);
}

.field__amount .field__control {
  border: none;
  border-radius: 0;
}

/*
  Le champ montant est un composite : le symbole devise et l'input vivent dans un
  cadre commun, et c'est LE CADRE qui porte l'anneau (`.field__amount:focus-within`).
  L'input doit donc renoncer au sien, sinon deux anneaux se dessinent l'un dans
  l'autre. C'est le seul `outline: none` légitime du fichier — il ne supprime pas
  l'indicateur de focus, il le délègue au parent, qui l'affiche toujours, y compris
  en contrastes forcés.
*/
.field__amount .field__control:focus {
  outline: none;
}

/* Groupe radio */
.field__group {
  border: none;
  padding: 0;
  margin: 0;
  min-width: 0; /* même raison que `.form__section` */
}

.field__group .field__label {
  margin-bottom: var(--sp-3);
}

.radio,
.consent {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.radio + .radio {
  margin-top: var(--sp-2);
}

.radio:hover,
.consent:hover {
  border-color: var(--accent-deep);
  background: var(--accent-soft);
}

.radio input,
.consent input {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  margin-top: 2px;
  accent-color: var(--accent-deep);
}

/*
  Le libellé est l'autre item du flex, et un item de flex a `min-width: auto` :
  il ne peut pas descendre sous son plus long mot. À 200 % de zoom texte dans une
  fenêtre de 320 px, le texte de consentement sortait de 47 px. `min-width: 0`
  rend la main à `overflow-wrap: break-word`, hérité du body.
*/
.radio > span,
.consent > span {
  min-width: 0;
}

.consent {
  margin-bottom: var(--sp-3);
  font-size: var(--fs-small);
}

/* Case fautive : le message, lui, est unique et vit sous tout le bloc. */
.consent--invalid {
  border-color: var(--accent-error);
}

.consent span {
  line-height: var(--lh-snug);
}

.form__actions {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin-top: var(--sp-8);
}

@media (min-width: 40em) {
  .form__actions {
    flex-direction: row;
    align-items: center;
  }

  /*
    ⚠️ Pas de `flex-wrap: wrap` ici, contrairement à `.hero__actions` : les deux
    boutons de ce pied de formulaire ne tiennent DÉJÀ pas sur une ligne à taille
    normale, ils s'y rétrécissent et leurs libellés passent sur deux lignes. Un
    `wrap` les empilerait donc de 640 px à 1440 px — un changement de mise en page
    permanent, pour un problème qui n'apparaît qu'au zoom texte.

    Le plancher `min-width: auto` des items de flex suffit à expliquer le
    débordement mesuré à 200 % (13 px à 768 px) : le rétrécissement s'arrête au
    plus long mot. `min-width: 0` le lève, et `overflow-wrap: break-word` (hérité
    du body) coupe le mot. Même lever que pour les grilles, voir sa note.
  */
  .form__actions > .btn {
    min-width: 0;
  }
}

.form__after {
  margin-top: var(--sp-6);
  font-size: var(--fs-small);
  color: var(--text-body);
}

/* ============ Confirmation ============ */

.confirmation__badge {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  margin-bottom: var(--sp-6);
  border-radius: 50%;
  background: var(--accent);
  color: var(--text-on-accent);
}

.confirmation__badge svg {
  width: 32px;
  height: 32px;
}

.confirmation__title {
  margin-bottom: var(--sp-4);
}

.confirmation__lead {
  font-size: var(--fs-lead);
  color: var(--text-strong);
  margin-bottom: var(--sp-8);
}

.confirmation .form-asso {
  margin-bottom: var(--sp-12);
}

.confirmation__steps {
  counter-reset: cstep;
  border-top: 1px solid var(--border);
  margin-bottom: var(--sp-8);
}

.confirmation__step {
  counter-increment: cstep;
  display: grid;
  /* `minmax(0, 1fr)` : même raison que `.figures__grid`, voir sa note. */
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-4);
  padding: var(--sp-6) 0;
  border-bottom: 1px solid var(--border);
}

.confirmation__step::before {
  content: counter(cstep);
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  color: var(--accent-deep);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-small);
}

.confirmation__step-title {
  grid-column: 2;
  font-size: var(--fs-body);
  text-transform: none;
  margin-bottom: var(--sp-1);
}

.confirmation__step p {
  grid-column: 2;
  margin: 0;
  font-size: var(--fs-small);
}

/* ============ Barre CTA mobile ============ */

.sticky-cta {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  padding: var(--sp-3) var(--gutter) calc(var(--sp-3) + env(safe-area-inset-bottom));
  background: rgba(255, 255, 255, 0.94);
  backdrop-filter: blur(8px);
  border-top: 1px solid var(--border);
}

/*
  Sur le premier écran, le CTA du hero est déjà là : la barre ferait doublon.
  Elle n'apparaît qu'une fois le hero dépassé. Animation pilotée par le scroll,
  sans JS. Navigateurs sans scroll-timeline : barre visible en permanence, ce qui
  reste correct.
*/
@supports (animation-timeline: scroll()) {
  .sticky-cta {
    animation: sticky-cta-reveal linear both;
    animation-timeline: scroll(root block);
    animation-range: 55vh 80vh;
  }

  /*
    ⚠️ `visibility` fait partie de l'animation, et ce n'est pas cosmétique : sans
    elle, la barre était à `opacity: 0` sur le premier écran mais son lien restait
    DANS L'ORDRE DE TABULATION. Au clavier, le focus disparaissait dans une zone
    vide de l'écran.

    `visibility` s'interpole par paliers : `hidden` uniquement à la progression 0
    (donc tant qu'on n'a pas atteint le début de l'animation-range, où `both` fige
    l'état `from`), `visible` dès qu'elle est entamée. C'est exactement la fenêtre
    où la barre ne doit pas être atteignable. Sans scroll-timeline, ce bloc ne
    s'applique pas du tout : la barre est visible en permanence, donc focusable —
    ce qui est correct.
  */
  @keyframes sticky-cta-reveal {
    from {
      opacity: 0;
      transform: translateY(100%);
      visibility: hidden;
    }
    to {
      opacity: 1;
      transform: translateY(0);
      visibility: visible;
    }
  }
}

/*
  ⚠️ 70em, la MÊME largeur que le passage du header en barre horizontale, et ce
  n'est pas une coïncidence : c'est le CTA du header qui prend le relais de cette
  barre. Les deux points de rupture sont solidaires. À 62em, la plage 992–1120 px
  se retrouvait sans aucun CTA visible — le header masquait le sien, la barre
  disparaissait, et il ne restait que celui caché dans le panneau au hamburger.
*/
@media (min-width: 70em) {
  .sticky-cta {
    display: none;
  }
}

body {
  /* ⚠️ `--cookie-banner-h` s'y AJOUTE : le bandeau cookies est lui aussi en
     `position: fixed`, et il est bien plus haut que la barre. Sa hauteur est
     MESURÉE par assets/js/tracking.js (elle dépend de la longueur des textes,
     donc de site.json) et posée sur `<html>`. Elle vaut 0px quand le bandeau
     est fermé — c'est le repli de `var()`, et c'est aussi ce que voit un
     visiteur sans JavaScript. */
  padding-bottom: calc(88px + var(--cookie-banner-h, 0px));
}

/* Même solidarité : la réserve de place sous la page suit la barre. */
@media (min-width: 70em) {
  body {
    padding-bottom: var(--cookie-banner-h, 0px);
  }
}

/* ============ Bandeau cookies ============ */
/*
  ⚠️ DISCRET, NON BLOQUANT, ET IL NE RECOUVRE RIEN DE DURABLE.

  Quatre décisions, et les quatre portent du sens :

    1. AUCUN VOILE, aucun centrage à l'écran, aucun `aria-modal` : la page
       reste lisible et utilisable sans répondre. Un voile ferait de la
       question un péage ;
    2. il se pose AU-DESSUS de la barre CTA mobile (`bottom: 88px`, sa hauteur)
       et pas dessous : deux barres superposées cacheraient la conversion ;
    3. `z-index: 44` — au-dessus de la barre CTA (40), EN DESSOUS du panneau de
       navigation mobile (45). Menu ouvert, le bandeau passe derrière, ce qui
       est le bon ordre : le menu est la chose qu'on vient d'ouvrir ;
    4. `max-height: 70vh` + défilement interne : sur un téléphone en paysage,
       deux paragraphes et trois boutons dépassent la hauteur de l'écran, et un
       bandeau plus grand que la fenêtre n'a plus de bouton atteignable.

  ⚠️ Le point de rupture ci-dessous est SOLIDAIRE de celui de `.sticky-cta` et
  du `padding-bottom` du body : les désynchroniser laisse soit un trou de 88px
  sous le bandeau, soit deux barres l'une sur l'autre.
*/
.cookie-banner {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 88px;
  z-index: 44;
  padding: var(--sp-4) var(--gutter);
  padding-bottom: max(var(--sp-4), env(safe-area-inset-bottom));
  max-height: 70vh;
  overflow-y: auto;
  background: var(--cookie-bg);
  color: var(--cookie-text);
  border-top: 1px solid var(--cookie-border);
  box-shadow: var(--cookie-shadow);
}

@media (min-width: 70em) {
  /* La barre CTA mobile a disparu à ce seuil : le bandeau redescend au ras de
     la fenêtre. */
  .cookie-banner {
    bottom: 0;
  }
}

.cookie-banner__inner {
  position: relative;
  max-width: var(--container);
  margin: 0 auto;
  display: grid;
  gap: var(--sp-4);
  /* Réserve pour la croix, en absolu dans ce bloc. */
  padding-right: var(--sp-8);
}

@media (min-width: 60em) {
  /* Au-delà, texte et boutons tiennent côte à côte : une seule ligne haute,
     la forme la moins intrusive. En dessous, les boutons passent sous le
     texte — les mettre à côté à 390px les réduirait à des cibles de 90px. */
  .cookie-banner__inner {
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: var(--sp-8);
  }
}

.cookie-banner__title {
  margin: 0 0 var(--sp-1);
  font-size: var(--fs-small);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--cookie-text);
}

.cookie-banner__body {
  margin: 0;
  font-size: var(--fs-small);
  /* La mesure du texte courant s'applique aussi ici : une phrase qui court sur
     toute la largeur du conteneur ne se lit pas. */
  max-width: var(--measure);
}

.cookie-banner__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
}

/*
  ⚠️ TOUS LES BOUTONS DE MÊME TAILLE, DANS LE MÊME FLUX. Le refus est un bouton
  à côté de l'accord, pas un lien relégué : la CNIL demande qu'il soit aussi
  simple à actionner. Seul le remplissage distingue l'accord — c'est une
  hiérarchie visuelle, pas une différence d'effort.
*/
.cookie-banner__actions .btn {
  /*
    ⚠️ DEUX BOUTONS PAR RANG, PAS TROIS EMPILÉS. Avec `flex: 1 1 auto`, les
    trois réponses prenaient chacune toute la largeur : le bandeau montait à
    375 px sur un écran de 390, soit près de la moitié de la fenêtre — ce n'est
    plus un bandeau discret, c'est un panneau. La base à 50 % les apparie.
  */
  flex: 1 1 calc(50% - var(--sp-3));
  justify-content: center;
}

@media (min-width: 60em) {
  .cookie-banner__actions .btn {
    flex: 0 0 auto;
  }
}

/*
  La croix ne vaut ni accord ni refus : elle met la question en veille pour
  l'onglet. Discrète, donc — mais avec une cible de 44px, parce qu'un bouton de
  fermeture minuscule est le premier reproche fait aux bandeaux.
*/
.cookie-banner__close {
  position: absolute;
  top: calc(-1 * var(--sp-2));
  right: calc(-1 * var(--sp-2));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: none;
  color: var(--cookie-text);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}

.cookie-banner__close:hover {
  background: var(--bg-alt);
}

/*
  « Cookies » — la dernière entrée du pied de page, qui ROUVRE le bandeau.
  ⚠️ C'est un `<button>`, pas un `<a>` : il ne mène à aucune page. Il doit donc
  se dessiner comme les liens qui l'entourent sans en être un — d'où la remise à
  zéro des styles de bouton natif et le soulignement, qui est ce qui le rend
  cliquable à l'œil dans cette colonne.
*/
.footer__cookies {
  appearance: none;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  text-decoration: underline;
  text-underline-offset: 0.15em;
  cursor: pointer;
  justify-self: start;
}


/*
  ============ Mode pré-ouverture ============

  Le panneau qui remplace la liste des associations sur l'accueil quand le mode
  est actif. Il ne réinvente rien : c'est le `.notice` de la page de demande,
  même aplat noir, même surlignage vert, parce que c'est le même geste — annoncer
  qu'il n'y a rien à remplir pour l'instant. Le modifieur ne fait que le recentrer
  et lui rendre une échelle de section.

  Le bouton reste en `btn--primary` : le vert tient sur le noir (18,3:1), donc pas
  de variante sombre à substituer ici — c'est la divergence avec le Marathon, où le
  primaire est du bleu sur bleu.

  Pas de filet, pas de motif, pas de texte en dégradé : la section n'en portait
  aucun avant, elle n'en gagne pas un ici. Une signature par section.
*/
.notice--prelaunch {
  /* Le `.notice` est un bandeau qui précède un formulaire ; ici il est le contenu. */
  margin-bottom: 0;
  text-align: center;
}

/*
  ⚠️ Le titre remonte à l'échelle des h2, donc il REPREND leurs axes. `.notice__title`
  ramène la largeur et la graisse au régime du texte, parce qu'à `--fs-h3` la
  condensation ne se lit pas comme un parti pris (voir le bloc plus haut) — mais ici
  le titre EST celui d'une section, et il doit peser comme les autres h2 de la page.
  Le seul précédent du fichier, `.assos-featured__title`, fait exactement pareil.
*/
.notice--prelaunch .notice__title {
  font-size: var(--fs-h2);
  font-weight: var(--display-weight);
  font-stretch: var(--display-width);
  line-height: var(--lh-tight);
}

.notice--prelaunch .notice__body {
  /* Mesure courte : le texte est centré, et centré long se lit mal. */
  max-width: 52ch;
  margin-inline: auto;
  font-size: var(--fs-lead);
}

/*
  Téléphone international : indicatif + numéro sur une ligne, deux champs pour un
  seul attribut Brevo (`SMS` + `SMS__COUNTRY_CODE`). `flex-wrap` plutôt qu'un
  point de rupture : à 390 px les deux ne tiennent pas côte à côte, et le passage
  à la ligne se fait tout seul quand le numéro n'a plus ses 12rem.

  Les deux contrôles gardent `.field__control`, donc le même cadre, la même
  hauteur et le même anneau de focus que le reste du formulaire.
*/
.phone-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
}

/*
  L'indicatif se dimensionne sur son contenu : sa base flex est la largeur de son
  option la plus longue (« États-Unis / Canada (+1) »). Aucun plancher à poser
  donc, mais il faut l'autoriser à RÉTRÉCIR.

  ⚠️ `flex-shrink: 1` (et `min-width: 0`), pas `0 0 auto`. Mesuré à 200 % de zoom
  TEXTE dans une fenêtre de 320 px : la liste réclamait 535 px, ne pouvait pas se
  réduire, et sortait la page en défilement horizontal (WCAG 1.4.4) — 215 px de
  débordement pour un seul contrôle. Rétrécie, elle reste utilisable : un `select`
  fermé peut être plus étroit que ses options, qui s'affichent dans le menu natif.
  Aucun effet aux tailles courantes, où le contenu tient largement dans la ligne.
*/
.phone-row__country {
  flex: 0 1 auto;
  width: auto;
  min-width: 0;
  max-width: 100%;
}

/*
  ⚠️ `min-width: 0` explicite : la base flex est `min-content`, et un numéro saisi
  d'un trait est insécable. Sans lui, la piste ne peut pas descendre sous la
  longueur du numéro et pousse la page en débordement horizontal au zoom TEXTE.
  Voir le bloc « Plancher des pistes de grille » en tête de fichier.
*/
.phone-row__number {
  flex: 1 1 12rem;
  min-width: 0;
}

/*
  ⚠️ CE BLOC DOIT RESTER LE DERNIER DU FICHIER.
  Il pose `color: transparent` sur des sélecteurs (`.testimonial__raised strong`,
  `.tagline em`…) que les composants plus haut colorent aussi, à specificité
  égale. C'est donc l'ordre du fichier, et lui seul, qui fait gagner le dégradé :
  remonté avant les composants, le procédé s'annule sans rien casser d'autre —
  le texte reste lisible, il perd juste sa couleur de marque.
*/
/* ============ Pages juridiques ============ */
/*
  /mentions-legales/ et /politique-de-confidentialite/ : beaucoup de titres, peu
  de hiérarchie. Deux écarts avec le reste du site, et les deux sont voulus :

    1. les `h2` descendent à `--fs-h3` et perdent les capitales. Une page qui
       empile neuf `h2` à `--fs-h2` en capitales étroites se lit comme neuf
       affiches, pas comme un document ;
    2. la mesure reste celle du texte courant (`container--narrow`), parce que
       c'est du texte suivi et rien d'autre.

  ⚠️ Bloc IDENTIQUE dans le dépôt du Marathon, et c'est normal : il ne pose
  aucune couleur ni aucune fonte, seulement de la structure. Ce sont les tokens
  qui portent la charte, et eux ne se copient jamais d'un dépôt à l'autre.

  ⚠️ Pas de `p + p` ici : `p` porte déjà `margin-bottom: var(--sp-4)` et les
  marges verticales de frères adjacents fusionnent. L'ajouter ne changerait rien
  et laisserait croire que la règle fait quelque chose.
*/
.legal__updated {
  margin-bottom: var(--sp-12);
  color: var(--text-body);
  font-size: var(--fs-small);
  font-style: italic;
}

.legal__section + .legal__section {
  margin-top: var(--sp-12);
}

/*
  `.legal` en tête du sélecteur : sans lui (0,1,1), la règle ne battrait pas
  `.section__head h1` et compagnie sur les autres pages si la classe voyageait.
  Le titre de la page, lui, garde le régime normal — c'est le seul de la page.
*/
.legal .legal__section h2 {
  margin-bottom: var(--sp-4);
  font-size: var(--fs-h3);
  line-height: var(--lh-snug);
  text-transform: none;
}

/*
  ⚠️ `list-style: disc` N'EST PAS DÉCORATIF, IL RÉTABLIT LES PUCES. La remise à
  zéro en tête de fichier pose `list-style: none` sur tous les `ul` — juste
  partout ailleurs (nav, cartes, grilles), faux ici : les traceurs de la
  politique et les crédits photo sortaient en paragraphes indentés, sans marque
  d'énumération. C'est la seule liste de PROSE du site.

  ⚠️ `margin-bottom` aussi : la même remise à zéro retire les marges des `ul`,
  et le `<p>` qui suit une liste n'apporte qu'une marge basse. Sans elle, le
  paragraphe de conclusion se collait à la dernière puce.

  `max-width` comme sur les paragraphes : sans elle, un `<li>` courait sur toute
  la largeur du conteneur alors que le `<p>` au-dessus s'arrêtait à
  `--measure`, et les deux bords ne s'alignaient pas.
*/
.legal__body ul {
  margin-top: var(--sp-4);
  margin-bottom: var(--sp-4);
  padding-left: var(--sp-6);
  max-width: var(--measure);
  list-style: disc;
}

.legal__body li + li {
  margin-top: var(--sp-2);
}

/*
  `<code>` pour les noms de cookies. Une seule ligne, et elle corrige un vrai
  défaut : quand `font-family` vaut exactement `monospace`, les navigateurs
  appliquent leur taille de police à chasse fixe (13 px) au lieu de la faire
  hériter — `_ga` sortait deux points plus petit que le texte autour. Empiler
  une famille devant `monospace` désarme cette bizarrerie.
*/
.legal__body code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.95em;
}

/*
  ============ Signature de la charte 2027 ============

  Deux procédés, et deux seulement. Toute la couleur vive de l'édition passe par
  l'un des deux — jamais en texte courant, jamais en petit.

  1. Le FILET de dégradé : une couche de `background` sur la section qui le porte.
  2. Le TEXTE en dégradé : `.gradient-text`.

  ⚠️ Le procédé rend le texte transparent et laisse le fond apparaître au travers.
  Sur un navigateur sans `background-clip: text`, le texte disparaîtrait : d'où le
  @supports, qui seul l'active. Réservé aux gros caractères — la rampe passe par
  des teintes claires.

  ⚠️ Le crochet est l'ÉLÉMENT (`h1 em`, `strong`), pas une classe, partout où le
  texte vit dans une region `editable` : l'éditeur visuel réécrit le contenu de
  ces éléments et n'a aucune raison de conserver une classe qu'il ne connaît pas.
  Un `<em>` ou un `<strong>`, lui, est ce que produit sa barre d'outils.
*/
@supports (background-clip: text) or (-webkit-background-clip: text) {
  .gradient-text,
  .hero h1 em,
  .section__head h1 em,
  .testimonial__raised strong,
  .tagline em {
    background-image: var(--gradient-on-light);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    /*
      La dernière lettre d'un mot penché déborde à droite de sa chasse. Comme le
      texte est `transparent` et n'existe que par le fond, tout ce qui sort de la
      boîte de fond n'est pas peint : le « T » de « COURT » perdait la moitié de
      sa barre et se lisait « 7 ». Le padding élargit la boîte peinte, la marge
      négative annule le décalage qu'il provoquerait sur le texte qui suit.
    */
    padding-right: 0.08em;
    margin-right: -0.08em;
    /* Sans ça, chaque ligne d'un titre qui passe à la ligne rejoue la rampe. */
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
  }

  /*
    Sur fond sombre, c'est la rampe VIVE qui passe — l'inverse de l'intuition, et
    l'inverse du site du Marathon. Voir tokens.css.
  */
  .section--accent-deep .gradient-text,
  .section--accent-deep .testimonial__raised strong,
  .cta-final .gradient-text,
  .assos-featured .gradient-text,
  .footer .gradient-text,
  .footer .tagline em {
    background-image: var(--gradient-on-dark);
  }
}
