/*
 * MADS — mises en page propres à chaque type de page.
 *
 * Convention : les blocs à deux colonnes partagent la primitive `.duo`, dont
 * l'ordre s'inverse par un attribut plutôt que par une seconde règle. Toutes
 * les bascules mobiles se font au même point (900 px), là où les maquettes
 * passent d'une composition en deux colonnes à un empilement.
 */

/* =================================================== primitive 2 colonnes */

/* Une moitié de texte calée sur la gouttière, une moitié d'image qui touche
   le bord de l'écran. Le sens s'inverse avec [data-reverse]. */
.duo {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: center;
}

.duo__text {
  /* Mesure commune à toute la colonne. Le sous-titre courait jusqu'au bord
     tandis que le paragraphe s'arrêtait à 34ch : trois longueurs de ligne
     différentes pour trois niveaux de texte, alors qu'ils forment un seul
     bloc. Une seule valeur ici, et les trois s'alignent. */
  --measure: 38ch;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.1rem;
  padding: clamp(2rem, 5vw, 4.5rem) clamp(2rem, 4.5vw, 4.5rem)
    clamp(2rem, 5vw, 4.5rem) var(--gutter);
}

.duo__text > * {
  max-width: var(--measure);
}

.duo__media {
  position: relative;
  align-self: stretch;
  min-height: 0;
}

.duo__media mads-picture {
  height: 100%;
  min-height: clamp(20rem, 46vw, 100%);
}

/* Ordre inversé : l'image passe à gauche, le texte à droite (bloc « À propos »
   de la page d'accueil, page à propos). `order` plutôt qu'une règle dupliquée
   pour que l'ordre du DOM reste l'ordre de lecture. */
.duo[data-reverse] .duo__media {
  order: -1;
}

.duo[data-reverse] .duo__text {
  padding-inline: clamp(2rem, 4.5vw, 4.5rem) var(--gutter);
}

@media (max-width: 900px) {
  .duo {
    grid-template-columns: minmax(0, 1fr);
  }

  .duo__text {
    padding: 1.5rem var(--gutter) 2.75rem;
  }

  .duo__media mads-picture {
    min-height: 0;
    aspect-ratio: 3 / 2;
  }

  /* Sur mobile, l'image passe systématiquement au-dessus du texte, quel que
     soit le sens choisi en desktop — c'est ce que montrent les maquettes. */
  .duo .duo__media,
  .duo[data-reverse] .duo__media {
    order: -1;
  }
}

/* ============================================================== accueil */

/* Accrochage au défilement, page d'accueil et grand écran uniquement.

   Deux précisions qui comptent :

   • la règle porte sur <html>, pas sur <body> : contrairement à overflow,
     scroll-snap-type ne remonte pas du body au viewport, et n'aurait donc
     aucun effet posée sur lui ;
   • elle est réservée au-dessus de 900 px, car c'est là que les sections font
     exactement une hauteur d'écran. En dessous, la maquette leur donne une
     hauteur libre : y accrocher le défilement le rendrait erratique. */
@media (min-width: 901px) {
  html:has(body.page-home) {
    scroll-snap-type: y mandatory;
  }

  /* Le pied de page EST un point d'accrochage, au même titre que les sections.
     Sans lui, le dernier point d'ancrage est le bloc « À propos » et le
     navigateur y ramène systématiquement : le pied de page devient
     pratiquement inatteignable à la molette. */
  .page-home .home-hero,
  .page-home .home-block,
  .page-home .site-footer {
    scroll-snap-align: start;
  }

  /* Une section plus haute que la fenêtre doit rester parcourable : sans
     cela, `mandatory` interdit de s'arrêter en son milieu et une partie du
     texte devient illisible sur les écrans courts. */
  .page-home .home-block {
    scroll-snap-stop: normal;
  }
}

.home-hero {
  position: relative;
  min-height: var(--vh);
  background: var(--c-cream);
}

