/*
 * site.css — feuille unique du site public.
 *
 * Parti pris : fond clair, très peu d'éléments, aucune image en pleine page.
 * Ce qui doit rester en tête après la visite, ce sont les photographies et les
 * titres de séries — tout le reste se tient en retrait.
 *
 * Les accents gardent la logique du cadre plomb de la note d'intention : le
 * rubedo signale l'image courante et rien d'autre, l'or la pièce unique.
 */

:root {
  /* Fonds — un blanc très légèrement chaud, pas un blanc d'écran */
  --fond:      #faf9f7;
  --fond-2:    #f2f0ec;
  --filet:     #e2dfda;

  /* Encre */
  --encre:     #16161a;   /* texte principal */
  --plomb:     #6b6e75;   /* texte secondaire, cartels */
  --plomb-pale:#9a9ca1;   /* désactivé */

  /* Accents — usage strictement réglé */
  --rubedo:    #9c4a2f;   /* image courante, focus */
  --or:        #8a6a2f;   /* la pièce unique uniquement */
  /* Les exemplaires d'une édition, et rien d'autre : vert disponible, rouge
     vendu. Le rouge est tenu à distance du rubedo — ils ne disent pas la même
     chose, ils ne doivent pas se ressembler. La croix porte le sens à elle
     seule ; la couleur ne fait que l'appuyer. Le site public ne montre plus de
     cases (seul le prochain tirage s'écrit, cf. cartel.js) : ces jetons ne
     servent plus qu'aux cases du back office. */
  --dispo:     #4f8a5b;
  --vendu:     #b23b3b;
  /* Le mur de la salle, du haut (près du rail de lumière) au bas. Ni l'un ni
     l'autre n'est un fond de page : c'est la cimaise, et rien d'autre. */
  --mur:       #f6f4f0;
  --mur-bas:   #e8e5df;

  /*
   * Type — une grotesque géométrique pour tout ce qui nomme, une romane pour
   * tout ce qui se lit.
   *
   * Le site est parti d'un Bodoni et d'un mono hérités d'un autre site
   * d'artiste. Jost — un Futura de source ouverte — remplace les deux : titres
   * et cartels sont désormais de la MÊME famille, distingués par le corps, la
   * casse et l'approche, comme sur un cartel de salle. C'est ce qui donne son
   * calme à la page : une seule voix pour nommer.
   *
   * Spectral reste pour les textes suivis — note d'intention, biographie : une
   * grotesque tient mal sur quatre paragraphes.
   *
   * Le back office, lui, garde ses fontes (cf. admin.css) : on y lit des noms
   * de fichiers, pas une identité.
   */
  --display: 'Jost', 'Futura', 'Century Gothic', 'Avenir Next', sans-serif;
  --lecture: 'Spectral', Georgia, serif;
  --cartel:  'Jost', 'Futura', 'Century Gothic', 'Avenir Next', sans-serif;

  --t-xs:   0.6875rem;
  --t-sm:   0.8125rem;
  --t-base: 1rem;
  --t-lg:   1.1875rem;
  --t-xl:   clamp(1.5rem, 2.6vw, 2.25rem);
  --t-2xl:  clamp(2.25rem, 5vw, 4rem);

  /* 4 rem de gouttière et 75 rem (1200 px) de mesure : les deux valeurs de la
     maquette. Elles décident de la taille des tirages de l'accueil, qui en
     occupent la moitié. */
  --gouttiere: clamp(1.25rem, 5vw, 4rem);
  --bande:     clamp(3.5rem, 9vh, 7rem);
  --mesure:    75rem;

  --transition: 360ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

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

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

/*
 * Le geste « page précédente » du navigateur part du MÊME balayage horizontal
 * que la traversée du mur : sur un trackpad, aller de gauche à droite dans la
 * galerie faisait tressauter le bord de la page pendant que le mur glissait.
 *
 * La salle confisque donc le débordement horizontal, et elle seule — ailleurs
 * sur le site, ce balayage reste un raccourci utile. La classe est posée par
 * serie.js sur <html> et non sur <body> : c'est la RACINE que la fenêtre lit
 * pour son débordement, et passer par <body> dépendrait d'une règle de
 * propagation qu'on n'a pas à supposer.
 */
html.salle { overscroll-behavior-x: none; }

body {
  margin: 0;
  background: var(--fond);
  color: var(--encre);
  font-family: var(--lecture);
  font-size: var(--t-base);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }

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

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/*
 * L'apparition — la seule animation de l'accueil.
 *
 * Un bloc monte d'un cheveu en arrivant à l'écran, lentement, une fois. La
 * classe `apparait` est posée par le script : sans JavaScript, rien n'est
 * jamais caché. La règle globale de `prefers-reduced-motion` ramène la durée à
 * zéro, et le script n'observe même pas dans ce cas.
 */
.apparait {
  opacity: 0;
  transform: translateY(1.25rem);
  transition: opacity 1100ms ease, transform 1100ms cubic-bezier(0.22, 0.61, 0.36, 1);
  transition-delay: var(--retard, 0ms);
}
.apparait.est-la { opacity: 1; transform: none; }

/* Un titre que seuls les lecteurs d'écran et les moteurs entendent. La page
   d'accueil n'écrit plus le nom en grand — elle doit tout de même en porter un,
   et l'écrire à l'image reviendrait à répéter l'en-tête. */
.hors-ecran {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Le filet court d'une gouttière à l'autre, à l'aplomb du contenu : une simple
   bordure sur l'en-tête déborderait de la largeur du texte. */
.filet-page {
  max-width: var(--mesure);
  margin: 0 auto;
  padding: 0 var(--gouttiere);
}
.filet-page::before {
  content: '';
  display: block;
  height: 1px;
  background: var(--filet);
}

.evitement {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--encre);
  color: var(--fond);
  padding: 0.75rem 1rem;
  z-index: 100;
}
.evitement:focus { left: 0; }

/* ------------------------------------------------------------------ */
/* Registres typographiques                                            */
/* ------------------------------------------------------------------ */

.display {
  font-family: var(--display);
  font-weight: 400;
  font-optical-sizing: auto;
  letter-spacing: -0.012em;
  line-height: 1.08;
  text-wrap: balance;
  margin: 0;
}

/* Le registre du cartel de musée : capitales espacées, chiffres alignés. */
.cartel {
  font-family: var(--cartel);
  font-size: var(--t-xs);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--plomb);
}

.lecture {
  max-width: 62ch;
  font-size: var(--t-lg);
  line-height: 1.75;
  color: #3a3a40;
}
.lecture p { margin: 0 0 1.4em; }
.lecture em { color: var(--encre); font-style: italic; }

/* ------------------------------------------------------------------ */
/* En-tête                                                             */
/* ------------------------------------------------------------------ */

.entete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
  max-width: var(--mesure);
  margin: 0 auto;
  padding: clamp(1.6rem, 4vh, 2.75rem) var(--gouttiere) clamp(1.4rem, 3vh, 2.25rem);
}

/* Le nom porte l'identité de la page à lui seul : capitales, approche large,
   graisse légère. C'est une signature gravée, pas un logotype. */
.entete__nom {
  font-family: var(--display);
  font-weight: 300;
  font-size: clamp(0.95rem, 1.4vw, 1.05rem);
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/*
 * `auto` à gauche : la navigation reste à sa place quelle que soit la longueur
 * du nom, plutôt que de glisser au bord opposé.
 *
 * Elle s'enroule, et ce n'est pas une précaution de principe : la quatrième
 * entrée (« Films ») a porté la rangée à 360 px pour 280 px disponibles sur un
 * écran de 320 — toute la page débordait alors vers la droite. `flex-end` pour
 * que la seconde ligne reste alignée à droite, avec la première.
 */
.entete__nav {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.6rem clamp(1rem, 3vw, 2.25rem);
  margin-left: auto;
}

.entete__lien {
  font-family: var(--cartel);
  font-size: var(--t-xs);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--plomb);
  padding-bottom: 2px;
  border-bottom: 1px solid transparent;
  transition: color var(--transition), border-color var(--transition);
}
.entete__lien:hover { color: var(--encre); }
.entete__lien[aria-current='page'] {
  color: var(--encre);
  border-bottom-color: var(--rubedo);
}

/* L'icône n'est pas un lien de navigation : elle ne prend ni le filet du bas,
   ni la place d'un mot. */
.entete__insta {
  display: flex;
  padding-bottom: 0;
  border-bottom: 0;
  margin-left: -0.5rem;
}

/* ================================================================== */
/* ACCUEIL                                                             */
/* ================================================================== */

/*
 * L'accueil ne porte plus le nom en grand : il est dans l'en-tête, écrit une
 * fois. Ce qui ouvre la page est une photographie et le titre de sa série. Sur
 * le site d'un photographe, l'image entre la première.
 *
 * Trois partis sont repris du site dont celui-ci est issu, et ils sont
 * délibérés :
 *  - de GROS visuels : le tirage de tête occupe la moitié de la mesure et
 *    presque toute la hauteur de l'écran ;
 *  - toutes les images au MÊME rapport 3/4, quitte à rogner ;
 *  - le nom d'une série ne s'inscrit sur sa couverture qu'AU SURVOL.
 * Ils prennent le pas sur les habitudes du site d'origine — voir CLAUDE.md.
 */

.accueil {
  max-width: var(--mesure);
  margin: 0 auto;
  padding: 0 var(--gouttiere) var(--bande);
}
/* La cible du lien d'évitement : on y saute, on ne l'encadre pas. */
.accueil:focus { outline: none; }

/*
 * Le rapport de TOUTES les images de l'accueil.
 *
 * Un seul rapport pour toute la page : c'est lui qui fait la grille, et une
 * grille aux hauteurs inégales ne serait plus une grille. Le prix est un
 * rognage — un paysage 3/2 perd la moitié de sa largeur. Il se paie au moment
 * du CHOIX DE COUVERTURE (`couverture` dans data.js, réglable au back office) :
 * une série au cadrage large se donne une couverture en hauteur.
 */
.accueil { --rapport: 3 / 4; }

/* --- La série principale -------------------------------------------- */

.avant { padding-top: clamp(2rem, 5vh, 3rem); }
/* Une règle d'auteur bat la feuille du navigateur. `.avant` n'a pas de
   `display` aujourd'hui ; la règle reste — la faute a déjà coûté cinq fois,
   cf. CLAUDE.md § pièges. */
.avant[hidden] { display: none; }

/* `flex-end` : le titre s'assoit sur le bas du tirage. C'est l'aplomb de la
   maquette, et c'est ce qui tient la double page. */
.mise {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 1.5rem clamp(1.75rem, 4vw, 3rem);
}
/*
 * La moitié de la mesure, et presque toute la hauteur de l'écran.
 *
 * `max-width` plutôt qu'un `max-height` : à rapport fixe, borner la hauteur
 * sans borner la largeur laisserait des bandes de fond de part et d'autre du
 * tirage. Le plafond est haut — il ne mord que sur un écran court, où il évite
 * qu'on ne voie jamais le bas de l'image.
 */
.mise__cadre {
  --haut: min(88vh, 46rem);
  flex: 0 1 calc(50% - 1.25rem);
  max-width: calc(var(--haut) * 3 / 4);
  aspect-ratio: var(--rapport);
  background: var(--fond-2);
  overflow: hidden;
}

.mise__cadre img,
.mise__cadre canvas {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  /* Une pointe de désaturation au repos, la couleur pleine à l'approche : le
     tirage s'allume quand on va vers lui. C'est le seul traitement d'image du
     site, et il vient de la maquette. */
  filter: saturate(0.92);
  transition: transform 2000ms cubic-bezier(0.22, 0.61, 0.36, 1),
              filter 900ms ease;
}
@media (hover: hover) {
  .mise:hover .mise__cadre img { transform: scale(1.02); filter: saturate(1); }
}

.mise__texte {
  flex: 1 1 16rem;
  min-width: 0;
  padding-bottom: 0.5rem;
}

/*
 * Le titre de SÉRIE, en capitales et en grand, et une seule ligne de cartel
 * sous lui : année et procédé. Rien d'autre — pas d'accroche, pas d'effectifs,
 * pas de « entrer dans la série ». L'accueil montre et nomme ; c'est le seuil
 * de la série qui explique.
 */
.mise__titre {
  font-size: clamp(2rem, 4.6vw, 3.4rem);
  line-height: 1.06;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  transition: color var(--transition);
}
@media (hover: hover) {
  .mise:hover .mise__titre { color: var(--rubedo); }
}

.mise__reperes { margin: 0.75rem 0 0; color: var(--plomb-pale); }

@media (max-width: 47.99rem) {
  /* Sur un téléphone, l'image passe pleine largeur et le texte dessous. */
  .mise__cadre {
    --haut: min(72vh, 30rem);
    flex: 1 1 100%;
    max-width: 100%;
  }
}

/* --- Les séries, en couvertures ------------------------------------- */

/*
 * `minmax(0, 1fr)` et non `1fr` : le minimum `auto` d'une colonne vaut son
 * contenu le plus long et pousse la page hors de l'écran d'un téléphone.
 *
 * Deux colonnes FIXES, jamais `auto-fit` : avec une seule série restante, une
 * piste qui s'effondre donnerait une couverture large de toute la mesure.
 */
.series {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(2rem, 5vh, 3rem);
  align-items: start;
  margin-top: clamp(2.5rem, 7vh, 3.5rem);
  padding-top: clamp(2.5rem, 7vh, 3rem);
  border-top: 1px solid var(--filet);
}
/* Cf. CLAUDE.md § pièges : sans ceci, `hidden` resterait sans effet. */
.series[hidden] { display: none; }

/* Sans mise en avant, la grille suit directement le filet de l'en-tête : un
   second filet à trois centimètres du premier ne sépare plus rien. */
.avant[hidden] + .series {
  margin-top: 0;
  padding-top: clamp(2rem, 5vh, 3rem);
  border-top: 0;
}

