@charset "utf-8";
/* ==========================================================================
   POLYENCO — POLY ENGINEERING COMPANY
   Feuille de style unique — HTML5 / CSS3, sans framework ni build.

   L'identité visuelle est dérivée du logo officiel :
   images/logo/logo_polyenco.jpeg

   Relevé effectué directement sur le fichier :
     • fond du logo ........ #AA1819 (valeur mesurée au pixel, dominante)
     • lettrage ............ #FFFFFF
     • sous-titre .......... #000000
     • wordmark ............ capitales géométriques très grasses, très espacées
     • sous-titres ......... capitales fines, interlettrage large
     • symbole ............. construction géométrique, angles droits + denture

   Le symbole ne sert pas d'illustration : il sert de règle. Sa géométrie
   a été relevée au pixel sur images/logo/polyenco-symbol-white.png, et
   c'est d'elle que sortent le module, les filets et la denture employés
   partout ailleurs (voir l'« ADN GÉOMÉTRIQUE » au § 01, puis le § 04bis).
   Le logo lui-même n'est jamais redessiné ni répété en décor : on n'en
   reprend que les proportions.
   ========================================================================== */


/* ==========================================================================
   00 — POLICE OFFICIELLE : Mont (Fontfabric), EN ATTENTE
   Les fichiers de /Font sont des versions DEMO : chaque lettre accentuée
   (é, à, ç...) porte un tampon « DEMO » (vérifié à nouveau le 13/09/2026).
   Inutilisables sur un site en français. Montserrat et Inter restent en
   place jusqu'à réception de la version complète de Mont ; il suffira alors
   de déclarer ici une @font-face par graisse fournie, puis de remplacer
   --font-heading et --font-body.
   ========================================================================== */


/* ==========================================================================
   01 — DESIGN SYSTEM (variables)
   ========================================================================== */

:root {

  /* --- Couleurs de marque (relevées sur le logo) --- */
  --polyenco-red:          #AA1819;   /* couleur principale, mesurée */
  --polyenco-red-light:    #CD1D1E;   /* dérivée : survols uniquement */
  --polyenco-red-dark:     #861314;   /* dérivée : état actif des boutons */
  --polyenco-black:        #000000;
  --polyenco-white:        #FFFFFF;
  --polyenco-anthracite:   #131316;

  /* --- Surfaces --- */
  --bg:                    #0A0A0B;   /* fond général, noir profond */
  --bg-raise:              #111113;   /* section alternée */
  --bg-steel:              #17171A;   /* blocs */

  /* --- Texte sur fond sombre ---
     Contrastes mesurés sur --bg-steel (le fond sombre le plus clair) :
     --text 17:1, --text-soft 13:1, --text-muted 8:1. Aucun gris clair sous
     8:1 : le visiteur ne doit jamais forcer ses yeux. Le rouge n'est jamais
     utilisé pour du petit texte : il sert aux filets et aux états. */
  --text:                  #FFFFFF;
  --text-soft:             #DCDCE2;
  --text-muted:            #B7B7BF;

  /* --- Lignes techniques --- */
  --line:                  rgba(255,255,255,.12);
  --line-strong:           rgba(255,255,255,.24);

  /* --- Typographie --- */
  --font-heading: "Montserrat", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-body:    "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;

  /* Interlettrage : l'esprit du logo, sans nuire à la lecture */
  --track-label:  .12em;   /* petits labels */
  --track-nav:    .08em;   /* navigation, boutons */

  /* --- Mise en page --- */
  --container-width: 1400px;
  --gutter:          clamp(20px, 5vw, 48px);
  --section-padding: clamp(64px, 7vw, 112px);
  --header-h:        84px;

  /* --- Divers --- */
  --ease: cubic-bezier(.22,.61,.36,1);
  --ease-out: cubic-bezier(.16,1,.3,1);

  /* Angles très légèrement adoucis : industriel et moderne, jamais
     « application mobile ». 12 px sur les visuels et les blocs, 8 px sur
     les petits éléments (boutons, filtres, étiquettes). */
  --radius:    12px;
  --radius-sm: 8px;


  /* ------------------------------------------------------------------------
     ADN GÉOMÉTRIQUE — relevé au pixel sur images/logo/polyenco-symbol-white.png
     (480 × 192 ; encre de x20..460, y20..172, soit 441 × 153).

     Le symbole PEC n'est pas dessiné au hasard : une coupe verticale du
     signe donne, du haut vers le bas,

         filet 9 px · vide 27 px · masse 81 px · vide 27 px · filet 9 px

     soit le rapport 1 : 3 : 9. Tout le système ci-dessous en découle — il
     n'y a pas une seule valeur inventée.

       --rail-w   le filet fin détaché de la masse (1).
       --tooth-*  la denture du flanc droit du symbole. Un pas complet
                  mesure 5 modules : flanc 1,5 · sommet 2 · flanc 1,5,
                  le sommet vaut donc exactement 2/5 du pas — c'est ce
                  rapport, et pas une dent « au jugé », qui est découpé
                  dans les masques ci-dessous (2,4 / 4,8 sur 12).
     ---------------------------------------------------------------------- */

  --rail-w:      1px;    /* le filet : toujours un cheveu */

  --tooth-pitch: 12px;   /* un pas de denture */
  --tooth-depth: 5px;    /* 4 px de dent posés sur 1 px de matière */

  /* La denture, découpée une fois pour tout le site. Sert de masque : la
     couleur reste libre (filet blanc discret ou rouge POLYENCO). Le tracé
     reprend les proportions ci-dessus : 2,4 / 4,8 / 2,4 / 2,4 sur 12.

     Les dents ne flottent jamais : sur le logo elles mordent dans la masse
     du bandeau. On garde donc sous elles un filet plein de 1 px — sans lui
     on ne lit plus une arête usinée, mais une rangée de points. */
  --tooth-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='5' viewBox='0 0 12 5'%3E%3Cpath d='M0 4L2.4 0H7.2L9.6 4ZM0 4H12V5H0Z' fill='%23000'/%3E%3C/svg%3E");

  /* Quatre dents rouges pleines, pour amorcer un filet de titre. Le rouge
     est écrit en clair : un masque ne sert à rien quand la couleur ne doit
     jamais changer. Même tracé que --tooth-mask, répété quatre fois. */
  --rule-teeth: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='5' viewBox='0 0 48 5'%3E%3Cpath d='M0 4L2.4 0H7.2L9.6 4ZM12 4L14.4 0H19.2L21.6 4ZM24 4L26.4 0H31.2L33.6 4ZM36 4L38.4 0H43.2L45.6 4ZM0 4H48V5H0Z' fill='%23AA1819'/%3E%3C/svg%3E");

  /* La denture retournée : filet plein en haut, dents vers le bas. Elle
     mord donc dans la matière qui la suit, et non dans celle qui la
     précède — indispensable au pied de page, où la section du dessus est
     tantôt rouge (appel à l'action) tantôt sombre. Posée dans l'autre
     sens, on n'y lirait plus une arête usinée mais un bord perforé. */
  --tooth-mask-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='5' viewBox='0 0 12 5'%3E%3Cpath d='M0 0H12V1H0ZM0 1L2.4 5H7.2L9.6 1Z' fill='%23000'/%3E%3C/svg%3E");

  /* La denture dressée : même tracé, pivoté d'un quart de tour, dents vers
     la droite. Sert aux arêtes verticales (état actif d'une liste). */
  --tooth-mask-v: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='5' height='12' viewBox='0 0 5 12'%3E%3Cpath d='M1 0L5 2.4V7.2L1 9.6ZM0 0H1V12H0Z' fill='%23000'/%3E%3C/svg%3E");

  /* La même amorce en blanc, pour la seule section posée sur du rouge. */
  --rule-teeth-white: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='5' viewBox='0 0 48 5'%3E%3Cpath d='M0 4L2.4 0H7.2L9.6 4ZM12 4L14.4 0H19.2L21.6 4ZM24 4L26.4 0H31.2L33.6 4ZM36 4L38.4 0H43.2L45.6 4ZM0 4H48V5H0Z' fill='%23FFFFFF'/%3E%3C/svg%3E");

  /* Une dent isolée, servant de repère (puces, jalons, états actifs).
     Le sommet occupe la moitié de la base : c'est la dent du logo sortie
     de sa denture, sans le vide qui la suit. */
  --tooth-clip: polygon(25% 0, 75% 0, 100% 100%, 0 100%);

  /* Le combiné WhatsApp dans sa bulle. Aucune couleur n'est écrite dans le
     tracé : il sert de masque, et c'est la couleur du lien qui le peint
     (voir a[href^="https://wa.me/"], § 22). Tracé officiel de la marque,
     repris tel quel — on ne redessine pas un logo. */
  --icon-whatsapp: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893A11.821 11.821 0 0020.465 3.488'/%3E%3C/svg%3E");

  /* L'enveloppe et le combiné, posés à côté du glyphe WhatsApp ci-dessus et
     réglés sur lui. Même principe : aucune couleur dans le tracé, ce sont des
     masques que la couleur du lien vient peindre (voir § 22).

     Deux traits seulement, aucun aplat, aucun angle arrondi : le rectangle de
     l'enveloppe et le rabat en V reprennent l'équerre du reste du site, et les
     bouts de ligne sont carrés comme ceux de la croix du menu mobile. Le trait
     est à 2,1 pour 24 : mesuré contre le tracé WhatsApp, qui est plein, 1,8
     paraissait maigre à côté de lui dans le pied de page. */
  --icon-mail: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.1' stroke-linecap='square'%3E%3Crect x='2.6' y='5.1' width='18.8' height='13.8'/%3E%3Cpath d='M2.6 6.1 12 13.2 21.4 6.1'/%3E%3C/svg%3E");
  --icon-phone: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.1' stroke-linecap='square'%3E%3Cpath d='M8.2 3.3H3.6v3c0 7.8 6.3 14.1 14.1 14.1h3v-4.6l-4.6-1.6-1.9 2.5a16.3 16.3 0 0 1-6.5-6.5l2.5-1.9z'/%3E%3C/svg%3E");

  /* Les trois pastilles du relevé de la page Contact — société, activité,
     localisation. Même famille que les deux ci-dessus, à la ligne près :
     même viewBox de 24, même trait de 2,1, mêmes bouts carrés, aucun aplat,
     aucun angle arrondi hors du bulbe du repère. Rien n'est emprunté à une
     bibliothèque extérieure : le site n'en charge aucune, et en poser une
     pour trois glyphes coûterait plus cher que le reste de la feuille.

     La société n'est pas un immeuble de bureaux mais une usine — le profil
     en dents de scie, le bâti haut et la ligne de sol : c'est le métier de
     POLYENCO, pas un siège social. L'activité prend la roue dentée. Le
     repère de localisation est la goutte usuelle ; son bulbe est le seul
     cercle du jeu avec celui de la roue, la pointe reste un angle vif comme
     le reste du dessin.

     Les deux premiers ont été redessinés après relevé à leur taille réelle
     (1.04em, soit 16,3 px dans ce bloc), et non d'après leur agrandissement :

       • la roue avait ses huit dents détachées d'un petit moyeu. Espacées de
         la sorte, elles ne se lisaient pas comme une denture mais comme des
         rayons : le glyphe faisait un astérisque. Les dents sont donc posées
         sur la bague, qui est passée de 3,8 à 5,6 de rayon — la couronne se
         referme et la roue redevient une roue ;
       • l'usine tenait sur 15 unités de large ; portée à 18,4 avec des dents
         de 6 au lieu de 5, le profil se distingue de la cheminée. */
  --icon-company: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.1' stroke-linecap='square'%3E%3Cpath d='M2.8 20.8h18.4M5.2 20.8v-9l6 3.6v-3.6l6 3.6V3.8h3.4v17'/%3E%3C/svg%3E");
  --icon-activity: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.1' stroke-linecap='square'%3E%3Ccircle cx='12' cy='12' r='5.6'/%3E%3Cpath d='M12 6.4V2.8M12 17.6v3.6M6.4 12H2.8M17.6 12h3.6M8 8 5.5 5.5M16 16l2.5 2.5M16 8l2.5-2.5M8 16l-2.5 2.5'/%3E%3C/svg%3E");
  --icon-place: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.1' stroke-linecap='square'%3E%3Cpath d='M12 21.4c4.3-4.8 6.5-8.4 6.5-10.9a6.5 6.5 0 1 0-13 0c0 2.5 2.2 6.1 6.5 10.9Z'/%3E%3Ccircle cx='12' cy='10.5' r='2.3'/%3E%3C/svg%3E");

  /* Trame d'implantation : le pas de 108 px vaut 4 × 27, la respiration
     du logo portée à l'échelle de la page. */
  --setout-step: 108px;
}


/* ==========================================================================
   02 — RÉINITIALISATION
   ========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 96px;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img, svg, video { display: block; max-width: 100%; }
img { height: auto; }

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

button, input, select, textarea { font: inherit; color: inherit; }

h1, h2, h3, h4, p, ul, ol, dl, dd, figure, blockquote { margin: 0; }
ul, ol { padding: 0; list-style: none; }

:focus-visible {
  outline: 2px solid var(--polyenco-red-light);
  outline-offset: 3px;
}

::selection { background: var(--polyenco-red); color: #fff; }


/* ==========================================================================
   03 — TYPOGRAPHIE
   ========================================================================== */

.display-xl,
.display-lg,
.display-md,
.display-sm {
  font-family: var(--font-heading);
  font-weight: 900;
  line-height: 1.06;
  letter-spacing: -.02em;
  color: var(--text);
  text-wrap: balance;
}

/* --------------------------------------------------------------------------
   MOTS INSÉCABLES
   Un mot ne doit jamais être coupé en deux lignes : « CONSTRUCTION » reste
   « CONSTRUCTION ». Quand la place manque, c'est la taille du texte qui
   diminue (les clamp ci-dessous sont réglés pour que le mot le plus long de
   chaque titre tienne jusqu'à 320 px de large), jamais le mot qui casse.
   -------------------------------------------------------------------------- */
h1, h2, h3, h4,
.display-xl, .display-lg, .display-md, .display-sm,
.hero__title, .hero__line, .cta__title, .cta__text, .cta__email,
.solution__title, .solution__lede, .principle__title, .principle__text,
.process__name, .process__text,
.folio__title, .work__title, .flow__name, .flow__say,
.sol-link__title, .sol-link__text, .scope__title,
.hero__activity, .hero__place,
.mailblock__link, .about-intro__lead, .lede, .label, .brand-subtitle,
.subnav__link, .filter, .btn, .tick-list li, .site-footer__list a {
  word-break: normal;
  overflow-wrap: normal;
  -webkit-hyphens: none;
          hyphens: none;
}

/* Deux exceptions : une adresse électronique n'a pas d'espace où passer à la
   ligne. On l'autorise à se couper, mais seulement si elle ne tient pas,
   seule, sur une ligne entière. */
.mailblock__link,
.cta__email { overflow-wrap: break-word; }


/* --------------------------------------------------------------------------
   PARAGRAPHES : DRAPEAU À DROITE
   Les paragraphes étaient justifiés. Mesure faite, la justification ne tenait
   pas : elle n'est propre qu'au-delà d'environ 65 signes par ligne, et les
   colonnes du site n'y arrivent presque jamais. Écarts relevés entre deux
   mots, pour un espace naturel de 4,6 px :

     « POLYENCO, Poly Engineering Company, intervient… »   64 px  (À propos, 390 px)
     « Structures métalliques, manutention… »              41 px  (Réalisations, 390 px)
     « Parlez-nous de votre besoin… »                      25 px  (Réalisations, 1440 px)
     « Un projet de construction métallique… »             28 px  (Contact, 1440 px)

   Des trous de cette taille creusent des rivières blanches dans le bloc et se
   voient avant le texte. Le drapeau à droite garde un interlettrage régulier,
   quelle que soit la largeur de la colonne — c'est la seule chose qui tienne
   à la fois sur un téléphone de 320 px et sur une colonne de 700 px.

   La marge gauche reste alignée : la composition éditoriale ne bouge pas, ce
   sont les fins de ligne qui redeviennent libres.
   -------------------------------------------------------------------------- */
p { text-align: left; }

/* Plancher bas et pente marquée : « DES STRUCTURES » en Montserrat 900
   doit tenir sur un écran de 360 px sans déborder. */
.display-xl { font-size: clamp(2rem, 7.6vw, 6rem); line-height: 1.03; }
.display-lg { font-size: clamp(2.05rem, 4.4vw, 3.7rem); }
.display-md { font-size: clamp(1.6rem, 2.8vw, 2.4rem); line-height: 1.12; }
.display-sm {
  font-size: clamp(1.1rem, 1.45vw, 1.3rem);
  font-weight: 800;
  line-height: 1.28;
  letter-spacing: -.01em;
}

/* Titres en capitales : on retrouve la logique du logo.
   Interligne un peu plus ouvert pour les capitales accentuées (É, Ç, À). */
.display-xl.is-caps,
.display-lg.is-caps {
  text-transform: uppercase;
  letter-spacing: -.005em;
  line-height: 1.06;
}

.is-dim  { color: var(--text-soft); }

/* --------------------------------------------------------------------------
   LE FILET DE TITRE
   Tous les titres de section portent le même bloc-titre : un filet fin,
   amorcé à gauche par quatre dents rouges. C'est la construction du logo
   — la matière dentée, puis le filet — ramenée à 5 px, et c'est ce qui
   fait que deux pages se reconnaissent d'un coup d'œil.

   Posé sur le titre lui-même : aucune page n'a besoin d'être modifiée, et
   aucune section ne peut l'oublier. Les grands titres d'appel à l'action
   (fond rouge) et les titres de hero en sont exclus : ils portent déjà
   leur propre cadrage.

   Largeur fixe, et c'est volontaire : selon les pages le titre vit dans
   une colonne de grille, dans un bloc de 58 caractères ou dans toute la
   largeur. Un filet en pourcentage changerait donc de longueur d'une page
   à l'autre — on lirait un bord de boîte. À 168 px il reste le même
   partout : une marque, pas une bordure.
   -------------------------------------------------------------------------- */
h2.display-lg::before,
h2.display-md::before {
  content: "";
  display: block;
  width: 168px;
  max-width: 100%;
  height: var(--tooth-depth);
  margin-bottom: clamp(18px, 2vw, 26px);
  background-image: var(--rule-teeth), linear-gradient(var(--line-strong), var(--line-strong));
  background-repeat: no-repeat, no-repeat;
  background-size: calc(var(--tooth-pitch) * 4) var(--tooth-depth), 100% var(--rail-w);
  background-position: left top, left bottom;
}

/* Sauf quand le bloc porte déjà son intitulé (VISION, EXPERTISE…) : c'est
   l'étiquette qui ouvre, le filet ferait doublon. */
.section-label + h2::before { content: none; }

.lede {
  font-size: clamp(1.02rem, 1.2vw, 1.15rem);
  line-height: 1.72;
  color: var(--text-soft);
}

/* Petit label technique */
.label {
  font-family: var(--font-heading);
  font-size: .75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--track-label);
  line-height: 1.4;
}

.brand-subtitle {
  font-family: var(--font-heading);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--track-label);
}

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

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 999;
  background: var(--polyenco-red); color: #fff;
  padding: 14px 22px;
  font-family: var(--font-heading); font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--track-nav);
  font-size: .75rem;
}
.skip-link:focus { left: 0; }


/* ==========================================================================
   04 — PRIMITIVES DE MISE EN PAGE
   ========================================================================== */

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

.section { padding-block: var(--section-padding); position: relative; }
.section--raise  { background: var(--bg-raise); }

/* Séparateur fin entre sections */
.section + .section { border-top: 1px solid var(--line); }

/* Intitulé de section : le texte seul, sans filet ni numéro.
   Les ornements répétés (01 —, flèches, tirets) ont été retirés du site. */
.section-label { margin-bottom: 20px; }
.section-label__text {
  display: inline-block;
  font-family: var(--font-heading);
  font-size: .75rem; font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--track-label);
  line-height: 1.4;
  color: var(--text-soft);
}

/* --------------------------------------------------------------------------
   LE BLOC-TITRE DE SECTION : LA COLONNE DE LECTURE PASSE SUR LE PARAGRAPHE
   Le bloc portait un plafond de 58ch. Une boîte mesurée en ch l'est sur la
   police de l'élément qui porte la règle — ici le corps de texte, à 16 px —,
   jamais sur celle du titre, composé en Montserrat 900 à une taille fluide.
   La boîte ne suivait donc pas la taille réelle du texte qu'elle contenait,
   et le titre repliait bien avant d'avoir atteint le bord de sa colonne.

   Relevé à 1440 px (colonne utile : 1304 px ; boîte : 585 px) :

     « De l'étude au chantier. »     texte  841 px  → 2 lignes
     « From engineering to site. »   texte  919 px  → 3 lignes

   La même composition donnait deux lignes en français et trois en anglais :
   c'est la boîte qui décidait, pas une intention. Les vraies compositions
   en plusieurs lignes de ce site sont écrites en toutes lettres dans le
   HTML — les <span class="hero__line"> du hero, le <br> de l'appel à
   l'action de l'accueil — et ne bougent pas.

   La colonne de lecture, elle, ne disparaît pas : elle descend sur le
   paragraphe, seul endroit où le ch est calculé sur la police du texte
   qu'il mesure. 58 ch y valent 596 px à 1280 px et 672 px à 1920 px, soit
   une soixantaine de signes par ligne à toutes les largeurs.

   Le bloc garde son rôle dans .section-bar : il reste l'élément de flex qui
   tient le titre et sa phrase d'un côté, le lien de l'autre.
   -------------------------------------------------------------------------- */
.section-head__text { margin-top: 18px; max-width: 58ch; }

/* Barre de section : titre à gauche, lien à droite */
.section-bar {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 28px 40px;
  flex-wrap: wrap;
  margin-bottom: clamp(36px, 4vw, 52px);
}

