* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* <picture> (variantes mobiles) : transparent au layout, l'<img> à
   l'intérieur (déjà display:block) porte seul la mise en forme. */
picture {
  display: block;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;         /* .stack devient le seul conteneur de scroll (voir fisheye.js) */
  background: #fff;
}

.stack {
  position: relative; /* contexte d'empilement propre : garantit que .stack peint au-dessus du hero fixe (z-index:0, cf. plus bas) quel que soit l'ordre du DOM */
  z-index: 1;
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh; /* mobile : suit la barre d'adresse au lieu du plus grand viewport possible */
  overflow-y: auto;
  overflow-x: hidden;
  /*
    PAS de -webkit-overflow-scrolling:touch : c'est un vieux hack (requis
    avant iOS 13 pour le scroll "momentum" sur un conteneur overflow) —
    iOS le gère nativement depuis longtemps, et cette propriété a un
    historique documenté de bugs quand elle est combinée à un filter CSS
    sur le même élément (le filtre ne s'applique tout simplement pas, sans
    erreur). Suspect direct de "pas de fisheye sur mobile" vu que .stack
    porte justement le filter (cf. fisheye.js).
  */
  /*
    Scroll "forcé" : chaque molette/swipe amène l'utilisateur au DÉBUT
    d'une section, jamais entre deux (mandatory) — un léger scroll ne
    laisse plus la page à mi-chemin entre deux médias. scroll-snap-stop:
    always (sur les enfants, ci-dessous) empêche en plus un flick rapide
    de sauter une section entière : chaque section s'arrête au moins une
    fois avant de continuer. Pas d'effet sur hero-fade.js / reveal.js, qui
    lisent scrollTop / observent l'intersection en continu — le snap est
    purement une contrainte CSS sur où le scroll s'arrête.
  */
  scroll-snap-type: y mandatory;
}

@media (prefers-reduced-motion: no-preference) {
  .stack {
    scroll-behavior: smooth; /* anime l'accrochage au lieu d'un saut sec */
  }
}

