@charset "UTF-8";

/* ==========================================================================
   Casa Paradisu — Seconde couche

   Chargée par LES NEUF PAGES, avec `assets/js/premium.js`. Rien n'y écrase le
   socle : les classes existantes (`.home-section`, `.home-media`,
   `.home-link`, `.section`, `.frame`, `.reveal`) sont reprises telles quelles,
   jamais redéfinies, sauf là où c'est signalé.

      1.  Rythme vertical   resserrement des écarts entre sections (accueil)
      1b. Lit de maquis     une texture fixe, très basse, derrière la page
      2.  Titre du hero     ombre en trois plans, halo central, brillance
      3.  Carrousel         le vide à gauche de l'introduction (accueil)
      4.  Bande des espaces sept espaces glissés au défilement (accueil)
      4b. Liste chiffrée    `.facts-xl`, en grand (accueil)
      8.  Clôture           le vol repris, puis un cadre qui se referme (accueil)
      9.  Zoom au survol    une seule valeur, sur toutes les images
     10.  Le reste du site  ce que les huit autres pages reprennent

   Les blocs marqués « accueil » sont portés par des classes qui n'existent que
   là : ils ne coûtent rien aux autres pages.

   ATTENTION. `.facts*` appartient à `components.css` et sert le bloc « En
   résumé » de la page Contact. La liste chiffrée de l'accueil s'appelle donc
   `.facts-xl*`, et pas autrement.

   Chargée APRÈS `responsive.css` et `hero-scrub.css`.
   ========================================================================== */

/* ==========================================================================
   1. Lit de maquis
   Une photographie de canopée, désaturée et aplatie, posée en fond fixe et
   multipliée sur l'ivoire à 10 %. Assez présente pour que le fond cesse
   d'être un aplat, assez basse pour qu'aucun texte n'en souffre.

   `z-index: -1` : le lit se peint derrière le contenu normal sans qu'aucun
   élément n'ait à remonter dans la pile. En contrepartie, les sections de
   l'accueil doivent cesser d'être opaques — c'est le seul endroit où ce
   fichier défait une règle du socle.
   ========================================================================== */

.maquis-bed {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: url("../brand/fond-maquis.svg");
  /* Taille NATURELLE, jamais `cover` : la tuile est raccordable, l'étirer
     déformerait le dessin et épaissirait le trait d'un bord à l'autre. */
  background-size: 1400px 1400px;
  background-position: center;
  background-repeat: repeat;
  opacity: 0.07;
  /* `multiply` plutôt qu'une simple opacité : le trait creuse l'ivoire au
     lieu de le grisailler. */
  mix-blend-mode: multiply;
}

/* Sur un téléphone, une tuile de 1 400 px ne montrerait que deux couronnes :
   le motif cesserait de se lire comme un dessin. */
@media (max-width: 700px) {
  .maquis-bed { background-size: 820px 820px; }
}

/* Le socle pose un aplat opaque sur chaque section : il masquerait le lit.
   Restreint à l'accueil, les autres pages ne changent pas d'un pixel. */
body[data-page="accueil"] .home-section { background-color: transparent; }

/* Même raison pour le bloc de tête des six pages qui en portent un : son
   aplat d'ivoire cachait le dessin sur toute la hauteur du titre, c'est-à-dire
   exactement là où l'œil arrive. Le filet du bas, lui, reste. Les surfaces
   qui doivent se détacher du fond — cartes, encadrés creusés — gardent leur
   aplat : ce sont des objets posés dessus, pas du fond. */
.page-head { background-color: transparent; }
/* Le fond creusé reste perceptible, mais laisse passer la texture. */
body[data-page="accueil"] .home-section--sunken {
  background-color: rgba(235, 229, 216, 0.72);
}
/* Les deux dernières sections prennent le charbon du pied de page, un cran
   plus sombre que le charbon courant : « Votre séjour », la clôture et le
   pied de page ne forment plus qu'un seul aplat, et la page se referme sans
   marche. Elles gardent un fond PLEIN — rien ne doit les traverser. */
body[data-page="accueil"] .home-final { background-color: var(--c-charcoal-deep); }

/* ==========================================================================
   1 bis. Rythme vertical
   L'accueil respirait sur 288 px de vide entre deux sections — deux fois
   `--home-space`, à 144 px sur un écran de 1 440. C'est beaucoup pour une
   page qui a perdu deux de ses sections : le vide se lisait comme un manque
   plutôt que comme une respiration.

   Une seule valeur est reprise, celle du socle, et elle continue de gouverner
   TOUS les écarts entre sections : aucun réglage au cas par cas n'est
   introduit ici. Les paliers de `responsive.css` sont absorbés par le
   `clamp()`, qui couvre la même plage de façon continue.

   Portée à l'accueil seulement : les huit autres pages gardent leur rythme
   jusqu'à ce qu'elles soient reprises à leur tour.
   ========================================================================== */

body[data-page="accueil"] {
  /* 1 440 px : 81 px, soit 162 px entre deux sections au lieu de 288. */
  --home-space: clamp(64px, 5.6vw, 96px);
}

/* La première section après le hero garde un cran de plus, mais un seul :
   six écrans et demi de vidéo viennent de défiler, l'œil a besoin d'une pause avant
   de se remettre à lire. */
body[data-page="accueil"] .home-section--open {
  padding-block: clamp(72px, 6vw, 104px);
}

/* EXCEPTION, et la seule de la page. L'introduction et la bande des espaces
   se suivent de trop près dans le propos pour être séparées par l'écart
   commun : le chapô annonce la maison, la bande la montre. 40 + 40 = 80 px
   entre les deux, contre 167 ailleurs.

   Deux sélecteurs d'identifiant plutôt qu'une valeur globale : l'écart entre
   toutes les autres sections continue d'être gouverné par `--home-space`, et
   rien d'autre n'est touché. */
body[data-page="accueil"] #introduction { padding-bottom: 25px; }
body[data-page="accueil"] #espaces { padding-top: 25px; }

/* Entre la liste chiffrée et la galerie, l'écart commun moins cinquante
   pixels. Exprimé en soustraction et non en valeur fixe : il continue de
   suivre `--home-space`, donc de se resserrer sur les écrans étroits. */
body[data-page="accueil"] #espaces { padding-bottom: calc(var(--home-space) - 25px); }
body[data-page="accueil"] #apercu { padding-top: calc(var(--home-space) - 25px); }

/* Les têtes de bloc suivent le même mouvement, sinon le resserrement des
   sections ferait ressortir les respirations internes. */
body[data-page="accueil"] .home-gallery__head {
  margin-bottom: clamp(28px, 2.6vw, 40px);
}
body[data-page="accueil"] .home-intro__aside,
body[data-page="accueil"] .home-intro__gallery {
  margin-top: var(--sp-6);
}

/* ==========================================================================
   2. Titre du hero
   Le titre traverse plus de six écrans de vidéo, dont des plans très
   clairs. Deux dispositifs, additifs et tous deux discrets.
   ========================================================================== */

/* Halo central : un assombrissement radial très étalé, centré sur la
   signature. Il ne touche ni les bords ni le haut de l'image, donc il ne se
   lit pas comme un voile. */
.hero-scrub__stage::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  background: radial-gradient(58% 44% at 50% 50%,
    rgba(10, 14, 12, 0.42) 0%,
    rgba(10, 14, 12, 0.26) 42%,
    rgba(10, 14, 12, 0.08) 68%,
    rgba(10, 14, 12, 0) 100%);
}

/* Ombre en cinq plans. Les trois dernières étaient déjà là : un contour serré
   qui détache les lettres des détails fins, une ombre moyenne qui porte le
   contraste, une très large qui assoit le mot sur l'image.

   LES DEUX PREMIÈRES SONT NOUVELLES, et elles règlent un problème précis.
   Le titre est posé sur treize secondes de plan de drone dont la luminosité
   varie du simple au double — de rgb(68) à rgb(138) sous les lettres, relevé
   image par image. Son contraste oscillait donc entre 7,1 et 11,9 : l'ivoire
   paraissait se ternir dès les premiers pixels de défilement, sans que rien
   n'ait changé dans la feuille de style.

   Le halo radial de la scène ne pouvait pas y remédier : assombrir le fond
   d'un montant fixe ne réduit qu'à peine un ÉCART de contraste, qui est un
   rapport. Mesuré : le porter de 0,42 à 0,55 ne ramenait la variation que de
   66 % à 55 %, en assombrissant l'image pour tout le monde.

   Deux ombres SANS DÉCALAGE, serrées contre la lettre, font ce que le halo ne
   peut pas : elles rendent le voisinage immédiat de chaque glyphe sombre quoi
   qu'il y ait derrière. La variation perçue tombe à 17 %, et le réglage ne
   dépend plus du tout de la vidéo — elle peut être remplacée sans que rien ne
   soit à reprendre ici. */
.hero--scrub .hero-intro__title {
  text-shadow:
    0 0 3px rgba(10, 14, 12, 0.50),
    0 0 12px rgba(10, 14, 12, 0.42),
    0 1px 2px rgba(10, 14, 12, 0.34),
    0 4px 16px rgba(10, 14, 12, 0.42),
    0 14px 56px rgba(10, 14, 12, 0.34);
}

/* Brillance : un reflet qui traverse les lettres une seule fois, à l'arrivée.
   Il est porté par un calque en `background-clip: text` superposé au titre,
   jamais par le titre lui-même — le texte reste sélectionnable et lisible si
   l'effet ne se joue pas. */
