@charset "UTF-8";
/* ============================================================================
   Tombola des 130 ans — FC Lausanne-Sport × Montriond FC (1896–2026)
   main.css — SOCLE DE DIRECTION ARTISTIQUE & DESIGN SYSTEM
   ----------------------------------------------------------------------------
   Registre : heritage, artisanat, edition limitee.
   Palette relevee sur la DA fournie (VISUEL_130_16x9_05.jpg,
   220926_PANNEAU_PROMO_MATCH_LS-THUN_v2.pdf, LOGO_MONTRIOND_X_LS.png) :
     - bleu officiel     IMPOSE par la DA 2026-27 -> #0165BA (item-022)
                         (les valeurs relevees a l'oeil sur les visuels
                          etaient #1C5B9E / #2A5696 -> #1B5BA8 ; elles ne
                          font plus autorite, le client a fourni la teinte
                          officielle Montriond.)
     - or-sable          mesure #C1A581 / #C3A885  -> retenu #C3A77E
     - creme / ivoire    mesure #FDF9F0 / #FEFCEE  -> retenu #F5F0E6 / #FAF6EC
     - bleu ciel (ecu)   mesure #7190BF            -> retenu #7190BF

   REGLE D'OR : aucune valeur hexadecimale en dur hors des blocs `:root`.
   Tout le reste du fichier ne consomme que des `var(--*)`.

   Conformite : layer-theme-common.md §1.2 (variables obligatoires), §1.3
   (prefixe `t-`), §2 (contrastes), §3 (typo), §4.3 (alternance des fonds),
   §9 (animations), §15 ; layer-guidelines.md §6 (contrastes mesures).
   ========================================================================= */

/* ===========================================================================
   0. POLICE OFFICIELLE TWK LAUSANNE (auto-hebergee) + FALLBACK CALIBRE (anti-CLS)
   3 fichiers (sur 4 max — layer-theme-common.md §3.1), fournis par le client
   (inputs/request-2026-10-15-1900/sources/TWKLausanne 2 (1).zip, dossier Web/).
   Metriques mesurees avec fontTools (tete/hhea/OS2) sur l'instance 650 —
   voir docs/BRAND.md « Typographie » pour le detail du calcul.
   ======================================================================== */

@font-face {
  font-family: 'TWK Lausanne';
  src: url('/assets/fonts/twk-lausanne-400.3edff1ede4.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2074,
    U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'TWK Lausanne';
  src: url('/assets/fonts/twk-lausanne-650.9df76945a7.woff2') format('woff2');
  font-weight: 650;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2074,
    U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'TWK Lausanne';
  src: url('/assets/fonts/twk-lausanne-850.9954c036ac.woff2') format('woff2');
  font-weight: 850;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2074,
    U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Fallback calibre — mesure sur l'instance 650 (corps), metriques hhea/OS2
   partagees par toutes les graisses TWK Lausanne (unitsPerEm 2048, ascent
   1866, descent 410, lineGap 0 ; xAvgCharWidth 1207 pour 650 vs 904 pour
   Arial a 2048 upm). Valeurs propres a cette fonte — distinctes du tableau
   de layer-site-common.md §4.3. */
@font-face {
  font-family: 'TWK Fallback';
  src: local('Arial');
  size-adjust: 133.52%;
  ascent-override: 68.24%;
  descent-override: 14.99%;
  line-gap-override: 0%;
}

/* ===========================================================================
   1. TOKENS — BLOC :root COMPLET (layer-theme-common.md §1.2)
   ======================================================================== */

:root {
  color-scheme: light;

  /* === COULEURS ========================================================= */

  /* Primaire — BLEU OFFICIEL MONTRIOND, DA 2026-27 (item-022).
     Toute la famille bleue du site est desormais une declinaison d'une
     SEULE teinte : on garde le H et le S de #0165ba (207.6deg, 98.9%) et
     on ne fait varier que la clarte. Le bleu « releve sur les visuels »
     d'item-001 (#1b5ba8, H 212.8 / S 72.3%) n'existe plus nulle part.

       token                  L (HSL)   remplace
       --color-primary          36.7%   #1b5ba8
       --color-primary-light    77.0%   #7fb2ec   <- ECLAIRCI, voir ci-dessous
       --color-primary-dark     23.5%   #0f3b6e
       --color-bg-deep          19.5%   #0e3463   (§ « Fonds »)
       --color-bg-deep-2        13.1%   #092544   (§ « Fonds »)

     ⚠️ `--color-primary-light` n'est PAS la simple transposition de
     l'ancienne clarte (71.2%). Mesure faite en composant reellement la
     couche `.section::before` (grain + guilloche) dans un navigateur :
     l'ancien #7fb2ec ne valait que 4.63:1 en moyenne et 3.73:1 dans le
     pire pixel sur le bleu profond, alors qu'il sert de `--surface-link`
     au §4 — un ECHEC AA pre-existant, de la meme famille que celui de
     l'or corrige en item-021 (contraste mesure sur le fond PUR, grain
     oublie). Monte a 77% de clarte, il tient 5.76:1 en moyenne et
     4.65:1 dans le pire pixel. */
  --color-primary: #0165ba;
  --color-primary-light: #8ac9fe;
  --color-primary-dark: #014177;
  --color-primary-rgb: 1, 101, 186;

  /* Secondaire & accent — or-sable.
     Trois valeurs, et chacune a un seul emploi legitime :
       --color-secondary       : l'or de la DA. DECORATIF et REMPLISSAGE
                                 (filets, fonds de bouton sur bleu profond :
                                 3.70:1 avec le fond grain compris, au-dessus
                                 du plancher de 3:1 d'une surface).
                                 ⚠️ PLUS JAMAIS COMME TEXTE : il ne vaut
                                 5.35:1 que sur le bleu profond PUR ; une fois
                                 la couche de grain prise en compte (screen,
                                 18%), le fond reel monte a ~#12436d et cet or
                                 tombe a 4.44:1 en moyenne et 3.58:1 dans le
                                 pire pixel. Mesure et corrige en item-021,
                                 re-mesure sur le bleu officiel en item-022.
       --color-secondary-light : l'or lisible en TEXTE sur les fonds sombres
                                 (5.84:1 avec le grain, 4.71:1 dans le pire
                                 pixel grain + guilloche) — c'est lui que
                                 `--surface-accent` consomme au §4.
       --color-accent          : or profond, seule version lisible en TEXTE
                                 sur les fonds clairs creme/ivoire (5.29:1). */
  --color-secondary: #c3a77e;
  --color-secondary-light: #d8c19b;
  --color-secondary-dark: #a4875a;
  --color-secondary-rgb: 195, 167, 126;
  --color-accent: #7a5f2a;
  --color-accent-rgb: 122, 95, 42;

  /* Bleu ciel de l'ecu Montriond — liseres, aplats decoratifs */
  --color-heritage: #7190bf;
  --color-heritage-rgb: 113, 144, 191;

  /* Fonds */
  --color-bg: #f5f0e6; /* creme  — fond de page */
  --color-bg-alt: #faf6ec; /* ivoire — section alternee */
  --color-bg-card: #fffcf5; /* blanc chaud — cartes */
  /* Bleu profond / bleu nuit — item-022 : le client demandait le bleu
     officiel « sur la section des paliers et le pied de page ». Poser
     #0165ba TEL QUEL sur ces deux fonds a ete MESURE puis ECARTE : une
     fois la couche de grain composee, le texte creme n'y vaut que 4.46:1
     (sous AA), l'or d'accent 2.82:1 et le remplissage or des boutons
     2.15:1 (plancher de surface : 3:1). Les deux fonds sombres seraient
     en outre devenus identiques, ce qui supprimait l'alternance imposee
     au §4.3 du layer et tout l'etagement de profondeur du site.
     Retenu : une DECLINAISON SOMBRE de la teinte officielle — meme H,
     meme S que #0165ba, clarte conservee a l'identique de l'ancienne
     paire. Les deux sections portent donc bien le bleu officiel, dans
     sa version profonde, et les contrastes restent a parite avec
     l'existant (or d'accent 4.71:1 contre 4.73:1 avant). */
  --color-bg-deep: #013663; /* bleu profond — sections d'emphase */
  --color-bg-deep-2: #002543; /* bleu nuit — pied de page */
  --color-bg-gold: #c3a77e; /* or — bandeau CTA */

  /* Textes */
  --color-text: #1a2430; /* encre sombre */
  --color-text-muted: #6b6054; /* encre sepia */
  --color-text-heading: #11253f; /* encre bleutee */
  --color-text-on-primary: #f7f3ea;

  /* Textes en contexte inverse (sur bleu profond) */
  --color-text-inverse: #f7f3ea;
  --color-text-inverse-muted: #bfd0e4;

  /* Semantique */
  --color-success: #2f6b3f;
  --color-warning: #8a5a12;
  --color-danger: #9b2c23;

  /* Triplets RGB pour composer des rgba() hors :root */
  --rgb-white: 255, 255, 255;
  --rgb-ink: 26, 36, 48;
  --rgb-cream: 247, 243, 234;

  /* === TYPOGRAPHIE ====================================================== */
  /* Police officielle du club (auto-hebergee) : une seule famille pour les
     titres et le corps. Les deux tokens restent distincts (obligatoire —
     layer-theme-common.md §1.2) mais partagent la meme pile. */
  --font-heading: 'TWK Lausanne', 'TWK Fallback', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
  --font-body: 'TWK Lausanne', 'TWK Fallback', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
  --font-size-base: 1rem;
  --line-height-base: 1.6;

  /* Echelle fluide — min a 360px, max a 1440px, ratio ~1.25 */
  --font-size-xs: clamp(0.75rem, 0.7292rem + 0.0926vw, 0.8125rem);
  --font-size-sm: clamp(0.875rem, 0.8542rem + 0.0926vw, 0.9375rem);
  --font-size-md: clamp(1.0625rem, 1.0208rem + 0.1852vw, 1.1875rem);
  --font-size-lg: clamp(1.1875rem, 1.125rem + 0.2778vw, 1.375rem);
  --font-size-xl: clamp(1.375rem, 1.25rem + 0.5556vw, 1.75rem);
  --font-size-2xl: clamp(1.625rem, 1.4167rem + 0.9259vw, 2.25rem);
  --font-size-3xl: clamp(1.9375rem, 1.5833rem + 1.5741vw, 3rem);
  --font-size-4xl: clamp(2.25rem, 1.7083rem + 2.4074vw, 3.875rem);
  --font-size-hero: clamp(2.625rem, 1.8333rem + 3.5185vw, 5rem);
  --font-size-display: clamp(4rem, 1.6667rem + 10.3704vw, 11rem); /* le "130" */

  /* Interlignes & graisses */
  --line-height-tight: 1.08;
  --line-height-heading: 1.18;
  --line-height-snug: 1.4;
  --line-height-loose: 1.75;
  /* 3 fichiers seulement (max 4 — layer-theme-common.md §3.1) : TWK Lausanne
     400 / 650 / 850. `font-synthesis-weight: none` interdit toute graisse
     simulee : ne referencer que ces 3 valeurs.
     - 400 (titres)    : h1-h6, poids regulier — grand corps de caracteres,
                         l'ampleur du texte porte l'effet monumental.
     - 650 (corps)     : graisse intermediaire — lisibilite confortable du
                         texte courant, plus soutenue que 400 a petite taille.
     - 850 (titres     : le chiffre « 130 » (.t-display) et les emphases
       monumentaux)      fortes (strong/b, .t-eyebrow) — le geste le plus
                         appuye de la DA. */
  --font-weight-body: 650;
  --font-weight-strong: 850;
  --font-weight-heading: 400;
  --font-weight-bold: 850;

  /* Approches — la DA repose sur des capitales tres espacees */
  --tracking-tight: -0.022em;
  --tracking-normal: 0;
  --tracking-wide: 0.08em;
  --tracking-wider: 0.18em;
  --tracking-widest: 0.3em;

  /* Mesure de ligne */
  --measure: 68ch;
  --measure-narrow: 54ch;

  /* === FORMES =========================================================== */
  --radius-sm: 6px;
  --radius-md: 12px;
  --radius-lg: 20px;
  --radius-xl: 32px;
  --radius-full: 9999px;

  /* === BORDURES ========================================================= */
  --border-width: 1px;
  --border-width-thick: 2px;
  --border-color: #e2d9c6; /* filet decoratif sur clair */
  --border-color-light: #efe8da;
  --border-color-strong: #8e8166; /* contour interactif, 3.37:1 sur creme */
  --border-color-inverse: #35537d; /* filet decoratif sur bleu profond */
  --border-color-inverse-strong: #8fa6c4; /* contour interactif, 4.98:1 */

  /* === OMBRES =========================================================== */
  /* Ombres chaudes : du noir pur sur un creme vire au gris sale.
     On teinte donc l'ombre a l'encre bleutee de la charte. */
  --shadow-sm: 0 1px 2px rgba(26, 36, 48, 0.05), 0 2px 8px rgba(26, 36, 48, 0.06);
  --shadow-md: 0 4px 10px rgba(26, 36, 48, 0.07), 0 12px 28px rgba(26, 36, 48, 0.1);
  --shadow-lg: 0 8px 20px rgba(26, 36, 48, 0.1), 0 24px 56px rgba(26, 36, 48, 0.16);
  --shadow-inset-hairline: inset 0 1px 0 rgba(255, 255, 255, 0.6);
  --shadow-glow: 0 0 24px rgba(1, 101, 186, 0.28);

  /* === ANIMATIONS ======================================================= */
  --transition-fast: 150ms;
  --transition-base: 300ms;
  --transition-slow: 600ms;
  --easing-default: cubic-bezier(0.4, 0, 0.2, 1);
  --easing-enter: cubic-bezier(0, 0, 0.2, 1);
  --easing-exit: cubic-bezier(0.4, 0, 1, 1);
  --easing-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
  --easing-elastic: cubic-bezier(0.68, -0.55, 0.27, 1.55);
  /* Easing « edition » : depart lent, arrivee posee. Signature du site. */
  --easing-editorial: cubic-bezier(0.22, 0.61, 0.36, 1);

  /* === GLASSMORPHISM ==================================================== */
  /* Contexte clair par defaut (layer-theme-common.md §7.2, ligne « Fond clair ») */
  --glass-bg: rgba(255, 255, 255, 0.6);
  --glass-blur: 20px;
  --glass-border: rgba(26, 36, 48, 0.08);
  /* Contexte sombre — consomme par .t-surface--deep */
  --glass-bg-inverse: rgba(255, 255, 255, 0.07);
  --glass-border-inverse: rgba(255, 255, 255, 0.14);

  /* === ESPACEMENT ======================================================= */
  --section-padding-y: 76px;
  --section-padding-y-mobile: 44px;
  --container-max-width: 1200px;
  --container-max-width-narrow: 820px;
  --container-padding-x: 24px;
  --space-3xs: 4px;
  --space-2xs: 8px;
  --space-xs: 12px;
  --space-sm: 16px;
  --space-md: 24px;
  --space-lg: 32px;
  --space-xl: 48px;
  --space-2xl: 64px;
  --space-3xl: 96px;
  --grid-gap: 16px;

  /* === GLOW & PULSE — tokens consommes par les items « effets » ========= */
  --glow-primary-soft: 0 0 18px rgba(1, 101, 186, 0.22);
  --glow-primary: 0 0 28px rgba(1, 101, 186, 0.38);
  --glow-primary-strong: 0 0 20px rgba(1, 101, 186, 0.5), 0 0 48px rgba(1, 101, 186, 0.28);
  --glow-gold-soft: 0 0 18px rgba(195, 167, 126, 0.28);
  --glow-gold: 0 0 28px rgba(195, 167, 126, 0.45);
  --glow-gold-strong: 0 0 20px rgba(195, 167, 126, 0.6), 0 0 52px rgba(195, 167, 126, 0.32);
  --glow-text-gold: 0 0 18px rgba(195, 167, 126, 0.45);
  --glow-text-primary: 0 0 18px rgba(138, 201, 254, 0.45);
  --pulse-duration: 2600ms;
  --pulse-scale: 1.04;
  --pulse-easing: cubic-bezier(0.45, 0, 0.3, 1);
  --shimmer-duration: 3200ms;
  --float-duration: 7000ms;
  --trail-color: rgba(195, 167, 126, 0.55); /* curseur a trainee */
  --trail-color-accent: rgba(1, 101, 186, 0.45);
  /* item-015 : masque de titres, halo du hero, parallaxe, compteurs */
  --reveal-word-step: 55ms;
  --cta-pop-duration: 640ms;
  --halo-breathe-duration: 6400ms;
  --counter-duration: 1300ms;
  --parallax-ratio: 0.06;
  --parallax-max: 16px;
  /* item-017 : fondu de disparition de l'overlay de lancement */
  --overlay-fade-duration: 1200ms;

  /* === DEGRADES ========================================================= */
  --gradient-primary: linear-gradient(135deg, #0165ba 0%, #014177 100%);
  /* Les stops sombres sont bornes : au-dela de #b59a72 le texte du bandeau or
     passe sous 4.5:1, et au-dela de #7a5f2a le titre en degrade passe sous
     3:1 sur le creme. Ne pas foncer ces fins de degrade. */
  --gradient-gold: linear-gradient(135deg, #d8c19b 0%, #c3a77e 55%, #b59a72 100%);
  --gradient-ink-gold: linear-gradient(135deg, #11253f 0%, #0165ba 55%, #7a5f2a 100%);
  --gradient-deep: linear-gradient(180deg, #013663 0%, #002543 100%);
  /* Halo or du hero plafonne a 0.13 : au-dela, le halo assombrit assez le
     creme pour faire passer le texte attenue sous 4.5:1. */
  --gradient-hero: radial-gradient(ellipse 120% 80% at 50% 0%, rgba(195, 167, 126, 0.13) 0%, transparent 62%),
    linear-gradient(180deg, #faf6ec 0%, #f5f0e6 100%);
  --gradient-rule: linear-gradient(90deg, transparent 0%, #c3a77e 50%, transparent 100%);

  /* === TEXTURE PAPIER ==================================================== */
  /* Grain fractal SVG — la DA est imprimee sur un papier creme texture. */
  --texture-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E");
  --texture-opacity: 0.1;
  --texture-opacity-inverse: 0.18;
  /* Sur un fond clair le grain se pose en `multiply`; sur le bleu profond il
     faut `screen`, sinon le bruit disparait dans le sombre. */
  --texture-blend: multiply;
  --texture-blend-inverse: screen;

  /* === TEXTURES & FILIGRANES DE HAUTE QUALITE (item-021) ================
     Le grain seul ne portait pas l'aspect « papier d'edition » voulu. On
     empile une SECONDE couche — une guilloche (rosette fiduciaire, meme
     registre qu'un certificat ou une coupure de banque) — dans la MEME
     couche composee par `.section::before` (§4) : aucun pseudo-element
     supplementaire, aucune requete reseau, un seul calque a peindre.
     Deux variantes par motif, et c'est obligatoire : l'encre pour les
     fonds clairs (blend `multiply`), la creme pour le bleu profond (blend
     `screen` — une guilloche encre y serait purement invisible).
     `--texture-weave` evoque la maille d'un maillot : reservee a la
     section « Les maillots » via `.t-texture--weave` (§19).
     `--texture-seal` est un MASQUE (alpha seul, aucune couleur dedans) :
     sa teinte vient de `background-color: var(--surface-watermark-color)`,
     ce qui le fait suivre le contexte de surface sans le dupliquer. */
  --texture-guilloche: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='360' height='360'%3E%3Cg transform='translate(180 180)' fill='none' stroke='%231a2430' stroke-width='0.5' stroke-opacity='0.5'%3E%3Cellipse rx='168' ry='60' transform='rotate(0)'/%3E%3Cellipse rx='168' ry='60' transform='rotate(22.5)'/%3E%3Cellipse rx='168' ry='60' transform='rotate(45)'/%3E%3Cellipse rx='168' ry='60' transform='rotate(67.5)'/%3E%3Cellipse rx='168' ry='60' transform='rotate(90)'/%3E%3Cellipse rx='168' ry='60' transform='rotate(112.5)'/%3E%3Cellipse rx='168' ry='60' transform='rotate(135)'/%3E%3Cellipse rx='168' ry='60' transform='rotate(157.5)'/%3E%3Ccircle r='168'/%3E%3Ccircle r='126'/%3E%3Ccircle r='84'/%3E%3Ccircle r='42'/%3E%3C/g%3E%3C/svg%3E");
  --texture-guilloche-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='360' height='360'%3E%3Cg transform='translate(180 180)' fill='none' stroke='%23f7f3ea' stroke-width='0.5' stroke-opacity='0.25'%3E%3Cellipse rx='168' ry='60' transform='rotate(0)'/%3E%3Cellipse rx='168' ry='60' transform='rotate(22.5)'/%3E%3Cellipse rx='168' ry='60' transform='rotate(45)'/%3E%3Cellipse rx='168' ry='60' transform='rotate(67.5)'/%3E%3Cellipse rx='168' ry='60' transform='rotate(90)'/%3E%3Cellipse rx='168' ry='60' transform='rotate(112.5)'/%3E%3Cellipse rx='168' ry='60' transform='rotate(135)'/%3E%3Cellipse rx='168' ry='60' transform='rotate(157.5)'/%3E%3Ccircle r='168'/%3E%3Ccircle r='126'/%3E%3Ccircle r='84'/%3E%3Ccircle r='42'/%3E%3C/g%3E%3C/svg%3E");
  --texture-weave: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cg fill='none' stroke='%231a2430' stroke-width='0.6' stroke-opacity='0.38'%3E%3Cpath d='M0 6h24M0 18h24M6 0v24M18 0v24'/%3E%3Ccircle cx='12' cy='12' r='1.6'/%3E%3Ccircle cx='0' cy='0' r='1.6'/%3E%3Ccircle cx='24' cy='0' r='1.6'/%3E%3Ccircle cx='0' cy='24' r='1.6'/%3E%3Ccircle cx='24' cy='24' r='1.6'/%3E%3C/g%3E%3C/svg%3E");
  --texture-weave-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cg fill='none' stroke='%23f7f3ea' stroke-width='0.6' stroke-opacity='0.34'%3E%3Cpath d='M0 6h24M0 18h24M6 0v24M18 0v24'/%3E%3Ccircle cx='12' cy='12' r='1.6'/%3E%3Ccircle cx='0' cy='0' r='1.6'/%3E%3Ccircle cx='24' cy='0' r='1.6'/%3E%3Ccircle cx='0' cy='24' r='1.6'/%3E%3Ccircle cx='24' cy='24' r='1.6'/%3E%3C/g%3E%3C/svg%3E");
  --texture-seal: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='600'%3E%3Cg transform='translate(300 300)' fill='none' stroke='%23000' stroke-width='1.6' stroke-opacity='1'%3E%3Cellipse rx='276' ry='104' transform='rotate(0)'/%3E%3Cellipse rx='276' ry='104' transform='rotate(15)'/%3E%3Cellipse rx='276' ry='104' transform='rotate(30)'/%3E%3Cellipse rx='276' ry='104' transform='rotate(45)'/%3E%3Cellipse rx='276' ry='104' transform='rotate(60)'/%3E%3Cellipse rx='276' ry='104' transform='rotate(75)'/%3E%3Cellipse rx='276' ry='104' transform='rotate(90)'/%3E%3Cellipse rx='276' ry='104' transform='rotate(105)'/%3E%3Cellipse rx='276' ry='104' transform='rotate(120)'/%3E%3Cellipse rx='276' ry='104' transform='rotate(135)'/%3E%3Cellipse rx='276' ry='104' transform='rotate(150)'/%3E%3Cellipse rx='276' ry='104' transform='rotate(165)'/%3E%3Ccircle r='288'/%3E%3Ccircle r='276'/%3E%3Ccircle r='196'/%3E%3Ccircle r='116'/%3E%3Ccircle r='44'/%3E%3C/g%3E%3C/svg%3E");
  --texture-tile-grain: 180px 180px;
  --texture-tile-guilloche: 360px 360px;
  --texture-tile-weave: 24px 24px;
  --watermark-opacity: 0.055;
  --watermark-opacity-inverse: 0.1;
  --watermark-size: clamp(300px, 46vw, 640px);
  --watermark-drift-duration: 28s;

  /* === REFLETS « PREMIUM » (item-021) ===================================
     Deux familles, et elles ne se melangent pas :
       - le reflet SPECULAIRE (`--sheen-*`) est blanc : il ne fait que
         « lever » la matiere qu'il traverse, un reflet colore salirait la
         palette ;
       - la bordure VIVANTE (`--gradient-metal-gold`) est un or metallique
         complet (ombre -> pleine lumiere -> ombre), la seule facon qu'un
         filet de 1 a 2 px se lise comme du metal et non comme un trait.
     Les deux bouts sombres de ce degrade ne descendent pas sous
     `--border-color-strong` (#8e8166, le filet interactif deja mesure du
     projet) : la bordure vivante est donc, partout, au moins aussi
     contrastee que le filet uni qu'elle remplace. */
  --sheen-strong: rgba(255, 255, 255, 0.62);
  --sheen-soft: rgba(255, 255, 255, 0.24);
  --gradient-metal-gold: linear-gradient(
    100deg,
    #8e8166 0%,
    #a4875a 12%,
    #d8c19b 26%,
    #fffcf5 36%,
    #d8c19b 46%,
    #a4875a 60%,
    #c3a77e 78%,
    #8e8166 100%
  );
  /* Reflet diagonal qui traverse une carte ou une image : transparent aux
     deux bouts, une seule bande claire au centre. */
  --gradient-sheen: linear-gradient(
    104deg,
    rgba(255, 255, 255, 0) 38%,
    rgba(255, 255, 255, 0.24) 47%,
    rgba(255, 255, 255, 0.62) 50%,
    rgba(255, 255, 255, 0.24) 53%,
    rgba(255, 255, 255, 0) 62%
  );
  /* Duree du balayage speculaire des CTA primaires. ⚠️ Ce token DOIT exister :
     il est consomme dans la propriete raccourcie `animation` de
     `.btn--primary::after` (§19.3), et une variable absente y invalide la
     declaration ENTIERE (`animation-name` retombe a `none`) — le reflet
     disparait alors en silence, sans la moindre erreur de console. */
  --sheen-duration: 5400ms;
  --edge-travel-duration: 7200ms;
  --reveal-distance: 34px;
  --reveal-blur: 10px;
  /* CTA fixe de bas de viewport (mobile) — item-021 */
  --sticky-cta-duration: 520ms;
  --sticky-cta-pulse-duration: 2400ms;

  /* === PROFONDEURS (z-index) ============================================ */
  --z-base: 0;
  --z-content: 2;
  --z-sticky: 100;
  --z-overlay: 900;
  --z-modal: 1000;
  --z-cursor: 2000;

  /* Valeur de peinture du masque mask-composite (§6.1 du layer theme) : non
     visuelle (alpha seul), mais gardee en token pour respecter la regle
     d'or « aucun hexadecimal hors :root » (item-015). */
  --mask-paint: #fff;

  /* === CONTEXTE DE SURFACE (valeurs par defaut = creme) =================
     Toute la page lit ces variables, jamais les couleurs brutes. Les classes
     `.t-surface--*` (§4) les reassignent, ce qui fait suivre automatiquement
     textes, filets, boutons et verre a chaque changement de fond. */
  --surface-bg: var(--color-bg);
  --surface-bg-image: none;
  --surface-text: var(--color-text);
  --surface-text-muted: var(--color-text-muted);
  --surface-text-heading: var(--color-text-heading);
  --surface-accent: var(--color-accent);
  --surface-link: var(--color-primary);
  --surface-border: var(--border-color);
  --surface-border-strong: var(--border-color-strong);
  --surface-card-bg: var(--color-bg-card);
  --surface-glass-bg: var(--glass-bg);
  --surface-glass-border: var(--glass-border);
  --surface-texture-opacity: var(--texture-opacity);
  --surface-texture-blend: var(--texture-blend);
  /* item-021 : la couche de texture compose deux motifs (grain + guilloche).
     Les surfaces sombres (§4) remplacent la guilloche encre par sa variante
     creme, sans quoi le blend `screen` l'effacerait completement. */
  --surface-texture-image: var(--texture-grain), var(--texture-guilloche);
  --surface-texture-size: var(--texture-tile-grain), var(--texture-tile-guilloche);
  --surface-watermark-color: var(--color-accent);
  --surface-watermark-opacity: var(--watermark-opacity);
  /* Boutons en contexte : le remplissage doit garder 3:1 avec le fond de
     section, et son libelle 4.5:1 avec le remplissage. */
  --surface-btn-bg: var(--color-primary);
  --surface-btn-text: var(--color-text-on-primary);
  --surface-btn-bg-hover: var(--color-primary-dark);
  --surface-btn-glow: var(--glow-primary);
  --surface-focus-ring: var(--color-primary);
}

/* ===========================================================================
   2. RESET & BASE
   ======================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Compense un header sticky lors des ancres */
  scroll-padding-top: var(--space-3xl);
}

body {
  margin: 0;
  background-color: var(--color-bg);
  color: var(--surface-text);
  font-family: var(--font-body);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-body);
  line-height: var(--line-height-base);
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-synthesis-weight: none;
  overflow-x: hidden;
}

img,
picture,
video,
canvas,
svg {
  display: block;
  max-width: 100%;
}

img {
  height: auto;
}

input,
button,
textarea,
select {
  font: inherit;
  color: inherit;
}

button {
  background: none;
  border: 0;
  cursor: pointer;
}

ul,
ol {
  margin: 0;
  padding: 0;
}

hr {
  border: 0;
  height: var(--border-width);
  background: var(--gradient-rule);
  margin: var(--space-xl) 0;
}

::selection {
  background: var(--color-secondary);
  color: var(--color-text);
}

/* ===========================================================================
   3. TYPOGRAPHIE
   ======================================================================== */

h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0 0 var(--space-sm);
  font-family: var(--font-heading);
  font-weight: var(--font-weight-heading);
  line-height: var(--line-height-heading);
  letter-spacing: var(--tracking-tight);
  color: var(--surface-text-heading);
  text-wrap: balance;
}

h1 {
  font-size: var(--font-size-hero);
  line-height: var(--line-height-tight);
}
h2 {
  font-size: var(--font-size-3xl);
}
h3 {
  font-size: var(--font-size-xl);
}
h4 {
  font-size: var(--font-size-lg);
}
h5,
h6 {
  font-size: var(--font-size-md);
}

p {
  margin: 0 0 var(--space-sm);
  max-width: var(--measure);
  text-wrap: pretty;
}

small {
  font-size: var(--font-size-sm);
}

strong,
b {
  font-weight: var(--font-weight-bold);
}

a {
  color: var(--surface-link);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  transition: color var(--transition-fast) var(--easing-default);
}

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

/* Chiffres : le site compte des billets, des paliers, des maillots numerotes.
   Tabular partout pour eviter la danse des chiffres. */
.t-numeric,
time,
[data-counter] {
  font-family: var(--font-body);
  font-variant-numeric: tabular-nums lining-nums;
  letter-spacing: var(--tracking-normal);
}

/* Surtitre en capitales espacees — le « 1896 - 2026 » de la DA. */
.t-eyebrow {
  display: inline-block;
  font-family: var(--font-body);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-strong);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--surface-accent);
  margin: 0 0 var(--space-xs);
}

/* Le chiffre « 130 » — geste typographique dominant de la DA. */
.t-display {
  font-family: var(--font-body);
  font-size: var(--font-size-display);
  font-weight: var(--font-weight-bold);
  line-height: 0.82;
  letter-spacing: var(--tracking-tight);
  font-variant-numeric: lining-nums;
  color: var(--color-primary);
}

/* Texte attenue — mentions, legendes, conditions. Mesure 5.40:1 sur creme. */
.t-muted {
  color: var(--surface-text-muted);
}

/* Exergue serif en italique — citations, chapeaux de section. */
.t-lede {
  font-family: var(--font-heading);
  font-size: var(--font-size-lg);
  line-height: var(--line-height-snug);
  letter-spacing: var(--tracking-normal);
  color: var(--surface-text);
  max-width: var(--measure-narrow);
}

/* item-032 : centrage de .t-lede porte dans la feuille plutot que par un
   style="margin-inline:auto" inline repete page apres page (7 occurrences
   avant ce correctif, absent des 3 pages privees). Scope deliberement limite
   aux contextes reellement centres (en-tete de section, intro des pages
   privees, hero) : .doc-hero__lead (regles/mentions legales) vit dans un
   gabarit documentaire aligne a gauche et ne doit pas bouger. */
.section__head .t-lede,
.private-intro .t-lede,
.hero .t-lede {
  margin-inline: auto;
}

/* Filet d'orfevre — separateur or en degrade. */
.t-rule {
  display: block;
  width: 120px;
  height: var(--border-width);
  border: 0;
  margin: var(--space-md) 0;
  background: var(--gradient-rule);
}

.t-rule--center {
  margin-inline: auto;
}

/* ===========================================================================
   4. LAYOUT & ALTERNANCE DES FONDS DE SECTION
   ---------------------------------------------------------------------------
   Cycle impose (layer-theme-common.md §4.3 — deux sections adjacentes ne
   partagent JAMAIS le meme fond) :

     1. Hero        -> .t-surface--hero    (creme + halo or)
     2. Le concept  -> .t-surface--ivory
     3. Les maillots-> .t-surface--cream
     4. Les paliers -> .t-surface--deep    (bleu profond, respiration)
     5. Comment     -> .t-surface--ivory
     6. Club des 130-> .t-surface--cream
     7. CTA final   -> .t-surface--gold    (or, point d'orgue)
     8. Footer      -> .t-surface--night   (bleu nuit)

   Chaque modificateur reassigne le contexte complet : textes, filets,
   boutons, verre et grain suivent sans intervention.
   ======================================================================== */

.container {
  width: 100%;
  max-width: var(--container-max-width);
  margin-inline: auto;
  padding-inline: var(--container-padding-x);
}

.container--narrow {
  max-width: var(--container-max-width-narrow);
}

.section {
  position: relative;
  z-index: var(--z-content);
  padding-block: var(--section-padding-y-mobile);
  background-color: var(--surface-bg);
  background-image: var(--surface-bg-image);
  color: var(--surface-text);
  isolation: isolate;
  contain: layout style;
}

/* Grain de papier, pose par surface : la DA est imprimee sur un papier creme
   texture. Une seule couche composee par section, sous le contenu.
   item-021 : cette couche compose desormais DEUX motifs (grain + guilloche,
   voir --surface-texture-image au §1) plutot qu'un seul. Volontairement dans
   le meme pseudo-element : deux fonds empiles ne coutent qu'une seule passe
   de peinture, la ou un second pseudo-element aurait double le calque. */
.section::before,
.t-grain::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: var(--surface-texture-image);
  background-repeat: repeat;
  background-size: var(--surface-texture-size);
  opacity: var(--surface-texture-opacity);
  mix-blend-mode: var(--surface-texture-blend);
}

.t-grain {
  position: relative;
  isolation: isolate;
}

/* --- Modificateurs de surface ------------------------------------------- */

.t-surface--cream {
  --surface-bg: var(--color-bg);
}

.t-surface--ivory {
  --surface-bg: var(--color-bg-alt);
  --surface-card-bg: var(--color-bg-card);
}

.t-surface--hero {
  --surface-bg: var(--color-bg);
  --surface-bg-image: var(--gradient-hero);
}

/* Bleu profond — tout bascule en contexte inverse. */
.t-surface--deep,
.t-surface--night {
  --surface-text: var(--color-text-inverse);
  --surface-text-muted: var(--color-text-inverse-muted);
  --surface-text-heading: var(--color-text-inverse);
  /* item-021 — CORRECTION DE CONTRASTE (bug pre-existant, mesure ici).
     L'accent valait `--color-secondary` (#c3a77e). Les 5.41:1 documentes
     depuis item-001 avaient ete mesures sur le bleu profond PUR, en oubliant
     que `.section::before` pose par-dessus une couche de grain en blend
     `screen` a 18% d'opacite : elle eclaircit le fond — sous le bleu
     officiel d'item-022 le fond reel est ~#12436d — ou cet or ne vaut que
     4.44:1 en moyenne, 3.58:1 au pire pixel : sous le plancher AA.
     `--color-secondary-light` (#d8c19b, token deja existant, aucune couleur
     nouvelle) tient 5.84:1 avec le grain et 4.71:1 dans le pire pixel
     cumule grain + guilloche. Seul l'ACCENT change : le remplissage des
     boutons reste `--color-secondary` (4.44:1 avec le fond, au-dessus du
     plancher de 3:1 exige d'une surface), et son libelle encre 6.83:1.
     item-022 : toutes ces valeurs re-mesurees sur les nouveaux fonds. */
  --surface-accent: var(--color-secondary-light);
  /* item-022 : `--color-primary-light` a ete ECLAIRCI (#7fb2ec -> #8ac9fe)
     precisement pour cette ligne — c'est le seul endroit du site ou il sert
     de texte. Il passe de 3.73:1 (echec AA pre-existant) a 4.65:1 au pire
     pixel, 5.76:1 en moyenne. Voir le §1. */
  --surface-link: var(--color-primary-light);
  --surface-border: var(--border-color-inverse);
  --surface-border-strong: var(--border-color-inverse-strong);
  --surface-card-bg: var(--glass-bg-inverse);
  --surface-glass-bg: var(--glass-bg-inverse);
  --surface-glass-border: var(--glass-border-inverse);
  --surface-texture-opacity: var(--texture-opacity-inverse);
  --surface-texture-blend: var(--texture-blend-inverse);
  /* item-021 : guilloche creme obligatoire ici — en blend `screen`, la
     variante encre serait strictement invisible. Le filigrane, lui, passe
     a l'or (la seule teinte de la charte qui se leve sur le bleu profond)
     et gagne en opacite : un sceau sombre sur fond sombre ne se lit pas. */
  --surface-texture-image: var(--texture-grain), var(--texture-guilloche-inverse);
  --surface-watermark-color: var(--color-secondary);
  --surface-watermark-opacity: var(--watermark-opacity-inverse);
  /* Le bleu primaire ne tient pas 3:1 sur le bleu profond — 2.09:1 sur le
     fond pur, 1.73:1 une fois le grain compose (item-022, re-mesure) :
     le bouton passe donc a l'or, libelle a l'encre. */
  --surface-btn-bg: var(--color-secondary);
  --surface-btn-text: var(--color-text);
  --surface-btn-bg-hover: var(--color-secondary-light);
  --surface-btn-glow: var(--glow-gold);
  --surface-focus-ring: var(--color-secondary-light);
  color-scheme: dark;
}

.t-surface--deep {
  --surface-bg: var(--color-bg-deep);
  --surface-bg-image: var(--gradient-deep);
}

.t-surface--night {
  --surface-bg: var(--color-bg-deep-2);
  --surface-bg-image: none;
}

/* Or — point d'orgue. L'encre sombre est la seule couleur de texte tenable. */
.t-surface--gold {
  --surface-bg: var(--color-bg-gold);
  --surface-bg-image: var(--gradient-gold);
  --surface-text: var(--color-text);
  --surface-text-muted: var(--color-text);
  --surface-text-heading: var(--color-text-heading);
  /* Le bleu primary-dark tombe a 3.54:1 sur la fin du degrade or (item-022,
     re-mesure) : accent et liens passent donc a l'encre, seule teinte sure
     sur toute la bande. */
  --surface-accent: var(--color-text);
  --surface-link: var(--color-text-heading);
  --surface-border: rgba(26, 36, 48, 0.18);
  --surface-border-strong: var(--color-text);
  --surface-card-bg: var(--color-bg-card);
  --surface-glass-bg: rgba(255, 255, 255, 0.28);
  --surface-glass-border: rgba(26, 36, 48, 0.14);
  /* item-021 : sur l'or, un filigrane or serait invisible et un filigrane
     blanc delaverait la bande — il passe donc a l'encre, tres bas. */
  --surface-watermark-color: var(--color-text-heading);
  --surface-watermark-opacity: var(--watermark-opacity);
  /* Le primaire ne tient pas 3:1 sur l'or (2.49:1 grain compris, item-022) :
     on fonce a primary-dark, qui tient 4.16:1 au pire pixel. */
  --surface-btn-bg: var(--color-primary-dark);
  --surface-btn-text: var(--color-text-on-primary);
  --surface-btn-bg-hover: var(--color-text-heading);
  --surface-btn-glow: var(--shadow-md);
  --surface-focus-ring: var(--color-text);
}

/* ===========================================================================
   5. MATIERES — VERRE, CARTES, FILETS
   ======================================================================== */

.t-card {
  background: var(--surface-card-bg);
  border: var(--border-width) solid var(--surface-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  transition:
    box-shadow var(--transition-base) var(--easing-editorial),
    transform var(--transition-base) var(--easing-editorial),
    border-color var(--transition-base) var(--easing-default);
  contain: layout style;
}

.t-card:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-4px);
  border-color: var(--surface-accent);
}

.t-glass {
  background: var(--surface-glass-bg);
  backdrop-filter: blur(var(--glass-blur)) saturate(140%);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(140%);
  border: var(--border-width) solid var(--surface-glass-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-inset-hairline);
  transform: translate3d(0, 0, 0); /* force le compositing GPU (§14.2) */
}

/* Encadrement « certificat » — double filet d'edition limitee. */
.t-frame {
  position: relative;
  border: var(--border-width) solid var(--surface-border);
  border-radius: var(--radius-sm);
  padding: var(--space-md);
}

.t-frame::after {
  content: '';
  position: absolute;
  inset: var(--space-3xs);
  border: var(--border-width) solid var(--surface-border);
  border-radius: var(--radius-sm);
  pointer-events: none;
  opacity: 0.6;
}

/* ===========================================================================
   6. BOUTONS
   Les remplissages et libelles sont pilotes par le contexte de surface (§4),
   ce qui garantit les ratios WCAG sur les quatre fonds du cycle.
   ======================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: 48px; /* > 44x44 (WCAG 2.5.5) */
  padding: var(--space-xs) var(--space-lg);
  font-family: var(--font-body);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-strong);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  text-decoration: none;
  text-align: center;
  border-radius: var(--radius-full);
  border: var(--border-width-thick) solid transparent;
  cursor: pointer;
  position: relative;
  isolation: isolate;
  transition:
    background-color var(--transition-base) var(--easing-default),
    color var(--transition-base) var(--easing-default),
    border-color var(--transition-base) var(--easing-default),
    box-shadow var(--transition-base) var(--easing-editorial),
    transform var(--transition-fast) var(--easing-editorial);
}

