/* ============================================================================
   EASTPAT — main.css
   Tout ce qui n'est pas un token. Aucune couleur, taille de police, rayon ni
   durée en dur : tout vient de tokens.css, éventuellement composé en calc().

   ARCHITECTURE
   main.js publie, une fois par frame, sur :root :
     --s-hero --s-cap --s-traversee --s-ancrage --s-carousel --s-outro
     --s-hint --s-progress --mx --my --ep-scroll-length
   Le JS n'anime jamais un nœud du DOM. C'est ce fichier qui décide de ce que
   ces scalaires veulent dire. Aucune transition sur une propriété pilotée par
   un signal : la valeur est déjà lissée par le moteur, une transition par
   dessus produit du retard et du rebond.
   ============================================================================ */

/* ============================================================
   0 · SCALAIRES D'EXÉCUTION — valeurs de repli avant le 1er frame
   ============================================================ */
:root{
  --s-hero: 0;
  --s-cap: 0;
  --s-traversee: 0;
  --s-ancrage: 0;
  --s-carousel: 0;
  --s-outro: 0;
  --s-hint: 0;
  --s-progress: 0;
  --mx: 0;
  --my: 0;
  --ep-scroll-length: 0px;

  /* Mesures de cadrage, composées à partir des espacements du système. */
  --ep-frame-x: clamp(var(--ep-gutter), 5.2vw, var(--ep-space-9));
  --ep-frame-y: clamp(var(--ep-space-6), 5.5vh, var(--ep-space-8));
  --ep-header-h: calc(var(--ep-space-7) + var(--ep-space-5));

  /* Durées composées : multiples de l'unique durée du système. */
  --ep-dur-2: calc(var(--ep-transition) * 2);
  --ep-dur-3: calc(var(--ep-transition) * 3);
  --ep-dur-4: calc(var(--ep-transition) * 4);
  --ep-dur-10: calc(var(--ep-transition) * 10);
  --ep-dur-16: calc(var(--ep-transition) * 16);
}

/* ============================================================
   1 · BASE
   ============================================================ */
*, *::before, *::after{ box-sizing: border-box; }

html{
  background: var(--ep-navy);
  -webkit-text-size-adjust: 100%;
}

body{
  margin: 0;
  min-height: 100%;
  font-family: var(--ep-font-body);
  font-size: var(--ep-size-body);
  line-height: var(--ep-leading-body);
  color: var(--ep-mist);
  background: var(--ep-navy);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

main{ position: relative; z-index: 1; display: block; }

h1, h2, h3, h4, p, ul, ol, dl, dd, figure{ margin: 0; }
ul, ol{ list-style: none; padding: 0; }
a{ color: inherit; }
button{ font: inherit; color: inherit; }

/* Anneau de focus orange visible partout — non négociable.
   Pas de border-radius ici : l'outline épouse déjà celui de l'élément. */
:where(a, button, input, textarea, select, [tabindex]):focus-visible{
  outline: 2px solid var(--ep-orange);
  outline-offset: 2px;
}

::selection{ background: var(--ep-orange); color: var(--ep-orange-ink); }

/* ============================================================
   2 · UTILITAIRES D'ACCESSIBILITÉ
   ============================================================ */
.ep-visually-hidden{
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; white-space: nowrap;
  clip: rect(0 0 0 0); clip-path: inset(50%);
}

.ep-skip{
  position: fixed; z-index: 20; top: 0; left: 0;
  transform: translate3d(0, -140%, 0);
  padding: var(--ep-space-3) var(--ep-space-5);
  background: var(--ep-orange);
  color: var(--ep-orange-ink);
  font-size: var(--ep-size-small);
  font-weight: 600;
  text-decoration: none;
  border-radius: 0 0 var(--ep-radius-control) 0;
  transition: transform var(--ep-dur-2) var(--ep-ease-out);
}
.ep-skip:focus{ transform: translate3d(0, 0, 0); }
/* Orange sur orange serait invisible : l'anneau passe en encre. */
.ep-skip:focus-visible{ outline-color: var(--ep-orange-ink); }

.ep-noscript{
  position: relative; z-index: 2;
  max-width: var(--ep-container);
  margin: 0 auto;
  padding: var(--ep-space-8) var(--ep-gutter);
  background: var(--ep-navy);
  color: var(--ep-mist);
  font-size: var(--ep-size-body-l);
  border-top: 1px solid var(--ep-border-dark);
}

/* ============================================================
   3 · PRIMITIVES DU SYSTÈME DE DESIGN
   ============================================================ */
.ep-display{
  font-family: var(--ep-font-display);
  font-weight: 400;            /* Anton n'a qu'une graisse. Jamais 700. */
  text-transform: uppercase;
  line-height: var(--ep-leading-display);
  letter-spacing: var(--ep-track-display);
  color: var(--ep-ink);
  font-synthesis: none;
}

.ep-eyebrow{
  font-size: var(--ep-size-eyebrow);
  letter-spacing: var(--ep-track-eyebrow);
  text-transform: uppercase;
  font-weight: 500;
  line-height: 1.5;
  color: var(--ep-orange);
}

.ep-accent{ color: var(--ep-orange); }

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

.ep-logo{
  font-family: var(--ep-font-body);
  font-weight: 600;
  font-size: var(--ep-size-display-s);
  letter-spacing: -.4px;
  line-height: 1;
  color: var(--ep-cream);
  text-decoration: none;
  white-space: nowrap;
}

/* ---- Boutons. Un seul .ep-btn--primary par écran. ---- */
.ep-btn{
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--ep-space-2);
  font-family: var(--ep-font-body);
  font-weight: 600;
  font-size: var(--ep-size-small);
  line-height: 1.2;
  letter-spacing: .2px;
  padding: var(--ep-space-3) var(--ep-space-5);
  border-radius: var(--ep-radius-control);
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition:
    background var(--ep-transition) var(--ep-ease-out),
    border-color var(--ep-transition) var(--ep-ease-out),
    color var(--ep-transition) var(--ep-ease-out),
    transform var(--ep-transition) var(--ep-ease-out);
}
.ep-btn:active{ transform: translate3d(0, 1px, 0); }
.ep-btn--primary{ background: var(--ep-orange); color: var(--ep-orange-ink); }
.ep-btn--primary:hover{ background: var(--ep-orange-hover); }
.ep-btn--secondary{
  background: transparent; color: var(--ep-cream);
  border-color: var(--ep-border-dark-strong);
}
.ep-btn--secondary:hover{ border-color: var(--ep-cream); }
.ep-btn--secondary-light{
  background: transparent; color: var(--ep-ink);
  border-color: var(--ep-border-light);
}
.ep-btn--secondary-light:hover{ border-color: var(--ep-ink); }
.ep-btn--ghost{
  background: transparent; color: var(--ep-orange);
  padding-left: 0; padding-right: 0;
}
.ep-btn--ghost:hover{ color: var(--ep-orange-hover); }
.ep-btn--sm{ font-size: var(--ep-size-caption); padding: var(--ep-space-2) var(--ep-space-4); }
.ep-btn:disabled{ opacity: .45; cursor: not-allowed; }

.ep-badge{
  display: inline-block;
  font-size: var(--ep-size-eyebrow);
  letter-spacing: var(--ep-track-eyebrow);
  text-transform: uppercase;
  font-weight: 500;
  padding: var(--ep-space-1) var(--ep-space-3);
  border-radius: var(--ep-radius-badge);
  background: var(--ep-orange-soft);
  color: var(--ep-orange);
}

.ep-input{
  font-family: var(--ep-font-body);
  font-size: var(--ep-size-body);
  padding: var(--ep-space-3) var(--ep-space-4);
  border-radius: var(--ep-radius-control);
  border: 1px solid var(--ep-border-light);
  background: var(--ep-white);
  color: var(--ep-ink);
  width: 100%;
  transition: border-color var(--ep-transition) var(--ep-ease-out),
              box-shadow var(--ep-transition) var(--ep-ease-out);
}
.ep-input::placeholder{ color: var(--ep-grey); }
.ep-input:focus{
  border-color: var(--ep-orange);
  outline: none;
  box-shadow: 0 0 0 3px var(--ep-orange-soft);
}

/* ============================================================
   4 · LE PLATEAU — canvas fixe, section collante
   ============================================================ */
#ep-stage{                        /* le canvas WebGL */
  position: fixed;
  inset: 0;
  z-index: 0;
  display: block;
  width: 100%;
  height: 100%;
  background: var(--ep-navy);
}

.ep-cinema{
  position: relative;
  z-index: 1;
  height: calc(100vh + var(--ep-scroll-length));
  height: calc(100svh + var(--ep-scroll-length));
}

.ep-stage{                        /* le cadre collant */
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  isolation: isolate;
}

/* ============================================================
   5 · LES COUCHES — opacité, élan et voile depuis leur signal
   ============================================================ */

/* Seuil de visibilité en CSS pur : une animation en pause dont on scrube la
   position avec le signal. En dessous de 1 %, la couche disparaît vraiment —
   elle ne coûte plus rien et n'est plus atteignable au clavier. */
@keyframes ep-layer-vis{
  0%   { visibility: hidden; }
  1%   { visibility: visible; }
  100% { visibility: visible; }
}

.ep-layer{
  --s: 0;
  position: absolute;
  inset: 0;
  z-index: 1;
  display: grid;
  pointer-events: none;
  opacity: var(--s);
  will-change: opacity, transform;
  animation: ep-layer-vis 1s linear paused;
  animation-delay: calc(var(--s) * -1s);
  /* Élan : la copie arrive et repart, elle ne se contente pas de changer
     d'alpha. Aucune transition — le signal est déjà lissé par le moteur. */
  transform:
    translate3d(0, calc((1 - var(--s)) * var(--ep-space-7)), 0)
    scale(calc(.985 + var(--s) * .015));
}
.ep-layer[aria-hidden="true"]{ visibility: hidden; }

.ep-layer[data-ui="hero"]     { --s: var(--s-hero); }
.ep-layer[data-ui="cap"]      { --s: var(--s-cap); }
.ep-layer[data-ui="traversee"]{ --s: var(--s-traversee); }
.ep-layer[data-ui="ancrage"]  { --s: var(--s-ancrage); }
.ep-layer[data-ui="carousel"] { --s: var(--s-carousel); }
.ep-layer[data-ui="outro"]    { --s: var(--s-outro); }

/* Interactif uniquement quand la couche est réellement en scène. */
.ep-layer.is-live :where(a, button, input, [tabindex]),
.ep-layer.is-live .ep-cities__viewport{ pointer-events: auto; }

