/* =================================================================
   TIBILLET — Design system du skin V2
   / TiBillet — V2 skin design system

   LOCALISATION : pages/static/V2/css/V2.css
   Source : maquette TEMP-tibillet-lespass-main/style.css
   (commit 49d31d0 « Direction artistique v1 »), fusionnée avec les
   ajouts propres au V2 (carte Leaflet, page Réseau, pont Bootstrap).

   Règle centrale de la DA : la couleur n'apparaît que sur les gestes
   qui appartiennent à TiBillet (carte, solde, recharge, action
   principale). Jamais sur le contenu d'un lieu (ses événements, ses
   catégories, son identité) : ce contenu reste en encre et papier.
   / Colour only on TiBillet gestures, never on a venue's content.

   Polices auto-hébergées dans pages/static/V2/fonts/ (aucun appel à
   Google, conforme RGPD, fonctionne hors ligne) :
   - Unbounded (SIL OFL) : gros titres uniquement.
   - Luciole (CC BY 4.0, conçue pour les personnes malvoyantes) :
     tout le reste.
   Chemins relatifs (../fonts/) : ils suivent le fichier CSS après
   collectstatic. / Self-hosted fonts, relative paths.
   ================================================================= */

@font-face { font-family: 'Unbounded'; src: url('../fonts/Unbounded-Variable-latin.woff2') format('woff2'); font-weight: 300 900; font-style: normal; font-display: swap; }
@font-face { font-family: 'Luciole'; src: url('../fonts/Luciole-Regular.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: 'Luciole'; src: url('../fonts/Luciole-Regular-Italic.woff2') format('woff2'); font-weight: 400; font-style: italic; font-display: swap; }
@font-face { font-family: 'Luciole'; src: url('../fonts/Luciole-Bold.woff2') format('woff2'); font-weight: 700; font-style: normal; font-display: swap; }
@font-face { font-family: 'Luciole'; src: url('../fonts/Luciole-Bold-Italic.woff2') format('woff2'); font-weight: 700; font-style: italic; font-display: swap; }

/*
Voir comment ajouter Material icon, la police fait 4mon, un peu too much pour juste quelques icones.
Une solution proposé par claude c'est d'utiliser fontTools pour limiter les icones a juste celle utilisé,
 en générant un autre fichier de police. Mais faut le refaire dès qu'on rajoute des icones :/

@font-face {
  font-family: "Material Symbols Lespass";
  font-style: normal;
  font-weight: 100 700;
  font-display: block;
  src: url("../../fonts/material-symbols/MaterialSymbolsOutlined-variable.woff2") format("woff2");
}

.material-symbols-outlined {
  font-family: "Material Symbols Lespass";
  font-weight: normal;
  font-style: normal;
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
  display: inline-block;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  vertical-align: middle;
  font-feature-settings: "liga";
  -webkit-font-feature-settings: "liga";
  -webkit-font-smoothing: antialiased;
  user-select: none;
  font-variation-settings: 'FILL' 1;
}
*/

:root {
  /* --- Palette de marque (charte Tibillet) ---
     Valeurs canoniques : réservées aux fonds pleins, dégradés et
     grands aplats. Ne jamais les employer comme couleur de TEXTE sur
     un fond clair — mesuré : 3,0 à 3,9:1 selon la teinte, sous le
     seuil d'accessibilité de 4,5:1 (cf. --letchi-text / --loseyan-text
     / --chouchou-text, assombries pour rester lisibles en texte). */
    /* action principale, dominante */

  /* Letchi utilisée comme FOND sous un texte encre (boutons, pastilles).
     Le letchi canonique y plafonne à 4,45:1, juste sous le seuil de 4,5 —
     et un bouton de 14,5px gras ne compte pas comme « grand texte » (il
     faut 18,66px gras). Éclaircie de 4 % vers le blanc : 4,63:1, écart
     invisible à l'œil. / Letchi as a FILL under ink text: canonical letchi
     is 4.45:1 (fails AA), lightened 4% it reaches 4.63:1. */

  /* --- Rôles fonctionnels --- */
  /* Fond d'action : porte toujours du texte encre, d'où --letchi-fond et non --letchi.
     / Action fill: always carries ink text, hence --letchi-fond. */
  --color-primary: var(--loseyan-button-fond);
  /* Bouton principal : fond encre, libellé blanc, letchi en liseré (voir .btn--primary).
     / Primary button: ink fill, white label, letchi as an inner edge. */
  --color-action-hover: #2c2630;               /* encre éclaircie au survol : blanc dessus 14,7:1 */
  --color-action-signal: var(--loseyan-button-fond);   /* liseré : élément non textuel, 4,6:1 contre l'encre (seuil 3:1) */
  --color-link: var(--loseyan-text);

  /* --- Neutres --- */
  --color-bg: var(--papier);            /* papier : jamais de blanc pur en fond de page */
  --color-surface: var(--papier);       /* Avec le fond qui est devenue --sable, les surface dessus passe à --papier */
  --color-surface-tint: #f1eee8;
  --color-primary-soft: #fce4eb;  /* tinte très claire de letchi, fonds discrets */
  --color-border: #ece7e1;        /* brume */
  --color-border-strong: #ded7cd;
  --color-footer-bg: #e2e3e5;     /* fond du footer : plus froid que le papier, signale le territoire plateforme */
  --color-surface-sunken: #eceae3; /* en-tête « moi », fond d'attente des cartes */
  --color-hover: #f2f0ea;          /* survol d'un contrôle neutre posé sur blanc */
  --color-hover-soft: #fbfaf7;     /* survol d'une carte-raccourci */
  --color-overlay-hover: rgba(32, 31, 28, 0.04); /* survol transparent (liens de sommaire, bouton contour) */
  --color-rule: rgba(32, 31, 28, 0.10);          /* filet posé sur une surface teintée */
  --color-grid-line: rgba(32, 31, 28, 0.05);     /* quadrillage du fond de carte */
  --color-primary-border: #f3c7d5; /* bordure de l'ancre « Mon espace » active */
  --color-avatar: #e4e1d6;         /* fond d'avatar sans photo */

  /* --- Sur fond sombre (encre, talisman) --- */
  --color-text-inverse: #ffffff;
  --color-inverse-muted: rgba(255, 255, 255, 0.65);
  --color-inverse-faint: rgba(255, 255, 255, 0.55);
  --color-inverse-soft: rgba(255, 255, 255, 0.22);
  --color-surface-overlay: rgba(255, 255, 255, 0.92); /* pastille posée sur une photo */

  /* --- Compositions de dégradés (voisines uniquement — deux couleurs
     opposées sur la roue produisent une zone boueuse au centre) --- */
  --grad-chaud: linear-gradient(135deg, var(--letchi-fond) 0%, var(--zanana) 100%);  /* signature : letchi → zanana — en liseré sous « Recharger » et « Faire un don », plus sous aucun texte (voir .btn--signature) */
  --grad-frais: linear-gradient(135deg, var(--zanana) 0%, var(--chouchou) 100%);  /* zanana → chouchou */
  --grad-calme: linear-gradient(135deg, var(--loseyan) 0%, var(--chouchou) 100%);  /* loséyan → chouchou */
  --grad-nuit:  linear-gradient(160deg, var(--loseyan) 0%, var(--violet) 50%, var(--letchi) 100%); /* loséyan → violet → letchi — décoratif seul, jamais sous du texte de bouton (le point violet retombe à 3,8:1) */
  --grad-talisman: linear-gradient(150deg, var(--loseyan) 0%, var(--violet) 55%, var(--letchi) 100%); /* variante de grad-nuit propre à la carte */

  /* --- Catégories : neutralisées ---
     Le contenu d'un lieu (type d'événement, badge de catégorie, marque
     d'un autre lieu) ne porte plus les couleurs de Tibillet — c'est la
     règle centrale de la DA. Chaque famille garde une variation de
     ton pour rester scannable dans une grille, mais reste dans le
     registre neutre (papier/brume/piton), jamais dans la palette de
     marque. */
  /* Approfondies : les premières valeurs (58%/36% de luminosité) étaient
     trop proches du gris de bordure (--color-border) et du papier —
     1,25:1 de contraste seulement, d'où le liseré pâle et sale autour
     des cartes. Mesuré maintenant à 2,2-2,8:1 contre la bordure. */
  --cat-green-from: #87a190;   --cat-green-to: #516758;   --cat-green-tint: #ebefec;   --cat-green-solid: #456851;
  --cat-slate-from: #8b919c;   --cat-slate-to: #545963;   --cat-slate-tint: #ecedef;   --cat-slate-solid: #495265;
  --cat-mauve-from: #9f8997;   --cat-mauve-to: #65535f;   --cat-mauve-tint: #efebee;   --cat-mauve-solid: #66475c;
  --cat-gold-from: #a59b83;    --cat-gold-to: #6a624d;    --cat-gold-tint: #f0eeea;    --cat-gold-solid: #6c5f42;
  --cat-terracotta-from: #a38d85; --cat-terracotta-to: #69564f; --cat-terracotta-tint: #f0eceb; --cat-terracotta-solid: #6a4e44;
  --cat-teal-from: #899f9f;    --cat-teal-to: #536565;    --cat-teal-tint: #ebefef;    --cat-teal-solid: #476666;
  --cat-olive-from: #9aa187;   --cat-olive-to: #616751;   --cat-olive-tint: #eeefeb;   --cat-olive-solid: #5f6845;
  --cat-indigo-from: #8b899f;  --cat-indigo-to: #545365;  --cat-indigo-tint: #ecebef;  --cat-indigo-solid: #4a4766;


  /* Échelle typographique, en rem : elle suit la taille de police choisie
     par l'utilisateur dans son navigateur — la raison d'être de Luciole.
     Remplace une vingtaine de tailles en px au demi-pixel héritées de la
     maquette (13,5 / 14,5 / 16,5…), arrondies au pas le plus proche.
     / Type scale in rem, so it follows the user's browser font size. */
  --text-2xs: 0.6875rem;  /* 11px — mentions minuscules, pastilles de date */
  --text-xs: 0.75rem;     /* 12px — étiquettes, badges */
  --text-sm: 0.8125rem;   /* 13px — méta, dates */
  --text-md: 0.875rem;    /* 14px — texte secondaire, petits boutons */
  --text-ui: 0.9375rem;   /* 15px — boutons, liens, texte de carte */
  --text-base: 1rem;      /* 16px — corps de texte */
  --text-lg: 1.125rem;    /* 18px — titres de carte et de module */
  --text-xl: 1.375rem;    /* 22px — titres de section (Unbounded) */
  --text-2xl: 1.75rem;    /* 28px — nom du lieu, montant du solde */
  /* Le seul vrai gros titre : Unbounded a besoin de place pour exister.
     28px à 375px de large, 48px au-delà de ~1180px. */
  --text-display: clamp(1.75rem, 1.1rem + 2.6vw, 3rem);

  /* Échelle d'espacement */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;

  --radius-s: 8px;
  --radius-m: 12px;
  --radius-l: 16px;
  --radius-pill: 999px;

  --shadow-card: 0 1px 2px rgba(32, 31, 28, 0.04), 0 1px 1px rgba(32, 31, 28, 0.03);
  --shadow-panel: 0 4px 16px rgba(32, 31, 28, 0.06);
  --shadow-pin: 0 2px 4px rgba(0, 0, 0, 0.2);

  --container-max: 1280px;

  /* Mouvement : deux durées, deux courbes. Jamais le `ease` par défaut du
     navigateur, jamais de rebond sur un état d'interface. On anime la
     couleur, l'opacité et `transform`, jamais la mise en page.
     / Motion: two durations, two curves; no browser `ease`, no bounce. */
  --dur-fast: 150ms;   /* survol, changement de couleur */
  --dur-base: 200ms;   /* rotation d'un chevron, bascule d'état */
  --ease-out: cubic-bezier(0.2, 0, 0, 1);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
}

:root{

  /***********************
  Variable depuis la DA V2
  ***********************/

  /* couleur */
  --letchi: #e93363;
  --zanana: #e9b322;  /* alerte, accent chaud — fonds/dégradés uniquement */
  --chouchou: #259d49;  /* succès, validation */
  --loseyan: #4296cc;  /* info, liens — fonds/dégradés */

  --encre: #17141a;
  --piton: #63515e;
  --papier: #faf8f5;
  --sable: #f4ede0;
  --violet: #8b5fb0;

  /* Gradients */
  --grad-fete:radial-gradient(120% 130% at 12% 8%, var(--letchi) 0%, #ed4960 5%, #f05a5c 10%, #f26958 15%, #f27553 20%, #f1804e 25%, #ee8a4a 30%, #eb9245 35%, #e69a41 40%, #dfa03d 45%, #d8a53a 50%, #cfa938 55%, #c5ab37 60%, #b9ad37 65%, #acae38 70%, #9dae3a 75%, #8dad3c 80%, #7baa3f 85%, #65a742 90%, #4ca246 95%, var(--chouchou) 100%);
  --grad-soiree: radial-gradient(130% 120% at 85% 10%, var(--letchi) 0%, var(--violet) 45%, var(--loseyan) 100%);
  --grad-lagon: radial-gradient(120% 120% at 20% 90%, var(--chouchou) 0%, var(--loseyan) 75%);
  --grad-soleil: radial-gradient(110% 100% at 50% 110%, var(--zanana) 0%, var(--letchi) 75%);
  --grad-jardin: radial-gradient(120% 120% at 80% 90%, var(--chouchou) 0%, var(--zanana) 80%);
  --grad-aurore: radial-gradient(85% 95% at 0% 0%, rgba(233,51,99,.85) 0%, rgba(233,51,99,0) 100%), radial-gradient(85% 95% at 100% 0%, rgba(233,179,34,.85) 0%, rgba(233,179,34,0) 100%), radial-gradient(85% 95% at 100% 100%, rgba(37,157,73,.8) 0%, rgba(37,157,73,0) 100%), radial-gradient(85% 95% at 0% 100%, rgba(66,150,204,.85) 0%, rgba(66,150,204,0) 100%), radial-gradient(55% 70% at 0% 55%, rgba(139,95,176,.75) 0%, rgba(139,95,176,0) 100%), var(--papier);
  --grad-nuit: radial-gradient(130% 80% at 50% 115%, rgba(233,51,99,.85) 0%, rgba(139,95,176,.6) 35%, rgba(66,150,204,.25) 62%, rgba(66,150,204,0) 80%), radial-gradient(70% 40% at 50% 0%, rgba(66,150,204,.18) 0%, rgba(66,150,204,0) 100%), var(--encre);

  /* Ruban signature */
  --grad-ruban: linear-gradient(90deg, var(--letchi) 0%, var(--zanana) 25%, var(--chouchou) 50%, var(--loseyan) 72%, var(--violet) 88%, var(--letchi) 100%);

  --letchi-fond: #ea3b69;
  /* Typo */
  --font-display: 'Unbounded', system-ui, sans-serif; /* Titre */
  --font-text: 'Luciole', system-ui, sans-serif; /* Le reste */

  /* Text color */
  --color-text: var(--encre);
  --color-text-muted: var(--piton);
  --color-text-faint: #808080;

  --letchi-text: #d22e59;
  --chouchou-text: #1e7e3a;
  --loseyan-text: #3578a3;

  /* Volutes */
  --volute-fond: url("../../fond/tibillet-volutes-fond-sable.svg");
  --volute-blanc:url("../../fond/tibillet-volutes-blanc.svg");

  /* Buttons */
  --letchi-button-fond : #fb4f7c;
  --letchi-button-hover : #fa99b2;
  --letchi-button-active : #fa3166;

  --loseyan-button-fond : #86ceff;
  --loseyan-button-hover : #a5d7f9;
  --loseyan-button-active : #a5d7f9;

  --button-primary-fond : var(--loseyan-button-fond);
  --button-primary-hover : var(--loseyan-button-hover);
  --button-primary-active : var(--loseyan-button-active);

  --secondary-button-fond : #fff;


}

/* =================================================================
   PONT BOOTSTRAP ET BLOCS CMS
   / Bootstrap and CMS blocks bridge

   Bootstrap 5.3 reste chargé par pages/V2/shell.html : les panneaux
   latéraux (connexion, contact, réservation, adhésion), les menus
   déroulants et les accordéons du compte en dépendent.
   pages/static/pages/css/tb-blocs.css (blocs des Pages CMS) lit aussi
   ces variables --bs-*. En les rebranchant ici sur les tokens de la
   DA, ces composants prennent le style V2 sans être réécrits.
   Les classes Bootstrap compilées (.btn-primary…) ne lisent pas ces
   variables : elles sont migrées page par page vers .btn--*.
   / Bootstrap vars remapped on the DA tokens: offcanvas, dropdowns,
   accordions and CMS blocks follow the V2 style.
   ================================================================= */
:root {
  --bs-body-font-family: var(--font-text);
  --bs-body-bg: var(--color-bg);
  --bs-body-color: var(--color-text);
  --bs-secondary-color: var(--color-text-muted);
  --bs-tertiary-bg: var(--color-surface-tint);
  --bs-border-color: var(--color-border);
  --bs-link-color: var(--color-link);
  --bs-link-color-rgb: 53, 120, 163;
  --bs-link-hover-color: var(--color-text);
  --bs-link-hover-color-rgb: 23, 20, 26;
  --bs-border-radius: var(--radius-s);
  --bs-border-radius-lg: var(--radius-m);
  /* La couleur principale de Bootstrap, pour tout ce qui la LIT : .bg-primary,
     .text-primary, les anneaux de focus. Le triplet est nécessaire en plus de la
     couleur, parce que Bootstrap écrit `rgba(var(--bs-primary-rgb), …)` et que
     color-mix() ne produit pas un triplet. 234, 59, 105 = --letchi-fond.
     Attention : ça ne suffit PAS pour .btn-primary, dont le Sass compile la
     couleur en littéral — voir le patch des --bs-btn-* plus bas.
     / Bootstrap's primary for everything that READS it. The triplet is needed
     because Bootstrap writes rgba(var(--bs-primary-rgb), …) and color-mix()
     cannot produce one. Not enough for .btn-primary, whose Sass hardcodes the
     colour — see the --bs-btn-* patch below. */
  --bs-primary: var(--color-primary);
  --bs-primary-rgb: 134, 206, 255;

  /* Blocs CMS : titres en Luciole, accent letchi, libellé des boutons
     en blanc sur encre (voir « BOUTONS DES BLOCS »).

     ATTENTION — CES TROIS LIGNES NE PORTENT RIEN POUR LES PAGES CMS, et c'est
     normal : tb-blocs.css déclare ces mêmes jetons sur `.tb-page` lui-même
     (bloc lignes 37-97), et une propriété personnalisée se résout élément par
     élément — l'ancêtre le plus proche qui la déclare gagne, quelles que soient
     la spécificité et l'ordre de chargement. Les surcharges qui agissent
     réellement sont donc posées sur `.tb-page`, dans la section « PAGE CMS » en
     fin de fichier. Ces trois-ci ne servent qu'aux composants hors `.tb-page`.
     Ne pas les croire suffisantes : c'est exactement le piège qui laissait sept
     titres en serif système et l'accent des blocs en bleu Bootstrap.
     / WARNING — these three do NOTHING for CMS pages: the base declares the
     same tokens on `.tb-page` itself, and custom properties resolve per element,
     so the effective overrides live on `.tb-page` (see the CMS section at the
     end of this file). These only serve components outside `.tb-page`. */
  --tb-police-titre: var(--font-text);
  --tb-accent: var(--color-primary);
  --tb-accent-contraste: var(--color-text-inverse);
  /* L'accent en TEXTE : letchi FONCÉ. Le letchi vif ne passe pas en texte sur
     papier (3,87:1, seuil 4,5) alors qu'il passe en aplat sous de l'encre
     (4,45:1) — et l'inverse est vrai du letchi foncé (encre dessus : 3,71:1).
     D'où deux jetons, et non un seul retargeté.
     / The accent AS TEXT: dark letchi. Vivid letchi fails as text (3,87:1) but
     passes as a fill under ink (4,45:1); the reverse holds for the dark one.
     Hence two tokens rather than one retargeted. */
  --tb-accent-texte: var(--letchi-text);
}

/* ---------- Reset / base ---------- */
*, *::before, *::after { box-sizing: border-box; }
/* `clip` et non `hidden` : coupe un débordement horizontal accidentel sans
   créer de conteneur de défilement, donc sans casser les éléments sticky
   (barre utilisateur, sommaire). / clip, not hidden: no scroll container, so
   sticky elements keep working. */
html { -webkit-text-size-adjust: 100%; overflow-x: clip; }
body {
  margin: 0;
  overflow-x: clip;
  /* Hauteur d'écran minimum (pied de page en bas). dvh suit la barre
     d'adresse mobile ; vh reste le repli. Remplace .min-vh-100 de
     Bootstrap, en 100vh et en !important.
     / Min screen height; dvh follows the mobile address bar. */
  min-height: 100vh;
  min-height: 100dvh;
  font-family: var(--font-text);
  color: var(--color-text);
  line-height: 1.6; /* Luciole veut de l'espacement large — lisibilité, pas esthétique */
  font-size: var(--text-base);
  -webkit-font-smoothing: antialiased;

  background-color: var(--sable);
  background-image: var(--volute-fond);
  background-size: 100% auto;
  background-repeat: repeat-y;
  background-position: center top;

}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 700;
  margin: 0;
  color: var(--color-text);
}
p { margin: 0; }
a { color: inherit;}
button { font-family: inherit; }
ul { margin: 0; padding: 0; list-style: none; }
img { max-width: 100%; display: block; }

.container {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 0 var(--space-6);
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

/* =================================================================
   HEADER / NAVIGATION
   ================================================================= */
/* -----------------------------------------------------------------
   BARRE UTILISATEUR — invariante, strictement identique sur toutes
   les pages et dans tous les lieux. N'appartient jamais à un lieu :
   elle ne porte que l'utilisateur et ses réglages.
   ----------------------------------------------------------------- */
.user-bar {
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  position: sticky;
  top: 0;
  z-index: 1000;
}
/* Le ruban signature (DA §04) : un filet fin et long, jamais un aplat.
   Sur la barre utilisateur — la seule surface strictement invariante,
   identique dans tous les lieux — pas sur le contenu d'un lieu. */
.user-bar::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: var(--grad-ruban);
}
/* Le bloc de GAUCHE de la barre : l'ancre « Mon espace » et, dans l'espace perso
   uniquement, le retour vers le lieu. Les regrouper dans un seul conteneur garde
   .user-bar__inner à DEUX enfants, donc son `justify-content: space-between`
   intact — sans ça le retour se serait inséré comme un troisième enfant et aurait
   poussé le groupe d'icônes au milieu.
   / The bar's LEFT block: grouping them keeps .user-bar__inner at two children,
   so its space-between still works; otherwise the back link would have become a
   third child and shoved the icon group to the middle. */
.user-bar__gauche { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.user-bar__inner {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: var(--space-3) var(--space-6);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
}
.me-anchor {
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-1) var(--space-4) var(--space-1) var(--space-1);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-bg);
  font-size: var(--text-ui);
  line-height: 0.9375rem;
  font-weight: 700;
  color: var(--color-text);
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.me-anchor:hover { background: var(--color-hover); border-color: var(--color-border-strong); }
.me-anchor.is-active {
  background: var(--color-primary-soft);
  border-color: var(--color-primary-border);
  color: var(--letchi-text);
}
/* Correction d'encre : mesuré via Canvas, pas deviné. Pour des
   initiales en capitales (sans jambage), l'encre réelle occupe la zone
   ascendante de la police mais laisse vide toute la zone descendante
   réservée — la boîte de ligne, elle, reste symétrique. Centrer la
   BOÎTE (ce que fait align-items:center) n'centre donc jamais l'ENCRE :
   aucune valeur de line-height ne peut corriger ça, l'écart est
   structurel à la police. D'où ce décalage direct, indépendant du
   line-height, calculé pour Luciole Bold à chaque taille utilisée. */
.ink-center-fix { display: inline-block; transform: translateY(2px); }
/* Là où le navigateur sait rogner la boîte de ligne sur la hauteur des
   capitales, plus besoin du décalage mesuré à la main : l'encre est centrée
   par construction, à toutes les tailles et au zoom texte. Le translateY reste
   le repli des navigateurs sans `text-box`.
   / Where supported, text-box trims to cap height: ink centred by design. */
@supports (text-box: trim-both cap alphabetic) {
  .ink-center-fix { transform: none; text-box: trim-both cap alphabetic; }
}

.me-anchor__avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--color-primary);
  color: var(--color-text);
  font-size: var(--text-xs);
  font-weight: 700;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.user-bar .icon-btn { width: 36px; height: 36px; border-radius: 50%; }
/* Sur écran tactile, 44px minimum pour un bouton d'icône (WCAG 2.5.5, et
   recommandation Apple/Google). À la souris, la barre garde sa densité.
   La barre passe alors à ~69px de haut : toujours sous le --tb-sticky-top de
   5rem (80px) du sommaire des pages CMS.
   / Touch screens: 44px icon buttons; mouse keeps the dense bar. */
@media (pointer: coarse) {
  .icon-btn,
  .user-bar .icon-btn { width: 44px; height: 44px; }
}

/* -----------------------------------------------------------------
   ACCUEIL D'UN LIEU — le cadre d'identité : ce qu'est le lieu à
   gauche (texte + praticités), où il est à droite.
   ----------------------------------------------------------------- */
.venue-brief {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 420px;
  gap: var(--space-6);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-l);
  padding: var(--space-6);
}
/* Lieu sans coordonnées GPS : pas de carte, le texte prend toute la largeur.
   / Venue without GPS coordinates: no map, text takes the full width. */