.mt-2 { margin-top: 24px; }
.mt-3 { margin-top: 40px; }
.mt-4 { margin-top: 56px; }


/* ==========================================================================
   04bis — SIGNES POLYENCO
   La langue visuelle tirée du symbole PEC tient en trois signes, et trois
   seulement. Aucun n'a été ajouté pour « faire dessiné » : chacun remplace
   un ornement générique qui traînait déjà sur le site.

     la denture    arête usinée du flanc droit du symbole. Elle signe
                   l'état actif (navigation, solution lue) et ferme
                   chaque page (pied de page). Voir --tooth-mask.
     la dent seule remplace la pastille ronde partout où il fallait un
                   repère : jalons du processus, catégorie d'un ouvrage.
                   Voir --tooth-clip.
     le filet      un cheveu amorcé par quatre dents rouges, au-dessus de
                   chaque titre de section. Voir --rule-teeth, § 03.

   Règle d'emploi — le rouge n'est jamais une surface, c'est un signal :
   étape atteinte, page en cours, élément sélectionné, repère de détail.
   Chacun de ces signes est posé directement sur le composant concerné
   (.nav__link, .process__node, .site-footer…) plutôt que sur une classe
   utilitaire : rien ne peut ainsi être saupoudré au hasard dans le HTML.
   La seule exception est la trame ci-dessous, qui s'applique à une
   section entière.

   --- Trame d'implantation ---
   Le quadrillage du calque de mise au net, au pas du logo. Il doit rester
   à la limite du visible : c'est un fond de plan, pas un motif. Il
   s'efface vers le bas pour ne jamais buter sur la section suivante.
   ========================================================================== */

.setout { position: relative; isolation: isolate; }
.setout::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(90deg, rgba(255,255,255,.055) var(--rail-w), transparent var(--rail-w)),
    linear-gradient(0deg,  rgba(255,255,255,.055) var(--rail-w), transparent var(--rail-w));
  background-size: var(--setout-step) var(--setout-step);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 42%, transparent 92%);
          mask-image: linear-gradient(180deg, #000 0%, #000 42%, transparent 92%);
}


/* ==========================================================================
   05 — BOUTONS
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  min-height: 50px;
  padding: 0 28px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  cursor: pointer;
  font-family: var(--font-heading);
  font-size: .75rem;
  font-weight: 700;
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: var(--track-nav);
  white-space: nowrap;
  transition: background-color .3s var(--ease),
              border-color .3s var(--ease),
              color .3s var(--ease);
}

/* Bouton principal : la surface rouge POLYENCO */
.btn--primary {
  background: var(--polyenco-red);
  border-color: var(--polyenco-red);
  color: #fff;
}
.btn--primary:hover { background: var(--polyenco-red-light); border-color: var(--polyenco-red-light); }
.btn--primary:active { background: var(--polyenco-red-dark); }

/* Bouton secondaire : filet seul */
.btn--ghost {
  border-color: var(--line-strong);
  color: var(--text);
}
.btn--ghost:hover {
  border-color: #fff;
  background: rgba(255,255,255,.06);
}

/* Boutons posés sur un fond rouge */
.btn--onred {
  background: #fff;
  border-color: #fff;
  color: var(--polyenco-red);
}
.btn--onred:hover { background: var(--polyenco-black); border-color: var(--polyenco-black); color: #fff; }

.btn--onred-ghost { border-color: rgba(255,255,255,.55); color: #fff; }
.btn--onred-ghost:hover { border-color: #fff; background: rgba(255,255,255,.12); }

.btn--lg { min-height: 56px; padding: 0 34px; font-size: .78rem; }


/* ==========================================================================
   05bis — ÉCRAN D'ENTRÉE
   À chaque ouverture de l'accueil, le visiteur voit d'abord le logo, seul,
   sur le rouge POLYENCO ; puis la page. Pas de roue qui tourne, pas
   d'attente fabriquée : l'écran dure 2 s et s'en va.

   Le fond est exactement le rouge du fichier logo (#AA1819, relevé au pixel
   sur images/logo/polyenco-logo.png, qui porte un chunk sRGB : le navigateur
   rend donc le PNG et l'aplat CSS à la même valeur). Les deux rouges se
   confondent : le logo paraît posé sur l'écran, sans bord ni cadre visible.
   Le logo n'est ni redessiné, ni recoloré, ni recadré — c'est le fichier tel
   quel, à ses proportions.

   C'est ce rouge commun qui rend le dévoilement possible. Le logo n'est pas
   mis en fondu — un fondu sur un aplat de la même couleur ne se voit pas,
   seules les lettres blanches frémiraient. Il est découvert de gauche à
   droite par un volet : la part non encore découverte laisse voir l'aplat
   CSS, exactement le même rouge, si bien que rien ne semble masqué. Seules
   les lettres apparaissent, l'une après l'autre. Le logo n'est ni redessiné
   ni retouché : on ne fait que choisir ce qu'on en montre à chaque instant.

   La marque se dessine, trait par trait, avant de paraître.

   Les contours ont été relevés sur le fichier lui-même — seuil sur les trois
   encres du PNG (le rouge du fond, le blanc du lettrage, le noir de la
   sous-ligne, plus le filet à 30 % qui les sépare), suivi de contour au
   pixel, puis simplification à 0,75 px. Le relevé n'est donc pas un
   redessin : c'est le tracé du fichier, aux mêmes proportions. Il compte
   65 glyphes, un par forme fermée — les quatre pièces du symbole, les huit
   lettres de POLYENCO, les deux lignes de texte, le filet.

   Chaque glyphe porte son propre départ (--d) et sa propre durée (--t),
   écrits dans le HTML par le relevé. L'ordre est celui de la lecture :
   le symbole d'abord, sans se presser, puis POLYENCO lettre à lettre —
   c'est le temps que l'œil suit —, puis les petites lignes et le filet.

   Le tracé se fait en blanc, d'un seul trait fin, comme l'épure du hero
   (§ 08). Les couleurs ne viennent qu'à la fin, et elles viennent du
   fichier : l'épure s'efface pendant que le PNG paraît. Le logo sur lequel
   l'écran s'arrête est donc bien l'original, pas le relevé.

   Minutage — 2,0 s en tout :
     0,00 → 1,15 s   les 65 glyphes se tracent, dans l'ordre de lecture
     1,15 → 1,35 s   le fichier paraît, l'épure s'efface
     1,35 s          le hero et le défilement reprennent la main (js/main.js)
     1,40 → 2,00 s   le rouge s'efface, la marque avance d'un souffle

   Le tracé prend 1,15 s et non 0,9 : à 65 glyphes, plus vite, le mouvement
   se lisait comme un scintillement plutôt que comme un tracé. Les trois
   minutages se tiennent — PACE dans tools/trace-logo.js, les pourcentages
   ci-dessous, et les deux constantes de js/main.js. Changer la durée totale
   demande de reprendre les trois.

   L'écran se rejoue à chaque chargement de l'accueil — rafraîchissement,
   Ctrl+R, F5, retour à l'accueil depuis une page intérieure. Rien n'est
   retenu d'une fois sur l'autre : ni session, ni cookie, ni drapeau d'aucune
   sorte. L'animation repart donc toujours de son premier instant.

   Tout le mouvement est ici, en CSS. Le JavaScript ne fait que poser la
   classe avant le premier rendu, puis retirer le bloc du document une fois
   l'animation finie. Sans JavaScript, la classe n'est jamais posée :
   l'écran n'existe pas et l'accueil s'affiche normalement.

   Nommage : « entrance » et non « intro ». La classe .intro appartient déjà
   au bloc de présentation du § 20 (accueil et page À propos) ; les deux ne
   doivent jamais se croiser.
   ========================================================================== */

/* Sans la classe posée par le <head>, le bloc ne pèse rien : ni affichage,
   ni place, ni gêne au défilement. C'est aussi ce qui le neutralise si le
   nettoyage de js/main.js n'a pas lieu. */
.entrance { display: none; }

html.entrance-on .entrance {
  display: flex;
  align-items: center;
  justify-content: center;
  position: fixed;
  inset: 0;
  z-index: 1000;              /* au-dessus de tout, menu mobile compris */
  padding: var(--gutter);
  background: var(--polyenco-red);
  animation: entrance-lift 2s linear both;
}

/* Le défilement est suspendu le temps de l'écran — 2 s suffisent largement à un
   coup de molette, et le visiteur défilerait à l'aveugle.

   Suspendre le défilement retire la barre : la page s'élargit d'une
   quinzaine de pixels, puis rétrécit quand elle revient. On rend donc la
   main à l'instant précis où le fondu commence (classe .entrance-go, posée
   par js/main.js) : le rouge est alors encore opaque et couvre le
   remaniement. Réserver la gouttière plutôt que la masquer ne marcherait
   pas : un écran en position fixe s'arrête au bord de la gouttière, et il
   resterait un liseré sombre le long du rouge. */
html.entrance-on:not(.entrance-go) body { overflow: hidden; }

/* La marque garde ses proportions quelle que soit la place disponible :
   jamais recadrée, jamais déformée, jamais agrandie au-delà de son fichier.
   Les trois bornes se lisent ainsi — 420 px, sa taille de confort ; 68 vw,
   la largeur d'un téléphone ; 115 vh, la hauteur d'un téléphone couché
   (le logo mesure 620 × 324, donc 60 vh de haut vaut 115 vh de large). */
html.entrance-on .entrance__mark {
  position: relative;
  display: block;
  width: min(420px, 68vw, 115vh);
  animation: entrance-mark 2s both;
}
html.entrance-on .entrance__logo {
  display: block;
  width: 100%;
  height: auto;
  animation: entrance-logo 2s both;
}

/* L'épure, posée exactement sur le fichier : même boîte, mêmes proportions,
   le viewBox du relevé étant celui du PNG (620 × 324). */
html.entrance-on .entrance__draw {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  animation: entrance-draw-out 2s both;
}

/* Le trait reste un cheveu quelle que soit la taille d'affichage : c'est
   tout l'objet de non-scaling-stroke. --d et --t viennent du HTML, un par
   glyphe ; les longhands évitent d'avoir à les passer par un calc(). */
html.entrance-on .entrance__draw path {
  fill: none;
  stroke: rgba(255, 255, 255, .9);
  stroke-width: 1.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation-name: entrance-draw;
  animation-duration: var(--t, 300ms);
  animation-delay: var(--d, 0ms);
  animation-timing-function: var(--ease);
  animation-fill-mode: both;
}

/* L'écran tient, puis s'efface. Le fondu est linéaire : sur un aplat uni,
   toute autre courbe se verrait comme un à-coup. 70 % de 2 s = 1,40 s. */
@keyframes entrance-lift {
  0%, 70% { opacity: 1; }
  100%    { opacity: 0; visibility: hidden; }
}

/* Le trait qui court. pathLength="1" est posé sur chaque glyphe par le
   relevé : la longueur réelle du contour n'a donc pas à être connue ici,
   un tiret de 1 couvre exactement le tracé, quel qu'il soit. Un glyphe
   percé (le O, le P, le B) est un seul chemin à deux boucles : le contour
   extérieur se trace, puis le contre-poinçon. */
@keyframes entrance-draw {
  to { stroke-dashoffset: 0; }
}

/* Passage de l'épure au fichier. Les deux fondus se croisent : le PNG est
   déjà à mi-chemin quand l'épure commence à partir, si bien qu'à aucun
   instant la marque ne s'allège. 57,5 % de 2 s = 1,15 s, 67,5 % = 1,35 s. */
@keyframes entrance-draw-out {
  0%, 58.5%   { opacity: 1; }
  67.5%, 100% { opacity: 0; }
}

@keyframes entrance-logo {
  0%, 57.5%   { opacity: 0; }
  67.5%, 100% { opacity: 1; }
}

/* La marque avance d'un souffle pendant que le rouge s'efface : le regard
   est déjà porté vers la page quand l'écran disparaît. */
@keyframes entrance-mark {
  0%, 70% { transform: scale(1); animation-timing-function: cubic-bezier(.4, 0, 1, 1); }
  100%    { transform: scale(1.03); }
}

/* Le hero joue son entrée pour le visiteur, pas derrière l'écran rouge : ses
   animations attendent, puis démarrent au moment où le rouge commence à
   s'effacer (classe posée par js/main.js). Rien n'est modifié au § 08 : seul
   le départ est repoussé de 1,35 s. */
html.entrance-on:not(.entrance-go) .hero__media,
html.entrance-on:not(.entrance-go) .hero__inner > *,
html.entrance-on:not(.entrance-go) .hero__line,
html.entrance-on:not(.entrance-go) .setout-draw,
html.entrance-on:not(.entrance-go) .setout-draw path {
  animation-play-state: paused;
}


/* ==========================================================================
   06 — EN-TÊTE
   ========================================================================== */

.site-header {
  position: sticky;
  top: 0;
  z-index: 90;
  background: rgba(10,10,11,.9);
  -webkit-backdrop-filter: blur(14px);
          backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: 32px;
  min-height: var(--header-h);
}

/* --- Logo officiel --- */
.brand { display: block; flex: none; margin-right: auto; transition: opacity .25s var(--ease); }
.brand img { width: 152px; }
.brand__symbol { display: none; width: 46px; }
.brand:hover { opacity: .82; }

/* --- Navigation --- */
.nav__list { display: flex; align-items: center; gap: 34px; }

.nav__link {
  position: relative;
  display: block;
  padding-block: 8px;
  font-family: var(--font-heading);
  font-size: .75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--track-nav);
  color: var(--text-soft);
  transition: color .25s var(--ease);
}
/* L'état actif ne se signale pas par un trait, mais par le flanc denté du
   symbole : la même matière usinée, réduite à 4 px sous le mot. */
.nav__link::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0;
  height: var(--tooth-depth);
  background: var(--polyenco-red);
  -webkit-mask-image: var(--tooth-mask);
          mask-image: var(--tooth-mask);
  -webkit-mask-repeat: repeat-x;
          mask-repeat: repeat-x;
  -webkit-mask-size: var(--tooth-pitch) var(--tooth-depth);
          mask-size: var(--tooth-pitch) var(--tooth-depth);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .35s var(--ease);
}
.nav__link:hover { color: var(--text); }
.nav__link:hover::after { transform: scaleX(1); }

.nav__link[aria-current="page"] { color: var(--text); }
.nav__link[aria-current="page"]::after { transform: scaleX(1); }

.site-header__actions { display: flex; align-items: center; gap: 14px; }

/* --- Sélecteur de langue FR | EN --- */
.lang-switch { display: flex; align-items: center; }
.lang-switch a {
  display: block;
  padding: 8px 12px;
  font-family: var(--font-heading);
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: var(--track-nav);
  line-height: 1.4;
  color: var(--text-muted);
  transition: color .25s var(--ease);
}
.lang-switch a + a { border-left: 1px solid var(--line-strong); }
.lang-switch a:hover { color: var(--text); }
.lang-switch a[aria-current="true"] {
  color: var(--text);
  text-decoration: underline;
  text-decoration-color: var(--polyenco-red);
  text-decoration-thickness: 2px;
  text-underline-offset: 7px;
}


/* ==========================================================================
   07 — MENU MOBILE (100 % CSS, sans JavaScript)
   ========================================================================== */

/* La case à cocher pilote l'ouverture ; elle reste focusable au clavier. */
.nav-toggle {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip: rect(0 0 0 0); clip-path: inset(50%);
  overflow: hidden;
}

.burger {
  display: none;
  width: 48px; height: 48px;
  border: 1px solid var(--line-strong);
  cursor: pointer;
  position: relative;
}
.burger span {
  position: absolute; left: 14px; right: 14px; height: 1.5px;
  background: var(--text);
  transition: transform .3s var(--ease), opacity .2s var(--ease);
}
.burger span:nth-child(1) { top: 17px; }
.burger span:nth-child(2) { top: 23px; }
.burger span:nth-child(3) { top: 29px; }

.nav-toggle:focus-visible ~ .site-header .burger { outline: 2px solid var(--polyenco-red-light); outline-offset: 3px; }
.nav-toggle:checked ~ .site-header .burger span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav-toggle:checked ~ .site-header .burger span:nth-child(2) { opacity: 0; }
.nav-toggle:checked ~ .site-header .burger span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

.nav-scrim {
  position: fixed; inset: 0; z-index: 95;
  background: rgba(0,0,0,.72);
  opacity: 0; pointer-events: none;
  transition: opacity .35s var(--ease);
}
.nav-toggle:checked ~ .nav-scrim { opacity: 1; pointer-events: auto; }

.mobile-nav {
  position: fixed;
  top: 0; right: 0; bottom: 0; z-index: 96;
  width: min(400px, 88vw);
  padding: 28px var(--gutter) 40px;
  background: var(--polyenco-anthracite);
  border-left: 1px solid var(--line);
  transform: translateX(102%);
  transition: transform .4s var(--ease);
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex; flex-direction: column;
}
.nav-toggle:checked ~ .mobile-nav { transform: translateX(0); }

.mobile-nav__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; padding-bottom: 26px; border-bottom: 1px solid var(--line);
}
.mobile-nav__head img { width: 120px; }

.mobile-nav__close {
  width: 46px; height: 46px; flex: none;
  border: 1px solid var(--line-strong);
  cursor: pointer;
  display: grid; place-items: center;
  color: var(--text);
}
.mobile-nav__close svg { width: 17px; height: 17px; }
.mobile-nav__close:hover { border-color: #fff; }

.mobile-nav__list { padding-block: 12px; }
.mobile-nav__list li { border-bottom: 1px solid var(--line); }

.mobile-nav__link {
  position: relative;
  display: flex; align-items: baseline; gap: 16px;
  padding: 18px 2px;
  transition: padding-left .3s var(--ease);
}
.mobile-nav__link:hover { padding-left: 10px; }

.mobile-nav__label {
  font-family: var(--font-heading);
  font-size: 1.1rem; font-weight: 700;
  letter-spacing: -.005em;
}
/* Même signal que dans la navigation de bureau et dans la liste des
   solutions : l'arête dentée, ici dressée sur deux pas. */
.mobile-nav__link[aria-current="page"] { padding-left: 18px; }
.mobile-nav__link[aria-current="page"]::before {
  content: "";
  position: absolute; left: 0; top: 50%;
  width: var(--tooth-depth);
  height: calc(var(--tooth-pitch) * 2);
  margin-top: calc(-1 * var(--tooth-pitch));
  background: var(--polyenco-red);
  -webkit-mask-image: var(--tooth-mask-v);
          mask-image: var(--tooth-mask-v);
  -webkit-mask-repeat: repeat-y;
          mask-repeat: repeat-y;
  -webkit-mask-size: var(--tooth-depth) var(--tooth-pitch);
          mask-size: var(--tooth-depth) var(--tooth-pitch);
}

.mobile-nav__foot { margin-top: auto; padding-top: 28px; }
.mobile-nav__contact {
  margin-top: 22px;
  font-size: .9rem;
  color: var(--text-soft);
  line-height: 1.9;
}
.mobile-nav__contact .label { display: block; color: var(--text); margin-bottom: 8px; }
.mobile-nav__contact a { color: var(--text); border-bottom: 1px solid var(--polyenco-red); }

.lang-switch--panel a { font-size: .8rem; padding-block: 10px; }
.lang-switch--panel a:first-child { padding-left: 0; }


/* ==========================================================================
   08 — HERO
   ========================================================================== */

.hero {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: flex-end;
  min-height: min(calc(100vh - var(--header-h)), 980px);
  min-height: min(calc(100svh - var(--header-h)), 980px);
  padding-block: clamp(64px, 9vh, 150px) clamp(36px, 6vh, 72px);
  overflow: hidden;
}

.hero--compact {
  min-height: auto;
  padding-block: clamp(72px, 8.5vw, 112px);
  align-items: center;
}

/* Entrée du visuel : léger recul de l'image, une seule fois */
.hero__media {
  position: absolute; inset: 0; z-index: -2;
  background: var(--bg);
  animation: hero-settle 2.6s var(--ease-out) both;
}
.hero__media img {
  width: 100%; height: 100%; object-fit: cover;
  filter: saturate(.82) contrast(1.06);
}

/* Vidéo d'accueil posée sur la photo : tant que la vidéo n'a pas démarré
   (ou si elle ne peut pas se lancer), c'est la photo qui reste visible.
   Cadrage décalé à droite pour garder le casque POLYENCO dans le champ. */
.hero__media--video { overflow: hidden; }
.hero__media--video img,
.hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 70% 50%;
  filter: saturate(.82) contrast(1.06);
}

/* Voile : dense à gauche sous le texte, ouvert à droite sur l'image,
   fondu vers le noir en bas pour enchaîner avec la section suivante. */
.hero::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(90deg, rgba(10,10,11,.94) 0%, rgba(10,10,11,.78) 34%, rgba(10,10,11,.30) 68%, rgba(10,10,11,.12) 100%),
    linear-gradient(0deg, #0A0A0B 0%, rgba(10,10,11,.55) 22%, rgba(10,10,11,0) 55%),
    linear-gradient(180deg, rgba(10,10,11,.6) 0%, rgba(10,10,11,0) 22%);
}
.hero--compact::before {
  background:
    linear-gradient(90deg, rgba(10,10,11,.94) 0%, rgba(10,10,11,.8) 45%, rgba(10,10,11,.45) 100%),
    linear-gradient(0deg, rgba(10,10,11,.85) 0%, rgba(10,10,11,0) 60%);
}

