/* ============================================================
   DESIGN SYSTEM Cercles : variables uniques, remplies depuis DESIGN.md
   (« trois rubans qui filent », refonte du 09/10/2026).
   Règle : AUCUNE couleur/taille/police en dur dans le reste du CSS.
   ============================================================ */
:root {
  color-scheme: light;

  /* Couleurs (DESIGN.md §4) : trois, et rien d'autre. Le blanc de la page, le noir du
     texte, des filets et des rubans, le bleu Klein de ce qu'on peut faire. Ni gris, ni
     dégradé : `npm run verif:couleurs` refuse toute autre valeur dans cette feuille. */
  --couleur-fond: #ffffff;
  --couleur-encre: #000000;
  --couleur-accent: #002fa7;

  /* Alias de sens, pour que le code se lise */
  --couleur-texte: var(--couleur-encre);
  --couleur-texte-sur-sombre: var(--couleur-fond);
  --couleur-accent-contraste: var(--couleur-fond);
  /* Le trait de CONTEXTE : anneau d'un portrait, glyphe d'un état d'écran, contour d'un
     badge, dessin d'un point d'état. Noir partout depuis que le panneau noir de l'accueil
     est devenu un cadre de verre (10/10/2026) : le jeton reste, il nomme ce trait. */
  --couleur-anneau-portrait: var(--couleur-encre);

  /* Le verre (DESIGN.md §4 et §5). L'opacité n'existe QUE dans ces jetons : le fond des
     capsules de verre, leur reflet, leur contour et leur ombre portée. Un texte, un fond
     de page, un filet, une bordure sont toujours à 1. Le blanc reste très opaque (0,92)
     pour que le verre ne vire pas au gris au-dessus des rayures. Le verre du CADRE des
     experts, lui, laisse voir le ruban qui passe derrière (0,6) : c'est un grand panneau
     flouté, pas une capsule. Ce ruban a une face bleue et une face rayée de noir : sous
     le flou les rayures se fondent, c'est le verre dépoli que Matthias a demandé
     (10/10/2026), et la seule exception au « rien n'est gris » (DESIGN.md §4). Le texte
     noir y garde 7,37:1 à la borne (du noir plein derrière, que des rayures floutées
     n'atteignent pas) et plus de 9:1 mesuré. */
  --verre-blanc: rgba(255, 255, 255, 0.92);
  --verre-bleu: rgba(0, 47, 167, 0.88);
  --verre-noir: rgba(0, 0, 0, 0.86);
  --verre-panneau: rgba(255, 255, 255, 0.6);
  --reflet-verre-blanc: inset 0 1.5px 0 #ffffff;
  --reflet-verre-bleu: inset 0 1.5px 0 rgba(255, 255, 255, 0.6);
  --reflet-verre-noir: inset 0 1.5px 0 rgba(255, 255, 255, 0.45);
  --contour-verre-blanc: inset 0 0 0 1.5px rgba(0, 0, 0, 0.9);
  --contour-verre-bleu: inset 0 0 0 1px rgba(255, 255, 255, 0.25);
  --contour-verre-noir: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
  --ombre-verre-blanc: 0 8px 22px rgba(0, 0, 0, 0.16);
  --ombre-verre-bleu: 0 10px 26px rgba(0, 47, 167, 0.35);
  --ombre-verre-noir: 0 8px 22px rgba(0, 0, 0, 0.25);
  /* Le flou n'existe que là où quelque chose passe derrière : le header, les boutons du
     hero et la capsule « prochain rendez-vous », posés sur les rubans. */
  --flou-verre: blur(6px) saturate(160%);
  /* Le flou du cadre des experts : plus fort que celui d'une capsule, parce qu'un ruban
     de 560 px de haut flouté de 6 px ne se lit pas comme flouté. */
  --flou-panneau: blur(9px) saturate(160%);

  /* Typographie (DESIGN.md §3) : une seule famille, Geist d'abord sur tous les systèmes,
     trois graisses. Le texte secondaire n'est pas gris : il est noir, plus petit, en 400. */
  --police: "Geist", system-ui, -apple-system, "Segoe UI", sans-serif;
  --taille-xs: 0.8125rem;
  --taille-s: 0.9375rem;
  --taille-base: 1.0625rem;
  --taille-m: clamp(1.1875rem, 1.6vw, 1.375rem);
  --taille-l: 1.25rem;
  --taille-xl: 1.5rem;
  --taille-xxl: clamp(2.25rem, 4.2vw, 3.75rem);
  --taille-hero: clamp(3.125rem, 7.4vw, 7rem);
  --taille-bouton-grand: 1.1875rem;
  /* Le besoin auquel répond une durée, sur l'accueil : une phrase de trois mots, grande. */
  --taille-besoin: clamp(2.5rem, 7vw, 5.5rem);
  --taille-marque-texte: 1.375rem;
  --taille-numero: 1.625rem;
  --graisse-normale: 400;
  --graisse-moyenne: 500;
  --graisse-titre: 600;
  /* DESIGN.md §3 donne un interlettrage ET un interligne PAR NIVEAU de titre, et les
     deux échelles ne se suivent pas. La règle `h1, h2, h3` porte les valeurs du h2 et
     chaque niveau reprend plus bas le seul jeton où il s'en écarte : un jeton par valeur
     du tableau, jamais une valeur en dur hors de ce bloc. */
  --interlettrage-hero: -0.045em;
  --interlettrage-serre: -0.035em;
  --interlettrage-titre-h2: -0.02em;
  --interlettrage-titre-h3: -0.01em;
  --interlettrage-marque: -0.02em;
  --interligne-texte: 1.5;
  --interligne-accroche: 1.45;
  --interligne-hero: 0.98;
  --interligne-titre: 1.15;
  --interligne-titre-h1: 1.05;
  --interligne-titre-h3: 1.2;

  /* Espacements */
  --espace-xs: 0.25rem;
  --espace-s: 0.5rem;
  --espace-m: 1rem;
  --espace-l: 2rem;
  --espace-xl: 4rem;
  --espace-xxl: 7rem;
  --espace-section-accueil: clamp(5rem, 10vw, 8.125rem);
  /* La marge haute du header : la capsule de verre est posée à 22 px du haut. */
  --espace-nav: 1.375rem;
  /* La réserve intérieure de la capsule segmentée (6 px dans la maquette), et la réserve
     latérale d'un segment : celle d'un bouton là où la place abonde, et fluide en dessous.
     Mesuré avec les trois durées : à 22 px fixes la capsule demandait 319 px, plus que la
     colonne d'un écran de 320 px (272 px), dont elle débordait. */
  --espace-reserve-segments: 0.375rem;
  --padding-segment: clamp(0.5rem, 4vw, 1.375rem);
  /* Les gouttières fluides du shell : celle du header et du hero (la même, pour que la
     capsule et l'accroche partagent leur colonne), celle du pied de page, celle de la
     colonne des sections de l'accueil ; et l'écart entre deux entrées du header.
     Celle du pied de page ne descend pas sous celle des colonnes de contenu : sur petit
     écran il s'aligne sur elles, au lieu de coller au bord. */
  --gouttiere-shell: clamp(1rem, 4vw, 3rem);
  --gouttiere-pied: clamp(1.5rem, 3vw, 2.5rem);
  /* Les colonnes du pied de page : la présentation, puis trois listes de liens. */
  --colonne-pied-presentation: 2fr;
  --colonne-pied-liens: 1fr;
  --largeur-pied-presentation: 22rem;
  /* La gouttière des colonnes de contenu (`.colonne`, `.page`). Sous 48rem elle passe à
     1,5rem (bloc mobile du shell) : avec le texte de 17 px, la colonne de 311 px d'un
     écran de 375 px ne tenait plus « Reporter » et « Annuler le rendez-vous » sur un
     rang (315 px mesurés) ; à 327 px elle les tient. */
  --gouttiere-page: 2rem;
  --gouttiere-accueil: clamp(0.75rem, 2vw, 1.5rem);
  --retrait-etapes: clamp(0.25rem, 2vw, 2rem);
  --ecart-nav: clamp(0.5rem, 2.2vw, 2rem);
  /* La recherche du header : elle prend la place libre de la capsule, sans la remplir. */
  --largeur-recherche-nav: 24rem;
  --largeur-recherche-nav-min: 8rem;

  /* Rayons (DESIGN.md §5) : tout est arrondi, aucun angle vif nulle part. */
  --rayon-carte: 1.5rem;
  --rayon-carte-grande: 2rem;
  --rayon-panneau: clamp(2.5rem, 6.5vw, 6rem);
  --rayon-pilule: 999px;
  --rayon-case: 0.3rem;

  /* Boutons */
  --hauteur-bouton: 2.75rem;
  --hauteur-bouton-grand: 4rem;
  --padding-bouton: 1.375rem;
  --padding-bouton-grand: 2.125rem;
  --taille-bouton-rond: 3.25rem;
  /* De combien le bouton rond au combiné déborde du portrait, en bas à droite. */
  --debord-decrocher: -0.375rem;
  --epaisseur-trait-icone: 2;

  /* Dimensions */
  /* La boîte du logo dans le header. Le dessin de `logo.svg` laisse au halo de son point
     12 unités sur 67 : dans 2rem, l'anneau mesure 26 px, la taille que DESIGN.md §7 lui
     donne (run DA-0, décision 3 ; mesure dans FEATURES.md § Refonte visuelle, run DA-1). */
  --taille-marque: 2rem;
  --taille-case: 1.125rem;
  /* La croix d'une langue choisie, et la hauteur de ses suggestions (huit lignes). */
  --taille-croix: 0.625rem;
  /* La hauteur d'une bulle du header sur grand écran : un peu plus haute qu'un bouton. */
  --hauteur-bulle: 3.25rem;
  /* La flottaison d'une bulle du header : 3 px de haut en bas, en cinq secondes, chaque
     bulle décalée de la précédente. */
  --bulle-flotte: -3px;
  --duree-bulle: 5s;
  --decalage-bulle: -1.1s;
  --hauteur-suggestions: 19rem;
  --taille-pastille: 6rem;
  /* Le portrait de la fiche publique est le plus grand de l'application : c'est la
     page d'une seule personne. Un jeton plutôt qu'une valeur posée sur place, la
     pastille et son anneau étant réglés une fois pour toutes par `--taille-pastille`. */
  --taille-pastille-fiche: 9rem;
  /* Le portrait d'un expert sur l'accueil : les 148 px de la maquette, qu'il garde à
     partir de 360 px de large. En dessous il suit la fenêtre : la grille du cadre des experts
     passe à deux colonnes de 112 px sur un écran de 320 px, et deux disques de 148 px
     s'y chevauchaient, le bouton rond du second sortant du cadre (mesuré au run DA-2).
     À 50vw moins 2rem, deux disques de 128 px y tiennent côte à côte sans se toucher. */
  --taille-portrait-accueil: min(9.25rem, 50vw - 2rem);
  --taille-portrait-prochain: 3.25rem;
  /* Le portrait de l'auteur d'un avis : 56 px. */
  --taille-portrait-avis: 3.5rem;
  /* Les avis de l'accueil en bulles : le cercle qu'elles dessinent, et le portrait de la
     carte de l'avis choisi. */
  --largeur-bulles-avis: 30rem;
  /* Trois formes de goutte (rayons horizontaux / verticaux). */
  --goutte-a: 58% 42% 47% 53% / 52% 46% 54% 48%;
  --goutte-b: 44% 56% 61% 39% / 47% 58% 42% 53%;
  --goutte-c: 52% 48% 38% 62% / 60% 44% 56% 40%;
  --duree-goutte: 6s;
  --taille-portrait-carte-avis: 5rem;
  /* Le point d'état et ses quatre dessins (DESIGN.md §4) : 12 px. */
  --taille-point: 0.75rem;
  /* Le disque d'un numéro : les quatre étapes de l'accueil, les deux niveaux de
     « Devenir expert ». */
  --taille-numero-etape: 4rem;
  /* Le signe rond d'une question de la FAQ. */
  --taille-signe-question: 2rem;
  --largeur-max: 77.5rem;
  --largeur-texte: 38rem;
  /* Une page de formulaire : assez étroite pour que les libellés restent au-dessus de
     leur champ plutôt qu'à l'autre bout de l'écran. */
  --largeur-page: 34rem;
  /* En `em` : elles suivent la taille du texte qu'elles bornent. */
  --largeur-accroche: 30em;
  --largeur-titre-hero: 11em;
  --largeur-titre-carte-bleue: 13em;
  --largeur-carte-bleue: 57.5rem;
  --largeur-portrait-min: min(8.75rem, 42%);
  --largeur-menu-compte: 14rem;
  /* La carte de réservation de la fiche publique (DESIGN.md §6). C'est elle qui
     décide du moment où la fiche passe sur une seule colonne : la largeur de lecture
     plus cette carte plus leur gouttière, et pas une taille d'écran devinée. */
  --largeur-reservation: 20rem;
  /* La carte d'une demande de certification (feature 9, run 92) : deux colonnes qui se
     replient par leurs propres largeurs, comme la fiche publique. À gauche les pièces et
     leurs trois commandes, à droite le panneau d'aperçu, assez large pour lire un PDF.
     Côte à côte dès que la colonne les tient, l'une sous l'autre en dessous, sans point
     de rupture. */
  --largeur-examen: 30rem;
  --largeur-apercu: 30rem;
  /* L'image de partage d'une fiche sous le portrait de `/expert` (feature 25) : la largeur
     d'une carte d'aperçu de lien dans une conversation, 300 px, pour qu'elle se lise comme
     ce qu'elle est, et qui tient dans la colonne la plus étroite. */
  --largeur-image-partage: 18.75rem;
  /* La hauteur du cadre d'aperçu : assez pour lire une page, jamais plus que la fenêtre,
     pour que le panneau collé au défilement tienne entier à l'écran. */
  --hauteur-apercu: min(70dvh, 48rem);
  /* La largeur sous laquelle le nom d'un expert ne tient plus à côté de son
     portrait : en dessous, l'identité passe sous la pastille. */
  --largeur-identite: 15rem;
  /* La colonne de gauche d'une ligne d'avis : la note, l'auteur et les deux dates, sur
     une largeur fixe pour que les textes s'alignent d'une ligne à l'autre. */
  --largeur-avis-tete: 17rem;
  /* Un champ qui n'attend que trois chiffres (le tarif horaire, le délai minimum de
     réservation) : pleine largeur, il promettrait une phrase. */
  --largeur-champ-court: 9rem;
  /* Une heure et une date (feature 13). Deux jetons et non un seul : le champ de date
     porte « 24/12/2026 » et son icône de calendrier, le champ d'heure « 09:00 » et la
     sienne, et le second serré à la largeur du premier coupe le sélecteur de Chrome.
     Remesurés à la refonte (texte de 17 px, contour de 1,5 px) : le sélecteur d'heure
     demande 77 px et en trouve 85, celui de date 132 et en trouve 133, avec la réserve
     latérale propre à ces deux champs écrite plus bas (le champ de date a pris un demi-rem
     pour cela). Les deux bornes d'une plage tiennent sur une ligne dans la colonne la plus
     étroite. */
  --largeur-champ-heure: 7.5rem;
  --largeur-champ-date: 10.5rem;
  /* Les intitulés du récapitulatif de réservation (« Date et heure », « Prix TTC ») :
     la PREMIÈRE PISTE de la grille de chaque ligne (`.recap-ligne`), sinon les quatre
     valeurs ne s'alignent pas d'une ligne à l'autre et la liste se lit en escalier. */
  --largeur-recap-libelle: 8rem;
  /* Les huit compteurs de l'administration : des intitulés de phrase, qui ne tiennent pas
     sur 8rem sans passer à trois lignes ; la part en pour cent les garde lisibles à 375 px. */
  --largeur-recap-admin: min(22rem, 60%);
  /* La barre fixée en bas d'écran qui porte « Réserver » sous 48rem (DESIGN.md §6) :
     la hauteur du bouton, sa réserve et LE FILET qui la sépare de la page, reprise en
     bas de page pour que le pied de page ne finisse jamais sous elle. Le filet en fait
     partie : la barre fait 61 px (8 + 44 + 8, plus son `border-top`), et l'oublier
     laissait la dernière rangée de pixels du pied de page passer dessous. */
  --hauteur-barre-reservation: calc(
    var(--hauteur-bouton) + var(--espace-m) + var(--epaisseur-filet)
  );
  /* La colonne des sept noms de jours. « Mercredi » est le plus long ; en dessous, le nom
     et ses plages se replient l'un sous l'autre. */
  --largeur-jour: 5.5rem;
  /* Ce que le bloc de plages demande pour tenir à côté du nom du jour : deux bornes et
     leur commande. En dessous, il passe sous le nom (aucun point de rupture, feature 11). */
  --largeur-plages: 22rem;
  --hauteur-zone-texte: 10rem;

  /* États d'écran (feature 7) : les formes du squelette de chargement, et le glyphe du
     logo à l'échelle d'un état vide ou d'une erreur réseau. La forme de titre prend la
     hauteur de ligne EXACTE du h1 qu'elle remplace, fluide comprise : c'est ce qui
     aligne le squelette sur son contenu à toutes les largeurs plutôt qu'à une seule. */
  --hauteur-squelette-ligne: 0.75rem;
  --hauteur-squelette-titre: calc(var(--taille-xxl) * var(--interligne-titre-h1));
  --largeur-squelette-bouton: 11rem;
  --largeur-squelette-prix: 7rem;
  --taille-anneau-etat: 5rem;
  /* Le point bleu du glyphe, et de combien il déborde de l'anneau en haut à droite. */
  --taille-point-etat: 1.4rem;
  --debord-point-etat: calc(var(--taille-point-etat) / -2.5);

  /* Traits */
  --epaisseur-filet: 1px;
  --epaisseur-contour: 1.5px;
  --epaisseur-champ-faute: 3px;
  --epaisseur-anneau-portrait: 3px;
  --epaisseur-logo: 0.4375rem;
  --epaisseur-point-cible: 2px;
  --epaisseur-focus: 2px;
  --epaisseur-marqueur: 2px;
  --decalage-focus: 3px;

  /* Hero de l'accueil et des pages d'état (DESIGN.md §6) */
  --hero-haut: clamp(3.5rem, 7vw, 5.75rem);
  --hero-ecart-cta: 9.375rem;
  --hero-ecart-prochain: 6.875rem;
  --hero-bas: 18.125rem;
  /* La hauteur que le hero de l'accueil garde au moins : celle du hero de la maquette
     (1063 px, moins les 87 px de sa barre de navigation, ici hors du hero). L'encre du
     dernier ruban finit 958 px sous le haut du hero, vent compris ; sans la capsule
     « prochain rendez-vous », le contenu s'arrête plus haut, et la section suivante venait
     couper ce ruban en deux. */
  --hero-hauteur-min: 61rem;

  /* Les rubans (DESIGN.md §5). Trois images de 2880 px, chacune doublée (voir `.ruban`), qui défilent d'une
     période dans trois conteneurs qui ondulent. `--rubans-haut` vaut 20rem et non les 400 px de la
     maquette : son hero contenait aussi la barre de navigation, ici le header est hors
     du hero. L'encre du premier ruban ne commence que 79 px sous le haut du bloc
     (mesure par balayage des trois images) : c'est ce qui laisse l'accroche sur blanc. */
  --rubans-haut: 20rem;
  --rubans-hauteur: 43.75rem;
  --echelle-rubans: 1;
  --ruban-largeur: 2880px;
  --ruban-large-haut: 4.375rem;
  --ruban-fin-haut: 25rem;
  --periode-ruban-large: 1440px;
  --periode-ruban-epais: 720px;
  --periode-ruban-fin: 960px;
  --duree-ruban-large: 46s;
  --duree-ruban-epais: 27s;
  --duree-ruban-fin: 19s;
  --duree-vent-a: 8s;
  --duree-vent-b: 5.5s;
  --duree-vent-c: 10s;
  --decalage-vent-c: -4s;
  --vent-haut: -18px;
  --vent-bas: 14px;
  --vent-rotation: 0.5deg;
  --vent-rotation-inverse: -0.7deg;
  /* Le ruban fin seul, incliné, au pied de la carte bleue et des pages d'état. */
  --ruban-cta-hauteur: 15rem;
  --ruban-cta-debord: 2.5rem;
  --ruban-cta-bas: -1.875rem;
  --ruban-cta-inclinaison: -4deg;
  /* Le ruban large, seul, derrière le cadre de verre des experts : il traverse la scène
     d'un bord à l'autre de la fenêtre, incliné dans l'autre sens que celui de la carte
     bleue. La réserve de la scène, au-dessus et au-dessous du cadre, est ce qui laisse
     voir le ruban net avant qu'il ne passe derrière le verre. */
  --ruban-verre-hauteur: 35rem;
  --ruban-verre-inclinaison: 5deg;
  /* Sous 48rem il reste presque entier, là où les autres rubans tombent à 0,55 : le cadre
     est haut sur petit écran (trois rangs de portraits), et un ruban réduit de moitié s'y
     perdait. */
  --echelle-ruban-verre-petit: 0.85;
  /* Les bandes de rubans : entre deux sections de l'accueil, et au pied de chaque page.
     Deux rubans qui se croisent, le grand bleu derrière, le petit noir devant, dans une
     bande qui a leur hauteur une fois réduits, vent compris. Le petit file plus lentement
     que le grand (demande de Matthias du 11/10/2026). Sous 48rem, les deux rétrécissent. */
  --echelle-filet-grand: 0.5;
  --echelle-filet-petit: 0.42;
  --filet-ruban-hauteur: 15rem;
  --filet-grand-haut: 0.5rem;
  --filet-petit-haut: 5rem;
  --duree-filet-petit-fin: 70s;
  --duree-filet-petit-epais: 80s;
  --taille-point-moyenne: 1.25rem;
  --taille-point-moyenne-carte: 0.875rem;
  --scene-verre-reserve: clamp(3rem, 7vw, 6rem);
  --ruban-etat-hauteur: 12rem;
  /* Sous 48rem seulement : la distance entre le bas du hero et le haut du bloc des
     rubans, qui s'y ancre par le bas (voir le bloc mobile du shell). */
  --rubans-remontee: 22.5rem;

  /* Panneaux de l'accueil */
  --panneau-padding-y: clamp(3.5rem, 7vw, 5.5rem);
  --panneau-padding-x: clamp(1.25rem, 4vw, 4rem);
  /* La colonne du titre d'une section en deux colonnes (« Comment ça marche », la FAQ). */
  --largeur-duo-tete: 5fr;
  --largeur-duo-contenu: 7fr;
  --carte-bleue-padding-haut: clamp(3.25rem, 6vw, 4.75rem);
  --carte-bleue-padding-bas: 11.25rem;

  /* Motion (DESIGN.md §8) */
  --duree-instant: 80ms;
  --duree-courte: 150ms;
  --courbe: ease-out;
  --luminosite-survol: 1.06;
  --descente-clic: 1px;
}

/* La police, self-hosted (source et licence SIL OFL dans contenu/polices) : Geist en
   variable, sous-ensemble latin, un seul fichier pour les trois graisses. Chemin
   absolu : la feuille est aussi chargée par la page hors ligne depuis n'importe quelle
   URL. */