/* Bloc projet : un plein écran, texte à gauche, image à droite */
.home-block {
  min-height: var(--vh);
  background: var(--c-white);
}

.home-block--about {
  background: var(--c-cream);
}

.home-block .duo,
.home-block .duo__media {
  min-height: inherit;
}

.home-block__title {
  margin-block: 0.35rem 0.4rem;
}

.home-block__desc {
  color: var(--c-ink-soft);
  font-size: var(--t-body);
}

.home-block__cta {
  margin-top: 0.9rem;
}

@media (max-width: 900px) {
  .home-hero {
    /* Le hero mobile n'occupe pas tout l'écran dans la maquette : le premier
       projet doit être visible dès le premier défilement. */
    min-height: auto;
  }

  .home-block {
    min-height: 0;
  }
}

/* ============================================================== projets */

.projects__head {
  padding-top: clamp(6rem, 11vw, 9.5rem);
  padding-bottom: clamp(1.25rem, 2vw, 1.75rem);
}

.projects__filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem;
  margin-top: 1.1rem;
}

/* Le bouton n'a pas d'apparence propre : c'est la pilule qu'il enveloppe qui
   porte tout le rendu. Il n'existe que pour la sémantique et le clavier. */
.projects__filter {
  display: block;
  border-radius: var(--radius-label);
}

.projects__filter:focus-visible {
  outline: 2px solid var(--c-ink);
  outline-offset: 3px;
}

/* Au repos, les filtres sont gris ; le sélectionné passe à l'encre. C'est
   l'état des maquettes « Page projet » et « Page projet filtre actif », et il
   distingue les deux états sans toucher à la graisse — donc sans faire varier
   d'un pixel la largeur des pilules. */
.projects__filter mads-label:not([pressed]) {
  --label-color: var(--c-muted);
  --label-border: var(--c-muted);
}

.projects__grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(1rem, 1.7vw, 1.5rem) clamp(1rem, 1.7vw, 1.5rem);
  padding-bottom: var(--section-y);
}

