/* ==========================================================================
   Talent de Demain FC — landing page
   Faithful reproduction of the reference design, with the center jersey
   replaced by a scroll-driven video (see js/main.js).
   ========================================================================== */

/* ---------- Self-hosted fonts (downloaded from Google Fonts, latin subset) -- */

/* Headline display face — closest match to the design's chamfered octagonal
   custom font. */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../assets/fonts/ChakraPetch-Bold.woff2') format('woff2');
}
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../assets/fonts/ChakraPetch-SemiBold.woff2') format('woff2');
}
/* UI / body face — variable font, one file covers 400–800 */
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../assets/fonts/Montserrat-Variable.woff2') format('woff2');
}

/* ---------- Design tokens (values sampled from the reference capture) ------ */
:root {
  --bg: #F0EFED;            /* warm off-white page background        */
  --ink: #101010;           /* near-black text / buttons             */
  --ink-soft: #3c3c3c;      /* body copy                             */
  --red: #C13327;           /* jersey red                            */
  --white: #ffffff;
  --card: #0D0D0D;          /* media card frame                      */

  --font-display: 'Chakra Petch', 'Arial Black', sans-serif;
  --font-ui: 'Montserrat', 'Segoe UI', sans-serif;

  --nav-h: 104px;
  --gutter: clamp(20px, 4vw, 76px);

  /* Video → page blending: lifts the clip's beige studio backdrop
     (~#C6C0BB) to white so `multiply` makes it vanish into --bg. */
  --jersey-filter: brightness(1.32) contrast(1.04) saturate(1.05);

  /* ---- Surfaces -----------------------------------------------------------
     Ces jetons ne changent pas de RÔLE d'un thème à l'autre, seulement de
     valeur. C'est la distinction qui manquait : --ink servait à la fois de
     couleur de texte sur la page ET de fond pour les cartes sombres. En
     thème sombre la première doit s'inverser, la seconde non — sinon une
     carte noire devient blanche. */
  --panel: #0D0D0D;         /* carte sombre posée sur la page               */
  --on-dark: #ffffff;       /* texte sur toute surface sombre               */
  --band: #101010;          /* bande pleine largeur inversée (outro, film)  */
  --surface: #ffffff;       /* carte claire (event-card)                    */
  --btn-bg: #101010;
  --btn-ink: #ffffff;
  --btn-alt-bg: #ffffff;    /* bouton posé DANS une bande inversée          */
  --btn-alt-ink: #101010;

  /* Filets, survols et panneaux translucides : ils s'inversent. */
  --line: rgba(16, 16, 16, .18);
  --line-strong: rgba(16, 16, 16, .24);
  --line-soft: rgba(16, 16, 16, .06);
  --tint: rgba(16, 16, 16, .05);
  --glass: rgba(240, 239, 237, .86);
  --glass-strong: rgba(240, 239, 237, .97);
  --glass-soft: rgba(240, 239, 237, .72);
}

/* ---------- Thème sombre ---------------------------------------------------
   Il n'existe pas seulement par confort : sans lui, Samsung Internet et le
   mode sombre automatique de Chrome inversent la page eux-mêmes, et le
   résultat est mauvais. Mesuré : déclarer color-scheme: light ne les arrête
   PAS — ils visent justement les pages sans thème sombre. Fournir le thème
   est le seul moyen de reprendre la main, et c'est vérifié.

   Deux déclencheurs, mêmes valeurs :
     - la préférence du système, SAUF si le visiteur a choisi « clair » ;
     - le choix explicite du visiteur (bouton de la barre, js/theme.js).
   D'où le doublon de sélecteurs : une règle ne peut pas être à la fois
   dans une media query et hors d'elle.

   Le fond n'est pas noir mais un brun très sombre, écho du blanc cassé chaud
   du thème clair ; le texte n'est pas blanc pur, qui vibre. Les cartes
   deviennent plus CLAIRES que la page, pour rester lisibles comme des
   surfaces posées. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #141211;
    --ink: #EDEAE6;
    --ink-soft: #A8A39D;
    --red: #E2543F;         /* remonté : le rouge d'origine manque de contraste */

    --panel: #211F1C;
    --on-dark: #F3F1EE;
    --band: #1C1A18;
    --surface: #211F1C;
    --btn-bg: #EDEAE6;
    --btn-ink: #141211;
    --btn-alt-bg: #EDEAE6;
    --btn-alt-ink: #141211;

    --line: rgba(255, 255, 255, .17);
    --line-strong: rgba(255, 255, 255, .27);
    --line-soft: rgba(255, 255, 255, .08);
    --tint: rgba(255, 255, 255, .07);
    --glass: rgba(26, 24, 22, .86);
    --glass-strong: rgba(20, 18, 17, .97);
    --glass-soft: rgba(26, 24, 22, .74);
  }
}
:root[data-theme="dark"] {
    --bg: #141211;
    --ink: #EDEAE6;
    --ink-soft: #A8A39D;
    --red: #E2543F;         /* remonté : le rouge d'origine manque de contraste */

    --panel: #211F1C;
    --on-dark: #F3F1EE;
    --band: #1C1A18;
    --surface: #211F1C;
    --btn-bg: #EDEAE6;
    --btn-ink: #141211;
    --btn-alt-bg: #EDEAE6;
    --btn-alt-ink: #141211;

    --line: rgba(255, 255, 255, .17);
    --line-strong: rgba(255, 255, 255, .27);
    --line-soft: rgba(255, 255, 255, .08);
    --tint: rgba(255, 255, 255, .07);
    --glass: rgba(26, 24, 22, .86);
    --glass-strong: rgba(20, 18, 17, .97);
    --glass-soft: rgba(26, 24, 22, .74);
}

