/* ==========================================================================
   PEAK PRODUCTION — Design system
   Blue #0080FF sampled directly from the real "behind the scenes" visuals.
   ========================================================================== */

:root {
  /* Neutrals — the site is mostly this */
  --black:        #08090A;
  --bg:           #0A0B0D;
  --bg-raise:     #0E1013;
  --anthracite:   #16181C;
  --anthracite-2: #1F2228;
  --line:         rgba(255, 255, 255, 0.09);
  --line-strong:  rgba(255, 255, 255, 0.18);

  --white:        #FFFFFF;
  --text:         #E8EAED;
  --muted:        #8B929C;
  --muted-dim:    #5F656E;

  /* Accent — used sparingly: buttons, rules, hover, a few words */
  --blue:         #0080FF;
  --blue-deep:    #0A6FD6;
  /* Pour les accents en corps d'affiche — voir .display .accent */
  --blue-display: #2E78D6;
  --blue-soft:    #4DA6FF;
  --blue-glow:    rgba(0, 128, 255, 0.30);
  --blue-wash:    rgba(0, 128, 255, 0.08);

  --font-display: "Anton", "Arial Narrow", Impact, sans-serif;
  --font-sans:    "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-serif:   "Playfair Display", Georgia, serif;

  --shell:      clamp(1.25rem, 5vw, 5.5rem);
  --max:        1600px;
  --section-y:  clamp(5.5rem, 13vw, 11rem);

  --ease:       cubic-bezier(0.22, 1, 0.36, 1);
  --ease-out:   cubic-bezier(0.16, 1, 0.3, 1);
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; padding: 0; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  /* La couleur de fond monte sur <html> pour que .ambiance puisse se glisser
     en z-index -1 : sous tout le contenu, mais au-dessus du fond de page. Si
     <body> gardait un fond opaque, il la masquerait entièrement. */
  background: var(--bg);
}

body {
  background: transparent;
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden; /* belt-and-braces against horizontal scroll on mobile */
}

img, video, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
::selection { background: var(--blue); color: #fff; }

:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 3px;
}

.shell {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--shell);
}

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip-link {
  position: absolute; left: 50%; top: 0;
  transform: translate(-50%, -120%);
  background: var(--blue); color: #fff;
  padding: 0.75rem 1.5rem; z-index: 200;
  font-weight: 600; letter-spacing: 0.04em;
}
.skip-link:focus { transform: translate(-50%, 0); }

/* ==========================================================================
   AMBIANCE — le fond vivant du site
   --------------------------------------------------------------------------
   Un noir profond traversé de deux halos bleus très diffus, du grain, et
   quelques traits techniques presque invisibles. Aucune image : tout est
   peint en CSS, ce qui pèse zéro octet de plus et se redimensionne à toutes
   les tailles d'écran sans jamais pixeliser.

   TROIS RÈGLES ONT GUIDÉ LES RÉGLAGES

   1. Le centre reste noir. Les halos sont ancrés aux bords, et une couche
      `--centre` rabat le milieu vers le noir par-dessus eux. Sans elle, la
      diffusion des dégradés remontait jusque sous les titres et le contraste
      du texte devenait irrégulier selon l'endroit de la page.

   2. Tout ce qui bouge ne bouge qu'en `transform`. Un dégradé animé sur ses
      propres positions force un repeint plein écran à chaque image ; une
      translation composite ne coûte quasi rien au processeur, ce qui compte
      d'autant plus qu'un téléphone repeint ici toute la surface visible.

   3. Aucune durée sous 19 s, aucune amplitude au-dessus de 3 %. En dessous, le
      mouvement se remarque — et un fond qui se remarque a raté son travail.
   ========================================================================== */
.ambiance {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
  /* Le parallaxe au défilement pose --amb-y depuis le JS. */
  transform: translate3d(0, var(--amb-y, 0px), 0);
}
.ambiance > * {
  position: absolute;
  /* Débordement volontaire : les halos dérivent, et sans cette marge on
     verrait passer leur bord franc au ras de l'écran. */
  inset: -25%;
}

.ambiance__halo {
  will-change: transform;
  animation-iteration-count: infinite;
  /* Boucle fermée plutôt qu'aller-retour. Un `alternate` fait revenir la
     lumière exactement sur ses pas, et ce va-et-vient se lit comme un
     balancier, pas comme une source qui se déplace. Chaque trajet part d'un
     point et y revient en passant par trois autres : le regard n'y retrouve
     pas de rythme. */
  animation-timing-function: cubic-bezier(0.42, 0, 0.58, 1);
}

/* Faisceau haut-droite, celui qui entre en diagonale. */
.ambiance__halo--a {
  background: radial-gradient(46% 40% at 80% 6%,
    rgba(0, 128, 255, 0.17) 0%,
    rgba(0, 128, 255, 0.055) 44%,
    transparent 72%);
  animation-name: ambDeriveA;
  animation-duration: 38s;
}
/* Halo bas-gauche, le plus lumineux des deux. */
.ambiance__halo--b {
  background: radial-gradient(40% 32% at 4% 74%,
    rgba(0, 128, 255, 0.20) 0%,
    rgba(0, 128, 255, 0.06) 40%,
    transparent 70%);
  animation-name: ambDeriveB;
  animation-duration: 31s;
}
/* Lueur basse très étalée : elle ne se voit pas, elle empêche seulement le
   bas de page de tomber dans un noir plat. */
.ambiance__halo--c {
  background: radial-gradient(66% 48% at 52% 108%,
    rgba(0, 128, 255, 0.075) 0%,
    transparent 64%);
  animation-name: ambDeriveC;
  animation-duration: 44s;
}

/* Les amplitudes sont plafonnées à 12 %. Ce n'est pas une valeur de goût :
   les couches débordent de 25 % du viewport de chaque côté (`inset: -25%`),
   et un `translate` en pourcentage se calcule sur la largeur de la couche,
   soit 150 % du viewport. 12 % x 150 % = 18 % de viewport de déplacement pour
   25 % de marge disponible. Au-delà de 16 %, le bord franc du dégradé entre
   dans le cadre. Les `scale` ne sont jamais sous 1 pour la même raison : ils
   ne peuvent qu'éloigner les bords.

   Vitesse résultante : environ 12 px/s sur un écran de 1440. Assez pour qu'on
   voie la lumière avancer en la regardant, trop peu pour l'attraper du coin
   de l'œil pendant une lecture. */
@keyframes ambDeriveA {
  0%   { transform: translate3d(0, 0, 0)          scale(1); }
  25%  { transform: translate3d(-8%, 4.5%, 0)     scale(1.15); }
  50%  { transform: translate3d(-12%, -3%, 0)     scale(1.05); }
  75%  { transform: translate3d(-4%, -7.5%, 0)    scale(1.17); }
  100% { transform: translate3d(0, 0, 0)          scale(1); }
}
@keyframes ambDeriveB {
  0%   { transform: translate3d(0, 0, 0)          scale(1.04); }
  25%  { transform: translate3d(7.5%, -5%, 0)     scale(1.14); }
  50%  { transform: translate3d(11%, 3.5%, 0)     scale(1.02); }
  75%  { transform: translate3d(3.5%, 8%, 0)      scale(1.12); }
  100% { transform: translate3d(0, 0, 0)          scale(1.04); }
}
@keyframes ambDeriveC {
  0%   { transform: translate3d(0, 0, 0)          scale(1); }
  33%  { transform: translate3d(9%, -4%, 0)       scale(1.12); }
  66%  { transform: translate3d(-9%, -2%, 0)      scale(1.08); }
  100% { transform: translate3d(0, 0, 0)          scale(1); }
}

/* Traits techniques : deux filets verticaux avec leur croisillon, et deux
   grands arcs qui effleurent les coins opposés. Fixes — s'ils dérivaient avec
   les halos, ils cesseraient de se lire comme des repères. */
.ambiance__traits {
  background-repeat: no-repeat;
  background-image:
    /* filet gauche */
    linear-gradient(to bottom, transparent, rgba(190, 215, 240, 0.11) 16%,
                    rgba(190, 215, 240, 0.11) 84%, transparent),
    /* croisillon gauche : barre horizontale puis verticale */
    linear-gradient(rgba(190, 215, 240, 0.16), rgba(190, 215, 240, 0.16)),
    linear-gradient(rgba(190, 215, 240, 0.16), rgba(190, 215, 240, 0.16)),
    /* filet droit */
    linear-gradient(to bottom, transparent, rgba(190, 215, 240, 0.09) 14%,
                    rgba(190, 215, 240, 0.09) 86%, transparent),
    /* croisillon droit */
    linear-gradient(rgba(190, 215, 240, 0.14), rgba(190, 215, 240, 0.14)),
    linear-gradient(rgba(190, 215, 240, 0.14), rgba(190, 215, 240, 0.14));
  background-size:
    1px 26%,
    13px 1px,  1px 13px,
    1px 30%,
    13px 1px,  1px 13px;
  background-position:
    31% 34%,
    calc(31% - 6px) 41%,  31% calc(41% - 6px),
    72% 38%,
    calc(72% - 6px) 45%,  72% calc(45% - 6px);
}
/* Les deux arcs sont des cercles centrés pile sur un coin de l'écran : seul le
   quart utile traverse le cadre, le reste est hors champ. */
.ambiance__traits::before,
.ambiance__traits::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  border: 1px solid rgba(190, 215, 240, 0.055);
}
.ambiance__traits::before {
  width: 64vmax; height: 64vmax;
  left: -32vmax; top: -32vmax;
}
.ambiance__traits::after {
  width: 92vmax; height: 92vmax;
  right: -46vmax; bottom: -46vmax;
}

/* Rabat le milieu vers le noir, par-dessus les halos. C'est ce qui garantit
   que titres, vidéos et réalisations gardent un fond constant. */