@media (max-width: 1100px) {
  .projects__grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 820px) {
  .projects__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 520px) {
  .projects__grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Tuile de projet : intitulé souligné au-dessus, visuel en dessous,
   étiquette du service posée en haut à droite du visuel. */
.tile {
  transition: opacity var(--d-base) var(--e-out);
}

.tile__link {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}

.tile__title {
  font-size: var(--t-body);
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 0.22em;
  text-decoration-thickness: 1px;
}

.tile__media {
  position: relative;
  display: block;
}

/* L'étiquette est purement informative : le clic doit traverser jusqu'au
   lien qui enveloppe la tuile. */
.tile__label {
  pointer-events: none;
}

.tile__media mads-picture {
  aspect-ratio: 1 / 1;
}

/* Survol : l'image s'agrandit légèrement et son cadrage dérive — la tuile,
   elle, ne bouge pas.

   Le déplacement vient de --pan-x / --pan-y, écrites image par image par
   mads-project-list. `scale` et `translate` sont employées comme propriétés
   distinctes plutôt que composées dans un seul `transform` : l'échelle reste
   ainsi confiée à une transition CSS pendant que le script ne pilote que le
   déplacement, sans que l'un écrase l'autre. */
.tile__media img {
  scale: 1;
  translate: var(--pan-x, 0px) var(--pan-y, 0px);
  transition:
    scale var(--d-slow) var(--e-out),
    translate var(--d-slow) var(--e-out);
}

/* Pendant le survol, le déplacement est réécrit à chaque frame : une
   transition dessus le ferait traîner d'une image sur l'autre. Elle n'agit
   donc qu'au relâchement, pour ramener le cadrage en douceur. */
.tile:hover .tile__media img,
.tile:focus-within .tile__media img {
  scale: 1.06;
  transition: scale var(--d-slow) var(--e-out);
}

@media (prefers-reduced-motion: reduce) {
  .tile:hover .tile__media img,
  .tile:focus-within .tile__media img {
    scale: 1;
  }
}

/* L'étiquette est posée à même la photo, sans fond : sa couleur est mesurée
   sur l'image qui passe derrière elle, exactement comme l'étoile du menu et le
   logo de l'en-tête (lib/contrast.js, déclenché par data-tone-watch).
   Le repli en blanc vaut tant que la mesure n'a pas eu lieu, et sans
   JavaScript — sur une photographie, c'est le pari le plus sûr des deux. */
.tile__label {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  --label-bg: transparent;
  --label-color: var(--tone-color, #fff);
  --label-border: var(--tone-color, #fff);
}

/* Filtre actif : les tuiles hors sélection s'effacent sans quitter la grille,
   ce qui conserve la composition (cf. « Page projet filtre actif »). */
.tile[data-dimmed] {
  opacity: 0.22;
  pointer-events: none;
}

.projects__empty {
  padding-block: 3rem;
  color: var(--c-ink-soft);
}

/* ========================================================== cas client */

.case-hero {
  /* Le retrait haut est nommé : le carrousel s'en sert pour ne jamais dépasser
     le bas de la fenêtre, et les deux valeurs restent liées. */
  --case-top: clamp(5.5rem, 9vw, 7.5rem);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--col-gap);
  align-items: start;
  padding-top: var(--case-top);
}

.case-hero__text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
  padding-left: var(--gutter);
  padding-bottom: var(--section-y);
}

.case-hero__title {
  margin-top: 0.3rem;
}

.case-hero__desc {
  color: var(--c-ink-soft);
  max-width: 52ch;
}

/* Étiquettes des autres projets du même client */
.case-hero__siblings {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem;
  margin-top: 1.4rem;
}

/* Hauteur DÉFINIE, et non un simple plafond.
 *
 * Avec `max-height` seul, la colonne gardait une hauteur `auto` : les
 * `height: 100%` de la chaîne carrousel → zone → diapositive ne se résolvaient
 * contre rien et retombaient sur `auto`, la diapositive conservait son ratio
 * 4/5, et le tout débordait par-dessus le module de retombées presse — sans
 * même être rogné, faute d'`overflow`.
 *
 * En donnant à la colonne une hauteur réelle et en distribuant l'espace par
 * flexbox, chaque maillon reçoit une hauteur définie : les commandes gardent
 * la leur, la zone d'images prend le reste, et l'image recadre au lieu de
 * pousser. `overflow: hidden` reste en filet de sécurité.
 */
.case-hero__media {
  /* Le carrousel touche le bord droit de l'écran */
  margin-right: calc(-1 * var(--gutter));
  padding-right: 0;
  height: calc(var(--vh) - var(--case-top));
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.case-hero__media mads-carousel {
  display: flex;
  flex-direction: column;
  /* min-height: 0 est indispensable : la base `auto` d'un enfant flex
     l'empêcherait de descendre sous la hauteur de son contenu. */
  flex: 1 1 auto;
  min-height: 0;
}

.case-hero__media .carousel__controls {
  flex: none;
}

.case-hero__media .carousel__viewport {
  flex: 1 1 auto;
  min-height: 0;
}

/* Le ratio de la diapositive ne doit pas l'emporter sur la hauteur imposée :
   on le neutralise et l'image remplit l'espace en recadrant (object-fit). */
.case-hero__media .carousel__slide mads-picture {
  aspect-ratio: auto;
  height: 100%;
}

@media (max-width: 900px) {
  .case-hero {
    grid-template-columns: minmax(0, 1fr);
    gap: 1.5rem;
    padding-top: clamp(4.5rem, 16vw, 5.5rem);
  }

  .case-hero__text {
    order: 1;
    padding-inline: var(--gutter);
    padding-bottom: 2.5rem;
  }

  /* Empilé, le carrousel n'a plus de colonne voisine dont il devrait suivre la
     hauteur : il reprend son ratio et sa hauteur naturelle. */
  .case-hero__media {
    order: 0;
    margin-right: 0;
    height: auto;
    display: block;
    overflow: visible;
  }

  .case-hero__media .carousel__slide mads-picture {
    aspect-ratio: 4 / 5;
    height: auto;
  }
}

/* --------------------------------------------------- retombées presse */

/* Le module suit le hero du cas client, qui n'a pas de marge basse propre :
   c'est ici que se trouve la respiration entre les deux. Mais --section-y
   (jusqu'à 8 rem) est le rythme des grandes sections autonomes ; appliqué des
   deux côtés d'une bande qui s'intercale entre le carrousel et la
   constellation, il isolait le module au lieu de l'y relier. */
.press {
  padding-top: clamp(2rem, 4vw, 3.5rem);
  padding-bottom: clamp(2rem, 4vw, 3.5rem);
}

.press__head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding-left: var(--gutter);
  margin-bottom: 1.25rem;
}

/* Grille horizontale : 4 tuiles visibles, défilement au doigt ou aux flèches.
   scroll-snap cale chaque tuile en bord de zone plutôt que de s'arrêter au
   milieu d'une citation. */
.press__track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, calc((100% - 3 * var(--gap)) / 4));
  gap: var(--gap);
  overflow-x: auto;
  /* Même raison que pour le carrousel : sans cette valeur explicite, l'axe
     vertical bascule sur `auto` et la grille devient défilable en hauteur. */
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-padding-left: var(--gutter);
  padding-inline: var(--gutter);
  /* Masque la barre de défilement sans empêcher le geste */
  scrollbar-width: none;
}