/* ---------- Le héro reste clair, même en thème sombre -----------------------
   Le maillot est détouré en temps réel contre le fond clair du studio. Sur
   fond sombre, ses pixels de bord — qui portent ce fond clair — dessinent une
   frange lumineuse : le vêtement paraît découpé aux ciseaux, et c'est ce qui
   a été pris pour une perte de qualité de la vidéo.
   La zone du héro rejoue donc les valeurs claires, sur un gris légèrement
   assombri qui s'accorde au reste du thème sombre sans trahir le détourage.
   Les propriétés personnalisées cascadent : tout ce qui vit dans le héro
   (titre, légendes, compteur, bouton, carte média) suit sans une seule règle
   supplémentaire. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .hero-track {
    --bg: #D2CFCA;
    --ink: #141211;
    --ink-soft: #3F3C39;
    --red: #C13327;
    --panel: #0D0D0D;
    --on-dark: #ffffff;
    --band: #101010;
    --btn-bg: #101010;
    --btn-ink: #ffffff;
    --line: rgba(16, 16, 16, .18);
    --line-strong: rgba(16, 16, 16, .24);
    --line-soft: rgba(16, 16, 16, .06);
    --tint: rgba(16, 16, 16, .05);
    --glass: rgba(210, 207, 202, .86);
    --glass-soft: rgba(210, 207, 202, .72);
    /* Fondu vers la page sur les derniers pixels de la piste. La page n'est
       pas assez haute pour que le héro sorte entièrement : il en reste ~84px
       derrière la barre en bas de page. En thème clair c'est invisible, en
       sombre cela dessinait une couture grise au-dessus de la section finale.
       La couleur de fin est le MÊME gris en alpha 0, pas `transparent` : ce
       dernier vaut rgba(0,0,0,0) et teinte le dégradé en gris sale. */
    background: linear-gradient(to bottom,
                var(--bg) calc(100% - 170px), rgba(210, 207, 202, 0));
    color: var(--ink);
  }
  :root:not([data-theme="light"]).is-over-hero .nav {
    --bg: #D2CFCA;
    --ink: #141211;
    --line-strong: rgba(16, 16, 16, .24);
    --tint: rgba(16, 16, 16, .05);
    color: var(--ink);
  }
}
:root[data-theme="dark"] .hero-track {
    --bg: #D2CFCA;
    --ink: #141211;
    --ink-soft: #3F3C39;
    --red: #C13327;
    --panel: #0D0D0D;
    --on-dark: #ffffff;
    --band: #101010;
    --btn-bg: #101010;
    --btn-ink: #ffffff;
    --line: rgba(16, 16, 16, .18);
    --line-strong: rgba(16, 16, 16, .24);
    --line-soft: rgba(16, 16, 16, .06);
    --tint: rgba(16, 16, 16, .05);
    --glass: rgba(210, 207, 202, .86);
    --glass-soft: rgba(210, 207, 202, .72);
    /* Fondu vers la page sur les derniers pixels de la piste. La page n'est
       pas assez haute pour que le héro sorte entièrement : il en reste ~84px
       derrière la barre en bas de page. En thème clair c'est invisible, en
       sombre cela dessinait une couture grise au-dessus de la section finale.
       La couleur de fin est le MÊME gris en alpha 0, pas `transparent` : ce
       dernier vaut rgba(0,0,0,0) et teinte le dégradé en gris sale. */
    background: linear-gradient(to bottom,
                var(--bg) calc(100% - 170px), rgba(210, 207, 202, 0));
    color: var(--ink);
}
:root[data-theme="dark"].is-over-hero .nav {
    --bg: #D2CFCA;
    --ink: #141211;
    --line-strong: rgba(16, 16, 16, .24);
    --tint: rgba(16, 16, 16, .05);
    color: var(--ink);
}

/* Le site sait faire les deux thèmes, et le DIT — c'est cette déclaration,
   pas un choix de confort, qui coupe l'assombrissement sauvage.
   Mesuré : `color-scheme: light` seul ne protège de rien, Samsung Internet et
   le mode sombre de Chrome inversent la page malgré lui. C'est cohérent avec
   leur but, qui est de traiter les pages DÉPOURVUES de thème sombre — leur
   dire « je suis clair » leur confirme d'agir. Avec `light dark` et le bloc
   prefers-color-scheme plus haut, ils cessent d'inverser et appliquent nos
   couleurs : vérifié en posant une teinte témoin, c'est bien la nôtre qui
   est peinte. Ne pas retirer l'un sans l'autre. */
:root { color-scheme: light dark; }

/* ---------- Reset / base ---------------------------------------------------
   REMISE EN PLACE : mon script de refonte du thème sombre avait découpé le
   fichier entre deux repères et emporté ce bloc au passage. Conséquences
   visibles sur téléphone — le corps de page reprenait la marge de 8px du
   navigateur (bandes sombres sur les côtés, page qui ne remplissait plus la
   largeur) et, sans box-sizing, tout élément à rembourrage devenait plus
   large que prévu. Ne pas retirer. */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html { scroll-behavior: smooth; }