@media (min-width: 42rem) {
  .series {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: clamp(2rem, 5vh, 3rem) clamp(1.5rem, 2.5vw, 2.5rem);
  }
}

.carte { display: block; }

.carte__cadre {
  position: relative;
  width: 100%;
  aspect-ratio: var(--rapport);
  background: var(--fond-2);
  overflow: hidden;
}

.carte__cadre img,
.carte__cadre canvas {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  transition: transform 1800ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
@media (hover: hover) {
  .carte:hover .carte__cadre img { transform: scale(1.03); }
}

/*
 * Le nom de la série est ÉCRIT SUR sa couverture, et il s'efface à l'approche :
 * on lit d'abord de quoi il s'agit, puis la photographie reste seule le temps
 * qu'on la regarde. Le voile qui le porte est un dégradé, pas un écran : il
 * assombrit le pied du tirage, jamais le sujet.
 *
 * Il n'y a rien à cacher au doigt ni à la tabulation : posé d'emblée, il n'a
 * pas besoin de repli — c'est l'effacement qui demande une souris, et son
 * absence ne retire rien.
 */
.carte__voile {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  padding: clamp(1rem, 2.5vw, 1.6rem);
  background: linear-gradient(to top,
    rgba(22, 22, 26, 0.62) 0%,
    rgba(22, 22, 26, 0.12) 45%,
    rgba(22, 22, 26, 0) 75%);
  opacity: 1;
  transition: opacity 700ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
@media (hover: hover) {
  /* Une série vide garde son nom : il n'y a pas de photographie à découvrir
     dessous, et l'effacer ne laisserait qu'un rectangle gris sans légende. */
  .carte:not(.carte--vide):hover .carte__voile { opacity: 0; }
}
/* À la tabulation, le nom reste : le focus n'est pas un coup d'œil, c'est une
   position — le lien doit dire où il mène tant qu'on est dessus. */
.carte:focus-visible .carte__voile { opacity: 1; }

.carte__titre {
  color: var(--fond);
  font-size: clamp(1.25rem, 2.4vw, 1.6rem);
  line-height: 1.15;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

/*
 * L'emplacement se tient en retrait, et son nom ne s'efface pas : il n'y a
 * rien à découvrir dessous.
 *
 * ⚠️ Plus aucune SÉRIE ne porte cette classe — une série sans photographie a
 * quitté l'accueil (cf. `dessinerSeries`). Elle ne sert plus qu'aux PROJETS
 * de `films.html`, qui gardent leur place en retrait parce que cette liste EST
 * le catalogue des projets : en retirer un le rendrait introuvable. La règle
 * est dans accueil.js et films.js, pas ici — la feuille ne fait que la vêtir.
 */
.carte--vide .carte__voile { background: none; }
.carte--vide .carte__titre { color: var(--plomb); }
.carte--vide .carte__cadre img,
.carte--vide .carte__cadre canvas { opacity: 0.45; }

/* ================================================================== */
/* FILMS                                                               */
/* ================================================================== */

/*
 * LA LISTE DES PROJETS — la même grille que les séries sur l'accueil : des
 * couvertures sur deux colonnes, le nom écrit dessus et effacé au survol. Le
 * site n'a qu'une seule façon de présenter un ensemble, qu'il s'agisse de
 * photographies ou de films, et un visiteur qui a compris l'accueil a compris
 * cette page. D'où la réutilisation de `.carte` telle quelle, et du rapport 3/4.
 *
 * ⚠️ Rien ne se lit ici : cliquer un projet mène à projet.html?p=<slug>. La
 * page a longtemps porté les affiches en 16/9 avec un bouton « Lire ».
 */
.projets {
  --rapport: 3 / 4;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(2rem, 5vh, 3rem);
  align-items: start;
}
/* Cf. CLAUDE.md § pièges : sans ceci, `hidden` resterait sans effet. */
.projets[hidden] { display: none; }

@media (min-width: 42rem) {
  .projets {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: clamp(2rem, 5vh, 3rem) clamp(1.5rem, 2.5vw, 2.5rem);
  }
}

.films__vide,
.accueil__vide { color: var(--plomb); margin: 0; }

/*
 * LA PAGE D'UN PROJET : les vidéos, puis les photographies. Un ordre fixe, qui
 * évite de mêler deux rapports dans une même grille — le 16/9 du cinéma et le
 * rapport variable des tirages ne font pas une grille, seulement une suite de
 * hauteurs qui se contredisent.
 *
 * Le lecteur de l'hébergeur ne se charge qu'au clic (cf. projet.js). Ce qu'on
 * voit tant qu'on n'a rien demandé est une image du dépôt, servie par le site :
 * aucune requête vers un tiers, aucun traceur posé d'office.
 */
.projet__retour { margin: 0 0 0.9rem; }
.projet__reperes { margin: 0.7rem 0 0; color: var(--plomb-pale); }
.projet__note { margin: 1.1rem 0 0; max-width: 52ch; color: var(--plomb); }
.projet__vide { color: var(--plomb); margin: clamp(2rem, 5vh, 3rem) 0 0; }

.projet__videos {
  display: grid;
  gap: clamp(2rem, 6vh, 3.5rem);
  margin-top: clamp(2.5rem, 7vh, 4rem);
}
.projet__videos[hidden] { display: none; }

/*
 * La galerie du projet. `auto-fill` et non `auto-fit` : à deux images, on veut
 * deux colonnes de largeur normale, pas deux colonnes étirées sur tout l'écran.
 * `align-items: start` laisse chaque image à SA hauteur — c'est le prix, et le
 * mérite, de ne rien rogner.
 */
.projet__galerie {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
  gap: clamp(1.5rem, 4vh, 2.5rem) clamp(1.25rem, 2.5vw, 2rem);
  align-items: start;
  margin-top: clamp(2.5rem, 7vh, 4rem);
  padding-top: clamp(2.5rem, 7vh, 3rem);
  border-top: 1px solid var(--filet);
}
.projet__galerie[hidden] { display: none; }
/* Sans vidéo, la galerie ouvre la page : le filet n'a rien à séparer. */
.projet__videos[hidden] + .projet__galerie { border-top: 0; padding-top: 0; }

.vue { margin: 0; }

/* Le cadre prend le rapport du fichier (`--ratio`, posé par le script au
   chargement) : la place est réservée avant l'arrivée de l'image, et RIEN
   N'EST ROGNÉ — on montre les photographies d'un projet, pas des vignettes. */
.vue__cadre {
  display: block;
  width: 100%;
  aspect-ratio: var(--ratio, 1.5);
  padding: 0;
  background: var(--fond-2);
  border: 0;
  cursor: zoom-in;
  overflow: hidden;
}
.vue__cadre img,
.vue__cadre canvas {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: opacity var(--transition);
}
@media (hover: hover) {
  .vue__cadre:hover img { opacity: 0.88; }
}

.vue__legende { margin: 0.6rem 0 0; color: var(--plomb); }

/* Une bande : le cadre, et sous lui le titre s'il y en a un. C'est un
   `<figure>` — d'où `margin: 0`, que le navigateur pose autrement (1em 40px),
   et qui décalerait toutes les vidéos vers la droite. */
.film { display: block; margin: 0; }

/*
 * Le 16/9 est le rapport du cinéma, pas celui des tirages : ici, il est celui
 * de l'œuvre — un film recadré en 3/4 n'est plus le film. La largeur se déduit
 * de la hauteur maximale, comme sur l'accueil, pour que la bande ne dépasse
 * jamais l'écran sans laisser de bandes noires autour de l'affiche.
 */
.film__cadre {
  --haut: min(70vh, 38rem);
  position: relative;
  width: min(100%, calc(var(--haut) * 16 / 9));
  aspect-ratio: 16 / 9;
  background: var(--fond-2);
  overflow: hidden;
}

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

/* Le lecteur prend toute la place du cadre : il en a exactement le rapport.
   La vidéo déposée est en `contain` et non en `cover` : un film n'est pas
   toujours en 16/9, et le rogner serait le recadrer. */
.film__cadre--lecture,
.film__cadre.est-en-lecture { background: var(--encre); }
.film__cadre iframe,
.film__cadre video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
.film__cadre video { object-fit: contain; background: var(--encre); }

/* Le titre d'une vidéo déposée, sous sa bande. Une vidéo d'hébergeur n'en a
   pas : son titre est chez lui, et le redire ici le ferait vieillir. */
.film__legende { margin: 0.7rem 0 0; color: var(--plomb); }

/*
 * Le déclencheur recouvre l'affiche entière : sur une image de cette taille,
 * viser un petit bouton central serait une épreuve. Bouton ou lien selon qu'on
 * lit ici ou chez l'hébergeur — la feuille ne les distingue pas, seul le verbe
 * change.
 */
.film__lecture {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 0.9rem;
  /* Le voile éclaircit à peine l'image ; il est là pour que le triangle tienne
     sur une affiche claire, pas pour masquer la photographie. */
  background: rgba(22, 22, 26, 0.2);
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--fond);
  cursor: pointer;
  transition: background var(--transition);
  /* Une affiche peut être claire — un ciel, une page blanche — et le voile est
     volontairement léger. L'ombre portée tient le contraste sans qu'on ait à
     assombrir la photographie : c'est elle qu'on est venu voir. */
  text-shadow: 0 1px 10px rgba(22, 22, 26, 0.55);
}
@media (hover: hover) {
  .film__lecture:hover { background: rgba(22, 22, 26, 0.34); }
}

.film__triangle {
  width: 4.25rem;
  height: 4.25rem;
  border: 1px solid currentColor;
  border-radius: 50%;
  display: grid;
  place-items: center;
  /* Le cercle est une bordure, pas du texte : `text-shadow` ne l'atteint pas. */
  filter: drop-shadow(0 1px 8px rgba(22, 22, 26, 0.5));
  transition: transform var(--transition);
}
/* Le triangle est dessiné en bordures : pas d'image à charger pour une forme
   de six pixels, et il suit la couleur du texte. Décalé d'un cheveu à droite,
   sans quoi un triangle paraît toujours trop à gauche dans son cercle. */
.film__triangle::before {
  content: '';
  border-style: solid;
  border-width: 0.55rem 0 0.55rem 0.9rem;
  border-color: transparent transparent transparent currentColor;
  margin-left: 0.2rem;
}
@media (hover: hover) {
  .film__lecture:hover .film__triangle { transform: scale(1.06); }
}

.film__verbe { color: inherit; }

.film__texte { margin-top: 1.25rem; }

.film__titre {
  font-size: clamp(1.6rem, 3.4vw, 2.6rem);
  line-height: 1.08;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.film__reperes { margin: 0.6rem 0 0; color: var(--plomb-pale); }

.film__note {
  margin: 0.9rem 0 0;
  max-width: 52ch;
  color: var(--plomb);
}


/* ------------------------------------------------------------------ */
/* Pages de texte                                                      */
/* ------------------------------------------------------------------ */

.page {
  max-width: var(--mesure);
  margin: 0 auto;
  padding: clamp(2rem, 6vh, 4rem) var(--gouttiere) var(--bande);
}

.page__titre {
  font-size: var(--t-2xl);
  margin-bottom: clamp(2rem, 6vh, 3.5rem);
  max-width: 14ch;
}

.page__grille {
  display: grid;
  gap: clamp(1rem, 4vw, 3rem);
  grid-template-columns: 1fr;
}
/* Une règle d'auteur bat la feuille du navigateur : sans ceci, `hidden` sur une
   section de contact resterait sans effet. Cf. CLAUDE.md § pièges. */
.page__grille[hidden] { display: none; }
@media (min-width: 56rem) {
  .page__grille { grid-template-columns: 14rem 1fr; }
}

.page__section + .page__section { margin-top: clamp(2.5rem, 7vh, 4rem); }

.page__soustitre {
  font-family: var(--cartel);
  font-size: var(--t-xs);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--plomb);
  margin: 0;
  padding-top: 0.55rem;
  border-top: 1px solid var(--filet);
}

.liste-nue { list-style: none; margin: 0; padding: 0; }

/* minmax(0, 1fr) et non 1fr : le minimum `auto` d'une colonne vaut son contenu
   le plus long, et pousse la page hors de l'écran d'un téléphone. */
.def {
  display: grid;
  grid-template-columns: minmax(8rem, 12rem) minmax(0, 1fr);
  gap: 0.4rem 1.5rem;
  margin: 0;
}
.def dt {
  font-family: var(--cartel);
  font-size: var(--t-xs);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--plomb);
  padding-top: 0.35em;
}
.def dd { margin: 0; color: #3a3a40; }

.lien-souligne {
  border-bottom: 1px solid var(--plomb-pale);
  transition: border-color var(--transition), color var(--transition);
}
.lien-souligne:hover { border-color: var(--rubedo); color: var(--rubedo); }

.a-faire {
  border-left: 2px solid var(--rubedo);
  padding: 0.75rem 0 0.75rem 1.25rem;
  color: var(--plomb);
  font-family: var(--cartel);
  font-size: var(--t-sm);
  letter-spacing: 0.03em;
  line-height: 1.6;
}

/* ------------------------------------------------------------------ */
/* Pied de page                                                        */
/* ------------------------------------------------------------------ */

.pied {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 2rem;
  justify-content: space-between;
  max-width: var(--mesure);
  margin: 0 auto;
  padding: 1.75rem var(--gouttiere) 2.5rem;
  border-top: 1px solid var(--filet);
}

/* La rangée de service : la signature à gauche, l'entrée du back office à
   droite, sur leur propre ligne sous les mentions de l'artiste. Elles
   s'enroulent l'une sous l'autre quand la place manque — ⚠️ AUCUN `&nbsp;`
   ici : un pied insécable de 398 px avait déjà poussé le téléphone hors de
   l'écran (cf. CLAUDE.md § 8). */
.pied__bas {
  flex-basis: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 2rem;
  justify-content: space-between;
  align-items: baseline;
}

/* Les deux dans le gris le plus pâle du site : discrètes, pas cachées — on les
   trouve quand on les cherche. */
.pied__signature,
.pied__admin { color: var(--plomb-pale); }

.pied__admin {
  /* Elle reste à droite même enroulée sur sa propre ligne : `space-between`
     ne place que la première rangée, un second élément retombé irait à
     gauche et les deux mentions se liraient comme une liste. */
  margin-left: auto;
  transition: color var(--transition);
}
.pied__admin:hover { color: var(--plomb); }

/* ================================================================== */
/* VISIONNEUSE                                                         */
/* ================================================================== */

/*
 * Deux rangées : l'en-tête de série DANS le flux, puis la scène. L'en-tête ne
 * peut donc jamais recouvrir l'image — le voile sur le haut des œuvres était
 * un défaut constaté en production, pas une hypothèse.
 */
/*
 * LA SALLE : un mur, une lumière, et l'œuvre décollée du mur.
 *
 * Trois couches, chacune sous le seuil où on la remarquerait seule ; ensemble
 * elles font une pièce. L'ordre compte :
 *
 *  1. La lumière fait le mur, pas la texture. Un mur de galerie est un plâtre
 *     mat, presque lisse — ce qui le fait lire comme surface, c'est la tombée
 *     de la lumière. Le grain (un bruit SVG en `data:`, aucune ressource
 *     tierce) est à 7 % : on le sent, on ne le voit pas. Plus fort, il se
 *     battrait avec le grain des cyanotypes, qui EST leur matière.
 *  2. Le spot est DERRIÈRE l'œuvre, jamais dessus : une ellipse claire un peu
 *     au-dessus du centre (la lumière vient du rail, au plafond), qui s'éteint
 *     vers les bords. Le tirage n'est pas touché — aucun dégradé, aucun filtre
 *     sur la photographie. On éclaire la salle, pas l'image. Fixé à la fenêtre
 *     pendant que le mur glisse, il fait passer les œuvres dans la lumière.
 *  3. L'ombre (sur `.oeuvre__cadre`) dit la distance au mur — et elle suit
 *     le parallaxe : quand le tirage penche, elle se déplace. C'est ce qui
 *     fait d'une inclinaison un objet, et non un effet.
 *
 * `isolation: isolate` : le spot est un `::before` en `z-index: -1`, et sans
 * contexte d'empilement propre il passerait SOUS le mur lui-même.
 */
.visionneuse {
  height: 100svh;
  display: grid;
  grid-template-rows: auto 1fr;
  overflow: hidden;
  position: relative;
  isolation: isolate;
  --spot: 1;
  background:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='g'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch' seed='7'/><feColorMatrix values='0 0 0 0 0.30  0 0 0 0 0.27  0 0 0 0 0.23  0 0 0 0.07 0'/></filter><rect width='180' height='180' filter='url(%23g)'/></svg>") 0 0 / 180px 180px,
    linear-gradient(180deg, var(--mur) 0%, var(--mur-bas) 100%);
}
.visionneuse::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  /* Un peu plus haut et plus franc depuis que le tirage porte une ombre : la
     lumière vient du rail, et c'est elle qui justifie que l'ombre parte vers
     le bas. */
  background: radial-gradient(ellipse 78% 70% at 50% 34%,
    rgba(255, 255, 255, 0.86), rgba(255, 255, 255, 0) 70%);
  opacity: var(--spot);
  transition: opacity 700ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
/* Sur le seuil, les vues de l'exposition et la fin, rien à éclairer : le spot
   s'éteint, le mur reste. */
body.est-au-seuil .visionneuse,
body.est-en-exposition .visionneuse,
body.est-a-la-fin .visionneuse { --spot: 0; }

.scene {
  position: relative;
  min-height: 0;
  margin: 0;
  display: grid;
  /* En étroit, deux rangées : le mur, puis le cartel. En large, le cartel et
     le pavé se posent SUR le mur (cf. plus bas) et le mur prend tout. */
  grid-template-rows: 1fr auto;
  gap: 1.25rem;
  padding: 0.75rem clamp(2rem, 7vw, 6rem) 1.5rem;
  /* Le glissement horizontal appartient au mur ; le défilement vertical de la
     page reste au navigateur. */
  touch-action: pan-y;
}

/* ------------------------------------------------------------------ */
/* La cimaise : le mur, les œuvres, les cadres plomb                    */
/* ------------------------------------------------------------------ */

/*
 * Le voile clippe le mur et porte la perspective : les inclinaisons des
 * œuvres (parallaxe, traversée) prennent leur profondeur ici.
 */
.scene__voile {
  min-height: 0;
  height: 100%;
  overflow: hidden;
  position: relative;
  perspective: 1600px;
}

.mur {
  display: flex;
  height: 100%;
  transition: transform 700ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
/* Pendant que le doigt tient le mur, il suit sans inertie. */
.mur.est-tenu { transition: none; }

/*
 * La traversée : au glissement, le mur respire légèrement — rien de plus. Le
 * flou de pose longue a été retiré : l'écho était joli en principe, mais il
 * brouillait la photographie au moment précis où on la découvre, et une œuvre
 * ne doit jamais être moins nette que le tirage.
 *
 * L'animation reste, et son NOM aussi : le JS retire `est-en-mouvement`,
 * `va-droite` et `va-gauche` sur son `animationend`. Sans elle, l'inclinaison
 * resterait figée de travers pour toujours.
 */
.mur.est-en-mouvement {
  animation: traversee 700ms cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: transform;
}
@keyframes traversee {
  0%   { opacity: 1; }
  45%  { opacity: 0.92; }
  100% { opacity: 1; }
}

/* L'œuvre s'incline légèrement dans le sens du passage, comme quand on la
   dépasse en marchant. Retour à plat en fin de course. */
.mur.va-droite .oeuvre__accroche { --ry: 2.2deg; }
.mur.va-gauche .oeuvre__accroche { --ry: -2.2deg; }

/* Tout ce que porte le mur occupe un écran plein, œuvre ou texte de salle. */
.creneau {
  flex: 0 0 100%;
  min-width: 0;
}

.oeuvre {
  position: relative;
  display: grid;
  place-items: center;
  /* Conteneur de taille : le cadre se mesure en cqw/cqh, ce qui évite toute
     chaîne de pourcentages fragile (piège déjà payé). */
  container-type: size;
  /* La marge du bas est la PLACE DE L'OMBRE : le voile clippe tout ce qui
     dépasse du mur, et une ombre coupée net par le bas se lit comme une ligne
     — c'est l'une des deux démarcations déjà payées (cf. `.oeuvre__cadre`).
     L'ombre porte au plus 8 % de la largeur du tirage sous lui. */
  padding: 0.5rem 0 2.5rem;
}

.oeuvre__accroche {
  transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  transition: transform 700ms cubic-bezier(0.22, 0.61, 0.36, 1);
  transform-style: preserve-3d;
}

/*
 * L'emplacement de l'œuvre.
 *
 * PAS DE CADRE : ni bande, ni fond. Le tirage est posé nu sur le mur, et c'est
 * l'échelle et le blanc autour qui font l'accrochage. Le site d'origine
 * dessinait un encadrement en plomb ; il n'a rien à voir avec ce travail-ci,
 * et tout cadre inventé se serait mis entre le visiteur et l'image.
 *
 * L'élément reste : il porte le rapport (--ratio), posé par serie.js au
 * chargement, qui réserve la place exacte du tirage avant qu'il n'arrive.
 *
 * C'EST UN BOUTON (`<button>`), et il descend au détail — d'où la remise à nu
 * (ni fond, ni bord, ni marge intérieure) et le `display`, qu'un bouton n'aurait
 * pas. Au survol il agrandit (`.tirage`), au doigt il ouvre le plein écran. Il a
 * été un lien vers la page de l'œuvre ; ce chemin n'existe plus (serie.js).
 *
 * La largeur est un jeton (`--largeur`) : l'ombre s'en sert, et la pleine
 * salle (≥ 56 rem, cf. `.scene`) la redéfinit.
 */
.oeuvre__cadre {
  --ratio: 1.5;
  --largeur: min(92cqw, calc(92cqh * var(--ratio)));
  display: block;
  aspect-ratio: var(--ratio);
  width: var(--largeur);
  box-sizing: border-box;
  position: relative;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

/*
 * L'OMBRE PORTÉE — le tirage est POSÉ sur le mur, à quelques centimètres.
 *
 * Elle est revenue à la demande de l'artiste (septembre 2026), et elle est
 * écrite pour ne pas refaire la BANDE d'autrefois : l'ancienne, 70 px de flou à
 * 40 % de noir poussée vers le bas, peignait sous l'image une masse grise qui
 * coupait le mur en deux — un second fond posé sur le premier. Deux causes,
 * deux gardes :
 *
 *  1. Elle est PROPORTIONNÉE AU TIRAGE, pas fixée en pixels : trois couches
 *     (un fil de contact, une ombre courte, une ombre longue et douce) dont
 *     chaque mesure est un pourcentage de `--largeur`. Sur un tirage de 650 px
 *     l'ombre longue tombe de 22 px et se fond sur 44 px, avec un ÉTALEMENT
 *     NÉGATIF qui la resserre sous l'image au lieu de la laisser déborder sur
 *     les côtés. Même règle sur la vignette du détail (`.detail__mobile`, en
 *     `cqw` de sa case) : pendant la descente, la transformation qui réduit
 *     l'image réduit l'ombre du même facteur, et ce qu'on voit à l'écran ne
 *     change pas d'un pixel entre le mur et la vignette.
 *  2. Elle a de la place : `.oeuvre` garde sous le tirage une marge où l'ombre
 *     s'éteint AVANT le bord du voile. Une ombre clippée par un `overflow:
 *     hidden` finit sur une ligne droite, et c'est une ligne de plus.
 *
 * Le fond, lui, reste uni : rien ne change à `--mur-bas`, et le détail le
 * reprend (cf. `.detail`). Ne pas remettre d'ombre en pixels fixes ni plus
 * sombre : à 34 % elle dit le relief ; au-delà, elle redit le fond.
 */
.oeuvre__cadre,
.detail__mobile {
  box-shadow:
    0 0 0 0.5px rgba(22, 20, 17, 0.10),
    0 calc(var(--l) * 0.003) calc(var(--l) * 0.009) rgba(22, 20, 17, 0.14),
    0 calc(var(--l) * 0.034) calc(var(--l) * 0.068) calc(var(--l) * -0.022) rgba(22, 20, 17, 0.34);
}
.oeuvre__cadre { --l: var(--largeur); }
.detail__mobile { --l: 100cqw; }

/*
 * PLEINE SALLE (≥ 56 rem). Le cartel et le pavé quittent leur rangée et se
 * posent en bas du mur, par-dessus : le mur prend toute la hauteur, et le
 * tirage gagne un quart de sa taille. Choisi sur le site repris entre trois
 * propositions (septembre 2026) — « les photos en plus grand ».
 *
 * Ce qui tient le recouvrement à distance : le tirage est CENTRÉ et sa largeur
 * est plafonnée à `100cqw - 26rem`, ce qui laisse à droite la place du pavé ;
 * et `.oeuvre` garde en bas une marge où le cartel monte sans atteindre le
 * bord du tirage — 6 rem au moins (la place de l'ombre), et davantage quand le
 * cartel est haut : `--sous-tirage` est sa hauteur, posée par serie.js à
 * chaque œuvre. Une note de trois lignes fait donc reculer le tirage d'autant,
 * comme le faisait la rangée d'autrefois, au lieu de se lire à travers son
 * coin. Vérifié sur tout le catalogue par le harnais, à 1024, 1280 et 1440.
 *
 * `pointer-events: none` sur le rang, `auto` sur ce qu'il porte : le rang
 * couvre toute la largeur, et il ne doit pas prendre au mur le balayage ni le
 * clic qui tombent entre le cartel et le pavé.
 */
@media (min-width: 56rem) {
  .scene {
    grid-template-rows: 1fr;
    gap: 0;
    padding: 0.5rem clamp(1.5rem, 5vw, 4.5rem) 0;
  }
  .scene__pied {
    position: absolute;
    left: clamp(1.5rem, 5vw, 4.5rem);
    right: clamp(1.5rem, 5vw, 4.5rem);
    bottom: 1.25rem;
    min-height: 0;
    z-index: 2;
    pointer-events: none;
  }
  .scene__pied > * { pointer-events: auto; }
  /* 2,5 rem au-delà du cartel : son décalage du bas (1,25 rem) et un souffle. */
  .oeuvre { padding-bottom: max(6rem, calc(var(--sous-tirage, 0px) + 2.5rem)); }
  .oeuvre__cadre { --largeur: min(calc(100cqw - 26rem), calc(92cqh * var(--ratio))); }
}

.oeuvre__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transition: opacity 480ms ease;
}
.oeuvre__image.est-visible { opacity: 1; }

/* L'entrée en scène : la salle s'allume sur l'œuvre d'arrivée. Sans flou, là
   encore — l'image arrive nette, elle se lève seulement. */
.oeuvre--nait .oeuvre__accroche {
  animation: lever-de-salle 650ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
}
@keyframes lever-de-salle {
  from { opacity: 0; transform: translateY(10px); }
}

.mur__vide {
  align-self: center;
  margin: 0 auto;
  max-width: 44rem;
  text-align: center;
  line-height: 2;
}
.mur__vide > p { margin: 0; }

/* Une série sans photographie n'a pas de seuil : sa note d'intention se lit
   ici, faute d'ailleurs où la mettre. */
.mur__note {
  margin-top: clamp(1.5rem, 5vh, 2.5rem);
  padding-top: clamp(1.25rem, 4vh, 2rem);
  border-top: 1px solid var(--filet);
  text-align: left;
}

/* --- Le seuil et la fin : les deux textes de salle ------------------ */

/*
 * Une exposition a une entrée et une sortie. Le seuil dit en cinq lignes ce
 * qu'est la série et propose d'y entrer ; la fin dit ce qu'on vient de voir et
 * ce qu'on peut en faire. Aucun des deux ne barre le passage : une flèche suffit
 * à les dépasser, et un lien profond ne les rencontre jamais.
 *
 * `safe center` n'est pas un raffinement : sur un écran court, un centrage
 * ordinaire ferait sortir le haut du texte de la boîte, sans moyen d'y revenir.
 */
.panneau {
  display: flex;
  flex-direction: column;
  justify-content: center;
  justify-content: safe center;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0.5rem 0 1rem;
}

.panneau__corps { width: 100%; max-width: 46rem; }

/*
 * L'ÉCRAN D'ENTRÉE — le seuil, tel qu'il se donne depuis que la note
 * d'intention y vit.
 *
 * À gauche la PREMIÈRE ŒUVRE, rognée : elle donne à voir ce qu'on s'apprête à
 * traverser, et un clic dessus la remet d'aplomb au milieu de la salle. À
 * droite le cartel de salle et la note. Le rognage est assumé (`cover`) : c'est
 * le geste de l'accueil, et l'œil le reconnaît — la vraie image, entière, est
 * à un clic de là.
 *
 * Le seuil ne barre toujours rien : depuis l'accueil on entre à la première
 * œuvre, et on ne vient ici que si on le demande.
 */
.seuil {
  max-width: 64rem;
  display: grid;
  gap: clamp(1.25rem, 3.5vw, 3rem);
  align-items: center;
}

/*
 * LE TIRAGE DU SEUIL : la couverture de la série, à côté du texte, DANS SES
 * PROPORTIONS.
 *
 * ⚠️ RIEN N'EST ROGNÉ. La boîte prend le rapport du fichier (`--ratio`, posé
 * par le script à l'arrivée du tirage) et sa hauteur est bornée : c'est la
 * LARGEUR qui suit. Un rapport fixe aurait obligé à choisir entre rogner et
 * laisser des bandes, et le site s'interdit les deux dès qu'il montre une
 * photographie. D'où l'absence de fond : il n'y a jamais rien autour du
 * tirage à colorer.
 *
 * ⚠️ `justify-self: start` — sans quoi la boîte, étirée par la grille,
 * reprendrait la largeur de sa colonne et le rapport ne dirait plus rien.
 *
 * ⚠️ UNE SEULE IMAGE, et elle ne bouge pas. Un montage de trois tirages
 * superposés et flottants a été essayé en septembre 2026, à la demande de
 * l'artiste, puis RETIRÉ par elle : à l'écran, la superposition ne se lisait
 * pas. Ne pas le réintroduire sans demande explicite.
 */
.seuil__tirage {
  display: block;
  justify-self: start;
  aspect-ratio: var(--ratio, 1.5);
  /* En étroit, l'image cède de la place aux premières lignes de la note :
     c'est le texte qu'on est venu chercher ici. */
  height: min(26vh, 13rem);
  width: auto;
  max-width: 100%;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
}
.seuil__tirage img {
  display: block;
  width: 100%;
  height: 100%;
  /* `contain` et non `cover` : la boîte a déjà le rapport du fichier, et si la
     largeur de la colonne la resserre, c'est l'image entière qui se réduit —
     jamais un cadrage que l'artiste n'a pas choisi. */
  object-fit: contain;
  transition: opacity var(--transition);
}
@media (hover: hover) {
  .seuil__tirage:hover img { opacity: 0.85; }
}

/* En colonne, pour que la note puisse passer APRÈS les actions en étroit. */
.seuil__texte { min-width: 0; display: flex; flex-direction: column; }

/*
 * En étroit, la note se lit d'un bout à l'autre et c'est le PANNEAU qui défile.
 * Ses premières lignes affleurent sous l'image, « Entrer dans la série » vient
 * au bout — on lit, puis on entre, et c'est l'ordre juste pour un écran où l'on
 * est venu exprès lire. Le balayage entre de toute façon sans rien attendre.
 *
 * L'image, la note entière et le verbe ne tiennent pas ensemble dans un
 * téléphone : puisqu'il faut que quelque chose se mérite au défilement, que ce
 * soit la fin d'un texte, pas son début.
 */
.seuil__note { margin-top: 1.1rem; }
.seuil__note p { margin: 0 0 0.9em; }
.seuil__note p:last-child { margin-bottom: 0; }

/* Une note plus longue que sa boîte se coupe net au bas, en plein milieu d'une
   ligne : ça ne se lit pas comme « il y a une suite », ça se lit comme un
   défaut d'affichage. Le dégradé le dit. La classe est posée par le script —
   la feuille ne sait pas si un texte déborde. */
.seuil__note--deborde {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 2.75rem), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 2.75rem), transparent);
}

