/* =========================================================
   Design tokens - shared across every page/component.
   Keep naming coordinated: any class prefixed `site-` is a
   layout chrome piece (header/footer), `hero-` is the home
   cover, `series-` covers the index + detail gallery views.
   ========================================================= */
@font-face{
  font-family: 'Aspekta';
  src: url('../fonts/aspekta-900.woff2') format('woff2');
  font-weight: 900;
  font-style: normal;
  font-display: swap;
}

:root{
  --color-paper: #fcfcfb;
  --color-ink: #565656;ß
  --color-gray: #8f8f8b;
  --color-muted: #565656;
  --color-filet: #e7e7e3;
  --color-placeholder-bg: #e9e7e3;
  --color-copyright: #b5b5b1;
  --color-overlay-bg: #1c1c1a;
  --color-overlay-fg: #fcfcfb;
  --color-nav-bg: #0f0f0f;

  --font-sans: 'Inter', system-ui, sans-serif;
  --font-serif: 'Lato', system-ui, sans-serif;
  --font-wordmark: 'Aspekta', system-ui, sans-serif;

  --content-col: clamp(320px, min(40vw, 50vh), 540px);
  --content-col-gallery: clamp(320px, min(50vw, 60vh), 640px);
  --text-2xs: clamp(10.5px, 0.55vw, 12.5px);

  /* Distanza dai bordi degli elementi fissi (indicatore, CTA, footer): scala in modo
     continuo fra 980px e 1980px di larghezza, poi si ferma. Sotto i 980px vale il
     minimo. Vedi docs/convenzioni.md, sezione "Impalcatura responsive". */
  --edge-x: clamp(18px, calc(10px + 0.8vw), 26px);
  --edge-y: clamp(16px, calc(10px + 0.6vw), 22px);

  /* Spazio occupato dagli elementi fissi che stanno sopra il contenuto: il logo e il
     menu in alto, la riga del footer e il pulsante "Guarda il lavoro completo" in
     basso. Le slide della gallery riservano queste due fasce e centrano la foto in
     quello che resta, invece di centrarla nello schermo intero: cosi' non risulta
     schiacciata verso il basso, dove il footer le toglie respiro. Sono le due
     manopole da ritoccare se il bilanciamento non convince. */
  --chrome-top: 56px;
  --chrome-bottom: 64px;

  /* Altezza di una slide. Sotto i 980px diventa 100svh (viewport "piccolo"): su iOS e
     Android 100vh e' calcolato sulla finestra CON le barre ritratte, quindi e' piu' alto
     di quello che si vede davvero e le slide non si allineerebbero mai. Sul desktop non
     cambia niente, li' le due misure coincidono. */
  --slide-h: 100vh;
  /* Riserva verticale di una scheda della home (era 8vh cablato su .series-item). */
  --slide-pad-y: 8vh;

  /* ---------- Velario di apertura (.velario) ----------
     DESKTOP. E' lo STESSO sipario del menu a tutto schermo: stesso clip-path, stessa
     durata (620ms) e stessa curva. Li' il pannello nero entra da destra e il marchio in
     alto diventa bianco sotto il bordo; qui il pannello nero si ritira verso destra
     scoprendo la pagina, e il marchio centrale diventa nero sotto lo stesso bordo. E' il
     medesimo gesto al contrario, di proposito: aprendo il sito e aprendo il menu si deve
     riconoscere la stessa mano.
     ATTENZIONE: --velario-sipario-ms comanda DUE animazioni che devono restare la stessa
     cosa vista da due parti, la corsa del pannello e il riempimento del marchio. Se una
     cambia durata o curva senza l'altra, il confine di colore sulle lettere si stacca dal
     bordo del sipario e l'effetto sparisce. Chi cambia questo numero ne sta cambiando due,
     e sono gli stessi 620ms di .site-nav-overlay: cambiarli qui li scollega dal menu. */
  --velario-sipario-ms: 620ms;
  /* Preambolo corto: il marchio compare e resta fermo un istante prima che parta il
     sipario. Serve solo a non aprire di taglio, non e' una scena a se'. */
  --velario-marchio-ms: 300ms;
  --velario-attesa-ms: 200ms;
  /* Dopo il passaggio del sipario il marchio resta fermo, nero sulla carta, per un paio di
     secondi: non e' un tempo tecnico, e' la scena. In tutta questa pausa la barra di
     caricamento NON c'e' (vedi .home-cover__load), lo schermo e' solo carta e marchio.
     Poi il marchio esce in dissolvenza, e solo a dissolvenza finita compare la barra:
     stanno esattamente nello stesso punto dello schermo, al centro, e sovrapposti
     farebbero confusione invece di effetto. */
  --velario-logo-fermo-ms: 2000ms;
  --velario-logo-uscita-ms: 380ms;
  --velario-marchio-size: clamp(20px, 2.4vw, 38px);
  /* Ritardo con cui il nome della sezione compare SOTTO il marchio, a velario finito:
     prima il marchio, poi lui. Se i due arrivassero insieme sarebbero una cosa sola e il
     "poi" non si leggerebbe. */
  --header-sezione-delay-ms: 340ms;

  /* ---------- Copertina iniziale della home (.home-cover) ----------
     DESKTOP. La copertina non e' a tutto schermo: e' una fotografia dentro una cornice
     centrata. Questi due tetti decidono quanto e' grande la cornice, e quindi anche
     quanto e' lunga la barra di caricamento, che e' larga esattamente quanto lei.
     Sono gli stessi due vincoli della foto grande nelle schede della gallery
     (.series-frame--large): 1600px e' il lato lungo con cui l'admin genera le varianti
     "display", oltre si ingrandirebbe un file che quei pixel non ha. */
  --splash-media-w: min(86vw, 1600px);
  /* Il fattore .82 e' lo stesso della scheda a foto singola della home, cosi' copertina e
     scheda hanno la stessa aria. Nasceva anche da un secondo vincolo, oggi decaduto: sotto
     la cornice doveva restare una fascia di carta per l'indicatore "SCROLL", che sulla
     copertina stava in basso al centro (con .88 gli finiva sopra). Dal 2026-08-25
     l'indicatore su desktop pende dall'hamburger, in alto a destra, e non ha piu' voce in
     capitolo qui; sotto i 980px la vecchia posizione in basso resta, quindi il vincolo
     vale ancora sul mobile. */
  --splash-media-h: calc((var(--slide-h) - var(--chrome-top) - var(--chrome-bottom)) * .95);
  --splash-open-ms: 1100ms;
  /* Pausa fra il sipario aperto e la comparsa dell'indicatore "SCROLL": deve arrivare
     dopo, non stare li' dall'inizio. Il tempo serve a far posare la fotografia prima di
     chiedere all'occhio di guardare altrove. */
  --splash-hint-delay-ms: 900ms;
  /* Pausa a barra piena prima che si apra: e' lo stesso valore di FILL_HOLD_MS nel motore
     delle slide (js/main.js), cosi' il ritmo del "riempimento concluso" e' uno solo in
     tutto il sito. Letto dal JS via getComputedStyle: i tempi stanno qui, non la'. */
  --splash-hold-ms: 320ms;
  /* Dissolvenza fra una fotografia e l'altra, e pausa su ciascuna. */
  --splash-cross-ms: 1200ms;
  --splash-still-ms: 4500ms;
  /* Sfumatura della barra mentre l'apertura cresce. */
  --splash-bar-fade-ms: 300ms;
  /* Il "respiro": la foto entra appena piu' grande e si posa. Mettere 1 lo spegne. */
  --splash-zoom: 1.04;

  /* DESKTOP. Tetto in altezza della fotografia nella scheda a foto singola della home:
     la fascia libera fra logo e footer, ridotta dell'82% per lasciare respiro sopra e
     sotto (stessa logica del fattore .88 di .series-frame--large nella gallery). Il
     valore per il mobile e' un altro, nel blocco in fondo al file. */
  --breath-media-h: calc((var(--slide-h) - var(--chrome-top) - var(--chrome-bottom)) * .82);

  /* DESKTOP. Altezza della scheda di copertina della gallery: e' la manopola del "peek",
     la porzione della fotografia successiva che sporge sotto la copertina e invoglia a
     proseguire. Non e' disegnata da nessuna parte, nasce dalla differenza fra questa
     altezza e la slide seguente (vedi docs/convenzioni.md). 86vh lascia una ventina di
     pixel di fotografia su un viewport alto 900. Portato a 82vh il 2026-08-24, in due
     passaggi, guardando il risultato dal vivo: circa 59px sullo stesso viewport. Ogni punto
     percentuale in meno vale circa 9px di fotografia in piu' su uno schermo alto 900. Il
     valore per il mobile e' un altro, nel blocco in fondo al file: le due versioni si
     regolano separatamente. */
  --cover-h: 82vh;

  /* Altezza della riga di chrome in alto: header e menu si centrano dentro questa,
     invece di appoggiarsi al bordo alto del proprio box. Vedi .site-header. */
  --chrome-row-h: 20px;
  /* Compenso ottico del wordmark: le maiuscole di Aspekta non riempiono tutto il box di
     testo, quindi a parita' di riquadro la scritta risulta piu' bassa e piu' rientrata
     delle barrette del menu. Questi due valori riallineano la FORMA delle lettere ai
     bordi, non il loro riquadro. Misurati sul rendering reale con le metriche del glifo
     (canvas measureText: cap-height 12.44px e bearing sinistro 0.88px su corpo 17px), non
     stimati a occhio: se cambia il font o il font-size del wordmark vanno rimisurati.
     Sono in pixel assoluti e il corpo del testo e' fisso, quindi valgono a ogni
     larghezza. */
  --wordmark-optical-top: -1.5px;
  --wordmark-optical-left: -.9px;

  /* ---------- Geometria dell'icona del menu, in coordinate di viewport ----------
     Servono all'indicatore di scroll, che dal 2026-08-25 pende esattamente da sotto
     l'hamburger: sono le uniche due misure che legano i due elementi, quindi stanno qui
     invece che ripetute a mano nel blocco dell'indicatore.
     Il bordo BASSO delle barrette: --edge-y di padding dell'header, piu' i 3px con cui il
     pulsante alto 14px si centra nella riga di chrome (--chrome-row-h: 20px), piu' i 14px
     del disegno stesso.
     Il CENTRO orizzontale, misurato dal bordo destro della finestra: --edge-x di padding
     piu' meta' dei 24px di larghezza del pulsante.
     Se cambiano le misure di .site-header__menu-btn o --chrome-row-h vanno rifatti i due
     conti, altrimenti l'indicatore si scentra rispetto all'icona. */
  --hamburger-bottom: calc(var(--edge-y) + 3px + 14px);
  --hamburger-center-right: calc(var(--edge-x) + 12px);

  /* ---------- Ingombro dell'indicatore di scroll appeso all'hamburger ----------
     Solo per il desktop: sotto i 980px l'indicatore sta altrove e questi token non
     vengono usati. La lunghezza della barra sta qui e non nella regola perche' entra nel
     conto dell'ingombro complessivo, che serve alla minimappa della gallery per non
     salirgli addosso su schermi bassi.
     --scroll-hint-zone e' la quota a cui finisce il pallino: distanza dall'alto, piu' i
     16px d'aria sotto l'icona, piu' l'etichetta ruotata (48px: "SCROLL" a 10px con
     .14em di spaziatura - misurato, non stimato; se cambia il testo o il font va
     rimisurato), i due spazi da 12px e i 7px del pallino. */
  --scroll-hint-track: clamp(96px, 15vh, 160px);
  --scroll-hint-zone: calc(
    var(--hamburger-bottom) + 16px + 48px + 12px + var(--scroll-hint-track) + 12px + 7px
  );

  /* Quanto si vede l'immagine di anteprima dietro al menu: alzare per renderla piu'
     presente, abbassare per tenerla piu' discreta. */
  --nav-preview-opacity: .3;

  --ease-out-expo: cubic-bezier(.16,1,.3,1);
  --ease-in-out-quint: cubic-bezier(.76,0,.24,1);
}

/* ---------- Reset ---------- */
*{ box-sizing: border-box; }
html,body{ margin: 0; }
body{
  background: var(--color-paper);
  color: var(--color-ink);
  font-family: var(--font-sans);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-anchor: none;
}
/* Sulle pagine a schede lo scorrimento e' gestito dal sito (vedi setupSlideScroll in
   js/main.js): la barra del browser e' fuorviante, e trascinarla scavalcherebbe il
   meccanismo. Sulle pagine a scorrimento normale resta. */
body[data-page="home"], body[data-page="series-detail"]{ scrollbar-width: none; }
body[data-page="home"]::-webkit-scrollbar,
body[data-page="series-detail"]::-webkit-scrollbar{ width: 0; height: 0; }

/* DESKTOP (sopra i 980px): nessuna barra di scorrimento del browser, su nessuna pagina e
   su nessun riquadro che scorre per conto suo (il menu a tutto schermo, per esempio). A
   dire dove si e' arrivati ci pensa l'indicatore appeso all'hamburger, e due indicatori
   della stessa cosa sono uno di troppo.
   Il pannello di amministrazione non e' toccato perche' non carica questo foglio di
   stile: la' le barre servono, i moduli sono lunghi e la barra e' anche una maniglia.
   Sotto i 980px non si cambia niente: mobile e desktop sono due versioni distinte (vedi
   docs/convenzioni.md), e sul telefono la barra e' quella sottile di sistema, che compare
   solo mentre si scorre e non ruba spazio.
   Lo scorrimento resta ovviamente possibile: sparisce il disegno della barra, non
   l'overflow. */
@media (min-width: 981px){
  /* Il selettore universale copre html e body (da cui il viewport prende la sua barra) e
     insieme ogni contenitore interno con overflow proprio, senza doverli elencare. */
  *{ scrollbar-width: none; }
  *::-webkit-scrollbar{ width: 0; height: 0; }
}

a{ color: inherit; text-decoration: none; }
img{ max-width: 100%; }
::selection{ background: var(--color-ink); color: var(--color-paper); }
:focus-visible{ outline: 1.5px solid var(--color-ink); outline-offset: 3px; }

/* Due versioni dello stesso contenuto nella pagina, una sola visibile per larghezza.
   Serve a testi e immagini che hanno una versione corta o diversa per il telefono: l'HTML
   e' lo stesso per ogni dispositivo (viene servito dalla cache), quindi la scelta la fa il
   CSS. Il confine e' quello dichiarato fra le due versioni del sito, 980px. */
.solo-mobile{ display: none; }
@media (max-width: 980px){
  .solo-desktop{ display: none; }
  .solo-mobile{ display: block; }
}

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

.skip-link{
  position: absolute; top: -60px; left: 12px; z-index: 100;
  background: var(--color-ink); color: var(--color-paper);
  padding: 10px 18px; font-family: var(--font-sans); font-size: 13px; letter-spacing: .02em;
  border-radius: 4px; transition: top 160ms ease;
}
.skip-link:focus{ top: 12px; }

/* ---------- Utility: fade-in image box (used by hero, series thumbs, breath image) ---------- */
.fade-box{ opacity: 0; transition: opacity .7s ease; }
.fade-box.is-loaded{ opacity: 1; }

/* ---------- Utility: reveal-on-scroll (used by series detail gallery) ---------- */
.reveal{ opacity: 0; transform: translateY(28px); transition: opacity .9s var(--ease-out-expo), transform .9s var(--ease-out-expo); }
.reveal.is-visible{ opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce){
  .fade-box{ opacity: 1; transition: none; }
  .reveal{ opacity: 1; transform: none; transition: none; }
}

/* ---------- Utility: ingresso a scaletta all'arrivo sulla pagina - i figli
   diretti compaiono con delay crescenti invece che tutti insieme. Usata su
   testo (titoli, paragrafi) e immagini indistintamente: basta aggiungere la
   classe `.stagger-in` al contenitore, i figli non richiedono markup extra. */
.stagger-in > *{
  opacity: 0; transform: translateY(14px);
  transition: opacity 1200ms var(--ease-out-expo), transform 1200ms var(--ease-out-expo);
}
.stagger-in.is-visible > *{ opacity: 1; transform: none; }
.stagger-in > *:nth-child(1){ transition-delay: 80ms; }
.stagger-in > *:nth-child(2){ transition-delay: 230ms; }
.stagger-in > *:nth-child(3){ transition-delay: 380ms; }
.stagger-in > *:nth-child(4){ transition-delay: 530ms; }
.stagger-in > *:nth-child(5){ transition-delay: 680ms; }
.stagger-in > *:nth-child(6){ transition-delay: 830ms; }
.stagger-in > *:nth-child(7){ transition-delay: 980ms; }
.stagger-in > *:nth-child(8){ transition-delay: 1130ms; }
.stagger-in > *:nth-child(n+9){ transition-delay: 1280ms; }

@media (prefers-reduced-motion: reduce){
  .stagger-in > *{ opacity: 1; transform: none; transition: none; }
}

