@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, « 42,195 » à 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 y tombe à 2,1:1, le blanc
  y tient de 3,8:1 (violet) à 19:1 (bleu nuit). 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);
}

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

  ⚠️ Le cadrage n'est plus une décision de CSS. Le redessin qu'on posait ici était
  un ornement de coin, réglé en largeur pour laisser du bleu vide autour. L'asset
  officiel, lui, PORTE sa composition : il est dense en bas, s'éclaircit vers le
  haut et s'éteint sur les bords. Le rôle de la CSS se réduit donc à le poser
  pleine surface et à l'ancrer par le bas, là où le dessin est dense.

  ⚠️ `opacity` n'est pas un réglage d'ambiance, c'est une contrainte de contraste,
  et le motif officiel est BEAUCOUP plus dense que le redessin qu'il remplace :
  la même valeur n'y produit pas le même fond. Mesuré sur le pire pixel sous
  chaque bloc de texte du témoignage, en desktop, la ligne la plus faible
  (`.testimonial__meta`, du blanc à 75 %) tombait à 4,38:1 à 0,5 — sous le seuil
  AA. À 0,4 elle remonte au-dessus de 4,5:1. Ne pas remonter cette valeur.

  D'où aussi la disparition du palier desktop : l'ancien motif était plus discret
  en grand écran parce qu'il n'occupait qu'un coin, celui-ci couvre la section
  dans les deux cas.
*/
.has-motif {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

/*
  `object-fit: cover` et pas `height: auto` : la hauteur d'une section varie avec
  son contenu (et avec le mode pré-ouverture), alors que les deux cadrages de la
  charte ont un rapport fixe. En hauteur libre, le motif laissait tantôt une bande
  bleue vide, tantôt débordait de la section. `cover` + ancrage bas garde toujours
  la partie dense contre le bord inférieur et laisse `overflow: hidden` rogner le
  reste — c'est-à-dire le haut, la zone que le dessin fait déjà disparaître.
*/
.motif {
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0.4;
  pointer-events: none;
}

.motif img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 100%;
}

.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 */
  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);
}

/* Bleu Asics, texte blanc : le CTA. */
.btn--primary {
  background: var(--accent);
  color: var(--text-on-accent);
}

.btn--primary:hover {
  background: var(--accent-deep);
  color: var(--c-white);
}

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

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

.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-600);
  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 asics.com — 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. */
  flex-wrap: wrap;
}

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

  En dessous, les cinq entrées + la marque + le CTA ne tiennent pas sur une ligne,
  et ce n'est pas une question de réglage : mesuré dans le navigateur, le contenu
  naturel du header réclame ~1300 px alors qu'il n'y a que 929 px utiles à 1024 px.
  Même à un réglage plancher (13 px, aucun interlettrage, gouttières de 10 px,
  logo à 44 px), il manque encore 14 px. Le header sortait donc sur deux lignes,
  avec CHAQUE libellé de menu cassé en deux et le CTA aussi — 105 px de haut au
  lieu de 69.

  Entre 992 et 1200 px, c'est le panneau au hamburger qui sert : il est fait pour
  ça, et il vaut mieux qu'une barre illisible. Descendre plus bas que 1200
  demanderait de trancher dans le contenu (une entrée de menu en moins, un CTA
  plus court, ou le nom du programme masqué) — pas d'y retoucher la typo.
*/
@media (min-width: 75em) {
  .header__inner {
    flex-wrap: nowrap;
  }
}

/*
  `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 est sur deux lignes : sous 48px, « MARATHON DE » devient illisible. */
@media (min-width: 48em) {
  .header__logo {
    height: 54px;
  }
}

/* Séparateur : le logo est la marque du marathon, 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` : depuis le passage au bleu Asics, le libellé était du bleu nuit
  sur un bouton bleu nuit — invisible. 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.
*/
.header__nav .header__nav-cta {
  margin-top: var(--sp-6);
  color: var(--text-on-accent);
}