.venue-brief--sans-carte { grid-template-columns: minmax(0, 1fr); }
.venue-brief__text {
  font-size: var(--text-ui);
  line-height: 1.65;
  color: var(--color-text-muted);
  max-width: 60ch;
}
.venue-facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-5);
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-border);
}
.venue-fact__label {
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text-muted);
  margin-bottom: var(--space-2);
}
.venue-fact__value { font-size: var(--text-md); line-height: 1.5; }
.venue-brief .map-panel { height: 100%; min-height: 260px; margin: 0; }
/* Une seule couche de contenant : la carte Leaflet est déjà posée dans le cadre
   blanc de .venue-brief, son propre filet doublait celui du cadre. Les coins
   arrondis restent (ils découpent les tuiles).
   / One containment layer: the map already sits in the brief's frame. */
.venue-brief .map-panel { border: none; border-radius: var(--radius-m); }

/* En-tête de rubrique sur l'accueil : le titre, et l'accès à la
   rubrique complète sur la même ligne. */
.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-2);
}

@media (max-width: 62rem) {
  .venue-brief { grid-template-columns: minmax(0, 1fr); }
  .venue-facts { grid-template-columns: minmax(0, 1fr); gap: var(--space-4); }
}

/* =================================================================
   PIED DE PAGE — il appartient à la plateforme, jamais au lieu.
   Aucun lien de lieu ici : ce serait rejouer la confusion qu'on a
   passé la maquette à défaire.
   ================================================================= */
.site-footer {
  /* Plus froid que le papier du reste du site : signale sans un mot
     qu'on change de territoire, de celui du lieu à celui de Tibillet. */
  background: var(--color-footer-bg);
  border-top: 1px solid var(--color-border-strong);
  margin-top: var(--space-8);
}
.site-footer__inner {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: var(--space-8) var(--space-6);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr) minmax(0, 1fr);
  gap: var(--space-8);
}
.footer-col__title {
  font-family: var(--font-text);
  font-size: var(--text-lg);
  font-weight: 700;
  margin-bottom: var(--space-4);
}
.footer-col nav { display: flex; flex-direction: column; gap: var(--space-3); }
.footer-col a,
.footer-col__bouton {
  font-size: var(--text-ui);
  color: var(--color-text-muted);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding:0;
}
.footer-col a:hover,
.footer-col__bouton:hover { color: var(--color-text); }
.footer-col a svg { flex-shrink: 0; opacity: 0.7; }
.footer-col__sep {
  border: none;
  border-top: 1px solid var(--color-border);
  margin: var(--space-4) 0;
}

.footer-brand__mark {
  display: inline-block;
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
  letter-spacing: -0.01em;
}
/* La marque est un lien vers tibillet.coop : soulignée au survol. / Brand is a link. */
.footer-brand__mark:hover { text-decoration: underline; text-underline-offset: 3px; }
.footer-brand__tagline {
  font-size: var(--text-base);
  font-weight: 700;
  margin-top: var(--space-2);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--color-border);
}
.footer-brand__desc {
  font-size: var(--text-ui);
  line-height: 1.6;
  color: var(--color-text-muted);
  margin: var(--space-4) 0 var(--space-5);
}

/* Appels à l'action du pied de page — même gabarit pour les trois,
   compositions voisines de la DA (jamais grad-nuit ici : le point
   violet retombe sous le seuil de lisibilité pour un texte de bouton).
   Le don n'a plus d'aplat dégradé sous son texte : fond encre, libellé
   blanc, dégradé en liseré (même règle que .btn--signature).

   UN SEUL DÉGRADÉ PAR SURFACE. Trois aplats en dégradé empilés
   contredisaient la règle du .btn--signature (« un CTA de plus en dégradé
   remettrait de la couleur partout ») : le don garde le dégradé chaud, les deux
   autres passent en bouton blanc cerné, leur composition réduite à une
   pastille devant le libellé. La couleur reste un repère, plus un vacarme.
   Libellés autorisés sur deux lignes : les boutons font toute la largeur de la
   colonne et leurs textes traduits (« Create a TiBillet space for your
   collective ») débordaient d'un écran de 320px en `nowrap`.
   / One full gradient per surface: donation keeps it, the other two become
   white buttons with a gradient dot. Labels may wrap (full-width, long
   translated texts overflowed at 320px). */
.footer-cta { display: flex; flex-direction: column; gap: var(--space-3); }

/* Liens de la communauté TiBillet (3e colonne), un par ligne. Le partial
   porte les classes .nav-link de Bootstrap : on retire leur padding et on
   prend la couleur des liens du pied de page.
   / TiBillet community links (3rd column), one per line. */
.footer-liens-tibillet__liste {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.footer-liens-tibillet__liste .nav-link {
  padding: 0;
  font-size: var(--text-ui);
  color: var(--color-text-muted);
}
.footer-liens-tibillet__liste .nav-link:hover { color: var(--color-text); }


.site-footer__legal {
  border-top: 1px solid var(--color-border);
}
.site-footer__legal-inner {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: var(--space-5) var(--space-6);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  flex-wrap: wrap;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.site-footer__logo-france-2030 {
  height: 64px;
  width: auto;
  flex-shrink: 0;
}

/* minmax(0, 1fr) et non 1fr : un `1fr` nu vaut minmax(auto, 1fr), la colonne
   prenait donc la largeur du plus long bouton et le pied de page était coupé à
   droite sur mobile (104px perdus à 320px).
   / minmax(0, 1fr), not 1fr: a bare 1fr grew to the longest button's width and
   the footer was clipped on mobile. */
@media (max-width: 62rem) {
  .site-footer__inner { grid-template-columns: minmax(0, 1fr); gap: var(--space-6); }
}


/* -----------------------------------------------------------------
   EN-TÊTE DE LIEU — fait partie du contenu, défile et disparaît.
   Jamais deux barres fixes empilées. L'identité visuelle du lieu
   s'injecte uniquement par --place / --place-tint.
   ----------------------------------------------------------------- */
.place-header {
  /* Neutre par défaut : la couleur d'un lieu lui appartient, elle ne
     doit jamais hériter de la couleur d'action de Tibillet. Le jour où
     un lieu a son propre thème, c'est cette variable qu'il redéfinit —
     jamais --color-primary. */
  --place: var(--color-text);
  --place-tint: var(--color-surface-tint);
  max-width: var(--container-max);
  margin: var(--space-6) auto;
}
.place-header__card {
  background: var(--papier);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-l);
  padding: var(--space-6);
}
.place-header__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  flex-wrap: wrap;
}
.place-id {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}
.place-id__mark {
  width: 52px;
  height: 52px;
  border-radius: var(--radius-m);
  background: var(--place);
  color: var(--color-text-inverse);
  font-family: var(--font-text);
  font-weight: 700;
  font-size: var(--text-xl);
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.place-id__name {
  font-family: var(--font-text);
  font-weight: 700;
  font-size: var(--text-2xl);
  line-height: 1.1;
}
.place-id__tagline {
  font-size: var(--text-md);
  color: var(--color-text-muted);
}
.place-id__site {
  color: var(--color-link);
  font-weight: 700;
}
.place-nav {
  display: flex;
  gap: var(--space-5);
  flex-wrap: wrap;
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-rule);
}
.place-nav > a,
.place-nav__dropdown > .dropdown-toggle {
  font-size: var(--text-ui);
  font-weight: 400;
  color: var(--color-text-muted);
  padding-bottom: 2px;
  transition: color var(--dur-fast) var(--ease-out);
  text-decoration: none;
}
.place-nav > a:hover,
.place-nav__dropdown > .dropdown-toggle:hover { color: var(--color-text); }
.place-nav > a.is-active,
.place-nav__dropdown > .dropdown-toggle.is-active {
  color: var(--place);
  font-weight: 700;
  border-bottom: 2px solid var(--place);
}

/* Variante « moi » : même composition que l'en-tête d'un lieu, mais en
   tons neutres — l'espace personnel n'est pas un lieu de plus. */
.place-header--me {
  --place: var(--color-text);
  --place-tint: var(--color-surface-sunken);
}
.place-header--me .place-id__mark {
  border-radius: 50%;
  font-size: var(--text-lg);
}

/* =================================================================
   LE TALISMAN — la carte comme objet, et le monde qu'elle héberge.
   Le solde est une information ; l'archipel dessiné derrière est la
   vraie charge. Les deux cohabitent sur le même objet.
   ================================================================= */
/* Bloc identité : le nom, l'adresse, puis l'action d'ouverture. */
.me-id {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
}
.me-id .btn { margin-top: var(--space-3); }

/* La carte, rattachée à l'espace lui-même : elle n'a pas de section à
   elle, elle EST l'espace. Compacte, à droite de l'identité. */
.me-card {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
  /* marge égale sur les quatre côtés : la carte doit « respirer »
     autant en haut et en bas qu'à gauche */
  padding: var(--space-5);
  margin: var(--space-2) 0;
}

.me-card__amount {
  font-family: var(--font-text);
  font-weight: 700;
  font-size: var(--text-2xl);
  line-height: 1;
  /* Encre pleine : un dégradé découpé dans le texte (background-clip: text)
     rend le montant invisible dès que la variable du dégradé est invalide.
     / Solid ink: clipped-gradient text goes invisible if the gradient var breaks. */
  color: var(--color-text);
  width: fit-content;
  margin: var(--space-2) 0 var(--space-3);
  padding-bottom: var(--space-2);
}
/* Monnaie cadeau, sous le solde en euros : plus petite, discrète.
   / Gift currency, under the euro balance: smaller, muted. */
.me-card__cadeau {
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text-muted);
  margin: calc(-1 * var(--space-2)) 0 var(--space-3);
}
.me-card__links {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin-top: var(--space-3);
}
/* Blocs empilés dans l'espace : la carte, puis les accès aux lieux.
   Chacun porte son intitulé et se sépare du précédent par un filet. */
.me-block {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-rule);
}
.me-block__title {
  font-family: var(--font-text);
  font-size: var(--text-lg);
  font-weight: 700;
  margin-bottom: var(--space-4);
}

.me-card { align-items: stretch; }
/* Dans l'en-tête « moi », la carte est déjà dans le cadre teinté : on garde son
   fond blanc (il détache l'objet-carte) mais pas son filet, qui faisait un
   deuxième cadre dans le premier. / Inside the tinted header: white fill kept,
   second border dropped. */
.place-header__card .me-card { border: none; }
/* L'enveloppe est l'élément flex : étirée, elle a une hauteur définie.
   La carte peut alors faire height:100% et laisser l'aspect-ratio
   calculer sa largeur — impossible directement sur un élément flex,
   dont le flex-basis:auto impose une largeur issue du contenu. */
.tcard {
  position: relative;
  height: 150px;
  min-width: 250px;
  border-radius: var(--radius-l);
  overflow: hidden;
  color: #fff;
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  font-family: var(--font-display);
  background: var(--letchi);
}
.tcard__v{
  position: absolute;
  inset: 0;
  background-image: var(--volute-blanc);
  background-size: cover;
  background-repeat: no-repeat;
  opacity: .17;
  pointer-events: none;
}
.tcard__top{
  display: flex;
  justify-content: space-between;
  position: relative;
  z-index: 1;
  font-size: 13px;
  font-weight: 700;
}

.tcard__logo{
  background-image: url("../../logo/logo-blanc.png");
  aspect-ratio: 560/247;
  display: block;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  width: 92px;
}

/* La carte et les infos passent l'une sous l'autre quand la place manque
   (mobile) : sans retour à la ligne, 200px d'infos + ~220px de carte
   débordaient d'un écran de 375px. / Wraps under the card on narrow screens. */
.me-card { flex-wrap: wrap; justify-content: space-around; }
.me-card__info { flex: 1 1 12.5rem; min-width: 0; }

/* -----------------------------------------------------------------
   RACCOURCIS — les lieux où l'on retourne le plus. Dans le bloc de
   l'espace, sous l'identité : c'est par là qu'on entre dans un lieu.
   ----------------------------------------------------------------- */
