/*
 * SKATE@LAB — foglio di stile condiviso.
 *
 * Principi (vedi VISION.md §9 e la guida ui-ux-specialist):
 * - mobile-first: progettato prima per ~375px (telefono a bordo pista),
 *   poi tablet/desktop.
 * - nessun framework: variabili CSS + regole semplici, coerente con
 *   l'HTML nativo minimale già in uso nei template.
 * - doppio output: leggibile a schermo E stampabile bene (vedi sezione
 *   "Stampa" in fondo) — la scheda allenamento è il caso critico.
 * - touch target generosi (min 44px) per uso da bordo pista.
 */

/* --- Tipografia (v1.27) ---------------------------------------------------
   Barlow e Barlow Condensed, self-hostate: l'app è locale/offline-first
   (VISION), quindi niente `<link>` a un CDN — a bordo pista la rete non è
   garantita, e un font che non arriva è un font che non c'è.

   Quattro tagli e non di più, perché ognuno è un file che pesa: Barlow 400
   (corpo) e 600 (etichette, testo forte, bottoni); Barlow Condensed 600
   (titoli, occhielli, intestazioni) e 700 (titolo di schermata, cifre
   grandi). Barlow 500 è stato **escluso**: a 15 px le etichette si separano
   già dal corpo per dimensione, e il 500 costava 22 KB per una sfumatura.

   Nessun corsivo, deliberatamente. I quattro `<em>` dei template nominano un
   livello di valutazione («ancora sotto *Applicare*»): diventeranno
   `.level-chip`, che esiste già, dice la stessa cosa meglio e non richiede
   un quinto file.

   Barlow ha l'altezza della x più bassa del 17 % rispetto a Manrope
   (0,449 em contro 0,540): a parità di corpo dichiarato si legge più
   piccola. Fino alla v1.28 la scala compensava moltiplicando per 1,2 ogni
   gradino, così che la dimensione **percepita** restasse quella di prima
   del cambio di carattere. Dalla v1.29 quella compensazione non c'è più: i
   corpi sono quelli dei mockup — 16 px il corpo, 14 px il secondario — e la
   scelta è del committente, che ha visto le due misure a confronto e ha
   preferito la più stretta. Quel che si perde, e la soglia che ne esce,
   stanno scritti nel commento della scala più sotto: è lì che va riletto,
   non qui.

   `font-display: swap`: se un file manca, la pagina resta leggibile sullo
   stack di sistema invece di restare bianca. Lo stack di ripiego di
   `--font-cond` mette per primo un condensato di sistema, perché ripiegare
   su un tondo cambierebbe le larghezze e romperebbe proprio le etichette
   che la condensata serve a far stare. --- */
