/* ============ Brand content — étagère de livres ============
   Chargée après site.css : réutilise .divider-player, .player-placeholder,
   .player-role, .about-placeholder, .shorts-close, .shorts-hint et les
   variables --tile-frame / --chrome-h. Les couleurs des livres viennent de
   l'admin (--book-bg / --book-text posées inline) : le thème n'y touche pas,
   il ne change que la planche, les ombres et le liseré. */
:root {
  --shelf-board: #e8e5dc;
  --shelf-board-edge: #ffffff;
  --shelf-shadow: rgba(0, 0, 0, 0.22);
  --book-shadow: rgba(20, 20, 25, 0.35);
  --book-line: rgba(0, 0, 0, 0.10);
  --pages-1: #f1efe8;
  --pages-2: #dcd9d0;
  --floor-h: 78px;
}
html[data-theme="dark"] {
  --shelf-board: #1e1e23;
  --shelf-board-edge: rgba(255, 255, 255, 0.08);
  --shelf-shadow: rgba(0, 0, 0, 0.7);
  --book-shadow: rgba(0, 0, 0, 0.65);
  --book-line: rgba(255, 255, 255, 0.14);
  --pages-1: #2a2a30;
  --pages-2: #1b1b1f;
}

/* ---------- Scène ---------- */
.shelf-page {
  position: relative;
  width: 100%;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
}
.shelf-stage {
  position: absolute;
  top: var(--chrome-h); left: 0; right: 0; bottom: 0;
  --hmax: 660px;
  --split: 0px;
}
.shelf-stage.empty .about-placeholder {
  position: absolute;
  top: 40%; left: 50%;
  transform: translate(-50%, -50%);
  margin: 0;
}
/* La planche : pleine largeur, d'écran à écran. Sous la rangée dans l'ordre
   de peinture pour que les ombres des livres tombent dessus. */
.shelf-board {
  position: absolute;
  left: 0; right: 0;
  top: calc(100% - var(--floor-h));
  height: 10px;
  background: var(--shelf-board);
  border-top: 1px solid var(--shelf-board-edge);
  box-shadow: 0 16px 30px -12px var(--shelf-shadow);
  transition: background 0.35s ease;
}
/* Conteneur défilant : 24 px de padding en haut pour le soulèvement au
   survol, 24 px en bas pour les ombres ; le bord bas des livres tombe donc
   exactement sur la ligne d'étagère. */
.shelf-scroll {
  position: absolute;
  z-index: 1;
  left: 0; right: 0;
  bottom: calc(var(--floor-h) - 24px);
  height: calc(var(--hmax) + 48px);
  padding: 24px 48px;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}