body {
  font-family: var(--font-ui);
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, video, canvas { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

/* ==========================================================================
   Navigation
   ========================================================================== */
.nav {
  position: fixed;
  inset: 0 0 auto 0;
  /* above .nav-panel (60): the close button and the crest must stay
     reachable while the panel is open */
  z-index: 80;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--gutter);
  /* soft fade so the giant headline slides under it while scrolling */
  background: linear-gradient(to bottom, var(--bg) 72%, transparent);
}

.nav__brand img {
  width: 52px;
  height: auto;
  /* The PNG now carries real transparency (tools/cutout_logo.py). It used to
     be an opaque RGB file whose white rectangle was hidden by
     mix-blend-mode: multiply — but Brave on Android does not always composite
     that blend, which left a visible white band under the crest on a real
     phone. Cutting the file is the fix; no blend mode to depend on. */
}

.nav__menu {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: clamp(20px, 3.2vw, 56px);
}

.nav__menu a {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  /* 44px de haut : mesuré à 35px. En rendu « en ligne » le rembourrage
     vertical ne crée pas de boîte cliquable fiable. La barre ayant une
     hauteur fixe, le supplément ne déplace rien. */
  display: inline-block;
  padding: 15px 4px;
  transition: opacity .2s ease;
}
.nav__menu a:hover { opacity: .55; }

.nav__actions { display: flex; align-items: center; gap: clamp(12px, 1.6vw, 26px); }
.icon-btn svg { width: 24px; height: 24px; display: block; }
/* 44px hit area (the icon stays 24px) — negative margin keeps the layout */
.icon-btn {
  padding: 10px;
  margin: -10px;
  transition: opacity .2s ease;
}
.icon-btn:hover { opacity: .55; }

/* ---- Language switch (built by js/i18n.js) ---- */
.lang-btn {
  min-width: 44px;
  padding: 9px 10px;
  position: relative;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: .12em;
  color: var(--ink);
  transition: border-color .2s ease, background .2s ease;
}
/* La zone cliquable atteint 44px SANS toucher à l'aspect du bouton : un
   calque transparent l'étend au-delà de sa bordure. Grossir le rembourrage
   fonctionnait aussi, mais rendait le bouton visiblement plus haut — et le
   rendu validé ne devait pas bouger. */
.lang-btn::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
}
.lang-btn:hover { border-color: var(--ink); background: var(--tint); }
.nav-open .lang-btn { opacity: 0; pointer-events: none; }

/* ---- Menu panel (built by js/nav.js) ---- */
.icon-btn .icon-close { display: none; }
.nav-open .icon-btn .icon-bars { display: none; }
.nav-open .icon-btn .icon-close { display: block; }

.nav-panel {
  position: fixed;
  inset: 0;
  z-index: 60;                     /* above the fixed header */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(24px, 5vh, 52px);
  padding: calc(var(--nav-h) + 24px) var(--gutter) 40px;
  background: var(--glass-strong);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  opacity: 0;
  visibility: hidden;
  transition: opacity .35s var(--ease-out-expo), visibility .35s;
}
.nav-open .nav-panel { opacity: 1; visibility: visible; }

.nav-panel__links {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(4px, 1.4vh, 14px);
}

.nav-panel__links a {
  display: block;
  padding: 10px 16px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(30px, 8vw, 56px);
  line-height: 1.05;
  letter-spacing: .01em;
  text-transform: uppercase;
  color: var(--ink);
  /* staggered rise, released with the panel */
  opacity: 0;
  transform: translateY(16px);
  transition: opacity .4s var(--ease-out-expo), transform .4s var(--ease-out-expo);
}
.nav-open .nav-panel__links a { opacity: 1; transform: none; }
.nav-panel__links a:nth-child(1) { transition-delay: .06s; }
.nav-panel__links a:nth-child(2) { transition-delay: .11s; }
.nav-panel__links a:nth-child(3) { transition-delay: .16s; }
.nav-panel__links a:nth-child(4) { transition-delay: .21s; }
.nav-panel__links a:nth-child(5) { transition-delay: .26s; }
.nav-panel__links a.is-active { color: var(--red); }

.nav-panel__extra {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
}
.nav-panel__extra .btn { margin-top: 0; }

.nav-panel__social { display: flex; gap: 10px; }
.nav-panel__social a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--ink);
  transition: border-color .2s ease, transform .2s ease;
}
.nav-panel__social a:hover { border-color: var(--ink); transform: translateY(-2px); }
.nav-panel__social svg { width: 20px; height: 20px; }

@media (prefers-reduced-motion: reduce) {
  .nav-panel, .nav-panel__links a { transition: none; }
}

/* ==========================================================================
   Hero — pinned section, scroll scrubs the jersey video
   ========================================================================== */
.hero-track {
  /* Scroll runway: the sticky viewport stays pinned while the user scrolls
     through this distance — that scroll maps to video time. */
  height: 480vh;
  position: relative;
}

.hero-sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  /* dvh et non svh. svh est la hauteur PETITE de la fenêtre — celle qu'on a
     quand les barres du navigateur sont visibles. Dès qu'elles se rétractent,
     la zone visible grandit mais le héro restait à sa taille : il restait
     178px de vide sous lui, et le bloc de texte, ancré à son bas, s'arrêtait
     au milieu de l'écran. Constaté sur un Galaxy S23+ à l'enregistrement
     d'écran ; aucun émulateur ne distingue ces trois unités.
     dvh suit la zone réellement visible : le héro la remplit toujours. Le
     changement de hauteur qui en découle est absorbé par la transition
     posée plus bas. */
  height: 100dvh;
  overflow: hidden;
  background: var(--bg);
  isolation: isolate;
}

/* ---- Giant display headline (behind the jersey) ---- */
.hero-title {
  position: absolute;
  /* Anchored to the TOP (≈ under the nav, like the reference) instead of the
     vertical center. Centering made the block drop into the bottom-left copy
     on short-but-wide viewports (e.g. 1366×768 laptops). Top-anchoring keeps
     the block height and the copy zone independent. */
  top: 14vh;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  z-index: 1;
  user-select: none;
  pointer-events: none;
}

.hero-title__line {
  display: block;
  /* Masks the per-letter rise: chars start translated below this box */
  overflow: hidden;
  font-family: var(--font-display);
  font-weight: 700;
  /* Bounded by BOTH width and height: on short viewports the vh cap shrinks
     the type so the second line always clears the copy block below. */
  font-size: min(15.4vw, 23vh);
  line-height: .88;
  letter-spacing: .005em;
  white-space: nowrap;
  color: var(--ink);
  will-change: transform;
}

/* Kinetic entrance: each letter (split by main.js) rises out of the line's
   mask with a staggered expo ease — then the whole line keeps moving with
   the scroll (drift + depth recession set per-frame by the scrub loop). */
.hero-title__line .char {
  display: inline-block;
  transform: translateY(118%);
  transition: transform .9s cubic-bezier(.16, 1, .3, 1);
  transition-delay: calc(var(--i, 0) * 38ms);
  will-change: transform;
}
.hero-title__line.is-in .char { transform: none; }