@media (min-width: 56rem) {
  /*
   * L'image prend EXACTEMENT sa largeur (`auto`), et le texte le reste. C'est
   * ce qui met de l'air entre les deux : une colonne en `fr` réservait la même
   * place à une couverture en hauteur qu'à un paysage, et le texte se
   * retrouvait collé à l'une, loin de l'autre. La gouttière, elle, est
   * franche — c'est une demande de l'artiste : le texte se lit à distance du
   * tirage, plus à droite dans la page.
   */
  .seuil {
    grid-template-columns: auto minmax(0, 1fr);
    gap: clamp(3rem, 7vw, 6rem);
  }
  .seuil__tirage { height: min(58vh, 30rem); }
  /* La mesure de lecture reste tenue : sans borne, la colonne de droite
     s'étirerait sur toute la place laissée par une couverture étroite. */
  .seuil__texte { max-width: 34rem; }
  /*
   * En large, l'inverse : le texte a une colonne à lui, et c'est la NOTE qui
   * défile dans sa boîte. Sans cette borne, une longue note pousserait « Entrer
   * dans la série » sous le bas de l'écran — et le seul verbe de la page se
   * trouverait là où personne ne le cherche.
   */
  .seuil__note {
    max-height: min(34vh, 22rem);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

.panneau__sur { margin: 0; }

.panneau__titre {
  font-size: clamp(1.9rem, 4.4vw, 3.1rem);
  max-width: 20ch;
  margin: 0.7rem 0 0;
}

.panneau__effectif { margin: 0.9rem 0 0; }

.panneau__phrase {
  margin: 1.1rem 0 0;
  max-width: 48ch;
  font-size: var(--t-lg);
  line-height: 1.7;
  color: #3a3a40;
}

.panneau__actions {
  margin-top: clamp(1.5rem, 4.5vh, 2.75rem);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.5rem;
}

/* La fin ne repart pas d'elle-même : c'est la différence entre une exposition
   qu'on quitte et un carrousel qui recommence. */
.panneau--fin .panneau__titre { font-size: clamp(1.6rem, 3.4vw, 2.4rem); }

/*
 * LES VUES DE L'EXPOSITION — la série accrochée, là où elle a été montrée.
 *
 * Un texte de salle d'abord (lieu, dates, quelques lignes), puis les vues à
 * HAUTEUR FIXE ET LARGEUR LIBRE, comme les autres vues d'une œuvre : une vue
 * de la salle entière et un détail de l'accrochage n'ont pas le même rapport,
 * et c'est la ligne de base commune qui fait la rangée. Rien n'est rogné.
 *
 * Le panneau est plus large que celui de la fin (une rangée de vues a besoin
 * de place), et il DÉFILE quand il y en a trop : c'est lui qui porte
 * `overflow-y: auto` (`.panneau`), et ↓ le parcourt.
 *
 * ⚠️ Aucune couleur n'y marque rien : le rubedo dit « l'image courante » et l'or
 * « la pièce unique » (§ 3). Ce ne sont ni l'une ni l'autre.
 */
.exposition { max-width: 64rem; }

.exposition__titre {
  font-size: clamp(1.6rem, 3.4vw, 2.4rem);
  margin: 0.7rem 0 0;
  max-width: 28ch;
}
.exposition__reperes { margin: 0.7rem 0 0; }
.exposition__texte { margin-top: 1rem; max-width: 40rem; }
.exposition__texte p { margin: 0 0 0.8em; }
.exposition__texte p:last-child { margin-bottom: 0; }

.exposition__video { margin-top: clamp(1.25rem, 3vh, 2rem); }
.exposition__video .piece-video {
  width: 100%;
  max-width: 36rem;
  background: var(--fond-2);
}

.exposition__vues {
  margin-top: clamp(1.25rem, 3.5vh, 2.25rem);
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 1.1rem;
}
.exposition__vue { max-width: 100%; min-width: 0; }
.exposition__cadre {
  display: block;
  padding: 0;
  border: 0;
  background: var(--fond-2);
  cursor: zoom-in;
  max-width: 100%;
}
.exposition__cadre img {
  display: block;
  height: clamp(6rem, 20vh, 11rem);
  width: auto;
  max-width: 100%;
  object-fit: contain;
  transition: opacity var(--transition);
}
@media (hover: hover) {
  .exposition__cadre:hover img { opacity: 0.86; }
}
.exposition__vue .annexe__legende { max-width: 18rem; }

/*
 * L'APERÇU DES EXPOSITIONS — sur le seuil et à la fin, pour dire qu'elles
 * existent. Quelques vues en cases carrées, et « +16 » sur la dernière quand il
 * en reste : à cette taille une vue désigne, elle ne se regarde pas (la même
 * raison que les couvertures de la sortie, rognées elles aussi).
 *
 * Le voile du « +16 » est de l'ENCRE, pas une couleur : le rubedo dit l'image
 * courante et l'or la pièce unique (§ 3). Ce n'est ni l'une ni l'autre.
 */
.apercu-expo {
  margin-top: clamp(1.25rem, 3.5vh, 2rem);
  display: grid;
  gap: 1rem;
  justify-items: start;
}
.apercu-expo__lien {
  display: grid;
  gap: 0.55rem;
  padding: 0;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  color: var(--plomb);
  transition: color var(--transition);
}
.apercu-expo__sur { display: block; }
.apercu-expo__vues { display: flex; gap: 0.4rem; }
.apercu-expo__vue {
  position: relative;
  display: block;
  width: 3.75rem;
  aspect-ratio: 1;
  background: var(--fond-2);
  overflow: hidden;
}
.apercu-expo__vue img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity var(--transition);
}
.apercu-expo__reste {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(22, 22, 26, 0.55);
  color: #faf9f7;
  font-family: var(--cartel);
  font-size: var(--t-base);
  letter-spacing: 0.04em;
}
@media (hover: hover) {
  .apercu-expo__lien:hover { color: var(--encre); }
  .apercu-expo__lien:hover .apercu-expo__vue img { opacity: 0.85; }
}

/*
 * LA SORTIE — à la fin d'une série, tout ce qu'il reste à voir, d'un coup d'œil.
 *
 * Sous les actions et derrière un filet, parce que ce n'est pas un verbe de
 * plus à peser au milieu des quatre autres : c'est ce qu'on fait ensuite.
 *
 * Les couvertures sont PETITES ici — on embrasse — et grandes dans le couloir
 * qui suit, où l'on choisit. Le nom est écrit dessous et ne s'efface pas : à
 * cette taille la photographie ne se regarde pas, elle désigne. C'est
 * exactement l'inverse de la carte d'accueil, et pour la même raison.
 *
 * ⚠️ Aucune couleur ne marque ces vignettes. Le rubedo dit « l'image
 * courante », l'or « la pièce unique » (§ 3) : les emprunter ici ferait dire
 * autre chose à la page. C'est l'encre qui répond au survol, et rien d'autre.
 */
.sortie {
  margin-top: clamp(1.5rem, 4vh, 2.25rem);
  padding-top: clamp(1.1rem, 3vh, 1.6rem);
  border-top: 1px solid var(--filet);
}
.sortie__sur { margin: 0 0 0.8rem; color: var(--plomb); }

.sortie__liste {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 1.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.sortie__item { min-width: 0; }

.sortie__serie {
  display: grid;
  gap: 0.45rem;
  width: 5.5rem;
  padding: 0;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  color: var(--plomb);
  transition: color var(--transition);
}
.sortie__vue {
  display: block;
  aspect-ratio: 3 / 4;
  background: var(--fond-2);
  overflow: hidden;
}
.sortie__vue img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: opacity var(--transition);
}
.sortie__nom { display: block; line-height: 1.25; }

@media (hover: hover) {
  .sortie__serie:hover { color: var(--encre); }
  .sortie__serie:hover .sortie__vue img { opacity: 0.85; }
}

/* ------------------------------------------------------------------ */
/* LE COULOIR : une porte par série, puis le contact                   */
/* ------------------------------------------------------------------ */

/*
 * Après la fin de parcours, le mur continue. Chaque porte montre une autre
 * série par sa COUVERTURE, au rapport 3/4 — la grammaire de l'accueil, reprise
 * telle quelle : le site n'a qu'une façon de présenter une série, et qui a vu
 * l'accueil n'a rien de neuf à apprendre ici.
 *
 * La hauteur est bornée en vh ET en rem (§ 3), et la LARGEUR se déduit de la
 * hauteur bornée : à rapport fixe, ne borner que la hauteur laisserait des
 * bandes de fond de part et d'autre du visuel.
 */
.porte {
  max-width: 64rem;
  display: grid;
  gap: clamp(1.25rem, 3.5vw, 3rem);
  align-items: center;
}

.porte__cadre {
  display: block;
  position: relative;
  width: min(100%, calc(min(34vh, 17rem) * 3 / 4));
  aspect-ratio: 3 / 4;
  background: var(--fond-2);
  overflow: hidden;
}
.porte__cadre img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity var(--transition), transform var(--transition);
}
@media (hover: hover) {
  .porte__cadre:hover img { opacity: 0.9; transform: scale(1.02); }
}

