/* =========================================================================
   Palestria v3.1 – animazioni
   Caricato dopo app.css e dopo gli stili delle singole pagine.

   Regole che valgono per tutto il file:
   - si animano opacity, translate / scale (proprietà singole, che si
     sommano al "transform" già usato per centrare alcuni elementi) e, per le
     entrate e le uscite, la sfocatura (filter: blur) che passa a nitido;
     niente animazioni di larghezze, altezze o righe della griglia;
   - durate brevi (0,14–0,42 s) e un'unica curva "morbida" per colori,
     dissolvenze e uscite; ciò che entra, si sposta o si preme usa una molla
     (v3.4, più sotto) e dura un po' di più perché comprende il rimbalzo;
     tutte moltiplicate per --fx-k (0,75, oppure 0,5 con le animazioni ridotte),
     tranne quelle che si ripetono di continuo (registrazione, caricamento);
   - gli effetti al passaggio del mouse solo con un mouse vero
     (hover: hover e pointer: fine): sugli schermi touch resterebbero attaccati;
   - animazioni ed effetti ridotti o spenti (scelti nelle impostazioni della
     home, vedi la sezione 18): ridotti = niente sfocature e animazioni più
     rapide, spenti = niente animazioni, sfocature né immagine di sfondo;
   - con "Riduci movimento" attivo nel sistema la regola globale di app.css
     spegne tutte le animazioni e le transizioni. Il passaggio da una pagina
     all'altra non è animato (cambio di pagina normale del browser).
   ========================================================================= */

:root {
  --ease-out: cubic-bezier(.2, .7, .2, 1);       /* parte decisa, arriva piano */
  /* Molle (v3.4). Qui la versione per i browser senza linear(): una curva con
     un solo rimbalzo. Quelle vere sono subito sotto. */
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1); /* morbida: popup, pannelli, spostamenti */
  --ease-bounce: cubic-bezier(.3, 1.9, .5, 1);    /* vivace: pressioni e piccoli elementi */
  /* Fattore di tutte le durate: i tempi scritti in questo file sono quelli
     "pieni", e con le animazioni complete durano il 25% in meno (v3.4: erano
     un po' lente). 0,5 con le animazioni ridotte (sezione 18). Per cambiare
     la velocità di tutto il sito basta questo numero, e MOTION_K in
     core/motion.js per le animazioni fatte in JavaScript. */
  --fx-k: .75;
  --dur-1: calc(140ms * var(--fx-k));  /* pressioni, piccoli cambi */
  --dur-2: calc(220ms * var(--fx-k));  /* colori, hover */
  --dur-3: calc(360ms * var(--fx-k));  /* entrate e uscite */
  /* durate delle molle: arrivano a destinazione nel primo terzo, il resto è il rimbalzo */
  --dur-spring: calc(520ms * var(--fx-k));
  --dur-bounce: calc(620ms * var(--fx-k));
}
/* Molle vere, campionate da un oscillatore smorzato (smorzamento 0,6 e 0,42):
   superano l'arrivo del 9% e del 23% e poi si assestano. Sono solo curve di
   tempo applicate a translate / scale: al browser costano come le altre. */
@supports (transition-timing-function: linear(0, 1)) {
  :root {
    --ease-spring: linear(0, 0.045, 0.155, 0.302, 0.46, 0.614, 0.751, 0.865, 0.954, 1.019, 1.062, 1.085, 1.094, 1.093, 1.084, 1.07, 1.056, 1.041, 1.027, 1.016, 1.007, 1, 0.995, 0.992, 1);
    --ease-bounce: linear(0, 0.053, 0.188, 0.371, 0.572, 0.766, 0.935, 1.068, 1.161, 1.215, 1.233, 1.224, 1.194, 1.152, 1.105, 1.06, 1.019, 0.987, 0.964, 0.951, 0.946, 0.947, 0.954, 0.963, 0.974, 0.985, 0.994, 1.002, 1.008, 1.011, 1.013, 1.012, 1);
  }
}

/* ---------- 1. Pulsanti ---------------------------------------------------- */
.btn {
  transition:
    background-color var(--dur-2) ease, border-color var(--dur-2) ease, color var(--dur-2) ease,
    box-shadow var(--dur-2) ease, filter var(--dur-2) ease,
    scale var(--dur-bounce) var(--ease-bounce), translate var(--dur-spring) var(--ease-spring);
}
/* si schiaccia in fretta sotto il dito e, rilasciato, torna con la molla */
.btn:active:not([disabled]) { scale: .95; transition-duration: calc(90ms * var(--fx-k)); transition-timing-function: var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .btn-primary:hover:not([disabled]) {
    translate: 0 -1px;
    box-shadow: 0 8px 18px -6px color-mix(in srgb, var(--primary) 60%, transparent);
  }
  .btn-danger:hover:not([disabled]) {
    translate: 0 -1px;
    box-shadow: 0 8px 18px -6px color-mix(in srgb, var(--rec) 60%, transparent);
  }
}
.btn-primary:active:not([disabled]), .btn-danger:active:not([disabled]) { translate: 0 0; box-shadow: none; }

/* freccia "indietro" e X di chiusura: piccolo movimento al passaggio del mouse */
.btn-back svg, .btn-icon svg { transition: translate var(--dur-spring) var(--ease-spring), rotate var(--dur-spring) var(--ease-spring); }
/* ingranaggio delle impostazioni (barra in alto di ogni pagina): un piccolo giro */
#settingsBtn svg { transition: rotate var(--dur-bounce) var(--ease-bounce); }
@media (hover: hover) and (pointer: fine) {
  .btn-back:hover svg { translate: -3px 0; }
  .btn-icon:hover svg { rotate: 90deg; }
  #settingsBtn:hover svg, #settingsBtn[aria-expanded="true"] svg { rotate: 60deg; }
}

/* contorno del focus da tastiera: si stringe sull'elemento invece di apparire di colpo */
:focus-visible { animation: focus-ring var(--dur-2) var(--ease-out); }
@keyframes focus-ring { from { outline-offset: 7px; outline-color: transparent; } }

/* campi di testo */
input[type=text], input[type=number], input[type=password], select, textarea {
  transition: border-color var(--dur-2) ease, box-shadow var(--dur-2) ease;
}

