/* ── Reset & base ── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  /* Blanc cassé : non plus #fff pur mais un blanc légèrement chaud qui
     contraste mieux avec les gris du site (#2f2f2f, #343a40…). Utilisé partout
     comme fond (CSS + canvas JS). */
  --bg:       #f4f4f1;
  --border:   #e6e6e6;
  --text:     #222;
  --muted:    #777;
  --font-mono: 'JetBrains Mono', 'Courier New', Consolas, monospace;
  --font-sans: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --radius:   6px;
  --transition: 0.4s ease;
}

/* Pas de scroll-behavior: smooth ici — le smooth scroll inertiel est géré
   par Lenis (main.js), les deux entreraient en conflit. */

/* ── Scrollbar custom ──
   IMPORTANT : si scrollbar-width / scrollbar-color est posé sur le scroller,
   Chromium IGNORE les ::-webkit-scrollbar et retombe sur le style standard
   (fin, arrondi). On réserve donc ces propriétés à Firefox (via @supports)
   et on laisse Chromium/Safari aux pseudo-éléments (contrôle total). */
::-webkit-scrollbar {                           /* WebKit / Chromium / Safari */
  width: 8px;
  height: 8px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background: #202020;                           /* gris RÉEL du bandeau 3D (mesuré à l'écran) */
  border-radius: 0;                             /* coins carrés */
  border: 0;                                    /* pleine largeur de la piste */
  min-height: 80px;                             /* thumb plus court */
}
::-webkit-scrollbar-thumb:hover {
  background: #383838;
}
/* Firefox uniquement (ne supporte pas ::-webkit-scrollbar) */
@supports not selector(::-webkit-scrollbar) {
  html {
    scrollbar-width: auto;
    scrollbar-color: #202020 transparent;
  }
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.6;
  /* Montserrat est plus large que Helvetica : on resserre globalement */
  letter-spacing: -0.015em;
}

/* ── Intro (blanc, plein écran) ── */
#intro {
  min-height: 100dvh;
  background: var(--bg);
  position: relative;
  overflow: hidden;
}

/* Tagline — top center */
#tagline {
  position: absolute;
  top: 2rem;
  left: 50%;
  transform: translateX(-50%);
  text-align: center;
  z-index: 10;
  color: #555;
  font-size: clamp(0.85rem, 1.6vw, 1.05rem);
  line-height: 1.4;
}

/* Fleur ASCII — plein écran */
#flower-player {
  position: absolute !important;
  inset: 0 !important;
  width: 100% !important;
  height: 100% !important;
  max-width: none !important;
}

#flower-player canvas {
  width: 100% !important;
  height: 100% !important;
  /* Le canvas a un ratio fixe (issu de la vidéo) : contain le met à
     l'échelle sans le déformer, le letterbox blanc reste invisible. */
  object-fit: contain;
  object-position: left center;
}

/* Marque — ferrée en bas droite de l'écran (fixe), persistante au scroll.
   mix-blend-mode: difference porté par #brand lui-même (enfant de la racine) :
   c'est la SEULE façon de composer contre le fond de page, donc d'obtenir le
   négatif sur les éléments recouverts. Posé sur un enfant, le contexte
   d'empilement de #brand (position: fixed) piège le blend et il n'inverse plus.
   Source #b0b0ad → |fond − 176| ≈ #444444 (gris moyen) sur le blanc cassé,
   et négatif (clair) sur les sections/éléments sombres. */
#brand {
  position: fixed;
  right: 0.5vw;
  bottom: 0vh;
  z-index: 50;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: clamp(3rem, 9vw, 7.5rem);
  letter-spacing: -0.05em;
  color: #b0b0ad;
  mix-blend-mode: difference;
  opacity: 0;
  animation: brand-in 0.9s ease 0.3s forwards;
  /* Le titre rétrécit dès qu'on scrolle (.scrolled) — transition douce. Le
     passage sur le footer (.at-footer) est instantané : il s'y pose d'un coup
     plutôt que de traverser tout l'écran. */
  transition: font-size 0.6s ease, right 0.6s ease, bottom 0.6s ease;
}

/* Dès le premier scroll : version réduite, ferrée dans le coin avec un léger
   retrait. */
#brand.scrolled {
  font-size: clamp(1.1rem, 2.4vw, 1.8rem);
  right: 1.2rem;
  bottom: 1rem;
}

