/* ── LE CADRE DE SCÈNE ──────────────────────────────────────────────────────
   La fenêtre de l'ouverture : la cuve, la chute, le sol. Elle est FIXE et
   vit hors des sections — les trois plans la traversent sans la remonter.

   Le partage de l'écran est la décision de fond : la scène tient la droite,
   le texte tient la gauche, et aucun des deux ne passe sur l'autre. Une
   typographie posée sur un objet 3D en mouvement ne se lit pas, et l'objet ne
   se voit plus : on perd les deux pour n'en avoir aucun.
   ───────────────────────────────────────────────────────────────────────── */

:root {
  /* Largeur du cadre, et ce qu'il reste pour la colonne de texte. Les deux
     sont liés par une seule soustraction : impossible qu'ils se chevauchent. */
  --scene-w: 44vw;
  --scene-gap: 3vw;
  --scene-col-w: calc(100vw - var(--scene-w) - var(--site-padding-x) * 2 - var(--scene-gap));
}

#scene {
  position: fixed;
  inset: 0;
  z-index: var(--z-behind);
  pointer-events: none;
  opacity: var(--presence, 0);
  /* Pas de transition : l'opacité est écrite frame par frame par la
     chorégraphie, qui est déjà lissée. Une transition CSS par-dessus
     ajouterait un second amortissement et ferait traîner le plan. */
}

#scene-frame {
  position: absolute;
  right: var(--site-padding-x);
  top: calc(var(--site-header) + var(--site-padding-y) * 1.2);
  bottom: calc(var(--site-padding-y) * 1.8);
  width: var(--scene-w);
  overflow: hidden;
  /* `isolate` enferme les `mix-blend-mode` de l'ombre de contact dans le
     cadre. Sans ça, l'ombre se mélangerait au fond de la page entière. */
  isolation: isolate;
  background:
    radial-gradient(120% 90% at 50% 38%, #0e131c 0%, #06070b 62%, #04050a 100%);
  border: 1px solid var(--line);
}

/* ── La plaque de sol ─────────────────────────────────────────────────────
   Un vrai plan d'un clip Sponge : sol au ras du gravier, contre-jour bleu,
   brume. C'est là-dessus que l'éponge atterrit.                            */

#scene-plate {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: var(--plate, 0);
  /* Elle monte en même temps qu'elle se révèle : le sol vient à l'objet. */
  transform: scale(calc(1.06 - var(--plate, 0) * 0.06))
             translateY(calc((1 - var(--plate, 0)) * 4%));
}

/* L'étalonnage de raccord. La plaque brute est un peu froide et un peu
   claire pour tenir sous un objet doré : on creuse les bords et on referme
   le haut du cadre, exactement comme on le ferait sur un plan destiné à
   recevoir une incrustation. */
#scene-plate-grade {
  position: absolute;
  inset: 0;
  opacity: var(--plate, 0);
  background:
    linear-gradient(to bottom, rgb(4 5 10 / 0.85) 0%, rgb(4 5 10 / 0.1) 34%, rgb(4 5 10 / 0) 55%),
    radial-gradient(120% 80% at 50% 78%, transparent 20%, rgb(4 5 10 / 0.72) 100%);
}

/* ── Le film d'ouverture ──────────────────────────────────────────────────
   Quand un film est fourni, il remplace la scène 3D dans le même cadre — et
   il est piloté par le scroll, pas lu en boucle. Le reste du dispositif ne
   change pas : la colonne de texte, les coupes, la signalétique du cadre.  */
#scene-film {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: var(--film, 0);
  /* Sans fichier, l'élément n'a pas de source : on le retire complètement du
     rendu plutôt que de laisser un rectangle noir par-dessus la plaque. */
  display: none;
}
html[data-film='on'] #scene-film { display: block; }

/* ── L'ombre de contact ───────────────────────────────────────────────────
   Deux calques, et ils font tout le travail d'incrustation. Le premier est
   une ombre portée qui se resserre quand l'objet touche ; le second une
   flaque dorée, parce que le gravier est mouillé et qu'un objet lumineux s'y
   reflète. Les deux se MÉLANGENT à la vidéo — c'est précisément ce qu'un
   rendu WebGL posé par-dessus ne sait pas faire.                           */

