@charset "UTF-8";
/* ===========================================================================
   Tom David — Portfolio
   Direction : galerie éditoriale sombre. Les paysages peints font la lumière,
   la typographie fait la structure, une ligne d'horizon vermillon fait le lien.
   Statique, sans dépendance, sans framework.
   =========================================================================== */

/* --- 1. Jetons -------------------------------------------------------------- */

:root {
  /* Palette extraite des 12 paysages */
  --void: #04070e;
  --ink: #080e1b;
  --ink-2: #0c1526;
  --indigo: #0e2750;
  --indigo-deep: #091a36;

  /* Verts échantillonnés sur les collines de l'image 04 :
     --moss      = moyenne des versants        (#1b4519)
     --moss-deep = ombre de premier plan       (#113712)
     --moss-lit  = crête éclairée, pour accents (#5b8233) */
  --moss: #1b4519;
  --moss-deep: #113712;
  --moss-lit: #5b8233;
  --vermillion: #de4126;
  --ember: #ff6a45;
  --coral: #ef8a70;
  --bone: #f4eee3;

  --bone-80: rgba(244, 238, 227, 0.8);
  --bone-60: rgba(244, 238, 227, 0.58);
  --bone-40: rgba(244, 238, 227, 0.38);
  --bone-20: rgba(244, 238, 227, 0.18);
  --rule: rgba(244, 238, 227, 0.13);
  --rule-soft: rgba(244, 238, 227, 0.07);

  /* Typographie — LT Superior Serif porte tout le site. Les trois jetons
     historiques pointent désormais sur la même famille : c'est ce qui permet
     de garder les rôles (titrage / texte / micro-labels) sans réécrire chaque
     règle, et de revenir en arrière sur un seul rôle si besoin. */
  --serif: 'Superior Serif', 'Iowan Old Style', Palatino, Georgia, serif;
  --sans: 'Superior Serif', 'Iowan Old Style', Palatino, Georgia, serif;
  --mono: 'Superior Serif', 'Iowan Old Style', Palatino, Georgia, serif;

  /* Réservé au titre « SW DESIGN » du héros, seule exception demandée. */
  --sans-display: 'Instrument Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;

  --t-display: clamp(3.4rem, 11vw, 10.5rem);
  --t-h2: clamp(2.1rem, 5.2vw, 4.3rem);
  --t-h3: clamp(1.35rem, 2.2vw, 1.85rem);
  --t-lede: clamp(1.05rem, 1.55vw, 1.32rem);
  --t-body: clamp(0.98rem, 1.05vw, 1.06rem);
  --t-label: clamp(0.66rem, 0.78vw, 0.74rem);

  /* Rythme */
  --gutter: clamp(1.25rem, 5vw, 4.5rem);
  --shell: 1380px;
  --rail: 12.5rem;
  --band: clamp(5.5rem, 12vh, 9.5rem);

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  /* Courbe relevée dans le CSS de monopo.london (easeOutQuart) : départ franc,
     arrivée très amortie. C'est elle qui donne ce toucher-là. */
  --ease-monopo: cubic-bezier(0.165, 0.84, 0.44, 1);
}

/* --- 2. Réinitialisation ---------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 6rem;
  /* La gouttière de la barre de défilement est réservée en permanence.
     Sans elle, verrouiller puis déverrouiller le défilement (ouverture, menu
     mobile) fait apparaître la barre et recompose TOUTE la page 15 px plus
     étroite en une seule image : le héros en cover se recadre, le titre
     dimensionné en vw change de taille. C'était le saut visible à la fin du
     voile. */
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  background: var(--void);
  color: var(--bone);
  font-family: var(--sans);
  font-size: var(--t-body);
  line-height: 1.62;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  text-rendering: optimizeLegibility;
}

body.is-locked {
  overflow: hidden;
}

img {
  max-width: 100%;
  display: block;
}

h1,
h2,
h3,
h4,
p,
figure,
blockquote,
ul,
ol {
  margin: 0;
}