.btn:active {
  transform: translateY(1px);
}

/* item-015 : halo en respiration lente (box-shadow — exception sanctionnee
   par layer-theme-common.md §10.3 « Glow pulse », impact Faible au §14.1).
   Pas de `will-change` permanent ici : l'animation n'a pas de fin, et
   box-shadow ne beneficie pas du compositing GPU que `will-change` cible. */
.btn--primary {
  background-color: var(--surface-btn-bg);
  color: var(--surface-btn-text);
  box-shadow: var(--shadow-sm);
  animation: t-btn-breathe var(--pulse-duration) var(--pulse-easing) infinite;
}

/* Elevation + glow renforce au survol/focus clavier — `will-change` scope a
   la pseudo-classe : pose au debut du geste, retire quand il se termine. */
.btn--primary:hover,
.btn--primary:focus-visible {
  background-color: var(--surface-btn-bg-hover);
  color: var(--surface-btn-text);
  box-shadow: var(--shadow-lg), var(--surface-btn-glow);
  transform: translateY(-3px);
  will-change: transform;
}

/* Retour tactile a l'appui : la respiration marque une pause, le bouton se
   tasse legerement (transform seul, §9.1.4). */
.btn--primary:active {
  transform: translateY(0) scale(0.97);
  box-shadow: var(--shadow-sm);
  animation-play-state: paused;
}

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

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

.btn--ghost {
  background-color: transparent;
  color: var(--surface-link);
  padding-inline: var(--space-sm);
}

.btn--ghost:hover {
  color: var(--surface-accent);
}

.btn--lg {
  min-height: 56px;
  font-size: var(--font-size-base);
  padding: var(--space-sm) var(--space-xl);
}

/* item-021 : gabarit compact, cree pour l'en-tete — le CTA y etait au meme
   corps que celui du hero et ecrasait la marque. 44 px de haut et pas un
   pixel de moins : c'est le plancher de cible tactile (WCAG 2.5.5,
   layer-theme-common.md §12.3), et c'est bien la hauteur reelle du bouton,
   pas seulement celle de son libelle. */
.btn--sm {
  min-height: 44px;
  font-size: var(--font-size-xs);
  letter-spacing: var(--tracking-wide);
  padding: var(--space-2xs) var(--space-md);
}

.btn--block {
  display: flex;
  width: 100%;
}

/* ===========================================================================
   7. FOCUS & ACCESSIBILITE DE NAVIGATION
   ======================================================================== */