@media (prefers-reduced-motion: reduce) {
  .hero-title__line .char { transition: none; transform: none; }
}

/* ---- Scroll-driven jersey video ---- */
.jersey {
  position: absolute;
  /* Bande pleine largeur, et le maillot est centré PAR LE FLUX.
     Auparavant : left: 50% + transform: translateX(-50%). Ce pourcentage se
     résout contre la largeur propre de l'élément, elle-même déduite
     d'aspect-ratio ; sur téléphone réel cette largeur n'est pas toujours
     résolue au moment du calcul, le décalage vaut alors 0 et le maillot
     déborde à droite. Constaté deux fois en QA sur appareil, jamais
     reproduit en émulation. Plus aucun pourcentage ici : plus de fragilité.
     Corollaire : js/main.js n'écrit plus que translateY + scale. */
  left: 0;
  right: 0;
  /* Sits low in the hero: the collar starts inside the lower third of the
     "DEMAIN FC" line, so both headline lines stay readable. */
  bottom: -8vh;
  display: flex;
  justify-content: center;
  align-items: flex-end;
  /* Scale anchors at the hem: the jersey grows UPWARD into the foreground
     while its floor shadow stays put (see the scale in main.js). */
  transform-origin: 50% 100%;
  z-index: 2;
  height: 66vh;
  pointer-events: none;
  will-change: transform;
}

.jersey video { display: none; /* frame source only — painted on the canvas */ }

.jersey canvas,
.jersey__fallback {
  /* Le rapport 5:6 vit ici (source 900×1080), plus sur la boîte positionnée :
     c'est ce qui permet à la bande d'être pleine largeur et au maillot d'être
     centré sans transform. */
  height: 100%;
  width: auto;
  aspect-ratio: 5 / 6;
  /* The WebGL renderer keys out the clip's studio backdrop in real time
     (see js/main.js) — the canvas holds a true transparent cutout of the
     jersey, so it melts into the page and occludes the headline exactly
     along its silhouette. Edge masks are kept as a safety net (they also
     guarantee no corner watermark can ever show). */
  -webkit-mask-image:
    linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%),
    linear-gradient(180deg, transparent 0, #000 5%, #000 97%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%),
    linear-gradient(180deg, transparent 0, #000 5%, #000 97%, transparent 100%);
  mask-composite: intersect;
}

/* Fallback when WebGL is unavailable: opaque frames, backdrop lifted to
   the page color and blended away with multiply. The blend sits on the
   .jersey wrapper (not the canvas) so it composites against the page
   background inside the hero's isolated stacking context. */
.no-webgl .jersey { mix-blend-mode: multiply; }
.no-webgl .jersey canvas,
.jersey__fallback {
  filter: var(--jersey-filter);
}
.jersey__fallback { mix-blend-mode: multiply; }

/* ---- Bottom-left copy block ---- */
.hero-copy {
  position: absolute;
  left: var(--gutter);
  bottom: clamp(28px, 6vh, 64px);
  z-index: 3;
  max-width: 430px;
}

.hero-copy__title {
  font-family: var(--font-ui);
  font-weight: 800;
  font-size: clamp(20px, 1.85vw, 30px);
  line-height: 1.14;
  letter-spacing: .01em;
  text-transform: uppercase;
}

.hero-copy__text {
  margin-top: 18px;
  font-size: clamp(14px, 1vw, 16px);
  font-weight: 500;
  line-height: 1.5;
  color: var(--ink-soft);
  max-width: 360px;
}

/* ---- Button ---- */
.btn {
  display: inline-block;
  margin-top: 26px;
  padding: 17px 30px;
  background: var(--btn-bg);
  color: var(--btn-ink);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  border-radius: 10px;
  transition: transform .25s ease, box-shadow .25s ease;
}
.btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 12px 28px var(--line-strong);
}
.btn--light { background: var(--btn-alt-bg); color: var(--btn-alt-ink); }

/* ---- Scroll-synced story captions (opacity/motion driven by main.js) ---- */
/* Repère technique : un trait rouge se tire vers le maillot, puis un volet
   rouge balaie la plaque et libère le texte en repartant. C'est le
   vocabulaire du graphisme de diffusion sportive croisé avec la fiche
   technique produit — le maillot devient un objet documenté.
   --t est la progression d'entrée du repère, posée par js/main.js. */
.story__item::before {
  content: '';
  position: absolute;
  top: 50%;
  height: 1.5px;
  background: var(--red);
  width: calc(var(--t, 0) * 46px);
  /* Le trait pointe VERS le maillot, donc vers le centre : à droite pour un
     repère de gauche, à gauche pour un repère de droite. Dans l'autre sens
     il désignait le bord de l'écran. */
  left: 100%;
  margin-left: 10px;
  pointer-events: none;
}
.story__item--right::before { left: auto; right: 100%; margin: 0 10px 0 0; }
.story__item > * { position: relative; z-index: 1; }

.story__item {
  position: absolute;
  /* 46% par defaut ; js/main.js abaisse cette valeur quand le bloc situe
     dessous (texte a gauche, carte a droite) remonte jusqu'a la legende */
  top: var(--story-top-l, 46%);
  z-index: 3;
  max-width: 230px;
  opacity: 0;                    /* JS animates in sync with the scrub */
  pointer-events: none;
  will-change: opacity, transform;
}
.story__item--left  { left: var(--gutter); }
.story__item--right { right: var(--gutter); text-align: right;
                      top: var(--story-top-r, 46%); }

.story__kicker {
  display: inline-block;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: .2em;
  color: var(--red);
  padding-bottom: 6px;
  border-bottom: 2px solid var(--ink);
  margin-bottom: 10px;
}

