/* ==========================================================================
   MACHINE REVIVAL — Site 2026 — V4 « direction artistique »
   Feuille de style unique : jetons, grille, blocs, composants.

   Parti pris, en trois règles qui ne souffrent pas d'exception :

   1. AUCUNE COULEUR. Du noir, du blanc, et des gris neutres pour hiérarchiser.
      Pas une teinte, pas un accent, pas même sur les états d'erreur — ceux-ci
      se signalent par le trait et le mot, jamais par la couleur seule.
   2. AUCUN ANGLE ARRONDI. La règle est réaffirmée en fin de feuille, pour
      qu'aucun composant ne la contourne par inadvertance.
   3. LA HIÉRARCHIE SE FAIT PAR LA TAILLE ET PAR LE VIDE. Les graisses restent
      constantes — 400 partout, 500 pour le seul mono des cartels. Ce qui
      distingue un titre d'un paragraphe, c'est son corps et l'air autour.

   Le modèle : une salle d'exposition. Le mur est blanc, l'œuvre est
   l'automobile, et chaque œuvre porte son cartel — ces petites lignes en
   capitales étroites qui disent le titre, la base, l'année, le statut.
   ========================================================================== */

/* --- 0. Fontes servies par le site ------------------------------------- */
/* Servies depuis le domaine, jamais depuis un CDN tiers : une page française
   qui appelle fonts.gstatic.com transmet l'IP du visiteur à un tiers, et cela
   a déjà valu des mises en demeure. Deux fichiers, 168 Ko en tout. */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-latin-wght-normal.woff2") format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-latin-ext-wght-normal.woff2") format("woff2-variations");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-latin-500-normal.woff2") format("woff2");
}

/* --- 1. Reset ------------------------------------------------------------ */

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--sans);
  font-size: var(--fs-body);
  line-height: 1.65;
  letter-spacing: -.002em;
  font-weight: 400;
  color: var(--ink);
  background: var(--bg);
  padding-top: var(--header-h);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

button { font: inherit; color: inherit; }

a { color: inherit; }

/* Les titres ne pèsent pas plus lourd que le texte : ils sont plus grands. */
h1, h2, h3, h4 {
  margin: 0 0 .5em;
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -.025em;
}
p { margin: 0 0 1.15em; }
p:last-child { margin-bottom: 0; }

::selection { background: var(--ink); color: var(--paper); }

/* Le site est clair, et il n'a pas de variante sombre. Sans cette ligne,
   Chrome sur Android et Samsung Internet se croient autorises a l'assombrir
   eux-memes : ils repeignent les fonds, et au passage le texte blanc pose sur
   les photographies du diptyque des Machines. `only` est important -- `light`
   seul annonce une preference, `only light` refuse la substitution.

   Le jour ou le site aura un vrai mode sombre, cette valeur devra passer a
   `light dark` et non disparaitre. */
:root { color-scheme: only light; }

/* --- 2. Variables ------------------------------------------------------- */

:root {
  /* — Valeurs. Rien d'autre que du neutre. — */
  --paper:       #ffffff;
  --ink:         #000000;
  --ink-soft:    #6f6f6f;   /* texte secondaire — 4.9:1 sur blanc */
  --ink-faint:   #9b9b9b;   /* cartels et mentions, jamais porteur seul */
  --line:        #e4e4e4;   /* filet ordinaire */
  --line-strong: #c2c2c2;   /* filet appuyé */
  --ph:          #f2f2f2;   /* fond d'un média non encore produit */
  --ph-ink:      #a8a8a8;
  --tag:         #b4b4b4;   /* annotations de production — les plus discrètes */

  /* Alias conservés depuis la V1 : les composants hérités les lisent encore. */
  --bg:      var(--paper);
  --surface: var(--paper);
  --accent:  var(--ink);
  --note:    var(--ink);    /* erreurs et focus : noirs, jamais orange */

  /* — Familles. —
     Inter tient lieu de grotesque de travail : neutre, variable, libre, très
     proche des Neue Haas et Helvetica Now des sites de référence. Le jour où
     une licence est prise (Neue Haas Grotesk, Söhne, ABC Diatype), une seule
     ligne change. IBM Plex Mono sert les cartels — capitales étroites, chiffres
     de largeur fixe, dessin sobre. */
  --sans: "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* — Corps. Les sauts sont larges : c'est là que se joue la hiérarchie. — */
  --fs-display:  clamp(44px, 9.4vw, 148px);  /* nom d'une Machine, wordmark */
  --fs-h1:       clamp(33px, 5.2vw, 74px);
  --fs-h2:       clamp(25px, 2.9vw, 40px);
  --fs-h3:       clamp(17px, 1.3vw, 20px);
  --fs-lead:     clamp(19px, 1.7vw, 26px);
  --fs-body:     17px;
  --fs-small:    14px;
  --fs-cartel:   11px;
  --fs-cartel-s: 10px;

  /* — Espacements. Une seule échelle, pour tout le site. — */
  --s1: 8px;  --s2: 16px; --s3: 24px; --s4: 40px;
  --s5: 64px; --s6: 96px; --s7: 136px; --s8: 184px;

  /* Le silence entre deux sections. C'est la mesure la plus structurante
     de la feuille : c'est elle qui fait la salle plutôt que la brochure. */
  --block-y: clamp(64px, 10.5vh, 152px);

  --header-h: 62px;
  /* Le site occupe toute la largeur de l'écran : plus de colonne centrée, plus
     de marge morte sur les grands moniteurs. Ne restent que les 100 px de
     retrait qui tiennent le contenu à distance du bord. */
  --gutter: clamp(20px, 7vw, 100px);
  --maxw: 100%;
  --col: 12;
  --gap: clamp(16px, 2.2vw, 36px);

  /* Le cartel, en une déclaration réutilisable. */
  --cartel: 500 var(--fs-cartel)/1.5 var(--mono);
  --cartel-ls: .14em;
}

@media (min-width: 900px) { :root { --header-h: 76px; } }

/* --- 3. Grille ---------------------------------------------------------- */

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

.grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--gap);
}

@media (min-width: 700px)  { .grid { grid-template-columns: repeat(8, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .grid { grid-template-columns: repeat(12, minmax(0, 1fr)); } }

[class*="c-"] { grid-column: 1 / -1; }

@media (min-width: 1000px) {
  .c-3  { grid-column: span 3; }
  .c-4  { grid-column: span 4; }
  .c-5  { grid-column: span 5; }
  .c-6  { grid-column: span 6; }
  .c-7  { grid-column: span 7; }
  .c-8  { grid-column: span 8; }
  .c-9  { grid-column: span 9; }
  .c-12 { grid-column: span 12; }
  /* Un seul bord gauche pour tout le site : titres, fils d'Ariane, vignettes,
     pied de page. Un mur d'accrochage n'a qu'une ligne de départ, et le moindre
     retrait se voit dès qu'il n'y a plus ni couleur ni cadre pour le masquer.
     Ce qui varie, c'est la largeur du bloc, jamais son point de départ. */
  .c-off1 { grid-column: 1 / span 10; }
  .c-text { grid-column: 1 / span 6; }
  .c-inset { grid-column: 1 / span 10; }
}

@media (min-width: 700px) and (max-width: 999px) {
  .c-3, .c-4 { grid-column: span 4; }
  .c-5, .c-6, .c-7 { grid-column: span 8; }
}

/* Calque de contrôle de la grille (touche G) — outil interne. */
.gridlayer { position: fixed; inset: 0; z-index: 900; pointer-events: none; display: none; }
body.show-grid .gridlayer { display: block; }
.gridlayer .shell, .gridlayer .grid { height: 100%; }
.gridlayer .grid > i { background: rgba(0, 0, 0, .04); border-left: 1px solid rgba(0,0,0,.10); border-right: 1px solid rgba(0,0,0,.10); }
@media (max-width: 999px) { .gridlayer .grid > i:nth-child(n+9) { display: none; } }
@media (max-width: 699px)  { .gridlayer .grid > i:nth-child(n+5) { display: none; } }

/* --- 4. Rythme vertical ------------------------------------------------- */

/* Aucun filet entre les sections : elles se séparent par le vide. Un trait
   horizontal pleine largeur découperait le mur, et le mur doit rester d'un
   seul tenant. */
/* `--block-pad` est la part de silence que chaque bloc porte. Toutes les
   variantes s'expriment en fraction de cette part, jamais de `--block-y` :
   un seul réglage déplace le rythme de la page entière, et les rapports entre
   un bloc ordinaire, un bloc qui respire et un bloc resserré ne bougent pas.
   Sur un écran étroit la part tombe à 40 % : le vide qui fait la salle sur un
   grand moniteur ne fait qu'un long défilement sur un téléphone. */
:root { --block-pad: calc(var(--block-y) * 1); }
@media (max-width: 999px) { :root { --block-pad: calc(var(--block-y) * .4); } }

.block { padding-block: var(--block-pad); position: relative; }
.block--tight { padding-block: calc(var(--block-pad) * .56); }
.block--flush { padding-block: 0; }

/* « alt » ne peint plus un fond : il respire davantage. L'alternance se lit
   dans l'espace, pas dans la valeur. */
.block--alt { background: transparent; padding-block: calc(var(--block-pad) * 1.44); }

/* Étiquette de bloc — repère de recette. Réduite à sa plus simple expression :
   du gris clair dans la marge, que l'œil apprend à ne plus voir. Touche N pour
   la faire disparaître tout à fait. */
.block__tag {
  position: absolute; top: var(--s2); left: 0;
  padding-inline: var(--gutter);
  font: 500 9px/1 var(--mono);
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--tag); background: transparent;
  z-index: 5; pointer-events: none;
}
body.hide-notes .block__tag { display: none; }
/* Sur une page à hero, la première étiquette passerait sous l'en-tête. */
.hero .block__tag { top: calc(var(--header-h) + var(--s2)); }

/* --- 5. Typographie ----------------------------------------------------- */

/* Le cartel. Petites capitales étroites, très interlettrées : la ligne qu'on
   lit à côté de l'œuvre, pas dans l'œuvre. */
.eyebrow {
  font: var(--cartel);
  letter-spacing: var(--cartel-ls); text-transform: uppercase;
  color: var(--ink-faint); margin: 0 0 var(--s3);
}

h1, .h1 { font-size: var(--fs-h1); line-height: 1.0; letter-spacing: -.035em; }
h2, .h2 { font-size: var(--fs-h2); line-height: 1.1; letter-spacing: -.028em; }
h3, .h3 { font-size: var(--fs-h3); line-height: 1.35; letter-spacing: -.012em; }

/* Le chapô n'est pas du texte secondaire : c'est la voix de la Maison.
   Il est grand, et il est noir. */
.lead {
  font-size: var(--fs-lead); line-height: 1.42; letter-spacing: -.014em;
  color: var(--ink); max-width: 34ch;
}
.lead + .lead, .lead p + p { margin-top: .8em; }

.small { font-size: var(--fs-small); line-height: 1.6; color: var(--ink-soft); }

.prose { max-width: 62ch; }

/* Le format manifeste. Chapô de page et blocs d'ouverture — « Notre vision »,
   les introductions des pages véhicule. Centré, sur 70 % de la grille.
   C'est le seul endroit du site qui échappe au bord gauche unique, et c'est
   voulu : il ne s'agit plus de lire une page, mais d'entendre une phrase.
   Le corps y grandit avec la colonne — à 26 px sur 70 % de large, la ligne
   dépasserait 85 signes, ce qu'aucun texte centré ne supporte. */
.c-lead {
  grid-column: 1 / -1;
  width: 70%;
  /* Depuis que la page occupe toute la largeur, 70 % d'un écran de 2560 px
     feraient des lignes de 140 signes. Le plafond garde la mesure lisible ;
     c'est lui qui agit sur les grands moniteurs, les 70 % en deçà. */
  max-width: 1040px;
  margin-inline: auto;
  text-align: center;
}
/* `.prose` plafonne à 62ch, ce qui vise une colonne de texte courant à 17 px —
   sans rapport avec le format manifeste. On lui substitue le plafond de la
   colonne, sans quoi cette règle, plus spécifique, l'annulait purement et
   simplement : le plafond posé plus haut n'a jamais agi. */
.c-lead.prose { max-width: 1040px; }
.c-lead .lead {
  max-width: none; margin-inline: auto;
  /* 2,5 vw : 36 px sur un écran de 1440, 48 px sur un 1920. Les bornes tiennent
     les extrêmes — en deçà de 880 px la ligne deviendrait illisible, au-delà de
     1680 px elle dépasserait la mesure que le plafond de 1040 px autorise. */
  font-size: clamp(22px, 2.5vw, 42px);
  line-height: 1.3;
  letter-spacing: -.018em;
}
/* Un corps composé comme le titre : deux lignes du même poids, sans marche
   entre elles. Le plafond du titre s'applique aussi — au-delà, la ligne
   dépasserait la mesure que la colonne autorise. */
/* Un titre qui prend sa propre rangée, à l'échelle du display, sur la mesure
   exacte du texte qui le suit. C'est le corps qui le détache, pas la largeur :
   la colonne reste la même, du titre au dernier paragraphe. */
.h2--display {
  font-size: var(--fs-h1);
  line-height: 1.02;
  letter-spacing: -.035em;
  text-align: center;
  margin: 0;
}
/* Le silence sous le titre. Le sélecteur porte la colonne parce que
   `.c-lead h2` y impose déjà un `margin-bottom: .35em` — plus spécifique que la
   classe seule, il ramenait ce silence à 26 px sans qu'on le voie. */
.c-lead .h2--display { margin-bottom: var(--s5); }
/* Même bascule que le format manifeste : en dessous de 1000 px, tout revient
   au bord gauche unique du site. */
@media (max-width: 999px) { .h2--display { text-align: left; } }

.c-lead .lead--h2 {
  font-size: var(--fs-h2);
  line-height: 1.1;
  letter-spacing: -.028em;
}
.c-lead .ctas { justify-content: center; }
.c-lead h1, .c-lead h2 { margin-bottom: .35em; }
@media (max-width: 999px) { .c-lead { width: 100%; text-align: left; } }

/* Le format manifeste respire plus large que le reste. Le silence autour de
   lui n'est pas une marge : c'est ce qui le detache du flux de lecture et
   lui donne le temps d'etre entendu. Porte par la rangee, et non par la
   section -- une section peut contenir un manifeste ET autre chose, comme ici
   un diaporama et les portraits des fondateurs, qui gardent leur rythme. */
/* Le silence est dissymetrique, et c'est voulu : large au-dessus, pour
   detacher le manifeste de ce qui precede, plus court en dessous, parce que
   l'image qui suit appartient au meme propos. Un meme ecart des deux cotes
   faisait flotter le diaporama comme un bloc etranger. */
.grid--manifeste { margin-block: var(--s7) var(--s5); }
@media (max-width: 999px) { .grid--manifeste { margin-block: var(--s6) var(--s4); } }

/* Sous WordPress, le manifeste et le diaporama qui le prolonge sont deux
   BLOCS, donc deux sections -- la maquette, elle, les tient dans une seule.
   Les deux remplissages de section s'ajoutaient alors au silence voulu, et
   l'ecart montait a plus de deux cents pixels la ou la maquette en montre
   soixante-quatre. On annule ces deux remplissages, et l'ecart redevient
   celui que la marge du manifeste annonce, le meme des deux cotes du site.

   Le couple est nomme explicitement : une section de manifeste SUIVIE d'une
   section qui porte un diaporama. Ailleurs, les silences de section restent
   intacts. */
.block:has(> .shell > .grid--manifeste) { padding-bottom: 0; }
.block:has(> .shell > .grid--manifeste) + .block:has(.slideshow) { padding-top: 0; }
.prose > * + h3 { margin-top: 2.2em; }
.prose h3 + p { margin-top: .6em; }

.prose > * + h3 { margin-top: 2.2em; }
.prose h3 + p { margin-top: .6em; }

/* Chiffres alignés partout où l'on compare des valeurs. */
.specs, .card__meta, .cal-grid, .slots, .crumb { font-variant-numeric: tabular-nums; }

/* --- 6. Header ---------------------------------------------------------- */

/* L'en-tête n'a plus de fond, à aucun moment du défilement : il flotte au-dessus
   de la page et laisse passer ce qui glisse dessous.

   Pour rester lisible, la navigation s'inverse sur ce qu'elle survole. Le
   procédé n'est pas `filter: invert()` — un filtre n'inverse que les pixels de
   l'élément lui-même, pas ceux du fond : sur une page blanche, une navigation
   noire filtrée deviendrait blanche, donc invisible. C'est `mix-blend-mode:
   difference` qui fait le travail, parce qu'il compose avec l'arrière-plan :
   du blanc posé sur du blanc donne du noir, du blanc posé sur une photo sombre
   reste blanc. La navigation est donc écrite en blanc et lue en noir tant que
   le mur l'est. */
/* V4 — la fusion `difference` est abandonnée. Elle convenait à un mur gris
   uniforme : elle rendait l'en-tête lisible sans qu'on ait à décider de sa
   couleur. Sur une vidéo, elle compose avec chaque pixel et rend des teintes
   sales et mouvantes. L'en-tête a donc désormais deux états francs, et le
   script dit lequel s'applique : blanc tant qu'un pixel de l'en-tête est encore
   sur le média d'accroche, noir dès qu'il l'a quitté. */
.header {
  position: fixed; top: 0; left: 0; right: 0; z-index: 800;
  height: var(--header-h);
  display: flex; align-items: center;
  background: transparent;
  border-bottom: 0;
  color: var(--ink);
  transition: color .25s ease;
}

/* Sur le média, et pendant une transition où une image occupe l'écran. */
.header.is-over-media,
body.is-leaving .header,
html.mr-from .header { color: var(--paper); }

/* Une section sombre glisse sous l'en-tête : il doit s'y lire aussi. Etat
   distinct de `is-over-media`, et sans le voile — sur un aplat noir, un
   dégradé noir ne rattraperait rien et ne ferait qu'ajouter une couche. */
.header.is-sur-sombre { color: var(--paper); }

/* Un voile très court sous le bord haut. Une photographie claire, un ciel, une
   carrosserie blanche : le blanc pur ne tient pas tout seul. Le dégradé ne se
   lit pas comme un bandeau, il rattrape juste le contraste. */
.header.is-over-media::before {
  content: '';
  position: absolute; inset: 0 0 auto 0; z-index: -1;
  height: calc(var(--header-h) * 2);
  background: linear-gradient(to bottom, rgba(0, 0, 0, .38), rgba(0, 0, 0, 0));
  pointer-events: none;
}
.header__inner { display: flex; align-items: center; gap: var(--s3); width: 100%; }
/* La classe reste posée par le script — d'autres comportements s'y accrochent —
   mais elle ne peint plus rien. */
.header.is-solid { background: transparent; border-bottom-color: transparent; }

/* L'en-tête parle la langue du contenu, pas celle des cartels : le mono est
   réservé aux étiquettes posées à côté des œuvres. Le nom de la Maison et les
   rubriques sont de la grotesque, en capitales largement interlettrées. */
.logo {
  display: inline-flex; align-items: center;
  font: 600 15px/1 var(--sans); letter-spacing: .17em; text-transform: uppercase;
  text-decoration: none; white-space: nowrap;
}

/* Le monogramme, à gauche du nom. Tracé en `currentColor`, donc dans la couleur
   de la navigation — et, avec elle, dans son mode de fusion. Il s'inverse
   exactement comme le texte, sans traitement propre. 25 px de haut : la lettre
   capitale du nom en fait 11, le monogramme la surplombe sans l'écraser. */
.logo__mono {
  display: block; flex: 0 0 auto;
  height: 25px; width: auto;
  margin-right: 12px;
}

/* Le monogramme est là en permanence, à 25 px, y compris quand le nom occupe
   toute la largeur — mais il ne partage pas sa ligne. Tant que le nom est
   déployé, la marque tient le coin supérieur gauche, sur la rangée du menu,
   là où vit le petit logo ; le nom garde toute la largeur en dessous. Les deux
   se rejoignent quand l'en-tête se referme.

   Il sort donc du flux : sa position verticale est interpolée par le script,
   d'un bout à l'autre du parcours, et le nom se décale d'autant à mesure que
   la place se libère à sa gauche. */
.page-home.wordmark .logo__mono {
  position: absolute; left: 0; top: var(--wm-mono-top, 0px);
  margin-right: 0;
}
.page-home.wordmark .logo__mark { margin-left: var(--wm-mark-x, 0px); }
/* La baseline sous le logo a été retirée en V4 : le mot-symbole se suffit.
   (La règle qui la grisait attrapait le mot lui-même — piège du sélecteur
   d'élément quand le contenu change sous lui.) */

/* Le menu est écrit dans la même langue que les cartels : capitales étroites,
   minuscules de corps. Il annonce, il ne réclame pas. */
.nav { margin-left: auto; min-width: 0; }
.nav ul {
  display: flex; align-items: center; gap: var(--s4);
  list-style: none; margin: 0; padding: 0; flex-wrap: nowrap;
}
.nav li { white-space: nowrap; }
.nav a {
  text-decoration: none;
  font: 600 13px/1 var(--sans);
  letter-spacing: .1em; text-transform: uppercase;
  padding: 8px 0; display: inline-block;
  border-bottom: 1px solid transparent;
}
.nav a:hover, .nav a:focus-visible { border-bottom-color: currentColor; }
.nav a[aria-current="page"] { border-bottom-color: currentColor; }

.lang { display: flex; gap: 5px; align-items: center; font: 600 12px/1 var(--sans); letter-spacing: .1em; }
.nav__lang { margin-left: 4px; padding-left: var(--s4); border-left: 1px solid currentColor; }
.nav__lang a { opacity: .4; padding: 4px 2px; border-bottom: 0; font: inherit; letter-spacing: .12em; }
.nav__lang a:hover { opacity: .8; border-bottom: 0; }
.nav__lang a[aria-current="true"] { opacity: 1; }
.nav__lang span { opacity: .25; }

@media (min-width: 900px) and (max-width: 1180px) {
  .nav ul { gap: var(--s3); }
  .nav__lang { padding-left: var(--s3); }
}

.burger { display: none; }

@media (max-width: 899px) {
  .burger {
    display: inline-flex; align-items: center; gap: 8px;
    margin-left: auto; background: none; border: 1px solid currentColor;
    padding: 9px 13px; cursor: pointer;
    font: 600 12px/1 var(--sans);
    letter-spacing: .12em; text-transform: uppercase;
  }
  /* Déroulé, le menu est un panneau plein : il quitte le mode de fusion,
     sans quoi son fond blanc s'inverserait avec la page. */
  .nav {
    position: fixed; inset: var(--header-h) 0 0 0;
    background: var(--paper); color: var(--ink);
    padding: var(--s4) var(--gutter); margin: 0;
    display: none; overflow-y: auto;
    mix-blend-mode: normal; isolation: isolate;
  }
  .nav.is-open { display: block; }
  .nav ul { flex-direction: column; align-items: stretch; gap: 0; }
  .nav li { border-bottom: 1px solid var(--line); }
  /* Déroulé, le menu quitte le mono : ce ne sont plus des cartels, ce sont
     des portes. */
  .nav a {
    font: 400 clamp(26px, 7vw, 40px)/1.2 var(--sans);
    letter-spacing: -.03em; text-transform: none;
    padding: var(--s3) 0; width: 100%; border: 0;
  }
  .nav__lang {
    border-left: 0; border-bottom: 0; margin: var(--s4) 0 0; padding: 0;
  }
  .nav__lang a { font: 600 13px/1 var(--sans); letter-spacing: .1em; padding: 8px 4px; width: auto; }
}

/* --- 6 bis. Le mot-symbole animé (accueil seulement) -------------------- */

/* Au chargement, le nom de la Maison occupe toute la largeur de la page ; il se
   réduit au défilement jusqu'à reprendre sa place ordinaire dans l'en-tête.

   Trois valeurs interpolées par le script — hauteur d'en-tête, corps du mot,
   interlettrage — et rien d'autre : pas de transform, pas d'opacité, pas de
   translation. Le mot ne se déplace pas, il rétrécit ; c'est ce qui le rend
   crédible plutôt que décoratif.

   Le corps maximal est mesuré, pas deviné : le script ajuste le mot à la
   largeur disponible, quelle que soit la fenêtre. L'interlettrage est exprimé
   en em, il suit donc le corps sans qu'on ait à l'interpoler séparément.

   Sans script, ou si le visiteur a demandé moins d'animations, la classe
   `wordmark` n'est jamais posée : l'en-tête reste celui de toutes les pages. */

.logo__mark { display: block; white-space: nowrap; }

/* --header-h est la mesure dont dépendent le décalage du hero, la position des
   étiquettes et le menu déroulé : elle doit suivre le mot-symbole, sinon la
   page se cale sur un en-tête qui n'existe plus. */
.page-home.wordmark { --header-h: var(--wm-h, 76px); }
.page-home.wordmark .header {
  height: var(--wm-h, 76px);
  align-items: stretch;
}
.page-home.wordmark .header__inner {
  position: relative;
  align-items: flex-start;
  padding-top: 24px;
}
.page-home.wordmark .logo {
  position: absolute; left: var(--gutter); bottom: var(--wm-bottom, 26px);
  letter-spacing: 0;
}
.page-home.wordmark .logo__mark {
  font-size: var(--wm-fs, 15px);
  letter-spacing: var(--wm-ls, .17em);
  line-height: .9;
}
/* L'en-tête devient opaque au scroll comme partout ailleurs ; le mot-symbole,
   lui, ne change pas de couleur : il est noir sur blanc du début à la fin. */
.page-home.wordmark .header:not(.is-solid) { background: transparent; }

@media (max-width: 899px) {
  /* Le burger occupe la rangée du haut ; le mot garde la rangée du bas. */
  .page-home.wordmark .header__inner { padding-top: 18px; }
}

/* --- 7. Placeholders média --------------------------------------------- */

/* Un cadre vide sur un mur blanc. Pas de croix de diagonales, pas de trait
   pointillé : la maquette doit déjà ressembler au site, et le jour où la
   photographie arrive, rien ne bouge autour d'elle. */
.ph {
  position: relative;
  background: var(--ph);
  border: 0;
  display: flex; align-items: flex-end; justify-content: flex-start;
  text-align: left; padding: var(--s2);
  overflow: hidden;
  color: var(--ph-ink);
}
.ph__label {
  position: relative; z-index: 2;
  font: 500 var(--fs-cartel-s)/1.6 var(--mono);
  letter-spacing: .12em; text-transform: uppercase;
  background: transparent; padding: 0; border: 0;
  max-width: 46ch; color: var(--ph-ink);
}
.ph__label b { display: block; font-weight: 500; }
.ph__label em {
  display: block; font-style: normal; font-weight: 400;
  text-transform: none; letter-spacing: 0;
  font-size: 10px; margin-top: 5px; opacity: .8;
}

.r-21x9  { aspect-ratio: 21 / 9; }
.r-16x9  { aspect-ratio: 16 / 9; }
.r-3x2   { aspect-ratio: 3 / 2; }
.r-4x3   { aspect-ratio: 4 / 3; }
.r-1x1   { aspect-ratio: 1 / 1; }
.r-4x5   { aspect-ratio: 4 / 5; }
/* Le hero occupe la totalité de l'écran. `svh` plutôt que `vh` : sur mobile,
   `vh` compte la barre d'adresse escamotable et déborde de l'écran réel. */
.r-hero  { min-height: 100vh; min-height: 100svh; }
@media (max-width: 699px) { .r-21x9 { aspect-ratio: 4 / 3; } }

/* La légende d'une image est un cartel, pas une note de bas de page. */
.figure figcaption {
  font: 500 var(--fs-cartel-s)/1.6 var(--mono);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-faint); margin-top: var(--s2);
  padding-left: 0; border-left: 0;
}