.place-shortcuts {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--space-3);
}
.shortcut {
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-m);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  min-width: 0;
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.shortcut:hover { border-color: var(--color-border-strong); background: var(--color-hover-soft); }
.shortcut__logo {
  width: 34px;
  height: 34px;
  border-radius: var(--radius-s);
  flex-shrink: 0;
}
.shortcut__text { min-width: 0; }
.shortcut__name {
  display: block;
  font-size: var(--text-md);
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.shortcut__meta {
  display: block;
  font-size: var(--text-2xs);
  color: var(--color-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Cinq colonnes ne tiennent pas en mobile : 3 en tablette, 2 en téléphone. */
@media (max-width: 62rem) { .place-shortcuts { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 30rem) { .place-shortcuts { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.place-shortcuts__more { margin-top: var(--space-4); font-size: var(--text-md); }

/* -----------------------------------------------------------------
   MODULES — une rubrique par type d'activité. Repliables (le
   glisser-déposer de la maquette attend dans V2-reserve.css).
   ----------------------------------------------------------------- */
.module {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
  margin-bottom: var(--space-4);
}
.module__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  cursor: pointer;
  user-select: none;
}
.module__title {
  font-family: var(--font-text);
  font-size: var(--text-lg);
  font-weight: 700;
}
.module__count { color: var(--color-text-faint); font-size: var(--text-md); font-weight: 400; }
.module__chevron {
  margin-left: auto;
  color: var(--color-text-muted);
  transition: transform var(--dur-base) var(--ease-out);
  flex-shrink: 0;
}
.module__body { display: none; border-top: 1px solid var(--color-border); }
.module__content { padding: var(--space-5); }

/* Module vide : aucune plainte, aucune icône triste. On ne dit pas ce
   qui manque, on ouvre une porte vers un lieu. */
.module__invite {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  flex-wrap: wrap;
  padding: var(--space-6) var(--space-5);
}
.module__invite p {
  margin: 0;
  font-size: var(--text-ui);
  line-height: 1.6;
  color: var(--color-text-muted);
  max-width: 58ch;
}
.module__invite .btn { flex-shrink: 0; }

/* Carte de service — adhésion, abonnement, accès. Compacte : ce qui
   compte, c'est le statut et l'échéance. */
.service-card {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.service-card__head { display: flex; align-items: center; gap: var(--space-3); }
.service-card__logo {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-s);
  flex-shrink: 0;
}
.service-card__place { font-size: var(--text-sm); color: var(--color-text-muted); }
.service-card__title { font-family: var(--font-text); font-size: var(--text-lg); font-weight: 700; }
.service-card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  border-top: 1px solid var(--color-border);
  padding-top: var(--space-3);
  margin-top: auto;
  font-size: var(--text-md);
}
/* Le lien ne casse jamais en deux lignes : c'est la mention d'échéance,
   plus longue et moins critique à lire d'un bloc, qui cède la place. */
.service-card__due { color: var(--color-text-muted); min-width: 0; }
.service-card__foot .link { white-space: nowrap; flex-shrink: 0; }
/* Dans un module de Mon espace, le module EST le cadre : les services n'y sont
   plus des cartes bordées posées dans une carte, mais des entrées ouvertes par un
   filet en tête, alignées sur la grille. Hors module (adhésions des lieux voisins),
   la carte garde son cadre.
   Les cartes événement de « Mon agenda » gardent le leur : leur photo de
   couverture a besoin d'un bord pour ne pas flotter.
   / Inside a module, the module is the frame: services become open entries with
   a top rule. Event cards keep their frame (their cover image needs an edge). */
.module__content .service-card {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.talisman {
  position: relative;
  /* la largeur commande la hauteur, jamais l'inverse : sans ça,
     l'étirement de la grille gonfle la carte via l'aspect-ratio */
  align-self: start;
  width: 100%;
  aspect-ratio: 1.586 / 1;
  border-radius: var(--radius-l);
  /* grad-nuit : la seule surface de marque à texte blanc plutôt
     qu'encre — traitement décoratif du talisman, pas un bouton lu. */
  background: var(--grad-talisman);
  color: var(--color-text-inverse);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  overflow: hidden;
  box-shadow: var(--shadow-panel);
}
.talisman__sky { position: absolute; inset: 0; }
.talisman__head,
.talisman__foot { position: relative; }
.talisman__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
}
.talisman__brand {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
}
.talisman__nfc {
  color: var(--color-inverse-muted);
  flex-shrink: 0;
}
.talisman__number {
  /* identifiant carte : 8 caractères alphanumériques, toujours en capitales */
  font-size: var(--text-lg);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}
.talisman__holder {
  font-size: var(--text-xs);
  color: var(--color-inverse-faint);
  margin-top: 3px;
}

.balance__label {
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text-muted);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 10px 18px;
  border-radius: var(--radius-pill);
  font-size: var(--text-ui);
  font-weight: 700;
  line-height: 0.9375rem;
  border: 1px solid transparent;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out), filter var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), transform 100ms var(--ease-out);
}

.btn--primary,
.btn--primary:focus-visible,
.btn-primary,
.btn-primary:focus-visible,
.btn-primaire{
  background: var(--button-primary-fond);
  color: var(--encre);
  border: none;
  transition: 0.2s;
}

.btn--primary:hover:not(:disabled),
.btn-primary:hover:not(:disabled),
.btn--outline-primary:hover:not(:disabled),
.btn-outline-primary:hover:not(:disabled),
.btn-primaire:hover{
  background: var(--button-primary-hover);
  color: var(--encre);
  border: none;
  transition: 0.2s;
}

.btn.btn-primary:active,
.btn-primaire:active,
.btn.btn-primaire:active{
  background: var(--button-primary-active);
  border: none;
}

/* Bootstrap met outline: 0 sur .btn:focus-visible : on remet l'anneau du skin. */
.btn--primary:focus-visible,
.btn-primary:focus-visible,
.btn--outline-primary:focus-visible,
.btn-outline-primary:focus-visible { outline: 2px solid var(--color-link); outline-offset: 2px; }
/* Désactivé : pas de liseré, un geste indisponible ne se signale pas. L'opacité
   vient de .btn:disabled. / Disabled: no letchi edge. */
.btn--primary:disabled,
.btn-primary:disabled,
.btn-primary.disabled { background: var(--color-text); color: var(--color-text-inverse); border-color: var(--color-text); box-shadow: none; }
.btn--secondary { background: var(--color-surface); color: var(--color-text); border-color: var(--color-border-strong); }
.btn--secondary:hover { background: var(--color-hover); }

.btn--tertiary {
  background: #fbd99c;  /* amber-100 /
  color: #17141a;       / encre */
}
.btn--tertiary:hover {
  background: #fdecce;
}
.btn--tertiary:active {
  background: #fdecce;
  transform: translateY(1px);
}






.btn--outline { background: transparent; color: var(--color-text); border-color: var(--color-border-strong); }
.btn--outline:hover { background: var(--color-overlay-hover); }
/* Bordure letchi sur papier. ÉCART ASSUMÉ à la règle centrale en tête de ce
   fichier, qui proscrit la couleur sur le contenu d'un lieu : « Ajouter » et
   « Proposer un évènement » ne SONT pas du contenu, ce sont des gestes qui
   ouvrent le wizard TiBillet — même famille que « Recharger ». En
   .btn--secondary ils se confondaient avec le papier, à côté du titre de page.
   Le TEXTE est en --letchi-text (4,6:1 sur papier) et jamais --color-primary
   brut (4,10:1, sous le seuil de 4,5). La BORDURE est un élément non textuel :
   3:1 suffit, --color-primary passe. Au survol il devient le bouton principal
   (fond encre, libellé blanc, liseré letchi) : règle groupée avec .btn--primary.
   NE PAS CONFONDRE avec .btn-outline-primary (un seul tiret), la classe
   Bootstrap corrigée plus bas.
   / Letchi border on paper. DELIBERATE DEPARTURE from the central rule above:
   these are gestures opening the TiBillet wizard, not venue content, and
   .btn--secondary made them invisible. Text uses --letchi-text; the border is
   non-text (3:1 suffices). Turns into the primary button on hover (grouped with
   .btn--primary above). Not to be confused with Bootstrap's single-dash
   .btn-outline-primary, patched below.
   Focus clavier : fond et libellé répétés, sinon le .btn:focus-visible de
   Bootstrap vide le fond et fait hériter la couleur du parent.
   / Keyboard focus: fill and colour repeated, else Bootstrap empties them. */
.btn--outline-primary,
.btn--outline-primary:focus-visible { background: transparent; color: var(--letchi-text); border-color: var(--color-primary); box-shadow: none; }

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

.btn-secondary:hover{
  background: var(--color-hover);
  color: inherit;
  border-color: var(--color-border-strong);
}

.btn-secondary:active{
  background: #e8e4db;
  transform: translateY(1px);}

/* =================================================================
   BOUTONS BOOTSTRAP DU CHROME PARTAGÉ — remis à la marque
   / Shared-chrome Bootstrap buttons, brought back to the brand
   =================================================================
   Le pont en tête de fichier remappe les --bs-* que Bootstrap LIT. Mais son Sass
   COMPILE sa couleur en littéral dans .btn-primary : il n'y a aucune variable à
   remapper, il faut réécrire ses --bs-btn-*. C'est la technique de
   BaseBillet/static/commun/css/marque.css, que le skin V2 ne charge pas
   (shell.html l.129 : V2 a sa propre palette).

   POURQUOI ICI ET PAS DANS LES GABARITS : ces classes viennent du chrome
   PARTAGÉ, rendu à l'identique par TOUS les skins — panneau de connexion,
   panneau contact, tunnel de réservation, tunnel d'adhésion, page solde, 404,
   500. Les convertir en .btn--primary casserait classic et faire_festival, où
   cette classe N'EXISTE PAS (elle est définie dans ce seul fichier) : ils
   auraient un .btn nu, sans fond. Et comme V2.css n'est chargé que par
   pages/V2/shell.html, la correction est limitée à V2 par construction.
   / The bridge remaps what Bootstrap READS, but its Sass hardcodes the colour in
   .btn-primary, so its --bs-btn-* must be rewritten. These classes come from the
   SHARED chrome rendered by every skin; porting them to .btn--primary would
   break the others, where that class does not exist. V2.css is loaded only by
   the V2 shell, so this patch is V2-scoped by construction. */
/* Les états visibles (fond encre, liseré, survol, appui) sont posés par les
   règles groupées avec .btn--primary. Les --bs-btn-* ci-dessous alignent en plus
   les états que seul Bootstrap connaît : .active, .show, et le bouton coché
   d'un groupe .btn-check (filtres de l'agenda), qui lisent --bs-btn-active-*.
   L'ombre de focus de Bootstrap (un halo) est remplacée par le liseré, pour
   qu'un focus clavier ne l'efface pas.
   / Visible states come from the rules grouped with .btn--primary. These vars
   align the Bootstrap-only states (.active, .show, checked .btn-check); the
   focus halo is replaced by the letchi edge. */
.btn-primary {
  --bs-btn-color: var(--color-text-inverse);
  --bs-btn-bg: var(--color-text);
  --bs-btn-border-color: var(--color-text);
  --bs-btn-hover-color: var(--color-text-inverse);
  --bs-btn-hover-bg: var(--color-action-hover);
  --bs-btn-hover-border-color: var(--color-action-hover);
  --bs-btn-active-color: var(--encre);
  --bs-btn-active-bg: var(--button-primary-active);
  --bs-btn-active-border-color: transparent;
  --bs-btn-disabled-color: var(--color-text-inverse);
  --bs-btn-disabled-bg: var(--color-text);
  --bs-btn-disabled-border-color: var(--color-text);
  --bs-btn-focus-box-shadow: inset 0 -3px 0 var(--color-action-signal);
}
.btn-outline-primary {
  --bs-btn-color: var(--loseyan-text);
  --bs-btn-border-color: var(--color-primary);
  --bs-btn-hover-color: var(--color-text-inverse);
  --bs-btn-hover-bg: var(--color-action-hover);
  --bs-btn-hover-border-color: var(--color-action-hover);
  --bs-btn-active-color: var(--color-text-inverse);
  --bs-btn-active-bg: var(--color-text);
  --bs-btn-active-border-color: var(--color-text);
  --bs-btn-disabled-color: var(--letchi-text);
  --bs-btn-disabled-border-color: var(--color-primary);
  --bs-btn-focus-box-shadow: inset 0 -3px 0 var(--color-action-signal);
}
/* line-height en entier (14px), pas 13.5px : une hauteur de ligne
   fractionnaire force le navigateur à arrondir la répartition de
   l'espace, et l'arrondi ne tombe pas toujours au milieu — mesuré à
   1,5px d'écart constant sur « Réserver », « Demander », etc. */
.btn--sm { padding: 7px 14px; font-size: var(--text-md); line-height: 0.875rem; }

.btn--grad {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: 999px;
  font-family: var(--font-display);
  font-weight: 900;
  min-height: 56px;
  width: 100%;
  max-width:520px;
  /* Libellé capé à 20 px : à 24 px, « Contribuer au projet » passait sur deux
     lignes à 375 px. / Capped at 20px: at 24px the label wrapped at 375px. */
  font-size: clamp(1rem, 0.85rem + 0.5vw, 1.25rem);
  letter-spacing: 0.02em;
  text-decoration: none;
  text-transform: uppercase;
  text-align: center;
  color: #000000;
  padding: 12px 28px;
  background-size: 220% 220% !important;
  background-position: 0% 0%;
  transition: background-position .35s ease, transform .08s ease;
}

.btn-wrap {
  position: relative;
  display: flex;
  z-index: 0;
  justify-content: space-around;
}
/* Halo sur le wrapper */
.btn-wrap::before {
  content:"";
  position:absolute;
  inset:0;
  border-radius:999px;
  background:var(--halo);
  filter:blur(8px);
  opacity:0;
  z-index:-1;
  transition:opacity .2s ease,inset .2s ease;
}

.btn--grad__next_background{
  position:absolute;
  inset:0;
  opacity:0;
  transition:opacity .35s ease;
}
.btn-wrap:hover .btn--grad__next_background,
.btn-wrap:active .btn--grad__next_background{
  opacity:1;
}

.btn-wrap:hover .btn--grad {
  background-position: 100% 100% !important;
}

.btn-wrap:active{
  transform: translateY(1px);
  background-position: 100% 100%;
}
.btn-wrap:hover::before {
  opacity: .4;
  inset: -2px;
}


.btn--grad .tcard__v{
  opacity: 0.2;
}


.btn--grad-content{
  z-index: 2;
}

/* Faire un don — la signature. Fond encre et liseré dégradé : voir .btn--signature.
   4px de plus en bas pour compenser le liseré et garder le libellé centré. */
.btn--grad-chaud {
  background: var(--grad-chaud);
}
.btn--grad-calme {
  background: var(--grad-calme);

}   /* Créer un espace */
.btn--grad-frais {
  background: var(--grad-frais);

}   /* Contribuer au projet */


/* Bootstrap met outline: 0 sur .btn:focus-visible : on remet l'anneau du skin. */
.btn--signature:focus-visible,
.btn--grad-chaud:focus-visible { outline: 2px solid var(--color-link); outline-offset: 2px; }
.btn:disabled { opacity: 0.5; cursor: default; }
/* Appui : le bouton s'enfonce d'un pixel. Un seul signal, pas de rebond ni de
   changement d'échelle. Exclu quand le bouton est désactivé.
   / Press: sinks one pixel. One signal, no bounce, no scale. */
.btn:active:not(:disabled),
.icon-btn:active,
.tag-pill:active { transform: translateY(1px); }

/* Signature et don : même enfoncement, le liseré dégradé (4px) rentre : la
   couche encre reprend toute la hauteur. / Same press, the 4px gradient edge
   retracts: the ink layer takes the full height. */
.btn--signature:active:not(:disabled),

.icon-btn {
  width: 38px;
  height: 38px;
  border-radius: var(--radius-s);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--color-text);
  transition: background var(--dur-fast) var(--ease-out), transform 100ms var(--ease-out);
  flex-shrink: 0;
}
.icon-btn:hover { background: var(--color-hover); }
.icon-group { display: flex; align-items: center; gap: var(--space-2); }

/* =================================================================
   PAGE HEADER (eyebrow + titre + intro)
   ================================================================= */
.page-header { padding: var(--space-7) 0 var(--space-5); }
.eyebrow {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
/* Le vrai gros titre de page (l'équivalent du h1 hero de la DA) :
   Unbounded, pas Luciole — contrairement aux titres de carte, bien
   plus petits, qui restent en Luciole Bold. */
.page-title {
  font-family: var(--font-display);
  font-weight: 800;
  letter-spacing: -0.01em;
  font-size: var(--text-display);
  line-height: 1.1;
  margin-bottom: var(--space-3);
  /* Unbounded est large : un mot long à 48px dépasse vite d'un écran étroit. */
  overflow-wrap: break-word;
  hyphens: auto;
}
.page-intro { color: var(--color-text-muted); font-size: var(--text-base); max-width: 62ch; line-height: 1.55; }

/* =================================================================
   SECTIONS
   ================================================================= */
.section { padding: var(--space-6) 0; }
.section__head { margin-bottom: var(--space-4); }
/* Graisse 500, pas 700 : Unbounded est une police large, à 22px en gras elle
   pèse plus lourd que le titre de page qu'elle est censée suivre. La hiérarchie
   passe par la taille (--text-display contre --text-xl), pas par la graisse.
   / Weight 500: wide Unbounded at 22px bold outweighed the page title. */
.section__title {
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: -0.01em;
  font-size: var(--text-xl);
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.section__title .num {
  font-family: var(--font-text);
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text-faint);
}
/* Chiffre : traitement signature (DA §typo, point 3). */
.section__title-marker { color: var(--letchi-text); font-size: var(--text-ui); }
/* Repasse en Luciole : sinon héritée en Unbounded du parent .section__title. */
.section__meta { font-family: var(--font-text); font-size: var(--text-md); color: var(--color-text-faint); font-weight: 400; margin-left: var(--space-1); }
.section__desc { color: var(--color-text-muted); font-size: var(--text-ui); margin-top: 4px; }
.section__rule { border: none; border-top: 1px solid var(--color-border); margin: var(--space-4) 0 var(--space-5); }

/* =================================================================
   CARDS génériques
   ================================================================= */
.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-card);
}
.card--pad { padding: var(--space-5); }

.grid { display: grid; gap: var(--space-5); }
.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* Grille qui suit un titre de section. Remplace les style="margin-top: …"
   des gabarits. / Grid following a section title; replaces inline styles. */
.grid--sous-titre-serre { margin-top: var(--space-4); }

/* Carte offre (Abonnements / Formations / Réservations / Accès / Programmes) */
.offer-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  /* Hauteur MINIMALE, jamais fixe : l'image de couverture (100px) s'ajoute au
     contenu. Avec une hauteur fixe et overflow:hidden, le pied (prix + bouton)
     sort de la carte et devient invisible. Dans une ligne de .grid, les cartes
     s'etirent toutes a la hauteur de la plus grande.
     / MIN height, never fixed: the cover adds to the content; a fixed height
     with overflow:hidden clips the footer (price + button). */
  min-height: 300px;
}