/* ⚠️ `#scene-contact` ne doit porter NI transform, NI will-change, NI filtre.

   C'est le piège classique du `mix-blend-mode`, et il est silencieux : un
   élément qui se mélange le fait avec le contenu de son contexte
   d'empilement parent. Or `transform` et `will-change` créent un contexte
   d'empilement. Poser la position sur ce conteneur enferme donc l'ombre dans
   un groupe qui ne contient qu'elle — elle se multiplie avec du vide, et on
   obtient exactement rien, sans le moindre message d'erreur.

   La position voyage donc en propriétés personnalisées (elles héritent) et
   c'est chaque calque qui se translate. Leur contexte parent reste
   `#scene-frame`, où vit la vidéo — celle avec qui on veut se mélanger. */
#scene-contact {
  position: absolute;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
}

#scene-contact i {
  position: absolute;
  display: block;
  left: calc(var(--r, 0px) * -1);
  width: calc(var(--r, 0px) * 2);
  border-radius: 50%;
  transform: translate3d(var(--cx, 0px), var(--cy, 0px), 0);
  will-change: transform;
}

/* ⚠️ L'ombre est en mélange NORMAL, pas en `multiply`.

   Le réflexe est d'écrire `multiply` — c'est ce que fait une ombre. Sauf que
   la plaque est un extérieur nuit : du gravier presque noir. Multiplier du
   noir par du noir ne donne rien, et l'ombre est rigoureusement invisible
   alors que tout est correct dans l'inspecteur. C'est un piège qui coûte une
   demi-heure parce qu'il ne produit aucune erreur.

   Sur un sol sombre, ce qui dit « posé », ce n'est pas l'assombrissement :
   c'est l'EXTINCTION des éclats de gravier juste sous l'objet. Une ellipse
   noire opaque et serrée le fait ; un `multiply` non. */
.scene-contact__shadow {
  /* Plus LARGE que l'objet, et c'est le point. Une ombre plus étroite que la
     silhouette reste intégralement cachée derrière elle : on la voit
     parfaitement dans l'inspecteur et jamais à l'écran. */
  top: calc(var(--r, 0px) * -0.2);
  left: calc(var(--r, 0px) * -1.35);
  width: calc(var(--r, 0px) * 2.7);
  height: calc(var(--r, 0px) * 0.66);
  background: radial-gradient(closest-side, rgb(2 3 6 / 0.96) 22%, rgb(2 3 6 / 0) 76%);
  opacity: var(--shadow, 0);
  filter: blur(5px);
}

/* La flaque. Le gravier est mouillé, et un objet doré posé dessus y allume un
   rond de lumière chaude. C'est LUI qui vend le contact sur un sol sombre. */
.scene-contact__glow {
  top: calc(var(--r, 0px) * -0.34);
  left: calc(var(--r, 0px) * -1.2);
  width: calc(var(--r, 0px) * 2.4);
  height: calc(var(--r, 0px) * 0.68);
  background: radial-gradient(closest-side, rgb(255 201 74 / 0.46), rgb(255 150 30 / 0) 74%);
  mix-blend-mode: screen;
  opacity: var(--glow, 0);
  filter: blur(7px);
}

/* Le reflet, étiré vers le bas. Une surface mouillée renvoie l'objet sous
   lui, brouillé par le grain du sol — c'est le deuxième indice, et le plus
   discret des trois. */
.scene-contact__reflect {
  top: 0;
  left: calc(var(--r, 0px) * -0.5);
  width: calc(var(--r, 0px) * 1);
  height: calc(var(--r, 0px) * 1.15);
  border-radius: 50% / 0 0 100% 100%;
  background: linear-gradient(
    to bottom,
    rgb(255 190 60 / 0.34) 0%,
    rgb(255 160 40 / 0.12) 42%,
    rgb(255 150 30 / 0) 100%
  );
  mix-blend-mode: screen;
  opacity: var(--glow, 0);
  filter: blur(9px);
}

/* ── Le flash de rupture ──────────────────────────────────────────────────
   Un voile blanc, très court, au moment où la paroi cède. C'est lui qui
   masque la frontière entre « il y a une cuve » et « il n'y en a plus ».   */
#scene-flash {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(70% 55% at 50% 44%, rgb(214 244 255 / 0.9) 0%, rgb(150 220 255 / 0.25) 45%, transparent 75%);
  opacity: var(--flash, 0);
  mix-blend-mode: screen;
}

/* ── La signalétique du cadre ─────────────────────────────────────────────
   Quatre équerres et une étiquette. C'est le vocabulaire du moniteur de
   retour, déjà tenu par le reste du site — le cadre en fait partie, il n'est
   pas une carte posée dessus.                                              */