.ambiance__centre {
  background: radial-gradient(62% 48% at 50% 46%,
    rgba(10, 11, 13, 0.94) 0%,
    rgba(10, 11, 13, 0.74) 40%,
    transparent 76%);
}

/* Grain. Fixe, jamais animé : un grain qui bouge, c'est du bruit vidéo, pas
   de la texture. Le SVG tient en une ligne et se répète en tuiles de 160px. */
.ambiance__grain {
  opacity: 0.05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

@media (max-width: 620px) {
  /* Trois surfaces composées plein écran, c'est trois textures à garder en
     mémoire vidéo. Le halo le plus discret est celui qui saute. */
  .ambiance__halo--c { display: none; }
  .ambiance__traits::after { display: none; }
  .ambiance__grain { opacity: 0.035; }
}

@media (prefers-reduced-motion: reduce) {
  .ambiance__halo { animation: none; }
  .ambiance { transform: none; }
}

/* ==========================================================================
   Typography
   ========================================================================== */

.display {
  font-family: var(--font-display);
  font-weight: 400;
  text-transform: uppercase;
  /* French uppercase carries accents (É, Ê, À) that sit above the cap height,
     so leading can't go as tight as it could in English. */
  line-height: 0.95;
  letter-spacing: -0.005em;
  color: var(--white);
  text-wrap: balance;
}

/* -12% sur les trois bornes (2.9/8.2/7.6). Le titre ne remplit plus la largeur
   à lui seul : c'est le blanc autour qui porte maintenant. Le palier mobile
   redéfinit sa propre taille plus bas, il n'est pas touché. */
.d-hero { font-size: clamp(2.55rem, 7.2vw, 6.7rem); }
.d-xl   { font-size: clamp(2.6rem, 8vw, 7rem); }
.d-lg   { font-size: clamp(2.1rem, 6vw, 5rem); }
.d-md   { font-size: clamp(1.7rem, 4vw, 3rem); }

/* Le même bleu ne pèse pas pareil à 12px et à 103px. Sur une petite étiquette
   il se lit comme une précision ; posé sur un mot à la taille du titre il
   couvre des milliers de pixels et vire au surligneur. Les accents en corps
   d'affiche prennent donc une version plus profonde et moins électrique —
   même teinte, moins de cri. Les petits accents gardent le bleu vif. */
.accent { color: var(--blue); }
.display .accent { color: var(--blue-display); }

/* The small editorial serif, used only in a few places — a nod to the
   "behind the scenes" lockup in the Instagram visuals. */
.editorial {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  color: var(--muted);
}

/* Les surtitres de section (« NOS EXPERTISES », « PROJETS », « BTS »…) ont été
   retirés, comme celle du hero. Cinq petites étiquettes en capitales avec leur
   trait bleu, une par section, c'était cinq fois le même signal juste avant
   chaque titre. Les titres se suffisent, et le bleu regagne de la valeur à se
   faire rare. */

.lede {
  font-size: clamp(1rem, 1.35vw, 1.2rem);
  color: var(--muted);
  max-width: 58ch;
  line-height: 1.7;
}

/* ==========================================================================
   Buttons
   ========================================================================== */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.8rem;
  /* Moins haut, plus large, moins gras, plus espacé : le bouton pèse le même
     poids visuel mais cesse de crier. La graisse 700 en majuscules serrées
     lisait « bouton d'application » ; 600 avec 0.18em d'interlettrage lit
     « bouton d'agence ». */
  padding: 0.95rem 2.4rem;
  font-size: 0.73rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid transparent;
  transition: background .35s var(--ease), color .35s var(--ease),
              border-color .35s var(--ease), box-shadow .35s var(--ease),
              transform .35s var(--ease);
}
.btn:active { transform: translateY(1px); }

.btn-primary {
  /* Le bleu profond plutôt que l'électrique : sur un aplat de 290x52 le second
     agit comme une source lumineuse et attire l'oeil avant le titre. Le bouton
     doit être le deuxième regard, pas le premier. */
  background: var(--blue-deep);
  color: #fff;
  box-shadow: 0 0 0 rgba(0, 128, 255, 0);
}
.btn-primary:hover {
  background: var(--blue-soft);
  /* Le halo de 40px faisait luire le bouton comme une enseigne. Réduit à une
     ombre portée courte : on sent que ça réagit, on ne voit pas l'effet. */
  box-shadow: 0 8px 24px -14px var(--blue-glow);
}

.btn-ghost {
  border-color: var(--line-strong);
  color: var(--white);
}
.btn-ghost:hover {
  border-color: var(--white);
  background: rgba(255, 255, 255, 0.05);
}

/* Arrow nudges on hover — a microanimation, nothing more */
.btn .arrow { transition: transform .35s var(--ease); }
.btn:hover .arrow { transform: translateX(5px); }

/* Text link with a rule that wipes in */
.link-underline {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--white);
  padding-bottom: 0.55rem;
}
.link-underline::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: var(--line-strong);
}
.link-underline::before {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: var(--blue);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .5s var(--ease);
  z-index: 1;
}
.link-underline:hover::before { transform: scaleX(1); }
.link-underline:hover { color: var(--blue-soft); }

/* ==========================================================================
   Header
   ========================================================================== */

.header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  transition: background .45s var(--ease), backdrop-filter .45s var(--ease),
              border-color .45s var(--ease);
  border-bottom: 1px solid transparent;
}
.header.is-scrolled {
  /* Plus transparent et plus flou : la barre devient un verre posé sur la page
     au lieu d'un bandeau opaque. La saturation retombe à 115% — à 140% le flou
     ravivait les couleurs du contenu qui passait dessous. */
  background: rgba(10, 11, 13, 0.58);
  backdrop-filter: blur(24px) saturate(115%);
  -webkit-backdrop-filter: blur(24px) saturate(115%);
  border-bottom-color: rgba(255, 255, 255, 0.06);
}
/* Slide the header away when scrolling down, bring it back on scroll up */
.header.is-hidden { transform: translateY(-100%); }
.header { transition-property: background, backdrop-filter, border-color, transform; }

.header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  height: clamp(72px, 8vw, 96px);
}

/* The logo file is pure black artwork on transparency; invert() renders it
   white on the dark page. The source file itself is never modified. */
/* Header lockup: the blue Peak icon beside a typeset wordmark. The stacked
   logo file turns to mush at header size, so the icon carries the mark and the
   name is set in the display face. */
.logo {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex: none;
}
.logo img {
  height: clamp(32px, 3.4vw, 42px);
  width: auto;
  /* Lifts the disc off a near-black background so it doesn't read as a hole */
  filter: drop-shadow(0 0 12px rgba(0, 128, 255, 0.35));
}
/* ---------- Horizontal wordmark lockup ----------
   Rebuilt in type rather than shipped as a raster: the source lockup has a
   dark textured background baked in, which would show as a box over the hero.
   This stays crisp at any size and sits on any background. */
.lockup {
  display: flex;
  flex-direction: column;
  align-items: center;   /* the two lines are optically centred on each other */
  line-height: 1;
  white-space: nowrap;
}

.lockup__peak {
  font-family: "Montserrat", var(--font-sans);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  /* Letter-spacing adds a trailing gap after the last glyph; pulling it back
     means the box matches the ink, so the two lines really do align. */
  margin-right: -0.05em;
  /* Brushed-metal ramp, matching the chrome treatment of the source artwork */
  background: linear-gradient(
    180deg,
    #FFFFFF 0%, #F2F6FB 26%, #8E9DB2 52%,
    #FDFEFF 68%, #C4D0DE 88%, #93A2B6 100%
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.lockup__production {
  font-family: "Montserrat", var(--font-sans);
  font-weight: 600;
  text-transform: uppercase;
  color: var(--blue);
  /* Tracked out so it optically spans the width of PEAK above it. The trailing
     letter-space is pulled back so the block stays flush-left and flush-right. */
  letter-spacing: 0.203em;
  margin-right: -0.203em;
}

/* Header sizing */
.logo .lockup__peak { font-size: clamp(1.15rem, 1.75vw, 1.55rem); }
.logo .lockup__production {
  font-size: clamp(0.4rem, 0.6vw, 0.53rem);
  margin-top: 0.42em;
}

.nav { display: flex; align-items: center; gap: clamp(1.4rem, 2.4vw, 2.6rem); }

.nav a {
  position: relative;
  /* Plus petit et nettement plus espacé : en capitales d'imprimerie l'oeil lit
     l'espacement avant la taille, donc le menu gagne en tenue en perdant en
     corps. */
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(232, 234, 237, 0.62);
  transition: color .3s var(--ease);
  padding: 0.4rem 0;
}
.nav a::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: var(--blue);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform .4s var(--ease);
}
.nav a:hover { color: var(--white); }
.nav a:hover::after { transform: scaleX(1); transform-origin: left; }

.header__cta { flex: none; }
.header__cta .btn { padding: 0.78rem 1.7rem; font-size: 0.67rem; letter-spacing: 0.2em; }

/* Burger */
.burger {
  display: none;
  width: 44px; height: 44px;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex: none;
  margin-right: -10px;
}
.burger span {
  display: block;
  width: 24px; height: 1.5px;
  background: var(--white);
  transition: transform .4s var(--ease), opacity .3s var(--ease);
}
.burger.is-open span:nth-child(1) { transform: translateY(7.5px) rotate(45deg); }
.burger.is-open span:nth-child(2) { opacity: 0; }
.burger.is-open span:nth-child(3) { transform: translateY(-7.5px) rotate(-45deg); }

/* Mobile drawer */
.mobile-nav {
  position: fixed;
  inset: 0;
  z-index: 99;
  background: var(--black);
  padding: calc(clamp(72px, 8vw, 96px) + 2rem) var(--shell) 2rem;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  transform: translateY(-100%);
  transition: transform .6s var(--ease-out);
  overflow-y: auto;
  visibility: hidden;
}
.mobile-nav.is-open { transform: translateY(0); visibility: visible; }

.mobile-nav__links { display: flex; flex-direction: column; }
.mobile-nav__links a {
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: clamp(1.9rem, 8vw, 2.8rem);
  line-height: 1.15;
  padding: 0.45rem 0;
  border-bottom: 1px solid var(--line);
  color: var(--white);
  transition: color .3s var(--ease), padding-left .4s var(--ease);
}
.mobile-nav__links a:hover,
.mobile-nav__links a:focus-visible { color: var(--blue); padding-left: 0.6rem; }

.mobile-nav__foot { padding-top: 2.5rem; }
.mobile-nav__foot .btn { width: 100%; }
.mobile-nav__meta {
  margin-top: 1.5rem;
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted-dim);
}

