/* components.css — composants et états (DESIGN.md § 5). Les survols sont réservés aux pointeurs fins. */

/* ---------- Icône flèche (seule icône du site) ---------- */
.icon-arrow {
  width: 1em;
  height: 1em;
  flex: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
  transition: transform var(--t-hover) var(--ease-out);
}

/* ---------- Boutons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 3rem;
  padding: 0 1.25rem;
  border: 1.5px solid transparent;
  border-radius: var(--r-1);
  font-family: var(--font-text);
  font-size: 1rem;
  font-weight: var(--fw-ui);
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  background: none;
  transition:
    transform var(--t-press) var(--ease-out),
    background-color var(--t-hover) ease,
    color var(--t-hover) ease,
    border-color var(--t-hover) ease;
}

.btn--sm {
  min-height: 2.75rem;
  padding: 0 1rem;
  font-size: 0.9375rem;
}

.btn--primary {
  background: var(--accent);
  color: var(--paper-0);
  border-color: var(--accent);
}

.btn--secondary {
  color: var(--ink);
  border-color: var(--ink);
}

.btn--block {
  width: 100%;
}

.btn:active {
  transform: scale(0.97);
}

.btn[disabled],
.btn[aria-disabled="true"] {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
}

@media (hover: hover) and (pointer: fine) {
  .btn--primary:hover {
    background: var(--accent-deep);
    border-color: var(--accent-deep);
  }
  .btn--secondary:hover {
    background: var(--ink);
    color: var(--paper);
  }
  .btn:hover .icon-arrow {
    transform: translateX(3px);
  }
  .btn[disabled]:hover,
  .btn[aria-disabled="true"]:hover {
    background: var(--accent);
  }
}

/* ---------- Liens ---------- */
.prose a,
.link {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  transition: text-decoration-thickness var(--t-hover) ease, color var(--t-hover) ease;
}

@media (hover: hover) and (pointer: fine) {
  .prose a:hover,
  .link:hover {
    color: var(--accent);
    text-decoration-thickness: 2px;
  }
}

.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font-weight: var(--fw-ui);
  color: var(--ink);
  text-decoration: none;
  background-image: linear-gradient(var(--accent), var(--accent));
  background-repeat: no-repeat;
  background-size: 0 1px;
  background-position: 0 100%;
  padding-bottom: 2px;
  transition: background-size var(--t-ui) var(--ease-out), color var(--t-hover) ease;
}

@media (hover: hover) and (pointer: fine) {
  .link-arrow:hover {
    background-size: 100% 1px;
    color: var(--accent);
  }
  .link-arrow:hover .icon-arrow {
    transform: translateX(3px);
  }
  .link-arrow:hover .icon-arrow--ext {
    transform: translate(2px, -2px);
  }
}

/* ---------- Étiquettes et repères ---------- */
.label {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  font-weight: var(--fw-ui);
  line-height: var(--lh-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

.label--plain {
  text-transform: none;
  letter-spacing: 0.02em;
}

.tag {
  display: inline-block;
  padding: 0.125rem 0.5rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-1);
}

.tag--accent {
  color: var(--accent);
  background: var(--accent-tint);
  border-color: transparent;
}

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  list-style: none;
  padding: 0;
}

.sec-num {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-4);
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  font-weight: var(--fw-ui);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
}

.sec-num::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

/* Ligne de cote */
.dim {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 1rem;
  color: var(--accent);
}

.dim__line {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 1px;
  background: currentColor;
  transform-origin: left;
}

.dim__label {
  position: relative;
  z-index: 1;
  padding: 0 var(--s-3);
  background: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  font-weight: var(--fw-ui);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
}

.dim__tick {
  position: absolute;
  top: 4px;
  width: 1px;
  height: 8px;
  background: currentColor;
}

.dim__tick--l { left: 0; }
.dim__tick--r { right: 0; }

.dim--left {
  justify-content: flex-start;
}

.dim--left .dim__label {
  padding-left: 0;
  padding-right: var(--s-3);
}

/* Repères de coin */
.marks {
  position: relative;
}

.marks__m {
  position: absolute;
  width: 10px;
  height: 10px;
  pointer-events: none;
}

.marks__m::before,
.marks__m::after {
  content: "";
  position: absolute;
  background: var(--line-strong);
}

.marks__m::before {
  left: 0;
  top: 50%;
  width: 100%;
  height: 1px;
}

.marks__m::after {
  top: 0;
  left: 50%;
  width: 1px;
  height: 100%;
}