/* Arrivé sur le footer : le titre s'ancre par le HAUT (le `top` est piloté en
   JS pour suivre le haut de la frame #cube-sticky, puis se fige à ~1rem une
   fois la frame épinglée). Il reste ainsi dans le coin haut-droit du footer et
   ne scrolle plus, sans jamais sauter en haut de l'écran. */
#brand.at-footer {
  bottom: auto;
}

@keyframes brand-in {
  to { opacity: 1; }
}

/* Au survol, l'astérisque clignote dans les trois accents du site (orange, bleu
   électrique, vert). Comme #brand applique difference, on PRÉ-INVERSE chaque
   couleur (source = fond − accent) pour qu'après le blend elle réapparaisse à
   sa vraie teinte sur le blanc cassé :
     #00b8f1 → #FE3C00 (orange) · #d0bc00 → #2438ff (bleu) · #e557ac → #0f9d45 (vert) */
@keyframes star-blink {
  0%, 100% { color: #00b8f1; }
  33%      { color: #d0bc00; }
  66%      { color: #e557ac; }
}

#brand:hover #brand-star {
  animation: star-blink 0.5s steps(1) infinite;
}

/* Mobile / tactile : un élément position:fixed en mix-blend-mode SCINTILLE au
   scroll (bug de compositing, surtout iOS Safari) — et comme il blende contre
   tout le contenu qui défile derrière, c'est « toute la zone du bas » qui
   clignote. On désactive le blend et on fige une couleur lisible. Le négatif
   ne servait qu'au survol (desktop), donc aucune perte sur tactile.
   On combine tactile ET largeur : certains navigateurs mobiles ne matchent pas
   (hover:none)/(pointer:coarse) de façon fiable → le critère de largeur garantit
   que le blend est bien coupé sur téléphone. */
@media (hover: none), (pointer: coarse), (max-width: 768px) {
  #brand { mix-blend-mode: normal !important; color: #1a1a1a !important; }
}

/* ── Shapes section ── */
#shapes {
  position: relative;
  display: flex;
  align-items: center;
  /* Texte ferré à gauche avec un léger margin. */
  justify-content: flex-start;
  min-height: 60vh;
  padding: 0 clamp(1.25rem, 5vw, 4rem);
  /* Pendant le reveal, les lettres très écartées débordent à droite :
     on clippe pour éviter tout scroll horizontal. */
  overflow: hidden;
}

#shapes-text {
  max-width: 720px;
  color: #2f2f2f;
  font-size: clamp(1rem, 1.8vw, 1.45rem);
  line-height: 1.45;
  letter-spacing: -0.02em;
  text-align: left;
}

/* Chaque ligne visuelle figée : un bloc qui ne wrap pas. Au letter-spacing
   maximal elle déborde (clippée par #shapes) sans créer de ligne en plus. */
.ss-line {
  display: block;
  white-space: nowrap;
}

/* Reveal letter-spacing : chaque caractère est un span inline-block dont le
   letter-spacing est animé (shapes-text.js). */
.ss-char {
  display: inline-block;
  will-change: letter-spacing;
}

/* ── Cube 3D (W → M au scroll) ── */
#cube {
  height: 260vh;
  position: relative;
  background: var(--bg);
}

#cube-sticky {
  position: sticky;
  top: 0;
  /* svh = hauteur du « petit » viewport (barre d'URL visible) : valeur STABLE
     qui ne change PAS pendant le scroll. Avec dvh (dynamique), la barre d'URL
     mobile redimensionnait le sticky à chaque scroll → le footer se décalait et
     le canvas se vidait/redessinait = clignotement de toute la zone. svh garde
     aussi le footer au-dessus de la barre d'URL. */
  height: 100svh;
}

#cube-canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* ── Buttons ── */
.btn {
  display: inline-block;
  padding: 0.6rem 1.8rem;
  border: 1px solid #222;
  color: #111;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  text-decoration: none;
  border-radius: var(--radius);
  transition: background var(--transition), color var(--transition);
  margin-top: 0.4rem;
}

.btn:hover {
  background: #111;
  color: var(--bg);
}

/* ── Sections ── */
/* Apparition au scroll : OPT-IN. On ajoute la classe .reveal aux éléments à
   révéler (cachés au départ, dévoilés via .in-view ajouté par l'observer dans
   main.js). Avantage vs l'ancienne règle « opt-out » sur toutes les <section> :
   une nouvelle section/page n'est plus invisible par accident. */
.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.7s ease, transform 0.7s ease;
}
.reveal.in-view {
  opacity: 1;
  transform: translateY(0);
}

/* Respiration autour des sections à hauteur fixe.
   Pas de marge basse sur #cube : sa fin fait office de footer. */
