/* ============================================================
   FORMULAIRES — l'habillage aux tokens
   CONTRAT « corrige » : le site relevé était hors système sur TOUTES ses pages
   (relevé § 5.3 et § 7.7) — bordure `#cbd5e0` (hors palette), rayon 3 px quand
   le système est à 8, astérisque requis en `#ff0000` pur, hauteur de champ
   40,4 px, texte du champ en `#888888`.
   Le complément § 6.6 ajoute deux écritures de plus dans l'espace adhérent :
   bouton d'envoi à rayon 7 px / graisse 500, champ de dépôt à rayon 4 px et
   bordure `rgb(206,212,218)`.

   Ici : un seul habillage, sur tokens, et sur NOS classes uniquement.

   MÉNAGE DU 22/08 (verdict du 19/08, recette É4). Le registre v1 habillait
   Fluent Forms : chaque règle de champ visait `.ff-el-*` en plus de nos
   classes, parce que le markup des formulaires était celui du plugin.
   L'arbitrage du 14/08 l'a sorti du site, le bloc T8 a rendu les six
   formulaires en markup à nous, le T8b a désinstallé le plugin. Les
   **14 sélecteurs** qui le visaient encore partent ici : 9 `.ff-el-*`,
   4 `.ff-btn-submit`, 1 `.error-text`. Aucun n'avait plus de markup à habiller
   (zéro occurrence de ces classes dans `lcdi-core` et dans le child theme,
   vérifié avant le geste) — et une règle sans porteur ne se contente pas de ne
   rien faire : elle survit aux relectures parce que personne n'ose y toucher,
   et elle brouille la lecture de ce qui sert vraiment.

   NOTE D'ACCESSIBILITÉ : le texte de champ passe de `#888888` (3,54:1) à
   `--text-body` (9,74:1). Un texte saisi doit être lisible ; le gris clair est
   réservé au `::placeholder`, qui reste au-dessus de 3:1 mais n'est jamais le
   seul porteur du libellé (le `<label>` est toujours présent).
   ============================================================ */

.lcdi-form { display: grid; gap: 1.25rem; }        /* 20 px entre groupes, relevé */

.lcdi-field { display: grid; gap: var(--space-2); }

.lcdi-label {
  font-family: var(--font-body);
  font-size: var(--type-body);
  font-weight: var(--weight-body);
  color: var(--text-body);
  margin: 0;
}

/* L'astérisque de champ requis : le rouge nu `#ff0000` (4,00:1) disparaît au
   profit du SEUL rouge d'état du système (6,45:1).

   `.lcdi-check .lcdi-required` EST AJOUTÉ AU GATE DU 18/08, et il répare un
   trou : le libellé d'une case à cocher n'est pas un `.lcdi-label` (c'est un
   `<label>` nu, posé dans un `.lcdi-check`). L'astérisque qu'il portait déjà
   sortait donc en encre de corps, indistinguable de la phrase. Une convention
   qui ne s'applique pas là où elle est écrite n'est pas une convention. */
.lcdi-label .lcdi-required,
.lcdi-check .lcdi-required {
  color: var(--state-danger);
}

.lcdi-input,
.lcdi-form select,
.lcdi-form textarea {
  /* LE CONTRÔLE DÉCLARE SON MODÈLE DE BOÎTE — CORRECTION DU 30/08, ET C'EST UN
     DÉFAUT PAYÉ AU VÉCU (constat de Julien sur la prod : les deux champs de
     « Ma connexion » sortaient du panneau blanc, en bureau comme en téléphone).

     LA CAUSE, MESURÉE AU NAVIGATEUR (Chrome 152, `/mon-espace/profil/`). Le
     thème parent ne DÉCLARE pas le modèle de boîte du site, il le fait
     HÉRITER : `*, ::before, ::after { box-sizing: inherit }` posé sur un
     `html { box-sizing: border-box }` (`kadence/assets/css/global.min.css`).
     C'est une chaîne, et une chaîne se coupe. Depuis Chrome 131, le navigateur
     insère un `::details-content` entre un `<details>` et son contenu — un
     pseudo-élément que `*` NE VISE PAS : il prend donc la valeur initiale,
     `content-box`, et tout ce qui vit dans un `<details>` en hérite.
     Relevé : colonne de 436 px, champ rendu à 470 px (436 + 2 × 16 de marge
     intérieure + 2 × 1 de filet), soit 34 px hors de sa colonne — le premier
     champ mangeait la gouttière du second, le second sortait du panneau. En
     téléphone, 17 px hors d'un panneau de 328. Les 37 autres champs de l'écran
     étaient justes : aucun ne vit dans un `<details>`.

     POURQUOI PAS `::details-content { box-sizing: inherit }`. Parce que ce
     serait réparer la chaîne d'un thème parent au lieu de s'en passer, et parce
     que ce pseudo-élément est plus jeune que le parc — même motif qu'au volet
     du rail (`member-space.css`, 17/08), où s'appuyer dessus laissait tout un
     pan d'iOS sans rien. Un contrôle déclare sa boîte, comme `.lcdi-check label`
     déclare son corps : ce qu'un composant ne déclare pas, il l'hérite d'un
     contexte qu'il ne choisit pas. */
  box-sizing: border-box;
  width: 100%;
  min-height: 48px;                      /* le champ relevé fait 40,4 px : trop court
                                            pour une cible tactile (24 px minimum WCAG
                                            2.5.8, 44 px recommandé). Porté à 48 px. */
  padding: var(--space-3) var(--space-4);
  font-family: var(--font-body);
  font-size: var(--type-body);
  color: var(--text-body);
  /* LES DEUX RÔLES DU CHAMP, ET RIEN D'AUTRE (retouche T8, 19/08). Ils basculent
     à la frontière de thème la plus proche : c'est `tokens.css` qui décide du
     monde, ce fichier ne le nomme jamais. Avant, un bloc `[data-theme="nuit"]
     .lcdi-input` en fin de fichier rhabillait le champ de nuit et traversait le
     `data-theme="jour"` du dialogue de modale — trois champs invisibles sur
     panneau blanc, 1:1. Motif complet dans `tokens.css` § --field-surface. */
  background: var(--field-surface);
  border: 1px solid var(--field-border);  /* 3,54:1 en jour : le filet a le droit de délimiter un contrôle */
  border-radius: var(--radius-m);         /* 8 px : le rayon système remplace le 3 px */
  box-shadow: none;
  transition: border-color var(--t-quick) var(--ease-brand), box-shadow var(--t-quick) var(--ease-brand);
}
.lcdi-input::placeholder { color: var(--text-soft); }

.lcdi-input:hover { border-color: var(--accent); }

.lcdi-input:focus,
.lcdi-input:focus-visible {
  outline: none;
  border-color: var(--accent-ink);
  box-shadow: var(--focus-ring);
}

.lcdi-input[aria-invalid="true"] {
  border-color: var(--state-danger);
  background: var(--state-danger-veil);
}