.offer-card.offer-card-ressource{
  min-height: 400px;
}

.offer-card.offer-card-ressource .offer-card__cover{
  height : 200px;
}

.offer-card__cover{height: 100px; flex-shrink: 0;}

.offer-card__content{
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  /* Prend la place restante sous la couverture (et non 100% de la carte).
     / Fills the space left under the cover (not 100% of the card). */
  flex-grow: 1;
  padding: var(--space-5);
}
.offer-card__title {
  font-size: var(--text-lg);
  font-family: var(--font-display);
  font-weight: 700;
  margin-bottom: var(--space-3);
}
.offer-card__desc {
  color: var(--color-text-muted);
  font-size: var(--text-md);
  line-height: 1.6;
  flex-grow: 1;
  /*overflow: hidden;*/
  /*white-space: nowrap; !* Don't forget this one *!*/
  /*text-overflow: ellipsis;*/
  height: 100%;

  --lignes: 6;
  max-height: calc(var(--lignes) * 1.6em);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--lignes);
  line-clamp: var(--lignes);
  overflow: hidden;
}
.offer-card__price { font-size: var(--text-ui); font-weight: 700; }
.offer-card__price .unit { font-weight: 400; color: var(--color-text-muted); font-size: var(--text-sm); }
.offer-card__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Prix et bouton passent l'un sous l'autre si la carte est trop étroite. */
  /* / Price and button wrap when the card is too narrow. */
  flex-wrap: wrap;
  gap: var(--space-2);
  padding-top: var(--space-3);
  margin-top: var(--space-3);
  border-top: 1px solid var(--color-border);
}
/* Loséyan pour les liens (rôle « info · liens » de la DA) — jamais
   letchi, qui reste réservé à l'action principale. */
.link {
  color: var(--color-link);
  font-weight: 700;
  font-size: var(--text-md);
  text-decoration: underline;
  text-underline-offset: 2px;
  transition: color var(--dur-fast) var(--ease-out);
  /* .link peut aussi être posé sur un <button> : on retire son cadre. */
  /* / .link may also sit on a <button>: remove its frame. */
  border: none;
  background: none;
}
.link:hover { color: var(--color-text); }

.ressource-group-title{
  font-size: 1.2rem;
  margin-top: var(--space-5);
  margin-bottom: var(--space-3);
}

/* =================================================================
   BADGES / CHIPS
   ================================================================= */
/* Un badge qui casse en deux lignes est toujours un bug, jamais voulu. */
.badge {
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: 700;
  line-height: 0.8125rem;
  white-space: nowrap;
  border: 1px solid transparent;
}
.badge--neutral { background: var(--color-bg); border-color: var(--color-border-strong); color: var(--color-text); }
.badge--more { background: var(--color-bg); border-color: var(--color-border-strong); color: var(--color-text-faint); padding-inline: 10px; }
.badge--green { background: var(--cat-green-tint); color: var(--cat-green-solid); }
.badge--slate { background: var(--cat-slate-tint); color: var(--cat-slate-solid); }
.badge--mauve { background: var(--cat-mauve-tint); color: var(--cat-mauve-solid); }
.badge--gold { background: var(--cat-gold-tint); color: var(--cat-gold-solid); }
.badge--terracotta { background: var(--cat-terracotta-tint); color: var(--cat-terracotta-solid); }
.badge--teal { background: var(--cat-teal-tint); color: var(--cat-teal-solid); }
.badge--olive { background: var(--cat-olive-tint); color: var(--cat-olive-solid); }
.badge--indigo { background: var(--cat-indigo-tint); color: var(--cat-indigo-solid); }
.badge--dark { background: var(--color-text); color: var(--color-text-inverse); }
/* Compteur du panier dans la barre utilisateur. Il portait la classe Bootstrap
   `.bg-primary`, dont la déclaration est en `!important` : aucune règle de classe
   ne pouvait la battre, il fallait donc retirer la classe et non la surcharger.
   Letchi assumé : ce qu'on emporte est un geste TiBillet, pas le contenu d'un
   lieu — la règle centrale est respectée. Ce n'est pas le `.badge` large de la
   DA, qui est une étiquette : ici c'est une pastille chiffrée.
   / Cart counter: it carried Bootstrap's `.bg-primary`, declared `!important`, so
   the class had to go rather than be overridden. Letchi is on-rule here: what you
   carry is a TiBillet gesture, not venue content. */
.badge--compteur { background: var(--color-primary); color: var(--color-text); padding: 2px 7px; font-size: var(--text-2xs); }

.tag-pill {
  display: inline-flex;
  align-items: center;
  padding: 9px 20px;
  border-radius: var(--radius-pill);
  font-size: var(--text-md);
  font-weight: 400;
  line-height: 1;
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), transform 100ms var(--ease-out);
  text-decoration: none;
}
.tag-pill:hover { background: var(--color-hover); }
/* Neutre, pas letchi : un filtre de catégorie appartient au contenu du
   lieu (type d'événement, type de lieu), pas à un geste Tibillet. */
.tag-pill.is-active { background: var(--color-text); border-color: var(--color-text); color: var(--color-text-inverse); }

/* =================================================================
   ÉVÉNEMENTS (agenda)
   ================================================================= */
.event-toolbar { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-4); }

.filters-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  flex-wrap: wrap;
}
.search-input {
  flex: 1;
  min-width: 220px;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-pill);
  padding: 10px 16px;
  color: var(--color-text-faint);
  font-size: var(--text-ui);
}
.results-count { margin-left: auto; font-size: var(--text-md); color: var(--color-text-muted); white-space: nowrap; }
.results-count strong { color: var(--color-text); }

.checkbox-line {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  font-size: var(--text-ui);
  cursor: pointer;
}
/* Le champ natif n'est masqué QUE s'il est remplacé par une .box dessinée,
   et visuellement seulement : `display: none` le retirait du clavier et des
   lecteurs d'écran. Les missions bénévoles (evenement_benevoles.html) n'ont
   pas de .box : leur bouton radio disparaissait, rien n'était cliquable
   visiblement ni atteignable à la tabulation.
   / The native input is hidden only when a drawn .box replaces it, and only
   visually: display:none removed it from keyboard and screen readers, and the
   volunteer missions (no .box) lost their radio entirely. */
.checkbox-line input:has(+ .box) {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}
.checkbox-line input:focus-visible + .box { outline: 2px solid var(--color-link); outline-offset: 2px; }
/* Champ natif visible (sans .box) : aligné sur la 1re ligne, coché en encre —
   choisir une mission est un geste sur le contenu du lieu, pas un geste TiBillet.
   / Visible native input: aligned on the first line, checked in ink. */
.checkbox-line > .form-check-input { flex-shrink: 0; margin: 0.2em 0 0; }
.checkbox-line > .form-check-input:checked { background-color: var(--color-text); border-color: var(--color-text); }
.checkbox-line .box {
  width: 20px; height: 20px; flex-shrink: 0;
  border: 1.5px solid var(--color-border-strong);
  border-radius: 5px;
  display: flex; align-items: center; justify-content: center;
  margin-top: 1px;
  background: var(--color-surface);
}
.checkbox-line input:checked + .box { background: var(--color-text); border-color: var(--color-text); color: var(--color-text-inverse); }
.checkbox-line .checkbox-text { color: var(--color-text); }
.checkbox-line .checkbox-text .muted { color: var(--color-text-faint); font-weight: 400; }

/* Carte événement */
.event-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
  overflow: hidden;
  box-shadow: var(--shadow-card);
  display: flex;
  flex-direction: column;
}
.event-card__cover { height: 108px; }
.event-card__body { padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); flex-grow: 1; }
/* La ou les pastilles de date sont posées à cheval sur la couture entre
   la photo et le bloc d'informations — le titre récupère toute la
   largeur de la carte en dessous, il n'a plus à partager la ligne avec
   la date. C'est ce qui permet une deuxième pastille (multi-jours)
   sans agrandir la carte. */
.event-card__dates {
  display: flex;
  align-items: center;
  gap: 4px;
  /* Pas de marge horizontale : elle hérite du padding du corps de carte,
     pile ce qu'il faut pour tomber sous le titre au pixel près — une
     marge ici la décale par rapport au texte en dessous. */
  margin: -48px 0 0;
  position: relative;
  z-index: 2;
}
.event-card__dates .event-date-box { background: var(--color-surface); box-shadow: var(--shadow-card); }
/* Décalée sous le centre de la pastille (~75% de sa hauteur) : au centre
   pile, elle se confond avec la couture photo/infos et devient illisible. */
.event-card__dates-arrow { color: var(--color-text-faint); font-size: var(--text-sm); position: relative; top: 16px; }
.event-date-box {
  width: 52px;
  text-align: center;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-s);
  padding: 5px 0 6px;
  flex-shrink: 0;
}
.event-date-box .dow { font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.03em; color: var(--color-text-muted); text-transform: uppercase; }
.event-date-box .day { font-size: var(--text-lg); font-weight: 700; line-height: 1.1; }
.event-date-box .mon { font-size: var(--text-xs); color: var(--color-text-faint); text-transform: uppercase; }

/* Page événement : bandeau d'ouverture. Même carte que .event-card,
   en grand format. */
.event-hero {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-l);
  overflow: hidden;
  box-shadow: var(--shadow-card);
}
.event-hero__cover {
  height: 220px;
  display: flex;
  align-items: flex-end;
  padding: var(--space-5);
}
.event-hero__badge { display: flex; gap: 6px; }
.event-hero__body { padding: var(--space-6); }
.event-card__title { font-family: var(--font-text); font-size: var(--text-base); font-weight: 700; line-height: 1.3; }
.event-card__time { font-size: var(--text-md); color: var(--color-text-muted); margin-top: 2px; }
.event-card__loc { display: flex; align-items: center; gap: 6px; font-size: var(--text-md); color: var(--color-text-muted); }
.event-card__tags { display: flex; flex-wrap: wrap; gap: 6px; }
/* `margin-top: auto` pousse « par … » et le pied de carte EN BAS du corps
   (colonne flex étirée à la hauteur de la rangée) : « En savoir plus » et le
   prix tombent à la même hauteur sur toutes les cartes, avec ou sans tags.
   / Pushes the byline and footer to the bottom: aligned across the row. */
.event-card__meta { margin-top: auto; font-size: var(--text-sm); color: var(--color-text-faint); padding-top: var(--space-3); border-top: 1px solid var(--color-border); }
.event-card__meta strong { color: var(--color-text); font-weight: 700; }
.event-card__meta a { color: var(--color-link); font-weight: 700; }
.event-card__footer { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--space-2); }
/* Traitement signature des chiffres, en discret : un prix est un geste
   de billetterie même niché dans le contenu d'un lieu. */
.event-card__price { font-size: var(--text-md); font-weight: 700; color: var(--encre); }
.event-hero .page-title{
  font-size: 34px;
}


/* Gradients par catégorie */
.bg-green { background: linear-gradient(135deg, var(--cat-green-from), var(--cat-green-to)); }
.bg-slate { background: linear-gradient(135deg, var(--cat-slate-from), var(--cat-slate-to)); }
.bg-mauve { background: linear-gradient(135deg, var(--cat-mauve-from), var(--cat-mauve-to)); }
.bg-gold { background: linear-gradient(135deg, var(--cat-gold-from), var(--cat-gold-to)); }
.bg-terracotta { background: linear-gradient(135deg, var(--cat-terracotta-from), var(--cat-terracotta-to)); }
.bg-teal { background: linear-gradient(135deg, var(--cat-teal-from), var(--cat-teal-to)); }
.bg-olive { background: linear-gradient(135deg, var(--cat-olive-from), var(--cat-olive-to)); }
.bg-indigo { background: linear-gradient(135deg, var(--cat-indigo-from), var(--cat-indigo-to)); }
.solid-green { background: var(--cat-green-solid); }
.solid-slate { background: var(--cat-slate-solid); }
.solid-mauve { background: var(--cat-mauve-solid); }
.solid-gold { background: var(--cat-gold-solid); }
.solid-terracotta { background: var(--cat-terracotta-solid); }
.solid-teal { background: var(--cat-teal-solid); }
.solid-olive { background: var(--cat-olive-solid); }
.solid-indigo { background: var(--cat-indigo-solid); }



/* =================================================================
   AUTOUR DU LIEU
   ================================================================= */
.around-layout { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: var(--space-6); align-items: start; }
.search-bar {
  display: flex; align-items: center; gap: var(--space-3);
  background: var(--color-surface); border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-m); padding: var(--space-3) var(--space-4);
  color: var(--color-text-faint); font-size: var(--text-ui);
  margin-bottom: var(--space-4);
}
.filter-tabs { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-4); }
.results-line { font-size: var(--text-sm); font-weight: 700; color: var(--color-text-muted); margin-bottom: var(--space-4); }

.place-card {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  padding: var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-card);
}
.place-card__logo { width: 64px; height: 64px; border-radius: var(--radius-m); flex-shrink: 0; }
.place-card__body { flex-grow: 1; min-width: 0; }
.place-card__head { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-2); flex-wrap: wrap; }
.place-card__name { font-family: var(--font-text); font-size: var(--text-lg); font-weight: 700; }
.place-card__dist { display: flex; align-items: center; gap: 4px; font-size: var(--text-sm); color: var(--color-text-faint); margin-left: auto; }
.place-card__desc { font-size: var(--text-ui); color: var(--color-text-muted); margin-bottom: var(--space-3); line-height: 1.5; }
.place-card__foot { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; }
.place-card__tags { display: flex; gap: var(--space-2); flex-wrap: wrap; flex-grow: 1; }

.map-panel {
  background:
    repeating-linear-gradient(0deg, var(--color-grid-line) 0 1px, transparent 1px 140px),
    repeating-linear-gradient(90deg, var(--color-grid-line) 0 1px, transparent 1px 140px),
    var(--color-surface-sunken);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-l);
  height: 100%;
  min-height: 640px;
  position: relative;
  overflow: hidden;
}
.map-panel__toolbar { position: absolute; top: var(--space-4); right: var(--space-4); right: var(--space-4); display: flex; justify-content: space-between; align-items: flex-start; z-index: 100; }
.map-panel__label { font-size: var(--text-sm); font-weight: 700; color: var(--color-text-muted); background: var(--color-bg); padding: 4px ;  border-radius: var(--radius-pill);}
/* Carte Leaflet reelle posee dans le panneau (accueil V2). Elle remplit tout
   le panneau ; le fond quadrille de .map-panel sert de fond de chargement
   avant l'arrivee des tuiles. La toolbar passe au-dessus des controles
   Leaflet (z-index 1000) grace a son z-index 1001.
   / Real Leaflet map inside the panel: fills it entirely; the grid background
   acts as a loading backdrop, the toolbar stays above Leaflet controls. */
.map-panel__carte { position: absolute; inset: 0; }

/* =================================================================
   PAGE RESEAU (V2/vues/reseau.html)
   Habille les composants de l'explorer (seo/explorer.css) au style de
   la maquette TEMP/autour-du-lieu.html.

   / Styles the explorer components with the autour-du-lieu mockup look.

   Toutes les regles sont prefixees #explorer-root : explorer.css est
   charge dans le <body> (apres V2.css qui vit dans le <head>), il faut
   donc une specificite superieure pour gagner la cascade.
   / All rules are prefixed #explorer-root: explorer.css loads in the
   <body> (after V2.css in the <head>), so higher specificity is needed
   to win the cascade.
   ================================================================= */

/* Layout 2 colonnes de la maquette. .explorer-container porte aussi la    */
/* classe .around-layout, mais explorer.css (display:flex + height en vh)  */
/* est charge apres V2.css : on redeclare la grille avec plus de           */
/* specificite. / The mockup 2-column layout, re-declared with higher      */
/* specificity to beat explorer.css flex rules.                            */
/* SEUIL 62rem / 61.99rem (et non 62.001rem / 62rem comme ailleurs) :      */
/* explorer.js bascule en vue bureau à `innerWidth >= 992`. Le CSS doit    */
/* suivre exactement ce seuil, sinon à 992px pile le JS et le CSS se       */
/* contredisent. / Must match explorer.js's `innerWidth >= 992` exactly.   */
@media (min-width: 62rem) {
  #explorer-root .explorer-container {
    display: grid;
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
    gap: var(--space-6);
    align-items: start;
    height: auto;
  }
  #explorer-root .explorer-list { width: auto; overflow: visible; padding: 0; gap: var(--space-4); }
  /* La carte remplit le map-panel (position relative + min-height). */
  /* / The map fills the map-panel (position relative + min-height). */
  #explorer-root .explorer-map { position: absolute; inset: 0; width: auto; height: auto; border-left: none; }
}

/* Mobile : le panneau carte est masque tant que le FAB n'a pas bascule en */
/* vue carte (comportement explorer conserve). / Mobile: the map panel is  */
/* hidden until the FAB toggles to map view (explorer behavior kept).      */
@media (max-width: 61.99rem) {
  #explorer-root .map-panel { display: none; }
  #explorer-root .explorer-container.explorer-view-map .map-panel { display: block; min-height: 0; }
  #explorer-root .explorer-container.explorer-view-map .explorer-map {
    display: block; position: relative; height: calc(100vh - 140px);
    /* dvh : suit la barre d'adresse mobile qui apparaît et disparaît. vh reste le repli. */
    /* / dvh follows the mobile address bar; vh stays as fallback. */
    height: calc(100dvh - 140px);
  }
}

#explorer-root{
  background-color: transparent;
}

/* Recherche : l'input vit dans la .search-bar de la maquette, on retire   */
/* le fond gris + bordure poses par explorer.css. / The input lives inside */
/* the mockup .search-bar: remove explorer.css grey background + border.   */
/* --text-base (16px) et non --text-ui (15px) : sous 16px, iOS zoome la page
   au toucher du champ. / 16px: below that, iOS zooms the page on focus. */
#explorer-root .explorer-search { background: none; border: none; box-shadow: none; padding: 0; font-size: var(--text-base); color: var(--color-text); width: 100%; }
#explorer-root .explorer-search:focus { border: none; box-shadow: none; }

/* Pills Lieux/Evenements : la classe .tag-pill (maquette) est posee sur   */
/* les memes boutons que .explorer-pill (binding JS). On neutralise le     */
/* look explorer et on rebranche l'etat actif sur la classe .active que    */
/* explorer.js bascule (tag-pill utilise .is-active). / The view pills     */
/* carry both classes: neutralize the explorer look and rewire the active  */
/* state to .active (explorer.js toggles .active, tag-pill uses .is-active).*/
#explorer-root .explorer-pill { border: 1px solid var(--color-border-strong); box-shadow: none; padding: 9px 20px; font-size: var(--text-md); }
#explorer-root .explorer-pill:hover { background: var(--color-hover); }
/* Neutre, pas letchi : filtrer la liste est un geste sur le contenu. */
#explorer-root .explorer-pill.active { background: var(--color-text); border-color: var(--color-text); color: var(--color-text-inverse); font-weight: 700; box-shadow: none; }