.hero--scrub .hero-intro__title {
  position: relative;
  isolation: isolate;
}
.hero--scrub .hero-intro__title::after {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(105deg,
    rgba(255, 255, 255, 0) 38%,
    rgba(255, 255, 255, 0.92) 50%,
    rgba(255, 255, 255, 0) 62%);
  background-size: 280% 100%;
  background-position: 180% 0;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  opacity: 0;
  /* `text-shadow` EST UNE PROPRIÉTÉ HÉRITÉE, et elle se peint même sous un
     texte transparent. Sans cette ligne, le calque reprenait les cinq ombres
     sombres du titre et les reposait exactement sur les lettres blanches
     pendant les 2,2 s de l'animation : la signature virait au gris foncé, puis
     redevenait blanche. C'est ce que voyait le visiteur — et cela n'avait rien
     à voir avec le défilement, seulement avec le moment où la brillance passe.

     Le calque ne doit porter QUE le reflet. Rien d'autre. */
  text-shadow: none;
}
.hero--scrub.is-shine .hero-intro__title::after {
  animation: casa-shine 2200ms cubic-bezier(0.33, 0, 0.2, 1) 1 both;
}
@keyframes casa-shine {
  0%   { opacity: 0;    background-position: 180% 0; }
  18%  { opacity: 0.85; }
  82%  { opacity: 0.85; }
  100% { opacity: 0;    background-position: -80% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .hero--scrub .hero-intro__title::after { animation: none !important; content: none; }
}

/* ==========================================================================
   3. Carrousel de l'introduction
   Il occupe les colonnes laissées vides à gauche du chapô. Fondu enchaîné,
   jamais de glissement : la composition de la page ne doit pas bouger.
   ========================================================================== */

/* Le chapô occupe les colonnes 8 à 12 et laisse les sept premières vides sous
   le titre : c'est exactement la place du carrousel. Les deux sont forcés sur
   la MÊME rangée — sans cela, le placement automatique de la grille pousse le
   second à la rangée suivante, le curseur ayant déjà dépassé la colonne 8.

   Réservé à la grille de douze colonnes. En dessous, tout s'empile dans
   l'ordre du document, comme le reste de la page. */
@media (min-width: 1200px) {
  .home-intro__gallery {
    grid-column: 1 / span 6;
    grid-row: 2;
    margin-top: var(--sp-7);
  }
  body[data-page="accueil"] .home-intro__aside { grid-row: 2; }
}
.home-intro__gallery { margin-top: var(--sp-6); }

.carousel {
  position: relative;
  isolation: isolate;
}

.carousel__frame {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  background-color: var(--bg-sunken);
}

.carousel__slide {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  /* Aucune transition sur l'inactif : la diapositive qui sort doit s'effacer
     à la même vitesse que l'entrante apparaît, sans à-coup au changement. */
  transition: opacity 1700ms var(--ease-out, ease);
}
.carousel__slide.is-active { opacity: 1; }

.carousel__slide img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--focal, 50% 50%);
  transform: scale(1.03);
  transition: transform 400ms linear;
}
/* Dérive très lente pendant que la photographie est affichée : le cadre
   respire sans qu'on puisse dire à quel moment il bouge. */
.carousel__slide.is-active img {
  transform: scale(1.08);
  transition: transform 8400ms linear;
}

/* --- Commandes --------------------------------------------------------------
   Elles ne recouvrent jamais le sujet : elles vivent sur le bord bas.

   VISIBLES PAR DÉFAUT, et effacées seulement là où le survol existe pour les
   rappeler. C'est l'inverse du réglage d'origine, et le sens compte.

   Avant, elles étaient effacées d'office, puis rendues visibles par une
   exception « moins de 768px ». Un téléphone COUCHÉ fait 812px de large :
   l'exception ne s'appliquait plus, les commandes retombaient à `opacity: 0`
   et n'attendaient qu'un survol qui ne vient jamais sur un écran tactile.
   Elles étaient purement introuvables.

   Énumérer les cas où il faut les montrer, c'est en oublier un. Énumérer le
   seul cas où on peut se permettre de les cacher — un vrai pointeur ET un
   grand écran — ne laisse aucun appareil dehors : partout ailleurs, elles
   sont là. Des commandes visibles ne sont jamais un défaut ; des commandes
   introuvables, si. */
.carousel__ui {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 18px 20px;
  opacity: 1;
  transform: none;
  transition: opacity 420ms var(--ease), transform 420ms var(--ease);
  background: linear-gradient(to top,
    rgba(10, 14, 12, 0.42) 0%, rgba(10, 14, 12, 0) 100%);
}

@media (hover: hover) and (min-width: 768px) {
  .carousel__ui {
    opacity: 0;
    transform: translate3d(0, 6px, 0);
  }
  .carousel:hover .carousel__ui,
  .carousel:focus-within .carousel__ui {
    opacity: 1;
    transform: none;
  }
}

.carousel__dots {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-right: auto;
}
.carousel__dot {
  width: 26px;
  height: 2px;
  padding: 10px 0;
  border: 0;
  background: none;
  cursor: pointer;
  position: relative;
}
.carousel__dot::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 2px;
  transform: translateY(-50%) scaleX(0.42);
  transform-origin: left center;
  background-color: rgba(255, 255, 255, 0.55);
  transition: transform 620ms var(--ease-out), background-color 300ms var(--ease);
}
.carousel__dot[aria-selected="true"]::before {
  transform: translateY(-50%) scaleX(1);
  background-color: #fff;
}
.carousel__dot:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* La cible du doigt, sans toucher au dessin. Le trait reste un trait de deux
   pixels ; c'est une surface invisible qui reçoit l'appui. Mesuré avant :
   26 × 20 px, soit moins de la moitié des 44 px recommandés. Après : 34 × 44.
   Les 4px latéraux comblent exactement la gouttière de 8px entre deux puces,
   sans jamais mordre sur la voisine. */
.carousel__dot::after {
  content: "";
  position: absolute;
  inset: -12px -4px;
}

/* --- Curseur ----------------------------------------------------------------
   Le cadre est la commande : moitié gauche pour reculer, moitié droite pour
   avancer. Le pointeur système est remplacé par un disque portant une flèche,
   qui dit à tout instant ce que le clic va faire. Les deux boutons de
   navigation ont donc été retirés : ils répétaient un geste désormais porté
   par toute la surface.

   Réservé au pointeur fin. Sur écran tactile il n'y a pas de curseur à
   remplacer, et les pastilles restent visibles en permanence. */
@media (pointer: fine) {
  .carousel__frame { cursor: none; }
}

.carousel__cursor {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 3;
  pointer-events: none;
  /* Position pure, SANS transition : le disque doit coller au pointeur. */
  transform: translate3d(var(--cx, 0px), var(--cy, 0px), 0);
}

.carousel__cursor-disc {
  position: absolute;
  top: -42px;
  left: -42px;
  width: 84px;
  height: 84px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background-color: rgba(255, 255, 255, 0.94);
  color: var(--c-charcoal);
  opacity: 0;
  /* Échelle et rotation ici, et ici seulement : confondues avec la position,
     elles auraient fait traîner le disque derrière la souris. */
  transform: scale(0.55) rotate(0deg);
  transition: opacity 320ms var(--ease), transform 460ms cubic-bezier(0.22, 1, 0.36, 1);
}
.carousel__cursor.is-visible .carousel__cursor-disc {
  opacity: 1;
  transform: scale(1) rotate(0deg);
}
/* Moitié gauche : le disque pivote d'un demi-tour. La flèche change de sens
   sans jamais disparaître, ce qui rend le passage d'une moitié à l'autre
   lisible. */
.carousel__cursor.is-left .carousel__cursor-disc { transform: scale(1) rotate(180deg); }
.carousel__cursor.is-press .carousel__cursor-disc { transform: scale(0.88) rotate(0deg); }
.carousel__cursor.is-press.is-left .carousel__cursor-disc { transform: scale(0.88) rotate(180deg); }

.carousel__frame:focus-visible { outline: 2px solid var(--c-charcoal); outline-offset: 3px; }

@media (prefers-reduced-motion: reduce) {
  .carousel__cursor-disc { transition: opacity 320ms linear; }
}

/* ==========================================================================
   4. Bande des espaces
   Sept cartes qui glissent latéralement pendant que la page descend. Même
   principe que le hero : une piste haute, une scène collante, un contenu
   déplacé en fonction de la progression.

   La hauteur de la piste est posée par le script, et seulement quand le
   glissement est réellement actif. Par défaut — sans JavaScript, sur écran
   tactile, sous `prefers-reduced-motion` — la bande est un simple ruban qui
   se fait défiler au doigt, avec accrochage. Rien n'est perdu.
   ========================================================================== */

.spaces {
  /* La bande a sa propre respiration : le titre reste dans la gouttière, le
     ruban va jusqu'au bord. */
  padding-inline: 0;
}
.spaces__head {
  padding-inline: var(--home-pad);
  margin-bottom: clamp(26px, 2.8vw, 44px);
}

/* --- Repli : ruban à faire défiler soi-même --------------------------------
   C'est l'état par défaut, celui qu'on obtient sans JavaScript, sur écran
   tactile et sous `prefers-reduced-motion`. Rien n'y est conditionné à une
   animation : les sept espaces sont là, on les parcourt au doigt. */
.spaces__scroller {
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  overscroll-behavior-inline: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.spaces__scroller::-webkit-scrollbar { display: none; }

.spaces__rail {
  display: flex;
  gap: clamp(16px, 1.8vw, 28px);
  margin: 0;
  padding: 0 var(--home-pad);
  list-style: none;
  width: max-content;
}

/* --- Glissement piloté au défilement ---------------------------------------
   Posé par le script, et seulement s'il a un sens. La scène cesse alors de
   défiler pour son propre compte : la position du rail est calculée. */
.spaces.is-driven .spaces__track {
  position: relative;
  height: var(--spaces-track-h, auto);
}
.spaces.is-driven .spaces__stage {
  position: sticky;
  top: 0;
  height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(28px, 4vh, 56px);
  padding-top: var(--header-h);
  overflow: hidden;
}
.spaces.is-driven .spaces__head {
  margin-bottom: 0;
  flex: 0 0 auto;
}
.spaces.is-driven .spaces__scroller {
  overflow: hidden;
  scroll-snap-type: none;
}
.spaces.is-driven .spaces__rail {
  transform: translate3d(var(--spaces-x, 0px), 0, 0);
  will-change: transform;
}

/* La carte est dimensionnée PAR SA HAUTEUR en mode piloté : la scène fait un
   écran, titre compris, et une carte calculée sur la largeur finirait par
   déborder en bas sur un portable en paysage. Le 0.8 tient le 4/5. */
.spaces.is-driven .spaces__card { width: calc(min(52vh, 520px) * 0.8); }
.spaces.is-driven .spaces__media {
  aspect-ratio: auto;
  height: min(52vh, 520px);
}

/* --- Une carte -------------------------------------------------------------- */
.spaces__card {
  flex: 0 0 auto;
  width: clamp(240px, 30vw, 420px);
  scroll-snap-align: center;
}
.spaces__media {
  position: relative;
  display: block;
  margin: 0 0 20px;
  overflow: hidden;
  aspect-ratio: 4 / 5;
  background-color: var(--bg-sunken);
}
.spaces__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--focal, 50% 50%);
  transform: scale(1.02);
  transition: transform 1100ms cubic-bezier(0.22, 1, 0.36, 1);
}
.spaces__card:hover .spaces__media img { transform: scale(1.05); }

