@charset "UTF-8";
/*!
 * Studio Broye — feuille de styles du site (studiobroye.ch) · v3
 * ---------------------------------------------------------------------------
 * Refonte éditoriale « noir & champagne » : fond noir chaud mat, titres en
 * Playfair Display doré, intertitres en capitales espacées, angles droits,
 * filets fins et panneaux sable. Partagée par toutes les pages.
 *
 * Organisation :
 *   01. Polices locales          07. Hero (accueil)
 *   02. Variables de conception  08. En-tête de page (pages internes)
 *   03. Réinitialisation         09. Blocs de contenu
 *   04. Largeur fluide           10. Pied de page
 *   05. Éléments partagés        11. Contact direct flottant
 *   06. En-tête & navigation     12. Animations, réactivité, accessibilité
 * ---------------------------------------------------------------------------
 */

/* ==========================================================================
   01. POLICES LOCALES
   Servies depuis notre domaine : aucune requête vers Google au chargement
   (performance + cohérence avec notre engagement nLPD).
   ========================================================================== */
@font-face {
  font-family: "Playfair Display";
  src: url("../fonts/playfair-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Playfair Display";
  src: url("../fonts/playfair-600.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Poppins";
  src: url("../fonts/poppins-300.woff2") format("woff2");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Poppins";
  src: url("../fonts/poppins-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Poppins";
  src: url("../fonts/poppins-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Poppins";
  src: url("../fonts/poppins-600.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("../fonts/jetbrains-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("../fonts/jetbrains-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* ==========================================================================
   02. VARIABLES DE CONCEPTION
   Palette calquée sur la référence « noir & champagne » : noir chaud mat,
   or champagne pour les titres, sable pour les panneaux d'appel.
   ========================================================================== */
:root {
  /* -------------------------------------------------------------------
     FICHAS DE LA DIRECCIÓN VISUAL
     Todo el color y la tipografía del sitio salen de aquí. Un tema no es
     más que una redefinición de este bloque: ningún otro punto de la hoja
     escribe un color a mano. Los valores de abajo son la dirección por
     defecto; scripts/build-themes.mjs los sustituye para las maquetas.
     ------------------------------------------------------------------- */

  /* Superficies */
  --bg: #ffffff;
  --bg-rgb: 255, 255, 255;
  --bg-2: #f4f3f0;
  --bg-panel: #ffffff;

  /* Texto */
  --ink: #111315;
  --ink-rgb: 17, 19, 21;
  --ink-soft: #4f545a;
  /* 5,4:1 sur la crème (#f4f3f0) et 6,3:1 sur le blanc : l'ancien #6b7178
     tombait à 4,44:1 sur la crème, juste sous le seuil AA. */
  --muted: #5f656c;

  /* Acento — en esta dirección el acento es la propia tinta */
  --accent: #111315;
  --accent-rgb: 17, 19, 21;
  --accent-2: #111315;
  --accent-deep: #111315;

  /* Banda de contraste (los bloques que invierten la página) */
  --band: #111315;
  --band-ink: #ffffff;
  --band-ink-rgb: 255, 255, 255;
  --band-ink-hard: #ffffff;
  --band-soft: rgba(255, 255, 255, 0.74);

  /* Acento de señalización — rojo suizo. Es el único color de la paleta y se
     usa con cuentagotas: filetes separadores, marcas y estados activos.
     Cambiarlo por otro color es cambiar esta línea. */
  --mark: #b81d24;
  --mark-rgb: 184, 29, 36;
  /* Sobre negro, el rojo de arriba se apaga y ensucia. El de la cabecera y
     las bandas oscuras es el mismo tono, subido de luminosidad. */
  --mark-lite: #ef4a52;

  /* Filetes */
  --line: rgba(17, 19, 21, 0.17);
  --line-soft: rgba(17, 19, 21, 0.1);

  /* Tipografía de titulares */
  --font-head: "Playfair Display", Georgia, serif;
  --head-weight: 500;
  --head-tracking: 0.002em;

  /* Hero: fotografía, velo y color del texto que va encima.
     Desde el 13.08.2026 el hero vuelve a llevar foto: un escritorio oscuro de
     noche, elegido tras probarlo DENTRO de la página. Toda la maquinaria ya
     existía y estaba apagada con --hero-media; encenderla es este bloque.
     El velo va más denso arriba y abajo que en el centro: arriba pelea con la
     filigrana negra de la cabecera, abajo con la costura hacia el bandeau. */
  --hero-media: block;
  --photo-filter: grayscale(0.2) contrast(1.04) brightness(1.24);
  /* Le portrait ne peut pas prendre le filtre des autres photographies.
     `brightness(1.24)` a été réglé pour les fonds sombres du hero, où il
     ouvre les noirs ; appliqué à un portrait de bureau déjà bien exposé, il
     brûle la chemise et la fenêtre — mesuré : la luminance moyenne passe de
     0,355 à 0,440. Christopher l'a vu avant de le mesurer (« sale con mucho
     brillo »). Ici, juste ce qu'il faut pour rester de la même famille que
     le reste du site : un soupçon de désaturation, et l'exposition telle
     qu'elle a été prise. */
  --portrait-filter: grayscale(0.08) contrast(1.04) brightness(1.01);
  /* Voile allégé d'environ un tiers : la photo était lisible mais éteinte, on
     n'y distinguait plus le poste de travail. Il reste plus dense en haut et
     en bas qu'au centre — en haut il croise le filigrane noir de l'en-tête,
     en bas la couture vers le bandeau. */
  /* Deux couches. La première est une ombre douce posée LÀ OÙ LE TEXTE SE
     TROUVE, et nulle part ailleurs : sans elle, éclaircir la photo rendait
     l'accent rouge illisible au moment où il passait devant l'écran allumé.
     C'est la solution d'un tirage photo — on assombrit une zone, on ne
     rabaisse pas toute l'image. La seconde tient les bords : plus dense en
     haut, où elle croise le filigrane noir de l'en-tête, et en bas, où elle
     rejoint la couture vers le bandeau. */
  --hero-veil: radial-gradient(
      ellipse 64% 44% at 50% 40%,
      rgba(8, 9, 11, 0.62) 0%,
      rgba(8, 9, 11, 0.34) 55%,
      transparent 78%
    ),
    linear-gradient(
      180deg,
      rgba(8, 9, 11, 0.58) 0%,
      rgba(8, 9, 11, 0.3) 42%,
      rgba(8, 9, 11, 0.4) 72%,
      rgba(8, 9, 11, 0.72) 100%
    );
  --hero-ink: #ffffff;
  --hero-soft: rgba(255, 255, 255, 0.82);
  --hero-muted: rgba(255, 255, 255, 0.66);
  --hero-line: rgba(255, 255, 255, 0.28);
  --hero-shadow: 0 2px 26px rgba(0, 0, 0, 0.5);
  --hero-pad-top: clamp(58px, 6.5vw, 120px);

  /* Filigrana de cabecera: un velo negro sobre lo alto de la página que se
     disuelve en cuanto se baja. Mientras está, la cabecera escribe en claro. */
  --filigrane: linear-gradient(
    180deg,
    rgba(0, 0, 0, 1) 0%,
    rgba(0, 0, 0, 0.96) 34%,
    rgba(0, 0, 0, 0.6) 62%,
    rgba(0, 0, 0, 0) 100%
  );
  --hdr-ink: #ffffff;
  --hdr-soft: rgba(255, 255, 255, 0.76);
  --hdr-line: rgba(255, 255, 255, 0.32);
  --hdr-btn-bg: #ffffff;
  --hdr-btn-ink: #111315;
  /* El acento y el botón del hero van sobre la fotografía, no sobre la
     página: en las direcciones claras el acento de página es oscuro y ahí
     resultaría ilegible. */
  /* Sobre la foto oscura, el rojo profundo se apaga: el hero pide el claro,
     igual que las costuras contra la tinta (4,8:1 frente a 2,7:1). */
  --hero-accent: var(--mark-lite);
  /* Les deux boutons du hero sont identiques, à la demande du responsable :
     un contour clair sur la photographie, sans aplat. Le rouge essayé un temps
     n'avait pas été demandé — il venait d'une initiative pour faire ressortir
     l'appel à l'action, et il déséquilibrait la paire. */
  --hero-btn-bg: transparent;
  --hero-btn-ink: var(--hero-ink);

  /* Alias heredados: las reglas escritas antes del sistema de temas
     siguen funcionando sin tocarlas. */
  --gold: var(--accent);
  --gold-2: var(--accent-2);
  --gold-deep: var(--accent-deep);
  --sand: var(--band);
  --sand-ink: var(--band-ink);
  --sand-muted: var(--band-soft);

  /* Largeur fluide : la page occupe l'écran quel qu'il soit, la gouttière
     et la largeur maximale s'adaptant à la taille disponible. */
  --gutter: clamp(1.15rem, 3vw, 3.5rem);
  --shell-max: 2400px;
  --reading-max: 1580px;

  /* Rythme vertical et hauteur d'en-tête, fluides eux aussi */
  --section-y: clamp(64px, 6.6vw, 128px);
  --hdr-h: clamp(56px, 3.9vw, 68px);

  /* Angles droits : signature de la refonte. */
  --r: 0px;
}

/* ==========================================================================
   03. RÉINITIALISATION & BASES
   ========================================================================== */
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}
body {
  background: var(--bg);
  color: var(--ink);
  font-family:
    "Poppins",
    system-ui,
    -apple-system,
    sans-serif;
  font-weight: 300;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
main {
  flex: 1 0 auto;
}
img,
svg {
  max-width: 100%;
}
a {
  color: inherit;
}
h1,
h2,
h3,
h4 {
  font-family: var(--font-head);
  font-weight: var(--head-weight);
  line-height: 1.12;
  letter-spacing: var(--head-tracking);
}
:focus-visible {
  outline: 2px solid var(--gold-2);
  outline-offset: 3px;
}
.skip {
  position: absolute;
  left: -9999px;
  top: auto;
}
.skip:focus {
  left: 16px;
  top: 12px;
  z-index: 200;
  background: var(--gold);
  color: var(--sand-ink);
  padding: 10px 16px;
}
.hp {
  position: absolute;
  left: -9999px;
}
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* ==========================================================================
   04. LARGEUR FLUIDE
   Un seul conteneur, qui s'adapte automatiquement à l'écran :
     • petits écrans  → 100 % moins une gouttière confortable
     • grands écrans  → jusqu'à --shell-max, sans vides latéraux marqués
   Les blocs très textuels utilisent .wrap--reading pour rester lisibles.
   ========================================================================== */
.wrap {
  width: min(100% - var(--gutter) * 2, var(--shell-max));
  margin-inline: auto;
  position: relative;
  z-index: 2;
}
.wrap--reading {
  max-width: var(--reading-max);
}

/* ==========================================================================
   05. ÉLÉMENTS PARTAGÉS
   ========================================================================== */
.section {
  padding: var(--section-y) 0;
  position: relative;
}
/* Séparation des bandes : un filet rouge d'un pixel qui s'efface sur les
   bords. Une barre pleine d'un bout à l'autre barrait la page ; celle-ci ne
   fait que souligner la rupture que les fonds marquent déjà. Écriture reprise
   du site Mesar, où le même filet est doré. */
.section + .section,
footer,
.marquee {
  position: relative;
}
.section + .section::before,
footer::before,
.marquee::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 1px;
  z-index: 4;
  pointer-events: none;
  /* Pleine densité. À 55 %, le rouge posé sur le blanc ou la crème donnait un
     rose que l'écran ne rend plus : la moitié des coutures était invisible et
     l'autre moitié — celles des bandes sombres — se voyait, ce qui faisait un
     site cousu à moitié. */
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--mark) 16%,
    var(--mark) 84%,
    transparent 100%
  );
}
.section + .section::before,
footer::before {
  top: 0;
}
/* Entre la bande rouge (04) et la bande noire (05), un filet rouge est invisible
   d'un côté et sourd de l'autre : il passe au blanc, seule teinte qui tienne sur
   les deux fonds à la fois. C'est la seule couture du site entre deux bandes
   colorées. */
.section + .section.band-dark::before {
  /* Blanc : au-dessus le rouge de la bande, au-dessous le noir de la section
     de contact. Le blanc n'est ni l'un ni l'autre — 8,3:1 contre le rouge,
     15:1 contre le noir. Le rouge clair essayé un temps enfreignait la règle,
     puisqu'il reprenait la couleur de la section du dessus. */
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(255, 255, 255, 0.85) 16%,
    rgba(255, 255, 255, 0.85) 84%,
    transparent 100%
  );
}

/* Entrée de la bande rouge (03→04) : le seul endroit du site où aucune couleur
   posée SUR la coupe ne peut fonctionner. Au-dessus, du blanc pur ; au-dessous,
   un rouge sombre (150,27,33). Un filet clair se noie dans le blanc du haut ;
   un filet foncé — encre comprise — plafonne à 2,2:1 contre le rouge et se lit
   comme le bord de la bande, pas comme un trait. Le calcul le confirme : aucune
   teinte unique ne dépasse 3,2:1 face aux deux à la fois, quand le filet blanc
   de la couture 04→05 tient 8,3:1 et 13,6:1.
   Ce qui donne sa netteté à ce filet-là, c'est d'avoir du sombre DES DEUX
   CÔTÉS. On lui rend donc la même situation : le trait entre dans la bande,
   avec du rouge au-dessus et au-dessous — 5,7:1 de chaque côté, la signature
   exacte de la couture que l'on prend pour modèle. */
.section + .section.band-tech::before {
  top: 0;
  /* Deux pixels, et c'est la seule couture du site qui en demande deux.
     Règle posée par le responsable : une couture ne porte jamais la couleur de
     la section du dessus ni de celle du dessous. Ici c'est blanc au-dessus et
     rouge sombre au-dessous — l'encre est donc la bonne réponse, elle n'est ni
     l'un ni l'autre et se lit à 18,6:1 contre le blanc.
     Mais contre le rouge elle ne vaut que 2,2:1, et à un seul pixel elle se
     confondait avec le bord de la bande : on ne voyait plus un trait, on
     voyait une bande qui commence. Cinq teintes ont été essayées au pixel — un
     gris moyen tient 3,3:1 et 2,6:1, un gris clair l'inverse, aucune ne fait
     mieux des deux côtés. Le second pixel règle la question sans changer de
     couleur : c'est le seul joint du site où le filet touche une couleur
     sombre d'un côté, il lui faut ce pixel de plus pour exister. */
  height: 2px;
  background: linear-gradient(90deg, transparent 0%, #111315 16%, #111315 84%, transparent 100%);
}

/* Tout en bas de l'accueil, le pied suit la bande noire : le rouge profond s'y
   éteint contre le noir, exactement comme sur la bande rouge. Même remède —
   le rouge clair. Les pages intérieures finissent sur fond clair et gardent
   le filet profond. */
body[data-page="home"] footer::before {
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--mark-lite) 16%,
    var(--mark-lite) 84%,
    transparent 100%
  );
}
/* Sous le bandeau : c'est ce filet qui le détache du reste de la page. */
.marquee::after {
  bottom: 0;
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--mark-lite) 16%,
    var(--mark-lite) 84%,
    transparent 100%
  );
}
.section-head {
  max-width: clamp(780px, 46vw, 1120px);
  margin-bottom: clamp(36px, 3.6vw, 64px);
}
.section-head h2 {
  font-size: clamp(1.9rem, 3.1vw, 3.6rem);
  color: var(--gold-2);
  margin: 20px 0 18px;
  max-width: 24ch;
}
.section-head p {
  color: var(--ink-soft);
  font-size: clamp(0.94rem, 0.92vw, 1.16rem);
  max-width: 62ch;
}
.section-head--center {
  max-width: clamp(820px, 50vw, 1180px);
  margin-inline: auto;
  text-align: center;
}
.section-head--center h2 {
  margin-inline: auto;
}
.section-head--center p {
  margin-inline: auto;
}
/* Intertitre : capitales espacées, précédées d'un tiret doré. Ton clair,
   comme les kickers de la référence. */
.eyebrow {
  font-family: "Poppins", system-ui, sans-serif;
  font-size: 0.75rem;
  letter-spacing: 0.42em;
  text-transform: uppercase;
  color: var(--ink-soft);
  font-weight: 500;
}
.section-head .eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 14px;
}
.section-head .eyebrow::before {
  content: "";
  width: 26px;
  height: 2px;
  background: var(--mark);
  flex: none;
}
.section-head--center .eyebrow {
  justify-content: center;
}
.gold-text {
  color: var(--gold-2);
}

/* Boutons : rectangles nets, capitales espacées. */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  border: 1px solid transparent;
  cursor: pointer;
  font-family: "Poppins", system-ui, sans-serif;
  font-weight: 500;
  font-size: 0.76rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  border-radius: var(--r);
  padding: 15px 30px;
  text-decoration: none;
  transition: 0.28s;
}
/* L'action principale est rouge : c'est le seul endroit du site où la
   couleur dit « appuyez ici ». */
.btn-gold {
  background: var(--mark);
  color: #ffffff;
  position: relative;
  overflow: hidden;
}
.btn-gold:hover {
  background: var(--ink);
  color: #ffffff;
}
/* --- Le destello : bande de lumière au survol -----------------------------
   Demande de Christopher, 25.08.2026 : reproduire le survol des boutons de
   BCR (`editorial.module.css`, classe `.destello`) — qui l'a lui-même repris
   de mesarbarbershop.ch. Ce que dit sa feuille de style : une bande inclinée
   à 45 % de blanc, large d'un peu moins de la moitié du bouton, qui le
   traverse de bord à bord en 700 ms. Elle ne s'allume pas sur place — elle
   PASSE, une seule fois par survol.
   Seulement sur `.btn-gold` : c'est le seul bouton plein du site (fond
   rouge). Sur `.btn-ghost`, transparent, il n'y a pas de surface pour que la
   lumière s'y détache. */
@media (hover: hover) and (pointer: fine) {
  .btn-gold::after {
    content: "";
    position: absolute;
    inset-block: 0;
    inset-inline-start: -75%;
    inline-size: 45%;
    background: linear-gradient(105deg, transparent, rgba(255, 255, 255, 0.45), transparent);
    transform: skewX(-20deg);
    transition: inset-inline-start 0.7s ease;
    pointer-events: none;
  }
  .btn-gold:hover::after,
  .btn-gold:focus-visible::after {
    inset-inline-start: 125%;
  }
}
/* Exception nommée, même raison que la dérive du hero (13.08.2026) : un
   passage de lumière est un changement d'opacité/position sans déplacement
   du contenu ni du point de vue — il reste visible sous « mouvement
   réduit ». Propriété longue et non le raccourci `transition`, pour la même
   raison qu'ailleurs dans ce fichier : la règle générale y répondrait par
   `!important` et l'emporterait. */
@media (prefers-reduced-motion: reduce) {
  .btn-gold::after {
    transition-property: inset-inline-start !important;
    transition-duration: 0.7s !important;
    transition-timing-function: ease !important;
  }
}
.btn-ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--line);
}
.btn-ghost:hover {
  border-color: var(--gold-2);
  color: var(--gold-2);
}
.btn-sm {
  padding: 10px 18px;
  font-size: 0.75rem;
  letter-spacing: 0.14em;
}
.btn:active {
  transform: scale(0.98);
}
/* Lien « suite » : étiquette en capitales + pastille dorée fléchée,
   directement reprise de la référence. */
.more {
  display: inline-flex;
  align-items: center;
  gap: 16px;
  margin-top: clamp(24px, 2.2vw, 38px);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink);
  text-decoration: none;
  transition: 0.25s;
}
.more svg {
  width: 46px;
  height: 46px;
  padding: 14px;
  border-radius: 50%;
  background: var(--mark);
  stroke: #ffffff;
  fill: none;
  stroke-width: 2;
  flex: none;
  transition: 0.25s;
}
.more:hover {
  color: var(--mark);
}
.more:hover svg {
  background: var(--ink);
  transform: translateX(6px);
}
.more-row {
  display: flex;
  justify-content: center;
  margin-top: clamp(24px, 2.2vw, 38px);
}
.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 18px;
}
.tag {
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  color: var(--muted);
  border: 1px solid var(--line-soft);
  padding: 6px 13px;
}
.chip {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--muted);
  border: 1px solid var(--line-soft);
  padding: 4px 10px;
}

/* ==========================================================================
   06. EN-TÊTE & NAVIGATION
   Navigation par clic : chaque onglet ouvre sa page. L'onglet de la page
   courante reste marqué d'un filet doré.
   ========================================================================== */
/* --hdr-p va de 0 (en haut de page) à 1 (une fois défilée), et c'est main.js
   qui l'écrit à chaque image de défilement. Rien ne commute donc à un seuil :
   le filigrane s'efface et le fond se referme *en même temps et au même
   rythme que le doigt*. C'était le défaut — un basculement d'un coup entre
   deux états. Aucune transition CSS ici : elle traînerait derrière le geste. */
/* La bande du haut : un noir profond que deux veines rouges traversent
   lentement — même respiration que le ruban sous le hero, qui plaisait. Le
   mouvement est porté par la position d'un dégradé, donc par le compositeur :
   il ne coûte pas une mise en page. La densité, elle, suit le défilement
   (--hdr-p, écrit par main.js à chaque image) : rien ne commute. */
header {
  --hdr-p: 0;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  /* Noir massif de fond — jamais translucide au point de laisser passer le
     blanc de la page. FIXE : ne dépend plus de --hdr-p. Un fond qui change de
     couleur sur un calque à backdrop-filter force le navigateur à refaire le
     flou entier à chaque image de défilement au lieu de recomposer une
     capture déjà calculée — c'est ce qui provoquait, par intermittence, des
     déchirures/éclairs blancs sur tout l'écran au défilement (rapporté le
     02-09-2026). Le noircissement progressif se fait maintenant sur
     `.hdr-scrim`, un calque SANS flou qui ne change que d'opacité — un calque
     purement composé, qui ne force aucun nouveau calcul du flou. */
  background: rgba(9, 10, 11, 0.93);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
/* Les veines rouges passent PAR-DESSUS ce noir, et un masque les retient sur
   la moitié droite : la gauche reste franchement noire, ce qui est la seule
   façon de faire lire STUDIO en rouge. */
/* Leur densité suit le défilement au lieu de commuter : `header.scrolled`
   les éteignait d'un coup au passage des 85 px, et le rouge de droite virait
   au noir d'un seul saut. Adossée à --hdr-p, la même extinction se joue
   image par image, au rythme du doigt. */
/* Le noircissement progressif de l'en-tête au défilement : une plaque
   opaque de la même teinte que le fond fixe ci-dessus, sans flou, dont seule
   l'opacité suit --hdr-p. Superposée à un fond fixe à 0,93, une plaque pleine
   à opacité p donne exactement 0,93 + 0,07·p — la même valeur finale que
   l'ancien calcul, mais recomposée au lieu d'être repeinte. */
.hdr-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: rgb(9, 10, 11);
  opacity: var(--hdr-p);
}
header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: calc(0.85 * (1 - var(--hdr-p)));
  background: linear-gradient(
    100deg,
    transparent 0%,
    #360a0e 18%,
    #7d151d 32%,
    #1a0a0c 48%,
    #360a0e 64%,
    #7d151d 80%,
    #1a0a0c 100%
  );
  background-size: 260% 100%;
  background-position: 0% 0;
  animation: hdrFlow 26s linear infinite;
  -webkit-mask-image: linear-gradient(
    90deg,
    transparent 0%,
    transparent 30%,
    rgba(0, 0, 0, 0.5) 52%,
    #000 78%
  );
  mask-image: linear-gradient(
    90deg,
    transparent 0%,
    transparent 30%,
    rgba(0, 0, 0, 0.5) 52%,
    #000 78%
  );
}
@keyframes hdrFlow {
  to {
    background-position: -260% 0;
  }
}
/* Filigrane supérieur : voile dégradé sous l'en-tête, posé sur le hero pour
   garder la navigation lisible. Il devient transparent dès que la page défile
   — même écriture que sur le site Mesar. */
/* Le filet rouge sous l'en-tête. Écriture reprise de gothardmotor.ch — le
   dégradé monte en luminosité au centre, ce qui évite l'effet « souligné de
   traitement de texte » d'une barre unie.
   Il est posé dès le chargement : accroché à --hdr-p, il n'existait qu'une
   fois la page défilée, et la barre noire flottait sans base sur le hero.
   C'est ce filet qui pose l'en-tête sur la page, il doit donc être là
   d'entrée — comme il l'est déjà sous le bandeau du hero. */
header::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 2px;
  pointer-events: none;
  /* Blanc, et non plus rouge. Sous l'en-tête noir et au-dessus d'un hero
     sombre, ce filet est la seule couture du site à ne toucher que du foncé :
     le blanc y tient 15:1 des deux côtés. Le rouge, lui, s'ajoutait au rouge
     de la marque, aux veines de l'en-tête et au bandeau qui suit le hero —
     la page en portait quatre dans le même écran. Il reste la couleur d'un
     accent, pas d'un décor. */
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(255, 255, 255, 0.9) 18%,
    #ffffff 50%,
    rgba(255, 255, 255, 0.9) 82%,
    transparent 100%
  );
}
/* Sans JavaScript, --hdr-p ne bouge pas : l'en-tête reste sur son filigrane,
   ce qui est le bon état de repli — le texte y est lisible. */