/* ---------- 2. Pulsante di registrazione ----------------------------------- */
#recBtn { position: relative; }
/* il pallino diventa un quadrato (e viceversa) con un piccolo rimbalzo */
#recBtn::before { transition: border-radius var(--dur-3) var(--ease-out), scale var(--dur-bounce) var(--ease-bounce); }
#recBtn.btn-danger::before { scale: .92; }
/* durante la registrazione un alone pulsa attorno al pulsante (solo opacity e scale) */
#recBtn::after {
  content: ""; position: absolute; inset: -2px; border-radius: inherit; pointer-events: none;
  border: 2px solid var(--rec); opacity: 0;
}
#recBtn.btn-danger::after { animation: rec-halo 1.8s var(--ease-out) infinite; }
@keyframes rec-halo {
  0%   { opacity: .55; scale: 1; }
  70%  { opacity: 0;   scale: 1.06; }
  100% { opacity: 0;   scale: 1.06; }
}
/* il punto rosso "REC" respira invece di lampeggiare a scatti */
body.is-recording .rec-chip .dot, .rec-dot { animation: rec-breathe 1.4s ease-in-out infinite; }
@keyframes rec-breathe { 50% { opacity: .3; scale: .75; } }
.card-rec { transition: border-color var(--dur-3) ease; }

/* ---------- 3. Controlli a segmenti: "pillola" che scivola ----------------- */
/* La pillola è un elemento aggiunto da core/segmented.js; senza JS resta lo
   stile di app.css (sfondo sul pulsante scelto). */
.seg { position: relative; }
.seg button { position: relative; z-index: 1; transition: color var(--dur-2) ease, background-color var(--dur-2) ease, scale var(--dur-bounce) var(--ease-bounce); }
.seg button:active { scale: .94; transition-duration: calc(90ms * var(--fx-k)); transition-timing-function: var(--ease-out); }
.seg__pill {
  position: absolute; top: 0; left: 0; z-index: 0; pointer-events: none;
  border-radius: 9px; background: var(--surface); box-shadow: 0 1px 3px rgba(0, 0, 0, .18);
  /* solo translate: i segmenti hanno tutti la stessa larghezza (colonne 1fr),
     quindi la larghezza della pillola cambia solo ridimensionando, senza animazione */
  transition: translate var(--dur-spring) var(--ease-spring), opacity var(--dur-2) ease;
}
.seg__pill.no-anim { transition: none; }
.seg--pill > button:is([aria-pressed="true"], [aria-checked="true"], [aria-selected="true"]) { background: transparent; box-shadow: none; }

/* ---------- 3b. Controllo a segmenti ripiegato e popup delle opzioni -------- */
/* Vedi app.css (.seg--fold, .optpop) e core/segmented.js. Il pulsante si preme
   come gli altri. Il popup entra come quelli sotto un pulsante (sezione 6): il
   pannello e lo strato con il suo vetro (::before) scendono di poco e crescono
   con la molla, dal lato del controllo, in dissolvenza; l'alone fa solo la
   dissolvenza; poi le voci entrano a cascata. Esce in fretta. */
.seg > .seg__face { transition: scale var(--dur-bounce) var(--ease-bounce), box-shadow var(--dur-2) ease; }
.seg > .seg__face:active { scale: .97; transition-duration: calc(90ms * var(--fx-k)); transition-timing-function: var(--ease-out); }

.optpop { transition: visibility 0s linear 0s; }
.optpop:not(.show) { transition: visibility 0s linear var(--dur-2); }
.optpop::before, .optpop > .panel {
  transform-origin: 50% 0;
  transition: opacity var(--dur-1) ease, translate var(--dur-spring) var(--ease-spring), scale var(--dur-spring) var(--ease-spring);
}
.optpop--up::before, .optpop--up > .panel { transform-origin: 50% 100%; }
.optpop:not(.show)::before, .optpop:not(.show) > .panel {
  opacity: 0; translate: 0 -8px; scale: .95;
  transition: opacity var(--dur-1) ease, translate var(--dur-2) var(--ease-out), scale var(--dur-2) var(--ease-out);
}
.optpop--up:not(.show)::before, .optpop--up:not(.show) > .panel { translate: 0 8px; }
.optpop:not(.show) > .halo::before,
.optpop:not(.show) > .halo::after { opacity: 0; }
.optpop__item { transition: background-color var(--dur-1) ease, color var(--dur-1) ease, scale var(--dur-bounce) var(--ease-bounce); }
.optpop__item:active { scale: .97; transition-duration: calc(90ms * var(--fx-k)); transition-timing-function: var(--ease-out); }
/* --opt-i: posizione della voce (core/segmented.js); pop-item è nella sezione 6 */
.optpop.show .optpop__item {
  animation: pop-item calc(360ms * var(--fx-k)) var(--ease-spring) backwards;
  animation-delay: calc((40ms + var(--opt-i, 0) * 30ms) * var(--fx-k));
}

/* ---------- 3c. Barra delle schede delle impostazioni ----------------------- */
/* quando si aggancia in cima al popup (app.css, .settings-nav) le schede si
   rimpiccioliscono con la molla e il vetro della fascia compare subito, prima
   che il testo le arrivi sotto */
.settings-nav > .seg { transition: scale var(--dur-bounce) var(--ease-bounce); }
.settings-nav::before { transition: opacity var(--dur-1) ease; }

/* ---------- 3d. Barre di scorrimento a scomparsa ---------------------------- */
/* app.css (.sbar) e core/scrollbar.js: il cursore compare in fretta ed esce con i popup */
.sbar__thumb {
  transition: opacity var(--dur-2) ease, scale var(--dur-2) var(--ease-out), background-color var(--dur-2) ease,
    visibility 0s linear var(--dur-2);
}
.sbar.is-on .sbar__thumb {
  transition: opacity var(--dur-1) ease, scale var(--dur-2) var(--ease-out), background-color var(--dur-2) ease, visibility 0s;
}

