/* =====================================================================
   LA VISIONNEUSE — UN SEUL HABILLAGE POUR TOUS LES THÈMES.
   =====================================================================
   Le balisage n'est écrit dans aucun gabarit : c'est assets/js/commun.js
   qui le fabrique, et il fabrique SEPT parties — le cadre, la scène,
   l'image, le pied, deux chevrons et la croix.

   Les thèmes n'en habillaient que trois. Le reste sortait nu, avec les
   styles par défaut du navigateur, et le résultat était une visionneuse
   à peu près inutilisable :

     1. ELLE N'OCCUPAIT PAS L'ÉCRAN. Un <dialog> est posé par l'agent
        utilisateur en « width: fit-content ; height: fit-content ;
        margin: auto ; border: solid ; padding: 1em ». `position: fixed`
        et `inset: 0` ne suffisent pas à l'étendre : tant que la largeur
        est « fit-content », la boîte se rétracte sur son contenu et
        `margin: auto` la centre. On voyait donc une petite fenêtre
        bordée au milieu d'un fond assombri, et non une photo agrandie.

     2. ON NE POUVAIT PAS LA FERMER — ou l'on ne voyait pas comment.
        Le bouton de fermeture contient un <span class="croix"> vide :
        sans règle, il ne dessine rien. Le bouton était donc un carré
        bordé, sans croix, dans un coin d'une fenêtre trop petite.

     3. LES DEUX CHEVRONS ÉTAIENT DANS LE FLUX. `.visionneuse` est une
        grille centrée ; « précédente » et « suivante », faute de
        position, s'empilaient sous l'image comme deux boutons vides.

   D'OÙ CETTE FEUILLE, ET D'OÙ SA PLACE. Elle est du SOCLE, comme celle
   du sélecteur de langue et celle de l'agenda, et pour la même raison :
   ce que le script fabrique, c'est le socle qui doit l'habiller. Cinq
   thèmes qui recopient chacun sept règles finissent par en oublier
   quatre, et c'est exactement ce qui est arrivé.

   ELLE N'EMPLOIE AUCUNE COULEUR DE THÈME, et c'est délibéré : une photo
   agrandie se regarde sur du noir, quel que soit le site. Les seules
   variables lues sont `--gouttiere` et `--rayon`, avec un repli pour le
   thème qui ne les déclarerait pas.
   ===================================================================== */

/* ═══════════════════════════════════════════════════════════════════
   LE SÉLECTEUR PORTE « [open] », ET C'EST TOUT LE SUJET.

   Il disait `.visionneuse { … display: grid … }`, sans condition. Or un
   <dialog> fermé n'est invisible que parce que l'agent utilisateur lui
   applique `display: none` — et une règle d'auteur qui pose un display
   BAT cette règle-là. Conséquence : la croix fermait bien le dialogue
   (`dialog.open` passait à false, `close()` était appelé), et la photo
   agrandie RESTAIT PEINTE en plein écran, `pointer-events: auto`,
   `z-index: 200`, par-dessus toute la page.

   Pour la visiteuse, la visionneuse ne se fermait pas. Et plus rien
   n'était cliquable ensuite, puisqu'un calque de 1440 × 900 interceptait
   tout : `elementFromPoint(centre)` renvoyait encore
   `img.visionneuse__vue` après la fermeture.

   POURQUOI LE VERROU NE L'A PAS VU. tools/verifier-visionneuse.mjs
   demandait `dialog.open === false` après le clic. C'était VRAI. Le
   contrôle vérifiait l'état de la mécanique, pas ce qui restait à
   l'écran — la même faute que celles déjà relevées dans ce dépôt : un
   contrôle qui interroge la cause et jamais l'effet passe sans rien
   prouver. Il mesure désormais ce qui est PEINT.
   ═══════════════════════════════════════════════════════════════════ */
.visionneuse[open] {
  /* LES QUATRE PROPRIÉTÉS QUI DÉFONT LE <dialog> PAR DÉFAUT. Sans
     elles, rien de ce qui suit ne sert : la boîte reste rétractée. */
  width: 100%;
  max-width: 100%;
  height: 100%;
  max-height: 100%;

  position: fixed;
  inset: 0;
  z-index: 200;
  margin: 0;
  border: 0;
  padding: clamp(12px, var(--gouttiere, 24px), 48px);
  background: #0B0B0D;
  color: #fff;
  overflow: hidden;

  display: grid;
  /* `minmax(0, 1fr)` ET NON `1fr`. Une piste `1fr` a pour taille minimale
     le contenu minimal de ce qu'elle porte : une photo haute la faisait
     grandir au-delà de l'écran, et l'image sortait par le bas — 829 px
     mesurés dans une fenêtre de 800. Le zéro explicite autorise la piste
     à se réduire, et c'est alors `max-height: 100%` sur l'image qui
     décide. */
  grid-template-rows: minmax(0, 1fr) auto;
  gap: 0.9rem;
  place-items: center;
}