.nav {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  height: var(--hdr-h);
  gap: clamp(12px, 1.6vw, 30px);
}
.brand {
  display: flex;
  flex-direction: column;
  /* Un cran au-dessus : la marque tenait moins de place que les onglets de
     navigation, alors que c'est elle qui doit être lue en premier. Le pas est
     court (+11 %) — au-delà, elle pousse la navigation vers la droite. */
  --lk: clamp(0.98rem, 1.16vw, 1.32rem);
  flex-shrink: 0;
  justify-self: start;
}
/* ---------------------------------------------------------------------------
   LOGO « Point rouge » — une seule écriture, quatre emplacements
   Pas de symbole : le lettrage et un carré rouge en guise de point final.
   STUDIO cale à gauche, Broye se pose dessous et à droite — c'est ce décalage
   qui fait la signature. Tout est dimensionné en em à partir de --lk, si bien
   que l'en-tête, le voile de chargement, le bandeau et le pied portent
   exactement le même dessin, à des tailles différentes.
   ------------------------------------------------------------------------- */
.lockup {
  --lk: 1rem;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  /* Le dessin repose sur « Broye » calé à droite de « Studio » : le bloc doit
     donc mesurer la largeur de son mot le plus large, jamais celle du parent.
     Sans cela, dans le pied (parent en display:block) le lockup s'étirait sur
     toute la colonne et « Broye. » partait sous « Navigation ». */
  width: max-content;
  max-width: 100%;
  gap: 0.3em;
  line-height: 1;
  font-size: var(--lk);
  text-decoration: none;
}
.lockup .lk-k {
  align-self: flex-start;
  font-family: "Poppins", system-ui, sans-serif;
  font-weight: 600;
  font-size: 1em;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  padding-left: 0.3em;
}
.lockup .lk-n {
  align-self: flex-end;
  font-family: var(--font-head);
  font-weight: var(--head-weight);
  font-size: 0.82em;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  margin-right: 0.1em;
}

.brand .brand-kick {
  color: var(--hdr-ink);
}
.pt {
  display: inline-block;
  width: 0.14em;
  height: 0.14em;
  margin-left: 0.1em;
  background: var(--mark);
  vertical-align: baseline;
}
.brand-name .pt,
.veil-load .vn .pt {
  background: var(--mark-lite);
}
.veil-load .vn .pt {
  background: var(--mark);
}
.brand .brand-name {
  font-family: var(--font-head);
  font-weight: 500;
  color: var(--ink);
  transition: 0.3s;
}
/* Le survol ne touche pas à la couleur du nom : sur le filigrane noir, la
   moindre teinte sombre le fait disparaître. C'était le défaut signalé. */
.brand:hover .brand-name,
.brand:focus-visible .brand-name {
  color: var(--hdr-ink);
}

.nav-main {
  justify-self: center;
}
.nav-links {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(6px, 1.3vw, 28px);
  list-style: none;
}
.nav-links li {
  display: flex;
  align-items: center;
}
.nav-links a {
  position: relative;
  display: block;
  padding: 12px clamp(7px, 0.7vw, 14px);
  color: var(--ink-soft);
  text-decoration: none;
  font-size: clamp(0.8rem, 0.88vw, 1.02rem);
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  transition:
    color 0.25s,
    transform 0.12s;
}
/* Filet doré : se déploie depuis la gauche */
.nav-links a::after {
  content: "";
  position: absolute;
  left: clamp(7px, 0.7vw, 14px);
  right: 100%;
  bottom: 4px;
  height: 2px;
  background: var(--mark);
  transition: right 0.3s;
}
.nav-links a:hover,
.nav-links a:focus-visible {
  color: var(--ink);
}
.nav-links a:hover::after,
.nav-links a:focus-visible::after,
.nav-links a.is-current::after {
  right: clamp(7px, 0.7vw, 14px);
}
/* Onglet de la page ouverte */
.nav-links a.is-current {
  color: var(--gold-2);
}
.nav-links a:active {
  transform: scale(0.96);
}
/* Confirmation visuelle au clic (classe posée par main.js) */
.nav-links a.tapped {
  animation: navTap 0.45s ease-out;
}
@keyframes navTap {
  from {
    opacity: 0.4;
  }
  to {
    opacity: 1;
  }
}

.nav-right {
  display: flex;
  align-items: center;
  gap: clamp(8px, 1vw, 16px);
  justify-self: end;
}
/* Cuatro siglas en fila comían el ancho de la cabecera. Un solo botón que
   despliega las lenguas escritas en su propio idioma — un germanófono busca
   « Deutsch », no « DE ». Mismo patrón que en el sitio de Mesar. */
.lang {
  position: relative;
}
.lang__toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 8px 12px;
  font-family: "Poppins", system-ui, sans-serif;
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
  transition:
    border-color 0.3s,
    color 0.3s,
    background 0.3s;
}
.lang__toggle svg {
  width: 13px;
  height: 13px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  flex: none;
}
.lang__chevron {
  transition: transform 0.35s ease;
}
.lang__toggle[aria-expanded="true"] .lang__chevron {
  transform: rotate(180deg);
}
.lang__toggle:hover,
.lang__toggle[aria-expanded="true"] {
  border-color: var(--accent);
}
.lang__menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 30;
  min-width: 11rem;
  margin: 0;
  padding: 5px;
  list-style: none;
  background: var(--bg);
  border: 1px solid var(--line);
  box-shadow: 0 18px 44px -18px rgba(0, 0, 0, 0.4);
}
.lang__menu[hidden] {
  display: none;
}
.lang__option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  width: 100%;
  background: none;
  border: 0;
  padding: 10px 12px;
  font-family: "Poppins", system-ui, sans-serif;
  font-size: 0.84rem;
  font-weight: 300;
  color: var(--ink-soft);
  text-align: left;
  cursor: pointer;
  transition:
    background 0.25s,
    color 0.25s;
}
.lang__option:hover,
.lang__option:focus-visible {
  background: rgba(var(--ink-rgb), 0.06);
  color: var(--ink);
}
.lang__sigle {
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  color: var(--muted);
}
/* La lengua actual va marcada, y no solo por aria-selected. */
.lang__option[aria-selected="true"] {
  color: var(--ink);
  font-weight: 500;
}
.lang__option[aria-selected="true"] .lang__sigle {
  color: var(--accent);
}
.burger {
  display: none;
  flex-direction: column;
  gap: 5px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 8px;
}
.burger span {
  width: 24px;
  height: 2px;
  background: var(--ink);
  transition: 0.3s;
}
.burger[aria-expanded="true"] span:first-child {
  transform: translateY(7px) rotate(45deg);
}
.burger[aria-expanded="true"] span:nth-child(2) {
  opacity: 0;
}
.burger[aria-expanded="true"] span:last-child {
  transform: translateY(-7px) rotate(-45deg);
}

/* ==========================================================================
   07. HERO (page d'accueil)
   Composition éditoriale : image pleine largeur, texte posé à gauche,
   grand titre serif, pastille fléchée. Image fixe — aucun carrousel.
   ========================================================================== */
/* Le hero occupe exactement une hauteur d'écran, et le bandeau se pose sur
   son bord inférieur : il est donc lu au chargement, sans avoir à défiler.
   C'est la mesure relevée sur le site Mesar — hero 828 px pour une fenêtre
   de 900, le bandeau affleurant en bas. */
/* Composition centrée. Sans photographie de fond, l'alignement à gauche
   laissait la moitié droite de l'écran vide sur toute la hauteur — près de
   700 px de blanc sans rien à y lire. Centré, le bloc n'a plus de côté vide et
   la page s'ouvre sur un axe unique : titre, phrase, boutons, garanties.
   Chaque niveau garde sa propre largeur de ligne pour que le fer central
   reste lisible et ne produise pas de lignes en drapeau. */
.hero {
  position: relative;
  overflow: hidden;
  text-align: center;
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding: calc(var(--hdr-h) + clamp(18px, 2vw, 34px)) 0 clamp(84px, 7vw, 108px);
}
.hero > .wrap {
  width: min(100% - var(--gutter) * 2, var(--shell-max));
}
.hero .marquee {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
}
.hero-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}
/* Trois calques empilés, un par photographie. Tous portent les MÊMES
   animations (entrée puis dérive), lancées ensemble au chargement : au moment
   du fondu, les deux images en jeu sont donc exactement à la même échelle et
   le mouvement ne marque aucun à-coup. Seule l'opacité change de calque en
   calque — c'est main.js qui déplace `is-on`. */
.hero-bg .img {
  position: absolute;
  inset: -6%;
  background-position: center 58%;
  background-size: cover;
  background-repeat: no-repeat;
  filter: var(--photo-filter);
  display: var(--hero-media);
  scale: var(--z, 1);
  transform-origin: center 45%;
  will-change: transform, opacity;
  opacity: 0;
  transition:
    opacity 1.8s ease,
    scale 0.1s linear;
}
.hero-bg .img.is-on {
  opacity: 1;
}
/* La photographie est un <img> dans le calque, plus un fond CSS : srcset
   choisit la largeur et le format (AVIF, WebP, JPEG). Elle remplit le calque
   comme le faisait `background-size: cover`, et se cadre comme lui. */
.hero-bg .img img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 58%;
}
/* Les deux mises en situation regardent un peu plus haut que le bureau : le
   portable y occupe le tiers central et 58 % l'aurait coupé au clavier. */
.hero-bg .img--2 img {
  object-position: center 55%;
}
.hero-bg .img--3 img {
  object-position: center 50%;
}
.hero-bg .veil {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--hero-veil);
  display: var(--hero-media);
}
.hero-inner {
  position: relative;
  z-index: 2;
  max-width: min(860px, 100%);
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.brand-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1;
  margin-bottom: clamp(10px, 1.2vw, 18px);
  opacity: 0;
  animation: rise 0.7s 0.05s forwards;
}
/* L'eyebrow à la Mesar : le métier et le lieu sur UNE ligne, encadrés de deux
   filets. Un niveau de moins dans la pile — le hero s'ouvre sur une étiquette,
   pas sur deux. */
.brand-hero .bh-kick {
  display: inline-flex;
  align-items: center;
  gap: clamp(0.7rem, 1.2vw, 1.1rem);
  max-width: 100%;
  font-size: clamp(0.75rem, 0.82vw, 0.82rem);
  font-weight: 500;
  letter-spacing: 0.28em;
  color: var(--gold);
  text-transform: uppercase;
  text-align: center;
  margin-bottom: clamp(14px, 1.8vw, 22px);
  text-shadow: var(--hero-shadow);
}
.brand-hero .bh-kick::before,
.brand-hero .bh-kick::after {
  content: "";
  flex: 0 0 clamp(20px, 3.2vw, 46px);
  height: 1px;
  /* Pleine densité : à 55 % sur le blanc, ces filets étaient le même rose
     évanoui que les coutures corrigées plus haut. */
  background: var(--mark);
}
/* Chaque morceau refuse la coupure : laissés en nœuds texte anonymes, le calcul
   flex les pliait alors que la mesure du hero leur suffisait. Au téléphone,
   c'est l'étiquette entière qui passe à la ligne — entre le métier et le lieu,
   jamais au milieu de « Région Broye ». */
.brand-hero .bh-kick > span {
  white-space: nowrap;
}
.brand-hero .bh-sep {
  margin: 0 clamp(0.5rem, 0.9vw, 0.85rem);
  opacity: 0.65;
}
.brand-hero .bh-name {
  font-family: var(--font-head);
  font-weight: 500;
  /* C'est LE titre de la page, à l'échelle du « Mesar » de la référence : il
     était à peine plus grand que la phrase qui le suivait, et le hero n'avait
     pas de sommet. Un seul élément énorme, tout le reste petit. */
  font-size: clamp(2.9rem, 8vw, 7.4rem);
  color: var(--ink);
  letter-spacing: 0.005em;
  line-height: 1;
  text-shadow: var(--hero-shadow);
}
.brand-hero .bh-claim {
  font-size: clamp(0.62rem, 0.85vw, 0.8rem);
  letter-spacing: 0.4em;
  color: var(--ink-soft);
  margin-top: clamp(16px, 1.8vw, 26px);
  text-transform: uppercase;
}
/* Le h1 n'est plus une phrase en serif de taille moyenne : c'est l'accroche
   sous le grand nom, en linéale légère — la hiérarchie de Mesar, un seul
   élément énorme et une seule phrase dessous. */
.hero h1 {
  text-shadow: var(--hero-shadow);
  font-family: var(--font-body);
  font-size: clamp(1.02rem, 1.32vw, 1.34rem);
  margin: clamp(16px, 1.9vw, 26px) auto clamp(22px, 2.4vw, 32px);
  max-width: 52ch;
  text-wrap: balance;
  font-weight: 300;
  letter-spacing: 0.01em;
  color: var(--hero-soft);
  opacity: 0;
  animation: rise 0.6s 0.22s forwards;
}
/* Invitation à défiler, reprise du hero de Mesar : le trait se déroule depuis
   le mot puis s'échappe vers le bas. Dans le flux, sous les garanties — collée
   en absolu au bord bas, elle chevauchait le contenu dès que l'écran manquait
   de hauteur (44 px de recouvrement à 700 px) et la cacher revenait à la
   supprimer sur tout écran de portable. Ici elle est visible partout. */
.hero-scroll {
  margin-top: clamp(26px, 3.4vh, 46px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.62);
  pointer-events: none;
  opacity: 0;
  animation: rise 0.6s 0.5s forwards;
}
.hero-scroll i {
  width: 1px;
  height: 46px;
  background: linear-gradient(180deg, var(--mark), transparent);
  transform-origin: top;
  animation: scrollCue 2.4s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}
@keyframes scrollCue {
  0% {
    transform: scaleY(0);
    opacity: 0;
  }
  35% {
    transform: scaleY(1);
    opacity: 1;
  }
  100% {
    transform: scaleY(1) translateY(14px);
    opacity: 0;
  }
}
/* Sur téléphone le hero est déjà long et le geste de défiler va de soi :
   l'invitation n'apporterait qu'une hauteur de plus. */
@media (max-width: 700px) {
  .hero-scroll {
    display: none;
  }
}
.hero-cta {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  opacity: 0;
  animation: rise 0.6s 0.36s forwards;
}
/* Les garanties vivent désormais dans la bande de contact (05) : la
   réassurance sert au moment de décider, pas au moment d'arriver — et le hero
   perd une rangée de texte. Fond sombre, donc encres claires. */
.trust {
  margin-top: clamp(22px, 2.6vw, 36px);
  display: flex;
  flex-wrap: wrap;
  gap: 12px clamp(18px, 2vw, 30px);
  align-items: center;
  justify-content: center;
  padding-top: 18px;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
}
.trust span {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 0.82rem;
  color: rgba(255, 255, 255, 0.78);
}
.trust svg {
  width: 15px;
  height: 15px;
  stroke: var(--mark-lite);
  fill: none;
  stroke-width: 1.6;
}

/* ==========================================================================
   08. EN-TÊTE DE PAGE (pages internes)
   Bandeau sobre : intertitre, grand titre serif doré, filet inférieur.
   ========================================================================== */
.page-head {
  position: relative;
  overflow: hidden;
  padding: calc(var(--hdr-h) + clamp(44px, 5vw, 88px)) 0 clamp(34px, 3.6vw, 60px);
  border-bottom: 1px solid var(--line-soft);
  background: var(--bg-2);
}
.page-head .wrap {
  position: relative;
  z-index: 2;
}
.crumb {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.75rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: clamp(16px, 1.6vw, 26px);
}
.crumb a {
  padding: 4px 0;
  color: var(--muted);
  text-decoration: none;
  transition: 0.2s;
}
.crumb a:hover {
  color: var(--gold-2);
}
.crumb span[aria-current] {
  color: var(--gold);
}
.page-head h1 {
  font-size: clamp(2rem, 3.6vw, 4.2rem);
  color: var(--gold-2);
  max-width: 26ch;
  margin-bottom: 16px;
}
.page-head .lead {
  color: var(--ink-soft);
  font-size: clamp(0.96rem, 1vw, 1.24rem);
  max-width: 62ch;
}
.page-head .eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 14px;
}
.page-head .eyebrow::before {
  content: "";
  width: 26px;
  height: 2px;
  background: var(--mark);
  flex: none;
}

/* ==========================================================================
   09. BLOCS DE CONTENU
   Les grilles passent par des paliers explicites : le nombre de colonnes
   suit la place disponible, sans jamais laisser de cartes orphelines.
   ========================================================================== */

/* --- Chapeau de section en deux colonnes : le titre tient la gauche, le
   texte d'introduction la droite. Sur toute la largeur, un titre seul suivi
   d'un paragraphe court laissait un vide qui faisait pauvre. --- */
.section-head--split {
  max-width: none;
}
.section-head--split h2 {
  max-width: 18ch;
}
@media (min-width: 1000px) {
  .section-head--split {
    display: grid;
    grid-template-columns: 1.1fr 0.9fr;
    column-gap: clamp(40px, 5vw, 120px);
    align-items: end;
  }
  .section-head--split .eyebrow {
    grid-column: 1 / -1;
  }
  .section-head--split h2 {
    margin-bottom: 0;
  }
  .section-head--split > p {
    max-width: 46ch;
    padding-bottom: 0.5em;
  }
}

/* --- Services : colonnes numérotées, filet supérieur qui rougit --- */
.svc-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(30px, 2.8vw, 54px) clamp(24px, 2.2vw, 44px);
  counter-reset: svc;
}
.svc {
  position: relative;
  counter-increment: svc;
  border-top: 2px solid var(--line-soft);
  padding-top: clamp(22px, 1.8vw, 32px);
  transition:
    border-color 0.35s,
    transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
.svc:hover {
  border-top-color: var(--mark);
  transform: translateY(-4px);
}
/* L'ordinal situe la prestation dans la série et donne du relief à une
   colonne qui, sans lui, n'était qu'un bloc de texte. */
.svc::before {
  content: counter(svc, decimal-leading-zero);
  display: block;
  font-family: var(--font-head);
  font-weight: var(--head-weight);
  font-size: clamp(1.4rem, 1.5vw, 1.9rem);
  line-height: 1;
  color: var(--mark);
  margin-bottom: clamp(16px, 1.4vw, 24px);
}
.svc .ic {
  margin-bottom: 16px;
}
.svc .ic svg {
  width: clamp(34px, 2vw, 46px);
  height: clamp(34px, 2vw, 46px);
  stroke: var(--ink);
  fill: none;
  stroke-width: 1.1;
  transition:
    stroke 0.35s,
    transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
.svc:hover .ic svg {
  stroke: var(--mark);
  transform: translateX(3px);
}
.svc h3 {
  font-family: "Poppins", system-ui, sans-serif;
  font-weight: 500;
  font-size: clamp(0.82rem, 0.86vw, 0.98rem);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: 12px;
}
.svc p {
  font-size: clamp(0.88rem, 0.92vw, 1.04rem);
  color: var(--ink-soft);
  margin-bottom: 14px;
}
/* La stack passe sous un filet : elle appartient à la fiche, mais elle se
   lit après le reste, pas en même temps. */
.svc .pts {
  font-size: clamp(0.75rem, 0.66vw, 0.8rem);
  color: var(--muted);
  letter-spacing: 0.04em;
  line-height: 1.8;
  padding-top: 12px;
  border-top: 1px solid var(--line-soft);
}

/* Page Services : lecture en deux colonnes — la prestation à gauche, son
   périmètre et la stack à droite. */
.svc-detail {
  display: grid;
  gap: 0;
  border-top: 1px solid var(--line-soft);
}
.svc-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(14px, 1.4vw, 30px);
  padding: clamp(26px, 2.2vw, 44px) 0;
  border-bottom: 1px solid var(--line-soft);
  transition: 0.35s;
}
.svc-row__head {
  display: flex;
  align-items: flex-start;
  gap: 18px;
}
.svc-row__head .ic {
  flex: none;
  display: grid;
  place-items: center;
  width: clamp(38px, 2.2vw, 48px);
  height: clamp(38px, 2.2vw, 48px);
}
.svc-row__head .ic svg {
  width: 100%;
  height: 100%;
  stroke: var(--gold);
  fill: none;
  stroke-width: 1.2;
}
.svc-row__head h3 {
  font-family: "Poppins", system-ui, sans-serif;
  font-weight: 500;
  font-size: clamp(0.84rem, 0.88vw, 1rem);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: 8px;
}
.svc-row__head p {
  font-size: clamp(0.86rem, 0.88vw, 1rem);
  color: var(--ink-soft);
}
.svc-row__inc {
  font-size: clamp(0.86rem, 0.88vw, 0.99rem);
  color: var(--ink-soft);
  margin-bottom: 14px;
  padding-left: 16px;
  border-left: 1px solid var(--gold);
}
.svc-row__stack {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}

/* --- Profil (page Expertise) --- */
.about-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(16px, 1.4vw, 24px);
}
.person {
  background: var(--bg-panel);
  border: 1px solid var(--line-soft);
  padding: clamp(26px, 2.2vw, 48px);
  position: relative;
  transition: border-color 0.35s;
}
.person:hover {
  border-color: var(--line);
}
/* Le portrait remplace l'initiale dorée : photographie verticale à gauche,
   parcours à droite — la composition de « Quién está detrás » de BCR.
   Elle n'est pas approchée à l'œil : elle a été MESURÉE sur le site en ligne
   (bcrentrenamiento.es/el-centro) dans une fenêtre de 1440 px, et ce sont ces
   chiffres qui sont repris ici.

       colonnes        1fr 1fr, écart 64 px, align-items: start
       photographie    544 × 680 px — 4/5, object-fit: cover
       son cadre       position: sticky, top = hauteur d'en-tête + 32 px
       texte           558 px de mesure, 17 px / 1,6

   Deux écarts assumés, et pourquoi :

   · La colonne de gauche est calée sur la photo (`minmax(0, 620px)`) au lieu
     d'un `1fr` qui lui laisserait du jeu. Chez BCR ce jeu tombe dans la marge
     de la page et se lit comme de l'air ; ici la fiche a un fond, et le même
     vide À L'INTÉRIEUR du cadre se lirait comme un défaut d'alignement. Ce
     qui compte est conservé : la photo, puis exactement 64 px avant le texte.
   · Le fichier arrive DÉJÀ recadré en 4/5, donc `object-fit` n'a plus rien à
     couper. Le cadrage se décide à l'export — c'est un plein pied, et le
     point de coupe (garder les chaussures, pas le plafond) est une décision
     de photographie, pas de feuille de style.

   RÉVISION du 25.08.2026, après relecture de Christopher. Trois corrections,
   et la mesure qui les justifie :

   · 620 px et non 544. « Le texte occupe plus de la moitié de la largeur et
     enlève sa place à l'image » : c'était exact — 544 px de photo contre
     680 px de colonne de texte, la photo perdait. À 620 / 606 elle reprend
     la main. Et 620 est un PLAFOND, pas un choix de goût : en 4/5 elle fait
     alors 775 px de haut, qui tiennent sous l'en-tête fixe dans une fenêtre
     de 900 px (88 + 775 = 863). Au-delà, le bas de la photo sortirait de
     l'écran et le maintien à la vue perdrait son sens.
   · Plus aucun filet autour de la photo. Il y en avait deux — le trait de
     l'image et le cadre décalé — et ils se lisaient comme un encadrement de
     tableau. La référence n'en a aucun : la photographie se pose sur le
     fond, sans bordure.
   · Le rapprochement au survol, repris de BCR (`movimiento.module.css`) :
     `scale(1.06)` en 600 ms sur `cubic-bezier(0.16, 1, 0.3, 1)`. Le cadre
     recadre, l'image grandit dedans — c'est le cadrage qui semble se
     resserrer, pas l'image qui déborde. */
.person--profil {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(24px, 3vw, 64px);
}
.person-photo {
  margin: 0;
  position: relative;
  max-width: 620px;
  /* `clip` et non `hidden`, et ce n'est pas un synonyme : `overflow: hidden`
     ferait de ce cadre un CONTENEUR DE DÉFILEMENT, et un conteneur de
     défilement à l'intérieur d'un élément collant se comporte mal. `clip`
     rogne exactement pareil sans en créer un. */
  overflow: clip;
}
.person-photo img {
  width: 100%;
  /* `height: auto` est indispensable ici : la balise porte ses dimensions en
     attributs (pour réserver la place avant le chargement), et ceux-ci se
     comportent comme un indice de présentation équivalent à `height: …px` —
     sans le remplacer explicitement, `aspect-ratio` n'a plus d'axe libre où
     s'appliquer et l'image ignore le 4/5. */
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: center;
  display: block;
  filter: var(--portrait-filter);
  transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
  /* CORRECTION du 25.08.2026 : à l'origine par défaut (50 % 50 %), le
     grossissement mange 3 % de la hauteur de CHAQUE côté — et il n'y avait
     pas 3 % de marge au-dessus des cheveux dans ce cadrage serré. Résultat,
     vu par Christopher : la tête se coupait légèrement au survol. Ancré à
     12 % — la marge que porte déjà `object-position` chez BCR — tout
     l'agrandissement pousse vers le bas, jamais vers le haut : la tête ne
     bouge pas d'un pixel, seuls les pieds se rapprochent un peu plus du
     bord. */
  transform-origin: 50% 12%;
}
/* Souris seulement : sur un écran tactile le survol reste « collé » après
   qu'on a touché, et la photo resterait agrandie sans qu'on ait rien demandé.
   Le doigt a son propre geste, plus bas. */