ul,
ol {
  padding: 0;
  list-style: none;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

::selection {
  background: var(--vermillion);
  color: var(--bone);
}

:focus-visible {
  outline: 2px solid var(--ember);
  outline-offset: 3px;
  border-radius: 1px;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- 2b. Voile d'ouverture -------------------------------------------------
   Écran bleu nuit avec le seul monogramme, qui se relève ensuite sur le héros.
   Entièrement en CSS : le JS ne fait que déverrouiller le défilement et retirer
   le voile à la fin. Il n'apparaît que si .js est présent sur <html>, donc
   jamais sans JavaScript.
   Chronologie : logo révélé à 120 ms (600 ms), maintenu, effacé à 900 ms,
   puis le voile monte à 1150 ms sur 850 ms. Total 2 s. */

.intro {
  display: none;
}

html.js .intro {
  display: grid;
  place-items: center;
  position: fixed;
  inset: 0;
  z-index: 100;
  /* aucun contenu cliquable, et le défilement est bloqué par html.is-intro */
  pointer-events: none;
  background: linear-gradient(180deg, #04070e 0%, #071328 58%, #091a36 100%);
  animation: intro-dissolve 900ms var(--ease-monopo) 1120ms forwards;
  will-change: opacity, transform, filter;
}

.intro__mark {
  width: clamp(4.5rem, 11vw, 7.5rem);
  height: auto;
  opacity: 0;
  transform: translateY(0.5rem);
  animation: intro-mark-in 600ms var(--ease-monopo) 120ms forwards,
    intro-mark-out 320ms var(--ease) 900ms forwards;
}

@keyframes intro-mark-in {
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes intro-mark-out {
  to {
    opacity: 0;
    transform: translateY(-0.4rem);
  }
}

/* Dissolution sur place, sans direction : le voile s'évapore en s'écartant
   légèrement et en se défocalisant, au lieu de coulisser comme un rideau. */
@keyframes intro-dissolve {
  to {
    opacity: 0;
    transform: scale(1.08);
    filter: blur(14px);
  }
}

/* Le héros part légèrement agrandi et se pose pendant la dissolution. C'est ce
   décalage entre les deux plans qui donne la sensation d'entrer dans la page
   plutôt que de voir un panneau s'enlever.

   Une animation et non une transition : une transition se déclenche au moment
   où la propriété change, donc au retrait de la classe — son délai décalait
   l'atterrissage bien après la dissolution. L'animation part du chargement de
   la page, exactement comme celle du voile, donc les deux sont synchrones. */
html.js .hero {
  /* Même départ ET même fin que la dissolution du voile : les deux plans
     s'arrêtent sur la même image, il n'y a plus d'animation qui traîne sur un
     sous-arbre lourd après la révélation. */
  animation: hero-settle 900ms var(--ease-monopo) 1120ms both;
}

/* Pas de will-change : le retirer en fin d'ouverture dé-promeut la couche du
   héros, ce qui force un ré-échantillonnage du titre — lui-même rendu via
   background-clip: text — et produit un léger scintillement au pire moment.
   Le navigateur promeut de lui-même pendant une animation de transform. */

@keyframes hero-settle {
  from {
    transform: scale(1.06);
  }
  to {
    transform: none;
  }
}

/* Le grain couvre tout l'écran en mix-blend-mode : au-dessus d'un sous-arbre
   masqué en cours de mise à l'échelle, il force une recomposition complète à
   chaque image. On le retire pendant l'ouverture. */
/* Le défilement n'est PLUS bloqué par overflow: hidden pendant l'ouverture.
   Raison : scrollbar-gutter est ignoré quand overflow vaut hidden, donc la
   gouttière disparaissait au verrouillage et réapparaissait au déverrouillage —
   la page se recomposait 15 px plus étroite en une image, ce qui était le saut
   visible à la fin du voile. Le blocage se fait maintenant par interception des
   gestes dans app.js : aucun changement de mise en page. */

/* Le titre monte PENDANT la dissolution et se termine 250 ms avant qu'elle
   s'achève : il est donc déjà en place quand le voile s'efface.
   La version précédente le mettait en pause puis le relançait à la fin — il
   démarrait 250 ms après la révélation et mettait 1,4 s, ce qui donnait
   exactement l'impression d'arriver sur un héros sans titre. */
html.js .hero__word {
  animation-delay: 620ms;
  animation-duration: 1150ms;
}

/* Mouvement réduit : pas de voile du tout, on entre directement sur le site. */
@media (prefers-reduced-motion: reduce) {
  html.js .intro {
    display: none;
  }
}

/* --- 3. Grain global ------------------------------------------------------- */
/* Le grain fait écho à la matière des paysages sources. */

.grain {
  position: fixed;
  inset: -50%;
  z-index: 60;
  pointer-events: none;
  mix-blend-mode: overlay;
  /* Fondu à l'arrivée : voir la règle html:not(.is-intro) plus bas. */
  opacity: 0;
  transition: opacity 700ms var(--ease);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* Le grain était animé en steps(6) sur 7s : la texture, qui couvre tout l'écran
   en mix-blend-mode, sautait donc d'un bloc SIX fois toutes les sept secondes.
   C'était un à-coup plein cadre permanent, pas seulement après l'ouverture.
   Grain fixe désormais. */

/* Arrivée en fondu après l'ouverture. Il était en display:none pendant celle-ci
   et réapparaissait d'un coup à la fin : un changement instantané sur tout le
   viewport, soit le plus gros saut mesuré de toute la séquence. */
html:not(.is-intro) .grain {
  opacity: 0.05;
}


/* --- 4. Primitives typographiques ------------------------------------------ */

.label {
  font-family: var(--mono);
  font-size: var(--t-label);
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--bone-40);
  display: flex;
  align-items: center;
  gap: 0.7em;
  white-space: nowrap;
}

.label::before {
  content: '';
  width: 1.6em;
  height: 1px;
  background: var(--vermillion);
  flex: none;
}

.label--bare::before {
  display: none;
}

.display {
  font-family: var(--serif);
  font-weight: 400;
  line-height: 0.94;
  letter-spacing: -0.022em;
  text-wrap: balance;
}

/* LT Superior Serif ne fournit aucune italique. Laisser font-style: italic
   pousserait le navigateur à fabriquer une fausse oblique en cisaillant les
   glyphes — sur une serif à fort contraste, le résultat est franchement laid.
   L'emphase passe donc par le poids et la couleur, pas par la pente.
   La règle vise tous les <em> des titres, pas seulement ceux de .display :
   les h2 d'Approche, Travaux et Contact n'ont pas cette classe. */
.display em,
.approach h2 em,
.contact h2 em {
  font-style: normal;
  font-weight: 600;
  color: var(--coral);
}

.lede {
  font-size: var(--t-lede);
  line-height: 1.52;
  color: var(--bone-80);
  text-wrap: pretty;
  max-width: 34ch;
}

.prose p + p {
  margin-top: 1.15em;
}

.prose {
  color: var(--bone-60);
  max-width: 58ch;
  text-wrap: pretty;
}

.prose strong {
  color: var(--bone);
  font-weight: 500;
}

/* Motif horizon : le trait récurrent du site. */
.horizon {
  position: relative;
  height: 1px;
  background: linear-gradient(90deg, var(--rule) 0%, var(--rule-soft) 78%, transparent 100%);
  border: 0;
  margin: 0;
}

.horizon::after {
  content: '';
  position: absolute;
  left: 0;
  top: -2px;
  width: 5px;
  height: 5px;
  background: var(--vermillion);
}

/* --- 5. Coquille ----------------------------------------------------------- */

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.band {
  padding-block: var(--band);
  position: relative;
}

/* --- 5b. La descente ------------------------------------------------------
   Un seul dégradé pour tout l'après-héros : du noir bleuté de la page vers le
   vert des collines de l'image 04. Les paliers ne suivent pas une
   interpolation droite entre les deux extrêmes — elle passerait par des
   turquoises boueux. Ils montent le vert en gardant le bleu bas.
   Le pied reprend exactement la couleur d'arrivée, sans rupture. */

.descent {
  background: linear-gradient(
    180deg,
    var(--void) 0%,
    #06120e 22%,
    #0a1f11 44%,
    #0f2c13 64%,
    #163916 84%,
    var(--moss) 100%
  );
}

.spread {
  display: grid;
  gap: clamp(1.75rem, 4vw, 3.5rem);
}

@media (min-width: 62rem) {
  .spread {
    grid-template-columns: var(--rail) minmax(0, 1fr);
    gap: clamp(2rem, 5vw, 5rem);
  }
  .spread__rail {
    position: sticky;
    top: 7.5rem;
    align-self: start;
  }
}

/* --- 6. Cadres image ------------------------------------------------------- */

.frame {
  position: relative;
  overflow: hidden;
  background-color: var(--ink-2);
  background-size: cover;
  background-position: center;
  /* le LQIP inline est flouté par mise à l'échelle */
}

.frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transform: scale(1.04);
  transition: opacity 1.1s var(--ease), transform 1.6s var(--ease);
}

.frame img.is-loaded {
  opacity: 1;
  transform: scale(1);
}

/* voile pour garder le texte lisible sur l'image */
.frame--veiled::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(4, 7, 14, 0.55) 0%,
    rgba(4, 7, 14, 0.12) 34%,
    rgba(4, 7, 14, 0.62) 82%,
    rgba(4, 7, 14, 0.92) 100%
  );
}

/* --- 7. Masthead ---------------------------------------------------------- */

.masthead {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: clamp(1rem, 2.2vw, 1.6rem) var(--gutter);
  transition: background-color 0.5s var(--ease), backdrop-filter 0.5s var(--ease),
    border-color 0.5s var(--ease);
  border-bottom: 1px solid transparent;
}

/* Au défilement : fond translucide flouté + filet. Sans lui, la navigation se
   superpose au texte des sections et devient illisible. */
.masthead.is-stuck {
  background: rgba(4, 7, 14, 0.72);
  backdrop-filter: blur(14px) saturate(130%);
  -webkit-backdrop-filter: blur(14px) saturate(130%);
  border-bottom-color: var(--rule-soft);
}

.mark {
  display: inline-flex;
  align-items: center;
  line-height: 1;
  /* le SVG est monochrome et déjà à la couleur de l'encre du site */
  transition: opacity 0.3s var(--ease);
}

.mark:hover {
  opacity: 0.72;
}

.mark img {
  height: clamp(1.5rem, 2.1vw, 1.85rem);
  width: auto;
}

.mark span {
  color: var(--vermillion);
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.2em;
  transform: translateY(-0.35em);
}

.nav {
  display: none;
}

@media (min-width: 56rem) {
  .nav {
    display: flex;
    align-items: center;
    gap: clamp(1rem, 2.4vw, 2.2rem);
  }
}

.nav a {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--bone-60);
  position: relative;
  padding-block: 0.35rem;
  transition: color 0.3s var(--ease);
}