@font-face {
  font-family: "Barlow";
  src: url("/static/fonts/Barlow-400-latin.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Barlow";
  src: url("/static/fonts/Barlow-600-latin.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Barlow Condensed";
  src: url("/static/fonts/BarlowCondensed-600-latin.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Barlow Condensed";
  src: url("/static/fonts/BarlowCondensed-700-latin.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Secondo subset per entrambe le famiglie: serve solo ai caratteri fuori
   dal latino di base. Le accentate italiane, francesi e tedesche stanno
   già nel subset sopra — questo copre il resto (es. nomi propri stranieri
   nel catalogo importato) senza pesare sul caso normale, perché il
   browser lo scarica solo se un glifo lo richiede. */
@font-face {
  font-family: "Barlow";
  src: url("/static/fonts/Barlow-400-latin-ext.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

@font-face {
  font-family: "Barlow";
  src: url("/static/fonts/Barlow-600-latin-ext.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

@font-face {
  font-family: "Barlow Condensed";
  src: url("/static/fonts/BarlowCondensed-600-latin-ext.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

@font-face {
  font-family: "Barlow Condensed";
  src: url("/static/fonts/BarlowCondensed-700-latin-ext.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

:root {
  /* --- Neutri freddi (v1.12, ritoccati alla v1.29 sui mockup) -------------
     Registro quasi piatto: bianco puro sulle card, grigio chiarissimo sulla
     pagina, testo quasi-nero. Lo scarto rispetto alla v1.28 è di pochi
     punti su ogni canale — fondo un soffio più freddo, inchiostro un soffio
     più blu, grigio del testo secondario più scuro. Quest'ultimo è l'unico
     che cambia qualcosa di misurabile: #6b7280 su bianco dava 4,8:1,
     #5F6875 dà 5,6:1, e le didascalie sono appena scese a 14 px. --- */
  --color-bg: #F5F6F8;
  --color-surface: #FFFFFF;
  --color-text: #171A21;
  --color-text-muted: #5F6875;
  --color-border: #e3e5e9;

  /* --- Accento unico: petrolio/ghiaccio (Opzione B del mockup) ----------
     Nome delle variabili invariato dalla palette verde precedente: bottoni,
     link e nav vi fanno già riferimento ovunque, quindi il cambio di
     accento è questo blocco e nient'altro — nessun template toccato.

     (v1.29) Il primario passa a #0F766E, e le altre due tinte lo seguono
     **per forza**: erano su una tonalità più fredda (187° contro 175°) e
     accostate al nuovo verde-petrolio si leggevano come un secondo
     accento, non come lo stesso più scuro e lo stesso più chiaro. Le tre
     tinte stanno ora sulla stessa tonalità e cambiano solo di luminosità.
     Bianco su #0F766E dà 5,5:1; #115E59 su bianco dà 7,6:1, ed è quello che
     vestono i link. --- */
  --color-primary: #0F766E;
  --color-primary-dark: #115E59;
  --color-primary-contrast: #ffffff;
  /* Tinta chiarissima dello stesso accento: stati selezionati, pill di nav,
     righe evidenziate, riquadri di precisazione. */
  --color-primary-soft: #E6F4F1;
  --color-ok-bg: #e6f4ea;
  --color-ok-border: #34a853;
  --color-ok-text: #1e7a34;
  --color-warning-bg: #fff4e0;
  --color-warning-border: #e0a012;
  --color-warning-text: #7a4d00;
  --color-danger-bg: #fdecea;
  --color-danger-border: #d64541;
  --color-danger-text: #a12822;
  --color-badge-bg: #ede7f6;
  --color-badge-text: #4527a0;

  /* --- (v1.21) Filtri: stato "attivo" di chip, toggle e contatore. Alias
     dell'accento già in uso, nessun colore nuovo: un filtro attivo è un
     elemento selezionato, stesso vocabolario visivo delle pill di nav. --- */
  --color-chip-bg: var(--color-primary-soft);
  --color-chip-text: var(--color-primary-dark);
  --color-chip-border: var(--color-primary);

  /* --- (v1.23) Famiglie di temi. Il colore appartiene al tema e non al
     contesto in cui compare: la stessa "Condizione fisica" è verde nella
     timeline di stagione, sulla scheda e nei filtri del catalogo. Sette
     famiglie e non undici temi perché oltre le sette-otto tinte due
     qualsiasi diventano indistinguibili per chi ha un deficit di visione
     cromatica — il tema resta identificato dalla sua etichetta, che in
     questi grafici c'è sempre.

     L'ordine di queste righe è quello su cui la palette è stata verificata
     per coppie adiacenti (`ThemeFamily::ALL` in core): due famiglie vicine
     sono garantite distinguibili solo così.

     NON usare queste tinte dove il colore significa già "quanto" (le scale
     ordinali hanno la loro rampa a un tono, e due passi di quella rampa
     ripetono per hex due valori di qui). Una superficie usa l'una o
     l'altra, mai entrambe.

     Unica fonte di verità: `ThemeFamily::color_light`/`color_dark` in core.
     Se cambiano lì, cambiano qui. --- */
  --fam-tecnica: #2a78d6;
  --fam-costruzione: #eb6834;
  --fam-recupero: #1baf7a;
  --fam-transizione: #eda100;
  --fam-gioco: #e87ba4;
  --fam-atletica: #008300;
  --fam-portiere: #4a3aa7;

  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 1rem;
  --space-4: 1.5rem;
  --space-5: 2.5rem;

  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-pill: 999px;
  /* (v1.23) Mancava: `--radius-nav` era usato da `.sidebar-link` e da
     `.team-switcher > summary` ma non definito da nessuna parte, quindi
     `border-radius: var(--radius-nav)` non produceva alcun arrotondamento.
     10px e non `--radius-pill`: la voce attiva porta una barra d'accento
     `inset` sul bordo sinistro, e con un raggio da pillola quella barra si
     incurverebbe agli estremi. */
  --radius-nav: 10px;

  /* Ombre molto tenui: il registro poggia sui bordi sottili, non
     sull'elevazione. */
  --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.04);
  --shadow-md: 0 4px 10px rgba(16, 24, 40, 0.06);

  --font-sans: "Barlow", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* Il ripiego è un condensato, non un tondo: se Barlow Condensed non
     arriva, ciò che deve reggere è la **larghezza** — è per starci dentro
     che questa famiglia esiste. */
  --font-cond: "Barlow Condensed", "Barlow Semi Condensed", "Arial Narrow", var(--font-sans);

  /* --- Scala tipografica (v1.29, corpi dei mockup «Valutazioni») ----------
     Sette gradini, ognuno un multiplo esatto di 1/16 rem, così la scala
     continua a rispettare la dimensione di testo scelta nel browser invece
     di ignorarla come farebbero i px.

     Non è più una progressione geometrica, e vale la pena saperlo prima di
     aggiungere un componente. Fino alla v1.28 il rapporto era costante
     (×1,2) e serviva a compensare l'altezza della x di Barlow; questi sono
     invece i corpi disegnati nei mockup, adottati per decisione esplicita
     del committente. Il prezzo si vede in due punti: fra `--t-3` e `--t-4`
     ci sono due pixel — un gradino che da solo non fa gerarchia, e infatti
     i ruoli che li usano cambiano anche carattere e peso — e fra `--t-4` e
     `--t-5` ce ne sono sei. Si scelga sempre un **ruolo**, mai un gradino:
     i ruoli sanno già quali dei sette stanno bene accanto a quale.

     `--t-6` e `--t-7` si sono scambiati di posto rispetto alla v1.28: nei
     mockup il titolo di schermata è più grande della cifra isolata, mentre
     prima era il contrario. Il gradino più alto è quindi il titolo.

     **La soglia è cambiata, ed è una decisione, non una dimenticanza.**
     Fino alla v1.28 era 15 px in cassa bassa, misurata al campo il
     2026-09-06 in piedi, con il telefono a distanza di braccio. Adesso:

       · 14 px (`--t-2`) è il minimo per il testo in **cassa bassa** —
         etichette, didascalie, corpo secondario;
       · 12 px (`--t-1`) è ammesso **solo in maiuscoletto**, perché una
         parola tutta maiuscola si legge sull'altezza delle maiuscole
         (0,662 em in Barlow Condensed, cioè 7,9 px a un corpo di 12) e non
         su quella della x, che a 12 px sarebbe 5,4 px. Un occhiello in
         cassa bassa a `--t-1` resta un difetto, non una scelta.

     Il ragionamento della v1.27 non era sbagliato e non va cancellato: a
     bordo pista, in piedi, con il telefono a distanza di braccio, 14 px in
     cassa bassa si decifrano ma non si leggono a colpo d'occhio come 15 —
     ed è a colpo d'occhio che questa app viene letta durante una seduta.
     È un **rischio accettato**: il committente ha visto le due misure a
     confronto e ha scelto la più stretta perché la pagina ci guadagna in
     densità, che su una matrice di quattordici colonne è quel che serve. Se
     un giorno una seduta dimostrerà che sul campo non regge, il posto da
     cui ripartire è questo: sono sette righe.

     Come verificare che la regola regge, senza rileggere il foglio: in
     console, `[...document.querySelectorAll("*")]` filtrato su
     `getComputedStyle(el).fontSize` minore di 14px e `textTransform`
     diverso da `uppercase` deve dare zero elementi su ogni schermata.
     (L'unico corpo che sta fra due gradini, i 15 px di
     `--type-figure-cell`, passa il controllo: è sopra la soglia.) --- */
  --t-1: 0.75rem; /* 12 px — occhielli, intestazioni di tabella; solo in maiuscoletto */
  --t-2: 0.875rem; /* 14 px — etichette, didascalie, corpo secondario */
  --t-3: 1rem; /* 16 px — corpo */
  --t-4: 1.125rem; /* 18 px — nomi, titoli di terzo livello */
  --t-5: 1.5rem; /* 24 px — titoli di sezione */
  --t-6: 1.875rem; /* 30 px — cifra isolata */
  --t-7: 2.125rem; /* 34 px — titolo di schermata */

  /* --- I ruoli ------------------------------------------------------------
     Un componente richiama un ruolo, non una dimensione: `font: var(--type-…)`
     e, dove serve, la spaziatura gemella `var(--type-…-ls)`. La scorciatoia
     `font` porta peso, dimensione, interlinea e famiglia in una riga sola;
     la spaziatura non ne fa parte e viaggia accanto.

     Attenzione a un effetto della scorciatoia: `font` azzera
     `font-variant-numeric` — e lo azzera anche quando il valore era
     **ereditato**, non dichiarato lì. Una cella dentro una tabella che
     imposta `tabular-nums` perde le cifre tabulari nel momento in cui
     prende un ruolo. Perciò ogni uso di `--type-figure-cell` ridichiara
     `tabular-nums` subito dopo la scorciatoia: è il contratto del ruolo,
     non una ripetizione.

     Regola per decidere: **tabulari quando il numero va confrontato con un
     altro** — sopra e sotto in colonna, oppure con sé stesso un attimo
     prima, se si aggiorna restando dov'è. Proporzionali quando è solo.

     (v1.29) I pesi dei mockup non esistono tutti come file, e i pesi che
     non esistono il browser se li **inventa** ingrossando le aste con un
     algoritmo: su una grottesca la differenza fra un 700 disegnato e un 700
     finto si vede, e si vede peggio ai corpi piccoli. Imbarcati ci sono
     soltanto Barlow 400/600 e Barlow Condensed 600/700 (quattro file, vedi
     i `@font-face` in cima), quindi l'800 dei mockup sul condensato qui è
     700 e il 700 sul tondo qui è 600. Ogni arrotondamento è annotato sulla
     riga che lo subisce. Nessun file nuovo: aggiungerne uno per una
     sfumatura di peso costerebbe una ventina di KB su una app che deve
     partire anche senza rete. --- */
  --type-screen: 700 var(--t-7) / 1.05 var(--font-cond); /* mockup 800 → 700: un Barlow Condensed 800 non è imbarcato */
  --type-section: 700 var(--t-5) / 1.1 var(--font-cond);
  --type-title: 700 var(--t-4) / 1.15 var(--font-cond);
  --type-eyebrow: 700 var(--t-1) / 1.2 var(--font-cond);
  --type-eyebrow-ls: 0.09em;
  --type-thead: 700 var(--t-1) / 1.2 var(--font-cond);
  --type-thead-ls: 0.07em;
  /* Interlinea di nuovo 1,5. La v1.27 l'aveva stretta a 1,35 perché l'em
     era cresciuto di 1,2 al cambio di carattere, e il moltiplicatore si
     applica all'em: lasciando 1,5 le righe si sarebbero allontanate del
     17 % senza che nessuno l'avesse deciso. Adesso l'em è tornato a 16 px e
     quella ragione è scaduta insieme alla scala che l'aveva prodotta —
     tenere 1,35 su un corpo da 16 px stringerebbe le righe di una
     spiegazione fin quasi a toccarsi. */
  --type-body: 400 var(--t-3) / 1.5 var(--font-sans);
  --type-body-strong: 600 var(--t-3) / 1.5 var(--font-sans);
  --type-label: 600 var(--t-2) / 1.25 var(--font-sans); /* mockup 700 → 600: sul tondo il 700 sarebbe sintetico */
  --type-label-ls: 0.01em;
  --type-caption: 400 var(--t-2) / 1.35 var(--font-sans);
  --type-figure: 700 var(--t-6) / 1 var(--font-cond);
  --type-figure-sm: 700 var(--t-4) / 1.1 var(--font-cond);
  /* L'unico corpo fuori scala del foglio, e sta scritto qui perché non
     resti un mistero per chi lo trova: 15 px cade fra `--t-2` e `--t-3`. A
     14 px una cifra dentro una cella colorata perde peso contro il fondo —
     e queste cifre sono l'unico posto in cui il livello **non** vive nel
     colore; a 16 px la matrice si allarga di una colonna intera e su
     telefono si perde prima. Vive solo nelle celle e nei segmenti delle
     barre. */
  --type-figure-cell: 600 0.9375rem / 1.3 var(--font-sans);
  --type-button: 600 var(--t-3) / 1 var(--font-sans);
  --type-button-sm: 600 var(--t-2) / 1 var(--font-sans);

  /* --- La variante di stampa (v1.27) --------------------------------------
     Stessi ruoli, stesso rapporto ×1,2, unità diversa: la carta ha una
     misura fisica e i `pt` sono la sua. Sei gradini invece di sette perché
     su un foglio non c'è la cifra isolata dei riquadri di sintesi.

     Cambia la soglia, non la regola: su carta, a 30 cm, con inchiostro nero
     pieno e senza riflessi, il minimo scende — e la scheda G+S ha una
     griglia data da rispettare. Il gradino a 6 pt di `.gs-slot-tag` però
     sparisce comunque: erano 2,1 mm, e sotto quella misura la fotocopia in
     bianco e nero — che è come queste schede circolano davvero — chiude i
     contrografi.

     Undici dimensioni distinte diventano sei. Il peso 700 diventa 600
     ovunque: l'inchiostro su carta ingrossa già di suo, e il 700 di Barlow
     Condensed sotto gli 11 pt impasta le aste.

     **Da provare stampato, non in anteprima.** La resa dei pesi condensati
     ai corpi piccoli dipende dal driver, e questa è l'unica parte del
     sistema che uno schermo non sa verificare. --- */
  --tp-1: 7.5pt; /* solo maiuscoletto */
  --tp-2: 9pt; /* didascalie, note, meta */
  --tp-3: 10.5pt; /* corpo */
  --tp-4: 12.5pt; /* nome dell'esercizio */
  --tp-5: 15pt; /* sezione */
  --tp-6: 18pt; /* titolo del foglio */

  --typep-screen: 600 var(--tp-6) / 1.1 var(--font-cond);
  --typep-section: 600 var(--tp-5) / 1.15 var(--font-cond);
  --typep-title: 600 var(--tp-4) / 1.2 var(--font-cond);
  --typep-eyebrow: 600 var(--tp-1) / 1.25 var(--font-cond);
  --typep-eyebrow-ls: 0.08em;
  --typep-body: 400 var(--tp-3) / 1.15 var(--font-sans);
  --typep-body-strong: 600 var(--tp-3) / 1.15 var(--font-sans);
  --typep-caption: 400 var(--tp-2) / 1.15 var(--font-sans);

  /* Larghezza di lettura di default. Le pagine che hanno bisogno di più
     spazio la sovrascrivono con un modificatore su `<main>` (vedi
     `main_modifier` in base.html), non con una seconda base.

     (v1.29) La colonna larga scende da 76rem a 75rem: 1200 px è la misura
     su cui i mockup sono disegnati, ed è anche il punto in cui la heatmap a
     quattordici colonne smette di aver bisogno di scorrere. Sedici pixel in
     meno non si vedono; averla identica al disegno sì, quando si confronta
     una schermata con il suo mockup. */
  --content-max-width: 40rem;
  --content-max-width-wide: 75rem;
  /* Colonna laterale (filtri del catalogo). */
  --sidebar-width: 17.5rem;
  /* (v1.20) Sidebar di navigazione permanente della shell, da 1024px. */
  --app-sidebar-width: 15.5rem;

  /* --- v1.6 (Fase 3.5): serie ricorrenti + tag/badge di calendario --- */
  --color-camp-bg: #e0f2fe;
  --color-camp-text: #075985;

  /* --- (v1.24) Neutri della timeline di stagione.

     Esistono perché in quella vista il colore è **prenotato** dai temi: i
     blocchi diventano due grigi alternati, le tacche fuori piano un grigio
     medio, i confini una linea sottile. Variabili proprie e non i
     `--color-*` generici perché sono una scala coerente fra loro — due grigi
     che devono restare distinguibili l'uno dall'altro e dal fondo — e
     ritoccarne uno per un'altra pagina ne romperebbe la lettura qui.

     `--timeline-today` è inchiostro e **non** `--color-today-accent`, che è
     rosso: il rosso è riservato agli stati di allarme, e oggi non è un
     allarme (requisito §3.4). Nella vista precedente attirava l'occhio più
     dei dati. --- */
  --timeline-band-1: #f0f0ed;
  --timeline-band-2: #e4e4e0;
  --timeline-lane: #ececea;
  --timeline-line: #d8d8d3;
  --timeline-neutral-mark: #9a9c9f;
  --timeline-today: #16181a;

  /* --- v1.7 (Fase 3.5): vista stagione per mese --- */
  --color-today-accent: #d64541;
  --color-block-1: #0b6e4f;
  --color-block-2: #4527a0;
  --color-block-3: #e0a012;
  --color-block-4: #1e7a9c;
  --color-block-5: #075a3f;
}

/* --- Reset minimale --- */

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

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  font: var(--type-body);
  color: var(--color-text);
  background: var(--color-bg);
}

h1,
h2,
h3,
h4 {
  margin-top: 0;
}

h1 {
  font: var(--type-screen);
}

h2 {
  font: var(--type-section);
  margin-bottom: var(--space-2);
}

h3 {
  font: var(--type-title);
  margin-bottom: var(--space-1);
}

/* (v1.26) `h4` non aveva `font-size`: ereditava 1rem dal browser, cioè
   esattamente il corpo del testo che introduce. Nella scheda di allenamento
   «Svolgimento», «Punti chiave», «Errori comuni» e «Varianti» si
   distinguevano dal testo sotto per il solo grassetto — la gerarchia più
   debole di tutta l'app, sulla schermata che si tiene in mano durante la
   seduta. Sono occhielli: è quello il loro ruolo, e ora lo dicono.

   `.gs-exercise-title` è l'unico altro `<h4>` dei template e si riprende il
   suo stile per classe, che vince sul tipo. */
h4 {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-bottom: var(--space-1);
}

p {
  margin: 0 0 var(--space-3) 0;
}

a {
  color: var(--color-primary-dark);
}

ul,
ol {
  padding-left: 1.25rem;
}

/* (v1.27) Nessun taglio corsivo è imbarcato, e non è una dimenticanza: i
   quattro `<em>` dei template nominano un termine — un livello di
   valutazione, «il disegno» contro «il testo» — e per dire «questa è una
   parola citata» il semi-grassetto basta. Senza questa riga il browser
   sintetizzerebbe un finto corsivo inclinando le lettere del tondo, che su
   una grottesca si vede ed è brutto; con essa risparmiamo 22 KB di file per
   un effetto che era peggio del rimedio.

   L'`<em>` resta `<em>` nel markup: il lettore di schermo continua a
   sentire l'enfasi, che è il motivo per cui il tag è lì. Cambia solo come
   si vede.

   (Nella risposta al brief avevo scritto che questi `<em>` sarebbero
   diventati `.level-chip`. Sbagliato: `.level-chip` è un bersaglio di tocco
   da 44 px del form di valutazione, e dentro una frase non ci sta.) */
em {
  font-style: normal;
  font-weight: 600;
}

/* --- Andata a capo e troncamento (v1.27) ---------------------------------
   Prima di questo blocco non esisteva nessuna regola: zero `hyphens`, zero
   `overflow-wrap`, e quattro `text-overflow: ellipsis` messi dove serviva
   caso per caso. Il risultato è che ogni contenitore stretto ha risolto il
   problema a modo suo, e i nomi lunghi sono diventati una sorpresa nuova
   ogni volta.

   Le regole sono tre e dipendono dal **contenitore**, non dalla schermata:

   1. contenitore a riga singola  → `.t-clip`    (una riga + `title`)
   2. contenitore a più righe     → `.t-clamp-2` (due righe al massimo)
   3. testo corrente              → sillabazione, qui sotto, senza classe

   `.t-clip` va usata **solo** insieme a un `title` con il testo intero: un
   nome troncato che non è raggiungibile da nessun'altra parte è un nome
   perso, non un nome corto.

   La sillabazione richiede l'attributo `lang` giusto sull'elemento: senza,
   il browser non sa con quale dizionario spezzare e non spezza. Oggi
   `base.html` dichiara `lang="it"` fisso ed è corretto, perché
   l'interfaccia è in italiano; il giorno in cui seguirà il catalogo
   trilingue quell'attributo deve seguire la lingua scelta, o queste regole
   smettono di funzionare in silenzio proprio in tedesco, che è la lingua
   che ne ha più bisogno («Entscheidungsfreudigkeit» sono ventiquattro
   caratteri indivisibili). --- */
p,
li,
dd,
figcaption {
  hyphens: auto;
  /* Non spezzare parole corte: «bloc-co» in una card da tre righe si legge
     come un difetto, non come composizione. Otto caratteri minimi, quattro
     prima del trattino e tre dopo — sotto quella soglia il vantaggio non
     ripaga il trattino. La sillabazione serve alle parole tedesche da venti
     caratteri, ed è lì che deve intervenire. */
  hyphenate-limit-chars: 8 4 3;
  -webkit-hyphenate-limit-before: 4;
  -webkit-hyphenate-limit-after: 3;
  /* `break-word` e non `anywhere`: spezza solo quando non c'è alternativa e
     non influenza la larghezza minima del contenitore, che con `anywhere`
     collasserebbe e cambierebbe la geometria delle griglie. */
  overflow-wrap: break-word;
}

/* Titoli, occhielli ed etichette non si sillabano: sono corti per
   costruzione, e una parola spezzata in un titolo si legge come un errore
   di stampa. Se un titolo non ci sta, il rimedio è il titolo. */
h1,
h2,
h3,
h4,
th,
label,
legend,
.badge,
.state-badge,
.kind-badge,
.level-chip {
  hyphens: manual;
}

.t-clip,
.timeline-row-label,
.timeline-periods-row .timeline-segment,
.stack-label,
.stack-table th[scope="row"] {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.t-clamp-2 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  /* Perché la seconda riga non resti di una parola sola. */
  text-wrap: balance;
  overflow-wrap: break-word;
}

/* --- Layout condiviso (base.html) --- */

.app-header {
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  padding: var(--space-3) var(--space-3);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  position: sticky;
  top: 0;
  z-index: 10;
}

.app-header .brand {
  /* (v1.23) Badge e nome sulla stessa riga, allineati al centro: il badge è
     alto quanto due righe di testo, e senza `align-items` il nome
     galleggerebbe sulla sua linea di base. */
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font: var(--type-title);
  color: var(--color-text);
  text-decoration: none;
}

.app-header nav {
  display: flex;
  gap: var(--space-2);
  /* (v1.20) Va a capo invece di uscire dallo schermo. Con l'arrivo della
     shell la nav è passata da quattro voci a cinque ("Stagione"), e a 375px
     la quinta finiva tagliata fuori dal bordo — irraggiungibile, non solo
     brutta. `flex-wrap` costa una riga in più su telefono e non toglie
     niente a nessuno: da 1024px questa nav non si vede affatto, la sostituisce
     la sidebar. */
  flex-wrap: wrap;
}

.app-header nav a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: var(--color-text);
  font: var(--type-body-strong);
}

.app-header nav a:hover,
.app-header nav a:focus-visible {
  background: var(--color-bg);
}

.app-main {
  max-width: var(--content-max-width);
  margin: 0 auto;
  padding: var(--space-4) var(--space-3) var(--space-5) var(--space-3);
}

.page-back-link {
  display: inline-block;
  margin-bottom: var(--space-3);
  font: var(--type-caption);
}

.page-subtitle {
  color: var(--color-text-muted);
  margin-top: calc(-1 * var(--space-2));
  margin-bottom: var(--space-4);
}

/* --- Form --- */

form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
}

form.form-inline {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-end;
}

fieldset {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
  margin: 0;
}

legend {
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
  padding: 0 var(--space-1);
}

label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
}

label.label-inline {
  flex-direction: row;
  align-items: center;
  gap: var(--space-2);
}

input[type="text"],
input[type="date"],
input[type="time"],
input[type="number"],
select,
textarea {
  /* (v1.23) Peso e stile appartengono al controllo, non a chi lo contiene.
     Un campo dentro un `<label>` — il caso normale in ogni form dell'app —
     ereditava via `font: inherit` anche il peso dell'etichetta, e usciva più
     marcato di essa: il *valore* («Pista», «Transizione») competeva con il
     titolo di pagina.

     (v1.26) Il ruolo è `--type-body`, uno solo per tutti i controlli. Le
     quattro dimensioni compatte che vivevano qui sotto (0.85/0.9/0.95rem,
     una per schermata densa) sono sparite: la densità la fanno `padding` e
     `min-height`, che restano diversi: rimpicciolire il testo per far
     stare le cose è la cosa che questa revisione toglie. */
  font: var(--type-body);
  padding: var(--space-2) var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  min-height: 44px;
  width: 100%;
}

input:disabled,
select:disabled {
  background: var(--color-bg);
  color: var(--color-text-muted);
}

/* (v1.21) Freccia propria al posto di quella di sistema. Il chevron è un
   data URI sul `select` stesso, non un `<svg>` in un wrapper: così ogni
   `<select>` dell'app lo eredita senza che nessun template cambi. Il colore
   del tratto è `--color-text-muted` scritto per esteso — dentro un data URI
   una `var()` non viene risolta. */
select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 9l6 6 6-6' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.7rem center;
  background-size: 16px;
  padding-right: 2.25rem;
}

/* Un `select` disabilitato non è cliccabile: la freccia non deve suggerire
   il contrario. */
select:disabled {
  cursor: default;
  opacity: 0.7;
}

input[type="text"]:hover,
input[type="date"]:hover,
input[type="time"]:hover,
input[type="number"]:hover,
select:hover:enabled,
textarea:hover {
  border-color: var(--color-text-muted);
}

/* Anello di focus unico per tutti i campi: il default del browser cambia da
   browser a browser ed è la ragione principale per cui i controlli
   sembravano "non progettati". */
input[type="text"]:focus-visible,
input[type="date"]:focus-visible,
input[type="time"]:focus-visible,
input[type="number"]:focus-visible,
input[type="search"]:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-soft);
}

.radio-row {
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.checkbox-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.form-row {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.form-row > label {
  flex: 1 1 10rem;
}

/* ==========================================================================
   Editor dell'esercizio (v1.23) — `exercise_edit_form.html`

   Era rimasta l'unica pagina del catalogo senza revisione desktop: 40rem di
   larghezza e trenta controlli in colonna singola. Due colonne da 1024px,
   divise per mestiere e non per riempire lo spazio — a sinistra il contenuto
   della scheda (quello che si stampa e si legge a bordo pista), a destra la
   classificazione (i campi che il motore legge per scegliere l'esercizio).
   ========================================================================== */

.exercise-editor-grid {
  display: grid;
  gap: var(--space-5);
}

@media (min-width: 1024px) {
  .exercise-editor-grid {
    /* Non `1fr 1fr`: la colonna di sinistra è fatta di textarea di prosa, che
       hanno bisogno di righe lunghe; quella di destra di select e numeri, che
       non ne guadagnano niente. */
    grid-template-columns: 1.35fr 1fr;
    gap: var(--space-5);
    align-items: start;
  }
}

.editor-col {
  display: flex;
  flex-direction: column;
  /* (v1.23) Da `--space-3` a `--space-4`: tolta la cornice del fieldset (vedi
     sotto) e con undici fra select e numeri fitti nella colonna di destra, è
     il respiro che tiene separati i gruppi di campi invece di farli leggere
     come una lista unica. */
  gap: var(--space-4);
  min-width: 0;
}

/* (v1.23) Il titolo di colonna deve pesare **più** delle etichette dei campi
   che raggruppa (0.95rem/600), non meno: era 1.05rem in grigio muto, cioè
   più debole di ogni valore sotto di lui. Ora è la voce più forte della
   colonna dopo l'`<h1>` di pagina. */
.editor-col-title {
  margin: 0;
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--color-border);
  font: var(--type-title);
  color: var(--color-text);
}

/* Le textarea hanno bisogno di un'altezza propria e del ridimensionamento
   verticale, che la regola di base non dà: lì `min-height: 44px` basta per un
   campo a una riga, non per dieci righe di svolgimento.
   (v1.23) Il `font-weight: 400` non sta più qui: era l'unica riga che
   correggeva il grassetto ereditato dal `<label>`, e lo faceva solo per le
   textarea. Ora la correzione è nella regola di base sopra, dove il difetto
   nasceva, e vale per tutti i controlli. */
textarea {
  line-height: 1.5;
  min-height: 0;
  resize: vertical;
}

/* Sotto il titolo della colonna, il primo gruppo di temi secondari occupa
   due colonne di caselle invece di undici righe in fila indiana. */
@media (min-width: 480px) {
  .editor-col .checkbox-list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2) var(--space-3);
  }
}

/* (v1.23) «Temi secondari» era l'unico campo con cornice e legenda incassata
   in mezzo a una colonna di etichetta-sopra-campo: quella differenza di
   linguaggio visivo era la fonte principale dell'effetto collage. Restano
   `<fieldset>`/`<legend>` — sono il markup corretto per un gruppo di
   checkbox correlate, lettori di schermo compresi — ma spogliati del bordo e
   vestiti come una qualunque etichetta della colonna. Scoped a
   `.editor-col`: il fieldset con bordo resta com'è dove serve ancora. */
.editor-col fieldset {
  border: none;
  padding: 0;
  margin: 0;
}

.editor-col legend {
  padding: 0;
  margin-bottom: var(--space-1);
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
  color: var(--color-text);
}

/* (v1.23) Spinner nativi via dai campi numerici della colonna: su una riga
   mista con una `select` accanto (Difficoltà + Porzione di pista) i pulsanti
   +/- rompevano l'allineamento verticale fra i due controlli, e a un form
   compilato da tastiera o a tocco non aggiungono niente. */
.editor-col input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}

.editor-col input[type="number"]::-webkit-outer-spin-button,
.editor-col input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* La nota sotto un campo non deve staccarsi dal campo che spiega: nella
   colonna il `gap` la allontanerebbe quanto il controllo successivo. */
.editor-col > .field-hint {
  margin-top: calc(-1 * var(--space-2));
}

/* --- Picker giorni della settimana (v1.6, ricorrenza calendario) --- */

.weekday-picker {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.weekday-pill {
  position: relative;
}

.weekday-pill input {
  position: absolute;
  opacity: 0;
  width: 44px;
  height: 44px;
  cursor: pointer;
  margin: 0;
}

.weekday-pill span {
  display: inline-flex;
  min-width: 44px;
  min-height: 44px;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
  background: var(--color-surface);
  pointer-events: none;
}

.weekday-pill input:checked + span {
  background: var(--color-primary);
  color: #fff;
  border-color: var(--color-primary);
}

.weekday-pill input:focus-visible + span {
  outline: 2px solid var(--color-primary-dark);
  outline-offset: 2px;
}

/* --- Anteprima serie ricorrente (v1.6) --- */

.preview-panel {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
}

.preview-count {
  font: var(--type-title);
  margin-bottom: var(--space-2);
}

.preview-warning {
  color: var(--color-warning-text);
  font: var(--type-caption);
  margin-bottom: var(--space-2);
}

.preview-dates {
  max-height: 230px;
  overflow-y: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  background: var(--color-surface);
}

.preview-month-heading {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: var(--space-2) 0 var(--space-1) 0;
}

.preview-month-heading:first-child {
  margin-top: 0;
}

.date-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  padding: 2px 10px;
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  margin: 0 4px 4px 0;
}

/* --- Tag "Serie" e badge di tipo voce di calendario (v1.6) --- */

.series-tag {
  background: var(--color-badge-bg);
  color: var(--color-badge-text);
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 999px;
}

.kind-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  padding: 2px 9px;
  border-radius: 999px;
  text-transform: uppercase;
  white-space: nowrap;
}

.kind-training {
  background: var(--color-ok-bg);
  color: var(--color-ok-text);
}

.kind-match {
  background: var(--color-badge-bg);
  color: var(--color-badge-text);
}

.kind-tournament {
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
}

.kind-camp {
  background: var(--color-camp-bg);
  color: var(--color-camp-text);
}

.kind-break {
  background: var(--color-surface);
  color: var(--color-text-muted);
  border: 1px dashed var(--color-border);
}

/* --- Vista stagione per mese (v1.7, mockup Schermata 1) --- */

.season-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.block-legend {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-4) 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.block-legend li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
}

.block-swatch {
  width: 14px;
  height: 14px;
  border-radius: 4px;
  flex: none;
}

.block-legend .block-legend-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.block-legend .block-legend-name {
  font: var(--type-title);
}

.block-legend .block-legend-meta {
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.month-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.month-row {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.month-row[open] {
  border-color: var(--color-primary);
}

.month-row.is-today {
  border-left: 4px solid var(--color-today-accent);
}

.month-row summary {
  cursor: pointer;
  padding: var(--space-3);
  list-style: none;
}

.month-row summary::-webkit-details-marker {
  display: none;
}

.month-row-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.month-row-title {
  font: var(--type-title);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.today-pill {
  background: var(--color-today-accent);
  color: #fff;
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 999px;
}

.month-row-toggle-hint {
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.block-track {
  position: relative;
  height: 14px;
  background: #eef1f0;
  border-radius: 999px;
  margin: var(--space-2) 0;
  overflow: visible;
}

.block-segment {
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: 999px;
}

.block-segment.gap {
  background: repeating-linear-gradient(
    45deg,
    #f2c6c6,
    #f2c6c6 4px,
    #fbe4e4 4px,
    #fbe4e4 8px
  );
  border-radius: 999px;
}

.today-marker {
  position: absolute;
  top: -3px;
  bottom: -3px;
  width: 3px;
  background: var(--color-today-accent);
  border-radius: 2px;
}

.today-marker .today-marker-label {
  position: absolute;
  top: -1.5rem;
  left: 50%;
  transform: translateX(-50%);
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  background: var(--color-today-accent);
  color: #fff;
  padding: 1px 6px;
  border-radius: 999px;
  white-space: nowrap;
}

.month-counts {
  display: flex;
  gap: var(--space-1);
  flex-wrap: wrap;
  margin-top: var(--space-2);
}

.no-block-note {
  font: var(--type-caption);
  color: var(--color-danger-text);
  margin-top: var(--space-1);
}

.month-detail {
  padding: 0 var(--space-3) var(--space-3) var(--space-3);
  border-top: 1px solid var(--color-border);
}

.month-detail-intro {
  font: var(--type-body);
  color: var(--color-text-muted);
  margin: var(--space-2) 0 var(--space-2) 0;
}

.month-detail .entity-row {
  padding: var(--space-2) var(--space-3);
}

/* ==========================================================================
   Fascia di totali di stagione (v1.23) — `season_overview.html`

   Registro discreto, come il resto dell'app (v1.12): card bianche su fondo
   chiaro, bordo sottile, il numero in petrolio. La fascia informa, non
   grida — sotto di lei c'è già la timeline, che è l'elemento con più
   colore della pagina, e due elementi che gridano non si sentono.
   ========================================================================== */

.season-stats {
  display: grid;
  /* `auto-fit` e non tre colonne fisse: la fascia sta in cima a una pagina
     senza tetto di larghezza (`app-main-wide`), e su un monitor largo tre
     card da 600px sarebbero tre insegne. Il `minmax` le tiene fra i 15rem
     — sotto cui "su 109h 30m" va a capo — e una frazione dello spazio. */
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.season-stat {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
}

.season-stat-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* L'icona del riquadro è un'emoji, non testo: non ha peso, non ha
   maiuscoletto e non partecipa alla scala — dimensionarla con un ruolo
   tipografico direbbe una cosa falsa. Presa dal gradino del corpo perché
   deve pareggiare otticamente l'occhiello che le sta accanto. */
.season-stat-icon {
  font-size: var(--t-3);
  line-height: 1;
}

.season-stat-label {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* Numero e totale sulla stessa riga di base: il totale è il metro del
   numero, e mandarlo a capo lo trasformerebbe in un fatto a sé. */
.season-stat-figure {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-2) 0 var(--space-3) 0;
}

.season-stat-value {
  font: var(--type-figure);
  color: var(--color-primary);
  /* Le tre card mostrano numeri di larghezza diversa che cambiano nel
     tempo: le cifre a larghezza fissa impediscono al numero di ballare
     quando passa da 9 a 10.

     (v1.27) È l'eccezione alla regola generale «tabulari solo in colonna».
     Un numero isolato di solito sta meglio proporzionale — a 37 px le cifre
     a larghezza fissa lasciano attorno all'1 due mezzi spazi che si vedono.
     Ma questo numero si aggiorna da sé, ogni giorno, restando dov'è: la
     regola vera è **tabulari quando il numero va confrontato con un altro,
     e l'altro può essere sé stesso ieri**. */
  font-variant-numeric: tabular-nums;
}

.season-stat-total {
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.season-stat-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--color-bg);
  overflow: hidden;
}

.season-stat-bar-fill {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--color-primary);
  /* Una barra a zero deve restare visibile come "zero", non sparire: senza
     larghezza minima, «0 allenamenti svolti» e «nessuna barra disegnata»
     sono indistinguibili, e il secondo si legge come un difetto. */
  min-width: 3px;
}

.season-stat-caption {
  margin: var(--space-2) 0 0 0;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

/* --- Menu azioni di serie (v1.6) --- */

.series-menu {
  position: relative;
}

.series-menu > summary {
  list-style: none;
}

.series-menu > summary::-webkit-details-marker {
  display: none;
}

.series-menu-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  z-index: 5;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  min-width: 12rem;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}

/* --- Bottoni --- */

button,
.btn {
  font: inherit;
  font: var(--type-button);
  cursor: pointer;
  border: 1px solid var(--color-primary);
  border-radius: var(--radius-sm);
  background: var(--color-primary);
  color: var(--color-primary-contrast);
  min-height: 44px;
  padding: var(--space-2) var(--space-4);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

button:hover,
.btn:hover {
  background: var(--color-primary-dark);
  border-color: var(--color-primary-dark);
}

.btn-block {
  width: 100%;
}

.btn-secondary {
  background: var(--color-surface);
  color: var(--color-primary-dark);
}

.btn-secondary:hover {
  background: var(--color-bg);
}

.btn-small {
  min-height: 36px;
  padding: var(--space-1) var(--space-3);
  font: var(--type-button-sm);
}

/* (correzione runtime, ROADMAP.md Fase 3.5) Pulsante di conferma delle
   pagine di cancellazione (`delete_confirm.html`): stesse dimensioni di
   `.btn`, ma sul colore "pericolo" già in uso per `.banner-error` invece
   del verde primario — la scelta di cancellare definitivamente non deve
   somigliare visivamente a un salvataggio/conferma neutra. */
.btn-danger {
  background: var(--color-danger-border);
  border-color: var(--color-danger-border);
  color: var(--color-primary-contrast);
}

.btn-danger:hover {
  background: var(--color-danger-text);
  border-color: var(--color-danger-text);
}

/* (v1.23) Cancellazione con un click nella vista mese per mese. Rosso pieno
   su ogni riga della stagione sarebbe un muro d'allarme su una pagina che si
   scorre per leggere; rosso pieno solo al passaggio del mouse dice che il
   gesto è irreversibile nel momento in cui si sta per farlo — che è quando
   serve saperlo, visto che non c'è una conferma dopo. */
.btn-quiet-danger {
  background: var(--color-surface);
  border-color: var(--color-border);
  color: var(--color-danger-text);
}

.btn-quiet-danger:hover,
.btn-quiet-danger:focus-visible {
  background: var(--color-danger-bg);
  border-color: var(--color-danger-border);
  color: var(--color-danger-text);
}

/* Il `<form>` che avvolge il bottone non deve introdurre una riga propria
   dentro `.entity-actions`, che è già una fila di azioni con il suo `gap`. */
.entity-actions form {
  display: contents;
}

/* --- Banner (errori, stati vuoti, onboarding) --- */

.banner {
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-bottom: var(--space-4);
  border: 1px solid transparent;
}

.banner-error,
p.error {
  background: var(--color-danger-bg);
  border-color: var(--color-danger-border);
  color: var(--color-danger-text);
}

/* (correzione runtime, ROADMAP.md Fase 3.5) Stesso trattamento visivo di
   `.banner-error`: entrambe usano la palette "pericolo" — `banner-error`
   per un tentativo già fallito, `banner-danger` per l'elenco delle
   conseguenze di un'azione distruttiva non ancora eseguita. Classe
   distinta (non un semplice riuso di `banner-error`) perché il contenuto
   non è un messaggio di errore. */
.banner-danger {
  background: var(--color-danger-bg);
  border-color: var(--color-danger-border);
  color: var(--color-danger-text);
}

.banner-danger ul {
  margin: var(--space-2) 0;
  padding-left: 1.25em;
}

.banner-onboarding {
  background: var(--color-surface);
  border-color: var(--color-border);
  text-align: left;
}

/* (v1.13) Un avviso, non un errore: qualcosa è andato come doveva ma il
   risultato è parziale o va letto con una riserva — risultati troncati,
   capitolo di sole figure. */
.banner-warning {
  background: var(--color-warning-bg);
  border-color: var(--color-warning-border);
  color: var(--color-warning-text);
}

.banner-onboarding h2 {
  margin-top: 0;
}

/* --- Guardrail --- */

.guardrail {
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-bottom: var(--space-4);
  border: 1px solid transparent;
}

.guardrail.ok {
  background: var(--color-ok-bg);
  border-color: var(--color-ok-border);
  color: var(--color-ok-text);
}

.guardrail.warning {
  background: var(--color-warning-bg);
  border-color: var(--color-warning-border);
  color: var(--color-warning-text);
}

.guardrail p {
  margin: 0 0 var(--space-1) 0;
  font: var(--type-body);
}

/* (v1.26, PIANO-CATEGORIE.md Fase 1) L'avviso sul sovraccarico non è una
   nota fra le note: dice che un esercizio in scheda non va fatto da questa
   squadra. Il filetto a sinistra e il grassetto lo staccano dall'elenco
   delle note ordinarie, che nello stesso riquadro parlano di dosaggio. */
.guardrail-overload {
  margin: 0 0 var(--space-2) 0;
  padding-left: var(--space-3);
  border-left: 3px solid currentColor;
  list-style: none;
  font-weight: 600;
}

.guardrail-overload li + li {
  margin-top: var(--space-1);
}

/* --- Scheda allenamento --- */

/* (v1.27) La testata. La data non è il nome della pagina: è il contesto in
   cui la pagina si trova, e sta nell'occhiello insieme all'ora e al posto —
   le tre cose che dicono *quale* seduta è questa. */
.sheet-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
}

.sheet-context {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0;
}

/* La fascia dei dati della seduta. Erano cinque paragrafi in cui l'etichetta
   pesava quanto il valore; sono quattro coppie, occhiello sopra e dato
   sotto. Il bordo unico con `gap: 1px` sul fondo disegna i separatori senza
   quattro card: sono un dato solo diviso in quattro, non quattro oggetti. */
.session-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
  gap: 1px;
  background: var(--color-border);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
  margin: 0 0 var(--space-4) 0;
}

.session-facts .fact {
  background: var(--color-surface);
  padding: var(--space-2) var(--space-3);
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.session-facts dt {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.session-facts dd {
  font: var(--type-figure-sm);
  /* Il numero di presenti e i minuti si guardano di colpo, non si
     confrontano con una colonna: proporzionali. */
  font-variant-numeric: normal;
  margin: 0;
  min-width: 0;
}

/* «Campionato Fase 2» e «Pista» sono testo, non cifre: stessa gerarchia,
   ma andati a capo come testo e senza il taglio da tabellone. */
.session-facts dd.fact-text {
  font: var(--type-body-strong);
}

.session-facts dd small {
  font: var(--type-caption);
  color: var(--color-text-muted);
  margin-left: 0.15em;
}

.session-phase {
  margin-bottom: var(--space-5);
}

/* I minuti della fase erano fra parentesi dentro il titolo. Sono il dato
   che si cerca guardando la scheda in mano: escono, e vanno a destra dove
   l'occhio li trova senza rileggere il titolo. */
.phase-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  border-bottom: 2px solid var(--color-text);
  padding-bottom: var(--space-1);
  margin-bottom: var(--space-3);
}

.phase-head h2 {
  margin: 0;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.phase-duration {
  font: var(--type-figure-sm);
  font-variant-numeric: normal;
  color: var(--color-text-muted);
  white-space: nowrap;
}

.exercise-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-3);
}

/* Durata e difficoltà a sinistra del nome: sono i due dati che servono in
   piedi, e in una frase in mezzo al testo non si trovavano. La colonna è
   fissa perché le righe di più esercizi si incolonnino fra loro. */
.exercise-head {
  display: grid;
  grid-template-columns: 3rem 1fr;
  gap: var(--space-3);
  align-items: baseline;
  margin-bottom: var(--space-2);
}

.exercise-clock {
  font: var(--type-figure-sm);
  font-variant-numeric: tabular-nums;
  margin: 0;
  white-space: nowrap;
}

.exercise-difficulty {
  display: block;
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.exercise-card h3 {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin: 0;
  min-width: 0;
}

/* L'obiettivo dell'esercizio è la riga che si legge davvero: misura di
   lettura, non larghezza della finestra. */
.exercise-goal {
  max-width: 64ch;
}

.badge {
  display: inline-block;
  background: var(--color-badge-bg);
  color: var(--color-badge-text);
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  padding: var(--space-1) var(--space-2);
  border-radius: 999px;
}

.diagram-note,
.goalkeeper-note {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font: var(--type-caption);
  color: var(--color-text-muted);
  font-style: normal;
  margin: 0 var(--space-2) var(--space-2) 0;
}

/* --- Catalogo esercizi / import mobilesport (v1.9, §8bis) --- */

.badge-ok {
  background: var(--color-ok-bg);
  color: var(--color-ok-text);
}

.badge-warning {
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
}

.exercise-image {
  display: block;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  margin: var(--space-2) 0;
}

/* Frase di licenza + link all'articolo originale (§8bis.5): condizione
   d'uso della licenza UFSPO/G+S, deve restare leggibile anche in stampa
   (nessuna regola `@media print` la nasconde qui, a differenza di `form`/
   `.no-print`). */
.attribution {
  font: var(--type-caption);
  color: var(--color-text-muted);
  margin: var(--space-2) 0 0 0;
  /* (v1.27) L'URL dell'articolo originale è una stringa di 110 caratteri
     senza spazi: senza questo spinge la card fuori dal telefono, e il
     rimedio non può essere rimpicciolirlo — è una condizione di licenza e
     deve restare leggibile. La regola generale di andata a capo per tutta
     l'app arriva con le regole di troncamento; qui serve subito perché è
     l'unico posto dove l'app stampa un URL per esteso. */
  overflow-wrap: anywhere;
}

/* --- Elenchi squadre/stagioni/calendario --- */

.entity-list {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-4) 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.entity-row {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.entity-row .entity-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* (v1.15) Cresce per occupare lo spazio fra miniatura e azioni: senza,
     con la miniatura davanti il testo resta stretto sulla sua larghezza
     naturale e le azioni gli si accostano invece di stare a destra. Base
     `12rem` perché sotto quella larghezza la riga vada a capo invece di
     spremere il titolo in due caratteri per riga. */
  flex: 1 1 12rem;
  min-width: 0;
}

.entity-row .entity-info .entity-title {
  font: var(--type-title);
}

.entity-row .entity-info .entity-subtitle {
  color: var(--color-text-muted);
  font: var(--type-caption);
}

.entity-row .entity-actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.state-badge {
  display: inline-block;
  background: var(--color-ok-bg);
  color: var(--color-ok-text);
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  padding: var(--space-1) var(--space-2);
  border-radius: 999px;
}

.club-group {
  margin-bottom: var(--space-4);
}

.empty-state {
  background: var(--color-surface);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  text-align: center;
  color: var(--color-text-muted);
  margin-bottom: var(--space-4);
}

/* ==========================================================================
   Revisione rapida del catalogo (v1.10, `exercise_tagging_table.html`)

   Due layout per la stessa griglia, non uno solo che si arrangia.

   La pagina ha nove campi per esercizio. Sopra ~62rem di finestra stanno
   in colonne allineate, ed è ciò che la rende veloce: si scorre una
   colonna sola con l'occhio e si vede subito quali righe sono ancora
   indietro. Sotto quella soglia le colonne non ci stanno in nessun modo, e
   comprimerle è la via peggiore — è il difetto trovato provandola su una
   finestra stretta: `<select>` larghi 40px che mostravano "Pa", "C",
   "Non", e i due campi dell'età impilati uno sull'altro. Lì la tabella
   diventa una scheda per esercizio, con ogni campo etichettato: si perde
   l'allineamento fra righe (a quella larghezza non era comunque
   ottenibile) ma ogni controllo torna leggibile e compilabile.

   Il riempimento per colonna funziona identico nei due layout: agisce sui
   `data-column`, che non dipendono dalla presentazione.
   ========================================================================== */

/* Nessun tetto di larghezza, non un tetto più alto: questa pagina è una
   griglia, non un testo da leggere, e ogni rem sottratto qui diventa
   scorrimento orizzontale sulla tabella. `--content-max-width` esiste per
   tenere le righe di prosa a una lunghezza leggibile — un vincolo che qui
   non ha alcun significato. Un valore fisso, per quanto grande, sarebbe
   comunque sbagliato su qualche schermo: la larghezza giusta è quella della
   finestra. */
.app-main-wide {
  max-width: none;
}

/* (v1.17) Su uno schermo molto largo una pagina senza tetto arriva a filo
   dei bordi della finestra, che è scomodo quanto una colonna troppo
   stretta. Il respiro cresce con lo spazio disponibile invece di restare la
   spaziatura fissa pensata per il telefono. */
@media (min-width: 1400px) {
  .app-main-wide {
    padding-left: var(--space-5);
    padding-right: var(--space-5);
  }
}

/* Via di mezzo fra la colonna di lettura (40rem) e l'assenza di tetto: le
   pagine che dispongono contenuto *in griglia* — catalogo, dashboard
   squadre, scheda generata — usano bene lo spazio orizzontale ma su uno
   schermo molto largo una griglia a piena finestra sparpaglia le card
   invece di aiutare. `main_modifier` in base.html sceglie per pagina. */
.app-main-roomy {
  max-width: var(--content-max-width-wide);
}

/* (v1.19) La home sta in mezzo fra le due: resta una pagina stretta — è un
   form, non una griglia (VISION §9) — ma da 1024px affianca al form il
   pannello di contesto, e in 40rem quel pannello si riduceva a ~130px, con
   ogni etichetta spezzata su due righe. Larghezza scelta per contenere le
   due colonne (26rem del form + spazio + ~20rem del pannello) e non un rem
   di più: la home non deve diventare un cruscotto. */
.app-main-hero {
  max-width: 52rem;
}

/* (v1.27) Su un monitor da 2500 px quelle 52rem sono un terzo della
   larghezza, e il pannello di contesto restava una colonna stretta e alta
   accanto a un form alto — con un rettangolo vuoto sotto di esso.

   La colonna del form **non** cresce: un campo largo mezzo schermo non è
   più facile da compilare, è solo più largo. Cresce lo spazio, e se lo
   prende il pannello, che ha tre voci da mettere in fila invece che in
   colonna. La home resta un form; smette di essere un form in mezzo al
   nulla. */
@media (min-width: 1440px) {
  .app-main-hero {
    max-width: 64rem;
  }
}

.page-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.page-actions .field-hint {
  margin: 0;
  flex: 1 1 16rem;
}

/* Barra "applica a tutte le righe": il motivo per cui la tabella è più
   veloce del form uno-per-pagina. Sta fuori dal <form> della tabella —
   i suoi controlli non vanno inviati, servono solo a riempire le colonne. */
.fill-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-bottom: var(--space-3);
}

.fill-bar-title {
  font: var(--type-title);
  flex-basis: 100%;
}

.fill-control {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--space-2);
}

/* `display`/`white-space` espliciti: la regola globale rende ogni <label>
   una colonna flex, che qui manderebbe l'etichetta sopra il controllo. */
.fill-control label {
  display: inline-block;
  flex-direction: row;
  white-space: nowrap;
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
  color: var(--color-text-muted);
}

.fill-control select,
.fill-control input[type="number"] {
  width: auto;
  min-height: 38px;
  padding: var(--space-1) var(--space-2);
}

.fill-control input[type="number"] {
  width: 4rem;
}

.fill-dash {
  color: var(--color-text-muted);
}

.tagging-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.tagging-actions .field-hint {
  margin: 0;
}

.tagging-title {
  font: var(--type-title);
}

.tagging-meta {
  display: block;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.tagging-warning {
  display: inline-block;
  font: var(--type-caption);
  white-space: nowrap;
  color: var(--color-warning-text);
  background: var(--color-warning-bg);
  border-radius: var(--radius-sm);
  padding: 0 var(--space-1);
}

.tagging-error {
  display: block;
  margin-top: var(--space-1);
  font: var(--type-caption);
  color: var(--color-danger-text);
  white-space: normal;
}

.cell-na {
  color: var(--color-text-muted);
}

.scroll-hint {
  font: var(--type-caption);
  color: var(--color-text-muted);
  margin: 0 0 var(--space-2) 0;
}

/* --------------------------------------------------------------------------
   Layout A — schede impilate (default, finestra stretta)

   Mobile-first come il resto del file: la tabella vera è l'aggiunta del
   media query sotto, non il contrario. Gli elementi `table` sono resi
   `block`, la testata è nascosta e ogni cella porta la propria etichetta
   in `data-label` — così nessun controllo resta senza nome quando le
   colonne spariscono.
   -------------------------------------------------------------------------- */

.tagging-table,
.tagging-table thead,
.tagging-table tbody,
.tagging-table tr,
.tagging-table th,
.tagging-table td {
  display: block;
}

.tagging-table thead {
  display: none;
}

.tagging-table tr {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-bottom: var(--space-3);
}

.tagging-table td {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-1) 0;
  min-height: 44px;
}

.tagging-table td::before {
  content: attr(data-label);
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
  flex: 0 0 auto;
}

/* La cella del titolo non è un campo: niente etichetta, niente colonna
   destra — è l'intestazione della scheda. */
.tagging-table td.col-title {
  display: block;
  border-bottom: 1px solid var(--color-border);
  padding-bottom: var(--space-2);
  margin-bottom: var(--space-2);
}

.tagging-table td.col-title::before {
  content: none;
}

.tagging-table select,
.tagging-table input[type="number"] {
  width: auto;
  min-height: 40px;
  padding: var(--space-1) var(--space-2);
}

.tagging-table select {
  max-width: 60%;
  background-position: right 0.35rem center;
  background-size: 13px;
  padding-right: 1.5rem;
}

.tagging-table input[type="number"] {
  width: 4rem;
}

.tagging-table input[type="checkbox"] {
  width: 22px;
  height: 22px;
}

/* Età: i due estremi e l'avviso restano su una riga sola, mai impilati. */
.tagging-table .age-fields {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-1);
}

.tagging-table .row-invalid {
  background: var(--color-danger-bg);
  border-color: var(--color-danger-border);
}

/* --------------------------------------------------------------------------
   Layout B — tabella vera (finestra larga)

   62rem è la larghezza sotto la quale le nove colonne non entrano senza
   comprimere i controlli: la soglia è quella, non un breakpoint di
   dispositivo.
   -------------------------------------------------------------------------- */

@media (min-width: 62rem) {
  .scroll-hint {
    display: none;
  }

  /* Il riquadro del form regala 1.5rem per lato alla cornice e li toglie
     alla griglia: qui è la griglia che deve avere lo spazio. */
  .tagging-form {
    padding: var(--space-3) var(--space-2);
  }

  /* Nessun `overflow` qui, deliberatamente. `overflow-x: auto` sembrava
     innocuo — "scorri in orizzontale se serve" — ma per le regole di CSS
     un contenitore con overflow su un asse lo diventa **anche sull'altro**:
     `.table-scroll` diventava così il contenitore di riferimento per le
     posizioni sticky al suo interno. La testata, con `top: 76px`, si
     ancorava a 76px dal bordo di un contenitore che in verticale non scorre
     mai — cioè restava piantata sopra le prime due righe, coprendole.
     Senza overflow il riferimento torna a essere la finestra: la testata si
     ferma sotto l'header dell'app scorrendo la pagina, e la prima colonna
     resta ancorata a sinistra se la pagina scorre in orizzontale. */

  .tagging-table {
    display: table;
    /* Solo `max-content`, senza `min-width: 100%`: la tabella prende la
       larghezza che le serve e lascia il resto vuoto, invece di stirarsi
       fino a riempire la finestra distanziando le colonne con spazio morto.
       Su uno schermo largo la differenza è tutta la leggibilità: colonne
       vicine si confrontano con un colpo d'occhio, colonne separate da
       trecento pixel di bianco no. */
    width: max-content;
    border-collapse: collapse;
    font: var(--type-body);
  }

  .tagging-table thead {
    display: table-header-group;
  }

  .tagging-table tbody {
    display: table-row-group;
  }

  .tagging-table tr {
    display: table-row;
    background: none;
    border: 0;
    border-radius: 0;
    padding: 0;
    margin: 0;
  }

  /* Padding stretto su entrambi gli assi: nove celle × due lati in
     orizzontale, e in verticale è ciò che decide l'altezza di riga — con
     settanta righe ogni 4px moltiplica. */
  .tagging-table th,
  .tagging-table td {
    display: table-cell;
    border-bottom: 1px solid var(--color-border);
    padding: var(--space-1);
    text-align: left;
    vertical-align: middle;
    white-space: nowrap;
    min-height: 0;
  }

  /* In tabella il ⚠ basta da solo (vedi la nota nel template): il testo
     esteso resta nella modalità a schede, dove lo spazio non manca. */
  .tagging-table .warning-text {
    display: none;
  }

  .tagging-table td::before {
    content: none;
  }

  .tagging-table thead th {
    position: sticky;
    top: 76px; /* sotto .app-header, che è a sua volta sticky */
    background: var(--color-bg);
    border-bottom: 2px solid var(--color-border);
    font: var(--type-thead);
    letter-spacing: var(--type-thead-ls);
    text-transform: uppercase;
    color: var(--color-text-muted);
    z-index: 5;
  }

  /* Prima colonna ancorata: scorrendo verso destra per arrivare alle
     caselle di conferma si deve continuare a vedere *di quale esercizio*
     si sta decidendo. */
  .tagging-table .col-title {
    position: sticky;
    left: 0;
    z-index: 4;
    background: var(--color-surface);
    border-right: 1px solid var(--color-border);
    white-space: normal;
    min-width: 12rem;
    max-width: 22rem;
    margin: 0;
    padding: var(--space-1) var(--space-2);
    line-height: 1.25;
  }

  .tagging-table .tagging-meta {
    font: var(--type-caption);
  }

  .tagging-table thead .col-title {
    background: var(--color-bg);
    z-index: 6;
  }

  .tagging-table td.col-title {
    display: table-cell;
  }

  .tagging-table .row-invalid {
    border-color: transparent;
  }

  .tagging-table .row-invalid .col-title {
    background: var(--color-danger-bg);
  }

  /* Larghezze minime dei `<select>`: senza queste il browser li stringe
     sulla colonna e resta visibile solo la prima sillaba dell'opzione
     scelta — il difetto che ha motivato questa revisione. */
  /* Controlli più bassi del resto dell'app (32px contro 44px): l'altezza
     di riga è il costo dominante di una griglia da settanta righe, e questa
     è la sola pagina che non si tocca con i guanti a bordo pista — vedi la
     deroga dichiarata in testa alla sezione. */
  .tagging-table select,
  .tagging-table input[type="number"] {
    max-width: none;
    min-height: 32px;
    padding: 0 var(--space-1);
  }

  .tagging-table select[data-column="session_part"] {
    min-width: 9rem;
  }

  .tagging-table select[data-column="theme"] {
    min-width: 11rem;
  }

  .tagging-table select[data-column="learning_phase"] {
    min-width: 8.5rem;
  }

  /* Dimensionati sull'opzione *comune*, non sulla più lunga in assoluto:
     "Non richiesto" è il caso di gran lunga più frequente e ci sta;
     "Incompatibile con un portiere" viene troncato dal browser, ed è
     accettabile perché è raro e comunque leggibile aprendo il menu. */
  .tagging-table select[data-column="goalkeeper_requirement"] {
    min-width: 9rem;
  }

  .tagging-table input[type="number"] {
    width: 3.25rem;
  }

  .tagging-table input[type="checkbox"] {
    width: 18px;
    height: 18px;
  }

  .tagging-table .age-fields {
    flex-wrap: nowrap;
    justify-content: flex-start;
  }

  .tagging-table .col-narrow {
    text-align: center;
  }

  .tagging-table .col-narrow input[type="checkbox"] {
    margin: 0 auto;
    display: block;
  }

  .tagging-table thead .col-toggle {
    display: inline-block;
    margin-left: var(--space-2);
    vertical-align: middle;
  }
}

/* --- Breakpoint tablet/desktop --- */

@media (min-width: 640px) {
  .app-main {
    padding-left: var(--space-4);
    padding-right: var(--space-4);
  }

  form.form-inline label {
    flex: 1 1 auto;
  }
}

/* ==========================================================================
   Stat card (v1.12) — mockup/README-desktop-revamp.md, Revisione 2.

   Una riga di numeri in cima a una schermata, non un cruscotto: esistono
   solo dove il numero è già calcolato e **azionabile** (dice cosa fare
   dopo). Deliberatamente assenti dalla home, che resta il form di
   generazione a un click (VISION §9) e non una dashboard da attraversare.
   ========================================================================== */

.stat-card-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

@media (min-width: 640px) {
  .stat-card-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

.stat-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
}

.stat-card-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-bottom: var(--space-1);
}

