/* =============================================================
   Galerie client — surface sombre, même grammaire que le portfolio
   ============================================================= */

/* --bg et non --ink-0 : sur une galerie en thème clair, la surface change
   mais la classe posée par le serveur, elle, reste la même. */
body.surface-dark, body.surface-paper { background: var(--bg); color: var(--fg); }
body.is-cover-page { overflow: hidden; }
/* Sur un écran très bas (téléphone couché), une couverture à photo posée
   ne tient plus : la page doit pouvoir défiler, sinon le formulaire ou le
   haut de la photo deviennent inaccessibles. */
@media (max-height: 620px) { body.is-cover-page { overflow: hidden auto; } }
/* Même raison, à toute hauteur, pour les couvertures à photo posée : un
   titre long passe sur deux lignes et la couverture dépasse de quelques
   pixels. Sans débordement, rien ne défile — la règle ne se voit pas. */
body.is-cover-page:has(:is(.cover--carte, .cover--arche, .cover--mosaique, .cover--tirage)) { overflow: hidden auto; }

/* =============================================================
   Page d'accès (code client)
   ============================================================= */
/* La porte d'entrée du sous-domaine, et la demande d'accès : on y arrive du
   site, elles en reprennent donc la voix — logo en haut, titre en Playfair
   capitales, accroche en serif italique, petites capitales mono. Les galeries
   elles-mêmes gardent leur habillage, choisi pour chaque cliente. */
.access {
  min-height: 100svh;
  display: grid; grid-template-rows: auto 1fr auto;
}
.access-tete { padding: 22px clamp(20px, 5vw, 64px); }
.access-marque {
  display: inline-block; text-decoration: none;
  font-family: var(--font-display); font-weight: 700; font-size: var(--text-sm);
  letter-spacing: .14em; text-transform: uppercase;
}
.access-marque img { height: 30px; width: auto; display: block; }
/* Quand les deux variantes sont déposées, le CSS montre celle qui se voit sur
   la surface réelle — les mêmes sélecteurs que les jetons de surface, le thème
   « auto » étant arrêté par le navigateur et non par le serveur. */
.a-deux-logos .logo-clair  { display: none; }
.surface-dark .a-deux-logos .logo-clair  { display: block; }
.surface-dark .a-deux-logos .logo-sombre { display: none; }
html[data-theme="light"] body.surface-dark .a-deux-logos .logo-clair  { display: none; }
html[data-theme="light"] body.surface-dark .a-deux-logos .logo-sombre { display: block; }
.access-inner {
  width: min(560px, 100%); margin: 0 auto; align-self: center;
  padding: clamp(24px, 5vh, 56px) clamp(20px, 5vw, 64px);
  animation: panelIn 620ms var(--ease-out) both;
}
.access-surtitre {
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase;
  color: var(--fg-3); margin: 0 0 14px;
}
.access-titre {
  font-family: 'Playfair Display', Georgia, serif;
  font-weight: 800; text-transform: uppercase;
  font-size: clamp(40px, 8vw, 84px); line-height: .92; letter-spacing: -.01em;
  margin: 0 0 22px;
}
.access-text {
  font-family: 'Cormorant Garamond', Georgia, serif; font-style: italic;
  font-size: clamp(19px, 2vw, 23px); line-height: 1.45;
  color: var(--fg-2); margin: 0 0 34px;
}
.access-form .field { margin-bottom: 20px; }
.access-code { font-family: var(--font-mono); font-size: 15px; letter-spacing: .04em; }
.access-ouvrir { width: 100%; }
.access-pied {
  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-6); justify-content: space-between;
  padding: 24px clamp(20px, 5vw, 64px) max(24px, var(--sab));
  border-top: 1px solid var(--hairline);
  font-family: var(--font-mono); font-size: var(--text-xs); line-height: 1.6;
  letter-spacing: .08em; color: var(--fg-3);
}
.access-pied a { text-decoration: none; }
@media (hover: hover) { .access-pied a:hover, .access-marque:hover { color: var(--fg); } }
@media (max-width: 560px) {
  /* Trois lignes sur un téléphone : on resserre plutôt que d'en retirer une. */
  .access-pied { gap: var(--space-1) var(--space-4); padding-top: 18px; padding-bottom: max(18px, var(--sab)); }
  .access-tete { padding-top: 18px; padding-bottom: 0; }
}

/* La demande d'accès n'a ni marque ni pied : elle garde la colonne centrée.
   Elle prend la même voix — c'est la même porte, vue d'un autre côté. */
.access:not(:has(.access-tete)) { place-items: center; grid-template-rows: none; }
.access-back { display: inline-block; margin-top: 34px; }

/* =============================================================
   Couverture de galerie
   ============================================================= */
.cover { position: relative; min-height: 100svh; display: flex; flex-direction: column; overflow: hidden; }

/* Les couvertures qui déploient la photo posent un voile sombre par-dessus :
   leur texte doit rester clair même quand la page est crème. On le dit une
   fois, en jetons locaux, plutôt qu'en blancs figés dans dix règles — c'est
   ce qui faisait disparaître « Galerie privée » sur les fonds clairs.

   Le diptyque n'y est pas : au-delà de 900 px, son texte quitte la photo pour
   le fond de la page, et ses propres règles s'en chargent déjà. */
.cover--plein, .cover--centre {
  --fg:   rgba(255,255,255,.92);
  --fg-2: rgba(255,255,255,.62);
  --fg-3: rgba(255,255,255,.42);
  --sur-media: #ffffff;
}
.cover-bg {
  position: absolute; inset: 0;
  background-size: cover; background-position: center;
  transform: scale(1.06);
  animation: coverZoom 14s var(--ease-out) both;
}
@keyframes coverZoom { from { transform: scale(1.14); opacity: 0; } to { transform: scale(1.06); opacity: 1; } }
.cover-scrim {
  position: absolute; inset: 0;
  background:
    linear-gradient(to bottom, rgba(8,11,12,.62) 0%, rgba(8,11,12,.28) 34%, rgba(8,11,12,.86) 100%),
    rgba(8,11,12,.18);
}

.cover-top, .cover-inner, .cover-foot {
  position: relative; z-index: 2;
  /* Sans width:100%, les marges automatiques de .wrap annulent l'étirement flex. */
  width: 100%;
}
.cover-top {
  display: flex; align-items: center; justify-content: space-between;
  padding-top: max(clamp(18px, 2.4vw, 30px), var(--sat));
  padding-bottom: clamp(18px, 2.4vw, 30px);
  animation: fadeIn 700ms 120ms var(--ease-out) both;
}
.cover-brand {
  font-family: var(--font-display); font-weight: 600; font-size: var(--text-md);
  letter-spacing: -.01em; color: var(--sur-media);
}
.cover-site {
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase;
  color: var(--fg-2); text-decoration: none;
}
.cover-site:hover { color: var(--sur-media); }
.cover-top-right { display: flex; align-items: center; gap: clamp(16px, 2.5vw, 32px); }

/* ---- Bascule FR / EN, reprise du portfolio ---- */
.lang-toggle {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .1em;
}
.lang-btn {
  color: inherit; text-decoration: none;
  padding: 4px 6px; min-height: 32px;
  display: inline-flex; align-items: center;
  opacity: .35;
  transition: opacity var(--dur-ui) var(--ease-out);
}
.lang-btn.active { opacity: 1; }
@media (hover: hover) { .lang-btn:not(.active):hover { opacity: .7; } }
.lang-sep { opacity: .2; }

.cover-inner {
  flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: flex-start;
  padding-bottom: clamp(40px, 8vh, 90px);
}
.cover-kicker, .cover-title, .cover-welcome { max-width: 15ch; }
.cover-title { max-width: 14ch; }
.cover-welcome { max-width: 46ch; }
.cover-kicker { color: var(--fg-2); max-width: none; animation: panelIn 620ms 160ms var(--ease-out) both; }
.cover-title {
  color: var(--sur-media); margin: clamp(18px, 3vh, 30px) 0;
  animation: panelIn 700ms 260ms var(--ease-out) both;
}
/* Le filet de la marque, or vers bleu, sous le titre : la seule couleur que
   la couverture ajoute à la photo — celle du logo, en trait fin. */
.cover-title:has(+ .cover-filet) { margin-bottom: clamp(12px, 2vh, 18px); }
.cover-filet {
  display: block; width: clamp(96px, 12vw, 168px); height: 3px; border-radius: 2px;
  margin: 0 0 clamp(18px, 3vh, 30px);
  background: linear-gradient(90deg, #c98716 0%, #cfa45b 50%, #1677b8 100%);
  animation: panelIn 700ms 300ms var(--ease-out) both;
}
.cover-welcome {
  color: var(--fg-2); font-size: clamp(15px, 1.4vw, 18px);
  line-height: 1.75; max-width: 52ch; margin-bottom: clamp(28px, 5vh, 44px);
  animation: panelIn 700ms 340ms var(--ease-out) both;
}
.cover-form { width: min(100%, 320px); animation: panelIn 700ms 420ms var(--ease-out) both; }
.cover-enter { animation: panelIn 700ms 420ms var(--ease-out) both; }
/* Le trait du champ suit la surface, il n'est pas figé en blanc : sur une
   couverture posée sur fond crème, un filet blanc n'existe pas. --fg-3 est
   déjà la bonne valeur des deux côtés, claire ou sombre. */
.cover-form .field-input { color: var(--sur-media); border-bottom-color: var(--fg-3); }
.cover-form .btn-solid { width: 100%; }
.cover-foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  flex-wrap: wrap;
  padding-bottom: max(clamp(18px, 3vh, 30px), var(--sab));
  /* Pas de blanc figé : sur une couverture posée sur crème, « Galerie privée »
     disparaissait purement et simplement. --fg-3 vaut sur les deux surfaces. */
  color: var(--fg-3);
  animation: fadeIn 800ms 600ms var(--ease-out) both;
}

/* =============================================================
   Barre flottante de l'album
   ============================================================= */
.gal-top {
  position: sticky; top: 0; z-index: 70;
  background: color-mix(in srgb, var(--bg) 84%, transparent);
  backdrop-filter: saturate(180%) blur(16px);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  border-bottom: 1px solid var(--hairline);
  transition: transform var(--dur-ui) var(--ease-out);
}
/* Repliée pendant qu'on descend — voir barreQuiSeReplie() dans gallery.js.
   La translation porte sur 105 % : les derniers pour-cent emportent l'ombre
   et le filet du bas, qui resteraient sinon comme une ligne flottante. */
.gal-top.est-repliee { transform: translateY(-105%); }

@media (prefers-reduced-motion: reduce) {
  .gal-top { transition: none; }
  .gal-top.est-repliee { transform: none; }
}
.gal-top-inner {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-6);
  padding-top: max(var(--space-4), var(--sat)); padding-bottom: var(--space-4);
  min-height: 72px;
}

/* La signature, dans la fonte de titre de la galerie — celle que choisit
   l'ambiance. Sur une galerie « Atelier » c'est donc un Cormorant, et c'est
   elle, la marque, qu'on voit en premier en haut de page. */
.gal-brand {
  display: flex; align-items: baseline; gap: var(--space-3); min-width: 0;
  font-family: var(--font-titres, var(--font-display)); font-weight: 500;
  font-size: clamp(18px, 1.9vw, 23px); letter-spacing: -.01em;
  color: var(--fg); text-decoration: none;
}
/* Le nom du client se lit, il ne se devine pas : --fg-3 tombait à 2,49 de
   contraste sur la surface claire, sous le minimum lisible. */
.gal-brand .role {
  font-family: var(--font-mono); font-size: 11px; font-weight: 400;
  letter-spacing: .08em; color: var(--fg-2); white-space: nowrap;
}
.gal-actions { display: flex; align-items: center; gap: clamp(12px, 2vw, 26px); flex: none; }
/* ---- La navigation de la galerie ----
   Elle était en capitales monospace interlettrées de 10 px : le traitement
   d'une étiquette technique, pour les six mots qu'une cliente lit en arrivant.
   Ça donnait à sa galerie un air de tableau de bord.

   Bas de casse, taille de lecture, aucun cadre : les mots se lisent d'un coup
   d'œil, et la page où l'on est se signale par un simple filet sous le mot —
   la seule marque dont on ait besoin. */
.gal-action {
  font-family: var(--font-body); font-size: 14.5px; font-weight: 400;
  letter-spacing: 0; text-transform: none;
  color: var(--fg-2); text-decoration: none;
  background: none; border: none; cursor: pointer;
  padding: 6px 0 4px; display: inline-flex; align-items: center; gap: var(--space-2);
  border-bottom: 1px solid transparent;
  transition: color var(--dur-micro) var(--ease-out), border-color var(--dur-micro) var(--ease-out);
  white-space: nowrap;
}
.gal-action:hover, .gal-action.is-active { color: var(--fg); border-bottom-color: var(--fg-3); }
.gal-badge {
  font-size: 10px; min-width: 18px; height: 18px; padding: 0 5px;
  border-radius: var(--radius-pill); background: var(--fg-4); color: var(--fg);
  display: inline-flex; align-items: center; justify-content: center;
}

/* Même remarque que pour .cta : les pastilles sont des <button>, il faut
   leur retirer la boîte grise du navigateur avant de poser la nôtre. */
/* ---- Les sections d'un album ----
   C'étaient des pastilles cerclées en capitales monospace de 10 px, et la
   section ouverte un aplat noir à texte blanc : le vocabulaire d'un filtre
   d'application, au milieu des photographies d'un mariage.

   La planche les donne en texte simple — le nom de la section, son compte en
   retrait — et souligne celle qu'on regarde. Rien n'entoure, rien n'aplatit :
   ce sont des mots sous des images. */
.gal-folder {
  font-family: var(--font-body); font-size: 14px; font-weight: 400;
  letter-spacing: 0; text-transform: none;
  color: var(--fg-2); text-decoration: none; white-space: nowrap;
  appearance: none; -webkit-appearance: none;
  background: none;
  cursor: pointer;
  padding: 4px 0 5px; border: 0;
  border-bottom: 1px solid transparent;
  border-radius: 0;
  transition: color var(--dur-micro) var(--ease-out),
              border-color var(--dur-micro) var(--ease-out);
}
.gal-folder span { opacity: .55; margin-left: 6px; font-variant-numeric: tabular-nums; }
@media (hover: hover) { .gal-folder:hover { color: var(--fg); } }
.gal-folder.is-active { color: var(--fg); border-bottom-color: var(--fg); background: none; }
.gal-folder.is-active span { opacity: .55; }

/* =============================================================
   Contenu
   ============================================================= */
.gal-main { padding-bottom: clamp(60px, 10vh, 120px); }
/* padding-block, jamais le raccourci « padding » : il écraserait les marges
   latérales de .wrap et le contenu viendrait coller au bord de l'écran. */
.gal-intro { padding-block: clamp(34px, 6vh, 64px) clamp(18px, 3vh, 30px); }
.gal-intro .display-sm { margin: 14px 0 0; }
.gal-intro-actions { display: flex; gap: var(--space-6); flex-wrap: wrap; margin-top: 26px; }
.gal-empty { padding-block: clamp(60px, 16vh, 160px); text-align: center; }
.gal-empty .lead { margin: 18px auto 28px; max-width: 44ch; color: var(--fg-2); }
/* L'étiquette est une rangée flex : sans ceci, elle restait calée à gauche
   au-dessus d'un texte centré. */
.gal-empty .kicker { justify-content: center; }
.gal-foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  flex-wrap: wrap; margin-top: clamp(48px, 8vh, 90px);
  padding-top: 24px; border-top: 1px solid var(--hairline);
  color: var(--fg-3);
}
/* Un lien de pied de page reste un lien qu'on touche : sans hauteur propre,
   il ne faisait que la hauteur de sa ligne, 16 px, impossible à viser au doigt. */
.gal-foot a {
  color: inherit;
  display: inline-flex; align-items: center;
  min-height: 24px;
}
/* La demande de prolongation est un envoi, donc un bouton — habillé comme
   les liens qui l'entourent. */
.gal-foot-form { display: inline; }
.gal-foot-lien {
  font: inherit; letter-spacing: inherit; text-transform: inherit;
  color: inherit; background: none; border: 0; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; min-height: 24px;
  text-decoration: underline; text-underline-offset: 3px;
}

/* =============================================================
   Grille justifiée
   ============================================================= */
.grid {
  display: flex; flex-wrap: wrap;
  gap: var(--grid-gap, 8px);
  /* La marge suit la densité : une grille aérée collée au bord de l'écran
     n'est pas aérée, elle est mal cadrée. */
  padding: calc(clamp(16px, 2.4vw, 30px) * var(--grid-marge, 1))
           calc(clamp(10px, 1.6vw, 22px) * var(--grid-marge, 1)) 0;
  align-content: flex-start;
}
/* Repli avant l'entrée en scène du JavaScript.
   Il ÉTIRAIT les vignettes pour remplir chaque rangée — donc une disposition
   sans rapport avec la grille justifiée qui la remplaçait un instant après.
   Au rafraîchissement, on voyait la page se recomposer entièrement.

   Même principe que le moteur, désormais : hauteur de rangée constante, la
   largeur suit le rapport de chaque photo. Il reste une différence — le
   moteur ajuste la hauteur pour que chaque rangée tombe juste — mais elle se
   compte en pixels, là où l'on voyait auparavant tout bouger. */
.grid:not(.is-justified) .cell {
  flex: 0 0 auto;
  height: var(--rangee);
  width: calc(var(--ar, 1.5) * var(--rangee));
  aspect-ratio: auto;
}
/* Les paliers du moteur, repris à l'identique : voir REGLAGES.rangees. */
.grid { --rangee: 340px; }
@media (max-width: 1400px) { .grid { --rangee: 300px; } }
@media (max-width: 900px)  { .grid { --rangee: 240px; } }
@media (max-width: 560px)  { .grid { --rangee: 62vw; } }

.cell {
  position: relative; margin: 0; overflow: hidden;
  background: var(--bg-2); border-radius: var(--vignette-rayon, 2px);
  opacity: 0; animation: fadeIn 520ms var(--ease-out) forwards;
}
.grid.is-justified .cell { flex: 0 0 auto; }
.cell-open {
  display: block; width: 100%; height: 100%;
  padding: 0; border: none; background: none; cursor: zoom-in;
}
.cell-img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform var(--dur-enter) var(--ease-out), opacity var(--dur-enter) var(--ease-out);
  -webkit-user-drag: none; user-select: none;
}
@media (hover: hover) {
  .cell:hover .cell-img { transform: scale(1.035); }
}

.cell-play {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 46px; height: 46px; border-radius: 50%;
  display: grid; place-items: center; pointer-events: none;
  background: rgba(10,13,14,.42); color: var(--sur-media);
  border: 1px solid rgba(255,255,255,.5);
  backdrop-filter: blur(3px);
}

.cell-fav {
  position: absolute; right: 8px; bottom: 8px;
  width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center; cursor: pointer;
  background: rgba(10,13,14,.35); border: none;
  color: rgba(255,255,255,.85);
  opacity: 0; transform: translateY(4px);
  transition: opacity var(--dur-micro) var(--ease-out),
              transform var(--dur-micro) var(--ease-out),
              color var(--dur-micro) var(--ease-out);
  backdrop-filter: blur(6px);
}
.cell-fav svg { fill: none; stroke: currentColor; stroke-width: 1.6; }
.cell-fav.is-on { opacity: 1; transform: none; color: var(--sur-media); }
.cell-fav.is-on svg { fill: var(--sur-media); stroke: var(--sur-media); }
.cell:hover .cell-fav, .cell-fav:focus-visible { opacity: 1; transform: none; }
@media (hover: none) { .cell-fav { opacity: .9; transform: none; } }

/* =============================================================
   Visionneuse
   ============================================================= */
/* La visionneuse reste sombre même sur une galerie en thème clair : une
   photographie se regarde sur du noir, pas sur du papier. C'est délibéré, et
   tokens.css lui donne le jeu de jetons sombre en propre (voir .surface-dark). */
/* Les couleurs de la visionneuse viennent des JETONS, jamais du blanc figé :
   elle suit désormais la teinte de la galerie, et sur un fond crème un
   pictogramme blanc n'existe pas. C'est le même défaut que sur la couverture,
   au même endroit du raisonnement — une couleur écrite en dur suppose une
   surface qu'on ne contrôle plus. */
.lb {
  position: fixed; inset: 0; z-index: 120;
  background: var(--bg);
  display: flex; flex-direction: column;
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-ui) var(--ease-out);
}
.lb.is-open { opacity: 1; pointer-events: auto; }