@font-face {
  font-family: "Geist";
  src: url("/fonts/geist-variable.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

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

/* L'attribut `hidden` pilote les états d'une application (écran affiché, bouton
   disponible…). Sans cette règle, la moindre classe qui fixe `display` l'emporte sur
   la feuille du navigateur et l'élément « caché » reste visible, voir PIEGES.md E1.
   C'est la seule justification acceptable d'un `!important` dans nos feuilles. */
[hidden] {
  display: none !important;
}

html {
  background: var(--couleur-fond);
}

/* L'accueil seulement : un lien du header ou du hero fait GLISSER la page jusqu'à sa section
   (demande de Matthias du 10/10/2026). Hors de l'accueil une ancre saute, comme avant : les
   mesures de défilement des autres écrans ne changent pas. Sans mouvement réduit seulement. */
@media (prefers-reduced-motion: no-preference) {
  html:has(.hero-accueil) {
    scroll-behavior: smooth;
  }
}

/* La section visée s'arrête un peu sous le bord de la fenêtre, pas collée à lui. */
.panneau-verre,
.accueil-section,
.panneau-bleu {
  scroll-margin-top: var(--espace-l);
}

/* La fenêtre entière, et le pied de page POUSSÉ EN BAS. Sans ces trois lignes, un
   écran court (le squelette de /connexion, l'état vide de /experts) laisse le pied de
   page flotter au milieu de la fenêtre avec une bande vide dessous. Aucun effet sur les
   pages longues, où `min-height` laisse le contenu grandir. C'est aussi ce qui donne
   toute la fenêtre à `main.hero` des pages d'état, qui n'ont que lui.
   `overflow-x: hidden` vient de la maquette : rien ne doit élargir la page, et les
   rubans de 2880 px sont de toute façon rognés par leur propre conteneur. */
body {
  margin: 0;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  font-family: var(--police);
  font-size: var(--taille-base);
  font-weight: var(--graisse-normale);
  line-height: var(--interligne-texte);
  color: var(--couleur-texte);
  background: var(--couleur-fond);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* ⚠️ `width: 100%` N'EST PAS DÉCORATIF : `.colonne` et `.page` posent
   `margin-inline: auto` sur ce `main` (la colonne centrée, DESIGN.md §6), et dans
   un conteneur flex une marge automatique sur l'axe transversal ANNULE l'étirement.
   Sans cette ligne, le `main` se réduit à la largeur de son plus large enfant
   et toute la colonne se recroqueville au milieu de l'écran. */
main {
  flex: 1;
  width: 100%;
}

/* L'ÉCHELLE DE DESIGN.md §3, ET PAS CELLE DU NAVIGATEUR : h2 à 24 px, h3 à 20 px, tous
   en 600. Le h1 prend `--taille-xxl` sur la classe de sa colonne (`.colonne`, `.page`),
   le hero ayant la sienne. L'interlettrage suit la taille : chaque niveau a le sien. */
h1, h2, h3 {
  font-weight: var(--graisse-titre);
  line-height: var(--interligne-titre);
  letter-spacing: var(--interlettrage-titre-h2);
}

h2 {
  font-size: var(--taille-xl);
}

h3 {
  font-size: var(--taille-l);
  letter-spacing: var(--interlettrage-titre-h3);
  line-height: var(--interligne-titre-h3);
}

p {
  max-width: var(--largeur-texte);
}

/* Un lien nu prend la couleur de son texte, et passe au bleu au survol (DESIGN.md §8).
   ⚠️ `:where()` RAMÈNE CE SURVOL À LA SPÉCIFICITÉ D'UN ÉLÉMENT. Écrit `a:hover`, il
   battait la couleur de toute classe posée sur un lien : un bouton bleu survolé passait
   en texte bleu sur fond bleu, le bouton noir du header en bleu sur noir (1,96:1,
   interdit par DESIGN.md §4), et la pilule courante d'une rangée de filtres perdait son
   texte blanc. Ainsi, toute classe qui fixe sa couleur la garde au survol, et celles
   qui veulent le bleu l'écrivent elles-mêmes. */
a {
  color: inherit;
}

a:where(:hover) {
  color: var(--couleur-accent);
}

/* Trois graisses seulement (DESIGN.md §3) : le gras du navigateur vaut 700, celui de
   Cercles 600. */
strong {
  font-weight: var(--graisse-titre);
}

/* Une seule famille, jusque dans ce que le navigateur dessine lui-même : un bouton, une
   case, une liste ou une jauge ont leur propre police par défaut, et aucun style calculé
   ne doit en porter une autre que Geist (DESIGN.md §9). */
button,
input,
select,
textarea,
progress {
  font-family: inherit;
}

img {
  max-width: 100%;
  height: auto;
}

::selection {
  background: var(--couleur-accent);
  color: var(--couleur-accent-contraste);
}

/* ---------- Focus : deux expressions (DESIGN.md §4 et §7) ----------
   Ce qui se commande porte l'anneau, noir sur blanc, blanc sur la carte bleue de
   l'accueil ; ce dans quoi on tape n'en porte aucun, sa bordure passe au bleu. Les cases et
   boutons radio restent des commandes, le `<select>` aussi : on ne tape pas dans une
   liste, on l'ouvre. */
:focus-visible {
  outline: var(--epaisseur-focus) solid var(--couleur-encre);
  outline-offset: var(--decalage-focus);
}

input:not([type="checkbox"]):not([type="radio"]):focus-visible,
textarea:focus-visible {
  outline: none;
}

/* ⚠️ LA BORDURE DE FAUTE GAGNE AUSSI SUR UN CHAMP QUI A LE FOCUS (corrections de
   review du run 91). La teinte de focus ci-dessus est exclue des champs
   `aria-invalid` : un champ en faute garde sa bordure noire de 3 px quand le focus s'y
   pose, ce qui est précisément le moment où l'écran doit dire pourquoi il a sauté
   jusque-là. L'anneau reste supprimé dans tous les cas par la règle du dessus. */
input:not([type="checkbox"]):not([type="radio"]):not([aria-invalid="true"]):focus-visible,
textarea:not([aria-invalid="true"]):focus-visible {
  border-color: var(--couleur-accent);
}

/* La case d'une pilule cochée vit sur le noir de l'inversion : son anneau noir n'y avait
   aucun contraste (1:1, mesuré au run 27 sur `/expert`). Même remède que sur un panneau. */
.panneau-bleu :focus-visible,
.choix:has(:checked) :focus-visible {
  outline-color: var(--couleur-fond);
}

/* ---------- Boutons : les capsules de verre (DESIGN.md §5 et §7) ----------
   Une classe de base, le verre blanc, et ses variantes : `.bouton-principal` (verre
   bleu), `.bouton-secondaire` (verre bleu aussi depuis le 11/10/2026 ; verre blanc dans le hero,
   verre noir dans le header),
   `.bouton-danger` (verre noir, un seul par écran), `.bouton-discret` (un mot
   souligné). Reflet en haut, contour fin et ombre portée sont trois `box-shadow` ; il
   n'y a donc AUCUNE bordure, et rien ne décale un voisin.
   ⚠️ TOUTE RÈGLE QUI PEINT UN BOUTON VIT DANS CE BLOC, avant `.bouton:disabled` : à
   spécificité égale c'est l'ordre qui tranche, et le désactivé doit gagner. */
.bouton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--espace-s);
  min-height: var(--hauteur-bouton);
  min-width: var(--hauteur-bouton);
  padding: 0 var(--padding-bouton);
  border: 0;
  border-radius: var(--rayon-pilule);
  font: inherit;
  font-size: var(--taille-base);
  font-weight: var(--graisse-titre);
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  color: var(--couleur-encre);
  background: var(--verre-blanc);
  box-shadow: var(--reflet-verre-blanc), var(--contour-verre-blanc), var(--ombre-verre-blanc);
}

.bouton:active {
  transform: translateY(var(--descente-clic));
}

/* ⚠️ LE SECONDAIRE EST BLEU LUI AUSSI depuis le 11/10/2026 (demande de Matthias : les pages
   faisaient trop noir, « mettez les boutons en bleu comme celui de l'accueil »). Il reste
   une classe à part : le verre blanc lui revient là où il fait la paire avec un bouton bleu
   sur les rubans (les deux boutons du hero), et le header garde son verre noir. Ce qui
   distingue l'action principale d'un écran n'est donc plus la couleur, c'est sa place et son
   libellé. */
.bouton-principal,
.bouton-secondaire {
  color: var(--couleur-accent-contraste);
  background: var(--verre-bleu);
  box-shadow: var(--reflet-verre-bleu), var(--contour-verre-bleu), var(--ombre-verre-bleu);
}

/* Le secondaire du hero de l'accueil : le verre blanc, à côté de « Trouver mon expert ». */
.hero-actions .bouton-secondaire {
  color: var(--couleur-encre);
  background: var(--verre-blanc);
  box-shadow: var(--reflet-verre-blanc), var(--contour-verre-blanc), var(--ombre-verre-blanc);
}

/* Le danger est un verre noir : sa gravité vient du noir plein, la seule masse sombre
   d'un écran de travail. Le bouton du header (« Se connecter ») est le même verre ; ils
   ne se croisent sur aucun écran. */
.bouton-danger,
.site-header .bouton-secondaire {
  color: var(--couleur-texte-sur-sombre);
  background: var(--verre-noir);
  box-shadow: var(--reflet-verre-noir), var(--contour-verre-noir), var(--ombre-verre-noir);
}

/* Le bouton de la carte bleue (« Rejoindre le cercle ») : blanc plein, sans contour. */
.panneau-bleu .bouton-principal {
  color: var(--couleur-encre);
  background: var(--couleur-fond);
  box-shadow: var(--reflet-verre-blanc), var(--ombre-verre-noir);
}

/* Le survol (DESIGN.md §8). Les verres bleu et noir s'éclaircissent (le secondaire et le
   bouton du header compris, qui sont bleu et noir) ; le verre blanc du hero devient blanc
   plein, sans éclaircissement. */
.bouton-principal:hover,
.bouton-secondaire:hover,
.bouton-danger:hover {
  filter: brightness(var(--luminosite-survol));
}

.hero-actions .bouton-secondaire:hover {
  filter: none;
  background: var(--couleur-fond);
}

/* Un mot souligné, pas une capsule : annuler, revenir, voir. Il perd le verre et la
   réserve horizontale, et GARDE la hauteur d'un bouton : c'est sa cible tactile, et
   c'est elle qui tient la hauteur des lignes où il est seul (« Retirer »). */
.bouton-discret {
  min-width: 0;
  padding: 0;
  background: transparent;
  box-shadow: none;
  color: var(--couleur-encre);
  font-weight: var(--graisse-moyenne);
  text-decoration: underline;
  text-decoration-thickness: var(--epaisseur-marqueur);
  text-underline-offset: var(--decalage-focus);
}

.bouton-discret:hover {
  color: var(--couleur-accent);
}

/* Le flou n'existe que sur les rubans (DESIGN.md §5) : partout ailleurs le verre a les
   mêmes couleurs, le même reflet, la même ombre, sans flou. Sur un fond uni il ne change
   rien et coûte une couche de composition par bouton. */
.site-header .nav,
.hero-actions .bouton,
.prochain {
  -webkit-backdrop-filter: var(--flou-verre);
  backdrop-filter: var(--flou-verre);
}

/* Les grands boutons de l'accueil : les deux du hero, et « Voir tous les experts » dans la
   tête du cadre de verre, le même bouton bleu que « Trouver mon expert ». */
.hero-actions .bouton,
.panneau-tete .bouton {
  min-height: var(--hauteur-bouton-grand);
  padding: 0 var(--padding-bouton-grand);
  font-size: var(--taille-bouton-grand);
}

/* LE MÊME BOUTON ROND, PARTOUT : celui du compte, celui qui ferme la barre de recherche
   de l'annuaire, et sur l'accueil le combiné sous un portrait et l'horloge de la capsule
   « prochain rendez-vous ». Un bouton dont le contenu est un disque ou un pictogramme
   perd la réserve horizontale de `.bouton` et devient carré, donc rond une fois le
   rayon de capsule appliqué. Deux tailles, une règle chacune. */
.compte-bouton,
.recherche-bouton {
  width: var(--hauteur-bouton);
  height: var(--hauteur-bouton);
  padding: 0;
}

.bouton-decrocher,
.prochain-bouton {
  flex: none;
  width: var(--taille-bouton-rond);
  height: var(--taille-bouton-rond);
  min-height: 0;
  padding: 0;
}

/* Les deux boutons ronds de verre noir : les initiales du compte, l'horloge de la
   capsule. */
.compte-bouton,
.prochain-bouton {
  color: var(--couleur-texte-sur-sombre);
  background: var(--verre-noir);
  box-shadow: var(--reflet-verre-noir), var(--contour-verre-noir), var(--ombre-verre-noir);
}

.compte-bouton:hover,
.prochain-bouton:hover {
  filter: brightness(var(--luminosite-survol));
}

/* Un compte était connecté à la dernière visite (js/amorce-compte.js) : « Se connecter » et
   « Devenir expert » gardent leur place sans se montrer, le temps que « qui suis-je »
   réponde. Sans quoi un compte connecté les voyait passer à chaque page. */
[data-compte="connu"] #lien-connexion,
[data-compte="connu"] .nav-devenir-expert {
  visibility: hidden;
}

/* La photo de profil remplit tout le bouton du compte, cadrée au centre (js/session.js),
   dans un anneau bleu (demande de Matthias du 16/10/2026). */
.compte-photo {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
  box-shadow: 0 0 0 var(--epaisseur-anneau-portrait) var(--couleur-accent);
}

/* ⚠️ UNE SEULE RÈGLE `:disabled`, POUR TOUTES LES VARIANTES, ET ELLE EST LA DERNIÈRE
   RÈGLE DE BOUTONS DE LA FEUILLE (corrections de review du run 92) : à spécificité
   égale (0,2,0) avec chaque `:hover` de variante, c'est l'ORDRE qui tranche, et rien ne
   doit plus se peindre après elle.
   Désactivé se dit par des POINTILLÉS, jamais par une opacité (DESIGN.md §4) : un bouton
   à 55 % est un bouton gris. Fond blanc, texte noir en 400, contour pointillé, et plus
   aucun verre. Le pointillé est un `outline` rentré dans la boîte, pas une bordure : un
   bouton n'en a aucune, et lui en donner une l'élargirait de 3 px à chaque fois qu'il
   s'allume ou s'éteint (« Ajouter » d'une exception, « Supprimer définitivement »). */
.bouton:disabled {
  background: var(--couleur-fond);
  color: var(--couleur-encre);
  font-weight: var(--graisse-normale);
  box-shadow: none;
  outline: var(--epaisseur-contour) dashed var(--couleur-encre);
  outline-offset: calc(var(--epaisseur-contour) * -1);
  cursor: not-allowed;
  filter: none;
  transform: none;
}

/* ---------- Marqueur « vous êtes ici » ----------
   ⚠️ IL N'EST PAS ICI, IL EST À LA TOUTE FIN DE LA FEUILLE, alors que c'est ici qu'on
   viendrait spontanément l'écrire (PIEGES.md E27). Voir la dernière section. */

/* ---------- La marque : le logo et « Cercles » (DESIGN.md §7) ---------- */
.marque {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-s);
  font-size: var(--taille-marque-texte);
  font-weight: var(--graisse-titre);
  letter-spacing: var(--interlettrage-marque);
  line-height: 1;
  color: inherit;
  text-decoration: none;
}

.marque img {
  width: var(--taille-marque);
  height: var(--taille-marque);
}

/* ---------- Header / navigation (DESIGN.md §6) ----------
   Une capsule de verre posée sur la page, à 22 px du haut, non collante : `.site-header`
   est transparent, `.nav` porte le verre. Même colonne que le hero et le footer.
   ⚠️ `.nav` EST AU-DESSUS DE CE QUI LA SUIT (`position` et `z-index`). Le flou en fait un
   contexte d'empilement : le menu du compte, qui en descend, y est enfermé, et sans ce
   rang la carte de réservation collée au défilement ou la barre fixée du mobile, venues
   plus tard dans la page, se peindraient par-dessus lui. */
.site-header {
  padding: var(--espace-nav) var(--gouttiere-shell) 0;
}

.nav {
  position: relative;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--espace-m);
  max-width: var(--largeur-max);
  margin-inline: auto;
  padding: var(--espace-s) var(--espace-s) var(--espace-s) var(--espace-l);
  border-radius: var(--rayon-carte-grande);
  font-weight: var(--graisse-moyenne);
  background: var(--verre-blanc);
  box-shadow: var(--reflet-verre-blanc), var(--contour-verre-blanc), var(--ombre-verre-blanc);
}

.nav-logo {
  min-height: var(--hauteur-bouton);
  border-radius: var(--rayon-pilule);
}

/* Le burger n'existe qu'en mobile (bloc @media en fin de section). */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: var(--hauteur-bouton);
  height: var(--hauteur-bouton);
  padding: 0;
  background: none;
  border: 0;
  border-radius: var(--rayon-pilule);
  color: var(--couleur-texte);
  cursor: pointer;
}

/* `flex: 1 1 0` et `min-width: 0` : le menu prend la place que la marque laisse et c'est la
   recherche qui rétrécit quand la fenêtre se resserre. Avec une base automatique, la capsule
   passait sur deux lignes à 1024 px (mesuré : 127 px de haut au lieu de 60). */
.nav-menu {
  display: flex;
  flex: 1 1 0;
  min-width: 0;
  align-items: center;
  justify-content: flex-end;
  gap: var(--ecart-nav);
}

/* La recherche du header : un champ de l'annuaire (`.champ`), posé dans la place libre entre
   la marque et les liens. Elle envoie à `/experts?q=…`, que l'annuaire sait déjà lire : aucun
   script. Sa marge automatique la tient contre la marque et pousse les liens à droite. */
.nav-recherche {
  flex: 1 1 auto;
  min-width: var(--largeur-recherche-nav-min);
  max-width: var(--largeur-recherche-nav);
  margin-inline-end: auto;
}

/* Sa réserve est celle d'un lien du header, pas celle d'un bouton : à sa largeur minimale,
   son exemple doit encore y tenir. */
.nav-recherche-champ {
  padding-inline: var(--espace-m);
}

/* Le bouton du header ne se laisse pas écraser par la recherche : sans cela son libellé
   passait sur deux lignes et sortait de la capsule (vu à 800 px). */
.nav-menu .bouton {
  flex: none;
  white-space: nowrap;
}

/* `nowrap` : serré par la recherche, un lien de deux mots passait sur deux lignes et
   grossissait la capsule de 7 px (mesuré de 769 à 1200 px). */
.nav-lien {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  min-height: var(--hauteur-bouton);
  padding: 0 var(--espace-m);
  border-radius: var(--rayon-pilule);
  font-size: var(--taille-base);
  font-weight: var(--graisse-moyenne);
  color: var(--couleur-texte);
  text-decoration: none;
}

.nav-lien:not([aria-current="page"]):hover {
  color: var(--couleur-accent);
}

/* ---------- Les bulles du header (demandes d'Abel du 14/10/2026) ----------
   Les liens du header et la capsule « Recherche » sont des bulles qui flottent, chacune à son
   rythme (bloc de motion), et ces bulles sont LES BOUTONS DU SITE : le verre blanc des
   boutons, le verre bleu pour « Les experts » (`.nav-bulle-bleue`), le verre noir de « Se
   connecter », qui n'a pas changé. L'entrée courante reste le marqueur noir. Les liens du menu
   du COMPTE ne sont pas des bulles : seuls les enfants directs du menu. */
.nav-menu > .nav-lien,
.nav-bulle {
  padding-inline: var(--espace-m);
}

/* ⚠️ PAS SUR L'ENTRÉE COURANTE : plus spécifique que le marqueur, le verre de la bulle
   effaçait son fond noir, et son texte blanc disparaissait (vu sur `/experts`). */
.nav-menu > .nav-lien:not([aria-current="page"]),
.nav-bulle {
  color: var(--couleur-encre);
  background: var(--verre-blanc);
  box-shadow: var(--reflet-verre-blanc), var(--contour-verre-blanc), var(--ombre-verre-blanc);
}

.nav-menu > .nav-bulle-bleue:not([aria-current="page"]) {
  color: var(--couleur-accent-contraste);
  background: var(--verre-bleu);
  box-shadow: var(--reflet-verre-bleu), var(--contour-verre-bleu), var(--ombre-verre-bleu);
}

/* Le survol des boutons du site : le verre bleu s'éclaircit, et son texte reste blanc (la
   règle des liens du header l'aurait passé au bleu, sur le bleu). */
.nav-menu > .nav-bulle-bleue:not([aria-current="page"]):hover {
  color: var(--couleur-accent-contraste);
  filter: brightness(var(--luminosite-survol));
}

/* La capsule « Recherche » : un bouton qui a la forme d'un lien du header, sa loupe après
   le mot. Le champ prend sa place une fois déplié (js/app.js). */
.nav-bulle {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-s);
  min-height: var(--hauteur-bouton);
  border: 0;
  border-radius: var(--rayon-pilule);
  font: inherit;
  font-size: var(--taille-base);
  font-weight: var(--graisse-moyenne);
  cursor: pointer;
  white-space: nowrap;
}

.nav-bulle:hover {
  color: var(--couleur-accent);
}

/* ⚠️ LES BULLES FLOTTENT LIBRES (demandes de Matthias du 15/10/2026) : la capsule qui les
   regroupait n'a plus ni verre, ni contour, ni ombre, sur grand écran puis sur téléphone. Le
   panneau du burger s'ouvre dans le flux, il pousse la page au lieu de la recouvrir : il n'a
   pas besoin de fond. Les couleurs des bulles ne changent pas. */
.site-header .nav {
  background: none;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* Sur grand écran, chaque bulle est un peu plus haute ; elle garde le contour en capsule des
   boutons (l'ovale essayé le même jour a été retiré à la demande de Matthias). Écrit après
   la règle de la capsule « Recherche », qu'il doit battre. */
@media (width > 64rem) {
  .nav-menu > .nav-lien,
  .nav-bulle,
  .nav-menu > #lien-connexion {
    min-height: var(--hauteur-bulle);
    padding-inline: var(--espace-l);
  }
}

/* ---------- Hero : l'accueil et les pages d'état (DESIGN.md §6) ----------
   Une composition centrée sur blanc, que les rubans portent : sans eux elle est le hero
   générique que DESIGN.md §9 interdit. `.hero-accueil` est la première section de
   l'accueil, `.hero-etat` le `main` entier de `/404`, `/acces-refuse` et `/offline`.
   `isolation` : le ruban d'une page d'état passe DERRIÈRE son texte (rang négatif), et
   ce rang ne doit pas le faire passer derrière la page. */
.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  text-align: center;
}

/* Même gouttière que `.site-header` : l'accroche et la capsule partagent leur colonne. */
.hero-contenu {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--espace-l);
  max-width: var(--largeur-max);
  margin-inline: auto;
  padding: var(--hero-haut) var(--gouttiere-shell) 0;
}

.hero h1 {
  margin: 0;
  max-width: var(--largeur-titre-hero);
  font-size: var(--taille-hero);
  font-weight: var(--graisse-titre);
  line-height: var(--interligne-hero);
  letter-spacing: var(--interlettrage-hero);
}

/* Le point bleu signe la phrase du hero et le titre des trois pages d'état : c'est
   l'écho du point du logo (DESIGN.md §7, composant 2). Il ne termine JAMAIS le titre
   d'un écran de travail (décision du 18/09/2026, inchangée dans son principe) : répété
   sur chaque écran il ne signe plus rien. */
.hero-point {
  color: var(--couleur-accent);
}

.hero p {
  margin: 0;
  max-width: var(--largeur-accroche);
  font-size: var(--taille-m);
  line-height: var(--interligne-accroche);
}