/* Compteur de resultats : la classe .results-line (maquette) porte le     */
/* style ; on annule juste la marge haute d'explorer.css. / The mockup     */
/* .results-line class carries the style; cancel explorer.css top margin.  */
#explorer-root .explorer-counter { margin-top: 0; }

/* Cards : look .place-card de la maquette. / Cards: mockup .place-card look. */
#explorer-root .explorer-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-card);
}
#explorer-root .explorer-card:hover { border-color: var(--color-border-strong); transform: none; }
#explorer-root .explorer-card-focus { padding: var(--space-5); gap: var(--space-4); }
#explorer-root .explorer-card-icon { width: 64px; height: 64px; border-radius: var(--radius-m); }
#explorer-root .explorer-card-title { font-family: var(--font-text); font-size: var(--text-lg); font-weight: 700; }
#explorer-root .explorer-card-meta { color: var(--color-text-faint); }
#explorer-root .explorer-card-desc { color: var(--color-text-muted); font-size: var(--text-ui); line-height: 1.5; }
#explorer-root .explorer-card-footer { border-top-color: var(--color-border); }

#explorer-root .explorer-card-dist { color: var(--color-text-faint); font-size: var(--text-sm); }
#explorer-root .explorer-card-dist--sans-lieu { color: var(--color-text-faint); }

#explorer-root .explorer-card-link {
  background: var(--button-primary-fond);
  color: #000;
  border-color: transparent;
  font-weight: 700;
  padding: 7px 14px;
  font-size: var(--text-md);
  border-radius: var(--radius-pill);
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out), transform 100ms var(--ease-out);
}
#explorer-root .explorer-card-link:hover {
  background: var(--button-primary-hover);
  color: #000;
  border-color: transparent;
  transition: 0.2s;
  transform: none;   /* explorer.css décale de 2px au survol : un seul mouvement, celui de l'appui */
}
#explorer-root .explorer-card-link:active {
  box-shadow: inset 0 0 0 var(--color-action-signal);
  transform: translateY(3px);
  transition-duration: 60ms;
}
@media (prefers-reduced-motion: reduce) {
  #explorer-root .explorer-card-link:active { transform: none; }
}

/* Carte du lieu courant ("Vous etes ici") : bordure + badge aux couleurs  */
/* du skin, au lieu du bleu Bootstrap (--bs-primary) pose par explorer.css.  */
/* SANS !important : explorer.css écrit `border-color: var(--bs-primary)    */
/* !important`. On ne surenchérit pas, on redéfinit --bs-primary SUR la     */
/* carte elle-même — la variable se résout élément par élément, donc sa     */
/* règle lit notre valeur. Portée limitée à cette carte.                    */
/* / No !important: explorer.css reads var(--bs-primary) with !important;  */
/* redefining the variable on the card itself makes that rule use our ink. */
/* DA v1 : « Vous êtes ici » désigne un lieu, donc encre et non letchi. */
#explorer-root .explorer-card--current { --bs-primary: var(--color-text); }
#explorer-root .explorer-badge--current { background: var(--color-text); color: var(--color-text-inverse); }

.map-pin { position: absolute; display: flex; flex-direction: column; align-items: center; gap: 4px; transform: translate(-50%, -100%); }
.map-pin__icon { width: 26px; height: 26px; border-radius: 50% 50% 50% 0; transform: rotate(45deg); display: flex; align-items: center; justify-content: center; box-shadow: var(--shadow-pin); }
.map-pin__icon svg { transform: rotate(-45deg); }
.map-pin--main .map-pin__icon { background: var(--color-text); width: 32px; height: 32px; }
.map-pin--place .map-pin__icon { background: var(--cat-slate-solid); }
.map-pin__label { background: var(--color-surface); border: 1px solid var(--color-border); padding: 3px 10px; border-radius: var(--radius-pill); font-size: var(--text-xs); font-weight: 700; line-height: 1.4; white-space: nowrap; box-shadow: var(--shadow-card); }

/* =================================================================
   CONTRIBUER
   ================================================================= */
.initiative-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
  overflow: hidden;
  box-shadow: var(--shadow-card);
}
.initiative-card__cover { height: 128px; }
.initiative-card__body { padding: var(--space-5); display: flex; flex-direction: column; gap: var(--space-4); }
.initiative-card__title { font-family: var(--font-text); font-size: var(--text-lg); font-weight: 700; }
.initiative-card__desc { font-size: var(--text-ui); color: var(--color-text-muted); line-height: 1.5; }
.progress-label { display: flex; justify-content: space-between; font-size: var(--text-md); color: var(--color-text-muted); margin-bottom: 6px; }
.progress-label strong { color: var(--color-text); font-weight: 700; }
.progress-track { height: 6px; border-radius: var(--radius-pill); background: var(--color-border); overflow: hidden; }
.progress-fill { height: 100%; background: var(--color-text); border-radius: var(--radius-pill); }
.initiative-card__foot { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--space-2); }
.tasks-open { font-size: var(--text-sm); font-weight: 700; line-height: 1.4; color: var(--cat-gold-solid); background: var(--cat-gold-tint); padding: 4px 12px; border-radius: var(--radius-pill); }

.callout {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  padding: var(--space-5) var(--space-6);
  border: 1.5px dashed var(--color-border-strong);
  border-radius: var(--radius-l);
  margin: var(--space-6) 0;
  flex-wrap: wrap;
}
.callout__title { font-size: var(--text-lg); font-weight: 700; margin-bottom: 4px; }
.callout__text { font-size: var(--text-md); color: var(--color-text-muted); }

.person { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-md); font-weight: 700; }

.avatar {
  border-radius: 50%;
  background: var(--color-avatar);
  display: flex; align-items: center; justify-content: center;
  color: var(--color-text-faint);
  font-family: var(--font-text);
  font-weight: 700;
  line-height: 1;
  flex-shrink: 0;
}
.avatar--sm { width: 28px; height: 28px; font-size: var(--text-xs); }
.avatar--md { width: 48px; height: 48px; font-size: var(--text-base); }
.avatar--lg { width: 64px; height: 64px; font-size: var(--text-xl); }

/* =================================================================
   QUI SOMMES-NOUS
   ================================================================= */
.info-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
  padding: var(--space-4);
  box-shadow: var(--shadow-card);
}
.info-card__label { display: flex; align-items: center; gap: 8px; font-size: var(--text-sm); font-weight: 700; color: var(--color-text-muted); margin-bottom: var(--space-3); }
.info-card__text { font-size: var(--text-ui); line-height: 1.6; }
.info-card__text a { color: var(--color-link); font-weight: 700; }

.team-card {
  display: flex; align-items: center; gap: var(--space-3);
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: var(--radius-m); padding: var(--space-3) var(--space-4);
  box-shadow: var(--shadow-card);
}
.team-card__name { font-size: var(--text-ui); font-weight: 700; }
.team-card__role { font-size: var(--text-sm); color: var(--color-text-muted); }

.timeline { position: relative; padding-left: var(--space-6); }
.timeline::before { content: ''; position: absolute; left: 5px; top: 6px; bottom: 6px; width: 2px; background: var(--color-border-strong); }
.timeline-item { position: relative; padding-bottom: var(--space-5); }
.timeline-item:last-child { padding-bottom: 0; }
/* Pastille centrée sur la ligne : la ligne est à 5 px (2 px de large, centre
   6 px) du bord de .timeline, l'élément commence à --space-6. Pastille de 12 px
   → left = -(--space-6) pour que son centre tombe sur 6 px.
   / Dot centred on the line (line centre = 6px, item starts at --space-6). */
.timeline-item::before {
  box-sizing: border-box;
  content: ''; position: absolute; left: calc(-1 * var(--space-6)); top: 5px;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--color-text); border: 2px solid var(--color-bg);
}
.timeline-item__year { font-weight: 700; font-size: var(--text-ui); margin-bottom: 4px; }
.timeline-item__text { font-size: var(--text-ui); color: var(--color-text-muted); line-height: 1.55; }

.resource-card { display: flex; gap: var(--space-3); align-items: flex-start; padding: var(--space-4); }
.resource-card__icon {
  width: 40px; height: 40px; border-radius: var(--radius-s); flex-shrink: 0;
  background: var(--color-surface-tint); color: var(--color-text);
  display: flex; align-items: center; justify-content: center;
}
.resource-card__title { font-weight: 700; font-size: var(--text-ui); margin-bottom: 3px; }
.resource-card__desc { font-size: var(--text-md); color: var(--color-text-muted); line-height: 1.5; }

.about-banner {
  height: 220px;
  border-radius: var(--radius-l);
  background: linear-gradient(135deg, var(--cat-green-from), var(--cat-green-to));
  margin-bottom: var(--space-5);
  /* Le bandeau accueille une VRAIE carte Leaflet, posée en absolu (bloc LIEU en
     affichage HORIZONTAL). Deux déclarations indispensables, et pour deux
     raisons différentes : sans `relative`, la carte se caderait sur le premier
     ancêtre positionné, donc ailleurs dans la page ; sans `overflow: hidden`,
     les tuiles carrées dépasseraient des coins arrondis. Même patron que
     `.map-panel`, qui porte déjà les deux. Le dégradé sert alors de fond
     d'attente avant l'arrivée des tuiles — la maquette, elle, n'a jamais eu
     que ce dégradé.
     / The banner hosts a REAL Leaflet map, absolutely positioned: without
     `relative` it would anchor to the wrong ancestor, and without
     `overflow: hidden` the square tiles would escape the rounded corners.
     Same pattern as `.map-panel`. The gradient then acts as a loading
     backdrop — the mockup only ever had the gradient. */
  position: relative;
  overflow: hidden;
}
/* La carte remplit le bandeau. Même rôle que `.map-panel__carte`.
   / The map fills the banner, like `.map-panel__carte`. */
.about-banner__carte { position: absolute; inset: 0; }

/* =================================================================
   RESPONSIVE (adaptation raisonnable ; les maquettes sont desktop)
   ================================================================= */
@media (max-width: 68.75rem) {
  .grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .around-layout { grid-template-columns: minmax(0, 1fr); }
  /* .place-nav n'a plus besoin d'être masquée : elle est dans le contenu
     et passe naturellement à la ligne. */
}
@media (max-width: 47.99rem) {
  .grid--4, .grid--3, .grid--2 { grid-template-columns: minmax(0, 1fr); }
  .container { padding: 0 var(--space-4); }
}


/* =================================================================
   AJOUTS DU SKIN V2 — intégration Django de la maquette
   / V2 skin additions — Django integration of the mockup

   Ce que la maquette statique n'avait pas à gérer : utilisateur
   anonyme, panier, logo du lieu, menu dynamique (main_nav), images
   réelles, pagination HTMX, focus clavier.
   ================================================================= */

/* Focus clavier visible partout (accessibilité). Loséyan : 4,5:1. */
/* / Visible keyboard focus everywhere. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--color-link);
  outline-offset: 2px;
}

/* Mouvement réduit : les personnes qui l'ont demandé à leur système n'ont plus
   de translation ni de rotation animée, seulement des changements immédiats.
   Couvre les transitions de tout le skin, y compris Bootstrap (panneaux,
   accordéons). Les ANIMATIONS ne sont pas touchées : les seules du skin sont
   les spinners de chargement de Bootstrap, qu'il ralentit déjà lui-même en
   mouvement réduit — les figer ferait disparaître l'indication de chargement.
   `!important` assumé : c'est une préférence d'accessibilité, elle doit
   l'emporter sur toute transition déclarée ailleurs.
   / Reduced motion: transitions only (Bootstrap spinners already slow down
   themselves; freezing them would hide the loading cue). */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .btn:active:not(:disabled),
  .icon-btn:active,
  .tag-pill:active { transform: none; }
}

/* --- Barre utilisateur --------------------------------------------- */
/* Anonyme : « Se connecter » est un <button> (il ouvre un panneau). */
.me-anchor--login { cursor: pointer; font-family: inherit; }
.user-bar .dropdown-menu { font-size: var(--text-md); }

/* Panier : le badge (rendu aussi par htmx/components/panier_badge.html)
   utilise les classes Bootstrap de position. On le recolore :
   un panier est un geste TiBillet, donc letchi avec texte encre.
   SANS !important : après une mise à jour HTMX, panier_badge.html (partagé par
   tous les skins) pose `.bg-primary` ou `.bg-secondary`, dont Bootstrap écrit le
   fond en `!important`. Plutôt que de surenchérir, on règle les TRIPLETS que ces
   utilitaires lisent : --bs-primary-rgb vaut déjà --letchi-fond (pont en tête de
   fichier), et --bs-secondary-rgb est posé ici sur le gris --color-text-faint
   (#6b6b6b), limité au badge du panier.
   / No !important: the shared partial uses .bg-primary/.bg-secondary, whose
   Bootstrap background is !important. We set the rgb triplets they read instead. */
.icon-btn--panier { position: relative; }
.icon-btn--panier .badge {
  --bs-secondary-rgb: 107, 107, 107;
  padding: 3px 6px;
  font-size: var(--text-2xs);
  line-height: 1;
  border: 2px solid var(--color-surface);
  color: var(--color-text);
}
.icon-btn--panier .badge.bg-secondary { color: var(--color-text-inverse); }

/* --- En-tête du lieu ----------------------------------------------- */
/* Logo réel : fond clair, image contenue (pas recadrée). */
.place-id__mark--logo {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  overflow: hidden;
  padding: 4px;
}
/* Seul !important restant du skin (hors mouvement réduit), et il est subi :
   l'include partagé commun/partials/picture.html pose toujours `.object-fit-cover`
   sur l'image, un utilitaire Bootstrap écrit en !important, sans variable à
   régler. L'enlever demanderait de modifier cet include commun à tous les skins.
   / The only forced !important: the shared picture include always adds
   Bootstrap's !important `.object-fit-cover`, with no variable to set. */
.place-id__mark--logo img { object-fit: contain !important; }
.place-id__name--lien { display: block; color: inherit; text-decoration: none}
.place-id__name--lien:hover { text-decoration: underline; text-underline-offset: 3px; }
.place-nav__dropdown .dropdown-menu { font-size: var(--text-md); }

/* --- Menu du lieu : version mobile (sélecteur dépliant) -------------
   LOCALISATION : pages/templates/pages/V2/tenant_header.html
   (.place-nav-mobile, juste après .place-nav)

   Sous 48rem, le menu en ligne ne tient pas (à 375px, le menu de Lespass
   montrait 2 entrées sur 6). Il est masqué et remplacé par un <details> :
   une ligne « Menu · page active », qui déplie toutes les entrées dans la
   carte du lieu. Aucune ligne de JavaScript.
   Options étudiées (ruban défilant, rangées, liste, « Plus », feuille du
   bas) : voir CHANGELOG/2026-09-25-lespass-v2-responsive.md.
   / Under 48rem the inline menu is hidden and replaced by a <details>
   disclosure: one "Menu · active page" row that expands all entries. */
.place-nav-mobile { display: none; }

@media (max-width: 47.99rem) {
  .place-header__card { padding: var(--space-5); }
  .place-id__name { font-size: var(--text-xl); }

  .place-nav { display: none; }
  .place-nav-mobile {
    display: block;
    margin-top: var(--space-5);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-rule);
  }
}

/* La ligne repliée : un vrai bouton de 48px, blanc sur la teinte du lieu.
   / The collapsed row: a real 48px button. */
.place-nav-mobile__bouton {
  list-style: none;
  cursor: pointer;
  min-height: 48px;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0 var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-m);
  transition: border-color var(--dur-fast) var(--ease-out);
}
/* Retire le triangle natif de <summary> (Safari l'affiche via ce pseudo-élément).
   / Removes the native <summary> marker. */
.place-nav-mobile__bouton::-webkit-details-marker { display: none; }
.place-nav-mobile__bouton:hover { border-color: var(--color-text-muted); }
.place-nav-mobile__bouton:active { background: var(--color-hover); }
.place-nav-mobile__intitule { font-size: var(--text-sm); color: var(--color-text-muted); flex-shrink: 0; }
.place-nav-mobile__page-active {
  flex: 1;
  min-width: 0;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.place-nav-mobile__chevron {
  flex-shrink: 0;
  margin-inline-start: auto;
  transition: transform var(--dur-base) var(--ease-out);
}
.place-nav-mobile[open] .place-nav-mobile__chevron { transform: rotate(180deg); }

/* La liste dépliée, dans la carte du lieu (elle pousse le contenu).
   Le reset global `ul { list-style: none; padding: 0 }` suffit aux puces.
   / The expanded list, inside the venue card (pushes content down). */
.place-nav-mobile__liste {
  margin-top: var(--space-2);
  padding: var(--space-1);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
}
.place-nav-mobile__liste > li + li { border-top: 1px solid var(--color-border); }
.place-nav-mobile__liste a {
  text-decoration: none;
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-s);
  font-size: var(--text-ui);
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.place-nav-mobile__liste a:hover { background: var(--color-hover); color: var(--color-text); }
/* Page active : en gras sur fond papier (pas la couleur seule), et
   aria-current="page" l'annonce aux lecteurs d'écran.
   / Active page: bold on paper, announced via aria-current. */
.place-nav-mobile__liste a.is-active {
  color: var(--color-text);
  font-weight: 700;
  background: var(--color-bg);
}
.place-nav-mobile__sous-liste { padding-inline-start: var(--space-4); padding-bottom: var(--space-1); }
.place-nav-mobile__sous-liste a { font-size: var(--text-md); }

@media (prefers-reduced-motion: reduce) {
  .place-nav-mobile__chevron { transition: none; }
}

/* --- Pied de page -------------------------------------------------- */
.footer-col__bouton {
  background: none;
  border: none;
  padding: 0;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
}
.site-footer__legal-texte { margin: 0; max-width: 62ch; }

/* --- Cartes événement et offre ------------------------------------- */
.event-card__cover { overflow: hidden; }
.event-card__loc svg { flex-shrink: 0; }
.event-card__tags a.badge:hover { border-color: var(--color-text-muted); }
.offer-card__cover picture { height: 100%; }
.offer-card__desc p { margin: 0 0 var(--space-2); }

/* --- Agenda -------------------------------------------------------- */
.page-header__top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.search-input input {
  border: none;
  outline: none;
  background: none;
  width: 100%;
  /* 16px : sous ce seuil, iOS zoome la page au toucher. / 16px avoids iOS zoom. */
  font-size: var(--text-base);
  color: var(--color-text);
}
.search-input:focus-within { outline: 2px solid var(--color-link); outline-offset: 2px; }
/* « Voir plus d'événements » : dernière case de la grille, sur toute
   la largeur. La page suivante vient la remplacer (hx-swap outerHTML).
   / "Load more": last grid cell, full width, replaced by the next page. */
.agenda-suite { grid-column: 1 / -1; text-align: center; padding: var(--space-4) 0 var(--space-6); }
.agenda-vide { grid-column: 1 / -1; color: var(--color-text-muted); }

/* --- Page événement ------------------------------------------------ */
/* / Event page */
.event-retour { margin: var(--space-2) 0 var(--space-4); }
.event-top-bar{
  display: flex;
  justify-content: space-between;
}
.event-admin-buttons .icon-btn{
  border : solid 1px var(--loseyan);
}
.event-date-and-admin{
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--space-2);
}
/* La couverture reçoit une vraie image : l'image remplit le bandeau,
   les tags restent posés dessus. / Real image fills the cover. */