/*
  Le lien sortant du bas du panneau mobile.

  ⚠️ SÉLECTEUR PRÉFIXÉ PAR `.header__nav`, et c'est le seul moyen de rendre ces
  déclarations effectives : `.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, elles étaient INERTES — le lien sortait en fonte d'affichage 700
  capitales, c'est-à-dire en petit lien de nav, alors qu'il doit se lire comme une
  note 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.

  Ce qui distingue ce lien des entrées du funnel : elles sont en capitales, 700,
  24 px ; il est en bas-de-casse, 400, 15 px, gris. La FAMILLE ne joue pas ici —
  `--font-display` et `--font-body` résolvent tous deux vers Google Sans Flex sur ce
  site, seules les piles de repli diffèrent. C'est sur le dépôt du Semi que le
  changement de famille se voit (The Future HOKA -> Futura).
*/
.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 une ligne 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: 75em) {
  /* 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 75em, 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;
  }

  /*
    --- Le réglage serré de la barre ---

    À 1200 px il ne reste que 1105 px utiles dans le conteneur (1200 - 2 x 40 px
    de gouttière - la barre de défilement), pour ~1300 px de contenu au réglage
    confortable. Sans ce réglage, tout casse sur deux lignes dès 1200.

    ⚠️ Et ce réglage est DÉFINITIF, pas un palier transitoire : le conteneur est
    plafonné à `--container` (1200 px), donc la largeur utile ne dépasse jamais
    1120 px, quelle que soit la taille de l'écran. Une première version faisait
    remonter ces valeurs en `clamp(…, vw, …)` vers le réglage confortable ; le
    header recassait sur deux lignes à partir de 1366 px, parce que la typo
    grandissait avec la fenêtre alors que la place, elle, ne grandissait plus.
    C'est aussi ce qui explique que le header était cassé à TOUTES les largeurs
    desktop, 1920 comprise, et pas seulement en bas de la plage.

    Mesuré : header sur une ligne, 69 px de haut, avec 17 px de marge à 1200 px
    et 32 px au-delà de 1280 px (où le conteneur est plafonné et la barre de
    défilement ne mange plus rien).
  */
  .header__logo {
    height: 48px;
  }

  /*
    L'axe `wdth` de la charte (`--display-width`, 85 %), celui que portent déjà
    les titres : ~7 % de largeur gagnés sans descendre le corps du texte. Et le
    repli reste cohérent — « Arial Narrow » est étroite elle aussi, c'est
    exactement ce que la pile `--font-display` promet (voir fonts.css).
  */
  .header__brand-text {
    font-stretch: var(--display-width);
  }

  .header__inner {
    gap: var(--sp-4);
  }

  .header__nav-list {
    flex-direction: row;
    gap: var(--sp-4);
  }

  /*
    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` (`--c-grey-300`, 2,5:1 sur
    blanc), 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 17 px de marge à 1200 px (voir le réglage serré plus haut).
    Un `border-left` avec ne serait-ce que 8 px de part et d'autre ajouterait
    4 x 17 = 68 px et recasserait le header sur deux lignes.

    `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);
  }

  /*
    ⚠️ `white-space: nowrap` : c'est la déclaration qui dit l'intention. Un
    libellé de menu sur deux lignes n'est pas une dégradation acceptable — mieux
    vaut un débordement visible, qui se voit et se corrige, qu'une barre à deux
    étages qui passe pour un choix de design.
  */
  .header__nav-list a {
    padding-block: var(--sp-2);
    border-bottom: 2px solid transparent;
    font-size: var(--fs-nav);
    font-stretch: var(--display-width);
    letter-spacing: 0.02em;
    white-space: 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;
  }

  /*
    Même resserrage sur le CTA : au réglage de base (15 px, 0.06em, gouttières de
    24 px) « CHOISIR MON ASSOCIATION » réclamait 281 px et sortait sur deux
    lignes, ce qui portait le bouton à 76 px de haut et le header avec lui.
  */
  .header__cta {
    display: inline-flex;
    padding-inline: var(--sp-4);
    font-size: var(--fs-nav);
    font-stretch: var(--display-width);
    letter-spacing: 0.03em;
    white-space: nowrap;
  }
}

/* ============ 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);
}

/*
  Photo en pied de hero, pleine largeur.

  ⚠️ `aspect-ratio` ne BORNE plus rien, il RECADRE — le raisonnement s'est inversé
  avec la photo. `hero-foule-2027.jpg` est un panorama (2000x764, ratio 2,62) :
  laissée à son ratio naturel elle ne ferait que 149 px de haut sur un mobile de
  390 px, un filet trop mince pour porter le pied du hero. Le 4/3 la remonte à
  292 px, au prix de la moitié de sa largeur (il n'en reste que 51 %) — la foule
  étant uniforme, la coupe ne se voit pas. En desktop, 21/8 (2,625) tombe sur le ratio de la source : la
  photo y est servie entière.
  L'ancienne photo (2000x1125) posait le problème opposé, elle mangeait l'écran.
*/
.hero__figure {
  position: relative;
  margin: 0;
  background: var(--bg-accent);
}