.shelf-scroll::-webkit-scrollbar { display: none; }
.shelf-scroll.is-overflow {
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 40px, #000 calc(100% - 40px), transparent);
  mask-image: linear-gradient(90deg, transparent, #000 40px, #000 calc(100% - 40px), transparent);
}
/* Livre ouvert : les transforms d'écartement étendraient la zone défilante. */
.shelf-stage.is-open .shelf-scroll { overflow: hidden; }
.shelf-row {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 4px;
  height: 100%;
  min-width: max-content;
  --delta: 0px;
  transform: translateX(var(--delta));
  transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}
.is-overflow .shelf-row { justify-content: flex-start; }

/* ---------- Un livre = logement (.book) + aspect (.spine-body) ----------
   Deux éléments, deux transforms : le logement porte la largeur, la hauteur
   et la translation d'écartement ; l'aspect porte la couleur, la matière,
   l'inclinaison du penché et le soulèvement au survol. */
.book {
  position: relative;
  flex: none;
  width: var(--w);
  height: var(--h);
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Marge du côté de l'appui = projection du sommet (--lean, arrondie) moins
   le gap de 4 px du flex : le sommet touche la tranche voisine sans la
   recouvrir, un triangle d'air au pied. Penché à droite : marge à droite ;
   penché à gauche : le miroir exact, marge à gauche. */
.book.lean { z-index: 2; }
.book.lean-r { margin-right: calc(var(--lean) - 4px); }
.book.lean-l { margin-left: calc(var(--lean) - 4px); }
.book:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}
.is-open .book[data-side="l"] { transform: translateX(calc(-1 * var(--split))); }
.is-open .book[data-side="r"] { transform: translateX(var(--split)); }

/* Aspect de base (pas de texture carton : c'est ce qui distingue Brand de
   YouTube), affiné ensuite par la matière (.fin-cuir / .fin-vernis /
   .fin-mat). Le liseré intérieur --book-line rend lisible un livre clair sur
   le fond clair. Partagé avec la face tranche de l'objet 3D. */
.spine-body,
.book3d-spine {
  display: block;
  background: var(--book-bg);
  color: var(--book-text);
  border: 1.5px solid var(--tile-frame);
  border-radius: 3px 3px 0 0;
  box-shadow: inset 0 0 0 1px var(--book-line), 0 1px 0 rgba(0, 0, 0, 0.08), 0 14px 22px -14px var(--book-shadow);
}
.spine-body {
  position: absolute;
  inset: 0;
  transition: transform 0.28s cubic-bezier(0.22, 0.9, 0.24, 1), box-shadow 0.28s ease, opacity 0.25s ease;
}
/* Relief cylindrique du dos : clair à gauche, sombre à droite. */
.spine-body::before,
.book3d-spine::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  background: linear-gradient(90deg, rgba(0, 0, 0, 0.16), rgba(255, 255, 255, 0.12) 18%, rgba(255, 255, 255, 0) 42%, rgba(0, 0, 0, 0.08) 84%, rgba(0, 0, 0, 0.22));
}
/* Deux filets (nerfs de reliure), à 12 px du haut et 40 px du bas. */
.spine-body::after,
.book3d-spine::after {
  content: '';
  position: absolute;
  top: 12px; bottom: 40px; left: 8px; right: 8px;
  pointer-events: none;
  border-top: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  opacity: 0.3;
}
/* Nom composé verticalement, lu de haut en bas (jaquette anglo-saxonne).
   Pas de text-transform : une Caveat ou une Pacifico doit s'exprimer. */
.spine-title {
  position: absolute;
  top: 18px; bottom: 48px; left: 50%;
  transform: translateX(-50%);
  writing-mode: vertical-rl;
  text-orientation: mixed;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 700;
  letter-spacing: 0.04em;
  line-height: 1;
  color: var(--book-text);
}
/* Numéro d'ordre (celui de l'admin) au pied, horizontal, en mono. */
.spine-foot {
  position: absolute;
  bottom: 12px; left: 0; right: 0;
  text-align: center;
  font: 10px/1 'IBM Plex Mono', monospace;
  font-weight: 400;
  letter-spacing: 0.1em;
  opacity: 0.7;
  white-space: nowrap;
  overflow: hidden;
}
.book.thin .spine-foot { display: none; }

/* ---------- Matières ----------
   Trois finitions en CSS pur, appliquées à la tranche (étagère et face 3D)
   et à la couverture ouverte, par-dessus la couleur du client. Les textures
   sont des calques de fond (sous le texte) : aucun voile sur le média. */
/* Cuir : grain (deux trames de points décalées), vignette dans les bords,
   reflet doux au centre, titre embossé. */
.fin-cuir.spine-body,
.fin-cuir.book3d-spine,
.book3d-cover.fin-cuir {
  background-image:
    radial-gradient(rgba(255, 255, 255, 0.075) 0.6px, transparent 1.1px),
    radial-gradient(rgba(0, 0, 0, 0.11) 0.7px, transparent 1.3px),
    radial-gradient(ellipse at 50% 45%, rgba(255, 255, 255, 0.09), transparent 62%),
    radial-gradient(ellipse at 50% 50%, transparent 58%, rgba(0, 0, 0, 0.26) 100%);
  background-size: 3px 3px, 5px 5px, 100% 100%, 100% 100%;
  background-position: 0 0, 1px 2px, 0 0, 0 0;
}
.fin-cuir .spine-title,
.book3d-cover.fin-cuir .cover-title {
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.16), 0 -1px 0 rgba(0, 0, 0, 0.42);
}
/* Vernis : couleur saturée, bande de reflet nette (verticale sur la tranche,
   en diagonale sur la couverture), bord brillant, titre net. */