body.nav-open { overflow: hidden; }

/* ==========================================================================
   Hero
   ========================================================================== */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  background: var(--black);
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}

/* ---- Diagonal panel montage ----
   Five vertical stills, one per shoot, sheared into diagonal columns. The rail
   is wider than the viewport so the shear never exposes the page behind it. */
.hero__panels {
  position: absolute;
  top: -4%;
  bottom: -4%;
  /* The rail is wider than the viewport so the 7deg shear never exposes the
     page behind it, and it is offset LEFT rather than centred. Both ends lose
     width off screen; putting more of that loss on the first panel costs
     nothing — it sits under the headline — while the last panel goes from
     showing 59% of itself to about 75%. Narrowing the rail instead was tried
     and is worse: every panel gets narrower, so object-fit crops more of every
     frame. */
  left: -9%;
  right: -9%;
  z-index: 0;
  display: flex;
}

.hero__panel {
  position: relative;
  flex: 1 1 0;
  /* A flex item defaults to min-width:auto, which for a replaced child resolves
     against the image intrinsic width — 1400px here. With five panels that is
     enough to push the last one out of the rail. */
  min-width: 0;
  overflow: hidden;
  transform: skewX(-6deg);
}

.hero__panel img,
.hero__media > picture img,
.hero__media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 38%;
  /* ---- Étalonnage unique pour les cinq plans ----
     Il y avait avant cinq réglages différents, un par tournage, pour rattraper
     les expositions d'origine. Ça équilibrait les images une à une mais ça se
     voyait : cinq colonnes côte à côte, chacune avec sa propre couleur, ça lit
     comme une planche-contact, pas comme un plan.

     Un seul étalonnage sombre et désaturé les rassemble : les tournages ne se
     ressemblent plus par hasard, ils partagent une image. La saturation
     descend à 0.52 — assez pour que la peau reste de la peau et que l'Audi
     reste bleue, assez peu pour qu'aucune colonne ne crie plus fort que sa
     voisine. Le contraste monte à peine, la luminosité descend : les noirs se
     ferment, le titre se pose dessus sans avoir besoin d'un voile plus lourd. */
  filter: saturate(0.52) contrast(1.16) brightness(0.74);
}

/* Le seul rattrapage qui survit : la photo du chantier est franchement plus
   claire que les quatre autres (plein soleil, gilet haute visibilité). Sans ces
   4%, elle reste un halo blanc au bout du rail. */
.hero__panel:nth-child(5) img { filter: saturate(0.52) contrast(1.16) brightness(0.70); }

/* Counter-shear the photo so subjects stay upright; scale to cover the shear. */
.hero__panel img {
  /* The scale exists only to cover the counter-shear, and the two are locked
     together: measured with tools/seam-check.js, 7deg needs 1.26, 6deg needs
     1.22, 5deg needs 1.19. The pair shipped before (7deg / 1.22) was actually
     0.04 short and left 0.12% of the hero uncovered — invisible only because
     the gap is near-black on near-black. 6deg makes 1.22 exactly right, so the
     hole is closed without magnifying the photos. */
  transform: skewX(6deg) scale(1.22);
}

/* Panels wipe up one after another on load. */
@keyframes panelIn {
  from { opacity: 0; clip-path: inset(0 0 100% 0); }
  to   { opacity: 1; clip-path: inset(0 0 0 0); }
}
.hero__panel {
  animation: panelIn 1s var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 130ms);
}

/* Feathered edges — this is what makes the joins read as a blend, not a cut. */
.hero__panel::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(
    to right,
    rgba(8, 9, 10, 0.85) 0%, rgba(8, 9, 10, 0.10) 14%,
    transparent 40%, transparent 60%,
    rgba(8, 9, 10, 0.10) 86%, rgba(8, 9, 10, 0.85) 100%
  );
}

/* The feather exists to blend one panel into the next — on the outer edges of
   the rail there is no neighbour, so the full-strength stop just crushed the
   first and last shoots into black. Ease those two sides off. */
.hero__panel:first-child::after {
  background: linear-gradient(
    to right,
    rgba(8, 9, 10, 0.30) 0%, transparent 22%,
    transparent 60%,
    rgba(8, 9, 10, 0.10) 86%, rgba(8, 9, 10, 0.85) 100%
  );
}
.hero__panel:last-child::after {
  background: linear-gradient(
    to right,
    rgba(8, 9, 10, 0.85) 0%, rgba(8, 9, 10, 0.10) 14%,
    transparent 40%, transparent 84%,
    rgba(8, 9, 10, 0.12) 100%
  );
}

/* Un liseré sur chaque raccord. Il vit à l'intérieur du panneau cisaillé, donc
   il suit la diagonale exactement, sans angle propre à tenir synchronisé.

   Le halo est un drop-shadow et non un box-shadow : box-shadow illuminerait
   toute la colonne d'1px, y compris la portion où le dégradé est déjà
   transparent — une ligne allumée sous une ligne éteinte. drop-shadow suit
   l'alpha réellement peint, donc le halo meurt avec le trait.

   z-index 2 le place au-dessus des photos et de leur fondu, mais à l'intérieur
   de .hero__media (z-index 0), donc toujours derrière .hero__inner.

   Version assumée : un trait bleu électrique, cœur clair presque blanc-bleu,
   halo diffus. Le dégradé s'allume vers le tiers haut et meurt avant le bas —
   une lame de lumière posée sur le raccord, pas une barre qui traverse tout.
   Deux halos superposés : un serré et clair qui donne le cœur incandescent,
   un large et sourd qui fait la diffusion. */
.hero__panel + .hero__panel::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 2px;
  z-index: 2;
  /* Deux calques : la lame statique, et par-dessus un faisceau qui la
     parcourt. Le faisceau est un dégradé de 45% de la hauteur, déplacé par
     background-position — sur une colonne de 2px, le repeint coûte quelques
     centaines de pixels par image, autant dire rien.

     Long et sourd, pas court et brillant. La version précédente culminait à
     0.98 d'alpha sur 38% de hauteur : le pic était si serré que l'oeil ne
     voyait plus un faisceau qui passe mais un point qui se déplace. Ici la
     montée s'étale sur la moitié du calque et plafonne à 0.52 — c'est une
     traînée, et une traînée se lit comme une longueur, pas comme un point.

     Le dégradé reste asymétrique (traîne diffuse derrière, avant plus dense) :
     c'est la dissymétrie qui donne une direction, et la direction est ce qui
     se lit comme du mouvement. --sens le retourne pour les raccords qui
     montent.

     Les drop-shadow suivent l'alpha réellement peint : le halo accompagne
     donc le faisceau dans sa course, sans rien coder de plus.

     Bleu et non blanc-bleu : les valeurs précédentes montaient à
     rgb(225,243,255), c'est-à-dire du blanc à peine teinté. Le faisceau reste
     ici dans la famille du bouton (#0D80F2) et ne blanchit jamais. Les alphas
     montent en compensation — un bleu saturé rend moins lumineux qu'un
     blanc-bleu à opacité égale. */
  background:
    linear-gradient(
      var(--sens, to bottom),
      rgba(40, 130, 235, 0) 0%,
      rgba(45, 140, 240, 0.06) 25%,
      rgba(55, 155, 250, 0.13) 55%,
      rgba(70, 175, 255, 0.22) 80%,
      rgba(85, 185, 255, 0.29) 92%,
      rgba(70, 175, 255, 0.16) 98%,
      rgba(50, 150, 250, 0) 100%
    ) no-repeat,
    /* La lame est volontairement en retrait par rapport au faisceau : à
       intensité égale, le passage se noyait dans le rail (vérifié en capture).
       Même bleu, jamais de blanc non plus. */
    linear-gradient(
      to bottom,
      rgba(40, 130, 235, 0) 0%,
      rgba(45, 140, 240, 0.12) 14%,
      rgba(60, 160, 250, 0.19) 34%,
      rgba(45, 140, 240, 0.10) 62%,
      rgba(40, 130, 235, 0.035) 82%,
      rgba(40, 130, 235, 0) 95%
    );
  background-size: 100% 45%, 100% 100%;
  /* -90% : avec un calque de 45%, l'espace libre vaut 0.55H, donc l'offset
     est de -0.495H — le faisceau démarre entièrement hors cadre, en haut
     (il faut P <= -82% pour cela). */
  background-position: 0 -90%, 0 0;
  /* Le voile .hero__media::after est en z-index 3, au-dessus du raccord, et
     passe à 0.80 de noir dans son quart bas : mesuré, le faisceau y disparaît
     purement et simplement. Il mourait donc d'un coup à mi-parcours au lieu
     de s'éteindre. Ce masque le fait décroître juste avant, là où le voile
     commence — le faisceau s'éteint au lieu d'être avalé. Le seuil descend un
     peu (70% -> 76%) pour laisser respirer un faisceau plus long. */
  mask-image: linear-gradient(to bottom, #000 0%, #000 48%, rgba(0, 0, 0, 0.35) 64%, transparent 76%);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 48%, rgba(0, 0, 0, 0.35) 64%, transparent 76%);
  /* Halo élargi mais affaibli : sur une traînée longue, le halo serré et fort
     de la version courte se recompactait en tache lumineuse. Les deux teintes
     descendent aussi vers le bleu franc, sinon un halo blanc-bleu reblanchit
     un faisceau bleu par les bords.

     Le halo baisse davantage que le faisceau lui-même : c'est lui qui donne
     l'essentiel de la présence à l'écran. Le trait fait 2px, le halo en fait
     40 — diviser le trait par deux sans toucher au halo n'aurait presque rien
     changé à ce que l'oeil perçoit. */
  filter: drop-shadow(0 0 7px rgba(55, 155, 255, 0.20))
          drop-shadow(0 0 20px rgba(0, 120, 255, 0.13));
  /* linear, et ce n'est pas un détail : avec une courbe en ease, le faisceau
     traverse le cadre en 20% du cycle puis rampe hors champ pendant les 80%
     restants — mesuré. En linéaire, le temps passé invisible est
     proportionnel à la distance parcourue invisible, et la course s'arrête
     désormais au masque : le faisceau est allumé ~90% du cycle. Le raccord
     n'est donc presque jamais figé, tout en gardant une vitesse posée.

     Les durées passent de 4.4/5.6/3.9/5.0 à 5.7/7.2/5.0/6.5 : la course est
     plus longue (225 points au lieu de 155), il faut le même facteur de temps
     pour conserver la vitesse réelle. Un faisceau long à vitesse égale paraît
     de toute façon plus calme qu'un point — c'est exactement le but.

     Une seule animation, et c'est délibéré : la respiration d'opacité qui
     accompagnait la course faisait battre le trait. Un faisceau qui passe a
     une intensité constante — c'est son déplacement qui le fait varier à
     l'écran, rien d'autre. */
  animation: seamBeam 5.7s linear infinite;
}