.marks__m--tl { top: -14px; left: -14px; }
.marks__m--tr { top: -14px; right: -14px; }
.marks__m--bl { bottom: -14px; left: -14px; }
.marks__m--br { bottom: -14px; right: -14px; }

/* Trame de points (hero) */
.dots {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: radial-gradient(var(--line) 1px, transparent 1.2px);
  background-size: 16px 16px;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0%, transparent 100%);
}

/* ---------- Cadres de captures ---------- */
.frame {
  position: relative;
  background: var(--paper-0);
  border: 1px solid var(--line-strong);
  overflow: hidden;
  transition: border-color var(--t-hover) ease;
}

.frame img {
  display: block;
  width: 100%;
  height: auto;
}

.frame--browser {
  border-radius: var(--r-2);
}

.frame__bar {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 10px;
  background: var(--paper-2);
  border-bottom: 1px solid var(--line);
}

.frame__dot {
  width: 8px;
  height: 8px;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
}

.frame__url {
  margin-left: auto;
  padding: 1px 8px;
  border-radius: var(--r-1);
  background: var(--paper-0);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 70%;
}

/* Sous 640 px, la pastille serait tronquée par des points de suspension et deviendrait le texte le plus gros
   du cadre : on la retire plutôt que de la couper. */
@media (max-width: 39.99rem) {
  .frame__url {
    display: none;
  }
}

.frame--mail .frame__dot {
  display: none;
}

/* AUCUN CADRE N'IMPOSE SON RAPPORT À SA CAPTURE : c'est la capture qui donne sa hauteur à l'écran.
   État antérieur : `aspect-ratio` sur le CADRE (qui a 0,5 rem de rembourrage sur ses quatre côtés, donc un
   rapport qui n'est jamais celui de son écran) + `object-fit: cover`. Mesuré au navigateur, ça tranchait
   4,4 % à 8,1 % de la LARGEUR des captures téléphone — d'où les libellés de barre d'onglets amputés de leur
   première et de leur dernière lettre (« ccueil », « Compt ») — et 6,4 % de la HAUTEUR des deux captures
   mobiles CBD (750 × 1526), c'est-à-dire la barre d'onglets basse coupée en deux.
   Déplacer le rapport sur l'écran réglait le premier cas, pas le second : une capture qui n'est pas
   exactement en 390/844 restait rognée. Donc plus de rapport imposé du tout — l'image coule (`height: auto`),
   le cadre suit, et `width`/`height` du <img> (manifest) réservent la place avant le chargement : aucun
   décalage de mise en page. Rognage résiduel mesuré sur toutes les pages, aux trois largeurs : 0 %.
   `.frame--fit` reste dans le balisage (data/projets.js, home.ejs) : c'est devenu le comportement par défaut. */
.frame--phone {
  width: 300px;
  max-width: 100%;
  padding: 0.5rem;
  border: 2px solid var(--ink);
  border-radius: var(--r-phone);
  background: var(--paper-0);
}

.frame--phone .frame__screen {
  position: relative;
  border-radius: var(--r-phone-in);
  overflow: hidden;
  background: var(--paper-0);
}

.frame--phone .frame__screen::before {
  content: "";
  position: absolute;
  top: 8px;
  left: 50%;
  width: 60px;
  height: 5px;
  margin-left: -30px;
  border-radius: 3px;
  background: var(--ink);
  z-index: 1;
}

.frame--phone img,
.frame--tablet img {
  width: 100%;
  height: auto;
}

/* Les rayons suivent la largeur du cadre et restent concentriques (extérieur − 8 px de coque = intérieur) :
   voir tokens.css pour la valeur par défaut et la raison. Laissé à 1,75 rem sur un écran de 200 px,
   l'arrondi mordait 8,9 px dans une barre d'onglets à fond perdu et lui mangeait sa première lettre
   (« bleau de bord » au lieu de « Tableau de bord », mesuré sur la carte du projet A à 390 px). */
.frame--phone.frame--w260 {
  width: 260px;
}

.frame--phone.frame--w220 {
  width: 220px;
  --r-phone: 1.25rem;
  --r-phone-in: 0.75rem;
}

.frame--tablet {
  padding: 0.5rem;
  border: 2px solid var(--ink);
  border-radius: var(--r-tablet);
}

.frame--tablet .frame__screen {
  position: relative;
  border-radius: calc(var(--r-tablet) - 0.5rem);
  overflow: hidden;
}

.frame--tall {
  height: 32rem;
}