@media (hover: hover) and (pointer: fine) {
  .person-photo:hover img,
  .person-photo:focus-within img {
    transform: scale(1.06);
  }
}
/* Et au doigt, l'équivalent : la photo répond au toucher. Sans lui, le seul
   geste de la section serait un survol — et un téléphone n'a pas de souris.
   C'est là que Christopher va la regarder en premier.
   CORRECTION du 25.08.2026 : `:active` ne suffisait pas. Safari iOS n'arme
   la pseudo-classe `:active` sur un élément SANS ÉCOUTEUR tactile — et
   `<figure>` n'en avait aucun — que ce soit documenté ou non, en pratique le
   survol ne se déclenchait jamais au doigt. `initPortraitTactile()` (main.js)
   pose la classe `.is-touched` sur `touchstart` et la retire sur
   `touchend`/`touchcancel` : la même chose que `:active`, mais qui marche. */
@media (hover: none) {
  .person-photo img {
    transition:
      transform 0.6s cubic-bezier(0.16, 1, 0.3, 1),
      filter 0.3s ease;
  }
  .person-photo.is-touched img {
    transform: scale(1.05);
    filter: var(--portrait-filter) brightness(1.06);
  }
}
/* Exception nommée — la huitième. Le rapprochement ne déplace pas le contenu
   de la page et ne bouge pas le point de vue : c'est un cadrage qui se
   resserre. Il reste donc visible sous « mouvement réduit », que Christopher
   a activé. Propriétés longues et non le raccourci, sinon la règle générale
   (`transition: none !important` sur `*`) l'emporte. */
@media (prefers-reduced-motion: reduce) {
  .person-photo img {
    transition-property: transform !important;
    transition-duration: 0.6s !important;
    transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1) !important;
  }
}
.person-body {
  min-width: 0;
}
/* 64rem (1024 px) : le seuil exact où BCR bascule sur ce même bloc
   (`@media (min-width: 64rem)` dans editorial.module.css). En dessous, une
   seule colonne — la photo passe au-dessus du texte et rien n'est collant.
   Au-dessus, la photo prend ses 544 px et reste ancrée sous l'en-tête fixe
   pendant que le parcours défile à côté : c'est ce qui tient le bloc en
   équilibre quand le texte mesure le double de la photographie. */
@media (min-width: 64rem) {
  .person--profil {
    grid-template-columns: minmax(0, 620px) 1fr;
    /* 64 px fixes, et non un `clamp` en vw : chez BCR c'est `--espacio-16`,
       une valeur fixe. Un écart proportionnel donnait 43 px à 1440 — la
       photo et le texte se touchaient de vingt pixels de trop. */
    gap: 64px;
    align-items: start;
  }
  .person-photo {
    position: sticky;
    top: calc(var(--hdr-h) + 32px);
  }
  /* `.section` porte `overflow: hidden` partout (§15, pour le filigrane et
     les bandes) — mais sur UN élément sticky, un ancêtre en overflow non
     `visible` redevient son cadre de défilement : le portrait ne dépassait
     jamais la hauteur de la section et restait donc figé, comme en
     `position: relative`. Rien d'autre à protéger ici (cette section ne
     contient que le profil), donc l'exception se limite à elle par `:has()`. */
  .section:has(.person-photo) {
    overflow: visible;
  }
}
/* La mesure du texte, alignée sur celle de BCR : 558 px relevés sur leur
   `--ancho-texto`. Sans ce plafond la colonne de droite irait jusqu'à ses
   680 px et les lignes deviendraient trop longues pour être suivies —
   c'est la mesure, pas la largeur du conteneur, qui décide du confort. */
.person--profil .person-body > p,
.person--profil .career,
.person--profil .cred li {
  max-width: 558px;
}
/* 17 px / 1,6, exactement le corps du texte de BCR. Le profil était à
   14,4 px : lisible, mais sensiblement plus petit que la référence — et
   c'est la page où l'on demande au visiteur de lire le plus. */
.person--profil .person-body > p {
  font-size: clamp(0.98rem, 1.18vw, 1.0625rem);
  line-height: 1.6;
}
/* Les deux paragraphes de la biographie se touchaient : sans respiration
   entre eux, la bio se lit comme un seul pavé de neuf lignes. 16 px —
   l'écart relevé entre les paragraphes de BCR. */
.person--profil .person-body > p + p {
  margin-top: 16px;
}

/* Formation, distinctions, recherche.
   Elles étaient sur deux colonnes de 240 px minimum. Dans une colonne de
   texte devenue plus étroite — celle du profil, calée sur les 558 px de la
   référence — deux colonnes donnaient des cellules de 327 px où chaque
   ligne (« Master Big Data & Business Intelligence — IMF · Université
   Nebrija (Madrid), mention honorifique 9,87/10… ») retombait sur six ou
   sept lignes. BCR les présente sur une seule colonne pleine mesure, et
   c'est ce qui se lit : une entrée, deux lignes, l'œil avance. */
.cred-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(14px, 1.6vw, 28px);
  margin-top: 20px;
  max-width: 558px;
}
/* Hors du profil, la règle d'origine reste : deux colonnes de repères
   courts, là où le conteneur est large. */
.person:not(.person--profil) .cred-grid {
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  max-width: none;
}
.cred h3 {
  font-size: 0.75rem;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--gold-2);
  font-weight: 500;
  margin-bottom: 10px;
}
.cred ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px;
}
.cred li {
  position: relative;
  padding-left: 18px;
  font-size: 0.87rem;
  color: var(--ink-soft);
  max-width: 52ch;
}
.cred li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 9px;
  height: 1px;
  background: var(--gold);
}

.person .ava {
  width: 58px;
  height: 58px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-family: var(--font-head);
  font-size: 1.5rem;
  color: var(--sand-ink);
  background: var(--gold);
  margin-bottom: 20px;
  font-weight: 500;
}
.person h2 {
  font-size: clamp(1.35rem, 1.7vw, 2.1rem);
  color: var(--gold-2);
}
.person .role {
  font-size: 0.75rem;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 500;
  margin: 8px 0 18px;
}
.person p {
  color: var(--ink-soft);
  font-size: clamp(0.9rem, 0.95vw, 1.1rem);
  max-width: 72ch;
}
.career {
  margin-top: 18px;
  padding: 14px 18px;
  border-left: 1px solid var(--gold);
  font-size: 0.87rem;
  color: var(--ink);
  max-width: 78ch;
}
.career .career-top {
  display: block;
  color: var(--gold-2);
  font-weight: 500;
  font-size: 0.93rem;
  margin-bottom: 7px;
}
.career .career-sci {
  display: block;
  margin-top: 7px;
  color: var(--ink-soft);
}
.person .links {
  margin-top: 22px;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.linkedin {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-2);
  text-decoration: none;
  border: 1px solid var(--line);
  padding: 10px 18px;
  transition: 0.25s;
}
.linkedin:hover {
  border-color: var(--gold-2);
  background: rgba(var(--accent-rgb), 0.08);
}
.linkedin svg {
  width: 14px;
  height: 14px;
  fill: var(--gold-2);
}
.phone-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.84rem;
  color: var(--ink);
  text-decoration: none;
}
.phone-pill svg {
  width: 15px;
  height: 15px;
  stroke: var(--gold);
  fill: none;
  stroke-width: 1.6;
}

/* Sceaux des entreprises traversées : traitement typographique monochrome. */
.orgs {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: 20px;
  padding-top: 18px;
  border-top: 1px solid var(--line-soft);
}
.org {
  font-size: 0.75rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-soft);
  border: 1px solid var(--line-soft);
  padding: 6px 11px;
  transition: 0.25s;
}
.org:hover {
  border-color: var(--gold);
  color: var(--ink);
}

/* Secteurs déjà livrés */
.dom-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(10px, 0.9vw, 16px);
}
.dom {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: clamp(14px, 1.2vw, 22px) clamp(14px, 1.2vw, 22px);
  border: 1px solid var(--line-soft);
  font-size: clamp(0.86rem, 0.88vw, 0.98rem);
  color: var(--ink-soft);
  transition: 0.28s;
}
.dom:hover {
  border-color: var(--gold);
  color: var(--ink);
}
.dom-ic {
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  flex: none;
}
.dom-ic svg {
  width: 22px;
  height: 22px;
  stroke: var(--gold);
  fill: none;
  stroke-width: 1.2;
}

/* --- Mosaïque éditoriale (accueil) : photographies « or sur noir » et
   tuile sable d'appel, directement reprises de la référence. --- */
/* La mosaïque respire en trois dimensions : le cadre s'incline vers le
   curseur, l'image plonge en avant. Les deux mouvements passent par des
   variables que main.js écrit au passage de la souris — sans lui, il reste le
   zoom au survol, qui suffit. */
.mosaic {
  display: grid;
  grid-template-columns: 1fr;
  grid-auto-rows: clamp(220px, 26vw, 460px);
  gap: clamp(10px, 0.9vw, 18px);
  perspective: 1400px;
}
.mo {
  position: relative;
  overflow: hidden;
  margin: 0;
  transform-style: preserve-3d;
  transform: rotateX(var(--mo-rx, 0deg)) rotateY(var(--mo-ry, 0deg));
  transition:
    transform 0.5s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.5s ease;
  will-change: transform;
}
.mo:hover {
  z-index: 2;
  box-shadow: 0 40px 90px -40px rgba(0, 0, 0, 0.55);
}
.mo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transform: scale(var(--mo-s, 1.01));
  /* Monochrome. Les deux images de cette section étaient la seule matière
     chaude de toute la page — un or hérité de la palette v3 — et elles
     juraient avec le rouge suisse. Désaturées, elles rendent au panneau
     central son rôle : c'est lui, et lui seul, qui porte la couleur.
     Le survol rend un peu de vie, sans revenir à l'or. */
  filter: grayscale(1) contrast(1.12) brightness(1.04);
  transition:
    transform 0.55s cubic-bezier(0.16, 1, 0.3, 1),
    filter 0.55s ease;
  will-change: transform;
}
.mo:hover img {
  --mo-s: 1.22;
  filter: grayscale(0.82) contrast(1.16) brightness(1.1);
}
/* Un voile qui s'ouvre au survol : la profondeur se lit aussi à la lumière. */
.mo::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    135deg,
    rgba(var(--mark-rgb), 0.32) 0%,
    transparent 55%,
    rgba(0, 0, 0, 0.26) 100%
  );
  opacity: 0;
  transition: opacity 0.5s ease;
}
.mo:hover::after {
  opacity: 1;
}
.mo-cta {
  background: var(--mark);
  color: #ffffff;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  padding: clamp(22px, 2.2vw, 44px);
}
.mo-cta .eyebrow {
  color: rgba(255, 255, 255, 0.78);
}
.mo-cta .mo-title {
  font-family: var(--font-head);
  font-size: clamp(1.3rem, 1.9vw, 2.3rem);
  line-height: 1.2;
  color: #ffffff;
  margin: 12px 0 18px;
  max-width: 16ch;
}
.mo-cta .more {
  margin-top: 0;
  color: #ffffff;
}
.mo-cta .more svg {
  background: #ffffff;
  stroke: var(--mark);
}
.mo-cta .more:hover svg {
  background: var(--ink);
  stroke: #ffffff;
}
.mo-cta .more:hover {
  color: rgba(255, 255, 255, 0.82);
}

/* --- Réalisations --- */
.proj-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(14px, 1.2vw, 22px);
}
.proj {
  background: var(--bg-panel);
  border: 1px solid var(--line-soft);
  overflow: hidden;
  transition: border-color 0.35s;
  display: flex;
  flex-direction: column;
  position: relative;
}
.proj:hover {
  border-color: var(--line);
}
/* Projecteur séquentiel : un projet mis en avant à la fois */
.proj.spot {
  border-color: var(--gold);
}
.proj .shot {
  background: var(--bg-2);
  min-height: 172px;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  border-bottom: 1px solid var(--line-soft);
}
.browser {
  width: 84%;
  background: var(--bg);
  border: 1px solid var(--line-soft);
  overflow: hidden;
}
.browser .top {
  display: flex;
  gap: 5px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--line-soft);
}
.browser .top i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--line-soft);
  display: block;
}
.browser .url {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  color: var(--muted);
  padding: 7px 12px;
  border-bottom: 1px solid var(--line-soft);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.browser .canvas {
  padding: 18px 14px;
  min-height: 96px;
}
.browser .canvas .l {
  height: 7px;
  background: var(--line-soft);
  margin-bottom: 9px;
}
.browser .canvas .l.g {
  background: var(--gold);
  width: 46%;
}
.browser .canvas .l.s {
  width: 80%;
}
.browser .canvas .l.t {
  width: 62%;
}
.proj .body {
  padding: clamp(18px, 1.4vw, 28px);
  display: flex;
  flex-direction: column;
  flex: 1;
}
.proj .sector {
  font-size: 0.75rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold);
  font-weight: 500;
}
.proj h2 {
  font-size: clamp(1.15rem, 1.3vw, 1.65rem);
  color: var(--ink);
  margin: 8px 0 10px;
}
.proj .chips {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin: 4px 0 14px;
}
.proj details {
  border-top: 1px solid var(--line-soft);
  margin-top: auto;
}
.proj summary {
  list-style: none;
  cursor: pointer;
  padding: 14px 0 4px;
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-2);
  display: flex;
  align-items: center;
  gap: 8px;
}
.proj summary::-webkit-details-marker {
  display: none;
}
.proj summary .arw {
  transition: 0.3s;
}
.proj details[open] summary .arw {
  transform: rotate(90deg);
}
.proj .detail {
  font-size: 0.86rem;
  color: var(--ink-soft);
  padding-bottom: 4px;
}
.proj .detail .row {
  margin: 11px 0;
}
.proj .detail .k {
  font-size: 0.75rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  display: block;
  margin-bottom: 3px;
}
/* Chiffre de réputation du client : la preuve la plus convaincante quand on
   vend à un commerce de proximité. */
.proj .metric {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 2px 0 12px;
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  color: var(--gold-2);
  border: 1px solid var(--line);
  padding: 5px 12px;
  width: fit-content;
}
/* Projet livré mais pas encore publié (domaine en attente côté client). */
.proj .status {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin-top: 14px;
  font-size: 0.8rem;
  color: var(--muted);
  width: fit-content;
}
.proj .status i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--mark);
  flex: none;
}
.proj .visit {
  margin-top: 10px;
  padding: 4px 0;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-2);
  text-decoration: none;
  width: fit-content;
}
.proj .visit svg {
  width: 13px;
  height: 13px;
  stroke: var(--gold-2);
  fill: none;
  stroke-width: 1.7;
}
.proj .visit:hover {
  text-decoration: underline;
}

/* --- Approche : panneau sable, signature de la référence --- */
.approach {
  background: var(--sand);
  color: var(--sand-ink);
  padding: clamp(30px, 3vw, 60px);
}
.approach .key {
  font-family: var(--font-head);
  font-size: clamp(1.4rem, 2.4vw, 2.6rem);
  line-height: 1.25;
  color: var(--sand-ink);
  max-width: 26ch;
  margin-bottom: clamp(26px, 2.6vw, 38px);
}
.approach .key b {
  font-weight: 500;
}
.feat-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(10px, 0.9vw, 16px);
}
.feat-item {
  border: 1px solid rgba(var(--band-ink-rgb), 0.22);
  padding: clamp(16px, 1.3vw, 24px);
  transition: 0.3s;
}
.feat-item:hover {
  border-color: rgba(var(--band-ink-rgb), 0.55);
}
.feat-item .n {
  font-size: 0.75rem;
  letter-spacing: 0.2em;
  color: var(--gold-deep);
  font-weight: 500;
}
.feat-item h3 {
  font-size: 0.94rem;
  color: var(--sand-ink);
  margin: 8px 0 4px;
  font-family: "Poppins", system-ui, sans-serif;
  font-weight: 500;
}
.feat-item p {
  font-size: 0.82rem;
  color: var(--sand-muted);
}

/* --- Présence locale --- */
.local-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(24px, 2.6vw, 44px);
  align-items: center;
}
.local-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 8px;
}
.local-list .li {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 0.86rem;
  color: var(--ink-soft);
  border: 1px solid var(--line-soft);
  padding: 10px 16px;
  transition: 0.25s;
}
.local-list .li:hover {
  border-color: var(--gold);
  color: var(--ink);
}
.local-list .li svg {
  width: 15px;
  height: 15px;
  stroke: var(--gold);
  fill: none;
  stroke-width: 1.5;
  flex: none;
}
.cities {
  border: 1px solid var(--line-soft);
  background: var(--bg-panel);
  padding: clamp(22px, 1.9vw, 36px);
  transition: border-color 0.35s;
}
.cities:hover {
  border-color: var(--line);
}
.cities .lbl {
  font-size: 0.75rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 500;
  margin-bottom: 16px;
}
.cities .city {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--line-soft);
  font-family: var(--font-head);
  font-size: clamp(1.1rem, 1.15vw, 1.35rem);
  color: var(--ink);
  transition: 0.25s;
}
.cities .city:hover {
  color: var(--gold-2);
  padding-left: 8px;
}
.cities .city:last-child {
  border-bottom: none;
}
.cities .city span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--mark);
  transition: 0.25s;
  flex: none;
}

/* --- Chiffres & atouts --- */
.stats {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(12px, 1.1vw, 20px);
}
.stat {
  text-align: center;
  padding: clamp(24px, 2vw, 36px) 18px;
  border-top: 1px solid var(--line-soft);
  transition: border-color 0.35s;
}
.stat:hover {
  border-top-color: var(--mark);
}
.stat .num {
  font-family: var(--font-head);
  font-size: clamp(2rem, 3.4vw, 3.1rem);
  color: var(--gold-2);
  line-height: 1;
}
.stat .lab {
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 10px;
}
.why {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: clamp(22px, 2.2vw, 32px);
  justify-content: center;
}
.why .w {
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--ink-soft);
  border: 1px solid var(--line-soft);
  padding: 9px 18px;
  transition: 0.25s;
}
.why .w:hover {
  border-color: var(--gold);
  color: var(--ink);
}

/* --- Contact --- */
.cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: clamp(24px, 2.4vw, 34px);
  justify-content: center;
}
.contact-strip {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 14px;
  margin-bottom: clamp(26px, 2.6vw, 38px);
}
.contact-strip .cmethod {
  flex: 1 1 240px;
  max-width: 340px;
}
.cmethod {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 15px 20px;
  border: 1px solid var(--line-soft);
  text-decoration: none;
  transition: 0.25s;
  background: var(--bg-panel);
}
.cmethod:hover {
  border-color: var(--gold);
}
.cmethod .ci {
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  flex-shrink: 0;
}
.cmethod .ci svg {
  width: 18px;
  height: 18px;
  stroke: var(--gold);
  fill: none;
  stroke-width: 1.5;
}
.cmethod .ci.pini {
  border-radius: 50%;
  background: var(--gold);
  border: none;
  font-family: var(--font-head);
  font-weight: 500;
  font-size: 1.05rem;
  color: var(--sand-ink);
}
.cmethod .lbl {
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  display: block;
}
.cmethod .val {
  font-size: 0.96rem;
  color: var(--ink);
  display: block;
}

/* Variante sable d'une section (appel à l'action de l'accueil) */
.section--sand {
  background: var(--sand);
  color: var(--sand-ink);
}
.section--sand .section-head h2 {
  color: var(--sand-ink);
}
.section--sand .section-head p {
  color: var(--sand-muted);
}
.section--sand .eyebrow {
  color: var(--sand-muted);
}
.section--sand .eyebrow::before {
  background: var(--gold-deep);
}
.section--sand .cmethod {
  background: transparent;
  border-color: rgba(var(--band-ink-rgb), 0.24);
}
.section--sand .cmethod:hover {
  border-color: var(--sand-ink);
}
.section--sand .cmethod .ci {
  border-color: rgba(var(--band-ink-rgb), 0.3);
}
.section--sand .cmethod .ci svg {
  stroke: var(--gold-deep);
}
.section--sand .cmethod .ci.pini {
  background: var(--sand-ink);
  color: var(--sand);
}
.section--sand .cmethod .lbl {
  color: var(--sand-muted);
}
.section--sand .cmethod .val {
  color: var(--sand-ink);
}
.section--sand .btn-gold {
  background: var(--sand-ink);
  color: var(--sand);
}
.section--sand .btn-gold:hover {
  background: var(--band-ink-hard);
}
.section--sand .btn-ghost {
  color: var(--sand-ink);
  border-color: rgba(var(--band-ink-rgb), 0.4);
}
.section--sand .btn-ghost:hover {
  border-color: var(--sand-ink);
  color: var(--sand-ink);
}

.form-wrap {
  width: min(760px, 100%);
  margin: 0 auto;
}
.form-wrap form {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0 18px;
}
.form-wrap form .field {
  grid-column: 1 / -1;
}
.form-wrap .form-foot {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  margin-top: 4px;
}
.form-wrap .form-foot .note {
  margin-top: 0;
}
form .field {
  margin-bottom: 18px;
}
form label {
  display: block;
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}
form input,
form select,
form textarea {
  width: 100%;
  background: transparent;
  border: 1px solid var(--line-soft);
  border-radius: var(--r);
  padding: 13px 15px;
  color: var(--ink);
  font-family: "Poppins", system-ui, sans-serif;
  font-size: 0.94rem;
  font-weight: 300;
  transition: 0.25s;
}
form select {
  background: var(--bg);
}
form input:focus,
form select:focus,
form textarea:focus {
  outline: none;
  border-color: var(--gold);
}
form textarea {
  resize: vertical;
  min-height: 110px;
}
form .note {
  font-size: 0.8rem;
  color: var(--muted);
  margin-top: 10px;
}

/* --- Pages légales : texte long, colonne de lecture étroite --- */
.legal {
  max-width: 78ch;
}
.legal h2 {
  font-size: clamp(1.15rem, 1.35vw, 1.55rem);
  color: var(--gold-2);
  margin: clamp(28px, 2.6vw, 42px) 0 12px;
}
.legal h2:first-child {
  margin-top: 0;
}
.legal p,
.legal li {
  color: var(--ink-soft);
  font-size: clamp(0.92rem, 0.92vw, 1.02rem);
  margin-bottom: 12px;
}
.legal ul {
  padding-left: 1.2em;
  margin-bottom: 12px;
}
.legal li {
  margin-bottom: 7px;
}
.legal strong {
  color: var(--ink);
  font-weight: 500;
}
.legal a {
  color: var(--gold-2);
}
.legal .updated {
  margin-top: clamp(30px, 3vw, 46px);
  padding-top: 18px;
  border-top: 1px solid var(--line-soft);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

/* --- Bloc de confirmation / erreur (merci, 404) --- */
.plain {
  min-height: 62vh;
  display: grid;
  place-items: center;
  text-align: center;
  padding: calc(var(--hdr-h) + clamp(50px, 6vw, 100px)) 0 clamp(50px, 6vw, 100px);
}
.plain h1 {
  font-size: clamp(2rem, 4vw, 3.4rem);
  color: var(--gold-2);
  margin: 16px auto 18px;
  max-width: 22ch;
}
.plain p {
  color: var(--ink-soft);
  max-width: 52ch;
  margin: 0 auto clamp(26px, 3vw, 38px);
}

/* ==========================================================================
   10. PIED DE PAGE
   Compact et sobre : filet supérieur, colonnes de liens, ligne de mentions.
   ========================================================================== */
/* Pas de border-top ici : le filet rouge de la couture est un pseudo-élément
   posé à top:0, c'est-à-dire à l'intérieur de la bordure. Les deux se
   superposaient donc en deux traits d'un pixel — un gris sur la coupe, un
   rouge un pixel plus bas — et la ligne rouge tombait à côté de la couture au
   lieu de la tenir. Mesar ne met pas non plus de bordure à son pied : le filet
   suffit, et il est alors exactement sur la jointure. */
footer {
  position: relative;
  z-index: 2;
  flex: none;
  padding: clamp(24px, 2vw, 34px) 0 clamp(10px, 0.8vw, 14px);
  background: var(--bg-2);
}
.foot-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(14px, 1.3vw, 26px);
  margin-bottom: clamp(14px, 1.2vw, 20px);
}
.foot-address {
  font-style: normal;
  font-size: 0.79rem;
  line-height: 1.55;
  color: var(--muted);
  margin-bottom: 8px;
}
.foot-line {
  display: block;
  font-size: 0.82rem;
  line-height: 1.55;
  color: var(--ink-soft);
  text-decoration: none;
  transition: color 0.25s;
  overflow-wrap: anywhere;
}
.foot-line:hover {
  color: var(--gold-2);
}
.foot-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.foot-badge i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--mark);
  flex: none;
}
.foot-grid .ft-title {
  font-size: 0.75rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--mark);
  margin: 0 0 10px;
  font-weight: 500;
}
.foot-grid a {
  position: relative;
  display: block;
  color: var(--ink-soft);
  text-decoration: none;
  font-size: 0.84rem;
  /* 24 px de haut au minimum (WCAG 2.5.8) : le rembourrage remplace la marge. */
  padding: 5px 0;
  margin-bottom: 0;
  transition:
    color 0.25s,
    padding-left 0.25s;
  width: fit-content;
}
.foot-grid a::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 0;
  height: 1px;
  background: var(--gold);
  transition: width 0.25s;
}
.foot-grid a:hover {
  color: var(--gold-2);
  padding-left: 14px;
}
.foot-grid a:hover::before {
  width: 9px;
}
/* Les lignes de contact ne sont pas des liens de navigation : pas de décalage. */
.foot-grid .foot-line::before {
  content: none;
}
.foot-grid .foot-line:hover {
  padding-left: 0;
}
.foot-brand .mark {
  --lk: 1.02rem;
}
.foot-brand .mark .lk-k {
  color: var(--mark);
}
.foot-brand .mark .lk-n {
  color: var(--ink);
}
.foot-brand p {
  color: var(--muted);
  font-size: 0.8rem;
  margin-top: 8px;
  max-width: 32ch;
}
.socials {
  display: flex;
  gap: 7px;
  margin-top: 12px;
}
.socials a {
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line-soft);
  transition: 0.25s;
}
.socials a:hover {
  border-color: var(--gold-2);
}
.socials svg {
  width: 15px;
  height: 15px;
  fill: var(--gold-2);
}
.foot-bottom {
  border-top: 1px solid var(--line-soft);
  padding-top: 10px;
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 20px;
  font-size: 0.75rem;
  color: var(--muted);
}