.porte__texte { min-width: 0; display: flex; flex-direction: column; }
.porte__sur { margin: 0; display: flex; flex-wrap: wrap; gap: 0.3rem 0.9rem; }
.porte__titre {
  font-size: clamp(1.7rem, 4vw, 2.7rem);
  max-width: 20ch;
  margin: 0.5rem 0 0;
}
.porte__reperes { margin: 0.6rem 0 0; }
.porte__effectif { margin: 0.35rem 0 0; }
.porte__phrase {
  margin: 1rem 0 0;
  max-width: 48ch;
  font-size: var(--t-lg);
  line-height: 1.7;
  color: #3a3a40;
}

/*
 * La note d'intention, repliée. `grid-template-rows: 0fr → 1fr` va vers une
 * hauteur qu'on ne connaît pas sans la mesurer, et n'anime qu'une piste de
 * grille — jamais une hauteur en pixels qu'il faudrait recalculer à chaque
 * redimensionnement. L'enfant porte `overflow: hidden`, sans quoi le texte
 * déborderait de la piste refermée au lieu d'être coupé par elle.
 *
 * ⚠️ Pas de `hidden` sur ce bloc : le couple `display`/`hidden` du § 8 s'est
 * déjà payé six fois, et une piste à zéro n'en a pas besoin.
 */