.lcdi-field__help { font-size: var(--type-caption); color: var(--text-body); margin: 0; }
.lcdi-field__error { font-size: var(--type-caption); color: var(--state-danger); margin: 0; }

/* Cases et boutons radio */
.lcdi-check { display: flex; align-items: flex-start; gap: var(--space-3); }
.lcdi-check input { width: 22px; height: 22px; accent-color: var(--accent); margin: 0; flex: none; }
/* LE LIBELLÉ DÉCLARE SON CORPS. Sans ça il prend celui du `<body>` : 18 px sur
   une maquette (qui charge `_base.css`), 16 px sur le site (où le child theme
   ne le charge pas). Trouvé au balayage `preuves/corps-declare.mjs`, lot 2 ;
   c'est la même classe de défaut que les 16 px payés à la main au lot 1. */
.lcdi-check label { font-size: var(--type-body); line-height: var(--lh-snug); }

/* PLUS DE BOUTON D'ENVOI ICI (ménage du 22/08). Il habillait `.ff-btn-submit`,
   le bouton de Fluent Forms : d'abord `all: unset` pour effacer le plugin, puis
   la voix CTA rejouée par-dessus. Les formulaires natifs rendent un
   `.lcdi-btn.lcdi-btn--cta` (vérifié dans les gabarits de `lcdi-core`) : c'est
   `buttons.css` qui les habille, une seule fois, pour tout le site. */

/* Zone de dépôt de fichier (espace adhérent : « Glissez/déposez ou cliquez ») */
.lcdi-dropzone {
  border: 2px dashed var(--accent);
  border-radius: var(--radius-m);
  background: var(--veil-accent);
  padding: var(--space-6);
  text-align: center;
  color: var(--text-body);
}
.lcdi-dropzone:hover { border-color: var(--accent-ink); background: var(--surface-warm); }

/* ============================================================
   LE FORMULAIRE EN GRILLE — AMENDEMENT DU BLOC T5 (15/08)
   Motif : l'édition de profil native (arbitrage FF du 14/08) porte TRENTE ET UN
   champs — identité, coordonnées, vie professionnelle, dix tailles, six
   caractéristiques de vélo, contact d'urgence. Empilés en une colonne, ils font
   quatre écrans et demi de bureau ; le membre y perd le fil et la barre
   d'enregistrement devient une récompense de fin de page.

   LA RÈGLE : un champ court (une taille, un code postal, une ville, une
   pointure) n'occupe pas une ligne entière. La grille se replie seule sous
   16 rem par colonne — donc trois colonnes dans la colonne de contenu de
   l'espace à 1440, deux en tablette, une en téléphone. Aucune requête média :
   la même règle vaut quel que soit le conteneur, y compris dans un panneau
   étroit.

   `--pleine` est l'exception explicite : une adresse, une zone de texte, un
   groupe de cases prennent toute la largeur. On la POSE, elle ne se devine pas.
   ============================================================ */

.lcdi-form--grille {
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: var(--space-4) var(--gap-grid-tight);
  align-items: start;
}
.lcdi-field--pleine { grid-column: 1 / -1; }

/* Le groupe de champs : un `<fieldset>` sans son cadre natif (que les
   navigateurs dessinent chacun à leur façon), sa légende ramenée au titre du
   système. */
.lcdi-form__groupe {
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;   /* sans lui, un `<fieldset>` refuse de rétrécir en grille */
}
.lcdi-form__legende {
  padding: 0;
  margin: 0 0 var(--space-4);
  font-family: var(--font-display);
  font-size: var(--type-h6);
  font-weight: var(--weight-strong);
  color: var(--text-strong);
}

/* L'UNITÉ D'UN CHAMP CHIFFRÉ (cm, kg, mm) : elle vit DANS le champ, à droite,
   parce que le libellé « Taille » ne dit pas si l'on attend des centimètres.
   Le site actuel l'écrivait après la valeur, en texte libre, hors du contrôle
   (« Taille : 168 cm ») — donc invisible au moment de la saisie. */
.lcdi-field--unite { position: relative; }
.lcdi-field--unite .lcdi-input { padding-right: 3.5rem; }
.lcdi-field__unite {
  position: absolute;
  right: var(--space-4);
  /* le champ fait 48 px de haut sous un libellé : on cale sur le bas du groupe
     plutôt que sur son centre, que la présence d'une aide ferait glisser */
  bottom: 0;
  height: 48px;
  display: inline-flex;
  align-items: center;
  font-size: var(--type-caption);
  color: var(--text-body);
  pointer-events: none;
}
.lcdi-field--unite:has(.lcdi-field__help) .lcdi-field__unite { bottom: auto; top: 1.75rem; }

/* L'ŒIL D'UN CHAMP SECRET — AJOUT DU 30/08 (demande de Julien sur « Mon
   profil » : « il manque un œil pour afficher/masquer le mot de passe »).

   IL N'EXISTE QUE SI LE SCRIPT EXISTE, et c'est la règle de l'écran. Le gabarit
   rend un `<input type="password">` nu ; c'est `espace-profil.js` qui pose
   l'enveloppe, le bouton et le modificateur `--secret`. Sans moteur de script,
   il n'y a pas d'œil et le champ se saisit exactement comme avant : révéler un
   mot de passe est un confort, jamais une condition de saisie.

   POURQUOI UNE ENVELOPPE PLUTÔT QUE LE PATRON DE `--unite` JUSTE AU-DESSUS.
   L'unité se cale sur le BAS du groupe (`bottom: 0`), avec un rattrapage quand
   une aide suit (`top: 1.75rem`) : deux valeurs qui supposent un libellé d'UNE
   ligne. Ici les deux libellés diffèrent (« Nouveau mot de passe », « Le même,
   une seconde fois »), une seule des deux cases porte une aide, et la grille se
   replie à 16 rem par colonne — le libellé peut passer sur deux lignes. Une
   enveloppe autour du seul contrôle donne à l'œil le repère du champ LUI-MÊME :
   il ne dépend plus de ce qu'il y a au-dessus ni en dessous.

   L'ÉTAT EST L'ATTRIBUT, PAS UNE CLASSE : `aria-pressed` dit à la fois au
   lecteur d'écran et à cette feuille lequel des deux glyphes montrer. Poser une
   classe en plus fabriquerait deux vérités dont une finirait fausse (même
   doctrine qu'`aria-busy` au bouton d'envoi). */
.lcdi-field__secret { position: relative; display: block; min-width: 0; }
/* La place du bouton, réservée dans le champ : sans elle, une saisie longue
   passerait sous l'œil. */
.lcdi-field__secret .lcdi-input { padding-right: 3.25rem; }