/* Voile : jamais un rectangle, jamais une bordure. Un dégradé doux,
   dimensionné sur la copie, qui s'efface avec le même signal. */
.ep-layer::before{
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

/* ---- 5.0 · Révélation en cascade -----------------------------------------
   ui/panels.js pose --i (index 0) sur chaque ligne de titre (.ep-line), sur
   .ep-panel__lead + les points, et sur les trois marches du hero. Le retard
   se lit ici, jamais en JS : --p est le signal de la couche décalé puis
   redressé. Aucune transition — la valeur est déjà lissée.                  */
.ep-line__in,
.ep-hero__step,
.ep-panel__lead,
.ep-panel__points li{
  --p: clamp(0, calc((var(--s) - var(--i, 0) * .06) * 3.2), 1);
}

.ep-line{
  display: block;
  overflow: hidden;                 /* le masque */
  padding-block: .1em;              /* Anton dépasse sa boîte de ligne : on */
  margin-block: -.1em;              /* recule le masque hors des jambages.  */
}
.ep-line__in{
  display: block;
  opacity: var(--p);
  transform: translate3d(0, calc((1 - var(--p)) * .5em), 0);
}

/* ---- 5.1 · HERO ---- */
.ep-layer--hero{
  align-content: end;
  align-content: safe end;
  justify-items: start;
  /* Ancré bas : le bloc occupe le quart inférieur gauche du cadre, pour que
     l'arche de l'Arc de Triomphe se lise au-dessus, plein centre. */
  padding: calc(var(--ep-header-h) + var(--ep-frame-y)) var(--ep-frame-x)
           calc(var(--ep-frame-y) + var(--ep-space-6));
  /* La caméra pousse : le hero repart vers le haut en se refermant un peu. */
  transform:
    translate3d(0, calc((1 - var(--s)) * (var(--ep-space-7) * -1)), 0)
    scale(calc(.94 + var(--s) * .06));
}
.ep-layer--hero::before{
  background:
    radial-gradient(64% 56% at 26% 74%, var(--ep-scrim) 0%, var(--ep-scrim) 24%, var(--ep-scrim-soft) 56%, transparent 82%),
    linear-gradient(to top, var(--ep-scrim-soft) 0%, transparent 52%);
}

.ep-hero{
  display: grid;
  justify-items: start;
  /* Parallaxe : la copie bouge à contre-sens du pointeur. 6–10 px, pas plus. */
  transform: translate3d(calc(var(--mx) * -10px), calc(var(--my) * -7px), 0);
}

.ep-hero .ep-eyebrow{
  color: var(--ep-orange);
  text-shadow: 0 1px 2px var(--ep-scrim);
}

.ep-hero__title{
  margin-top: var(--ep-space-4);
  font-size: var(--ep-size-display-hero);
  color: var(--ep-cream);
  text-shadow: 0 1px 2px var(--ep-scrim), 0 var(--ep-space-1) var(--ep-space-7) var(--ep-scrim-soft);
}

.ep-hero__sub{
  position: relative;
  margin-top: var(--ep-space-5);
  padding-left: calc(var(--ep-space-7) + var(--ep-space-3));
  font-size: var(--ep-size-display-l);
  color: var(--ep-cream);
  opacity: .82;
  text-shadow: 0 1px 2px var(--ep-scrim), 0 var(--ep-space-1) var(--ep-space-7) var(--ep-scrim-soft);
}
.ep-hero__sub::before{
  content: "";
  position: absolute;
  left: 0;
  top: .48em;
  width: var(--ep-space-7);
  height: 2px;
  background: var(--ep-cream);
  opacity: .55;
}

.ep-hero__lead{
  margin-top: var(--ep-space-5);
  max-width: 40ch;
  font-size: var(--ep-size-body-l);
  color: var(--ep-cream);
  text-shadow: 0 1px 2px var(--ep-scrim), 0 var(--ep-space-1) var(--ep-space-6) var(--ep-scrim);
}

.ep-hero__actions{
  display: flex;
  flex-wrap: wrap;
  gap: var(--ep-space-3);
  margin-top: var(--ep-space-6);
}

.ep-hero__step{
  padding: var(--ep-space-3) var(--ep-space-5);
  backdrop-filter: blur(2px);
  opacity: var(--p);
  transform: translate3d(0, calc((1 - var(--p)) * var(--ep-space-4)), 0);
  /* Pas de transition sur transform : il est piloté par le signal. */
  transition:
    background var(--ep-transition) var(--ep-ease-out),
    border-color var(--ep-transition) var(--ep-ease-out),
    color var(--ep-transition) var(--ep-ease-out);
}
.ep-hero__step:active{ transform: translate3d(0, 1px, 0); }
/* Le chiffre n'est PAS l'accent : c'est un index. Le hero porte déjà son
   orange — l'eyebrow et le point final du titre. Trois numéros orange de
   plus faisaient cinq oranges sur le même écran, et cinq oranges ne
   désignent plus rien. Le numéro recule donc en brume : il ordonne les trois
   marches sans se disputer la hiérarchie avec le titre. */
.ep-hero__num{
  font-size: var(--ep-size-eyebrow);
  letter-spacing: var(--ep-track-eyebrow);
  font-weight: 500;
  color: var(--ep-mist);
}

/* ---- 5.2 · PANNEAUX : Le Cap · La Traversée · L'Ancrage ----
   `safe` : quand la copie est plus haute que le cadre — téléphone couché,
   375 px de haut — un centrage classique la fait déborder des DEUX côtés, et
   .ep-stage est en `overflow: hidden` : le haut du bloc (eyebrow, titre,
   chapô) devient purement inatteignable. `safe` bascule sur `start` dans ce
   seul cas. La déclaration est doublée : les navigateurs qui ne connaissent
   pas le mot-clé gardent la première et rien ne change pour eux. */
.ep-layer--panel{
  align-content: center;
  align-content: safe center;
  justify-items: start;
  padding: calc(var(--ep-header-h) + var(--ep-frame-y)) var(--ep-frame-x)
           calc(var(--ep-frame-y) + var(--ep-space-7));
}
/* Deux lobes, pas un. Le grand assoit tout le bloc de copie ; le petit, plus
   dense et posé plus haut, tient le tiers supérieur — c'est là que vivent
   l'eyebrow et le titre, et c'est là que le ciel de Varsovie est le plus
   clair. Un seul lobe centré laissait le haut du bloc à découvert. */
.ep-layer--panel::before{
  background:
    radial-gradient(38% 30% at 24% 32%, var(--ep-scrim) 0%, var(--ep-scrim) 20%, var(--ep-scrim-soft) 56%, transparent 84%),
    radial-gradient(58% 66% at 24% 50%, var(--ep-scrim) 0%, var(--ep-scrim-soft) 48%, transparent 76%);
}

/* La Traversée : même système, miroir — les deux temps ne doivent pas se
   lire comme la même diapositive deux fois. La marge droite dégage le rail
   de chapitres, qui vit sur ce bord. */
.ep-layer[data-ui="traversee"]{
  justify-items: end;
  padding-right: calc(var(--ep-frame-x) + var(--ep-space-9) * 2);
}
/* Le lit du texte. Deux nappes radiales assoient le bloc de copie, mais elles
   ne suffisaient pas : derrière le paragraphe se tient le Palais de la
   Culture, dont la façade ajourée est à la fois très contrastée et à la même
   luminance que la crème du texte. On ajoute donc une RAMPE horizontale — un
   dégradé neutre, façon filtre dégradé sur l'objectif — qui part de rien à
   mi-cadre (là où commence la tour) et monte jusqu'au voile plein sur le
   tiers droit. Elle prépare le fond du texte ET fait retomber la tour d'un
   cran de valeur, sans jamais dessiner d'arête. */
.ep-layer[data-ui="traversee"]::before{
  background:
    linear-gradient(to right,
      transparent 0%,
      transparent 48%,
      var(--ep-scrim-soft) 55%,
      var(--ep-scrim) 68%,
      var(--ep-scrim) 100%),
    radial-gradient(38% 30% at 68% 32%, var(--ep-scrim) 0%, var(--ep-scrim) 20%, var(--ep-scrim-soft) 56%, transparent 84%),
    radial-gradient(58% 66% at 74% 50%, var(--ep-scrim) 0%, var(--ep-scrim-soft) 48%, transparent 76%);
}
/* La colonne est calée sur l'image, pas posée dessus : à 28rem son bord
   gauche tombe sur l'arête est du Palais, si bien que la copie quitte
   entièrement la façade ajourée au lieu de la chevaucher. */
.ep-layer[data-ui="traversee"] .ep-panel{ max-width: min(100%, 28rem); }
.ep-layer[data-ui="traversee"] .ep-panel__lead{ max-width: min(100%, 25rem); }
/* Les filets s'arrêtent avant le rail de chapitres. Ils couraient jusqu'au
   bord de la colonne et venaient buter dans les intitulés : un filet qui
   frôle un autre élément se lit comme un débordement, pas comme une règle. */
.ep-layer[data-ui="traversee"] .ep-panel__points{ width: min(100%, 23rem); }

/* L'Ancrage : épinglé à GAUCHE — la boulangerie occupe le centre-droit du
   cadre et ne doit jamais être couverte. C'est le plan de la récompense :
   la copie descend dans le bas du cadre et laisse respirer le rendu. */
.ep-layer--panel-side{
  justify-items: start;
  align-content: end;
  align-content: safe end;
  padding-right: 48%;
  padding-bottom: calc(var(--ep-frame-y) + var(--ep-space-8));
}
/* Le voile est une flaque en bas à gauche : il ne monte jamais sur la
   boulangerie, qui tient le centre-droit du cadre. */
.ep-layer--panel-side::before{
  background:
    radial-gradient(62% 58% at 20% 84%, var(--ep-scrim) 0%, var(--ep-scrim) 26%, var(--ep-scrim-soft) 56%, transparent 80%),
    /* La rampe de gauche part du voile plein, pas du voile doux : depuis que
       la profondeur de champ rend au fond ses fenêtres allumées et ses arêtes,
       la ville derrière le titre porte du détail à la même valeur que la
       crème. La flaque du bas ne monte pas jusque-là — c'est cette rampe qui
       tient le tiers haut de la colonne. */
    linear-gradient(to right, var(--ep-scrim) 0%, var(--ep-scrim-soft) 22%, transparent 48%);
}

.ep-panel{
  max-width: 30rem;
  display: grid;
  justify-items: start;
  transform: translate3d(calc(var(--mx) * -7px), calc(var(--my) * -5px), 0);
}
/* L'eyebrow est le seul orange du plan, et sur La Traversée il tombe sur le
   ciel doré de Varsovie, où il disparaît. Il lui faut sa propre densité : un
   halo radial calibré sur le texte, fondu à transparent sur les quatre côtés.
   Aucune arête de boîte — juste de l'ombre sous les lettres. */
.ep-panel .ep-eyebrow{
  position: relative;
  text-shadow: 0 1px 2px var(--ep-scrim), 0 0 var(--ep-space-2) var(--ep-scrim);
}
.ep-panel .ep-eyebrow::before{
  content: "";
  position: absolute;
  z-index: -1;
  /* Très large et très plat : la retombée a besoin de place. Serré, le halo
     se lirait comme une pastille posée sur le ciel ; étalé sur deux fois la
     longueur du texte, il se lit comme une ombre de nuage. */
  inset: calc(var(--ep-space-4) * -1) calc(var(--ep-space-9) * -1);
  pointer-events: none;
  /* Une seule nappe du voile du système, avec une longue traîne : pleine
     densité sous les lettres, puis --ep-scrim-soft, puis rien, sur plus de
     la moitié du rayon. La traîne est ce qui empêche la limite de l'ellipse
     de se localiser — un dégradé court se lirait comme une pastille.
     `ellipse closest-side` est obligatoire : une ellipse plus grande que sa
     boîte se ferait trancher au bord et rendrait le rectangle visible. */
  background:
    radial-gradient(ellipse closest-side,
      var(--ep-scrim) 0%, var(--ep-scrim) 40%, var(--ep-scrim-soft) 62%, transparent 100%);
}

.ep-panel__title{
  margin-top: var(--ep-space-3);
  font-size: var(--ep-size-display-beat);
  color: var(--ep-cream);
  text-shadow: 0 1px 2px var(--ep-scrim), 0 var(--ep-space-1) var(--ep-space-7) var(--ep-scrim-soft);
}

.ep-panel__lead{
  margin-top: var(--ep-space-4);
  font-size: var(--ep-size-body-l);
  color: var(--ep-cream);
  text-shadow: 0 1px 2px var(--ep-scrim), 0 var(--ep-space-1) var(--ep-space-6) var(--ep-scrim);
  opacity: var(--p);
  transform: translate3d(0, calc((1 - var(--p)) * var(--ep-space-4)), 0);
}

.ep-panel__points{
  margin-top: var(--ep-space-6);
  width: 100%;
  border-top: 1px solid var(--ep-border-dark);
}
.ep-panel__points li{
  padding: var(--ep-space-4) 0;
  border-bottom: 1px solid var(--ep-border-dark);
  font-size: var(--ep-size-body);
  line-height: var(--ep-leading-body);
  color: var(--ep-cream);
  opacity: var(--p);
  transform: translate3d(calc((1 - var(--p)) * var(--ep-space-5)), 0, 0);
  text-shadow: 0 1px 2px var(--ep-scrim);
}
.ep-panel__points li::before{
  content: "";
  display: inline-block;
  width: var(--ep-space-3);
  height: 1px;
  margin-right: var(--ep-space-3);
  vertical-align: middle;
  background: var(--ep-border-dark-strong);
}

/* ---- 5.3 · LES VILLES ---- */
.ep-layer--cities{
  align-content: center;
  align-content: safe center;
  justify-items: stretch;
  padding: calc(var(--ep-header-h) + var(--ep-frame-y)) 0 var(--ep-frame-y);
}
.ep-layer--cities::before{
  background:
    linear-gradient(to top, var(--ep-scrim) 0%, var(--ep-scrim-soft) 38%, transparent 78%),
    radial-gradient(52% 34% at 16% 34%, var(--ep-scrim) 0%, var(--ep-scrim-soft) 46%, transparent 78%);
}

.ep-cities{
  display: grid;
  gap: var(--ep-space-6);
  width: 100%;
}

.ep-cities__head{
  padding: 0 var(--ep-frame-x);
  max-width: 46rem;
}
.ep-cities__head .ep-eyebrow{
  color: var(--ep-grey);       /* l'accent du plan appartient aux chiffres */
  text-shadow: 0 1px 2px var(--ep-scrim);
}
.ep-cities__title{
  margin-top: var(--ep-space-3);
  font-size: var(--ep-size-display-m);
  color: var(--ep-cream);
  text-shadow: 0 1px 2px var(--ep-scrim), 0 var(--ep-space-1) var(--ep-space-7) var(--ep-scrim-soft);
}

.ep-cities__viewport{
  overflow: hidden;
  touch-action: pan-y;          /* sinon le navigateur confisque le swipe */
  cursor: grab;
  /* Le rembourrage doit dégager la carte active — levée, agrandie, et son
     ombre portée — sans quoi `overflow: hidden` lui coupe le haut. Il est
     repris à l'identique en marge négative : la boîte de marge ne bouge pas,
     donc ni la grille ni la mesure du pas du carrousel ne bougent. */
  padding: var(--ep-space-6) 0 var(--ep-space-7);
  margin: calc(var(--ep-space-6) * -1) 0 calc(var(--ep-space-7) * -1);
  /* Les deux bords se dissolvent au lieu d'être tranchés au rasoir. Le fondu
     fait la largeur de la gouttière de cadre : le rail disparaît exactement
     là où le titre et les commandes commencent.
     Dans un masque seul l'alpha compte — --ep-ink n'y sert que d'opaque. */
  --ep-edge-fade: var(--ep-frame-x);
  -webkit-mask-image:
    linear-gradient(to right,
      transparent 0,
      var(--ep-ink) var(--ep-edge-fade),
      var(--ep-ink) calc(100% - var(--ep-edge-fade)),
      transparent 100%);
  mask-image:
    linear-gradient(to right,
      transparent 0,
      var(--ep-ink) var(--ep-edge-fade),
      var(--ep-ink) calc(100% - var(--ep-edge-fade)),
      transparent 100%);
}
.ep-cities.is-dragging{ user-select: none; }
.ep-cities.is-dragging .ep-cities__viewport{ cursor: grabbing; }

.ep-cities__track{
  display: flex;
  align-items: stretch;
  gap: var(--ep-space-4);
  transform: translate3d(var(--cities-shift, 0px), 0, 0);
  transition: transform var(--ep-dur-4) var(--ep-ease-out);
  will-change: transform;
}
/* La couture de la boucle, le glisser, et le mouvement réduit. */
.ep-cities__track.is-jumping{ transition: none; }

.ep-city{
  /* La largeur de mise en page ne change JAMAIS : la carte centrale est
     mise en avant par transform, sinon le pas du carrousel se déforme. */
  flex: 0 0 clamp(14.5rem, 19vw, 18rem);
  display: grid;
  align-content: start;
  gap: var(--ep-space-2);
  padding: var(--ep-space-5);
  /* UN seul procédé, pas trois. Le rayon de carte du système, une surface
     crème qui appartient à cette palette (le blanc pur appartenait à
     Bootstrap), et l'élévation pour tout dire du reste — donc pas de filet
     de contour : c'est l'ombre qui porte le repos, la levée et le survol. */
  background: var(--ep-cream);
  border-radius: var(--ep-radius-card);
  box-shadow: var(--ep-shadow-card);
  color: var(--ep-slate);
  cursor: pointer;
  transition:
    transform var(--ep-dur-3) var(--ep-ease-out),
    box-shadow var(--ep-dur-3) var(--ep-ease-out);
}
/* Une carte que ui/carousel.js n'a pas remplie n'est pas une carte vide :
   elle n'existe pas. (Voir le rapport : le repli sans WebGL devrait quand
   même appeler createCarousel().) */
.ep-city:empty{ display: none; }

/* Survol : la même levée que `.ep-card:hover` du système de design, arrondie
   au pas d'espacement le plus proche. Une carte cliquable sans état de survol
   se lit comme cassée. Réservé aux pointeurs fins — sur tactile un :hover
   collant resterait allumé après le tap. */
@media (hover: hover){
  .ep-city:hover{
    transform: translate3d(0, calc(var(--ep-space-1) * -1), 0);
    box-shadow: var(--ep-shadow-lift);
  }
}
/* La carte centrale est levée et légèrement plus grande. */
.ep-city.is-active{
  transform: translate3d(0, calc(var(--ep-space-3) * -1), 0) scale(1.05);
  box-shadow: var(--ep-shadow-lift);
}
@media (hover: hover){
  .ep-city.is-active:hover{
    transform: translate3d(0, calc((var(--ep-space-3) + var(--ep-space-1)) * -1), 0) scale(1.05);
  }
}

/* L'anneau de focus vit DANS la boîte de la carte. Posé à l'extérieur, il se
   fait trancher le bord haut par l'`overflow: hidden` du viewport dès que la
   carte est levée : l'utilisateur au clavier voyait un ⊔, pas un anneau.
   Décalage négatif = anneau complet, quoi qu'il arrive au rembourrage. */
.ep-city:focus-visible{
  outline: 2px solid var(--ep-orange);
  outline-offset: calc(var(--ep-space-1) * -1);
}

.ep-city__country{
  font-size: var(--ep-size-eyebrow);
  letter-spacing: var(--ep-track-eyebrow);
  text-transform: uppercase;
  font-weight: 500;
  line-height: 1.4;
  color: var(--ep-grey);
}

/* Le nom de la ville est une ÉTIQUETTE, pas le sujet : il recule d'un cran
   sous l'échelle d'affichage pour que la note d'installation puisse passer
   devant. (Système : « la plus grande typo appartient aux données ».) */
.ep-city__name{
  font-size: var(--ep-size-body-l);
  color: var(--ep-ink);
}

/* LES CHIFFRES PASSENT AVANT LES SLOGANS : la facilité d'installation porte
   le plus grand caractère de la carte — plus grand que le nom de la ville. */
.ep-city__stats{
  display: grid;
  gap: var(--ep-space-4);
  margin-top: var(--ep-space-3);
}
.ep-city__stat{
  display: flex;
  flex-direction: column-reverse;   /* la donnée d'abord, sa légende ensuite */
  gap: var(--ep-space-2);
}
.ep-city__stat + .ep-city__stat{
  padding-top: var(--ep-space-4);
  border-top: 1px solid var(--ep-border-light);
}
.ep-city__stat dt{
  font-size: var(--ep-size-caption);
  line-height: 1.35;
  color: var(--ep-slate);
}

.ep-city__ease{
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--ep-space-1);
}
/* Le plus gros caractère de la carte, et de loin : tout en haut de l'échelle
   d'affichage, presque cinq fois le corps du nom de la ville. C'est la
   donnée — c'est elle qu'on doit lire d'un seul coup d'œil, pas le slogan. */