.porte__note {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 420ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.porte__note-corps { overflow: hidden; }
.porte__note-corps p { margin: 0 0 0.9em; }
.porte__note-corps p:first-child { margin-top: 1rem; }
.porte__note-corps p:last-child { margin-bottom: 0; }
.creneau.est-depliee .porte__note { grid-template-rows: 1fr; }

/* Le contact n'a pas d'image — ce n'est pas une série, et une image posée là
   dirait quelque chose de faux. Son texte prend donc toute la mesure. */
.porte--contact { max-width: 46rem; }

@media (min-width: 56rem) {
  .porte { grid-template-columns: minmax(0, 0.7fr) minmax(0, 1fr); }
  .porte__cadre { width: min(100%, calc(min(58vh, 30rem) * 3 / 4)); }
  .porte--contact { grid-template-columns: minmax(0, 1fr); }
}

/*
 * FRANCHIR — la couverture grandit et se dissout, puis la série s'ouvre.
 *
 * C'est la SEULE animation du couloir, et elle tombe au seul endroit qui la
 * mérite : le moment où l'on quitte la page. Le glissement du mur suffit
 * partout ailleurs, et deux mouvements de même force se neutraliseraient.
 * 520 ms, dans l'échelle des transitions de page ; `transform` et `opacity`,
 * rien d'autre, donc rien à repeindre.
 *
 * ⚠️ Le départ ne dépend pas d'elle (cf. `engager` dans serie.js) : une
 * minuterie court en parallèle, et sous mouvement réduit le lien part seul —
 * sans quoi un `animationend` qui ne vient jamais laisserait le visiteur devant
 * une porte qui ne s'ouvre pas.
 */
@keyframes franchir-la-porte {
  to { transform: scale(1.3); opacity: 0; }
}
@keyframes franchir-le-texte {
  to { opacity: 0; transform: translateY(0.75rem); }
}
.creneau.est-franchie .porte__cadre {
  animation: franchir-la-porte 520ms cubic-bezier(0.55, 0.06, 0.68, 0.19) forwards;
}
.creneau.est-franchie .porte__texte {
  animation: franchir-le-texte 300ms cubic-bezier(0.55, 0.06, 0.68, 0.19) forwards;
}

/* --- Les commandes de texte : un verbe, rien de plus ---------------- */

.action {
  font-family: var(--cartel);
  font-size: var(--t-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--plomb);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--filet);
  padding: 0.45rem 0 0.35rem;
  cursor: pointer;
  transition: color var(--transition), border-color var(--transition),
              background-color var(--transition);
}
.action:hover { color: var(--encre); border-bottom-color: var(--plomb-pale); }

/* Une seule action forte par panneau : celle qu'on attend du visiteur. */
.action--fort {
  color: var(--encre);
  border: 1px solid var(--encre);
  padding: 0.7rem 1.15rem;
}
.action--fort:hover { color: var(--fond); background: var(--encre); }

/*
 * L'effacement : après quelques secondes sans geste, tout fond sauf l'œuvre.
 * En opacité seule — la mise en page ne bouge pas, l'image ne saute pas.
 * Sur <body> parce que l'en-tête de série est hors de la visionneuse.
 *
 * ⚠️ Ce sont les PARTIES de l'en-tête qui fondent, pas l'en-tête : le TITRE est
 * la sortie vers l'accueil et doit rester visible, et l'opacité d'un parent ne
 * se rattrape pas sur un enfant. Une sortie qui disparaît au bout de trois
 * secondes n'est pas discrète, elle est introuvable.
 */
body.est-calme .entete-serie__liens,
body.est-calme .scene__pied {
  opacity: 0;
  transition: opacity 900ms ease;
}

.scene__pied {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2rem;
  min-height: 3.5rem;
  /*
   * Pendant la descente, la salle s'en va — et son cartel avec elle, au rythme
   * exact du défilement (`--descente`, posé par le script). Sans cela, le
   * tirage qui rétrécit passait DEVANT un cartel resté plein, et on lisait deux
   * fois le même titre à travers la photographie.
   */
  opacity: calc(1 - var(--descente, 0));
}

/*
 * Au seuil, le titre et les repères sont écrits en grand sur le mur, à trois
 * centimètres de là : l'en-tête s'efface plutôt que de les redire. Il l'avait
 * fait, puis avait dû les garder du temps où le titre PORTAIT la sortie — le
 * seuil étant le seul écran d'où l'on ne peut plus rien atteindre d'autre.
 * La sortie est maintenant le nom du site, qui reste : la règle « jamais deux
 * fois la même chose dans un écran » n'a plus à céder.
 */
body.est-au-seuil .entete-serie__identite {
  opacity: 0;
  transition: opacity var(--transition);
}

/* --- Le pavé de flèches : les touches du clavier, cliquables -------- */

.pave {
  display: grid;
  /* 2,9 rem, soit 46 px : au-dessus de la cible tactile de 44 px. La taille est
     un jeton parce que la grille ET les touches doivent la suivre — sur écran
     court elle tombe à 2,4 rem, et des pistes restées larges laisseraient les
     touches flotter dans leurs cases. */
  --touche: 2.9rem;
  /* Trois colonnes, deux rangées : la boîte du pavé ne dépend pas de l'ordre de
     remplissage, et les quatre touches gardent leur place quelle que soit celle
     qui est désactivée. */
  grid-template-columns: repeat(3, var(--touche));
  grid-template-rows: repeat(2, var(--touche));
  gap: 0.35rem;
  justify-items: center;
  flex: 0 0 auto;
  /* Le pavé reste à droite même quand le cartel disparaît (seuil, fin) : une
     commande qui change de place à chaque écran ne se retient pas. */
  margin-left: auto;
}
/* Une règle d'auteur bat la feuille du navigateur : sans cette ligne, `hidden`
   ne masque rien et une série vide gardait ses flèches (piège déjà payé sur
   .portail et .console, retrouvé ici au banc d'essai). */
.pave[hidden] { display: none; }

/*
 * AU DOIGT, PAS DE PAVÉ.
 *
 * Ces touches sont le clavier rendu cliquable — leur raison d'être est de dire
 * qu'un clavier existe. Sur un écran tactile il n'y en a pas : elles ne
 * montrent plus rien, et elles prennent la place de l'œuvre. Le geste les
 * remplace toutes : on balaie pour changer d'œuvre, on tape l'image pour ouvrir
 * sa fiche, on la fait défiler du doigt, la croix la referme.
 *
 * `hover: none` et non `pointer: coarse` : c'est l'absence de survol qui dit
 * qu'on est au doigt, et c'est la requête que le reste de la feuille emploie.
 */
@media (hover: none) {
  .pave { display: none; }
}

/*
 * T inversé, comme sur le clavier : ↑ seul au-dessus, ← ↓ → en dessous.
 *
 * Les quatre cases sont posées EXPLICITEMENT. Placer la seule ↑ en colonne 2 ne
 * suffisait pas : le placement automatique reprend après la dernière case
 * occupée, si bien que ← tombait à côté de ↑ sur la rangée haute et qu'on
 * lisait « ↑ ← » puis « ↓ → » — deux rangées de deux, plus aucun rapport avec
 * un clavier. Constaté en production.
 */
.pave__touche--haut   { grid-area: 1 / 2; }
.pave__touche--gauche { grid-area: 2 / 1; }
.pave__touche--bas    { grid-area: 2 / 2; }
.pave__touche--droite { grid-area: 2 / 3; }

/* Assez grande pour se voir sans se faire remarquer — c'est la seule commande
   de la salle. La taille vient du jeton `--touche` posé sur le pavé. */
.pave__touche {
  font-family: var(--cartel);
  font-size: var(--t-base);
  line-height: 1;
  width: var(--touche);
  height: var(--touche);
  display: grid;
  place-items: center;
  background: var(--fond);
  border: 1px solid var(--filet);
  border-bottom-width: 2px;   /* l'épaisseur d'une touche */
  border-radius: 4px;
  color: var(--plomb);
  cursor: pointer;
  transition: color var(--transition), border-color var(--transition),
              transform 120ms ease;
}
.pave__touche:hover:not(:disabled) { color: var(--encre); border-color: var(--plomb-pale); }
.pave__touche:active:not(:disabled) { transform: translateY(1px); border-bottom-width: 1px; }
.pave__touche:disabled { opacity: 0.3; cursor: default; }

/*
 * L'INVITE — montrer la commande, une fois.
 *
 * Rien dans la salle ne dit que le pavé et le clavier marchent : il n'y a ni
 * barre, ni pellicule, ni bouton « suivant ». La touche bat donc DANS SA PROPRE
 * DIRECTION — → part à droite, ↓ descend —, de trois pixels, avec un halo qui
 * s'ouvre et se dissipe. C'est le geste qu'elle demande, en petit.
 *
 * Trois battements et plus rien : une invite qui insiste devient une réclame.
 * Le script la retire dès que le geste est fait. L'encre et le filet passent au
 * registre du survol pendant ce temps — le mouvement seul se raterait d'un œil
 * posé ailleurs sur l'image.
 *
 * Sous `prefers-reduced-motion`, la règle globale ramène la durée à presque
 * rien : le script n'invite alors pas du tout, sinon `animationend` ne
 * viendrait jamais lever l'interdiction d'effacement de la salle.
 */
@keyframes invite {
  0%, 100% { transform: translate(0, 0);
             box-shadow: 0 0 0 0 rgba(22, 22, 26, 0); }
  40%      { transform: translate(var(--vers-x, 0), var(--vers-y, 0));
             box-shadow: 0 0 0 0.3rem rgba(22, 22, 26, 0.07); }
  72%      { transform: translate(0, 0);
             box-shadow: 0 0 0 0.5rem rgba(22, 22, 26, 0); }
}
.pave__touche--invite {
  animation: invite 1.2s ease-in-out 3;
  color: var(--encre);
  border-color: var(--plomb-pale);
}
.pave__touche--droite.pave__touche--invite { --vers-x: 0.2rem; }
.pave__touche--bas.pave__touche--invite    { --vers-y: 0.2rem; }

/* --- Cartel -------------------------------------------------------- */

.cartel-oeuvre {
  max-width: 38rem;
  margin: 0;
  display: grid;
  gap: 0.35rem;
  transition: opacity var(--transition);
}
.cartel-oeuvre[hidden] { display: none; }

.compteur { font-variant-numeric: tabular-nums; }
.compteur b { font-weight: 400; color: var(--encre); }

.cartel-oeuvre__titre {
  font-family: var(--display);
  font-size: var(--t-lg);
  line-height: 1.2;
  margin: 0;
}
.cartel-oeuvre__titre:empty { display: none; }

.cartel-oeuvre__ligne { display: flex; flex-wrap: wrap; gap: 0.35rem 1.1rem; }

.cartel-oeuvre__note {
  font-size: var(--t-sm);
  color: var(--plomb);
  margin: 0;
  max-width: 46ch;
}

/* Le procédé, en toutes lettres. La pastille de couleur qui l'accompagnait
   désignait la matière du cadre : sans cadre, elle ne disait plus rien. */
.etat { display: inline-flex; align-items: center; }

.unique { color: var(--or); }

/* --- Boutons utilitaires (partagés avec le back office) ------------ */

.bouton {
  font-family: var(--cartel);
  font-size: var(--t-xs);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--plomb);
  background: transparent;
  border: 1px solid var(--filet);
  padding: 0.45rem 0.8rem;
  cursor: pointer;
  transition: color var(--transition), border-color var(--transition);
}
.bouton:hover { color: var(--encre); border-color: var(--plomb-pale); }
.bouton[aria-pressed='true'] { color: var(--encre); border-color: var(--rubedo); }

kbd {
  font-family: var(--cartel);
  font-size: var(--t-xs);
  border: 1px solid var(--filet);
  padding: 0.15rem 0.4rem;
  color: var(--plomb);
}

/* --- Bandeau de tête de série -------------------------------------- */

/*
 * Dans le flux de la grille, jamais par-dessus l'image — et TROIS PLACES :
 * le nom du site à gauche (la sortie), le titre de la série AU MILIEU (où l'on
 * est), les sorties de la série à droite.
 *
 * EN LARGE, une rangée de trois colonnes. Les deux colonnes de bord sont en
 * `1fr` : c'est ce qui met le titre au milieu de la PAGE et non au milieu de ce
 * qui reste, quelle que soit la longueur du nom ou du nombre de liens.
 *
 * EN ÉTROIT, deux rangées : le nom et les liens se partagent la première, le
 * titre prend la seconde, centré. Trois choses côte à côte ne tiennent pas dans
 * 320 px, et c'est le titre qui mérite la ligne entière — c'est lui qu'on lit.
 *
 * ⚠️ La bascule est à 56 rem, pas à 48 : pour que le titre soit au MILIEU, les
 * deux colonnes de bord valent chacune la plus large des deux (les liens, ~16
 * rem). Sous 56 rem, la rangée de trois ne tient donc qu'en enroulant les liens
 * sur deux lignes — alors que la forme à deux rangées, elle, y tient sans rien
 * plier. On bascule quand ça tient, pas quand ça rentre.
 *
 * `auto` pour le nom, `minmax(0, 1fr)` pour les liens : le nom passe d'abord —
 * c'est la sortie. Sans cela, à 320 px, sa colonne tombait à ZÉRO de large.
 */