/* ---------- 3e. Cursori ------------------------------------------------------ */
/* app.css (.range) e core/range.js. Il pomello scivola fino al valore (tasti,
   clic sul binario, valore messo dal codice); mentre lo si trascina sta sotto
   il puntatore senza transizione (is-drag) e, rilasciato, si assesta sul passo
   più vicino con la molla (is-snap). Premuto, si ingrandisce. */
.range__rail { transition: translate var(--dur-2) var(--ease-out); }
.range__fill { transition: scale var(--dur-2) var(--ease-out); }
.range.is-snap .range__rail { transition: translate var(--dur-bounce) var(--ease-bounce); }
.range.is-snap .range__fill { transition: scale var(--dur-bounce) var(--ease-bounce); }
.range.is-drag :is(.range__rail, .range__fill) { transition: none; }
.range__thumb { transition: scale var(--dur-bounce) var(--ease-bounce), box-shadow var(--dur-2) ease; }
@media (hover: hover) and (pointer: fine) {
  .range:hover .range__thumb { scale: 1.1; }
}
.range.is-active .range__thumb { scale: 1.22; transition-duration: calc(120ms * var(--fx-k)); transition-timing-function: var(--ease-out); }

/* ---------- 4. Interruttori -------------------------------------------------- */
input.switch { transition: background-color var(--dur-2) ease, border-color var(--dur-2) ease; }
input.switch::before { transform-origin: 0 50%; transition: translate var(--dur-bounce) var(--ease-bounce), scale var(--dur-2) var(--ease-out); }
/* app.css sposta il pallino con transform: qui si usa translate, che si anima con il rimbalzo */
input.switch:checked::before { transform: none; translate: 20px 0; transform-origin: 100% 50%; }
/* tenendo premuto il pallino si allunga verso il centro (scale, non width:
   niente impaginazione) */
input.switch:active::before { scale: 1.25 1; }

/* ---------- 5. Scelta della mano ------------------------------------------- */
.choice {
  transition: border-color var(--dur-2) ease, background-color var(--dur-2) ease, box-shadow var(--dur-2) ease,
    translate var(--dur-spring) var(--ease-spring), scale var(--dur-bounce) var(--ease-bounce);
}
@media (hover: hover) and (pointer: fine) {
  .choice:hover { translate: 0 -2px; border-color: var(--muted); }
}
.choice:active { scale: .96; transition-duration: calc(90ms * var(--fx-k)); transition-timing-function: var(--ease-out); }
/* bordo di selezione disegnato con un'ombra interna: niente salto di 2 px nel contenuto */
.choice[aria-checked="true"] { border: 1px solid var(--primary); box-shadow: inset 0 0 0 2px var(--primary); }
.choice svg { transition: color var(--dur-2) ease; }
/* "scale" e non "transform": l'icona della mano sinistra è già specchiata con transform */
.choice[aria-checked="true"] svg { animation: choice-pop calc(420ms * var(--fx-k)) var(--ease-spring); }
@keyframes choice-pop { 0% { scale: .8; } 60% { scale: 1.12; } 100% { scale: 1; } }

/* ---------- 6. Popup con lo sfondo che si sfoca ----------------------------- */
/* Restano sempre nella pagina ma invisibili: così possono entrare e uscire con
   una transizione (con display:none comparivano e sparivano di colpo). Lo
   sfondo sfocato entra in dissolvenza: si anima la trasparenza dello strato
   sfocato, non il raggio della sfocatura (molto più leggero). */
.modal {
  display: flex; visibility: hidden; opacity: 0; pointer-events: none;
  transition: opacity var(--dur-3) var(--ease-out), visibility 0s linear var(--dur-3);
}
.modal.show {
  visibility: visible; opacity: 1; pointer-events: auto;
  transition: opacity var(--dur-3) var(--ease-out), visibility 0s;
}
/* il pannello entra salendo e passando da sfocato a nitido */
.modal > .panel {
  translate: 0 14px; scale: .94; filter: blur(6px);
  transition: translate var(--dur-3) var(--ease-out), scale var(--dur-3) var(--ease-out), filter var(--dur-3) var(--ease-out);
}
/* entra con la molla (un piccolo rimbalzo all'arrivo); esce senza */
.modal.show > .panel {
  translate: 0 0; scale: 1; filter: none;
  transition: translate var(--dur-spring) var(--ease-spring), scale var(--dur-spring) var(--ease-spring), filter var(--dur-3) var(--ease-out);
}

/* Popup sotto un pulsante (Impostazioni, Controlli dello Specchio; vedi
   app.css): il pannello scende di poco con la molla ed entra in dissolvenza;
   l'alone sfocato resta fermo e fa solo la dissolvenza. Muovere o scalare
   l'alone (due sfocature con maschera) lo faceva ricalcolare a ogni fotogramma
   e, passando da un popup all'altro, sfarfallava. La trasparenza si anima sul
   pannello e sui due strati dell'alone, mai su un loro contenitore comune (le
   sfocature non vedrebbero più la pagina sotto). */
.popover { transition: visibility 0s linear 0s; }
.popover:not(.show) { transition: visibility 0s linear var(--dur-3); }
/* .sbar: la barra di scorrimento del pannello (core/scrollbar.js), che gli sta
   accanto e non dentro. ::before: lo strato con il vetro del popup (app.css,
   .glass-host), subito dietro al pannello. Fanno esattamente quello che fa il pannello, così
   entrano ed escono con lui: a popup chiuso non si vedono mai, nemmeno aprendo
   e chiudendo in fretta. */
.popover::before, .popover > :is(.panel, .sbar) { transition: opacity var(--dur-2) ease, translate var(--dur-spring) var(--ease-spring); }
.popover:not(.show)::before, .popover:not(.show) > :is(.panel, .sbar) {
  opacity: 0; translate: 0 -10px;
  transition: opacity var(--dur-2) ease, translate var(--dur-3) var(--ease-out);
}
.halo::before, .halo::after { transition: opacity var(--dur-2) ease; }
.popover:not(.show) > .halo::before,
.popover:not(.show) > .halo::after { opacity: 0; }

/* Popup sopra il video (calibrazione del viso): il pannello sale e cresce con
   la molla, in dissolvenza; l'alone resta fermo e fa solo la dissolvenza (come
   sopra); poi il contenuto entra a cascata (l'icona con un piccolo scatto, il
   resto salendo). Niente filter: blur qui: sopra il video si ricalcolerebbe a
   ogni fotogramma. Resta visibile il tempo dell'uscita. */
