:root {
  --bg: #0a0a0a;
  --fg: #f4f4f4;
  --muted: #8a8a8a;
  --line: #1e1e1e;
  --accent: #e63329;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { background: var(--bg); color: var(--fg); }
body {
  font-family: "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  letter-spacing: .02em;
}
a { color: inherit; text-decoration: none; }

/* QUI c'era il guscio dello scaffold iniziale (commit "Scaffold sito Hugo + admin stile
   Format"): .site-header — barra scura sticky col nome spaziato, hamburger e nav — più
   .site-footer, .hero/.hero-title e la griglia "classica" delle gallery. Era montato da
   baseof.html su OGNI pagina e poi rispento a valle da cinque `display:none`, uno per ogni
   CSS di pagina (gallery, portfolio, workshop, about, home).
   Il costo non erano i 663 byte: era che il guscio si ereditava per default e si toglieva a
   mano, quindi un tipo di pagina nuovo se lo ritrovava addosso senza che niente segnalasse
   l'errore. È così che è rimasto visibile per anni sulla 404 e sopra l'informativa privacy.
   Ora non c'è default: la navigazione è il monogramma (partials/brand-menu.html) e ogni tipo
   di pagina porta il proprio fondo. Se qui cerchi una regola di base per una pagina nuova,
   non c'è apposta. */

/* Pagine di testo: il contenitore leggibile su cui si appoggiano l'informativa privacy
   (.privacy-page) e la 404. Non ha più colori propri — li mette la pagina. */
.text-page { padding: 4rem 2rem; max-width: 900px; margin: 0 auto; }
.text-page h1 {
  font-weight: 300; text-transform: uppercase; letter-spacing: .12em;
  font-size: clamp(1.4rem, 3vw, 2.2rem); margin-bottom: 1.5rem;
}
.prose { line-height: 1.7; max-width: 60ch; }

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

/* Titolo H1 tagline: in alto a sinistra, allineato al monogramma (left 4vw), sopra
   la gallery. Nascosto finché il nome non collassa (classe .is-visible via JS).
   Colore: bianco + difference sul layer stesso (vedi "Blend globale" più sotto) -> si
   inverte su qualsiasi sezione passi sotto, senza switch di colore pilotato dal JS. */
.site-tagline {
  position: fixed; top: 6vh; left: 4vw; z-index: 30; margin: 0;
  max-width: 92vw;
  font-family: "Helvetica Neue", Arial, sans-serif;
  font-weight: 600; font-size: clamp(0.72rem, 1.05vw, 0.95rem);
  letter-spacing: 0.14em; text-transform: uppercase;
  color: #fff;
  mix-blend-mode: difference;
  -webkit-backface-visibility: hidden; backface-visibility: hidden;
  pointer-events: none;
  opacity: 0; transform: translateY(8px) translateZ(0);
  transition: opacity 500ms ease, transform 600ms cubic-bezier(0.16, 1, 0.3, 1);
}
.site-tagline.is-visible { opacity: 1; transform: translateZ(0); }
/* Nella sezione "about" il tagline della pagina va nascosto (lo impone il JS via classe). */
.site-tagline.tag-hidden { opacity: 0 !important; }
@media (prefers-reduced-motion: reduce) {
  .site-tagline { transition: none; }
}

/* ===== Pagine di servizio su fondo crema (body.is-editorial) ==================
   Informativa privacy, prenotazioni e 404: tre pagine che scorrono, in cui la navigazione è
   il monogramma e il fondo è lo stesso.

   Il colore va sul <body>, non solo sul contenitore: i contenitori hanno `min-height:100vh` e
   coprono la finestra, ma sull'elastico dello scroll (iOS, trackpad macOS) oltre il bordo si
   vedrebbe il nero di `--bg`, che qui è dichiarato per le pagine scure. */
body.is-editorial { background: #ebe8e0; color: #181714; }

/* Occhiello sopra il titolo (l'"Informativa" della privacy). Era `booking-kicker`, un nome
   preso in prestito dalla pagina prenotazioni per cui però non è mai esistita una regola:
   restava testo non stilato. */
.privacy-kicker { margin: 0; text-transform: uppercase; letter-spacing: .14em; font-size: .68rem; }

/* Pagina prenotazioni: il widget resta un oggetto autonomo e incorporabile, mentre questa
   cornice lo mette in continuità con il ritmo editoriale del portfolio. */
.booking-page{min-height:100vh;padding:clamp(8rem,16vh,13rem) clamp(1.2rem,4vw,4rem) 6rem;background:#ebe8e0;color:#181714}
.booking-page-intro{display:grid;grid-template-columns:1fr 2fr 1fr;gap:2rem;align-items:end;max-width:1400px;margin:0 auto clamp(3rem,8vw,7rem)}
.booking-page-intro>p{margin:0;text-transform:uppercase;letter-spacing:.14em;font-size:.68rem}
.booking-page-intro h1{margin:0;font:400 clamp(3.5rem,8.5vw,9rem)/.84 Georgia,serif;letter-spacing:-.055em}
.booking-page-intro>div{max-width:30ch;line-height:1.55}
.booking-page .booking-widget{max-width:1400px;margin:auto}
.privacy-page{min-height:100vh;padding:clamp(8rem,16vh,13rem) clamp(1.5rem,8vw,9rem) 6rem;background:#ebe8e0;color:#181714}.privacy-page h1{margin:.5rem 0 3rem;font:400 clamp(3.5rem,9vw,8rem)/.9 Georgia,serif;letter-spacing:-.05em}.privacy-page .prose{max-width:720px;font-size:1.05rem;line-height:1.75}.privacy-page .prose p{margin:0 0 1.25rem}
@media(max-width:760px){.booking-page{padding-top:7rem}.booking-page-intro{grid-template-columns:1fr}.booking-page-intro h1{order:-1}}

/* Pagina 404 (layouts/404.html). Stessa famiglia delle due qui sopra — fondo crema, Georgia,
   la stessa corsia del monogramma — ma centrata invece che impaginata: qui non c'è un
   contenuto da leggere, c'è una direzione da dare.

   Il numero è l'elemento tipografico e il titolo è quello semantico, non viceversa: "404" è
   `aria-hidden` (a un lettore di schermo direbbe "quattrocentoquattro", non "pagina non
   trovata") e sotto c'è un <h1> vero. */
.notfound {
  min-height: 100vh;
  /* Due colonne: il testo a sinistra, il flusso di foto a destra. `minmax(0,1fr)` e non `1fr`
     perché una traccia `1fr` non scende mai sotto il contenuto minimo, e le colonne di
     immagini spingerebbero il testo fuori dal padding. */
  display: grid; grid-template-columns: minmax(0,1fr) auto;
  /* Le aree, e non l'ordine del markup: la firma sta SOTTO le colonne su desktop e SOTTO
     l'invito su mobile — due posti diversi dell'impaginato per lo stesso elemento. Il testo
     scavalca le due righe e resta centrato accanto alle foto. */
  grid-template-areas:
    "testo foto"
    "testo firma";
  grid-template-rows: auto auto;
  align-items: center;
  /* Righe e colonne separate: fra le foto e la firma serve un respiro tipografico, non lo
     stesso vuoto che separa il testo dalle immagini. */
  row-gap: clamp(1rem,2.5vh,1.75rem); column-gap: clamp(1.5rem,5vw,5rem);
  /* In basso NON 6rem ma 26vh: il monogramma è fisso nell'angolo in basso a sinistra —
     `geom()` in brand-menu.js lo mette a 4vw dal bordo e la riga alta gli parte intorno al
     78% dell'altezza, su qualunque viewport — ed è esattamente dove cade il link di ritorno
     di una pagina centrata. Non è una questione di estetica: sopra il monogramma c'è
     #menu-btn, un bottone vero, che si prenderebbe i click destinati al link.
     Il padding sta sul contenitore e non come margine del link perché `justify-content:
     center` centra dentro il box del padding: così l'intero blocco sale, e resta centrato
     nello spazio che gli rimane davvero. */
  padding: clamp(5rem,10vh,9rem) clamp(1.5rem,8vw,9rem) 30vh;
  max-width: 1400px; margin: 0 auto;
}
.notfound-code {
  margin: 0 0 clamp(1rem,3vh,2.5rem);
  /* Anche in ALTEZZA (min con 26vh), non solo in larghezza: su un portatile basso il numero
     spingeva il resto del blocco dentro la corsia del monogramma. */
  font: 400 clamp(5rem,min(20vw,26vh),16rem)/.78 Georgia, serif;
  letter-spacing: -.06em;
  /* Non è un titolo: è una texture. Al pieno contrasto ruberebbe la scena alla riga che dice
     davvero cosa fare. */
  opacity: .13;
}
.notfound h1 {
  margin: 0 0 1.25rem;
  font: 400 clamp(2rem,5vw,3.6rem)/1 Georgia, serif;
  letter-spacing: -.03em;
}
/* 1.25rem e non 2.5: ora sotto non c'è un bottone staccato ma un secondo paragrafo, e i due
   vanno letti come un blocco solo. */
.notfound-lead { margin: 0 0 1.25rem; max-width: 42ch; font-size: 1.05rem; line-height: 1.6; }
/* Sottolineatura staccata dalla base, come un tratto disegnato invece che un link di sistema. */
/* L'invito: stessa misura della riga sopra, ma smorzato — è la frase che porta altrove, non
   quella che spiega cosa è successo. A distinguersi è il link dentro, non il paragrafo. */
.notfound-invite { margin: 0; max-width: 42ch; font-size: 1.05rem; line-height: 1.6; opacity: .62; }
.notfound-invite a {
  /* Sottolineatura staccata dalla base: un tratto disegnato, non il link di sistema. */
  border-bottom: 1px solid currentColor; padding-bottom: .12rem;
  transition: opacity 300ms ease;
}
.notfound-invite a:hover, .notfound-invite a:focus-visible { opacity: .55; }

/* Le tre aree della griglia (vedi .notfound). */
.notfound-text   { grid-area: testo; }
.nf-stream       { grid-area: foto; }
.notfound-credit { grid-area: firma; }

/* La firma dello studio, per esteso. Piccola e in maiuscoletto spaziato come le altre
   sovrascritte del sito: deve essere leggibile e trovabile, non contendere la scena alla
   riga che dice dove andare. */
.notfound-credit {
  margin: 0;
  font-size: .72rem; letter-spacing: .12em; text-transform: uppercase; line-height: 1.5;
  opacity: .5;
}
.notfound-credit a {
  border-bottom: 1px solid currentColor; padding-bottom: .12rem;
  transition: opacity 300ms ease;
}
.notfound-credit a:hover, .notfound-credit a:focus-visible { opacity: .6; }

/* ----- Il flusso di foto della 404 -------------------------------------------------------
   Due colonne verticali che scorrono in VERSI OPPOSTI. Nessun JS: ogni pista contiene la
   lista due volte e trasla di -50%, cioè esattamente la prima copia, quindi l'ultimo
   fotogramma coincide col primo e il giro non ha cuciture.

   Il passo (`--nf-gap`) è un MARGINE su ogni foto, non un `gap` del flex: col `gap`
   l'altezza della pista sarebbe 2N foto + (2N-1) spazi, e metà di quel totale non
   corrisponderebbe a una copia intera — il giro slitterebbe di mezzo spazio a ogni ciclo,
   finché la cucitura non diventa visibile. Col margine l'altezza è 2N*(foto+passo) e -50%
   cade esatto.

   Durate diverse fra le due colonne (46s/57s): con la stessa durata i due versi opposti
   leggono come un unico meccanismo a ingranaggi, che è proprio ciò che non si vuole. */
.nf-stream {
  --nf-gap: clamp(.5rem, .9vw, 1rem);
  display: flex; gap: var(--nf-gap);
  height: min(76vh, 660px);
  /* Sfuma in cima e in fondo invece di tagliare netto: senza, il flusso sembra dentro una
     finestrella, con la sfumatura sembra passare dietro la pagina. */
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 14%, #000 86%, transparent);
          mask-image: linear-gradient(to bottom, transparent, #000 14%, #000 86%, transparent);
}
.nf-col { width: clamp(104px, 12.5vw, 186px); overflow: hidden; }
.nf-track {
  display: flex; flex-direction: column;
  will-change: transform;
  animation: nf-su 46s linear infinite;
}
.nf-col--down .nf-track { animation-name: nf-giu; animation-duration: 57s; }
.nf-track img {
  display: block; width: 100%; height: auto;
  aspect-ratio: 2 / 3; object-fit: cover;
  margin-bottom: var(--nf-gap);
  /* Il fondo è crema: una foto a pieno contrasto ci starebbe sopra come un adesivo. */
  filter: saturate(.92);
}
@keyframes nf-su  { from { transform: translateY(0); }     to { transform: translateY(-50%); } }
@keyframes nf-giu { from { transform: translateY(-50%); }  to { transform: translateY(0); } }

/* Su MOBILE: una riga sola, sopra il testo, stesso meccanismo girato di 90°.
   Sparisce la seconda colonna (la prima porta già il mix di tutti i generi) e la pista
   diventa orizzontale — quindi il margine passa da `bottom` a `right`, o il calcolo del
   -50% tornerebbe a non chiudere. */
@media (max-width: 760px) {
  .notfound {
    /* `minmax(0,1fr)` e non `1fr`, per lo stesso motivo del desktop e con un sintomo peggiore:
       il minimo automatico di `1fr` è il contenuto, e qui dentro c'è la pista orizzontale, che
       è larga quanto tutte le foto messe in fila. La colonna diventava più larga del viewport,
       le foto sbordavano e la firma non andava mai a capo — usciva dallo schermo. */
    grid-template-columns: minmax(0,1fr);
    /* Una colonna sola e le tre aree impilate: le foto sopra il 404, poi il testo, e la firma
       in coda subito sotto l'invito. Prima qui c'era `order: -1` sul flusso; con le aree
       l'ordine lo decide la griglia, e la firma trova posto senza una seconda regola. */
    grid-template-areas: "foto" "testo" "firma";
    grid-template-rows: auto auto auto;
    row-gap: clamp(1.25rem,4vw,2rem); column-gap: 0;
  }
  /* Aria in più prima della firma, e non è respiro tipografico: su un viewport basso
     (360x640 e simili) la riga cadeva dentro la corsia del monogramma, che è fisso in basso a
     sinistra — e lì sopra c'è #menu-btn, che si sarebbe preso i click destinati al link dello
     studio. Con questo scarto la firma scende sotto la corsia invece di finirci dentro. */
  .notfound-credit { margin-top: clamp(1rem, 5vh, 2.5rem); }
  .nf-stream {
    height: auto; width: 100%;
    -webkit-mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
            mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
  }
  .nf-col--down { display: none; }
  .nf-col--up { width: 100%; }
  .nf-col--up .nf-track { flex-direction: row; animation-name: nf-sinistra; animation-duration: 42s; }
  .nf-col--up .nf-track img {
    width: clamp(104px, 33vw, 150px); flex: 0 0 auto;
    margin-bottom: 0; margin-right: var(--nf-gap);
  }
}
@keyframes nf-sinistra { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* Il flusso è decorativo: chi ha chiesto meno movimento lo riceve fermo, non assente. */
@media (prefers-reduced-motion: reduce) {
  .nf-track { animation: none; }
}

/* ===== Intro animata home ===== */
body.is-home { background: #000; }
.intro {
  position: fixed; inset: 0; background: #000; z-index: 10; overflow: hidden;
  transition: opacity 700ms ease;
  /* Dimensione delle iniziali (deve combaciare con bigFont() in brand-menu.js) e righe
     della pila ADL. Le righe sono usate SIA dalle lettere rosse (.ltr) SIA dal nome nella
     stage (.fn-line): devono coincidere, è su quella sovrapposizione che regge il
     passaggio di consegne al reveal. Un'unica sorgente per entrambe. */
  --lf: min(150px, 15vh, 11vw);
  --row-a: 12vh;
  --row-d: 34vh;
  --row-l: 56vh;
}
/* Hero "dormiente": quando le sezioni lo coprono del tutto non serve dipingerlo né
   tenerne vivi i layer di compositing. Le animazioni della gallery vengono messe in
   PAUSA dal JS, perché in WebKit `visibility: hidden` NON le ferma (continuano a girare)
   e `display: none` le fermerebbe ma RESETTANDOLE, con uno scatto visibile al ritorno.
   Tolgo anche `will-change` per liberare i layer con le immagini decodificate. */
.intro.dormant { visibility: hidden; }
.intro.dormant .gallery-track { will-change: auto; }

/* L'utente ha scrollato prima che l'intro finisse: la parte restante (nome che scende e
   collassa nel monogramma) prosegue SOPRA le sezioni invece di restarne coperta.
   Tolgo tutti gli strati opachi dell'hero (nero, sfondo chiaro della stage, gallery):
   restano solo le lettere. Le lettere da sole NON possono fondersi con le sezioni (#intro
   è `fixed` -> gruppo isolato), quindi il blend lo fa QUESTO layer: dentro, la stage ha
   ormai backdrop trasparente e le lettere si compongono bianche; fuori, #intro in
   `difference` le inverte sulla sezione che passa sotto. Vedi "Blend globale". */
.intro.overlay {
  z-index: 25;                 /* sopra .scroll-root (20), sotto pannello menu (29) */
  background: transparent;
  mix-blend-mode: difference;
}
.intro.overlay .intro-letters,
.intro.overlay .stage-bg,
.intro.overlay .gallery { opacity: 0; }
/* Il suggerimento di scroll è ancora in dissolvenza quando scatta l'overlay: bianco anche
   lui, altrimenti il `difference` del layer lo mostrerebbe in grigio chiaro. */
.intro.overlay .scroll-hint { color: #fff; }
.intro-letters { position: absolute; inset: 0; }

/* Suggerimento di scroll: "Scroll" + un binario verticale sottile su cui scende in loop
   un segmento sfumato. Discreto: il binario è appena accennato (::before, opacità bassa),
   il segmento (::after) è pieno ma sottile e con dissolvenza alle estremità.
   Compare a fine intro (.is-visible, come tagline e monogramma) e svanisce al primo
   accenno di scroll (.gone) — che vince perché dichiarata dopo, a pari specificità. */
.scroll-hint {
  position: absolute; z-index: 12;
  left: 50%; transform: translateX(-50%);
  bottom: 4vh;
  display: flex; flex-direction: column; align-items: center; gap: 0.75rem;
  color: #202020;            /* l'hero a fine intro è chiaro (#E8E8E8) */
  pointer-events: none;
  opacity: 0;
  transition: opacity 700ms ease;
}
.scroll-hint.is-visible { opacity: 1; }
.scroll-hint.gone { opacity: 0; }
.scroll-hint-label {
  font-family: "Helvetica Neue", Arial, sans-serif;
  font-weight: 600; font-size: clamp(0.58rem, 0.78vw, 0.7rem);
  letter-spacing: 0.28em; text-transform: uppercase;
  text-indent: 0.28em;       /* compensa lo spazio che letter-spacing aggiunge in coda */
}
.scroll-hint-line { position: relative; width: 1px; height: 44px; overflow: hidden; }
.scroll-hint-line::before {   /* binario */
  content: ""; position: absolute; inset: 0;
  background: currentColor; opacity: 0.16;
}
.scroll-hint-line::after {    /* segmento che scende */
  content: ""; position: absolute; left: 0; top: 0; width: 100%; height: 45%;
  background: linear-gradient(to bottom, transparent, currentColor 35%, currentColor 65%, transparent);
  animation: scroll-hint-beam 2.6s ease-in-out infinite;
}
/* Metà tempo di discesa, metà di pausa (fuori dal binario, quindi invisibile): il gesto
   si ripete senza risultare insistente. */
@keyframes scroll-hint-beam {
  0%        { transform: translateY(-110%); }
  50%, 100% { transform: translateY(240%); }
}
@media (prefers-reduced-motion: reduce) {
  .scroll-hint { transition: none; }
  .scroll-hint-line::after { animation: none; opacity: 0.5; }
}
/* Stage rivelata: maschera a due bande (alto/basso) che crescono fino a incontrarsi
   al centro -> stessa dinamica del vecchio reveal, ma su un unico layer continuo. */
.stage {
  position: absolute; inset: 0;
  isolation: isolate;   /* il testo (difference) si fonde con sfondo + galleria di
                           questa stage, non col resto (come .portfolio in cardinali) */
  -webkit-mask-image: linear-gradient(#000, #000), linear-gradient(#000, #000);
          mask-image: linear-gradient(#000, #000), linear-gradient(#000, #000);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: top, bottom; mask-position: top, bottom;
  -webkit-mask-size: 100% 0%, 100% 0%; mask-size: 100% 0%, 100% 0%;
  transition: -webkit-mask-size 650ms cubic-bezier(0.65, 0, 0.35, 1),
                      mask-size 650ms cubic-bezier(0.65, 0, 0.35, 1);
}
.intro.reveal-on .stage {
  /* 51% (non 50%): le due bande si sovrappongono di poco al centro -> niente cucitura */
  -webkit-mask-size: 100% 51%, 100% 51%; mask-size: 100% 51%, 100% 51%;
}
/* A reveal concluso la maschera copre tutto: la rimuovo del tutto. Su iOS la maschera
   (buffer offscreen) attorno al testo in mix-blend-mode ne rompe la fusione -> senza
   maschera il blend regge. Visivamente identico (era già piena). */
.intro.reveal-done .stage { -webkit-mask: none; mask: none; }
.stage-bg { position: absolute; inset: 0; background: #E8E8E8; }
/* contenitore semplice (nessun isolamento qui): così il testo bianco in difference
   si fonde anche con lo sfondo #E8E8E8 sottostante -> resta scuro/leggibile ovunque */
.stage-blend { position: absolute; inset: 0; }
/* galleria a scorrimento orizzontale automatico, loop infinito */
.gallery { position: absolute; inset: 0; overflow: hidden; }
.gallery-track {
  position: absolute; top: 0; left: 0; height: 100%;
  display: flex; align-items: center; will-change: transform;
  animation-name: gallery-scroll; animation-timing-function: linear; animation-iteration-count: infinite;
}
/* Seconda traccia: nascosta di default (desktop/tablet), scorre in senso opposto
   (reverse: da -50% a 0 -> si muove verso destra). Visibile solo su mobile. */
.gallery-track--rev { display: none; animation-direction: reverse; }
/* Tutte le slide hanno la STESSA altezza (= altezza max della galleria); la larghezza
   segue l'aspect ratio nativo dell'immagine -> le verticali sono più strette, niente
   crop. Gap uniforme tra le immagini. (durata animazione: inline dal template) */
.slide { flex: 0 0 auto; height: 62vh; margin-right: 10vw; }
.slide img { height: 100%; width: auto; display: block; }
/* Tablet: immagini più basse (una interamente visibile in orizzontale) e gap ridotto */
@media (max-width: 900px) {
  .slide { height: 42vh; margin-right: 6vw; }
}
/* Mobile: anche le landscape più larghe stanno nel viewport; gap minimo */
@media (max-width: 640px) {
  .slide { height: 23vh; margin-right: 4vw; }
  /* Due gallerie impilate e centrate verticalmente come un unico blocco: .gallery
     diventa una colonna centrata (justify-content: center). Le tracce escono dal
     posizionamento assoluto e diventano righe alte quanto le slide. */
  .gallery { display: flex; flex-direction: column; justify-content: center; gap: 4vh; }
  .gallery-track {
    position: relative; top: auto; left: auto;
    height: 23vh;          /* = altezza slide mobile */
    width: max-content;    /* larghezza = contenuto -> translateX(-50%) resta esatto */
  }
  .gallery-track--rev { display: flex; }
}
/* Loop: il nastro ha 2 copie identiche; -50% = una copia -> ripetizione senza salti,
   indipendente dalle larghezze (che variano con portrait/landscape). */
@keyframes gallery-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
/* Nome completo: 3 righe (Andrea / Di / Lorenzo). Le iniziali (lead) coincidono
   con la pila ADL rivelata; le lettere restanti salgono dal basso in stagger. */
.fullname { position: absolute; inset: 0; }
.fn-line {
  position: absolute; left: 5vw; white-space: nowrap;
  font-family: "Helvetica Neue", Arial, sans-serif;
  font-weight: 700; font-size: var(--lf); line-height: 1;
  transform-origin: top left; /* per lo scale del collapse finale */
}
.fn-a { top: var(--row-a); }
.fn-d { top: var(--row-d); }
.fn-l { top: var(--row-l); }
.fn-line .fc {
  display: inline-block; color: #fff;
  mix-blend-mode: difference;   /* bianco + difference: si inverte verso il contrasto
                                   (scuro su chiaro, chiaro su scuro) -> sempre leggibile */
  /* iOS Safari: senza un layer GPU persistente le lettere STATICHE perdono il blend e
     mostrano il colore sorgente (bianco). Le teniamo su un layer -> il blend regge. */
  -webkit-backface-visibility: hidden;
          backface-visibility: hidden;
  will-change: transform;
  transform: translateZ(0);
  opacity: 0;
  transition: opacity 300ms ease, transform 450ms cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--i, 0) * 35ms);
}
/* stato iniziale delle lettere non-iniziali: sotto + invisibili (salgono in stagger) */
.fn-line .fc:not(.lead) { transform: translateY(0.5em) translateZ(0); }
/* le iniziali sono già a destinazione e visibili fin dal reveal (la maschera le
   scopre insieme allo sfondo); nessuno spostamento (ma tengono il layer con translateZ). */
.fn-line .fc.lead { opacity: 1; transform: translateZ(0); transition: none; }
/* name-on: le lettere restanti salgono dal basso in stagger (mantenendo il layer) */
.intro.name-on .fc { opacity: 1; transform: translateZ(0); }
/* collapse: il nome si ricompone in un piccolo monogramma "ADL" in basso a sinistra.
   Le lettere non-iniziali si ritraggono verso l'iniziale (scaleX->0 da sinistra) e
   svaniscono; le iniziali si riducono via font-size + top/left (calcolato in JS) -> nessun
   transform di riga, così il blend "difference" resta attivo anche nel monogramma. */
.intro.collapse-on .fc:not(.lead) {
  opacity: 0; transform: scaleX(0); transform-origin: left center;
  transition: opacity 300ms ease, transform 500ms cubic-bezier(0.65, 0, 0.35, 1);
}
.intro .ltr {
  position: absolute;
  color: #A9432E;   /* lo ereditano anche le .lc (il resto del nome) */
  font-family: "Helvetica Neue", Arial, sans-serif;
  font-weight: 700;
  font-size: var(--lf);
  line-height: 1;
  white-space: nowrap;   /* il resto del nome non deve mai andare a capo */
  will-change: transform;
  transition: transform 800ms cubic-bezier(0.16, 1, 0.3, 1);
}
/* Resto del nome sul layer rosso (visibile su nero, PRIMA del reveal dello sfondo
   chiaro): stessa dinamica di .fn-line .fc:not(.lead) — salgono dal basso in stagger —
   così le due copie (qui e nel .fullname dentro .stage) restano sovrapponibili. */
.intro .ltr .lc {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.5em);
  transition: opacity 300ms ease, transform 450ms cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--i, 0) * 35ms);
}
.intro.name-on .ltr .lc { opacity: 1; transform: translateY(0); }
/* posizioni finali (immagine 3): pila al margine sinistro */
.intro .ltr.a { left: 5vw; top: var(--row-a); }
.intro .ltr.d { left: 5vw; top: var(--row-d); }
.intro .ltr.l { left: 5vw; top: var(--row-l); }
/* Mobile: la dimensione delle lettere è limitata da 11vw (molto meno di 15vh), mentre le
   righe erano fisse in vh -> tre iniziali piccole e lontanissime tra loro. Qui le distanzio
   in PROPORZIONE alla loro dimensione, con lo stesso rapporto del desktop (passo = 1.45 *
   --lf, dove il desktop ha 22vh su un font di 15vh), e centro la pila nel viewport.
   Cambiando --row-* si spostano insieme lettere rosse e nome della stage: restano allineati. */
@media (max-width: 640px) {
  .intro {
    --row-a: calc(50vh - 1.95 * var(--lf));
    --row-d: calc(50vh - 0.50 * var(--lf));
    --row-l: calc(50vh + 0.95 * var(--lf));
  }
}
/* posizioni iniziali: tutte FUORI dal viewport (A sotto, D e L a destra) */
.intro .ltr.a { transform: translate(49vw, 100vh); }
.intro .ltr.d { transform: translate(100vw, 0); }
.intro .ltr.l { transform: translate(100vw, 0); }
/* A parte per prima, D e L leggermente dopo */
.intro .ltr.d, .intro .ltr.l { transition-delay: 100ms; }
/* stage1 = immagine 2 (posizione diagonale) */
.intro.stage1 .ltr.a { transform: translate(49vw, 0); }
.intro.stage1 .ltr.d { transform: translate(57vw, 0); }
.intro.stage1 .ltr.l { transform: translate(63vw, 0); }
/* stage2 = immagine 3 (margine sinistro); stessa durata per tutte -> arrivano insieme.
   Selettori per-lettera per pari specificità con stage1 (vince l'ordine: viene dopo). */
.intro.stage2 .ltr.a,
.intro.stage2 .ltr.d,
.intro.stage2 .ltr.l { transform: translate(0, 0); transition-duration: 850ms; transition-delay: 0ms; }

@media (prefers-reduced-motion: reduce) {
  .intro .ltr { transition: none; }
  .intro .ltr .lc { transition: none; transition-delay: 0s; }
  .stage { transition: none; }
  .gallery-track { animation: none; }
  .fn-line, .fn-line .fc { transition: none; transition-delay: 0s; }
}

/* ===== Scroll guidato + sezioni full-viewport ===== */

/* Contenitore scroll: fisso a schermo, contiene le sezioni impilate. Bloccato durante
   l'intro (overflow hidden); sbloccato a fine intro (classe .ready). Snap verticale. */
.scroll-root {
  position: fixed; inset: 0; z-index: 20;
  overflow-x: hidden; overflow-y: hidden;
  /* mandatory: lo scroll dell'utente porta sempre una sezione a occupare l'intero
     viewport — su DESKTOP vale per tutte le sezioni (scroll guidato). Su mobile invece
     `updateSnapScope()` nel JS della home (hero/v5.js) lo spegne inline oltre le stats: lì rompe
     l'inerzia nativa di iOS e fa rimbalzare in cima le sezioni più alte del viewport. */
  scroll-snap-type: y mandatory;
  background: transparent;

  /* Fetta della sezione successiva che resta scoperta in fondo a bio e reach: quelle
     sezioni sono alte 1 viewport MENO questa banda, così sotto si intravede l'inizio
     della prossima (le cui immagini sono a filo del proprio bordo superiore). L'ultima
     sezione resta alta 1 viewport intero, altrimenti il suo snap point non sarebbe
     raggiungibile (scrollTop massimo = altezza totale - 1 viewport). */
  --peek: clamp(28px, 5vh, 60px);
}

/* Geometria del monogramma, replicata in CSS. Sta su :root e non su .scroll-root perché il
   monogramma è fisso in TUTTE le pagine, non solo in home: la sezione delle porte, che ora
   sta anche in fondo alle gallery, riserva la corsia sinistra con --mono-right, e da lì
   dentro .scroll-root non esiste (una custom property scende, non risale). */
:root {
  /* Bordo destro del monogramma ADL: stessa formula del JS (positionBrandMark) ->
     left 4vw + larghezza monogramma (coeff. 0.70 misurato sui glifi D/L) * bigFont,
     con bigFont = min(150px,15vh,11vw). Traccia il monogramma a QUALSIASI aspect ratio. */
  --mono-right: calc(4vw + 0.70 * min(150px, 15vh, 11vw));
  /* Banda verticale occupata dal monogramma, dal suo bordo SUPERIORE al fondo del
     viewport (stesso JS): base a 6vh dal fondo + due righe alte SF = 0.5 * bigFont con
     gap 0.05*SF -> 2.05 * SF. Serve a tenere immagini e testi fuori dal monogramma. */
  --mono-band: calc(6vh + 1.025 * min(150px, 15vh, 11vw));
  /* La corsia da lasciare libera a sinistra perché un testo non finisca sul monogramma: il
     suo bordo destro più un margine. È una variabile perché la usano due pagine diverse (la
     sezione delle porte in home e la stessa sezione in fondo alle gallery, che alla corsia
     del monogramma deve aggiungerne un'altra) e la formula deve restare una sola. */
  --mono-lane: calc(var(--mono-right) + clamp(14px, 1.8vw, 36px));

  /* ALLINEAMENTO OTTICO: quanto la prima riga di un testo va tirata su perché le sue
     MAIUSCOLE — non la cima invisibile del suo box — si allineino al bordo di un'immagine
     accanto. Il box e l'immagine si allineano già da soli; il glifo no, perché sopra le
     maiuscole ci sono l'ascendente del carattere e la mezza interlinea.

        trim = ascendente + (interlinea − ascendente − discendente) / 2 − altezza maiuscole

     Sono METRICHE DI HELVETICA NEUE misurate sul font vero, non stime, e per questo stanno
     qui e non nel CSS di una pagina: le usano workshop.css e about.css, e due copie di numeri
     misurati sono due copie che prima o poi divergono. In `em`, quindi valgono a qualunque
     corpo: dipendono solo dall'INTERLINEA. Se cambi la `line-height` di un testo che si
     allinea a un'immagine, ricalcola il suo trim — e attenzione al segno: quando l'interlinea
     è più STRETTA della cassa del font (1.12 contro ~1.17) la mezza interlinea è negativa. */
  --trim-caps: 0.224em;    /* interlinea `normal` — occhielli e testi in maiuscolo */
  --trim-title: 0.194em;   /* interlinea 1.08 — i titoli di pagina */
  --trim-name: 0.203em;    /* interlinea 1.12 — i titoli di sezione */
  --trim-prose: 0.458em;   /* interlinea 1.62 — il corpo del testo */
}
/* Scrollbar nascosta. Motivo (bug Safari): .scroll-root è fixed inset:0 e copre TUTTO il
   viewport, ma le sue sezioni sono larghe quanto il content box (viewport meno scrollbar).
   Con la scrollbar classica — macOS "Mostra barre di scorrimento: Sempre", oppure mouse
   collegato — restava una striscia non dipinta da nessuna sezione, che lasciava vedere
   l'hero (#intro) dietro: si intravedeva la gallery scorrere accanto alla scrollbar.
   NB: `scrollbar-gutter` NON risolve, perché è inefficace con le scrollbar overlay che
   macOS/iOS usano di default. */
.scroll-root { scrollbar-width: none; -ms-overflow-style: none; }
.scroll-root::-webkit-scrollbar { width: 0; height: 0; display: none; }
.scroll-root.ready { overflow-y: scroll; }
/* `scroll-snap-stop: always` sulle due sezioni della sequenza guidata iniziale: il
   contenitore NON può "passare sopra" al loro punto di aggancio. È questo che rende il
   passaggio hero -> stats un vero scatto obbligato — anche con una sberla veloce, dove
   il solo `mandatory` lascerebbe scivolare oltre (il classico "one flick scrolls to the
   end" di iOS). Solo qui: le sezioni successive restano attraversabili di slancio. */
.hero-spacer {
  height: 100%; scroll-snap-align: start; scroll-snap-stop: always;
}   /* trasparente: mostra l'hero (#intro) dietro */
.stats {
  height: 100%; scroll-snap-align: start; scroll-snap-stop: always;
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  /* Sfondo: foto full-bleed (cover) + scrim scuro (radiale, più intenso al centro dove
     sta il numero) così il testo bianco resta leggibile. --stats-img è impostata inline
     nel template (usa il CDN in locale, R2 in produzione). #000 = fallback. */
  background-color: #000;
  --stats-img: var(--stats-img-d);   /* desktop/tablet: laphroaig (override su mobile) */
  background-image:
    radial-gradient(ellipse 75% 60% at center, rgba(0,0,0,0.78) 0%, rgba(0,0,0,0.62) 55%, rgba(0,0,0,0.66) 100%),
    var(--stats-img, none);
  background-size: cover; background-position: center; background-repeat: no-repeat;
}
/* Mobile: usa l'immagine verticale (food-straf). Solo questa viene scaricata su mobile;
   la laphroaig (in --stats-img-d) resta non usata -> non caricata. E viceversa. */
@media (max-width: 640px) {
  .stats { --stats-img: var(--stats-img-m); }
}

/* ===== Blend globale degli overlay fissi =====
   #brand-mark e .site-tagline sono figli diretti di <main>, che non ha alcuna proprietà
   che crei un gruppo isolato: mettendo `mix-blend-mode` SUL CONTENITORE (non sui figli,
   che resterebbero chiusi nel suo gruppo) il backdrop diventa tutto ciò che è dipinto
   sotto — hero, gallery e le sezioni dentro .scroll-root. Bianco + difference = sempre
   l'inverso di ciò che passa sotto, senza switch di colore pilotato dal JS.
   NB: il blend NON può mai partire da dentro #intro o .scroll-root: essendo `fixed` sono
   gruppi isolati e i loro discendenti si fondono solo lì dentro. */
/* Monogramma persistente: fisso sopra tutte le sezioni, resta durante lo scroll. */
#brand-mark {
  position: fixed; inset: 0; z-index: 30; pointer-events: none;
  opacity: 0; transition: opacity 200ms ease;
  mix-blend-mode: difference;
  /* Stesso accorgimento delle lettere dell'hero (vedi .fn-line .fc): su iOS Safari senza
     un layer GPU persistente il blend di elementi statici salta e mostra il bianco. */
  -webkit-backface-visibility: hidden; backface-visibility: hidden;
  transform: translateZ(0);
}
#brand-mark.on { opacity: 1; }
.bm {
  position: absolute; color: #fff;   /* sorgente del difference (vedi sopra) */
  font-family: "Helvetica Neue", Arial, sans-serif; font-weight: 700; line-height: 1;
  transition: color 350ms ease;
}
/* Blend sospeso mentre il pannello del menu copre il nome: sul rosso #B94732 il difference
   darebbe ciano. In questo stato (e SOLO in questo) tornano il colore piatto e lo switch
   chiaro/scuro pilotato dal JS. La classe .flat la mette/toglie setMarkBlend(). */
#brand-mark.flat { mix-blend-mode: normal; }
#brand-mark.flat .bm { color: #202020; }            /* sezioni chiare */
#brand-mark.flat.on-dark .bm { color: #fff; }       /* quando la sezione nera lo copre */
/* Segno del monogramma: tre linee orizzontali (hamburger) che ruotano e si sovrappongono
   al centro formando un asterisco. Tutto in em (1em = font-size impostato dal JS = SF,
   come per le lettere), così al resize basta aggiornare font-size/left/top.
   Il quadrato è alto quanto le maiuscole e allineato alla loro banda: questi valori sono
   solo un fallback (metriche Helvetica Neue), il JS li sovrascrive in px misurando la cap
   band del font EFFETTIVO -> allineamento esatto anche con altri fallback di font. */
.bm.mk { width: 0.713em; height: 0.713em; margin-top: 0.171em; }
/* L'asterisco gira su se stesso: giro completo in senso ANTIORARIO ogni 4s (angoli
   negativi; con `rotate` i positivi sono orari).
   Uso la proprietà `rotate` e NON `transform`: quest'ultima è già occupata dalle tre aste
   per il morphing hamburger <-> asterisco, e sono indipendenti, quindi le due cose non si
   pestano i piedi. La rotazione vale solo nello stato .star — da hamburger inclinerebbe le
   linee — e rientra a 0 con una transizione, così l'hover non fa scattare il segno.
   Il ciclo asterisco/hamburger (5s / 1s) lo pilota startStarCycle() in brand-menu.js. */
.bm.mk { rotate: 0deg; transition: rotate 600ms cubic-bezier(0.65, 0, 0.35, 1); }
.bm.mk.star { animation: mk-spin 4s linear infinite; }
@keyframes mk-spin { from { rotate: 0deg; } to { rotate: -360deg; } }
.bm.mk i {
  position: absolute; left: 0; top: 50%; width: 100%; height: 0.10em;  /* tratto sottile */
  margin-top: -0.05em; background: currentColor;
  transform: translateY(var(--y, 0em));
  transition: transform 700ms cubic-bezier(0.65, 0, 0.35, 1);
}
.bm.mk i:nth-child(1) { --y: -0.26em; }
.bm.mk i:nth-child(3) { --y: 0.26em; }
/* asterisco: le tre linee tornano al centro e ruotano a 30/90/150 (60° l'una dall'altra) */
.bm.mk.star i:nth-child(1) { transform: rotate(30deg); }
.bm.mk.star i:nth-child(2) { transform: rotate(90deg); }
.bm.mk.star i:nth-child(3) { transform: rotate(150deg); }
/* Riespansione del monogramma nel nome completo: le lettere non-iniziali si riaprono
   (inverso del collapse) e la L scivola dopo "Di" (posizione calcolata dal JS). */
.bm { white-space: nowrap; }
.bm .bc {
  display: inline-block; font-style: normal;
  opacity: 0; transform: scaleX(0); transform-origin: left center;
  /* In CHIUSURA nessun ritardo: le lettere devono essere già rientrate quando il
     pannello, restringendosi verso sinistra, smette di coprirle. */
  transition: opacity 300ms ease, transform 550ms cubic-bezier(0.65, 0, 0.35, 1);
  transition-delay: 0s;
}
/* In APERTURA le lettere aspettano che il blocco sia arrivato: il mattone entra in
   dissolvenza (500ms) e le lettere sono bianche, quindi prima che sia opaco sarebbero
   bianche sul chiaro. 220ms è il punto in cui il fondo c'è già abbastanza. Non è più il
   vecchio 380ms, che misurava l'avanzata del bordo di una maschera che non c'è più. */
#brand-mark.name-open .bc {
  opacity: 1; transform: none;
  transition-delay: calc(var(--i, 0) * 25ms + 220ms);
}
/* Col blocco mattone dietro, il nome va in bianco (name-open implica sempre .flat, quindi
   qui il colore è quello dipinto davvero; vince sul chiaro per ordine). */
#brand-mark.name-open .bm { color: #fff; }
/* a nome esteso il segno starebbe sopra la "ndrea": lo tolgo */
#brand-mark.name-open .mk { opacity: 0; transition: opacity 250ms ease; }

/* Bersaglio invisibile sopra il monogramma (hover + click). */
.menu-btn {
  position: fixed; z-index: 31; display: none;
  padding: 0; border: 0; background: none; -webkit-appearance: none; appearance: none;
  opacity: 0; cursor: pointer;
}
.menu-btn.on { display: block; }

/* ===== Menu: DUE BLOCCHI che ruotano in posizione ======================================
   Modello dichiarato: il menu di cardinali.studio (public/shared/topbar.css) — le card
   partono più in basso, sfumate e inclinate, e salgono a fuoco con un piccolo sfasamento.
   Due differenze volute rispetto al modello, ed è da lì che discende il resto:
     - NIENTE cover colorato a tutto schermo su desktop: il fondo della pagina resta
       visibile, quindi i blocchi prendono il mattone #B94732 che era del pannello unico;
     - il blocco in basso AVVOLGE il nome esteso, come faceva il pannello unico: dentro di
       lui, dopo le voci, `.menu-name-space` tiene libero l'ingombro di «Andrea Di Lorenzo»
       (le misure le dà layoutMenu; il nome vive sul layer #brand-mark, che sta più in alto).
       Da qui discendono due cose che sembrano di un altro capitolo: sopra il mattone il
       `difference` darebbe ciano, quindi a menu aperto il blend resta sospeso e il nome va
       in bianco — vedi setMenu() in brand-menu.js.
   Il pannello è un CONTENITORE trasparente: il colore ce l'hanno i blocchi. Posizione e
   misure gliele dà layoutMenu() ancorandolo al monogramma, esattamente come prima.
   **Il padding sta sui BLOCCHI e non su `.menu-inner`**: il pannello è ancorato a
   `marginX - pad`, quindi è il padding del blocco a riportare il suo contenuto — voci e
   spazio del nome — esattamente sulla colonna del monogramma. Sommandone due, il nome si
   troverebbe rientrato di un padding rispetto alle voci.
   Niente `clip-path`: mascherava il pannello dall'angolo, e su un blocco ruotato
   ritaglierebbe gli angoli che escono dal riquadro. */
.menu-panel {
  position: fixed; z-index: 29;
  width: max-content; max-width: 92vw;
  display: flex; flex-direction: column; justify-content: flex-end; align-items: flex-start;
  visibility: hidden;   /* chiuso: i link non sono raggiungibili da tastiera */
  /* Il ritardo tiene i blocchi raggiungibili finché non hanno finito di uscire di scena. */
  transition: visibility 0s linear 700ms;
}
.menu-panel.open { visibility: visible; transition: visibility 0s; }
/* Velo bianco a tutto schermo: 30% su desktop (la pagina resta leggibile sotto, il menu si
   stacca), pieno su mobile. È un pseudo-elemento `fixed` e non lo sfondo del pannello,
   perché il pannello su desktop è grande quanto il suo contenuto e coprirebbe solo il suo
   angolo. Sta prima dei figli nell'ordine di disegno, quindi i blocchi gli restano sopra.
   `pointer-events: none` NON è un dettaglio: senza, il velo raccoglie ogni click e il
   bersaglio diventa #menu-panel, che il gestore in brand-menu.js legge come "click dentro
   il menu" — il click fuori smetterebbe di chiudere. */
.menu-panel::before {
  content: ""; position: fixed; inset: 0; pointer-events: none;
  background: #fff; opacity: 0;
  transition: opacity 320ms ease;
}
.menu-panel.open::before { opacity: var(--veil, 0.3); }
/* Niente padding qui: ce l'hanno i blocchi (vedi il commento in testa al blocco). */
.menu-inner { flex: 0 0 auto; display: flex; flex-direction: column; align-items: stretch; align-self: stretch; }
/* Fra i due blocchi lo stacco è stretto: sono una cosa sola. `stretch` su entrambi gli assi
   perché prendano la larghezza del più largo — il pannello è `max-content`, quindi quella
   del menu, che è anche il blocco che deve contenere il nome. Se il menu si allarga, il
   blocco dei recapiti lo segue: al centro della composizione c'è un bordo verticale solo. */
.menu-cards { display: flex; flex-direction: column; gap: calc(var(--pad, 1rem) * 0.55); align-self: stretch; align-items: stretch; }
.menu-card {
  background: #B94732; color: #fff;
  border-radius: var(--r, 16px); padding: var(--pad, 1rem);
  box-shadow: 0 30px 60px -30px rgba(0, 0, 0, 0.45);
  opacity: 0;
  /* Ruotano attorno all'angolo in BASSO A SINISTRA: è l'angolo ancorato al monogramma,
     l'unico che deve restare fermo mentre il resto si raddrizza. */
  transform-origin: 0% 100%;
  transition: opacity 500ms ease, transform 600ms cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform, opacity;
}
/* Inclinazioni OPPOSTE, come nel modello: da chiusi i due blocchi non sembrano un unico
   rettangolo spezzato. Lo scarto verticale è in px e non in em perché il corpo del testo
   dei due blocchi è diverso (lo imposta il JS): in em partirebbero da due altezze diverse. */
.menu-contact { transform: translateY(clamp(30px, 5vh, 66px)) rotate(-3.5deg); }
.menu-nav { transform: translateY(clamp(22px, 3.5vh, 48px)) rotate(3.5deg); }
.menu-panel.open .menu-card { opacity: 1; transform: translateY(0) rotate(0deg); }
/* Prima il menu (è quello che si cerca), poi i recapiti. */
.menu-panel.open .menu-nav { transition-delay: 60ms; }
.menu-panel.open .menu-contact { transition-delay: 160ms; }

.menu-nav { display: flex; flex-direction: column; }
/* Blocco dei recapiti */
.menu-card-head {
  display: block; margin-bottom: 0.7em; opacity: 0.62;
  font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase;
  font-size: 0.78em; line-height: 1.2;
}
.menu-contact-item {
  display: block; color: #fff; white-space: nowrap;
  font-weight: 600; letter-spacing: 0.06em; line-height: 1.95;
  opacity: 0; transform: translateY(0.6em);
  transition: opacity 350ms ease, transform 500ms cubic-bezier(0.16, 1, 0.3, 1), color 200ms ease;
}
.menu-panel.open .menu-contact-item { opacity: 1; transform: none; transition-delay: calc(var(--i, 0) * 45ms + 260ms); }
.menu-contact-item:hover, .menu-contact-item:focus-visible { color: #F2D3CC; }
/* Il blocco dei recapiti è a colori INVERTITI rispetto al menu: bianco con testo mattone.
   I due blocchi restano la stessa cosa (stessa forma, stesso raggio, stessa entrata) e si
   distinguono per il verso del colore, non per un secondo colore. Di conseguenza cambia
   anche il colore dell'hover: il rosa chiaro del menu, su bianco, sparirebbe. */
.menu-contact { background: #fff; color: #B94732; }
.menu-contact .menu-contact-item { color: #B94732; }
.menu-contact .menu-contact-item:hover,
.menu-contact .menu-contact-item:focus-visible { color: #8A3324; }
.menu-item {
  display: block; color: #fff; white-space: nowrap;
  font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; line-height: 1.85;
  opacity: 0; transform: translateY(0.6em);
  transition: opacity 350ms ease, transform 500ms cubic-bezier(0.16, 1, 0.3, 1), color 200ms ease;
}
.menu-panel.open .menu-item { opacity: 1; transform: none; transition-delay: calc(var(--i, 0) * 45ms + 120ms); }
.menu-item:hover, .menu-item:focus-visible { color: #F2D3CC; }
/* Sta DENTRO il blocco del menu, come ultima riga: è il buco che tiene libero il nome.
   `--gap` (largo, 0.85 SF) è lo stacco fra l'ultima voce e il nome — la stessa aria che
   c'era nel pannello unico. */
.menu-name-space { flex: 0 0 auto; display: block; margin-top: var(--gap, 1rem); }

/* X di chiusura: solo mobile (vedi media query). Sta DENTRO il pannello, quindi la
   maschera la scopre insieme al resto; l'angolo in alto a destra è l'ultimo a comparire,
   perciò le do anche una dissolvenza ritardata. */
.menu-close {
  display: none; position: absolute; top: 0; right: 0;
  width: 2.75rem; height: 2.75rem; margin: clamp(0.9rem, 4vw, 1.6rem);
  padding: 0; border: 0; background: none; cursor: pointer;
  opacity: 0; transition: opacity 300ms ease;
}
.menu-panel.open .menu-close { opacity: 1; transition-delay: 380ms; }
.menu-close span {
  position: absolute; left: 50%; top: 50%;
  width: 62%; height: 2px; margin: -1px 0 0 -31%;
  background: #fff;
}
.menu-close span:first-child { transform: rotate(45deg); }
.menu-close span:last-child { transform: rotate(-45deg); }

/* ===== Menu su mobile: pannello a TUTTO SCHERMO ===== */
/* Su schermi stretti la geometria calcolata sul monogramma dà un pannello minuscolo e voci
   di pochi px (il font era 0.32 * SF, e SF dipende da min(150px,15vh,11vw)). Qui la
   geometria la decide il CSS: il JS toglie i propri valori inline (vedi layoutMenu). */
@media (max-width: 760px) {
  .menu-panel {
    left: 0; right: 0; top: 0; bottom: 0;
    width: auto; max-width: none;
    /* Velo più fitto che su desktop (70% contro 30%): su uno schermo piccolo i blocchi
       coprono comunque poco e la pagina viva sotto è rumore. Ma NON pieno — a bianco pieno
       il blocco dei recapiti, che è bianco anche lui, resterebbe appeso alla sola ombra. */
    --veil: 0.7;
    /* A schermo pieno il pannello non ha angoli, ma i BLOCCHI sì: il raggio serve ancora. */
    --r: clamp(14px, 4vw, 22px);
  }
  /* I due blocchi occupano la larghezza disponibile: in `max-content` resterebbero due
     rettangoli stretti in mezzo al bianco. */
  .menu-cards { align-self: stretch; }
  .menu-card { padding: clamp(1rem, 4.5vw, 1.6rem); }
  .menu-inner {
    align-self: stretch;
    --pad: clamp(1.25rem, 6vw, 2rem);
    /* Stacco generoso tra l'ultima voce e "Andrea Di Lorenzo": il contenuto è ancorato in
       basso, quindi aumentare il gap spinge in alto tutto il blocco delle voci. */
    --gap: clamp(2rem, 8vh, 4rem);
    padding: var(--pad);
  }
  /* Le voci vanno a capo invece di sforare: i titoli delle pagine li decide l'admin e
     uno lungo ("HOSPITALITY & INTERIORS") uscirebbe dal viewport. Con l'a-capo la
     spaziatura tra voci la dà il gap, non la line-height. */
  .menu-nav { font-size: clamp(1rem, 5vw, 1.75rem); gap: clamp(0.55rem, 2.2vh, 1.1rem); }
  .menu-item { line-height: 1.25; letter-spacing: 0.08em; white-space: normal; }
  /* La X sta sul velo bianco, quindi va mattone: bianca su bianco sparirebbe. */
  .menu-close { display: block; }
  .menu-close span { background: #B94732; }
  /* Il pannello copre tutto: la tagline (fixed, z-index 30) resterebbe sopra il velo.
     La nascondo finché il menu è aperto. */
  body.menu-open .site-tagline { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .bm.mk i, .bm .bc, .menu-panel, .menu-panel::before, .menu-card, .menu-item,
  .menu-contact-item, .menu-close { transition: none; }
  /* Senza transizione i blocchi resterebbero fermi nella posizione di partenza (ruotati e
     più in basso): la loro entrata È il transform, quindi qui va tolta anche quella. */
  .menu-contact, .menu-nav { transform: none; }
  .bm.mk { animation: none; transition: none; }   /* niente rotazione continua */
}

/* ===== Collegamento a Instagram (partials/instagram.html) =====================
   L'altro angolo dell'interfaccia condivisa: in alto a destra, con la stessa geometria del
   monogramma rispecchiata (4vw dal bordo, 6vh dal proprio) e lo stesso trattamento —
   bianco + `difference`, così si inverte su qualunque cosa gli passi sotto invece di dover
   scegliere un colore che vada bene sia sulle sezioni chiare sia sulle foto scure.
   Sta in baseof.html, quindi è di ogni pagina; il blend regge perché è figlio di <main> e
   non di un contenitore `fixed` (vedi "Blend globale degli overlay fissi" più sopra).

   `absolute` di suo, cioè SCORRE con la pagina: è in cima al documento e sparisce appena si
   scorre, come qualsiasi altro elemento del contenuto. Diventa `fixed` solo dove la pagina
   NON scorre — home, gallery e portfolio sono a schermo intero e a muoversi è ciò che sta
   dentro un contenitore (le sezioni, la griglia, la lista dei lavori): lì un'icona che
   "scorre via" non avrebbe nessuno scroll da seguire, e resterebbe semplicemente immobile in
   un angolo qualsiasi. */
.ig {
  position: absolute; top: 6vh; right: 4vw; z-index: 60;
  display: block; width: clamp(20px, 2.2vw, 26px);
  color: #fff;
  mix-blend-mode: difference;
  /* Stesso accorgimento del monogramma (vedi `.bm`): su iOS Safari, senza un layer GPU
     persistente, il blend di un elemento statico salta e mostra il colore sorgente. */
  -webkit-backface-visibility: hidden; backface-visibility: hidden;
  transform: translateZ(0);
  transition: opacity 400ms ease, transform 400ms cubic-bezier(0.16, 1, 0.3, 1);
}
.ig svg { display: block; width: 100%; height: auto; }
.ig:hover, .ig:focus-visible { transform: translateZ(0) scale(1.12); }

/* Le tre pagine a schermo intero: fissa, e in scena insieme al monogramma. Su queste il
   monogramma è `data-manual` — arriva col warp delle celle o a fine intro — e un'icona che
   comparisse da sola prima di lui sarebbe l'unico pezzo di interfaccia sullo schermo. */
body.is-home .ig,
body.is-rgallery .ig,
body.is-portfolio .ig { position: fixed; opacity: 0; }
body.brand-on .ig { opacity: 1; }
/* Col menu aperto sparisce, come la tagline: su mobile il pannello copre tutto lo schermo e
   l'icona (che gli sta sopra) resterebbe l'unica cosa fuori posto sul rosso. */
body.menu-open .ig { opacity: 0; }

/* Qui c'era `body.has-header .ig { top: 6.2rem }`: sulle pagine col guscio scuro una barra
   alta ~75px occupava la cima, e l'icona doveva scavalcarla. Senza barra torna a 6vh, cioè
   nella stessa corsia del monogramma su tutte le pagine — che era il punto. */

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

/* ===== Firma dello studio (partials/credit.html) =============================
   Il quarto angolo: in basso a destra, la riga che dice chi ha fatto il sito. Modello
   dichiarato: le piattaforme (Format, Framer) mettono la propria firma lì, minuscola e appena
   leggibile — deve essere trovabile, non notabile.

   `fixed` SEMPRE, su ogni pagina, anche su quelle che scorrono davvero (workshop, about, il
   vecchio guscio scuro): al contrario del collegamento a Instagram — che è in cima al
   documento e può permettersi di scorrere via — questa deve restare nell'angolo, e "in fondo
   al documento" non è "in fondo allo schermo".

   Il grigio non è un colore: è bianco + `difference` a opacità bassa. Il difference dà sempre
   l'inverso di ciò che passa sotto (quindi resta leggibile sia sulle foto scure sia sulle
   sezioni chiare), e l'opacità lo ricompone col fondo fino a lasciarne solo un velo. Un grigio
   fisso invece sparirebbe su metà delle sezioni e griderebbe sull'altra metà.

   NON la geometria del monogramma (6vh/4vw): quella è la corsia dell'interfaccia, e la firma
   non ne fa parte — sta nell'angolo vero, appena dentro il bordo. In clamp e non in vh/vw puri
   perché è un margine tipografico, non una proporzione della finestra: su uno schermo alto
   1.4vh sarebbero 20px, e la firma tornerebbe a galleggiare. */
.credit {
  position: fixed;
  bottom: clamp(8px, 1.4vh, 14px); right: clamp(10px, 1.2vw, 18px); z-index: 60;
  font-family: "Helvetica Neue", Arial, sans-serif;
  font-weight: 400; font-size: clamp(0.53rem, 0.57vw, 0.64rem);
  letter-spacing: 0.05em; line-height: 1;
  color: #fff; text-decoration: none;
  opacity: 0.28;
  mix-blend-mode: difference;
  /* Stesso accorgimento del monogramma e di `.ig`: su iOS Safari, senza un layer GPU
     persistente, il blend di un elemento statico salta e mostra il colore sorgente. */
  -webkit-backface-visibility: hidden; backface-visibility: hidden;
  transform: translateZ(0);
  transition: opacity 400ms ease;
}
.credit:hover, .credit:focus-visible { opacity: 0.75; text-decoration: underline; }

/* Le tre pagine a schermo intero: in scena insieme al monogramma, come `.ig`. Lì il monogramma
   è `data-manual` (arriva col warp delle celle o a fine intro) e una firma che comparisse
   prima di lui sarebbe l'unico pezzo di interfaccia sullo schermo. */
body.is-home .credit,
body.is-rgallery .credit,
body.is-portfolio .credit { opacity: 0; }
body.brand-on .credit { opacity: 0.28; }
/* Col menu aperto sparisce, come la tagline e `.ig`: sul rosso #B94732 il difference darebbe
   ciano, e su mobile il pannello copre tutto lo schermo. */
body.menu-open .credit { opacity: 0; }

/* Qui c'era `body.has-header .site-footer { padding-bottom: 4rem }`: il footer dello scaffold
   stava nel flusso, e arrivati in fondo la firma fissa gli si posava sopra. Senza footer non
   c'è più niente su cui posarsi. */

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

/* Sezione stats: tre numeri affiancati, tutti con slot-machine simultanea. */
/* Altezza cella 1.3em (non 1em): Georgia usa cifre "old-style" e 3/4/5/7/9 scendono
   sotto la linea di base — con 1em la finestra overflow:hidden ne tagliava la coda.
   NB: il passo della slot-machine nel JS della home (REEL_STEP) deve valere 1.3. */
.reel { flex: 0 0 auto; height: 1.3em; overflow: hidden; }
.reel-inner { display: block; will-change: transform; }
.reel-g { display: block; height: 1.3em; line-height: 1.3; text-align: center; }
.lab-c {
  display: inline-block; white-space: pre;
  opacity: 0; transform: translateY(0.7em);
  transition: opacity 400ms ease, transform 500ms cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--i, 0) * 28ms);
}
.stat-item-lab.in .lab-c { opacity: 1; transform: none; }

.stats-all {
  display: flex;
  width: 100%; padding: 0 4vw;
  align-items: flex-start; justify-content: center;
  gap: clamp(1.2rem, 6vw, 6rem);
}
.stat-item { display: flex; flex-direction: column; align-items: center; min-width: 0; text-align: center; }
.stat-item-num {
  display: flex; justify-content: center;
  font-family: Georgia, "Times New Roman", serif; font-weight: 400;
  /* 1.3 come i reel: lascia spazio ai discendenti delle cifre old-style */
  font-size: clamp(1.6rem, min(13vh, 9vw), 8rem); line-height: 1.3;
  color: #fff; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.stat-item-lab {
  margin-top: 0.6vh;
  font-family: "Helvetica Neue", Arial, sans-serif; font-weight: 600;
  font-size: clamp(0.62rem, 1.15vw, 1rem);
  letter-spacing: 0.14em; text-transform: uppercase; color: #fff;
}

/* Mobile: i tre numeri si incolonnano (760px, la stessa soglia a cui si impilano about,
   reach e generi). Affiancati, la loro dimensione la decideva la LARGHEZZA — il ramo 9vw
   del clamp vince su min(13vh, …) su qualunque telefono — quindi tre numeri da poche
   decine di px al centro di una sezione alta un viewport intero: tutto lo spazio stava
   sopra e sotto. In colonna il vincolo torna verticale e i numeri possono crescere.
   Le misure restano in min(vh, vw): il tetto in vh è ciò che tiene la pila dentro la
   sezione anche sui viewport bassi (un telefono in orizzontale sotto i 760px di larghezza),
   che qui è alta esattamente 100% e non scrolla al proprio interno. */
@media (max-width: 760px) {
  .stats-all {
    flex-direction: column;
    align-items: center;
    gap: clamp(1.5rem, 5vh, 3rem);
  }
  .stat-item-num { font-size: clamp(2.2rem, min(11vh, 22vw), 7rem); }
  /* Ora che c'è spazio, la label smette di essere il ripiego a 0.62rem. Resta però
     abbondantemente dentro la riga: le lettere sono span separati (stagger d'entrata) e
     andrebbero a capo una per una, spezzando la parola invece della frase. */
  .stat-item-lab { margin-top: 0.9vh; font-size: clamp(0.7rem, 2.9vw, 0.95rem); }
}

/* ===== Sezione "about" (chi è Andrea) — sfondo chiaro #E8E8E8 ===== */
/* Layout stile reference: titolo grande in alto a sx + descrizione sotto; a destra la
   frase-manifesto e il ritratto svelato da una maschera. Impilati su mobile. */
.about {
  /* Poco meno di 1 viewport, così in fondo resta scoperta la fascia --peek con l'inizio
     dell'immagine della sezione "reach". `min-height` e non `height`: se il contenuto non
     ci sta (schermi bassi) la sezione CRESCE. Prima invece scrollava internamente
     (.about-inner con overflow-y: auto) e quello scroller annidato si mangiava il primo
     gesto dell'utente: sembrava che lo scroll della pagina non rispondesse, e su touch
     rompeva l'inerzia nativa. */
  min-height: calc(100% - var(--peek)); scroll-snap-align: start;
  display: flex;                /* fa "stirare" .about-inner all'altezza della sezione */
  background: #E8E8E8; color: #1a1a1a;
  overflow: hidden;
  /* Indent condiviso (paragrafo, e su mobile anche l'immagine): parte oltre il monogramma. */
  --desc-indent: max(0px, calc(var(--mono-right) + clamp(24px, 3vw, 60px) - clamp(1.6rem, 5vw, 5rem)));
}
.about-inner {
  flex: 1 1 auto; min-width: 0;
  display: grid;
  grid-template-columns: 1fr minmax(300px, 40%);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: start;
  align-content: safe center;   /* centrato quando avanza spazio, dall'alto quando eccede */
  padding: clamp(2.2rem, 6vh, 5rem) clamp(1.6rem, 5vw, 5rem);
}
.about-left, .about-right { display: flex; flex-direction: column; min-width: 0; }

/* Titolo grande in alto a sinistra (reveal by word) */
.about-title {
  margin: 0; font-family: "Helvetica Neue", Arial, sans-serif;
  font-weight: 700; line-height: 1.08; letter-spacing: -0.02em; color: #111;
  font-size: clamp(2rem, 4.6vw, 4.2rem);
}
.about-title .tl { display: block; }   /* ogni riga del titolo su una riga a sé */
.about-title .w { display: inline-block; overflow: hidden; vertical-align: top; }
.about-title .w-in {
  display: inline-block; transform: translateY(120%);
  transition: transform 720ms cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--w, 0) * 70ms);
}
.about.in .about-title .w-in { transform: none; }

/* Descrizione sotto (reveal by line: le righe sono create in JS) */
.about-desc {
  margin-top: clamp(2rem, 7vh, 4.5rem);
  /* Colonna paragrafo spostata a destra: parte appena oltre il monogramma (+ gap). */
  margin-left: var(--desc-indent);
  max-width: 60ch; color: #333;
  font-size: clamp(0.95rem, 1.15vw, 1.12rem); line-height: 1.62;
}
.about-desc p { margin: 0; }
.about-desc p + p { margin-top: 0.85em; }
.about-lead { font-style: italic; font-size: 1.12em; line-height: 1.5; color: #262626; }
.about-desc .ln { display: block; overflow: hidden; }
.about-desc .ln-in {
  display: block; transform: translateY(110%);
  transition: transform 640ms cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--l, 0) * 45ms);
}
.about.in .about-desc .ln-in { transform: none; }
/* Fallback se il JS non splitta (nessun .ln): il testo resta comunque visibile. */

/* Frase-manifesto sopra l'immagine (più piccola del titolo per non comprimere l'immagine) */
.about-statement {
  margin: 0 0 clamp(1.2rem, 3vh, 2.2rem);
  font-family: "Helvetica Neue", Arial, sans-serif;
  font-weight: 500; line-height: 1.32; color: #1a1a1a;
  font-size: clamp(1rem, 1.5vw, 1.5rem); max-width: 32ch;
  opacity: 0; transform: translateY(18px);
  transition: opacity 700ms ease, transform 800ms cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: 120ms;
}
.about.in .about-statement { opacity: 1; transform: none; }

/* Immagine a destra, già in posizione, svelata da una maschera che si allarga dal basso */
.about-portrait { margin: 0; overflow: hidden; }
.about-portrait img {
  width: 100%; height: auto; aspect-ratio: 4 / 5; object-fit: cover; max-height: 70vh;
  display: block; background: #d8d8d8;
  clip-path: inset(100% 0 0 0);   /* nascosta: la banda visibile ha altezza 0 in basso */
  transition: clip-path 950ms cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: 150ms; will-change: clip-path;
}
.about.in .about-portrait img { clip-path: inset(0 0 0 0); }

@media (max-width: 760px) {
  /* Stack verticale: titolo, frase, immagine, descrizione. display:contents fa uscire i
     figli dai wrapper left/right così l'ordine può essere ridefinito. */
  .about-inner { display: flex; flex-direction: column; gap: clamp(1.4rem, 4vw, 2rem); }
  .about-left, .about-right { display: contents; }
  .about-title { order: 1; font-size: clamp(1.9rem, 8vw, 3rem); }
  .about-statement { order: 2; max-width: none; }
  /* Su mobile l'immagine ha lo stesso indent del paragrafo (parte a destra del monogramma).
     align-self:stretch la fa riempire la larghezza disponibile (container - margin), a
     prescindere dalla dimensione intrinseca dell'immagine (robusto se non è ancora caricata). */
  .about-portrait { order: 3; flex: 0 0 auto; align-self: stretch; margin-left: var(--desc-indent); }
  .about-portrait img { width: 100%; aspect-ratio: 4 / 5; max-height: 52vh; }
  .about-desc { order: 4; margin-top: 0; padding-top: 0; }
}

/* ===== Layout "reach" — immagine grande da un lato, testo in colonna dall'altro =====
   Non è UNA sezione ma un layout, usato da due: "reach" (immagine a sinistra) e la sezione
   finale .reach-flip (immagine a destra), che è la stessa cosa rispecchiata — vedi in fondo
   a questo blocco.
   Desktop: due colonne dentro UN viewport (la sezione deve restare alta 1 schermo, altrimenti
   intrappola lo snap mandatory dello .scroll-root). L'immagine è a filo del bordo laterale e
   di quello superiore: il suo inizio è proprio la fetta che si intravede in fondo alla sezione
   precedente. In basso resta libera la banda del monogramma fisso, più la fetta --peek della
   sezione successiva. Su mobile diventa una colonna. */
.reach {
  height: calc(100% - var(--peek)); scroll-snap-align: start;
  background: #E8E8E8; color: #1a1a1a;
  overflow: hidden;
}
.reach-track {
  height: 100%;
  display: grid;
  /* Colonna di testo stretta come nella reference (~1/4), il resto all'immagine. */
  grid-template-columns: minmax(0, 1fr) clamp(200px, 26%, 460px);
  align-items: center;
  gap: clamp(1rem, 2.5vw, 2.5rem);
  /* Niente padding sopra e a sinistra: l'immagine è a filo. In basso lo spazio arriva
     fino al bordo superiore del monogramma: --mono-band è misurata dal fondo del
     VIEWPORT, la sezione finisce --peek più in alto -> va scalata di --peek. */
  padding: 0 clamp(1.2rem, 3vw, 3rem)
           calc(var(--mono-band) - var(--peek) + clamp(0.6rem, 2vh, 1.6rem)) 0;
}
.reach-figure { margin: 0; overflow: hidden; min-height: 0; align-self: stretch; }
.reach-figure img {
  display: block; width: 100%; height: 100%; object-fit: cover; background: #d8d8d8;
  clip-path: inset(100% 0 0 0);   /* svelata dal basso, come nella bio */
  transition: clip-path 950ms cubic-bezier(0.16, 1, 0.3, 1);
  will-change: clip-path;
}
/* .img-in arriva prima di .in (vedi observeSection nel JS della home): l'immagine si svela
   appena la sezione affiora, così la fetta in fondo alla bio mostra la foto e non il grigio. */
.reach.img-in .reach-figure img { clip-path: inset(0 0 0 0); }
.reach-text { min-width: 0; align-self: center; }

/* Corpi identici a quelli "corrispondenti" della bio: il titolo ha la misura della
   frase-manifesto (.about-statement), sottotitolo e paragrafi quella di .about-desc. */
.reach-title {
  margin: 0; font-family: "Helvetica Neue", Arial, sans-serif;
  font-weight: 700; line-height: 1.14; letter-spacing: -0.01em; color: #111;
  text-transform: uppercase;
  font-size: clamp(1rem, 1.5vw, 1.5rem);
}
.reach-subtitle {
  margin: clamp(0.9rem, 2.2vh, 1.6rem) 0 0;
  font-weight: 700; line-height: 1.34; color: #1a1a1a;
  font-size: clamp(0.95rem, 1.15vw, 1.12rem);
}
.reach-p {
  margin: 1.1rem 0 0;
  font-size: clamp(0.95rem, 1.15vw, 1.12rem); line-height: 1.62; color: #333;
}
/* Ingresso: ogni blocco entra scorrendo da destra, con un leggero delay sul precedente. */
.reach-text > * {
  opacity: 0; transform: translateX(48px);
  transition: opacity 700ms ease, transform 800ms cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--i, 0) * 100ms);
}
.reach.in .reach-text > * { opacity: 1; transform: none; }

/* ---- Rispecchiata: immagine a DESTRA, testo a sinistra --------------------------------
   È l'ULTIMA sezione, quindi — a differenza di .reach — resta alta 1 viewport intero: lo
   scrollTop massimo è (totale - 1 viewport), e una sezione più corta avrebbe uno snap point
   irraggiungibile (con `mandatory` lo scroll rimbalzerebbe indietro).
   Ne segue anche il padding inferiore: --mono-band è misurata dal fondo del VIEWPORT, e qui
   la sezione ci arriva davvero -> niente da scalare, a differenza di .reach che finisce
   --peek più in alto. */
/* NB: .reach-flip è stata l'ULTIMA sezione, e allora era alta un viewport intero con la banda
   del monogramma non scalata. Da quando sotto c'è "contact" vale per lei la regola di tutte le
   altre: --peek in meno (è la fetta che lascia intravedere la sezione dopo) e --mono-band
   scalata di altrettanto, perché non arriva più al fondo del viewport. Le due cose vanno
   insieme: cambiarne una sola lascia il testo sotto il monogramma o una fetta che non si vede. */
.reach-flip { height: calc(100% - var(--peek)); }
.reach-flip .reach-track {
  /* colonne invertite: la stretta (testo) a sinistra, l'immagine prende il resto. Nel
     markup il testo viene per primo, quindi l'ordine delle celle è già questo. */
  grid-template-columns: clamp(200px, 26%, 460px) minmax(0, 1fr);
  /* l'immagine è a filo a DESTRA: il padding passa dall'altro lato */
  padding: 0 0 calc(var(--mono-band) - var(--peek) + clamp(0.6rem, 2vh, 1.6rem)) clamp(1.2rem, 3vw, 3rem);
}
/* I blocchi entrano da sinistra, come l'originale li fa entrare da destra: il movimento
   segue il lato in cui vive il testo. */
.reach-flip .reach-text > * { transform: translateX(-48px); }
.reach-flip.in .reach-text > * { transform: none; }

/* ===== Sezione "contact" — l'ultima schermata =====================================
   L'invito a scrivere, i recapiti e il form. È l'ULTIMA, quindi resta alta un viewport
   intero: lo scrollTop massimo è (totale - 1 viewport) e una sezione più corta avrebbe uno
   snap point irraggiungibile — con `mandatory` lo scroll rimbalzerebbe indietro. Per lo
   stesso motivo qui la banda del monogramma NON si scala di --peek: al fondo del viewport
   questa sezione ci arriva davvero.
   Stesso fondo chiaro e stesse proporzioni di "reach": due colonne, testo e form. */
.contact {
  height: 100%; scroll-snap-align: start;
  background: #E8E8E8; color: #1a1a1a;
  overflow: hidden;
}
.contact-track {
  height: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(280px, 34%, 520px);
  align-items: center;
  gap: clamp(1.6rem, 4vw, 4rem);
  padding: clamp(2rem, 6vh, 4rem) clamp(1.2rem, 3vw, 3rem)
           calc(var(--mono-band) + clamp(0.6rem, 2vh, 1.6rem)) clamp(1.2rem, 3vw, 3rem);
}
/* Il testo sta a destra del monogramma, come il paragrafo della bio: --mono-right replica
   la geometria che il JS calcola per #brand-mark. */
.contact-text { padding-left: var(--mono-right); }

/* Entrata: come in "reach", i blocchi salgono in stagger quando la sezione viene svelata
   (classe .in, messa da revealSecs in js/home/hero.js). */
.contact-text > *, .contact-form {
  opacity: 0; transform: translateY(28px);
  transition: opacity 700ms ease calc(var(--i, 0) * 90ms),
              transform 800ms cubic-bezier(0.16, 1, 0.3, 1) calc(var(--i, 0) * 90ms);
}
.contact.in .contact-text > *, .contact.in .contact-form { opacity: 1; transform: none; }

.contact-title {
  margin: 0;
  font-family: "Helvetica Neue", Arial, sans-serif;
  font-weight: 700; line-height: 1.05; text-transform: uppercase; letter-spacing: 0.01em;
  font-size: clamp(1.6rem, 3.4vw, 3.2rem);
}
.contact-p {
  margin: clamp(1rem, 2.4vh, 1.8rem) 0 0; max-width: 46ch;
  font-size: clamp(0.92rem, 1.05vw, 1.05rem); line-height: 1.6; color: #3a3a3a;
}
.contact-meta {
  margin: clamp(1.2rem, 3vh, 2rem) 0 0; padding: 0; list-style: none;
  display: flex; flex-wrap: wrap; gap: 0.4rem clamp(1rem, 2.5vw, 2rem);
  font-size: 0.78rem; letter-spacing: 0.1em; text-transform: uppercase;
}
.contact-meta a { color: #111; text-decoration: none; border-bottom: 1px solid transparent; }
.contact-meta a:hover, .contact-meta a:focus-visible { border-bottom-color: currentColor; }

/* ---- Il form -------------------------------------------------------------------------
   Campi senza riquadro, solo una riga sotto: sul resto del sito non c'è un solo bordo, e
   un input di sistema qui stonerebbe più di quanto aiuti. */
.contact-form { display: flex; flex-direction: column; gap: clamp(0.8rem, 2vh, 1.2rem); }
.contact-field { display: flex; flex-direction: column; gap: 0.35rem; }
.contact-field > span {
  font-size: 0.7rem; letter-spacing: 0.14em; text-transform: uppercase; color: #6a6a6a;
}
.contact-field input, .contact-field textarea {
  font: inherit; font-size: 0.95rem; color: #111;
  background: transparent; border: 0; border-bottom: 1px solid #b9b9b9;
  padding: 0.45rem 0; border-radius: 0;   /* iOS arrotonda gli input di suo */
  transition: border-color 260ms ease;
}
.contact-field textarea { resize: vertical; min-height: 4.5rem; }
.contact-field input:focus, .contact-field textarea:focus {
  outline: none; border-bottom-color: #111;
}
/* La TRAPPOLA: fuori campo, non `display: none` — alcuni bot saltano ciò che è nascosto
   così, e un campo che non esiste non può tradirli. Fuori anche dal fuoco da tastiera
   (tabindex=-1 nel markup) e dai lettori di schermo (aria-hidden). */
.contact-trap {
  position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;
}
.contact-send {
  align-self: flex-start; margin-top: clamp(0.4rem, 1.5vh, 1rem);
  font: inherit; font-size: 0.78rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: #fff; background: #111; border: 0; border-radius: 0;
  padding: 0.85rem clamp(1.4rem, 3vw, 2.4rem); cursor: pointer;
  transition: opacity 260ms ease;
}
.contact-send:hover, .contact-send:focus-visible { opacity: 0.82; }
.contact-send[disabled] { opacity: 0.45; cursor: default; }
/* L'esito. Vuoto non occupa spazio: la riga compare solo quando c'è qualcosa da dire. */
.contact-state { margin: 0; font-size: 0.82rem; line-height: 1.5; color: #3a3a3a; }
.contact-state:empty { display: none; }
.contact-state.is-error { color: #A9432E; }

/* ---- Riquadro che cicla fra più foto -------------------------------------------------
   Stessa idea dei numeri che ruotano nella sezione nera: un posto solo, contenuto che
   cambia. Le immagini sono impilate e si alternano in dissolvenza; a girare il ciclo è il
   JS, che sposta la classe .is-on.
   La PRIMA immagine resta nel FLUSSO e dà la misura al riquadro: così continuano a valere
   le regole normali di .reach-figure img (altezza piena su desktop, aspect-ratio su
   mobile) e non serve ridichiarare la geometria in due posti. Le altre le stanno sopra. */
.reach-roll {
  position: relative;
  --roll-hold: 3600;   /* ms di posa su ogni foto (numero puro: lo rilegge il JS) */
}
.reach-roll img { transition: opacity 900ms ease, clip-path 950ms cubic-bezier(0.16, 1, 0.3, 1); }
.reach-roll img + img { position: absolute; inset: 0; width: 100%; height: 100%; }
/* Tutte trasparenti tranne quella di turno. Non uso `visibility` perché la dissolvenza
   deve poter incrociare le due immagini. */
.reach-roll img { opacity: 0; }
.reach-roll img.is-on { opacity: 1; }

/* ---- Porta verso la gallery ----------------------------------------------------------
   Stesso trattamento dei titoli di genres: maiuscolo e sottolineatura che cresce da
   sinistra. Foto ed etichetta sono celle separate della griglia, quindi ad accenderle
   insieme è il JS (classe .is-hot su entrambi i bersagli con lo stesso data-warp). */
.reach-more {
  margin: clamp(1.2rem, 3vh, 2rem) 0 0;
  font-family: "Helvetica Neue", Arial, sans-serif;
  font-weight: 700; line-height: 1.2; text-transform: uppercase; letter-spacing: 0.06em;
  font-size: clamp(0.95rem, 1.15vw, 1.12rem);
}
.reach-more a { color: #111; text-decoration: none; }
.reach-more a::after {
  content: ""; display: block; height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: left center;
  transition: transform 420ms cubic-bezier(0.16, 1, 0.3, 1);
}
.reach-figure[data-warp], .reach-more[data-warp] { cursor: pointer; }
.reach-figure[data-warp] { transition: transform 500ms cubic-bezier(0.16, 1, 0.3, 1); }
.reach-figure[data-warp].is-hot { transform: scale(1.02); }
.reach-more[data-warp].is-hot a::after,
.reach-more a:focus-visible::after { transform: scaleX(1); }
@media (hover: none) {
  /* Su touch `:hover` resta appiccicato all'ultimo elemento toccato: niente stati accesi. */
  .reach-figure[data-warp].is-hot { transform: none; }
  .reach-more[data-warp].is-hot a::after { transform: scaleX(0); }
}

/* ===== Sezione "genres" — tre immagini e, sotto, tre colonne di testo ===== */
/* Le immagini sono a filo del bordo superiore, così la loro cima è la fetta che si
   intravede in fondo alla sezione "reach". La griglia parte oltre il monogramma fisso
   (--mono-right), che resta nel margine sinistro invece di finire sopra la prima immagine.
   Alta --peek in meno del viewport: sotto c'è ancora una sezione (.reach-flip), e la fetta
   scoperta è ciò che ne lascia intravedere l'immagine. Quando "genres" era l'ultima questa
   era invece alta 1 viewport intero. */
.genres {
  height: calc(100% - var(--peek)); scroll-snap-align: start;
  background: #E8E8E8; color: #1a1a1a;
  overflow: hidden;
}
.genres-grid {
  --gap-x: clamp(0.7rem, 2.2vw, 2.4rem);
  --gap-y: clamp(0.8rem, 2.2vh, 1.6rem);
  --pad-r: clamp(1.2rem, 3vw, 3rem);
  --pad-b: clamp(1.4rem, 4.5vh, 3rem);
  --pad-l: max(var(--pad-r), var(--mono-lane));
  /* Spazio verticale disponibile per le immagini: il viewport meno la riga di testo
     (stima prudente: 3-4 righe + titolo), il gap e il padding inferiore. */
  --img-h-max: calc(100vh - var(--peek) - 8rem - var(--gap-y) - var(--pad-b));

  height: 100%;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* Riga 1: immagini, alte quanto impone il loro aspect ratio (vedi .genre-figure).
     Riga 2: le tre colonne di testo, subito sotto (align-content: start) — lo spazio che
     avanza resta in fondo alla sezione, dove sta il monogramma. */
  grid-template-rows: auto auto;
  align-content: start;
  column-gap: var(--gap-x);
  row-gap: var(--gap-y);
  padding: 0 var(--pad-r) var(--pad-b) var(--pad-l);
  /* Con ratio 2:3 l'altezza dell'immagine è 1.5x la larghezza della colonna: su schermi
     larghi e bassi (ultrawide) sforerebbe l'altezza disponibile. Limito la larghezza
     TOTALE della griglia — che resta ancorata a sinistra, quindi posizione e allineamento
     delle immagini non cambiano: si stringe solo il lato destro. */
  max-width: calc(var(--pad-l) + var(--pad-r) + 2 * var(--gap-x) + 3 * var(--img-h-max) * 2 / 3);
}
/* Titolo della sezione — facoltativo, e infatti in home non c'è: lì la sezione è una delle
   schermate del mazzo di affermazioni, e un'intestazione la spezzerebbe. In fondo a una
   gallery invece sì, perché è il punto in cui il contenuto cambia argomento.
   Prima riga della griglia per intero, così le colonne restano allineate sotto di lui e i
   margini (--pad-l e compagni, dichiarati su .genres-grid) non vanno ridichiarati qui.
   Corpo: un gradino sopra i titoli delle colonne, stessa famiglia — è la loro intestazione,
   non una quarta colonna. */
.genres-heading {
  grid-column: 1 / -1;
  margin: 0 0 clamp(0.4rem, 1.2vh, 1rem);
  font-family: "Helvetica Neue", Arial, sans-serif;
  font-weight: 700; line-height: 1.1; color: #111;
  text-transform: uppercase; letter-spacing: 0.04em;
  font-size: clamp(1.1rem, 1.7vw, 1.6rem);
  /* Entra come i testi delle colonne, con lo stesso movimento e un attimo prima (--i vale 0
     e non è dichiarato: il ritardo è quello base). */
  opacity: 0; transform: translateY(20px);
  transition: opacity 700ms ease, transform 800ms cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: 240ms;
}
.genres.in .genres-heading { opacity: 1; transform: none; }

/* Aspect ratio nativo delle tre foto (1708x2560 = 2:3): l'altezza segue la larghezza
   della colonna, così l'immagine NON viene ritagliata. */
.genre-figure { margin: 0; overflow: hidden; aspect-ratio: 2 / 3; }
.genre-figure img {
  display: block; width: 100%; height: 100%; object-fit: cover; background: #d8d8d8;
  clip-path: inset(100% 0 0 0);   /* svelata dal basso, come nelle altre sezioni */
  transition: clip-path 950ms cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--i, 0) * 140ms);
  will-change: clip-path;
}
.genres.img-in .genre-figure img { clip-path: inset(0 0 0 0); }
.genre-text { min-width: 0; }
.genre-title {
  margin: 0 0 0.5em; font-family: "Helvetica Neue", Arial, sans-serif;
  font-weight: 700; line-height: 1.2; color: #111;
  text-transform: uppercase; letter-spacing: 0.06em;
  font-size: clamp(0.95rem, 1.15vw, 1.12rem);
}
.genre-p {
  margin: 0;
  font-size: clamp(0.88rem, 1.05vw, 1.02rem); line-height: 1.55; color: #333;
}
/* Ingresso: i testi salgono con lo stesso stagger delle immagini, poco dopo. */
.genre-text {
  opacity: 0; transform: translateY(20px);
  transition: opacity 700ms ease, transform 800ms cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(240ms + var(--i, 0) * 140ms);
}
.genres.in .genre-text { opacity: 1; transform: none; }

/* ---- Colonna cliccabile (apre la gallery della sezione) -------------------------------
   Immagine, titolo e descrizione portano tutti alla stessa pagina: il link vero sta sul
   titolo, il resto lo rende cliccabile il JS (`initWarp`, via `data-warp`).
   Il problema che risolvono queste regole non è funzionale ma di LEGGIBILITÀ: senza,
   l'unica parte che si annuncia come cliccabile è il titolo — sotto al puntatore l'immagine
   non fa nulla, quindi sembra decorativa. Qui l'intera colonna reagisce insieme.
   La classe `.is-hot` la mette il JS su ENTRAMBI gli elementi della stessa sezione:
   immagine e testo sono celle separate della griglia (e in ordine diverso), quindi il CSS
   da solo non potrebbe far reagire l'una al passaggio sull'altro. */
.genre-figure[data-warp], .genre-text[data-warp] { cursor: pointer; }
.genre-figure[data-warp] { transition: transform 500ms cubic-bezier(0.16, 1, 0.3, 1); }
.genre-figure[data-warp].is-hot { transform: scale(1.02); }
.genre-title a { color: inherit; text-decoration: none; }
/* La sottolineatura cresce da sinistra invece di comparire di colpo: sotto il puntatore
   ovunque nella colonna, non solo sul titolo. */
.genre-title a::after {
  content: ""; display: block; height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: left center;
  transition: transform 420ms cubic-bezier(0.16, 1, 0.3, 1);
}
.genre-text[data-warp].is-hot .genre-title a::after { transform: scaleX(1); }
@media (hover: none) {
  /* Su touch `:hover` resta appiccicato all'ultimo elemento toccato: niente stati accesi. */
  .genre-figure[data-warp].is-hot { transform: none; }
  .genre-text[data-warp].is-hot .genre-title a::after { transform: scaleX(0); }
}

@media (max-width: 760px) {
  /* Mobile: le sezioni crescono col contenuto. Lo snap resta solo sulla coppia guidata
     hero -> stats (le uniche due alte esattamente 1 viewport); da qui in poi lo spegne il
     JS, così lo scroll è quello nativo di iOS, inerzia compresa. In iOS Safari una snap
     area più alta del viewport NON si comporta come vuole la spec (dovrebbe essere valida
     in ogni posizione che copre il viewport): riaggancia al proprio inizio, ed era il
     rimbalzo in cima che si vedeva scorrendo la sezione Food/Hospitality/Drink. */
  .about, .reach, .genres { height: auto; min-height: calc(100% - var(--peek)); }
  .reach-flip { height: auto; min-height: calc(100% - var(--peek)); }
  /* l'ultima resta alta 1 viewport intero, come su desktop e per la stessa ragione */
  .contact { min-height: 100%; }
  /* Incolonnate, tutte le sezioni mostrano prima l'immagine e poi il testo: è la foto che
     deve comparire nella fetta scoperta dalla sezione precedente. Nel markup della
     rispecchiata il testo viene per primo (l'ordine delle colonne su desktop), quindi qui
     va rimesso dopo. */
  .reach-flip .reach-figure { order: -1; }
  /* La griglia a due colonne diventa una colonna sola, nell'ordine del markup.
     Padding-top 0: l'immagine resta a filo del bordo superiore anche qui. */
  /* `.reach-flip .reach-track` è nell'elenco perché la sua regola desktop (padding a filo
     a destra) ha specificità 0,2,0: una media query NON aggiunge specificità, quindi da
     sola questa non la scavalcherebbe e su mobile testo e foto arriverebbero a toccare il
     bordo destro dello schermo. */
  .reach-track,
  .reach-flip .reach-track {
    height: auto;
    display: flex; flex-direction: column; align-items: stretch;
    gap: clamp(1.4rem, 4vw, 2rem);
    padding: 0 clamp(1.6rem, 5vw, 5rem) clamp(2rem, 5vh, 3rem);
  }
  /* Contatto: una colonna sola, il form sotto il testo. Il rientro del monogramma non serve
     più (qui il testo non gli passa accanto) e in fondo resta la sua banda, che su questa
     sezione non va scalata: è l'ultima e arriva davvero al fondo del viewport. */
  .contact-track {
    height: auto;
    display: flex; flex-direction: column; align-items: stretch;
    gap: clamp(1.6rem, 5vw, 2.4rem);
    padding: clamp(2rem, 6vh, 3rem) clamp(1.6rem, 5vw, 5rem)
             calc(var(--mono-band) + clamp(0.6rem, 2vh, 1.6rem));
  }
  .contact-text { padding-left: 0; }
  /* Immagini incolonnate col testo (non a tutta larghezza): a sinistra resta il margine
     dove sta il monogramma fisso, come per il ritratto della bio. */
  .reach-figure { height: auto; }
  .reach-figure img { height: auto; aspect-ratio: 3 / 2; max-height: 52vh; }
  /* Genres: una colonna sola, ogni immagine seguita dal proprio testo (--o). */
  .genres-grid {
    height: auto; max-width: none;
    display: flex; flex-direction: column; align-items: stretch;
    gap: clamp(0.9rem, 3vw, 1.6rem);
    padding: 0 clamp(1.6rem, 5vw, 5rem) clamp(2rem, 5vh, 3rem);
  }
  /* aspect-ratio 2:3 ereditato dalla regola base: anche qui niente ritaglio. */
  .genre-figure, .genre-text { order: var(--o, 0); flex: 0 0 auto; }
  /* Stacco tra un blocco (immagine + testo) e il successivo. */
  .genre-text { margin-bottom: clamp(0.8rem, 3vw, 1.6rem); }
}

@media (prefers-reduced-motion: reduce) {
  .scroll-root { scroll-snap-type: none; }
  .reel-inner { transition: none !important; }
  .lab-c { transition: none; }
  .about-title .w-in, .about-desc .ln-in, .about-statement, .about-portrait img { transition: none; }
  .reach-text > *, .reach-figure img { transition: none; }
  .reach-figure[data-warp], .reach-more a::after { transition: none; }
  .genre-figure img, .genre-text, .genres-heading { transition: none; }
  .contact-text > *, .contact-form { transition: none; }
}