/* Course calée sur un calque de 45% : à -90% le faisceau est entièrement
   au-dessus du cadre (seuil -82%), à 135% il est entièrement sous le masque
   (seuil 127%). Le saut de fin de boucle est donc invisible aux deux bouts —
   il n'y a rien de peint ni à l'arrivée ni au départ.
   La lame statique, elle, ne bouge pas d'un pixel. */
@keyframes seamBeam {
  from { background-position: 0 -90%, 0 0; }
  to   { background-position: 0 135%, 0 0; }
}

/* Quatre raccords, quatre durées premières entre elles et deux sens opposés :
   la figure ne se répète jamais et ne forme jamais de rideau. Les délais sont
   négatifs pour que tout soit déjà en mouvement au chargement.

   reverse + --sens: to top vont par paire — inverser la course sans retourner
   le dégradé ferait voyager le faisceau la traîne en avant. */
.hero__panel:nth-child(2)::before {
  animation-duration: 5.7s;
  animation-delay: -1.2s;
}
.hero__panel:nth-child(3)::before {
  --sens: to top;
  animation-duration: 7.2s;
  animation-delay: -4.4s;
  animation-direction: reverse;
}
.hero__panel:nth-child(4)::before {
  animation-duration: 5.0s;
  animation-delay: -2.6s;
}
.hero__panel:nth-child(5)::before {
  --sens: to top;
  animation-duration: 6.5s;
  animation-delay: -3.1s;
  animation-direction: reverse;
}

@media (prefers-reduced-motion: reduce) {
  .hero__panel + .hero__panel::before {
    animation: none;
    /* Le masque n'existe que pour éteindre le faisceau avant le voile ; sans
       faisceau, il ne ferait qu'amputer la lame de son tiers bas. */
    mask-image: none;
    -webkit-mask-image: none;
    /* Sans animation, le calque du faisceau resterait figé hors cadre :
       autant ne peindre que la lame. Elle est un peu plus soutenue que la
       lame animée puisque rien ne doit s'en détacher, mais elle reste dans
       la même discrétion d'ensemble — et dans le même bleu franc, jamais de
       blanc. */
    background:
      linear-gradient(
        to bottom,
        rgba(40, 130, 235, 0) 0%,
        rgba(50, 150, 245, 0.20) 14%,
        rgba(70, 175, 255, 0.30) 34%,
        rgba(50, 150, 245, 0.16) 62%,
        rgba(40, 130, 235, 0.06) 82%,
        rgba(40, 130, 235, 0) 95%
      );
  }
}

/* Two-layer scrim: a vertical fade for text legibility plus a subtle
   cool cast so the imagery sits in the Peak palette. */
.hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3; /* above the panels and their seams */
  background:
    /* Voile vertical : il ancre le bas où vivent le titre et le bouton, et il
       referme aussi le haut. La barre de navigation se posait jusqu'ici sur
       des visages en pleine lumière — 0.24 en haut ne suffisait pas. À 0.55 le
       menu a son fond noir, et la bande claire au milieu reste intacte : c'est
       là que les photos travaillent. */
    linear-gradient(to top, var(--bg) 1%, rgba(8, 9, 10, 0.80) 24%, rgba(8, 9, 10, 0.24) 56%, rgba(8, 9, 10, 0.14) 74%, rgba(8, 9, 10, 0.55) 100%),
    /* Side scrim: keeps the type side calm. Kept light — the panels are already
       dark, and anything heavier erases the first two of them. */
    linear-gradient(to right, rgba(8, 9, 10, 0.42) 0%, rgba(8, 9, 10, 0.20) 32%, rgba(8, 9, 10, 0.04) 60%, transparent 80%),
    /* Le voile bleu de coin passe de 0.10 à 0.045 : à la première valeur, la
       teinte se lisait comme une couleur posée sur la photo. Là c'est une
       température, pas un filtre. */
    radial-gradient(120% 90% at 15% 90%, rgba(0, 60, 130, 0.045) 0%, transparent 60%),
    /* Vignettage. C'est le geste le plus cinématographique du lot et le moins
       voyant : les coins perdent un demi-diaphragme, l'oeil est ramené au
       centre sans qu'on sache pourquoi. Le centre est laissé strictement
       intact jusqu'à 55% pour ne pas ternir les visages. */
    radial-gradient(130% 105% at 50% 45%,
      transparent 0%, transparent 55%,
      rgba(0, 0, 0, 0.22) 82%, rgba(0, 0, 0, 0.42) 100%);
}

/* ---- Respiration lente ----
   Un très long zoom avant sur chaque plan, décalé d'un panneau à l'autre. À
   28 secondes par aller-retour le mouvement n'est jamais perceptible sur
   l'instant : on ne le voit pas bouger, on remarque juste au bout d'un moment
   que le cadre n'est plus tout à fait le même.

   L'échelle ne descend jamais sous 1.22, la valeur qui couvre exactement le
   cisaillement de 6deg (voir tools/seam-check.js). Un zoom qui passerait en
   dessous ouvrirait un jour sous les panneaux. On ne monte donc que vers le
   haut, de 1.22 à 1.28.

   Cantonné au-dessus de 900px : les paliers tablette et mobile redéfinissent
   le couple cisaillement/échelle (5deg/1.19 et 4deg/1.18), et une animation
   écrirait par-dessus leur transform. */
@keyframes heroBreathe {
  from { transform: skewX(6deg) scale(1.22); }
  to   { transform: skewX(6deg) scale(1.28); }
}

@media (min-width: 901px) {
  .hero__panel img {
    animation: heroBreathe 28s var(--ease) infinite alternate;
    animation-delay: calc(var(--i, 0) * -5.5s);   /* négatif : déjà en cours au chargement */
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero__panel img { animation: none; }
}

.hero__inner {
  position: relative;
  z-index: 2;
  width: 100%;
  padding-bottom: clamp(4rem, 11vh, 8rem);
  padding-top: clamp(9.5rem, 22vh, 15rem);
}

/* La surtitre « PEAK PRODUCTION » a été retirée : le logo est déjà dans la
   barre, et la répéter juste au-dessus du titre faisait doublon. Le hero
   s'ouvre maintenant directement sur la phrase. */

.hero__title {
  margin-bottom: clamp(2rem, 4vw, 3rem);
  /* Le corps ayant baissé de 12%, l'interligne peut se resserrer sans que les
     accents des capitales françaises ne se touchent. */
  line-height: 0.92;
}

.hero__lede {
  font-size: clamp(1rem, 1.35vw, 1.15rem);
  line-height: 1.65;
  color: rgba(232, 234, 237, 0.7);
  /* 46ch laissait la ligne courir presque aussi loin que le titre. Plus court,
     le paragraphe se lit d'un coup d'œil et dégage la moitié droite. */
  max-width: 38ch;
  margin-bottom: clamp(2.75rem, 5.5vw, 4rem);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.hero__scroll {
  position: absolute;
  right: var(--shell);
  bottom: clamp(3rem, 8vh, 6rem);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-size: 0.66rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted-dim);
  writing-mode: vertical-rl;
}
.hero__scroll .rule {
  width: 1px; height: 60px;
  background: linear-gradient(to bottom, var(--blue), transparent);
}

/* ==========================================================================
   Showreel
   ========================================================================== */

.section { padding-block: var(--section-y); }

.section__head {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
  margin-bottom: clamp(2.5rem, 5vw, 4rem);
}
.section__head--split {
  grid-template-columns: 1.2fr 0.8fr;
  align-items: end;
  gap: clamp(2rem, 5vw, 4rem);
}

/* A clearly-labelled placeholder — used where real content is still missing */
.placeholder-tag {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--blue-soft);
  border: 1px dashed rgba(0, 128, 255, 0.5);
  background: var(--blue-wash);
  padding: 0.4rem 0.7rem;
}