.hero__figure img {
  width: 100%;
  height: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  /*
    Centré, et pas décalé comme avec l'ancienne photo : le recadrage mobile garde
    toute la hauteur (la source est plus large que 4/3) et ne joue donc que sur
    l'axe X, où le centre porte le groupe de spectateurs. Les deux extrémités sont
    les coureurs flous, la partie la moins lisible du cliché.
  */
  object-position: 50% 50%;
}

@media (min-width: 48em) {
  .hero__figure img {
    aspect-ratio: 21 / 8;
  }
}

.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;
  }
}

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

/*
  Pastille orange. Elle ne PORTE pas l'information (le texte à côté le fait) :
  l'orange n'atteint que 2,9:1 sur clair, il ne peut être que le signal visuel.
*/
.hero__note::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent-hot);
  box-shadow: 0 0 0 4px var(--glow-hot);
}

/*
  Crédit photo. « Toujours sur du sombre » était faux : il tombe en bas à droite
  de la photo du hero, où le ciel est clair. À blanc 70 % sans fond, il mesurait
  1,8:1 sur cette zone — et une `text-shadow` ne rattrape pas un fond clair, elle
  ne fait que salir les contours.

  Donc un vrai aplat sous le texte : blanc opaque sur un voile bleu nuit à 72 %,
  qui tient de 7,9:1 (sur du blanc pur) à 18:1 (sur une zone sombre) quelle que
  soit la photo. Et 12 px au lieu de 10.
*/
.credit {
  position: absolute;
  right: var(--sp-3);
  bottom: var(--sp-3);
  padding: 0.25em 0.6em;
  border-radius: var(--radius-sm);
  background: rgba(0, 14, 46, 0.72);
  color: var(--c-white);
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.04em;
}

/*
  Le crédit est un champ de CMS, souvent vide (il l'est aujourd'hui sur l'accueil).
  Vide, l'aplat s'afficherait comme une pastille orpheline sur la photo : on garde
  donc le <figcaption> dans le template — l'éditeur en a besoin pour le remplir —
  mais on ne le peint que s'il porte quelque chose.
*/
.credit:empty {
  display: none;
}

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

/* Filet en tête : il marque la couture entre la photo du hero et l'aplat bleu. */
.pricing-bar {
  background-color: var(--bg-accent-deep);
  background-image: var(--rule-brand);
  background-repeat: no-repeat;
  background-size: 100% var(--rule-h);
  background-position: top left;
  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 sur le bleu nuit : 6,0:1, donc lisible même si le lecteur ne
  distingue pas la couleur. C'est le seul chiffre en accent chaud de la page.
*/
.pricing-bar dt span {
  color: var(--accent-pink);
}

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

.figures {
  background: var(--bg-page);
  padding-block: var(--sp-12);
  border-bottom: 1px solid var(--border-light);
}

.figures__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-8) var(--sp-4);
  text-align: center;
}

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

.figure__value {
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 5vw, 2.75rem);
  font-weight: 700;
  color: var(--accent-deep);
  line-height: 1;
  letter-spacing: -0.03em;
  margin-bottom: var(--sp-2);
}

/*
  Court filet au-dessus de chaque chiffre. Le dégradé complet n'a pas de sens sur
  40 px : chaque chiffre prend UNE teinte de la rampe, et les quatre la parcourent
  de gauche à droite. Décoratif — l'information est dans le chiffre.
*/
.figure::before {
  content: "";
  display: block;
  width: 32px;
  height: 3px;
  margin: 0 auto var(--sp-3);
  background: var(--accent-bright);
}

.figure:nth-child(2)::before {
  background: var(--accent-violet);
}

.figure:nth-child(3)::before {
  background: var(--accent-pink);
}

.figure:nth-child(4)::before {
  background: var(--accent-hot);
}

.figure__label {
  font-size: var(--fs-small);
  color: var(--text-body);
}

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