/* Réalisations : hero plus haut, titre éditorial calé en bas à gauche */
.hero--editorial {
  min-height: min(calc(72vh - var(--header-h)), 660px);
  min-height: min(calc(72svh - var(--header-h)), 660px);
  align-items: flex-end;
  padding-block: clamp(88px, 10vh, 120px) clamp(44px, 6vh, 68px);
}
.hero--editorial::before {
  background:
    linear-gradient(90deg, rgba(10,10,11,.9) 0%, rgba(10,10,11,.6) 45%, rgba(10,10,11,.28) 100%),
    linear-gradient(0deg, #0A0A0B 0%, rgba(10,10,11,0) 55%);
}
.hero--editorial .hero__title {
  max-width: none;
  font-size: clamp(1.75rem, min(8.6vw, 13vh), 7.2rem);
}

.hero__inner { position: relative; }

/* Taille bornée par la largeur ET la hauteur : sur un portable 1440 × 900,
   la ligne des étapes reste visible sans défiler. Le plancher (1.75rem) est
   calculé pour que « RÉALISATIONS » ou « CONSTRUCTION » tienne d'un seul
   tenant sur un écran de 320 px. */
.hero__title {
  max-width: 15ch;
  font-size: clamp(1.75rem, min(8vw, 11vh), 6.6rem);
  line-height: 1;
  letter-spacing: -.01em;
}
.hero__line { display: block; }
.hero__dot  { color: var(--polyenco-red); }

/* L'accroche du hero : une seule colonne de lecture pour tous les heros.
   Elle valait 50ch, ramenés à 46ch sur les pages intérieures — soit 543 px
   à 1440 px. Or les accroches françaises de quatre pages mesurent plus que
   cela d'un seul tenant, et leur traduction anglaise, moins :

     Solutions               647 px (FR)  /  529 px (EN)
     Réalisations            616 px (FR)  /  526 px (EN)
     Traitement de surface   597 px (FR)  /  461 px (EN)
     Contact                 594 px (FR)  /  495 px (EN)

   La même phrase tenait donc sur une ligne en anglais et se cassait en
   deux en français, la seconde ligne réduite à deux ou trois mots. Ce
   n'est pas le repli qui était voulu, c'est le plafond qui était posé
   quelques caractères trop bas.

   58 ch — la mesure de lecture du reste du site — les couvrent toutes et
   laissent replier celles qui le méritent vraiment : l'accroche de la page
   À propos fait 861 px en français et 807 px en anglais, elle garde ses
   deux lignes dans les deux langues. */
/* Quand l'accroche dépasse vraiment la colonne, les deux lignes se
   partagent le texte au lieu de laisser un reste de deux mots : à 1920 px,
   l'accroche anglaise de la page À propos tombait en 702 px + 121 px. Même
   équilibrage que les titres du § 03 — il ne change jamais le nombre de
   lignes, seulement l'endroit où elles se coupent. */
.hero__text {
  max-width: 58ch;
  margin-top: clamp(24px, 3.4vh, 36px);
  font-size: clamp(1.02rem, 1.3vw, 1.2rem);
  color: var(--text-soft);
  text-wrap: balance;
}

.hero__actions {
  display: flex; flex-wrap: wrap; gap: 14px;
  margin-top: clamp(32px, 4.4vh, 44px);
}

/* --------------------------------------------------------------------------
   LE BLOC DE MARQUE SOUS LE TITRE
   Le logo officiel empile « POLYENCO », puis « POLY ENGINEERING COMPANY »,
   puis un filet, puis la ligne de métier. On reprend exactement cette
   construction : le titre tient le rôle du wordmark, les deux lignes et
   leur filet suivent. Rien n'est ajouté, rien n'est inventé.
   -------------------------------------------------------------------------- */
.hero__lockup {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  /* Le lockup se cale sur son contenu, mais jamais au-delà de la colonne :
     sur un écran étroit le métier passe à la ligne, entre deux mots. */
  max-width: 100%;
  margin-top: clamp(26px, 3.6vh, 42px);
}
.hero__activity,
.hero__place {
  font-family: var(--font-heading);
  text-transform: uppercase;
  line-height: 1.4;
}
/* Le métier porte la signature : c'est la ligne forte du lockup. */
.hero__activity {
  font-size: clamp(.78rem, 1.02vw, .94rem);
  font-weight: 700;
  letter-spacing: .18em;   /* l'espacement du lockup officiel */
  color: var(--text);
}
/* L'adresse reste en retrait : plus petite, plus sourde. */
.hero__place {
  position: relative;
  margin-top: 13px;
  padding-top: 13px;
  font-size: clamp(.68rem, .82vw, .78rem);
  font-weight: 600;
  letter-spacing: .14em;
  color: var(--text-muted);
}
/* Le filet du logo, entre le métier et l'adresse. */
.hero__place::before {
  content: "";
  position: absolute; top: 0; left: 0; right: 0;
  height: var(--rail-w);
  background: var(--line-strong);
}


/* --------------------------------------------------------------------------
   SIGNATURE POLYENCO — LA MISE AU NET
   Une seule animation sur tout le site, et elle raconte le métier :

     1. les deux filets du symbole PEC s'établissent      (la géométrie)
     2. lignes d'implantation, axe, flanc denté            (l'étude)
     3. le portique acier se trace par-dessus              (l'ouvrage)
     4. un repère rouge se pose sur le faîtage             (le contrôle)

   Elle dure le temps que la photo finisse de se poser, puis retombe à
   l'état de résidu : à la fin, il ne reste qu'un calque très pâle. Aucun
   volume, aucune perspective, aucune rotation — que des traits.
   -------------------------------------------------------------------------- */
.setout-draw {
  position: absolute;
  z-index: 0;                 /* au-dessus du voile, sous le texte */
  top: 50%;
  right: clamp(-60px, 1vw, 60px);
  transform: translateY(-50%);
  width: min(56vh, 44vw, 600px);
  height: auto;
  aspect-ratio: 1;
  pointer-events: none;
  opacity: .8;
  animation: sd-settle 1.8s var(--ease) 3.1s both;
}

.setout-draw path {
  fill: none;
  stroke-linecap: square;
  stroke-linejoin: miter;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: sd-draw 1s var(--ease) both;
}

.sd-line  { stroke: rgba(255,255,255,.55); stroke-width: 1; }
.sd-hair  { stroke: rgba(255,255,255,.30); stroke-width: 1; }
.sd-rack  { stroke: rgba(255,255,255,.62); stroke-width: 1.5; }
.sd-steel { stroke: rgba(255,255,255,.78); stroke-width: 2; }

/* L'axe est déjà en pointillés : on ne peut pas le tracer au dash, les deux
   dasharray se contrarieraient. Il paraît donc en fondu. */
.setout-draw .sd-axis {
  stroke: rgba(255,255,255,.34);
  stroke-width: 1;
  stroke-dasharray: 7 7;
  stroke-dashoffset: 0;
  animation: sd-point .7s var(--ease) 1.15s both;
}

/* Le seul rouge du hero, avec les points du titre : le repère posé sur le
   nœud de faîtage. Pan coupé, comme les extrémités du bandeau PEC. */
.setout-draw .sd-node {
  fill: var(--polyenco-red);
  stroke: none;
  stroke-dasharray: none;
  animation: sd-point .6s var(--ease) 3s both;
}

.setout-draw .sd-rails path         { animation-delay: .25s; animation-duration: .95s; }
.setout-draw .sd-tech  .sd-hair     { animation-delay: .95s; animation-duration: .7s; }
.setout-draw .sd-rack               { animation-delay: 1.35s; animation-duration: .9s; }
.setout-draw .sd-frame .sd-steel    { animation-delay: 1.95s; animation-duration: 1.05s; }
.setout-draw .sd-frame .sd-steel + .sd-steel { animation-delay: 2.35s; }
.setout-draw .sd-frame .sd-hair     { animation-delay: 2.65s; animation-duration: .5s; }

@keyframes sd-draw   { to { stroke-dashoffset: 0; } }
@keyframes sd-point  { from { opacity: 0; } to { opacity: 1; } }
/* Le tracé se fait à pleine force, puis s'efface sans disparaître : la
   photo reprend la main, le calque reste lisible sur le ciel comme sur
   l'ombre. Les traits portent déjà leur propre transparence — inutile de
   la cumuler deux fois, on tomberait sous le seuil du visible. */
@keyframes sd-settle { from { opacity: .8; } to { opacity: .45; } }

/* Fil d'Ariane */
.breadcrumb {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin-bottom: 26px;
  color: var(--text-soft);
}
.breadcrumb li { display: flex; align-items: center; gap: 10px; }
.breadcrumb li + li::before { content: "/"; color: var(--text-muted); }
.breadcrumb a:hover { color: var(--text); }

/* --- Animation d'entrée : montée courte et fondu, décalés --- */
@keyframes rise {
  from { opacity: 0; transform: translateY(26px); }
  to   { opacity: 1; transform: none; }
}
@keyframes hero-settle {
  from { opacity: 0; transform: scale(1.06); }
  35%  { opacity: 1; }
  to   { opacity: 1; transform: scale(1); }
}

.hero__inner > *,
.hero__line { animation: rise 1s var(--ease-out) both; }
.hero__inner > * { animation-delay: .15s; }
.hero__inner > .hero__title { animation: none; }

.hero .section-label        { animation-delay: .2s; }
.hero h1                     { animation-delay: .32s; }
.hero .hero__line:nth-child(1) { animation-delay: .3s; }
.hero .hero__line:nth-child(2) { animation-delay: .42s; }
.hero .hero__line:nth-child(3) { animation-delay: .54s; }
.hero .hero__text           { animation-delay: .62s; }
.hero .hero__lockup         { animation-delay: .7s; }
.hero .hero__actions        { animation-delay: .84s; }


/* ==========================================================================
   09 — RÉVÉLATION AU DÉFILEMENT
   Activée seulement si le script est chargé (classe .js sur <html>).
   ========================================================================== */

.js .reveal {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 1s var(--ease-out), transform 1s var(--ease-out);
}
.js .reveal.is-in { opacity: 1; transform: none; }


/* ==========================================================================
   11 — DE L'ÉTUDE AU CHANTIER (processus en trois étapes)
   Un filet très fin relie les trois étapes ; le rouge n'est qu'un accent,
   réduit à trois points qui s'allument l'un après l'autre à l'apparition
   de la section. Intitulé et phrase forment un seul bloc sous la photo.
   ========================================================================== */

.process {
  --pgap: clamp(20px, 2.4vw, 40px);
  --pstep: .38s;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: var(--pgap);
  margin-top: clamp(36px, 4vw, 56px);
}

.process__step { position: relative; display: flex; flex-direction: column; }

/* Hauteur contenue : la section se parcourt d'un regard sans occuper
   l'accueil entier. */
.process__media {
  overflow: hidden;
  aspect-ratio: 5/4;
  border-radius: var(--radius);
  background: var(--bg-steel);
}
.process__media img {
  width: 100%; height: 100%; object-fit: cover;
  filter: grayscale(.5) contrast(1.04) brightness(.86);
  transition: filter .5s var(--ease);
}
/* Survol : la photo s'éclaircit, l'intitulé passe au blanc. Pas de zoom. */
.process__step:hover .process__media img { filter: grayscale(0) contrast(1.02) brightness(1); }
.process__step:hover .process__name { color: var(--text); }

/* L'étude est un rendu 3D sur fond blanc, seule image du processus qui ne
   vienne pas du chantier : un voile de la couleur de la section éteint ce
   blanc pour qu'il ne domine pas la rangée. Le fichier n'est pas touché ;
   le voile s'efface au survol comme les photos s'éclaircissent. */
.process__media--render { position: relative; }
.process__media--render img { filter: grayscale(.32) contrast(.92) brightness(.9); }
.process__media--render::after {
  content: "";
  position: absolute; inset: 0;
  background: var(--bg-raise);
  opacity: .33;
  transition: opacity .5s var(--ease);
}
.process__step:hover .process__media--render img { filter: grayscale(0) contrast(1) brightness(1); }
.process__step:hover .process__media--render::after { opacity: .14; }

/* Un seul filet traverse les trois étapes, d'un bord à l'autre de la
   grille : c'est la même ligne, pas quatre traits alignés. */
.process__rail { position: relative; display: block; height: 8px; margin-block: 20px 16px; }
.process__rail::before {
  content: "";
  position: absolute; bottom: 0; left: 0;
  right: calc(-1 * var(--pgap));
  height: var(--rail-w);
  background: var(--line);
}
.process__step:last-child .process__rail::before { right: 0; }

/* Le jalon n'est pas une pastille : c'est une dent posée sur le filet,
   base contre la matière, exactement comme sur le flanc du symbole.
   Les trois s'allument l'une après l'autre à l'apparition de la section. */
.process__node {
  position: absolute; left: 0; bottom: var(--rail-w); z-index: 1;
  width: 9px; height: 6px;
  background: var(--line-strong);
  -webkit-clip-path: var(--tooth-clip);
          clip-path: var(--tooth-clip);
  transition: background-color .3s var(--ease);
  transition-delay: calc(var(--i, 0) * var(--pstep) + .3s);
}
.process.is-in .process__node,
html:not(.js) .process__node { background: var(--polyenco-red); }

.process__name {
  font-family: var(--font-heading);
  font-size: clamp(1.1rem, 1.5vw, 1.35rem);
  font-weight: 900;
  line-height: 1.15;
  text-transform: uppercase;
  letter-spacing: -.005em;
  color: var(--text-soft);
  transition: color .3s var(--ease);
}
.process__text {
  margin-top: 6px;
  max-width: 30ch;
  font-size: .95rem;
  line-height: 1.6;
  color: var(--text-soft);
}

/* ==========================================================================
   12 — LA PRÉCISION DANS LE DÉTAIL (visuel technique)
   Le relevé est dessiné en SVG par-dessus une photo au format 16:9 exact :
   le repère « Détail A » reste donc calé sur l'assemblage photographié,
   quelle que soit la largeur de l'écran.
   ========================================================================== */

.precision { overflow: hidden; }
.precision::before {
  content: "";
  position: absolute; inset: 0;
  background-image:
    linear-gradient(rgba(255,255,255,.035) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.035) 1px, transparent 1px);
  background-size: 64px 64px;
  -webkit-mask-image: radial-gradient(ellipse 60% 70% at 72% 50%, #000 0%, transparent 100%);
          mask-image: radial-gradient(ellipse 60% 70% at 72% 50%, #000 0%, transparent 100%);
  pointer-events: none;
}

.precision__grid {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(40px, 5vw, 88px);
  align-items: center;
}

.spec-list { margin-top: 32px; border-top: 1px solid var(--line); }
.spec-list__row {
  display: grid;
  grid-template-columns: minmax(0, 11rem) 1fr;
  gap: 8px 24px;
  padding-block: 16px;
  border-bottom: 1px solid var(--line);
}
.spec-list dt { color: var(--text); }
.spec-list dd { font-size: .95rem; line-height: 1.6; color: var(--text-soft); }

.blueprint {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: var(--radius);
  background: #000;
  outline: 1px solid var(--line);
  outline-offset: 12px;
}
.blueprint img {
  width: 100%; height: 100%; object-fit: cover;
  filter: grayscale(1) contrast(1.15) brightness(.5);
  transform: scale(1.03);
  transition: filter .9s var(--ease), transform 1.6s var(--ease-out);
}
.blueprint:hover img { filter: grayscale(.5) contrast(1.1) brightness(.62); transform: scale(1); }

.blueprint__svg { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.bp-grid   { stroke: rgba(255,255,255,.12); stroke-width: 1; vector-effect: non-scaling-stroke; }
.bp-axis   { stroke: rgba(255,255,255,.55); stroke-width: 1; stroke-dasharray: 6 6; vector-effect: non-scaling-stroke; fill: none; }
.bp-corner { stroke: #fff; stroke-width: 1.5; vector-effect: non-scaling-stroke; fill: none; }
.bp-detail { stroke: var(--polyenco-red); stroke-width: 3; fill: none; }
.bp-leader { stroke: #fff; stroke-width: 2; fill: none; }

/* Tracé progressif à l'apparition */
.js .blueprint .bp-draw { stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset 1.4s var(--ease) .5s; }
.js .blueprint .bp-fade { opacity: 0; transition: opacity 1.2s var(--ease) .2s; }
.js .blueprint.is-in .bp-draw { stroke-dashoffset: 0; }
.js .blueprint.is-in .bp-fade { opacity: 1; }

.blueprint__tag {
  position: absolute;
  left: 74.5%; top: 50%;
  transform: translateY(-50%);
  padding: 8px 12px;
  background: rgba(10,10,11,.82);
  border-left: 2px solid var(--polyenco-red);
  border-radius: var(--radius-sm);
  white-space: nowrap;
}
.js .blueprint .blueprint__tag { opacity: 0; transition: opacity .8s var(--ease) 1.5s; }
.js .blueprint.is-in .blueprint__tag { opacity: 1; }
.blueprint__tag .label { display: block; color: var(--text); }
.blueprint__tag small { display: block; margin-top: 2px; font-size: .78rem; color: var(--text-soft); }


/* ==========================================================================
   13 — SOLUTIONS : liste à gauche, grand visuel à droite
   Le survol (ou le focus clavier) d'une ligne change l'image. Sur mobile,
   la même liste devient une pile de cartes image + texte.
   ========================================================================== */

.sol-explorer {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(32px, 4.5vw, 80px);
  align-items: stretch;
}

.sol-list { border-top: 1px solid var(--line-strong); }

.sol-item { position: relative; border-bottom: 1px solid var(--line-strong); }
/* La solution lue porte, sur son bord gauche, le flanc denté du symbole :
   l'arête se dresse du haut vers le bas quand la ligne devient active. */
.sol-item::before {
  content: "";
  position: absolute; left: 0; top: -1px; bottom: -1px;
  width: var(--tooth-depth);
  background: var(--polyenco-red);
  -webkit-mask-image: var(--tooth-mask-v);
          mask-image: var(--tooth-mask-v);
  -webkit-mask-repeat: repeat-y;
          mask-repeat: repeat-y;
  -webkit-mask-size: var(--tooth-depth) var(--tooth-pitch);
          mask-size: var(--tooth-depth) var(--tooth-pitch);
  transform: scaleY(0);
  transform-origin: top;
  transition: transform .5s var(--ease);
}
.sol-item.is-active::before { transform: scaleY(1); }

.sol-link { display: block; transition: background-color .5s var(--ease); }
.sol-item.is-active .sol-link { background: rgba(255,255,255,.03); }

.sol-link__media { display: none; overflow: hidden; border-radius: var(--radius) var(--radius) 0 0; }

.sol-link__body {
  display: block;
  padding: 24px 16px 24px 24px;
  transition: padding-left .5s var(--ease);
}
.sol-item.is-active .sol-link__body { padding-left: 34px; }

.sol-link__title {
  font-family: var(--font-heading);
  font-size: clamp(1.1rem, 1.5vw, 1.38rem);
  font-weight: 800;
  line-height: 1.25;
  text-transform: uppercase;
  letter-spacing: 0;
  color: var(--text-soft);
  transition: color .35s var(--ease);
}
.sol-item.is-active .sol-link__title,
.sol-link:hover .sol-link__title { color: var(--text); }

/* Le texte se déplie sous le titre actif */
.sol-link__more {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows .55s var(--ease);
}
.sol-item.is-active .sol-link__more { grid-template-rows: 1fr; }
.sol-link__more > span { display: block; min-height: 0; overflow: hidden; }

.sol-link__text {
  display: block;
  max-width: 46ch;
  padding-top: 10px;
  font-size: .95rem;
  line-height: 1.65;
  color: var(--text-soft);
}
.sol-stage {
  position: relative;
  min-height: 500px;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--bg-steel);
}
.sol-stage__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transform: scale(1.06);
  transition: opacity .8s var(--ease), transform 1.8s var(--ease-out);
}
.sol-stage__img.is-active { opacity: 1; transform: scale(1); }
.sol-stage::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  background: linear-gradient(0deg, rgba(10,10,11,.35) 0%, rgba(10,10,11,0) 40%);
  box-shadow: inset 0 0 0 1px var(--line);
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   SOLUTIONS : la version repliée, réservée au téléphone
   La composition du grand écran — la liste à gauche, le grand visuel à
   droite — tient jusqu'à 700 px et c'est elle qui vaut partout au-dessus :
   sur une tablette, la section garde donc exactement l'allure du bureau,
   simplement mise à l'échelle.

   En dessous, les deux colonnes ne tiennent plus : le grand visuel s'efface
   et chaque solution reprend sa propre vignette (mise en lignes éditoriales
   par le bloc 700 px plus bas). Les liens vers les pages dédiées ne changent
   jamais.

   Le repli ne dépend pas que de la largeur. Le grand visuel est piloté au
   survol : sur un écran tactile il n'y a pas de survol, donc il resterait
   figé sur la première solution. Une tablette reçoit donc la même version
   repliée qu'un téléphone, quelle que soit sa largeur.

   `any-pointer: coarse` est vrai dès qu'un doigt peut servir de pointeur —
   tablettes, téléphones, portables tactiles — et faux sur un poste à souris,
   qui garde donc le grand écran intact. La borne de 1366 px (iPad Pro en
   paysage) évite qu'un moniteur tactile de bureau bascule lui aussi.
   -------------------------------------------------------------------------- */
@media (max-width: 700px), (any-pointer: coarse) and (max-width: 1366px) {
  .sol-explorer { grid-template-columns: 1fr; }
  .sol-stage { display: none; }
  .sol-list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px;
    border-top: 0;
  }
  .sol-item {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--bg);
  }
  .sol-item::before { display: none; }
  .sol-link,
  .sol-item.is-active .sol-link { background: none; }
  .sol-link__media { display: block; }
  /* Une solution montre une vidéo au lieu d'une photo : même cadre, même
     recadrage. display:block supprime le liseré que laisserait un élément
     en ligne sous la vidéo. */
  .sol-link__media img,
  .sol-link__media video { width: 100%; aspect-ratio: 16/10; object-fit: cover; }
  .sol-link__media video { display: block; }
  .sol-link__body,
  .sol-item.is-active .sol-link__body { padding: 22px 22px 26px; }
  .sol-link__title { color: var(--text); }
  .sol-link__more { grid-template-rows: 1fr; }
}



/* ==========================================================================
   14 — PARTENAIRES : défilement continu des logos (100 % CSS)
   La piste contient deux séries identiques ; elle glisse de -50 %, soit
   exactement une série, puis recommence : la boucle est invisible.
   Chaque logo reçoit une largeur calculée pour occuper une surface
   visuelle équivalente (logos très larges comme logos carrés), sans
   jamais être déformé. Bande blanche : plusieurs logos ont un fond blanc
   intégré au fichier.
   ========================================================================== */

/* Le bloc porte les réglages : la bande et le repère de défilement placé
   dessous partagent la même durée, donc ils restent synchronisés. */
.partners {
  --logo-scale: 1;
  --logo-gap: 56px;
  --cell-w: 172px;
  --cell-h: 84px;
  --marquee-duration: 112s;
  margin-top: clamp(32px, 4vw, 48px);
}

/* La bande blanche : c'est l'élément visuel de la section. Elle est blanche
   parce que ces logos gardent leurs couleurs d'origine — beaucoup sont des
   tracés sombres, et plusieurs ont un fond blanc intégré au fichier. */
.marquee {
  border-radius: var(--radius);
  background: #fff;
}

/* Fondu sur les bords : les logos entrent et sortent en douceur.
   Le rembourrage est porté ici, et non par .marquee : overflow découpe à la
   limite de la boîte de rembourrage, donc un logo plus haut que sa case
   déborde dans ce blanc sans être coupé. C'est ce qui permet de rattraper
   les fichiers à large marge intégrée (voir la largeur posée par le HTML). */
.marquee__viewport {
  overflow: hidden;
  padding-block: clamp(20px, 2.4vw, 32px);
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 6%, #000 94%, transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0, #000 6%, #000 94%, transparent 100%);
}

.marquee__track {
  display: flex;
  width: max-content;
  animation: marquee var(--marquee-duration) linear infinite;
  will-change: transform;
}
.marquee:hover .marquee__track,
.marquee:hover ~ .marquee__progress span { animation-play-state: paused; }

.marquee__group {
  display: flex;
  align-items: center;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Une case de taille fixe par logo : les deux séries font exactement la
   même largeur, donc le retour à -50 % ne laisse jamais de vide.
   Marge à droite plutôt que gap, pour la même raison. */
.partner-logo {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: calc(var(--cell-w) * var(--logo-scale));
  height: calc(var(--cell-h) * var(--logo-scale));
  margin-right: var(--logo-gap);
}

/* Surface visuelle commune. Chaque logo reçoit dans le HTML une largeur
   calculée sur son rapport réel : w = √(A·r), soit la même aire A pour
   tous. Un logo carré et un logo cinq fois plus large que haut pèsent donc
   pareil à l'œil — plus de « petits » à côté de « gros ». Seule la largeur
   est imposée, la hauteur suit (height: auto) : les proportions d'origine
   sont exactes, rien n'est rogné ni étiré.

   Pas de max-height ici, et c'est volontaire : un plafond de hauteur
   écraserait la hauteur calculée alors que la largeur reste fixe, ce qui
   déformerait le logo. A est choisi pour que la plus grande hauteur
   obtenue (logo carré) tienne dans --cell-h. Aucun filtre : les couleurs
   d'origine sont intactes. */
.partner-logo img {
  width: calc(var(--w, 110px) * var(--logo-scale));
  height: auto;
  max-width: 100%;
  transition: transform .45s var(--ease);
}
.partner-logo:hover img { transform: scale(1.05); }

@keyframes marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* Repère de défilement : un trait qui parcourt sa glissière au rythme exact
   de la bande. Il indique où en est la boucle, il ne se clique pas — un
   défilé continu n'a pas de vues séparées à sélectionner. */
.marquee__progress {
  position: relative;
  overflow: hidden;
  width: 132px;
  height: 2px;
  margin: clamp(16px, 1.8vw, 22px) auto 0;
  border-radius: 2px;
  background: var(--line);
}
.marquee__progress span {
  position: absolute;
  inset: 0;
  display: block;
  width: 34%;
  border-radius: inherit;
  background: var(--polyenco-red);
  animation: marquee-progress var(--marquee-duration) linear infinite;
}
@keyframes marquee-progress {
  from { transform: translateX(-100%); }
  to   { transform: translateX(294%); }
}

/* Les durées suivent la longueur de la piste : les cases et les écarts se
   resserrent, la série raccourcit, la durée baisse — la vitesse perçue
   reste la même (~70 px/s) d'un écran à l'autre. La bande reste un défilé
   horizontal sur mobile, jamais une grille. */
@media (max-width: 1000px) {
  .partners {
    --logo-scale: .85; --logo-gap: 44px;
    --marquee-duration: 94s;
  }
}

@media (max-width: 700px) {
  .partners {
    --logo-scale: .72; --logo-gap: 32px;
    --marquee-duration: 75s;
  }
}

/* Mouvement réduit : la bande ne défile plus. Les logos s'enroulent alors
   dans la bande blanche — sans animation, une file unique laisserait la
   moitié des partenaires hors de l'écran, sans moyen de les atteindre. */
@media (prefers-reduced-motion: reduce) {
  .marquee__track { animation: none; width: auto; }
  .marquee__group { flex-wrap: wrap; justify-content: center; row-gap: 20px; width: 100%; }
  .marquee__group[aria-hidden="true"] { display: none; }
  .marquee__viewport { -webkit-mask-image: none; mask-image: none; }
  .marquee__progress { display: none; }
}


/* ==========================================================================
   15b — FILTRES (page Réalisations) ET APERÇU DES RÉALISATIONS (accueil)
   Filtres : sans script, la barre est masquée et tous les projets restent
   visibles. Aperçu d'accueil : grille de 12 colonnes à formats fixes, écrits
   dans le HTML (data-size) :
     a = grand (8 × 2)   b = petit (4 × 1)   f / g = paire (7 × 2 + 5 × 2)
   ========================================================================== */

.filters {
  display: flex;
  gap: 6px;
  margin-bottom: clamp(28px, 3.5vw, 44px);
  padding-bottom: 18px;
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
  scrollbar-width: none;
}
.filters::-webkit-scrollbar { display: none; }
html:not(.js) .filters { display: none; }

.filter {
  flex: none;
  display: inline-flex; align-items: center;
  min-height: 44px;
  padding: 0 18px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  cursor: pointer;
  font-family: var(--font-heading);
  font-size: .75rem; font-weight: 700;
  line-height: 1.2;
  text-transform: uppercase; letter-spacing: var(--track-nav);
  color: var(--text-soft);
  transition: color .25s var(--ease), border-color .25s var(--ease), background-color .25s var(--ease);
}
.filter:hover { color: var(--text); border-color: var(--line-strong); }
.filter.is-active {
  background: var(--polyenco-red);
  border-color: var(--polyenco-red);
  color: #fff;
}

.works {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  grid-auto-rows: clamp(170px, 17vw, 250px);
  grid-auto-flow: row dense;
  gap: clamp(10px, 1.2vw, 18px);
}

.work { position: relative; grid-column: span 4; grid-row: span 1; }
.work[data-size="a"]    { grid-column: span 8; grid-row: span 2; }
.work[data-size="f"]    { grid-column: span 7; grid-row: span 2; }
.work[data-size="g"]    { grid-column: span 5; grid-row: span 2; }

.work__figure {
  position: absolute; inset: 0;
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--bg-steel);
}
.work__figure img {
  width: 100%; height: 100%;
  object-fit: cover;
  filter: saturate(.85) contrast(1.03);
  transition: transform 1.1s var(--ease-out), filter .6s var(--ease);
}

/* Voile et légende : invisibles au repos, l'image seule occupe la case */
.work__figure::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(0deg, rgba(10,10,11,.9) 0%, rgba(10,10,11,.4) 42%, rgba(10,10,11,.18) 100%);
  opacity: 0;
  transition: opacity .5s var(--ease);
  pointer-events: none;
}
.work__caption {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 1;
  padding: clamp(18px, 2vw, 30px);
  opacity: 0;
  transform: translateY(12px);
  transition: opacity .5s var(--ease), transform .6s var(--ease);
}
.work__cat {
  display: inline-block;
  margin-bottom: 10px;
  color: var(--text);
}
.work__title {
  font-family: var(--font-heading);
  font-size: clamp(1.05rem, 1.45vw, 1.35rem);
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: -.01em;
  color: var(--text);
}
.work[data-size="a"] .work__title,
.work[data-size="f"] .work__title { font-size: clamp(1.3rem, 2.2vw, 2rem); }