.lb-top {
  position: relative; z-index: 3;
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  padding: max(14px, var(--sat)) clamp(14px, 2.5vw, 28px) 14px;
}
.lb-meta {
  font-family: var(--font-mono); font-size: 11px;
  letter-spacing: .12em; color: var(--fg-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lb-tools { display: flex; align-items: center; gap: var(--space-1); flex: none; }
.lb-btn {
  width: 44px; height: 44px; display: grid; place-items: center;
  background: none; border: none; cursor: pointer;
  color: var(--fg-2); border-radius: 50%;
  transition: color var(--dur-micro) var(--ease-out), background var(--dur-micro) var(--ease-out);
}
.lb-btn:hover { color: var(--fg); background: var(--hover); }
.lb-btn svg { fill: none; stroke: currentColor; stroke-width: 1.6; }
.lb-btn.is-on { color: var(--fg); }
.lb-btn.is-on svg { fill: var(--fg); }

.lb-stage { flex: 1; position: relative; min-height: 0; }
/* Le porte-image est en position absolue : ainsi l’image ne peut jamais
   pousser la hauteur de la scène, quelle que soit sa définition. */
.lb-holder {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  padding: 0 clamp(10px, 5vw, 70px);
}
.lb-img {
  max-width: 100%; max-height: 100%;
  width: auto; height: auto;
  object-fit: contain;
  opacity: 0; transition: opacity var(--dur-ui) var(--ease-out);
  -webkit-user-drag: none; user-select: none;
}
.lb-img.is-loaded { opacity: 1; }

/* ── D'une photo à l'autre (réglage « visionneuse ») ─────────────────────
   « fondu » est l'opacité ci-dessus. Les deux autres ne jouent qu'en
   naviguant (classe est-navigation, posée par gallery.js) et passent par
   « translate » et « scale » : le zoom de la visionneuse emploie transform,
   les deux ne se gênent pas. --lb-sens vaut 1 en avançant, -1 en reculant. */
@keyframes lbGlisse { from { opacity: 0; translate: calc(var(--lb-sens, 1) * 56px) 0; } to { opacity: 1; translate: 0 0; } }
@keyframes lbZoom   { from { opacity: 0; scale: .94; } to { opacity: 1; scale: 1; } }
[data-visionneuse="glisse"] .lb-holder.est-navigation .lb-img.is-loaded:not(.lb-img-reference) { animation: lbGlisse 420ms var(--ease-out) both; }
[data-visionneuse="zoom"]   .lb-holder.est-navigation .lb-img.is-loaded:not(.lb-img-reference) { animation: lbZoom 380ms var(--ease-out) both; }
@media (prefers-reduced-motion: reduce) {
  .lb-holder .lb-img { animation: none !important; }
}
.lb-holder .vp { width: 100%; height: 100%; }

.lb-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  background: none; border: none; cursor: pointer;
  color: var(--fg-3); padding: 20px 14px;
  transition: color var(--dur-micro) var(--ease-out);
}
.lb-nav:hover { color: var(--fg); }
.lb-nav svg { fill: none; stroke: currentColor; stroke-width: 1.4; }
.lb-prev { left: clamp(2px, 1.2vw, 18px); }
.lb-next { right: clamp(2px, 1.2vw, 18px); }
.lb-nav[hidden] { display: none; }

.lb-foot {
  padding: 14px clamp(14px, 2.5vw, 28px) max(16px, var(--sab));
  display: flex; align-items: center; justify-content: center; gap: var(--space-4);
}
.lb-counter {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .14em;
  color: var(--fg-3);
}

/* Panneau de commentaires */
.lb-comments {
  position: absolute; right: 0; top: 0; bottom: 0; z-index: 4;
  width: min(92vw, 380px);
  /* --bg-2, pas --ink-1 : le fond d'un panneau doit suivre la SURFACE.
     Une couleur d'encre figée donnait un panneau sombre dont le texte, lui,
     venait des jetons — donc sombre aussi sur une galerie claire. Le panneau
     était là, lisible nulle part. Sur une galerie sombre, --bg-2 vaut
     exactement --ink-1 : rien ne change de ce côté. */
  background: var(--bg-2); border-left: 1px solid var(--hairline);
  transform: translateX(100%); transition: transform var(--dur-ui) var(--ease-in-out);
  display: flex; flex-direction: column;
  padding: max(20px, var(--sat)) 22px max(20px, var(--sab));
}
.lb.comments-open .lb-comments { transform: none; }
.lb-comments h3 {
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase; color: var(--fg-3);
  margin-bottom: 18px;
}
.lb-comment-list { flex: 1; overflow-y: auto; margin: 0 0 18px; padding: 0; list-style: none; }
.lb-comment { padding: 12px 0; border-bottom: 1px solid var(--hairline); }
.lb-comment b { display: block; font-weight: 500; font-size: 13px; margin-bottom: 5px; }
.lb-comment time { font-family: var(--font-mono); font-size: 10px; color: var(--fg-3); }
.lb-comment p { font-size: 14px; line-height: 1.65; color: var(--fg-2); margin-top: 6px; }
.lb-comment-empty { color: var(--fg-3); font-size: 13px; line-height: 1.7; }
.lb-comment-form .field-input, .lb-comment-form .field-textarea { font-size: 14px; }

/* =============================================================
   Lecteur vidéo (reprise du player du portfolio)
   ============================================================= */
/* Deux étages : l'image, puis ce qui se lit dessous.
   La barre de commandes se cale en bas de son étage — celui du film — et non
   en bas de l'ensemble, sans quoi elle recouvre chapitres et validation.
   minmax(0, 1fr) et non 1fr : un enfant de grille refuse de rétrécir sous sa
   taille intrinsèque, et la vidéo pousserait le panneau hors de l'écran. */
.vp { position: relative; width: 100%; height: 100%; display: grid; grid-template-rows: minmax(0, 1fr) auto; }
.vp-scene { position: relative; min-height: 0; display: grid; place-items: center; }

/* Ce qui se lit sous le film. L'ensemble est borné pour ne pas manger l'image,
   mais c'est la LISTE des chapitres qui défile — jamais la validation : la
   faire disparaître sous le pli reviendrait à ne pas la proposer du tout. */
.vp-sous:empty { display: none; }
/* Pas de hauteur en pourcentage ici : dans une rangée de grille dimensionnée
   en « auto », un pourcentage se résout contre une hauteur elle-même en cours
   de calcul, et le panneau s'écrasait à seize pixels. C'est la LISTE qui porte
   sa propre limite (26vh), en unité absolue ; la rangée du film, en
   minmax(0, 1fr), rend ce qu'il faut. */
.vp-sous { display: flex; flex-direction: column; padding: 0 2px; }
/* En position absolue, jamais en élément de grille avec « height: 100% ».
   Même piège que dans le diaporama : une hauteur en pourcentage exige un bloc
   conteneur de hauteur DÉFINIE, et la rangée de grille est dimensionnée par
   son contenu. La hauteur retombait donc sur « auto », le film prenait sa
   taille naturelle et débordait sous la barre de commandes. */
.vp-video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain; background: var(--letterbox);
}
.vp-play {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 76px; height: 76px; border-radius: 50%;
  background: rgba(10,13,14,.4); border: 1px solid rgba(255,255,255,.55);
  color: var(--sur-media); cursor: pointer; display: grid; place-items: center;
  backdrop-filter: blur(4px);
  transition: opacity var(--dur-ui) var(--ease-out), transform var(--dur-ui) var(--ease-out);
}
.vp.is-playing .vp-play { opacity: 0; transform: translate(-50%, -50%) scale(.85); pointer-events: none; }

/* =============================================================
   Sélection : quota, listes, notes, validation
   ============================================================= */
.gal-badge.is-full { background: var(--btn-bg); color: var(--btn-fg); }

.sel-lists { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: 22px; }
/* Pastille discrète sur les photos annotées */
.cell.has-note::after {
  content: ''; position: absolute; left: 8px; bottom: 8px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--sur-media); opacity: .85;
  box-shadow: 0 0 0 3px rgba(10,13,14,.35);
}

/* Bloc « note pour le photographe » dans le panneau latéral */
.lb-note { padding-bottom: 20px; margin-bottom: 4px; border-bottom: 1px solid var(--hairline); }
.lb-note .field-textarea { min-height: 88px; font-size: 14px; }
.lb-note-save { width: 100%; }

/* Aperçu flouté : le fond de la cellule pendant que la vignette arrive */
/* ── L'attente d'une photo ───────────────────────────────────────────────
   Le remplacement est une image de 20 × 30 pixels. Affichée telle quelle à
   trois cents pixels de large, elle montre ses pavés : ce n'est pas un flou,
   c'est une image cassée — et cela se voyait comme tel pendant une demi-
   seconde à chaque chargement.

   Un vrai élément, et non ::before : celui-ci porte déjà la pastille des
   nouveautés (.cell.is-new::before), et un élément n'a qu'un seul ::before.
   Ma première version se faisait donc recouvrir sur les trois quarts des
   vignettes. ::after est pris aussi, par la note et l'image cassée. */
.cell-flou {
  position: absolute; inset: 0; z-index: 0;
  background-image: var(--lqip);
  background-size: cover; background-position: center;
  /* Le vrai flou, celui qui rend l'attente douce au lieu de la rendre laide.
     L'agrandissement compense le retrait du flou sur les bords, sans quoi on
     verrait un liseré clair tout autour. */
  /* Huit pixels, pas quatorze : à quatorze, une image de 20 × 30 perd toute
     sa matière et la page paraît vide plutôt qu'en cours de chargement.
     Assez pour effacer les pavés, assez peu pour qu'on reconnaisse la photo. */
  filter: blur(8px);
  transform: scale(1.06);
  /* Il s'EFFACE pendant que la photo paraît, au lieu de rester dessous : deux
     images superposées à demi visibles donnaient l'impression de brouillard
     qu'on cherchait justement à éviter. */
  opacity: 1;
  transition: opacity var(--dur-ui) var(--ease-out);
  pointer-events: none;
}
.cell.est-nette .cell-flou { opacity: 0; }

.cell-img { position: relative; z-index: 1; opacity: 0; transition: opacity var(--dur-ui) var(--ease-out), transform var(--dur-enter) var(--ease-out); }
.cell-img.is-shown, .cell-img[data-loaded] { opacity: 1; }

/* Sentinelle de chargement en bas de grille */
.grid-sentinel {
  display: flex; align-items: center; justify-content: center;
  padding: clamp(30px, 6vh, 60px) 0;
}

/* =============================================================
   Téléchargement en volumes
   ============================================================= */
.vol-page { padding-top: clamp(60px, 14vh, 130px); padding-bottom: clamp(60px, 12vh, 120px); max-width: 720px; }
.vol-page .display-sm { margin: 16px 0 18px; }
.vol-intro { color: var(--fg-2); font-size: var(--text-sm); line-height: 1.75; margin-bottom: clamp(30px, 5vh, 48px); }
.vol-list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--hairline); }
.vol-item { border-bottom: 1px solid var(--hairline); }
.vol-link {
  display: grid; grid-template-columns: 1fr auto 28px;
  gap: var(--space-4); align-items: baseline;
  padding: 20px 0; text-decoration: none;
  transition: opacity var(--dur-micro) var(--ease-out);
}
.vol-link:hover { opacity: .6; }
.vol-name { font-family: var(--font-display); font-size: 19px; font-weight: 400; letter-spacing: -.015em; }
.vol-meta { font-family: var(--font-mono); font-size: 11px; letter-spacing: .08em; color: var(--fg-3); }
.vol-arrow { text-align: right; color: var(--fg-3); }
.vol-back { margin-top: clamp(32px, 6vh, 56px); }

/* =============================================================
   Zoom, diaporama, nouveautés, aide clavier
   ============================================================= */
.lb-img { transform-origin: center; will-change: transform; }
.lb.is-zoomed .lb-holder { cursor: grab; }
.lb.is-zoomed .lb-holder:active { cursor: grabbing; }
.lb.is-zoomed .lb-nav { opacity: 0; pointer-events: none; }

/* Pendant le diaporama, l'interface s'efface pour laisser la place aux images */
.lb.is-playing .lb-top,
.lb.is-playing .lb-nav,
.lb.is-playing .lb-foot { opacity: .12; transition: opacity var(--dur-enter) var(--ease-out); }
.lb.is-playing:hover .lb-top,
.lb.is-playing:hover .lb-nav,
.lb.is-playing:hover .lb-foot { opacity: 1; }
.lb.is-playing [data-act="slideshow"] { color: var(--fg); }

/* Repère discret sur les médias jamais ouverts */
.cell.is-new::before {
  content: ''; position: absolute; top: 8px; left: 8px; z-index: 2;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--sur-media); box-shadow: 0 0 0 3px rgba(10,13,14,.35);
}

/* Aide clavier */
.help-panel {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(8,11,12,.86);
  display: grid; place-items: center; padding: 24px;
  animation: fadeIn var(--dur-ui) var(--ease-out) both;
  backdrop-filter: blur(6px);
}
.help-inner {
  width: min(100%, 440px);
  background: var(--bg-2); border: 1px solid var(--hairline);
  border-radius: var(--radius-md); padding: clamp(24px, 4vw, 36px);
}
.help-inner h2 {
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase; color: var(--fg-3);
  margin-bottom: 22px;
}
.help-inner dl {
  display: grid; grid-template-columns: 88px 1fr; gap: var(--space-3) var(--space-4);
  margin: 0 0 26px; align-items: baseline;
}
.help-inner dt {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .08em;
  color: var(--fg); padding: 5px 9px; border: 1px solid var(--hairline);
  border-radius: var(--radius-sm); text-align: center;
}
.help-inner dd { margin: 0; font-size: 14px; line-height: 1.5; color: var(--fg-2); }
.help-inner .btn { width: 100%; }

/* Filtre « nouvelles » : on masque ce qui a déjà été ouvert */
.grid.only-new .cell:not(.is-new) { display: none; }
.gal-action.is-active { color: var(--fg); border-bottom-color: var(--fg); }

/* Choix de la taille au téléchargement */
.lb-tools { position: relative; }
.lb-dl-menu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 5;
  background: var(--bg-2); border: 1px solid var(--hairline);
  border-radius: var(--radius-sm); overflow: hidden; min-width: 220px;
  animation: fadeIn var(--dur-ui) var(--ease-out) both;
}
.lb-dl-menu a {
  display: block; padding: 12px 16px; text-decoration: none;
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase; color: var(--fg-2);
  transition: background var(--dur-micro) var(--ease-out), color var(--dur-micro) var(--ease-out);
}
.lb-dl-menu a + a { border-top: 1px solid var(--hairline); }
.lb-dl-menu a:hover { background: var(--hover); color: var(--fg); }

/* =============================================================
   Filtres du client
   ============================================================= */
.gal-filters {
  /* Des mots, donc l'espace entre des mots — pas l'écart serré qui convenait
     à des pastilles cerclées. */
  display: flex; align-items: center; gap: var(--space-6);
  flex-wrap: wrap;
  padding-bottom: var(--space-4);
}
.gal-filters .gal-folder { cursor: pointer; }
.gal-filters .gal-folder.is-active { background: none; color: var(--fg); border-bottom-color: var(--fg); }
.gal-filters .gal-folder.is-active span { opacity: .55; }
.gal-filters-clear {
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase;
  background: none; border: none; cursor: pointer;
  color: var(--fg-3); padding: 8px 4px;
  border-bottom: 1px solid transparent;
  transition: color var(--dur-micro) var(--ease-out), border-color var(--dur-micro) var(--ease-out);
}
.gal-filters-clear:hover { color: var(--fg); border-bottom-color: var(--fg-3); }

.gal-filter-empty {
  padding: clamp(50px, 12vh, 110px) clamp(20px, 3.4vw, 48px);
  text-align: center; color: var(--fg-2);
  font-size: clamp(15px, 1.4vw, 18px);
}

/* Séparateur entre dossiers et étiquettes dans la barre de filtres */
.gal-filters-sep {
  width: 1px; height: 20px; background: var(--hairline);
  margin: 0 6px; flex: none;
}
.gal-folder.is-tag { border-style: dashed; }
.gal-folder.is-tag.is-active { border-style: solid; }

/* =============================================================
   Performance : ce qui est hors écran ne coûte rien
   ============================================================= */

/* Le navigateur saute la mise en page et le dessin des cellules hors écran.
   Sur 800 photos, c'est le réglage qui compte le plus sur un téléphone.
   La taille annoncée évite que la barre de défilement saute. */
.grid .cell {
  content-visibility: auto;
  contain-intrinsic-size: auto 280px;
}

/* La visionneuse est le dernier enfant de <body> : on ne peut pas masquer
   la grille par un sélecteur de voisinage, et lui poser content-visibility
   casserait la transition de vue à l'ouverture. On s'en tient aux cellules. */

/* Bascule clair / sombre — même poids visuel que la bascule de langue. */
.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  color: var(--fg-2); text-decoration: none;
  border-radius: var(--radius-pill);
  transition: color var(--dur-micro) var(--ease-out), background var(--dur-micro) var(--ease-out);
}
@media (hover: hover) { .theme-toggle:hover { color: var(--fg); background: var(--hover); } }

/* Durée d'une vidéo, lisible avant de cliquer : on sait si c'est un plan de
   quinze secondes ou le film complet. Même grammaire que les pastilles. */
.cell-duration {
  position: absolute; right: 8px; bottom: 8px;
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: .06em; font-variant-numeric: tabular-nums;
  color: var(--sur-media); background: rgba(10,13,14,.62);
  padding: 3px 7px; border-radius: var(--radius-pill);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  pointer-events: none;
}

/* Croix de fermeture du panneau de commentaires. Le panneau glisse par-dessus
   le bouton de la barre : sans elle, on ne pouvait plus le refermer. */
.lb-comments-close {
  position: absolute; top: 10px; right: 10px; z-index: 2;
  width: 34px; height: 34px;
  display: grid; place-items: center;
  appearance: none; -webkit-appearance: none;
  background: none; border: 0; border-radius: var(--radius-pill);
  color: var(--fg-2); cursor: pointer;
  transition: color var(--dur-micro) var(--ease-out), background var(--dur-micro) var(--ease-out);
}
@media (hover: hover) {
  .lb-comments-close:hover { color: var(--fg); background: var(--hover); }
}

/* Bandeau d'aperçu administrateur — jamais vu par un client. Il doit trancher
   avec la galerie pour qu'on ne le confonde pas avec son contenu. */
.apercu-admin {
  position: relative; z-index: 200;
  display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap;
  padding: 10px clamp(14px, 3vw, 28px);
  background: var(--bandeau-apercu); color: var(--sur-media);
  font-size: 13px; line-height: 1.5;
}
.apercu-admin-etiquette {
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase;
  padding: 3px 9px; border-radius: var(--radius-pill);
  /* Un voile blanc à 18 % laissait le mot à 3,96:1 sur le rouge du bandeau.
     La pastille pleine inverse le contraste et monte à 5,15:1. */
  background: var(--sur-media); color: var(--bandeau-apercu); font-weight: 500; white-space: nowrap;
}
.apercu-admin a {
  color: var(--sur-media); text-decoration: underline; text-underline-offset: 3px; white-space: nowrap;
  /* Le lien ne fait que 20 px de haut : au doigt, on vise à côté. */
  display: inline-flex; align-items: center; min-height: 24px;
}
@media (pointer: coarse) { .apercu-admin a { min-height: 44px; } }

/* « Qui êtes-vous ? » — l'identification à l'entrée d'une galerie. Un voile
   plein écran : c'est la première chose à faire, pas une option à côté. */
.qui {
  position: fixed; inset: 0; z-index: 300;
  display: grid; place-items: center;
  padding: 24px;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  animation: fadeIn var(--dur-ui) var(--ease-out) both;
}
.qui.is-parti { animation: fadeIn var(--dur-ui) var(--ease-out) reverse both; pointer-events: none; }

.qui-boite {
  width: min(100%, 440px);
  animation: panelIn 480ms 60ms var(--ease-out) both;
}
.qui-boite .display-sm { margin: 14px 0 16px; }
.qui-intro { color: var(--fg-2); font-size: var(--text-sm); line-height: 1.7; margin-bottom: 30px; }
.qui-form .field { margin-bottom: 22px; }
.qui-valider { width: 100%; }

.qui-connus-titre {
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase;
  color: var(--fg-2); margin: 34px 0 12px;
}
.qui-connus { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-0); }

/* Reprendre sa sélection depuis un autre appareil : la ligne annonce combien
   de photos attendent, sinon on ne sait pas ce qu'on récupère. */
.qui-personne {
  width: 100%; text-align: left;
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4);
  appearance: none; -webkit-appearance: none; background: none; cursor: pointer;
  border: 0; border-top: 1px solid var(--hairline);
  padding: 14px 2px; color: var(--fg);
  transition: background var(--dur-micro) var(--ease-out), padding var(--dur-micro) var(--ease-out);
}
.qui-connus li:last-child .qui-personne { border-bottom: 1px solid var(--hairline); }
@media (hover: hover) { .qui-personne:hover { background: var(--hover); padding-left: 10px; } }
.qui-personne-nom { font-size: var(--text-md); }
.qui-personne-compte {
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: .1em; color: var(--fg-2); white-space: nowrap;
}

.qui-plus-tard {
  display: block; margin: 30px auto 0;
  appearance: none; -webkit-appearance: none; background: none; border: 0;
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase;
  color: var(--fg-2); cursor: pointer; padding: 10px;
}
@media (hover: hover) { .qui-plus-tard:hover { color: var(--fg); } }

/* Récit par chapitres — les moments de la journée deviennent des sections
   traversées en défilant, au lieu de simples filtres. Le titre occupe toute la
   largeur de la grille : il coupe la rangée et ouvre la section suivante. */
.chapitre {
  width: 100%; flex-basis: 100%;
  margin: clamp(38px, 6vh, 72px) 0 clamp(14px, 2vh, 22px);
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(26px, 4vw, 44px); letter-spacing: -.02em;
  color: var(--fg);
  scroll-margin-top: 90px;
}
.chapitre:first-child { margin-top: clamp(8px, 1vh, 16px); }