/* Le fond derrière la boîte : la page ne doit plus rien laisser voir. */
.visionneuse::backdrop { background: #0B0B0D; }

/* LA SCÈNE occupe toute la rangée haute, et l'image s'y inscrit sans
   jamais la déborder. `min-height: 0` est ce qui autorise une piste
   `1fr` à se réduire : sans lui, une photo haute pousse le pied hors de
   l'écran. */
.visionneuse__scene {
  min-width: 0; min-height: 0;
  width: 100%; height: 100%;
  display: grid; place-items: center;
}
.visionneuse__scene picture { display: contents; }
.visionneuse img,
.visionneuse__vue {
  display: block;
  /* L'IMAGE OCCUPE SA CASE, ET `scale-down` DÉCIDE DU RESTE.

     `max-width: 100% ; max-height: 100% ; height: auto` semblait suffire
     et ne suffisait pas : dans une grille, le pourcentage d'une hauteur
     maximale se résout contre une hauteur que la piste tient elle-même
     du contenu, et le navigateur choisit alors la largeur. Mesuré : une
     photo de 829 px de haut dans une fenêtre de 800, débordant par le
     bas, sur un cadre dont la case faisait pourtant 643.

     En donnant à l'élément la taille exacte de sa case, il n'y a plus de
     cycle : c'est `object-fit` qui place la photo dedans. `scale-down`
     plutôt que `contain` parce qu'il ne grandit jamais une petite image
     au-delà de sa taille réelle. */
  width: 100%; height: 100%;
  /* `min-*: 0` N'EST PAS DÉCORATIF ICI. La taille minimale automatique
     d'un élément de grille vaut sa taille intrinsèque : pour une image
     large de 1184 px au rapport 0,70, cela fait 829 px de haut, et cette
     minimale-là bat un `height: 100%`. C'est la dernière raison pour
     laquelle la photo sortait du cadre, et la plus discrète. */
  min-width: 0; min-height: 0;
  object-fit: scale-down;
  border-radius: var(--rayon, 0);
  /* Le glissement au doigt change de photo : le navigateur ne doit pas
     l'interpréter comme un défilement horizontal. */
  touch-action: pan-y;
  user-select: none;
}

/* LE PIED : la légende, puis le compteur. Toujours lisible, jamais
   au-dessus de l'image. */
.visionneuse__pied {
  display: flex; flex-direction: column; align-items: center; gap: 0.25rem;
  max-width: 46rem; text-align: center;
  padding-bottom: env(safe-area-inset-bottom, 0);
}
.visionneuse__legende {
  margin: 0; color: #fff;
  font-size: 0.9375rem; line-height: 1.45;
}
.visionneuse__legende:empty { display: none; }
.visionneuse__compteur {
  margin: 0; color: rgba(255, 255, 255, 0.62);
  font-size: 0.8125rem; letter-spacing: 0.06em; font-variant-numeric: tabular-nums;
}

/* =====================================================================
   LES TROIS COMMANDES
   =====================================================================
   Même dessin, même taille, même contraste : 44 px de côté au moins,
   un trait blanc à demi opaque, et le signe tracé en CSS plutôt qu'en
   caractère — une croix composée de deux barres ne dépend d'aucune
   police et ne se traduit pas.
   ===================================================================== */

.visionneuse__commande {
  position: absolute;
  display: grid; place-items: center;
  min-width: 48px; min-height: 48px;
  padding: 0;
  background: rgba(0, 0, 0, 0.45);
  border: 1px solid rgba(255, 255, 255, 0.45);
  border-radius: var(--rayon, 0);
  color: #fff;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.visionneuse__commande:hover { background: rgba(255, 255, 255, 0.14); }
.visionneuse__commande:focus-visible {
  outline: 3px solid #fff; outline-offset: 2px;
}

.visionneuse__fermer     { top: 12px; right: 12px; }
.visionneuse__precedente { left: 12px;  top: 50%; transform: translateY(-50%); }
.visionneuse__suivante   { right: 12px; top: 50%; transform: translateY(-50%); }

/* Sur un écran étroit, les chevrons descendent au niveau du pied :
   au milieu, ils recouvriraient la photo et le pouce les toucherait
   en essayant de faire glisser. */
@media (max-width: 599px) {
  .visionneuse__precedente,
  .visionneuse__suivante {
    top: auto; bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    transform: none;
  }
}

/* LA CROIX ET LES CHEVRONS, tracés. Deux barres pour la croix, deux
   pour un chevron : c'est ce qui les rend indépendants de toute
   police et de toute langue. */
.visionneuse__croix,
.visionneuse__chevron {
  display: block;
  width: 17px; height: 17px;
  position: relative;
}
.visionneuse__croix::before,
.visionneuse__croix::after {
  content: ""; position: absolute; left: 0; top: 50%;
  width: 100%; height: 2px; background: currentColor;
}
.visionneuse__croix::before { transform: rotate(45deg); }
.visionneuse__croix::after  { transform: rotate(-45deg); }

.visionneuse__chevron::before {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 11px; height: 11px;
  border-left: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
}
.visionneuse__chevron--gauche::before { transform: rotate(45deg)  translate(1px, -1px); }
.visionneuse__chevron--droite::before { transform: rotate(-135deg) translate(1px, -1px); }

/* Une seule photo dans la série : les chevrons n'ont rien à feuilleter.
   Le script les cache par l'attribut « hidden », et la règle du socle
   « [hidden] { display: none } » perd contre n'importe quelle classe qui
   pose un display — d'où ce rappel, comme dans l'administration. */
.visionneuse__commande[hidden] { display: none !important; }

/* Le mouvement éteint : rien ici n'anime, mais la règle est écrite pour
   que l'ajout d'une transition demain ne la contourne pas. */
@media (prefers-reduced-motion: reduce) {
  .visionneuse, .visionneuse * { transition: none !important; animation: none !important; }
}