.work:hover .work__figure img { transform: scale(1.03); filter: saturate(1) contrast(1); }
.work:hover .work__figure::after,
.work:hover .work__caption { opacity: 1; transform: none; }

/* Accueil : vignette cliquable vers la page Réalisations. Au clavier, le
   focus révèle la légende comme le survol. */
.work__link { position: absolute; inset: 0; display: block; }
.work__link:focus-visible { outline-offset: -3px; }
.work__link:focus-visible .work__figure img { transform: scale(1.03); filter: saturate(1) contrast(1); }
.work__link:focus-visible .work__figure::after,
.work__link:focus-visible .work__caption { opacity: 1; transform: none; }

/* Écrans tactiles : aucun survol requis, titre et catégorie toujours lisibles */
@media (hover: none) {
  .work__figure::after {
    opacity: 1;
    background: linear-gradient(0deg, rgba(10,10,11,.88) 0%, rgba(10,10,11,.3) 40%, rgba(10,10,11,0) 65%);
  }
  .work__caption { opacity: 1; transform: none; }
}

@media (max-width: 1000px) {
  /* Les filtres passent à la ligne au lieu de défiler.

     Sur grand écran les quatre tiennent sur une ligne et rien ne bouge. En
     dessous ils débordaient, et la barre les poussait hors de l'écran : un
     filtre qu'on ne voit pas est un filtre qui n'existe pas. Repliés sur
     deux rangées, ils se lisent tous d'un coup d'œil — aucun tronqué,
     aucun recouvert, et le rouge du filtre actif reste le seul rouge. */
  .filters {
    flex-wrap: wrap;
    overflow-x: visible;
    gap: 8px;
  }

  /* Dès qu'ils se replient, les filtres prennent l'aspect de pastilles.
     Sur une seule ligne, un filtre inactif peut rester du texte nu : la
     ligne elle-même fait le groupe, et le survol désigne celui qu'on vise.
     Réparti sur deux rangées, ce même texte nu se disperse et ne se lit
     plus comme une série de boutons. Un contour et un fond très discret
     redonnent à chacun son cadre. Le rouge POLYENCO reste réservé au
     filtre actif, qui tranche d'autant mieux sur ce gris.

     Même traitement du repli jusqu'au téléphone : un seul jeu de règles
     pour toute la plage où la barre passe à la ligne. */
  .filter {
    padding: 0 16px;
    border-color: var(--line-strong);
    background: rgba(255, 255, 255, .045);
    color: var(--text);
  }

  .works { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: clamp(190px, 30vw, 290px); }
  .work,
  .work[data-size] { grid-column: span 1; grid-row: span 1; }
  .work[data-size="a"] { grid-column: 1 / -1; grid-row: span 2; }
}

@media (max-width: 640px) {
  /* Les filtres n'ont plus de règle propre au téléphone : le repli et les
     pastilles valent pour toute la plage sous 1000 px (bloc ci-dessus).
     Le survol n'a rien à neutraliser ici — une pastille survolée prend
     exactement la couleur et le contour qu'elle a au repos, donc un
     :hover resté collé après un appui ne se voit pas. */

  /* L'aperçu garde les deux colonnes ouvertes à 1000 px : seules la hauteur
     de rangée et la gouttière se resserrent. La hauteur suit la largeur de
     l'écran plutôt qu'une valeur fixe — sur un petit téléphone, une rangée
     figée à 190 px donnerait des vignettes étroites et hautes, donc
     davantage recadrées. La vignette de tête garde sa pleine largeur :
     c'est elle qui annonce la page Réalisations. */
  .works { grid-auto-rows: clamp(150px, 46vw, 210px); gap: 10px; }
  .work__caption { padding: 12px; }
  .work__cat { margin-bottom: 4px; font-size: .66rem; letter-spacing: .06em; }
  .work__title { font-size: clamp(.82rem, 3.4vw, .95rem); line-height: 1.25; }
  .work[data-size="a"] .work__title,
  .work[data-size="f"] .work__title { font-size: clamp(1.05rem, 4.6vw, 1.4rem); }
}


/* ==========================================================================
   15ter — PROJET À LA UNE (ouvrage documenté, page Réalisations)
   Un ouvrage suivi de bout en bout, en quatre temps sur une seule ligne :
   le modèle de l'architecte, notre étude, la structure en fabrication,
   l'escalier installé. Rien de neuf dans le vocabulaire — c'est le
   processus de l'accueil (section 11) porté à quatre étapes, avec le
   filet de liaison passé au rouge POLYENCO.
   ========================================================================== */

/* Le projet à la une est rattaché à une catégorie de la galerie : il ne
   s'ouvre qu'avec son filtre (js/main.js pose .is-shown). Sans JavaScript
   les filtres ne fonctionnent pas — le bloc reste alors affiché. */
/* Le repère ne vaut plus pour la seule étude de cas : la vue aérienne s'y
   range aussi. Elle montre une ossature, donc elle n'a de sens que sous
   « Tous » et sous « Structure métallique » — son data-filter-only porte
   les deux filtres, séparés par une espace (voir js/main.js). Le sélecteur
   ne vise donc plus .case, mais tout bloc qui porte le repère. */
.js [data-filter-only] { display: none; }
.js [data-filter-only].is-shown { display: block; }

/* Le bloc peut recevoir le focus (le lecteur d'écran annonce alors son
   titre) : c'est un repère, pas un élément à encadrer. */
.case[tabindex]:focus { outline: none; }

/* En tête de catégorie : le bloc ouvre la galerie filtrée, juste sous la
   barre de filtres. Un filet le sépare des autres projets, qui suivent.
   Il vit dans le conteneur de la galerie : pas de rembourrage de section,
   seulement son propre pied. */
.case--lead {
  margin-bottom: clamp(40px, 5vw, 72px);
  padding-bottom: clamp(40px, 5vw, 72px);
  border-bottom: 1px solid var(--line);
}

/* Même correction qu'au bloc-titre de section (§ 04) : le plafond de 62ch
   était posé sur la boîte, donc mesuré à 16 px, alors que l'intitulé de
   l'ouvrage est composé en Montserrat 900 à 59 px. Relevé à 1440 px, boîte
   de 626 px, filtre « Pièces sur mesure » actif :

     « Escalier métallique sur mesure »   texte 1161 px  → 3 lignes
     « Custom steel staircase »           texte  852 px  → 2 lignes

   Le titre du hall (« Hall industriel », 564 px) tenait, celui de
   l'escalier non : le défaut ne se voyait que sous l'un des filtres. La
   colonne de lecture passe donc sur la phrase, où elle est mesurée dans la
   bonne police. */
.case__cat { display: block; margin-bottom: 12px; color: var(--text-soft); }
.case__lede { margin-top: 14px; max-width: 62ch; }

/* --------------------------------------------------------------------------
   LE PARCOURS DE L'OUVRAGE — quatre étapes sur une ligne
   Un seul filet rouge traverse les quatre colonnes, d'un bord à l'autre de
   la grille : c'est la même ligne, pas quatre traits alignés. Chaque étape
   y pose une dent du symbole, base contre le filet, puis son numéro — deux
   chiffres discrets, jamais un gros bandeau. Sous le filet, l'image au même
   cadrage pour les quatre (l'ouvrage est vertical : cadre en portrait),
   l'intitulé et une phrase.
   -------------------------------------------------------------------------- */

.flow {
  --fgap: clamp(18px, 2.2vw, 36px);
  --fstep: .38s;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: var(--fgap);
  margin-top: clamp(34px, 4vw, 56px);
  padding: 0;
  list-style: none;
}

.flow__step { position: relative; display: flex; flex-direction: column; }

/* Le filet et sa dent, au-dessus de l'image. Il déborde dans la gouttière
   jusqu'à l'étape suivante ; la dernière s'arrête à son propre bord. */
.flow__rail { position: relative; display: block; height: 8px; }
.flow__rail::before {
  content: "";
  position: absolute; top: 0; left: 0;
  right: calc(-1 * var(--fgap));
  height: var(--rail-w);
  background: rgba(170, 24, 25, .55);
}
.flow__step:last-child .flow__rail::before { right: 0; }

/* La dent s'allume l'une après l'autre à l'apparition de la section,
   comme sur le processus de l'accueil. */
.flow__node {
  position: absolute; top: var(--rail-w); left: 0; z-index: 1;
  width: 9px; height: 6px;
  background: var(--line-strong);
  -webkit-clip-path: var(--tooth-clip);
          clip-path: var(--tooth-clip);
  transition: background-color .3s var(--ease);
  transition-delay: calc(var(--i, 0) * var(--fstep) + .3s);
}
.flow.is-in .flow__node,
html:not(.js) .flow__node { background: var(--polyenco-red); }

.flow__num {
  display: block;
  margin: 10px 0 14px;
  font-family: var(--font-heading);
  font-size: .72rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: var(--track-label);
  color: var(--text-muted);
}

/* Cadrage commun aux quatre vues : deux rendus en paysage, deux photos de
   chantier en portrait, ramenés au même cadre pour que l'œil suive
   l'escalier d'une étape à l'autre. */
.flow__media {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 5;
  border-radius: var(--radius);
  background: var(--bg-steel);
}
.flow__media img {
  width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 50%;
  filter: grayscale(.42) contrast(1.03) brightness(.88);
  transition: filter .5s var(--ease);
}
.flow__step:hover .flow__media img { filter: grayscale(0) contrast(1.02) brightness(1); }
.flow__step:hover .flow__name { color: var(--text); }

/* L'étude est une capture d'écran sur fond clair, seule image de la rangée
   qui ne vienne ni du chantier ni du rendu : un voile de la couleur du fond
   l'éteint pour qu'elle ne brûle pas à côté des photos, et un léger
   agrandissement sort la barre d'onglets du cadre. */
.flow__media--render img {
  transform: scale(1.09);
  filter: grayscale(.34) contrast(.9) brightness(.66);
}
.flow__media--render::after {
  content: "";
  position: absolute; inset: 0;
  background: var(--bg);
  opacity: .34;
  transition: opacity .5s var(--ease);
}
.flow__step:hover .flow__media--render img { filter: grayscale(0) contrast(1) brightness(1); }
.flow__step:hover .flow__media--render::after { opacity: .12; }

.flow__name {
  margin-top: 18px;
  font-family: var(--font-heading);
  font-size: clamp(1rem, 1.25vw, 1.2rem);
  font-weight: 900;
  line-height: 1.15;
  text-transform: uppercase;
  letter-spacing: -.005em;
  color: var(--text-soft);
  transition: color .3s var(--ease);
}
.flow__say {
  margin-top: 6px;
  max-width: 32ch;
  font-size: .95rem;
  line-height: 1.6;
  color: var(--text-soft);
}

/* Repère de position du carrousel mobile (voir plus bas). Il n'existe que
   sous 620 px : au-dessus, les quatre temps sont tous à l'écran et n'ont
   rien à repérer. La règle est ici pour que le repère reste masqué même si
   js/main.js a laissé son bloc dans le document. */
.flow__dots { display: none; }

/* Tablette : les quatre temps restent sur une seule rangée.

   Repliés sur deux rangées de deux, ils se lisaient comme deux paires sans
   rapport — or c'est un parcours, et c'est la rangée qui le dit : l'étude,
   puis le dessin, puis l'acier, puis la pose, de gauche à droite. On garde
   donc les quatre colonnes et on resserre ce qui peut l'être : la
   gouttière, les intitulés, la phrase. Le cadre des photos ne change pas,
   il rétrécit avec sa colonne — aucune n'est recadrée.

   Le filet continue de déborder vers le temps suivant, comme sur grand
   écran : la règle de fin de rangée à deux colonnes n'a plus lieu d'être. */