/* ==========================================================================
   Services — two equal blocks
   ========================================================================== */

.services {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1px;
  background: var(--line);
  border-block: 1px solid var(--line);
}

.service {
  position: relative;
  background: var(--bg);
  padding: clamp(1.6rem, 3.1vw, 3.1rem);
  display: flex;
  flex-direction: column;
  /* Deliberately compact: the two blocks are a signpost to the two service
     pages, not a page of their own. */
  min-height: clamp(21rem, 32vw, 31rem);
  overflow: hidden;
  transition: background .6s var(--ease);
}
.service:hover { background: var(--bg-raise); }

/* The media is visible at rest — not a black void — and lifts on hover. */
.service__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0.34;
  transition: opacity .7s var(--ease), transform 1.4s var(--ease);
  transform: scale(1.05);
}
.service__bg img {
  width: 100%; height: 100%;
  object-fit: cover;
  filter: saturate(0.85) contrast(1.04) brightness(var(--bg-lift, 1));
}
/* The showroom frame is ~2.7x brighter than the asphalt frame, which made the
   web block visually dominate. Measured and normalised so the two read equally. */
.service:nth-child(2) .service__bg { --bg-lift: 0.58; }
.service__bg::after {
  content: "";
  position: absolute; inset: 0;
  /* Lighter than before, and it eases back further on hover */
  background: linear-gradient(to top, var(--bg) 4%, rgba(10, 11, 13, 0.74) 46%, rgba(10, 11, 13, 0.56) 100%);
  transition: opacity .7s var(--ease);
}
/* Reveal, not full brightness. Now that the backdrop really sits behind the
   copy, the old 0.82/0.74 pair dropped the description to 3.61:1 — below AA.
   0.54/0.92 measures 4.90:1, with margin rather than scraping the 4.5 line.
   The blue lift below does the brightening; the photo only needs to surface. */
.service:hover .service__bg { opacity: 0.54; transform: scale(1); }
.service:hover .service__bg::after { opacity: 0.92; }

/* Laser bars, top and bottom.
   The dim tracks are inset box-shadows on the block itself; only the bright
   fills live on ::before, and only they animate. That split is not cosmetic:
   with all four as background layers on one pseudo-element, Chrome refused to
   interpolate background-size at all and the bar snapped straight to full — it
   bails on the layer list when some layers change and others do not. Measured
   with tools/laser-check.js, not assumed; two layers interpolate correctly.

   The glow is drop-shadow rather than box-shadow: box-shadow would halo the
   whole pseudo-element rectangle, drop-shadow follows the two painted bars. */
.service {
  box-shadow:
    inset 0 2px 0 rgba(0, 128, 255, 0.16),
    inset 0 -2px 0 rgba(0, 128, 255, 0.16);
}
.service::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    linear-gradient(90deg, var(--blue-soft), var(--blue)) left top / 0% 2px no-repeat,
    linear-gradient(90deg, var(--blue-soft), var(--blue)) left bottom / 0% 2px no-repeat;
  filter: drop-shadow(0 0 0 rgba(0, 128, 255, 0));
  transition: background-size .7s var(--ease), filter .7s var(--ease);
}
.service:hover::before,
.service:focus-within::before {
  background-size: 100% 2px, 100% 2px;
  filter: drop-shadow(0 0 7px rgba(77, 166, 255, 0.7));
}

/* The positive lift: a warm-to-blue glow rising from the base of the block
   on hover, so the card brightens rather than just revealing an image. */
.service::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    linear-gradient(to top, rgba(0, 128, 255, 0.20) 0%, rgba(0, 128, 255, 0.07) 34%, transparent 68%),
    radial-gradient(90% 60% at 50% 100%, rgba(77, 166, 255, 0.14) 0%, transparent 70%);
  opacity: 0;
  transition: opacity .6s var(--ease);
}
.service:hover::after { opacity: 1; }

/* No hover on touch, so show a little more of the image by default */
@media (hover: none) {
  .service__bg { opacity: 0.46; }
}

/* Must exclude the backdrop: .service > * (0,1,1) outranks .service__bg
   (0,1,0), which silently forced the absolute backdrop back into the flow. It
   rendered as a photo band above the text instead of a full-bleed background,
   and padded each block with ~330px of dead height. */
.service > :not(.service__bg) { position: relative; z-index: 1; }

.service__num {
  font-family: var(--font-display);
  font-size: clamp(1.8rem, 3.3vw, 2.6rem);
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1px var(--line-strong);
  margin-bottom: clamp(1rem, 2vw, 1.6rem);
  transition: -webkit-text-stroke-color .6s var(--ease);
}
.service:hover .service__num { -webkit-text-stroke-color: var(--blue); }

/* Overrides .d-lg here only — the same class is sized for full section
   headings elsewhere, which is far too big for a block this compact. */
.service__title { font-size: clamp(1.7rem, 4vw, 3.2rem); margin-bottom: 0.9rem; }
.service__desc { color: var(--muted); max-width: 40ch; margin-bottom: 1.4rem; }

.service__list {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem 1.5rem;
  margin-bottom: auto;
  padding-bottom: 1.7rem;
}
.service__list li {
  font-size: 0.83rem;
  color: var(--text);
  padding-left: 1.1rem;
  position: relative;
}
.service__list li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.62em;
  width: 5px; height: 1px;
  background: var(--blue);
}

/* ==========================================================================
   Portfolio
   ========================================================================== */

.filters {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}
/* Sit them against the right edge opposite the heading, not floating mid-row */
.section__head--split .filters { justify-content: flex-end; }
@media (max-width: 1100px) {
  .section__head--split .filters { justify-content: flex-start; }
}
.filter {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 0.78rem 1.3rem; /* keeps the tap target at/above 44px */
  border: 1px solid var(--line);
  transition: color .3s var(--ease), border-color .3s var(--ease), background .3s var(--ease);
}
.filter:hover { color: var(--white); border-color: var(--line-strong); }
.filter.is-active {
  color: var(--white);
  border-color: var(--blue);
  background: var(--blue-wash);
}

.work-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(0.5rem, 1.2vw, 1.15rem);
}

.work-group-heading {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 1rem;
  color: var(--blue-soft);
  font-size: clamp(0.75rem, 1.2vw, 0.95rem);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin: 0 0 0.75rem;
}
.work-group-heading::after {
  content: '';
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, rgba(0,128,255,.5), transparent);
}
.work-group-heading--web { margin-top: clamp(2rem, 5vw, 4.5rem); }
.work-group-heading[hidden] { display: none; }

.work {
  position: relative;
  /* So the caption can be sized against the tile instead of the viewport. */
  container-type: inline-size;
  overflow: hidden;
  background: var(--anthracite);
  display: block;
  transition: opacity .5s var(--ease), transform .5s var(--ease);
}
/* Le `>` est volontaire. Ce bloc habille le visuel de FOND de la tuile : il
   l'étire sur toute la surface, l'assombrit et le zoome au survol. Le logo du
   client vit dans le bandeau, trois niveaux plus bas — sans le `>`, il héritait
   du `width:100%` et couvrait la tuile entière. */
.work > img,
.work > picture img,
.work > video {
  width: 100%; height: 100%;
  object-fit: cover;
  filter: saturate(0.8) brightness(0.74);
  transition: transform 1.3s var(--ease), filter .7s var(--ease);
}
.work:hover > img,
.work:hover > picture img,
.work:hover > video { transform: scale(1.05); filter: saturate(1) brightness(0.9); }

/* The showreel tile plays its own footage — keep it a touch brighter so the
   video reads as content rather than as a darkened backdrop. */
.work__video { filter: saturate(0.88) brightness(0.86); }
.work:hover .work__video { filter: saturate(1) brightness(1); }

/* Asymmetric layout — deliberately not a uniform card grid */
/* 16:9 rather than a cinematic crop — this slot now holds a real 16:9 edit and
   cropping it would cut a quarter of the frame the client is showcasing. */
.work--wide  { grid-column: span 12; aspect-ratio: 16 / 9; }
.work--half  { grid-column: span 6;  aspect-ratio: 4 / 3; }
/* This row holds vertical 9:16 material (tattoo, barber, boxe) — showing it in
   a 3:4 box cropped a quarter of every frame. */
.work--third { grid-column: span 4;  aspect-ratio: 9 / 16; }
.work--two3  { grid-column: span 8;  aspect-ratio: 16 / 9; }

/* Website screenshots: keep browser proportions and show the top of the page
   rather than cropping to the middle of it. */
.work--site { aspect-ratio: 16 / 10; }
.work--site img { object-position: top center; }

.work.is-hidden { display: none; }

.work__overlay {
  position: absolute;
  inset: auto 0 0 0;
  padding: clamp(1.1rem, 2.2vw, 1.9rem);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  /* Always legible, at every width. A caption that only exists on hover is
     invisible on touch, and unreadable to anyone scanning the grid — the tile
     is a client's name, not a hover easter egg. The gradient has to hold
     against a moving video frame, not a fixed still, so it is heavier than a
     still image would need. */
  background: linear-gradient(to top,
    rgba(8, 9, 10, 0.96) 0%, rgba(8, 9, 10, 0.86) 34%,
    rgba(8, 9, 10, 0.52) 68%, transparent 100%);
  transition: background .5s var(--ease);
}
.work:hover .work__overlay,
.work:focus-visible .work__overlay {
  background: linear-gradient(to top,
    rgba(8, 9, 10, 0.98) 0%, rgba(8, 9, 10, 0.90) 38%,
    rgba(8, 9, 10, 0.58) 72%, transparent 100%);
}