.ep-city__value{
  font-family: var(--ep-font-display);
  font-weight: 400;
  font-size: var(--ep-size-display-xl);
  line-height: 1;
  letter-spacing: var(--ep-track-display);
  color: var(--ep-ink);
  font-synthesis: none;
}
/* Un seul orange par écran : seule la carte centrée porte l'accent. */
.ep-city.is-active .ep-city__value{ color: var(--ep-orange); }
.ep-city__max{
  font-family: var(--ep-font-display);
  font-weight: 400;
  font-size: var(--ep-size-display-s);
  line-height: 1;
  color: var(--ep-grey);
  font-synthesis: none;
}

/* Le rail : un filet, une jauge à l'échelle de --v.
   Hauteur : aucun token ne vaut 3px et le système interdit d'en inventer un.
   On prend donc le plus petit pas d'espacement, --ep-space-1 (4px). */
.ep-meter{
  flex: 1 0 100%;
  display: block;
  height: var(--ep-space-1);
  margin-top: var(--ep-space-2);
  border-radius: var(--ep-radius-badge);
  background: var(--ep-border-light);
  overflow: hidden;
}
/* Quinze jauges orange à l'écran, c'était quinze fois rien : l'accent ne
   désigne plus personne. Au repos la jauge est neutre — l'information de
   niveau reste lisible, l'accent non. */