/* Un filet sous le titre, comme les listes du portfolio. */
.chapitre::after {
  content: ''; display: block;
  width: 48px; height: 1px; margin-top: 14px;
  background: var(--fg-3);
}

/* Le récit n'a pas de sens quand un filtre isole un seul moment. */
.grid.only-new .chapitre,
.grid.is-filtered .chapitre { display: none; }

/* Couleur d'accent tirée de la couverture. Le repli est le jeton habituel :
   sans couverture, ou sur une photo sans teinte franche, rien ne change. */
:root { --accent: var(--fg); }

/* Employée là où l'œil cherche un repère, jamais sur du texte courant : la
   lisibilité ne doit pas dépendre d'une couleur qu'on ne choisit pas. */
.chapitre::after { background: var(--accent); }
.gal-action.is-active { border-bottom-color: var(--accent); }
.cell.is-fav .cell-fav { color: var(--accent); }
/* La pastille de filtre active reste blanche : c'est la langue du portfolio,
   et l'accent doit rester une touche, pas une reprise en main. */
.qui-personne:hover { box-shadow: inset 2px 0 0 var(--accent); }
.gal-progress { background: var(--accent); }

/* =============================================================
   Mises en page de couverture
   ============================================================= */

/* « plein » est la mise en page d'origine : photo pleine page, texte en bas à
   gauche. Les deux autres ne changent que le placement — le balisage est le
   même, ce qui évite trois gabarits à maintenir. */

/* ---- Diptyque : la photo à droite, le texte sur une surface pleine ---- */
@media (min-width: 900px) {
  .cover--diptyque .cover-bg,
  .cover--diptyque .cover-scrim { left: 42%; }

  .cover--diptyque .cover-scrim {
    background: linear-gradient(to right, rgba(8,11,12,.55) 0%, rgba(8,11,12,.12) 30%, transparent 100%);
  }

  /* Le texte quitte la photo : il repose sur le fond de la galerie, donc il
     reprend les couleurs de la surface au lieu du blanc forcé. */
  .cover--diptyque::before {
    content: ''; position: absolute; inset: 0 58% 0 0; z-index: 1;
    background: var(--bg);
  }
  /* Une largeur maximale recentrerait le bloc : .wrap a des marges
     automatiques. On le retient par la droite pour qu'il reste collé au bord
     gauche, à distance de la photo. */
  .cover--diptyque .cover-inner { padding-right: 60%; }
  .cover--diptyque .cover-title { max-width: 11ch; }
  .cover--diptyque .cover-welcome,
  .cover--diptyque .cover-kicker { max-width: 34ch; }
  .cover--diptyque .cover-title,
  .cover--diptyque .cover-brand { color: var(--fg); }
  .cover--diptyque .cover-kicker { color: var(--fg-2); }
  .cover--diptyque .cover-welcome { color: var(--fg-2); }
  .cover--diptyque .cover-site,
  .cover--diptyque .cover-foot { color: var(--fg-2); }
  .cover--diptyque .cover-form .field-input { color: var(--fg); }
}

/* ---- Centre : le titre au milieu, la photo en fond assourdi ---- */
.cover--centre .cover-scrim {
  background:
    linear-gradient(to bottom, rgba(8,11,12,.58) 0%, rgba(8,11,12,.50) 50%, rgba(8,11,12,.78) 100%),
    rgba(8,11,12,.30);
}
.cover--centre .cover-inner {
  align-items: center; text-align: center;
  justify-content: center;
}
.cover--centre .cover-kicker { justify-content: center; }
.cover--centre .cover-title {
  max-width: 18ch;
  font-size: clamp(46px, 10vw, 150px);
}
.cover--centre .cover-welcome { max-width: 54ch; }
.cover--centre .cover-form { margin-inline: auto; }

/* Comparaison côte à côte — départager deux prises presque identiques sans
   faire l'aller-retour entre elles. La photo mise de côté reste à gauche
   pendant qu'on parcourt les autres. */
.lb-holder.is-comparaison {
  display: flex; align-items: center; justify-content: center;
  gap: clamp(8px, 1.6vw, 22px);
}
.lb-holder.is-comparaison .lb-img { max-width: calc(50% - 11px); }

/* Un filet discret distingue la référence de la photo en cours de parcours. */
.lb-holder.is-comparaison .lb-img-reference { outline: 1px solid var(--fg-4); outline-offset: 4px; }

.lb-btn.is-on { color: var(--fg); }

/* Sur un téléphone, deux photos côte à côte ne montrent plus rien : on
   superpose verticalement, ce qui reste lisible en portrait. */
@media (max-width: 700px) {
  .lb-holder.is-comparaison { flex-direction: column; }
  .lb-holder.is-comparaison .lb-img { max-width: 100%; max-height: calc(50% - 8px); }
}

/* Musique du diaporama : le bouton n'apparaît que pendant la lecture, et
   l'onde du haut-parleur ne se dessine que si le son est rendu. */
.lb [data-act="son"] { display: none; }
.lb.is-playing [data-act="son"] { display: grid; }
.lb [data-act="son"] .onde { opacity: 0; transition: opacity var(--dur-micro) var(--ease-out); }
.lb.musique-on [data-act="son"] { color: var(--fg); }
.lb.musique-on [data-act="son"] .onde { opacity: 1; }

/* Le formulaire d'envoi de la musique, dans les réglages. */
.musique-form { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; margin-top: 12px; }
.musique-form .field-input { width: auto; flex: 1; min-width: 200px; }

/* =============================================================
   ADAPTATIONS
   Toutes les surcharges de taille d'écran vivent ici, en fin de feuille :
   à spécificité égale c'est la dernière règle qui gagne, donc les placer
   après les styles de base est la seule façon fiable de les voir s'appliquer.
   ============================================================= */

/* En dessous de 700 px, le titre et les actions ne tiennent plus côte à côte :
   le titre garde sa ligne, les actions passent dessous et défilent
   horizontalement plutôt que de déborder ou de s'empiler sur trois rangs. */
@media (max-width: 700px) {
  .gal-top-inner { flex-wrap: wrap; gap: var(--space-2) var(--space-4); padding-bottom: 6px; }
  .gal-brand .role { display: none; }

  /* Sept commandes et un nom de fichier ne tiennent pas sur un téléphone : le
     nom se réduisait à « IM… », ce qui n'apprend rien. Mieux vaut le taire,
     le compteur du bas situe déjà la photo. */
  .lb-meta { display: none; }
  .gal-brand {
    display: block; max-width: 100%;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .gal-actions {
    width: 100%;
    gap: var(--space-6);
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 6px;
  }
  .gal-actions::-webkit-scrollbar { display: none; }
  .gal-action { font-size: 13.5px; letter-spacing: 0; flex: none; }

  /* Les filtres défilent aussi : une ligne, pas trois. */
  .gal-filters {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 12px;
  }
  .gal-filters::-webkit-scrollbar { display: none; }
  .gal-filters > * { flex: none; }

  /* Visionneuse : l'image occupe toute la largeur, les flèches se resserrent. */
  .lb-stage { padding: 0; }
  .lb-holder { padding: 0 8px; }
  .lb-nav { padding: 30px 6px; }
  .lb-comments { width: 100%; }
}

/* =============================================================
   Écrans tactiles — placé en fin de feuille pour surcharger les
   tailles définies plus haut, à spécificité égale.
   ============================================================= */
@media (pointer: coarse) {
  /* Le lien vers le portfolio, en haut de la couverture : 16 px de haut, on le
     manque au doigt. Le seul de la galerie qui n'avait pas sa zone de contact. */
  .cover-site { display: inline-flex; align-items: center; min-height: 44px; }
  .gal-action,
  .gal-folder,
  /* Les étiquettes manquaient à cette liste : elles se cliquent autant que
     les dossiers, et rien ne justifiait qu'elles soient plus petites. */
  .lang-btn,
  .gal-filters-clear { min-height: 44px; }

  .gal-brand { padding: 6px 0; }
  .gal-foot a,
  .gal-foot-lien { min-height: 44px; }
  .cell-fav { width: 42px; height: 42px; }
  .cell-play { width: 54px; height: 54px; }
  .lb-btn { width: 48px; height: 48px; }
  .lb-nav { padding: 34px 18px; }
  .cta { padding-top: 10px; padding-bottom: 8px; }

  /* Le survol n'existe pas au doigt : les commandes restent visibles. */
  .cell-fav { opacity: .92; transform: none; }
}

/* Téléphone en paysage : la hauteur est la ressource rare. On rend tout ce qui
   n'est pas l'image aussi mince que possible, sans rien retirer. */
@media (max-height: 520px) and (orientation: landscape) {
  .gal-top-inner { min-height: 0; padding-top: max(8px, var(--sat)); padding-bottom: 6px; }
  .gal-actions { padding-bottom: 2px; }
  .gal-filters { padding-bottom: 8px; }
  .gal-folder { padding-top: 6px; padding-bottom: 6px; }

  .lb-top { padding-top: max(6px, var(--sat)); padding-bottom: 6px; }
  .lb-foot { padding-top: 6px; padding-bottom: max(8px, var(--sab)); }
  .lb-btn { width: 40px; height: 40px; }
  .lb-nav { padding: 16px 10px; }

  .cover-inner { padding-bottom: 20px; }
  .cover-title { font-size: calc(clamp(28px, 6vw, 44px) * var(--titres-echelle, 1) * var(--titres-geant, 1) * var(--titres-police, 1)); }
  .cover-welcome { display: none; }

  .gal-intro { padding-block: 20px 12px; }
}

/* =============================================================
   Robustesse visible : image en échec, bandeau hors ligne, éclat
   ============================================================= */

/* Une vignette qui n'a pas pu charger le dit, plutôt que de rester floue */
.cell.is-broken .cell-img { opacity: 0; }
.cell.is-broken::after {
  content: attr(data-broken);
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase;
  color: var(--fg-3);
  background: var(--bg-2);
}

/* Bandeau hors ligne : présent, discret, jamais bloquant */
.offline-banner {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(20px + var(--sab)); z-index: 210;
  max-width: min(92vw, 460px);
  padding: 12px 20px; border-radius: var(--radius-pill);
  background: var(--bg-2); color: var(--fg);
  border: 1px solid var(--hairline);
  font-family: var(--font-mono); font-size: 11px;
  letter-spacing: .06em; text-align: center;
  animation: panelIn var(--dur-ui) var(--ease-out) both;
}
.offline-banner.is-back { border-color: color-mix(in srgb, var(--success) 45%, transparent); }

/* Éclat au moment où une photo rejoint les favoris */
@keyframes coeurEclat {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.32); }
  60%  { transform: scale(.94); }
  100% { transform: scale(1); }
}
.cell-fav.is-burst svg,
.lb-btn.is-burst svg { animation: coeurEclat 520ms var(--ease-out); }
.cell-fav.is-burst::after {
  content: ''; position: absolute; inset: -6px;
  border-radius: 50%; border: 1.5px solid rgba(255,255,255,.55);
  animation: ringPulse 560ms var(--ease-out) forwards;
}
@keyframes ringPulse {
  from { transform: scale(.7); opacity: .8; }
  to   { transform: scale(1.5); opacity: 0; }
}

/* =============================================================
   Agréments : transition d'ouverture, fil, curseur
   ============================================================= */

/* L'image cliquée grandit jusqu'à sa place plein écran — et y redescend.
   --------------------------------------------------------------------------
   Trois pseudo-éléments, et il faut les régler tous les trois :

     -old / -new  les deux images, qui se fondent l'une dans l'autre ;
     -group       la BOÎTE, qui se déplace et change de taille.

   C'est le groupe qui porte le mouvement. En ne réglant que old et new, le
   fondu durait le temps voulu pendant que le déplacement gardait les 250 ms
   d'usine du navigateur : deux rythmes pour un seul geste, ce qu'on ne
   nomme pas mais qu'on sent. */
::view-transition-old(photo-active),
::view-transition-new(photo-active),
::view-transition-group(photo-active) {
  animation-duration: var(--dur-enter);
  animation-timing-function: var(--ease-out);
}
::view-transition-old(photo-active),
::view-transition-new(photo-active) { mix-blend-mode: normal; }

::view-transition-old(root),
::view-transition-new(root),
::view-transition-group(root) {
  animation-duration: var(--dur-ui);
  animation-timing-function: var(--ease-out);
}

/* Fil de progression : un pixel qui dit où l'on en est dans l'album */
.gal-progress {
  position: absolute; left: 0; right: 0; bottom: -1px;
  height: 2px; background: var(--fg);
  transform: scaleX(0); transform-origin: left;
  transition: transform var(--dur-micro) linear;
  opacity: .55;
}

/* Curseur éditorial, sur pointeur fin uniquement */
.gal-cursor {
  position: fixed; top: 0; left: 0; z-index: 90;
  width: 62px; height: 62px; border-radius: 50%;
  display: grid; place-items: center;
  pointer-events: none;
  background: var(--btn-bg); color: var(--btn-fg);
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase;
  opacity: 0; transform: translate(-50%, -50%) scale(.7);
  transition: opacity var(--dur-ui) var(--ease-out), scale var(--dur-ui) var(--ease-out);
}
.gal-cursor.is-on { opacity: 1; }
@media (pointer: fine) {
  .grid .cell-open { cursor: none; }
}

/* Sur pointeur fin, la vignette respire légèrement au survol */
@media (hover: hover) and (pointer: fine) {
  .cell { transition: filter var(--dur-ui) var(--ease-out); }
  .grid:hover .cell:not(:hover) { filter: brightness(.82); }
}

/* ── Sous la souris (réglage « survol ») ─────────────────────────────────
   « zoom » est la règle d'avant, plus haut. Le soulèvement passe par la
   propriété « translate », distincte de « transform » : les animations
   d'arrivée figent transform sur la case, et l'auraient annulé. */
@media (hover: hover) {
  [data-survol="eclat"] .cell-img { transition: filter var(--dur-ui) var(--ease-out), opacity var(--dur-enter) var(--ease-out); }
  [data-survol="eclat"] .cell:hover .cell-img { transform: none; filter: brightness(1.08) saturate(1.06); }
  [data-survol="leve"] .cell {
    transition: translate var(--dur-ui) var(--ease-out), box-shadow var(--dur-ui) var(--ease-out), filter var(--dur-ui) var(--ease-out);
  }
  [data-survol="leve"] .cell:hover { translate: 0 -5px; box-shadow: 0 18px 32px -20px rgba(0,0,0,.6); }
  [data-survol="leve"] .cell:hover .cell-img { transform: none; }
  [data-survol="aucun"] .cell:hover .cell-img { transform: none; }
  [data-survol="aucun"] .grid:hover .cell:not(:hover) { filter: none; }
}

/* =============================================================
   Lecteur vidéo — même grammaire que le reste
   ============================================================= */
.vp-controls {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; align-items: center; gap: var(--space-3);
  padding: 14px clamp(12px, 2vw, 20px) max(14px, var(--sab));
  background: linear-gradient(to top, rgba(8,11,12,.82), transparent);
  opacity: 0; transform: translateY(6px);
  transition: opacity var(--dur-ui) var(--ease-out), transform var(--dur-ui) var(--ease-out);
}
.vp:not(.is-playing) .vp-controls,
.vp:hover .vp-controls,
.vp:focus-within .vp-controls { opacity: 1; transform: none; }

.vp-btn {
  width: 34px; height: 34px; flex: none;
  display: grid; place-items: center;
  background: none; border: none; cursor: pointer;
  color: rgba(255,255,255,.85); border-radius: 50%;
  transition: color var(--dur-micro) var(--ease-out), background var(--dur-micro) var(--ease-out);
}
.vp-btn:hover { color: var(--sur-media); background: rgba(255,255,255,.1); }
.vp-btn svg { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }

.vp-time {
  font-family: var(--font-mono); font-size: 11px;
  color: rgba(255,255,255,.8); flex: none; min-width: 38px;
}
.vp-total { text-align: right; }

.vp-bar {
  position: relative; flex: 1; height: 22px; cursor: pointer;
  display: flex; align-items: center;
}
.vp-bar::before {
  content: ''; position: absolute; left: 0; right: 0; height: 2px;
  background: rgba(255,255,255,.22); border-radius: 2px;
}
.vp-buffered {
  position: absolute; left: 0; height: 2px; border-radius: 2px; width: 0;
}
/* L'avancement de la lecture : une seule valeur, posée sur la barre, dont la
   piste tire sa largeur et le curseur sa position. Le script écrivait la
   largeur et personne n'écrivait « --pos » : le curseur restait collé à
   gauche pendant tout le film — invisible au repos, faux dès le survol. */
.vp-played {
  position: absolute; left: 0; height: 2px; border-radius: 2px;
  width: var(--pos, 0);
}
.vp-buffered { background: rgba(255,255,255,.38); }
.vp-played   { background: var(--sur-media); }
.vp-knob {
  position: absolute; top: 50%; left: 0;
  width: 10px; height: 10px; border-radius: 50%; background: var(--sur-media);
  transform: translate(-50%, -50%) scale(0);
  transition: transform var(--dur-micro) var(--ease-out);
}
.vp-bar:hover .vp-knob,
.vp-bar:focus-visible .vp-knob { transform: translate(-50%, -50%) scale(1); }
.vp-played + .vp-knob { left: var(--pos, 0); }