.nav a::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  height: 1px;
  width: 100%;
  background: var(--vermillion);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s var(--ease-out);
}

.nav a:hover,
.nav a[aria-current='true'] {
  color: var(--bone);
}

.nav a:hover::after,
.nav a[aria-current='true']::after {
  transform: scaleX(1);
}

.masthead__end {
  display: flex;
  align-items: center;
  gap: clamp(0.75rem, 1.6vw, 1.4rem);
}

/* Sélecteur de langue */
.lang {
  display: flex;
  align-items: center;
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.14em;
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0.28rem;
}

.lang button {
  padding: 0.22rem 0.62rem;
  border-radius: 999px;
  color: var(--bone-40);
  transition: color 0.3s var(--ease), background-color 0.3s var(--ease);
  line-height: 1.4;
}

.lang button:hover {
  color: var(--bone);
}

.lang button[aria-pressed='true'] {
  background: var(--vermillion);
  color: var(--bone);
}

/* Bouton menu mobile */
.burger {
  width: 2.2rem;
  height: 2.2rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--rule);
  border-radius: 999px;
}

@media (min-width: 56rem) {
  .burger {
    display: none;
  }
}

.burger i {
  display: block;
  width: 0.85rem;
  height: 1px;
  background: var(--bone);
  position: relative;
  transition: transform 0.35s var(--ease), background-color 0.35s var(--ease);
}