#shapes { margin: 4vh 0; }
#cube   { margin-top: 10vh; }

.container {
  max-width: 900px;
  margin: 0 auto;
}

h2 {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: clamp(1.4rem, 3vw, 2rem);
  color: #1c1c1c;
  margin-bottom: 1.5rem;
  letter-spacing: -0.03em;
}

/* Canvas layer system (partagé avec oeuvres) */
.canvas-wrap {
  cursor: crosshair;
  position: relative;
  will-change: transform;
}

.layer {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
}

.layer-image {
  opacity: 0;
  transition: opacity 600ms ease-in-out;
}

.layer-scramble {
  pointer-events: none;
}

.loading {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--muted);
  letter-spacing: 0.1em;
  padding: 4rem 2rem;
  text-align: center;
}

/* ── CTA ── */
#cta {
  position: relative;   /* accueille la grille lampe torche */
  text-align: center;
  padding: 10rem 1.5rem;
}

/* Lampe torche : UN SEUL calque fixe pour tout le site — le halo est
   continu d'une section à l'autre, jamais coupé aux frontières. Il vit
   sous le contenu (z-index négatif) ; la grille est ancrée à la page via
   background-position, et --mx / --my suivent la souris (main.js). */
.torch-grid {
  position: fixed;
  inset: 0;
  z-index: -1;
  background-image:
    linear-gradient(to right,  rgba(0, 0, 0, 0.09) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(0, 0, 0, 0.09) 1px, transparent 1px);
  background-size: 56px 56px;
  -webkit-mask-image: radial-gradient(
    circle 230px at var(--mx, -999px) var(--my, -999px),
    rgba(0, 0, 0, 1) 0%,
    rgba(0, 0, 0, 0.7) 50%,
    transparent 100%
  );
  mask-image: radial-gradient(
    circle 230px at var(--mx, -999px) var(--my, -999px),
    rgba(0, 0, 0, 1) 0%,
    rgba(0, 0, 0, 0.7) 50%,
    transparent 100%
  );
  opacity: 0;
  transition: opacity 0.35s ease;
  pointer-events: none;
}

/* Apparition « pose d'étiquette » : le texte se dévoile de gauche à
   droite (clip-path) et se pose à plat depuis une légère inclinaison.
   Les insets négatifs laissent déborder les yeux sans les couper. */
.cta-link {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: clamp(2rem, 5.5vw, 4.2rem);
  font-weight: 400;
  letter-spacing: -0.05em;
  line-height: 1.15;
  color: #2f2f2f;
  text-decoration: none;
  clip-path: inset(-60% 110% -60% -20%);
  transform: rotate(-2.5deg);
  transition: clip-path 0.9s cubic-bezier(0.65, 0, 0.25, 1),
              transform 0.9s cubic-bezier(0.65, 0, 0.25, 1);
}

.cta-mark { display: inline; }

/* Surlignage SÉQUENTIEL ligne par ligne : les lignes visuelles sont figées en
   blocs .cta-line par JS (main.js). Chaque ligne se remplit (wipe) à sa propre
   largeur et seulement APRÈS la précédente — son délai = index de ligne × durée
   du wipe (var --line). Le texte passe en blanc juste après le début du wipe. */
.cta-line {
  display: block;
  width: -moz-fit-content;
  width: fit-content;
  margin: 0 auto;                 /* centré, comme le CTA */
  white-space: nowrap;            /* ligne figée → ne se re-wrappe pas */
  padding: 0.06em 0.3em;
  background-image: linear-gradient(#3c3c3c, #3c3c3c);
  background-repeat: no-repeat;
  background-position: left center;
  background-size: 0% 100%;
  transition: background-size 0.45s cubic-bezier(0.65, 0, 0.25, 1),
              color 0.3s ease;
  transition-delay: calc(0.95s + var(--line, 0) * 0.45s),
                    calc(0.95s + var(--line, 0) * 0.45s + 0.1s);
}

#cta.in-view .cta-line {
  background-size: 100% 100%;
  color: var(--bg);
}

/* Une fois l'étiquette posée (clip-path + rotation), le surlignage se joue
   ligne par ligne (cf. .cta-line). */
#cta.in-view .cta-link {
  clip-path: inset(-60% -20% -60% -20%);
  transform: rotate(0deg);
}

@media (prefers-reduced-motion: reduce) {
  .cta-link {
    clip-path: none;
    transform: none;
  }
}

