@charset "UTF-8";
/* ══════════════════════════════════════════════════════════════════════════
   CARAVELLI OLYMPE — VILLA V923.2
   Le film de la villa tourne en fond sur l'accueil, et il y est NET.

   LA CONTRAINTE, ET COMMENT ELLE EST TENUE
   Le film source est en 720 × 1280 — portrait. La demande est double : qu'il
   tourne en fond de l'accueil, et qu'il n'y soit pas flou. Ces deux exigences
   ne se contredisent que si l'on s'obstine à l'étaler en plein cadre : un
   héros fait 1440 px de large, l'étirer depuis 720 c'est ×2, et ×2 c'est du
   flou — sur un écran à densité double, ×4.

   La sortie n'est pas de renoncer, c'est de donner à un plan vertical un
   cadre vertical. D'où deux emplois, tous deux en RÉDUCTION :

   1. LA NAPPE — une colonne portrait, à droite, sur toute la hauteur du
      héros. Rendue à 605 × 816 css depuis 720 × 1280, elle est réduite de
      ×0,84 : aucun pixel n'est inventé. Son bord gauche est estompé au
      masque, elle ne se pose donc pas comme un rectangle rapporté, elle
      émerge de la nuit. Le compte à rebours flotte devant : on lit des
      plans, ce qui est exactement l'effet recherché.

   2. LE NOM — le même film vu À TRAVERS les lettres de CARAVELLI. Boîte de
      680 × 165 : le film y est encore réduit (×0,94). Le masque est un
      <mask> svg en ligne, donc il hérite des polices du document, ce qu'un
      masque en URI de données ne permet pas.

   UN MASQUE EN UNITÉS D'ESPACE UTILISATEUR, ET POURQUOI
   Une version précédente utilisait `maskContentUnits="objectBoundingBox"`.
   En unités de boîte objet, la taille de police n'est pas rapportée à la
   hauteur mais à la diagonale normalisée de la boîte, puis le repère est
   étiré non uniformément par les proportions de l'élément — sur une boîte
   1240 × 300, les lettres sortaient écrasées 4:1 : des éclats, pas un mot.
   `userSpaceOnUse` résout les coordonnées en pixels css de l'élément
   référant : ce qui est écrit est ce qui est rendu. La boîte est donc figée
   en pixels, et c'est `scale()` qui l'adapte aux petits écrans — une
   transformation n'altère pas l'espace du masque, qui s'applique avant.

   TECHNIQUES
   `@property` pour l'angle animé · `animation-timeline: view()` pour la
   dérive pilotée au défilement, sans écouteur · `color-mix(in oklab)` ·
   masques composés · `content-visibility` · repli complet sous
   `prefers-reduced-motion`.
   ══════════════════════════════════════════════════════════════════════════ */

@property --villa-angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

body[data-page="index"] .hero { --villa-ech: 1; }


/* ══ 1 · LE VOILE DE NUIT ══════════════════════════════════════════════════
   À vérifier avant toute chose : le film livré à l'origine dans le héros
   (hero_1080p_muted.mp4) est un plan rapproché de mur enduit, clair, joué à
   0,72 d'opacité. Le titre blanc « Restaurer un lieu… » s'y perdait — mesuré
   avant toute intervention de la villa.

   Ce voile rend au titre son fond. Il est asymétrique : dense en bas à
   gauche, là où porte le texte ; transparent à droite, là où passe le film
   de la villa, qu'il ne doit pas éteindre. */

body[data-page="index"] .villa-nuit {
  position: absolute;
  inset: 0;
  z-index: 3;                     /* sur les films, sous .hero-body (4) */
  pointer-events: none;
  background:
    /* la colonne de texte, tenue au noir : c'est elle qui portait le defaut */
    radial-gradient(72% 82% at 2% 84%,
      oklch(.1304 calc(var(--ol-c-body)*.32) var(--ol-h) / .97) 0%,
      oklch(.147 calc(var(--ol-c-body)*.375) var(--ol-h) / .90) 24%,
      oklch(.1519 calc(var(--ol-c-body)*.399) var(--ol-h) / .66) 46%,
      oklch(.1625 calc(var(--ol-c-body)*.435) var(--ol-h) / .26) 66%,
      transparent 82%),
    /* degrade lateral : dense a gauche, transparent la ou passe le film */
    linear-gradient(90deg,
      oklch(.1422 calc(var(--ol-c-body)*.35) var(--ol-h) / .82) 0%,
      oklch(.147 calc(var(--ol-c-body)*.375) var(--ol-h) / .58) 26%,
      oklch(.1625 calc(var(--ol-c-body)*.435) var(--ol-h) / .22) 48%,
      transparent 64%),
    linear-gradient(180deg,
      oklch(.1625 calc(var(--ol-c-body)*.435) var(--ol-h) / .44) 0%,
      oklch(.1625 calc(var(--ol-c-body)*.435) var(--ol-h) / .08) 24%,
      oklch(.1422 calc(var(--ol-c-body)*.35) var(--ol-h) / .24) 58%,
      oklch(.1422 calc(var(--ol-c-body)*.35) var(--ol-h) / .78) 100%);
}