:focus-visible {
  outline: var(--border-width-thick) solid var(--surface-focus-ring);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

:focus:not(:focus-visible) {
  outline: none;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: var(--space-2xs);
  left: var(--space-2xs);
  z-index: var(--z-modal);
  padding: var(--space-xs) var(--space-md);
  background: var(--color-primary);
  color: var(--color-text-on-primary);
  border-radius: var(--radius-sm);
  transform: translateY(-200%);
  transition: transform var(--transition-fast) var(--easing-enter);
}

.skip-link:focus-visible {
  transform: translateY(0);
}

/* ===========================================================================
   8. TEXTE STYLISE
   ======================================================================== */

.t-gradient-text {
  color: var(--color-primary); /* fallback obligatoire (§11.1) */
  background: var(--gradient-ink-gold);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.t-surface--deep .t-gradient-text,
.t-surface--night .t-gradient-text {
  color: var(--color-secondary);
  background: var(--gradient-gold);
  -webkit-background-clip: text;
  background-clip: text;
}

/* item-015 : degrade bleu -> or anime sur les mots-cles — reutilise le
   keyframe t-shimmer (§9) deja prevu pour cet usage. Le fallback `color`
   de .t-gradient-text (ligne ci-dessus) reste la base d'accessibilite :
   seule la position du degrade bouge, jamais sa palette ni son contraste. */
.t-gradient-text--animated {
  background-size: 200% auto;
  animation: t-shimmer var(--shimmer-duration) linear infinite;
}

/* Le glow reste decoratif : le texte doit rester lisible sans lui (§11.4). */
.t-glow-text {
  text-shadow: var(--glow-text-gold);
}

.t-surface--deep .t-glow-text,
.t-surface--night .t-glow-text {
  text-shadow: var(--glow-text-primary);
}

/* ===========================================================================
   9. ANIMATIONS — PRIMITIVES & TOKENS D'EFFETS
   Les items « effets » consomment ces keyframes et les tokens --glow-* / --pulse-*.
   ======================================================================== */

.reveal {
  opacity: 0;
  transform: translateY(28px);
  transition:
    opacity var(--transition-slow) var(--easing-editorial),
    transform var(--transition-slow) var(--easing-editorial);
  will-change: opacity, transform;
}

.reveal.is-visible {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* Stagger — cadence d'apparition des grilles */
.reveal--d1 {
  transition-delay: 90ms;
}
.reveal--d2 {
  transition-delay: 180ms;
}
.reveal--d3 {
  transition-delay: 270ms;
}
.reveal--d4 {
  transition-delay: 360ms;
}
.reveal--d5 {
  transition-delay: 450ms;
}

@keyframes t-pulse-glow {
  0%,
  100% {
    box-shadow: var(--glow-gold-soft);
  }
  50% {
    box-shadow: var(--glow-gold-strong);
  }
}

@keyframes t-pulse-scale {
  0%,
  100% {
    transform: scale(1);
  }
  50% {
    transform: scale(var(--pulse-scale));
  }
}

@keyframes t-shimmer {
  0% {
    background-position: 200% 50%;
  }
  100% {
    background-position: -200% 50%;
  }
}

@keyframes t-float {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-8px);
  }
}

@keyframes t-fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.t-pulse {
  animation: t-pulse-glow var(--pulse-duration) var(--pulse-easing) infinite;
}

.t-float {
  animation: t-float var(--float-duration) var(--easing-default) infinite;
}

/* --- Curseur a trainee (item-016) ---------------------------------------
   Le conteneur couvre tout le viewport (`inset: 0`) : `contain: strict`
   cree une boite de reference pour les enfants `position: absolute`, une
   boite 0x0 les aurait tous clippes (paint containment). Chaque enfant ne
   recoit, frame apres frame, QUE `transform: translate3d(...)` depuis la
   boucle rAF du JS (assets/script.<hash>.js) ; taille, opacite et couleur
   sont figees une fois a la creation — jamais de propriete animee hors
   transform (garde-fou §14.2). Teinte bleu LS -> or le long de la trainee
   via les tokens --trail-color-accent/--trail-color (§1.2), jamais de
   hexadecimal ici. Desactive par le JS lui-meme hors (pointer: fine) +
   (hover: hover) et sous prefers-reduced-motion ; le curseur systeme reste
   visible tant que `html.gotb-cursor-active` n'est pas posee (repli
   garanti si le script est bloque ou absent — voir §11/§12 plus bas pour
   les reinstaurations de `cursor: auto`). */
.t-cursor {
  position: fixed;
  inset: 0;
  z-index: var(--z-cursor);
  pointer-events: none;
  contain: strict;
}

.t-cursor__dot,
.t-cursor__seg {
  position: absolute;
  top: 0;
  left: 0;
  border-radius: 50%;
  will-change: transform;
}

.t-cursor__dot {
  background: var(--trail-color-accent);
  box-shadow: var(--glow-primary-soft);
  transition: box-shadow var(--transition-base) var(--easing-default);
}

.t-cursor__seg {
  background: var(--trail-color);
  background: color-mix(
    in srgb,
    var(--trail-color-accent) calc(100% - var(--i, 1) * 9%),
    var(--trail-color) calc(var(--i, 1) * 9%)
  );
  opacity: calc(1 - var(--i, 1) * 0.08);
}

.t-cursor.is-hover .t-cursor__dot {
  box-shadow: var(--glow-primary);
}

/* Le curseur systeme ne disparait que si cette classe est posee par le JS,
   une fois la boucle rAF reellement demarree (premier `pointermove`) —
   jamais `cursor: none` sans ce repli (demande explicite de l'item). */
html.gotb-cursor-active,
html.gotb-cursor-active * {
  cursor: none;
}

/* --- item-015 : apparition en masque des titres, mot par mot --------------
   Pose par assets/script.<hash>.js sur tout element [data-reveal-words] :
   chaque mot est enveloppe dans .word-mask (fenetre, overflow hidden) >
   .word-mask__inner (le mot qui coulisse). Le `will-change` n'est actif
   qu'avant reveal — la regle `.is-revealed` le repasse a `auto` (§14.2). */
[data-reveal-words] {
  display: block;
}

.word-mask {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}

.word-mask__inner {
  display: inline-block;
  transform: translateY(110%);
  opacity: 0;
  transition:
    transform var(--transition-slow) var(--easing-editorial),
    opacity var(--transition-slow) var(--easing-editorial);
  transition-delay: calc(var(--word-i, 0) * var(--reveal-word-step));
  will-change: transform, opacity;
}

[data-reveal-words].is-revealed .word-mask__inner {
  transform: translateY(0);
  opacity: 1;
  will-change: auto;
}

/* --- item-015 : halo CTA en respiration lente (consomme par .btn--primary,
   §6) — alterne entre repos et glow contextuel (--surface-btn-glow suit le
   fond de section, cf. §4). ------------------------------------------------ */
@keyframes t-btn-breathe {
  0%,
  100% {
    box-shadow: var(--shadow-sm);
  }
  50% {
    box-shadow: var(--shadow-sm), var(--surface-btn-glow);
  }
}

/* --- item-015 : pulse discret a l'apparition d'un CTA (ponctuel, retire
   apres coup par le JS via `animationend` — jamais permanent, §14.2). ----- */
@keyframes t-cta-pop {
  0% {
    transform: scale(0.9);
    opacity: 0;
  }
  60% {
    transform: scale(1.03);
    opacity: 1;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

.cta-pop {
  animation: t-cta-pop var(--cta-pop-duration) var(--easing-bounce) both;
  will-change: transform, opacity;
}

/* --- item-015 : halo radial du hero, respiration en opacite seule ------- */
@keyframes t-halo-breathe {
  0%,
  100% {
    opacity: 0.55;
  }
  50% {
    opacity: 1;
  }
}

/* --- item-015 : parallaxe du « 130 » hero — translate pose en JS, filet de
   securite si le JS echoue ou si le mouvement reduit est demande. -------- */
[data-parallax] {
  will-change: auto;
}

@media (prefers-reduced-motion: reduce), (pointer: coarse) {
  [data-parallax] {
    transform: none !important;
  }
}

/* ===========================================================================
   10. RESPONSIVE (layer-theme-common.md §12)
   ======================================================================== */

@media (min-width: 641px) {
  :root {
    --grid-gap: 24px;
  }
}

@media (min-width: 769px) {
  :root {
    --container-padding-x: 40px;
    --glass-blur: 30px;
  }

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

@media (min-width: 1280px) {
  :root {
    --glass-blur: 36px;
  }
}

/* Le hover n'existe pas au doigt : pas d'effet hover-only (§12.3). */
@media (hover: none) {
  .t-card:hover {
    transform: none;
  }
}

/* ===========================================================================
   11. PREFERENCES UTILISATEUR
   ======================================================================== */

/* --- Mouvement reduit (§9.4 — non negociable) ---------------------------- */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  .reveal {
    opacity: 1;
    transform: none;
  }

  .t-card:hover {
    transform: none;
  }

  .t-cursor {
    display: none;
  }

  /* item-016 : si `prefers-reduced-motion` change apres coup (ou si le JS
     avait deja demarre juste avant), le curseur systeme redevient visible
     immediatement — jamais invisible sans repli. */
  html.gotb-cursor-active,
  html.gotb-cursor-active * {
    cursor: auto;
  }

  /* item-015 : filet de securite — meme si le JS tarde ou ne tourne pas,
     rien ne doit rester invisible ou en mouvement. */
  .word-mask__inner {
    opacity: 1;
    transform: none;
  }

  .btn--primary {
    animation: none;
  }

  .btn--primary:hover,
  .btn--primary:focus-visible {
    transform: none;
  }

  .cta-pop {
    animation: none;
  }

  .hero::after {
    animation: none;
  }
}

/* --- Contraste renforce ---------------------------------------------------
   item-022 : ce bloc redefinit le primaire et sa variante claire, il suit
   donc le changement de teinte. Les deux restent des declinaisons de
   #0165ba (H 207.6 / S ~98%), poussees d'un cran vers les extremes :
   `--color-primary` reprend la clarte de `--color-primary-dark` (23.5%,
   comme #0f3b6e le faisait avant) et `--color-primary-light` monte a 81%
   de clarte (4.65:1 -> 5.14:1 sur le bleu profond au pire pixel). Les
   fonds sombres ne sont PAS redefinis ici : le grain y est coupe
   (`--texture-opacity-inverse: 0`), ce qui ameliore deja tous les
   contrastes mesures au §1.
   ------------------------------------------------------------------------ */
@media (prefers-contrast: more) {
  :root {
    --color-text: #000000;
    --color-text-muted: #3b332a;
    --color-text-heading: #061423;
    --color-primary: #014177;
    --color-accent: #543f17;
    --color-text-inverse: #ffffff;
    --color-text-inverse-muted: #e3ecf7;
    --color-primary-light: #9fd2fe;
    --color-secondary: #e2cda9;
    --border-color: #8e8166;
    --border-color-light: #a4967c;
    --border-color-inverse: #8fa6c4;
    --texture-opacity: 0;
    --texture-opacity-inverse: 0;
    --border-width: 2px;
    /* Le verre translucide ruine le contraste : on l'opacifie. */
    --glass-bg: #fffcf5;
    --glass-blur: 0px;
    /* Doit rester STRICTEMENT egal a `--color-bg-deep-2` : c'est le verre
       opacifie des cartes posees sur le pied de page. item-022 l'a suivi. */
    --glass-bg-inverse: #002543;
  }

  .t-gradient-text {
    background: none;
    -webkit-text-fill-color: currentColor;
    color: var(--surface-text-heading);
  }

  .t-glow-text {
    text-shadow: none;
  }
}

/* --- Mode contraste force (Windows High Contrast) ------------------------- */
@media (forced-colors: active) {
  :root {
    --texture-opacity: 0;
    --texture-opacity-inverse: 0;
  }

  .section,
  .t-card,
  .t-glass,
  .t-frame {
    background: Canvas !important;
    background-image: none !important;
    color: CanvasText !important;
    border-color: CanvasText;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  .t-card,
  .t-glass,
  .t-frame {
    border: 1px solid CanvasText;
  }

  .btn {
    border: 1px solid ButtonText;
    background: ButtonFace !important;
    color: ButtonText !important;
    box-shadow: none;
    forced-color-adjust: none;
  }

  .btn:hover,
  .btn:focus-visible {
    background: Highlight !important;
    color: HighlightText !important;
  }

  a {
    color: LinkText;
  }

  :focus-visible {
    outline: 2px solid CanvasText;
  }

  .t-gradient-text {
    background: none;
    -webkit-text-fill-color: CanvasText;
    color: CanvasText;
  }

  .t-gradient-text--animated {
    animation: none;
  }

  .t-glow-text,
  .t-pulse,
  .btn--primary {
    text-shadow: none;
    box-shadow: none;
    animation: none;
  }

  .t-cursor,
  .section::before,
  .t-grain::before,
  .hero::after,
  .pricing-card--featured::before {
    display: none;
  }

  /* item-016 : meme garde-fou qu'en reduced-motion ci-dessus. */
  html.gotb-cursor-active,
  html.gotb-cursor-active * {
    cursor: auto;
  }

  /* item-015 : le masque de mots ne doit rien cacher en contraste force. */
  .word-mask__inner {
    opacity: 1;
    transform: none;
  }

  hr,
  .t-rule {
    background: none;
    border-top: 1px solid CanvasText;
  }

  /* Club des 130 : la gradation ne doit plus reposer sur la couleur (le filet
     d'or et la pastille du monogramme disparaissent) — restent la taille de
     typo, l'encadrement, le portrait et la densite de grille. */
  .club-entry--base,
  .club-entry--plus,
  .club-entry__portrait {
    background: Canvas !important;
    background-image: none !important;
    border: 1px solid CanvasText;
    box-shadow: none;
  }

  .club-entry__monogram {
    color: CanvasText;
  }

  .club-entry--plus::before,
  .club-legal li::before {
    background: CanvasText;
  }
}

/* ===========================================================================
   12. IMPRESSION
   ======================================================================== */

@media print {
  .section::before,
  .t-grain::before,
  .t-cursor,
  .hero::after,
  .pricing-card--featured::before {
    display: none;
  }

  .btn--primary {
    animation: none;
    box-shadow: none;
  }

  .section {
    padding-block: var(--space-md);
    background: none;
    color: var(--color-text);
  }

  .t-card,
  .t-glass {
    box-shadow: none;
    border: var(--border-width) solid var(--border-color-strong);
  }
}

/* ===========================================================================
   13. COMPOSANTS — PAGE D'ACCUEIL (BEM structurel, layer-site-common.md §2.4)
   Consomme uniquement les tokens du §1 et les primitives des §4 a 8. Aucune
   valeur hexadecimale en dur (regle d'or du fichier, voir l'entete).
   ======================================================================== */

/* --- En-tete ------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  min-height: 72px;
  display: flex;
  align-items: center;
  background: var(--glass-bg);
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
  border-bottom: var(--border-width) solid var(--border-color-light);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  width: 100%;
}

.site-header__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  color: var(--color-text-heading);
  text-decoration: none;
  font-family: var(--font-heading);
  font-weight: var(--font-weight-heading);
  font-size: var(--font-size-md);
}

.site-header__brand:hover {
  color: var(--color-primary);
}

.site-header__logo {
  width: 40px;
  height: 41px;
  flex-shrink: 0;
}

.site-header__nav {
  display: none;
}

/* item-021 : la liste portait son `display:flex` et son `gap` en style
   inline dans index.html, et heritait le gap du <nav> par `gap: inherit` —
   une dependance fragile et invisible depuis la feuille. Le CSS redevient
   la seule source de verite de la mise en page (layer-guidelines.md §1.2). */
.site-header__nav ul {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-header__nav a {
  position: relative;
  display: inline-block;
  padding-block: var(--space-2xs);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-strong);
  color: var(--color-text);
  text-decoration: none;
}

/* Soulignement d'orfevre : un filet or qui se deploie depuis le centre.
   `transform: scaleX()` seul (§9.1.4), jamais une largeur animee. */
.site-header__nav a::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: var(--border-width);
  background: var(--gradient-rule);
  transform: scaleX(0);
  transition: transform var(--transition-base) var(--easing-editorial);
}

.site-header__nav a:hover::after,
.site-header__nav a:focus-visible::after {
  transform: scaleX(1);
}

.site-header__nav a:hover {
  color: var(--color-primary);
}

.site-header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

/* item-021 : la barre n'affiche plus que 4 entrees (au lieu de 6) — elle
   tient donc des 900px, la ou il fallait 960px pour les loger. */
@media (min-width: 900px) {
  .site-header__nav {
    display: flex;
  }
}

/* --- Hero ------------------------------------------------------------------ */

.hero {
  padding-block-start: var(--space-2xl);
  text-align: center;
}

/* item-015 : halo radial du hero, calque dedie au-dessus du degrade statique
   (--gradient-hero, §4) et du grain (.section::before) — opacite seule
   animee (§9.1.4), neutralisee par le media query global (§11). */
.hero::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--gradient-hero);
  opacity: 0.55;
  animation: t-halo-breathe var(--halo-breathe-duration) var(--easing-default) infinite;
}

.hero__crest {
  width: 96px;
  height: 98px;
  margin: 0 auto var(--space-md);
}

.hero__title {
  margin-bottom: var(--space-md);
}

.hero__title .t-display {
  display: block;
  /* item-015 : largeur fixe pendant le compteur 0 -> 130 (bloc pleine largeur
     + tabular-nums deja poses par [data-counter], §1) — zero CLS. */
  min-width: 3ch;
}

.hero__kicker {
  display: block;
  font-family: var(--font-heading);
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-heading);
  line-height: var(--line-height-snug);
  color: var(--color-text-heading);
  margin-top: var(--space-xs);
}

.hero__lede {
  margin-inline: auto;
  margin-bottom: var(--space-lg);
  color: var(--surface-text);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-xl);
}

.hero__meta {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-sm) var(--space-lg);
  padding-top: var(--space-lg);
  border-top: var(--border-width) solid var(--border-color);
  margin-inline: auto;
  max-width: var(--container-max-width-narrow);
}

.hero__meta-item {
  font-size: var(--font-size-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--surface-text-muted);
}

.hero__meta-item strong {
  display: block;
  font-family: var(--font-heading);
  font-size: var(--font-size-md);
  text-transform: none;
  letter-spacing: var(--tracking-normal);
  color: var(--color-text-heading);
}

/* --- Visuel encadre (hero + panneau du match) -------------------------------
   Cadre photographique reutilisable : bordure fine, angles arrondis, ombre
   douce — meme traitement que .t-split__figure, pour une seule image centree
   plutot qu'une colonne de split texte/media. */

.t-media-frame {
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius-lg);
  border: var(--border-width) solid var(--surface-border);
  box-shadow: var(--shadow-md);
}

.t-media-frame__img {
  display: block;
  width: 100%;
  height: auto;
}

.t-media-frame figcaption {
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-xs);
  color: var(--surface-text-muted);
  background: var(--surface-card-bg);
}

.hero__visual {
  max-width: 640px;
  margin: 0 auto var(--space-xl);
}

/* --- En-tetes de section ---------------------------------------------------- */

.section__head {
  max-width: var(--container-max-width-narrow);
  margin-inline: auto;
  margin-bottom: var(--space-xl);
  text-align: center;
}

.section__head--split {
  text-align: left;
  margin-inline: 0;
  max-width: none;
}

/* --- Grille generique -------------------------------------------------------- */

.t-grid {
  display: grid;
  gap: var(--grid-gap);
  grid-template-columns: 1fr;
}

.t-split {
  display: grid;
  gap: var(--space-xl);
  align-items: center;
}

@media (min-width: 641px) {
  .t-grid--2 {
    grid-template-columns: repeat(2, 1fr);
  }
  .sizes-grid {
    grid-template-columns: repeat(5, 1fr);
  }
}

@media (min-width: 769px) {
  .t-grid--3 {
    grid-template-columns: repeat(3, 1fr);
  }
  .t-grid--4 {
    grid-template-columns: repeat(4, 1fr);
  }
  /* item-031 : le carre pose en item-006/026 (voir commentaire ci-dessous)
     laissait encore ~188px de blanc de chaque cote du texte a 1380px — le
     client juge le point non resolu. Deux leviers combines plutot qu'un
     ratio encore plus ponce sur la seule image : la grille se desequilibre
     en faveur du texte (60/40 au lieu de 50/50) ET le ratio de la photo
     s'elargit en paysage (4/3 au lieu du carre) — chaque regle cible la
     colonne qui porte reellement le texte selon la variante (`--text-media` :
     texte en 1re position ; `--media-text` : photo en 1re position), donc
     la photo est toujours la colonne etroite, quel que soit son cote. */
  .t-split--text-media {
    grid-template-columns: 3fr 2fr;
  }
  .t-split--media-text {
    grid-template-columns: 2fr 3fr;
  }
  .t-split--text-media .t-split__media {
    order: 2;
  }

  /* item-006/026 : #concept et #maillots sont cote-a-cote ici, et leurs
     photos (387x516, 480x758) sont nettement plus hautes a largeur de
     colonne egale que les 2-3 paragraphes en face — jusqu'a environ 850px de
     photo contre 150px de texte a 1380px, d'ou les « grandes zones blanches »
     du retour client. Le texte est deja centre verticalement
     (`align-items: center` du `.t-split`, ci-dessus) : ce qui manquait,
     c'etait de brider la photo pour que ce centrage n'ait plus une si grande
     hauteur a repartir. La regle cible l'`img`, pas `.t-split__figure` : la
     figure porte aussi la `figcaption`, qui doit garder sa hauteur naturelle
     sous la photo plutot que de se faire comprimer dans un cadre englobant
     les deux. item-026 avait borne ce ratio en carre (1/1, convention de
     `.pricing-card__visual`) : insuffisant seul, voir item-031 ci-dessus qui
     l'elargit en 4/3 paysage, en complement du desequilibrage de grille.
     Scope volontairement limite a cette rupture : en dessous de 769px les
     deux blocs sont empiles (texte au-dessus ou en dessous de l'image,
     jamais a cote), donc aucune « zone a cote » possible et le cadrage
     portrait d'origine reste preferable tel quel. */
  .t-split__figure img {
    aspect-ratio: 4 / 3;
    height: auto;
  }

  /* `#maillots` empile desormais 2 `.t-split__figure` dans la meme colonne
     media (macro du blason, sous le plan d'epaule) : au 4/3 commun ci-dessus,
     les 2 photos cumulees depassent largement la colonne texte (courte dans
     cette section), rouvrant a 1380px la zone blanche que item-026/031 ont
     mis 2 passes a resorber sur `#concept` -- mesure en navigateur reel avant
     d'intervenir (voir journal), pas suppose. Seul le second visuel est
     reduit, en 16/9 plutot qu'en 4/3 -- assez pour revenir a un ecart du
     meme ordre que celui deja accepte sur `#concept` (~137px/cote a 1380px),
     pas pour l'annuler (physiquement impossible sans rendre la photo
     illisible : la 1re figure seule occupe deja quasiment toute la hauteur
     du texte a 1380px). Meme specificite que la regle precedente (1 classe +
     element) : place apres elle dans le fichier pour trancher par l'ordre,
     doctrine deja suivie pour le contraste du CTA (item-021 §2) et le label
     taille (item-029). */
  .t-split__figure--panoramic img {
    aspect-ratio: 16 / 9;
  }
}

/* --- Cartes « feature » (concept, maillots) ---------------------------------- */

.feature__index {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  margin-bottom: var(--space-sm);
  border-radius: var(--radius-full);
  background: rgba(var(--color-primary-rgb), 0.1);
  color: var(--surface-accent);
  font-family: var(--font-heading);
  font-weight: var(--font-weight-heading);
  font-size: var(--font-size-md);
}