.cta-watching {
  position: relative;
  display: inline-block;
  text-decoration: underline;
  text-decoration-thickness: 0.045em;
  text-underline-offset: 0.14em;
}

/* Yeux en traînée de curseur sur « watching » : créés en JS (main.js) à la
   position du pointeur, pop élastique à l'apparition, fondu à l'extinction */
.cta-eye {
  position: absolute;
  width: 0.5em;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.2) rotate(var(--r, 0deg));
  transition: opacity 0.2s ease,
              transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
  pointer-events: none;
}

.cta-eye.in {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1) rotate(var(--r, 0deg));
}

.cta-eye.out {
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.35) rotate(var(--r, 0deg));
  transition: opacity 0.35s ease, transform 0.35s ease;
}

/* ── Footer : barre posée sur la fin de la section cube ── */
.footer-bottom {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 1.4rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 2.2rem;
  font-size: 0.95rem;
  letter-spacing: -0.015em;
  color: #343a40;
  opacity: 0;              /* révélée par cube.js quand le M se termine */
  pointer-events: none;
  transition: opacity 0.4s ease;   /* fondu doux (révélation binaire en JS) */
}

.footer-bottom a {
  color: #343a40;
  text-decoration: none;
  transition: color var(--transition);
}

/* Seul l'email est souligné, comme la maquette */
.footer-bottom a[href^="mailto"] {
  text-decoration: underline;
  text-underline-offset: 0.18em;
}