.entete-serie {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    "nom   liens"
    "titre titre";
  align-items: baseline;
  gap: 0.45rem 1rem;
  padding: 1.1rem var(--gouttiere) 0.8rem;
  transition: opacity var(--transition);
}

@media (min-width: 56rem) {
  .entete-serie {
    grid-template-columns: minmax(0, 1fr) minmax(0, auto) minmax(0, 1fr);
    grid-template-areas: "nom titre liens";
    padding-top: 1.4rem;
    padding-bottom: 0.9rem;
  }
}

/*
 * SOUS 24 rem (384 px), trois rangées. À côté du nom, les liens n'ont plus que
 * cent soixante pixels : ils se coupaient AU MILIEU DES MOTS — « NOTE /
 * D'INTENTION » —, et l'en-tête montait à cent dix-sept pixels pour dire deux
 * choses. Une rangée à eux les tient d'un trait.
 *
 * L'approche descend à 0,1 em et la gouttière à 0,9 rem : à 320 px, les deux
 * liens font 286 px pour 280 disponibles — six pixels de trop suffisaient à les
 * renvoyer à la ligne. Le registre reste le même, l'œil ne voit pas la
 * différence, et la barre tient. C'est la largeur où la navigation du site casse
 * aussi (§ 8) : on l'éprouve, on ne la suppose pas.
 */
@media (max-width: 24rem) {
  .entete-serie {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "nom" "titre" "liens";
  }
  .entete-serie__liens { gap: 0.3rem 0.9rem; }
  .entete-serie__liens .entete__lien {
    white-space: nowrap;
    letter-spacing: 0.1em;
  }
}

/*
 * LE NOM DU SITE — et c'est la sortie. Même registre que l'en-tête des autres
 * pages (`.entete__nom`) : capitales, approche large, graisse légère. Une
 * signature gravée, pas un logotype. C'est le seul élément de cette barre qui ne
 * s'efface jamais.
 */
.entete-serie__nom {
  grid-area: nom;
  /* `start` : sa colonne fait un tiers de la page, pas lui. Étiré, il offrait un
     soulignement de survol de six cents pixels et une cible à l'avenant. */
  justify-self: start;
  font-family: var(--display);
  font-weight: 300;
  font-size: clamp(0.8rem, 1.1vw, 0.95rem);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--encre);
  border-bottom: 1px solid transparent;
  transition: border-color var(--transition);
  white-space: nowrap;
}
.entete-serie__nom:hover { border-bottom-color: var(--encre); }

/* Le titre de la série : où l'on est. Un repère, pas un bouton. */
.entete-serie__identite {
  grid-area: titre;
  min-width: 0;
  text-align: center;
}
.entete-serie__titre {
  font-family: var(--display);
  font-size: var(--t-base);
  line-height: 1.2;
  margin: 0;
  overflow-wrap: anywhere;
}
.entete-serie__reperes { margin: 0.25rem 0 0; }

.entete-serie__liens {
  grid-area: liens;
  justify-self: end;
  display: flex;
  align-items: baseline;
  /* Elle s'enroule, et ce n'est pas une précaution de principe : à 320 px, deux
     liens et le nom du site ne tiennent pas sur une rangée. */
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.3rem clamp(0.9rem, 2.5vw, 2rem);
}

/* Le même registre que les liens de navigation, sur un bouton. */
button.entete__lien {
  background: none;
  border: 0;
  border-bottom: 1px solid transparent;
  cursor: pointer;
}

/* --- Le détail : sous la salle, dans le même défilement ------------- */

/*
 * IL N'Y A PLUS DE PANNEAU QUI SE LÈVE. Le détail est la suite de la page : on
 * descend, et ce qui monte sous les yeux n'est pas un autre écran mais le bas de
 * celui-ci. Un seul défilement — la molette, le doigt, ↓ et la barre d'espace
 * font tous la même chose.
 *
 * ⚠️ LE FOND DOIT RESTER UNI. La salle fait un écran plein et se termine sur
 * `--mur-bas` ; ce bloc REPREND cette valeur en haut et fond vers `--fond` sur
 * une dizaine de rem. C'est exactement la ligne franche qui avait fait retirer
 * le pied de page (cf. CLAUDE.md § 8) : un bloc de pleine hauteur avec un fond à
 * lui ne supporte rien en dessous qui ait un autre fond — ou bien on partage le
 * fond, ou bien il n'y a rien dessous. Ici, on partage.
 */
.detail {
  --air-detail: clamp(2rem, 6vh, 4rem);
  position: relative;
  /* UN ÉCRAN AU MOINS. La descente n'est achevée que lorsque le détail atteint
     le haut de la fenêtre ; un bloc plus court que l'écran ne laisserait jamais
     la salle sortir, et la vignette n'arriverait jamais dans son coin — elle
     resterait figée à mi-chemin, entre deux tailles. */
  min-height: 100svh;
  padding: var(--air-detail) var(--gouttiere) clamp(4rem, 10vh, 7rem);
  background-color: var(--fond);
  background-image: linear-gradient(180deg, var(--mur-bas) 0, var(--fond) 100%);
  background-repeat: no-repeat;
  background-size: 100% clamp(8rem, 22vh, 14rem);
}
.detail[hidden] { display: none; }

.detail__corps {
  max-width: var(--mesure);
  margin: 0 auto;
  display: grid;
  /* En étroit : la vignette à gauche du cartel, le texte en pleine mesure
     dessous. Un téléphone n'a pas de place pour une colonne collante, et un
     élément qui se fige en haut d'un petit écran recouvre ce qu'on lit. */
  grid-template-columns: 6.5rem minmax(0, 1fr);
  gap: 1rem clamp(0.9rem, 3vw, 1.5rem);
  align-items: start;
}

/*
 * LA VIGNETTE — la photographie rappelée en petit, pour le contexte. Un bouton
 * qui rend la salle (le geste de ↑ en haut du détail), remis à nu ; elle a été
 * un lien vers la page de l'œuvre, et ce chemin n'existe plus.
 *
 * Le bouton ne bouge JAMAIS : c'est lui qu'on mesure pour savoir où la
 * réduction doit arriver. C'est l'élément intérieur qui porte la transformation.
 * Mesurer une boîte qu'on vient de transformer ne dirait plus où elle DEVRAIT
 * être, et l'erreur s'accumulerait d'une image à l'autre.
 *
 * Le rapport (`--ratio`) est posé par le script, et c'est celui du tirage : sans
 * lui, la réduction ne serait pas une réduction mais une déformation.
 */
.detail__vignette {
  grid-column: 1;
  grid-row: 1;
  display: block;
  position: relative;
  width: 100%;
  aspect-ratio: var(--ratio, 1.5);
  /* Conteneur de la vignette : son ombre (sur `.detail__mobile`) se mesure en
     `cqw` de cette case, pour rester proportionnée au tirage comme en salle. */
  container-type: inline-size;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  /*
   * ⚠️ TANT QU'ON N'A PAS DESCENDU, ELLE N'EXISTE PAS. À la part zéro, la
   * vignette recouvre EXACTEMENT le grand tirage — c'est ce qui rend la bascule
   * invisible —, et une image posée sur une autre lui prend tout : le survol
   * (donc la loupe, qu'on ne verrait plus jamais), le clic, le menu contextuel.
   * Elle est donc effacée et transparente aux gestes jusqu'au premier pixel de
   * descente, et ne redevient cliquable qu'une fois rangée dans son coin.
   */
  opacity: 0;
  pointer-events: none;
}
body.est-en-descente .detail__vignette { opacity: 1; }
body.est-en-detail .detail__vignette { pointer-events: auto; }
.detail__mobile {
  position: absolute;
  inset: 0;
  display: block;
  /* Le coin haut-gauche : la réduction part de là et y arrive. */
  transform-origin: top left;
  will-change: transform;
}
.detail__mobile img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.detail__cartel { grid-column: 2; grid-row: 1; }
.detail__sur { margin: 0; display: flex; flex-wrap: wrap; gap: 0.3rem 1rem; }
.detail .fiche {
  grid-column: 1 / -1;
  grid-row: 2;
  margin-top: clamp(1.25rem, 4vh, 2rem);
}

/*
 * En large, DEUX COLONNES : la vignette à gauche, COLLANTE — elle reste sous les
 * yeux pendant qu'on lit, et c'est tout l'intérêt de la mettre à côté plutôt
 * qu'au-dessus. `align-items: start` est ce qui lui laisse de quoi coller : un
 * élément de grille étiré fait toute la hauteur de sa piste et n'a nulle part où
 * glisser.
 *
 * Son décalage collant vaut l'air du haut du bloc : la vignette se fige donc
 * PILE quand le détail atteint le haut de l'écran, c'est-à-dire à l'instant où
 * la descente s'achève. Changer l'un sans l'autre ferait sauter l'arrivée.
 */
@media (min-width: 56rem) {
  .detail__corps {
    grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
    /*
     * `auto 1fr`, et ce n'est pas du remplissage : la vignette occupe LES DEUX
     * rangées, et une grille répartit la hauteur d'un tel élément entre les
     * rangées qu'il traverse. En `auto auto`, le cartel se retrouvait poussé à
     * cent cinquante pixels de son propre texte, pour la seule raison qu'une
     * image haute passait à côté. La rangée souple absorbe ce surplus.
     */
    grid-template-rows: auto 1fr;
    /* Pas d'espace entre les rangées : la fiche pose elle-même le sien, et une
       gouttière large à la verticale séparait le cartel de son propre texte. */
    gap: 0 clamp(2rem, 4vw, 4rem);
  }
  .detail__vignette { grid-row: 1 / span 2; position: sticky; top: var(--air-detail); }
  .detail .fiche { grid-column: 2; grid-row: 2; margin-top: clamp(1.75rem, 4vh, 2.5rem); }
}

/*
 * La lane du pavé : il flotte au-dessus de ce qui défile, et sans cette réserve
 * il viendrait se poser sur les lignes qu'on lit. Là où il n'y a pas de survol,
 * il n'existe pas — et la réserve non plus.
 */
@media (hover: hover) and (min-width: 48rem) {
  .detail__cartel, .detail .fiche { padding-right: 10.5rem; }
}

/*
 * Le pavé du détail : le même qu'en salle, au même endroit. Il ne paraît qu'une
 * fois la descente engagée — dans la salle, c'est le pavé de la salle qui
 * commande, et deux pavés à la même place ne peuvent pas répondre tous les deux.
 *
 * `visibility` et pas seulement `opacity` : un bouton transparent reste dans
 * l'ordre de tabulation, et le focus irait se poser sur une touche qu'on ne voit
 * pas (piège déjà payé sur les créneaux hors écran). Elle se transitionne par
 * paliers — elle apparaît au début du fondu et disparaît à la fin.
 */
.pave--detail {
  position: fixed;
  right: var(--gouttiere);
  bottom: 1.5rem;
  z-index: 60;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--transition), visibility var(--transition);
}
body.est-en-detail .pave--detail { opacity: 1; visibility: visible; }

/*
 * Dès le premier pixel de descente, le grand tirage s'efface : la vignette le
 * recouvre alors EXACTEMENT, et la bascule ne se voit pas. Sans cela, les deux
 * images se superposeraient en se décalant.
 */
body.est-en-descente .scene__voile { opacity: 0; }

/* --- La fiche : ce qu'on lit à côté de l'œuvre ---------------------- */

/*
 * Sans marge intérieure : elle est posée dans le détail de la salle comme dans
 * la page de l'œuvre, et c'est à chacun de dire son air. Un bloc vide n'existe
 * pas — le script n'écrit que ce qui est renseigné.
 */
.fiche {
  max-width: 48rem;
  min-width: 0;
  display: grid;
  gap: clamp(1.75rem, 4vh, 2.5rem);
}

.fiche__bloc { min-width: 0; }

.fiche__statut { margin: 0; color: var(--encre); }

/*
 * Le prix, sous la disponibilité — la ligne « Disponible — tirage n° 2 / 29 »
 * et lui se répondent, on ne les sépare pas.
 *
 * Écrit dans la fonte des cartels, à peine plus grand que le reste, sans gras
 * ni couleur : l'or dit la pièce unique et le rubedo l'image courante, les
 * emprunter ici ferait dire au chiffre autre chose que lui-même. Il n'est pas
 * en capitales — « 1 200 € » n'y gagnerait rien, et « sur demande » y perdrait
 * le ton de la phrase.
 */
.fiche__prix {
  margin: 0.9rem 0 0;
  font-family: var(--cartel);
  font-size: var(--t-base);
  font-variant-numeric: tabular-nums;
  color: var(--encre);
}

/* --- La pièce en mouvement ------------------------------------------- */

/*
 * La façade d'une vidéo : l'affiche, un triangle, et le fichier qui n'arrive
 * qu'au clic. La boîte prend le rapport de son affiche (`--ratio`), puis celui
 * du fichier quand il est connu — une vidéo verticale sous une œuvre
 * horizontale ne doit pas ouvrir deux bandes noires.
 *
 * Elle sert à deux endroits, et le second lui ajoute sa propre taille : dans la
 * fiche d'une œuvre (ici), et sur le seuil d'une série, où elle porte en plus
 * `.seuil__tirage` — c'est pourquoi la hauteur est bornée et non imposée.
 */