.event-hero__cover { position: relative; overflow: hidden; }
.event-hero__cover picture { position: absolute; inset: 0; }
.event-hero__badge { position: relative; z-index: 1; flex-wrap: wrap; }
.event-hero__badge .badge { background: var(--color-surface); }
.event-hero__actions {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-top: var(--space-5);
  justify-content: space-around;
}
.event-hero__etat { white-space: normal; }
/* Talon au-dessus du bouton. Le bloc prend la largeur de son bouton (jamais
   plus que le bandeau) et le talon s'étire à cette même largeur. Ne pas le
   brider à une largeur fixe : à 30rem, le libellé par défaut (~650px en 28px)
   passait sur deux lignes même en desktop.
   / The block is as wide as its button (never wider than the hero) and the
   stub stretches to match. No fixed width: at 30rem the default label wrapped
   even on desktop. */
.reservation-declencheur { display: flex; flex-direction: column; align-items: stretch; gap: var(--space-3); max-width: 100%; }
/* Le libellé traduit est long (« Je veux réserver une ou plusieurs places ») :
   en 28px sur une seule ligne il faisait ~650px et était coupé net par le
   bandeau (overflow: hidden) sur mobile ET tablette. Taille fluide, retour à
   la ligne autorisé, jamais plus large que le bandeau.
   / Long translated label: fluid size, wrapping allowed, never wider than the hero. */
.reservation-declencheur > .btn--primary {
  font-size: clamp(1.125rem, 0.9rem + 1.5vw, var(--text-2xl));
  line-height: 1.2;
  white-space: normal;
  text-align: center;
  max-width: 100%;
}
/* Talon de billet : prix et places restantes de part et d'autre d'une
   perforation. Contenu du lieu, donc encre et papier ; Unbounded seulement
   pour les deux chiffres, comme le montant du solde. La pastille zanana
   (alerte) signale une jauge presque pleine, jamais sous du texte.
   Une ou deux cases : chaque case prend une colonne égale.
   / Ticket stub: price and seats left on either side of a perforation. Ink and
   paper; Unbounded only for the two figures. Zanana dot = nearly full. */
.talon {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  margin: 0;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-m);
  background: var(--color-bg);
}
.talon__case { position: relative; display: grid; gap: 2px; align-content: start; padding: var(--space-4); min-width: 0; }
/* Perforation : pointillé rentré de 12px en haut et en bas. / Inset dashed perforation. */
.talon__case + .talon__case::before {
  content: "";
  position: absolute;
  inset-block: var(--space-3);
  inset-inline-start: -1px;
  border-inline-start: 2px dashed var(--color-border-strong);
}
.talon__libelle { margin: 0; font-size: var(--text-sm); font-weight: 400; color: var(--color-text-muted); }
.talon__chiffre {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.25rem, 1rem + 1.2vw, var(--text-2xl));
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.talon__alerte {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-inline-end: 6px;
  border-radius: 50%;
  background: var(--zanana);
  vertical-align: 1px;
}
/* Petit téléphone : les cases s'empilent, la perforation devient horizontale.
   / Small phones: cells stack, the perforation turns horizontal. */
@media (max-width: 26rem) {
  .talon { grid-auto-flow: row; }
  .talon__case + .talon__case::before { inset-block: -1px auto; inset-inline: var(--space-3); border-inline-start: 0; border-block-start: 2px dashed var(--color-border-strong); }
}
/* Texte long saisi par le lieu (HTML riche) : largeur de lecture. */
/* / Long rich text from the venue: reading width. */
.event-prose { max-width: 70ch; color: var(--color-text); line-height: 1.65; margin-top: var(--space-4); overflow-wrap: anywhere; }
.event-prose p { margin: 0 0 var(--space-3); }
.event-prose img { border-radius: var(--radius-m); }
/* Le lieu colle ce qu'il veut dans ce texte riche : vidéo intégrée, tableau,
   bloc de code. Rien ne doit dépasser la colonne ; un tableau large défile
   dans sa propre boîte. Les listes retrouvent leurs puces, que le reset
   global `ul { list-style: none }` retirait.
   / Venue rich text: embeds, tables and code never exceed the column; wide
   tables scroll in their own box. Lists get their bullets back. */
.event-prose :is(iframe, video, table, pre) { max-width: 100%; }
.event-prose table { display: block; overflow-x: auto; }
.event-prose pre { overflow-x: auto; }
.event-prose ul { list-style: disc; padding-inline-start: 1.25rem; margin: 0 0 var(--space-3); }
.event-prose ol { padding-inline-start: 1.25rem; margin: 0 0 var(--space-3); }
.event-carrousel, .event-carte { margin-top: var(--space-5); }
.info-card address { font-style: normal; margin: 0; }
.event-hote { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; margin-top: var(--space-4); }

/* Bénévoles : accordéon Bootstrap, habillé par le pont --bs-* plus ces règles. */
/* / Volunteers: Bootstrap accordion styled by the --bs-* bridge. */
.benevoles.accordion-item { border: 1px solid var(--color-border); border-radius: var(--radius-m); overflow: hidden; background: var(--color-surface); }
.benevoles .accordion-button { font-weight: 700; background: var(--color-surface); color: var(--color-text); box-shadow: none; }
.benevoles .accordion-button:not(.collapsed) { background: var(--color-surface-tint); }
.benevoles__missions { border: none; padding: 0; margin: 0 0 var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); }
.benevoles__mission { display: flex; align-items: center; gap: var(--space-3); }
.benevoles__mission .muted { color: var(--color-text-faint); font-size: var(--text-sm); }
.benevoles__mission--complete { color: var(--color-text-muted); }
.benevoles__rappel { margin-top: var(--space-5); }

@media (max-width: 47.99rem) {
  .event-hero__cover { height: 160px; }
  .event-hero__body { padding: var(--space-5); }
}

/* --- Mon espace : modules repliables en <details> natifs ------------ */
/* / My space: collapsible modules as native <details> */
/* La maquette ouvrait/fermait les modules en JS (classe .is-open).
   <details> fait la même chose sans JS, au clavier et au lecteur d'écran. */
details.module > summary { list-style: none; cursor: pointer; }
details.module > summary::-webkit-details-marker { display: none; }
details.module[open] > .module__body { display: block; }
details.module[open] .module__chevron { transform: rotate(180deg); }
details.module + details.module { margin-top: var(--space-4); }

/* --- Mon espace : en-tête, raccourcis, alertes --------------------- */
.compte__entete { margin-bottom: var(--space-5); }
.compte__alerte { margin: 0 0 var(--space-5); }
.compte__modules { padding-bottom: 0; }
.compte__raccourcis .shortcut { justify-content: center; text-align: center; }
.compte__raccourcis .shortcut__name { white-space: normal; }
.me-id .place-id__name { margin: 0; }
.me-block__title { margin-top: 0; }
.compte__remboursement { display: flex; flex-direction: column; gap: var(--space-3); }
.compte__remboursement .callout { flex-direction: column; }
.callout__text p { margin: 0 0 var(--space-2); }
/* .link posé sur un <button> garde la taille du texte voisin. */
.me-card__links .link { font-size: inherit; padding: 0; cursor: pointer; }

/* --- Mon espace : paiement par QR code ------------------------------ */
/* / My space: QR code payment entry points
   Deux gestes repris du skin historique : « Initier un paiement » (bandeau bleu
   clair, bouton jaune) en tête de « Mes responsabilités », et « Scanner un QR code
   de paiement » (bouton bleu) sous « Ma carte ».
   PIÈGES V2 :
   - .btn-primary est ENCRE en V2 (voir plus haut) : le bouton bleu a sa classe.
   - .btn interdit le retour à la ligne (white-space: nowrap) : les libellés
     longs (« Please valid your email to scan a payment QR Code ») déborderaient
     sur mobile. Ces boutons l'autorisent.
   - Les :hover / :focus-visible / :active de Bootstrap lisent des --bs-btn-* que
     ces classes ne définissent pas, et videraient le fond : chaque état est
     reposé, avec .btn devant pour passer devant `.btn:hover`.
   Couleurs (tokens de la palette, en tête de fichier) :
   - bleu : --color-link (#3578a3) sous texte blanc (≈ 4,8:1) ; le fond du bandeau
     est --loseyan (#4296cc) à 12 % sur blanc = #e8f2f9 ;
   - jaune : --zanana, en FOND seulement, toujours sous texte encre ; survol =
     zanana assombrie de 12 % vers l'encre = #d0a021 ;
   - survol du bleu : --color-link assombri de 15 % vers l'encre = #30698e.
   / Blue = link blue under white text; yellow = zanana fill under ink text only.
   Every Bootstrap state is restated, else it empties the fill. */
.compte__encaisser {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin: var(--space-5) var(--space-5) 0;
  padding: var(--space-4) var(--space-5);
  background: #e8f2f9;
  border-left: 4px solid var(--color-link);
  border-radius: var(--radius-s);
}
/* Seul dans le module (caissier·e sans lieu administré) : la marge du bas est
   la sienne. / Alone in the module: it owns the bottom margin. */
.compte__encaisser:last-child { margin-bottom: var(--space-5); }
.compte__encaisser-texte {
  flex: 1 1 20rem;
  margin: 0;
  font-size: var(--text-ui);
  line-height: 1.5;
  color: var(--color-text);
  text-wrap: pretty;
}
.btn.compte__encaisser-bouton,
.btn.compte__encaisser-bouton:focus-visible,
.btn.compte__encaisser-bouton:active {
  background: var(--zanana);
  color: var(--color-text);
  border-color: var(--zanana);
}
.btn.compte__encaisser-bouton:hover {
  background: #d0a021;
  color: var(--color-text);
  border-color: #d0a021;
}
.btn.compte__scanner,
.btn.compte__scanner:focus-visible,
.btn.compte__scanner:active {
  background: var(--color-link);
  color: var(--color-text-inverse);
  border-color: var(--color-link);
}
.btn.compte__scanner:hover:not(:disabled) {
  background: #30698e;
  color: var(--color-text-inverse);
  border-color: #30698e;
}
/* Email non validé : le gris de .btn--secondary, rendu pâle par .btn:disabled.
   / Email not validated: .btn--secondary grey, faded by .btn:disabled. */
.btn.compte__scanner:disabled {
  background: var(--color-surface);
  color: var(--color-text);
  border-color: var(--color-border-strong);
}
.compte__encaisser-bouton,
.compte__scanner {
  min-height: 44px;           /* cible tactile / touch target */
  white-space: normal;
  text-align: center;
  line-height: 1.3;
}
.compte__scanner { margin-top: var(--space-4); }
.compte__encaisser-bouton:focus-visible,
.compte__scanner:focus-visible { outline: 2px solid var(--color-link); outline-offset: 2px; }
/* Mobile : bandeau empilé, boutons sur toute la largeur.
   / Mobile: stacked banner, full-width buttons. */
@media (max-width: 47.99rem) {
  .compte__encaisser { flex-direction: column; align-items: stretch; }
  .compte__encaisser-texte { flex-basis: auto; }
  .compte__scanner { width: 100%; }
}

/* --- Tirelire (/my_account/balance/) -------------------------------- */
/* / Wallet page
   Le bouton scanner : largeur du texte sur ordinateur, toute la largeur sur mobile.
   / Scan button: text width on desktop, full width on mobile. */
.compte-tirelire { margin-top: var(--space-4); }
.compte-tirelire__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.compte-tirelire__bouton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 44px;           /* cible tactile / touch target */
  white-space: normal;
  text-align: center;
  line-height: 1.3;
}
/* .compte__scanner pose une marge haute pour /my_account/ : pas ici.
   / .compte__scanner adds a top margin for /my_account/: not here. */
.compte-tirelire .compte__scanner { margin-top: 0; }
/* Le bandeau d'encaissement n'est pas dans un module : pas de marge latérale,
   un espace sous lui avant le bouton scanner.
   / The collect banner is not in a module: no side margin, space below it. */
.compte-tirelire .compte__encaisser { margin: 0 0 var(--space-4); }
/* Historique : un bouton centre, sous la liste des monnaies.
   / History: one centred button, under the currency list. */
.compte-historique { text-align: center; }
.compte-historique__bouton {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
}
@media (max-width: 47.99rem) {
  .compte-tirelire__actions { flex-direction: column; }
  .compte-tirelire__bouton { width: 100%; }
}

/* --- Sous-pages du compte ------------------------------------------ */
/* / Account sub-pages */
.compte-section { padding-top: var(--space-4); }
.compte-section__contenu { margin-top: var(--space-4); }
.compte__chargement { display: flex; align-items: center; gap: var(--space-2); color: var(--color-text-muted); font-size: var(--text-md); }
.compte .spinner-border { color: var(--color-text-muted); }
.compte-champ { max-width: 32rem; margin-bottom: var(--space-4); }
.compte-champ__aide { font-size: var(--text-sm); color: var(--color-text-muted); margin-top: var(--space-1); }
/* Formulaires Bootstrap au style de la DA. / Bootstrap forms, DA style. */
.compte .form-label { font-weight: 700; font-size: var(--text-md); }
.compte .form-control,
.compte .form-select { border-color: var(--color-border-strong); border-radius: var(--radius-s); font-family: inherit; }
.compte .form-check-input:checked { background-color: var(--color-text); border-color: var(--color-text); }
/* Tableaux : lignes fines, en-têtes discrets. / Tables: thin rows, quiet headers. */
.compte-table { --bs-table-bg: transparent; font-size: var(--text-ui); margin-bottom: var(--space-4); }
.compte-table th { font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--color-text-muted); }
.compte-table td, .compte-table th { border-color: var(--color-border); padding: var(--space-3) var(--space-2); }
.compte-carte { display: flex; flex-direction: column; gap: var(--space-3); }
.compte-carte__pied { margin-top: auto; padding-top: var(--space-3); border-top: 1px solid var(--color-border); }
.callout--erreur { border-color: var(--letchi-text); }

/* Réservation : image à gauche, détail à droite ; empilé sur mobile. */
/* / Booking card: image left, details right; stacked on mobile. */
.reservation-card { display: grid; grid-template-columns: 220px minmax(0, 1fr); overflow: hidden; margin-bottom: var(--space-5); }
.reservation-card__cover { position: relative; min-height: 160px; }
.reservation-card__cover picture { position: absolute; inset: 0; }
.reservation-card__body { padding: var(--space-5); display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.reservation-card__title { font-size: var(--text-lg); margin: 0; }
.reservation-card__lien { color: inherit; }
.reservation-card__lien:hover { text-decoration: underline; text-underline-offset: 3px; }
.reservation-card__meta { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3); font-size: var(--text-md); color: var(--color-text-muted); }
.reservation-card__billets .accordion-item { background: transparent; border-color: var(--color-border); }
.reservation-card__billets .accordion-button { font-weight: 700; font-size: var(--text-md); background: var(--color-surface); box-shadow: none; }
.compte-billet__actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-3); }

/* Adhésion dans l'espace perso. / Membership in the personal space. */
.compte-adhesion--expiree { opacity: 0.7; }
.compte-adhesion__infos { margin: 0; display: flex; flex-direction: column; gap: var(--space-2); font-size: var(--text-md); }
.compte-adhesion__infos dt { font-size: var(--text-sm); font-weight: 700; color: var(--color-text-muted); }
.compte-adhesion__infos dd { margin: 0; }
.compte-adhesion__pied { flex-direction: column; align-items: flex-start; gap: var(--space-2); }

@media (max-width: 47.99rem) {
  .reservation-card { grid-template-columns: minmax(0, 1fr); }
  .reservation-card__cover { min-height: 140px; }
}

/* --- Pages CMS (pages/V2/page.html + blocs) ------------------------ */
/* / CMS pages (pages/V2/page.html + blocks)
   tb-blocs.css garde la grille (.tb-flux) et la mise en page des blocs.
   Ici, on habille seulement : titres, fil d'Ariane, sommaire, bloc lieu,
   sous-cartes. V2.css est chargé APRÈS tb-blocs.css (shell.html), donc à
   spécificité égale, ces règles gagnent.
   / tb-blocs.css keeps the grid; here we only re-dress to the DA v1. */

/* Titre de la page : le vrai gros titre, en Unbounded (comme .page-title). */
.tb-page .tb-page__titre {
  font-family: var(--font-display);
  font-weight: 800;
  letter-spacing: -0.01em;
  font-size: var(--text-display);
  line-height: 1.1;
  overflow-wrap: break-word;
  hyphens: auto;
}
/* Titres des blocs : comme .section__title de la maquette.
   .tb-jetons : le hero de l'accueil par défaut (pages/V2/vues/accueil.html),
   pour qu'il ait le même titre que le bloc HERO d'une page CMS.
   / .tb-jetons: the default home hero, same title as a CMS HERO block. */
.tb-page .tb-bloc__titre,
.tb-jetons .tb-bloc__titre {
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: -0.01em;
  font-size: var(--text-xl);
}
/* UNE SEULE VOIX DE TITRE DE SECTION : celle de .section__title, utilisée par
   les sections codées en dur (« Ce qui arrive », cotton/V2/agenda_a_venir.html)
   et désormais par le bloc LISTE / EVENEMENTS. Sans ce réalignement, une même
   page mélangeait deux styles : titres de bloc avec le filet d'accent du socle
   (tb-blocs.css, `h2.tb-bloc__titre::before`), titres de section sans.
   On retire donc le filet et on reprend l'espacement de .section__title.
   Le titre du HERO est un h1 : il n'est pas concerné.
   / ONE section-title voice, .section__title's: the base's accent rule above
   h2 block titles is removed and the spacing matched, so a page no longer
   mixes two title styles. */
.tb-page h2.tb-bloc__titre::before { content: none; }
.tb-page h2.tb-bloc__titre {
  margin: 0 0 var(--space-3);
  line-height: 1.2;
}
/* Fil d'Ariane : petit, gras, discret — en casse normale, plus lisible que des capitales espacées. */
.tb-page .tb-fil-ariane {
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text-muted);
}
.tb-page .tb-fil-ariane a { color: var(--color-link); }
.tb-page .tb-markdown { font-size: var(--text-base); }
/* Listes du texte libre : le reset global `ul { list-style: none }` (haut de ce
   fichier) retire les puces partout. Dans un texte rédigé, une liste sans puce
   se lit comme des lignes décalées sans raison : on les rend ici.
   / Free-text lists: the global `ul { list-style: none }` reset strips
   bullets; in written text an unbulleted list reads as stray indents. */