/* Voile qui se retire au survol : la photographie gagne en lumière quand on
   la regarde, au lieu d'un simple agrandissement. */
.spaces__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top,
    rgba(10, 14, 12, 0.34) 0%, rgba(10, 14, 12, 0) 52%);
  opacity: 1;
  transition: opacity 700ms var(--ease);
}
.spaces__card:hover .spaces__media::after { opacity: 0.35; }

.spaces__num {
  display: block;
  margin-bottom: 8px;
  color: var(--text-muted, var(--c-charcoal-55));
  font-family: var(--font-azimut, var(--font-hero));
  font-size: 11px;
  font-weight: var(--fw-medium);
  letter-spacing: 1.4px;
}
.spaces__name {
  margin: 0 0 6px;
  font-family: var(--font-hero);
  font-size: clamp(19px, 1.55vw, 25px);
  font-weight: var(--fw-light);
  line-height: 1.14;
  letter-spacing: -0.5px;
  color: var(--text-strong);
}
.spaces__detail {
  margin: 0;
  max-width: 30ch;
  color: var(--text-body);
  font-size: 14px;
  line-height: 1.5;
}

/* Invitation à glisser, affichée seulement dans le repli : en mode piloté le
   geste est le défilement habituel, il n'y a rien à expliquer. */
.spaces__hint {
  margin: 22px var(--home-pad) 0;
  color: var(--text-muted, var(--c-charcoal-55));
  font-family: var(--font-azimut, var(--font-hero));
  font-size: 11px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
}
.spaces.is-driven .spaces__hint { display: none; }

/* La liste chiffrée reprend la gouttière après le ruban. */
.spaces__facts { padding-inline: var(--home-pad); margin-top: clamp(44px, 4vw, 72px); }

/* ==========================================================================
   4 bis. La liste chiffrée, en grand
   Chaque équipement tient une ligne pleine largeur. C'est la liste qui porte
   la section, elle n'est plus un bloc posé à côté d'un titre.
   ========================================================================== */

.facts-xl__head {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  max-width: 46rem;
  margin-bottom: clamp(30px, 3vw, 52px);
}

.facts-xl__list {
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--rule);
}

.facts-xl__row {
  display: flex;
  /* Alignement sur la ligne de base, et non en haut : le numéro se pose sur
     la même ligne que la première ligne du libellé, quelle que soit la taille
     de l'un et de l'autre. C'est le seul alignement qui tienne quand les deux
     corps sont aussi éloignés. */
  align-items: baseline;
  gap: clamp(16px, 2.6vw, 48px);
  padding-block: clamp(18px, 2.1vw, 32px);
  border-bottom: 1px solid var(--rule);
}

.facts-xl__num {
  flex: 0 0 auto;
  width: 2.2em;
  font-family: var(--font-azimut, var(--font-hero));
  font-size: 11px;
  font-weight: var(--fw-medium);
  line-height: 14px;
  letter-spacing: 1.2px;
  color: var(--text-muted);
  font-variant-numeric: lining-nums tabular-nums;
}

.facts-xl__name {
  font-family: var(--font-hero);
  /* Une seule variable pour tout le composant : la page « La villa » en aligne
     vingt-quatre là où l'accueil en aligne huit, et le même corps y aurait
     donné une section de trois mille cinq cents pixels. Seule la taille change
     d'une page à l'autre ; tout le reste est commun, et toutes les lignes d'une
     même liste restent au même corps. */
  font-size: var(--facts-xl-size, clamp(26px, 4.6vw, 68px));
  font-weight: var(--fw-light);
  line-height: 1.04;
  letter-spacing: -1.6px;
  color: var(--text-strong);
  text-wrap: balance;
}

/* Découverte par la gauche. Comme le masque, elle est posée sur les ENFANTS :
   `clip-path` sur la ligne elle-même annulerait son aire visible, et
   l'observateur de `main.js` ne la révélerait jamais.

   Aucun décalage n'est ajouté d'une ligne à l'autre : chacune entre à son
   propre moment dans le champ, ce qui produit déjà la cascade — et une
   cascade subie est plus juste qu'une cascade programmée. */
.reveal--wipe > * {
  clip-path: inset(0 102% 0 0);
  transition: clip-path 1300ms cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal--wipe.is-in > * { clip-path: inset(0 0 0 0); }
/* Le libellé part un souffle après le numéro : la ligne se lit de gauche à
   droite au lieu de s'ouvrir d'un bloc. */
.reveal--wipe > .facts-xl__name { transition-delay: 90ms; }

/* Variante dense : beaucoup de lignes, donc un corps plus mesuré. */
.facts-xl--dense { --facts-xl-size: clamp(20px, 2.4vw, 38px); }
.facts-xl--dense .facts-xl__row { padding-block: clamp(12px, 1.3vw, 20px); }

/* Intitulé de groupe. Volontairement discret, au même registre que les
   numéros : il ordonne la liste, il ne rivalise pas avec elle. */
.facts-xl__group {
  margin: clamp(28px, 3vw, 48px) 0 clamp(10px, 1vw, 16px);
  font-family: var(--font-azimut, var(--font-hero));
  font-size: 11px;
  font-weight: var(--fw-medium);
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--text-muted);
}
.facts-xl__group:first-child { margin-top: 0; }

@media (max-width: 767.98px) {
  .facts-xl__name { letter-spacing: -0.8px; }
  .facts-xl__num { width: 1.9em; }
}

@media (prefers-reduced-motion: reduce) {
  .reveal--wipe > * { clip-path: none; transition: none; }
}

/* ==========================================================================
   5. Révélations et survols
   Le socle pose une entrée courte, de six pixels. Elle convient au texte
   courant ; elle ne porte pas une photographie pleine largeur. Deux variantes
   s'ajoutent, réservées à l'accueil, et aucune ne remplace `.reveal` : elles
   s'y superposent.
   ========================================================================== */

/* Entrée ample, pour les blocs de tête et les médias.
   `--dur-reveal` vaut 300 ms, ce qui convient à six pixels de texte mais pas
   à vingt-six : la variante porte donc sa propre durée. `main.js` pose
   `is-settled` à la fin de la transition de `transform`, quelle qu'en soit la
   durée — la mécanique du socle reste donc juste. */
