/* ============================================================
   Damiano Rosa — Homepage
   ============================================================ */

:root {
  --black:  #090909;
  --lime:   #D4FF00;
  --cyan:   #4EDADA;
  --cream:  #E9E0C9;
  /* griglia di fondo: il Figma la tiene al 20%, quindi è appena percepibile.
     Alzare l'alpha per renderla più marcata. */
  --line:   rgba(233, 224, 201, 0.045);

  /* margine sinistro comune a logo / headline / bottoni (come da Figma) */
  --pad-x: 8.3%;

  /* misura unica per i cerchietti di navigazione (frecce delle schede e
     della pagina About) e per la X: così restano coerenti fra loro e con
     l'altezza dei bottoni */
  --nav-btn: 2.2rem;

  --font-serif: "Instrument Serif", Georgia, serif;
  --font-sans:  "Vend Sans", "Helvetica Neue", Arial, sans-serif;

  /* puntatori disegnati: i numeri dopo l'url sono il punto attivo, cioè
     dove "punta" davvero il cursore — la cima della freccia e del dito.
     Li ricava `node tools/cursori.js` dal disegno: non si scrivono a mano,
     e vanno rifatti a ogni ridisegno dei puntatori. */
  --cur-freccia: url("../assets/cursors/puntatore-1.png") 0 0, auto;
  --cur-manina:  url("../assets/cursors/puntatore-2.png") 8 1, pointer;
}

*, *::before, *::after { box-sizing: border-box; }

/* Intensità dell'alone della headline. Registrarla come <number> è l'unico
   modo perché venga interpolata: animando direttamente il `text-shadow` con
   `transparent` o `color-mix(currentColor …)` il browser non sa fondere i due
   estremi e l'alone si accende di scatto. */
@property --glow {
  syntax: "<number>";
  initial-value: 0;
  inherits: true;
}

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
}

/* su mobile evita il salto della barra URL */
@supports (height: 100dvh) {
  html, body { height: 100dvh; }
}

body {
  cursor: var(--cur-freccia);
  background-color: var(--black);
  color: var(--cream);
  font-family: var(--font-sans);
  overflow: hidden;              /* la HP è un'unica schermata */
  -webkit-font-smoothing: antialiased;

  /* Griglia a rettangoli: le 6 linee verticali da 1px del Figma (prima a
     5.952vw, passo 17.857vw) più le orizzontali, col passo in vh invece che
     in vw. Il passo in vh non è un dettaglio: tiene il conto delle linee
     fisso — 5,6 colonne e 5,6 righe sempre — quindi le maglie hanno la forma
     dello schermo e la griglia resta la stessa a qualsiasi formato di
     finestra. Con lo stesso passo in vw le righe diventerebbero centinaia
     su una finestra alta e stretta. */
  background-image:
    linear-gradient(90deg,  var(--line) 0 1px, transparent 1px),
    linear-gradient(180deg, var(--line) 0 1px, transparent 1px);
  background-size: 17.857vw 100%, 100% 17.857vh;
  background-position: 5.952vw 0, 0 5.952vh;
  background-repeat: repeat-x, repeat-y;
}

/* ------------------------------------------------------------
   Stage — il campo fisico degli oggetti che cadono
   ------------------------------------------------------------ */

#stage {
  position: fixed;
  inset: 0;
  overflow: hidden;
  z-index: 1;
  touch-action: none;            /* il trascinamento non deve scrollare la pagina */
}

/* Il puntamento lo fa la fisica sulla sagoma ritagliata, non il rettangolo
   dell'<img>: così l'angolo trasparente di un PNG non ruba il clic al pezzo
   che gli sta sotto. */
.obj { pointer-events: none; }

/* manina su tutto ciò che si può prendere, lanciare o aprire */
#stage.is-over,
#stage.is-link,
#stage.is-dragging { cursor: var(--cur-manina); }

/* gli oggetti con un link restano raggiungibili da tastiera */
.obj:focus-visible {
  outline: 2px solid var(--cream);
  outline-offset: 4px;
  border-radius: 4px;
}

.obj {
  position: absolute;
  top: 0;
  left: 0;
  /* la catena translate→rotate→translate(-50%,-50%) centra l'oggetto solo
     se l'origine è l'angolo dell'elemento */
  transform-origin: 0 0;
  transform: translate(-9999px, -9999px);   /* fuori schermo finché non parte il drop */
  opacity: 0;
}

.obj.is-live { opacity: 1; }

/* il layer sulla GPU costa memoria: si tiene solo mentre la scena si muove */
#stage.is-moving .obj { will-change: transform; }

/* un oggetto può essere anche un video (la figurina di About) */
.obj img,
.obj video {
  display: block;
  width: 100%;
  height: auto;
  user-select: none;
  -webkit-user-drag: none;
  transform-origin: 50% 50%;
}

/* lo scuotimento vive sull'<img> interna: la posa finale (sul .obj) non cambia mai */
.obj.is-shaking img,
.obj.is-shaking video { animation: shake var(--shake-dur, 620ms) cubic-bezier(.36,.07,.19,.97) both; }