.work__client {
  font-family: var(--font-display);
  text-transform: uppercase;
  /* cqw, not vw: the tile follows the 12-column grid while the viewport unit
     follows the window, so the two never track. The longest client name wrapped
     onto a second line at 1280 AND again near 1100 — sizing against the tile
     removes the whole class of break. The clamp ceiling keeps the wide tiles
     from shouting. Swept 360-1700px by tools/label-fit.js. */
  font-size: clamp(1rem, 5.6cqw, 1.7rem);
  line-height: 1;
  color: var(--white);
  margin-bottom: 0.45rem;
}
/* Les tuiles vidéo n'ont plus de ligne de type sous le nom du client. Cette
   marge servait à l'en séparer : sans elle en dessous, elle laisse un vide qui
   décolle le nom du bas de la tuile et le désaligne de l'année, qui est posée
   sur la même ligne de base. Les tuiles web gardent leur marge, leur ligne de
   type suit toujours. */
.work__client:last-child { margin-bottom: 0; }

/* Logo du client, là où une source exploitable existe — six tuiles sur neuf.
   Goodfellas, Arès Ironman et Élagage QC n'en ont aucune : elles gardent leur
   nom seul, et c'est voulu.

   Le monochrome blanc et la normalisation à surface égale sont expliqués dans
   tools/build-client-logos.js. Aucune dimension n'est fixée ici : chaque logo
   porte les siennes dans ses attributs width/height, et ce sont elles qui
   portent la normalisation. Imposer une hauteur commune la détruirait — un
   logo 4:1 et un logo carré à la même hauteur n'ont pas le même poids à
   l'œil. */
.work__logo {
  display: block;
  margin-bottom: 0.55rem;
  opacity: 0.72;
  transition: opacity .5s var(--ease);
}
.work:hover .work__logo,
.work:focus-visible .work__logo { opacity: 1; }

/* Repli Instagram, là où le client n'a aucune marque exploitable. Volontairement
   plus discret que les vraies : un pictogramme générique posé au même niveau
   qu'un logo de client se lirait comme la marque du client. */
.work__logo--repli { opacity: 0.46; }
.work:hover .work__logo--repli,
.work:focus-visible .work__logo--repli { opacity: 0.78; }

.work__type {
  font-size: 0.68rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--blue-soft);
}
.work__year {
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  color: var(--muted);
  flex: none;
}

/* A thin blue rule that draws in along the top edge on hover */
.work::before {
  content: "";
  position: absolute;
  left: 0; top: 0;
  height: 2px; width: 100%;
  background: var(--blue);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .6s var(--ease);
  z-index: 2;
}
.work:hover::before { transform: scaleX(1); }

/* ==========================================================================
   Behind the scenes — horizontal strip
   ========================================================================== */

.bts {
  background: var(--black);
  overflow: hidden;
  position: relative;
}
.bts__separator {
  position: absolute;
  top: 0;
  left: 2%;
  right: 2%;
  height: 8px;
  pointer-events: none;
}
.bts__separator::before {
  content: "";
  position: absolute;
  top: 3px;
  left: 0;
  width: 100%;
  height: 2px;
  background: linear-gradient(to right, transparent, rgba(0, 128, 255, 0.38) 22%, rgba(0, 128, 255, 0.7) 50%, rgba(0, 128, 255, 0.38) 78%, transparent);
  transform: scaleX(var(--separator-progress, 1));
  transform-origin: center;
  transition: transform 1.1s cubic-bezier(0.22, 1, 0.36, 1);
}
@media (prefers-reduced-motion: reduce) {
  .bts__separator::before { transform: none; transition: none; }
}

.bts__quote {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: clamp(1.05rem, 2vw, 1.55rem);
  line-height: 1.5;
  color: var(--muted);
  max-width: 34ch;
}
.bts__quote strong { color: var(--white); font-weight: 400; font-style: normal; }
/* A short blue rule closing the quote, echoing the one that opens every
   section eyebrow. */
.bts__quote::after {
  content: "";
  display: block;
  width: clamp(28px, 4vw, 56px);
  height: 1px;
  margin-top: clamp(1.1rem, 2vw, 1.6rem);
  background: var(--blue);
}

/* Infinite marquee: the track is cloned in JS and translated continuously,
   slowing on hover. Horizontal drag is ours; vertical still scrolls the page. */
.bts__viewport {
  overflow: hidden;
  width: 100%;
  touch-action: pan-y;
  cursor: grab;
}
.bts__viewport.is-dragging { cursor: grabbing; }

.bts__track {
  display: flex;
  gap: 24px;
  width: max-content;
  will-change: transform;
}

/* No motion: fall back to a plain, user-driven scroller. */
@media (prefers-reduced-motion: reduce) {
  .bts__viewport {
    overflow-x: auto;
    scrollbar-width: none;
    cursor: default;
  }
  .bts__viewport::-webkit-scrollbar { display: none; }
  .bts__track { transform: none !important; }
}

.bts__item {
  flex: none;
  width: clamp(200px, 24vw, 330px);
  overflow: hidden;
  position: relative;
  background: transparent;
}
.bts__item picture {
  display: block;
  aspect-ratio: 9 / 16;
  overflow: hidden;
  background: var(--anthracite);
}
.bts__item img {
  width: 100%; height: 100%;
  object-fit: cover;
  filter: saturate(0.8) brightness(0.8);
  transition: transform 1.1s var(--ease), filter .6s var(--ease);
}
.bts__item:hover img { transform: scale(1.05); filter: saturate(1) brightness(0.95); }


.bts__hint {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 1.75rem;
  padding-inline: var(--shell);
  font-size: 0.66rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted-dim);
}

/* ==========================================================================
   Web section
   ========================================================================== */

.web { background: var(--bg-raise); }

.web__layout {
  display: grid;
  grid-template-columns: 0.9fr 1.1fr;
  gap: clamp(2.5rem, 6vw, 6rem);
  align-items: center;
}
/* Headings only get their spacing from .section__head's grid gap; standalone
   ones like this need it explicitly. */
.web__layout .display { margin-bottom: clamp(1.25rem, 2.5vw, 1.9rem); }

.mockups {
  position: relative;
  /* Keep the enlarged previews close with a light overlap. */
  aspect-ratio: 1 / 1;
}

.mockup {
  position: absolute;
  background: var(--anthracite);
  border: 1px solid var(--line-strong);
  overflow: hidden;
  box-shadow: 0 40px 90px -30px rgba(0, 0, 0, 0.9);
}

/* Browser chrome bar */
.mockup__bar {
  height: 26px;
  display: flex;
  align-items: center;
  gap: 6px;
  padding-inline: 12px;
  background: var(--anthracite-2);
  border-bottom: 1px solid var(--line);
}
.mockup__bar i {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.18);
  display: block;
}
.mockup__screen {
  position: relative;
  background: var(--black);
  height: calc(100% - 26px);
  display: grid;
  place-items: center;
  overflow: hidden;
}
.mockup__screen img {
  display: block;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: top center;
}

/* Le film occupe le cadre comme le ferait la fenêtre du navigateur : il a été
   tourné à la taille d'un écran, ce n'est pas une page entière à translater.
   C'est `currentTime` que la molette déplace, pas la position de l'élément. */
.mockup__film {
  display: block;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: top center;
  background: var(--black);
  /* Une vidéo n'est pas cliquable, mais elle reste une cible de pointeur qui
     peut voler le curseur. Le cadre parent porte l'écouteur de molette. */
  pointer-events: none;
}

/* Signale que ça défile sans ajouter le moindre bouton. */
[data-mockup-scroll] { cursor: ns-resize; }

/* Staggered, not stacked — each frame keeps its own readable area so none of
   the labels get buried behind another.
   Quatre cadres larges qui se mordent franchement plutôt que quatre vignettes
   posées côte à côte : à taille égale et bien rangées, 29% du carré restait
   vide et plus aucune fenêtre ne dominait. Ici on retrouve les 92% de
   remplissage d'avant, à quatre.

   La contrainte qui fixe les largeurs : chaque cadre n'est recouvert que sur sa
   droite, et jamais avant 53% de sa propre largeur — c'est là que se termine le
   titre le plus long (« ...avant qu'il ne soit trop tard. » chez Scellement).
   Logos et titres restent donc tous entiers. */
.mockup--desktop {
  left: 0; top: 0;
  width: 86%;
  aspect-ratio: 16 / 10;
  z-index: 1;
}
.mockup--desktop-2 {
  right: 0; top: 20%;
  width: 54%;
  aspect-ratio: 16 / 10;
  z-index: 2;
}
.mockup--laptop {
  left: 2%; bottom: 0;
  width: 74%;
  aspect-ratio: 16 / 10;
  z-index: 3;
}
.mockup--mobile {
  right: 2%; bottom: 3%;
  width: 28%;
  aspect-ratio: 9 / 19.5;
  z-index: 4;
  border-radius: 20px;
  border-width: 5px;
  border-color: #202329;
}
.mockup--mobile .mockup__screen { height: 100%; border-radius: 14px; }

.mockup__empty {
  text-align: center;
  padding: 0.75rem;
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted-dim);
  line-height: 1.7;
  overflow-wrap: anywhere;
}
/* The phone mockup is far too narrow for a tracked-out label on small screens */
.mockup--mobile .mockup__empty {
  font-size: 0.5rem;
  letter-spacing: 0.08em;
  padding: 0.4rem;
}

/* ==========================================================================
   Contact
   ========================================================================== */

.contact__layout {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: clamp(2.5rem, 6vw, 6rem);
}