/* ==========================================================================
   11. CONTACT DIRECT FLOTTANT (WhatsApp)
   Pastille dorée circulaire, cohérente avec les boutons fléchés.
   ========================================================================== */
.wa-float {
  position: fixed;
  right: clamp(14px, 1.5vw, 28px);
  bottom: clamp(14px, 1.5vw, 28px);
  z-index: 60;
  display: inline-flex;
  align-items: center;
  gap: 0;
  height: 54px;
  padding: 0 15px;
  border-radius: 100px;
  background: var(--gold);
  color: var(--sand-ink);
  text-decoration: none;
  box-shadow: 0 14px 34px -12px rgba(0, 0, 0, 0.8);
  transition: 0.3s;
  overflow: hidden;
}
.wa-float svg {
  width: 24px;
  height: 24px;
  fill: var(--sand-ink);
  flex: none;
}
.wa-float span {
  max-width: 0;
  opacity: 0;
  white-space: nowrap;
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  transition:
    max-width 0.35s,
    opacity 0.25s,
    margin-left 0.35s;
  margin-left: 0;
}
.wa-float:hover,
.wa-float:focus-visible {
  background: var(--gold-2);
}
.wa-float:hover span,
.wa-float:focus-visible span {
  max-width: 220px;
  opacity: 1;
  margin-left: 10px;
}

/* ==========================================================================
   12. ANIMATIONS, RÉACTIVITÉ, ACCESSIBILITÉ
   ========================================================================== */
@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(22px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
/* Révélation au défilement — sans JS, le contenu reste visible. */
.reveal {
  opacity: 1;
}
html.js .reveal {
  opacity: 0;
  /* 34 px et 0,95 s, contre 26 px et 0,8 s : le fondu se jouait bel et bien —
     mesuré à l'écran, l'opacité montait par paliers — mais si peu et si vite
     qu'il passait pour une bascule. C'est la course du mouvement qui se voit,
     pas sa durée seule. */
  transform: translateY(34px);
  /* Les DEUX propriétés sont nommées. Le raccourci `transition: 0.95s …`
     s'applique à tout ce qui est animable — `flex-basis` compris. Quand une
     fiche passait de la mosaïque au rail, sa largeur ne sautait donc pas à
     84 % : elle y allait en presque une seconde, en partant de 30 px. C'est
     le « rectangle extra-fin qui s'ouvre » qu'on voyait à chaque changement
     de vue. Un raccourci de transition sans liste de propriétés est un piège :
     il anime aussi ce qu'on n'avait pas prévu d'animer. */
  transition:
    opacity 0.95s cubic-bezier(0.2, 0.7, 0.2, 1),
    transform 0.95s cubic-bezier(0.2, 0.7, 0.2, 1);
}
html.js .reveal.in {
  opacity: 1;
  transform: none;
}
/* Entrée en cascade des éléments d'un bloc révélé */
.reveal.in .feat-item,
.reveal.in .stat,
.reveal.in .local-list .li,
.reveal.in .why .w,
.reveal.in .couv,
.reveal.in .svc {
  animation: rise 0.6s backwards;
}
.reveal.in .feat-item:nth-child(2),
.reveal.in .stat:nth-child(2) {
  animation-delay: 0.08s;
}
.reveal.in .feat-item:nth-child(3),
.reveal.in .stat:nth-child(3) {
  animation-delay: 0.16s;
}
.reveal.in .feat-item:nth-child(4),
.reveal.in .stat:nth-child(4) {
  animation-delay: 0.24s;
}
.reveal.in .feat-item:nth-child(5) {
  animation-delay: 0.32s;
}
.reveal.in .feat-item:nth-child(6) {
  animation-delay: 0.4s;
}
.reveal.in .local-list .li:nth-child(2),
.reveal.in .why .w:nth-child(2) {
  animation-delay: 0.07s;
}
.reveal.in .local-list .li:nth-child(3),
.reveal.in .why .w:nth-child(3) {
  animation-delay: 0.14s;
}
.reveal.in .local-list .li:nth-child(4),
.reveal.in .why .w:nth-child(4) {
  animation-delay: 0.21s;
}
.reveal.in .local-list .li:nth-child(5) {
  animation-delay: 0.28s;
}

/* Transition douce d'une page à l'autre (navigation par onglets) */
@view-transition {
  navigation: auto;
}
html.js main {
  animation: pageIn 0.42s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
@keyframes pageIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* --- Paliers de mise en page ------------------------------------------- */
@media (min-width: 560px) {
  .form-wrap form {
    grid-template-columns: 1fr 1fr;
  }
  .form-wrap form .field.half {
    grid-column: auto;
  }
  .svc-grid,
  .feat-row {
    grid-template-columns: repeat(2, 1fr);
  }
  .mosaic {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 760px) {
  .proj-grid {
    grid-template-columns: repeat(2, 1fr);
  }
  .stats {
    grid-template-columns: repeat(4, 1fr);
  }
  .foot-grid {
    grid-template-columns: 1.5fr 1fr 1fr;
  }
}
@media (min-width: 1000px) {
  .svc-row {
    grid-template-columns: 0.95fr 1.05fr;
    align-items: start;
  }
  .mosaic {
    grid-template-columns: repeat(3, 1fr);
  }
  .feat-row {
    grid-template-columns: repeat(3, 1fr);
  }
  .local-grid {
    grid-template-columns: 1.05fr 0.95fr;
  }
  .foot-grid {
    grid-template-columns: 1.35fr 0.8fr 0.95fr 0.95fr 1.1fr;
  }
}
@media (min-width: 1100px) {
  .svc-grid {
    grid-template-columns: repeat(4, 1fr);
  }
  .dom-grid {
    grid-template-columns: repeat(4, 1fr);
  }
  .proj-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}
/* Très grands écrans : une colonne de plus plutôt que des cartes étirées. */
@media (min-width: 2000px) {
  .proj-grid {
    grid-template-columns: repeat(4, 1fr);
  }
  .feat-row {
    grid-template-columns: repeat(6, 1fr);
  }
}

/* --- Navigation mobile ------------------------------------------------- */
@media (max-width: 900px) {
  .nav {
    grid-template-columns: auto auto;
    justify-content: space-between;
  }
  /* Le panneau prolonge l'en-tête : même noir, mêmes encres claires. Il était
     posé sur le blanc de la page alors que les liens, eux, sont écrits pour la
     barre noire (`header .nav-links a` les met en clair) — le menu s'ouvrait
     donc blanc sur blanc, vide.
     `justify-self: stretch` annule le `center` de la règle de base : sur un
     élément positionné, cet alignement fait rétrécir la boîte à son contenu
     puis la centre, si bien que le panneau ne mesurait que 159 px et flottait
     au milieu de l'écran au lieu de tenir toute la largeur. */
  .nav-main {
    position: fixed;
    top: var(--hdr-h);
    left: 0;
    right: 0;
    justify-self: stretch;
    /* Opaque : à 98 % le flou d'arrière-plan ramenait les blancs du hero et le
       titre transparaissait derrière les liens. */
    background: #0a0b0c;
    padding: 6px clamp(1.15rem, 5vw, 3.5rem) 18px;
    border-bottom: 2px solid var(--mark);
    box-shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.65);
    transform: translateY(-135%);
    transition: transform 0.4s;
    z-index: 40;
    visibility: hidden;
  }
  .nav-main.open {
    transform: translateY(0);
    visibility: visible;
  }
  .nav-links {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }
  /* Séparateurs clairs : sur le noir du panneau, --line-soft (une encre à 10 %)
     est invisible. */
  .nav-links li {
    width: 100%;
    border-bottom: 1px solid rgba(255, 255, 255, 0.12);
  }
  .nav-links li:last-child {
    border-bottom: none;
  }
  .nav-links a {
    padding: 15px 2px;
    font-size: 0.94rem;
  }
  /* L'onglet courant se signale par le rouge de la marque, comme le point du
     logo — le soulignement de bureau est illisible dans une liste verticale.
     Un sélecteur de plus que `header .nav-links a.is-current`, écrit plus bas
     dans la feuille : à égalité de spécificité, c'est lui qui gagnerait. */
  header .nav-main .nav-links a.is-current {
    color: var(--mark-lite);
  }
  .nav-links a.is-current::after {
    display: none;
  }
  .nav-links a::after {
    bottom: 8px;
  }
  .burger {
    display: flex;
  }
  .nav-right .btn {
    display: none;
  }

  /* --- Corrections propres au téléphone ---------------------------------- */

  /* L'étiquette du hero passe sur deux lignes ici, et les filets ne flanquaient
     alors que la première : ils sautent. Le corps remonte au-dessus du seuil de
     lisibilité — 9,9 px mesurés, c'est trop peu sur un écran tenu à la main. */
  .brand-hero .bh-kick {
    font-size: 0.75rem;
    letter-spacing: 0.2em;
  }
  .brand-hero .bh-kick::before,
  .brand-hero .bh-kick::after {
    display: none;
  }

  /* Fil d'Ariane : 10,2 px et 17 px de haut — illisible et intouchable. */
  .crumb {
    font-size: 0.75rem;
  }
  .crumb a {
    display: inline-block;
    padding: 6px 0;
  }

  /* « Voir le site » : la seule sortie de chaque fiche projet, elle faisait
     20 px de haut. */
  .proj .visit {
    font-size: 0.8rem;
    padding: 8px 0;
  }

  /* Cibles tactiles : appeler depuis un téléphone est le geste le plus utile
     du site, et la pastille ne faisait que 23 px. Les icônes sociales du pied
     passent de 30 à 40 px. */
  .phone-pill {
    min-height: 44px;
    padding-block: 10px;
  }
  .socials a {
    width: 40px;
    height: 40px;
  }
}
@media (max-width: 620px) {
  .foot-bottom {
    justify-content: flex-start;
  }
  .contact-strip .cmethod {
    max-width: none;
  }
  .wa-float {
    height: 48px;
  }
}

/* --- Mouvement réduit ---------------------------------------------------
   Aucune exception : le hero est une image fixe, et tout ce qui bouge tout
   seul s'arrête. Seul le zoom du hero suit encore le geste de défilement
   (il est piloté par JS et ne s'anime pas de lui-même). --- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
  .hero h1,
  .hero-scroll,
  .hero-cta,
  .brand-hero,
  html.js main {
    opacity: 1 !important;
    transform: none !important;
  }
  /* Cinquième exception nommée : le trait d'invitation au défilement. Un
     déroulé d'opacité lent, sans déplacement de contenu — même famille que le
     bandeau et les ondes du lecteur. */
  .hero-scroll i {
    animation: scrollCue 3.8s cubic-bezier(0.16, 1, 0.3, 1) infinite !important;
  }
  /* Quatrième exception nommée : la révélation au défilement garde son fondu.
     Elle était éteinte ici — opacity:1 !important — et tout le contenu
     apparaissait d'un bloc, alors que c'est justement ce qui donne son rythme
     au site de Mesar, qui ne neutralise que le déplacement (transform: none,
     transition ramenée à 0,5 s) et conserve la montée en opacité. Un fondu
     sans translation ni échelle ne provoque pas de malaise vestibulaire :
     c'est le même raisonnement que pour le voile de chargement, plus haut.
     Les blocs restés hors JavaScript sont couverts par `.reveal { opacity: 1 }`
     et par le filet de sécurité de main.js. */
  /* CORRECTION du 13.08.2026. La translation était supprimée ici, et il ne
     restait qu'un fondu d'opacité : sur le poste du studio, où ce réglage est
     actif, le contenu semblait apparaître d'un coup — « sans effet ». Le
     responsable a demandé que les effets se voient sur tous les appareils.
     Le déplacement reste donc, réduit au tiers : 12 px au lieu de 34. Une
     course aussi courte ne provoque pas de malaise vestibulaire, et elle
     suffit à ce que l'œil voie le bloc arriver plutôt que se substituer au
     vide. Huitième exception nommée. */
  html.js .reveal {
    opacity: 0;
    transform: translateY(12px) !important;
    transition:
      opacity 0.85s ease,
      transform 0.85s cubic-bezier(0.2, 0.7, 0.2, 1) !important;
  }
  html.js .reveal.in {
    transform: none !important;
  }
  html.js .reveal.in {
    opacity: 1;
  }
  /* La cascade interne suit : mêmes retards, mais le keyframe est redéfini ici
     sans translation — un @keyframes peut être surchargé dans une media query. */
  @keyframes rise {
    from {
      opacity: 0;
      transform: translateY(9px);
    }
    to {
      opacity: 1;
      transform: none;
    }
  }
  .reveal.in .feat-item,
  .reveal.in .stat,
  .reveal.in .local-list .li,
  .reveal.in .why .w,
  .reveal.in .couv,
  .reveal.in .svc {
    animation: rise 0.75s ease backwards !important;
  }
  .reveal.in .feat-item:nth-child(2),
  .reveal.in .stat:nth-child(2),
  .reveal.in .local-list .li:nth-child(2),
  .reveal.in .why .w:nth-child(2) {
    animation-delay: 0.13s !important;
  }
  .reveal.in .feat-item:nth-child(3),
  .reveal.in .stat:nth-child(3),
  .reveal.in .local-list .li:nth-child(3),
  .reveal.in .why .w:nth-child(3) {
    animation-delay: 0.26s !important;
  }
  .reveal.in .feat-item:nth-child(4),
  .reveal.in .stat:nth-child(4),
  .reveal.in .local-list .li:nth-child(4),
  .reveal.in .why .w:nth-child(4) {
    animation-delay: 0.39s !important;
  }
  .reveal.in .feat-item:nth-child(5),
  .reveal.in .local-list .li:nth-child(5) {
    animation-delay: 0.52s !important;
  }
  .reveal.in .feat-item:nth-child(6) {
    animation-delay: 0.65s !important;
  }
  /* Excepción asumida y única: el fundido de entrada. Es un cambio de
     opacidad, sin desplazamiento ni escala — no provoca malestar vestibular,
     y es lo que da continuidad al pasar de una página a otra. Sin él, el velo
     de carga desaparecería de golpe.

     CORRECCIÓN del 13.08.2026. Esta excepción apuntaba a `.vk` y `.vn`,
     clases que dejaron de existir cuando la v4.0.0 unificó el logotipo en un
     solo `.lockup`. El resultado: bajo este ajuste, la regla general apagaba
     la animación `veilIn` y las dos palabras se quedaban en `opacity: 0`
     —su valor de partida— sin nada que las levantara. **El velo de carga era
     una pantalla vacía**, sin marca, en todos los equipos con «reducir
     movimiento» activado. Se conserva la animación, que solo mueve opacidad. */
  .veil-load .lk-k {
    animation: veilIn 0.7s ease 0.05s forwards !important;
  }
  .veil-load .lk-n {
    animation: veilIn 0.7s ease 0.16s forwards !important;
  }
  body.is-loaded .veil-load {
    transition:
      opacity 0.45s cubic-bezier(0.4, 0, 0.2, 1) 0.04s,
      visibility 0s linear 0.5s !important;
  }
  /* Deuxième exception nommée : le bandeau ralentit au lieu de se figer.
     Arrêté, il afficherait des mots coupés par son propre dégradé de bord ;
     à cette vitesse, le mouvement n'accroche plus l'œil. */
  .marquee__track {
    animation: marquee 95s linear infinite !important;
  }
  /* Les veines de l'en-tête : même traitement. Figées, elles laisseraient une
     tache rouge arbitraire en travers de la bande. */
  header::before {
    animation: hdrFlow 150s linear infinite !important;
  }
  /* Troisième exception nommée : les ondes du lecteur continuent. Elles
     étaient éteintes ici (opacity 0), si bien que sur un Mac réglé sur
     « réduire les animations » — celui du responsable — le bouton restait nu
     et rien n'invitait à lancer le son. Le lecteur de Mesar, lui, ne neutralise
     pas son halo : c'est la référence, et le geste est lent, ample et sans
     déplacement de contenu, donc sans danger pour qui souffre du mouvement. */
  .player__waves i {
    animation: player-onde 3.4s ease-out infinite !important;
  }
  .player__waves i:nth-child(2) {
    animation-delay: 1.13s !important;
  }
  .player__waves i:nth-child(3) {
    animation-delay: 2.26s !important;
  }
}

/* ==========================================================================
   13. EL HERO Y SU CABECERA
   El texto del hero se posa sobre la fotografía, no sobre el fondo de la
   página: necesita su propio juego de colores. En las direcciones claras sin
   foto, esas fichas apuntan al color normal del texto y todo sigue igual.
   ========================================================================== */
.hero-inner,
.hero .lead,
.brand-hero .bh-name {
  color: var(--hero-ink);
}
.brand-hero .bh-claim {
  color: var(--hero-soft);
}
.brand-hero .bh-kick {
  color: var(--hero-accent);
}
/* « un expert d'ici » est LE mot à accentuer du hero — et la fiche --hero-accent
   pointe sur l'encre depuis que l'or a été retiré : l'accent était donc noir
   sur noir, invisible. Il prend le rouge de la marque, seule touche de rouge
   au centre du hero. */
/* La phrase du hero est d'un seul blanc, à la demande du responsable :
   l'accent coloré sur « un expert d'ici » découpait la ligne en deux, alors
   qu'elle se lit d'un trait. Le rouge reste la couleur de la marque, pas celle
   d'un mot. */
.hero .gold-text {
  color: inherit;
}
.hero .btn-gold {
  background: var(--hero-btn-bg);
  color: var(--hero-btn-ink);
}
.hero .btn-ghost {
  color: var(--hero-ink);
  border-color: var(--hero-line);
}
.hero .btn-ghost:hover {
  border-color: var(--hero-ink);
  background: transparent;
}
/* L'en-tête est sombre du haut de page jusqu'au bas : ses couleurs ne
   changent donc jamais en cours de route. */
/* Il faut la même profondeur de sélecteur que la règle de base, sinon
   celle-ci l'emporte et le nom reste noir sur le filigrane noir. */
header .brand .brand-name {
  color: var(--hdr-ink);
}
header .brand .brand-kick {
  color: var(--mark-lite);
}
header .nav-links a {
  color: var(--hdr-soft);
}
header .nav-links a.is-current,
header .nav-links a:hover,
header .nav-links a:focus-visible {
  color: var(--hdr-ink);
}
header .nav-links a::after {
  background: var(--hdr-ink);
}
header .lang__toggle {
  color: var(--hdr-ink);
  border-color: var(--hdr-line);
}
header .lang__toggle:hover,
header .lang__toggle[aria-expanded="true"] {
  border-color: var(--hdr-ink);
}
/* Même bouton que « Demander une démo » du hero : noir, pas rouge. Sur une
   bande noire il lui faut son filet, sinon il n'existe pas. */
header .btn-gold {
  background: rgba(0, 0, 0, 0.55);
  color: #ffffff;
  border: 1px solid rgba(255, 255, 255, 0.42);
}
header .btn-gold:hover {
  background: #ffffff;
  color: var(--ink);
  border-color: #ffffff;
}
header .burger span {
  background: var(--hdr-ink);
}
/* Las fotografías del sitio reciben el tratamiento de la dirección.
   `.mo img` ya NO está aquí: desde que `--photo-filter` pasó a ser el ajuste
   del hero —desaturación leve y mucho brillo, pensados para una foto bajo
   velo—, esta regla le imponía al mosaico justo lo contrario de lo que se
   busca ahí. El mosaico lleva su propio filtro, declarado con él. */
.cmethod-photo img {
  filter: var(--photo-filter);
}

/* ==========================================================================
   14. FUNDIDO DE ENTRADA
   Un velo del color de la página cubre la carga y se disuelve. Como cada
   pestaña es una página real, el mismo velo produce la transición al pasar
   de una sección a otra del sitio. Es un fundido de opacidad, sin
   desplazamiento: por eso se conserva incluso con « animaciones reducidas ».
   ========================================================================== */
.veil-load {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: grid;
  place-content: center;
  justify-items: center;
  background: var(--bg);
  pointer-events: none;
}
/* Le voile porte le même lockup que l'en-tête, en grand : c'est la marque
   qu'on doit reconnaître pendant le chargement, pas une variante. */
.veil-load .lockup {
  --lk: clamp(1.5rem, 3.4vw, 2.6rem);
}
.veil-load .lk-k {
  color: var(--mark);
  opacity: 0;
  animation: veilIn 0.7s ease 0.05s forwards;
}
.veil-load .lk-n {
  color: var(--ink);
  opacity: 0;
  animation: veilIn 0.7s ease 0.16s forwards;
}
@keyframes veilIn {
  to {
    opacity: 1;
  }
}
body.is-loaded .veil-load {
  opacity: 0;
  visibility: hidden;
  transition:
    opacity 0.45s cubic-bezier(0.4, 0, 0.2, 1) 0.04s,
    visibility 0s linear 0.5s;
}
/* Sin JavaScript el velo no debe atrapar la página. */
html:not(.js) .veil-load {
  display: none;
}

/* ==========================================================================
   15. RITMO ENTRE SECCIONES
   En una página de un solo color todo se funde. Tres recursos, en orden de
   fuerza: fondos alternos, un ordinal grande en filigrana, y la banda
   invertida — que corta el recorrido en seco.
   ========================================================================== */
.section {
  overflow: hidden;
}
/* Un tono alterno cada dos secciones: es lo que de verdad separa. */
.section--alt {
  background: var(--bg-2);
}
/* Ordinal en filigrane : il tient dans la marge haute de la section, au-dessus
   du contenu. Sans cette réserve, un panneau pleine largeur — celui de la page
   Approche, par exemple — lui passait par-dessus et le mangeait. */
.section[data-idx] {
  padding-top: calc(var(--section-y) + clamp(26px, 3.4vw, 62px));
}
.section[data-idx]::after {
  content: attr(data-idx);
  position: absolute;
  top: clamp(8px, 1.2vw, 22px);
  right: clamp(18px, 3vw, 64px);
  z-index: 0;
  font-family: var(--font-head);
  font-weight: var(--head-weight);
  font-size: clamp(3.2rem, 6.4vw, 7.6rem);
  line-height: 0.8;
  color: var(--mark);
  /* Sur les bandes sombres l'ordinal ressort tout seul ; sur le blanc, à 13 %,
     le rouge donnait rgb(246,226,227) — un rose que l'écran ne rend plus. Les
     sections claires demandent donc le double d'encre pour le même effet de
     filigrane. */
  opacity: 0.3;
  pointer-events: none;
  user-select: none;
}
.section--alt[data-idx]::after {
  opacity: 0.3;
}
.band-dark[data-idx]::after {
  color: var(--mark-lite);
  opacity: 0.2;
}
/* Le filet court à CHAQUE changement de bande, y compris — et surtout — là
   où le ton de fond change : c'est la couture qu'il faut souligner. */

/* ==========================================================================
   16. LA MARQUE RÉAGIT AU SURVOL
   Le lettrage de l'en-tête n'est pas une image posée : il respire quand on
   l'approche. Le mouvement est court et lisible — la marque doit rester
   déchiffrable pendant tout le geste, pas se transformer en effet.
   ========================================================================== */
/* Le survol échange les couleurs : STUDIO cède son rouge à Broye, Broye cède
   son blanc à STUDIO — le point rouge devient blanc dans le même geste. Rien
   ne bouge ni ne grossit : la quantité de rouge et de blanc reste la même,
   seule leur place s'inverse, si bien que la marque répond sans jamais peser
   plus lourd. Aucune propriété de mise en page n'est touchée : le menu reste
   à 0,0 px (l'ancien effet d'écart de lettres le déplaçait de 2,9 px). */
.brand .brand-kick,
.brand .brand-name {
  transition: color 0.4s ease;
}
.brand .brand-name .pt {
  transition: background 0.4s ease;
}
.brand:hover .brand-kick,
.brand:focus-visible .brand-kick {
  color: #ffffff;
}
.brand:hover .brand-name,
.brand:focus-visible .brand-name {
  color: var(--mark-lite);
}
.brand:hover .brand-name .pt,
.brand:focus-visible .brand-name .pt {
  background: #ffffff;
}
/* Pas de trait sous le lettrage : la marque porte déjà son point rouge, et un
   souligné au survol ajoutait une deuxième ligne rouge juste au-dessus du
   filet de l'en-tête. L'écart des lettres suffit à marquer la réaction. */
.brand {
  position: relative;
}

/* ==========================================================================
   17. BANDEAU DÉFILANT
   Juste sous le hero : ce que fait le studio, en continu. Deux groupes
   identiques défilent d'une demi-longueur — la boucle est donc invisible.
   Décoratif : masqué aux lecteurs d'écran, l'information est dans la page.
   ========================================================================== */
/* Le lockup tient la gauche du ruban et les mots défilent à sa droite : la
   marque signe la bande au lieu de la laisser filer anonyme. */
.marquee {
  display: flex;
  align-items: stretch;
  position: relative;
  overflow: hidden;
  /* Noir à gauche, où commencent les mots, et le rouge qui monte vers la
     droite. Même logique que l'en-tête : la couleur ne doit jamais gêner la
     lecture de ce qui est écrit au départ. */
  background: linear-gradient(
    90deg,
    var(--band) 0%,
    var(--band) 34%,
    #3d0b10 58%,
    #7d151d 78%,
    var(--mark) 100%
  );
  color: var(--band-ink);
  padding-block: clamp(14px, 1.4vw, 22px);
}
/* La fenêtre porte le dégradé d'effacement, pas le ruban : sinon le fond
   coloré disparaîtrait avec les mots, et le masque défilerait avec eux. */
.marquee__brand {
  --lk: 0.72rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  flex: none;
  padding-inline: clamp(16px, 2.4vw, 40px);
  border-right: 1px solid rgba(255, 255, 255, 0.18);
  z-index: 2;
}
.marquee__brand .lk-k {
  color: var(--mark-lite);
}
.marquee__brand .lk-n {
  color: #ffffff;
}
.marquee__win {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee__track {
  display: flex;
  width: max-content;
  animation: marquee 34s linear infinite;
  will-change: transform;
}
.marquee__group {
  display: flex;
  align-items: center;
  gap: clamp(1.6rem, 3vw, 3rem);
  padding-right: clamp(1.6rem, 3vw, 3rem);
}
.marquee span {
  font-size: clamp(0.72rem, 1.05vw, 0.92rem);
  font-weight: 500;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--band-ink);
  white-space: nowrap;
}
.marquee i {
  font-style: normal;
  color: var(--mark);
  font-size: 1.1em;
}
@keyframes marquee {
  to {
    transform: translateX(-50%);
  }
}

/* ==========================================================================
   18. LECTEUR D'AMBIANCE
   Bouton discret en bas à gauche, jamais de son sans un geste explicite.
   Le choix (en cours, en pause, coupé) survit au changement de page.
   ========================================================================== */
/* Le lecteur passe à droite, empilé au-dessus de WhatsApp : en bas à gauche
   il recouvrait le lockup du ruban, qui est justement ce qu'on veut lire. */
/* L'écart au bouton WhatsApp tient compte des ondes : elles vont jusqu'à
   scale(2.1), soit ~29 px au-delà de la pastille de 52 px. En dessous de cette
   marge, le halo rouge traversait la pastille verte. */
.player {
  position: fixed;
  right: clamp(14px, 1.5vw, 28px);
  bottom: calc(clamp(14px, 1.5vw, 28px) + 54px + 32px);
  z-index: 60;
}
/* Bouton repris tel quel du lecteur de Mesar : pastille ronde, fond sombre
   translucide, anneau coloré, quatre barres qui ne dansent que si le son
   joue vraiment. Seule la couleur change — leur or devient notre rouge. */
.player__toggle {
  position: relative;
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  flex-shrink: 0;
  border-radius: 50%;
  background: rgba(17, 19, 21, 0.88);
  border: 1px solid rgba(var(--mark-rgb), 0.55);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  cursor: pointer;
  transition:
    background 0.3s,
    border-color 0.3s,
    transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.player__toggle:hover,
.player__toggle[aria-expanded="true"] {
  background: var(--mark);
  border-color: var(--mark);
  transform: translateY(-2px);
}
/* Ondes concentriques tant que le visiteur n'a pas lancé le son. Écriture
   reprise telle quelle du lecteur de Mesar : l'anneau part à 4 px du bouton et
   ne s'ouvre que jusqu'à 1,45 — chez nous il montait à 2,1, et un trait d'un
   pixel étiré sur ce rayon-là ne laissait qu'un rose délavé. Seule la couleur
   change : leur laiton devient notre rouge, à pleine densité pour qu'il se lise
   aussi bien sur le blanc du hero que sur les bandes sombres.
   Trois anneaux décalés d'un tiers de période : le halo ne bat pas, il ondule. */
.player__waves {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
/* Les ondes ne dépendent plus d'aucun état. Elles étaient accrochées à
   `.is-inviting`, que le lecteur retire au PREMIER geste de la page — y
   compris un simple coup de molette. Autrement dit : elles s'éteignaient
   avant d'avoir été vues, et un « Couper » les supprimait pour toute la
   session. Elles tournent maintenant en permanence, sur toute machine et tout
   navigateur : c'est le seul endroit d'où le son peut partir, il doit se
   signaler tant qu'on est sur la page. */
.player__waves i {
  position: absolute;
  inset: -5px;
  border-radius: 50%;
  /* Deux pixels, pas un : le filet d'un pixel de Mesar se lit sur leur fond
     noir, mais ici l'onde traverse le blanc du hero, où il disparaissait à
     l'échelle 1. Elle part opaque et ne s'éteint qu'en fin de course. */
  border: 2px solid var(--mark-lite);
  opacity: 0;
  animation: player-onde 2.6s ease-out infinite;
}
.player__waves i:nth-child(2) {
  animation-delay: 0.87s;
}
.player__waves i:nth-child(3) {
  animation-delay: 1.74s;
}
/* L'onde s'ouvre jusqu'à 1,75 : au-delà elle irait mordre la pastille
   WhatsApp, qui est 32 px plus bas. */
@keyframes player-onde {
  0% {
    transform: scale(1);
    opacity: 1;
  }
  65% {
    opacity: 0.5;
  }
  100% {
    /* L'ouverture est une fiche : sur téléphone la pastille est plus petite et
       plus près du bord, et une onde à 1,75 sortait de l'écran. */
    transform: scale(var(--onde-max, 1.75));
    opacity: 0;
  }
}
/* Quand la musique joue, l'onde repart depuis le bouton, plus discrète :
   elle dit « ça tourne » sans réclamer l'attention. */
.player.is-playing .player__waves i {
  animation: player-onde 3.4s ease-out infinite;
  border-color: var(--mark-lite);
}
.player.is-playing .player__waves i:nth-child(2) {
  animation-delay: 1.13s;
}
.player.is-playing .player__waves i:nth-child(3) {
  animation-delay: 2.26s;
}
.player__bars {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 18px;
}
.player__bars i {
  display: block;
  width: 3px;
  height: 6px;
  background: var(--mark-lite);
  transition: background 0.3s;
}
.player__toggle:hover .player__bars i {
  background: #ffffff;
}
.player.is-playing .player__bars i {
  animation: player-bar 1s ease-in-out infinite;
}
.player.is-playing .player__bars i:nth-child(2) {
  animation-delay: 0.18s;
}
.player.is-playing .player__bars i:nth-child(3) {
  animation-delay: 0.36s;
}
.player.is-playing .player__bars i:nth-child(4) {
  animation-delay: 0.54s;
}
@keyframes player-bar {
  0%,
  100% {
    height: 5px;
  }
  50% {
    height: 17px;
  }
}
/* Panneau sombre, comme celui de Mesar : un rectangle blanc sur une page
   blanche n'était pas un panneau, c'était un trou. Le fond reprend le noir de
   l'en-tête, le titre et l'action portent le rouge de la marque. */
.player__panel {
  position: absolute;
  right: 0;
  bottom: calc(100% + 12px);
  width: min(286px, calc(100vw - 5rem));
  padding: 20px 22px 22px;
  background: rgba(17, 19, 21, 0.96);
  border: 1px solid rgba(var(--mark-rgb), 0.34);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow: 0 22px 54px -18px rgba(0, 0, 0, 0.55);
}
.player__panel[hidden] {
  display: none;
}
.player__title {
  font-size: 0.75rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--mark-lite);
  font-weight: 500;
  margin-bottom: 16px;
}
/* Liste des pistes : celle qui joue porte le filet rouge. */
.player__list {
  display: grid;
  gap: 3px;
  margin: 0 0 16px;
  padding: 0;
  list-style: none;
}
.player__track {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  border-left: 2px solid transparent;
  padding: 8px 10px;
  cursor: pointer;
  transition:
    background 0.25s,
    border-color 0.25s;
}
.player__track:hover {
  background: rgba(var(--mark-rgb), 0.16);
}
.player__track.is-active {
  background: rgba(var(--mark-rgb), 0.2);
  border-left-color: var(--mark-lite);
}
.player__track-name {
  display: block;
  font-family: "Poppins", system-ui, sans-serif;
  font-size: 0.85rem;
  color: #ffffff;
}
.player__track-artist {
  display: block;
  font-size: 0.75rem;
  color: rgba(255, 255, 255, 0.52);
}
.player__controls {
  display: flex;
  gap: 8px;
}
.player__btn {
  flex: 1;
  padding: 11px 6px;
  font-family: "Poppins", system-ui, sans-serif;
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  cursor: pointer;
  border: 1px solid transparent;
  transition: 0.25s;
}
.player__play {
  background: var(--mark);
  color: #ffffff;
}
.player__play:hover {
  background: var(--mark-lite);
}
.player__play:disabled {
  background: rgba(255, 255, 255, 0.14);
  color: rgba(255, 255, 255, 0.4);
  cursor: not-allowed;
}
.player__stop {
  background: none;
  border-color: rgba(var(--mark-rgb), 0.5);
  color: rgba(255, 255, 255, 0.86);
}
.player__stop:hover {
  border-color: var(--mark-lite);
  background: rgba(var(--mark-rgb), 0.16);
}
.player__hint {
  margin-top: 14px;
  font-size: 0.75rem;
  color: rgba(255, 255, 255, 0.55);
  line-height: 1.5;
}
.player__hint:empty {
  margin-top: 0;
}
/* Le bouton flottant WhatsApp partage le bas de l'écran : on les sépare. */
/* Les pastilles flottantes sur écran tactile. Ce bloc vient APRÈS les règles de
   base du lecteur : placé plus haut, sa déclaration `right` perdait contre
   elles, à spécificité égale. Mesuré avant correction sur un téléphone de
   390 px : l'onde sortait de l'écran par la droite de 12 px et traversait le
   bouton WhatsApp, qui n'était qu'à 12 px en dessous. */
@media (max-width: 900px) {
  .player {
    --onde-max: 1.45;
    right: 22px;
  }
  .wa-float {
    right: 22px;
  }
}
@media (max-width: 620px) {
  .player__toggle {
    width: 46px;
    height: 46px;
  }
  /* L'écart tient compte de l'onde : à 1,45 elle monte de 17 px au-dessus de
     la pastille, et 12 px ne suffisaient pas. */
  .player {
    bottom: calc(16px + 48px + 20px);
  }
  .wa-float {
    bottom: 16px;
  }
}

/* Sur un écran court — un portable de 800 px de haut — le hero ne doit pas
   déborder. L'ancien réglage ramenait le titre à 3rem : écrit pour le hero à
   neuf rangées, il annulait toute la hiérarchie sur l'écran le plus courant.
   Le hero n'a plus que cinq rangées : le titre peut rester grand, il cède
   juste un cran. */
/* Portables courts et larges : le nom se raboterait sinon contre le bord bas.
   La condition de largeur est indispensable — un téléphone tenu à la main fait
   844 px de haut et tombait dans cette règle, qui le ramenait à 41,6 px. */
@media (max-height: 860px) and (min-width: 901px) {
  .brand-hero .bh-name {
    font-size: clamp(2.6rem, 6.8vw, 5.4rem);
  }
}

/* ==========================================================================
   19. BANDE TECHNIQUE — le bloc rouge de l'accueil
   Même écriture que la bande Instagram du site Mesar : une photographie en
   fond, un voile de couleur franc par-dessus, et le contenu centré en blanc.
   Ici la photographie est un circuit imprimé et le voile est le rouge de la
   marque : la section « expérience » cesse d'être un bloc de texte pâle.
   ========================================================================== */
.band-tech {
  position: relative;
  overflow: hidden;
  color: #ffffff;
}
.band-tech__bg {
  position: absolute;
  inset: 0;
  background: url("../img/mosaic-2.jpg") center / cover no-repeat;
  background-image: image-set(
    url("../img/mosaic-2.avif") type("image/avif"),
    url("../img/mosaic-2.webp") type("image/webp"),
    url("../img/mosaic-2.jpg") type("image/jpeg")
  );
  filter: grayscale(1) contrast(1.15) brightness(0.72);
}
.band-tech__veil {
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, rgba(var(--mark-rgb), 0.93) 0%, rgba(84, 11, 15, 0.96) 100%);
}
.band-tech > .wrap {
  position: relative;
  z-index: 2;
}
/* Sur le rouge, tout repasse en blanc — y compris ce qui portait l'encre. */
.band-tech .section-head h2,
.band-tech .stat .num,
.band-tech .foot,
.band-tech .more {
  color: #ffffff;
}
.band-tech .section-head p,
.band-tech .stat .lab,
.band-tech .why .w {
  color: rgba(255, 255, 255, 0.82);
}
.band-tech .eyebrow {
  color: rgba(255, 255, 255, 0.88);
}
.band-tech .eyebrow::before {
  background: #ffffff;
}
.band-tech .stat {
  border-top-color: rgba(255, 255, 255, 0.28);
}
.band-tech .stat:hover {
  border-top-color: #ffffff;
}
.band-tech .why .w {
  border-color: rgba(255, 255, 255, 0.3);
}
.band-tech .why .w:hover {
  border-color: #ffffff;
  color: #ffffff;
}
.band-tech .more svg {
  background: #ffffff;
  stroke: var(--mark);
}
.band-tech .more:hover svg {
  background: var(--ink);
  stroke: #ffffff;
}
/* L'ordinal passe à l'encre sur le rouge. En blanc à 16 % il se confondait
   avec les traits clairs du circuit imprimé qui court en fond de bande ; le
   noir, lui, ne ressemble à rien d'autre ici et se lit d'un coup d'œil. */
.band-tech[data-idx]::after {
  color: var(--ink);
  opacity: 0.34;
}

/* ==========================================================================
   20. BARRE DE PROGRESSION DE LECTURE
   Deux pixels rouges en tête de page qui disent où l'on en est. C'est le seul
   repère de position sur un site d'une seule colonne — et il coûte un
   transform, pas une mise en page.
   ========================================================================== */
.scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  z-index: 70;
  pointer-events: none;
}
.scroll-progress span {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--mark), var(--mark-lite));
  transform: scaleX(0);
  transform-origin: 0 50%;
  will-change: transform;
}