@keyframes shake {
  0%   { transform: translate(0,0) rotate(0deg); }
  12%  { transform: translate(-1.6%, 0.5%) rotate(-1.9deg); }
  26%  { transform: translate(1.5%, -0.6%) rotate(1.7deg); }
  40%  { transform: translate(-1.1%, 0.4%) rotate(-1.3deg); }
  54%  { transform: translate(0.9%, -0.3%) rotate(1deg); }
  68%  { transform: translate(-0.6%, 0.2%) rotate(-0.6deg); }
  82%  { transform: translate(0.35%, -0.1%) rotate(0.3deg); }
  100% { transform: translate(0,0) rotate(0deg); }
}

/* ------------------------------------------------------------
   Hero — logo, headline, bottoni
   ------------------------------------------------------------ */

.hero {
  position: relative;
  z-index: 2;
  height: 100%;
  padding: 4.4vh var(--pad-x) 0;
  pointer-events: none;          /* solo i figli interattivi riattivano il puntatore */
}

.logo {
  display: block;
  width: 12.6vw;
  min-width: 128px;
  max-width: 260px;
  margin-left: -0.4%;            /* compensa il margine ottico del disegno */
  pointer-events: auto;
  opacity: 0;
  transform: translateY(-14px);
  transition: opacity .7s ease, transform .7s cubic-bezier(.2,.8,.25,1);
}

.logo img { display: block; width: 100%; height: auto; }

.headline {
  margin: 2.6vh 0 0;
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(34px, 6.32vw, 146px);
  line-height: 1.10;
  letter-spacing: 0.023em;
  color: var(--cream);
}

.headline .line {
  display: block;
  position: relative;
  text-shadow:
    0 0 0.07em rgb(from currentColor r g b / calc(var(--glow) * 0.55)),
    0 0 0.22em rgb(from currentColor r g b / calc(var(--glow) * 0.30));
  padding-bottom: 0.22em;        /* spazio per i discendenti (j, y, g) */
  margin-bottom: -0.22em;
}

.headline .line > span { display: inline-block; }

/* --- macchina da scrivere -------------------------------------
   Il testo non viene spezzato in <span> per lettera (si perderebbero
   crenatura e legature del serif): resta intero e si scopre con un
   clip-path. A guidarlo è il JS, che ferma il ritaglio sul bordo esatto
   di ogni carattere — con un passo costante sulla larghezza il cursore
   finiva sopra le lettere, perché i caratteri sono larghi diversi. --- */

/* Riga ancora da scrivere: il ritaglio deve avere larghezza ZERO, quindi il
   margine sinistro qui è 0 e non -0.06em come durante la battitura. Con il
   margine negativo restava aperta una fessura di 0.06em fra il bordo sinistro
   e il ritaglio, e da lì sbucava il filo della prima lettera: era il "puntino"
   che si vedeva comparire a inizio seconda riga in About, prima del testo. */
.js-type .headline .line > span {
  clip-path: inset(-0.9em 100% -0.9em 0);
}

/* cursore: sta attaccato all'ultima lettera scritta */
.caret {
  position: absolute;
  top: 0.15em;
  left: 0;
  width: 0.055em;
  height: 0.74em;
  background: var(--cream);
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s linear;
}

.caret.is-typing-here { opacity: 1; }

/* a fine riga lampeggia un paio di volte e si spegne */
.caret.is-done {
  animation: caret-blink .55s steps(2) 2 forwards;
}