.press__track::-webkit-scrollbar {
  display: none;
}

mads-scroller {
  display: block;
}

.press-item {
  scroll-snap-align: start;
}

/* Le lien enveloppe toute la tuile : c'est lui qui porte la colonne, pas le
   <li>, sinon la zone cliquable ne couvrirait pas les espaces entre blocs. */
.press-item__link {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  height: 100%;
}

.press-item__quote {
  font-family: var(--f-title);
  font-size: var(--t-body);
  font-weight: 300;
  line-height: 1.42;
  /* Les quatre citations n'ont pas la même longueur : on force les métadonnées
     et les images à s'aligner sur une même ligne de base. */
  flex: 1 0 auto;
}

.press-item__meta {
  font-size: var(--t-small);
  line-height: 1.45;
}

.press-item__media {
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 0.22em;
}

.press-item__date {
  color: var(--c-ink-soft);
}

.press-item mads-picture {
  aspect-ratio: 4 / 3;
}

.press-item__link:hover .press-item__media {
  text-decoration-thickness: 2px;
}

@media (max-width: 1100px) {
  .press__track {
    grid-auto-columns: minmax(0, calc((100% - 2 * var(--gap)) / 2.35));
  }
}

@media (max-width: 620px) {
  .press__track {
    grid-auto-columns: minmax(0, 78%);
  }
}

/* ------------------------------------------------------------ mini-hero */

.case-constellation {
  position: relative;
  /* Bandeau : assez haut pour que la constellation respire, sans occuper un
     écran entier — c'est une transition, pas une section. */
  min-height: clamp(20rem, 34vw, 26rem);
  background: var(--c-cream);
}

/* ============================================================== service */

.service {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--col-gap);
  align-items: start;
  padding-top: clamp(5.5rem, 9vw, 7.5rem);
  padding-inline: var(--gutter);
  padding-bottom: var(--section-y);
}

.service__intro {
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  /* La colonne de gauche reste lisible pendant qu'on parcourt les projets */
  position: sticky;
  top: clamp(6rem, 10vw, 8rem);
}

.service__title {
  margin-bottom: 0.4rem;
}

.service__chapo {
  max-width: 40ch;
}

.service__body {
  color: var(--c-ink-soft);
  max-width: 46ch;
  font-size: var(--t-body);
}

.service__projects {
  display: flex;
  flex-direction: column;
}