/* --- 8. Hero ------------------------------------------------------------ */

/* Seule exception à l'accrochage : le hero occupe l'écran bord à bord. */
body.has-hero { padding-top: 0; }

.hero { position: relative; }
/* Le hero est le seul média bord à bord du site. Sa mention de production se
   range en haut à droite : l'angle inférieur gauche appartient au titre. */
.hero .ph {
  border: 0;
  padding: calc(var(--header-h) + var(--s5)) var(--gutter) var(--s3);
  align-items: flex-start; justify-content: flex-end; text-align: right;
}
.hero .ph__label { max-width: 34ch; }
.hero__content {
  position: absolute; inset: auto 0 0 0; z-index: 3;
  padding-bottom: clamp(var(--s4), 7vh, var(--s6));
}
.hero__content .inner { max-width: 60ch; }
.hero h1 { margin-bottom: .25em; max-width: 14ch; letter-spacing: -.04em; }
.hero .sub {
  font-size: clamp(16px, 1.5vw, 20px); line-height: 1.45;
  color: var(--ink-soft); margin-bottom: var(--s4); max-width: 40ch;
}
.hero__anchor {
  display: inline-flex; align-items: center; gap: 10px; margin-top: var(--s3);
  font: var(--cartel); letter-spacing: var(--cartel-ls); text-transform: uppercase;
  text-decoration: none; border-bottom: 1px solid currentColor; padding-bottom: 7px;
}

/* --- 9. Boutons / CTA -------------------------------------------------- */

.ctas { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); }
.ctas--equal .btn { flex: 1 1 260px; justify-content: center; }
/* L'ancre « Découvrir » peut entrer dans la rangée : elle perd alors le retrait
   qui la décrochait du bouton, et se cale sur son axe. */
.ctas .hero__anchor { margin-top: 0; margin-left: var(--s3); }

/* Un rectangle noir. Rien de plus, et surtout aucun angle. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 12px;
  min-height: 56px; padding: 16px var(--s4);
  font: 500 var(--fs-cartel)/1.2 var(--mono);
  letter-spacing: var(--cartel-ls); text-transform: uppercase;
  text-decoration: none; text-align: center; cursor: pointer;
  border: 1px solid var(--ink); background: var(--ink); color: var(--paper);
  transition: background-color .2s ease, color .2s ease;
}
.btn:hover { background: var(--paper); color: var(--ink); }
.btn--ghost { background: transparent; color: var(--ink); }
.btn--ghost:hover { background: var(--ink); color: var(--paper); }
.btn--wide { width: 100%; }
.btn:focus-visible, a:focus-visible, button:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--ink); outline-offset: 3px;
}

/* Un lien de découverte n'est pas un bouton : c'est un mot souligné d'un
   filet, qui se remplit au survol. */
.link {
  display: inline-flex; align-items: center; gap: 10px;
  font: var(--cartel); letter-spacing: var(--cartel-ls); text-transform: uppercase;
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong); padding-bottom: 6px;
  transition: border-color .2s ease;
}
.link:hover { border-bottom-color: var(--ink); }
/* Un lien peut être un bouton : quand l'action se joue dans la page et non par
   une adresse, c'est `button` qu'il faut écrire. Le navigateur lui donne alors
   un fond et un cadre dont il faut le débarrasser pour qu'il redevienne le
   lien qu'il est à l'œil. */
button.link {
  -webkit-appearance: none; appearance: none;
  background: transparent; border: 0;
  border-bottom: 1px solid var(--line-strong);
  padding: 0 0 6px; color: inherit; cursor: pointer;
}

/* --- 10. Cartes -------------------------------------------------------- */

/* La carte perd son cadre. Une œuvre accrochée n'a pas de boîte autour d'elle :
   il y a l'image, puis le cartel en dessous. C'est ce seul retrait qui fait
   basculer la page du catalogue vers la galerie. */
.cards { display: grid; gap: var(--s6) var(--gap); grid-template-columns: 1fr; }
@media (min-width: 800px)  { .cards--2 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 800px)  { .cards--3 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1100px) { .cards--3 { grid-template-columns: repeat(3, 1fr); } }

.card {
  display: flex; flex-direction: column;
  background: transparent; border: 0;
  position: relative;
}
.card .ph { transition: opacity .3s ease; }
.card:hover .ph { opacity: .82; }
.card__body {
  padding: var(--s3) 0 0; display: flex; flex-direction: column; flex: 1;
  gap: var(--s2);
}
.card__body > * { margin: 0; }
.card__body .btn, .card__body .link { margin-top: var(--s3); align-self: flex-start; }
.card h3 { font-size: clamp(20px, 1.7vw, 25px); letter-spacing: -.025em; }
.card p { color: var(--ink-soft); font-size: var(--fs-small); line-height: 1.6; max-width: 42ch; }

/* Le cartel : des lignes clé-valeur en capitales étroites, sans filet, sans
   fond. Elles se lisent comme une étiquette de musée. */
.card__meta {
  list-style: none; margin: 0; padding: 0;
  font: 400 var(--fs-cartel-s)/1.9 var(--mono);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-soft);
}
.card__meta li { display: flex; gap: var(--s2); border: 0; padding: 0; }
.card__meta b { font-weight: 400; color: var(--ink-faint); min-width: 10em; }

.badge {
  display: inline-block; align-self: flex-start;
  font: 500 var(--fs-cartel-s)/1 var(--mono);
  letter-spacing: var(--cartel-ls); text-transform: uppercase;
  border: 1px solid var(--ink); padding: 7px 10px;
}
.badge--soft { border-color: var(--line-strong); color: var(--ink-soft); }
.badge--float {
  position: absolute; top: var(--s2); left: var(--s2); z-index: 4;
  background: var(--paper);
}
/* Sur un hero, le statut se range sous l'en-tête et sur la marge de la page. */
.hero .badge--float { top: calc(var(--header-h) + var(--s5)); left: var(--gutter); }

/* Un texte flanqué d'une image. Deux colonnes, l'image d'un côté ou de
   l'autre, et un axe médian commun : le texte ne s'aligne pas sur le haut de
   l'image, il se centre avec elle. L'image reste dans le blanc, jamais bord à
   bord — c'est l'accrochage, pas le plein cadre. */
.split { align-items: center; row-gap: var(--s5); }
/* Sous la bascule, l'image n'a plus de colonne assignée : sans cette ligne
   elle retombe sur une seule piste de la grille et se réduit au quart de la
   largeur. Elle passe pleine mesure, sous le texte. */
.split .split__media { grid-column: 1 / -1; align-self: center; }
@media (min-width: 1000px) {
  .split .split__media { grid-column: 8 / span 5; }
  /* L'image passe à gauche sans que le texte quitte la première place dans le
     document : la grille les échange, l'ordre de lecture ne bouge pas. */
  .split--left .split__media { grid-column: 1 / span 5; grid-row: 1; }
  .split--left .c-text { grid-column: 7 / span 6; grid-row: 1; }
}

/* --- 11 bis. Deux colonnes, tête fixe ---------------------------------- */

/* Le titre et le chapô tiennent la colonne de gauche pendant que la liste
   défile à droite. Le propos reste sous les yeux tout le temps qu'on parcourt
   ce qu'il énonce — c'est ce qui distingue une méthode d'une énumération. */
.duo { display: grid; gap: var(--s5) var(--gap); }
@media (min-width: 1000px) {
  .duo { grid-template-columns: repeat(12, minmax(0, 1fr)); align-items: start; }
  .duo__head {
    grid-column: 1 / span 5;
    position: sticky;
    top: calc(var(--header-h) + var(--s5));
  }
  .duo__list { grid-column: 7 / span 6; }
}
/* Les étapes sont largement espacées : sans cette longueur, la colonne de
   gauche n'aurait pas le temps de tenir et l'effet passerait inaperçu. */
/* Une méthode est ordonnée : ses étapes portent un numéro, en cartel au-dessus
   du titre — jamais dans le titre, qui doit rester lisible seul. */
.duo__list--num { counter-reset: d; }
.duo__list--num .duo__item { counter-increment: d; }
.duo__list--num .duo__item h3::before {
  content: counter(d, decimal-leading-zero);
  display: block; margin-bottom: .7em;
  font: 400 var(--fs-cartel)/1 var(--mono);
  letter-spacing: .1em; color: var(--ink-faint);
}
.duo__item + .duo__item { margin-top: var(--s7); }
.duo__item h3 { margin-bottom: .5em; }
.duo__item p { color: var(--ink-soft); }
.duo__outro { margin-top: var(--s7); padding-top: var(--s4); border-top: 1px solid var(--line); }

/* --- 10 ter. Deux blocs en vis-à-vis ----------------------------------- */

/* Deux gabarits complets sur une même rangée : le déroulé d'un côté, la fiche
   de l'autre. On les lit ensemble plutôt que l'un après l'autre. */
.pair { display: grid; gap: var(--s6) var(--gap); }
@media (min-width: 1000px) {
  .pair { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }
}
/* Dans une demi-largeur, la grille de douze colonnes n'a plus de sens : une
   colonne nommée y tiendrait le quart de l'écran. Chaque cellule redevient un
   bloc simple, et ce qu'elle contient l'occupe entièrement. */
.pair__cell .grid { display: block; }
.pair__cell .prose { max-width: none; }

/* --- 11. Étapes / listes ---------------------------------------------- */

.steps { list-style: none; margin: 0; padding: 0; counter-reset: s; display: grid; gap: 0; }
.steps > li {
  counter-increment: s;
  display: grid; gap: var(--s1) var(--s4); padding: var(--s4) 0;
  border-top: 1px solid var(--line);
}
.steps > li:last-child { border-bottom: 1px solid var(--line); }
.steps > li::before {
  content: counter(s, decimal-leading-zero);
  font: 400 var(--fs-cartel)/1 var(--mono); letter-spacing: .1em; color: var(--ink-faint);
}
@media (min-width: 800px) {
  .steps > li { grid-template-columns: 5em 1fr 2fr; align-items: start; }
  .steps > li::before { padding-top: 8px; }
}
.steps h3 { margin: 0; }
.steps p { margin: 0 0 .8em; color: var(--ink-soft); }

/* Variante compacte : le déroulé se range sur plusieurs colonnes, le numéro
   revient contre son intitulé. Les règles arrivent après celles du pas à pas
   normal — à spécificité égale, c'est l'ordre qui tranche, y compris sur la
   grille à trois pistes déclarée plus haut. */
.steps--compact > li {
  grid-template-columns: 2.6em 1fr;
  gap: var(--s1) var(--s2);
  align-items: baseline;
  padding: var(--s2) 0;
}
.steps--compact > li::before { padding-top: 0; }
.steps--compact > li:last-child { border-bottom: 0; }
@media (min-width: 700px) {
  .steps--compact { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s5); }
}
@media (min-width: 1000px) {
  .steps--compact { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.rubriques {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 0; background: transparent; border: 0;
  border-top: 1px solid var(--line);
}
.rubriques li {
  background: transparent; padding: var(--s3) 0; font-weight: 400;
  border-bottom: 1px solid var(--line);
}
@media (min-width: 700px) { .rubriques { grid-template-columns: repeat(2, 1fr); column-gap: var(--s5); } }
@media (min-width: 1100px) { .rubriques { grid-template-columns: repeat(3, 1fr); } }

/* Deux colonnes de texte, sans boîte : un filet en tête, et de l'air. */
.cols2 { display: grid; gap: var(--s5); }
@media (min-width: 800px) { .cols2 { grid-template-columns: repeat(2, 1fr); } }
.cols2 > div {
  background: transparent; border: 0; border-top: 1px solid var(--ink);
  padding: var(--s3) 0 0;
}
.cols2 h3 { font-size: var(--fs-h3); }
.cols2 p { color: var(--ink-soft); }

/* --- 12. Fiche technique ---------------------------------------------- */

/* Le tableau devient une suite de lignes séparées par un filet. Aucun fond,
   aucune bordure extérieure : le cartel s'étend, il ne s'encadre pas. */
.specs {
  margin: 0; display: grid; gap: 0; background: transparent;
  border: 0; border-top: 1px solid var(--line);
  font-size: var(--fs-small);
}
.specs > div {
  background: transparent; display: grid; grid-template-columns: 1fr; gap: 4px;
  padding: var(--s2) 0; border-bottom: 1px solid var(--line);
}
@media (min-width: 620px) {
  .specs > div { grid-template-columns: 18em 1fr; gap: var(--s3); align-items: baseline; }
}
.specs dt {
  font: 400 var(--fs-cartel-s)/1.8 var(--mono);
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-faint);
}
.specs dd { margin: 0; }

/* Sur une demi-page, la colonne de libellés se resserre et ne s'ouvre qu'une
   fois la grille passée à douze pistes : en dessous, la moitié du conteneur est
   trop étroite pour deux colonnes, et le libellé repasse au-dessus de sa
   valeur. */
.specs--half > div { grid-template-columns: 1fr; }
@media (min-width: 1000px) {
  .specs--half > div { grid-template-columns: minmax(0, 14em) 1fr; gap: var(--s3); }
}

/* La fiche complète : une pile de groupes repliés sur toute la largeur du
   contenu, chacun annoncé par son intitulé au cartel. Fermée, elle tient en une
   douzaine de lignes ; ouverte groupe par groupe, elle rend la colonne de 18em
   du cartel, qui a la place de tenir puisque rien ne partage plus la largeur.
   Aucun filet en tête : le titre de section marque déjà l'entrée, et un second
   trait juste dessous ferait doublon. Le repli est un `details` natif : pas de
   script, et le contenu reste dans le document. */
.acc { border-bottom: 1px solid var(--line); }
.acc__sum {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3); padding: var(--s3) 0;
  font: 500 var(--fs-cartel-s)/1.6 var(--mono);
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink);
}
.acc__sum::-webkit-details-marker { display: none; }
.acc__sum:hover { color: var(--ink-soft); }
.acc__sum:focus-visible { outline: 1px solid var(--ink); outline-offset: 4px; }
/* Le signe se referme sur lui-même : la barre verticale bascule à
   l'horizontale, les deux se superposent, il ne reste qu'un moins. */
.acc__sign { position: relative; flex: none; width: 12px; height: 12px; }
.acc__sign::before, .acc__sign::after {
  content: ""; position: absolute; top: 50%; left: 0;
  width: 12px; height: 1px; background: currentColor;
}
.acc__sign::after { transform: rotate(90deg); transition: transform .25s ease; }
.acc[open] .acc__sign::after { transform: rotate(0deg); }
@media (prefers-reduced-motion: reduce) { .acc__sign::after { transition: none; } }
.acc__body { padding-bottom: var(--s3); }
.acc__body .specs { border-top: 0; }
.acc__body .specs > div:last-child { border-bottom: 0; }

/* --- 13. Galeries ------------------------------------------------------ */

/* Accrochage. Les images ne se touchent pas et ne touchent pas les bords :
   l'écart entre deux œuvres est plus grand que sur n'importe quelle grille de
   catalogue, et c'est ce qui fait la salle. */
.gallery { display: grid; gap: var(--s6) var(--gap); grid-template-columns: repeat(2, 1fr); }
@media (min-width: 900px) { .gallery { grid-template-columns: repeat(3, 1fr); } }
.gallery .ph:first-child { grid-column: 1 / -1; }

/* Une séquence se regarde une image à la fois, centrée, jamais pleine largeur. */
.gallery--seq { grid-template-columns: 1fr; gap: var(--s6); }
.gallery--seq .ph { max-width: 1080px; margin-inline: auto; width: 100%; }
.gallery-seq__head {
  font: var(--cartel); letter-spacing: var(--cartel-ls); text-transform: uppercase;
  color: var(--ink-faint); margin: var(--s6) 0 var(--s3);
}

/* --- 13 bis. Accrochage réglé ------------------------------------------ */

/* Six colonnes plutôt que trois : c'est le plus petit nombre qui se divise par
   un, par deux et par trois. Une image occupe donc la largeur entière du
   conteneur, la moitié ou le tiers — jamais rien d'autre. Une ligne ne peut
   plus se terminer sur une colonne vide : il suffit que la somme des largeurs
   d'une ligne fasse six. */