.piece-video {
  position: relative;
  display: block;
  aspect-ratio: var(--ratio, 1.5);
  overflow: hidden;
}
/*
 * ⚠️ LA TAILLE EST DONNÉE PAR LE CONTEXTE, jamais par la façade elle-même.
 * Posée sur `.piece-video`, une largeur aurait écrasé celle du seuil — qui est
 * bornée en HAUTEUR (`.seuil__tirage`, plus haut dans cette feuille) et dont la
 * largeur suit le rapport du fichier. Deux règles de taille pour une même boîte,
 * et c'est la dernière écrite qui l'emporte, sans rapport avec ce qu'on voulait.
 */
.fiche .piece-video {
  width: 100%;
  max-width: 32rem;
  background: var(--fond-2);
}
.piece-video img,
.piece-video video {
  display: block;
  width: 100%;
  height: 100%;
  /* `contain` : le cadre a déjà le bon rapport, et si la colonne le resserre
     c'est l'image entière qui se réduit — jamais un cadrage qu'on n'a pas
     choisi. Le même égard que partout ailleurs sur ce site. */
  object-fit: contain;
}
.piece-video.est-en-lecture { background: var(--encre); }

/* --- Les autres vues d'une pièce ------------------------------------ */

/*
 * L'encadrement, l'accrochage, un détail du tirage. Ce sont des vues DE
 * l'œuvre, pas des œuvres : elles se tiennent donc en petit, sous le texte,
 * là où l'on a fini de lire — jamais dans la salle, où elles feraient perdre
 * le compte des créneaux.
 *
 * Un nom de rubrique, parce qu'une rangée de vignettes sans mot au-dessus se
 * lirait comme « la suite de la série ». C'est le seul intertitre de la fiche,
 * et il est au registre du cartel : on l'entend, on ne l'écoute pas.
 */
.fiche__rubrique {
  margin: 0 0 0.9rem;
  color: var(--plomb);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: var(--t-xs);
}

/*
 * Des vignettes à hauteur fixe et à largeur libre : chaque vue garde SES
 * proportions (une prise verticale du cadre, une horizontale de l'accrochage),
 * et c'est la ligne de base commune qui fait la rangée. Rogner les aurait
 * toutes ramenées au même rectangle — exactement ce que la vue d'ensemble se
 * refuse à faire.
 */
.annexes {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 1.1rem;
}

.annexe { max-width: 100%; }

.annexe__cadre {
  display: block;
  padding: 0;
  border: 0;
  background: var(--fond-2);
  cursor: zoom-in;
  max-width: 100%;
}
.annexe__cadre img {
  display: block;
  height: clamp(5.5rem, 16vh, 8rem);
  width: auto;
  max-width: 100%;
  object-fit: contain;
  transition: opacity var(--transition);
}
@media (hover: hover) {
  .annexe__cadre:hover img { opacity: 0.86; }
}
.annexe__cadre:focus-visible { outline: 2px solid var(--rubedo); outline-offset: 3px; }

/* La légende suit la largeur de sa vignette, et se tait quand il n'y en a pas
   — un champ vide ne s'affiche pas, et ne laisse pas sa gouttière. */
.annexe__legende {
  margin: 0.45rem 0 0;
  max-width: 16rem;
  color: var(--plomb);
}

.fiche__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.5rem;
}
/* L'adresse en clair, pour qui n'a pas de client de messagerie : le `mailto`
   ne doit pas être la seule porte. */
.fiche__adresse { flex-basis: 100%; margin: 0; }

/* Le lien, montré quand le presse-papiers refuse : sélectionné, prêt à copier. */
.fiche__lien {
  flex-basis: 100%;
  margin: 0;
  font-family: var(--cartel);
  font-size: var(--t-xs);
  color: var(--plomb);
  word-break: break-all;
  user-select: all;
}
.fiche__lien[hidden] { display: none; }

/*
 * LE LIEN DE L'ŒUVRE, sous le cartel de salle et sur toutes les œuvres : c'est
 * la seule chose de cette page qu'on transmet à quelqu'un, et il ne doit pas
 * falloir descendre pour la trouver. Un verbe (« Copier le lien »), serré, en
 * retrait — il ne doit pas peser autant que le cartel qu'il suit.
 */
.cartel-oeuvre__actions { margin-top: 0.15rem; gap: 0.4rem 1.25rem; }
.cartel-oeuvre__actions .action { padding: 0.2rem 0 0.15rem; }

/*
 * AU DOIGT, LA SALLE DIT SES GESTES — demandé par l'artiste (septembre 2026).
 *
 * Sans pavé, rien ne disait qu'on pouvait balayer ni qu'un détail attendait
 * dessous : le visiteur descendait, lisait la première œuvre, et ne voyait
 * jamais la deuxième. Trois ajouts, tous du TEXTE dans le registre du cartel —
 * ni barre, ni pastilles, ni touches :
 *   - ‹ › autour du compteur (`.compteur__pas`) : le balayage, en toutes
 *     lettres, là où l'on lit déjà « 01 / 19 » ;
 *   - « Détail de l'œuvre ↓ » dans le cartel : le verbe de ↓ ;
 *   - les voisines au bas du détail : on n'y reste pas coincé.
 *
 * Masqués par défaut et montrés sous `hover: none` seulement — la requête qui
 * masque le pavé : là où il y a un survol, le pavé et le clic sur l'œuvre
 * disent déjà tout cela, et le redire serait écrire deux fois (règle 4).
 */
.compteur__pas,
.cartel-oeuvre__detail,
.detail__voisines { display: none; }

@media (hover: none) {
  /* La case de toucher fait ~44 px, mais la ligne du cartel ne grandit pas :
     les marges négatives rendent en hauteur ce que le rembourrage prend. */
  .compteur__pas {
    display: inline-block;
    font: inherit;
    font-size: 1.2em;
    line-height: 1;
    color: var(--plomb);
    background: none;
    border: 0;
    padding: 0.8rem 0.7rem;
    margin: -0.8rem 0;
    cursor: pointer;
  }
  /* Le premier chevron s'aligne sur le bord du cartel, pas sur sa case. */
  .compteur__pas:first-child { margin-left: -0.7rem; }

  .cartel-oeuvre__detail { display: inline-block; color: var(--encre); }

  .detail__voisines {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 1rem 1.5rem;
    max-width: var(--mesure);
    margin: clamp(2.5rem, 7vh, 4rem) auto 0;
    padding-top: 1.1rem;
    border-top: 1px solid var(--filet);
  }
  /* Seule, la suivante reste à droite : c'est le sens de la lecture. */
  .detail__voisines [data-voisine="1"] { margin-left: auto; }

  /*
   * La salle ne s'efface plus au doigt. Sur un téléphone le cartel est SOUS le
   * tirage, pas dessus : l'effacer ne rendait rien à l'image, et retirait
   * justement ce qui dit comment avancer. Et pour le faire revenir, il fallait
   * toucher l'écran — où un tap sur l'image ouvre le plein écran.
   */
  body.est-calme .scene__pied { opacity: calc(1 - var(--descente, 0)); }
}

/*
 * L'INVITE AU DOIGT : le tirage de la première œuvre glisse d'un pas de pouce
 * vers la gauche et revient, deux fois — le balayage, montré par ce qu'on
 * balaie. `translate` et non `transform` : l'accroche porte déjà l'inclinaison
 * et l'entrée en scène, et la propriété à part se compose avec elles au lieu
 * de les écraser. Le délai laisse finir l'entrée en scène (650 ms).
 */
@keyframes invite-balayage {
  0%, 100% { translate: 0 0; }
  38%      { translate: -1.6rem 0; }
  70%      { translate: 0 0; }
}
.oeuvre__cadre--invite {
  animation: invite-balayage 1.3s cubic-bezier(0.45, 0, 0.35, 1) 0.9s 2;
}

/* --- La loupe : 1 pixel du fichier pour 1 pixel d'écran -------------- */

/*
 * Elle agit partout où un tirage porte la classe `.tirage` : dans la salle et
 * sur la page de l'œuvre. Le curseur ne la propose que si ça agrandit
 * (`est-agrandissable`, posé par le script d'après la définition du fichier).
 * Sous la loupe, le cadre devient une fenêtre qui défile sur l'image à sa taille
 * de fichier (`--loupe`, en pixels CSS = pixels du fichier / densité de l'écran).
 *
 * `safe center` : une image plus petite que le cadre dans un axe reste centrée,
 * une image plus grande part du bord — un centrage ordinaire en ferait sortir le
 * début sans moyen d'y revenir (piège payé sur le seuil).
 */
.tirage.est-agrandissable { cursor: zoom-in; }
.tirage.est-en-loupe {
  /* On est DANS l'image et le curseur y désigne un point : ni « zoom-in », qui
     mentirait, ni « pointer », qui promettrait un clic alors qu'on regarde. */
  cursor: crosshair;
  /*
   * `hidden` et non `auto` : le cadre est défilé PAR LE SCRIPT, qui suit le
   * curseur. En `auto`, la molette passée sur le tirage aurait fait glisser
   * l'image au lieu de faire descendre la page — et c'est la page qu'on lit.
   */
  overflow: hidden;
  display: flex;
  justify-content: center;
  justify-content: safe center;
  align-items: center;
  align-items: safe center;
}
.tirage.est-en-loupe img {
  position: static;
  flex: none;
  width: var(--loupe);
  height: auto;
  max-width: none;
  object-fit: fill;
}

/*
 * Les images ne se tirent pas hors de la page, ne se sélectionnent pas, et le
 * toucher long d'iOS n'ouvre pas sa feuille « Enregistrer l'image ». Le menu
 * contextuel est retiré par site.js. C'est un FREIN, pas une serrure : ce qui
 * s'affiche se capture, et un navigateur garde toujours l'adresse du fichier.
 * La protection réelle est la définition des fichiers — cf. CMS.md § 4. Le
 * back office n'est pas concerné : on y manipule ses propres images.
 *
 * Le cadre de l'œuvre y figure encore, bien qu'il ne soit plus un lien : c'est
 * lui que le doigt tient pendant un balayage, et une sélection qui partirait de
 * là emporterait le tirage avec elle.
 */