/* Captures pleine page : une VRAIE région défilante, pas un <div> qui écoute le clic.
   Le mécanisme précédent (survol au pointeur fin + `click` sur un div nu) n'existait ni au clavier
   ni sous prefers-reduced-motion : les trois légendes qui promettent une page « complète » étaient
   fausses pour ces deux publics, et axe-core ne signale pas un gestionnaire de clic sur un div.
   Ici le déroulé est du défilement natif : molette, doigt, barre de défilement, et flèches / Page
   suivante une fois la région focalisée (tabindex="0", role="group", nom accessible dans la vue).
   axe le voit désormais : la règle `scrollable-region-focusable` porte exactement sur ce cas. */
.frame--tall .frame__scroll {
  height: calc(100% - 28px);
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior-y: auto;
  /* Barre de défilement classique et permanente, gouttière réservée : c'est l'affordance visible du
     mécanisme (Chromium pose sinon une barre flottante qui n'apparaît qu'au geste), et dans un cadre
     qui figure une fenêtre de navigateur, une barre de défilement est à sa place. `stable` garde la
     gouttière quand l'aperçu au survol passe la région en `overflow: hidden` : aucune largeur ne bouge. */
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) var(--paper-2);
}

/* Repli pour les moteurs qui n'ont pas les deux propriétés standard ci-dessus (elles les neutralisent
   quand elles sont reconnues) : mêmes couleurs, mêmes jetons. */
.frame--tall .frame__scroll::-webkit-scrollbar {
  width: 10px;
}

.frame--tall .frame__scroll::-webkit-scrollbar-track {
  background: var(--paper-2);
}

.frame--tall .frame__scroll::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border: 2px solid var(--paper-2);
  border-radius: 5px;
}

/* La bordure du cadre est en `overflow: hidden` : un contour posé à l'extérieur serait rogné. */
.frame--tall .frame__scroll:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -4px;
}

.frame--tall img {
  will-change: transform;
}

@media (hover: hover) and (pointer: fine) {
  a:hover .frame,
  .frame:hover {
    border-color: var(--ink);
  }
}

figure.fig {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

figcaption {
  display: flex;
  gap: var(--s-3);
  align-items: baseline;
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--ink-2);
}

figcaption .label {
  flex: none;
}

/* ---------- Cartes projet ---------- */
.proj {
  position: relative;
  padding-block: var(--s-7);
  border-top: 1px solid var(--line-strong);
}

.proj::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 100%;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 240ms var(--ease-out);
}

.proj__index {
  font-family: var(--font-mono);
  font-size: var(--fs-figure);
  font-weight: var(--fw-ui);
  line-height: 1;
  color: var(--accent);
}

.proj__title {
  margin-top: var(--s-3);
}

/* La carte entière est la cible du lien (cible tactile de ~350 × 900 px au lieu d'un libellé de 21 px de
   haut), et c'est un VRAI lien : pas un <div> avec un gestionnaire de clic. Le contour de focus suivait
   le seul texte du titre, ce qui ne disait pas ce qui allait s'ouvrir — il ceinture maintenant la carte.
   `:has()` plutôt que `:focus-within` : la souris ne doit pas déclencher le contour. Sur un moteur sans
   `:has()`, les deux règles tombent ensemble et le lien garde son propre contour : aucune régression. */
.proj__link::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Sans z-index, la vignette échappait au lien : motion.js rend .proj__frame positionné (voile de
     révélation), et un élément positionné plus loin dans le DOM se peint au-dessus. Vérifié : un clic
     sur la capture ne naviguait pas, un clic sur le texte si. Le voile est en `pointer-events: none`,
     il ne reprend pas le clic. */
  z-index: 1;
}

.proj:has(.proj__link:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

.proj:has(.proj__link:focus-visible) .proj__link:focus-visible {
  outline: none;
}

.proofs {
  list-style: none;
  padding: 0;
  margin-top: var(--s-4);
}

.proofs li,
.checklist li {
  position: relative;
  padding-left: 1.5rem;
}

.proofs li + li,
.checklist li + li {
  margin-top: var(--s-2);
}

.proofs li::before,
.checklist li::before {
  content: "✓";
  position: absolute;
  left: 0;
  top: 0.1em;
  font-family: var(--font-mono);
  font-size: 0.9em;
  color: var(--accent);
}

.checklist {
  list-style: none;
  padding: 0;
}

.bullets {
  list-style: none;
  padding: 0;
}

.bullets li {
  position: relative;
  padding-left: 1.25rem;
}

.bullets li + li {
  margin-top: var(--s-2);
}

.bullets li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.65em;
  width: 4px;
  height: 4px;
  background: var(--accent);
}

/* Titre de projet : le dégradé de .link-arrow est réservé aux liens d'une seule ligne — dans un h3 la boîte
   inline-flex prend toute la largeur de la colonne et le filet dépasse le texte. text-decoration suit les
   fragments de ligne, donc le soulignement s'arrête où le titre s'arrête. */