.lcdi-field__oeil {
  position: absolute;
  /* Collé au filet du champ, jamais dessus : 1 px de retrait sur les trois
     bords laisse la bordure et l'anneau de focus du champ entiers. */
  top: 1px;
  right: 1px;
  bottom: 1px;
  width: 3rem;              /* 48 px de cible tactile, comme la hauteur du champ */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* LES REMISES À ZÉRO DU BOUTON SE DÉCLARENT, Y COMPRIS L'OMBRE. Le thème
     parent habille TOUS ses boutons d'une ombre portée (constat du 17/08 sur la
     ligne du rail, `member-space.css`) : un bouton qui ne l'efface pas rend un
     cadre flottant sur le site et rien sur sa maquette. */
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
  font-family: inherit;
  border-radius: var(--radius-s);
  color: var(--text-body);
  cursor: pointer;
  transition: color var(--t-quick) var(--ease-brand);
}
/* LES TROIS ÉTATS SE DÉCLARENT ENSEMBLE, ET C'EST UNE MESURE QUI L'A DÉCIDÉ
   (constat du 30/08, premier rendu de l'œil au navigateur). Le thème parent
   peint TOUS ses boutons au survol, au focus ET à l'appui d'une seule règle,
   `button:hover, button:focus, button:active` (`kadence/assets/css/
   global.min.css`) : fond d'accent, encre claire, ombre portée. Sa spécificité
   est (0,1,1) — elle bat une simple classe, donc la remise à zéro du repos
   ci-dessus ne la voit même pas passer. Résultat mesuré : au premier clic,
   l'œil devenait un pavé d'accent et le pictogramme disparaissait dedans.
   `:focus` ET `:focus-visible` sont tous les deux nommés, et ce n'est pas une
   redite : après un clic à la souris, un bouton a `:focus` sans avoir
   `:focus-visible`. Ne traiter que le second laissait exactement le pavé. */
.lcdi-field__oeil:hover,
.lcdi-field__oeil:focus,
.lcdi-field__oeil:active {
  background: none;
  box-shadow: none;
  color: var(--accent-ink);
}
/* L'anneau du système reprend la main sur la remise à zéro juste au-dessus, et
   seulement là où il sert : au clavier. */
.lcdi-field__oeil:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* LE DESSIN EST DIMENSIONNÉ DIRECTEMENT, et c'est l'exception que `icon.css`
   prévoit (« les composants qui ont une mesure relevée la posent »). Ailleurs
   c'est le conteneur qui donne la taille et le SVG qui le remplit ; ici le
   conteneur fait 48 px parce que c'est une CIBLE TACTILE, pas parce que le
   pictogramme doit faire 48 px. Les deux mesures sont indépendantes. */
.lcdi-field__oeil .lcdi-icone { width: 1.35rem; height: 1.35rem; }

/* LA BARRE DE L'ŒIL SE DESSINE EN DEUX TRAITS, et le premier n'est pas un
   ornement : un trait d'encre posé sur un pictogramme de la même encre ne se
   voit pas. Le trait large reprend le sol du champ et creuse la gouttière, le
   trait fin porte l'information. Les deux partent quand le mot de passe est
   masqué : le glyphe au repos est l'œil ouvert, qui dit ce que le clic FERA. */
.lcdi-field__oeil .lcdi-icone__barre,
.lcdi-field__oeil .lcdi-icone__barre-fond { display: none; }
.lcdi-field__oeil[aria-pressed="true"] .lcdi-icone__barre,
.lcdi-field__oeil[aria-pressed="true"] .lcdi-icone__barre-fond { display: inline; }
.lcdi-field__oeil .lcdi-icone__barre-fond { fill: var(--field-surface); }

/* L'APERÇU D'UN CHAMP — AJOUT DU BLOC T5 (15/08)
   Un champ de dépôt de photo montre ce qu'il y a DÉJÀ : le portrait, ou son
   repli en monogramme, avec une phrase qui dit à quoi il sert. La maquette
   posait d'abord `.lcdi-check` pour obtenir cette rangée — un détournement :
   cette classe décrit une case à cocher et son libellé, pas un aperçu. Deux
   objets différents ne partagent pas une classe parce qu'ils se ressemblent. */
.lcdi-field__apercu {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0;
}

/* --- Formulaire posé sur nuit : PLUS AUCUNE RÈGLE ICI (retouche T8, 19/08) ---
   Ce fichier portait un bloc `[data-theme="nuit"] .lcdi-input` qui rejouait
   EXACTEMENT ce que les tokens font déjà seuls — même fond, même filet, même
   encre — au prix d'un sélecteur descendant (0,2,0) qui ne s'arrêtait à AUCUNE
   frontière de thème. Il rhabillait donc de nuit les champs d'un dialogue
   `data-theme="jour"` posé dans un voile de nuit : la modale d'inscription
   rendait trois champs à 1:1 sur son panneau blanc, invisibles tant qu'on ne
   cliquait pas dedans (trouvé au vécu par Julien, mesuré au navigateur).
   La ligne `.lcdi-label` du même bloc était, elle, une pure redite de la règle
   de haut de fichier (`color: var(--text-body)` dans les deux mondes).
   Le monde d'un champ se lit maintenant dans `--field-surface` / `--field-border`
   (`tokens.css`), qui basculent à la frontière la plus proche. Ne pas rouvrir ce
   bloc : le rouvrir, c'est reproduire le défaut. */

/* ============================================================
   LE BLOC DE CONTACT — le formulaire et son aparté
   AJOUTÉ AU BLOC 5a, VAGUE A2 (10/08). Figure relevée sur la page « Contact » :
   deux panneaux côte à côte, très inégaux — le formulaire dans un panneau de
   givre, les réseaux dans une colonne d'accent.

   RELEVÉ (live 1440, sonde `probe-5a2-live.js` du 10/08) :
     colonnes     80 / 20 (Kadence « left-golden »)
     panneau      fond palette7 (#e0eaec), rayon 8, marge intérieure moyenne
     aparté       aplat palette1 (#68a1b0), rayon 8, contenu centré verticalement
     réseaux      en colonne, 45 px, blancs
     TÉLÉPHONE    l'aparté est MASQUÉ sous 768 px (réglage du live), et le
                  panneau perd ses marges latérales pour aller bord à bord

   L'APARTÉ MASQUÉ EN TÉLÉPHONE EST REPRODUIT, ET SANS PERTE : les quatre mêmes
   réseaux sont servis par le pied de page, sur tous les écrans. Rien de ce que
   le masquage retire n'est absent de l'écran.
   ============================================================ */

.lcdi-form-bloc {
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 1fr);   /* 80 / 20, relevé */
  gap: var(--gap-grid);
  align-items: stretch;
}

/* LE BLOC SANS APARTÉ (ajouté au gate du 18/08, pour le formulaire sponsors).
   La page Partenaires n'a pas de colonne de réseaux à poser à côté de sa
   demande de partenariat : le bloc tombe sur une seule colonne et le panneau
   prend toute la largeur. Un modificateur plutôt qu'un panneau posé seul :
   `.lcdi-form-bloc__panneau` est un élément de ce bloc, il ne vit pas sans lui,
   et une classe orpheline finit toujours par perdre ses règles de repli. */
.lcdi-form-bloc--seul { grid-template-columns: minmax(0, 1fr); }

.lcdi-form-bloc__panneau {
  background: var(--surface-warm);
  border-radius: var(--radius-m);
  padding: var(--space-6);
}
.lcdi-form-bloc__panneau > *:first-child { margin-top: 0; }