@media (max-width: 980px) and (min-width: 621px) {
  .flow {
    --fgap: clamp(10px, 1.6vw, 20px);
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
  .flow__num { margin: 8px 0 10px; font-size: .68rem; }
  .flow__name {
    margin-top: 12px;
    font-size: clamp(.8rem, 1.5vw, 1.05rem);
  }
  .flow__say {
    margin-top: 5px;
    max-width: none;
    font-size: clamp(.76rem, 1.35vw, .9rem);
    line-height: 1.5;
  }
}

/* Mobile : les quatre temps deviennent une bande qui se fait glisser du
   pouce, un temps à la fois. Empilés, ils demandaient quatre écrans de
   défilement et se lisaient comme quatre sections sans rapport.

   Le défilement est celui du navigateur : scroll-snap et rien d'autre, pas
   de bibliothèque, pas de flèches. js/main.js n'ajoute que le repère de
   position sous la bande ; sans lui, la bande glisse exactement pareil.

   Un temps occupe toute la largeur : on n'en lit jamais deux à la fois.
   Deux choses disent que les quatre vues forment un seul parcours :
     • le filet rouge repart vers le temps suivant et sort de l'écran, comme
       sur grand écran — c'est lui qui annonce la suite ;
     • les quatre filets du repère, sous la bande.
   Mêmes cadres, mêmes numéros, mêmes dents qu'ailleurs : rien du
   vocabulaire de la section ne change, seule la direction de lecture. */
@media (max-width: 620px) {
  .flow {
    display: flex;
    grid-template-columns: none;
    column-gap: var(--fgap);
    /* Bord à bord : la bande sort de la gouttière, le temps suivant glisse
       donc depuis le bord de l'écran et non depuis une marge. */
    margin-inline: calc(-1 * var(--gutter));
    padding-inline: var(--gutter);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-padding-left: var(--gutter);
    /* Le geste ne doit pas entraîner la page derrière la bande. */
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    /* La barre de défilement ferait un trait gris sous la photo ; le repère
       de position la remplace. */
    scrollbar-width: none;
  }
  .flow::-webkit-scrollbar { display: none; }

  .flow__step {
    /* Pleine largeur : un temps à l'écran, jamais deux. La largeur de carte
       et la gouttière tombent juste — le dernier temps atteint exactement sa
       position d'accroche en fin de course, sans reste. */
    flex: 0 0 100%;
    scroll-snap-align: start;
    /* Un temps par geste : on ne saute pas de la conception au montage d'une
       chiquenaude, on suit le parcours. */
    scroll-snap-stop: always;
  }

  /* Le filet repart vers le temps suivant, hors cadre ; le dernier s'arrête
     à son propre bord, comme sur grand écran. */
  .flow__rail::before { right: calc(-1 * var(--fgap)); }
  .flow__step:last-child .flow__rail::before { right: 0; }

  /* La carte donne déjà sa largeur au texte. */
  .flow__say { max-width: none; }

  /* Repère de position : quatre filets courts, celui du temps lu en rouge.
     Le même vocabulaire que les filets de la section — ni pastilles, ni
     flèches, ni compteur. */
  .flow__dots {
    display: flex;
    justify-content: center;
    gap: 7px;
    margin: 20px 0 0;
    padding: 0;
    list-style: none;
  }
  .flow__dot {
    width: 24px;
    height: 3px;
    border-radius: 2px;
    background: var(--line-strong);
    transition: background-color .35s var(--ease);
  }
  .flow__dot.is-on { background: var(--polyenco-red); }
}


/* ==========================================================================
   15quater — BANDE CINÉMATIQUE (un ouvrage, plusieurs vues)
   Sept photos d'un même hall tenaient sur deux rangées de vignettes : plus
   de mille pixels de haut pour dire une seule chose. Elles passent ici dans
   un seul cadre, l'une après l'autre, en fondu — la hauteur est divisée par
   deux et l'unité de l'ouvrage se voit au lieu de se déduire.

   Aucune bibliothèque : le fondu est une transition d'opacité, et
   js/main.js ne fait qu'avancer une classe sur un intervalle. Sans le
   script, les sept vues se dépilent normalement, légendes comprises —
   rien n'est perdu.

   Pas de flèches, pas de pastilles : le repère est celui du parcours de
   l'escalier sur téléphone, des filets courts dont celui de la vue lue
   passe au rouge. Le même vocabulaire, au même endroit.
   ========================================================================== */
.cine { margin: clamp(30px, 3.6vw, 52px) 0 0; }

.cine__frame {
  position: relative;
  /* La largeur est déclarée : sans elle, le plafond de hauteur rétrécirait
     aussi la largeur pour tenir le 16/9, et la bande laisserait 300 px de
     vide à sa droite. Largeur fixée, c'est le rapport qui cède. */
  width: 100%;
  aspect-ratio: 16 / 9;
  /* Le plafond garde la bande à sa place sur un grand écran : sans lui, une
     fenêtre de 1900 px lui donnerait 950 px de haut et l'ouvrage reprendrait
     la place qu'on vient de lui retirer. Au-delà, la bande s'élargit au lieu
     de grandir — elle devient cinémascope, ce qui lui va. */
  max-height: min(72vh, 700px);
  overflow: hidden;
  border-radius: var(--radius);
  /* Le fond des cadres du site : il reste visible de part et d'autre de la
     seule vue en portrait, qui n'est pas recadrée. */
  background: var(--bg-steel);
}

.cine__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity .9s var(--ease);
}
.cine__slide.is-on { opacity: 1; }

.cine__slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* Six vues sur sept sont en 4/3 : le cadre en 16/9 les rogne du ciel et du
   sol, jamais de la structure. La septième — le contreventement de toiture
   — est la seule en portrait : la recadrer lui ôterait 58 % de sa hauteur,
   c'est-à-dire l'ouvrage lui-même. Elle s'affiche donc entière, posée sur
   le fond d'acier, plutôt que tronquée. */
.cine__slide--tall img { object-fit: contain; }

/* Le voile : il ne noircit que le bas, là où se lisent la légende et le
   compteur. Le haut de la photo n'est pas touché. */
.cine__veil {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(0deg,
    rgba(10, 10, 11, .78) 0%,
    rgba(10, 10, 11, .34) 26%,
    rgba(10, 10, 11, 0) 52%);
}

.cine__caption,
.cine__count {
  position: absolute;
  bottom: clamp(14px, 1.8vw, 26px);
  z-index: 1;
  margin: 0;
  pointer-events: none;
}
.cine__caption {
  left: clamp(16px, 2vw, 30px);
  max-width: 68%;
  opacity: 0;
  /* La légende ne suit pas l'opacité de sa vue : pendant les 0,9 s du
     fondu, les deux textes se superposeraient et aucun ne se lirait. Celle
     qui part s'efface tout de suite, celle qui arrive attend que l'image
     soit posée. Les deux ne se croisent jamais. */
  transition: opacity .3s var(--ease);
}
.cine__slide.is-on .cine__caption {
  opacity: 1;
  transition-delay: .6s;
}
.cine__count   { right: clamp(16px, 2vw, 30px); color: var(--text-muted); }

/* Le numéro en rouge, l'intitulé en blanc : le même partage que la dent
   rouge devant une catégorie de la galerie. */
.cine__num {
  margin-right: 10px;
  color: var(--polyenco-red-light);
}
.cine__name {
  font-family: var(--font-heading);
  font-size: clamp(.92rem, 1.5vw, 1.2rem);
  font-weight: 800;
  letter-spacing: -.01em;
  color: #fff;
}
.cine__at { color: var(--text); }

/* Repère de position : sept filets courts, celui de la vue lue en rouge.
   Repris tel quel du parcours de l'escalier sur téléphone. */
.cine__dots {
  display: flex;
  justify-content: center;
  gap: 7px;
  margin: 18px 0 0;
  padding: 0;
  list-style: none;
}
.cine__dot {
  width: 24px;
  height: 3px;
  border-radius: 2px;
  background: var(--line-strong);
  transition: background-color .5s var(--ease);
}
.cine__dot.is-on { background: var(--polyenco-red); }

/* Les deux boutons : rien tant qu'on ne survole pas le cadre. Au doigt, où
   le survol n'existe pas, la bande défile d'elle-même et ils ne servent à
   rien — ils restent donc cachés, sauf au clavier et en mouvement réduit,
   les deux cas où ils sont le seul moyen d'avancer. */
.cine__step {
  position: absolute;
  top: 50%;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  margin-top: -22px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, .28);
  border-radius: 50%;
  background: rgba(10, 10, 11, .46);
  color: #fff;
  cursor: pointer;
  opacity: 0;
  transition: opacity .35s var(--ease), background-color .35s var(--ease);
}
.cine__step svg { width: 18px; height: 18px; }
.cine__step--prev { left: clamp(10px, 1.4vw, 18px); }
.cine__step--next { right: clamp(10px, 1.4vw, 18px); }

.cine__frame:hover .cine__step,
.cine__step:focus-visible,
.cine.is-still .cine__step { opacity: 1; }

.cine__step:hover { background: rgba(10, 10, 11, .72); }
.cine__step:focus-visible {
  outline: 2px solid var(--polyenco-red-light);
  outline-offset: 2px;
}

/* Sur un écran tactile il n'y a pas de survol : les boutons resteraient
   affichés en permanence par le :hover collant d'un appui. On ne les montre
   donc que lorsqu'ils servent vraiment. */
@media (hover: none) {
  .cine__frame:hover .cine__step { opacity: 0; }
  .cine.is-still .cine__step { opacity: 1; }
}

/* Téléphone : cadre en 4/3, soit exactement le format des six vues en
   paysage — aucune n'est rognée, et la bande reste basse. */
@media (max-width: 700px) {
  .cine__frame { aspect-ratio: 4 / 3; max-height: 46vh; }
  .cine__caption { max-width: 78%; }
}

/* Sans JavaScript, rien ne fait avancer les vues : elles se dépilent toutes,
   chacune avec sa légende. Le cadre perd sa hauteur fixe et son voile. */
html:not(.js) .cine__frame {
  aspect-ratio: auto;
  max-height: none;
  background: none;
}
html:not(.js) .cine__slide {
  position: static;
  opacity: 1;
  margin-bottom: 14px;
}
html:not(.js) .cine__slide img { height: auto; border-radius: var(--radius); }
html:not(.js) .cine__veil,
html:not(.js) .cine__count,
html:not(.js) .cine__step,
html:not(.js) .cine__dots { display: none; }
html:not(.js) .cine__caption {
  position: static;
  max-width: none;
  margin-top: 10px;
  opacity: 1;
  transition: none;
}


/* ==========================================================================
   15bis-b — BANDE VIDÉO (page Réalisations)
   Douze secondes de drone, muettes et en boucle. La photo reste posée
   dessous : si la vidéo ne démarre pas — animations réduites, lecture
   automatique refusée, données coupées — il reste une image.
   ========================================================================== */

.filmband__frame {
  position: relative;
  margin: clamp(28px, 3.5vw, 44px) 0 0;
  aspect-ratio: 30 / 17;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--bg-steel);
}

/* Page Réalisations : la bande ferme la galerie, elle ne la concurrence
   pas. Le cadre est donc plafonné — étalée sur toute la largeur, la source
   960 px se voyait pixelliser. Proportions d'origine conservées, aucun
   recadrage ; sous 820 px le cadre reprend toute la largeur. */
.filmband__frame--compact {
  max-width: 820px;
  margin-inline: auto;
}
.filmband__poster,
.filmband__video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  filter: saturate(.85) contrast(1.03);
}
.filmband__caption {
  margin-top: 14px;
  font-size: .95rem;
  color: var(--text-soft);
}

@media (prefers-reduced-motion: reduce) {
  .filmband__video { display: none; }
}


/* ==========================================================================
   15c — RÉALISATIONS : galerie en rangées justifiées
   Chaque photo s'affiche dans ses proportions d'origine (--ar, rapport
   largeur / hauteur), sans recadrage. js/main.js regroupe les projets
   visibles en rangées d'images de même hauteur et met en grand les projets
   « à la une », légende à côté. Sans script, les projets s'enchaînent
   simplement, toujours sans recadrage.
   ========================================================================== */

.folio {
  --folio-gap: clamp(12px, 1.4vw, 20px);
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--folio-gap);
  transition: opacity .25s var(--ease), transform .25s var(--ease);
}
.folio.is-switching { opacity: 0; transform: translateY(8px); }

.folio__item { flex: 1 1 320px; min-width: 0; }
.folio__item[hidden] { display: none; }
.folio.is-laid .folio__item { flex-grow: 0; flex-shrink: 0; }

/* Retour des projets après un filtre : montée courte, décalée */
.folio__item.is-entering {
  animation: folio-in .5s var(--ease-out) both;
  animation-delay: calc(var(--n, 0) * 45ms);
}
@keyframes folio-in {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}

.folio__figure { position: relative; margin: 0; }

/* Une vignette de galerie n'est pas un lien : les photos de la page
   Réalisations ne mènent nulle part. Une seule fait exception — la carte
   d'un ouvrage suivi sur plusieurs photos, qui renvoie à son parcours sur
   la page Réalisations. Le lien couvre toute la vignette et n'apporte
   aucun style propre : ni couleur, ni soulignement, ni changement de
   taille. Seul le curseur dit qu'on peut cliquer. */
.folio__link {
  display: block;
  color: inherit;
  text-decoration: none;
}
.folio__link:focus-visible {
  outline: 2px solid var(--polyenco-red-light);
  outline-offset: 4px;
}

.folio__media {
  position: relative;
  aspect-ratio: var(--ar, 1.4);
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--bg-steel);
}
.folio__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 1.1s var(--ease-out);
}
/* Portfolio : la photo domine, mais la catégorie et l'intitulé restent
   toujours lisibles — pas d'information cachée derrière un survol. */
.folio__media::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  background: linear-gradient(0deg, rgba(10,10,11,.88) 0%, rgba(10,10,11,.3) 40%, rgba(10,10,11,0) 66%);
  transition: opacity .5s var(--ease);
  pointer-events: none;
}

.folio__caption {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 1;
  padding: clamp(16px, 1.8vw, 28px);
  pointer-events: none;
}
.folio__cat {
  display: block;
  margin-bottom: 6px;
  color: var(--text-soft);
}

/* Le repère de catégorie : la même dent que les jalons du processus, en
   rouge, posée devant l'intitulé. C'est le seul rouge de la galerie — il
   sert à indexer l'ouvrage, pas à décorer la photo. */
.folio__cat::before,
.work__cat::before {
  content: "";
  display: inline-block;
  width: 7px;
  height: 5px;
  margin-right: 9px;
  background: var(--polyenco-red);
  -webkit-clip-path: var(--tooth-clip);
          clip-path: var(--tooth-clip);
}
.folio__title {
  font-family: var(--font-heading);
  font-size: clamp(1.02rem, 1.3vw, 1.3rem);
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: -.01em;
  color: var(--text);
}

.folio__item:hover .folio__media img { transform: scale(1.03); }

/* À la une (grand écran) : la photo occupe toute la largeur, l'intitulé se
   lit dessous. Aucune colonne vide à côté de l'image. */
.folio__item.is-feature {
  flex-basis: 100%;
  margin-block: clamp(16px, 2vw, 32px);
}
.folio__item.is-feature:first-child { margin-top: 0; }
.folio__item.is-feature .folio__media { max-height: 78vh; }
.folio__item.is-feature .folio__caption {
  position: static;
  padding: 20px 0 0;
  border-top: 1px solid var(--line-strong);
  pointer-events: auto;
}
.folio__item.is-feature .folio__cat { margin-bottom: 12px; }
.folio__item.is-feature .folio__title {
  font-size: clamp(1.7rem, 2.8vw, 2.6rem);
  line-height: 1.1;
  letter-spacing: -.02em;
}
/* À la une : la légende est posée à côté, l'image reste nette */
.folio__item.is-feature .folio__media::after { opacity: 0; }

/* Mobile : deux projets par rangée, sur une grille.

   Les rangées ne sont volontairement pas alignées en bas. Chaque média
   porte son propre --ar, écrit dans le HTML : une photo en portrait reste
   haute, une photo large reste basse, et la grille garde seulement le pas
   des colonnes. C'est ce décalage qui fait le mur de photos — et il ne
   coûte aucun recadrage, puisque le cadre a exactement les proportions de
   l'image qu'il contient. À hauteur d'écran égale, le visiteur voit deux
   fois plus d'ouvrages.

   align-items: start évite que la plus courte des deux photos d'une rangée
   soit étirée à la hauteur de sa voisine. */
@media (max-width: 640px) {
  .folio {
    --folio-step: 4px;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    /* « dense » : un ouvrage pleine largeur exige ses deux colonnes libres,
       il attend donc la plus haute des deux et laisse l'autre en plan — un
       aplat noir haut de plusieurs centaines de pixels avant lui. Le rangement
       dense fait redescendre le premier ouvrage suivant qui tient dans ce
       creux, au lieu de le laisser vide. Rien d'autre ne bouge : les pleines
       largeurs gardent leur place et leur taille, seuls les petits formats
       comblent. */
    grid-auto-flow: row dense;
    align-items: start;
    column-gap: 10px;
    row-gap: 24px;
  }

  /* Avec le script : trame fine, et js/main.js donne à chaque carte un
     nombre de pas égal à sa hauteur. Les deux colonnes coulent alors
     indépendamment — plus de blanc sous la plus courte des deux, et les
     projets gardent leur ordre.
     Sans le script, la grille régulière ci-dessus reste en place : les
     rangées s'alignent, ce qui laisse des blancs mais reste lisible. */
  .js .folio {
    grid-auto-rows: var(--folio-step);
    row-gap: 0;
  }

  /* Quatre ouvrages tiennent toute la largeur, seuls sur leur rangée :
     ce sont les vues d'ensemble, et à mi-largeur on n'y distinguait plus
     rien — l'ombrière et le hall font près de quatre fois plus larges que
     hauts, soit une bande de vingt pixels dans une colonne de 170.
     Le repère est data-wide, écrit dans le HTML, et non data-feature :
     celui-ci commande la mise en avant du grand écran, que l'on ne touche
     pas. Deux des quatre portent les deux attributs, deux n'en portent
     qu'un — les deux réglages restent ainsi indépendants.
     La photo garde son propre --ar : pleine largeur, elle est donc plus
     grande, jamais recadrée ni déformée. */
  .folio__item[data-wide] { grid-column: 1 / -1; }
  /* La grille commande les largeurs : les bases flex que js/main.js calcule
     pour les rangées justifiées du grand écran n'ont plus cours ici. */
  .folio__item,
  .folio.is-laid .folio__item { flex-basis: auto; }

  /* La légende passe sous la photo — le traitement déjà retenu pour les
     projets à la une du grand écran.

     Posée sur l'image à mi-largeur, elle ne tenait plus : sur un ouvrage
     large, donc bas, la catégorie et l'intitulé occupaient quatre lignes,
     dépassaient la hauteur du cadre et débordaient sur la rangée du dessus.
     Là où elle tenait encore, elle montait au-delà du voile et retombait
     sur un ciel clair, illisible. Sous l'image, l'intitulé se lit sur le
     fond de la page à tous les formats, la photo reste nette, et la carte
     garde une hauteur qui suit son contenu. */
  .folio__caption {
    position: static;
    padding: 9px 2px 0;
    pointer-events: auto;
  }
  .folio__media::after { opacity: 0; }

  .folio__cat {
    margin-bottom: 3px;
    font-size: .66rem;
    letter-spacing: .06em;
  }
  .folio__cat::before { width: 6px; height: 4px; margin-right: 6px; }
  .folio__title {
    font-size: clamp(.82rem, 3.4vw, .95rem);
    line-height: 1.25;
  }
}


/* ==========================================================================
   15quinquies — VISIONNEUSE PLEIN ÉCRAN (lightbox)
   La galerie sert à parcourir, la visionneuse à regarder. Rien de la page
   ne change : ni l'ordre, ni les formats, ni la hauteur des cartes. On
   ajoute seulement, sur chaque photo, une surface transparente qui ouvre
   la vue en grand — un vrai <button>, posé en absolu, donc sans un pixel
   de hauteur en plus et accessible au clavier comme au doigt.

   Dans la visionneuse la photo n'est jamais recadrée (object-fit: contain)
   ni déformée : c'est le seul endroit du site où l'ouvrage se regarde en
   entier. Le fond reprend le noir de la page, le compteur et l'intitulé
   reprennent ceux de la bande cinématique (§ 15quater) — même vocabulaire,
   mêmes places.
   ========================================================================== */

/* --- La surface d'ouverture, posée sur la photo ----------------------
   Aucune icône, aucun libellé : seul le curseur dit qu'on peut agrandir,
   et sur écran tactile il n'y a rien du tout à voir. Le cadre qui
   l'accueille est toujours en position: relative avec ses propres angles
   (folio__media, flow__media, cine__frame) : le bouton s'y cale au pixel
   et n'ajoute rien à la mise en page. */
.pic-open {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: inherit;
  background: none;
  color: inherit;
  font: inherit;
  cursor: zoom-in;
  -webkit-appearance: none;
          appearance: none;
  -webkit-tap-highlight-color: transparent;
}
.pic-open:focus-visible {
  outline: 2px solid var(--polyenco-red-light);
  outline-offset: -3px;
}

/* --- Le verrou de défilement ------------------------------------------
   La page ne doit pas glisser derrière la visionneuse, et l'on doit
   revenir exactement où l'on était. Le corps est donc figé à sa position
   du moment (js/main.js pose le décalage dans « top ») : c'est la seule
   méthode qui tienne aussi sur iOS, où overflow: hidden ne suffit pas.

   La barre de défilement disparaît alors, et la page derrière s'élargirait
   d'autant : js/main.js rend cette largeur au corps en rembourrage, de
   sorte que rien ne bouge — ni la page, ni l'en-tête collant. La
   visionneuse, elle, prend l'écran entier, barre comprise. Sur mobile et
   sur les barres flottantes de macOS, cette largeur vaut zéro et il n'y a
   rien à compenser. */
html.lightbox-on { overflow: hidden; }
html.lightbox-on body {
  position: fixed;
  left: 0;
  right: 0;
  overflow: hidden;
}

/* --- La visionneuse ---------------------------------------------------- */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 2000;              /* l'en-tête est à 90, le menu mobile à 96 */
  width: 100vw;
  height: 100vh;
  height: 100dvh;             /* barre d'adresse repliée ou non, même cadre */
  background: rgba(10, 10, 11, .965);
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
  opacity: 0;
  transition: opacity .22s var(--ease);
  overscroll-behavior: contain;
  -webkit-tap-highlight-color: transparent;
}
.lightbox[hidden] { display: none; }
.lightbox.is-on { opacity: 1; }

/* Le cadre de lecture. Le retrait laisse respirer le bouton de fermeture
   en haut et la légende en bas : la photo ne passe jamais dessous. */
.lightbox__stage {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: clamp(52px, 8vh, 78px) clamp(12px, 4vw, 72px);
  /* Le glissement horizontal nous revient, le reste (pincer pour zoomer,
     glisser verticalement) reste au navigateur. */
  touch-action: pan-y;
  touch-action: pan-y pinch-zoom;
}

.lightbox__img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  /* Élément de grille : sans cela, sa taille intrinsèque l'empêcherait de
     descendre sous ses pixels d'origine et la photo déborderait du cadre. */
  min-width: 0;
  min-height: 0;
  /* Rien n'est rogné : l'ouvrage se voit en entier, dans ses proportions. */
  object-fit: contain;
  opacity: 1;
  transition: opacity .18s var(--ease), transform .28s var(--ease-out);
}
.lightbox.is-swapping .lightbox__img { opacity: 0; }
.lightbox.is-dragging .lightbox__img { transition: none; }

/* Compteur et intitulé : mêmes places qu'au bas de la bande cinématique,
   l'un à gauche, l'autre à droite. Purement indicatifs, donc hors du
   relevé des lecteurs d'écran et hors de portée du doigt. */