.t-surface--deep .feature__index,
.t-surface--night .feature__index {
  background: rgba(var(--rgb-white), 0.12);
}

.feature {
  padding: var(--space-lg);
}

.feature__title {
  font-size: var(--font-size-lg);
  margin-bottom: var(--space-2xs);
}

.feature__text {
  margin-bottom: 0;
  color: var(--surface-text-muted);
}

/* --- Duo image / texte (concept) --------------------------------------------- */

/* item-031 : `.t-split__media` est un `<figure>` — sans ce reset, la marge
   UA par defaut (16px 40px, cf. feuille agent) retranchait silencieusement
   80px de largeur et 32px de hauteur a l'image dans sa colonne de grille,
   jamais detecte faute de navigateur reel lors des items 025/026/030. Trouve
   en verifiant au rendu (Chrome 156.0.8078.47) : `.t-split__figure`, juste
   en dessous, avait deja ce reset — il manquait seulement sur son parent. */
.t-split__media {
  display: grid;
  gap: var(--space-sm);
  margin: 0;
}

.t-split__figure {
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius-lg);
  border: var(--border-width) solid var(--surface-border);
  box-shadow: var(--shadow-md);
}

.t-split__figure img {
  width: 100%;
  object-fit: cover;
}

.t-split__figure figcaption {
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-xs);
  color: var(--surface-text-muted);
  background: var(--surface-card-bg);
}

/* --- Tailles disponibles ------------------------------------------------------ */

.sizes-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-sm);
  margin-bottom: var(--space-lg);
}

.sizes-grid__item {
  padding: var(--space-md) var(--space-sm);
  text-align: center;
}

.sizes-grid__size {
  display: block;
  font-family: var(--font-heading);
  font-size: var(--font-size-xl);
  color: var(--color-text-heading);
}

.sizes-grid__count {
  display: block;
  /* item-015 : largeur fixe pendant le compteur (ex. "50") — zero CLS. */
  min-width: 2.4ch;
  font-size: var(--font-size-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--surface-text-muted);
}

/* item-018 : rarete visible des tailles S/XXL (5 exemplaires chacune) —
   jamais que la couleur : un filet dore + un intitule texte ("Rare"),
   reutilise du .t-eyebrow deja verifie sur ce fond (surface--cream). */
.sizes-grid__item--rare {
  border-color: var(--surface-accent);
}

.sizes-grid__rarity {
  display: block;
  margin: var(--space-2xs) 0 0;
}

/* --- Paliers (pricing) -------------------------------------------------------- */

.pricing-grid {
  display: grid;
  gap: var(--space-sm);
  grid-template-columns: 1fr;
}

@media (min-width: 641px) {
  .pricing-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1024px) {
  .pricing-grid {
    gap: var(--space-md);
    grid-template-columns: repeat(4, 1fr);
  }
}

.pricing-card {
  display: flex;
  flex-direction: column;
  padding: var(--space-lg);
}

.pricing-card--featured {
  position: relative;
  border-color: transparent;
  border-width: var(--border-width-thick);
}

/* item-015 : bordure en degrade (mask-composite, layer-theme-common.md
   §6.1) — remplace le filet uni par un ruban or anime au rythme du
   shimmer deja pose au §9. */
.pricing-card--featured::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: var(--border-width-thick);
  background: var(--gradient-gold);
  background-size: 200% auto;
  animation: t-shimmer var(--shimmer-duration) linear infinite;
  -webkit-mask:
    linear-gradient(var(--mask-paint) 0 0) content-box,
    linear-gradient(var(--mask-paint) 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

/* item-005 (demande) : emplacement reserve au visuel de chaque palier. Le
   client produit ces visuels et les enverra plus tard ("on les produit et
   on t'envoie") : seule la structure d'accueil est posee ici, avec un
   placeholder « ? » conforme a `layer-site-common.md` §1.4 (meme convention
   que le placeholder deja en place pour la photo Montriond FC 1896, §2) —
   jamais un fichier image invente. `.t-media-frame` est reemploye tel quel
   (cadre, overflow masque, filet metallique statique du §19.3) plutot que de
   recreer une variante de cadre : seule la mise au ratio est propre a cette
   carte. `aspect-ratio` fixe la hauteur du visuel independamment du texte
   qui suit : les 1 a 3 entrees de `.pricing-card__perks` (variable selon le
   palier) n'influencent jamais sa taille, et les 4 cartes d'une meme ligne
   de `.pricing-grid` partageant la meme largeur de colonne a chaque point de
   rupture, elles partagent aussi la meme hauteur de visuel. item-035 : passe
   de 1/1 (placeholder carre d'item-025) a 16/9, les 4 visuels reels livres en
   item-034 etant en 16/9 — un cadre carre + `object-fit: cover` en aurait
   rogne ~44% de la largeur, coupant les premiere/derniere lettres de
   « SUPPORTER »/« MONTRIOND ». */
.pricing-card__visual {
  aspect-ratio: 16 / 9;
  margin-bottom: var(--space-sm);
}

.pricing-card__visual img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* `.t-media-frame` porte sa propre ombre (pensee pour un cadre isole dans un
   `.t-split`) : nichee dans une carte qui a deja la sienne (`.t-card`), elle
   double l'effet de relief. Neutralisee ici sans toucher a la regle source,
   qui reste correcte pour ses autres usages (hero, concept). */
.pricing-card__visual.t-media-frame {
  box-shadow: none;
}

.pricing-card__badge {
  align-self: flex-start;
  margin-bottom: var(--space-sm);
}

.pricing-card__name {
  font-size: var(--font-size-lg);
  margin-bottom: var(--space-3xs);
}

.pricing-card__price {
  font-family: var(--font-body);
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-2xl);
  color: var(--surface-accent);
  margin-bottom: var(--space-3xs);
}

.pricing-card__price sup {
  font-size: var(--font-size-sm);
}

.pricing-card__tickets {
  font-size: var(--font-size-sm);
  color: var(--surface-text-muted);
  margin-bottom: var(--space-3xs);
}

.pricing-card__unit {
  font-size: var(--font-size-xs);
  color: var(--surface-text-muted);
  margin-bottom: var(--space-sm);
}

.pricing-card__perks {
  list-style: none;
  margin: 0 0 var(--space-sm);
  padding: 0;
  flex-grow: 1;
}

.pricing-card__perks li {
  position: relative;
  padding-left: var(--space-md);
  margin-bottom: var(--space-2xs);
  font-size: var(--font-size-sm);
}

.pricing-card__perks li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.6em;
  width: 8px;
  height: var(--border-width);
  background: var(--surface-accent);
}

.pricing-card__cta {
  margin-top: var(--space-sm);
}

/* --- Mecanique : panneau du match + chronologie -------------------------------- */

.match-panel {
  max-width: 640px;
  margin: 0 auto var(--space-xl);
}

.timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: timeline;
  display: grid;
  gap: var(--space-md);
}

@media (min-width: 769px) {
  .timeline {
    grid-template-columns: repeat(4, 1fr);
  }
}

.timeline__item {
  counter-increment: timeline;
  padding: var(--space-lg) var(--space-md);
  position: relative;
}

.timeline__item::before {
  content: counter(timeline, decimal-leading-zero);
  display: block;
  font-family: var(--font-body);
  font-variant-numeric: tabular-nums;
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-sm);
  letter-spacing: var(--tracking-wide);
  color: var(--surface-accent);
  margin-bottom: var(--space-xs);
}

.timeline__item h3 {
  font-size: var(--font-size-md);
  margin-bottom: var(--space-3xs);
}

.timeline__item p {
  margin-bottom: 0;
  font-size: var(--font-size-sm);
  color: var(--surface-text-muted);
}

/* --- Club des 130 ---------------------------------------------------------------
   La section signature de l'operation. La gradation de visibilite demandee par
   le client (Supporter/Montriond -> Tuiliere -> 1896) est traduite sur QUATRE
   axes simultanes, pour qu'elle se lise sans legende :

     axe              degre 1 (base)     degre 2 (plus)      degre 3 (max)
     taille de typo   --font-size-sm     --font-size-md      --font-size-lg
     encadrement      pastille, 1 filet  carte + filet d'or  double filet (.t-frame)
     portrait 1:1     non                non                 oui
     densite grille   flux dense         2 -> 3 -> 5 col     2 -> 3 -> 4 col

   Les specimens de l'echelle (.club-step__specimen) reutilisent exactement les
   classes des entrees du tableau d'honneur : la legende EST le composant, elle
   ne peut donc pas deriver de ce qui est reellement affiche.
   -------------------------------------------------------------------------- */

/* Les deux « certificats » de la promesse : « a vie », et « tous les
   participants » (pas seulement les 130 gagnants). */
.club-promise {
  display: grid;
  gap: var(--space-md);
  margin-bottom: var(--space-xl);
}

@media (min-width: 769px) {
  .club-promise {
    grid-template-columns: 1fr 1fr;
  }
}

.club-promise__item {
  background: var(--surface-card-bg);
  padding: var(--space-lg);
}

.club-promise__item p:last-child {
  margin-bottom: 0;
}

/* --- L'echelle des trois degres ------------------------------------------- */

.club-ladder__title {
  text-align: center;
  font-size: var(--font-size-xl);
  margin-bottom: var(--space-lg);
}

.club-ladder {
  list-style: none;
  margin: 0 0 var(--space-3xl);
  padding: 0;
  display: grid;
  gap: var(--space-md);
}

@media (min-width: 769px) {
  .club-ladder {
    grid-template-columns: repeat(3, 1fr);
  }
}

.club-step {
  display: flex;
  flex-direction: column;
  padding: var(--space-lg) var(--space-md);
}

.club-step__level {
  margin-bottom: var(--space-2xs);
}

.club-step__tiers {
  font-size: var(--font-size-lg);
  margin-bottom: var(--space-2xs);
}

/* Pas de flex-grow ici : pousser le specimen en pied de carte alignerait trois
   echantillons de hauteurs tres differentes sur leur base, ce qui ouvre un
   trou au-dessus des deux premiers. On laisse le contenu couler, les trois
   libelles « Affichage » se retrouvent alors quasiment sur la meme ligne. */
.club-step__gain {
  font-size: var(--font-size-sm);
  color: var(--surface-text-muted);
  margin-bottom: var(--space-md);
}

.club-step__specimen-label {
  font-size: var(--font-size-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  margin-bottom: var(--space-2xs);
}

/* Le specimen est pose sur le fond de section, en filet pointille : il se lit
   comme un echantillon, jamais comme un vrai membre. */
.club-step__specimen {
  display: flex;
  justify-content: center;
  padding: var(--space-md) var(--space-sm);
  background: var(--surface-bg);
  border: var(--border-width) dashed var(--surface-border);
  border-radius: var(--radius-sm);
}

.club-step__specimen .club-entry--max {
  width: 150px;
}

.club-step__specimen .club-entry--plus {
  width: 100%;
  max-width: 240px;
}

/* --- Les entrees : les trois degres de visibilite ------------------------- */

.club-entry {
  /* Empeche un nom long de faire deborder sa colonne de grille. */
  min-width: 0;
}

.club-entry__name {
  display: block;
  color: var(--surface-text-heading);
  letter-spacing: var(--tracking-tight);
  text-wrap: balance;
}

/* Degre 1 — Supporter & Montriond : pastille dense, typo courante. */
.club-entry--base {
  display: inline-flex;
  align-items: center;
  padding: var(--space-3xs) var(--space-sm);
  background: var(--surface-card-bg);
  border: var(--border-width) solid var(--surface-border);
  border-radius: var(--radius-full);
  box-shadow: var(--shadow-sm);
}

.club-entry--base .club-entry__name {
  display: inline;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-body);
}

/* Degre 2 — Tuiliere : carte propre, filet d'or lateral, typo plus grande. */
.club-entry--plus {
  position: relative;
  padding: var(--space-sm) var(--space-md);
  padding-inline-start: calc(var(--space-md) + var(--space-3xs));
  background: var(--surface-card-bg);
  border: var(--border-width) solid var(--surface-border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
}

.club-entry--plus::before {
  content: '';
  position: absolute;
  inset-block: var(--space-2xs);
  inset-inline-start: 0;
  width: 3px;
  border-radius: var(--radius-full);
  background: var(--surface-accent);
}

.club-entry--plus .club-entry__name {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-strong);
}

/* Degre 3 — 1896 : double filet d'or (.t-frame), portrait 1:1, typo maximale. */
.club-entry--max {
  /* Reassigne le double filet de .t-frame en or profond. On passe par
     --surface-accent (et non --color-secondary-dark, mesure a 2.99:1 sur le
     creme, sous le plancher 3:1 des contours) : 5.86:1 sur la carte et 5.29:1
     sur le creme, et le filet suit la surface si la section change de fond. */
  --surface-border: var(--surface-accent);
  background: var(--surface-card-bg);
  box-shadow: var(--shadow-sm);
  text-align: center;
}

.club-entry--max .club-entry__name {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-strong);
  line-height: var(--line-height-heading);
}

.club-entry__tier {
  margin-bottom: var(--space-3xs);
}

/* Portrait 1:1 — reserve au degre 3, et seulement si le participant l'a
   explicitement consenti. A defaut, son initiale tient la place : pastille or,
   lettres a l'encre — le couple mesure de .t-surface--gold (5.85:1 au stop le
   plus sombre du degrade, 9.05:1 au plus clair). */
.club-entry__portrait {
  width: 100%;
  aspect-ratio: 1 / 1;
  margin-bottom: var(--space-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background-image: var(--gradient-gold);
}

.club-entry__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.club-entry__monogram {
  font-family: var(--font-body);
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--tracking-wide);
  color: var(--color-text);
}

/* --- Le tableau d'honneur ------------------------------------------------- */

.club-roll {
  border-top: var(--border-width) solid var(--surface-border);
  padding-top: var(--space-xl);
}

.club-roll__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-xs);
  margin-bottom: var(--space-lg);
}

.club-roll__title {
  margin: 0;
  font-size: var(--font-size-xl);
}

.club-roll__total {
  margin: 0;
  font-size: var(--font-size-sm);
}

.club-roll__empty {
  max-width: var(--measure-narrow);
  margin: 0 auto;
  padding: var(--space-xl) var(--space-md);
  text-align: center;
  color: var(--surface-text-muted);
  border: var(--border-width) dashed var(--surface-border);
  border-radius: var(--radius-md);
}

/* L'etat vide suit le phasing temporel pose en tete d'index.html (item-004) :
   avant l'ouverture on annonce la date, apres on annonce la publication. */
.club-roll__when-live {
  display: none;
}

html:not([data-sales-phase='pre']) .club-roll__when-pre {
  display: none;
}

html:not([data-sales-phase='pre']) .club-roll__when-live {
  display: inline;
}

/* Bandeau de l'apercu de recette (?__=) — jamais visible en production. */
.club-roll__demo {
  max-width: none;
  margin: 0 0 var(--space-lg);
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-sm);
  color: var(--surface-text-muted);
  border: var(--border-width) dashed var(--surface-border-strong);
  border-radius: var(--radius-sm);
}

.club-group {
  margin-bottom: var(--space-2xl);
}

.club-group:last-child {
  margin-bottom: 0;
}

.club-group__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2xs) var(--space-sm);
  margin-bottom: var(--space-md);
  padding-bottom: var(--space-2xs);
  border-bottom: var(--border-width) solid var(--surface-border);
}

.club-group__label {
  margin: 0;
  font-size: var(--font-size-md);
}

.club-group__gain,
.club-group__count {
  margin: 0;
}

.club-group__count {
  margin-inline-start: auto;
  font-size: var(--font-size-sm);
}

.club-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* La densite de grille est le quatrieme axe de la gradation : plus la
   visibilite est forte, plus l'entree occupe de place. */
.club-list--max {
  display: grid;
  gap: var(--space-md);
  grid-template-columns: repeat(2, 1fr);
}

.club-list--plus {
  display: grid;
  gap: var(--space-sm);
  grid-template-columns: repeat(2, 1fr);
}

.club-list--base {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
}

@media (min-width: 641px) {
  .club-list--max,
  .club-list--plus {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (min-width: 1024px) {
  .club-list--max {
    grid-template-columns: repeat(4, 1fr);
  }

  .club-list--plus {
    grid-template-columns: repeat(5, 1fr);
  }
}

/* Mentions legales et doctrine de publication. */
.club-legal {
  list-style: none;
  margin: var(--space-2xl) 0 0;
  padding: var(--space-lg) 0 0;
  border-top: var(--border-width) solid var(--surface-border);
  display: grid;
  gap: var(--space-2xs);
  font-size: var(--font-size-sm);
}

.club-legal li {
  position: relative;
  max-width: var(--measure);
  padding-inline-start: var(--space-md);
}

.club-legal li::before {
  content: '';
  position: absolute;
  inset-inline-start: 0;
  top: 0.62em;
  width: 8px;
  height: var(--border-width);
  background: var(--surface-accent);
}

/* --- CTA final ------------------------------------------------------------------- */

.cta-final {
  text-align: center;
}

.cta-final__title {
  margin-bottom: var(--space-sm);
}

.cta-final__lede {
  margin-inline: auto;
  margin-bottom: var(--space-lg);
}

/* --- FAQ (accordeon natif <details>, sans JS) ------------------------------------ */

.faq {
  max-width: var(--container-max-width-narrow);
  margin-inline: auto;
  display: grid;
  gap: var(--space-xs);
}

.faq__item {
  padding: 0;
  overflow: hidden;
}

.faq__item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  cursor: pointer;
  font-family: var(--font-heading);
  font-size: var(--font-size-md);
  color: var(--surface-text-heading);
  list-style: none;
}

.faq__item summary::-webkit-details-marker {
  display: none;
}

.faq__item summary::after {
  content: '+';
  flex-shrink: 0;
  font-family: var(--font-body);
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-strong);
  color: var(--surface-accent);
  transition: transform var(--transition-base) var(--easing-default);
}

.faq__item[open] summary::after {
  transform: rotate(45deg);
}

.faq__item p {
  margin: 0;
  padding: 0 var(--space-md) var(--space-md);
  color: var(--surface-text-muted);
}

/* --- Pied de page ------------------------------------------------------------------ */

.site-footer__grid {
  display: grid;
  gap: var(--space-xl);
  margin-bottom: var(--space-xl);
}

@media (min-width: 769px) {
  .site-footer__grid {
    grid-template-columns: 1.3fr 1fr 1fr;
  }
}

.site-footer__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-sm);
  color: var(--surface-text-heading);
  text-decoration: none;
  font-family: var(--font-heading);
  font-size: var(--font-size-lg);
}

.site-footer__logo {
  width: 44px;
  height: 45px;
}

.site-footer h3 {
  font-size: var(--font-size-sm);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--surface-text);
  margin-bottom: var(--space-sm);
}

.site-footer address {
  font-style: normal;
}

.site-footer__nav {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2xs);
}

.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding-top: var(--space-lg);
  border-top: var(--border-width) solid var(--surface-border);
}

/* La marge basse par defaut des paragraphes desequilibrerait la ligne. */
.site-footer__bottom p {
  margin: 0;
}

.badge-gotombola {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3xs);
  padding: var(--space-3xs) var(--space-sm);
  border: var(--border-width) solid var(--surface-border);
  border-radius: var(--radius-full);
  font-size: var(--font-size-xs);
  letter-spacing: var(--tracking-wide);
  text-decoration: none;
  color: var(--surface-text-muted);
}

.badge-gotombola:hover {
  color: var(--surface-text-heading);
  border-color: var(--surface-border-strong);
}

@media (hover: none) {
  .pricing-card--featured {
    border-width: var(--border-width);
  }
}

/* ===========================================================================
   14. COMPOSANTS — DOCUMENT JURIDIQUE (/terms-and-conditions/)
   ---------------------------------------------------------------------------
   Gabarit documentaire : sommaire collant, articles numerotes par compteur
   CSS, listes de definitions, encarts, badge « a completer ». 100 % CSS —
   la page du reglement n'execute AUCUN script (CSP `script-src 'none'`),
   le repli du sommaire et la mise en evidence de l'article cible reposent
   sur <details> et `:target`.

   La numerotation « Art. n » est produite par un compteur : inserer ou
   deplacer un <article> renumerote tout le document, sans toucher une seule
   ancre. Le sommaire, lui, est un <ol> : ses numeros suivent le meme ordre.

   Comme partout : aucune valeur hexadecimale, uniquement des tokens du §1
   et les primitives des §4 a 8. Les regles `forced-colors` et `print`
   propres au gabarit sont groupees en fin de section plutot que dans les
   §11/§12 : elles viennent apres eux dans le fichier, donc elles priment a
   specificite egale, et le composant reste lisible d'un seul tenant.
   ======================================================================== */

/* --- Chapeau du document -------------------------------------------------- */

/* Le filet `.t-rule` ferme deja le chapeau : la pleine respiration de section
   en dessous eloignerait inutilement le premier article. */
.doc-hero {
  padding-block-end: var(--space-lg);
}

.doc-hero__sub {
  margin-bottom: var(--space-2xs);
  color: var(--surface-text-muted);
  font-size: var(--font-size-sm);
}

.doc-hero__title {
  margin-bottom: var(--space-md);
}

.doc-hero__lead {
  max-width: var(--measure);
  margin-bottom: var(--space-xl);
}

/* Les faits du document (version, dates, operation) : une fiche d'etat civil
   plutot qu'un paragraphe — c'est ce qu'on vient verifier en premier. */
.doc-facts {
  display: grid;
  gap: var(--space-md);
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  margin: 0 0 var(--space-xl);
}

.doc-fact {
  padding-top: var(--space-2xs);
  border-top: var(--border-width-thick) solid var(--surface-accent);
}

.doc-fact__key {
  margin: 0;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-strong);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--surface-text-muted);
}

.doc-fact__val {
  margin: var(--space-3xs) 0 0;
  font-family: var(--font-heading);
  font-size: var(--font-size-md);
  color: var(--surface-text-heading);
}

/* --- Mise en page : sommaire + corps -------------------------------------- */

.doc-layout {
  display: grid;
  gap: var(--space-xl);
}

@media (min-width: 1024px) {
  .doc-layout {
    grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
    gap: var(--space-2xl);
    align-items: start;
  }

  /* 72px = hauteur de l'en-tete collant (.site-header). */
  .doc-toc {
    position: sticky;
    top: calc(72px + var(--space-md));
    max-height: calc(100vh - 72px - var(--space-2xl));
    overflow-y: auto;
  }
}

/* --- Sommaire -------------------------------------------------------------- */

.doc-toc__panel {
  background: var(--surface-card-bg);
  border: var(--border-width) solid var(--surface-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: var(--space-2xs) var(--space-md) var(--space-md);
}

.doc-toc__title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  min-height: 44px; /* cible tactile WCAG 2.5.5 */
  cursor: pointer;
  list-style: none;
  font-family: var(--font-heading);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-strong);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--surface-text-heading);
}

.doc-toc__title::-webkit-details-marker {
  display: none;
}