.story__title {
  font-size: 17px;
  font-weight: 800;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.story__text {
  margin-top: 6px;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.45;
  color: var(--ink-soft);
}

/* ---- Rotation meter: "SCROLL" hint at rest, live degrees while scrubbing */
.rotation-meter {
  position: absolute;
  left: 50%;
  /* 16px par defaut ; js/main.js le releve quand le bloc de texte atteint
     le centre de l'ecran (ecrans etroits) */
  bottom: var(--meter-bottom, 16px);
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 12px;
  pointer-events: none;
  /* HUD chip: stays readable when the enlarged jersey passes behind it */
  padding: 9px 16px;
  border-radius: 999px;
  background: var(--glass-soft);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  box-shadow: 0 1px 0 var(--line-soft);
}

.rotation-meter__label,
.rotation-meter__deg {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--ink);
}

.rotation-meter__deg { min-width: 44px; text-align: left; }

/* La jauge se lit comme un cadran, pas comme une barre de progression :
   un cran tous les 10 degrés, et un curseur rouge qui parcourt la
   graduation. Les crans déjà parcourus passent au rouge. */
.rotation-meter__line {
  position: relative;
  width: 150px;
  height: 12px;
  background-image: repeating-linear-gradient(90deg,
    var(--line-strong) 0 1px, transparent 1px 10px);
  background-size: 100% 6px;
  background-position: center;
  background-repeat: no-repeat;
}
.rotation-meter__line i {
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(90deg,
    var(--red) 0 1px, transparent 1px 10px);
  background-size: 100% 6px;
  background-position: center;
  background-repeat: no-repeat;
  /* découpe, et non mise à l'échelle : étirer déformerait la graduation */
  clip-path: inset(0 100% 0 0);
  will-change: clip-path;
}
/* Le curseur : c'est de lui que part la traînée de fin de héro. */
.rotation-meter__line::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  left: var(--p, 0%);
  width: 2px;
  margin-left: -1px;
  background: var(--red);
}

/* ---- Bottom-right media card (phone-style frame) ---- */
.media-card {
  position: absolute;
  right: var(--gutter);
  bottom: clamp(28px, 6vh, 64px);
  z-index: 3;
  width: clamp(240px, 17.5vw, 340px);
  background: var(--panel);
  border-radius: 26px;
  padding: 10px;
  box-shadow: 0 24px 48px var(--line-strong);
}

.media-card__screen {
  position: relative;
  border-radius: 18px;
  overflow: hidden;
  aspect-ratio: 16 / 10.2;
}

.media-card__screen img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* vertical scrub progress line inside the card (mirrors video progress) */
.media-card__progress {
  position: absolute;
  top: 12px;
  bottom: 12px;
  right: 10px;
  width: 4px;
  border-radius: 2px;
  background: rgba(255, 255, 255, .28);
}
.media-card__progress i {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 2px;
  background: var(--on-dark);
  transform-origin: top;
  transform: scaleY(0);
  will-change: transform;
}

/* ==========================================================================
   Outro
   ========================================================================== */
.outro {
  position: relative;
  z-index: 4;
  background: var(--band);
  color: var(--on-dark);
  min-height: 72vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 96px var(--gutter) 48px;
}

.outro__kicker {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .3em;
  text-transform: uppercase;
  opacity: .55;
}

.outro__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(30px, 4.6vw, 72px);
  text-transform: uppercase;
  margin-top: 18px;
  max-width: 18ch;
}

.outro__footer {
  margin-top: auto;
  padding-top: 72px;
  font-size: 12px;
  letter-spacing: .08em;
  text-transform: uppercase;
  opacity: .4;
}

/* Official sponsor sits above the copyright, slightly stronger */
.outro__sponsor {
  /* Deux blocs : côte à côte tant que ça tient, empilés et centrés sinon.
     Le nom du sponsor ne se coupe donc jamais en deux. */
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 2px .7em;
  margin-bottom: 12px;
  padding-bottom: 12px;
  border-bottom: 1px solid rgba(255, 255, 255, .14);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .24em;
}
.outro__sponsor strong { letter-spacing: .16em; }
/* Le tiret n'a de sens que sur une seule ligne — il est porté par le libellé
   et disparaît avec l'empilement. */
.outro__sponsor-label::after { content: '—'; margin-left: .7em; opacity: .55; }

/* ==========================================================================
   Load-in choreography
   Every element starts offset + transparent and settles into place when
   <html> gets `is-ready` (set by main.js). The start state is scoped to
   `.js-anim` — a one-line inline script in <head> — so the page is never
   left invisible if JavaScript is disabled or fails to run.
   Each rule keeps the element's own hover transition intact.
   ========================================================================== */
:root { --ease-out-expo: cubic-bezier(.16, 1, .3, 1); }

.js-anim .nav__brand,
.js-anim .nav__menu,
.js-anim .nav__actions,
.js-anim .hero-copy__title,
.js-anim .hero-copy__text,
.js-anim .media-card {
  opacity: 0;
  transition: opacity .85s var(--ease-out-expo) var(--d, 0s),
              transform .85s var(--ease-out-expo) var(--d, 0s);
}

/* Start positions */
.js-anim .nav__brand,
.js-anim .nav__menu,
.js-anim .nav__actions   { transform: translateY(-14px); }
.js-anim .hero-copy__title,
.js-anim .hero-copy__text { transform: translateY(26px); }
.js-anim .media-card      { transform: translate(48px, 14px); }

/* .nav__menu is centered with translateX(-50%) — keep it while rising */
.js-anim .nav__menu { transform: translate(-50%, -14px); }

/* Nav links rise individually; their hover opacity transition is preserved */
.js-anim .nav__menu a {
  transform: translateY(-10px);
  transition: transform .8s var(--ease-out-expo) var(--d, 0s),
              opacity .2s ease;
}

/* The CTA fades only — its hover transform/shadow timing stays untouched */
.js-anim .hero-copy .btn {
  opacity: 0;
  transition: opacity .85s var(--ease-out-expo) var(--d, 0s),
              transform .25s ease, box-shadow .25s ease;
}