.hang {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  /* Un seul écart, et le même dans les deux sens. Sur un mur, la distance entre
     deux cadres ne change pas selon qu'on la mesure de haut en bas ou de gauche
     à droite ; la grille précédente ouvrait 96 px en vertical pour 36 px en
     horizontal, et la rangée du bas semblait avoir quitté celle du haut. */
  gap: var(--gap);
}

/* En dessous de 700 px, aucune division n'a de sens : le tiers d'un téléphone
   n'est plus une image, c'est une vignette. Tout passe pleine largeur, et
   chaque image forme à elle seule une ligne complète. */
.hang > * { grid-column: 1 / -1; }
@media (min-width: 700px) {
  .hang > .w-half  { grid-column: span 3; }
  .hang > .w-third { grid-column: span 2; }
}

/* Le texte qui précède un accrochage garde son propre retrait ; deux
   accrochages qui se suivent, en revanche, ne font qu'un seul mur. */
.grid + .hang { margin-top: var(--s5); }
.hang + .hang { margin-top: var(--gap); }

/* Sur une page de projet livré, les cartels de séquence rythment le mur sans le
   couper en quatre : le silence au-dessus du cartel reste supérieur à l'écart
   entre deux images, sans valoir la hauteur d'une rangée. */
.page-delivered .gallery-seq__head { margin: var(--s5) 0 var(--s3); }

/* --- 13 ter. Diaporama -------------------------------------------------- */

/* Plusieurs images à la même place. Les flèches se posent sur l'image, aux
   bords, et ne prennent rien à la page ; le compteur passe dessous, en cartel.
   Pas de pastilles : il n'y a pas de rondeur sur ce site, et un compteur dit
   mieux qu'une file de points où l'on en est. */
.slideshow { position: relative; }
/* Les flèches se calent sur l'image, pas sur le bloc : sans ce repère, le
   compteur les tirait sous le centre du cadre. */
.slideshow__viewport { position: relative; overflow: hidden; }
.slideshow__track { display: flex; transition: transform .45s cubic-bezier(.4, 0, .2, 1); }
@media (prefers-reduced-motion: reduce) { .slideshow__track { transition: none; } }
.slideshow__slide { flex: 0 0 100%; min-width: 0; }

.slideshow__nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 52px; height: 84px; padding: 0;
  display: grid; place-items: center;
  font: 400 28px/1 var(--mono); color: var(--ph-ink);
  background: transparent; border: 0; cursor: pointer;
  transition: color .15s, background-color .15s;
}
.slideshow__nav:hover { color: var(--ink); background: rgba(255, 255, 255, .6); }
.slideshow__nav--prev { left: 0; }
.slideshow__nav--next { right: 0; }
.slideshow:focus-visible { outline: 1px solid var(--ink); outline-offset: 4px; }

.slideshow__count {
  margin: var(--s2) 0 0; text-align: right;
  font: var(--cartel); letter-spacing: var(--cartel-ls); text-transform: uppercase;
  color: var(--ink-faint);
}

/* --- 14. News ---------------------------------------------------------- */

.posts { display: grid; gap: var(--s6) var(--gap); }
@media (min-width: 700px)  { .posts { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .posts { grid-template-columns: repeat(3, 1fr); } }
.post { display: flex; flex-direction: column; gap: var(--s2); }
.post__meta {
  font: 400 var(--fs-cartel-s)/1 var(--mono);
  letter-spacing: var(--cartel-ls); text-transform: uppercase;
  color: var(--ink-faint); display: flex; gap: var(--s2);
}
/* Dans une colonne au format manifeste, le cartel se centre comme le reste et
   respire avant le chapô : ce sont deux voix différentes, pas deux lignes du
   même paragraphe. */
.c-lead .post__meta { justify-content: center; flex-wrap: wrap; row-gap: var(--s1); }
.c-lead .post__meta + .lead { margin-top: var(--s4); }
.post h3 { font-size: clamp(19px, 1.5vw, 23px); margin: 0; letter-spacing: -.025em; }
.post p { margin: 0; color: var(--ink-soft); font-size: var(--fs-small); }
.post .link { margin-top: var(--s2); align-self: flex-start; }

/* Les actualités en rangées, sur le modèle de la liste d'étapes de Sur-mesure :
   un filet, trois colonnes, pas d'image. Sur l'accueil, le Journal s'annonce
   sans prendre la place d'une galerie — c'est le retrait de l'image, plus que
   tout le reste, qui lui rend sa juste importance. */
.newsrows { list-style: none; margin: var(--s5) 0 0; padding: 0; display: grid; gap: 0; }
.newsrows > li {
  display: grid; gap: var(--s2) var(--s4);
  padding: var(--s4) 0;
  border-top: 1px solid var(--line);
}
.newsrows > li:last-child { border-bottom: 1px solid var(--line); }
@media (min-width: 800px) {
  .newsrows > li { grid-template-columns: 11em 1fr 1.6fr; align-items: start; }
  /* La date sous la rubrique plutôt qu'à côté : la colonne est étroite, et les
     deux mentions se lisent mieux empilées qu'à l'étroit sur une ligne. */
  .newsrows .post__meta { flex-direction: column; gap: 6px; padding-top: 6px; }
}
.newsrows h3 { margin: 0; font-size: clamp(19px, 1.5vw, 23px); letter-spacing: -.025em; }
.newsrows p { margin: 0; color: var(--ink-soft); font-size: var(--fs-small); }
.newsrows .link { margin-top: var(--s3); }
.newsrows > li > div { display: flex; flex-direction: column; align-items: flex-start; }

.filters { display: flex; flex-wrap: wrap; gap: var(--s3); margin-bottom: var(--s5); }
.chip {
  font: 500 var(--fs-cartel-s)/1 var(--mono);
  letter-spacing: var(--cartel-ls); text-transform: uppercase;
  border: 0; border-bottom: 1px solid transparent;
  background: transparent; color: var(--ink-faint);
  padding: 6px 0; cursor: pointer;
}
.chip:hover { color: var(--ink); }
.chip[aria-pressed="true"] { background: transparent; color: var(--ink); border-bottom-color: var(--ink); }

.pager { display: flex; justify-content: center; margin-top: var(--s6); }

/* --- 15. Formulaires -------------------------------------------------- */

/* La colonne d'un formulaire : centrée sur la page, largeur de lecture d'un
   champ. Elle ne suit pas la grille de 12 colonnes — un formulaire n'est pas
   une colonne de contenu parmi d'autres, c'est la seule chose que l'écran
   demande à ce moment-là. */
.c-form {
  grid-column: 1 / -1;
  width: 100%;
  max-width: 680px;
  margin-inline: auto;
}
/* La colonne du formulaire est déjà une mesure de lecture : le chapô n'a pas à
   s'y restreindre une seconde fois. Sur 680 px, ses 34 signes laissaient un
   tiers de la colonne vide à droite du texte, au-dessus de champs qui, eux,
   allaient jusqu'au bord. */
.c-form .lead { max-width: none; }

/* Un formulaire de galerie : pas de boîtes grises, des champs soulignés d'un
   seul filet, qui noircit quand on y écrit. */
.form { display: grid; gap: var(--s4); max-width: none; }
.form > .btn, .cfg__form > .btn { justify-self: start; }
.field { display: grid; gap: var(--s1); }
.field > label, .fieldset > legend {
  font: 400 var(--fs-cartel)/1.5 var(--mono);
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-soft);
  padding: 0;
}
.field .req { color: var(--ink); }
.field .help { font-size: 13px; color: var(--ink-faint); text-transform: none; letter-spacing: 0; font-family: var(--sans); }
.field input, .field select, .field textarea {
  font: inherit; color: inherit; width: 100%;
  padding: 12px 0; background: transparent;
  border: 0; border-bottom: 1px solid var(--line-strong);
  transition: border-color .2s ease;
}
.field input:focus, .field select:focus, .field textarea:focus { border-bottom-color: var(--ink); }
.field select { padding-right: var(--s3); }
.field textarea { min-height: 150px; resize: vertical; border: 1px solid var(--line-strong); padding: var(--s2); }
.field input[aria-invalid="true"], .field select[aria-invalid="true"], .field textarea[aria-invalid="true"] {
  border-bottom-width: 2px; border-bottom-color: var(--ink);
}
/* L'erreur ne se signale pas par une couleur — il n'y en a pas — mais par un
   trait plus épais et par un mot précédé d'une barre. */
.field .err {
  font: 500 12px/1.5 var(--mono); letter-spacing: .06em;
  color: var(--ink); min-height: 0;
}
.field .err:not(:empty)::before { content: "— "; }

.telgroup { display: flex; gap: var(--s2); min-width: 0; }
.telgroup select { flex: 0 1 7.5rem; width: auto; min-width: 0; }
.telgroup input { flex: 1 1 0%; width: auto; min-width: 0; }

.msel__label { font: 400 var(--fs-cartel)/1.5 var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--ink-soft); }
.msel { position: relative; }
.msel__btn, .dpick__btn {
  width: 100%; text-align: left; cursor: pointer;
  display: flex; align-items: center; gap: var(--s2);
  padding: 12px 0; min-height: 48px;
  background: transparent; border: 0; border-bottom: 1px solid var(--line-strong);
  font: inherit; color: inherit;
}
.msel__value { flex: 1; min-width: 0; }
.msel__value:not(.is-set) { color: var(--ink-faint); }
.msel__caret { flex: 0 0 auto; font-size: 12px; color: var(--ink-faint); transition: transform .15s ease; }
.msel__btn[aria-expanded="true"], .dpick__btn[aria-expanded="true"] { border-bottom-color: var(--ink); }
.msel__btn[aria-expanded="true"] .msel__caret { transform: rotate(180deg); }
.dpick__btn[aria-expanded="true"] .msel__caret { transform: rotate(180deg); }
.msel__btn[aria-invalid="true"] { border-bottom-width: 2px; border-bottom-color: var(--ink); }