.hero-accueil {
  min-height: var(--hero-hauteur-min);
  padding-bottom: var(--hero-bas);
}

/* Les deux boutons, posés 150 px sous l'accroche : c'est là que passent les rubans. */
.hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--espace-m);
  margin-top: var(--hero-ecart-cta);
}

/* Les pages d'état : la fenêtre entière, le texte centré, le ruban fin en bas. */
.hero-etat {
  min-height: 100dvh;
  display: grid;
  align-content: center;
  justify-items: center;
  padding: var(--hero-haut) var(--espace-l) var(--hero-bas);
}

.hero-etat p {
  margin-top: var(--espace-m);
}

.hero-etat .bouton {
  margin-top: var(--espace-l);
}

/* ---------- Les rubans (DESIGN.md §5) ----------
   Un bloc absolu, sans prise sur la souris. Dans le hero de l'accueil : trois
   conteneurs qui ondulent (`.ruban`), trois images qui défilent d'une période
   (`.ruban-image`), empilés dans l'ordre du document, épais derrière, large au milieu,
   fin devant. Les animations sont dans le bloc de motion, en fin de section : sans
   elles les rubans restent posés, immobiles. */
.rubans,
.rubans-cta,
.rubans-verre {
  position: absolute;
  pointer-events: none;
}

/* L'échelle se règle par `transform` (jamais `zoom`, non standard), depuis le coin haut
   gauche : le bloc reste calé sur le bord gauche, et sa hauteur visible rétrécit. */
.hero-accueil .rubans {
  left: 0;
  right: 0;
  top: var(--rubans-haut);
  height: var(--rubans-hauteur);
  transform: scale(var(--echelle-rubans));
  transform-origin: 0 0;
}

/* Deux images, la seconde recouvrant la première d'une période (le motif se répète
   à cette distance) : bout à bout, leur raccord à 2880 px se verrait, le motif n'y étant
   pas continu. La bande couvre 5760 px moins deux périodes (le recouvrement, puis le
   défilement) : de quoi remplir les fenêtres jusqu'à 2560 px à tout instant. */
.ruban {
  position: absolute;
  left: 0;
  display: flex;
  width: max-content;
  will-change: transform;
}

.ruban-image + .ruban-image {
  margin-left: calc(var(--periode-ruban) * -1);
}

.ruban-epais {
  --periode-ruban: var(--periode-ruban-epais);
  top: 0;
}

.ruban-large {
  --periode-ruban: var(--periode-ruban-large);
  top: var(--ruban-large-haut);
}

.ruban-fin {
  --periode-ruban: var(--periode-ruban-fin);
  top: var(--ruban-fin-haut);
}

/* `max-width: none` : la règle générale de `img` ramènerait les 2880 px à la largeur
   de la fenêtre, et le ruban défilerait écrasé. */
.ruban-image {
  display: block;
  width: var(--ruban-largeur);
  height: auto;
  max-width: none;
  flex: none;
  will-change: transform;
}

/* LE RUBAN FIN SEUL, INCLINÉ : au pied de la carte bleue de l'accueil (`.rubans-cta`) et
   des trois pages d'état (`.rubans-etat`, qui porte aussi `.rubans`). Il déborde de son
   cadre à gauche et à droite, qui le rogne.
   ⚠️ L'ÉCHELLE DES PETITS ÉCRANS S'Y APPLIQUE AUTOUR DU CENTRE, ET LE BAS S'Y RATTRAPE.
   La maquette réduisait ce ruban par `zoom`, qui rétrécit aussi les décalages ; un
   `transform` ne rétrécit que le dessin, autour de son centre. Le `bottom` reprend donc
   la moitié de ce que la hauteur a perdu : à l'échelle 1 il vaut `--ruban-cta-bas`, à
   0,55 il descend le ruban exactement là où `zoom` le posait. */
.rubans-cta,
.rubans-etat {
  left: calc(var(--ruban-cta-debord) * -1);
  right: calc(var(--ruban-cta-debord) * -1);
  top: auto;
  bottom: calc(
    var(--ruban-cta-bas) * var(--echelle-rubans) -
    var(--ruban-seul-hauteur) * (1 - var(--echelle-rubans)) / 2
  );
  height: var(--ruban-seul-hauteur);
  transform: rotate(var(--ruban-cta-inclinaison)) scale(var(--echelle-rubans));
  transform-origin: center;
}

.rubans-cta {
  --ruban-seul-hauteur: var(--ruban-cta-hauteur);
}

.rubans-etat {
  --ruban-seul-hauteur: var(--ruban-etat-hauteur);
  z-index: -1;
}

/* Calé en haut de son conteneur, et décalé d'une demi-période vers la gauche : réduit
   autour du centre, le conteneur ne commence plus au bord de son cadre, et le ruban doit
   pourtant le couvrir d'un bord à l'autre à tout instant du défilement. Une demi-période
   ne change que la phase du motif. */
.rubans-cta .ruban-fin,
.rubans-etat .ruban-fin {
  top: 0;
  left: calc(var(--periode-ruban-fin) / -2);
}

/* ---------- Du bleu dans les pages (demande de Matthias du 11/10/2026) ----------
   Les pages intérieures faisaient « trop noir ». Leurs titres de section (un `h2` sans classe
   dans une colonne de contenu) et les liens de leurs paragraphes passent au bleu ; les
   titres d'état d'écran et les noms d'une liste gardent l'encre. Bleu sur blanc : 9,6:1. */
.page h2:not([class]),
.colonne h2:not([class]),
.page p a:not(.bouton),
.colonne p a:not(.bouton) {
  color: var(--couleur-accent);
}

/* ---------- Footer (DESIGN.md §6) : sur blanc, sans filet, sobre ----------
   Quatre colonnes depuis le 10/10/2026 (il « faisait vide ») : la marque et une phrase,
   puis trois listes de liens titrées (découvrir, votre espace, informations légales). Sur
   petit écran, la présentation prend la largeur et les listes passent à deux colonnes. */
.site-footer {
  font-size: var(--taille-s);
}

.footer-contenu {
  display: grid;
  grid-template-columns:
    minmax(0, var(--colonne-pied-presentation))
    repeat(3, minmax(0, var(--colonne-pied-liens)));
  align-items: start;
  gap: var(--espace-l);
  max-width: var(--largeur-max);
  margin-inline: auto;
  padding: var(--espace-xl) var(--gouttiere-pied) var(--espace-l);
}

.footer-presentation {
  display: grid;
  gap: var(--espace-m);
}

.footer-presentation p {
  margin: 0;
  max-width: var(--largeur-pied-presentation);
}

/* La marque du pied de page est celle du header, au même écart entre le logo et le mot. */
.footer-marque {
  display: flex;
  align-items: center;
  gap: var(--espace-s);
}

/* Le titre d'une liste a la hauteur d'un de ses liens : les trois colonnes commencent
   sur la même ligne que la marque. */
.footer-titre {
  display: flex;
  align-items: center;
  min-height: var(--hauteur-bouton);
  margin: 0;
  font-weight: var(--graisse-titre);
}

.footer-liens ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* « CGU » et « CGV » ne faisaient que 31 px de large : la réserve latérale porte la cible
   à 47 px, et la marge négative la reprend, pour que le texte reste aligné sur le titre
   de sa liste (run 27). */
.footer-liens a {
  display: inline-flex;
  align-items: center;
  min-height: var(--hauteur-bouton);
  margin-inline: calc(-1 * var(--espace-s));
  padding-inline: var(--espace-s);
  color: inherit;
  text-decoration: none;
}

.footer-liens a:hover {
  color: var(--couleur-accent);
}

/* Sur son propre rang, sous les quatre colonnes. `max-width: none` : c'est un paragraphe,
   et la largeur de lecture de la règle générale le bornerait. */
.footer-copyright {
  grid-column: 1 / -1;
  max-width: none;
  margin: 0;
  font-size: var(--taille-xs);
}

@media (max-width: 48rem) {
  .footer-contenu {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .footer-presentation {
    grid-column: 1 / -1;
  }
}

/* ---------- Colonne de contenu (DESIGN.md §6) ----------
   CENTRÉE dans la fenêtre, jamais collée à gauche : règle d'agence, un écran étroit
   posé à gauche laisse une demi-fenêtre vide sur un grand écran. C'est le bloc qui
   est centré, pas le texte, qui reste aligné à gauche. */
.colonne {
  max-width: var(--largeur-texte);
  margin-inline: auto;
  padding: var(--espace-xl) var(--gouttiere-page);
}

/* Une page de contenu ordinaire (les espaces connectés) : même gouttière que le reste,
   largeur bornée pour que les libellés restent au-dessus de leur champ. */
.page {
  max-width: var(--largeur-page);
  margin: 0 auto;
  padding: var(--espace-xl) var(--gouttiere-page) var(--espace-xxl);
}

/* Le titre d'écran, quelle que soit la colonne qui le porte : les deux largeurs de
   colonne de l'application partagent la même échelle, et l'écrire deux fois
   garantirait qu'elles divergent. Seule la marge basse diffère, la page de formulaire
   ayant besoin de plus d'air sous son titre.
   L'interligne se resserre comme sur `.hero h1` : plus un titre grandit, plus l'espace
   entre ses lignes doit se réduire pour qu'il tienne en bloc. La forme de titre du
   squelette suit le même jeton, faute de quoi l'écran sauterait au chargement. */
.colonne h1,
.page h1 {
  font-size: var(--taille-xxl);
  line-height: var(--interligne-titre-h1);
  letter-spacing: var(--interlettrage-serre);
  margin: 0 0 var(--espace-m);
}

.page h1 {
  margin-bottom: var(--espace-l);
}

.chapeau {
  font-size: var(--taille-m);
  line-height: var(--interligne-accroche);
  margin: 0 0 var(--espace-l);
}

/* ---------- Champs de formulaire (DESIGN.md §4 et §7) ----------
   Une capsule blanche à contour noir. Le champ n'a PAS d'anneau de focus : sa bordure
   passe au bleu, et c'est tout (les deux règles sont plus haut, avec le focus). Un
   `<select>` porte la même classe et reste du côté commandé : il garde son anneau et sa
   flèche native. */
.champ-groupe {
  display: grid;
  gap: var(--espace-xs);
}

.champ-libelle {
  font-size: var(--taille-s);
  font-weight: var(--graisse-moyenne);
}

.champ {
  width: 100%;
  min-height: var(--hauteur-bouton);
  padding: 0 var(--padding-bouton);
  background: var(--couleur-fond);
  border: var(--epaisseur-contour) solid var(--couleur-encre);
  border-radius: var(--rayon-pilule);
  font: inherit;
  color: var(--couleur-texte);
}

/* Sans gris, l'exemple d'un champ a la couleur d'une saisie : il s'en distingue par le
   libellé, toujours au-dessus, et par le curseur. `opacity: 1` : Firefox l'éclaircit de
   lui-même, ce qui ferait un gris. */
.champ::placeholder {
  color: var(--couleur-texte);
  opacity: 1;
  font-weight: var(--graisse-normale);
}

/* La biographie. `.champ` est fait pour une ligne : hauteur fixe et réserve horizontale
   seule. Une zone de texte a besoin des deux réserves, de son interligne, du rayon d'un
   bloc (une capsule de dix rems de haut dessinerait deux demi-cercles) et du droit de
   grandir à la main, jamais en largeur. */
.champ-zone {
  min-height: var(--hauteur-zone-texte);
  padding: var(--espace-s) var(--espace-m);
  border-radius: var(--rayon-carte);
  line-height: var(--interligne-texte);
  resize: vertical;
}

.champ-court {
  width: var(--largeur-champ-court);
}

/* Un champ signalé par l'écran ou par le serveur : sa bordure passe de 1,5 à 3 px, et
   la phrase sous le bouton dit pourquoi. La forme ne porte pas l'information seule. */
.champ[aria-invalid="true"] {
  border-width: var(--epaisseur-champ-faute);
}

/* Désactivé : des pointillés, jamais un gris (DESIGN.md §4). Le navigateur grise de
   lui-même le texte d'un champ ou d'une case désactivés, et pâlit une liste : la couleur
   du texte et l'opacité sont donc reposées. */
.champ:disabled {
  border-style: dashed;
  cursor: not-allowed;
}

.champ:disabled,
.case input:disabled,
.choix-case:disabled {
  border-style: dashed;
  color: var(--couleur-texte);
  opacity: 1;
}

.formulaire,
.avis-bloc {
  display: grid;
  gap: var(--espace-m);
}

.formulaire .bouton {
  justify-self: start;
}

/* L'écran de l'expert est bien plus long que celui du membre : ses blocs
   respirent davantage, faute de quoi ses trois listes de pilules, sa biographie et
   son tarif se lisent comme une seule masse. */
.formulaire-fiche {
  gap: var(--espace-l);
}

/* Deux usages d'une même mise hors de l'écran, et ils sont exactement inverses :
   `.piege` est le champ à robots du formulaire de connexion, invisible à tous et
   hors de l'arbre d'accessibilité (hors de l'écran plutôt qu'en `display: none`,
   qu'un automate sait reconnaître ; son `aria-hidden` et son `tabindex` négatif
   font le reste) ; `.hors-ecran` porte au contraire du texte destiné aux seuls
   lecteurs d'écran, comme l'annonce d'un squelette de chargement. La géométrie
   étant la même, l'écrire deux fois en serait une vraie duplication. */
.piege,
.hors-ecran {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Personne ne le voit, mais il existe : sans cette ligne il garde la bordure grise que
   le navigateur donne à un champ nu, la seule teinte hors palette de la page. */
.piege input {
  border: 0;
}

.separateur {
  margin: var(--espace-m) 0;
  font-size: var(--taille-s);
}

.bouton-google {
  gap: var(--espace-s);
}

.case {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--espace-s);
  align-items: start;
  padding-block: var(--espace-s);
  font-size: var(--taille-s);
}

/* LA MÊME CASE, LES DEUX FOIS : celle du consentement de /connexion et celle des
   pilules `.choix` (domaines et durées de l'expert) sont le même objet, une case à
   cocher à la taille et à la teinte de l'application. Seule la boîte AUTOUR de la case
   change d'un contexte à l'autre, et elle se règle sur `.case` et sur `.choix`. */
.case input,
.choix-case {
  appearance: none;
  position: relative;
  flex: none;
  width: var(--taille-case);
  height: var(--taille-case);
  margin: 0;
  border: var(--epaisseur-contour) solid currentColor;
  border-radius: var(--rayon-case);
  background: transparent;
  color: inherit;
}

/* ⚠️ COCHÉE, LA CASE SE REMPLIT, ET LA COCHE SE DESSINE DANS LA COULEUR OPPOSÉE (demande de
   Matthias du 11/10/2026 : « la coche ne se met pas bien »). La coche fine en `currentColor`
   dans une case vide se lisait mal à 18 px, décalée vers le haut et à gauche. La case
   prend la couleur du texte, noire sur le blanc et blanche dans la pilule inversée, et la
   coche celle du fond. Elle est centrée par `inset: 0` et `margin: auto`, puis remontée
   d'un cran : un « L » tourné a son poids dans le bas. */
.case input:checked,
.choix-case:checked {
  background: currentColor;
}

.case input:checked::after,
.choix-case:checked::after {
  content: "";
  position: absolute;
  inset: 0;
  width: 28%;
  height: 52%;
  margin: auto;
  border: solid var(--couleur-fond);
  border-width: 0 var(--epaisseur-point-cible) var(--epaisseur-point-cible) 0;
  transform: translateY(-12%) rotate(45deg);
}

.choix:has(:checked) .choix-case:checked::after {
  border-color: var(--couleur-encre);
}

.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-m);
  margin-top: var(--espace-l);
}

/* ⚠️ UN `.actions` DE TROIS COMMANDES NE WRAP PAS EN DEUX PUIS UNE ORPHELINE
   (corrections de review du run 92) : sur `/admin/certification`, « Certifier »,
   « Demander un complément » et « Refuser la demande » demandent plus que leur colonne
   avec leur gouttière, et la troisième passait seule sur une seconde ligne. Les
   `.actions` à un ou deux boutons (Annuler, Enregistrer…) restent en ligne : seul un
   TROISIÈME enfant fait basculer la disposition, en colonne, chaque bouton gardant sa
   largeur naturelle (`align-items: flex-start`, pas d'étirement comme `.choix-empiles`,
   qui est fait pour des pilules de même famille et non pour un bouton principal, un
   secondaire et un lien discret côte à côte). */
.actions:has(> :nth-child(3)) {
  flex-direction: column;
  align-items: flex-start;
}

/* Le dépliant « L'email n'arrive pas ? ». Il grandit sur place et pousse ce qui le
   suit : rien au-dessus de lui ne doit être centré verticalement, sinon l'ouvrir
   remonterait le résumé qu'on vient de cliquer (PIEGES.md E32). */
.depliant {
  margin-top: var(--espace-l);
  padding-top: var(--espace-m);
  border-top: var(--epaisseur-filet) solid var(--couleur-encre);
  font-size: var(--taille-s);
}

/* Le résumé qui se clique : celui du dépliant, celui d'une question de la FAQ de l'accueil
   (dont le dessin est avec l'accueil). `display: flex` lui retire le triangle du
   navigateur. */
.depliant summary,
.question summary {
  display: flex;
  align-items: center;
  min-height: var(--hauteur-bouton);
  cursor: pointer;
}

.depliant summary {
  font-weight: var(--graisse-moyenne);
}

.depliant ul {
  margin: 0;
  padding-left: var(--espace-m);
}

.depliant li {
  margin-top: var(--espace-s);
}

/* Le texte d'aide sous un champ : la phrase qui dit POURQUOI, notamment pour
   l'adresse de connexion en lecture seule. Noir, plus petit, en 400 : c'est toute la
   voix secondaire de l'application (DESIGN.md §3). */
.champ-aide {
  font-size: var(--taille-s);
}

/* ⚠️ LA GOUTTIÈRE DE LIGNE EST PLUS COURTE QUE CELLE DE COLONNE, et ce n'est pas
   un détail : cette ligne se replie (les boutons sous le portrait sur petit écran, le
   badge « Expert certifié » sous le portrait dès que les trois éléments ne tiennent
   plus). Avec une gouttière unique de `--espace-l`, ce qui passe à la ligne se retrouve
   à exactement la même distance du portrait que du bloc SUIVANT du formulaire, qui a
   la même gouttière : il n'appartient alors visuellement à rien. */
.champ-photo {
  display: flex;
  align-items: center;
  gap: var(--espace-m) var(--espace-l);
  flex-wrap: wrap;
}

/* Le bouton qui ouvre le sélecteur de fichier est un `<label>` : sans cette
   ligne il ne montrerait pas qu'il se clique. */
.champ-photo label.bouton {
  cursor: pointer;
}

/* La marge du bloc d'actions est faite pour séparer un formulaire de ses boutons.
   Dans la ligne de la photo, elle décalerait les boutons vers le bas. */
.champ-photo .actions {
  margin-top: 0;
}

/* La zone de suppression de compte. Elle est séparée du formulaire par un filet
   et non par un aplat : le danger est dans le bouton, le seul verre noir de l'écran,
   pas dans la section. La zone « Mes données » (feature 24), posée juste avant elle,
   se sépare du formulaire de la même façon. */
.zone-donnees,
.zone-danger {
  margin-top: var(--espace-xxl);
  padding-top: var(--espace-l);
  border-top: var(--epaisseur-filet) solid var(--couleur-encre);
}

.zone-donnees h2,
.zone-danger h2 {
  margin-bottom: var(--espace-s);
}

/* La jauge d'un envoi (PIEGES.md E5), le temps que le fichier monte : un `<progress>`
   redessiné, contour noir en pilule, piste blanche, barre bleue, sur toute la largeur
   du bloc. */
.jauge {
  appearance: none;
  width: 100%;
  height: var(--taille-point);
  border: var(--epaisseur-contour) solid var(--couleur-encre);
  border-radius: var(--rayon-pilule);
  overflow: hidden;
  background: var(--couleur-fond);
}

.jauge::-webkit-progress-bar {
  background: var(--couleur-fond);
}

.jauge::-webkit-progress-value {
  background: var(--couleur-accent);
}

/* Deux règles : un sélecteur de moteur inconnu invalide toute la liste qui le porte. */
.jauge::-moz-progress-bar {
  background: var(--couleur-accent);
}

/* ---------- La zone de message ----------
   Elle garde sa place, VIDE COMPRISE : elle parle à presque chaque geste, et une zone
   qui apparaît et disparaît dans le flux fait sauter tout ce qui la suit (PIEGES.md
   E22). Une ligne réservée, en `em` pour suivre la taille du texte, et jamais la
   hauteur du pire message.
   Une erreur se dit en 600, précédée du point « cible » (DESIGN.md §4) : jamais de
   rouge. Un avertissement est une capsule à contour noir. Un succès est du texte. */
.message {
  min-height: calc(var(--interligne-texte) * 1em);
  margin: var(--espace-m) 0 0;
  font-size: var(--taille-s);
}

.message[data-nature="erreur"] {
  font-weight: var(--graisse-titre);
}

.message[data-nature="erreur"]::before {
  content: "";
  display: inline-block;
  vertical-align: middle;
  margin-right: var(--espace-s);
  width: var(--taille-point);
  height: var(--taille-point);
  border-radius: 50%;
  border: var(--epaisseur-point-cible) solid var(--couleur-encre);
  background: var(--couleur-encre);
  box-shadow: inset 0 0 0 var(--epaisseur-point-cible) var(--couleur-fond);
}

/* Le bandeau des pages légales est une capsule à contour, posée au-dessus d'un sommaire
   en capsules à contour : à 8 px de lui, il se lisait comme sa première entrée (balayage
   du run 27). Il reçoit l'écart de deux blocs qui ne se touchent pas (DESIGN.md §6). */
.message[data-nature="attention"] {
  display: inline-block;
  margin-bottom: var(--espace-l);
  padding: var(--espace-s) var(--espace-m);
  border: var(--epaisseur-contour) solid var(--couleur-encre);
  border-radius: var(--rayon-pilule);
}

.message[data-nature="attention"]:empty {
  display: block;
  margin-bottom: 0;
  padding: 0;
  border: 0;
}

/* ---------- Le compte dans le header : les initiales et leur menu ----------
   Un disque de verre noir aux initiales blanches (ses couleurs sont avec les boutons),
   ou la photo de `/profil` depuis le 11/10/2026 (`.compte-photo`), et un panneau blanc
   à contour noir. Jamais l'avatar de better-auth (PIEGES.md A14, js/session.js). */
/* ⚠️ `display: flex` : le bouton n'est plus aligné sur une ligne de texte. Avec la photo, sa
   ligne de base devenait le BAS de l'image, la ligne gagnait 8 px et le header se décentrait
   (vu par Matthias le 11/10/2026) ; les initiales, du texte, ne le montraient pas. */
.compte {
  position: relative;
  display: flex;
}

.compte-menu {
  position: absolute;
  right: 0;
  top: calc(100% + var(--espace-s));
  z-index: 2;
  display: grid;
  justify-items: start;
  gap: var(--espace-s);
  min-width: var(--largeur-menu-compte);
  padding: var(--espace-m);
  background: var(--couleur-fond);
  border: var(--epaisseur-contour) solid var(--couleur-encre);
  border-radius: var(--rayon-carte);
  box-shadow: var(--ombre-verre-blanc);
}

.compte-email {
  margin: 0;
  font-size: var(--taille-xs);
  word-break: break-all;
}

