/* ------------------------------------------------------------------
   Étape 1 — structure uniquement. Pas d'esthétique définitive ici :
   couleurs neutres, bordures visibles pour matérialiser les blocs.
   ------------------------------------------------------------------ */

@import url("fonts.css");

* {
  box-sizing: border-box;
}

html {
  color-scheme: light;
}

:root {
  --ease-enter: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-standard: cubic-bezier(0.22, 1, 0.36, 1);
}

body {
  margin: 0;
  overflow-x: hidden;
  font-family: "Noir Pro", system-ui, sans-serif;
  font-weight: 300;
  color: #1a1a1a;
  background: #fff;
  line-height: 1.5;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-weight: 900;
}

/* Le style par défaut des navigateurs pour <strong> est `bolder`, une
   graisse *relative* : à partir du 300 du texte courant, elle ne monte
   qu'à 400 et le gras ne se voit presque pas. On désigne donc la graisse
   voulue, celle du fichier NoirPro-Bold. */
b,
strong {
  font-weight: 700;
}

.container {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 24px;
}

a {
  color: inherit;
}

/* Header */

.site-header {
  border-bottom: 1px solid #ccc;
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 16px;
  padding-bottom: 16px;
}

.logo {
  font-weight: bold;
  text-decoration: none;
}

.main-nav ul {
  display: flex;
  gap: 24px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.main-nav a {
  text-decoration: none;
}

.main-nav a[aria-current="page"] {
  text-decoration: underline;
}

/* Main content */

main {
  padding: 48px 0;
}

section {
  margin-bottom: 48px;
}

/* Blocs image placeholder */

.placeholder-image {
  aspect-ratio: 4 / 3;
  border: 1px dashed #999;
  background: #f2f2f2;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #666;
  font-size: 0.9rem;
  text-align: center;
}

/* Grille de projets */

.project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 24px;
}

.project-card {
  text-decoration: none;
  color: inherit;
  border: 1px solid #ddd;
  padding: 16px;
  display: block;
}

.project-card h2,
.project-card h3 {
  margin-bottom: 0;
}

/* Contenu de projet (logos, intro, galeries d'images) */

.project-media {
  display: grid;
  gap: 24px;
}

.project-media.cols-1 {
  grid-template-columns: 1fr;
}

/* Nombre de colonnes fixe : avec `auto-fit`, les colonnes se
   réduisaient à une seule dès que la largeur minimale ne tenait plus,
   ce qui empilait les visuels sur mobile. Sur desktop le rendu est
   identique, `auto-fit` y produisant déjà ce même nombre de colonnes. */

.project-media.cols-2 {
  grid-template-columns: repeat(2, 1fr);
}

.project-media.cols-4 {
  grid-template-columns: repeat(4, 1fr);
}

.project-media img {
  width: 100%;
  height: auto;
  display: block;
}

/* Hero d'ouverture : logos + texte seuls au premier écran, puis la carte
   (affiches) s'incline/se met à l'échelle en se redressant au scroll. */

.scroll-hero-title {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 20px;
  padding: 24px 0;
  will-change: transform;
}

.scroll-hero-logos {
  height: clamp(56px, 8vw, 110px);
  width: auto;
}

.scroll-hero-title h1 {
  max-width: 900px;
  margin: 0;
  font-size: clamp(1.4rem, 1rem + 1.8vw, 2.1rem);
  line-height: 1.2;
}

.scroll-hero-title p {
  max-width: 640px;
  margin: 0;
}

.scroll-hero-card-outer {
  perspective: 1200px;
  padding: 8vh 0 22vh;
}

.scroll-hero-card {
  transform-origin: center top;
  will-change: transform;
}

/* Section téléphones : reste fixée à l'écran tant que le scroll interne
   des images n'est pas terminé (hauteur ajoutée dynamiquement en JS). */

.phones-section {
  position: relative;
}

.phones-sticky {
  position: sticky;
  top: 0;
  min-height: 100vh;
  display: flex;
  align-items: center;
}

.phones-sticky .project-media {
  width: 100%;
}

/* Mockup téléphone avec écran qui défile au scroll de la page */

.phone-mockup {
  aspect-ratio: 9 / 19.5;
}

.phone-frame {
  position: relative;
  width: 100%;
  height: 100%;
  background: #111;
  border-radius: 13% / 6%;
  box-shadow: 0 16px 32px rgba(0, 0, 0, 0.18);
  padding: 3.5%;
  box-sizing: border-box;
}

.phone-notch {
  position: absolute;
  top: 3.5%;
  left: 50%;
  transform: translateX(-50%);
  width: 34%;
  height: 3%;
  background: #111;
  border-radius: 999px;
  z-index: 2;
}

.phone-screen {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border-radius: 9% / 4%;
  background: #000;
}

.phone-screen-content {
  display: block;
  width: 100%;
  height: auto;
  will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
  .phone-screen-content {
    transform: none !important;
  }
}

/* Carrousel automatique plein large (stories/vidéos qui défilent en boucle) */

.story-carousel {
  width: 100vw;
  position: relative;
  left: 50%;
  right: 50%;
  margin-left: -50vw;
  margin-right: -50vw;
  height: clamp(360px, 40vw, 620px);
  overflow: hidden;
}

.story-carousel-track {
  display: flex;
  height: 100%;
  width: max-content;
  padding: 60px 0;
  animation: story-marquee 30s linear infinite;
}

/* Chaque visuel garde son cadrage : les hauteurs sont communes, les
   largeurs suivent le rapport du fichier. Un cadre imposé à 9:16 rognerait
   un tiers de la largeur des invitations, qui sont en 4:5. */
.story-carousel-track .story-slide {
  flex: 0 0 auto;
  height: 100%;
  margin-right: 36px;
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 16px 32px rgba(0, 0, 0, 0.22);
}

.story-carousel-track img,
.story-carousel-track video {
  width: auto;
  height: 100%;
  display: block;
}

/* Variante pilotable du carrousel : la position est tenue par
   story-carousel.js, l'animation CSS doit donc lui céder la place. La
   classe est-pilotable n'est posée qu'une fois le script en route, pour
   que le défilement CSS reste en secours s'il ne s'exécute pas. */

.story-carousel.est-pilotable .story-carousel-track {
  animation: none;
  will-change: transform;
}

.story-carousel.est-pilotable {
  cursor: grab;
  /* Le glissement horizontal nous revient, le défilement vertical de la
     page reste au navigateur. */
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}

.story-carousel.est-pilotable.est-saisi {
  cursor: grabbing;
}

.story-carousel.est-pilotable:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: -4px;
}

.story-carousel.est-pilotable img {
  /* Sans ça, un glissement démarré sur une image devient un
     glisser-déposer de l'image. */
  -webkit-user-drag: none;
  pointer-events: none;
}

@keyframes story-marquee {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .story-carousel-track {
    animation: none;
  }
}

/* CV */

.cv-page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 64px;
  padding: 40px 24px;
}

.cv-plein-ecran {
  display: block;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
}

/* 560px, soit la largeur maximale précédente moins 20 %. Le terme en vh
   garantit en plus de l'air en haut et en bas sur un écran peu haut, bouton
   de téléchargement compris. */

.cv-image {
  width: min(80vw, 48vh, 560px);
  height: auto;
  display: block;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.28);
}

/* Sur grand écran, on décale la composition vers le bas pour dégager le
   bouton de menu, fixé en haut de la fenêtre (il descend jusqu'à 88px).
   Le mobile n'y touche pas : la page y tient déjà comme il faut. */
@media (min-width: 721px) {
  .cv-page {
    --cv-haut: clamp(104px, 15vh, 168px);
    padding-top: var(--cv-haut);
    padding-bottom: 64px;
  }

  /* La largeur se déduit de la hauteur qui reste une fois le décalage
     pris : 0,7071 est le rapport de l'image du CV (1654/2339), et 187px
     la somme du bas de page (64), de l'écart (64) et du bouton (59).
     Sans ce calcul, un terme fixe en vh déborderait sur une fenêtre
     basse ou rapetisserait l'image pour rien sur un grand écran. */
  .cv-image {
    width: min(80vw, calc((100vh - var(--cv-haut) - 187px) * 0.7071), 560px);
  }
}

/* Formulaire de contact. Le bleu est celui relevé sur l'image du CV, en
   remplacement de l'indigo du composant d'origine. */

.contact-page {
  --contact-bleu: #0936d1;
  --contact-bleu-fonce: #072aa6;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* Les trois écarts sont nommés : la largeur de la carte se calcule à
     partir d'eux, et une valeur changée ici se répercute toute seule. */
  --contact-marge: clamp(64px, 9vh, 112px);
  --contact-ecart-titre: clamp(44px, 8vh, 96px);
  --contact-ecart-icones: clamp(48px, 9vh, 104px);
  padding: var(--contact-marge) 24px;
}

.contact-titre {
  margin: 0;
  font-size: clamp(2.5rem, 5.5vw, 4.5rem);
  line-height: 1.1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  text-align: center;
}

/* La carte de visite au centre, posée de travers, et qui suit le curseur.

   Deux enveloppes sont nécessaires : tilt-card.js réécrit entièrement la
   transformation de l'élément qu'il pilote, il effacerait une rotation
   posée sur le même. Le socle porte donc l'inclinaison fixe, la carte
   intérieure le suivi du curseur. */
.contact-carte-socle {
  margin-top: var(--contact-ecart-titre);
  transform: rotate(-3deg);
}

.contact-carte-tilt {
  /* Trois bornes : la place disponible, un plafond, et la hauteur restante
     convertie au rapport de l'image (2000/1295 = 1,544), divisé par ce que
     l'inclinaison ajoute en hauteur — d'où 1,43 plutôt que 1,544.

     Ce qui reste, c'est l'écran moins les deux marges, les deux écarts, et
     215px pour le titre, les icônes et la bulle. Les marges et les écarts
     sont repris de leurs variables plutôt que réestimés : ils sont bornés
     en pixels sur grand écran, une approximation en vh les sous-estimait
     et la page finissait par défiler. */
  width: min(
    88vw,
    880px,
    calc(
      (
          100vh - 2 * var(--contact-marge) - var(--contact-ecart-titre) -
            var(--contact-ecart-icones) - 180px
        ) * 1.43
    )
  );
  box-shadow: 0 30px 60px rgba(0, 0, 0, 0.26);
}

.contact-carte {
  display: block;
  width: 100%;
  height: auto;
}

/* Les quatre coordonnées, en pastilles. Les deux premières sont des
   boutons — elles recopient — les deux autres des liens. */

.contact-liens {
  list-style: none;
  margin: var(--contact-ecart-icones) 0 0;
  padding: 0;
  display: flex;
  gap: clamp(14px, 2vw, 22px);
}

.contact-lien {
  display: grid;
  place-items: center;
  width: clamp(48px, 5vw, 58px);
  aspect-ratio: 1;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--contact-bleu);
  color: #fff;
  cursor: pointer;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: background 200ms ease, transform 200ms ease;
}

.contact-lien:hover,
.contact-lien:focus-visible {
  background: var(--contact-bleu-fonce);
  transform: translateY(-2px);
}

.contact-lien svg {
  width: 46%;
  height: 46%;
}

/* Bulle blanche sous les icônes. Sa place est réservée en permanence, et
   sa hauteur est fixe : sans ça, son apparition décalerait tout le bloc. */
.contact-message {
  margin: clamp(18px, 2.4vh, 26px) 0 0;
  display: grid;
  place-items: center;
  min-height: 42px;
  padding: 0 22px;
  border-radius: 999px;
  background: #fff;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.16);
  font-size: 0.95rem;
  color: var(--contact-bleu);
  text-align: center;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 240ms ease, transform 240ms ease;
}

.contact-message.est-visible {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .contact-carte-socle {
    transform: none;
  }

  .contact-lien,
  .contact-message {
    transition-duration: 1ms;
  }
}

.contact-form {
  display: flex;
  flex-direction: column;
  align-items: center;
  font-size: 0.875rem;
  color: #1e293b;
}

.contact-pastille {
  margin: 0;
  padding: 4px 12px;
  border-radius: 999px;
  background: #dbe2fb;
  color: var(--contact-bleu);
  font-size: 0.75rem;
  font-weight: 500;
}



.contact-accroche {
  margin: 0 0 40px;
  color: #6b7280;
  text-align: center;
}

.contact-accroche a {
  color: var(--contact-bleu);
  text-decoration: none;
}

.contact-accroche a:hover {
  text-decoration: underline;
}