.doc-toc__title::after {
  content: '+';
  flex-shrink: 0;
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-strong);
  color: var(--surface-accent);
  transition: transform var(--transition-base) var(--easing-default);
}

.doc-toc__panel[open] .doc-toc__title::after {
  transform: rotate(45deg);
}

.doc-toc__list {
  display: grid;
  gap: var(--space-2xs);
  margin: var(--space-2xs) 0 0;
  padding-inline-start: var(--space-lg);
  font-size: var(--font-size-sm);
}

.doc-toc__item {
  padding-inline-start: var(--space-3xs);
}

.doc-toc__item::marker {
  color: var(--surface-accent);
  font-weight: var(--font-weight-strong);
  font-variant-numeric: tabular-nums;
}

.doc-toc__link {
  color: var(--surface-text);
  text-decoration: none;
  border-bottom: var(--border-width) solid transparent;
}

.doc-toc__link:hover {
  color: var(--surface-link);
  border-bottom-color: var(--surface-accent);
}

/* Entre 641 et 1023 px le sommaire est deplie au-dessus du texte : deux
   colonnes evitent de reporter le premier article sous la ligne de flottaison. */
@media (min-width: 641px) and (max-width: 1023px) {
  .doc-toc__list {
    grid-template-columns: 1fr 1fr;
  }
}

/* --- Articles -------------------------------------------------------------- */

.doc-body {
  counter-reset: doc-art;
  display: grid;
  gap: var(--space-2xl);
}

.doc-article {
  counter-increment: doc-art;
  scroll-margin-top: calc(72px + var(--space-md));
}

.doc-article__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2xs) var(--space-sm);
  margin-bottom: var(--space-md);
  padding-bottom: var(--space-2xs);
  border-bottom: var(--border-width) solid var(--surface-border);
}

.doc-article__num {
  margin: 0;
  flex: 0 0 auto;
  font-family: var(--font-heading);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-strong);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--surface-accent);
}

.doc-article__num::before {
  content: 'Art. ' counter(doc-art);
}

.doc-article__title {
  flex: 1 1 12ch;
  margin: 0;
  font-size: var(--font-size-xl);
}

.doc-article__anchor {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  margin-block: calc(var(--space-sm) * -1);
  text-decoration: none;
  color: var(--surface-text-muted);
  opacity: 0;
  transition: opacity var(--transition-fast) var(--easing-default);
}

.doc-article:hover .doc-article__anchor,
.doc-article__anchor:focus-visible {
  opacity: 1;
}

/* Au doigt, le survol n'existe pas (§12.3) : l'ancre reste visible. */
@media (hover: none) {
  .doc-article__anchor {
    opacity: 1;
  }
}

/* Article vise par une ancre : le filet de titre passe a l'or et s'epaissit —
   repere non chromatique (epaisseur) double d'un repere chromatique. */
.doc-article:target .doc-article__head {
  border-bottom-width: var(--border-width-thick);
  border-bottom-color: var(--surface-accent);
}

.doc-article p,
.doc-article li {
  max-width: var(--measure);
}

/* --- Primitives de contenu ------------------------------------------------- */

/* Liste de definitions : les faits opposables (dates, montants, references)
   sortent du texte courant et se relisent d'un coup d'oeil. */
.doc-dl {
  display: grid;
  gap: var(--space-2xs) var(--space-md);
  margin: var(--space-md) 0;
  padding: var(--space-md);
  background: var(--surface-card-bg);
  border: var(--border-width) solid var(--surface-border);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-sm);
}

@media (min-width: 641px) {
  .doc-dl {
    grid-template-columns: minmax(150px, 13rem) minmax(0, 1fr);
  }
}

.doc-dl dt {
  margin: 0;
  font-weight: var(--font-weight-strong);
  color: var(--surface-text-heading);
}

.doc-dl dd {
  margin: 0 0 var(--space-2xs);
}

@media (min-width: 641px) {
  .doc-dl dd {
    margin-bottom: 0;
  }
}

.doc-list {
  display: grid;
  gap: var(--space-2xs);
  margin: var(--space-md) 0;
  padding-inline-start: var(--space-lg);
}

.doc-list li::marker {
  color: var(--surface-accent);
  font-weight: var(--font-weight-strong);
}

.doc-list--numbered li::marker {
  font-variant-numeric: tabular-nums;
}

.doc-callout {
  margin: var(--space-md) 0;
  padding: var(--space-md);
  background: var(--surface-card-bg);
  border: var(--border-width) solid var(--surface-border);
  border-inline-start: var(--border-width-thick) solid var(--surface-accent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.doc-callout > :last-child {
  margin-bottom: 0;
}

/* Donnee non encore fournie par le client : badge explicite, jamais un
   placeholder credible qui se ferait passer pour une information. */
.doc-todo {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--space-2xs);
  border: var(--border-width) solid var(--color-warning);
  border-radius: var(--radius-full);
  color: var(--color-warning);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-strong);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  white-space: nowrap;
}

/* Dernier enfant de la grille `.doc-body` : l'ecart au dernier article vient
   deja du `gap` de la grille, d'ou l'absence de marge haute ici. */
.doc-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  padding-top: var(--space-lg);
  border-top: var(--border-width) solid var(--surface-border);
}

/* Bloc de version : la seule signature qui compte sur un document juridique.
   Toute modification du texte doit incrementer version et date (et les metas
   `gotombola:doc-*` du <head>). */
.doc-version {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs) var(--space-md);
  margin: 0 0 var(--space-xl);
  padding-bottom: var(--space-md);
  border-bottom: var(--border-width) solid var(--surface-border);
  font-size: var(--font-size-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--surface-text-muted);
}

/* --- Contraste force ------------------------------------------------------- */

@media (forced-colors: active) {
  .doc-toc__panel,
  .doc-dl,
  .doc-callout {
    background: Canvas !important;
    border: 1px solid CanvasText;
    box-shadow: none;
  }

  .doc-fact {
    border-top-color: CanvasText;
  }

  .doc-article__num,
  .doc-toc__title::after,
  .doc-toc__item::marker,
  .doc-list li::marker {
    color: CanvasText;
  }

  /* L'article cible ne peut plus se signaler par la couleur : seule
     l'epaisseur du filet porte l'information. */
  .doc-article:target .doc-article__head {
    border-bottom-color: CanvasText;
  }

  .doc-todo {
    color: CanvasText;
    border-color: CanvasText;
    forced-color-adjust: none;
  }
}

/* --- Impression ------------------------------------------------------------ */

@media print {
  /* Sur le papier, le sommaire collant, l'en-tete et les boutons n'ont plus
     d'objet ; le texte des articles, lui, doit passer en entier. */
  .site-header,
  .doc-toc,
  .doc-actions {
    display: none;
  }

  .doc-layout {
    display: block;
  }

  .doc-body {
    display: block;
  }

  .doc-article {
    margin-bottom: var(--space-lg);
  }

  .doc-article__head {
    break-after: avoid;
  }

  .doc-dl,
  .doc-callout,
  .doc-fact {
    break-inside: avoid;
    box-shadow: none;
  }

  .doc-article__anchor {
    display: none;
  }
}

/* ===========================================================================
   15. COMPOSANTS — PAGES PRIVEES (/private/sale.html, /private/ticket.html,
   /private/presult.html — item-011)
   ---------------------------------------------------------------------------
   Pages a lien prive : noindex/nofollow, jamais liees depuis la navigation
   du site, absentes du sitemap — accessibles uniquement par le lien recu
   par e-mail (voir docs/SITE.md). Tout le contenu central est rendu par un
   web component du widget (<gotb-sale-detail>, <gotb-ticket-detail>,
   <gotb-presult-detail> — public/assets/widget/*-detail.*.js, charges en
   trois temps : bootstrap stable -> manifest -> payload+CSS avec SRI).
   Cette section ne pose que l'arche (zone de contenu, squelette d'attente
   avant upgrade du Custom Element, message no-script) et reutilise les
   tokens/primitives des sections precedentes (§1 a §8, dont `t-shimmer`
   du §9) — aucune nouvelle couleur hexadecimale. Le `.site-header` et le
   `.site-footer` de la page d'accueil sont repris tels quels (§13).
   ======================================================================== */

.private-main {
  padding-block: var(--space-2xl) var(--space-3xl);
}

.private-intro {
  max-width: var(--measure);
  margin: 0 auto var(--space-xl);
  text-align: center;
}

.private-slot {
  max-width: 640px;
  margin-inline: auto;
}

/* Squelette d'attente : avant que le navigateur n'ait defini l'element
   (upgrade asynchrone, apres fetch du manifest + payload), un squelette
   minimal evite tout flash de contenu vide (< 100 ms visés). */
gotb-sale-detail:not(:defined),
gotb-ticket-detail:not(:defined),
gotb-presult-detail:not(:defined) {
  display: block;
  padding: var(--space-xl) var(--space-lg);
  background: var(--surface-card-bg);
  border: var(--border-width) solid var(--surface-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

gotb-sale-detail:not(:defined)::before,
gotb-sale-detail:not(:defined)::after,
gotb-ticket-detail:not(:defined)::before,
gotb-ticket-detail:not(:defined)::after,
gotb-presult-detail:not(:defined)::before,
gotb-presult-detail:not(:defined)::after {
  content: '';
  display: block;
  border-radius: var(--radius-md);
  background-image: linear-gradient(
    90deg,
    var(--surface-border) 0%,
    var(--surface-accent) 50%,
    var(--surface-border) 100%
  );
  background-size: 200% 100%;
  opacity: 0.35;
  animation: t-shimmer 2.2s linear infinite;
}

/* Jeton rond (avatar/sceau) avant le bloc principal — sale et presult
   affichent une icone de statut en tete de carte, ticket affiche un visuel
   16:9 (le ticket lui-meme). */
gotb-sale-detail:not(:defined)::before,
gotb-presult-detail:not(:defined)::before {
  width: 72px;
  height: 72px;
  border-radius: var(--radius-full);
  margin: 0 auto var(--space-lg);
}

gotb-ticket-detail:not(:defined)::before {
  aspect-ratio: 16 / 9;
  margin-bottom: var(--space-md);
}

gotb-sale-detail:not(:defined)::after,
gotb-ticket-detail:not(:defined)::after,
gotb-presult-detail:not(:defined)::after {
  height: 160px;
}

/* Message <noscript> : reprend la carte `.t-card` (classe combinee dans le
   HTML), cette regle n'ajoute que l'espacement interne et le centrage. */
.private-noscript {
  max-width: var(--measure-narrow);
  margin: var(--space-xl) auto 0;
  padding: var(--space-lg);
  text-align: center;
}

.private-noscript p {
  margin: 0 0 var(--space-sm);
}

.private-noscript p:last-child {
  margin-bottom: 0;
}

/* --- Mouvement reduit (coherent avec §11.4) -------------------------------- */
@media (prefers-reduced-motion: reduce) {
  gotb-sale-detail:not(:defined)::before,
  gotb-sale-detail:not(:defined)::after,
  gotb-ticket-detail:not(:defined)::before,
  gotb-ticket-detail:not(:defined)::after,
  gotb-presult-detail:not(:defined)::before,
  gotb-presult-detail:not(:defined)::after {
    animation: none;
  }
}

/* --- Contraste force -------------------------------------------------------- */
@media (forced-colors: active) {
  gotb-sale-detail:not(:defined),
  gotb-ticket-detail:not(:defined),
  gotb-presult-detail:not(:defined) {
    background: Canvas !important;
    border: 1px solid CanvasText;
    box-shadow: none;
  }

  gotb-sale-detail:not(:defined)::before,
  gotb-sale-detail:not(:defined)::after,
  gotb-ticket-detail:not(:defined)::before,
  gotb-ticket-detail:not(:defined)::after,
  gotb-presult-detail:not(:defined)::before,
  gotb-presult-detail:not(:defined)::after {
    background-image: none;
    background: CanvasText;
    opacity: 0.3;
    animation: none;
  }
}

/* --- Impression -------------------------------------------------------------- */
@media print {
  .private-noscript {
    display: none;
  }
}

/* ===========================================================================
   16. COMPOSANTS — RESULTATS DU TIRAGE (public/results.html — item-012)
   ---------------------------------------------------------------------------
   Page publique (a la difference de §15), publiee a l'issue du tirage du
   1er novembre 2026. Deux etats pilotes par le phasing pose en tete de
   chaque page (`html.gotb-drawn`, item-004) : coque d'attente par defaut
   (`.results-pending`), palmares complet une fois le tirage effectue
   (`.results-published`). Les deux blocs existent toujours dans le DOM —
   seul l'affichage change — ce qui evite tout flash de contenu au chargement
   (meme principe que `.club-roll__when-pre/live` du §13).

   Les deux premieres regles (`.results-alert`, `.hero__cta--drawn`) ne sont
   pas consommees par results.html mais par index.html : c'est ici, et non au
   §4, qu'elles sont ajoutees, pour garder la convention d'ajout de ce fichier
   (chaque item ajoute sa section en fin de feuille, jamais au milieu).
   ======================================================================== */

/* --- Bascule des CTA de l'accueil (index.html) une fois le tirage effectue - */

.results-alert {
  display: none;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm) var(--space-md);
  flex-wrap: wrap;
  padding: var(--space-sm) var(--container-padding-x);
  background: var(--color-bg-gold);
  background-image: var(--gradient-gold);
  color: var(--color-text);
  text-align: center;
}

.results-alert__text {
  margin: 0;
  font-weight: var(--font-weight-strong);
}

html.gotb-drawn .results-alert {
  display: flex;
}

.hero__cta--drawn {
  display: none;
}

html.gotb-drawn .hero__cta--pre {
  display: none;
}

html.gotb-drawn .hero__cta--drawn {
  display: inline-flex;
}

/* --- Coque avant tirage ----------------------------------------------------- */

.results-published {
  display: none;
}

html.gotb-drawn .results-pending {
  display: none;
}

html.gotb-drawn .results-published {
  display: block;
}

.results-card--narrow {
  max-width: var(--measure);
  margin: 0 auto;
}

/* --- Bandeau de l'apercu de recette (?__=) — jamais visible en production,
   meme principe et meme intitule que `.club-roll__demo` (§13). ------------- */
.results-demo {
  max-width: none;
  margin: 0 0 var(--space-lg);
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-sm);
  color: var(--surface-text-muted);
  border: var(--border-width) dashed var(--surface-border-strong);
  border-radius: var(--radius-sm);
}

/* --- Recherche (progressive enhancement — F1 de docs/layer-guidelines.md
   applique au modele de reference : le panneau n'existe visuellement
   qu'une fois le module JS execute). -------------------------------------- */

.results-search {
  max-width: 34rem;
  margin: 0 auto var(--space-xl);
  padding: var(--space-lg);
  text-align: left;
}

.results-search__title {
  margin-bottom: var(--space-2xs);
}

.results-search__sub {
  margin-bottom: var(--space-md);
  color: var(--surface-text-muted);
}

.results-search__field {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding-inline: var(--space-sm);
  border: var(--border-width) solid var(--surface-border);
  border-radius: var(--radius-md);
  background: var(--surface-bg);
  transition: border-color var(--transition-base) var(--easing-default), box-shadow var(--transition-base) var(--easing-default);
}

.results-search__field:focus-within {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgba(var(--color-primary-rgb), 0.18);
}

.results-search__ico {
  flex: 0 0 auto;
  width: 1.1rem;
  height: 1.1rem;
  color: var(--surface-text-muted);
}

.results-search__ico svg {
  width: 100%;
  height: 100%;
}

.results-search__input {
  flex: 1 1 auto;
  min-width: 0;
  padding-block: var(--space-sm);
  border: 0;
  background: transparent;
  font: inherit;
  letter-spacing: var(--tracking-wide);
  outline: none;
}

.results-search__clear {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  min-width: 28px;
  min-height: 28px;
  border-radius: var(--radius-full);
  background: var(--surface-border);
  color: var(--surface-text);
  font-size: 1.1rem;
  line-height: 1;
}

.results-search__status {
  min-height: 1.4em;
  margin-top: var(--space-sm);
  font-size: var(--font-size-sm);
  color: var(--surface-accent);
}

.results-search__hint {
  margin: var(--space-2xs) 0 0;
  font-size: var(--font-size-xs);
  color: var(--surface-text-muted);
}

.results-search__copy {
  margin-top: var(--space-xs);
}

/* --- Palmares : grille des 130 maillots -------------------------------------- */

.results-count {
  margin-inline: auto;
}

.results-privacy {
  margin-inline: auto;
}

.results-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(15rem, 100%), 1fr));
  gap: var(--space-sm);
  list-style: none;
  margin: 0;
  padding: 0;
}

.results-shirt {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  padding: var(--space-sm) var(--space-md);
  background: var(--surface-card-bg);
  border: var(--border-width) solid var(--surface-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  transition:
    border-color var(--transition-base) var(--easing-default),
    box-shadow var(--transition-base) var(--easing-default),
    opacity var(--transition-base) var(--easing-default),
    filter var(--transition-base) var(--easing-default);
}

.results-shirt__num {
  font-family: var(--font-heading);
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-strong);
  color: var(--surface-text-heading);
}

.results-shirt__size {
  font-size: var(--font-size-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--surface-text-muted);
}

.results-shirt__winner {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-strong);
  color: var(--surface-text-heading);
}

.results-shirt__winner--empty {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-body);
  font-style: italic;
  color: var(--surface-text-muted);
}

.results-shirt__code {
  font-size: var(--font-size-xs);
  color: var(--surface-text-muted);
}

.results-shirt--empty {
  background: transparent;
  border-style: dashed;
  box-shadow: none;
}

/* Etats de recherche : jamais de `display:none` — le palmares complet reste
   lisible et imprimable (cf. docs/layer-guidelines.md §9.4, meme logique que
   l'overlay : on attenue, on ne retire pas). */
.results-shirt.is-hit {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-md), 0 0 0 2px rgba(var(--color-primary-rgb), 0.22);
}

.results-shirt.is-dim {
  opacity: 0.32;
  filter: saturate(0.35);
}

.results-empty {
  display: none;
  max-width: 34rem;
  margin: var(--space-lg) auto 0;
  padding: var(--space-md) var(--space-lg);
  text-align: center;
  color: var(--surface-text-muted);
  border: var(--border-width) dashed var(--surface-border);
  border-radius: var(--radius-lg);
}

[data-results][data-search-empty] .results-empty {
  display: block;
}

/* --- Mouvement reduit -------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .results-shirt,
  .results-search__field {
    transition: none;
  }
}

/* --- Contraste force ---------------------------------------------------------- */
@media (forced-colors: active) {
  .results-search,
  .results-shirt,
  .results-empty,
  .results-demo {
    background: Canvas !important;
    border: 1px solid CanvasText;
    box-shadow: none;
  }

  .results-shirt.is-dim {
    opacity: 1;
    filter: none;
  }

  .results-shirt.is-hit {
    border-width: 2px;
  }
}

/* --- Impression ---------------------------------------------------------------- */
@media print {
  .results-alert,
  .site-header,
  .site-footer,
  .skip-link,
  .results-search,
  .results-demo {
    display: none !important;
  }

  .results-grid {
    grid-template-columns: repeat(3, 1fr) !important;
    gap: var(--space-xs) !important;
  }

  .results-shirt {
    break-inside: avoid;
    box-shadow: none !important;
  }

  .results-shirt.is-dim {
    opacity: 1 !important;
    filter: none !important;
  }
}

/* ===========================================================================
   17. COMPOSANTS — PAGE DE PARTAGE (public/share.html — item-013)
   ---------------------------------------------------------------------------
   Page 100% orientee client final : lien + QR + boutons WhatsApp/e-mail/copie,
   repli natif navigator.share. Les modes `?se=`/`?sg=`/`?z=` (espace vendeur,
   partage de groupe, parrainage) ne changent que le texte et le lien, en JS
   (public/assets/script-partage.<hash>.js) — zero nouvelle classe pour eux.
   `.site-header`/`.site-footer` et le trio `.hero`/`.t-display`/`.hero__kicker`
   de l'accueil (§13) sont repris tels quels pour le "visuel 130" de la page.
   Zero nouvelle couleur hexadecimale : le QR (<svg> construit en JS) lit lui
   -meme --color-bg-card/--color-text-heading via getComputedStyle, et le
   `.toast` ci-dessous ne consomme que des tokens existants.
   ======================================================================== */

.share-card {
  max-width: 34rem;
  margin: 0 auto;
  padding: var(--space-xl) var(--space-lg);
}

.share-card__label {
  margin: 0 0 var(--space-xs);
  font-family: var(--font-body);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-strong);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--surface-accent);
  text-align: center;
}

.share-card__field {
  display: flex;
  align-items: stretch;
  gap: var(--space-2xs);
  padding-inline-start: var(--space-sm);
  border: var(--border-width) solid var(--surface-border);
  border-radius: var(--radius-md);
  background: var(--surface-bg);
  margin-bottom: var(--space-md);
  transition:
    border-color var(--transition-base) var(--easing-default),
    box-shadow var(--transition-base) var(--easing-default);
}

.share-card__field:focus-within {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgba(var(--color-primary-rgb), 0.18);
}

.share-card__input {
  flex: 1 1 auto;
  min-width: 0;
  padding-block: var(--space-sm);
  border: 0;
  background: transparent;
  font: inherit;
  font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  font-size: var(--font-size-sm);
  letter-spacing: var(--tracking-normal);
  color: var(--surface-text);
  outline: none;
}

.share-card__copy {
  flex: 0 0 auto;
  margin-block: var(--space-2xs);
  border-radius: var(--radius-sm);
}

.share-card__copy svg,
.share-net svg,
.share-card__natif svg {
  width: 1.1rem;
  height: 1.1rem;
  flex: 0 0 auto;
}

.share-card__natif {
  margin-bottom: var(--space-lg);
}

.share-card__divider {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin: 0 0 var(--space-md);
  font-family: var(--font-body);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-strong);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--surface-text-muted);
}

.share-card__divider::before,
.share-card__divider::after {
  content: '';
  flex: 1 1 auto;
  height: var(--border-width);
  background: var(--surface-border);
}

.share-card__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-lg);
  align-items: center;
  margin-bottom: var(--space-lg);
  text-align: center;
}

.share-card__qr-canvas {
  display: inline-block;
  width: 148px;
  height: 148px;
  padding: var(--space-xs);
  background: var(--surface-card-bg);
  border: var(--border-width) solid var(--surface-border);
  border-radius: var(--radius-md);
}

.share-card__qr-canvas svg {
  display: block;
  width: 100%;
  height: 100%;
  shape-rendering: crispEdges;
}

.share-card__qr-caption {
  margin: var(--space-xs) 0 0;
  font-size: var(--font-size-xs);
  color: var(--surface-text-muted);
}

.share-card__networks {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-sm);
  margin: 0;
  padding: 0;
  list-style: none;
}