.burger i::before,
.burger i::after {
  content: '';
  position: absolute;
  left: 0;
  width: 100%;
  height: 1px;
  background: var(--bone);
  transition: transform 0.35s var(--ease), opacity 0.2s var(--ease);
}

.burger i::before {
  top: -4px;
}
.burger i::after {
  top: 4px;
}

.burger[aria-expanded='true'] i {
  background: transparent;
}
.burger[aria-expanded='true'] i::before {
  transform: translateY(4px) rotate(45deg);
}
.burger[aria-expanded='true'] i::after {
  transform: translateY(-4px) rotate(-45deg);
}

/* Panneau mobile */
.drawer {
  position: fixed;
  inset: 0;
  z-index: 45;
  background: var(--void);
  display: grid;
  align-content: center;
  padding: var(--gutter);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.45s var(--ease), visibility 0.45s;
}

.drawer.is-open {
  opacity: 1;
  visibility: visible;
}

.drawer ol {
  display: grid;
  gap: 0.35rem;
  counter-reset: d;
}

.drawer a {
  font-family: var(--serif);
  font-size: clamp(2rem, 11vw, 3.2rem);
  line-height: 1.12;
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  opacity: 0;
  transform: translateY(0.5rem);
}

.drawer.is-open a {
  animation: rise 0.6s var(--ease-out) forwards;
}

.drawer li:nth-child(1) a {
  animation-delay: 0.08s;
}
.drawer li:nth-child(2) a {
  animation-delay: 0.14s;
}
.drawer li:nth-child(3) a {
  animation-delay: 0.2s;
}
.drawer li:nth-child(4) a {
  animation-delay: 0.26s;
}
.drawer li:nth-child(5) a {
  animation-delay: 0.32s;
}

.drawer a::before {
  counter-increment: d;
  content: '0' counter(d);
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.18em;
  color: var(--vermillion);
}

@keyframes rise {
  to {
    opacity: 1;
    transform: none;
  }
}

/* --- 8. Héros ------------------------------------------------------------- */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  isolation: isolate;
  padding-bottom: clamp(2.5rem, 6vh, 4.5rem);
  overflow: hidden;
}