.proj__title .link-arrow {
  display: inline;
  background-image: none;
  padding-bottom: 0;
  text-decoration: underline;
  text-decoration-color: transparent;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  transition: text-decoration-color var(--t-hover) ease, color var(--t-hover) ease;
}

@media (hover: hover) and (pointer: fine) {
  .proj:hover::before {
    transform: scaleX(1);
  }
  .proj:hover .proj__title .link-arrow {
    color: var(--accent);
    text-decoration-color: var(--accent);
  }
  .proj:hover .frame {
    border-color: var(--ink);
  }
}

/* ---------- Chapitre sombre (DESIGN.md § 2 bis) ---------- */
/* Un seul chapitre du site passe sur fond sombre : la section 02 de l'accueil (projets + chiffres).
   Le fond est STATIQUE, posé par cette règle : aucune bascule pilotée au défilement, aucun JavaScript —
   un déclencheur qui se déclenche trop tôt affiche un titre dans le costume du chapitre précédent.

   Rien n'est redessiné : la section réécrit les jetons sémantiques pour ses descendants, et les composants
   (cadres, étiquettes, coches, filets, repères, focus, voile de révélation) suivent sans une seule règle
   supplémentaire. Le fond n'est pas une couleur de plus, c'est l'encre du site ; le texte est son papier ;
   l'accent est le même rouge de marque (orange brûlé jusqu'au 8 septembre 2026, après-midi), remonté en
   clarté pour tenir le contraste sur l'encre — à 68 % et non ≈56 % comme l'ancien orange, parce qu'à
   pleine saturation le rouge sature plus vite (voir tokens.css § chapitre sombre).

   Contrastes mesurés (WCAG 2.2, sur le fond #1B1A17), recalculés au retint du 8 septembre : texte 15,4:1 ·
   texte secondaire 9,7:1 · étiquettes mono 6,0:1 · accent 5,75:1 · bordures de composants 4,6:1
   (≥ 3:1 exigé). */
.section--dark {
  --paper: var(--slate);
  --paper-0: var(--slate-0);
  --paper-2: var(--slate-2);
  --ink: var(--chalk);
  --ink-2: var(--chalk-2);
  --ink-3: var(--chalk-3);
  --accent: var(--accent-lift);
  --accent-deep: var(--accent-lift-deep);
  --accent-tint: var(--accent-shade);
  --line: var(--line-dark);
  --line-strong: var(--line-dark-strong);

  background: var(--paper);
  color: var(--ink);
  /* La jointure avec les sections papier est une arête franche : le chapitre commence et finit là.
     Un peu plus d'air en haut et en bas pour qu'il se lise comme un chapitre et non comme un encadré. */
  padding-block: calc(var(--section-y) + var(--s-5));
}

/* Le cadre téléphone et le cadre tablette sont bordés de 2 px `--ink` : sur fond sombre l'encre du chapitre
   est la craie, la bordure suit toute seule. Les captures, elles, restent des interfaces claires : c'est
   voulu, elles ressortent comme des feuilles posées sur la table. */