.stage-pop { transition: visibility 0s; }
.stage-pop:not(.show) { transition: visibility 0s linear var(--dur-3); }
/* ::before: lo strato con il vetro del popup (app.css, .glass-host), grande
   quanto il pannello: fa le stesse cose */
.stage-pop .panel, .stage-pop__box::before {
  transition: opacity var(--dur-2) ease, translate var(--dur-spring) var(--ease-spring), scale var(--dur-spring) var(--ease-spring);
}
.stage-pop:not(.show) .panel, .stage-pop:not(.show) .stage-pop__box::before {
  opacity: 0; translate: 0 18px; scale: .92;
  transition: opacity var(--dur-2) ease, translate var(--dur-3) var(--ease-out), scale var(--dur-3) var(--ease-out);
}
.stage-pop:not(.show) .halo::before,
.stage-pop:not(.show) .halo::after { opacity: 0; }
/* la barra di scorrimento del pannello esce con lui */
.stage-pop .sbar { transition: opacity var(--dur-2) ease; }
.stage-pop:not(.show) .sbar { opacity: 0; }
/* la cascata riparte ogni volta che il popup si apre (l'animazione c'è solo con .show) */
.stage-pop.show .panel > * {
  animation: pop-item calc(480ms * var(--fx-k)) var(--ease-spring) backwards;
  animation-delay: calc((110ms + var(--pop-i, 0) * 50ms) * var(--fx-k));
}
.stage-pop .panel > :nth-child(2) { --pop-i: 1; }
.stage-pop .panel > :nth-child(3) { --pop-i: 2; }
.stage-pop .panel > :nth-child(4) { --pop-i: 3; }
.stage-pop .panel > :nth-child(n + 5) { --pop-i: 4; }
.stage-pop.show .panel > .panel-icon { animation-name: pop-icon; animation-timing-function: var(--ease-bounce); animation-duration: var(--dur-bounce); }
@keyframes pop-item { from { opacity: 0; translate: 0 12px; } }
@keyframes pop-icon { from { opacity: 0; scale: .4; rotate: -20deg; } }

/* ---------- 7. Overlay (caricamento, errore, conto alla rovescia) ----------- */
.overlay {
  display: flex; visibility: hidden; opacity: 0; pointer-events: none;
  transition: opacity var(--dur-3) var(--ease-out), visibility 0s linear var(--dur-3);
}
.overlay.show {
  visibility: visible; opacity: 1; pointer-events: auto;
  transition: opacity var(--dur-3) var(--ease-out), visibility 0s;
}
.overlay > .panel { transition: translate var(--dur-3) var(--ease-out), scale var(--dur-3) var(--ease-out), filter var(--dur-3) var(--ease-out); }
/* Overlay creati già visibili (caricamento, errore): partono trasparenti.
   Nei browser senza @starting-style compaiono come prima, senza errori. */
@starting-style {
  .overlay.show { opacity: 0; }
  .overlay.show > .panel { translate: 0 12px; scale: .96; filter: blur(6px); }
}
.spinner { animation-timing-function: cubic-bezier(.5, .15, .5, .85); }

/* ---------- 8. Conto alla rovescia ------------------------------------------ */
/* L'anello è un cerchio SVG (aggiunto da core/ui.js) che si svuota in ogni
   secondo animando stroke-dashoffset: si ridisegna solo il cerchio. Prima era
   un gradiente conico con maschera radiale guidato da una proprietà animata
   (@property --cd): a ogni fotogramma ricalcolo dello stile e ridisegno di
   gradiente e maschera, sopra il video. Il numero è in uno <span> a parte,
   così entra grande e sfocato e torna nitido senza toccare l'anello. */
.overlay .count { position: relative; border-color: transparent; }
/* stessa posizione del vecchio anello: occupa il bordo di 8px del cerchio */
.count__ring {
  position: absolute; left: -8px; top: -8px; width: calc(100% + 16px); height: calc(100% + 16px);
  rotate: -90deg; pointer-events: none;
}
.count__ring circle { fill: none; stroke-width: 8; }
.count__ring .count__track { stroke: rgba(255, 255, 255, .14); }
/* trattino un po' più lungo del cerchio (1): pieno senza fessura in alto */
.count__ring .count__arc { stroke: var(--live-ok); stroke-dasharray: 1.02 1.02; stroke-dashoffset: 0; }
.count__num { display: block; }
.overlay .hint { transition: opacity var(--dur-3) ease; }

/* ---------- 9. Controlli dello Specchio virtuale ---------------------------- */
/* Sono un popup sotto il loro pulsante, come le Impostazioni: stesse
   animazioni (sezione 6, .popover). Prima erano un pannello a lato del video. */

/* ---------- 10. Avvisi e banner sopra il video ------------------------------ */
/* Come i popup: sempre presenti ma invisibili, entrano scorrendo e mettendo a
   fuoco. "translate" si somma al transform che li centra orizzontalmente. */
.warning, .banner {
  visibility: hidden; opacity: 0; filter: blur(4px);
  transition: opacity var(--dur-3) var(--ease-out), translate var(--dur-3) var(--ease-out),
    scale var(--dur-3) var(--ease-out), filter var(--dur-3) var(--ease-out),
    background-color var(--dur-2) ease, color var(--dur-2) ease, visibility 0s linear var(--dur-3);
}
.warning { display: inline-flex; translate: 0 12px; }
.warning.warning-top, .banner { translate: 0 -12px; }
.banner { display: inline-flex; scale: .96; }
.warning.show, .banner.show {
  visibility: visible; opacity: 1; translate: 0 0; scale: 1; filter: none;
  transition: opacity var(--dur-3) var(--ease-out), translate var(--dur-spring) var(--ease-spring),
    scale var(--dur-spring) var(--ease-spring), filter var(--dur-3) var(--ease-out),
    background-color var(--dur-2) ease, color var(--dur-2) ease, visibility 0s;
}