.reveal--rise {
  transform: translate3d(0, 12px, 0);
  transition: opacity 1200ms cubic-bezier(0.16, 1, 0.3, 1),
              transform 1400ms cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal--rise.is-in { transform: none; }

/* Le média se découvre par le bas, comme un store qu'on lève, pendant que
   l'image se remet à l'échelle. Deux mouvements de sens contraire : c'est ce
   qui donne la sensation de profondeur.

   Le masque est posé sur L'ENFANT, jamais sur l'élément qui porte `.reveal`,
   et ce n'est pas un détail de style : `clip-path: inset(0 0 100%)` réduit à
   zéro l'aire visible de l'élément, et l'`IntersectionObserver` de `main.js`
   la mesure. Posé sur l'élément observé, le masque empêchait donc sa propre
   révélation — il restait fermé indéfiniment. Sur l'enfant, la boîte observée
   garde sa taille pleine ; l'opacité, elle, n'entre pas dans le calcul.

   Effet de bord heureux : `is-settled`, qui coupe les transitions de
   l'élément à 300 ms, ne touche pas celles de l'enfant. L'ouverture peut donc
   durer 1,1 s sans qu'on ait rien à défaire au socle. */
.reveal--mask > * {
  clip-path: inset(0 0 100% 0);
  transition: clip-path 1500ms cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal--mask.is-in > * { clip-path: inset(0 0 0 0); }
.reveal--mask img {
  transform: scale(1.05);
  transition: transform 1900ms cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal--mask.is-in img { transform: scale(1); }

/* Parallaxe : le script pose `--shift`, en pixels. L'amplitude est bornée
   dans le script, pas ici.

   `--media-scale` est repris tel quel : c'est la variable par laquelle le
   socle joue l'agrandissement d'entrée et celui du focus. L'écraser aurait
   supprimé les deux. Le facteur 1.09 n'est là que pour donner au déplacement
   de quoi mordre sans découvrir le bord du cadre. */
[data-parallax] > img {
  transform: translate3d(0, var(--shift, 0px), 0)
             scale(calc(var(--media-scale, 1) * 1.09));
}

/* L'entrée par défaut du socle dure 300 ms, ce qui est franc. Sur l'accueil
   elle est allongée et réamortie : le contenu se pose au lieu d'apparaître.
   L'amplitude, elle, ne bouge pas — six pixels restent six pixels. */
body[data-page="accueil"] .home-section .reveal:not(.reveal--rise) {
  /* Longhands, et surtout PAS le raccourci : `transition` remettrait
     `transition-delay` à zéro, et avec lui le décalage que `.home-seq` pose
     sur les deuxième, troisième et quatrième enfants d'un bloc. La cascade
     interne des blocs éditoriaux tient à ces trois lignes de `pages.css`.

     `:not(.reveal--rise)` pour la même raison : sans lui, cette règle plus
     spécifique écraserait la durée propre de la variante ample. */
  transition-duration: 1100ms;
  transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}

/* Le libellé du lien glisse d'un cheveu quand la flèche part : les deux
   partent ensemble, le mot ne reste pas en arrière. */
.home-link span {
  display: inline-block;
  transition: transform 420ms cubic-bezier(0.22, 1, 0.36, 1);
}
.home-link:hover span,
.home-link:focus-visible span { transform: translateX(2px); }

@media (prefers-reduced-motion: reduce) {
  .reveal--rise { transform: none; transition: none; }
  .reveal--mask > * { clip-path: none; transition: none; }
  .reveal--mask img,
  [data-parallax] > img { transform: none; transition: none; }
  .carousel__slide,
  .carousel__slide img,
  .carousel__ui,
  .home-link span,
  .spaces__media img,
  .spaces__media::after { transition: none; }
  .carousel__slide.is-active img { transform: scale(1.04); }
}

/* ==========================================================================
   6. Adaptations
   ========================================================================== */

@media (max-width: 1199.98px) {
  .carousel__frame { aspect-ratio: 3 / 2; }
}

@media (max-width: 767.98px) {
  .carousel__frame { aspect-ratio: 4 / 3; }
  .spaces__card { width: 74vw; }
  .spaces__name { font-size: 20px; }
  /* Un cran sous le réglage général, mais un seul : à 820 px de tuile le
     trait ne fait déjà plus que 0,79 px, il est plus pâle de lui-même. */
  .maquis-bed { opacity: 0.06; }
}

/* ==========================================================================
   8. Clôture
   Le vol du hero repris à l'envers, puis résorbé vers un point.

   Deux temps sur une seule piste : la vidéo remonte de la quatrième seconde
   vers la première image, puis le cadre se réduit vers son centre jusqu'à
   disparaître. Le pied de page suit immédiatement.

   Par défaut la piste vaut UN écran : sans JavaScript, sur écran tactile,
   sous `prefers-reduced-motion` ou avec l'économiseur de données, la section
   est une photographie d'un écran. Le script ne l'allonge que s'il pilote
   réellement.
   ========================================================================== */

@supports not (height: 100svh) {
  .outro { --hero-screen: 100vh; }
}

.outro {
  /* Le charbon du pied de page, qu'elle précède, et celui de la bande sombre
     qui la précède : les trois ne forment qu'un seul aplat et la page se
     referme sans marche. C'est le filet doré du pied de page qui marque la
     fin, pas un changement de fond.

     La valeur est écrite ICI, et non dans une règle plus haut dans le fichier :
     à spécificité égale c'est l'ordre qui tranche, et cette déclaration-là
     l'emportait. */
  background-color: var(--c-charcoal-deep);
}

.outro__track {
  position: relative;
  height: calc(var(--hero-screen, 100svh) * var(--outro-screens, 1));
}

.outro__stage {
  position: sticky;
  top: 0;
  height: var(--hero-screen, 100svh);
  overflow: hidden;
  isolation: isolate;
  background-color: var(--c-charcoal-deep);
}

/* C'est cette boîte, et elle seule, que le script réduit. L'image et la vidéo
   sont ses enfants : elles suivent la transformation sans qu'on ait à les
   toucher, et le recadrage `cover` reste juste à toutes les échelles. */
.outro__shell {
  position: absolute;
  inset: 0;
  overflow: hidden;
  transform: scale(var(--outro-scale, 1));
  opacity: var(--outro-fade, 1);
  /* Un RECTANGLE qui se referme, et rien d'autre. `inset()` en pourcentages
     rentre les quatre bords vers le centre : la forme garde ses angles droits
     du début à la fin, et son rapport de côtés ne bouge pas — deux moitiés de
     hauteur et deux moitiés de largeur se réduisent au même rythme relatif.

     Ni `circle()`, ni `border-radius` : l'un donnait une ellipse sur un cadre
     de 1 440 par 860, l'autre arrondit les coins. C'est précisément ce qu'on
     ne veut pas. */
  clip-path: inset(var(--outro-inset-y, 0%) var(--outro-inset-x, 0%));
  will-change: transform, opacity, clip-path;
}

.outro__shell img,
.outro__shell video {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--focal, 50% 50%);
}

.outro__shell video {
  z-index: 1;
  opacity: 0;
  transition: opacity 700ms var(--ease, ease);
}
.outro.is-video-ready .outro__shell video { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .outro__shell video { transition: none; }
}

/* ==========================================================================
   9. Zoom au survol
   Un seul geste sur tout le site, et le même partout : la photographie
   s'agrandit d'un peu. Pas d'inclinaison, pas de lumière qui suit la main,
   pas de voisines qui s'effacent — ce qui a été essayé puis retiré.

   La valeur est unique — `--zoom-hover` — pour qu'un aperçu de l'accueil, une
   vignette de galerie et une carte d'espace bougent exactement du même
   montant. Le socle en avait trois : 1,025 sur `.home-media`, 1,015 sur
   `.gallery-item`, 1,05 sur les cartes d'espace.

   Le passage par `--media-scale` n'est pas un détour : c'est la variable par
   laquelle `pages.css` joue la détente de l'image à l'apparition. L'écraser
   par un `transform` direct aurait supprimé cette détente.
   ========================================================================== */

:root { --zoom-hover: 1.04; }

/* Durée longue et courbe très amortie : c'est ce qui distingue un zoom posé
   d'un zoom qui claque. Le socle montait à 250 ms sur les vignettes. */
.home-media > img,
.gallery-item img,
.spaces__media img,
a .frame > img {
  transition: transform 700ms cubic-bezier(0.22, 1, 0.36, 1);
}

a.home-media:hover > img,
a.home-media:focus-visible > img { --media-scale: var(--zoom-hover); }

.gallery-item:hover img,
.gallery-item:focus-visible img { transform: scale(var(--zoom-hover)); }

.spaces__card:hover .spaces__media img { transform: scale(var(--zoom-hover)); }

a:hover .frame > img,
a:focus-visible .frame > img { transform: scale(var(--zoom-hover)); }

@media (pointer: coarse), (prefers-reduced-motion: reduce) {
  .home-media > img,
  .gallery-item img,
  .spaces__media img,
  a .frame > img { transition: none; }
  .gallery-item:hover img,
  .spaces__card:hover .spaces__media img,
  a:hover .frame > img { transform: none; }
  a.home-media:hover > img { --media-scale: 1; }
}

/* ==========================================================================
   10. Le reste du site
   La même grammaire que l'accueil, portée aux huit autres pages. Rien n'y est
   réinventé : ce sont les mêmes variantes de révélation, le même
   ralentissement, le même lit de maquis, la même fin de page.

   Ce qui n'est PAS porté, et pour cause :
     - le rythme vertical. `--section-y` donne déjà 120 px entre deux sections
       sur ces pages, contre 288 sur l'accueil avant resserrement. Il n'y a
       rien à y reprendre.
     - les composants propres à l'accueil (carrousel, bande d'espaces, liste
       chiffrée, clôture). Ils répondent à des contenus qui n'existent que là.
   ========================================================================== */

/* --- Révélations ------------------------------------------------------------
   Même parti qu'à l'accueil : peu d'amplitude, beaucoup de durée, courbe très
   amortie. L'amplitude de base reste celle du socle — six pixels sur ces
   pages —, seule la durée change. Les titres, eux, prennent `reveal--rise`.

   La règle de l'accueil est conservée À PART, plus spécifique : `pages.css` y
   impose 800 ms depuis un sélecteur à trois classes, qu'une règle générale ne
   pourrait pas atteindre. */
main .reveal:not(.reveal--rise) {
  transition-duration: 1100ms;
  transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}

/* --- Entrée des pages d'action -----------------------------------------------
   `--intro-pt` vaut `header-h + 4,5vw`, et les 84 px de l'en-tête y sont
   comptés une SECONDE fois : le `body` les dégage déjà pour passer sous la
   barre fixe. Résultat, 160 px de vide entre le filet de l'en-tête et le
   surtitre, sur des pages dont le premier geste attendu est une action.

   Deux pages seulement sont reprises ici — celles où l'on vient faire quelque
   chose. Sont volontairement laissées de côté :
     - la galerie, dont le bloc de tête est centré verticalement sur une
       photographie : son rythme a été réglé à part, et le déséquilibrer
       reviendrait à décaler l'image ;
     - les trois pages légales, où la respiration ne coûte rien à personne.

   On ne touche pas au jeton lui-même : il sert aussi à ces quatre pages-là. */
body[data-page="contact"] .page-head,
body[data-page="tarifs"] .page-head {
  padding-top: clamp(1.25rem, 0.4rem + 1.1vw, 2rem);
}

/* --- Fin de page ------------------------------------------------------------
   La bande de demande qui ferme presque toutes les pages prend le charbon du
   pied de page, comme « Votre séjour » et la clôture sur l'accueil : la page
   se referme d'un seul aplat, sans marche. */
.request-band { background-color: var(--c-charcoal-deep); }

/* --- Titres posés sur une photographie --------------------------------------
   Les heros de « La villa » et « Autour » portent leur H1 sur une image. Ils
   reçoivent l'ombre en trois plans du hero de l'accueil : un contour serré qui
   détache les lettres des détails fins, une ombre moyenne qui porte le
   contraste, une très large qui assoit le mot. Le voile `.hero__scrim`
   existant n'est pas touché — il travaille sur le bas de l'image, l'ombre
   travaille sur les lettres. */
.hero--page h1,
.hero--page .hero__intro {
  text-shadow:
    0 1px 2px rgba(10, 14, 12, 0.34),
    0 4px 16px rgba(10, 14, 12, 0.38),
    0 14px 56px rgba(10, 14, 12, 0.30);
}

/* --- Heros de page ----------------------------------------------------------
   La photographie dérive verticalement pendant qu'on descend, comme les
   compositions de l'accueil. Le facteur d'échelle est repris tel quel de la
   règle générale, qui multiplie `--media-scale` : ici cette variable n'est pas
   posée, elle vaut donc 1, et seul le débord de 9 % subsiste — assez pour que
   le déplacement ne découvre jamais le bord du cadre. */
.hero__media[data-parallax] { overflow: hidden; }

/* --- Liens textuels ---------------------------------------------------------
   `.link-line` est au reste du site ce que `.home-link` est à l'accueil. Le
   libellé part avec sa flèche au lieu de rester en arrière : les deux
   s'avancent ensemble, d'un cheveu. */
.link-line { transition: border-bottom-color var(--dur) var(--ease); }
.link-line > :not(.arrow) {
  display: inline-block;
  transition: transform 420ms cubic-bezier(0.22, 1, 0.36, 1);
}
.link-line:hover > :not(.arrow),
.link-line:focus-visible > :not(.arrow) { transform: translateX(2px); }

@media (prefers-reduced-motion: reduce) {
  .link-line > :not(.arrow) { transition: none; }
}

/* ==========================================================================
   11. Hero de « La villa »
   Quatre photographies enchaînées en fondu au défilement, puis la maison qui
   se dessine au trait par-dessus la dernière, assombrie. Le dessin achevé,
   tout s'efface et la visite commence.

   Même charpente que le hero de l'accueil et que la clôture : une piste haute
   donne la course, une scène collante d'un écran reste à l'écran, et tout ce
   qui bouge est une variable posée par le script.

   Par défaut la piste vaut UN écran. Sans JavaScript, sur écran tactile ou
   sous `prefers-reduced-motion`, la section est un hero classique : la
   première photographie, le titre, le lien. Le dessin n'est jamais montré et
   les trois autres images ne sont jamais demandées.
   ========================================================================== */

@supports not (height: 100svh) {
  .lvhero { --hero-screen: 100vh; }
}

.lvhero {
  /* `.hero` est une boîte flexible d'une hauteur bornée qui coupe son
     débordement. Les trois sont à défaire : un ancêtre qui coupe le
     débordement désactive silencieusement `position: sticky`. */
  display: block;
  min-height: 0;
  height: auto;
  overflow: visible;
}
/* Le voile supérieur de `.hero` défilerait avec la section : il est repris par
   la scène, qui elle reste fixe. */
.lvhero::before { content: none; }

.lvhero__track {
  position: relative;
  height: calc(var(--hero-screen, 100svh) * var(--lvhero-screens, 1));
}

.lvhero__stage {
  position: sticky;
  top: 0;
  height: var(--hero-screen, 100svh);
  overflow: hidden;
  isolation: isolate;
  background-color: var(--c-charcoal);
}

/* Voile sous l'en-tête, repris de `.hero::before` et posé sur la scène. */
.lvhero__stage::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: calc(var(--header-h) + 2rem);
  z-index: 4;
  pointer-events: none;
  background: linear-gradient(to bottom,
    rgba(8, 14, 16, 0.64) 0%,
    rgba(8, 14, 16, 0.46) 40%,
    rgba(8, 14, 16, 0) 100%);
}

/* --- Les quatre panneaux ----------------------------------------------------
   Chacun porte SA photographie et SON texte : le libellé voyage avec l'image
   qu'il désigne, au lieu de changer à côté d'elle. Empilés, tous au même
   endroit, tous OPAQUES. Le panneau du dessus remonte de 0 à moins un écran ;
   celui qui apparaît dessous ne bouge pas. La séparation vient de la position,
   jamais d'un fondu ni d'un fond intermédiaire.

   PILE INVERSÉE. Le premier panneau est AU-DESSUS, le dernier tout au fond :
   c'est ce qui permet à celui du dessus de découvrir celui de derrière, déjà
   en place et plein écran. Les valeurs sont décroissantes pour six panneaux au
   plus ; le script n'y touche pas. */
.lvhero__media {
  position: absolute;
  inset: 0;
  z-index: 1;
}
.lvhero__panel {
  position: absolute;
  inset: 0;
  overflow: hidden;
  /* Chaque panneau est son propre contexte d'empilement : son halo ne peut
     donc jamais assombrir un autre panneau. */
  isolation: isolate;
  /* Le décalage est posé en PIXELS ENTIERS par le script, jamais en
     pourcentage : deux pourcentages arrondis indépendamment laissaient une
     ligne d'un pixel entre les panneaux, où l'on apercevait le fond. */
  transform: translate3d(0, var(--y, 0px), 0);
  will-change: transform;
}
.lvhero__panel:nth-child(1) { z-index: 6; }
.lvhero__panel:nth-child(2) { z-index: 5; }
.lvhero__panel:nth-child(3) { z-index: 4; }
.lvhero__panel:nth-child(4) { z-index: 3; }
.lvhero__panel:nth-child(5) { z-index: 2; }
.lvhero__panel:nth-child(6) { z-index: 1; }

.lvhero__shot {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--focal, 50% 50%);
}

