/* --- Palette Polaire ------------------------------------------------------
   Le thème passe entièrement par ces variables, déclarées dans :root de
   style.css. Les redéfinir ici suffit : nul besoin de toucher la feuille du
   thème, et les teintes dérivées (survols, ombres) suivent.

   Les trois valeurs de noir du thème (--black, --off-black, --off-black-shade)
   sont ramenées à une seule : le pied de page était en #151612 et les sections
   sombres en #1d1e19, d'où deux noirs différents à l'écran. */
:root {
  --black: #1f1c17;
  --off-black: #1f1c17;
  --off-black-shade: #1f1c17;
  --off-black-tint: #3a362f;   /* éclairci, pour les survols de boutons */
  --fts-soot: #1f1c17;

  --yellow: #ffeb00;
  --yellow-tint: #fff37a;
  --yellow-shade: #e0cf00;
}

/* Deux endroits où le thème écrit le noir en dur plutôt que par variable. */
.nav,
.nav.nav--show {
  --nav-color: #1f1c17;
}

.b-fts-tiles {
  background: #1f1c17;
}

/* --- Logo Polaire --------------------------------------------------------
   Le fichier source est monochrome noir sur fond transparent. Le thème attend
   un logo qui suit la couleur du contexte : on l'inverse en blanc là où le fond
   est sombre (nav en mode color-flip, pied de page). Pour changer de logo, il
   suffit de remplacer le PNG ou de pointer ailleurs dans les règles ci-dessous.
   Un logo en couleur demanderait de fournir deux fichiers au lieu d'inverser. */

/* Le logo Polaire est plus dense que le mot-signe d'origine : à hauteur égale il
   pesait trop dans la barre. On reprend les paliers du thème, un cran plus bas. */
.c-NavLogo__animation {
  width: auto;
  height: 26px;
}

@media (min-width: 768px) {
  .c-NavLogo__animation {
    height: 32px;
  }
}

.c-NavLogo__animation img {
  display: block;
  height: 100%;
  width: auto;
}

/* Nav posée sur un fond sombre : logo en blanc.

   Le sélecteur reste volontairement permissif — il ne préjuge pas de l'élément
   qui porte `nav--color-flip`. Deux composants de main.js la posent en effet à
   deux endroits différents : FlipNavColour sur la nav
   (`toggleClass:{targets:this.nav}`), et sticky() sur le <body> quand on revient
   en haut des pages projet et de Contact. Exiger `.nav.nav--color-flip` casse le
   second cas et le logo reste noir sur fond sombre. */
.nav--color-flip .c-NavLogo__animation img {
  filter: invert(1);
}

/* Nav remplie au scroll : le thème lui donne un fond clair et repasse
   `--nav-color` au sombre (`.nav.nav--show`). Les liens suivent cette variable,
   pas le logo : c'est une image inversée par filtre, qu'il faut désinverser ici,
   sans quoi elle resterait blanche sur blanc et disparaîtrait.

   La condition porte sur la nav seule, sans `nav--color-flip` : c'est ce qui
   permet de rattraper le cas où la classe est restée sur le <body>. Sur une page
   sans héros sombre la règle s'applique aussi, sans effet, `filter: none` étant
   déjà la valeur par défaut. */
.nav.nav--show .c-NavLogo__animation img {
  filter: none;
}

/* Menu ouvert : le thème repasse la nav en clair, le logo suit. */
body.nav--open .c-NavLogo__animation img {
  filter: none;
}

/* Pied de page : fond toujours sombre. */
.c-LogoHoverSwap__logo img {
  display: block;
  width: 100%;
  height: auto;
  filter: invert(1);
}

/* Le thème réservait 50 à 80 px au-dessus du mot-signe. Le logo Polaire porte
   déjà sa propre marge haute : on colle le bloc aux liens du pied de page. */
.c-FooterLogo__logo {
  padding-top: 0;
}

/* Écran de chargement. Le thème y pose une image de fond, pas une animation :
   il suffit de la remplacer. Les proportions suivent celles du fichier
   (2822 x 1113), sans quoi le logo flotterait dans une boîte trop large. */
.c-Preloader {
  background: #1f1c17; /* le noir Polaire, aligné sur --black */
}