/*
  Sans ça, flexbox rétrécit les sections pour faire tenir tout le cumul
  dans les 100dvh de .stack (flex-shrink:1 par défaut), au lieu de laisser
  déborder pour que .stack scrolle dessus (c'est tout l'intérêt de son
  overflow-y:auto). flex-shrink:0 fige chaque section à sa taille propre,
  quel que soit le nombre/la hauteur des sections suivantes.
*/
.stack > * {
  flex-shrink: 0;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

/*
  Fond opaque sur chaque section réelle (pas .hero-spacer, qui doit rester
  transparent pour laisser voir le hero fixe en dessous tant qu'on ne l'a
  pas dépassé) : au fil du scroll, chaque section recouvre complètement le
  hero au lieu de laisser voir au travers.
*/
.stack > *:not(.hero-spacer) {
  background: #fff;
}

/*
  position: fixed (pas sticky) : .stack, pas <body>, est le conteneur de
  scroll (cf. plus haut) — un sticky à l'intérieur de .stack scrollerait
  avec lui, alors qu'ici le header est un frère de .stack, donc fixed par
  rapport au viewport le maintient toujours visible. Placé hors de .stack,
  il échappe aussi au filtre fisheye (appliqué uniquement à .stack).
*/
.site-header {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 100vh;
  height: 100dvh; /* mobile : suit la barre d'adresse au lieu du plus grand viewport possible */
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5vw;
  padding: 1.2rem 1.5vw;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 0.8rem;
  font-weight: 900;
  color: #000;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  -webkit-text-stroke: 1.5px #000; /* allégé pour rester proportionné à la typo réduite ; pas de version standard non préfixée à ce jour */
  pointer-events: none; /* la boîte couvre tout le viewport : seuls titre/nav doivent capter les clics */
}

.site-header__title,
.site-header__nav {
  pointer-events: auto;
}

.site-header__title {
  white-space: nowrap;
}

.site-header__nav {
  white-space: nowrap;
}

@media (max-width: 520px) {
  .site-header {
    gap: 3vw;
    padding: 0.9rem 1.5vw;
    font-size: 0.5rem;
    -webkit-text-stroke: 1px #000; /* allégé avec la typo, sinon le contour épaissit le texte réduit au point de le rendre illisible */
  }
}

/*
  Hero : fond FIXE hors de .stack (cf. commentaire dans home.php) — plein
  écran, sous le contenu scrollable (z-index:0 < .stack, z-index:1) et sous
  le header (z-index:20). Ne bouge jamais, ne scrolle jamais : c'est
  .hero-spacer (ci-dessous), premier enfant de .stack, qui occupe la place
  correspondante dans le flux de scroll.
*/
.media--hero {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 0;
  width: 100%;
  max-width: 100%;
  overflow: hidden; /* garde-fou : la vidéo ne doit jamais faire déborder le bloc horizontalement */
  height: 100vh;
  height: 100dvh;
}
.media--hero video {
  display: block;
  width: 100%;
  max-width: 100%;
  height: 100%;
  object-fit: cover; /* remplit tout le viewport, quitte à rogner la largeur */
}

/*
  Mobile : le fichier vidéo mobile compose son texte bord à bord, sans
  marge de sécurité. object-fit:cover (réglage desktop ci-dessus) doit
  alors rogner la largeur sur un écran d'iPhone (plus élancé que le ratio
  de la vidéo) pour remplir toute la hauteur — ce qui coupe du contenu sur
  les bords. object-fit:contain garantit au contraire que la largeur
  affichée est toujours exactement 100% du viewport, sans jamais rogner :
  au pire une fine bande vide en haut/bas (comblée par le fond blanc).
*/
@media (max-width: 520px) {
  .media--hero video {
    object-fit: contain;
    background: #fff;
  }
}

/*
  Distance de scroll avant que la section suivante n'arrive — DOIT rester
  un écran plein (100dvh) : c'est ce qui garantit que rien du contenu
  suivant n'est visible avant que l'utilisateur ait vraiment scrollé (sinon
  la section suivante remonte et se superpose dès le chargement — bug
  constaté en réduisant cette valeur). hero-fade.js cale la fin du fondu
  vidéo sur cette même distance (FADE_FRACTION), donc le fondu se termine
  pile quand le contenu suivant arrive, quelle que soit cette hauteur —
  jamais de blanc, jamais de chevauchement prématuré.
*/
.hero-spacer {
  height: 100vh;
  height: 100dvh;
  margin-bottom: 3rem; /* respiration avant la section suivante, reprise du réglage d'origine du hero */
}

/* Single full-width image below the hero. */
.media--row img {
  display: block;
  width: 100%;
  height: auto;
}

/*
  Mobile : la section prend systématiquement un écran plein (comme le hero
  et les logos, cf. .partners plus bas) — une seule image à la fois à
  l'écran, cohérent avec le reste du parcours mobile. object-fit: contain
  (pas cover) pour rester fidèle au principe du fichier (jamais de
  recadrage) : au pire des bandes blanches en haut/bas si le ratio de
  l'image ne colle pas exactement à l'écran, jamais de perte de contenu.
*/
@media (max-width: 520px) {
  .media--row {
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
  }
  .media--row picture {
    height: 100%;
  }
  .media--row img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    background: #fff;
  }
}

/*
  Image grid: 4 columns x 3 rows on desktop, stepping down to 2 columns
  on tablet and 1 column on mobile. Images always keep width:100%/height:auto
  so they scale with their cell and are never cropped or distorted.
*/
.media-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}
.media-grid__cell img {
  display: block;
  width: 100%;
  height: auto;
}

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

@media (max-width: 520px) {
  .media-grid {
    grid-template-columns: repeat(1, 1fr);
  }
}

/* Full-width single images */
.media--full img {
  display: block;
  width: 100%;
  height: auto;
}

/*
  Padding horizontal : sur la largeur du bloc lui-même (max-width +
  centrage), pas un padding sur .media--full. reveal.js mesure le bloc
  [data-reveal] et fait coïncider mosaïque/masque avec cette boîte ; un
  simple padding créerait un décalage entre la boîte mesurée (pleine
  largeur) et l'image réellement affichée (rétrécie par le padding).
*/
.media--full {
  max-width: calc(100% - 3vw);
  margin-inline: auto;
}