.msel__panel {
  position: absolute; z-index: 40; top: 100%; left: 0; right: 0;
  margin: 0; padding: 0; list-style: none;
  background: var(--paper); border: 1px solid var(--ink);
  max-height: 300px; overflow-y: auto;
  box-shadow: none;
}
.msel__panel li {
  display: flex; align-items: flex-start; gap: var(--s2);
  padding: 12px var(--s2); cursor: pointer; font-size: var(--fs-small);
  border-bottom: 1px solid var(--line);
}
.msel__panel li:last-child { border-bottom: 0; }
.msel__panel li::before {
  content: ""; flex: 0 0 auto; width: 14px; height: 14px; margin-top: 4px;
  border: 1px solid var(--line-strong); background: transparent;
}
.msel__panel li[aria-selected="true"]::before {
  background: var(--ink); border-color: var(--ink);
  background-image: linear-gradient(to bottom right, transparent 44%, #fff 44%, #fff 56%, transparent 56%),
                    linear-gradient(to top right, transparent 44%, #fff 44%, #fff 56%, transparent 56%);
  background-size: 60% 60%; background-position: center; background-repeat: no-repeat;
}
.msel__panel li:hover, .msel__panel li.is-active { background: var(--ph); }
.msel__panel li.is-active { outline: 1px solid var(--ink); outline-offset: -1px; }
.msel__count { font: 400 var(--fs-cartel-s)/1 var(--mono); color: var(--ink-faint); margin-left: var(--s1); }

.fieldset {
  border: 0; border-top: 1px solid var(--line); padding: var(--s3) 0 0; margin: 0;
  display: grid; gap: var(--s2); background: transparent;
}
.fieldset legend { margin-bottom: var(--s1); }
.opt { display: flex; align-items: flex-start; gap: var(--s2); font-size: var(--fs-small); }
.opt input { width: 16px; height: 16px; margin: 4px 0 0; flex: 0 0 auto; accent-color: #000; }
.opts--2 { display: grid; gap: var(--s2); }
@media (min-width: 700px) { .opts--2 { grid-template-columns: repeat(2, 1fr); } }

.consent {
  display: flex; align-items: flex-start; gap: var(--s2);
  font-size: 13px; line-height: 1.6; color: var(--ink-soft);
}
.consent input { width: 16px; height: 16px; margin: 3px 0 0; flex: 0 0 auto; accent-color: #000; }

.formstatus {
  border: 0; border-left: 2px solid var(--ink);
  background: transparent; padding: var(--s2) var(--s3);
  font-size: var(--fs-small);
}
.formstatus:empty { display: none; }

/* --- 15 bis. Configurateur ------------------------------------------- */
/* Aucune pastille de couleur, aucune silhouette teintée, aucune barre de prix
   fixe : la V1 n'engage aucun choix graphique. Seule la logique est démontrée. */

/* L'ancienne sous-grille de 10 colonnes — 7 pour les étapes, 3 pour le
   récapitulatif — a disparu avec l'écran immersif : le configurateur n'est plus
   posé dans la grille du site, il occupe l'écran. Voir « écran immersif ». */

/* Parties du parcours : une seule est visible à la fois. */
.cfg__part { display: grid; gap: 20px; }
.cfg__part[hidden] { display: none; }
.cfg__title { margin: 0; font-size: clamp(21px, 2.4vw, 30px); }
.cfg__title:focus-visible { outline: 2px solid var(--note); outline-offset: 6px; }
.cfg__legend { font-weight: 600; font-size: 15px; padding: 0; }
.cfg__nav {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  border-top: 1px solid var(--line); padding-top: 20px;
}
.cfg__nav [data-cfg-back] { order: 1; }
.cfg__nav [data-cfg-next] { order: 2; margin-left: auto; }
/* Retour et Continuer partagent le même état fermé. */
.cfg__nav .btn[disabled] { opacity: .4; cursor: not-allowed; }
.cfg__nav .btn[disabled]:hover { background: transparent; color: var(--ink); }

/* Étapes : des fieldsets sans habillage, la sélection se fait au bloc. */
/* fieldset volontairement en display:block : le placement de <legend> reste
   fiable dans tous les navigateurs, contrairement aux fieldsets en grille. */
.cfg__step {
  border: 0; border-top: 1px solid var(--line);
  margin: 0; padding: 24px 0 0; background: transparent;
  min-width: 0;
}
.cfg__step:first-of-type { border-top: 0; padding-top: 0; }
.cfg__step > legend { padding: 0; margin: 0 0 14px; }
.cfg__step .help { margin: 12px 0 0; }

/* Blocs sélectionnables : cases radio natives masquées dans un label. */
.opts { display: grid; gap: 10px; }
/* Base et niveau : trois blocs côte à côte, un par groupe de colonnes. */
.opts--3 { grid-template-columns: 1fr; }
@media (min-width: 620px) { .opts--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
/* Les teintes se lisent sur deux colonnes : sans echantillon, une tuile ne
   porte plus qu'un nom, et trois colonnes de noms courts laissent autant de
   vide qu'elles gagnent de place. */
.opts--tiles { grid-template-columns: 1fr; }
@media (min-width: 620px) { .opts--tiles { grid-template-columns: repeat(2, 1fr); } }
.opts--pairs { grid-template-columns: 1fr; }
@media (min-width: 620px) { .opts--pairs { grid-template-columns: repeat(2, 1fr); } }

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

.opt-card {
  display: block; position: relative; cursor: pointer;
  background: var(--surface); border: 1px solid var(--line-strong); border-radius: 2px;
  padding: 16px;
  transition: border-color .15s ease;
}
.opts--3 .opt-card, .opts--tiles .opt-card { padding: 14px; }
.opts--3 .opt-card--row { grid-column: 1 / -1; }   /* choix hors catalogue */
.opt-card:hover { border-color: var(--ink-soft); }
.opt-card__body { display: grid; gap: 5px; }
.opt-card__title { font-weight: 600; font-size: 15px; }
.opt-card__title em { font-style: normal; font-weight: 400; font-size: 13px; color: var(--ink-soft); }
.opt-card__note { font-size: 13px; color: var(--ink-soft); line-height: 1.5; }
.opt-card__aside { font: 600 13px/1.4 var(--mono); color: var(--ink); }
/* Détail du niveau, dans le bloc du niveau. */
.opt-card__list { display: grid; gap: 3px; margin-top: 8px; padding-top: 10px; border-top: 1px dotted var(--line); }
.opt-card__li {
  display: block; position: relative; padding-left: 14px;
  font-size: 13px; line-height: 1.45; color: var(--ink-soft);
}
.opt-card__li::before { content: "—"; position: absolute; left: 0; color: var(--ink-faint); }

/* L'état sélectionné se lit au filet : il passe d'un trait clair à un trait
   noir de deux pixels. C'est une différence d'épaisseur autant que de valeur,
   donc lisible sans percevoir la couleur — la règle tient toujours sans la
   marque dessinée qui occupait le coin. Le retrait de deux pixels au rembourrage
   compense l'épaississement : le contenu ne bouge pas à la sélection. */
.opt-card:has(input:checked) { border-color: var(--ink); border-width: 2px; padding: 15px; }
.opt-card:has(input:focus-visible) { outline: 2px solid var(--note); outline-offset: 3px; }
.opt-card:has(input:disabled) { opacity: .4; cursor: not-allowed; border-style: dashed; }

/* Repli pour les navigateurs sans :has() — classes posées par le script. */
.opt-card.is-on { border-color: var(--ink); border-width: 2px; padding: 15px; }
.opt-card.is-off { opacity: .4; cursor: not-allowed; border-style: dashed; }

/* Le questionnaire occupe la même largeur que les étapes. Les champs courts se
   posent deux par ligne pour ne pas laisser la colonne à moitié vide. */
.cfg__form { display: grid; gap: 22px; }
.cfg__form > p { margin: 0; }
.cfg__form .field[hidden] { display: none; }
@media (min-width: 700px) {
  .cfg__form { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--gap); }
  .cfg__form > * { grid-column: 1 / -1; }
  .cfg__form > .field--half { grid-column: span 1; }
}
/* ============================================================ écran immersif
   L'écran de configuration sort de la structure du site : pas d'en-tête, pas de
   pied de page, pas de grille de 12 colonnes. Deux tiers pour le véhicule que
   l'on configure, un tiers pour les choix, qui défilent. */

body.is-immersive { --header-h: 0px; }
body.is-immersive main { padding: 0; }
body.is-immersive .block__tag { top: 0; left: 0; }

.cfgx { display: block; }

.cfgx__stage {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  padding: 20px;
  gap: 14px;
}

/* Les deux panneaux doivent pouvoir se réduire : sans min-width, le minimum
   automatique d'une piste en fr est le min-content de son contenu, et la colonne
   des choix — pastilles, indicatif, liens — écraserait la scène. */
.cfgx__stage, .cfgx__panel { min-width: 0; }

@media (min-width: 1000px) {
  .cfgx {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    align-items: start;
    min-height: 100vh;
  }
  /* La scène ne défile pas : le véhicule reste sous les yeux pendant que les
     choix passent. C'est tout l'intérêt de la disposition. */
  .cfgx__stage {
    position: sticky;
    top: 0;
    height: 100vh;
    border-right: 1px solid var(--line);
  }
}
.cfgx__ph { border-color: var(--line-strong); }
.cfgx__ph .ph__label { max-width: 32ch; }

.cfgx__close {
  position: absolute;
  top: 14px; right: 14px;
  z-index: 2;
  width: 38px; height: 38px;
  display: grid; place-items: center;
  font-size: 22px; line-height: 1; text-decoration: none;
  color: var(--ink); background: var(--paper);
  border: 1px solid var(--line);
}
.cfgx__close:hover { border-color: var(--ink); }

/* Les angles de prise de vue : la galerie de la référence, réduite à ce qu'une
   maquette sans image peut honnêtement montrer — la liste des vues à produire. */
/* Diaporama des vues : un rail de cadres qui se translate, deux flèches posées
   dessus et des points de position. Rien ne nomme la vue dans les contrôles — on
   fait défiler des images, on ne choisit pas un angle dans une liste. */
.cfgx__slider { position: relative; flex: 1 1 auto; overflow: hidden; min-height: 240px; }
.cfgx__track {
  display: flex; height: 100%;
  transition: transform .45s cubic-bezier(.4, 0, .2, 1);
}
.cfgx__slide { flex: 0 0 100%; min-width: 0; display: flex; }
.cfgx__slide .cfgx__ph { flex: 1 1 auto; min-height: 0; }
@media (prefers-reduced-motion: reduce) { .cfgx__track { transition: none; } }

/* Les diagonales du placeholder sont dessinées en ::after, donc peintes après les
   enfants positionnés : sans z-index, elles recouvrent les contrôles et avalent
   le clic. */
.cfgx__vue, .cfgx__dots { z-index: 2; }

.cfgx__vue {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 46px; height: 72px; padding: 0;
  display: grid; place-items: center;
  font-size: 26px; line-height: 1;
  background: transparent; border: 0; color: var(--ph-ink);
  cursor: pointer; transition: color .15s, background-color .15s;
}
.cfgx__vue:hover { color: var(--ink); background: rgba(255, 255, 255, .6); }
.cfgx__vue--prev { left: 0; }
.cfgx__vue--next { right: 0; }

.cfgx__dots {
  position: absolute; left: 0; right: 0; bottom: 14px;
  display: flex; justify-content: center; gap: 9px;
}
.cfgx__dot {
  width: 8px; height: 8px; padding: 0; border-radius: 50%;
  background: transparent; border: 1px solid var(--ph-ink);
  cursor: pointer; transition: background-color .15s;
}
.cfgx__dot:hover { background: rgba(0, 0, 0, .2); }
.cfgx__dot.is-on { background: var(--ph-ink); }

/* Seul récapitulatif de la configuration : base · niveau · couleur · jantes ·
   sellerie · tarif. Il gagne en lisibilité ce qu'il a perdu en cadre. */
.cfgx__legend {
  margin: 0; font-size: 13px; color: var(--ink);
  letter-spacing: .04em; min-height: 1.4em;
}

/* La colonne des choix : les groupes s'empilent, séparés d'un filet. Volontairement
   en flux normal et non en grille — les blocs qu'elle contient portent des
   grid-column héritées d'ailleurs, qui y créeraient des colonnes fantômes. */
.cfgx__panel { display: block; padding: 0; }
.cfgx__group {
  padding: 30px 28px;
  border-bottom: 1px solid var(--line);
}
.cfgx__group[hidden] { display: none; }
/* Le compteur d'étape et ses deux flèches sur la même ligne. */
.cfg__count {
  margin: 0 0 6px;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.cfg__arrows { display: flex; gap: 6px; }
.cfg__arrow {
  width: 27px; height: 27px; padding: 0;
  display: grid; place-items: center;
  font-size: 16px; line-height: 1;
  background: transparent; border: 1px solid var(--line); color: var(--ink);
  cursor: pointer; transition: border-color .15s, color .15s;
}
.cfg__arrow:hover:not([disabled]) { border-color: var(--ink); }
/* Fermée, la flèche reste visible : elle annonce qu'il reste quelque chose à
   choisir, là où la faire disparaître laisserait croire à la fin du parcours. */
.cfg__arrow[disabled] { opacity: .28; cursor: not-allowed; }

/* Une étape finale, deux visages. Ce qui n'appartient pas à la branche courante
   est masqué et désactivé, donc absent de l'envoi comme de la validation. */
.cfg__branch { display: contents; }
.cfg__branch[hidden] { display: none; }
/* Le titre de la seconde moitié de l'étape finale : c'est là que le visiteur
   décide de poursuivre. Il porte donc l'échelle d'un titre de section — la même
   que le titre d'étape, la colonne n'en supporte pas de plus grande — et non
   celle d'une sous-section perdue entre deux champs. */
.cfg__more {
  margin: var(--s2) 0 0;
  font-size: clamp(19px, 2vw, 24px);
  line-height: 1.15; letter-spacing: -.028em;
  border-top: 1px solid var(--line); padding-top: var(--s4);
}

/* Le créneau, choisi dans le formulaire plutôt que sur une autre page. Les
   sections sont celles de la page rendez-vous — mêmes cartels, même calendrier,
   mêmes pastilles — et le filet vient du bloc, pour que « Format » ne se colle
   pas au dernier champ de coordonnées. */
/* Dans un formulaire, le choix du rendez-vous n'est pas une autre section :
   c'est la suite des champs. Aucun filet ne l'en sépare, ni lui ni ce qu'il
   contient — un formulaire tranché en tronçons se lit comme plusieurs
   formulaires. */
.picker { border-top: 0; padding-top: 0; }
.picker .booking__section { border-top: 0; padding-top: 0; margin-top: var(--s4); }
.picker > .booking__section:first-child,
.picker__row--two .booking__section { margin-top: 0; }
.picker .formstatus:empty { display: none; }
/* Le jour et l'heure, côte à côte : deux listes déroulantes au format des
   autres champs du formulaire. `auto-fit` les remet l'une sous l'autre quand la
   colonne devient trop étroite pour deux libellés. */
.picker__row { display: grid; gap: var(--gap); grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.picker__row .field { margin: 0; }
/* Le format et la langue sont deux questions à deux réponses : elles tiennent
   la même rangée, et repassent l'une sous l'autre quand la colonne se resserre. */
.picker__row--two { grid-template-columns: 1fr; }
@media (min-width: 620px) { .picker__row--two { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Le jour se prend dans un calendrier, mais replié : le champ garde la taille
   des autres, et le mois ne se déplie que le temps du choix. */
.dpick { position: relative; }
.dpick__panel {
  position: absolute; z-index: 40; top: 100%; left: 0; right: 0;
  min-width: 250px; padding: var(--s2);
  background: var(--paper); border: 1px solid var(--ink);
}
.dpick__panel[hidden] { display: none; }
.dpick__head { display: flex; align-items: center; justify-content: space-between; gap: var(--s1); }
.dpick__month {
  margin: 0; font: 500 var(--fs-cartel)/1 var(--mono);
  letter-spacing: var(--cartel-ls); text-transform: uppercase;
}
.dpick__nav {
  flex: 0 0 auto; width: 28px; height: 28px; cursor: pointer;
  border: 1px solid var(--line-strong); background: transparent;
  font: 400 15px/1 var(--mono); color: inherit;
}
.dpick__nav:hover:not([disabled]) { border-color: var(--ink); }
.dpick__nav[disabled] { opacity: .28; cursor: not-allowed; }
.dpick__panel .cal-grid { margin: var(--s2) 0 0; }
.dpick__panel .cal-grid span, .dpick__panel .cal-grid button { font-size: 12px; }
.cfgx .cfg__title { font-size: clamp(19px, 2vw, 24px); }

/* Dans une colonne d'un tiers d'écran, les blocs de choix se posent les uns sous
   les autres : une grille de trois n'y serait pas lisible. */
.opts--1 { display: grid; gap: 10px; }

.cfgx .cfg__recap { border: 0; }
@media (min-width: 1000px) { .cfgx .cfg__recap { position: static; } }
.cfgx .cfg__form { gap: 20px; }
@media (min-width: 700px) {
  /* La colonne est trop étroite pour deux champs par ligne. */
  .cfgx .cfg__form { grid-template-columns: minmax(0, 1fr); }
  .cfgx .cfg__form > .field--half { grid-column: 1 / -1; }
}

@media (max-width: 999px) {
  /* En mobile la scène coiffe l'écran et y reste : on voit ce que l'on change. */
  .cfgx__stage {
    position: sticky; top: 0; z-index: 3;
    height: 42vh; min-height: 260px;
    border-bottom: 1px solid var(--line);
  }
  .cfgx__slider { min-height: 0; }
  /* Zones de touche pleine hauteur, la scène étant plus basse. */
  .cfgx__vue { width: 44px; height: 100%; top: 0; transform: none; }
  .cfgx__dots { bottom: 10px; }
  .cfgx__group { padding: 26px 20px; }
}

/* Palier 1 — l'email seul. L'action est un lien : le CTA relationnel unique du
   §30 ne doit pas avoir de concurrent visuel à cette étape. */
.cfg__keepnav { margin: 0; }
.cfg__share { display: grid; gap: 8px; }
/* display:grid l'emporterait sur le [hidden] de la feuille du navigateur. */
.cfg__share[hidden] { display: none; }
.cfg__share label { display: block; }
.cfg__sharerow { display: flex; gap: 8px; align-items: center; }
.cfg__sharerow input { flex: 1 1 auto; width: auto; min-width: 0; font-size: 14px; }
.cfg__sharerow .link { flex: 0 0 auto; }


/* Réassurance juste avant la demande, à l'endroit exact de l'hésitation. */
.cfg__reassure {
  margin: 0; font-size: 14px; color: var(--ink-soft);
  border-left: 2px solid var(--line); padding-left: 14px;
}

.cfg__resume { margin: 14px 0 0; }
.cfg__resume[hidden] { display: none; }

/* Messages d'état de la colonne : un choix réinitialisé, une configuration
   retrouvée. Sans cadre, et sans place quand il n'y a rien à dire. */
/* L'espace est porté par les paragraphes, non par le conteneur : quand il n'y a
   rien à dire, il ne reste aucune place vide. */
.cfgx__status { padding: 0; }
.cfgx__status p { margin: 0; padding: 16px 28px 0; }
.cfgx__status p:empty, .cfgx__status p[hidden] { display: none; }
@media (max-width: 999px) { .cfgx__status p { padding: 14px 20px 0; } }

/* Mention de bas de colonne : elle engage la Maison sur les montants, mais elle
   n'est pas un contenu. Pas de filet, pas de fond, italique et discrète. */
.cfgx__foot { padding: 20px 28px 30px; }
.cfgx__mention {
  margin: 0; font-style: italic; font-size: 12.5px;
  line-height: 1.6; color: var(--ink-soft);
}
.cfgx__mention + .cfgx__mention { margin-top: 8px; }
@media (max-width: 999px) { .cfgx__foot { padding: 18px 20px 26px; } }

.block__tag--inline { position: static; align-self: start; justify-self: start; }

.cfg__recap { background: var(--surface); border: 1px solid var(--line); padding: 24px; }
@media (min-width: 1000px) { .cfg__recap { position: sticky; top: calc(var(--header-h) + 24px); } }
.cfg__recap .eyebrow { margin-bottom: 12px; }

.specs--flush { border: 0; background: transparent; gap: 0; }
.specs--flush > div { padding: 9px 0; border-bottom: 1px dotted var(--line); }
.specs--flush > div:last-child { border-bottom: 0; }
/* Une ligne du récapitulatif peut être retirée en cours de parcours — l'acompte
   quitte l'écran au moment de la demande. La grille l'emporterait sur [hidden]. */
.specs > div[hidden], .specs--flush > div[hidden] { display: none; }
@media (min-width: 620px) { .specs--flush > div { grid-template-columns: 7em 1fr; gap: 10px; } }


#cfg-warn:empty { display: none; }
#cfg-warn {
  margin-top: 18px; padding: 12px 14px;
  border-left: 3px solid var(--line-strong); background: var(--bg);
}

/* --- 16. Rendez-vous -------------------------------------------------- */

.booking { display: grid; gap: var(--s6) var(--s5); }
@media (min-width: 1000px) { .booking { grid-template-columns: 1fr 1.4fr; align-items: start; } }
.booking__recap {
  background: transparent; border: 0; border-top: 1px solid var(--ink);
  padding: var(--s3) 0 0;
}
.booking__recap h2 { font-size: var(--fs-h3); }
.calendar {
  background: transparent; border: 0; border-top: 1px solid var(--ink);
  padding: var(--s3) 0 0;
}
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; margin: var(--s3) 0; background: var(--line); border: 1px solid var(--line); }
.cal-grid span, .cal-grid button {
  aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  font: 400 13px/1 var(--mono); border: 0; background: var(--paper);
}
.cal-grid span { color: var(--ink-faint); font-size: 9px; letter-spacing: .12em; text-transform: uppercase; aspect-ratio: auto; padding: 8px 0; }
.cal-grid button { cursor: pointer; }
.cal-grid button:hover:not([disabled]) { background: var(--ph); }
.cal-grid button[disabled] { color: var(--line-strong); cursor: not-allowed; }
.cal-grid button[aria-pressed="true"] { background: var(--ink); color: var(--paper); }
.slots { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: var(--s1); }
.slots button { font: 400 13px/1 var(--mono); padding: 14px 6px; border: 1px solid var(--line-strong); background: transparent; cursor: pointer; }
.slots button:hover { border-color: var(--ink); }
.slots button[aria-pressed="true"] { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.segmented { display: flex; gap: var(--s1); flex-wrap: wrap; }
.segmented button {
  font: 500 var(--fs-cartel-s)/1 var(--mono); letter-spacing: var(--cartel-ls); text-transform: uppercase;
  padding: 13px var(--s2); border: 1px solid var(--line-strong); background: transparent; cursor: pointer;
}
.segmented button:hover { border-color: var(--ink); }
.segmented button[aria-pressed="true"] { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.booking__section { border-top: 1px solid var(--line); padding-top: var(--s3); margin-top: var(--s3); }
.booking__section:first-child { border-top: 0; padding-top: 0; margin-top: 0; }
.booking__section h3 {
  font: 400 var(--fs-cartel-s)/1 var(--mono);
  letter-spacing: var(--cartel-ls); text-transform: uppercase;
  color: var(--ink-faint); margin-bottom: var(--s2);
}

/* --- 16 bis. Bouton d'accompagnement ------------------------------------ */

/* Sur une page longue, le bouton du hero disparaît au premier écran et la
   sortie de page est loin. Celui-ci prend le relais entre les deux : il paraît
   une fois le hero passé, se retire quand la sortie arrive, et ne double donc
   jamais le bouton en clair. */
.cta-dock {
  position: fixed; z-index: 60;
  right: var(--gutter); bottom: var(--s4);
  opacity: 0; transform: translateY(12px);
  pointer-events: none;
  transition: opacity .3s ease, transform .3s ease;
}
.cta-dock[hidden] { display: none; }
.cta-dock.is-in { opacity: 1; transform: none; pointer-events: auto; }
/* La maquette cernait ce bouton d'un liseré blanc, pour le détacher d'une
   photographie claire sur laquelle il viendrait se poser. À l'usage le
   contour se voyait plus que le service qu'il rendait : retiré le
   8 octobre 2026, à la demande. C'est le seul écart volontaire du thème
   sur ce bouton. */
@media (prefers-reduced-motion: reduce) { .cta-dock { transition: none; } }
@media (max-width: 699px) {
  /* Le pouce atteint le bas de l'écran, pas son coin : le bouton prend la
     largeur, à distance du bord. */
  .cta-dock { left: var(--gutter); right: var(--gutter); bottom: var(--s3); }
  .cta-dock .btn { width: 100%; }
}

/* --- 17. Empty state -------------------------------------------------- */

.empty {
  border: 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  background: transparent; padding: var(--s6) 0; text-align: center;
}
.empty h3 { margin-bottom: .5em; }
.empty p { color: var(--ink-soft); max-width: 44ch; margin-inline: auto; }

/* --- 18. Article ------------------------------------------------------ */

/* Un article tient sur deux mesures et deux seulement : le texte occupe la
   moitié du conteneur, les images sa totalité. Aucune mesure intermédiaire, et
   aucun plafond en `ch` — la colonne est celle de la grille, pas une largeur de
   lecture négociée à part. En dessous de 1 000 px, `c-text` passe pleine
   largeur comme partout ailleurs. */
.article__head, .article__body { max-width: none; }
.article__head h1 { text-wrap: balance; }
/* Les intertitres sont écrits « ### » dans le texte source et sortent donc en
   h3 : la règle porte sur les deux niveaux, sans quoi un intertitre se lirait à
   la taille du texte courant et ne découperait plus rien. */
.article__body h2,
.article__body h3 { font-size: clamp(21px, 2.1vw, 28px); margin-top: 2.2em; letter-spacing: -.025em; }
.article__body > :first-child { margin-top: 0; }
.article__body blockquote {
  margin: 2em 0; padding-left: var(--s3); border-left: 1px solid var(--ink);
  font-size: clamp(19px, 2vw, 24px); line-height: 1.4; letter-spacing: -.02em;
}
/* Deux passages de texte qui se suivent forment un seul article, pas deux
   blocs : ils s'enchaînent au rythme du paragraphe. Ce qui les sépare, c'est le
   mur d'images quand il s'intercale — et le silence est alors le même de part
   et d'autre du mur. */
.article__row + .article__row { margin-top: 1.2em; }
.hang + .article__row { margin-top: var(--s5); }
.article__hero { margin: var(--s4) 0 var(--s5); }
.article__tail { margin-top: var(--s5); }
.figure { margin: 0; }

/* --- 18 bis. Machine précédente / suivante ---------------------------- */

/* La fin d'une fiche n'est pas une impasse : elle ouvre sur la Machine
   voisine. Le nom prend l'échelle d'un titre, parce que c'est lui qu'on suit,
   et la mention qui le précède reste un cartel. Un seul filet en tête, comme
   partout : rien n'encadre. Quand une Machine n'a qu'un voisin, la cellule
   vide garde sa colonne pour que l'autre reste de son côté. */
.pn {
  display: grid; gap: var(--s4) var(--gap);
  border-top: 1px solid var(--ink); padding-top: var(--s4);
}
@media (min-width: 700px) { .pn { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.pn__cell { display: block; text-decoration: none; border: 0; color: inherit; }
@media (min-width: 700px) { .pn__cell--next { text-align: right; } }
.pn__label {
  display: block; margin-bottom: var(--s1);
  font: 500 var(--fs-cartel-s)/1.6 var(--mono);
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-faint);
}
.pn__name {
  display: block;
  font-size: var(--fs-h2); line-height: 1.1; letter-spacing: -.028em;
}
.pn__cell:hover .pn__name,
.pn__cell:focus-visible .pn__name { color: var(--ink-soft); }
.pn__cell:focus-visible { outline: 1px solid var(--ink); outline-offset: 6px; }

/* --- 19. Footer ------------------------------------------------------- */

/* Le pied de page reste blanc : le mur ne s'interrompt pas. Un seul filet
   noir marque la fin de l'accrochage. */
/* Le silence se place AVANT le filet, jamais après : c'est la fin du contenu
   qu'il détache du pied de page. Le retrait intérieur de l'ours, lui, ne bouge
   pas — il tient le filet à bonne distance de la première ligne. */
.footer {
  background: var(--paper); color: var(--ink);
  border-top: 1px solid var(--ink-faint);
  margin-top: var(--s8);
  padding-block: var(--s6) var(--s4);
}
@media (max-width: 999px) { .footer { margin-top: var(--s6); } }
.footer a { text-decoration: none; border-bottom: 1px solid transparent; }
.footer a:hover { text-decoration: none; border-bottom-color: currentColor; }
.footer h2 {
  font: 400 var(--fs-cartel-s)/1 var(--mono);
  letter-spacing: var(--cartel-ls); text-transform: uppercase;
  color: var(--ink-faint); margin-bottom: var(--s3);
}
.footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s2); font-size: var(--fs-small); }
.footer__legal {
  margin-top: var(--s6); padding-top: var(--s3); border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: var(--s1) var(--s4); justify-content: space-between;
  font: 400 var(--fs-cartel-s)/1.8 var(--mono); letter-spacing: .1em;
  color: var(--ink-faint);
}

/* --- 20. Fil d'Ariane ------------------------------------------------- */

.crumb {
  font: 400 var(--fs-cartel-s)/1 var(--mono);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-faint); padding-top: var(--s5);
}
.crumb ol { list-style: none; display: flex; flex-wrap: wrap; gap: var(--s1); margin: 0; padding: 0; }
.crumb a { text-decoration: none; }
.crumb a:hover { color: var(--ink); }
.crumb li:last-child { color: var(--ink); }

/* --- 21. Spécification de page — lightbox (outil interne) ------------- */

.spec {
  padding: 0; border: 1px solid var(--ink);
  background: var(--paper); color: var(--ink);
  width: min(780px, calc(100vw - 32px));
  max-height: min(80vh, 780px);
  font-size: var(--fs-small);
  box-shadow: none;
  overflow: hidden;
}
.spec:not([open]) { display: none; }
.spec::backdrop { background: rgba(255, 255, 255, .82); }

.spec[data-fallback] {
  position: fixed; z-index: 980; top: 50%; left: 50%;
  transform: translate(-50%, -50%); display: block;
}
.spec[data-fallback][hidden] { display: none; }

.spec__head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s2);
  padding: var(--s2) var(--s3); border-bottom: 1px solid var(--ink);
  background: var(--paper); position: sticky; top: 0;
}
.spec__title {
  margin: 0;
  font: 500 var(--fs-cartel-s)/1.4 var(--mono);
  letter-spacing: var(--cartel-ls); text-transform: uppercase; color: var(--ink);
}
.spec__close {
  background: transparent; border: 1px solid var(--ink); color: var(--ink);
  padding: 7px 12px; cursor: pointer;
  font: 500 9px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase;
}
.spec__close:hover { background: var(--ink); color: var(--paper); }
.spec__body {
  padding: var(--s3); display: grid; gap: var(--s2);
  max-height: calc(min(80vh, 780px) - 54px); overflow-y: auto;
}
.spec dl { display: grid; gap: 4px var(--s3); margin: 0; }
@media (min-width: 700px) { .spec dl { grid-template-columns: 13em 1fr; } }
.spec dt { font: 400 var(--fs-cartel-s)/1.8 var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--ink-faint); }
.spec dd { margin: 0; }
.spec ol, .spec ul { margin: 0; padding-left: 1.3em; }
.spec code { font: 400 12px/1.6 var(--mono); background: var(--ph); padding: 1px 5px; }

/* --- 22. Barre d'outils (outil interne) ------------------------------ */

.devbar {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 950;
  background: var(--ink); color: var(--paper);
  display: flex; align-items: center; gap: var(--s1); flex-wrap: wrap;
  padding: var(--s1) var(--gutter);
  font: 500 9px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase;
}
.devbar strong { color: var(--paper); letter-spacing: .18em; margin-right: var(--s1); font-weight: 500; }
.devbar button, .devbar a {
  background: transparent; border: 1px solid #555; color: var(--paper);
  padding: 7px 10px; cursor: pointer; text-decoration: none; font: inherit;
}
.devbar button[aria-pressed="true"] { background: var(--paper); color: var(--ink); border-color: var(--paper); }
.devbar .spacer { margin-left: auto; }
body.hide-devbar .devbar { display: none; }
body:not(.hide-devbar) .footer { padding-bottom: 80px; }

.devbar-show {
  position: fixed; z-index: 949; bottom: 14px; right: 14px;
  display: none; align-items: center; justify-content: center;
  width: 40px; height: 40px;
  background: var(--ink); color: var(--paper); border: 1px solid var(--ink);
  cursor: pointer; opacity: .3;
  font: 500 10px/1 var(--mono); letter-spacing: .06em;
  transition: opacity .18s ease;
}
.devbar-show:hover, .devbar-show:focus-visible { opacity: 1; }
body.hide-devbar .devbar-show { display: inline-flex; }

/* --- 23. Accessibilité ------------------------------------------------ */

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 999;
  background: var(--ink); color: var(--paper); padding: 12px 18px;
  font: 500 var(--fs-cartel)/1 var(--mono); letter-spacing: .12em; text-transform: uppercase;
}
.skip:focus { left: 8px; top: 8px; }

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

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* --- 24. Chemin de fer (page d'index interne) ------------------------ */

.railmap { padding-block: var(--s5) var(--s7); }
.railmap__table { width: 100%; border-collapse: collapse; font-size: var(--fs-small); background: transparent; }
.railmap__table th, .railmap__table td { text-align: left; padding: var(--s2); border: 0; border-bottom: 1px solid var(--line); vertical-align: top; }
.railmap__table th { font: 400 9px/1.4 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--ink-faint); background: transparent; border-bottom-color: var(--ink); }
.railmap__table code { font: 400 12px/1.7 var(--mono); }
.flow { list-style: none; margin: 0 0 var(--s5); padding: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1); }
.flow li { background: transparent; border: 1px solid var(--line-strong); padding: 10px 13px; font: 400 11px/1 var(--mono); letter-spacing: .06em; }
.flow li::after { content: " →"; color: var(--ink-faint); }
.flow li:last-child::after { content: ""; }
.flow li:last-child { border-color: var(--ink); }

@media print {
  .devbar, .header, .gridlayer { display: none !important; }
  body { padding-top: 0; }
}

/* ------------------------------------------ capture en deux paliers, hors écran
   immersif. Le premier palier est un palier, pas un préambule : il porte son
   propre titre et se ferme par un trait avant que la demande commence.         */

.form--steps .step { padding: 0 0 var(--s4); }
/* Le second palier reprend la grille du formulaire : un pas régulier entre les
   champs, et un silence plus large avant le bouton — on relit ce qu'on accepte
   avant de le confirmer. */
.form--steps .step--second { display: grid; gap: var(--s4); }
.form--steps .step--second > .btn { justify-self: start; }
.form--steps .step--second > .consent + .btn { margin-top: var(--s3); }
.form--steps .step--first {
  border-bottom: 1px solid var(--line);
  margin-top: var(--s4); margin-bottom: var(--s4);
}
.form--steps .step h3 { margin: 0 0 var(--s2); }
.form--steps .step__action { margin: var(--s3) 0 var(--s1); }
.form--steps .step__action .link { font-size: var(--fs-cartel); }

/* Le palier 1 du configurateur porte désormais la géométrie des boutons du
   site — même rectangle, même mono, même absence d'angle — mais en contour
   plutôt qu'en plein : c'est une porte qu'on ouvre sans rien s'engager. */
.cfg__keepnav { margin: var(--s4) 0 var(--s2); }
.cfg__keepnav .btn { min-height: 52px; }

/* --- 25. Particularités de l'accueil ---------------------------------- */

/* Le titre d'accueil est le seul du site à jouer dans le registre du display :
   il n'y a ni surtitre ni sous-titre pour l'accompagner, il porte donc seul. */
.page-home .hero h1 {
  font-size: clamp(34px, 5vw, 72px);
  line-height: 1.0;
  letter-spacing: -.038em;
}

/* Une section qui prend l'air : la vision, la sortie de page. Le silence y est
   la moitié du propos. */
.block--air { padding-block: calc(var(--block-pad) * 2.3); }


/* Une section qui occupe l'écran entier, son contenu calé au milieu. L'écran
   n'a plus qu'une chose à montrer, et le vide au-dessus et en dessous n'est pas
   une marge : c'est le cadre. `svh` pour la même raison que sur le hero — `vh`
   compte la barre d'adresse escamotable et déborde de l'écran réel. */
.block--full {
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  align-items: center;
}
.block--full > .shell { width: 100%; }

/* La queue d'une liste : un bouton posé sous les vignettes, centré et détaché.
   On a fini de regarder, on peut aller voir le reste. */
.cards__tail { display: flex; justify-content: center; margin-top: var(--s7); }

/* --- 26. Apparition au défilement -------------------------------------- */

/* Quatorze pixels et une opacité. Juste assez pour que la page respire quand on
   la parcourt, jamais assez pour qu'on l'attende : la transition est plus
   rapide que le scroll qui la déclenche.

   La classe `reveal` est posée par le script, jamais par le gabarit — sans
   JavaScript, rien n'est masqué. Et la règle entière tombe si le visiteur a
   demandé moins d'animations. */
@media (prefers-reduced-motion: no-preference) {
  .reveal { opacity: 0; transform: translateY(14px); will-change: opacity, transform; }
  .reveal.is-in {
    opacity: 1; transform: none; will-change: auto;
    transition: opacity .65s cubic-bezier(.22, .61, .36, 1),
                transform .65s cubic-bezier(.22, .61, .36, 1);
  }
}

/* --- 27. Vignette vers hero -------------------------------------------- */

/* Un clic sur une vignette de la Galerie n'ouvre pas une page : il agrandit
   l'image jusqu'au plein écran, et la page suivante commence exactement là où
   l'agrandissement s'est arrêté. Le visiteur ne voit pas deux pages, il voit
   une image qui se rapproche.

   Le cadre agrandi vit dans un calque qui ne fait partie ni de l'ancienne page
   ni de la nouvelle. Il reste affiché pendant le chargement — le navigateur
   garde l'ancienne page à l'écran jusqu'à ce que la suivante soit prête — et
   la nouvelle page l'efface en posant son propre hero au même endroit.

   Toute la section tombe si le visiteur a demandé moins d'animations : le lien
   redevient un lien, et la navigation est instantanée. */
@media (prefers-reduced-motion: no-preference) {

  /* La vignette devient cliquable dans son entier. La classe est posée par le
     script : sans JavaScript, seul le lien « Découvrir » reste actionnable, et
     rien ne promet un clic qui ne se produirait pas. */
  .ph--tap { cursor: pointer; }

  .thumbfull { position: fixed; inset: 0; z-index: 9999; pointer-events: none; }
  .thumbfull__box {
    position: absolute;
    background: var(--ph); color: var(--ph-ink);
    overflow: hidden;
    display: flex; align-items: flex-end; justify-content: flex-start;
    padding: var(--s2);
    will-change: top, left, width, height;
  }
  /* Le jour où la photographie remplace le cadre gris, elle se recadre d'elle
     même pendant l'agrandissement : jamais d'étirement, jamais de déformation. */
  .thumbfull__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
  .thumbfull__label { position: relative; z-index: 2; }

  /* Le départ : tout ce qui n'est pas l'image s'efface, et plus vite qu'elle ne
     s'agrandit. L'image reste presque seule à l'écran avant même d'avoir fini
     sa course.

     L'en-tête, lui, ne bouge pas : il est le seul élément commun aux deux
     pages, et c'est ce qui dit au visiteur qu'il n'a pas quitté le site. Il
     passe au-dessus du calque le temps de la course — sa fusion en
     `difference` le rend lisible sur l'image qui grandit dessous, quelle que
     soit sa valeur. */
  body.is-leaving .header { z-index: 10000; }

  body.is-leaving main,
  body.is-leaving .footer,
  body.is-leaving .gridlayer,
  body.is-leaving .devbar,
  body.is-leaving .devbar-show { opacity: 0; transition: opacity .3s ease; }

  /* Une page sans image d'accroche — Sur commande, Contact, La Maison, les
     formulaires — n'a rien à quoi rattacher la vignette. Elle se présente donc
     autrement : la page monte, bloc après bloc, au lieu d'être posée d'un coup.

     Le masquage initial est écrit ici, donc dès la première image rendue. Il ne
     tient que le temps que le script pose sa propre classe d'apparition, plus
     spécifique, qui prend alors la main et porte la montée. Sans script, la
     règle tombe avec la classe et rien ne reste caché. */
  html.mr-rise main > section.block { opacity: 0; }

  /* Une page d'article n'a pas de hero bord à bord : son image principale se
     range sous le titre, et le titre la précède. Le cadre n'y passe donc pas
     par le plein écran — il part du rectangle qu'occupait la vignette sur la
     page précédente et rejoint directement sa place, pendant que le texte se
     pose autour de lui.

     Ce rectangle de départ est écrit par le script d'en-tête dans quatre
     variables, et le cadre lui-même est un pseudo-élément : il existe donc dès
     la première image rendue, avant tout script de fin de page, et ne peut pas
     laisser un instant de blanc. Le script n'a plus qu'à changer les quatre
     valeurs pour que la transition emporte le cadre à destination. */
  html.mr-from body.page-article::before {
    content: '';
    position: fixed; z-index: 9998;
    left: var(--mr-l, 0); top: var(--mr-t, 0);
    width: var(--mr-w, 0); height: var(--mr-h, 0);
    background: var(--ph);
    transition: left .7s cubic-bezier(.62, .05, .2, 1),
                top .7s cubic-bezier(.62, .05, .2, 1),
                width .7s cubic-bezier(.62, .05, .2, 1),
                height .7s cubic-bezier(.62, .05, .2, 1);
  }
  html.mr-from .page-article .article__hero .ph { visibility: hidden; }

  /* Même règle que sur le départ : l'en-tête ne quitte pas l'écran, il passe
     au-dessus du cadre pendant que celui-ci fait son trajet. */
  html.mr-from .header { z-index: 10000; }

  html.mr-arriving .page-article .article__head > *,
  html.mr-arriving .page-article .article__row,
  html.mr-arriving .page-article .hang,
  html.mr-arriving .page-article .article__tail { opacity: 0; transform: translateY(16px); }

  html.mr-arriving.mr-arrived .page-article .article__head > *,
  html.mr-arriving.mr-arrived .page-article .article__row,
  html.mr-arriving.mr-arrived .page-article .hang,
  html.mr-arriving.mr-arrived .page-article .article__tail {
    opacity: 1; transform: none;
    transition: opacity .9s cubic-bezier(.22, .61, .36, 1) .45s,
                transform .9s cubic-bezier(.22, .61, .36, 1) .45s;
  }
  html.mr-arriving.mr-arrived .page-article .article__head > *:nth-child(2) { transition-delay: .55s; }
  html.mr-arriving.mr-arrived .page-article .article__head > *:nth-child(3) { transition-delay: .65s; }

  /* L'arrivée : le hero est déjà en place et l'en-tête n'a jamais disparu. Seul
     le texte se pose dessus, et il prend son temps — un quart de seconde de
     silence d'abord, pour que l'image soit regardée avant d'être commentée,
     puis le titre, le sous-titre et l'ancre, à un septième de seconde d'écart.
     C'est ce décalage qui fait la différence entre un texte qui apparaît et un
     texte qui se dépose. */
  html.mr-arriving .hero__content .inner > *,
  html.mr-arriving .hero .ph__label,
  html.mr-arriving .hero .badge--float { opacity: 0; transform: translateY(16px); }

  html.mr-arriving.mr-arrived .hero__content .inner > *,
  html.mr-arriving.mr-arrived .hero .ph__label,
  html.mr-arriving.mr-arrived .hero .badge--float {
    opacity: 1; transform: none;
    transition: opacity .9s cubic-bezier(.22, .61, .36, 1) .25s,
                transform .9s cubic-bezier(.22, .61, .36, 1) .25s;
  }
  html.mr-arriving.mr-arrived .hero__content .inner > *:nth-child(2) { transition-delay: .39s; }
  html.mr-arriving.mr-arrived .hero__content .inner > *:nth-child(3) { transition-delay: .53s; }
  html.mr-arriving.mr-arrived .hero .ph__label,
  html.mr-arriving.mr-arrived .hero .badge--float { transition-delay: .6s; }
}

/* --- 27 bis. Le hero sur fond photo ------------------------------------- */

/* Le contenu du hero est posé sur le média, il ne peut donc pas être écrit à
   l'encre du texte courant. La règle est réservée aux pages dont le hero porte
   une vraie image animée : sur un cadre gris clair en attente de production,
   du blanc ne se lirait pas. Tout y passe au blanc : le titre, le sous-titre,
   l'ancre, les boutons, le cartel de production et la pastille de statut.

   Rien ne revient au noir ici, contrairement à l'en-tête : ce contenu ne reste
   pas à l'écran, il défile et sort. Seul l'en-tête, qui est fixe, a deux
   états. */
body.hero-media .hero__content { color: var(--paper); }
body.hero-media .hero .sub { color: rgba(255, 255, 255, .82); }
body.hero-media .hero__anchor { color: var(--paper); }
/* Le cartel de production n'a plus lieu d'être une fois le média livré : il
   décrivait une image à produire. Il reste sur les heros encore en attente. */
body.hero-media .hero .ph__label { display: none; }
body.hero-media .hero .badge--float { color: var(--paper); border-color: var(--paper); }

/* Les deux boutons échangent leurs valeurs : le plein devient blanc sur noir,
   le contour devient blanc. Le rapport entre les deux — un bouton qui pèse, un
   bouton qui accompagne — est conservé. */
body.hero-media .hero .btn {
  background: var(--paper); border-color: var(--paper); color: var(--ink);
}
body.hero-media .hero .btn:hover { background: transparent; color: var(--paper); }
body.hero-media .hero .btn--ghost {
  background: transparent; border-color: var(--paper); color: var(--paper);
}
body.hero-media .hero .btn--ghost:hover { background: var(--paper); color: var(--ink); }

body.hero-media .hero .btn:focus-visible,
body.hero-media .hero__anchor:focus-visible { outline-color: var(--paper); }

/* Le même voile que sous l'en-tête, mais par le bas et plus ample : c'est là
   que le titre est posé, et une carrosserie claire au bas de l'image le ferait
   disparaître. */
body.hero-media .hero__content::before {
  content: '';
  position: absolute; inset: -40vh 0 0 0; z-index: -1;
  background: linear-gradient(to top, rgba(0, 0, 0, .55), rgba(0, 0, 0, 0));
  pointer-events: none;
}

/* --- 28. Vidéo du hero --------------------------------------------------- */

/* La vidéo occupe le cadre du hero, elle ne le remplace pas : le cadre garde sa
   hauteur d'écran, son cartel de production et sa place dans la transition.

   Elle est toujours visible, y compris avant d'avoir la moindre source : son
   poster tient l'affiche. Le poster doit donc être la première image exacte de
   la boucle, sans quoi le démarrage de la lecture se verrait. Si le poster
   manque, le gris du cadre reste dessous et rien ne casse.

   Le son n'est pas une option : une image d'accroche qui parle sans qu'on le
   lui ait demandé fait fermer l'onglet. */
.hero__video {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%;
  object-fit: cover;
  pointer-events: none;
}

/* Animations réduites : le visiteur a demandé le calme, il n'y a pas de demi
   mesure possible avec une vidéo. Le poster reste, et le script ne charge
   jamais les fichiers. */
@media (prefers-reduced-motion: reduce) {
  .hero__video { display: none; }
}

/* --- 46. Le récit de La Maison ------------------------------------------

   Trois chapitres qui se lisent d'un seul tenant : le texte défile à droite,
   plus lentement que la page, pendant que l'image de gauche change d'un coup
   au passage d'un chapitre au suivant.

   AU REPOS — sans script, sous 900 px, ou quand les animations sont refusées —
   le récit n'est rien d'autre que trois rangées image/texte, comme les trois
   sections qu'il remplace. C'est l'état écrit ici ; le mécanisme ne s'ajoute
   qu'une fois armé par le module 16, qui pose `is-actif`. Un récit qui ne
   défile pas se lit encore ; un récit qui ne se lit qu'en défilant, non. */

.recit__duo { display: grid; row-gap: var(--s5); align-items: center; }

/* `figure` arrive du navigateur avec une marge de 40 px a gauche et a droite.
   L'etat arme la remet a zero, mais au repos -- telephone -- elle restait, et
   l'image se retrouvait rentree de 40 px quand le texte, lui, tombait sur la
   gouttiere. Le cadre occupe toute la largeur qu'on lui donne, sans marge
   propre : c'est la gouttiere du bloc, et elle seule, qui l'ecarte du bord. */
.recit__vue { margin: 0; }
.recit__duo + .recit__duo { margin-top: var(--s7); }
.recit__chapitre > :first-child { margin-top: 0; }

/* Au repos, le récit n'a pas de `.shell` : le balisage reste nu pour que
   l'état armé puisse coller l'écran aux deux bords. Sur téléphone, où le
   récit ne s'arme jamais, le texte se retrouvait donc contre le bord de
   l'écran, seul de toute la page à n'avoir pas de gouttière. On lui rend ici
   à la main la mesure et la gouttière de `.shell`, et seulement au repos :
   armé, la colonne de texte porte déjà son propre retrait et l'image doit,
   elle, toucher le bord. */
.recit:not(.is-actif) .recit__ecran {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

@media (min-width: 900px) {
  .recit__duo { grid-template-columns: 1fr 1fr; column-gap: var(--s6); }
  /* Le deuxième chapitre renvoie son image à droite, comme l'alternance des
     trois sections d'origine. */
  .recit__duo:nth-child(2) .recit__vue { order: 2; }
}

/* --- armé ---------------------------------------------------------------

   `recit__piste` donne la course : c'est sa hauteur, posée par le script
   d'après le texte réellement mesuré, qui décide du temps de lecture.
   `recit__ecran` s'y colle et tient l'écran entier. Les images sont sorties
   du flux et occupent la moitié gauche ; les chapitres coulent dans la moitié
   droite et sont déplacés tous ensemble, du même nombre de pixels. */

.recit.is-actif { padding-top: 0; padding-bottom: 0; }
.recit.is-actif .recit__piste { height: var(--recit-course, 300vh); }

.recit.is-actif .recit__ecran {
  position: sticky; top: 0;
  height: 100vh; height: 100svh;
  /* Le rognage est posé sur l'élément collant lui-même, et non sur un parent :
     un ancêtre qui rogne romprait le collage. */
  overflow: clip;
  /* Et l'écran doit former son propre contexte : sans quoi la marge haute du
     premier chapitre se fusionne avec la sienne et ressort de la boîte --
     le silence d'ouverture disparaissait alors purement et simplement.
     `overflow: clip`, contrairement à `hidden`, ne suffit pas à l'empêcher. */
  display: flow-root;
}

/* `contents` dissout la rangée : l'image et le chapitre deviennent enfants de
   l'écran, l'un calé à gauche, l'autre coulant à droite, sans que le balisage
   ait eu à les séparer. */
.recit.is-actif .recit__duo { display: contents; }

.recit.is-actif .recit__vue {
  position: absolute; top: 0; left: 0;
  /* Le cadre est une FENETRE, pas une boite a image. `--recit-large` vaut 1
     aux deux bords du recit et 0 pendant la lecture : la fenetre part de
     l'ecran entier, se referme sur sa moitie le temps du texte, puis se
     rouvre en grand pour sortir. L'image, elle, ne bouge ni ne change de
     taille -- elle est simplement masquee puis devoilee. */
  width: calc(50% + var(--recit-large, 0) * 50%);
  height: 100%;
  margin: 0;
  /* Sans ce rognage, l'image debordait de la fenetre au lieu d'etre coupee
     par elle. C'est lui qui fait le masque. */
  overflow: clip;
  /* L'image passe AU-DESSUS du texte. C'est elle qui le decouvre en se
     retirant vers sa moitie, et qui le recouvre en se redeployant : un store
     qu'on leve et qu'on baisse, pas un fondu. Le texte, lui, ne bouge ni ne
     palit -- il est simplement cache, puis la. */
  z-index: 1;
  /* Chaque image attend son tour un écran plus bas. `--i` est son rang,
     `--recit-vue` la position courante du train : la soustraction donne le
     déplacement, en hauteurs d'écran. */
  transform: translateY(calc((var(--i, 0) - var(--recit-vue, 0)) * 100%));
  will-change: transform;
}
.recit.is-actif .recit__vue .ph {
  /* L'image tient la largeur de l'ECRAN, jamais celle de la fenetre : c'est
     ce qui distingue un masque d'une reduction. Quand la fenetre se referme
     sur sa moitie, on voit la moitie gauche de la meme image, au meme
     endroit, a la meme taille ; aucun pixel ne se deplace.

     La largeur est posee en pixels par le module 16 et non en `vw` : `100vw`
     compte la barre de defilement, que l'ecran colle n'a pas, et l'image
     aurait depasse d'une quinzaine de pixels -- visibles au moment ou elle
     est entierement devoilee. Le repli reste `100vw` pour le cas ou le
     script n'aurait pas encore mesure. */
  position: absolute;
  top: 0; left: 0;
  width: var(--recit-ecran, 100vw);
  height: 100%;
  /* Le cadre ne tient plus son rapport : c'est l'ecran qui le dicte, et la
     photographie s'y recadre d'elle-meme (`object-fit: cover`). */
  aspect-ratio: auto;
}

.recit.is-actif .recit__chapitre {
  margin-left: 50%;
  /* La colonne prend toute la moitie droite ; c'est la mesure de lecture, a
     l'interieur, qui est centree. Sans `width`, `.prose` bornait le chapitre
     lui-meme a 62ch et le collait contre la ligne mediane. */
  width: 50%;
  max-width: none;
  padding: 0 var(--gutter);
  min-height: 100vh; min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  transform: translateY(calc(var(--recit-y, 0) * -1px));
  will-change: transform;
}
/* Tous les enfants partagent exactement la meme largeur, en pixels : le bloc
   est centre d'un seul tenant et titre et paragraphes sont ferres sur la meme
   verticale.

   La mesure n'est surtout pas dite en `ch` : le `ch` vaut la largeur du zero
   dans la fonte de CHAQUE element, et le titre est bien plus gros que le
   texte. Une meme valeur en `ch` donnait donc deux largeurs differentes, deux
   boites centrees differemment, et deux bords gauches a quelques dizaines de
   pixels l'un de l'autre. */
.recit.is-actif .recit__chapitre { --recit-mesure: min(27rem, 100%); }
.recit.is-actif .recit__chapitre > * { width: 100%; max-width: var(--recit-mesure); }

/* Le récit se ferme sur le même silence qu'il s'ouvre. En marge basse, et
   non en retrait : un retrait serait pris sur la hauteur d'écran du chapitre
   et remonterait le texte au lieu d'ajouter du vide. Le script ajoute cette
   marge à sa mesure -- `offsetHeight` ne la compte pas. */
.recit.is-actif .recit__duo:last-child .recit__chapitre {
  margin-bottom: var(--recit-entre);
}

/* Un petit filet entre deux chapitres. Quarante pixels : c'est une marque,
   pas une separation -- il ponctue la lecture sans couper la colonne en deux.
   Pose au milieu du silence, et centre sur la mesure, il se lit comme le
   point d'orgue entre deux mouvements.

   `--line-strong` plutot que `--line` : sur quarante pixels, le filet
   ordinaire du site ne se verrait pas. Ce qui vaut pour un trait qui traverse
   une colonne ne vaut pas pour un trait qui tient dans un pouce.

   Le chapitre porte deja une transformation, donc un bloc de positionnement :
   `position: relative` ne change rien a la mise en page, il ne fait que
   nommer ce qui etait deja vrai. */
.recit.is-actif .recit__duo + .recit__duo .recit__chapitre { position: relative; }
.recit.is-actif .recit__duo + .recit__duo .recit__chapitre::before {
  content: '';
  position: absolute;
  top: calc(var(--recit-entre) / -2);
  /* Ferre sur le bord gauche du texte, et non centre sous lui. La mesure est
     centree dans la colonne : son bord gauche tombe donc a la moitie de la
     colonne moins la moitie de la mesure. Un seul endroit ou la mesure est
     ecrite -- `--recit-mesure` --, et le filet la suit sans qu'on y revienne. */
  left: calc(50% - var(--recit-mesure) / 2);
  width: min(40px, var(--recit-mesure));
  /* Deux pixels et du noir franc, demandes le 14 octobre. Sur quarante
     pixels, le filet gris du site passait inapercu ; celui-ci se lit comme
     une ponctuation voulue et non comme un reste de bordure. */
  height: 2px;
  background: #000;
}

/* Au repos -- telephone, sans script, animations refusees -- la meme marque,
   posee au milieu de l'ecart qui separe deux rangees. Le selecteur exclut
   l'etat arme : la rangee y est dissoute par `display: contents`, et les deux
   regles se superposeraient. */
.recit:not(.is-actif) .recit__duo + .recit__duo { position: relative; }
.recit:not(.is-actif) .recit__duo + .recit__duo::before {
  content: '';
  position: absolute;
  top: calc(var(--s7) / -2);
  /* Au repos la colonne n'est plus centree : le texte part du bord, le filet
     aussi. C'est le bord gauche unique du site qui reprend ses droits. */
  left: 0;
  width: min(40px, 100%);
  /* Meme trait qu'a l'etat arme : deux pixels, noir franc. */
  height: 2px;
  background: #000;
}

/* Le silence entre deux chapitres. Il est dit en hauteur d'écran, et non en
   pixels : c'est un temps de lecture, pas une marge, et il doit valoir la
   même pause sur un portable que sur un grand écran. Posé en marge haute
   seulement, pour ne pas décaler le premier chapitre du haut de l'écran.
   La course s'allonge d'autant, le script la remesure d'elle-même. */
.recit.is-actif .recit__chapitre { --recit-entre: 30vh; }
/* Le meme silence ouvre le recit : le premier chapitre n'arrive pas deja
   installe, il monte en place. La course en tient compte -- voir le module 16,
   qui mesure desormais depuis le haut de l'ecran et non depuis le premier
   chapitre, sans quoi le dernier serait rogne de cette meme hauteur. */
.recit.is-actif .recit__chapitre { margin-top: var(--recit-entre); }

/* --- repère de recette ---------------------------------------------------

   Trois gris très voisins, pris autour de `--ph`, pour qu'on voie le train
   d'images bouger tant qu'aucune photographie n'est arrivée. Ce ne sont pas
   des couleurs de la charte : elles n'existent que sous un cadre vide, et
   disparaissent d'elles-mêmes dès qu'une image se pose par-dessus. À retirer
   le jour où les trois photographies sont livrées. */
.recit__duo:nth-child(1) .recit__vue .ph { --ph: #efefef; }
.recit__duo:nth-child(2) .recit__vue .ph { --ph: #e4e4e4; }
.recit__duo:nth-child(3) .recit__vue .ph { --ph: #d8d8d8; }

/* --- 47. Une section sur fond noir ----------------------------------

   Rien n'est repeint un par un : la section redéfinit les jetons de couleur,
   et tout ce qu'elle contient suit. Un filet reste `var(--line)`, un texte
   secondaire reste `var(--ink-soft)` ; seules leurs valeurs changent. C'est
   la raison d'être d'une palette nommée par rôle plutôt que par teinte — et
   la seule façon d'inverser une section sans oublier la moitié de ses règles.

   Les valeurs ne sont pas les complémentaires arithmétiques des claires : un
   gris à 0,45 de luminance n'a pas le même poids sur noir que sur blanc.
   Elles sont choisies pour tenir les mêmes rapports de contraste dans l'autre
   sens — `--ink-soft` donne 7,0:1 sur noir là où il donnait 4,9:1 sur blanc,
   et le filet se détache autant du fond qu'un #e4e4e4 sur du papier. */

.block--sombre {
  --paper:       #000000;
  --ink:         #ffffff;
  --ink-soft:    #a3a3a3;   /* texte secondaire — 7,0:1 sur noir */
  --ink-faint:   #7a7a7a;   /* cartels et mentions, jamais porteur seul */
  --line:        #2b2b2b;   /* filet ordinaire */
  --line-strong: #4a4a4a;   /* filet appuyé */
  --ph:          #151515;   /* fond d'un média non encore produit */
  --ph-ink:      #6e6e6e;
  --tag:         #5a5a5a;

  --bg:      var(--paper);
  --surface: var(--paper);
  --accent:  var(--ink);
  --note:    var(--ink);

  background: var(--paper);
  color: var(--ink);
}

/* --- 48. Le savoir-faire en accordéon -----------------------------------

   Cinq disciplines, cinq titres, et le détail sous le titre qu'on choisit
   d'ouvrir. La colonne de gauche ne change pas : c'est la droite qui se replie.

   Le repli est écrit en `details`/`summary`. Deux raisons : sans script, le
   dépliement fonctionne quand même, au clavier comme à la souris ; et
   l'attribut `name`, partagé par les cinq, suffit à n'en laisser qu'un ouvert
   à la fois, sans une ligne de JavaScript. Le module 17 ne fait que rattraper
   les navigateurs qui ne connaissent pas encore cet attribut. */

/* Ni colonne figee, ni colonne qui file : les deux se tiennent cote a cote,
   calees sur la meme ligne de depart, et c'est `block--full` qui centre
   l'ensemble dans l'ecran. Le collage de la tete n'avait de sens que lorsque
   la liste etait longue et defilait sous elle ; repliee en accordeon, elle ne
   defile plus, et une tete collante n'aurait plus rien a accompagner.

   `align-items: start` est deja la regle de `.duo` au-dela de 1000 px ; on la
   redit ici pour qu'elle vaille a toutes les largeurs, et pour qu'on sache
   en lisant ce bloc que l'alignement par le haut est voulu. */
.duo--accordeon { align-items: start; }
.duo--accordeon .duo__head { position: static; top: auto; }

/* Le titre de cette section parle au meme niveau que celui d'une page : il
   prend donc l'allure d'un h1, sans changer de rang dans le document -- c'est
   un h2 et il le reste, pour que le plan de la page tienne debout. Les trois
   valeurs sont celles de la regle `h1, .h1`, recopiees et non heritees : un
   selecteur de classe sur un titre rendu par un bloc aurait demande de
   toucher au gabarit pour un effet purement visuel. */
.duo--accordeon .duo__head h2 {
  font-size: var(--fs-h1);
  line-height: 1.0;
  letter-spacing: -.035em;
}

/* Le cadrage de gauche monte d'un cran dans l'echelle : ni chapo -- ce n'est
   pas la voix de la Maison -- ni corps courant. On reprend `--fs-h3`, le
   degre juste au-dessus du corps, qui se trouve etre aussi celui des cinq
   titres d'en face : les deux colonnes se repondent a la meme taille. Pas de
   valeur inventee pour l'occasion ; l'echelle du site en avait une. L'interligne,
   lui, reste celui d'un paragraphe et non celui d'un titre. */
/* L'amorce repliee et la suite depliee parlent de la meme voix que le chapo
   d'origine : la regle les nomme ensemble, sans quoi le texte aurait change
   de taille en se depliant. L'outro n'a jamais ete concerne -- il vit dans
   `.duo__outro` et garde le corps courant. */
.duo--accordeon .duo__head > p,
.duo--accordeon .duo__suite > p {
  /* Le plancher de `--fs-h3` vaut 17 px -- la taille du corps : sur un ecran
     de 1280 le cadrage retombait donc exactement au corps, et l'ecart voulu
     disparaissait. Ce plancher convient a un titre, qui se distingue par
     autre chose que sa taille ; pas a un paragraphe qui doit se lire un cran
     au-dessus du texte courant. On lui en donne un a lui. */
  font-size: max(18px, var(--fs-h3));
  line-height: 1.55;
  letter-spacing: -.008em;
}

.duo--accordeon .duo__suite > :first-child { margin-top: 0; }

/* Un lien, pas un bouton : il ouvre un texte, il ne declenche rien. C'est
   pourtant un `button` dans le balisage -- le seul element qui s'annonce
   comme actionnable aux outils d'assistance sans promettre une destination
   qui n'existe pas. Le dessin, lui, dit le lien. */
.duo--accordeon .duo__plus {
  display: inline-block;
  margin-top: var(--s2);
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  font-size: var(--fs-small);
  letter-spacing: .02em;
  color: var(--ink);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: .35em;
  text-decoration-thickness: 1px;
}
.duo--accordeon .duo__plus:hover { color: var(--ink-soft); }
.duo--accordeon .duo__plus:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }

.duo--accordeon .duo__item { margin: 0; border-top: 1px solid var(--line); }
.duo--accordeon .duo__item:last-of-type { border-bottom: 1px solid var(--line); }

.duo--accordeon summary {
  /* Le chevron du navigateur n'appartient pas à la Maison. */
  list-style: none;
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s3);
  padding: var(--s3) 0;
  cursor: pointer;
  transition: color .2s ease;
}
.duo--accordeon summary::-webkit-details-marker { display: none; }
.duo--accordeon summary h3 { margin: 0; }
.duo--accordeon summary:hover h3 { color: var(--ink-soft); }
/* Au clavier, le repli doit se voir sans être cerné d'un halo du système. */
.duo--accordeon summary:focus-visible { outline: 1px solid var(--ink); outline-offset: 4px; }

/* Le signe, en cartel : un trait vertical qu'on retire quand c'est ouvert.
   Pas de glyphe « + » ni « – » : leurs chasses diffèrent et la colonne
   tremblerait d'un état à l'autre. */
.duo--accordeon summary::after {
  content: '';
  flex: 0 0 auto;
  width: 11px; height: 11px;
  align-self: center;
  background:
    linear-gradient(var(--ink), var(--ink)) center / 11px 1px no-repeat,
    linear-gradient(var(--ink), var(--ink)) center / 1px 11px no-repeat;
  transition: transform .25s ease;
}
.duo--accordeon .duo__item[open] summary::after {
  /* Un huitieme de tour, et les deux barres font une croix : le signe qui
     ouvre devient celui qui ferme, sans changer de dessin. */
  transform: rotate(45deg);
}

.duo--accordeon .duo__repli { padding-bottom: var(--s4); }
.duo--accordeon .duo__repli p:last-child { margin-bottom: 0; }

/* Ni ouverture ni fermeture animees : le texte est la, ou il n'y est pas.
   Seul le signe tourne. Rien d'autre dans la page ne se deplace, la hauteur
   etant reservee d'avance -- une animation n'aurait rien eu a accompagner. */

/* Le mot de la fin a quitte la liste pour la tete : il clot la colonne de
   gauche, sous le chapeau. Pas de filet : il prolonge le chapeau au lieu de
   s'en detacher, et l'ecart suffit a le distinguer. Il n'est plus du texte de
   volet, il ne prend donc pas la couleur attenuee des volets. */
.duo--accordeon .duo__head .duo__outro {
  margin-top: var(--s5);
  padding-top: 0;
  border-top: 0;
}

/* La hauteur de la liste est posee par le module 17, qui reserve d'avance la
   place du volet le plus long. La colonne de gauche cesse ainsi de dependre
   de ce qui est ouvert a droite : la section garde la meme hauteur, ouverte
   ou fermee, et rien ne bouge sous les yeux du lecteur.

   Sans script, rien n'est reserve et l'accordeon se comporte comme un
   accordeon ordinaire -- la page s'allonge a l'ouverture. C'est moins bien,
   ce n'est pas casse. */
.duo--accordeon .duo__list { min-height: var(--duo-reserve, 0px); }

/* Le mot de la fin a allonge la colonne de gauche de pres de 230 px, et la
   section d'une alternee -- 136 px de marge en haut comme en bas -- cessait
   alors de tenir dans un ecran court : 1 084 px pour 900. Ici la marge
   devient un plancher et non un cadre. Rien n'est perdu : `block--full`
   centre son contenu, et rend donc l'air des que l'ecran en a.

   La marge haute porte en plus la hauteur de l'en-tete. L'en-tete flotte
   au-dessus du contenu sans l'ecarter : la zone reellement libre commence
   donc 76 px plus bas que la fenetre, et son centre optique est 38 px sous
   le centre geometrique. Un contenu centre dans la fenetre entiere passe
   d'autant trop haut -- c'est ce qui se voyait sur un 1920x1080.

   L'ecart entre les deux marges vaut exactement `--header-h` : le centrage
   se decale donc de sa moitie, ni plus ni moins. Changer la hauteur de
   l'en-tete corrige le calage tout seul. */
.block--full:has(.duo--accordeon) {
  padding-top: calc(var(--s3) + var(--header-h));
  padding-bottom: var(--s3);
}

/* Sur telephone, la section cesse de tenir l'ecran.

   Deux raisons, et la meme conclusion. D'abord les deux colonnes s'empilent :
   la hauteur du contenu depasse de toute facon l'ecran, et `min-height` ne
   fait plus qu'ajouter du vide sous un bloc deja trop long. Ensuite la
   reserve de hauteur -- celle qui empeche la colonne de gauche de sauter
   quand on ouvre un volet -- n'a plus rien a stabiliser une fois les colonnes
   empilees, alors qu'un volet replie sur une colonne etroite se deplie sur
   bien plus de lignes : la reserve y valait presque un ecran de vide.

   Le fond noir, lui, ne bouge pas. C'est la hauteur imposee qui tombe, pas le
   traitement de la section. */
@media (max-width: 899px) {
  .block--full:has(.duo--accordeon) {
    min-height: 0;
    display: block;
    padding-top: calc(var(--block-pad) + var(--header-h));
    padding-bottom: var(--block-pad);
  }
  .duo--accordeon .duo__list { --duo-reserve: 0px; min-height: 0; }
}

/* ===========================================================================
   49. Le récit, vu depuis l'éditeur
   Comme le bouton d'accompagnement, son rendu réel ne se laisse pas montrer
   ici : il est collant et piloté au défilement. Le bloc dessine à la place la
   liste de ses chapitres, qui est ce qu'on relit en écrivant. Ces règles ne
   servent qu'à l'éditeur.
   ========================================================================= */
.mr-recit-apercu {
  padding: var(--s3);
  border: 1px dashed var(--line);
  background: var(--ph);
}
.mr-recit-apercu__note {
  margin: 0 0 var(--s2);
  font: 500 var(--fs-cartel-s)/1.6 var(--mono);
  letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint);
}
.mr-recit-apercu__liste { margin: 0; padding-left: 1.4em; }
.mr-recit-apercu__liste li { margin: 0 0 .3em; }

/* ==========================================================================
   29. Le bandeau de consentement
   Posé en bas, dans la typographie du site, sans ombre ni angle arrondi. Deux
   boutons de même poids : refuser doit être aussi simple qu'accepter, et pas
   seulement en droit. Il ne masque jamais le contenu — on peut lire le site
   sans avoir répondu, et rien n'est alors mesuré.
   ========================================================================== */
.consentbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 9000;
  background: var(--paper); border-top: 1px solid var(--ink);
}
.consentbar__inner {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--s3); padding-top: var(--s3); padding-bottom: var(--s3);
}
.consentbar__text {
  margin: 0; max-width: 62ch; font-size: 14px; line-height: 1.55; color: var(--ink-soft);
}
.consentbar__text a { color: var(--ink); }
.consentbar__actions { display: flex; gap: var(--s2); flex-shrink: 0; }
.consentbar__actions .btn { font-size: 13px; padding: 10px var(--s3); }