.hero__stage {
  position: absolute;
  inset: 0;
  /* Ciel prolongé au-dessus de la scène. Le palier bas est la couleur exacte
     du haut de bg-04 (#03366a, moyenne de la première ligne), d'où l'absence
     de raccord ; il s'assombrit vers le haut pour donner de la profondeur. */
  background: linear-gradient(180deg, #011c3d 0%, #022a55 55%, #03366a 100%);
}

/* Hauteur de la bande de ciel ajoutée en haut du héros. */
.hero__scene {
  position: absolute;
  inset: var(--sky-band, 14%) 0 0 0;
  /* aperçu flou le temps que l'image arrive */
  background: var(--lqip-04) center / cover no-repeat;
  /* Le haut de la scène se fond dans le ciel CSS. Compter sur une égalité de
     couleur exacte au raccord est fragile (la dérive rezoome l'image, le voile
     et le grain s'y superposent) : un dégradé de masque garantit l'absence de
     ligne quoi qu'il arrive. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 8%);
  mask-image: linear-gradient(180deg, transparent 0, #000 8%);
}

/* Les trois calques se superposent dans l'ordre du document.
   Chaque image porte sa propre transformation, donc son propre contexte
   d'empilement : le z-index les ordonne entre elles et autour du titre. */
.hero__layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1.2s var(--ease);
}

.hero__layer.is-loaded {
  opacity: 1;
}

.hero__plate {
  z-index: 1;
}

.hero__crest {
  z-index: 3;
}

.hero__veil {
  position: absolute;
  inset: 0;
  z-index: 4;
  background:
    /* 1. Fondu final vers --void, exactement la couleur de départ de .descent.
       Exprimé en vh et non en pourcentage : la longueur du fondu ne dépend
       donc pas de la hauteur du héros. En pourcentage il se comprimait sur
       une dizaine de pour cent et l'œil lisait son arrivée comme un bord.
       Les paliers intermédiaires adoucissent la course de l'alpha, sinon le
       vert vif des collines s'éteint trop brutalement. */
    linear-gradient(
        to top,
        var(--void) 0,
        rgba(4, 7, 14, 0.88) 7vh,
        rgba(4, 7, 14, 0.5) 20vh,
        rgba(4, 7, 14, 0.16) 34vh,
        rgba(4, 7, 14, 0) 48vh
      ),
    /* 2. Voile de lisibilité sous l'en-tête */
    linear-gradient(
      180deg,
      rgba(4, 7, 14, 0.5) 0%,
      rgba(4, 7, 14, 0.03) 26%,
      rgba(4, 7, 14, 0.1) 60%
    );
}

/* Dérive lente et continue, identique sur les deux calques d'image pour que la
   crête reste alignée avec le fond. Le titre, lui, ne bouge pas : la ligne
   d'occlusion glisse donc de quelques pixels sur les lettres, ce qui fait
   respirer l'effet. En aller-retour, et coupée si l'on demande moins de
   mouvement. */
.hero__plate,
.hero__crest {
  animation: drift 34s var(--ease) infinite alternate;
}

/* --- Titre SW DESIGN ------------------------------------------------------
   Pris entre les deux calques : le bas des lettres disparaît derrière les
   sommets enneigés. Noir plein, comme une découpe dans le ciel. */

.hero__word {
  position: absolute;
  z-index: 2;
  /* Ancré par le BAS, pas par le haut : la ligne de crête est à une fraction
     fixe de la hauteur (20,6 % pour le sommet central, l'image étant toujours
     visible sur toute sa hauteur en dessous du ratio 1,68). En ancrant le bas
     de la ligne de texte à 26 %, le point de coupe reste le même quelle que
     soit la taille de police — les lettres grandissent vers le haut. */
  /* Centré comme un seul bloc, et posé assez haut pour que seul le bas des
     lettres soit mordu par les sommets : le mot doit rester entièrement
     lisible. */
  bottom: 78%;
  left: 0;
  right: 0;
  margin: 0;
  display: flex;
  justify-content: center;
  gap: 0.3em;
  font-family: var(--sans-display);
  font-weight: 500;
  /* bornée aussi par la hauteur, sinon un écran large et court donne un mot
     disproportionné par rapport à la montagne */
  font-size: clamp(2.4rem, min(13vw, 17vh), 11rem);
  line-height: 0.86;
  letter-spacing: -0.035em;
  text-transform: uppercase;
  /* Couleur de repli, assortie au blanc gris de la neige (échantillons :
     #f7f5f8 sur les crêtes éclairées, #c1becd dans les ombres). Elle sert si
     background-clip: text n'est pas supporté — sans elle, le titre serait
     purement invisible. */
  color: #dcdae2;
  white-space: nowrap;
  user-select: none;
}

/* Remplissage granuleux : bande prélevée dans la neige de bg-04, noirs relevés
   à 150 pour qu'aucune lettre ne tombe dans une ombre illisible. Le ratio de
   la texture (5,85) est calé sur celui de la boîte du mot, donc le grain n'est
   pas étiré. */
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  .hero__word {
    background-image: url('../bg/snow-texture.webp');
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

.hero__word span {
  display: block;
}

/* L'animation d'entrée porte sur le conteneur et non sur les mots : le
   remplissage texturé appartient au conteneur, une opacité posée sur les
   <span> ne l'aurait pas fait apparaître en fondu. */
.hero__word {
  opacity: 0;
  animation: word-in 1.4s var(--ease-out) 0.25s forwards;
}

@keyframes word-in {
  from {
    opacity: 0;
    transform: translateY(0.16em);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes drift {
  from {
    transform: scale(1.03) translate3d(0, 0, 0);
  }
  to {
    transform: scale(1.09) translate3d(-1.1%, -1.2%, 0);
  }
}

/* Indice de défilement */
.cue {
  position: absolute;
  right: var(--gutter);
  bottom: clamp(2.5rem, 6vh, 4.5rem);
  display: none;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--bone-40);
  writing-mode: vertical-rl;
}

@media (min-width: 62rem) {
  .cue {
    display: flex;
  }
}

.cue::after {
  content: '';
  width: 1px;
  height: 3.5rem;
  background: linear-gradient(180deg, var(--vermillion), transparent);
  animation: cue-pulse 2.4s var(--ease) infinite;
}

@keyframes cue-pulse {
  0%,
  100% {
    transform: scaleY(0.4);
    transform-origin: top;
    opacity: 0.4;
  }
  50% {
    transform: scaleY(1);
    transform-origin: top;
    opacity: 1;
  }
}

/* --- 9. Section approche -------------------------------------------------- */

.approach h2 {
  font-family: var(--serif);
  font-size: var(--t-h2);
  line-height: 0.98;
  letter-spacing: -0.022em;
  max-width: 22ch;
  text-wrap: balance;
}

.approach__body {
  display: grid;
  gap: clamp(2rem, 5vw, 4rem);
  margin-top: clamp(2rem, 5vh, 3.25rem);
}

@media (min-width: 62rem) {
  .approach__body {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
    align-items: start;
  }
}

.approach__figure {
  aspect-ratio: 4 / 5;
  border: 1px solid var(--rule);
}

@media (min-width: 62rem) {
  .approach__figure {
    margin-top: 0.6rem;
  }
}

/* --- 10. Pratique --------------------------------------------------------- */

/* --- 10. Pratique : section épinglée -------------------------------------- */

/* La hauteur de .pinned EST la course de défilement. 100svh sert à afficher,
   le reste sert à faire défiler les trois états. */
.pinned {
  position: relative;
  height: 320svh;
}

.pinned__stage {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.pinned__plate {
  position: absolute;
  inset: 0;
  /* Le masque porte sur l'image ET son voile. Les deux bords descendent jusqu'à
     la transparence totale, ce qui laisse apparaître le dégradé de .descent :
     le raccord se fait donc de lui-même, quelle que soit la couleur du dégradé
     à cette hauteur de page.

     Fondu long (36vh de chaque côté) et à six paliers : le ciel comme le champ
     de bg-11 sont vifs, et une rampe courte ou linéaire laissait l'œil
     percevoir son début et sa fin comme des bords. */
  -webkit-mask-image: linear-gradient(
    180deg,
    transparent 0,
    rgba(0, 0, 0, 0.12) 6vh,
    rgba(0, 0, 0, 0.45) 15vh,
    rgba(0, 0, 0, 0.8) 24vh,
    rgba(0, 0, 0, 0.96) 30vh,
    #000 36vh,
    #000 calc(100% - 36vh),
    rgba(0, 0, 0, 0.96) calc(100% - 30vh),
    rgba(0, 0, 0, 0.8) calc(100% - 24vh),
    rgba(0, 0, 0, 0.45) calc(100% - 15vh),
    rgba(0, 0, 0, 0.12) calc(100% - 6vh),
    transparent 100%
  );
  mask-image: linear-gradient(
    180deg,
    transparent 0,
    rgba(0, 0, 0, 0.12) 6vh,
    rgba(0, 0, 0, 0.45) 15vh,
    rgba(0, 0, 0, 0.8) 24vh,
    rgba(0, 0, 0, 0.96) 30vh,
    #000 36vh,
    #000 calc(100% - 36vh),
    rgba(0, 0, 0, 0.96) calc(100% - 30vh),
    rgba(0, 0, 0, 0.8) calc(100% - 24vh),
    rgba(0, 0, 0, 0.45) calc(100% - 15vh),
    rgba(0, 0, 0, 0.12) calc(100% - 6vh),
    transparent 100%
  );
}

.pinned__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.pinned__veil {
  position: absolute;
  inset: 0;
  background: radial-gradient(
      ellipse 70% 46% at 50% 50%,
      rgba(4, 7, 14, 0.62) 0%,
      rgba(4, 7, 14, 0.12) 100%
    ),
    linear-gradient(180deg, rgba(4, 7, 14, 0.34) 0%, transparent 26%);
}

.pinned__head {
  position: absolute;
  top: clamp(5.5rem, 12vh, 8rem);
  left: 0;
  right: 0;
}

/* Posé haut plutôt que centré : au centre, le titre venait masquer la petite
   maison blanche qui est le seul point d'accroche de l'image. Il se tient
   maintenant sur les montagnes sombres, au-dessus de la ligne d'horizon. */
.pinned__captions {
  position: absolute;
  top: 32%;
  left: 0;
  right: 0;
  display: grid;
  place-items: center;
  padding-inline: var(--gutter);
}

/* Les trois titres occupent la même cellule de grille : ils se superposent
   exactement, donc le fondu se fait sans décalage de mise en page. */
.pinned__cap {
  grid-area: 1 / 1;
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.1rem, 6.4vw, 5.2rem);
  line-height: 1;
  letter-spacing: -0.022em;
  text-align: center;
  text-wrap: balance;
  color: var(--bone);
  text-shadow: 0 0.1em 0.9em rgba(4, 7, 14, 0.55);
  opacity: 0;
  transform: translateY(0.55rem);
  transition: opacity 0.75s var(--ease-out), transform 0.75s var(--ease-out);
  pointer-events: none;
}

.pinned__cap.is-active {
  opacity: 1;
  transform: none;
}


/* Le bloc .cards / .card a été retiré : la section Pratique est désormais
   une image épinglée avec titres en fondu, plus une grille de cartes. */

/* --- 11. Méthode : format « recent work » ---------------------------------
   Repris de la section du même nom de monopo.london : rail de repères à
   gauche, intitulé qui se relaie au centre, pile d'images qui coulisse à
   droite, sur un panneau sombre traversé d'une lueur d'accent en haut.
   La hauteur de la section porte la course de défilement, comme pour Pratique. */

.pinned--work {
  height: 460svh;
}

/* Panneau sombre propre à la section. Seul le bord HAUT est fondu, vers le
   dégradé de la descente. Le bord bas reste opaque : il rejoint ainsi le sommet
   de bg-08 dans Contact (#03100a contre #05080e, indiscernables), alors qu'un
   fondu y faisait apparaître le vert de la page entre deux panneaux sombres. */
.pinned--work .pinned__stage {
  display: block;
  background: #05080e;
  -webkit-mask-image: linear-gradient(
    180deg,
    transparent 0,
    rgba(0, 0, 0, 0.1) 6vh,
    rgba(0, 0, 0, 0.3) 14vh,
    rgba(0, 0, 0, 0.56) 22vh,
    rgba(0, 0, 0, 0.78) 30vh,
    rgba(0, 0, 0, 0.93) 37vh,
    #000 44vh,
    #000 100%
  );
  mask-image: linear-gradient(
    180deg,
    transparent 0,
    rgba(0, 0, 0, 0.1) 6vh,
    rgba(0, 0, 0, 0.3) 14vh,
    rgba(0, 0, 0, 0.56) 22vh,
    rgba(0, 0, 0, 0.78) 30vh,
    rgba(0, 0, 0, 0.93) 37vh,
    #000 44vh,
    #000 100%
  );
}

/* Lueur d'entrée : c'est elle qui fait le lien avec la section précédente, à la
   manière du halo rouge qui déborde en haut de la section « recent work » de
   monopo. Ici en vermillon, l'accent du site. */
.work__glow {
  position: absolute;
  inset: 0;
  background: radial-gradient(
      120% 62% at 6% -8%,
      rgba(222, 65, 38, 0.38) 0%,
      rgba(222, 65, 38, 0.12) 34%,
      transparent 68%
    ),
    radial-gradient(80% 40% at 96% 4%, rgba(14, 39, 80, 0.5) 0%, transparent 70%);
}

/* Disposition reprise de leur capture : l'image est CENTRÉE et large, le texte
   est posé PAR-DESSUS sa moitié gauche, et le rail de repères est collé au bord.
   Ce n'était pas une grille en colonnes — le titre chevauche bien l'image. */
.work {
  position: relative;
  height: 100%;
  display: grid;
  place-items: center;
  padding-block: clamp(4rem, 10vh, 7rem);
}

.work__media {
  width: 100%;
  min-width: 0;
}

@media (min-width: 62rem) {
  .work__media {
    width: min(64vw, 62rem);
  }
}

/* Rail de repères, au bord de la page. */
.work__index {
  display: none;
}

@media (min-width: 62rem) {
  .work__index {
    display: grid;
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    gap: 0.7rem;
    justify-items: start;
    z-index: 3;
  }
  .work__index li {
    width: 0.85rem;
    height: 1px;
    background: var(--bone-20);
    transition: width 0.5s var(--ease-out), background-color 0.5s var(--ease);
  }
  .work__index li.is-active {
    width: 1.9rem;
    background: var(--vermillion);
  }
}

.work__text {
  min-width: 0;
  position: relative;
  z-index: 2;
}

@media (min-width: 62rem) {
  .work__text {
    position: absolute;
    left: var(--gutter);
    top: 50%;
    transform: translateY(-50%);
    width: min(42ch, 48%);
  }
}

/* Les intitulés occupent la même cellule de grille : ils se superposent, donc
   le relais n'entraîne aucun décalage de mise en page. */
.work__titles,
.work__cats {
  display: grid;
  margin-top: clamp(1rem, 2.5vh, 1.6rem);
}

.work__title,
.work__cat {
  grid-area: 1 / 1;
  opacity: 0;
  transform: translateY(0.6rem);
  transition: opacity 0.7s var(--ease-out), transform 0.7s var(--ease-out);
}

.work__title.is-active,
.work__cat.is-active {
  opacity: 1;
  transform: none;
}

.work__title {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.4rem, 6vw, 4.6rem);
  line-height: 1;
  letter-spacing: -0.024em;
  color: var(--bone);
  text-wrap: balance;
}

.work__cats {
  margin-top: clamp(0.9rem, 2.2vh, 1.5rem);
}

.work__cat {
  color: var(--bone-60);
  font-size: var(--t-lede);
  line-height: 1.5;
  max-width: 34ch;
  text-wrap: pretty;
}

/* Pile d'images qui coulisse. Le conteneur rogne, la pile est translatée d'un
   multiple de 100 % par le script. */
.work__media {
  min-width: 0;
}

/* Le conteneur rogne et ne bouge JAMAIS : c'est la piste interne qui coulisse.
   Translater le conteneur lui-même déplaçait la fenêtre au lieu du contenu, et
   la pile sortait du cadre. */
.work__reel {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  border: 1px solid var(--rule);
}

@media (min-width: 62rem) {
  .work__reel {
    aspect-ratio: 16 / 10;
  }
}

/* Voile sur la gauche de l'image : le titre y est posé par-dessus et les ciels
   de bg-02, 05, 06 et 09 sont trop clairs pour le porter tel quel. */
.work__reel::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(
    90deg,
    rgba(5, 8, 14, 0.9) 0%,
    rgba(5, 8, 14, 0.66) 34%,
    rgba(5, 8, 14, 0.16) 62%,
    transparent 82%
  );
}

.work__track {
  position: absolute;
  inset: 0;
  transition: transform 0.9s var(--ease-monopo);
}

/* Hauteur donnée explicitement, et surtout PAS d'inset: 0 ici : conserver
   bottom: 0 en plus d'un top en pourcentage fait calculer une hauteur négative
   aux cadres décalés, qui s'effondrent alors à zéro — l'image disparaissait dès
   la deuxième étape. */
.work__frame {
  position: absolute;
  left: 0;
  right: 0;
  height: 100%;
  overflow: hidden;
  background-color: var(--ink-2);
  background-size: cover;
  background-position: center;
}

.work__frame:nth-child(1) {
  top: 0;
}
.work__frame:nth-child(2) {
  top: 100%;
}
.work__frame:nth-child(3) {
  top: 200%;
}
.work__frame:nth-child(4) {
  top: 300%;
}

.work__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* --- 13. Contact ---------------------------------------------------------- */

/* --- 13. Contact : formulaire ----------------------------------------------
   Fond noir identique à la fin du panneau de Méthode (#05080e) : les deux
   sections se touchent sur la même valeur, aucun raccord à traiter.
   Le pied de page a été supprimé ; ses mentions sont reprises en bas ici. */

.cta {
  position: relative;
  background: #05080e;
  padding-block: clamp(5rem, 14vh, 10rem) clamp(2.5rem, 6vh, 4rem);
}

.cta__grid {
  display: grid;
  gap: clamp(3rem, 9vw, 7rem);
  align-items: center;
}

@media (min-width: 62rem) {
  .cta__grid {
    /* Colonnes écartées : le formulaire tient à gauche, le bloc d'appel est
       repoussé vers le bord droit avec un vide franc entre les deux. */
    grid-template-columns: minmax(0, 0.9fr) minmax(0, 1fr);
    gap: clamp(4rem, 11vw, 11rem);
  }
  .cta__side {
    justify-self: end;
  }
}

/* --- champs --- */

.cta__form {
  display: grid;
  gap: clamp(1.5rem, 3vh, 2.25rem);
  min-width: 0;
}

.field {
  display: grid;
  gap: 0.5rem;
}

.field label {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--bone-40);
}