.ep-meter > i{
  display: block;
  height: 100%;
  width: calc(var(--v, 0) * 100%);
  background: var(--ep-mist);
  border-radius: inherit;
}
/* Seule la carte centrée porte l'orange — et elle le porte une seule fois,
   sur la donnée : le chiffre et sa jauge sont le même énoncé. */
.ep-city.is-active .ep-meter > i{ background: var(--ep-orange); }

/* Trois glyphes ; les inactifs sont ternis, jamais recolorés. */
.ep-city__cost{
  display: flex;
  align-items: baseline;
  gap: var(--ep-space-1);
  font-family: var(--ep-font-display);
  font-size: var(--ep-size-display-s);
  line-height: 1;
  color: var(--ep-ink);
  font-synthesis: none;
}
.ep-city__cost .is-off{ opacity: .2; }

.ep-cities__controls{
  display: flex;
  align-items: center;
  gap: var(--ep-space-3);
  padding: 0 var(--ep-frame-x);
}
.ep-cities__nav{
  width: calc(var(--ep-space-7) - var(--ep-space-1));
  height: calc(var(--ep-space-7) - var(--ep-space-1));
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--ep-border-dark-strong);
  background: transparent;
  color: var(--ep-cream);
  font-size: var(--ep-size-body-l);
  line-height: 1;
  cursor: pointer;
  transition: border-color var(--ep-transition) var(--ep-ease-out),
              background var(--ep-transition) var(--ep-ease-out);
}
.ep-cities__nav:hover{ border-color: var(--ep-cream); }
.ep-cities__nav:disabled{ opacity: .35; cursor: not-allowed; }

.ep-cities__counter{
  margin-left: var(--ep-space-3);
  font-family: var(--ep-font-display);
  font-weight: 400;
  font-size: var(--ep-size-display-s);
  line-height: 1;
  letter-spacing: var(--ep-track-display);
  color: var(--ep-cream);
  text-shadow: 0 1px 2px var(--ep-scrim);
}
.ep-cities__total{ color: var(--ep-grey); }

/* ---- 5.4 · SORTIE ---- */
.ep-layer--outro{
  align-content: center;
  align-content: safe center;
  justify-items: center;
  padding: calc(var(--ep-header-h) + var(--ep-frame-y)) var(--ep-frame-x) var(--ep-frame-y);
  text-align: center;
}
.ep-layer--outro::before{
  background:
    radial-gradient(52% 54% at 50% 48%, var(--ep-scrim) 0%, var(--ep-scrim) 26%, var(--ep-scrim-soft) 58%, transparent 82%);
}
.ep-outro{
  display: grid;
  justify-items: center;
  max-width: 44rem;
  transform: translate3d(calc(var(--mx) * -6px), calc(var(--my) * -4px), 0);
}
/* Sur ce plan, l'orange appartient entièrement au bouton : c'est le seul
   bouton primaire du site et il doit se mériter. L'eyebrow s'efface. */
.ep-outro .ep-eyebrow{
  color: var(--ep-cream);
  opacity: .72;
  text-shadow: 0 1px 2px var(--ep-scrim);
}
.ep-outro__title{
  margin-top: var(--ep-space-3);
  font-size: var(--ep-size-display-beat);
  color: var(--ep-cream);
  text-shadow: 0 1px 2px var(--ep-scrim), 0 var(--ep-space-1) var(--ep-space-7) var(--ep-scrim-soft);
}
.ep-outro__lead{
  margin-top: var(--ep-space-4);
  max-width: 46ch;
  font-size: var(--ep-size-body-l);
  color: var(--ep-cream);
  text-shadow: 0 1px 2px var(--ep-scrim), 0 var(--ep-space-1) var(--ep-space-6) var(--ep-scrim);
}
/* LE bouton orange du site entier. Il arrive à la fin, il est mérité. */
.ep-outro__cta{
  margin-top: var(--ep-space-6);
  padding: var(--ep-space-4) var(--ep-space-6);
  font-size: var(--ep-size-body);
  box-shadow: var(--ep-shadow-lift);
}

/* ============================================================
   6 · HABILLAGE
   ============================================================ */

/* ---- 6.1 · En-tête ---- */
.ep-header{
  position: fixed;
  z-index: 5;
  top: 0; left: 0; right: 0;
  min-height: var(--ep-header-h);
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--ep-space-5);
  padding: var(--ep-space-4) var(--ep-frame-x);
  color: var(--ep-cream);
  transform: translate3d(calc(var(--mx) * 6px), 0, 0);
}
/* Voile permanent : l'en-tête doit survivre à un ciel clair. Il doit surtout
   survivre à un ciel clair PAR ENDROITS : sur La Traversée, deux intitulés
   tombaient sur la nuit et deux sur l'or de l'horizon, et l'écart de contraste
   se lisait comme une différence d'état — un bug, pas une intention. Le voile
   est donc assez dense pour que les quatre reposent sur la même valeur. */
.ep-header::before{
  content: "";
  position: absolute; inset: 0;
  z-index: -2;
  pointer-events: none;
  background: linear-gradient(to bottom,
    var(--ep-scrim) 0%, var(--ep-scrim-soft) 62%, transparent 100%);
}
/* Fond réel : il n'arrive qu'une fois le hero parti. */
.ep-header::after{
  content: "";
  position: absolute; inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: calc(1 - var(--s-hero));
  background: linear-gradient(to bottom, var(--ep-scrim) 0%, var(--ep-scrim-soft) 62%, transparent 100%);
  border-bottom: 1px solid var(--ep-border-dark);
}

.ep-header__nav{
  justify-self: center;
  display: flex;
  gap: var(--ep-space-6);
  font-size: var(--ep-size-small);
}
/* Une seule valeur inactive pour les quatre. L'état courant s'exprime par UN
   signal, et un seul : le filet qui se déploie sous l'intitulé. Il est piloté
   par les scalaires que le moteur publie (SPEC §9) — aucune écriture de style
   en JS, et l'état suit le récit au lieu d'être posé au clic. */
.ep-header__nav a{
  position: relative;
  color: var(--ep-cream);
  opacity: .72;
  text-decoration: none;
  transition: opacity var(--ep-transition) var(--ep-ease-out);
  text-shadow: 0 1px 2px var(--ep-scrim);
}
.ep-header__nav a::after{
  content: "";
  position: absolute;
  left: 0; right: 0;
  bottom: calc(var(--ep-space-2) * -1);
  height: 1px;
  background: var(--ep-cream);
  transform: scaleX(var(--a, 0));
  transform-origin: left center;
}
.ep-header__nav a[data-goto="cap"]      { --a: var(--s-cap); }
.ep-header__nav a[data-goto="traversee"]{ --a: var(--s-traversee); }
.ep-header__nav a[data-goto="ancrage"]  { --a: var(--s-ancrage); }
.ep-header__nav a[data-goto="carousel"] { --a: var(--s-carousel); }
.ep-header__nav a:hover{ opacity: 1; }
.ep-header__cta{ justify-self: end; }
.ep-header .ep-logo{ text-shadow: 0 1px 2px var(--ep-scrim); }

/* ---- 6.2 · Invitation à défiler ---- */
@keyframes ep-hint-run{
  0%   { transform: translate3d(0, -100%, 0); }
  55%  { transform: translate3d(0, 100%, 0); }
  100% { transform: translate3d(0, 100%, 0); }
}
.ep-hint{
  position: absolute;
  z-index: 3;
  left: 50%;
  bottom: var(--ep-space-6);
  transform: translate3d(-50%, 0, 0);
  display: grid;
  justify-items: center;
  gap: var(--ep-space-3);
  opacity: var(--s-hint);
  pointer-events: none;
  animation: ep-layer-vis 1s linear paused;
  animation-delay: calc(var(--s-hint) * -1s);
}
.ep-hint__label{
  font-size: var(--ep-size-eyebrow);
  letter-spacing: var(--ep-track-eyebrow);
  text-transform: uppercase;
  font-weight: 500;
  color: var(--ep-cream);
  opacity: .78;
  text-shadow: 0 1px 2px var(--ep-scrim);
}
.ep-hint__rail{
  position: relative;
  display: block;
  width: 1px;
  height: var(--ep-space-8);
  background: var(--ep-border-dark-strong);
  overflow: hidden;
}
.ep-hint__rail i{
  position: absolute;
  left: 0; top: 0;
  width: 100%;
  height: 45%;
  background: var(--ep-cream);
  animation: ep-hint-run var(--ep-dur-16) var(--ep-ease-inout) infinite;
}

/* ---- 6.3 · Rail de chapitres ---- */
/* La marge droite est FIXE : liée à --ep-frame-x, le rail se rapprochait du
   bord sur les cadres étroits, exactement là où la colonne de copie était
   déjà la plus proche de lui. */