/* ==========================================================================
   21. FICHES DE RÉALISATION — relief au survol
   Une grille de vignettes grises ne dit pas qu'elle est cliquable : le filet
   rouge se déploie en tête de fiche et la carte se soulève.
   ========================================================================== */
.proj {
  transition:
    border-color 0.35s,
    transform 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.45s ease;
}
.proj::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--mark);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
  z-index: 3;
}
.proj:hover,
.proj.spot {
  transform: translateY(-6px);
  box-shadow: 0 34px 70px -38px rgba(0, 0, 0, 0.42);
}
.proj:hover::after,
.proj.spot::after {
  transform: scaleX(1);
}

/* Mouvement réduit : l'inclinaison et le relief s'effacent, le contenu reste. */
@media (prefers-reduced-motion: reduce) {
  .mo,
  .mo img {
    transform: none !important;
  }
  .proj:hover,
  .proj.spot {
    transform: none !important;
  }
}

/* ==========================================================================
   22. FORME MOBILE — la même information, présentée autrement
   Au-dessous de 900 px, une grille « une colonne » transforme huit fiches en
   huit écrans de défilement : Réalisations mesurait 6,9 écrans au téléphone
   contre 2,9 au portable, Services 5,2 contre 2,9. On ne réduit pas la
   taille du contenu, on change sa forme :
     • Réalisations : la grille devient un rail horizontal à aimantation ;
     • Services     : les huit domaines se replient en accordéon.
   Rien n'est retiré du document — tout reste dans la page, donc lisible par
   les moteurs et par les lecteurs d'écran. Et rien ne dépend du navigateur :
   c'est la largeur qui décide, comme partout ailleurs dans cette feuille.
   Ce bloc est volontairement en fin de fichier : les règles de .proj vivent
   dans la section 21, et une déclaration placée avant elles perdrait par
   ordre d'écriture à spécificité égale.
   ========================================================================== */

/* Repères du rail : créés par le script, invisibles hors du mobile. */
.rail-pts,
.rail-compte {
  display: none;
}

@media (max-width: 900px) {
  /* --- Rails horizontaux ------------------------------------------------
     Deux grilles y passent : les huit réalisations, et les huit services de
     l'accueil. Même geste, même repères. --- */
  .proj-grid,
  .svc-grid {
    display: flex;
    grid-template-columns: none;
    gap: 12px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    padding: 2px 0 16px;
    scrollbar-width: none;
  }
  .proj-grid::-webkit-scrollbar,
  .svc-grid::-webkit-scrollbar {
    display: none;
  }
  /* 84 % : la fiche suivante dépasse assez pour qu'on comprenne qu'on peut
     glisser, sans qu'on la prenne pour une colonne. */
  .proj,
  .svc-grid .svc {
    flex: 0 0 84%;
    scroll-snap-align: center;
  }
  .proj .shot {
    min-height: 132px;
  }
  /* Les fiches d'un rail ont toutes la même hauteur : sinon l'aimantation
     fait sauter le bas de la page à chaque glissement. */
  .svc-grid .svc {
    min-height: 236px;
  }
  /* Le relief au survol n'existe pas au doigt, et le projecteur est coupé au
     mobile (voir main.js) : une fiche soulevée ne serait qu'un décalage. */
  .proj:hover,
  .proj.spot {
    transform: none;
    box-shadow: none;
  }

  .rail-pts {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    margin-top: 2px;
  }
  .rail-pts b {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--line);
    transition:
      width 0.3s,
      background 0.3s;
  }
  .rail-pts b.on {
    width: 20px;
    border-radius: 3px;
    background: var(--mark);
  }
  .rail-compte {
    display: block;
    text-align: center;
    font-size: 0.75rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--ink-soft);
    margin-top: 10px;
  }

  /* --- Services : accordéon -------------------------------------------- */
  /* Le repli ne s'applique qu'aux fiches que le script a rendues pliables :
     sans JavaScript, tout reste ouvert et la page se lit comme avant. */
  .svc-row[data-pliable] .svc-row__body {
    display: none;
  }
  .svc-row[data-pliable].ouvert .svc-row__body {
    display: block;
  }
  .svc-row[data-pliable] .svc-row__head {
    position: relative;
    /* 38 px : le chevron occupe les 17 derniers, et « Développement Web »
       — le titre le plus long en français — venait finir pile sur la limite.
       L'allemand compose des mots plus longs encore. */
    padding-right: 38px;
    cursor: pointer;
    min-height: 44px;
  }
  /* Repliée, la fiche ne garde que l'icône et le titre : la phrase de
     description faisait passer chaque ligne fermée de 72 à 153 px, soit huit
     domaines en 1 225 px — l'accordéon ne servait plus à rien. Elle réapparaît
     à l'ouverture, juste au-dessus de ce qu'elle annonce. */
  .svc-row[data-pliable]:not(.ouvert) .svc-row__head p {
    display: none;
  }
  .svc-row[data-pliable] .svc-row__head::after {
    content: "";
    position: absolute;
    right: 7px;
    top: 20px;
    width: 10px;
    height: 10px;
    border-right: 2px solid var(--mark);
    border-bottom: 2px solid var(--mark);
    transform: rotate(45deg);
    transition: transform 0.3s;
  }
  .svc-row[data-pliable].ouvert .svc-row__head::after {
    transform: rotate(-135deg);
  }
  .svc-row[data-pliable] .svc-row__head:focus-visible {
    outline: 2px solid var(--mark);
    outline-offset: 5px;
  }
}

/* --- Pied de page au téléphone -------------------------------------------
   Cinq blocs empilés faisaient 988 px, soit 1,2 écran de pied — plus haut
   que n'importe quelle section de la page. La marque et le contact gardent
   toute la largeur (ils portent une phrase et une adresse) ; les trois
   colonnes de liens, elles, se rangent deux par deux. On ne retire aucun
   lien : ils se serrent. --- */
@media (max-width: 700px) {
  .foot-grid {
    grid-template-columns: 1fr 1fr;
    gap: 18px 22px;
    margin-bottom: 12px;
  }
  /* Seule la marque tient la largeur entière : elle porte une phrase et les
     icônes sociales. Les trois colonnes de liens et le bloc contact se
     rangent deux par deux — laisser le contact seul sur sa ligne gaspillait
     160 px pour une demi-largeur vide. */
  .foot-grid > .foot-brand {
    grid-column: 1 / -1;
  }
  .foot-reach .foot-address,
  .foot-reach .foot-line {
    font-size: 0.78rem;
  }
  .foot-brand p {
    max-width: 44ch;
    margin-top: 6px;
  }
  .socials {
    margin-top: 10px;
  }
  .foot-grid .ft-title {
    margin-bottom: 7px;
  }
  .foot-grid a {
    font-size: 0.8rem;
    margin-bottom: 4px;
  }
  footer {
    padding-top: 20px;
  }
}

/* --- Coutures de la page Réalisations -------------------------------------
   La galerie est passée sur la bande sombre, ce qui crée deux changements de
   bande que la feuille ne connaissait pas : l'entrée (crème → noir), qui
   n'avait aucun filet parce qu'elle suit un `.page-head` et non une section,
   et la sortie (noir → blanc), qui héritait du rouge profond.

   Couleur choisie contre les DEUX voisins, comme partout ailleurs. Luminances
   relatives : crème 0,896 · blanc 1,000 · encre 0,011 · `--mark` 0,112 ·
   `--mark-lite` 0,239.

     --mark      contre l'encre : 2,7:1  → sous la barre, il s'éteint
     --mark-lite contre l'encre : 4,8:1
     --mark-lite contre la crème : 3,3:1
     --mark-lite contre le blanc : 3,6:1

   Le rouge clair est donc la seule teinte qui tienne des deux côtés des deux
   coutures — le même raisonnement qui avait imposé le blanc entre les bandes
   04 et 05 de l'accueil. --- */
.page-head + .section {
  position: relative;
}
.page-head + .section::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  z-index: 4;
  pointer-events: none;
  /* Rouge PROFOND, et non plus clair. Le clair avait été choisi quand la seule
     page concernée était Réalisations, dont la première section est sombre —
     il y tient 4,8:1. Mais sur les cinq autres pages, cette couture sépare la
     crème du bandeau de titre et le blanc de la section : le rouge clair n'y
     vaut que 3,3:1 et 3,6:1, et se lit comme un rose délavé, c'est-à-dire
     comme rien. Le rouge profond tient 5,8:1 et 6,5:1 sur ces deux fonds.
     La bande sombre garde le clair, plus bas. */
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--mark) 16%,
    var(--mark) 84%,
    transparent 100%
  );
}
/* Réalisations : la première section est la bande sombre, où le rouge profond
   s'éteint (2,4:1). Elle seule garde le rouge clair. */
.page-head + .section.band-dark::before {
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--mark-lite) 16%,
    var(--mark-lite) 84%,
    transparent 100%
  );
}
.band-dark + .section::before {
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--mark-lite) 16%,
    var(--mark-lite) 84%,
    transparent 100%
  );
}

/* --- Hero au téléphone : l'étiquette et le nom ----------------------------
   Deux défauts mesurés à 390 px :

   • L'étiquette tenait 393 px pour 353 px utiles. Le navigateur coupait donc
     où il pouvait, et laissait « BROYE » tout seul sur la seconde ligne. Elle
     se plie maintenant entre le métier et le lieu, et le tiret disparaît —
     il n'a de sens qu'en bout de ligne.

   • Le nom faisait 41,6 px pour 244 px de large, soit 69 % de la largeur
     disponible. C'est LE titre de la page : il doit occuper sa mesure comme
     sur portable. La règle `max-height: 860px`, écrite pour les portables
     courts, attrapait aussi les téléphones (844 px de haut) et le rabotait —
     elle est désormais réservée aux écrans larges. --- */