/* Les entrées du menu du compte, qui sont des liens au milieu de boutons :
   « Mon profil », « Mes rendez-vous » et « Mes paiements » pour tous (features 16 et
   19 : un expert est aussi membre, un administrateur peut réserver), puis les entrées
   de l'espace expert, « Devenir expert » pour un membre, « Profil et tarifs »,
   « Disponibilités », « Compte de reversement » et « Mes revenus » pour un expert, et
   « Administration » pour un administrateur (public/js/session.js). */
.compte-profil,
.compte-rendez-vous,
.compte-paiements,
.compte-devenir-expert,
.compte-expert,
.compte-disponibilites,
.compte-reversement,
.compte-revenus,
.compte-administration {
  display: block;
  padding: var(--espace-xs) 0;
}

/* ---------- Motion : uniquement sans préférence de réduction (DESIGN.md §8) ----------
   La liste est exhaustive : la couleur des liens, le fond et la luminosité des boutons,
   la bordure d'un champ, l'inversion du marqueur, et les rubans (défilement en boucle
   et vent). Rien d'autre ne bouge, ni au défilement, ni à l'apparition d'une section ;
   avec la préférence de réduction il n'y a aucune transition et les rubans sont
   immobiles. */
@media (prefers-reduced-motion: no-preference) {
  a,
  .choix,
  .segment {
    transition:
      color var(--duree-courte) var(--courbe),
      background-color var(--duree-courte) var(--courbe),
      border-color var(--duree-courte) var(--courbe);
  }

  .bouton {
    transition:
      background-color var(--duree-courte) var(--courbe),
      filter var(--duree-courte) var(--courbe),
      transform var(--duree-instant);
  }

  input,
  textarea {
    transition: border-color var(--duree-courte) var(--courbe);
  }

  /* Les bulles du header flottent, chacune décalée de sa voisine (demande d'Abel du
     14/10/2026). `translate` et non `transform`, que `.bouton:active` emploie déjà : la
     bulle « Se connecter » s'enfonce encore au clic. */
  .nav-menu > .nav-lien,
  .nav-bulle,
  .nav-menu > #lien-connexion,
  .compte-bouton {
    animation: flotte var(--duree-bulle) ease-in-out infinite;
  }

  .nav-menu > :nth-child(2) {
    animation-delay: var(--decalage-bulle);
  }

  .nav-menu > :nth-child(3) {
    animation-delay: calc(var(--decalage-bulle) * 2);
  }

  .nav-menu > :nth-child(4) {
    animation-delay: calc(var(--decalage-bulle) * 3);
  }

  .nav-menu > :nth-child(5) {
    animation-delay: calc(var(--decalage-bulle) * 4);
  }

  /* Le bord des bulles des avis ondule comme une goutte en apesanteur : trois rythmes, trois
     décalages, pour qu'aucune ne bouge comme sa voisine. Leur dérive, elle, est tirée au
     hasard par js/avis-bulles.js. */

  .avis-bulle .photo-pastille {
    animation: goutte var(--duree-goutte) ease-in-out infinite alternate;
  }

  .avis-bulle:nth-child(3n + 2) .photo-pastille {
    animation-duration: calc(var(--duree-goutte) * 1.4);
    animation-delay: calc(var(--decalage-bulle) * 2);
  }

  .avis-bulle:nth-child(3n) .photo-pastille {
    animation-duration: calc(var(--duree-goutte) * 0.8);
    animation-delay: calc(var(--decalage-bulle) * 3);
  }

  @keyframes goutte {
    0% { border-radius: var(--goutte-a); }
    33% { border-radius: var(--goutte-b); }
    66% { border-radius: var(--goutte-c); }
    100% { border-radius: var(--goutte-a); }
  }

  @keyframes flotte {
    0%, 100% { translate: 0 0; }
    50% { translate: 0 var(--bulle-flotte); }
  }

  /* Le « plus » d'une question devient un « moins » : sa barre verticale se couche. */
  .question-signe::after {
    transition: transform var(--duree-courte) var(--courbe);
  }

  .ruban-epais {
    animation: vent var(--duree-vent-c) ease-in-out infinite;
    animation-delay: var(--decalage-vent-c);
  }

  .ruban-large {
    animation: vent var(--duree-vent-a) ease-in-out infinite;
  }

  .ruban-fin {
    animation: vent-inverse var(--duree-vent-b) ease-in-out infinite;
  }

  .ruban-epais .ruban-image {
    animation: defile-epais var(--duree-ruban-epais) linear infinite;
  }

  .ruban-large .ruban-image {
    animation: defile-large var(--duree-ruban-large) linear infinite;
  }

  .ruban-fin .ruban-image {
    animation: defile-fin var(--duree-ruban-fin) linear infinite;
  }

  /* Le petit ruban d'une bande file plus lentement que le grand qu'il croise. */
  .filet-petit .ruban-fin .ruban-image {
    animation-duration: var(--duree-filet-petit-fin);
  }

  .filet-petit .ruban-epais .ruban-image {
    animation-duration: var(--duree-filet-petit-epais);
  }

  /* Le large et l'épais filent vers la gauche, le fin vers la droite. */
  @keyframes defile-large {
    to { transform: translateX(calc(var(--periode-ruban-large) * -1)); }
  }

  @keyframes defile-epais {
    to { transform: translateX(calc(var(--periode-ruban-epais) * -1)); }
  }

  @keyframes defile-fin {
    from { transform: translateX(calc(var(--periode-ruban-fin) * -1)); }
    to { transform: translateX(0); }
  }

  @keyframes vent {
    0%, 100% { transform: translateY(0) rotate(0deg); }
    50% { transform: translateY(var(--vent-haut)) rotate(var(--vent-rotation)); }
  }

  @keyframes vent-inverse {
    0%, 100% { transform: translateY(0) rotate(0deg); }
    50% { transform: translateY(var(--vent-bas)) rotate(var(--vent-rotation-inverse)); }
  }
}

/* ---------- Mobile ---------- */
/* ---------- Le header replié : sous 64rem, et non 48rem comme le reste des petits écrans ----------
   Depuis qu'elle porte la recherche (10/10/2026), la capsule ne tient plus ses liens et son
   bouton sous 1024 px : à 800 px le bouton s'écrasait et le champ tombait à 46 px (mesuré, puis
   vu sur capture). Le burger prend donc le relais plus tôt. ⚠️ js/app.js porte LE MÊME seuil
   (`matchMedia`) : les deux se changent ensemble. */
@media (max-width: 64rem) {
  /* Sans capsule autour, le burger est lui aussi une bulle de verre blanc. */
  .nav-toggle {
    display: inline-flex;
    background: var(--verre-blanc);
    box-shadow: var(--reflet-verre-blanc), var(--contour-verre-blanc), var(--ombre-verre-blanc);
  }

  /* Replié derrière le burger : app.js pose et retire `hidden` (même point de rupture).
     Le menu s'ouvre DANS la capsule, qui devient un panneau arrondi : le rayon de `.nav`
     le donne déjà. */
  .nav-menu {
    flex-direction: column;
    align-items: stretch;
    gap: var(--espace-s);
    width: 100%;
    padding-bottom: var(--espace-s);
  }

  /* ⚠️ REPLIÉ DÈS LE PREMIER RENDU, AVANT MÊME QUE app.js N'AIT POSÉ `hidden`. Le HTML
     livre le menu sans `hidden`, et les scripts sont en fin de page : le navigateur peut
     peindre entre la feuille et eux. Le menu s'affichait alors déplié dans la capsule,
     puis se repliait, et tout l'écran remontait de 172 px (trois entrées de 44 px, leurs
     écarts et la réserve) : Lighthouse mobile relevait un décalage de mise en page de
     0,188 sur l'accueil, entièrement dû à cela (FEATURES.md § Refonte visuelle, run
     DA-2). Le burger porte `aria-expanded="false"` dans le HTML, et js/menu.js tient cet
     attribut en même temps que `hidden` : la règle dit donc la même chose que le script,
     une image plus tôt, et ne s'applique que sous ce point de rupture.
     Ce qu'elle coûte : sans JavaScript, le menu reste replié derrière un burger inerte
     (rien de l'application ne fonctionne sans lui) ; et une page qui oublierait app.js ne
     se trahit plus par un menu déplié (PIEGES.md E40), mais par un burger qui ne répond
     pas. */
  .nav-toggle[aria-expanded="false"] + .nav-menu {
    display: none;
  }

  /* En colonne, les liens du COMPTE s'alignent sur le logo ; les bulles du menu, elles,
     gardent leur réserve et la largeur de leur mot. */
  .nav-lien {
    padding-inline: 0;
  }

  .nav-menu > .nav-lien,
  .nav-bulle {
    align-self: flex-start;
    padding-inline: var(--espace-m);
  }

  /* Dans le menu replié, la recherche prend la largeur du panneau, en tête ; et le menu
     reprend toute la ligne sous la marque (sa base nulle le laisserait à côté du burger). */
  .nav-menu {
    flex-basis: 100%;
  }

  .nav-recherche {
    max-width: none;
    margin-inline-end: 0;
  }

  /* L'entrée courante du menu du compte garde la réserve de sa pilule noire et la reprend
     en marge : son mot reste aligné sur les autres. */
  .compte-menu .nav-lien[aria-current="page"] {
    align-self: flex-start;
    padding-inline: var(--espace-m);
    margin-inline-start: calc(var(--espace-m) * -1);
  }

  /* Le panneau ouvert garde à droite la réserve du logo à gauche : la recherche touchait le
     bord de la capsule, dont la réserve droite (8 px) est celle du bouton rond du grand
     écran. */
  .nav-menu {
    padding-inline-end: calc(var(--espace-l) - var(--espace-s));
  }

  /* ⚠️ SUR TÉLÉPHONE, LE COMPTE EST À PLAT DANS LE PANNEAU (demande de Matthias du
     14/10/2026 : adapter le menu à la vue téléphone). Une pastille seule ouvrait un second
     panneau flottant DANS le premier, et « Devenir expert » s'y lisait deux fois. Sous le
     filet, l'adresse du compte puis ses entrées, comme les liens du dessus ; la pastille,
     qui ne sert qu'à ouvrir ce panneau, n'a plus d'objet. Le panneau est montré quel que
     soit son `hidden` (js/menu.js le tient toujours, pour le grand écran) : la règle bat
     celle de `[hidden]` par sa spécificité, toutes deux étant `!important`. */
  .compte {
    display: block;
    padding-top: var(--espace-m);
    border-top: var(--epaisseur-filet) solid var(--couleur-encre);
  }

  .compte-bouton {
    display: none;
  }

  .compte-menu,
  .compte-menu[hidden] {
    display: grid !important;
    position: static;
    min-width: 0;
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }

  /* Le compte d'un membre porte déjà « Devenir expert » : l'entrée du header ne se répète
     pas au-dessus. */
  .nav-menu:has(.compte) .nav-devenir-expert {
    display: none;
  }
}

@media (max-width: 48rem) {
  /* Sur petit écran les rubans sont réduits à 0,55 (DESIGN.md §5), et le hero resserre
     l'écart qui les précède. Le bas du hero leur laisse de quoi finir avant le cadre
     des experts : 216 px, là où la maquette logeait sa notification. */
  :root {
    --echelle-rubans: 0.55;
    /* Les bandes de rubans rétrécissent avec les autres rubans. */
    --echelle-filet-grand: 0.34;
    --echelle-filet-petit: 0.3;
    --filet-ruban-hauteur: 12rem;
    --filet-petit-haut: 3.5rem;
    /* Les boutons suivent l'accroche de près (demande de Matthias du 14/10/2026 : un grand
       blanc les séparait) ; les rubans, ancrés au bas du hero, passent toujours derrière. */
    --hero-ecart-cta: var(--espace-s);
    --hero-bas: 13.5rem;
    --hero-hauteur-min: 0px;
    --gouttiere-page: 1.5rem;
  }

  /* ⚠️ ICI LE BLOC DES RUBANS S'ANCRE PAR LE BAS DU HERO, PAS PAR LE HAUT. La hauteur de
     l'accroche varie du simple au double entre 320 et 767 px (le titre passe d'une à
     trois lignes, le paragraphe de trois à sept) : calé à une distance fixe du haut, le
     bloc passait sous les dernières lignes du paragraphe sur un écran étroit, texte noir
     sur rayures noires. Le bas du hero, lui, est à distance constante des deux boutons :
     les rubans passent donc toujours derrière eux, et leur encre commence sous
     l'accroche à toutes les largeurs (mesures dans FEATURES.md § Refonte visuelle, run
     DA-1). Le bloc garde sa hauteur de 700 px, dont l'échelle ne montre que le haut. */
  .hero-accueil .rubans {
    top: auto;
    bottom: calc(var(--rubans-remontee) - var(--rubans-hauteur));
  }

  /* Le ruban d'une page d'état garde sa taille : seul dans la fenêtre, réduit, il ne
     serait plus qu'un trait. */
  .hero-etat .rubans-etat {
    --echelle-rubans: 1;
  }

  /* Celui du cadre des experts aussi reste presque entier. */
  .rubans-verre {
    --echelle-rubans: var(--echelle-ruban-verre-petit);
  }

  .colonne {
    padding-block: var(--espace-l);
  }
}

/* ============================================================
   PORTRAITS (feature 6, composant signature 6 de DESIGN.md §7)
   ============================================================ */

/* La pastille de photo, et l'avatar à initiales qui la remplace tant qu'il n'y en
   a pas (EX-PROF-001). Les deux ont exactement la même boîte, sinon la mise en
   page bougerait au moment où la photo arrive.

   Un cercle blanc à initiales noires en 600, cerclé de noir : l'anneau fait le tour des
   deux boîtes en `box-shadow`, pour ne rogner ni la photo ni les initiales et ne décaler
   aucun voisin. Sa couleur est le trait de contexte (`--couleur-anneau-portrait`).

   ⚠️ LA COULEUR PAR FAMILLE DE DOMAINES DU 18/09/2026 A DISPARU avec la refonte
   (DESIGN.md §4) : il n'y a que trois couleurs, et le domaine reste écrit sous chaque
   nom. `js/portrait.js` pose toujours `data-domaine` sur la boîte du portrait ; plus
   aucune règle ne le lit. */
.photo-pastille {
  display: grid;
  place-items: center;
  width: var(--taille-pastille);
  height: var(--taille-pastille);
  border-radius: 50%;
  background: var(--couleur-fond);
  color: var(--couleur-encre);
  box-shadow: 0 0 0 var(--epaisseur-anneau-portrait) var(--couleur-anneau-portrait);
  font-size: var(--taille-l);
  font-weight: var(--graisse-titre);
  letter-spacing: var(--interlettrage-titre-h3);
}

.photo-profil {
  object-fit: cover;
}

/* L'image de partage de la fiche, sous le portrait de `/expert` (feature 25, DESIGN.md
   §6) : un bloc à part entière du formulaire, à la gouttière des autres, sa légende dans
   la voix des faits secondaires (`.fiche-meta`). Un cadre à contour noir, au rayon des
   cartes : c'est une carte d'aperçu, comme une conversation la montrera. */
.image-partage {
  display: grid;
  gap: var(--espace-s);
  max-width: var(--largeur-image-partage);
  margin: 0;
}

.image-partage img {
  border-radius: var(--rayon-carte);
  border: var(--epaisseur-contour) solid var(--couleur-encre);
}

/* ============================================================
   ÉTATS D'ÉCRAN (feature 7) : chargement, erreur réseau, liste vide
   Deux composants communs, posés ici pour que les features suivantes n'inventent
   pas chacune leur façon d'attendre, d'échouer et de n'avoir rien à montrer.
   ============================================================ */

/* ---------- Le squelette de chargement ----------
   Une forme par ligne du contenu qu'il remplace, dans le même ordre : titre,
   lignes de texte, libellé, champ, bouton. Chaque page pose autant de formes que
   son écran a de lignes, faute de quoi le squelette annonce une hauteur qu'il n'a
   pas et l'arrivée des données fait sauter l'écran, ce qu'il est précisément censé
   empêcher (mesures dans FEATURES.md § Feature 7).

   La géométrie tient à trois nombres : une ligne de texte fait
   `--hauteur-squelette-ligne` et la gouttière `--espace-m`, soit 28 px par ligne ; un
   libellé suivi de son champ fait 12 + 16 + 44 px, soit 72 px. AUCUN padding : le
   squelette commence là où commencera la première ligne, sinon tout l'écran se
   décale à la bascule.

   Sans gris, une forme pleine serait noire ou invisible : ce sont des formes CREUSES à
   contour pointillé noir (DESIGN.md §4), aux dimensions exactes de ce qu'elles
   remplacent, le contour étant compté dans la boîte.

   ⚠️ AUCUNE ANIMATION, ET C'EST VOULU : la liste de motion de DESIGN.md §8 est
   exhaustive, le miroitement des squelettes n'y figure pas, et une nouvelle
   animation se propose avant de s'écrire (ia_slop.md §3). */
.squelette {
  display: grid;
  /* Une colonne pleine largeur, déclarée : une colonne `auto` se dimensionnerait
     sur le contenu des formes, qui est vide, et elles disparaîtraient. */
  grid-template-columns: 100%;
  justify-items: start;
  gap: var(--espace-m);
}

.squelette-forme {
  background: transparent;
  border: var(--epaisseur-contour) dashed var(--couleur-encre);
  border-radius: var(--rayon-pilule);
}

.squelette-pastille {
  width: var(--taille-pastille);
  height: var(--taille-pastille);
  border-radius: 50%;
}

.squelette-titre {
  width: 100%;
  height: var(--hauteur-squelette-titre);
  border-radius: var(--rayon-carte);
}

.squelette-ligne {
  width: 100%;
  height: var(--hauteur-squelette-ligne);
}

/* Le libellé d'un champ, la fin d'un paragraphe : une ligne qui ne va pas au bout. */
.squelette-courte {
  width: 40%;
}

.squelette-champ {
  width: 100%;
  height: var(--hauteur-bouton);
}

/* La zone de texte d'une biographie : même boîte que `.champ-zone`, faute de quoi
   le squelette annoncerait la hauteur d'un champ d'une ligne. */
.squelette-zone {
  width: 100%;
  height: var(--hauteur-zone-texte);
  border-radius: var(--rayon-carte);
}

.squelette-bouton {
  width: var(--largeur-squelette-bouton);
  height: var(--hauteur-bouton);
}

/* Le prix d'entrée d'une ligne d'annuaire : la boîte du montant, à sa hauteur. */
.squelette-prix {
  width: var(--largeur-squelette-prix);
  height: var(--taille-l);
}

/* ---------- L'état d'écran : erreur réseau, liste vide, statut nommé ----------
   Le même bloc dans tous les cas, et TOUJOURS une action : un écran qui dit
   seulement « rien ici » est une impasse. Aligné à gauche de sa colonne, comme tout le
   reste des espaces connectés (DESIGN.md §6), jamais centré. */
.etat {
  display: grid;
  grid-template-columns: 100%;
  justify-items: start;
  gap: var(--espace-m);
  padding-block: var(--espace-xl);
}

/* Le glyphe du logo, en CSS (DESIGN.md §7, composant 1) : un anneau dans le trait de
   contexte, et le point bleu posé sur son bord haut droit, cerclé d'un halo de la
   couleur du fond. L'anneau est une bordure, le point un pseudo-élément : aucun dégradé
   ne dessine une forme dans cette feuille. Ni icône empruntée, ni emoji. */
.etat-anneau {
  position: relative;
  width: var(--taille-anneau-etat);
  height: var(--taille-anneau-etat);
  border: var(--epaisseur-logo) solid var(--couleur-anneau-portrait);
  border-radius: 50%;
}

.etat-anneau::after {
  content: "";
  position: absolute;
  right: var(--debord-point-etat);
  top: var(--debord-point-etat);
  width: var(--taille-point-etat);
  height: var(--taille-point-etat);
  border-radius: 50%;
  background: var(--couleur-accent);
  box-shadow: 0 0 0 var(--epaisseur-logo) var(--couleur-fond);
}

/* Le titre prend l'ÉCHELLE ENTIÈRE des h2 de DESIGN.md §3 quel que soit le niveau
   de titre que son contexte impose : h1 sur l'écran hors ligne de la connexion, qui
   n'affiche que lui, h2 partout où l'état s'insère sous le titre d'une page, h3 dans le
   panneau de l'accueil. La taille ne suffit pas à la reprendre : l'interlettrage et
   l'interligne se règlent par niveau, et le même bloc n'aurait pas le même dessin d'une
   page à l'autre. Les trois valeurs du h2 sont donc reprises ici. */
.etat .etat-titre {
  margin: 0;
  font-size: var(--taille-xl);
  letter-spacing: var(--interlettrage-titre-h2);
  line-height: var(--interligne-titre);
}

/* ⚠️ ET IL NE PORTE PAS L'ANNEAU DE FOCUS, alors qu'il REÇOIT le focus.
   L'anneau appartient à ce qui se COMMANDE (DESIGN.md §7) ; un titre ne se commande
   pas, et sur une page qui est déjà un écran d'erreur, un rectangle noir autour du
   titre se lit comme le cadre d'un champ en faute. Ce n'est pas un cas réservé au
   clavier : sans interaction préalable, un `.focus()` programmatique fait matcher
   `:focus-visible`, donc TOUT le monde le voit au premier chargement manqué. On garde
   le focus, qui est ce qui fait annoncer l'état par un lecteur d'écran quand il
   remplace le squelette, et on retire l'anneau. Écrite sur la classe, la règle vaut
   d'avance pour tout état qui prendra le focus. */
.etat-titre:focus-visible,
.avis-titre:focus-visible,
.avis:focus-visible,
.reglage h2:focus-visible,
.examen-etat .chapeau:focus-visible,
.carte-zone:focus-visible,
.carte-commandes .message:focus-visible,
main h1:focus-visible {
  outline: none;
}

/* Le texte d'un état EST un chapeau : même rôle, même voix, on ne recrée pas la
   règle. Seule sa marge basse ferait double emploi avec la gouttière de la grille. */
.etat .chapeau {
  margin-bottom: 0;
}

/* ============================================================
   PROFIL ET TARIFS DE L'EXPERT (feature 8)
   Trois composants de cette section ne lui appartiennent pas en propre et sont
   écrits pour être repris tels quels : la pilule à cocher `.choix` (jours et créneaux
   de la réservation, filtres de l'annuaire), le prix `.prix` (annuaire, fiche publique,
   accueil), et le point d'état, avec ses quatre dessins.
   ============================================================ */

/* Un `<fieldset>` porte une bordure, un padding et une marge du navigateur : sans
   cette remise à zéro, chaque groupe de pilules serait encadré d'un filet que
   personne n'a demandé. Le `<legend>` reprend exactement la place d'un
   `.champ-libelle`, pour que les groupes s'alignent sur les champs ordinaires. */
fieldset.champ-groupe {
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}

fieldset.champ-groupe > legend {
  padding: 0;
}

/* ⚠️ DANS UN FORMULAIRE À GRANDE GOUTTIÈRE, LES MARGES PROPRES S'EFFACENT, sinon elles
   s'AJOUTENT à la gouttière. `.message` et `.actions` portent les marges d'un formulaire
   à gouttière ordinaire ; avec celle de `.formulaire-fiche`, il restait 136 px de vide
   entre la dernière pilule de durée et le bouton, assez pour qu'on croie la page finie
   avant de l'avoir vu. La gouttière suffit à les séparer. */
.formulaire-fiche .message,
.formulaire-fiche .actions,
.motif .message,
.motif .actions,
.carte-commandes .message,
.carte-zone .actions {
  margin-top: 0;
}