@media (max-width: 700px) {
  .consentbar__inner { flex-direction: column; align-items: stretch; }
  .consentbar__actions .btn { flex: 1 1 0; justify-content: center; }
}

/* Tant que le visiteur n'a pas répondu, on réserve la place du bandeau sous le
   pied de page : sans cela, il recouvre les dernières lignes du site. */
html.mr-ask-consent body { padding-bottom: 120px; }
@media (max-width: 700px) { html.mr-ask-consent body { padding-bottom: 190px; } }

/* ==========================================================================
   30. Les échantillons de palette
   Une case de choix qui porte une image la montre en premier et large : sur
   une palette on regarde la couleur, puis on lit son nom. L'image occupe
   toute la largeur de la case, bord à bord du filet, et le texte se range
   dessous — pas d'encadré dans l'encadré.
   ========================================================================== */
.opt-card--swatch { padding: 0 0 14px; }
.opt-card--swatch .opt-card__body { gap: 0; }

.opt-card__swatch { display: block; line-height: 0; }
.opt-card__swatch img {
  display: block; width: 100%; height: auto;
  aspect-ratio: 1 / 1; object-fit: cover;
}

/* Le nom retrouve la respiration que le retrait du padding lui a prise. */
.opt-card--swatch .opt-card__title,
.opt-card--swatch .opt-card__note { padding: 0 14px; }
.opt-card--swatch .opt-card__title { margin-top: 12px; }