.ep-chapters{
  position: absolute;
  z-index: 3;
  right: var(--ep-space-8);
  top: 50%;
  transform: translate3d(0, -50%, 0);
  display: grid;
  gap: var(--ep-space-4);
  pointer-events: none;
  /* Le carrousel est plein cadre : le rail lui cède le bord droit. */
  opacity: calc(1 - var(--s-carousel));
}
.ep-chapters li{
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--ep-space-3);
  font-size: var(--ep-size-eyebrow);
  letter-spacing: var(--ep-track-eyebrow);
  text-transform: uppercase;
  font-weight: 500;
  line-height: 1;
  /* Crème à 55 %, pas gris à 50 % : le gris du système est fait pour du texte
     sur fond clair et il disparaissait sur le ciel de Varsovie. Une seule et
     même valeur pour les quatre états inactifs — un écart entre deux d'entre
     eux se lit comme un bug de rendu, jamais comme une intention. */
  color: var(--ep-cream);
  opacity: .55;
  transition: opacity var(--ep-dur-2) var(--ep-ease-out),
              color var(--ep-dur-2) var(--ep-ease-out);
  /* Trois voiles empilés sous les lettres plutôt qu'une plaque : une plaque,
     même fondue, se localise dès que le ciel derrière est clair — on la voit
     comme une tache. L'ombre, elle, ne suit que les glyphes. */
  text-shadow:
    0 1px 2px var(--ep-scrim),
    0 0 var(--ep-space-2) var(--ep-scrim),
    0 0 var(--ep-space-5) var(--ep-scrim);
}
.ep-chapters li::after{
  content: "";
  width: var(--ep-space-4);
  height: 2px;
  /* Crème pleine, atténuée par l'opacité de la ligne : à --ep-border-dark-
     strong le filet retombait à 19 % effectifs et disparaissait sur le ciel. */
  background: var(--ep-cream);
  box-shadow: 0 1px 2px var(--ep-scrim);
  transition: width var(--ep-dur-2) var(--ep-ease-out),
              background var(--ep-dur-2) var(--ep-ease-out);
}
.ep-chapters li.is-current{ opacity: 1; }
.ep-chapters li.is-current::after{ width: var(--ep-space-7); background: var(--ep-orange); }

/* ---- 6.4 · Progression ---- */
.ep-progress{
  position: absolute;
  z-index: 6;
  top: 0; left: 0; right: 0;
  height: 2px;
  pointer-events: none;
}
.ep-progress i{
  display: block;
  height: 100%;
  background: var(--ep-orange);
  transform: scaleX(var(--s-progress));
  transform-origin: left center;
}

/* ---- 6.5 · Amorçage ---- */
@keyframes ep-boot-fill{
  0%   { transform: scaleX(.04); }
  62%  { transform: scaleX(.72); }
  100% { transform: scaleX(1); }
}
.ep-boot{
  position: fixed;
  inset: 0;
  z-index: 9;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: var(--ep-space-5);
  background: var(--ep-navy);
  transition: opacity var(--ep-dur-3) var(--ep-ease-out),
              transform var(--ep-dur-3) var(--ep-ease-out),
              visibility 0s linear var(--ep-dur-3);
}
.ep-boot__mark{
  font-family: var(--ep-font-body);
  font-weight: 600;
  font-size: var(--ep-size-display-m);
  letter-spacing: -.4px;
  line-height: 1;
  color: var(--ep-cream);
}
.ep-boot__mark i{ font-style: normal; color: var(--ep-orange); }
.ep-boot__bar{
  display: block;
  width: calc(var(--ep-space-9) * 2);
  max-width: 60vw;
  height: 2px;
  background: var(--ep-border-dark);
  overflow: hidden;
}
.ep-boot__bar i{
  display: block;
  height: 100%;
  background: var(--ep-orange);
  transform-origin: left center;
  animation: ep-boot-fill var(--ep-dur-10) var(--ep-ease-out) forwards;
}
.ep-boot.is-done{
  opacity: 0;
  visibility: hidden;
  transform: translate3d(0, calc(var(--ep-space-5) * -1), 0);
  pointer-events: none;
}

/* Tant que le premier frame n'est pas dessiné, rien ne doit clignoter. */
body.ep-booting .ep-layer,
body.ep-booting .ep-header,
body.ep-booting .ep-hint,
body.ep-booting .ep-chapters,
body.ep-booting .ep-progress,
body.ep-booting .ep-footer{
  opacity: 0;
  visibility: hidden;
}

/* ============================================================
   7 · PIED DE PAGE — il défile normalement après la scène collante
   ============================================================ */
.ep-footer{
  position: relative;
  z-index: 1;
  background: var(--ep-navy);
  color: var(--ep-mist);
  padding: var(--ep-space-8) 0 var(--ep-space-6);
}

.ep-newsletter{
  background: var(--ep-orange);
  border-radius: var(--ep-radius-card);
  padding: var(--ep-space-6);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--ep-space-5);
  align-items: center;
  margin-bottom: var(--ep-space-8);
}
.ep-newsletter .ep-display{
  font-size: var(--ep-size-display-m);
  color: var(--ep-orange-ink);
}
.ep-newsletter__form{ display: flex; gap: var(--ep-space-2); }
.ep-newsletter__submit{
  background: var(--ep-navy);
  color: var(--ep-cream);
}
.ep-newsletter__submit:hover{ background: var(--ep-navy-2); }
.ep-newsletter .ep-input:focus{
  border-color: var(--ep-orange-ink);
  box-shadow: none;
}
.ep-newsletter :where(a, button, input):focus-visible{
  outline-color: var(--ep-orange-ink);
}

.ep-footer__grid{
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1fr;
  gap: var(--ep-space-6);
}
.ep-footer h4{
  font-size: var(--ep-size-eyebrow);
  letter-spacing: var(--ep-track-eyebrow);
  text-transform: uppercase;
  font-weight: 500;
  color: var(--ep-grey);
  margin: 0 0 var(--ep-space-3);
}
.ep-footer ul{ display: grid; gap: var(--ep-space-2); }
.ep-footer a{
  color: var(--ep-mist);
  text-decoration: none;
  font-size: var(--ep-size-small);
  transition: color var(--ep-transition) var(--ep-ease-out);
}
.ep-footer a:hover{ color: var(--ep-cream); }
.ep-footer__pitch{
  margin-top: var(--ep-space-3);
  max-width: 34ch;
  font-size: var(--ep-size-small);
}
.ep-footer__base{
  border-top: 1px solid var(--ep-border-dark);
  margin-top: var(--ep-space-7);
  padding-top: var(--ep-space-4);
  font-size: var(--ep-size-caption);
  color: var(--ep-grey);
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--ep-space-3);
}

/* ============================================================
   8 · RESPONSIVE
   ============================================================ */

/* ---- 1500 : le cadre se resserre ---- */
@media (max-width: 1500px){
  .ep-layer--panel-side{ padding-right: 40%; }
  .ep-header__nav{ gap: var(--ep-space-5); }
}

/* ---- 1100 : le rail de chapitres disparaît ---- */
@media (max-width: 1100px){
  .ep-chapters{ display: none; }
  .ep-layer--panel-side{ padding-right: 30%; }
  .ep-layer[data-ui="traversee"]{ padding-right: var(--ep-frame-x); }
  .ep-cities__head{ max-width: 34rem; }
  /* Le bloc hero s'élargit dans le cadre : on lui réserve le couloir de
     l'invitation à défiler, qui vit au centre du bord bas. */
  .ep-layer--hero{
    padding-bottom: calc(var(--ep-frame-y) + var(--ep-space-9) + var(--ep-space-4));
  }
}

/* Cadre court : l'invitation à défiler n'a plus la place d'exister. */
@media (max-height: 620px){
  .ep-hint{ display: none; }
}

/* ---- 820 : point de rupture du système de design ---- */
@media (max-width: 820px){
  .ep-newsletter{ grid-template-columns: 1fr; }
  .ep-footer__grid{ grid-template-columns: 1fr 1fr; }
  /* La nav NE disparaît PAS ici. Le rail de chapitres est déjà parti à
     1100 px, et il ne pouvait de toute façon pas la remplacer : dans
     index.html l'`<ol class="ep-chapters">` est `aria-hidden="true"`, ses
     items sont des `<li><span>` sans lien ni `data-goto`, et la règle porte
     `pointer-events: none` — c'est un indicateur de position, pas une
     navigation. En la masquant aussi, il ne restait au visiteur étroit que
     le logo et « Nous parler » : aucun moyen d'atteindre les cinq temps du
     récit autre que 7 600 px de défilement. On la resserre donc au lieu de
     la supprimer — à 820 px les quatre intitulés tiennent encore sur la
     ligne de l'en-tête. */
  .ep-header{ gap: var(--ep-space-4); }
  .ep-header__nav{
    gap: var(--ep-space-4);
    font-size: var(--ep-size-caption);
    white-space: nowrap;
  }
  .ep-layer--panel-side{ padding-right: var(--ep-frame-x); }
}