.share-net {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: 48px;
  padding-inline: var(--space-sm);
  border: var(--border-width) solid var(--surface-border);
  border-radius: var(--radius-full);
  color: var(--surface-text);
  text-decoration: none;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-strong);
  transition:
    background-color var(--transition-base) var(--easing-default),
    border-color var(--transition-base) var(--easing-default),
    color var(--transition-base) var(--easing-default),
    transform var(--transition-fast) var(--easing-editorial);
}

.share-net:hover,
.share-net:focus-visible {
  background: var(--surface-bg);
  border-color: var(--surface-accent);
  color: var(--surface-accent);
  transform: translateY(-2px);
}

.share-card__cause {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  padding: var(--space-md);
  border: var(--border-width) dashed var(--surface-border-strong);
  border-radius: var(--radius-md);
  text-align: left;
}

.share-card__cause svg {
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
  margin-top: 2px;
  color: var(--surface-accent);
}

.share-card__cause p {
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--surface-text-muted);
}

@media (min-width: 560px) {
  .share-card__grid {
    grid-template-columns: auto 1fr;
    text-align: left;
  }
}

/* --- Toast « Lien copie ! » ------------------------------------------------ */

.toast {
  position: fixed;
  left: 50%;
  bottom: var(--space-lg);
  z-index: var(--z-modal);
  transform: translate(-50%, 12px);
  padding: var(--space-xs) var(--space-md);
  border-radius: var(--radius-full);
  background: var(--color-bg-deep);
  color: var(--color-text-inverse);
  font-family: var(--font-body);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-strong);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  pointer-events: none;
  transition:
    opacity var(--transition-base) var(--easing-default),
    transform var(--transition-base) var(--easing-default);
}

.toast.is-visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* --- Mouvement reduit (coherent avec §11.4) -------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .share-net {
    transition: none !important;
  }

  .share-net:hover {
    transform: none !important;
  }

  .toast {
    transition: none !important;
  }
}

/* --- Contraste force -------------------------------------------------------- */
@media (forced-colors: active) {
  .share-card__field,
  .share-card__qr-canvas,
  .share-net {
    border: 1px solid CanvasText;
  }

  .toast {
    background: Canvas;
    color: CanvasText;
    border: 1px solid CanvasText;
  }
}

/* --- Impression -------------------------------------------------------------- */
@media print {
  .share-card__copy,
  .share-card__natif,
  .share-card__networks,
  .toast {
    display: none !important;
  }
}

/* ===========================================================================
   18. COMPOSANTS — OVERLAY DE LANCEMENT & BANDEAU DE CLOTURE (item-017)
   ---------------------------------------------------------------------------
   Deux etats du meme phasing temporel pose en tete d'index.html (item-004,
   `data-launch` sur <html>, jamais de date en dur dans le JS) :
     - `.launch-overlay` — avant l'ouverture des ventes (1er novembre 2026,
       9h00 heure suisse). Premier enfant de <body>, avant le header. Le
       script interne (index.html) ne le revele, et ne pose `has-countdown`
       sur <body>, qu'en mode "countdown" — deja tranche avant que <body> ne
       soit parse, donc zero flash. `[hidden]` reste la garde par defaut :
       sans JS ou hors countdown, l'attribut n'est jamais retire et la page
       s'affiche normalement (echec ouvert, meme doctrine que §13 pour
       `.club-roll__when-pre/live`).
     - item-037 : le bandeau de cloture `.close-banner` qui vivait ici (compte a
       rebours permanent « Ventes ouvertes — cloture a la fin du match ») a ete
       RETIRE a la demande du client. Ses regles, son balisage et son minuteur
       JS ont ete supprimes, pas seulement masques. L'horaire de cloture reste
       indique dans « Les paliers », « La mecanique » et a l'art. 4 du reglement.
   ======================================================================== */

/* --- Overlay d'attente --------------------------------------------------- */

.launch-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-lg);
  text-align: center;
  background-color: var(--color-bg-deep-2);
  background-image: var(--gradient-deep), var(--texture-grain);
  background-blend-mode: normal, var(--texture-blend-inverse);
  opacity: 1;
  transition: opacity var(--overlay-fade-duration) var(--easing-editorial);
}

/* L'attribut `[hidden]` doit rester prioritaire sur `display: flex` ci-dessus
   (une regle d'auteur de meme specificite que l'agent utilisateur gagne par
   defaut) — sans cette ligne l'overlay resterait visible meme masque. */
.launch-overlay[hidden] {
  display: none;
}

.launch-overlay.is-hidden {
  opacity: 0;
  pointer-events: none;
}

.launch-overlay__inner {
  max-width: 34rem;
}

.launch-overlay__eyebrow {
  margin: 0 0 var(--space-xs);
  font-family: var(--font-heading);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-strong);
  letter-spacing: var(--tracking-widest);
  text-transform: uppercase;
  color: var(--color-secondary);
}

.launch-overlay__figure {
  margin: 0;
  font-family: var(--font-body);
  font-size: clamp(3.5rem, 2rem + 8vw, 7rem);
  font-weight: var(--font-weight-bold);
  line-height: 0.82;
  letter-spacing: var(--tracking-tight);
  font-variant-numeric: lining-nums;
  color: var(--color-text-inverse);
  text-shadow: var(--glow-text-gold);
}

.launch-overlay__title {
  margin: var(--space-sm) 0 var(--space-2xs);
  font-family: var(--font-heading);
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-heading);
  color: var(--color-text-inverse);
}

.launch-overlay__lede {
  max-width: var(--measure-narrow);
  margin: 0 auto var(--space-lg);
  color: var(--color-text-inverse-muted);
}

.launch-overlay__countdown {
  display: flex;
  justify-content: center;
  gap: var(--space-sm);
}

.launch-overlay__cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 4.5ch;
  padding: var(--space-sm) var(--space-xs);
  border: var(--border-width) solid var(--border-color-inverse);
  border-radius: var(--radius-md);
  background: var(--glass-bg-inverse);
}

/* Largeur fixe (toujours 2 chiffres, §1) : zero decalage visuel seconde a
   seconde, meme principe que les compteurs du hero (item-015, §13). */
.launch-overlay__value {
  min-width: 2ch;
  font-family: var(--font-body);
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-strong);
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--color-text-inverse);
}

.launch-overlay__label {
  margin-top: var(--space-3xs);
  font-size: var(--font-size-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-inverse-muted);
}

/* Masque le contenu sous l'overlay pendant l'attente (§9.4 de
   layer-guidelines.md) : l'element garde son espace (zero reflow au retrait
   de la classe), seule la peinture est coupee — un swap de police ne compte
   alors jamais comme layout shift. Classe retiree par le JS a la disparition
   (index.html + script.js). */
body.has-countdown header,
body.has-countdown main,
body.has-countdown footer {
  visibility: hidden;
}

/* --- Mouvement reduit (§9.4/§11 — non negociable) ------------------------- */
@media (prefers-reduced-motion: reduce) {
  .launch-overlay {
    transition: none !important;
  }
}

/* --- Contraste force -------------------------------------------------------- */
@media (forced-colors: active) {
  .launch-overlay {
    background-color: Canvas;
    background-image: none;
    color: CanvasText;
  }

  .launch-overlay__eyebrow,
  .launch-overlay__figure,
  .launch-overlay__title,
  .launch-overlay__lede,
  .launch-overlay__value,
  .launch-overlay__label {
    color: CanvasText;
    text-shadow: none;
  }

  .launch-overlay__cell {
    background: Canvas;
    border: 1px solid CanvasText;
  }
}

/* --- Impression -------------------------------------------------------------- */
@media print {
  .launch-overlay {
    display: none !important;
  }

  /* Sans cette regle, une impression lancee pendant l'attente afficherait une
     page blanche : `.launch-overlay` disparait ci-dessus mais `has-countdown`
     masquerait encore header/main/footer en `visibility: hidden`. */
  body.has-countdown header,
  body.has-countdown main,
  body.has-countdown footer {
    visibility: visible !important;
  }
}

/* ===========================================================================
   19. COUCHE « PREMIUM VISUEL » (item-021)
   ---------------------------------------------------------------------------
   Demande de l'operateur : un evenement haut de gamme, donc des bordures
   brillantes et vivantes (reflets), des animations legerement realistes de
   type reflex, une texture / un filigrane de haute qualite en fond, et de
   nombreuses apparitions au scroll. Plus : un en-tete desengorge et un CTA
   fixe en bas de viewport sur mobile.

   Comme chaque item precedent, cette section est ajoutee EN FIN de feuille
   et ne consomme que les tokens du §1 et les primitives des §4 a §9 : aucune
   valeur hexadecimale ici (regle d'or du fichier, voir l'entete).

   Garde-fous repris a la lettre de layer-theme-common.md §9/§14 :
   - les seules proprietes animees en boucle sont `transform`, `opacity` et
     `box-shadow` (cette derniere explicitement sanctionnee au §10.3
     « Glow pulse », impact Faible au §14.1) ;
   - aucune animation infinie n'est posee sur plus d'une poignee d'elements :
     les cartes ne brillent qu'au survol/focus, jamais en permanence ;
   - `prefers-reduced-motion`, `forced-colors` et `print` sont traites en fin
     de section (ils y viennent apres les §11/§12, donc ils priment).
   ======================================================================== */

/* --- 19.1 Filigrane de haute qualite -------------------------------------
   Le sceau guilloche (--texture-seal, §1) est un MASQUE : sa teinte vient de
   `background-color`, donc du contexte de surface (§4). Il reste
   volontairement DANS les limites de la section (`inset-inline-end: 0`,
   jamais une valeur negative) : un filigrane qui deborde creerait une barre
   de defilement horizontale sur mobile — c'est le piege a eviter ici.
   Respiration en opacite seule, jamais en transform : le sceau est enorme,
   le composer a chaque frame coute cher pour rien. */

.t-watermark::after {
  content: '';
  position: absolute;
  z-index: -1;
  top: 50%;
  inset-inline-end: 0;
  width: var(--watermark-size);
  height: var(--watermark-size);
  max-width: 100%;
  transform: translateY(-50%);
  pointer-events: none;
  background-color: var(--surface-watermark-color);
  -webkit-mask: var(--texture-seal) no-repeat center center / contain;
  mask: var(--texture-seal) no-repeat center center / contain;
  opacity: var(--surface-watermark-opacity);
  animation: t-watermark-breathe var(--watermark-drift-duration) var(--easing-default) infinite;
}

/* Variante : sceau cale a gauche, pour alterner d'une section a l'autre et
   ne jamais donner l'impression d'un motif plaque toujours au meme endroit. */
.t-watermark--start::after {
  inset-inline-end: auto;
  inset-inline-start: 0;
}

@keyframes t-watermark-breathe {
  0%,
  100% {
    opacity: calc(var(--surface-watermark-opacity) * 0.6);
  }
  50% {
    opacity: var(--surface-watermark-opacity);
  }
}

/* Trame de maille — reservee a la section « Les maillots » : la texture de
   fond y evoque le textile du maillot lui-meme plutot que le papier. */
.t-texture--weave {
  --surface-texture-image: var(--texture-grain), var(--texture-weave);
  --surface-texture-size: var(--texture-tile-grain), var(--texture-tile-weave);
}

.t-surface--deep.t-texture--weave,
.t-surface--night.t-texture--weave {
  --surface-texture-image: var(--texture-grain), var(--texture-weave-inverse);
}

/* --- 19.2 Bordures vivantes (reflets) ------------------------------------
   Un filet uni ne se lit jamais comme du metal. On remplace donc le filet
   des cartes par un ruban `--gradient-metal-gold` (§1) pose en anneau grace
   a la technique `mask-composite` deja employee au §13 pour la carte
   « Recommande » (layer-theme-common.md §6.1).

   `inset: calc(var(--border-width) * -1)` est la valeur exacte, et pas un
   choix esthetique : la boite d'un enfant absolu se cale sur la boite de
   PADDING du parent, donc sur l'interieur de sa bordure. Sans ce decalage
   negatif d'un pixel, l'anneau se dessinerait a l'interieur du filet
   existant et on verrait un double trait.

   Au repos le ruban est fige (aucune animation permanente sur des dizaines
   de cartes) ; il ne VOYAGE qu'au survol et au focus clavier. */

.t-edge,
.feature,
.pricing-card:not(.pricing-card--featured),
.timeline__item,
.club-step,
.faq__item,
.sizes-grid__item--rare {
  position: relative;
  border-color: transparent;
}

.t-edge::after,
.feature::after,
.pricing-card:not(.pricing-card--featured)::after,
.timeline__item::after,
.club-step::after,
.faq__item::after,
.sizes-grid__item--rare::after {
  content: '';
  position: absolute;
  inset: calc(var(--border-width) * -1);
  border-radius: inherit;
  padding: var(--border-width);
  background-image: var(--gradient-metal-gold);
  background-size: 280% 100%;
  background-position: 18% 50%;
  -webkit-mask:
    linear-gradient(var(--mask-paint) 0 0) content-box,
    linear-gradient(var(--mask-paint) 0 0);
  mask:
    linear-gradient(var(--mask-paint) 0 0) content-box,
    linear-gradient(var(--mask-paint) 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
  transition: background-position var(--edge-travel-duration) var(--easing-editorial);
}

/* La rarete des tailles S/XXL (item-018) doit rester lisible : son anneau est
   deux fois plus epais que celui des autres cartes, pour que la hierarchie
   survive au passage de toutes les cartes en bordure metallique. Le mot
   « Rare » reste de toute facon le signal non colorimetrique (a11y). */
.sizes-grid__item--rare::after {
  inset: calc(var(--border-width-thick) * -1);
  padding: var(--border-width-thick);
}

.t-edge:hover::after,
.t-edge:focus-within::after,
.feature:hover::after,
.pricing-card:not(.pricing-card--featured):hover::after,
.pricing-card:not(.pricing-card--featured):focus-within::after,
.timeline__item:hover::after,
.club-step:hover::after,
.faq__item:hover::after,
.faq__item:focus-within::after,
.sizes-grid__item--rare:hover::after {
  background-position: 100% 50%;
}

/* Le survol du §5 pose `border-color: var(--surface-accent)` sur `.t-card` :
   sur une carte a anneau, cette bordure est invisible (l'anneau la couvre) et
   la regle deviendrait un faux signal. On la neutralise explicitement plutot
   que de la laisser agir dans le vide. */
.t-edge:hover,
.feature:hover,
.pricing-card:not(.pricing-card--featured):hover,
.timeline__item:hover,
.club-step:hover,
.faq__item:hover,
.sizes-grid__item--rare:hover {
  border-color: transparent;
}

/* --- 19.3 Reflets speculaires --------------------------------------------
   Deux mecaniques distinctes, pour deux objets differents :

   a) Les CTA primaires recoivent un reflet qui BALAYE en boucle lente — le
      geste d'un objet verni qu'on incline. `overflow: hidden` est
      indispensable pour que la bande reste dans la pilule ; il ne clippe
      aucun contenu reel (un bouton n'a que son libelle) et jamais le
      `outline` de focus, qui n'est pas affecte par l'overflow.

   b) Les images recoivent un reflet pilote par le DEFILEMENT (« type
      reflex ») : la bande suit la position de l'image dans le viewport, donc
      la lumiere semble balayer la surface quand on scrolle. Le calque est
      cree par assets/script.<hash>.js, jamais ecrit dans le HTML, et chaque
      frame n'ecrit qu'un `transform: translate3d(...)`. */

.btn--primary {
  overflow: hidden;
}

.btn--primary::after {
  content: '';
  position: absolute;
  z-index: -1;
  top: -60%;
  bottom: -60%;
  left: -40%;
  width: 40%;
  pointer-events: none;
  background-image: var(--gradient-sheen);
  transform: translate3d(0, 0, 0) rotate(14deg);
  animation: t-sheen-sweep var(--sheen-duration) var(--easing-editorial) infinite;
}

@keyframes t-sheen-sweep {
  0% {
    transform: translate3d(0, 0, 0) rotate(14deg);
  }
  /* Le balayage occupe le premier tiers du cycle ; les deux autres tiers sont
     une pause hors champ, sans quoi l'effet devient un stroboscope. */
  34%,
  100% {
    transform: translate3d(420%, 0, 0) rotate(14deg);
  }
}

/* Cadres photographiques : hotes du reflet au defilement. `overflow: hidden`
   est deja pose par le §13 sur les deux ; seul le contexte de positionnement
   manquait. */
.t-media-frame,
.t-split__figure {
  position: relative;
}

.t-reflex {
  position: absolute;
  top: -25%;
  bottom: -25%;
  left: -70%;
  width: 60%;
  z-index: 1;
  pointer-events: none;
  background-image: var(--gradient-sheen);
  mix-blend-mode: screen;
  opacity: 0.7;
  /* --reflex est pose par le JS (0 -> 100 selon la progression de l'image
     dans le viewport) ; la valeur de repli 0 garde la bande hors cadre si le
     script ne tourne pas. */
  transform: translate3d(calc(var(--reflex, 0) * 2.6%), 0, 0) rotate(12deg);
  will-change: transform;
}

/* Les cadres gagnent aussi un filet metallique, mais statique : un anneau
   anime par-dessus une photographie distrait de la photographie. */
.t-media-frame,
.t-split__figure {
  border-color: var(--color-secondary);
}

/* --- 19.4 Apparitions au scroll, enrichies -------------------------------
   Variantes de la primitive `.reveal` du §9 (meme classe `.is-visible`, donc
   le JS d'item-015 les pilote deja sans modification). `.reveal.is-visible`
   remet `transform: none` : toutes les variantes ci-dessous n'ont donc besoin
   de declarer que leur etat INITIAL.

   Aucune de ces variantes n'est posee sur une section entiere ni sur l'image
   LCP du hero : si le JS etait bloque (CSP, erreur, reseau), un bloc en
   `opacity: 0` ne se montrerait jamais. On ne revele donc que des blocs
   internes, exactement comme item-015 l'avait tranche. */

.reveal--left {
  transform: translate3d(calc(var(--reveal-distance) * -1), 0, 0);
}

.reveal--right {
  transform: translate3d(var(--reveal-distance), 0, 0);
}

.reveal--up {
  transform: translate3d(0, var(--reveal-distance), 0);
}

.reveal--scale {
  transform: scale(0.94);
}

/* Mise au point optique : l'element arrive flou puis se « fixe ». `filter`
   n'est pas une propriete composee par le GPU : la transition est donc
   ponctuelle (jamais une boucle) et reservee a quelques blocs de texte. */
.reveal--focus {
  filter: blur(var(--reveal-blur));
  transition:
    opacity var(--transition-slow) var(--easing-editorial),
    transform var(--transition-slow) var(--easing-editorial),
    filter var(--transition-slow) var(--easing-editorial);
}

.reveal--focus.is-visible {
  filter: none;
}

/* Rideau : l'image se devoile du haut vers le bas, sans jamais bouger ni
   changer d'opacite — le cadre reste plein, seul le contenu se decouvre.
   `round var(--radius-lg)` n'est pas decoratif : un `inset()` sans lui clippe
   un RECTANGLE, ce qui carrerait les angles arrondis des cadres photo
   (.t-media-frame et .t-split__figure ont tous deux ce rayon). */
/* ⚠️ CORRIGE EN item-036 — ce rideau n'a JAMAIS fonctionne depuis sa creation.
   Le `clip-path` etait pose sur l'element HOTE, celui-la meme qui porte
   `.reveal` et que l'IntersectionObserver surveille. Or un `inset(0 0 100%)`
   reduit la boite affichee a une hauteur NULLE : l'observateur ne voit donc
   jamais l'element entrer dans le champ, n'ajoute jamais `is-visible`, et le
   rideau ne s'ouvre jamais. Blocage circulaire, parfaitement silencieux —
   l'image etait chargee (verifie : `naturalWidth` correct) mais rognee a
   100 %, donc invisible.

   Mesure a l'appui (3 variantes, navigateur reel, item-036) :
     clip sur l'hote .................. 0/4 rideaux ouverts
     clip retire de l'hote ............ 4/4 rideaux ouverts
     clip a 99,5 % (boite quasi nulle)  0/4 rideaux ouverts

   Correctif : le rideau se pose desormais sur le MEDIA contenu dans le cadre
   (`> picture` / `> img`), jamais sur l'hote. L'hote garde une boite pleine,
   donc l'observateur le detecte normalement. L'effet visuel est identique. */
.reveal--curtain {
  opacity: 1;
  transform: none;
}

.reveal--curtain > picture,
.reveal--curtain > img {
  clip-path: inset(0 0 100% 0 round var(--radius-lg));
  transition: clip-path var(--transition-slow) var(--easing-editorial);
}

.reveal--curtain.is-visible > picture,
.reveal--curtain.is-visible > img {
  clip-path: inset(0 0 0 0 round var(--radius-lg));
}

/* Filet d'orfevre qui se trace depuis le centre. */
.reveal--rule {
  opacity: 0;
  transform: scaleX(0);
}

.t-rule.reveal--rule {
  transform-origin: center;
}

.reveal--d6 {
  transition-delay: 540ms;
}

.reveal--d7 {
  transition-delay: 630ms;
}

/* --- 19.5 En-tete : densification au defilement --------------------------
   L'en-tete ne change JAMAIS de hauteur : il est `position: sticky`, donc
   toute variation de sa hauteur decalerait `<main>` et compterait en CLS
   (layer-guidelines.md §4.4). Seules la matiere et l'ombre evoluent. */

.site-header {
  transition:
    background-color var(--transition-base) var(--easing-default),
    box-shadow var(--transition-base) var(--easing-editorial),
    border-color var(--transition-base) var(--easing-default);
}

.site-header.is-stuck {
  box-shadow: var(--shadow-md);
  border-bottom-color: var(--color-secondary);
}

/* Liseré d'or sous l'en-tete, trace une fois cale en haut de page. */
.site-header::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  bottom: calc(var(--border-width) * -1);
  height: var(--border-width);
  background: var(--gradient-rule);
  opacity: 0;
  transition: opacity var(--transition-base) var(--easing-default);
  pointer-events: none;
}

.site-header.is-stuck::after {
  opacity: 1;
}

/* A 360px, le blason + le nom + le CTA se disputent la largeur : le nom
   cede le premier, mais il reste toujours lisible (jamais masque). */
@media (max-width: 479px) {
  .site-header__brand {
    font-size: var(--font-size-sm);
    gap: var(--space-2xs);
  }

  .site-header__logo {
    width: 32px;
    height: 33px;
  }
}

/* --- 19.6 CTA fixe de bas de viewport (mobile) ---------------------------
   Demande explicite : apparait des que l'on commence a defiler, en mobile
   uniquement, avec pulse + glow + reflet. `transform` seul pour l'entree
   (jamais `bottom`, qui declencherait un layout a chaque frame), et la barre
   n'existe pas du tout au-dessus de 900px (la ou l'en-tete porte deja son
   propre CTA).

   `env(safe-area-inset-bottom)` est obligatoire : sans lui, la barre passe
   sous l'indicateur d'accueil des iPhone recents (`viewport-fit=cover` est
   deja pose dans index.html). */