@media (max-width: 900px) {
  .brand-hero .bh-kick {
    flex-wrap: wrap;
    justify-content: center;
    row-gap: 3px;
  }
  .brand-hero .bh-sep {
    display: none;
  }
  /* Le lieu prend toute la ligne : sans cela il remonterait se coller au
     métier dès qu'il resterait deux centimètres au bout. */
  .brand-hero .bh-lieu {
    flex: 0 0 100%;
    text-align: center;
  }
  /* 13vw plafonné : à 430 px le nom atteint 328 px sur 393 utiles (83 %),
     à 390 px il fait 297 px sur 353 (84 %). La marge reste franche. */
  .brand-hero .bh-name {
    /* Le plancher est à 2,7rem et non 3 : sur un iPhone SE (320 px) le nom
       venait finir à 2 px du bord, soit 99 % de la mesure. */
    font-size: clamp(2.7rem, 13vw, 4.8rem);
  }
}

/* ==========================================================================
   23. HERO — l'image respire
   Recette relevée sur mesarbarbershop.ch, la référence demandée :

     .hero__img { animation: hero-intro 2.2s both,
                             hero-drift 9s ease-in-out 2.2s infinite alternate }
     @keyframes hero-intro { 0% { scale 1.32 } 100% { scale 1 } }
     @keyframes hero-drift { 0% { scale 1 } 100% { scale 1.2 translate(-2.2%,-1.5%) } }

   Deux temps, donc : une poussée à l'arrivée — l'image se pose en reculant —
   puis une dérive lente qui ne s'arrête jamais et repart en sens inverse.

   Ici les deux vivent sur `transform`, tandis que le zoom au défilement garde
   la propriété `scale` que lui écrit main.js. `transform` et `scale` sont deux
   propriétés distinctes que le navigateur COMPOSE : elles se multiplient au
   lieu de s'écraser. Le geste du visiteur s'ajoute donc à la respiration de
   l'image, sans que l'un annule l'autre.
   ========================================================================== */
@keyframes hero-entree {
  from {
    transform: scale(1.26);
  }
  to {
    transform: scale(1);
  }
}
@keyframes hero-derive {
  from {
    transform: scale(1) translate(0, 0);
  }
  to {
    transform: scale(1.26) translate(-2.6%, -1.8%);
  }
}
/* 3,4 s et une courbe douce, là où Mesar tient en 2,2 s : ce site-ci passe son
   premier instant sous un voile de chargement. Avec la courbe expo de Mesar,
   la poussée était retombée à 1,011 dès 400 ms — donc entièrement jouée
   derrière le voile, et invisible. Étalée, elle est encore à 1,09 quand le
   voile se lève, et le mouvement se voit. */
.hero-bg .img {
  animation:
    hero-entree 3.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.15s both,
    hero-derive 9s ease-in-out 3.55s infinite alternate;
}

/* MOUVEMENT RÉDUIT — sixième exception nommée de la feuille, et la seule
   demandée explicitement par le responsable du site.

   Ce réglage coupait la dérive. Or il est activé sur le Mac du studio et pas
   sur le téléphone : le hero respirait donc sur l'iPhone et restait figé sur
   le portable, ce qui se lisait comme un bug dépendant du navigateur. C'en
   était bien un, au sens où le résultat n'était pas le même partout.

   La consigne est que l'effet se voie sur tous les appareils et tous les
   navigateurs. La dérive reste donc, mais d'un tiers plus courte : elle garde
   sa présence sans devenir une agitation pour qui a demandé du calme.
   L'entrée, elle, ne se joue qu'une fois — elle n'a jamais posé de problème. */
@keyframes hero-derive-douce {
  from {
    transform: scale(1) translate(0, 0);
  }
  to {
    transform: scale(1.14) translate(-1.4%, -1%);
  }
}
@media (prefers-reduced-motion: reduce) {
  /* `!important` obligatoire : la règle générale de ce mode éteint toute
     animation sur `*`, `*::before` et `*::after` avec la même arme. Sans lui,
     la déclaration ci-dessous est écrasée quelle que soit sa place dans le
     fichier — c'est ce qui laissait le hero figé sur le portable du studio.
     Le keyframes est sorti de la media query : une règle @keyframes n'y était
     pas garantie, et le navigateur retombait sur une animation sans étapes. */
  .hero-bg .img {
    animation:
      hero-entree 3.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.15s both,
      hero-derive-douce 11s ease-in-out 3.55s infinite alternate !important;
    /* Le fondu entre les trois photographies doit rester : la règle générale
       de ce mode éteint aussi toute transition sur `*`, et sans cette ligne
       le passage d'une image à l'autre devenait une coupe sèche — précisément
       sur le poste du studio, où ce réglage est actif. Un fondu d'opacité sur
       place ne provoque pas de malaise vestibulaire : même raisonnement que
       pour la révélation au défilement, quatrième exception. */
    transition:
      opacity 1.8s ease,
      scale 0.1s linear !important;
  }
}

/* --- Hero : cadrage au téléphone, et le nom qui laisse passer la photo ----
   Le fond est en `cover` : au téléphone il est mis à l'échelle par la hauteur
   et l'on n'en voit qu'une bande verticale, prise au centre. Sur cette photo,
   le centre est un coin de bureau vide — le poste de travail, lui, est à
   droite. Quatre cadrages essayés à 390 px : à 76 % on voit l'écran, le
   clavier et le portable, sans que la lueur de l'écran monte derrière le
   titre (ce que faisait 84 %). --- */
@media (max-width: 900px) {
  /* UNE AUTRE IMAGE, et non un autre cadrage.
     Déplacer `background-position` ne suffisait pas : en `cover`, la part de
     la photo visible dépend de la forme de l'écran — 32 % de la largeur sur
     un iPhone 15, 39 % sur un SE — donc le même pourcentage ne montre pas la
     même chose d'un téléphone à l'autre. Le poste de travail se retrouvait
     coupé selon l'appareil.
     Le téléphone reçoit donc son propre fichier : le même cliché, recadré en
     9/16 autour du poste — écran, portable, clavier, souris. Comme il est
     déjà vertical, il ne reste presque rien à couper, et le cadrage est le
     même partout. 168 Ko, et il remplace l'autre au lieu de s'y ajouter :
     la précaharge est séparée par `media` dans l'en-tête du document. */
  .hero-bg .img--1 img {
    object-position: center 46%;
  }
  /* Les deux mises en situation n'ont pas de recadrage 9/16 dédié : en
     `cover`, le téléphone n'en montre qu'une bande verticale, que l'on pose
     sur le portable — à gauche du centre sur les deux clichés. */
  .hero-bg .img--2 img {
    object-position: 37% center;
  }
  .hero-bg .img--3 img {
    object-position: 42% center;
  }
  /* La dalle allumée passe maintenant derrière le titre : le voile central se
     resserre pour que le blanc du nom garde sa marge. */
  .hero-bg .veil {
    background: radial-gradient(
        ellipse 78% 42% at 50% 40%,
        rgba(8, 9, 11, 0.74) 0%,
        rgba(8, 9, 11, 0.46) 58%,
        transparent 82%
      ),
      linear-gradient(
        180deg,
        rgba(8, 9, 11, 0.62) 0%,
        rgba(8, 9, 11, 0.34) 42%,
        rgba(8, 9, 11, 0.44) 72%,
        rgba(8, 9, 11, 0.74) 100%
      );
  }
}

/* Le nom laisse transparaître la photographie, comme le « MESAR » de la
   référence. 0,82 d'opacité et non 0,6 : en dessous, le trait fin des empattements
   de la Playfair se met à disparaître avant le reste de la lettre et le mot
   paraît sale plutôt que translucide. L'ombre portée est renforcée pour que
   le contour tienne quel que soit ce qui passe dessous. */
.brand-hero .bh-name {
  /* 0,62 et non 0,82 : au-dessus, la transparence ne se voyait pas — le nom
     restait au même niveau que la phrase qui le suit, qui vaut 0,82. À 0,62 la
     photographie passe franchement à travers les lettres, et le mot mesure
     115 px : le seuil de contraste des grands textes (3:1) est tenu avec de
     la marge, la mesure donne 4,7:1 sur le fond assombri. */
  color: rgba(255, 255, 255, 0.62);
  text-shadow:
    0 2px 34px rgba(0, 0, 0, 0.6),
    0 0 100px rgba(0, 0, 0, 0.4);
}

/* --- Champ de noms de la bande de contact (accueil, section 05) -----------
   Un aplat noir n'accroche rien, et c'est pourtant la section qui demande
   d'écrire. Le nom du studio s'y répète sur quatre lignes de tailles, de
   pâleurs et de vitesses différentes, deux vers la gauche et deux vers la
   droite. C'est le décalage entre les vitesses qui fait la profondeur : l'œil
   lit les grandes lettres comme un fond lointain et les petites comme un
   premier plan, alors que tout est dans le même plan.

   Chaque piste porte assez d'exemplaires pour couvrir deux fois la largeur, et
   se décale de la moitié de sa longueur : au bout du cycle elle est exactement
   dans sa position de départ, donc la boucle est invisible.

   Tout reste très pâle — entre 4 et 8 % de blanc. Le texte de la section passe
   par-dessus (le `.wrap` est en z-index 2) et ne perd pas un point de
   contraste. --- */
.band-mark {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  user-select: none;
  /* Les bords s'effacent, sinon les lettres sont tranchées net par la bande. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
}
.band-mark__ligne {
  position: absolute;
  left: 0;
  right: 0;
  display: flex;
  white-space: nowrap;
}
.band-mark__track {
  display: flex;
  white-space: nowrap;
  will-change: transform;
}
.band-mark span {
  font-family: var(--font-head);
  font-weight: var(--head-weight);
  line-height: 0.86;
  letter-spacing: 0.012em;
  padding-right: 0.3em;
}
/* Le mot est porté par l'attribut, pas par un nœud texte : il est décoratif,
   déjà masqué aux lecteurs d'écran, et un texte réel à 1,2:1 de contraste
   compte comme illisible pour les outils d'audit — à raison, s'il s'agissait
   d'un texte à lire. */
.band-mark span::before {
  content: attr(data-text);
}

/* Neuf lignes qui couvrent toute la hauteur de la bande, des plus grandes et
   lentes aux plus petites et vives, alternées de sens. C'est le désordre des
   tailles ET l'écart des vitesses qui font la profondeur : l'œil lit les
   grandes lettres comme un fond lointain et les petites comme un premier
   plan, alors que tout est dans le même plan. Les plus visibles montent à
   12 % de blanc — la lisibilité du texte, qui passe au-dessus, reste
   intacte. */
.band-mark__ligne--a {
  top: -7%;
}
.band-mark__ligne--a span {
  font-size: clamp(4.2rem, 11vw, 11rem);
  color: rgba(255, 255, 255, 0.05);
}
.band-mark__ligne--a .band-mark__track {
  animation: band-mark-gauche 78s linear infinite;
}
.band-mark__ligne--b {
  top: 4%;
}
.band-mark__ligne--b span {
  font-size: clamp(1.5rem, 3.4vw, 3.4rem);
  color: rgba(255, 255, 255, 0.095);
}
.band-mark__ligne--b .band-mark__track {
  animation: band-mark-droite 27s linear infinite;
}
.band-mark__ligne--c {
  top: 12%;
}
.band-mark__ligne--c span {
  font-size: clamp(2.6rem, 6.4vw, 6.4rem);
  color: rgba(255, 255, 255, 0.07);
}
.band-mark__ligne--c .band-mark__track {
  animation: band-mark-gauche 46s linear infinite;
}
.band-mark__ligne--d {
  top: 24%;
}
.band-mark__ligne--d span {
  font-size: clamp(1.2rem, 2.6vw, 2.6rem);
  color: rgba(255, 255, 255, 0.11);
}
.band-mark__ligne--d .band-mark__track {
  animation: band-mark-droite 21s linear infinite;
}
.band-mark__ligne--e {
  top: 32%;
}
.band-mark__ligne--e span {
  font-size: clamp(3.4rem, 8.4vw, 8.4rem);
  color: rgba(255, 255, 255, 0.055);
}
.band-mark__ligne--e .band-mark__track {
  animation: band-mark-gauche 62s linear infinite;
}
.band-mark__ligne--f {
  top: 48%;
}
.band-mark__ligne--f span {
  font-size: clamp(1.1rem, 2.2vw, 2.2rem);
  color: rgba(255, 255, 255, 0.115);
}
.band-mark__ligne--f .band-mark__track {
  animation: band-mark-droite 18s linear infinite;
}
.band-mark__ligne--g {
  top: 56%;
}
.band-mark__ligne--g span {
  font-size: clamp(4.6rem, 12vw, 12rem);
  color: rgba(255, 255, 255, 0.045);
}
.band-mark__ligne--g .band-mark__track {
  animation: band-mark-gauche 88s linear infinite;
}
.band-mark__ligne--h {
  top: 76%;
}
.band-mark__ligne--h span {
  font-size: clamp(2rem, 4.6vw, 4.6rem);
  color: rgba(255, 255, 255, 0.085);
}
.band-mark__ligne--h .band-mark__track {
  animation: band-mark-droite 34s linear infinite;
}
.band-mark__ligne--i {
  top: 88%;
}
.band-mark__ligne--i span {
  font-size: clamp(1rem, 1.9vw, 1.9rem);
  color: rgba(255, 255, 255, 0.12);
}
.band-mark__ligne--i .band-mark__track {
  animation: band-mark-gauche 24s linear infinite;
}
@keyframes band-mark-gauche {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}
@keyframes band-mark-droite {
  from {
    transform: translateX(-50%);
  }
  to {
    transform: translateX(0);
  }
}
/* Septième exception nommée, même raison que la dérive du hero : le
   responsable a demandé que les effets se voient sur tous les appareils.
   Le champ garde son glissement, deux fois plus lent. */
@media (prefers-reduced-motion: reduce) {
  .band-mark__ligne--a .band-mark__track {
    animation: band-mark-gauche 156s linear infinite !important;
  }
  .band-mark__ligne--b .band-mark__track {
    animation: band-mark-droite 54s linear infinite !important;
  }
  .band-mark__ligne--c .band-mark__track {
    animation: band-mark-gauche 92s linear infinite !important;
  }
  .band-mark__ligne--d .band-mark__track {
    animation: band-mark-droite 42s linear infinite !important;
  }
  .band-mark__ligne--e .band-mark__track {
    animation: band-mark-gauche 124s linear infinite !important;
  }
  .band-mark__ligne--f .band-mark__track {
    animation: band-mark-droite 36s linear infinite !important;
  }
  .band-mark__ligne--g .band-mark__track {
    animation: band-mark-gauche 176s linear infinite !important;
  }
  .band-mark__ligne--h .band-mark__track {
    animation: band-mark-droite 68s linear infinite !important;
  }
  .band-mark__ligne--i .band-mark__track {
    animation: band-mark-gauche 48s linear infinite !important;
  }
}

/* --- Vignettes du portfolio : la capture du vrai site ---------------------
   Chaque fiche montrait un navigateur dessiné en CSS, avec des barres grises
   à la place d'une page. Elle montre maintenant la capture du site en
   production, prise par `npm run build:captures`. Le cadre de navigateur
   reste — pastilles et adresse — parce qu'il donne l'échelle et qu'une URL
   affichée sous une capture vaut preuve.

   La capture est cadrée sur le premier écran et rognée en 16:10 : c'est la
   proportion d'un écran de portable, donc celle qu'on reconnaît. --- */
.browser .canvas--shot {
  padding: 0;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--bg-2);
}
.browser .canvas--shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 0;
  display: block;
  /* Les fiches vivent sur la bande sombre : une capture en pleine lumière y
     ferait un trou. Un rien de retenue, que le survol lève. */
  filter: saturate(0.92) brightness(0.97);
  transition: filter 0.5s ease;
}
.proj:hover .canvas--shot img {
  filter: none;
}
/* Le cadre prend un peu plus de place : c'est lui qu'on regarde désormais. */
.proj .shot:has(.canvas--shot) .browser {
  width: 92%;
}

/* --- Les deux boutons du hero, à l'identique ------------------------------
   `.btn-gold` porte ailleurs un aplat ; dans le hero il prend le même contour
   que `.btn-ghost`, qui est à sa droite. --- */
.hero .btn-gold {
  border: 1px solid var(--hero-line);
}
.hero .btn-gold:hover {
  border-color: var(--hero-ink);
  background: transparent;
}

/* --- Section 03 sans ses images -------------------------------------------
   Les deux photographies latérales sont retirées. Le panneau rouge prend
   toute la mesure : la section devient une bande, et non plus une mosaïque à
   trois cases dont deux seraient vides. --- */
/* Sans ses images, le panneau rouge posé sur le blanc devenait un rectangle
   flottant — et surtout un SECOND rouge, collé juste au-dessus de la bande
   rouge de la section 04. Deux aplats de la même couleur à la suite, séparés
   par une bande de blanc : ça se lit comme une erreur de montage.

   La section devient donc ce qu'elle doit être à cet endroit du parcours :
   le silence avant la bande rouge. Une question, en grand, à l'encre sur le
   blanc, avec le rouge réduit à ce qu'il désigne — un filet et une flèche.
   Le rythme de la page y gagne : blanc, crème, blanc, ROUGE, noir. */
.mosaic--seule {
  grid-template-columns: 1fr;
  /* La rangée prend la hauteur de son contenu : avec la hauteur fixe de la
     mosaïque, la question flottait au milieu d'un écran entier de blanc. */
  grid-auto-rows: auto;
}
.mosaic--seule .mo-cta {
  background: transparent;
  color: var(--ink);
  align-items: center;
  text-align: center;
  padding: clamp(12px, 1.6vw, 26px) 0 clamp(6px, 1vw, 14px);
  position: relative;
}
/* Le filet rouge qui ouvre la question : c'est la seule couleur du bloc. */
.mosaic--seule .mo-cta::before {
  content: "";
  width: 54px;
  height: 2px;
  background: var(--mark);
  margin-bottom: clamp(18px, 2vw, 28px);
}
.mosaic--seule .mo-cta .eyebrow {
  color: var(--ink-soft);
}
.mosaic--seule .mo-cta .mo-title {
  color: var(--ink);
  font-size: clamp(1.9rem, 3.6vw, 3.8rem);
  line-height: 1.12;
  max-width: 20ch;
  margin: clamp(14px, 1.4vw, 22px) auto clamp(24px, 2.6vw, 38px);
}
.mosaic--seule .mo-cta .more {
  color: var(--ink);
}
/* `stroke` et non `color` : la flèche est un tracé SVG. La règle du panneau
   rouge la dessinait en rouge sur blanc ; ici le disque est rouge, il lui faut
   donc un tracé blanc — sans quoi la flèche disparaît dans son propre fond. */
.mosaic--seule .mo-cta .more svg {
  background: var(--mark);
  stroke: #ffffff;
}
.mosaic--seule .mo-cta .more:hover svg {
  background: var(--ink);
  stroke: #ffffff;
}

/* --- Couvertures des projets (accueil, section 02) ------------------------
   La section montrait trois fiches complètes sur neuf projets : on y voyait
   trois chips et trois maquettes, mais pas le travail. Elle montre maintenant
   la couverture de chacun — la capture du site en production, en petit. Huit
   images à la place de trois fiches, et la section est plus courte qu'avant.

   Aventic n'y figure pas : son site ne répond plus, il n'y a pas de capture à
   montrer. La fiche complète, elle, reste sur la page Réalisations. --- */
/* Trois vues au choix, comme dans une boutique en ligne : on décide combien
   de projets tiennent à l'écran. Les classes sont génériques — `est-rail`,
   `est-2`, `est-4` — parce que la même barre pilote deux grilles : les
   couvertures de l'accueil et les fiches de la page Réalisations.

   Ce bloc est en fin de feuille à dessein : il doit l'emporter sur la mise en
   forme mobile de la section 22, qui impose un rail au-dessous de 900 px.
   Quand le visiteur choisit une vue, c'est son choix qui gagne. */
.couvertures,
.proj-grid {
  gap: clamp(10px, 1vw, 18px);
}
.couvertures.est-2,
.proj-grid.est-2 {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  overflow: visible;
  scroll-snap-type: none;
}
/* Trois par ligne au téléphone, quatre dès qu'il y a la place.
   Ce bouton donnait deux colonnes sous 620 px — exactement comme le bouton
   « deux » : les deux vues étaient identiques sur un téléphone, et le
   troisième bouton ne servait à rien. */
.couvertures.est-4,
.proj-grid.est-4 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  overflow: visible;
  scroll-snap-type: none;
}
/* --- Sans fiche orpheline ------------------------------------------------
   Onze couvertures sur l'accueil, douze fiches sur Réalisations : aucun des
   deux ne remplit une grille de trois ou de quatre tout seul, et la dernière
   ligne restait avec une ou deux fiches isolées. Chaque densité a donc sa
   recette, refaite le 03.09.2026 quand ROJ et Le Marquis sont entrés :

   · Réalisations, quatre colonnes : les quatre références les plus fortes —
     Mesar, BCR, ROJ, Le Forgeron — en double largeur (2 + 2, puis 4 + 4) ;
   · Réalisations, trois colonnes et moins : 4 × 3, sans mise en avant ;
     douze se divise par trois, il n'y a rien à rattraper ;
   · Réalisations, deux colonnes : 6 × 2, rien à faire ;
   · accueil, bouton « trois » : les deux ancres prennent leur ligne, puis
     3 × 3 (2 + 9 = 11) ;
   · accueil, « deux » : Mesar seul prend la ligne, puis 5 × 2 (1 + 10 = 11). */
@media (min-width: 1000px) {
  .proj-grid.est-4 {
    grid-template-columns: repeat(4, 1fr);
  }
  .proj-grid.est-4 .proj:nth-child(-n + 4) {
    grid-column: span 2;
  }
}
/* L'accueil, lui, garde ses trois colonnes à toute largeur : ce n'est pas le
   catalogue, c'est un aperçu. Les deux ancres ouvrent la grille. */
.couvertures.est-4 .couv:nth-child(-n + 2) {
  grid-column: 1 / -1;
}
@media (min-width: 620px) {
  .couvertures.est-4 .couv:nth-child(-n + 2) {
    aspect-ratio: 21 / 9;
  }
}
.couvertures.est-2 .couv:first-child {
  grid-column: 1 / -1;
  aspect-ratio: 21 / 9;
}
.couvertures.est-2 .couv,
.couvertures.est-4 .couv,
.proj-grid.est-2 .proj,
.proj-grid.est-4 .proj {
  flex: initial;
}
/* Le rail : les fiches s'alignent et se font glisser au doigt, avec
   aimantation. La suivante dépasse assez pour qu'on sache qu'il y en a. */
.couvertures.est-rail,
.proj-grid.est-rail {
  display: flex;
  grid-template-columns: none;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 12px;
  scrollbar-width: none;
}
.couvertures.est-rail::-webkit-scrollbar,
.proj-grid.est-rail::-webkit-scrollbar {
  display: none;
}
.couvertures.est-rail .couv,
.proj-grid.est-rail .proj {
  /* 84 % comme la forme mobile de la section 22 : la fiche suivante dépasse
     assez pour qu'on comprenne qu'on peut glisser. À 76 % elle ne se devinait
     plus. */
  flex: 0 0 84%;
  scroll-snap-align: center;
}
@media (min-width: 620px) {
  .couvertures.est-rail .couv,
  .proj-grid.est-rail .proj {
    flex: 0 0 42%;
  }
}
@media (min-width: 1000px) {
  .couvertures.est-rail .couv,
  .proj-grid.est-rail .proj {
    flex: 0 0 28%;
  }
}

/* --- La barre de choix ---------------------------------------------------
   Écrite dans la page pour exister sans JavaScript, mais inutile tant que le
   script ne l'a pas branchée : `main.js` pose `data-prete` et c'est cet
   attribut qui la rend visible. Un bouton mort vaut moins qu'un bouton
   absent. --- */
.densite {
  display: none;
  gap: 4px;
  justify-content: flex-end;
  margin-bottom: clamp(12px, 1.2vw, 18px);
}
.densite[data-prete] {
  display: flex;
}
.densite__b {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--line-soft);
  color: var(--ink-soft);
  cursor: pointer;
  transition:
    border-color 0.25s,
    color 0.25s,
    background 0.25s;
}
.densite__b svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
}
.densite__b:hover {
  border-color: var(--ink);
  color: var(--ink);
}
.densite__b:focus-visible {
  outline: 2px solid var(--mark);
  outline-offset: 3px;
}
.densite__b.is-on {
  border-color: var(--mark);
  color: var(--mark);
}
.couv {
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: 16 / 10;
  border: 1px solid var(--line-soft);
  background: var(--bg-2);
  text-decoration: none;
  transition:
    border-color 0.35s,
    transform 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.45s ease;
}
.couv img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 0;
  display: block;
  filter: saturate(0.9) brightness(0.94);
  transition:
    filter 0.45s ease,
    transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
/* Le nom se lit sur un dégradé, pas sur l'image : sur une capture claire, du
   texte blanc posé nu devient illisible. */