/* ---- 640 : mobile ---- */
@media (max-width: 640px){
  .ep-layer{
    padding-left: var(--ep-space-5);
    padding-right: var(--ep-space-5);
  }

  /* ---- L'en-tête passe à deux rangées --------------------------------------
     La marque et l'action tiennent la première, les cinq temps du récit la
     seconde. À 13 px les quatre intitulés mesurent ensemble ~230 px : ils
     entrent dans les 272 px utiles d'un écran de 320. C'est la seule
     navigation possible — index.html est figé, il n'a pas de bouton de menu,
     et le rail de chapitres n'est pas cliquable (voir le palier 820).
     Le filet sous chaque intitulé est piloté par --s-cap, --s-traversee… :
     sur mobile il fait donc aussi office de fil d'Ariane.                    */
  :root{
    --ep-header-h: calc(var(--ep-space-8) + var(--ep-space-6));
  }
  .ep-header{
    grid-template-columns: auto auto;
    justify-content: space-between;
    row-gap: var(--ep-space-3);
    padding-left: var(--ep-space-5);
    padding-right: var(--ep-space-5);
  }
  .ep-header__nav{
    grid-column: 1 / -1;
    grid-row: 2;
    justify-self: stretch;
    justify-content: space-between;
    gap: var(--ep-space-2);
  }

  /* ------------------------------------------------------------------------
     LES VOILES DENSES sont définis en § 8.5 et ne sont PAS clavés sur la
     largeur : ils appartiennent au palier de qualité du moteur. Ce palier est
     posé une seule fois, au démarrage — un desktop redimensionné en cours de
     route garde donc `ep-quality-high` alors que ces règles de largeur, elles,
     s'appliquent. C'est la seule raison d'être de cette seconde porte
     d'entrée : elle ne recopie aucune pile, elle réutilise les mêmes.        */
  .ep-layer--hero::before{ background: var(--ep-veil-hero); }

  /* Le hero descend d'un cran et s'assied plus bas dans le cadre. */
  .ep-hero__title{ font-size: var(--ep-size-display-xl); }
  .ep-hero__sub{
    font-size: var(--ep-size-display-m);
    padding-left: calc(var(--ep-space-6) + var(--ep-space-2));
  }
  .ep-hero__sub::before{ width: var(--ep-space-6); }
  .ep-hero__lead{ font-size: var(--ep-size-body); }
  .ep-hero__actions{
    width: 100%;
    display: grid;
    gap: var(--ep-space-2);
  }
  .ep-hero__step{ width: 100%; justify-content: flex-start; }

  .ep-layer--hero{
    align-content: end;
    align-content: safe end;
    padding-bottom: calc(var(--ep-space-9) + var(--ep-space-5));
  }

  /* Les panneaux prennent plus de gouttière et s'assoient plus bas. */
  .ep-layer--panel,
  .ep-layer--panel-side{
    align-content: end;
    align-content: safe end;
    padding-right: var(--ep-space-5);
    padding-bottom: calc(var(--ep-space-8) + var(--ep-space-5));
  }
  .ep-layer[data-ui="traversee"]{ justify-items: start; }
  /* Les trois panneaux partagent la même nappe : sur un cadre étroit ils
     occupent tous le même quadrant, et L'Ancrage — la boulangerie nette,
     plein cadre, derrière quatre lignes de texte — fixe le besoin. */
  .ep-layer[data-ui="traversee"]::before,
  .ep-layer--panel::before,
  .ep-layer--panel-side::before{ background: var(--ep-veil-panel); }
  /* Les quatre eyebrows qui tombent sur du ciel reçoivent leur halo dense. */
  .ep-cities__head .ep-eyebrow{ width: max-content; max-width: 100%; }
  :is(.ep-panel, .ep-hero, .ep-cities__head, .ep-outro) > .ep-eyebrow::before{
    inset: calc(var(--ep-space-3) * -1) calc(var(--ep-space-9) * -1);
    background: var(--ep-veil-word);
  }
  .ep-panel{ max-width: 100%; }
  .ep-panel__title{ font-size: var(--ep-size-display-l); }
  .ep-panel__points{ margin-top: var(--ep-space-5); }
  .ep-panel__points li{ padding: var(--ep-space-3) 0; }

  /* Le carrousel se fait glisser au doigt (pointer drag, ui/carousel.js). */
  .ep-layer--cities{
    padding-left: 0; padding-right: 0;
    align-content: end;
    align-content: safe end;
    padding-bottom: var(--ep-space-7);
  }
  /* Ici le titre tombe pile sur le fronton éclairé de la boulangerie. Les
     cartes, elles, sont opaques : la nappe ne se voit que derrière l'en-tête
     et autour des cartes, on peut donc la monter franchement. */
  .ep-layer--cities::before{ background: var(--ep-veil-cities); }
  .ep-cities{ gap: var(--ep-space-5); }
  .ep-cities__head,
  .ep-cities__controls{ padding: 0 var(--ep-space-5); }
  .ep-cities__title{ font-size: var(--ep-size-display-s); }
  .ep-city{ flex: 0 0 82vw; }
  .ep-city.is-active{ transform: translate3d(0, calc(var(--ep-space-2) * -1), 0); }
  /* Un écran étroit sur machine à souris existe : la levée de survol suit le
     même barème réduit que la carte active. */
  @media (hover: hover){
    .ep-city.is-active:hover{
      transform: translate3d(0, calc((var(--ep-space-2) + var(--ep-space-1)) * -1), 0);
    }
  }

  .ep-layer--outro{
    align-content: end;
    align-content: safe end;
    padding-bottom: calc(var(--ep-space-8) + var(--ep-space-5));
  }
  /* La sortie n'est plus centrée mais assise en bas : la nappe descend avec
     elle, sinon le lobe centré du desktop laisse tout le bloc à découvert. */
  .ep-layer--outro::before{ background: var(--ep-veil-outro); }
  .ep-outro__title{ font-size: var(--ep-size-display-l); }
  .ep-outro__cta{ width: 100%; }

  .ep-hint{ bottom: var(--ep-space-4); }
  .ep-hint__rail{ height: var(--ep-space-7); }

  .ep-footer__grid{ grid-template-columns: 1fr; }
  .ep-newsletter{ padding: var(--ep-space-5); }
  .ep-newsletter__form{ flex-direction: column; }
  .ep-newsletter__submit{ justify-content: center; }
}

@media (max-width: 640px) and (orientation: landscape){
  .ep-layer--hero{ padding-bottom: var(--ep-space-7); }
  .ep-hero__sub, .ep-hint{ display: none; }
}

/* ---- Cadre court : téléphone couché --------------------------------------
   375 px de haut, dont 72 pour l'en-tête. Le panneau mesurait ~320 px pour
   190 utiles : il débordait des deux côtés, et `.ep-stage` étant en
   `overflow: hidden`, l'eyebrow, le titre et le chapô étaient purement
   inatteignables. `align-content: safe` (§5.2) évite déjà de perdre le haut ;
   ici on récupère la hauteur qui manque — le titre descend d'un palier
   d'affichage, les points passent au corps « small », et le cadre rend ses
   marges. Ce bloc vient APRÈS le palier 640 : sur un écran à la fois étroit
   et court, c'est la hauteur qui commande.                                  */
@media (max-height: 560px){
  .ep-layer--panel,
  .ep-layer--panel-side,
  .ep-layer--outro{
    padding-top: calc(var(--ep-header-h) + var(--ep-space-3));
    padding-bottom: var(--ep-space-5);
  }
  .ep-panel__title,
  .ep-outro__title{ font-size: var(--ep-size-display-m); }
  .ep-panel__lead,
  .ep-outro__lead{ margin-top: var(--ep-space-3); }
  .ep-outro__cta{ margin-top: var(--ep-space-4); }
  .ep-panel__points{ margin-top: var(--ep-space-4); }
  .ep-panel__points li{
    padding: var(--ep-space-2) 0;
    font-size: var(--ep-size-small);
  }
}

/* ============================================================
   8.5 · LE VOILE DENSE — clavé sur le PALIER DE QUALITÉ, pas sur la largeur
   ============================================================

   Le moteur retombe en palier 0.5 dès que `min(innerWidth, innerHeight) < 700`
   (main.js, `pickQuality`) et il ne monte alors PAS le BokehPass
   (`if (quality >= 1)`). Le desktop tire l'essentiel de sa séparation
   texte/fond d'un arrière-plan défocalisé ; ce palier reçoit le même monde,
   NET, contrasté, avec du détail exactement à la fréquence du texte —
   mullions de fenêtres, lettrage de la boulangerie, rayures du store. Le
   voile doit donc y faire matériellement plus de travail.

   CE BLOC ÉTAIT CLAVÉ SUR `max-width: 640px`, ET C'ÉTAIT LE MAUVAIS SIGNAL.
   Les deux critères divergent sur TOUS les téléphones couchés : 667×375,
   736×414 et 844×390 sont en palier 0.5 — donc rendu net — et pourtant plus
   larges que 640, donc servis avec le voile du desktop. Sur L'Ancrage le
   chapô traversait la façade éclairée de la boulangerie à 1,4:1 mesuré.
   Le moteur publie maintenant le fait lui-même (SPEC §9) :
     body.ep-quality-low   — palier < 1
     body.ep-quality-high  — palier 1
     --ep-has-dof          — 1 ou 0
   On lit le fait, plus le proxy. `ep-quality-low` n'est délibérément pas posé
   sur le repli sans WebGL : il n'y a donc rien à en exclure ici.

   POURQUOI DEUX PORTES D'ENTRÉE. Le palier est décidé UNE FOIS, au démarrage
   (`publishQuality` n'est pas rappelé par `resize`). Un desktop démarré large
   puis réduit garde donc `ep-quality-high` alors que les règles de largeur,
   elles, basculent. Le palier 640 garde pour ce cas sa propre porte — mais
   les deux portes appliquent LES MÊMES PILES, définies une seule fois
   ci-dessous. Rien n'est recopié.

   PROCÉDÉ. On empile les deux voiles du système au lieu d'en inventer un
   troisième : deux nappes à --ep-scrim (.55) composent .80 d'alpha, une nappe
   --ep-scrim-soft porte la retombée. Aucune valeur nouvelle — de la
   composition, comme les calc() ailleurs dans ce fichier.

   GÉOMÉTRIE. Chaque nappe est une ellipse dimensionnée sur le bloc de copie,
   et son rayon vertical MEURT à l'intérieur du cadre : elle n'est jamais
   tranchée en haut, donc aucune arête. Seuls le bas et les côtés sont coupés
   — c'est le bord de l'écran, il n'y a rien à y voir.
   Les DEUX SEULS réglages qui changent d'un cadrage à l'autre sont l'assise
   du bloc de copie (--ep-veil-y) et la demi-hauteur qu'il occupe
   (--ep-veil-r). La composition, elle, est écrite une seule fois : le cadre
   couché (§8.6) redéfinit ces deux nombres, il ne recopie pas les piles.
   (Le piège documenté : dans `radial-gradient(56% 100% …)` les pourcentages
   sont des RAYONS. Sur une boîte à la taille d'un mot — les halos ci-dessous
   — il faut `ellipse closest-side`, sinon l'ellipse déborde sa boîte, se fait
   trancher au bord, et le rectangle devient visible.)                       */