/* Une ligne = texte + vignette, séparée de la suivante par un filet */
.service-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.92fr);
  gap: clamp(1.25rem, 2.5vw, 2.25rem);
  align-items: start;
  padding-block: clamp(1.5rem, 2.6vw, 2.25rem);
  border-top: 1px solid var(--c-rule);
}

.service-row:first-child {
  border-top: 0;
  padding-top: 0;
}

.service-row__text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.6rem;
  /* Le lien « Lire la suite » se cale en bas de la cellule, comme sur la
     maquette, quelle que soit la longueur du résumé. */
  min-height: 100%;
}

.service-row__title {
  font-size: var(--t-h3);
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* La description portait --t-tiny, jeton réservé aux mentions accessoires
   (compteur du carrousel, message d'erreur de champ, ligne Instagram du pied
   de page mobile). C'est ici un vrai texte de lecture. */
.service-row__desc {
  font-size: var(--t-body);
  color: var(--c-ink-soft);
  line-height: var(--lh-body);
}

/* Le calage en bas de cellule est obtenu par `margin-top: auto`, et par lui
   seul. Un `align-self: flex-end` s'y était ajouté : dans un conteneur en
   colonne, il agit sur l'axe transversal — c'est-à-dire horizontalement — et
   renvoyait le lien à droite au lieu de l'aligner sur la description. */
.service-row__cta {
  margin-top: auto;
  padding-top: 0.9rem;
}

.service-row mads-picture {
  aspect-ratio: 16 / 10;
}

@media (max-width: 900px) {
  .service {
    grid-template-columns: minmax(0, 1fr);
    gap: 2.25rem;
    padding-top: clamp(4.5rem, 16vw, 5.5rem);
  }

  .service__intro {
    position: static;
  }

  /* Mobile : titre, résumé sous l'image — l'ordre de la maquette mobile */
  .service-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.75rem;
  }

  .service-row__media {
    order: -1;
  }

  .service-row__text {
    display: contents;
  }

  .service-row__title {
    order: -2;
  }

  .service-row__cta {
    /* `display: contents` sur le bloc de texte fait du lien un élément de
       grille : on le cale explicitement à gauche, l'étirement par défaut
       n'étant pas un alignement. */
    justify-self: start;
    padding-top: 0.25rem;
  }
}

/* ------------------------------------------------- index des services */

.services-index {
  padding-top: clamp(6rem, 11vw, 9.5rem);
  padding-bottom: var(--section-y);
}

.services-index__intro {
  max-width: 42ch;
  margin-top: 1rem;
}

/* Une seule valeur d'espacement pour toute la liste. L'écart entre deux
   entrées est la gouttière de la grille ; celui entre le filet et le titre
   qui le suit est le retrait haut de l'entrée. Les deux doivent être égaux
   pour que le filet paraisse à égale distance de ce qu'il sépare — d'où la
   variable commune plutôt que deux valeurs à tenir synchronisées. */
.services-index__list {
  --service-gap: clamp(1.5rem, 3vw, 2.75rem);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
  gap: var(--service-gap);
  margin-top: clamp(1.75rem, 3vw, 2.75rem);
}

.services-index__item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.6rem;
  padding-top: var(--service-gap);
  border-top: 1px solid var(--c-rule);
}

.services-index__item h2 {
  font-size: var(--t-h3);
  font-family: var(--f-body);
  font-weight: 600;
}

.services-index__item p {
  font-size: var(--t-body);
  color: var(--c-ink-soft);
}

.services-index__item .arrow-link {
  margin-top: auto;
  padding-top: 0.6rem;
}

/* ============================================================ à propos */

/* ============================== pages « texte + image pleine hauteur » ==== */

/* « À propos » et « Contact » partagent la même composition : une colonne de
   texte respirante, et une image qui court du bord haut de la fenêtre au pied
   de page, sans marge, comme les blocs de la page d'accueil.
   Les règles communes sont ici, les deux pages ne gardant ensuite que ce qui
   les distingue — le côté de l'image, et le contenu de la colonne. */