/* Halo central, repris du hero de l'accueil. Il vit sur la SCÈNE, entre les
   photographies et les textes : c'est une couche de lisibilité, elle doit
   rester en place pendant que les panneaux défilent dessous. */
.lvhero__stage::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: var(--lvhero-sign, 1);
  background: radial-gradient(58% 44% at 50% 50%,
    rgba(10, 14, 12, 0.42) 0%,
    rgba(10, 14, 12, 0.26) 42%,
    rgba(10, 14, 12, 0.08) 68%,
    rgba(10, 14, 12, 0) 100%);
}

/* Les textes, eux aussi sur la scène : ils restent CENTRÉS À L'ÉCRAN pendant
   que les photographies remontent. Logés dans un panneau, ils remontaient avec
   lui et se retrouvaient hors d'axe pendant toute la transition. */
.lvhero__signs {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}
.lvhero .hero-intro__content {
  opacity: calc(var(--o, 1) * var(--lvhero-sign, 1));
}
/* Seul le premier texte est visible au départ, et il le reste si le script ne
   tourne pas : sans cette paire de règles, les quatre se superposaient en
   repli — sur écran tactile, où la séquence ne monte jamais, les quatre
   libellés s'écrivaient les uns sur les autres. */
.lvhero__signs > .hero-intro__content { --o: 0; }
.lvhero__signs > .hero-intro__content:first-child { --o: 1; }
.lvhero .hero-intro__title {
  /* `main p { max-width: var(--measure) }` — 544 px, la mesure de lecture du
     socle. Le libellé du hero est un `<p>` et non un `<h1>` (le titre de la
     page est ailleurs, caché) : il héritait donc de cette contrainte. Sa boîte
     restait à 544 px pendant que le texte en rendait 1 123, et le mot débordait
     vers la droite — centré dans sa boîte, décentré à l'écran. C'est la seule
     conséquence visible du choix de balise, et elle se répare ici. */
  max-width: none;

  /* Les libellés reprennent des titres de sections, plus longs que « Casa
     Paradisu » : le `nowrap` de l'accueil les aurait fait déborder de la
     fenêtre sur un écran étroit. Ils passent à la ligne, et l'équilibrage
     répartit les mots au lieu d'en laisser un seul en bas. */
  white-space: normal;
  text-wrap: balance;
  text-shadow:
    0 1px 2px rgba(10, 14, 12, 0.34),
    0 4px 16px rgba(10, 14, 12, 0.42),
    0 14px 56px rgba(10, 14, 12, 0.34);
}

/* Le voile de la phase de dessin vit sur la SCÈNE, au-dessus de tous les
   panneaux : c'est la seule couche qui ne doit pas défiler avec eux. */
.lvhero__veil {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-color: #0A0E0C;
  opacity: var(--lvhero-veil, 0);
}

/* --- La maison au trait -----------------------------------------------------
   Chaque tracé porte sa propre longueur en `stroke-dasharray`, mesurée par le
   script avec `getTotalLength()` : aucune valeur n'est écrite à la main, et
   modifier un `d=` ne demande rien d'autre. */