/* ---------- La pilule à cocher ----------
   Une case, son libellé, un contour noir. Choisie, elle s'inverse : c'est le marqueur
   « vous êtes ici » de toute l'application (DESIGN.md §4), écrit une fois plus haut. La
   case reste VISIBLE : la cacher pour faire plus net coûterait l'anneau de focus du
   clavier, réservé précisément à ce qui se commande. La case elle-même (`.choix-case`)
   est celle de `.case input`, réglée avec les champs de formulaire. */
.choix-liste {
  display: flex;
  flex-wrap: wrap;
  gap: var(--espace-s);
}

.choix {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-s);
  min-height: var(--hauteur-bouton);
  padding: 0 var(--espace-m);
  border: var(--epaisseur-contour) solid var(--couleur-encre);
  border-radius: var(--rayon-pilule);
  font-size: var(--taille-s);
  font-weight: var(--graisse-moyenne);
  background: var(--couleur-fond);
  color: var(--couleur-encre);
  cursor: pointer;
}

/* LA MÊME PILULE, EN BOUTON : les jours et les créneaux de la réservation
   (feature 14). Les domaines de l'expert sont des cases, les filtres de l'annuaire
   des liens (`.filtre`), et ceux-ci des commandes, parce que choisir un jour ou
   retenir un créneau FAIT quelque chose et n'a pas d'adresse propre. Un bouton
   apporte sa police ; on la remet à celle de la pilule, et rien d'autre ne change.
   `flex: none` pour qu'elle garde sa largeur, qu'elle passe à la ligne ou qu'elle
   sorte d'un cadre défilant, comme `.filtre`. */
button.choix {
  flex: none;
  font-family: inherit;
}

/* Le survol d'une pilule qui se commande : son texte et son contour passent au bleu.
   Il ne vaut que pour la pilule EN BOUTON et EN LIEN, la case se cochant ; et jamais
   pour la pilule choisie, qui reste inversée (voir le marqueur). */
button.choix:not([aria-current="true"]):hover,
.filtre:not([aria-current="page"]):hover {
  color: var(--couleur-accent);
  border-color: var(--couleur-accent);
}

/* ⚠️ LES TROIS DURÉES DE `/expert` S'EMPILENT, À TOUTES LES LARGEURS (décision de
   direction artistique du 29/09/2026, FEATURES.md § Feature 9, run 91). Chaque pilule
   porte son prix, montant ET durée (« 112,50 € · 45 min », composant signature 4) : les
   trois demandent plus que la colonne, et la troisième passait SEULE à la ligne, pilule
   orpheline sous deux autres. Une colonne, donc, à la largeur de la plus longue pour
   que les trois se lisent comme une liste et non comme trois boutons de tailles
   différentes. `fit-content(100%)` et non `max-content` : la colonne ne déborde jamais
   du formulaire, même si un montant s'allongeait. Sans prix (tarif pas encore
   enregistré), les trois tiendraient en ligne : empilées quand même, la liste ne
   change pas de forme au moment où les prix arrivent. */
.choix-empiles {
  display: grid;
  grid-template-columns: fit-content(100%);
}

/* ---------- La recherche des langues de `/expert` (js/choix-langues.js) ----------
   Les langues choisies sont des pilules-boutons qui se retirent d'un clic (leur croix est
   dessinée, deux traits tournés), puis le champ de recherche, et sous lui ses suggestions
   dans le même cadre que le menu du compte. La suggestion active s'inverse : c'est le
   marqueur « vous êtes ici » de toute l'application. */
.choix-liste:empty {
  display: none;
}

.langue-retirer {
  position: relative;
  width: var(--taille-croix);
  height: var(--taille-croix);
}

.langue-retirer::before,
.langue-retirer::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto;
  height: var(--epaisseur-contour);
  background: currentColor;
  transform: rotate(45deg);
}

.langue-retirer::after {
  transform: rotate(-45deg);
}

.langues-recherche {
  position: relative;
  max-width: var(--largeur-recherche-nav);
  margin-top: var(--espace-s);
}

.langues-suggestions {
  position: absolute;
  inset-inline: 0;
  top: calc(100% + var(--espace-xs));
  z-index: 2;
  max-height: var(--hauteur-suggestions);
  margin: 0;
  padding: var(--espace-xs);
  overflow-y: auto;
  list-style: none;
  background: var(--couleur-fond);
  border: var(--epaisseur-contour) solid var(--couleur-encre);
  border-radius: var(--rayon-carte);
  box-shadow: var(--ombre-verre-blanc);
}

.langue-suggestion {
  padding: var(--espace-s) var(--espace-m);
  border-radius: var(--rayon-pilule);
  cursor: pointer;
}

/* Le survol souligne, l'active s'inverse : deux suggestions ne paraissent jamais
   choisies ensemble. */
.langue-suggestion:hover {
  text-decoration: underline;
}

.langue-suggestion[aria-selected="true"] {
  background: var(--couleur-encre);
  color: var(--couleur-fond);
}

/* Le délai de réservation et sa lecture en clair, sur une ligne : « 30 h, soit 1 j et
   6 h avant la date de rdv ». Sur un téléphone, la phrase passe sous le nombre. */
.champ-unite {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-s) var(--espace-m);
}

.champ-unite-texte {
  font-weight: var(--graisse-moyenne);
}

/* Le secteur libre de « Autres secteurs », sous les pilules des domaines. */
.champ-autre-secteur {
  margin-top: var(--espace-m);
}

/* Le plafond de trois domaines est atteint : les cases restantes ne se cochent
   plus. Empêcher plutôt que refuser après coup (js/expert.js). Désactivé se dit en
   pointillés et en 400, jamais par une opacité. */
.choix:has(:disabled) {
  border-style: dashed;
  font-weight: var(--graisse-normale);
  cursor: not-allowed;
}

/* ---------- Le prix (DESIGN.md §7, composant 4) ----------
   « 97,50 € · 45 min » : le MONTANT D'ABORD, en 600 à `--taille-l` avec ses chiffres
   tabulaires, puis la durée en 400 à `--taille-s` à côté. C'est la forme exacte du
   composant signature, et elle ne change pas avec le contexte : un composant qui part
   en deux variantes finit par en avoir quatre.

   Le point médian appartient au composant, pas au contenu : porté par le montant,
   il disparaît avec lui quand le serveur n'a pas encore de prix à donner, et la
   pilule se lit alors « 30 min », sans séparateur orphelin. */
.prix {
  display: inline-flex;
  align-items: baseline;
  gap: var(--espace-xs);
  font-weight: var(--graisse-titre);
  font-variant-numeric: tabular-nums;
  font-size: var(--taille-l);
  line-height: 1;
  /* Aucun de ses morceaux ne se coupe : « 90 € » ne passe pas sur deux lignes. */
  white-space: nowrap;
}

.prix-montant::after {
  content: "·";
  margin-left: var(--espace-xs);
  font-weight: var(--graisse-normale);
}

/* ⚠️ ET IL DISPARAÎT AUSSI QUAND RIEN NE SUIT LE MONTANT. Le récapitulatif de la
   réservation (feature 14) écrit la durée sur sa propre ligne, et le montant y reste
   seul. Sans cette règle, le composant finissait par un point médian suspendu,
   « 97,50 € · », qui se lit comme une phrase coupée. Le séparateur SÉPARE : il n'existe
   que lorsqu'il y a deux choses. */
.prix-montant:last-child::after {
  content: none;
}

/* Ce qui accompagne le montant, avant ou après, est du texte courant : la durée
   qu'il achète, et, sur une carte d'annuaire, le « À partir de » qui dit que c'est
   un prix d'entrée (EX-CAT-004). Une seule règle pour les deux, sans quoi deux
   morceaux de la même ligne finiraient par ne plus avoir la même voix. */
.prix-duree,
.prix-avant {
  font-size: var(--taille-s);
  font-weight: var(--graisse-normale);
}

/* ---------- La mise en service (EX-EXP-013) ---------- */
.mise-en-service {
  margin-bottom: var(--espace-xl);
  padding-bottom: var(--espace-l);
  border-bottom: var(--epaisseur-filet) solid var(--couleur-encre);
}

.mise-en-service h2 {
  margin-bottom: var(--espace-s);
}

/* Une liste de lignes de texte, sans puce : les conditions de la mise en service, et les
   reversements et virements de « Mes revenus » (feature 22). */
.conditions,
.liste-revenus {
  display: grid;
  gap: var(--espace-s);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* LA MÊME LIGNE « POINT PUIS TEXTE » : une condition de mise en service, l'état d'une
   pièce de la certification sous le nom d'un fichier (sa taille, son point, son état),
   l'état d'un champ décidé par l'administrateur, celui d'un rendez-vous. */
.condition,
.fichier-details,
.piece-etat {
  display: flex;
  align-items: baseline;
  gap: var(--espace-s);
}

/* ---------- Le point d'état, quatre dessins (DESIGN.md §4, composant 5 du §7) ----------
   Un état se dit par un MOT, toujours, et par une FORME, jamais par une teinte de plus :
   « Fait », « À faire », « Confirmé », « Annulé » sont écrits en toutes lettres à côté
   du point. Le même disque de 12 px sert aux conditions, au badge et aux états d'une
   pièce, d'une demande, d'un rendez-vous ou d'un paiement.
   Le dessin de départ est le NEUTRE : un anneau creux, pour ce qui n'attend plus rien
   (déposée, terminé, remboursé depuis). Son trait est celui du contexte. */
.condition-point,
.badge-point,
.piece-point,
.notation-point {
  flex: none;
  align-self: center;
  width: var(--taille-point);
  height: var(--taille-point);
  border: var(--epaisseur-contour) solid var(--couleur-anneau-portrait);
  border-radius: 50%;
  background: transparent;
}

/* En attente de quelqu'un : un disque noir plein. Une condition « À faire », un compte
   de reversement que l'expert ou Stripe doit encore compléter (`data-attente` sur le
   badge), une demande à examiner, des pièces à supprimer, un remboursement en cours,
   un reversement à venir (registres de l'administration, run 231). */
.condition-point,
.badge[data-attente] .badge-point,
[data-etat="envoyee"] .piece-point,
[data-etat="a_purger"] .piece-point,
[data-etat="remboursement_en_cours"] .piece-point,
[data-etat="reversement_en_attente"] .piece-point {
  border-color: var(--couleur-anneau-portrait);
  background: var(--couleur-anneau-portrait);
}

/* Fait, positif : un disque bleu plein. Une condition faite, le badge « Expert
   certifié » et un compte actif, une pièce acceptée, une demande certifiée, un
   rendez-vous confirmé, un paiement reçu, un remboursement parti, et dans les registres
   de l'administration (run 231) une fiche publiée et une part reversée ; un avis publié (feature 29).
   ⚠️ CE GROUPE RESTE APRÈS LE PRÉCÉDENT : `.badge-point` y est à la spécificité de la
   règle de départ, et c'est l'ordre qui lui donne son bleu ; le badge qui attend, lui,
   gagne par sa spécificité. */
.condition[data-fait] .condition-point,
.badge-point,
[data-etat="acceptee"] .piece-point,
[data-etat="certifiee"] .piece-point,
[data-etat="confirmee"] .piece-point,
[data-etat="paye"] .piece-point,
[data-etat="remboursement_reussi"] .piece-point,
[data-etat="publiee"] .piece-point,
[data-etat="reversee"] .piece-point,
[data-etat="reverse"] .piece-point,
[data-etat="avis_publie"] .piece-point {
  border-color: var(--couleur-accent);
  background: var(--couleur-accent);
}

/* En échec, refusé, annulé : la cible. Un contour noir, un anneau de la couleur du
   fond, un cœur noir. Les registres de l'administration (run 231) y rangent une fiche
   retirée de l'annuaire, un compte suspendu et un reversement en échec ; un avis retiré
   (feature 29). */
[data-etat="refusee"] .piece-point,
[data-etat="retiree"] .piece-point,
[data-etat="annulee"] .piece-point,
[data-etat="remboursement_echoue"] .piece-point,
[data-etat="depubliee"] .piece-point,
[data-etat="suspendu"] .piece-point,
[data-etat="reversement_echoue"] .piece-point,
[data-etat="avis_retire"] .piece-point {
  border-width: var(--epaisseur-point-cible);
  background: var(--couleur-anneau-portrait);
  box-shadow: inset 0 0 0 var(--epaisseur-point-cible) var(--couleur-fond);
}

/* ⚠️ ET LE MOT NE SE COUPE PAS : « À faire » se cassait en « À » puis « faire » sur
   deux lignes sur petit écran, sur toutes les conditions à la fois. C'est le seul
   endroit de l'écran qui dit à l'expert ce qui lui manque pour être visible, et c'est
   ce qu'il lit en premier en arrivant. */
.condition-etat {
  margin-left: auto;
  padding-left: var(--espace-m);
  font-size: var(--taille-xs);
  white-space: nowrap;
}

/* ============================================================
   DEVENIR EXPERT ET BADGE DE CERTIFICATION (feature 9)
   Le badge ne vit pas ici en propre : la carte de l'annuaire, la fiche publique, la
   réservation et le compte de reversement le reprennent tel quel.
   ============================================================ */

/* ---------- Le badge « Expert certifié » ----------
   ⚠️ CONTOUR, POINT, TEXTE NOIR : jamais la pastille « fond pâle + texte de la même
   teinte », qui est le réflexe d'IA, et que trois couleurs interdisent de toute façon.
   Le point ne porte jamais l'information seul, « Expert certifié » est écrit en toutes
   lettres. Son dessin est avec les points d'état, plus haut.

   Il n'a AUCUNE marge, et c'est ce qui le rend reposable ailleurs : tous ses
   emplacements sont des conteneurs à gouttière, où une marge propre s'ajouterait.

   Il est plus bas qu'un bouton, volontairement : à 2,75rem il se lirait comme un
   bouton désactivé, juste à côté du vrai bouton de la ligne du portrait. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-s);
  padding: var(--espace-s) var(--espace-m);
  border: var(--epaisseur-contour) solid var(--couleur-anneau-portrait);
  border-radius: var(--rayon-pilule);
  font-size: var(--taille-s);
  font-weight: var(--graisse-moyenne);
  line-height: 1;
  color: inherit;
}

/* ---------- Les deux niveaux, en liste numérotée ----------
   DESIGN.md §6 : jamais des cartes. Deux cartes côte à côte donneraient deux offres à
   comparer, alors que le second niveau vient APRÈS le premier et ne se choisit pas à sa
   place. Le numéro est le disque des étapes de l'accueil (même règle) : noir, et bleu
   pour le dernier.

   ⚠️ AUCUNE MARGE BASSE, ET C'EST UNE MESURE, PAS UN GOÛT. La zone de message
   réserve sa ligne (`.message`) et le bloc d'actions porte déjà la marge qui sépare
   un écran de son bouton : une marge de plus ici s'y AJOUTAIT, et il restait 136 px
   de vide entre la dernière phrase et « Devenir expert ». */
/* Les deux niveaux de « Devenir expert » et les quatre étapes de l'accueil : la même liste. */
.niveaux,
.etapes {
  display: grid;
  gap: var(--espace-xl);
  margin: 0;
  padding: 0;
  list-style: none;
}

.niveau,
.etape {
  display: grid;
  grid-template-columns: var(--taille-numero-etape) 1fr;
  gap: var(--espace-m);
  align-items: start;
}

.niveau-numero,
.etape-numero {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--taille-numero-etape);
  height: var(--taille-numero-etape);
  border-radius: 50%;
  background: var(--couleur-encre);
  color: var(--couleur-fond);
  font-size: var(--taille-numero);
  font-weight: var(--graisse-titre);
  line-height: 1;
}

.niveau:last-child .niveau-numero,
.etape:last-child .etape-numero {
  background: var(--couleur-accent);
}

/* `justify-items: start` pour que le badge garde sa largeur de pilule : dans une
   grille, un enfant s'étire par défaut, `inline-flex` compris. Même geste que
   `.etat` et `.squelette`. L'état d'une demande de certification (feature 9) est le
   même bloc, en tête de `/certification` (le badge, puis sa phrase) comme dans la zone
   du badge de `/expert` (la phrase, puis son lien). */
.niveau-texte {
  display: grid;
  justify-items: start;
  gap: var(--espace-m);
}

/* La gouttière de la grille suffit à séparer les blocs : les marges propres d'un
   titre et d'un paragraphe s'y ajouteraient et casseraient le rythme. */
.niveau-texte h2,
.niveau-texte p,
.carte-zone p {
  margin: 0;
}

/* ============================================================
   DISPONIBILITÉS DE L'EXPERT (feature 13)
   DESIGN.md §6, espaces connectés : « colonne de contenu centrée, h1 à gauche de la
   colonne, listes en lignes à filet ». La semaine type EST une liste en lignes : sept
   jours séparés par un filet noir, le nom à gauche, les plages à droite. Aucun aplat,
   aucune carte, aucune grille de sept cases identiques.
   ⚠️ Ce que cette section ne pose PAS : la case à cocher, la pilule, le badge, le prix,
   le squelette et l'état d'écran, qui existent depuis les features 7 à 9 et servent ici
   tels quels.
   ============================================================ */

/* Un cran plus large que les écrans de formulaire (`--largeur-page`) : une ligne de jour
   porte un nom, deux heures et une commande, et dans une page de formulaire la ligne se
   replie dès le grand écran, ce qui ferait lire la semaine en escalier. La largeur de
   LECTURE suffit, la colonne ne contenant pas de liste à cinq colonnes comme l'annuaire.
   L'écran de la certification (feature 9) a la même raison : une ligne de fichier porte
   un nom, sa taille, son état et « Retirer ». Et « Mes rendez-vous » (feature 16) : une
   ligne y porte un portrait, quatre lignes de texte et un état, pas les cinq colonnes
   d'une ligne d'annuaire. « Mes paiements » (feature 19) est la même liste. */
.page-disponibilites,
.page-certification,
.page-rendez-vous,
.page-paiements,
.page-revenus,
.page-legale {
  max-width: var(--largeur-texte);
}

/* Un bloc de réglage : son titre, sa phrase d'aide, ses commandes. `justify-items:
   start` pour qu'un bouton ou une pilule garde sa largeur, comme `.niveau-texte` et
   `.etat` (dans une grille, un enfant s'étire par défaut). */
.reglage {
  display: grid;
  justify-items: start;
  gap: var(--espace-m);
  width: 100%;
}

/* La gouttière de la grille suffit : les marges propres du titre et du paragraphe s'y
   ajouteraient et casseraient le rythme de l'écran. */
.reglage h2,
.reglage p {
  margin: 0;
}

/* ⚠️ DEUX RÉGLAGES D'UN MÊME FORMULAIRE, ET NON DEUX SECTIONS DE PAGE : `--espace-l`
   en plus de la gouttière de `.formulaire-fiche`, à toutes les largeurs (décision 5 de
   direction artistique, 19/09/2026, FEATURES.md § Notes de cadrage, reprise par
   DESIGN.md §6). La semaine type et les exceptions portent chacune un h2, mais elles
   appartiennent à un seul formulaire, qui s'enregistre d'un coup : l'espacement de
   section les détacherait au point de les faire lire comme deux écrans. Avec la seule
   gouttière, en revanche, les deux se lisaient comme une seule liste qui continue. */
.reglage + .reglage {
  margin-top: var(--espace-l);
}

.jours {
  width: 100%;
}

/* ⚠️ LE REPLI VIENT DES LARGEURS DE LA LIGNE, PAS D'UN POINT DE RUPTURE, exactement
   comme la fiche publique (feature 11) : dès que le nom du jour et le bloc de ses plages
   ne tiennent plus côte à côte, les plages passent dessous d'elles-mêmes. Un `@media`
   aurait figé ce pli à une largeur d'écran devinée, qui deviendrait fausse le jour où le
   champ d'heure change de taille.
   Le filet SÉPARE deux jours, il ne les encadre pas : `+` le pose entre eux et nulle
   part ailleurs, donc ni au-dessus de lundi ni sous dimanche, où il ferait une boîte. */
.jour {
  display: flex;
  flex-wrap: wrap;
  gap: var(--espace-s) var(--espace-m);
  padding-block: var(--espace-m);
}

.jour + .jour {
  border-top: var(--epaisseur-filet) solid var(--couleur-encre);
}

/* Le nom du jour est le libellé de ses champs, et il en porte donc la voix : la classe
   `.champ-libelle` est posée avec celle-ci dans le HTML, et rien n'est recopié ici. La
   largeur fixe est ce qui aligne les sept noms en colonne ; le décalage vertical rattrape
   la hauteur d'un champ, sans quoi le nom flotterait au milieu de la première plage. */
.jour-nom {
  flex: 0 0 var(--largeur-jour);
  padding-top: var(--espace-s);
}

/* Les plages s'empilent, et « Fermé » partage sa ligne avec la commande qui ouvre le
   jour : sur une semaine neuve, où les sept jours sont fermés, deux lignes par jour
   donnaient quatorze rangs pour dire sept fois la même chose. Une ligne par jour, c'est
   ce que « listes en lignes » veut dire (DESIGN.md §6).

   ⚠️ L'INTERLIGNE DE CE BLOC EST PLUS GRAND QUE CELUI DE `.intervalle`, ET C'EST LUI QUI
   GROUPE UNE PLAGE AVEC SON « RETIRER ». Sur petit écran, une plage se replie en deux
   rangs (les deux heures, puis sa commande) : avec le même `--espace-s` dedans et
   dehors, « Retirer » se trouvait à égale distance des champs qu'il détruit et de ceux
   de la plage suivante, et « Ajouter une plage » collé au dernier « Retirer ». Rien ne
   rattachait alors une commande destructrice à son objet. L'écart DEHORS vaut donc le
   double de l'écart DEDANS, et c'est la proximité qui dit l'appartenance. Invisible sur
   grand écran, où une plage tient sur un rang et n'use jamais son interligne. */
.jour-plages {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-m);
  flex: 1 1 var(--largeur-plages);
}

/* Une plage prend toute la largeur du bloc : deux plages ne se mettent jamais côte à
   côte, elles se lisent l'une sous l'autre comme les heures d'une journée. */
.jour-plages .intervalle {
  flex-basis: 100%;
}

/* UNE LIGNE QUI PORTE DEUX BORNES ET LA COMMANDE QUI VA AVEC : une plage horaire, et la
   ligne d'ajout d'une exception. Les deux sont le même objet, et deux règles jumelles
   auraient divergé au premier réglage (même geste que `.case input, .choix-case`). */
.intervalle {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-s) var(--espace-m);
}

/* Le mot qui nomme une borne (« De », « à », « Du », « au »), collé à son champ : c'est
   un `<label>` qui ENVELOPPE le champ, donc il le nomme sans identifiant à fabriquer. */
.borne {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-s);
  font-size: var(--taille-s);
}

.champ-heure {
  width: var(--largeur-champ-heure);
}

.champ-date {
  width: var(--largeur-champ-date);
}

/* ⚠️ CES DEUX CHAMPS ONT LA RÉSERVE D'UNE PILULE, PAS CELLE D'UN CHAMP DE TEXTE. Leur
   largeur est comptée au plus juste, pour que deux bornes tiennent sur un rang dans la
   colonne la plus étroite : avec les 22 px de `.champ` de chaque côté, il ne restait que
   73 px au sélecteur d'heure de Chrome, qui en demande 77 (« 09:00 » et son icône). */
.champ-heure,
.champ-date {
  padding-inline: var(--espace-m);
}