#scene-frame-lines i {
  position: absolute;
  width: 14px;
  height: 14px;
  border: 0 solid var(--gold);
  opacity: calc(0.5 + var(--presence, 0) * 0.5);
}
#scene-frame-lines i:nth-child(1) { top: 8px; left: 8px; border-top-width: 1px; border-left-width: 1px; }
#scene-frame-lines i:nth-child(2) { top: 8px; right: 8px; border-top-width: 1px; border-right-width: 1px; }
#scene-frame-lines i:nth-child(3) { bottom: 8px; right: 8px; border-bottom-width: 1px; border-right-width: 1px; }
#scene-frame-lines i:nth-child(4) { bottom: 8px; left: 8px; border-bottom-width: 1px; border-left-width: 1px; }

#scene-tag {
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: 12px;
  display: flex;
  justify-content: space-between;
  color: var(--signal-34);
  text-transform: uppercase;
  letter-spacing: 0.2em;
}
#scene-state { color: var(--gold); }

/* ── Le canvas ───────────────────────────────────────────────────────────
   Il n'est PAS plein écran : il occupe exactement le cadre, et c'est le JS
   qui pose ses quatre coordonnées (cf. webgl/Stage.js → setRect). Il n'y a
   donc plus de `clip-path` — ce qui sort du cadre n'est pas rogné, il n'est
   pas dessiné.

   Le gain n'est pas que cosmétique : en portrait le cadre fait 46 % de la
   hauteur, donc plus de la moitié des pixels de l'ancienne version partait à
   la poubelle, sur exactement les appareils qui ne peuvent pas se le
   permettre. Et on ne dépend plus d'un `clip-path` sur un élément fixe, qui
   est un terrain à rendus vides sur les moteurs mobiles.                    */

/* ── Portrait ─────────────────────────────────────────────────────────────
   Côte à côte, la colonne tomberait à 40 % d'un écran de téléphone : ni le
   texte ni la scène ne seraient lisibles. On empile — la scène en haut, ce
   qu'on en dit dessous — et le partage tient toujours : rien ne se
   superpose.                                                               */
@media (max-width: 899.98px) {
  :root {
    --scene-w: auto;
    --scene-col-w: calc(100vw - var(--site-padding-x) * 2);
  }

  #scene-frame {
    left: var(--site-padding-x);
    right: var(--site-padding-x);
    width: auto;
    top: calc(var(--site-header) + var(--site-padding-y) * 0.8);
    bottom: auto;
    height: 46svh;
  }

  #scene-tag { letter-spacing: 0.14em; }

  /* ⚠️ La colonne de texte, elle, est empilée dans sections.css et pas ici.
     Elle porte sa position de bureau dans une feuille chargée APRÈS celle-ci :
     une règle `.scene-col` posée dans ce fichier a la même spécificité et se
     fait donc écraser en silence. Une media query ne compte pas dans le
     calcul de spécificité — c'est le piège. */
}

/* Mouvement réduit : la 3D ne démarre pas du tout (cf. webgl/Stage.js). Le
   cadre garde alors la plaque de sol en affiche fixe — l'ouverture reste une
   image de Sponge, elle ne devient pas un trou noir. */
/* ── Mouvement réduit ─────────────────────────────────────────────────────
   La scène 3D CONTINUE d'exister — elle est simplement immobile (cf.
   webgl/Stage.js). Il n'y a donc plus à révéler la plaque en grand ni à
   baisser le cadre : on retire seulement ce qui bouge tout seul, la plaque
   vidéo restant en affiche fixe. */
@media (prefers-reduced-motion: reduce) {
  #scene-plate { transform: none; }
  #scene-flash { display: none; }
}

/* ── Le repli quand la 3D ne démarre pas ──────────────────────────────────
   Contexte WebGL refusé, GPU sur liste noire, extension bloquée : quelle
   qu'en soit la raison, le cadre ne doit JAMAIS être un rectangle vide. Il
   affiche alors une image de la scène — un vrai rendu de la cuve, versé au
   dépôt — et le site garde son premier plan.

   C'est la seule garantie qui ne dépend d'aucune capacité du navigateur : une
   image. Tout le reste peut échouer. */
html[data-fallback='plate'] #scene-frame::before {
  content: '';
  position: absolute;
  inset: 0;
  background: url('../assets/img/scene/cuve.webp') center / cover no-repeat;
  opacity: 0.92;
}