.couv__nom {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 26px 10px 8px;
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  color: #ffffff;
  background: linear-gradient(180deg, transparent, rgba(8, 9, 11, 0.86) 62%);
  pointer-events: none;
}
.couv:hover,
.couv:focus-visible {
  border-color: var(--mark);
  transform: translateY(-4px);
  box-shadow: 0 26px 50px -30px rgba(0, 0, 0, 0.45);
}
.couv:hover img,
.couv:focus-visible img {
  filter: none;
  transform: scale(1.06);
}
/* Le filet rouge de la marque en tête, comme sur les fiches du portfolio. */
.couv::after {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: var(--mark);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
  z-index: 2;
}
.couv:hover::after,
.couv:focus-visible::after {
  transform: scaleX(1);
}
/* `couv--muette` servait à Mesar tant que son domaine n'était pas enregistré.
   Il l'est depuis le 13.08.2026 : les huit couvertures mènent au site. La
   règle reste, sans usage actuel, pour le jour où un projet sera livré avant
   sa mise en ligne. */
.couv--muette {
  cursor: default;
}

/* ==========================================================================
   24. FORMULAIRE DE CONTACT — il faut voir où l'on écrit
   Les champs étaient transparents, cernés d'un filet à 10 % d'encre, sur un
   fond blanc : rien ne disait où cliquer, et la page se lisait comme une
   feuille vide. C'est le seul endroit du site qui demande une action longue —
   il doit être le plus lisible, pas le plus discret.

   Trois changements, et rien de décoratif :
     • les champs prennent un fond crème et un filet deux fois plus dense,
       donc ils existent avant même qu'on les touche ;
     • le focus est franc — filet rouge et halo — parce qu'au clavier on doit
       savoir où l'on est sans chercher ;
     • les étiquettes passent de `--muted` à l'encre : elles se lisaient à
       peine, alors qu'elles disent ce qu'on attend.
   ========================================================================== */
.form-wrap {
  background: var(--bg);
  border: 1px solid var(--line-soft);
  padding: clamp(22px, 3vw, 44px);
}
form label {
  color: var(--ink);
  font-weight: 400;
}
form input,
form select,
form textarea {
  background: var(--bg-2);
  border: 1px solid var(--line);
  padding: 14px 16px;
  transition:
    border-color 0.25s,
    box-shadow 0.25s,
    background 0.25s;
}
form input::placeholder,
form textarea::placeholder {
  color: var(--muted);
}
form input:hover,
form select:hover,
form textarea:hover {
  border-color: var(--ink-soft);
}
form input:focus,
form select:focus,
form textarea:focus {
  outline: none;
  background: var(--bg);
  border-color: var(--mark);
  box-shadow: 0 0 0 3px rgba(var(--mark-rgb), 0.14);
}
/* La flèche du menu déroulant est dessinée : celle du système change d'un
   navigateur à l'autre et jurait avec le reste. */
form select {
  background: var(--bg-2)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%234f545a' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E")
    no-repeat right 14px center / 16px 16px;
  -webkit-appearance: none;
  appearance: none;
  padding-right: 42px;
}
form select:focus {
  background-color: var(--bg);
}
form textarea {
  min-height: 150px;
}
/* Le champ obligatoire se signale : une étoile rouge vaut mieux qu'un message
   d'erreur après coup. */
form .field--requis > label::after {
  content: " *";
  color: var(--mark);
}

/* --- Cascade des couvertures ---------------------------------------------
   Les huit vignettes de l'accueil montent l'une après l'autre plutôt qu'en
   bloc. C'est ce décalage qui fait l'effet — le fondu seul, sur un bloc de
   500 px de haut, se joue tout entier avant qu'on ait eu le temps de le
   regarder. --- */
.reveal.in .couv:nth-child(2) {
  animation-delay: 0.07s;
}
.reveal.in .couv:nth-child(3) {
  animation-delay: 0.14s;
}
.reveal.in .couv:nth-child(4) {
  animation-delay: 0.21s;
}
.reveal.in .couv:nth-child(5) {
  animation-delay: 0.28s;
}
.reveal.in .couv:nth-child(6) {
  animation-delay: 0.35s;
}
.reveal.in .couv:nth-child(7) {
  animation-delay: 0.42s;
}
.reveal.in .couv:nth-child(8) {
  animation-delay: 0.49s;
}

/* La barre sur la bande sombre de Réalisations. */
.band-dark .densite__b {
  border-color: rgba(255, 255, 255, 0.24);
  color: rgba(255, 255, 255, 0.72);
}
.band-dark .densite__b:hover {
  border-color: #ffffff;
  color: #ffffff;
}
.band-dark .densite__b.is-on {
  border-color: var(--mark-lite);
  color: var(--mark-lite);
}

/* --- Bande d'onde rouge (Réalisations, section 02) ------------------------
   La section de sortie fermait la page sur du blanc, juste après la galerie
   posée sur l'encre : deux clairs séparés par du sombre, et la page finissait
   sans souffle. Elle prend le fond demandé — une onde de particules rouges
   sur noir, tirée de la direction B de l'exploration du hero.

   L'image est native en 850 px et servie en 1920 : c'est un champ de points
   sans arête franche, qui supporte l'agrandissement, et elle passe sous un
   voile. Elle ne serait pas utilisable telle quelle en plein cadre. --- */
.band-onde {
  position: relative;
  overflow: hidden;
  background: #07080a;
  color: #ffffff;
}
.onde-bg {
  position: absolute;
  inset: 0;
  background: url("../img/onde-rouge.jpg") center / cover no-repeat;
  background-image: image-set(
    url("../img/onde-rouge.avif") type("image/avif"),
    url("../img/onde-rouge.webp") type("image/webp"),
    url("../img/onde-rouge.jpg") type("image/jpeg")
  );
  /* L'onde respire, comme le hero : même famille de mouvement, deux fois plus
     lente parce qu'elle n'est qu'un fond de section. */
  animation: onde-derive 26s ease-in-out infinite alternate;
}
@keyframes onde-derive {
  from {
    transform: scale(1.04) translate(0, 0);
  }
  to {
    transform: scale(1.16) translate(-2%, -1.4%);
  }
}
.onde-veil {
  position: absolute;
  inset: 0;
  background: radial-gradient(
    ellipse 70% 60% at 50% 50%,
    rgba(7, 8, 10, 0.72) 0%,
    rgba(7, 8, 10, 0.5) 55%,
    rgba(7, 8, 10, 0.78) 100%
  );
}
.band-onde > .wrap {
  position: relative;
  z-index: 2;
}
.band-onde .section-head h2 {
  color: #ffffff;
}
.band-onde .section-head p {
  color: rgba(255, 255, 255, 0.78);
}
.band-onde .eyebrow {
  color: rgba(255, 255, 255, 0.66);
}
.band-onde .eyebrow::before {
  background: var(--mark-lite);
}
.band-onde .btn-gold {
  background: var(--mark);
  color: #ffffff;
  border: 1px solid var(--mark);
}
.band-onde .btn-gold:hover {
  background: #ffffff;
  border-color: #ffffff;
  color: var(--ink);
}
.band-onde .btn-ghost {
  color: #ffffff;
  border-color: rgba(255, 255, 255, 0.3);
}
.band-onde .btn-ghost:hover {
  border-color: #ffffff;
  background: transparent;
}
/* L'ordinal en filigrane se lit en clair sur ce fond. */
.band-onde[data-idx]::after {
  color: var(--mark-lite);
  opacity: 0.26;
}
/* Huitième exception nommée : le mouvement est conservé, ralenti de moitié. */
@media (prefers-reduced-motion: reduce) {
  .onde-bg {
    animation: onde-derive 52s ease-in-out infinite alternate !important;
  }
}

/* ==========================================================================
   25. LE BLOC « APPROCHE » — il était un aplat noir
   Une dalle noire pleine largeur, six cases au filet à 22 % et des numéros
   d'ordre écrits dans une couleur qui n'existe plus depuis la v4
   (`--gold-deep`) : ils étaient invisibles. Le bloc ne se lisait pas comme un
   sommaire mais comme un trou dans la page.

   Quatre gestes, aucun décoratif :
     • le fond n'est plus plat — l'onde rouge y passe, très en retrait, et
       respire comme celle de Réalisations ;
     • les numéros reviennent, au rouge clair, et prennent la taille d'un
       repère ;
     • les cases posent un fond légèrement plus clair que la dalle, donc
       elles existent avant le survol ;
     • au survol, un filet rouge se déploie en tête de case.
   ========================================================================== */
.approach {
  position: relative;
  overflow: hidden;
  background: #0a0b0d;
  isolation: isolate;
}
.approach::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -2;
  background: url("../img/onde-rouge.jpg") center / cover no-repeat;
  background-image: image-set(
    url("../img/onde-rouge.avif") type("image/avif"),
    url("../img/onde-rouge.webp") type("image/webp"),
    url("../img/onde-rouge.jpg") type("image/jpeg")
  );
  opacity: 0.42;
  animation: onde-derive 34s ease-in-out infinite alternate;
}
.approach::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    180deg,
    rgba(10, 11, 13, 0.86) 0%,
    rgba(10, 11, 13, 0.68) 45%,
    rgba(10, 11, 13, 0.9) 100%
  );
}
.approach .key {
  position: relative;
  padding-top: clamp(18px, 1.8vw, 26px);
}
/* Un filet rouge ouvre la citation, comme partout ailleurs sur le site. */
.approach .key::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 54px;
  height: 2px;
  background: var(--mark-lite);
}
.feat-item {
  position: relative;
  background: rgba(255, 255, 255, 0.035);
  border-color: rgba(255, 255, 255, 0.14);
  overflow: hidden;
}
.feat-item:hover {
  background: rgba(255, 255, 255, 0.07);
  border-color: rgba(255, 255, 255, 0.4);
}
.feat-item::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: var(--mark-lite);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.feat-item:hover::before {
  transform: scaleX(1);
}
/* Les numéros d'ordre pointaient sur `--gold-deep`, une fiche de la palette
   « noir & or » abandonnée : ils s'écrivaient dans une couleur morte. */
.feat-item .n {
  font-family: var(--font-head);
  font-size: 1.15rem;
  letter-spacing: 0.04em;
  color: var(--mark-lite);
  opacity: 0.9;
}
@media (prefers-reduced-motion: reduce) {
  .approach::before {
    animation: onde-derive 68s ease-in-out infinite alternate !important;
  }
}

/* --- Fiches du portfolio : moins hautes ----------------------------------
   Seize chips empilaient cinq lignes et la fiche devenait une colonne sans
   fin. Les huit premières restent, le reste se replie derrière un « +N ».
   Les chips cachées sont dans la page — donc indexables et lisibles par un
   lecteur d'écran une fois ouvertes — simplement masquées ici. --- */
.chip--cache {
  display: none;
}
.proj.chips-ouvertes .chip--cache {
  display: inline-flex;
}
.chip--plus {
  cursor: pointer;
  font-family: inherit;
  border-color: var(--mark);
  color: var(--mark);
  background: transparent;
  transition:
    background 0.25s,
    color 0.25s;
}
.chip--plus:hover {
  background: var(--mark);
  color: #ffffff;
}
.chip--plus:focus-visible {
  outline: 2px solid var(--mark);
  outline-offset: 2px;
}
.proj.chips-ouvertes .chip--plus {
  background: var(--mark);
  color: #ffffff;
}
/* Les chips se serrent : c'est une liste de mots-clés, pas un menu. */
.proj .chips {
  gap: 5px;
  margin: 4px 0 12px;
}
.proj .chip {
  font-size: 0.75rem;
  padding: 3px 8px;
}

/* --- L'adresse du navigateur devient un lien ------------------------------
   Elle était un texte mort sous une capture cliquable ailleurs dans la fiche.
   C'est pourtant l'endroit où l'on cherche à cliquer. --- */
.url--lien {
  display: block;
  text-decoration: none;
  transition: color 0.25s;
}
.url--lien:hover,
.url--lien:focus-visible {
  color: var(--mark);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ==========================================================================
   26. VIDÉO AU SURVOL DES VIGNETTES
   Une capture fixe dit à quoi ressemble une page ; une vidéo dit comment elle
   se déroule. Chaque vignette du portfolio et de l'accueil porte donc une
   petite vidéo du site, jouée quand la souris entre — et seulement là.

   Le poids est le vrai sujet. Huit vidéos chargées d'office coûteraient
   plusieurs mégaoctets à chaque visite, pour quelque chose que la plupart des
   visiteurs ne regarderont jamais. D'où `preload="none"` sur la balise et un
   `src` que le script ne pose qu'au premier survol : tant qu'on n'entre pas
   dans une vignette, il ne part pas un octet.

   Au doigt, il n'y a pas de survol : la capture reste, et c'est très bien —
   c'est aussi l'écran où l'on tient le moins à dépenser des données.
   ========================================================================== */
.canvas--shot,
.couv {
  position: relative;
}
.canvas__video,
.couv__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 0;
  opacity: 0;
  transition: opacity 0.45s ease;
  pointer-events: none;
  z-index: 1;
}
/* La vidéo ne prend la place de l'image qu'une fois qu'elle joue : sans cette
   condition, un survol sur une connexion lente montrait un rectangle noir le
   temps du chargement. C'est main.js qui pose `est-en-lecture`. */
.proj:hover .canvas__video.est-en-lecture,
.proj:focus-within .canvas__video.est-en-lecture,
.couv:hover .couv__video.est-en-lecture,
.couv:focus-within .couv__video.est-en-lecture {
  opacity: 1;
}
.couv__nom {
  z-index: 2;
}
/* Repère discret : un point rouge dit qu'il y a quelque chose à voir. Il
   s'efface pendant la lecture, où la vidéo parle d'elle-même. */
.couv::before {
  content: "";
  position: absolute;
  top: 10px;
  right: 10px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--mark);
  box-shadow: 0 0 0 3px rgba(var(--mark-rgb), 0.28);
  opacity: 0.9;
  transition: opacity 0.35s ease;
  z-index: 3;
}
.couv:hover::before {
  opacity: 0;
}
/* Sans vidéo — Aventic — pas de point. */
.couv--sans-video::before {
  display: none;
}

/* ==========================================================================
   27. EXPERTISE — la page n'avait pas de colonne vertébrale
   Trois sections, aucune bande sombre : blanc, crème, blanc. La page se
   lisait comme un document, pas comme un site. Or c'est la page qui doit
   convaincre qu'il y a un ingénieur derrière — elle ne peut pas être la plus
   pâle du site.

   Deux gestes :
     • les chiffres passent sur la bande sombre, avec l'onde rouge très en
       retrait. Le rythme devient crème → blanc → crème → ENCRE, et la page
       finit sur son argument le plus fort au lieu de s'éteindre ;
     • la fiche de l'ingénieur cesse d'être un contour posé sur du blanc : un
       fond crème l'installe, et un filet rouge en tête la désigne.
   ========================================================================== */
.band-chiffres {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
/* Un seul pseudo-élément, et c'est `::before` : `::after` porte déjà
   l'ordinal en filigrane (règle générale des sections). L'avoir pris pour le
   voile effaçait le « 03 ». L'onde et son voile sont donc empilés dans la
   même déclaration de fond. */
.band-chiffres::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(
      180deg,
      rgba(10, 11, 13, 0.8) 0%,
      rgba(10, 11, 13, 0.3) 48%,
      rgba(10, 11, 13, 0.86) 100%
    ),
    url("../img/onde-rouge.jpg") center / cover no-repeat;
  background-image: linear-gradient(
      180deg,
      rgba(10, 11, 13, 0.8) 0%,
      rgba(10, 11, 13, 0.3) 48%,
      rgba(10, 11, 13, 0.86) 100%
    ),
    image-set(
      url("../img/onde-rouge.avif") type("image/avif"),
      url("../img/onde-rouge.webp") type("image/webp"),
      url("../img/onde-rouge.jpg") type("image/jpeg")
    );
  animation: onde-derive 40s ease-in-out infinite alternate;
}
/* Les chiffres et leurs filets, écrits pour le blanc, passent au clair. */
.band-chiffres .stat {
  border-top-color: rgba(255, 255, 255, 0.2);
}
.band-chiffres .stat .num {
  color: #ffffff;
}
.band-chiffres .stat .lab {
  color: rgba(255, 255, 255, 0.66);
}
.band-chiffres .why .w {
  border-color: rgba(255, 255, 255, 0.2);
  color: rgba(255, 255, 255, 0.86);
}
.band-chiffres .why .w:hover {
  border-color: #ffffff;
  color: #ffffff;
}
.band-chiffres .more {
  color: #ffffff;
}
.band-chiffres .more svg {
  background: var(--mark);
  stroke: #ffffff;
}
/* Le filet du sur-titre : il est peint pour le blanc, il disparaissait ici. */
.band-chiffres .eyebrow {
  color: rgba(255, 255, 255, 0.7);
}
.band-chiffres .eyebrow::before {
  background: var(--mark-lite);
}
.band-chiffres .section-head h2 {
  color: #ffffff;
}
.band-chiffres .section-head p {
  color: rgba(255, 255, 255, 0.78);
}
/* L'ordinal se lit en rouge clair sur ce fond, comme sur les autres bandes. */
.band-chiffres[data-idx]::after {
  color: var(--mark-lite);
  opacity: 0.24;
}

/* La fiche de l'ingénieur : blanche, comme toutes les cartes du site.
   Elle avait reçu un fond crème pour ne pas flotter sur le blanc de la
   section — mais c'était le seul encadré beige du site, et il se remarquait
   comme une erreur plutôt que comme une intention. C'est son filet rouge en
   tête et son cadre qui l'installent, pas un fond. */
.person {
  background: var(--bg);
  border-color: rgba(17, 19, 21, 0.16);
}
.person::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: var(--mark);
}

/* --- Fiches du portfolio : encore 100 px de moins -------------------------
   La vignette occupait 247 px des 565 de la fiche. En 16/9 au lieu de 16/10
   elle en rend 25, six chips au lieu de huit en rendent 28, et le corps
   resserré une vingtaine. Rien n'est perdu : les chips au-delà de six sont
   toujours dans la page, derrière leur « +N ». --- */
.browser .canvas--shot {
  aspect-ratio: 16 / 9;
}
.proj .body {
  padding: clamp(15px, 1.1vw, 22px);
}
.proj h2 {
  margin: 6px 0 8px;
}
.proj .metric {
  margin: 0 0 8px;
}
.proj details {
  padding-top: 2px;
}
.proj summary {
  padding: 11px 0 3px;
}

/* --- Page contact : écrire d'un côté, joindre de l'autre ------------------
   Le formulaire seul au milieu d'une colonne étroite laissait la page vide,
   et donnait l'impression qu'il n'existait qu'un moyen de parler à quelqu'un
   — alors que c'est la promesse du site : un interlocuteur joignable.

   À gauche les moyens directs et trois promesses courtes ; à droite le
   formulaire, avec son propre titre. Sous 900 px, tout se replie en une
   colonne, le formulaire d'abord : c'est ce qu'on est venu faire. --- */
.contact-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(22px, 2.4vw, 40px);
}
@media (min-width: 900px) {
  .contact-grid {
    grid-template-columns: 0.78fr 1.22fr;
    align-items: start;
  }
}
.contact-grid__aside {
  order: 2;
}
.contact-grid .form-wrap {
  order: 1;
  width: 100%;
}
@media (min-width: 900px) {
  .contact-grid__aside {
    order: 0;
    position: sticky;
    top: calc(var(--hdr-h) + 24px);
  }
  .contact-grid .form-wrap {
    order: 0;
  }
}
.contact-grid .contact-strip {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
  margin-top: 14px;
}
.contact-grid .cmethod {
  max-width: none;
}
/* Trois promesses courtes : ce sont elles qui font écrire, pas le formulaire. */
.promesses {
  list-style: none;
  margin: clamp(20px, 2vw, 30px) 0 0;
  padding: 0;
  display: grid;
  gap: 10px;
}
.promesses li {
  position: relative;
  padding-left: 22px;
  font-size: 0.87rem;
  color: var(--ink-soft);
  line-height: 1.5;
}
.promesses li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 9px;
  height: 2px;
  background: var(--mark);
}
/* Le formulaire porte son propre titre : un champ « Nom » posé nu ne dit pas
   ce qu'on attend. */
.form-tete {
  margin-bottom: clamp(18px, 1.8vw, 28px);
  padding-bottom: clamp(14px, 1.4vw, 20px);
  border-bottom: 1px solid var(--line-soft);
}
.form-tete h2 {
  font-family: var(--font-head);
  font-size: clamp(1.25rem, 1.8vw, 1.9rem);
  color: var(--ink);
  margin: 8px 0 0;
  line-height: 1.2;
}
/* Deux champs par ligne dès qu'il y a la place. */
@media (min-width: 620px) {
  .form-wrap form {
    grid-template-columns: 1fr 1fr;
  }
  .form-wrap form .field.half {
    grid-column: auto;
  }
}
.form-wrap .form-foot {
  align-items: center;
}
.form-wrap button[type="submit"],
.form-wrap .btn-gold {
  padding-inline: clamp(28px, 3vw, 48px);
}

/* ==========================================================================
   28. LE RIDEAU DE TRANSITION — noir, et lettre à lettre
   Il était blanc, tenait 420 ms et partait au milieu de son animation : le
   passage d'une page à l'autre se lisait comme une coupure, pas comme une
   transition. Trois changements :

     • le fond redevient NOIR. C'est la couleur de l'en-tête et des bandes du
       site ; un rideau blanc sur un site blanc ne se voit pas partir, il
       clignote ;
     • il tient ~900 ms sur la première page de la session (le temps des onze
       lettres) et ~350 ms ensuite, sans jamais attendre le chargement complet
       de la page — voir initVeil() dans main.js ;
     • les lettres entrent une par une, de gauche à droite : STUDIO d'abord,
       Broye ensuite, puis le point rouge. C'est l'ordre de lecture du
       logotipe, et il devient lisible au lieu d'apparaître d'un bloc.
   ========================================================================== */
.veil-load {
  background: #0a0b0c;
}
.veil-load .lk-k {
  color: var(--mark-lite);
}
.veil-load .lk-n {
  color: #ffffff;
}
.veil-load .lk-n .pt {
  background: var(--mark-lite);
  opacity: 0;
  animation: veilIn 0.4s ease var(--d, 0ms) both;
}
/* Chaque lettre monte de trois pixels en apparaissant : sans ce rien de
   déplacement, la succession se lit comme un clignotement. */
.veil-load .vl {
  display: inline-block;
  font-style: normal;
  opacity: 0;
  transform: translateY(3px);
  animation: veilLettre 0.36s cubic-bezier(0.2, 0.7, 0.2, 1) var(--d, 0ms) both;
}
@keyframes veilLettre {
  to {
    opacity: 1;
    transform: none;
  }
}
/* Le mot entier n'a plus besoin de son propre fondu : ce sont les lettres qui
   le portent. La règle d'origine reste pour le cas — sans JavaScript — où le
   découpage n'a pas eu lieu. */
.veil-load .lk-k:not([data-decoupe]),
.veil-load .lk-n:not([data-decoupe]) {
  animation: veilIn 0.7s ease 0.05s forwards;
}
.veil-load .lk-k[data-decoupe],
.veil-load .lk-n[data-decoupe] {
  opacity: 1;
  animation: none;
}
/* La sortie est plus longue et plus douce qu'avant : le rideau s'efface au
   lieu de disparaître. */
body.is-loaded .veil-load {
  transition:
    opacity 0.45s cubic-bezier(0.4, 0, 0.2, 1) 0.04s,
    visibility 0s linear 0.5s;
}
/* Pages suivantes de la session : les lettres arrivent presque ensemble. */
.veil-load.veil--suite .vl {
  animation-duration: 0.22s;
}
/* Le logotype du rideau doit avoir disparu AVANT que le fond noir ne
   devienne assez transparent pour laisser deviner ce qu'il y a dessous.
   Sinon, pendant le fondu, trois « Studio Broye » se superposent : celui du
   rideau, celui de l'en-tête et le titre du hero — un logo qui semble
   dédoublé. 180 ms sans délai, pendant que le fond est encore opaque à
   85 % environ (le fondu du fond dure 720 ms), suffit à éviter le
   chevauchement. */
body.is-loaded .veil-load .lockup {
  opacity: 0;
  transition: opacity 0.14s ease;
}
@media (prefers-reduced-motion: reduce) {
  /* Propriétés longues, et surtout PAS le raccourci `animation` : celui-ci
     remet `animation-delay` à zéro, et comme il porte `!important` il l'
     emportait sur le retard écrit en style en ligne par le script. Résultat :
     les onze lettres arrivaient ensemble sous ce réglage, c'est-à-dire
     exactement l'effet qu'on voulait éviter. */
  .veil-load .vl {
    animation-name: veilLettre !important;
    animation-duration: 0.36s !important;
    animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1) !important;
    animation-fill-mode: both !important;
    animation-iteration-count: 1 !important;
    animation-delay: var(--d, 0ms) !important;
  }
  .veil-load .lk-n .pt {
    animation-name: veilIn !important;
    animation-duration: 0.4s !important;
    animation-fill-mode: both !important;
    animation-iteration-count: 1 !important;
    animation-delay: var(--d, 0ms) !important;
  }
  body.is-loaded .veil-load {
    transition:
      opacity 0.45s cubic-bezier(0.4, 0, 0.2, 1) 0.04s,
      visibility 0s linear 0.5s !important;
  }
  .veil-load.veil--suite .vl {
    animation-duration: 0.22s !important;
  }
  /* Même raison qu'en dehors de ce bloc : sans elle, la règle générale
     annule le fondu court du logotype (`transition: none !important` sur
     `*`), qui resterait donc affiché plein temps par-dessus le titre du
     hero pendant tout le fondu du fond. */
  body.is-loaded .veil-load .lockup {
    opacity: 0 !important;
    transition: opacity 0.14s ease !important;
  }
}