/* LA LIGNE QUI DIT QU'IL N'Y A RIEN : « Fermé » pour un jour sans plage, « Aucune
   exception » pour la liste vide. Deux phrases, un seul dessin, et surtout pas le bloc
   `.etat` des features 7 et 10, qui répond quand c'est tout un ÉCRAN qui n'a rien à
   montrer (DESIGN.md §6) : ici il n'y a qu'une ligne de formulaire à renseigner. */
.rien {
  margin: 0;
  font-size: var(--taille-s);
}

/* LA LISTE EN LIGNES À FILET, LA COMMANDE À DROITE : les exceptions de
   `/disponibilites` et les fichiers déposés de `/certification` (feature 9) sont le même
   objet, une ligne de texte et le « Retirer » qui la supprime. Mêmes règles, mêmes
   mesures : la phrase cède sa place, jamais la commande. */
.liste-exceptions,
.liste-fichiers {
  display: grid;
  gap: var(--espace-s);
  width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* La période à gauche, la commande qui la retire à droite, et le filet entre deux
   exceptions comme entre deux jours. */
.exception,
.fichier {
  display: flex;
  align-items: center;
  gap: var(--espace-m);
  padding-block: var(--espace-s);
}

/* ⚠️ C'EST LA PÉRIODE QUI POUSSE LE BOUTON À DROITE, ET SURTOUT PAS
   `justify-content: space-between`, QUI LE DÉPLAÇAIT D'UN RANG À L'AUTRE. Sur deux
   exceptions voisines d'un petit écran, « Du 24 décembre 2026 au 26 décembre 2026 »
   remplissait sa ligne, son « Retirer » passait au rang suivant et `space-between`,
   seul sur ce rang, le calait à GAUCHE ; « Le 1er février 2027 » gardait le sien à
   DROITE. Deux emplacements pour la même commande dans la même liste : l'œil doit
   retrouver « Retirer » au même endroit d'un rang au suivant, sinon il le cherche à
   chaque ligne. La période prend donc la place libre et cède la sienne (`1 1 auto`) :
   c'est SA phrase qui passe à deux lignes, et le bouton ne bouge plus. Le rang ne peut
   alors plus se replier, et c'est pourquoi `flex-wrap` et l'interligne du `gap` partent
   avec `space-between`. */
.exception-periode,
.fichier-texte {
  flex: 1 1 auto;
}

/* ⚠️ ET LA COMMANDE NE CÈDE RIEN, ce qui ne va pas de soi : `.bouton-discret` porte
   `min-width: 0` (c'est un mot souligné, pas une capsule), si bien qu'en flex il se
   laissait comprimer avec la période, « Retirer » rogné par son propre cadre. C'est la
   période qui rend sa place, jamais le bouton. */
.exception > .bouton,
.fichier > .bouton {
  flex-shrink: 0;
}

.exception + .exception,
.fichier + .fichier {
  border-top: var(--epaisseur-filet) solid var(--couleur-encre);
}

/* ============================================================
   ANNUAIRE PUBLIC DES EXPERTS (feature 10)
   DESIGN.md §6 : barre de recherche en capsule fermée par un bouton rond bleu ; dix
   filtres en pilules à contour, le courant inversé en noir, qui passent à la ligne
   au-dessus de 48rem et défilent en dessous ; les experts en lignes séparées d'un filet
   noir (portrait cerclé de noir, nom en 600, domaines, badge, prix à droite), pas une
   grille de cartes. Sur mobile la ligne se replie en deux rangs.
   ============================================================ */

/* La seule colonne large de l'application, et ce n'est pas un caprice : une ligne
   d'annuaire porte un portrait, un nom, ses domaines, un badge et un prix, c'est-à-
   dire cinq choses sur une rangée. Dans la colonne de LECTURE, tout se replie et la
   liste redevient une pile de blocs. La largeur est celle du header et du footer, si
   bien que la page s'aligne sur son propre shell. Les paragraphes, eux, restent bornés
   à `--largeur-texte` par la règle générale de `p`. */
.colonne-annuaire {
  max-width: var(--largeur-max);
}

/* ---------- La barre de recherche ----------
   Le champ en capsule et le bouton rond qui la ferme : un seul objet visuel, le
   bouton d'action principal de cet écran, donc son seul verre bleu.
   Bornée à la largeur de lecture, comme le chapeau juste au-dessus : étirée sur toute
   la colonne, elle promet une phrase là où on tape deux mots, et elle ne s'aligne plus
   sur rien.
   La marge basse est `--espace-l` et non `--espace-m` : la barre de recherche et la
   ligne de filtres sont deux blocs qui ne se touchent pas, et DESIGN.md §6 leur
   interdit moins de 2rem. Le `padding-block` de `.filtres` juste dessous n'est pas
   de l'espacement, c'est la réserve de l'anneau de focus. */
.recherche {
  display: flex;
  gap: var(--espace-s);
  max-width: var(--largeur-texte);
  margin-bottom: var(--espace-l);
}

/* Le champ est déjà une capsule (`.champ`). Dans cette rangée il cède sa place plutôt que
   de pousser le bouton rond hors de la colonne : un champ en flex ne descend pas, sans
   cette ligne, sous la largeur que le navigateur lui prête. */
.recherche-champ {
  flex: 1 1 auto;
  min-width: 0;
}

/* ---------- Les filtres par domaine ----------
   DIX PILULES QUI PASSENT À LA LIGNE, la ligne défilante n'étant plus qu'un motif de
   petit écran (décision 4 de direction artistique du 18/09/2026, reprise par DESIGN.md
   §6). ⚠️ CE QUI A TRANCHÉ EST UNE MESURE : sur grand écran les dix filtres demandent
   plus que la colonne, donc plusieurs d'entre eux restaient HORS CADRE, et le marqueur
   « vous êtes ici » avec eux quand le filtre courant en faisait partie. Une ligne qui
   défile à la souris n'a aucune affordance sans flèches, qu'on n'invente pas pour dix
   liens. Repliées, elles tiennent sur deux rangs.
   C'est la même ligne de pilules que `.choix-liste` (feature 8) ; ce qui lui
   appartient en propre est la réserve de l'anneau de focus et le défilement mobile.
   ⚠️ LE PADDING VERTICAL N'EST PAS DE L'AIR, c'est ce qui sauve l'anneau de focus :
   l'`overflow-x: auto` du petit écran (plus bas) rogne ce qui dépasse en HAUT et en
   BAS aussi, et l'anneau est posé à 3 px de la pilule. Sans lui, le repère de qui
   navigue au clavier serait coupé en deux. La réserve LATÉRALE, elle, est écrite dans
   le bloc média : elle n'a de sens que là où le cadre défile. */
/* ⚠️ LA BANDE DES JOURS DE `/reserver` EST LE MÊME OBJET (feature 14), et c'est
   pourquoi elle partage cette règle et celle du bloc média plus bas : une ligne de
   pilules qui passe à la ligne sur grand écran et défile sur petit, avec la réserve
   d'anneau de focus sur les deux axes. Deux copies auraient divergé au premier
   réglage, et c'est justement la réserve de focus qui a coûté trois tours de review
   sur cet écran-ci. Seules leurs marges leur appartiennent en propre. */
.filtres,
.bande-jours {
  display: flex;
  flex-wrap: wrap;
  gap: var(--espace-s);
  padding-block: var(--espace-s);
}

.filtres {
  margin-bottom: var(--espace-l);
}

/* La pilule à cocher de la feature 8, en lien : même boîte, même contour, même
   inversion quand elle est choisie, même survol (écrit avec la pilule). Un lien apporte
   ce qu'un bouton ne donne pas ici, une adresse qui se partage et un retour arrière qui
   fonctionne. `flex: none` : une pilule garde sa largeur, qu'elle passe à la ligne sur
   grand écran ou qu'elle sorte du cadre défilant sur petit écran, où c'est exactement
   ce qu'on attend d'elle. */
.filtre {
  flex: none;
  text-decoration: none;
}

/* ---------- La liste et ses lignes ---------- */
.liste-experts {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Trois colonnes : le portrait, l'identité, le prix. Le filet SÉPARE deux lignes,
   il ne les encadre pas : `+` le pose entre elles et nulle part ailleurs, donc ni
   au-dessus de la première ni sous la dernière, où il ferait une boîte. */
.carte {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--espace-m) var(--espace-l);
  padding-block: var(--espace-l);
}

.carte + .carte {
  border-top: var(--epaisseur-filet) solid var(--couleur-encre);
}

/* `justify-items: start` pour que le badge garde sa largeur de pilule : dans une
   grille, un enfant s'étire par défaut, `inline-flex` compris.
   Le bloc d'identité de la fiche publique (feature 11) est le MÊME objet, à une
   autre échelle : le nom, les domaines, le badge, empilés et alignés à gauche. Une
   seule règle pour les deux ; ce que la fiche y ajoute en propre, la largeur sous
   laquelle elle passe sous le portrait, est dans sa section. */
.carte-identite,
.fiche-identite {
  display: grid;
  justify-items: start;
  gap: var(--espace-s);
}

/* Le nom est un `h2` parce que la page est une liste de gens sous un seul titre, et
   qu'on doit pouvoir la parcourir de nom en nom au lecteur d'écran. Il prend
   l'échelle des h3 de DESIGN.md §3, qui est celle d'un titre DANS un bloc : même
   geste que `.etat-titre`, le niveau de titre que le document impose n'est pas toujours
   la taille que la composition demande. La graisse est écrite aussi : sur `/reserver`
   le nom est un paragraphe, et il doit garder le 600 d'un nom d'expert.
   Le titre de la carte de réservation (feature 11) est le même cas, et celui de chaque
   pièce de `/certification` (feature 9) : huit blocs d'un formulaire, qu'un h2 de
   section ferait lire comme huit sections de page. */
.carte-nom,
.reservation h2,
.apercu h2,
.piece h2,
.avis-zone-titre {
  margin: 0;
  font-size: var(--taille-l);
  font-weight: var(--graisse-titre);
  letter-spacing: var(--interlettrage-titre-h3);
  line-height: var(--interligne-titre-h3);
}

/* Le nom est le lien vers la fiche publique (feature 11). Il garde EXACTEMENT le
   dessin du titre, couleur comprise : ce qui dit qu'il se clique est le survol, qui
   passe au bleu (DESIGN.md §8), et l'anneau de focus au clavier. Un titre souligné
   ferait de chaque ligne de l'annuaire un lien de page d'aide.

   ⚠️ SA CIBLE FAIT 44 PX SANS QUE RIEN NE BOUGE (run 27). Un nom mesurait 26 px de haut,
   19 px dans le journal : sous le doigt, c'est la seule porte d'une ligne d'annuaire. La
   réserve l'agrandit de tous les côtés, la marge négative la reprend là où elle
   déplacerait un voisin, et l'anneau de focus est ramené d'autant : il se dessine où il
   se dessinait, à `--decalage-focus` du texte. La réserve est un nombre ENTIER de
   pixels : à 14,5 px, le texte d'une ligne sur trois se décalait d'un pixel (captures
   comparées). Elle dépasse le manque parce que le bas de la zone est repris par la
   ligne de texte qui suit le nom : c'est le haut qui fait le compte, mesuré au pointage.
   */
.carte-lien {
  margin: calc(-1 * var(--espace-m));
  padding: var(--espace-m);
  outline-offset: calc(var(--decalage-focus) - var(--espace-m));
  color: inherit;
  text-decoration: none;
}

.carte-lien:hover {
  color: var(--couleur-accent);
}

/* La ligne de fait secondaire posée sous un nom : les domaines d'une ligne
   d'annuaire, et sur la fiche publique la phrase du badge comme les langues parlées.
   Même rôle, même voix, une seule règle : noir, plus petit, en 400. */
.carte-domaines,
.fiche-meta {
  margin: 0;
  font-size: var(--taille-s);
}

/* Le prix d'entrée, calé à droite de la ligne et sur sa ligne de base. Dans la file des
   certifications (run 92), l'état de la demande prend exactement cette place. */
.carte-prix,
.carte-etat {
  margin: 0;
  justify-self: end;
}

/* SOUS LA CARTE, ET ALIGNÉES SUR SON TEXTE (feature 17) : les commandes d'un rendez-vous
   et la zone d'annulation qu'elles déplient prennent chacune un rang entier de la grille,
   de la colonne de l'identité au bord droit. Leur boîte est réglée avec celles qu'elles
   reprennent (`.piece-commandes`, `.motif`, section des certifications).
   ⚠️ CETTE RÈGLE RESTE AVANT LE REPLI MOBILE CI-DESSOUS, qui les fait passer sous le
   portrait avec l'état : à spécificité égale, c'est l'ordre qui tranche. */
.carte-commandes,
.carte-zone {
  grid-column: 2 / -1;
}

/* ---------- La pagination ----------
   Centrée, et non étalée aux deux bords : en page 1 le bouton « Précédent »
   disparaît (il n'y a rien avant, et un bouton mort se clique quand même), et un
   `space-between` laisserait alors « Suivant » seul à droite d'un grand vide. */
.pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--espace-l);
  margin-top: var(--espace-xl);
}

.pagination-position {
  margin: 0;
  font-size: var(--taille-s);
}

/* ---------- Mobile : la ligne se replie en deux rangs (DESIGN.md §6) ----------
   Le portrait et l'identité tiennent le premier rang, le prix passe dessous sur
   toute la largeur. Le garder dans une troisième colonne sur petit écran écraserait le
   nom sur trois lignes pour lui laisser sa place. */
@media (max-width: 48rem) {
  /* Et c'est ICI, et nulle part ailleurs, que les filtres redeviennent une seule
     ligne défilante (DESIGN.md §6) : dix pilules repliées dans un écran de 375 px
     prendraient quatre rangs et repousseraient le premier expert sous la ligne de
     flottaison.
     L'affordance de suite est la pilule que le bord droit COUPE, la plus simple qui
     existe : on voit qu'il y a autre chose derrière, sans flèche à inventer et sans
     fondu de bord, qui éteindrait la dernière pilule et son anneau de focus une fois
     la ligne défilée jusqu'au bout. Le filtre courant, lui, est ramené dans le cadre
     par js/experts.js, sans quoi son marqueur serait posé hors de l'écran.
     ⚠️ ET LE CADRE A LA MÊME RÉSERVE À GAUCHE ET À DROITE QU'EN HAUT ET EN BAS :
     `overflow-x: auto` rogne sur LES DEUX AXES, si bien que l'anneau de la PREMIÈRE
     pilule était coupé à plat à scrollLeft 0 et celui de la DERNIÈRE au défilement
     maximal, faute du moindre dégagement latéral pour les 5 px que l'anneau porte
     (2 px de trait à 3 px de décalage). Le `padding-inline` donne le dégagement,
     le `margin-inline` négatif le reprend sur la colonne : les pilules restent
     alignées sur le champ de recherche et sur le h1, et la pilule coupée par le bord
     droit reste coupée, donc l'affordance de suite tient.
     ⚠️ ET LE `scroll-padding-inline` FERME LE MÊME DÉFAUT À UN TROISIÈME ENDROIT, celui
     où js/cadre.js dépose le filtre courant (il lit cette réserve dans le style
     calculé) : « au plus près » colle la pilule contre le bord du cadre, si bien qu'un
     `Tab` jusqu'à elle rognait son anneau alors que Chrome ne redéfilait pas, la
     trouvant déjà visible. Le `scroll-padding-inline` fait atterrir le filtre 8 px à
     l'intérieur. Même réserve, même jeton, trois positions.
     ⚠️ ET UNE QUATRIÈME POSITION, QUE LA RÉSERVE NE POUVAIT PAS FERMER (nettoyage du
     07/10/2026, PIEGES.md E50) : la pilule que le bord COUPE et qui reçoit le focus au
     clavier. Chrome n'amène dans le cadre qu'une pilule cachée ou presque ; dès qu'une
     bonne part en est visible, il lui donne le focus SANS défiler, et son anneau reste
     rogné avec elle. Un `scroll-margin` sur la pilule n'y change RIEN : il déplace
     l'endroit où un défilement s'arrête, il n'en déclenche aucun. Ce qui en déclenche
     un, c'est l'accroche : le cadre s'accroche obligatoirement, et la SEULE cible est
     la pilule qui porte le focus clavier, que le navigateur ramène donc au centre dès
     qu'elle le reçoit (aux deux bouts, contre la réserve). Sans focus clavier il n'y a
     aucune cible, donc aucune accroche : le défilement au doigt et l'arrivée
     du filtre courant ne changent pas d'un pixel. Aucune taille n'entre ici. */
  .filtres,
  .bande-jours {
    flex-wrap: nowrap;
    overflow-x: auto;
    padding-inline: var(--espace-s);
    margin-inline: calc(var(--espace-s) * -1);
    scroll-padding-inline: var(--espace-s);
    scroll-snap-type: inline mandatory;
  }

  .filtres > :focus-visible,
  .bande-jours > :focus-visible {
    scroll-snap-align: center;
  }

  .carte {
    grid-template-columns: auto 1fr;
  }

  .carte-prix,
  .carte-etat,
  .carte-commandes,
  .carte-zone {
    grid-column: 1 / -1;
    justify-self: start;
  }
}

/* ============================================================
   FICHE EXPERT PUBLIQUE (feature 11)
   DESIGN.md §6 : deux colonnes sur grand écran, le portrait de 9rem cerclé de noir, le
   nom en h1, les domaines en pilules-liens et la biographie à gauche ; à droite, collée
   au défilement, la carte de réservation. Sur petit écran la carte passe sous la
   biographie.
   ⚠️ TOUT CE QUI S'Y VOIT EXISTE DÉJÀ AILLEURS : la pastille de portrait et son anneau
   (feature 6), la pilule `.choix` en lien (features 8 et 10), le badge (9), le prix (8),
   l'état d'écran (7). Cette section ne pose que la composition de la page et la carte.
   ============================================================ */

/* ⚠️ AUCUN POINT DE RUPTURE DE PLUS, ET CE N'EST PAS UNE ÉCONOMIE : la fiche se
   replie sur SES PROPRES LARGEURS. La colonne de gauche ne descend jamais sous la
   largeur de lecture (`--largeur-texte`) ; dès que cette largeur, la carte et leur
   gouttière ne tiennent plus côte à côte, la carte passe dessous d'elle-même. Un
   `@media` aurait figé ce pli à une taille d'écran devinée, qui deviendrait fausse le
   jour où la carte ou la colonne changent de largeur.
   `align-items: flex-start` est indispensable au `position: sticky` de la carte :
   étirée sur toute la hauteur de la ligne, elle n'aurait plus rien à parcourir et ne
   collerait jamais. */
.fiche,
.examen {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--espace-xl);
}

.fiche-principal {
  display: grid;
  gap: var(--espace-l);
  flex: 1 1 var(--largeur-texte);
}

/* Le portrait et l'identité sur une rangée, qui se replie quand le nom n'a plus la
   place de tenir à côté de la pastille (`--largeur-identite`). */
.fiche-tete {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-l);
}

/* Rien n'est redéfini du portrait : seul son jeton de taille change, et
   `.photo-pastille` suit, anneau compris. */
.fiche-portrait {
  --taille-pastille: var(--taille-pastille-fiche);
}

.fiche-identite {
  flex: 1 1 var(--largeur-identite);
}

/* Le nom EST le titre de la page et garde l'échelle du h1 (`.colonne h1`). Il perd
   la marge basse de ce titre-là, que la gouttière de la pile remplace : les deux
   s'ajouteraient. */
.fiche-identite h1 {
  margin-bottom: 0;
}

/* La biographie est le seul texte à sauts de ligne de l'application (`texteLong`,
   functions/_lib/profil.ts) : sans `pre-line`, les deux paragraphes qu'un expert a
   écrits se recolleraient en un seul pavé, et il ne pourrait rien y faire. Sa largeur
   de lecture vient de la règle générale de `p`. */
.fiche-biographie {
  margin: 0;
  white-space: pre-line;
}

/* ---------- La carte de réservation ----------
   Un panneau blanc à contour noir et coins doux (`--rayon-carte-grande`, en squircle là
   où le navigateur le sait). Le panneau d'aperçu de la carte d'une certification
   (run 92) est le même objet : une surface collée au défilement à côté de ce qu'on lit.
   Seule sa largeur diffère. */
.reservation,
.apercu {
  display: grid;
  gap: var(--espace-m);
  padding: var(--espace-l);
  background: var(--couleur-fond);
  border: var(--epaisseur-contour) solid var(--couleur-encre);
  border-radius: var(--rayon-carte-grande);
  corner-shape: squircle;
  /* Elle suit la lecture de la biographie (DESIGN.md §6). Sur une seule colonne elle
     est le dernier bloc de la fiche : il ne reste rien à parcourir, et le collage
     n'a alors aucun effet, sans qu'il faille l'annuler. */
  position: sticky;
  top: var(--espace-l);
  /* Elle ne grandit pas quand la place abonde, mais elle se resserre quand elle
     manque : sur petit écran, une largeur intouchable déborderait de la colonne. */
  flex: 0 1 var(--largeur-reservation);
}

/* LES PRIX PAR DURÉE, EN BLOC EMPILÉ (DESIGN.md §6), DEBOUT ET NON COUCHÉ, et c'est une
   mesure qui l'a décidé : un segment porte le montant ET sa durée (« 97,50 € · 45 min »,
   composant signature 4), et les trois durées demandent le double de ce que la carte
   offre. Les segments s'empilent donc, le contour reste UNIQUE et les filets les
   séparent : c'est ce qui en fait un seul objet. Le rayon est celui d'une carte et non
   `--rayon-pilule`, qui dessinerait deux grands arcs sur un bloc de trois rangs. */
.durees {
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--couleur-fond);
  border: var(--epaisseur-contour) solid var(--couleur-encre);
  border-radius: var(--rayon-carte);
}

.duree {
  padding: var(--espace-m);
}

/* Le filet SÉPARE deux segments, il ne les encadre pas : même geste que les lignes
   de l'annuaire, le contour du bloc faisant déjà le tour. */
.duree + .duree {
  border-top: var(--epaisseur-filet) solid var(--couleur-encre);
}

/* LE BOUTON « RÉSERVER » (feature 14). Il prend toute la largeur de la carte : c'est
   l'action principale de la page, son seul verre bleu, et un bouton étroit flottant
   dans la carte se lirait comme une option parmi d'autres. */
.reservation-action {
  display: grid;
}

.reservation-action .bouton {
  width: 100%;
}

/* ============================================================
   RÉSERVATION D'UN CRÉNEAU (feature 14)
   DESIGN.md §6 : la ligne de l'expert, puis trois sections de page (durée, créneau,
   récapitulatif). La durée en capsule de verre segmentée, le prix dessous ; la bande
   des jours et les créneaux en pilules ; le récapitulatif en lignes à filet.
   ⚠️ Ce que cette section ne pose PAS : la ligne d'expert, la pastille de portrait, le
   badge, la pilule, le prix, le squelette et l'état d'écran, qui existent depuis les
   features 6 à 11 et servent ici tels quels. La bande des jours partage la mécanique
   des filtres de l'annuaire, écrite là-bas.
   ============================================================ */

/* Un cran plus large que les écrans de formulaire, comme les disponibilités : la bande
   des jours porte quatorze pastilles et la liste des créneaux jusqu'à une vingtaine, et
   dans une page de formulaire les deux se lisent en escalier. La largeur de LECTURE
   suffit, la page ne contenant aucune liste à cinq colonnes comme l'annuaire. */
.page-reserver {
  max-width: var(--largeur-texte);
}