/* Une option fermée par le niveau retenu : l'image s'éteint avec le reste. */
.opt-card--swatch[data-origin="0"] .opt-card__swatch img { opacity: .45; }

/* Les matières de sellerie ne sont pas carrées — leur grain se lit mieux en
   bandeau qu'en vignette. */
.opts--pairs .opt-card--swatch .opt-card__swatch img { aspect-ratio: 4 / 3; }

/* ==========================================================================
   31. Le rendu dans la scène du configurateur
   Tant qu'aucune photographie n'existe, le cadre décrit le rendu attendu.
   Dès qu'une image le remplace, le cadre s'efface : plus de filet, plus de
   diagonales, plus de cartel — l'image occupe toute la place.
   ========================================================================== */
.cfgx__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  /* L'image remplace le cadre : elle passe devant les diagonales du
     placeholder, qui sont peintes en ::after. */
  z-index: 1;
}

/* Une teinte en remplace une autre sans clignotement : la nouvelle image est
   déjà chargée — le script précharge les vues voisines — et la transition
   masque le dernier décalage. */
.cfgx__slide.has-render .cfgx__ph { border-color: transparent; background: var(--ink); }
.cfgx__slide.has-render .cfgx__ph::after { display: none; }
.cfgx__slide.has-render .ph__label { display: none; }

/* Le fondu au changement de teinte. Il ne masque pas un chargement — le
   script attend que la nouvelle image soit décodée avant de la poser — il
   adoucit seulement la substitution, qui autrement se verrait comme une
   coupe. L'image ne descend pas à zéro : à mi-opacité, l'œil lit une
   transition, pas une disparition. */
@media (prefers-reduced-motion: no-preference) {
  .cfgx__img { transition: opacity .22s ease; }
  .cfgx__slide.is-swapping .cfgx__img { opacity: .55; transition: none; }
}

/* ===========================================================================
   32. Feuille blanche : un seul cadre
   La carte blanche n'a qu'une image. Les commandes du diaporama n'ont donc
   plus rien à parcourir : on les retire du flux plutôt que de les laisser
   visibles et inertes.
   ========================================================================= */
.cfgx__stage.is-mono .cfgx__vue,
.cfgx__stage.is-mono .cfgx__dots { display: none; }

/* ===========================================================================
   33. Commandes du diaporama : la teinte suit la photographie
   Tant qu'il n'y a pas d'image, les commandes gardent le gris du cadre de
   recette. Dès qu'une photographie est posée, elles passent au blanc, et
   basculent au noir là où le fond qu'elles recouvrent se lit clair. Chaque
   commande décide pour elle-même : une carrosserie sombre à gauche et un ciel
   clair à droite est le cas courant.
   ========================================================================= */