.fin-vernis.spine-body,
.fin-vernis.book3d-spine,
.book3d-cover.fin-vernis {
  filter: saturate(1.35) contrast(1.04);
  outline: 1px solid rgba(255, 255, 255, 0.5);
  outline-offset: -3px;
}
.fin-vernis.spine-body::before,
.fin-vernis.book3d-spine::before {
  background:
    linear-gradient(90deg, rgba(255, 255, 255, 0) 14%, rgba(255, 255, 255, 0.55) 24%, rgba(255, 255, 255, 0.18) 34%, rgba(255, 255, 255, 0) 48%),
    linear-gradient(90deg, rgba(0, 0, 0, 0.16), rgba(255, 255, 255, 0.12) 18%, rgba(255, 255, 255, 0) 42%, rgba(0, 0, 0, 0.08) 84%, rgba(0, 0, 0, 0.22));
}
.book3d-cover.fin-vernis {
  background-image: linear-gradient(105deg, rgba(255, 255, 255, 0) 26%, rgba(255, 255, 255, 0.28) 36%, rgba(255, 255, 255, 0.08) 44%, rgba(255, 255, 255, 0) 54%);
}
/* Mat : aplat, pas de reflet (relief réduit à deux ombres de bord), léger
   grain papier, ombre douce. */
.fin-mat.spine-body,
.fin-mat.book3d-spine,
.book3d-cover.fin-mat {
  background-image:
    repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.02) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.02) 0 1px, transparent 1px 4px);
}
.fin-mat.spine-body::before,
.fin-mat.book3d-spine::before {
  background: linear-gradient(90deg, rgba(0, 0, 0, 0.10), rgba(0, 0, 0, 0) 22%, rgba(0, 0, 0, 0) 78%, rgba(0, 0, 0, 0.12));
}
.fin-mat.spine-body {
  box-shadow: inset 0 0 0 1px var(--book-line), 0 1px 0 rgba(0, 0, 0, 0.05), 0 10px 18px -12px var(--book-shadow);
}

/* Survol / focus : le livre se tire de l'étagère. */
.book:hover .spine-body,
.book:focus-visible .spine-body {
  transform: translateY(-10px);
  box-shadow: inset 0 0 0 1px var(--book-line), 0 1px 0 rgba(0, 0, 0, 0.08), 0 22px 30px -14px var(--book-shadow);
}
/* Le penché à droite pivote sur son coin bas droit : son coin bas gauche se
   soulève, comme un vrai livre appuyé contre son voisin. Le penché à gauche
   est le miroir (pivot bas gauche, rotation négative). Le survol se compose. */
.book.lean .spine-body { z-index: 2; }
.book.lean-r .spine-body {
  transform-origin: 100% 100%;
  transform: rotate(var(--deg, 8deg));
}
.book.lean-l .spine-body {
  transform-origin: 0 100%;
  transform: rotate(calc(-1 * var(--deg, 8deg)));
}
.book.lean-r:hover .spine-body,
.book.lean-r:focus-visible .spine-body { transform: rotate(var(--deg, 8deg)) translateY(-10px); }
.book.lean-l:hover .spine-body,
.book.lean-l:focus-visible .spine-body { transform: rotate(calc(-1 * var(--deg, 8deg))) translateY(-10px); }
/* Livre sorti : sa place reste visible en fantôme ; le penché se redresse. */
.book.ghost .spine-body {
  opacity: 0.25;
  pointer-events: none;
  transform: none !important;
  transition: opacity 0.25s ease, transform 0.3s ease;
}

/* ---------- Calque du livre ouvert (vol 3D) ----------
   .book3d est la boîte de la couverture (Wc × Hc). La face tranche part de
   l'arête gauche vers le visiteur ; à rotateY(90deg) elle est de face et la
   couverture de profil, à rotateY(0) c'est l'inverse : un vrai volume qu'on
   tourne vers soi. La perspective n'est pas sur ce calque mais dans le
   transform du livre (perspective() dans la liste, voir bookTransform dans
   shelf.js) : le point de fuite est l'arête du livre, rien ne se projette à
   sa gauche pendant le vol. */