/* ---------- 11. Numeri che cambiano ----------------------------------------- */
/* Il "salto" è fatto da core/motion.js (bump); qui solo il colore. */
.hud .value, .stat .value { transition: color var(--dur-2) ease; }

/* ---------- 12. Riquadro informazioni dello Specchio (tasto I) -------------- */
.hud {
  transition: opacity var(--dur-3) var(--ease-out), translate var(--dur-spring) var(--ease-spring),
    filter var(--dur-3) var(--ease-out), visibility 0s linear 0s;
}
.hud.is-off {
  opacity: 0; translate: 0 10px; filter: blur(4px); visibility: hidden;
  transition: opacity var(--dur-3) var(--ease-out), translate var(--dur-3) var(--ease-out),
    filter var(--dur-3) var(--ease-out), visibility 0s linear var(--dur-3);
}

/* ---------- 13. Barre di avanzamento ---------------------------------------- */
/* La barra si riempie con transform: scaleX (app.css e core/ui.js setBar),
   non più con width: niente impaginazione a ogni aggiornamento. */
.bar > span { transition: transform calc(180ms * var(--fx-k)) var(--ease-out), background-color var(--dur-3) ease; }

/* ---------- 15. Cambio tema chiaro/scuro ------------------------------------ */
/* core/theme.js usa document.startViewTransition: tutta la pagina sfuma dal
   vecchio al nuovo tema. */
html.theme-switching::view-transition-old(root),
html.theme-switching::view-transition-new(root) { animation-duration: var(--dur-3); animation-timing-function: ease; }
html.theme-switching::view-transition-old(root) { animation-name: theme-out; }
html.theme-switching::view-transition-new(root) { animation-name: theme-in; }
@keyframes theme-out { to { opacity: 0; } }
@keyframes theme-in { from { opacity: 0; } }

/* ---------- 15b. Sfondo: dalla home alle sezioni e ritorno ----------------- */
/* Nelle sezioni lo sfondo (app.css, body::before) è un poco ingrandito.
   Lo script in base.html mette su <html> bg-zoom-in arrivando in una sezione
   dalla home e bg-zoom-out tornando alla home da una sezione: lo sfondo parte
   dalla misura della pagina precedente e si porta lentamente a quella nuova.
   Parte insieme alla cascata dei contenuti, quando core/common.js toglie
   stagger-pending: fino ad allora resta fermo sulla misura di partenza (se
   partisse subito, parte dell'animazione passerebbe prima che la pagina sia
   disegnata). Solo scale su uno strato fisso: nessun ricalcolo dell'impaginazione. */
html.bg-zoom-in.stagger-pending body::before { scale: 1; }
html.bg-zoom-out.stagger-pending body::before { scale: 1.06; }
html.bg-zoom-in:not(.stagger-pending) body::before { animation: bg-zoom-in calc(1s * var(--fx-k)) var(--ease-out) backwards; }
@keyframes bg-zoom-in { from { scale: 1; } }
html.bg-zoom-out:not(.stagger-pending) body::before { animation: bg-zoom-out calc(1s * var(--fx-k)) var(--ease-out) backwards; }
@keyframes bg-zoom-out { from { scale: 1.06; } }

/* ---------- 15c. Accesso e uscita ------------------------------------------- */
/* Sfondo (app.css, body::after): entrando lo strato sfocato svanisce insieme
   all'ingresso dei contenuti, come lo zoom qui sopra; uscendo compare mentre
   la pagina sparisce, poi core/common.js invia l'uscita e arriva la pagina di
   accesso, già sfocata. */
html.bg-unblur.stagger-pending body::after { opacity: 1; }
html.bg-unblur:not(.stagger-pending) body::after { animation: bg-unblur calc(900ms * var(--fx-k)) var(--ease-out) backwards; }
@keyframes bg-unblur { from { opacity: 1; } }
html.is-leaving body::after { animation: bg-blur-in calc(460ms * var(--fx-k)) ease backwards; }
@keyframes bg-blur-in { from { opacity: 0; } }
/* Benvenuto, distintivo dell'amministratore (app.css, .role-badge--admin):
   appena è entrato, un riflesso lo attraversa una volta da sinistra a destra. */
.welcome .role-badge--admin::after { animation: role-shine calc(760ms * var(--fx-k)) var(--ease-out) calc(1000ms * var(--fx-k)) backwards; }
@keyframes role-shine { to { translate: 130% 0; } }
/* Uscendo, insieme allo sfondo si sfoca tutta l'interfaccia: la barra in alto e
   la pagina diventano sfocate, si rimpiccioliscono un poco e svaniscono,
   lasciando il posto alla scheda di accesso (stessa durata di LEAVE_MS in
   core/common.js). */
html.is-leaving :is(.app-header, body > main, body > .split, body > .stage-only) {
  opacity: 0; scale: .96; filter: blur(14px); pointer-events: none;
  transition: opacity calc(460ms * var(--fx-k)) ease, scale calc(460ms * var(--fx-k)) var(--ease-out), filter calc(460ms * var(--fx-k)) ease;
}
html.perf-noblur.is-leaving :is(.app-header, body > main, body > .split, body > .stage-only) { filter: none; }

/* Moduli "vivi" (app.css: accesso, nuovo utente): il campo in cui si scrive si
   allarga un poco; il pulsante si accende e cresce quando i campi obbligatori
   ci sono tutti; la rotellina entra con un piccolo scatto. */
.live-form .field input { transition: border-color var(--dur-2) ease, box-shadow var(--dur-2) ease, scale var(--dur-bounce) var(--ease-bounce); }
.live-form .field input:focus { scale: 1.025; }
.live-submit {
  scale: 1.02;
  transition:
    background-color var(--dur-3) ease, border-color var(--dur-3) ease, color var(--dur-3) ease,
    box-shadow var(--dur-2) ease, filter var(--dur-3) ease,
    scale var(--dur-bounce) var(--ease-bounce), translate var(--dur-spring) var(--ease-spring);
}
.live-submit.is-loading .live-spinner { animation: spin .8s linear infinite, spinner-in var(--dur-bounce) var(--ease-bounce) backwards; }
@keyframes spinner-in { from { scale: 0; opacity: 0; } }
/* dati non validi: il pulsante si scuote a destra e sinistra, sempre meno */
.live-submit.is-error { animation: login-shake calc(520ms * var(--fx-k)) var(--ease-out); }
@keyframes login-shake {
  0%, 100% { translate: 0 0; }
  15% { translate: -9px 0; }
  32% { translate: 8px 0; }
  50% { translate: -6px 0; }
  68% { translate: 4px 0; }
  84% { translate: -2px 0; }
}
/* il messaggio apre la scheda: qui sì si anima l'altezza, è un solo elemento e
   capita di rado (ovunque altrove il sito lo evita) */