.lightbox__name,
.lightbox__count {
  position: absolute;
  bottom: clamp(14px, 2.2vh, 26px);
  z-index: 1;
  margin: 0;
  max-width: 46%;
  pointer-events: none;
}
.lightbox__name {
  left: clamp(14px, 3vw, 30px);
  font-family: var(--font-heading);
  font-size: clamp(.9rem, 1.4vw, 1.12rem);
  font-weight: 800;
  letter-spacing: -.01em;
  line-height: 1.2;
  color: #fff;
}
.lightbox__count {
  right: clamp(14px, 3vw, 30px);
  text-align: right;
  color: var(--text-muted);
}
.lightbox__at { color: var(--text); }

/* Les trois boutons partagent le dessin des pas de la bande cinématique :
   un rond sombre cerné d'un filet, rien de plus. */
.lightbox__close,
.lightbox__step {
  position: absolute;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, .28);
  border-radius: 50%;
  background: rgba(10, 10, 11, .46);
  color: #fff;
  cursor: pointer;
  transition: opacity .3s var(--ease), background-color .3s var(--ease);
}
.lightbox__close svg,
.lightbox__step svg { width: 18px; height: 18px; }

.lightbox__close {
  top: clamp(10px, 2vh, 22px);
  right: clamp(10px, 3vw, 22px);
}
.lightbox__step--prev { top: 50%; margin-top: -22px; left: clamp(10px, 2vw, 26px); }
.lightbox__step--next { top: 50%; margin-top: -22px; right: clamp(10px, 2vw, 26px); }

.lightbox__close:hover,
.lightbox__step:hover { background: rgba(10, 10, 11, .78); }
.lightbox__close:focus-visible,
.lightbox__step:focus-visible {
  outline: 2px solid var(--polyenco-red-light);
  outline-offset: 2px;
}
.lightbox__close[hidden],
.lightbox__step[hidden] { display: none; }

/* Les deux pas restent discrets : à peine là tant que la souris ne passe
   pas sur eux. Au doigt, ils ne servent à rien — c'est le glissement qui
   fait passer d'une vue à l'autre — et ils s'effacent complètement, sauf
   si le clavier vient les chercher. */
.lightbox__step { opacity: .34; }
.lightbox:hover .lightbox__step { opacity: .55; }
.lightbox__step:hover { opacity: 1; }

@media (hover: none) {
  .lightbox__step,
  .lightbox:hover .lightbox__step { opacity: 0; pointer-events: none; }
}
.lightbox__step:focus-visible { opacity: 1; pointer-events: auto; }

/* Téléphone : le cadre gagne les marges latérales que les pas laissent
   libres, et la légende peut prendre toute la largeur si le compteur
   n'est pas utile. */
@media (max-width: 640px) {
  .lightbox__stage { padding: clamp(46px, 7vh, 62px) 10px; }
  .lightbox__name { font-size: .9rem; max-width: 58%; }
}

/* Paysage sur téléphone : la hauteur est la denrée rare, on rend au cadre
   tout ce que l'on peut. */
@media (max-height: 460px) {
  .lightbox__stage { padding: 46px clamp(10px, 3vw, 56px) 34px; }
  .lightbox__name,
  .lightbox__count { bottom: 8px; }
}

/* Mouvement réduit : la règle générale du § 25 coupe déjà toutes les
   transitions du site, celles de la visionneuse comprises. js/main.js fait
   le reste — l'ouverture, le changement de vue et la fermeture se font
   alors d'un coup, sans attendre un fondu qui n'a pas lieu. */

/* À l'impression, la visionneuse n'existe pas. */
@media print {
  .lightbox { display: none !important; }
  .pic-open { display: none; }
}


/* ==========================================================================
   15d — AVANT / APRÈS (page Réalisations, pièces sur mesure)
   Deux états du même ouvrage dans un seul cadre : la virole peinte sert de
   fond, la virole assemblée est posée dessus et découpée à la position de
   la barre (clip-path). La barre est un input range transparent posé sur
   l'image : souris, doigt et flèches du clavier la pilotent sans code de
   glisser-déposer. Sans JavaScript le curseur est masqué et la comparaison
   reste affichée moitié / moitié.
   Le cadre est au format des photos (3/4, l'ouvrage est vertical) : aucune
   des deux n'est recadrée ni étirée. Il occupe une colonne, la légende
   prend l'autre.
   ========================================================================== */

.prepost {
  --pos: 50%;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: clamp(28px, 5vw, 72px);
  margin-top: clamp(28px, 3.5vw, 44px);
  /* Les autres réalisations suivent dans la galerie : il leur faut de l'air. */
  margin-bottom: clamp(36px, 4.5vw, 64px);
}

.prepost__frame {
  position: relative;
  isolation: isolate;
  aspect-ratio: 3 / 4;
  /* Photos en portrait : sans plafond, le cadre dépasserait la hauteur de
     l'écran et la comparaison ne se lirait plus d'un seul regard.
     width: 100% est indispensable — tout le contenu du cadre est en position
     absolue, donc sa largeur « au contenu » vaut zéro. Sans cette ligne, le
     margin-inline: auto du mode étroit le ferait disparaître. */
  width: 100%;
  max-width: 520px;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--bg-steel);
}

/* Cadrage commun aux deux vues : même conteneur, même object-fit. La virole
   tombe au même endroit de l'une à l'autre. */
.prepost__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 50% 50%;
  -webkit-user-select: none; user-select: none;
  -webkit-user-drag: none;
}

/* Les deux couches occupent exactement le même cadre ; seule celle du haut
   (« avant ») est découpée à la position de la barre. */
.prepost__layer--after  { position: absolute; inset: 0; z-index: 1; }
.prepost__layer--before {
  position: absolute; inset: 0; z-index: 2;
  clip-path: inset(0 calc(100% - var(--pos)) 0 0);
}

/* L'étiquette : le bandeau sombre et flouté déjà utilisé sur les photos du
   site, posé dans un coin haut — jamais au milieu de l'ouvrage. */
.prepost__tag {
  position: absolute; top: 14px; z-index: 3;
  display: inline-flex; align-items: center;
  padding: 7px 12px;
  border-radius: var(--radius-sm);
  background: rgba(10, 10, 11, .72);
  -webkit-backdrop-filter: blur(6px);
          backdrop-filter: blur(6px);
  color: var(--text);
}
.prepost__tag--before { left: 14px; }
.prepost__tag--after  { right: 14px; }

/* La dent rouge ne marque que l'état d'arrivée : c'est le seul rouge du
   bloc, et il dit dans quel sens la comparaison se lit. */
.prepost__tag--after::before {
  content: "";
  width: 7px; height: 5px;
  margin-right: 9px;
  background: var(--polyenco-red);
  -webkit-clip-path: var(--tooth-clip);
          clip-path: var(--tooth-clip);
}

/* La barre : blanche, le meilleur contraste sur ces deux photos claires */
.prepost__divider {
  position: absolute; top: 0; bottom: 0; z-index: 4;
  left: var(--pos);
  width: 2px;
  margin-left: -1px;
  background: #fff;
  box-shadow: 0 0 0 1px rgba(10, 10, 11, .22);
  pointer-events: none;
}

.prepost__handle {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  display: flex; align-items: center; justify-content: center;
  gap: 5px;
  width: 52px; height: 52px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 2px 16px rgba(0, 0, 0, .38);
}
.prepost__handle svg { display: block; width: 8px; height: 13px; }
.prepost__handle path {
  fill: none;
  stroke: var(--polyenco-red);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Le curseur lui-même : invisible, il couvre toute l'image.
   touch-action: pan-y laisse le défilement vertical à la page. */
.prepost__range {
  position: absolute; inset: 0; z-index: 5;
  width: 100%; height: 100%;
  margin: 0; padding: 0;
  border: 0; background: none;
  cursor: ew-resize;
  touch-action: pan-y;
  -webkit-appearance: none; appearance: none;
}
.prepost__range::-webkit-slider-runnable-track { height: 100%; background: none; }
.prepost__range::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 56px; height: 56px;
  border: 0; border-radius: 50%;
  background: transparent;
  cursor: ew-resize;
}
.prepost__range::-moz-range-track { height: 100%; border: 0; background: none; }
.prepost__range::-moz-range-thumb {
  width: 56px; height: 56px;
  border: 0; border-radius: 50%;
  background: transparent;
  cursor: ew-resize;
}
.prepost__range:focus-visible { outline: 2px solid var(--polyenco-red-light); outline-offset: 4px; }

html:not(.js) .prepost__range { display: none; }

.prepost__cat { display: block; margin-bottom: 8px; color: var(--text-soft); }
.prepost__title {
  font-family: var(--font-heading);
  font-size: clamp(1.5rem, 2.4vw, 2.2rem);
  font-weight: 900;
  line-height: 1.1;
  letter-spacing: -.02em;
}
.prepost__text { margin-top: 10px; font-size: 1rem; color: var(--text-soft); }

/* Variante paysage (abri de process) : les deux vues sont horizontales.
   Le cadre prend la colonne large et passe au 16/10 du site ; object-fit:
   cover rogne les bords de l'une et de l'autre, sans jamais les déformer. */
.prepost--wide { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); }
.prepost--wide .prepost__frame { aspect-ratio: 16 / 10; max-width: none; }

/* Sous 900 px la légende passe sous le cadre, qui se centre dans la
   colonne : la barre reste au doigt, jamais coupée. */
@media (max-width: 900px) {
  .prepost { grid-template-columns: 1fr; gap: 0; }
  .prepost__frame { margin-inline: auto; }
  .prepost__caption { margin-top: 22px; }
}

@media (max-width: 700px) {
  .prepost__handle { width: 46px; height: 46px; }
  .prepost__tag { top: 10px; padding: 6px 10px; font-size: .68rem; }
  .prepost__tag--before { left: 10px; }
  .prepost__tag--after  { right: 10px; }
  .prepost__caption { margin-top: 18px; }
}


/* ==========================================================================
   16 — POURQUOI POLYENCO ? (principes)
   ========================================================================== */

.principles {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(40px, 6vw, 112px);
  align-items: start;
}
.principles__head { position: sticky; top: calc(var(--header-h) + 48px); }

.principle-list { border-top: 1px solid var(--line-strong); }

.principle {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
  gap: 12px 40px;
  align-items: baseline;
  padding: clamp(24px, 2.8vw, 38px) 12px clamp(24px, 2.8vw, 38px) 26px;
  border-bottom: 1px solid var(--line-strong);
  transition: background-color .5s var(--ease);
}
.principle::before {
  content: "";
  position: absolute; left: 0; top: -1px; bottom: -1px;
  width: 2px;
  background: var(--polyenco-red);
  transform: scaleY(0);
  transform-origin: bottom;
  transition: transform .5s var(--ease);
}
.principle:hover { background: rgba(255,255,255,.03); }
.principle:hover::before { transform: scaleY(1); }

.principle__title {
  font-family: var(--font-heading);
  font-size: clamp(1.25rem, 2vw, 1.7rem);
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: -.01em;
  color: var(--text);
}
.principle__text { font-size: 1rem; line-height: 1.7; color: var(--text-soft); }


/* ==========================================================================
   17 — SOLUTIONS (lignes détaillées, page Solutions)
   ========================================================================== */

:root { --subnav-h: 65px; }

/* --- Sous-navigation collante : accès direct aux six solutions.
       La solution lue est signalée par le filet rouge (js/main.js). --- */
.subnav {
  position: sticky;
  top: var(--header-h);
  z-index: 80;
  background: rgba(10,10,11,.92);
  -webkit-backdrop-filter: blur(14px);
          backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
}
.subnav__list {
  display: grid;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  gap: clamp(16px, 2vw, 32px);
}
.subnav__link {
  position: relative;
  display: flex; align-items: center;
  height: 100%;
  min-height: 64px;
  padding-block: 12px;
  font-family: var(--font-heading);
  font-size: .8rem;
  font-weight: 700;
  line-height: 1.3;
  /* Capitales et interlettrage du menu principal (.nav__link) */
  text-transform: uppercase;
  letter-spacing: var(--track-nav);
  color: var(--text-soft);
  transition: color .25s var(--ease);
}
.subnav__link::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: var(--polyenco-red);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .35s var(--ease);
}
.subnav__link:hover,
.subnav__link.is-active { color: var(--text); }
.subnav__link.is-active::after { transform: scaleX(1); }

/* --- Périmètre : trois colonnes ouvertes, sans cadre --- */
.scope {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(24px, 3.2vw, 52px);
  margin-top: clamp(30px, 3.6vw, 48px);
}
.scope__col { padding-top: 22px; border-top: 1px solid var(--line-strong); }
.scope__title {
  margin-bottom: 8px;
  font-family: var(--font-heading);
  font-size: clamp(1.15rem, 1.6vw, 1.4rem);
  font-weight: 800;
  line-height: 1.25;
  letter-spacing: -.01em;
}

/* --- Détail d'une solution : grande photo, texte court à côté --- */
.solution {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: clamp(32px, 4.5vw, 72px);
  align-items: center;
  padding-block: clamp(44px, 5vw, 76px);
  border-top: 1px solid var(--line);
  scroll-margin-top: calc(var(--header-h) + var(--subnav-h) + 32px);
}
.solution:first-of-type { padding-top: 0; border-top: 0; }
.solution:last-of-type  { padding-bottom: 0; }

/* Image à droite : les colonnes s'inversent aussi, l'image garde la plus large */
.solution--reverse { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
.solution--reverse .solution__media { order: 2; }

.solution__media {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--bg-steel);
}
/* Une solution peut montrer une vidéo au lieu d'une photo : même cadre, même
   recadrage, même voile et même réveil au survol. display:block supprime le
   liseré que laisserait un élément en ligne sous la vidéo. */
.solution__media img,
.solution__media video {
  width: 100%; aspect-ratio: 16/10; object-fit: cover;
  filter: saturate(.85) contrast(1.03);
  transition: transform 1.2s var(--ease-out), filter .6s var(--ease);
}
.solution__media video { display: block; }
.solution__media:hover img,
.solution__media:hover video { transform: scale(1.03); filter: none; }

/* La photo mène à la page de la solution ; le bouton dit la même chose en
   toutes lettres, pour qui ne survole pas. */
.solution__link { display: block; }
.solution__link:focus-visible { outline-offset: -3px; }
.solution__link:focus-visible img,
.solution__link:focus-visible video { transform: scale(1.03); filter: none; }

.solution__more { margin-top: clamp(24px, 2.6vw, 34px); }

.solution__title {
  font-family: var(--font-heading);
  font-size: clamp(1.9rem, 3.4vw, 3rem);
  font-weight: 900;
  line-height: 1.08;
  /* Noms des domaines en capitales, comme les autres grands titres
     (.is-caps) : même interlettrage. */
  text-transform: uppercase;
  letter-spacing: -.005em;
  text-wrap: balance;
}
.solution__lede {
  margin-top: 20px;
  max-width: 44ch;
  font-size: clamp(1.1rem, 1.4vw, 1.3rem);
  line-height: 1.55;
  color: var(--text);
}

/* Une seule liste sous la phrase : les applications. Rien de plus. */
.solution__apps {
  margin-top: clamp(28px, 3vw, 40px);
  padding-top: 22px;
  border-top: 1px solid var(--line-strong);
}
.solution__apps h4 {
  margin-bottom: 10px;
  color: var(--text-muted);
}

/* Tablette : les six solutions passent à la ligne plutôt que de défiler.
   Deux rangées tiennent dans la barre collante sans manger l'écran, et
   toutes les solutions se lisent d'un coup — c'est le même parti que pour
   les filtres de la page Réalisations.

   --subnav-h sert à décaler les ancres sous la barre collante : sur deux
   rangées elle mesure le double, la valeur suit. Mieux vaut la surestimer
   que la sous-estimer — un titre qui arrive un peu bas se lit, un titre
   caché sous la barre, non. */
/* En capitales, les huit libellés débordent de leur colonne dès 1300 px :
   entre 621 et 1320 px, corps et interlettrage sont resserrés (même
   encombrement qu'en minuscules), et la barre passe sur deux rangées dès
   1100 px au lieu de 1000. */
@media (min-width: 621px) and (max-width: 1320px) {
  .subnav__link { font-size: .7rem; letter-spacing: 0; }
}

@media (max-width: 1100px) {
  :root { --subnav-h: 112px; }
  .subnav__list {
    display: flex;
    flex-wrap: wrap;
    gap: 0 28px;
  }
  .subnav__link { flex: none; min-height: 54px; padding-block: 0; white-space: nowrap; }
}

/* Sous 760 px, les huit libellés prennent trois rangées : le décalage des
   ancres suit, pour qu'aucun titre ne passe sous la barre. */
@media (min-width: 621px) and (max-width: 760px) {
  :root { --subnav-h: 166px; }
}

/* Téléphone : on revient à la barre défilante. Six libellés à 390 px
   feraient trois ou quatre rangées, soit une barre collante plus haute que
   le contenu qu'elle sert à parcourir. Le fondu de droite dit qu'il reste
   des solutions à atteindre. */
@media (max-width: 620px) {
  :root { --subnav-h: 55px; }
  .subnav .container { padding-inline: 0; }
  .subnav__list {
    flex-wrap: nowrap;
    gap: 28px;
    padding-inline: var(--gutter) calc(var(--gutter) + 40px);
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-mask-image: linear-gradient(90deg, #000 85%, transparent 100%);
            mask-image: linear-gradient(90deg, #000 85%, transparent 100%);
  }
  .subnav__list::-webkit-scrollbar { display: none; }
}

/* Listes : lignes simples, sans puces ni filets */
.tick-list li {
  padding-block: 5px;
  font-size: 1rem;
  line-height: 1.5;
  color: var(--text);
}

/* Quatre applications sur deux colonnes : compact, sans zone vide */
.tick-list--cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 clamp(20px, 3vw, 40px);
}

@media (max-width: 480px) {
  .tick-list--cols { grid-template-columns: 1fr; }
}


/* ==========================================================================
   20 — À PROPOS
   ========================================================================== */

/* --------------------------------------------------------------------------
   PRÉSENTATION (accueil et À propos)
   Un bloc de lecture, pas une affiche. L'étiquette ouvre, le titre prend la
   largeur de la colonne, le paragraphe reste dans une colonne de lecture et
   la ligne de savoir-faire ferme. Une seule colonne : l'œil descend, il ne
   cherche pas. Le rouge se limite aux séparateurs de cette dernière ligne —
   devant l'étiquette, le filet denté du titre s'efface déjà de lui-même.
   -------------------------------------------------------------------------- */

/* Le titre de présentation n'a pas de plafond propre : il avait un bloc de
   26ch, mesuré lui aussi sur le corps de texte et non sur la police de
   titrage (voir § 04). Relevé à 1440 px, boîte de 691 px :

     « Des projets métalliques, de l'étude au chantier. »  texte 948 px → 2 lignes
     « Steel projects, from engineering to site. »         texte 799 px → 2 lignes

   Les deux tiennent sur une ligne dans la colonne de 1304 px, et repartent
   d'eux-mêmes à la ligne sous 768 px, quand la place manque pour de bon. */

/* Le paragraphe portait un plafond fixe de 700 px. Sur un écran de 1440 px
   la colonne en offre 1304 : le texte repliait donc à la moitié de la page,
   sans que la largeur réelle soit atteinte, et le bloc paraissait tassé à
   gauche sous un titre qui, lui, prend toute la colonne. Le plafond saute :
   le repli ne dépend plus que de la largeur réellement disponible. */
.intro__text {
  margin-top: clamp(16px, 1.8vw, 22px);
}

.intro__skills {
  max-width: 700px;
  margin-top: clamp(20px, 2.2vw, 28px);
  font-size: .95rem;
  line-height: 1.7;
  color: var(--text-soft);
}
/* Seul rouge du bloc. L'espace qui précède chaque séparateur est insécable
   dans le HTML : en repliant, la ligne ne commence jamais par un point. */
.intro__sep { color: var(--polyenco-red); }

.intro__actions { margin-top: clamp(24px, 2.8vw, 34px); }

/* Le relevé suit la présentation, au même pas que sous l'ancien bloc. */
.intro + .about-facts { margin-top: clamp(36px, 4.5vw, 60px); }


/* Présentation : titre et texte côte à côte, puis une grande image */
.about-intro {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(32px, 6vw, 104px);
  align-items: start;
}
.about-intro__body > * + * { margin-top: 18px; }
.about-intro__body > .about-intro__actions { margin-top: 30px; }
/* Accueil, Contact : le relevé suit directement le texte, sans image */
.about-intro + .about-facts { margin-top: clamp(36px, 4.5vw, 60px); }
.about-intro__lead {
  font-size: clamp(1.1rem, 1.45vw, 1.3rem);
  line-height: 1.6;
  color: var(--text);
}


/* Relevé : trois informations confirmées sous l'image */
.about-facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0 clamp(28px, 4vw, 64px);
  margin-top: clamp(28px, 3vw, 40px);
}
.about-facts__row { padding-top: 18px; border-top: 1px solid var(--line-strong); }
.about-facts dt { margin-bottom: 8px; color: var(--text-muted); }
.about-facts dd { font-size: .98rem; line-height: 1.5; color: var(--text); }
/* Les valeurs sont du texte, sauf le téléphone et WhatsApp : un filet sous
   le numéro signale qu'on peut appuyer dessus, et passe au rouge POLYENCO
   au survol — le même geste que dans le panneau de navigation. */
.about-facts dd a {
  padding-bottom: 2px;
  color: var(--text);
  border-bottom: 1px solid var(--line-strong);
  transition: border-color .35s var(--ease);
}
.about-facts dd a:hover,
.about-facts dd a:focus-visible { border-bottom-color: var(--polyenco-red); }