.c-Preloader--static .c-Preloader__animation {
  width: 45%;
  padding-bottom: 17.75%;
  /* Le PNG est noir sur fond transparent : on s'en sert comme masque et on
     peint la forme en blanc. Inverser l'élément entier aurait aussi inversé
     son fond, et le masque donne un blanc franc quelle que soit la source. */
  background-image: none;
  background-color: #ffffff;
  -webkit-mask: url("/assets/images/polaire-logo-noir.png") center center / contain no-repeat;
  mask: url("/assets/images/polaire-logo-noir.png") center center / contain no-repeat;
  animation: polaire-preloader-zoom 0.9s cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* On anime la propriété `scale`, pas `transform` : à la sortie du préchargeur
   le thème pose sa propre transform sur cet élément (GSAP, yPercent), et une
   animation CSS sur transform la neutraliserait. Les deux se composent. */
@keyframes polaire-preloader-zoom {
  from { opacity: 0; scale: 0.82; }
  to   { opacity: 1; scale: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .c-Preloader--static .c-Preloader__animation { animation: none; }
}


/* --- Formulaire de contact ------------------------------------------------
   Le formulaire d'origine était injecté par Gravity Forms depuis le WordPress
   de Fiasco : il ne pouvait pas se charger ici. Il est reconstruit en HTML, en
   reprenant les classes de Gravity Forms — toute la mise en forme du thème est
   scopée sur .gform_wrapper et reste donc intacte.

   Ne manquaient que les primitives de grille, qui vivaient dans la feuille du
   plugin (supprimée car morte à 99 %). Elles sont reprises ici à l'identique
   depuis gravity-forms-theme-foundation.css : mêmes valeurs, même grille de 12
   colonnes. */

.gform_wrapper {
  --gf-form-gap-x: 16px;
  --gf-form-gap-y: 40px;
}

.gform-theme--foundation .gform_fields {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-template-rows: repeat(auto-fill, auto);
  column-gap: var(--gf-form-gap-x);
  row-gap: var(--gf-form-gap-y);
  inline-size: 100%;
}

.gform-theme--foundation .gfield {
  grid-column: span 12;
}

.gform-theme--foundation .gfield--width-half {
  grid-column: span 6;
}

.gform_wrapper .gfield_label {
  display: block;
  margin-bottom: 8px;
}

.gform_wrapper input:not([type=checkbox]),
.gform_wrapper textarea {
  inline-size: 100%;
  border: 1px solid var(--field-border);
  border-radius: var(--field-radius);
  margin-bottom: 0;
}

.gform_wrapper .gfield textarea {
  min-height: 132px;
  resize: vertical;
}

/* Case de consentement : la puce et son libellé côte à côte.

   Le thème pose `appearance: none` sur les cases sans rien dessiner à la
   place : ni bordure, ni coche. La case cochée était donc identique à la case
   vide — le champ étant obligatoire, on se retrouvait bloqué à l'envoi devant
   une case qu'on croyait n'avoir jamais réussi à cocher. On redessine les deux
   états : un carré bordé au repos, un carré vert marqué d'une coche une fois
   coché. Le `!important` sur la couleur de fond répond à celui du thème, qui
   la reprend sur un sélecteur à quatre niveaux. */
.gform_wrapper .ginput_container_consent {
  display: flex;
  gap: 14px;
  align-items: flex-start;
}

.gform_wrapper .ginput_container_consent input[type=checkbox] {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  min-height: 0;
  margin-top: 1px;
  border: 1px solid rgba(248, 249, 243, 0.45) !important;
  border-radius: 4px;
  background-color: rgba(248, 249, 243, 0.06) !important;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 13px 13px;
  cursor: pointer;
}

/* Le thème pose une transition sur les champs de formulaire. Sur une case,
   elle ne se terminait jamais : Chrome gardait la transition « running » et
   la valeur animée l'emportait sur toute déclaration, `!important` en style en
   ligne compris — la case cochée gardait le fond de la case vide. Le passage
   au vert doit être net de toute façon : on coupe l'animation sur ce champ. */
.gform_wrapper .ginput_container_consent input[type=checkbox] {
  transition: none !important;
}

.gform_wrapper .ginput_container_consent input[type=checkbox]:hover {
  border-color: rgba(248, 249, 243, 0.85) !important;
}

.gform_wrapper .ginput_container_consent input[type=checkbox]:checked {
  border-color: var(--polaire-succes) !important;
  background-color: var(--polaire-succes) !important;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13 13'%3E%3Cpath d='M2 6.8 5 9.8 11 3.2' fill='none' stroke='%23ffffff' stroke-width='2.1' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

.gform_wrapper .ginput_container_consent input[type=checkbox]:focus-visible {
  outline: 2px solid var(--off-white);
  outline-offset: 2px;
}

/* Le libellé commande la case : tout le texte devient cliquable. */
.gform_wrapper .ginput_container_consent .gfield_consent_label {
  cursor: pointer;
}

/* Le thème positionne ce champ en bas à droite de .gform_fields. */
.gform-theme--foundation .gfield--type-submit {
  display: flex;
}

/* Champ leurre de Web3Forms : jamais visible pour un humain. */
.gform_wrapper input[name=botcheck] {
  display: none !important;
}

/* --- Grille des projets ---------------------------------------------------
   Le thème répartit les vignettes dans deux colonnes flex (gauche / droite)
   au gabarit décalé : largeurs et marges dépendent de la position de chaque
   vignette. Dès qu'une colonne est vide — c'est le cas de la seconde grille
   des études de cas — toutes les vignettes s'empilent en une seule file.
   On aplatit les colonnes (display: contents) pour reposer les vignettes dans
   une grille à deux par ligne, dans l'ordre du HTML. */
@media (min-width: 768px) {
  .projects-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 80px 32px;
    align-items: start;
  }

  .projects-grid__column {
    display: contents;
  }

  /* Les largeurs et marges du gabarit décalé sont posées en !important sur des
     sélecteurs à cinq classes (nth-child, ar-*) : il faut la même spécificité
     pour les neutraliser, d'où la classe répétée. */
  .projects-grid .projects-grid__column .tile-project.tile-project.tile-project {
    width: 100% !important;
    margin: 0 !important;
    /* Les vignettes portent aussi les classes de colonnes du gabarit (c4-l,
       c-all) : dans une grille à deux colonnes elles s'étaleraient sur toute
       la ligne. Chaque vignette occupe désormais une case. */
    grid-column: auto !important;
  }
}

@media (min-width: 1024px) {
  .projects-grid {
    gap: 120px 32px;
  }
}


/* --- Visualiseur vidéo interne --------------------------------------------
   Le thème ne fournit pas de lecteur modal. Celui-ci repose sur <dialog>, d'où
   les remises à zéro ci-dessous : le navigateur lui pose par défaut une bordure,
   un fond blanc, un padding et des dimensions maximales dont on ne veut pas. */
.c-VideoViewer {
  --viewer-marge: clamp(16px, 3vw, 44px);

  width: 100vw;
  height: 100vh;
  height: 100dvh;
  max-width: 100vw;
  max-height: 100vh;
  max-height: 100dvh;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  overflow: hidden;
  color: var(--off-white, #f8f9f3);
}

/* <dialog> reste en display:none tant qu'il n'est pas ouvert : on ne peut donc
   pas poser le flex sur la règle de base, il écraserait ce masquage. */
.c-VideoViewer[open] {
  display: flex;
  align-items: center;
  justify-content: center;
}

.c-VideoViewer::backdrop {
  background: rgba(10, 10, 8, 0.94);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  animation: viewer-fond 0.35s ease both;
}

/* Le cadre porte le rapport 16/9 et s'inscrit au plus grand dans l'écran : sa
   largeur est bornée à la fois par la largeur et par la hauteur disponibles,
   d'où le calc. dvh et non vh, sinon la barre d'adresse mobile rogne l'image.
   La marge laisse respirer l'arrondi et dégage la place de la croix. */
.c-VideoViewer__frame {
  position: relative;
  width: min(
    100vw - var(--viewer-marge) * 2,
    (100vh - var(--viewer-marge) * 2) * 16 / 9
  );
  width: min(
    100vw - var(--viewer-marge) * 2,
    (100dvh - var(--viewer-marge) * 2) * 16 / 9
  );
  aspect-ratio: 16 / 9;
  background: #000;
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.55);
  animation: viewer-cadre 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.c-VideoViewer__frame iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Posée sur l'écran et non sur le cadre : en grand format la vidéo touche
   presque les bords, il n'y reste pas de marge où loger la croix. */
.c-VideoViewer__close,
.c-Reservation__close {
  position: absolute;
  top: var(--viewer-marge);
  right: var(--viewer-marge);
  z-index: 1;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid rgba(248, 249, 243, 0.25);
  border-radius: 50%;
  background: rgba(10, 10, 8, 0.4);
  color: inherit;
  cursor: pointer;
  animation: viewer-fond 0.5s 0.1s ease both;
  transition: border-color 0.25s ease, background-color 0.25s ease,
    transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}

.c-VideoViewer__close:hover,
.c-VideoViewer__close:focus-visible,
.c-Reservation__close:hover,
.c-Reservation__close:focus-visible {
  border-color: rgba(248, 249, 243, 0.9);
  background: rgba(248, 249, 243, 0.12);
  transform: rotate(90deg);
}

.c-VideoViewer__close:focus-visible,
.c-Reservation__close:focus-visible {
  outline: 1px solid var(--off-white, #f8f9f3);
  outline-offset: 3px;
}

/* Croix dessinée en CSS : aucune icône à charger. Volontairement petite et
   fine, pour rester dans le trait du reste du site. */
.c-VideoViewer__close::before,
.c-VideoViewer__close::after,
.c-Reservation__close::before,
.c-Reservation__close::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 13px;
  height: 1px;
  margin: -0.5px 0 0 -6.5px;
  background: currentColor;
}

.c-VideoViewer__close::before,
.c-Reservation__close::before { transform: rotate(45deg); }
.c-VideoViewer__close::after,
.c-Reservation__close::after  { transform: rotate(-45deg); }

@keyframes viewer-fond {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes viewer-cadre {
  from { opacity: 0; transform: translateY(12px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .c-VideoViewer::backdrop,
  .c-VideoViewer__frame,
  .c-VideoViewer__close,
  .c-Reservation::backdrop,
  .c-Reservation__frame,
  .c-Reservation__close {
    animation: none;
  }
  .c-VideoViewer__close { transition: none; }
  .c-VideoViewer__close:hover { transform: none; }
}


/* --- Compteur « Nous sommes déjà » ----------------------------------------
   Animation de la maquette, ramenée dans la page. La scène est dessinée dans
   un repère fixe de 1920 x 1080 puis mise à l'échelle par le JavaScript : les
   positions ci-dessous sont donc en pixels de maquette, pas d'écran.

   Tout est scopé sous .c-Compteur. La maquette d'origine posait ses styles sur
   html, body et des #id génériques (#stage, #number, #flash) : appliqués tels
   quels ici, ils auraient débordé sur le reste du site.

   Polices auto-hébergées : la maquette les tirait de Google Fonts, ce qui
   ajoutait deux domaines tiers au chargement de la page. Anton et Barlow
   Condensed sont sous licence SIL OFL, l'auto-hébergement est permis. */

@font-face {
  font-family: 'Anton';
  src: url('/assets/fonts/anton-latin-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Barlow Condensed';
  src: url('/assets/fonts/barlow-condensed-latin-500.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

.c-Compteur {
  --compteur-pale: #dce8f7;
  --compteur-blue: #1e5bb8;
  --compteur-navy: #0a2a66;

  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--compteur-pale);
}

.c-Compteur__stage {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 1920px;
  height: 1080px;
  overflow: hidden;
  font-kerning: none;
  transform-origin: 50% 50%;
  transform: translate(-50%, -50%);
}

.c-Compteur__camera {
  position: absolute;
  inset: 0;
  transform-origin: 50% 46%;
  will-change: transform;
}

.c-Compteur__col {
  position: absolute;
  top: 0;
  height: 1080px;
  overflow: hidden;
}

.c-Compteur__colInner {
  position: absolute;
  inset: 0;
  will-change: transform;
}

/* Le témoignage court verticalement, comme dans la maquette. L'origine de la
   rotation est posée au point d'ancrage (milieu de colonne, 26 px du bas) pour
   que le texte monte depuis ce point, exactement comme le faisait le canvas. */
.c-Compteur__colText {
  position: absolute;
  left: 50%;
  bottom: 26px;
  transform-origin: 0 50%;
  transform: rotate(-90deg);
  white-space: nowrap;
  font: 500 12.5px 'Barlow Condensed', 'Helvetica Neue', Arial, sans-serif;
  line-height: 1;
}

.c-Compteur__type {
  position: absolute;
  left: 585px;
  top: 222px;
  width: 1150px;
}

.c-Compteur__kicker,
.c-Compteur__line {
  margin: 0;
  font: 400 90px 'Anton', 'Helvetica Neue', Arial, sans-serif;
  color: var(--compteur-blue);
  text-transform: uppercase;
  will-change: transform, opacity;
}

.c-Compteur__kicker { letter-spacing: 0.004em; line-height: 0.98; }
.c-Compteur__line { line-height: 1; }

.c-Compteur__numberWrap {
  margin-top: 18px;
  transform-origin: 0% 50%;
  will-change: transform, opacity;
}

.c-Compteur__number {
  display: flex;
  align-items: baseline;
  font: 400 212px 'Anton', 'Helvetica Neue', Arial, sans-serif;
  color: var(--compteur-navy);
  line-height: 1.02;
}

/* Largeur fixe par chiffre : le nombre ne tremble pas pendant qu'il monte. */
.c-Compteur__number span {
  display: inline-block;
  width: 0.405em;
  text-align: center;
}

.c-Compteur__number span.c-Compteur__sep { width: 0.19em; }

.c-Compteur__lines { margin-top: 26px; }

.c-Compteur__flash {
  position: absolute;
  inset: 0;
  background: #fff;
  opacity: 0;
  pointer-events: none;
}



/* --- Titres d'accroche : adaptation aux textes français ---------------------
   Le thème dimensionne ces blocs pour des accroches anglaises courtes. Les
   textes français font deux à quatre fois leur longueur : à 98 px sur Contact
   et 78 px sur Engagements, l'accroche débordait sur cinq puis quinze lignes.

   On garde la hiérarchie voulue par le thème, on réduit seulement l'échelle,
   en clamp() pour que ça tienne aussi sur petit écran. */

/* Engagements : la surcharge du showcase (.c-ScrollMediaShowcase) a été retirée
   le 8 septembre 2026, le composant n'est plus utilisé sur la page. */

/* Contact — accroche du hero (était 98 px, soit 7vw posé par le thème sur
   .page-contact .b-contact-hero .hero .h1 ; on reprend cette spécificité). */
.page-contact .b-contact-hero .hero p.h1,
.page-sign-up-success .b-contact-hero .hero p.h1 {
  font-size: clamp(26px, 3.4vw, 52px);
  line-height: 1.14;
}

/* Services : le titre d'intro est rendu en --fs-mega (80 px au-delà de 1441 px).
   Même principe que pour Contact : on borne l'échelle pour qu'un titre de deux
   lignes tienne à toutes les largeurs. */
.b-services-intro .mega {
  font-size: clamp(32px, 5vw, 68px);
  line-height: 1.08;
}

.b-services-intro .b-services-intro__lead {
  max-width: 60ch;
  margin: 0.9em auto 0;
  font-size: clamp(18px, 1.6vw, 24px);
  line-height: 1.4;
}

/* Approche : la phrase pivot est rendue en --fs-mega. Même bornage que Services. */
.b-rollover-stats__title.mega {
  font-size: clamp(30px, 4.6vw, 64px);
  line-height: 1.08;
}

/* Contact : message de confirmation du formulaire, visible seulement quand
   l'URL porte l'ancre #merci, où web3forms renvoie après l'envoi. Sans JavaScript. */
.form-merci {
  display: none;
}

.form-merci:target,
.form-merci--ouvert {
  display: block;
}

/* --- Contact : le retour après envoi --------------------------------------

   Le formulaire partait en rechargeant la page : l'animation d'entrée du logo
   rejouait, la page se rouvrait en haut, et la confirmation attendait en petit
   et en blanc tout en bas de la section — invisible sans redescendre. L'envoi
   se fait maintenant sans quitter la page (polaire-formulaire.js) et prend la
   place du formulaire : un panneau vert quand le message est parti, rouge
   quand il ne l'est pas, tous deux annoncés par la même apparition.

   Les deux panneaux partagent leur dessin ; seule la couleur d'accent change.
   `--polaire-echec` est posé ici plutôt que dans la palette du thème : le site
   n'avait jusqu'ici aucun rouge, faute d'avoir eu quoi que ce soit à refuser. */
:root {
  --polaire-succes: #03ac47;
  --polaire-echec: #e5484d;
}

.form-erreur {
  display: none;
}

.form-merci--ouvert,
.form-erreur--ouvert {
  display: block;
  margin-bottom: 24px;
  padding: 22px 26px 22px 62px;
  position: relative;
  border-radius: 10px;
  border: 1px solid;
  animation: polaire-retour 0.42s cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* La pastille : une coche pour le succès, un point d'exclamation pour l'échec.
   Le trait est dessiné en fond plutôt qu'en svg en ligne — comme la flèche du
   bouton d'envoi, que le thème réduisait à 2 x 2 px quand elle était balisée. */
.form-merci--ouvert::before,
.form-erreur--ouvert::before {
  content: "";
  position: absolute;
  top: 24px;
  left: 24px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 13px 13px;
}

.form-merci--ouvert {
  border-color: rgba(3, 172, 71, 0.55);
  background-color: rgba(3, 172, 71, 0.12);
}

.form-merci--ouvert::before {
  background-color: var(--polaire-succes);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13 13'%3E%3Cpath d='M2 6.8 5 9.8 11 3.2' fill='none' stroke='%23ffffff' stroke-width='2.1' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  animation: polaire-pastille 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.12s both;
}

.form-erreur--ouvert {
  border-color: rgba(229, 72, 77, 0.55);
  background-color: rgba(229, 72, 77, 0.12);
}

.form-erreur--ouvert::before {
  background-color: var(--polaire-echec);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13 13'%3E%3Cpath d='M6.5 2.6v4.6M6.5 9.9v.1' fill='none' stroke='%23ffffff' stroke-width='2.1' stroke-linecap='round'/%3E%3C/svg%3E");
  animation: polaire-pastille 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.12s both;
}

.form-erreur--ouvert a {
  text-decoration: underline;
}

@keyframes polaire-retour {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

@keyframes polaire-pastille {
  from { transform: scale(0.4); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* Le bouton pendant l'envoi : la flèche cède la place à un anneau qui tourne,
   le temps que l'API réponde. Rien ne bouge dans la mise en page. */
.gform_submit_btn--envoi {
  cursor: progress;
  background-image: none !important;
  position: relative;
}

.gform_submit_btn--envoi::after {
  content: "" !important;
  position: absolute;
  top: 50%;
  left: 50%;
  width: 20px;
  height: 20px;
  margin: -10px 0 0 -10px;
  border-radius: 50%;
  border: 2px solid rgba(31, 28, 23, 0.25);
  border-top-color: var(--off-black);
  animation: polaire-rotation 0.7s linear infinite;
}

@keyframes polaire-rotation {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .form-merci--ouvert,
  .form-erreur--ouvert,
  .form-merci--ouvert::before,
  .form-erreur--ouvert::before {
    animation: none;
  }

  .gform_submit_btn--envoi::after {
    animation: none;
    border-top-color: rgba(31, 28, 23, 0.25);
  }
}


/* --- Libellés du dépliage de chapô ----------------------------------------
   Le thème les écrit en dur en anglais, via ::after sur .synopsis .link span. */
.synopsis .link span:after { content: "Lire +"; }
.synopsis[open] .link span:after { content: "Lire moins"; }


/* Panneau de transition entre pages. La couleur est ensuite pilotée par
   polaire-transition.js, qui la fait tourner à chaque passage ; cette valeur
   sert de repli et évite le jaune du thème si le script ne se charge pas. */
.c-PageTransition {
  background: #ff2d87;
}


/* --- Curseur « son » sur le showreel ---------------------------------------
   Le thème gère déjà la bascule du son au clic et le libellé qui suit le
   curseur dès qu'un élément porte data-cursor. On y ajoute l'icône, pour que
   l'intention se lise avant même d'avoir bougé la souris.

   Le haut-parleur est un SVG en ligne (aucun fichier à charger), dessiné en
   blanc avec un liseré sombre : il reste lisible sur une image claire comme
   sur une image sombre. */
.video[data-sound-toggle],
.video[data-sound-toggle] * {
  cursor:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='36' viewBox='0 0 36 36'%3E%3Cg fill='none' stroke='%231f1c17' stroke-width='3.6' stroke-linejoin='round' stroke-linecap='round' opacity='.45'%3E%3Cpath d='M15 13 10 17H6v6h4l5 4z'/%3E%3Cpath d='M21.5 14.5a5 5 0 0 1 0 7'/%3E%3Cpath d='M25 11a10 10 0 0 1 0 14'/%3E%3C/g%3E%3Cg fill='none' stroke='%23ffffff' stroke-width='1.8' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M15 13 10 17H6v6h4l5 4z' fill='%23ffffff'/%3E%3Cpath d='M21.5 14.5a5 5 0 0 1 0 7'/%3E%3Cpath d='M25 11a10 10 0 0 1 0 14'/%3E%3C/g%3E%3C/svg%3E") 18 18,
    pointer;
}


/* --- Formulaire de contact : bouton d'envoi rond ---------------------------
   La maquette pose un bouton circulaire portant la seule flèche, aligné à
   droite de la case de consentement. Le libellé reste dans le DOM via
   aria-label : un bouton sans texte accessible n'est annonçable par aucun
   lecteur d'écran. */
.gform_submit_btn--rond {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  padding: 0;
  flex: 0 0 auto;
  border: 0;
  border-radius: 50%;
  background-color: var(--yellow);
  color: var(--off-black);
  cursor: pointer;
  transition: background-color 0.2s ease, transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}

.gform_submit_btn--rond:hover,
.gform_submit_btn--rond:focus-visible {
  transform: scale(1.06);
}

.gform_submit_btn--rond:focus-visible {
  outline: 2px solid var(--off-white);
  outline-offset: 3px;
}

/* La couleur du trait est posée sur le tracé, pas via `color` : le thème
   surcharge la couleur du bouton en !important sur des sélecteurs à quatre
   classes, et la flèche disparaissait (jaune sur jaune) au survol. */
/* La flèche est une image de fond et non un svg en ligne : le thème impose
   aux svg de formulaire une taille qui le réduisait à 2 x 2 px, et une couleur
   en !important qui l'effaçait au survol. En fond, rien ne l'atteint. */
.gform_submit_btn--rond {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 18 18'%3E%3Cpath d='M5 5 13 13M13 7v6h-6' fill='none' stroke='%231f1c17' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 18px 18px;
}

/* Le champ « Type de demande » reprend l'habillage des autres champs : le
   thème stylait déjà .ginput_container_select, mais pas le select lui-même,
   qui n'existait pas dans le formulaire reconstruit. */
.gform_wrapper .gfield_select {
  inline-size: 100%;
  border: 1px solid var(--field-border);
  border-radius: var(--field-radius);
  background-color: var(--field-bg);
  color: var(--form-input-color);
  padding: 12px 16px;
  min-height: 44px;
  appearance: none;
  -webkit-appearance: none;
}


/* --- Page contact : mise au gabarit de la maquette -------------------------
   Le thème pose `.b-contact-cards-with-form > * { width: 50% }` sur un flex
   sans gouttière : les deux moitiés se touchent, et la deuxième colonne de
   coordonnées venait buter contre le bord de la carte du formulaire. */
.b-contact-cards-with-form {
  gap: clamp(32px, 5vw, 88px);
}

.b-contact-cards-with-form > * {
  width: auto;
  flex: 1 1 0;
  min-width: 0;
}

/* Le thème dessine déjà une flèche en ::after sur le bouton d'envoi. La nôtre
   est en image de fond : sans ça, elles s'affichaient toutes les deux. */
.b-contact-cards-with-form__form .gform_submit_btn--rond::after,
.b-contact-cards-with-form__form .gform_submit_btn--rond::before {
  content: none !important;
}

/* Le conteneur du bouton est positionné en absolu par le thème sur 134px de
   large, calibrés pour l'ancien bouton en gélule. */
.b-contact-cards-with-form__form .gform_wrapper form .gfield--type-submit {
  width: auto;
}

/* --- Page contact : l'ouverture de la page ---------------------------------

   Au-dessus de 1240 px, le thème donne au chapô 7vw de corps et 200 px de
   respiration au-dessus comme au-dessous : le titre occupait à lui seul plus
   de 260 px, et le premier champ du formulaire tombait juste sous la ligne de
   flottaison. On ouvrait la page de contact sans voir le formulaire.

   Le titre tient désormais sur une ligne à cette largeur. `text-wrap: balance`
   est le filet : si une langue plus longue, une police de repli ou un écran
   plus étroit le fait passer sur deux lignes, elles se répartissent à parts
   égales au lieu de laisser un mot seul en dessous. Les navigateurs qui
   ignorent la propriété coupent comme avant.

   Sous 1240 px, rien n'est touché : le thème y pose déjà un corps et des
   marges plus mesurés. */
@media (min-width: 1240px) {
  .page-contact .b-contact-hero {
    padding-top: 120px;
    padding-bottom: 96px;
  }

  .page-contact .b-contact-hero .hero .h1 {
    font-size: 4.6vw;
    text-wrap: balance;
  }
}

/* Les deux colonnes démarrent sur la même ligne : « Réserver un créneau » et
   « Écrire » d'un côté, « Ou remplissez ce formulaire » de l'autre. Deux
   retraits les séparaient — les 40 px de marge intérieure que le thème pose
   sur la colonne du formulaire à partir de 1024 px, et les 8 px de marge que
   la feuille du formulaire ajoute à son premier paragraphe. On les retire en
   haut seulement : la marge latérale, elle, tient les champs à distance du
   bord et reste en place.

   Uniquement à partir de 1240 px, le seuil où le thème met les deux colonnes
   côte à côte. En dessous elles s'empilent, et ce retrait les sépare. */
@media (min-width: 1240px) {
  .b-contact-cards-with-form__form {
    padding-top: 0;
  }

  .site-main .b-contact-cards-with-form__form .gform_wrapper form .gfield--type-html p {
    margin-top: 0;
  }
}

/* Titre du formulaire : la maquette le pose nettement plus grand, et le thème
   plafonne ces titres à 280 px de large — la phrase se cassait en quatre lignes
   dans une carte qui en fait près de sept cents.

   Le sélecteur vise la classe, pas la balise : le champ est écrit
   `<p class="h3">`, comme le fait le formulaire, et deux règles qui ne
   nommaient que `h3` sont restées sans effet — le titre gardait le corps du
   texte courant.

   Il faut aussi passer devant `.site-main .gform_wrapper .gfield--type-html p`,
   qui ramène ces paragraphes au corps du texte : à trois classes et une balise,
   le thème l'emportait sur un sélecteur qui n'alignait que trois classes. D'où
   la chaîne complète, calquée sur la sienne. */
.site-main .b-contact-cards-with-form__form .gform_wrapper .gfield--type-html p.h3,
.site-main .b-contact-cards-with-form__form .gform_wrapper .gfield--type-html h3,
.site-main .b-contact-cards-with-form__form .gform_wrapper .gfield--input-type-html p.h3,
.site-main .b-contact-cards-with-form__form .gform_wrapper .gfield--input-type-html h3 {
  max-width: none;
  font-size: clamp(22px, 2.1vw, 34px);
  line-height: 1.18;
  margin-bottom: 8px;
}

/* Coordonnées : la maquette les donne à la taille du texte courant, pas en
   petit. Le nom et l'adresse gagnent un cran. */
.b-contact-cards-with-form__contact-cards .contact-card__name,
.b-contact-cards-with-form__contact-cards .contact-card__info {
  font-size: clamp(17px, 1.4vw, 22px);
}

/* L'adresse du CENTQUATRE est le seul lien du site trop long pour sa colonne.
   Le thème pose `white-space: nowrap` sur `.link` — commode pour un numéro ou
   une adresse électronique, intenable ici : la ligne poussait de 150 px au-delà
   de sa carte et passait par-dessus le formulaire, jusque sur le champ « Nom et
   prénom ». On rend le retour à la ligne à ces liens-là.

   `.link` est un conteneur flex dont la flèche est le second élément : sur deux
   lignes elle se recentrerait verticalement, à mi-hauteur du texte. On la pose
   au niveau de la dernière ligne, où elle suit la fin de la phrase. */
.b-contact-cards-with-form__contact-cards .contact-card__info .link {
  display: inline;
  white-space: normal;
  max-width: 100%;
}

/* La flèche redevient un caractère de la phrase : elle suit le dernier mot au
   lieu d'être renvoyée au bord de la colonne par la répartition du flex. */
.b-contact-cards-with-form__contact-cards .contact-card__info .link::after {
  display: inline-block;
  vertical-align: -0.12em;
  margin-left: 0.35em;
}

/* Les coordonnées se rangent en deux colonnes : « Réserver un créneau » et
   « Écrire » sur la première ligne, « Appeler » et « Venir » sur la deuxième,
   « 30 minutes » en pied de grille. Le thème les espaçait de 56 px en hauteur,
   calibrés pour des cartes plus hautes : entre des lignes d'une ou deux lignes,
   cela ouvrait des trous que rien ne venait combler.

   Le pas horizontal ne peut pas être repris seul. Le thème donne aux cartes une
   largeur de `calc(50% - 16px)`, où 16 est la moitié de sa gouttière de 32 px :
   toute autre valeur et la somme des deux cartes dépasse la ligne, la deuxième
   passe dessous, et la grille s'effondre en une seule colonne. On pose donc la
   gouttière dans une variable dont la largeur des cartes se déduit — les deux
   restent d'équerre quelle que soit la valeur choisie.

   La ligne reste plus aérée que la colonne : c'est elle qui sépare deux
   coordonnées sans rapport, là où la gouttière ne sépare qu'un libellé d'un
   autre, déjà distingués par leur intertitre. */
@media (min-width: 640px) {
  .b-contact-cards-with-form__contact-cards {
    --pas-colonnes: clamp(24px, 2vw, 36px);
    gap: clamp(34px, 3vw, 52px) var(--pas-colonnes);
    align-content: flex-start;
  }

  .b-contact-cards-with-form__contact-cards .contact-card-container {
    width: calc(50% - var(--pas-colonnes) / 2);
  }
}

/* --------------------------------------------------------------------------
   Citation client unique — .b-quote-slideshow--single
   --------------------------------------------------------------------------
   Le carrousel de citations du thème suppose plusieurs témoignages : les
   vignettes servent de boutons de navigation. Avec une seule citation, la
   vignette n'a plus rien à piloter — elle devient la signature visuelle de
   l'organisation citée, posée sous le verbatim.

   Trois comportements du thème sont donc neutralisés : l'opacité réduite des
   vignettes inactives (.35, puis .1 au-delà d'un palier), et la bascule en
   pastille de 16 px qui masque le logo sur petit écran. */
.b-quote-slideshow--single .b-quote-slideshow__thumb,
.b-quote-slideshow--single .b-quote-slideshow__thumb:hover {
  width: auto;
  height: auto;
  padding: 0;
  border-radius: 0;
  background: none;
  opacity: 1;
  cursor: default;
}

/* Le logo est affiche dans les couleurs de la marque citee, sans filtre : sur
   un fond clair, une silhouette noire ferait perdre le dessin du globe. Un
   logo qui n'existerait qu'en monochrome pourrait, lui, etre passe en
   brightness(0) pour le ramener au noir en preservant la transparence.

   Le fichier fait 391 px de large pour un affichage a 190 px : le double,
   donc net sur les ecrans a forte densite. */
.b-quote-slideshow--single .b-quote-slideshow__thumb img {
  display: block;
  width: auto;
  height: auto;
  max-width: 190px;
}

/* --- Image d'attente du showreel -----------------------------------------
   Le lecteur Vimeo du thème n'accepte pas d'affiche : il crée son iframe à
   l'exécution, et tant que le player n'a pas répondu le cadre reste vide. On
   glisse donc une image dans .video__wrapper, avant que le SDK n'y ajoute son
   iframe (il fait un appendChild, il ne vide pas le conteneur).

   L'affiche est posée DEVANT l'iframe, et non derrière : l'iframe peint le fond
   noir du player Vimeo dès qu'elle est chargée, soit une à deux secondes avant
   la première image de la vidéo. Derrière, l'affiche était donc recouverte de
   noir pendant tout ce temps. Le retrait est confié à polaire-affiche.js, qui
   attend que la lecture soit réellement engagée.

   Le z-index reste sous le dégradé du diaporama (z-index 3), pour que l'affiche
   soit voilée exactement comme l'est la vidéo.

   Le fichier est la vignette Vimeo de la vidéo (le carton « showreel. 2025 »),
   donc identique à ce que Vimeo affiche lui-même pendant sa mise en mémoire
   tampon : le raccord ne se voit pas. À régénérer si la vignette change côté
   Vimeo. */
.video__wrapper > .video__poster {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
  opacity: 1;
  transition: opacity .4s ease;
}

.video__wrapper > .video__poster--masquee {
  opacity: 0;
}

/* --- Labels et certifications (pied de page) ------------------------------
   L'emplacement n'accueillait que le macaron B Corp, sur lequel le thème
   impose des dimensions en dur : `.footer__carbon img{width:47px;height:83px}`,
   soit le format portrait de ce macaron. Appliquée à deux logos aux
   proportions différentes, cette règle les écrase. Il faut donc la neutraliser
   explicitement — d'où le `height: auto`, qui n'est pas superflu.

   La mesure commune est la LARGEUR, pas la hauteur : 1% for the Planet est un
   bloc horizontal, Ecoprod un tampon presque carré. À hauteur égale, le tampon
   paraissait deux fois plus étroit que son voisin. Les hauteurs diffèrent donc,
   et c'est voulu.

   Les deux fichiers sont blancs sur fond transparent, donc lisibles tels quels
   sur le pied de page sombre : aucun filtre, contrairement au logo Polaire qui
   part, lui, d'un fichier noir. */
.footer__carbon .footer__labels {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
}

.footer__carbon .footer__label {
  display: block;
  width: 112px;
  height: auto;
  margin: 0;
}

/* Au-delà du palier large, le thème aligne ce bloc sur la droite du pied de
   page : les labels suivent. */
@media (min-width: 1024px) {
  .footer__carbon .footer__labels {
    align-items: flex-end;
    gap: 26px;
  }

  .footer__carbon .footer__label {
    width: 124px;
  }
}

/* Coordonnées sous les macarons : une ligne, petite, centrée en mobile et
   alignée à droite au-delà du palier large comme le reste du bloc. */
/* --- Logos clients --------------------------------------------------------
   Les fichiers servis (assets/images/clients) sont normalisés sur le gabarit du
   template : cadre 232x80 transparent, encre centrée, taille calée sur la
   surface moyenne des logos d'origine. Sans ce cadre, le thème dimensionnant
   par max-height, des fichiers recadrés au ras de l'encre rendaient des logos
   deux fois trop gros. Les sources non retouchées restent dans _clients-logos,
   et la normalisation est rejouable : _normaliser-logos-clients.py, à la racine
   du projet.

   `brightness(0)` ramène au noir les logos fournis en couleurs de marque, en
   préservant la transparence. Deux logos y échappent, marqués
   data-logo-bichrome : ils sont déjà convertis en noir et blanc dans le fichier
   parce que le filtre les aurait aplatis — le carré bleu de la Fondation de
   France serait devenu un pavé noir, et le globe de L'Affaire du Siècle un
   disque plein. Leurs continents et leur texte sont passés en blanc.

   Pour remplacer un logo : déposer la source dans _clients-logos et relancer le
   script, plutôt que de toucher à cette règle. */
.logo-grid__image img:not([data-logo-bichrome]) {
  filter: brightness(0);
}

/* --- Pages légales : parité de gabarit avec la page de confidentialité -----
   Le thème réserve le gabarit « page de texte » à deux pages nommées en dur :
   .page-privacy-policy et .page-sitemap. C'est ce bloc qui décolle le titre du
   haut de la fenêtre (136 px, 200 px au-delà de 1024) et qui règle l'espacement
   de la colonne latérale et des blocs.

   Mentions légales et CGV utilisent la même structure mais portent une autre
   classe de page : elles n'héritaient de rien et leur titre remontait sous la
   barre de navigation. On étend les mêmes sélecteurs à ces deux pages, aux
   valeurs exactes du thème — rien n'est recalculé ici, pour que les trois pages
   légales restent alignées sur l'original si le thème évolue. */
.page-mentions-legales .article__content,
.page-cgv .article__content {
  padding-top: 136px;
}

.page-mentions-legales .article__content .article-sidebar,
.page-cgv .article__content .article-sidebar {
  margin-bottom: 56px;
}

@media (min-width: 1024px) {
  .page-mentions-legales .article__content,
  .page-cgv .article__content {
    padding-top: 200px;
  }

  .page-mentions-legales .article__content [class^="bb-"],
  .page-cgv .article__content [class^="bb-"] {
    --block-spacing: 56px;
  }

  .page-mentions-legales .article__content .article-sidebar,
  .page-cgv .article__content .article-sidebar {
    margin-bottom: 0;
  }
}

/* --- Accroche sous le titre : retrouver l'espacement de l'original ---------
   Le thème aère l'accroche qui suit le titre d'une page de texte via

       .wysiwyg h4:first-of-type { margin-top: var(--tag-spacing-large) }

   soit 36 px au lieu des 12 px de --tag-spacing. Le sélecteur vise le nom de
   balise : dans le scrape Fiasco l'accroche est un <h4>, elle en héritait.

   Ici l'accroche est écrite <h2 class="h4"> — même graisse et même corps, mais
   un h2, choisi pour ne pas sauter de h1 à h4 dans la hiérarchie des titres.
   Le sélecteur du thème ne la voit donc pas et elle remontait à 12 px du titre.

   On rejoue la règle du thème sur ce motif, en gardant sa variable plutôt qu'une
   valeur en dur : l'espacement suit l'original, la hiérarchie reste correcte. */
.wysiwyg h2.h4:first-of-type {
  margin-top: var(--tag-spacing-large);
}

/* --- Chapô des pages projet : accorder le texte déplié au texte replié -----
   Le bloc « Lire + » est un <details> dont le thème ne stylait que la partie
   visible :

       .b-project-intro__content .synopsis summary { line-height: 1.6em }

   Le texte révélé vit dans .synopsis__content, hors du <summary> : il n'était
   donc pas atteint et retombait sur l'interlignage global de body (1.5). Même
   police, même corps — var(--fs-b) des deux côtés — mais deux interlignages,
   visible dès que le paragraphe déplié fait plus d'une ligne.

   On étend la valeur du thème au contenu déplié. Les marges, elles, sont déjà
   neutralisées par le thème (p:first-child sans marge haute, *:last-child sans
   marge basse) : il n'y a que l'interlignage à reprendre. */
.b-project-intro__content .synopsis .synopsis__content {
  line-height: 1.6em;
}

/* Même bloc, la graisse. Le thème met en demi-gras les paragraphes nus d'un
   bloc wysiwyg :

       .wysiwyg p:not([class]) { font-weight: 600 }

   Dans le scrape Fiasco, le résumé contenait lui aussi des <p> — les deux
   moitiés du chapô étaient donc en 600 et s'accordaient. Ici le texte du résumé
   est posé à nu dans <span class="synopsis__summary">, qui reste en 400, tandis
   que le texte déplié est encore un <p> et bascule en 600.

   On aligne le texte déplié sur le résumé, qui est la référence visible et qui
   suit le corps de texte du reste de la page. Passer les deux en 600, comme
   l'original, alourdirait le chapô entier. */
.b-project-intro__content .synopsis .synopsis__content p:not([class]) {
  font-weight: 400;
}

/* --- Études de cas : grille 3 colonnes ------------------------------------
   Le thème dispose les projets en deux colonnes verticales
   (.projects-grid__column) contenant des tuiles de rapports variés — 16:9, 4:3,
   portrait — pour un effet de mosaïque. La maquette Polaire veut au contraire
   une grille régulière, toutes les tuiles au même format.

   Les colonnes ont donc été retirées du balisage : les tuiles sont désormais
   filles directes de .projects-grid, qui est déjà une grille CSS via .g-fill.
   Reste à fixer le nombre de colonnes. On ne peut pas passer par le système du
   thème : il pose `grid-column: span var(--cols)` sur une grille de
   --totalcols: 16, et aucun entier ne donne 3. On redéfinit donc les colonnes,
   et on remet les tuiles sur une cellule chacune pour neutraliser les classes
   c-all et c4-l qu'elles portent encore.

   Tout est limité à .page-work : la même .projects-grid sert la page d'accueil
   et les pages de catégorie, qui gardent la mosaïque du thème.

   Le passage à trois colonnes n'a lieu qu'à partir de 1400 px, et c'est une
   question de largeur utile, pas de goût. En retirant le padding de page et les
   gouttières, une tuile mesure (largeur − 32 − 64) / 3 : 309 px à 1024, 395 px à
   1280, 435 px à 1400. Or l'état à deux colonnes, lui, ne descend jamais sous
   (1024 − 32 − 32) / 2 = 480 px. En dessous de 1400 px, trois colonnes donnent
   donc des vignettes nettement plus étroites que tout ce que le thème a jamais
   affiché : l'accroche part en bouillie et le titre se casse en trois lignes.
   1400 px est le premier palier où la tuile reste dans l'ordre de grandeur pour
   lequel la typographie du thème est calibrée. */
@media (min-width: 768px) {
  /* Une seule colonne tant que la navigation est en burger.

     Le bloc « Grille des projets » ci-dessus passe à deux colonnes dès 768 px.
     Entre 768 et 1023, avec --hpad à 32 px, cela donne des vignettes de 336 à
     463 px : la typographie des tuiles, calibrée pour bien plus large, s'y
     déséquilibre. On revient donc à une colonne sur cet intervalle et on
     n'ouvre la seconde qu'à 1024 px, là où le thème rebascule justement la nav
     du burger au menu déployé (.nav__icon { display: none }). Les deux seuils
     coïncident, ce qui n'est pas un hasard : c'est la largeur à partir de
     laquelle la maquette respire. */
  .page-work .projects-grid {
    grid-template-columns: 1fr;
  }

  /* La règle de neutralisation du bloc ci-dessus exige un ancêtre
     .projects-grid__column ; ces colonnes ont été retirées du balisage de cette
     page, elle ne s'applique donc plus ici. Sans quoi les vignettes gardent le
     `grid-column: 1/-1` de leur classe c-all et s'étalent sur toute la ligne. */
  .page-work .projects-grid > .tile-project {
    grid-column: auto / span 1;
    width: 100%;
    margin: 0;
  }
}

/* Menu déployé : deux colonnes. Le `gap` reste celui du thème — 120 px à partir
   de 1024 px — c'est l'état que vous validez aujourd'hui, on n'y touche pas. */
@media (min-width: 1024px) {
  .page-work .projects-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1400px) {
  .page-work .projects-grid {
    grid-template-columns: repeat(3, 1fr);
    /* Les vignettes sont plus courtes qu'à deux colonnes : les 120 px de
       gouttière verticale du thème creuseraient des trous entre les lignes. */
    gap: 72px 32px;
  }
}

/* --- Écrans larges et ultra-larges ----------------------------------------
   Au-delà, on ajoute une colonne à chaque fois que la largeur disponible
   permet de le faire SANS retomber sous la largeur de vignette de référence.

   .full n'ayant pas de max-width, la grille occupe toute la fenêtre moins
   2 × 32 px de --hpad. Une vignette mesure donc :

       (largeur − 64 − (n − 1) × 32) / n

   Les paliers ci-dessous sont choisis pour qu'à chaque seuil la vignette
   retombe sur ~440 px, exactement comme au passage à trois colonnes (435 px à
   1400). La densité augmente, la lecture ne change pas :

       4 col. à 1920  ->  440 px          5 col. à 2400  ->  442 px
       6 col. à 2880  ->  443 px

   Ce que ça donne sur les formats 21:9 courants :

       2560 × 1080  ->  5 colonnes, vignettes de 474 px
       3440 × 1440  ->  6 colonnes, vignettes de 536 px

   La gouttière de 72px 32px posée à 1400 px reste valable : la vignette garde
   la même taille au seuil, donc le même rythme vertical. */
@media (min-width: 1920px) {
  .page-work .projects-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

@media (min-width: 2400px) {
  .page-work .projects-grid {
    grid-template-columns: repeat(5, 1fr);
  }
}

@media (min-width: 2880px) {
  .page-work .projects-grid {
    grid-template-columns: repeat(6, 1fr);
  }
}

/* --- Séparateur entre deux lignes de projets ------------------------------
   Remplace le bloc pleine hauteur qui coupait la page en deux. Même texte, même
   lien, mais réduit à un filet : la lecture de la grille n'est plus
   interrompue. Les filets reprennent --opacity-10, la couleur de bordure du
   thème.

   Le calque intermédiaire .section--top a été retiré du balisage de cette page.
   Il n'apportait aucune couleur — .page-layer et .section partagent le même
   --off-white — mais il remontait de calc(var(--layer-radius) * -2), soit -80 px,
   avec un angle arrondi et une ombre portée `0 -20px 80px` : d'où une rupture
   qui se lisait comme un nouveau panneau au milieu de la grille. Les deux
   grilles et le séparateur vivent désormais dans un seul .page-layer, sur une
   surface continue.

   La transition vers le bloc jaune qui suit n'en souffre pas : .page-layer porte
   déjà border-top-radius: 0, un arrondi bas et l'ombre `0 28px 80px`.

   Le séparateur porte donc ses propres marges verticales, calées sur la
   gouttière de la grille pour que le rythme reste régulier. */
.projects-separator {
  margin: 48px 0;
}

@media (min-width: 1024px) {
  .projects-separator {
    margin: 56px 0;
  }
}

/* Bandeau sombre, texte clair, bouton jaune — la palette du bloc d'origine,
   ramenée à la hauteur d'un séparateur.

   On passe par --text-color plutôt que par une couleur en dur : c'est la
   variable dont héritent les titres, les liens et les puces du thème, et c'est
   ce que fait `.block-fullwidthcta.section--dark`. Le fond reprend --black, que
   la palette Polaire ramène à #1f1c17 en tête de fichier.

   Le bouton n'a besoin d'aucune règle : la classe btn--yellow du thème pose
   déjà --button-bg: var(--yellow) et --button-text: var(--off-black), y compris
   pour la flèche, qui est un masque teinté par --button-text. */
.projects-separator__inner {
  --text-color: var(--white);

  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px 24px;
  padding: 20px 24px;
  border-radius: 16px;
  background: var(--black);
  color: var(--text-color);
}

.projects-separator__text {
  margin: 0;
  font-size: var(--fs-large);
}

@media (min-width: 768px) {
  .projects-separator__inner {
    padding: 24px 32px;
  }
}

/* --- Vidéos Vimeo dans un cadre qui n'est pas en 16:9 ---------------------
   Le lecteur Vimeo laisse toujours la vidéo entière dans son iframe : il
   ajoute des bandes noires plutôt que de recadrer. Le thème compense en
   agrandissant l'iframe au-delà du cadre, qui la rogne — mais il ne le fait que
   pour les formats 9:16, 3:4, portrait et 21:9. En 1:1 et 4:3, rien : la vidéo
   n'occupait que 57 % de la hauteur d'une tuile carrée et 75 % d'une tuile 4:3,
   le reste en noir. Dans le collage de la page Services, cela donnait des
   images petites et très espacées.

   Une iframe deux fois plus large que le cadre, à la hauteur du cadre, suffit :
   la vidéo s'y ajuste par la hauteur, donc elle couvre, et le débord horizontal
   est rogné au centre. Même règle pour les deux formats — le recadrage obtenu
   est identique, il ne dépend que de la hauteur. */
.video--vimeo.ar-1x1 .video__wrapper iframe,
.video--vimeo.ar-4x3 .video__wrapper iframe {
  width: 200% !important;
  height: 100% !important;
}

/* --- Collage « Avec les agences » ----------------------------------------
   Le gabarit répartissait les cinq vidéos sur toute la hauteur de la section
   (150vh) : trois collées en haut, deux en bas. Quand le texte est centré à
   l'écran, il ne restait donc quasiment rien de visible — mesuré : 0 %, 0 %,
   16 %, 60 %, 0 %.

   On les regroupe dans la bande que la fenêtre couvre à ce moment-là, en deux
   colonnes de part et d'autre du texte : la petite et la grande carrée à
   gauche, les trois autres à droite. Les pourcentages sont relatifs au collage,
   haut de 150vh, dont le texte occupe le centre.

   Les vitesses de parallaxe ont été resserrées dans le HTML (1,05 à 1,15 au
   lieu de 1,5 à 2) : c'est leur écart qui écartait les tuiles les unes des
   autres au fil du défilement. Il en reste assez pour que le collage respire.

   Rien de tout cela ne s'applique en dessous de 1024 px : la mise en page
   mobile empile déjà les tuiles autrement. */
@media (min-width: 1024px) {
  .b-services-titleandgallery__media-item--1 {
    top: 22%;
    bottom: auto;
    left: -2%;
    right: auto;
  }

  .b-services-titleandgallery__media-item--2 {
    top: 42%;
    bottom: auto;
    left: auto;
    right: 2%;
    margin-left: auto;
    margin-right: 0;
  }

  .b-services-titleandgallery__media-item--3 {
    top: 20%;
    bottom: auto;
    left: auto;
    right: 0;
  }

  .b-services-titleandgallery__media-item--4 {
    top: 44%;
    bottom: auto;
    left: -2%;
    right: auto;
  }

  .b-services-titleandgallery__media-item--5 {
    top: 58.5%;
    bottom: auto;
    left: auto;
    right: 2%;
    margin-left: auto;
    margin-right: 0;
  }
}

/* --- Logos clients : taille sur mobile ------------------------------------
   Le thème cale les logos de la grille sur max-height: var(--logo-height),
   qui vaut 42px sous 768px, 80px au-delà, 108px au-delà de 1024. Nos fichiers
   normalisés occupent un cadre 464x160 dont l'encre ne remplit au mieux que
   70% de la hauteur : à 42px, le logo réellement visible faisait moins de
   30px, illisible sur téléphone alors que la colonne est large.

   On remonte la variable sous 768px. Le rapport du cadre est de 2,9:1, donc
   un logo de 72px de haut occupe 209px de large : il tient dans une colonne
   de la grille à deux colonnes qui s'active à partir de 600px, et le
   max-width: 100% du thème garde le filet de sécurité en dessous. */
@media (max-width: 767px) {
  .logo-grid {
    --logo-height: 72px;
    --grid-gap: 24px;
  }

  .logo-grid__image {
    min-height: 72px;
  }
}

/* --- Pages projet : intertitres des colonnes de texte -----------------------
   Sur le site d'origine, chaque section d'une page projet est titrée par un
   <p class="subheader"> : HAL Timezone italique, --fs-subheader (18 à 20 px),
   interligne 1.4, collé au paragraphe qui suit. Les pages Polaire titrent ces
   sections en <h3 class="h4"> pour tenir la hiérarchie de titres, mais la règle
   .wysiwyg h3 du thème impose --fs-h3 (30 px) et l'espacement large : le titre
   était deux fois trop gros et trop loin de son texte.

   On rend au h3 l'apparence exacte du subheader d'origine, sans toucher au
   balisage. La spécificité (bloc + wysiwyg + balise + classe) passe devant
   .wysiwyg h3 et .wysiwyg .h3. */
.b-text-columns .wysiwyg h3.h4 {
  /* Pile de repli explicite : --headingFontAlt vaut « HAL Timezone », une
     police sous licence Fiasco (voir LISEZ-MOI). Le jour où elle est remplacée,
     Georgia garde un italique serif proche et le bloc ne se déforme pas. */
  font-family: var(--headingFontAlt), Georgia, "Times New Roman", serif;
  font-size: var(--fs-subheader);
  font-style: italic;
  font-weight: 400;
  line-height: 1.4em;
  margin-top: 0;
  margin-bottom: 0.3em;
  text-wrap: balance;
}

.b-text-columns .wysiwyg h3.h4 + p {
  margin-top: var(--tag-spacing);
}

/* --- Graisse des paragraphes : retour à la règle du thème d'origine ---------
   Le style.css local contient une règle absente de la feuille du thème en
   ligne sur fiasco.design (comparaison du 8 septembre 2026) :

       .wysiwyg p:not([class]) { font-weight: 600 }

   Elle met en demi-gras tous les paragraphes nus du site : sections des pages
   projet, accueil, réponses de la FAQ. C'est elle qui rendait le texte courant
   plus lourd que sur le site d'origine, à taille et interligne pourtant
   identiques (18 px / 28,8 px). On la neutralise ici plutôt que dans
   style.css, qui ne se modifie pas. */
.wysiwyg p:not([class]) {
  font-weight: 400;
}


/* --- Modale de réservation ------------------------------------------------
   Même principe que le visualiseur vidéo : un <dialog> pour le focus et la
   fermeture par Échap, et les mêmes remises à zéro, le navigateur posant par
   défaut bordure, fond blanc, padding et dimensions maximales.

   La croix, les animations et le repli en mouvement réduit sont partagés avec
   .c-VideoViewer plus haut, d'où les sélecteurs à deux noms : c'est le même
   objet à l'écran, il n'avait pas à être décrit deux fois.

   Le cadre est ici un panneau clair aux proportions libres, et non un 16/9 :
   les calendriers de prise de rendez-vous affichent une colonne de détails,
   un mois et une liste d'horaires, soit environ 1000 x 970 avant de basculer
   en colonne unique. On vise donc cette largeur, bornée par l'écran. */
.c-Reservation {
  --viewer-marge: clamp(16px, 3vw, 44px);

  width: 100vw;
  height: 100vh;
  height: 100dvh;
  max-width: 100vw;
  max-height: 100vh;
  max-height: 100dvh;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  overflow: hidden;
  color: var(--off-white, #f8f9f3);
}

.c-Reservation[open] {
  display: flex;
  align-items: center;
  justify-content: center;
}

.c-Reservation::backdrop {
  background: rgba(10, 10, 8, 0.94);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  animation: viewer-fond 0.35s ease both;
}

.c-Reservation__frame {
  position: relative;
  width: min(1040px, 100vw - var(--viewer-marge) * 2);
  height: min(940px, 100vh - var(--viewer-marge) * 2);
  height: min(940px, 100dvh - var(--viewer-marge) * 2);
  background: var(--off-white, #f8f9f3);
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.55);
  animation: viewer-cadre 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* L'iframe n'apparaît qu'une fois chargée : sans cela le calendrier se peint
   par morceaux au-dessus du message d'attente. */
.c-Reservation__frame iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.c-Reservation__frame--prete iframe {
  opacity: 1;
}

/* Le message d'attente est posé par-dessus l'iframe, sinon celle-ci le
   masquerait. Il laisse passer les clics tant qu'il n'est qu'informatif ;
   il les reprend quand il devient le lien de secours. */
.c-Reservation__state {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: 0 24px;
  color: var(--off-black, #1f1c17);
  font-size: 15px;
  text-align: center;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

.c-Reservation__state--secours {
  pointer-events: auto;
}

.c-Reservation__frame--prete .c-Reservation__state {
  opacity: 0;
  visibility: hidden;
}