.live-error {
  transition: grid-template-rows var(--dur-3) var(--ease-out), margin-top var(--dur-3) var(--ease-out), opacity var(--dur-2) ease;
}
/* Popup aperto (app.css, html.pop-open): il contenuto della pagina e gli
   elementi della barra in alto si rimpiccioliscono, e poi tornano, con la
   molla. I pulsanti e il logo hanno già la loro transizione sulla scala (più
   sopra); qui quella degli altri elementi della barra, con :where perché non
   tolga le transizioni a chi ne ha già. */
:is(body > main, body > .split, body > .stage-only) { transition: scale var(--dur-spring) var(--ease-spring); }
:where(.app-header__inner > :not(.brand-group, .app-header__status), .app-header .brand-group > *, .app-header__status > *) {
  transition: scale var(--dur-spring) var(--ease-spring);
}
/* Popup per eliminare un utente o una sessione (posizione: core/delpop.js):
   entra ed esce come i popup sotto i pulsanti della barra (sezione 6,
   .popover): il pannello e lo strato con il suo vetro (::before) scendono di
   poco con la molla ed entrano in dissolvenza, l'alone fa solo la dissolvenza.
   Sopra il cestino invece che sotto (.delpop--up) sale invece di scendere.
   Poi il contenuto che entra a cascata, la scossa del pulsante che vince sulla
   cascata, e la riga dell'elemento eliminato. */
.delpop { transition: visibility 0s linear 0s; }
.delpop:not(.show) { transition: visibility 0s linear var(--dur-3); }
.delpop::before, .delpop > .panel { transition: opacity var(--dur-2) ease, translate var(--dur-spring) var(--ease-spring); }
.delpop:not(.show)::before, .delpop:not(.show) > .panel {
  opacity: 0; translate: 0 -10px;
  transition: opacity var(--dur-2) ease, translate var(--dur-3) var(--ease-out);
}
.delpop--up:not(.show)::before, .delpop--up:not(.show) > .panel { translate: 0 10px; }
.delpop:not(.show) > .halo::before, .delpop:not(.show) > .halo::after { opacity: 0; }
.delpop.show > .panel > * {
  animation: pop-item calc(340ms * var(--fx-k)) var(--ease-spring) backwards;
  animation-delay: calc((60ms + var(--pop-i, 0) * 30ms) * var(--fx-k));
}
.delpop > .panel > :nth-child(4) { --pop-i: 1; }
.delpop > .panel > :nth-child(5) { --pop-i: 2; }
.delpop > .panel > :nth-child(6) { --pop-i: 3; }
.delpop > .panel > :nth-child(n + 7) { --pop-i: 4; }
.delpop .live-submit.is-error { animation: login-shake calc(520ms * var(--fx-k)) var(--ease-out); }
/* Scheda sotto una riga di un elenco (utente, sessione): si allarga verso il basso (qui sì si
   anima l'altezza: è un solo elemento, aperto a mano) mentre il contenuto
   entra a cascata, da sfocato a nitido; chiudendosi si restringe e svanisce. */
.row-chevron { transition: rotate var(--dur-bounce) var(--ease-bounce), color var(--dur-2) ease; }
.row-detail__clip { transition: grid-template-rows var(--dur-3) var(--ease-out); }
.row-detail.show .row-detail__clip { transition: grid-template-rows var(--dur-spring) var(--ease-out); }
.row-detail__inner { opacity: 0; transition: opacity var(--dur-2) ease; }
.row-detail.show .row-detail__inner { opacity: 1; }
.row-detail.show :is(.user-detail__grid > *, .user-detail__meta, .session-detail > *, .group-detail > *) {
  animation: detail-in calc(480ms * var(--fx-k)) var(--ease-spring) backwards;
  animation-delay: calc(90ms * var(--fx-k));
}
.row-detail.show :is(.user-detail__grid, .session-detail, .group-detail) > :nth-child(2) { animation-delay: calc(150ms * var(--fx-k)); }
.row-detail.show .user-detail__meta { animation-delay: calc(210ms * var(--fx-k)); }
@keyframes detail-in { from { opacity: 0; translate: 0 -10px; scale: .97; filter: blur(5px); } }
html.perf-noblur .row-detail.show :is(.user-detail__grid > *, .user-detail__meta, .session-detail > *, .group-detail > *) { animation-name: detail-in-flat; }
@keyframes detail-in-flat { from { opacity: 0; translate: 0 -10px; scale: .97; } }
/* Moduli da accendere (app.css, .mod-tile): acceso, il riquadro cresce con un
   rimbalzo mentre cambia colore; premuto si schiaccia un poco. */
.mod-tile__face {
  transition:
    background-color var(--dur-2) ease, border-color var(--dur-2) ease, color var(--dur-2) ease,
    scale var(--dur-bounce) var(--ease-bounce);
}
.mod-tile:active input + .mod-tile__face { scale: .96; transition-duration: calc(90ms * var(--fx-k)); transition-timing-function: var(--ease-out); }
.row-gone { animation: row-gone-in var(--dur-spring) var(--ease-spring) backwards; }
@keyframes row-gone-in { from { opacity: 0; scale: .96; } }
/* Pagina Utenti, popup "Nuovo utente" (app.css): il contenuto entra a
   cascata; l'esito (vetro verde) entra in dissolvenza, la spunta cresce con
   uno scatto e si disegna, le scritte salgono. */