.contact-champs {
  width: 100%;
  max-width: 384px;
  padding: 0 16px;
}

.contact-label {
  display: block;
  font-weight: 500;
}

/* Champ en gélule : l'icône et la saisie partagent la bordure, et l'anneau
   de focus est porté par la boîte, pas par l'input. */
.contact-boite {
  display: flex;
  align-items: center;
  height: 40px;
  margin: 8px 0 16px;
  padding-left: 12px;
  border: 1px solid #cbd5e1;
  border-radius: 999px;
  overflow: hidden;
  transition: box-shadow 200ms ease, border-color 200ms ease;
}

.contact-boite:focus-within {
  border-color: var(--contact-bleu);
  box-shadow: 0 0 0 2px rgba(9, 54, 209, 0.35);
}

.contact-boite svg {
  flex: 0 0 auto;
  color: #475569;
}

.contact-boite input {
  height: 100%;
  width: 100%;
  padding: 0 8px;
  border: 0;
  outline: none;
  background: transparent;
  font: inherit;
  color: inherit;
}

.contact-champs textarea {
  width: 100%;
  margin-top: 8px;
  padding: 8px;
  border: 1px solid #cbd5e1;
  border-radius: 8px;
  background: transparent;
  resize: none;
  outline: none;
  font: inherit;
  color: inherit;
  transition: box-shadow 200ms ease, border-color 200ms ease;
}

.contact-champs textarea:focus {
  border-color: var(--contact-bleu);
  box-shadow: 0 0 0 2px rgba(9, 54, 209, 0.35);
}

.contact-envoyer {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: 100%;
  margin-top: 20px;
  padding: 10px 0;
  border: 0;
  border-radius: 999px;
  background: var(--contact-bleu);
  color: #fff;
  font: inherit;
  cursor: pointer;
  transition: background-color 200ms ease;
}

.contact-envoyer:hover {
  background: var(--contact-bleu-fonce);
}

.contact-envoyer svg {
  margin-top: 2px;
}

.contact-retour {
  min-height: 1.2em;
  margin: 12px 0 0;
  color: #6b7280;
  text-align: center;
}

.contact-retour.is-erreur {
  color: #b42318;
}

.btn {
  display: inline-block;
  border: 1px solid #1a1a1a;
  padding: 10px 20px;
  text-decoration: none;
  color: inherit;
  width: fit-content;
}

/* Footer */

.site-footer {
  border-top: 1px solid #ccc;
}

.footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-top: 16px;
  padding-bottom: 16px;
}

.footer-links {
  display: flex;
  gap: 16px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.footer-links a {
  text-decoration: none;
}

/* Projet Bling — hero plein large sombre (fond texturé à venir) */

.page-bling main {
  padding-top: 0;
}

.bling-hero {
  width: 100vw;
  position: relative;
  left: 50%;
  right: 50%;
  margin-left: -50vw;
  margin-right: -50vw;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #fff;
  color: #111;
  padding: 48px 24px;
  margin-bottom: 0;
}

.bling-hero-inner {
  max-width: 1100px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  text-align: center;
}

/* Texte d'introduction sous le logo, sur le modèle du chapeau de la page
   Sony : même largeur de ligne, même écart. Il remplace l'image de carte,
   dont le texte n'était lisible que par son attribut alt. */
.bling-intro {
  /* La largeur est en em, donc indexée sur la taille du texte : le nombre
     de signes par ligne — et donc les coupures — reste le même à toutes
     les tailles d'écran, et le pavé retrouve les cinq lignes de la
     maquette. La fenêtre où les cinq coupures tombent exactement comme
     sur la maquette est étroite — de 29,6 à 29,9 em — d'où cette valeur
     au milieu. Un moteur aux métriques légèrement différentes peut
     déplacer un mot d'une ligne ; la composition, elle, tient. */
  max-width: 29.75em;
  margin: 0;
  font-size: clamp(1.05rem, 0.72rem + 1.05vw, 1.6rem);
  line-height: 1.35;
}

.bling-logo {
  width: 100%;
  max-width: 640px;
  height: auto;
  display: block;
  margin: 0 auto;
}

/* Deux rangées de spreads, inclinées, qui défilent horizontalement
   au scroll de la page (même principe pin + scrub que les téléphones
   de la page Sony, mais sur l'axe X et de gauche à droite). */

.bling-mag-section {
  width: 100vw;
  position: relative;
  left: 50%;
  right: 50%;
  margin-left: -50vw;
  margin-right: -50vw;
  background: #fff;
  margin-bottom: 0;
}

/* Les rangées sont pivotées de 10° : leur emprise à l'écran est plus haute
   que leur boîte de mise en page (la rotation ne change pas le layout), et
   elles débordaient donc d'environ 150 px sous le bloc. La section blanche
   suivante, peinte après, recouvrait ce débordement — d'où le bas du
   dernier spread coupé net à l'horizontale. Ce padding rend la boîte assez
   haute pour contenir les rangées pivotées. */
.bling-mag-sticky {
  position: sticky;
  top: 0;
  min-height: 100vh;
  padding-bottom: 12vw;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.bling-mag-row {
  overflow: hidden;
  width: 120%;
  margin-left: -10%;
  padding: 24px 0;
  transform: rotate(10deg);
}

/* Les spreads gardent une marge transparente (ombre) : 18,7 % de leur
   hauteur au-dessus, 9,4 % en dessous, 16,5 % de leur largeur de chaque
   côté. Les rangées et les cartes se chevauchent d'une part de cette
   marge, en proportion de la carte : à 820 px, 150 px entre les rangées
   et 160 px entre les cartes. En pixels fixes, sur un téléphone où la
   carte est quatre fois plus petite, les images se recouvraient. */
.bling-mag-row + .bling-mag-row {
  margin-top: calc(min(95vw, 820px) * -0.183);
}

@media (max-width: 720px) {
  .bling-mag-row + .bling-mag-row {
    margin-top: calc(min(95vw, 820px) * -0.225);
  }
}

.bling-mag-track {
  display: flex;
  width: max-content;
  padding: 0 24px;
}

.bling-mag-card {
  flex: 0 0 auto;
  width: min(95vw, 820px);
  border-radius: 4px;
}

.bling-mag-card + .bling-mag-card {
  margin-left: calc(min(95vw, 820px) * -0.195);
}

.bling-mag-card img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
  .bling-mag-track {
    transform: none !important;
  }
}

/* Cover du magazine + bouton vers le PDF, en bas de page */

.bling-cover-section {
  /* Le terme en vh est calé pour que couverture et bouton tiennent
     ensemble à l'écran : au-delà, le bouton passe sous la ligne de flottaison. */
  --cover-w: min(86vw, 86vh, 800px);
  width: 100vw;
  position: relative;
  left: 50%;
  right: 50%;
  margin-left: -50vw;
  margin-right: -50vw;
  background: #fff;
  margin-bottom: 0;
  padding: clamp(28px, 4vh, 56px) 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
}

/* Le mockup intègre déjà son ombre : pas de box-shadow ajoutée. Il porte
   aussi de larges marges transparentes — mesurées sur le fichier : 23,3 %
   de vide au-dessus et 14,4 % en dessous, ombre comprise. Ramenées en
   fraction de la largeur (l'image fait 1,51 fois sa largeur en hauteur),
   ça fait 0,35 et 0,21 : ce sont ces marges mortes qui éloignaient le
   bouton de la couverture, on les reprend en marges négatives. */
.bling-cover-image {
  width: var(--cover-w);
  height: auto;
  display: block;
  margin-top: calc(var(--cover-w) * -0.35);
  margin-bottom: calc(var(--cover-w) * -0.21);
}

.btn-round {
  display: inline-block;
  background: #2b2b2b;
  color: #fff;
  font-size: 1.125rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 16px 40px;
  border-radius: 999px;
}

/* Variante bleue, dans le bleu relevé sur l'image du CV (médiane du bloc
   « Portfolio »). Doit suivre .btn-round pour l'emporter à spécificité égale. */
.btn-round-bleu {
  background: #0936d1;
}

/* Projet Bloom — page entière sur le bleu pâle de l'identité, logo seul
   centré au premier écran (même principe que le hero de Bling). */

.page-bloom {
  background: #e0eaf6;
}

.page-bloom main {
  padding-top: 0;
}

.bloom-hero {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px 24px;
  margin-bottom: 0;
}

.bloom-hero-inner {
  max-width: 860px;
  margin: 0 auto;
  text-align: center;
}

.bloom-logo {
  width: min(80vw, 760px);
  height: auto;
  display: block;
  margin: 0 auto;
}

/* Carte d'intro + les deux étiquettes. La section fait un écran de haut et
   défile normalement — rien ne se fige. La carte traverse le centre de
   l'écran, et l'étiquette tissée puis l'étiquette cartonnée apparaissent
   l'une après l'autre, déclenchées un peu avant qu'elle y arrive. */

/* Le stage porte les proportions exactes de la carte : les étiquettes sont
   positionnées en % de cette boîte, donc leur calage reste identique à
   toutes les tailles d'écran. Le terme en vh est indispensable : les deux
   étiquettes débordent de la carte (en haut et en bas), et sans lui elles
   sortent de l'écran sur un format 16/9. */
.bloom-card-stage {
  position: relative;
  width: min(76vw, 1180px, 105vh);
  aspect-ratio: 2564 / 1732;
}

.bloom-carte {
  width: 100%;
  height: 100%;
  display: block;
}

.bloom-label {
  position: absolute;
  height: auto;
  display: block;
}

.bloom-label-1 {
  width: 35%;
  top: -8.9%;
  right: -12.8%;
}

.bloom-label-2 {
  width: 25.6%;
  left: -6.7%;
  bottom: -4.4%;
}

/* Apparition au scroll, partagée par les deux scènes de la page (la carte
   et ses étiquettes, Lazzio et ses deux encarts). État par défaut : tout
   est visible ; le JS n'arme le masquage que si les animations sont
   autorisées, pour que la page reste lisible sans JS ou en "reduced
   motion". Chaque élément décrit ensuite sa position de départ. */
.bloom-stage.is-armed .bloom-reveal {
  opacity: 0;
  transition: opacity 0.7s ease, transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}

.bloom-stage.is-armed .bloom-label-1 {
  transform: translate(14px, -18px) rotate(6deg);
}

.bloom-stage.is-armed .bloom-label-2 {
  transform: translate(-14px, 26px) rotate(-8deg);
  transition-delay: 0.18s;
}

.bloom-stage.is-armed.is-revealed .bloom-reveal {
  opacity: 1;
  transform: none;
}

@media (max-width: 700px) {
  .bloom-card-stage {
    width: min(80vw, 105vh);
  }
}

/* Les trois publications Instagram : rangée centrée, celle du milieu un peu
   plus grande (~14 %) et les trois alignées sur le même axe horizontal.
   L'ombre passe par filter: drop-shadow et non box-shadow, pour qu'elle
   épouse les coins arrondis détourés des visuels plutôt que leur cadre. */

.bloom-posts {
  padding: clamp(64px, 10vh, 140px) 24px;
  margin-bottom: 0;
}

/* Les cinq largeurs (3 visuels + 2 gouttières) sont exprimées en % de la
   rangée et totalisent 100 % : les proportions relevées sur la maquette
   sont donc conservées à l'identique quelle que soit la largeur d'écran. */
.bloom-posts-row {
  width: min(90vw, 1580px);
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4.3%;
}

.bloom-post {
  margin: 0;
  flex: 0 1 29.1%;
}

.bloom-post-featured {
  flex: 0 1 33.1%;
}

.bloom-post img {
  width: 100%;
  height: auto;
  display: block;
  filter: drop-shadow(0 10px 24px rgba(30, 50, 80, 0.16));
}

/* Les trois visuels restent côte à côte sur tous les écrans : la rangée
   s'élargit sur mobile pour leur rendre le peu de place disponible, mais
   elle ne passe jamais en colonne. Les proportions, exprimées en % de la
   rangée, se conservent donc d'elles-mêmes. */
@media (max-width: 700px) {
  .bloom-posts-row {
    width: 96vw;
  }
}

/* Scène Lazzio — même mécanique que la carte : la photo traverse le centre
   de l'écran, le lecteur et l'encart de présentation viennent s'y poser.
   Leurs largeurs et débords sont exprimés en % de la photo, relevés sur la
   maquette, donc le montage garde ses proportions à toutes les tailles. */

/* Section à défilement horizontal, deux panneaux d'un écran chacun.
   Un écran pour l'arrivée verticale de la carte, puis un écran et demi
   d'épinglage pour la traversée — pas un pixel de plus : tout surplus se
   traduirait par un arrêt net de la page une fois Lazzio au centre. */
.bloom-h-section {
  position: relative;
  height: 250vh;
  margin-bottom: 0;
}

.bloom-h-sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  /* Le second panneau attend hors cadre à droite : sans ça, il
     élargirait la page. */
  overflow: hidden;
}

