/* ============================================================
   BASE — reset, guscio dell'app, navigazione, utilità.
   Usa esclusivamente i token di token.css.
   ============================================================ */

*,
*::before,
*::after {
  box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent;
  /* IL GRADIENTE DEVE ARRIVARE AL BORDO ARROTONDATO.
     Difetto trovato sul telefono, sulla casella del giorno scelto: attorno al
     riempimento restava un anello scuro largo un pixel, che sul raggio di 14 px
     si leggeva come un alone sporco.

     La causa non era il colore ma la GEOMETRIA dello sfondo. Un elemento con
     `border: 1px solid transparent` dipinge lo sfondo anche sotto il bordo
     (`background-clip: border-box`, che è il valore predefinito), ma lo
     DIMENSIONA sul riquadro interno (`background-origin: padding-box`, anch'esso
     predefinito). Un'immagine più piccola dell'area da dipingere viene
     RIPETUTA: nell'anello di un pixel lungo il bordo ricompariva quindi
     l'inizio del gradiente — cioè il viola scuro — proprio dove ci si aspetta
     il ciano chiaro della fine. L'«anello non riempito» era il gradiente che
     ricominciava da capo.

     `border-box` come origine dello sfondo lo dimensiona sull'intero riquadro,
     bordo compreso: nessuna ripetizione, nessun anello, in ogni stato e in
     entrambi i temi. Sugli elementi senza bordo o con sfondo a tinta unita —
     cioè la quasi totalità — questa riga non cambia assolutamente nulla:
     origine e riempimento coincidono già.

     PERCHÉ `!important`, che qui non è pigrizia. `background` è una PROPRIETÀ
     ABBREVIATA: ogni volta che un componente scrive `background: var(--grad-caldo)`
     — e in questo foglio succede una trentina di volte, dalle pillole ai
     pulsanti alle caselle del calendario — riporta `background-origin` al suo
     valore iniziale, cioè `padding-box`. Senza `!important` questa riga
     verrebbe quindi annullata esattamente dove serve, e l'anello tornerebbe
     su ognuno di quei componenti (verificato: sulla casella del giorno
     l'origine calcolata restava `padding-box`). L'alternativa sarebbe
     ripetere `border-box` dentro ogni singola abbreviazione, cioè affidare a
     una lista di trenta punti un difetto che deve essere impossibile.
     In tutta l'app non esiste un solo caso in cui si voglia davvero
     `padding-box`: se un giorno servisse, si dichiara sul componente con la
     forma estesa e la propria `!important`. */
  background-origin: border-box !important;
}

html {
  background: var(--bg);
  /* `manipulation` toglie il doppio-tap-zoom e i 300 ms di attesa che ne derivano:
     ogni tocco risponde subito, e nessuna spunta rapida ingrandisce la pagina. */
  touch-action: manipulation;
  /* NIENTE overflow-x qui: se <html> ha un overflow diverso da `visible`,
     <body> smette di propagarlo alla finestra e diventa lui il contenitore di
     scorrimento — e ogni `position: sticky` (barra dei giorni, avanzamento
     della spesa) smette di funzionare. Il taglio orizzontale sta su <body>. */
  /* Niente rimbalzo elastico né "pull to refresh": in palestra è troppo facile
     innescarlo per sbaglio e sembrerebbe che l'app si stia ricaricando. */
  overscroll-behavior: none;
  text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
  /* Spazio della barra di scorrimento sempre riservato: senza, una schermata
     più corta della finestra (un giorno di riposo) fa sparire la barra e tutta
     la pagina scivola di lato/su di qualche pixel al cambio di giorno.
     Sui telefoni la barra è in sovrimpressione e questa riga non cambia nulla.
     Con la barra sottile qui sotto il varco riservato è di 4 px, non di 15. */
  scrollbar-gutter: stable;
}

/* ---------- BARRA DI SCORRIMENTO ----------
   Quella predefinita è una barra chiara da sito web: larga, sempre accesa,
   estranea al tema scuro. Qui è un filo di 4 px, arrotondato, del colore del
   testo terziario, su traccia trasparente — e soprattutto COMPARE SOLO MENTRE
   SI SCORRE (js/app.js mette e toglie la classe `scorre` sulla radice) per poi
   dissolversi: durante la lettura non c'è nulla che divida la pagina.

   Due sintassi perché sono due mondi diversi:
   · `::-webkit-scrollbar` (Chrome, Edge, Safari desktop) permette il disegno
     completo ed è l'unica che consente la dissolvenza;
   · `scrollbar-width`/`scrollbar-color` (standard, Firefox) permette solo
     "sottile + due colori": è la riserva, senza auto-nascondimento.
   NOTA iOS: Safari su iPhone disegna il proprio indicatore di sistema in
   sovrimpressione, non stilizzabile da nessuna delle due sintassi. Lì resta
   quello nativo — che è comunque un filo sottile e semitrasparente. */
