/* ==========================================================================
   PRIVADA — ATMOSPHÈRE
   Fonds animés, texture et lumière.

   PRINCIPE DE PERFORMANCE : rien ici ne touche au layout ni au paint.
   On n'anime que `transform` et `opacity`, sur des couches composées par le
   GPU. Aucun WebGL, aucune bibliothèque, aucun canvas en boucle — un héros
   3D qui tombe à 18 fps sur un Android moyen dessert le site plus qu'il ne
   l'aide, et les jurys testent sur du matériel réel.

   Coût total : 0 Ko de JavaScript, 0 requête supplémentaire.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. GRAIN — la texture qui fait « tirage » plutôt que « rendu »
   Un bruit fin, animé par sauts, posé sur toute la page. C'est le détail qui
   sépare une surface numérique plate d'une matière imprimée.
   -------------------------------------------------------------------------- */
/* IMPORTANT — la couche extérieure fait exactement la taille du viewport et
   clippe la couche animée. Une seule boîte en `position: fixed` débordant de
   l'écran étendrait la zone défilable du document : `body { overflow-x: hidden }`
   ne clippe pas ses enfants, la valeur est propagée au viewport et le body
   repasse en `visible`. C'est ce qui provoquait un débordement en mobile. */
.grain {
  position: fixed;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
  opacity: 0.032;
}
.grain::before {
  content: "";
  position: absolute;
  inset: -25%;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: grain-shift 6s steps(6) infinite;
  will-change: transform;
}
@keyframes grain-shift {
  0%   { transform: translate3d(0, 0, 0); }
  16%  { transform: translate3d(-2%, -3%, 0); }
  33%  { transform: translate3d(-4%, 1%, 0); }
  50%  { transform: translate3d(2%, -2%, 0); }
  66%  { transform: translate3d(-1%, 3%, 0); }
  83%  { transform: translate3d(3%, 1%, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

/* --------------------------------------------------------------------------
   2. CHAMP DE BALAYAGE — pour les sections sombres
   Une grille technique fine, plus une nappe dorée qui tourne très lentement.
   Lecture : un instrument en veille. Jamais un « fond animé » décoratif.
   -------------------------------------------------------------------------- */
.atmo { position: relative; isolation: isolate; overflow: hidden; }

/* On ne remonte QUE ce qui est déjà dans le flux. Une règle `.atmo > *`
   globale forçait `position: relative` sur les enfants décoratifs — le
   filigrane, absolu et haut de 150 %, repassait alors dans le flux et
   gonflait la section de 2000 px. Cibler le contenu, pas tout. */
.atmo > .container,
.atmo > .manifesto__inner { position: relative; z-index: 1; }

/* Les couches décoratives restent absolues, derrière le contenu. */
.atmo > .watermark__mark,
.atmo > .manifesto__mark { position: absolute; z-index: 0; }
.atmo > .iconfield { position: absolute; z-index: 0; }

/* Grille de fond, estompée vers les bords */
.atmo::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background-image:
    linear-gradient(rgba(177, 125, 50, 0.055) 1px, transparent 1px),
    linear-gradient(90deg, rgba(177, 125, 50, 0.055) 1px, transparent 1px);
  background-size: 76px 76px;
  -webkit-mask-image: radial-gradient(ellipse 80% 65% at 50% 50%, #000 25%, transparent 80%);
  mask-image: radial-gradient(ellipse 80% 65% at 50% 50%, #000 25%, transparent 80%);
  pointer-events: none;
}

/* Nappe dorée en rotation lente — la seule chose qui bouge vraiment */
.atmo::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: 150%;
  aspect-ratio: 1;
  z-index: 0;
  translate: -50% -50%;
  background:
    conic-gradient(from 0deg,
      transparent 0deg,
      rgba(177, 125, 50, 0.13) 42deg,
      transparent 96deg,
      transparent 208deg,
      rgba(177, 125, 50, 0.07) 250deg,
      transparent 300deg);
  -webkit-mask-image: radial-gradient(circle at center, #000 12%, transparent 66%);
  mask-image: radial-gradient(circle at center, #000 12%, transparent 66%);
  animation: sweep 44s linear infinite;
  pointer-events: none;
  will-change: transform;
}
@keyframes sweep {
  from { rotate: 0deg; }
  to   { rotate: 360deg; }
}

/* Variante claire — même principe, encre au lieu d'or, encore plus discret */
.atmo--light::before {
  background-image:
    linear-gradient(rgba(17, 17, 17, 0.035) 1px, transparent 1px),
    linear-gradient(90deg, rgba(17, 17, 17, 0.035) 1px, transparent 1px);
}
.atmo--light::after {
  background:
    conic-gradient(from 0deg,
      transparent 0deg,
      rgba(177, 125, 50, 0.09) 48deg,
      transparent 110deg,
      transparent 220deg,
      rgba(17, 17, 17, 0.035) 264deg,
      transparent 320deg);
}

/* --------------------------------------------------------------------------
   3. HORIZON — filet lumineux qui traverse une section
   Remplace la séparation nette entre deux sections par une ligne de lumière.
   -------------------------------------------------------------------------- */
.horizon { position: relative; }
.horizon::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 1px;
  background: linear-gradient(90deg,
    transparent, rgba(177, 125, 50, 0.5) 22%, rgba(177, 125, 50, 0.85) 50%,
    rgba(177, 125, 50, 0.5) 78%, transparent);
}

/* --------------------------------------------------------------------------
   4. COMPOSITION D'ICÔNES — à la place des photographies
   Les icônes du métier, agrandies à l'extrême, recadrées par les bords,
   en or très pâle. Elles construisent le fond sans rien illustrer
   littéralement — et elles sont vectorielles, donc nettes partout et
   quasi gratuites en poids.
   -------------------------------------------------------------------------- */
.iconfield {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
.iconfield svg {
  position: absolute;
  color: var(--gold);
  opacity: 0.055;
  stroke-width: 0.55;
  will-change: transform;
}
.section--invert .iconfield svg { opacity: 0.075; }

/* Trois positions, trois échelles, trois dérives — jamais le même rythme */
.iconfield svg:nth-child(1) {
  width: 46%; top: -12%; right: -6%;
  animation: drift-a 34s ease-in-out infinite alternate;
}
.iconfield svg:nth-child(2) {
  width: 30%; bottom: -14%; left: -4%;
  animation: drift-b 41s ease-in-out infinite alternate;
}
.iconfield svg:nth-child(3) {
  width: 20%; top: 34%; left: 41%;
  opacity: 0.035;
  animation: drift-c 29s ease-in-out infinite alternate;
}
@keyframes drift-a { to { transform: translate3d(-3%, 4%, 0) rotate(-6deg); } }
@keyframes drift-b { to { transform: translate3d(4%, -3%, 0) rotate(7deg); } }
@keyframes drift-c { to { transform: translate3d(-5%, -4%, 0) rotate(10deg); } }

/* --------------------------------------------------------------------------
   4b. LUMIÈRE AU CURSEUR
   Un halo doré très diffus suit le pointeur dans les sections sombres.
   Coût : deux variables CSS mises à jour, une couche composée. Aucun repaint,
   aucun détournement d'interaction — contrairement au défilement inertiel,
   ça n'entrave personne.
   -------------------------------------------------------------------------- */
.spotlight { position: relative; }
.spotlight::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(460px circle at var(--px, 50%) var(--py, 50%),
    rgba(177, 125, 50, 0.11), transparent 68%);
  opacity: 0;
  transition: opacity 520ms var(--ease);
}
body.has-pointer .spotlight:hover::before { opacity: 1; }
.spotlight--light::before {
  background: radial-gradient(520px circle at var(--px, 50%) var(--py, 50%),
    rgba(177, 125, 50, 0.09), transparent 66%);
}

/* --------------------------------------------------------------------------
   4c. SECTIONS EMPILÉES
   La section sombre reste figée pendant que la suivante glisse par-dessus.
   `position: sticky` pur : aucun JavaScript, aucun conflit d'accessibilité,
   le défilement natif reste intact.
   -------------------------------------------------------------------------- */
.stack-under { position: sticky; top: 0; z-index: 0; }
.stack-over {
  position: relative;
  z-index: 1;
  box-shadow: 0 -30px 60px rgba(17, 17, 17, 0.22);
}
/* Sur écran étroit, l'empilement mange trop de hauteur utile. */
@media (max-width: 860px) {
  /* , pas  : un parent statique cesse d'être le bloc conteneur
     de son ::after absolu, qui échappe alors à son overflow:hidden. */
  .stack-under { position: relative; }
  .stack-over { box-shadow: none; }
}

/* --------------------------------------------------------------------------
   4d. RÉVÉLATION CARACTÈRE PAR CARACTÈRE — le manifeste, et lui seul
   -------------------------------------------------------------------------- */
/* Le mot est l'unité de rupture ; la lettre, l'unité d'animation. */
.chars .wd { display: inline-block; white-space: nowrap; }

.js .chars .ch {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.36em);
  transition: opacity 420ms var(--ease), transform 560ms var(--ease-out);
  transition-delay: calc(var(--i) * 12ms);
}
.js .chars.is-in .ch { opacity: 1; transform: none; }
/* État terminal dur — le texte ne peut jamais rester invisible. */
.js .chars.is-done .ch { opacity: 1; transform: none; transition: none; }

/* --------------------------------------------------------------------------
   5. ANIMATIONS PILOTÉES PAR LE DÉFILEMENT (CSS natif)
   `animation-timeline: view()` lie l'animation à la position de l'élément
   dans le viewport — sans JavaScript, sans écouteur de scroll, et calculé
   hors du fil principal. C'est la technique 2026 pour ce genre d'effet.
   Enveloppé dans @supports : là où ce n'est pas géré, l'observateur JS
   existant prend le relais, comme aujourd'hui.
   -------------------------------------------------------------------------- */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* Parallaxe du filigrane — remplace le calcul JS au scroll */
    .watermark__mark {
      animation: mark-drift linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
    @keyframes mark-drift {
      from { transform: translateY(-6%) rotate(-2.5deg); }
      to   { transform: translateY(6%) rotate(2.5deg); }
    }

    /* Le manifeste se rapproche à mesure qu'il entre.
       La plage reste ENTIÈREMENT dans la phase `entry` et s'achève à 85 % :
       mélanger `entry` et `cover` étirait l'animation si loin que le texte
       plafonnait à 0,56 d'opacité une fois centré à l'écran — donc illisible
       en permanence. Ici, il est à pleine opacité bien avant d'être lu. */
    .manifesto__inner {
      animation: manifesto-in linear both;
      animation-timeline: view();
      animation-range: entry 15% entry 85%;
    }
    @keyframes manifesto-in {
      from { opacity: 0.45; transform: scale(0.975); }
      to   { opacity: 1; transform: none; }
    }

    /* La photo du héros dérive légèrement au défilement */
    .hero__visual img {
      animation: hero-drift linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
    @keyframes hero-drift {
      from { transform: scale(1.06) translateY(-1.5%); }
      to   { transform: scale(1.06) translateY(1.5%); }
    }
  }
}

/* --------------------------------------------------------------------------
   6. PRÉFÉRENCES
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .grain::before { animation: none; }
  .atmo::after { animation: none; }
  .iconfield svg { animation: none; }
  .js .chars .ch { opacity: 1; transform: none; transition: none; }
  .spotlight::before { display: none; }
  /* , pas  : un parent statique cesse d'être le bloc conteneur
     de son ::after absolu, qui échappe alors à son overflow:hidden. */
  .stack-under { position: relative; }
  .stack-over { box-shadow: none; }
}

/* Sur petit écran, on allège : moins de couches composées, plus de batterie. */
@media (max-width: 760px) {
  .grain { opacity: 0.024; }
  .grain::before { animation-duration: 9s; }
  .atmo::after { animation-duration: 70s; }
  .iconfield svg:nth-child(3) { display: none; }
}