/* En images : mosaïque asymétrique, légende posée sur l'image */
.mosaic {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  grid-auto-rows: clamp(190px, 18vw, 270px);
  gap: clamp(10px, 1.2vw, 18px);
  margin-top: clamp(32px, 4vw, 52px);
}
.mosaic__item {
  position: relative;
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--bg-steel);
}
/* Deux blocs de deux rangées, le second en miroir du premier : la photo
   haute passe de gauche à droite, la photo large de haut en bas. */
.mosaic__item:nth-child(1) { grid-column: 1 / 6;  grid-row: span 2; }
.mosaic__item:nth-child(2) { grid-column: 6 / 13; }
.mosaic__item:nth-child(3) { grid-column: 6 / 9; }
.mosaic__item:nth-child(4) { grid-column: 9 / 13; }
.mosaic__item:nth-child(5) { grid-column: 1 / 5; }
.mosaic__item:nth-child(6) { grid-column: 5 / 9; }
.mosaic__item:nth-child(7) { grid-column: 9 / 13; grid-row: span 2; }
.mosaic__item:nth-child(8) { grid-column: 1 / 9; }

.mosaic__item img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  filter: saturate(.85);
  transition: transform 1.1s var(--ease-out), filter .6s var(--ease);
}
.mosaic__item::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(0deg, rgba(10,10,11,.82) 0%, rgba(10,10,11,0) 45%);
  pointer-events: none;
}
.mosaic__item figcaption {
  position: absolute; left: 0; bottom: 0; z-index: 1;
  padding: clamp(16px, 1.8vw, 26px);
  color: var(--text);
}
.mosaic__item:hover img { transform: scale(1.03); filter: none; }

@media (max-width: 1000px) {
  .mosaic { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: clamp(200px, 34vw, 320px); }
  .mosaic__item:nth-child(n) { grid-column: auto; grid-row: auto; }
  .mosaic__item:nth-child(1) { grid-row: span 2; }
  .mosaic__item:nth-child(8) { grid-column: 1 / -1; }
}

@media (max-width: 900px) {
  .about-intro { grid-template-columns: 1fr; }
}

@media (max-width: 640px) {
  .about-facts { grid-template-columns: 1fr; gap: 20px; }

  /* La mosaïque reste à deux colonnes : seules la hauteur de rangée et la
     gouttière se resserrent. La première photo garde ses deux rangées et la
     dernière sa pleine largeur — c'est ce décalage, hérité du grand écran,
     qui empêche la mosaïque de retomber sur un damier. */
  .mosaic { grid-auto-rows: clamp(150px, 44vw, 210px); gap: 10px; }
  .mosaic__item figcaption { padding: 12px; font-size: .8rem; line-height: 1.35; }
  /* À mi-largeur, une légende comme « Parc à profilés » passe sur deux
     lignes et sa première ligne remonte au-dessus du voile, sur un ciel
     clair. Le voile monte donc plus haut — sans s'alourdir en bas, où la
     photo doit rester lisible. */
  .mosaic__item::after {
    background: linear-gradient(0deg, rgba(10,10,11,.86) 0%, rgba(10,10,11,.3) 42%, rgba(10,10,11,0) 72%);
  }
}


/* ==========================================================================
   20bis — IDENTITÉ DE LA SOCIÉTÉ (page À propos)
   Le relevé officiel se lit comme un registre : l'intitulé à gauche, la
   mention exacte à droite, un filet fin entre deux lignes. Pas de cadre,
   pas de carte, pas de couleur ajoutée — le rouge reste au filet denté du
   titre de section, comme partout ailleurs.
   ========================================================================== */

.registry { margin-top: clamp(30px, 3.4vw, 46px); }

.registry__row {
  display: grid;
  grid-template-columns: minmax(0, 15em) minmax(0, 1fr);
  gap: 6px clamp(24px, 4vw, 64px);
  padding: clamp(18px, 2vw, 24px) 0;
  border-top: 1px solid var(--line);
}
.registry__row:first-child { border-top-color: var(--line-strong); }
.registry__row:last-child  { border-bottom: 1px solid var(--line); }

.registry__key { padding-top: 3px; color: var(--text-muted); }
.registry__value {
  margin: 0;
  font-size: clamp(1rem, 1.15vw, 1.1rem);
  line-height: 1.55;
  color: var(--text);
}
/* La raison sociale et l'identifiant sont des mentions, pas des phrases :
   ils reprennent la police de titrage et l'espacement du lockup. */
.registry__value--name {
  font-family: var(--font-heading);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .08em;
}
.registry__value--id {
  font-family: var(--font-heading);
  font-weight: 700;
  letter-spacing: .12em;
}

@media (max-width: 700px) {
  .registry__row { grid-template-columns: 1fr; gap: 8px; }
}


/* ==========================================================================
   20ter — Les deux règles qui rendaient leur colonne aux titres de la page
   À propos n'ont plus lieu d'être : le relevé mené sur l'ensemble du site a
   montré le même défaut à l'accueil, sur les pages Solutions et sur la page
   Réalisations, dans les deux langues. La correction est donc passée sur
   les composants eux-mêmes (§ 04 pour le bloc-titre de section, § 15ter
   pour l'ouvrage documenté, § 20 pour la présentation), et la classe
   .page-about ne sert plus à rien en feuille de style — elle reste dans le
   <body> des deux pages À propos, sans effet.
   ========================================================================== */


/* ==========================================================================
   21 — APPEL À L'ACTION — la seule grande surface rouge du site
   ========================================================================== */

.cta {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--polyenco-red);
  padding-block: clamp(68px, 7.5vw, 112px);
}
/* Trame fine, estompée vers la gauche : rappel discret du dessin technique.
   Le pas suit celui du reste du site — la moitié de la trame d'implantation,
   donc toujours un multiple du module du logo. */
.cta::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background-image:
    linear-gradient(rgba(255,255,255,.07) var(--rail-w), transparent var(--rail-w)),
    linear-gradient(90deg, rgba(255,255,255,.07) var(--rail-w), transparent var(--rail-w));
  background-size: calc(var(--setout-step) / 2) calc(var(--setout-step) / 2);
  -webkit-mask-image: linear-gradient(90deg, transparent 20%, #000 100%);
          mask-image: linear-gradient(90deg, transparent 20%, #000 100%);
}

/* Colonne du titre un peu plus large : « A STEEL CONSTRUCTION PROJECT? »
   doit tenir sans jamais couper « CONSTRUCTION ». */
/* Les deux colonnes se centrent l'une sur l'autre. Alignées par le bas,
   la plus courte — l'étiquette et le titre — descendait au ras du pied du
   cadre : relevé à 1440 px, 271 px de vide au-dessus d'elle contre 108 px
   en dessous, alors que la colonne de droite, elle, était d'aplomb
   (108 px / 108 px). Centrées, les deux respirent pareil de part et
   d'autre. Rien d'autre ne bouge : ni la hauteur du cadre, ni le partage
   des colonnes, ni l'ordre de lecture. */
/* Colonnes égales. Elles étaient à 1.2fr contre .8fr pour protéger le titre
   anglais ; la contrepartie était une colonne de droite trop étroite pour
   l'adresse. Le corps de l'adresse suit la fenêtre (2.6vw) pendant que sa
   colonne ne valait que 40 % de la grille : dès 1024 px l'adresse occupait
   94 à 95 % de sa colonne, et l'enveloppe qui la précède était renvoyée
   seule à la ligne. À 1fr/1fr la colonne de droite passe de 487 à 609 px
   sur un écran de 1440 px. Le titre reste au large des deux côtés — mesures
   plus bas, dans le bloc des pastilles. */
.cta__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(36px, 6vw, 104px);
  align-items: center;
}

/* Le même bloc-titre que partout ailleurs, retourné en blanc : sur le rouge
   POLYENCO, la denture reste le signal, c'est le fond qui a changé. */
.cta__label {
  display: block;
  color: #fff;
}
.cta__label::before {
  content: "";
  display: block;
  width: 168px;
  max-width: 100%;
  height: var(--tooth-depth);
  margin-bottom: clamp(18px, 2vw, 26px);
  background-image: var(--rule-teeth-white), linear-gradient(rgba(255,255,255,.45), rgba(255,255,255,.45));
  background-repeat: no-repeat, no-repeat;
  background-size: calc(var(--tooth-pitch) * 4) var(--tooth-depth), 100% var(--rail-w);
  background-position: left top, left bottom;
}

.cta__title {
  margin-top: 18px;
  font-size: clamp(1.9rem, 5.2vw, 4.6rem);
  line-height: 1.03;
  color: #fff;
}

/* Même colonne de lecture que partout ailleurs. Sur grand écran, la
   deuxième colonne de la grille est plus étroite que ce plafond : c'est
   elle qui mesure la phrase, et rien ne change. Le plafond ne travaille
   que sous 900 px, là où la grille repasse sur une seule colonne — et à
   44ch il travaillait trop : le texte long de la page Réalisations
   (1031 px d'un seul tenant) était tenu à 444 px alors que la colonne en
   offrait 691 à 768 px et 810 à 900 px, soit trois lignes au lieu de deux. */
.cta__text {
  max-width: 58ch;
  font-size: clamp(1rem, 1.2vw, 1.12rem);
  line-height: 1.7;
  color: #fff;
}

/* L'adresse officielle, mise en avant : un vrai lien mailto.

   L'enveloppe et l'adresse sont sur la même ligne, et c'est la boîte
   elle-même qui le garantit : en inline-flex, la pastille est un élément de
   la ligne, jamais un mot que le retour à la ligne peut emporter. Si la
   place venait à manquer, c'est l'adresse qui se replie à côté d'elle — la
   pastille ne se retrouve plus seule au-dessus.

   Encore fallait-il faire la place. Relevé à 1024, 1280, 1440 et 1920 px,
   l'adresse occupait 94 à 95 % de sa colonne à elle seule : il ne restait
   pas 20 px pour une pastille. Le corps passe donc de
   clamp(1.3rem, 2.6vw, 2.3rem) à clamp(1.2rem, 2.4vw, 2.15rem) — 34,4 px
   au lieu de 36,8 px au maximum, soit 6 % —, et l'adresse tient désormais
   avec sa pastille et 16 à 29 px de marge à toutes les largeurs, 320 px
   compris (c'est le plancher qui y travaillait : 280,4 px demandés pour une
   colonne de 280). Le reste du style est intact : même police, même
   graisse, même couleur, même filet qui court au survol. */
.cta__email {
  display: inline-flex;
  align-items: center;
  gap: .3em;
  margin-top: clamp(16px, 1.8vw, 22px);
  padding-bottom: 8px;
  font-family: var(--font-heading);
  font-size: clamp(1.2rem, 2.6vw, 2.3rem);
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: -.01em;
  color: #fff;
  background-image:
    linear-gradient(#fff, #fff),
    linear-gradient(rgba(255,255,255,.45), rgba(255,255,255,.45));
  background-size: 0% 2px, 100% 2px;
  background-position: 0 100%, 0 100%;
  background-repeat: no-repeat;
  transition: background-size .55s var(--ease);
}
.cta__email:hover,
.cta__email:focus-visible { background-size: 100% 2px, 100% 2px; }

/* Téléphone et WhatsApp, sous l'adresse : la même invitation par un autre
   canal. Deux lignes discrètes — l'email reste le geste principal du bloc,
   et le filet blanc à 45 % est celui de l'adresse, en plus fin. */
.cta__contacts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 28px;
  margin-top: clamp(12px, 1.3vw, 16px);
}
.cta__contacts a {
  padding-bottom: 3px;
  font-family: var(--font-heading);
  font-size: clamp(.9rem, 1.1vw, 1.02rem);
  font-weight: 700;
  color: #fff;
  border-bottom: 1px solid rgba(255, 255, 255, .45);
  transition: border-color .35s var(--ease);
}
.cta__contacts a:hover,
.cta__contacts a:focus-visible { border-bottom-color: #fff; }

/* --------------------------------------------------------------------------
   PASTILLES DE CONTACT — enveloppe, combiné, WhatsApp
   Le glyphe est un masque, pas une image : c'est `currentColor` qui le
   peint. Il est donc blanc sur le rouge du bloc d'appel, gris clair dans le
   pied de page, et il suit le lien au survol — le site est en noir, blanc et
   rouge POLYENCO, le vert de la marque WhatsApp y ferait une tache.

   Dimension en `em` : la pastille grandit avec le texte qui la porte, de la
   ligne de 15 px du pied de page jusqu'à l'adresse en 2,3 rem du bloc
   d'appel. Le calage vertical (-.16em) est commun aux trois, c'est ce qui
   aligne les glyphes entre eux quand les trois lignes se suivent.

   WHATSAPP vise l'adresse du lien, pas une classe : les quatre endroits où
   le numéro apparaît (panneau de navigation, pied de page, bloc d'appel,
   relevé de la page Contact) la reçoivent sans qu'on touche au balisage, et
   tout futur lien wa.me l'aura aussi.

   ENVELOPPE ET COMBINÉ sont au contraire nommés bloc par bloc : les trois
   blocs « Contact » du site. Le relevé de la page Contact, lui, ne passe
   plus par l'adresse du lien — ses cinq lignes ne sont pas toutes des
   liens, et elles portent désormais leur glyphe en propre (voir le bloc
   « LE RELEVÉ DE LA PAGE CONTACT », plus bas).

   L'icône est dans le flux du lien, donc le filet du bloc d'appel et les
   soulignés du pied de page et du menu passent aussi sous elle. C'est déjà
   le cas de la pastille WhatsApp depuis qu'elle existe : les trois lignes se
   comportent de la même façon.
   -------------------------------------------------------------------------- */
a[href^="https://wa.me/"]::before,
.cta__email::before,
.cta__contacts a[href^="tel:"]::before,
.site-footer__list a[href^="mailto:"]::before,
.site-footer__list a[href^="tel:"]::before,
.mobile-nav__contact a[href^="mailto:"]::before,
.mobile-nav__contact a[href^="tel:"]::before,
.mailblock__link::before {
  content: "";
  display: inline-block;
  width: 1.04em;
  height: 1.04em;
  margin-right: .46em;
  vertical-align: -.16em;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
}

a[href^="https://wa.me/"]::before {
  -webkit-mask-image: var(--icon-whatsapp);
          mask-image: var(--icon-whatsapp);
}

.cta__email::before,
.site-footer__list a[href^="mailto:"]::before,
.mobile-nav__contact a[href^="mailto:"]::before,
.mailblock__link::before {
  -webkit-mask-image: var(--icon-mail);
          mask-image: var(--icon-mail);
}

/* L'enveloppe du bloc d'appel est plus petite que les autres pastilles :
   elle accompagne une adresse composée à 34 px, pas une ligne de texte
   courant. À .78em elle mesure de 16 px sur un téléphone à 27 px sur un
   grand écran — assez pour se lire comme une enveloppe, assez peu pour que
   l'adresse garde sa colonne. L'écart est porté par le `gap` de la boîte,
   et le centrage par son `align-items` : plus rien à caler à la main. */
.cta__email::before {
  width: .78em;
  height: .78em;
  margin-right: 0;
}

/* Page Contact, même raison qu'au-dessus mais plus marquée : l'adresse y va
   jusqu'à 73,6 px. À 1.04em l'enveloppe mesurait 76 px et pesait plus lourd
   que l'adresse qu'elle annonce.

   La taille est en plus contrainte par la géométrie du bloc. Le corps suit
   6.6vw et la colonne 90vw environ, si bien que l'adresse occupe 92 à 94 %
   de sa ligne à TOUTES les largeurs en dessous de 1180 px — le rapport ne
   se desserre jamais. Relevé avec une pastille à .78em, il ne restait que
   1 à 8 px de marge entre 375 et 1024 px, et il en manquait 1 à 4 sous
   375 px : l'adresse passait à la ligne sous l'enveloppe.

   À .52em (+.22em d'écart) la pastille pèse .74em, soit 4,9vw contre les
   8,2vw disponibles : il reste 3,3vw de marge, donc 12 px sur un téléphone
   de 375 px et 34 px à 1024 px. La marge est proportionnelle comme le
   reste, elle ne peut plus se refermer. Le corps du texte ne bouge pas. */
.mailblock__link::before {
  width: .52em;
  height: .52em;
  margin-right: .22em;
  vertical-align: .01em;
}

.cta__contacts a[href^="tel:"]::before,
.site-footer__list a[href^="tel:"]::before,
.mobile-nav__contact a[href^="tel:"]::before {
  -webkit-mask-image: var(--icon-phone);
          mask-image: var(--icon-phone);
}


/* --------------------------------------------------------------------------
   LE RELEVÉ DE LA PAGE CONTACT — une pastille par ligne
   Cinq informations, cinq pastilles, toutes réglées pareil. Le combiné et le
   glyphe WhatsApp y étaient déjà, mais posés dans le flux du lien : ils
   passaient donc sous le filet du numéro, tandis que les trois lignes de
   texte — société, activité, localisation — n'avaient rien. Une rangée sur
   deux était marquée, et pas de la même façon.

   La pastille quitte donc le lien pour la valeur elle-même. Les cinq
   partent du même bord, à la même taille, et aucune n'est soulignée : le
   filet ne court plus que sous les deux numéros, là où il dit qu'on peut
   appuyer dessus. C'est le seul endroit du site où les pastilles marquent
   des lignes qui ne sont pas toutes des liens, d'où ce réglage à part.

   Le glyphe est nommé dans le HTML (data-icon), pas déduit de l'adresse du
   lien : trois des cinq lignes n'en sont pas. Le relevé de l'accueil
   partage la classe .about-facts et reste donc nu — il n'a ni numéro ni
   adresse, une pastille n'y désignerait rien.

   Le bloc est en flex plutôt qu'en ligne : sur un téléphone, la
   localisation passe sur deux lignes, et le texte doit s'aligner sous
   lui-même, pas repasser sous la pastille. Celle-ci garde sa place en tête,
   calée sur la première ligne.
   -------------------------------------------------------------------------- */
.about-facts__row[data-icon] dd {
  display: flex;
  align-items: flex-start;
  gap: .46em;
}

.about-facts__row[data-icon] dd::before {
  content: "";
  flex: none;
  width: 1.04em;
  height: 1.04em;
  /* centrée sur la première ligne, pas sur le bloc : la moitié de ce qui
     sépare l'interligne (1.5em, voir .about-facts dd) de la pastille. */
  margin-top: calc((1.5em - 1.04em) / 2);
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
}

.about-facts__row[data-icon="company"]  dd::before { -webkit-mask-image: var(--icon-company);  mask-image: var(--icon-company); }
.about-facts__row[data-icon="activity"] dd::before { -webkit-mask-image: var(--icon-activity); mask-image: var(--icon-activity); }
.about-facts__row[data-icon="place"]    dd::before { -webkit-mask-image: var(--icon-place);    mask-image: var(--icon-place); }
.about-facts__row[data-icon="phone"]    dd::before { -webkit-mask-image: var(--icon-phone);    mask-image: var(--icon-phone); }
.about-facts__row[data-icon="whatsapp"] dd::before { -webkit-mask-image: var(--icon-whatsapp); mask-image: var(--icon-whatsapp); }

/* La règle générale du § 22 pose le glyphe WhatsApp sur tout lien wa.me :
   ici la valeur le porte déjà, il serait en double. */
.about-facts__row[data-icon] dd a::before { content: none; }

.cta__actions {
  display: flex; flex-wrap: wrap; gap: 14px;
  margin-top: clamp(20px, 2.2vw, 26px);
}


/* ==========================================================================
   22 — CONTACT (coordonnées)
   ========================================================================== */

/* L'adresse officielle en très grand, entre deux filets. Le trait sous
   l'adresse passe du gris au rouge POLYENCO au survol. La présentation
   reprend .about-intro et .about-facts (section 20). */
/* Pas de filet en bas : le relevé qui suit porte déjà ses propres filets. */
/* L'adresse est le seul vrai geste de la page : son filet porte donc la
   denture, comme un titre de section. C'est le même signe, au même endroit
   — en tête du bloc qui compte. */
.mailblock {
  position: relative;
  margin-top: clamp(32px, 4vw, 52px);
  padding-top: clamp(26px, 3vw, 40px);
}
.mailblock::before {
  content: "";
  position: absolute; left: 0; right: 0; top: 0;
  height: var(--tooth-depth);
  background-image: var(--rule-teeth), linear-gradient(var(--line-strong), var(--line-strong));
  background-repeat: no-repeat, no-repeat;
  background-size: calc(var(--tooth-pitch) * 4) var(--tooth-depth), 100% var(--rail-w);
  background-position: left top, left bottom;
}
.mailblock__label { color: var(--text-muted); }
.mailblock__link {
  display: inline-block;
  margin-top: clamp(14px, 1.6vw, 22px);
  padding-bottom: clamp(6px, .8vw, 12px);
  font-family: var(--font-heading);
  font-size: clamp(1.2rem, 6.6vw, 4.6rem);
  font-weight: 900;
  line-height: 1.05;
  letter-spacing: -.02em;
  color: var(--text);
  background-image:
    linear-gradient(var(--polyenco-red), var(--polyenco-red)),
    linear-gradient(var(--line-strong), var(--line-strong));
  background-size: 0% 3px, 100% 3px;
  background-position: 0 100%, 0 100%;
  background-repeat: no-repeat;
  transition: background-size .6s var(--ease);
}
.mailblock__link:hover,
.mailblock__link:focus-visible { background-size: 100% 3px, 100% 3px; }


/* ==========================================================================
   23 — PIED DE PAGE
   ========================================================================== */

/* Le pied de page ferme chaque page sur l'arête dentée du symbole, en
   rouge et sur toute la largeur : on quitte la page sur la matière usinée
   du logo, pas sur un trait neutre.

   Denture retournée (--tooth-mask-down) : le filet plein est en haut, les
   dents descendent dans l'anthracite du pied. Elles restent donc lisibles
   que la section précédente soit rouge ou sombre. Dans l'autre sens, sous
   l'appel à l'action, on aurait lu du rouge troué de noir. */
.site-footer {
  position: relative;
  background: var(--polyenco-anthracite);
  padding-top: clamp(48px, 5.5vw, 76px);
}
.site-footer::before {
  content: "";
  position: absolute; left: 0; right: 0; top: 0;
  height: var(--tooth-depth);
  background: var(--polyenco-red);
  -webkit-mask-image: var(--tooth-mask-down);
          mask-image: var(--tooth-mask-down);
  -webkit-mask-repeat: repeat-x;
          mask-repeat: repeat-x;
  -webkit-mask-size: var(--tooth-pitch) var(--tooth-depth);
          mask-size: var(--tooth-pitch) var(--tooth-depth);
}

.site-footer__grid {
  display: grid;
  grid-template-columns: 1.6fr 1fr 1.3fr 1.2fr;
  gap: clamp(30px, 3.5vw, 56px);
  padding-bottom: 48px;
}

.site-footer__brand img { width: 190px; }
.site-footer__name {
  margin-top: 24px;
  font-size: .75rem;
  color: var(--text);
}
.site-footer__slogan {
  margin-top: 10px;
  font-size: .75rem;
  color: var(--text-soft);
}
.site-footer__place { margin-top: 22px; color: var(--text-muted); }

.site-footer h2 { color: var(--text); margin-bottom: 18px; }

.site-footer__list li + li { margin-top: 12px; }
.site-footer__list a,
.site-footer__list span {
  font-size: .92rem;
  color: var(--text-soft);
  transition: color .25s var(--ease);
}
.site-footer__list a:hover { color: var(--text); }

/* Bandeau des partenaires, sur toute la largeur */
.site-footer__partners {
  padding-block: 18px 22px;
  border-top: 1px solid var(--line);
}
/* Un peu moins d'air sous l'intitulé qu'ailleurs dans le pied de page : les
   autres colonnes ouvrent une liste de liens qu'on parcourt, celle-ci une
   suite de noms qu'on balaie. La règle ne vise que ce bloc — les intitulés
   Navigation, Solutions et Contact gardent leur écart. */
.site-footer__partners h2 { margin-bottom: 12px; }
/* Les noms de partenaires sont posés en capitales. Passer par la feuille de
   style plutôt que par le HTML garde les accents corrects (É, È) et évite de
   réécrire la même liste sur les vingt-deux pages. Un peu d'interlettrage :
   sans lui, une suite de capitales se lit mal à cette taille. */
/* Trente-cinq noms : c'est la longueur de la liste qui fait la hauteur du
   bloc, pas la taille du texte. On joue donc d'abord sur ce qui sépare les
   noms — la gouttière passe de 36 à 18 px, l'interligne de 10 à 5 — puis
   sur le corps, ramené à .76rem, soit celui de la signature juste au-dessus
   dans le même pied de page. Chaque ligne en porte ainsi une moitié de plus
   et il en faut d'autant moins. */
.site-footer__partner-list {
  display: flex; flex-wrap: wrap;
  gap: 5px 18px;
  font-size: .76rem;
  line-height: 1.45;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--text-soft);
}