/* ══ 2 · LA NAPPE — COLONNE PORTRAIT ═══════════════════════════════════════
   Un cadre vertical pour un plan vertical. C'est tout le principe. */

body[data-page="index"] .villa-nappe {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  /* 42vw plafonné à 640 : à 1440 px de large cela donne 605 px, soit 0,84 ×
     la définition native. Le film est réduit, donc net. Le plancher à 220 px
     évite qu'il ne devienne un filet illisible sur petit écran. */
  width: clamp(220px, 42vw, 640px);
  z-index: 2;
  overflow: hidden;
  pointer-events: none;
  contain: paint;
  /* Deux masques croisés, pas un seul : le bord gauche s'éteint pour que la
     colonne émerge de la nuit au lieu d'y être collée, et les bords haut et
     bas s'éteignent pour qu'elle ne vienne pas buter contre l'en-tête ni
     contre le pied du héros. L'intersection des deux dégradés donne un cadre
     sans arête — `mask-composite: intersect`, et son ancien nom webkit. */
  -webkit-mask-image:
    linear-gradient(90deg,
      transparent 0%, rgba(0, 0, 0, .16) 14%, rgba(0, 0, 0, .62) 40%,
      rgba(0, 0, 0, .94) 70%, rgba(0, 0, 0, .82) 100%),
    linear-gradient(180deg,
      transparent 0%, rgba(0, 0, 0, .55) 9%, #000 22%,
      #000 76%, rgba(0, 0, 0, .42) 93%, transparent 100%);
  mask-image:
    linear-gradient(90deg,
      transparent 0%, rgba(0, 0, 0, .16) 14%, rgba(0, 0, 0, .62) 40%,
      rgba(0, 0, 0, .94) 70%, rgba(0, 0, 0, .82) 100%),
    linear-gradient(180deg,
      transparent 0%, rgba(0, 0, 0, .55) 9%, #000 22%,
      #000 76%, rgba(0, 0, 0, .42) 93%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

body[data-page="index"] .villa-nappe video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* Aucun flou : la netteté est le sujet. Seulement un étalonnage, qui
     ramène un mur clair dans le bleu nuit de la Maison — désaturation,
     assombrissement, virage froid. Le film éclaire le héros, il n'en décide
     pas la couleur. */
  filter: saturate(.34) contrast(1.24) brightness(.54) hue-rotate(198deg);
  opacity: .82;
  will-change: transform;
}

/* Dérive très lente, pilotée par le défilement du héros — donc sans écouteur
   et sans un seul cycle processeur quand la page ne bouge pas. Les échelles
   restent sous 1 : même en fin de course le film demeure une réduction. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    body[data-page="index"] .villa-nappe video {
      animation: villa-derive linear both;
      animation-timeline: view();
      animation-range: cover;
    }
  }
}

@keyframes villa-derive {
  from { transform: scale(1.00) translate3d(0, -1.8%, 0); }
  to   { transform: scale(1.06) translate3d(0,  1.8%, 0); }
}

/* Un liseré d'or à l'aplomb de la colonne : l'angle est animé par @property,
   ce qui rend un dégradé réellement interpolable. */
body[data-page="index"] .villa-nappe::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, oklch(.147 calc(var(--ol-c-body)*.375) var(--ol-h) / .58) 0%, transparent 22%,
                            transparent 74%, oklch(.1422 calc(var(--ol-c-body)*.35) var(--ol-h) / .72) 100%),
    linear-gradient(var(--villa-angle),
      color-mix(in oklab, oklch(.7362 calc(var(--ol-c-pat)*.959) calc(var(--ol-h-pat) + -4.5) / .16), transparent 62%) 0%,
      transparent 58%);
  animation: villa-tourner 26s linear infinite;
}