/* Meter: centered with translateX(-50%) */
.js-anim .rotation-meter {
  opacity: 0;
  transform: translate(-50%, 18px);
  transition: opacity .85s var(--ease-out-expo) var(--d, 0s),
              transform .85s var(--ease-out-expo) var(--d, 0s);
}

/* The jersey fades only: its transform is written every frame by the scrub
   loop, so a transform transition here would fight the per-frame updates. */
.js-anim .jersey {
  opacity: 0;
  transition: opacity 1.1s var(--ease-out-expo) .25s;
}

/* Stagger — nav first, then the copy column, then the side elements */
.js-anim .nav__brand              { --d: .05s; }
.js-anim .nav__menu               { --d: .12s; }
.js-anim .nav__menu a:nth-child(1) { --d: .18s; }
.js-anim .nav__menu a:nth-child(2) { --d: .24s; }
.js-anim .nav__menu a:nth-child(3) { --d: .30s; }
.js-anim .nav__menu a:nth-child(4) { --d: .36s; }
.js-anim .nav__menu a:nth-child(5) { --d: .42s; }
.js-anim .nav__actions            { --d: .20s; }
.js-anim .hero-copy__title        { --d: .60s; }
.js-anim .hero-copy__text         { --d: .70s; }
.js-anim .hero-copy .btn          { --d: .80s; }
.js-anim .media-card              { --d: .90s; }
.js-anim .rotation-meter          { --d: 1.05s; }

/* Settled state */
.js-anim.is-ready .nav__brand,
.js-anim.is-ready .nav__actions,
.js-anim.is-ready .nav__menu a,
.js-anim.is-ready .hero-copy__title,
.js-anim.is-ready .hero-copy__text,
.js-anim.is-ready .hero-copy .btn,
.js-anim.is-ready .media-card,
.js-anim.is-ready .jersey { opacity: 1; }

.js-anim.is-ready .nav__brand,
.js-anim.is-ready .nav__actions,
.js-anim.is-ready .nav__menu a,
.js-anim.is-ready .hero-copy__title,
.js-anim.is-ready .hero-copy__text,
.js-anim.is-ready .media-card     { transform: none; }

.js-anim.is-ready .nav__menu      { opacity: 1; transform: translateX(-50%); }
.js-anim.is-ready .rotation-meter { opacity: 1; transform: translate(-50%, 0); }

@media (prefers-reduced-motion: reduce) {
  .js-anim .nav__brand,
  .js-anim .nav__actions,
  .js-anim .nav__menu a,
  .js-anim .hero-copy__title,
  .js-anim .hero-copy__text,
  .js-anim .hero-copy .btn,
  .js-anim .media-card,
  .js-anim .jersey      { opacity: 1; transform: none; transition: none; }
  .js-anim .nav__menu   { opacity: 1; transform: translateX(-50%); transition: none; }
  .js-anim .rotation-meter { opacity: 1; transform: translate(-50%, 0); transition: none; }
}

/* ==========================================================================
   Responsive
   ========================================================================== */
/* ==========================================================================
   Below 900px the hero stacks: headline under the nav, jersey in the middle
   band, copy at the foot. Centering the title (as on desktop) had put it
   straight behind the jersey, which hid it completely.

   The narrative layer is NOT dropped here — same experience on every
   device. The floating side captions have no room next to the jersey, so
   they become one centred strip in the gap between the headline and the
   jersey, with the compact rotation meter right under it (its old
   bottom-centre spot collided with the CTA at these sizes).
   ========================================================================== */