/*
  Logos partenaires : la section occupe toute la largeur du viewport (son
  fond blanc — cf. règle .stack > *:not(.hero-spacer) — doit couvrir le
  hero fixe jusqu'aux bords, sans quoi il resterait visible en bande de
  chaque côté). Le resserrement (calc(100% - 3vw) + margin-inline: auto,
  même valeurs que .media--full) passe donc sur .partners__grid : les
  logos restent centrés avec la même marge qu'avant, seul le fond s'étend
  maintenant edge-to-edge. Pas de data-reveal ici — la mosaïque de
  reveal.js est pensée pour des photos/vidéos, pas pour des logos qui
  doivent rester lisibles immédiatement.
*/
.partners {
  padding-block: 4rem;
}

/*
  Mobile : même logique que .media--row ci-dessus — un écran plein, comme
  le hero, sa propre "slide" dans le scroll-snap de .stack. padding-block
  repasse à 0 (la hauteur vient du viewport, pas du contenu) ;
  display:flex + align-items:center centre verticalement .partners__grid
  dans cet écran, quelle que soit sa hauteur réelle une fois les logos
  disposés. overflow-y: auto en garde-fou : les logos étant empilés un
  par ligne (.partners__grid ci-dessous), 8 logos + gaps peuvent dépasser
  100dvh selon les hauteurs réglées dans le Panel — le débordement devient
  alors un scroll interne à la section plutôt qu'un chevauchement visuel
  avec la suite.
*/
@media (max-width: 520px) {
  .partners {
    height: 100vh;
    height: 100dvh;
    padding-block: 0;
    display: flex;
    align-items: center;
    overflow-y: auto;
  }
}

/*
  Flex plutôt que grid à colonnes égales : avec des colonnes égales,
  chaque logo — quelle que soit sa largeur propre — est centré dans une
  boîte aussi large que le logo le plus large (Région Île-de-France),
  donc entouré d'une marge asymétrique selon sa propre largeur. C'est
  cette marge de boîte, bien plus que le gap, qui créait l'essentiel de
  l'espace perçu entre logos. En flex, chaque cellule ne prend que la
  largeur de son logo : le gap ci-dessous devient la SEULE source
  d'espacement, donc réellement réglable. flex-wrap remplace aussi les
  anciens seuils de colonnes par écran (900px/520px) : les logos se
  reflow naturellement selon la place disponible — une ligne en desktop,
  plusieurs en mobile — sans nombre de colonnes à recalculer si un logo
  est ajouté/retiré.
*/
.partners__grid {
  max-width: calc(100% - 3vw);
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 1.8rem 3.2rem;
}

.partners__cell {
  flex: 0 0 auto;
  /* Hauteur pilotable logo par logo depuis le Panel (champs Hauteur du
     logo desktop/mobile sur chaque fichier) : le template pose --logo-h /
     --logo-h-m sur la cellule ; sans valeur, repli sur la hauteur de
     référence commune. Largeur = celle du logo (voir commentaire ci-dessus) */
  height: var(--logo-h, 3.25rem);
  display: flex;
  align-items: center;
  justify-content: center;
}

@media (max-width: 520px) {
  .partners__cell {
    /* mobile : sa propre valeur, sinon celle du desktop, sinon le standard —
       réduite de 15% par rapport à cette référence (calc), pour que les
       logos restent un peu plus petits qu'en desktop même quand aucun
       champ mobile n'est réglé dans le Panel. */
    height: calc(var(--logo-h-m, var(--logo-h, 3.25rem)) * 0.85);
  }
}

/*
  Logos forcés en noir plein quelle que soit leur couleur d'origine :
  brightness(0) ramène tout pixel opaque à #000 tout en préservant la
  transparence du fond (contrairement à grayscale(), qui produit des
  niveaux de gris fondés sur la luminance plutôt qu'un noir uniforme).
*/
.partners__logo {
  display: block;
  max-height: 100%; /* remplit la hauteur de référence de .partners__cell ; ajustements fins par logo ci-dessous */
  width: auto;
  height: auto;
  object-fit: contain;
  filter: brightness(0);
}