.cfgx__stage {
  --ctl-prev: var(--ph-ink);
  --ctl-next: var(--ph-ink);
  --ctl-dots: var(--ph-ink);
  --ctl-ombre: none;
}
.cfgx__stage.a-rendu {
  --ctl-prev: #ffffff;
  --ctl-next: #ffffff;
  --ctl-dots: #ffffff;
  /* Une ombre portée très basse : elle ne se voit pas, elle décolle le trait
     d'un fond chargé — un reflet, une arête de carrosserie. */
  --ctl-ombre: 0 1px 3px rgba(0, 0, 0, .45);
}
.cfgx__stage.a-rendu.ctl-prev-noir { --ctl-prev: #000000; }
.cfgx__stage.a-rendu.ctl-next-noir { --ctl-next: #000000; }
.cfgx__stage.a-rendu.ctl-dots-noir { --ctl-dots: #000000; }

.cfgx__vue--prev { color: var(--ctl-prev); }
.cfgx__vue--next { color: var(--ctl-next); }
.cfgx__stage.a-rendu .cfgx__vue { text-shadow: var(--ctl-ombre); }
.cfgx__stage.a-rendu .cfgx__vue--prev:hover { color: var(--ctl-prev); background: rgba(127, 127, 127, .22); }
.cfgx__stage.a-rendu .cfgx__vue--next:hover { color: var(--ctl-next); background: rgba(127, 127, 127, .22); }

.cfgx__stage .cfgx__dot { border-color: var(--ctl-dots); }
.cfgx__stage .cfgx__dot.is-on { background: var(--ctl-dots); }
.cfgx__stage.a-rendu .cfgx__dot { box-shadow: var(--ctl-ombre); }
.cfgx__stage.a-rendu .cfgx__dot:hover { background: var(--ctl-dots); opacity: .55; }

/* Un fond clair rend l'ombre noire sale : on la retourne. */
.cfgx__stage.a-rendu.ctl-prev-noir .cfgx__vue--prev,
.cfgx__stage.a-rendu.ctl-next-noir .cfgx__vue--next { text-shadow: 0 1px 3px rgba(255, 255, 255, .5); }
.cfgx__stage.a-rendu.ctl-dots-noir .cfgx__dot { box-shadow: 0 1px 3px rgba(255, 255, 255, .5); }

/* ===========================================================================
   34. Les photographies dans leur cadre
   La maquette v4 n'avait aucune image : ses cadres n'étaient que des
   rectangles gris portant un cartel. `.ph__img` n'avait donc jamais eu de
   règle, et une photographie y arrivait comme un enfant de `flex` ordinaire —
   rangée en bas à gauche, cernée du rembourrage du cartel, et étirée, parce
   que `max-width` rognait sa largeur pendant que l'attribut `height` de
   WordPress tenait sa hauteur.

   Le cadre décide désormais du format, l'image le remplit sans se déformer,
   et son recadrage se fait au centre. C'est ce que faisait déjà l'image du
   configurateur ; la règle est la même, étendue à tout le site.
   ========================================================================= */
.ph__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  /* Au-dessus du fond du cadre, sous le cartel et les commandes. */
  z-index: 1;
}

/* Un cadre qui porte sa photographie n'a plus de cartel à montrer : le cartel
   énonçait la commande photo, elle est arrivée. */
.ph--img .ph__label { display: none; }

/* Le hero garde sa hauteur d'écran, pas celle de sa photographie. Sans image,
   c'était déjà le cas ; avec une image posée en absolu, le cadre ne peut plus
   être poussé au-delà. La règle est écrite pour dire l'intention, et pour
   qu'un contenu futur ne la défasse pas. */
.hero .ph.r-hero { max-height: 100vh; max-height: 100svh; }

/* ===========================================================================
   35. L'amorce du hero
   Le cadre porte en fond la petite version de sa photographie — celle que la
   vignette de la page précédente a déjà servie, donc déjà en cache. Elle se
   peint à la première trame, et la grande se pose par-dessus dès qu'elle est
   décodée : le fond gris n'a plus l'occasion d'apparaître.

   Le recadrage est identique à celui de `.ph__img`, sans quoi la substitution
   se verrait comme un saut.
   ========================================================================= */
.ph--amorce { background-size: cover; background-position: center; }

/* La boîte de la transition n'a de fond gris que sans photographie : sous une
   image, ce fond était ce qu'on apercevait au clic, le temps d'une trame. */
.thumbfull__box--img { background: transparent; }

/* ===========================================================================
   36. La colonne de droite
   Une fiche technique peut se ranger face au texte plutôt que sous lui :
   la colonne de lecture occupe la moitié gauche — c'est `.c-text` —, la liste
   prend la moitié droite, sur la même rangée. Sous la grille à douze colonnes,
   les deux se replient l'une sous l'autre.
   ========================================================================= */
@media (min-width: 1000px) {
  .c-droite { grid-column: 7 / span 6; }
}

/* L'accordeon d'une fiche en groupes peut lui aussi tenir cette moitie. Sa
   colonne de libelles, taillee pour la pleine largeur, y serait trop large :
   elle se resserre comme celle d'une liste en demi-page. */
.c-droite .specsheet .specs > div { grid-template-columns: 1fr; }
@media (min-width: 1000px) {
  .c-droite .specsheet .specs > div {
    grid-template-columns: minmax(0, 14em) 1fr; gap: var(--s3);
  }
}

/* ===========================================================================
   37. Le titre de vignette comme lien
   Quand la ligne « Découvrir … » est retirée, la vignette perdrait son seul
   lien. Le titre le reprend : même adresse, mêmes attributs, même mécanique
   d'agrandissement. Il ne doit pas pour autant prendre l'allure d'un lien —
   c'est toujours un titre, et le survol de la vignette dit déjà qu'on peut
   cliquer. On annule donc ce que `.link` pose, sans toucher à `.link` ailleurs.
   ========================================================================= */
.card h3 .card__titre {
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: none;
  text-decoration: none;
  display: inline;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
}
.card h3 .card__titre::after,
.card h3 .card__titre::before { content: none; }
.card:hover h3 .card__titre,
.card h3 .card__titre:hover { color: inherit; }
/* Le parcours au clavier doit rester visible : c'est le seul lien de la carte. */
.card h3 .card__titre:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
}

/* ===========================================================================
   38. La ligne « Découvrir » colle à ce qui la précède
   La colonne de la vignette espace déjà ses éléments de 16 px. Les 24 px que
   `.card__body .link` ajoutait par-dessus faisaient 40 px, et détachaient la
   ligne au point qu'elle ne semblait plus appartenir à la même vignette.
   Le bouton garde les siens : il se pose, lui, comme une action.
   ========================================================================= */
.card__body > a.link { margin-top: 0; }

/* ===========================================================================
   39. Resserrer un bloc vers celui qui le précède
   L'espace entre deux sections est la somme de deux silences : le bas de la
   première et le haut de la seconde. Quand la seconde prolonge la première
   plutôt qu'elle ne lui succède — un chapô puis ce qu'il annonce —, cette
   somme est trop longue, et `block--alt` l'allonge encore (1,44 fois).

   `block--serre` ne touche qu'au silence du haut, et le ramène à 0,4 part.
   Le bas reste entier : le bloc garde sa place vis-à-vis de ce qui le suit.
   Posé après `block--alt` et `block--air`, il l'emporte sur les deux.
   ========================================================================= */
.block--serre { padding-top: calc(var(--block-pad) * .4); }

/* ===========================================================================
   40. Le menu mobile occupe l'écran entier
   Il s'arrêtait sous l'en-tête, et laissait voir une bande de page au-dessus
   de lui : on lisait deux choses à la fois. Il part désormais du bord haut et
   passe SOUS l'en-tête, qui reste au-dessus (z-index 800) — la marque garde
   donc sa place, et le bouton avec elle. Le panneau se décale d'autant par sa
   marge intérieure, pour que la première porte ne vienne pas sous le logo.
   ========================================================================= */
@media (max-width: 899px) {
  .nav {
    inset: 0;
    /* Le menu est un enfant de l'en-tête, qui est posé et porte un z-index :
       le rang se joue donc À L'INTÉRIEUR de l'en-tête, pas vis-à-vis de lui.
       Un panneau à `inset: 0` recouvre sinon la marque et le bouton — c'est
       ce que le banc a relevé. Les deux repassent devant juste en dessous.

       Ils y arrivent par leur seul z-index, sans `position` : ce sont des
       éléments de la boîte flexible de l'en-tête, et cela suffit. Poser
       `position: relative` sur `.logo` ferait d'elle le repère du monogramme
       absolu du mot-symbole, et décalerait l'animation de l'accueil. */
    z-index: 1;
    padding-top: calc(var(--header-h) + var(--s4));
    /* Les téléphones réservent une bande en bas pour leur propre barre. */
    padding-bottom: calc(var(--s4) + env(safe-area-inset-bottom, 0px));
  }

  /* Le mot « Menu » et la croix sont le même bouton, dans ses deux états.
     C'est `aria-expanded` qui tranche : l'attribut que le script tient déjà
     à jour pour les lecteurs d'écran sert aussi à l'œil, et les deux ne
     peuvent donc pas se contredire. */
  .header__inner > .logo,
  .header__inner > .burger { z-index: 2; }

  .burger__fermer { display: none; }
  .burger[aria-expanded="true"] .burger__ouvrir { display: none; }
  .burger[aria-expanded="true"] .burger__fermer { display: inline; }
  /* La croix n'est plus un mot : elle se tient sur un carré, pas sur une
     ligne de texte. */
  .burger[aria-expanded="true"] {
    font-size: 17px; line-height: 1;
    padding: 7px 11px;
  }
}

/* Ouvert, le panneau est blanc. Un en-tête posé en blanc sur un média — ce
   qu'il est sur l'accueil — y deviendrait invisible : on le ramène à l'encre,
   et on retire le voile sombre qui l'accompagnait. */
body.nav-ouvert .header,
body.nav-ouvert .header.is-over-media { color: var(--ink); }
body.nav-ouvert .header::before { display: none; }

/* ===========================================================================
   41. Les deux boutons d'accroche tiennent sur une ligne, sur téléphone
   « Les Machines » et « Galerie » sont les deux portes d'entrée du site : les
   voir l'une sous l'autre en fait une principale et une accessoire, ce qu'elles
   ne sont pas. Elles partagent donc la rangée.

   Le partage se fait en `flex: 1 1 0` — parts égales de la place disponible,
   et non de la largeur du texte : les deux rectangles ont alors la même
   largeur, ce qui est la seule façon de les lire comme un couple. Le retrait
   horizontal se resserre pour que le mot le plus long tienne sans se couper.
   La hauteur, elle, ne bouge pas : c'est la surface que vise le pouce.
   ========================================================================= */
@media (max-width: 599px) {
  /* Le retour à la ligne reste permis : l'ancre « Découvrir » partage parfois
     la rangée, et doit pouvoir passer dessous. Ce sont les deux boutons qui
     tiennent la ligne, par leur base nulle — ils n'occupent donc jamais
     assez de place pour se repousser l'un l'autre. */
  .ctas { flex-wrap: wrap; gap: var(--s1); }
  .ctas > .btn {
    flex: 1 1 0;
    min-width: 0;
    padding-left: var(--s2);
    padding-right: var(--s2);
    gap: 8px;
    white-space: nowrap;
  }
  /* L'ancre « Découvrir » n'appartient pas au couple : si elle est de la
     rangée, elle la fait déborder. Elle reprend sa ligne. */
  .ctas .hero__anchor { flex: 0 0 100%; margin-left: 0; margin-top: var(--s2); }
}

/* ===========================================================================
   42. Les diaporamas se prennent à la main
   Le rail suit le doigt, puis se range ou revient. Le script pose `pan-y`
   lui-même — il ne faut pas que la règle s'applique à un diaporama qu'il
   n'aurait pas armé, sans quoi la page cesserait de défiler là où rien ne
   glisserait.
   ========================================================================= */
.slideshow__viewport.is-glissable,
.cfgx__slider.is-glissable { cursor: grab; }
.slideshow__viewport.is-glissant,
.cfgx__slider.is-glissant { cursor: grabbing; }

/* Pendant le geste, rien ne doit se sélectionner ni se laisser traîner :
   une image prise à la main n'est pas un texte qu'on surligne. */
.slideshow__viewport.is-glissant,
.cfgx__slider.is-glissant { user-select: none; -webkit-user-select: none; }
.slideshow__viewport.is-glissable img,
.cfgx__slider.is-glissable img { -webkit-user-drag: none; user-select: none; }

/* Au doigt, la souris n'existe pas : le curseur n'a rien à annoncer. */
@media (hover: none) {
  .slideshow__viewport.is-glissable,
  .cfgx__slider.is-glissable { cursor: auto; }
}

/* ===========================================================================
   43. Le déroulé de méthode : des étapes rangées, pas espacées
   Les 136 px qui séparaient deux étapes tenaient lieu de séparation : c'est le
   vide qui disait « autre étape ». Un filet le dit mieux, et en moins de place.
   On passe donc à 40 px de part et d'autre d'un filet — 80 px au lieu de 136,
   et une frontière qu'on voit au lieu d'une qu'on devine.

   Le filet est le filet ordinaire de la Maison, pas un gris de circonstance :
   c'est le même que sous les rangées de vignettes et de fiches techniques.

   Il ne se pose qu'ENTRE deux étapes — `+` ne touche jamais la première —, et
   rien ne ferme la dernière : la liste s'arrête, elle ne se clôt pas.

   La colonne de gauche reste fixe pendant qu'on parcourt la droite ; six
   étapes à 80 px lui laissent encore largement de quoi tenir.

   La règle vise la variante NUMÉROTÉE seule. Le même gabarit sert aussi le
   savoir-faire de La Maison, qui n'est pas une méthode : rien n'y est ordonné,
   et une liste sans ordre n'appelle pas d'étapes rangées. Elle garde donc son
   espacement.
   ========================================================================= */
.duo__list--num .duo__item + .duo__item {
  margin-top: var(--s4);
  padding-top: var(--s4);
  border-top: 1px solid var(--line);
}

/* ===========================================================================
   44. Le bouton d'accompagnement, vu depuis l'éditeur
   Son rendu réel est `position: fixed` et naît caché : dans l'éditeur il ne
   montrerait rien, ou flotterait au-dessus de la surface d'écriture. Le bloc
   dessine donc à la place une maquette dans le flux, qui dit ce que la page
   publique affichera. Ces règles ne servent qu'à l'éditeur — la feuille y est
   chargée par `add_editor_style`, et aucune page publique ne porte cette
   classe.
   ========================================================================= */
.mr-dock-apercu {
  display: flex; align-items: center; gap: var(--s3);
  flex-wrap: wrap;
  padding: var(--s3);
  border: 1px dashed var(--line);
  background: var(--ph);
}
.mr-dock-apercu__note {
  flex: 1 1 18ch; margin: 0;
  font: 500 var(--fs-cartel-s)/1.6 var(--mono);
  letter-spacing: .04em; color: var(--ink-faint);
}

/* --- 50. Le diptyque des Machines ---------------------------------------

   Deux portes, cote a cote, et rien d'autre a l'ecran. La page des Machines
   ne presente pas deux produits : elle pose un choix, et ce choix merite la
   hauteur entiere. Le texte vient sur l'image comme sur un hero -- pas sous
   elle dans une carte -- parce qu'une carte invite a comparer des objets, et
   qu'il s'agit ici d'entrer quelque part.

   C'est le volet ENTIER qui est l'ancre. Le libelle n'est donc plus un lien
   dans le texte mais une marque posee dessus : une ancre dans une ancre
   serait du balisage invalide, et deux cibles de clic pour une seule
   destination est une hesitation que l'oeil paie. */

.diptyque { padding-block: 0; }

.diptyque__piste {
  display: flex;
  min-height: 100vh; min-height: 100svh;
}

.diptyque__volet {
  /* `0 0` et non `1 1` : c'est la base qui doit decider seule de la largeur.
     Avec une part de croissance, les deux volets se partageaient le reste et
     la bascule 60/40 ne se voyait plus. */
  flex: 0 0 50%;
  position: relative;
  display: flex;
  align-items: flex-end;
  overflow: clip;
  /* Du blanc franc, et non `--paper` : le texte ne repose pas sur le papier
     du site mais sur une photographie, ou le jeton ne veut plus rien dire. */
  color: #fff;
  text-decoration: none;
  transition: flex-basis .6s ease-in-out;
}

/* L'image remplit le volet quoi qu'il arrive a sa largeur : c'est la moitie
   d'ecran qui dicte le cadre, pas le rapport de la photographie. */
.diptyque__volet .ph {
  position: absolute;
  inset: 0;
  height: 100%;
  aspect-ratio: auto;
}

/* Le voile. Une photographie ne garantit pas la lisibilite d'un texte pose
   dessus : le degrade assombrit le bas, la ou le texte se trouve, et laisse
   le haut intact. Il est pose au-dessus du cadre et sous le texte. */
.diptyque__volet::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to top,
              rgba(0, 0, 0, .74) 0%, rgba(0, 0, 0, .38) 38%, rgba(0, 0, 0, 0) 72%);
}