html {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--txt-3) 42%, transparent) transparent;
}
::-webkit-scrollbar { width: 4px; height: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-corner { background: transparent; }
::-webkit-scrollbar-thumb {
  border-radius: var(--r-pill);
  background: transparent;
  transition: background var(--t-lento) var(--e);
}
/* Acceso solo durante (e per un istante dopo) lo scorrimento. */
html.scorre ::-webkit-scrollbar-thumb,
html.scorre::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--txt-3) 62%, transparent);
}
html.scorre ::-webkit-scrollbar-thumb:hover { background: var(--txt-3); }

body {
  background: var(--bg);
  color: var(--txt);
  font-family: var(--font);
  font-size: var(--f-b);
  font-weight: var(--peso-normale);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  max-width: var(--max-w);
  width: 100%;
  margin: 0 auto;
  min-height: 100vh;
  min-height: 100dvh;
  overflow-x: hidden;
  position: relative;
  /* Esattamente l'ingombro della barra delle schede, né un pixel di più:
     è lo spazio che serve perché l'ultima card non le finisca dietro.
     Il respiro fra l'ultima card e la barra lo mette `.schermata`. */
  padding-bottom: calc(var(--tab-h) + var(--safe-bot));
  overscroll-behavior: none;
}

/* ---------- COMPORTAMENTO DA APP NATIVA, NON DA PAGINA WEB ----------
   In un'app installata la selezione del testo non è una funzione: è un
   incidente. Basta trattenere un attimo di troppo il dito su una grammatura
   mentre si scorre, e mezza ricetta si evidenzia in azzurro; su iOS compare
   anche il menù «Copia / Cerca / Condividi» (il callout del tocco lungo), che
   copre il contenuto e va chiuso con un altro tocco. Con le mani sudate, in
   palestra, succede in continuazione.

   Qui la selezione è spenta su TUTTA l'app e riaccesa solo dove serve davvero:
   i campi in cui si scrive, dove selezionare è il modo normale di correggere un
   valore. Nessuna eccezione per i contenuti di lettura: non c'è nessun posto in
   cui si copi un ingrediente, e per portarsi via i dati esiste «Esporta dati»
   in «Altro» — che è il modo giusto, completo e datato di farlo.

   `-webkit-touch-callout` è una proprietà a sé: senza di essa iOS mostra il
   menù del tocco lungo anche dove la selezione è disattivata.
   Il lampo grigio del tocco (`-webkit-tap-highlight-color`) è già trasparente
   per tutti gli elementi nella regola di azzeramento in cima a questo file: la
   risposta al dito è quella dell'app (`--scala-tocco`), mai quella del browser.

   PERCHÉ SUL SELETTORE UNIVERSALE E NON PIÙ SOLO SU `body` (correzione del
   giro 4). Dichiarata sul solo `body`, la regola conta sull'EREDITARIETÀ — e
   l'ereditarietà perde contro qualunque foglio di stile dell'interprete che
   dichiari la proprietà sull'elemento stesso. WebKit lo fa: sui controlli di
   modulo e su alcuni contenitori il valore d'autore ereditato non arriva, e il
   tocco lungo su una grammatura faceva comparire la lente d'ingrandimento e il
   menù «Copia / Cerca». Con il selettore universale il valore è dichiarato su
   OGNI elemento: nessuna regola dell'interprete può più vincere per
   specificità, e non resta nessun punto scoperto da scoprire uno per uno.
   Il costo è nullo: sono due proprietà ereditate, non c'è alcun ricalcolo in
   più rispetto a dichiararle sulla radice.

   LE ECCEZIONI SONO ESPLICITE e riguardano solo i posti in cui si SCRIVE: i
   campi del registro dei carichi (carico, ripetizioni, RIR, note), il campo del
   prezzo nella spesa, le note di allenamento. Lì selezionare è il modo normale
   di correggere un valore, e toglierlo romperebbe il doppio tocco che seleziona
   la cifra da sostituire. Il collaudo DOM verifica entrambe le facce della
   regola: `none` sui contenuti, `text` sugli input. */