.tb-page .tb-markdown ul { list-style: disc; }
.tb-page .tb-markdown ul ul { list-style: circle; }
.tb-page .tb-markdown ol { list-style: decimal; }
.tb-page .tb-markdown li::marker { color: var(--color-text-faint); }

/* Citation : en romain, comme tout texte d'affichage (pas d'italique sur un
   grand corps). Le guillemet décoratif est ramené DANS le cadre : le socle le
   remontait de 0,55 em, il chevauchait le bord haut du bloc.
   / Quote: upright like all display text. The decorative mark is pulled back
   inside the frame; the base raised it 0.55em, over the block's top edge. */
.tb-page .tb-bloc--temoignage .tb-bloc__citation {
  font-style: normal;
  padding-top: var(--space-7);
}
.tb-page .tb-bloc--temoignage .tb-bloc__citation::before { top: 0; }

/* Sommaire : même allure que .toc de la maquette (liste de liens,
   entrée active sur fond blanc). Les classes lues par
   pages/static/pages/js/sommaire_actif.js ne changent pas. */
.tb-page .tb-sommaire { background: transparent; border: none; box-shadow: none; padding: 0; }
/* Étiquette du sommaire : exactement .toc__label de la maquette (V2-reserve.css).
   PAS de font-family ici, volontairement : la maquette n'en pose pas non plus
   (elle hérite de body), et --tb-police-titre est désormais recâblé sur .tb-page
   ci-dessus. Forcer la police ICI aurait réparé cette seule étiquette et laissé
   les six autres titres du socle en serif, en masquant la vraie panne.
   / Exactly the mockup's .toc__label. No font-family on purpose: the mockup has
   none either, and the token is now rewired on .tb-page — forcing the font here
   would have fixed this one label while hiding the real breakage. */
.tb-page .tb-sommaire__titre {
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text-muted);
  margin-bottom: var(--space-3);
}
.tb-page .tb-sommaire__liste { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 2px; }
/* Numéro et libellé collés à gauche, alignés sur la ligne de base.
   `space-between` est exclu : il repoussait les libellés courts (« Et
   maintenant ? ») contre le bord droit, loin de leur numéro.
   / Number and label packed left, on the baseline. `space-between` pushed
   short labels to the right edge, away from their number. */
.tb-page .tb-sommaire__entree a {
  display: flex;
  justify-content: flex-start;
  align-items: baseline;
  gap: var(--space-2);
  padding: 7px var(--space-3);
  border-radius: var(--radius-s);
  border: 1px solid transparent;
  font-size: var(--text-ui);
  color: var(--color-text);
  text-decoration: none;
}
.tb-page .tb-sommaire__entree a:hover { background: var(--color-overlay-hover); }
.tb-page .tb-sommaire__entree--active a {
  background: var(--color-surface);
  border-color: var(--color-border);
  font-weight: 700;
}

/* Bloc LIEU : les infos pratiques prennent l'allure d'une .info-card. */
.tb-page .tb-bloc__infos {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
  padding: var(--space-5);
  box-shadow: var(--shadow-card);
  font-size: var(--text-ui);
  line-height: 1.6;
}
.tb-page .tb-bloc__infos-badge {
  font-size: var(--text-sm);
  font-weight: 700;
}
.tb-page .tb-bloc__infos address { font-style: normal; }
.tb-page .tb-bloc__infos-transport h3 { font-size: var(--text-ui); font-weight: 700; margin: var(--space-3) 0 var(--space-1); }

/* Sous-cartes (MEDIA_ET_CARTES) : allure de .resource-card. */
/* Le socle ne pose qu'un padding-left (il dessinait un simple filet à gauche).
   La carte ayant ici une bordure sur les quatre côtés, il faut un padding sur
   les quatre côtés : sans lui, le texte touche le haut et le bord droit.
   / The base only sets padding-left (it drew a left rule). A four-sided card
   needs four-sided padding, or the text touches the top and right edges. */
.tb-page .tb-sous-carte {
  padding: var(--space-4) var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-card);
}
/* Semi-gras et non gras : ces cartes portent souvent des phrases entières,
   qui deviennent un pavé noir en 700.
   / Semi-bold, not bold: these cards often carry full sentences. */
.tb-page .tb-sous-carte__titre {
  margin: 0;
  font-size: var(--text-ui);
  font-weight: 600;
  line-height: 1.45;
}
.tb-page .tb-sous-carte__titre + .tb-sous-carte__texte { margin-top: var(--space-2); }

/* MEDIA_ET_CARTES, forme « liste » (des phrases sans texte, cf. le tag
   forme_des_sous_cartes) : une liste à puces sur deux colonnes, séparée par
   des filets, au lieu de cartes qui faisaient un pavé en gras.
   / "liste" shape: a two-column bullet list with hairlines instead of cards. */
/* DEUX colonnes fixes, pas `auto-fit` : sur grand écran, auto-fit en ouvrait
   trois et un 4e élément restait seul sur sa ligne. Une seule colonne sous
   48rem.
   / TWO fixed columns: auto-fit opened three and left a 4th item orphaned. */
.tb-page .tb-liste-points {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--space-6);
  margin: var(--space-5) 0 0;
  padding: 0;
  list-style: none;
}
@media (max-width: 47.99rem) {
  .tb-page .tb-liste-points { grid-template-columns: minmax(0, 1fr); }
}
.tb-page .tb-liste-points__element {
  display: grid;
  grid-template-columns: 8px minmax(0, 1fr);
  align-items: baseline;
  column-gap: var(--space-3);
  padding-block: var(--space-3);
  border-top: 1px solid var(--color-border);
}
/* Puce : petit carré d'accent, calé sur la première ligne du texte.
   / Bullet: small accent square, aligned with the first line. */
.tb-page .tb-liste-points__element::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 2px;
  background: var(--loseyan);
  transform: translateY(-1px);
}
.tb-page .tb-liste-points__texte {
  font-size: var(--text-base);
  font-weight: 500;
  line-height: 1.5;
  color: var(--color-text);
}

/* MEDIA_ET_CARTES, forme « pastilles » (un mot par élément, ex : des
   valeurs) : une rangée de pastilles en police d'affichage, au lieu de cartes
   presque vides.
   / "pastilles" shape: a row of display-font pills instead of near-empty cards. */
.tb-page .tb-pastilles {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: var(--space-5) 0 0;
  padding: 0;
  list-style: none;
}
.tb-page .tb-pastille {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 10px var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-pill);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--text-lg);
  line-height: 1.2;
  color: var(--color-text);
}
.tb-page .tb-bloc__carte-badge {
  font-size: var(--text-2xs);
  font-weight: 700;
  border-radius: var(--radius-pill);
  background: var(--color-bg);
  border: 1px solid var(--color-border-strong);
  color: var(--color-text);
}

/* Nouveaux affichages SECTION : EQUIPE, FRISE, RESSOURCES (maquette qui-sommes-nous). */
.equipe-grille,
.ressources-grille {
  list-style: none;
  padding: 0;
  margin: var(--space-5) 0 0;
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr));
}
.team-card__name, .team-card__role { display: block; }
.equipe-grille__badge { margin-top: var(--space-2); }
.tb-bloc--frise .timeline { list-style: none; margin: var(--space-5) 0 0; }
.ressources-grille__lien { font-size: inherit; }

/* =================================================================
   PAGE CMS : SOMMAIRE À GAUCHE, SECTIONS NUMÉROTÉES
   Maquette : qui-sommes-nous.html (.layout-with-sidebar, .toc, .section__title)
   / CMS page: ToC on the left, numbered sections.
   ================================================================= */

/* LE SOMMAIRE PASSAIT SOUS LA BARRE UTILISATEUR — ce n'était pas un problème
   de z-index. tb-blocs.css colle le sommaire à `top: var(--tb-sticky-top)` et
   déclare cette variable à 1.5rem, en prévenant qu'« un skin dont l'entête
   reste fixe la surcharge, sinon le haut du sommaire disparaît dessous au
   défilement ». V2 a une .user-bar collante (top: 0) et ne la surchargeait
   pas : le point d'extension existait, il n'était pas utilisé.
   faire_festival.css fait de même (6.5rem, entête plus haut).
   MESURE : .user-bar__inner = 38px (.icon-btn) + 2 × 12px (--space-3) + 1px de
   filet ≈ 63px. 5rem (80px) la dégage avec une respiration, et coïncide avec le
   `scroll-margin-top: 5rem` que le socle pose sur les titres — une ancre visée
   depuis le sommaire se cale donc au même endroit que le sommaire lui-même.
   / The ToC slid under the sticky user bar. Not a z-index issue: tb-blocs.css
   pins it at --tb-sticky-top (1.5rem) and documents that a skin with a fixed
   header must override it. V2 never did. */
/* TOUS LES JETONS DU SOCLE SURCHARGÉS PAR V2 VIVENT ICI, ET PAS SUR :root.
   La raison est la même pour les cinq, et c'est le piège central de ce fichier :
   tb-blocs.css déclare ses jetons sur `.tb-page, .tb-jetons` (bloc lignes 37-97),
   donc sur l'élément lui-même. Or une propriété personnalisée se résout ÉLÉMENT
   PAR ÉLÉMENT : l'ancêtre le plus proche qui la déclare gagne, quelles que soient
   la spécificité et l'ordre de chargement des feuilles — l'héritage depuis :root
   ne sert qu'aux éléments qui n'ont aucune déclaration plus proche. Une
   surcharge posée sur :root est donc TOUJOURS perdante pour `.tb-page` et ses
   descendants : ce n'est pas une question de cascade, les deux déclarations ne
   visent pas le même élément (html d'un côté, div.tb-page de l'autre).
   `.tb-page` est un point d'accroche sûr : un seul élément le porte dans tout le
   skin (pages/templates/pages/V2/page.html).
   / EVERY base token V2 overrides lives HERE, not on :root: the base declares
   them on `.tb-page` itself, and a custom property resolves per element — the
   nearest declaring ancestor always wins, whatever the specificity or sheet
   order. A :root override can never reach .tb-page. */
.tb-page {
  --tb-sticky-top: 5rem;

  /* CADRAGE : main.container (shell.html) cadre DÉJÀ la page à 1216 px
     (1280 − 2 × 32), et le bandeau du lieu (.place-header) épouse exactement
     cette boîte, n'ayant aucun padding propre. Le socle posait un SECOND
     cadrage par-dessus : sa gouttière plafonnée à 64 px, plus une largeur-max
     de 72rem, ramenaient la colonne à 1088 px démarrant à 96 px du bord du
     <main> au lieu de 32 — « trop étroit, pas aligné ». On neutralise donc le
     cadrage interne : la page CMS prend la même colonne que toutes les autres
     vues V2, qui s'appuient directement sur main.container.

     LES DEUX JETONS SONT NÉCESSAIRES, la gouttière étant le PLANCHER du max() :
     largeur-max seule → max(64px, 0) = 64px, rien ne change ; gouttière seule
     → max(0, (1216−1152)/2) = 32px, toujours désaligné.

     `0px` et NON `0` : le jeton est substitué dans max()/calc(), où un zéro sans
     unité rend la valeur invalide au calcul — le padding retomberait à sa valeur
     initiale, en silence.
     `100%` et NON une longueur fixe : le même jeton sert de largeur de référence
     aux blocs pleine largeur, où il est comparé à 100vw (marge négative puis
     padding symétrique). À 100 % le bloc touche pile les bords de l'écran et son
     contenu revient pile sur le bord du bandeau ; à 1280px il s'arrêterait 32 px
     avant, laissant deux bandes de papier.
     La largeur de LECTURE ne change pas : .tb-markdown reste capé à 72ch.
     / main.container already frames the page at 1216px and .place-header hugs
     that box; the base added a SECOND framing on top. Both tokens are needed
     (the gutter is the max()'s floor). `0px` not `0` (a unitless zero is invalid
     inside max()/calc()); `100%` not a fixed length (full-bleed blocks compare
     this token to 100vw). Reading width unchanged. */
  --tb-gouttiere: 0px;
  --tb-largeur-max: 100%;

  /* POLICE DES TITRES DE BLOCS. Le socle pose ici une serif système (ligne 88) ;
     le pont de :root qui la recâblait vers Luciole ne pouvait pas l'atteindre,
     pour la raison expliquée en tête de ce bloc. Sept sélecteurs s'affichaient
     donc en Georgia : étiquette du sommaire et du menu latéral, questions de
     FAQ, titres de cartes et de sous-cartes, liens précédent/suivant, citation
     de témoignage. Seuls .tb-page__titre et .tb-bloc__titre y échappaient, parce
     que V2 leur impose explicitement --font-display plus bas.
     / The base sets a system serif here; the :root bridge could not reach it, so
     seven selectors were rendering in Georgia. */
  --tb-police-titre: var(--font-text);

  /* Même cause : l'accent des blocs CMS lisait var(--bs-primary), c'est-à-dire le
     bleu par défaut posé par bootstrap.min.5.3.3.css (la valeur n'est pas écrite
     ici : tests/pytest/test_marque.py refuse ce littéral dans le CSS du dépôt).
     Filets au-dessus des titres, badges du bloc
     LIEU, bordure des sous-cartes, bande du bloc CTA et bordures des boutons
     étaient en bleu Bootstrap au lieu du letchi.
     / Same cause: the CMS accent was reading Bootstrap blue instead of letchi.
     --tb-accent-contraste en BLANC : les boutons des blocs ne posent plus leur
     libellé sur le letchi mais sur l'encre (voir « BOUTONS DES BLOCS » juste
     après ce bloc). / White: block buttons now sit on ink, see below. */
  --tb-accent: var(--color-primary);
  --tb-accent-contraste: var(--color-text-inverse);
  /* Et l'accent en TEXTE, sur le même élément que --tb-accent (c'est là que le
     socle le déclare, donc le seul endroit où une surcharge gagne). Letchi FONCÉ :
     le vif échoue en texte (3,87:1 sur papier, seuil 4,5) tout en passant en aplat
     sous de l'encre (4,45:1). Deux rôles, deux jetons.
     / The accent AS TEXT, on the same element as --tb-accent (the only place an
     override wins). Dark letchi: the vivid one fails as text while passing as a
     fill under ink. Two roles, two tokens. */
  --tb-accent-texte: var(--letchi-text);
}

/* BOUTONS DES BLOCS — même dessin que .btn--primary : fond encre, libellé blanc,
   liseré letchi de 3px (5px au survol) qui rentre à l'appui pendant que le
   bouton descend. Le socle posait le libellé sur le letchi : 4,63:1 en WCAG,
   Lc 38 en APCA, sur un texte de 16px en semi-gras. Le contour garde son letchi
   au repos (--tb-accent-texte, Lc 72 sur blanc) et devient le bouton plein au
   survol. Le filet des titres, les guillemets et les bordures restent en letchi :
   ce ne sont pas des textes.
   Le socle soulève le bouton plein au survol (translateY(-2px) et ombre portée) :
   annulé, le seul mouvement est l'enfoncement. Sélecteurs préfixés .tb-page pour
   passer devant le socle, y compris devant son bloc prefers-reduced-motion qui
   remet une ombre au survol.
   / Block buttons: same look as .btn--primary. The base put the label on letchi
   (Lc 38). The outline keeps letchi at rest and fills on hover. The base's hover
   lift is cancelled: the press is the only motion. */
/* Taille et interligne communs aux DEUX variantes : posés sur une seule, les
   deux boutons d'un même bloc n'avaient ni la même hauteur ni la même ligne de
   base (15 px contre 16 px).
   / Shared size and line-height: set on one variant only, two side-by-side
   buttons had different heights and baselines. */
.tb-page .tb-bloc__bouton--plein,
.tb-page .tb-bloc__bouton--contour {
  border-radius: var(--radius-pill);
  font-size: var(--text-ui);
  line-height: 1.25;
  /* Écran étroit : le socle autorise le retour à la ligne (≤ 30rem), mais un
     libellé sans espace (une adresse e-mail) n'a aucun point de coupure et
     débordait du bouton, coupé net. On l'autorise à se couper n'importe où.
     / A label with no spaces (an e-mail address) overflowed the button. */
  overflow-wrap: anywhere;
}
.tb-page .tb-bloc__bouton--plein {
  background: var(--button-primary-fond);
  color: #000;
  border-color: transparent;
}
.tb-page .tb-bloc__bouton--plein:hover,
.tb-page .tb-bloc__bouton--contour:hover {
  background: var(--button-primary-hover);
  color: #000;
  border-color: transparent;
  transition: 0.2s;
  transform: none;
  box-shadow: none;
}
.tb-page .tb-bloc__bouton--plein:active,
.tb-page .tb-bloc__bouton--contour:active {
  background: var(--button-primary-fond);
  color: #000;
  border-color: transparent;
  transition: 0.2s;
}

/* SOMMAIRE À GAUCHE. Le socle le place à DROITE (grid-column: 10 / -1), en
   vis-à-vis du menu de rubrique ; la DA v1 le veut à gauche, comme
   .layout-with-sidebar de la maquette (220px + contenu). On échange donc les
   colonnes, sans toucher à l'ordre du DOM : le placement est explicite des deux
   côtés, donc un lecteur d'écran continue d'annoncer le sommaire après le titre.

   DEUX PRÉCAUTIONS, dictées par le socle :
   — la surcharge vit dans `min-width: 62.001rem`, EXCLUSIVE du `max-width:
     62rem` où le socle remet tout sur une colonne. Les deux ne s'appliquent
     jamais ensemble, donc aucune guerre de spécificité avec la version mobile
     — le socle prévient lui-même qu'« une media query ne l'emporte pas sur la
     spécificité » et que ses règles mobiles reprennent celles du desktop ;
   — le cas « menu latéral + sommaire » est EXCLU : le menu occupe déjà la
     colonne de gauche, et le socle gère déjà ces trois colonnes (y compris son
     repli en tablette). Deux colonnes de gauche se recouvriraient.
   / ToC on the LEFT (the base puts it right), scoped to the desktop query,
   which is mutually exclusive with the base's mobile reset — hence no
   specificity fight. The menu+ToC three-column case is excluded. */
@media (min-width: 62.001rem) {
  .tb-page.tb-flux:has(> .tb-sommaire):not(:has(> .tb-menu-lateral)) > .tb-sommaire {
    grid-column: 1 / span 3;
  }
  .tb-page.tb-flux:has(> .tb-sommaire):not(:has(> .tb-menu-lateral))
    > *:not(.tb-sommaire):not(.tb-colonnes-4):not(.tb-colonnes-6) {
    grid-column: 4 / -1;
  }
}