/* AVEC QUI : la ligne de l'annuaire telle quelle (`.carte`), sans sa troisième
   colonne, celle du prix. Le filet la sépare de la suite au lieu de séparer deux
   lignes, et le haut du padding part, le h1 étant juste au-dessus avec sa marge. */
.reserver-expert,
.examen-expert {
  padding-top: 0;
  margin-bottom: var(--espace-xl);
  border-bottom: var(--epaisseur-filet) solid var(--couleur-encre);
}

/* ⚠️ L'ESPACEMENT DE SECTION DE DESIGN.md §6, AUX DEUX VALEURS QU'IL DONNE : sept
   rems au-dessus de 48rem, quatre en dessous (décision 5 de direction artistique,
   19/09/2026, FEATURES.md § Notes de cadrage). Les trois blocs de cet écran sont des
   SECTIONS de page, chacune sous son h2 (« Durée de la session », « Créneau »,
   « Récapitulatif »), et non deux groupes d'un même formulaire comme sur
   `/disponibilites`, dont la règle voisine reste à `--espace-l`. Ici les sections sont
   des enfants directs d'un bloc, sans gouttière à compléter : la marge porte donc
   l'espacement en entier. L'historique de la carte d'une certification (run 92) est une
   section de page de même sorte, sous les deux colonnes. */
.page-reserver .reglage + .reglage,
.examen + .historique {
  margin-top: var(--espace-xxl);
}

/* ---------- La capsule segmentée des durées (DESIGN.md §6) ----------
   COUCHÉE, contrairement au bloc des prix de la fiche publique : un segment ne porte
   qu'une durée et tient donc sur une ligne. C'est le sélecteur de l'accueil, le même
   composant : un verre blanc, des segments transparents qui sont chacun une capsule, et
   le segment choisi inversé en noir (le marqueur « vous êtes ici », écrit plus haut).
   Plus aucun filet entre deux segments.

   ⚠️ AUCUN `overflow: hidden` SUR LA CAPSULE, alors que c'est le réflexe pour en
   arrondir les extrémités : il rognerait l'anneau de focus des segments, réservé
   précisément à ce qui se commande. Chaque segment porte son propre rayon.
   Et rien n'est figé en `flex` : le segment garde le `0 1 auto` par défaut, donc il
   cède plutôt que de pousser la capsule hors de la colonne si un libellé s'allongeait. */
.segments {
  display: inline-flex;
  gap: var(--espace-xs);
  max-width: 100%;
  padding: var(--espace-reserve-segments);
  border-radius: var(--rayon-pilule);
  background: var(--verre-blanc);
  box-shadow: var(--reflet-verre-blanc), var(--contour-verre-blanc), var(--ombre-verre-blanc);
}

.segment {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  min-height: var(--hauteur-bouton);
  padding: 0 var(--padding-segment);
  border: 0;
  border-radius: var(--rayon-pilule);
  background: transparent;
  font: inherit;
  font-size: var(--taille-base);
  font-weight: var(--graisse-moyenne);
  line-height: 1;
  color: inherit;
  white-space: nowrap;
  cursor: pointer;
}

.segment:not([aria-pressed="true"]):hover {
  color: var(--couleur-accent);
}

.segment[aria-pressed="true"] {
  font-weight: var(--graisse-titre);
}

/* ---------- La bande des jours et les créneaux ---------- */

/* Sa mécanique (repli sur grand écran, défilement et réserve d'anneau de focus sur
   petit) est celle des filtres de l'annuaire, écrite avec eux. Ne lui appartient en
   propre que sa largeur : dans la grille d'un `.reglage`, qui aligne ses enfants au
   début, un cadre défilant doit occuper toute la colonne pour avoir de quoi défiler. */
.bande-jours {
  width: 100%;
}

/* ---------- Le récapitulatif ---------- */

/* Une liste en lignes à filet (DESIGN.md §6), et non une carte : l'écran n'a pas
   besoin d'un panneau sous les deux listes de pilules qui le précèdent. */
.recap {
  display: grid;
  width: 100%;
  margin: 0;
}

/* ⚠️ UNE GRILLE À DEUX COLONNES, ET NON UN FLEX QUI SE REPLIE. La valeur doit
   s'enrouler DANS sa colonne. En `flex-wrap: wrap`, une valeur dont la largeur
   `max-content` dépasse la place restante ne rétrécit pas, elle passe à la ligne du
   dessous et se pose à l'abscisse de son propre intitulé : la date tombait donc sous
   « Date et heure » et les quatre valeurs se lisaient en escalier, sur grand écran
   comme sur petit. Le repli n'était pas un repli de petit écran, c'était la mesure
   `max-content` d'une phrase.
   La seconde piste est `minmax(0, 1fr)` et non `1fr` : le `1fr` nu vaut
   `minmax(auto, 1fr)`, donc la piste garde la largeur minimale de son contenu et une
   valeur sans espace déborderait au lieu de s'enrouler. */
.recap-ligne {
  display: grid;
  grid-template-columns: var(--largeur-recap-libelle) minmax(0, 1fr);
  align-items: baseline;
  gap: var(--espace-m);
  padding-block: var(--espace-s);
}

.recap-ligne + .recap-ligne {
  border-top: var(--epaisseur-filet) solid var(--couleur-encre);
}

/* L'intitulé occupe la première colonne, dont la largeur est la même pour toutes les
   lignes : c'est elle qui aligne les valeurs. Il a la voix d'un texte secondaire, noir,
   plus petit, en 400 ; la valeur, elle, est en 500 : sans gris, c'est la graisse et la
   taille qui les distinguent (DESIGN.md §3). */
.recap dt {
  font-size: var(--taille-s);
}

/* Les huit compteurs du hub SEULEMENT : le journal et les fiches de l'administration
   gardent la largeur d'intitulé commune, sinon leurs valeurs se resserrent à 375 px. Le
   nombre ne se coupe pas (« 144 · 8 760 € » ne renvoie jamais le « € » à la ligne). */
.recap-compteurs {
  --largeur-recap-libelle: var(--largeur-recap-admin);
}

.recap-compteurs dd {
  white-space: nowrap;
}

/* La valeur occupe la seconde colonne et s'enroule dedans : c'est la date, longue, qui
   passe à deux lignes DANS sa colonne, jamais sous son intitulé. */
.recap dd {
  margin: 0;
  font-weight: var(--graisse-moyenne);
}

/* ---------- Mobile ---------- */
@media (max-width: 48rem) {
  /* La valeur mobile de l'espacement de section (DESIGN.md §6, décision 5 de direction
     artistique) : quatre rems, sur le point de rupture de toute la feuille. */
  .page-reserver .reglage + .reglage,
  .examen + .historique {
    margin-top: var(--espace-xl);
  }

  /* LA BARRE FIXÉE EN BAS D'ÉCRAN de DESIGN.md §6 : c'est le MÊME bouton que celui de
     la carte de réservation, déplacé par le CSS, et surtout jamais un second bouton
     recopié. La fiche est longue (portrait, domaines, badge, biographie, langues, prix)
     et la carte est son dernier bloc : sans cette barre, l'action principale de la page
     n'est visible qu'après l'avoir lue en entier. Fond blanc, filet noir au-dessus.
     Le pied de page reçoit la hauteur correspondante en bas de la page, faute de quoi
     la barre lui passerait dessus. */
  .reservation-action {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 1;
    padding: var(--espace-s) var(--gouttiere-page);
    background: var(--couleur-fond);
    border-top: var(--epaisseur-filet) solid var(--couleur-encre);
  }

  /* ⚠️ LA RÉSERVE NE VAUT QUE LÀ OÙ LA BARRE SE PEINT, et le même gabarit sert aux
     deux états de `/experts/<id>`. Posée sur `.page-fiche` seule, elle restait sur
     « Expert non disponible », où `#fiche` garde son `hidden` et où la barre n'existe
     donc pas : une bande vide sous le pied de page. La condition lit l'attribut que la
     Function pose ou retire (`functions/experts/[id].ts`), donc la réserve suit la
     barre sans une ligne de JavaScript, et sans clignotement : c'est le serveur qui a
     déjà tranché avant le premier rendu. */
  .page-fiche:has(.fiche:not([hidden])) {
    padding-bottom: var(--hauteur-barre-reservation);
  }
}

/* ============================================================
   CERTIFICATION DE L'EXPERT (feature 9, second segment)
   Une liste en lignes à filet, un bloc par type de pièce, jamais une grille de cartes.
   ⚠️ Ce que cette section ne pose PAS : le bloc de réglage, la ligne de fichier et sa
   commande, la pilule à cocher, le badge, le point d'état et ses quatre dessins, le
   titre de bloc, la zone de message, la jauge, le champ en faute, le squelette et l'état
   d'écran, qui existent ailleurs dans la feuille et servent ici tels quels.
   ============================================================ */

/* ⚠️ DEUX PIÈCES SONT DEUX RÉGLAGES D'UN MÊME FORMULAIRE, qui part d'un seul bouton :
   `--espace-l` en plus de la gouttière (décision 5b de direction artistique), et le
   filet de la « liste en lignes » posé AU MILIEU de cet écart. Le `margin-top` de
   `.reglage + .reglage` le mettait tout entier au-dessus du filet, qui aurait alors
   touché le titre suivant : il devient ici un `padding` sous le filet. `+` ne pose le
   filet qu'entre deux pièces, jamais au-dessus de la première. */
.piece + .piece {
  margin-top: 0;
  padding-top: var(--espace-l);
  border-top: var(--epaisseur-filet) solid var(--couleur-encre);
}

/* Dans la grille d'un `.reglage`, qui aligne ses enfants au début, un groupe de champs
   se réduirait à la largeur de son contenu : les deux références prennent la colonne. */
.piece > .champ-groupe {
  width: 100%;
}

/* ⚠️ ET LA SECONDE RÉFÉRENCE S'ÉCARTE DE LA PREMIÈRE PLUS QUE SES CHAMPS NE S'ÉCARTENT
   ENTRE EUX : avec la seule gouttière du bloc, « Seconde référence » tombait à la même
   distance de l'email qui la précède que de son propre nom, et rien ne disait où finit
   une référence. `--espace-l` au total entre les deux, `--espace-m` entre deux champs. */
.piece > .champ-groupe + .champ-groupe {
  margin-top: var(--espace-m);
}

/* Le nom d'un fichier vient de l'expert, et il peut être long et sans espace : il
   s'enroule dans sa colonne plutôt que de pousser « Retirer » hors de la ligne.
   `min-width: 0` parce qu'un élément flex ne descend pas sous sa largeur de contenu. */
.fichier-texte {
  display: grid;
  gap: var(--espace-xs);
  min-width: 0;
}

.fichier-nom {
  overflow-wrap: anywhere;
}

/* ============================================================
   LA FILE ET LA CARTE D'UNE CERTIFICATION, ÉCRANS DE L'ADMINISTRATEUR (feature 9, run 92)
   La file est la liste de l'annuaire (`.carte`), l'état à la place du prix ; la carte a
   deux colonnes qui se replient par leurs propres largeurs, comme la fiche publique, les
   pièces en lignes à filet à gauche, le panneau d'aperçu collé au défilement à droite.
   Jamais une modale.
   ⚠️ Ce que cette section ne pose PAS : la ligne d'expert, la pastille, le badge, la
   ligne de fichier et son point d'état, les conditions, le récapitulatif en lignes
   (l'historique), la surface collée (celle de la carte de réservation), les états
   d'écran et le squelette.
   ============================================================ */

/* La colonne large de l'annuaire, pour les deux écrans : la file porte les mêmes lignes
   (un portrait, un nom, ses domaines, une ligne de dates et l'état à la place du prix),
   qu'une colonne de lecture repliait en pile ; la carte porte une liste de pièces et un
   panneau où lire un PDF. Les paragraphes restent à la largeur de lecture par la règle
   générale de `p`. Les écrans de l'administration (feature 23 : le tableau, les
   réglages, le journal, les registres) la prennent aussi : ils portent la même
   navigation que la file, qui ne doit pas changer de largeur d'un écran à l'autre. */
.page-file,
.page-examen,
.page-admin {
  max-width: var(--largeur-max);
}

/* La colonne des pièces ne descend pas sous sa base, et cède sa place au panneau quand
   les deux ne tiennent plus côte à côte : c'est alors lui qui passe dessous. */
.examen-principal {
  flex: 1 1 var(--largeur-examen);
  min-width: 0;
}

/* L'état de la demande, entre la ligne de l'expert et les pièces : le même écart que
   celui qui sépare la ligne de l'expert de la suite. */
.examen-etat {
  margin-bottom: var(--espace-xl);
}

/* Le panneau d'aperçu : la surface de la carte de réservation, à sa propre largeur. Ses
   enfants gardent leur largeur (le bouton « Télécharger » n'est pas l'action de l'écran). */
.apercu {
  flex: 1 1 var(--largeur-apercu);
  justify-items: start;
  /* Passé sous la liste, le panneau est ramené à l'écran par « Voir » : il s'y pose à la
     même distance du bord que lorsqu'il colle au défilement. */
  scroll-margin-top: var(--espace-l);
}

/* Une image entière dans le cadre de la fenêtre, jamais rognée ni étirée. */
.apercu-image {
  display: block;
  max-height: var(--hauteur-apercu);
  object-fit: contain;
}

/* Le PDF, lu par le lecteur du navigateur, dans un cadre de même origine (kit §7). */
.apercu-cadre {
  width: 100%;
  height: var(--hauteur-apercu);
  border: var(--epaisseur-contour) solid var(--couleur-encre);
  border-radius: var(--rayon-carte);
  background: var(--couleur-fond);
}

/* L'historique, sous les deux colonnes : à la largeur de lecture, comme tout texte. */
.historique,
.avis-zone {
  max-width: var(--largeur-texte);
}

/* La valeur d'un champ (le lien LinkedIn, le numéro ORIAS, les références) prend la
   largeur du panneau : un récapitulatif en lignes ne se réduit pas à son contenu. */
.apercu-valeur {
  width: 100%;
}

/* ⚠️ UNE LIGNE DE PIÈCE SE REPLIE, À LA DIFFÉRENCE D'UNE LIGNE DE FICHIER DE L'EXPERT :
   elle porte trois commandes au lieu d'une. Quand le texte et les commandes ne tiennent
   plus côte à côte, les commandes passent dessous, alignées à gauche ; et le motif d'un
   refus se déplie SOUS la ligne, sur toute sa largeur (décision 28). Le repli vient de
   leurs largeurs, jamais d'un point de rupture. */
.piece-examen {
  flex-wrap: wrap;
}

.piece-examen .fichier-texte {
  flex-basis: var(--largeur-identite);
}

/* Ce qui résume une pièce (un nom de fichier, un lien LinkedIn) vient de l'expert, et
   peut être long et sans espace : il s'enroule sur sa propre ligne plutôt que de pousser
   la ligne sur les commandes. La valeur d'une ligne de récapitulatif fait de même : la
   fiche d'une réservation (run 231) y écrit des identifiants Stripe et des adresses,
   plus longs que sa colonne sur petit écran, qui élargissaient la page. Et l'identité
   d'une ligne de registre : un compte sans nom s'y nomme par son adresse, un reversement
   par son transfert (`tr_…`), sans une espace où couper. */
.piece-resume,
.recap dd,
.carte-identite {
  overflow-wrap: anywhere;
}

/* Les commandes d'une carte de « Mes rendez-vous » (feature 17) sont la même rangée :
   un bouton, et la zone de message à sa suite, qui ne prend de place que lorsqu'elle
   parle. */
.piece-commandes,
.carte-commandes {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-m);
}

/* ⚠️ VIDE, LA ZONE DE MESSAGE D'UNE CARTE NE PREND AUCUNE PLACE, GOUTTIÈRE COMPRISE
   (PIEGES.md E49). Elle reste dans la page (une région vivante ne s'annonce que si elle
   y était avant de parler), mais sa gouttière suffisait à la faire passer seule à la
   ligne dès que deux commandes remplissent le rang, « Reporter » et « Annuler le
   rendez-vous » sur petit écran : 40 px de vide sous chaque carte. Sa marge rend donc
   la gouttière ; dès qu'elle parle, elle la reprend et passe à la ligne comme avant. */
.carte-commandes .message:empty {
  margin-inline-start: calc(var(--espace-m) * -1);
}

/* La zone de motif, dépliée en ligne sous ce qu'elle décide : son libellé, son champ, la
   raison d'un refus dans la zone même, et ses deux commandes. L'avertissement d'une
   annulation (feature 17, décision 16) est la même zone, dépliée sous une carte de « Mes
   rendez-vous » : son titre, sa phrase, sa case quand rien n'est remboursé, ses deux
   commandes. Jamais une fenêtre. */
.motif,
.carte-zone {
  display: grid;
  gap: var(--espace-m);
  flex-basis: 100%;
  width: 100%;
}

/* La règle et les trois verbes, sous la liste des pièces : un bloc qui prend la colonne,
   pour que « Fait » et « À faire » s'alignent au bord droit. Sa marge complète la
   gouttière du bloc des pièces jusqu'à `--espace-l` : la liste et la règle sont deux
   blocs qui ne se touchent pas (DESIGN.md §6), la règle ne se lit pas comme une pièce de
   plus. */
.decisions {
  display: grid;
  gap: var(--espace-m);
  width: 100%;
  margin-top: var(--espace-m);
}

/* ============================================================
   ACCUEIL (refonte du 09/10/2026, DESIGN.md §6)
   Sous le hero et ses rubans : le cadre de verre « Ils décrochent » et le ruban qui
   passe derrière lui, « Combien de temps ? » et son prix géant, « Comment ça marche » en
   quatre temps, la FAQ, la carte bleue « Rejoignez le cercle ».
   C'est la vitrine : le seul grand panneau de verre et le seul aplat bleu de
   l'application, que DESIGN.md §9 interdit sur un écran de travail.
   ⚠️ Ce que cette section ne pose PAS : le hero, les rubans du hero, les boutons (les
   grands, les ronds et leur flou sont avec les boutons), la capsule segmentée, le disque
   d'un numéro et sa grille, le résumé cliquable, le portrait, le prix, le squelette et
   l'état d'écran. Elle ne fait que les poser dans leur contexte.
   ============================================================ */

.accueil {
  max-width: var(--largeur-max);
  margin-inline: auto;
  padding: 0 var(--gouttiere-accueil);
}

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

/* Les titres de section de l'accueil ont l'échelle d'un h1 de page (DESIGN.md §3). */
.accueil-section h2,
.accueil-section-titre,
.panneau-bleu h2 {
  margin: 0;
  font-size: var(--taille-xxl);
  line-height: var(--interligne-titre-h1);
  letter-spacing: var(--interlettrage-serre);
}

/* ---------- La scène de verre des experts ----------
   Deux couches : le ruban large, bleu, qui traverse la fenêtre d'un bord à l'autre, et
   le cadre de verre posé dessus, qui le floute là où il passe derrière (demande de
   Matthias du 10/10/2026 ; avant, un panneau noir). La scène s'isole : le rang négatif du
   ruban ne le fait pas passer derrière la page.
   ⚠️ ELLE NE ROGNE LE RUBAN QUE SUR LES CÔTÉS. Sa hauteur suit le contenu du cadre, pas
   l'emprise du ruban incliné : rogné aussi en hauteur, le ruban était coupé NET par le haut
   de la scène quand sa crête passait au bord gauche (20 px à 820 px, 61 px à 2560 px,
   mesurés en relecture). `clip` autorise un axe rogné et l'autre visible, ce que `hidden`
   ne permet pas ; le ruban dépasse alors de quelques dizaines de pixels dans le blanc du
   bas du hero. Sans `clip` (navigateur ancien), la scène rogne tout, comme avant. */
.scene-verre {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding: var(--scene-verre-reserve) var(--gouttiere-accueil);
}

@supports (overflow: clip) {
  .scene-verre {
    overflow: clip visible;
  }
}

/* La scène a déjà sa réserve sous le cadre : la section qui la suit en retranche autant de
   la sienne, pour que l'écart reste celui de toutes les sections de l'accueil. */
.scene-verre + .accueil > .accueil-section:first-child {
  padding-top: calc(var(--espace-section-accueil) - var(--scene-verre-reserve));
}

/* Centré sur la scène, incliné, réduit autour de son centre sur petit écran comme celui
   de la carte bleue. */
.rubans-verre {
  z-index: -1;
  left: calc(var(--ruban-cta-debord) * -1);
  right: calc(var(--ruban-cta-debord) * -1);
  top: 50%;
  height: var(--ruban-verre-hauteur);
  transform: translateY(-50%) rotate(var(--ruban-verre-inclinaison)) scale(var(--echelle-rubans));
}

/* Réduit autour du centre, le conteneur ne commence plus au bord de la scène : le ruban
   recule d'autant, pour la couvrir d'un bord à l'autre. À l'échelle 1 il ne bouge pas. */
.rubans-verre .ruban-large {
  top: 0;
  left: calc(var(--periode-ruban-large) / -2 * (1 - var(--echelle-rubans)));
}

/* Le cadre : un verre blanc qui laisse voir, le reflet, le contour et l'ombre d'une
   capsule de verre blanc. Il garde la colonne de `.accueil`, dont il est sorti pour que
   la scène prenne toute la fenêtre. */
.panneau-verre {
  display: flex;
  flex-direction: column;
  gap: var(--espace-xl);
  max-width: calc(var(--largeur-max) - 2 * var(--gouttiere-accueil));
  margin-inline: auto;
  padding: var(--panneau-padding-y) var(--panneau-padding-x);
  border-radius: var(--rayon-panneau);
  corner-shape: squircle;
  background: var(--verre-panneau);
  box-shadow: var(--reflet-verre-blanc), var(--contour-verre-blanc), var(--ombre-verre-blanc);
  -webkit-backdrop-filter: var(--flou-panneau);
  backdrop-filter: var(--flou-panneau);
}

/* Sans flou d'arrière-plan (un navigateur ancien), le ruban passerait net sous le texte :
   le cadre reprend alors le verre presque opaque des capsules. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .panneau-verre {
    background: var(--verre-blanc);
  }
}

.panneau-tete {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--espace-m);
}

/* ⚠️ AUCUN TEXTE NE PASSE AU BLEU DANS LE CADRE : au-dessus du ruban flouté, le bleu
   tombe à 4,5:1 mesuré et 3,75:1 à la borne (DESIGN.md §4). Le nom d'un expert, qui passe
   au bleu sur la page, se souligne ici. */
.panneau-verre .carte-lien:hover {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: var(--epaisseur-marqueur);
  text-underline-offset: var(--decalage-focus);
}

/* L'étiquette d'un exemple, en mode test : sur un expert d'exemple du cadre, sur un avis
   d'exemple. C'est le message « attention » en capsule, sans les marges qu'il prend
   au-dessus d'un sommaire (d'où le sélecteur à deux classes, qui gagne sur lui). */
.message.etiquette-exemple {
  justify-self: start;
  margin: 0;
  font-weight: var(--graisse-titre);
}

.grille-experts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--largeur-portrait-min), 1fr));
  gap: var(--espace-xl) var(--espace-l);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ⚠️ ÉCRIT APRÈS LA GRILLE, ET NON AVEC LES AUTRES RÈGLES DU PETIT ÉCRAN : à spécificité égale,
   la règle de base, plus bas, l'emportait. */