/* (v1.26) Erano due regole. `.stat-card-value` stava a 1.5rem, e a quel
   corpo un nome di blocco come «Campionato Fase 2» andava a capo male:
   `.stat-card-value-text` esisteva solo per rimediarci con un corpo più
   contenuto. Con il valore sceso a `--type-title` il difetto non c'è più, e
   con lui la ragione della seconda regola. Le due classi restano nei
   template — sono la stessa cosa e ora si vede. */
.stat-card-value,
.stat-card-value-text {
  font: var(--type-title);
}

.stat-card-sub {
  margin-top: 2px;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

/* Il numero stesso è il problema: pochi candidati per una fase significa
   che la generazione non ha scelta lì. Colore di avviso, non di errore. */
.stat-card.is-warning .stat-card-value,
.stat-card.is-warning .stat-card-sub {
  color: var(--color-warning-text);
}

/* --- Miniatura di riga del catalogo (v1.15) ---
   Rapporto 2:1 come la pista regolamentare (art. 1.2.1): una foto di
   mobilesport e uno schema disegnato occupano la stessa impronta, e la lista
   non "salta" scorrendo fra esercizi di provenienza diversa. */
.entity-thumb {
  flex: none;
  width: 5rem;
  aspect-ratio: 2 / 1;
  object-fit: cover;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background: var(--color-bg);
}

.entity-thumb-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.35rem;
}

.entity-thumb-empty svg {
  width: 100%;
  height: 100%;
  fill: none;
  stroke: var(--color-border);
  stroke-width: 1.5;
}

/* La miniatura non si stampa: sulla scheda serve il diagramma a piena
   larghezza, e in un elenco stampato è decorazione. */
@media print {
  .entity-thumb {
    display: none;
  }
}

/* ==========================================================================
   Catalogo — filtri in colonna laterale da 900px (v1.12)
   ========================================================================== */

.catalog-layout {
  display: block;
}

@media (min-width: 900px) {
  .catalog-layout {
    display: grid;
    grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
    gap: var(--space-4);
    align-items: start;
  }

  /* Scorrere una lista lunga senza perdere i filtri. `top` è l'altezza
     dell'header orizzontale: quando arriverà la shell con sidebar
     (branch dedicato) andrà riletto insieme a quello. */
  .catalog-filters {
    position: sticky;
    top: var(--space-3);
  }
}

/* (v1.21) Contenitore di sola posizione: la cornice (sfondo, bordo, raggio)
   sta su `.filters-panel`, cioè sul `<form>` stesso. Prima erano due
   scatole annidate — `.catalog-filters` con bordo *dentro* il quale il
   `form` generico ne disegnava un secondo — ed è il bordo doppio che si
   vedeva sotto la testata "Tema". */
.catalog-filters {
  margin-bottom: var(--space-3);
}

/* ==========================================================================
   Pannello filtri (v1.21)

   Una sola scatola, etichette "eyebrow" invece di label in grassetto pieno
   (che pesavano quanto il valore sotto e appiattivano il ritmo), select con
   chevron proprio (regola generica sopra), i "Solo…" come righe-toggle
   intere invece di checkbox nude, e in testata quanti filtri sono attivi
   con un modo diretto per azzerarli.

   È un `<form>`, quindi eredita `display:flex; flex-direction:column` dalla
   regola generica ed è già escluso dalla stampa (`@media print`); qui si
   azzerano solo cornice e spaziature.
   ========================================================================== */

.filters-panel {
  gap: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-bottom: 0;
}

.filters-panel-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.filters-panel-title {
  margin: 0;
  flex: 1;
  font: var(--type-title);
}

.filters-count-badge {
  background: var(--color-chip-bg);
  color: var(--color-chip-text);
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  padding: 2px 9px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
}

/* Compare solo quando c'è qualcosa da azzerare (il template lo omette
   altrimenti): un "Azzera" sempre visibile e sempre inerte è rumore. */
.filters-clear {
  font: var(--type-button-sm);
  color: var(--color-text-muted);
  white-space: nowrap;
}

.filters-panel-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.filter-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

/* Eyebrow: piccola, maiuscoletta e muta — stesso registro di
   `.switcher-label`, la cosa meglio riuscita che l'app aveva già. Non
   compete col valore selezionato, che è l'informazione vera. */
.filter-eyebrow {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.toggle-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* Checkbox travestita da riga selezionabile: input trasparente sovrapposto
   più uno `span` fratello che porta l'aspetto — la stessa tecnica già usata
   per `.weekday-pill`, che non dipende da `:has()`. A riga piena e non a
   pillola: in una colonna da 17.5rem una pillola centrata lascia spazio
   morto ai lati. L'area cliccabile resta l'intera riga, ≥44px. */
.filter-toggle {
  position: relative;
  display: flex;
  /* Anche questo è un `<label>`: annulla `flex-direction: column` e il gap
     della regola generica, che qui aprirebbero uno stacco sotto la riga. */
  flex-direction: row;
  gap: 0;
}

.filter-toggle input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.filter-toggle span {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: 44px;
  padding: 0 var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
  white-space: nowrap;
  pointer-events: none;
}

.filter-toggle span::before {
  content: "";
  width: 18px;
  height: 18px;
  flex: none;
  border: 1.5px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-surface);
}

.filter-toggle input:hover + span {
  border-color: var(--color-text-muted);
}

.filter-toggle input:checked + span {
  background: var(--color-chip-bg);
  border-color: var(--color-chip-border);
  color: var(--color-chip-text);
}

.filter-toggle input:checked + span::before {
  border-color: var(--color-chip-border);
  background: var(--color-chip-border);
  box-shadow: inset 0 0 0 3px var(--color-chip-bg);
}

.filter-toggle input:focus-visible + span {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-soft);
}

.filters-panel .btn,
.filters-panel button[type="submit"] {
  width: 100%;
}

/* Variante a piena larghezza (tabella di taggatura): stessa cornice e stesse
   etichette, ma i campi in riga — la pagina non ha una colonna laterale e
   una pila di controlli larghi quanto la tabella sarebbe assurda. */
@media (min-width: 700px) {
  .filters-panel-inline .filters-panel-body {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-end;
  }

  .filters-panel-inline .filter-field {
    flex: 1 1 11rem;
  }

  .filters-panel-inline .toggle-row {
    flex-direction: row;
  }

  /* I due toggle stanno su una riga sola: `flex: 1 1 11rem` come gli altri
     campi li stringerebbe fino a spezzare "Solo da confermare" su due
     righe. Larghezza dal contenuto, e vanno in fondo alla riga. */
  .filters-panel-inline .filter-field-toggles {
    flex: 0 0 auto;
  }

  .filters-panel-inline .filters-panel-actions {
    flex: 0 0 auto;
  }

  .filters-panel-inline .btn,
  .filters-panel-inline button[type="submit"] {
    width: auto;
  }
}

/* ==========================================================================
   Dashboard squadre — griglia di club da 900px (v1.12)
   ========================================================================== */

.clubs-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}

/* Cresce col numero di club invece di essere fissata a due colonne: con un
   club solo una griglia 2×N lascia sempre una colonna vuota. */
@media (min-width: 900px) {
  .clubs-grid {
    grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr));
  }
}

/* ==========================================================================
   Home "Oggi" (v1.28 — mockup «Mockup pagina principale.png»)

   # Cosa cambia rispetto alla v1.27

   Il protagonista non è più il form. Fino alla v1.27 la pagina si apriva su
   cinque campi, e nel caso normale — la scheda del prossimo allenamento
   esiste già — quei campi non servivano a niente: la cosa da fare era
   aprirla. Adesso in cima c'è l'allenamento (giorno, ora, luogo, stato) e i
   parametri stanno sotto, **aperti**: `skaters_present` è un default fisso e
   i presenti cambiano ogni volta, quindi nasconderli dietro un "modifica"
   avrebbe messo un click davanti al campo che si tocca sempre.

   La colonna di destra resta contesto e non aggiunge passaggi: nessuno dei
   suoi elementi sta fra l'apertura della pagina e il bottone.
   ========================================================================== */

/* Le due colonne da 1024px. L'ordine del markup mette l'azione per prima —
   su telefono il contesto finisce SOTTO il bottone, non sopra: il contesto è
   un'informazione, il bottone è lo scopo della pagina. */
.home-hero {
  display: block;
}

@media (min-width: 1024px) {
  .home-hero {
    display: grid;
    /* La colonna dei campi non cresce oltre 34rem: un input largo mezzo
       schermo non è più facile da compilare, è solo più largo. Lo spazio in
       più se lo prende la colonna di destra, che ha elenchi da mandare a
       capo. */
    grid-template-columns: minmax(0, 1.15fr) minmax(20rem, 1fr);
    gap: var(--space-4);
    align-items: start;
  }
}

.home-main > form {
  border: none;
  padding: 0;
  margin: 0;
}

.home-aside {
  display: grid;
  gap: var(--space-4);
  /* `min-width: 0` su una traccia di griglia: senza, una riga lunga
     dell'elenco allenamenti allargherebbe la colonna oltre la sua frazione
     invece di andare a capo. */
  min-width: 0;
}

/* --- Intestazione: chi siamo, e quando siamo ---------------------------- */
.home-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

/* `flex: 1 1 auto` e non la larghezza naturale: senza, il blocco identità si
   riduce alla larghezza del solo `h1` («Mini (U12)», corto) e la riga sotto —
   stagione più selettore — va a capo pur avendo mezzo schermo libero
   accanto. */
.home-head-identity {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  flex: 1 1 auto;
  min-width: 0;
}

.home-head-icon {
  color: var(--color-primary);
  flex: none;
  /* Allineata all'altezza della maiuscola del titolo, non alla sua riga:
     l'icona di un'intestazione va in asse con le lettere, non con
     l'interlinea. */
  margin-top: 2px;
}

/* Il blocco dei nomi cresce con la riga: senza, è un flex item dimensionato
   al proprio contenuto più stretto, e la riga «stagione + selettore» andava a
   capo con mezzo schermo libero accanto. */
.home-head-names {
  flex: 1 1 auto;
  min-width: 0;
}

.home-head h1 {
  margin: 0;
}

/* La categoria è una qualifica del nome, non parte del nome: stesso corpo,
   peso e colore di una didascalia, così "Mini" resta la parola che si
   riconosce da lontano. */
.home-head-category {
  font: var(--type-title);
  color: var(--color-text-muted);
}

/* Riga di testo e non contenitore flex: il nome della stagione e il
   selettore sono una frase con dentro un comando, e in `flex` il `<details>`
   diventava un item che si portava dietro l'intera riga a capo anche con
   mezzo schermo libero accanto. */
.home-head-season {
  margin: 2px 0 0 0;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

/* Il selettore in intestazione: stesso componente della topbar, ma qui
   accompagna una riga di didascalia invece di occupare una barra sua — via
   l'altezza minima da voce di menu, che spingerebbe la riga a 40px. Resta un
   bersaglio da 44px grazie al padding negativo compensato dal margine. */
.team-switcher-inline {
  display: inline-block;
  vertical-align: middle;
  margin-left: var(--space-2);
}

.team-switcher-inline > summary {
  padding: var(--space-1) var(--space-2);
  min-height: 0;
  font: var(--type-caption);
  color: var(--color-primary-dark);
  border: 1px solid var(--color-border);
}

.home-head-today {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  color: var(--color-text-muted);
  /* A destra su schermo largo, ma sotto l'identità quando la riga si spezza:
     non è un'informazione da leggere per prima. */
  margin-left: auto;
}

.home-head-today svg {
  flex: none;
  margin-top: 2px;
}

.home-head-today-date,
.home-head-today-next {
  display: block;
}

.home-head-today-date {
  font: var(--type-body-strong);
  color: var(--color-text);
}

.home-head-today-next {
  font: var(--type-caption);
}

@media (min-width: 640px) {
  .home-head-today {
    text-align: right;
  }
}

/* --- La card dell'allenamento ------------------------------------------ */
/* Fondo tenue dell'accento e non bianco: è l'unica superficie della pagina
   che porta la decisione, e distinguerla per **colore di fondo** invece che
   per un filo in alto (v1.12) è ciò che la fa leggere per prima senza
   ingrandire nulla. Le altre card restano bianche, quindi il secondo colore
   non entra in competizione: compare una volta sola. */
.next-training-card {
  background: var(--color-primary-soft);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
}

/* Senza un allenamento in vista la card non porta nessuna decisione: torna
   bianca come le altre e smette di reclamare lo sguardo. */
.next-training-card-empty {
  background: var(--color-surface);
}

.next-training-head {
  margin: 0 0 var(--space-2) 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

.next-training-when {
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
  color: var(--color-primary-dark);
  background: var(--color-surface);
  border-radius: var(--radius-pill);
  padding: 3px var(--space-3);
}

/* Il titolo della pagina in tutto tranne il livello: `h1` resta il nome
   della squadra, ma la data è ciò che si legge per primo dentro la card. */
.next-training-date {
  font: var(--type-screen);
  margin: 0 0 var(--space-3) 0;
}

.next-training-meta {
  list-style: none;
  margin: 0 0 var(--space-3) 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}

.next-training-meta li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font: var(--type-body-strong);
}

.next-training-meta svg {
  color: var(--color-primary);
  flex: none;
}

.next-training-meta-sub,
.next-training-note {
  font: var(--type-caption);
  color: var(--color-text-muted);
}

/* Nota di stato, non azione: il testo resta in corpo normale e l'accento se
   lo prendono i bottoni sotto — stessa regola già applicata alla v1.27 a
   `.target-training-ready`, che questo blocco sostituisce. */
.next-training-ready {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: 0 0 var(--space-3) 0;
  padding: var(--space-3);
  background: var(--color-ok-bg);
  border-radius: var(--radius-md);
}

.next-training-ready svg {
  color: var(--color-ok-text);
  flex: none;
  margin-top: 1px;
}

.next-training-ready strong {
  display: block;
  color: var(--color-ok-text);
}

.next-training-ready-sub {
  display: block;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.next-training-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* A bordo pista si tocca con il pollice: i due gesti occupano la riga intera
   e si dividono lo spazio, invece di essere due bersagli piccoli a sinistra.
   Da 480px in su tornano della loro larghezza naturale. */
.next-training-actions > * {
  flex: 1 1 12rem;
  text-align: center;
}

@media (min-width: 480px) {
  .next-training-actions > * {
    flex: 0 1 auto;
  }
}

/* --- La card dei parametri --------------------------------------------- */
.params-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
}

.params-card-head,
.priorities-head,
.upcoming-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
  /* Il link va a capo *sotto* il titolo quando la colonna è stretta. Senza
     `wrap`, era il titolo a spezzarsi — «Le priorità / della / squadra» su
     tre righe accanto a un link intero, che è il contrario della gerarchia
     voluta. */
  flex-wrap: wrap;
}