@media (max-width: 900px) {
  :root { --nav-h: 76px; }

  .nav__brand img { width: 42px; }

  /* Course de défilement du héro sur tactile.
     Elle ne règle pas seulement la durée du héro : elle règle la FINESSE
     perçue de la rotation. La séquence compte 193 pas ; répartis sur 2030px
     de défilement (mesuré sur l'appareil du client), cela faisait un pas tous
     les 10,5px, soit ~14 pas/s au doigt lent — pendant que la page, elle,
     affichait 60 images/s. D'où une rotation moins fluide que sur desktop,
     où la molette avance par crans bien plus larges et masque le phénomène.
     À 235vh la course tombe à ~1015px, soit 5,3px par pas et ~28,5 pas/s —
     la densité qu'aurait donnée le doublement des images, obtenue sans une
     image de plus ni un octet de plus. Réglable à la volée par ?piste=<vh>
     (voir js/main.js) : le bon réglage est une affaire de ressenti.
     Le desktop garde sa course de 480vh (ligne 227) : sa pagination à la
     molette n'a jamais posé ce problème. */
  .hero-track { height: 235vh; }

  .hero-title {
    top: calc(var(--nav-h) + 6px);
    transform: translateX(-50%);
  }
  /* Le titre est un décor de fond que le maillot traverse : plus il est
     grand, plus la superposition a de force. Il déborde volontairement de
     l'écran en largeur, .hero-title étant masqué en débordement. */
  .hero-title__line { font-size: min(16vw, 11vh); }

  /* Named so the caption strip can be anchored to the top of the jersey.
     Short viewports get a smaller jersey: at 900x800 the band between the
     headline and the jersey was 16px, which fits nothing. */
  /* NE PAS ancrer le maillot sur --copy-clear. Essayé, régression sur
     téléphone réel : --copy-clear vaut « hauteur du bloc de texte + 36px »,
     soit ~400px en français, ce qui projetait le maillot en haut de l'écran.
     Le recouvrement que cela visait était celui des BOÎTES, largement
     transparent — pas des pixels visibles. */
  .hero-sticky { --jersey-h: 40vh; --jersey-bottom: 31vh; }
  /* Quand les barres du navigateur se rétractent, la hauteur de fenêtre
     change et la composition se recalcule. Sans transition, l'ajustement
     claque en plein défilement. Avec, il se pose. Seuls la hauteur et
     l'ancrage sont concernés : la rotation, elle, est écrite à chaque image
     et ne doit surtout pas être transitionnée. */
  .jersey { height: var(--jersey-h); bottom: var(--jersey-bottom);
            transition: height .34s var(--ease-out-expo),
                        bottom .34s var(--ease-out-expo); }
  .rotation-meter, .story__item { transition: bottom .34s var(--ease-out-expo); }

  .hero-copy {
    max-width: none;
    right: var(--gutter);
    bottom: 22px;
  }
  .hero-copy__title { font-size: 21px; }
  .hero-copy__text  { margin-top: 12px; max-width: none; }
  .hero-copy .btn   { margin-top: 18px; }

  /* Captions: one centred strip in the headline→jersey gap. They are all
     absolutely stacked on the same spot — the scrub shows one at a time. */
  .story__item,
  .story__item--left,
  .story__item--right {
    left: var(--gutter);
    right: auto;
    top: auto;
    /* Dans la bande libre SOUS le maillot, juste au-dessus du compteur.
       En haut, quelle que soit sa largeur, la légende masquait le grand
       titre : celui-ci fait 16vw et occupe toute la largeur, il n'existe
       aucune position là-haut qui ne cache rien.
       Ici elle ne recouvre ni le titre, ni le vêtement — la boîte du maillot
       a une marge transparente sous l'ourlet. Alignée à gauche ou à droite
       selon la légende, comme sur desktop. Sur une seule ligne : la bande
       fait une trentaine de pixels. */
    top: auto;
    bottom: var(--story-bottom, 32%);
    max-width: 82%;
    text-align: left;
    display: flex;
    align-items: baseline;
    gap: 9px;
    padding: 5px 11px;
  }
  .story__item--right {
    left: auto;
    right: var(--gutter);
    text-align: right;
  }
  /* En ligne : le numéro perd son filet et sa pile verticale. */
  .story__kicker {
    padding: 0;
    margin: 0;
    border: 0;
    font-size: 10px;
    flex: none;
  }
  /* The strip sits over the giant headline on shorter screens: grey on grey
     was unreadable. A soft panel guarantees contrast whatever passes behind,
     and reuses the treatment already on the rotation meter. */
  .story__item {
    padding: 9px 14px;
    border-radius: 12px;
    background: var(--glass);
    -webkit-backdrop-filter: blur(7px);
    backdrop-filter: blur(7px);
  }
  .story__kicker { padding-bottom: 3px; margin-bottom: 4px; font-size: 11px; }
  .story__title  { font-size: 11.5px; line-height: 1.15; letter-spacing: .1em; }
  /* Le trait de repère désigne le maillot depuis le côté : sur téléphone il
     n'y a pas de côté, la légende est sous le maillot. Il flotterait dans le
     vide. */
  .story__item::before { display: none; }
  .story__text   { display: none; }      /* one line only at this size */

  /* Le nom du sponsor passe sous son libellé, centré, comme sur desktop
     quand la ligne est large. */
  /* align-items: baseline (règle de base) se comporte comme un alignement au
     début dès qu'on passe en colonne : sans ce centrage, le nom se collait à
     gauche au lieu de rester sous son libellé. */
  .outro__sponsor { flex-direction: column; align-items: center; gap: 5px; }
  .outro__sponsor-label::after { content: none; }

  /* Compteur compact, glissé sous la bande de légende. Sa pastille translucide
     recouvre volontairement le haut de la boîte du maillot : c'est un bandeau
     de type HUD, pas une collision. Le sortir de là repoussait la légende sur
     le grand titre (essayé, illisible en 375x667). */
  .rotation-meter {
    /* --meter-touch est mesuré par js/main.js et place la pastille au-dessus
       du maillot dans sa plus grande extension. Le calcul de repli sert avant
       que la mesure n'arrive. */
    bottom: var(--meter-touch,
                calc(var(--jersey-bottom) + var(--jersey-h) - 34px));
    padding: 6px 12px;
  }
  .rotation-meter__line { width: 72px; }
  .rotation-meter__label, .rotation-meter__deg { font-size: 10px; }
  .rotation-meter__deg { min-width: 34px; }
}

/* Ecrans courts : plus de seuil arbitraire, la remontee des legendes est
   calculee au pixel par js/main.js (voir --story-top-l / --story-top-r). */
@media (min-width: 901px) and (max-height: 760px) {
  .story__text { font-size: 12px; }
  .story__item { max-width: 200px; }
}

/* ---- Ecran court en portrait (<=900 de large, <=860 de haut) ------------
   nav + titre + maillot + texte occupaient 736px sur 800 : la bande
   narrative n'existait plus. Le maillot cede la place. ---- */
@media (max-width: 900px) and (max-height: 860px) and (orientation: portrait) {
  .hero-sticky { --jersey-h: 33vh; --jersey-bottom: 27vh; }
  .hero-title__line { font-size: min(14vw, 9.5vh); }
  .story__text { display: none; }
}

/* ---- Phone only (< 768px): burger nav, no room for the media card ------- */
@media (max-width: 767px) {
  .nav__menu { display: none; }          /* collapsed behind the burger */
  .media-card { display: none; }
}

/* ---- Portrait tablet (768–900px): full nav and media card fit ----------- */
@media (min-width: 768px) and (max-width: 900px) {
  .nav__menu { gap: 18px; }
  .nav__menu a { font-size: 13px; }

  .media-card { width: 220px; }

  /* the copy would run underneath the media card at full width */
  .hero-copy { right: 300px; }

  /* Placing these under the jersey put them straight on the copy block
     (mesure : 564x45px de recouvrement en 900x800). They stay in the band
     above the jersey, like the phone, and simply keep their description. */
  .story__text { display: block; font-size: 12.5px; margin-top: 4px; }
}

/* ==========================================================================
   Phone in landscape — very short viewport
   Stacking headline / jersey / copy vertically needs ~700px of height; at
   390px the jersey landed on top of the copy. Here the hero goes side by
   side instead: headline across the top, jersey right, copy left.
   ========================================================================== */