.shelf-open {
  position: fixed;
  inset: 0;
  z-index: 140;
  pointer-events: none;
}
.book3d {
  position: absolute;
  transform-style: preserve-3d;
  transform-origin: 0 100%;
  pointer-events: auto;
  will-change: transform;
  /* Courbe en S (et non la courbe « rapide » de l'étagère) : le quart de
     tour doit se voir, à mi-parcours la couverture est encore à 45°. */
  transition: transform 0.7s cubic-bezier(0.4, 0, 0.2, 1);
  outline: none;
}
/* Posé : plus rien de composité en 3D, texte 2D net, média présent. */
.book3d.settled {
  transform-style: flat;
  will-change: auto;
}
.book3d.settled .book3d-spine { visibility: hidden; }
/* Fermeture : la zone média (cadre, étiquette de rôle, onglets) s'efface en
   120 ms pendant que la couverture est encore immobile ; le pivot ne
   commence qu'après (transition-delay posé par shelf.js). */
.book3d.closing .cover-player-wrap,
.book3d.closing .cover-tabs {
  opacity: 0;
  transition: opacity 0.12s ease;
}
/* Copie de la tranche à sa taille réelle (w × h), agrandie par 1/k pour
   remplir la hauteur Hc : la géométrie du texte, des filets et des bords est
   exactement celle de la tranche d'origine une fois le tout réduit par k.
   Le translate3d d'un pixel la décolle du plan de la couverture : les deux
   faces ne se touchent plus sur l'arête et Chrome les trie correctement
   (sans lui la couverture était peinte par-dessus la tranche en vol). */
.book3d-spine {
  position: absolute;
  left: 0; top: 0;
  transform-origin: 0 0;
  transform: translate3d(-1px, 0, 1px) rotateY(-90deg);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  box-shadow: none;
  border-radius: 3px 0 0 3px;
  overflow: hidden;
}
.book3d-spine.thin .spine-foot { display: none; }
/* Couverture : dos carré à gauche avec son mors, gouttière arrondie à
   droite, tranche des pages en escalier (deux marches), ombre large.
   Padding 20.5 px + bordure 1.5 px = 22 px depuis le bord extérieur.
   overflow hidden : rien de son contenu ne dépasse ses bords, en vol non plus. */