/* =========================================================
   Site header - fixed top bar + hamburger trigger
   ========================================================= */
/* Le distanze dai bordi sono le stesse degli elementi fissi in basso (footer,
   indicatore, CTA): --edge-x/--edge-y, non piu' due numeri cablati. Cosi' il chrome in
   alto e quello in basso restano equidistanti dal bordo a ogni larghezza.
   I due figli sono centrati su una riga di altezza dichiarata (--chrome-row-h) invece di
   appoggiarsi al bordo alto: allineati con `flex-start` a decidere la posizione era il
   riquadro del testo, non le lettere, e il wordmark risultava piu' basso delle barrette
   del menu. */
.site-header{
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  display: flex; justify-content: space-between; align-items: center;
  min-height: calc(var(--chrome-row-h) + var(--edge-y) * 2);
  padding: var(--edge-y) var(--edge-x);
  pointer-events: none;
  font-family: var(--font-sans);
}
.site-header__wordmark{
  font-family: var(--font-wordmark);
  font-size: 17px; letter-spacing: -.01em;
  pointer-events: auto; font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 1px;
  /* line-height: 1 toglie il mezzo-interlinea sopra e sotto le lettere, che e' la parte
     grossa dello scarto; i due compensi ottici recuperano il residuo (cap-height e side
     bearing della prima lettera). Vedi i token in :root. */
  line-height: 1;
  margin-top: var(--wordmark-optical-top);
  margin-left: var(--wordmark-optical-left);
  /* Riempimento a sipario, agganciato alla posizione REALE del bordo
     dell'overlay (non a un timing "a sensazione"): il gradiente e'
     background-attachment:fixed e largo 200vw (il doppio del VIEWPORT, non
     dell'elemento) - cosi' background-position usa le stesse coordinate
     percentuali del clip-path dell'overlay (che e' anch'esso ancorato al
     viewport). Con la stessa durata/easing (620ms, ease-in-out-quint) il
     confine del gradiente attraversa il wordmark esattamente nell'istante
     in cui il bordo nero del sipario ci passa davvero sopra, invece di
     riempirsi a un ritmo indipendente legato solo alla larghezza del testo. */
  background-image: linear-gradient(to right, #000 50%, var(--color-overlay-fg) 50%);
  background-size: 200vw 100%;
  background-attachment: fixed;
  background-position: 0% 0;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  transition: background-position 620ms var(--ease-in-out-quint),
              opacity 420ms ease;
}

/* Sulla home con copertina il marchio in alto arriva per ultimo: compare nell'istante in
   cui la barra tocca il 100% e il sipario scopre le fotografie (`is-cover-open`), non
   prima. Fino a quel momento il marchio dell'apertura sta al centro, e due marchi insieme
   non hanno senso.
   Lo scope e' `data-cover` e non `data-page="home"` di proposito: senza fotografie di
   copertina non esiste nessun secondo marchio, e nascondere questo lascerebbe la pagina
   senza nessun marchio. */
body[data-cover] .site-header__wordmark{
  opacity: 0;
  pointer-events: none;
}
html.is-cover-open body[data-cover] .site-header__wordmark{
  opacity: 1;
  pointer-events: auto;
}
/* Eccezione: col menu aperto il marchio serve comunque, ed e' anzi il protagonista del
   sipario del menu (diventa bianco sotto il bordo del pannello). Senza questa riga,
   aprendo il menu durante l'apertura, in alto a sinistra non ci sarebbe niente. */
body[data-cover] .site-header.is-open .site-header__wordmark{
  opacity: 1;
  pointer-events: auto;
}
/* ---------- Marchio piu' nome della sezione (pagine categoria) ----------
   Il contenitore esiste solo per dare un riferimento al titolo, che e' fuori dal flusso:
   in flusso aggiungerebbe una riga all'header e il marchio non sarebbe piu' allineato
   alle barrette del menu, che e' un allineamento misurato (vedi i token ottici in :root).
   `display: flex` e `line-height: 1` tengono l'altezza del blocco identica a quella che
   aveva il solo marchio: 17px, il suo corpo. */
.site-header__brand{
  position: relative;
  display: flex; flex-direction: column; align-items: flex-start;
  line-height: 1;
}
/* Stesso idioma delle etichettine del sito (l'indicatore "SCROLL", il "FINE ANTEPRIMA"
   della gallery): sans, 10px, maiuscoletto spaziato. Sotto un marchio nero e pesante
   serve un contrappunto leggero, non un secondo titolo. */
.site-header__section{
  position: absolute; top: 100%; left: 0;
  margin-top: 7px;
  font-family: var(--font-sans); font-size: 10px; font-weight: 400;
  letter-spacing: .14em; text-transform: uppercase; white-space: nowrap;
  color: var(--color-gray);
  transition: opacity 420ms ease var(--header-sezione-delay-ms);
}
body.is-dark-slide .site-header__section{ color: var(--color-overlay-fg); }

.site-header__menu{ position: relative; pointer-events: auto; display: flex; }
/* Il disegno resta 24x14 con le barre a filo del margine, ma il bersaglio del dito e'
   44x44 (la misura minima raccomandata: prima era 24x14, troppo piccolo su un telefono).
   Il padding allarga l'area sensibile, il margine negativo di pari valore la riassorbe
   nel layout, cosi' la posizione delle barrette sullo schermo non cambia di un pixel.
   Serve box-sizing: content-box perche' il reset globale mette border-box, che invece
   mangerebbe il padding dall'interno dei 24x14. */
.site-header__menu-btn{
  position: relative; z-index: 2;
  box-sizing: content-box;
  width: 24px; height: 14px;
  display: flex; flex-direction: column; justify-content: center; align-items: stretch; gap: 5px;
  border: 0; background: none; cursor: pointer;
  padding: 15px 10px; margin: -15px -10px;
  -webkit-tap-highlight-color: transparent;
}
.site-header__menu-bar{
  width: 100%; height: 1px; min-height: 1px; flex: 0 0 1px;
  background: var(--color-ink);
  transition: transform 400ms var(--ease-out-expo), background 400ms ease, opacity 200ms ease;
  transform-origin: center;
}
/* menu open state - driven by .is-open on the header */
.site-header.is-open .site-header__wordmark{ background-position: 100% 0; }

/* Slide finale scura della gallery: marchio e barrette passano al bianco, altrimenti sul
   fondo nero sparirebbero. Qui NON si usa il riempimento a sipario del menu: quello e'
   un movimento orizzontale, agganciato al bordo dell'overlay che entra da destra, mentre
   qui il movimento e' lo scorrimento verticale fra una slide e l'altra. Una dissolvenza
   semplice non suggerisce una direzione sbagliata. Il colore pieno prevale sul gradiente
   ritagliato sul testo, che resta intatto per il menu. */
body.is-dark-slide .site-header__wordmark{
  color: var(--color-overlay-fg);
  -webkit-text-fill-color: var(--color-overlay-fg);
  transition: color 420ms ease, -webkit-text-fill-color 420ms ease;
}
body.is-dark-slide .site-header__menu-bar{ background: var(--color-overlay-fg); }
.site-header.is-open .site-header__menu-bar{ background: var(--color-overlay-fg); }
.site-header.is-open .site-header__menu-bar--top{ transform: translateY(6px) rotate(45deg); }
.site-header.is-open .site-header__menu-bar--mid{ opacity: 0; }
.site-header.is-open .site-header__menu-bar--bottom{ transform: translateY(-6px) rotate(-45deg); }

/* ---------- Fullscreen nav overlay ---------- */
.site-nav-overlay{
  position: fixed; inset: 0; z-index: 55;
  background: var(--color-nav-bg); color: var(--color-overlay-fg);
  display: flex; align-items: flex-start; justify-content: center;
  overflow-y: auto;
  font-family: var(--font-serif);
  clip-path: inset(0 0 0 100%);
  transition: clip-path 620ms var(--ease-in-out-quint);
  pointer-events: none;
  will-change: clip-path;
}
.site-nav-overlay.is-open{ clip-path: inset(0 0 0 0); pointer-events: auto; }
.site-nav-overlay__inner{
  position: relative; z-index: 1;
  width: 100%; max-width: 1600px; box-sizing: border-box;
  padding: 6vh clamp(28px,6vw,90px);
  margin: auto 0;
  display: grid; grid-template-columns: 1fr; justify-items: end; align-items: start;
  opacity: 0; transition: opacity 300ms ease;
}
.site-nav-overlay.is-open .site-nav-overlay__inner{ opacity: 1; transition-delay: 180ms; }

/* ---------- Anteprima immagine a sinistra: si accende all'hover su una voce del sottomenu -----------
   Colonna sinistra della griglia: la voce di menu, in colonna centrale, resta sempre
   visivamente centrata sullo schermo indipendentemente dalla presenza/larghezza
   dell'immagine. `minmax(0,1fr)` (invece di un semplice `1fr`) sulle due colonne
   laterali impedisce alla larghezza naturale dell'immagine di "spingere" la colonna
   sinistra piu' larga di quella destra (vuota) - senza, la colonna centrale si
   sposterebbe leggermente a destra invece di restare davvero centrata. */
/* L'anteprima non e' piu' una miniatura nella colonna sinistra: e' uno strato a tutta
   altezza dietro al menu, con opacita' bassa, ancorato al bordo sinistro e in
   proporzioni reali. Essendo fuori dal flusso della griglia non viene piu' schiacciata
   dalla colonna centrale alle larghezze intermedie. Il funzionamento dell'hover resta
   identico: il JS continua ad agire sulle stesse classi (is-visible, is-loaded).
   --nav-preview-opacity e' la manopola per regolare quanto si vede. */
.nav-preview{
  position: absolute; top: 0; bottom: 0; left: 0; width: 50%; z-index: 0;
  pointer-events: none; overflow: hidden;
  opacity: 0; transition: opacity 360ms ease;
}
.nav-preview.is-visible{ opacity: var(--nav-preview-opacity, .3); }
/* L'immagine riempie sempre l'intera meta' schermo, qualunque siano le sue proporzioni:
   `cover` la scala fino a coprire il riquadro e ritaglia l'eccedenza, `center` tiene il
   ritaglio simmetrico invece di sacrificare solo un lato. */
.nav-preview__img{
  display: block; width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  opacity: 0; transition: opacity 360ms ease;
}
.nav-preview__img.is-loaded{ opacity: 1; }

@media (max-width: 1080px){
  .nav-preview{ display: none; }
}

/* ---------- Nav menu: plain links + per-category gallery accordion ----------
   Il menu e' allineato a destra (justify-items: end sulla griglia del contenitore) e
   rimpicciolisce con la larghezza: a schermo pieno resta alla misura massima, sui
   laptop e sui tablet si stringe progressivamente invece di restare della stessa
   dimensione fino a un salto brusco. */
.nav-menu__item{margin-bottom:20px}
.nav-menu__panel-body:last-child{margin-bottom:20px}
.nav-menu-col{ grid-column: 1; min-width: 0; width: fit-content; max-width: min(620px, 78vw); text-align: right; }
.nav-menu{ list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column;}
.nav-menu__link{
  display: inline-block; font-family: var(--font-serif); font-size: clamp(24px,2.6vw,38px); font-weight: 300;
  letter-spacing: -.005em; color: var(--color-overlay-fg);
  transition: color 220ms ease;
}
/* Un `a:visited` del browser (elemento + pseudoclasse) batte in specificita' un
   nostro selettore a una classe sola: senza questa riga, dopo il primo click il link
   tornava al colore di default (quasi nero) invece di restare chiaro come richiede lo
   sfondo scuro dell'overlay. Va PRIMA di :hover (ordine LVHA) o l'hover su un link
   gia' visitato smetterebbe di funzionare. */
.nav-menu__link:visited{ color: var(--color-overlay-fg); }
.nav-menu__link:hover{ color: var(--color-gray); }

/* ---------- Selettore di lingua nell'header ----------
   Sta accanto alle barrette del menu e deve pesare meno di loro: e' un interruttore, non
   una voce di navigazione. Stesso corpo delle etichette piccole del sito, colore grigio,
   e la lingua attiva marcata solo dal colore pieno invece che da un riquadro.
   Eredita da .site-header il colore invertito sulle schede scure (body.is-dark-slide),
   quindi non serve una regola in piu' per la copertina a tutto schermo. */
.site-header__lingue{
  display: flex; align-items: center; gap: 5px;
  /* SERVE: .site-header ha pointer-events:none perche' e' una fascia trasparente che
     non deve intercettare i clic sul contenuto sotto. Ogni elemento cliccabile
     dell'header deve riattivarli per conto suo, come gia' fanno il marchio e il menu.
     Senza questa riga il selettore si vede ma non si clicca. */
  pointer-events: auto;
  /* `margin-left: auto` e non un terzo posto nello space-between: l'header ha
     justify-content: space-between e con tre figli il selettore finirebbe al centro,
     staccato dal menu a cui appartiene. Cosi' assorbe lo spazio libero e resta
     appaiato alle barrette. */
  margin-left: auto; margin-right: clamp(14px, 2vw, 26px);
  font-size: var(--text-2xs); letter-spacing: .08em;
}
.site-header__lingua{ color: var(--color-gray); transition: color 220ms ease; }
.site-header__lingua:visited{ color: var(--color-gray); }
.site-header__lingua:hover{ color: currentColor; }
.site-header__lingua.is-attiva{ color: inherit; }
.site-header__lingue-sep{ color: var(--color-filet); }
body.is-dark-slide .site-header__lingua.is-attiva{ color: var(--color-overlay-fg); }

/* Contenitore di solo layout (non piu' un <button>): il testo e la croce sono due
   elementi cliccabili distinti, vedi sotto. */
.nav-menu__toggle{
  display: flex; align-items: center; justify-content: flex-end; gap: 14px; width: 100%;
  text-align: right;margin-bottom:20px
}
.nav-menu__toggle-label{
  display: inline-block; font-family: var(--font-serif); font-size: clamp(24px,2.6vw,38px); font-weight: 300;
  color: var(--color-paper); transition: color 220ms ease;
}
.nav-menu__toggle-label:visited{ color: var(--color-paper); }
.nav-menu__toggle-label:hover{ color: var(--color-gray); }

/* Pulsante separato, solo per aprire/chiudere la tendina (cliccare sul testo porta
   invece alla pagina della categoria, e' un <a> qui sopra). Il padding allarga l'area
   cliccabile oltre le dimensioni visive della croce, il margine negativo lo stesso
   valore compensa cosi' la croce resta dove appare visivamente, non spostata dal
   padding in piu'. */
.nav-menu__toggle-btn{
  flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
  background: none; border: 0; padding: 20px 10px 10px 10px; margin: -10px; cursor: pointer;
}

/* Croce sottile disegnata con due pseudo-elementi invece del "+" cerchiato: niente
   glifo di font da allineare al centro (il vecchio bordo rotondo ballava rispetto alla
   riga di base del testo a seconda del font-size fluido). Il riquadro dell'icona e'
   centrato dal flex del bottone, quindi le due barrette sono sempre esattamente al
   centro della propria altezza, non della riga di testo. In apertura la barretta
   verticale ruota di 90 gradi e combacia esattamente su quella orizzontale (stessa
   lunghezza e spessore): diventa un "meno" senza bisogno di un secondo stato disegnato
   a parte. */
.nav-menu__toggle-icon{
  flex: 0 0 auto; position: relative;
  width: clamp(15px,1.3vw,19px); height: clamp(15px,1.3vw,19px);
}
.nav-menu__toggle-icon::before,
.nav-menu__toggle-icon::after{
  content: ""; position: absolute; top: 50%; left: 50%;
  background: var(--color-overlay-fg);
  transition: transform 320ms var(--ease-out-expo), background 220ms ease;
}
.nav-menu__toggle-icon::before{ width: 100%; height: 1px; transform: translate(-50%,-50%); }
.nav-menu__toggle-icon::after{ width: 1px; height: 100%; transform: translate(-50%,-50%); }
.nav-menu__toggle-btn:hover .nav-menu__toggle-icon::before,
.nav-menu__toggle-btn:hover .nav-menu__toggle-icon::after{ background: var(--color-gray); }
.nav-menu__toggle-btn[aria-expanded="true"] .nav-menu__toggle-icon::after{ transform: translate(-50%,-50%) rotate(90deg); }

/* Margine destro a zero: il pannello arriva a filo del bordo destro della colonna, che
   e' dove finisce l'etichetta della voce (la riga e' impacchettata a destra). Cosi' i
   riquadri delle gallery risultano allineati esattamente sotto l'ultima lettera del
   titolo. Il rientro che segnala la gerarchia sta sul lato sinistro. */
.nav-menu__panel{
  max-height: 0; overflow: hidden;
  transition: max-height 520ms ease;
}
.nav-menu__panel[hidden]{ display: none; }

.nav-menu__panel-body{
  display: flex; flex-direction: column; gap: 10px;
  opacity: 0;
  transition: opacity 520ms ease;
}
.nav-menu__panel.is-open .nav-menu__panel-body{ opacity: 1; }

.nav-menu__gallery-link{
  display: block; font-family: var(--font-serif); font-size: clamp(16px,1.5vw,20px); font-weight: 300;
  color: var(--color-gray);
  padding: 12px clamp(18px,2vw,24px);
  border: 1px solid #3a3a36; border-radius: 6px;
  transition: color 220ms ease, border-color 220ms ease, background-color 220ms ease;
}
.nav-menu__gallery-link:visited{ color: var(--color-gray); }
.nav-menu__gallery-link:hover{
  color: var(--color-overlay-fg);
  border-color: var(--color-overlay-fg);
  background: rgba(252,252,251,.06);
}

@media (prefers-reduced-motion: reduce){
  .nav-menu__panel{ transition: none; max-height: none; }
  .nav-menu__panel-body{ transition: none; opacity: 1; }
}

/* ---------- Menu a scaletta: le voci salgono sfalsate dopo il wipe ---------- */
.nav-menu > li{
  opacity: 0; transform: translateY(22px);
  transition: opacity 560ms var(--ease-out-expo), transform 560ms var(--ease-out-expo);
  transition-delay: calc(300ms + var(--i, 0) * 50ms);
}
.site-nav-overlay.is-open .nav-menu > li{ opacity: 1; transform: none; }

@media (max-width: 640px){
  .nav-menu__panel{ margin-left: 24px; }
}

@media (prefers-reduced-motion: reduce){
  .nav-menu > li{ opacity: 1; transform: none; transition: none; }
}

/* =========================================================
   Site footer
   ========================================================= */
.site-footer{
  font-family: var(--font-sans);
  position: fixed; left: 0; right: 0; bottom: var(--edge-y); z-index: 40;
  display: flex; justify-content: center; pointer-events: none;
}
.site-footer__inner{
  display: flex; align-items: baseline; flex-wrap: wrap; justify-content: center; gap: 10px;
  pointer-events: auto;
  opacity: 0; transform: translateY(10px);
  text-transform:uppercase;
  letter-spacing:1px;
  transition: opacity 700ms var(--ease-out-expo), transform 700ms var(--ease-out-expo);
}
.site-footer__inner.is-visible{ opacity: 0.7; transform: none; }
.site-footer__inner > *:not(:first-child)::before{ content: "·"; margin-right: 10px; color: var(--color-filet); }
.site-footer__legal{ font-size: 12px; letter-spacing: .01em; color: var(--color-ink); white-space: nowrap; }
.site-footer__link{ font-size: 12px; letter-spacing: .01em; color: var(--color-ink); white-space: nowrap; transition: color 200ms ease; }
.site-footer__link:hover{ color: var(--color-gray); }

@media (prefers-reduced-motion: reduce){
  .site-footer__inner{ opacity: 1; transform: none; transition: none; }
}

/* =========================================================
   Home - hero / cover section
   ========================================================= */
/* =========================================================
   Velario di apertura della home. E' lo stesso sipario del menu a tutto schermo
   (.site-nav-overlay), rovesciato: li' il pannello nero ENTRA da destra e copre la
   pagina, qui SI RITIRA verso destra e la scopre. Stesso clip-path, stessa durata, stessa
   curva.

   COME FA IL MARCHIO A CAMBIARE COLORE ESATTAMENTE SOTTO IL BORDO DEL SIPARIO.
   E' l'idioma gia' usato da .site-header__wordmark quando si apre il menu (vedi il
   commento lungo li', e docs/convenzioni.md). Il marchio e' UN testo solo, non due copie
   sovrapposte: due copie si tradirebbero al primo pixel di disallineamento. Il testo e'
   riempito da un gradiente a due colori netti largo 200vw, con
   `background-attachment: fixed`, ritagliato sulle lettere con `background-clip: text`.
   Essendo ancorato al VIEWPORT e largo il doppio, la percentuale di background-position
   produce uno scostamento negativo: a 100% il confine dei colori sta a x=0, a 0% sta a
   x=100vw. E' lo stesso tragitto del bordo del pannello, quindi il marchio parte da 100%
   (bianco, sul nero) e arriva a 0% (nero, sulla carta).

   TRAPPOLA: il marchio NON va centrato con `transform: translate(-50%, -50%)`. Un
   elemento trasformato rende relativo a se stesso il proprio background fisso, il
   gradiente perde l'aggancio al viewport e il confine smette di seguire il sipario. Si
   centra col flex del contenitore, ed e' il motivo per cui .velario e' un flex.

   .velario non ha sfondo: il nero e' tutto nel pannello. Cosi' quando il pannello si
   ritira si vede la pagina vera sotto, non un fondo intermedio.
   ========================================================= */
.velario{
  position: fixed; inset: 0;
  /* Sopra tutto: .site-header sta a 60 e .site-nav-overlay a 55. */
  z-index: 70;
  display: none;
  align-items: center; justify-content: center;
  pointer-events: none;
}
html.is-velario .velario{ display: flex; }

/* Identico a .site-nav-overlay, percorso al contrario. Le quattro misure sono scritte per
   esteso in entrambi gli stati: due inset() interpolano solo se hanno la stessa forma. */
.velario__pannello{
  position: absolute; inset: 0;
  background: var(--color-nav-bg);
  clip-path: inset(0px 0px 0px 0px);
  transition: clip-path var(--velario-sipario-ms) var(--ease-in-out-quint);
  will-change: clip-path;
}
html.is-velario-sipario .velario__pannello{ clip-path: inset(0px 0px 0px 100%); }

/* `position: relative` senza z-index basta a tenerlo sopra il pannello (fra elementi
   posizionati decide l'ordine nel DOM) e, a differenza di transform, non tocca
   l'aggancio al viewport del background. */
.velario__marchio{
  position: relative;
  font-family: var(--font-wordmark);
  font-weight: 900; text-transform: uppercase;
  font-size: var(--velario-marchio-size);
  letter-spacing: 1px; line-height: 1;
  background-image: linear-gradient(to right, #000 50%, var(--color-overlay-fg) 50%);
  background-size: 200vw 100%;
  background-attachment: fixed;
  background-position: 100% 0;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  opacity: 0;
  transition: opacity var(--velario-marchio-ms) ease,
              background-position var(--velario-sipario-ms) var(--ease-in-out-quint);
}
/* Il marchio del sito e' una parola sola e non va mai a capo; il nome di una sezione puo'
   essere lungo ("Fotografie di viaggio"), quindi qui puo' andare a capo invece di uscire
   dallo schermo. Il riempimento a sipario funziona uguale su due righe: il gradiente e'
   ancorato al viewport, non al testo. */
.velario__marchio--sezione{
  max-width: calc(100vw - var(--edge-x) * 4);
  text-align: center;
  line-height: 1.12;
  text-wrap: balance;
}

html.is-velario-marchio .velario__marchio{ opacity: 1; }
html.is-velario-sipario .velario__marchio{ background-position: 0% 0; }
html.is-velario-via .velario__marchio{
  opacity: 0;
  transition: opacity var(--velario-logo-uscita-ms) ease;
}

/* Blocco dello scorrimento finche' il velario e' su. Questo da solo NON basta: il motore
   delle schede in js/main.js annulla gli eventi e scorre da se', quindi la guardia vera
   e' dentro advance() e slideBy().
   Nota per chi si chiedesse se serva `scrollbar-gutter: stable`: no. Nascondere l'overflow
   di solito fa sparire la barra di scorrimento e sposta il contenuto centrato, ma su
   questa pagina la barra non c'e' gia' (piu' su: `body[data-page="home"] {
   scrollbar-width: none }`), quindi non c'e' nessuna larghezza che cambia. */
html.is-velario, html.is-velario body{ overflow: hidden; }

/* ---------- Copertina iniziale: sipario sopra-sotto su una fotografia ----------
   Prima scheda della home. NON e' a tutto schermo: la fotografia sta dentro una cornice
   centrata, e la barra di caricamento e' larga esattamente quanto la cornice.
   La fotografia e' gia' li', ferma e a piena cornice: quello che si apre e' la FINESTRA
   che la inquadra, disegnata da clip-path. Parte da una fessura alta un pixel al centro e
   cresce verso l'alto e verso il basso, come un sipario. I due lati restano fermi a zero
   per tutta l'animazione, perche' barra e cornice hanno gia' la stessa larghezza: e' il
   motivo per cui l'apertura si legge come sopra-sotto e non come un rettangolo che cresce
   in tutte le direzioni.
   PERCHE' clip-path E NON transform: scaleY. Scalando un contenitore si scala anche la
   fotografia dentro, che comparirebbe schiacciata e si "sbloccherebbe" di colpo a fine
   animazione. Con clip-path la foto non si muove di un pixel e cambia solo il ritaglio.
   Ed e' composto dalla GPU, quindi resta fluido.
   La classe `is-cover-open` sta su <html>, non sul body, perche' viene messa da uno
   script inline nel template PRIMA del primo disegno quando la copertina e' gia' stata
   vista in questa sessione: essendo gia' presente al primo calcolo dello stile, la
   transizione non parte affatto e non si vede nessuna animazione al ritorno. */
.home-cover{
  position: relative;
  min-height: var(--slide-h);
  background: var(--color-paper);
  display: flex; align-items: center; justify-content: center;
  padding: var(--chrome-top) var(--edge-x) var(--chrome-bottom);
  box-sizing: border-box;
}

/* La cornice: proporzione dalla PRIMA fotografia (--splash-ar, dichiarata inline dal
   template perche' e' un dato del contenuto), larghezza limitata dai due tetti. La forma
   `min(tetto-larghezza, tetto-altezza * proporzione)` fa si' che sia sempre il vincolo
   piu' stretto a comandare, senza dover distinguere schermi larghi e schermi bassi.
   La copertina desktop usa solo fotografie orizzontali (vedi templates/home.php): con
   formati misti la barra cambierebbe larghezza a ogni scatto della rotazione. Sotto i
   640px c'e' il secondo elenco, verticale, con la sua proporzione: vedi in fondo a questa
   sezione. */
.home-cover__frame{
  position: relative;
  aspect-ratio: var(--splash-ar, 3 / 2);
  width: min(var(--splash-media-w), calc(var(--splash-media-h) * (var(--splash-ar, 3 / 2))));
  max-width: 100%;
}

.home-cover__media{
  position: absolute; inset: 0;
  overflow: hidden;
  /* Fessura iniziale: alta 1px esatto al centro (0.5px sopra e sotto la meta'), larga
     quanto tutta la cornice. Le quattro misure sono scritte per esteso in entrambi gli
     stati, aperto compreso: due inset() interpolano solo se hanno la stessa forma. */
  clip-path: inset(calc(50% - 0.5px) 0px calc(50% - 0.5px) 0px);
  /* Invisibile fino all'apertura. La fessura chiusa e' alta 1px ma si vede benissimo: e'
     una riga di FOTOGRAFIA larga quanto la cornice. Tenuta accesa durante il caricamento
     si intravedeva sotto la parte non ancora riempita della barra, e sporcava di colore
     una riga che deve essere di due tinte piatte e basta (a 1 pixel di altezza su schermo
     non retina barra e fessura cadono a meta' pixel e si mescolano).
     Niente transizione sull'opacita': la fotografia non deve comparire in dissolvenza, si
     accende nell'istante in cui il sipario comincia ad aprirsi, cosi' l'unico movimento
     che si vede e' l'apertura. */
  opacity: 0;
  transition: clip-path var(--splash-open-ms) var(--ease-in-out-quint);
  will-change: clip-path;
}
html.is-cover-open .home-cover__media{ opacity: 1; }
html.is-cover-open .home-cover__media{ clip-path: inset(0px 0px 0px 0px); }

.home-cover__img{
   border:3px solid #000;
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  background: var(--color-placeholder-bg);
  opacity: 0;
  transform: scale(var(--splash-zoom));
  transition: opacity var(--splash-cross-ms) var(--ease-out-expo),
              transform var(--splash-open-ms) var(--ease-out-expo);
}
.home-cover__img.is-current{ opacity: 1; }
html.is-cover-open .home-cover__img{ transform: scale(1); }

/* ---------- Copertina a tutto schermo (da 981px in su) ----------
   La cornice centrata lascia il posto a una fotografia a bordo pagina: stessa apertura a
   sipario di sempre (il clip-path sopra non cambia, e' tutto in percentuale sulla forma
   della cornice), cambia solo quanto spazio la cornice occupa. `aspect-ratio: auto` annulla
   quella della regola base (che viene dalla proporzione della prima fotografia, qui non
   piu' rilevante: object-fit:cover ritaglia qualunque foto sulla forma della scheda).
   Il colore dell'header sopra si inverte finche' la copertina e' a schermo e torna normale
   allo scroll: vedi setupHomeCover() in js/main.js, che riusa la classe body.is-dark-slide
   gia' esistente per la slide finale scura della gallery (era gia' stata usata qui in
   passato, tolta quando la copertina e' diventata una cornice su carta - la si rimette
   apposta per questo esperimento). L'indicatore SCROLL non ha bisogno di nulla in piu': usa
   gia' mix-blend-mode:difference, resta leggibile sia su carta sia sopra una fotografia.
   Nato come esperimento solo desktop il 2026-08-26. Non lo e' piu': dal 2026-08-31 il
   tutto schermo vale a ogni larghezza, le stesse dichiarazioni sono ripetute nel blocco
   `@media (max-width: 980px)` piu' sotto. Se si tocca questo, si tocca anche quello. */
@media (min-width: 981px){
  .home-cover{ padding: 0; }
  .home-cover__frame{
    aspect-ratio: auto;
    width: 100%;
    height: var(--slide-h);
    max-width: none;
  }
  .home-cover__img{ border: 0; }
}

/* ---------- Due fotografie affiancate, solo su schermi davvero ultra-wide ----------
   Nato per il tetto dei 1980px (vedi convenzioni.md: sopra quella larghezza una sola
   fotografia distesa su tutto lo schermo supererebbe i 1600px della variante 'display' e
   si sgranerebbe), ma legato alla sola larghezza scattava anche su schermi larghi con
   proporzione normale (2560x1440, 16:9): li' due pannelli quasi quadrati avrebbero tagliato
   pesantemente le foto orizzontali, un difetto peggiore di quello che risolvevano
   (segnalato dall'utente il 2026-08-26). Serve quindi anche una proporzione ultra-wide
   (21:9, cioe' 2.33, i monitor tipo 2560x1080/3440x1440/5120x1440), non solo la larghezza:
   un 2560x1440 (rapporto 1.78) resta a una sola fotografia, un vero ultrawide la sdoppia.
   Foto attualmente ancora al tetto di 1600px anche qui: l'utente ha gia' in programma di
   ricaricarle a risoluzione piu' alta per questo caso, non e' un problema da risolvere ora.
   `.home-cover__media--b` esiste nel markup solo per questo caso (secondo elenco di
   immagini in templates/home.php): fuori da questa condizione resta nascosto, ed essendo
   le sue immagini `loading="lazy"` il browser non le scarica affatto.
   Le due meta' ruotano in modo indipendente e sfalsato, sia nella foto di partenza sia nel
   tempo (vedi startRotationB() in js/main.js): non mostrano mai la stessa fotografia
   insieme, e non cambiano mai nello stesso istante. */
.home-cover__media--b{ display: none; }
@media (min-width: 1981px) and (min-aspect-ratio: 21 / 9){
  .home-cover__frame{ display: flex; }
  .home-cover__media{ position: relative; inset: auto; width: 50%; height: 100%; flex: 0 0 50%; }
  .home-cover__media--b{ display: block; }
}

/* ---------- Le due copertine: orizzontali sopra i 640px, verticali sotto ----------
   Dal 2026-08-25. Nella pagina ci sono tutti e due gli elenchi (l'HTML e' uno solo per
   ogni dispositivo, arriva dalla cache) e a scegliere e' qui. Le classi di variante ci
   sono solo quando gli elenchi sono davvero due: con uno solo le fotografie non le
   portano e restano visibili a ogni larghezza.
   `display: none` non e' un ripiego: le immagini nascoste sono `loading="lazy"`, e una
   immagine pigra dentro un elemento non visualizzato il browser non la scarica. Il
   telefono quindi non paga le fotografie del desktop, ne' viceversa. */
.home-cover__img--mobile{ display: none; }

/* A 640px cambia SOLO quale elenco di fotografie si vede: sopra le orizzontali, sotto le
   verticali. Non e' piu' anche il punto in cui la copertina diventa a tutto schermo:
   quello vale ovunque, vedi il blocco subito sotto. */
@media (max-width: 640px){
  .home-cover__img--desktop{ display: none; }
  .home-cover__img--mobile{ display: block; }
}

/* ---------- Copertina a tutto schermo anche sotto i 981px ----------
   Stesse identiche dichiarazioni del blocco desktop qui sopra. Sono qui e non nel blocco
   mobile in fondo al file perche' vanno lette accanto a quelle: sono la stessa regola, e
   separarle e' il modo in cui il difetto e' nato.
   COSA ERA SUCCESSO (segnalato dall'utente il 2026-08-31). Il tutto schermo era stato
   dichiarato due volte in due momenti diversi: sopra i 981px il 2026-08-26 come
   esperimento desktop, sotto i 640px lo stesso giorno per la variante verticale del
   telefono. Nessuno dei due copriva la fascia in mezzo, e fra 641px e 980px - tablet in
   verticale, finestre di computer non a tutto schermo - restava in piedi la vecchia
   copertina incorniciata su carta, che nel frattempo non esisteva piu' da nessun'altra
   parte. Non era una scelta per quelle larghezze: era il buco fra due intervalli che non
   si toccavano.
   Adesso il tutto schermo vale a ogni larghezza. Il 640 resta dov'era e continua a fare
   il suo mestiere, che e' un altro: scegliere fra fotografie orizzontali e verticali.
   Nota: cosi' la cornice su carta non si vede piu' a nessuna larghezza, e con lei
   restano inutilizzati --splash-media-w e --splash-media-h (dichiarati in :root e
   ridichiarati nel blocco mobile). Non li tolgo insieme a una correzione: annotato in
   docs/da-fare.md. */
@media (max-width: 980px){
  .home-cover{ padding: 0; }
  .home-cover__frame{
    aspect-ratio: auto;
    width: 100%;
    height: var(--slide-h);
    max-width: none;
  }
  .home-cover__img{ border: 0; }
}

/* ---------- Il gruppo del caricamento: percentuale + barra ----------
   Sta sopra la cornice, centrato sulla stessa riga da cui si aprira' il sipario. La
   percentuale e' allineata a destra perche' e' li' che la barra sta andando: il numero
   segna la meta, non fa da titolo. */
/* NIENTE `transform` qui dentro, ne' sul gruppo ne' sul riempimento: e' la causa del
   tremolio segnalato dall'utente. La barra e' alta un pixel e cade a meta' della cornice,
   cioe' quasi sempre su mezzo pixel; dentro un elemento trasformato diventa un livello
   composto a se', e siccome il riempimento cambiava a ogni fotogramma quel livello veniva
   ri-rasterizzato in continuazione, con l'antialiasing della riga che cambiava ogni volta.
   Il risultato era una riga che pulsava invece di riempirsi.
   Percio': posizione calcolata (`top: calc(50% - 0.5px)`) invece di
   `top: 50% + translateY(-50%)`, e larghezza in percentuale invece di `scaleX`. */
.home-cover__load{
  position: absolute; left: 0; right: 0;
  top: calc(50% - 0.5px);
  height: 1px;
  /* Nascosta finche' non parte davvero. Prima era visibile fin dal primo disegno: durante
     tutta l'apertura si intravedevano la riga e uno "0%" fermi sotto il marchio del
     velario, nello stesso punto dello schermo. La classe la mette startBar() in
     js/main.js, nell'istante in cui la barra comincia a correre.
     `:not(.is-cover-open)` invece di una regola separata dopo: le due avrebbero la stessa
     specificita' e a decidere sarebbe l'ordine nel file, che e' fragile. */
  opacity: 0;
  transition: opacity var(--splash-bar-fade-ms) ease;
}
html.is-cover-barra:not(.is-cover-open) .home-cover__load{ opacity: 1; }
.home-cover__pct{
  position: absolute; right: 0; bottom: 11px;
  font-family: var(--font-sans); font-size: 11px; font-weight: 400;
  letter-spacing: .12em; color: var(--color-ink);
  /* Cifre a larghezza fissa: senza, il numero balla da sinistra a destra a ogni
     passaggio da una a due a tre cifre, e a 60 fotogrammi al secondo si vede. */
  font-variant-numeric: tabular-nums;
}
/* La barra: stesso identico idioma di .scroll-hint__fill (una sola --fill da 0 a 1
   applicata come scala), ruotato in orizzontale. Il sito ha gia' un linguaggio "barra
   che si riempie" e la copertina ne e' la versione grande: se si cambia l'uno va
   guardato anche l'altro. Alta un pixel come la fessura da cui nasce l'apertura. */
/* Due colori piatti e basta: il percorso ancora da fare in --color-filet, quello fatto in
   nero pieno, lo stesso nero del marchio che ha appena lasciato lo schermo. */
.home-cover__bar{
  position: relative; display: block;
  width: 100%; height: 100%;
  background: var(--color-filet);
  overflow: hidden;
}
.home-cover__bar-fill{
  position: absolute; top: 0; bottom: 0; left: 0;
  /* La stessa --fill da 0 a 1 usata da .scroll-hint__fill, ma applicata alla LARGHEZZA e
     non a una scala: vedi il commento sopra .home-cover__load per il perche'.
     E nessuna transizione: qui --fill e' gia' aggiornata a ogni fotogramma dal JS, e una
     transizione sopra non ammorbidisce, insegue. Ogni fotogramma ne faceva ripartire una
     nuova verso un bersaglio nuovo, ed era la seconda meta' del tremolio. */
  width: calc(var(--fill, 0) * 100%);
  background: #000;
}

/* ---------- L'indicatore "SCROLL" sulla copertina ----------
   Non c'e' dall'inizio: compare a sipario aperto, dopo una pausa (--splash-hint-delay-ms),
   perche' prima non c'e' ancora niente da scorrere e perche' comparirebbe sopra la carta.
   La classe `is-cover-shown` arriva dal JS proprio a quel punto.
   `data-cover` sul body dice che la copertina esiste davvero, cosi' una home senza
   fotografie di copertina (o qualsiasi altra pagina) non viene toccata. */
html:not(.is-cover-shown) body[data-cover] .scroll-hint{ opacity: 0; }

@media (prefers-reduced-motion: reduce){
  /* L'opacita' non serve ridichiararla: qui `is-cover-open` viene messa comunque (la
     copertina parte gia' aperta), ed e' quella a rendere visibile la fotografia. */
  .home-cover__media{ clip-path: inset(0px 0px 0px 0px); transition: none; }
  .home-cover__img{ transform: none; transition: none; }
  .home-cover__load{ display: none; }
  html body[data-cover] .scroll-hint{ opacity: 1; }
}

/* ---------- Cosa arriva quando il velario se ne va (pagine con `data-velario`) ----------
   E' la stessa logica della home, dove il marchio in alto compare solo a fine apertura:
   li' il segnale e' `is-cover-open` (la barra arrivata al 100%), qui - dove la barra di
   caricamento non c'e', perche' non c'e' nessuna fotografia di copertina da aspettare -
   il segnale e' la fine del velario, cioe' la sparizione di `is-velario`.
   Scritto come "nascondi FINCHE' il velario e' su" e non come "mostra dopo": cosi' con le
   animazioni ridotte, o senza JavaScript, dove `is-velario` non viene messa mai, marchio e
   titolo sono visibili dal primo istante senza bisogno di eccezioni.
   Il titolo di sezione ha un ritardo suo (--header-sezione-delay-ms): arriva dopo il
   marchio, come chiesto. */
html.is-velario body[data-velario] .site-header__wordmark,
html.is-velario body[data-velario] .site-header__section,
html.is-velario body[data-velario] .scroll-hint{ opacity: 0; }

.hero{ min-height: var(--slide-h); display: flex; align-items: center; justify-content: center; background: var(--color-paper); position: relative; }
.hero__intro{ width: min(86vw,440px); padding: 0 24px; background: var(--color-paper); }
.hero__intro-title{ font-family: var(--font-serif); font-size: clamp(28px,2.4vw,42px); font-weight: 300; color: #000; margin: 0 0 20px; line-height: 1.15; text-align: center; }
.hero__intro p{ margin: 0 0 11px; font-family: var(--font-sans); font-weight: 400; font-size: 12px; line-height: 1.62; text-align: justify; text-wrap: pretty; color: var(--color-muted); }
.hero__intro p:last-child{ margin-bottom: 0; }
.hero__intro ul, .hero__intro ol{ margin: 0 0 11px; padding-left: 20px; font-family: var(--font-sans); font-size: 12px; line-height: 1.62; color: var(--color-muted); text-align: left; }
.hero__intro a{ color: var(--color-ink); text-decoration: underline; text-underline-offset: 2px; }

/* ---------- Bottom-left "now viewing" series indicator ---------- */
.series-indicator{
  position: fixed; left: var(--edge-x); bottom: var(--edge-y); z-index: 40;
  display: flex; align-items: baseline; gap: 8px;
  font-family: var(--font-sans); font-weight: 400; color: var(--color-ink);
  opacity: 0; transition: opacity 600ms ease; pointer-events: none;
}
.series-indicator.is-visible{ opacity: 1; }
.series-indicator__num{ font-size: 11px; letter-spacing: .08em; color: var(--color-gray); }
.series-indicator__title{ font-size: 13px; letter-spacing: .01em; }

/* ---------- Indicatore di scroll verticale ----------
   E' lo stesso su tutte le pagine e resta acceso per tutta la pagina, dalla prima
   schermata all'ultima (fino al 2026-08-25 spariva dopo la prima scheda).
   mix-blend-mode:difference invece di un colore fisso: cosi' resta leggibile sia sulle
   pagine chiare (quasi tutte) sia sopra una fotografia sia sulla slide scura finale della
   gallery, senza bisogno di varianti per sezione. E' anche il motivo per cui puo' restare
   acceso ovunque senza controlli.
   Le posizioni sono qui sotto; quella valida sopra i 980px e' l'ultima del gruppo.
   Solo desktop (vedi media query sotto). ---------- */
.scroll-hint{
  position: fixed; left: 50%; bottom: 130px; z-index: 40;
  transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  pointer-events: none;
  color: #fff; mix-blend-mode: difference;
  opacity: 1; transition: opacity 400ms ease;
}
/* Sopra una fotografia chiara (es. un cielo azzurro) "difference" non da' bianco: da'
   il colore calcolato dalla sottrazione, che puo' finire su un ocra/arancio. Sulla
   copertina a tutto schermo, dove is-dark-slide e' gia' acceso mentre la foto occupa lo
   schermo (vedi setupHomeCover() in js/main.js), l'indicatore deve leggersi bianco vero:
   si toglie il blend mode e resta il colore piatto gia' dichiarato sopra. */
body.is-dark-slide .scroll-hint{ mix-blend-mode: normal; }
/* Spento quando non c'e' piu' niente da scorrere: ultima scheda sulle pagine a schede,
   fondo pagina (o pagina piu' corta dello schermo) su quelle a scorrimento libero. La
   classe la mette js/main.js in tutti e due i casi. */
.scroll-hint.is-hidden{ opacity: 0; }
.scroll-hint__label{
  font-family: var(--font-sans); font-size: 10px; font-weight: 400;
  letter-spacing: .14em; text-transform: uppercase; white-space: nowrap;
}
.scroll-hint__track{
  position: relative; width: 1px; height: 46px; background: rgba(255,255,255,.35);
  overflow: hidden;
}
.scroll-hint__fill{
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  background: #fff; transform: scaleY(var(--fill, 0)); transform-origin: top;
  transition: transform 260ms ease-out;
}
.scroll-hint__dot{
  position: relative;
  width: 7px; height: 7px; border-radius: 999px;
  border: 1px solid #fff; background: transparent;
}
.scroll-hint__dot::after{
  content: ""; position: absolute; inset: 1px; border-radius: 999px;
  background: #fff; transform: scale(var(--fill, 0));
  transition: transform 260ms ease-out;
}

/* SOTTO I 980px (sopra decide il blocco in fondo a questa sezione: l'indicatore pende
   dall'hamburger). Su home e gallery l'indicatore sta di fianco alla colonna di testo.
   Al centro in basso finiva sopra il pulsante, perche' la lunghezza del testo
   della prima schermata non e' prevedibile; di lato il testo non ci arriva mai. La
   soglia a 1440px provata prima e' stata tolta: lasciava il vecchio comportamento (e
   quindi la sovrapposizione) su tutti i monitor piu' larghi.
   Lo scostamento orizzontale e' calcolato dalla colonna di testo di ciascuna pagina, non
   da un numero fisso, cosi' la distanza dal bordo del testo e' identica sulle due: la
   gallery usa --content-col (.series-detail__col), la home la larghezza di .hero__intro. */
body[data-page="series-detail"] .scroll-hint,
body[data-page="home"] .scroll-hint{
  top: 50%; bottom: auto;
  transform: translate(-50%, -50%);
  gap: 12px;
}
body[data-page="series-detail"] .scroll-hint{
  left: calc(50% - var(--content-col) / 2 - clamp(44px, 5vw, 96px));
}
body[data-page="home"] .scroll-hint{
  left: calc(50% - min(86vw, 440px) / 2 - clamp(44px, 5vw, 96px));
}
body[data-page="series-detail"] .scroll-hint__label,
body[data-page="home"] .scroll-hint__label{ writing-mode: vertical-rl; }
body[data-page="series-detail"] .scroll-hint__track,
body[data-page="home"] .scroll-hint__track{ height: clamp(64px, 12vh, 120px); }

/* SOTTO I 980px, come la regola qui sopra. Sulla home CON copertina l'indicatore torna
   dov'era di regola nel resto del sito: in basso, al centro, orizzontale. La posizione laterale, a meta' altezza, serviva a non
   finire sopra la colonna di testo della prima schermata; ma la prima schermata ora e' una
   fotografia centrata dentro una cornice, e sotto di lei lo spazio e' libero.
   DEVE stare qui, dopo le regole di body[data-page="home"] appena sopra: hanno la stessa
   specificita', quindi a decidere e' l'ordine nel file. Spostarla in alto la disattiva
   silenziosamente. */
body[data-cover] .scroll-hint{
  top: auto; bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  gap: 10px;
}
body[data-cover] .scroll-hint__label{ writing-mode: horizontal-tb; }
body[data-cover] .scroll-hint__track{ height: 46px; }

/* ---------- DESKTOP (sopra i 980px): l'indicatore pende dall'hamburger ----------
   Dal 2026-08-25 la posizione e' una sola in tutto il sito: una colonna appesa sotto
   l'icona del menu, in alto a destra, centrata sulle sue barrette (vedi i token
   --hamburger-bottom / --hamburger-center-right in :root).
   Prima erano tre posizioni diverse - in basso al centro, di fianco alla colonna di testo
   su home e gallery, in basso al centro sulla copertina - e ognuna aveva il suo modo di
   finire addosso al contenuto, perche' la lunghezza dei testi non e' prevedibile.
   Nell'angolo del chrome non c'e' niente su cui sovrapporsi, a nessuna lunghezza.
   L'etichetta resta ruotata (l'aspetto che home e gallery avevano gia'): e' l'unico che
   tiene la colonna larga quanto l'icona, quindi il centraggio si legge davvero.
   Le regole qui sopra restano in vigore sotto i 980px: mobile e desktop sono due versioni
   distinte e questa modifica non tocca il telefono.
   I selettori ripetono per esteso quelli delle tre varianti perche' hanno la stessa
   specificita': a decidere e' l'ordine nel file, quindi questo blocco DEVE restare dopo di
   loro. Spostarlo piu' in alto lo disattiva in silenzio. */
@media (min-width: 981px){
  .scroll-hint,
  body[data-cover] .scroll-hint,
  body[data-page="home"] .scroll-hint,
  body[data-page="series-detail"] .scroll-hint{
    /* I 16px sono l'aria fra la barretta piu' bassa dell'hamburger e la prima lettera:
       abbastanza da non sembrare attaccato all'icona, poco da leggersi come una cosa
       sola con lei. */
    top: calc(var(--hamburger-bottom) + 16px);
    right: var(--hamburger-center-right);
    bottom: auto; left: auto;
    /* `right` appoggia il bordo destro della colonna sul centro dell'icona; il mezzo
       passo indietro la ricentra sopra quel punto. */
    transform: translateX(50%);
    gap: 12px;
  }
  .scroll-hint__label,
  body[data-cover] .scroll-hint__label,
  body[data-page="home"] .scroll-hint__label,
  body[data-page="series-detail"] .scroll-hint__label{ writing-mode: vertical-rl; }
  /* Un po' piu' lunga di prima: erano 46px in basso al centro e clamp(64px, 12vh, 120px)
     di lato. La riga sotto l'icona e' l'unica cosa in quell'angolo e puo' permettersi di
     scendere di piu'; il tetto in pixel evita che su un monitor molto alto arrivi a
     meta' schermo. */
  .scroll-hint__track,
  body[data-cover] .scroll-hint__track,
  body[data-page="home"] .scroll-hint__track,
  body[data-page="series-detail"] .scroll-hint__track{ height: var(--scroll-hint-track); }
}

/* MOBILE: l'indicatore di scroll non esiste proprio. La soglia era 720px, e fra 720 e
   980 ricompariva nella vecchia posizione (in basso al centro o di fianco al testo),
   cioe' un terzo comportamento che non era ne' quello nuovo del desktop ne' il niente del
   telefono. Portata a 980, il confine dichiarato fra le due versioni del sito. */
@media (max-width: 980px){
  .scroll-hint{ display: none; }
  /* Eccezione alla riga sopra: sulla copertina della home l'indicatore di trascinamento
     serve anche su mobile (segnalato dall'utente il 2026-08-26, la copertina a tutto
     schermo qui sopra ne era priva). Solo mentre la copertina e' davvero a schermo
     (`.is-dark-slide`, la stessa classe che gia' segnala "copertina on screen" per
     l'inversione colore dell'header, aggiornata da coverOnScreen in js/main.js):
     legata al solo `[data-cover]` compariva su tutta la home, anche dopo aver scorso
     oltre la prima schermata, cosa non voluta (segnalato dall'utente). La dissolvenza in
     ingresso non serve ridichiararla: e' gia' la stessa regola di
     `html:not(.is-cover-shown) body[data-cover] .scroll-hint{ opacity: 0 }` usata su
     desktop, qui semplicemente non piu' bloccata da `display: none`. */
  body[data-cover].is-dark-slide .scroll-hint{ display: flex; }
  /* Stessa animazione continua (non legata allo scroll reale) dell'indicatore di
     trascinamento della modalita' film (vedi .drag-track/.drag-dot e
     @keyframes drag-dot-fall in css/film.css). Qui pero' `.scroll-hint__dot` e' un
     fratello di `.scroll-hint__track` nel markup condiviso (templates/scrollhint.php),
     non un suo figlio come in film.php: il punto animato e' quindi uno pseudo-elemento
     dentro al track stesso, invece dell'elemento .dot vero (nascosto insieme al
     riempimento legato allo scroll, che su una copertina ferma resterebbe un cerchio
     vuoto e immobile). */
  body[data-cover] .scroll-hint__fill,
  body[data-cover] .scroll-hint__dot{ display: none; }
  /* Il track ha `overflow: hidden` nella regola base (serve al riempimento a scala):
     qui andrebbe a tagliare ai lati il pallino da 6px dentro una linea da 1px. */
  body[data-cover] .scroll-hint__track{ overflow: visible; }
  body[data-cover] .scroll-hint__track::after{
    content: "";
    position: absolute; left: 50%; top: 0;
    width: 6px; height: 6px; margin-left: -3px;
    border-radius: 50%; background: #fff;
    animation: home-cover-dot-fall 1.6s cubic-bezier(.5, 0, .3, 1) infinite;
  }
  /* Un po' piu' in alto rispetto al bordo dello schermo (segnalato dall'utente). */
  body[data-cover] .scroll-hint{ bottom: 56px; }
}
@keyframes home-cover-dot-fall {
  0% { top: 0; opacity: 0; }
  25% { opacity: 1; }
  55% { top: 35px; opacity: 0; }
  100% { top: 35px; opacity: 0; }
}
@media (prefers-reduced-motion: reduce){
  .scroll-hint__fill, .scroll-hint__dot::after{ transition: none; }
}

/* =========================================================
   Series index (home) + series/gallery detail pages - shared
   pieces use the `series-` prefix throughout.
   ========================================================= */
.series-index{ background: var(--color-paper); }
.series-item{ min-height: var(--slide-h); display: flex; flex-direction: column; justify-content: center; align-items: center; padding: var(--slide-pad-y) 0; box-sizing: border-box; }
.series-item__body{ width: var(--content-col); }
.series-item__title-row{ padding-bottom: 0; margin-left: -30px; }
.series-item__title{ display: inline-block; font-family: var(--font-serif); font-size: 24px; 
  font-weight: 100; line-height: 1.2; }
.series-item__frame-wrap{ position: relative; }
/* Il margine sopra il riquadro serve solo a staccarlo dal titolo: se il titolo non e'
   pubblicato in home (checkbox "Pubblica in home il titolo"), il riquadro e' il primo
   elemento del blocco e quel margine non ha piu' ragione di esistere. */
.series-item__frame-wrap:first-child .series-item__frame{ margin-top: 0; }
.series-item__frame{
  display: grid; box-sizing: border-box;
  width: calc(100% + 60px); margin-top: 10px; margin-left: -30px;
  border: 1px solid var(--color-filet); background: var(--color-paper);
  padding: 20px;
  grid-template-columns: repeat(2, 1fr); gap: 20px;
}
/* Miniature sempre in 3:4 (verticali), ritagliate dal centro, cosi' il mosaico ha
   colonne allineate. Riguarda SOLO le miniature della home: le foto nelle pagine
   gallery restano intere. Il ritaglio e' solo di inquadratura (object-fit), i file
   non vengono rigenerati.
   ATTENZIONE: e' tarato su mosaici di sole foto verticali. Una foto orizzontale
   dentro questo formato verrebbe ritagliata molto, perdendo i lati: caso ancora da
   decidere insieme (vedi docs/da-fare.md). */
.series-item__thumb{
  display: block; width: 100%; height: auto; background: var(--color-placeholder-bg);
  aspect-ratio: 3 / 4; object-fit: cover; object-position: center;
}
.series-item__thumb.reveal{
  transform: none;
  transition: opacity 1.7s var(--ease-out-expo);
  transition-delay: calc(var(--i, 0) * 140ms);
}
.series-item__thumb.reveal.is-visible{ transform: none; }
.series-item__meta{
  position: absolute; right: -48px; bottom: 0;
  writing-mode: vertical-rl;
  font-size: var(--text-2xs); letter-spacing: .05em; color: var(--color-gray); white-space: nowrap;
}

/* ---------- Home: mosaico "ancora e satelliti" (DESKTOP, da 981px in su) ----------
   Sostituisce la griglia 2x2 di quattro riquadri uguali: la prima miniatura (per
   ordine) diventa l'ancora, verticale e alta quanto le altre due messe insieme; le
   restanti due si impilano piu' piccole a destra. La quarta miniatura, ancora
   caricata e presente nel markup, resta nascosta qui: la usa solo il mosaico
   mobile sotto, che non e' toccato da questo blocco (vedi convenzioni.md sulla
   fragilita' della formula mobile del mosaico, tarata su quattro riquadri uguali).

   L'aspect-ratio sta sul RIQUADRO, non sulle singole foto: un primo tentativo dava
   a ogni miniatura la propria proporzione (3/4.6 l'ancora, 4/3 le satelliti) e
   lasciava le due righe alte quanto bastava al loro contenuto. Con l'ancora che
   scavalca le due righe, pero', la griglia le allunga finche' insieme non
   raggiungono l'altezza dell'ancora: le foto satellite non si allungano con la
   riga (restano alla loro altezza naturale), quindi si apriva un vuoto sotto la
   prima satellite, largo quanto la differenza, diverso per ogni gallery a seconda
   delle proporzioni originali delle foto (segnalato dall'utente su Kyoto, 92px di
   buco). Con l'altezza del riquadro fissata qui e ogni miniatura a `height: 100%`
   (si ritaglia con `object-fit: cover' gia' della regola base), le due righe sono
   sempre esattamente meta' dell'altezza disponibile: zero vuoti, qualunque sia la
   proporzione delle foto originali.

   Lo sbordo di 8px sulla seconda satellite e' voluto (proposta scelta dall'utente
   il 2026-08-26): tenuto piccolo apposta per non arrivare a toccare l'etichetta
   verticale di .series-item__meta, spostata qui apposta un po' piu' in la'.

   Aggiornato lo stesso giorno, su richiesta dell'utente ("eliminare il bordino,
   differenziare di piu' le dimensioni, ingrandire un po' l'insieme"): colonne
   piu' sbilanciate (2fr/1fr invece di 1.5fr/1fr) e righe non piu' uguali fra
   loro (le due satelliti hanno percio' dimensioni diverse anche fra loro, non
   solo rispetto all'ancora), e piu' bleed orizzontale (110px invece di 60px)
   per un riquadro complessivamente piu' grande. Tutto confinato qui dentro, il
   mosaico mobile sotto non cambia.

   Il filetto e il padding tolti in quel giro sono stati rimessi subito dopo,
   sempre su richiesta dell'utente: senza, il mosaico risultava incoerente col
   resto del sito (le altre immagini - copertina, foto singola, pagina gallery -
   hanno tutte una cornice). Bastava non sovrascrivere piu' `border`/`padding`
   qui: la regola base di `.series-item__frame` li aveva gia' (border filetto,
   padding 20px), servivano gia' al mosaico mobile e non erano mai stati
   tolti da li'. */
@media (min-width: 981px){
  .series-item__frame{
    width: calc(100% + 110px); margin-left: -55px;
    grid-template-columns: 2fr 1fr; grid-template-rows: 6fr 5fr;
    aspect-ratio: 11 / 9;
  }
  .series-item__thumb:nth-child(1),
  .series-item__thumb:nth-child(2),
  .series-item__thumb:nth-child(3){ height: 100%; }
  .series-item__thumb:nth-child(1){ grid-row: 1 / span 2; }
  .series-item__thumb:nth-child(4){ display: none; }
  /* L'etichetta verticale si sposta piu' in la' per via del bleed orizzontale
     del riquadro (110px invece dei 60 originali): il -48px di base finirebbe
     sotto il bordo destro del riquadro. Nessuno sbordo di miniatura da
     compensare qui (tolto, vedi cronologia.md): il numero tiene conto solo
     del bleed del riquadro. Scoperto qui (2026-08-26): senza scoparla al
     mosaico, `.series-item__meta` da sola e' troppo generica - la scheda a
     foto singola riusa la stessa classe per la sua etichetta e non ha lo
     stesso bleed, quindi non deve ereditare questo spostamento.
     Portato da -63px a -84px il 2026-08-27 (utente: "si appiccica al bordo"):
     con -63px l'etichetta finiva 5px DENTRO il filetto destro invece che
     staccata da esso. Misurato nel browser, non calcolato a mano: il riquadro
     sborda di 55px oltre .series-item__frame-wrap e l'etichetta verticale e'
     larga 13px, quindi servono 55 + 16 + 13 = 84px per lasciare gli stessi
     16px di stacco della scheda a foto singola qui sotto. */
  .series-item__frame-wrap .series-item__meta{ right: -84px; }

  /* Specchiato ogni due gallery (:nth-of-type conta solo gli <article>, quindi
     ignora le schede a foto singola intercalate): l'ancora passa a destra, le
     satelliti a sinistra. Stesso identico rapporto di colonne della versione
     normale, solo invertito (2fr/1fr diventa 1fr/2fr): usare un rapporto
     diverso qui (era rimasto 1.5fr da prima di allargare le colonne base a
     2fr) rompeva la coerenza fra le dimensioni del blocco specchiato e non
     specchiato, un bug reale segnalato dall'utente. */
  .series-item:nth-of-type(even) .series-item__frame{ grid-template-columns: 1fr 2fr; }
  .series-item:nth-of-type(even) .series-item__thumb:nth-child(1){ grid-column: 2; }
}

/* ---------- Mosaico ancora ancora piu' grande, sopra i 1920px ----------
   Segnalato dall'utente il 2026-08-26: il mosaico smette di crescere molto prima dei
   monitor grandi, perche' la sua larghezza (100% del riquadro qui sopra, piu' bleed
   fisso) dipende da --content-col, che si blocca al proprio tetto di 540px gia' intorno
   ai 1350px di larghezza (vedi convenzioni.md, "impalcatura responsive") - su un monitor
   1920, 2560 o piu' largo il mosaico e' fermo alla stessa dimensione da tempo, anche se
   le sorgenti (thumb 400px per le satelliti, display 1600px per l'ancora) hanno margine
   per essere mostrate piu' grandi senza sgranare.
   Soglia 1921px scelta com'e' stata chiesta ("sopra i 1920"), non e' il tetto dei 1980px
   gia' documentato altrove: sono due numeri diversi, non confonderli.
   La larghezza diventa una `clamp()` legata al viewport (non piu' a --content-col, che
   qui viene ignorato di proposito) fra 650px (dove finiva prima) e 1100px: il tetto non
   e' arbitrario, e' dove la colonna dell'ancora (2/3 del riquadro) supera gli ~700px e
   comincerebbe a riavvicinarsi al limite dei 1600px della variante 'display' su schermi
   retina (700px * 2 = 1400px, ancora sotto; oltre i 1100px totali si comincerebbe a
   perdere il margine). Il margine sinistro si ricalcola di conseguenza per restare
   centrato sul riquadro di --content-col, con la stessa tecnica gia' usata sopra
   (calc esplicito, non margin:auto - vedi convenzioni.md sul perche' margin:auto non
   funziona qui). */
@media (min-width: 1921px){
  /* La variabile sta sul genitore comune (.series-item__frame-wrap), non sul riquadro:
     .series-item__meta e' una SORELLA del riquadro, non una discendente, e una custom
     property si eredita solo scendendo nell'albero - dichiarata sul riquadro stesso non
     sarebbe mai arrivata all'etichetta (provato, "right" veniva fuori positivo e sbagliato
     invece che negativo: la dichiarazione con var() irrisolta viene scartata dalla
     cascata). */
  .series-item__frame-wrap{ --mosaico-w: clamp(650px, 38vw, 1100px); }
  .series-item__frame{
    width: var(--mosaico-w);
    margin-left: calc((100% - var(--mosaico-w)) / 2);
  }
  /* L'etichetta verticale e' ancorata a .series-item__frame-wrap, che resta largo
     quanto --content-col: quando il riquadro cresce oltre quello, la distanza fra
     il suo bordo e l'etichetta si allarga a sua volta se non si ricalcola anche
     questo offset. Lo sbordo del riquadro e' esattamente (--mosaico-w meno la
     larghezza dell'ancora) / 2, piu' i 29px che danno lo stacco di 16px.
     Il termine sottratto e' --content-col, NON i 540px scritti a mano nella
     versione precedente (era `-65px - (--mosaico-w - 650px) / 2`): 540px e' solo
     il TETTO di --content-col, che vale `clamp(320px, min(40vw, 50vh), 540px)` e
     su un monitor largo ma basso scende sotto quel tetto per via del 50vh. In quel
     caso il numero fisso sbagliava lo sbordo e l'etichetta rientrava dentro il
     riquadro: misurato il 2026-08-27 a 2400x900, l'etichetta finiva 31px dentro
     il filetto. Con la variabile al posto della costante il conto torna a
     qualunque combinazione di larghezza e altezza. */
  .series-item__frame-wrap .series-item__meta{ right: calc(-29px - (var(--mosaico-w) - var(--content-col)) / 2); }
}

/* ---------- Home: scheda a foto singola (DESKTOP, da 981px in su) ----------
   La foto singola e' l'alternativa al mosaico: stessa cornice (filetto da 1px e
   20px di margine interno), cosi' le due schede della home si leggono come due
   varianti dello stesso oggetto e non come due grafiche diverse.
   La cornice si stringe addosso alla fotografia (`width: fit-content`): la
   larghezza nasce dall'altezza massima trasferita attraverso le proporzioni
   dell'immagine, quindi vale sia per una foto verticale sia per una orizzontale
   senza dover distinguere i due casi.
   PRIMA c'erano `width: 80%` sulla figure e `width: 100%` sull'immagine: l'80%
   era calcolato sull'ancora, che si stringe sul contenuto, e la figure restava
   incollata al bordo sinistro dell'ancora invece di stare al centro. Misurato a
   1440x900: foto larga 864px con 180px di margine a sinistra e 396px a destra.
   Il tetto in altezza e' la fascia libera fra logo e footer, come nelle schede
   della gallery: a 1440x900 la fotografia passa da 864x576 a 960x640. */
.series-breath{ min-height: var(--slide-h); display: flex; align-items: center; justify-content: center; background: var(--color-paper); }
.series-breath__link{ display: block; width: 100%; }
.series-breath__figure{
  position: relative;
  width: fit-content; max-width: min(86vw, 1600px);
  margin: 0 auto; box-sizing: border-box;
  border: 1px solid var(--color-filet); background: var(--color-paper);
  padding: 20px;
}
/* Stessa distanza reale del mosaico (16px fra filetto ed etichetta), non lo stesso
   numero: il -48px di base (pensato per un riquadro senza bleed) lasciava 34px qui,
   quasi il doppio, perche' il mosaico usa -84px apposta per compensare il suo bleed
   orizzontale (110px) che questa cornice non ha. Misurare la distanza vera invece di
   copiare il valore, segnalato dall'utente il 2026-08-26.
   I 16px di questa scheda sono il riferimento: quando il mosaico si e' scostato da
   questo numero (era finito a -63px, cioe' 5px dentro il filetto) e' stato riportato
   a -84px, non il contrario. */
.series-breath__figure .series-item__meta{ right: -30px; }
.series-breath__img{
  display: block;
  width: auto; max-width: 100%;
  height: auto; max-height: var(--breath-media-h);
  background: var(--color-placeholder-bg);
}

/* ---------- Series detail page ---------- */
.series-detail{ min-height: 100vh; padding: 16vh 0 20vh; }
.series-detail__col{ width: var(--content-col); margin: 0 auto; }
.series-detail__back{ display: inline-block; font-size: var(--text-2xs); letter-spacing: .06em; color: var(--color-gray); margin-bottom: 9vh; }
.series-detail__breadcrumb{ display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-bottom: 9vh; }
.series-detail__breadcrumb .series-detail__back{ margin-bottom: 0; }
.series-detail__breadcrumb-sep{ font-size: var(--text-2xs); color: var(--color-filet); }
.series-detail__title{ font-family: var(--font-serif); font-size: clamp(34px,2.6vw,54px); font-weight: 300; letter-spacing: +.005em; color: #000; margin: 0 0 3vh; line-height: 1.15; }
.series-detail__meta{ font-size: var(--text-2xs); letter-spacing: .05em; color: var(--color-gray); padding-bottom: 12px; border-bottom: 1px solid var(--color-filet); }
.series-detail__text{ font-family: var(--font-sans); font-size: 12px; line-height: 1.62; font-weight: 400; margin: 0 auto 5vh auto;  text-wrap: pretty; color: var(--color-muted); }
/* Lo stacco fra un paragrafo e l'altro lo decide QUI, non il contenuto. Era `0`, e per
   avere aria fra i paragrafi bisognava lasciare righe vuote dentro il testo del CMS: da
   li' i <p><br></p> e i <br><br> che rendevano l'impaginazione dipendente da come era
   stato battuto il testo (segnalato dall'utente il 2026-08-25). Stessa misura
   dell'introduzione della home, cosi' i due testi lunghi del sito hanno lo stesso ritmo. */
.series-detail__text p{ margin: 0 0 11px; }
.series-detail__text p:last-child{ margin-bottom: 0; }
.series-detail__text ul, .series-detail__text ol{ margin: 0 0 12px; padding-left: 22px; text-align: left; }
.series-detail__text a{ color: var(--color-ink); text-decoration: underline; text-underline-offset: 2px; }
.series-detail__gallery{ width: var(--content-col-gallery); border: 1px solid var(--color-filet); margin: auto; background: var(--color-paper); padding: 20px 20px 4px; box-sizing: border-box; column-count: 2; column-gap: 10px; }
.series-detail__figure{ margin: 0 0 10px; break-inside: avoid; }
.series-detail__figure--wide{ column-span: all; }
.series-detail__img{ display: block; width: 100%; height: auto; background: var(--color-placeholder-bg); }
.series-detail__figure-link{ display: block; cursor: pointer; }

/* ---------- Pulsante esplicito "Guarda a schermo intero" (usa $filmUrl) ---------- */
.series-detail__cta{
  display: inline-flex; align-items: center; gap: 9px;
  margin-top: 1vh; padding: 11px 22px;
  font-family: var(--font-sans); font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  border: 1px solid var(--color-ink); border-radius: 999px; color: var(--color-ink);
  transition: background 220ms ease, color 220ms ease, opacity 1200ms var(--ease-out-expo), transform 1200ms var(--ease-out-expo);
}
.series-detail__cta:hover{ background: var(--color-ink); color: var(--color-paper); }
.series-detail__cta svg{ width: 13px; height: 13px; flex: 0 0 auto; }

/* ---------- Indicatore di scorrimento in fondo alla copertina della gallery ----------
   SOLO TELEFONO: nel markup porta `solo-mobile`, quindi sopra i 980px non compare mai, e
   la sua `display` sta nel blocco mobile in fondo al file (qui non va dichiarata: la
   regola `.solo-mobile{ display: none }` ha la stessa specificita' e verrebbe scavalcata,
   facendo comparire l'indicatore anche sul desktop).
   Prende il posto del pulsante "guarda la gallery completa", che sul telefono chiudeva la
   prima schermata come se non ci fosse altro. E' lo stesso alfabeto dell'indicatore di
   scroll del desktop - etichetta piccola, filo verticale, pallino - perche' e' la stessa
   cosa detta in un posto diverso, non un secondo linguaggio.
   Il pallino scende lungo il filo e ricomincia: e' quello che imita il gesto del dito. */
.cover-hint{
  flex-direction: column; align-items: center; gap: 9px;
  margin-top: 1vh;
  pointer-events: none;
}
.cover-hint__label{
  font-family: var(--font-sans); font-size: 10px; font-weight: 400;
  letter-spacing: .14em; text-transform: uppercase; color: var(--color-gray);
}
/* Il filo e' alto 44px e il pallino ne percorre 39 (44 meno il suo diametro): i due
   numeri stanno insieme, se cambia l'altezza va rifatta la corsa nei keyframes. */
.cover-hint__track{
  position: relative; width: 1px; height: 44px; background: var(--color-filet);
}
.cover-hint__dot{
  position: absolute; top: 0; left: 50%; margin-left: -2.5px;
  width: 5px; height: 5px; border-radius: 999px; background: var(--color-ink);
  animation: cover-hint-corsa 2s var(--ease-in-out-quint) infinite;
}
@keyframes cover-hint-corsa{
  0%   { transform: translateY(0); opacity: 0; }
  18%  { opacity: 1; }
  70%  { opacity: 1; }
  100% { transform: translateY(39px); opacity: 0; }
}
/* Con le animazioni ridotte il pallino non corre: resta fermo in fondo al filo, che e'
   comunque la direzione da prendere. */
@media (prefers-reduced-motion: reduce){
  .cover-hint__dot{ animation: none; transform: translateY(39px); }
}

/* Sulla home l'indicatore entra per ultimo, a testo composto (richiesta dell'utente il
   2026-08-25: "fallo comparire alla fine del caricamento degli altri elementi"). Non usa
   `.stagger-in` come il testo sopra: quello fa entrare i figli a scaletta appena il
   velario se ne va, mentre qui serve aspettare che la scaletta di SOPRA sia finita, e
   quanto duri dipende da quanti paragrafi ha scritto il CMS. A dare il via e'
   setupCoverHintHome() in js/main.js, che quel tempo lo legge dall'ultimo paragrafo
   invece di indovinarlo.
   Nella gallery non serve niente di tutto questo: li' l'indicatore e' dentro la colonna
   `.stagger-in` ed entra da se', per ultimo, come ultimo figlio del gruppo. */
.cover-hint--home{
  opacity: 0; transform: translateY(14px);
  transition: opacity 1000ms var(--ease-out-expo), transform 1000ms var(--ease-out-expo);
}
.cover-hint--home.is-visible{ opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce){
  .cover-hint--home{ opacity: 1; transform: none; transition: none; }
}

/* =========================================================
   Series detail (gallery): sequenza immersiva a schede/cover.
   `.series-detail--reel` toglie il padding statico del contenitore
   condiviso con pagina.php: qui ogni sezione gestisce da sola i suoi
   100vh (stesso motore di scroll-snap di home/categoria).
   ========================================================= */
.series-detail--reel{ min-height: auto; padding: 0; }
.series-detail__cover{
  min-height: var(--cover-h); display: flex; flex-direction: column; align-items: center; justify-content: center;
  position: relative; padding: 12vh 24px; box-sizing: border-box;
}
.series-detail__cover .series-detail__col{ margin: 0 auto; }

/* Il contenuto della prima schermata deve stare DENTRO i 90vh della sezione:
   se sfora, spinge la slide successiva fuori dallo schermo e annulla il peek
   (vedi docs/convenzioni.md, "L'invariante del peek"). Spazi verticali fluidi
   e compatti, scoped al reel per non toccare pagina.php che usa le stesse classi. */
.series-detail--reel .series-detail__cover{ padding: clamp(36px, 5.5vh, 92px) 24px; }
.series-detail--reel .series-detail__cover .series-detail__breadcrumb{ margin-bottom: clamp(18px, 3vh, 48px); }
.series-detail--reel .series-detail__cover .series-detail__title{ margin-bottom: clamp(10px, 1.8vh, 26px); }
.series-detail--reel .series-detail__cover .series-detail__text{ margin-bottom: clamp(16px, 2.6vh, 38px); }

.series-frame{
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: max(6vh, var(--chrome-top)) clamp(24px,6vw,80px) max(6vh, var(--chrome-bottom));
  box-sizing: border-box;
}
.series-frame__figure{ margin: 0; max-width: 100%; }

/* ---------- Reveal morbido delle foto nella gallery immersiva: legato
   al cambio di scheda attiva (stesso IntersectionObserver dell'indicatore),
   non a un generico trigger di soglia - piu' visibile durante lo scroll. */
.frame-reveal{
  opacity: 0; transform: translateY(48px) scale(.97);
  transition: opacity 1.2s var(--ease-out-expo), transform 1.2s var(--ease-out-expo);
}
.frame-reveal.is-visible{ opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce){
  .frame-reveal{ opacity: 1; transform: none; transition: none; }
}
.series-frame__link{ display: block; cursor: pointer; }
.series-frame__img{ display: block; width: auto; height: auto; background: var(--color-placeholder-bg); border: 3px solid #000; box-sizing: border-box; }
/* L'altezza massima della foto grande e' esattamente la fascia libera fra logo e
   footer (100vh meno le due riserve): cosi' riempie lo spazio disponibile senza
   finire sotto al logo o addosso al footer. Se si cambiano le riserve in :root,
   questa formula si adegua da sola. */
.series-frame--large .series-frame__img{
  /* Il tetto in larghezza era 1360px: su uno schermo largo (misurato a 2560x1440) una
     foto ORIZZONTALE ci sbatteva contro prima di arrivare al tetto in altezza, restava
     alta 906px in una fascia libera di 1267px e il centraggio verticale la spingeva
     tutta sotto la piega: peek 0px, mentre una verticale sullo stesso schermo ne aveva
     97px. 1600px e' il lato lungo con cui l'admin genera le varianti "display"
     (IMAGE_DISPLAY_MAX), quindi e' il massimo utile: oltre si ingrandirebbe un file che
     non ha quei pixel. Sotto i 1860px di viewport non cambia nulla, perche' li' e' 86vw
     a comandare. */
  max-width: min(86vw, 1600px);
  /* Il fattore .88 lascia respiro sopra e sotto: senza, la singola centrale riempiva
     tutta la fascia libera e risultava piu' incombente delle altre. */
  max-height: calc((100vh - max(6vh, var(--chrome-top)) - max(6vh, var(--chrome-bottom))) * .88);
}
.series-frame--small .series-frame__img{ max-width: min(52vw, 720px); max-height: 74vh; }
.series-frame--left{ justify-content: flex-start; }
.series-frame--right{ justify-content: flex-end; }

/* ---------- Scheda "duo": due immagini piu' piccole affiancate ---------- */
.series-frame--duo{ gap: clamp(16px,3vw,40px); }
.series-frame--duo .series-frame__img{ max-width: min(43vw, 560px); max-height: 70vh; }
/* Nelle coppie la seconda foto entra staccata dalla prima, cosi' l'ingresso si legge
   come due tempi invece che come un blocco unico. */
.series-frame--duo .frame-reveal:nth-of-type(2){ transition-delay: 280ms; }

@media (max-width: 720px){
  .series-frame--small .series-frame__img{ max-width: 100%; }
  .series-frame--left, .series-frame--right{ justify-content: center; }
  .series-frame--duo{ flex-direction: column; gap: 20px; }
  /* Incolonnate, due foto da 42vh l'una arrivavano quasi a riempire da sole i 100vh
     della scheda (.series-frame, padding compreso): il centraggio verticale non aveva
     piu' margine da distribuire, e la prima foto finiva a ridosso del chrome fisso in
     alto (segnalato dall'utente con uno screenshot). Ridotte a 34vh: la coppia libera
     spazio che il centraggio rimette sopra e sotto, senza bisogno di un padding-top
     dedicato come sulla copertina (vedi piu' sopra in questo file). */
  .series-frame--duo .series-frame__img{ max-width: 100%; max-height: 34vh; }
}

/* ---------- Pulsante "schermo intero" fisso, speculare all'indicatore ---------- */
.series-float-cta{
  position: fixed; right: var(--edge-x); bottom: var(--edge-y); z-index: 40;
  opacity: 0; pointer-events: none; transition: opacity 600ms ease;
}
.series-float-cta.is-visible{ opacity: 1; pointer-events: auto; }

@media (max-width: 720px){ .series-float-cta{ display: none; } }

/* ---------- Minimappa a miniature: naviga la sequenza di schede ---------- */
.series-map{
  position: fixed; right: clamp(10px, calc(6px + 0.4vw), 14px); top: 50%; transform: translateY(-50%); z-index: 40;
  display: flex; flex-direction: column; gap: 8px;
  transition: opacity 420ms ease;
}
/* Sulla slide finale la minimappa si ritira: naviga fra le schede fotografiche, che li'
   non ci sono piu', e resterebbe sospesa sopra il fondo scuro. */
body.is-dark-slide .series-map{ opacity: 0; pointer-events: none; }
.series-map__dot{
  display: block; width: 26px; height: 26px; border-radius: 3px;
  background-size: cover; background-position: center; background-color: var(--color-placeholder-bg);
  border: 1px solid transparent; opacity: .5;
  transition: opacity 220ms ease, border-color 220ms ease, transform 220ms ease;
}
/* Conteggio delle foto che restano fuori dall'anteprima, sotto l'ultima miniatura della
   mappa: porta alla gallery completa. Largo quanto le miniature, cosi' resta incolonnato
   con loro. */
.series-map__more{
  display: flex; align-items: center; justify-content: center;
  width: 26px; min-height: 22px; margin-top: 2px;
  font-family: var(--font-sans); font-size: 11px; font-weight: 700; letter-spacing: .02em;
  white-space: nowrap; color: var(--color-ink); opacity: .75;
  transition: opacity 220ms ease;
}
.series-map__more:hover{ opacity: 1; }
.series-map__dot:hover{ opacity: .85; }
.series-map__dot.is-active{ opacity: 1; border-color: var(--color-ink); transform: scale(1.15); }

@media (max-width: 900px){ .series-map{ display: none; } }

/* DESKTOP: la minimappa e l'indicatore di scroll vivono nella stessa striscia a destra, e
   sulla prima scheda ci sono tutti e due. Centrata sul viewport, su una finestra bassa
   (1440x720, tutt'altro che rara) la prima miniatura finiva sotto la barra dell'indicatore.
   La regola: la mappa resta centrata finche' ci sta, e solo quando salirebbe nella fascia
   dell'indicatore scende quel tanto che basta - non si nasconde e non si rimpicciolisce
   (vedi "adattare, non nascondere" in docs/convenzioni.md). Il terzo termine e' il
   paracadute in fondo: su uno schermo davvero basso preferisce restare dentro la finestra.
   L'altezza della mappa serve prima che il browser la misuri, quindi arriva dal template
   come numero di miniature (--map-n) piu' l'eventuale conteggio "+N" (--map-extra): 26px
   di miniatura, 8px di spazio fra una e l'altra, e 32px il conteggio (22px di altezza
   minima, 2px di margine e l'ottavo spazio del flex, che vale anche prima di lui:
   verificato misurando la mappa, non a mente). Se cambiano quelle misure in
   .series-map__dot / .series-map__more va rifatto questo conto. */
@media (min-width: 981px){
  .series-map{
    --map-h: calc(
      var(--map-n, 6) * 26px + (var(--map-n, 6) - 1) * 8px + var(--map-extra, 0px)
    );
    top: min(
      max(calc(50% - var(--map-h) / 2), calc(var(--scroll-hint-zone) + 20px)),
      calc(100% - var(--edge-y) - var(--map-h))
    );
    /* Stesso asse verticale dell'hamburger e dell'indicatore, non piu' una distanza dal
       bordo per conto suo: la mappa e' larga 26px contro i 24px del pulsante, quindi
       allineare i BORDI destri lasciava i tre centri sfalsati di qualche pixel, e in una
       colonna di tre elementi uno scarto di 3px si vede. Ancorata come l'indicatore: il
       bordo destro sul centro dell'icona, poi mezzo passo indietro per ricentrarsi. */
    right: var(--hamburger-center-right);
    transform: translateX(50%);
  }
}

/* ---------- Slide finale: contrasto scuro, richiama la visione immersiva ---------- */
.series-outro{
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  background: var(--color-overlay-bg); color: var(--color-overlay-fg); text-align: center;
  padding: max(8vh, var(--chrome-top)) 24px max(8vh, var(--chrome-bottom));
  box-sizing: border-box;
}
.series-outro__inner{ max-width: 480px; }
.series-outro__eyebrow{ display: block; font-family: var(--font-sans); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--color-gray); margin-bottom: 2.5vh; }
.series-outro__title{ font-family: var(--font-serif); font-weight: 300; font-size: clamp(28px,3vw,44px); margin: 0 0 2vh; line-height: 1.2; }
.series-outro__note{ font-family: var(--font-sans); font-size: 12px; color: var(--color-gray); margin: 0 0 4vh; }
.series-detail__cta--invert{ border-color: var(--color-overlay-fg); color: var(--color-overlay-fg); }
.series-detail__cta--invert:hover{ background: var(--color-overlay-fg); color: var(--color-overlay-bg); }

/* ---------- Category listing page ---------- */
.category-list{ width: var(--content-col); margin: 6vh auto 0; display: flex; flex-direction: column; gap: 8vh; padding-bottom: 10vh; }
.category-empty{ text-align: center; color: var(--color-gray); font-size: 13px; padding: 6vh 0; }

/* ---------- Categoria: griglia di copertine (invece dello scroll a pagina piena) ---------- */
/* La colonna e' piu' stretta di prima (era min(1180px, 92vw)) perche' le schede ora sono
   due per riga a qualsiasi larghezza: su un monitor largo due schede dentro 1180px
   diventavano due fotografie da mezzo schermo l'una. A 920px una scheda sta sui 440px,
   la stessa misura della colonna di testo del resto del sito. */
.category-page{ width: min(920px, 92vw); margin: 0 auto; padding: 18vh 0 12vh; }
/* Il titolo grande in pagina non c'e' piu': dal 2026-08-25 il nome della sezione lo dice
   il velario di apertura e poi resta piccolo sotto il marchio, in alto a sinistra.
   Ripeterlo al centro della pagina sarebbe stato dirlo tre volte. La regola resta per un
   eventuale uso futuro e perche' l'H1 esiste ancora, nascosto ai soli occhi. */
.category-page__title{ font-family: var(--font-serif); font-weight: 300; font-size: clamp(30px,3vw,48px); color: #000; margin: 0 0 2vh; text-align: center; line-height: 1.15; }
.category-page__desc{ max-width: 640px; margin: 0 auto 8vh; font-family: var(--font-sans); font-size: 12px; line-height: 1.62; text-align: center; color: var(--color-muted); }
.category-page__desc p{ margin: 0 0 10px; }
.category-page__desc p:last-child{ margin-bottom: 0; }
.category-page__desc a{ color: var(--color-ink); text-decoration: underline; text-underline-offset: 2px; }

/* Due per riga a ogni larghezza sopra i 980px (richiesta dell'utente il 2026-08-25):
   non piu' `auto-fit`, che a seconda della larghezza della finestra dava due, tre o
   quattro colonne e faceva sembrare la pagina un'altra pagina. Una sola per riga sotto i
   980px, nel blocco mobile in fondo al file. */
.category-grid{ display: grid; grid-template-columns: repeat(2, 1fr); gap: 56px 40px; }
.category-card{ display: block; }
.category-card__figure{ margin: 0 0 14px; aspect-ratio: 4 / 5; border: 1px solid var(--color-filet); background: var(--color-placeholder-bg); overflow: hidden; transition: border-color 200ms ease; }
.category-card:hover .category-card__figure{ border-color: var(--color-ink); }
.category-card__img{ display: block; width: 100%; height: auto; aspect-ratio: 4 / 5; object-fit: cover; background: var(--color-placeholder-bg); }
.category-card__title{ font-family: var(--font-serif); font-weight: 300; font-size: 17px; margin: 0 0 4px; color: #000; }
.category-card__caption{ font-family: var(--font-sans); font-size: 12px; line-height: 1.5; color: var(--color-gray); margin: 0; }

/* ---------- Contact form (Contatti page) ----------
   Sul desktop il modulo e' una frase da completare, non un modulo: le caselle stanno
   dentro il testo e sono larghe quanto la parola che ci va. Perche' l'illusione regga,
   la frase e la casella del messaggio devono essere LO STESSO testo: una sola misura,
   dichiarata qui in --contact-testo e usata da tutt'e due. Prima il messaggio era piu'
   piccolo (16-22px contro 20-30px) e si leggeva come il campo di un altro modulo.
   La forma a frase vale solo da 981px in su: sotto, nel blocco mobile in fondo al file,
   diventa un modulo normale in colonna. */
.contact-form{ --contact-testo: clamp(20px,2.4vw,30px); margin: 5vh 0; }
.contact-form__honeypot{ position: absolute; left: -9999px; top: -9999px; }
.contact-form__sentence{
  font-family: var(--font-serif); font-weight: 300; font-size: var(--contact-testo);
  line-height: 1.8; color: #000; margin: 0 0 5vh; text-wrap: pretty;
}
/* Le etichette dei campi esistono sempre nel documento: qui le legge solo chi usa un
   lettore di schermo, perche' la frase attorno gia' dice cosa scrivere. Sul telefono
   tornano visibili e diventano le etichette del modulo. */
.contact-form__label{
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.contact-form__blank{
  font-family: inherit; font-weight: 400; font-size: 1em; color: var(--color-ink);
  background: transparent; border: 0; border-bottom: 1px solid var(--color-filet);
  padding: 0 2px 3px; margin: 0 2px; text-align: center; vertical-align: baseline;
  box-sizing: border-box; text-overflow: ellipsis;
}
.contact-form__blank::placeholder{ color: var(--color-gray); }
.contact-form__blank:focus{ outline: none; border-bottom-color: var(--color-ink); }
.contact-form__blank--nome{ width: clamp(110px, 13vw, 190px); }
.contact-form__blank--oggetto{ width: clamp(150px, 20vw, 300px); }
.contact-form__blank--email{ width: clamp(150px, 18vw, 260px); }
.contact-form__message-box{ margin: 0 0 3vh; }
/* Parte alta una riga sola (rows="1" nel markup) e cresce da se' mentre si scrive: la
   riga sotto scende con il testo invece di stare ferma su un riquadro vuoto. Chi la fa
   crescere e' setupAutoGrowTextarea in js/main.js. L'interlinea e' piu' stretta di
   quella della frase perche' qui la riga deve restare attaccata al testo, come il
   filetto sotto le caselle in linea. */
.contact-form__textarea{
  display: block; width: 100%; padding: 0 2px 3px; font-family: var(--font-serif);
  font-weight: 400; font-size: var(--contact-testo); line-height: 1.4; color: var(--color-ink);
  background: transparent; border: 0; border-bottom: 1px solid var(--color-filet);
  resize: none; overflow: hidden; box-sizing: border-box;
}
.contact-form__textarea:focus{ outline: none; border-bottom-color: var(--color-ink); }
.contact-form__textarea::placeholder{ color: var(--color-gray); }
.contact-form__button{
  padding: 11px 22px; font-family: var(--font-sans); font-size: 12px; letter-spacing: .04em;
  background: var(--color-ink); color: var(--color-paper); border: 0; border-radius: 4px; cursor: pointer;
}
.contact-form__button:hover{ opacity: .88; }
.contact-form__success{ font-size: 13px; color: var(--color-ink); }
.contact-form__errors{ font-size: 13px; color: #b3261e; margin: 0 0 18px; }
.contact-form__errors ul{ margin: 0; padding-left: 18px; }

/* =========================================================
   MOBILE - sotto i 980px (il confine desktop/mobile dichiarato in
   docs/convenzioni.md). Riguarda la HOME e, dal 2026-08-25, la griglia delle pagine
   categoria; gallery e pagine di contenuto restano da rivedere (vedi docs/da-fare.md).

   Questo blocco sta in fondo al file DI PROPOSITO: le eccezioni mobile devono
   venire dopo tutte le regole base, altrimenti la cascata le ignora. E' un bug
   gia' successo (vedi docs/cronologia.md, 2026-08-18).
   ========================================================= */
@media (max-width: 980px){
  :root{
    /* MOBILE. Velario di apertura. Cambia solo la misura del marchio: non puo' scalare
       in vw come sul desktop (a 390px di larghezza un 2.4vw sarebbe 9px). La durata del
       sipario NON e' ridichiarata di proposito: sono gli stessi 620ms del menu a tutto
       schermo, che e' identico su tutte le larghezze, e separarli qui vorrebbe dire
       scollegare l'apertura del sito dall'apertura del menu proprio sul telefono, dove il
       menu si usa di piu'. Anche gli altri tempi del velario sono ritmo, non spazio, e le
       due versioni li condividono per scelta. */
    --velario-marchio-size: 22px;

    /* MOBILE. Copertina iniziale della home. Cambiano solo queste manopole rispetto al
       desktop: la cornice si prende tutta la larghezza utile fra i due margini di bordo
       (su un telefono un 86vw lascerebbe due strisce di carta senza motivo), e l'apertura
       e' piu' rapida perche' la distanza da coprire e' minore e 1100ms sembrerebbero
       un'esitazione.
       Gli altri token della copertina (--splash-hold-ms, --splash-cross-ms,
       --splash-still-ms, --splash-bar-fade-ms, --splash-hint-delay-ms, --splash-zoom) NON
       sono ridichiarati di proposito: sono tempi di ritmo, non di spazio, e le due versioni
       li condividono per scelta. Se un giorno vanno separati, si ridichiarano qui col
       valore che hanno oggi.
       --splash-media-h usa var(--slide-h), che sotto i 980px e' gia' 100svh: la cornice si
       adatta da sola alle barre del browser mobile senza altre righe. */
    --splash-media-w: calc(100vw - var(--edge-x) * 2);
    --splash-media-h: calc((var(--slide-h) - var(--chrome-top) - var(--chrome-bottom)) * .82);
    --splash-open-ms: 900ms;

    /* MOBILE. Copertina della gallery: questo e' il valore di RIPIEGO, quello che vale
       quando sotto la copertina non c'e' nessuna fotografia da far sbirciare (gallery
       senza immagini). Quando c'e', l'altezza la calcola la regola
       .series-detail__cover--peek in fondo a questo blocco, partendo dalla striscia di
       fotografia da lasciare in vista invece che da una percentuale fissa: sul telefono
       una percentuale sola non puo' funzionare, perche' a decidere dove comincia la
       fotografia della scheda seguente e' la SUA forma (vedi il commento di quella
       regola). In svh e non in vh: e' l'altezza vera della finestra, barre del browser
       escluse. */
    --cover-h: 90svh;

    /* MOBILE. Riserve verticali delle schede fotografiche. Sono dichiarate qui, e non
       scritte dentro la regola, perche' le usano in due: la scheda, per il suo padding, e
       il conto del peek, che deve sapere esattamente dove finisce l'aria e comincia la
       fotografia. Se i due valori si separassero, il peek sarebbe sbagliato di quella
       differenza. Stessa formula del desktop (`max(6vh, ...)`) ma in svh, per lo stesso
       motivo di --cover-h. */
    --frame-pad-top: max(6svh, var(--chrome-top));
    --frame-pad-bottom: max(6svh, var(--chrome-bottom));

    /* MOBILE. Quanta FOTOGRAFIA (non spazio bianco: e' l'errore in cui e' gia' caduta
       questa pagina) deve restare in vista sotto la copertina della gallery. E' la
       manopola del peek sul telefono: alzarla mostra piu' foto e accorcia la copertina,
       abbassarla il contrario. Sul desktop la manopola e' un'altra (--cover-h): li' le
       fotografie sono limitate dall'altezza e una percentuale basta. */
    --peek-mobile: 128px;

    /* Altezza reale della finestra, barre del browser escluse. I browser che non
       conoscono svh ignorano questa riga e tengono il 100vh dichiarato in :root. */
    --slide-h: 100svh;
    --slide-pad-y: clamp(24px, 5vh, 48px);

    /* Colonna di testo/interfaccia: parte dai bordi reali dello schermo invece che dal
       minimo di 320px di --content-col, che su un telefono da 360px non lasciava
       margine. Il tetto in pixel resta, come su desktop. */
    --home-content-w: min(calc(100vw - var(--edge-x) * 2), 540px);
    /* Le fotografie invece non hanno tetto in pixel (principio in convenzioni.md):
       occupano la larghezza disponibile fino ai bordi. */
    --home-media-w: calc(100vw - var(--edge-x) * 2);

    /* Geometria del mosaico 2x2 della home. --frame-chrome e' tutto cio' che nel
       riquadro non e' fotografia: i due bordi interni piu' lo spazio fra le colonne. */
    --frame-pad: clamp(10px, 3vw, 20px);
    --frame-gap: clamp(10px, 3vw, 20px);
    --frame-chrome: calc(var(--frame-gap) + var(--frame-pad) * 2);
    /* Spazio da lasciare al titolo e all'etichetta sopra e sotto il riquadro. */
    --frame-extras: 88px;
    /* Tetto della colonna ricavato dall'ALTEZZA disponibile, non dalla larghezza: le
       miniature sono in 3:4, quindi due righe di mosaico crescono insieme alla
       larghezza e su uno schermo basso (telefono ruotato, iPhone SE) sforerebbero la
       scheda. E' la formula del mosaico invertita: due righe in 3:4 piu' --frame-chrome
       devono stare nell'altezza libera, da cui larghezza = altezza_foto * 3/4 * 2.
       Se si cambiano padding, gap o proporzione delle miniature va rifatto il conto. */
    --frame-w-from-h: calc(
      (var(--slide-h) - var(--slide-pad-y) * 2 - var(--frame-extras) - var(--frame-chrome)) * 0.75
      + var(--frame-chrome)
    );
    /* Altezza stimata del footer su schermo stretto (va a capo su due righe): serve a
       far salire l'indicatore della serie sull'ultima scheda, dove i due compaiono
       insieme. */
    --footer-h-mobile: 44px;
  }

  /* Il passaggio fra le schede e' comandato dal dito (vedi setupSlideScroll in
     js/main.js): il rimbalzo elastico e il pull-to-refresh a inizio gesto darebbero
     l'impressione che la pagina scorra da sola. */
  body[data-page="home"], body[data-page="series-detail"]{ overscroll-behavior-y: none; }

  /* ---------- Home: schermata iniziale ---------- */
  /* La schermata iniziale non riservava spazio al chrome: su uno schermo basso (320x568,
     o un telefono ruotato) il testo, centrato sull'intera altezza, finiva sotto il logo e
     il pulsante del menu, che sono fissi. In basso basta il margine di bordo: durante le
     schede il footer non c'e' (compare solo sull'ultima). */
  .hero{ padding: var(--chrome-top) var(--edge-x) var(--edge-y); }
  .hero__intro{ width: min(var(--home-content-w), 440px); padding: 0; }
  /* Il testo giustificato su una colonna stretta apre fiumi bianchi fra le parole:
     a 12px su ~300px diventa evidente a ogni riga. */
  .hero__intro p{ text-align: left; }
  /* In colonna, perche' sotto il testo ora c'e' l'indicatore di scorrimento: la sezione
     era un flex in riga con un figlio solo, e un secondo figlio gli si sarebbe messo di
     fianco. Sopra i 980px l'indicatore e' `display: none` e la riga non cambia niente,
     ma la regola resta qui lo stesso: e' il telefono ad avere due figli. */
  .hero{ flex-direction: column; }
  /* Piu' staccato dal testo di quanto lo sia nella gallery (li' e' 1vh): la copertina
     della gallery e' un blocco solo, qui l'indicatore e' l'unica cosa sotto un testo
     lungo e attaccato sembrerebbe l'ultima riga del paragrafo. */
  .cover-hint--home{ margin-top: clamp(20px, 3.5vh, 40px); }

  /* ---------- Home: scheda di una serie ---------- */
  /* Titolo, riquadro ed etichetta condividono la stessa colonna, cosi' restano
     incolonnati anche quando e' l'altezza a stringere il riquadro. */
  .series-item__body{ width: min(var(--home-content-w), var(--frame-w-from-h)); }
  .series-item__title{ font-size: 20px; }
  /* Sul desktop titolo e riquadro sbordano di 30px a sinistra rispetto alla colonna di
     testo: qui non c'e' margine per farlo, il riquadro uscirebbe dallo schermo. */
  .series-item__title-row{ margin-left: 0; }
  /* Bordino del mosaico tolto solo qui (richiesta esplicita dell'utente per il
     mobile): sul desktop resta, vedi il commento sopra `@media (min-width: 981px)`
     sul perche' li' e' stato rimesso dopo un giro precedente. */
  .series-item__frame{
    width: 100%; margin-left: 0;
    padding: var(--frame-pad);
    gap: var(--frame-gap);
    border: 0;
  }
  /* L'etichetta verticale usciva di 48px oltre il bordo destro del riquadro, cioe'
     fuori dallo schermo. Sotto i 980px diventa una riga di testo sotto il mosaico. */
  .series-item__meta{
    position: static;
    writing-mode: horizontal-tb;
    display: block;
    margin-top: 10px;
    white-space: normal;
  }

  /* ---------- Home: scheda a foto singola ---------- */
  /* Il desktop ha ricevuto la cornice del mosaico (filetto, margine interno) e un
     centraggio esplicito: qui vanno annullati uno per uno, altrimenti scenderebbero
     per cascata anche sul mobile, che e' una versione a se'. L'ancora torna a
     stringersi sul contenuto (`width: auto`): e' cosi' che la fotografia risulta
     centrata sul telefono, perche' e' la sezione flex a centrare l'ancora.
     Verificato prima e dopo a 390x844, 430x932, 768x1024 e 980x800: fotografia
     identica al pixel, x=18 e larghezza 100vw meno i due margini di bordo. */
  .series-breath__link{ width: auto; }
  .series-breath__figure{
    width: var(--home-media-w);
    max-width: none;
    margin: 0;
    border: 0;
    padding: 0;
    max-height: calc(var(--slide-h) - var(--chrome-top) - var(--chrome-bottom));
  }
  /* Larghezza e altezza entrambe `auto` con due tetti: cosi' e' il browser a scalare la
     foto mantenendo le sue proporzioni. Con la larghezza forzata al 100% e un tetto in
     altezza, invece, il riquadro diventava piu' largo che alto e `object-fit: cover`
     ritagliava: una foto verticale su un telefono ruotato si riduceva a una striscia. */
  .series-breath__img{
    width: auto; max-width: 100%;
    height: auto;
    max-height: calc(var(--slide-h) - var(--chrome-top) - var(--chrome-bottom));
    margin: 0 auto;
    /* Con larghezza e altezza entrambe `auto` il ritaglio non puo' scattare (le
       proporzioni restano quelle del file), ma la dichiarazione stava nella regola
       comune che il desktop ha riscritto: ripetuta qui perche' il mobile resti
       identico anche nello stile calcolato, non solo nella geometria. */
    object-fit: cover;
  }

  /* ---------- Fascia bassa ---------- */
  /* Footer e indicatore della serie non stanno affiancati su uno schermo stretto: il
     footer va a capo su due righe e ci finisce sopra. Sulle pagine a schede il footer
     resta quindi fermo finche' si sfogliano le fotografie e compare sull'ultima scheda.
     La condizione e' scritta qui e non nel JS di proposito: `is-last-slide` la mette il
     motore delle slide, e lasciare che sia la cascata a reagire evita di dipendere
     dall'ordine con cui girano due listener di scroll diversi. */
  body[data-page="home"] .site-footer__inner,
  body[data-page="series-detail"] .site-footer__inner{ opacity: 0; transform: translateY(10px); }
  body[data-page="home"].is-last-slide .site-footer__inner,
  body[data-page="series-detail"].is-last-slide .site-footer__inner{ opacity: 1; transform: none; }
  /* I link vanno sotto il nome, ma restano in riga fra loro: il nome forza il capo riga
     con flex-basis 100%, i link restano un flex in riga sulla riga sotto. Il trattino
     prima del primo link (pensato per separare due elementi sulla stessa riga) sparisce
     percio' solo li', gli altri restano fra un link e l'altro. */
  .site-footer__inner{
    row-gap: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--color-filet);
  }
  .site-footer__legal{ flex: 1 0 100%; text-align: center; }
  .site-footer__link:first-of-type::before{ content: none; }

  /* Bug reale segnalato dall'utente (screenshot): su una pagina che scorre nel modo
     normale (categoria, pagina statica, 404) il footer restava `position: fixed` come
     sulle schede, quindi bastava scrollare un po' perche' si sovrapponesse a qualunque
     contenuto si trovasse in quel momento sul fondo dello schermo - non solo all'ultima
     riga della pagina. Su home e series-detail il fisso resta voluto (fa parte del
     sistema di "schede" a tutto schermo, ed e' gia' tenuto a bada da `is-last-slide` qui
     sopra): sono le uniche due escluse dalla regola. Ovunque altro, sotto i 980px, il
     footer torna nel flusso normale del documento: essendo l'ultimo elemento prima di
     `</body>` (vedi `templates/*.php`, il footer e' incluso subito dopo `</main>`),
     finisce semplicemente in fondo alla pagina vera, mai sovrapposto - non serve
     calcolare un'altezza da riservare col padding, che oltretutto varierebbe a seconda
     che i link vadano a capo su una riga o due. */
  body:not([data-page="home"]):not([data-page="series-detail"]) .site-footer{
    position: static;
    padding: 30px 0 var(--edge-y);
  }
  body:not([data-page="home"]):not([data-page="series-detail"]) .site-footer__inner{
    opacity: .7;
    transform: none;
  }
  /* L'etichetta in basso a sinistra col numero e il titolo della serie non c'e' su
     telefono: finiva addosso al footer (che li' va a capo su due righe) e ripeteva un
     titolo che e' gia' scritto nella scheda. Il compenso che la faceva salire di una riga
     sull'ultima scheda e' sparito con lei, insieme al bisogno di --footer-h-mobile per
     questo scopo.
     Non e' un "nascondi per non sistemare": e' un elemento che sul desktop accompagna lo
     sfogliare le schede a schermo largo, e su un telefono non ha ne' spazio ne' compito. */
  .series-indicator{ display: none; }

  /* ---------- Pagine categoria ---------- */
  /* Una gallery per riga: due colonne su uno schermo da telefono ridurrebbero ogni
     fotografia a un francobollo. Sopra i 980px sono due (regola base). */
  .category-grid{ grid-template-columns: 1fr; }

  /* ---------- Corpo dei testi ----------
     15px invece dei 12 del desktop (richiesta dell'utente il 2026-08-25: a 12px sul
     telefono non si legge). Non e' una scelta di stile: sul desktop quei 12px si leggono
     a distanza di monitor e dentro una colonna larga, su un telefono la stessa misura e'
     sotto la soglia di comodita' di lettura.
     Riguarda i testi da LEGGERE, non le etichette dell'interfaccia (didascalie delle
     schede, numeri, "SCROLL"): quelle restano piccole di proposito, e ingrandirle
     farebbe sembrare tutto un modulo. Il testo piu' lungo diventa quindi piu' alto: e'
     il motivo per cui gallery e home hanno un campo "testo per mobile" nel CMS. */
  .hero__intro p,
  .hero__intro ul, .hero__intro ol,
  .series-detail__text,
  .category-page__desc{ font-size: 15px; }

  /* ---------- Testi centrati (richiesta dell'utente il 2026-08-25) ----------
     Su home e gallery, sul solo telefono, i testi descrittivi e i loro titoli sono
     centrati. Sul desktop restano allineati a sinistra: sono due versioni distinte e
     questo blocco non le mescola. I selettori partono da body[data-page] apposta, cosi'
     le pagine di contenuto (Chi sono, Contatti, che usano `data-page="pagina"` e le
     stesse classi .series-detail__*) non vengono toccate.
     Il giustificato della home sparisce da se': era gia' stato tolto qui sopra perche' su
     una colonna stretta apriva fiumi bianchi. */
  body[data-page="home"] .hero__intro p,
  body[data-page="home"] .hero__intro ul,
  body[data-page="home"] .hero__intro ol,
  body[data-page="series-detail"] .series-detail__title,
  body[data-page="series-detail"] .series-detail__text,
  body[data-page="series-detail"] .series-detail__text ul,
  body[data-page="series-detail"] .series-detail__text ol{ text-align: center; }
  /* Un elenco puntato centrato con il pallino appeso fuori dal testo risulterebbe
     appeso nel vuoto: il segno rientra nella riga e il rientro sparisce. */
  body[data-page="home"] .hero__intro ul,
  body[data-page="home"] .hero__intro ol,
  body[data-page="series-detail"] .series-detail__text ul,
  body[data-page="series-detail"] .series-detail__text ol{ list-style-position: inside; padding-left: 0; }
  /* Titolo della gallery in home e sua didascalia: il titolo e' un'ancora in linea, a
     centrarlo e' la riga che lo contiene. */
  body[data-page="home"] .series-item__title-row,
  body[data-page="home"] .series-item__meta{ text-align: center; }
  /* La riga "home | categoria" sopra il titolo della gallery e' un flex: si centra col
     suo, non con text-align. */
  body[data-page="series-detail"] .series-detail__breadcrumb{ justify-content: center; }
  /* L'indicatore che ha preso il posto del pulsante. La `display` sta qui e non nella
     regola del componente: sopra i 980px deve vincere `.solo-mobile{ display: none }`,
     che ha la stessa specificita' e sta prima nel file. */
  .cover-hint{ display: flex; }
  /* E il pulsante se ne va. `.solo-desktop` da solo non basta per lui: quella regola sta
     in cima al file e vale `display: none`, ma .series-detail__cta dichiara
     `display: inline-flex` mille righe piu' sotto, a parita' di specificita', e vince per
     ordine. Serve quindi un selettore piu' specifico. Verificato misurando: senza questa
     riga il pulsante restava in pagina sul telefono. */
  .series-detail__cta.solo-desktop{ display: none; }

  /* ---------- Schede fotografiche: la finestra vera ----------
     100svh invece di 100vh: su un telefono con la barra dell'indirizzo visibile i 100vh
     sono piu' alti dello schermo, quindi la fotografia si centra dentro una scheda piu'
     alta di quella che si vede e finisce piu' in basso di dove la si e' calcolata: il
     peek qui sotto sarebbe giusto sulla carta e zero sul telefono. Il margine laterale
     passa da clamp(24px,6vw,80px) a --edge-x, lo stesso di tutto il resto del telefono:
     la fotografia arriva ai bordi reali dello schermo (principio in convenzioni.md) e,
     soprattutto, la larghezza usata dalla fotografia diventa una misura che anche il
     conto del peek puo' scrivere. Sopra i 980px non cambia niente. */
  .series-frame{
    min-height: var(--slide-h);
    padding: var(--frame-pad-top) var(--edge-x) var(--frame-pad-bottom);
  }
  .series-frame--large .series-frame__img{
    max-width: calc(100vw - var(--edge-x) * 2);
    max-height: calc((var(--slide-h) - var(--frame-pad-top) - var(--frame-pad-bottom)) * .88);
  }

  /* La copertina della gallery non puo' avere meno aria in alto della riga di logo e
     menu, che e' fissa e le passa sopra. Il margine di serie (clamp(36px, 5.5vh, 92px))
     sotto i 980px vale 36-46px, meno dei 56 riservati al chrome: finche' la copertina e'
     piu' alta del suo contenuto non si vede, perche' il contenuto sta centrato e resta
     lontano dal bordo, ma quando il testo e' lungo ed e' lui a dettare l'altezza la
     briciola di pagina in alto e' proprio quella - e su uno schermo basso la riga
     "home | categoria" finisce sotto il marchio. Il max() la tiene fuori.
     +20px oltre il chrome: senza, il breadcrumb tocca il bordo esatto del chrome, senza
     aria propria (segnalato dall'utente il 2026-08-26 con uno screenshot).
     Sulle gallery con testo lungo (come questa) il contenuto e' gia' piu' alto del
     minimo `--cover-h` che calcola il peek (misurato: 594.5px di contenuto contro
     562.157px di minimo, su questa pagina a 390x844): la' a comandare l'altezza vera
     e' il contenuto, non `--peek-mobile`, quindi i 20px aggiunti sopra si mangiavano
     20px di fotografia in vista sotto (segnalato dall'utente con un secondo
     screenshot). Tolti da qui, dal basso, dove sono solo aria dopo il paragrafo e non
     si notano: cosi' l'altezza totale della copertina non cresce per via del gap in
     piu' in alto, anzi il peek risulta un po' piu' grande di prima (richiesto). */
  .series-detail--reel .series-detail__cover{
    padding-top: max(calc(var(--chrome-top) + 20px), clamp(36px, 5.5vh, 92px));
    padding-bottom: clamp(12px, 2.4vh, 56px);
  }

  /* ---------- IL PEEK SUL TELEFONO ----------
     Il peek e' la striscia della fotografia seguente che sporge sotto la copertina e fa
     capire che si scorre. Non e' disegnato: nasce dalla differenza fra l'altezza della
     copertina e la posizione della fotografia nella scheda dopo (vedi convenzioni.md).
     Sul desktop bastava accorciare la copertina di una percentuale. Sul telefono no, ed
     e' il motivo per cui prima il peek era ZERO: misurato a 390x844, sotto la copertina
     restavano 84px di pagina e 0 pixel di fotografia. La fotografia li' e' strozzata
     dalla LARGHEZZA dello schermo, quindi la sua altezza dipende dalla sua forma - una
     verticale e' alta il doppio di un'orizzontale - e con essa la quota a cui comincia,
     visto che nella scheda sta centrata. Una percentuale fissa che va bene per le
     verticali lascia le orizzontali completamente sotto la piega.
     Quindi qui la copertina non parte da una percentuale ma dal RISULTATO voluto: si
     calcola dove comincia la prima fotografia e si toglie da li' la striscia da mostrare.
     --prima-foto-ratio (altezza / larghezza della prima fotografia) arriva dal template,
     e' l'unico dato che il CSS non puo' conoscere da solo.
     I 6px sono i due bordi neri da 3px di .series-frame__img, che con box-sizing:
     border-box stanno DENTRO la larghezza: senza toglierli il conto sbaglia di 6px.
     Il min() e' il caso della fotografia altissima, che non arriva a occupare tutta la
     larghezza perche' prima sbatte contro il tetto in altezza della scheda.
     Il clamp e' il paracadute: sotto sta il caso della fotografia molto larga e bassa,
     che pretenderebbe una copertina brevissima, sopra quello della fotografia molto alta.
     E resta l'invariante di sempre: questa e' un'altezza MINIMA, se il testo della
     copertina non ci sta dentro e' il testo a vincere e il peek si mangia - per quello nel
     CMS c'e' il campo "testo per mobile". */
  .series-detail__cover--peek{
    --prima-img-w: calc(100vw - var(--edge-x) * 2);
    --prima-img-h: min(
      calc((var(--prima-img-w) - 6px) * var(--prima-foto-ratio) + 6px),
      calc((var(--slide-h) - var(--frame-pad-top) - var(--frame-pad-bottom)) * .88)
    );
    --prima-img-top: calc(
      var(--frame-pad-top)
      + (var(--slide-h) - var(--frame-pad-top) - var(--frame-pad-bottom) - var(--prima-img-h)) / 2
    );
    --cover-h: clamp(
      50svh,
      calc(var(--slide-h) - var(--peek-mobile) - var(--prima-img-top)),
      86svh
    );
  }

  /* ---------- IL MODULO CONTATTI SUL TELEFONO ----------
     Sul desktop il modulo e' una frase da completare (vedi il blocco .contact-form in
     mezzo al file): le caselle stanno dentro il testo, larghe quanto la parola che ci
     va. Sul telefono quella forma non regge, e non e' questione di gusto: tre caselle a
     larghezza fissa dentro un serif da 20px vanno a capo dove capita, la frase si
     spezza a meta' e per scrivere il proprio nome bisogna centrare una riga alta pochi
     millimetri. Diventa quindi un modulo normale, in colonna, con le etichette sopra i
     campi.
     Le etichette non vengono aggiunte qui: esistono gia' nel documento, invisibili sul
     desktop, dove le legge solo chi usa un lettore di schermo. Qui tornano visibili. La
     prosa che le teneva insieme sparisce perche' in colonna non c'e' piu' una frase da
     comporre - e' la trasformazione richiesta, non una toppa su una sovrapposizione. */
  .contact-form__prosa,
  .contact-form__acapo{ display: none; }

  .contact-form__sentence{
    display: flex; flex-direction: column; gap: 20px;
    font-size: 16px; line-height: 1.4; margin: 0 0 20px;
  }
  .contact-form__campo,
  .contact-form__message-box{ display: flex; flex-direction: column; gap: 7px; }
  .contact-form__message-box{ margin: 0 0 26px; }

  .contact-form__label{
    position: static; width: auto; height: auto; padding: 0; margin: 0;
    overflow: visible; clip: auto; white-space: normal;
    font-family: var(--font-sans); font-size: 11px; letter-spacing: .08em;
    text-transform: uppercase; color: var(--color-gray);
  }

  /* I 16px non sono una scelta tipografica: sotto quella misura iOS ingrandisce la
     pagina da solo appena si tocca un campo, e da li' il sito resta zoomato. */
  .contact-form__blank,
  .contact-form__blank--nome,
  .contact-form__blank--oggetto,
  .contact-form__blank--email,
  .contact-form__textarea{
    width: 100%; margin: 0; padding: 11px 12px; text-align: left;
    font-family: var(--font-sans); font-weight: 400; font-size: 16px; line-height: 1.5;
    color: var(--color-ink); background: var(--color-paper);
    border: 1px solid var(--color-filet); border-radius: 4px;
  }
  .contact-form__blank:focus,
  .contact-form__textarea:focus{ border-color: var(--color-ink); }

  /* Il messaggio continua a crescere da se': in colonna parte da tre righe, che e'
     l'altezza minima perche' somigli a una casella di testo e non a un campo qualunque. */
  .contact-form__textarea{ min-height: calc(3 * 1.5em + 22px); }

  .contact-form__button{ width: 100%; padding: 15px 22px; font-size: 13px; }
}