.lvhero__draw {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: grid;
  place-items: center;
  padding: max(var(--header-h), 8vh) var(--gutter) 8vh;
  pointer-events: none;
  opacity: var(--lvhero-draw, 0);
}
.lvhero__draw svg {
  width: min(80vw, 900px);
  height: auto;
}
.lvhero__draw path {
  fill: none;
  stroke: var(--c-ivory);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

/* Repli : aucun dessin, aucune course. La section redevient un hero d'un
   écran. Le `display: none` vaut aussi sans JavaScript — c'est la seule
   manière de garantir que le dessin ne se montre jamais figé. */
.lvhero:not(.is-driven) .lvhero__draw { display: none; }
.lvhero:not(.is-driven) .lvhero__veil { opacity: 0; }

/* Le socle borne le titre à la largeur de fenêtre sous 768 px : cette
   contrainte-là reste utile, elle est reposée après l'avoir levée. */
@media (max-width: 767.98px) {
  .lvhero .hero-intro__title { max-width: calc(100vw - 32px); }
}

@media (pointer: coarse), (prefers-reduced-motion: reduce) {
  .lvhero__draw { display: none; }
}

/* ==========================================================================
   12. Rythme vertical de « La villa »
   La page enchaîne sept sections courtes : l'écart commun du site y laissait
   des respirations plus longues que les blocs eux-mêmes. Une seule valeur est
   reprise, celle du socle, et elle continue de gouverner tous les intervalles.
   Portée à cette page seulement.
   ========================================================================== */

body[data-page="la-villa"] {
  /* 1 440 px : 20 px, soit 40 px entre deux sections au lieu de 120. */
  --section-y: clamp(16px, 7px + 0.9vw, 20px);
}
/* Les têtes de section et les sous-parties suivent, sinon le resserrement des
   sections ferait ressortir les respirations internes. */
body[data-page="la-villa"] .section-head { margin-bottom: clamp(18px, 1.4vw, 22px); }
/* Devant une scène épinglée, la tête de section n'a plus rien à séparer : le
   haut de la scène est vide sur toute la marge du centrage. Elle vient donc
   la toucher, et ce sont ces deux vides cumulés — le sien et celui du
   centrage — qui faisaient la longue respiration entre le grand titre et le
   premier texte. */
body[data-page="la-villa"] .section-head:has(+ .space.is-read) { margin-bottom: 0; }
/* Et la scène remonte dans cette marge : le grand titre vient s'appuyer contre
   la photographie. Il n'en reste que 45 %, soit une quarantaine de pixels sur
   un écran de 820 — et la proportion tient sur un écran haut comme sur un
   écran court. */
.section-head + .space.is-read { margin-top: calc(var(--space-marge) * -0.55); }
/* `:not(.is-read)` n'est pas une précaution : sans lui, cette règle — plus
   spécifique que `.space.is-read` — remettait 30,8 px de padding en haut et en
   bas de la PISTE. Or une scène collante est bornée par le contenu de son
   parent : 61 px de course disparaissaient, et les dernières lignes du texte
   n'arrivaient jamais en bas de la fenêtre. */
body[data-page="la-villa"] .space:not(.is-read) { padding-block: clamp(22px, 2.2vw, 36px); }

/* --- La bande des chambres --------------------------------------------------
   Le composant `.spaces` est repris tel quel ; seules les dimensions changent.
   Une carte de chambre porte bien plus qu'une carte d'espace — une
   photographie, un nom, quatre lignes de caractéristiques et un lien — et le
   dimensionnement de l'accueil, calculé pour trois lignes, l'aurait fait
   déborder de la scène. */
body[data-page="la-villa"] .spaces { padding-inline: 0; }
body[data-page="la-villa"] .spaces__head { padding-inline: var(--gutter); }
/* La seule respiration qu'on ouvre au lieu de la fermer : « Chambres » et la
   ligne qu'il annonce se touchaient presque (7 px d'interligne et rien de
   plus). */
body[data-page="la-villa"] .spaces__head .display-m { margin-top: 10px; }
body[data-page="la-villa"] .spaces.is-driven .spaces__stage {
  gap: clamp(20px, 2.4vh, 36px);
  padding-top: calc(var(--header-h) * 0.6);
}
body[data-page="la-villa"] .spaces__rail { padding-inline: var(--gutter); }

body[data-page="la-villa"] .spaces.is-driven .spaces__card { width: clamp(240px, 25vw, 340px); }
body[data-page="la-villa"] .spaces__card { width: clamp(240px, 25vw, 340px); }
body[data-page="la-villa"] .room-card { margin: 0; height: 100%; }
/* La photographie est bornée par la HAUTEUR disponible : c'est elle qui varie
   avec l'écran, le reste de la carte ayant une hauteur à peu près fixe. */
body[data-page="la-villa"] .spaces.is-driven .room-card__media {
  height: clamp(140px, 24vh, 240px);
}
body[data-page="la-villa"] .spaces.is-driven .room-card__media .gallery-item,
body[data-page="la-villa"] .spaces.is-driven .room-card__media img {
  height: 100%;
  object-fit: cover;
}

/* --- Les carrousels d'espaces -----------------------------------------------
   `.space__gallery` est un conteneur flex fait pour aligner des vignettes : un
   carrousel posé dedans n'a aucune largeur propre et se réduisait à zéro. Le
   bloc redevient un bloc, et le carrousel prend toute sa mesure. */
.space__carousel { display: block; }
.space__carousel .carousel { width: 100%; }
.space__carousel .carousel__frame { aspect-ratio: 4 / 3; }

/* Sur une colonne étroite, une image moins haute se lit mieux qu'un carré. */
@media (max-width: 767.98px) {
  .space__carousel .carousel__frame { aspect-ratio: 3 / 2; }
}

/* --- La lecture épinglée ----------------------------------------------------
   Chaque espace devient une piste : on arrive dessus, la scène s'épingle, la
   photographie se cale au centre de l'écran — et c'est le TEXTE qui défile,
   dans sa colonne, comme sur un téléphone. Il ne sort jamais du cadre de
   l'image : il s'efface en haut et paraît en bas.

     .space           la piste. Sa hauteur vaut un écran plus le débordement
                      du texte : la section s'épingle exactement le temps de
                      le lire, pas une ligne de plus.
     .space__pin      la scène collante, d'un écran, où la grille se centre.
     .space__head     la FENÊTRE de lecture, débordement masqué.
     .space__flow     le flux, déplacé en Y par le script.

   Les deux hauteurs sont distinctes, et il le faut : la photographie veut être
   HAUTE — c'est le vide au-dessus et au-dessous d'elle, doublé entre deux
   espaces, qui creusait la page — tandis que la fenêtre de lecture veut être
   plus COURTE, sinon le texte y tient presque en entier et il ne reste rien à
   faire défiler. La fenêtre se centre donc dans la hauteur de l'image.

   Le fondu en haut et en bas n'est pas un dégradé posé par-dessus : c'est un
   masque sur la fenêtre elle-même. Un voile aurait dû prendre la couleur du
   fond — impossible ici, le lit de maquis passe derrière.

   Rien de tout cela n'est posé sans la classe `is-read` : sans JavaScript, sur
   écran tactile ou sous `prefers-reduced-motion`, l'espace reste la grille
   d'origine, texte entier et photographie à côté. */

.space.is-read {
  height: var(--space-track, auto);
  padding-block: 0;
  border-top: 0;
  display: block;
}
/* Deux barres restent collées en haut de « La villa » : le bandeau du site
   (84 px) et la sous-navigation des sections (48 px). Centrée sur la fenêtre
   ENTIÈRE, la photographie se calait 66 px au-dessus du centre de ce qu'on
   voit RÉELLEMENT — assez pour qu'elle paraisse monter trop haut. La scène
   commence donc sous elles, et son centre est le vrai centre de l'écran. */
.space.is-read {
  --space-ancre: var(--header-h);
  /* La marge de centrage : ce qui reste de la scène au-dessus et au-dessous de
     la photographie. C'est elle, doublée d'un espace à l'autre, qui faisait les
     longues respirations — et c'est à elle qu'on se réfère pour les resserrer,
     jamais à une valeur en dur : elle change avec la hauteur de l'écran. */
  --space-marge: calc((100svh - var(--space-ancre) - var(--space-window, 62vh)) / 2);
}
body:has(.section-nav) .space.is-read {
  --space-ancre: calc(var(--header-h) + var(--subnav-h));
}
.space.is-read .space__pin {
  position: sticky;
  top: var(--space-ancre);
  height: calc(100svh - var(--space-ancre));
  display: grid;
  grid-template-columns: inherit;
  align-items: center;
  column-gap: var(--grid-gap);
}
/* La grille des colonnes vit sur `.space` : elle est reprise telle quelle par
   la scène, variante inversée comprise. */
/* Ces proportions appartiennent au LECTEUR, pas à la page : « Autour »
   reprend le même composant pour sa carte et pour ses activités. */
.space.is-read { grid-template-columns: minmax(0, 45fr) minmax(0, 52fr); }
.space--reverse.is-read { grid-template-columns: minmax(0, 52fr) minmax(0, 45fr); }
.space.is-read .space__head { grid-column: 1; }
.space.is-read .space__gallery { grid-column: 2; }
.space--reverse.is-read .space__head { grid-column: 2; }
.space--reverse.is-read .space__gallery { grid-column: 1; grid-row: 1; }

.space.is-read .space__head {
  height: var(--space-read, 52vh);
  overflow: hidden;
  /* Les deux bords s'ouvrent et se ferment au fil du défilement : en haut de
     course il n'y a rien à masquer en haut, en fin de course rien en bas. */
  -webkit-mask-image: linear-gradient(to bottom,
    transparent 0,
    #000 var(--mask-top, 0%),
    #000 calc(100% - var(--mask-bot, 0%)),
    transparent 100%);
  mask-image: linear-gradient(to bottom,
    transparent 0,
    #000 var(--mask-top, 0%),
    #000 calc(100% - var(--mask-bot, 0%)),
    transparent 100%);
}
.space.is-read .space__flow {
  transform: translate3d(0, var(--flow-y, 0px), 0);
  will-change: transform;
}

/* La photographie occupe la hauteur, pas un rapport fixe : c'est elle qui
   donne sa mesure à la fenêtre de lecture. */
.space.is-read .space__carousel .carousel__frame {
  aspect-ratio: auto;
  height: var(--space-window, 60vh);
}

/* --- En grand, mais dans les registres du site ------------------------------
   Le corps a grandi, pas la palette. Le titre d'un espace passe dans le
   registre des VRAIS titres — Libre Baskerville, graisse normale, interligne
   et approche des H2 de la page — et le texte reste le paragraphe courant,
   Inter à son interligne de lecture. Un titre de 40 px et un paragraphe de
   22 px, c'est-à-dire les mêmes lettres qu'ailleurs, simplement vues de plus
   près. Le titre reste d'un cran sous le H2 de la section : c'est lui qui
   annonce, l'espace n'est qu'une de ses sous-parties. */
[data-space-read] .space__title {
  font-family: var(--font-display);
  font-weight: var(--fw-regular);
  font-size: clamp(27px, 2.85vw, 42px);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
}
[data-space-read] .space__intro { margin-top: clamp(18px, 1.8vw, 28px); }
[data-space-read] .space__intro p {
  font-family: var(--font-text);
  font-size: clamp(19px, 2.05vw, 30px);
  line-height: var(--lh-body);
  letter-spacing: 0;
  /* Mesure courte, et c'est voulu : moins de signes par ligne, donc plus de
     lignes, donc un vrai défilement — et une lecture qui avance par blocs.
     C'est le paragraphe, pas le titre, qui fait la longueur de la lecture. */
  max-width: 24ch;
}
/* Une caractéristique par ligne. En deux colonnes elles se lisaient d'un bloc ;
   en une seule, elles défilent l'une après l'autre comme le reste. */
[data-space-read] .space__facts {
  grid-template-columns: 1fr;
  display: grid;
  gap: clamp(9px, 0.9vw, 14px);
  margin-top: clamp(20px, 2vw, 30px);
  font-size: clamp(16px, 1.5vw, 23px);
}
[data-space-read] .space__more { margin-top: clamp(22px, 2.2vw, 34px); }
[data-space-read] .space__more .link-line { font-size: clamp(14px, 1.15vw, 18px); }

/* --- Les carrousels d'espaces -----------------------------------------------
   `.space__gallery` est un conteneur flex fait pour aligner des vignettes : un
   carrousel posé dedans n'a aucune largeur propre et se réduisait à zéro. Le
   bloc redevient un bloc, et le carrousel prend toute sa mesure. */
.space__carousel { display: block; }
.space__carousel .carousel { width: 100%; }
.space__carousel .carousel__frame { aspect-ratio: 4 / 3; }

/* Sur une colonne étroite, une image moins haute se lit mieux qu'un carré. */
@media (max-width: 767.98px) {
  .space__carousel .carousel__frame { aspect-ratio: 3 / 2; }
}

/* La colonne éditoriale porte maintenant l'essentiel de la lecture : elle
   reprend un peu de largeur à la photographie. */
@media (min-width: 1025px) {
  body[data-page="la-villa"] .space {
    grid-template-columns: minmax(0, 45fr) minmax(0, 52fr);
  }
  body[data-page="la-villa"] .space--reverse {
    grid-template-columns: minmax(0, 52fr) minmax(0, 45fr);
  }
}

/* ==========================================================================
   13. « Autour » : la carte, les destinations, les activités, les adresses
   La page reprend deux dispositifs déjà en place — la lecture épinglée de
   « La villa » et les révélations au défilement de l'accueil — et n'ajoute
   que ce que ses contenus propres réclament.

   PHOTOGRAPHIES. Celles des destinations et des adresses n'existent pas
   encore. `.frame--attente` tient leur place : même cadre, mêmes dimensions,
   même apparition. Rien à reprendre ici quand elles arriveront.
   ========================================================================== */

/* --- Le jeu de pictogrammes -------------------------------------------------
   Le sprite lui-même ne s'affiche jamais : il n'est qu'une réserve de tracés.
   `display: none` empêcherait le rendu des `<use>` sur d'anciens moteurs, d'où
   le retrait par la taille. */
.sprite {
  position: absolute;
  width: 0; height: 0;
  overflow: hidden;
}

/* --- La carte épinglée ------------------------------------------------------
   La carte ne suit pas la hauteur des photographies : elle porte neuf
   libellés de 13 px dans une boîte de 560, et réduite à leur hauteur ils
   tomberaient à neuf pixels. Elle prend donc toute la scène. */
.space__figure { display: block; }
.space--map.is-read { grid-template-columns: minmax(0, 40fr) minmax(0, 57fr); }
.space--map.is-read .map-editorial img {
  width: auto;
  max-width: 100%;
  height: calc(100svh - var(--space-ancre) - 118px);
  max-height: 780px;
  margin-inline: auto;
}

/* --- La photographie des activités -----------------------------------------
   Elle, en revanche, se cale exactement comme celles de « La villa » : c'est
   le même geste, il doit avoir la même mesure. */
.space--activites.is-read .space__figure .frame { height: var(--space-window); }
.space--activites.is-read .space__figure .frame > img {
  width: 100%; height: 100%;
  object-fit: cover;
}

/* --- Le cadre en attente ----------------------------------------------------
   Ni image cassée ni aplat gris : une surface sourde, tramée du maquis de la
   propriété, qui porte le nom du lieu. Elle occupe au pixel près la place de
   la photographie à venir — rien ne bougera le jour où on la dépose. */
.frame--attente {
  display: grid;
  place-items: center;
  background-color: var(--bg-sunken);
  background-image: url("../images/texture-maquis-900.jpg");
  background-size: cover;
  background-position: center;
  box-shadow: inset 0 0 0 1px var(--rule-soft);
}
.frame--attente::after {
  content: "";
  position: absolute;
  inset: 0;
  background-color: rgba(235, 229, 216, 0.88);
}
.frame__attente-nom {
  position: relative;
  z-index: 1;
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-medium);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-muted);
  text-align: center;
  padding-inline: var(--sp-4);
}