.form {
  display: grid;
  gap: clamp(1.1rem, 2vw, 1.6rem);
  min-width: 0;
  padding: clamp(1.25rem, 2.5vw, 2.25rem);
  border: 1px solid rgba(0, 128, 255, 0.28);
  border-top: 2px solid var(--blue);
  background: linear-gradient(145deg, rgba(0, 128, 255, 0.13), transparent 65%), var(--bg-raise);
}
.form__heading {
  display: block;
  color: var(--blue-soft);
  font-size: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  margin: 0 0 0.25rem;
}
.form .field input:not([type="radio"]),
.form .field select,
.form .field textarea { border-bottom-color: rgba(0, 128, 255, 0.35); }
.form .field input:focus,
.form .field select:focus,
.form .field textarea:focus { border-bottom-color: var(--blue); }
.form__row {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(1.1rem, 2vw, 1.6rem);
}

.field { display: flex; flex-direction: column; gap: 0.55rem; }
.field label {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.field input,
.field select,
.field textarea {
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--line-strong);
  padding: 0.75rem 0;
  font-size: 0.95rem;
  color: var(--white);
  transition: border-color .35s var(--ease);
  border-radius: 0;
}
.field textarea { resize: vertical; min-height: 110px; }
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-bottom-color: var(--blue);
}
.field input::placeholder,
.field textarea::placeholder { color: var(--muted-dim); }
.field select { cursor: pointer; }
.field select option { background: var(--anthracite); color: var(--white); }

/* Service picker — chips, not a dropdown, so it stays fast */
.chips { display: flex; flex-wrap: wrap; gap: 0.6rem; }
.chip { position: relative; }
.chip input {
  position: absolute; opacity: 0;
  width: 100%; height: 100%; left: 0; top: 0;
  cursor: pointer; margin: 0;
}
.chip span {
  display: block;
  padding: 0.7rem 1.25rem;
  border: 1px solid var(--line-strong);
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  transition: all .3s var(--ease);
  pointer-events: none;
}
.chip input:checked + span {
  border-color: var(--blue);
  background: var(--blue-wash);
  color: var(--white);
}
.chip input:focus-visible + span { outline: 2px solid var(--blue); outline-offset: 3px; }

.contact__aside { display: flex; flex-direction: column; gap: 2rem; }
.contact__block { border-top: 1px solid var(--line); padding-top: 1.25rem; }
.contact__label {
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted-dim);
  margin-bottom: 0.6rem;
}
.contact__value { font-size: 1rem; color: var(--white); overflow-wrap: anywhere; }
.contact__value a:hover { color: var(--blue-soft); }

.form__note { font-size: 0.76rem; color: var(--muted-dim); }
.form__status { font-size: 0.85rem; min-height: 1.4em; }
.form__status.ok { color: var(--blue-soft); }
.form__status.err { color: #FF6B6B; }

.field-error { border-bottom-color: #FF6B6B !important; }

/* ==========================================================================
   Footer
   ========================================================================== */

.footer {
  background: var(--black);
  border-top: 1px solid var(--line);
  padding-top: clamp(3.5rem, 7vw, 6rem);
  padding-bottom: 2.5rem;
}
.footer__top {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1fr;
  gap: clamp(2rem, 4vw, 3.5rem);
  padding-bottom: clamp(3rem, 6vw, 5rem);
}
.footer__logo {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin-bottom: 1.5rem;
}
.footer__logo img {
  height: 54px; width: auto;
  filter: drop-shadow(0 0 16px rgba(0, 128, 255, 0.3));
}
.footer__logo .lockup__peak { font-size: 1.85rem; }
.footer__logo .lockup__production {
  font-size: 0.63rem;
  margin-top: 0.45em;
}
.footer__tagline {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
}
.footer__col h4 {
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted-dim);
  margin-bottom: 1.25rem;
}
.footer__col ul { list-style: none; display: grid; gap: 0.7rem; }
.footer__col a, .footer__col li { font-size: 0.88rem; color: var(--text); }
.footer__col a:hover { color: var(--blue-soft); }

.footer__bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem;
  padding-top: 2rem;
  border-top: 1px solid var(--line);
  font-size: 0.72rem;
  color: var(--muted-dim);
  letter-spacing: 0.06em;
}

/* ==========================================================================
   Scroll reveals
   ========================================================================== */

[data-reveal] {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity .9s var(--ease-out), transform .9s var(--ease-out);
  transition-delay: var(--d, 0ms);
}
[data-reveal].is-in { opacity: 1; transform: none; }

/* Image reveal: a blue wipe uncovers the frame */
[data-reveal-img] { position: relative; overflow: hidden; }
[data-reveal-img]::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--bg);
  transform-origin: right;
  transition: transform 1.1s var(--ease-out);
  z-index: 3;
  pointer-events: none;
}
[data-reveal-img].is-in::after { transform: scaleX(0); }

/* ==========================================================================
   Responsive
   ========================================================================== */

/* Slightly tighter central panels give the two edge photographs more room. */
@media (min-width: 1281px) {
  .hero__panel:first-child,
  .hero__panel:last-child { flex-grow: 1.2; }
}

/* Five columns need the room; below this the fifth is a sliver. */
@media (max-width: 1280px) {
  .hero__panel:nth-child(n+5) { display: none; }
}