/* Le sommaire prend l'allure de .toc : étiquette en petites capitales, liens en
   pastilles, entrée active sur fond blanc cerné (déjà posé plus haut).
   LES NUMÉROS VIENNENT D'UN COMPTEUR CSS, jamais des données : ni `Bloc` ni
   `table_des_matieres` ne portent de numéro, et sommaire_actif.js relie chaque
   entrée à son titre PAR L'ANCRE — un numéro écrit dans le texte changerait le
   libellé et l'ancre, donc casserait cette correspondance. Un compteur laisse
   le DOM intact : le script continue de marquer l'entrée lue.
   / Numbers come from a CSS COUNTER, never from the data: sommaire_actif.js
   matches entries to headings by anchor, so a number written into the text
   would break that. A counter leaves the DOM untouched. */
.tb-page .tb-sommaire__liste { counter-reset: tb-sommaire; }
.tb-page .tb-sommaire__entree--niveau-1 { counter-increment: tb-sommaire; }
/* Largeur fixe du numéro : les libellés des entrées de niveau 1 démarrent tous
   au même endroit, et les sous-entrées (sans numéro) se calent dessus.
   / Fixed number width: level-1 labels all start at the same x, and the
   unnumbered sub-entries line up with them. */
.tb-page .tb-sommaire__entree--niveau-1 > a::before {
  content: counter(tb-sommaire, decimal-leading-zero);
  flex: 0 0 1.25rem;
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--color-text-faint);
  font-variant-numeric: tabular-nums;
}
/* Sous-entrées : décalées de la largeur du numéro + l'écart, pour que leur
   libellé tombe sous celui de l'entrée parente.
   / Sub-entries: shifted by number width + gap, under the parent's label. */
.tb-page .tb-sommaire__entree--niveau-2 { padding-left: calc(1.25rem + var(--space-2)); }
.tb-page .tb-sommaire__entree--niveau-3 { padding-left: calc(1.25rem + var(--space-2) + var(--space-4)); }

/* TITRES DE SECTION NUMÉROTÉS, comme .section__title de la maquette : le numéro
   en Luciole devant le titre en Unbounded, puis un filet fin.

   LA BALISE EST `h3` ET NON `h2` : le moteur démote les titres nativement
   (`baselevel = NIVEAU_DE_BASE_DES_TITRES = 2`, cf. pages_tags.py), donc un
   `##` écrit dans un bloc TEXTE sort en `<h3>`. Viser `h2` ne numéroterait
   rien, en silence.

   LE COMPTEUR PORTE SUR CES MÊMES TITRES `##`, qui sont exactement ceux que
   `table_des_matieres` liste : les deux numérotations ne peuvent donc pas se
   contredire, même si un bloc est réordonné dans l'admin.

   PORTÉE : seulement les pages qui AFFICHENT un sommaire. Ailleurs les numéros
   n'auraient aucun repère en face et ne diraient rien.
   / Target `h3`, not `h2`: the engine demotes headings natively (baselevel 2),
   so a `##` renders as <h3> — targeting h2 would silently number nothing. The
   counter runs on the very headings the ToC lists, so both agree. Scoped to
   pages that actually show a ToC. */
.tb-page.tb-flux:has(> .tb-sommaire) { counter-reset: tb-section; }
.tb-page.tb-flux:has(> .tb-sommaire) .tb-markdown > h3 {
  counter-increment: tb-section;
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: -0.01em;
  font-size: var(--text-xl);
}
/* Repasse en Luciole : sinon le numéro hériterait de l'Unbounded du titre. */
/* / Back to Luciole: the number would otherwise inherit the title's Unbounded. */
.tb-page.tb-flux:has(> .tb-sommaire) .tb-markdown > h3::before {
  content: counter(tb-section, decimal-leading-zero);
  flex-shrink: 0;
  font-family: var(--font-text);
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text-faint);
  font-variant-numeric: tabular-nums;
}

/* Le filet de .section__rule. La maquette le pose SOUS la phrase de situation
   quand il y en a une, sinon sous le titre : on distingue donc les deux cas
   plutôt que de supposer qu'un paragraphe suit toujours — sur une page de
   journal, un titre peut être suivi d'une liste ou d'une image.
   / The .section__rule hairline: under the description line when there is one,
   otherwise under the title — rather than assuming a paragraph always follows. */
.tb-page.tb-flux:has(> .tb-sommaire) .tb-markdown > h3:not(:has(+ p)) {
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--color-border);
}
.tb-page.tb-flux:has(> .tb-sommaire) .tb-markdown > h3 + p {
  color: var(--color-text-muted);
  font-size: var(--text-ui);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--color-border);
}

/* =================================================================
   PAGES CMS — ÉQUILIBRE OPTIQUE DES BLOCS
   / CMS pages — optical balance of blocks

   Une seule règle de lecture : tout part du MÊME bord gauche (celui du
   bandeau du lieu, x = bord de main.container) et chaque encadré a le même
   air en haut et en bas. Les cadres réutilisent le dessin des cartes
   (surface papier, filet, rayon M, ombre de carte) au lieu d'inventer un
   fond par bloc.
   / One reading rule: everything starts from the SAME left edge, and every
   framed block has equal air top and bottom. Frames reuse the card look
   instead of inventing a background per block.
   ================================================================= */

/* Titre de page, fil d'Ariane et signature sur l'axe gauche du contenu.
   Le socle leur donne `margin: 0 auto` + une largeur max : dans la grille
   .tb-flux, un élément à marges automatiques se rétrécit à son texte et se
   CENTRE. Un titre court (« Vortex ») flottait alors au milieu, au-dessus de
   blocs alignés à gauche.
   / The base's `margin: 0 auto` makes a grid item shrink to its text and
   centre itself: short titles floated mid-page above left-aligned blocks. */
.tb-page.tb-flux > .tb-page__titre,
.tb-page.tb-flux > .tb-fil-ariane,
.tb-page.tb-flux > .tb-page__signature {
  margin-inline: 0;
  max-width: none;
  padding-inline: 0;
}

/* BANNIÈRE AVEC IMAGE : la carte de titre est posée DANS l'image, avec un
   retrait, au lieu de coller au bord gauche. Le retrait est une MARGE DE LA
   CARTE, jamais un padding de la section : le socle (tb-blocs.css, « pleine
   largeur ») pose sur la section une marge négative compensée par un padding
   calculé — remplacer ce padding décale tout le contenu hors de l'image.
   Le titre prend la taille d'un vrai titre de page : à la taille d'un titre
   de section, la bannière pesait moins que les sections qu'elle annonce.
   / Hero card inset via the CARD's margin, never the section's padding (the
   base pairs a negative margin with a computed padding on full-bleed blocks).
   The hero title gets page-title size. */
/* Retrait des DEUX côtés : sur mobile la carte occupe toute la largeur et
   doit rester centrée dans l'image ; sur ordinateur sa largeur max (44rem)
   la laisse à gauche, le retrait droit n'y change rien.
   / Inset on BOTH sides: centred on mobile, left-placed on desktop. */
.tb-page .tb-bloc--hero-image .tb-bloc__contenu {
  margin-inline: clamp(1.25rem, 4vw, 3rem);
}

/* Frise : les récits sont des paragraphes, ils gardent une longueur de
   ligne lisible (~72 caractères) au lieu de courir sur toute la page.
   / Timeline stories keep a readable line length. */
.tb-page .tb-bloc--frise .timeline-item__text { max-width: 72ch; }
.tb-page .tb-bloc--hero .tb-bloc__titre {
  font-size: var(--text-display);
  font-weight: 800;
  line-height: 1.1;
}
.tb-page .tb-bloc--hero .tb-bloc__sous-titre {
  margin-top: var(--space-3);
  margin-bottom: 0;
  font-size: var(--text-lg);
  color: var(--color-text-muted);
}

/* CARTES D'ÉVÈNEMENT : boîte image en 16/9, le format des visuels
   d'évènement (les variations crop_* d'Event sont en 960×540). En 3/2, une
   photo 16/9 laissait deux bandes grises dessus/dessous. L'image reste
   ENTIÈRE (`contain` du socle) : une affiche en portrait garde ses bords.
   / Event cards: 16/9 image box, the event visual format. The image stays
   whole (`contain`), so a portrait poster keeps its edges. */
.tb-page .tb-bloc--carte > .tb-bloc__carte-image { aspect-ratio: 16 / 9; }
/* Écart minimal titre → bouton : le bouton est poussé en bas par
   `margin-top: auto`, qui vaut 0 quand la carte n'a pas de texte.
   / Minimum title → button gap (margin-top:auto is 0 without text). */
.tb-page .tb-bloc--carte > .tb-bloc__carte-titre { margin-bottom: var(--space-4); }

/* CARTE DE SOUS-PAGE (bloc LISTE, source SOUS_PAGES) : même dessin que
   .event-card — mêmes jetons de surface, filet, rayon, ombre et padding.
   Gabarit : pages/templates/pages/V2/partials/bloc_liste.html.
   / Sub-page card: same look and tokens as .event-card. */
.page-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-card);
}
.page-card__cover { height: 108px; overflow: hidden; }
.page-card__cover img { width: 100%; height: 100%; object-fit: cover; }
.page-card__body {
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  gap: var(--space-3);
  padding: var(--space-4);
}
.page-card__title {
  margin: 0;
  font-family: var(--font-text);
  font-size: var(--text-base);
  font-weight: 700;
  line-height: 1.3;
  text-wrap: balance;
}
.page-card__desc {
  margin: 0;
  font-size: var(--text-md);
  line-height: 1.5;
  color: var(--color-text-muted);
}
/* MÊME GOUTTIÈRE POUR LES GRILLES DE BLOCS (16 px) : équipe, ressources
   (.equipe-grille, .ressources-grille), sous-cartes et sous-pages. Avec trois
   écarts différents (16, 20, 24 px), les bords de colonnes de deux grilles
   voisines ne tombaient pas l'un sous l'autre.
   La grille d'agenda (bloc EVENEMENTS) garde l'écart de .grid (24 px) : elle
   doit rester identique à la section « Ce qui arrive » de l'accueil.
   / Same 16px gutter for block grids so neighbouring column edges line up;
   the agenda grid keeps .grid's 24px to match the hardcoded home. */
.tb-page .tb-sous-cartes,
.tb-page .grid[data-testid="bloc-liste-sous-pages"] { gap: var(--space-4); }

/* Pied poussé en bas de la carte : « Lire la suite » à la même hauteur sur
   toute la rangée, que la carte ait un résumé ou non (comme .event-card__meta).
   / Footer pushed down: links aligned across the row. */
.page-card__footer {
  margin-top: auto;
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
}

/* APPEL À L'ACTION et NEWSLETTER : encadrés comme des cartes, padding égal
   sur les quatre côtés. Le fond bleuté et la barre de gauche juraient avec le
   papier chaud du site, et le padding du socle (haut ≠ bas) décentrait le
   contenu dans son cadre.
   `.tb-page.tb-flux >` : il faut passer devant `.tb-flux > .tb-bloc--cta` du
   socle, qui le traite en pleine largeur (marge négative + padding calculé).
   Un cadre de carte reste DANS la colonne : marge à 0.
   / CTA and NEWSLETTER: framed like cards, equal padding on all four sides.
   A card frame stays INSIDE the column, so the base's full-bleed margin is reset. */
.tb-page.tb-flux > .tb-bloc--cta,
.tb-page.tb-flux > .tb-bloc--newsletter {
  margin-inline: 0;
  /* L'air entre blocs venait du padding du socle, remplacé ici par le
     padding du cadre : sans marge, deux encadrés consécutifs se touchent.
     Les marges ne fusionnent pas dans la grille : 16 + 16 = 32 px entre
     deux encadrés voisins.
     / Inter-block air came from the base padding, replaced here. Grid margins
     do not collapse: 16 + 16 = 32px between two neighbouring frames. */
  margin-block: var(--space-4);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-card);
  padding: var(--space-6);
}
.tb-page .tb-bloc--cta .tb-bloc__contenu {
  border-left: none;
  padding-left: 0;
  max-width: none;
}
.tb-page .tb-bloc--cta .tb-bloc__titre { font-size: var(--text-xl); }
.tb-page .tb-bloc--cta .tb-bloc__sous-titre,
.tb-page .tb-bloc--newsletter .tb-bloc__sous-titre {
  margin: var(--space-2) 0 0;
  font-size: var(--text-base);
  color: var(--color-text-muted);
}
.tb-page .tb-bloc--cta .tb-bloc__texte { margin-top: var(--space-2); }
.tb-page .tb-bloc--cta .tb-bloc__boutons { margin-top: var(--space-5); }
.tb-page .tb-bloc--cta .tb-bloc__contenu > :last-child,
.tb-page .tb-bloc--newsletter .tb-bloc__contenu > :last-child { margin-bottom: 0; }
/* Le formulaire Ghost en variante minimale (champ + bouton) : on retire la
   hauteur réservée par le socle pour sa version plein cadre (360 px).
   / Minimal Ghost form: drop the 360px reserved for its full-frame variant. */
.tb-page .tb-bloc--newsletter .tb-newsletter {
  min-height: 0;
  max-width: 30rem;
  margin-top: var(--space-5);
}

/* =================================================================
   MOBILE ÉTROIT — corrections de l'audit responsive (2026-09-25)
   / Narrow mobile — fixes from the responsive audit

   LOCALISATION : pages/static/V2/css/V2.css (fin du fichier)

   Rappel : html et body sont en `overflow-x: clip`. Un élément trop
   large ne crée donc JAMAIS de barre de défilement : il est coupé, en
   silence. Ces règles empêchent les éléments de dépasser l'écran
   entre 320px et 414px.
   / Reminder: html/body clip overflow, so anything too wide is silently
   cut. These rules keep elements inside the screen from 320px to 414px.

   Point de rupture : 30rem (480px), le même que .place-shortcuts.
   ================================================================= */

/* --- Textes longs saisis par le lieu ou la personne ---------------
   Un nom de lieu, une adresse web ou un e-mail n'ont pas d'espace où
   couper : ils poussaient la carte hors de l'écran. On autorise la
   coupure n'importe où, et on laisse les enfants flex rétrécir.
   / Long venue/user strings (names, URLs, e-mails) may break anywhere. */
.place-id { min-width: 0; }
.place-id > span:not(.place-id__mark) { min-width: 0; }
.me-id { min-width: 0; max-width: 100%; }
.place-id__name,
.place-id__tagline,
.venue-fact__value,
.event-card__meta,
.compte-table th,
.compte-table td {
  overflow-wrap: anywhere;
}

/* --- Double gouttière ----------------------------------------------
   Le <main> du shell porte déjà .container. Quelques gabarits partagés
   (panier, 404, 500, paiement par QR code) posent une deuxième
   .container dedans : la marge latérale était comptée deux fois (32px
   de chaque côté sur mobile, 64px sur ordinateur). La .container
   intérieure ne rajoute plus rien.
   / Nested .container inside main.container no longer doubles the gutter. */
main.container .container { padding-inline: 0; max-width: none; }

@media (max-width: 30rem) {
  /* --- Barre du haut ---------------------------------------------
     À 320px, « Mon espace » + 4 boutons d'icônes de 44px + 32px de
     marge de chaque côté ne tenaient pas : le bouton panier était
     coupé. On réduit les marges, et le libellé de l'ancre peut se
     raccourcir avec des points de suspension (l'avatar reste).
     / Top bar: smaller gutters; the anchor label may ellipsize. */
  .user-bar__inner { padding-inline: var(--space-4); gap: var(--space-3); }

  /* --- Pied de page : marges du bureau (64px / 32px) réduites --------
     / Footer: desktop paddings reduced on narrow screens. */
  .site-footer__inner { padding: var(--space-7) var(--space-4); }
  .site-footer__legal-inner { padding-inline: var(--space-4); }
  .icon-group { gap: var(--space-1); }
  .me-anchor { min-width: 0; }
  .me-anchor > span:last-child {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* --- Boutons : retour à la ligne permis sur écran étroit ----------
     .btn est en `nowrap` pour tout le skin (Bootstrap, lui, laisse passer
     à la ligne). Sur un écran étroit, les libellés longs étaient COUPÉS :
     tunnels de réservation et d'adhésion (« Connectez-vous pour ajouter
     au panier », icône sortie à gauche), page Contribuer (« Voir plus de
     détail et les actions en cours », coupé des deux côtés). Un libellé
     sur deux lignes vaut mieux qu'un libellé tronqué. Les libellés courts
     tiennent sur une ligne et ne changent pas.
     / Narrow screens: buttons may wrap; truncation was worse. */
  .btn {
    white-space: normal;
    line-height: 1.25;
    text-align: center;
    max-width: 100%;
  }

  /* Sélecteur de quantité du tunnel de réservation (commun/formulaires/
     reservation.html) : le composant <bs-counter> ne rétrécissait pas, et
     le mot « billet(s) » à sa droite était coupé à 320px.
     / The quantity stepper must be allowed to shrink so "ticket(s)" fits. */
  bs-counter { min-width: 0; }
}

/* --- Très petit écran (< 360px) : l'ancre « Connexion / Mon espace » -
   Mesuré à 320px : il manquait 11px pour « Connexion » et 22px pour
   « Mon espace » à côté des boutons d'icônes de 44px. On resserre
   l'ancre (écarts, marge droite, police 14px) pour que le libellé
   tienne en entier. Les points de suspension restent le filet de
   sécurité pour une traduction plus longue.
   / Under 360px: tighten the anchor so its label fits in full. */
@media (max-width: 22.5rem) {
  .user-bar__inner { gap: var(--space-2); }
  .icon-group { gap: 2px; }
  .me-anchor {
    gap: var(--space-2);
    padding-inline-end: var(--space-3);
    font-size: var(--text-md);
  }
}

/* --- Cibles tactiles : 44px minimum sur écran tactile ---------------
   Même règle que les boutons d'icônes de la barre du haut (WCAG 2.5.5).
   L'audit mesurait 24 à 34px sur le menu du lieu, le pied de page, les
   filtres, les petits boutons et la croix des panneaux. À la souris,
   rien ne change : la densité du bureau est gardée.
   / Touch screens: 44px minimum targets; mouse layout unchanged. */
@media (pointer: coarse) {
  .place-nav > a,
  .place-nav__dropdown > .dropdown-toggle,
  .footer-col a,
  .footer-col__bouton,
  .tag-pill,
  .btn,
  .dropdown-item {
    min-height: 44px;
  }
  .place-nav > a,
  .place-nav__dropdown > .dropdown-toggle {
    display: inline-flex;
    align-items: center;
  }
  .dropdown-item { display: flex; align-items: center; }
  /* .btn-close de Bootstrap est en content-box (1em + marge interne) :
     on agrandit la marge interne pour atteindre 44px au total.
     / Bootstrap's close button is content-box: grow its padding to 44px. */
  .btn-close { padding: calc((44px - 1em) / 2); }
}