@keyframes caret-blink {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* --- tabellone: la qualifica si rimescola -----------------------
   Una parola sola, di cui cambia il testo: le lettere friggono su
   caratteri casuali e si fissano da sinistra a destra (vedi scrambleTo).
   Sta in posizione assoluta, così la larghezza ballerina del testo
   rimescolato non tocca il layout della riga. --- */

.roll {
  display: inline-block;
  position: relative;
  vertical-align: top;
  height: 1.1em;
  white-space: nowrap;
  transition: width .5s cubic-bezier(.66,0,.24,1);
}

.roll b {
  position: absolute;
  left: 0;
  top: 0;
  font-style: italic;            /* stacca la dicitura dal resto della frase */
  font-weight: inherit;
  white-space: nowrap;
  transition: color .45s ease;
}

.actions {
  display: flex;
  gap: 0.85rem;
  margin-top: 4.05vh;
}

.btn {
  --btn-bg: var(--cyan);
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  padding: 0.4em 1.78em;
  border-radius: 999px;          /* pill pieno, come da Figma */
  background: var(--btn-bg);
  color: var(--black);
  font-family: var(--font-sans);
  font-weight: 600;
  /* asse variabile: il grassetto può crescere con continuità invece di
     scattare da un peso all'altro */
  font-variation-settings: 'wght' 600;
  font-size: clamp(12px, 1.21vw, 22px);
  line-height: 1.2;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  pointer-events: auto;
  opacity: 0;
  transform: translateY(16px) scale(.94);
  transition:
    opacity .5s ease,
    transform .55s cubic-bezier(.2,1.3,.4,1),
    filter .3s ease,
    box-shadow .3s ease,
    font-variation-settings .32s cubic-bezier(.4,0,.2,1);
}

.btn--cyan { --btn-bg: var(--cyan); }
.btn--lime { --btn-bg: var(--lime); }

/* freccia disegnata: tratti dritti e spigoli vivi */
.btn .arrow {
  width: 1.55em;
  height: auto;
  flex: none;
  overflow: visible;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: square;        /* squadrata, non arrotondata */
  stroke-linejoin: miter;
  transition: stroke-width .32s ease, transform .3s cubic-bezier(.2,.8,.25,1);
}

.btn:hover {
  font-variation-settings: 'wght' 700;
}

/* all'hover la freccia si ispessisce con il testo e va avanti e indietro */
.btn:hover .arrow {
  stroke-width: 2.6;
  animation: arrow-sway 1.15s cubic-bezier(.45,0,.55,1) infinite alternate;
}

@keyframes arrow-sway {
  from { transform: translateX(-1px); }
  to   { transform: translateX(5px); }
}

/* hover: la luce sale e basta, senza picco né ricaduta */
.btn { cursor: var(--cur-manina); }

.btn:hover {
  filter: brightness(1.07);
  box-shadow: 0 0 10px -2px var(--btn-bg), 0 0 26px -4px var(--btn-bg);
}

.btn:active {
  filter: brightness(1.04);
  box-shadow: 0 0 12px -6px var(--btn-bg);
}
.btn:focus-visible { outline: 2px solid var(--cream); outline-offset: 3px; }

/* Anche i bottoni si scuotono ogni tanto, come gli oggetti della catasta.
   Parte solo a scena ferma (`is-settled`), o darebbe fastidio durante
   l'entrata — e sovrascriverebbe il transform che la anima. */
.is-settled .btn {
  animation: btn-shake 7.5s cubic-bezier(.36,.07,.19,.97) infinite;
}
.is-settled .btn--lime { animation-delay: 3.75s; }

@keyframes btn-shake {
  0%, 84%, 100% { transform: translate(0,0) rotate(0deg); }
  86%  { transform: translate(-1.4px, .6px) rotate(-1.1deg); }
  88.5%{ transform: translate(1.3px, -.5px) rotate(1deg); }
  91%  { transform: translate(-.9px, .4px) rotate(-.7deg); }
  93.5%{ transform: translate(.6px, -.2px) rotate(.4deg); }
  96%  { transform: translate(-.3px, .1px) rotate(-.2deg); }
}

/* ...e la headline non resta immobile: fluttua e pulsa piano, sfasata riga
   per riga. Due animazioni con periodi diversi (10s e 7,5s) così il ciclo non
   si ripete mai uguale. L'alone usa currentColor: sulla riga del tabellone
   prende quindi il giallo o l'azzurro della dicitura di turno. */
/* il fluttuare resta sfasato riga per riga... */
.is-settled .headline .line {
  animation: wiggle 10s ease-in-out infinite;
}
.is-settled .headline .line:nth-child(2) { animation-delay: -3.3s; }
.is-settled .headline .line:nth-child(3) { animation-delay: -6.6s; }

/* ...ma la pulsazione è di TUTTO il blocco insieme: sfasata riga per riga
   sembrava un difetto di rendering, sincronizzata si legge come una scelta.
   Sale piano, tocca il culmine, si rispegne e resta a zero un momento. */
.is-settled .headline {
  animation: breathe 9s cubic-bezier(.45, 0, .55, 1) infinite;
}

@keyframes wiggle {
  0%, 100% { transform: translate(0, 0) rotate(0deg); }
  20%  { transform: translate(0.26%, -2.4px) rotate(0.16deg); }
  45%  { transform: translate(-0.2%, 1.9px) rotate(-0.14deg); }
  70%  { transform: translate(0.15%, 1.2px) rotate(0.09deg); }
  85%  { transform: translate(-0.08%, -0.8px) rotate(-0.05deg); }
}

@keyframes breathe {
  0%, 100% { --glow: 0; }
  50%      { --glow: 1; }
}



@media (prefers-reduced-motion: reduce) {
  .is-settled .btn,
  .is-settled .headline .line,
  .btn:hover .arrow { animation: none; }
}

/* il testo si muove: l'alone non deve essere tranciato dal ritaglio della
   digitazione, che resta sulla riga anche a scrittura finita */

/* ------------------------------------------------------------
   Stati di entrata (attivati dal JS)
   ------------------------------------------------------------ */

.is-ready .logo { opacity: 1; transform: translateY(0); }

/* i bottoni entrano appena finita la digitazione (delay impostato dal JS) */
.is-typing .btn { opacity: 1; transform: translateY(0) scale(1); }

/* ------------------------------------------------------------
   Mobile
   ------------------------------------------------------------ */

@media (max-width: 900px) {
  :root { --pad-x: 7%; }

  .hero { padding-top: 5vh; }

  .logo { width: 40%; min-width: 0; }

  /* la headline tiene le stesse tre righe del desktop: il corpo scende
     quanto basta perché "I'm just a simple art director" non vada a capo */
  .headline {
    font-size: clamp(20px, 7.0vw, 44px);
    line-height: 1.16;
  }

  .actions { gap: 0.6rem; margin-top: 4vh; }

  .btn {
    font-size: clamp(11px, 3.1vw, 15px);
    padding: 0.78em 1.5em;
  }
}

/* schermi molto bassi: comprimi il blocco testo */
@media (max-height: 620px) and (min-width: 901px) {
  .headline { font-size: clamp(28px, 4.6vw, 90px); margin-top: 1.6vh; }
  .actions { margin-top: 2.2vh; }
}

/* ------------------------------------------------------------
   Accessibilità — niente caduta né scuotimento
   ------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  .logo,
  .btn { transition: none !important; opacity: 1; transform: none; }

  .js-type .headline .line > span,
  .is-typing .headline .line > span { clip-path: none; animation: none; }

  .caret { display: none; }
  .obj.is-shaking img,
.obj.is-shaking video { animation: none; }
}

/* ------------------------------------------------------------
   Pop: un logo rimasto storto si raddrizza di scatto, e lo stacco
   viene coperto da questo scoppio di bolla
   ------------------------------------------------------------ */

.obj.is-popping img,
.obj.is-popping video { animation: pop .44s cubic-bezier(.34,1.56,.64,1) both; }

@keyframes pop {
  0%   { transform: scale(1); opacity: 1; }
  22%  { transform: scale(.55); opacity: .25; }
  55%  { transform: scale(1.12); opacity: 1; }
  75%  { transform: scale(.97); }
  100% { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .obj.is-popping img { animation: none; }
}

/* ============================================================
   Scheda progetto — si apre cliccando un oggetto della catasta
   ============================================================ */

.modal {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  padding: 3vh 3vw;
  cursor: var(--cur-freccia);
}

.modal[hidden] { display: none; }

.modal__veil {
  position: absolute;
  inset: 0;
  background: rgba(9, 9, 9, 0.74);
  backdrop-filter: blur(4px);
  animation: veil-in .5s cubic-bezier(.33, 0, .2, 1) both;
}

@keyframes veil-in { from { opacity: 0 } to { opacity: 1 } }

.modal__panel:focus { outline: none; }   /* ci arriva solo il fuoco programmatico */

.modal__panel {
  position: relative;
  width: min(1080px, 92vw);
  /* L'altezza la detta il riquadro delle immagini (vedi .modal__shots), non
     la lunghezza del testo: le schede restano tutte uguali senza che un
     testo corto lasci mezzo pannello vuoto. */
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  background: var(--cream);
  border-radius: 20px;
  padding: 10px 10px 14px;
  color: var(--black);
  transform-origin: var(--ox, 50%) var(--oy, 50%);
  box-shadow: 0 28px 80px -24px rgba(0, 0, 0, .85);

  /* La bolla: sale da piccola, supera di un soffio la misura e ci si posa.
     La curva è quasi tutta in uscita — parte svelta e frena a lungo, che è
     ciò che dà la sensazione di morbido. */
  animation: modal-in .62s cubic-bezier(.16, .84, .27, 1.03) both;
}

@keyframes modal-in {
  0%   { opacity: 0; transform: scale(.82) translateY(10px); }
  100% { opacity: 1; transform: scale(1) translateY(0); }
}

/* La chiusura non è l'entrata al contrario: si sgonfia appena e sfuma,
   con la curva speculare (svelta alla fine) perché sparisca senza strappo. */
.modal.is-closing .modal__panel {
  animation: modal-out .34s cubic-bezier(.5, -0.02, .78, .32) both;
}
.modal.is-closing .modal__veil { animation: veil-in .34s cubic-bezier(.4, 0, .7, 1) reverse both; }

@keyframes modal-out {
  0%   { opacity: 1; transform: scale(1) translateY(0); }
  100% { opacity: 0; transform: scale(.9) translateY(6px); }
}

/* ---- barra in alto ---- */

/* Chiusura a sinistra e, a destra, un'unica etichetta: l'attività con gli anni
   in coda fra parentesi. Gli anni erano una riga a sé nell'angolo e restavano
   staccati da tutto; messi in coda alla descrizione, in alto a destra c'è un
   blocco solo, allineato a destra. */
.modal__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: .4rem .7rem .7rem;
  font-size: clamp(11px, .95vw, 13px);
}

.modal__kicker {
  margin: 0;
  max-width: min(74%, 66ch);
  text-align: right;
  line-height: 1.34;

  /* Al massimo DUE righe: oltre, il testo viene troncato con i puntini
     invece di far crescere la barra e schiacciare la galleria. Se una
     descrizione ci sbatte contro, si accorcia in js/projects.js. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

/* gli anni non si spezzano mai dentro le parentesi */
.modal__years { white-space: nowrap; }

.modal__close {
  width: var(--nav-btn);
  height: var(--nav-btn);
  padding: 0;
  border: 1.6px solid var(--black);
  border-radius: 999px;
  background: none;
  color: inherit;
  cursor: var(--cur-manina);
  display: grid;
  place-items: center;
  transition: background-color .18s ease, color .18s ease, transform .25s cubic-bezier(.34,1.4,.5,1);
}

.modal__close svg { width: 100%; height: 100%; stroke: currentColor; stroke-width: 1.7; fill: none; }
.modal__close:hover { background: var(--black); color: var(--cream); transform: rotate(90deg); }
.modal__close:focus-visible { outline: 2px solid var(--black); outline-offset: 3px; }

/* ---- corpo: immagini a sinistra, testo a destra ---- */

.modal__body {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(.6rem, 1.4vw, 1.4rem);
}

.modal__shots {
  position: relative;
  height: min(62vh, 520px);
  min-height: 0;
  overflow: hidden;
  border-radius: 12px;
  touch-action: pan-y;
}

/* una scheda senza immagini (cartella ancora da riempire) non lascia
   mezzo pannello vuoto: il testo si prende tutta la larghezza */
.modal.is-textonly .modal__body { grid-template-columns: 1fr; }
.modal.is-textonly .modal__shots { display: none; }
.modal.is-textonly .modal__info { padding-left: clamp(1rem, 1.8vw, 1.6rem); }
/* e senza immagini l'altezza fissa lascerebbe mezzo pannello vuoto */
.modal.is-textonly .modal__shots { height: auto; }
.modal.is-textonly .modal__meta { margin-top: 1.6rem; }

.modal__track {
  display: flex;
  height: 100%;
  transition: transform .5s cubic-bezier(.32, .72, .2, 1);
}

/* Ogni immagine è una lastra a sé, centrata nel riquadro: i formati sono
   tutti diversi, quindi ci si adatta all'immagine invece di ritagliarla.

   Il raggio sta sulla cornice, non sull'<img>: la cornice conosce le
   proporzioni dell'immagine (arrivano da shots.js) e le prende esatte,
   quindi arrotonda proprio l'immagine. Messo sull'<img> con object-fit,
   il raggio seguirebbe il contenitore e resterebbero due orecchie vuote
   agli angoli — è il nero che si vedeva spuntare. */
.modal__slide {
  flex: 0 0 100%;
  display: grid;
  place-items: center;
  min-width: 0;
  padding: 0 .2rem;
  container-type: size;         /* così l'immagine può misurarsi sull'altezza */
}

/* L'immagine prende tutta la larghezza che può, ma senza mai superare in
   altezza il riquadro: la larghezza massima consentita dall'altezza è
   `altezza × proporzioni`, e si tiene la minore delle due.

   Il calcolo è esplicito perché `max-height` da solo non basta: con una
   larghezza dichiarata il browser non ricalcola l'altra dimensione, e le
   immagini verticali sfondavano il pannello. Le proporzioni arrivano da
   shots.js, quindi valgono anche prima che l'immagine sia scaricata — il
   riquadro non si assesta a scatti mentre carica.

   Il box coincide sempre con ciò che si vede: il raggio arrotonda
   l'immagine, non un contenitore più grande. */
.modal__pic {
  position: relative;              /* ci si appoggia sopra lo sticker */
  width: min(100%, calc(100cqh * var(--r, 1)));
  max-height: 100%;
}

.modal__frame {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: var(--r, 1);
  border-radius: 10px;

  /* L'ombra su fondo crema va tenuta bassissima e calda, non nera: le
     immagini chiare hanno il fondo quasi uguale al pannello, e un'ombra
     con del nero dentro si legge come una banda sporca attaccata al bordo
     invece che come profondità. Il contorno lo fa un filo di grigio, che
     stacca l'immagine senza doverla sollevare. */
  box-shadow: 0 10px 26px -20px rgba(58, 44, 18, .45);
  outline: 1px solid rgba(9, 9, 9, .07);
  outline-offset: -1px;

  user-select: none;
  -webkit-user-drag: none;
}

/* ---- frecce e contatore ----
   Con dodici immagini i pallini diventano un pettine illeggibile: si
   scorre con le frecce e si sa dove si è da un numero. */

.modal__nav {
  position: absolute;
  top: 50%;
  translate: 0 -50%;
  width: var(--nav-btn);
  height: var(--nav-btn);
  padding: 0;
  display: grid;
  place-items: center;
  border: 1.6px solid var(--black);
  border-radius: 999px;
  background: rgba(233, 224, 201, .82);
  backdrop-filter: blur(6px);
  color: var(--black);
  cursor: var(--cur-manina);
  opacity: 0;
  transition: opacity .25s ease, background-color .2s ease, color .2s ease, translate .2s ease;
}

.modal__shots:hover .modal__nav,
.modal__nav:focus-visible { opacity: 1; }

.modal__nav svg { width: 62%; height: 62%; stroke: currentColor; stroke-width: 2.2; fill: none; }
.modal__nav:hover { background: var(--black); color: var(--cream); }
.modal__nav--prev { left: .5rem; }
.modal__nav--next { right: .5rem; }
.modal__nav--prev:hover { translate: -2px -50%; }
.modal__nav--next:hover { translate: 2px -50%; }
.modal__nav[disabled] { display: none; }

.modal__count {
  position: absolute;
  left: 50%;
  bottom: .35rem;
  translate: -50% 0;
  padding: .18rem .6rem;
  border-radius: 999px;
  background: rgba(9, 9, 9, .72);
  color: var(--cream);
  font-size: 11px;
  letter-spacing: .06em;
  font-variant-numeric: tabular-nums;
  opacity: 0;
  transition: opacity .25s ease;
}

.modal__shots:hover .modal__count { opacity: 1; }
.modal__count[hidden] { display: none; }

/* su tocco non esiste il passaggio del mouse: si mostrano e basta */
@media (hover: none) {
  .modal__nav, .modal__count { opacity: 1; }
}

/* ---- pannello del testo ---- */

/* Il blocco di testo va tenuto STRETTO: titolo, paragrafi e riga finale
   sono una cosa sola, e distanziandoli si sfilaccia — la scheda sembra
   montata male invece che ariosa. Lo spazio si guadagna in fondo, dove
   non disturba, non fra una riga e l'altra. */
.modal__info {
  padding: clamp(.5rem, 1.2vw, 1rem) clamp(1rem, 1.8vw, 1.6rem) clamp(.6rem, 1.4vw, 1rem) 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: .55rem;
}

.modal__title {
  margin: 0 0 .1rem;
  line-height: 1.2;
  font-size: clamp(17px, 1.6vw, 23px);
  font-variation-settings: "wght" 700;
  font-weight: 400;              /* il peso lo fa l'asse variabile */
  letter-spacing: -0.01em;
}

.modal__title i { font-style: italic; }   /* solo i pezzi fra *asterischi* */

.modal__text {
  display: flex;
  flex-direction: column;
  gap: .62rem;
  font-size: clamp(13px, 1.15vw, 16px);
  line-height: 1.36;
  text-align: justify;
  hyphens: auto;
}

.modal__text p { margin: 0; }

.modal__meta {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: .05rem;
  padding-top: 1.1rem;
  line-height: 1.32;
  font-size: clamp(11px, .95vw, 13.5px);
}

.modal__meta span[hidden] { display: none; }
.modal__meta b { font-weight: 400; font-variation-settings: "wght" 700; }

/* ---- mobile: una colonna, immagine sopra ---- */

@media (max-width: 900px) {
  .modal { padding: 2vh 3vw; }

  .modal__panel { width: 94vw; height: auto; max-height: 92vh; }

  /* L'etichetta in alto a destra: sono solo etichette, quindi il corpo cala
     invece di rubare righe. Le due righe restano due anche qui. */
  .modal__bar {
    gap: .5rem;
    padding: .3rem .5rem .5rem;
    font-size: 10px;
  }

  .modal__kicker {
    max-width: 84%;                /* di là dalla X non serve altro spazio */
    font-size: 9.5px;
    line-height: 1.32;
    letter-spacing: .005em;
  }

  .modal__body {
    grid-template-columns: 1fr;
    grid-template-rows: auto minmax(0, 1fr);
    overflow-y: auto;
  }

  .modal__shots { height: 38vh; }

  .modal__info { overflow: visible; padding: 0 .6rem .4rem; }
  /* niente giustificato su colonna stretta: farebbe buchi fra le parole,
     e senza giustificato la sillabazione non serve più */
  .modal__text { text-align: left; hyphens: manual; }
  .modal__meta { padding-top: 1.6rem; }
}

/* Con la scheda aperta la homepage è coperta: fluttuare e pulsare dietro
   il velo è solo lavoro sprecato dal compositor. */
body.has-modal .headline,
body.has-modal .headline .line,
body.has-modal .btn { animation-play-state: paused; }

@media (prefers-reduced-motion: reduce) {
  .modal__panel,
  .modal__veil,
  .modal.is-closing .modal__panel,
  .modal.is-closing .modal__veil { animation: none; }
  .modal__track { transition: none; }
  .modal__close:hover { transform: none; }
}

/* ============================================================
   Scheda "Say hi" — stessa meccanica della scheda progetto,
   contenuto diverso: il gameboy a sinistra, i contatti a destra
   ============================================================ */

.modal--sayhi .modal__panel { width: min(880px, 92vw); }

.modal--sayhi .modal__bar { padding-bottom: .2rem; }

.modal--sayhi .modal__body {
  grid-template-columns: minmax(0, .95fr) minmax(0, 1.05fr);
  align-items: center;
  padding-bottom: clamp(.6rem, 1.4vw, 1.2rem);
}

.sayhi__shot {
  display: grid;
  place-items: center;
  height: min(56vh, 460px);
  padding-left: clamp(.4rem, 1.4vw, 1.4rem);
  container-type: size;          /* serve al calcolo qui sotto */
}

/* Stessa storia delle immagini nella scheda progetto: `max-height` da solo
   non basta a rimpicciolire un'immagine alta — il gameboy usciva dal fondo
   del pannello. La larghezza va calcolata dall'altezza disponibile per le
   proporzioni dell'immagine (396×678). */
.sayhi__shot img {
  width: min(100%, calc(100cqh * 0.584));
  height: auto;
  user-select: none;
  -webkit-user-drag: none;
  cursor: var(--cur-manina);       /* si può toccare: risponde vibrando */

  /* Vibra subito all'apertura e poi ogni cinque secondi. La scossa sta in
     FONDO al ciclo (84%→100%), quindi il ritardo negativo di 4,2s fa partire
     il ciclo già dentro la scossa: appena la scheda si apre il gameboy si
     muove, invece di stare fermo i primi quattro secondi. A rimettere la fase
     all'apertura e a ogni tap pensa `vibra()` in sayhi.js, che riavvia
     l'animazione — senza, la fase sarebbe quella del caricamento della
     pagina, cioè un momento qualsiasi.

     Lo scuotimento sta sull'immagine e il fluttuare sul contenitore: sono due
     animazioni che scrivono entrambe `transform`, e sullo stesso elemento la
     seconda cancellerebbe la prima. */
  animation: gb-buzz 5s cubic-bezier(.36, .07, .19, .97) -4.2s infinite;
}

/* e intanto galleggia, con un'escursione da OGGETTO: quella delle righe della
   headline (due pixel, perché è testo e non deve sembrare instabile) su
   un'immagine alta quattrocento pixel non si vedeva. */
.sayhi__shot { animation: gb-float 9s ease-in-out infinite; }

@keyframes gb-float {
  0%, 100% { transform: translate(0, 0) rotate(0deg); }
  22%  { transform: translate(1.1%, -2.6%) rotate(1.1deg); }
  48%  { transform: translate(-0.9%, 1.8%) rotate(-0.9deg); }
  72%  { transform: translate(0.6%, 1.1%) rotate(0.5deg); }
  88%  { transform: translate(-0.3%, -0.7%) rotate(-0.25deg); }
}

/* La scossa: breve e stretta come quella dei bottoni, ma in percentuale
   dell'immagine e con qualche grado in più — in pixel fissi (1,4px) su un
   oggetto di questa misura non si leggeva. */
@keyframes gb-buzz {
  0%, 84%, 100% { transform: translate(0, 0) rotate(0deg); }
  86%   { transform: translate(-2.2%, .9%) rotate(-3.2deg); }
  88.5% { transform: translate(2%, -.7%)   rotate(2.8deg); }
  91%   { transform: translate(-1.4%, .5%) rotate(-2deg); }
  93.5% { transform: translate(.9%, -.3%)  rotate(1.2deg); }
  96%   { transform: translate(-.45%, .15%) rotate(-.6deg); }
}

@media (prefers-reduced-motion: reduce) {
  .sayhi__shot, .sayhi__shot img { animation: none; }
}

.sayhi__info {
  padding: 0 clamp(1rem, 2.4vw, 2.4rem) 0 clamp(.6rem, 1.6vw, 1.6rem);
}

.sayhi__title {
  margin: 0 0 clamp(1.2rem, 2.6vw, 2.2rem);
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(22px, 2.5vw, 40px);
  line-height: 1.1;
}

.sayhi__rows { display: flex; flex-direction: column; }

/* ogni contatto è una riga cliccabile: etichetta piccola sopra, valore
   sotto, e un filo che le separa — il colore lo prende al passaggio */
.sayhi__row {
  display: block;
  padding: .55rem 0 .7rem;
  border-bottom: 1px solid rgba(9, 9, 9, .18);
  color: inherit;
  text-decoration: none;
  cursor: var(--cur-manina);
  transition: color .18s ease, border-color .18s ease, padding-left .22s cubic-bezier(.2,.8,.25,1);
}

.sayhi__row:first-child { border-top: 1px solid rgba(9, 9, 9, .18); }
.sayhi__row:hover { padding-left: .5rem; border-color: rgba(9, 9, 9, .45); }
.sayhi__row:focus-visible { outline: 2px solid var(--black); outline-offset: 3px; }

.sayhi__label {
  display: block;
  font-size: clamp(9px, .72vw, 11px);
  letter-spacing: .09em;
  text-transform: uppercase;
  opacity: .62;
}

.sayhi__value {
  display: block;
  margin-top: .12rem;
  font-size: clamp(13px, 1.15vw, 19px);
  font-weight: 400;
  font-variation-settings: "wght" 700;
}

@media (max-width: 900px) {
  .modal--sayhi .modal__body {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto;
    align-items: start;
  }

  .sayhi__shot { height: 34vh; padding: 0; }
  .sayhi__info { padding: 1rem .6rem .4rem; }
  .sayhi__title { margin-bottom: 1rem; }
}

/* ------------------------------------------------------------
   Nuvoletta "coming soon"

   Esce dal punto in cui si è cliccato un oggetto la cui scheda non
   è ancora pronta, e sparisce da sola. Stessa pillola crema dei
   bottoni, con la coda che punta all'oggetto.
   ------------------------------------------------------------ */

.soon {
  position: fixed;
  z-index: 60;
  translate: -50% -100%;
  margin-top: -14px;             /* si stacca da dove hai cliccato */
  padding: .5em 1em .55em;
  border-radius: 999px;
  background: var(--cream);
  color: var(--black);
  font-size: clamp(11px, .95vw, 14px);
  font-weight: 400;
  font-variation-settings: "wght" 600;
  letter-spacing: .02em;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  box-shadow: 0 10px 26px -16px rgba(0, 0, 0, .8);
}

/* la coda: un triangolino sotto, dalla parte dell'oggetto */
.soon::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -5px;
  translate: -50% 0;
  width: 12px;
  height: 12px;
  background: var(--cream);
  clip-path: polygon(50% 100%, 0 0, 100% 0);
}

.soon.is-on { animation: soon-pop 1.9s cubic-bezier(.2, .9, .3, 1) both; }

@keyframes soon-pop {
  0%   { opacity: 0; scale: .7;  translate: -50% -90%; }
  12%  { opacity: 1; scale: 1.04; translate: -50% -104%; }
  20%  { scale: 1; translate: -50% -100%; }
  78%  { opacity: 1; translate: -50% -100%; }
  100% { opacity: 0; translate: -50% -112%; }
}

@media (prefers-reduced-motion: reduce) {
  .soon.is-on { animation: none; opacity: 1; }
}

/* ------------------------------------------------------------
   Lo sticker "check the video"

   Compare sopra le immagini che rimandano a un video: sta
   sull'angolo dell'IMMAGINE (non del riquadro) ed è storto
   quanto basta perché si legga come un adesivo appiccicato lì.
   ------------------------------------------------------------ */

.shot-sticker {
  position: absolute;
  right: 4.5%;
  bottom: 4.5%;
  display: inline-flex;
  align-items: center;
  gap: .45em;
  padding: .55em 1em .6em;
  border-radius: 999px;
  background: var(--lime);
  color: var(--black);
  font-size: clamp(10px, .92vw, 14px);
  font-weight: 400;
  font-variation-settings: "wght" 650;
  letter-spacing: .01em;
  text-decoration: none;
  white-space: nowrap;
  rotate: -5deg;
  box-shadow: 0 8px 20px -10px rgba(0, 0, 0, .7);
  cursor: var(--cur-manina);
  transition: rotate .2s ease, scale .2s cubic-bezier(.34,1.4,.5,1), box-shadow .2s ease;
}

.shot-sticker svg {
  width: 1.5em;
  height: .85em;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: square;
  fill: none;
}

.shot-sticker:hover {
  rotate: -2deg;
  scale: 1.05;
  box-shadow: 0 10px 24px -10px rgba(0, 0, 0, .8);
}

.shot-sticker:focus-visible { outline: 2px solid var(--cream); outline-offset: 3px; }

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

/* ============================================================
   Pagina 404 — riusa hero, logo, headline e bottoni della home:
   qui c'è solo la riga di spiegazione, che in home non esiste
   ============================================================ */

/* La headline è più corta di quella della home (due righe invece di tre) e su
   uno schermo alto resterebbe appesa in cima: la pagina si centra. */
.is-404 .hero {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  padding-bottom: 4.4vh;
}

.is-404 .logo { margin-bottom: 3.2vh; }

.e404__note {
  max-width: 46ch;
  margin: 2.6vh 0 0;
  font-size: clamp(13px, 1.15vw, 17px);
  line-height: 1.45;
  color: var(--cream);
  opacity: .72;                  /* è una nota: sta sotto alla headline, non accanto */
}

.e404__note b { font-weight: 400; font-variation-settings: "wght" 700; }

/* Il droide: la mezza pagina a destra sarebbe vuota, e la battuta parla di lui.
   Non cade e non si prende — qui non c'è fisica, è un'immagine che fluttua. */
.e404__droid {
  position: fixed;
  right: 7vw;
  bottom: 9vh;
  width: clamp(110px, 13vw, 220px);
  height: auto;
  z-index: 1;                    /* sotto all'hero: il testo resta sopra */
  rotate: -7deg;
  animation: droid-float 11s ease-in-out infinite;
  user-select: none;
  -webkit-user-drag: none;
}

@keyframes droid-float {
  0%, 100% { translate: 0 0;        rotate: -7deg; }
  30%      { translate: -0.6% -3.4%; rotate: -4.4deg; }
  62%      { translate: 0.5% 2.2%;  rotate: -9.2deg; }
  84%      { translate: -0.2% -1%;  rotate: -6deg; }
}

@media (max-width: 900px) {
  .is-404 .logo { margin-bottom: 2.4vh; }
  .e404__note { max-width: 100%; }
  .e404__droid { right: 6vw; bottom: 6vh; width: clamp(96px, 26vw, 150px); }
}

@media (prefers-reduced-motion: reduce) {
  .e404__droid { animation: none; }
}