.params-card-head svg,
.priorities-head svg,
.upcoming-head svg {
  color: var(--color-primary);
  flex: none;
}

.params-card-head h2,
.priorities-head h2,
.upcoming-head h2 {
  font: var(--type-title);
  margin: 0;
  flex: 1 1 auto;
}

/* Il link della testata va a fondo riga e cede per primo quando lo spazio
   manca: è la via d'uscita, non il contenuto. */
.priorities-head-link,
.upcoming-head-link {
  margin-left: auto;
  font: var(--type-caption);
  white-space: nowrap;
}

/* Due colonne dove ci stanno: i cinque campi sono corti (una data, una
   tendina, tre numeri) e in colonna singola facevano una pagina alta il
   doppio di quel che serviva. */
.params-grid {
  display: grid;
  gap: var(--space-3);
}

@media (min-width: 560px) {
  .params-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.params-grid label {
  margin: 0;
}

.params-card .btn-block {
  margin-top: var(--space-4);
}

.params-card-hint {
  margin: var(--space-3) 0 0 0;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

/* --- Colonna di destra: il blocco e il suo avanzamento ------------------ */
.home-context-card,
.priorities-card,
.upcoming-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  min-width: 0;
}

.context-label {
  display: block;
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-bottom: 2px;
}

.context-block-name {
  font: var(--type-section);
  margin: 0;
}

.context-sub {
  display: block;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.context-figure {
  display: block;
  font: var(--type-figure-sm);
  color: var(--color-primary-dark);
}

.block-progress {
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-3);
}

/* La barra e il numero affiancati appena c'è spazio, con un filetto in mezzo:
   sono due letture dello stesso blocco — quanto è alle spalle e quanto resta
   — e vicine si confrontano invece di sommarsi. */
@media (min-width: 420px) {
  .block-progress {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--space-4);
  }

  .block-progress-remaining {
    border-left: 1px solid var(--color-border);
    padding-left: var(--space-4);
  }
}

.block-progress-caption {
  margin: 0 0 var(--space-2) 0;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.block-progress-caption strong {
  font: var(--type-body-strong);
  color: var(--color-text);
}

.block-progress-track {
  height: 10px;
  border-radius: var(--radius-pill);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  overflow: hidden;
}

.block-progress-fill {
  height: 100%;
  background: var(--color-primary);
  border-radius: var(--radius-pill);
}

/* Il link di uscita non deve toccare la barra sopra di sé: è la riga che si
   legge per ultima, non la didascalia del grafico. */
.home-context-card .field-hint {
  margin: var(--space-4) 0 0 0;
}

/* --- Le priorità -------------------------------------------------------- */
.priority-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}

/* La riga intera è il bersaglio, non solo il testo: 44px di altezza minima,
   come ogni altro tocco dell'app. */
.priority-row,
.upcoming-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: inherit;
  text-decoration: none;
}

.priority-row:hover,
.upcoming-row:hover,
.priority-row:focus-visible,
.upcoming-row:focus-visible {
  border-color: var(--color-primary);
  background: var(--color-primary-soft);
}

/* Il quadrato porta la tinta della famiglia del tema — la stessa che quel
   tema ha nella timeline di stagione e sulla scheda (`--fam`, impostata
   dalle classi `.fam-*` già esistenti). Il colore identifica, l'etichetta
   accanto dice: nessuna informazione sta nel solo colore.

   Tinta sul **tratto e sul bordo**, non su un fondo pieno con l'icona in
   bianco come nel mockup: due delle sette famiglie sono un ambra e un rosa
   chiaro, e il bianco sopra di esse sta sotto 3:1. Un fondo pieno avrebbe
   funzionato per cinque famiglie su sette, che è il modo peggiore di
   sbagliare — si nota solo sul tema che capita. */
.priority-row-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex: none;
  border-radius: var(--radius-md);
  background: var(--color-bg);
  border: 1px solid var(--fam, var(--color-border));
  color: var(--fam, var(--color-primary));
}

.priority-row-text {
  min-width: 0;
  color: var(--color-text);
}

.priority-row-text strong {
  display: block;
  font: var(--type-body-strong);
}

.priority-row-sub {
  display: block;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.priority-row-go,
.upcoming-go {
  margin-left: auto;
  color: var(--color-text-muted);
  flex: none;
}

/* --- Il riquadro che spiega -------------------------------------------- */
/* Unico contenuto della card quando non ci sono priorità da elencare: senza
   una testata sopra, lo stacco non serve. */
.priorities-card > .rationale-box:first-child {
  margin-top: 0;
}

.rationale-box {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-top: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-bg);
}

.rationale-box > svg {
  color: var(--color-warning-border);
  flex: none;
  margin-top: 1px;
}

.rationale-title {
  margin: 0 0 2px 0;
  font: var(--type-body-strong);
}

.rationale-body {
  margin: 0;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.rationale-body strong {
  color: var(--color-text);
}

/* Il nome del tema dentro la frase porta la tinta della sua famiglia, come
   ovunque nell'app. Resta sottolineato dal link di base: dentro un
   paragrafo il colore da solo non basta a dire che si può cliccare. */
.rationale-theme {
  color: var(--fam, var(--color-primary-dark));
  font: var(--type-body-strong);
}

.rationale-link {
  display: inline-block;
  margin-top: var(--space-2);
  font: var(--type-caption);
}

/* --- I prossimi allenamenti -------------------------------------------- */
.upcoming-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}

.upcoming-row {
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
}

.upcoming-date {
  font: var(--type-body-strong);
}

.upcoming-weekday,
.upcoming-time,
.upcoming-place {
  font: var(--type-caption);
  color: var(--color-text-muted);
}

/* Il luogo cede per primo quando la riga è stretta: data, ora e stato sono
   ciò che distingue una riga dall'altra, il luogo di solito è lo stesso per
   tutte. */
.upcoming-place {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Lo stato porta un pallino colorato E la sua etichetta: il colore da solo
   direbbe "verde/giallo/grigio" a chi lo distingue, e niente a chi non lo
   distingue. */
.upcoming-status {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font: var(--type-caption);
  white-space: nowrap;
}

.upcoming-status::before {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.training-status-ready {
  color: var(--color-ok-text);
}

.training-status-todo {
  color: var(--color-warning-text);
}

.training-status-planned {
  color: var(--color-text-muted);
}

/* Il richiamo al piano sulla scheda generata: stessa misura delle altre
   righe di `.session-meta`, ma staccata da un filo sopra — appartiene alla
   pianificazione, non ai parametri di generazione elencati sopra di essa. */
.session-planned {
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-border);
}

/* ==========================================================================
   Scheda generata — affiancamenti da 900px (v1.12)

   Solo a schermo: la sezione Stampa più sotto riporta tutto a colonna
   singola, che resta un requisito di prodotto e non una conseguenza del
   layout scelto qui.
   ========================================================================== */

.session-head-grid {
  display: block;
}

@media (min-width: 900px) {
  .session-head-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 20rem;
    gap: var(--space-3);
    align-items: start;
  }

  .session-head-grid .session-meta,
  .session-head-grid .guardrail {
    margin-bottom: 0;
  }
}

.phase-exercise-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}

@media (min-width: 900px) {
  .phase-exercise-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .phase-exercise-grid .exercise-card {
    margin-bottom: 0;
    height: 100%;
  }
}

/* ==========================================================================
   Regolamento FSIH (v1.13) — `/rules`, `/rules/signals`.

   L'export offline (`/rules/offline`) NON usa questo foglio: è un documento
   autonomo con lo stile inline, perché deve funzionare sul telefono senza
   questo server. Le due cose si assomigliano di proposito, ma non
   condividono un file — condividerlo significherebbe che il file salvato
   dipende da un indirizzo che in palazzetto non risponde.
   ========================================================================== */

.rules-search-form {
  align-items: flex-end;
  margin-bottom: var(--space-3);
}

.rules-search-field {
  flex: 1 1 22rem;
}

.rule-hits {
  list-style: none;
  margin: 0;
  padding: 0;
}

.rule-hit {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-bottom: var(--space-3);
}

.rule-hit-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

/* Il numero d'articolo è la chiave che si pronuncia a bordo pista: cifre a
   larghezza fissa perché una colonna di numeri resti allineata e leggibile
   di sfuggita. */
.rule-number {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  font-variant-numeric: tabular-nums;
  color: var(--color-primary);
}

.rule-section-title {
  color: var(--color-text-muted);
  font: var(--type-title);
}

.rule-text {
  margin: 0 0 var(--space-2) 0;
}

.rule-items {
  margin: 0 0 var(--space-2) 0;
  padding-left: 1.25em;
}

.rule-items li {
  margin-bottom: var(--space-1);
}

.rule-meta {
  margin: 0;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.rule-fr {
  margin-top: var(--space-2);
}

.rule-fr > summary {
  cursor: pointer;
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
  color: var(--color-primary);
}

.rule-text-fr {
  margin-top: var(--space-2);
  color: var(--color-text-muted);
}

.signal-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}

@media (min-width: 900px) {
  .signal-list {
    grid-template-columns: repeat(2, 1fr);
  }
}

.signal-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
}

.signal-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.signal-name {
  font: var(--type-title);
}

/* Riquadrato e non colorato soltanto: è una lettera stampata su un referto,
   e va riconosciuta come tale — non è un tag né un link. */
.signal-code {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  border: 1px solid var(--color-primary);
  color: var(--color-primary);
  border-radius: var(--radius-sm);
  padding: 0 var(--space-2);
}

.signal-note {
  font: var(--type-caption);
  color: var(--color-text-muted);
}

/* ==========================================================================
   Drill Designer (v1.14) — `/exercises/{id}/diagram`.
   ========================================================================== */

/* (v1.23) Prima: tre colonne — palette 17,5rem, pista, proprietà 17,5rem —
   e su un portatile alla pista restava poco più della metà della finestra,
   con i due pannelli quasi vuoti. Ora la pista è l'unica colonna: gli
   strumenti sono una barra sopra, l'ispettore una barra sotto, e ciò che
   limita la pista è l'altezza della finestra, non due sidebar. */
.designer-layout {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* --- Barra alta: titolo + azioni --- */

.designer-topbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
}

.designer-topbar h1 {
  margin: 0;
  font: var(--type-title);
}

.designer-back {
  font: var(--type-caption);
}

.designer-topbar-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.designer-dirty {
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.designer-history {
  display: flex;
  gap: var(--space-1);
}

/* Annulla/Rifai sono due gesti frequenti ma secondari: due quadrati con la
   freccia, non due bottoni con l'etichetta. Il titolo resta nel `title` e
   in `aria-label`. */
.designer-icon-btn {
  min-height: 38px;
  width: 38px;
  padding: 0;
  font: var(--type-title);
  line-height: 1;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: var(--color-text);
}

.designer-icon-btn:hover:not(:disabled) {
  background: var(--color-bg);
  border-color: var(--color-primary);
}

.designer-icon-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

/* Il form di salvataggio è solo il contenitore del bottone: senza questo
   prenderebbe il vestito da card di `form` (bordo, padding, margine). */
.designer-save-form {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
  margin: 0;
  gap: 0;
}

.designer-save-form button {
  min-height: 38px;
  padding: var(--space-1) var(--space-3);
  font: var(--type-button-sm);
}

/* --- Barra strumenti --- */

.designer-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
}

.tool-group {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

/* Separatore fra gruppi: una riga verticale sottile costa meno spazio di un
   titolo su una riga sua. */
.tool-group + .tool-group {
  border-left: 1px solid var(--color-border);
  padding-left: var(--space-3);
}

.tool-group-label {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
  white-space: nowrap;
}

.tool-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.designer-help {
  margin-left: auto;
  position: relative;
}

.designer-help > summary {
  list-style: none;
  cursor: pointer;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
}

.designer-help > summary::-webkit-details-marker {
  display: none;
}

.designer-help[open] > summary {
  border-color: var(--color-primary);
  color: var(--color-primary-dark);
}

.designer-help-body {
  position: absolute;
  right: 0;
  top: calc(100% + var(--space-1));
  z-index: 5;
  width: min(26rem, 80vw);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: var(--space-3);
  font: var(--type-body);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.designer-help-body p {
  margin: 0;
}

.designer-help-body kbd {
  font: inherit;
  font: var(--type-caption);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 0 0.3em;
  background: var(--color-bg);
}

/* --- Pista --- */

.designer-stage {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.designer-canvas {
  position: relative;
}

.designer-caption {
  font: var(--type-caption);
  color: var(--color-text-muted);
  margin: 0;
}

/* (v1.23) Un ferro della barra strumenti: l'icona sopra — il disegno vero
   che finirà sulla pista, non un'astrazione — e il nome sotto in piccolo,
   perché un triangolo arancione da solo non dice "cono" a chi apre la
   pagina la prima volta.
   Prima erano bottoni col solo nome a 1rem e 44px d'altezza: quattordici
   nomi in fila leggono come un menu, non come una cassetta degli attrezzi.
   Il bersaglio da dito cresce sotto `pointer: coarse` più in basso — a
   bordo pista si usa il tablet, sulla scrivania il mouse. */
.palette-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  /* Larghezza dal nome, con un minimo: "Segnaposto" e "Spostamento" sono
     più larghi dell'icona più larga, e a larghezza fissa uscivano dal
     bordo del ferro. */
  min-width: 3.4rem;
  padding: var(--space-1) 6px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  cursor: grab;
  /* Il gesto di trascinamento è nostro (pointer events in designer.js): se
     il browser interpretasse il primo movimento come uno scorrimento, il
     trascinamento non partirebbe mai. Solo da 1024px, dove la pagina sta
     tutta nello schermo e non c'è niente da scorrere: sul telefono lo
     scorrimento serve, e lì si tocca l'icona e poi la pista. */
  touch-action: manipulation;
}

@media (min-width: 1024px) {
  .palette-btn {
    touch-action: none;
  }
}

/* Le traiettorie sono frecce: un'icona larga in un riquadro quadrato
   sarebbe minuscola. E i loro nomi sono lunghi («Indietro con pallina»):
   larghezza fissa e nome su due righe, altrimenti nove ferri da 130px si
   prendono l'intera barra. */
.palette-btn-wide {
  width: 5.4rem;
}

.palette-btn-wide .tool-label {
  white-space: normal;
}

.tool-icon {
  display: block;
  width: auto;
  height: 24px;
  /* L'icona non intercetta il puntatore: `pointerdown` deve arrivare al
     bottone, che è chi sa quale strumento è. */
  pointer-events: none;
}

.tool-icon-wide {
  height: 14px;
}

.tool-label {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--color-text-muted);
  text-align: center;
  pointer-events: none;
}

.palette-btn:hover:not(:disabled) {
  border-color: var(--color-primary);
  background: var(--color-bg);
}

.palette-btn:hover:not(:disabled) .tool-label {
  color: var(--color-text);
}

/* Il fantasma che segue il dito/puntatore durante il trascinamento: l'icona
   stessa, un po' più grande, sotto il puntatore e fuori dal flusso. */
.tool-ghost {
  position: fixed;
  z-index: 30;
  pointer-events: none;
  transform: translate(-50%, -50%);
  opacity: 0.85;
  filter: drop-shadow(0 2px 4px rgba(16, 24, 40, 0.25));
}

.tool-ghost .tool-icon {
  height: 34px;
}

.tool-ghost .tool-icon-wide {
  height: 20px;
}

/* Lo strumento "armato" resta visibile: senza, dopo aver scelto "Cono" non
   c'è modo di sapere che il prossimo tocco piazzerà qualcosa. */
.palette-btn.is-armed {
  background: var(--color-primary-soft);
  border-color: var(--color-primary);
  box-shadow: inset 0 0 0 1px var(--color-primary);
}

.palette-btn.is-armed .tool-label {
  color: var(--color-primary-dark);
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
}

/* Etichetta galleggiante sopra la pista: non ruba una riga quando non c'è
   niente da dire, e quando c'è la si legge dove guardi (la pista), non
   sopra. `:empty` la fa sparire — `announce('')` svuota il nodo. */
.designer-status {
  position: absolute;
  top: var(--space-2);
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  pointer-events: none;
  font: var(--type-caption);
  color: var(--color-primary-contrast);
  background: var(--color-primary-dark);
  border-radius: var(--radius-pill);
  padding: 0.25rem var(--space-3);
  box-shadow: var(--shadow-sm);
  max-width: calc(100% - var(--space-4));
  text-align: center;
}

.designer-status:empty {
  display: none;
}

.designer-svg {
  width: 100%;
  height: auto;
  display: block;
  border-radius: var(--radius-md);
  /* Senza questo, su un tablet il primo movimento del dito scorre la pagina
     invece di trascinare l'elemento, e il gesto è perso. */
  touch-action: none;
  user-select: none;
}

.designer-svg.is-placing {
  cursor: crosshair;
}

.diagram-element {
  cursor: grab;
}

/* Bersagli di tocco invisibili ma generosi: una pallina ha raggio 0,28 m,
   cioè pochi pixel a schermo, e una traiettoria è una linea sottile. */
.element-hit {
  fill: transparent;
  stroke: none;
}

.trajectory-hit {
  fill: none;
  stroke: transparent;
  stroke-width: 1.2;
  cursor: pointer;
}

.selection-ring {
  fill: none;
  stroke: var(--color-primary);
  stroke-width: 0.16;
  stroke-dasharray: 0.4 0.3;
}

.trajectory.is-selected {
  stroke: var(--color-primary);
}

/* (v1.23) La pallina dentro gli archi del pattinaggio indietro con pallina:
   è un `<circle>`, non un tratto, quindi la selezione la deve tingere di
   riempimento e non di contorno. */
.trajectory-dot.is-selected {
  fill: var(--color-primary);
}

.handle {
  fill: var(--color-surface);
  stroke: var(--color-primary);
  stroke-width: 0.14;
  cursor: grab;
}

/* --- Ispettore della selezione --- */

/* I campi di un elemento sono tre (ruolo, numero, etichetta): in una riga
   sotto la pista stanno tutti, e la colonna da 17,5rem che li conteneva
   torna alla pista. Il contenuto lo costruisce `renderProperties()` in
   designer.js — qui si stilano i nodi che crea. */
.designer-inspector {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-2) var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
  /* Alta come quando è piena: la pista si prende l'altezza che resta, e se
     l'ispettore crescesse alla selezione la pista si rimpicciolirebbe di
     qualche pixel a ogni tocco. */
  min-height: 4rem;
}

.designer-inspector h3 {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0;
  align-self: center;
  white-space: nowrap;
}

.designer-inspector > .field-hint {
  margin: 0;
  align-self: center;
}

.designer-inspector label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  width: 9rem;
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  color: var(--color-text-muted);
  text-transform: uppercase;
}

/* Un numero di maglia sono due cifre, un'etichetta è una frase breve: la
   larghezza segue il contenuto invece di dare 9rem a entrambi. */
.designer-inspector label:has(input[type="number"]) {
  width: 6rem;
}

.designer-inspector label:has(input[type="text"]) {
  width: 12rem;
}

.designer-inspector input,
.designer-inspector select {
  width: 100%;
  min-height: 34px;
  padding: 0 var(--space-2);
  text-transform: none;
  letter-spacing: normal;
  color: var(--color-text);
}

/* "Elimina" all'estremo destro: è l'azione irreversibile della riga, non la
   quarta di quattro nella stessa fila di campi. `btn-block` arriva dal JS. */
.designer-inspector .btn-danger {
  margin-left: auto;
  width: auto;
  min-height: 34px;
  padding: 0 var(--space-3);
  font: var(--type-button-sm);
  background: var(--color-surface);
}

.designer-inspector .btn-danger:hover {
  background: var(--color-danger-bg, var(--color-bg));
  color: var(--color-danger-text);
}

/* A bordo pista si usa il tablet, e un chip da 30px non si prende col dito.
   Con un mouse invece 30px sono comodi e la barra resta sottile: la soglia
   è il tipo di puntatore, non la larghezza della finestra (un tablet in
   orizzontale è largo come un portatile). */
@media (pointer: coarse) {
  .palette-btn {
    min-width: 4rem;
    padding: var(--space-2) 8px;
  }

  .palette-btn-wide {
    width: 6rem;
  }

  .tool-icon {
    height: 28px;
  }

  .tool-label {
    font: var(--type-eyebrow);
    letter-spacing: var(--type-eyebrow-ls);
  }

  .designer-icon-btn {
    min-height: 44px;
    width: 44px;
  }

  .designer-save-form button {
    min-height: 44px;
  }

  .designer-inspector input,
  .designer-inspector select,
  .designer-inspector .btn-danger {
    min-height: 44px;
  }
}

/* La pista prende l'altezza che resta, invece di una larghezza calcolata a
   mano: `main` alta quanto la finestra, e il canvas è l'unica riga elastica
   fra la barra strumenti e l'ispettore. Così la pista è sempre la più grande
   che ci sta — senza numeri magici da riaggiustare quando la barra strumenti
   passa da una riga a due.
   `preserveAspectRatio` sull'SVG tiene le proporzioni FSIH e centra la pista
   nella larghezza in eccesso: la scatola è larga tutto, la pista no.
   Solo da 1024px (dove l'header orizzontale ha già ceduto il posto alla
   sidebar) e solo su finestre abbastanza alte: sotto, la pista torna larga
   quanto lo schermo e la pagina scorre. */
@media (min-width: 1024px) and (min-height: 620px) {
  .designer-page {
    height: 100dvh;
    display: flex;
    flex-direction: column;
    /* Se il contenuto non ci sta comunque (zoom del browser, testo grande)
       si scorre dentro `main`: meglio scorrere che tagliare. */
    overflow: auto;
  }

  .designer-page .designer-layout,
  .designer-page .designer-stage {
    flex: 1 1 auto;
    min-height: 0;
  }

  .designer-page .designer-canvas {
    flex: 1 1 auto;
    min-height: 14rem;
  }

  .designer-page .designer-svg {
    width: 100%;
    height: 100%;
  }
}

/* Sotto i 700px la barra strumenti diventa una colonna di gruppi: i chip
   affiancati a un'etichetta verticale su un telefono si spezzano in una
   riga per chip. */
@media (max-width: 700px) {
  .tool-group {
    flex-direction: column;
    align-items: flex-start;
    flex-basis: 100%;
  }

  .tool-group + .tool-group {
    border-left: 0;
    padding-left: 0;
    border-top: 1px solid var(--color-border);
    padding-top: var(--space-2);
  }

  .designer-help {
    margin-left: 0;
  }

  .designer-inspector label {
    width: 100%;
  }
}

.btn-danger {
  border-color: var(--color-danger-border);
  color: var(--color-danger-text);
}

/* La scheda mostra il diagramma renderizzato lato server: nessun JS, si
   stampa. */
.rink-diagram {
  display: block;
  width: 100%;
  height: auto;
  margin: var(--space-2) 0;
}

/* Il tetto di larghezza vale **sulla scheda**, dove il diagramma sta dentro
   una card di testo e dilatarlo renderebbe illeggibili i numeri dei
   giocatori. (v1.19) Prima era su `.rink-diagram` e quindi colpiva anche il
   canvas del Designer, che condivide la classe: l'editor si ritrovava una
   pista da 34rem in una colonna larga il doppio, con metà dello spazio
   sprecato proprio dove serve precisione per trascinare. */
.exercise-card .rink-diagram {
  max-width: 34rem;
}

/* (v1.24) A sparire sotto i 1024px è **il grafico**, non la card.
   Prima spariva tutta, e con essa il riepilogo, la vista tabella e il
   commutatore: da telefono la stessa stagione in cifre — la sola forma che
   lì si legge — non era raggiungibile per niente. Ora la card resta, l'asse
   annuale no, e al suo posto c'è una riga che dice dove sono i numeri. */
.timeline-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
}
.timeline-chart {
  display: none;
}
.timeline-narrow-note {
  margin: 0;
  font: var(--type-caption);
  color: var(--color-text-muted);
}
@media (min-width: 1024px) {
  .timeline-chart {
    display: block;
  }
  .timeline-narrow-note {
    display: none;
  }
}

.timeline-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.timeline-card-head h2 {
  margin: 0;
}
.timeline-card-sub {
  font: var(--type-caption);
  color: var(--color-text-muted);
  margin: 0;
}
.timeline-head-right {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

/* (v1.24) Legenda: senza, "banda tenue" e "tacca satura" sono due forme che
   il lettore deve indovinare. Le tre voci sono le sole tre cose che la riga
   di un tema può dire, e la tinta della famiglia usata per gli esempi è
   arbitraria — serve solo a mostrare che il colore c'è. */
.timeline-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  font: var(--type-caption);
  color: var(--color-text-muted);
}
.timeline-legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}
.timeline-legend-band {
  width: 26px;
  height: 9px;
  border-radius: 2px;
  background: var(--timeline-band-1);
  background: color-mix(in srgb, var(--fam) 17%, transparent);
  border: 1px solid var(--timeline-line);
  border-color: color-mix(in srgb, var(--fam) 32%, transparent);
}
.timeline-legend-tick {
  width: 5px;
  height: 14px;
  border-radius: 2px;
  background: var(--fam, var(--timeline-neutral-mark));
}
.timeline-legend-tick.is-out {
  background: var(--timeline-neutral-mark);
}

/* La commutazione timeline/tabella: due link, non due bottoni. Lo stato
   attivo è `aria-current="page"`, che è anche l'attributo che lo dice a chi
   non vede lo sfondo cambiare. */
.timeline-toggle {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}
.timeline-toggle a {
  padding: 4px 12px;
  border-radius: 6px;
  font: var(--type-button-sm);
  color: var(--color-text-muted);
  text-decoration: none;
}
.timeline-toggle a[aria-current="page"] {
  background: var(--color-surface);
  color: var(--color-text);
  box-shadow: var(--shadow-sm);
}

/* Il contenuto scorre nel suo contenitore, mai la pagina (requisito §7). Un
   asse annuale sotto una certa larghezza non è compresso, è illeggibile:
   meglio farlo scorrere che spalmare dodici mesi su 600px. */