@media (max-width: 950px) and (max-height: 520px) and (orientation: landscape) {
  :root { --nav-h: 54px; }

  .hero-title { top: calc(var(--nav-h) + 2px); }
  .hero-title__line { font-size: min(5.5vw, 10vh); }

  /* Les variables sont réécrites, sinon tout ce qui s'ancre au haut du
     maillot (légendes, compteur) viserait sa position en portrait. */
  .hero-sticky { --jersey-h: 64vh; --jersey-bottom: 0px; }
  .jersey {
    height: var(--jersey-h);
    bottom: var(--jersey-bottom);
    /* le maillot se range à droite sans quitter la bande pleine largeur */
    justify-content: flex-end;
    padding-right: 5vw;
  }

  /* Téléphone couché : 390px de haut moins la nav, le maillot occupe toute
     la moitié droite. Il ne reste aucune bande libre pour la narration —
     mesuré jusqu'à 35px de recouvrement sur le maillot. Elle est masquée
     plutôt que posée dessus ; le héro garde titre, texte et compteur. */
  .story { display: none; }

  .hero-copy {
    max-width: 46%;
    right: auto;
    bottom: 14px;
  }
  .hero-copy__title { font-size: 17px; }
  .hero-copy__text  { margin-top: 8px; font-size: 12.5px; line-height: 1.4; }
  .hero-copy .btn   { margin-top: 12px; padding: 12px 20px; }

  /* The portrait caption strip is anchored to the stacked-jersey geometry,
     which does not exist here. Re-anchor both to the side-by-side layout:
     caption over the jersey column, meter under the headline. */
  .story__item,
  .story__item--left,
  .story__item--right {
    left: auto;
    right: 5vw;
    bottom: calc(64vh + 6px);
    width: 40vw;
    max-width: none;
    text-align: center;
  }
  .rotation-meter {
    bottom: auto;
    top: calc(var(--nav-h) + 22vh);
  }

  /* 844px wide falls into the tablet tier, but at 390px tall the card can
     only land on top of the jersey column — no room for both */
  .media-card { display: none; }
}

/* ==========================================================================
   Reduced motion — static jersey frame, no scrub
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .jersey, .rotation-meter, .story__item { transition: none; }
  html { scroll-behavior: auto; }
  /* La piste de défilement et l'épinglage sont CONSERVÉS.
     Ils étaient supprimés ici (height:auto + position:relative), ce qui
     ramenait la course à zéro : js/main.js calcule sa progression comme
     hauteur_de_piste − hauteur_d_écran, donc elle restait bloquée à 0 et le
     maillot ne tournait jamais, quoi qu'on fasse côté JS.
     Or l'économiseur de batterie Android active ce réglage : un visiteur à
     4% de batterie perdait purement et simplement le héro. Constaté en QA
     sur l'appareil du client, reproduit puis vérifié en ligne.
     Le réglage vise le mouvement SUBI : ici la rotation est pilotée par le
     visiteur, elle ne bouge que s'il défile.
     La course garde donc sa longueur normale : la raccourcir donnerait PLUS
     de mouvement par cran de défilement, soit l'inverse du but recherché.
     Ce réglage ne supprime plus ici que le défilement fluide ; les
     transitions d'entrée sont neutralisées plus haut, et l'intro qui tourne
     toute seule l'est dans js/main.js. */
}


/* ==========================================================================
   Écrans courts — le maillot reste le sujet
   --------------------------------------------------------------------------
   Le maillot est dimensionné avec la place qui RESTE entre la barre et le
   bloc de texte. Le texte ayant une hauteur imposée par son contenu, c'est
   lui qui était la constante et le maillot la variable : mesuré, le maillot
   tombait à 155px sur 320x568 et à 25px sur 280x480 — il avait disparu.

   On inverse le rapport : sous 640px de haut, c'est le texte qui cède.
   Chaque palier lui retire un peu de corps, ce qui rend d'autant de place
   au maillot. Rien au-dessus de 640px : le format de référence est intact.
   ========================================================================== */

@media (max-width: 900px) and (max-height: 640px) {
  .hero-copy__title { font-size: 15.5px; line-height: 1.12; }
  .hero-copy__text  { margin-top: 6px; font-size: 11.5px; line-height: 1.35; }
  .hero-copy .btn   { margin-top: 10px; padding: 15px 17px; font-size: 11.5px; }
  .hero-title__line { font-size: min(13vw, 9vh); }
}

@media (max-width: 900px) and (max-height: 570px) {
  .hero-copy__title { font-size: 14px; }
  /* La description passe à la trappe avant le titre et le bouton : c'est
     l'élément dont l'absence coûte le moins à la compréhension. */
  .hero-copy__text  { display: none; }
  .hero-copy .btn   { margin-top: 10px; }
  .hero-title__line { font-size: min(12vw, 8.5vh); }
  .story__title     { font-size: 10.5px; }
  .rotation-meter   { padding: 4px 10px; }
  .rotation-meter__line { width: 110px; }
}

@media (max-width: 900px) and (max-height: 470px) {
  /* Dernier retranchement : il ne reste que le nom, le maillot et le bouton. */
  .hero-copy__title { font-size: 12.5px; }
  .hero-title__line { font-size: min(11vw, 8vh); }
  .story__item      { display: none; }
}

/* ==========================================================================
   Replis pour navigateurs sans flou d'arrière-plan
   --------------------------------------------------------------------------
   backdrop-filter est utilisé sur les panneaux translucides du héro et du
   menu. Sans lui, ces panneaux restent semi-transparents SANS flou : le
   texte se lit par-dessus le maillot ou le grand titre. On les rend opaques
   dans ce cas — moins joli, toujours lisible.
   ========================================================================== */
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .story__item    { background: var(--bg); }
  .rotation-meter { background: var(--bg); }
  .nav-panel      { background: var(--bg); }
}