/* ---------- Bande de chiffres ---------- */
.figures {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

@media (min-width: 56.25rem) {
  .figures {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.figures__cell {
  padding: var(--s-6) var(--s-5);
  border-right: 1px solid var(--line);
}

.figures__cell:nth-child(2n) {
  border-right: 0;
}

@media (min-width: 56.25rem) {
  .figures__cell:nth-child(2n) {
    border-right: 1px solid var(--line);
  }
  .figures__cell:nth-child(4n),
  .figures__cell:last-child {
    border-right: 0;
  }
}

.figure {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-figure);
  font-weight: var(--fw-ui);
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.figure__unit {
  font-size: 0.5em;
  color: var(--accent);
  margin-left: 0.2em;
}

.figures__label {
  display: block;
  margin-top: var(--s-3);
  font-size: var(--fs-small);
  color: var(--ink-2);
}

.figures__note {
  margin-top: var(--s-4);
}

/* ---------- Étapes ---------- */
.steps {
  list-style: none;
  padding: 0;
  margin: 0;
  border-left: 1px solid var(--line-strong);
}

.step {
  position: relative;
  padding: 0 0 var(--s-8) var(--s-6);
}

.step:last-child {
  padding-bottom: 0;
}

.step::before {
  content: "";
  position: absolute;
  left: -4.5px;
  top: 0.55rem;
  width: 8px;
  height: 8px;
  background: var(--accent);
}

.step__num {
  display: block;
  margin-bottom: var(--s-2);
}

.step__cols {
  display: grid;
  gap: var(--s-5);
  margin-top: var(--s-4);
}

@media (min-width: 40rem) {
  .step__cols {
    grid-template-columns: 1fr 1fr;
  }
}

/* À partir de 900 px l'étape s'ouvre sur toute la largeur : repère, titre et chapeau à gauche,
   « vous apportez / vous recevez » à droite. La liste n'a plus besoin d'une colonne d'accompagnement
   qui resterait vide sur 1 500 px, et chaque colonne de texte revient sous la mesure (~46 caractères). */
@media (min-width: 56.25rem) {
  .step {
    display: grid;
    grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
    column-gap: var(--s-7);
    align-items: start;
  }
  .step__head {
    padding-right: var(--s-5);
  }
  .step__cols {
    margin-top: 0;
  }
}

.step__cols .label {
  display: block;
  margin-bottom: var(--s-2);
}

/* ---------- Offres ---------- */
.offer {
  padding-block: var(--s-8);
  border-top: 1px solid var(--line);
}

/* flex-end et non baseline : la baseline aligne le pavé de prix sur l'étiquette « Offre 1 », ce qui le fait
   flotter au-dessus du h3 sans être aligné sur rien. Aligné en bas, il tient au titre. */
.offer__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-3) var(--s-6);
}

.price {
  display: inline-flex;
  align-items: baseline;
  gap: var(--s-3);
}

.price__value {
  font-family: var(--font-mono);
  font-size: 1.75rem;
  font-weight: var(--fw-ui);
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--ink);
  white-space: nowrap;
}

/* ---------- Formulaire ---------- */
.form {
  max-width: var(--measure);
}

.field + .field {
  margin-top: var(--s-5);
}

.field__label {
  display: block;
  margin-bottom: var(--s-2);
  font-weight: var(--fw-ui);
}

.field__req {
  color: var(--accent);
}

.field__help {
  margin-top: var(--s-2);
  font-size: var(--fs-small);
  color: var(--ink-3);
}

.field__error {
  margin-top: var(--s-2);
  font-size: var(--fs-small);
  font-weight: var(--fw-ui);
  color: var(--error);
}

.input,
.select,
.textarea {
  display: block;
  width: 100%;
  min-height: 3rem;
  padding: 0.75rem 0.875rem;
  background: var(--paper-0);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-1);
  color: var(--ink);
  font-size: 1rem;
  line-height: 1.4;
  transition: border-color var(--t-hover) ease, box-shadow var(--t-hover) ease;
}

.textarea {
  min-height: 10rem;
  resize: vertical;
}

.select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 2.5rem;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='%231B1A17' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.875rem center;
  background-size: 16px 16px;
}

.input::placeholder,
.textarea::placeholder {
  color: var(--ink-3);
  opacity: 1;
}