/* --- Les huit destinations --------------------------------------------------
   Une photographie, un grand titre, un texte qui se révèle. Un rang sur deux
   inverse les colonnes ; l'ordre du DOM ne change pas, si bien qu'en colonne
   unique la lecture reste partout image puis texte. */
.places { display: grid; gap: 0; }
.lieu {
  display: grid;
  grid-template-columns: minmax(0, 52fr) minmax(0, 44fr);
  column-gap: clamp(28px, 4vw, 76px);
  row-gap: var(--sp-5);
  align-items: center;
}
.lieu + .lieu { margin-top: clamp(44px, 5.5vw, 104px); }
.lieu:nth-child(even) .lieu__media { grid-column: 2; grid-row: 1; }
.lieu:nth-child(even) .lieu__body { grid-column: 1; grid-row: 1; }

.lieu__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2) var(--sp-4);
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-muted);
}
/* `--c-gold` sur l'ivoire ne donnait que 2,35:1 pour du 12px, la ou 4,5 est
   requis. Meme or, assombri, pose sur fond clair. */
.lieu__time { color: var(--c-gold-ink); }
.lieu__name {
  font-family: var(--font-display);
  font-weight: var(--fw-regular);
  font-size: clamp(28px, 3.3vw, 50px);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  margin-top: clamp(10px, 1vw, 16px);
}
.lieu__text {
  font-size: clamp(17px, 1.7vw, 26px);
  line-height: var(--lh-body);
  margin-top: clamp(14px, 1.4vw, 22px);
  max-width: 26ch;
}
/* Le conseil est d'une autre nature que la description : il est dit plus bas,
   et un filet le sépare sans le mettre en encadré. */
.lieu__tip {
  margin-top: clamp(14px, 1.4vw, 22px);
  padding-left: clamp(14px, 1.2vw, 20px);
  border-left: 2px solid var(--c-gold);
  font-size: clamp(15px, 1.3vw, 20px);
  line-height: var(--lh-secondary);
  color: var(--text-muted);
  max-width: 30ch;
}
.lieu__link { margin-top: clamp(16px, 1.6vw, 26px); }

/* --- Les activités ----------------------------------------------------------
   Elles reprennent la liste chiffrée de l'accueil, et pas une copie de celle-ci :
   `.facts-xl__list` et `.facts-xl__row` sont les classes du composant, donc la
   typographie — la lineale claire, l'interligne serré, l'approche négative — et
   la découverte par la gauche viennent de là. Une seule chose change : le
   repère de gauche est un pictogramme au lieu d'un numéro.

   `.activities` du socle — les étiquettes encadrées — n'est plus posée du tout
   sur la liste. Rien à neutraliser : ses règles ne s'appliquent simplement plus.
   ------------------------------------------------------------------------- */

/* Dix lignes dans une colonne de 580 px : le corps de l'accueil (68 px sur
   pleine largeur) y passerait à la ligne. */
.activites-xl { --facts-xl-size: clamp(22px, 2.7vw, 40px); }

/* Le composant aligne sur la ligne de base, parce qu'il oppose un numéro de
   11 px à un libellé de 68. Ici le repère est un dessin carré : il se centre
   sur la ligne. */
.activites-xl .facts-xl__row {
  align-items: center;
  gap: clamp(14px, 1.6vw, 26px);
  padding-block: clamp(12px, 1.4vw, 22px);
}

/* Un filet d'or, à la graisse des autres traits du site. Les tracés viennent du
   sprite ; seules la couleur et l'épaisseur sont posées ici. */