.lcdi-form-bloc__aparte {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-8);                 /* le grand écart relevé entre le titre et les réseaux */
  background: var(--accent);
  border-radius: var(--radius-m);
  padding: var(--space-6) var(--space-4);
  color: var(--text-on-night);
  text-align: center;
}
.lcdi-form-bloc__aparte .lcdi-social a { color: var(--text-on-night); }
.lcdi-form-bloc__aparte .lcdi-social a:hover { color: var(--surface-warm); }

/* Le titre de l'aparté est le seul texte posé sur l'aplat d'accent : il reste
   grand et gras, jamais du corps de texte (blanc sur `--accent` = 2,87:1, sous
   le seuil du texte courant, au-dessus de celui du grand texte). */
.lcdi-form-bloc__titre {
  font-family: var(--font-display);
  font-size: var(--type-title);
  font-weight: var(--weight-strong);
  line-height: var(--lh-title);
  color: var(--text-on-night);
  margin: 0;
}

@media (max-width: 1024px) {
  .lcdi-form-bloc { grid-template-columns: minmax(0, 1fr); }
  .lcdi-form-bloc__aparte { gap: var(--space-6); padding: var(--space-6); }
}

@media (max-width: 767px) {
  .lcdi-form-bloc__aparte { display: none; }   /* réglage du live, cf. en-tête */
  /* Le panneau garde son rayon et le bord de page du système (16 px) ; le live,
     lui, le fait sortir bord à bord en téléphone. Écart assumé et signalé : nos
     sections respectent toutes `--page-edge-mobile`, une seule exception
     fabriquerait une bosse dans la colonne de lecture. */
  .lcdi-form-bloc__panneau { padding: var(--space-4); }
}

/* ============================================================
   LE FORMULAIRE NATIF — AMENDEMENT DU BLOC T8 (18/08)

   MOTIF DE NAISSANCE. Le registre v1 habillait Fluent Forms : ses règles
   visaient `.ff-el-*` autant que nos classes, parce que le markup était celui
   du plugin. L'arbitrage du 14/08 sort Fluent Forms du site — les six formulaires
   publics (candidature, renouvellement, contact, newsletter, rétractation,
   sponsors) deviennent du markup à nous. Ce qui manquait alors se voit d'un
   coup : le plugin fournissait, en plus des champs, TOUT ce qui entoure la
   saisie — le bilan d'erreurs, le message de réussite, le bouton qui s'occupe,
   le piège à robots, la case de consentement. Ces pièces-là n'ont jamais été
   dessinées ; elles étaient empruntées.

   CE QUI EST AJOUTÉ ICI, ET RIEN D'AUTRE :
     · les types de champ que le v1 ne couvrait pas (date, fichier, sélecteur,
       bouton radio) ;
     · les trois états d'un champ que la saisie réclame (erreur nommée au
       champ, éteint, occupé) ;
     · les deux messages qui encadrent un envoi (bilan d'échec, panneau de
       réussite) ;
     · le piège à robots, invisible à l'œil ET au lecteur d'écran ;
     · le sommaire d'un long formulaire, et le bloc d'engagements ;
     · la signature électronique simple.

   RIEN N'EST DUPLIQUÉ : `.lcdi-input`, `.lcdi-field`, `.lcdi-label`,
   `.lcdi-check`, `.lcdi-form--grille`, `.lcdi-field--pleine` et le bloc de
   contact ci-dessus servent tels quels. Les règles `.ff-el-*` du v1, elles,
   sont parties au ménage du 22/08 : le plugin est désinstallé depuis le T8b,
   plus aucun markup ne portait ces classes.

   ZÉRO JAVASCRIPT OBLIGATOIRE. Chaque état se lit d'un attribut porté par le
   HTML rendu au serveur (`aria-invalid`, `disabled`, `aria-busy`, `[open]`) :
   une page servie sans JS montre exactement les mêmes états. Le JS de la
   session code ne fera qu'éviter l'aller-retour, jamais révéler l'information.
   ============================================================ */

/* ------------------------------------------------------------------
   1. LES TYPES DE CHAMP QUI MANQUAIENT
   ------------------------------------------------------------------ */

/* LE SÉLECTEUR. La flèche native diffère d'un navigateur à l'autre et, sous
   Windows, elle arrive sur un aplat gris qui ne vient d'aucun token. On la
   remplace par un chevron dessiné en fond — un SVG en `data:` plutôt qu'un
   fichier : le sélecteur ne doit pas dépendre d'une requête réseau pour
   paraître complet. La couleur du chevron est `--brand-maillot-encre` écrite
   en clair parce qu'une URL de données n'accepte pas de variable CSS ; c'est
   la SEULE valeur littérale de ce fichier, et elle est nommée ici pour qu'un
   changement de palette la trouve. */
.lcdi-select,
.lcdi-form select {
  appearance: none;
  padding-right: calc(var(--space-4) + 1.5rem);
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%233c6e7c'%3E%3Cpath d='M5.293 9.707l6 6c0.391 0.391 1.024 0.391 1.414 0l6-6c0.391-0.391 0.391-1.024 0-1.414s-1.024-0.391-1.414 0l-5.293 5.293-5.293-5.293c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-4) center;
  background-size: 1.1rem 1.1rem;
}

/* LE CHAMP DE DATE. Il garde son sélecteur natif (c'est le calendrier du
   système d'exploitation, celui que la personne connaît déjà), mais son
   indicateur reprend l'encre du système : par défaut il est noir sur tous les
   navigateurs Chromium, seule tache de noir pur d'une page qui n'en a aucune. */
.lcdi-input[type="date"]::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: .75;
}
.lcdi-input[type="date"]::-webkit-calendar-picker-indicator:hover { opacity: 1; }

/* LE CHAMP DE DÉPÔT DE FICHIER.
   `.lcdi-dropzone` (registre v1) reste la zone de glisser-déposer de l'espace
   adhérent ; ce qui suit habille le contrôle `<input type="file">` LUI-MÊME,
   celui que la candidature pose deux fois pour ses photos. Le bouton natif
   « Choisir un fichier » est le seul élément d'un formulaire qui refuse
   `font-family` : on l'habille par `::file-selector-button`, la seule prise
   standard. */
.lcdi-input[type="file"] {
  padding: var(--space-2) var(--space-3);
  line-height: 2;
  cursor: pointer;
}
.lcdi-input[type="file"]::file-selector-button {
  font-family: var(--font-body);
  font-size: var(--type-caption);
  font-weight: var(--weight-strong);
  color: var(--btn-secondary-text);
  background: var(--btn-secondary-bg);
  border: 1px solid var(--btn-secondary-border);
  border-radius: var(--radius-s);
  padding: var(--space-2) var(--space-4);
  margin-right: var(--space-4);
  cursor: pointer;
  transition: background-color var(--t-quick) var(--ease-brand), color var(--t-quick) var(--ease-brand);
}
.lcdi-input[type="file"]::file-selector-button:hover {
  background: var(--btn-secondary-bg-hover);
  color: var(--btn-secondary-text-hover);
}