:root{
  /* Cadre debout : la copie est assise dans le bas du cadre. */
  --ep-veil-y: 66%;
  --ep-veil-r: 54%;

  /* La nappe des panneaux — et de la sortie, qui s'assied pareil. */
  --ep-veil-panel:
    radial-gradient(132% var(--ep-veil-r) at 50% var(--ep-veil-y),
      var(--ep-scrim) 0%, var(--ep-scrim) 50%, var(--ep-scrim-soft) 78%, transparent 100%),
    radial-gradient(154% calc(var(--ep-veil-r) + 12%) at 50% calc(var(--ep-veil-y) + 8%),
      var(--ep-scrim-soft) 0%, var(--ep-scrim-soft) 52%, transparent 100%),
    linear-gradient(to top,
      var(--ep-scrim-soft) 0%, var(--ep-scrim-soft) 34%, transparent 76%);

  --ep-veil-outro:
    radial-gradient(136% calc(var(--ep-veil-r) - 2%) at 50% var(--ep-veil-y),
      var(--ep-scrim) 0%, var(--ep-scrim) 48%, var(--ep-scrim-soft) 78%, transparent 100%),
    radial-gradient(158% calc(var(--ep-veil-r) + 10%) at 50% calc(var(--ep-veil-y) + 8%),
      var(--ep-scrim-soft) 0%, var(--ep-scrim-soft) 50%, transparent 100%),
    linear-gradient(to top,
      var(--ep-scrim-soft) 0%, var(--ep-scrim-soft) 32%, transparent 74%);

  /* Les villes : les cartes sont opaques, la nappe ne se voit qu'autour
     d'elles et derrière le titre — elle peut donc monter franchement. */
  --ep-veil-cities:
    radial-gradient(140% calc(var(--ep-veil-r) - 8%) at 50% calc(var(--ep-veil-y) - 10%),
      var(--ep-scrim) 0%, var(--ep-scrim) 46%, var(--ep-scrim-soft) 76%, transparent 100%),
    radial-gradient(160% calc(var(--ep-veil-r) + 6%) at 50% var(--ep-veil-y),
      var(--ep-scrim-soft) 0%, var(--ep-scrim-soft) 48%, transparent 100%),
    linear-gradient(to top,
      var(--ep-scrim) 0%, var(--ep-scrim-soft) 34%, transparent 74%);

  /* Le hero porte une nappe de plus que les autres : la liste des sept pays
     est le seul texte du site à monter aussi haut dans le cadre — des
     capitales de 11 px, en orange, sur le ciel de Paris, qui est la valeur la
     plus claire du plan. Un halo elliptique ne peut pas la tenir :
     `closest-side` s'inscrit DANS sa boîte, et les extrémités d'un bloc
     pleine largeur tombent à ~0,8 du rayon, là où la densité est déjà
     retombée. Une bande de cadre, large et plate, calée sur ces lignes, les
     couvre uniformément — et meurt bien avant le haut comme les autres. */
  --ep-veil-hero:
    radial-gradient(150% 24% at 50% calc(var(--ep-veil-y) - 28%),
      var(--ep-scrim) 0%, var(--ep-scrim) 36%, var(--ep-scrim-soft) 68%, transparent 100%),
    radial-gradient(136% calc(var(--ep-veil-r) + 2%) at 50% calc(var(--ep-veil-y) - 8%),
      var(--ep-scrim) 0%, var(--ep-scrim) 48%, var(--ep-scrim-soft) 78%, transparent 100%),
    radial-gradient(158% calc(var(--ep-veil-r) + 16%) at 50% calc(var(--ep-veil-y) + 6%),
      var(--ep-scrim-soft) 0%, var(--ep-scrim-soft) 50%, transparent 100%),
    linear-gradient(to top,
      var(--ep-scrim-soft) 0%, var(--ep-scrim-soft) 30%, transparent 72%);

  /* Le halo d'un MOT. 11 px en capitales est le plus petit texte du site et
     l'orange plafonne à 5,6:1 sur la marine : il lui faut quasiment tout le
     voile sous les lettres. Deux nappes empilées, toujours `closest-side`. */
  --ep-veil-word:
    radial-gradient(ellipse closest-side,
      var(--ep-scrim) 0%, var(--ep-scrim) 44%, var(--ep-scrim-soft) 66%, transparent 100%),
    radial-gradient(ellipse closest-side,
      var(--ep-scrim) 0%, var(--ep-scrim-soft) 54%, transparent 100%);
}

/* La BOÎTE du halo existe partout ; elle ne PEINT que si une pile lui est
   donnée. Sans `background`, un pseudo-élément vide ne coûte pas un pixel et
   ne prend pas un pixel de place — absolu, hors flux, derrière son parent :
   le desktop est donc strictement inchangé, et les deux portes d'entrée
   n'ont plus qu'une seule déclaration à poser.
   La seule chose qui, elle, changerait une BOÎTE est `max-content` sur
   l'eyebrow des villes : il reste donc dans les portes d'entrée, pas ici.
   (`.ep-panel`, `.ep-hero` et `.ep-outro` sont des grilles en
   `justify-items` : leurs items sont déjà à la largeur du contenu.
   `.ep-cities__head` est un bloc — sans `max-content` son paragraphe fait
   toute la largeur et l'ellipse `closest-side` redevient une bande.) */
:where(.ep-hero, .ep-cities__head, .ep-outro) > .ep-eyebrow{ position: relative; }
:where(.ep-hero, .ep-cities__head, .ep-outro) > .ep-eyebrow::before{
  content: "";
  position: absolute;
  z-index: -1;
  inset: calc(var(--ep-space-3) * -1) calc(var(--ep-space-8) * -1);
  pointer-events: none;
}

/* ---- La porte d'entrée : le fait publié par le moteur, à toute largeur. -- */
body.ep-quality-low .ep-layer--hero::before  { background: var(--ep-veil-hero); }
body.ep-quality-low .ep-layer--panel::before,
body.ep-quality-low .ep-layer--panel-side::before,
body.ep-quality-low .ep-layer[data-ui="traversee"]::before{ background: var(--ep-veil-panel); }
body.ep-quality-low .ep-layer--cities::before{ background: var(--ep-veil-cities); }
body.ep-quality-low .ep-layer--outro::before { background: var(--ep-veil-outro); }
/* Le halo doit être DEUX FOIS plus large que le mot, pas juste plus grand que
   lui : `closest-side` cale le rayon sur la boîte, donc c'est la boîte qui
   décide où le plateau de pleine densité s'arrête. À --ep-space-8 les deux
   dernières lettres d'« ACCOMPAGNEMENT 01 » tombaient déjà dans la retombée
   et l'eyebrow mesurait 4,4:1 ; à --ep-space-9 le mot entier tient dans le
   plateau. */
body.ep-quality-low .ep-cities__head .ep-eyebrow{ width: max-content; max-width: 100%; }
body.ep-quality-low :is(.ep-panel, .ep-hero, .ep-cities__head, .ep-outro) > .ep-eyebrow::before{
  inset: calc(var(--ep-space-3) * -1) calc(var(--ep-space-9) * -1);
  background: var(--ep-veil-word);
}
/* Le rail de chapitres et les intitulés de l'en-tête reposent sur le même
   ciel net. Ils portent déjà trois ombres empilées ; sur un rendu sans
   profondeur de champ il leur faut en plus leur pleine valeur. */
body.ep-quality-low .ep-header__nav a{ opacity: 1; }
body.ep-quality-low .ep-header::before{
  background: linear-gradient(to bottom,
    var(--ep-scrim) 0%, var(--ep-scrim) 46%, var(--ep-scrim-soft) 78%, transparent 100%);
}

/* ============================================================
   8.6 · TÉLÉPHONE COUCHÉ — le panneau passe en DEUX COLONNES
   ============================================================

   844×390 : 390 px de haut, dont 84 pour l'en-tête et la gouttière basse. Il
   reste 282 px utiles et le panneau en mesurait 335 — le quatrième point
   tombait hors du cadre et, `.ep-stage` étant en `overflow: hidden`, il
   disparaissait purement. Le rythme vertical avait déjà été comprimé au
   palier précédent (§ max-height 560) : il manquait encore ~65 px, et il n'y
   a plus rien à prendre en hauteur.

   On arrête donc de se battre pour une hauteur qui n'existe pas et on prend
   la largeur, elle, disponible : 844 px pour une colonne de copie de 480.
   L'eyebrow, le titre et le chapô tiennent la première colonne ; la liste des
   points, la seconde. Aucun changement de balisage — `grid-template-areas`
   sur le `.ep-panel` existant suffit, et la cascade de révélation (--i, posé
   par ui/panels.js) continue de se lire en ordre de document.

   LA GOUTTIÈRE EST LE SUJET. Elle vaut un tiers du cadre, et ce n'est pas du
   vide : sur L'Ancrage elle tombe exactement sur la boulangerie, qui reste
   donc lisible ENTRE les deux colonnes au lieu d'être recouverte — la règle
   « la boulangerie ne doit jamais être couverte » (§5.2) survit au cadre
   couché. Sur La Traversée elle dégage de même le Palais de la Culture.

   Le seuil : 641 px de large (le palier mobile commande en dessous) et
   560 px de haut. Un desktop 1600×900 n'entre dans aucun des deux.        */