.input:focus,
.select:focus,
.textarea:focus {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.input[aria-invalid="true"],
.select[aria-invalid="true"],
.textarea[aria-invalid="true"] {
  /* Repère non coloré (WCAG 1.4.1) : le rouge d'erreur reste proche du rouge d'accent malgré l'écart de
     teinte/clarté (tokens.css § --error) — le contour passe donc à 2 px, plus épais que le 1 px du focus
     à l'accent, pour que l'état d'erreur se distingue aussi par la forme, pas seulement par la couleur. */
  border-color: var(--error);
  box-shadow: inset 0 0 0 2px var(--error);
}

.hp {
  position: absolute;
  left: -10000px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form__actions {
  margin-top: var(--s-6);
}

.form__rgpd {
  margin-top: var(--s-5);
  font-size: var(--fs-small);
  color: var(--ink-2);
}

/* ---------- Messages ---------- */
.notice {
  padding: var(--s-5);
  background: var(--paper-2);
  border-left: 3px solid var(--accent);
}

.notice--error {
  /* Filet plus épais qu'un `.notice` accentué : même logique que le contour des champs invalides,
     un repère de forme en plus de la couleur (WCAG 1.4.1). */
  border-left-width: 4px;
  border-left-color: var(--error);
}

.notice > * + * {
  margin-top: var(--s-3);
}

/* ---------- En-tête et navigation ---------- */
/* En-tête COLLANT (repris de site-b, DESIGN.md § 5 disait « non collant » : la décision est rouverte).
   L'accueil fait ~14 000 px de haut à 390 px. Non collant, l'en-tête n'existait que dans la première
   bande : passé le premier écran, plus aucune route vers Contact ni vers le téléphone sur les dix
   suivantes. Collant, le menu et le bouton d'appel restent à un geste sur toute la page.
   `scroll-padding-top` sur html (base.css) évite qu'une ancre atterrisse sous l'en-tête. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  border-bottom: 1px solid var(--line);
  background: var(--paper);
  view-transition-name: site-header;
}

.site-header__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  min-height: var(--header-h);
}

/* La marque : le symbole rouge (logos/serie-3/01-rouge) puis le nom en texte. Le symbole est aligné sur la
   HAUTEUR DE CAPITALE du nom, pas sur la ligne de base, d'où les deux boîtes imbriquées : `.brand` centre le
   carré sur le bloc de texte, `.brand__text` garde l'alignement sur la ligne de base entre le nom et la place. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  text-decoration: none;
  color: var(--ink);
  font-weight: var(--fw-title);
  font-size: 1.125rem;
  padding-block: var(--s-2);
}

.brand__text {
  display: inline-flex;
  align-items: baseline;
  gap: var(--s-3);
}

/* Taille en em : le symbole suit le nom si la taille du texte change (zoom texte seul compris).
   1,3333 em et pas 1,3 : `.brand` est à 1,125 rem = 18 px, donc le symbole tombe sur 24,0 px pile aux
   réglages par défaut — c'est ce nombre-là, et pas un autre, que les attributs width/height du HTML
   annoncent. La hauteur de capitale de Hanken Grotesk vaut ~0,73 em : le symbole la dépasse d'un tiers,
   comme dans le bloc-marque livré (1,62 × capitale). */
.brand__mark {
  flex: none;
  width: 1.3333em;
  height: 1.3333em;
  display: block;
}

/* Sous 640 px, la place passe sur une deuxième ligne (l'en-tête fait 64 px) au lieu de disparaître :
   c'est le mot qui distingue JRS computing d'une agence anonyme. */
@media (max-width: 39.99rem) {
  .brand__text {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
  }
  .brand {
    padding-block: var(--s-1);
  }
  .brand__sep {
    display: none;
  }
}

/* Sous 360 px (320 px de large : le plus petit écran courant), marque + « Appeler » + « Menu » ne tiennent plus sur
   une ligne : la place (« Montélimar ») disparaît de l'en-tête (elle reste dans le pied de page et le texte), le nom ne se
   coupe pas, et les marges de l'en-tête se resserrent. */
@media (max-width: 22.49rem) {
  .brand__place {
    display: none;
  }
  .brand__name {
    white-space: nowrap;
  }
  .site-header__bar {
    gap: var(--s-1);
  }
  .brand {
    font-size: 1rem;
    gap: var(--s-2);
  }
  .nav-call {
    padding: 0 var(--s-1);
    font-size: 0.875rem;
  }
  .nav-toggle {
    padding: 0 var(--s-2);
    font-size: 0.875rem;
  }
}

.nav {
  display: none;
}

.nav__list {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  list-style: none;
  margin: 0;
  padding: 0;
}

.nav__link {
  position: relative;
  display: inline-block;
  padding-block: var(--s-2);
  font-size: 0.9375rem;
  font-weight: var(--fw-ui);
  color: var(--ink);
  text-decoration: none;
}

.nav__link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--t-ui) var(--ease-out);
}

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

@media (hover: hover) and (pointer: fine) {
  .nav__link:hover::after {
    transform: scaleX(1);
  }
}

.nav-toggle {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  padding: 0 var(--s-3);
  border: 1.5px solid var(--ink);
  border-radius: var(--r-1);
  background: none;
  font-weight: var(--fw-ui);
  font-size: 0.9375rem;
}

/* Accès rapide à l'appel sur mobile : un lien « Appeler » à côté du bouton Menu (44 px de haut), rien de fixe en bas.
   Masqué dès que la navigation de bureau s'affiche. */
.nav-call {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  margin-left: auto;
  padding: 0 var(--s-2);
  font-size: 0.9375rem;
  font-weight: var(--fw-ui);
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}

html:not(.js) .nav-call {
  display: none;
}

.nav-panel {
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: var(--paper-2);
  /* L'en-tête est collant : ouvert, le panneau ne doit jamais dépasser la fenêtre sans pouvoir défiler. */
  max-height: calc(100dvh - var(--header-h));
  overflow-y: auto;
}

.nav-panel[hidden] {
  display: none;
}

.nav-panel__list {
  list-style: none;
  margin: 0;
  padding: var(--s-3) 0 var(--s-5);
}

.nav-panel__list li + li {
  border-top: 1px solid var(--line);
}

.nav-panel__link {
  display: flex;
  align-items: center;
  min-height: 3.5rem;
  font-weight: var(--fw-ui);
  text-decoration: none;
  color: var(--ink);
}