/* LA CONTRAINTE D'UN CHAMP DE FICHIER SE LIT AVANT LE CLIC, jamais après le
   refus. Formats acceptés, poids maximal, nombre : trois faits que la personne
   a besoin de connaître pendant qu'elle choisit son fichier, pas une fois son
   envoi rejeté. Le site actuel les gardait pour le message d'erreur. */
.lcdi-field__contrainte {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--type-fine);
  color: var(--text-body);
}
.lcdi-field__contrainte li { display: flex; align-items: center; gap: var(--space-2); }
.lcdi-field__contrainte li::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: var(--radius-round);
  background: var(--accent);
  flex: none;
}

/* LE GROUPE DE BOUTONS RADIO. Il partage l'anatomie de `.lcdi-check` (contrôle
   de 22 px, libellé qui déclare son corps) et n'ajoute qu'une chose : la mise
   en colonne, et l'écart entre deux choix. Un groupe de radios est un CHOIX
   UNIQUE : ses options s'empilent, elles ne se rangent pas en grille où l'œil
   perdrait l'ordre de lecture. */
.lcdi-radios { display: grid; gap: var(--space-3); }
.lcdi-radios--ligne { grid-auto-flow: column; justify-content: start; gap: var(--space-6); }
.lcdi-radios .lcdi-check input { border-radius: var(--radius-round); }

@media (max-width: 767px) {
  /* En téléphone, un groupe en ligne redevient une pile : trois choix côte à
     côte sur 390 px donnent des cibles de moins de 44 px. */
  .lcdi-radios--ligne { grid-auto-flow: row; gap: var(--space-3); }
}

/* ------------------------------------------------------------------
   2. LES ÉTATS D'UN CHAMP
   ------------------------------------------------------------------ */

/* L'ERREUR SE MONTRE TROIS FOIS, ET C'EST VOULU : le filet rouge (la couleur),
   le message écrit sous le champ (le texte), et le marqueur devant ce message
   (la forme). WCAG 1.4.1 interdit que la couleur soit le seul porteur ; ici
   elle est le troisième porteur, pas le premier. */
.lcdi-field__error {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  font-weight: var(--weight-medium);
}
.lcdi-field__error::before {
  content: "!";
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  margin-top: .1rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-round);
  background: var(--state-danger);
  color: var(--brand-blanc);
  font-size: var(--type-badge);
  font-weight: var(--weight-title);
  line-height: 1;
}

/* Le libellé d'un champ en erreur se renforce lui aussi : sur un formulaire de
   trente champs, un filet rouge à mi-hauteur d'écran ne se voit pas si le
   libellé au-dessus reste identique à ses vingt-neuf voisins. */
.lcdi-field--erreur > .lcdi-label { color: var(--state-danger); font-weight: var(--weight-strong); }

/* L'ÉTAT ÉTEINT — même doctrine qu'au bureau (`admin.css`, 18/08) : le champ
   est `disabled` POUR DE VRAI, jamais seulement pâli. Un champ gris qui reste
   focalisable au clavier est un demi-mensonge, et c'est la personne qui
   navigue à la tabulation qui le paie. Le contraste réduit est légitime ici :
   WCAG 1.4.3 exempte explicitement les composants inactifs. */
.lcdi-input:disabled,
.lcdi-form select:disabled,
.lcdi-form textarea:disabled {
  background: var(--surface-page);
  border-color: var(--border-soft);
  color: var(--text-muted);
  cursor: not-allowed;
}
.lcdi-input:disabled:hover,
.lcdi-form select:disabled:hover,
.lcdi-form textarea:disabled:hover { border-color: var(--border-soft); }
.lcdi-check input:disabled { cursor: not-allowed; }
/* Éteint, ce qui DÉCRIT le champ s'efface avec lui : une étiquette restée
   franche au-dessus d'un champ mort se lit comme une consigne à suivre. */
.lcdi-field:has(.lcdi-input:disabled) > .lcdi-label,
.lcdi-field:has(.lcdi-input:disabled) > .lcdi-field__help,
.lcdi-check:has(input:disabled) label { opacity: .55; }

/* ------------------------------------------------------------------
   3. LES DEUX MESSAGES QUI ENCADRENT UN ENVOI
   ------------------------------------------------------------------ */

/* LE BILAN D'ÉCHEC. Il vit EN TÊTE du formulaire, il compte les champs à
   reprendre, et il les liste en liens d'ancre : sur la candidature, l'erreur
   peut être à quatre écrans de défilement du bouton d'envoi. Un message qui dit
   « des champs sont en erreur » sans dire lesquels ni où oblige la personne à
   relire trente champs.
   `role="alert"` fait annoncer le bilan par le lecteur d'écran dès qu'il
   paraît ; `tabindex="-1"` permet au serveur d'y poser le focus au retour. */
/* SON FOND EST OPAQUE, ET C'EST UNE MESURE QUI L'A DÉCIDÉ. Posé sur le voile
   d'alerte (`--state-danger-veil`), le bilan héritait du fond de son panneau :
   dans le panneau de givre de la page Contact, ses liens tombaient à 4,07:1
   (sonde du 18/08) — sous le seuil AA, pour la seule raison qu'un voile
   transparent prend la couleur de ce qu'il y a dessous. Un composant d'alerte
   ne peut pas voir son contraste dépendre de l'endroit où on le pose.
   Le fond surélevé le rend constant (liens à 5,61:1 partout), et le filet
   d'alerte, lui, porte l'information : couleur, épaisseur à gauche, et le
   titre rouge au-dessus. */
.lcdi-form__bilan {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-m);
  border: 1px solid var(--state-danger);
  border-left-width: 6px;
  background: var(--surface-raised);
  color: var(--text-body);
}
.lcdi-form__bilan:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* LES DEUX SÉLECTEURS CI-DESSOUS PASSENT PAR LEUR PARENT, ET CE N'EST PAS UN
   TIC D'ÉCRITURE. Le bilan vit aussi DANS un panneau de prose (page « Droit de
   rétractation »), et `prose.css` y habille les éléments : `.lcdi-prose p`
   (0,1,1) l'emportait sur `.lcdi-form__bilan-titre` (0,1,0), et le titre
   d'alerte rendait du gris de corps au lieu du rouge d'état — 9,74:1, donc
   parfaitement lisible, donc invisible à une sonde de contraste. C'est un œil
   sur la capture 1440 du 18/08 qui l'a vu.
   `.lcdi-form__bilan > …` monte à (0,2,0) et reprend la main sans dépendre de
   l'ordre de chargement des deux feuilles. */
.lcdi-form__bilan > .lcdi-form__bilan-titre {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--type-h6);
  font-weight: var(--weight-strong);
  color: var(--state-danger);
}
.lcdi-form__bilan > .lcdi-form__bilan-liste {
  margin: 0;
  padding-left: var(--space-5);
  display: grid;
  gap: var(--space-1);
  font-size: var(--type-caption);
  color: var(--text-body);
}
.lcdi-form__bilan-liste a { color: var(--link); text-decoration: underline; }
.lcdi-form__bilan-liste a:hover { color: var(--link-hover); }