.sticky-cta {
  display: none;
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: var(--z-sticky);
  justify-content: center;
  gap: var(--space-sm);
  padding: var(--space-xs) var(--container-padding-x);
  padding-bottom: calc(var(--space-xs) + env(safe-area-inset-bottom, 0px));
  background: var(--glass-bg);
  backdrop-filter: blur(var(--glass-blur)) saturate(140%);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(140%);
  border-top: var(--border-width) solid var(--color-secondary);
  box-shadow: var(--shadow-lg);
  transform: translate3d(0, 120%, 0);
  transition: transform var(--sticky-cta-duration) var(--easing-editorial);
}

/* Libelle et mention de prix EMPILES, pas cote a cote : a 390px, un libelle
   en capitales espacees et son complement sur la meme ligne forcent le
   libelle a passer sur deux lignes et la mention se retrouve a flotter a
   droite, desaxee. En colonne, les deux lignes sont centrees et lues dans
   l'ordre. (Constate en rendu reel, pas suppose.) */
.sticky-cta__link {
  width: 100%;
  max-width: 420px;
  flex-direction: column;
  gap: var(--space-3xs);
  line-height: var(--line-height-tight);
}

/* La mention de prix reste un complement : jamais en capitales, jamais au
   meme poids, et elle ne doit pas agrandir la cible au-dela de la hauteur
   deja confortable du bouton. */
.sticky-cta__hint {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-body);
  letter-spacing: var(--tracking-normal);
  text-transform: none;
  opacity: 0.86;
}

@media (max-width: 899px) {
  .sticky-cta {
    display: flex;
  }

  /* `body.has-scrolled` est pose par le JS des le premier defilement reel. */
  body.has-scrolled .sticky-cta {
    transform: translate3d(0, 0, 0);
  }

  /* Reserve de place en pied de page : permanente (donc zero decalage au
     moment ou la barre apparait), et seulement sur les viewports qui ont
     reellement cette barre. */
  .site-footer__bottom {
    padding-bottom: calc(var(--space-2xl) + env(safe-area-inset-bottom, 0px));
  }
}

/* Pendant l'attente d'ouverture (item-017), l'overlay occupe tout l'ecran :
   la barre n'a rien a proposer et ne doit pas percer par-dessus. */
body.has-countdown .sticky-cta {
  display: none;
}

/* Bascule avant/apres tirage — meme mecanique CSS pure que `.hero__cta--pre`
   / `--drawn` du §16, pour la meme raison : zero flash de contenu. */
.sticky-cta__link--drawn {
  display: none;
}

html.gotb-drawn .sticky-cta__link--pre {
  display: none;
}

html.gotb-drawn .sticky-cta__link--drawn {
  display: inline-flex;
}

/* Pulse + glow renforces : c'est le point d'entree unique de l'achat sur
   mobile, il a le droit d'etre le seul element a pulser en permanence.
   `box-shadow` (glow) et `transform` (respiration) sont declares comme deux
   animations distinctes sur la meme ligne — sinon la seconde ecraserait la
   premiere. Elles remplacent `t-btn-breathe` du §6. */
.sticky-cta .btn--primary {
  animation:
    t-sticky-glow var(--sticky-cta-pulse-duration) var(--pulse-easing) infinite,
    t-sticky-lift var(--sticky-cta-pulse-duration) var(--pulse-easing) infinite;
}

@keyframes t-sticky-glow {
  0%,
  100% {
    box-shadow: var(--shadow-md), var(--glow-primary-soft);
  }
  50% {
    box-shadow: var(--shadow-lg), var(--glow-primary-strong);
  }
}

@keyframes t-sticky-lift {
  0%,
  100% {
    transform: scale(1);
  }
  50% {
    transform: scale(var(--pulse-scale));
  }
}

/* Retour tactile a l'appui. Le §6 le confie a `animation-play-state: paused`,
   mais une animation en pause continue d'IMPOSER la valeur de sa frame
   courante : le `transform` d'appui serait purement ignore ici. On retire donc
   l'animation de respiration de la liste (`animation-name` ne garde que le
   glow) pour que le `scale` d'appui reprenne la main. Sans ca, le CTA
   principal du mobile n'a aucun retour au doigt. */
.sticky-cta .btn--primary:active {
  animation-name: t-sticky-glow;
  transform: scale(0.97);
}

/* Reflet plus rapide que sur les autres CTA : la barre est petite, le
   balayage doit s'y lire. */
.sticky-cta .btn--primary::after {
  animation-duration: calc(var(--sheen-duration) * 0.62);
}

/* --- 19.7 Club des 130 : la publication vit ailleurs ---------------------
   Le tableau d'honneur a quitte l'accueil (voir docs/SITE.md, item-021) :
   l'inscription et la publication des participants se font sur le site
   interne du Club des 130, pas sur ce site de tombola. Ce bloc dit ou, sans
   jamais laisser croire que le nom s'affichera sur cette page. */

.club-elsewhere {
  display: grid;
  gap: var(--space-md);
  margin-top: var(--space-2xl);
  padding: var(--space-xl) var(--space-lg);
  text-align: center;
  justify-items: center;
}

.club-elsewhere__title {
  margin: 0;
}

.club-elsewhere__text {
  max-width: var(--measure-narrow);
  margin: 0;
  color: var(--surface-text-muted);
}

.club-elsewhere__note {
  margin: 0;
  font-size: var(--font-size-sm);
}

/* --- 19.7bis Grille des tailles : regle morte, reparee -------------------
   BUG PRE-EXISTANT, constate au rendu reel (pas suppose) : le §13 declare
   `.sizes-grid { grid-template-columns: repeat(2, 1fr) }` APRES la media
   query `(min-width: 641px)` du §10 qui la passe a 5 colonnes. Les deux ont
   la meme specificite (0,1,0), donc l'ordre du fichier tranche : la version
   2 colonnes gagnait TOUJOURS, media query comprise. La grille des cinq
   tailles s'affichait donc en 2+2+1 sur grand ecran depuis l'origine, avec
   un XXL orphelin sur sa propre ligne — exactement le genre de chose que
   « tout doit etre bien cale » vise. Reparee ici, et pas en place : deplacer
   la declaration du §13 reviendrait a reecrire une section d'un autre item.

   `auto-fit` plutot que `repeat(5, 1fr)` parce que la media query mesure le
   VIEWPORT alors que cette grille vit dans la demi-colonne d'un `.t-split`
   (~600px au plus) : a 5 colonnes fixes, les cartes seraient a l'etroit sur
   les viewports juste au-dessus de 769px. `auto-fit` en pose 5 quand la
   place existe et retombe tout seul sinon. En dessous de 641px, les
   2 colonnes d'origine sont conservees telles quelles. */
@media (min-width: 641px) {
  .sizes-grid {
    grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));
  }
}

/* --- 19.8 Mouvement reduit (§9.4 — non negociable) ----------------------- */
@media (prefers-reduced-motion: reduce) {
  /* Le bloc global du §11 coupe deja les durees ; ces regles-ci suppriment
     les effets qui, meme instantanes, resteraient genants ou invisibles. */
  .t-watermark::after,
  .btn--primary::after,
  .sticky-cta .btn--primary {
    animation: none;
  }

  .t-reflex {
    display: none;
  }

  .reveal--left,
  .reveal--right,
  .reveal--up,
  .reveal--scale,
  .reveal--rule {
    opacity: 1;
    transform: none;
  }

  .reveal--focus {
    filter: none;
  }

  .reveal--curtain,
  .reveal--curtain > picture,
  .reveal--curtain > img {
    clip-path: none;
  }

  /* La barre doit rester ACCESSIBLE sans animation : on la montre d'emblee
     plutot que de la laisser hors champ faute de transition. */
  .sticky-cta {
    transition: none;
  }

  .sticky-cta .btn--primary {
    box-shadow: var(--shadow-md);
    transform: none;
  }
}

/* --- 19.9 Contraste force ------------------------------------------------- */
@media (forced-colors: active) {
  /* Tous les effets de matiere disparaissent : en contraste force, seules les
     couleurs systeme ont un sens. Les anneaux metalliques sont remplaces par
     un filet CanvasText franc, sinon les cartes perdraient toute limite. */
  .t-watermark::after,
  .btn--primary::after,
  .t-reflex,
  .t-edge::after,
  .feature::after,
  .pricing-card:not(.pricing-card--featured)::after,
  .timeline__item::after,
  .club-step::after,
  .faq__item::after,
  .sizes-grid__item--rare::after,
  .site-header::after {
    display: none;
  }

  .t-edge,
  .feature,
  .pricing-card,
  .timeline__item,
  .club-step,
  .faq__item,
  .sizes-grid__item--rare,
  .t-media-frame,
  .t-split__figure {
    border: 1px solid CanvasText;
  }

  /* La rarete ne peut plus se lire sur l'epaisseur d'un anneau supprime :
     elle repose alors entierement sur l'intitule texte « Rare », deja
     present dans le DOM depuis item-018. */
  .sizes-grid__item--rare {
    border-width: 2px;
  }

  .sticky-cta {
    background: Canvas;
    border-top: 1px solid CanvasText;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  .sticky-cta .btn--primary {
    animation: none;
    box-shadow: none;
  }
}

/* --- 19.10 Impression ----------------------------------------------------- */
@media print {
  .t-watermark::after,
  .btn--primary::after,
  .t-reflex,
  .sticky-cta,
  .site-header::after {
    display: none !important;
  }

  /* A l'impression, rien ne doit rester cache par une apparition jamais
     declenchee (le JS ne tourne pas forcement avant l'ouverture de la
     boite d'impression). */
  .reveal,
  .reveal--left,
  .reveal--right,
  .reveal--up,
  .reveal--scale,
  .reveal--rule,
  .reveal--focus {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }

  .reveal--curtain,
  .reveal--curtain > picture,
  .reveal--curtain > img {
    clip-path: none !important;
  }

  /* Les anneaux metalliques ne sortent pas a l'impression : on rend aux
     cartes un filet imprimable. */
  .t-edge::after,
  .feature::after,
  .pricing-card::after,
  .timeline__item::after,
  .club-step::after,
  .faq__item::after,
  .sizes-grid__item--rare::after {
    display: none !important;
  }

  .t-edge,
  .feature,
  .pricing-card,
  .timeline__item,
  .club-step,
  .faq__item,
  .sizes-grid__item--rare {
    border: var(--border-width) solid var(--border-color-strong);
  }
}

/* ===========================================================================
   20. RELANCE D'ACHAT DEPUIS LE RECAPITULATIF (item-023)

   Le client demande, « a la fin du processus de paiement », une option
   « Je veux aussi tenter ma chance sur d'autres tailles » qui relance un
   achat « sans repasser par le debut du parcours ».

   OU CE BLOC VIT, ET POURQUOI PAS AILLEURS. Le tunnel d'achat QUITTE le site
   au paiement (`ie()` fait `window.location.href = pay.gotombola.co/s?...`
   apres avoir vide son etat de session) : plus une ligne de nous ne tourne
   ensuite. La derniere etape du <dialog>, elle, est AVANT le paiement — y
   poser « acheter autre chose » reviendrait a detourner une commande en
   cours, juste a cote du bouton qui la conclut. La seule page du site qui
   soit reellement APRES le paiement est le recapitulatif d'achat a lien
   prive, /private/sale.html : c'est la, et seulement la, que ce bloc est
   rendu.

   Il n'apparait QUE pendant la fenetre de vente
   (`html:not(.gotb-sales-locked)`, meme bascule 100 % CSS que la
   `.results-alert` du §16) : apres la cloture ou le tirage, un recapitulatif
   ne doit pas proposer de racheter.

   Ajoute en fin de feuille et non dans le §15 (composants des pages
   privees) : c'est la convention d'ajout de ce fichier, deja appliquee au
   §19.7bis — deplacer une section ecrite par un autre item la reecrirait.
   ======================================================================== */

.relance {
  display: none;
  max-width: 640px; /* meme gouttiere que `.private-slot` du §15 */
  margin: var(--space-2xl) auto 0;
}

html:not(.gotb-sales-locked) .relance {
  display: block;
}

/* La carte elle-meme reprend `.t-card` (classe combinee dans le HTML) :
   cette regle n'ajoute que la mise en page interne. */
.relance__card {
  display: grid;
  gap: var(--space-md);
  justify-items: center;
  padding: var(--space-xl) var(--space-lg);
  text-align: center;
}

.relance__title {
  margin: 0;
}

.relance__text {
  max-width: var(--measure-narrow);
  margin: 0;
  color: var(--surface-text-muted);
}

/* Les cinq tailles. Flex et non grille : a 390 px la carte ne tient que
   trois cibles par ligne, et une grille `auto-fit` laisserait la seconde
   ligne calee a gauche, seule desalignee dans un bloc entierement centre.
   Le couple `flex: 1 1 72px` / `max-width: 88px` donne des cibles de
   largeur quasi identique sur les deux lignes (87 px et 88 px a 390 px),
   et cinq sur une seule ligne des que la place existe. 72 px de base, parce
   qu'en dessous « XXL » ne tient plus dans sa cible. */
.relance__sizes {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2xs);
  width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
}

.relance__sizes > li {
  display: flex;
  flex: 1 1 72px;
  max-width: 88px;
}

/* Cible tactile 56 x 72 px au minimum (WCAG 2.5.5). Le filet est
   `--border-color-strong` (3.37:1 sur creme) et non le filet decoratif des
   cartes (`--border-color`, 1.3:1) : ces elements sont interactifs, leur
   contour doit l'annoncer — et il est seul a le faire, d'ou le plancher de
   3:1 (WCAG 1.4.11). */
.relance__size {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: center;
  min-height: 56px;
  padding: var(--space-2xs);
  font-family: var(--font-heading);
  font-size: var(--font-size-lg);
  color: var(--surface-text-heading);
  text-decoration: none;
  background-color: var(--surface-card-bg);
  border: var(--border-width) solid var(--surface-border-strong);
  border-radius: var(--radius-md);
  transition:
    background-color var(--transition-base) var(--easing-default),
    color var(--transition-base) var(--easing-default),
    border-color var(--transition-base) var(--easing-default),
    transform var(--transition-fast) var(--easing-editorial);
}

/* Survol et focus clavier traites ensemble : le remplissage devient le bleu
   officiel, le libelle son `--surface-btn-text` (5.88:1, meme paire que les
   CTA primaires du site). L'anneau de focus du §7 reste pose par-dessus. */
.relance__size:hover,
.relance__size:focus-visible {
  background-color: var(--surface-btn-bg);
  color: var(--surface-btn-text);
  border-color: var(--surface-btn-bg);
  transform: translateY(-2px);
}

.relance__size:active {
  transform: translateY(0);
}

.relance__note {
  max-width: var(--measure-narrow);
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--surface-text-muted);
}

/* --- Mouvement reduit (coherent avec §11.4 et §19.8) ---------------------- */
@media (prefers-reduced-motion: reduce) {
  .relance__size:hover,
  .relance__size:focus-visible {
    transform: none;
  }
}

/* --- Contraste renforce --------------------------------------------------- */
@media (forced-colors: active) {
  .relance__size {
    border-color: ButtonText;
  }
}

/* --- Impression ----------------------------------------------------------- */
@media print {
  /* Un recapitulatif imprime n'a pas a porter d'appel a l'achat, et les
     liens `/?taille=…#buy` n'auraient aucun sens sur papier. */
  .relance {
    display: none !important;
  }
}

/* ===========================================================================
   21. COUCHE PHOTOGRAPHIQUE (item-036)
   ---------------------------------------------------------------------------
   Le client a livre trois photographies de studio (epaule du maillot sur fond
   noir, macro de la broderie fil d'or, coffret anniversaire). Jusqu'ici elles
   n'etaient servies qu'en 960 px de large, dans des colonnes de 40 % : le
   client ne les « voyait pas ». Cette couche leur donne la place qu'elles
   meritent — hero plein cadre, bandes pleine largeur — sans rien casser du
   systeme de surfaces du §4 : aucune nouvelle teinte, uniquement des
   declinaisons ALPHA du bleu nuit deja present dans la palette.

   Trois pieges du fichier, tous evites ici volontairement :
   - les regles de base posees APRES une media query la neutralisent : tous
     les selecteurs ci-dessous sont NOUVEAUX, sauf les surcharges de `.hero*`
     et de `.t-split__figure--portrait`, ou cet ordre est justement l'effet
     recherche et documente a chaque occurrence ;
   - `--color-text-heading` et `--border-color` sont des tokens de CONTEXTE
     CLAIR : `.hero__kicker`, `.hero__meta-item strong` et `.hero__meta` les
     consommaient en dur. Sur le hero photographique (contexte sombre) ils
     deviendraient illisibles — ils sont repasses sur les tokens de surface ;
   - chaque `var()` consommee ici existe dans le §1 (verifie un a un).
   ======================================================================== */

:root {
  /* Voiles photographiques (« scrims »). Ce sont des declinaisons ALPHA du
     SEUL bleu nuit --color-bg-deep-2 (#002543 = rgb(0 37 67)) : aucune teinte
     nouvelle n'entre dans la palette, seule l'opacite varie. Declares dans
     :root parce que la regle d'or du fichier interdit toute valeur
     chromatique ailleurs. */
  --scrim-night-96: rgba(0, 37, 67, 0.96);
  --scrim-night-84: rgba(0, 37, 67, 0.84);
  --scrim-night-42: rgba(0, 37, 67, 0.42);
  --scrim-night-00: rgba(0, 37, 67, 0);

  /* Voile du hero. Le texte occupe toute la hauteur du cadre : la densite de
     la bande centrale est donc dimensionnee pour le PIRE pixel possible de
     la photographie, une rayure BLANCHE pure. A 0,88 d'opacite, un blanc pur
     compose a rgb(36 68 93) : le creme y tient 9,2:1 et l'or clair 5,8:1 —
     tous deux au-dessus du plancher AA, quelle que soit la zone de l'image
     qui se trouve derriere. Le voile se relache a 0,50 sur les 14 % du haut
     et du bas, ou il n'y a pas de texte : c'est la que la photographie
     apparait NETTE. Valeurs re-mesurees au pixel sur rendu reel ensuite
     (voir docs/SITE.md, item-036), jamais estimees.

     Un seul degre vertical, volontairement : une variante « fenetre »
     horizontale avait ete essayee pour degager les marges laterales sur
     grand ecran, mais la composition des deux couches rendait la bande
     centrale quasi opaque (0,96) tout en risquant un banding visible a la
     jonction. Une seule couche est plus previsible, et mesurable. */
  --gradient-hero-scrim: linear-gradient(
    180deg,
    var(--scrim-night-42) 0%,
    var(--scrim-night-84) 12%,
    var(--scrim-night-84) 88%,
    var(--scrim-night-42) 100%
  );

  --hero-photo-min-height: clamp(560px, 84svh, 880px);
  --hero-drift-duration: 32000ms;
  --media-zoom-scale: 1.045;
}

/* --- 21.1 Hero photographique plein cadre --------------------------------- */

/* Surcharge volontaire des regles `.hero`/`.section` des §10 et §13 : meme
   specificite, mais declarees plus loin dans le fichier, donc prioritaires a
   toutes les largeurs. Le rythme vertical est redeclare ici en entier plutot
   que d'heriter de `--section-padding-y`, parce qu'un hero d'image ne se
   dimensionne pas comme une section de texte. */
.hero--feature {
  display: flex;
  align-items: center;
  min-height: var(--hero-photo-min-height);
  padding-block: clamp(var(--space-2xl), 8vw, var(--space-3xl));
  overflow: hidden;
}

.hero--feature .container {
  width: 100%;
}

/* Calque photographique. `.section` pose `isolation: isolate` : ce z-index
   negatif reste confine au hero et ne peut pas passer sous le fond de page.
   Etagement interne : photo (-3) < voile (-2) < grain et guilloche du
   `.section::before` (-1) < contenu. Le grain par-dessus la photo donne le
   rendu argentique voulu par la DA — il n'est pas la par accident. */
.hero__backdrop {
  position: absolute;
  inset: 0;
  z-index: -3;
  overflow: hidden;
  pointer-events: none;
}

.hero__backdrop picture {
  display: block;
  width: 100%;
  height: 100%;
}

.hero__bg {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* L'epaule et l'ecusson sont au tiers inferieur du cadre source : on
     remonte le point d'ancrage pour qu'ils restent visibles sous le texte. */
  object-position: 50% 42%;
  /* Derive tres lent (scale 1.045 -> 1.075) : la photographie respire sans
     jamais attirer l'oeil. Neutralise sous prefers-reduced-motion, plus bas. */
  transform: scale(var(--media-zoom-scale));
  animation: t-hero-drift var(--hero-drift-duration) var(--easing-default) infinite alternate;
}

@keyframes t-hero-drift {
  from {
    transform: scale(1.045) translate3d(0, 0, 0);
  }
  to {
    transform: scale(1.075) translate3d(0, -1.2%, 0);
  }
}

/* Le hero photographique NE PORTE PLUS `.t-watermark` (retire du HTML en
   item-036). Raison mesuree, pas supposee : le sceau filigrane est peint en
   `--surface-watermark-color`, c'est-a-dire en CREME sur une surface sombre,
   a 50 % de hauteur et sur une largeur de `--watermark-size`. Derriere le
   « 130 » creme du hero, il faisait tomber le contraste a 1,00:1 — le texte
   et son fond exactement de la meme couleur. Sur une photographie, ce
   filigrane n'apporte de toute facon plus rien : la matiere vient de l'image.

   Consequence a traiter explicitement : sans `.t-watermark`, le `::after` de
   la section redevient celui de `.hero` (§13), c'est-a-dire le halo or
   respirant d'item-015 — un radial dore a 55 % d'opacite qui eclaircirait le
   haut du voile et ruinerait les contrastes mesures ci-dessous. On le coupe
   net plutot que de le laisser ressusciter par effet de bord. */
.hero--feature::after {
  content: none;
}

/* Le VOILE est porte par `.hero__backdrop::after`, et surtout PAS par
   `.hero--feature::after`.

   ⚠️ Raison, trouvee en mesurant et non par lecture : la section du hero
   porte a la fois `.hero` et `.t-watermark`, et ces deux classes reclament
   le MEME pseudo-element `::after`. Or `.t-watermark::after` (§19.1) le
   transforme en sceau MASQUE (`mask: var(--texture-seal)`), de la taille de
   `--watermark-size` et cale a 50 % de hauteur. Tout `background` pose la
   par une regle ulterieure est donc decoupe a la forme du sceau et confine
   a sa boite : invisible en tant que voile, ET destructeur pour le
   filigrane, dont la teinte vient justement de `background-color`.

   Corollaire documente au passage : le halo or respirant de `.hero::after`
   (item-015) est MORT depuis qu'item-021 a ajoute `t-watermark` au hero —
   meme collision, jamais detectee parce qu'elle ne produit aucune erreur.
   Rien n'est « repare » ici (le filigrane est le comportement voulu depuis
   item-021), mais le piege est desormais ecrit.

   Le voile vit donc dans le `::after` du BACKDROP, qui n'a aucun autre
   pretendant. `.hero__backdrop` etant positionne avec un z-index propre, il
   forme son propre contexte d'empilement : un z-index 1 y passe au-dessus
   de la photographie de maniere garantie, sans dependre de l'etagement
   negatif de la section. */