/* --- L'adresse quitte le cadre du navigateur ------------------------------
   Elle occupait une rangée de 34 px AU-DESSUS de la capture, dans le cadre
   dessiné. En la descendant sous « Voir le site », la capture remonte
   d'autant et chaque fiche perd cette hauteur — sans qu'on retire une seule
   information. Elle reste cliquable, en plus petit et en retrait : c'est une
   précision, pas un appel à l'action. --- */
.browser .url {
  display: none;
}
/* Sur une seule ligne : le lien à gauche, l'adresse à droite. Descendue en
   dessous, elle coûtait 21 px de hauteur et ne rendait que 13 des 34 gagnés
   en sortant du cadre. Sur la même ligne, elle ne coûte rien. */
.visit-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-top: auto;
}
.visit-row .visit {
  flex: none;
}
.visit-url {
  display: block;
  min-width: 0;
  padding: 3px 0;
  text-align: right;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  letter-spacing: 0.01em;
  color: var(--muted);
  text-decoration: none;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  transition: color 0.25s;
}
.visit-url:hover,
.visit-url:focus-visible {
  color: var(--mark);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* --- La couture du pied, et sa hauteur ------------------------------------
   La couture est en rouge profond : 5,8:1 sur la crème du pied, parfait quand
   la section qui précède est claire. Mais trois pages finissent sur une bande
   sombre, où ce rouge ne tient que 2,4:1 — il s'y éteint. `data-fin-sombre`
   est posé par main.js d'après la luminance réelle de la dernière section :
   la règle suit donc les bandes, elle ne suit pas une liste de pages.

   La hauteur, ensuite : 295 px de pied sur un écran de 900, pour cinq
   colonnes de liens, c'est un tiers d'écran donné à la sortie. Les marges se
   resserrent et le bas de page se colle. --- */
body[data-fin-sombre] footer::before {
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--mark-lite) 16%,
    var(--mark-lite) 84%,
    transparent 100%
  );
}
footer {
  padding: clamp(20px, 1.5vw, 26px) 0 clamp(8px, 0.6vw, 11px);
}
.foot-grid {
  gap: clamp(12px, 1vw, 20px);
  margin-bottom: clamp(10px, 0.8vw, 14px);
}
.foot-grid a {
  margin-bottom: 0;
  padding: 5px 0;
  font-size: 0.82rem;
}
.foot-grid .ft-title {
  margin-bottom: 8px;
}
.foot-brand p {
  margin-top: 6px;
  font-size: 0.78rem;
}
.socials {
  margin-top: 9px;
}
.foot-bottom {
  padding-top: 8px;
  font-size: 0.75rem;
}

/* --- Le pied de page, deuxième passe -------------------------------------
   259 px sur portable, 613 au téléphone : la rangée de liens en occupait
   189 et 519. Le premier resserrage n'avait touché qu'aux marges extérieures,
   or ce qui coûte ici c'est l'interligne de listes de six liens.

   On ne retire aucun lien. On serre l'interligne (1,15 au lieu du 1,6 hérité
   du corps de texte), on descend les titres de colonne et les liens d'un cran,
   et on réduit la pastille sociale. La lisibilité tient : ce sont des mots
   isolés, pas des phrases, et les cibles tactiles restent au-dessus de 32 px
   grâce au remplissage vertical. --- */
.foot-grid a {
  font-size: 0.79rem;
  line-height: 1.15;
  margin-bottom: 0;
  padding-block: 4px;
}
.foot-grid .ft-title {
  font-size: 0.75rem;
  margin-bottom: 5px;
}
.foot-address,
.foot-line {
  font-size: 0.76rem;
  line-height: 1.35;
}
.foot-badge {
  margin-top: 6px;
  font-size: 0.75rem;
}
.foot-brand p {
  font-size: 0.76rem;
  line-height: 1.4;
  margin-top: 5px;
  max-width: 30ch;
}
.foot-brand .mark {
  --lk: 0.86rem;
}
.socials {
  gap: 6px;
  margin-top: 8px;
}
.socials a {
  width: 26px;
  height: 26px;
}
.socials svg {
  width: 13px;
  height: 13px;
}
.foot-bottom {
  padding-top: 7px;
  font-size: 0.75rem;
  gap: 4px 18px;
}
footer {
  padding: clamp(16px, 1.2vw, 22px) 0 clamp(7px, 0.5vw, 9px);
}

/* Au téléphone, la marque n'a pas besoin de sa phrase : le pied la répète
   depuis l'en-tête, et elle coûtait à elle seule 45 px sur un bloc de 137. */
@media (max-width: 700px) {
  .foot-brand p {
    display: none;
  }
  .foot-grid {
    gap: 14px 20px;
  }
  .socials a {
    width: 34px;
    height: 34px;
  }
}

/* --- Le pied est blanc partout --------------------------------------------
   Il était crème sur toutes les pages. Mais l'œil ne juge pas une couleur, il
   juge un écart : sous la bande noire de l'accueil, cette crème paraît
   blanche ; sous une section blanche, elle paraît beige. Le même ton se lit
   donc de deux façons selon la page — c'est un contraste simultané, et il
   donne l'impression d'une incohérence.

   Le pied passe au blanc partout. Ce qui le sépare de la section du dessus
   n'est plus une différence de ton — qui ne valait que 1,1:1 quand celle-ci
   était blanche — mais sa couture, qui tient 6,5:1. Une ligne franche vaut
   mieux qu'un dégradé qu'on ne voit pas. --- */
footer {
  background: var(--bg);
}

/* --- Les fiches du portfolio au téléphone ---------------------------------
   En rail, une fiche occupe 84 % de 390 px : une colonne de 300 px de large.
   Y empiler seize chips, un état, un lien et un dépliant donnait un rectangle
   blanc étroit et interminable — l'inverse de ce qu'on veut montrer.

   On garde ce qui identifie le projet et ce qui permet d'y aller : le secteur,
   le nom, la note s'il y en a une, et « Voir le site ». Tout le reste — les
   technologies — descend dans « Détails du projet », qui existait déjà pour
   ça. Rien n'est retiré du document : les chips sont dans la page, repliées.
   --- */
@media (max-width: 900px) {
  .proj .chips {
    display: none;
  }
  /* Sauf une fois le dépliant ouvert : elles y retrouvent leur place, en tête
     du détail, avant le problème et la solution. */
  .proj details[open] .chips {
    display: flex;
    margin: 0 0 14px;
  }
  .proj .body {
    padding: 14px 15px 15px;
  }
  .proj .metric {
    margin: 0 0 10px;
  }
  .proj summary {
    padding: 12px 0 4px;
  }
}

/* ==========================================================================
   29. LES CHAMPS DU FORMULAIRE, IDENTIQUES PARTOUT
   Ils apparaissaient crème sur iPhone et blancs sur Mac. Ce n'est pas la
   feuille de style qui varie, c'est le navigateur : sur iOS, Safari applique
   son propre habillage aux champs (`-webkit-appearance`), qui repeint le fond
   et arrondit les angles par-dessus ce qu'on a déclaré. La couleur écrite
   ici n'était donc respectée que sur les navigateurs de bureau.

   On coupe cet habillage sur tous les contrôles, on pose un fond explicite,
   et on neutralise le jaune du remplissage automatique — que Chrome et Safari
   peignent eux aussi par-dessus, avec une règle interne qu'aucune déclaration
   de `background-color` ne peut atteindre : seule une ombre intérieure de la
   taille du champ la recouvre.

   Le fond retenu est le BLANC, sur un cadre à l'encre franche : c'est ce
   qu'on voyait sur Mac, et un champ blanc cerné se lit mieux qu'un aplat
   crème sur une carte déjà claire.
   ========================================================================== */
form input,
form select,
form textarea {
  -webkit-appearance: none;
  appearance: none;
  border-radius: 0;
  background-color: #ffffff;
  background-image: none;
  border: 1px solid rgba(17, 19, 21, 0.28);
  color: var(--ink);
}
/* Le menu déroulant garde sa flèche dessinée : elle est en `background-image`,
   donc réaffirmée après la remise à zéro ci-dessus. */
form select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%234f545a' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 16px 16px;
}
form input:hover,
form select:hover,
form textarea:hover {
  border-color: rgba(17, 19, 21, 0.5);
}
form input:focus,
form select:focus,
form textarea:focus {
  background-color: #ffffff;
  border-color: var(--mark);
  box-shadow: 0 0 0 3px rgba(var(--mark-rgb), 0.16);
}
/* Remplissage automatique : Chrome et Safari repeignent le champ en jaune
   pâle par une règle interne. Une ombre intérieure de la hauteur du champ est
   le seul moyen de la recouvrir, et `-webkit-text-fill-color` d'y garder
   l'encre du site. */
form input:-webkit-autofill,
form input:-webkit-autofill:hover,
form input:-webkit-autofill:focus,
form select:-webkit-autofill,
form textarea:-webkit-autofill {
  -webkit-box-shadow: 0 0 0 60px #ffffff inset;
  box-shadow: 0 0 0 60px #ffffff inset;
  -webkit-text-fill-color: var(--ink);
  caret-color: var(--ink);
}
/* iOS zoome sur un champ dont le texte fait moins de 16 px. Le formulaire se
   met donc à 16 px au téléphone : sans cela, toucher « Nom » agrandit la page
   et le visiteur doit la remettre en place à la main. */
@media (max-width: 900px) {
  form input,
  form select,
  form textarea {
    font-size: 16px;
  }
}

/* ==========================================================================
   30. LA FICHE DU PORTFOLIO — la capture à fleur de carte
   Le cadre de navigateur avait un sens tant qu'il portait l'adresse : il
   donnait l'échelle et servait de preuve. L'adresse est descendue à côté de
   « Voir le site », et il ne restait que trois pastilles grises sur une barre
   claire — une décoration sans propos, qui coûtait 28 px et repoussait la
   capture vers le bas au lieu de la laisser occuper le haut de la carte.

   La capture prend maintenant toute la largeur et affleure le bord haut. On
   regarde le travail, pas le dessin d'un navigateur autour.
   ========================================================================== */
.proj .shot {
  min-height: 0;
  padding: 0;
  display: block;
  border-bottom: 1px solid var(--line-soft);
}
.proj .shot .browser {
  width: 100%;
  border: 0;
}
.proj .shot .browser .top {
  display: none;
}
.proj .shot:has(.canvas--shot) .browser {
  width: 100%;
}
.browser .canvas--shot {
  aspect-ratio: 16 / 10;
}
/* Les fiches sans capture — un site hors ligne — gardent leur maquette
   dessinée, qui a besoin de sa respiration. */
.proj .shot:not(:has(.canvas--shot)) {
  padding: 16px 14px;
  background: var(--bg-2);
}
.proj .shot:not(:has(.canvas--shot)) .browser {
  width: 84%;
  border: 1px solid var(--line-soft);
  margin-inline: auto;
}
.proj .shot:not(:has(.canvas--shot)) .browser .top {
  display: flex;
}

/* --- La troisième vue : trois par ligne, et lisible ----------------------
   À 390 px, trois colonnes donnent des vignettes de 111 px. Le nom écrit en
   0,72 rem par-dessus n'y tenait plus : il débordait sur deux lignes et
   mangeait l'image. Il se réduit avec la vignette. --- */
@media (max-width: 620px) {
  .couvertures.est-4 .couv__nom {
    font-size: 0.75rem;
    letter-spacing: 0;
    padding: 18px 5px 5px;
    line-height: 1.15;
  }
  .couvertures.est-4 .couv::before {
    top: 5px;
    right: 5px;
    width: 5px;
    height: 5px;
    box-shadow: 0 0 0 2px rgba(var(--mark-rgb), 0.28);
  }
}

/* --- Aucun fond crème derrière les captures -------------------------------
   `.shot` et `.canvas--shot` portaient le crème du site : le temps qu'une
   capture arrive — elle est en `loading="lazy"` — on voyait un rectangle
   beige, puis l'image. Sur une carte blanche, ce clignotement se lit comme un
   défaut de couleur. Le fond d'attente est désormais celui de la carte. --- */
.proj .shot,
.browser .canvas--shot,
.couv {
  background: var(--bg-panel);
}
.proj .shot:not(:has(.canvas--shot)) {
  background: var(--bg-2);
}

/* ==========================================================================
   31. LES HUIT PRESTATIONS — d'une liste à un index
   --------------------------------------------------------------------------
   Christopher, 25.08.2026 : « la page est très pauvre et simple, et elle n'a
   aucun effet visuel ». C'était exact, et pour trois raisons précises :

     · huit lignes rigoureusement identiques, séparées par le même filet.
       Rien ne dit où l'on en est, rien n'accroche l'œil qui descend ;
     · la colonne de gauche tient en deux lignes, la droite en quatre : il
       restait une centaine de pixels de vide sur chaque ligne, et ce vide
       n'était pas une respiration, c'était un trou ;
     · `transition: 0.35s` était déclaré sur `.svc-row`… sans qu'aucune
       propriété ne change jamais. Le survol ne faisait donc rien du tout.

   La réponse n'est pas d'ajouter des cartes : le reste du site est fait de
   filets et de blanc, et huit rectangles ombrés jureraient. C'est un INDEX
   qu'on construit — le vocabulaire que la page d'accueil emploie déjà avec
   ses ordinales en filigrane — et on lui donne le seul geste qui manquait :
   la ligne survolée s'allume.

     l'ordinale   01 à 08, dans le serif du site. Elle numérote, elle occupe
                  le vide de la colonne de gauche, et elle donne à l'œil un
                  repère pour savoir combien il en reste.
     la pastille  l'icône flottait, seule, en trait fin doré. Un carré au
                  filet l'ancre et lui donne quelque chose à remplir.
     l'allumage   au survol : fond très légèrement teinté, filet d'accent qui
                  POUSSE sous la ligne de gauche à droite, ordinale et
                  pastille qui passent au rouge de la marque.

   Les numéros sortent d'un compteur CSS et non du HTML : les huit fiches sont
   écrites dans `build-pages.mjs`, et une numérotation à la main s'y serait
   désynchronisée à la première prestation ajoutée ou déplacée.
   ========================================================================== */
.svc-detail {
  counter-reset: svc;
}
.svc-row {
  counter-increment: svc;
  position: relative;
  /* Un peu de marge intérieure : sans elle, la teinte du survol viendrait
     buter exactement sur la première lettre du titre. */
  padding-inline: clamp(10px, 1.1vw, 20px);
  transition:
    background-color 0.4s ease,
    border-color 0.4s ease;
}
/* Le filet d'accent qui pousse sous la ligne survolée. Il double le filet
   existant au lieu de le remplacer : en changeant seulement `border-color`,
   la ligne s'allumait d'un bloc ; en `scaleX`, elle se trace. */
.svc-row::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 1px;
  background: var(--mark);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.55s cubic-bezier(0.2, 0.7, 0.2, 1);
  pointer-events: none;
}
/* L'ordinale. Largeur fixe : les huit titres doivent commencer sur le même
   axe, et « 01 » n'a pas la chasse de « 08 » dans un serif à chasse
   proportionnelle. */
.svc-row__head::before {
  content: counter(svc, decimal-leading-zero);
  flex: none;
  width: clamp(30px, 2.3vw, 44px);
  margin-top: -2px;
  font-family: var(--font-head);
  font-weight: var(--head-weight);
  font-size: clamp(1.4rem, 1.9vw, 2rem);
  line-height: 0.95;
  color: var(--mark);
  opacity: 0.28;
  transition:
    opacity 0.4s ease,
    transform 0.4s cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* La pastille de l'icône : un carré au filet, et le trait de l'icône reste
   là où il était. `border-box` est déjà global, donc la taille ne bouge pas
   d'un pixel — seul le dessin respire à l'intérieur. */
.svc-row__head .ic {
  padding: 9px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r);
  transition:
    border-color 0.4s ease,
    background-color 0.4s ease;
}
.svc-row__head .ic svg {
  transition: stroke 0.4s ease;
}

/* --- L'allumage ----------------------------------------------------------
   `:focus-within` autant que `:hover` : au clavier, la ligne qui reçoit le
   focus doit s'allumer comme celle que la souris désigne. --- */
.svc-row:hover,
.svc-row:focus-within {
  background: rgba(var(--mark-rgb), 0.022);
}
.svc-row:hover::after,
.svc-row:focus-within::after {
  transform: scaleX(1);
}
.svc-row:hover .svc-row__head::before,
.svc-row:focus-within .svc-row__head::before {
  opacity: 1;
  transform: translateX(-3px);
}
.svc-row:hover .svc-row__head .ic,
.svc-row:focus-within .svc-row__head .ic {
  border-color: var(--mark);
  background: var(--mark);
}
.svc-row:hover .svc-row__head .ic svg,
.svc-row:focus-within .svc-row__head .ic svg {
  stroke: #ffffff;
}
/* Le filet du périmètre passe du doré au rouge : la ligne entière répond,
   pas seulement son bord gauche. */
.svc-row__inc {
  transition: border-color 0.4s ease;
}
.svc-row:hover .svc-row__inc,
.svc-row:focus-within .svc-row__inc {
  border-left-color: var(--mark);
}
/* Les étiquettes de technologies se détachent un peu, sans changer de
   couleur : elles restent une information, pas un appel. */
.chip {
  transition: border-color 0.4s ease;
}
.svc-row:hover .chip,
.svc-row:focus-within .chip {
  border-color: var(--line);
}

/* Sous « mouvement réduit », tout reste : ce sont des changements de couleur
   et un filet qui se trace, aucun déplacement de contenu. Seul le décalage
   de l'ordinale — le seul vrai mouvement — est retiré. C'est la distinction
   déjà retenue pour le rideau et pour le destello des boutons. */
@media (prefers-reduced-motion: reduce) {
  .svc-row::after {
    transition-property: transform !important;
    transition-duration: 0.55s !important;
    transition-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1) !important;
  }
  .svc-row:hover .svc-row__head::before,
  .svc-row:focus-within .svc-row__head::before {
    transform: none;
  }
}

/* ==========================================================================
   23. HERO AU TÉLÉPHONE — le texte dans le tiers supérieur
   Centré verticalement, le bloc tombait à 550 px de haut : la moitié
   supérieure du premier écran n'était que le noir de la photographie. Le
   texte monte, et c'est le bas — clavier, portable, la partie éclairée du
   cliché — qui reste visible sous lui.
   ========================================================================== */
@media (max-width: 900px) {
  .hero {
    align-items: flex-start;
    padding-top: calc(var(--hdr-h) + 13svh);
  }
}

/* Les liens du pied : 24 px de haut quoi qu'en disent les règles de forme
   mobile plus haut (WCAG 2.5.8, taille des cibles). */
.foot-grid nav a,
.foot-grid .foot-line {
  min-height: 24px;
  padding-block: 5px;
}

/* ==========================================================================
   24. IMAGES RESPONSIVES
   Les <picture> ne créent pas de boîte : l'<img> qu'ils enveloppent se place
   exactement comme avant dans les couvertures, les fiches et le hero.
   ========================================================================== */
picture {
  display: contents;
}

/* ==========================================================================
   25. FICHES DE CAS — une page par projet
   La capture en grand dans son cadre de navigateur, le récit (problème,
   solution, résultat, évolutions) à gauche, la fiche « en bref » collante à
   droite, et le projet suivant pour continuer la visite.
   ========================================================================== */
.cas-shot {
  max-width: 1100px;
  margin: 0 auto clamp(28px, 3vw, 48px);
}
.cas-shot .browser {
  width: 100%;
}
.cas-shot .canvas--shot img {
  display: block;
  width: 100%;
  height: auto;
}
.cas-shot:hover .canvas__video.est-en-lecture,
.cas-shot:focus-within .canvas__video.est-en-lecture {
  opacity: 1;
}
.cas-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(28px, 3vw, 56px);
  align-items: start;
}
@media (min-width: 900px) {
  .cas-grid {
    grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  }
  .cas-fiche {
    position: sticky;
    top: calc(var(--hdr-h) + 24px);
  }
}
.cas-recit .row {
  padding: clamp(18px, 1.8vw, 26px) 0;
  border-top: 1px solid var(--line-soft);
}
.cas-recit .row:last-child {
  border-bottom: 1px solid var(--line-soft);
}
.cas-recit .k,
.cas-fiche .k {
  font-family: "Poppins", system-ui, sans-serif;
  font-size: 0.75rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--mark);
  font-weight: 500;
  margin: 0 0 10px;
}
.cas-recit p {
  margin: 0;
  font-size: clamp(1rem, 1.05vw, 1.12rem);
  line-height: 1.6;
  color: var(--ink-soft);
  max-width: 62ch;
}
.cas-fiche {
  border: 1px solid var(--line-soft);
  background: var(--bg-panel);
  padding: clamp(20px, 2vw, 30px);
}
.cas-fiche dl {
  margin: 0;
  display: grid;
  gap: 14px;
}
.cas-fiche dl > div {
  display: grid;
  gap: 6px;
  padding-top: 12px;
  border-top: 1px solid var(--line-soft);
}
.cas-fiche dt {
  font-size: 0.75rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}
.cas-fiche dd {
  margin: 0;
  color: var(--ink);
  font-size: 0.95rem;
}
.cas-fiche dd a {
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
  padding-block: 3px;
}
.cas-fiche dd a:hover,
.cas-fiche dd a:focus-visible {
  color: var(--mark);
  border-color: var(--mark);
}
.cas-fiche .chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.statut {
  display: inline-flex;
  align-items: center;
  gap: 9px;
}
.statut i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--mark);
  flex: none;
}
.statut--demo i,
.proj .status--demo i {
  background: var(--muted);
}
.statut--bientot i {
  background: transparent;
  border: 1px solid var(--muted);
}
.cas-cta {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--line-soft);
}
.cas-cta__h {
  font-family: var(--font-head);
  font-size: 1.25rem;
  margin: 0 0 6px;
  color: var(--ink);
}
.cas-cta p {
  margin: 0 0 14px;
  color: var(--ink-soft);
  font-size: 0.95rem;
}
/* Le lien vers la fiche, au pied de chaque carte de Réalisations. */
.cas-lien {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 14px;
  padding: 12px 0 2px;
  border-top: 1px solid var(--line-soft);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  text-decoration: none;
}
.cas-lien svg {
  width: 15px;
  height: 15px;
  stroke: var(--mark);
  fill: none;
  stroke-width: 1.8;
  transition: transform 0.3s;
}
.cas-lien:hover svg,
.cas-lien:focus-visible svg {
  transform: translateX(4px);
}
.cas-suivant {
  display: grid;
  gap: 18px;
}
.cas-suivant__lien {
  display: grid;
  grid-template-columns: minmax(0, 220px) 1fr auto;
  gap: clamp(16px, 2vw, 32px);
  align-items: center;
  text-decoration: none;
  color: var(--ink);
  border: 1px solid var(--line-soft);
  background: var(--bg-panel);
  padding: clamp(14px, 1.4vw, 22px);
  transition:
    border-color 0.3s,
    transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.cas-suivant__lien:hover,
.cas-suivant__lien:focus-visible {
  border-color: var(--mark);
  transform: translateY(-3px);
}
.cas-suivant__img img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  object-position: 50% 0;
}
.cas-suivant__txt {
  display: grid;
  gap: 6px;
}
.cas-suivant__txt .sector {
  font-size: 0.75rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--gold);
}
.cas-suivant__nom {
  font-family: var(--font-head);
  font-size: clamp(1.3rem, 1.8vw, 2rem);
  line-height: 1.15;
}
.cas-suivant__lien > svg {
  width: 22px;
  height: 22px;
  stroke: var(--mark);
  fill: none;
  stroke-width: 1.8;
}
@media (max-width: 620px) {
  .cas-suivant__lien {
    grid-template-columns: 1fr;
  }
  .cas-suivant__lien > svg {
    display: none;
  }
}

/* ==========================================================================
   26. APPROCHE — les cinq étapes d'un projet
   Une vraie séquence : les ordinaux disent l'ordre, pas la décoration.
   ========================================================================== */
.etapes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(14px, 1.4vw, 24px);
}
@media (min-width: 620px) {
  .etapes {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 1000px) {
  .etapes {
    grid-template-columns: repeat(5, 1fr);
  }
}
.etape {
  border-top: 2px solid var(--line-soft);
  padding-top: 16px;
}
.etape__n {
  display: block;
  font-family: var(--font-head);
  font-size: 1.3rem;
  color: var(--mark);
  margin-bottom: 10px;
}
.etape h3 {
  font-size: 1rem;
  margin: 0 0 8px;
  color: var(--ink);
}
.etape p {
  margin: 0;
  font-size: 0.92rem;
  color: var(--ink-soft);
  line-height: 1.55;
}
.approach__h2 {
  font-size: clamp(1.4rem, 2vw, 2rem);
  color: var(--sand-ink);
  margin: 0 0 14px;
}