.bloom-h-piste {
  display: flex;
  width: 200vw;
  height: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
  will-change: transform;
}

.bloom-h-panneau {
  flex: 0 0 100vw;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

/* Les encarts sont masqués avant que le script ne prenne la main, sinon
   ils apparaissent en place le temps du premier calcul. */
.bloom-h-section.est-animee .bloom-lazzio-spotify,
.bloom-h-section.est-animee .bloom-lazzio-texte {
  opacity: 0;
}

/* Sans animation : pas de course ni d'épinglage, les deux panneaux se
   suivent verticalement. */
.bloom-h-section.est-statique {
  height: auto;
}

.bloom-h-section.est-statique .bloom-h-sticky {
  position: static;
  height: auto;
  overflow: visible;
}

.bloom-h-section.est-statique .bloom-h-piste {
  display: block;
  width: auto;
}

.bloom-h-section.est-statique .bloom-h-panneau {
  min-height: 100vh;
}

/* C'est la photo qui est centrée à l'écran, pas le montage : l'encart
   descend nettement plus bas sous la photo (21 % de sa hauteur) que le
   lecteur ne monte au-dessus (8 %). Le bas est donc la contrainte, et le
   terme en vh en découle — sans lui l'encart sortait de l'écran. */
.bloom-lazzio-stage {
  position: relative;
  width: min(60vw, 58vh, 900px);
  aspect-ratio: 1990 / 2126;
}

.bloom-lazzio-photo {
  width: 100%;
  height: 100%;
  display: block;
}

.bloom-lazzio-spotify,
.bloom-lazzio-texte {
  position: absolute;
  height: auto;
  display: block;
}

.bloom-lazzio-spotify {
  width: 48%;
  left: -21.6%;
  top: -8.4%;
}

.bloom-lazzio-texte {
  width: 81.5%;
  left: 49.5%;
  bottom: -21.2%;
}

/* Galerie de fin — deux rangées relevées sur la maquette. Rangée du haut :
   quatre portraits à leur cadrage d'origine (2278/3400), donc non rognés.
   Rangée du bas : deux paysages encadrant un portrait, proportions
   2,2 / 1 / 2,3, avec une hauteur de rangée un peu plus courte que le
   cadrage naturel — les trois images y sont donc légèrement recadrées,
   comme sur la maquette. */

.bloom-galerie {
  --galerie-gap: clamp(6px, 0.5vw, 12px);
  width: min(94vw, 1800px);
  margin: 0 auto;
  padding: clamp(64px, 10vh, 140px) 0;
  display: flex;
  flex-direction: column;
  gap: var(--galerie-gap);
  margin-bottom: 0;
}

.bloom-galerie-row {
  display: grid;
  gap: var(--galerie-gap);
}

.bloom-galerie-row-1 {
  grid-template-columns: repeat(4, 1fr);
}

.bloom-galerie-row-1 figure {
  aspect-ratio: 2278 / 3400;
}

/* Rangée basse : le rapport est porté par chaque tuile et non par la
   grille — posé sur la grille il est écrasé par la hauteur intrinsèque des
   images. Les trois rapports partagent le même dénominateur (1,425 fois la
   largeur de la colonne du milieu), donc les trois tuiles retombent
   exactement sur la même hauteur, plus courte que le cadrage naturel des
   photos : c'est ce qui produit le recadrage de la maquette. */
.bloom-galerie-row-2 {
  grid-template-columns: 2.2fr 1fr 2.3fr;
}

.bloom-galerie-row-2 figure:nth-child(1) {
  aspect-ratio: 2.2 / 1.425;
}

.bloom-galerie-row-2 figure:nth-child(2) {
  aspect-ratio: 1 / 1.425;
}

.bloom-galerie-row-2 figure:nth-child(3) {
  aspect-ratio: 2.3 / 1.425;
}

.bloom-galerie figure {
  margin: 0;
  overflow: hidden;
}

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

/* Chaque vignette est un bouton qui remplit sa case. */
.galerie-zoom {
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
}

/* Sous 700px, sept images sur deux rangées deviennent des vignettes :
   on passe à deux colonnes, les paysages occupant toute la largeur. */
@media (max-width: 700px) {
  .bloom-galerie-row-1 {
    grid-template-columns: repeat(2, 1fr);
  }

  .bloom-galerie-row-2 {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto;
    aspect-ratio: auto;
  }

  .bloom-galerie-row-2 figure:nth-child(1),
  .bloom-galerie-row-2 figure:nth-child(3) {
    grid-column: span 2;
    aspect-ratio: 3400 / 2278;
  }

  .bloom-galerie-row-2 figure:nth-child(2) {
    aspect-ratio: 2278 / 3400;
  }
}

/* Projet Ellipse — fond texturé sombre sur toute la page, logo seul centré
   au premier écran (même traitement que le hero de Bloom). */

.page-ellipse {
  color-scheme: dark;
  background-color: #0c0c0c;
  color: #f0efec;
}

/* Le fond est porté par un calque fixe, dont fond-ellipse.js descend
   l'image au rythme du défilement : à l'œil, il défile comme un fond
   ordinaire, mais il s'arrête net pendant que la bande de photos est
   épinglée — là où la page continue de défiler sans que rien ne monte.
 
   Un calque plutôt que background-attachment, que Safari mobile rend mal.
   z-index négatif : il passe au-dessus du fond de la page, sous tout son
   contenu. Sans le script, --fond-y vaut 0 et le fond reste simplement
   fixe, ce qui reste présentable. */
.page-ellipse::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background-image: url("/assets/images/ellipse/fond.webp");
  /* Mêmes réglages qu'à l'origine, quand le fond était posé sur la page :
     pleine largeur, répété vers le bas. Le grain est donc inchangé. */
  background-size: 100% auto;
  background-repeat: repeat-y;
  background-position: center var(--fond-y, 0px);
  pointer-events: none;
}

/* Note : on a essayé de déplacer ce calque par une transformation plutôt
   que par background-position, pour éviter une repeinte par image. Le
   déplacement devait alors être ramené à l'intérieur d'une tuile, et
   comme la texture ne se raccorde pas exactement à elle-même, chaque
   retour de tuile faisait sauter le fond. La repeinte, elle, n'a jamais
   été mesurable. On en est donc resté à background-position. */

.page-ellipse main {
  padding-top: 0;
}

.ellipse-hero {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px 24px;
  margin-bottom: 0;
}

.ellipse-hero-inner {
  max-width: 1000px;
  margin: 0 auto;
  text-align: center;
}

.ellipse-logo {
  width: min(84vw, 900px);
  height: auto;
  display: block;
  margin: 0 auto;
}

/* Carte d'intro Ellipse, seule dans la section qui suit le titre. Elle est
   centrée verticalement puis décalée vers la droite. Le décalage est en %
   de sa propre largeur (transform), il suit donc sa taille. */

.ellipse-carte-section {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px 24px;
  margin-bottom: 0;
}

.ellipse-carte {
  width: min(72vw, 118vh, 1150px);
  height: auto;
  display: block;
  transform: translateX(8%);
}

/* Affiche — reprend le composant d'inclinaison au scroll de la page Sony
   (.scroll-hero-card-outer / .scroll-hero-card, pilotés par scroll-hero.js),
   ici sans titre associé. */

.ellipse-affiche-section {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 0;
}

/* Carte qui s'incline vers le curseur (pilotée par tilt-card.js). La
   perspective est dans le transform posé en JS, pas ici : c'est ce que fait
   le composant d'origine. */
.tilt-card {
  transform-style: preserve-3d;
  transform: perspective(1000px) rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1);
  transition: transform 0.4s ease-in-out;
  will-change: transform;
}

/* Sans souris, ou en "reduced motion", la carte reste à plat — le script ne
   s'active pas non plus dans ces deux cas. */
@media (hover: none), (prefers-reduced-motion: reduce) {
  .tilt-card {
    transform: none;
    transition: none;
  }
}

/* Les deux affiches côte à côte, comme les affiches métro de Sony : même
   composant (.scroll-hero-card-outer / .scroll-hero-card), mêmes réglages
   d'inclinaison au scroll. Elles ont exactement le même rapport (0,757),
   donc à colonnes égales elles font la même taille. Le terme en vh garde
   l'ensemble à l'écran sur un format bas. */

.ellipse-affiches {
  /* 80vw et non 92 : la carte est inclinée en 3D par scroll-hero.js, et
     l'arête la plus proche s'élargit de ~25 % — à 92vw les coins bas
     sortaient de l'écran et se faisaient rogner sur toute fenêtre de
     moins de 1200px. Au-delà, c'est le plafond de 1100px qui gouverne :
     rien ne change sur grand écran. */
  width: min(80vw, 124vh, 1100px);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
}

.ellipse-affiche {
  width: 100%;
  height: auto;
  display: block;
}

/* Page Shotgun de l'événement, seule et centrée. */

.ellipse-shotgun-section {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px 24px;
  margin-bottom: 0;
}

.ellipse-shotgun {
  width: min(68vw, 100vh, 980px);
  height: auto;
  display: block;
}

/* Les trois slides Instagram : rangée centrée, celle du milieu plus grande.
   Les largeurs suivent le rapport des fichiers d'origine (877 / 1124 / 877),
   ramené à 100 % avec les deux gouttières. */

.ellipse-slides-section {
  padding: clamp(64px, 10vh, 140px) 24px;
  margin-bottom: 0;
}

.ellipse-slides-row {
  width: min(92vw, 1400px);
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4.4%;
}

.ellipse-slides-row figure {
  margin: 0;
  flex: 0 1 27.8%;
}

.ellipse-slides-row figure:nth-child(2) {
  flex: 0 1 35.6%;
}

.ellipse-slides-row img {
  width: 100%;
  height: auto;
  display: block;
}

@media (max-width: 700px) {
  .ellipse-carte {
    transform: translateX(4%);
  }

  /* Affiches et slides restent côte à côte sur tous les écrans. On leur
     rend seulement un peu de largeur et on resserre les gouttières. */
  .ellipse-affiches {
    /* Même raison qu'au-dessus, mais la carte est plus courte ici donc
       l'arête ne gagne que ~13 % : 86vw tient dans l'écran. */
    width: 86vw;
    gap: 10px;
  }

  .ellipse-shotgun {
    width: min(86vw, 100vh, 980px);
  }

  .ellipse-slides-row {
    width: 96vw;
  }

}

/* Accueil — fond texturé bleu sur toute la page, logo seul au centre.
   Le fichier d'origine est vertical : il sert tel quel sur mobile, et une
   version pivotée de 90° prend le relais à partir de 701px. C'est le même
   grain dans les deux cas, mais à la bonne échelle : en étirant le fichier
   vertical sur un écran large, "cover" n'en garderait qu'une bande
   centrale agrandie. */

.page-home {
  color-scheme: dark;
  background-color: #1a2ea6;
  color: #f2f2f2;
}

/* Le fond est porté par un calque fixe, pas par le corps de la page.

   Posé sur le corps, il est propagé au canevas, et sa zone de placement
   reste celle du corps : dès que celle-ci ne couvre pas la fenêtre, le
   reste tombe sur l'aplat bleu et la texture s'arrête net. Un calque en
   position fixe, lui, est la fenêtre par définition — il ne peut pas la
   sous-remplir. */
.page-home::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background-image: url("/assets/images/accueil/fond-vertical.webp");
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  pointer-events: none;
}

@media (min-width: 701px) {
  .page-home::before {
    background-image: url("/assets/images/accueil/fond-horizontal.webp");
  }
}

.page-home main {
  padding: 0;
}

.home-hero {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px 24px;
  margin-bottom: 0;
}

.home-logo {
  width: min(78vw, 820px);
  height: auto;
  display: block;
}

/* ------------------------------------------------------------------
   Menu plein écran (bouton + panneau), injecté par menu.js
   ------------------------------------------------------------------ */