.timeline-scroller {
  overflow-x: auto;
}
.timeline-grid {
  min-width: 52rem;
}

/* Scala dei mesi: solo etichette, nessuna griglia verticale — meno rumore,
   e le etichette da sole bastano a orientarsi su un asse di 365 giorni. */
/* (v1.19) La scala vive dentro una `.timeline-row` come tutte le altre
   righe, con la cella etichetta vuota: così la sua origine è **la stessa**
   colonna delle tracce, per costruzione. Prima era un blocco a sé con un
   `margin-left: 160px` scritto a mano che replicava la larghezza della
   colonna etichetta — e quando da 1600px quella colonna è passata a 12rem,
   i mesi si sono staccati dalle tracce di 48px, sbagliando di quasi
   un'etichetta intera a dicembre. Un valore duplicato invece di una
   geometria condivisa: è durato fino alla prima prova in Chrome. */
.timeline-scale {
  position: relative;
  height: 1.1rem;
  /* Nessun margine: l'allineamento con le tracce lo dà la griglia della
     riga che la contiene, non un valore ricopiato a mano. */
  margin: 0;
}
.timeline-scale span {
  position: absolute;
  top: 0;
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
  transform: translateX(-1px);
  white-space: nowrap;
}

/* (v1.24) Tre colonne e non due: a destra di ogni riga di tema c'è il
   conteggio delle sedute che l'hanno toccato. La colonna vuota sulle altre
   righe non è uno spreco — è ciò che tiene le tracce allineate per
   costruzione, la stessa lezione della scala dei mesi (v1.19): due geometrie
   scritte a mano si separano al primo cambio di larghezza. */
.timeline-row {
  display: grid;
  grid-template-columns: 9rem 1fr 3.25rem;
  align-items: center;
  gap: var(--space-3);
  min-height: 2rem;
}

/* Il troncamento viene da `.t-clip`, in testa al foglio: qui resta solo ciò
   che è proprio di questa riga. Il nome intero è nel `title`, che il
   template mette da `full_label`. */
.timeline-row-label {
  font: var(--type-caption);
  color: var(--color-text);
}

.timeline-track {
  position: relative;
  height: 10px;
  background: var(--timeline-lane);
  border-radius: var(--radius-pill);
}

/* La fascia dei blocchi è più alta e porta il nome dentro al segmento
   stesso — è la riga che orienta tutte le altre. */
.timeline-periods-row .timeline-track {
  height: 2rem;
  background: var(--color-bg);
  border-radius: var(--radius-sm);
}
/* Spazio sotto per l'etichetta "oggi", che sta **sotto** la fascia e non
   sopra: sopra coprirebbe l'etichetta del mese (criterio §8.11). */
.timeline-periods-row {
  margin-bottom: 1.1rem;
}

.timeline-segment {
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: var(--radius-pill);
  overflow: hidden;
}
/* (v1.24) Bande neutre in due grigi alternati. Il colore appartiene ai temi:
   ridirlo qui lo spenderebbe due volte sulla stessa informazione — il blocco
   è già dato dalla posizione e dal nome scritto dentro — e toglierebbe
   identità al tema, che diventerebbe verde a gennaio e blu a luglio.
   L'alternanza risolve per costruzione un difetto della vista precedente:
   due blocchi adiacenti con la stessa tinta si leggevano come uno solo. */
.timeline-periods-row .timeline-segment {
  border-radius: 3px;
  display: flex;
  align-items: center;
  padding: 0 var(--space-2);
  background: var(--timeline-band-1);
  border: 1px solid var(--timeline-line);
  color: var(--color-text-muted);
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  text-decoration: none;
}
.timeline-periods-row .timeline-segment.alt {
  background: var(--timeline-band-2);
}
a.timeline-segment:hover,
a.timeline-segment:focus-visible {
  filter: brightness(1.08);
  text-decoration: none;
  outline: 2px solid var(--color-text);
  outline-offset: -2px;
}
/* Un periodo senza blocco resta segnalato e non neutro: è un buco di
   pianificazione, non un blocco come gli altri. */
.timeline-segment.gap {
  background: repeating-linear-gradient(45deg, #f2c6c6, #f2c6c6 4px, #fbe4e4 4px, #fbe4e4 8px);
}
.timeline-periods-row .timeline-segment.gap {
  color: var(--color-danger-text);
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  justify-content: center;
  border-color: transparent;
}

/* --- (v1.24) Righe dei temi: banda, corsia, tacche ---------------------- */

.timeline-family {
  margin-top: var(--space-2);
}
.timeline-family-head {
  min-height: 1.5rem;
}
.timeline-family-head .timeline-row-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
}
/* L'unico punto in cui la tinta della famiglia compare da sola, senza
   un'etichetta accanto che la ripeta: è un pallino di orientamento, non un
   identificatore — il nome della famiglia gli sta a fianco. */
.timeline-family-dot {
  width: 8px;
  height: 8px;
  border-radius: 2px;
  background: var(--fam, var(--color-text-muted));
  flex: none;
}
.timeline-family-rule {
  height: 1px;
  background: var(--color-border);
  border-radius: 0;
}

.timeline-theme-row .timeline-track {
  height: 1.375rem;
  background: none;
  border-radius: 0;
}

/* La corsia: un filo grigio per tutto l'anno, l'asse su cui banda e tacche
   si appoggiano. Senza, una riga vuota non si distingue da una riga che non
   c'è. */
.timeline-lane {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 3px;
  margin-top: -1.5px;
  border-radius: 2px;
  background: var(--timeline-lane);
}

/* La banda: "il tema era pianificato qui". Tenue, perché è un'intenzione e
   non un fatto — i fatti sono le tacche sopra. La prima dichiarazione di
   `background` è il ripiego per i browser senza `color-mix`: un grigio
   invece di niente. */
.timeline-band {
  position: absolute;
  top: 2px;
  bottom: 2px;
  border-radius: 3px;
  display: block;
  background: var(--timeline-band-1);
  background: color-mix(in srgb, var(--fam) 17%, transparent);
  border: 1px solid var(--timeline-line);
  border-color: color-mix(in srgb, var(--fam) 32%, transparent);
}
.timeline-band:hover,
.timeline-band:focus-visible {
  background: color-mix(in srgb, var(--fam) 28%, transparent);
  outline: 2px solid var(--fam);
  outline-offset: -1px;
}

/* La tacca: una seduta svolta che ha lavorato quel tema. Satura, perché è
   l'unico fatto della riga. L'alone del colore della card la stacca dalla
   banda e dalle tacche vicine quando la stagione si infittisce. */
.timeline-tick {
  position: absolute;
  top: 4px;
  width: 4px;
  height: 14px;
  border-radius: 2px;
  background: var(--fam, var(--color-primary));
  box-shadow: 0 0 0 1.5px var(--color-surface);
  z-index: 3;
}
/* Fuori dal piano: neutra. Non è un errore — spesso è il segnale che il
   piano andava scritto diversamente — quindi neutra e non d'allarme. */
.timeline-tick.is-out {
  background: var(--timeline-neutral-mark);
}
/* Tema toccato solo come secondario: mezza altezza. Il tema è stato
   sfiorato dentro esercizi che ne allenavano un altro, e una tacca piena lo
   direbbe più forte di com'è. */
.timeline-tick.is-secondary {
  top: 7px;
  height: 8px;
}
.timeline-tick:hover,
.timeline-tick:focus-visible {
  outline: 2px solid var(--timeline-today);
  outline-offset: 2px;
}

/* Il confine di un blocco, ripetuto su ogni riga: costa un pixel e dice
   quello che il colore delle bande diceva male. Sopra la banda e sotto le
   tacche — separa il piano senza coprire i fatti. */
.timeline-vline {
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 1px;
  background: var(--timeline-line);
  z-index: 2;
  pointer-events: none;
}

/* Il conteggio a destra della riga. In avviso quando il tema era
   pianificato e la stagione l'ha allenato due volte o meno: è il numero che
   porta a cambiare la seduta di giovedì. */
.timeline-count {
  font: var(--type-figure-cell);
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
  text-align: right;
  white-space: nowrap;
}
.timeline-count.is-rare {
  color: var(--color-danger-text);
  font: var(--type-figure-cell);
  font-variant-numeric: tabular-nums;
}

/* --- (v1.24) Vista tabella: gli stessi numeri, in cifre ----------------- */
.timeline-table {
  width: 100%;
  border-collapse: collapse;
  font: var(--type-body);
}
.timeline-table th,
.timeline-table td {
  text-align: left;
  padding: var(--space-2);
  border-bottom: 1px solid var(--color-border);
}
.timeline-table thead th {
  font: var(--type-thead);
  letter-spacing: var(--type-thead-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.timeline-table .num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.timeline-table .num.is-rare {
  color: var(--color-danger-text);
  font: var(--type-figure-cell);
  font-variant-numeric: tabular-nums;
}
.timeline-table-swatch {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  margin-right: var(--space-2);
  background: var(--fam, var(--color-text-muted));
}

.timeline-note {
  margin: var(--space-3) 0 0;
  font: var(--type-caption);
  color: var(--color-text-muted);
  max-width: 80ch;
}

/* Indicatore "oggi": una linea sottile ripetuta su ogni riga della
   timeline, non un'unica linea continua sovrapposta a tutte — vedi README,
   "dove il risultato è più povero del riferimento".

   (v1.24) In inchiostro e non in rosso: il rosso è riservato agli stati di
   allarme, e oggi non è un allarme. Nella versione precedente attirava
   l'occhio più dei dati che deve solo situare. Un filo di trasparenza per
   restare dietro alle tacche. */
.timeline-today-tick {
  position: absolute;
  top: -4px;
  bottom: -4px;
  width: 1px;
  background: var(--timeline-today);
  opacity: 0.5;
  z-index: 2;
}
a.timeline-today-tick {
  display: block;
}
/* Sotto la fascia dei blocchi, non sopra: sopra c'è la scala dei mesi, e
   l'etichetta la coprirebbe (criterio §8.11). */
.timeline-today-label {
  position: absolute;
  top: calc(100% + 3px);
  transform: translateX(-50%);
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
  background: var(--color-surface);
  padding: 0 4px;
  white-space: nowrap;
  text-decoration: none;
}

.timeline-group {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.timeline-group-title {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-1) 0;
}

/* Marker puntiformi (partite/tornei): un pallino, non una barra — sono
   eventi di un giorno, non intervalli. */
.timeline-marker {
  position: absolute;
  top: 50%;
  width: 9px;
  height: 9px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  border: 2px solid var(--color-surface);
}
.timeline-marker.marker-match { background: var(--color-badge-text); }
.timeline-marker.marker-tournament { background: var(--color-warning-border); }

/* (v1.16, correzione rispetto al mockup) Il mockup disegnava camp e pause
   come BARRE, assumendo che fossero intervalli. Nel dominio non lo sono:
   `CalendarEntry` porta una `date` singola, quindi un camp di una settimana
   sono sette voci distinte (o una serie ricorrente), non un intervallo.
   Disegnarli come barre avrebbe richiesto di fondere voci consecutive —
   un'inferenza che il dato non autorizza: due camp in giorni contigui non
   sono lo stesso camp. Restano pallini, come le partite. */
.timeline-marker.marker-camp { background: var(--color-camp-text); }
.timeline-marker.marker-break { background: var(--color-text-muted); }


/* --- Allenamenti: pianificato contro svolto (v1.16) --------------------
   Due righe invece di una perché la domanda è un confronto: si legge nei
   BUCHI fra la riga di sopra e quella di sotto. Un pallino pieno nella riga
   "svolti" ha sempre un gemello in quella "pianificati"; il contrario no, ed
   è esattamente l'informazione che si cerca.

   Il terzo stato è quello che conta davvero: un allenamento già passato di
   cui non risulta nessuna scheda salvata. Non è "non ancora fatto" — è un
   buco. Colore d'allarme e pallino vuoto, così si distingue a colpo d'occhio
   da un allenamento futuro, che è vuoto ma neutro. */
.timeline-marker {
  background: var(--color-border);
}

.timeline-marker.is-done {
  background: var(--color-primary);
}

.timeline-marker.is-missed {
  background: var(--color-surface);
  border-color: var(--color-danger-border);
  box-shadow: 0 0 0 1px var(--color-danger-border);
}

/* (v1.21) Il terzo stato nato con la generazione guidata dal piano: un
   allenamento ancora da svolgere che ha già la sua scheda. Non è pieno come
   uno svolto — non è successo niente, ancora — e non è vuoto come uno
   scoperto: contorno d'accento su fondo chiaro, cioè "pronto". */
.timeline-marker.is-prepared {
  background: var(--color-primary-soft);
  border-color: var(--color-primary);
  box-shadow: 0 0 0 1px var(--color-primary);
}

/* ==========================================================================
   Vista stagione — impaginazione da laptop (v1.17)

   La pagina non ha più il tetto di 40rem (`app-main-wide` su `<main>`): era
   la colonna di lettura pensata per la prosa, e teneva la timeline — che è
   un asse annuale — schiacciata in meno di metà dello schermo di un laptop.
   ========================================================================== */

/* Senza tetto di pagina, tre stat card in `repeat(3, 1fr)` diventano tre
   riquadri da 600px con dentro una parola: "usare tutto lo spazio" non vuol
   dire stirare ogni componente fino ai bordi. Le card restano di una misura
   sensata e la riga parte da sinistra, allineata alla timeline sotto.
   Scoped a `.app-main-wide` per non toccare il catalogo, che ha un tetto
   suo (`app-main-roomy`, 76rem) dove `repeat(3, 1fr)` va già bene. */
@media (min-width: 1024px) {
  .app-main-wide .stat-card-grid {
    grid-template-columns: repeat(3, minmax(14rem, 26rem));
    justify-content: start;
  }
}

/* (v1.24) La vista stagione ha acquisito una quarta card — i temi
   pianificati e quasi mai allenati — e `repeat(3, …)` la manderebbe a capo
   da sola sotto le altre tre. `auto-fit` le dispone come stanno: quattro in
   fila su uno schermo largo, due e due su un laptop. */
.stat-card-grid-four {
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}
/* Il tetto per card scende da 26rem a 18rem: con `auto-fit` il numero di
   colonne si calcola sulla misura **massima**, e a 26rem l'ultima card
   finiva da sola su una seconda riga su un laptop — una card orfana sotto
   tre allineate si legge come un errore di impaginazione. */
@media (min-width: 1024px) {
  .app-main-wide .stat-card-grid-four {
    grid-template-columns: repeat(auto-fit, minmax(12rem, 18rem));
  }
}

.season-layout {
  display: block;
}

@media (min-width: 1024px) {
  .season-layout {
    display: grid;
    /* I mesi prendono lo spazio che avanza, la legenda una colonna fissa:
       è un elenco di nomi brevi, allargarla non la rende più leggibile. */
    grid-template-columns: minmax(0, 1fr) 22rem;
    gap: var(--space-4);
    align-items: start;
  }

  /* Resta sott'occhio mentre si scorrono dodici mesi: senza, per ricordare
     di che colore fosse un blocco bisogna risalire in cima alla pagina. */
  .season-legend-col {
    position: sticky;
    top: var(--space-3);
  }

  /* I due titoli di colonna partono dalla stessa linea. */
  .season-months-col > h2,
  .season-legend-col > h2 {
    margin-top: 0;
  }
}

/* Su schermi molto larghi i mesi vanno a due colonne: dodici righe in una
   colonna sola lasciano metà pagina vuota, ed è proprio la lamentela da cui
   nasce questa revisione. Restano `<details>`: aprirne uno allunga la sua
   colonna, non entrambe — accettabile, e comunque meglio di dodici righe
   larghe 900px con dentro tre badge. */
@media (min-width: 1600px) {
  .month-list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2);
    align-items: start;
  }
}

/* La timeline occupa tutta la larghezza disponibile e ne guadagna: più
   pixel sull'asse sono più risoluzione sull'anno. Da 1600px l'etichetta di
   riga può permettersi di essere più larga senza rubare spazio all'asse. */
@media (min-width: 1600px) {
  .timeline-row {
    grid-template-columns: 12rem 1fr 3.5rem;
  }
}

/* ==========================================================================
   Elenco + form affiancati (v1.20) — calendario, dettaglio stagione.

   Due pagine con la stessa forma: "ecco cosa c'è" seguito da "ecco come
   aggiungerne". In una colonna sola su un laptop il form finiva sotto la
   piega, e per aggiungere una voce dopo aver guardato l'elenco bisognava
   scorrere fino in fondo. Affiancati, il form resta sott'occhio mentre si
   legge l'elenco — che è l'ordine in cui si lavora davvero.
   ========================================================================== */

.list-and-form {
  display: block;
}

@media (min-width: 1024px) {
  .list-and-form {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 24rem;
    gap: var(--space-4);
    align-items: start;
  }

  .list-and-form-aside {
    position: sticky;
    top: var(--space-3);
  }

  .list-and-form-main > h2:first-child,
  .list-and-form-aside > h2:first-child {
    margin-top: 0;
  }
}

/* ==========================================================================
   Shell applicativa (v1.20) — sidebar permanente da 1024px.

   Portata dal mockup `desktop-revamp.html`, con tre differenze rispetto a
   quel documento, tutte perché lì era una dimostrazione e qui è l'app:
   niente offset per il banner/tab del mockup (`top: 0`), niente voce "Drill
   Designer" (non è una destinazione), e la topbar si nasconde quando è
   vuota invece di restare una fascia decorativa.

   Sotto 1024px non cambia niente: resta l'header orizzontale di sempre,
   senza cassetti da aprire e senza JavaScript. È la scelta già motivata nel
   mockup — l'header attuale sul telefono funziona, non c'era un problema da
   risolvere lì.
   ========================================================================== */

.app-shell {
  display: block;
}

.app-shell-main {
  min-width: 0;
}

.icon-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* ==========================================================================
   Sidebar scura (v1.23) — riferimento fornito dall'allenatore, validato.

   Telaio scuro attorno a un contenuto chiaro: la navigazione arretra e la
   pagina avanza. Regge qui per una ragione di contesto, non solo di gusto:
   **la sidebar esiste solo da 1024px in su**. Sotto resta l'header chiaro
   orizzontale, che è la vista di bordo pista — dove uno schermo scuro al
   sole è un problema vero (VISION §8). Il registro scuro si applica quindi
   al solo laptop, dove si pianifica con calma.

   Token scoped su `.app-sidebar` e non in `:root`: sono i colori di un
   componente, e definirli globalmente inviterebbe il resto dell'app a
   pescarci dentro finché il "tema scuro" non sarebbe più circoscritto.
   ========================================================================== */
.app-sidebar {
  display: none;

  /* I tre toni del fondo sono campionati dal riferimento dell'allenatore,
     non stimati a occhio: in cima #303437, assestato #1e2529 all'altezza in
     cui finisce il logo, #1b2125 in fondo. */
  --sidebar-bg-top: #303437;
  --sidebar-bg: #1e2529;
  --sidebar-bg-bottom: #1b2125;
  --sidebar-border: rgba(255, 255, 255, 0.08);
  /* Contrasti verificati sui **tre** toni, non solo sul più scuro: il fondo
     ora è un gradiente, e un colore che passa in basso può non passare in
     cima. Testo 7.7-10.0:1, spento 5.2-6.7:1, accento 5.5-7.2:1. */
  --sidebar-text: #c7cbd2;
  --sidebar-muted: #a1a7b1;
  /* Non `--color-primary-dark` (#0a5f67): qui darebbe 1.6:1. Stessa
     tonalità dell'accento petrolio, luminosità alzata per il fondo scuro —
     resta lo stesso colore, non un ciano nuovo. */
  --sidebar-accent: #3cbccd;
  --sidebar-active-bg: rgba(60, 188, 205, 0.14);
  --sidebar-hover-bg: rgba(255, 255, 255, 0.06);
}

.app-topbar:empty {
  display: none;
}

@media (min-width: 1024px) {
  /* L'header orizzontale cede il posto alla sidebar: due navigazioni
     contemporanee sarebbero due modi di fare la stessa cosa. */
  .app-header {
    display: none;
  }

  .app-shell {
    display: grid;
    grid-template-columns: var(--app-sidebar-width) minmax(0, 1fr);
    align-items: start;
    min-height: 100vh;
  }

  .app-sidebar {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    /* Il fondo schiarisce in cima, dove sta il logo, e si assesta appena
       sotto di lui. La misura è in **pixel e non in percentuale**: deve
       finire dove finisce il logo, che è alto quanto è alto — con una
       percentuale, su un monitor alto la zona chiara si allungherebbe per
       mezza sidebar, sotto il logo, dove non ha più niente da fare. */
    background: linear-gradient(
      to bottom,
      var(--sidebar-bg-top) 0,
      var(--sidebar-bg) 180px,
      var(--sidebar-bg-bottom) 100%
    );
    /* Nessun bordo destro: fra questo fondo e una pagina chiara il salto di
       luminosità è già una separazione, e una riga in più sarebbe rumore. */
    border-right: none;
    padding: var(--space-4) var(--space-3);
    position: sticky;
    top: 0;
    height: 100vh;
    overflow-y: auto;
  }

  .app-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
    padding: var(--space-2) var(--space-4);
    position: sticky;
    top: 0;
    z-index: 8;
  }
}

/* (v1.23) Il marchio SKATE@LAB. Il quadrato in tinta d'accento che stava
   qui era un segnaposto in attesa di un logo: ora c'è. */
/* Il badge del logo è quasi nero (#070707 campionato dall'artwork) e la
   sidebar in cima è #303437: la differenza è il bordo, e non serve
   disegnarne uno. È il motivo per cui il gradiente ha sostituito il vassoio
   che stava qui — separava, ma introduceva una scheda dentro una barra che
   di schede non ne ha nessun'altra. Verificato sul riferimento: nemmeno lì
   il logo porta un contorno. */
.sidebar-brand {
  display: flex;
  justify-content: center;
  margin-bottom: var(--space-2);
  text-decoration: none;
}

.sidebar-brand:hover {
  text-decoration: none;
}

.sidebar-logo {
  display: block;
  width: 100%;
  max-width: 150px;
  height: auto;
  /* L'artwork è un badge arrotondato su fondo nero, ritagliato al vivo: il
     raggio lo rimette in CSS invece di lasciare che i quattro angoli grigi
     residui si vedano come spigoli. In percentuale perché segue la misura,
     che cambia col contenitore. */
  border-radius: 18%;
}

/* Il badge nell'header orizzontale sotto i 1024px, dove il logo intero non
   entrerebbe: qui la tagline non si leggerebbe comunque, e il nome ce l'ha
   il testo di fianco. */
.brand-badge {
  width: 32px;
  height: 32px;
  border-radius: 22%;
  flex: none;
}

.sidebar-nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
}

.sidebar-group-label {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--sidebar-muted);
  padding: var(--space-3) var(--space-2) var(--space-1) var(--space-2);
}

.sidebar-group-label:first-child {
  padding-top: var(--space-1);
}

.sidebar-link {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2);
  border-radius: var(--radius-nav);
  color: var(--sidebar-text);
  text-decoration: none;
  font: var(--type-body-strong);
  /* 40px: la sidebar si usa anche da tablet in orizzontale. */
  min-height: 40px;
}

/* Il grigio chiarissimo di `--color-bg` era corretto su una sidebar bianca;
   su questo fondo sarebbe un lampo. Un velo di bianco al 6% fa lo stesso
   mestiere senza accendere la riga. */
.sidebar-link:hover {
  background: var(--sidebar-hover-bg);
  text-decoration: none;
}

.sidebar-link svg {
  flex: none;
  color: var(--sidebar-muted);
}

/* Voce attiva. Le coppie sono enumerate perché il CSS non sa confrontare la
   classe di un antenato con quella di un discendente: la pagina dichiara
   `nav-<voce>` sul `<body>` (vedi base.html) e qui si accende la voce
   corrispondente. Dieci righe, esplicite, senza una riga di Rust. */
.nav-oggi .sidebar-link.nav-link-oggi,
.nav-stagione .sidebar-link.nav-link-stagione,
.nav-roster .sidebar-link.nav-link-roster,
.nav-valutazioni .sidebar-link.nav-link-valutazioni,
.nav-persone .sidebar-link.nav-link-persone,
.nav-catalogo .sidebar-link.nav-link-catalogo,
.nav-regolamento .sidebar-link.nav-link-regolamento,
.nav-squadre .sidebar-link.nav-link-squadre,
.nav-categorie .sidebar-link.nav-link-categorie,
.nav-profilo .sidebar-link.nav-link-profilo {
  background: var(--sidebar-active-bg);
  color: var(--sidebar-accent);
  /* La barra d'accento a sinistra come `box-shadow: inset` e non come
     `border-left`: un bordo occupa spazio nel box e sposterebbe il testo di
     3px nel momento in cui la voce si attiva — uno scatto visibile su ogni
     cambio di pagina. L'ombra interna non tocca il layout. */
  box-shadow: inset 3px 0 0 var(--sidebar-accent);
}

.nav-oggi .sidebar-link.nav-link-oggi svg,
.nav-stagione .sidebar-link.nav-link-stagione svg,
.nav-roster .sidebar-link.nav-link-roster svg,
.nav-valutazioni .sidebar-link.nav-link-valutazioni svg,
.nav-persone .sidebar-link.nav-link-persone svg,
.nav-catalogo .sidebar-link.nav-link-catalogo svg,
.nav-regolamento .sidebar-link.nav-link-regolamento svg,
.nav-squadre .sidebar-link.nav-link-squadre svg,
.nav-categorie .sidebar-link.nav-link-categorie svg,
.nav-profilo .sidebar-link.nav-link-profilo svg {
  color: var(--sidebar-accent);
}

/* La firma di marchio, non testo d'interfaccia: due righe che vanno lette
   come un blocco unico. L'interlinea larga è deliberata e sta fuori dal
   ruolo — è l'unica eccezione dichiarata alla scala, e vale per il lockup,
   non per una schermata. */
.sidebar-footer {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  line-height: 1.7;
  color: var(--sidebar-muted);
  padding: var(--space-3) var(--space-2);
  border-top: 1px solid var(--sidebar-border);
  text-transform: uppercase;
}

/* Il filetto di marchio sotto la firma. Rosso dell'artwork, non
   `--color-danger-*`: quello significa "errore" in ogni form dell'app.
   Regola che questo colore si porta dietro — **non deve mai più comparire
   altrove nella sidebar**: il giorno che qui servisse un contatore o una
   pastiglia di avviso, quello usa il rosso di stato, altrimenti i due
   significati si sovrappongono. */
.sidebar-footer-rule {
  width: 28px;
  height: 2px;
  margin-top: var(--space-2);
  border-radius: 1px;
  background: #f03c14;
}