.activite__ic {
  flex: 0 0 auto;
  width: clamp(24px, 2.2vw, 34px);
  height: auto;
  aspect-ratio: 1;
  fill: none;
  stroke: var(--c-gold);
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- Les adresses -----------------------------------------------------------
   Même cadre photographique que les destinations, en plus petit. */
.addresses { display: grid; gap: clamp(26px, 3.2vw, 56px); }
.adresse {
  display: grid;
  grid-template-columns: minmax(0, 34fr) minmax(0, 62fr);
  column-gap: clamp(20px, 2.6vw, 48px);
  row-gap: var(--sp-4);
  align-items: center;
}
/* La ligne d'itinéraire. Trois services nommés plutôt qu'un lien unique :
   aucun schéma d'URL n'ouvre un sélecteur d'application sur les trois
   plateformes, et le visiteur sait mieux que nous ce qu'il utilise.
   Elle se tient volontairement sous le renvoi au site : c'est une commodité,
   pas l'action principale. */
.adresse__aller {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2) var(--sp-3);
  margin-top: var(--sp-3);
  font-size: var(--fs-fine);
  line-height: var(--lh-secondary);
}
.adresse__aller-lib {
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}
.adresse__appli {
  position: relative;
  color: var(--text-body);
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 2px;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
/* Même chose que pour les puces : la cible grandit, le soulignement reste
   collé au mot. « Google Maps · Waze · Plans » faisait 24px de haut, ce qui
   se rate une fois sur deux en marchant. `::after` porte déjà le point
   médian, la surface d'appui revient donc à `::before`. */
.adresse__appli::before {
  content: "";
  position: absolute;
  inset: -11px -4px;
}
.adresse__appli:hover,
.adresse__appli:focus-visible {
  color: var(--text-strong);
  border-bottom-color: var(--c-charcoal);
}
/* Un point médian entre deux services, jamais après le dernier. Il est
   décoratif : il ne doit pas être lu. */
.adresse__appli:not(:last-child)::after {
  content: "·";
  position: absolute;
  right: calc(var(--sp-3) * -1);
  transform: translateX(50%);
  color: var(--text-muted);
  border: 0;
  pointer-events: none;
}

.adresse__where {
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.adresse__name {
  font-family: var(--font-display);
  font-weight: var(--fw-regular);
  font-size: clamp(24px, 2.4vw, 36px);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  margin-top: clamp(6px, 0.6vw, 10px);
}
.adresse__text {
  margin-top: clamp(10px, 1vw, 16px);
  font-size: clamp(16px, 1.45vw, 22px);
  line-height: var(--lh-body);
  max-width: 34ch;
}
.adresse__link { margin-top: clamp(12px, 1.2vw, 20px); }

/* --- Le zoom au survol, ici aussi -------------------------------------------
   Ces photographies ne sont pas des liens : le survol est donc écouté sur la
   fiche entière, et non sur un `<a>`. Même valeur que partout ailleurs. */
.lieu .frame > img,
.adresse .frame > img {
  transition: transform 700ms cubic-bezier(0.22, 1, 0.36, 1);
}
.lieu:hover .frame > img,
.adresse:hover .frame > img { transform: scale(var(--zoom-hover)); }

@media (pointer: coarse), (prefers-reduced-motion: reduce) {
  .lieu .frame > img,
  .adresse .frame > img { transition: none; }
  .lieu:hover .frame > img,
  .adresse:hover .frame > img { transform: none; }
}

/* --- En colonne unique ------------------------------------------------------ */
@media (max-width: 900px) {
  .lieu,
  .adresse { grid-template-columns: minmax(0, 1fr); }
  .lieu:nth-child(even) .lieu__media,
  .lieu:nth-child(even) .lieu__body { grid-column: 1; grid-row: auto; }
  .lieu + .lieu { margin-top: clamp(36px, 8vw, 64px); }
}

/* Le bouton du hero a une largeur fixe de 205 px : un libellé de plus d'un mot
   y passait à la ligne. On le garde sur une ligne — si un libellé devient trop
   long, cela se verra tout de suite au lieu de se replier en silence. */
.hero-intro__discover span { white-space: nowrap; }

/* La mesure de 24 signes a été choisie pour la colonne étroite de « La villa ».
   Ici les colonnes sont plus larges — 517 px pour la carte, 720 pour les
   activités — et le chapô s'y étirait sur douze lignes en n'occupant que la
   moitié de la place. Chaque section reprend donc la mesure qui lui va. */
.space--map .space__intro > p { max-width: 32ch; }
.space--activites .space__intro > p { max-width: 46ch; }

/* La légende suit la photographie dans le flux, si bien que la grille centrait
   l'ensemble « image + légende » : l'image se retrouvait 17 px au-dessus du
   centre. Épinglée, la légende sort du flux et se pose dans la marge vide sous
   l'image — c'est bien l'IMAGE qui est au centre de l'écran. */
.space--activites.is-read .space__figure .figure { position: relative; }
.space--activites.is-read .space__figure .figure__caption {
  position: absolute;
  top: calc(100% + var(--sp-3));
  left: 0;
  right: 0;
}

/* ==========================================================================
   14. La visionneuse : la photographie vient de sa place
   Servie par les trois pages qui ouvrent des photographies — l'accueil,
   « La villa » et la galerie.

   Le principe : on ne fait pas apparaître une boîte au milieu de l'écran, on
   agrandit la vignette. Le script mesure la vignette et la grande image, puis
   pose sur cette dernière un `transform` et un `clip-path` qui la font
   ressembler exactement à la vignette — même cadrage, même place — avant de
   les ramener tous deux à zéro. Aucune image dupliquée, aucune distorsion :
   l'échelle est uniforme, c'est le découpage qui s'ouvre.
   ========================================================================== */

/* Le fond passe sur un pseudo-élément. Il peut ainsi s'effacer pendant que la
   photographie, elle, retourne se poser dans la grille — porté par la boîte,
   il aurait emmené l'image avec lui. */
.lightbox { background-color: transparent; }
.lightbox::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background-color: #141613;
  transition: opacity 420ms var(--ease);
}
.lightbox.is-closing::before { opacity: 0; }
.lightbox__bar,
.lightbox__stage,
.lightbox__foot { position: relative; z-index: 1; }

/* Pendant l'agrandissement, l'image ne doit pas être bornée par la scène :
   elle déborde volontairement pour aller chercher la place de la vignette. */
.lightbox__stage { overflow: visible; }

/* Le mobilier — barre, flèches, légende — ne paraît qu'une fois la
   photographie arrivée, et s'efface quand la main ne bouge plus. Il ne
   disparaît jamais au clavier : `is-idle` n'est posée qu'au pointeur. */
.lightbox__bar,
.lightbox__foot,
.lightbox__nav {
  opacity: 0;
  transition: opacity 420ms var(--ease);
}
.lightbox.is-chrome .lightbox__bar,
.lightbox.is-chrome .lightbox__foot,
.lightbox.is-chrome .lightbox__nav { opacity: 1; }
.lightbox.is-chrome.is-idle .lightbox__bar,
.lightbox.is-chrome.is-idle .lightbox__foot,
.lightbox.is-chrome.is-idle .lightbox__nav { opacity: 0; }
.lightbox__bar:focus-within,
.lightbox__nav:focus-visible { opacity: 1 !important; }

/* Flèches : un disque très sobre, sans cadre, qui ne pèse pas sur l'image. */
.lightbox__nav {
  width: 52px; height: 52px;
  border: 0;
  border-radius: 50%;
  background-color: rgba(20, 22, 19, 0.42);
  backdrop-filter: none;
}
.lightbox__nav:hover { background-color: rgba(20, 22, 19, 0.72); }

/* La légende descriptive sert les lecteurs d'écran et la compréhension ; elle
   n'a pas à concurrencer la photographie. */
.lightbox__foot { font-size: var(--fs-micro); color: var(--c-ivory-40); }

@media (prefers-reduced-motion: reduce) {
  .lightbox::before,
  .lightbox__bar,
  .lightbox__foot,
  .lightbox__nav { transition: none; }
  .lightbox.is-chrome.is-idle .lightbox__bar,
  .lightbox.is-chrome.is-idle .lightbox__foot,
  .lightbox.is-chrome.is-idle .lightbox__nav { opacity: 1; }
}

/* La vignette d'arrivée s'efface le temps du retour : sinon on la verrait sous
   la grande image pendant tout son trajet. Vaut pour les vignettes de la
   galerie comme pour celles de l'accueil et de « La villa ». */
.gallery-item.is-lifted { opacity: 0; transition: opacity 120ms linear; }


/* ==========================================================================
   15. Passer l'introduction
   Trois pages ouvrent sur un hero long : l'accueil et « Autour » sur une
   vidéo parcourue au défilement, « La villa » sur quatre panneaux épinglés.
   Entre cinq et huit écrans de course pour qui veut simplement lire la page.

   Le raccourci est un LIEN D'ANCRE ordinaire, et c'est tout l'intérêt :
   `main.js` intercepte déjà `a[href^="#"]`, tient compte du
   `scroll-margin-top` de la cible, révèle son contenu et y pose le focus.
   Rien de cela n'est réécrit. Sans JavaScript, le navigateur suit l'ancre
   tout seul.

   Il vit dans la SCÈNE, jamais dans la piste : la scène est collante et
   d'exactement un écran, le lien reste donc en bas à droite de la fenêtre
   pendant toute la traversée. Posé dans la piste, il aurait attendu au pied
   de huit écrans — là où il ne sert plus à rien.
   ========================================================================== */

.hero-skip {
  position: absolute;
  z-index: 6;   /* au-dessus du voile de la scène (4) et de la signature (5) */
  right: var(--gutter);
  bottom: clamp(20px, 3.4vh, 40px);

  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 44px;              /* cible tactile */
  padding: 10px var(--sp-4) 10px var(--sp-5);
  border: 1px solid rgba(244, 240, 231, 0.34);
  border-radius: 999px;

  /* 72 % d'opacité, et ce n'est pas un réglage d'ambiance : le fond est une
     vidéo dont on ne connaît pas l'image. Sur la plus claire possible, le
     composite tombe à rgb(78) et l'ivoire y tient 7,2:1. À 34 %, il serait
     descendu sous 2,5:1 — illisible une seconde sur deux. */
  background-color: rgba(12, 16, 14, 0.72);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);

  color: var(--c-ivory);
  font-family: var(--font-hero);
  font-size: 11px;
  font-weight: var(--fw-medium);
  line-height: 14px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  text-decoration: none;

  /* Absent tant que la page n'a pas bougé : à l'arrivée, le hero doit être
     entier. `pointer-events` et non `visibility` — l'élément doit rester
     atteignable à la tabulation, exactement comme `.skip-link`. */
  opacity: 0;
  transform: translateY(10px);
  pointer-events: none;
  transition:
    opacity 520ms cubic-bezier(0.16, 1, 0.3, 1),
    transform 520ms cubic-bezier(0.16, 1, 0.3, 1),
    background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

.hero-skip.is-shown,
.hero-skip:focus-visible {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.hero-skip:hover {
  background-color: rgba(12, 16, 14, 0.88);
  border-color: rgba(244, 240, 231, 0.66);
}

.hero-skip__fleche {
  flex: 0 0 auto;
  transition: transform 450ms cubic-bezier(0.22, 1, 0.36, 1);
}
.hero-skip:hover .hero-skip__fleche,
.hero-skip:focus-visible .hero-skip__fleche { transform: translateY(3px); }

/* Une fenêtre étroite AVEC une souris existe — et c'est le seul cas où ce
   lien apparaît sur peu de largeur : les trois heros ne se déploient que sur
   pointeur fin, jamais au doigt. Le libellé ne se tronque pas pour autant,
   il perdrait son sens ; seuls le retrait et l'approche se resserrent. */
@media (max-width: 560px) {
  .hero-skip {
    right: var(--sp-4);
    padding-inline: var(--sp-4);
    letter-spacing: 1px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero-skip,
  .hero-skip__fleche { transition: none; }
  .hero-skip:hover .hero-skip__fleche { transform: none; }
}