.hero__backdrop::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: var(--gradient-hero-scrim);
}

/* La guilloche du `.section::before` est pensee pour un APLAT de couleur. En
   blend `screen` par-dessus une photographie elle devient envahissante — et
   elle eclaircit le fond, donc elle ronge le contraste calcule ci-dessus.
   Attenuee ici, sur le seul hero photographique : la matiere reste perceptible
   (c'est la signature « certificat » de la DA), sans plus concurrencer la
   photo ni fausser la mesure. */
.hero--feature {
  --surface-texture-opacity: 0.07;
}

/* Filet d'or en pied de hero : la couture entre la photographie et le creme
   de la section suivante. Sans lui, la bascule sombre -> clair est brutale.
   Porte par la section elle-meme et non par `.section::before` : ce dernier
   est en `mix-blend-mode: screen`, le filet y serait blend donc delave. */
.hero--feature {
  border-bottom: var(--border-width) solid var(--color-secondary);
}

/* --- Reprises de contexte : tokens CLAIRS en dur dans le §13 -------------
   Ces trois regles ne sont pas cosmetiques, elles sont correctives : sur un
   hero sombre, `--color-text-heading` (#11253f) et `--border-color`
   (#e2d9c6, filet « sur clair ») donnaient respectivement un texte quasi
   invisible et un filet trop clair. */

.hero--feature .hero__kicker,
.hero--feature .hero__meta-item strong {
  color: var(--surface-text-heading);
}

.hero--feature .hero__meta {
  border-top-color: var(--surface-border);
}

/* Le « 130 » : `.t-display` (§3) le peint en --color-primary, un bleu qui ne
   tient evidemment pas sur un fond bleu nuit. Sur le hero photographique il
   passe en creme, avec le halo or deja tokenise au §1. */
.hero--feature .t-display {
  color: var(--surface-text-heading);
  text-shadow: var(--glow-text-gold);
}

.hero--feature .hero__crest {
  /* Le blason est fourni sur fond transparent : sur le sombre il gagne une
     ombre portee douce, sinon ses contours or se noient dans la photo. */
  filter: drop-shadow(0 2px 10px rgba(0, 37, 67, 0.7));
}

/* --- 21.2 Bandes photographiques pleine largeur --------------------------
   `.showcase` est un enfant DIRECT de `.section`, jamais de `.container` :
   c'est ce qui lui donne toute la largeur de la fenetre. Il remplace, dans
   « Le concept », le duo texte/image dont les colonnes creaient les « zones
   blanches » signalees par le client en item-026 puis item-031 — une bande
   pleine largeur n'a tout simplement pas de colonne voisine a desequilibrer. */

/* `<picture>` est un element INLINE par defaut : laisse tel quel, il casse le
   dimensionnement des cadres et `object-fit` n'a plus de boite sur laquelle
   operer. On le repasse donc en bloc.

   ⚠️ `height: 100%` est reserve au SEUL cas ou le parent a une hauteur
   DEFINIE (`.pricing-card__visual` porte un aspect-ratio 16/9). Applique a un
   parent de hauteur automatique, il fait occuper au `<picture>` toute la
   hauteur de la figure legende comprise, et la `<figcaption>` se retrouve
   repoussee hors du cadre a `overflow: hidden` : la legende disparait sans
   la moindre erreur. Defaut constate en relisant un rendu reel, pas suppose. */
.t-media-frame picture,
.t-split__figure picture,
.showcase__media {
  display: block;
  width: 100%;
}

.pricing-card__visual picture {
  display: block;
  width: 100%;
  height: 100%;
}

.showcase {
  position: relative;
  margin: var(--space-xl) 0;
  width: 100%;
  overflow: hidden;
  border-block: var(--border-width) solid var(--color-secondary);
  background-color: var(--color-bg-deep-2);
}

/* La bande qui CLOT une section (le coffret) ne doit pas ajouter sa marge au
   padding de section : les deux se cumuleraient en un blanc de ~124 px,
   exactement le defaut de rythme vertical corrige en item-030. */
.showcase:last-child {
  margin-block-end: 0;
}

.showcase__media {
  position: relative;
  overflow: hidden;
}

.showcase__img {
  display: block;
  width: 100%;
  height: auto;
  /* Bande cinematographique : la source est en 3/2 ou 16/9, le cadre la
     recadre en panoramique sans jamais la deformer (`cover`). Borne haute en
     svh pour qu'une bande ne puisse pas occuper tout l'ecran sur un portable
     en paysage. */
  aspect-ratio: 16 / 9;
  max-height: 78svh;
  object-fit: cover;
  object-position: 50% 50%;
  transition: transform var(--transition-slow) var(--easing-editorial);
}

@media (min-width: 900px) {
  /* Cadrage PRODUIT par defaut : 2/1 seulement, parce qu'un objet photographie
     en entier (le coffret) ne supporte pas d'etre tranche — a 2.35/1 le
     couvercle etait coupe net en haut du cadre. */
  .showcase__img {
    aspect-ratio: 2 / 1;
  }

  /* Cadrage CINEMA, reserve aux photos de matiere (la macro de broderie) : la
     ou il n'y a pas d'objet entier a preserver, la bande large est un parti
     pris, pas une mutilation. */
  .showcase--cinema .showcase__img {
    aspect-ratio: 2.35 / 1;
  }
}

/* Variante a legende SOUS l'image, et non posee dessus. Utilisee quand la
   photographie porte deja du texte dans son bas (le coffret affiche
   « MONTRIOND FC 1896-2026 » sur sa facade) : une legende superposee y
   entrerait en concurrence avec la typographie de l'objet. Le pave reprend le
   bleu nuit de la bande, la couture est invisible. */
.showcase--caption-below .showcase__caption {
  position: static;
  padding-block: var(--space-md);
  background: var(--color-bg-deep-2);
}

/* Legende posee DANS la bande, en bas, sur son propre degrade : une legende
   en dehors du cadre casserait la continuite de la bande pleine largeur. */
.showcase__caption {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  /* Le haut de pave est genereux A DESSEIN : il donne au degrade la hauteur
     necessaire pour atteindre sa pleine densite AVANT la premiere ligne de
     texte. Avec l'ancien `--space-md`, le surtitre or tombait dans la zone
     encore claire du degrade et ne valait que 4,25:1 sur la broderie doree
     de la photo — mesure, puis corrige ici. */
  padding-block: var(--space-xl) var(--space-md);
  /* z-index 2 : le reflet au defilement (.t-reflex, §19.3) est en 1 et
     passerait sinon par-dessus le texte de la legende. */
  z-index: 2;
  /* Stops en PIXELS, pas en pourcentage — c'est le point important. En
     pourcentage, la densite du voile a l'endroit du texte depend de la
     hauteur de la legende, donc du nombre de lignes, donc de la largeur de
     l'ecran : le surtitre or passait a 7,9:1 sur ordinateur mais tombait a
     4,1:1 sur mobile, ou la legende est plus haute. Avec un stop a 42 px et
     un pave haut de 48 px (--space-xl), le voile a TOUJOURS atteint sa
     pleine densite avant la premiere ligne, quelle que soit la largeur. */
  background: linear-gradient(
    180deg,
    var(--scrim-night-00) 0,
    var(--scrim-night-84) 42px,
    var(--scrim-night-96) 100%
  );
  color: var(--color-text-inverse);
  font-size: var(--font-size-xs);
  letter-spacing: var(--tracking-wide);
}

.showcase__caption .t-eyebrow {
  color: var(--color-secondary-light);
  margin: 0 0 var(--space-3xs);
}

.showcase__caption p {
  margin: 0;
  color: var(--color-text-inverse);
  max-width: var(--measure-narrow, var(--container-max-width-narrow));
}

/* --- 21.3 Les photographies reagissent au survol -------------------------
   Zoom tres leger, sur `transform` seul (§9.1.4). Les cadres concernes
   portent deja `overflow: hidden` (§13 et §19), c'est lui qui contient le
   debordement du zoom. */

.t-media-frame__img,
.t-split__figure img,
.pricing-card__visual img {
  transition: transform var(--transition-slow) var(--easing-editorial);
}

.t-media-frame:hover .t-media-frame__img,
.t-split__figure:hover img,
.pricing-card:hover .pricing-card__visual img,
.showcase:hover .showcase__img {
  transform: scale(var(--media-zoom-scale));
}

/* Au doigt le survol n'existe pas (§12.3) : aucun etat ne doit rester fige. */
@media (hover: none) {
  .t-media-frame:hover .t-media-frame__img,
  .t-split__figure:hover img,
  .pricing-card:hover .pricing-card__visual img,
  .showcase:hover .showcase__img {
    transform: none;
  }
}

/* --- 21.4 Cadre portrait pour le duo texte/image -------------------------
   item-031 a borne `.t-split__figure img` en 4/3 a partir de 769 px pour
   resorber les blancs. Une photographie cadree en PORTRAIT (l'ecusson du
   maillot) y serait rognee de moitie. Cette regle a la meme specificite mais
   vient APRES la media query d'item-031 : elle prime donc a toutes les
   largeurs — c'est exactement le « piege de l'ordre du fichier » documente
   dans docs/SITE.md, utilise ici a dessein et non subi. */
.t-split__figure--portrait img {
  aspect-ratio: 3 / 4;
  object-fit: cover;
}

/* --- Mouvement reduit (§9.4 — non negociable) ---------------------------- */
@media (prefers-reduced-motion: reduce) {
  .hero__bg {
    animation: none;
    transform: none;
  }

  .t-media-frame__img,
  .t-split__figure img,
  .pricing-card__visual img,
  .showcase__img {
    transition: none;
  }

  .t-media-frame:hover .t-media-frame__img,
  .t-split__figure:hover img,
  .pricing-card:hover .pricing-card__visual img,
  .showcase:hover .showcase__img {
    transform: none;
  }
}

/* --- Contraste renforce --------------------------------------------------- */
@media (prefers-contrast: more) {
  /* Le voile se densifie : en contraste renforce, une photographie visible
     derriere le texte n'est plus un atout mais une gene. */
  .hero__backdrop::after {
    background: var(--scrim-night-96);
  }
}

/* --- Couleurs forcees ----------------------------------------------------- */
@media (forced-colors: active) {
  /* En couleurs forcees le voile n'est plus peint : la photographie passerait
     en pleine lumiere sous le texte systeme. On la retire purement. */
  .hero__backdrop {
    display: none;
  }

  .showcase {
    border-color: CanvasText;
  }

  .showcase__caption {
    background: Canvas;
    color: CanvasText;
  }
}

/* --- Impression ----------------------------------------------------------- */
@media print {
  .hero__backdrop {
    display: none;
  }

  .hero--feature {
    min-height: 0;
  }

  .showcase {
    break-inside: avoid;
  }

  .showcase__caption {
    position: static;
    background: none;
    color: inherit;
  }
}

/* ===========================================================================
   22. FILET DE SECURITE DES APPARITIONS (item-036)
   ---------------------------------------------------------------------------
   Tout le systeme `.reveal` repose sur une classe posee par le JS : sans lui,
   un bloc en `opacity: 0` ou sous rideau ne se montre JAMAIS. Le risque etait
   assume depuis item-015, mais item-036 a montre ce que coute un contenu
   invisible : le client n'a pas vu ses propres photographies.

   `scripting: none` est le signal standard « le script ne tournera pas »
   (navigateur sans JS, lecteur simplifie, certains convertisseurs). On rend
   alors tous les etats d'apparition inoffensifs. Ce filet ne couvre pas une
   ERREUR de script — pour ce cas, la seule garantie reste de ne jamais poser
   d'etat masquant sur un contenu essentiel.
   ======================================================================== */

@media (scripting: none) {
  .reveal,
  [data-reveal-words] {
    opacity: 1 !important;
    transform: none !important;
  }

  .reveal--curtain,
  .reveal--curtain > picture,
  .reveal--curtain > img {
    clip-path: none !important;
  }
}

/* ===========================================================================
   23. COMPOSANTS — PAGE PANIER (/panier/ — item-040)
   ---------------------------------------------------------------------------
   La page d'affectation des billets aux maillots. L'essentiel de son
   interface n'est PAS style ici : le panier lui-meme (liste des tailles,
   quantites, totaux, coordonnees) est rendu dans un SHADOW ROOT par le
   payload du widget, qui porte sa propre feuille
   (`assets/widget/purchase-page.<hash>.css`). Cette section ne style donc
   que la coquille de page autour de ce shadow root.

   Consequence a connaitre avant d'ajouter une regle ici : aucun selecteur
   de ce fichier n'atteint l'interieur du panier. Seules les PROPRIETES
   PERSONNALISEES traversent la frontiere du shadow root — c'est d'ailleurs
   par la que la feuille du panier reprend `--space-*`, `--font-size-*` et
   `--tracking-wider` definis dans le `:root` ci-dessus.
   ======================================================================== */

.panier-main {
  padding-block: var(--space-2xl) var(--space-3xl);
}

.panier-intro {
  max-width: var(--measure);
  margin: 0 auto var(--space-xl);
  text-align: center;
}

.panier-intro .t-lede {
  margin-inline: auto;
}

/* Le panier rendu par le widget est borne a 44rem en interne : le logement
   doit lui laisser au moins cette largeur, sinon il se retrecirait sans
   raison visible depuis sa propre feuille. */
.panier-slot {
  max-width: 46rem;
  margin-inline: auto;
}

/* ---------------------------------------------------------------------------
   PIEGE RENCONTRE POUR DE VRAI ICI — le chrome des deux etapes.

   Les deux jeux de titres (`[data-gotb-step="selection"]` et
   `[data-gotb-step="coordonnees"]`) coexistent dans le HTML et c'est
   l'attribut `hidden` que le panier bascule (`syncPageChrome()` ecrit
   `node.hidden`). Ce masquage ne vient PAS d'une regle de ce fichier : il
   vient de la feuille du NAVIGATEUR, `[hidden] { display: none }`. Or une
   regle d'agent utilisateur est supplantee par la moindre declaration
   `display` d'auteur.

   C'est exactement ce qui s'est produit : `.t-eyebrow` (§6) porte
   `display: inline-block`. Les surtitres « Etape 1 sur 2 » et « Etape 2
   sur 2 » s'affichaient donc TOUS LES DEUX, cote a cote, malgre leur
   attribut `hidden` — et la mesure de contraste au pixel l'a revele en
   trouvant « Etape 2 sur 2 » visible pendant l'etape 1, alors que la
   lecture du HTML seule ne montrait rien d'anormal.

   La regle ci-dessous reaffirme donc le comportement d'agent utilisateur
   pour ces elements precis. `!important` est ici le bon outil et non un
   raccourci : il faut battre `.t-eyebrow` (une classe) depuis un selecteur
   d'attribut, et surtout battre toute classe FUTURE qui porterait un
   `display`. Quand le panier revele un element, il retire l'attribut
   `hidden` : le selecteur ne s'applique plus, rien n'est bloque.

   Corollaire pour la suite : ne jamais compter sur `[hidden]` seul pour
   masquer un element qui porte par ailleurs une classe de ce fichier.
   ------------------------------------------------------------------------ */

[data-gotb-step][hidden] {
  display: none !important;
}

/* ---------------------------------------------------------------------------
   Verrou de vente. Meme signal que partout sur le site : `gotb-sales-locked`
   est posee sur <html> par le bootstrap de phasing, avant tout rendu. Le
   panier lui-meme est RETIRE DU DOM par un script de la page (et pas
   seulement masque) : un panier masque resterait actionnable au clavier et
   son bouton de paiement resterait joignable.
   ------------------------------------------------------------------------ */
.panier-lock {
  display: none;
  max-width: var(--measure-narrow);
  margin-inline: auto;
  text-align: center;
}

html.gotb-sales-locked .panier-lock {
  display: block;
}

html.gotb-sales-locked .panier-live {
  display: none;
}

.panier-lock__card {
  padding: var(--space-lg);
}

.panier-lock__card h2 {
  margin-block: 0 var(--space-sm);
}

.panier-lock__card p {
  margin-block: 0 var(--space-md);
}

.panier-lock__card p:last-child {
  margin-bottom: 0;
}

/* Deux messages exclusifs dans le verrou : avant l'ouverture et apres la
   cloture. On s'appuie sur `data-sales-phase`, pose par le meme bootstrap
   (`pre` ou `ended`), plutot que de dupliquer la logique de dates. */
.panier-lock__pre,
.panier-lock__ended {
  display: none;
}

html[data-sales-phase='pre'] .panier-lock__pre,
html[data-sales-phase='ended'] .panier-lock__ended {
  display: block;
}

/* Repli affiche quand le panier ne peut pas se monter (pas de JavaScript,
   script en echec, shadow DOM indisponible). Il n'est jamais visible en
   fonctionnement normal : c'est le payload, ou le script de secours de la
   page, qui leve son attribut `hidden`. */
.panier-fallback {
  max-width: var(--measure-narrow);
  margin: var(--space-xl) auto 0;
  padding: var(--space-lg);
  text-align: center;
}

.panier-fallback p {
  margin-block: 0 var(--space-md);
}

.panier-fallback p:last-child {
  margin-bottom: 0;
}

/* ---------------------------------------------------------------------------
   23.1 Tuiles de taille devenues des POINTS D'ENTREE (item-040)

   Les 5 tuiles de `.sizes-grid` (§13) etaient des `<div>` purement
   informatifs. Elles sont desormais des `<a>` vers /panier/, chacune
   pre-remplissant un billet sur sa taille. Un `<a>` est un element de
   phrase : sans `display: block` il ne prendrait pas la boite de la carte.

   Le libelle d'action (`.sizes-grid__go`) est VISIBLE et non reserve au
   survol : au doigt le survol n'existe pas (§12.3), et une tuile cliquable
   dont rien n'annonce l'action ne se devine pas.
   ------------------------------------------------------------------------ */

.sizes-grid__item--link {
  display: block;
  color: inherit;
  text-decoration: none;
  /* Tokens reels du fichier (`--transition-fast`, `--easing-default`), et
     non un `--duration-fast` qui n'existe pas : une var() absente dans une
     propriete raccourcie comme `transition` invalide la declaration
     ENTIERE, sans aucune erreur de console (charte, regle n. 6). Verifie
     par recherche avant d'ecrire ces deux lignes. */
  transition:
    border-color var(--transition-fast) var(--easing-default),
    transform var(--transition-fast) var(--easing-default);
}

.sizes-grid__go {
  display: block;
  margin-top: var(--space-2xs);
  font-size: var(--font-size-xs);
  font-weight: 850;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  /* `--surface-link` est reassigne par la classe de surface de la section :
     la tuile suit donc automatiquement son contexte, clair ou sombre, sans
     qu'aucune couleur ne soit posee a la main (voir BRAND.md). */
  color: var(--surface-link);
}

.sizes-grid__item--link:hover,
.sizes-grid__item--link:focus-visible {
  border-color: var(--surface-link);
}

/* Le deplacement reste minime et porte sur `transform` seul (§9.1.4). */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .sizes-grid__item--link:hover {
    transform: translateY(-2px);
  }
}

/* ---------------------------------------------------------------------------
   23.2 Precision tarifaire sous le titre des paliers (item-040)

   Porte dans la feuille et non par un `style=` en ligne : c'est la doctrine
   posee par item-032, qui avait justement rapatrie ici sept
   `margin-inline: auto` ecrits a la main dans le HTML.
   ------------------------------------------------------------------------ */

.pricing-note {
  max-width: var(--measure-narrow);
  margin: calc(-1 * var(--space-sm)) auto var(--space-lg);
  text-align: center;
}

/* ---------------------------------------------------------------------------
   23.3 Le titre d'etape ne doit pas porter d'anneau de focus (item-040)

   A chaque changement d'etape, le panier pose `tabindex="-1"` sur le `<h1>`
   de l'etape et y deplace le focus. C'est voulu et utile : c'est ce qui
   annonce le changement d'etape aux lecteurs d'ecran, et ce qui replace le
   point de depart du clavier en haut du nouveau contenu.

   Mais Chrome fait correspondre `:focus-visible` a ce focus programmatique,
   et la regle generique de focus du socle (§11) peint alors un anneau bleu
   de 2px autour du titre — des le chargement de la page. Mesure : le `<h1>`
   est bien `document.activeElement`, `:focus-visible` correspond, et
   l'anneau calcule vaut `solid 2px rgb(1, 101, 186)`. A l'ecran, cela se lit
   comme un defaut d'affichage, pas comme une aide.

   Retirer l'anneau ICI est sans risque pour l'accessibilite, et ce n'est pas
   un compromis : un element en `tabindex="-1"` n'est JAMAIS atteignable a la
   tabulation. Le seul focus qu'il puisse recevoir est celui que le panier lui
   donne — jamais celui d'un utilisateur au clavier, qui a donc toujours son
   anneau sur les elements qu'il parcourt reellement.

   Le selecteur exige les deux conditions (`[data-gotb-step]` ET
   `[tabindex="-1"]`) : aucun element interactif du site ne peut y repondre
   par accident.
   ------------------------------------------------------------------------ */

[data-gotb-step][tabindex='-1']:focus,
[data-gotb-step][tabindex='-1']:focus-visible {
  outline: none;
}

/* ===========================================================================
   24. COMPOSANTS — LES DEUX ISSUES DU TIRAGE (public/results.html — item-041)
   ---------------------------------------------------------------------------
   Le client a fourni les deux textes a afficher apres le tirage : celui du
   gagnant (modalites de retrait : caisse Ville, 18h30-19h30, tribune D,
   piece d'identite) et celui du participant non tire au sort. Ils vivent dans
   `.results-published` (§16), donc invisibles tant que `html.gotb-drawn`
   n'est pas pose : aucun risque d'annoncer un resultat avant l'heure.

   Volontairement MINUSCULE : le balisage reemploie `.results-card--narrow` et
   `.doc-callout`, deja utilises par le bloc d'attente de cette meme page.
   Tout vient donc des tokens de surface, et aucun couple de contraste
   nouveau n'est introduit — rien a remesurer.

   Une seule chose ne pouvait PAS etre reemployee : la taille du titre. Un
   `<h2>` de page vaut `--font-size-3xl` (jusqu'a 3rem) ; dans un encart de
   la largeur d'une mesure de lecture, il deborde en typographie et ecrase le
   texte qui le suit. Il est ramene au palier d'un titre de carte.
   ======================================================================== */

.results-outcome {
  margin-bottom: var(--space-2xl);
}

.results-outcome h2 {
  font-size: var(--font-size-xl);
}

/* Les deux encarts se suivent : leurs marges verticales se fusionnent a
   `--space-md` (regle `.doc-callout`, §14). On les espace un peu plus pour
   qu'on lise deux messages distincts, pas une suite de paragraphes. */
.results-outcome .doc-callout + .doc-callout {
  margin-top: var(--space-lg);
}