@media (max-width: 48rem) {
  /* LES PORTRAITS DE « ILS DÉCROCHENT » DÉFILENT À L'HORIZONTALE (demande de Matthias du
     14/10/2026) : six portraits sur deux colonnes faisaient trois rangs, plus d'un écran de
     haut. Une ligne, que le doigt fait défiler ; le portrait que le bord coupe dit qu'il y
     en a d'autres, comme la pilule coupée des filtres. Même réserve et même reprise que
     les filtres de l'annuaire, en hauteur aussi : le bouton rond déborde du portrait et porte
     une ombre. Chaque portrait est une cible d'accroche, au bord gauche. */
  .grille-experts {
    display: flex;
    overflow-x: auto;
    gap: var(--espace-l);
    padding: var(--espace-s);
    margin: calc(var(--espace-s) * -1);
    scroll-padding-inline: var(--espace-s);
    scroll-snap-type: inline mandatory;
  }

  .grille-experts > li {
    flex: none;
    width: var(--taille-portrait-accueil);
    scroll-snap-align: start;
  }
}

.expert-accueil {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--espace-xs);
  text-align: center;
}

/* Le portrait de 148 px, et la boîte où le bouton rond au combiné vient déborder en bas
   à droite. Le disque en pointillés du squelette porte la même classe et prend la même
   taille. */
.expert-accueil .portrait {
  position: relative;
  --taille-pastille: var(--taille-portrait-accueil);
  margin-bottom: var(--espace-m);
}

/* ⚠️ LA PHOTO GARDE SA LARGEUR. La règle générale des images la ramenait à la largeur de
   sa colonne de grille, plus étroite que le portrait sous 395 px : la photo devenait un
   ovale (139 x 148 px à 375 px) à côté de disques d'initiales bien ronds. */
.expert-accueil .photo-profil {
  max-width: none;
}

.expert-accueil h3,
.expert-accueil p {
  margin: 0;
}

.expert-accueil p,
.expert-accueil .prix {
  font-size: var(--taille-s);
}

/* Sous un portrait de 148 px, « À partir de 90 € · 30 min » ne tient pas sur une ligne :
   « À partir de » prend la sienne, le montant et sa durée restent ensemble dessous. Sans
   cette base, c'est la durée qui passait seule à la ligne, derrière un point médian
   suspendu. */
.expert-accueil .prix {
  flex-wrap: wrap;
  justify-content: center;
}

.expert-accueil .prix-avant {
  flex-basis: 100%;
}

.bouton-decrocher {
  position: absolute;
  right: var(--debord-decrocher);
  bottom: var(--debord-decrocher);
}

/* Les pictogrammes des deux boutons ronds de l'accueil : des traits, dans la couleur du
   bouton. Leurs attributs de forme sont dans le HTML, leur dessin ici (un SVG en ligne
   ne porte aucun attribut `style`, CSP). */
.bouton-decrocher svg,
.prochain-bouton svg {
  width: var(--taille-m);
  height: var(--taille-m);
  fill: none;
  stroke: currentColor;
  stroke-width: var(--epaisseur-trait-icone);
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* L'état vide et l'état d'erreur de l'annuaire, dans le cadre : le même bloc `.etat`,
   centré, sans son padding (le cadre a le sien). Le halo du point y reste blanc : sur le
   verre, c'est lui qui interrompt l'anneau, que le ruban passe derrière ou non. */
.panneau-verre .etat {
  justify-items: center;
  text-align: center;
  padding-block: 0;
}

/* ---------- « Combien de temps ? » ---------- */
.durees-accueil {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--espace-l);
  text-align: center;
}

.durees-accueil p {
  margin: 0;
  font-size: var(--taille-m);
}

/* Les trois besoins occupent LA MÊME CASE : la section garde la hauteur du plus long, et
   rien ne bouge sous elle quand on change de durée.
   ⚠️ UN BESOIN CACHÉ RESTE DANS LA GRILLE, INVISIBLE. C'est la seule règle de la feuille
   qui rend une boîte à un élément `hidden` (PIEGES.md E1), et c'est voulu : en
   `display: none` il ne compterait plus dans la hauteur. `visibility: hidden` le retire
   de l'écran comme des lecteurs d'écran. */
.besoins {
  display: grid;
  justify-items: center;
}

.besoin {
  grid-area: 1 / 1;
  display: grid;
  justify-items: center;
  align-content: start;
  gap: var(--espace-m);
}

.besoin[hidden] {
  display: grid;
  visibility: hidden;
}

/* Le besoin, en grand : il a pris la place du prix géant de la maquette. Écrit sous
   `.durees-accueil` pour gagner sur la règle du paragraphe juste au-dessus : c'est un
   paragraphe lui aussi, et il en quitte la largeur de lecture. */
.durees-accueil .besoin-titre {
  max-width: none;
  font-size: var(--taille-besoin);
  font-weight: var(--graisse-titre);
  line-height: 1;
  letter-spacing: var(--interlettrage-hero);
}

/* ---------- Une bande de rubans ----------
   Une bande pleine largeur où se croisent deux rubans réduits : le grand, bleu et rayé,
   DEVANT ; un petit noir derrière lui (demandes de Matthias du 10 et du 11/10/2026 : garder
   le visuel jusqu'en bas, du bleu avec le noir, puis le gros ruban devant le petit). Entre deux colonnes de l'accueil, elle se
   loge dans la réserve de la section qui la précède et en rend autant à la suivante, qui
   n'en a pas en haut. Elle rogne ce qui dépasse. */
.filet-ruban {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  height: var(--filet-ruban-hauteur);
  margin-block: calc(var(--espace-section-accueil) / -2) calc(var(--espace-section-accueil) / 2);
  pointer-events: none;
}

/* ⚠️ AU PIED D'UNE PAGE, ET C'EST LA SEULE EXCEPTION À « AUCUN RUBAN SUR UN ÉCRAN DE
   TRAVAIL » (décision de Matthias du 11/10/2026 : au moins un ruban par page) : sous le
   contenu, jamais au milieu d'un formulaire ou d'une liste. Elle se sépare du contenu par
   l'écart de deux blocs, et le pied de page la suit sans écart de plus. */
.filet-ruban-pied {
  margin-block: var(--espace-l) 0;
}

/* L'échelle se règle depuis le coin haut gauche, comme pour les rubans du hero : la
   bande reste calée sur le bord gauche. */
.filet-ruban .rubans {
  left: 0;
  transform: scale(var(--echelle-filet));
  transform-origin: 0 0;
}

/* Le grand passe devant le petit, qui le suit pourtant dans le HTML des pages. */
.filet-ruban .filet-grand {
  --echelle-filet: var(--echelle-filet-grand);
  top: var(--filet-grand-haut);
  z-index: 1;
}

.filet-ruban .filet-petit {
  --echelle-filet: var(--echelle-filet-petit);
  top: var(--filet-petit-haut);
}

.filet-ruban .ruban {
  top: 0;
}

/* ---------- Une section en deux colonnes : « Comment ça marche », la FAQ ----------
   Le titre à gauche, qui reste en vue pendant qu'on lit la colonne de droite ; sous 64rem
   (le seuil du header replié) il passe au-dessus.
   ⚠️ LA RÉSERVE EST ÉCRITE ICI, APRÈS `.accueil-section` que ces sections portent aussi :
   aucune en haut (la section qui précède a déjà la sienne en bas, et les deux
   s'ajouteraient), une en bas, et le retrait latéral que la maquette donnait aux étapes,
   pour que le texte ne colle pas au bord d'un petit écran. La section des avis
   (js/avis-site.js) prend la même. */
.section-duo,
.accueil-avis {
  padding: 0 var(--retrait-etapes) var(--espace-section-accueil);
}

.section-duo {
  display: grid;
  gap: var(--espace-xl);
}

.section-duo-tete {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--espace-l);
}

.section-duo-tete .chapeau {
  margin: 0;
}

@media (min-width: 64rem) {
  .section-duo {
    grid-template-columns: minmax(0, var(--largeur-duo-tete)) minmax(0, var(--largeur-duo-contenu));
    align-items: start;
  }

  .section-duo-tete {
    position: sticky;
    top: var(--espace-xl);
  }
}

/* ---------- Les quatre étapes ----------
   Des numéros ronds (leur disque et leur grille sont avec les deux niveaux de « Devenir
   expert »), reliés par un filet qui descend d'un disque au suivant ; à côté, un titre,
   une phrase et deux précisions. Sans carte. */
.etape {
  position: relative;
}

/* Le filet part du bas du disque et rejoint le disque suivant, à travers l'écart de la
   liste. La dernière étape n'en a pas : rien ne la suit. */
.etape:not(:last-child)::before {
  content: "";
  position: absolute;
  top: var(--taille-numero-etape);
  bottom: calc(var(--espace-xl) * -1);
  left: calc(var(--taille-numero-etape) / 2);
  border-left: var(--epaisseur-contour) solid var(--couleur-encre);
}

/* La première ligne du titre se centre sur le disque : la moitié de ce que le disque a de
   plus haut qu'elle. */
.etape-corps {
  display: flex;
  flex-direction: column;
  gap: var(--espace-s);
  padding-top: calc((var(--taille-numero-etape) - var(--taille-xl) * var(--interligne-titre-h3)) / 2);
}

.etape h3 {
  margin: 0;
  font-size: var(--taille-xl);
  letter-spacing: var(--interlettrage-titre-h2);
}

.etape p {
  margin: 0;
}

.etape-points {
  display: grid;
  gap: var(--espace-xs);
  margin: var(--espace-s) 0 0;
  padding-left: var(--espace-m);
  font-size: var(--taille-s);
}

/* ---------- La FAQ ----------
   Des `<details>` séparés par un filet : la question, et à droite un signe rond, anneau
   creux au repos, disque noir une fois ouverte (l'inversion, DESIGN.md §4), dont le
   « plus » devient un « moins ». Le résumé cliquable est avec le dépliant de la connexion. */
/* Le titre d'un groupe de questions : sa ligne du bas est le filet du haut de la
   première. */
.questions-titre {
  margin: 0;
  padding-bottom: var(--espace-m);
  border-bottom: var(--epaisseur-filet) solid var(--couleur-encre);
  font-size: var(--taille-s);
  font-weight: var(--graisse-titre);
}

.question + .questions-titre {
  margin-top: var(--espace-xl);
}

.question {
  border-bottom: var(--epaisseur-filet) solid var(--couleur-encre);
}

.question summary {
  justify-content: space-between;
  gap: var(--espace-m);
  padding-block: var(--espace-m);
  font-size: var(--taille-l);
  font-weight: var(--graisse-titre);
  letter-spacing: var(--interlettrage-titre-h3);
  line-height: var(--interligne-titre-h3);
}

.question summary:hover .question-texte {
  color: var(--couleur-accent);
}

/* L'anneau de focus rentre dans le résumé : dehors, il longeait les deux filets de la
   question et se lisait comme un cadre à double trait. */
.question summary:focus-visible {
  outline-offset: calc((var(--decalage-focus) + var(--epaisseur-focus)) * -1);
}

.question p {
  margin: 0;
  padding-bottom: var(--espace-l);
}

.question-signe {
  position: relative;
  flex: none;
  width: var(--taille-signe-question);
  height: var(--taille-signe-question);
  border: var(--epaisseur-contour) solid var(--couleur-encre);
  border-radius: 50%;
}

.question-signe::before,
.question-signe::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--taille-point);
  border-top: var(--epaisseur-marqueur) solid currentColor;
  transform: translate(-50%, -50%);
}

.question-signe::after {
  transform: translate(-50%, -50%) rotate(90deg);
}

.question[open] .question-signe {
  background: var(--couleur-encre);
  color: var(--couleur-fond);
}

.question[open] .question-signe::after {
  transform: translate(-50%, -50%);
}

/* ---------- La carte bleue ----------
   Le seul grand aplat bleu de l'application. Elle rogne son ruban, qui passe DERRIÈRE
   son texte : tous ses enfants sont au-dessus, le ruban restant en absolu. */
.panneau-bleu {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--espace-l);
  max-width: var(--largeur-carte-bleue);
  margin: 0 auto var(--espace-xl);
  padding: var(--carte-bleue-padding-haut) var(--espace-l) var(--carte-bleue-padding-bas);
  border-radius: var(--rayon-panneau);
  corner-shape: squircle;
  background: var(--couleur-accent);
  color: var(--couleur-fond);
  text-align: center;
}

.panneau-bleu > * {
  position: relative;
}

.panneau-bleu > .rubans-cta {
  position: absolute;
}

.panneau-bleu h2 {
  max-width: var(--largeur-titre-carte-bleue);
}

.panneau-bleu p {
  margin: 0;
  max-width: var(--largeur-accroche);
  font-size: var(--taille-m);
}

/* ---------- La capsule « prochain rendez-vous » ----------
   Un verre blanc posé sur les rubans, sous les deux boutons du hero, pour le membre
   connecté qui a un rendez-vous à venir ; pour tout autre visiteur elle n'existe pas
   (`hidden`), et sa place n'est pas réservée. */
.prochain {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-m);
  margin-top: var(--hero-ecart-prochain);
  padding: var(--espace-s) var(--espace-s) var(--espace-s) var(--espace-m);
  border-radius: var(--rayon-pilule);
  background: var(--verre-blanc);
  box-shadow: var(--reflet-verre-blanc), var(--contour-verre-blanc), var(--ombre-verre-blanc);
  text-align: left;
}

.prochain .portrait {
  --taille-pastille: var(--taille-portrait-prochain);
}

.prochain-texte {
  display: grid;
  gap: 0;
  padding-right: var(--espace-m);
}

.prochain-titre {
  font-weight: var(--graisse-titre);
}

.prochain-sous {
  font-size: var(--taille-s);
}

/* ---------- Mobile ---------- */
@media (max-width: 48rem) {
  .panneau-verre {
    gap: var(--espace-l);
  }
}

/* ============================================================
   LES AVIS (feature 29, DESIGN.md §7, composants 7 et 8)
   Deux composants et deux seulement : la notation et la ligne d'avis, plus la moyenne qui
   les résume. Aucune couleur de plus, aucune étoile, aucune opacité : une note est une
   MESURE, ni un état ni une action, donc de l'encre.
   ⚠️ Ce que cette section ne pose PAS : le choix de la note (la capsule segmentée), la
   zone dépliée du formulaire (`.carte-zone`), ses champs, ses messages, ses boutons, la
   pagination et l'état vide, qui existent et servent tels quels. Le point creux d'une
   notation, le titre du formulaire, sa largeur, la pile `.avis-bloc` et le titre qui
   prend le focus sont des sélecteurs AJOUTÉS à des règles existantes, plus haut.
   ============================================================ */

/* La notation : cinq points, pleins jusqu'à la note, et la note écrite. En BLEU, pleins
   comme creux, depuis le 10/10/2026 (demande de Matthias ; avant, de l'encre) : c'est le
   seul dessin bleu qui ne soit ni une action ni un état, et la note reste dite en toutes
   lettres à côté. `.notation-point` s'AJOUTE au groupe de base `.condition-point,
   .badge-point, .piece-point` (l'anneau creux), dont il ne change que la couleur. */
.notation {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-s);
  margin: 0;
  font-size: var(--taille-s);
}

.notation-points {
  display: inline-flex;
  gap: var(--espace-xs);
}

.notation-point {
  border-color: var(--couleur-accent);
}

.notation[data-note="1"] .notation-point:nth-child(-n + 1),
.notation[data-note="2"] .notation-point:nth-child(-n + 2),
.notation[data-note="3"] .notation-point:nth-child(-n + 3),
.notation[data-note="4"] .notation-point:nth-child(-n + 4),
.notation[data-note="5"] .notation-point {
  background: var(--couleur-accent);
}

.notation-valeur {
  font-weight: var(--graisse-titre);
  font-variant-numeric: tabular-nums;
}

/* La moyenne : cinq points bleus remplis jusqu'à elle, arrondie au demi-point, puis le
   chiffre précis, en grand, et sa légende (demande de Matthias du 11/10/2026 : une moyenne
   qui se voit). Les points sont ceux de la notation, en plus grand. */
.moyenne {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-s) var(--espace-m);
  margin: 0;
  font-size: var(--taille-s);
}

.moyenne-points {
  display: inline-flex;
  gap: var(--espace-s);
}

.moyenne-points .notation-point {
  width: var(--taille-point-moyenne);
  height: var(--taille-point-moyenne);
}

.moyenne .notation-point[data-rempli="plein"] {
  background: var(--couleur-accent);
}

/* Le demi-point : la moitié gauche du disque, sans dégradé (DESIGN.md §4) ; le disque rogne
   ce qui dépasse de son cercle. */
.moyenne .notation-point[data-rempli="moitie"] {
  position: relative;
  overflow: hidden;
}

.moyenne .notation-point[data-rempli="moitie"]::after {
  content: "";
  position: absolute;
  inset: 0 50% 0 0;
  background: var(--couleur-accent);
}

.moyenne-valeur {
  font-size: var(--taille-xl);
  font-weight: var(--graisse-titre);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* La même moyenne dans une carte de l'annuaire, à la taille d'une ligne de la carte. */
.moyenne-carte {
  --taille-point-moyenne: var(--taille-point-moyenne-carte);
  gap: var(--espace-xs) var(--espace-s);
}

.moyenne-carte .moyenne-points {
  gap: var(--espace-xs);
}

.moyenne-carte .moyenne-valeur {
  font-size: var(--taille-base);
}

/* La ligne d'avis : la grammaire des listes de l'application (lignes à filet), pas une grille
   de cartes. À gauche qui et quand, à droite ce qui est dit. */
.avis-liste {
  margin: 0;
  padding: 0;
  list-style: none;
}

.avis {
  display: grid;
  grid-template-columns: var(--largeur-avis-tete) minmax(0, 1fr);
  gap: var(--espace-m) var(--espace-l);
  padding-block: var(--espace-l);
}

.avis + .avis {
  border-top: var(--epaisseur-filet) solid var(--couleur-encre);
}

/* Qui : le portrait (la photo de profil, les initiales à défaut), puis le nom, la note et
   les dates. */
.avis-tete {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: var(--espace-m);
}

.avis-portrait {
  --taille-pastille: var(--taille-portrait-avis);
}

.avis-portrait .photo-pastille {
  font-size: var(--taille-base);
}

.avis-qui {
  display: grid;
  align-content: start;
  justify-items: start;
  gap: var(--espace-xs);
}

.avis-qui .fiche-meta {
  margin: 0;
}

.avis-auteur {
  margin: 0;
  font-weight: var(--graisse-titre);
}

.avis-texte {
  margin: 0;
  max-width: var(--largeur-texte);
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* Sur l'accueil, la section prend la réserve des sections en deux colonnes (`.section-duo,
   .accueil-avis`, avec l'accueil). */
.accueil-avis .avis-texte {
  font-size: var(--taille-m);
  line-height: var(--interligne-accroche);
}

@media (max-width: 48rem) {
  .avis {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---------- Les avis de l'accueil en bulles (demande d'Abel du 16/10/2026) ----------
   À gauche le cercle des auteurs, à droite la carte de l'avis choisi ; sur un écran étroit,
   le cercle puis la carte. Les bulles sont posées par js/avis-bulles.js, en pourcentages
   du cercle : elles grandissent et rétrécissent avec lui. */
.avis-scene {
  display: grid;
  grid-template-columns: minmax(0, var(--largeur-bulles-avis)) minmax(0, 1fr);
  align-items: center;
  gap: var(--espace-xl);
  margin-block: var(--espace-l);
}

.avis-bulles {
  position: relative;
  aspect-ratio: 1;
  border-radius: 50%;
}

.avis-bulle {
  position: absolute;
  aspect-ratio: 1;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  cursor: pointer;
  container-type: inline-size;
}

/* Le portrait cloné dans la bulle en prend toute la largeur : sa pastille mesure 100 % de
   lui, et lui 100 % de la bulle. */
.avis-bulle .avis-portrait {
  --taille-pastille: 100%;
  width: 100%;
  height: 100%;
}

/* ⚠️ UNE GOUTTE EN APESANTEUR, PAS UN CERCLE (demande d'Abel du 16/10/2026) : pas de contour
   noir, et un bord irrégulier, que le bloc de motion fait onduler. Sans mouvement, la goutte
   garde sa forme irrégulière. */
.avis-bulle .photo-pastille {
  font-size: 30cqi;
  border-radius: var(--goutte-a);
  box-shadow: none;
}

.avis-bulle:nth-child(3n + 2) .photo-pastille {
  border-radius: var(--goutte-b);
}

.avis-bulle:nth-child(3n) .photo-pastille {
  border-radius: var(--goutte-c);
}

/* La bulle choisie : un anneau bleu épais qui suit la goutte, et elle grossit un peu. Une
   forme, pas seulement une couleur. */
.avis-bulle[aria-pressed="true"] .photo-pastille {
  box-shadow: 0 0 0 var(--epaisseur-champ-faute) var(--couleur-accent);
}

/* Plus d'agrandissement : la bulle choisie dériverait hors de son cercle. */
.avis-bulle[aria-pressed="true"] {
  z-index: 1;
}

/* La carte : la ligne de l'avis choisi, seule visible, en grand. */
/* Sa bordure est bleue et deux fois plus épaisse que le contour des cartes (demande d'Abel
   du 16/10/2026). */
.avis-scene .avis {
  grid-template-columns: minmax(0, 1fr);
  padding: var(--espace-xl);
  border: calc(var(--epaisseur-contour) * 2) solid var(--couleur-accent);
  border-radius: var(--rayon-carte-grande);
  background: var(--couleur-fond);
}

.avis-scene .avis + .avis {
  border-top: calc(var(--epaisseur-contour) * 2) solid var(--couleur-accent);
}

.avis-scene .avis-liste .avis-portrait {
  --taille-pastille: var(--taille-portrait-carte-avis);
}

@media (max-width: 48rem) {
  .avis-scene {
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
  }

  /* Le filet du cercle déborde des bulles d'un cran : la largeur le lui laisse. */
  .avis-bulles {
    width: min(calc(100% - var(--espace-m) * 2), var(--largeur-bulles-avis));
    margin-block: var(--espace-m);
  }

  .avis-scene .avis-liste {
    width: 100%;
  }

  .avis-scene .avis {
    padding: var(--espace-l);
  }
}

/* ============================================================
   LE MARQUEUR « VOUS ÊTES ICI » : L'INVERSION (DESIGN.md §4)
   Un seul pour toute l'application : entrée de navigation courante, onglet actif,
   filtre courant, durée choisie, jour, créneau, case cochée. Sur blanc, l'élément
   choisi passe en noir plein à texte blanc. Jamais de filet à gauche, jamais de
   contour ajouté : rien ne décale un voisin.

   ⚠️ CETTE SECTION RESTE LA DERNIÈRE DE LA FEUILLE (PIEGES.md E27). Le marqueur doit
   avoir le dernier mot sur le fond et la couleur de ce qu'il marque, et il a la
   spécificité d'une classe : écrit en tête avec les composants communs, il était battu
   par la règle de base de chaque composant venu après lui (`.nav-lien`, `.choix`), et
   l'entrée courante du header sortait en texte noir sur fond noir. Une seule exception,
   voulue et tenue par la spécificité : le bouton « Se connecter » du header, sur
   `/connexion`, reste le verre noir qu'il est déjà.
   ⚠️ ET IL NE SE DÉFAIT PAS AU SURVOL : chaque survol de composant qui peut croiser un
   élément choisi (`.nav-lien`, `.filtre`, `button.choix`, `.segment`) l'exclut par un
   `:not()`, sans quoi son bleu donnerait un texte bleu sur fond noir (1,96:1).
   ============================================================ */
[aria-current="page"],
[aria-current="true"],
.choix:has(:checked),
.segment[aria-pressed="true"] {
  background: var(--couleur-encre);
  color: var(--couleur-fond);
  box-shadow: none;
}