body:not(.bo) img,
body:not(.bo) canvas,
body:not(.bo) .oeuvre__cadre {
  -webkit-user-drag: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/* --- La page d'une œuvre -------------------------------------------- */

/*
 * Une pièce, une adresse. Deux colonnes quand il y a de quoi : le tirage à
 * gauche, collant, et ce qui s'en dit à droite — la même forme que le détail de
 * la salle, parce que c'est la même chose qu'on lit.
 */
.piece[hidden] { display: none; }
.piece__introuvable { color: var(--plomb); }
.piece__introuvable[hidden] { display: none; }

.piece__retour {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.25rem;
  margin: 0 0 clamp(1.25rem, 3vh, 2rem);
}

.piece__corps {
  display: grid;
  gap: clamp(1.5rem, 4vh, 2.25rem);
  align-items: start;
}

/* `inset: 0` + `contain` : le motif prouvé — un max-height en pourcentage dans
   une rangée `1fr` retombe en `auto` et laisse déborder (piège payé). Le rapport
   vient du script, au chargement du fichier. */
.piece__cadre {
  position: relative;
  width: 100%;
  aspect-ratio: var(--ratio, 1.5);
  max-height: 72svh;
}
.piece__cadre img,
.piece__cadre canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.piece__cartel { max-width: 44rem; }

@media (min-width: 64rem) {
  .piece__corps {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    /* `auto 1fr` : le tirage traverse les deux rangées, et c'est la rangée
       souple qui absorbe sa hauteur — sinon le cartel se retrouve poussé loin
       de son propre texte. Cf. `.detail__corps`. */
    grid-template-rows: auto 1fr;
    gap: 0 clamp(2rem, 4vw, 4rem);
  }
  /* Le tirage reste sous les yeux pendant qu'on lit : c'est tout l'intérêt de
     le mettre à côté du texte plutôt qu'au-dessus. `align-items: start` lui
     laisse de quoi coller — un élément de grille étiré n'a nulle part où
     glisser. */
  .piece__cadre { grid-column: 1; grid-row: 1 / span 2; position: sticky; top: clamp(1rem, 3vh, 2rem); }
  .piece__cartel { grid-column: 2; grid-row: 1; }
  .piece__corps .fiche { grid-column: 2; grid-row: 2; margin-top: clamp(1.75rem, 4vh, 2.5rem); }
}

/* --- Le plein écran, au doigt -------------------------------------- */

/*
 * Le tirage seul, sur fond sombre, et le PINCH DU NAVIGATEUR pour l'agrandir.
 * `touch-action: pinch-zoom` le dit explicitement : rien d'autre ne se passe
 * ici, ni glissement de mur, ni défilement de fiche — on regarde une image.
 *
 * Il ne s'ouvre QUE là où il n'y a pas de survol : sur pointeur fin, la loupe
 * fait mieux, sans quitter le texte qu'on lisait.
 *
 * ⚠️ Le couple `display` / `hidden` : une règle d'auteur bat la feuille du
 * navigateur, et sans cette ligne le panneau resterait à l'écran (piège payé
 * six fois — cf. CLAUDE.md § 8).
 */
.plein {
  position: fixed;
  inset: 0;
  z-index: 95;
  display: grid;
  background: #0f0f12;
  touch-action: pinch-zoom;
}
.plein[hidden] { display: none; }

.plein__cadre { position: relative; min-width: 0; }
.plein__cadre img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/*
 * LES VUES D'UNE EXPOSITION, EN GRAND — pensées pour le téléphone d'abord.
 *
 * En haut, la ligne de l'exposition (lieu · événement · dates) ; en bas, la
 * légende de la vue et son rang (« 3 / 20 ») ; ‹ › de part et d'autre. Au
 * doigt c'est le BALAYAGE qui parcourt, mais rien d'autre ne dirait qu'il y a
 * une suite : les boutons restent, petits et posés sur les bords.
 *
 * ⚠️ Le cadre se RETIRE du haut et du bas (`.plein--galerie`) : sur un
 * téléphone en hauteur, une vue en largeur passerait sinon sous la ligne de
 * l'exposition. La croix, elle, garde son coin — la ligne lui laisse sa place.
 *
 * ⚠️ `display` d'auteur + `hidden` : chaque règle `[hidden]` suit (CLAUDE.md § 8).
 */
.plein--galerie .plein__cadre {
  margin: calc(4.25rem + env(safe-area-inset-top, 0px)) 0 calc(3.25rem + env(safe-area-inset-bottom, 0px));
}

.plein__legende {
  position: absolute;
  top: calc(1rem + env(safe-area-inset-top, 0px));
  left: 1rem;
  right: 4.25rem;
  min-height: 2.6rem;
  margin: 0;
  display: flex;
  align-items: center;
  color: #edeae5;
  line-height: 1.45;
  pointer-events: none;
}
.plein__legende[hidden] { display: none; }

.plein__pas {
  position: absolute;
  top: 50%;
  z-index: 1;
  translate: 0 -50%;
  width: 2.75rem;
  height: 2.75rem;
  display: grid;
  place-items: center;
  font-family: var(--cartel);
  font-size: 1.3rem;
  line-height: 1;
  background: rgba(15, 15, 18, 0.55);
  border: 1px solid rgba(237, 234, 229, 0.35);
  border-radius: 50%;
  color: #edeae5;
  cursor: pointer;
}
.plein__pas[hidden] { display: none; }
.plein__pas:disabled { opacity: 0.25; cursor: default; }
.plein__pas--prec { left: 0.5rem; }
.plein__pas--suiv { right: 0.5rem; }

.plein__pied {
  position: absolute;
  left: 1rem;
  right: 1rem;
  bottom: calc(0.9rem + env(safe-area-inset-bottom, 0px));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  text-align: center;
  color: #edeae5;
  pointer-events: none;
}
.plein__legende-vue {
  margin: 0;
  max-width: 40rem;
  font-family: var(--lecture);
  font-size: 0.95rem;
  line-height: 1.4;
}
.plein__legende-vue[hidden], .plein__compteur[hidden] { display: none; }
.plein__compteur { margin: 0; color: rgba(237, 234, 229, 0.75); }

/* Plus large : de l'air autour, les boutons à la gouttière du site. */
@media (min-width: 48rem) {
  .plein__legende { left: var(--gouttiere); right: calc(var(--gouttiere) + 3.5rem); }
  .plein__pas { width: 2.9rem; height: 2.9rem; font-size: 1.4rem; }
  .plein__pas--prec { left: var(--gouttiere); }
  .plein__pas--suiv { right: var(--gouttiere); }
  .plein--galerie .plein__cadre { margin-left: calc(var(--gouttiere) + 3.5rem); margin-right: calc(var(--gouttiere) + 3.5rem); }
}

/* Le tirage suit le doigt pendant le balayage, puis revient en place. */
.plein__cadre img { transition: transform 0.25s ease; }

/* Sur fond sombre, la croix du site clair serait un timbre blanc collé sur le
   tirage : elle prend les valeurs de la salle qu'elle occupe. */
.plein__croix {
  position: absolute;
  top: calc(1rem + env(safe-area-inset-top, 0px));
  right: var(--gouttiere);
  z-index: 1;
  width: 2.6rem;
  height: 2.6rem;
  display: grid;
  place-items: center;
  font-family: var(--cartel);
  font-size: var(--t-base);
  background: rgba(15, 15, 18, 0.55);
  border: 1px solid rgba(237, 234, 229, 0.35);
  border-radius: 50%;
  color: #edeae5;
  cursor: pointer;
}

/* --- La vue professionnelle ---------------------------------------- */

/*
 * Elle ne remplace pas la galerie, elle répond à une autre question : embrasser
 * la série d'un regard. D'où la planche-contact — le geste du métier — et pas
 * une grille de produits : pas de vignettes rognées, et RIEN QUE LES IMAGES,
 * sous le titre de la série. Elle a porté une ligne de données par pièce, la
 * note et une demande de dossier ; le site repris a demandé une planche nue,
 * et les données se lisent au détail de l'œuvre, où un clic mène (serie.js).
 */
.dossier {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: var(--fond);
  overflow-y: auto;
  overscroll-behavior: contain;
  transform: translateY(100%);
  transition: transform 520ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.dossier[hidden] { display: none; }
.dossier.est-leve { transform: translateY(0); }

/* La croix reste sous la main tout le long d'un dossier qui défile. Fixe DANS
   le panneau : c'est lui qui porte la transformation, donc le bloc conteneur. */
/* La croix de la vue d'ensemble. `fixed` : elle reste sous la main pendant
   qu'on parcourt la planche, qui fait plusieurs écrans. */
.dossier__croix {
  position: fixed;
  top: 1rem;
  right: var(--gouttiere);
  z-index: 1;
  width: 2.4rem;
  height: 2.4rem;
  display: grid;
  place-items: center;
  font-family: var(--cartel);
  font-size: var(--t-base);
  background: var(--fond);
  border: 1px solid var(--filet);
  border-radius: 50%;
  color: var(--plomb);
  cursor: pointer;
  transition: color var(--transition), border-color var(--transition);
}
.dossier__croix:hover { color: var(--encre); border-color: var(--plomb-pale); }

.dossier__corps {
  max-width: var(--mesure);
  margin: 0 auto;
  padding: clamp(2.5rem, 6vh, 4rem) var(--gouttiere) var(--bande);
}

.dossier__titre { font-size: var(--t-2xl); margin: 0.6rem 0 0; max-width: 16ch; }
/* Trois lignes, et rien de plus : l'accroche sous le titre, puis année ·
   technique. Un champ vide ne s'écrit pas. */
.dossier__phrase { margin-top: 1rem; }
.dossier__reperes { margin: 0.9rem 0 0; color: var(--plomb); }

.planche {
  list-style: none;
  margin: clamp(2rem, 5vh, 3.25rem) 0 0;
  padding: 0;
  display: grid;
  gap: clamp(1.25rem, 3vw, 2.25rem);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 13.5rem), 1fr));
}

/* Un bouton remis à nu : il mène au détail de l'œuvre, dans la salle. */
.planche__lien {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

/*
 * PAS DE FOND sous la case. Les tirages n'ont pas tous le même format et la
 * case est en `contain` : un fond creusé (`--fond-2`) dessinait un rectangle
 * gris autour de chaque portrait, et la planche se lisait comme une grille de
 * cadres inégaux. Sur le blanc de la page, seul le tirage se voit.
 */
.planche__vue {
  display: block;
  aspect-ratio: 3 / 2;
}
/* `contain` et non `cover` : sur une planche-contact, le cadrage de l'œuvre est
   précisément ce qu'on vient juger. Mieux vaut une planche irrégulière qu'un
   tirage rogné. */
.planche__vue img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: opacity var(--transition);
}
/* Le survol ne se voit qu'à la souris : au doigt, il reste collé après le tap
   et désignerait n'importe quoi. */
@media (hover: hover) {
  .planche__lien:hover .planche__vue img { opacity: 0.85; }
}

/*
 * Il n'y a plus de section « Note d'intention » sous la galerie : elle vit sur
 * le seuil, où l'en-tête et la fin de parcours mènent. Sous une visionneuse
 * haute d'un écran entier, personne n'y descendait.
 */

@media (max-width: 48rem) {
  .scene { padding-left: 1rem; padding-right: 1rem; }
  /* L'en-tête rend ce qu'il peut : lieu et année se lisent sur le seuil et dans
     le détail de chaque œuvre. C'est le titre qui doit tenir la ligne. */
  .entete-serie__reperes { display: none; }
  /* Le cartel de SALLE abandonne sa note : sous l'image, chaque ligne se paie
     en hauteur de tirage. Celle du détail et celle de la page de l'œuvre la
     gardent — elles ont la place, et c'est une information. */
  .scene__pied .cartel-oeuvre__note { display: none; }
  .entete-serie { padding-top: 1rem; padding-bottom: 0.6rem; }

  /* Les actions passent en colonne : à cette largeur, trois verbes côte à côte
     se lisent comme une seule ligne illisible. */
  .panneau__actions { flex-direction: column; align-items: flex-start; gap: 0.9rem; }
  .action--fort { width: 100%; text-align: center; }

  /*
   * Sur une porte, la couverture cède de la place au texte — la même valeur que
   * le tirage du seuil, parce que c'est le même arbitrage : l'image, le nom et
   * le verbe ne tiennent pas ensemble dans un téléphone, et c'est le PANNEAU qui
   * défile. La couverture reste néanmoins entière au-dessus de la ligne de
   * flottaison, et c'est elle qu'on tape pour entrer : au doigt, il n'y a donc
   * rien à faire défiler pour agir.
   */
  /*
   * La couverture cède de la place au texte, comme le tirage du seuil : l'image,
   * le nom et le verbe ne tiennent pas ensemble dans un téléphone. Ce qui reste
   * entier au-dessus de la ligne de flottaison, c'est la COUVERTURE — et c'est
   * elle qu'on tape pour entrer, si bien qu'au doigt il n'y a rien à faire
   * défiler pour agir. Le reste se mérite au défilement, et le balayage d'une
   * porte à l'autre continue de marcher pendant ce temps (cf. serie.js, où le
   * couloir échappe à la garde du `pointerdown`).
   */
  .porte__cadre { width: min(100%, calc(min(30vh, 15rem) * 3 / 4)); }
  .porte__titre { font-size: clamp(1.5rem, 6.4vw, 2rem); }
  .porte__phrase { font-size: 1rem; line-height: 1.55; margin-top: 0.8rem; }

  /* La fin de parcours se resserre depuis qu'elle porte la vue d'ensemble des
     séries : sans cela elle débordait, et elle n'a rien à faire lire. */
  .sortie { margin-top: 1rem; padding-top: 0.9rem; }
  .sortie__sur { margin-bottom: 0.55rem; }
  .sortie__liste { gap: 0.7rem 0.9rem; }
  .sortie__serie { width: 4.25rem; gap: 0.35rem; }
  .apercu-expo { margin-top: 1rem; }
  .apercu-expo__vue { width: 3.25rem; }
}

/* 320 px — la largeur où tout casse, et la seule qui le montre. */
@media (max-width: 24rem) {
  .sortie__liste { gap: 0.55rem 0.7rem; }
  .sortie__serie { width: 3.6rem; gap: 0.3rem; }
  .apercu-expo { margin-top: 0.6rem; }
  .apercu-expo__lien { gap: 0.35rem; }
  .apercu-expo__vue { width: 2.75rem; }
}

/*
 * Écran court (390 × 480 et compagnie). La galerie tient dans la hauteur
 * disponible, donc chaque rangée doit rendre ce qu'elle peut : le texte de salle
 * se resserre, le cartel abandonne ce qui n'est pas l'œuvre.
 */
@media (max-height: 34rem) {
  /* Le seuil n'a pas de quoi montrer un tirage ET son texte : le texte gagne. */
  .seuil__tirage { display: none; }
  .seuil__note { max-height: 40vh; overflow-y: auto; margin-top: 0.5rem; }

  .panneau__titre { font-size: clamp(1.3rem, 5.4vw, 1.7rem); margin-top: 0.3rem; }
  .panneau__effectif { margin-top: 0.4rem; }
  .panneau__phrase { font-size: 0.95rem; line-height: 1.4; margin-top: 0.5rem; }
  .panneau__actions { margin-top: 0.65rem; gap: 0.45rem; }
  .exposition__titre { font-size: clamp(1.3rem, 5.4vw, 1.7rem); margin-top: 0.3rem; }
  .exposition__vues { margin-top: 0.75rem; gap: 0.6rem 0.7rem; }
  .exposition__cadre img { height: clamp(4.5rem, 22vh, 6.5rem); }
  .action { padding: 0.25rem 0 0.2rem; }
  .action--fort { padding: 0.45rem 0.9rem; }
  .scene { gap: 0.45rem; padding-top: 0.4rem; padding-bottom: 0.7rem; }

  /* Sur un écran court, l'en-tête rend le peu qu'il peut : les repères sont
     déjà écrits sur le seuil et dans le cartel de chaque œuvre. */
  .entete-serie { padding-top: 0.55rem; padding-bottom: 0.2rem; gap: 0.2rem 1rem; }
  .entete-serie__reperes { display: none; }
  /* La sortie et les portes se resserrent comme le reste du texte de salle :
     sur un écran court, la fin de parcours dépassait le bas. */
  .sortie { margin-top: 0.7rem; padding-top: 0.6rem; }
  .sortie__sur { margin-bottom: 0.45rem; }
  .sortie__liste { gap: 0.6rem 0.8rem; }
  .sortie__serie { width: 3.6rem; gap: 0.3rem; }
  .apercu-expo { margin-top: 0.7rem; gap: 0.6rem; }
  .apercu-expo__lien { gap: 0.4rem; }
  .apercu-expo__vue { width: 2.6rem; }
  .porte__titre { font-size: clamp(1.3rem, 5.4vw, 1.7rem); margin-top: 0.3rem; }
  .porte__reperes, .porte__effectif { margin-top: 0.3rem; }
  .porte__phrase { font-size: 0.95rem; line-height: 1.4; margin-top: 0.5rem; }
  /* Le tirage cède la place au texte, comme sur le seuil. */
  .porte__cadre { width: min(100%, calc(min(22vh, 11rem) * 3 / 4)); }
  .scene__pied { min-height: 0; }
  /* Le jeton suffit : la grille du pavé et les touches le suivent ensemble. */
  .pave { --touche: 2.4rem; }
  .pave__touche { font-size: var(--t-sm); }
  .panneau { padding: 0.25rem 0 0.5rem; }

  /* On revient à des actions qui s'enroulent : en colonne, la fin de parcours
     et ses quatre verbes dépassaient le bas de l'écran. */
  .panneau__actions { flex-direction: row; align-items: center; gap: 0.5rem 1.1rem; }
  .action--fort { width: auto; }
}