.about,
.contact {
  /* Les marges verticales appartiennent à la colonne de texte, pas au
     conteneur : sur le conteneur, elles repousseraient aussi l'image. */
  padding-top: 0;
  padding-bottom: 0;
}

.about .duo,
.contact .duo {
  align-items: stretch;
  min-height: var(--vh);
}

.about .duo__text,
.contact .duo__text {
  padding-top: clamp(5.5rem, 9vw, 7.5rem);
  padding-bottom: var(--section-y);
}

.about .duo__media,
.contact .duo__media {
  align-self: stretch;
}

.about .duo__media mads-picture,
.contact .duo__media mads-picture {
  /* Ni ratio ni hauteur minimale : l'image épouse la hauteur de la colonne,
     que la grille étire sur toute la section. */
  aspect-ratio: auto;
  height: 100%;
  min-height: 0;
}

/* Titre de page.
 *
 * Une seule classe pour les six pages qui portaient jusqu'ici trois traitements
 * distincts — --t-h2 sur « À propos », « Contact » et « Services », le petit
 * corps de .t-eyebrow sur « Projets », « Mentions légales » et « Plan du
 * site ». Le corps retenu est --t-h1, le plus grand de l'échelle, déjà celui
 * des titres de projet et de service.
 *
 * La graisse et la police restent celles des maquettes (Poppins demi-gras) :
 * les titres de projet, eux, sont des noms propres composés en Gyst. Si tu
 * veux aussi unifier la police, il suffit de retirer les deux lignes
 * ci-dessous — la règle h1 de base.css prend alors le relais.
 */
.page-title {
  font-family: var(--f-body);
  font-size: var(--t-h1);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.02em;
}

/* Empilées sur mobile, les deux colonnes reprennent une hauteur libre : une
   image de pleine fenêtre y occuperait tout l'écran avant le moindre texte. */
@media (max-width: 900px) {
  .about,
  .contact {
    padding-bottom: var(--section-y);
  }

  .about .duo,
  .contact .duo {
    min-height: 0;
  }

  .about .duo__text,
  .contact .duo__text {
    padding-bottom: 0;
  }

  .about .duo__media mads-picture,
  .contact .duo__media mads-picture {
    aspect-ratio: 4 / 3;
    height: auto;
  }
}

/* =============================================================== à propos */

.about .duo__media {
  /* L'image touche le bord gauche de l'écran */
  margin-left: calc(-1 * var(--gutter));
}

.about__title {
  margin-bottom: 1.1rem;
}

@media (max-width: 900px) {
  .about .duo__media {
    margin-left: 0;
  }
}

/* ============================================================= contact */

.contact .duo__text {
  /* La mesure commune aux blocs éditoriaux ne s'applique pas ici : un
     formulaire n'est pas un paragraphe, le brider à 38ch le tassait dans le
     quart gauche de l'écran. Le titre et le chapô gardent la leur, posée
     explicitement plus bas. */
  --measure: none;
}

.contact .duo__media {
  /* Débord jusqu'au bord droit de la fenêtre, la gouttière du .wrap étant
     annulée par une marge négative de même valeur. */
  margin-right: calc(-1 * var(--gutter));
}

@media (max-width: 900px) {
  .contact .duo__media {
    margin-right: 0;
  }
}

/* Le chapô garde une mesure courte, que --measure: none vient de lever pour
   l'ensemble de la colonne. */
.contact__intro {
  max-width: 34ch;
  margin-bottom: clamp(1.25rem, 2.5vw, 2rem);
  font-size: var(--t-body);
}

.contact__intro a {
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 0.22em;
}

.contact__form {
  display: flex;
  flex-direction: column;
  gap: clamp(0.9rem, 1.6vw, 1.3rem);
  /* Le formulaire occupe toute la colonne, jusqu'à la gouttière qui le sépare
     de l'image. Le plafond en rem n'a plus lieu d'être : c'est la colonne qui
     borne la largeur, et elle vaut déjà la moitié de l'écran. */
  width: 100%;
}

