@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 600 700;
  font-display: swap;
  src: local('Poppins');
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: local('Inter');
}

* { box-sizing: border-box; }
/* Garantit que l'attribut natif "hidden" masque toujours l'élément, même si un
   composant (ex. .icon-picker-overlay) définit son propre display:flex/block
   avec la même spécificité — sans ce garde-fou, ce dernier gagne la cascade
   et l'élément reste visible malgré hidden=true. Toggle visibility via
   el.hidden, jamais via style.display, dans tout le JS du site. */
[hidden] { display: none !important; }
/* overflow-x ici (pas sur body) : posé sur html, l'élément qui gère déjà le
   scroll normal de la page, ça bloque juste la barre horizontale parasite
   des variantes d'apparition (droite/gauche) qui glissent depuis hors-cadre.
   Sur body avec une hauteur fixée (height:100% ou similaire), un overflow-x
   non-visible transforme body en SON PROPRE conteneur de scroll interne et
   casse le scroll de page normal (window.scrollY reste bloqué à 0) : bug
   vécu, à ne pas réintroduire — voir aussi pourquoi body n'a plus de
   height:100% du tout, juste en dessous. */
html { scroll-behavior: smooth; overflow-x: hidden; }
/* Ancre le footer en bas de l'écran sur les pages courtes (peu de contenu) :
   body en colonne flex, min-height:100vh (voir plus bas) pousse le footer
   vers le bas ; sur une page longue, le contenu déborde normalement et le
   footer suit en fin de page.
   PAS de "height:100%" ici (piège vécu) : contrairement à min-height, une
   hauteur FIXE plafonne la boîte de body à tout juste un écran de haut, même
   quand le contenu réel est plus long — le contenu déborde alors visuellement
   (overflow:visible), mais la boîte de body, elle, reste courte. Or c'est
   cette boîte qui sert de repère à .site-header (position:sticky, plus bas)
   pour savoir jusqu'où rester accroché : dès qu'on défile plus bas qu'un
   écran, la boîte de body est entièrement sortie de l'écran, le sticky n'a
   plus rien à quoi s'accrocher, et le header disparaît au lieu de rester en
   haut. */
body {
  margin: 0;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  font-family: var(--font-body);
  color: var(--color-text);
  background: var(--color-bg);
  line-height: 1.6;
}
.site-header, .site-footer { flex-shrink: 0; }
main { flex: 1 0 auto; }
img { max-width: 100%; display: block; }
a { color: var(--color-accent); }
h1, h2, h3, h4 {
  font-family: var(--font-heading);
  color: var(--color-primary);
  line-height: 1.25;
  margin: 0 0 0.6em;
}
h1 { font-size: clamp(1.8rem, 3vw, 2.6rem); }
h2 { font-size: clamp(1.4rem, 2.4vw, 2rem); }
p { margin: 0 0 1em; }

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

/* --- Header / nav --- */
.site-header {
  border-bottom: 1px solid var(--color-border);
  background: var(--color-bg);
  position: sticky;
  top: 0;
  z-index: 50;
}
.site-header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-top: 12px;
  padding-bottom: 12px;
}
.site-logo {
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
}
.site-logo img { height: 44px; width: auto; }
.site-logo-text {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1.15rem;
  color: var(--color-primary);
  line-height: 1.1;
}
.main-nav ul {
  list-style: none;
  display: flex;
  gap: 6px;
  margin: 0;
  padding: 0;
  flex-wrap: wrap;
}
.main-nav a {
  display: inline-block;
  padding: 8px 14px;
  border-radius: var(--radius-sm);
  color: var(--color-primary);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.95rem;
}
.main-nav a:hover,
.main-nav a[aria-current="page"] {
  background: var(--color-bg-alt);
  color: var(--color-secondary);
}
.nav-cta {
  background: var(--color-secondary) !important;
  color: #fff !important;
}
.nav-cta:hover { background: var(--color-primary) !important; color: #fff !important; }

.nav-toggle {
  display: none;
  background: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  font-size: 1.1rem;
  cursor: pointer;
}

/* --- Hero --- */
.hero {
  position: relative;
  overflow: hidden;
  background: linear-gradient(135deg, rgba(11,79,108,0.05), rgba(20,184,166,0.08));
  padding: 64px 0 96px;
  text-align: center;
}
/* Volant de badminton en filigrane — photo détourée (fond noir d'origine retiré
   via mix-blend-mode:screen), recadrée sur l'éventail de plumes (plus large,
   sert de support au texte qui s'affiche par-dessus). */
.hero::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: -20px;
  background: url('../img/shuttlecock-feathers.jpg') no-repeat center / cover;
  /* Photo détourée sur fond noir, à afficher sur un hero clair : on inverse les
     couleurs (le noir devient blanc) puis on fusionne en "multiply" — le blanc
     ne laisse aucune trace et le noir d'origine (désormais clair) disparaît,
     ne laissant que la texture des plumes en gris doux. */
  filter: invert(1);
  mix-blend-mode: multiply;
  opacity: 0.22;
  pointer-events: none;
  z-index: 1;
}
.hero > .container { position: relative; z-index: 2; }
/* Découpe diagonale entre le hero et la section suivante, pour casser l'effet "bloc" */
.hero-divider {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 64px;
  background: var(--color-bg);
  clip-path: polygon(0 100%, 100% 40%, 100% 100%);
  z-index: 1;
}
.hero .eyebrow {
  display: inline-block;
  color: var(--color-secondary);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 0.8rem;
  margin-bottom: 10px;
}
.hero .lead {
  max-width: 640px;
  margin: 0 auto 28px;
  color: var(--color-text-light);
  font-size: 1.1rem;
}
.hero-actions { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }

/* --- Buttons --- */
.btn {
  display: inline-block;
  padding: 12px 24px;
  border-radius: var(--radius-md);
  font-weight: 600;
  text-decoration: none;
  border: 2px solid transparent;
  cursor: pointer;
  font-size: 0.98rem;
  font-family: var(--font-body);
  transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease, color 0.2s ease;
}
.btn:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.btn:active { transform: translateY(0); box-shadow: none; }
.btn-primary { background: var(--color-secondary); color: #fff; }
.btn-primary:hover { background: var(--color-primary); }
.btn-outline { border-color: var(--color-primary); color: var(--color-primary); background: transparent; }
.btn-outline:hover { background: var(--color-primary); color: #fff; }
.btn-sm { padding: 8px 16px; font-size: 0.88rem; }
.btn-danger { background: var(--color-danger); color: #fff; }

/* --- Sections --- */
.section { padding: 56px 0; }
.section-alt { background: var(--color-bg-alt); }
.section-title { text-align: center; margin-bottom: 40px; }
.section-title p { color: var(--color-text-light); max-width: 620px; margin: 8px auto 0; }
/* Barre d'accent colorée sous chaque titre de section — touche de couleur
   répétée à chaque section plutôt qu'un simple texte plat. */
.section-title h1::after,
.section-title h2::after {
  content: "";
  display: block;
  width: 64px;
  height: 5px;
  margin: 14px auto 0;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--color-accent), var(--color-secondary), var(--color-tertiary));
  background-size: 200% 100%;
}

/* Titres en dégradé bleu/vert — accroche visuelle plus vive que le bleu plat */
.gradient-heading {
  background: linear-gradient(100deg, var(--color-primary) 10%, var(--color-accent) 45%, var(--color-secondary) 85%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Décor "confettis" (carrés colorés, clin d'œil au maillot) : appliqué
   directement sur .card et .tarif-card (voir plus bas), pas sur les sections. */

/* Flexbox plutôt que CSS Grid : avec un nombre variable d'éléments (tarifs,
   réductions...), une grille en auto-fit laisse un dernier élément isolé,
   étriqué et collé à gauche quand le compte ne remplit pas la dernière ligne.
   En flex + wrap + justify-content:center, la dernière ligne se centre et
   chaque carte s'élargit proportionnellement à l'espace réellement dispo. */
.grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 24px;
}
.grid-3 > * { flex: 1 1 240px; max-width: 360px; }
.grid-2 > * { flex: 1 1 300px; max-width: 460px; }

.card {
  position: relative;
  overflow: hidden;
  background: #fff;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 24px;
  box-shadow: var(--shadow-sm);
  transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
}
.card:hover { transform: translateY(-5px); box-shadow: var(--shadow-md); border-color: var(--color-secondary); }
.card h3 { margin-top: 0; }
/* Confettis en filigrane directement sur la carte (et non plus seulement en
   fond de section) — clippés dans ses coins arrondis grâce à overflow:hidden. */
.card::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url('../img/confetti-squares.svg') repeat;
  background-size: 200px 200px;
  opacity: 0.22;
  pointer-events: none;
  z-index: 0;
}
/* Le contenu de la carte doit rester au-dessus du décor */
.card > * { position: relative; z-index: 1; }

/* Badges d'icônes colorés (section "Pourquoi nous rejoindre ?") */
.feature-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  font-size: 1.6rem;
  margin-bottom: 14px;
  color: #fff;
  box-shadow: var(--shadow-sm);
}
.feature-icon-1 { background: linear-gradient(135deg, var(--color-primary), var(--color-accent)); }
.feature-icon-2 { background: linear-gradient(135deg, var(--color-secondary), var(--color-accent)); }
.feature-icon-3 { background: linear-gradient(135deg, var(--color-tertiary), #E0783A); }
.feature-icon { transition: transform 0.3s ease; }
.card:hover .feature-icon { transform: rotate(-8deg) scale(1.08); }

/* Fond légèrement teinté par carte, assorti au badge d'icône — un peu de
   couleur en plus qu'un simple fond blanc uniforme. */
.feature-card-1 { background: linear-gradient(160deg, rgba(11,79,108,0.06), #fff 55%); }
.feature-card-2 { background: linear-gradient(160deg, rgba(20,184,166,0.08), #fff 55%); }
.feature-card-3 { background: linear-gradient(160deg, rgba(242,165,65,0.10), #fff 55%); }

/* --- Horaires --- */
.table-wrap { overflow-x: auto; }
.horaires-table {
  width: 100%;
  border-collapse: collapse;
  /* Confettis en filigrane en fond de table (les <td> n'ont pas de fond
     opaque, donc ça transparaît dans les lignes ; le <th> garde son fond
     --color-bg-alt, non affecté). Un fond de table ne peut pas utiliser
     l'astuce ::before à opacité réduite des .card/.tarif-card : on obtient
     la même atténuation avec un voile blanc translucide superposé au motif. */
  background:
    linear-gradient(rgba(255,255,255,0.8), rgba(255,255,255,0.8)),
    url('../img/confetti-squares.svg') repeat,
    #fff;
  background-size: auto, 200px 200px, auto;
}
.horaires-table th, .horaires-table td {
  padding: 14px 20px;
  text-align: left;
  border-bottom: 1px solid var(--color-border);
}
.horaires-table th:first-child, .horaires-table td:first-child { padding-left: 26px; }
.horaires-table th:last-child, .horaires-table td:last-child { padding-right: 26px; }
.horaires-table th {
  font-family: var(--font-heading);
  color: var(--color-primary);
  background: var(--color-bg-alt);
}
.badge {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 999px;
  background: rgba(20,184,166,0.12);
  color: var(--color-secondary);
  font-size: 0.78rem;
  font-weight: 700;
}

/* --- Tarifs --- */
.tarif-card {
  border-radius: var(--radius-lg);
  border: 2px solid var(--color-border);
  border-top: 6px solid var(--color-accent);
  padding: 28px;
  background: #fff;
  position: relative;
  overflow: hidden;
  transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
}
.tarif-card:hover { transform: translateY(-5px); box-shadow: var(--shadow-md); }
.tarif-card::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url('../img/confetti-squares.svg') repeat;
  background-size: 200px 200px;
  opacity: 0.22;
  pointer-events: none;
  z-index: 0;
}
.tarif-card > * { position: relative; z-index: 1; }
.tarif-card .prix {
  font-family: var(--font-heading);
  font-size: 2.2rem;
  color: var(--color-primary);
  margin: 12px 0;
}
.tarif-card.tarif-jeune_primo { border-top-color: var(--color-accent); }
.tarif-card.tarif-adulte_primo { border-top-color: var(--color-secondary); }
.tarif-card.reduction { border-color: var(--color-tertiary); border-top-color: var(--color-tertiary); }
.tarif-card .justif {
  font-size: 0.82rem;
  color: var(--color-warning);
  font-weight: 600;
}
/* padding horizontal explicite (et pas juste "0") : sinon, à spécificité égale
   avec .card { padding: 24px }, cette règle l'emporte à la cascade (déclarée
   après) et annule complètement le padding de la carte — texte collé aux bords. */
.options-list { list-style: none; padding: 8px 24px; margin: 0; }
.options-list li {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--color-border);
}
.options-list li:last-child { border-bottom: none; }
.options-list .prix { font-weight: 700; color: var(--color-primary); white-space: nowrap; }
.places-limitees { color: var(--color-danger); font-size: 0.8rem; font-weight: 600; }

/* --- Bureau (club) --- */
/* padding horizontal explicite (et pas juste "0") : sinon, à spécificité égale
   avec .card { padding: 24px }, cette règle l'emporte à la cascade (déclarée
   après) et annule complètement le padding de la carte — texte collé aux bords. */
.bureau-list { list-style: none; padding: 8px 24px; margin: 0; }
.bureau-list li {
  padding: 10px 0;
  border-bottom: 1px solid var(--color-border);
  display: flex;
  justify-content: space-between;
  gap: 12px;
}
.bureau-list li:last-child { border-bottom: none; }
.bureau-role { color: var(--color-text-light); }

/* --- Galerie --- */
.galerie-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 16px;
}
.galerie-grid figure { margin: 0; border-radius: var(--radius-md); overflow: hidden; box-shadow: var(--shadow-sm); }
.galerie-grid img { width: 100%; height: 220px; object-fit: cover; }
.galerie-grid figcaption { padding: 8px 12px; font-size: 0.85rem; color: var(--color-text-light); background: #fff; }
.galerie-empty { text-align: center; color: var(--color-text-light); padding: 40px 0; }

/* --- Partenaires --- */
/* Sponsors : mis en avant, description toujours visible.
   Partenaires : plus discrets, description révélée au survol/focus. */
.partenaire-card { text-align: center; }
.sponsor-card {
  border-color: rgba(20, 184, 166, 0.45);
  background: linear-gradient(180deg, rgba(20, 184, 166, 0.06), transparent 60%);
}
.sponsor-badge {
  display: inline-block; font-size: 0.7rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--color-secondary); margin-bottom: 8px;
}
.partenaire-logo { max-height: 70px; width: auto; margin: 0 auto 16px; object-fit: contain; }
.sponsor-card .partenaire-logo { max-height: 90px; }
/* Révélation au survol, contenue DANS la carte (pas de position:absolute
   qui sortirait du cadre — important pour les chips du bandeau défilant,
   dont le conteneur masque tout débordement horizontal). */
.partenaire-card:not(.sponsor-card) .partenaire-desc {
  max-height: 0; overflow: hidden; opacity: 0; margin: 0;
  transition: max-height 0.25s ease, opacity 0.2s ease, margin-top 0.25s ease;
}
.partenaire-card:not(.sponsor-card):hover .partenaire-desc,
.partenaire-card:not(.sponsor-card):focus-within .partenaire-desc {
  max-height: 160px; opacity: 1; margin-top: 8px;
}
/* Piste de défilement infini — un seul jeu de logos + sa copie décorative
   dupliquée (voir index.php) ; on translate exactement de -50% (= la largeur
   d'un jeu) pour boucler sans coupure visible. */
.partenaires-marquee {
  overflow: hidden;
  width: 100%;
  margin-bottom: 8px;
  /* estompe les deux bords plutôt qu'une coupure nette */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.partenaires-track {
  display: flex;
  align-items: center;
  gap: 24px;
  width: max-content;
  animation: partenaires-defilement 28s linear infinite;
}
.partenaires-marquee:hover .partenaires-track { animation-play-state: paused; }
@keyframes partenaires-defilement {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .partenaires-track { animation: none; }
}
.partenaire-chip {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 160px;
  flex-shrink: 0;
  padding: 16px 14px;
  background: #fff;
  border: 1px solid rgba(20, 184, 166, 0.35);
  border-radius: var(--radius-md);
  text-decoration: none;
  color: var(--color-primary);
  font-weight: 600;
  text-align: center;
  transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
}
.partenaire-chip:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
  border-color: var(--color-secondary);
}
.partenaire-chip img {
  max-height: 46px;
  max-width: 130px;
  object-fit: contain;
}
.partenaire-chip-nom { font-size: 0.85rem; line-height: 1.25; }
/* Description révélée au survol/focus, contenue DANS le chip (le bandeau
   défilant masque tout débordement horizontal — un tooltip en position
   absolute serait invisible, coupé par ce overflow) : le chip grandit un
   peu à la place, sans déplacer ses voisins (flex-shrink:0 + largeur fixe). */
.partenaire-chip-desc {
  max-height: 0; overflow: hidden; opacity: 0; width: 100%; margin: 0;
  font-size: 0.76rem; font-weight: 400; line-height: 1.35; color: var(--color-text-light);
  transition: max-height 0.25s ease, opacity 0.2s ease, margin-top 0.25s ease;
}
.partenaire-chip:hover .partenaire-chip-desc,
.partenaire-chip:focus-visible .partenaire-chip-desc {
  max-height: 140px; opacity: 1; margin-top: 2px;
}

/* --- Contact --- */
.contact-info { list-style: none; padding: 0; margin: 0; }
.contact-info li { padding: 8px 0; }
.contact-info a { color: var(--color-primary); font-weight: 600; text-decoration: none; }
.contact-info a:hover { color: var(--color-secondary); }

.social-link { display: inline-flex; align-items: center; gap: 10px; }
.social-link svg { flex-shrink: 0; }

/* --- Footer --- */
.site-footer {
  /* Dégradé auparavant utilisé par le bandeau "Prêt(e) à nous rejoindre ?"
     (devenu une section-alt classique) — repris tel quel ici. */
  background: linear-gradient(120deg, var(--color-primary), var(--color-secondary));
  color: rgba(255,255,255,0.92);
  padding: 40px 0 24px;
  margin-top: 48px;
}
.site-footer a { color: #fff; }

/* --- Échange de badminton animé, sur la ligne du haut du footer ---
   Silhouettes en SVG inline (pas d'astuce réseau nécessaire pour la "lazy
   load" : c'est déjà là avec la page). L'activation est différée en JS
   (IntersectionObserver, classe .rally-active) tant que le footer n'est pas
   à l'écran — et jamais activée si l'utilisateur préfère réduire les
   animations : la scène reste alors figée sur une pose de repos, jamais
   invisible.
   Positionné en absolu, calé sur le BAS de son propre cadre = la ligne du
   haut du footer (bottom:100%) : les joueurs ont donc les pieds pile sur
   cette ligne, le haut du corps/la raquette levée débordant légèrement
   au-dessus, comme s'ils jouaient sur le rebord du footer. */
.site-footer { position: relative; }
.footer-rally {
  position: absolute;
  left: 0; right: 0;
  bottom: 100%;
  height: 92px;
  overflow: visible;
  pointer-events: none;
}
.footer-rally svg { width: 100%; height: 100%; display: block; overflow: visible; }

/* Bras raquette en DEUX pivots qui bougent ENSEMBLE (épaule + coude), pas
   juste le poignet : quand la raquette descend, le bras entier descend
   avec elle — voir footer.php. Le coude porte le mouvement principal
   (rally-hit-*), l'épaule l'accompagne avec une amplitude plus faible
   (rally-shoulder-*), même pourcentage de temps. */
.rally-forearm.rally-arm-a, .rally-forearm.rally-arm-b { transform: rotate(10deg); transform-origin: 10px 50px; }
.rally-upperarm.rally-arm-a, .rally-upperarm.rally-arm-b { transform: rotate(40deg); transform-origin: 7px 59px; }

/* Déplacement des joueurs entre le centre de leur moitié de terrain (position
   de repli après chaque frappe) et le filet (translation locale : voir
   footer.php, ce groupe est imbriqué DANS le <g> qui porte déjà la position
   de base + l'éventuel miroir, donc une seule et même valeur positive
   rapproche toujours chaque joueur du filet, des deux côtés). */
.rally-move-a, .rally-move-b { transform: translateX(0); }
.rally-active .rally-move-a { animation: rally-move-a-anim 13s linear infinite paused; animation-play-state: running; }
.rally-active .rally-move-b { animation: rally-move-b-anim 13s linear infinite paused; animation-play-state: running; }
/* Un point sur deux, le joueur de gauche (A) gagne plutôt que le joueur de
   droite (B) : main.js bascule cette classe sur .footer-rally à chaque fin
   de boucle (événement "animationiteration") — un point normal, un point
   "échangé" où A et B jouent la chorégraphie de l'autre (et le volant suit
   le tracé miroir ci-dessous), en alternance infinie. */
.rally-active.rally-swap .rally-move-a { animation-name: rally-move-b-anim; }
.rally-active.rally-swap .rally-move-b { animation-name: rally-move-a-anim; }

/* Volant unique (pas de traînée) : liège en tête, plumes à la traîne (voir
   footer.php) — offset-rotate auto aligne l'axe du dessin sur la tangente du
   trajet, donc il s'oriente tout seul dans le sens du déplacement et se
   retourne naturellement à chaque frappe (le trajet change de sens à cet
   instant précis). Chaque coup a son propre easing "ease-out" posé sur le
   point d'ARRIVÉE : rapide juste après la frappe, il ralentit ensuite tout du
   long jusqu'à l'autre joueur, comme un vrai volant freiné par l'air. */
.rally-shuttle {
  /* Points d'arrivée = position RÉELLE du TAMIS de la raquette au moment
     exact de la frappe, mesurée directement dans le navigateur, avec les
     VRAIS repères du corps (tête y≈33-50, épaule y≈57, hanche y≈95, genou
     y≈110) : un "amorti/contre-amorti" se joue à hauteur de hanche
     (y≈103), un service à hauteur de côtes (y≈90), un coup "haut" bien
     au-dessus de la tête (y≈15) — voir docs/JOURNAL.md pour le détail des
     6 mesures. Chaque arc est vérifié numériquement pour bien passer
     au-dessus du filet (y<56) à x=600. */
  offset-path: path("M294,90 C500,-30 850,-35 1064,15 C850,-45 300,-45 136,15 C400,-5 620,35 759,103 C650,20 550,20 441,103 C600,20 850,-30 1064,15 C900,20 500,60 255,95 C150,110 60,125 20,132");
  offset-rotate: auto;
  offset-distance: 0%;
  animation: rally-shuttle-move 13s linear infinite paused;
}
/* Tracé miroir (mêmes hauteurs de frappe, x symétrique par rapport au filet
   à x=600) pour le point "échangé" — voir .rally-swap ci-dessus. */
.rally-active.rally-swap .rally-shuttle {
  offset-path: path("M906,90 C700,-30 350,-35 136,15 C350,-45 900,-45 1064,15 C800,-5 580,35 441,103 C550,20 650,20 759,103 C600,20 350,-30 136,15 C300,20 700,60 945,95 C1050,110 1140,125 1180,132");
}

.rally-active .rally-shuttle { animation-play-state: running; }
/* "running" directement dans le raccourci (pas de "paused" + override
   séparé) : un ancien override .rally-active .rally-arm-a{ animation-
   play-state:running } (2 classes) ne suffisait plus à battre CES règles-ci
   (3 classes : rally-active + rally-forearm/upperarm + rally-arm-a/b) une
   fois le rig séparé en coude/épaule — la spécificité plus forte du
   raccourci imposait son "paused" implicite, et le bras ne bougeait plus
   du tout malgré .rally-active. */
.rally-active .rally-forearm.rally-arm-a { animation: rally-hit-a 13s linear infinite running; }
.rally-active .rally-forearm.rally-arm-b { animation: rally-hit-b 13s linear infinite running; }
.rally-active .rally-upperarm.rally-arm-a { animation: rally-shoulder-a 13s linear infinite running; }
.rally-active .rally-upperarm.rally-arm-b { animation: rally-shoulder-b 13s linear infinite running; }
.rally-active.rally-swap .rally-forearm.rally-arm-a { animation-name: rally-hit-b; }
.rally-active.rally-swap .rally-forearm.rally-arm-b { animation-name: rally-hit-a; }
.rally-active.rally-swap .rally-upperarm.rally-arm-a { animation-name: rally-shoulder-b; }
.rally-active.rally-swap .rally-upperarm.rally-arm-b { animation-name: rally-shoulder-a; }

/* Service long, dégagement, amorti, contre-amorti, dégagement, smash, temps
   mort avant le prochain service — puis on boucle. Le tracé du volant étant
   symétrique par rapport au joueur qui le frappe, ce sont les lignes
   ci-dessus (quel joueur joue quelle chorégraphie) qui décident qui sert et
   qui termine sur le smash — pas ces libellés, qui décrivent juste la
   nature du coup à chaque instant du trajet. */
/* Pourcentages recalculés à partir de la LONGUEUR D'ARC RÉELLE du tracé
   (getTotalLength()/getPointAtLength(), pas une estimation) : offset-distance
   est une fraction de la longueur du chemin, PAS de l'ordre des 6 courbes —
   après plusieurs retouches du tracé, les anciens pourcentages "ronds"
   (18%/34%/53%…) ne tombaient plus du tout sur les points d'arrivée réels,
   ce qui décalait le volant par rapport au tamis de la raquette. */
@keyframes rally-shuttle-move {
  /* NB : le timing-function posé SUR un palier régit le trajet QUI SUIT ce
     palier (jusqu'au suivant), pas celui qui y mène. Il doit donc être posé
     au DÉBUT de chaque vol (juste après l'arrêt précédent), pas juste avant
     l'arrivée — sinon il retombe sur le petit palier immobile (même valeur
     des deux côtés) et n'a plus aucun effet : le vol réel restait alors en
     linéaire pur du début à la fin, d'où l'impression de vitesse constante
     qui ne ralentit jamais avant la frappe. */
  /* Le chemin (offset-path ci-dessus) a été rallongé d'un dernier tronçon
     qui continue jusqu'au sol après le smash, au lieu de s'arrêter
     pile sur le joueur battu — donc le total de la trajectoire est plus
     long qu'avant, et TOUTES les valeurs offset-distance ci-dessous ont été
     recalculées (mesure réelle getTotalLength(), pas une estimation) pour
     que chaque volant coïncide toujours exactement avec le tamis de la
     raquette au même pourcentage de TEMPS qu'avant (18.95/41.43/56.71/
     65.07/80.46 %) : seule la fraction de longueur d'arc correspondant à
     ces points a changé (ratio ancien/nouveau ≈0.946), pas le calage
     temporel des frappes. */
  0%      { offset-distance: 0%; animation-timing-function: ease-out; }  /* service long : ralentit en approchant B */
  16.95%  { offset-distance: 17.93%; }   /* B réceptionne le service, en reculant */
  18.95%  { offset-distance: 17.93%; animation-timing-function: ease-out; }
  39.43%  { offset-distance: 39.19%; }   /* A réceptionne, en reculant */
  41.43%  { offset-distance: 39.19%; animation-timing-function: ease-out; }
  54.71%  { offset-distance: 53.64%; }  /* B réceptionne au filet, hanche */
  56.71%  { offset-distance: 53.64%; animation-timing-function: ease-out; }
  63.07%  { offset-distance: 61.55%; }  /* A réceptionne au filet, hanche */
  65.07%  { offset-distance: 61.55%; animation-timing-function: ease-out; }
  78.46%  { offset-distance: 76.11%; }  /* B réceptionne en reculant */
  80.46%  { offset-distance: 76.11%; }  /* PAS de ease-out, PAS de palier intermédiaire : un seul segment linéaire
                                            jusqu'au sol, vitesse constante — un smash ne ralentit pas en passant
                                            devant l'adversaire (un palier à 92% donnait involontairement une 2e
                                            vitesse, plus lente, sur la fin du vol) */
  100%    { offset-distance: 100%; }    /* touche le sol ; temps mort avant le prochain service */
}
/* Déplacements sur TROIS zones (pas juste centre/filet) : centre (0),
   filet (+175px, coups à hauteur de hanche), fond de court (-110px, coups
   hauts qui obligent à reculer) — d'où les vrais pas avant/arrière demandés,
   pas seulement une avancée pour les coups au filet. */
@keyframes rally-move-a-anim {
  /* Anticipation élargie : le joueur se remet en mouvement dès la fin de
     l'échange précédent, avec une vraie transition pour le pas. ET, règle
     de base au badminton : après CHAQUE frappe, on ne reste pas planté dans
     le fond ou au filet — on repart vers le milieu (position médiane) avant
     de repartir vers la zone de la frappe suivante. D'où, entre deux
     frappes qui ne sont ni l'une ni l'autre au centre, un aller-retour par
     le milieu au lieu d'un saut direct fond↔filet. */
  0%, 24%      { transform: translateX(0); }       /* centre : sert, puis attend */
  32%          { transform: translateX(-110px); }  /* recule tôt pour prendre le dégagement... */
  41.43%       { transform: translateX(-110px); }  /* ...et frapper l'amorti, en fond de court */
  43.3%        { transform: translateX(-110px); }
  49.7%, 52.1% { transform: translateX(0); }        /* replacement au milieu après l'amorti */
  59.2%        { transform: translateX(175px); }    /* puis avance au filet */
  65.07%       { transform: translateX(175px); }    /* réceptionne le contre-amorti + relance, à hauteur de hanche */
  67.9%        { transform: translateX(175px); }
  77.3%, 100%  { transform: translateX(0); }         /* replacement au milieu (= position de service suivant) */
}
@keyframes rally-move-b-anim {
  0%, 2%       { transform: translateX(0); }        /* centre : attend le service */
  9.45%        { transform: translateX(-110px); }   /* recule tôt pour prendre le service long... */
  18.95%       { transform: translateX(-110px); }   /* ...et dégager, en fond de court */
  22%          { transform: translateX(-110px); }
  32.2%, 35.9% { transform: translateX(0); }        /* replacement au milieu après le dégagement */
  47.3%        { transform: translateX(175px); }     /* puis avance au filet */
  56.71%       { transform: translateX(175px); }     /* réceptionne l'amorti + contre-amorti, à hauteur de hanche */
  58.6%        { transform: translateX(175px); }
  65%, 67.4%   { transform: translateX(0); }         /* replacement au milieu après le contre-amorti */
  74.5%        { transform: translateX(-110px); }    /* puis recule pour le smash, en fond de court */
  80.46%       { transform: translateX(-110px); }    /* smash */
  82%          { transform: translateX(-110px); }
  87.3%, 100%  { transform: translateX(0); }          /* replacement au milieu (= position d'attente du service) */
}
/* Angles calibrés EMPIRIQUEMENT sur les VRAIS repères du corps (mesure
   directe dans le navigateur, tête/épaule/hanche/genou — pas un calcul à la
   main). Coude (rally-hit-*) ET épaule (rally-shoulder-*) bougent TOUJOURS
   ensemble.
     attente     : coude 10° / épaule 40°  → tamis en y≈71, pose neutre entre
                   deux frappes, raquette levée mais pas armée ;
     service     : coude 20° / épaule 60°  → tamis en y≈90, hauteur des
                   côtes basses (règle du badminton : sous la taille) ;
     amorti/     : coude 20° / épaule 100° → tamis en y≈103, hauteur de
     contre-amorti  hanche (net et proche du sol, comme demandé) ;
     haut        : coude -95° / épaule 0°  → tamis en y≈15, bien au-dessus
                   de la tête (dégagement/smash/amorti) — grand geste,
                   anticipé (arrêt intermédiaire avant l'impact). */
/* Fenêtres d'anticipation élargies (le bras s'arme plus tôt avant chaque
   frappe, au lieu d'un geste sec à 2-4% de la frappe) — les % de FRAPPE
   eux-mêmes (41.43/65.07 pour A, 18.95/56.71/80.46 pour B) restent
   inchangés : ce sont ceux qui doivent rester synchronisés avec le volant. */
@keyframes rally-hit-a {
  0%           { transform: rotate(20deg); }  /* service : côtes basses */
  5%, 14%      { transform: rotate(10deg); }  /* attente */
  18.56%       { transform: rotate(45deg); }  /* anticipation, encore plus tôt : arme avant l'amorti (recule aussi, voir move) */
  41.43%       { transform: rotate(-95deg); } /* amorti : frappe HAUTE, overhead */
  41.73%, 42.23% { transform: rotate(10deg); } /* attente quasi nulle : les 2 frappes sont trop rapprochées pour plus */
  47.14%       { transform: rotate(-8deg); }  /* petit geste avant la frappe hanche, encore plus tôt */
  65.07%       { transform: rotate(20deg); }  /* réceptionne le contre-amorti + relance : hanche */
  70%, 75%     { transform: rotate(10deg); }  /* attente, prolongée avant le service suivant */
  80%          { transform: rotate(15deg); }  /* anticipation du service suivant, encore plus tôt */
  100%         { transform: rotate(20deg); }  /* = 0% : boucle sans à-coup */
}
@keyframes rally-hit-b {
  /* Frappe 1 (dégagement, 18.95%) : déjà au maximum de son anticipation
     possible dans cette fenêtre (0-1-2.35%) — juste après la boucle, il n'y
     a plus de marge sans rogner sur la pause de fin du smash précédent
     (85.46-100%). Laissé tel quel pour l'instant. */
  0%, 1%       { transform: rotate(10deg); }  /* attente très brève : juste après la boucle */
  2.35%        { transform: rotate(45deg); }  /* anticipation : arme avant le dégagement (recule aussi) */
  18.95%       { transform: rotate(-95deg); } /* dégagement : frappe HAUTE, overhead */
  23.95%, 32%  { transform: rotate(10deg); }  /* attente */
  36.36%       { transform: rotate(-8deg); }  /* petit geste avant le contre-amorti, encore plus tôt */
  56.71%       { transform: rotate(20deg); }  /* contre-amorti au filet : hanche */
  57%, 57.5%   { transform: rotate(10deg); }  /* attente quasi nulle : les 2 frappes sont rapprochées */
  60.07%       { transform: rotate(45deg); }  /* anticipation, encore plus tôt : arme avant le smash */
  80.46%       { transform: rotate(-95deg); } /* smash : frappe HAUTE, geste le plus puissant */
  85.46%, 100% { transform: rotate(10deg); }  /* attente */
}
@keyframes rally-shoulder-a {
  0%           { transform: rotate(60deg); }  /* service */
  5%, 14%      { transform: rotate(40deg); }  /* attente */
  18.56%       { transform: rotate(30deg); }  /* anticipation avant l'amorti, encore plus tôt */
  41.43%       { transform: rotate(0deg); }    /* amorti : HAUTE */
  41.73%, 42.23% { transform: rotate(40deg); }
  47.14%       { transform: rotate(50deg); }   /* petit geste avant la frappe hanche, encore plus tôt */
  65.07%       { transform: rotate(100deg); }  /* hanche */
  70%, 75%     { transform: rotate(40deg); }
  80%          { transform: rotate(50deg); }   /* anticipation du service suivant, encore plus tôt */
  100%         { transform: rotate(60deg); }   /* = 0% */
}
@keyframes rally-shoulder-b {
  0%, 1%       { transform: rotate(40deg); }
  2.35%        { transform: rotate(30deg); }  /* anticipation avant le dégagement */
  18.95%       { transform: rotate(0deg); }   /* dégagement : HAUTE */
  23.95%, 32%  { transform: rotate(40deg); }
  36.36%       { transform: rotate(50deg); }  /* petit geste avant le contre-amorti, encore plus tôt */
  56.71%       { transform: rotate(100deg); } /* hanche */
  57%, 57.5%   { transform: rotate(40deg); }
  60.07%       { transform: rotate(30deg); }  /* anticipation avant le smash, encore plus tôt */
  80.46%       { transform: rotate(0deg); }   /* smash : HAUTE */
  85.46%, 100% { transform: rotate(40deg); }
}

/* --- Foulée de marche pendant les déplacements vers le filet et le retour ---
   Un <g> par jambe (transform-origin sur la hanche, voir footer.php),
   rotation additionnelle par-dessus l'angle de base déjà baké sur le <rect>.
   Actif uniquement pendant les fenêtres où le joueur se déplace réellement
   (mêmes % que rally-move-*-anim) ; jambes au repos (0deg ajouté) le reste
   du temps, pendant qu'il sert/frappe/attend. Deux jambes en opposition de
   phase = une vraie alternance de pas, pas juste un "glissement". */
/* Réaligné sur les nouvelles fenêtres de déplacement (rally-move-*-anim
   ci-dessus, avancées pour anticiper) — sinon les pas se déclenchaient sur
   d'anciens repères et n'étaient plus synchronisés avec le vrai pas. */
/* Foulée quasi continue entre CHAQUE frappe (et pas seulement pendant le
   grand déplacement de zone) : le joueur reprend un petit jeu de jambes dès
   la fin d'une frappe pour anticiper le replacement fond-de-court/filet,
   au lieu de rester planté jusqu'au début du prochain déplacement. Chaque
   fenêtre [frappe→frappe suivante] a son propre trio de pas, qui s'arrête
   juste avant l'impact (pieds au sol au moment de frapper). */
@keyframes rally-legs-a-stride {
  0%, 2.5%      { transform: rotate(0deg); }
  11.7%         { transform: rotate(16deg); }
  21%           { transform: rotate(-12deg); }
  30.2%         { transform: rotate(16deg); }
  39.4%, 42.9%  { transform: rotate(0deg); }
  48.2%         { transform: rotate(16deg); }
  53.4%         { transform: rotate(-12deg); }
  58.7%         { transform: rotate(16deg); }
  63.9%, 67.2%  { transform: rotate(0deg); }
  75%           { transform: rotate(16deg); }
  82.8%         { transform: rotate(-12deg); }
  90.5%         { transform: rotate(16deg); }
  98.3%, 100%   { transform: rotate(0deg); }
}
@keyframes rally-legs-a-stride-alt {
  0%, 2.5%      { transform: rotate(0deg); }
  11.7%         { transform: rotate(-16deg); }
  21%           { transform: rotate(12deg); }
  30.2%         { transform: rotate(-16deg); }
  39.4%, 42.9%  { transform: rotate(0deg); }
  48.2%         { transform: rotate(-16deg); }
  53.4%         { transform: rotate(12deg); }
  58.7%         { transform: rotate(-16deg); }
  63.9%, 67.2%  { transform: rotate(0deg); }
  75%           { transform: rotate(-16deg); }
  82.8%         { transform: rotate(12deg); }
  90.5%         { transform: rotate(-16deg); }
  98.3%, 100%   { transform: rotate(0deg); }
}
@keyframes rally-legs-b-stride {
  0%, 1.1%      { transform: rotate(0deg); }
  5.3%          { transform: rotate(16deg); }
  9.6%          { transform: rotate(-12deg); }
  13.8%         { transform: rotate(16deg); }
  18%, 21.2%    { transform: rotate(0deg); }
  29.6%         { transform: rotate(16deg); }
  38%           { transform: rotate(-12deg); }
  46.4%         { transform: rotate(16deg); }
  54.8%, 58.1%  { transform: rotate(0deg); }
  63.4%         { transform: rotate(16deg); }
  68.7%         { transform: rotate(-12deg); }
  74%           { transform: rotate(16deg); }
  79.3%, 81.6%  { transform: rotate(0deg); }
  86%           { transform: rotate(16deg); }
  90.3%         { transform: rotate(-12deg); }
  94.7%         { transform: rotate(16deg); }
  99%, 100%     { transform: rotate(0deg); }
}
@keyframes rally-legs-b-stride-alt {
  0%, 1.1%      { transform: rotate(0deg); }
  5.3%          { transform: rotate(-16deg); }
  9.6%          { transform: rotate(12deg); }
  13.8%         { transform: rotate(-16deg); }
  18%, 21.2%    { transform: rotate(0deg); }
  29.6%         { transform: rotate(-16deg); }
  38%           { transform: rotate(12deg); }
  46.4%         { transform: rotate(-16deg); }
  54.8%, 58.1%  { transform: rotate(0deg); }
  63.4%         { transform: rotate(-16deg); }
  68.7%         { transform: rotate(12deg); }
  74%           { transform: rotate(-16deg); }
  79.3%, 81.6%  { transform: rotate(0deg); }
  86%           { transform: rotate(-16deg); }
  90.3%         { transform: rotate(12deg); }
  94.7%         { transform: rotate(-16deg); }
  99%, 100%     { transform: rotate(0deg); }
}
.rally-leg-a1, .rally-leg-a2, .rally-leg-b1, .rally-leg-b2 { transform: rotate(0deg); }
.rally-active .rally-leg-a1 { animation: rally-legs-a-stride 13s linear infinite paused; animation-play-state: running; }
.rally-active .rally-leg-a2 { animation: rally-legs-a-stride-alt 13s linear infinite paused; animation-play-state: running; }
.rally-active .rally-leg-b1 { animation: rally-legs-b-stride 13s linear infinite paused; animation-play-state: running; }
.rally-active .rally-leg-b2 { animation: rally-legs-b-stride-alt 13s linear infinite paused; animation-play-state: running; }
/* Même bascule que les bras/déplacements : un point sur deux, chaque joueur
   marche sur le tempo de l'autre (voir plus haut pour le pourquoi). */
.rally-active.rally-swap .rally-leg-a1 { animation-name: rally-legs-b-stride; }
.rally-active.rally-swap .rally-leg-a2 { animation-name: rally-legs-b-stride-alt; }
.rally-active.rally-swap .rally-leg-b1 { animation-name: rally-legs-a-stride; }
.rally-active.rally-swap .rally-leg-b2 { animation-name: rally-legs-a-stride-alt; }

@media (prefers-reduced-motion: reduce) {
  .rally-shuttle, .rally-shuttle-trail,
  .rally-forearm.rally-arm-a, .rally-forearm.rally-arm-b,
  .rally-upperarm.rally-arm-a, .rally-upperarm.rally-arm-b,
  .rally-move-a, .rally-move-b,
  .rally-leg-a1, .rally-leg-a2, .rally-leg-b1, .rally-leg-b2 { animation: none !important; }
}
.site-footer .footer-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 24px;
  margin-bottom: 24px;
}
.site-footer h4 { color: #fff; font-size: 1rem; }
.footer-bottom {
  border-top: 1px solid rgba(255,255,255,0.15);
  padding-top: 16px;
  font-size: 0.82rem;
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
  color: rgba(255,255,255,0.6);
}
.footer-bottom a { color: rgba(255,255,255,0.6); }

/* --- Flash messages --- */
.flash {
  padding: 14px 18px;
  border-radius: var(--radius-sm);
  margin-bottom: 20px;
  font-weight: 600;
}
.flash-success { background: rgba(31,157,85,0.12); color: var(--color-success); }
.flash-error { background: rgba(192,57,43,0.12); color: var(--color-danger); }

/* --- Forms (public + admin) --- */
label { display: block; font-weight: 600; margin-bottom: 6px; color: var(--color-primary); }
input[type="text"], input[type="email"], input[type="password"], input[type="number"],
input[type="time"], input[type="file"], select, textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: 0.96rem;
  margin-bottom: 16px;
}
textarea { min-height: 120px; }
.form-row { display: flex; gap: 16px; flex-wrap: wrap; }
.form-row > * { flex: 1; min-width: 200px; }
.checkbox-row { display: flex; align-items: center; gap: 8px; }
.checkbox-row input { width: auto; margin: 0; }
/* Pour une case à cocher seule DANS UNE CELLULE DE TABLEAU (<td>) : jamais
   .checkbox-row ici — un display:flex sur un <td> lui fait perdre son
   display:table-cell et casse l'alignement des colonnes avec les autres
   lignes. Un simple centrage suffit, la cellule reste une vraie cellule. */
.td-checkbox { text-align: center; vertical-align: middle; }
.td-checkbox input { margin: 0; }

/* --- Apparition au défilement (progressive enhancement) ---
   Par défaut le contenu est toujours visible : le masquage n'est activé que
   par main.js (classe .js-reveal posée sur <html>) quand IntersectionObserver
   est dispo et que l'utilisateur n'a pas demandé "prefers-reduced-motion".
   Sans JS (ou moteur trop ancien), rien ne disparaît jamais. */
.js-reveal .reveal {
  opacity: 0;
  /* Volontairement lent (le retour utilisateur : "ça charge trop vite, je
     n'ai pas le temps de le voir") : 1.6s, largement au-dessus des 0.2-0.4s
     habituels, pour qu'on voie clairement le mouvement même en défilant
     vite. ease-out : départ franc, arrivée en douceur. Inclut box-shadow/
     border-color pour ne pas écraser (plus grande spécificité que
     .card/.tarif-card/.partenaire-chip) leur propre transition au survol. */
  transition: opacity 1.6s cubic-bezier(0.16, 1, 0.3, 1),
              transform 1.6s cubic-bezier(0.16, 1, 0.3, 1),
              box-shadow 0.25s ease, border-color 0.25s ease;
}
/* Direction d'arrivée : main.js tire l'une de ces classes au hasard pour
   chaque bloc (par-dessus .reveal), pour que les apparitions ne soient pas
   toutes identiques. Le point de départ (translation/rotation) ci-dessous ;
   .is-visible (3 classes, donc plus spécifique) ramène tout à l'état final. */
.js-reveal .reveal-bottom { transform: translateY(46px); }
.js-reveal .reveal-top { transform: translateY(-46px); }
.js-reveal .reveal-left { transform: translateX(-56px); }
.js-reveal .reveal-right { transform: translateX(56px); }
.js-reveal .reveal-flip { transform: perspective(700px) rotateY(90deg); }

.js-reveal .reveal.is-visible {
  opacity: 1;
  transform: none;
}

@media (max-width: 780px) {
  .nav-toggle { display: inline-block; }
  /* Le menu ouvert est un panneau détaché (position:absolute), pas un bloc
     empilé dans le header (l'ancienne approche, avec order:3 + flex-wrap,
     agrandissait la boîte du header lui-même — dès que cette boîte dépassait
     la hauteur de l'écran, position:sticky décrochait et le menu défilait
     normalement au lieu de rester collé en haut). Ici le header garde une
     hauteur fixe et minime (juste le logo + le bouton), donc il reste
     toujours bien accroché ; le panneau se contente de s'ancrer juste
     en dessous (.site-header, sticky, sert de repère de positionnement). */
  .main-nav {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--color-bg);
    border-top: 1px solid var(--color-border);
    box-shadow: 0 12px 24px rgba(11,79,108,0.15);
    max-height: calc(100vh - 70px);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    z-index: 49;
  }
  .main-nav.is-open { display: block; }
  .main-nav ul { flex-direction: column; padding: 8px 16px 16px; }
}