@media (max-width: 1100px) {
  .section__head--split { grid-template-columns: 1fr; align-items: start; }
  .web__layout { grid-template-columns: 1fr; }
  .contact__layout { grid-template-columns: 1fr; }
  .footer__top { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 900px) {
  .nav, .header__cta { display: none; }
  .burger { display: flex; }

  .services { grid-template-columns: 1fr; }
  .service { min-height: auto; }

  .hero__scroll { display: none; }

  /* Straight to two here: more columns at this width are ~300px each, a
     1:3.4 sliver, and the outer ones get crushed by the edge feather. */
  .hero__panel:nth-child(n+3) { display: none; }

  .hero__panel { transform: skewX(-5deg); }
  .hero__panel img { transform: skewX(5deg) scale(1.19); }

  /* Portfolio collapses to a readable 2-up / full-width rhythm */
  .work--wide  { grid-column: span 12; aspect-ratio: 16 / 9; }
  .work--two3  { grid-column: span 12; aspect-ratio: 16 / 10; }
  .work--half  { grid-column: span 6;  aspect-ratio: 3 / 4; }
  .work--third { grid-column: span 6;  aspect-ratio: 9 / 16; }

  .work::before { display: none; }

  .mockups { aspect-ratio: 1 / 1; }
}

@media (max-width: 620px) {
  /* ---- Rythme vertical ----
     88px top and bottom put 176px of dead space between every section — over a
     third of a phone screen, six times down the page. Measured with
     tools/mobile-audit.js. */
  :root { --section-y: 3.5rem; }

  /* Mobile only: retain the compact photo crop and lift the copy over it.
     Photo height is independent of the text, so the subjects never zoom in. */
  .hero { display: block; min-height: 0; align-items: stretch; }

  .hero__media {
    position: relative;
    inset: auto;
    height: clamp(17rem, 73vw, 23rem);
    overflow: hidden;
    /* Keep the narrow mobile montage steady while scrolling. */
    transform: none !important;
  }

  .hero__panels {
    position: absolute;
    /* Débord latéral : le cisaillement vertical ne doit jamais laisser
       apparaître la page derrière. */
    inset: 0 -5%;
    flex-direction: row;
  }

  .hero__panel { flex: 1 1 0; transform: skewX(-4deg); }
  .hero__panel img { transform: skewX(4deg) scale(1.18); object-position: center; }
  /* Offset the worker toward the visible centre of the cropped outer panel.
     Override the inline desktop framing only on phones. */
  .hero__panel:nth-child(5) img { object-position: 35% 36% !important; }

  /* Restore all five shoots hidden by the tablet breakpoint. */
  .hero__panel:nth-child(n+3) { display: block; }
  .hero__panel:nth-child(3) { display: block; }

  /* Fine vertical joins echo the desktop montage without darkening each strip. */
  .hero__panel::after,
  .hero__panel:first-child::after,
  .hero__panel:last-child::after {
    background: linear-gradient(
      to right,
      rgba(8, 9, 10, 0.24) 0%, transparent 16%,
      transparent 84%, rgba(8, 9, 10, 0.24) 100%
    );
  }
  /* Même retrait que sur desktop. Sur un écran de 390px, cinq traits bleus à
     0.95 d'opacité tenaient plus de place que les photos qu'ils séparaient. */
  .hero__panel + .hero__panel::before {
    left: 0; right: auto; top: 0; bottom: 0;
    /* Même lame bleue qu'en desktop, à l'échelle du téléphone : trait fin sur
       des panneaux d'une centaine de pixels, halo raccourci. */
    width: 1px; height: auto;
    /* Le faisceau tourne aussi sur téléphone — c'est là que le hero est le
       plus statique, le reste tient dans un bandeau de 20rem. Calque à 40% au
       lieu de 45% : le bandeau est court, et une traînée qui en couvre presque
       la moitié ne se lirait plus comme un passage. Les seuils de la course
       (-82% / 127% en desktop) deviennent -67% / 117% à 40%, donc les mêmes
       -90% / 135% conviennent : une seule @keyframes pour les deux formats. */
    background:
      linear-gradient(
        var(--sens, to bottom),
        rgba(40, 130, 235, 0) 0%,
        rgba(45, 140, 240, 0.055) 25%,
        rgba(55, 155, 250, 0.12) 55%,
        rgba(70, 175, 255, 0.21) 80%,
        rgba(85, 185, 255, 0.28) 92%,
        rgba(70, 175, 255, 0.155) 98%,
        rgba(50, 150, 250, 0) 100%
      ) no-repeat,
      linear-gradient(
        to bottom,
        rgba(40, 130, 235, 0) 0%,
        rgba(45, 140, 240, 0.12) 18%,
        rgba(60, 160, 250, 0.18) 42%,
        rgba(45, 140, 240, 0.09) 70%,
        rgba(40, 130, 235, 0) 95%
      );
    background-size: 100% 40%, 100% 100%;
    background-position: 0 -90%, 0 0;
    /* box-shadow illuminerait la colonne entière, faisceau ou pas ;
       drop-shadow suit l'alpha peint, donc le halo voyage avec la traînée. */
    box-shadow: none;
    filter: drop-shadow(0 0 4px rgba(55, 155, 255, 0.18))
            drop-shadow(0 0 11px rgba(0, 120, 255, 0.12));
  }

  /* Keep the upper photos bright, darken only the text/button area. */
  .hero__media::after {
    background: linear-gradient(
      to bottom,
      rgba(8, 9, 10, 0.36) 0%, rgba(8, 9, 10, 0.05) 18%,
      rgba(8, 9, 10, 0.08) 40%, rgba(8, 9, 10, 0.70) 64%,
      rgba(8, 9, 10, 0.92) 85%, var(--bg) 100%
    );
  }

  .hero__inner { margin-top: -7rem; padding-top: 0; padding-bottom: 1.5rem; }
  .hero__title { font-size: clamp(2rem, 8.8vw, 3.4rem); line-height: 1; margin-bottom: 1rem; }
  .hero__lede { font-size: 0.92rem; line-height: 1.5; margin-bottom: 1.25rem; }
  .hero__actions { gap: 0.65rem; }
  .hero__actions .btn { width: 100%; min-height: 46px; padding-block: 0.8rem; }
  .hero__eyebrow { font-size: 0.62rem; letter-spacing: 0.18em; margin-bottom: 1rem; gap: 0.6rem; }
  .hero__eyebrow::before { width: 44px; }

  /* ---- Titres de section ---- */
  .section__head { margin-bottom: 1.75rem; }

  /* ---- Services ---- */
  .services { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .service { min-width: 0; padding: 1.1rem clamp(0.75rem, 3vw, 1.15rem); }
  /* Keep the hover treatment visible on phones without looping animation. */
  .service { background: var(--bg-raise); }
  .service .service__bg { opacity: 0.54; transform: scale(1); }
  .service .service__bg::after { opacity: 0.92; }
  .service::before {
    background-size: 100% 2px, 100% 2px;
    filter: drop-shadow(0 0 7px rgba(77, 166, 255, 0.7));
  }
  .service::after { opacity: 1; }
  .service .service__num { -webkit-text-stroke-color: var(--blue); }
  .service__num { font-size: 1.6rem; margin-bottom: 0.85rem; }
  .service__title { font-size: clamp(1.4rem, 6.5vw, 2.4rem); line-height: 1.05; }
  .service__desc { font-size: 0.78rem; line-height: 1.55; margin-bottom: 1rem; }
  .service__list { grid-template-columns: 1fr; gap: 0.45rem; padding-bottom: 1rem; }
  .service__list li { font-size: 0.72rem; line-height: 1.45; padding-left: 0.75rem; }
  .service .link-underline { min-height: 44px; font-size: 0.66rem; line-height: 1.5; letter-spacing: 0.04em; gap: 0.35rem; }

  /* ---- Réalisations ----
     Two up rather than full width. Seven full-bleed 9:16 tiles made the section
     4900px tall — about twelve screens of scrolling. Paired, each is still a
     native 9:16 video and the section halves. */
  .work--third { grid-column: span 6; aspect-ratio: 9 / 16; }
  .work--half  { grid-column: span 12; aspect-ratio: 4 / 3; }
  /* Two-up tiles are 171px wide, and the year sitting to the right stole
     enough of that for 'Contenu réseaux sociaux' to wrap onto three lines.
     Stacking the overlay gives the type the full width. */
  .work__overlay {
    padding: 0.65rem 0.7rem;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.15rem;
  }
  .work__client { margin-bottom: 0.25rem; }
  /* Les tuiles verticales ne font que 171px de large, dont 149 utilisables une
     fois la marge du bandeau retirée. À sa taille de bureau, le logo
     Sharpshooter en fait 206 à lui seul : il déborderait. `zoom` plutôt qu'une
     hauteur fixe, parce qu'il réduit les six dans la même proportion et garde
     donc la normalisation à surface égale intacte. */
  .work .work__logo { zoom: 0.5; margin-bottom: 0.35rem; }
  .work__type {
    font-size: clamp(0.42rem, 5cqw, 0.7rem);
    letter-spacing: 0.02em;
    white-space: nowrap;
    line-height: 1.4;
  }
  .work__year { font-size: 0.7rem; letter-spacing: 0.09em; }
  /* The wide and site tiles keep the desktop arrangement — they have the room. */
  .work--wide .work__overlay,
  .work--site .work__overlay {
    flex-direction: row;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1rem;
  }
  .filter { font-size: 0.75rem; padding: 0.7rem 1.05rem; }
  .filters { gap: 0.4rem; }

  /* ---- Behind the scenes ---- */
  .bts__item { width: clamp(120px, 36vw, 180px); }
  .bts__track { gap: 12px; }
  .bts__quote { font-size: 1rem; }

  /* ---- Création web ---- */
  .mockups { aspect-ratio: 1 / 1; }
  /* La capture XL Gym faisait 1249x712, une proportion à elle seule, et il
     fallait la laisser en `contain` pour ne pas lui rogner les côtés. Elle est
     maintenant en 1600x1000 comme les trois autres : plus rien à compenser. */

  /* ---- Contact ---- */
  #contact { padding-block: 2.25rem; }
  #contact .section__head { gap: 0.7rem; margin-bottom: 1rem; }
  #contact .d-xl { font-size: clamp(2rem, 9vw, 3rem); }
  .contact__layout { gap: 1.25rem; min-width: 0; }
  .contact__aside { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.65rem 1rem; }
  .contact__aside > .lede { grid-column: 1 / -1; font-size: 0.85rem; line-height: 1.5; }
  .contact__aside > .contact__block:nth-child(2) { grid-column: 1 / -1; }
  .contact__block { min-width: 0; padding-top: 0.55rem; }
  .contact__label { margin-bottom: 0.15rem; letter-spacing: 0.1em; }
  #contact .contact__value { font-size: 0.83rem; }
  #contact .contact__value a { min-height: 44px; display: inline-flex; align-items: center; padding-block: 0.35rem; }
  .form { gap: 0.8rem; padding: 1rem; }
  .form__heading { display: block; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.12em; margin: 0 0 0.25rem; }
  .form__row { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.75rem; }
  .field { min-width: 0; gap: 0.2rem; }
  .field label { font-size: 0.65rem; letter-spacing: 0.07em; }
  .field input:not([type="radio"]), .field select, .field textarea { width: 100%; min-width: 0; font-size: 16px; padding: 0.45rem 0; min-height: 44px; }
  .field textarea { height: 80px; min-height: 80px; }
  .form .chips { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.35rem; }
  .form .chip span { height: 100%; min-height: 44px; display: flex; align-items: center; justify-content: center; padding: 0.4rem 0.2rem; text-align: center; font-size: 0.62rem; line-height: 1.35; letter-spacing: 0; }
  .form .btn { min-height: 46px; padding: 0.8rem 0.5rem; font-size: 0.68rem; }
  .form__status:empty { display: none; }
  .form__note { margin-top: 0.6rem; font-size: 0.68rem; line-height: 1.45; overflow-wrap: anywhere; }

  /* ---- Pied de page ---- */
  .footer { padding-top: 0.85rem; padding-bottom: 0.55rem; }
  .footer__top { grid-template-columns: 1.25fr 0.85fr 1fr; gap: 0.45rem 0.65rem; }
  .footer__brand { grid-column: 1 / -1; display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; }
  .footer__logo { margin-bottom: 0; gap: 0.45rem; flex-shrink: 0; }
  .footer__logo img { height: 32px; }
  .footer__logo .lockup__peak { font-size: 1.2rem; }
  .footer__logo .lockup__production { font-size: 0.41rem; }
  .footer__tagline { max-width: 16ch; font-size: 0.56rem; line-height: 1.45; letter-spacing: 0.06em; text-align: right; }
  .footer__col { min-width: 0; margin-top: 0.4rem; }
  .footer__col h4 { font-size: 0.6rem; letter-spacing: 0.07em; margin-bottom: 0; }
  .footer__col a, .footer__col li { font-size: 0.74rem; line-height: 1.4; }
  .footer .footer__col li a { display: flex; align-items: center; min-height: 32px; padding-block: 0.25rem; }
  .footer__bottom { padding-top: 0.45rem; gap: 0.1rem; font-size: 0.6rem; line-height: 1.4; letter-spacing: 0; }

  /* ---- Zones tactiles ----
     A 29px-high text link is below the 44px minimum; the padding is vertical
     only so nothing shifts horizontally. */
  .link-underline { padding-top: 0.55rem; padding-bottom: 0.95rem; }
  .logo { padding-block: 0.4rem; }
  /* The links carry their own tap padding now, so the list gap would double
     the spacing — it added 150px to the footer. */
  .footer__col li a,
  .contact__value a { display: inline-block; padding-block: 0.55rem; }
  .footer__col ul { gap: 0; }
  .footer__top { padding-bottom: 0.3rem; }

  /* ---- Planchers de lisibilité ----
     Tracked-out uppercase reads smaller than its size suggests; below ~11px it
     stops being legible on a phone. */
  .bts__hint, .contact__label { font-size: 0.72rem; }
  /* .logo .lockup__* is more specific than .lockup__*, so the override has to
     match it — the first attempt at this silently lost the cascade. */
  .logo .lockup__peak { font-size: 1.2rem; }
  .logo .lockup__production { font-size: 0.46rem; }
}

/* ==========================================================================
   Reduced motion — honour it properly, not just partially
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  [data-reveal] { opacity: 1; transform: none; }
  [data-reveal-img]::after { display: none; }
  .header.is-hidden { transform: none; }
}