.field input,
.field textarea {
  font: inherit;
  color: var(--bone);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rule);
  padding: 0.55rem 0 0.7rem;
  border-radius: 0;
  transition: border-color 0.4s var(--ease);
  min-width: 0;
}

.field textarea {
  resize: vertical;
  line-height: 1.6;
}

.field input:hover,
.field textarea:hover {
  border-bottom-color: var(--bone-20);
}

.field input:focus,
.field textarea:focus {
  outline: none;
  border-bottom-color: var(--vermillion);
}

/* Le champ ne devient rouge qu'une fois une tentative d'envoi faite : signaler
   « invalide » avant que la personne ait fini d'écrire est hostile. */
.cta__form.is-checked input:invalid,
.cta__form.is-checked textarea:invalid {
  border-bottom-color: var(--vermillion);
}

/* --- bouton --- */

.cta__send {
  justify-self: start;
  margin-top: 0.5rem;
  padding: 0.95rem 2.2rem;
  font-family: var(--mono);
  font-size: 0.74rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--void);
  background: var(--bone);
  position: relative;
  transition: transform 0.4s var(--ease-out), box-shadow 0.45s var(--ease-out);
}

/* Décalage vermillon/indigo, comme les deux ombres colorées de la référence.
   En ombres portées et non en pseudo-éléments : avec z-index: -1 ceux-ci
   passaient derrière le fond de la section et devenaient invisibles. */