/* La riga funzionale sotto la firma: minuscola, non maiuscoletto — non è
   marchio, è un'informazione, e VISION la vuole visibile per rassicurare
   chi non è tecnico che non esiste nessun account da creare. */
.sidebar-footer-note {
  margin-top: var(--space-2);
  text-transform: none;
  letter-spacing: 0;
  font: var(--type-caption);
}

/* Il focus dell'app è `--color-primary-dark`, che su questo fondo dà 2.5:1
   — sotto la soglia 3:1 richiesta a un indicatore non testuale. Qui vale
   l'accento chiaro. */
.app-sidebar a:focus-visible,
.app-sidebar summary:focus-visible {
  outline: 2px solid var(--sidebar-accent);
  outline-offset: 2px;
}

/* Selettore squadra: `<details>` puro CSS, stesso pattern di "Serie ▾" nel
   calendario. Nessun JavaScript. */
.team-switcher {
  position: relative;
}

.team-switcher > summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-nav);
  font: var(--type-body-strong);
  min-height: 40px;
}

.team-switcher > summary::-webkit-details-marker {
  display: none;
}

.team-switcher > summary:hover {
  background: var(--color-bg);
}

.team-switcher[open] > summary {
  background: var(--color-primary-soft);
  color: var(--color-primary-dark);
}

.team-switcher-panel {
  position: absolute;
  top: calc(100% + var(--space-1));
  left: 0;
  z-index: 20;
  min-width: 18rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: var(--space-2);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.switcher-label {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
  padding: var(--space-2) var(--space-2) var(--space-1) var(--space-2);
}

.switcher-current {
  padding: var(--space-2);
  font: var(--type-body-strong);
  color: var(--color-primary-dark);
}

/* Un bottone che deve leggersi come una voce di menu: il cambio di squadra
   è un `POST` (§7.1, `activate_team`), non un link, ma per chi guarda è una
   scelta in un elenco. */
/* Ogni voce è un `<form>` perché il cambio squadra è un `POST`, ma dentro
   il menu non deve sembrare una scheda: lo stile generico dei form (cornice,
   sfondo, spaziatura) qui va spento. */
.team-switcher-panel form {
  border: none;
  background: none;
  padding: 0;
  margin: 0;
  box-shadow: none;
}

.switcher-option {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--space-2);
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-text);
  font: inherit;
  min-height: 40px;
  cursor: pointer;
}

.switcher-option:hover {
  background: var(--color-bg);
}

.switcher-all {
  padding: var(--space-2);
  font: var(--type-caption);
  border-top: 1px solid var(--color-border);
  margin-top: var(--space-1);
}

.topbar-context {
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.topbar-context strong {
  color: var(--color-text);
}

/* ==========================================================================
   Stampa — pensato per la scheda allenamento (session.html), ma le regole
   .no-print/navigazione si applicano a qualunque pagina venga stampata per
   errore. Niente bottoni/link interattivi in stampa (VISION §9).
   ========================================================================== */

/* (v1.22) Il rovescio di `.no-print`: markup che esiste solo sul foglio.
   Serve alla scheda G+S di `session.html`, che a schermo sarebbe una
   seconda copia dello stesso contenuto. */
.print-only {
  display: none;
}

@media print {
  @page {
    size: A4;
    margin: 1.5cm;
  }

  /* (v1.22) I margini asimmetrici del modello G+S originale (alto 1.1,
     destro 1.0, basso 0.8, sinistro 1.5 cm), applicati **solo** alla
     scheda tramite pagina con nome: `@page` senza nome è globale, e
     stringere i margini di ogni pagina stampata dall'app per far tornare
     un foglio solo sarebbe un effetto collaterale sproporzionato. Dove
     `page:` non è supportato la scheda esce con i margini di default qui
     sopra — cambia di qualche millimetro, non si rompe. */
  @page gs-sheet {
    size: A4 portrait;
    margin: 1.1cm 1cm 0.8cm 1.5cm;
  }

  .print-only {
    display: block;
  }

  body {
    background: #fff;
    color: #000;
  }

  .app-header,
  .app-sidebar,
  .app-topbar,
  .no-print,
  form {
    display: none !important;
  }

  /* La griglia della shell non deve lasciare la colonna vuota della sidebar
     sul foglio. */
  .app-shell {
    display: block !important;
  }

  .app-main {
    max-width: none;
    padding: 0;
    margin: 0;
  }

  /* La scheda si stampa sempre a colonna singola a piena larghezza
     (requisito di prodotto): le griglie a schermo non devono sopravvivere
     alla stampa, altrimenti due esercizi affiancati su A4 diventano due
     colonne strette e spezzate. */
  .timeline-card {
    display: none;
  }

  /* (v1.24) La **vista tabella** però si stampa: è metà del suo motivo di
     esistere (requisito §7, "serve a chi stampa"), ed è l'unica forma di
     questa vista che su carta dice ancora qualcosa — un asse di 365 giorni
     con tacche da quattro pixel, no. Fuori dal foglio solo ciò che sulla
     carta non ha funzione: il commutatore, che è un link, e la legenda, che
     spiega forme che la tabella non usa. */
  .timeline-card-table {
    display: block;
    border: 0;
    padding: 0;
  }
  .timeline-toggle,
  .timeline-legend,
  .timeline-narrow-note {
    display: none;
  }

  /* (v1.22) `.session-head-grid` non è più qui: su `session.html` è
     `no-print`, e questa regola — più in basso nel file, a parità di
     `!important` — la rimetteva a video sul foglio. */
  .catalog-layout,
  .list-and-form,
  .season-layout,
  .month-list,
  .clubs-grid,
  .stat-card-grid,
  .signal-list,
  .home-hero,
  .home-head,
  .params-grid,
  .block-progress {
    display: block !important;
  }

  /* Il diagramma si stampa: è il motivo per cui il renderer è server-side
     e produce un SVG inerte. Il tetto di larghezza a schermo qui non serve. */
  .rink-diagram {
    max-width: 100%;
    break-inside: avoid;
    page-break-inside: avoid;
  }

  /* (v1.22) `.exercise-card` non è più in questo elenco: in stampa le card
     non escono affatto — le sostituisce la tabella G+S, e `session.html`
     le marca `no-print` alla sorgente. */
  .session-meta,
  .guardrail {
    border: 1px solid #999;
    break-inside: avoid;
    page-break-inside: avoid;
  }

  a[href]::after {
    content: none;
  }
}

/* ==========================================================================
   Scheda G+S «Preparazione lezione inline» (v1.22) — stampa e anteprima

   Riproduce l'impaginazione del modello ufficiale UFSPO (docs/gs-thb/,
   foglio omonimo): testata, tre colonne nelle proporzioni dell'originale
   (9% / 48% / 43% della larghezza utile), piede con Formazione,
   Osservazioni e Giudizio all.

   Quello che NON riproduce, per scelta: la rigatura fissa a 12pt su 40
   righe. Quel foglio è pensato per essere riempito a penna; la nostra
   scheda arriva già scritta, e un esercizio con svolgimento, punti chiave
   ed errori comuni non entra in una riga da 12pt. Righe alte quanto il
   contenuto — vedi mockup/README-scheda-gs-print.md per il confronto con
   la variante a griglia rigida, disegnata e scartata.

   Tutto in `pt`/`cm`: sono le unità della pagina fisica. Le stesse unità
   valgono a schermo (1cm CSS = 96dpi/2.54), che è la ragione per cui
   l'anteprima di `?anteprima=1` può essere fedele senza una seconda resa:
   il foglio è **questo**, disegnato una volta sola.

   (v1.23) Il blocco viveva dentro `@media print`, e il foglio era quindi
   invisibile fino al dialogo di stampa. Portato fuori: tutti i selettori
   qui sotto sono `.gs-*` e non toccano nient'altro, quindi a schermo non
   cambia niente finché `.print-only` resta `display: none`. È
   l'interruttore di **visibilità** a decidere quando il foglio si vede —
   in stampa sempre, a schermo solo in anteprima — non più il fatto che
   queste regole esistano.
   ========================================================================== */

.gs-sheet {
  page: gs-sheet;
  font: var(--typep-body);
  color: #000;
}

/* --- Testata --- */

.gs-masthead {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  font: var(--typep-caption);
  color: #555;
  margin-bottom: 6pt;
}

.gs-masthead-right {
  text-align: right;
}

.gs-title {
  font: var(--typep-screen);
  text-align: center;
  margin: 4pt 0 8pt 0;
  border-bottom: 1.5pt solid #000;
  padding-bottom: 6pt;
}

/* I quattro campi d'identità dell'originale su una riga sola. Il filo
   punteggiato sotto ogni valore serve anche quando il valore c'è: dice
   che quello è un campo del modulo, e lascia scrivere sopra a chi
   corregge a bordo pista. */
.gs-identity {
  display: flex;
  flex-wrap: wrap;
  gap: 4pt 18pt;
  font: var(--typep-caption);
  margin: 0 0 10pt 0;
}

.gs-identity dt {
  display: inline;
  font-weight: 700;
}

.gs-identity dd {
  display: inline;
  margin: 0 0 0 3pt;
  min-width: 3cm;
  border-bottom: 0.75pt dotted #555;
  padding-bottom: 1pt;
}

/* Non è un campo dell'originale, che dell'obiettivo non fa un blocco a
   sé. Sta qui perché VISION §9 vuole che l'obiettivo della seduta si
   legga per primo, e nella tabella finirebbe disperso fra gli esercizi. */
.gs-objective {
  font: var(--typep-body-strong);
  border-left: 3pt solid #000;
  padding: 5pt 8pt;
  margin: 0 0 10pt 0;
}

/* --- Corpo: la tabella a tre colonne --- */

.gs-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}

col.gs-col-tempo {
  width: 9%;
}

col.gs-col-obiettivi {
  width: 48%;
}

col.gs-col-disegno {
  width: 43%;
}

/* Il motore di impaginazione ripete `thead` a ogni pagina da solo: è il
   motivo per cui questa è una `<table>` vera e non una griglia CSS. */
.gs-table thead {
  display: table-header-group;
}

/* Striscia d'identità in cima alle pagine di continuazione. Non c'è
   nell'originale — che sta su una pagina sola — ed è la sola aggiunta
   che una scheda già scritta rende necessaria: un foglio staccato deve
   dire di chi è. */
.gs-identity-repeat td {
  padding: 3pt 4pt;
  font: var(--typep-eyebrow);
  letter-spacing: var(--typep-eyebrow-ls);
  color: #555;
  border-bottom: 0.75pt solid #999;
}

.gs-col-headers th {
  font: var(--typep-eyebrow);
  letter-spacing: var(--typep-eyebrow-ls);
  text-transform: uppercase;
  text-align: left;
  padding: 5pt 6pt;
  border: 0.75pt solid #000;
  border-top-width: 1.5pt;
}

.gs-table td {
  vertical-align: top;
  padding: 5pt 6pt;
  border: 0.75pt solid #999;
  font: var(--typep-body);
}

.gs-table .gs-col-tempo {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

/* Un esercizio non si taglia a metà fra due pagine: o ci sta, o passa
   intero alla successiva. */
.gs-exercise-row {
  break-inside: avoid;
  page-break-inside: avoid;
}

/* Riga di fase a piena larghezza. L'originale non ce l'ha (affida tutto
   alla colonna Tempo), ma senza di essa la struttura della seduta —
   riscaldamento, parte principale, parte finale — sparisce, ed è la
   prima cosa che si cerca guardando il foglio a bordo pista. */
.gs-phase-row td {
  background: #000;
  color: #fff;
  font: var(--typep-section);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 5pt 6pt;
  border: none;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

.gs-phase-duration {
  font-weight: 400;
  text-transform: none;
  margin-left: 6pt;
}

/* Il nome dell'esercizio è un `<h4>` nel markup, e la regola globale su
   `h4` lo vestirebbe da occhiello — maiuscoletto e grigio. Qui è il nome
   della cosa, non l'etichetta di ciò che segue: si riprende la cassa e
   l'inchiostro. Sul foglio stampato il grigio sarebbe anche il colore
   sbagliato: la scheda va in bianco e nero. */
.gs-exercise-title {
  font: var(--typep-title);
  text-transform: none;
  letter-spacing: normal;
  color: #000;
  margin: 0 0 2pt 0;
}

.gs-new-badge {
  font: var(--typep-eyebrow);
  letter-spacing: var(--typep-eyebrow-ls);
  text-transform: uppercase;
  border: 0.5pt solid #555;
  padding: 1pt 4pt;
  border-radius: 3pt;
  margin-left: 4pt;
}

.gs-exercise-meta {
  font: var(--typep-caption);
  color: #555;
  margin: 0 0 4pt 0;
}

.gs-exercise-goal {
  margin: 0 0 4pt 0;
}

.gs-subhead {
  display: block;
  font: var(--typep-eyebrow);
  letter-spacing: var(--typep-eyebrow-ls);
  text-transform: uppercase;
  color: #555;
  margin: 4pt 0 1pt 0;
}

.gs-table ul,
.gs-table ol {
  margin: 0 0 3pt 0;
  padding-left: 1.15em;
}

.gs-table li {
  margin-bottom: 1.5pt;
}

.gs-goalkeeper-note {
  display: inline-block;
  font: var(--typep-caption);
  border: 0.5pt solid #000;
  padding: 1pt 5pt;
  border-radius: 3pt;
  margin: 3pt 0 0 0;
}

/* Attribuzione senza `<a>`: su carta un link non si clicca, e l'URL per
   esteso mangerebbe mezza colonna. */
.gs-attribution {
  font: var(--typep-caption);
  color: #555;
  margin: 3pt 0 0 0;
}

.gs-no-diagram {
  font: var(--typep-caption);
  color: #999;
}

.gs-col-disegno .rink-diagram,
.gs-exercise-image {
  width: 100%;
  max-width: 100%;
  height: auto;
}

.gs-key-points {
  font: var(--typep-body);
  margin: 6pt 0 0 0;
}

/* Materiale: elenco compatto nel piede, sotto «Osservazioni». Due colonne
   perché una seduta con otto esercizi produce facilmente una decina di
   voci corte, e in colonna singola sprecherebbe metà riga per ognuna. */
.gs-footer-equipment ul {
  margin: 0;
  padding-left: 1.1em;
  columns: 2;
  column-gap: 10pt;
  font: var(--typep-caption);
}

.gs-footer-equipment li {
  margin-bottom: 1pt;
  break-inside: avoid;
}

/* Con il materiale nel piede le righe delle osservazioni si stringono:
   restano tre righe da scrivere, che è quanto serve a una nota a caldo. */
.gs-footer-notes .gs-ruled-lines {
  min-height: 2.2cm;
}

/* --- Piede: Formazione, Osservazioni, Giudizio all. --- */

.gs-footer {
  display: flex;
  gap: 12pt;
  margin-top: 10pt;
  break-inside: avoid;
  page-break-inside: avoid;
}

.gs-footer-formation {
  flex: 0 0 58%;
}

.gs-footer-side {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 8pt;
}

.gs-footer h3 {
  font: var(--typep-eyebrow);
  letter-spacing: var(--typep-eyebrow-ls);
  text-transform: uppercase;
  border-bottom: 1pt solid #000;
  padding-bottom: 2pt;
  margin: 0 0 5pt 0;
}

.gs-formation-row {
  display: flex;
  align-items: center;
  gap: 8pt;
  margin-bottom: 5pt;
}

.gs-formation-label {
  flex: 0 0 2cm;
  font: var(--typep-eyebrow);
  letter-spacing: var(--typep-eyebrow-ls);
}

.gs-formation-goalies {
  display: flex;
  gap: 6pt;
}

.gs-formation-slot {
  position: relative;
  width: 2.4cm;
  height: 1cm;
  border: 0.75pt solid #555;
  border-radius: 3pt;
}

.gs-formation-line {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 2pt;
  flex: 1;
}

.gs-formation-cell {
  position: relative;
  height: 0.85cm;
  border: 0.75pt solid #555;
  border-radius: 3pt;
}

.gs-slot-tag {
  position: absolute;
  top: 1pt;
  left: 2pt;
  font: var(--typep-eyebrow);
  letter-spacing: var(--typep-eyebrow-ls);
  color: #555;
}

/* Righe su cui scrivere a mano: sono la resa a stampa di un campo che si
   compila dopo l'allenamento, non un segnaposto per un dato mancante. */
.gs-ruled-lines {
  min-height: 3.2cm;
  background-image: repeating-linear-gradient(
    to bottom,
    transparent,
    transparent 0.72cm,
    #555 0.72cm,
    #555 calc(0.72cm + 0.75pt)
  );
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

.gs-notes-written {
  font: var(--typep-body);
}

.gs-notes-written p {
  margin: 0 0 3pt 0;
}

.gs-footer-box {
  min-height: 1.6cm;
  border: 0.75pt solid #555;
  border-radius: 3pt;
}

/* ==========================================================================
   Anteprima di stampa della scheda (v1.23) — `GET /sessions/:id?anteprima=1`

   Perché esiste: la scheda G+S è il formato in cui il lavoro dell'app esce
   davvero, e fino alla v1.22 l'unico modo di vederla era aprire il dialogo
   di stampa — cioè impegnarsi a stampare, o a produrre un file, per
   scoprire com'era venuta. L'anteprima rimuove quel pedaggio.

   Non è una seconda resa: mostra **lo stesso** markup `.gs-sheet` con **le
   stesse** regole di sopra, su un foglio largo quanto un A4 vero. Quello
   che l'anteprima NON sa mostrare sono i salti pagina: il CSS a schermo non
   pagina, quindi qui il foglio è un nastro continuo. Impaginazione,
   proporzioni e tipografia sono esatte; "questo esercizio finisce a pagina
   2" lo sa solo la stampa. Vedi TODO.md per il PDF lato server, che quel
   limite lo toglierebbe.
   ========================================================================== */

/* Fondo grigio attorno al foglio: dice "questa è carta, non è la pagina
   web". Lo stesso mestiere che fa il grigio dietro l'anteprima di Chrome. */
.sheet-preview {
  background: #55585f;
  margin: 0 calc(-1 * var(--space-4));
  padding: var(--space-5) var(--space-4);
  /* Sotto i ~21cm di larghezza il foglio non entra. Scorre questa fascia,
     non la pagina intera: il resto della shell resta al suo posto. */
  overflow-x: auto;
}

/* In anteprima la scheda a schermo (card, form di rigenerazione, meta)
   sparisce: si guarda il foglio, non due copie della stessa seduta. */
.sheet-preview .no-print {
  display: none;
}

/* ...tranne la barra delle azioni, che è il solo modo di uscire
   dall'anteprima e di lanciare la stampa. Selettore per id: vince su
   `.sheet-preview .no-print` per specificità, senza `!important`.
   (v1.25) Due id e non una classe condivisa: sono due barre distinte, in due
   pagine, e un id resta il selettore che vince senza `!important`. */
.sheet-preview #print-actions,
.sheet-preview #player-print-actions {
  /* `display` e non `visibility`: serve a battere `.sheet-preview
     .no-print`. Il resto (flex, gap, a capo) lo porta già `.page-actions`.
     Allineata al foglio, non alla colonna: stessa larghezza, stesso asse. */
  display: flex;
  max-width: 21cm;
  margin: 0 auto var(--space-4) auto;
  color: #fff;
}

.sheet-preview #print-actions .field-hint,
.sheet-preview #player-print-actions .field-hint {
  color: #d3d6dc;
}

.sheet-preview .print-only {
  display: block;
}

/* Il foglio: A4 esatto, con i margini di `@page gs-sheet` resi come
   padding. `box-sizing: border-box` perché 21cm è la carta, non l'area di
   testo — gli stessi 21cm che escono dalla stampante. */
.sheet-preview .gs-sheet {
  box-sizing: border-box;
  width: 21cm;
  min-height: 29.7cm;
  padding: 1.1cm 1cm 0.8cm 1.5cm;
  margin: 0 auto;
  background: #fff;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
}

/* Nota esplicativa sotto un campo di form: serve dove l'etichetta da sola
   si presta a essere fraintesa (es. il "giorno d'allenamento" G+S, che in
   italiano suona come una seduta qualsiasi ma vale solo per attività di
   240-300 minuti). */
.field-hint {
  margin: calc(-1 * var(--space-2)) 0 0 0;
  font: var(--type-caption);
  color: var(--color-text-muted);
}


/* --- (v1.23 → v1.27) Famiglie di temi su superficie scura -----------------
   Qui c'era un blocco `@media (prefers-color-scheme: dark)` che ridefiniva
   le sette tinte di famiglia. **Rimosso**, e non perché le tinte fossero
   sbagliate.

   L'app ha un solo `:root` chiaro. Quel blocco si attivava da solo su un
   sistema impostato scuro e cambiava il colore dei temi **dentro una pagina
   che restava bianca**: non un tema scuro, un pezzo di tema scuro acceso da
   sé in mezzo a un tema chiaro. La timeline di gennaio diventava di un blu
   diverso da quella di chi ha il sistema in chiaro, senza che nessuno lo
   avesse deciso e senza che si potesse vedere provando l'app.

   È la stessa lezione già scritta poco più sotto per la rampa ordinale, e
   vale identica qui: le variabili di colore si commutano **insieme** al
   tema della pagina, non prima.

   Le tinte verificate non si perdono, stanno in `ThemeFamily::color_dark`
   in core — che è dove erano già, e che questo blocco si limitava a
   copiare. Il giorno in cui l'app avrà un tema scuro suo, si riprendono di
   lì. `--fam-atletica` è deliberatamente identica alla sua tinta chiara:
   quel verde regge entrambe le superfici, e ritoccarlo lo avvicinerebbe a
   `--fam-recupero`. --- */

/* --- (v1.24) I neutri della timeline **non** commutano con
   `prefers-color-scheme`.

   (v1.27) Questo commento diceva «e le tinte di famiglia qui sopra sì»,
   come se le due cose facessero apposta scelte diverse. Facevano scelte
   diverse, ma la sbagliata era l'altra: ora nessuna delle due commuta, e la
   ragione scritta qui sotto — buona già allora — vale per entrambe.

   I neutri **invertono** la chiarezza, e finché il resto della pagina non
   ha un tema scuro proprio (§10bis.4) il risultato è una corsia quasi nera
   dentro una card bianca. Provato in Chrome con il sistema in modalità
   scura: la riga di ogni tema diventava un tratto d'inchiostro.

   I valori scuri esistono e sono stati verificati insieme a quelli chiari —
   `#232322` / `#2d2d2b` / `#26261f` / `#3d3d38` / `#75756e`, con
   `--timeline-today: #f4f4f0` — e vanno attivati **insieme** al tema scuro
   dell'app, non prima. Sono scritti qui perché ritrovarli fra sei mesi
   costerebbe di nuovo la verifica. --- */

/* --- (v1.23) Tema imposto alla generazione ------------------------------
   Il chip che dice, sopra il form della home, che si sta generando su un
   tema scelto altrove (`?theme=<slug>`). Il colore della famiglia sta sul
   nome del tema e non sullo sfondo del chip: qui il colore identifica il
   tema, e riempirci un rettangolo lo farebbe leggere come uno stato. */
.focus-theme-chip {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
}

.focus-theme-chip-label {
  color: var(--color-text-muted);
}

.focus-theme-chip strong {
  /* La tinta di famiglia, scelta dalla classe `.fam-*` sull'elemento. Il
     fallback non è decorativo: se un giorno si aggiunge una famiglia in core
     senza la sua variabile qui, il nome resta leggibile invece di sparire. */
  color: var(--fam, var(--color-text));
}

.focus-theme-chip-clear {
  color: var(--color-text-muted);
  font: var(--type-caption);
}

/* Ogni famiglia espone la sua tinta come `--fam`, così qualunque elemento
   discendente la usa senza sapere quale famiglia sia. È il meccanismo su cui
   si appoggiano sia il chip qui sopra sia le righe della timeline. */
.fam-tecnica { --fam: var(--fam-tecnica); }
.fam-costruzione { --fam: var(--fam-costruzione); }
.fam-recupero { --fam: var(--fam-recupero); }
.fam-transizione { --fam: var(--fam-transizione); }
.fam-gioco { --fam: var(--fam-gioco); }
.fam-atletica { --fam: var(--fam-atletica); }
.fam-portiere { --fam: var(--fam-portiere); }


/* ==========================================================================
   (v1.24) Roster e valutazioni
   ========================================================================== */

/* --- La rampa ordinale ---------------------------------------------------
   Un solo tono, dal chiaro allo scuro, per una scala a quattro livelli. Mai
   una palette a colori diversi per livello — quella dice "categorie", non
   "quantità" — e mai una scala divergente, che implicherebbe un polo
   negativo e un neutro che su una progressione di apprendimento non
   esistono.

   Rispecchia `SkillLevel::color_light`/`color_dark` in core, stesso rapporto
   già in uso fra `ThemeFamily::color_light` e le `--fam-*`.

   **Regola vincolante** (ARCHITECTURE.md §10bis.2): una superficie usa
   questa rampa **oppure** le `--fam-*`, mai entrambe. Due passi qui sotto
   ripetono per hex due tinte di famiglia, e sulla stessa superficie lo
   stesso blu significherebbe sia "livello 2" sia "tecnica individuale".
   Nella heatmap comandano queste; il colore di famiglia compare solo sui
   riquadri di sintesi, dove nessun livello è colorato. --- */
:root {
  --lvl-1: #86b6ef;
  --lvl-2: #3987e5;
  --lvl-3: #1c5cab;
  --lvl-4: #0d3566;
  /* Il testo dentro una cella colorata: scuro sui due passi chiari, chiaro
     sui due scuri. Non un colore solo con opacità — il contrasto va
     verificato su entrambi gli estremi, e a metà rampa fallirebbe. */
  --lvl-1-ink: #0b2440;
  --lvl-2-ink: #ffffff;
  --lvl-3-ink: #ffffff;
  --lvl-4-ink: #ffffff;
  /* Il vuoto della heatmap: una tinta neutra chiarissima, distinta sia dal
     primo passo della rampa sia dalla superficie della card, così che
     "non valutata" si veda come uno stato e non come un buco nel disegno. */
  --lvl-empty: #f0f1f4;
  --lvl-na: #fafbfc;
  /* Il tratteggio di «non pertinente». Non un grigio più chiaro di
     `--lvl-empty`: due grigi vicini si distinguono solo mettendoli uno
     accanto all'altro, e nella matrice non capita mai. Un motivo invece si
     riconosce da solo, e sopravvive alla fotocopia in bianco e nero, che è
     come questa pagina finisce sulla carta di chi la stampa.

     La tinta è volutamente debolissima, e la prima prova l'aveva sbagliata.
     A #d5d9e0 una cella isolata si leggeva bene, ma le celle non pertinenti
     non arrivano mai isolate: un portiere dentro un gruppo di giocatori di
     movimento le accende tutte insieme — cinque colonne per dodici righe —
     e le diagonali si allineavano da una cella all'altra formando un campo
     di righe continue che tirava l'occhio più dei numeri accanto. Questo
     motivo deve dire «qui non c'è niente da guardare», e per dirlo va visto
     soltanto quando lo si cerca.

     `--lvl-na-key` è lo stesso motivo per la pastiglia della legenda, dove
     la tinta debole non basta: un quadratino di 16 px va riconosciuto da
     solo e fuori contesto, come il campione di retino sulla legenda di una
     carta, che è sempre più fitto di quanto lo sia sulla carta. */
  --lvl-na-line: #e5e8ed;
  --lvl-na-key: #c9cfd8;
}

/* --- Perché qui NON c'è un blocco `prefers-color-scheme: dark` -----------
   Trovato provando la pagina in un browser impostato su tema scuro. L'app
   ha un solo `:root` chiaro (§10bis.4): il resto della pagina resta chiaro
   qualunque cosa dica il sistema. Un blocco scuro per queste variabili si
   sarebbe attivato **da solo**, dipingendo celle quasi nere e una rampa
   invertita dentro una pagina per il resto bianca — non un tema scuro, un
   errore.

   Le tinte scure esistono già, verificate, in `SkillLevel::color_dark`: è
   lì che aspettano il giorno in cui l'intera pagina avrà un tema scuro, che
   è l'unico momento in cui applicarle ha senso. Il blocco `--fam-*` più
   sopra se lo può permettere perché quelle tinte vestono testo e bordi di
   pochi elementi; questa rampa dipinge superfici grandi, e sbagliarla si
   vede subito.

   Se un giorno si aggiunge il tema scuro, questo è uno dei punti da
   riaprire. --- */

.lvl-1 { background: var(--lvl-1); color: var(--lvl-1-ink); }
.lvl-2 { background: var(--lvl-2); color: var(--lvl-2-ink); }
.lvl-3 { background: var(--lvl-3); color: var(--lvl-3-ink); }
.lvl-4 { background: var(--lvl-4); color: var(--lvl-4-ink); }

/* --- Elementi di pagina condivisi da queste viste ------------------------ */

.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin: var(--space-4) 0;
}