.photo-band {
  height: clamp(140px, 22vw, 240px);
  background-image: url("/assets/img/band-arche-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 (~72 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;
}

/*
  Tint froid plutôt que blanc pur : la phrase est un service, c'est le logotype
  qui porte le nom. Même répartition que `.notice__body` / `.notice__title`.
*/
.platform-band__text {
  max-width: var(--measure);
  margin: 0;
  font-size: var(--fs-small);
  color: var(--text-highlight-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 rampe : bleu -> violet -> rose -> orange. La
  progression de la palette dit la progression du parcours, et c'est l'usage en
  « aplats » que décrit la charte.

  ⚠️ La couleur du chiffre change avec la pastille, elle ne peut pas être fixe :
  les deux teintes chaudes sont claires et ne portent pas du blanc. Chaque paire
  vient de tokens.css, où les contrastes sont notés. Le violet ne passe qu'en
  GRAND texte — d'où les 40px et le gras, jamais moins.
*/
.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 ============ */
/* Bleu Asics + 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-ink);
}

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

.testimonial__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /*
    La photo « verbatim » de la charte est un portrait serré (602 x 852) : le
    visage est dans le tiers haut. À 62 % le recadrage 4/3 du mobile lui coupait
    le front.
  */
  object-position: 50% 30%;
}

.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. Bleu Asics = l'accent le plus fort 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 bleu nuit : sur le blanc de la
  carte, les trois teintes chaudes de la charte ne peuvent pas porter de texte
  (rose 2,6:1) mais elles peuvent en porter un (6,0:1 dans ce sens).
*/
.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 bleu nuit, 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;
  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 : la
  charte interdit de poser orange ou violet en TEXTE sur fond clair, mais pas en
  aplat ni en pictogramme. Le texte reste bleu nuit 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 : l'orange de la charte porte la rareté. */
.asso-card--waiting-list .asso-card__status {
  --status-tint: var(--tint-orange);
  --status-hue: var(--accent-hot);
  --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-400);
  --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;
  --status-hue: var(--c-grey-300);
  --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-600);
}

/*
  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);
  font-size: var(--fs-small);
  color: var(--text-body);
  cursor: pointer;
}

.filter-check input {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  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.
  Bleu Asics -> 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);
  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);
}

/*
  Le bouton principal est bleu : sur cet aplat bleu, il disparaîtrait.
  On l'inverse — blanc sur bleu. (Le défaut existait déjà en vert sur vert,
  il ne se voyait pas.)
*/
.cta-final .btn--primary {
  background: var(--c-white);
  color: var(--accent);
}

.cta-final .btn--primary:hover {
  background: var(--accent-soft);
  color: var(--accent-deep);
}

/* ============ 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;
  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 le paragraphe
  portait quand la réponse était forcément un seul paragraphe. Sans elle, un CTA
  en pied de réponse se retrouvait décollé du texte par le retrait du paragraphe.
*/
.faq__answer {
  padding-bottom: var(--sp-6);
}

.faq__answer > * + * {
  margin-top: var(--sp-4);
}

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

/* ============ Footer ============ */
/* Bleu Asics + barre légale plus foncée. */

.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 joie court les rues » — la signature de l'édition. Trois lignes décalées et
  penchées, comme sur les barrières de la charte. Le mot en dégradé passe par le
  <em>, pas par une classe : le bloc est éditable (voir le @supports plus haut).
*/
.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 bleu Asics + 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 orange ni rose : sur cette surface ils imposeraient un
  texte bleu nuit, et un aplat chaud de cette taille hurlerait. Le bouton passe en
  `btn--white` — `btn--primary` serait du bleu sur bleu, donc invisible.
*/
.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 {
  /* Tint froid plutôt que blanc transparent : le texte doit rester net. */
  color: var(--text-highlight-on-dark);
  margin: 0 0 var(--sp-6);
}

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

.notice .btn--primary:hover {
  background: var(--accent-soft);
  color: var(--accent-deep);
}

@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;
  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);
}

.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;
  }
}

.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;
  grid-template-columns: auto 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;
    }
  }
}

/*
  ⚠️ 75em, 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–1200 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: 75em) {
  .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: 75em) {
  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: 75em) {
  /* 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;
}


/*
  ⚠️ 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.
*/
/*
  ============ 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 bleu Asics, même texte blanc, 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.

  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;
}

.notice--prelaunch .notice__title {
  font-size: var(--fs-h2);
}

.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);
}

.phone-row__country {
  flex: 0 0 auto;
  width: auto;
  min-width: 11rem;
}

.phone-row__number {
  flex: 1 1 12rem;
}

/* ============ 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 Semi, 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-cool);
    -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 moitié chaude qui passe. 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-warm);
  }
}