.cta__send {
  box-shadow: -4px -4px 0 0 var(--vermillion), 4px 4px 0 0 var(--indigo);
}

.cta__send:hover {
  box-shadow: -9px -9px 0 0 var(--vermillion), 9px 9px 0 0 var(--indigo);
  transform: translate(1px, -1px);
}

.cta__send:active {
  box-shadow: -2px -2px 0 0 var(--vermillion), 2px 2px 0 0 var(--indigo);
  transform: none;
}

.cta__status {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  color: var(--coral);
  min-height: 1.2em;
}

/* --- bloc de droite --- */

.cta__side {
  min-width: 0;
  display: grid;
  justify-items: start;
  gap: clamp(1.25rem, 3vh, 2rem);
}

.cta__mark {
  position: relative;
  width: 100%;
  max-width: 32rem;
  padding: clamp(2.5rem, 7vw, 4.5rem) clamp(1.5rem, 4vw, 3rem);
  display: grid;
  justify-items: center;
  gap: 0.9rem;
}

/* Contour dessiné, à peine visible : il enferme le titre sans le cadrer. */
.cta__blob {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  color: var(--bone-20);
  overflow: visible;
}

.cta__logo {
  position: relative;
  width: clamp(2.4rem, 5vw, 3.2rem);
  height: auto;
  opacity: 0.75;
}