@media (pointer: coarse) {
  .vp-btn { width: 42px; height: 42px; }
  .vp-bar { height: 34px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   LES RETOURS HORODATÉS

   Sur un film, un commentaire sans son moment ne sert à rien : « la coupe
   arrive trop tôt » oblige à chercher. La case rattache le mot à la seconde
   affichée, et chaque retour reçu devient un bouton qui y ramène.
   ═══════════════════════════════════════════════════════════════════════════ */
.lb-tc {
  display: flex; align-items: center; gap: var(--space-3);
  margin-bottom: 14px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  cursor: pointer;
}
.lb-tc input { flex: 0 0 auto; width: 16px; height: 16px; accent-color: var(--accent); }
.lb-tc-texte {
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase;
}

/* Le timecode d'un retour : un bouton, parce que c'en est un. */
.lb-tc-saut {
  float: left; margin: 2px 10px 2px 0;
  font-family: var(--font-mono); font-size: 11px;
  padding: 3px 8px; min-height: 24px;
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: transparent; color: inherit; cursor: pointer;
  transition: all var(--dur-micro) var(--ease-out);
}
.lb-tc-saut:hover { background: var(--accent); border-color: transparent; color: var(--sur-media); }

/* ═══════════════════════════════════════════════════════════════════════════
   LES FILMS EN TÊTE D'ALBUM

   Un mariage se livre comme un film accompagné de photos. Rangé dans la
   grille, l'aftermovie s'affichait comme la photo numéro deux cent trente-sept.

   Le premier film prend toute la largeur ; les suivants — teaser, format
   vertical — se rangent en dessous, à deux par ligne. Le rapport 16/9 est
   imposé au principal pour qu'il ne dépende pas du chargement de l'image.
   ═══════════════════════════════════════════════════════════════════════════ */
.films {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
  gap: clamp(14px, 2vw, 26px);
  margin: 0 auto clamp(28px, 5vh, 56px);
  padding: 0 var(--gutter);
  max-width: var(--wrap-max, 1600px);
}
.film-principal { grid-column: 1 / -1; }

.film { margin: 0; position: relative; }
.film-ouvrir {
  display: block; width: 100%; padding: 0; border: 0; cursor: pointer;
  background: var(--letterbox); border-radius: var(--radius-md); overflow: hidden;
  position: relative;
  /* Le rapport du film lui-même, 16/9 seulement à défaut. Un cadre 16/9 imposé
     rognait une version verticale en bandeau — le format qu'on livre justement
     pour être vu en entier sur un téléphone. Mesuré : 1080×1920 rendu à 1,78. */
  aspect-ratio: var(--ar, 1.7778);
}

/* Le lecteur natif de la page d'aperçu public : même cadre que les films de
   l'album, mais sans le bouton de lecture maison — la page est publique, sans
   session, et l'on ne charge pas tout le script d'album pour lire une vidéo. */
.film-lecteur {
  display: block; width: 100%;
  aspect-ratio: var(--ar, 1.7778);
  background: var(--letterbox);
  border-radius: var(--radius-md);
  object-fit: contain;
}
.film-principal.est-vertical .film-lecteur {
  max-width: calc(74vh * var(--ar, 0.5625));
  margin-inline: auto;
}

/* Le film principal prend toute la largeur — sauf s'il est VERTICAL : à
   1280 px de large, un 9/16 mesurerait 2275 px de haut et occuperait trois
   écrans. Celui-là seul est borné en hauteur, sa largeur suivant son rapport,
   et il se centre. Un 16/9 garde toute la largeur : le plafonner le rétrécissait
   sur les écrans bas, pour rien. */
.film-principal.est-vertical .film-ouvrir {
  max-width: calc(74vh * var(--ar, 0.5625));
  margin-inline: auto;
}
.film-image {
  width: 100%; height: 100%; object-fit: cover; display: block;
  /* « --dur-slow » et « --dur-base » n'ont jamais existé : ces deux
     transitions ne faisaient donc rien. La vignette et son bouton de lecture
     bougent sur le même survol — un seul temps les garde ensemble. */
  transition: transform var(--dur-ui) var(--ease-out), filter var(--dur-ui) var(--ease-out);
}
@media (hover: hover) {
  .film-ouvrir:hover .film-image { transform: scale(1.03); filter: brightness(.86); }
}

/* Le triangle de lecture : c'est lui qui dit « film » avant toute légende. */
.film-play {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 72px; height: 72px; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(0, 0, 0, .42); color: #fff;
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  transition: transform var(--dur-ui) var(--ease-out), background var(--dur-ui) var(--ease-out);
}
.film-ouvrir:hover .film-play { transform: translate(-50%, -50%) scale(1.08); background: rgba(0, 0, 0, .58); }
.film:not(.film-principal) .film-play { width: 54px; height: 54px; }
.film:not(.film-principal) .film-play svg { width: 22px; height: 22px; }

.film-legende {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4);
  margin-top: 10px;
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase;
}
.film-duree { opacity: .6; white-space: nowrap; }

/* Sans ce titre, les photos paraissent appartenir au dernier film. */
.films-suite {
  max-width: var(--wrap-max, 1600px);
  margin: 0 auto clamp(16px, 3vh, 28px);
  padding: 0 var(--gutter);
  font-family: var(--font-mono); font-size: 11px; font-weight: 400;
  letter-spacing: var(--etiq-chasse); text-transform: uppercase; opacity: .55;
}

@media (max-width: 640px) {
  .film-play { width: 56px; height: 56px; }
  .film-play svg { width: 22px; height: 22px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   LES CHAPITRES D'UN FILM

   Un montage de cérémonie dure trente minutes ; sans repère on cherche « les
   vœux » en faisant glisser le curseur. La liste se pose sous le lecteur et
   défile si elle est longue — un film de rushes peut en compter vingt.
   ═══════════════════════════════════════════════════════════════════════════ */
.vp-chapitres {
  list-style: none; margin: 0; padding: 10px 0 0;
  max-height: 26vh; overflow-y: auto;
  display: flex; flex-direction: column; gap: var(--space-0);
}
.vp-chapitre {
  display: flex; align-items: baseline; gap: var(--space-4);
  width: 100%; min-height: 32px; padding: 6px 10px;
  border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--sur-media); cursor: pointer;
  font-family: var(--font-mono); font-size: 12px; text-align: left;
  transition: background var(--dur-micro) var(--ease-out);
}
.vp-chapitre:hover { background: rgba(255, 255, 255, .1); }
.vp-chapitre-t { flex: 0 0 auto; opacity: .55; font-variant-numeric: tabular-nums; }
.vp-chapitre-n { flex: 1 1 auto; letter-spacing: .04em; }

/* ═══════════════════════════════════════════════════════════════════════════
   CLORE LA BOUCLE

   Recueillir des retours et livrer des versions ne suffit pas : sans un geste
   qui dit « c'est bon », « encore une petite chose » ne rencontre aucune
   limite. Le bouton se pose sous les chapitres, après le film — on valide ce
   qu'on vient de regarder, pas ce qu'on s'apprête à regarder.
   ═══════════════════════════════════════════════════════════════════════════ */
.vp-validation {
  padding: 14px 0 4px;
  /* Le filet suit la surface : la visionneuse peut être claire, et un
     blanc à 12 % n'y existe pas. */
  border-top: 1px solid var(--line, var(--hairline));
  margin-top: 10px;
}
.vp-revisions {
  margin: 0 0 12px;
  font-family: var(--font-mono); font-size: 11px;
  letter-spacing: .06em; opacity: .7;
}
/* Le forfait est épuisé : cela doit se voir sans se lire deux fois. */
.vp-revisions.est-epuisee { opacity: 1; color: var(--etat-devis, #d9a441); }

.vp-valider { width: 100%; }

/* Une fois validé, il ne reste qu'un constat — plus aucun bouton. */
.vp-validation.est-validee { border-top-color: var(--fg-3); }
.vp-valide-mot {
  margin: 0; display: flex; align-items: center; gap: var(--space-2);
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .06em;
}
.vp-valide-mot::before {
  content: ''; flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%;
  background: var(--etat-confirme, #2f7d5c);
}

/* ── Le bouton des sous-titres ────────────────────────────────────────────
   « CC » plutôt qu'un pictogramme : c'est la marque universelle, comprise
   partout, et elle laisse la place d'afficher « FR » quand une piste tourne. */
.vp-btn.vp-st {
  font-family: var(--font-mono);
  font-size: 10px; font-weight: 500; letter-spacing: .08em;
  min-width: 30px;
}
/* Le bouton est rond : un soulignement s'y courbe et ressemble à un défaut.
   L'état actif se dit par le contraste — fond clair, texte sombre. */
.vp-btn.vp-st.est-actif {
  background: rgba(255, 255, 255, .9);
  color: var(--paper-ink, #0d1113);
}
.vp-btn.vp-st.est-actif:hover { background: #fff; }

/* ═══════════════════════════════════════════════════════════════════════════
   LES DISPOSITIONS DE LA GRILLE

   Six façons de poser les mêmes photos. La justifiée est la planche contact :
   on voit beaucoup, vite. Les autres ralentissent le regard — c'est leur
   raison d'être, pas un effet.

   Trois d'entre elles n'ont pas une ligne de JavaScript : leur rythme tient à
   la largeur de la fenêtre, et le navigateur le recalcule seul à chaque
   redimensionnement. Y mettre un moteur coûterait un recalcul par défilement
   pour exactement le même résultat.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Mosaïque : colonnes de largeur égale, hauteurs libres ───────────────── */
.grid.is-mosaique { position: relative; display: block; }
.grid.is-mosaique .cell,
.grid.is-mosaique .chapitre { position: absolute; margin: 0; }
/* Marges à zéro : en position absolue elles s'ajoutent au « top » calculé,
   si bien que le titre descend là où le JavaScript ne l'attend pas et
   recouvre la première rangée. L'espacement est donné par le moteur, qui
   sait alors exactement où reprendre les colonnes. */
.grid.is-mosaique .chapitre { margin: 0; }

/* ── Plein écran : une photo à la fois, on descend ───────────────────────
   « contain » et non « cover » : recadrer soi-même le cadrage de quelqu'un
   dont c'est le métier serait déplacé. Une photo verticale garde ses marges. */
.grid[data-grille="plein"] {
  display: block; padding: 0;
}
.grid[data-grille="plein"] .cell {
  width: 100%; margin: 0 0 var(--grid-gap, 8px);
  border-radius: 0;
  /* « background-color », surtout pas la raccourcie « background » : celle-ci
     remet AUSSI background-size et background-repeat à leurs valeurs
     initiales (auto / repeat). L'aperçu flouté, posé en image de fond par le
     serveur, se répétait alors en damier sur toute la cellule — le « truc
     bizarre » du mode nuit, qui emploie cette disposition. */
  background-color: transparent;

  /* La hauteur vient du RAPPORT de la photo, pas de l'image chargée.
     « content-visibility: auto » ne rend pas ce qui est loin de l'écran :
     si la hauteur dépendait du contenu, chaque cellule hors champ vaudrait
     la taille de repli — et la hauteur de page, donc l'ascenseur, sauterait
     à chaque défilement. Avec un rapport, elle est connue d'avance. */
  aspect-ratio: var(--ar, 1.5);
  max-height: 100svh;
}
/* Le remplacement se cadre comme la photo : en « contain ». Laissé en
   « cover », il débordait de chaque côté d'une image verticale et l'on voyait
   deux bandes floues encadrer la photographie. */
.grid[data-grille="plein"] .cell-flou {
  background-size: contain; background-repeat: no-repeat;
}
.grid[data-grille="plein"] .cell-open { cursor: zoom-in; }
.grid[data-grille="plein"] .cell-img {
  width: 100%; height: 100%; object-fit: contain;
}
.grid[data-grille="plein"] .chapitre { padding: 0 clamp(16px, 4vw, 48px); }

/* ── Cinéma : une bande large par rangée, séparée de noir ────────────────
   Ici on recadre, et c'est voulu : la photo se lit comme un plan. Le rapport
   suit le format de diffusion, pas celui du capteur. */
.grid[data-grille="cinema"] {
  display: block; padding: clamp(16px, 3vh, 40px) 0;
  background: var(--letterbox, #000);
}
.grid[data-grille="cinema"] .cell {
  width: 100%; height: auto; aspect-ratio: 2.4;
  margin: 0 0 clamp(16px, 3vh, 40px); border-radius: 0;
}
.grid[data-grille="cinema"] .cell-img {
  width: 100%; height: 100%; object-fit: cover;
}
.grid[data-grille="cinema"] .chapitre { padding: 0 clamp(16px, 4vw, 48px); }

/* Sur téléphone, une bande en 2.4 ne montre plus rien : on rend sa hauteur
   à la photo plutôt que d'en garder un bandeau de trente pixels. */
@media (max-width: 640px) {
  .grid[data-grille="cinema"] .cell { aspect-ratio: 16 / 9; }
}

/* ── Magazine : grandes et petites en alternance ─────────────────────────
   Un rythme de six colonnes, répété toutes les cinq photos : une pleine
   largeur, deux moitiés, puis deux tiers et un tiers. Le motif se voit sans
   qu'on puisse le prévoir, ce qui est exactement ce qu'on demande à une
   maquette de magazine. */
.grid[data-grille="magazine"] {
  display: grid; grid-template-columns: repeat(6, 1fr);
  gap: var(--grid-gap, 8px);
  align-items: start;
}
.grid[data-grille="magazine"] .cell {
  width: auto; height: auto; aspect-ratio: var(--ar, 1.5);
  grid-column: span 3;
}
.grid[data-grille="magazine"] .cell:nth-child(5n + 1) { grid-column: span 6; }
.grid[data-grille="magazine"] .cell:nth-child(5n + 4) { grid-column: span 4; }
.grid[data-grille="magazine"] .cell:nth-child(5n)     { grid-column: span 2; }
.grid[data-grille="magazine"] .cell-img { width: 100%; height: 100%; object-fit: cover; }
.grid[data-grille="magazine"] .chapitre { grid-column: 1 / -1; }

@media (max-width: 900px) {
  .grid[data-grille="magazine"] { grid-template-columns: repeat(2, 1fr); }
  .grid[data-grille="magazine"] .cell { grid-column: span 1; }
  .grid[data-grille="magazine"] .cell:nth-child(5n + 1) { grid-column: span 2; }
  .grid[data-grille="magazine"] .cell:nth-child(5n + 4) { grid-column: span 2; }
  .grid[data-grille="magazine"] .cell:nth-child(5n)     { grid-column: span 1; }
}

/* ═══ Trois grilles de plus : carrés, vedette, défilé ═══════════════════════
   Du CSS seul, comme le magazine : leur rythme tient à la largeur de la
   fenêtre, le navigateur le recalcule sans script. gallery.js les range avec
   les modes « sans moteur » (voir disposer()). */
.grid[data-grille="carres"] {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(clamp(130px, 20vw, 240px), 1fr));
  gap: var(--grid-gap, 8px); align-items: start;
}
.grid[data-grille="carres"] .cell,
.grid[data-grille="vedette"] .cell { width: auto; height: auto; aspect-ratio: 1; }
.grid[data-grille="carres"] .cell-img,
.grid[data-grille="vedette"] .cell-img,
.grid[data-grille="defile"] .cell-img { width: 100%; height: 100%; object-fit: cover; }
.grid[data-grille="carres"] .chapitre,
.grid[data-grille="vedette"] .chapitre { grid-column: 1 / -1; }

/* Vedette : quatre colonnes, une photo sur cinq en double largeur et double
   hauteur. Les cases étant carrées, la grande tombe juste sur deux rangées. */
.grid[data-grille="vedette"] {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-auto-flow: dense; gap: var(--grid-gap, 8px);
}
.grid[data-grille="vedette"] .cell:nth-child(5n + 1) { grid-column: span 2; grid-row: span 2; }
@media (max-width: 700px) {
  .grid[data-grille="vedette"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Défilé : une rangée qu'on fait glisser. Elle défile dans sa propre boîte —
   jamais la page, qui ne doit pas déborder sur le côté. */
.grid[data-grille="defile"] {
  display: flex; flex-wrap: nowrap; align-items: center;
  gap: var(--grid-gap, 8px);
  overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain;
  scroll-snap-type: x proximity; scrollbar-width: thin;
  padding-bottom: 14px;
}
.grid[data-grille="defile"] .cell {
  flex: 0 0 auto; width: auto; height: min(64vh, 560px);
  aspect-ratio: var(--ar, 1.5); scroll-snap-align: center;
}
.grid[data-grille="defile"] .chapitre {
  flex: 0 0 auto; width: 13ch; white-space: normal; align-self: center;
  margin: 0 clamp(8px, 2vw, 24px);
}
@media (max-width: 560px) {
  .grid[data-grille="defile"] .cell { height: 52vh; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   LE DIAPORAMA

   Une projection. Rien à l'écran que l'image, et des commandes qui s'effacent
   quand on ne s'en sert pas.
   ═══════════════════════════════════════════════════════════════════════════ */
.diapo-body { margin: 0; overflow: hidden; background: var(--letterbox, #000); }
.diapo-body.sans-curseur { cursor: none; }

.diapo {
  position: fixed; inset: 0;
  display: grid; place-items: center;
  background: var(--letterbox, #000);
}

/* ── Les deux scènes qui alternent ───────────────────────────────────────── */
.diapo-scene {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  opacity: 0; pointer-events: none;
  transition: opacity 900ms var(--ease-out);
}
.diapo-scene.est-visible { opacity: 1; }

/* La photo occupe la projection, quitte à être agrandie. « max-width » seul
   laissait une image de 2400 px flotter au milieu d'un écran 4K, entourée de
   noir : ce n'est pas une projection, c'est une vignette. « contain » garde
   le cadrage intact — on agrandit, on ne recadre pas. */
.diapo-media {
  /* En position absolue, jamais en élément de grille avec « height: 100% ».
     Une hauteur en pourcentage exige un bloc conteneur de hauteur DÉFINIE ;
     la rangée de grille était dimensionnée par son contenu, donc indéfinie,
     et la hauteur retombait sur « auto ». L'image prenait alors sa hauteur
     naturelle : 1067 px dans une fenêtre de 900, coupée en haut et en bas.
     « inset: 0 » sur un parent positionné donne toujours une boîte définie. */
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
}

/* Fondu et zoom lent : le mouvement inventé pour donner vie à des images
   fixes dans un documentaire. Quatorze secondes pour six pour cent — assez
   lent pour qu'on ne le voie pas commencer. */
.diapo-scene.t-zoom.est-visible .diapo-media { animation: diapoZoom 14s var(--ease-out) forwards; }
@keyframes diapoZoom { from { transform: scale(1); } to { transform: scale(1.06); } }

.diapo-scene.t-glisse { transition: opacity var(--dur-enter) var(--ease-out), transform var(--dur-enter) var(--ease-out); transform: translateX(4%); }
.diapo-scene.t-glisse.est-visible { transform: none; }

.diapo-scene.t-coupe { transition: none; }
/* Volet : la suivante se découvre de droite à gauche ; la précédente reste
   dessous jusqu'au bout, puis s'efface d'un coup — sans quoi les deux se
   refermeraient l'une sur l'autre. */
.diapo-scene.t-volet { clip-path: inset(0 0 0 100%); transition: clip-path 0s 950ms, opacity 0s 950ms; }
.diapo-scene.t-volet.est-visible { clip-path: inset(0); z-index: 1; transition: clip-path 950ms var(--ease-out), opacity 0s; }
/* Flou : l'image se brouille en s'effaçant, la suivante se précise. */
.diapo-scene.t-flou { filter: blur(16px); transition: opacity 1100ms var(--ease-out), filter 1100ms var(--ease-out); }
.diapo-scene.t-flou.est-visible { filter: none; }

/* ── Les cartons d'ouverture et de fin ───────────────────────────────────── */
.diapo-carton {
  position: absolute; inset: 0; z-index: 3;
  display: grid; place-content: center; justify-items: center;
  gap: var(--space-3); padding: 24px; text-align: center;
  background: var(--letterbox, #000);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-enter) var(--ease-out);
}
/* Une seule condition pour paraître : « is-visible ». Y ajouter
   « :not([hidden]) » rendait le carton d'ouverture éternel — il n'a pas
   l'attribut « hidden », donc il restait par-dessus la première photo. */
.diapo-carton.is-visible { opacity: 1; pointer-events: auto; }
.diapo-carton[hidden] { display: none; }

.diapo-kicker {
  margin: 0; font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase; color: rgba(255,255,255,.48);
}
.diapo-titre {
  margin: 0; font-family: var(--font-display); font-weight: 300;
  font-size: clamp(32px, 6vw, 76px); letter-spacing: -.035em;
  color: rgba(255,255,255,.92); overflow-wrap: anywhere;
}
.diapo-date, .diapo-attente {
  margin: 0; font-family: var(--font-mono); font-size: 11px;
  letter-spacing: .12em; color: rgba(255,255,255,.4);
}
.diapo-attente { margin-top: 18px; }
.diapo-fin-actions { display: flex; flex-wrap: wrap; gap: var(--space-3); justify-content: center; margin-top: 22px; }

/* ── La barre de commandes ───────────────────────────────────────────────── */
.diapo-barre {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 4;
  padding: 40px clamp(12px, 3vw, 28px) clamp(12px, 2vh, 20px);
  background: linear-gradient(to top, rgba(0,0,0,.6), transparent);
  transition: opacity var(--dur-enter) var(--ease-out), transform var(--dur-enter) var(--ease-out);
}
.diapo-barre.est-endormie { opacity: 0; transform: translateY(12px); pointer-events: none; }

.diapo-progres {
  height: 2px; margin-bottom: 14px;
  background: rgba(255,255,255,.18); border-radius: 2px; overflow: hidden;
}
.diapo-progres > span {
  display: block; height: 100%; width: 100%;
  background: rgba(255,255,255,.85);
  transform: scaleX(0); transform-origin: left;
}

.diapo-boutons { display: flex; align-items: center; gap: var(--space-2); }
.diapo-espace { flex: 1 1 auto; }

.diapo-btn {
  display: grid; place-items: center; flex: none;
  width: 40px; height: 40px; padding: 0;
  border: none; border-radius: 50%;
  background: none; color: rgba(255,255,255,.85); cursor: pointer;
  text-decoration: none;
  transition: color var(--dur-micro) var(--ease-out), background var(--dur-micro) var(--ease-out);
}
.diapo-btn:hover { color: #fff; background: rgba(255,255,255,.12); }
.diapo-btn svg { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
/* Les icônes pleines — lecture, pause — sont dessinées par leur remplissage.
   La règle générale les vide, il faut leur rendre les deux propriétés, pas
   seulement le trait : autrement elles ne dessinent plus rien du tout. */
.diapo-btn svg[fill="currentColor"] { fill: currentColor; stroke: none; }
.diapo-btn-gros { width: 48px; height: 48px; }

.diapo-compteur {
  margin-left: 8px; font-size: 11px; letter-spacing: .1em;
  color: rgba(255,255,255,.55); white-space: nowrap;
}

/* Sur téléphone, le compteur passe après les commandes : quarante pixels de
   large valent mieux à un pouce qu'un chiffre. */
@media (max-width: 480px) {
  .diapo-compteur { display: none; }
}

/* Le repos demandé par le système coupe tout mouvement, zoom compris. */
@media (prefers-reduced-motion: reduce) {
  .diapo-scene { transition: opacity var(--dur-ui) linear; }
  .diapo-scene.t-zoom.est-visible .diapo-media { animation: none; }
  .diapo-scene.t-glisse { transform: none; }
}

/* ── Diaporama en fond ───────────────────────────────────────────────────
   L'album s'ouvre sur une image, en grand, avant de montrer qu'il y en a
   quatre cents. La grille commence juste sous le pli : un mouvement de
   défilement suffit, rien n'est caché. */
.gal-fond {
  position: relative;
  height: 78svh; min-height: 320px;
  margin-bottom: clamp(20px, 4vh, 46px);
  overflow: hidden;
  background: var(--letterbox, #000);
}
.gal-fond-couche {
  position: absolute; inset: 0;
  /* « contain » et non « cover » : recadrer soi-même le cadrage de quelqu'un
     dont c'est le métier n'est pas à nous. On voit la photographie entière,
     quitte à laisser du fond de chaque côté — ce fond est celui de la page,
     donc il s'accorde à la teinte choisie. */
  background-size: contain; background-repeat: no-repeat; background-position: center;
  opacity: 0; transition: opacity 1600ms var(--ease-out);
}
.gal-fond-couche.est-visible { opacity: 1; }

/* Un dégradé en pied : la grille naît de l'image au lieu de la couper net. */
.gal-fond::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 42%;
  background: linear-gradient(to top, var(--bg), transparent);
  pointer-events: none;
}

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

/* ── L'aperçu intégré dans un autre site ─────────────────────────────────
   Un cadre est large et bas : tout ce qui n'est pas l'image lui prend la
   seule chose dont il manque. D'où une page sans barre ni pied. */
.embed-body { margin: 0; background: var(--letterbox, #000); overflow: hidden; }
.embed { position: relative; height: 100svh; display: grid; }
.embed-film { width: 100%; height: 100%; object-fit: contain; background: var(--letterbox, #000); min-height: 0; }

/* Un lecteur pose ses propres commandes sur ses derniers pixels. Une barre de
   titre en surimpression les rendrait inatteignables — on lui donne sa
   rangée, et le lecteur garde la sienne. */
.embed.a-un-film { grid-template-rows: minmax(0, 1fr) auto; }
.embed.a-un-film .embed-lien {
  position: static; background: none;
  padding: 10px 16px 12px; align-items: center;
}

.embed-bande {
  display: flex; gap: var(--space-1); height: 100%;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.embed-bande::-webkit-scrollbar { display: none; }
.embed-vue { height: 100%; width: auto; object-fit: cover; scroll-snap-align: start; flex: none; }

.embed-lien {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-4); padding: 28px 16px 12px;
  background: linear-gradient(to top, rgba(0,0,0,.72), transparent);
  color: rgba(255,255,255,.92); text-decoration: none;
}
.embed-titre {
  font-family: var(--font-display); font-weight: 300; font-size: 17px;
  letter-spacing: -.02em; min-width: 0; overflow-wrap: anywhere;
}
.embed-cta {
  flex: none;
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase;
  border-bottom: 1px solid currentColor; padding-bottom: 2px;
}
.embed-lien:hover .embed-cta { color: #fff; }

/* ═══════════════════════════════════════════════════════════════════════════
   LE PANNEAU DE PARTAGE

   Deux liens possibles, et il faut voir lequel on donne : celui de la galerie
   demande un mot de passe, l'aperçu s'ouvre à tout le monde. La note change
   de couleur pour que la différence se voie avant qu'on ait cliqué.
   ═══════════════════════════════════════════════════════════════════════════ */
.partage {
  width: min(460px, calc(100vw - 28px));
  padding: 0; border: 1px solid var(--line);
  border-radius: var(--radius-md, 12px);
  /* « --bg-1 » n'a jamais existé dans les jetons : c'était donc TOUJOURS le
     repli sombre écrit à droite qui s'appliquait, y compris sur une galerie
     claire. Un repli qui sert toujours n'est pas un repli, c'est la valeur. */
  background: var(--bg-2); color: var(--fg);
}
.partage::backdrop { background: rgba(0, 0, 0, .58); backdrop-filter: blur(3px); }

.partage-tete {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); padding: 16px 16px 10px;
}
.partage-titre {
  margin: 0; font-family: var(--font-display); font-weight: 300;
  font-size: 20px; letter-spacing: -.02em; min-width: 0;
}
.partage-fermer {
  flex: none; width: 32px; height: 32px; display: grid; place-items: center;
  border: none; border-radius: 50%; background: none;
  color: var(--fg-2); cursor: pointer;
}
.partage-fermer:hover { color: var(--fg); background: var(--hover); }
.partage-fermer svg { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }

.partage-choix { display: flex; gap: var(--space-2); padding: 0 16px 4px; flex-wrap: wrap; }
.partage-onglet {
  flex: 1 1 auto; min-width: 0; padding: 8px 12px;
  border: 1px solid var(--line); border-radius: var(--radius-pill, 999px);
  background: none; color: var(--fg-2); cursor: pointer;
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase;
  overflow-wrap: anywhere;
}
.partage-onglet.est-actif { background: var(--fg); color: var(--bg); border-color: transparent; }

.partage-corps { padding: 12px 16px 16px; }

.partage-note {
  margin: 0 0 12px; font-size: 12px; line-height: 1.5; color: var(--fg-2);
  padding-left: 10px; border-left: 2px solid var(--etat-confirme, #2f7d5c);
}
/* Un lien protégé : la barre change de couleur, et l'on n'a pas à lire pour
   comprendre qu'il ne se publie pas. */
.partage-note.est-privee { border-left-color: var(--etat-devis, #d9a441); }

.partage-lien {
  display: flex; align-items: center; gap: var(--space-3);
  padding: 8px 8px 8px 12px; margin-bottom: 14px;
  border: 1px solid var(--line); border-radius: var(--radius-sm, 6px);
}
.partage-url {
  flex: 1 1 auto; min-width: 0;
  font-family: var(--font-mono); font-size: 11px; color: var(--fg-2);
  overflow-wrap: anywhere; user-select: all;
  /* Deux lignes au plus : une adresse longue ne doit pas pousser les boutons
     hors du panneau. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.partage-lien .btn { flex: none; }

.partage-reseaux { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-2); }
@media (max-width: 380px) { .partage-reseaux { grid-template-columns: repeat(2, 1fr); } }

.partage-reseau {
  min-height: 40px; padding: 10px 8px;
  border: 1px solid var(--line); border-radius: var(--radius-sm, 6px);
  background: none; color: var(--fg); cursor: pointer;
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase;
  overflow-wrap: anywhere;
  transition: background var(--dur-micro) var(--ease-out), border-color var(--dur-micro) var(--ease-out);
}
.partage-reseau:hover { background: var(--hover); border-color: var(--fg-3); }

.partage-embed { padding: 0 16px 16px; border-top: 1px solid var(--line); margin-top: 4px; padding-top: 14px; }
.partage-embed .field-textarea { font-family: var(--font-mono); font-size: 11px; margin-top: 6px; }

/* ═══════════════════════════════════════════════════════════════════════════
   L'APPARENCE D'UNE GALERIE

   Tout ce qui suit est piloté par des attributs posés sur <body> :

       <body data-densite="aeree" data-coins="doux" data-grain="1">

   Aucune décision n'est prise en PHP ni en JavaScript — c'est le CSS qui tire
   les conséquences. L'aperçu en direct de l'administration n'a donc qu'un
   attribut à changer pour montrer le résultat, sans recharger et sans qu'une
   logique existe en double.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── La densité : l'air entre les photos ─────────────────────────────────── */
[data-densite="normale"] { --grid-gap: var(--space-2);  --grid-marge: 1; }
[data-densite="aeree"]   { --grid-gap: var(--space-6); --grid-marge: 1.9; }
[data-densite="dense"]   { --grid-gap: var(--space-1);  --grid-marge: .45; }

/* ── Les coins des vignettes ─────────────────────────────────────────────── */
[data-coins="nets"]  { --vignette-rayon: 2px; }
[data-coins="doux"]  { --vignette-rayon: 8px; }
[data-coins="ronds"] { --vignette-rayon: 18px; }

/* ── Le cadre des photos ─────────────────────────────────────────
   Posé sur la case de la grille, jamais sur le calcul des rangées : la
   photo remplit toujours sa case (object-fit: cover), le cadre prend sur
   elle. Le passe-partout et le polaroïd sont blancs sur tous les fonds —
   c'est un tirage, pas un décor de la page. */
[data-cadre="bordure"] .cell { box-shadow: 0 0 0 1px var(--line); }
[data-cadre="passe_partout"] .cell,
[data-cadre="polaroid"] .cell {
  padding: clamp(5px, .9vw, 12px);
  background: #fbfaf8;
  box-shadow: 0 1px 2px rgba(0,0,0,.10), 0 6px 18px -10px rgba(0,0,0,.35);
}
[data-cadre="polaroid"] .cell { padding-bottom: clamp(18px, 2.6vw, 34px); }
[data-cadre="passe_partout"] .cell-img,
[data-cadre="polaroid"] .cell-img { border-radius: max(0px, calc(var(--vignette-rayon, 2px) - 4px)); }
/* Le zoom au survol déborderait sur la marge blanche : un tirage ne grossit pas. */
@media (hover: hover) {
  [data-cadre="passe_partout"] .cell:hover .cell-img,
  [data-cadre="polaroid"] .cell:hover .cell-img { transform: none; }
}
[data-cadre="ombre"] .cell {
  box-shadow: 0 2px 4px rgba(0,0,0,.12), 0 14px 30px -14px rgba(0,0,0,.5);
}
/* ── L'apparition des photos ─────────────────────────────────────────────
   La grille pose « animation: fadeIn » sur chaque vignette. On ne remplace
   pas l'animation, on remplace ses IMAGES CLÉS : le nom reste, la cascade et
   les délais déjà calculés restent, et seule la façon d'arriver change. */
/* fadeIn n'est pas redéfini ici : il vit dans tokens.css, et deux copies
   d'une même animation finiraient par ne plus dire la même chose. */
@keyframes monteeIn    { from { opacity: 0; transform: translateY(26px); } to { opacity: 1; transform: none; } }
@keyframes echelleIn   { from { opacity: 0; transform: scale(.94); }       to { opacity: 1; transform: none; } }

[data-apparition="montee"]  .cell { animation-name: monteeIn; animation-duration: 620ms; }
[data-apparition="echelle"] .cell { animation-name: echelleIn; animation-duration: 560ms; }
/* « aucune » ne coupe pas l'animation, elle la rend instantanée : la règle
   qui pose « opacity: 0 » sur la cellule reste, et la supprimer laisserait
   tout invisible. */
[data-apparition="aucune"]  .cell { animation-duration: 1ms; }

/* Deux arrivées de plus. Leur image d'arrivée ne nomme que l'opacité : le
   flou et le décalage reviennent d'eux-mêmes à la valeur de la case, sans
   être figés ensuite par l'animation — le survol peut alors encore agir. */
@keyframes flouIn   { from { opacity: 0; filter: blur(14px); }         to { opacity: 1; } }
@keyframes glisseIn { from { opacity: 0; transform: translateX(-28px); } to { opacity: 1; } }
[data-apparition="flou"]   .cell { animation-name: flouIn; animation-duration: 760ms; }
[data-apparition="glisse"] .cell { animation-name: glisseIn; animation-duration: 620ms; }

/* ── Le mouvement de la couverture ───────────────────────────────────────── */
@keyframes couvertureZoom { from { transform: scale(1); } to { transform: scale(1.09); } }
[data-cover_motion="zoom"] .cover-bg {
  animation: couvertureZoom 26s var(--ease-out) forwards;
}
/* La parallaxe se fait par « background-attachment » : pas une ligne de
   JavaScript, donc rien qui s'exécute à chaque pixel de défilement. */
[data-cover_motion="parallaxe"] .cover-bg { background-attachment: fixed; }

/* ── Les titres ──────────────────────────────────────────────────────────
   Trois traitements des MÊMES fontes. Je n'ajoute pas de famille : une
   nouvelle fonte veut dire un fichier de plus à charger, et le site
   d'Eloic tient sur deux familles — les mélanger en ajouterait une
   troisième qui ne viendrait de nulle part. */
/* ── La taille des titres ────────────────────────────────────────────────
   Trois facteurs, qui valent 1 par défaut — ces deux tailles sont alors
   exactement celles de tokens.css : le curseur des réglages fins
   (--titres-echelle, 0,8 à 1,4), le style « Géant » (--titres-geant) et la
   fonte (--titres-police) — une Cormorant ou une Parisienne a l'œil plus
   petit et paraîtrait deux crans en dessous à taille égale.

   Ces deux règles sont posées AVANT la liste « Fin » qui suit, et non en
   son milieu : insérées entre ses sélecteurs, elles en faisaient partie, et
   les petits titres prenaient la taille des grands. */
.display    { font-size: calc(clamp(38px, 7vw, 92px) * var(--titres-echelle, 1) * var(--titres-geant, 1) * var(--titres-police, 1)); }
.display-sm { font-size: calc(clamp(28px, 4vw, 46px) * var(--titres-echelle, 1) * var(--titres-geant, 1) * var(--titres-police, 1)); }

[data-titres="fin"]     .display,
[data-titres="fin"]     .display-sm,
[data-titres="fin"]     .cover-title { font-weight: 300; letter-spacing: -.035em; }

[data-titres="affirme"] .display,
[data-titres="affirme"] .display-sm,
[data-titres="affirme"] .cover-title { font-weight: 500; letter-spacing: -.02em; }

[data-titres="mono"]    .display,
[data-titres="mono"]    .display-sm,
[data-titres="mono"]    .cover-title {
  font-family: var(--font-mono); font-weight: 400;
  letter-spacing: .04em; text-transform: uppercase;
  /* Sa propre taille — des capitales espacées tiennent moins large —, mais
     le curseur des réglages fins s'y applique comme aux autres styles. */
  font-size: calc(clamp(24px, 4.4vw, 58px) * var(--titres-echelle, 1)); line-height: 1.15;
}

/* Italique : la vraie italique des fontes hébergées ; avec la fonte du site,
   qui n'en a pas, le navigateur penche les lettres. */
[data-titres="italique"] .display,
[data-titres="italique"] .display-sm,
[data-titres="italique"] .cover-title { font-style: italic; font-weight: 400; letter-spacing: -.01em; }

/* Géant : un tiers plus grand, et plus serré pour tenir la ligne. Il se
   multiplie avec le curseur des réglages fins. */
[data-titres="geant"] { --titres-geant: 1.3; }
[data-titres="geant"] .display,
[data-titres="geant"] .display-sm,
[data-titres="geant"] .cover-title { font-weight: 300; letter-spacing: -.045em; line-height: .9; }

/* ── Le grain argentique ─────────────────────────────────────────────────
   Un bruit en SVG, tramé par le navigateur : aucune image à télécharger, et
   il tient dans une ligne. Posé au-dessus de tout, mais transparent aux
   clics — on ne veut pas qu'il avale un bouton.

   « mix-blend-mode: overlay » plutôt qu'une simple opacité : le grain doit
   s'assombrir dans les hautes lumières et disparaître dans les noirs, comme
   un vrai grain. Une couche uniforme voilerait l'image. */
[data-grain="1"]::after {
  content: '';
  position: fixed; inset: 0; z-index: 9;
  pointer-events: none; opacity: .22;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)' opacity='.5'/%3E%3C/svg%3E");
}

/* Tout mouvement s'efface pour qui a demandé à son système de le réduire. */
@media (prefers-reduced-motion: reduce) {
  [data-cover_motion="zoom"] .cover-bg { animation: none; }
  [data-cover_motion="parallaxe"] .cover-bg { background-attachment: scroll; }
  [data-apparition] .cell { animation-duration: 1ms; }
}

/* ── Les quatre fontes hébergées ─────────────────────────────────────────
   Cormorant Garamond, Playfair Display, Jost et Parisienne (licence libre
   OFL, voir fonts/LICENCES.txt), en latin et latin étendu seulement : le
   français et l’anglais n’en demandent pas plus. Le navigateur ne télécharge
   une fonte que si la page l’affiche — une galerie qui ne les choisit pas ne
   paie rien. « swap » : le texte paraît tout de suite, dans la fonte de
   repli, puis prend la sienne. */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/cormorant-italique-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/cormorant-italique-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/cormorant-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/cormorant-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Jost';
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/jost-italique-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Jost';
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/jost-italique-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/jost-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/jost-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Parisienne';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/parisienne-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Parisienne';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/parisienne-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Playfair Display';
  font-style: italic;
  font-weight: 400 900;
  font-display: swap;
  src: url('../fonts/playfair-italique-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Playfair Display';
  font-style: italic;
  font-weight: 400 900;
  font-display: swap;
  src: url('../fonts/playfair-italique-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('../fonts/playfair-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('../fonts/playfair-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ── La fonte ────────────────────────────────────────────────────────────
   Le levier qui change le plus l'allure — avant la couleur. Trois des quatre
   galeries qui ont servi de repère emploient une fonte à empattements.

   Aucun fichier n'est ajouté : « --font-serif » est la fonte de livre du
   système du lecteur. Voir tokens.css pour ce que ce choix coûte et rapporte. */
[data-police="mixte"] .display,
[data-police="mixte"] .display-sm,
[data-police="mixte"] .gal-titre-nom,
[data-police="mixte"] .cover-title,
[data-police="mixte"] .chapitre,
[data-police="mixte"] .diapo-titre,
[data-police="mixte"] .card-title { font-family: var(--font-serif); }

[data-police="serif"] { font-family: var(--font-serif); }
[data-police="serif"] .display,
[data-police="serif"] .display-sm,
[data-police="serif"] .gal-titre-nom,
[data-police="serif"] .cover-title,
[data-police="serif"] .chapitre,
[data-police="serif"] .diapo-titre { font-family: var(--font-serif); }
/* Les kickers et compteurs restent à chasse fixe : c'est la grammaire du
   site, et une serif en capitales espacées de 16 % n'est plus lisible. */
[data-police="serif"] .kicker,
[data-police="serif"] .mono,
[data-police="serif"] [class*="-time"] { font-family: var(--font-mono); }

/* Une serif a un œil plus petit : sans cette reprise, un titre en Georgia
   paraît deux crans plus petit qu'en Space Grotesk à taille égale. */
[data-police="mixte"] .cover-title,
[data-police="serif"] .cover-title,
[data-police="mixte"] .display,
[data-police="serif"] .display { letter-spacing: -.015em; }

/* Les quatre fontes hébergées. Elles visent la même liste que « Titres à
   empattements » : les titres, jamais les boutons ni la navigation. */
[data-police="cormorant"]  { --font-titres: 'Cormorant Garamond', var(--font-serif); --titres-police: 1.12; }
[data-police="playfair"]   { --font-titres: 'Playfair Display', var(--font-serif); }
[data-police="parisienne"] { --font-titres: 'Parisienne', 'Snell Roundhand', 'Apple Chancery', cursive; --titres-police: 1.18; }
[data-police="jost"]       { --font-titres: 'Jost', var(--font-body); font-family: 'Jost', var(--font-body); }

:is([data-police="cormorant"], [data-police="playfair"], [data-police="parisienne"], [data-police="jost"])
  :is(.display, .display-sm, .cover-title, .gal-titre-nom, .chapitre, .diapo-titre, .card-title) { font-family: var(--font-titres); }

/* Tout en Jost, sauf ce qui est à chasse fixe par principe — même règle que
   « Tout à empattements ». */
[data-police="jost"] .kicker,
[data-police="jost"] .mono,
[data-police="jost"] [class*="-time"] { font-family: var(--font-mono); }

/* Une écriture liée ne supporte ni l'interlettrage serré des titres fins ni
   les graisses : les lettres se chevaucheraient. */
[data-police="parisienne"] :is(.display, .display-sm, .cover-title, .gal-titre-nom, .chapitre, .diapo-titre, .card-title) {
  letter-spacing: 0; font-weight: 400; text-transform: none; line-height: 1.1;
}
[data-police="cormorant"] :is(.display, .cover-title),
[data-police="playfair"]  :is(.display, .cover-title) { letter-spacing: -.01em; }

/* ── Le fond ─────────────────────────────────────────────────────────────
   Aucune des galeries de référence n'emploie de noir pur ni de blanc pur.
   Une teinte donne un ton avant qu'on ait regardé une seule photo.

   Les jetons redéfinis sont ceux de la surface — le reste de la feuille les
   lit, rien d'autre n'a besoin de savoir qu'un fond a changé. */
/* Le jeu de jetons est repris EN ENTIER, pas seulement le fond et le texte.
   Une teinte qui ne redéfinirait que « --bg » laisserait « --btn-bg » à sa
   valeur de surface sombre : un bouton blanc sur du crème, illisible. C'est
   exactement le défaut que j'ai obtenu en n'en changeant que la moitié.

   Les surfaces claires reprennent donc aussi --bg-3, --fg-4, --hairline et
   les deux jetons de bouton ; les sombres également, avec les états
   éclaircis — un jeton qui ment sur une surface piège la page suivante. */

/* ── Les trois teintes claires ───────────────────────────────────────────── */
[data-fond="papier"],
[data-fond="creme"],
[data-fond="sable"],
[data-fond="sauge"],
[data-fond="brume"],
[data-fond="rose"] {
  color-scheme: light;
  --btn-fg: var(--bg); --btn-bg: var(--fg);
  /* --sur-media reste BLANC, même sur une surface claire : il ne désigne pas
     le texte de la page mais ce qui se pose SUR une photographie — un cœur de
     favori, une pastille de durée, un bouton de lecture, tous sur un voile
     sombre incrusté dans l'image. Le faire suivre la page rendait ces
     éléments-là invisibles. Ce qui appartient au décor de la visionneuse,
     lui, emploie --fg. */
  --sur-media: #ffffff;
}
[data-fond="papier"] {
  --bg: #fbfaf8; --bg-2: #f4f2ef; --bg-3: #eae7e2;
  --fg: #1c1a17; --fg-2: rgba(28,26,23,.62); --fg-3: rgba(28,26,23,.38); --fg-4: rgba(28,26,23,.2);
  --line: rgba(28,26,23,.12); --hairline: rgba(28,26,23,.1); --hover: rgba(28,26,23,.045);
}
[data-fond="creme"] {
  --bg: #f7f3ea; --bg-2: #f0eade; --bg-3: #e6ded0;
  --fg: #2a241b; --fg-2: rgba(42,36,27,.62); --fg-3: rgba(42,36,27,.38); --fg-4: rgba(42,36,27,.2);
  --line: rgba(42,36,27,.13); --hairline: rgba(42,36,27,.11); --hover: rgba(42,36,27,.05);
}
[data-fond="sable"] {
  --bg: #eae5db; --bg-2: #e1dbcf; --bg-3: #d5cec0;
  --fg: #4a3a28; --fg-2: rgba(74,58,40,.66); --fg-3: rgba(74,58,40,.4); --fg-4: rgba(74,58,40,.22);
  --line: rgba(74,58,40,.15); --hairline: rgba(74,58,40,.13); --hover: rgba(74,58,40,.06);
}

/* ── Les trois teintes sombres ───────────────────────────────────────────── */
[data-fond="encre"],
[data-fond="cacao"],
[data-fond="nuit"],
[data-fond="olive"],
[data-fond="ardoise"] {
  color-scheme: dark;
  --btn-fg: var(--bg); --btn-bg: var(--fg);
  --sur-media: #ffffff;
  --etat-demande: #7fa8c4; --etat-devis: #d9a34a; --etat-confirme: #6fbf9a;
  --etat-realise: #a394bd; --etat-livre: #9aa8a2; --etat-perdu: #e08a72;
}
[data-fond="encre"] {
  --bg: #10151c; --bg-2: #161c25; --bg-3: #1e2530;
  --fg: rgba(238,242,248,.94); --fg-2: rgba(238,242,248,.56); --fg-3: rgba(238,242,248,.32); --fg-4: rgba(238,242,248,.16);
  --line: rgba(238,242,248,.12); --hairline: rgba(238,242,248,.1); --hover: rgba(238,242,248,.05);
}
[data-fond="cacao"] {
  --bg: #241a19; --bg-2: #2c211f; --bg-3: #372a27;
  --fg: #f0e7de; --fg-2: rgba(240,231,222,.6); --fg-3: rgba(240,231,222,.34); --fg-4: rgba(240,231,222,.17);
  --line: rgba(240,231,222,.13); --hairline: rgba(240,231,222,.11); --hover: rgba(240,231,222,.06);
}
[data-fond="nuit"] {
  --bg: #14110f; --bg-2: #1b1714; --bg-3: #241f1b;
  --fg: rgba(246,241,235,.93); --fg-2: rgba(246,241,235,.55); --fg-3: rgba(246,241,235,.32); --fg-4: rgba(246,241,235,.16);
  --line: rgba(246,241,235,.12); --hairline: rgba(246,241,235,.1); --hover: rgba(246,241,235,.06);
}

/* ── Cinq teintes de plus : trois claires, deux sombres ─────────────────── */
[data-fond="sauge"] {
  --bg: #eef1ea; --bg-2: #e4e9df; --bg-3: #d6ddcf;
  --fg: #25302a; --fg-2: rgba(37,48,42,.72); --fg-3: rgba(37,48,42,.46); --fg-4: rgba(37,48,42,.2);
  --line: rgba(37,48,42,.13); --hairline: rgba(37,48,42,.11); --hover: rgba(37,48,42,.05);
}
[data-fond="brume"] {
  --bg: #eef1f4; --bg-2: #e4e8ed; --bg-3: #d7dde4;
  --fg: #1f2a35; --fg-2: rgba(31,42,53,.72); --fg-3: rgba(31,42,53,.46); --fg-4: rgba(31,42,53,.2);
  --line: rgba(31,42,53,.13); --hairline: rgba(31,42,53,.11); --hover: rgba(31,42,53,.05);
}
[data-fond="rose"] {
  --bg: #f6ece9; --bg-2: #efe0dc; --bg-3: #e4d1cc;
  --fg: #3b2724; --fg-2: rgba(59,39,36,.72); --fg-3: rgba(59,39,36,.46); --fg-4: rgba(59,39,36,.2);
  --line: rgba(59,39,36,.13); --hairline: rgba(59,39,36,.11); --hover: rgba(59,39,36,.05);
}
[data-fond="olive"] {
  --bg: #1c1e15; --bg-2: #24271c; --bg-3: #2e3225;
  --fg: rgba(240,238,224,.93); --fg-2: rgba(240,238,224,.56); --fg-3: rgba(240,238,224,.32); --fg-4: rgba(240,238,224,.16);
  --line: rgba(240,238,224,.12); --hairline: rgba(240,238,224,.1); --hover: rgba(240,238,224,.06);
}
[data-fond="ardoise"] {
  --bg: #1a2025; --bg-2: #21282e; --bg-3: #2a3239;
  --fg: rgba(234,240,244,.93); --fg-2: rgba(234,240,244,.56); --fg-3: rgba(234,240,244,.32); --fg-4: rgba(234,240,244,.16);
  --line: rgba(234,240,244,.12); --hairline: rgba(234,240,244,.1); --hover: rgba(234,240,244,.06);
}

/* ── Le fond sur mesure ────────────────────────────────────────────────────
   Le serveur pose --bg (la couleur choisie), --fg (une encre claire ou
   foncée selon sa luminance) et color-scheme sur le corps. Tout le reste en
   découle ici : une teinte libre ne peut pas avoir de palette écrite à la
   main, elle est donc dérivée. */
[data-fond="libre"] {
  --btn-fg: var(--bg); --btn-bg: var(--fg);
  --sur-media: #ffffff;
  --bg-2: color-mix(in srgb, var(--fg) 6%, var(--bg));
  --bg-3: color-mix(in srgb, var(--fg) 12%, var(--bg));
  /* Plus appuyé que sur les fonds écrits à la main : une couleur libre peut
     être un ton moyen, où l'encre pleine plafonne vers 5 de contraste. */
  --fg-2: color-mix(in srgb, var(--fg) 80%, transparent);
  --fg-3: color-mix(in srgb, var(--fg) 56%, transparent);
  --fg-4: color-mix(in srgb, var(--fg) 24%, transparent);
  --line: color-mix(in srgb, var(--fg) 13%, transparent);
  --hairline: color-mix(in srgb, var(--fg) 11%, transparent);
  --hover: color-mix(in srgb, var(--fg) 5%, transparent);
}
/* Sans color-mix (Safari avant 16.2) : des nuances plus franches, mais
   lisibles — le texte secondaire prend l'encre pleine. */
@supports not (color: color-mix(in srgb, red 50%, transparent)) {
  [data-fond="libre"] {
    --bg-2: var(--bg); --bg-3: var(--bg);
    --fg-2: var(--fg); --fg-3: var(--fg); --fg-4: var(--fg);
    --line: var(--fg); --hairline: var(--fg); --hover: transparent;
  }
}

/* Le corps porte la teinte : sans cette ligne, le fond du navigateur apparaît
   derrière, et l'on voit une bande étrangère au rebond de défilement. */
[data-fond]:not([data-fond="defaut"]) { background: var(--bg); color: var(--fg); }

/* La barre flottante suit la teinte plutôt que la classe de surface : c'est
   le fond qui décide, et il peut être clair sur une page dite « sombre ». */
[data-fond]:not([data-fond="defaut"]) .gal-top {
  background: color-mix(in srgb, var(--bg) 88%, transparent);
}

/* ═══════════════════════════════════════════════════════════════════════════
   TROIS COUVERTURES QUI POSENT LA PHOTO

   Les trois premières mises en page déploient l'image sur tout l'écran ; ces
   trois-là la posent au milieu du vide. C'est ce que font les galeries qui
   servent de repère, et la différence n'est pas décorative : une image plein
   cadre est un décor, une image posée est une photographie qu'on regarde.

   Le vide autour n'est donc pas de la place perdue — c'est lui qui fait
   regarder. Elles emploient le fond de la page, pas un voile sur l'image :
   c'est le réglage « fond » qui donne le ton.
   ═══════════════════════════════════════════════════════════════════════════ */

.cover--carte,
.cover--arche,
.cover--bandeau {
  background: var(--bg);
  justify-content: center;
}
/* L'image de fond et son voile n'ont plus lieu d'être : la photo est un
   élément posé, pas une toile. */
.cover--carte .cover-bg, .cover--carte .cover-scrim,
.cover--arche .cover-bg, .cover--arche .cover-scrim,
.cover--bandeau .cover-scrim { display: none; }

/* Les textes reprennent la couleur de la page — sur un fond crème, du blanc
   ne se lit pas. */
.cover--carte .cover-title, .cover--arche .cover-title, .cover--bandeau .cover-title,
.cover--carte .cover-brand, .cover--arche .cover-brand,
.cover--carte .cover-welcome, .cover--arche .cover-welcome, .cover--bandeau .cover-welcome { color: var(--fg); }
.cover--carte .cover-form .field-input,
.cover--arche .cover-form .field-input,
.cover--bandeau .cover-form .field-input { color: var(--fg); }
.cover--carte .cover-kicker, .cover--arche .cover-kicker, .cover--bandeau .cover-kicker,
.cover--carte .cover-site, .cover--arche .cover-site,
.cover--carte .cover-foot, .cover--arche .cover-foot, .cover--bandeau .cover-foot { color: var(--fg-2); }

/* ── Carte : une photo posée au milieu du vide ───────────────────────────── */
.cover--carte .cover-inner {
  display: grid; justify-items: center; text-align: center;
  gap: clamp(26px, 5vh, 60px);
  padding-top: clamp(24px, 6vh, 70px);
  padding-bottom: clamp(24px, 6vh, 70px);
}
/* La hauteur que la photo posée peut prendre : l'écran, moins ce qui
   l'entoure (barre, titre, formulaire, pied — environ 470 px). Sans cette
   borne, une photo réglée sur la largeur débordait d'un écran de portable ;
   la page ne défilant pas, le haut de la photo restait hors de portée.
   Chaque style en tire sa largeur maximale selon son format. */
.cover { --photo-posee-h: max(180px, 100svh - 470px); }
.cover--carte .cover-photo {
  width: min(560px, 78%, var(--photo-posee-h) * 4 / 3); aspect-ratio: 4 / 3;
  background-size: cover; background-position: center;
  border-radius: var(--vignette-rayon, 2px);
  animation: fadeIn 900ms 200ms var(--ease-out) both;
}

/* ── Arche : la photo sous une voûte ─────────────────────────────────────── */
.cover--arche .cover-inner {
  display: grid; justify-items: center; text-align: center;
  gap: clamp(24px, 4vh, 48px);
}
.cover--arche .cover-photo {
  width: min(420px, 68%, var(--photo-posee-h) * 3 / 4); aspect-ratio: 3 / 4;
  background-size: cover; background-position: center;
  /* Une voûte plein cintre : le haut est un demi-cercle de la largeur, le bas
     reste droit. Deux valeurs suffisent, aucune image de masque n'est
     nécessaire. */
  border-radius: 50% 50% var(--vignette-rayon, 2px) var(--vignette-rayon, 2px) / 42% 42% 2% 2%;
  animation: fadeIn 900ms 200ms var(--ease-out) both;
}

/* ── Bandeau : un aplat de couleur, la photo dessous ─────────────────────
   C'est la couverture de Ransom : la couleur occupe le haut de l'écran, le
   titre est dedans, et la photo commence au pli. On descend pour la voir. */
.cover--bandeau { justify-content: flex-start; }
.cover--bandeau .cover-aplat {
  background: var(--accent, var(--bg-2));
  display: grid; justify-items: center; align-content: center;
  min-height: 62svh; padding: clamp(30px, 8vh, 90px) 20px;
  text-align: center;
}
/* Le texte doit se lire sur l'aplat quelle que soit sa teinte : on le prend
   au fond de la page, qui lui est accordé. */
.cover--bandeau .cover-aplat .cover-title,
.cover--bandeau .cover-aplat .cover-kicker { color: var(--sur-aplat, var(--bg)); }
.cover--bandeau .cover-photo {
  flex: 1 1 auto; min-height: 38svh;
  background-size: cover; background-position: center;
}
.cover--bandeau .cover-inner { padding-top: clamp(20px, 4vh, 40px); }

@media (max-width: 640px) {
  .cover--carte .cover-photo { width: min(84%, var(--photo-posee-h) * 4 / 3); }
  .cover--arche .cover-photo { width: min(78%, var(--photo-posee-h) * 3 / 4); }
}

/* ── Quatre couvertures de plus ──────────────────────────────────────────
   Mosaïque, tirage et fenêtre posent la photo sur le fond de la page, comme
   la carte ; typographique n'en a pas. Leurs règles sont écrites à part
   plutôt qu'ajoutées aux listes des trois premières : une liste qu'on
   allonge au milieu est une liste qu'on casse. */
.cover--mosaique, .cover--tirage, .cover--fenetre { background: var(--bg); justify-content: center; }
:is(.cover--mosaique, .cover--tirage, .cover--fenetre, .cover--typographique) .cover-scrim { display: none; }
:is(.cover--mosaique, .cover--tirage, .cover--fenetre) :is(.cover-title, .cover-brand, .cover-welcome) { color: var(--fg); }
:is(.cover--mosaique, .cover--tirage, .cover--fenetre) .cover-form .field-input { color: var(--fg); }
:is(.cover--mosaique, .cover--tirage, .cover--fenetre) :is(.cover-kicker, .cover-site, .cover-foot) { color: var(--fg-2); }

/* Mosaïque : la couverture en grand à gauche, deux photos empilées à droite. */
.cover--mosaique .cover-inner {
  display: grid; justify-items: center; text-align: center;
  gap: clamp(22px, 4vh, 44px); padding-block: clamp(24px, 5vh, 60px);
}
.cover-mosaique {
  display: grid; grid-template-columns: 1.4fr 1fr; grid-template-rows: 1fr 1fr;
  gap: clamp(6px, 1vw, 12px); width: min(760px, 92%, var(--photo-posee-h) * 16 / 10); aspect-ratio: 16 / 10;
}
.cover-mosaique .cover-photo {
  background-size: cover; background-position: center;
  border-radius: var(--vignette-rayon, 2px);
  animation: fadeIn 900ms 200ms var(--ease-out) both;
}
.cover-mosaique .cover-photo:first-child { grid-row: 1 / span 2; }
.cover-mosaique .cover-photo:nth-child(2) { animation-delay: 320ms; }
.cover-mosaique .cover-photo:nth-child(3) { animation-delay: 440ms; }
/* Moins de trois photos dans l'album : la mise en page se resserre. */
.cover-mosaique .cover-photo:only-child { grid-column: 1 / -1; }
.cover-mosaique .cover-photo:nth-child(2):last-child { grid-row: 1 / span 2; }

/* Typographique : le titre seul, sur la couleur d'accent — ou sur le fond
   de la page quand la galerie n'en a pas. */
.cover--typographique { background: var(--accent, var(--bg)); justify-content: center; }
.cover--typographique .cover-inner { display: grid; justify-items: center; text-align: center; }
.cover--typographique .cover-title {
  color: var(--sur-accent, var(--fg)); max-width: 16ch;
  font-size: calc(clamp(54px, 11vw, 168px) * var(--titres-echelle, 1) * var(--titres-geant, 1) * var(--titres-police, 1));
}
.cover--typographique :is(.cover-kicker, .cover-welcome, .cover-site, .cover-foot, .cover-brand) { color: var(--sur-accent, var(--fg-2)); }
.cover--typographique .cover-form .field-input { color: var(--sur-accent, var(--fg)); }

/* Tirage : un tirage posé, dans son passe-partout, un rien de travers. */
.cover--tirage .cover-inner {
  display: grid; justify-items: center; text-align: center;
  gap: clamp(22px, 4vh, 44px); padding-block: clamp(24px, 6vh, 70px);
}
.cover--tirage .cover-photo {
  /* Son titre est plus petit : la photo reprend les 30 px gagnés. */
  width: min(420px, 72%, (var(--photo-posee-h) + 30px) * 4 / 5); aspect-ratio: 4 / 5;
  background-size: cover; background-position: center;
  border: clamp(12px, 1.8vw, 22px) solid #fbfaf8;
  border-bottom-width: clamp(34px, 5vw, 60px);
  box-shadow: 0 2px 6px rgba(0,0,0,.18), 0 30px 60px -30px rgba(0,0,0,.6);
  rotate: -1.2deg;
  animation: fadeIn 900ms 200ms var(--ease-out) both;
}
.cover--tirage .cover-title {
  font-size: calc(clamp(30px, 4.6vw, 60px) * var(--titres-echelle, 1) * var(--titres-geant, 1) * var(--titres-police, 1));
}

/* Fenêtre : une bande de photo à gauche, le texte à droite, centré sur la
   hauteur. Les lignes auto portent le texte ; les deux 1fr, en haut et en
   bas, prennent la place que la photo demande — sans quoi le texte se
   répartirait sur toute sa hauteur. */
.cover--fenetre .cover-inner {
  display: grid;
  grid-template-columns: min(28vw, 360px) minmax(0, 1fr);
  grid-template-rows: 1fr repeat(6, auto) 1fr;
  column-gap: clamp(28px, 5vw, 80px);
  min-height: 72svh; text-align: left; align-items: start;
}
.cover--fenetre .cover-inner > * { grid-column: 2; }
.cover--fenetre .cover-inner > .cover-kicker { grid-row-start: 2; }
.cover--fenetre .cover-photo {
  grid-column: 1; grid-row: 1 / -1; align-self: stretch; min-height: 60svh;
  background-size: cover; background-position: center;
  border-radius: var(--vignette-rayon, 2px);
  animation: fadeIn 900ms 200ms var(--ease-out) both;
}
@media (max-width: 760px) {
  .cover--fenetre .cover-inner { display: block; min-height: 0; }
  .cover--fenetre .cover-photo { min-height: 0; height: 34svh; margin-bottom: 22px; }
  .cover-mosaique { width: min(100%, var(--photo-posee-h) * 4 / 5); aspect-ratio: 4 / 5; grid-template-columns: 1fr 1fr; grid-template-rows: 1.6fr 1fr; }
  .cover-mosaique .cover-photo:first-child { grid-row: auto; grid-column: 1 / -1; }
  .cover--tirage .cover-photo { width: min(78%, (var(--photo-posee-h) + 30px) * 4 / 5); }
}

/* Les quatre couvertures à photo posée tiennent dans l'écran : le titre
   perd ses marges (l'écart de la grille les remplace) et les espacements se
   resserrent, pour que la photo garde la place. Écrit après leurs règles
   propres, et à la même force : c'est l'ordre qui tranche. */
:is(.cover--carte, .cover--arche, .cover--mosaique, .cover--tirage) .cover-inner {
  gap: clamp(14px, 2.6vh, 34px); padding-block: clamp(8px, 2.4vh, 36px);
}
:is(.cover--carte, .cover--arche, .cover--mosaique, .cover--tirage) .cover-title { margin: 0; }

/* ── Le logo ─────────────────────────────────────────────────────────────
   Il prend la place du nom là où la marque paraît. Les hauteurs sont fixées
   par le contexte, pas par le fichier : un logo déposé en 1200 px de large
   ne doit pas décider de la hauteur d'une barre. */
.marque-logo { display: block; width: auto; object-fit: contain; }
.cover-brand .marque-logo { height: clamp(26px, 3.4vw, 40px); }
.gal-brand   .marque-logo { height: clamp(20px, 2.4vw, 28px); }
.diapo-logo  { height: clamp(22px, 3vw, 34px); width: auto; object-fit: contain; margin-bottom: 6px; }

/* Dans la barre, le titre passe derrière le logo, en second : c'est la
   marque qu'on lit d'abord. */
.gal-brand:has(.marque-logo) { display: flex; align-items: center; gap: var(--space-3); }

/* ── Le mot de la fin ────────────────────────────────────────────────────
   Après la grille, avant le pied de page. Il respire : c'est un moment, pas
   une bannière — et une bannière serrée entre deux blocs se lit comme de la
   publicité, ce qu'il ne doit surtout pas devenir. */
.mot-fin {
  display: grid; gap: clamp(20px, 3vw, 36px);
  grid-template-columns: minmax(0, 1fr);
  justify-items: center; text-align: center;
  /* Une marge latérale explicite : .wrap n'en donne pas sous sa largeur
     maximale, et le texte venait toucher les deux bords de l'écran. */
  padding: clamp(56px, 10vh, 120px) clamp(18px, 4vw, 32px) clamp(30px, 5vh, 60px);
  margin-top: clamp(30px, 6vh, 70px);
  border-top: 1px solid var(--line);
}
.mot-fin-portrait {
  width: clamp(96px, 14vw, 136px); aspect-ratio: 1;
  object-fit: cover; border-radius: 50%;
}
.mot-fin-texte { max-width: 56ch; display: grid; gap: var(--space-4); justify-items: center; }
.mot-fin-titre {
  margin: 0; font-family: var(--font-display); font-weight: 300;
  font-size: clamp(24px, 3.4vw, 38px); letter-spacing: -.025em; color: var(--fg);
}
.mot-fin-mot { margin: 0; color: var(--fg-2); line-height: 1.65; }
.mot-fin-actions { display: flex; flex-wrap: wrap; gap: var(--space-3); justify-content: center; margin-top: 8px; }

/* Au-delà de 760 px, le portrait passe à gauche et le texte s'aligne : une
   colonne centrée sur toute la largeur d'un écran se lit mal. */
@media (min-width: 760px) {
  .mot-fin {
    grid-template-columns: auto minmax(0, 1fr);
    justify-items: start; text-align: left; align-items: center;
  }
  .mot-fin-texte { justify-items: start; }
  .mot-fin-actions { justify-content: flex-start; }
  .mot-fin:not(:has(.mot-fin-portrait)) { grid-template-columns: minmax(0, 1fr); }
}

/* ── Le mot d'un moment ──────────────────────────────────────────────────
   Posé DANS le titre plutôt qu'à côté : la grille justifiée et la mosaïque
   placent le titre comme un bloc unique, et un second élément fausserait
   leur calcul de hauteur. Le ::after du titre passe donc après le mot. */
.chapitre-mot {
  display: block; max-width: 56ch;
  margin-top: 12px;
  font-family: var(--font-body); font-weight: 400;
  font-size: clamp(14px, 1.3vw, 16px); line-height: 1.6;
  letter-spacing: 0; text-transform: none;
  color: var(--fg-2);
}

/* ═══════════════════════════════════════════════════════════════════════════
   LA COULEUR D'ACCENT, POUR DE BON

   Elle ne servait qu'à huit endroits, tous discrets : un filet sous un titre,
   un soulignement de menu, le cœur d'un favori. On pouvait la changer du vert
   au brun sans rien voir bouger — ce qui donnait l'impression, à juste titre,
   que l'aperçu ne suivait pas.

   Elle touche maintenant ce qu'on regarde : les boutons pleins, la pastille de
   filtre active, les liens, l'anneau de mise au point. Elle n'est pas posée sur
   les FONDS de la page — une galerie n'est pas une charte graphique, et une
   photographie se regarde sur du neutre.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Les jetons de bouton suivent l'accent, quand il y en a un. Le repli garde le
   comportement d'avant : sans accent choisi, rien ne change. */
[style*="--accent"] {
  --btn-bg: var(--accent);
  /* L'encre du bouton se calcule à partir de l'accent : du blanc sur un ambre
     clair ne se lit pas. color-mix rend un noir ou un blanc teinté selon la
     luminance, ce qu'aucune valeur figée ne saurait faire. */
  --btn-fg: var(--sur-accent, #fff);
}

.gal-folder.is-active {
  background: var(--accent);
  color: var(--sur-accent, #fff);
  border-color: transparent;
}

.gal-action:hover { color: var(--accent); }
.cell-fav:hover { color: var(--accent); }

/* L'anneau de mise au point : c'est lui qu'on suit au clavier, il a tout
   intérêt à être de la couleur de la galerie. */
:where(.gal-action, .gal-folder, .gal-tag, .btn, .cell-open):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── Un film livré au téléchargement ─────────────────────────────────────
   Master en pleine définition : aucun navigateur ne le lirait, et on ne
   voudrait pas le lui faire charger. On dit ce qu'il est et comment
   l'obtenir, au lieu d'un cadre noir qui promet une lecture qui n'aura
   pas lieu. */
.film-fichier {
  display: grid; justify-items: center; align-content: center; gap: var(--space-3);
  min-height: 46vh; padding: clamp(24px, 5vw, 56px); text-align: center;
}
.film-fichier-nom {
  margin: 0; font-family: var(--font-display); font-weight: 300;
  font-size: clamp(20px, 3vw, 30px); letter-spacing: -.02em;
  color: var(--fg); overflow-wrap: anywhere; max-width: 24ch;
}
.film-fichier-meta {
  margin: 0; font-size: 12px; letter-spacing: .08em; color: var(--fg-2);
}
.film-fichier .btn { margin-top: 6px; }

.livre-intro { color: var(--fg-2); font-size: var(--text-sm); line-height: 1.75; max-width: 56ch; margin-bottom: clamp(28px, 5vh, 44px); }
.livre-merci {
  margin: 0 0 28px; padding: 12px 16px;
  border-left: 2px solid var(--fg-3);
  font-size: var(--text-sm); color: var(--fg);
}
.livre-form { display: grid; gap: var(--space-1); margin-bottom: clamp(44px, 8vh, 72px); }
.livre-form .cta { justify-self: start; margin-top: 6px; }
/* Hors de l'écran plutôt que display:none : certains robots sautent les champs
   masqués, pas ceux qu'on a simplement poussés hors de la vue. */
.livre-piege { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
.livre-mots { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--hairline); }
.livre-mot { padding: 22px 0; border-bottom: 1px solid var(--hairline); }
.livre-texte {
  font-family: var(--font-serif); font-size: clamp(17px, 1.8vw, 20px);
  line-height: 1.6; color: var(--fg); overflow-wrap: anywhere;
}
.livre-signe { margin-top: 10px; font-family: var(--font-mono); font-size: 11px; letter-spacing: .08em; color: var(--fg-2); }
.livre-vide { color: var(--fg-2); font-size: var(--text-sm); }

/* ═══════════════════════════════════════════════════════════════════════════
   LE MODE FILM

   Une galerie qui porte un film ne se présente plus comme une galerie de
   photos avec une vidéo dedans : la couverture joue le teaser, l'album
   s'ouvre sur une bande sombre où le film se lit sur place, et le lecteur
   prend l'accent de la galerie. Écrit en fin de fichier, à part : aucune
   liste de sélecteurs existante n'est allongée.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── La couverture « Film » ─────────────────────────────────────────────── */
.cover--film {
  --fg:   rgba(255, 255, 255, .92);
  --fg-2: rgba(255, 255, 255, .64);
  --fg-3: rgba(255, 255, 255, .44);
  --sur-media: #ffffff;
  background: #050607;
}
/* Le teaser, muet, en boucle, par-dessus la photo : si la vidéo tarde ou si
   le navigateur refuse de la lancer, la photo est déjà là. */
.cover-film {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%; object-fit: cover;
  animation: fadeIn 1400ms 150ms var(--ease-out) both;
}
.cover--film .cover-scrim {
  z-index: 1;
  background: linear-gradient(to bottom, rgba(5, 6, 7, .55) 0%, rgba(5, 6, 7, .12) 42%, rgba(5, 6, 7, .86) 100%);
}
/* Les bandes du cinémascope, en haut et en bas : ce qui dit « film » avant
   même que l'image bouge. Sous le texte, qui garde le premier plan. */
.cover--film::before,
.cover--film::after {
  content: ""; position: absolute; left: 0; right: 0; z-index: 1;
  height: clamp(16px, 5.5vh, 60px);
  background: #050607; pointer-events: none;
}
.cover--film::before { top: 0; }
.cover--film::after  { bottom: 0; }
.cover-film-etiquette {
  display: inline-flex; align-items: center; gap: var(--space-2); margin: 0 0 14px;
  font-family: var(--font-mono); font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse); text-transform: uppercase; color: var(--fg-2);
  animation: panelIn 620ms 100ms var(--ease-out) both;
}
@media (prefers-reduced-motion: reduce) {
  .cover-film { display: none; }
}

/* La marge latérale de l'album, nommée. « --gutter » était employée par les
   films en tête d'album et par le titre « Les photos » sans être définie
   nulle part : la marge tombait à zéro, et tout collait au bord de l'écran
   sur un téléphone. Même valeur que .wrap. */
:root {
  --gutter: max(clamp(20px, 3.4vw, 48px), var(--sal, 0px));
  /* La largeur du cadre. Elle n'était déclarée nulle part : les deux grilles
     de films vivaient sur la valeur de repli de leur var(), donc sur un
     réglage que rien ne permettait de changer. */
  --wrap-max: 1600px;
}

/* ── La bande cinéma, en tête d'album ──────────────────────────────────────
   Sombre quel que soit le thème : c'est la salle. Ses propres jetons, pour
   que tout ce qui s'y pose — boutons, filets, chapitres, validation du
   montage — se lise sur le noir. */
.cinema {
  --fg:   rgba(255, 255, 255, .92);
  --fg-2: rgba(255, 255, 255, .62);
  --fg-3: rgba(255, 255, 255, .40);
  --line: rgba(255, 255, 255, .14);
  --hairline: rgba(255, 255, 255, .10);
  --hover: rgba(255, 255, 255, .08);
  --btn-bg: #fbfdfb;
  --btn-fg: #0d1113;
  background: #07090a; color: var(--fg);
  margin: 0 0 clamp(28px, 5vh, 56px);
  padding: clamp(18px, 3.5vh, 44px) var(--gutter) clamp(26px, 5vh, 56px);
}
/* Bornée aussi par la HAUTEUR de l'écran : à 1280×800, un film pleine
   largeur mesurait 671 px et repoussait son titre et « Regarder le film »
   sous le pli. À 66 % de la hauteur en 16/9 — la barre de l'album prend
   déjà 137 px au-dessus —, film, titre et bouton se voient ensemble. */
.cinema-inner { max-width: min(1400px, 100%, calc(66svh * 1.7778)); margin: 0 auto; }
.cinema-film { margin: 0; }
.cinema .film-ouvrir,
.vp-sur-place .vp-scene { border-radius: var(--vignette-rayon, var(--radius-md)); }
.cinema-film .film-ouvrir { box-shadow: 0 40px 90px -50px rgba(0, 0, 0, .9); }
/* Un film vertical est borné en hauteur et se centre : à 1280 px de large,
   un 9/16 mesurerait trois écrans. */
.cinema-film.est-vertical .film-ouvrir,
.cinema-film.est-vertical .vp-scene {
  max-width: calc(74vh * var(--ar, .5625)); margin-inline: auto;
}
.cinema-film .film-play {
  width: clamp(64px, 8vw, 92px); height: clamp(64px, 8vw, 92px);
  border: 1px solid rgba(255, 255, 255, .5);
  background: rgba(0, 0, 0, .42);
  background: color-mix(in srgb, var(--accent) 62%, rgba(0, 0, 0, .35));
}

/* Le lecteur sur place : l'image garde le rapport du film et la page son
   rythme — pas de hauteur à 100 %, contrairement à la visionneuse. */
.vp-sur-place { display: block; height: auto; }
.vp-sur-place .vp-scene {
  aspect-ratio: var(--ar, 1.7778); overflow: hidden;
  background: var(--letterbox);
}
.vp-sur-place .vp-sous { padding-top: 8px; }

.cinema-infos {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: end;
  gap: var(--space-4) var(--space-8); margin-top: clamp(18px, 3vh, 32px);
}
.cinema-kicker { color: var(--fg-2); }
.cinema-titre { margin: 10px 0 0; color: #fff; overflow-wrap: anywhere; }
.cinema-actions { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3) var(--space-6); }
.cinema-lancer { display: inline-flex; align-items: center; gap: var(--space-3); }
.cinema-photos { color: var(--fg-2); }

.cinema-chapitres {
  grid-column: 1 / -1; list-style: none;
  margin: 4px 0 0; padding: 16px 0 0;
  border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: var(--space-2);
}
.cinema-chapitres button {
  display: inline-flex; align-items: baseline; gap: var(--space-3);
  min-height: 38px; padding: 7px 15px; border-radius: 999px;
  border: 1px solid var(--line); background: transparent; color: var(--fg);
  font-size: 13px; cursor: pointer;
  transition: background var(--dur-micro) var(--ease-out), border-color var(--dur-micro) var(--ease-out);
}
.cinema-chapitres button:hover { background: var(--hover); border-color: rgba(255, 255, 255, .3); }
.cinema-chapitres button span {
  font-family: var(--font-mono); font-size: 11px;
  color: var(--fg-3); font-variant-numeric: tabular-nums;
}

.cinema-autres {
  display: grid; gap: clamp(14px, 2vw, 22px);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  margin-top: clamp(22px, 4vh, 38px); padding-top: clamp(18px, 3vh, 28px);
  border-top: 1px solid var(--line);
}
.cinema-autres .film-legende { color: var(--fg-2); }
/* Un format vertical, seul sur sa ligne au téléphone, mesurait deux écrans
   de haut : il est borné et se centre, comme le film principal. */
.cinema-autres .film.est-vertical .film-ouvrir,
.cinema-autres .film.est-vertical .vp-scene {
  max-width: calc(62vh * var(--ar, .5625)); margin-inline: auto;
}

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

/* ── Le lecteur habillé par l'apparence ─────────────────────────────────
   L'accent de la galerie colore le grand bouton et le curseur. La barre
   jouée reste claire : elle doit se lire sur n'importe quelle image, et un
   accent sombre y disparaîtrait. */
.vp-play {
  background: rgba(10, 13, 14, .4);
  background: color-mix(in srgb, var(--accent) 62%, rgba(10, 13, 14, .35));
}
.vp-knob { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 70%, transparent); }
.cinema .vp-chapitre { color: var(--fg); }

/* ── L'aperçu au survol d'une vignette de film ──────────────────────────
   Sans z-index : posé juste après le bouton, il passe sous le triangle,
   la durée et le cœur, qui le suivent dans la page. */
.cell-apercu {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  border-radius: inherit; pointer-events: none;
  opacity: 0; transition: opacity var(--dur-ui) var(--ease-out);
}
.cell-apercu.est-pret { opacity: 1; }

/* =============================================================
   Le reportage porte son nom
   -------------------------------------------------------------
   Au-dessus de la grille, et non dans la barre : c'est la
   première chose qu'une cliente doit lire en arrivant sur ses
   photos. Centré, comme la couverture dont cette page est la
   suite, et à une taille qui domine les étiquettes d'action.

   L'échelle suit celle des titres de la galerie (--titres-*) :
   une ambiance « Géante » agrandit celui-ci comme les autres.
   ============================================================= */
.gal-titre {
  text-align: center;
  padding-block: clamp(30px, 5.5vh, 56px) clamp(16px, 2.6vh, 26px);
}
.gal-titre-nom {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: calc(clamp(24px, 3.4vw, 38px) * var(--titres-echelle, 1) * var(--titres-police, 1));
  letter-spacing: -.02em;
  line-height: 1.12;
  text-wrap: balance;
}
.gal-titre-meta {
  margin: 14px 0 0;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: var(--etiq);
  letter-spacing: var(--etiq-chasse);
  text-transform: uppercase;
  color: var(--fg-2);
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
}
.gal-titre-meta .kicker-dot { opacity: .35; }

/* =============================================================
   Le cadre (planche « Galerie cliente · le cadre »)
   Deux marges : le texte respire, les photographies vont plus près
   du bord. Le titre et les moments en serif, aucune capitale
   espacée — elles faisaient « interface », pas « album ». Le même
   pied sur chaque écran.
   ============================================================= */
:root {
  --marge-texte:  max(clamp(20px, 5vw, 72px), var(--sal, 0px));
  --marge-images: max(clamp(12px, 2.4vw, 32px), var(--sal, 0px));
  --encre: var(--fg);
}
/* L'encre gris-bleu des planches, sur les fonds clairs : jamais noire. */
[data-fond="papier"], [data-fond="creme"], [data-fond="brume"] { --encre: #2f3c45; }
.gal-top-inner { padding-inline: var(--marge-texte); max-width: none; }
.gal-signature {
  font-family: var(--font-titres, var(--font-serif)); font-weight: 500;
  font-size: clamp(18px, 1.8vw, 22px); letter-spacing: 0; text-transform: none;
  color: var(--encre); text-decoration: none;
}
.gal-action.is-active { color: var(--fg); border-bottom-color: var(--fg); }
.gal-action .gal-badge { min-width: 0; height: auto; padding: 0; background: none; color: var(--fg-3); font-size: inherit; }
.gal-action .gal-badge.is-full { color: var(--accent); }

.album-une { height: clamp(220px, 44vh, 520px); overflow: hidden; background: var(--bg-2); }
.album-une img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gal-titre { text-align: center; padding-inline: var(--marge-texte); padding-block: clamp(30px, 5vh, 56px) 0; }
.gal-titre-nom {
  margin: 0; font-family: var(--font-titres, var(--font-serif)); font-weight: 400;
  font-size: calc(clamp(34px, 5vw, 56px) * var(--titres-echelle, 1)); line-height: 1.08; letter-spacing: -.005em; color: var(--encre);
  text-wrap: balance;
}
.gal-titre-meta {
  margin: 12px 0 0; font-family: var(--font-body); font-size: 13px; font-weight: 400;
  letter-spacing: 0; text-transform: none; color: var(--fg-2); white-space: pre-wrap;
}
.gal-titre-film {
  display: inline-flex; align-items: center; gap: 8px; margin-top: 18px; padding: 6px 2px 5px;
  font-size: 13px; color: var(--fg); text-decoration: none; border-bottom: 1px solid var(--line);
}
@media (hover: hover) { .gal-titre-film:hover { border-bottom-color: var(--fg); } }
.gal-main > .gal-filters {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 22px;
  position: static; margin: clamp(22px, 3vh, 34px) 0 0; padding-inline: var(--marge-texte);
  background: none; border: 0;
}
.grid { padding-left: calc(var(--marge-images) * var(--grid-marge, 1)); padding-right: calc(var(--marge-images) * var(--grid-marge, 1)); }
.chapitre {
  text-align: center; font-family: var(--font-titres, var(--font-serif)); font-weight: 400;
  font-size: clamp(22px, 2.6vw, 30px); letter-spacing: 0; color: var(--encre);
}
.chapitre::after { display: none; }
.chapitre-mot { display: block; max-width: 52ch; margin: 10px auto 0; font-family: var(--font-body); font-size: 14px; line-height: 1.6; color: var(--fg-2); }

/* Le pied : la signature, la langue et le thème ; dessous, la date de
   fermeture, la prolongation, la sortie. */
.gal-foot.wrap { display: grid; gap: 12px; padding-inline: var(--marge-texte); max-width: none; padding-block: 30px 6px; }
.gal-pied-haut { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.gal-pied-haut .gal-signature { font-size: 18px; }
.gal-pied-reglages { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 8px 18px; color: var(--fg-2); font-size: 13px; }
.gal-pied-bas { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; font-size: 12.5px; color: var(--fg-3); }
.mot-fin { text-align: center; }
.mot-fin { justify-items: center; padding-inline: var(--marge-texte); }
.mot-fin-texte { margin-inline: auto; text-align: center; max-width: 56ch; }
.mot-fin-mot { font-family: var(--font-titres, var(--font-serif)); font-style: italic; font-size: clamp(19px, 2vw, 24px); line-height: 1.45; color: var(--encre); }
.mot-fin-actions { justify-content: center; }

/* =============================================================
   La visionneuse (planche « Galerie cliente · visionneuse »)
   ============================================================= */
.lb-top {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 16px;
  min-height: 64px; padding: max(8px, var(--sat)) var(--marge-texte) 8px;
  border-bottom: 1px solid var(--hairline);
}
.lb-retour {
  justify-self: start; display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 0; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 14px; color: var(--fg-2);
}
.lb-retour svg { fill: none; stroke: currentColor; stroke-width: 1.8; }
@media (hover: hover) { .lb-retour:hover { color: var(--fg); } }
.lb-titre { display: inline-flex; align-items: baseline; gap: 10px; min-width: 0; justify-self: center; }
.lb-moment { font-family: var(--font-titres, var(--font-serif)); font-size: 19px; color: var(--encre); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lb-titre .lb-counter { font-family: var(--font-body); font-size: 12.5px; letter-spacing: 0; color: var(--fg-3); font-variant-numeric: tabular-nums; }
.lb-titre .lb-meta { font-size: 11px; letter-spacing: 0; }
.lb-tools { justify-self: end; gap: 4px; }
.lb-btn.lb-mot {
  width: auto; height: 36px; padding: 0 8px; border-radius: 0; gap: 6px; display: inline-flex; align-items: center;
  font: inherit; font-size: 14px; color: var(--fg-2); border-bottom: 1px solid transparent;
}
.lb-btn.lb-mot svg { width: 13px; height: 13px; }
.lb-btn.lb-mot:hover { background: none; color: var(--fg); }
.lb-btn.lb-mot.is-on { color: var(--fg); border-bottom-color: var(--fg); }
.lb.comments-open [data-act="comment"] { color: var(--fg); border-bottom-color: var(--fg); }

.lb-foot {
  justify-content: space-between; gap: 18px; min-height: 76px;
  padding: 12px var(--marge-texte) max(12px, var(--sab)); border-top: 1px solid var(--hairline);
}
.lb-bande { display: flex; gap: 8px; min-width: 0; overflow-x: auto; scrollbar-width: none; }
.lb-vignette {
  flex: none; width: 64px; height: 44px; padding: 0; border: 0; background: var(--bg-2); cursor: pointer;
  opacity: .62; transition: opacity var(--dur-micro) var(--ease-out);
}
.lb-vignette img { width: 100%; height: 100%; object-fit: cover; display: block; }
.lb-vignette.is-current { opacity: 1; outline: 1px solid var(--fg); outline-offset: 2px; width: 76px; }
@media (hover: hover) { .lb-vignette:hover { opacity: 1; } }
.lb-foot-dit { flex: none; font-size: 12.5px; color: var(--fg-3); }

.lb-comments { background: var(--bg); padding-inline: 24px; }
.lb-comments h3 {
  font-family: var(--font-titres, var(--font-serif)); font-size: 20px; letter-spacing: 0; text-transform: none;
  color: var(--encre); margin: 6px 0 8px; font-weight: 400;
}
.lb-note { margin-bottom: 22px; }
.lb-note-aide { margin: 0 0 12px; font-size: 12.5px; line-height: 1.55; color: var(--fg-2); }
.lb-lien {
  margin-top: 10px; padding: 4px 0; border: 0; border-bottom: 1px solid var(--fg); background: none; cursor: pointer;
  font: inherit; font-size: 13.5px; color: var(--fg);
}
.lb-comment p { font-family: var(--font-titres, var(--font-serif)); font-size: 16px; color: var(--fg); }
.lb-raccourcis { margin: 14px 0 0; font-size: 11.5px; color: var(--fg-3); }
@media (max-width: 699px) {
  .lb-top { grid-template-columns: auto 1fr auto; }
  .lb-titre { justify-self: center; }
  .lb-moment { display: none; }
  .lb-btn.lb-mot .lb-mot-txt { display: none; }
  .lb-btn.lb-mot { width: 40px; justify-content: center; }
  .lb-btn.lb-mot[data-act="comment"]::before { content: '✎'; font-size: 16px; }
  .lb-btn.lb-mot[data-act="download"]::before { content: '↓'; font-size: 17px; }
  .lb-foot-dit, .lb-raccourcis { display: none; }
  .lb-vignette { width: 52px; height: 38px; }
}
.lb-btn.lb-mot { white-space: nowrap; }
/* Le panneau s'ouvre entre la barre et la bande, comme une colonne : la
   barre reste lisible, « Une note » reste là pour le refermer. */
@media (min-width: 700px) {
  .lb-comments { top: 64px; bottom: 76px; padding-top: 20px; padding-bottom: 16px; }
  .lb-comments-close { display: none; }
}

/* =============================================================
   La sélection (planche « Galerie cliente · sélection »)
   ============================================================= */
.sel-tete { text-align: center; padding-inline: var(--marge-texte); padding-block: clamp(40px, 7vh, 64px) clamp(24px, 4vh, 36px); }
.sel-titre {
  margin: 0 0 14px; font-family: var(--font-titres, var(--font-serif)); font-weight: 500;
  font-size: clamp(34px, 4.4vw, 46px); line-height: 1.08; color: var(--encre);
}
.sel-meta { margin: 0 0 20px; font-size: 14px; color: var(--fg-2); }
.sel-mot {
  margin: 0 auto; max-width: 54ch; font-family: var(--font-titres, var(--font-serif));
  font-size: clamp(17px, 1.6vw, 19px); line-height: 1.65; color: var(--fg-2);
}
.sel-tete .sel-lists { justify-content: center; display: flex; flex-wrap: wrap; gap: 6px 22px; margin-top: 22px; }
.sel-barre {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  display: flex; align-items: center; gap: 12px 22px; flex-wrap: wrap;
  padding: 14px var(--marge-texte) max(14px, var(--sab));
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  backdrop-filter: saturate(160%) blur(14px); -webkit-backdrop-filter: saturate(160%) blur(14px);
  border-top: 1px solid var(--hairline);
}
.sel-barre-compte { font-size: 14px; color: var(--fg); }
.sel-barre-compte strong { font-family: var(--font-titres, var(--font-serif)); font-size: 22px; font-weight: 500; margin-right: 4px; }
.sel-barre-mot { flex: 1 1 220px; font-size: 13px; color: var(--fg-3); }
.sel-barre-gestes { display: inline-flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.sel-lien {
  padding: 4px 0; border: 0; border-bottom: 1px solid var(--line); background: none; cursor: pointer;
  font: inherit; font-size: 14px; color: var(--fg-2); text-decoration: none;
}
@media (hover: hover) { .sel-lien:hover { color: var(--fg); border-bottom-color: var(--fg); } }
.sel-valider {
  height: 44px; padding: 0 22px; border: 0; border-radius: 0; cursor: pointer;
  background: var(--btn-bg, var(--fg)); color: var(--btn-fg, var(--bg)); font: inherit; font-size: 14.5px; font-weight: 500;
}
body:has(.sel-barre) .gal-main { padding-bottom: 140px; }
@media (max-width: 699px) {
  .sel-barre-mot { display: none; }
  .sel-barre { justify-content: space-between; }
}

.livre-tete { text-align: center; padding-block: clamp(40px, 7vh, 64px) clamp(30px, 5vh, 44px); }
.livre-titre { margin: 0 0 12px; font-family: var(--font-titres, var(--font-serif)); font-weight: 500; font-size: clamp(32px, 4vw, 42px); line-height: 1.1; color: var(--encre); }
.livre-tete .livre-intro { margin: 0 auto; max-width: 60ch; font-size: 14px; color: var(--fg-2); }
.livre-colonnes { display: flex; gap: 56px; flex-wrap: wrap; align-items: flex-start; max-width: 1040px; margin: 0 auto; }
.livre-ecrire { flex: 1 1 300px; min-width: 0; }
.livre-colonnes > #livre-mots { flex: 999 1 420px; min-width: 0; }
.livre-form .field-label { font-family: var(--font-body); font-size: 12.5px; letter-spacing: 0; text-transform: none; color: var(--fg-2); }
.livre-form .field-input, .livre-form .field-textarea {
  border: 0; border-bottom: 1px solid var(--line); border-radius: 0; background: transparent; padding: 0 0 10px; font-size: 15px;
}
.livre-envoyer {
  padding: 0 0 4px; border: 0; border-bottom: 1px solid var(--fg); background: none; cursor: pointer;
  font: inherit; font-size: 14.5px; color: var(--fg);
}
.livre-mots .livre-texte { font-family: var(--font-titres, var(--font-serif)); font-size: 20px; line-height: 1.55; color: var(--encre); }
.livre-mots .livre-signe { font-size: 12.5px; letter-spacing: 0; text-transform: none; font-family: var(--font-body); color: var(--fg-3); }
.livre-mot { padding-bottom: 26px; margin-bottom: 26px; border-bottom: 1px solid var(--hairline); }
.livre-envoyer { width: auto; display: inline-block; } .livre-tete .kicker-dot { margin: 0 8px; }

/* ---- La sélection : la croix pour retirer, la note sur la photo ---- */
.est-selection .cell-fav {
  top: 10px; right: 10px; bottom: auto; width: 28px; height: 28px;
  opacity: 1; transform: none; background: rgba(255,255,255,.9); color: #2f3c45; backdrop-filter: none;
}
.est-selection .cell-fav svg { display: none; }
.est-selection .cell-fav::before { content: '×'; font-size: 19px; line-height: 1; }
.est-selection .cell-fav:not(.is-on) { background: var(--accent); color: var(--sur-media); }
.est-selection .cell-fav:not(.is-on)::before { content: '+'; }
.cell-note {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; pointer-events: none;
  padding: 22px 12px 9px; font-size: 12.5px; line-height: 1.4; color: #fff;
  background: linear-gradient(to top, rgba(10,13,14,.72), rgba(10,13,14,0));
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cell-note[hidden] { display: none; }
.gal-pied-reglages .lang-btn { font-size: 13px; letter-spacing: 0; text-transform: none; }
/* Avec son mot, la bascule n'est plus une pastille de 32 px : elle prend la
   largeur de « Thème sombre ». */
.gal-pied-reglages .theme-toggle { display: inline-flex; align-items: center; gap: 7px; width: auto; height: auto; min-height: 32px; font-size: 13px; color: var(--fg-2); text-decoration: none; }
.theme-toggle-mot { white-space: nowrap; }

/* ═══ Les panneaux de la galerie ═══════════════════════════════════════════
   Comme les rubriques du site : un voile translucide par-dessus l'album, qu'on
   voit au travers et qu'on ne quitte pas. Il prend les teintes de la galerie —
   papier clair ou nuit, selon son ambiance — au lieu du voile sombre du site,
   qui aurait jeté une autre maison au milieu de l'album.
   Sans script, l'ancre suffit à l'ouvrir (:target). */
.gal-panneau {
  position: fixed; inset: 0; z-index: 80;
  overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
  background: color-mix(in srgb, var(--bg) 84%, transparent);
  backdrop-filter: blur(14px) saturate(115%);
  -webkit-backdrop-filter: blur(14px) saturate(115%);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity var(--dur-enter, .35s) var(--ease-out, ease), visibility 0s linear var(--dur-enter, .35s);
}
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
  .gal-panneau { background: var(--bg); }
}
/* :target ne sert que sans script : history.replaceState ne le met pas à
   jour, et un panneau refermé au clavier restait affiché. */
.gal-panneau.est-ouvert, html:not(.js-panneaux) .gal-panneau:target {
  opacity: 1; visibility: visible; pointer-events: auto;
  transition: opacity var(--dur-enter, .35s) var(--ease-out, ease), visibility 0s;
}
.gal-panneau:focus { outline: none; }
.gal-panneau-corps {
  position: relative;
  padding-top: max(clamp(72px, 12vh, 120px), calc(env(safe-area-inset-top, 0px) + 64px));
  padding-bottom: clamp(60px, 12vh, 120px);
  animation: none;
}
.gal-panneau.est-ouvert .gal-panneau-corps { animation: gal-panneau-entree var(--dur-enter, .35s) var(--ease-out, ease) both; }
@keyframes gal-panneau-entree { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .gal-panneau, .gal-panneau.est-ouvert { transition: none; }
  .gal-panneau.est-ouvert .gal-panneau-corps { animation: none; }
}
.gal-panneau-fermer {
  position: fixed; z-index: 1;
  top: max(18px, calc(env(safe-area-inset-top, 0px) + 12px));
  right: max(18px, calc(env(safe-area-inset-right, 0px) + 12px));
  display: grid; place-items: center; width: 44px; height: 44px; border-radius: 50%;
  font-size: 26px; line-height: 1; text-decoration: none; color: var(--fg);
  background: color-mix(in srgb, var(--fg) 6%, transparent);
}
@media (hover: hover) { .gal-panneau-fermer:hover { background: color-mix(in srgb, var(--fg) 12%, transparent); } }
.gal-panneau-kicker {
  display: flex; justify-content: center; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 14px; font-family: var(--font-mono); font-size: var(--etiq-page, 11px);
  letter-spacing: var(--etiq-chasse, .14em); text-transform: uppercase; color: var(--fg-2);
}
.gal-panneau .livre-tete { padding-top: 0; }
/* La page dessous ne défile plus tant qu'un panneau est ouvert. */
body.panneau-ouvert { overflow: hidden; }

/* ═══ Display P3 — généré par bin/couleurs-p3.php, ne pas modifier à la main ═══ */
@supports (color: color(display-p3 0 0 0)) {
  @media (color-gamut: p3) {
    .cover--plein, .cover--centre {
      --fg: color(display-p3 1 1 1 / 0.92);
      --fg-2: color(display-p3 1 1 1 / 0.62);
      --fg-3: color(display-p3 1 1 1 / 0.42);
      --sur-media: color(display-p3 1 1 1);
    }
    [data-fond="papier"], [data-fond="creme"], [data-fond="sable"], [data-fond="sauge"], [data-fond="brume"], [data-fond="rose"] {
      --sur-media: color(display-p3 1 1 1);
    }
    [data-fond="papier"] {
      --bg: color(display-p3 0.9836 0.9805 0.9733);
      --bg-2: color(display-p3 0.9555 0.9493 0.9385);
      --bg-3: color(display-p3 0.9156 0.9063 0.8883);
      --fg: color(display-p3 0.1084 0.1022 0.0915);
      --fg-2: color(display-p3 0.1084 0.1022 0.0915 / 0.62);
      --fg-3: color(display-p3 0.1084 0.1022 0.0915 / 0.38);
      --fg-4: color(display-p3 0.1084 0.1022 0.0915 / 0.2);
      --line: color(display-p3 0.1084 0.1022 0.0915 / 0.12);
      --hairline: color(display-p3 0.1084 0.1022 0.0915 / 0.1);
      --hover: color(display-p3 0.1084 0.1022 0.0915 / 0.045);
    }
    [data-fond="creme"] {
      --bg: color(display-p3 0.9659 0.9535 0.9212);
      --bg-2: color(display-p3 0.9371 0.9184 0.8754);
      --bg-3: color(display-p3 0.8965 0.8717 0.8214);
      --fg: color(display-p3 0.1608 0.142 0.11);
      --fg-2: color(display-p3 0.1608 0.142 0.11 / 0.62);
      --fg-3: color(display-p3 0.1608 0.142 0.11 / 0.38);
      --fg-4: color(display-p3 0.1608 0.142 0.11 / 0.2);
      --line: color(display-p3 0.1608 0.142 0.11 / 0.13);
      --hairline: color(display-p3 0.1608 0.142 0.11 / 0.11);
      --hover: color(display-p3 0.1608 0.142 0.11 / 0.05);
    }
    [data-fond="sable"] {
      --bg: color(display-p3 0.9142 0.8987 0.8628);
      --bg-2: color(display-p3 0.8782 0.8596 0.8166);
      --bg-3: color(display-p3 0.8305 0.8088 0.7586);
      --fg: color(display-p3 0.2802 0.2299 0.1663);
      --fg-2: color(display-p3 0.2802 0.2299 0.1663 / 0.66);
      --fg-3: color(display-p3 0.2802 0.2299 0.1663 / 0.4);
      --fg-4: color(display-p3 0.2802 0.2299 0.1663 / 0.22);
      --line: color(display-p3 0.2802 0.2299 0.1663 / 0.15);
      --hairline: color(display-p3 0.2802 0.2299 0.1663 / 0.13);
      --hover: color(display-p3 0.2802 0.2299 0.1663 / 0.06);
    }
    [data-fond="encre"], [data-fond="cacao"], [data-fond="nuit"], [data-fond="olive"], [data-fond="ardoise"] {
      --sur-media: color(display-p3 1 1 1);
      --etat-demande: color(display-p3 0.5312 0.6543 0.7577);
      --etat-devis: color(display-p3 0.8185 0.6477 0.3504);
      --etat-confirme: color(display-p3 0.5098 0.7412 0.6136);
      --etat-realise: color(display-p3 0.6293 0.5825 0.7294);
      --etat-livre: color(display-p3 0.6141 0.6571 0.6365);
      --etat-perdu: color(display-p3 0.8313 0.5568 0.4665);
    }
    [data-fond="encre"] {
      --bg: color(display-p3 0.0666 0.0818 0.1074);
      --bg-2: color(display-p3 0.0908 0.1091 0.142);
      --bg-3: color(display-p3 0.123 0.1443 0.1845);
      --fg: color(display-p3 0.9361 0.9485 0.9702 / 0.94);
      --fg-2: color(display-p3 0.9361 0.9485 0.9702 / 0.56);
      --fg-3: color(display-p3 0.9361 0.9485 0.9702 / 0.32);
      --fg-4: color(display-p3 0.9361 0.9485 0.9702 / 0.16);
      --line: color(display-p3 0.9361 0.9485 0.9702 / 0.12);
      --hairline: color(display-p3 0.9361 0.9485 0.9702 / 0.1);
      --hover: color(display-p3 0.9361 0.9485 0.9702 / 0.05);
    }
    [data-fond="cacao"] {
      --bg: color(display-p3 0.135 0.1035 0.0992);
      --bg-2: color(display-p3 0.1657 0.1311 0.1232);
      --bg-3: color(display-p3 0.2076 0.1667 0.1551);
      --fg: color(display-p3 0.935 0.9071 0.8745);
      --fg-2: color(display-p3 0.935 0.9071 0.8745 / 0.6);
      --fg-3: color(display-p3 0.935 0.9071 0.8745 / 0.34);
      --fg-4: color(display-p3 0.935 0.9071 0.8745 / 0.17);
      --line: color(display-p3 0.935 0.9071 0.8745 / 0.13);
      --hairline: color(display-p3 0.935 0.9071 0.8745 / 0.11);
      --hover: color(display-p3 0.935 0.9071 0.8745 / 0.06);
    }
    [data-fond="nuit"] {
      --bg: color(display-p3 0.0764 0.0671 0.0598);
      --bg-2: color(display-p3 0.1033 0.0908 0.0799);
      --bg-3: color(display-p3 0.1379 0.1223 0.1078);
      --fg: color(display-p3 0.9613 0.9458 0.9241 / 0.93);
      --fg-2: color(display-p3 0.9613 0.9458 0.9241 / 0.55);
      --fg-3: color(display-p3 0.9613 0.9458 0.9241 / 0.32);
      --fg-4: color(display-p3 0.9613 0.9458 0.9241 / 0.16);
      --line: color(display-p3 0.9613 0.9458 0.9241 / 0.12);
      --hairline: color(display-p3 0.9613 0.9458 0.9241 / 0.1);
      --hover: color(display-p3 0.9613 0.9458 0.9241 / 0.06);
    }
    [data-fond="sauge"] {
      --bg: color(display-p3 0.9354 0.9447 0.9199);
      --bg-2: color(display-p3 0.8976 0.9131 0.8778);
      --bg-3: color(display-p3 0.8442 0.8658 0.8164);
      --fg: color(display-p3 0.1537 0.187 0.1662);
      --fg-2: color(display-p3 0.1537 0.187 0.1662 / 0.72);
      --fg-3: color(display-p3 0.1537 0.187 0.1662 / 0.46);
      --fg-4: color(display-p3 0.1537 0.187 0.1662 / 0.2);
      --line: color(display-p3 0.1537 0.187 0.1662 / 0.13);
      --hairline: color(display-p3 0.1537 0.187 0.1662 / 0.11);
      --hover: color(display-p3 0.1537 0.187 0.1662 / 0.05);
    }
    [data-fond="brume"] {
      --bg: color(display-p3 0.9354 0.9447 0.9556);
      --bg-2: color(display-p3 0.8969 0.9093 0.9274);
      --bg-3: color(display-p3 0.8474 0.8659 0.8913);
      --fg: color(display-p3 0.1303 0.1635 0.2039);
      --fg-2: color(display-p3 0.1303 0.1635 0.2039 / 0.72);
      --fg-3: color(display-p3 0.1303 0.1635 0.2039 / 0.46);
      --fg-4: color(display-p3 0.1303 0.1635 0.2039 / 0.2);
      --line: color(display-p3 0.1303 0.1635 0.2039 / 0.13);
      --hairline: color(display-p3 0.1303 0.1635 0.2039 / 0.11);
      --hover: color(display-p3 0.1303 0.1635 0.2039 / 0.05);
    }
    [data-fond="rose"] {
      --bg: color(display-p3 0.9579 0.9268 0.9155);
      --bg-2: color(display-p3 0.9272 0.8805 0.8652);
      --bg-3: color(display-p3 0.8815 0.8222 0.8032);
      --fg: color(display-p3 0.2197 0.1562 0.1441);
      --fg-2: color(display-p3 0.2197 0.1562 0.1441 / 0.72);
      --fg-3: color(display-p3 0.2197 0.1562 0.1441 / 0.46);
      --fg-4: color(display-p3 0.2197 0.1562 0.1441 / 0.2);
      --line: color(display-p3 0.2197 0.1562 0.1441 / 0.13);
      --hairline: color(display-p3 0.2197 0.1562 0.1441 / 0.11);
      --hover: color(display-p3 0.2197 0.1562 0.1441 / 0.05);
    }
    [data-fond="olive"] {
      --bg: color(display-p3 0.1112 0.1174 0.0859);
      --bg-2: color(display-p3 0.1433 0.1526 0.1141);
      --bg-3: color(display-p3 0.1833 0.1956 0.1501);
      --fg: color(display-p3 0.9398 0.9336 0.8837 / 0.93);
      --fg-2: color(display-p3 0.9398 0.9336 0.8837 / 0.56);
      --fg-3: color(display-p3 0.9398 0.9336 0.8837 / 0.32);
      --fg-4: color(display-p3 0.9398 0.9336 0.8837 / 0.16);
      --line: color(display-p3 0.9398 0.9336 0.8837 / 0.12);
      --hairline: color(display-p3 0.9398 0.9336 0.8837 / 0.1);
      --hover: color(display-p3 0.9398 0.9336 0.8837 / 0.06);
    }
    [data-fond="ardoise"] {
      --bg: color(display-p3 0.1065 0.1248 0.1431);
      --bg-2: color(display-p3 0.1347 0.156 0.178);
      --bg-3: color(display-p3 0.1707 0.1951 0.2208);
      --fg: color(display-p3 0.9219 0.9404 0.9551 / 0.93);
      --fg-2: color(display-p3 0.9219 0.9404 0.9551 / 0.56);
      --fg-3: color(display-p3 0.9219 0.9404 0.9551 / 0.32);
      --fg-4: color(display-p3 0.9219 0.9404 0.9551 / 0.16);
      --line: color(display-p3 0.9219 0.9404 0.9551 / 0.12);
      --hairline: color(display-p3 0.9219 0.9404 0.9551 / 0.1);
      --hover: color(display-p3 0.9219 0.9404 0.9551 / 0.06);
    }
    [data-fond="libre"] {
      --sur-media: color(display-p3 1 1 1);
    }
    .cover--film {
      --fg: color(display-p3 1 1 1 / 0.92);
      --fg-2: color(display-p3 1 1 1 / 0.64);
      --fg-3: color(display-p3 1 1 1 / 0.44);
      --sur-media: color(display-p3 1 1 1);
    }
    .cinema {
      --fg: color(display-p3 1 1 1 / 0.92);
      --fg-2: color(display-p3 1 1 1 / 0.62);
      --fg-3: color(display-p3 1 1 1 / 0.4);
      --line: color(display-p3 1 1 1 / 0.14);
      --hairline: color(display-p3 1 1 1 / 0.1);
      --hover: color(display-p3 1 1 1 / 0.08);
      --btn-bg: color(display-p3 0.9857 0.9919 0.9849);
      --btn-fg: color(display-p3 0.054 0.0662 0.0736);
    }
    [data-fond="papier"], [data-fond="creme"], [data-fond="brume"] {
      --encre: color(display-p3 0.1944 0.2338 0.267);
    }
  }
}
/* ═══ fin du bloc Display P3 ═══ */