.footer-bottom a:hover { color: #000; }

/* ── Curseur personnalisé : point gris + anneau fin ──
   L'anneau se resserre sur le point au survol d'un élément cliquable
   (positions et échelle pilotées par main.js). Pointeur précis uniquement. */
@media (hover: hover) and (pointer: fine) {
  body, body * { cursor: none !important; }

  .cursor-dot,
  .cursor-ring {
    position: fixed;
    top: 0;
    left: 0;
    border-radius: 50%;
    pointer-events: none;
    z-index: 9999;
    opacity: 0;
    transition: opacity 0.25s ease;
  }

  .cursor-dot {
    width: 6px;
    height: 6px;
    background: #FE3C00;
  }

  .cursor-ring {
    width: 30px;
    height: 30px;
    border: 1px solid #9a9a9a;
  }
}

/* ════════════════════════════════════════════════════════════════════════
   RESPONSIVE — mobile · tablette · laptop · desktop
   --------------------------------------------------------------------------
   Le socle est déjà fluide (clamp() partout) et les effets pointeur (curseur,
   lampe torche, yeux) sont déjà restreints aux écrans à pointeur fin, donc
   inertes au toucher. Ces règles ajustent les points sensibles à chaque
   gabarit : titre marque, légende Services, barre footer, et la montée en
   échelle sur très grand écran.
   ════════════════════════════════════════════════════════════════════════ */

/* ── Très grands écrans (desktop large, 2K / 4K) ───────────────────────────
   Les clamp() plafonnent vite : on relève les maxima pour que le hero reste
   proportionné au lieu de paraître perdu au centre. */
@media (min-width: 1600px) {
  #brand          { font-size: clamp(7.5rem, 8vw, 10rem); }
  #brand.scrolled { font-size: clamp(1.8rem, 1.9vw, 2.3rem); }
  #tagline        { top: 2.6rem; font-size: 1.2rem; }
  #shapes-text    { max-width: 56rem; font-size: 1.7rem; }
  .cta-link       { font-size: clamp(4.2rem, 5vw, 6rem); }
  .footer-bottom  { bottom: 2rem; padding: 0 3rem; font-size: 1.1rem; }
}

/* ── Laptop ───────────────────────────────────────────────────────────────── */
@media (min-width: 1025px) and (max-width: 1599px) {
  #services .cw-caption { right: 20vw; }
}

/* ── Tablette paysage / petit laptop ──────────────────────────────────────── */
@media (max-width: 1024px) {
  .footer-bottom { padding: 0 1.6rem; }
}

/* ── Tablette portrait / grand téléphone ──────────────────────────────────── */
@media (max-width: 768px) {
  /* Texte Shapes centré → autant de marge à gauche qu'à droite (au lieu d'être
     ferré à gauche, ce qui laissait plus de marge à droite). */
  #shapes      { min-height: 52vh; justify-content: center; }
  #shapes-text { max-width: 36rem; }
  /* #services pour battre la règle de base de corner-walls.css (chargé après). */
  #services .cw-caption { left: 35vw; right: 0; font-size: 2.6vh; }
}

/* ── Téléphones ───────────────────────────────────────────────────────────── */
@media (max-width: 640px) {
  /* Intro */
  #tagline { top: 1.4rem; font-size: 0.9rem; max-width: 90vw; }
  /* Vidéo d'intro agrandie : bande centrée occupant ~80 % de la hauteur de
     l'écran, REMPLIE (cover) au lieu d'être réduite en letterbox (contain),
     et ferrée à gauche (object-position left). */
  #flower-player        { top: 10% !important; bottom: auto !important; height: 80% !important; }
  #flower-player canvas { object-fit: cover; object-position: left center; }

  /* Marque : compacte pour ne pas déborder la largeur du téléphone */
  #brand          { font-size: clamp(2rem, 13vw, 3.4rem); right: 0.6rem; }
  #brand.scrolled { font-size: 1.15rem; right: 0.9rem; bottom: 0.85rem; }

  /* Shapes : texte réduit pour rentrer dans la largeur du téléphone.
     Ferré à gauche (flex-start) — sinon, le centrage hérité du breakpoint 768px
     fait déborder le texte des DEUX côtés pendant le reveal (lettres écartées) :
     il « part » à gauche puis revient. En flex-start, le bord gauche reste ancré
     à la marge et l'expansion se fait uniquement vers la droite (clippée). */
  #shapes      { min-height: 48vh; margin: 3vh 0; padding: 0 1.25rem; justify-content: flex-start; }
  #shapes-text { font-size: 0.72rem; line-height: 1.5; margin-inline: 1rem; }

  /* CTA un peu plus serré */
  #cta      { padding: 7rem 1.25rem; }
  .cta-link { font-size: clamp(1.9rem, 9vw, 2.8rem); }

  /* Services : bandeau pleine largeur (left/right 0), réduit, et texte
     « WHAT WE'RE CAPABLE OF » petit. Préfixe #services pour battre la règle de
     base de corner-walls.css (chargé après style.css) qui sinon l'emportait.
     Taille en vw → rentre dans la largeur quelle que soit la hauteur d'écran. */
  #services .cw-caption {
    left: 18vw; right: 8vw;
    height: 12vh;
    font-size: clamp(0.95rem, 6.5vw, 1.7rem);
  }

  /* Murs 3D : bande pleine largeur, hauteur = moitié de la largeur (ratio 2:1),
     centrée verticalement → elle n'occupe plus tout l'écran. La taille
     d'affichage est imposée ici en !important (sinon le height:100% de base et
     le style inline de Three.js l'emportaient et l'étiraient plein écran) ; le
     buffer de rendu (corner-walls.js : largeur/2) a le même ratio → pas de
     déformation. */
  #services .cw-canvas {
    position: absolute;
    top: 50%;
    left: 0;
    transform: translateY(-50%);
    width: 100% !important;
    height: 70vw !important;   /* hauteur de la bande — doit matcher renderH dans corner-walls.js */
  }

  /* Frame Services épinglée sur mobile comme sur desktop (sticky plein écran) :
     la bande se fige et le défilement des services est scrubbé sur la hauteur de
     la section (corner-walls.js : scrollPerServiceMobile). On hérite donc du
     sticky/top:0/height:100dvh de base — pas de surcharge ici. */

  /* Footer mobile : on masque l'email et on garde « based in Paris » et
     « contact » sur une seule ligne (space-between). */
  .footer-bottom {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    bottom: 1.1rem;
    padding: 0 1.25rem;
    font-size: 0.82rem;
    line-height: 1.45;
  }

  .footer-bottom a[href^="mailto"] { display: none; }
}

/* ── Petits téléphones ────────────────────────────────────────────────────── */
@media (max-width: 380px) {
  #brand          { font-size: clamp(1.8rem, 14vw, 2.8rem); }
  #brand.scrolled { font-size: 1.05rem; }
  #shapes-text    { font-size: 0.66rem; }
  .footer-bottom  { font-size: 0.76rem; }
}

/* ── Téléphones en paysage (faible hauteur) ────────────────────────────────
   On raisonne en vh pour ne pas que le hero/écran déborde verticalement. */
@media (max-height: 480px) and (orientation: landscape) {
  #tagline        { top: 0.8rem; font-size: 0.8rem; }
  #brand          { font-size: clamp(1.8rem, 7vh, 3rem); }
  #brand.scrolled { font-size: 1.05rem; }
  #shapes         { min-height: 85vh; }
  #services .cw-caption { left: 20vw; right: 0; height: 26vh; font-size: 4.5vh; }
}