.diptyque__texte {
  position: relative;
  z-index: 2;
  padding: var(--s5) var(--gutter);
  max-width: 46ch;
}
.diptyque__texte > :first-child { margin-top: 0; }
.diptyque__texte p { color: #fff; }
/* Ecrit, et non herite du volet. Une couleur heritee est une couleur qu'une
   autre feuille peut reprendre sans qu'on s'en apercoive, et celle-ci porte
   la lisibilite du titre sur une photographie. */
.diptyque__texte h2 { color: #fff; }

/* La marque de la destination : un bouton, porte le 15 octobre. Le balisage
   reste un `span` -- le volet entier est deja l'ancre --, mais il prend la
   classe `.btn` et donc le dessin exact des boutons de la Maison.

   Deux couleurs sont reprises ici, et seulement deux : un bouton noir sur une
   photographie sombre disparaitrait. On inverse donc, comme le fait deja
   l'etat de survol de `.btn` : fond blanc, texte sombre. Les valeurs sont
   ecrites en dur parce qu'elles ne repondent pas au papier du site mais a une
   image, ou les jetons ne veulent plus rien dire -- c'est la meme raison qui
   fait le texte blanc juste au-dessus. */
.diptyque__lien {
  margin-top: var(--s4);
  background: #fff;
  border-color: #fff;
  color: #111;
}

/* Le volet de droite ferre son texte a droite, et ce n'est pas une coquetterie :
   son bord gauche bouge au survol, son bord droit ne bouge jamais. Un texte
   cale a gauche glissait donc de dix pour cent de l'ecran a chaque survol,
   quand celui de gauche, lui, restait immobile. Ancre du cote qui ne bouge
   pas, il ne bouge plus.

   Seulement a partir de 900 px : en dessous les volets sont empiles, plus
   rien ne se deplace, et un texte ferre a droite n'y serait qu'une bizarrerie. */
@media (min-width: 900px) {
  .diptyque__volet:last-child .diptyque__texte {
    margin-left: auto;
    text-align: right;
  }
}

/* La bascule 60/40. Elle ne vaut qu'avec un vrai survol et de la place : sur
   un ecran tactile, `:hover` reste colle apres la frappe, et sous 900 px les
   volets sont empiles -- une largeur n'y veut plus rien dire.

   `:focus-within` double le survol : on arrive aussi sur ces deux portes a
   la tabulation, et la mise en avant doit suivre le clavier. */
@media (hover: hover) and (min-width: 900px) {
  .diptyque__piste:hover .diptyque__volet,
  .diptyque__piste:focus-within .diptyque__volet { flex-basis: 40%; }
  .diptyque__piste .diptyque__volet:hover,
  .diptyque__piste .diptyque__volet:focus-within { flex-basis: 60%; }
}

@media (prefers-reduced-motion: reduce) {
  .diptyque__volet { transition: none; }
}

/* Empiles sur telephone. Chacun garde assez de hauteur pour que la
   photographie existe, sans exiger un ecran entier par volet -- on doit
   pouvoir embrasser les deux portes d'un seul regard. */
@media (max-width: 899px) {
  .diptyque__piste { display: block; min-height: 0; }
  /* Un rapport, pas une part d'ecran. 62 % de la hauteur donnait un volet
     different sur chaque telephone, et un cadre qui ne disait rien de la
     photographie. Le 4:5 est le meme partout. La hauteur peut encore
     grandir si le texte depasse : `aspect-ratio` propose une hauteur, il ne
     l'impose pas contre le contenu. */
  .diptyque__volet { display: flex; aspect-ratio: 4 / 5; }
  .diptyque__texte { padding: var(--s4) var(--gutter); max-width: none; }
}

/* --- 51. La section qui clot la page ----------------------------------

   Le pied de page porte son propre silence -- 184 px -- et il s'ajoute a
   celui de la derniere section. Le contenu qui ferme une page a donc toujours
   plus d'air sous lui que dessus : 94 px d'un cote, 278 de l'autre. Cela ne se
   remarque pas quand la section precedente est un bloc de texte ; cela se voit
   des que c'est une image bord a bord, dont le bord franc donne la mesure.

   `block--clore` rend ce rapport symetrique : une meme part de silence des
   deux cotes, prise dans l'echelle du site, et le silence du pied de page
   neutralise pour ne pas s'y ajouter. La regle nomme explicitement le cas --
   une derniere section qui porte cette classe -- et laisse toutes les autres
   pages a leur rythme. */

.block--clore { padding-block: var(--s7); }
main:has(> section:last-child.block--clore) + .footer { margin-top: 0; }

@media (max-width: 999px) {
  .block--clore { padding-block: var(--s6); }
}

/* --- 52. La liste des programmes ---------------------------------------

   Trois programmes ne sont pas une galerie. Une vignette invite a comparer des
   objets ; ici il s'agit de choisir une porte, et trois portes se lisent comme
   un sommaire -- le nom, la base, l'ecran ou l'on compose la sienne.

   Les filets font deux pixels et sont noirs : sur une ligne qui traverse la
   page, le filet gris du site ne tiendrait pas le rythme. La liste est fermee
   des deux cotes, comme l'accordeon du savoir-faire : une liste qui commence
   sans trait semble commencer au milieu. */

/* Les trois colonnes sont celles de la LISTE, pas celles de chaque ligne.

   Ecrit ligne par ligne, chaque rangee resolvait ses propres largeurs : la
   troisieme colonne se dimensionne sur son contenu, et « Configurer votre
   Cavale » ne fait pas la largeur de « Configurer votre 911 ». Les bases se
   retrouvaient donc decalees d'une ligne a l'autre, chacune alignee sur rien.

   `subgrid` fait partager a toutes les rangees les pistes de leur parent :
   une seule mesure pour la liste entiere. La declaration ligne par ligne reste
   en repli pour les navigateurs qui l'ignorent -- la mise en page y est
   seulement moins bien reglee, elle n'est pas cassee. */
.progs__ligne {
  display: grid;
  grid-template-columns: 1.1fr 1fr auto;
  align-items: baseline;
  column-gap: var(--s5);
  padding: var(--s4) 0;
  border-top: 2px solid #000;
}
.progs__ligne:last-child { border-bottom: 2px solid #000; }

@supports (grid-template-columns: subgrid) {
  .progs { display: grid; grid-template-columns: 1.1fr 1fr auto; column-gap: var(--s5); }
  .progs__ligne { grid-column: 1 / -1; grid-template-columns: subgrid; }
}

.progs__nom { margin: 0; font-size: var(--fs-h2); line-height: 1.1; letter-spacing: -.028em; }
.progs__nom a { text-decoration: none; }
.progs__nom a:hover { text-decoration: underline; text-underline-offset: .22em; }

/* La base n'est pas un sous-titre : c'est un cartel, et elle en prend la
   langue -- capitales, petite chasse, interlettrage ouvert. */
.progs__base {
  margin: 0;
  font: var(--cartel);
  letter-spacing: var(--cartel-ls);
  text-transform: uppercase;
  color: var(--ink-soft);
}

.progs__action { margin: 0; justify-self: end; text-align: right; }

/* Quand la troisieme colonne porte une date et non un lien, elle parle la
   langue des cartels, comme la base : ce n'est pas une action, c'est un fait. */
.progs__date {
  font: var(--cartel);
  letter-spacing: var(--cartel-ls);
  text-transform: uppercase;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

/* La ligne entiere mene a la fiche, mais ce n'est pas une ancre : elle en
   contient deja deux, et une ancre dans une ancre n'est pas du balisage
   valide. C'est le module 18 qui ouvre la page, et c'est lui aussi qui pose
   cette classe -- sans script, rien ne laisse croire que la ligne est
   cliquable, et seul le nom l'est, comme il se doit. */
.progs__ligne.is-cliquable { cursor: pointer; }

/* La photographie au survol.

   Elle ne prend aucune place dans la liste : sortie du flux, posee pres du
   curseur par le module 19, qui ecrit `--x` et `--y` en pixels. La liste garde
   donc exactement la meme mesure qu'une liste sans images.

   Le deplacement n'est PAS anime : une image qui poursuit le curseur avec du
   retard donne le sentiment d'un decor qui traine. Seules l'apparition et la
   disparition le sont -- une opacite et un soupcon d'echelle, pour que l'image
   arrive au lieu de surgir.

   `visibility` est retardee a la disparition : sans elle, le cadre reste
   cliquable une fraction de seconde apres s'etre efface, et il intercepte la
   ligne suivante. `pointer-events: none` l'en empeche deja, mais deux
   precautions valent mieux qu'un rattrapage. */
.progs__vue {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 700;
  width: clamp(260px, 26vw, 420px);
  margin: 0;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transform: translate3d(var(--x, 0px), var(--y, 0px), 0) scale(.985);
  transition: opacity .2s ease, scale .2s ease, visibility 0s linear .2s;
}
.progs__ligne.is-survol .progs__vue {
  opacity: 1;
  visibility: visible;
  transform: translate3d(var(--x, 0px), var(--y, 0px), 0) scale(1);
  transition-delay: 0s, 0s, 0s;
}

/* Pas de survol, pas d'image flottante : sur un ecran tactile large elle
   resterait collee apres la frappe. Sous 900 px, en revanche, elle ne
   disparait pas -- elle redescend dans le flux et devient la vignette. */
@media (hover: none) and (min-width: 900px) {
  .progs__vue { display: none; }
}

/* Sur telephone, trois colonnes deviennent trois lignes : la mesure ne permet
   pas de les tenir cote a cote sans couper les mots. */
/* --- Sur telephone, la liste redevient des vignettes ----------------------

   Une liste de trois colonnes suppose de la largeur ; sous 900 px il n'y en a
   pas, et trois lignes de texte empilees entre deux filets ne disent plus rien
   de ce qu'elles presentent. On retrouve donc la vignette : une image, un nom,
   une base, un lien.

   Rien n'est duplique pour autant. Le gabarit n'emet qu'une seule fois chaque
   programme ; c'est la MEME matiere qui se reorganise -- le cadre du survol
   redescend dans le flux et devient l'image de la vignette. Emettre deux
   balisages, un par format, aurait double le nombre d'images a charger sur
   l'appareil qui en a le moins les moyens. */
@media (max-width: 899px) {
  .progs { display: block; }

  .progs__ligne {
    /* En colonne, et non en bloc : le cadre est le DERNIER enfant dans le
       balisage -- sa place naturelle quand il flotte -- alors qu'une vignette
       le montre en premier. `order` le remonte sans toucher a l'ordre de
       lecture, qui lui reste bon : le nom d'abord, l'image etant decorative
       et deja masquee aux outils d'assistance. */
    display: flex;
    flex-direction: column;
    /* `baseline` vient de la liste : en colonne, il fait retrecir chaque
       element a son contenu, et l'image n'occupait plus que sa largeur
       naturelle. On rend donc la pleine mesure a la vignette. */
    align-items: stretch;
    grid-column: auto;
    grid-template-columns: none;
    /* Une vignette ne se separe pas d'une autre par un filet : c'est le blanc
       qui les detache. */
    border-top: 0;
    padding: 0;
    margin-bottom: var(--s6);
  }
  .progs__ligne:last-child { border-bottom: 0; margin-bottom: 0; }

  /* Le cadre reprend sa place : plus de position fixe, plus d'echelle, plus
     d'attente -- il est la, au-dessus du nom. */
  .progs__vue {
    order: -1;
    position: static;
    width: auto;
    opacity: 1;
    visibility: visible;
    transform: none;
    transition: none;
    pointer-events: auto;
    margin-bottom: var(--s3);
  }

  /* Le nom reprend la taille d'un titre de vignette, et non celle d'un titre
     de section : c'est une carte, pas un chapitre. */
  .progs__nom { font-size: var(--fs-h3); line-height: 1.35; letter-spacing: -.012em; }
  .progs__base { margin-top: var(--s1); }
  .progs__action { justify-self: start; text-align: left; margin-top: var(--s3); }
}

/* --- 53. Le diaporama pleine page --------------------------------------

   Une galerie qui occupe l'ecran entier ne se regarde pas comme une image
   posee dans une colonne : il n'y a plus de page autour, il n'y a que la
   photographie. Tout ce qui l'accompagne passe donc PAR-DESSUS elle, et se
   retire des qu'on ne le demande pas.

   Les fleches, en particulier, quittent l'ecran. Ce ne sont plus deux boutons
   poses sur l'image mais ses deux moities, et c'est le CURSEUR qui annonce ce
   qu'un clic va faire : il devient une fleche, tournee vers la gauche ou vers
   la droite selon le cote ou l'on se trouve. Rien ne se pose donc plus sur la
   photographie, et la zone de clic est aussi large qu'elle.

   Sur un appareil tactile, rien de tout cela : on glisse l'image du doigt,
   et `:hover` y resterait colle apres la frappe. */

.diaporama-plein { padding-block: 0; }

.slideshow--plein .slideshow__viewport { height: 100vh; height: 100svh; }
/* Le rail et ses vues prennent toute la hauteur : sans cela l'image n'a aucun
   plein a remplir et retombe sur la hauteur de son cartel. */
.slideshow--plein .slideshow__track { height: 100%; }
.slideshow--plein .slideshow__slide { display: flex; height: 100%; }
/* Le cadre ne tient plus son rapport : c'est l'ecran qui le dicte, et la
   photographie s'y recadre d'elle-meme. */
.slideshow--plein .ph { width: 100%; height: 100%; aspect-ratio: auto; }

/* Un voile court en bas, pour le compteur. Comme sur l'en-tete au-dessus d'un
   media : il ne se lit pas comme un bandeau, il rattrape juste le contraste. */
.slideshow--plein .slideshow__viewport::after {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 30%;
  z-index: 1;
  background: linear-gradient(to top, rgba(0, 0, 0, .5), rgba(0, 0, 0, 0));
  pointer-events: none;
}

.slideshow--plein .slideshow__count {
  position: absolute;
  right: var(--gutter);
  bottom: var(--s4);
  margin: 0;
  z-index: 3;
  color: #fff;
}

@media (hover: hover) and (min-width: 900px) {
  .slideshow--plein .slideshow__nav {
    top: 0;
    bottom: 0;
    transform: none;
    /* Une moitie chacune : il n'y a plus de zone morte, et le curseur dit a
       chaque instant ou l'on est. */
    width: 50%;
    height: auto;
    z-index: 2;
    display: grid;
    align-items: center;
    font-size: 44px;
    color: #fff;
    background: transparent;
    /* Le glyphe reste dans le document -- il porte le nom du bouton -- mais
       il ne se montre plus : le curseur a pris sa place. Seul le clavier le
       rappelle, puisqu'il n'a pas de curseur a suivre. */
    opacity: 0;
    transition: opacity .25s ease;
  }
  .slideshow--plein .slideshow__nav:hover { background: transparent; color: #fff; }
  .slideshow--plein .slideshow__nav:focus-visible { opacity: 1; }
  .slideshow--plein .slideshow__nav--prev {
    left: 0; justify-items: start; padding-left: var(--gutter);
    cursor: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2248%22%20height=%2248%22%20viewBox=%220%200%2048%2048%22%3E%3Cpath%20d=%22M31%2013L17%2024L31%2035%22%20fill=%22none%22%20stroke=%22%23000%22%20stroke-opacity=%22.25%22%20stroke-width=%225%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22/%3E%3Cpath%20d=%22M31%2013L17%2024L31%2035%22%20fill=%22none%22%20stroke=%22%23fff%22%20stroke-opacity=%221%22%20stroke-width=%222%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22/%3E%3C/svg%3E') 24 24,
    url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAFRElEQVR42u2YS2hUZxTHf/O6M/FOJvOeTGwyOnm/fFBcaNq0tbQMgWxtIXZhF90USpGKOy1CF4WQkqIIkl2EQnFTCIgUrIpppQslEqFTUEzUYqRhbB5OMpO5p5vvltvBlKaaOynMgY+5M/DN/f/P9z/nO+dA1apWtapV0hxb4H3yIn/othG0o+w3eRlE3DaAL1/lBAzLs2wlAiZgp2WZ302whvrNUGvDJFybBNwE7FLLDXjMVVtb60mn0x7DMBz5fF62UhA7yjxfDt4VCoXcuVyuCCwAmvJ4ASipZWzkFJybJBmXAqwBPrVqGhoaanO5XCmTyYRnZmZOnDp1qhEwIpGIe51YsS01msDdgBfQgSAQB14BmhOJRA+QGh4efnthYeGWiMiDBw++BHxdXV1hoEbtd9pJoNzjPsAPhIB6oEnTtHZd13uB9IULFz4qFAp/iIg8evRo/OjRo23xeDyhyNpOoBx8DVALRIAksCMUCnUD3UD31NTUWRERwzDkxo0bJ4Ht6nQSQMBuAlbJaMA2oA6IKWDpeDzeC7RlMpnXHj58eElEZGlpaXZsbOwDIBWJRNp9Pl+jIlyrpGcLgX/SeyPQGovFdgPNIyMjhxYWFn4REXn8+PHkwMBAP9CSSCS6gZTyflA5QFOnaTt4U+8pv9/fEQwGdwPNV65cOVEsFldERLLZ7DjQA3SHw+EuYIfaE1IxY4v3y8Fbg9Wq995sNjsuIlIoFJYmJiaOAa26ru/SNK0DaKoEeJOAy6L5oAV8D9A+NDT07tzc3I8iIk+fPs0ODw+/B7TEYrE9QKuSWFzt1Sule58KuhjQ5Pf7O4GW06dPf5jP538XEZmdnf3+4MGDbwFd0Wh0L9CiMk5MBbup+YpknRrlwWQgEGgBmicnJ78QZZcvX/4a2Afsq6urexVoV7KJl4F3VSLnexSAcDKZbAK2Z7PZMyIiz549+210dPRTYL/X633T7XbvB3qBZqABCG+G5l+0FjL8fn8KYGVlZX5sbCwLuH0+n3Ntbc0oa1as66WZawMnYL15XaVSya3ruu/27ds/9fX1Jevr618/cuTIQCAQuDMxMfGrz+fTHA6HYRjGmqowS2UEzGdbJWQGcQCIe73enUAb0DU5OTlixsH169e/AvYCe4LB4C4grWQUsZQNnkrUPmYa1ZWmk16vt0XX9R6g9fz58x/n8/l5EZG7d+9+d+DAgf1AZzweN0lsB6KKxDY70+h6qTSiPNusQLYdPnz4nfn5+VsiIrlcbvr48eOD6i7YbUmn1oxkOwlr6WwlsVNdaN3RaHTvvXv3vlW38eLFixePAc3BYHCXpmnWtBqsRFp1PKf+D1vqoU6Px9MLdFy9evVzsx6ampo6q0qNznA43Gm5lessMWF7PHjKiroE0KhpWqsZvKOjo0OLi4uzqgO7NDg42Aekk8lkh4qJiN01UTmJ9XqCv+Iik8m88eTJk0nVE8ycO3fuEFAfj8fT6hQC6jQr2lJqSgoBS1yYVWoX0D09PX1GRKRYLK7cvHnzMyAajUaTFhnZTmC91tIaF2Zf3AM0Xbt27ZNisbggInL//v2TgP4iTf3LGKuIZcpWAtaAIrAKrAArhUJheXl5eTmVSun9/f3fjI+Pv7+4uPjDnTt3flZlSMkymdsSgy2n5c7422ArkUh45ubmlhTpktpXUMQ3PNhybaK0ygs4AygtLy8XE4mE0+l0Sk1NjayurhYsdZJR6RNYb8RYPuC1ElyzeH1D3seGaHc8h4xjnRP6T+W2w6YsxTozT1nnc0sR+DfvEqpWtapV7X9pfwJBaeAkoe8CfwAAAABJRU5ErkJggg==') 24 24,
    pointer;
  }
  .slideshow--plein .slideshow__nav--next {
    right: 0; justify-items: end; padding-right: var(--gutter);
    cursor: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2248%22%20height=%2248%22%20viewBox=%220%200%2048%2048%22%3E%3Cpath%20d=%22M17%2013L31%2024L17%2035%22%20fill=%22none%22%20stroke=%22%23000%22%20stroke-opacity=%22.25%22%20stroke-width=%225%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22/%3E%3Cpath%20d=%22M17%2013L31%2024L17%2035%22%20fill=%22none%22%20stroke=%22%23fff%22%20stroke-opacity=%221%22%20stroke-width=%222%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22/%3E%3C/svg%3E') 24 24,
    url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAFU0lEQVR42u2ZXWhTZxjHf6dJzkl68m0+1ppqTT/SD211rsyNQt1aqINdzMF2MRDUm107Nuhutl3t6yJ4I4yyC0GRUXqxG5VuIFWRQYUpMpB40UIUUtyoXStt2jR5dvOecYjtkNokQ/LAyzkJvG+ej///eZ73CdSlLnWpy8simlpV/9EX3bvVGVL2rIg4X9DbWtnnrZSW/5MBduUbyp6WlNQS9ayYEY5tGtCglgNw9vf3G/l83rm2tuZU3zVsATGt1hywe94JOP1+v760tJQHvB6Px7m6uloEisCGbRVtEZGdjIZjm953qKWvra0Vr127NvrgwYOnc3Nz+Wg06l1ZWbHDqibZ6b+87wSMZDIZABqnpqbeERFZXl7+/cyZM0eA1ng83gskgQQQBQJAI2Co/Q07ZZRjm+R1AM5wOGwMDAxIIpGIhEKh4aGhofeSyeT9S5cuzZqm6dU0raFYLJZHQZ4jBVcsAhb2DcBnmmYMiAOJO3fufCNKbt++/T3QA/SGw+EeoBVoAsKAD3ADrk0IXxUIOQAd8AJhj8eT8Pl8HcCeiYmJj/P5/F8iInNzcz8PDg6+DnTFYrEDClLNQATwK0jpOw2p5zHCobznAYJADNircN928uTJtxcWFn4XEXny5MkfY2Nj7wLt0Wi0H2hXvIipvWYlePG8MNKVAiHgFaA1FAr1Aj3RaPTg7OzshIjI+vr68tWrVz8D2oLBYL+u6ylgj4JeqNZGGApKlhF7vV5vt8vlOgB0Xb9+/atCoZAXEbl79+4PwH6gVxnaqvaE1RlVM0LbxAgrEnGgRdf1zkAgcBBoT6fTJ5aXl7MiItls9pfR0dGjNl5YRliR0BVEtWpnJV2RMqjyfgvQEYlEDgG9w8PDI7lc7jcRkaWlpYfpdPoEkIzH4/uBvYoTfpWdqk7qzYyIKZyn/H7/a8AAcGRmZuZHEZFCoVC4cePGl0CLYRjtwG4VhUaVIBqqWSM240RYpcx2oM/pdL5hGMZR4Mj58+c/X1lZmVeQ+imVSqXcbncLsGs7MGqosHECsLGxIaZpOgHH5OTk/dXV1QWAQCDQFwgEHJqmidfrrUm/ZG/urNrgt/Gg0+VyHXK73W8Ch8bHxz9ZX19/KiIyOzs7GQ6Huw3DaPN4PAkVtapCyK68blM+ojDdFgwG+4GDwKsW/kulkkxPT38NpHRd7zYMY5/KXgF1RtVSaTl5A8rzu4E2VXm7RkZGhrLZ7K+qY3147ty5U0BnMBjsMwyjTXHFXgsc1VTeqgFW5mkBOqy2IZ1Of7i4uJgREZmfn7+lakBnLBbrAyzld5U1eFXxvmOztOnz+VLBYLAPaJuenv7CqsKZTOYCcADYr6rwPtWdbqZ8QzW8bxHWgk1LOBzuBrqA3kwmc0H1QU+vXLnyKdBhmmafrutdqnDZWwh7a13R21t5O21a+T4SiXQC+06fPv3W48ePb4mILC4uZs6ePfsBkFSQ6lAQK+9Eq551/r3Q2NJl/OLFix/l8/k/RUQePXo0dezYsUEb3pOK3BHb9bImdwHLADfgj8fjsePHjyfu3bs3ViqVRERkZmbmOyAF9JTdxnapNOupxW3smQgcPnw4ADTevHnzfRGRjY2Nvy9fvnwK2GOaZq+u651lfb+3VlfJrTKQCYRM04xls9lvx8fHR4FIc3NzSnl9t4JYRW9e2gsQ2WofXMAq0NjU1GTmcrmCbbBlfy+VDbdqNlp8xvienh7D5XJpuVxu3aZ4wTaVK1ZiKrfduUz5fMg+4MU20K3KgFd7gX2bLft4XSrl9Z0yoPxdK1NUqvH/gFbBM4S61KUudXnp5R+eybtCEqO6PQAAAABJRU5ErkJggg==') 24 24,
    pointer;
  }
  /* Les deux moities couvrent desormais toute la photographie, et le module
     de glissement refuse de partir depuis un controle : a la souris, l'image
     ne se prend plus a la main sur une galerie pleine page -- elle se clique.
     Le curseur « grab » annoncerait donc un geste qui n'existe plus. Au doigt,
     rien ne change : ces deux zones n'existent pas (`hover: hover`). */
  .slideshow--plein .slideshow__viewport.is-glissable { cursor: default; }
}

@media (hover: none), (max-width: 899px) {
  .slideshow--plein .slideshow__nav { display: none; }
}

/* --- 54. La loupe d'un mur d'images -------------------------------------

   Une photographie de mur s'ouvre par-dessus la page, et l'on fait defiler
   tout le mur. L'ecran reprend le balisage de la galerie pleine page : il
   herite donc de ses curseurs flechés, de son compteur et de son glissement,
   et il n'y a ici que ce qu'une loupe ajoute.

   Deux differences avec une galerie posee dans la page. Le fond est noir,
   parce qu'il n'y a plus de page autour. Et l'image y est CONTENUE : une
   galerie se compose, on y choisit le cadrage ; une loupe ne compose rien,
   elle montre la photographie, et la montrer en la coupant n'aurait pas de
   sens.
   ========================================================================= */

/* Le bouton qui ouvre : rien a voir, tout a cliquer. Il couvre le cadre,
   sous le cartel d'un cadre encore vide — un mur sans photographie n'a rien
   a agrandir, et le bouton n'y est de toute facon pas rendu. */
.ph__loupe {
  position: absolute; inset: 0;
  z-index: 2;
  margin: 0; padding: 0;
  border: 0; background: transparent;
  cursor: pointer;
  -webkit-appearance: none; appearance: none;
}
.ph__loupe:focus-visible { outline: 2px solid #fff; outline-offset: -4px; }

.loupe {
  position: fixed; inset: 0;
  z-index: 9500;
  background: #000;
}
.loupe[hidden] { display: none; }

/* Sous la loupe, la photographie entre entiere. */
.loupe .ph { background: #000; }
.loupe .ph__img { object-fit: contain; }

/* Le cartel fait face au compteur, de l'autre cote du meme voile. */
.loupe__cartel {
  position: absolute;
  left: var(--gutter); bottom: var(--s4);
  margin: 0; z-index: 3;
  max-width: 46ch;
  color: #fff;
  font: 500 var(--fs-cartel-s)/1.6 var(--mono);
  letter-spacing: .12em; text-transform: uppercase;
}
.loupe__cartel:empty { display: none; }

.loupe__fermer {
  position: absolute;
  top: var(--s3); right: var(--gutter);
  z-index: 4;
  width: 44px; height: 44px;
  display: grid; place-items: center;
  margin: 0; padding: 0;
  border: 0; background: transparent;
  color: #fff; font-size: 20px; line-height: 1;
  cursor: pointer;
  -webkit-appearance: none; appearance: none;
}
.loupe__fermer:hover { color: var(--ink-soft); }
.loupe__fermer:focus-visible { outline: 1px solid #fff; outline-offset: 2px; }

/* ==========================================================================
   LA RÈGLE DE L'ANGLE DROIT
   Réaffirmée en dernier, et sans exception possible : aucun composant du site
   ne porte d'angle arrondi. Seule la pastille de rappel de la barre d'outils —
   qui n'appartient pas au site, mais à la recette — y échappe.
   ========================================================================== */

*:not(.devbar-show), *::before, *::after { border-radius: 0 !important; }
.devbar-show { border-radius: 50%; }