/*
  Ajustements par logo : à hauteur de cellule identique pour tous, un
  trait fin (Ministère de la Culture) paraît plus petit qu'un aplat
  épais (Fun Radio) à taille réelle égale. Le max-height ci-dessous
  (en % de la hauteur de .partners__cell — cf. .partners__logo) corrige
  cette homogénéité involontaire : plus fort sur les tracés fins/chargés,
  plus faible sur les aplats qui portent déjà bien. Volontairement en
  max-height plutôt qu'en transform: scale() — un scale() est purement
  visuel et ne repousse pas la mise en page, ce qui, dans ce flex à
  cellules ajustées au contenu, ferait chevaucher un logo agrandi sur
  son voisin ; max-height, lui, est pris en compte dans le calcul de la
  largeur de la cellule flex, donc l'espacement reste correct.

  Fichiers non identifiables par leur nom
  (logo-partenaires-tp26-copie-01.png, etc.) → ciblage par position dans
  .partners__grid, ordre actuel du champ partner_logos (panneau) : 1
  Technopol, 2 Ministère de la Culture, 3 Région Île-de-France, 4 Ville de
  Paris, 5 Visit Detroit, 6 Fun Radio, 7 FG, 8 Durevie. Si l'ordre change
  dans le panneau, ces règles viseront le mauvais logo — à réassigner en
  conséquence.
*/
.partners__cell:nth-child(2) .partners__logo { max-height: 130%; } /* Ministère de la Culture : bloc-marque + sous-texte fin, plusieurs lignes */
.partners__cell:nth-child(4) .partners__logo { max-height: 95%; }  /* Ville de Paris : aplat compact */
.partners__cell:nth-child(5) .partners__logo { max-height: 90%; }  /* Visit Detroit : script épais, porte déjà bien */
.partners__cell:nth-child(6) .partners__logo { max-height: 85%; }  /* Fun Radio : aplat très épais */
.partners__cell:nth-child(7) .partners__logo { max-height: 85%; }  /* FG : marque minimale, épaisse */
.partners__cell:nth-child(8) .partners__logo { max-height: 110%; } /* Durevie : trait fin */
/* Technopol (1) et Région Île-de-France (3) : pas d'override, gardent le 100% de base. */

@media (max-width: 520px) {
  /*
    Un logo par ligne sur mobile (plutôt que le flex-wrap du desktop) —
    cf. overflow-y: auto sur .partners ci-dessus, garde-fou si les 8
    logos empilés + gaps dépassent les 100dvh de l'écran.
  */
  .partners__grid {
    flex-direction: column;
    gap: 2.2rem;
  }
  /* Hauteur des cellules : cf. règle .partners__cell plus haut
     (var(--logo-h-m, var(--logo-h, 3.25rem))) — un height ici l'écraserait
     pour TOUS les logos, quels que soient les champs Hauteur du logo
     desktop/mobile réglés dans le Panel (bug constaté : tous les logos
     retombaient à 2.2rem sur mobile, y compris ceux avec une hauteur
     desktop personnalisée). Sans réglage mobile propre au fichier, le
     mobile reprend donc la hauteur desktop. */
}

/* ————————————————————————————————————————————————
   Mosaïque de points (voir assets/js/reveal.js).
   Enrichissement progressif : les règles ne s'appliquent que si
   le JS a posé .js-reveal sur <html>. Sans JS, les médias
   s'affichent normalement.
   ———————————————————————————————————————————————— */
/* .media--hero exclu : elle a déjà son position:fixed (cf. plus haut), une
   règle plus tardive de même spécificité l'écraserait sinon en position:relative. */
.media:not(.media--hero), .media-grid__cell { position: relative; }

/* Images : mosaïque peinte directement dans le canvas. */
.media__reveal {
  display: none;                 /* affiché uniquement quand le JS tourne */
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  z-index: 2; pointer-events: none;
}

.js-reveal .media__reveal { display: block; }

/* Vidéos : <video.media__mosaic> lit le même fichier que le média natif,
   habillée par un mask-image (silhouette de la mosaïque) posé en JS — la
   vidéo reste décodée/jouée nativement, pas de copie par frame. Le
   sélecteur à deux classes l'emporte sur ".media--hero video"
   (taille/object-fit) pour la repositionner en overlay absolu. */
.media--hero .media__mosaic {
  display: none;                 /* affiché uniquement quand le JS tourne */
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  z-index: 2; pointer-events: none;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%; mask-size: 100% 100%;
}

.js-reveal .media__mosaic { display: block; }

/* le média natif reste visible et se fond avec la mosaïque au-dessus
   (mix-blend-mode) au lieu d'être masqué : il continue aussi de fournir
   couleur/luminance à la mosaïque. */
.js-reveal [data-reveal] .media__native { mix-blend-mode: normal; }