*, *::before, *::after {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
input, textarea, select, [contenteditable="true"], [contenteditable=""] {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}

/* La vista di stampa non esiste a schermo (vedi css/stampa.css). */
#stampa { display: none; }

/* ---------- FASCIA DELLA BARRA DI SISTEMA ----------
   L'app dichiara `viewport-fit=cover`: la pagina arriva ai bordi fisici del
   telefono e passa SOTTO la Dynamic Island, dove iOS disegna orologio, rete e
   batteria. Senza copertura, ogni cosa che scorre (le celle dei giorni, il
   contatore della spesa, una riga di ingredienti) finisce a intrecciarsi con
   l'orologio: testo su testo, illeggibile.

   Una sola fascia fissa e opaca per TUTTA l'app, invece di un margine interno
   su ogni barra appiccicata. Due motivi, entrambi decisivi:
   1. le barre appiccicate si fermano semplicemente sotto la fascia
      (`top: var(--safe-top)`) e restano alte quanto erano: se la zona sicura
      fosse un loro margine interno, quel margine occuperebbe spazio anche
      MENTRE la barra è ferma a metà pagina, e sopra la striscia dei giorni
      comparirebbe un vuoto di sessanta pixel;
   2. la protezione vale anche sulle schermate SENZA barre appiccicate
      (Progressi, Mesi, Batch), dove il contenuto scorre comunque sotto l'isola.

   Su ogni dispositivo senza zone sicure `--safe-top` vale 0: la fascia esiste
   ma è alta zero e non cambia assolutamente nulla.
   z-index 35: sopra le barre appiccicate (30), sotto la barra delle schede
   (40) e sotto tutti i pannelli a schermo intero, che hanno una zona sicura
   propria. */
body::after {
  content: "";
  position: fixed;
  top: 0; left: 0; right: 0;
  height: var(--safe-top);
  z-index: 35;
  pointer-events: none;
  background: var(--bg);
}

/* Aloni di sfondo: la firma "Energia". Fissi, non scorrono, costo zero (nessun repaint). */
body::before {
  content: "";
  position: fixed;
  inset: -30% -30% auto -30%;
  height: 70vh;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(45% 40% at 22% 12%, var(--bg-alone-1), transparent 70%),
    radial-gradient(45% 40% at 82% 6%, var(--bg-alone-2), transparent 70%);
}

button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: none; cursor: pointer; }

/* ---------- RISPOSTA AL TOCCO (regola unica dell'app) ----------
   Ogni cosa toccabile affonda dello stesso tanto (--scala-tocco) e nello stesso
   tempo (--t-tocco). È la regola che, più di ogni altra, fa sembrare l'app
   "un pezzo solo": il dito riceve sempre la stessa risposta, che stia premendo
   una casella della spesa, una scheda in basso o una riga del calendario.
   I componenti che hanno bisogno di affondare di più (icone tonde, caselle
   della settimana) sovrascrivono con la propria regola, più specifica.
   Si anima solo `transform`: nessun ricalcolo di layout, 60 fps garantiti. */
button, [role="button"], summary, label.tattile {
  transition: transform var(--t-tocco) var(--e), opacity var(--t-tocco) var(--e),
              background-color var(--t-tocco) var(--e), border-color var(--t-tocco) var(--e),
              color var(--t-tocco) var(--e);
}
button:active:not(:disabled),
[role="button"]:active { transform: scale(var(--scala-tocco)); }
a { color: var(--acc-txt); text-decoration: none; }
ul, ol { list-style: none; }

:focus-visible {
  outline: 3px solid var(--acc-3);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

/* Cifre operative: mai ballerine, sempre leggibili a distanza di braccio (eredità Ghisa). */
.num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; letter-spacing: -.02em; }

.eti {
  font-size: var(--f-eti);
  letter-spacing: .14em;
  text-transform: uppercase;
  font-weight: var(--peso-forte);
  color: var(--txt-3);
}