.nav-panel__link[aria-current="page"] {
  color: var(--accent);
}

.nav-panel__cta {
  margin-top: var(--s-4);
}

/* Sans JS : le panneau mobile est toujours déplié, le bouton disparaît. Un en-tête collant qui porte
   un panneau toujours ouvert mangerait la moitié de l'écran : sans JS, l'en-tête redevient statique. */
html:not(.js) .site-header {
  position: static;
}

html:not(.js) .nav-toggle {
  display: none;
}

html:not(.js) .nav-panel[hidden] {
  display: block;
}

html:not(.js) .nav-panel {
  opacity: 1;
  transform: none;
  max-height: none;
  overflow: visible;
}

@media (min-width: 56.25rem) {
  .nav {
    display: block;
  }
  .nav-toggle,
  .nav-call,
  .nav-panel {
    display: none !important;
  }
}

/* ---------- Bandeaux ---------- */
.band {
  background: var(--paper-2);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  padding-block: var(--s-6);
}

.band__inner {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}

@media (min-width: 40rem) {
  .band__inner {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-6);
  }
}

.band .lead {
  color: var(--ink);
}

/* Bandeau de clôture « Un devis, ça commence par un appel » : phrase, deux actions, une ligne de rappel. */
.band__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
}

.band__note {
  margin-top: var(--s-3);
  color: var(--ink-2);
}

/* ---------- Pied de page ---------- */
.site-footer {
  margin-top: auto;
  border-top: 2px solid var(--ink);
  padding-top: var(--s-8);
  padding-bottom: var(--s-6);
}

.site-footer__cols {
  display: grid;
  gap: var(--s-6);
}

/* Deux colonnes entre 640 et 900 px (le plan passe dessous), trois à partir de 900 px : l'e-mail en mono ne se coupe pas. */
@media (min-width: 40rem) {
  .site-footer__cols {
    grid-template-columns: 1fr 1fr;
  }
}

@media (min-width: 56.25rem) {
  .site-footer__cols {
    grid-template-columns: 1.1fr 1.3fr 0.8fr;
  }
}

.site-footer__contact a {
  overflow-wrap: anywhere;
}

/* Téléphone et e-mail en mono --fs-mono-text (DESIGN.md § 5, pied de page). */
.site-footer__contact .mono {
  font-size: var(--fs-mono-text);
}

.site-footer__col > * + * {
  margin-top: var(--s-2);
}

.site-footer__brand {
  font-weight: var(--fw-title);
  font-size: 1.125rem;
}

.site-footer .small {
  color: var(--ink-2);
}

.site-footer__list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.site-footer__list a {
  display: inline-block;
  padding-block: var(--s-2);
  font-size: var(--fs-small);
  font-weight: var(--fw-ui);
}

.site-footer__contact a {
  display: inline-block;
  padding-block: var(--s-2);
}

.site-footer__legal {
  margin-top: var(--s-7);
  max-width: 60rem;
  overflow-wrap: anywhere;
}

.site-footer__legal + .site-footer__bottom {
  margin-top: var(--s-4);
}

.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--s-3) var(--s-5);
  margin-top: var(--s-8);
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
}

.site-footer__bottom .label {
  text-transform: none;
  letter-spacing: 0.02em;
}

/* ---------- En-tête de page (contact, mentions, données, 404, 500) ---------- */
.page-head {
  padding-bottom: var(--s-7);
}

.page-head--tight {
  padding-bottom: var(--s-5);
}

.page-head .label + h1 {
  margin-top: var(--s-3);
}

.page-head__lead {
  margin-top: var(--s-5);
}

.page-head__dim {
  margin-top: var(--s-7);
}

.page-head__dim--short {
  max-width: 20rem;
  margin-top: var(--s-6);
}

/* ---------- Pages d'erreur (404, 500) ---------- */
.error-page {
  padding-block: var(--section-y) var(--s-10);
}

.error-page__body > .page-head__dim {
  margin-top: 0;
}

.error-page__body > .page-head__dim + .label {
  margin-top: var(--s-5);
}

.error-page__body > .label + h1 {
  margin-top: var(--s-3);
}

/* ---------- Contact : formulaire (partials/contact-form) + coordonnées (partials/contact-aside) ---------- */
.contact__grid {
  row-gap: var(--s-7);
}

.contact__notice {
  margin-bottom: var(--s-6);
  max-width: var(--measure);
}

.contact__notice h2,
.contact__notice h3 {
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
}

.contact__card {
  padding-top: var(--s-5);
  border-top: 1px solid var(--line-strong);
}