.book3d-cover {
  position: absolute;
  inset: 0;
  padding: 20.5px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  overflow: hidden;
  background: var(--book-bg);
  color: var(--book-text);
  border: 1.5px solid var(--tile-frame);
  border-radius: 3px 10px 10px 3px;
  box-shadow: inset 0 0 0 1px var(--book-line), 4px 4px 0 var(--pages-1), 8px 8px 0 var(--pages-2), 0 40px 70px -30px var(--book-shadow);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
.book3d-cover::before {
  content: '';
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 18px;
  background: linear-gradient(90deg, rgba(0, 0, 0, 0.18), transparent);
  pointer-events: none;
  border-radius: 3px 0 0 3px;
}
/* Aucun enfant ne se comprime : le média est dimensionné en px par le JS
   d'après la place que laissent les autres. */
.book3d-cover > * { flex: none; min-width: 0; }
.cover-head {
  min-height: 44px;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.cover-title {
  margin: 0;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.1;
  letter-spacing: 0.02em;
}
.cover-num {
  flex: none;
  font: 11px/1 'IBM Plex Mono', monospace;
  letter-spacing: 0.14em;
  opacity: 0.7;
}
/* Onglets de projet : une seule rangée soudée au bord haut du cadre vidéo
   (gap de 12 px − 15,5 px de marge + 2 px de padding bas qui logent le
   décalage des onglets inactifs = 1,5 px de recouvrement, l'épaisseur du
   trait). Quand ils ne tiennent pas, la rangée défile (doigt, molette,
   focus) sous un fondu latéral, barre masquée : jamais de rangée coupée ni
   d'onglet « flottant » au-dessus d'une autre rangée. L'actif est noir comme
   le cadre : littéralement fusionné au player. */
.cover-tabs {
  position: relative;
  z-index: 1;
  align-self: center;
  display: flex;
  align-items: flex-end;
  gap: 6px;
  flex-wrap: nowrap;
  margin-bottom: -15.5px;
  padding: 0 10px 2px;
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}
.cover-tabs::-webkit-scrollbar { display: none; }
.cover-tabs.scrolls {
  padding-left: 24px;
  padding-right: 24px;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 24px, #000 calc(100% - 24px), transparent);
  mask-image: linear-gradient(90deg, transparent, #000 24px, #000 calc(100% - 24px), transparent);
}
.cover-tab {
  flex: none;
  border: 1.5px solid var(--tile-frame);
  border-bottom: none;
  border-radius: 8px 8px 0 0;
  padding: 7px 14px 9px;
  font-family: inherit;
  font-weight: 700;
  font-size: 13px;
  line-height: 1;
  letter-spacing: 0.06em;
  white-space: nowrap;
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  color: var(--book-text);
  background: color-mix(in srgb, var(--book-bg) 82%, #000);
  transition: transform 0.18s ease, background 0.18s ease, color 0.18s ease;
  transform: translateY(2px);
}
.cover-tab:hover { transform: translateY(1px); }
.cover-tab:focus-visible { outline: 2px solid currentColor; outline-offset: -4px; }
.cover-tab.active {
  background: #101013;
  color: #f2f2ef;
  transform: none;
}
.cover-player-wrap {
  position: relative;
  align-self: center;
  max-width: 100%;
}
/* La moitié basse de l'étiquette de rôle déborde sous le player. */
.cover-player-wrap.has-role { margin-bottom: 26px; }
/* « L'écran s'allume » : le contenu du player apparaît en fondu. */
.cover-player > * {
  opacity: 0;
  transition: opacity 0.25s ease;
}
.cover-player.lit > * { opacity: 1; }
/* Vidéo verticale : cadre 9:16 aux allures de téléphone, discret (coins
   arrondis, bord épais, fin liseré intérieur), centré dans la zone média. */
.cover-player.portrait {
  margin: 0 auto;
  border: 3px solid #000;
  border-radius: 18px;
  box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, 0.14), 0 14px 28px -14px rgba(0, 0, 0, 0.55);
}
.cover-player.portrait iframe,
.cover-player.portrait video { border-radius: 14px; }
/* Galerie : une photo à la fois, entière (contain) sur fond sombre. */
.cover-gallery {
  position: absolute;
  inset: 0;
  background: #101013;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
}
.gal-img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: opacity 0.15s ease;
}
.gal-img.fading { opacity: 0; }
.gal-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 40px;
  height: 60px;
  padding: 0 0 4px;
  border: 0;
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.45);
  color: #f2f2ef;
  font: 34px/1 'IBM Plex Mono', monospace;
  cursor: pointer;
  opacity: 0.8;
  transition: opacity 0.18s ease, background 0.18s ease;
}
.gal-prev { left: 10px; }
.gal-next { right: 10px; }
.gal-arrow:hover,
.gal-arrow:focus-visible { opacity: 1; background: rgba(0, 0, 0, 0.72); }
.gal-arrow:focus-visible { outline: 2px solid #f2f2ef; outline-offset: -3px; }
/* Plus rien à faire défiler de ce côté (ni photo ni texte) : grisée, inerte. */
.gal-arrow.off,
.gal-arrow.off:hover { opacity: 0.28; background: rgba(0, 0, 0, 0.3); cursor: default; }
/* Points et compteur en haut : le bas du cadre est à l'étiquette de rôle. */
.gal-foot {
  position: absolute;
  top: 10px; left: 0; right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  pointer-events: none;
}
.gal-dots { display: flex; gap: 5px; }
.gal-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.38);
  transition: background 0.18s ease;
}
.gal-dot.active { background: #ffffff; }
.gal-count {
  font: 11px/1 'IBM Plex Mono', monospace;
  letter-spacing: 0.12em;
  color: #f2f2ef;
  background: rgba(0, 0, 0, 0.55);
  padding: 5px 8px;
  border-radius: 3px;
}
/* Étiquette de rôle : à cheval sur le bord bas du player, couleurs du livre. */
.book3d .player-role {
  bottom: 0;
  font-size: clamp(18px, 2.4vh, 30px);
  padding: 9px 22px 12px;
  max-width: calc(100% - 32px);
  background: var(--book-bg);
  color: var(--book-text);
  border-color: var(--tile-frame);
  box-shadow: 0 8px 18px rgba(0, 0, 0, 0.3);
}
/* Image en haut, texte en bas, comme une jaquette. Le texte entier est dans
   le DOM : une zone de 3 lignes au plus (4 au téléphone) qui défile — molette,
   doigt, flèches de la galerie ou ▲ ▼ — d'une page à la fois, en douceur ; un
   fondu signale ce qui reste dessous (.fade-b) ou dessus (.fade-t). Un texte
   court prend sa hauteur naturelle : la zone ne se réserve pas 3 lignes pour rien. */
.cover-text {
  margin: auto 0 0;
  display: flex;
  align-items: stretch;
  gap: 8px;
  min-height: 0;
}
.cover-desc {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: 14px;
  line-height: 1.45;
  max-height: 4.35em;   /* 3 lignes : le pas d'une page tombe sur une ligne entière */
  opacity: 0.85;
  overflow-wrap: anywhere;
  overflow-y: auto;
  overscroll-behavior: contain;
  scroll-behavior: smooth;
  scrollbar-width: none;
  touch-action: pan-y;
}
.cover-desc::-webkit-scrollbar { display: none; }
.cover-desc.fade-b {
  -webkit-mask-image: linear-gradient(#000 calc(100% - 1.3em), transparent);
  mask-image: linear-gradient(#000 calc(100% - 1.3em), transparent);
}
.cover-desc.fade-t {
  -webkit-mask-image: linear-gradient(transparent, #000 1.3em);
  mask-image: linear-gradient(transparent, #000 1.3em);
}
.cover-desc.fade-t.fade-b {
  -webkit-mask-image: linear-gradient(transparent, #000 1.3em, #000 calc(100% - 1.3em), transparent);
  mask-image: linear-gradient(transparent, #000 1.3em, #000 calc(100% - 1.3em), transparent);
}
/* ▲ ▼ à droite du texte (projets sans galerie, texte débordant) : discrètes,
   couleur du livre, grisées quand ce sens est épuisé. */
.desc-nav {
  flex: none;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
}
.desc-arrow {
  width: 22px;
  height: 18px;
  padding: 0;
  border: 0;
  border-radius: 3px;
  background: none;
  color: inherit;
  font: 9px/1 'IBM Plex Mono', monospace;
  opacity: 0.55;
  cursor: pointer;
  transition: opacity 0.18s ease;
}
.desc-arrow:hover,
.desc-arrow:focus-visible { opacity: 1; }
.desc-arrow:focus-visible { outline: 1.5px solid currentColor; outline-offset: -2px; }
.desc-arrow.off,
.desc-arrow.off:hover { opacity: 0.18; cursor: default; }
/* display: flex l'emporterait sur le [hidden] du navigateur. */
.cover-text[hidden],
.desc-nav[hidden],
.gal-arrow[hidden] { display: none; }
.book3d-cover.tight .cover-text { display: none; }
/* Le titre du projet ferme la jaquette : au bas, sous la description s'il y
   en a une, sinon seul en pied. */
.cover-foot {
  margin: auto 0 0;
  font: 11px/1 'IBM Plex Mono', monospace;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0.7;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cover-text + .cover-foot { margin-top: 0; }
.book3d-cover.tight .cover-foot { margin-top: auto; }

/* Redimensionnement avec un livre ouvert : tout se replace sans animation. */
.shelf-stage.no-anim .shelf-row,
.shelf-stage.no-anim .book,
.shelf-stage.no-anim .spine-body,
.book3d.no-anim { transition: none !important; }

/* ---------- Téléphone ---------- */
@media (max-width: 860px) {
  :root { --floor-h: 64px; }
  .shelf-scroll {
    padding: 24px 16px;
    overscroll-behavior-x: contain;
  }
  .shelf-board { height: 8px; }
  .shorts-close { top: 108px; right: 16px; }
  .shorts-hint { font-size: 10px; right: 16px; }
  .book3d-cover { padding: 14.5px; gap: 10px; }
  .cover-head { min-height: 30px; }
  .cover-tab { font-size: 12px; padding: 6px 10px 8px; }
  .book3d .player-role { font-size: 15px; padding: 7px 14px 9px; }
  .cover-desc { font-size: 13px; max-height: 5.8em; }   /* 4 lignes */
  .cover-foot { display: none; }
  .cover-player.portrait { border-width: 2.5px; border-radius: 14px; }
  .cover-player.portrait iframe,
  .cover-player.portrait video { border-radius: 11px; }
  /* Flèches de galerie à la taille d'un doigt. */
  .gal-arrow { width: 44px; height: 64px; font-size: 30px; }
  .gal-prev { left: 6px; }
  .gal-next { right: 6px; }
  /* Téléphone en paysage (scène < 300 px) : tranches de 120 à 182 px, sans
     pied ni filets, titre sur toute la hauteur ; livre ouvert, le fantôme
     s'efface (la couverture, centrée dans la scène, ne le recouvre pas). */
  .shelf-page.landscape .spine-title { top: 12px; bottom: 12px; }
  .shelf-page.landscape .spine-foot,
  .shelf-page.landscape .spine-body::after,
  .shelf-page.landscape .book3d-spine::after { display: none; }
  .shelf-page.landscape .is-open .book.ghost .spine-body { opacity: 0; }

  /* ---- Étages (téléphone en portrait ; .tiers posée par shelf.js) ----
     La page redevient un flux : les étages (.shelf-tier = rangée + planche)
     se superposent au bas de la scène, du haut vers le bas ; s'il y en a plus
     que d'écran la page défile, l'en-tête reste (fixé, sur fond opaque) et
     l'aide-mémoire suit la fin de la dernière étagère. Mêmes valeurs que
     shelf.js : marges 16 px, air 16 px dessus / 22 px dessous, planche 8 px,
     pied de 64 px (22 + aide-mémoire 42). */
  .shelf-page.tiers {
    height: auto;
    min-height: 100vh;
    min-height: 100svh;
    overflow: visible;
    display: flex;
    flex-direction: column;
  }
  .shelf-page.tiers .shelf-stage {
    position: static;
    flex: 1 0 auto;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding-top: var(--chrome-h);
  }
  .shelf-page.tiers .shelf-stage > .shelf-board { display: none; }
  .shelf-page.tiers .shelf-scroll {
    position: static;
    height: auto;
    padding: 0;
    overflow: visible;
    -webkit-mask-image: none;
    mask-image: none;
  }
  /* Chaque étage clippe ses livres écartés (jamais de défilement horizontal
     du document) ; l'air du haut loge le soulèvement au survol, celui du bas
     les ombres. */
  .shelf-tier {
    position: relative;
    overflow: hidden;
    padding: 16px 16px 22px;
  }
  .shelf-tier .shelf-row {
    height: var(--hmax);
    min-width: 0;
    justify-content: center;
  }
  .shelf-tier .shelf-board {
    position: relative;
    top: auto;
    margin: 0 -16px;
    height: 8px;
  }
  .shelf-page.tiers .shorts-hint {
    position: static;
    flex: none;
    margin: 0;
    padding: 8px 16px 22px;
    text-align: right;
  }
  body.shelf-tiers .site-header {
    position: fixed;
    background: var(--bg);
    transition: background 0.35s ease;
  }
}

/* ---------- Mouvement réduit : tout est instantané sauf les fondus ---------- */
@media (prefers-reduced-motion: reduce) {
  .shelf-row, .book, .spine-body, .cover-tab { transition-duration: 1ms !important; }
  .book3d { transition: transform 1ms linear, opacity 0.2s ease !important; }
  .book.ghost .spine-body, .cover-player > * { transition: opacity 0.2s ease !important; }
  .gal-img { transition: none !important; }
  .cover-desc { scroll-behavior: auto; }
}