.cta__title {
  position: relative;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.6rem, 7vw, 4.6rem);
  line-height: 1.02;
  letter-spacing: -0.02em;
  text-align: center;
}

.cta__title-b {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
}

/* Le filet reprend le tiret long de la référence. */
.cta__title-b i {
  width: clamp(2rem, 6vw, 4.5rem);
  height: 1px;
  background: var(--bone-40);
  flex: none;
}

.cta__title-b em {
  font-style: normal;
  color: var(--coral);
}

.cta__text {
  color: var(--bone-60);
  max-width: 42ch;
  text-wrap: pretty;
}

/* --- mentions, reprises du pied supprimé --- */

.cta__legal {
  margin-top: clamp(4rem, 10vh, 7rem);
  padding-top: clamp(1.5rem, 4vh, 2.5rem);
  border-top: 1px solid var(--rule-soft);
  display: grid;
  gap: 1.25rem;
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bone-40);
  line-height: 1.9;
}

@media (min-width: 56rem) {
  .cta__legal {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.cta__legal p {
  max-width: 34ch;
}

/* --- 15. Révélations au défilement --------------------------------------- */

.reveal {
  opacity: 0;
  transform: translateY(1.6rem);
  transition: opacity 0.95s var(--ease-out), transform 0.95s var(--ease-out);
}

.reveal.is-in {
  opacity: 1;
  transform: none;
}

.reveal[data-delay='1'] {
  transition-delay: 0.1s;
}
.reveal[data-delay='2'] {
  transition-delay: 0.2s;
}
.reveal[data-delay='3'] {
  transition-delay: 0.3s;
}

/* --- 16. Accessibilité mouvement ----------------------------------------- */

@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;
  }
  .reveal {
    opacity: 1;
    transform: none;
  }
  /* le titre doit rester visible même sans animation */
  .hero__word span {
    opacity: 1;
    transform: none;
  }
  .step::before {
    width: 100%;
  }
}