.contact__card > .label + p {
  margin-top: var(--s-1);
}

.contact__card > p + .label {
  margin-top: var(--s-5);
}

.contact__card .small {
  color: var(--ink-2);
}

/* Page /contact : « Ce qui se passe ensuite » et rappel des prix (v2.1.0). */
.contact__next {
  margin-top: var(--s-6);
}

.contact__next-title {
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  margin-bottom: var(--s-3);
}

.contact__prices {
  margin-top: var(--s-4);
  color: var(--ink-2);
  max-width: var(--measure);
}

.contact__actions {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  margin-top: var(--s-1);
}

.contact__actions .contact__big {
  width: 100%;
  font-size: 1rem;
  font-weight: var(--fw-ui);
}

.contact__actions + .small {
  margin-top: var(--s-3);
}

/* Le téléphone est une action, pas une ligne de texte : cible pleine largeur de 48 px (WCAG 2.2 AA, 2.5.8). Deux boutons de
   même hauteur depuis la v2.1.0 (téléphone et WhatsApp), dans .contact__actions. */
.contact__big {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 3rem;
  padding: 0 1.25rem;
  border: 1.5px solid var(--ink);
  border-radius: var(--r-1);
  font-size: 1.25rem;
  text-decoration: none;
  transition: background-color var(--t-hover) ease, color var(--t-hover) ease;
}

@media (hover: hover) and (pointer: fine) {
  .contact__big:hover {
    color: var(--paper-0);
    background: var(--ink);
    text-decoration: none;
  }
}

/* Lien hors phrase : l'exception « inline » de 2.5.8 ne s'applique pas, il lui faut 24 px de haut. */
.contact__mail {
  display: inline-block;
  padding-block: var(--s-2);
  font-size: 1rem;
  overflow-wrap: anywhere;
}

/* Sous 900 px, la gérante qui clique sur « Contact » veut appeler : le numéro passe avant les cinq champs
   et le pavé RGPD, au lieu d'être à 1 400 px sous la ligne de flottaison. */
@media (max-width: 56.24rem) {
  .contact__aside {
    order: -1;
  }
  .contact__big {
    width: 100%;
  }
}

@media (min-width: 56.25rem) {
  .contact__aside {
    grid-column: 9 / span 4;
    position: sticky;
    top: var(--s-6);
    align-self: start;
  }
}

/* ---------- Compositions de captures des cartes projet (accueil) ---------- */
/* Enveloppe non positionnée qui porte data-reveal="frame" : le clip-path ne s'applique jamais à un élément positionné
   (DESIGN.md § 6) ; motion.js observe le parent, car Chromium ne déclenche pas l'IntersectionObserver sur un élément
   entièrement rogné par clip-path. */
.proj__frame {
  width: 100%;
}

/* Navigateur + téléphone (fiche B) : côte à côte, jamais superposés — le téléphone recouvrait le titre de la
   vitrine (« …orks, …es …ode »), ce qui se lisait comme un bug d'empilement, pas comme une composition.
   Le téléphone est décalé vers le bas ; la hauteur suit le plus haut des deux. */
.stage {
  display: grid;
  grid-template-columns: 8.5rem minmax(0, 1fr);
  column-gap: var(--s-4);
  grid-template-rows: 2rem auto;
  align-items: start;
}

.stage__browser {
  grid-column: 2 / 3;
  grid-row: 1 / 3;
}

.stage__phone {
  grid-column: 1 / 2;
  grid-row: 2 / 3;
  justify-self: start;
  width: 100%;
}

/* Sous 640 px, la capture de vitrine de 1440 px tomberait à ~200 px : illisible. Le téléphone — capture
   native — porte seul la preuve, comme sur les deux autres cartes. */
@media (max-width: 39.99rem) {
  .stage {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto;
    justify-items: center;
  }
  .stage__browser {
    display: none;
  }
  .stage__phone {
    grid-column: 1;
    grid-row: 1;
    justify-self: center;
    width: 220px;
    max-width: 100%;
  }
}

/* ---------- Parcours (« Qui est derrière JRS computing ») : puces à filets, repère de cote accent ---------- */
.parcours li {
  position: relative;
  padding: var(--s-4) 0 var(--s-4) 1.75rem;
  border-top: 1px solid var(--line);
  text-wrap: pretty;
}

.parcours li:last-child {
  border-bottom: 1px solid var(--line);
}

.parcours li::before {
  content: "";
  position: absolute;
  left: 0;
  top: calc(var(--s-4) + 0.7em);
  width: 0.875rem;
  height: 2px;
  background: var(--accent);
}