.menu-bouton {
  position: fixed;
  top: clamp(16px, 2.5vw, 32px);
  right: clamp(16px, 2.5vw, 32px);
  z-index: 40;
  display: block;
  width: clamp(40px, 4vw, 56px);
  height: clamp(40px, 4vw, 56px);
  padding: 0;
  border: 0;
  background: none;
  /* Sombre par défaut (pages à fond clair), clair sur les pages sombres,
     et toujours clair une fois ouvert puisqu'il passe sur le voile. */
  color: #1a1a1a;
  cursor: pointer;
}

.page-home .menu-bouton,
.page-ellipse .menu-bouton,
#site-menu.is-open .menu-bouton {
  color: #fff;
}

.menu-icone {
  width: 100%;
  height: 100%;
  transition: transform 500ms ease-in-out;
}

/* Le premier tracé est un long chemin sinueux : en n'en affichant qu'un
   court segment (dasharray 12), on ne voit que la barre du haut. À
   l'ouverture le segment s'allonge et glisse le long du chemin, ce qui
   déplie la barre en crochet — et l'icône entière pivote de -45°. */
.menu-icone-trait {
  stroke-dasharray: 12 63;
  transition: stroke-dasharray 500ms ease-in-out, stroke-dashoffset 500ms ease-in-out;
}

#site-menu.is-open .menu-icone {
  transform: rotate(-45deg);
}

#site-menu.is-open .menu-icone-trait {
  stroke-dasharray: 20 300;
  stroke-dashoffset: -32.42px;
}

.menu-panneau {
  position: fixed;
  inset: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 96px clamp(24px, 6vw, 80px) 48px;
  overflow-y: auto;
  background: rgba(8, 16, 60, 0.82);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  opacity: 0;
  visibility: hidden;
  /* Le délai sur visibility la maintient jusqu'à la fin du fondu de sortie,
     mais la bascule est immédiate à l'ouverture — sans quoi les liens ne
     sont pas focusables au moment où on leur donne le focus. */
  transition: opacity 400ms ease, visibility 0s linear 400ms;
}

#site-menu.is-open .menu-panneau {
  opacity: 1;
  visibility: visible;
  transition: opacity 400ms ease, visibility 0s;
}

.menu-liens {
  width: 100%;
  max-width: 1024px;
  margin: 0 auto;
}

.menu-lien {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 16px 0;
  border-bottom: 2px solid rgba(255, 255, 255, 0.22);
  color: inherit;
  text-decoration: none;
  /* Les liens montent et apparaissent l'un après l'autre à l'ouverture. */
  opacity: 0;
  transform: translateY(24px);
  transition: border-color 500ms ease, opacity 400ms ease, transform 400ms ease;
}

.menu-lien:nth-child(2) { transition-delay: 60ms; }
.menu-lien:nth-child(3) { transition-delay: 120ms; }
.menu-lien:nth-child(4) { transition-delay: 180ms; }

#site-menu.is-open .menu-lien {
  opacity: 1;
  transform: none;
}

.menu-lien:hover,
.menu-lien:focus-visible {
  border-bottom-color: #fff;
}

.menu-titre {
  display: block;
  font-size: clamp(2.25rem, 6vw, 3.75rem);
  font-weight: 900;
  line-height: 1.05;
  color: rgba(255, 255, 255, 0.55);
  transition: color 500ms ease, transform 400ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Le titre recule pendant que chaque lettre avance : c'est l'écart entre
   les deux qui produit l'écartement, et la cascade vient du délai par
   lettre posé en JS. Pas de délai au retour, pour que ce soit immédiat. */
.menu-lettre {
  display: inline-block;
  transition: transform 400ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.menu-lien:hover .menu-titre,
.menu-lien:focus-visible .menu-titre {
  color: #fff;
  transform: translateX(-16px);
}

.menu-lien:hover .menu-lettre,
.menu-lien:focus-visible .menu-lettre {
  transform: translateX(16px);
  transition-delay: var(--delai);
}

.menu-fleche {
  flex: 0 0 auto;
  overflow: hidden;
  padding: 16px;
  color: #fff;
}

.menu-fleche svg {
  display: block;
  width: clamp(32px, 3.5vw, 48px);
  height: clamp(32px, 3.5vw, 48px);
  opacity: 0;
  transform: translateX(100%);
  transition: transform 400ms cubic-bezier(0.34, 1.56, 0.64, 1), opacity 400ms ease;
}

.menu-lien:hover .menu-fleche svg,
.menu-lien:focus-visible .menu-fleche svg {
  opacity: 1;
  transform: none;
}

@media (min-width: 768px) {
  .menu-lien {
    padding: 32px 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .menu-icone,
  .menu-panneau,
  .menu-lien,
  .menu-titre,
  .menu-lettre,
  .menu-fleche svg {
    transition-duration: 1ms;
  }

  .menu-lien:hover .menu-titre,
  .menu-lien:hover .menu-lettre {
    transform: none;
  }
}

/* ------------------------------------------------------------------
   Visionneuse plein écran avec zoom (lightbox.js)
   ------------------------------------------------------------------ */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #111;
  opacity: 0;
  visibility: hidden;
  /* Bascule immédiate à l'ouverture pour que le bouton de fermeture soit
     focusable tout de suite, différée à la fermeture pour laisser le fondu. */
  transition: opacity 300ms ease, visibility 0s linear 300ms;
}

.lightbox.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 300ms ease, visibility 0s;
}

.lightbox-scene {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  /* Sans ça le navigateur intercepte le pincement et le glissement. */
  touch-action: none;
}

.lightbox img {
  max-width: 96vw;
  max-height: 96vh;
  width: auto;
  height: auto;
  display: block;
  cursor: zoom-in;
  transform-origin: center center;
  will-change: transform;
  transition: transform 140ms ease-out;
}

/* Pendant le glissement, l'image doit coller au doigt : pas de transition. */
.lightbox.is-dragging img {
  transition: none;
}

.lightbox.is-zoomed img {
  cursor: grab;
}

.lightbox.is-zoomed.is-dragging img {
  cursor: grabbing;
}

.lightbox-fermer {
  position: absolute;
  top: clamp(16px, 2.5vw, 32px);
  right: clamp(16px, 2.5vw, 32px);
  z-index: 2;
  width: clamp(36px, 3.5vw, 48px);
  height: clamp(36px, 3.5vw, 48px);
  padding: 0;
  border: 0;
  background: none;
  color: #fff;
  cursor: pointer;
}

.lightbox-fermer svg {
  width: 100%;
  height: 100%;
}

@media (prefers-reduced-motion: reduce) {
  .lightbox,
  .lightbox img {
    transition-duration: 1ms;
  }
}

/* ------------------------------------------------------------------
   Page Projets — un carrousel unique, en boucle, au centre de l'écran.

   Les cartes forment un éventail en 3D, piloté par carrousel-projets.js :
   celle du milieu est droite et agrandie, ses voisines s'inclinent,
   reculent et rétrécissent. Les poses, l'amortissement et l'arrondi des
   coins en mouvement sont repris du composant « fan carousel » ; le flou
   progressif des bords, du composant « focus carousel ».

   Les classes sont préfixées cp- et non pc- : pc- est celui des carrousels
   de la page Naiart (post-carrousel.js), et les deux se seraient mêlés.

   Les couvertures sont en 16:9 et remplissent toute leur largeur : elles
   ne sont jamais recadrées, sur aucun écran.
   ------------------------------------------------------------------ */

.projets-page {
  padding: 0;
}

/* Masqué à l'œil, lu par les lecteurs d'écran. */
.cp-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.cp-scene {
  /* Largeur de la carte du milieu. Le terme en svh la garde entière à
     l'écran sur un format bas, l'autre borne la limite sur grand écran. */
  --cp-w: min(46vw, 78svh, 860px);
  --cp-h: calc(var(--cp-w) * 9 / 16);
  position: relative;
  height: 100vh;
  height: 100svh;
  margin-bottom: 0;
  overflow: hidden;
}

/* L'origine est la ligne médiane de l'écran : la hauteur est nulle, et la
   perspective, posée par le script en proportion de la carte, y a son
   point de fuite. Les cartes sont centrées dessus. */
.cp-piste {
  position: absolute;
  inset: 50% 0 auto;
  height: 0;
  cursor: grab;
  /* Le glissement horizontal nous revient ; la page ne défile pas. */
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}

.cp-piste.est-saisi {
  cursor: grabbing;
}

.cp-carte {
  /* --k va de 0 (au repos) à 1 (en pleine course) ; le script l'écrit à
     chaque image. Au repos, un petit rayon uniforme ; en mouvement, chaque
     côté se courbe en entier — 38 + 62 = 100 % d'un bord à l'autre — et la
     carte prend la forme d'un galet. Valeurs relevées sur le composant. */
  --k: 0;
  --r0: clamp(12px, calc(var(--cp-w) * 0.058), 36px);
  --a: calc((1 - var(--k)) * var(--r0));
  position: absolute;
  top: 0;
  left: 50%;
  display: block;
  width: var(--cp-w);
  height: var(--cp-h);
  margin: calc(var(--cp-h) / -2) 0 0 calc(var(--cp-w) / -2);
  overflow: hidden;
  border-radius:
    calc(var(--a) + var(--k) * 38%) calc(var(--a) + var(--k) * 62%)
    calc(var(--a) + var(--k) * 55%) calc(var(--a) + var(--k) * 45%) /
    calc(var(--a) + var(--k) * 44%) calc(var(--a) + var(--k) * 38%)
    calc(var(--a) + var(--k) * 62%) calc(var(--a) + var(--k) * 56%);
  box-shadow: 0 calc(var(--cp-w) * 0.04) calc(var(--cp-w) * 0.09) rgba(0, 0, 0, 0.26);
  /* Invisibles tant que le script ne les a pas posées. */
  opacity: 0;
  will-change: transform;
  -webkit-tap-highlight-color: transparent;
}

.cp-carte.est-actif {
  cursor: pointer;
}

.cp-carte img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
  -webkit-user-drag: none;
}

.cp-carte:focus-visible {
  outline: 3px solid #0936d1;
  outline-offset: 5px;
}

/* Titre et flèches, sous la carte du milieu. Le bloc est calé sur le
   bas de la carte, pas centré dans la page : c'est la carte qui est au
   centre de l'écran. */
.cp-pied {
  position: absolute;
  right: 0;
  left: 0;
  top: calc(50% + var(--cp-h) / 2 + clamp(28px, 5svh, 56px));
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(16px, 3.4vw, 48px);
  /* Le bloc est large, les flèches seules sont cliquables. */
  pointer-events: none;
}

/* Largeur fixe : les flèches ne bougent pas d'un titre à l'autre. */
.cp-titres {
  display: grid;
  justify-items: center;
  width: clamp(140px, 24vw, 340px);
}

.cp-titre {
  grid-area: 1 / 1;
  margin: 0;
  font-size: clamp(1.25rem, 2.1vw, 1.9rem);
  font-weight: 900;
  line-height: 1.1;
  letter-spacing: 0.04em;
  text-align: center;
  text-transform: uppercase;
  white-space: nowrap;
  /* Opacité, flou et décalage sont écrits par le script. */
  opacity: 0;
  will-change: opacity, filter, transform;
}

.cp-sous-titre {
  display: block;
  margin-top: 0.35em;
  font-size: 0.6em;
  font-weight: 300;
  letter-spacing: 0.02em;
  text-transform: none;
}

.cp-fleche {
  flex: none;
  display: grid;
  place-items: center;
  width: clamp(40px, 3.4vw, 48px);
  aspect-ratio: 1;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #0936d1;
  color: #fff;
  cursor: pointer;
  pointer-events: auto;
  transition: transform 0.25s var(--ease-standard);
  -webkit-tap-highlight-color: transparent;
}

.cp-fleche svg {
  width: 46%;
  height: 46%;
}

.cp-fleche:hover {
  transform: scale(1.08);
}

.cp-fleche:active {
  transform: scale(0.94);
}

.cp-fleche:focus-visible {
  outline: 3px solid #0936d1;
  outline-offset: 3px;
}

/* Flou progressif aux deux bords, sur bureau seulement : il faut une
   souris et de la largeur. Cinq calques de flou doublé à chaque fois, chacun
   visible sur sa tranche seulement ; à la jonction, les flous se cumulent
   et l'ensemble donne un dégradé continu, sans palier. Le bord de l'écran
   est à 100 %, la limite intérieure à 0 %. Les calques ne captent aucun
   clic : on peut saisir une carte à travers eux. */