.card > h2:first-child {
  margin-top: 0;
}

.section-hint,
.empty-state-hint {
  color: var(--color-text-muted);
  font: var(--type-caption);
  margin: 0 0 var(--space-3);
}

.muted-cell {
  color: var(--color-text-muted);
  font: var(--type-caption);
}

.tag-chip {
  display: inline-block;
  background: var(--color-primary-soft);
  color: var(--color-primary-dark);
  border-radius: var(--radius-pill);
  padding: 0 var(--space-2);
  margin-right: var(--space-1);
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  white-space: nowrap;
}

/* Uno stato non "attivo" non deve leggersi come una spunta verde: chi è
   inattivo o infortunato spiega da solo perché la sua riga è vuota. */
.state-open,
.state-injured { background: var(--color-warning-bg); color: var(--color-warning-text); }
.state-trial { background: var(--color-badge-bg); color: var(--color-badge-text); }
.state-inactive { background: var(--color-bg); color: var(--color-text-muted); }

.banner-info {
  background: var(--color-primary-soft);
  border-left: 4px solid var(--color-primary);
  color: var(--color-text);
}

.banner-ok {
  background: var(--color-ok-bg);
  border-left: 4px solid var(--color-ok-border);
  color: var(--color-ok-text);
}

.field-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.field-row > label {
  flex: 1 1 12rem;
}

.checkbox-group {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* --- Il numero di maglia -------------------------------------------------
   Un badge quadrato e monospaziato per numeri: due cifre e una cifra devono
   occupare la stessa larghezza, altrimenti la colonna balla. */
.jersey-number {
  display: inline-block;
  min-width: 2rem;
  padding: 0 var(--space-1);
  background: var(--color-text);
  color: var(--color-surface);
  border-radius: var(--radius-sm);
  font: var(--type-figure-sm);
  /* Dopo la scorciatoia, mai prima: `font` azzera `font-variant-numeric`. */
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.jersey-number-none {
  background: transparent;
  color: var(--color-text-muted);
  font: var(--type-caption);
}

.jersey-number-lg {
  font: var(--type-figure);
  min-width: 2.75rem;
  vertical-align: middle;
}

.roster-number-col {
  width: 3.5rem;
}

.roster-table,
.progress-table,
.profile-table,
.dumbbell-table {
  width: 100%;
  border-collapse: collapse;
}

.roster-table th,
.roster-table td,
.progress-table th,
.progress-table td,
.profile-table th,
.profile-table td,
.dumbbell-table th,
.dumbbell-table td {
  text-align: left;
  padding: var(--space-2);
  border-bottom: 1px solid var(--color-border);
  vertical-align: middle;
}

.jersey-history {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-3);
}

.jersey-history li {
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--color-border);
}

.player-notes {
  white-space: pre-wrap;
  background: var(--color-bg);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}

/* --- §6.1: profilo a barre ----------------------------------------------
   Barre orizzontali e non un radar: con nove assi la forma del poligono
   sembra significativa mentre l'ordine delle aree è arbitrario. */
.profile-bar-col {
  width: 40%;
  min-width: 10rem;
}