.create-pop.show > .panel > * {
  animation: pop-item calc(420ms * var(--fx-k)) var(--ease-spring) backwards;
  animation-delay: calc((60ms + var(--pop-i, 0) * 35ms) * var(--fx-k));
}
/* la scossa del pulsante (dati non validi) vince sulla cascata qui sopra */
.create-pop.show > .panel > .live-submit.is-error { animation: login-shake calc(520ms * var(--fx-k)) var(--ease-out); }
.create-pop > .panel > :nth-child(3) { --pop-i: 1; }
.create-pop > .panel > :nth-child(4) { --pop-i: 2; }
.create-pop > .panel > :nth-child(5) { --pop-i: 3; }
.create-pop > .panel > :nth-child(6) { --pop-i: 4; }
.create-pop > .panel > :nth-child(n + 7) { --pop-i: 5; }
.create-done { transition: opacity var(--dur-3) ease, visibility 0s linear var(--dur-3); }
.create-pop.is-done .create-done { transition: opacity var(--dur-3) ease, visibility 0s; }
/* quando parte il volo verso l'elenco sparisce di colpo: al suo posto c'è la copia in volo (app.css) */
.create-pop.is-flying .create-done { transition: none; }
.create-pop.is-done .create-done__check { animation: done-pop var(--dur-bounce) var(--ease-bounce) backwards; }
.create-pop.is-done .create-done__check path { animation: draw-line calc(420ms * var(--fx-k)) var(--ease-out) calc(180ms * var(--fx-k)) backwards; }
.create-pop.is-done :is(.create-done__title, .create-done__name) { animation: pop-item calc(460ms * var(--fx-k)) var(--ease-spring) calc(160ms * var(--fx-k)) backwards; }
.create-pop.is-done .create-done__name { animation-delay: calc(230ms * var(--fx-k)); }
@keyframes done-pop { from { opacity: 0; scale: .3; } }
table.data tbody tr.is-new, table.data tbody tr.is-new td:first-child { transition-duration: calc(900ms * var(--fx-k)); }

/* La scheda di accesso si apre al contrario di come si chiude entrando: da
   piccola e sfocata al centro fino a normale e nitida, con la molla. Quando
   si arriva alla pagina (anche uscendo), non dopo una password sbagliata:
   login-in lo mette base.html. Al posto dell'ingresso a cascata (.reveal-in). */
html.login-in .login-form.reveal-in { animation-name: login-in; animation-duration: calc(680ms * var(--fx-k)); animation-timing-function: var(--ease-spring); }
@keyframes login-in { from { opacity: 0; scale: .55; filter: blur(16px); } }
html.perf-noblur.login-in .login-form.reveal-in { animation-name: login-in-flat; }
@keyframes login-in-flat { from { opacity: 0; scale: .55; } }
/* accesso riuscito: la scheda si rimpicciolisce e si sfoca fino a sparire
   (durata: LEAVE_MS in pages/login.js), poi si cambia pagina */
.login-form.is-leaving { pointer-events: none; animation: login-out calc(460ms * var(--fx-k)) cubic-bezier(.5, 0, .75, 0) forwards; }
@keyframes login-out { to { opacity: 0; scale: .55; filter: blur(16px); } }
html.perf-noblur .login-form.is-leaving { animation-name: login-out-flat; }
@keyframes login-out-flat { to { opacity: 0; scale: .55; } }

/* ---------- 16. Ingresso dei contenuti a cascata ---------------------------- */
/* La classe e la variabile --i (posizione nella cascata) sono assegnate da
   core/common.js; con "backwards" lo stile iniziale vale durante l'attesa e
   alla fine non resta nulla di "bloccato" dall'animazione. */
.reveal-in {
  animation: rise-in calc(520ms * var(--fx-k)) var(--ease-spring) backwards;
  animation-delay: calc(var(--i, 0) * 32ms * var(--fx-k));
}
@keyframes rise-in { from { opacity: 0; translate: 0 14px; filter: blur(4px); } }
/* stesso elenco di STAGGER in core/common.js */
html.stagger-pending :is(
  .container > :not(.section, .stat-grid, .vol-grid, .pose-grid),
  .container > .section > :not(.tiles),
  .container .tiles > *,
  .container .stat-grid > .stat,
  .container .vol-grid > *,
  table.data:not(.kv) tbody tr
) { opacity: 0; }
tr.reveal-in { animation-name: rise-in-row; }
@keyframes rise-in-row { from { opacity: 0; translate: 0 6px; } }
/* Barra in alto: indietro / logo, titolo, chip e pulsanti scendono di poco
   dall'alto passando da sfocati a nitidi, uno dopo l'altro da sinistra. */
.app-header .reveal-in {
  animation-name: drop-in; animation-duration: calc(480ms * var(--fx-k));
  animation-delay: calc(var(--i, 0) * 45ms * var(--fx-k));
}
@keyframes drop-in { from { opacity: 0; translate: 0 -8px; filter: blur(3px); } }
html.stagger-pending :is(.app-header__inner > :not(.brand-group, .app-header__status), .app-header .brand-group > *, .app-header__status > *) { opacity: 0; }

/* ---------- 17. Galleria pose, tabelle, schede ------------------------------ */
.pose-card { transition: border-color var(--dur-2) ease, box-shadow var(--dur-2) ease, translate var(--dur-spring) var(--ease-spring), scale var(--dur-bounce) var(--ease-bounce); }
.pose-card img { transition: scale calc(600ms * var(--fx-k)) var(--ease-out); }
.pose-card:active { scale: .97; transition-duration: calc(90ms * var(--fx-k)); transition-timing-function: var(--ease-out); }

table.data:not(.kv) tbody tr { transition: background-color var(--dur-2) ease; }
/* riga sotto il mouse: una barretta colorata entra sul bordo sinistro */
table.data:not(.kv) tbody td:first-child { transition: box-shadow var(--dur-2) var(--ease-out); }

/* Sessioni registrate, nessuna sessione: le righe dell'icona si disegnano una
   dopo l'altra mentre la scheda entra (pathLength="1" nel template). */
.empty-state__icon path { stroke-dasharray: 1 1.1; animation: draw-line calc(520ms * var(--fx-k)) var(--ease-out) backwards; }
.empty-state__icon path:nth-child(1) { animation-delay: calc(220ms * var(--fx-k)); }
.empty-state__icon path:nth-child(2) { animation-delay: calc(340ms * var(--fx-k)); }
.empty-state__icon path:nth-child(3) { animation-delay: calc(460ms * var(--fx-k)); }
@keyframes draw-line { from { stroke-dashoffset: 1.05; } }