.cp-flou {
  display: none;
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 5;
  width: clamp(140px, 22vw, 380px);
  pointer-events: none;
}

.cp-flou-g {
  --sens: to left;
  left: 0;
}

.cp-flou-d {
  --sens: to right;
  right: 0;
}

.cp-flou i {
  position: absolute;
  inset: 0;
}

.cp-flou i:nth-child(1) {
  -webkit-backdrop-filter: blur(0.5px);
  backdrop-filter: blur(0.5px);
  -webkit-mask-image: linear-gradient(var(--sens), rgba(0, 0, 0, 0) 0%, #000 20%, #000 40%, rgba(0, 0, 0, 0) 60%);
  mask-image: linear-gradient(var(--sens), rgba(0, 0, 0, 0) 0%, #000 20%, #000 40%, rgba(0, 0, 0, 0) 60%);
}

.cp-flou i:nth-child(2) {
  -webkit-backdrop-filter: blur(1px);
  backdrop-filter: blur(1px);
  -webkit-mask-image: linear-gradient(var(--sens), rgba(0, 0, 0, 0) 20%, #000 40%, #000 60%, rgba(0, 0, 0, 0) 80%);
  mask-image: linear-gradient(var(--sens), rgba(0, 0, 0, 0) 20%, #000 40%, #000 60%, rgba(0, 0, 0, 0) 80%);
}

.cp-flou i:nth-child(3) {
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  -webkit-mask-image: linear-gradient(var(--sens), rgba(0, 0, 0, 0) 40%, #000 60%, #000 80%, rgba(0, 0, 0, 0) 100%);
  mask-image: linear-gradient(var(--sens), rgba(0, 0, 0, 0) 40%, #000 60%, #000 80%, rgba(0, 0, 0, 0) 100%);
}

.cp-flou i:nth-child(4) {
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  -webkit-mask-image: linear-gradient(var(--sens), rgba(0, 0, 0, 0) 60%, #000 80%, #000 100%);
  mask-image: linear-gradient(var(--sens), rgba(0, 0, 0, 0) 60%, #000 80%, #000 100%);
}

.cp-flou i:nth-child(5) {
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  -webkit-mask-image: linear-gradient(var(--sens), rgba(0, 0, 0, 0) 80%, #000 100%);
  mask-image: linear-gradient(var(--sens), rgba(0, 0, 0, 0) 80%, #000 100%);
}

@media (min-width: 900px) and (hover: hover) {
  .cp-flou {
    display: block;
  }
}

/* Sur téléphone la carte du milieu laisse voir ses voisines de chaque
   côté (environ 65 px à 375 px de large). */
@media (max-width: 720px) {
  .cp-scene {
    --cp-w: min(64vw, 50svh);
  }
}

/* ------------------------------------------------------------------
   Projet 808rocket — pour l'instant la seule couverture, posée sur le
   bleu relevé sur l'image pour que le raccord soit invisible.
   ------------------------------------------------------------------ */

.page-808rocket {
  background: #fff;
  color: #1a1a1a;
}

.page-808rocket main {
  padding: 0;
}

.rocket-hero {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px 24px;
  margin-bottom: 0;
}

.rocket-logo {
  width: min(72vw, 620px);
  height: auto;
  display: block;
}

/* La bannière, seule et centrée. Jamais au-delà de sa définition. */

.rocket-banniere-section {
  display: flex;
  justify-content: center;
  padding: 0 24px clamp(64px, 10vh, 140px);
  margin-bottom: 0;
}

.rocket-banniere {
  width: min(92vw, 1200px);
  height: auto;
  display: block;
}

/* Bande sans fin, pleine largeur. La piste porte deux fois la même série
   et glisse d'une demi-longueur : au bout, elle se retrouve exactement
   là où elle a commencé, et le retour ne se voit pas. */

.rocket-bande {
  --rocket-ecart: clamp(16px, 2vw, 32px);
  width: 100vw;
  position: relative;
  left: 50%;
  margin-left: -50vw;
  overflow: hidden;
  margin-bottom: 0;
  padding: clamp(24px, 4vh, 56px) 0;
}

.rocket-piste {
  display: flex;
  width: max-content;
  gap: var(--rocket-ecart);
  /* La gouttière de tête compense celle qui suit la dernière vignette :
     sans elle, le raccord sauterait d'un écart. */
  padding-left: var(--rocket-ecart);
  animation: rocket-defile 48s linear infinite;
}

.rocket-vignette {
  flex: 0 0 auto;
  width: min(62vw, 520px);
}

.rocket-vignette img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 16px;
}

@keyframes rocket-defile {
  from {
    transform: translateX(0);
  }
  to {
    /* Exactement une demi-piste. C'est la gouttière de tête qui rend ce
       compte juste : sans elle, la piste mesurerait une gouttière de
       moins que deux séries, et le retour sauterait d'autant. */
    transform: translateX(-50%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .rocket-piste {
    animation: none;
  }
}

/* Le carrousel en perspective accueille ici des vidéos : mêmes réglages
   que pour ses images. */
.cf-item video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 24px;
  border: 2px solid rgba(26, 26, 26, 0.1);
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  pointer-events: none;
}

/* ------------------------------------------------------------------
   Projet Naiart — comme 808rocket, la seule couverture pour l'instant,
   posée sur le gris relevé sur l'image.
   ------------------------------------------------------------------ */

.page-naiart {
  color-scheme: dark;
  background: #1a1a1a;
  color: #f0efec;
}

.page-naiart main {
  padding: 0;
}

.page-naiart .menu-bouton {
  color: #fff;
}

.naiart-hero {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px 24px;
  margin-bottom: 0;
}

/* Le logo est en SVG : net à n'importe quelle taille, et il pèse 4 Ko. */
.naiart-logo {
  width: min(84vw, 900px);
  height: auto;
  display: block;
}

/* ------------------------------------------------------------------
   Carrousel en perspective — transposition du composant « feature
   carousel ». Le script ne pose que la position de chaque vignette ;
   toute l'animation est dans la transition ci-dessous.
   ------------------------------------------------------------------ */

.cf-section {
  /* Largeur de la vignette centrale. Les flèches s'en servent pour se
     placer, elle doit donc rester une seule et même valeur. */
  --cf-largeur: 250px;
  width: 100vw;
  position: relative;
  left: 50%;
  margin-left: -50vw;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Le bas laisse la place aux flèches, qui passent sous la scène tant
     que l'écran est étroit. */
  padding: 16px 16px 88px;
  margin-bottom: 0;
  overflow-x: hidden;
}

.cf-scene {
  position: relative;
  width: 100%;
  height: 440px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* C'est cette perspective qui donne du relief au rotateY des vignettes. */
  perspective: 1000px;
}

.cf-piste {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  /* L'espace vide autour des vignettes doit aussi répondre à la saisie,
     sinon il y a des trous où le glissement ne prend pas. */
  pointer-events: auto;
  /* Le glissement horizontal nous revient, le défilement vertical de la
     page reste au navigateur. */
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}

.cf-section.est-saisi .cf-piste {
  cursor: grabbing;
}

.cf-item {
  position: absolute;
  width: var(--cf-largeur);
  height: 440px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 500ms ease-in-out, opacity 500ms ease-in-out,
    filter 500ms ease-in-out;
}

/* Pendant la saisie, les vignettes suivent le doigt : une transition les
   ferait traîner d'une demi-seconde derrière lui. */
.cf-section.est-saisi .cf-item {
  transition: none;
}

.cf-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 24px;
  border: 2px solid rgba(240, 239, 236, 0.1);
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  -webkit-user-drag: none;
  pointer-events: none;
}

.cf-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 20;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid rgba(240, 239, 236, 0.2);
  border-radius: 999px;
  background: rgba(26, 26, 26, 0.5);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  color: inherit;
  cursor: pointer;
  transition: background 200ms ease;
}

.cf-nav:hover {
  background: rgba(60, 60, 60, 0.7);
}

.cf-nav svg {
  width: 20px;
  height: 20px;
}

/* Sur petit écran, les vignettes voisines occupent toute la largeur
   disponible : il n'y a plus de place à leur côté. Les flèches passent
   donc sous la scène plutôt que de se poser sur une diapositive. */
.cf-prec,
.cf-suiv {
  top: auto;
  bottom: -60px;
  transform: none;
}

.cf-prec {
  left: calc(50% - 48px);
}

.cf-suiv {
  right: calc(50% - 48px);
}

@media (min-width: 768px) {
  .cf-section {
    --cf-largeur: 340px;
  }

  .cf-scene {
    height: 600px;
  }

  .cf-item {
    height: 600px;
  }

  /* Les flèches se placent au-delà des vignettes voisines, à 16 px de
     leur bord extérieur. Ce bord est à 0,875 largeur du centre : 0,45
     pour le décalage de la voisine, plus la moitié de sa largeur une
     fois réduite à 0,85. Calées plus près, elles se posaient dessus. */
  .cf-prec,
  .cf-suiv {
    top: 50%;
    bottom: auto;
    transform: translateY(-50%);
  }

  .cf-prec {
    left: calc(50% - var(--cf-largeur) * 0.875 - 56px);
  }

  .cf-suiv {
    right: calc(50% - var(--cf-largeur) * 0.875 - 56px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .cf-item {
    transition-duration: 1ms;
  }
}

/* ------------------------------------------------------------------
   Galerie Ellipse — transposition du composant « portfolio scroll grid ».

   Le logo reste épinglé au centre de l'écran pendant que cinq colonnes de
   photos, décalées en quinconce, défilent par-dessus : il est tour à tour
   masqué par les photos qui passent et révélé par les vides entre elles.
   Tout en CSS — c'est `position: sticky` qui épingle et le défilement de
   la page qui déplace les photos, sans aucun JavaScript.

   Toutes les tailles sont des multiples de --u, 1 % de la largeur de la
   section, relevés sur une référence de 1456px puis agrandis de 12 % :
   pas de colonne 393px, pas de rangée 469px, boîte image 320x309px. Cette
   unité unique garde la composition en proportion à toutes les largeurs,
   sans le moindre point de rupture.
   ------------------------------------------------------------------ */

.ellipse-galerie {
  --u: max(1cqw, 5.5px);
  --pas-col: calc(var(--u) * 26.98);
  --pas-rang: calc(var(--u) * 32.23);

  /* Le logo est posé à 50vh. --phase est l'endroit où cette hauteur tombe
     dans une rangée, mesuré depuis un inter-rangée des colonnes décalées
     d'une demi-rangée. Remonter la grille d'autant place un vide des
     colonnes 1 et 3 pile derrière le logo, et descendre la colonne
     centrale au-delà dégage le centre : le logo est donc découvert au
     départ quel que soit le format de l'écran, les photos atteignant
     malgré tout le bord haut. Les navigateurs sans `mod()` démarrent
     simplement avec le logo à moitié couvert. */
  --phase: mod(50vh - var(--u) * 16.115, var(--pas-rang));

  position: relative;
  width: 100%;
  margin-bottom: 0;
  /* `clip` et non `hidden` : `hidden` ferait de la section le conteneur de
     défilement et l'épinglage ne fonctionnerait plus. */
  overflow: clip;
  container-type: inline-size;
}

/* Calque du logo : il couvre la section, son enfant épinglé le tient au
   centre de l'écran. */
.ellipse-gal-titre {
  position: absolute;
  inset: 0;
}

.ellipse-gal-titre-fixe {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 16px;
}

.ellipse-gal-logo {
  width: min(calc(var(--u) * 52), 88vw);
  height: auto;
  display: block;
}

/* Positionnée et plus loin dans le document : la grille passe par-dessus
   le logo. */
.ellipse-gal-grille {
  position: relative;
  display: flex;
  justify-content: center;
  margin-top: calc(var(--phase) - var(--pas-rang));
}

.ellipse-gal-col {
  flex: 0 0 auto;
  width: var(--pas-col);
}

/* Les colonnes impaires descendent d'une demi-rangée — c'est le décalage
   en quinconce. Uniquement des transformations : la hauteur de la section
   reste celle des huit rangées, et ce qui dépasse est rogné en bas. */
.ellipse-gal-col:nth-child(2),
.ellipse-gal-col:nth-child(4) {
  transform: translateY(calc(var(--pas-rang) / 2));
}

.ellipse-gal-col:nth-child(3) {
  transform: translateY(calc(50vh - var(--phase) + var(--u) * 48.345));
}

.ellipse-gal-case {
  height: var(--pas-rang);
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Boîte fixe en `contain` : les photos carrées remplissent la largeur, la
   verticale remplit la hauteur, rien n'est recadré et rien ne bouge
   pendant le chargement. */
.ellipse-gal-case img {
  width: calc(var(--u) * 22);
  height: calc(var(--u) * 21.24);
  object-fit: contain;
  display: block;
}

/* ------------------------------------------------------------------
   Carrousels simples des études de cas — une diapositive à la fois,
   sans avance automatique. Coins arrondis et ombre portée sur le cadre.
   Ils vont par deux, dans les panneaux de la piste horizontale.
   ------------------------------------------------------------------ */

.pc-carrousel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  width: 100%;
}

.pc-cadre {
  position: relative;
  width: min(100%, 58vh, 500px);
}

@media (max-width: 1000px) {
  .pc-carrousel {
    gap: 14px;
  }
}

/* L'ombre est portée par le cadre lui-même : posée sur l'image, elle
   serait rognée par le `overflow: hidden` qui retient la piste. */
.pc-fenetre {
  overflow: hidden;
  border-radius: 20px;
  box-shadow: 0 28px 60px rgba(0, 0, 0, 0.45);
  cursor: grab;
  /* Le glissement horizontal nous revient, le défilement vertical de la
     page reste au navigateur. */
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}

/* C'est le carrousel qui porte la classe pendant la saisie, pas la
   section : celle-ci n'existe plus. */
.pc-carrousel.est-saisi .pc-fenetre {
  cursor: grabbing;
}

.pc-piste {
  display: flex;
  transition: transform 500ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Pendant la saisie, la piste doit coller au doigt : une transition la
   ferait traîner d'une demi-seconde derrière lui. */
.pc-carrousel.est-saisi .pc-piste {
  transition: none;
}

.pc-slide {
  flex: 0 0 100%;
}

.pc-slide img {
  width: 100%;
  height: auto;
  display: block;
  -webkit-user-drag: none;
  pointer-events: none;
}

/* Barre de navigation sous l'image : une petite flèche de chaque côté
   des pastilles. Pas de cercle ni de fond — ce sont des repères, pas des
   boutons posés sur la photo. */
.pc-barre {
  display: flex;
  align-items: center;
  gap: 14px;
}

.pc-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  opacity: 0.75;
  cursor: pointer;
  transition: opacity 200ms ease;
}

.pc-nav:hover:not(:disabled) {
  opacity: 1;
}

.pc-nav:disabled {
  opacity: 0.2;
  cursor: default;
}

.pc-nav svg {
  width: 100%;
  height: 100%;
}

.pc-points {
  display: flex;
  gap: 10px;
}

.pc-point {
  width: 9px;
  height: 9px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(240, 239, 236, 0.3);
  cursor: pointer;
  transition: background 200ms ease, transform 200ms ease;
}

.pc-point.est-actif {
  background: #f0efec;
  transform: scale(1.25);
}

/* En dessous de cette largeur, les deux cadres ne tiennent plus de front
   sans devenir illisibles : ils passent l'un sous l'autre. */
/* Les deux carrousels restent de front à toutes les largeurs. En dessous
   de 1000 px il n'y a plus la place de loger les flèches de part et
   d'autre des cadres : on resserre la marge et l'écart au maximum, et les
   flèches viennent se poser sur les bords des cadres. */
@media (max-width: 1000px) {
  .pc-barre {
    gap: 10px;
  }

  .pc-points {
    gap: 7px;
  }

  .pc-point {
    width: 7px;
    height: 7px;
  }

  .pc-nav {
    width: 18px;
    height: 18px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .pc-piste {
    transition-duration: 1ms;
  }
}


/* ------------------------------------------------------------------
   Bande horizontale épinglée de la page Naiart — six carrousels à la
   file, deux à l'écran à la fois.

   L'écart est le même partout, entre les deux carrousels visibles comme
   entre le second et celui qui suit. C'est pour cela que la bande est
   une seule rangée à `gap` constant, et non une suite de panneaux d'un
   écran : entre deux panneaux, l'écart aurait valu deux fois leur marge
   intérieure, donc jamais celui d'une paire.

   La cellule porte directement la largeur du cadre, sans jeu autour :
   du mou dans la cellule se lirait comme un écart plus grand.

   La hauteur de la section est la course totale — un écran pour son
   arrivée, puis une traversée et demie par passage. Elle doit rester
   d'accord avec PH_TRAVERSEE dans piste-horizontale.js, sinon la bande
   finit sa course avant ou après la fin de l'épinglage ; dans le second
   cas la page se fige un instant.
   ------------------------------------------------------------------ */

.nh-section {
  position: relative;
  /* Un écran pour l'arrivée, puis une traversée et demie par passage.
     --nh-passages est posé sur chaque rangée dans le document, au plus
     près du nombre de carrousels qu'elle contient. */
  height: calc(100vh + var(--nh-passages) * 150vh);
  margin-bottom: 0;

  --nh-ecart: clamp(12px, 3vw, 48px);
  /* Deux cellules et un écart tiennent dans l'écran, en gardant de quoi
     respirer sur les côtés. */
  --nh-cellule: min(500px, 58vh, (100vw - 3 * var(--nh-ecart)) / 2);
}

.nh-sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  /* La suite de la bande attend hors cadre à droite : sans ça, elle
     élargirait la page. */
  overflow: hidden;
}

.nh-bande {
  display: flex;
  align-items: center;
  width: max-content;
  height: 100%;
  gap: var(--nh-ecart);
  /* Centre la première paire : de quoi loger la moitié de ce qui reste
     une fois les deux cellules et leur écart posés. */
  padding-inline: calc((100vw - 2 * var(--nh-cellule) - var(--nh-ecart)) / 2);
  will-change: transform;
}

.nh-bande .pc-carrousel {
  flex: 0 0 var(--nh-cellule);
  width: var(--nh-cellule);
}

/* La cellule porte déjà les limites de taille : le cadre la remplit. */
.nh-bande .pc-cadre {
  width: 100%;
}

/* Sans animation : pas d'épinglage, les carrousels se suivent
   verticalement. */
.nh-section.est-statique {
  height: auto;
}

.nh-section.est-statique .nh-sticky {
  position: static;
  height: auto;
  overflow: visible;
}

.nh-section.est-statique .nh-bande {
  display: grid;
  width: auto;
  gap: 64px;
  padding: 64px max(12px, 2vw);
  justify-items: center;
}

.nh-section.est-statique .nh-bande .pc-carrousel {
  flex: none;
  width: min(100%, 500px);
}

/* Page Sony : vidéo seule au centre, et affichage grand format.

   La vidéo est verticale (1440 × 2560). Sa largeur est bornée par trois
   valeurs : la place disponible, la hauteur de la fenêtre convertie à ce
   rapport (47vh ≈ 84vh × 9/16, pour qu'elle tienne à l'écran sans qu'on
   ait à la faire défiler), et sa taille maximale. Elle reste nettement
   plus grande que les vignettes du carrousel, qui plafonnent à ~350px. */

.sony-video-section {
  display: flex;
  justify-content: center;
}

.sony-video {
  position: relative;
  width: min(100%, 47vh, 560px);
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 16px 32px rgba(0, 0, 0, 0.22);
}

.sony-video video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 9 / 16;
  object-fit: cover;
}

.sony-video-section {
  margin-bottom: 0;
}

/* Bouton de lecture en verre, posé au centre de la vidéo.

   Le principe du verre d'iOS : la pastille ne porte pas de couleur à elle,
   elle reprend l'image qui passe dessous en la floutant et en la saturant,
   puis l'éclaircit d'un voile. Un liseré clair en haut et un reflet
   décentré lui donnent son épaisseur, l'ombre portée la décolle. Elle
   reste donc lisible sur un plan clair comme sur un plan sombre. */

.sony-video-play {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: none;
  color: #fff;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.sony-video-pastille {
  position: relative;
  display: grid;
  place-items: center;
  width: clamp(68px, 21%, 104px);
  aspect-ratio: 1;
  border-radius: 999px;

  background: rgba(255, 255, 255, 0.14);
  backdrop-filter: blur(18px) saturate(180%);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  box-shadow:
    inset 0 1.5px 1px rgba(255, 255, 255, 0.55),
    inset 0 -1.5px 1px rgba(255, 255, 255, 0.12),
    inset 0 0 0 1px rgba(255, 255, 255, 0.22),
    0 10px 30px rgba(0, 0, 0, 0.35);
  transition: transform 280ms var(--ease-enter), background 280ms ease,
    opacity 280ms ease;
}

/* Reflet spéculaire, décalé vers le haut comme sur une goutte. */
.sony-video-pastille::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(
    120% 90% at 30% 10%,
    rgba(255, 255, 255, 0.5),
    rgba(255, 255, 255, 0) 62%
  );
  pointer-events: none;
}

.sony-video-icone {
  width: 38%;
  height: 38%;
  /* Un triangle paraît décentré quand il est centré géométriquement. */
  margin-left: 6%;
}

.sony-video.est-en-lecture .sony-video-icone {
  margin-left: 0;
}

.sony-video-play:hover .sony-video-pastille {
  transform: scale(1.06);
  background: rgba(255, 255, 255, 0.2);
}

.sony-video-play:active .sony-video-pastille {
  transform: scale(0.96);
}

/* Pendant la lecture la pastille s'efface, et revient au survol pour
   offrir la pause. */
.sony-video.est-en-lecture .sony-video-pastille {
  opacity: 0;
}

.sony-video.est-en-lecture .sony-video-play:hover .sony-video-pastille,
.sony-video.est-en-lecture .sony-video-play:focus-visible .sony-video-pastille {
  opacity: 1;
}

.sony-video-play:focus-visible {
  outline: 2px solid #fff;
  outline-offset: -6px;
}

@media (prefers-reduced-motion: reduce) {
  .sony-video-pastille {
    transition-duration: 1ms;
  }
}

/* Affichage : la source fait 1240px de large, on ne la dépasse jamais. */

.sony-affichage {
  display: flex;
  justify-content: center;
}

.sony-affichage-bouton {
  display: block;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  -webkit-tap-highlight-color: transparent;

  width: min(100%, 1240px);
  cursor: zoom-in;
}

.sony-affichage-bouton img {
  display: block;
  width: 100%;
  height: auto;
}

.sony-affichage-bouton:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 4px;
}

/* Lecteur plein écran de la vidéo Sony.

   Les contrôles du navigateur ne sont pas habillables : impossible d'en
   retirer le volume ou le minutage. Ceux-ci sont donc les nôtres — un
   bouton de lecture et une barre de progression, rien d'autre. */

.vp-calque {
  position: fixed;
  inset: 0;
  z-index: 55;
  display: grid;
  grid-template-rows: 1fr auto;
  background: #000;
  opacity: 0;
  visibility: hidden;
  /* Bascule immédiate à l'ouverture pour que le bouton de fermeture soit
     focusable tout de suite, différée à la fermeture pour le fondu. */
  transition: opacity 300ms ease, visibility 0s linear 300ms;
}

.vp-calque.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 300ms ease, visibility 0s;
}

.vp-scene {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Sans ça, une rangée de grille refuse de comprimer son contenu et la
     vidéo déborde sous la barre de contrôle. */
  min-height: 0;
  padding: clamp(16px, 3vw, 40px);
}

.vp-scene video {
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  border-radius: 8px;
  cursor: pointer;
}

.vp-fermer {
  position: absolute;
  top: clamp(16px, 2.5vw, 32px);
  right: clamp(16px, 2.5vw, 32px);
  z-index: 2;
  width: clamp(36px, 3.5vw, 48px);
  height: clamp(36px, 3.5vw, 48px);
  padding: 0;
  border: 0;
  background: none;
  color: #fff;
  cursor: pointer;
}

.vp-fermer svg {
  width: 100%;
  height: 100%;
}

.vp-barre {
  display: flex;
  align-items: center;
  gap: clamp(12px, 1.6vw, 20px);
  padding: clamp(12px, 1.8vw, 20px) clamp(16px, 3vw, 40px) clamp(20px, 2.6vw, 32px);
  color: #fff;
}

.vp-lecture {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
}

.vp-lecture svg {
  width: 100%;
  height: 100%;
}

.vp-piste {
  flex: 1 1 auto;
  height: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.28);
  overflow: hidden;
  cursor: pointer;
  /* La piste se saisit au doigt sans que la page ne défile derrière. */
  touch-action: none;
  transition: height 160ms ease;
}

.vp-piste:hover,
.vp-piste:focus-visible,
.vp-piste.est-saisie {
  height: 7px;
}

.vp-avance {
  width: 100%;
  height: 100%;
  background: #fff;
  /* Posée par requestAnimationFrame, donc à chaque image : une transition
     ou une largeur en pourcentage la rendrait saccadée ou coûteuse. */
  transform-origin: left center;
  transform: scaleX(0);
}

@media (prefers-reduced-motion: reduce) {
  .vp-calque,
  .vp-piste {
    transition-duration: 1ms;
  }
}

/* Logo de l'accueil : il ouvre le menu, donc c'est un bouton. */

.home-logo-bouton {
  display: block;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.home-logo-bouton:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 8px;
}

/* La couverture Bling mène au magazine, comme le bouton. Le lien reprend
   la géométrie de l'image — marges négatives comprises — pour que la mise
   en page ne bouge pas d'un pixel. */

.bling-cover-lien {
  display: block;
  width: var(--cover-w);
  margin-top: calc(var(--cover-w) * -0.35);
  margin-bottom: calc(var(--cover-w) * -0.21);
}

.bling-cover-lien .bling-cover-image {
  width: 100%;
  margin: 0;
}

/* Bandes de photos épinglées (Ellipse, Bloom).

   Même mécanique que la bande de carrousels de Naiart, mais les cellules
   prennent la largeur de leur image au lieu d'une largeur commune : les
   photos partagent une hauteur et gardent leur cadrage, aucune n'est
   recadrée.

   --ph-large est la somme des rapports largeur/hauteur de la paire la plus
   large que le défilement met côte à côte. C'est elle qui borne la hauteur,
   pour que cette paire tienne dans l'écran sans déborder. */

.nh-section.ph-photos {
  /* Facteur de retouche, appliqué après la borne : il permet d'ajuster la
     taille page par page et selon l'écran sans toucher au calcul. */
  --ph-echelle: 1;
  --ph-hauteur: calc(
    min(78vh, 820px, (100vw - 3 * var(--nh-ecart)) / var(--ph-large))
      * var(--ph-echelle)
  );
}

.nh-section.ellipse-bande {
  /* Deux carrés : 1 + 1. */
  --ph-large: 2;
  --ph-echelle: 0.9;
}

.ph-photos .nh-bande {
  padding-inline: var(--nh-ecart);
}

.ph-photos .ph-photo {
  flex: 0 0 auto;
  margin: 0;
}

.ph-photos .ph-photo img {
  display: block;
  height: var(--ph-hauteur);
  width: auto;
  border-radius: 12px;
  box-shadow: var(--ph-ombre);
}

/* L'ombre du site (0 16px 32px / 0.22) disparaît sur le noir d'Ellipse :
   elle y est reprise du réglage des pages sombres, plus large et plus
   dense. */
.nh-section.ellipse-bande {
  --ph-ombre: 0 28px 60px rgba(0, 0, 0, 0.75);
}

.ph-photos .ph-zoom {
  display: block;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  -webkit-tap-highlight-color: transparent;
}

.ph-photos .ph-zoom:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 4px;
}

/* Sans animation : la bande redevient une simple suite verticale. */

.nh-section.ph-photos.est-statique .nh-bande {
  flex-wrap: wrap;
  justify-content: center;
  padding-block: 48px;
}

.nh-section.ph-photos.est-statique .ph-photo img {
  height: auto;
  width: min(90vw, 520px);
}

/* La bande de photos gagne un peu de taille sur mobile, là où deux
   cellules par écran la rendent forcément étroite. Déclarée ici, après la
   valeur de bureau : une requête de média n'ajoute pas de spécificité,
   c'est l'ordre qui tranche. */

@media (max-width: 700px) {
  .nh-section.ellipse-bande {
    --ph-echelle: 1.05;
  }
}

/* Page Naiart — blocs de texte et visuels entre les bandes de carrousels. */

.naiart-bloc {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(32px, 6vh, 72px);
  padding: clamp(64px, 10vh, 140px) 24px;
  margin-bottom: 0;
}

.naiart-texte {
  /* En em : la mesure suit le corps du texte, la longueur de ligne reste
     la même quelle que soit la taille de police choisie. */
  max-width: 34em;
  margin: 0;
  font-size: clamp(1.05rem, 1.5vw, 1.35rem);
  line-height: 1.65;
  text-align: center;
  text-wrap: pretty;
}

/* Le schéma du clipping porte son texte : il doit rester lisible, donc
   large. Jamais au-delà de sa définition d'origine (3583px). */
.naiart-clipping {
  display: block;
  width: min(94vw, 1500px);
  height: auto;
}

.naiart-offcall-logo {
  display: block;
  width: min(72vw, 420px);
  height: auto;
}

/* Révélation des paragraphes, mot à mot (posée par texte-revele.js).

   Chaque mot est enfermé dans un cache à débordement masqué, d'où il monte
   en se dénouant d'un flou. Le mouvement est entièrement en CSS : le script
   ne fait que découper le texte et donner le départ. */

.tr-pret {
  --tr-duree: 0.8s;
  /* Flou de départ : doux sur un paragraphe, plus marqué sur un titre. */
  --tr-flou: 4px;
}

.tr-pret h1,
h1.tr-pret,
h2.tr-pret {
  --tr-flou: 8px;
}

/* Le texte entier, pour les lecteurs d'écran seulement : les mots découpés
   seraient énoncés un à un. */
.tr-lecture {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  white-space: nowrap;
  border: 0;
  clip-path: inset(50%);
}

.tr-cache {
  display: inline-block;
  overflow: hidden;
  /* Le cache épouse la ligne au pixel près : sans ce débord, les jambages
     des g, p et y se feraient trancher par le masquage. */
  padding-bottom: 0.16em;
  margin-bottom: -0.16em;
  vertical-align: bottom;
}

.tr-mot {
  display: inline-block;
  will-change: transform, filter, opacity;
  /* État de départ, tenu tant que la révélation n'est pas lancée. */
  transform: translateY(110%);
  filter: blur(var(--tr-flou));
  opacity: 0;
}

.tr-pret.est-revele .tr-mot {
  animation: tr-monte var(--tr-duree) var(--ease-enter) var(--tr-retard, 0s) both;
}

@keyframes tr-monte {
  from {
    transform: translateY(110%);
    filter: blur(var(--tr-flou));
    opacity: 0;
  }
  to {
    transform: translateY(0);
    filter: blur(0);
    opacity: 1;
  }
}

/* Le script s'abstient en mouvement réduit, mais si la classe est déjà
   posée — page laissée ouverte, réglage changé — le texte reste lisible. */
@media (prefers-reduced-motion: reduce) {
  .tr-mot {
    transform: none;
    filter: none;
    opacity: 1;
    animation: none;
  }
}



/* Galerie Bloom : trois photos, puis deux, puis deux, d'un bord de l'écran
   à l'autre. Rien autour d'elles — ni marge, ni arrondi, ni ombre.

   La largeur d'une photo dans sa rangée est proportionnelle à son rapport
   largeur/hauteur, porté par --r : avec une base de 0, flex répartit toute
   la rangée selon ces parts, et chaque photo reçoit donc une largeur
   exactement proportionnelle à sa forme. Les hauteurs tombent d'elles-mêmes
   à l'identique, et aucune photo n'est recadrée. C'est cette égalité qui
   rend le collage possible sans object-fit, qui aurait rogné les portraits
   placés à côté d'un paysage. */

.bloom-grille {
  /* Pleine largeur. main n'a pas de retrait sur cette page : 100 % suffit,
     et c'est plus sûr que 100vw ou une marge négative en vw, qui comptent
     la barre de défilement et débordent de quelques pixels à gauche. */
  width: 100%;
  display: flex;
  flex-direction: column;
  /* Les photos se touchent : la section ne garde aucun espacement propre. */
  margin-bottom: 0;
  padding: 0;
}

.bloom-rangee {
  display: flex;
  /* Pas de retour à la ligne : la rangée garde son compte de photos à
     toutes les tailles, comme demandé. */
  flex-wrap: nowrap;
}

.bloom-case {
  flex: var(--r) 1 0;
  margin: 0;
  /* Les largeurs calculées tombent sur des fractions de pixel. Sans ce
     débord d'un demi-pixel, un liséré du fond apparaît entre deux photos
     voisines selon l'arrondi du navigateur. */
  margin-right: -0.5px;
  min-width: 0;
}

.bloom-case:last-child {
  margin-right: 0;
}

.bloom-zoom {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  -webkit-tap-highlight-color: transparent;
}

.bloom-zoom:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: -4px;
}

.bloom-grille img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 0;
  box-shadow: none;
}

/* Fin de page : plus de blanc résiduel sous le dernier bloc.

   Il venait de trois endroits — le remplissage bas de main, la marge basse
   que la dernière section héritait de la règle générale, et le remplissage
   propre à la page projets. Aucun pied de page n'existe dans le HTML :
   c'est cette bande vide qui en avait l'air.

   Les pages CV et contact ne sont pas concernées : leur contenu est centré
   verticalement, l'espace du bas y répond à celui du haut. */

main {
  padding-bottom: 0;
}

.projets-page {
  padding-bottom: 0;
}

main > :last-child {
  margin-bottom: 0;
}

/* Sony — affichage métro : inclinaison plus marquée (34°, posée en data-tilt).

   Deux ajustements vont avec l'angle. La perspective passe de 1200 à
   2600px : un objectif plus long garde le basculement lisible sans
   l'élargissement qu'il produit de biais — à 1200px, 34° gonflaient la
   carte de moitié et les affiches sortaient de l'écran. La largeur est
   ensuite bornée sur ce qui reste d'élargissement (×1,26), pour que les
   coins bas ne soient jamais rognés.

   La règle ne vise que le bloc de Sony : celui d'Ellipse n'est pas dans
   un .scroll-hero et garde ses propres réglages. */

.scroll-hero .scroll-hero-card-outer {
  perspective: 2600px;
  width: min(100%, 78vw, 1000px);
  margin-inline: auto;
}

/* Naiart — carte de présentation, seule et centrée sous le logo.

   Trois bornes : la place disponible, un plafond, et la hauteur d'écran
   convertie au rapport de l'image (3559/2283 = 1,559), pour qu'elle tienne
   d'un seul tenant sans qu'on ait à la faire défiler. Jamais au-delà de sa
   définition d'origine. */

.naiart-presentation {
  display: block;
  width: min(92vw, 1100px, calc(78vh * 1.559));
  height: auto;
}

/* Sur grand écran, les rangées de carrousels se calent sur la gauche au
   lieu de se centrer : le bord gauche de chaque groupe tombe toujours à la
   même distance du bord de l'écran.

   La règle épargne les bandes de photos d'Ellipse et de Bloom, qui gardent
   leur centrage. En dessous de 1000px elle ne s'applique pas non plus :
   la paire y occupe déjà toute la largeur, il n'y a rien à décaler.

   La valeur est donnée en pixels, par paliers, et non en clamp() : le
   script la lit avec getPropertyValue, qui rend une variable telle qu'elle
   est écrite sans jamais résoudre une fonction. */

@media (min-width: 1000px) {
  .nh-section:not(.ph-photos) {
    --nh-bord: 48px;
  }
}

@media (min-width: 1400px) {
  .nh-section:not(.ph-photos) {
    --nh-bord: 80px;
  }
}

/* La carte de présentation est décalée vers la droite : son logo imprimé
   tire l'œil à gauche, le décalage rééquilibre l'ensemble. En pourcentage
   de sa propre largeur, borné en pixels pour ne pas sortir de l'écran sur
   un téléphone, où elle occupe déjà 92vw. */

.naiart-presentation {
  transform: translateX(clamp(8px, 3.5%, 56px));
}

/* ------------------------------------------------------------------
   Téléphone — un seul espace entre les sections, sur toutes les pages.

   Le blanc entre deux sections venait de quatre sources, qui s'ajoutaient
   sans s'accorder : des sections hautes d'un écran entier, centrées dans
   leur hauteur ; des remplissages en vh ; la marge générale des sections ;
   et, pour les bandes épinglées, le vide de la boîte collante autour du
   contenu. Résultat : de 48 à 850 px selon l'endroit.

   Ici, plus aucune section ne porte de hauteur ni de remplissage vertical
   propre. Chacune est séparée de la suivante par --espace-section, mesuré
   entre le bord visible des deux contenus. Les ombres des carrousels, qui
   avaient besoin d'une marge intérieure pour ne pas être rognées, débordent
   désormais dans cet espace (overflow-x: clip, et non hidden : hidden
   forcerait aussi le rognage vertical).

   Les bandes épinglées gardent leur boîte d'un écran, indispensable à
   l'épinglage : vide-epingle.js mesure l'air qu'elle laisse autour du
   contenu, et la marge négative ci-dessous le reprend.

   Le premier écran de chaque page fait exception : le logo y est seul, au
   centre de l'écran.
   ------------------------------------------------------------------ */

@media (max-width: 720px) {
  :root {
    --espace-section: clamp(24px, 8vw, 40px);
  }

  main > section:not(:last-child) {
    margin-bottom: var(--espace-section);
  }

  /* Premier écran : le logo seul, au centre de l'écran visible (svh : la
     hauteur sans la barre d'adresse, que vh compte à tort). */
  .naiart-hero,
  .bling-hero,
  .bloom-hero,
  .ellipse-hero,
  .rocket-hero {
    min-height: 100svh;
  }

  .scroll-hero-title {
    min-height: 100svh;
    margin-bottom: 0;
  }

  .scroll-hero-card-outer {
    padding: 0;
  }

  .ellipse-carte-section,
  .ellipse-affiche-section,
  .ellipse-shotgun-section {
    min-height: 0;
    padding-block: 0;
  }

  .naiart-bloc,
  .bloom-posts,
  .ellipse-slides-section,
  .bling-cover-section {
    padding-block: 0;
  }

  .naiart-bloc {
    gap: 24px;
  }

  /* La rangée fait déjà 96 % de l'écran : les 24 px latéraux de la section
     la faisaient dépasser de 9 px à droite, et le navigateur élargissait
     la page pour tout montrer. */
  .bloom-posts,
  .ellipse-slides-section {
    padding-inline: 0;
  }

  .rocket-banniere-section {
    padding-bottom: 0;
  }

  /* Carrousels dont les ombres tenaient dans une marge intérieure : la
     hauteur de la section ne garde que celle des cartes (360 - 2 x 60). */
  .story-carousel {
    height: calc(clamp(360px, 40vw, 620px) - 120px);
    overflow-x: clip;
    overflow-y: visible;
  }

  .story-carousel-track {
    padding: 0;
  }

  .rocket-bande {
    padding-block: 0;
    overflow-x: clip;
    overflow-y: visible;
  }

  .cf-section {
    min-height: 0;
    padding: 0 16px;
    overflow-x: clip;
    overflow-y: visible;
  }

  /* Sections épinglées : on reprend le vide mesuré autour du contenu. */
  main > section.nh-section,
  main > section.bloom-h-section,
  main > section.bling-mag-section,
  main > section.phones-section {
    margin-top: calc(var(--vide-haut, 0px) * -1);
  }

  main > section.nh-section:not(:last-child),
  main > section.bloom-h-section:not(:last-child),
  main > section.bling-mag-section:not(:last-child),
  main > section.phones-section:not(:last-child) {
    margin-bottom: calc(var(--espace-section) - var(--vide-bas, 0px));
  }

  main > section.nh-section:last-child,
  main > section.bloom-h-section:last-child,
  main > section.bling-mag-section:last-child,
  main > section.phones-section:last-child {
    margin-bottom: calc(var(--vide-bas, 0px) * -1);
  }

  /* Point d'épinglage : 0, sauf pour une section si haute dans la page que
     sa boîte serait coincée dès l'ouverture (voir vide-epingle.js). */
  .nh-sticky,
  .bloom-h-sticky,
  .bling-mag-sticky,
  .phones-sticky {
    top: var(--epingle-haut, 0px);
  }

  .phones-sticky {
    min-height: 100svh;
  }

  /* La boîte collante empiète sur les sections voisines, par sa partie
     transparente seulement : elle ne doit rien intercepter. */
  .nh-sticky,
  .nh-bande,
  .bloom-h-sticky,
  .bloom-h-piste,
  .bloom-h-panneau,
  .bling-mag-sticky,
  .phones-sticky {
    pointer-events: none;
  }

  .nh-bande > *,
  .phones-sticky > .project-media {
    pointer-events: auto;
  }

  /* Son fond blanc recouvrirait le bas de la section précédente. */
  .bling-mag-section {
    background: transparent;
  }
}

/* Les rangées de magazines, inclinées et plus larges que l'écran (120 %),
   dépassent à droite. Sur un téléphone, le navigateur élargit alors sa
   fenêtre pour tout montrer — 434 px pour un écran de 375 — et la page
   entière s'affiche en réduction, décalée. Rogner en largeur règle cela ;
   clip, et non hidden, parce que hidden ferait de la section un conteneur
   de défilement et casserait l'épinglage de la boîte collante. */
.bling-mag-section {
  overflow-x: clip;
}

/* ------------------------------------------------------------------
   Téléphone, page Sony.

   La vidéo est un peu moins large, les bannières et les visuels du
   carrousel plus serrés. Les quatre téléphones prennent presque toute la
   largeur de l'écran, avec un simple liseré entre eux : la place que
   leur laissait la marge de la page (24 px de chaque côté, 24 px entre
   deux) allait en blanc.
   ------------------------------------------------------------------ */

@media (max-width: 720px) {
  .page-sony .sony-video {
    width: 77%;
  }

  .page-sony .project-media.cols-1 {
    gap: 12px;
  }

  .page-sony .story-carousel-track .story-slide,
  .page-naiart .story-carousel-track .story-slide {
    margin-right: 22px;
  }

  /* Les téléphones étant au centre de l'écran quand l'épinglage commence,
     l'affichage qui les surmonte se loge au-dessus : un peu plus petit, il
     tient entier, titre compris. */
  .page-sony .sony-affichage-bouton {
    width: 82%;
  }

  .page-sony .phones-sticky .project-media.cols-4 {
    width: calc(100% + 36px);
    margin-inline: -18px;
    gap: 6px;
  }

  /* Premier écran : le titre sur une seule ligne, le texte sur quatre. Les
     tailles suivent la largeur utile (écran moins les 2 x 24 px de marge) :
     les coefficients sont ceux de la police, mesurés à 320, 360, 375, 390,
     414 et 430 px, avec une réserve de 2 % pour le titre. */
  .page-sony .scroll-hero-title h1 {
    max-width: none;
    white-space: nowrap;
    font-size: calc((100vw - 48px) / 19.6);
  }

  .page-sony .scroll-hero-title p {
    max-width: none;
    text-wrap: balance;
    font-size: calc((100vw - 48px) / 22.7);
    margin-top: -8px;
  }

  /* Les affiches se touchent presque. Les fichiers gardent une marge
     transparente (ombre portée comprise) d'environ 10 % de leur largeur de
     chaque côté, qui laissait 40 px de blanc entre deux affiches : on
     agrandit chaque image de 22 % autour de son centre, la marge déborde
     de sa colonne, l'affiche grandit d'autant et il ne reste que ~10 px. */
  .page-sony .scroll-hero-card {
    gap: 8px;
  }

  .page-sony .scroll-hero-card img {
    width: 122%;
    max-width: none;
    justify-self: center;
  }

  /* Plus d'air entre la vidéo, le carrousel, l'affichage et les
     téléphones. */
  .page-sony {
    --espace-sony: clamp(44px, 15vw, 64px);
  }

  .page-sony .sony-video-section,
  .page-sony .story-carousel,
  .page-sony .sony-affichage {
    margin-bottom: var(--espace-sony);
  }
}

/* ------------------------------------------------------------------
   Téléphone, page Naiart : 80 px entre les sections, et le texte
   d'Offcall un peu plus petit.
   ------------------------------------------------------------------ */

@media (max-width: 720px) {
  .page-naiart {
    --espace-section: 80px;
  }

  .page-naiart .naiart-texte {
    font-size: 0.9rem;
  }
}

/* ------------------------------------------------------------------
   Téléphone — groupes épinglés (groupe-epingle.js).

   La section épinglée embarque dans sa boîte collante les sections qui la
   précèdent : le tout se fige ensemble, rien ne continue de défiler. La
   boîte a la hauteur de son contenu, et se cale en bas de l'écran
   (--groupe-haut) ; la course — la hauteur que la section ajoute sous la
   boîte pour qu'elle reste collée — vient d'un bloc vide placé après elle
   (--course). Plus de boîte d'un écran, donc plus de vide à reprendre.
   ------------------------------------------------------------------ */

@media (max-width: 720px) {
  main > section.groupe-epingle {
    height: auto;
    margin-top: 0;
  }

  main > section.groupe-epingle::after {
    content: "";
    display: block;
    height: var(--course, 0px);
  }

  .groupe-epingle > .nh-sticky,
  .groupe-epingle > .phones-sticky {
    position: sticky;
    top: var(--groupe-haut, 0px);
    display: block;
    height: auto;
    min-height: 0;
    overflow-x: clip;
    overflow-y: visible;
    pointer-events: auto;
  }

  /* La bande des carrousels dépasse de l'écran à droite, pas les
     téléphones : leur boîte ne rogne rien (le carrousel pleine largeur
     qu'elle embarque déborde de ses marges). */
  .groupe-epingle > .phones-sticky {
    overflow: visible;
  }

  .groupe-epingle > .nh-sticky > .nh-bande {
    height: auto;
    align-items: flex-start;
    pointer-events: auto;
  }

  /* Les sections embarquées ne sont plus des enfants de main : elles
     gardent l'espace qui les séparait de la suivante. La dernière de la
     boîte n'en a pas : la section suit déjà à cette distance. */
  .groupe-epingle > .nh-sticky > section,
  .groupe-epingle > .nh-sticky > .nh-bande:not(:last-child) {
    margin-bottom: var(--espace-section);
  }

  .groupe-epingle > .nh-sticky > section:last-child {
    margin-bottom: 0;
  }

  .groupe-epingle > .phones-sticky > section {
    margin-bottom: var(--espace-sony);
  }
}

/* ------------------------------------------------------------------
   Téléphone, page Bloom : 75 px entre la présentation de Lazzio et les
   publications, 85 px entre les publications et la galerie. L'écart est
   mesuré entre les bords visibles : le bas de l'encart de texte de
   Lazzio, le haut et le bas de la publication du milieu (la plus haute),
   le haut de la galerie. La bande épinglée retire son vide de
   --espace-section : on règle la variable sur elle seule.
   ------------------------------------------------------------------ */

@media (max-width: 720px) {
  main > section.bloom-h-section {
    --espace-section: 75px;
  }

  main > section.bloom-posts {
    margin-bottom: 85px;
  }
}

/* ------------------------------------------------------------------
   Téléphone, page Bling : texte d'introduction plus petit, bouton plus
   discret, et plus d'air entre les rangées de spreads et la couverture.
   ------------------------------------------------------------------ */

@media (max-width: 720px) {
  .bling-intro {
    font-size: 0.9rem;
  }

  .bling-cover-section .btn-round {
    font-size: 0.8125rem;
    padding: 11px 26px;
  }

  main > section.bling-mag-section {
    --espace-section: 60px;
  }

  /* Du blanc sous le bouton, en fin de page. */
  .bling-cover-section {
    padding-bottom: 96px;
  }
}

/* ------------------------------------------------------------------
   Téléphone, page Ellipse : 80 px entre les sections.
   ------------------------------------------------------------------ */

@media (max-width: 720px) {
  .page-ellipse {
    --espace-section: 80px;
  }
}

/* Téléphone : bouton « Télécharger le CV » plus petit, boutons ronds de la
   page Contact un peu plus petits (48 px -> 42 px). */
@media (max-width: 720px) {
  .cv-page .btn-round {
    font-size: 0.8125rem;
    padding: 11px 26px;
  }

  .contact-lien {
    width: 42px;
  }
}

/* Mentions légales et lien du menu. */
.legal-page {
  max-width: 44rem;
  margin: 0 auto;
  padding: 120px 24px 80px;
  line-height: 1.6;
}

.legal-page h1 {
  margin: 0 0 40px;
}

.legal-page h2 {
  margin: 32px 0 8px;
  font-size: 1.1rem;
}

.legal-page p {
  margin: 0;
}

.legal-page a {
  color: inherit;
}

.menu-legal {
  position: absolute;
  left: 50%;
  bottom: 28px;
  transform: translateX(-50%);
  color: #fff;
  font-size: 0.8rem;
  opacity: 0.55;
  text-decoration: none;
}

.menu-legal:hover,
.menu-legal:focus-visible {
  opacity: 1;
  text-decoration: underline;
}