.profile-track {
  position: relative;
  height: 1.25rem;
  background: var(--lvl-empty);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.profile-bar {
  height: 100%;
  border-radius: var(--radius-sm);
}

.level-cell {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* «non valutata» non è un livello: si scrive, non si colora. Il grigio
   basta a dirlo — il corsivo che stava qui lo diceva una seconda volta, e
   senza un taglio corsivo imbarcato usciva come un tondo inclinato. */
.not-rated {
  color: var(--color-text-muted);
}

/* --- §6.2: dumbbell ------------------------------------------------------ */

.dumbbell-axis-col {
  width: 45%;
  min-width: 12rem;
}

.dumbbell-scale {
  display: flex;
  justify-content: space-between;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.dumbbell-track {
  position: relative;
  height: 1.5rem;
  background: var(--lvl-empty);
  border-radius: var(--radius-pill);
}

.dumbbell-link {
  position: absolute;
  top: 50%;
  height: 3px;
  transform: translateY(-50%);
  background: var(--color-text-muted);
}

.dumbbell-dot {
  position: absolute;
  top: 50%;
  width: 0.875rem;
  height: 0.875rem;
  margin-left: -0.4375rem;
  transform: translateY(-50%);
  border-radius: 50%;
  border: 2px solid var(--color-surface);
}

/* Il punto di partenza è cavo, quello di arrivo pieno: la differenza fra i
   due estremi non poggia sul colore, che qui è già occupato dal livello. */
.dumbbell-dot-first {
  background: var(--color-surface);
  box-shadow: inset 0 0 0 3px currentColor;
}

.dumbbell-movement {
  font: var(--type-caption);
  white-space: nowrap;
}

.movement-label {
  display: block;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.dumbbell-up .movement-label { color: var(--color-ok-text); }
.dumbbell-down .movement-label { color: var(--color-warning-text); }

/* --- (v1.29) Testata della pagina Valutazioni -----------------------------
   Due cose di natura diversa sulla stessa riga: a sinistra chi stiamo
   guardando, a destra da dove vengono i numeri. Fino alla v1.28 la
   provenienza era una frase sotto il sottotitolo, e la data — l'unica cosa
   che si cerca davvero in quella riga — stava in mezzo alla prosa. Come
   coppia etichetta/valore si legge senza leggerla.

   Il caso «nessuna rilevazione chiusa» resta invece un `.banner-warning` a
   tutta larghezza sotto la testata: lì il messaggio non è *quando* si è
   osservato, ma che i numeri possono cambiare mentre li si guarda, e una
   pastiglia in un angolo non è il posto per dirlo. --- */
.assessments-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  margin-bottom: var(--space-4);
}

/* `1 1 20rem`: il blocco dei titoli si tiene tutta la riga finché la coppia
   etichetta/valore ha meno di 20rem in cui stare, e allora va sotto invece
   di stringersi in colonna. */
.assessments-head-titles {
  flex: 1 1 20rem;
  min-width: 0;
}

.assessments-head-titles h1 {
  margin-bottom: var(--space-2);
}

/* `.page-subtitle` porta un margine negativo in alto per incollarsi al
   titolo: qui il titolo ha già il suo spazio e quel correttivo produrrebbe
   una sovrapposizione. */
.assessments-head-titles .page-subtitle {
  margin-top: 0;
  margin-bottom: 0;
}

.source-note {
  flex: 0 1 auto;
  margin: 0;
}

.source-note-label {
  display: block;
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.source-note-value {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: 2px;
  font: var(--type-body-strong);
  color: var(--color-text);
}

.source-note-value svg {
  flex: none;
  color: var(--color-primary);
}

/* Il nome che l'allenatore ha dato alla rilevazione. Sotto la data e non al
   posto suo: «Inizio stagione» dice quale, «1 ottobre» dice quando, e la
   domanda che si fa guardando dei grafici è la seconda. */
.source-note-sub {
  display: block;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

/* Da 40rem la coppia si allinea al bordo destro, dove il titolo non arriva.
   Sotto quella misura resta allineata a sinistra sotto il sottotitolo: una
   riga di due parole appoggiata al margine destro di un telefono sembra un
   pezzo staccato di un'altra pagina. */
@media (min-width: 40rem) {
  .source-note {
    text-align: right;
  }

  .source-note-value {
    justify-content: flex-end;
  }
}


/* --- (v1.29) Titolo di sezione e strumenti per leggerla, sulla stessa riga
   La legenda della rampa era in fondo alla spiegazione, sopra la tabella:
   per ricordarsi cosa fosse il blu scuro bisognava risalire con gli occhi
   ogni volta, e la legenda è proprio lo strumento con cui la tabella si
   legge. In linea con il titolo, a destra, sta dove la si cerca. Su schermo
   stretto va sotto, che è l'unico posto in cui ci sta. --- */
.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  margin-bottom: var(--space-2);
}

.section-head > h2 {
  margin-bottom: 0;
}

.section-head-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-left: auto;
}

/* Quattro pixel di scarto ottico, non un capriccio: il titolo è condensato
   a 24 px e la legenda tonda a 14, e allineare le due scatole in alto
   lascia la legenda visibilmente più su della prima linea del titolo. */
.ramp-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin: var(--space-1) 0 0 0;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.ramp-legend-title {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
}

/* La variante corta, sotto «Come si muove il gruppo»: qui i nomi dei
   livelli per esteso li ha già dati la sezione sopra, e ripeterli
   allungherebbe la riga fino a mangiarsi il titolo. Il nome resta nel
   `title` di ogni chiave. */
.ramp-legend-compact {
  gap: var(--space-2);
}

.ramp-legend-compact .ramp-key {
  gap: var(--space-1);
  font-variant-numeric: tabular-nums;
}

.ramp-key {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  white-space: nowrap;
}

.ramp-swatch {
  display: inline-block;
  width: 1rem;
  height: 1rem;
  border-radius: 4px;
  vertical-align: middle;
}

.ramp-swatch-empty { background: var(--lvl-empty); }

.ramp-swatch-na {
  background-color: var(--lvl-na);
  background-image: repeating-linear-gradient(
    45deg,
    var(--lvl-na-key) 0 1.5px,
    transparent 1.5px 5px
  );
  border: 1px solid var(--color-border);
}


/* --- (v1.29) Il riquadro di precisazione ---------------------------------
   Il rettangolo azzurro del mockup. Ha il vocabolario di `.banner-info` —
   fondo tenue dell'accento, icona, testo di corpo — ma non è un banner e
   non ne porta la classe: un banner annuncia qualcosa che è appena
   successo e occupa la larghezza della pagina, questo spiega **come conta**
   il grafico che gli sta sotto e vive accanto al suo titolo. --- */
.info-note {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  max-width: 22rem;
  margin: 0;
  padding: var(--space-2) var(--space-3);
  background: var(--color-primary-soft);
  border-radius: var(--radius-sm);
  font: var(--type-caption);
  color: var(--color-text);
}

.info-note-icon {
  flex: none;
  margin-top: 1px;
  color: var(--color-primary);
}


/* --- §6.4: heatmap -------------------------------------------------------
   Righe = giocatori, colonne = aree. Le celle portano la rampa ordinale e
   il colore di famiglia non entra affatto (§10bis.2). Il numero del livello
   resta stampato nella cella: il valore non vive mai solo nel colore.

   (v1.29) La matrice scorre dentro il proprio riquadro, su tutti e due gli
   assi, e ha l'intestazione e la colonna anagrafica appiccicate.

   Il riquadro è indispensabile perché siano appiccicate *bene*, e la
   ragione è la stessa che nella tabella di revisione del catalogo aveva
   portato alla scelta opposta (vedi il commento a ~riga 2136): per le
   regole di CSS un contenitore con `overflow` su un asse lo diventa
   **anche sull'altro**, e diventa il riferimento delle posizioni `sticky`
   al suo interno. Là quel riferimento era un guaio, perché l'intestazione
   voleva fermarsi sotto l'header dell'app, che sta nella finestra; qui è
   esattamente ciò che serve — `top: 0` è il bordo alto del riquadro,
   `left: 0` il suo bordo sinistro — e in cambio la pagina intera non scorre
   più in orizzontale portandosi via le card sopra e sotto, che su un
   telefono a bordo pista è la differenza fra scorrere una tabella e perdere
   la pagina.

   Il tetto d'altezza esiste perché un riquadro che non scorre in verticale
   non ha nulla da cui staccare l'intestazione: `min(72vh, 40rem)` lascia
   sempre un pezzo di pagina intorno, così si vede che a scorrere è la
   tabella. Se le righe ci stanno tutte, il riquadro si accorcia da sé e non
   succede niente.

   `.table-scroll` resta sul `<div>` per coerenza con le altre tabelle
   dell'app, ma non porta stile: lo stile è tutto qui, su una classe che
   riguarda solo questa matrice. --- */
.heatmap-scroll {
  overflow: auto;
  max-height: min(72vh, 40rem);
}

/* `table-layout: fixed` e non `auto`, ed è la riga che tiene in piedi tutta
   la matrice. Con la disposizione automatica la larghezza di una colonna la
   detta il suo **contenuto più largo**, che qui non è mai la cella — una
   cifra sola — ma l'intestazione: «P· LETTURA» è lunga il doppio di «TIRO»,
   e la colonna la seguiva. Ne usciva una griglia di rettangoli di sette
   misure diverse in cui la dimensione della cella sembrava voler dire
   qualcosa, mentre dipendeva soltanto da quanto è lungo il nome dell'area.
   In una matrice il posto di un valore è già un'informazione: se anche la
   sua forma varia, l'occhio legge una differenza che non c'è.

   Con la disposizione fissa comandano le larghezze dichiarate sulla prima
   riga — la riga d'intestazione — e il contenuto non le sposta più. Quel
   che cambia in cambio: un'intestazione più larga della sua colonna va a
   capo invece di allargarla, ed è il motivo per cui qui sotto `.heatmap-area`
   perde il `nowrap` che aveva.

   `width: max-content` non è decorativo e non si tolga: con la larghezza
   **automatica** Chrome determina prima la larghezza della tabella con
   l'algoritmo normale — cioè guardando i contenuti — e le colonne tornavano
   disuguali nonostante `table-layout: fixed`, misurate fra 52 e 68 px. Con
   `max-content` la larghezza della tabella è per definizione la somma delle
   colonne dichiarate, e le dichiarazioni valgono davvero. Se la somma non
   sta nella pagina, scorre: `.heatmap-scroll` e le colonne appiccicate
   esistono per quello. */
.heatmap-table {
  table-layout: fixed;
  width: max-content;
  border-collapse: separate;
  border-spacing: 2px;
  font-variant-numeric: tabular-nums;
  /* Larghezza di riferimento della colonna anagrafica: la usano sia la
     cella del numero sia lo scostamento a cui si ancora quella del nome.
     Un solo posto da cambiare, altrimenti fra le due si apre una fessura
     larga quanto l'errore. */
  --heatmap-num-w: 3rem;
  /* La colonna del nome ha una misura sua, dichiarata: con la disposizione
     fissa nessuna colonna può più crescere da sé, nemmeno questa. Undici
     rem tengono «Arthur Jimmy Giabardo» (163 px misurati) più lo spazio
     che lo stacca dal filetto; oltre, il nome si accorcia con i puntini
     invece di invadere la prima area. */
  --heatmap-name-w: 11rem;
  /* La misura unica di **tutte** le colonne di valutazione. Non nasce dalla
     cella, che di suo starebbe in molto meno, ma dall'intestazione più
     larga che non si può mandare a capo: «ATTEGGIAM.» è una parola sola, e
     una parola sola è il pavimento sotto cui questa misura non può
     scendere. Le altre intestazioni lunghe («P· BASTONE», «P· LETTURA»)
     hanno uno spazio e si spezzano in due righe.

     Sessanta pixel: «ATTEGGIAM.» ne misura 55,4 alla spaziatura ridotta di
     qui sotto. Con questa misura, quattordici aree più l'anagrafica fanno
     1098 px e stanno dentro i 1102 della card sul monitor su cui è stata
     provata — di poco, e di proposito: allargarla anche solo di un gradino
     fa comparire una barra di scorrimento su una tabella che ci starebbe. */
  --heatmap-col-w: 3.75rem;
}

/* Sfondo pieno su tutto ciò che è appiccicato, e non è pignoleria: con
   `border-collapse: separate` una cella non ha un fondo di riga da cui
   ereditare, e una `th` trasparente lascia vedere in trasparenza le righe
   che le passano sotto.

   L'ombra senza sfocatura e con due pixel di espansione serve al problema
   gemello: `border-spacing: 2px` lascia due pixel di **vuoto** fra una
   cella e l'altra, e in quel vuoto passerebbe il contenuto che scorre. Due
   pixel di espansione nel colore della superficie chiudono la fessura su
   tutti e quattro i lati senza invadere la cella vicina, che comincia
   esattamente dove l'ombra finisce. */
.heatmap-table thead th {
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--color-surface);
  box-shadow: 0 0 0 2px var(--color-surface);
  font: var(--type-thead);
  letter-spacing: var(--type-thead-ls);
  text-transform: uppercase;
  text-align: left;
  vertical-align: bottom;
  padding-bottom: var(--space-1);
}

.heatmap-corner,
.heatmap-num {
  width: var(--heatmap-num-w);
}

/* Le larghezze che la disposizione fissa legge: stanno tutte sulla riga
   d'intestazione, perché è quella la riga che decide. */
.heatmap-table thead .heatmap-name {
  width: var(--heatmap-name-w);
}

.heatmap-area {
  width: var(--heatmap-col-w);
}

/* Le due celle dell'anagrafica restano ancorate a sinistra: scorrendo verso
   destra per arrivare alle ultime aree si deve continuare a vedere *di chi*
   si sta leggendo la riga. Lo scostamento del nome è la larghezza del
   numero più i quattro pixel di `border-spacing` che stanno prima e dopo. */
.heatmap-num,
.heatmap-table tbody .heatmap-name {
  position: sticky;
  z-index: 2;
  background: var(--color-surface);
  box-shadow: 0 0 0 2px var(--color-surface);
}

.heatmap-num {
  left: 0;
}

.heatmap-table .heatmap-name {
  left: calc(var(--heatmap-num-w) + 4px);
}

/* Gli angoli in alto a sinistra sono appiccicati su tutti e due gli assi, e
   devono stare sopra sia alla riga d'intestazione sia alla colonna dei
   nomi: quando si scorre in diagonale sono l'unica cosa che si sovrappone
   a entrambe. */
.heatmap-table thead .heatmap-corner,
.heatmap-table thead .heatmap-name {
  position: sticky;
  z-index: 4;
}

.heatmap-table thead .heatmap-corner {
  left: 0;
}

.heatmap-area {
  /* Le intestazioni ruotate sono illeggibili su schermi piccoli e in
     stampa: si abbrevia il testo e il nome intero resta nel `title`. */
  writing-mode: horizontal-tb;
  color: var(--color-text-muted);
  /* Niente `nowrap`: con la disposizione fissa un'intestazione che non ci
     sta va a capo, e andare a capo è esattamente quel che deve fare —
     prima allargava la colonna, e la colonna si portava dietro la cella. */
  padding: 0 0 var(--space-1);
  text-align: center;
  line-height: 1.15;
  /* Spaziatura più stretta della `--type-thead-ls` di regola, e solo qui.
     A 0,07em «ATTEGGIAM.» misura 59 px e obbligherebbe **tutte** e
     quattordici le colonne a 68, portando la tabella a 1250 px e a una
     barra di scorrimento su un monitor che la conterrebbe. Il maiuscoletto
     regge una spaziatura minore senza chiudersi; una colonna in più da
     scoprire scorrendo no. */
  letter-spacing: 0.04em;
}

/* Il nome del giocatore è un `<th scope="row">`, ma è il soggetto della
   riga, non la sua etichetta di colonna: prende il ruolo di un nome. Il
   filetto a destra separa l'anagrafica dal blocco delle valutazioni — due
   cose che si leggono in modo diverso, una per parole e una per colore. */
.heatmap-name {
  font: var(--type-body-strong);
  text-align: left;
  white-space: nowrap;
  /* La colonna ha ora una larghezza dichiarata, e un nome più lungo non la
     allarga più: senza questi due, traboccherebbe sulla prima area invece
     di fermarsi. I puntini sono un ripiego che non dovrebbe mai vedersi —
     il nome intero resta comunque nel link. */
  overflow: hidden;
  text-overflow: ellipsis;
  padding-right: var(--space-2);
  border-right: 1px solid var(--color-border);
}

.heatmap-cell {
  /* Nessuna larghezza qui: con la disposizione fissa la detta la riga
     d'intestazione, e dichiararla due volte in due posti diversi vuol dire
     soltanto avere due numeri da tenere d'accordo. */
  height: 2.25rem;
  text-align: center;
  border-radius: var(--radius-sm);
  font: var(--type-figure-cell);
  /* Dopo la scorciatoia, mai prima: `font` azzera `font-variant-numeric`
     anche quando era ereditato dalla tabella. */
  font-variant-numeric: tabular-nums;
}

.heatmap-cell-empty { background: var(--lvl-empty); }

/* «Non pertinente» è un'altra cosa da «non ancora valutata», e la
   differenza non può essere un grigio un po' più chiaro: due grigi vicini
   si distinguono solo affiancandoli, e nella matrice non capita mai.
   Il tratteggio si riconosce da solo, anche in fotocopia. */
.heatmap-cell-na {
  background-color: var(--lvl-na);
  /* Righe sottili e rade: è una campitura, non una barratura. */
  background-image: repeating-linear-gradient(
    45deg,
    var(--lvl-na-line) 0 1.5px,
    transparent 1.5px 9px
  );
  color: var(--color-text-muted);
  font: var(--type-figure-cell);
  font-variant-numeric: tabular-nums;
}


/* --- §6.3: piccoli multipli ---------------------------------------------
   Quattordici riquadri piccoli invece di quattordici serie colorate su un
   grafico solo: oltre le sette-otto serie i colori non si distinguono più.
   Ogni riquadro porta sotto la sua tabella con i valori esatti, che è anche
   la sua versione leggibile a voce.

   (v1.29) Colonne fisse e non più `auto-fill`: con `auto-fill` era la
   griglia a decidere quante colonne ci stessero, e su un monitor largo ne
   metteva cinque o sei da 13rem — riquadri in cui due barre affiancate
   diventano due strisce e la tabella dei valori va a capo. Quattro è il
   numero su cui i mockup sono disegnati, ed è il massimo con cui la riga
   «Livello 1 2 3 4 n.v.» sta ancora su una riga sola. --- */
.small-multiples {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-3);
}

@media (min-width: 36rem) {
  .small-multiples {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 75rem) {
  .small-multiples {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.small-multiple {
  margin: 0;
  min-width: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
}

.small-multiple-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.small-multiple-title {
  margin: 0;
  min-width: 0;
  font: var(--type-title);
}

/* --- Il badge di tendenza -------------------------------------------------
   Verde, grigio, rosso: sono gli unici tre stati e non una scala, quindi i
   colori di stato dell'app bastano — nessuna tinta nuova. La freccia sta
   **prima** della parola e non al posto suo: il verso non deve vivere solo
   nella tinta, e chi non distingue il verde dal rosso legge «Migliora».

   La freccia arriva già formata da Rust (↗ ↘ →) e quei tre segni cadono
   fuori dal subset imbarcato di Barlow: li disegna il carattere di sistema.
   È accettabile perché sono segni geometrici e non lettere — accanto a un
   glifo condensato non stonano — ed è un altro motivo per cui la parola c'è
   sempre. --- */
.trend-badge {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
  white-space: nowrap;
}

.trend-arrow {
  line-height: 1;
}

.trend-migliora { background: var(--color-ok-bg); color: var(--color-ok-text); }
.trend-stabile { background: var(--color-bg); color: var(--color-text-muted); }
.trend-peggiora { background: var(--color-danger-bg); color: var(--color-danger-text); }

.stack-row {
  display: flex;
  align-items: flex-end;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.stack {
  flex: 1 1 0;
  min-width: 0;
}

/* Barre larghe e non colonnine: il numero dei giocatori sta **dentro** il
   segmento, e per starci il segmento deve essere largo quanto una parola.
   Con due rilevazioni la coppia riempie il riquadro; con cinque si
   stringono tutte insieme, ed è il momento in cui la tabella sotto smette
   di essere una ridondanza. */
.stack-bar {
  display: flex;
  flex-direction: column;
  height: 5.5rem;
  background: var(--lvl-empty);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.stack-segment {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Un giocatore su quattordici è un segmento alto sei pixel: la cifra non
     ci sta, e una barra con dei numeri e dei buchi si legge peggio di una
     barra senza numeri. Il minimo è l'altezza di una riga di testo. La
     distorsione che introduce è dichiarata dalla tabella qui sotto, che
     porta gli stessi numeri senza nessuna scala di mezzo. */
  min-height: 1.25rem;
  font: var(--type-figure-cell);
  font-variant-numeric: tabular-nums;
}

/* Sotto la barra il **periodo**, in maiuscoletto: fra due barre affiancate
   la domanda è in che mese si sta guardando, non come quella rilevazione
   era stata chiamata — il nome scelto dall'allenatore vive nel `title` del
   gruppo. Troncamento da `.t-clip`. */
.stack-label {
  margin-top: var(--space-2);
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
  text-align: center;
}

.stack-table {
  width: 100%;
  border-collapse: collapse;
  font: var(--type-caption);
  font-variant-numeric: tabular-nums;
}

.stack-table th,
.stack-table td {
  padding: var(--space-1);
  text-align: right;
  border-bottom: 1px solid var(--color-border);
}

.stack-table thead th {
  font: var(--type-thead);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--type-thead-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.stack-table thead th:first-child,
.stack-table th[scope="row"] {
  text-align: left;
}

/* Troncamento da `.t-clip`. */
.stack-table th[scope="row"] {
  font: var(--type-caption);
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
  max-width: 7rem;
}

/* L'ultima riga non porta filetto: sotto non c'è niente da separare, e la
   riga chiusa fa sembrare la tabella troncata. */
.stack-table tbody tr:last-child th,
.stack-table tbody tr:last-child td {
  border-bottom: 0;
}


/* --- §6.5: riquadri di sintesi ------------------------------------------
   L'unica superficie di queste viste che porta il colore di **famiglia** del
   tema, e può portarlo perché qui non c'è nessun livello colorato con cui
   confondersi. Il colore sta sul filetto e sul nome del tema, non a
   riempire un rettangolo: qui identifica un tema, e un rettangolo pieno si
   leggerebbe come uno stato.

   (v1.29) L'ordine di lettura è cambiato, ed è tutto il senso della
   revisione: prima il nome dell'area, poi la frazione grande, poi sei
   parole che dicono di che cosa è la frazione, poi il bottone. La frase
   lunga che c'era prima («4 giocatori su 12 valutati sono ancora sotto
   Applicare») era giusta ma andava letta tutta per arrivare al numero, e
   queste tre card si guardano di sfuggita per decidere quale toccare. --- */
.summary-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: var(--space-3);
  margin: var(--space-4) 0;
}

.summary-card {
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--fam, var(--color-primary));
  border-radius: var(--radius-md);
  padding: var(--space-3);
}

.summary-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
}

.summary-area {
  margin: 0;
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--fam, var(--color-text));
}

/* La quota in percentuale, nell'angolo. Pastiglia **neutra** e non in tinta
   di famiglia come nel mockup: tre delle sette famiglie sono tinte chiare
   (giallo, arancio, rosa), e un «33 %» giallo su fondo giallo diluito, a
   12 px, non si legge. Il colore di famiglia resta dove porta un
   significato — il filetto e il nome dell'area — e qui non manca a
   nessuno: la pastiglia è già identificata dalla card che la contiene. */
.summary-share {
  flex: none;
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--color-bg);
  color: var(--color-text-muted);
  font: var(--type-eyebrow);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--type-eyebrow-ls);
}

/* La frazione, non il solo numeratore: «4» da solo non dice niente, «4 / 12»
   è già un giudizio. Tabulari perché le tre card si leggono in fila e i
   numeri devono incolonnarsi anche quando non stanno in una colonna. */
.summary-figure {
  margin: var(--space-2) 0 0 0;
  font: var(--type-figure);
  font-variant-numeric: tabular-nums;
}

.summary-note {
  margin: 0 0 var(--space-3) 0;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.summary-note strong {
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
  color: var(--color-text);
}

/* Il bottone pieno, a tutta larghezza della card, con la freccia appoggiata
   al bordo destro. `margin-top: auto` lo incolla al fondo: le tre card
   hanno nomi di area di lunghezza diversa, e senza questa riga i tre
   bottoni finirebbero a tre altezze diverse — una fila di bersagli che non
   sono in fila.

   L'altezza minima di 44px arriva da `.btn` e non va ridotta: è il
   bersaglio da pollice. Il corpo scende invece a quello del bottone piccolo
   perché «Pianifica una seduta su Lettura del gioco e transizione» dentro
   17rem, a 16 px, sono tre righe. */
.summary-cta {
  margin-top: auto;
  width: 100%;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  font: var(--type-button-sm);
  text-align: left;
}

.summary-cta-arrow {
  flex: none;
}


/* --- Avanzamento di una rilevazione -------------------------------------- */

.progress-bar-col {
  width: 30%;
  min-width: 6rem;
}

.progress-track {
  height: 0.5rem;
  background: var(--lvl-empty);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  background: var(--color-primary);
}

/* --- Il form di compilazione --------------------------------------------
   Le scelte di livello sono radio e non una tendina: cinque bersagli
   visibili si toccano con il pollice a bordo pista, una tendina va aperta.
   La prima opzione è «non valutata», che è anche il valore iniziale — il
   form non deve mai suggerire un livello per riempire un vuoto. */
.area-block {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-bottom: var(--space-4);
}

.area-block > legend {
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
  padding: 0 var(--space-2);
}

.level-choice {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.level-option {
  margin: 0;
  cursor: pointer;
}

/* Il radio nativo resta nel DOM (accessibilità, tastiera, `:focus-visible`)
   ma non si vede: è il chip a fare da bersaglio, con un'area di tocco
   generosa come il resto dell'app. */
.level-option input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}

/* La scelta non selezionata è **desaturata**, non sbiadita: `opacity`
   avrebbe fatto sfumare anche il testo verso lo sfondo della pagina, e sul
   passo più scuro della rampa il bianco sarebbe sceso attorno a 2:1.
   `grayscale` cambia la tinta lasciando intatto il rapporto di luminanza
   fra testo e fondo, quindi tutte e cinque le opzioni restano leggibili e
   quella scelta si riconosce dal colore *e* dal bordo — mai dal solo
   colore. */
.level-chip {
  display: inline-block;
  padding: var(--space-2) var(--space-3);
  min-height: 2.75rem;
  line-height: 1.5rem;
  border-radius: var(--radius-pill);
  border: 2px solid transparent;
  font: var(--type-body-strong);
  filter: grayscale(0.85);
}

.level-chip-empty {
  background: var(--lvl-empty);
  color: var(--color-text-muted);
}

.level-option input:checked + .level-chip {
  filter: none;
  border-color: var(--color-text);
  font-weight: 700;
}

.level-option input:focus-visible + .level-chip {
  outline: 3px solid var(--color-primary);
  outline-offset: 2px;
}

.level-option input:disabled + .level-chip {
  cursor: default;
}

.area-note {
  display: block;
}

.sub-skills > summary {
  cursor: pointer;
  font: var(--type-title);
  color: var(--color-text-muted);
  padding: var(--space-2) 0;
}

.sub-skill-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: var(--space-2);
}

.sub-skill {
  font: var(--type-body);
  margin: 0;
}

.scale-help {
  margin-bottom: var(--space-4);
}

.scale-help > summary {
  cursor: pointer;
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
  padding: var(--space-2) 0;
}

.scale-list dt {
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
  margin-top: var(--space-2);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.scale-list dd {
  margin: 0 0 0 1.5rem;
  color: var(--color-text-muted);
}

.player-pager {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.player-pager-next {
  margin-left: auto;
}

/* --- Stampa --------------------------------------------------------------
   La scheda del singolo giocatore si stampa per il colloquio con i genitori
   (§8: si esporta il singolo, mai la matrice di squadra). I colori di sfondo
   vanno forzati, altrimenti la rampa sparisce e restano barre bianche. */
@media print {
  .profile-bar,
  .heatmap-cell,
  .stack-segment,
  .dumbbell-dot,
  .ramp-swatch,
  .level-chip {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  /* Il riquadro che scorre non ha senso su carta, e lasciarlo com'è
     stamperebbe la sola porzione visibile della matrice tagliando il resto.
     Le posizioni appiccicate vanno sciolte per la ragione gemella: su carta
     non c'è niente da cui staccarsi, e un'intestazione `sticky` in stampa
     esce sovrapposta alle prime righe. */
  .heatmap-scroll {
    overflow: visible;
    max-height: none;
  }

  .heatmap-table thead th,
  .heatmap-table .heatmap-name,
  .heatmap-table .heatmap-num {
    position: static;
    box-shadow: none;
  }

  /* Su schermo la tabella è larga quanto le sue colonne dichiarate e, se
     non ci sta, scorre. Su carta non c'è niente da far scorrere: la stessa
     regola stamperebbe la parte sinistra e taglierebbe il resto al margine.
     `width: 100%` con la disposizione **ancora fissa** lascia al foglio il
     compito di stringere le colonne, e le stringe tutte nella stessa
     proporzione — che è il punto: le celle restano uguali fra loro anche
     quando sono più strette che a monitor. Con `table-layout: auto`
     tornerebbero a inseguire la lunghezza dell'intestazione. */
  .heatmap-table {
    width: 100%;
  }

  /* Il tratteggio del «non pertinente» è un `background-image`, quindi
     `print-color-adjust: exact` più sopra lo porta sul foglio insieme ai
     fondi pieni. Se un driver lo mangia comunque resta il punto in mezzo
     alla cella, che è il motivo per cui il punto c'è. */

  /* Un bottone stampato è inchiostro speso per una cosa che non si può
     toccare (VISION §9). */
  .summary-cta,
  .player-pager {
    display: none;
  }
}

/* --- (v1.25) Tabella di inserimento rapido del roster ---------------------
   Il roster si compila una volta l'anno, tutto insieme, con la lista della
   società di fianco. Le celle sono strette perché la tabella deve stare in
   una schermata: se per vedere l'ultima riga bisogna scorrere, si perde il
   colpo d'occhio che è il motivo per cui la pagina esiste. */
.quick-add-table input[type="text"],
.quick-add-table input[type="number"],
.quick-add-table select {
  width: 100%;
  min-width: 0;
  padding: var(--space-1) var(--space-2);
}

.quick-add-table input[type="number"] {
  /* Anno, mese e maglia sono al massimo quattro cifre: lasciarli larghi
     quanto i cognomi ruberebbe spazio proprio alle colonne che ne hanno
     bisogno. */
  max-width: 6.5ch;
}

.quick-add-table td,
.quick-add-table th {
  padding: var(--space-1) var(--space-2);
}

/* Il numero di riga. Non è decorativo: è il numero che i messaggi d'errore
   usano per dire quale riga correggere («riga 7: manca l'anno di nascita di
   Marco Bernasconi»), e senza averlo davanti quel messaggio non aiuta. */
.quick-add-num {
  width: 2.5rem;
  text-align: right;
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

/* Le tre caselle dei ruoli su una riga sola: a U12 più di un ruolo è la
   norma, non l'eccezione, quindi si spuntano spesso in due. */
.quick-add-roles {
  white-space: nowrap;
}

.quick-add-roles label {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  margin-right: var(--space-2);
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
}

/* La riga di riempimento. Sfondo diverso dalle righe dati perché non è una
   riga di dati: niente di ciò che si scrive qui viene salvato da solo. */
.quick-add-fill td {
  background: var(--color-surface-2, var(--color-bg));
  border-bottom: 2px solid var(--color-border);
  vertical-align: middle;
}

.quick-add-fill-label {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
  text-align: right;
}

.quick-add-fill select {
  max-width: 11ch;
  margin-right: var(--space-1);
}

/* La riga che non passa e il suo motivo, subito sotto. Il colore da solo non
   basterebbe — chi distingue male i rossi vedrebbe quattordici righe uguali —
   quindi il messaggio è scritto per esteso sotto la riga che lo riguarda, e
   il bordo a sinistra serve a ritrovarla scorrendo. */
.quick-add-row-error td {
  background: var(--color-danger-bg);
}

.quick-add-row-error td:first-child {
  box-shadow: inset 3px 0 0 var(--color-danger-border);
}

.quick-add-error-message td {
  padding-top: 0;
  color: var(--color-danger-text);
  background: var(--color-danger-bg);
  font: var(--type-caption);
}

/* (v1.27) La riga che chiede «è lui?». Colore diverso dall'errore ed è
   voluto: non c'è niente di sbagliato in quello che è stato scritto, manca
   una risposta che solo chi conosce il gruppo può dare. */
.quick-add-question td {
  padding-top: 0;
  background: var(--color-info-bg, var(--color-surface-2, var(--color-bg)));
  font: var(--type-caption);
}

.quick-add-question select {
  margin-left: var(--space-2);
  max-width: 28ch;
}


/* --- (v1.25) Un'area, tutta la squadra -----------------------------------
   La trasposta del form per giocatore: quattordici righe con la stessa scala
   ripetuta. I chip qui portano il solo numero di livello — il nome per esteso
   moltiplicato per quattordici righe riempirebbe lo schermo di parole
   identiche, e la legenda in cima più il `title` di ogni chip dicono già cosa
   significano. Il numero resta perché la scala è ordinale e l'ordine è
   l'informazione. */
.level-choice-compact {
  flex-wrap: nowrap;
  gap: var(--space-1);
  margin-bottom: 0;
}

.level-choice-compact .level-chip {
  /* Quadrato e non pillola: cinque bersagli per riga per quattordici righe,
     e la pillola larga li porterebbe fuori schermo. L'altezza resta quella
     degli altri bersagli dell'app — a bordo pista si tocca col pollice, non
     col mouse. */
  min-width: 2.75rem;
  padding: var(--space-2) 0;
  text-align: center;
  border-radius: var(--radius-sm);
}

.area-grid td {
  vertical-align: middle;
}

.area-grid-name {
  display: block;
  font: var(--type-title);
}

/* L'anno e il mese sotto il nome. Piccoli e in grigio perché sono contesto,
   non un dato da confrontare: servono a ricordare, mentre si guarda una
   colonna di livelli, che fra gennaio e dicembre ci sono undici mesi di
   sviluppo (§2.3). */
.area-grid-age {
  display: block;
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.area-grid-note input[type="text"] {
  width: 100%;
  min-width: 14rem;
  padding: var(--space-1) var(--space-2);
}

.area-grid-details {
  display: block;
  margin-top: var(--space-1);
  font: var(--type-caption);
  color: var(--color-text-muted);
}

/* I due link fra un'area e la successiva. Larghi e in fondo: si arriva qui
   dopo aver percorso quattordici righe, e la cosa che serve subito dopo è
   l'area seguente. */
.pager {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.pager:has(> :only-child) {
  /* Una sola freccia — prima o ultima area — non deve stare al centro né
     saltare da un lato all'altro: resta dove starebbe se ci fossero
     entrambe. */
  justify-content: flex-start;
}

.pager > :last-child:not(:only-child) {
  margin-left: auto;
}


/* --- (v1.25) Selettore della passata: area o una delle sue sotto-voci -----
   Non è una modalità diversa, è la stessa tabella con una lente più stretta:
   quello che si osserva davvero è un'esercitazione di incroci, non «il
   pattinaggio». Un elenco di link e non un menu a tendina, perché le voci
   sono al massimo sette e si vogliono vedere tutte insieme — un menu
   chiederebbe un click per sapere cosa c'è dentro. */
.sub-switch {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.sub-switch-label {
  font: var(--type-eyebrow);
  letter-spacing: var(--type-eyebrow-ls);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.sub-switch-option {
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
  text-decoration: none;
  color: var(--color-text);
}

/* La passata attiva si riconosce dal fondo pieno *e* dal bordo, non dal solo
   colore: `aria-current` la dice a chi non vede la differenza di tinta. */
.sub-switch-option.is-current {
  background: var(--color-chip-bg);
  border-color: var(--color-chip-border);
  color: var(--color-chip-text);
  font: var(--type-label);
  letter-spacing: var(--type-label-ls);
}

/* Il nome della sotto-voce nel titolo, dopo quello dell'area: più leggero,
   perché è la lente e non il soggetto. */
.area-sub-title {
  color: var(--color-text-muted);
  font: var(--type-title);
}

/* Riga senza livello d'area: al posto dei chip, il motivo. Controlli che non
   salvano sarebbero peggio di nessun controllo. */
/* Il livello d'area accanto al nome durante una passata su una sotto-voce:
   contesto, quindi discreto, ma non invisibile — è il giudizio che si sta
   approfondendo. */
.area-grid-context {
  font: var(--type-caption);
  color: var(--color-text);
}

.area-grid-blocked {
  font: var(--type-caption);
  color: var(--color-text-muted);
}


/* --- (v1.25) Scheda del singolo giocatore ---------------------------------
   §8 del requisito: se serve condividere qualcosa in un colloquio si esporta
   la scheda del singolo, mai la matrice di squadra.

   Fuori da `@media print`, come il foglio G+S della seduta e per lo stesso
   motivo (§10bis... no: ARCHITECTURE.md §7.8septies): è la **visibilità** a
   decidere quando il foglio si vede — in stampa sempre, a schermo solo in
   anteprima — non il fatto che le regole esistano. Tutti i selettori sono
   `.player-sheet-*`, quindi finché `.print-only` resta `display: none` a
   schermo non cambia niente.

   Unità in `pt` e `cm`: sono unità fisiche e valgono anche a schermo
   (1cm CSS = 96dpi/2.54), ed è la ragione per cui l'anteprima può essere
   fedele senza una seconda resa. */
.player-sheet {
  font: var(--typep-body);
  line-height: 1.4;
  color: #000;
}

.player-sheet-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1cm;
  margin-bottom: 10pt;
  padding-bottom: 8pt;
  border-bottom: 1pt solid #000;
}

/* 6cm e non a tutta pagina: il logo ha il fondo nero, e una fascia larga
   21cm sarebbe mezza cartuccia spesa per una riga di identità. */
.player-sheet-logo {
  width: 6cm;
  height: auto;
  display: block;
}

.player-sheet-meta {
  text-align: right;
  font: var(--typep-caption);
  color: #444;
}

.player-sheet-name {
  display: flex;
  align-items: center;
  gap: 8pt;
  margin: 0 0 6pt;
  font: var(--typep-screen);
  line-height: 1.1;
}

.player-sheet-number {
  display: inline-block;
  min-width: 1.5em;
  padding: 1pt 5pt;
  border: 1.5pt solid #000;
  border-radius: 3pt;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* Anagrafica su due colonne che scorrono: etichetta grigia, valore nero.
   `dl` e non una tabella perché sono coppie nome/valore, ed è la struttura
   che una tecnologia assistiva legge come tali. */
.player-sheet-id {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2pt 10pt;
  margin: 0 0 10pt;
}

.player-sheet-id dt {
  color: #555;
  font: var(--typep-eyebrow);
  letter-spacing: var(--typep-eyebrow-ls);
}

.player-sheet-id dd {
  margin: 0;
}

.player-sheet-note {
  margin: 0 0 10pt;
  padding: 5pt 7pt;
  border-left: 2pt solid #000;
  font: var(--typep-caption);
}

.player-sheet-section {
  margin: 12pt 0 5pt;
  font: var(--typep-section);
  /* Il titolo non deve restare da solo in fondo a una pagina con la sua
     tabella che comincia sulla successiva. */
  break-after: avoid;
  page-break-after: avoid;
}

.player-sheet-table {
  width: 100%;
  border-collapse: collapse;
  font: var(--typep-body);
}

.player-sheet-table th,
.player-sheet-table td {
  text-align: left;
  vertical-align: top;
  padding: 3pt 6pt 3pt 0;
  border-bottom: 0.5pt solid #bbb;
}

.player-sheet-table thead th {
  border-bottom: 1pt solid #000;
  font: var(--typep-eyebrow);
  letter-spacing: var(--typep-eyebrow-ls);
  text-transform: uppercase;
  color: #444;
}

/* La barra **e** l'etichetta del livello, non l'una o l'altra: su carta in
   bianco e nero la rampa di colore non sopravvive, la lunghezza sì, e il
   testo regge da solo se anche quella si perde. */
.player-sheet-bar-track {
  display: inline-block;
  width: 2.2cm;
  height: 7pt;
  margin-right: 5pt;
  border: 0.5pt solid #999;
  vertical-align: middle;
}

/* Il riempimento lo porta la classe `.lvl-N` della rampa ordinale, come nel
   profilo a schermo: una sola tabella di colori per una sola scala. Qui
   c'era `background: currentColor`, che essendo scritto dopo vinceva sulla
   rampa e lasciava la barra vuota — la classe c'era ma non si vedeva.

   `print-color-adjust: exact` perché senza, molti browser stampano gli
   sfondi come bianco e la barra sparirebbe sul foglio. Non è decorazione: la
   lunghezza è il livello. Il livello è comunque scritto per esteso accanto,
   quindi anche una stampante che ignorasse tutto lascia la scheda leggibile. */
.player-sheet-bar {
  display: block;
  height: 100%;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

.player-sheet-empty {
  color: #777;
}

.player-sheet-free-note {
  margin: 0;
  font: var(--typep-caption);
}

.player-sheet-foot {
  margin-top: 14pt;
  padding-top: 5pt;
  border-top: 0.5pt solid #999;
  font: var(--typep-caption);
  color: #555;
}

/* In anteprima il foglio è A4 esatto, con i margini di `@page` resi come
   padding — stesso trattamento del foglio G+S. */
.sheet-preview .player-sheet {
  box-sizing: border-box;
  width: 21cm;
  min-height: 29.7cm;
  padding: 1.2cm 1.5cm;
  margin: 0 auto;
  background: #fff;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
}

/* --- (v1.26, PIANO-CATEGORIE.md Fase 2) Categorie d'età --- */

/* La fonte di ogni categoria, sotto la tabella. Corpo piccolo e a due
   colonne su schermo largo: è la riga che si legge una volta e poi mai più,
   ma che serve a chi fra due anni deve decidere se fidarsi dei numeri. */
.category-sources {
  margin: var(--space-4) 0 0;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

.category-sources dt {
  font-weight: 600;
}

.category-sources dd {
  margin: 0 0 var(--space-2);
}

/* Le due fasce di ammissione stanno affiancate, e la larghezza le tiene
   insieme: separate da un a-capo smetterebbero di leggersi come coppia. */
.admission-pair {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.admission-pair > * {
  flex: 1 1 12rem;
}

/* (v1.26, PIANO-CATEGORIE.md Fase 3b) L'avviso di fascia d'età. Corpo
   piccolo e a capo sotto il nome: è un'informazione da leggere una volta e
   poi confermare, non un errore da correggere — un ragazzo fuori fascia si
   tessera lo stesso, motivando. */
.admission-warning {
  display: block;
  font: var(--type-caption);
  color: var(--color-warning-text);
}

/* --- (v1.26, Fase 5) La vista persona --- */

/* Le stagioni in fila. Numerata nel markup (`<ol>`) perché è una sequenza
   temporale, ma senza i numeri a schermo: contarle non serve a nessuno, e
   una carriera non è una classifica. */
.person-timeline {
  list-style: none;
  padding: 0;
  margin: 0;
}

.person-season {
  border-left: 3px solid var(--color-border);
  padding: 0 0 var(--space-4) var(--space-4);
  margin: 0;
}

.person-season:last-child {
  padding-bottom: 0;
}

.person-season-head h2 {
  margin: 0;
  font: var(--type-title);
}

.person-season-team {
  margin: 0 0 var(--space-1);
}

.person-assessments {
  list-style: none;
  padding: 0;
  margin: var(--space-2) 0 0;
}

.person-assessments li {
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--color-border);
}

.person-assessments li:last-child {
  border-bottom: none;
}

/* Le righe di contesto — ruolo e stato di una stagione, data e squadra di
   una rilevazione — vanno a capo sotto il nome. Inline si incollavano al
   titolo che le precede e la riga diventava una frase sola senza pause. */
.person-season-line {
  display: block;
  margin: 0;
  font: var(--type-caption);
  color: var(--color-text-muted);
}

/* Il link alla scheda della persona, accanto al nome nel roster. Una freccia
   e non una parola: la riga è già piena, e «tutte le sue stagioni» starebbe
   su due righe in una colonna che ne ha una. Il titolo dice il resto. */
.roster-person-link {
  margin-left: var(--space-1);
  text-decoration: none;
  color: var(--color-text-muted);
}

/* ===========================================================================
   Il cancello (PIANO-CLOUD.md §A1)
   ===========================================================================

   `login.html` non estende `base.html` — niente header, niente sidebar,
   niente griglia `.app-shell` — quindi ha bisogno del proprio impianto di
   pagina, non solo di ritocchi. Sono poche regole: una porta chiusa deve
   avere una cosa sola su cui puntare lo sguardo.

   Il fondo è quello dell'app (`--color-bg`) e non un colore a sé: chi
   arriva qui deve riconoscere subito dove sta entrando, e una schermata di
   login che sembra un altro sito è il modo migliore per far dubitare che
   lo sia. */

.login-body {
  min-height: 100vh;
  /* `dvh` dove c'è: su iOS la barra degli indirizzi entra e esce dalla
     viewport, e con `100vh` la card salterebbe mentre si digita. */
  min-height: 100dvh;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-sans);
}

.login-card {
  width: 100%;
  max-width: 22rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  padding: var(--space-5);
  text-align: center;
}

/* Il logo porta già nome e tagline: qui non serve nessun titolo di testo
   accanto, per la stessa ragione per cui non ce n'è nella sidebar. */
.login-logo {
  width: 11rem;
  height: auto;
  margin: 0 auto var(--space-4);
  display: block;
}

/* La regola globale `form` (più in alto) dà a ogni form superficie, bordo e
   padding: dentro la card di login diventa una scatola dentro la scatola —
   due cornici concentriche attorno a un campo solo. Qui il form non è un
   blocco *dentro* una pagina, è il contenuto della card, quindi le tre
   proprietà si annullano invece di aggiungere una terza cornice. */
.login-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  text-align: left;
  background: none;
  border: none;
  border-radius: 0;
  padding: 0;
  margin-bottom: 0;
}

.login-form label {
  font-weight: 600;
}

.login-form input[type="password"] {
  width: 100%;
  box-sizing: border-box;
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  /* 16px pieni e non di meno: sotto i 16px iOS ingrandisce la pagina
     appena il campo prende il fuoco, e la card finisce fuori schermo
     proprio mentre si scrive. */
  font-size: 16px;
  font-family: inherit;
}

.login-form input[type="password"]:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}

.login-card .banner {
  text-align: left;
}

/* Sul telefono — che è il modo in cui questa pagina verrà aperta più spesso,
   a bordo pista — 2.5rem di padding per lato su una card da 22rem lasciano
   al campo meno spazio di quanto meriti l'unica cosa da fare qui. Si stringe
   la cornice, non il contenuto. */
@media (max-width: 26rem) {
  .login-body {
    padding: var(--space-3);
  }

  .login-card {
    padding: var(--space-4);
  }

  .login-logo {
    width: 9rem;
  }
}

/* Il logout vive nel piede della sidebar, dove stava la riga «Uso locale ·
   nessun login» che questo cancello ha reso falsa. Discreto di proposito:
   è un'azione che serve una volta ogni mai, e in una app usata da una
   persona sola un pulsante «esci» in evidenza è solo un modo di uscire per
   sbaglio. */
.sidebar-logout {
  margin-top: var(--space-2);
}

.sidebar-logout button {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  opacity: 0.75;
  text-decoration: underline;
  cursor: pointer;
}

.sidebar-logout button:hover,
.sidebar-logout button:focus-visible {
  opacity: 1;
}