.nascosto { display: none !important; }
.solo-lettori {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- INTESTAZIONE ---------- */
.intesta {
  padding: calc(20px + var(--safe-top)) 18px 6px;
}
/* Sezioni con la riga «Indietro»: la zona sicura è già stata scavalcata da
   quella riga, qui resta solo il respiro tipografico. */
#cima.con-indietro .intesta { padding-top: 10px; }
/* Riga alta della testata: data a sinistra, azione della settimana (PDF) a destra.
   Il titolo resta sotto, a piena larghezza: i nomi delle sedute sono lunghi e
   non devono mai dividersi lo spazio con un pulsante. */
.intesta-riga {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-2);
  min-height: var(--touch);
}
.intesta-riga .data { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.intesta .data { font-size: var(--f-xs); color: var(--txt-2); font-weight: var(--peso-medio); letter-spacing: .02em; }
.intesta h1 {
  font-size: var(--f-xl);
  font-weight: var(--peso-forte);
  letter-spacing: -.035em;
  line-height: 1.08;
  margin-top: 3px;
}
.intesta .sotto { font-size: var(--f-s); color: var(--txt-2); margin-top: 6px; line-height: 1.4; }

/* ---------- TITOLO VARIABILE (nome della seduta) ----------
   I nomi delle sedute nei piani vanno da «Riposo» a «AGOSTO — DORSO B
   (spessore + larghezza) + deltoidi laterali (dose 2) + bicipiti — deload, ~45».
   Prima il corpo del carattere scalava con la lunghezza del testo (34 → 26 → 21 px)
   e il numero di righe cambiava: l'intestazione era alta diversa in ogni giorno e
   tutta la schermata — striscia dei giorni compresa — saltava in su o in giù a
   ogni cambio. Qui l'altezza è INCHIODATA: una riga di titolo grande più due
   righe riservate di dettaglio, identiche per ogni giorno e per ogni sezione. */
.titolo-blocco { margin-top: 3px; }
.titolo-blocco h1 {
  font-size: 27px;
  font-weight: var(--peso-forte);
  letter-spacing: -.035em;
  line-height: 1.14;
  height: 1.14em;                 /* sempre esattamente una riga */
  margin-top: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.titolo-blocco .coda {
  font-size: var(--f-xs);
  line-height: 1.36;
  color: var(--txt-2);
  margin-top: 5px;
  height: calc(1.36em * 2);       /* due righe riservate anche quando è vuota */
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
}

.icona-tonda {
  flex: 0 0 auto;
  width: var(--touch); height: var(--touch);
  border-radius: var(--r-pill);
  display: flex; align-items: center; justify-content: center;
  background: var(--velo);
  border: 1px solid var(--bordo);
  color: var(--txt-2);
  transition: transform var(--t-veloce) var(--e), background var(--t-veloce) var(--e);
}
.icona-tonda:active { transform: scale(.92); background: var(--velo-2); }
.icona-tonda svg { width: 20px; height: 20px; }

/* ---------- PILLOLE ---------- */
/* `min-height` di una riga: nei giorni di riposo resta una pillola sola, ma lo
   spazio occupato dall'intestazione non deve cambiare rispetto a un giorno ON. */
.pillole { display: flex; gap: 7px; flex-wrap: wrap; margin-top: 12px; min-height: 30px; }
.pl {
  font-size: var(--f-eti);
  font-weight: var(--peso-forte);
  letter-spacing: .08em;
  text-transform: uppercase;
  border-radius: var(--r-pill);
  padding: 6px 13px;
  background: var(--velo);
  color: var(--txt-2);
  border: 1px solid var(--bordo);
  white-space: nowrap;
  /* Cambiando giorno le pillole passano da ON a OFF (e viceversa): il colore
     deve virare nello stesso tempo del tocco, non saltare. */
  transition: background-color var(--t-tocco) var(--e), color var(--t-tocco) var(--e),
              border-color var(--t-tocco) var(--e);
}
/* ---------- TESTATE DELLE SCHERMATE A GIORNO (Scheda e Dieta) ----------
   Condividono lo stesso guscio — riga di settimana, striscia dei sette giorni,
   carosello — e quindi DEVONO essere alte uguale: la striscia sta sotto di loro
   e la sua quota è la somma di ciò che le sta sopra. Lo scheletro è identico per
   costruzione (`intesta-riga` + `titolo-blocco` + `pillole`) e questa classe
   esiste per dichiararlo: chi aggiunge una riga a una delle due sa che deve
   aggiungerla a entrambe, o la striscia tornerà a saltare. */
.intesta-giorno .pillole { align-items: center; }
/* Il numero della giornata, spinto a destra: bilancia la riga e riempie la metà
   che restava vuota. `margin-left: auto` e non una griglia a due colonne perché
   se un giorno servisse una pillola in più a sinistra la riga continua a
   funzionare, e il numero resta comunque all'estremo destro.

   L'ALTEZZA È QUELLA DI UNA PILLOLA QUALUNQUE, e va tenuta: la riga delle
   pillole è l'ultima cosa dentro l'intestazione, quindi un pixel in più qui è
   un pixel in più sotto la striscia dei giorni — e la striscia è l'elemento
   che Scheda e Dieta hanno in comune. Le altre pillole sono scatole di linea:
   alte quanto il loro `line-height` (11 px × 1,5 = 16,5 px di contenuto).
   Questa è un contenitore flessibile, quindi è alta quanto il figlio più alto:
   senza un'altezza dichiarata il numero a 13,5 px la faceva crescere.
   Misurato: 30,5 px come tutte le altre. */
.pl.kcal-testa {
  margin-left: auto;
  display: inline-flex; align-items: center; gap: 6px;
  line-height: 1;
  background: var(--velo-2);
  border-color: var(--bordo-forte);
  color: var(--txt-2);
  letter-spacing: .04em;
}
/* Ogni pezzo dentro la pillola occupa esattamente la RIGA di una pillola
   normale — corpo dell'etichetta per l'interlinea del testo, cioè 16,5 px — e
   ci centra il proprio contenuto. È l'unico modo per far convivere un numero a
   13,5 px con un'etichetta a 11 px senza che la pillola cresca: misurato, senza
   questa riga la testata della Dieta era alta 1 px in più di quella della
   Scheda e la striscia dei giorni si spostava di altrettanto. */
.pl.kcal-testa > * {
  display: inline-flex; align-items: center;
  height: calc(var(--f-eti) * 1.5);
}
.pl.kcal-testa .ci { color: var(--ok-txt); }
.pl.kcal-testa b {
  font-size: 13.5px; font-weight: var(--peso-forte); color: var(--txt);
  letter-spacing: -.02em;
}
.pl.kcal-testa .u { color: var(--txt-3); }

.pl.on    { background: var(--grad-caldo);  color: var(--txt-inv); border-color: transparent; box-shadow: var(--ombra-caldo); }
.pl.fredda{ background: var(--grad-freddo); color: var(--txt-inv); border-color: transparent; box-shadow: var(--ombra-freddo); }
.pl.verde { background: var(--grad-verde);  color: var(--txt-inv); border-color: transparent; box-shadow: var(--ombra-verde); }
.pl.demo  { border-style: dashed; color: var(--ambra); border-color: var(--ambra); background: none; }

/* ---------- SCHERMATE ----------
   Il margine interno sta sulla SCHERMATA, non su <main>: così lo strato di
   uscita (`.uscita`, più sotto) può essere posizionato a `inset: 0` dentro
   <main> e coincidere al pixel con la schermata che sta entrando. Con il
   margine su <main> sarebbe risultato spostato di 18 px a ogni cambio. */
main { padding: 0; position: relative; }
/* Il margine di fondo è SOLO il respiro fra l'ultima card e la barra delle
   schede: l'ingombro della barra è già in `body`. Erano 34 px e sommati ai 68
   della barra facevano oltre cento pixel di vuoto scorribile sotto l'ultima
   riga di ogni schermata — si scorreva e non arrivava più niente. */
.schermata { display: none; padding: 14px 18px 16px; }
.schermata.attiva { display: block; }
/* Caricamento iniziale ed errori vengono messi direttamente in <main>. */
#vista > .caricamento,
#vista > .vuoto { margin: 14px 18px; }

/* L'intestazione arriva un soffio prima del corpo: dà il senso della gerarchia.
   SI MUOVE NELLA STESSA DIREZIONE DEL CORPO, non più verso il basso. Prima la
   testata scendeva di 10 px mentre la schermata entrava di lato: due movimenti
   ortogonali nello stesso istante si leggono come uno scatto, non come una
   transizione. Con la stessa `--verso-x` (dimezzata, perché la testata è più
   vicina all'occhio e un movimento pieno sembrerebbe eccessivo) testata e
   contenuto arrivano da destra o da sinistra insieme: il gesto è uno solo. */
#cima > .intesta { animation: entra-schermata var(--t-veloce) var(--e) both; }
@keyframes entra-schermata {
  from { opacity: 0; transform: translate3d(calc(var(--verso-x) * .5), 0, 0); }
  to   { opacity: 1; transform: none; }
}

/* Aggiornamento dell'intestazione al cambio giorno: mai un lampeggio bianco,
   solo un cambio di quota di pochi pixel. */
.intesta.cambia { animation: cambia-testa var(--t-veloce) var(--e) both; }
@keyframes cambia-testa {
  from { opacity: .25; transform: translateY(-5px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- CAMBIO DI SCHERMATA (direzionale) ----------
   Le schede hanno un ordine — Oggi, Scheda, Dieta, Progressi, Altro — e quindi
   hanno una posizione nello spazio: spostandosi verso destra nella barra la
   schermata nuova entra da destra, tornando indietro entra da sinistra. È
   l'unica cosa che, senza leggere niente, dice al dito in che direzione si è
   mosso. `--verso-x` è la sola manopola: le animazioni la leggono e basta.

   Si animano SOLO `transform` e `opacity`: nessun ricalcolo di layout, tutto
   sul compositore, 60 fps anche mentre la schermata nuova finisce di montarsi.
   Nessuna istantanea, nessun top layer: l'impilamento resta quello normale del
   documento e la barra delle schede (z-index 40) sta sopra per costruzione.
   Vedi il commento esteso in js/app.js per il perché non si usano le View
   Transitions. */
:root { --verso-x: 22px; }
html[data-verso="indietro"] { --verso-x: -22px; }

.schermata.attiva { animation: sch-entra var(--t) var(--e) both; }
@keyframes sch-entra {
  from { opacity: 0; transform: translate3d(var(--verso-x), 0, 0); }
  to   { opacity: 1; transform: none; }
}
/* Quando le card arrivano a cascata la schermata rinuncia alla propria
   dissolvenza (sarebbe una seconda opacità sopra quella delle card) e tiene
   solo lo scorrimento laterale, che è ciò che comunica la direzione. */
.schermata.attiva.con-cascata { animation-name: sch-entra-piano; }
@keyframes sch-entra-piano {
  from { transform: translate3d(var(--verso-x), 0, 0); }
  to   { transform: none; }
}

/* ---------- I BLOCCHI CONDIVISI NON SI MUOVONO, PUNTO ----------
   Scheda e Dieta sono due viste dello stesso giorno e mostrano gli STESSI
   elementi in cima: la riga della settimana e la striscia dei sette giorni.
   Passando dall'una all'altra quegli elementi non cambiano — quindi non devono
   nemmeno spostarsi. Prima scorrevano di 22 px insieme al resto della
   schermata, e in mezzo alla transizione erano l'unica cosa che l'occhio poteva
   agganciare: si leggeva come uno scatto, non come un cambio di sezione.

   COME. Il blocco dichiara `data-fisso` (js/vistagiorno.js) e riceve
   l'animazione ESATTAMENTE INVERSA di quella della schermata: stessa durata,
   stessa curva, stesso valore cambiato di segno. Le due trasformazioni si
   compongono a ogni fotogramma e la somma è zero — non «quasi zero»: zero, in
   ogni istante, perché a interpolare è la stessa funzione.
   E poiché una dissolvenza non si può annullare allo stesso modo (l'opacità si
   moltiplica, non si somma), una schermata che contiene blocchi fissi rinuncia
   alla propria dissolvenza e tiene solo lo scorrimento: la classe `con-fissi`
   la mette js/app.js quando trova un `data-fisso`. */
.schermata.attiva.con-fissi { animation-name: sch-entra-piano; }
.schermata.attiva [data-fisso] { animation: sch-resta var(--t) var(--e) both; }
@keyframes sch-resta {
  from { transform: translate3d(calc(var(--verso-x) * -1), 0, 0); }
  to   { transform: none; }
}

/* Strato della schermata che se ne va: ritagliato all'altezza dello schermo
   (`overflow: clip` non crea un contenitore di scorrimento, quindi la pagina
   continua a scorrere normalmente e le barre appiccicate restano tali) e senza
   eventi, così un tocco durante l'uscita colpisce già la schermata nuova.

   PERCHÉ `fixed` E NON PIÙ `absolute` (il salto fra Scheda e Dieta).
   Le testate delle schermate NON sono alte uguale — misurate: Oggi 127 px,
   Scheda 185, Dieta 190, Progressi 84 — perché il contenuto è diverso davvero
   (un titolo di seduta su due righe, una riga di pillole che va a capo). Con lo
   strato ancorato a `main`, cambiare testata spostava anche LUI: la schermata
   che stava uscendo scivolava in giù di 5 px passando da Scheda a Dieta, e di
   58 px passando da Oggi a Scheda — e quello scatto, dipinto sopra tutto per
   110 ms, era esattamente il «salto» segnalato.

   Fissato alla finestra, e piazzato da js/app.js alle coordinate che la vista
   aveva PRIMA del cambio, lo strato non si muove di un pixel: la schermata
   vecchia sfuma esattamente dov'era, la nuova arriva di lato con la sua
   testata. Nessuno dei due contenuti «scatta» di posizione.
   `top`, `left`, `width` e `height` sono scritti inline: sono misure, non stile.
   Ancorato alla finestra non contribuisce più nemmeno all'area scorribile: la
   pagina non si allunga per 110 ms sotto l'ultima card. */
.uscita {
  position: fixed;
  top: 0; left: 0;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  /* `hidden` è la riserva per Safari 15 e precedenti, che non conoscono `clip`
     e scarterebbero la riga; `clip` è preferibile perché non crea un
     contenitore di scorrimento. Lo strato vive 110 ms, quindi anche la
     riserva è innocua. */
  overflow: hidden;
  overflow: clip;
  pointer-events: none;
  z-index: 1;
}
/* Uscita RAPIDA (110 ms) contro entrata distesa (250 ms): è la «dissolvenza
   attraverso» dei sistemi operativi. Farle durare uguale significa avere due
   schermate diverse al 50% di opacità nello stesso istante — e due schermate
   dense sovrapposte non sono un effetto, sono confusione. Così la vecchia
   sgombra subito e la nuova arriva su fondo pulito. */
.uscita > .schermata.esce { animation: sch-esce var(--t-lampo) var(--e) both; }
@keyframes sch-esce {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translate3d(calc(var(--verso-x) * -.55), 0, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .schermata.attiva,
  .schermata.attiva [data-fisso],
  .uscita > .schermata.esce,
  #cima > .intesta,
  .intesta.cambia { animation: none; }
}

.sez-tit {
  font-size: var(--f-s);
  font-weight: var(--peso-forte);
  letter-spacing: -.01em;
  color: var(--txt);
  margin: var(--sp-5) 4px var(--sp-2);
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-2);
}
/* Titolo e coda possono essere entrambi lunghi: nessuno dei due deve mai
   costringere l'altro a incolonnarsi una parola per riga. */
.sez-tit > span:first-child { min-width: 0; overflow-wrap: break-word; }
.sez-tit:first-child { margin-top: 4px; }
/* Avanzamento vivo della seduta: «4/7 esercizi · 12/21 serie». Sta nel titolo
   di sezione perché è la risposta a «quanto manca?» e deve trovarsi dove
   l'occhio già passa scorrendo, non in un riquadro in più. */
.avanz-seduta { letter-spacing: -.01em; transition: color var(--t) var(--e); }
.avanz-seduta.piena { color: var(--ok-txt); }
.sez-tit .sez-fine { display: flex; align-items: baseline; gap: var(--sp-2); flex: 0 1 auto; min-width: 0; }

/* ---------- BARRA DI NAVIGAZIONE ---------- */
.tabbar {
  position: fixed;
  bottom: 0; left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: var(--max-w);
  z-index: 40;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  background: var(--sup);
  border-top: 1px solid var(--bordo);
  box-shadow: var(--ombra-alta);
  padding-bottom: var(--safe-bot);
}
.tabbar button {
  min-height: var(--tab-h);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  color: var(--txt-3);
  font-size: 10.5px;
  font-weight: var(--peso-forte);
  letter-spacing: .04em;
  position: relative;
  /* `transform` va elencato qui o la scalatura al tocco della regola generale
     scatterebbe di colpo invece di seguire la stessa curva di tutto il resto. */
  transition: color var(--t-tocco) var(--e), transform var(--t-tocco) var(--e);
}
.tabbar button svg { width: 23px; height: 23px; transition: transform var(--t-molla) var(--e-molla); }
.tabbar button[aria-current="page"] { color: var(--txt); }
.tabbar button[aria-current="page"] svg { transform: translateY(-2px) scale(1.1); }
.tabbar button:active svg { transform: scale(.88); }

/* ---------- CURSORE DELLA SCHEDA ATTIVA ----------
   Una pillola sola che SCORRE da una voce all'altra, invece di cinque punti
   che si accendono e si spengono. È il dettaglio che lega il tocco alla
   transizione: la pillola parte nel frame del `pointerdown` (js/app.js), la
   schermata arriva subito dopo dalla stessa parte, e il gesto sembra uno.
   `--tab-i` è l'indice della scheda attiva, scritto da js/app.js; la larghezza
   del 20% è una delle cinque colonne della griglia. */
.tab-cursore {
  position: absolute;
  top: 6px; left: 0;
  width: 20%;
  height: 3px;
  display: flex; justify-content: center;
  pointer-events: none;
  transform: translate3d(calc(var(--tab-i, 0) * 100%), 0, 0);
}
/* La transizione si accende solo dopo il primo posizionamento: all'apertura
   dell'app la pillola deve essere già sotto la scheda giusta, non arrivarci
   scivolando da «Oggi». */
.tabbar.pronta .tab-cursore { transition: transform var(--t-molla) var(--e-molla); }
.tab-cursore i {
  display: block;
  width: 26px; height: 3px;
  border-radius: var(--r-pill);
  background: var(--grad-caldo);
  /* Un alone appena percettibile: sul nero della barra una pillola di 3 px
     senza luce si legge come una riga stampata, non come un indicatore acceso. */
  box-shadow: 0 0 10px -1px color-mix(in srgb, var(--acc) 75%, transparent);
}
/* ---------- SCORRIMENTO CONTINUO SULLE SCHEDE ----------
   Il dito appoggiato sulla barra la percorre e attraversa le sezioni in
   anteprima (js/app.js). Tutto ciò che si muove qui sotto è `transform` o
   `opacity`: il gesto deve restare a 60 fps mentre il dito è ancora giù.

   `touch-action: none` sulla barra è ciò che rende il gesto POSSIBILE: senza,
   il browser interpreta lo scorrimento orizzontale come uno scorrimento della
   pagina e ruba il pointer a metà strada. La barra non ha nulla da scorrere,
   quindi non si toglie nessuna funzione. */
.tabbar { touch-action: none; }
/* Durante il gesto la pillola smette di inseguire con la molla e incolla il
   dito: una molla che rincorre un dito che si muove sembra elastico rotto. */
.tabbar.scorre .tab-cursore { transition: transform var(--t-veloce) linear; }
.tabbar.scorre button svg { transition: none; }

/* La targhetta con il nome della destinazione: compare sopra la barra solo
   durante lo scorrimento. È l'unica cosa che dice DOVE si sta andando prima di
   arrivarci, ed è ciò che permette di non disegnare cinque schermate. */
.tab-anteprima {
  position: absolute;
  bottom: calc(100% + 10px); left: 50%;
  padding: 7px 16px;
  border-radius: var(--r-pill);
  background: var(--sup-alta, var(--sup));
  border: 1px solid var(--bordo-forte);
  box-shadow: var(--ombra-alta);
  color: var(--txt);
  font-size: var(--f-s); font-weight: var(--peso-forte);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translate3d(-50%, 6px, 0) scale(.94);
  transition: opacity var(--t-veloce) var(--e), transform var(--t-molla) var(--e-molla);
}
.tabbar.scorre .tab-anteprima { opacity: 1; transform: translate3d(-50%, 0, 0) scale(1); }

/* La vista arretra di un soffio mentre si sceglie: dice «sotto sta per
   cambiare» senza costare un solo ricalcolo di impaginazione. */
.anteprima-sezione #vista {
  opacity: .48;
  transform: scale(.985);
  transition: opacity var(--t-veloce) var(--e), transform var(--t-veloce) var(--e);
}
#vista { transform-origin: 50% 30%; }

/* Con il movimento ridotto resta tutto tranne il movimento: la targhetta
   compare senza scivolare e la vista non si ridimensiona. La funzione non si
   toglie mai, si toglie solo l'animazione. */
@media (prefers-reduced-motion: reduce) {
  .tab-anteprima { transition: none; transform: translate3d(-50%, 0, 0); }
  .anteprima-sezione #vista { transform: none; transition: none; }
}

/* L'icona della scheda attiva è accesa, non solo più chiara. */
.tabbar button[aria-current="page"] svg {
  filter: drop-shadow(0 3px 8px color-mix(in srgb, var(--acc) 42%, transparent));
}

/* ---------- STATO VUOTO ---------- */
.vuoto {
  text-align: center;
  padding: 40px 22px;
  background: var(--sup);
  border: 1px dashed var(--bordo-forte);
  border-radius: var(--r);
  color: var(--txt-2);
}
/* L'icona dello stato vuoto è un SVG: eredita il colore del testo, quindi
   resta leggibile in entrambi i temi senza doppie regole. */
.vuoto .ic { display: flex; justify-content: center; margin-bottom: 12px; color: var(--txt-3); }
.vuoto .ic svg { width: 34px; height: 34px; }
.vuoto h3 { font-size: var(--f-m); font-weight: var(--peso-forte); color: var(--txt); margin-bottom: 8px; letter-spacing: -.02em; }
.vuoto p { font-size: var(--f-s); line-height: 1.55; }
/* Messaggio con JavaScript disattivato: unico contenuto statico dell'app. */
.senza-js { margin: 46px 18px; }

/* ---------- CARICAMENTO ---------- */
.caricamento {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; padding: 80px 20px; color: var(--txt-3);
}
.caricamento .anello {
  width: 34px; height: 34px; border-radius: 50%;
  border: 3px solid var(--velo-2);
  border-top-color: var(--acc);
  animation: gira 800ms linear infinite;
}
@keyframes gira { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .caricamento .anello { animation-duration: 2s; } }