@media (min-width: 641px) and (max-height: 560px){
  /* Le bloc de copie n'est plus assis dans le bas du cadre : il l'occupe en
     entier. Le voile (§8.5) monte avec lui et s'élargit — deux nombres. */
  :root{
    --ep-veil-y: 62%;
    --ep-veil-r: 58%;
  }

  /* ---- Les trois panneaux ---- */
  .ep-layer--panel,
  .ep-layer--panel-side{
    align-content: center;
    align-content: safe center;
    padding-left: var(--ep-frame-x);
    padding-right: var(--ep-frame-x);
    padding-top: calc(var(--ep-header-h) + var(--ep-space-2));
    padding-bottom: var(--ep-space-4);
  }
  .ep-layer[data-ui="traversee"]{ justify-items: start; }

  .ep-panel,
  .ep-layer[data-ui="traversee"] .ep-panel{
    width: 100%;
    max-width: 100%;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: 32%;
    grid-template-areas:
      "eyebrow points"
      "title   points"
      "lead    points";
    align-content: start;
  }
  .ep-panel > .ep-eyebrow{ grid-area: eyebrow; }
  .ep-panel__title      { grid-area: title; }
  .ep-panel__lead       { grid-area: lead; font-size: var(--ep-size-body); }
  .ep-panel__points     { grid-area: points; align-self: start; margin-top: 0; }
  .ep-layer[data-ui="traversee"] .ep-panel__points{ width: 100%; }
  .ep-layer[data-ui="traversee"] .ep-panel__lead{ max-width: 100%; }

  /* ---- Le hero ----
     Le titre descend d'un palier d'affichage — display-hero coûtait 149 px de
     hauteur pour 294 utiles — et le chapô part sur la même ligne que les
     trois marches. En dessous, le chapô ET les trois marches tombaient
     entièrement hors du cadre : le visiteur couché n'avait plus aucune
     navigation vers les trois programmes. */
  .ep-layer--hero{
    align-content: end;
    align-content: safe end;
    padding-top: calc(var(--ep-header-h) + var(--ep-space-2));
    padding-bottom: var(--ep-space-5);
  }
  .ep-hero{
    width: 100%;
    /* La colonne des marches est la plus large des deux : les trois libellés
       mis bout à bout mesurent plus que le chapô. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr);
    column-gap: var(--ep-space-6);
    grid-template-areas:
      "eyebrow eyebrow"
      "title   title"
      "sub     sub"
      "lead    actions";
  }
  .ep-hero > .ep-eyebrow{ grid-area: eyebrow; }
  .ep-hero__title{
    grid-area: title;
    margin-top: var(--ep-space-3);
    font-size: var(--ep-size-display-xl);
  }
  .ep-hero__sub{
    grid-area: sub;
    margin-top: var(--ep-space-3);
    font-size: var(--ep-size-display-m);
    padding-left: calc(var(--ep-space-6) + var(--ep-space-2));
  }
  .ep-hero__sub::before{ width: var(--ep-space-6); }
  .ep-hero__lead{
    grid-area: lead;
    align-self: center;
    margin-top: var(--ep-space-4);
    font-size: var(--ep-size-body);
  }
  .ep-hero__actions{
    grid-area: actions;
    align-self: center;
    margin-top: var(--ep-space-4);
    gap: var(--ep-space-2);
  }
  .ep-hero__step{
    padding: var(--ep-space-2) var(--ep-space-4);
    font-size: var(--ep-size-caption);
  }

  /* ---- Les villes ----
     Même mouvement : l'intitulé et les commandes prennent une colonne, le
     rail garde toute la largeur restante et continue de sortir du cadre à
     droite. La carte, elle, remet ses deux données côte à côte — empilées
     elle mesurait 275 px pour 254 utiles et la légende de la première donnée
     se faisait couper. */
  .ep-layer--cities{
    align-content: center;
    align-content: safe center;
    padding-top: calc(var(--ep-header-h) + var(--ep-space-2));
    padding-bottom: var(--ep-space-4);
  }
  .ep-cities{
    grid-template-columns: minmax(0, 16rem) minmax(0, 1fr);
    grid-template-areas:
      "head     rail"
      "controls rail";
    align-content: center;
    column-gap: var(--ep-space-5);
    row-gap: var(--ep-space-4);
  }
  .ep-cities__head{
    grid-area: head;
    align-self: end;
    max-width: 100%;
    padding: 0 0 0 var(--ep-frame-x);
  }
  .ep-cities__title{ font-size: var(--ep-size-display-s); }
  .ep-cities__controls{
    grid-area: controls;
    align-self: start;
    padding: 0 0 0 var(--ep-frame-x);
  }
  .ep-cities__viewport{
    grid-area: rail;
    /* Le fondu de bord vaut la gouttière de cadre partout ailleurs ; ici le
       rail commence au milieu de l'écran et un fondu de cette largeur
       mangerait le tiers gauche de la première carte. */
    --ep-edge-fade: var(--ep-space-3);
    padding: var(--ep-space-4) 0 var(--ep-space-5);
    margin: calc(var(--ep-space-4) * -1) 0 calc(var(--ep-space-5) * -1);
  }
  .ep-city{ padding: var(--ep-space-4); }
  .ep-city__stats{
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    gap: var(--ep-space-4);
    margin-top: var(--ep-space-2);
  }
  /* Le filet de séparation tourne d'un quart de tour avec les données. */
  .ep-city__stat + .ep-city__stat{
    padding-top: 0;
    border-top: 0;
    padding-left: var(--ep-space-4);
    border-left: 1px solid var(--ep-border-light);
  }
  .ep-city__value{ font-size: var(--ep-size-display-l); }
}

/* ============================================================
   9 · REPLIS — gradués, pas optionnels
   ============================================================ */

/* Un vrai document, complet et lisible, sans une ligne de 3D. Le même jeu de
   règles sert trois cas : pas de WebGL, mouvement réduit, pas de script. */
.ep-no-webgl #ep-stage,
.ep-no-webgl .ep-boot,
.ep-no-webgl .ep-hint,
.ep-no-webgl .ep-chapters,
.ep-no-webgl .ep-progress{ display: none !important; }

.ep-no-webgl{ background: var(--ep-navy); }

.ep-no-webgl .ep-cinema{ height: auto; }

.ep-no-webgl .ep-stage{
  position: static;
  height: auto;
  overflow: visible;
  display: block;
}

.ep-no-webgl .ep-layer{
  position: static;
  display: block;
  /* Chromium applique désormais l'alignement de boîte en disposition bloc :
     sans ces deux remises à zéro, les couches restent alignées à droite. */
  justify-items: start;
  align-content: start;
  text-align: left;
  opacity: 1 !important;
  visibility: visible !important;
  transform: none !important;
  animation: none !important;
  pointer-events: auto;
  padding: var(--ep-space-9) var(--ep-frame-x);
  border-top: 1px solid var(--ep-border-dark);
}
.ep-no-webgl .ep-layer:first-of-type{ border-top: 0; }
.ep-no-webgl .ep-layer::before{ display: none; }
.ep-no-webgl .ep-layer--hero{ padding-top: calc(var(--ep-header-h) + var(--ep-space-8)); }
.ep-no-webgl .ep-layer--panel-side{ padding-right: var(--ep-frame-x); }
.ep-no-webgl .ep-layer--cities{ padding-left: 0; padding-right: 0; }

.ep-no-webgl .ep-hero,
.ep-no-webgl .ep-panel,
.ep-no-webgl .ep-outro{ transform: none !important; }
.ep-no-webgl .ep-outro{ justify-items: start; max-width: 44rem; }

/* Les cascades pilotées par le signal deviennent inconditionnelles. */
.ep-no-webgl :where(.ep-line__in, .ep-hero__step, .ep-panel__lead, .ep-panel__points li){
  --p: 1;
  opacity: 1 !important;
  transform: none !important;
}

.ep-no-webgl :where(.ep-hero__title, .ep-hero__sub, .ep-hero__lead,
  .ep-panel__title, .ep-panel__lead, .ep-panel__points li,
  .ep-cities__title, .ep-outro__title, .ep-outro__lead,
  .ep-cities__counter, .ep-header__nav a, .ep-logo, .ep-eyebrow){
  text-shadow: none;
}

.ep-no-webgl .ep-header{
  position: static;
  transform: none;
  background: var(--ep-navy);
  border-bottom: 1px solid var(--ep-border-dark);
}
.ep-no-webgl .ep-header::before,
.ep-no-webgl .ep-header::after{ display: none; }

.ep-no-webgl .ep-cities__viewport{
  overflow: visible;
  padding: 0;
  margin: 0;
  touch-action: auto;
  cursor: auto;
  -webkit-mask-image: none;
  mask-image: none;
}
/* Plus de ciel derrière le texte : les halos n'ont plus rien à compenser. */
.ep-no-webgl .ep-panel .ep-eyebrow::before,
.ep-no-webgl .ep-cities__head .ep-eyebrow::before{ display: none; }
.ep-no-webgl .ep-cities__track{
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: var(--ep-space-4);
  padding: 0 var(--ep-frame-x);
  transform: none;
  transition: none;
}
.ep-no-webgl .ep-city{ flex: initial; cursor: auto; transform: none !important; }
/* Les deux jeux clonés de la boucle n'ont plus de raison d'être. */
.ep-no-webgl .ep-city[aria-hidden="true"]{ display: none; }
.ep-no-webgl .ep-cities__controls{ display: none; }

@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    transition: none !important;
    animation: none !important;
    scroll-behavior: auto !important;
  }
}

/* Mouvement réduit ou pas de script : même document statique que ci-dessus.
   Aucune parallaxe, aucune couche empilée, aucun signal — juste le contenu. */
@media (prefers-reduced-motion: reduce), (scripting: none){
  body{ background: var(--ep-navy); }

  #ep-stage, .ep-boot, .ep-hint, .ep-chapters, .ep-progress{ display: none !important; }

  .ep-cinema{ height: auto; }
  .ep-stage{ position: static; height: auto; overflow: visible; display: block; }

  .ep-layer{
    position: static;
    display: block;
    justify-items: start;
    align-content: start;
    text-align: left;
    opacity: 1 !important;
    visibility: visible !important;
    transform: none !important;
    pointer-events: auto;
    padding: var(--ep-space-9) var(--ep-frame-x);
    border-top: 1px solid var(--ep-border-dark);
  }
  .ep-layer:first-of-type{ border-top: 0; }
  .ep-layer::before{ display: none; }
  .ep-layer--hero{ padding-top: calc(var(--ep-header-h) + var(--ep-space-8)); }
  .ep-layer--panel-side{ padding-right: var(--ep-frame-x); }
  /* Même spécificité que la règle en miroir, donc elle la remplace ici. */
  .ep-layer[data-ui="traversee"]{
    justify-items: start;
    padding-right: var(--ep-frame-x);
  }
  .ep-layer[data-ui="carousel"]{ padding-left: 0; padding-right: 0; }

  .ep-hero, .ep-panel, .ep-outro{ transform: none !important; }
  .ep-outro{ justify-items: start; max-width: 44rem; }

  :where(.ep-line__in, .ep-hero__step, .ep-panel__lead, .ep-panel__points li){
    --p: 1;
    opacity: 1 !important;
    transform: none !important;
  }

  :where(.ep-hero__title, .ep-hero__sub, .ep-hero__lead,
    .ep-panel__title, .ep-panel__lead, .ep-panel__points li,
    .ep-cities__title, .ep-outro__title, .ep-outro__lead,
    .ep-cities__counter, .ep-header__nav a, .ep-logo, .ep-eyebrow){
    text-shadow: none;
  }

  .ep-header{
    position: static;
    transform: none;
    background: var(--ep-navy);
    border-bottom: 1px solid var(--ep-border-dark);
  }
  .ep-header::before, .ep-header::after{ display: none; }

  .ep-cities__viewport{
    overflow: visible; padding: 0; margin: 0;
    touch-action: auto; cursor: auto;
    -webkit-mask-image: none;
    mask-image: none;
  }
  .ep-panel .ep-eyebrow::before,
  .ep-cities__head .ep-eyebrow::before{ display: none; }
  .ep-cities__track{
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    gap: var(--ep-space-4);
    padding: 0 var(--ep-frame-x);
    transform: none;
  }
  .ep-city{ flex: initial; cursor: auto; transform: none !important; }
  .ep-city[aria-hidden="true"]{ display: none; }
  .ep-cities__controls{ display: none; }

  body.ep-booting .ep-layer,
  body.ep-booting .ep-header,
  body.ep-booting .ep-footer{ opacity: 1; visibility: visible; }
}