/* LE PANNEAU DE RÉUSSITE. Il REMPLACE le formulaire, il ne se glisse pas
   au-dessus : c'est le comportement du site actuel (`samePageFormBehavior:
   hide_form`) et c'est le bon — un formulaire encore là après un envoi réussi
   invite à renvoyer.
   Il porte ce que la personne veut savoir dans cet ordre : c'est parti, voici
   ce qui se passe ensuite, voici quand. */
.lcdi-form__succes {
  display: grid;
  gap: var(--space-4);
  justify-items: start;
  padding: var(--space-6);
  border-radius: var(--radius-m);
  border: 1px solid var(--state-success);
  background: var(--state-success-veil);
}
.lcdi-form__succes--centre { justify-items: center; text-align: center; }
.lcdi-form__succes-marque {
  width: 3rem;
  height: 3rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-round);
  background: var(--state-success);
  color: var(--brand-blanc);
  flex: none;
}
.lcdi-form__succes-marque svg { width: 1.5rem; height: 1.5rem; fill: currentColor; }
.lcdi-form__succes-titre {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--type-title);
  font-weight: var(--weight-strong);
  line-height: var(--lh-title);
  color: var(--text-strong);
}
.lcdi-form__succes p { margin: 0; color: var(--text-body); }
.lcdi-form__succes p + p { margin-top: var(--space-3); }

/* ============================================================
   L'ANCRE DU VERDICT — correction 4 du lot 8 (25/08, constat de Julien sur
   /partenaires/) : « la soumission recharge la page en haut ; l'avis est en
   bas, invisible sans re-scroller ».

   LES DEUX MOITIÉS DU GESTE. Les formulaires posaient DÉJÀ le dièse dans leur
   `action` (`…/partenaires/#sponsors`, `…/adherer/#adhesion`) : le navigateur
   cherchait donc bien une ancre au retour du POST — et ne trouvait RIEN, parce
   qu'aucun élément du document ne portait cet identifiant. Le module pose
   maintenant l'ancre (`Rendu::ancre()`), et cette règle-ci lui donne son
   dégagement. L'une sans l'autre ne sert à rien : sans identifiant la page
   reste en haut, sans marge la ligne visée se range SOUS l'en-tête collant.

   LA VALEUR EST CELLE, MESURÉE, DE L'ANCRE DE CALENDRIER (`calendar.css`,
   14/08) : `--space-9` seul tombait à un pixel près sur le bas de l'en-tête
   collant (104 contre 105, constaté à 390 comme à 1440) ; un cran de plus porte
   la marge à 128 px et dégage 23 px. Même en-tête, même contrainte, même
   nombre — le réécrire ailleurs le ferait diverger.
   `--header-h` ne peut pas être lu ici : il est déclaré SUR `.lcdi-header`.

   CE QUE CETTE MARGE GOUVERNE, ET CE QU'ELLE NE GOUVERNE PAS — mesuré au
   navigateur le 25/08, et il vaut mieux l'écrire que le découvrir :
     · défilement natif vers un dièse DANS le document déjà chargé, et tout
       `scrollIntoView()` : la marge s'applique (haut de l'ancre à 128 px) ;
     · ARRIVÉE SUR UNE URL QUI PORTE DÉJÀ LE DIÈSE (le cas d'un retour de POST) :
       le navigateur l'applique d'abord — relevé à t≈1 s, haut de l'ancre à
       128 px — puis le thème PARENT reprend la main. `kadence/assets/js/
       navigation.min.js` § `initAnchorScrollTo` : 100 ms après le chargement,
       il rejoue le défilement lui-même (`scrollToElement`) avec SON propre
       décalage, calculé sur `#main-header .kadence-sticky-header` — un élément
       que ce site n'a plus, donc un décalage de 0. Relevé à t≈1,6 s : haut de
       l'ancre à 0. L'avis reste ENTIÈREMENT visible (l'en-tête, lui, se replie
       au défilement descendant), mais la marge n'est pas ce qui l'a placé.
   Le levier `--form-ancre-marge` reste le point de reprise ; reprendre la main
   sur le parent est une décision de site (classe `no-anchor-scroll` sur le
   `<body>`), pas une décision de composant : elle est portée au rapport du
   lot 8, pas prise ici.

   L'ANCRE PEUT ÊTRE PORTÉE PAR LE FORMULAIRE, PAR SON PANNEAU DE RÉUSSITE (qui
   le remplace, donc reprend son identifiant) OU PAR LA SECTION QUI L'ABRITE —
   c'est le cas de `<section id="demande">` sur `/mon-renouvellement/`, dont
   l'ancre existait déjà et arrivait sous l'en-tête. Les trois portes se règlent
   ici, une seule fois. */
.lcdi-form[id],
.lcdi-form__succes[id],
[id]:has( > .lcdi-form, > .lcdi-form__succes ) {
  scroll-margin-top: var(--form-ancre-marge, calc(var(--space-9) + var(--space-5)));
}

/* LA SUITE, EN TROIS TEMPS. Un envoi réussi ouvre une attente ; la dire est
   moins cher qu'un courriel de relance. Reprend l'anatomie du parcours de
   renouvellement (`renewal-situation.css`), sans en dépendre : les deux
   composants vivent dans des pages différentes. */
.lcdi-form__suite {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-3);
  counter-reset: lcdi-suite;
  text-align: left;
  justify-items: start;
  width: 100%;
}
.lcdi-form__suite li {
  counter-increment: lcdi-suite;
  display: grid;
  grid-template-columns: 1.75rem 1fr;
  gap: var(--space-3);
  align-items: start;
  font-size: var(--type-caption);
  color: var(--text-body);
}
.lcdi-form__suite li::before {
  content: counter(lcdi-suite);
  width: 1.75rem;
  height: 1.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-round);
  background: var(--surface-raised);
  border: 1px solid var(--state-success);
  color: var(--state-success);
  font-weight: var(--weight-strong);
  font-size: var(--type-fine);
}

/* ------------------------------------------------------------------
   4. LE GESTE D'ENVOI
   ------------------------------------------------------------------ */

.lcdi-form__envoi {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  margin: 0;
}

/* LE BOUTON OCCUPÉ. `aria-busy` est l'attribut, pas une classe : c'est lui que
   la technologie d'assistance lit, et poser une classe en plus fabriquerait
   deux vérités dont une finirait fausse. Le bouton reste LISIBLE pendant
   l'attente (contraste inchangé) ; ce qui change est qu'il n'accepte plus le
   clic et que sa pastille tourne. */
.lcdi-btn__attente { display: none; }
.lcdi-btn[aria-busy="true"] { pointer-events: none; }
.lcdi-btn[aria-busy="true"] .lcdi-btn__attente {
  display: inline-block;
  width: 1rem;
  height: 1rem;
  flex: none;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: var(--radius-round);
  animation: lcdi-tourne 700ms linear infinite;
}