@keyframes villa-tourner { to { --villa-angle: 360deg; } }


/* ══ 3 · LE NOM ════════════════════════════════════════════════════════════
   Le film est DÉCOUPÉ à la forme des lettres — il n'y a aucune plaque de
   fond. Une première version posait un rectangle de nuit percé du mot : elle
   recouvrait le titre du héros. Le masque inverse le principe — seules les
   lettres portent de l'image, tout le reste du rectangle est simplement
   absent.

   Le nom se tient DERRIÈRE le contenu du héros, en filigrane : il accompagne
   le titre, il ne se bat pas avec lui. */

body[data-page="index"] .villa-nom__def { position: absolute; width: 0; height: 0; }

body[data-page="index"] .villa-nom__lettres {
  font-family: "Bodoni Moda", Didot, "Bodoni 72", Georgia, serif;
  font-weight: 500;
  font-size: 108px;              /* pixels d'espace utilisateur — cf. entête */
  letter-spacing: 5px;
  font-synthesis: none;
}

body[data-page="index"] .villa-nom {
  position: absolute;
  z-index: 2;
  left: 9%;                      /* à l'aplomb du titre du héros */
  top: 27%;
  width: 680px;                  /* figé : l'espace du masque en dépend */
  height: 165px;
  transform: translateY(-50%) scale(var(--villa-ech, 1));
  transform-origin: 0 50%;
  pointer-events: none;
  opacity: 0;
  animation: villa-paraitre 3s cubic-bezier(.19, 1, .22, 1) 1.2s both;
}

@keyframes villa-paraitre { to { opacity: 1; } }

body[data-page="index"] .villa-nom video {
  width: 100%;
  height: 100%;
  object-fit: cover;             /* 680 / 720 = ×0,94 : encore une réduction */
  display: block;
  filter: saturate(.62) contrast(1.28) brightness(1.06);
  -webkit-mask: url(#villaMot);
  mask: url(#villaMot);
  opacity: .72;
}

/* Sans masque svg, on n'affiche rien plutôt qu'un rectangle de film brut. */
@supports not ((mask: url(#villaMot)) or (-webkit-mask: url(#villaMot))) {
  body[data-page="index"] .villa-nom { display: none; }
}


/* ══ 4 · COHABITATION ══════════════════════════════════════════════════════
   Le film d'origine du héros garde exactement son réglage livré : on ne
   touche pas à son opacité. Une version intermédiaire la remontait pour
   faire place à une nappe plein cadre — ce dispositif n'existe plus, et il
   éclaircissait le héros pour rien. Seule subsiste la mise en couches. */

body[data-page="index"] .hero-vid  { z-index: 1; }
body[data-page="index"] .hero-body { position: relative; z-index: 4; }


/* ══ 5 · ÉCRANS ÉTROITS ════════════════════════════════════════════════════
   Le nom est mis à l'échelle par transformation, jamais par redimensionnement
   de sa boîte : l'espace utilisateur du masque doit rester celui pour lequel
   les coordonnées du <text> ont été écrites. */

@media (max-width: 1180px) { body[data-page="index"] .hero { --villa-ech: .78; } }
@media (max-width: 860px) {
  body[data-page="index"] .hero { --villa-ech: .56; }
  body[data-page="index"] .villa-nom { left: 6%; top: 24%; }
  /* Sur mobile la colonne mangerait la moitié de l'écran : on la couche en
     bandeau bas, où elle reste une réduction et ne dispute rien au texte. */
  body[data-page="index"] .villa-nappe {
    top: auto; bottom: 0; right: 0; left: 0;
    width: auto; height: 38%;
    -webkit-mask-image: linear-gradient(180deg, transparent 0%, rgba(0,0,0,.7) 44%, #000 100%);
    mask-image: linear-gradient(180deg, transparent 0%, rgba(0,0,0,.7) 44%, #000 100%);
  }
  body[data-page="index"] .villa-nappe video { opacity: .58; }
}

@media (prefers-reduced-motion: reduce) {
  body[data-page="index"] .villa-nappe video,
  body[data-page="index"] .villa-nappe::after,
  body[data-page="index"] .villa-nom { animation: none; }
  body[data-page="index"] .villa-nom { opacity: 1; }
}