/* schede della home: si sollevano e si premono con la molla (in app.css il
   sollevamento aveva la curva morbida) */
.tile { transition: translate var(--dur-spring) var(--ease-spring), scale var(--dur-bounce) var(--ease-bounce); }
.tile__icon { transition: translate var(--dur-3) var(--ease-out), scale var(--dur-bounce) var(--ease-bounce); }
:is(a, button).tile:active { scale: .98; transition-duration: calc(90ms * var(--fx-k)); transition-timing-function: var(--ease-out); }
.game-card { transition: border-color var(--dur-2) ease, box-shadow var(--dur-2) ease; }
.game-card__art svg { transition: scale var(--dur-bounce) var(--ease-bounce), rotate var(--dur-bounce) var(--ease-bounce); }
.naps-logo, .brand__mark { transition: scale var(--dur-bounce) var(--ease-bounce); }

@media (hover: hover) and (pointer: fine) {
  .pose-card:hover { translate: 0 -2px; }
  .pose-card:hover img { scale: 1.05; }
  table.data:not(.kv) tbody tr:hover { background: var(--surface-2); }
  table.data:not(.kv) tbody tr:hover td:first-child { box-shadow: inset 3px 0 0 var(--primary); }
  a.tile:not(.tile--reveal):hover .tile__icon { scale: 1.06; }
  .game-card:hover { border-color: var(--primary); box-shadow: var(--shadow); }
  .game-card:hover .game-card__art svg { scale: 1.08; rotate: -6deg; }
  .brand:hover .brand__mark { scale: 1.05; }
}

/* ---------- 17b. Pannello della sessione FootSense (v3.5) ------------------- */
/* Pagina FootSense, core/fslink.js. Le entrate (voci della sessione a cascata,
   riga di stato, avviso, campo del codice, Scollega) le fa il JavaScript con
   la molla, da sfocate a nitide; un codice sbagliato fa tremare il campo; la
   pastiglia dello stato fa un "pop" quando cambia; il numero dei frame inviati
   salta a ogni invio; il puntino in basso pulsa a ogni segnale del server.
   Qui i colori che sfumano e i puntini che respirano. */
.fs-rec, .fs-rec__dot, .fs-state, .fs-beat, .fs-alert, .fs-stat .value {
  transition: background-color var(--dur-2) ease, color var(--dur-2) ease;
}
/* in registrazione il puntino respira come quello della barra in alto (sezione 2) */
.fs-link[data-state="recording"] .fs-rec__dot { animation: rec-breathe 1.4s ease-in-out infinite; }
/* in attesa (collegata, avvio, invio degli ultimi dati) pulsa più lento e si allarga */
.fs-link:is([data-state="linked"], [data-state="waiting"], [data-state="starting"], [data-state="stopping"]) .fs-rec__dot {
  animation: fs-wait 1.6s ease-in-out infinite;
}
@keyframes fs-wait { 50% { scale: 1.4; opacity: .45; } }
/* la scheda si colora di rosso durante la registrazione (app.css, .card-rec) con la stessa durata */
.fs-link { transition: border-color var(--dur-3) ease, scale var(--dur-bounce) var(--ease-bounce); }

/* ---------- 18. Animazioni ed effetti ridotti o spenti ---------------------- */
/* Scelti a mano nelle impostazioni della home (core/prefs.js) e applicati da
   base.html prima del disegno, con classi su <html>:
     ridotti  perf-noblur + perf-fast
     spenti   perf-noblur + perf-off
   Le sfocature (backdrop-filter e filter: blur) sono la parte più pesante,
   soprattutto sopra il video, che le fa ricalcolare a ogni fotogramma. */

/* 18a. Niente sfocature (ridotti e spenti): né di sfondo, con sfondi più
   coprenti al loro posto, né nelle entrate e uscite. */
html.perf-noblur .app-header {
  -webkit-backdrop-filter: none; backdrop-filter: none;
  background: var(--surface); border-bottom-color: var(--line);
}
html.perf-noblur .modal { -webkit-backdrop-filter: none; backdrop-filter: none; background: rgba(19, 32, 29, .55); }
html.perf-noblur[data-theme="dark"] .modal { background: rgba(0, 0, 0, .66); }
html.perf-noblur .overlay { -webkit-backdrop-filter: none; backdrop-filter: none; background: rgba(5, 8, 7, .86); }
html.perf-noblur .hud { -webkit-backdrop-filter: none; backdrop-filter: none; background: rgba(8, 14, 13, .8); }
html.perf-noblur .panel--glass { -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--surface); }
html.perf-noblur .panel--glass .card { background: var(--surface); }
html.perf-noblur .halo { display: none; }
html.perf-noblur :is(.modal > .panel, .overlay > .panel, .warning, .banner, .hud) { filter: none !important; }
/* cascata d'ingresso: stesse animazioni senza la sfocatura */
html.perf-noblur .reveal-in:not(tr) { animation-name: rise-in-flat; }
@keyframes rise-in-flat { from { opacity: 0; translate: 0 14px; } }
html.perf-noblur .app-header .reveal-in { animation-name: drop-in-flat; }
@keyframes drop-in-flat { from { opacity: 0; translate: 0 -8px; } }

/* 18b. Animazioni più rapide (ridotti): tutte le durate a metà di quelle
   scritte (--fx-k, vedi in cima; MOTION_K in core/motion.js per quelle fatte
   in JS). Restano com'erano
   solo quelle che si ripetono (registrazione, caricamento) e l'anello del
   conto alla rovescia, che segna i secondi. */
html.perf-fast { --fx-k: .5; }

/* 18c. Tutto spento (spenti): niente animazioni né transizioni, come con
   "Riduci movimento" (anche la rotellina di caricamento e il puntino della
   registrazione restano fermi), e niente immagine di sfondo: resta il colore
   del tema (--bg). */
html.perf-off *, html.perf-off *::before, html.perf-off *::after { animation: none !important; transition: none !important; }
html.perf-off body::before { content: none; background: none; }