@keyframes lcdi-tourne { to { transform: rotate(360deg); } }

/* MOUVEMENT RÉDUIT : la pastille cesse de tourner et devient un anneau plein.
   L'information « ça travaille » reste, l'animation part. */
@media (prefers-reduced-motion: reduce) {
  .lcdi-btn[aria-busy="true"] .lcdi-btn__attente {
    animation: none;
    border-top-color: currentColor;
  }
}

/* ------------------------------------------------------------------
   5. LE PIÈGE À ROBOTS
   ------------------------------------------------------------------ */

/* LE HONEYPOT. Un champ vide qu'un humain ne voit jamais et qu'un robot
   remplit : s'il revient rempli, l'envoi est jeté. C'est l'anti-spam maison de
   l'architecture, et il remplace le champ `item_XX__fluent_sf` que Fluent Forms
   posait sur chaque formulaire du site (relevé au navigateur le 18/08).

   IL EST CACHÉ QUATRE FOIS, ET AUCUNE N'EST DE TROP :
     · `position: absolute` hors de l'écran — mais un lecteur d'écran le lirait
       encore ;
     · `aria-hidden` (posé sur le markup) le retire de l'arbre d'accessibilité ;
     · `tabindex="-1"` (markup) le retire du parcours au clavier ;
     · `autocomplete="off"` (markup) empêche le navigateur de le remplir seul —
       le piège se déclencherait sur un humain, ce qui est le seul défaut
       impardonnable d'un piège.
   `display: none` serait plus court ET INSUFFISANT : une partie des robots
   ignore les champs non affichés, et le piège ne prendrait plus rien. */
.lcdi-honeypot {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ------------------------------------------------------------------
   6. LE CONSENTEMENT ET LES ENGAGEMENTS
   ------------------------------------------------------------------ */

/* LA MENTION DE CONSENTEMENT. Elle accompagne la case, elle ne la remplace
   pas : la case porte la phrase courte (« j'accepte que… »), la mention porte
   le renvoi vers la politique de confidentialité et la durée de conservation.
   Corps réduit, encre du corps — jamais `--text-soft`, qui tomberait à 3,54:1. */
.lcdi-form__mention {
  margin: 0;
  font-size: var(--type-fine);
  color: var(--text-body);
}
.lcdi-form__mention a { color: var(--link); text-decoration: underline; }
.lcdi-form__mention a:hover { color: var(--link-hover); }

/* LE BLOC D'ENGAGEMENTS — onze cases sur la candidature, dix sur le
   renouvellement. Empilées à l'écart d'un champ ordinaire (20 px), elles se
   lisent comme une liste de courses ; ici chaque engagement est une ligne à
   part entière, séparée d'un filet, parce que cocher est un acte et que onze
   actes méritent onze arrêts.
   FOND SURÉLEVÉ, PAS DE CADRE SUPPLÉMENTAIRE : le bloc se distingue du reste du
   formulaire sans fabriquer une boîte dans une boîte (il vit déjà dans un
   panneau). */
.lcdi-engagements {
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-m);
  background: var(--surface-raised);
  list-style: none;
}
.lcdi-engagements > li { padding: var(--space-4); }
.lcdi-engagements > li + li { border-top: 1px solid var(--border-soft); }
.lcdi-engagements > li:has(input:checked) { background: var(--veil-accent); }
.lcdi-engagements .lcdi-check label { font-size: var(--type-caption); }
/* Une case obligatoire non cochée dans un bloc de onze : le filet gauche dit
   laquelle, sans attendre que l'œil compare onze lignes. */
.lcdi-engagements > li.lcdi-engagements__manquant {
  background: var(--state-danger-veil);
  box-shadow: inset 4px 0 0 var(--state-danger);
}

/* L'OBLIGATION D'UNE CASE SE VOIT AU REPOS, PAS AU REFUS (gate du 18/08).
   Constat de Julien sur les maquettes : rien ne montrait que ces cases sont
   obligatoires. Elles portaient bien `required` et `aria-required` — un lecteur
   d'écran l'entendait, l'œil ne le voyait pas. La règle actée : TOUTES les
   cases d'engagement sont obligatoires, sauf « Je souhaite obtenir une
   facture… ».

   TROIS MARQUES, ET AUCUNE NE REMPLACE LES DEUX AUTRES :
     · l'astérisque rouge sur chaque case obligatoire, la convention déjà posée
       sur les champs, rendue visible ici par la règle du haut de fichier ;
     · la mention « (facultatif) » sur la seule case qui l'est, ci-dessous ;
     · la phrase d'intro du bloc, qui dit la règle en français.
   Une marque seule se rate : l'astérisque suppose qu'on connaisse la
   convention, la mention ne parle que d'une ligne sur onze, la phrase se lit
   une fois et s'oublie.

   ELLE NE SE CONFOND PAS AVEC L'ÉTAT D'ERREUR, et les deux se lisent ensemble :
   au repos, un glyphe de 15 px en bout de phrase ; en erreur, tout le bloc de
   la ligne change (voile rouge, filet de 4 px à gauche, message nommé sous la
   case). L'astérisque reste rouge sur le voile, il ne devient pas illisible
   quand la ligne rougit — mesuré par la sonde, qui compte cette paire comme
   n'importe quelle autre. */

/* LA MENTION « (FACULTATIF) ». Elle ne peut pas emprunter `.lcdi-field__help` :
   dans un bloc d'engagements, l'aide de saisie et le libellé ont déjà le MÊME
   corps (`--type-caption`), la mention se fondrait dans la phrase — l'inverse
   exact de ce qu'on lui demande. Elle descend d'un cran (`--type-fine`) et
   remonte en graisse : plus petite ET plus dense que le texte qu'elle suit,
   donc lue comme une étiquette, jamais comme la fin de la phrase. */
.lcdi-check__facultatif {
  font-size: var(--type-fine);
  font-weight: var(--weight-strong);
  color: var(--text-body);
  white-space: nowrap;
}

/* LA SIGNATURE ÉLECTRONIQUE SIMPLE (article 1367 du Code civil). Deux champs de
   nom et une phrase : c'est la forme du site actuel, conservée. Le bloc est
   isolé sur un aplat chaud pour qu'on ne le remplisse pas machinalement à la
   suite du reste. */
.lcdi-form__signature {
  display: grid;
  gap: var(--space-4);
  padding: var(--space-5);
  border-radius: var(--radius-m);
  background: var(--surface-warm);
}

/* ------------------------------------------------------------------
   7. LE SOMMAIRE D'UN LONG FORMULAIRE
   ------------------------------------------------------------------ */