.site-footer__bottom {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 16px;
  padding-block: 26px;
  border-top: 1px solid var(--line);
  font-size: .84rem;
  color: var(--text-muted);
}


/* ==========================================================================
   24 — RESPONSIVE
   ========================================================================== */

@media (max-width: 1180px) {
  .site-footer__grid { grid-template-columns: 1fr 1fr; }
}

/* Bascule vers le menu mobile */
@media (max-width: 1080px) {
  :root { --header-h: 72px; }
  .nav { display: none; }
  .burger { display: block; }
  .brand img { width: 132px; }
}

@media (max-width: 1100px) {
  /* En deux colonnes, l'intitulé le plus long (« Accompagnement ») ne tient
     plus dans sa colonne : les principes passent en pleine largeur. */
  .principle { grid-template-columns: 1fr; padding-left: 26px; }
}

@media (max-width: 1000px) {
  /* Trois étapes tiennent encore de front ici : on resserre seulement la
     gouttière, sans casser la rangée — une grille 2 × 2 laisserait une
     case vide. */
  .process {
    --pgap: clamp(14px, 2vw, 24px);
  }
}

@media (max-width: 900px) {
  .precision__grid,
  .principles,
  .cta__grid { grid-template-columns: 1fr; }

  .principles__head { position: static; }

  .principle { padding-left: 22px; }

  .scope { grid-template-columns: 1fr; }

  /* Le titre occupe désormais toute la largeur : la mise au net passerait
     derrière les mots. On la retire plutôt que de l'affaiblir. */
  .setout-draw { display: none; }
}

/* --------------------------------------------------------------------------
   SOLUTIONS DÉTAILLÉES : côte à côte tant que la largeur le permet
   À 768 px, la colonne de texte fait encore 380 px et la photo 270 px : la
   composition du grand écran tient, on la garde. Ce n'est qu'en dessous de
   760 px qu'elle devient trop étroite et que les deux passent l'un sous
   l'autre — la photo d'abord, le texte ensuite, sur les blocs inversés
   comme sur les autres.
   -------------------------------------------------------------------------- */
@media (max-width: 760px) {
  .solution { grid-template-columns: 1fr; gap: 32px; }
  .solution--reverse .solution__media { order: 0; }
  .solution__media { position: relative; top: auto; }
  .solution__media img { aspect-ratio: 16/10; }
}

/* --------------------------------------------------------------------------
   TABLETTE EN PORTRAIT : le hero rendu au rapport de la vidéo
   Une tablette tenue verticalement donne un hero presque carré, où le plan
   16:9 perd la moitié de sa largeur. La hauteur du hero n'étant qu'un
   plancher, l'abaisser ne coupe aucun contenu : le cadre se rapproche
   simplement du format de la vidéo. Le cadrage, lui, reste celui du grand
   écran (object-position 70 %), comme sur un ordinateur.
   -------------------------------------------------------------------------- */
@media (min-width: 701px) and (max-width: 1000px) and (orientation: portrait) {
  .hero {
    min-height: min(calc(100svh - var(--header-h)), 560px);
    padding-block: clamp(56px, 7vh, 96px) clamp(32px, 4vh, 56px);
  }
}

@media (max-width: 700px) {
  html { scroll-padding-top: 80px; }

  /* Logo compact sur mobile : uniquement le symbole */
  .brand__full   { display: none; }
  .brand__symbol { display: block; }

  /* Un plein écran de téléphone est deux fois plus haut que large : le plan
     16:9 y perdait les deux tiers de sa largeur. La hauteur n'est qu'un
     plancher — le contenu du hero la repousse de lui-même — donc l'abaisser
     ne coupe rien : cela rapproche seulement le cadre du rapport de la
     vidéo, et la part réellement visible passe d'un tiers à près de la
     moitié. Le desktop n'est pas touché. */
  .hero {
    min-height: min(calc(100svh - var(--header-h)), 430px);
    padding-block: 44px 32px;
  }
  .hero--compact { min-height: auto; padding-block: 72px 60px; }
  .hero--editorial { min-height: 62svh; padding-block: 88px 44px; }
  /* Cadrage portrait : le marquage POLYENCO du casque reste entier. Dans la
     vidéo il occupe 67-82 % de la largeur au début et 62-76 % à la fin :
     un cadrage à 80 % contient les deux positions. */
  .hero__media--video img { object-position: 84% 50%; }
  .hero__video { object-position: 80% 50%; }
  .hero::before {
    background:
      linear-gradient(0deg, #0A0A0B 0%, rgba(10,10,11,.8) 45%, rgba(10,10,11,.45) 100%);
  }

  /* Processus : les trois étapes restent de front, même sur un téléphone.
     Ce sont les trois temps d'un seul processus — mises l'une sous l'autre
     elles se liraient comme trois sujets séparés, et « étude → fabrication
     → montage » se perdrait. On resserre donc la gouttière et la
     typographie plutôt que de casser la rangée ; le cadrage des photos ne
     change pas (5/4, comme sur grand écran), et le filet reste horizontal
     avec sa dent rouge.
     Réglage propre à cette section : les autres galeries gardent leur
     mise en page — deux colonnes, et pleine largeur pour les panoramiques. */
  .process { --pgap: 10px; }

  .process__name {
    font-size: clamp(.68rem, 2.9vw, .95rem);
    line-height: 1.2;
    letter-spacing: -.015em;
  }
  .process__text {
    max-width: none;
    font-size: clamp(.66rem, 2.6vw, .84rem);
    line-height: 1.45;
  }
  .process__rail { margin-block: 14px 10px; }

  .blueprint { outline-offset: 6px; }
  /* Le relevé est réduit de moitié : traits épaissis pour rester lisibles */
  .bp-detail { stroke-width: 7; }
  .bp-leader { stroke-width: 5; }
  .blueprint__tag { left: 70%; padding: 5px 8px; }
  .blueprint__tag small { display: none; }

  .spec-list__row { grid-template-columns: 1fr; }
  .site-footer__grid { grid-template-columns: 1fr; gap: 40px; }
  .lang-switch a { padding-inline: 9px; }
}

/* Processus sur téléphone, JS actif : cartes empilées. La carte active
   occupe le centre à pleine lecture, les deux autres dépassent derrière,
   à gauche et à droite ; un glisser horizontal fait tourner l'ordre
   (js/main.js). Sans JS, la rangée ci-dessus s'applique. */
.process__count { display: none; }

@media screen and (max-width: 700px) {
  .process.is-stack {
    grid-template-columns: minmax(0, 1fr);
    overflow: hidden;
    overflow: clip;
    touch-action: pan-y;
    -webkit-user-select: none;
            user-select: none;
  }
  .process.is-stack .process__step {
    grid-area: 1 / 1;
    justify-self: center;
    width: 80%;
    z-index: 1;
    opacity: .5;
    transform: translateX(calc(var(--pos, 0) * 30%)) scale(.82);
    transition: transform .45s var(--ease), opacity .45s var(--ease);
    cursor: pointer;
  }
  .process.is-stack .process__step[data-pos="-1"] { --pos: -1; }
  .process.is-stack .process__step[data-pos="1"]  { --pos: 1; }
  .process.is-stack .process__step.is-active {
    z-index: 2;
    opacity: 1;
    transform: none;
    cursor: auto;
  }
  .process.is-stack .process__media img { -webkit-user-drag: none; }
  .process.is-stack .process__step.is-active .process__media img { filter: grayscale(0) contrast(1.02) brightness(1); }

  /* Seule la carte centrale porte son texte, à taille de lecture. */
  .process.is-stack .process__rail,
  .process.is-stack .process__name,
  .process.is-stack .process__text {
    opacity: 0;
    transition: opacity .3s var(--ease);
  }
  .process.is-stack .is-active .process__rail,
  .process.is-stack .is-active .process__name,
  .process.is-stack .is-active .process__text { opacity: 1; }
  .process.is-stack .process__rail::before { right: 0; }
  .process.is-stack .process__name { font-size: 1.15rem; color: var(--text); }
  .process.is-stack .process__text { font-size: .95rem; line-height: 1.55; }

  .process__count {
    display: block;
    margin-top: 18px;
    text-align: center;
    font-size: .78rem;
    letter-spacing: .12em;
    font-variant-numeric: tabular-nums;
    color: var(--text-soft);
  }
  .process__count span { color: var(--text); }
}

@media screen and (max-width: 700px) and (prefers-reduced-motion: reduce) {
  .process.is-stack .process__step { transition: none; }
}


/* ==========================================================================
   24b — SOLUTIONS : la liste éditoriale (téléphone ET appareils tactiles)
   Même condition que le repli de la section 13 : ces règles suivent la
   version repliée partout où elle s'affiche. Elles sont sorties du bloc
   « 700 px » ci-dessus pour ne pas emmener avec elles le reste des règles
   téléphone (typographie, pied de page, en-tête) sur une tablette, qui doit
   garder la composition de bureau sur tout le reste de la page.
   ========================================================================== */
@media (max-width: 700px), (any-pointer: coarse) and (max-width: 1366px) {
  /* --------------------------------------------------------------------
     SOLUTIONS : un index éditorial, une solution par ligne
     Sur un téléphone, deux colonnes de cartes donnaient six vignettes
     carrées empilées — un rayon de boutique, et beaucoup de défilement
     pour six entrées. On repasse donc à une ligne par solution : le nom à
     gauche, la photo à droite, un filet fin entre deux lignes. Toute la
     ligne reste le lien vers la page dédiée.

     L'ordre du HTML place la photo avant le texte (c'est ce que veut le
     grand écran) : `order` les inverse à l'affichage sans toucher au
     balisage, donc sans changer l'ordre de lecture au clavier ni pour un
     lecteur d'écran, où le nom vient de toute façon avant l'image, qui est
     décorative.

     Le texte de présentation reste replié : sur une demi-largeur il
     tripleraient la hauteur de la ligne. Le nom et la photo suffisent à
     choisir ; la page de la solution dit le reste.
     -------------------------------------------------------------------- */
  .sol-list {
    grid-template-columns: 1fr;
    gap: 12px;
  }
  .sol-item {
    border: 0;
    border-bottom: 1px solid var(--line);
    border-radius: 0;
    padding-bottom: 12px;
    background: none;
  }
  .sol-item:last-child { border-bottom: 0; padding-bottom: 0; }

  .sol-link {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 42%;
    gap: 14px;
    align-items: stretch;
    min-height: clamp(104px, 31vw, 132px);
  }

  .sol-link__media {
    order: 2;
    position: relative;
    display: block;
    border-radius: 4px;
  }
  /* La photo est posée en absolu dans sa case : elle la remplit sans jamais
     peser sur la hauteur de la ligne. Sans cela, les deux photos en portrait
     (Traitement de surface, Livraison) imposaient leur propre hauteur et
     faisaient des lignes de 184 px là où les autres en font 121.
     object-fit: cover garde les proportions d'origine — la photo est
     rognée sur ses bords, jamais déformée. */
  .sol-link__media img,
  .sol-link__media video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
    object-fit: cover;
  }

  .sol-link__body,
  .sol-item.is-active .sol-link__body {
    order: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 4px 0;
  }

  .sol-link__title {
    font-size: clamp(1rem, 4.6vw, 1.2rem);
    line-height: 1.22;
  }
  /* L'accent rouge : la dent du symbole, posée au-dessus du nom. C'est le
     seul rouge de la liste, et le même repère que dans la galerie. */
  .sol-link__title::before {
    content: "";
    display: block;
    width: 9px;
    height: 6px;
    margin-bottom: 9px;
    background: var(--polyenco-red);
    -webkit-clip-path: var(--tooth-clip);
            clip-path: var(--tooth-clip);
  }

  .sol-link__more,
  .sol-item.is-active .sol-link__more { grid-template-rows: 0fr; }

  /* Sur une tablette la ligne est deux fois plus large que sur un téléphone :
     sans borne, la photo prendrait 42 % de 1200 px. On la plafonne pour
     garder la proportion vue sur mobile — le texte respire, la photo reste
     une vignette. En dessous de 700 px, 42 % vaut moins que 300 px : le
     rendu téléphone est inchangé. */
  .sol-link { grid-template-columns: minmax(0, 1fr) clamp(120px, 42%, 300px); }
}

@media (max-width: 420px) {
  .hero__actions .btn,
  .cta__actions .btn { width: 100%; }

  /* Très petits écrans : logo et sélecteur de langue resserrés pour que
     l'en-tête tienne dans la largeur. */
  .site-header__inner { gap: 14px; }
  .brand img { width: 104px; }
  .lang-switch a { padding-inline: 7px; }
}


/* ==========================================================================
   25 — PRÉFÉRENCES UTILISATEUR
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    transition-delay: 0s !important;
  }
  .js .reveal { opacity: 1; transform: none; }
  .hero__video { display: none; }
}

/* ==========================================================================
   26 — IMPRESSION ET EXPORT PDF
   Le site est sombre : sans impression des aplats, le texte blanc
   disparaîtrait sur du papier blanc. On force donc les fonds, puis on fige
   tout ce qui dépend du défilement, du survol ou d'un geste : blocs
   révélés, légendes, relevé technique, défilé des logos, liste des
   solutions et parcours de l'ouvrage. Rien ne doit manquer sur le PDF.

   Largeur d'une page A4 : environ 794 px CSS. Ce sont donc les règles
   responsives de la section 24 qui composent la page imprimée.
   ========================================================================== */

@page {
  size: A4;
  margin: 0;   /* le fond sombre couvre la page entière, comme à l'écran */
}

@media print {

  /* --- Fonds, aplats, dégradés et photos : imprimés tels quels --- */
  *, *::before, *::after {
    -webkit-print-color-adjust: exact !important;
            print-color-adjust: exact !important;
    animation: none !important;
    transition: none !important;
  }

  /* Respiration resserrée : sur écran l'air est une qualité, sur papier il
     multiplie les pages à moitié vides. */
  :root { --section-padding: 48px; }

  html { background: var(--bg); }
  body {
    background: var(--bg);
    color: var(--text);
    overflow: visible;
  }

  /* L'écran d'entrée n'a pas de sens sur papier, et une impression lancée
     pendant l'animation le fixerait sur sa première image — un aplat rouge
     en pleine page. */
  .entrance { display: none !important; }

  /* --- Rien n'est réservé au défilement ni au survol --- */
  .js .reveal,
  .reveal { opacity: 1 !important; transform: none !important; }

  /* La mise au net du hero est une animation : privée d'animation elle
     resterait à son premier instant, c'est-à-dire un cadre vide. On
     imprime donc son état d'arrivée, entièrement tracée. */
  .setout-draw { opacity: .45 !important; }
  .setout-draw path { stroke-dashoffset: 0 !important; }

  .work__figure::after,
  .work__caption,
  .folio__caption,
  .folio__media::after,
  .blueprint .bp-fade,
  .blueprint__tag { opacity: 1 !important; transform: none !important; }
  .folio__item.is-feature .folio__media::after { opacity: 0 !important; }
  .blueprint .bp-draw { stroke-dashoffset: 0 !important; }
  .work__figure img,
  .folio__media img,
  .process__media img,
  .mosaic__item img { transform: none !important; }

  /* --- Commandes d'interface : l'en-tête reste pour le logo, le reste
         disparaît (doublons de navigation, boutons de filtre, curseur) --- */
  .site-header { position: static !important; }
  .mobile-nav,
  .nav-scrim,
  .burger,
  .skip-link,
  .subnav,
  .filters { display: none !important; }
  .principles__head { position: static !important; }

  /* --- Les hauteurs d'écran deviennent des hauteurs de page --- */
  .hero,
  .hero--compact,
  .hero--editorial {
    min-height: auto !important;
    padding-block: 56px 48px !important;
  }
  .hero__video { display: none !important; }
  .sol-stage { min-height: 0 !important; }

  /* Le voile du hero superpose trois dégradés translucides : certains
     lecteurs PDF les rendent mal (teinte parasite). Un aplat uni donne
     exactement le même service — assombrir la photo sous le titre — et se
     rend à l'identique partout. */
  .hero::before,
  .hero--compact::before,
  .hero--editorial::before {
    background: rgba(10, 10, 11, .62) !important;
  }

  /* --- Réalisations : tous les projets, y compris ceux qu'un filtre
         masquait à l'écran, sur deux colonnes régulières --- */
  .folio__item[hidden] { display: block !important; }
  .folio__item,
  .folio.is-laid .folio__item { flex: 0 0 calc(50% - var(--folio-gap)) !important; }
  .folio__item.is-feature { flex-basis: 100% !important; }
  .folio__item.is-feature .folio__media { max-height: none !important; }

  /* --- Partenaires : le défilé devient une grille, une seule série --- */
  .marquee__track { width: auto !important; }
  .marquee__group {
    flex-wrap: wrap;
    justify-content: center;
    row-gap: 24px;
    width: 100%;
  }
  .marquee__group[aria-hidden="true"] { display: none !important; }
  .marquee__viewport {
    overflow: visible;
    -webkit-mask-image: none !important;
            mask-image: none !important;
  }

  /* --- Étude de cas : les quatre étapes sur deux colonnes, images
         entières et non grisées, dents rouges en place. --- */
  .flow { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; row-gap: 26px; }
  .flow__step:nth-child(2n) .flow__rail::before { right: 0; }
  .flow__node { background: var(--polyenco-red) !important; }
  .flow__media { max-width: none !important; }
  .flow__media img { filter: none !important; }
  .flow__media--render::after { opacity: .12 !important; }

  /* --- Avant / après : la barre n'a plus de sens sur papier. Les deux
         vues entières, l'une sous l'autre, chacune avec son étiquette. --- */
  .prepost { display: block !important; }
  .prepost__frame {
    display: flex;
    flex-direction: column;
    gap: 14px;
    max-width: none !important;
    aspect-ratio: auto !important;
    overflow: visible !important;
    background: none !important;
  }
  .prepost__layer--before,
  .prepost__layer--after {
    position: relative !important;
    inset: auto !important;
    clip-path: none !important;
    overflow: hidden;
    border-radius: var(--radius);
  }
  .prepost__layer--before { order: 1; }
  .prepost__layer--after  { order: 2; }
  .prepost__img {
    position: static !important;
    width: 100% !important;
    height: auto !important;
  }
  .prepost__divider,
  .prepost__range { display: none !important; }
  .prepost__caption { margin-top: 18px; }

  /* --- Coupes de page : aucun bloc ni aucune photo coupé en deux --- */
  h1, h2, h3, h4 { break-after: avoid; break-inside: avoid; }
  p, li, dd, dt, figcaption { orphans: 3; widows: 3; }

  .hero,
  .cta,
  .work,
  .folio__item,
  .principle,
  .process__step,
  .mosaic__item,
  .solution,
  .scope__col,
  .sol-item,
  .spec-list__row,
  .about-facts__row,
  .blueprint,
  .marquee,
  .flow__step,
  .prepost__layer--before,
  .prepost__layer--after,
  .prepost__caption,
  .mailblock,
  .site-footer__grid > *,
  .site-footer__partners { break-inside: avoid; }
}