/* mads-contact-form n'est qu'une enveloppe de comportement : sans display,
   c'est un élément inline qui ne transmet pas la largeur au formulaire. */
mads-contact-form {
  display: block;
  width: 100%;
}

/* Champ à filet : le libellé est au-dessus, la ligne dessous — pas de cadre */
.field {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.field label {
  font-size: var(--t-small);
  /* L'interligne de lecture courante (~1,65) creusait un vide sous le
     libellé, indépendamment du `gap`. Sur une étiquette d'une seule ligne, il
     n'apporte rien. */
  line-height: 1.2;
}

.field input,
.field textarea {
  width: 100%;
  line-height: 1.35;
  padding: 0.15rem 0 0.45rem;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--c-ink);
  border-radius: 0;
  font-size: var(--t-body);
  transition: border-color var(--d-fast) ease;
}

.field textarea {
  min-height: 5.5rem;
  resize: vertical;
  line-height: 1.55;
}

.field input:focus,
.field textarea:focus {
  outline: none;
  border-bottom-width: 2px;
  /* Compense l'épaississement du filet pour que le champ ne saute pas */
  margin-bottom: -1px;
}

.field[data-invalid] input,
.field[data-invalid] textarea {
  border-bottom-color: #a4322b;
}

.field__error {
  font-size: var(--t-tiny);
  color: #a4322b;
}

/* Leurre anti-robot : hors du flux et hors de portée du clavier.
   `display:none` serait détecté par les robots un peu sérieux. */
.field--decoy {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.contact__submit {
  align-self: flex-end;
  margin-top: 0.4rem;
  font-size: var(--t-body);
}

.contact__status {
  font-size: var(--t-small);
  line-height: 1.5;
}

.contact__status[data-state='ok'] {
  color: #2f6b4f;
}

.contact__status[data-state='error'] {
  color: #a4322b;
}

/* ======================================================= pages de texte */

.text-page {
  padding-top: clamp(6rem, 11vw, 9.5rem);
  padding-bottom: var(--section-y);
  max-width: 46rem;
}

.text-page h1 {
  margin-bottom: 1.5rem;
}

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

/* ============================================================== erreur */

.error-page {
  min-height: 72vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  /* Le gap uniforme donnait le même poids à tout : le code, le titre, le
     détail et les liens formaient une pile indifférenciée. Les écarts sont
     désormais portés par les éléments, de façon à grouper ce qui se lit
     ensemble et à détacher les sorties. */
  gap: 0;
  padding-top: clamp(6rem, 11vw, 9.5rem);
  padding-bottom: var(--section-y);
}

.error-page__code {
  font-family: var(--f-title);
  font-size: clamp(4rem, 12vw, 9rem);
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.01em;
  /* Le chiffre est le seul élément gris de la page : il annonce sans réclamer
     la lecture, qui doit aller au message juste en dessous. */
  color: var(--c-rule);
  margin-bottom: clamp(0.75rem, 1.5vw, 1.25rem);
}

.error-page__title {
  max-width: 22ch;
}

.error-page__detail {
  max-width: 46ch;
  margin-top: 0.9rem;
  color: var(--c-ink-soft);
}

/* Filet fin et petites capitales espacées : le même traitement que les
   surtitres du pied de page et de l'email de contact. */
.error-page__ref {
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
  padding-top: 0.9rem;
  border-top: 1px solid var(--c-rule);
  font-size: var(--t-tiny);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--c-ink-soft);
}

.error-page__ref b {
  font-weight: 500;
  letter-spacing: 0.16em;
  color: var(--c-ink);
}

.error-page__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.9rem clamp(1.5rem, 4vw, 2.75rem);
  margin-top: clamp(1.75rem, 3.5vw, 2.75rem);
}