/* POURQUOI IL EXISTE. La candidature porte six groupes, une trentaine de
   champs et onze engagements. Le site actuel la découpe en SEPT ÉTAPES avec
   une barre de progression (constat navigateur du 18/08 : sept panneaux, titres
   « Step 1 » à « Step 7 » restés en anglais, aucune persistance déclarée —
   `enable_step_data_persistency: no`). Le découpage rassure ; l'absence de
   persistance fait perdre toute la saisie au moindre incident, et c'est le
   téléphone qui paie.

   CE SOMMAIRE GARDE LE BÉNÉFICE ET LAISSE LE RISQUE : la personne voit les six
   groupes, sait où elle en est et combien il reste, sur UNE page qui ne perd
   rien. Il colle au défilement au-dessus de 1024 px ; en dessous il redevient
   une simple liste en tête de formulaire, parce qu'un rail collant sur 390 px
   mange l'écran qu'il devrait servir.

   IL EST UNE NAVIGATION D'ANCRES, PAS UN ÉTAT DE MACHINE : chaque entrée est un
   lien vers un `<fieldset>` de la même page. Sans JavaScript, il fonctionne. */
.lcdi-form__sommaire {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-5);
  border-radius: var(--radius-m);
  background: var(--surface-warm);
  /* IL DÉCLARE SON ALIGNEMENT, et ce n'est pas une précaution : la section
     « Formulaire » du gabarit `centre` centre tout son contenu, sommaire
     compris. Une liste de navigation centrée fait danser le début de chaque
     ligne, et c'est justement le début de ligne qu'on parcourt des yeux quand
     on cherche où l'on en est. Constaté à la capture 1440 du 18/08. */
  text-align: left;
}
.lcdi-form__sommaire-titre {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--type-h6);
  font-weight: var(--weight-strong);
  color: var(--text-strong);
}
.lcdi-form__sommaire ol {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-2);
  counter-reset: lcdi-etape;
}
.lcdi-form__sommaire li { counter-increment: lcdi-etape; }
.lcdi-form__sommaire a {
  display: grid;
  grid-template-columns: 1.6rem 1fr;
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-2) var(--space-2) var(--space-2) 0;
  font-size: var(--type-caption);
  color: var(--text-body);
  text-decoration: none;
  border-radius: var(--radius-s);
}
.lcdi-form__sommaire a::before {
  content: counter(lcdi-etape);
  width: 1.6rem;
  height: 1.6rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-round);
  border: 1px solid var(--border-accent);
  background: var(--surface-raised);
  color: var(--accent-ink);
  font-weight: var(--weight-strong);
  font-size: var(--type-fine);
}
.lcdi-form__sommaire a:hover { color: var(--link-hover); text-decoration: underline; }
.lcdi-form__sommaire a:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* Un groupe qui porte une erreur se signale DANS le sommaire : c'est la seule
   pièce visible quand l'erreur est à trois écrans plus bas. */
.lcdi-form__sommaire a[data-erreur]::before {
  content: "!";
  border-color: var(--state-danger);
  background: var(--state-danger);
  color: var(--brand-blanc);
}
.lcdi-form__sommaire a[data-erreur] { color: var(--state-danger); font-weight: var(--weight-strong); }

/* LA COLONNE. Au-dessus de 1024 px, le sommaire prend le tiers gauche et colle ;
   le formulaire garde les deux tiers. En dessous, une seule colonne, et le
   sommaire repasse au-dessus, sans collage. */
.lcdi-form-long {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2.2fr);
  gap: var(--gap-grid);
  align-items: start;
}
.lcdi-form-long > .lcdi-form__sommaire { position: sticky; top: var(--space-5); }

@media (max-width: 1024px) {
  .lcdi-form-long { grid-template-columns: minmax(0, 1fr); }
  .lcdi-form-long > .lcdi-form__sommaire { position: static; }
}

/* ------------------------------------------------------------------
   8. LE GROUPE DE CHAMPS, POSÉ
   ------------------------------------------------------------------ */

/* Un groupe de la candidature est un `<fieldset>` qui porte son numéro, son
   titre et, quand il en faut une, sa phrase d'explication. `.lcdi-form__groupe`
   (registre T5) portait déjà le fieldset nu et sa légende ; ce qui suit ajoute
   l'espacement entre deux groupes et le rappel de rang, sans toucher au v1. */
.lcdi-form__groupes { display: grid; gap: var(--space-7); }

/* LA LÉGENDE RESTE UN ENFANT DIRECT DU `<fieldset>`, et ce n'est pas
   négociable : glissée dans une enveloppe, elle cesse d'être la légende du
   groupe pour le navigateur comme pour le lecteur d'écran, et redevient un
   titre parmi d'autres. Le trait de séparation est donc porté par ELLE, pas par
   une boîte d'en-tête. */
.lcdi-form__groupe > .lcdi-form__legende {
  display: block;
  float: none;
  width: 100%;
  padding: 0 0 var(--space-3);
  margin: 0 0 var(--space-5);
  border-bottom: 1px solid var(--border-soft);
}
/* Une phrase d'explication remonte contre sa légende : le trait doit rester
   sous les deux, sinon il sépare le groupe de sa propre consigne. */
.lcdi-form__groupe > .lcdi-form__legende:has(+ .lcdi-form__groupe-intro) {
  margin-bottom: var(--space-3);
  padding-bottom: 0;
  border-bottom: 0;
}
.lcdi-form__groupe-intro {
  margin: 0 0 var(--space-5);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-soft);
  font-size: var(--type-caption);
  color: var(--text-body);
}
/* Le rang du groupe : il répond au sommaire, et il ne dit rien de plus. */
.lcdi-form__rang {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  margin-right: var(--space-3);
  border-radius: var(--radius-round);
  background: var(--accent-ink);
  color: var(--brand-blanc);
  font-family: var(--font-body);
  font-size: var(--type-fine);
  font-weight: var(--weight-strong);
  vertical-align: 2px;
}

/* LE RAPPEL D'IDENTITÉ d'un membre connecté (renouvellement) reprend
   `.lcdi-evt-identite` (bloc T6b) plutôt que d'en fabriquer un jumeau : même
   objet, même pose, une seule feuille à corriger. */

/* ------------------------------------------------------------------
   9. LE FORMULAIRE POSÉ SUR UNE SCÈNE
   ------------------------------------------------------------------ */

/* La section « Formulaire » du gabarit `centre` pose la candidature SUR une
   photo voilée (relevé : `/adherer/`, section 3, image de fond + voile de
   club). Le texte d'accompagnement y est blanc ; les champs, eux, restent des
   champs — fond surélevé, encre du corps. Ce qu'il faut, c'est un panneau qui
   REPREND la lumière du jour au milieu d'une bande de nuit, sinon les libellés
   héritent du blanc de la scène et disparaissent sur le blanc des champs. */
.lcdi-form-scene {
  background: var(--surface-raised);
  border-radius: var(--radius-m);
  padding: var(--space-6);
  box-shadow: var(--shadow-soft);
  text-align: left;
}
.lcdi-form-scene > *:first-child { margin-top: 0; }
.lcdi-form-scene > *:last-child { margin-bottom: 0; }

@media (max-width: 767px) {
  .lcdi-form-scene { padding: var(--space-4); }
  .lcdi-form__sommaire { padding: var(--space-4); }
  .lcdi-form__signature { padding: var(--space-4); }
  .lcdi-engagements > li { padding: var(--space-3); }
}
