/**
 * BI Perspect - App Stylesheet
 * Single source of truth for all UI styles
 *
 * SECTIONS:
 * 1. Design Tokens (CSS Variables)
 * 2. Base & Reset
 * 3. Typography
 * 4. Layout & Sidebar
 * 5. Cards & Widgets
 * 6. Buttons
 * 7. Forms & Inputs
 * 8. Tables
 * 9. Badges, Pills, Chips
 * 10. Progress Bars
 * 11. Tabs
 * 12. Charts
 * 13. Alerts
 * 14. Utilities
 */

/* =============================================================================
   1. DESIGN TOKENS
   ============================================================================= */

:root {
    /* ═══ Palette definitiva — 11 colori ═══ */
    /* ⚠️ I NOMI sono quelli di sempre, i VALORI no: dal 07/09/2026 la palette e'
       quella del marchio Axerta (`#002C57` e `#4384F7`, letti dal tema di
       `axerta.it`), e i neutri sono virati sul freddo per stare con quel blu.
       I nomi non sono stati rifatti apposta: valgono 1.500 riferimenti fra
       `.php`, `.js`, `.css` e `.md`, e una rinomina di quella misura
       nasconderebbe dentro il proprio diff qualunque errore di colore. Il
       colore vero lo dice il commento accanto a ciascuno. Il solo nome
       davvero fuorviante e' `--jet`, che oggi e' un blu. */
    --snow:         #FAFBFD;   /* Bright Snow — sfondo body e widget */
    --platinum:     #F1F4F8;   /* Platinum — sfondo hover, header tabelle. Contro il
                                  bianco misura 1,103:1, cioe' la stessa zebratura di
                                  prima (1,101): la riga alternata non e' cambiata. */
    --alabaster:    #D5D5D5;   /* Alabaster Grey — bordi, divisori */
    --silver:       #A5AAAE;   /* Silver — bordi, campioni, e testo SOLO su fondo scuro:
                                  2,34 su bianco · 2,26 su --snow · 2,12 su --platinum.
                                  Su fondo chiaro non e' un testo, e' una decorazione. */
    --stone:        #657179;   /* Stone — testo muted leggibile, 87 usi (`text-ui-light`).
                                  Rimisurato l'11/09/2026 sui fondi di HEAD: 5,01:1 su
                                  bianco · 4,84 su --snow · 4,54 su --platinum.
                                  ⚠️⚠️ 4,54 e' 0,04 sopra il 4,5 di WCAG AA, e --bg-page
                                  E' --platinum: il caso peggiore e' quello normale, e il
                                  prossimo ritocco di un fondo lo fa cadere sotto.
                                  ⚠️ Qui stava scritto «5,43 · 5,24 · 4,92» con la
                                  motivazione che il candidato #64707F «dava 4,57 su
                                  --platinum: sopra la soglia ma senza margine». Erano i
                                  numeri del platinum VECCHIO, e rimisurati su quello
                                  nuovo (#F1F4F8, DeltaE 1,68) la conclusione si ROVESCIA:
                                  #64707F ne fa 4,57 e il colore scelto 4,54 — si e'
                                  preferito il margine minore credendo il contrario.
                                  La scelta va rifatta, ed e' un task: vedi
                                  [PALETTE-NUMERI-NON-RIMISURATI] in tasks/todo.md.
                                  Chi tocca un fondo chiaro rimisura QUESTA riga per
                                  prima. */
    --iron:         #5C6972;   /* Iron Grey — testo corpo su bianco. 5,65 su bianco ·
                                  5,45 su --snow · 5,12 su --platinum (diceva 7,08, che
                                  era il valore sul platinum vecchio). */
    --jet:          #050834;   /* Axerta Navy — titoli, bottoni, tab attivi, badge scuri.
                                  19,25 su bianco · 18,59 su --snow · 17,45 su --platinum
                                  (diceva 12,71, di nuovo il fondo vecchio: qui la palette
                                  nuova ha MIGLIORATO il contrasto, non peggiorato).
                                  E' il blu del marchio, non un nero. */
    /* Il quasi-nero che e' stato la sidebar dal 07/09 al 22/09/2026 (ΔE 7,4 da
       `--jet`: un solo scuro). Dal 22/09/2026 la sidebar e' `--navy`, su
       richiesta di Nicola, e questo token non lo usa piu' nessuno: resta per
       non perdere il valore, e chi lo riusa gli da' un ruolo in
       DESIGN_SYSTEM.md. */
    --jet-black:    #0D1030;
    --burgundy:      #93102D;   /* Burgundy — dark red */
    --cherry:        #C21B3F;   /* Intense Cherry — bright red */
    --dark-emerald:  #1B6B52;   /* Dark Emerald — dark green */
    --seaweed:       #1AAD7E;   /* Seaweed — bright green */
    --princeton:     #FB850F;   /* Princeton Orange — dal 07/09/2026 SOLO warning: era
                                   anche l'accento UI, e nel marchio Axerta l'arancio non
                                   esiste. Il posto di accento lo prende --azure. */
    /* Azure — il blu del marchio, preso tale e quale dal gradiente dei titoli di
       `axerta.it`. Fino al 07/09/2026 questo token portava `#7CB8F2`, cioe' una
       SCHIARITA di quel blu: a schermo l'accento risultava slavato e il blu vero
       del marchio non compariva da nessuna parte. Ora il chiaro ha un token suo
       (`--sky`) e questo e' il colore firmato.
       NON e' un testo: 3,57:1 su bianco e 3,24:1 su --platinum, sopra il 3 che
       WCAG chiede a un elemento non testuale e sotto il 4,5 di un testo. Serve
       come fondo, come tratto e come area di grafico; la variante che si legge
       e' `--azure-dark`. */
    --azure:         #4384F7;
    /* La schiaritura di --azure: il gradino CHIARO delle categorie nei grafici e
       le velature di superficie. 2,10:1 su bianco — area, mai tratto sottile. */
    --sky:           #7CB8F2;
    /* La versione di --azure che si puo' usare come TESTO e come PIENO sotto un
       testo bianco: i link, l'accento scritto, i bottoni pieni (`--btn-pieno`),
       lo stato scelto dei configuratori e i conteggi. Stesso rapporto che
       --princeton-dark ha con --princeton. I contrasti li scrive la tabella
       palette di DESIGN_SYSTEM.md, che il linter ricalcola: qui accanto
       erano 6,01 e 5,45, e misuravano 5,95 e 5,39. */
    --azure-dark:    #3A63A8;
    /* Il gradino SCURO delle categorie nei grafici. NON e' --jet: il navy del
       marchio ha luminanza 0,005 e una barra piena di quel colore pesa quanto
       una card scura — e' la ragione per cui i grafici si leggevano «troppo
       scuri». Questo ne ha 0,05, cioe' dieci volte tanto, e resta a 10,46:1 su
       bianco. ΔE 25,1 da --jet e 39,6 da --azure: sopra il pavimento di 20,8,
       quindi i tre blu dei grafici restano tre. */
    --navy:          #1F3D7A;

    /* Ambra scura — la versione di --princeton che si puo' usare come TESTO.
       Princeton nudo su fondo chiaro da' 2,2:1: sotto il 4,5:1 di WCAG per un
       testo, quindi su fondo chiaro serve solo come fondo o come tratto di un
       grafico. Questa da' 6,09:1 sul fondo del badge --warn. Sta fra i colori
       pieni e non fra le tinte trasparenti perche' e' un colore, non una
       derivazione in alpha. Scelta ambrata e non ruggine per non avvicinarsi
       a --cherry: warn e danger convivono nella stessa colonna. */
    --princeton-dark: #8A4B08;
    /* Tinte trasparenti (derivate dai 10 colori) */
    --dark-emerald-light: rgba(27, 107, 82, 0.12);
    --cherry-light: rgba(194, 27, 63, 0.12);
    --princeton-light: rgba(251, 133, 15, 0.12);
    --azure-light: rgba(67, 132, 247, 0.10);

    /* Base */
    --white: #ffffff;

    /* Elevazione — due sole misure, e si riusano.
       L'elevazione e' spenta globalmente (vedi `box-shadow: none !important`);
       queste servono ai pannelli che galleggiano su contenuto chiaro, dove il
       solo bordo da 1px misura 1,21:1 contro il fondo e non dice niente. */
    --elevazione-pannello: 0 8px 20px rgba(5, 8, 52, 0.12);
    --elevazione-tooltip:  0 4px 12px rgba(5, 8, 52, 0.10);
    /* L'ombra della CARD: 1px, 5% di nero navy. Non e' elevazione, e' il modo in
       cui una superficie bianca su un fondo chiaro dice di essere davanti senza
       che il bordo debba gridarlo — ed e' cio' che permette al filo di scendere
       da 1,468:1 a 1,231:1 senza che le card si sciolgano nella pagina. E'
       l'UNICA eccezione allo spegnimento globale delle ombre, insieme ai
       pannelli che galleggiano. */
    --elevazione-card:     0 1px 2px rgba(5, 8, 52, 0.05);

    /* Anello di focus — un colore solo per tutta l'applicazione.
       Jet da' 13,8:1 sui fondi chiari, contro il 3:1 che WCAG 2.2 chiede a un
       indicatore di focus. Si dichiara come `outline`, MAI come `box-shadow`:
       la regola globale piu' sotto spegne ogni ombra e un anello dichiarato
       cosi' non viene disegnato mai. */
    --focus-ring: var(--jet);

    /* Text */
    --text-heading: var(--jet);       /* Jet Black — titoli, testo su grigio/sidebar */
    --text-body: var(--iron);         /* Iron Grey — testo corpo su sfondo bianco */
    --text-muted: var(--iron);        /* Iron — testo secondario/muted (helpers, label uppercase) */
    --text-light: var(--stone);       /* Stone — testo terziario, placeholder, icone decorative */

    /* Gerarchia di etichetta e valore (22/09/2026, Nicola sul Profilo: «poca
       differenza tra label e testi», bottone del modulo piccolo). UNA scala per
       ogni coppia dell'applicazione — `ui-form-label`, `ui-campo-*`,
       `ui-field-row`, le righe di `ui-metrics--aligned` — letta da qui e da
       nessun'altra parte: il linter pretende questi token su quelle regole.
       L'etichetta nomina, il valore si legge: due punti di corpo, un gradino di
       peso e il passaggio da Iron a jet. L'aiuto sta sotto, al corpo
       dell'etichetta ma grigio chiaro e a peso normale. I contrasti sono nella
       tabella di DESIGN_SYSTEM.md § Gerarchia di etichetta e valore. */
    --etichetta-testo: 13px;
    --etichetta-peso: 500;
    --etichetta-colore: var(--iron);
    --valore-testo: 15px;
    --valore-peso: 600;
    --valore-colore: var(--jet);
    --aiuto-testo: 13px;
    --aiuto-colore: var(--text-light);
    /* Il campo e il bottone del suo modulo sono ALTI UGUALE: il bottone che
       chiude un modulo si legge come parte del modulo, non come un'etichetta
       appesa sotto. `.ui-btn-lg` e i bottoni dei piedi di modulo leggono questa. */
    --campo-altezza: 40px;
    --campo-testo: 15px;

    /* Primary = Axerta Navy, hover = Azure */
    --primary-dark: var(--jet);
    --primary: var(--jet);
    --primary-mid: var(--jet);
    --primary-soft: var(--silver);
    --primary-hover: var(--azure);
    --primary-light: rgba(5, 8, 52, 0.06);

    /* Il FONDO dei bottoni pieni (22/09/2026 sera — Nicola: «il blu scuro è
       molto scuro, rende tutto cupo, non usarlo nei bottoni»). Era `--primary`,
       cioè il navy del marchio, jet: la cosa più pesante di ogni schermata. Ora è
       il blu del marchio nella sua variante che regge il testo bianco, e al
       passaggio il navy dei grafici: più scuro del fondo, lontano dal jet. I testi
       scuri, la sidebar e le intestazioni restano `--primary`. */
    --btn-pieno: var(--azure-dark);
    --btn-pieno-hover: var(--navy);

    /* Accent = azure (accento UI, non finanziario) */
    /* ⚠️ Valeva `--princeton`, l'arancio: un accento che il marchio Axerta non
       ha. Dal 07/09/2026 e' il blu chiaro del marchio, e resta valida la regola
       che l'ha corretto il 20/08/2026 — la variante SCURA per cio' che si legge,
       la tinta piena solo per cio' che si guarda (sotto il 4,5 di un testo). */
    --accent-dark: var(--azure-dark);
    --accent: var(--azure);
    --accent-mid: var(--azure);
    --accent-hover: var(--azure);
    /* Si chiamava --accent-light e portava gia' Sky, non l'accento Azure:
       rinominato il 06/10/2026. Un velo --X-light porta la tinta di --X. */
    --sky-light: rgba(124, 184, 242, 0.10);

    /* Borders and surfaces */
    /* IL FILO. Era `--alabaster` (#D5D5D5), che contro il bianco misura 1,468:1:
       su una schermata fatta di card, tabelle, celle e campi diventa un reticolo
       di righe grigie che si vedono prima del contenuto. 1,231:1 e' abbastanza
       per dire dove finisce una superficie e non abbastanza per essere guardato.
       ⚠️ `--alabaster` resta e non e' un doppione: e' un COLORE (campione,
       gradino chiaro dei grafici, tratteggio della cella futura), questo e' un
       RUOLO. */
    --hairline: #E3E8EF;
    --border: var(--hairline);
    --border-hover: var(--alabaster);
    --border-focus: var(--iron);
    --bg-page: var(--platinum);       /* Platinum — sfondo pagina/body */
    /* La card e' BIANCA, la pagina e' --platinum. Erano --snow su --platinum,
       cioe' 1,05:1 fra le due superfici: la card non si staccava dalla pagina e
       tutto il lavoro di separazione restava al filo del bordo. Col bianco sono
       1,10:1 — poco, ed e' voluto: la separazione la fanno bordo e ombra, il
       fondo deve solo non contraddirli. */
    --bg-card: var(--white);
    --bg-hover: rgba(5, 8, 52, 0.04);
    --bg-subtle: var(--platinum);
    --bg-muted: var(--platinum);
    --bg-chip-doc: #E6EAF0;

    /* Table */
    --table-bg: var(--white);
    /* Zebratura e intestazione su --snow e non su --platinum: dentro una card
       BIANCA il platinum e' il colore della PAGINA, e una riga alternata di quel
       colore fa sembrare la tabella bucata. Contro il bianco la riga si distingue
       e non si conta (il contrasto sta nella tabella palette di DESIGN_SYSTEM.md). */
    --table-row-alt: var(--snow);
    --table-header-bg: var(--snow);
    --table-row-hover: rgba(5, 8, 52, 0.04);
    --table-font-size: 14px;
    --table-link-color: var(--jet);
    --table-link-hover: var(--iron);
    --table-focus-ring: rgba(5, 8, 52, 0.15);
    --table-page-active-bg: var(--iron);
    --table-page-active-text: var(--white);
    --table-header-text: var(--stone);

    /* Component accents */
    --loader-overlay-color: rgba(5, 8, 52, 0.15);
    --ai-card-bg-start: rgba(5, 8, 52, 0.04);
    --ai-card-bg-end: rgba(5, 8, 52, 0.02);
    --ai-card-border: rgba(5, 8, 52, 0.15);
    --ai-card-hover-shadow: rgba(5, 8, 52, 0.10);
    --ai-card-hover-border: rgba(5, 8, 52, 0.25);
    --ai-card-accent-start: rgba(5, 8, 52, 0.06);
    --ai-card-accent-end: rgba(5, 8, 52, 0.03);
    --ai-card-title-accent: var(--jet);
    --ai-card-chip-bg: rgba(5, 8, 52, 0.08);
    --ai-card-chip-text: var(--jet);

    /* Internal navigation */
    --internal-menu-bg: var(--platinum);
    --internal-menu-border: var(--border);
    --internal-menu-item-text: var(--stone);
    --internal-menu-item-hover-bg: rgba(5, 8, 52, 0.04);
    --internal-menu-item-hover-text: var(--jet);
    --internal-menu-active-bg: var(--jet);
    --internal-menu-active-text: var(--white);
    --internal-menu-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
    --internal-menu-focus-ring: var(--focus-ring);
    --internal-menu-focus-ring-on-active: var(--platinum);

    /* Sidebar — Navy dal 22/09/2026 (Nicola: «sidebar navy #1F3D7A»; era
       `--jet-black`). Ogni velo bianco qui sotto si compone su questo fondo:
       chi lo cambia rimisura TUTTE le coppie della barra, perche' il navy e'
       molto piu' chiaro del jet-black (testo bianco 10,46:1 contro 18,52). */
    --sidebar-bg: var(--navy);
    --sidebar-border: rgba(255, 255, 255, 0.08);
    --sidebar-text: rgba(255, 255, 255, 0.85);
    /* ⚠️ L'alpha e' 0,64: e' il testo attenuato (etichette di gruppo, terzo
       livello, contatore di spesa), e il caso peggiore e' il contatore, che
       sta sul velo `--sidebar-hover-bg`. Sul navy lo 0,52 del jet-black dava
       4,07:1 sulla barra e 3,67 sul contatore — sotto il 4,5 di un testo. A
       0,64: 5,31 sulla barra, 4,70 sul contatore.
       ⚠️ La sonda del contrasto qui non guarda: i selettori della sidebar
       stanno in `FONDI_SCURI`, dichiarati apposta perche' un testo chiaro su
       fondo scuro sarebbe indistinguibile da un difetto. Il che vuol dire che
       questi numeri si misurano a mano, ed e' il motivo per cui stanno scritti. */
    --sidebar-text-muted: rgba(255, 255, 255, 0.64);
    --sidebar-hover-bg: rgba(255, 255, 255, 0.06);
    --sidebar-tree-line: rgba(255, 255, 255, 0.12);
    /* Sidebar nav active card */
    /* LA SEZIONE aperta: un velo bianco leggero, **1,33:1** contro la barra.
       Poco, e va bene cosi': una sezione non e' dove si e', e' dentro cosa si
       sta.
       ⚠️ Vale anche per il terzo livello, che sta dentro una pagina gia'
       evidenziata dal suo secondo livello. */
    --sidebar-nav-active-bg: rgba(255, 255, 255, 0.10);
    --sidebar-nav-active-text: #ffffff;
    /* LA PAGINA su cui si e': lo STESSO velo bianco della sezione, il doppio
       piu' carico. **1,77:1** contro la barra navy, dove la sezione sta a 1,33.
       ⚠️ E' tono su tono per scelta, non per svista: la pastiglia piena
       dell'accento era stata provata il 07/09/2026 (sul jet-black di allora) e
       scartata — un blocco di colore dentro una barra scura pesa piu' della
       voce che deve segnalare; e sul navy l'Azure non arriverebbe nemmeno al
       3:1 (2,93). Quindi il numero qui NON arriva al 3:1 che WCAG chiede a un
       elemento non testuale, e a portare il segnale sono **tre** cose insieme:
       il fondo, il peso 600 e il testo bianco pieno (5,91:1 sul velo, contro
       lo 0,85 di alpha delle voci a riposo).
       ⚠️ Sezione e pagina si distinguono per INTENSITA', ed e' la sola cosa che
       le separa: chi tocca uno dei due veli rimisuri l'altro, o tornano a
       leggersi come due voci ugualmente attive. */
    --sidebar-pagina-bg: rgba(255, 255, 255, 0.20);
    --sidebar-pagina-text: #ffffff;
    /* Il FUOCO sulla barra: `--focus-ring` e' jet, e sul navy misura 1,84:1
       (sul jet-black 1,04) — un anello che non si vede. Bianco: 10,46 sulla
       barra, 5,91 sulla pastiglia della pagina. */
    --sidebar-focus-ring: var(--white);
    /* Il FILO dei contatori della barra: `--conteggio` (Mariner) sul navy
       misura 1,76:1 e sulla pastiglia della pagina 1,01 — il badge si
       confondeva col fondo. Il numero bianco sul blu resta 5,95; il filo
       bianco lo stacca (10,46 sulla barra, 5,91 sulla pastiglia). */
    --sidebar-conteggio-filo: var(--white);

    /* Semantic — green/red ONLY for financial meaning */
    --income: var(--seaweed);      /* #1AAD7E */
    --expense: var(--cherry);  /* #C21B3F */
    --success: var(--seaweed);        /* #1AAD7E */
    --danger: var(--cherry);         /* #C21B3F */
    --warning: var(--princeton);     /* #FB850F */
    --info: var(--silver);
    /* ⚠️ Le varianti SCURE, non le tinte piene: questi tre token si usano solo
       come `color` e `border-color` — mai come fondo — e seaweed/princeton su
       chiaro danno 2,59 e 2,39, cioe' sotto il 4,5 del testo e sotto il 3 che
       WCAG chiede a un bordo di stato. Cambiarli qui sistema in un colpo i
       feedback dei form, le icone dentro i campi e i bordi is-valid/is-warning. */
    --ui-success: var(--dark-emerald);
    --ui-danger: var(--cherry);
    --ui-warning: var(--princeton-dark);

    /* Lo STATO SCELTO dei configuratori (Campagne, Liste lead, Professionisti):
       passo corrente, tessere, segmenti, pastiglie, cursori, caselle degli
       alberi. Tre giri lo stesso giorno, il 22/09/2026, tutti su richiesta di
       Nicola: Princeton (l'arancio, «No arancioni»), poi Iron («tieni shuttle
       grey»: le caselle spuntate grigie si leggevano spente), poi il blu dei
       bottoni pieni («sta meglio questo blu chiaro»). Il token è `--btn-pieno`
       per riferimento e non per valore: scelta e comando pieno sono lo stesso
       blu, e se quel blu cambia cambiano insieme.
       Il pieno regge il testo bianco e il filo regge da sé su ogni fondo chiaro
       (i contrasti li ricalcola `scelto_senza_contrasto()` del linter, che
       sta al posto dei numeri qui accanto). Il velo è lo stesso blu al 12%,
       scritto in rgba perché una `var()` non prende un'alpha: il linter
       pretende che il suo rgb sia quello di `--scelto`, e che titoli e testo
       corpo sopra il velo composto sul bianco arrivino a 4,5.
       I controlli nativi (le caselle degli alberi) prendono il filo come
       `accent-color`. Il fuoco resta `--focus-ring`, un anello staccato di
       2 px: un'altra forma. */
    --scelto: var(--btn-pieno);
    --scelto-filo: var(--btn-pieno);
    --scelto-testo: var(--white);
    --scelto-velo: rgba(58, 99, 168, 0.12);

    /* I CONTEGGI che si leggono come notifica — il numero in una pastiglia
       piena: quanti criteri sono attivi in un gruppo o in un ramo chiuso,
       quanti filtri in una tendina, i contatori della sidebar e del menu
       interno. Stesso blu dello stato scelto (Nicola, 22/09/2026 sera: «anche i
       numeri nelle notifiche»), token a parte perché dice un'altra cosa: un
       numero che chiede attenzione, non una scelta. */
    --conteggio: var(--btn-pieno);
    --conteggio-testo: var(--white);

    /* Accent color for native controls */
    accent-color: var(--iron);

    /* «Unified radius» erano tre token con lo stesso valore scritto tre volte:
       unificati nel nome, non nel numero. Adesso derivano da --radius-md, cosi'
       il gradino si cambia in un posto solo. */
    --sidebar-active-radius: var(--radius-md);
    --interactive-radius: var(--radius-md);
    --badge-radius: var(--radius-md);

    /* Typography */
    /* ⚠️ DUE famiglie, e `--font-display` ha UN SOLO consumatore: `.page-title`,
       l'h1 della pagina. Nient'altro.
       Outfit e' il carattere di `axerta.it` (variabile, 100-900) ed e'
       geometrico: bello a corpo grande, peggiore di Inter appena il testo si
       fa piccolo, fitto o maiuscoletto. Un primo giro lo aveva messo anche su
       titoli di widget (11 px uppercase), etichette KPI, voci di menu e
       bottoni, e li' si legge peggio — il marchio non vale la leggibilita' del
       testo che si consulta tutto il giorno.
       Il titolo di pagina invece e' grande, corto e **uno solo per schermata**:
       superficie giusta per un marchio e abbastanza isolata da non costare
       niente a nessun altro.
       `--font-sans` (Inter) e' tutto il resto; `--font-mono` (Roboto Mono) i
       numeri. */
    --font-display: 'Outfit', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --font-mono: 'Roboto Mono', monospace;

    /* Spacing - Premium (more generous) */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 20px;   /* ← Up from 16px */
    --space-5: 28px;   /* ← Up from 20px */
    --space-6: 36px;   /* ← Up from 24px */
    --space-8: 48px;   /* ← Up from 32px */

    /* Il respiro interno di una card. Token suo e non `--space-4`, che vale
       anche per `.p-4`, i margini e il piede della sidebar: cambiare la scala
       di spaziatura per stringere una card muove mezza applicazione.
       Le regole che lo usano portavano lo stesso valore ripetuto, una a mano e
       le altre dal token sbagliato. Una delle tre era .widget-card.widget-compact,
       tolta l'08/08/2026 perche' dichiarava fondo e padding IDENTICI a quelli che
       .widget-card gia' applica: 656 proprieta' calcolate confrontate, 0 differenze. */
    --widget-padding: 16px;

    /* La larghezza MINIMA di una colonna di dato — una coppia etichetta/valore,
       una riga di recapito. Sotto questa, etichetta e valore non ci stanno più
       affiancati e la griglia deve togliere una colonna.
       ⚠️ È un minimo e non più un tetto, dal 21/08/2026: il tetto serviva
       quando il valore era appeso al bordo destro, perché lì la larghezza della
       colonna ERA la distanza dall'etichetta (244-367 px misurati). Con
       l'etichetta su colonna fissa quella distanza non dipende più da quanto è
       larga la colonna, quindi il tetto costava soltanto — lasciava 274 px di
       card inutilizzati a destra e i fili delle righe tagliati a metà. Ora le
       colonne prendono `1fr` e sono `auto-fill` a decidere quante:
       quattro su una card a piena larghezza, due a 768, una a 500. */
    --colonna-dato: 320px;

    /* La colonna dell'ETICHETTA dentro una riga di dato. Tiene i valori
       incolonnati fra loro — che è il motivo per cui esiste una colonna fissa
       invece di `max-content` per riga — ed è dimensionata sull'etichetta più
       lunga in uso, «Ragione sociale verificata», CON la sua icona: l'asse dei
       valori è UNO SOLO, righe larghe e righe corte cominciano dalla stessa
       colonna, e una riga larga con l'etichetta a `max-content` avrebbe
       spostato il suo valore altrove, dando tre inizi diversi in una card.
       ⚠️ Era tarata sul solo testo, e il testo non è l'etichetta: a 168 px
       l'etichetta andava a capo su due righe (alta 39 invece di 20). Misurato
       il 06/10/2026 a 1440 con `white-space: nowrap`: testo 156, icona 12, gap
       6 — cioè 174 — più un margine di 10 per le differenze di resa del
       carattere fra macchine. */
    --etichetta-dato: 184px;

    /* Radius - Corporate: tight, minimal.
       ⚠️ Il 07/09/2026 la scala e' stata alzata di un gradino (5/6/8/10/14 →
       6/8/10/12/16) per avvicinarsi al `--border-radius: 10px` che `axerta.it`
       dichiara sui propri blocchi, e **rimessa com'era lo stesso giorno**: a
       schermo l'insieme risultava troppo tondo. La lezione non e' il numero, e'
       che il marchio del sito non si trasferisce per proporzione — quella pagina
       ha blocchi grandi e pochi, questa ha tabelle, badge e comandi fitti, e lo
       stesso raggio su elementi piccoli e vicini legge come morbidezza invece
       che come cura. Il colore si porta, la geometria si rimisura. */
    --radius-xs: 4px;
    --radius-sm: 5px;
    --radius-md: 6px;
    --radius-lg: 8px;
    --radius-xl: 10px;
    --radius-2xl: 14px;

    /* Lo stacco fra un avviso e il contenuto che segue. In una variabile perche'
       lo dichiarano DUE regole — l'avviso fratello del contenuto e lo slot che
       ne contiene uno solo — e due letterali divergono alla prima modifica. */
    --spazio-avviso: 16px;
    /* Il respiro verticale di una cella. In una variabile perche' `th` e `td`
       devono muoversi INSIEME: se divergono, l'intestazione e le righe si
       disallineano in altezza e la tabella sembra storta senza che nulla lo
       dica. Alzato da 0,75rem il 25/08/2026 — le righe erano 49 px e la
       scheda si leggeva compressa. */
    --cella-y: 0.9rem;

    /* Elevations - Corporate: subtle, nearly flat */
    --elevation-1: 0 1px 2px rgba(0, 0, 0, 0.04);
    --elevation-2: 0 2px 4px rgba(0, 0, 0, 0.06);
    --elevation-3: 0 4px 8px rgba(0, 0, 0, 0.08);
    --elevation-4: 0 8px 16px rgba(0, 0, 0, 0.10);

    /* Curve di casa, e sono DUE. `ease` e' un in-out debole: parte lento proprio
       nell'istante in cui si guarda. Chi entra o esce usa --ease-out, chi si
       muove restando a schermo --ease-in-out.
       Qui accanto ce n'erano altre tre — --transition-smooth, --ease-out-expo,
       --ease-in-out-circ — con UNA occorrenza ciascuna, cioe' la propria
       dichiarazione: un elenco di curve che nessuno usava e da cui il prossimo
       avrebbe scelto credendole di casa. */
    --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
    --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);

    /* La durata piu' comune, non una terza curva: prima ripeteva --ease-out
       alla lettera, quindi cambiare la curva di casa ne lasciava indietro
       nove usi. */
    --transition: 200ms var(--ease-out);

    /* Tracking per gradino di dimensione. Il testo grande legge troppo largo
       se non lo si stringe, il minuscolo troppo stretto se non lo si allarga:
       un solo letter-spacing per tutta la pagina e' sbagliato da qualche parte.
       In prova nella palestra (?page=template#tipografia): i 35 valori
       esistenti non sono ancora ricondotti qui. */
    --tracking-display: -0.02em;   /* >= 24px: titoli, valori KPI */
    --tracking-body: -0.01em;      /* 13-16px: corpo, celle — e' cio' che `body` applica */
    --tracking-label: 0.08em;      /* <= 11px maiuscoletto: etichette, badge */

    /* ⚠️ Qui stavano `--sidebar-width: 240px` e `--sidebar-collapsed: 72px`,
       usciti il 14/09/2026: **zero consumatori**, e il primo dichiarava anche un
       numero falso — la barra misura **260 px** (72 compressa), scritti nelle
       regole di `.sidebar-container` e nello stile anti-lampeggio di
       `header.php`. Un token che nessuno legge non e' una costante: e' una
       didascalia che invecchia da sola, e questa mentiva di 20 px. */
}

/* =============================================================================
   2. BASE & RESET
   ============================================================================= */

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

body {
    font-family: var(--font-sans);
    font-size: 14px;
    line-height: 1.5;
    color: var(--text-body);
    background: var(--bg-page);
    letter-spacing: -0.01em;   /* ← Minimal: Slightly less tight */
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* Prevent content overflow in widgets and tables */
.widget-card,
.overflow-x-auto,
.ui-tabs,
.ui-tab-panels,
.ui-tab-panel {
    min-width: 0;
}

/* Text overflow utilities */
.truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Cella di tabella con un testo lungo: due righe, poi ellissi. Serve dove una
   colonna testuale allargherebbe la tabella oltre il suo contenitore — con
   `table-layout: auto` la colonna cresce fino al contenuto e nessuna larghezza
   dichiarata sul `td` la ferma.
   ⚠️ La clamp sta sullo SPAN, non sul `td`: `display: -webkit-box` su una cella
   le toglie il `display: table-cell` e la tabella si scompone. Il `max-width`
   sullo span basta a limitare la colonna, perche' la larghezza di colonna la
   decide il contenuto piu' largo. */
/* ── Selezione a spunta dentro una lista ─────────────────────────────────
   La colonna della spunta NON e' una colonna del registro: non ha etichetta,
   non si ordina e non finisce nel CSV. Si dichiara nel markup con
   `data-colonna-controllo`, e `verifica-elenchi.php` la salta confrontando
   `<thead>` e registro — un'eccezione dichiarata, non dedotta.
   La larghezza e' fissa perche' una colonna di sole caselle non deve
   contendere spazio ai dati: 36 px sono la casella (16) piu' il respiro. */
.ui-col-check {
    width: 36px;
    padding-right: 0;
}

.ui-col-check input[type="checkbox"] {
    display: block;
    margin: 0;
    cursor: pointer;
    accent-color: var(--jet);
}

/* La barra che compare quando qualcosa e' selezionato. Sta SOPRA la tabella e
   non nell'intestazione del widget: e' uno stato dell'insieme, non un comando
   permanente, e sparisce quando la selezione si svuota. */
.ui-selezione-barra {
    display: flex;
    align-items: center;
    gap: 12px 20px;
    flex-wrap: wrap;
    padding: 12px 16px;
    border-radius: var(--radius-md);
    background: var(--bg-muted);
    border: 1px solid var(--border);
    margin-bottom: 16px;
}

.ui-selezione-conta {
    font-size: 13px;
    font-weight: 600;
    color: var(--text-heading);
}

.ui-selezione-azioni {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-left: auto;
    min-width: 0;
}

/* Il filetto che stacca l'uscita dalle azioni. Senza, «annulla» sta in fila
   con «arricchisci» e si legge come una terza cosa da fare. */
.ui-selezione-divisore {
    width: 1px;
    height: 20px;
    background: var(--border);
    margin: 0 4px;
    flex-shrink: 0;
}

.ui-cell-clamp {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    max-width: 260px;
}

/* =============================================================================
   3. TYPOGRAPHY
   ============================================================================= */

h1, h2, h3, h4, h5, h6 {
    color: var(--text-heading);
    font-weight: 600;
    line-height: 1.3;
}

/* Il tracking e' funzione della DIMENSIONE, non del tag: `--tracking-display`
   vale dai 24px in su, e in casa ci arrivano solo h1 e h2 (h3-h6 stanno fra 14
   e 20). Scritto su tutti e sei stringerebbe proprio i titoli piccoli, dove il
   difetto e' l'opposto. Outfit e' geometrico e a corpo grande legge largo:
   `axerta.it` rende il suo h1 cosi'. */
h1, h2 {
    letter-spacing: var(--tracking-display);
}

/* ⚠️ Il link era `--princeton` NUDO, cioe' l'arancio pieno: 2,48:1 su bianco,
   la meta' del 4,5 che WCAG chiede a un testo, ed era vero anche prima del
   restyling — la regola «la tinta piena non e' un colore da testo» era scritta
   e questa riga la violava. Ora e' la variante scura dell'accento (6,01:1 su
   bianco, 5,45 su --platinum). */
a {
    color: var(--accent-dark);
    text-decoration: underline;
}

a:hover {
    color: var(--accent-dark);
    text-decoration: none;
}

code {
    font-family: var(--font-mono);
    font-size: 0.9em;
    color: var(--iron);
}

/* Sidebar Brand */
.sidebar-brand {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-5);
}

.sidebar-brand-text {
    color: var(--sidebar-text);
    font-weight: 600;
    font-size: 14px;
    white-space: nowrap;
}

/* Sidebar User */
.sidebar-user {
    padding: var(--space-4);
    border-top: 1px solid var(--sidebar-border);
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.sidebar-user-info {
    flex: 1;
    min-width: 0;
}

/* =============================================================================
   5. CARDS & WIDGETS
   ============================================================================= */

/* Widget Variants */
.widget {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    border-left: 3px solid var(--iron);
    padding: var(--space-5);
}

/* Widget Card - Corporate Flat */
.widget-card {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: var(--widget-padding);
    overflow: visible;
    box-shadow: var(--elevazione-card);
    transition: border-color var(--transition);
}

/* Il RITMO VERTICALE dentro una card.

   Due `.widget-body` consecutivi sono due blocchi diversi — una banda di
   metriche, due grafici, una tabella — e fino al 21/08/2026 stavano attaccati:
   misurate **nove** coppie consecutive in tutta l'applicazione, tutte a
   `margin-top: 0` e `padding-top: 0`. Sul Rapporto commerciale i grafici
   toccavano l'intestazione della tabella, e le etichette dell'asse X si
   leggevano come una riga di quella.

   36 px (`--space-6`) perché dev'essere nettamente più del respiro INTERNO di
   un blocco, che vale 8 (didascalia → grafico): la gerarchia si legge dal
   rapporto fra le due distanze, non dal valore assoluto.

   ⚠️ `:not(.hidden)` sul PRIMO dei due: un corpo nascosto non occupa spazio, ma
   il combinatore `+` lo vede lo stesso, e il blocco dopo prenderebbe 36 px da
   un fratello che a schermo non c'è. Sulla scheda anagrafica quel fratello
   esiste (`#vuotoRapporto`), quindi non è un caso teorico.
   ⚠️ Il paginatore è escluso: non è un blocco, è la coda della sua tabella, e
   `.ui-paginator` porta già un filo e 12 px di suo. */
.widget-card > .widget-body:not(.hidden) + .widget-body:not(.hidden):not(.ui-paginator-mount):not(.ui-coda-blocco) {
    margin-top: var(--space-6);
}

/* ⚠️ La riga d'identità di una scheda (`ui-scheda-testata`) è il SOTTOTITOLO
   della voce, non un blocco fra i blocchi: a 36 px dai dati verificati si
   staccava da ciò che introduce, e la card Anagrafica aveva tre passi diversi
   (20 · 36 · 20). A 20 la scala della card è una sola: 20 sotto
   l'intestazione, 20 fra identità, fascia e righe. Il selettore ripete quello
   del ritmo generale con un `:has()` in più, perché deve vincerlo. */
.widget-card > .widget-body:has(> .ui-scheda-testata):not(.hidden) + .widget-body:not(.hidden):not(.ui-paginator-mount):not(.ui-coda-blocco) {
    margin-top: var(--space-4);
}

/* Il contenitore in cui `BiUI.paginate` monta la barra. Era `widget-body pt-0`
   scritto a mano in nove viste: un nome invece di un'utility, così il ritmo fra
   blocchi sa di doverlo saltare. Non prende nemmeno i 12 px della coda:
   `.ui-paginator` porta già un filo e 12 px di padding suo. */
.ui-paginator-mount {
    padding-top: 0;
}

/* La CODA di un blocco: il comando che agisce su ciò che sta sopra — il modulo
   per aggiungere una riga alla lista che precede. Non è un blocco a sé, quindi
   non prende i 36 px del ritmo; ma non è nemmeno attaccato, perché è un'altra
   cosa dalla lista. ⚠️ Senza, il modulo «Aggiungi figura chiave» stava 36 px
   sotto la tabella e in mezzo restava una fascia vuota che lo staccava da ciò
   su cui agisce: a schermo si leggeva come un comando della card, non della
   lista. `.ui-paginator-mount` è il caso particolare a zero. */
.ui-coda-blocco {
    margin-top: var(--space-3);
    padding-top: 0;
}

.widget-card:hover {
    border-color: var(--border-hover);
}

/* Widget Header - Metabolis Style: Flat, no glassmorphism */
.widget-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    /* Va a capo invece di sfondare. Senza, a 500 px l'intestazione di «Clienti
       a cui manca il servizio accanto» misurava 415 su 394 disponibili e
       trascinava fuori tutta la card — 21 px che sul telefono diventano una
       barra di scorrimento orizzontale su un widget.
       Il wrap non cambia niente finche' ci si sta: scatta solo quando il
       titolo e i comandi non entrano sulla stessa riga. */
    flex-wrap: wrap;
    row-gap: var(--space-2);
    /* 20 px (`--space-4`) e non 12: l'intestazione di una card è una
       maiuscola micro e il primo blocco spesso ne porta un'altra — a 12 px
       le due si leggevano come una riga sola. Il ritmo interno della card
       è 8 dentro un blocco · 20 sotto l'intestazione · 36 fra blocchi. */
    margin: 0 0 var(--space-4) 0;
    padding: 0;                       /* allineato al widget-body (padding 0) — no rientro titolo */
    background: transparent;          /* ← Metabolis: No background */
    /* No blur, no border-bottom */
}

/* Il titolo e' l'elemento ELASTICO dell'intestazione, e la sua base e' zero.
   ⚠️ In un flex che va a capo la riga si spezza sulla dimensione IPOTETICA dei
   figli — `flex-basis: auto`, cioe' il contenuto — non su quella a cui si
   restringerebbero: un titolo da 183 px accanto a una «i» da 16 in 172 px
   disponibili mandava la «i» a capo, e l'intestazione passava da 20,5 a 45 px.
   In una riga di KPI succedeva a tre card su quattro, e i valori cadevano 24 px
   piu' in basso di quelli accanto (misurato il 22/09/2026 su
   `?page=portafoglio&view=segmenti` e `view=ritenzione` a 1280 px).
   Con la base a zero il titolo non forza mai il capo: prende lo spazio che
   resta e, se non basta, si tronca con l'ellissi che `.widget-title span`
   dichiara gia'. `flex-wrap` sull'intestazione resta per il caso vero — un
   blocco di comandi che da solo non ci sta. */
.widget-header .widget-title {
    margin-bottom: 0;
    flex: 1 1 0;
}

/* Widget Body - Corporate Flat */
.widget-body {
    background: transparent;
    border-radius: 0;
    padding: 0;
    box-shadow: none;
    /* Container per `.ui-campi`: le sue due colonne devono rispondere alla
       larghezza REALE del corpo, non a quella del viewport — la stessa card
       misura 546 px o 338 px dentro lo split 1/2+1/2 e 828 px sotto la sua
       rottura a `lg`, e un `@media` guarda solo il viewport. */
    container-type: inline-size;
}

.widget-body canvas,
.widget-body .ui-chart-container {
    overflow: hidden;
}

/* Text list block (component: text-blocks -> text-list) */
.ui-text-list {
    font-size: 12px;
    line-height: 1.4;
}

/* Widget Title - Corporate Monochrome */
.widget-title {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    color: var(--iron);
    margin-bottom: 10px;
    line-height: 1;
    /* Flex item dentro .widget-header: senza questo non si restringe sotto il proprio
       contenuto e l'ellipsis dichiarata sullo span non scatta mai. */
    min-width: 0;
}

.widget-title i {
    font-size: 12px;
    color: var(--iron);
    flex-shrink: 0;
}

.widget-title i.bi-info-circle {
    color: var(--iron);
}

.widget-title span {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    /* Idem: l'ellipsis su un flex item richiede min-width 0, altrimenti il titolo sfora
       la card invece di troncarsi. Si vedeva a 1024 sui widget in colonna da 1/3. */
    min-width: 0;
}

/* L'INTESTAZIONE DI SEZIONE di una scheda-documento (la scheda voce): 13 · 700
   jet, maiuscoletto e spaziatura di `widget-title`. Il titolo di card globale
   (11 · 600 Iron) resta com'è sulle 228 testate dell'app, dove il protagonista
   è il valore del KPI e il titolo è una didascalia. Su una scheda che si LEGGE
   invece le card sono sezioni di un documento, e a 11 px grigi il titolo
   pesava meno di ogni valore sotto di lui (15 · 600) e del bottone pieno
   accanto: misurato il 06/10/2026, la card Anagrafica non aveva un'intestazione
   che si leggesse come tale. Vince sulla riduzione a 9 px sotto i 640 px (due
   classi contro una): lì il titolo di sezione serve di più, non di meno. */
.widget-title.ui-titolo-sezione {
    font-size: 13px;
    font-weight: 700;
    color: var(--jet);
}
.widget-title.ui-titolo-sezione i { font-size: 13px; }

/* Il FUOCO sul titolo. I configuratori a passi spostano il fuoco sul titolo del
   passo mostrato (`tabindex="-1"` + `focus()`, Campagne · Liste lead ·
   Professionisti): senza una regola propria quel titolo disegnava l'anello del
   BROWSER — `rgb(0, 95, 204) auto 1px`, un azzurro che nella palette non esiste
   — steso per tutta la larghezza della card, dove si legge come un campo di
   testo invece che come un'intestazione raggiunta. L'anello e' quello di casa
   (`--focus-ring`, 2 px staccati di 2) e sta stretto al TESTO: il titolo e' un
   blocco largo quanto la card, il suo `<span>` e' largo quanto la parola.
   Col mouse non compare (`:focus-visible`), quindi riguarda solo chi naviga da
   tastiera — ed e' l'unico segno che gli dice dov'e' arrivato. */
.widget-title:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}
.widget-title:focus-visible:has(> span) { outline: none; }
.widget-title:focus-visible > span {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
    border-radius: 2px;
}


/* =============================================================================
   KPI WIDGET VARIANTS

   .kpi-widget  - Standard KPI (header+body, left-aligned, 3-4 per row)
   .kpi-mini    - Compact KPI (inline, no header/body, 5+ per row)
   ============================================================================= */

/* KPI Widget - Standard left-aligned with header+body */
.widget-card.kpi-widget {
    display: flex;
    flex-direction: column;
}

/* KPI Widget with highlight - Corporate subtle */
.widget-card.kpi-widget.highlight {
    box-shadow: none;
    border-color: var(--alabaster);
}

.kpi-widget .widget-body {
    flex: 1;
    display: flex;
    flex-direction: column;
}

.kpi-widget .kpi-value {
    font-size: 2.75rem;        /* 44px — editorial hero */
    font-family: var(--font-mono);
    font-weight: 500;
    color: var(--jet);
    line-height: 1;
    letter-spacing: -0.03em;
}
/* IL NUMERO NON PRENDE COLORE, LO PRENDE LA SUA DIDASCALIA.
   Un KPI e' 44px: colorarlo mette in pagina una macchia rossa o verde grande
   quanto un titolo, e con quattro KPI affiancati la schermata si legge come un
   semaforo prima che come dei numeri. Il segno economico pero' non si perde —
   scende sulla riga sotto, dove «3.663.025 € non riconfermati» e' gia' la frase
   che lo dice, e dove 13px di colore bastano a qualificarla.
   ⚠️ Le due classi restano scritte sul VALORE nel markup: e' li' che il server
   sa il segno, e spostarle vorrebbe dire toccare ogni vista. Qui il valore le
   neutralizza e il fratello adiacente le raccoglie.
   Contrasti sul fondo bianco della card: --dark-emerald 5,97:1, --cherry
   5,96:1, entrambi sopra il 4,5 richiesto a un testo di 13px. */
/* ⚠️ SENZA `.kpi-widget` davanti, e non e' una svista. Le due regole che stavano
   qui prima erano `.kpi-widget .kpi-value.text-*` e sul cruscotto vero NON si
   applicavano: le card dei KPI del Portafoglio non portano `.kpi-widget`
   (`closest('.kpi-widget')` misurato `false` su tutti e quattro), quindi il
   colore arrivava dall'utility `text-cherry` del CDN e la regola di casa non
   ha mai deciso niente. Diceva il vero per caso: il valore CHE colore avesse lo
   sceglieva Tailwind. Con due sole classi (0,2,0) vince sull'utility (0,1,0)
   ovunque il markup la scriva, dentro o fuori una `.kpi-widget`. */
.kpi-value.text-dark-emerald,
.kpi-value.text-cherry       { color: var(--jet); }
.kpi-value.text-dark-emerald + .kpi-label { color: var(--dark-emerald); }
.kpi-value.text-cherry       + .kpi-label { color: var(--cherry); }

.kpi-widget .kpi-label {
    font-size: 13px;
    color: var(--iron);
    margin-top: 8px;
    letter-spacing: 0.01em;
    font-weight: 400;
}

/* KPI Mini - Compact inline structure */
.widget-card.kpi-mini {
    padding: var(--widget-padding);
    background: var(--white);
}

.kpi-mini .widget-title {
    margin-bottom: var(--space-2);
}

.kpi-mini .kpi-value {
    font-size: 1.75rem;        /* 28px */
    font-family: var(--font-mono);
    font-weight: 500;
    color: var(--jet);
    line-height: 1.05;
    letter-spacing: -0.02em;
    margin-top: 8px;
}
/* Stessa regola della card grande, ripetuta qui perche' queste due erano piu'
   specifiche e vincevano: il numero resta scuro, il segno scende sulla
   didascalia. ⚠️ E' la KPI-MINI la card che il cruscotto usa davvero — le
   quattro in cima alla Ritenzione sono queste, non `.kpi-widget`. */
.kpi-mini .kpi-value.text-dark-emerald,
.kpi-mini .kpi-value.text-cherry       { color: var(--jet); }
.kpi-mini .kpi-value.text-dark-emerald + .kpi-label { color: var(--dark-emerald); }
.kpi-mini .kpi-value.text-cherry       + .kpi-label { color: var(--cherry); }

.kpi-mini .kpi-label {
    font-size: 12px;
    color: var(--iron);
    margin-top: 4px;
}

/* Section Title - Titoli sezione pagina */
.section-title {
    font-size: 11px !important;
    font-weight: 600 !important;
    text-transform: uppercase !important;
    letter-spacing: var(--tracking-label) !important;
    color: var(--iron) !important;
    padding-bottom: 0 !important;
    margin-bottom: 16px !important;
    margin-top: 40px !important;
    border-bottom: none !important;
}

/* =============================================================================
   6. TABLES
   ============================================================================= */

/* Sortable Tables */
.sortable-table th[data-sort] {
    cursor: pointer;
    user-select: none;
    position: relative;
    padding-right: 20px;
}
.sortable-table th[data-sort].text-right,
.sortable-table th[data-sort].text-center {
    padding-right: 24px;
}
/* Preserve sticky positioning when both sticky and sortable */
.sortable-table th[data-sort].sticky {
    position: sticky;
}
.sortable-table th[data-sort]:hover {
    color: var(--text-heading);
}

.sortable-table thead th[data-sort]:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: -2px;
}
.sortable-table th[data-sort]::after {
    content: '⇅';
    position: absolute;
    right: 6px;
    top: 50%;
    transform: translateY(-50%);
    font-family: var(--font-sans);
    font-size: 10px;
    opacity: 0.3;
}
.sortable-table th[aria-sort="ascending"]::after {
    content: '↑';
    opacity: 0.8;
    color: var(--accent-dark);
}
.sortable-table th[aria-sort="descending"]::after {
    content: '↓';
    opacity: 0.8;
    color: var(--accent-dark);
}

/* Sticky table header — intestazione fissa allo scroll. Richiede un wrapper
   con overflow-auto + max-height; bg opaco per coprire le righe sottostanti.
   Il secondo selettore batte la specificità di `.sortable-table th[data-sort]`
   (che imporrebbe position:relative sui th ordinabili). */
.ui-table-sticky-head thead th,
.ui-table-sticky-head.sortable-table thead th[data-sort] {
    position: sticky;
    top: 0;
    z-index: 10;
    background: var(--platinum);
}

/* Zebra rows - global table alignment */
table tbody tr:nth-child(odd) td:not([class*="bg-"]) {
    background-color: var(--table-bg);
}

table tbody tr:nth-child(even) td:not([class*="bg-"]) {
    background-color: var(--table-row-alt);
}

table tbody tr:nth-child(odd) td.sticky {
    background-color: var(--table-bg);
}

table tbody tr:nth-child(even) td.sticky {
    background-color: var(--table-row-alt);
}

/* Table Row - Metabolis Style */
table tbody tr {
    transition: background-color 0.15s ease;
}

table td,
table th {
    padding: 10px 12px;                               /* ← Minimal: Compact vertical padding */
    font-size: 12px;
    color: var(--iron);
}

/* Table Row Hover */
.sortable-table tbody tr:hover td,
table tbody tr:hover td {
    background-color: var(--table-row-hover) !important;
}

/* Ensure sticky columns also get the hover effect */
.sortable-table tbody tr:hover td.sticky,
.sortable-table tbody tr:hover td[class*="sticky"],
table tbody tr:hover td.sticky,
table tbody tr:hover td[class*="sticky"] {
    background-color: var(--table-row-hover) !important;
}

/* Table Headers - Clean Minimal */
table th,
thead th {
    background: var(--table-header-bg);
    border-bottom: 1px solid var(--border);
    font-weight: 600;
    font-size: 12px;
    letter-spacing: 0.05em;
    color: var(--table-header-text);
}

/* Ensure table utility classes don't shrink headers/cells */
.sortable-table th.text-xs,
table th.text-xs,
.sortable-table td.text-xs,
table td.text-xs {
    font-size: 12px;
}

.ui-table-link {
    color: var(--table-link-color);
    text-decoration: none;
}

.ui-table-link:hover {
    color: var(--table-link-hover);
    text-decoration: none;
}

.ui-table-page-active {
    background: var(--table-page-active-bg);
    color: var(--table-page-active-text);
}

/* =============================================================================
   9. CHIPS
   ============================================================================= */

/* Chips - Interactive pills */
.chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    padding: 5px 12px;
    min-width: 32px;
    font-size: 11px;
    font-weight: 500;
    border-radius: var(--radius-md);
    background: var(--bg-muted);
    color: var(--text-body);
    cursor: pointer;
    transition: background-color var(--transition);
    white-space: nowrap;
    flex-shrink: 0;
    text-decoration: none;
}

.chip:hover {
    background: var(--border);
    text-decoration: none;
}

/* La famiglia .badge + .badge-* e' stata tolta il 07/08/2026: 9 varianti
   piu' 4 .badge-on-dark-*, zero occorrenze nel markup, viva solo perche'
   due renderer di helpers.js la componevano a runtime — e quei renderer
   non erano chiamati da nessuno. Chi scrive un badge usa .ui-badge--*. */
.ui-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 6px 12px;
    border-radius: 2px;
    /* Trasparente e non assente: le varianti grigie ci scrivono un colore, e
       senza la dichiarazione qui la loro riga sarebbe 2 px piu' alta delle
       altre nella stessa colonna. */
    border: 1px solid transparent;
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    /* Un'etichetta di stato sta su una riga: spezzata in due smette di leggersi
       come un'etichetta e alza la riga della tabella. .ui-pill ce l'aveva gia',
       badge e chip no — e sotto i 1024px "In scadenza" andava a capo in una
       colonna da 115px. Non e' una questione di dimensione: il badge a <=640px
       rende gia' 23px, esattamente come il chip che li' si rimpicciolisce. */
    white-space: nowrap;
    background: rgba(5, 8, 52, 0.08);
    color: var(--jet);
    transition: background var(--transition), color var(--transition);
}

/* L'over solo dove il badge e' cliccabile. Un badge che cambia colore al
   passaggio del mouse promette un'azione, e in tabella e' un'etichetta: chi
   ci clicca sopra non ottiene niente e conclude che la pagina non funziona.
   `a` e `button` bastano a distinguerli, non serve una classe in piu'. */
a.ui-badge:hover,
button.ui-badge:hover,
.ui-badge[role="button"]:hover {
    background: var(--iron);
    color: #ffffff;
}

.ui-badge--warn {
    background: var(--princeton-light);
    color: var(--princeton-dark);
}

/* ⚠️ Le due varianti col fondo `--platinum` portano un BORDO, e non e' una
   rifinitura: `--platinum` e' anche il fondo delle righe pari di ogni tabella,
   quindi una riga si' e una no il badge spariva — restava il testo, che si
   legge come una cella di testo normale invece che come una classificazione.
   Misurato il 21/08/2026 sulla lista Clienti: fondo del badge e fondo della
   riga identici, contrasto 1,00. Il rimedio non e' cambiare il fondo (sarebbe
   `--neutral`, e due varianti con lo stesso aspetto non sono due varianti): e'
   definire il chip col contorno, come per le icone di riga. */
.ui-badge--info {
    background: var(--platinum);
    color: var(--jet);
    border-color: var(--alabaster);
}

.ui-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 3px 10px;
    min-width: 24px;
    font-size: 11px;
    font-weight: 500;
    border-radius: var(--radius-md);
    background: rgba(5, 8, 52, 0.08);
    color: var(--jet);
    white-space: nowrap;
    flex-shrink: 0;
}

.ui-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    padding: 5px 12px;
    min-width: 32px;
    font-size: 11px;
    font-weight: 500;
    border-radius: var(--radius-sm);
    background: var(--bg-muted);
    color: var(--text-body);
    cursor: pointer;
    white-space: nowrap;          /* stessa ragione di .ui-badge */
    transition: background-color var(--transition), color var(--transition);
    text-decoration: none;
    flex-shrink: 0;
}

.ui-chip:hover {
    background: var(--platinum);
    color: var(--jet);
    text-decoration: none;
}

/* Pastiglia che nomina un componente nella palestra: fondo DICHIARATO, perche'
   composto (velo sul fondo della sezione) il contrasto cambiava con il posto. */
.ui-chip-doc {
    display: inline-block;
    padding: 2px 8px;
    border-radius: var(--radius-sm);
    background: var(--bg-chip-doc);
    color: var(--text-body);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10px;
}

/* Testo per le tecnologie assistive: fuori vista, letto dallo schermo. */
.ui-sr-only {
    /* Ancorata in alto a sinistra del contenitore: lasciata alla posizione statica,
       dentro una cella di tabella scorrevole sfugge allo scroller (che non è
       posizionato) e allarga .main-content, che la taglia. */
    position: absolute;
    top: 0;
    left: 0;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}


/* =============================================================================
   10. PROGRESS BARS
   ============================================================================= */

/* ⚠️ Qui stavano `.ui-progress` e `.ui-progress > .ui-progress-bar`: il secondo
   sistema di barre, ereditato dal BI, con un override che esisteva solo per non
   farsi spegnere dal primo. Zero consumatori in Spark — nessuna vista rendeva
   mai `class="ui-progress"` — quindi la convivenza fra i due sistemi era un
   problema che non si poneva. Usciti il 14/09/2026. Resta il sistema vivo,
   `.ui-progress-bar` + `.ui-progress-fill`, piu' sotto. */

/* =============================================================================
   11. TABS
   ============================================================================= */

/* Container */
.ui-tabs {
    display: flex;
    flex-direction: column;
    gap: 0;
    min-width: 0;
}

/* Tab buttons row */
.ui-tabs .ui-tablist {
    display: flex;
    gap: 2px;
    border: 1px solid var(--internal-menu-border);
    border-radius: var(--radius-sm);
    background: var(--internal-menu-bg);
    padding: 3px;
    border-bottom: none;
    margin-bottom: 0;
}

.ui-tab {
    padding: 8px 14px;
    font-size: 12px;
    font-weight: 500;
    color: var(--iron);
    cursor: pointer;
    transition: color var(--transition), background-color var(--transition), border-color var(--transition), box-shadow var(--transition), opacity var(--transition);
    background: rgba(5, 8, 52, 0.05);
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    margin-bottom: 0;
}

.ui-tab:hover:not(:disabled) {
    color: var(--jet);
    background: rgba(5, 8, 52, 0.08);
}

.ui-tab.active {
    color: var(--white);
    background: var(--jet);
    border-color: var(--iron);
}

.ui-tab:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}


/* Panels container */
.ui-tab-panels {
    min-width: 0;
    padding-top: var(--space-4);
}

.ui-tab-panel {
    display: none;
    min-width: 0;
}

/* Il pannello che entra si dissolve, quello che esce no: animare l'uscita
   vorrebbe dire tenere a schermo per 120 ms un contenuto che l'utente ha gia'
   sostituito, cioe' far sembrare lento un comando che si preme spesso.
   120 ms e' al limite del percettibile, ed e' voluto: si cambia scheda decine
   di volte al giorno, non due. */
.ui-tab-panel.active {
    display: block;
    animation: ui-fade-in 120ms var(--ease-out);
}

/* Variant: in-widget (inside widget-card) */
.widget-body .ui-tabs .ui-tablist {
    background: var(--internal-menu-bg);
}

.widget-body .ui-tabs .ui-tab {
    padding: 6px 12px;
}

.widget-body .ui-tabs .ui-tab.active {
    background: var(--jet);
    color: var(--white);
    border-color: var(--iron);
}

.widget-body .ui-tabs .ui-tab-panels {
    padding-top: var(--space-3);
}

/* Internal segmented menu (tabs-like) */
.ui-internal-menu {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px;
    border: 1px solid var(--internal-menu-border);
    border-radius: 16px;
    background: var(--internal-menu-bg);
}

.ui-internal-menu-item {
    flex: 1 1 0;
    min-width: 0;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 12px;
    background: transparent;
    color: var(--internal-menu-item-text);
    font-size: 14px;
    font-weight: 500;
    letter-spacing: 0.01em;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease;
}

.ui-internal-menu-item:hover:not(.active) {
    background: var(--internal-menu-item-hover-bg);
    color: var(--internal-menu-item-hover-text);
}

.ui-internal-menu-item.active {
    background: var(--internal-menu-active-bg);
    color: var(--internal-menu-active-text);
}

/* L'anello di focus e' un outline e non una box-shadow: la regola globale
   in fondo al file (`body *`) spegne ogni box-shadow con !important, quindi
   l'anello dichiarato come ombra non veniva mai disegnato e il focus da
   tastiera era invisibile. outline quella regola non lo tocca.
   L'offset e' NEGATIVO perche' la variante --compact --inline ha gap 2px e
   2px di margine interno: un anello esterno da 4px sforerebbe il contenitore
   e coprirebbe l'item vicino. Dentro l'elemento la geometria non dipende
   dal gap, quindi vale per tutte e tre le varianti. */
.ui-internal-menu-item:focus-visible {
    outline: 2px solid var(--internal-menu-focus-ring);
    outline-offset: -2px;
}

/* L'item attivo ha il fondo dello stesso colore dell'anello: senza invertire
   il colore l'anello ci sparisce dentro. */
.ui-internal-menu-item.active:focus-visible {
    outline-color: var(--internal-menu-focus-ring-on-active);
}

.ui-internal-menu--compact {
    padding: 4px;
    gap: 4px;
}

.ui-internal-menu--compact .ui-internal-menu-item {
    height: 36px;
    border-radius: 10px;
    font-size: 12px;
}

.ui-internal-menu--inline .ui-internal-menu-item {
    flex: 0 0 auto;
    min-width: auto;
    padding: 0 12px;
}

.ui-internal-menu--compact.ui-internal-menu--inline {
    padding: 1px;
    gap: 2px;
    border-radius: 10px;
    background: var(--bg-card);
    border-color: var(--border);
}

.ui-internal-menu--compact.ui-internal-menu--inline .ui-internal-menu-item {
    height: 32px;
    border-radius: 8px;
    padding: 0 10px;
    font-size: 11px;
    color: var(--text-body);
}

.ui-internal-menu--compact.ui-internal-menu--inline .ui-internal-menu-item:hover:not(.active) {
    background: rgba(5, 8, 52, 0.06);
    color: var(--primary);
}

.ui-internal-menu--compact.ui-internal-menu--inline .ui-internal-menu-item.active {
    background: var(--primary);
    color: #fff;
    box-shadow: none;
}

.ui-internal-menu-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 16px;
    padding: 0 5px;
    margin-left: 6px;
    border-radius: 999px;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
    background: var(--conteggio);
    color: var(--conteggio-testo);
    border: 1px solid rgba(255, 255, 255, 0.15);
}

.ui-internal-menu--compact.ui-internal-menu--inline .ui-internal-menu-item.active .ui-internal-menu-badge {
    background: #fff;
    color: var(--conteggio);
    border-color: #fff;
}

/* =============================================================================
   12. CHARTS
   ============================================================================= */

.ui-chart-container {
    position: relative;
    width: 100%;
}

.ui-chart-container canvas {
    width: 100% !important;
    height: 100% !important;
}

/* Chart height: fixed by default */
.ui-chart-sm { height: 150px; }
.ui-chart-md { height: 250px; }
.ui-chart-lg { height: 350px; }
.ui-chart-xl { height: 450px; }

/* Capped-height widget: prevents widgets from growing too tall.
   - Chart widgets: chart container shrinks to fit via flex.
   - List widgets (.ui-widget-scrollable): content area scrolls. */
@media (min-width: 1024px) {
    .ui-widget-capped {
        max-height: 450px;
        display: flex;
        flex-direction: column;
    }
    .ui-widget-capped .ui-chart-container {
        flex: 1 1 0%;
        height: auto;
        min-height: 150px;
    }
    .ui-widget-scrollable {
        flex: 1 1 0%;
        overflow-y: auto;
        min-height: 0;
        padding-right: 8px;
        margin-right: -4px;
        scrollbar-width: thin;
        scrollbar-color: var(--border) transparent;
    }
    .ui-widget-scrollable::-webkit-scrollbar {
        width: 4px;
    }
    .ui-widget-scrollable::-webkit-scrollbar-track {
        background: transparent;
    }
    .ui-widget-scrollable::-webkit-scrollbar-thumb {
        background: var(--border);
        border-radius: 2px;
    }
}

/* =============================================================================
   13. UTILITIES
   ============================================================================= */

/* Text Colors */
.text-accent { color: var(--accent); }
/* ⚠️ Le utility di testo semantico portano la variante SCURA: seaweed nudo su
   fondo chiaro da' 2,59:1 e cherry regge (5,96). Il verde chiaro resta un
   colore da fondo e da tratto di grafico, non da testo. */
/* ⚠️ `.text-ui-success`, `.text-ui-warning` e `.text-ui-danger` NON stanno qui:
   le genera Tailwind dagli alias `ui.*` del config in `views/partials/header.php`,
   e quel foglio viene iniettato DOPO questo — le copie scritte qui rendevano un
   colore e a schermo se ne misurava un altro. Il valore si cambia la', in un
   posto solo. */

/* Font Sizes — ⚠️ questa scala RIDEFINISCE le utility native di Tailwind, che
   arriva dal CDN e viene iniettato prima di questo foglio: `text-sm` qui vale
   12px contro i 14 di Tailwind, `text-xl` 18 contro 20, `text-2xl` 20 contro
   24. Chi copia una misura da una guida di Tailwind ne ottiene un'altra, e non
   ha modo di accorgersene. Le due voci INERTI sono uscite il 24/08/2026 —
   `.text-base` (14px contro 16) e `.text-3xl`: nessun markup le scriveva,
   quindi erano una trappola armata e non una convenzione. Le altre restano
   perche' sono la scala in uso; la loro divergenza e' dichiarata qui e in
   `DESIGN_SYSTEM.md § Tipografia`. */
.text-2xs { font-size: 10px; }  /* labels, badges, tags */
.text-xs { font-size: 11px; }   /* small text, metadata */
.text-sm { font-size: 12px; }   /* body small, descriptions */
.text-lg { font-size: 16px; }   /* headings small */
.text-xl { font-size: 18px; }   /* headings */
.text-2xl { font-size: 20px; }  /* headings large */

/* Fixed dimensions */
.h-card-sm { height: 52px; }

/* Font Weights */
.font-normal { font-weight: 400; }
.font-medium { font-weight: 500; }
.font-semibold { font-weight: 600; }
.font-bold { font-weight: 700; }

/* Spacing (for use without Tailwind) */
.mt-1 { margin-top: var(--space-1); }
.mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); }
.mt-4 { margin-top: var(--space-4); }
.mb-1 { margin-bottom: var(--space-1); }
.mb-2 { margin-bottom: var(--space-2); }
.mb-3 { margin-bottom: var(--space-3); }
.mb-4 { margin-bottom: var(--space-4); }
.p-4 { padding: var(--space-4); }
.p-5 { padding: var(--space-5); }
.p-6 { padding: var(--space-6); }

/* Responsive utilities */
.overflow-x-auto {
    overflow-x: auto;
    min-width: 0;
}

/* Scrollbar styling */
::-webkit-scrollbar {
    width: 6px;
    height: 6px;
}

::-webkit-scrollbar-track {
    background: transparent;
}

::-webkit-scrollbar-thumb {
    background: var(--border);
    border-radius: 3px;
}

::-webkit-scrollbar-thumb:hover {
    background: var(--text-light);
}

/* =============================================================================
   15. UI COMPONENTS (from template.php)
   ============================================================================= */

/* Metrics */
.ui-metrics {
    display: grid;
    gap: 10px;
}

.ui-metric {
    display: flex;
    justify-content: space-between;
    font-size: 12px;
    color: var(--iron);
    padding-bottom: 8px;
    border-bottom: 1px solid var(--border);
}

.ui-metric strong {
    color: var(--text-heading);
}

.ui-metrics .ui-metric:last-child {
    border-bottom: none;
    padding-bottom: 0;
}

/* Due o tre metriche AFFIANCATE, per quando stanno sopra una tabella o due
   grafici larghi. È una BANDA DI FATTI, non un elenco: etichetta sopra, valore
   sotto, i gruppi impacchettati a sinistra.

   ⚠️ Non è la stessa cosa di prima, ed è una correzione del 21/08/2026. La
   variante affiancava tre `.ui-metric`, che restavano `space-between` dentro un
   terzo di riga: a 1440 px l'etichetta e il suo valore finivano a **225 px**
   l'uno dall'altro (a 2100 px, ~450), e i sei elementi si leggevano come sei
   colonne invece di tre coppie — la stessa famiglia del difetto che questa
   variante era nata per chiudere, solo un livello più in basso. Oggi la
   distanza è la sola interlinea e i bordi sinistri distinti sono **tre**.

   Perché a pacchetto e non in griglia: distribuire su `1fr` ciascuna lega la
   distanza fra i gruppi alla larghezza della card, quindi il difetto ritorna da
   solo appena la finestra cresce. Con `flex` la banda ha la larghezza del suo
   contenuto e la prossimità dice il raggruppamento — 2 px dentro la coppia
   contro 48 fra una coppia e l'altra.

   `flex-wrap` invece della rottura a 1024: i gruppi vanno a capo quando non ci
   stanno, senza una soglia da indovinare. */
.ui-metrics--riga {
    display: flex;
    flex-wrap: wrap;
    column-gap: 48px;
    row-gap: 16px;
    align-items: flex-start;
}

.ui-metrics--riga .ui-metric {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    padding-bottom: 0;
    border-bottom: none;
    min-width: 0;
}

/* IDENTICA alle didascalie dei grafici che stanno sotto (`text-2xs
   font-medium uppercase tracking-wider` = 10 px, 0,05em): l'etichetta di una
   metrica e la didascalia di un grafico dicono la stessa cosa — che dato è
   questo — quindi hanno lo stesso rango. A 11 px erano *quasi* uguali, che a
   40 px di distanza si legge come una disattenzione invece che come una
   gerarchia. Nella banda l'unica cosa che cambia rango è il valore. */
.ui-metrics--riga .ui-metric > span {
    font-size: 10px;
    font-weight: 500;
    line-height: 1.3;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-muted);
}

/* 20 px: il valore è ciò che si cita a voce, e a 12 px pesava come la sua
   etichetta. Tracking negativo perché il testo cresce — vedi §Tono e numeri. */
.ui-metrics--riga .ui-metric strong {
    font-size: 20px;
    font-weight: 600;
    line-height: 1.15;
    letter-spacing: -0.01em;
    color: var(--text-heading);
}

/* Text Block */
.ui-text-block {
    display: grid;
    gap: 10px;
    font-size: 14px;
    line-height: 1.6;
    color: var(--text-body);
}

.ui-text-block h3 {
    font-size: 13px;
    color: var(--text-heading);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.025em;
}
.ui-text-block--mid { font-weight: 500; }

.ui-text-block blockquote {
    border-left: 3px solid rgba(5, 8, 52, 0.20);
    padding-left: 10px;
    color: var(--text-body);
    font-style: italic;
}

.ui-text-accent {
    color: var(--iron);
    font-weight: 700;
}

/* Documento lungo (il manuale di `?page=guida`). `ui-text-block` resta la
   prosa breve dentro una card; questo regge venti sezioni, elenchi e tabelle.
   La misura di riga sta in `max-width`: oltre ~80 caratteri l'occhio perde
   la riga successiva, e la card a 2/3 di pagina a 1440 ne darebbe ~100. */
.ui-documento {
    max-width: 46rem;
    font-size: 14px;
    line-height: 1.65;
    color: var(--text-body);
}
.ui-documento > * + * { margin-top: 12px; }
.ui-documento h3:focus { outline: none; }
.ui-documento h3:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.ui-documento h3 {
    margin-top: 40px;
    padding-top: 20px;
    border-top: 1px solid var(--border);
    font-size: 17px;
    line-height: 1.3;
    font-weight: 600;
    color: var(--text-heading);
    scroll-margin-top: 24px;
}
.ui-documento > h3:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.ui-documento h4 {
    margin-top: 24px;
    font-size: 14px;
    font-weight: 600;
    color: var(--text-heading);
    scroll-margin-top: 24px;
}
.ui-documento h3 + *,
.ui-documento h4 + * { margin-top: 8px; }
.ui-documento strong { color: var(--text-heading); font-weight: 600; }
.ui-documento ul,
.ui-documento ol { padding-left: 1.25rem; }
.ui-documento ul { list-style: disc; }
.ui-documento ol { list-style: decimal; }
.ui-documento li + li { margin-top: 4px; }
.ui-documento li::marker { color: var(--text-light); }
.ui-documento a {
    color: var(--accent-dark);
    text-decoration: underline;
    text-underline-offset: 2px;
}
.ui-documento blockquote {
    border-left: 3px solid var(--accent);
    background: var(--bg-subtle);
    padding: 12px 16px;
    border-radius: 0 var(--radius-md) var(--radius-md) 0;
}
.ui-documento blockquote > * + * { margin-top: 8px; }
/* Nel sorgente `---` separa le sezioni, ma il filo lo porta già il titolo:
   due fili di fila si leggono come una sezione vuota. */
.ui-documento hr { display: none; }
.ui-documento-tabella { overflow-x: auto; }
/* Le celle di `ui-table` sono pensate per i dati; qui sono frasi. */
.ui-documento-tabella .ui-table td,
.ui-documento-tabella .ui-table th {
    text-transform: none;
    white-space: normal;
    vertical-align: top;
    font-size: 13px;
    line-height: 1.5;
}

/* Indice del documento: una voce per sezione, in colonne che scendono con
   la larghezza della card (stessa logica di `ui-domande`). */
.ui-documento-indice {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    gap: 2px 16px;
    font-size: 13px;
}
.ui-documento-indice a {
    display: block;
    padding: 6px 10px;
    border-radius: var(--radius-md);
    color: var(--text-body);
    text-decoration: none;
    transition: background-color 120ms var(--ease-out), color 120ms var(--ease-out);
}
.ui-documento-indice a:hover { background: var(--bg-subtle); color: var(--text-heading); }

/* Form Inputs */
.ui-input {
    display: block;
    height: var(--campo-altezza);
    padding: 0 12px;
    font-size: var(--campo-testo);
    color: var(--valore-colore);
    background: #fff;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    transition: border-color 0.15s ease;
}

textarea.ui-input {
    height: auto;
    min-height: 80px;
    padding: 8px 12px;
    line-height: 1.5;
    resize: vertical;
}

/* Variante compatta, gemella di `.ui-select-sm`. Serve dove il campo sta
   NELL'INTESTAZIONE di un widget e non in un form: li' i 38px e i 14px di
   `.ui-input` pesano piu' del titolo che dovrebbero accompagnare. Resta anche
   dove il campo sta in una riga di comandi di testata (36 px), non in un modulo. */
.ui-input-sm {
    height: 32px;
    padding: 0 10px;
    font-size: 13px;
}

/* Il CAMPO DI RICERCA sopra una lista (`ui-input ui-input-sm max-w-xs`, nove
   viste): la larghezza la detta il SEGNAPOSTO, non il browser. Un `<input>` con
   `width: auto` vale la sua `size` — 20 caratteri, 170 px, 150 utili — e il
   segnaposto piu' corto dell'app ne misura 152: a ogni larghezza si leggeva
   «Cerca per nome o P.IV», con 189 px liberi accanto e 337 a 375 px, perche' la
   misura non dipende dallo spazio ma dal default del controllo. 15rem = 240 px,
   220 utili: ci sta il segnaposto piu' lungo («Cerca per azienda o
   referente…», 198 px). `max-width: 100%` lo tiene dentro il contenitore
   stretto, `max-w-xs` nel markup resta il tetto a 320. */
.ui-input[type="search"] {
    width: 15rem;
    max-width: 100%;
}

.ui-input:focus {
    outline: none;
    border-color: var(--primary);
}

.ui-input::placeholder {
    color: var(--text-light);
}

.ui-input:disabled {
    background: var(--bg-subtle);
    cursor: not-allowed;
    opacity: 0.7;
}

/* Select dropdown */
.ui-select {
    display: block;
    height: var(--campo-altezza);
    padding: 0 32px 0 12px;
    font-size: var(--campo-testo);
    color: var(--valore-colore);
    background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%235a5a5a' d='M3 4.5L6 8l3-3.5H3z'/%3E%3C/svg%3E") no-repeat right 10px center;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    cursor: pointer;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    transition: border-color 0.15s ease;
}

.ui-select:focus {
    outline: none;
    border-color: var(--primary);
}

.ui-select:disabled {
    background-color: var(--bg-subtle);
    cursor: not-allowed;
    opacity: 0.7;
}

/* Date Input Styling */
input[type="date"],
input[type="datetime-local"],
input[type="time"],
.ui-input[type="date"],
.ui-input[type="datetime-local"],
.ui-input[type="time"] {
    padding-right: 8px;
    accent-color: var(--primary);
    color-scheme: light;
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    font-feature-settings: 'tnum' on, 'lnum' on;
}


.ui-input[type="date"]::-webkit-calendar-picker-indicator,
.ui-input[type="datetime-local"]::-webkit-calendar-picker-indicator,
.ui-input[type="time"]::-webkit-calendar-picker-indicator {
    width: 18px;
    height: 18px;
    padding: 2px;
    margin-left: 4px;
    cursor: pointer;
    border-radius: var(--radius-sm);
    opacity: 0.6;
    transition: opacity 0.15s ease, background-color 0.15s ease;
}

.ui-input[type="date"]::-webkit-calendar-picker-indicator:hover,
.ui-input[type="datetime-local"]::-webkit-calendar-picker-indicator:hover,
.ui-input[type="time"]::-webkit-calendar-picker-indicator:hover {
    opacity: 1;
    background-color: var(--primary-light);
}

/* Date picker popup styling (Chrome/Edge) */
::-webkit-datetime-edit {
    padding: 0;
    font-family: inherit;
}

::-webkit-datetime-edit-fields-wrapper {
    padding: 0;
    font-family: inherit;
}

::-webkit-datetime-edit-text {
    color: var(--text-light);
    padding: 0 2px;
    font-family: inherit;
}

::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-year-field {
    color: var(--text-body);
    font-family: inherit;
}

::-webkit-datetime-edit-month-field:focus,
::-webkit-datetime-edit-day-field:focus,
::-webkit-datetime-edit-year-field:focus {
    background-color: var(--primary-light);
    color: var(--primary);
    border-radius: var(--radius-sm);
    outline: none;
}

/* ═══════════════════════════════════════════════════════════════════════════
   FLATPICKR CUSTOM THEME (Violet)
   ═══════════════════════════════════════════════════════════════════════════ */

/* Datepicker input styling */
.ui-datepicker,
.ui-daterange-picker {
    padding-left: 36px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23888888'%3E%3Cpath d='M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: 12px center;
    cursor: pointer;
}

.ui-datepicker:focus,
.ui-daterange-picker:focus {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%233D458D'%3E%3Cpath d='M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z'/%3E%3C/svg%3E");
}

.ui-datepicker.active,
.ui-daterange-picker.active {
    border-color: var(--primary);
}

.flatpickr-calendar {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    border-radius: var(--radius-md);
    border: 1px solid var(--border);
    background: #fff;
}

.flatpickr-calendar.arrowTop:before,
.flatpickr-calendar.arrowTop:after {
    border-bottom-color: var(--border);
}

.flatpickr-calendar.arrowBottom:before,
.flatpickr-calendar.arrowBottom:after {
    border-top-color: var(--border);
}

/* Month navigation */
.flatpickr-months {
    padding: 8px 4px 4px;
}

.flatpickr-months .flatpickr-month {
    height: 36px;
}

.flatpickr-current-month {
    font-size: 13px;
    font-weight: 600;
    color: var(--text-heading);
    padding-top: 6px;
}

.flatpickr-current-month .flatpickr-monthDropdown-months {
    font-weight: 600;
    color: var(--text-heading);
    background: transparent;
}

.flatpickr-current-month .flatpickr-monthDropdown-months:hover {
    background: var(--primary-light);
}

.flatpickr-current-month input.cur-year {
    font-weight: 600;
    color: var(--text-heading);
}

.flatpickr-months .flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month {
    padding: 8px 10px;
    border-radius: var(--radius-sm);
    transition: background-color 0.15s ease;
}

.flatpickr-months .flatpickr-prev-month:hover,
.flatpickr-months .flatpickr-next-month:hover {
    background: var(--primary-light);
}

.flatpickr-months .flatpickr-prev-month svg,
.flatpickr-months .flatpickr-next-month svg {
    fill: var(--primary);
}

.flatpickr-months .flatpickr-prev-month:hover svg,
.flatpickr-months .flatpickr-next-month:hover svg {
    fill: var(--primary-dark);
}

/* Weekdays header */
.flatpickr-weekdays {
    background: transparent;
    padding: 0 8px;
}

.flatpickr-weekday {
    font-size: 10px;
    font-weight: 600;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

/* Days grid */
.flatpickr-days {
    padding: 4px 8px 8px;
}

.dayContainer {
    min-width: 280px;
    max-width: 280px;
}

.flatpickr-day {
    font-size: 13px;
    font-weight: 500;
    color: var(--text-body);
    border-radius: var(--radius-sm);
    height: 36px;
    line-height: 36px;
    max-width: 36px;
    margin: 1px;
    border: none;
    transition: background-color 0.15s ease, color 0.15s ease;
}

.flatpickr-day:hover {
    background: var(--primary-light);
    border-color: transparent;
    color: var(--primary);
}

.flatpickr-day.today {
    border: 1px solid var(--primary);
    background: transparent;
    color: var(--primary);
    font-weight: 600;
}

.flatpickr-day.today:hover {
    background: var(--primary-light);
    color: var(--primary);
}

.flatpickr-day.selected,
.flatpickr-day.startRange,
.flatpickr-day.endRange,
.flatpickr-day.selected.inRange,
.flatpickr-day.startRange.inRange,
.flatpickr-day.endRange.inRange,
.flatpickr-day.selected:focus,
.flatpickr-day.startRange:focus,
.flatpickr-day.endRange:focus,
.flatpickr-day.selected:hover,
.flatpickr-day.startRange:hover,
.flatpickr-day.endRange:hover,
.flatpickr-day.selected.prevMonthDay,
.flatpickr-day.startRange.prevMonthDay,
.flatpickr-day.endRange.prevMonthDay,
.flatpickr-day.selected.nextMonthDay,
.flatpickr-day.startRange.nextMonthDay,
.flatpickr-day.endRange.nextMonthDay {
    background: var(--primary);
    border-color: var(--primary);
    color: #fff;
}

.flatpickr-day.inRange,
.flatpickr-day.prevMonthDay.inRange,
.flatpickr-day.nextMonthDay.inRange,
.flatpickr-day.today.inRange,
.flatpickr-day.prevMonthDay.today.inRange,
.flatpickr-day.nextMonthDay.today.inRange,
.flatpickr-day:hover,
.flatpickr-day.prevMonthDay:hover,
.flatpickr-day.nextMonthDay:hover,
.flatpickr-day:focus,
.flatpickr-day.prevMonthDay:focus,
.flatpickr-day.nextMonthDay:focus {
    border-color: transparent;
}

.flatpickr-day.inRange {
    background: rgba(5, 8, 52, 0.06);
    color: var(--primary-dark);
}

.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay,
.flatpickr-day.notAllowed,
.flatpickr-day.notAllowed.prevMonthDay,
.flatpickr-day.notAllowed.nextMonthDay {
    color: var(--text-light);
}

.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover {
    color: var(--text-light);
    opacity: 0.5;
    background: transparent;
}

/* Time picker */
.flatpickr-time {
    border-top: 1px solid var(--border);
    padding: 8px;
}

.flatpickr-time input {
    font-size: 14px;
    font-weight: 500;
    color: var(--text-heading);
}

.flatpickr-time input:hover,
.flatpickr-time input:focus {
    background: var(--primary-light);
}

.flatpickr-time .flatpickr-time-separator,
.flatpickr-time .flatpickr-am-pm {
    color: var(--text-muted);
    font-weight: 500;
}

.flatpickr-time .flatpickr-am-pm:hover {
    background: var(--primary-light);
    color: var(--primary);
}

.numInputWrapper span {
    border-color: var(--border);
}

.numInputWrapper span:hover {
    background: var(--primary-light);
}

.numInputWrapper span:after {
    border-color: var(--text-muted) transparent;
}

.numInputWrapper span:hover:after {
    border-color: var(--primary) transparent;
}

/* Buttons - Clean Minimal */
.ui-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    height: 36px;
    padding: 0 14px;
    font-size: 14px;
    font-weight: 500;
    border-radius: var(--radius-md);
    border: 1px solid transparent;
    cursor: pointer;
    transition: background-color 140ms var(--ease-out),
                border-color 140ms var(--ease-out),
                color 140ms var(--ease-out),
                box-shadow 140ms var(--ease-out),
                transform 140ms var(--ease-out);
    white-space: nowrap;
    position: relative;
}

/* Il bottone deve dire di aver sentito il click prima che la risposta arrivi.
   Al posto del ripple, che cresceva width/height per 600 ms partendo dal centro
   invece che dal puntatore: layout + paint, e fuori budget di dieci volte. */
.ui-btn:active {
    transform: scale(0.97);
}

.ui-btn:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

/* Spento = metà opacità, su ogni variante: un comando che non si può premere
   deve pesare meno di uno che si può. Sul pieno il risultato sta sotto il 3:1
   ed è una scelta: WCAG esenta i componenti inattivi, e il PERCHÉ di un comando
   spento si scrive accanto (`ui-passi-piede-perche`, `ui-form-hint`). */
.ui-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.ui-btn:disabled:active {
    transform: none;
}

/* ⚠️ Le tre varianti piene ripetono il bianco anche al PASSAGGIO: su un
   `<a class="ui-btn">` la regola `a:hover` (un tipo e una pseudo-classe) batte
   `.ui-btn-primary` (una classe sola) e rimetteva il colore dei link — misurato
   il 25/09/2026 su «Apri in Anagrafica»: `rgb(58,99,168)` su `rgb(31,61,122)`,
   testo blu su blu. Secondary, outline e ghost il colore del passaggio lo
   dichiarano già con `:hover:not(:disabled)`, che pesa di più. */
/* Primary: il pieno dei bottoni (`--btn-pieno`), testo bianco; al passaggio
   `--btn-pieno-hover`. Fino al 22/09/2026 il fondo era jet e il passaggio
   l'azzurro pieno, che col bianco non arrivava a 4,5. */
.ui-btn-primary {
    background: var(--btn-pieno);
    color: var(--white);
    border-color: var(--btn-pieno);
}

.ui-btn-primary:hover:not(:disabled) {
    background: var(--btn-pieno-hover);
    border-color: var(--btn-pieno-hover);
    color: var(--white);
}

/* Accent (blu). ⚠️ Il FONDO e' la variante scura dell'accento, non la tinta
   piena: col bianco la tinta piena non arriva a 4,5, cioe' con quella *non
   esiste* un testo leggibile. Dal 22/09/2026 sera coincide con Primary — il
   pieno dei bottoni e' questo stesso blu — e resta per i consumatori che la
   nominano. */
.ui-btn-accent {
    background: var(--btn-pieno);
    color: var(--white);
    border-color: var(--btn-pieno);
}

.ui-btn-accent:hover:not(:disabled) {
    background: var(--btn-pieno-hover);
    border-color: var(--btn-pieno-hover);
    color: var(--white);
}

/* Secondary: fondo subtle, al passaggio il pieno dei bottoni. */
.ui-btn-secondary {
    background: var(--bg-subtle);
    color: var(--text-body);
    border-color: var(--border);
}

.ui-btn-secondary:hover:not(:disabled) {
    background: var(--btn-pieno);
    border-color: var(--btn-pieno);
    color: var(--white);
}

/* Outline - Trasparente, hover solo bordo e testo viola */
.ui-btn-outline {
    background: transparent;
    color: var(--text-body);
    border-color: var(--border);
}

.ui-btn-outline:hover:not(:disabled) {
    background: transparent;
    border-color: var(--primary);
    color: var(--primary);
}

.ui-btn-outline.active {
    background: transparent;
    border-color: var(--primary);
    color: var(--primary);
}

/* Ghost */
.ui-btn-ghost {
    background: transparent;
    color: var(--text-muted);
    border-color: transparent;
}

.ui-btn-ghost:hover:not(:disabled) {
    background: var(--bg-subtle);
    color: var(--text-body);
}

/* Filo: il comando NON principale che sta DA SOLO — «Modifica» nella testata di
   una card, «Ripeti la ricerca», «Aggiungi …» in coda a una lista. Filo e testo
   `--accent-dark` sul bianco: 5,95:1, sopra il 3:1 di un componente non
   testuale e il 4,5 del testo. È la ricetta di `ui-testata-azzera` resa
   variante di `.ui-btn`, così compone con `ui-btn-sm` (28 px in una card) e
   con la base (36 px in testata di pagina).
   Perché esiste: da solo secondary misura 1,10:1 di fondo e 1,23 di bordo (non
   si vede), il pieno invece si vede TROPPO — 28 px di blu pieno accanto a un
   titolo da 11 px erano l'elemento più saliente della card Anagrafica, e
   modificare non è ciò per cui una scheda si apre. Il pieno resta UNO per
   blocco, per l'azione che il blocco esiste per fare. */
.ui-btn-filo {
    background: var(--white);
    color: var(--accent-dark);
    border-color: var(--accent-dark);
}

.ui-btn-filo:hover:not(:disabled) {
    background: var(--accent-dark);
    border-color: var(--accent-dark);
    color: var(--white);
}

.ui-btn-filo:disabled {
    border-style: dashed;
    color: var(--text-body);
}

/* Danger */
.ui-btn-danger {
    background: var(--cherry);
    color: #fff;
    border-color: var(--cherry);
}

.ui-btn-danger:hover:not(:disabled) {
    background: var(--danger);
    border-color: var(--danger);
    color: #fff;
}

/* Sizes */
.ui-btn-sm {
    height: 28px;
    padding: 0 10px;
    font-size: 11px;
}

/* La taglia dei comandi di un CONFIGURATORE A PASSI: «Indietro» e «Avanti» del
   piede, e il comando che il passo esiste per premere — Conta, Estrai, Cerca,
   gli export e il salvataggio del segmento. 40 px e il testo di casa (14): un
   gradino sopra la base, non un ingrandimento. Il 22/09/2026 era 44 a 15 px, e
   col resto del passo ingrandito Nicola l'ha giudicato «troppo grande»: il
   comando si distingue perché è l'unico della card, non perché è enorme.
   ⚠️ NON sale la taglia di base: la riga di testata resta a 36 px, dove `.ui-btn`
   e `.ui-dropdown-trigger` devono misurare uguale (§Design System n. 12). */
/* ⚠️ Dal 22/09/2026 (sera) è anche la taglia del BOTTONE DI UN MODULO — il
   comando che chiude un modulo in una card, il piede di una modale, la
   conferma — e la dichiarano i tre contenitori qui sotto invece di chiederla a
   ogni bottone: alta quanto il campo (`--campo-altezza`), peso 600. A 28 px e
   11 px («Cambia la password» sul Profilo) il comando pesava meno
   dell'etichetta del campo sopra di lui. */
.ui-btn-lg,
.ui-form-azioni .ui-btn,
.ui-modal-footer .ui-btn,
.ui-confirm-actions .ui-btn {
    height: var(--campo-altezza);
    padding: 0 18px;
    font-size: 14px;
    font-weight: 600;
}

/* Icon only */
.ui-btn-icon {
    width: 36px;
    padding: 0;
    background: transparent;
    color: var(--iron);
    border-color: var(--border);
}

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

.ui-btn-sm.ui-btn-icon {
    width: 28px;
}

.ui-btn-xs.ui-btn-icon {
    width: 24px;
}

/* ⚠️ Due comandi icona adiacenti si toccano: concatenati nel JS senza nulla in
   mezzo la distanza misura **0 px** (Chrome 150), e i due quadrati col bordo si
   leggono come un controllo solo diviso a metà. Il rimedio non è uno spazio nel
   markup — `lead.js` lo faceva e dava **3 px**, cioè la larghezza accidentale
   di uno spazio a quel font, che cambia col font.
   `0.5rem` è lo stesso `gap` che `.ui-btn` usa fra icona e testo: una misura
   sola per «due cose che appartengono allo stesso comando ma restano
   distinte». */
.ui-row-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}

/* ⚠️ `ghost` NON toglie il bordo a un'icona di riga, e fino al 21/08/2026 lo
   toglieva: l'idea era che undici cornici in colonna disegnassero una griglia
   in competizione coi dati. Guardato su una tabella vera — la coda delle
   conferme, righe a zebra — vince l'obiezione opposta, che quel commento
   scriveva da sé: senza cornice la spunta e la croce si leggono come due glifi
   appoggiati, non come comandi. Il bordo a riposo è `--border`, lo stesso peso
   dei filetti di riga, e a distinguerlo basta la forma: un quadrato arrotondato
   di 28 px non si confonde con un filetto orizzontale largo quanto la tabella.
   ⚠️ Un bordo più CHIARO dei filetti non è la via di mezzo che sembra: sui
   fondi a zebra (`--platinum`) misura 1,08:1 contro 1,21 del bordo pieno, cioè
   sparisce una riga sì e una no — la visibilità a intermittenza si legge come
   due comandi diversi. Per questo `ghost` e `icon` insieme non dichiarano più
   niente: valgono le regole di `.ui-btn-icon` qui sopra. */

.ui-btn-lg.ui-btn-icon {
    width: 44px;
}

/* Loading */
.ui-btn-loading {
    pointer-events: none;
    opacity: 0.8;
}

/* Button Group */
.ui-btn-group {
    display: inline-flex;
}

.ui-btn-group .ui-btn {
    border-radius: 0;
    margin-left: -1px;
}

.ui-btn-group .ui-btn:first-child {
    border-radius: var(--radius-sm) 0 0 var(--radius-sm);
    margin-left: 0;
}

.ui-btn-group .ui-btn:last-child {
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.ui-btn-group .ui-btn:hover,
.ui-btn-group .ui-btn.active {
    z-index: 1;
}

/* Accordion */
.ui-accordion {
    border: 1px solid var(--internal-menu-border);
    border-radius: var(--radius-sm);
    padding: 10px 12px;
    background: var(--internal-menu-bg);
}

.ui-accordion summary {
    font-size: 12px;
    font-weight: 600;
    color: var(--internal-menu-item-hover-text);
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.ui-accordion summary::after {
    content: '';
    width: 7px;
    height: 7px;
    margin-left: 10px;
    border-right: 1.8px solid var(--internal-menu-item-text);
    border-bottom: 1.8px solid var(--internal-menu-item-text);
    transform: rotate(45deg) translateY(-1px);
    /* Ruota restando dov'e': --ease-in-out, come .nav-chevron e .ui-criteri-freccia. */
    transition: transform 0.2s var(--ease-in-out), border-color 0.2s ease;
    flex-shrink: 0;
}

.ui-accordion[open] summary::after {
    transform: rotate(-135deg) translateY(1px);
    border-color: var(--internal-menu-active-bg);
}

.ui-accordion summary::-webkit-details-marker {
    display: none;
}

/* Lo stacco dal `summary` vale per QUALUNQUE contenuto, non per i soli
   paragrafi: fino al 27/08/2026 la regola era `.ui-accordion p`, e un accordion
   con dentro una tabella la attaccava al titolo — 0 px misurati contro gli 8 di
   un `<p>`. Non si vedeva perche' il padding della prima cella riempie lo spazio
   che manca, quindi a schermo somiglia a una scelta. La tipografia resta sui
   soli paragrafi: una tabella ha la sua. */
.ui-accordion > summary + * {
    margin-top: 8px;
}

.ui-accordion p {
    font-size: 12px;
    color: var(--text-muted);
}

/* Accordion variant: in-widget (flat style) */
.widget-body .ui-accordion {
    border: 1px solid var(--internal-menu-border);
    background: var(--internal-menu-bg);
    border-radius: var(--radius-sm);
}

.widget-body .ui-accordion[open] {
    background: var(--internal-menu-item-hover-bg);
    border-color: var(--internal-menu-active-bg);
}

.widget-body .ui-accordion summary {
    font-size: 12px;
}

/* Dropdown */
.ui-dropdown {
    position: relative;
    display: inline-block;
}

.ui-dropdown-trigger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: 36px;
    padding: 0 12px;
    font-size: 12px;
    font-weight: 500;
    color: var(--text-body);
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background-color 150ms var(--ease-out), border-color 150ms var(--ease-out);
}

.ui-dropdown-trigger:hover {
    background: var(--bg-subtle);
    border-color: var(--primary);
}

.ui-dropdown-trigger--icon {
    padding: 0;
    width: 36px;
}

/* Il pallino che dice «questo comando ha qualcosa di attivo».
   UNO per tutti i comandi d'intestazione: filtri, calendario, e quelli che
   verranno. Si chiamava `.ui-calendar-notify-dot`, cioe' del calendario e di
   nessun altro, e ad accenderlo non c'era NIENTE — `data-calendar-dot` non lo
   leggeva nessun JS, quindi era markup che sembrava una convenzione.
   Arancione e non --primary: il jet su un'icona jet non si distingue dal
   glifo, e il punto e' farsi notare. */
.ui-trigger-dot {
    position: absolute;
    top: 6px;
    right: 6px;
    width: 8px;
    height: 8px;
    border-radius: 999px;
    background: var(--princeton);
}

/* Compact variant for widget/toolbar */
.ui-dropdown-trigger--sm {
    height: 32px;
}

.ui-dropdown-trigger--icon.ui-dropdown-trigger--sm {
    width: 32px;
    font-size: 11px;
}

.ui-dropdown-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    min-width: 180px;
    /* Il pannello ha un suo margine interno: senza, le voci toccano il bordo
       e l'evidenziazione dell'hover finisce a filo della cornice. Prima
       questo padding stava solo su una variante, cioe' il menu nudo non era
       usabile da solo. */
    padding: 4px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    z-index: 50;
    opacity: 0;
    visibility: hidden;
    /* Il menu nasce dal suo trigger, non dal proprio centro: l'origine sta
       sull'angolo da cui il pannello e' ancorato. */
    transform-origin: top left;
    transform: translateY(-4px) scale(0.97);
    transition: opacity 150ms var(--ease-out),
                transform 150ms var(--ease-out),
                visibility 150ms var(--ease-out);
}

/* Allineamento a destra: serve a ogni menu ancorato a un trigger che sta
   sul bordo destro dell'intestazione, altrimenti il pannello esce dallo
   schermo. Era scritto `style="right:0;left:auto"` a mano voce per voce. */
.ui-dropdown-menu--right {
    left: auto;
    right: 0;
}

/* L'origine segue l'ancoraggio: il menu nasce dall'angolo da cui pende. */
.ui-dropdown-menu--right,
.ui-timefilter-menu {
    transform-origin: top right;
}

/* ⚠️ Sotto i 640 px l'ancoraggio a destra si ROVESCIA, e non e' cosmesi.
   `right: 0` vuol dire «il bordo destro del pannello sta su quello del
   trigger»: a schermo largo il trigger e' a destra e il menu cade dentro la
   finestra, ma a schermo stretto l'intestazione va a capo (`.page-header-row`,
   `flex-wrap: wrap`) e lo STESSO trigger finisce a sinistra — il pannello si
   stende all'indietro e ne esce. Misurato il 22/09/2026 a 375 e 414 px:
   Anagrafica «Altre azioni» `left = -208` su 280 di larghezza e «Filtri»
   `-158` su 272, Opportunita' `-103` e `-158`, Portafoglio/Ritenzione `-109` e
   `-116`, Consumi `-150`. E non si raggiungono in nessun modo: `.main-content`
   ha `overflow-x: hidden` e l'eccedenza misura 0, quindi il contenuto non esce
   dallo schermo — viene tagliato.

   ⚠️ Riancorare a sinistra NON basta, ed e' stato il primo tentativo: il
   pannello resta appeso al TRIGGER, e un trigger a x=120 con un pannello da 272
   esce di 17 px dall'altra parte (misurato su Ritenzione a 375). Finche'
   l'ancora e' il trigger non esiste una larghezza che vada bene per tutte le
   sue posizioni. Quindi a questa larghezza il pannello si ancora al BLOCCO DEI
   COMANDI: `.ui-dropdown` smette di essere il suo contenitore posizionato, il
   menu si stende da bordo a bordo del blocco e scende sotto tutta
   l'intestazione. La sua larghezza non dipende piu' da quale dei comandi lo ha
   aperto — che e' la proprieta' che mancava.
   ⚠️ `width`/`min-width` vanno azzerati insieme: le varianti li dichiarano
   (272 su `--filtri`, 180 di base) e vincerebbero su `left`/`right`.
   La regola sta insieme a `.page-header-row`, piu' in basso in questo file: e'
   li' che nasce il difetto, e li' l'ordine della cascata la fa vincere sul
   `justify-content: flex-end` dei comandi. */

.ui-dropdown:hover .ui-dropdown-menu,
.ui-dropdown:focus-within .ui-dropdown-menu {
    opacity: 1;
    visibility: visible;
    transform: translateY(0) scale(1);
}

.ui-dropdown--click:hover .ui-dropdown-menu,
.ui-dropdown--click:focus-within .ui-dropdown-menu {
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px) scale(0.97);
}

.ui-dropdown.is-open .ui-dropdown-menu {
    opacity: 1;
    visibility: visible;
    transform: translateY(0) scale(1);
}

/* Intestazione di gruppo dentro il menu.
   Reggeva solo se il markup ci aggiungeva anche `widget-title`: da sola era
   testo a 14px, indistinguibile da una voce. Ora e' un maiuscoletto come
   ogni altro titolo del sistema, e il markup non deve ricordarsi niente. */
.ui-dropdown-header {
    padding: 7px 8px 5px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    color: var(--text-muted);
}

.ui-dropdown-item {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 7px 8px;
    font-size: 13px;
    line-height: 1.4;
    white-space: nowrap;
    color: var(--text-body);
    background: transparent;
    border: none;
    cursor: pointer;
    transition: background 0.15s var(--ease-out);
    text-align: left;
}

/* Larghezza fissa all'icona: senza, ogni glifo ha la sua e le etichette
   partono da ascisse diverse. Il markup lo faceva con `w-4` a mano, voce
   per voce — cioe' finche' qualcuno se lo ricordava. */
.ui-dropdown-item > i {
    flex: none;
    width: 16px;
    text-align: center;
    font-size: 13px;
    color: var(--text-light);
}

.ui-dropdown-item:hover > i {
    color: inherit;
}

/* Una voce pericolosa e' rossa anche nell'icona: spegnerla la farebbe
   sembrare disabilitata proprio dove il colore serve ad avvertire. */
.ui-dropdown-item--danger > i {
    color: inherit;
}

.ui-dropdown-item:hover {
    background: var(--bg-subtle);
}

/* Il click deve dirsi sentito prima che la voce faccia effetto. Non `scale`
   come sul bottone: una riga a tutta larghezza che si stringe sembra un
   difetto di disegno, non una pressione. Il fondo scende subito (0 ms) e
   risale in 140: la risposta e' istantanea, il ritorno no. */
.ui-dropdown-item:active {
    background: rgba(5, 8, 52, 0.12);
    transition-duration: 0ms;
}

/* La voce scelta, in un menu che rappresenta uno stato (filtro attivo,
   ordinamento corrente). Lo stile esisteva solo dentro .ui-quickfilter-menu,
   una variante che nessuno usava: tolta quella, una voce marcata .active
   sarebbe rimasta identica alle altre in ogni dropdown dell'app. */
.ui-dropdown-item.active {
    background: rgba(5, 8, 52, 0.06);
    color: var(--primary);
    font-weight: 600;
}

.ui-dropdown-item.active > i {
    color: var(--primary);
}

.ui-dropdown-item--danger {
    color: var(--danger);
}

.ui-dropdown-item--danger:hover {
    background: var(--cherry-light);
}

/* La freccetta del trigger. Non aveva NESSUNA regola di base: l'unica che la
   nominava era dentro il blocco `.ui-dropdown-demo`, quindi fuori dalla
   palestra era un'icona ferma. Ruota quando il menu e' aperto — e la
   rotazione e' l'unico segnale che il click ha fatto qualcosa quando il
   pannello si apre fuori dal campo visivo. */
.ui-dropdown-caret {
    font-size: 10px;
    color: var(--text-light);
    transition: transform 150ms var(--ease-out);
}

.ui-dropdown.is-open .ui-dropdown-caret {
    transform: rotate(180deg);
}

/* Il separatore taglia tutto il pannello, quindi rientra del padding del
   menu: un filo che si ferma prima del bordo sembra un errore di disegno. */
.ui-dropdown-divider {
    margin: 4px -4px;
    border: none;
    border-top: 1px solid var(--border);
}

/* Dropdown variant: in-widget */
.widget-body .ui-dropdown-trigger {
    background: var(--bg-subtle);
    border-color: transparent;
}

.widget-body .ui-dropdown-trigger:hover {
    background: var(--bg-muted);
    border-color: transparent;
}

/* AI Components */
.ui-ai-card {
    border-radius: var(--radius-sm);
    padding: 18px;
    background: linear-gradient(135deg, var(--ai-card-bg-start), var(--ai-card-bg-end));
    border: 1px dashed var(--ai-card-border);
    display: grid;
    gap: 8px;
    font-size: 12px;
    color: var(--text-body);
    transition: transform 140ms var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
    .ui-ai-card:hover {
        transform: translateY(-2px);
        border-color: var(--ai-card-hover-border);
    }
}

.ui-ai-card--accent {
    background: linear-gradient(135deg, var(--ai-card-accent-start), var(--ai-card-accent-end));
}

.ui-ai-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    color: var(--ai-card-title-accent);
    font-weight: 600;
}

.ui-ai-chip {
    background: var(--ai-card-chip-bg);
    color: var(--ai-card-chip-text);
    padding: 2px 6px;
    border-radius: var(--radius-sm);
    font-size: 9px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.ui-ai-title {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    font-weight: 700;
    color: var(--text-heading);
}

.ui-ai-sparkle {
    color: var(--ai-card-title-accent);
}

.ui-ai-footer {
    font-size: 11px;
    color: var(--text-muted);
}

.ui-ai-accordion {
    border-style: dashed;
    background: rgba(255, 255, 255, 0.85);
}
.ui-ai-card p,
.ui-ai-card li,
.ui-ai-card .text-iron {
    color: var(--text-heading);
}

.ui-ai-kpis {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 8px;
}

.ui-ai-kpi {
    background: rgba(255, 255, 255, 0.7);
    border-radius: var(--radius-md);
    padding: 8px;
    text-align: center;
    font-size: 11px;
    color: var(--text-muted);
}

.ui-ai-kpi strong {
    display: block;
    font-size: 16px;
    color: var(--text-heading);
}


/* Contrast Cards — filled backgrounds */
.ui-contrast-card {
    border-radius: var(--radius-md);
    padding: var(--widget-padding);
    color: #fff;
    display: grid;
    gap: 8px;
    border: none;
}
.ui-contrast-card--positive { background: var(--dark-emerald); }  /* seaweed pieno col bianco sopra: 2,87:1 */
.ui-contrast-card--negative { background: var(--danger); }
.ui-contrast-card--neutral { background: var(--jet); }

.ui-contrast-card .ui-contrast-header { opacity: 0.85; }
.ui-contrast-card .ui-contrast-value { color: #fff; }
.ui-contrast-card .ui-contrast-meta { color: rgba(255,255,255,0.8); }

.ui-contrast-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    opacity: 0.75;
}

.ui-contrast-value { font-size: 28px; font-family: var(--font-mono); font-weight: 700; letter-spacing: 0; }
.ui-contrast-meta { font-size: 12px; opacity: 0.8; }

/* Dark KPI Cards */
.ui-kpi-dark {
    background: var(--jet);
    color: var(--platinum);
    border-radius: var(--radius-md);
    padding: var(--widget-padding);
    display: grid;
    gap: 8px;
    border: 1px solid var(--iron);
    box-shadow: none;
}

.ui-kpi-dark-soft {
    background: var(--iron);
    color: var(--platinum);
    border-radius: var(--radius-md);
    padding: var(--widget-padding);
    display: grid;
    gap: 8px;
    border: 1px solid var(--iron);
    box-shadow: none;
}

.ui-kpi-dark-subtle {
    background: var(--iron);
    color: var(--platinum);
    border-radius: var(--radius-md);
    padding: var(--widget-padding);
    display: grid;
    gap: 8px;
    border: 1px solid var(--iron);
    box-shadow: none;
}

.ui-kpi-dark-soft canvas,
.ui-kpi-dark canvas,
.ui-kpi-dark-subtle canvas { max-width: 100%; display: block; }

.ui-kpi-dark-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    color: rgba(255, 255, 255, 0.70);
}

.ui-kpi-dark-soft .ui-kpi-dark-header { color: rgba(255, 255, 255, 0.70); }
.ui-kpi-dark-soft .ui-kpi-dark-meta { color: rgba(255, 255, 255, 0.55); }

.ui-kpi-dark-value { font-size: 2.25rem; font-family: var(--font-mono); font-weight: 500; letter-spacing: -0.02em; line-height: 1; margin-bottom: 1px; }
.ui-kpi-dark-meta { font-size: 12px; color: rgba(255, 255, 255, 0.55); line-height: 1.2; }
.ui-kpi-dark-value + .ui-kpi-dark-meta,
.ui-kpi-dark-soft .ui-kpi-dark-value + .ui-kpi-dark-meta,
.ui-kpi-dark-subtle .ui-kpi-dark-value + .ui-kpi-dark-meta { margin-top: -3px; }

.ui-kpi-dark .h-card-sm,
.ui-kpi-dark-soft .h-card-sm,
.ui-kpi-dark-subtle .h-card-sm { height: 96px; }

/* Semantica su superficie scura.
   seaweed #1AAD7E e cherry #C21B3F sono tarati sul bianco: su --jet #002C57 il rosso
   scende a ~2,2:1, sotto il 3:1 richiesto per un indicatore non testuale. Queste due
   tinte schiarite valgono SOLO dentro le card scure; altrove si usano i colori pieni. */
:root {
    --seaweed-on-dark: #4ADE80;
    --cherry-on-dark: #FF7A93;
}
.ui-kpi-dark .ui-dot--verde,
.ui-kpi-dark-soft .ui-dot--verde,
.ui-kpi-dark-subtle .ui-dot--verde { background: var(--seaweed-on-dark); }
.ui-kpi-dark .ui-dot--rosso,
.ui-kpi-dark-soft .ui-dot--rosso,
.ui-kpi-dark-subtle .ui-dot--rosso { background: var(--cherry-on-dark); }

/* L'icona del tooltip eredita text-ui-light (stone), illeggibile su jet. */
.ui-kpi-dark .bi-info-circle,
.ui-kpi-dark-soft .bi-info-circle,
.ui-kpi-dark-subtle .bi-info-circle { color: rgba(255, 255, 255, 0.55); }

/* Un ui-badge dentro un contenitore a fondo pieno.
   Le varianti .ui-badge--* hanno fondo del colore semantico al 12% e testo
   dello stesso colore pieno: leggibili su fondo chiaro, illeggibili appena
   il contenitore porta quel colore. Misurato prima di scrivere questa
   regola: "Alert" (--danger) dentro .ui-contrast-card--negative dava
   1,00:1, cioe' testo cherry su fondo cherry — invisibile — e "OK"
   (--success) dentro --positive 1,09:1. Entrambi sono il markup che il
   design system prescrive, e stanno a schermo nella palestra.
   Qui il fondo torna chiaro e il colore passa al testo, nella sua versione
   scura: --dark-emerald 5,52:1, --cherry 5,13:1, --iron 6,52:1.
   --warn usa --princeton-dark, il token ambrato nato proprio perche' in
   palette nessun arancio arrivava a 4,5:1 come testo. --iron resta il
   ripiego per le varianti senza una versione scura propria. */
.ui-contrast-card .ui-badge,
.ui-kpi-dark .ui-badge,
.ui-kpi-dark-soft .ui-badge,
.ui-kpi-dark-subtle .ui-badge {
    background: rgba(255, 255, 255, 0.92);
    color: var(--iron);
}

.ui-contrast-card .ui-badge--success,
.ui-kpi-dark .ui-badge--success,
.ui-kpi-dark-soft .ui-badge--success,
.ui-kpi-dark-subtle .ui-badge--success { color: var(--dark-emerald); }

.ui-contrast-card .ui-badge--danger,
.ui-contrast-card .ui-badge--error,
.ui-kpi-dark .ui-badge--danger,
.ui-kpi-dark .ui-badge--error,
.ui-kpi-dark-soft .ui-badge--danger,
.ui-kpi-dark-soft .ui-badge--error,
.ui-kpi-dark-subtle .ui-badge--danger,
.ui-kpi-dark-subtle .ui-badge--error { color: var(--cherry); }

.ui-contrast-card .ui-badge--warn,
.ui-kpi-dark .ui-badge--warn,
.ui-kpi-dark-soft .ui-badge--warn,
.ui-kpi-dark-subtle .ui-badge--warn { color: var(--princeton-dark); }

/* Pallino di stato.
   Era definito DUE VOLTE, a ~600 righe di distanza: una versione quadrata
   (border-radius 2px, «legend dots») e questa tonda. Vinceva questa perche'
   veniva dopo, quindi la prima non ha mai disegnato niente — ma chi leggeva
   il file dall'alto trovava per prima quella morta. Ne resta una.
   flex-shrink: 0 viene dalla versione tolta: senza, dentro una riga flex il
   pallino si schiaccia a ellisse quando l'etichetta e' lunga. */
.ui-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--primary);
    flex-shrink: 0;
}

/* Timeline */
.ui-timeline {
    display: grid;
    gap: 14px;
    position: relative;
    padding-left: 12px;
}

.ui-timeline::before {
    content: '';
    position: absolute;
    left: 4px;
    top: 4px;
    bottom: 4px;
    width: 2px;
    background: var(--border);
}

.ui-timeline-item {
    display: flex;
    gap: 12px;
    align-items: flex-start;
}

.ui-timeline-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--primary);
    margin-top: 4px;
}

/* Badges extras */
/* Il testo e' --dark-emerald e non --seaweed perche' il fondo e' dark-emerald
   al 12%: fondo e testo venivano da due verdi diversi, e il piu' chiaro dei
   due dava 2,40:1. Il token si chiamava --seaweed-light e portava gia' questo
   valore: rinominato il 06/10/2026, perche' il nome faceva leggere un verde
   che non c'era. */
.ui-badge--success { background: var(--dark-emerald-light); color: var(--dark-emerald); }
/* Burgundy e non cherry: sul proprio velo cherry regge su bianco (4,88) e cade
   dentro le righe di tabella, dove il fondo e' --platinum (4,46). */
.ui-badge--danger { background: var(--cherry-light); color: var(--burgundy); }
.ui-badge--error { background: var(--cherry-light); color: var(--burgundy); }
.ui-badge--secondary { background: rgba(5, 8, 52, 0.08); color: var(--jet); }
.ui-badge--dark { background: var(--jet); color: #ffffff; }

/* Alerts */
.ui-alert {
    display: flex;
    gap: 8px;
    align-items: center;
    padding: 10px 12px;
    border-radius: var(--radius-md);
    font-size: 12px;
    border: 1px solid transparent;
}

/* ⚠️ Un avviso SEGUITO da qualcosa gli sta attaccato: misurato **0 px** fra
   l'avviso «il Registro non copre questa voce» e lo stato vuoto sotto, che li
   fa leggere come un blocco solo — la frase del vuoto sembra la coda
   dell'avviso. Un avviso che è l'ultimo figlio non cambia, e non serve.

   ⚠️ Non si somma a `space-y-*`: in flow normale i margini adiacenti
   COLLASSANO, e dentro `space-y-4` la distanza resta 16 px, non 28 (misurato in
   Chrome 150). Il collasso però NON avviene in un contenitore flex o grid: lì i
   due margini si sommerebbero. Contati il 24/08/2026 su tutto il markup —
   **zero** contenitori flex/grid che dichiarano anche `space-y`. Chi ne
   introduce uno con dentro un avviso rimisuri questa distanza. */
.ui-alert:not(:last-child) {
    margin-bottom: var(--spazio-avviso);
}

/* ⚠️ Un avviso dentro uno SLOT che il JS riempie sfugge alla regola qui sopra,
   e in silenzio: dentro il proprio wrapper l'avviso e' `:last-child`, quindi
   `:not(:last-child)` non lo raggiunge mai — anche quando il wrapper e'
   seguito da una tabella. Misurato il 25/08/2026 sulla nota della ricerca web:
   **0 px** fra la nota e l'intestazione dei recapiti, cioe' esattamente il
   difetto che la regola sopra esiste per impedire, ricomparso perche' il
   markup ha guadagnato un livello.

   Lo stacco lo prende il WRAPPER, non l'avviso: e' l'unico dei due che sa di
   essere seguito da qualcosa. `:not(:empty)` perche' uno slot vuoto non deve
   lasciare un buco quando non c'e' niente da dire. */
.ui-alert-slot:not(:empty) {
    /* ⚠️ Sopra E sotto. Questa regola e' nata quando l'avviso stava PRIMA del
       suo elenco e bastava il margine inferiore; dal 25/08/2026 sta sotto, e
       col solo `margin-bottom` l'avviso tocca la tabella — misurato **0 px**.
       Una regola tarata su una posizione non sopravvive allo spostamento, e
       non lo dice: rende attaccato, che a schermo somiglia a una scelta. */
    margin: var(--spazio-avviso) 0;
}

/* ⚠️ Il testo dell'avviso deve poter STRINGERSI: un figlio flex ha
   `min-width: auto`, quindi non scende sotto il proprio contenuto e sotto i
   ~520 px le parole escono dal bordo invece di andare a capo. Non si vede,
   perche' `.main-content` ha `overflow-x: hidden` e le taglia. L'icona invece
   non si deve stringere. Best practice 5. */
.ui-alert > * { min-width: 0; }
.ui-alert > i { flex-shrink: 0; }

/* ⚠️ Il TESTO di queste varianti non e' la tinta piena: sul proprio velo
   princeton e seaweed stanno a meta' della soglia, e cherry sotto di poco su
   --platinum. Si scrive la versione scura della stessa tinta, come nei badge; i
   numeri li ricalcola il controllo 8 del linter, non questo commento. I veli
   sono i token *-light: un rgba con una terna fuori palette fa uscire 1. */
.ui-alert--warn { background: var(--princeton-light); color: var(--princeton-dark); border-color: rgba(251, 133, 15, 0.30); }
.ui-alert--danger { background: var(--cherry-light); color: var(--burgundy); border-color: rgba(194, 27, 63, 0.30); }
.ui-alert--success { background: rgba(26, 173, 126, 0.10); color: var(--dark-emerald); border-color: rgba(26, 173, 126, 0.30); }
/* ⚠️ Non piu' grigio dal 25/08/2026: neutro non vuol dire invisibile, e questi
   avvisi dicono da dove vengono i dati che si stanno leggendo — la parte che
   cambia quanto valgono. Restano il tono piu' quieto dei quattro; a distinguerli
   dall'ambra e' il significato, non l'intensita'. */
/* ⚠️ L'informazione e' BLU, non viola, dal 07/09/2026. Il viola era entrato il
   25/08 perche' i tre semantici avevano gia' un significato e il blu petrolio di
   allora distava ΔE 11,6 dal testo corpo — sotto il pavimento di 20,8. Col blu
   del marchio quel vincolo non c'e' piu': `--azure-dark` sta a **36,5** da
   `--iron`, 85,8 da cherry, 119,9 da princeton, 81,5 da seaweed, e da' 5,95:1 su
   bianco. Il viola era l'unica tinta calda-fredda estranea alla gamma, e su una
   schermata di avvisi era la cosa piu' colorata che ci fosse. */
.ui-alert--info { background: rgba(58, 99, 168, 0.08); color: var(--azure-dark); border-color: rgba(58, 99, 168, 0.20); }

/* L'avviso che COMPARE a caricamento avvenuto, non quello scritto nel markup:
   e' un modificatore e non il default apposta, altrimenti ogni avviso gia' in
   pagina si dissolverebbe all'apertura e la palestra, che ne ha una decina,
   diventerebbe una cascata che nessuno ha chiesto.
   Stessa tecnica del modale: `allow-discrete` fa transitare anche `display`,
   @starting-style da' i valori del primo frame.
   ⚠️ La dissolvenza NON annulla lo spostamento del contenuto sotto: lo rende
   leggibile. Chi non puo' permetterselo riserva lo spazio, non anima. */
.ui-alert--reveal {
    opacity: 1;
    transform: translateY(0);
    transition: opacity 200ms var(--ease-out),
                transform 200ms var(--ease-out),
                display 200ms allow-discrete;
}

.ui-alert--reveal.hidden {
    display: none;
    opacity: 0;
    transform: translateY(-4px);
}

@starting-style {
    .ui-alert--reveal:not(.hidden) {
        opacity: 0;
        transform: translateY(-4px);
    }
}

.ui-alert-close {
    margin-left: auto;
    background: transparent;
    border: 0;
    color: inherit;
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    opacity: 0.6;
    transition: opacity 0.15s ease;
}

.ui-alert-close:hover { opacity: 1; }

/* Qui stavano `.ui-animate` e `@keyframes demoFadeUp`: 600 ms su `ease`, cioe'
   il doppio del budget su una curva che il sistema non usa, e per di piu' un
   doppione di `.ui-animate-fade-up`. Zero occorrenze nel markup, come la
   palestra dichiarava gia'. */

/* =============================================================================
   16. SIDEBAR NAVIGATION (App-specific)
   ============================================================================= */

.nav-primary-item {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border-radius: var(--sidebar-active-radius);
    font-size: 14px;
    font-weight: 500;
    transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
    color: var(--sidebar-text);
    background: none;
    border: 1px solid transparent;
    cursor: pointer;
    text-align: left;
}

.nav-primary-item:hover {
    background: var(--sidebar-hover-bg);
    color: #ffffff;
    border-color: transparent;
    text-decoration: none;
}

.nav-item-wrapper:hover > .nav-primary-item:not(.active) {
    background: var(--sidebar-hover-bg);
    color: #ffffff;
    border-color: transparent;
}

.nav-item-wrapper:hover > .nav-primary-item:not(.active) .nav-primary-icon,
.nav-item-wrapper:hover > .nav-primary-item:not(.active) .nav-chevron {
    color: #ffffff;
    background: transparent;
}

.nav-primary-item.active {
    background: var(--sidebar-nav-active-bg);
    color: var(--sidebar-nav-active-text);
    font-weight: 600;
    border-color: transparent;
}

.nav-primary-item.active:hover {
    background: var(--sidebar-nav-active-bg);
    color: var(--sidebar-nav-active-text);
}

.nav-primary-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    font-size: 16px;
    line-height: 1;
    color: var(--sidebar-text);
    border-radius: 6px;
    background: transparent;
    transition: color 0.2s ease, opacity 0.2s ease, background-color 0.2s ease;
}.nav-primary-icon::before,
.nav-secondary-icon::before,
.nav-flyout-icon::before,
.nav-chevron::before{
    line-height: 1;
}

.nav-primary-item:hover .nav-primary-icon {
    color: var(--sidebar-text);
    background: transparent;
}

.nav-primary-item.active .nav-primary-icon {
    color: var(--sidebar-nav-active-text);
    background: transparent;
}

.nav-chevron {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    color: var(--sidebar-text);
    line-height: 1;
    /* Ruota restando dov'e': --ease-in-out. Portava una cubic-bezier Material
       scritta a mano, mai dichiarata da nessuna parte. */
    transition: transform 0.25s var(--ease-in-out), color 0.2s ease;
}

.nav-chevron.expanded { transform: rotate(180deg); }
.nav-primary-item.active .nav-chevron { color: var(--sidebar-nav-active-text); }

.nav-secondary-panel {
    margin-top: 4px;
    margin-left: 20px;
    padding-left: 14px;
    border-left: 1.5px dashed var(--sidebar-tree-line);
    overflow: hidden;
    display: grid;
    grid-template-rows: 0fr;
    opacity: 0;
    /* Si apre restando dov'e': --ease-in-out, su tutte e tre le proprieta' —
       sono lo stesso gesto, e l'opacita' su `ease` nudo ne faceva due.
       `0fr -> 1fr` e non `max-height`: il tetto di 1200px su un pannello da
       ~210 rendeva visibile solo una frazione della corsa, e in chiusura i
       primi 150 ms restavano fermi a tutta altezza. La riga a frazione sa
       l'altezza vera; il figlio unico (`.nav-secondary-list`) porta
       `min-height: 0` per poter scendere sotto il proprio contenuto (e
       padding e margine a zero da chiuso, sotto). */
    transition: grid-template-rows 0.3s var(--ease-in-out), opacity 0.25s var(--ease-in-out), margin-top 0.25s var(--ease-in-out);
}

.nav-secondary-panel.expanded {
    grid-template-rows: 1fr;
    opacity: 1;
    margin-top: 8px;
    overflow: visible;
}

.nav-secondary-list {
    min-height: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding: 6px 0 8px;
    margin-bottom: 15px;
    transition: padding 0.3s var(--ease-in-out), margin-bottom 0.3s var(--ease-in-out);
}

/* Una riga `0fr` non scende sotto padding e margine del figlio: chiuso
   lasciava 29 px vuoti sotto ogni voce. Si chiudono insieme alla riga. */
.nav-secondary-panel:not(.expanded) > .nav-secondary-list {
    padding-block: 0;
    margin-bottom: 0;
}

/* ⚠️ Il raggio era `6px` scritto a mano, e coincideva col `--radius-md` di
   allora: le due voci di menu rendevano lo stesso raggio per combinazione, non
   per dichiarazione. Alzando la scala il 07/09/2026 avrebbero cominciato a
   divergere — la primaria a 8, questa ferma a 6 — senza dare nessun errore. */
.nav-secondary-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 10px;
    font-size: 14px;
    font-weight: 500;
    color: var(--sidebar-text);
    border-radius: var(--sidebar-active-radius);
    border: 1px solid transparent;
    background: transparent;
    transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
    text-decoration: none;
}

.nav-secondary-item:hover {
    background: var(--sidebar-hover-bg);
    color: #ffffff;
    text-decoration: none;
}

.nav-secondary-item.active {
    background: var(--sidebar-pagina-bg);
    color: var(--sidebar-pagina-text);
    font-weight: 600;
    text-decoration: none;
}

/* L'icona segue il testo. Senza, resta sul colore ereditato dalla barra —
   chiaro su chiaro — e sparisce dentro la pastiglia. */
.nav-secondary-item.active .nav-secondary-icon { color: var(--sidebar-pagina-text); }

/* La voce di PRIMO livello che e' essa stessa la pagina (nessuna sezione sotto:
   «Componenti»). Prende la pastiglia come una voce di secondo livello, perche'
   dice la stessa cosa — sei qui — e prima prendeva il velo piu' debole della
   SEZIONE, che dice un'altra cosa: «la pagina sta qui dentro».
   ⚠️ I due veli si distinguono per INTENSITA', e i numeri sono di oggi
   (14/09/2026, rimisurati sul reso): pagina **1,83:1** contro la barra, sezione
   **1,29**. Il testo bianco pieno sul velo della pagina misura 10,12. Qui
   stavano scritti 5,19 e 1,48 — veri sulla palette di prima del 07/09/2026 e
   diventati falsi quando la barra ha cambiato fondo, senza che niente lo
   segnalasse: un numero ricopiato accanto alla sua fonte invecchia da solo.
   La classe la dichiara il server in `sidebar.php`: il markup sa se ci sono
   sezioni, il CSS no. */
.nav-primary-item.active.nav-primary-item--pagina,
.nav-primary-item.active.nav-primary-item--pagina:hover {
    background: var(--sidebar-pagina-bg);
    color: var(--sidebar-pagina-text);
}
.nav-primary-item.active.nav-primary-item--pagina .nav-primary-icon {
    color: var(--sidebar-pagina-text);
}

.nav-secondary-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 14px;
    font-size: 14px;
    line-height: 1;
    color: inherit;
    flex-shrink: 0;
}

.nav-secondary-label {
    display: inline-block;
    line-height: 1.3;
}

/* Sidebar Container */
.sidebar-container {
    position: fixed;
    top: 0;
    left: 0;
    width: min(280px, 85vw);
    height: 100vh;
    z-index: 45;
    transform: translateX(-100%);
    /* Entra ed esce dal bordo: --ease-out, la stessa curva e la stessa durata
       del velo (#sidebarOverlay). Erano due curve diverse per due cose che si
       muovono insieme — il pannello su una Material scritta a mano, il velo
       sulla curva di casa. */
    transition: transform 300ms var(--ease-out);
    overflow: hidden;
    box-shadow: none;
    background: var(--sidebar-bg);
    border-right: none;
    color: var(--sidebar-text);
}

.sidebar-container nav {
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: rgba(255,255,255,0.15) transparent;
}
.sidebar-container nav::-webkit-scrollbar { width: 4px; }
.sidebar-container nav::-webkit-scrollbar-track { background: transparent; }
.sidebar-container nav::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 4px; }
.sidebar-container nav::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.25); }

.main-content {
    padding: 32px 36px;
    border-left: none;
    box-shadow: none;
}

.sidebar-container.is-open {
    transform: translateX(0);
}

.sidebar-brand-text { color: var(--sidebar-text); font-weight: 700; }
.sidebar-container .sidebar-brand {
    padding: 0;
}
.sidebar-container > .px-3,
.sidebar-container > nav.px-3 {
    padding-right: 22px;
}

.sidebar-container .border-b { border-color: var(--sidebar-border) !important; }
.sidebar-container .border-t, .sidebar-container .border-dashed { border-color: var(--sidebar-border) !important; }

.sidebar-user-btn { color: var(--sidebar-text); }
.sidebar-user-btn:hover { background: var(--sidebar-hover-bg); }
.sidebar-container svg { color: var(--sidebar-text); }
.sidebar-user-icon { flex-shrink: 0; }
.sidebar-user-info > div:first-child { color: var(--sidebar-text); font-weight: 600; }
.sidebar-user-info > div:last-child { color: var(--sidebar-text-muted); }
.sidebar-user-more { color: var(--sidebar-text-muted); }

/* ── Contatore di spesa in sidebar ──────────────────────────────────────────
   Sta sopra il pannello utente perche' l'offerta promette un contatore SEMPRE
   VISIBILE e Spark non ha una topbar. Numeri in Roboto Mono come ovunque:
   due importi incolonnati che cambiano ogni mese devono restare allineati. */
/* ⚠️ Il contatore non appartiene ne' al menu sopra ne' alla zona utente sotto:
   e' una terza cosa, e lo spazio e' l'unica dichiarazione che lo dice. Con
   `margin-bottom: 4px` — com'era fino al 21/08/2026 — si appoggiava al filetto
   del pannello utente e si leggeva come la prima riga di quel blocco, mentre
   sopra aveva 0 px dal menu: incollato da entrambe le parti, e quindi
   raggruppato con tutti e due.
   Le due misure verticali non sono uguali apposta: sopra c'e' un'area che
   SCORRE, e lo spazio li' esiste solo quando il menu e' corto; sotto c'e' un
   confine fisso, ed e' quello che va staccato.
   ⚠️ E il margine DESTRO e' 22, non 12: `.sidebar-container > .px-3` da'
   `padding-right: 22px` alla nav e al pannello utente, quindi ogni voce di
   menu, ogni etichetta di gruppo e il bottone utente finiscono a 238 px. Con
   12 il contatore finiva a 248 — l'unico elemento della sidebar fuori dalla
   colonna, e 10 px di sporgenza si leggono come «pesa piu' degli altri» prima
   ancora che come un disallineamento. Misurato il 21/08/2026. */
.ui-spesa-badge {
    margin: 12px 22px 16px 12px;
    padding: 10px 12px;
    border-radius: var(--radius-md);
    background: var(--sidebar-hover-bg);
}
.ui-spesa-badge-riga {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    min-width: 0;
}
.ui-spesa-badge-riga + .ui-spesa-badge-riga { margin-top: 2px; }
.ui-spesa-badge-etichetta {
    color: var(--sidebar-text-muted);
    font-size: 11px;
    white-space: nowrap;
}
/* Il residuo a zero non si dice colorando il numero: sulla sidebar scura cherry
   da' 2,13:1, cioe' il segnale sparisce proprio quando serve. Si dice col chip
   pieno, dove il bianco su cherry misura 5,96. */
.ui-spesa-badge-valore--esaurito {
    background: var(--cherry);
    color: #fff;
    padding: 0 5px;
    border-radius: var(--radius-sm);
}

.ui-spesa-badge-valore {
    color: var(--sidebar-text);
    font-family: var(--font-mono);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.ui-spesa-badge .v--empty { font-size: 11px; }

/* Il riquadro con cui la palestra mostra il contatore sul fondo VERO della
   sidebar: fuori da quel fondo i tre stati non renderebbero come a schermo, e
   si copierebbe un componente giudicato sul contesto sbagliato. */
.ui-sidebar-preview {
    width: 260px;
    padding: 12px 0;
    border-radius: 8px;
    background: var(--sidebar-bg);
}

.sidebar-close-btn { color: var(--sidebar-text-muted); }
.sidebar-close-btn:hover { color: var(--sidebar-text); background: var(--sidebar-hover-bg); }

.sidebar-collapse-zone {
    display: flex;
    justify-content: center;
    padding: 10px 0 14px;
}


/* Mobile */
@media (max-width: 1023px) {
    body { padding-top: 56px; }
    .main-content { padding-top: 0; position: relative; z-index: 1; }

    /* Il flyout serve solo alla sidebar desktop COMPRESSA (hover su
       .sidebar-container.is-collapsed, dentro il media query ≥1024px più
       sotto). Il suo `display: none` era dichiarato SOLO là dentro: sotto i
       1024px il div non aveva nessun display di base, quindi il browser
       applicava block — un secondo elenco piatto sotto ogni voce accordion
       già espansa, sempre visibile, mai voluto su mobile. */
    .nav-flyout { display: none; }
}

/* Desktop sidebar */
@media (min-width: 1024px) {
    .sidebar-container {
        position: sticky;
        top: 0;
        left: auto;
        width: 260px;
        min-width: 260px;
        max-width: 260px;
        height: 100vh;
        flex-shrink: 0;
        transform: none;
        z-index: 100;
        overflow: hidden;
        border-right: none;
        margin-right: 0;
        /* Da 260 a 72 px si passava di scatto. La `transition: transform` del
           blocco mobile qui sopravvive ma non anima niente: a questa larghezza
           `transform` vale `none` in tutti e due gli stati, quindi sembrava
           esserci un'animazione e non c'era.
           --ease-in-out perche' il pannello non entra e non esce: resta a
           schermo e cambia misura. E' una transizione di LAYOUT, l'unica del
           sistema: la larghezza della sidebar e' anche quella del contenuto,
           e non esiste modo di ottenerla con transform senza staccare la
           pagina dal suo flusso. Costo misurato in fondo a questo blocco. */
        transition: width 200ms var(--ease-in-out),
                    min-width 200ms var(--ease-in-out),
                    max-width 200ms var(--ease-in-out);
    }

    body { padding-top: 0; }

    /* Let body scroll, not main-content - required for sticky sidebar */
    .main-content {
        overflow-y: visible;
        height: auto;
    }

    .sidebar-container.is-collapsed {
        width: 72px;
        min-width: 72px;
        max-width: 72px;
        z-index: 50;
        overflow: visible;
    }

    .sidebar-container.is-collapsed nav { overflow: visible; }

    .sidebar-container.is-collapsed .sidebar-brand-text,
    .sidebar-container.is-collapsed .nav-primary-item > span,
    .sidebar-container.is-collapsed .nav-chevron,
    .sidebar-container.is-collapsed .sidebar-user-info,
    .sidebar-container.is-collapsed .sidebar-user-more,
    /* A 72 px non ci stanno due colonne: resta il solo importo speso, e
       l'etichetta sparisce invece di troncarsi — un'etichetta a meta' e'
       peggio della sua assenza. */
    .sidebar-container.is-collapsed .ui-spesa-badge-etichetta,
    .sidebar-container.is-collapsed .ui-spesa-badge-riga + .ui-spesa-badge-riga {
        opacity: 0;
        width: 0;
        overflow: hidden;
        white-space: nowrap;
    }

    .sidebar-container.is-collapsed .ui-spesa-badge {
        margin-left: 8px;
        margin-right: 8px;
        padding: 6px 4px;
    }
    .sidebar-container.is-collapsed .ui-spesa-badge-riga { justify-content: center; }
    .sidebar-container.is-collapsed .ui-spesa-badge-valore { font-size: 10px; }

    .sidebar-container.is-collapsed .nav-secondary-panel { display: none !important; }
    .sidebar-container.is-collapsed .nav-primary-item { justify-content: center; padding: 12px; gap: 0; }
    .sidebar-container.is-collapsed .nav-primary-item.active,
    .sidebar-container.is-collapsed .nav-primary-item.active:hover {
        background: var(--sidebar-nav-active-bg) !important;
        color: var(--sidebar-nav-active-text) !important;
        border-color: transparent !important;
        box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(5, 8, 52, 0.05) !important;
    }
    .sidebar-container.is-collapsed .nav-primary-item.active .nav-primary-icon,
    .sidebar-container.is-collapsed .nav-primary-item.active:hover .nav-primary-icon {
        color: var(--sidebar-nav-active-text) !important;
    }
    .sidebar-container.is-collapsed nav { padding: 8px; }
    .sidebar-container.is-collapsed .nav-item-wrapper { display: flex; justify-content: center; }
    .sidebar-container.is-collapsed .sidebar-brand, .sidebar-container.is-collapsed .sidebar-brand > div:first-child { justify-content: center; }
    .sidebar-container.is-collapsed .sidebar-user-btn { justify-content: center; padding: 12px; gap: 0; }
    .sidebar-container.is-collapsed .sidebar-collapse-zone { justify-content: center; }
    .sidebar-container.is-collapsed .px-3 { padding-left: 8px; padding-right: 8px; }

    .sidebar-container.is-collapsed #userMenuDropdown {
        left: 100%;
        right: auto;
        bottom: 0;
        top: auto;
        margin-left: 8px;
        margin-bottom: 0;
        width: 200px;
    }

    .sidebar-container.is-collapsed .nav-primary-item { position: relative; }

    .sidebar-container.is-collapsed .nav-primary-item::after {
        content: attr(data-tooltip);
        position: absolute;
        left: 100%;
        top: 50%;
        transform: translateY(-50%);
        margin-left: 12px;
        padding: 10px 12px;
        background: var(--jet);
        color: #ffffff;
        font-size: 14px;
        font-weight: 700;
        border-radius: 7px;
        white-space: nowrap;
        opacity: 0;
        visibility: hidden;
        pointer-events: none;
        transition: opacity 0.2s ease, visibility 0.2s ease;
        z-index: 9999;
        border: 1px solid var(--jet);
    }

    .sidebar-container.is-collapsed .nav-primary-item::before {
        display: none;
    }

    .sidebar-container.is-collapsed .nav-primary-item:hover::after,
    .sidebar-container.is-collapsed .nav-primary-item:hover::before {
        opacity: 1;
        visibility: visible;
    }

    .sidebar-container.is-collapsed .nav-item-wrapper:has(.nav-secondary-panel) .nav-primary-item::after,
    .sidebar-container.is-collapsed .nav-item-wrapper:has(.nav-secondary-panel) .nav-primary-item::before { display: none; }

    .nav-flyout { display: none; }

    .sidebar-container.is-collapsed .nav-item-wrapper { position: relative; }

    .sidebar-container.is-collapsed .nav-flyout {
        display: block;
        position: absolute;
        left: 100%;
        top: 0;
        margin-left: 8px;
        min-width: 200px;
        background: var(--white);
        border: 1px solid var(--alabaster);
        border-radius: 0 4px 4px 0;
        padding: 0;
        opacity: 0;
        visibility: hidden;
        /* Pende dal bordo della sidebar: nasce da li', non dal proprio centro. */
        transform-origin: left center;
        transform: translateX(-8px) scale(0.97);
        transition: opacity 150ms var(--ease-out), visibility 150ms var(--ease-out), transform 150ms var(--ease-out);
        z-index: 9999;
        color: var(--jet);
    }

    .sidebar-container.is-collapsed .nav-flyout::before {
        display: none;
    }

    .sidebar-container.is-collapsed .nav-item-wrapper:hover .nav-flyout {
        opacity: 1;
        visibility: visible;
        transform: none;
    }

    .nav-flyout-header {
        display: flex;
        align-items: center;
        font-size: 14px;
        font-weight: 700;
        color: var(--jet);
        background: var(--platinum);
        min-height: 46px;
        padding: 0 12px;
        border: 1px solid var(--alabaster);
        border-radius: 0 4px 0 0;
        margin-bottom: 8px;
    }

    .nav-flyout-item {
        display: flex;
        align-items: center;
        gap: 8px;
        padding: 10px 12px;
        font-size: 14px;
        font-weight: 500;
        color: var(--iron);
        border-radius: 4px;
        border: 1px solid transparent;
        transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
        text-decoration: none;
    }

    .nav-flyout-icon {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 14px;
        font-size: 14px;
        line-height: 1;
        color: inherit;
        flex-shrink: 0;
    }

    /* ⚠️ Hover e attivo erano la STESSA regola, e sono due cose diverse:
       passandoci sopra col mouse una voce qualunque prendeva l'aspetto della
       pagina corrente, quindi nel pannello ne risultavano due. Qui il pannello
       e' CHIARO, quindi l'attivo non e' il velo della barra: e' --alabaster
       (1,468:1 sul bianco) contro il --platinum dell'hover (1,103), piu' il
       peso 600 e il testo --jet. */
    .nav-flyout-item:hover {
        background: var(--platinum);
        color: var(--jet);
        text-decoration: none;
    }

    .nav-flyout-item.active,
    .nav-flyout-item.active:hover {
        background: var(--alabaster);
        color: var(--jet);
        font-weight: 600;
        text-decoration: none;
    }
}

/* =============================================================================
   16b. SIDEBAR NAVIGATION — NEW STRUCTURE (mobile-shell, brand-shell, tertiary, flyout groups)
   ============================================================================= */

/* Mobile header */
/* Fondo scuro come la sidebar, e dagli stessi token: sotto i 1024 px il guscio
   E' la sidebar chiusa, e prendere `--sidebar-bg` invece di ricopiarne il valore
   vuol dire che un cambio di palette li sposta insieme. Il prefisso `.mobile-shell`
   e' gia' dichiarato fra i FONDI_SCURI della sonda del design system. */
.mobile-shell-header {
    background: var(--sidebar-bg);
    border-bottom: 1px solid var(--sidebar-border);
}
/* Il riquadro chiaro resta: la favicon Axerta e' una X blu navy su fondo
   trasparente, e su `--jet` sparirebbe. Qui il riquadro non e' decorazione,
   e' cio' che rende leggibile il marchio del cliente. */
.mobile-shell-logo {
    width: 36px;
    height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 10px;
    background: var(--white);
    flex-shrink: 0;
}
.mobile-shell-logo img {
    display: block;
    border-radius: 6px;
}
/* Stessa scala del brand della sidebar (.sidebar-brand-text, 16px/700): sopra e
   sotto i 1024 px e' lo stesso marchio, e due misure diverse lo fanno sembrare
   due intestazioni diverse. Il sopratitolo "Lead Intelligence" e' uscito il
   24/08/2026: era la sola parte del guscio mobile che il desktop non aveva. */
.mobile-shell-title {
    font-size: 16px;
    font-weight: 700;
    color: var(--sidebar-text);
    line-height: 1.2;
}
.mobile-shell-toggle {
    width: 36px;
    height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--sidebar-border);
    border-radius: 10px;
    background: transparent;
    color: var(--sidebar-text);
    cursor: pointer;
}
.mobile-shell-toggle:hover {
    background: var(--sidebar-hover-bg);
}

/* Brand shell */
.sidebar-brand-shell {
    position: relative;
    /* Era `style="padding-left: 24px; padding-right: 24px;"` sul markup, ed
       e' il motivo per cui la variante collassata piu' sotto doveva usare
       !important su quattro righe: uno stile inline vince su qualunque
       classe. Spostato qui, gli !important non servono piu'. */
    padding-left: 24px;
    padding-right: 24px;
}
.sidebar-brand-main {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    min-height: 40px;
    padding-right: 32px;
}
.sidebar-top-actions {
    display: inline-flex;
    align-items: center;
    gap: 0;
    flex-shrink: 0;
}
/* Il marchio del cliente nella sidebar. Nessun bordo — il filo del guscio
   mobile era decorazione — ma il fondo chiaro resta: la X di Axerta e'
   `#002B5F` su trasparente e su `--jet` misura 1,09:1, cioe' sparisce.
   ⚠️ Sopravvive al collasso della sidebar: li' `.sidebar-brand-text` va a
   `display: none` e il logo resta l'unica cosa che dice di che prodotto si
   tratta — prima di questo la barra compressa non aveva nessun marchio. */
.sidebar-brand-logo {
    width: 30px;
    height: 30px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    background: var(--white);
    flex-shrink: 0;
}
.sidebar-brand-logo img { display: block; border-radius: 5px; }

.sidebar-brand-copy-wrap {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}
.sidebar-collapse-toggle {
    position: absolute;
    top: 50%;
    right: 12px;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 0;
    background: transparent;
    color: rgba(255, 255, 255, 0.45);
    cursor: pointer;
    font-size: 16px;
    line-height: 1;
    transition: color 0.16s ease;
}
.sidebar-collapse-toggle:hover { color: #ffffff; }

/* Il fuoco da tastiera sulla barra scura. Le voci non ne dichiaravano uno
   (quelle di terzo livello lo spegnevano con `outline: 0`), e quello di casa
   e' jet: sul navy non si vede. Solo i comandi che stanno SUL fondo scuro — il
   flyout e il pannello utente sono chiari e tengono `--focus-ring`. */
.nav-primary-item:focus-visible,
.nav-secondary-item:focus-visible,
.nav-tertiary-item:focus-visible,
.sidebar-collapse-toggle:focus-visible,
.sidebar-close-btn:focus-visible,
.sidebar-user-btn:focus-visible,
.mobile-shell-toggle:focus-visible {
    outline: 2px solid var(--sidebar-focus-ring);
    outline-offset: 2px;
}

/* Nav section labels */
.nav-section-label {
    padding: 8px 10px 6px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--sidebar-text-muted);
}
.nav-section-label--secondary { padding-top: 14px; }

/* Tertiary (3rd level) items */
.nav-tertiary-group {
    display: grid;
    gap: 4px;
}
.nav-tertiary-list {
    display: grid;
    gap: 2px;
    margin-left: 6px;
    padding-left: 16px;
    border-left: 1.5px dashed var(--sidebar-tree-line);
    padding-top: 2px;
    padding-bottom: 2px;
}
.nav-tertiary-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    min-height: 28px;
    padding: 5px 10px;
    border-radius: 4px;
    color: var(--sidebar-text-muted);
    font-size: 13px;
    border: 0;
    outline: 0;
    transition: background 0.15s ease, color 0.15s ease;
}
.nav-tertiary-item:hover {
    background: var(--sidebar-hover-bg);
    color: #ffffff;
}
.nav-tertiary-item.active {
    background: var(--sidebar-nav-active-bg);
    color: var(--sidebar-nav-active-text);
    font-weight: 600;
}
.nav-tertiary-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 16px;
    height: 14px;
    padding: 0 4px;
    margin-left: 4px;
    border-radius: var(--badge-radius);
    font-size: 9px;
    font-weight: 700;
    line-height: 1;
    background: var(--conteggio);
    color: var(--conteggio-testo);
    border: 1px solid var(--sidebar-conteggio-filo);
    flex-shrink: 0;
}

/* Flyout groups (collapsed sidebar) */
.nav-flyout-group { display: grid; gap: 4px; }
.nav-flyout-sublist {
    display: grid;
    gap: 3px;
    padding: 0 0 8px 26px;
}
.nav-flyout-subitem {
    display: block;
    padding: 6px 10px;
    border-radius: 4px;
    /* ⚠️ NON `--silver`: il pannello del flyout ha `background: var(--white)`
       dichiarato in un ALTRO blocco (`.sidebar-container.is-collapsed
       .nav-flyout`), quindi il controllo 8 — che guarda le sole coppie dello
       stesso blocco — non poteva vederlo, e silver su bianco misura 2,34:1.
       Era l'ultimo uso di silver come TESTO rimasto in casa, contro la regola
       gia' scritta in DESIGN_SYSTEM.md:81. Stone ne fa 5,01. */
    color: var(--text-light);
    font-size: 13px;
    border: 0;
    outline: 0;
}
.nav-flyout-subitem:hover {
    background: var(--platinum);
    color: var(--jet);
}
/* Stesso fondo della voce di pannello: dentro il flyout la pagina corrente si
   dichiara con --alabaster, un gradino sopra il --platinum dell'hover. */
.nav-flyout-subitem.active,
.nav-flyout-subitem.active:hover {
    background: var(--alabaster);
    color: var(--jet);
    font-weight: 600;
    box-shadow: none;
}

/* User panel */
.sidebar-user-panel { border-color: var(--sidebar-border); }
.sidebar-user-dropdown {
    background: var(--white);
    border: 1px solid var(--alabaster);
    border-radius: var(--radius-md);
}

/* Collapsed sidebar — new brand shell */
@media (min-width: 1024px) {
    .sidebar-container.is-collapsed .sidebar-brand-text,
    .sidebar-container.is-collapsed .nav-section-label,
    .sidebar-container.is-collapsed .sidebar-user-info,
    .sidebar-container.is-collapsed .sidebar-user-more {
        display: none;
    }
    .sidebar-container.is-collapsed .sidebar-brand-copy-wrap {
        justify-content: center;
        width: 100%;
    }
    /* ⚠️ Nascondere il TESTO non basta a centrare il logo: il suo contenitore
       resta un flex item, e il `gap: 12px` che lo precede si somma comunque.
       Misurato a sidebar collassata — `.sidebar-brand-copy-wrap` largo 42px
       invece di 30 — e centrare 42 sposta il marchio di 6px a sinistra, cioe'
       meta' del gap. Un elemento di larghezza zero non e' un elemento assente:
       il gap lo conta lo stesso. */
    .sidebar-container.is-collapsed .sidebar-brand-copy {
        display: none;
    }
    .sidebar-container.is-collapsed .sidebar-brand-shell {
        display: flex;
        flex-direction: column;
        align-items: center;
        padding-top: 16px;
        padding-bottom: 14px;
        padding-left: 8px;
        padding-right: 8px;
    }
    .sidebar-container.is-collapsed .sidebar-brand {
        order: 1;
        width: 100%;
    }
    .sidebar-container.is-collapsed .sidebar-brand-main {
        flex-direction: column;
        justify-content: flex-start;
        align-items: center;
        gap: 10px;
        min-height: 0;
        padding-right: 0;
    }
    .sidebar-container.is-collapsed .sidebar-top-actions {
        width: 100%;
        justify-content: center;
    }
    .sidebar-container.is-collapsed .sidebar-collapse-toggle {
        position: static;
        order: 2;
        transform: none;
        margin-top: 10px;
    }
}

/* =============================================================================
   17. RESPONSIVE
   ============================================================================= */

@media (max-width: 640px) {
    /* Widget responsiveness */
    .widget-card {
        min-height: auto;
    }

    /* Sotto i 640 px il titolo torna a misurarsi sul CONTENUTO. Sopra, la base
       a zero serve ad allineare le card di una stessa riga (l'intestazione non
       va a capo e i valori restano alla stessa quota); qui le card sono in
       colonna, non c'e' nessuna riga da allineare, e un'intestazione che va a
       capo conserva il titolo intero invece di troncarlo — misurato sulla
       palestra a 375 px: 12 titoli troncati con la regola generale, 21 con la
       base a zero. */
    .widget-header .widget-title { flex: 0 1 auto; }

    /* E i BLOCCHI DI COMANDI dell'intestazione vanno a capo. In fila su una
       riga sola si stringono a vicenda: nell'intestazione del cruscotto —
       etichetta, tendina, ricerca e tooltip in 269 px — il campo di ricerca
       scendeva a 170 px, cioe' sotto il suo segnaposto (148 utili contro 152),
       e il testo che il campo promette di accettare restava tagliato. */
    .widget-header > div { flex-wrap: wrap; }
    .widget-card canvas { max-height: 180px; }

    .widget-title {
        font-size: 9px;
        gap: 4px;
        margin-bottom: 8px;
    }
    .widget-title i { font-size: 9px; }

    .ui-metric { min-height: auto; }

    /* Tabs on mobile */
    .ui-tabs .ui-tablist { flex-wrap: wrap; gap: 2px; }
    .ui-tab { padding: 6px 10px; font-size: 11px; }
    .widget-body .ui-tabs .ui-tab { padding: 5px 10px; font-size: 10px; }

    /* Pills and badges */
    .ui-pill { padding: 2px 8px; font-size: 9px; min-width: 20px; }
    .ui-chip { padding: 4px 10px; font-size: 10px; }

    /* Chart containers */
    .ui-chart-container { min-height: 120px; }
}

/* =============================================================================
   18. ADVANCED COMPONENTS
   ============================================================================= */

/* -----------------------------------------------------------------------------
   18.1 Variance / Delta Indicators
   ----------------------------------------------------------------------------- */

.ui-variance {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border-radius: 4px;
    font-size: 11px;
    font-weight: 700;
}

/* ⚠️ `--dark-emerald` e non `--success`: seaweed sul proprio velo da' 2,37:1.
   `.ui-badge--success` era gia' corretto e questo no — due punti che dicono la
   stessa cosa con due colori diversi. */
.ui-variance-positive {
    background: rgba(26, 173, 126, 0.1);
    color: var(--dark-emerald);
}

.ui-variance-negative {
    background: var(--cherry-light);
    color: var(--burgundy);
}

.ui-variance i {
    font-size: 9px;
}/* -----------------------------------------------------------------------------
   18.2 Gauge / Speedometer
   ----------------------------------------------------------------------------- */
/* -----------------------------------------------------------------------------
   18.3 Skeleton Loaders
   ----------------------------------------------------------------------------- */


.ui-skeleton-kpi {
    height: 80px;
    border-radius: 6px;
}

/* -----------------------------------------------------------------------------
   18.4 Date Range Picker
   ----------------------------------------------------------------------------- */

/* ⚠️ `.ui-date-range` — la scatola bordata che avrebbe dovuto contenere i due
   campi — e' uscita il 14/09/2026: zero consumatori, e il calendario vero e'
   un `ui-input ui-daterange-picker` che `initDatepicker()` monta da se'. Le due
   regole che seguono restano perche' sono VIVE: i preset del menu del periodo. */

.ui-date-range-presets {
    display: flex;
    gap: 4px;
    flex-wrap: wrap;
}

.ui-date-preset {
    padding: 4px 10px;
    font-size: 11px;
    font-weight: 500;
    color: var(--text-muted);
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background-color 150ms var(--ease-out), color 150ms var(--ease-out);
}

.ui-date-preset:hover {
    background: var(--primary);
    color: white;
}

.ui-date-preset.active {
    background: var(--primary);
    color: white;
}

/* -----------------------------------------------------------------------------
   18.5 Step Wizard / Stepper
   ----------------------------------------------------------------------------- */

.ui-stepper {
    display: flex;
    align-items: flex-start;
    gap: 0;
}

.ui-step {
    display: flex;
    flex-direction: column;
    align-items: center;
    flex: 1;
    position: relative;
}

.ui-step:not(:last-child)::after {
    content: '';
    position: absolute;
    top: 14px;
    left: calc(50% + 18px);
    right: calc(-50% + 18px);
    height: 2px;
    background: var(--alabaster);
}

.ui-step.completed:not(:last-child)::after {
    background: var(--primary);
}

.ui-step-indicator {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    font-weight: 600;
    background: var(--platinum);
    color: var(--stone);
    border: 2px solid var(--alabaster);
    position: relative;
    z-index: 1;
    transition: background-color 200ms var(--ease-out), color 200ms var(--ease-out), border-color 200ms var(--ease-out);
}

/* La variante scura, non la tinta piena: il cerchio del passo corrente è un
   oggetto grafico e WCAG 1.4.11 gli chiede 3:1 sul fondo — princeton pieno
   ne misurava 2,25. Col fondo scuro il numero gira a bianco. */
.ui-step.active .ui-step-indicator {
    background: var(--ui-warning);
    color: var(--white);
    border-color: var(--ui-warning);
}

.ui-step.completed .ui-step-indicator {
    background: var(--primary);
    color: white;
    border-color: var(--primary);
}

.ui-step-label {
    margin-top: 8px;
    font-size: 11px;
    font-weight: 500;
    color: var(--stone);
    text-align: center;
}

.ui-step.active .ui-step-label {
    color: var(--accent-dark);
    font-weight: 600;
}

.ui-step.completed .ui-step-label {
    color: var(--iron);
}

/* Stepper verticale */
.ui-stepper-vertical {
    flex-direction: column;
    gap: 0;
}

.ui-stepper-vertical .ui-step {
    flex-direction: row;
    align-items: flex-start;
    padding-left: 0;
}

.ui-stepper-vertical .ui-step:not(:last-child)::after {
    top: 28px;
    left: 13px;
    right: auto;
    width: 2px;
    height: calc(100% - 8px);
    background: var(--alabaster);
}

.ui-stepper-vertical .ui-step.completed:not(:last-child)::after {
    background: var(--primary);
}

.ui-stepper-vertical .ui-step-content {
    margin-left: 12px;
    padding-bottom: 24px;
}

.ui-stepper-vertical .ui-step-label {
    margin-top: 0;
    text-align: left;
}

/* Lo stepper NAVIGABILE (21/09/2026, Liste lead — il configuratore). Ogni passo
   è un `<button>`: quelli raggiungibili si premono per tornarci o per saltare a
   un passo già completo, quelli ancora chiusi sono `disabled`, e il corrente
   porta `aria-current="step"`. Il bottone si spoglia del suo aspetto nativo e
   prende quello del passo: la regola sta sul TAG, così lo stepper descrittivo
   (`<div>`) resta com'era. */
button.ui-step {
    padding: 0;
    border: 0;
    background: transparent;
    font: inherit;
    color: inherit;
    cursor: pointer;
}
button.ui-step:disabled { cursor: default; }
button.ui-step:not(:disabled):not(.active):hover .ui-step-indicator { border-color: var(--iron); }
button.ui-step:not(:disabled):not(.active):hover .ui-step-label { color: var(--text-heading); }
button.ui-step:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* Sotto i 640 px cinque etichette non stanno in fila: resta quella del passo
   corrente, le altre diventano i soli numeri. L'etichetta nascosta resta
   leggibile da uno screen reader — si ritaglia, non si toglie. */
@media (max-width: 640px) {
    .ui-stepper:not(.ui-stepper-vertical) .ui-step:not(.active) .ui-step-label {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        white-space: nowrap;
    }
}

/* Lo stepper LEGGERO (`ui-stepper--leggero`, 22/09/2026, i tre configuratori —
   Nicola: «questa parte iniziale va pulita»). Nella testata di un configuratore
   lo stepper è navigazione, non il dato: i numeri vivi accanto sono ciò che si
   guarda. Cerchio da 32 col numero a 14 (era 24 col numero a 11: il 22/09/2026
   Nicola ha chiesto «il pallino più grande»; la spunta eredita i 14), filo da
   1 px, e il passo FATTO non è più un
   disco pieno scuro ma un cerchio bianco col filo `--primary` e la spunta —
   cinque dischi neri di fila erano la cosa più pesante della card. Resta pieno
   il solo passo corrente (`--scelto`, la regola qui sotto): uno, e si trova a
   colpo d'occhio. Il filo fra due passi fatti è `--silver`, non jet: su
   Campagne i passi sono facoltativi e non in sequenza, e un binario nero
   raccontava un avanzamento che non c'è. */
.ui-stepper--leggero .ui-step-indicator {
    width: 32px;
    height: 32px;
    font-size: 14px;
    border-width: 1px;
}
/* Il filo fra i passi passa a meta' del cerchio e se ne stacca di 4 px: si
   sposta insieme alla sua misura. */
.ui-stepper--leggero .ui-step:not(:last-child)::after {
    top: 16px;
    left: calc(50% + 20px);
    right: calc(-50% + 20px);
    height: 1px;
}
.ui-stepper--leggero .ui-step.completed:not(:last-child)::after { background: var(--silver); }
.ui-stepper--leggero .ui-step.completed .ui-step-indicator {
    background: var(--white);
    color: var(--primary);
    border-color: var(--primary);
}
.ui-stepper--leggero .ui-step-label { margin-top: 6px; }
/* Il passo corrente dei configuratori è `--scelto` pieno col numero
   `--scelto-testo`, senza anello: tolto il 22/09/2026 su richiesta di Nicola,
   che lo leggeva come un bordo estraneo. Non serve: il cerchio pieno supera da
   sé il 3:1 di un oggetto grafico sui fondi chiari, e il numero bianco il 4,5
   sul pieno (li verifica il linter, `scelto_senza_contrasto`). L'etichetta
   resta jet, col peso che la distingue dalle altre. */
.ui-stepper--leggero .ui-step.active .ui-step-indicator {
    background: var(--scelto);
    color: var(--scelto-testo);
    border-color: var(--scelto);
}
.ui-stepper--leggero .ui-step.active .ui-step-label { color: var(--text-heading); }

/* -----------------------------------------------------------------------------
   18.6 Export Buttons
   ----------------------------------------------------------------------------- */


/* =============================================================================
   MODALS
   ============================================================================= */

/**
 * L'entrata e l'uscita sono guidate dalla `.hidden` che c'era gia', non da una
 * classe nuova: i modali si aprono in 88 punti diversi, 53 via BiUI.openModal()
 * e 35 con classList.remove('hidden') diretto. Una classe in piu' avrebbe
 * voluto dire toccarli tutti, e dimenticarne qualcuno.
 *
 * `transition-behavior: allow-discrete` fa transitare anche `display`, e
 * @starting-style da' i valori di partenza al primo frame in cui l'elemento
 * viene reso. Senza, da display:none non parte niente.
 */
.ui-modal {
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(5, 8, 52, 0.45);
    z-index: 100;
    padding: 16px;
    opacity: 1;
    transition: opacity 200ms var(--ease-out),
                display 200ms allow-discrete;
}

.ui-modal.hidden {
    display: none;
    opacity: 0;
}

@starting-style {
    .ui-modal:not(.hidden) {
        opacity: 0;
    }
}

.ui-modal-card {
    background: #ffffff;
    border-radius: var(--radius-lg);
    width: 100%;
    max-width: 420px;
    padding: 28px 24px;
    max-height: 90vh;
    overflow-y: auto;
    /* La card resta ancorata al centro: il modale e' l'eccezione fra i
       pannelli, non nasce da un trigger e non ha un angolo da cui crescere. */
    transition: transform 200ms var(--ease-out),
                opacity 200ms var(--ease-out);
}

.ui-modal.hidden .ui-modal-card {
    transform: scale(0.97);
    opacity: 0;
}

@starting-style {
    .ui-modal:not(.hidden) .ui-modal-card {
        transform: scale(0.97);
        opacity: 0;
    }
}
.ui-modal-card--md { max-width: 520px; }
.ui-modal-card--lg { max-width: 600px; }
.ui-modal-card--xl { max-width: 920px; }

/* =============================================================================
   MICRO ANIMATIONS
   Animazioni sottili per rendere l'UI più reattiva e accattivante
   ============================================================================= */

/* -----------------------------------------------------------------------------
   Keyframes
   ----------------------------------------------------------------------------- */

/* Fade in from bottom */
@keyframes ui-fade-up {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: translateY(0); }
}

/* Fade in from top */
/* Scale in */
/* Subtle bounce */
/* Pulse glow */
/* Shake (for errors) */
/* Spin */
@keyframes ui-spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}

/* Slide in from right */
/* Highlight flash */
/* Ripple effect */
/* Progress slide (indeterminate) */
@keyframes ui-progress-slide {
    0% { transform: translateX(-100%); }
    100% { transform: translateX(400%); }
}

/* Shimmer effect - Premium */
/* -----------------------------------------------------------------------------
   Animation Utility Classes
   ----------------------------------------------------------------------------- */

/* Dissolvenza pura, senza spostamento. Non e' un doppione di ui-fade-up: vale
   dove il contenuto va LETTO mentre arriva — un numero da 44px che sale di 8px
   mentre lo si mette a fuoco si legge peggio, non meglio. */
@keyframes ui-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

.ui-animate-fade-up { animation: ui-fade-up 300ms var(--ease-out) forwards; }
.ui-animate-fade-in { animation: ui-fade-in 180ms var(--ease-out); }
.ui-animate-spin { animation: ui-spin 1s linear infinite; }
.bi-spin { animation: ui-spin 1s linear infinite; display: inline-block; }
/* -----------------------------------------------------------------------------
   Skeleton Loaders - Premium Shimmer Effect
   ----------------------------------------------------------------------------- */

.ui-skeleton {
    background: linear-gradient(
        90deg,
        var(--platinum) 0%,
        var(--alabaster) 20%,
        var(--platinum) 40%,
        var(--platinum) 100%
    );
    background-size: 200% 100%;
    animation: ui-skeleton-pulse 1.5s linear infinite;
    border-radius: var(--radius-sm);
    position: relative;
    overflow: hidden;
}

/* Una corsa sola, non andata e ritorno: con background-size 200% le due
   posizioni distano due periodi del gradiente, quindi il salto del ciclo non
   si vede e il riflesso scorre invece di rimbalzare. */
@keyframes ui-skeleton-pulse {
    from { background-position: 200% 0; }
    to { background-position: -200% 0; }
}

/* Shimmer overlay - Premium */
/* Delayed animations */
/* -----------------------------------------------------------------------------
   Hover Micro-interactions
   ----------------------------------------------------------------------------- */

/* Lift on hover (for cards, widgets) - Premium */
/* Subtle scale on hover (for buttons, icons) */
/* Glow on hover */
/* Icon rotate on hover */
/* Icon bounce on hover */
/* Arrow slide on hover */
/* -----------------------------------------------------------------------------
   Active/Click States
   ----------------------------------------------------------------------------- */

/* Press down effect */
/* Ripple container (requires JS to add ripple span) */
/* -----------------------------------------------------------------------------
   Focus States
   ----------------------------------------------------------------------------- */

/* -----------------------------------------------------------------------------
   Staggered List Animations
   ----------------------------------------------------------------------------- */

/* -----------------------------------------------------------------------------
   Progress Bar Animation
   ----------------------------------------------------------------------------- */

.ui-progress-bar {
    height: 6px;
    background: var(--bg-subtle);
    border-radius: 3px;
    overflow: hidden;
}

.ui-progress-fill {
    height: 100%;
    background: var(--primary);
    border-radius: 3px;
    /* 500 ms erano fuori budget e per una barra di avanzamento non e' un
       dettaglio di stile: mezzo secondo di inseguimento vuol dire che il
       riempimento riporta il passato. */
    transition: width 250ms var(--ease-out);
}

/* Indeterminate progress */
.ui-progress-fill.indeterminate {
    width: 30%;
    animation: ui-progress-slide 1.5s linear infinite;
}

/* -----------------------------------------------------------------------------
   Count Up Animation (visual only, JS handles actual counting)
   ----------------------------------------------------------------------------- */


/* =============================================================================
   EMPTY STATES
   Placeholder UI per assenza di dati
   ============================================================================= */

.ui-empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 48px 24px;
    text-align: center;
}

.ui-empty-state-icon {
    width: 64px;
    height: 64px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-subtle);
    border-radius: 50%;
    margin-bottom: 16px;
    color: var(--text-light);
    font-size: 24px;
}

.ui-empty-state-title {
    font-size: 16px;
    font-weight: 600;
    color: var(--text-heading);
    margin-bottom: 8px;
}

.ui-empty-state-text {
    font-size: 13px;
    color: var(--text-muted);
    max-width: 320px;
    margin-bottom: 20px;
}

.ui-empty-state-compact {
    padding: 32px 16px;
}

/* Il vuoto di una SEZIONE dentro una card, non della card intera: una riga
   sola, allineata a sinistra, senza icona.

   ⚠️ Serve perché due `ui-empty-state` centrati impilati nella stessa card
   producono ~600 px di vuoto — misurati sulla scheda anagrafica, dove
   l'anagrafica non rilevata e i recapiti assenti convivono su 2.728 voci su
   2.729. Un rettangolo vuoto grande quanto una card piena si legge come
   contenuto perduto; qui il titolo della sezione resta e sotto una riga dice
   perché. La card intera continua a usare la forma centrata: lì il vuoto È il
   contenuto, e centrarlo lo dichiara. */
.ui-empty-state--inline {
    display: block;
    padding: 0;
    text-align: left;
}
.ui-empty-state--inline .ui-empty-state-icon { display: none; }
.ui-empty-state--inline .ui-empty-state-title {
    display: inline;
    font-size: 12px;
    font-weight: 500;
}
/* ⚠️ `:not(:last-child)`: il trattino SEPARA il titolo dal testo, e senza
   testo restava sospeso in fondo alla riga — «Nessun dato dal Registro Imprese
   —», che si legge come una frase troncata. Il testo è facoltativo per
   costruzione (dove l'avviso sopra ha già spiegato il perché), quindi il
   separatore deve dipendere dalla sua presenza. */
.ui-empty-state--inline .ui-empty-state-title:not(:last-child)::after { content: ' — '; }
.ui-empty-state--inline .ui-empty-state-text {
    display: inline;
    font-size: 12px;
    max-width: none;
}

.ui-empty-state-compact .ui-empty-state-icon {
    width: 48px;
    height: 48px;
    font-size: 18px;
    margin-bottom: 12px;
}

.ui-empty-state-compact .ui-empty-state-title {
    font-size: 13px;
}

.ui-empty-state-compact .ui-empty-state-text {
    font-size: 12px;
    margin-bottom: 16px;
}

/* =============================================================================
   ERROR STATES
   UI per errori e problemi
   ============================================================================= */

.ui-error-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 48px 24px;
    text-align: center;
}

.ui-error-state-icon {
    width: 64px;
    height: 64px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--cherry-light);
    border-radius: 50%;
    margin-bottom: 16px;
    color: var(--burgundy);
    font-size: 24px;
}

.ui-error-state-title {
    font-size: 16px;
    font-weight: 600;
    color: var(--text-heading);
    margin-bottom: 8px;
}

.ui-error-state-text {
    font-size: 13px;
    color: var(--text-muted);
    max-width: 320px;
    margin-bottom: 20px;
}

.ui-error-state-code {
    font-family: 'Roboto Mono', monospace;
    font-size: 11px;
    color: var(--text-light);
    background: var(--bg-subtle);
    padding: 8px 12px;
    border-radius: var(--radius-sm);
    margin-bottom: 20px;
}

/* =============================================================================
   FORM VALIDATION STATES
   Stati di validazione per input
   ============================================================================= */

.ui-input-group {
    position: relative;
}

.ui-input-feedback {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 6px;
    font-size: 12px;
}

.ui-input-feedback i {
    font-size: 11px;
}

/* Success state */
.ui-input.is-valid,
.ui-select.is-valid {
    border-color: var(--ui-success);
}

/* I tre stati di validazione avevano un focus CIECO, e non si vedeva leggendo:
   il bordo a riposo e quello di focus sono la STESSA variabile, quindi l'unica
   differenza era la box-shadow — che la regola globale spegne. Un campo in
   errore restava identico a se stesso quando lo si metteva a fuoco.
   Il colore del bordo qui non puo' diventare l'indicatore, perche' e' gia'
   occupato dallo stato: serve un canale in piu', e l'outline e' l'unico che
   la globale non tocca. */
.ui-input.is-valid:focus,
.ui-select.is-valid:focus {
    border-color: var(--ui-success);
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

.ui-input-feedback.is-valid {
    color: var(--ui-success);
}

/* Error state */
.ui-input.is-invalid,
.ui-select.is-invalid {
    border-color: var(--ui-danger);
}

.ui-input.is-invalid:focus,
.ui-select.is-invalid:focus {
    border-color: var(--ui-danger);
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

.ui-input-feedback.is-invalid {
    color: var(--ui-danger);
}

/* Warning state */
.ui-input.is-warning,
.ui-select.is-warning {
    border-color: var(--ui-warning);
}

.ui-input.is-warning:focus,
.ui-select.is-warning:focus {
    border-color: var(--ui-warning);
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

.ui-input-feedback.is-warning {
    color: var(--ui-warning);
}

/* Input with icon */
.ui-input-icon-wrapper {
    position: relative;
}

.ui-input-icon-wrapper .ui-input {
    padding-right: 36px;
}

.ui-input-icon {
    position: absolute;
    right: 12px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 14px;
    pointer-events: none;
}

/* =============================================================================
   DROPDOWN MENU
   Menu contestuali e azioni
   ============================================================================= */


/* =============================================================================
   CONFIRMATION DIALOG
   Modal di conferma azioni
   ============================================================================= */

.ui-confirm-icon {
    width: 56px;
    height: 56px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    margin: 0 auto 20px;
    font-size: 24px;
}

.ui-confirm-icon.warning {
    background: var(--princeton-light);
    color: var(--princeton-dark);
}

.ui-confirm-icon.danger {
    background: var(--cherry-light);
    color: var(--burgundy);
}

.ui-confirm-icon.info {
    background: rgba(5, 8, 52, 0.05);
    color: var(--iron);
}

.ui-confirm-title {
    font-size: 16px;
    font-weight: 600;
    color: var(--text-heading);
    text-align: center;
    margin-bottom: 8px;
}

.ui-confirm-text {
    font-size: 13px;
    color: var(--text-muted);
    text-align: center;
    margin-bottom: 24px;
}

.ui-confirm-input {
    margin-bottom: 20px;
}

.ui-confirm-input label {
    display: block;
    font-size: 12px;
    color: var(--text-muted);
    margin-bottom: 8px;
    text-align: center;
}

.ui-confirm-input code {
    font-family: 'Roboto Mono', monospace;
    background: var(--bg-subtle);
    padding: 2px 6px;
    border-radius: var(--radius-sm);
    color: var(--ui-danger);
}

.ui-confirm-actions {
    display: flex;
    gap: 12px;
    justify-content: center;
}

/* =============================================================================
   DRAG & DROP UPLOAD
   Area upload file con drag & drop
   ============================================================================= */

.ui-dropzone {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 32px 24px;
    border: 2px dashed var(--border);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    cursor: pointer;
    transition: border-color 200ms var(--ease-out), background-color 200ms var(--ease-out);
}

.ui-dropzone:hover {
    border-color: var(--primary);
    background: rgba(5, 8, 52, 0.02);
}

.ui-dropzone.dragover {
    border-color: var(--primary);
    background: rgba(5, 8, 52, 0.05);
    transform: scale(1.01);
}

.ui-dropzone-icon {
    width: 48px;
    height: 48px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #fff;
    border-radius: 50%;
    margin-bottom: 16px;
    color: var(--primary);
    font-size: 20px;
}

.ui-dropzone-text {
    font-size: 14px;
    font-weight: 500;
    color: var(--text-heading);
    margin-bottom: 4px;
}

.ui-dropzone-hint {
    font-size: 12px;
    color: var(--text-muted);
}

.ui-dropzone-hint a {
    color: var(--iron);
    font-weight: 500;
}

.ui-dropzone input[type="file"] {
    position: absolute;
    inset: 0;
    opacity: 0;
    cursor: pointer;
}

/* File list */
.ui-file-list {
    margin-top: 16px;
}

.ui-file-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    margin-bottom: 8px;
}

.ui-file-item:last-child {
    margin-bottom: 0;
}

.ui-file-icon {
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-subtle);
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font-size: 16px;
}

.ui-file-icon.pdf { color: var(--cherry); }
.ui-file-icon.doc { color: var(--iron); }
.ui-file-icon.xls { color: var(--ui-success); }   /* NON --success (seaweed): 2,60:1 su --bg-subtle, e DESIGN_SYSTEM.md:86 dice «bg-seaweed, mai come testo». --ui-success e' dark-emerald, 5,82. Le tre sorelle passavano gia'. */
.ui-file-icon.img { color: var(--iron); }

.ui-file-info {
    flex: 1;
    min-width: 0;
}

.ui-file-name {
    font-size: 13px;
    font-weight: 500;
    color: var(--text-heading);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.ui-file-size {
    font-size: 11px;
    color: var(--text-light);
}

.ui-file-progress {
    width: 100%;
    height: 4px;
    background: var(--bg-subtle);
    border-radius: 2px;
    margin-top: 6px;
    overflow: hidden;
}

.ui-file-progress-bar {
    height: 100%;
    background: var(--primary);
    border-radius: 2px;
    /* Stessa barra di .ui-progress-fill: 250 ms --ease-out, entro il budget UI. */
    transition: width 250ms var(--ease-out);
}

.ui-file-actions {
    display: flex;
    gap: 4px;
}

.ui-file-action {
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-light);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background-color 150ms var(--ease-out), color 150ms var(--ease-out);
}

.ui-file-action:hover {
    background: var(--bg-subtle);
    color: var(--text-body);
}

.ui-file-action.danger:hover {
    background: var(--cherry-light);
    color: var(--burgundy);
}

.ui-file-status {
    font-size: 11px;
    font-weight: 500;
}

.ui-file-status.success {
    color: var(--ui-success);
}

.ui-file-status.error {
    color: var(--ui-danger);
}

/* =============================================================================
   EDIT MODAL PATTERNS
   Pattern per modal di modifica
   ============================================================================= */

.ui-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-bottom: 16px;
    margin-bottom: 20px;
    border-bottom: 1px solid var(--border);
}

.ui-modal-title {
    font-size: 16px;
    font-weight: 600;
    color: var(--text-heading);
}

.ui-modal-close {
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-light);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background-color 150ms var(--ease-out), color 150ms var(--ease-out);
}

.ui-modal-close:hover {
    background: var(--bg-subtle);
    color: var(--text-body);
}

.ui-modal-body {
    margin-bottom: 24px;
}

.ui-modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: 12px;
    padding-top: 16px;
    border-top: 1px solid var(--border);
}

/* -----------------------------------------------------------------------------
   Picker: cerca e scegli una voce dentro una modale
   -----------------------------------------------------------------------------
   Serve dove l'azione ha bisogno di UNA riga scelta a mano fra migliaia, e
   nessun elenco a tendina puo' contenerle: la coda delle Conferme, dove il
   file non porta il nome del cliente. E' una lista di ESITI, non un menu —
   ogni riga porta il nome piu' una riga di metadati che la distingue dalle
   omonime, e senza quella riga due voci con lo stesso nome sarebbero
   indistinguibili proprio nel momento in cui si sceglie.

   L'altezza e' fissa e la lista scorre DENTRO di se': il contenuto lungo non
   deve far crescere la modale oltre lo schermo, o il bottone di chiusura
   finisce sotto la piega. */

.ui-picker-list {
    max-height: 320px;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
}

.ui-picker-item {
    display: block;
    width: 100%;
    padding: 10px 12px;
    text-align: left;
    background: transparent;
    border: none;
    border-bottom: 1px solid var(--border);
    cursor: pointer;
    transition: background 150ms var(--ease-out);
}

.ui-picker-item:last-child {
    border-bottom: none;
}

.ui-picker-item:hover {
    background: var(--bg-subtle);
}

/* Il fondo scende subito e risale in 140 ms, come `.ui-dropdown-item:active`:
   una riga a tutta larghezza che si stringe sembra un difetto di disegno. */
.ui-picker-item:active {
    background: rgba(5, 8, 52, 0.12);
    transition-duration: 0ms;
}

.ui-picker-item:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: -2px;
}

.ui-picker-nome {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-heading);
}

/* Cio' che distingue due voci con lo stesso nome: P.IVA, comune, categoria.
   `--text-light` (stone #657179) misura 5,01:1 sul bianco della modale e
   4,54:1 sul fondo hover `--platinum`, quindi regge anche sulla riga
   sorvolata. */
.ui-picker-meta {
    margin-top: 2px;
    font-size: 11px;
    color: var(--text-light);
}

/* Il conteggio sopra la lista: «i primi 20 di 137» e' la sola cosa che dice a
   chi cerca che deve stringere la chiave invece di scorrere. */
.ui-picker-conteggio {
    font-size: 11px;
    color: var(--text-light);
    margin-bottom: 8px;
}

/* Form layout in modal */
.ui-form-row {
    margin-bottom: 16px;
}

.ui-form-row:last-child {
    margin-bottom: 0;
}

.ui-form-label {
    display: block;
    font-size: var(--etichetta-testo);
    font-weight: var(--etichetta-peso);
    color: var(--etichetta-colore);
    margin-bottom: 6px;
}

.ui-form-label.required::after {
    content: ' *';
    color: var(--ui-danger);
}

.ui-form-hint {
    font-size: var(--aiuto-testo);
    line-height: 1.45;
    color: var(--aiuto-colore);
    margin-top: 6px;
}

/* Il valore IN SOLA LETTURA accanto ai campi di un modulo (il nome sul
   Profilo, lo speso del mese accanto al tetto): è un dato, non un campo, e si
   legge con la scala del valore. Prima era un `<p class="text-sm text-ui-body">`
   — 14 px Iron, cioè lo stesso grigio dell'etichetta sopra di lui. */
.ui-form-valore {
    font-size: var(--valore-testo);
    font-weight: var(--valore-peso);
    color: var(--valore-colore);
    line-height: 1.4;
    overflow-wrap: anywhere;
}
/* Accanto a un campo nella stessa riga (lo speso del mese accanto al tetto):
   alto quanto il campo e centrato come il suo testo, o galleggia sulla linea
   dell'etichetta mentre il valore del campo sta 10 px più in basso. */
.ui-form-valore--in-riga {
    display: flex;
    align-items: center;
    min-height: var(--campo-altezza);
}


.ui-form-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 16px;
}

/* Tre campi per riga: per form larghi (ui-modal-card--lg e oltre), dove due
   colonne lascerebbero mezza riga vuota. Sotto i 640px scende a due, poi a una:
   tre select affiancate su un tablet diventano illeggibili. */
.ui-form-grid--3 {
    grid-template-columns: repeat(3, 1fr);
}

/* Matrice permessi: moduli in riga, livelli in colonna.
   Le tre etichette stanno in un'intestazione, una volta sola — ripetute su ogni
   riga (dodici parole per quattro moduli) il testo pesava piu' delle caselle e
   le colonne non si leggevano come colonne. Le colonne dei livelli sono a
   larghezza FISSA perche' devono restare allineate fra le righe: con `1fr` si
   ridimensionerebbero sul nome del modulo piu' lungo. */
.ui-permessi-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) repeat(3, 76px);
    gap: 10px 8px;
    align-items: center;
    margin-top: 8px;
}

.ui-permessi-intestazione {
    font-size: 11px;
    font-weight: 500;
    color: var(--text-light);
    text-align: center;
}

.ui-permessi-modulo {
    font-size: 13px;
    color: var(--text-body);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* La casella si centra sotto la sua intestazione: allineata a sinistra, la
   colonna si legge solo grazie all'etichetta, che qui compare una volta sola. */
.ui-permessi-cella {
    display: flex;
    justify-content: center;
}

/* La vetrina del catalogo: la matrice fuori da una modale non ha un contenitore
   che la limiti, e stesa su tutta la card non si legge come si legge davvero. */
.ui-permessi-grid--vetrina { max-width: 420px; }

@media (max-width: 640px) {
    .ui-form-grid--3 {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 480px) {
    .ui-form-grid,
    .ui-form-grid--3 {
        grid-template-columns: 1fr;
    }
}

/* =============================================================================
   CUSTOM SCROLLBAR - PREMIUM
   ============================================================================= */

/* Webkit (Chrome, Safari, Edge) */
::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}

::-webkit-scrollbar-track {
    background: transparent;
    border-radius: 10px;
}

::-webkit-scrollbar-thumb {
    background: linear-gradient(
        180deg,
        rgba(5, 8, 52, 0.15) 0%,
        rgba(5, 8, 52, 0.10) 100%
    );
    border-radius: 10px;
    border: 2px solid transparent;
    background-clip: padding-box;
    transition: background 0.3s ease;
}

::-webkit-scrollbar-thumb:hover {
    background: linear-gradient(
        180deg,
        rgba(5, 8, 52, 0.30) 0%,
        rgba(5, 8, 52, 0.20) 100%
    );
    background-clip: padding-box;
}

::-webkit-scrollbar-thumb:active {
    background: linear-gradient(
        180deg,
        rgba(5, 8, 52, 0.50) 0%,
        rgba(5, 8, 52, 0.40) 100%
    );
    background-clip: padding-box;
}

/* Firefox */
* {
    scrollbar-width: thin;
    scrollbar-color: rgba(5, 8, 52, 0.15) transparent;
}

/* =============================================================================
   PAGE HEADER PATTERN
   ============================================================================= */

.page-header {
    margin-bottom: 24px;
}

.page-breadcrumb {
    font-size: 11px;
    color: var(--text-muted);
    margin-bottom: 8px;
    display: flex;
    align-items: center;
    gap: 4px;
}

.page-breadcrumb a {
    color: var(--text-muted);
    text-decoration: none;
}

.page-breadcrumb a:hover {
    color: var(--primary);
}

.page-header-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    /* A schermo stretto i comandi scendono sotto il titolo invece di spingere
       fuori la riga. Senza, a 500px l'intestazione chiedeva 520px in uno slot
       da 394 e a scorrere era LA PAGINA — su ogni vista, perche' il colpevole
       e' questo partial condiviso, non una vista. */
    flex-wrap: wrap;
}

/* Un flex item non scende sotto il proprio min-content finche' `min-width` resta
   `auto`: il blocco del titolo restava a 216px e mandava fuori riga i comandi
   anche quando avrebbe potuto stringersi a 93. */
.page-header-row > :first-child {
    min-width: 0;
}

/* I comandi non si stringono (`flex-shrink-0` nel markup, ed e' giusto: un'icona
   tagliata a meta' non e' un comando), e su schermi stretti superano la riga
   anche da soli — il blocco col menu interno chiede 400px di min-content contro
   i 394 disponibili. Quindi vanno a capo fra loro.
   ⚠️ NON `overflow-x: auto`: era il primo tentativo e rompeva ogni dropdown
   dell'intestazione. Un overflow diverso da `visible` su un asse rende `auto`
   anche l'altro, quindi il blocco diventa un contesto di RITAGLIO — e il menu
   del filtro temporale, alto 364px, si apriva dentro un contenitore alto 36:
   372px tagliati. Misurato. Andare a capo non ritaglia niente. */
.page-header-row > :last-child:not(:first-child) {
    max-width: 100%;
    flex-wrap: wrap;
    justify-content: flex-end;
}

/* ── L'intestazione sotto i 640 px ──────────────────────────────────────────
   Due difetti della stessa famiglia, misurati il 22/09/2026 a 375 e 414 px.

   (1) I comandi vanno a capo e `justify-content: flex-end` li lascia sospesi:
   su Ritenzione «Altre azioni» a x=78 e «Filtri» a x=120 in prima riga, con
   «Periodo» a x=36 in seconda — tre comandi su due margini diversi. Dal bordo
   sinistro si incolonnano col resto della pagina.

   (2) Lo stesso a-capo porta a sinistra un trigger che `ui-dropdown-menu--right`
   ancora a destra, e il pannello si stende all'indietro fuori dallo schermo:
   `left = -208` su Anagrafica, `-158`, `-150`, `-116`, `-103`. Nessuno
   scorrimento ci arriva — `.main-content` e' `overflow-x: hidden` e l'eccedenza
   misura 0, quindi quel contenuto non esce, viene TAGLIATO.
   ⚠️ Riancorarlo a sinistra non lo chiude: resta appeso al trigger, e a x=120
   un pannello da 272 esce di 17 px dall'altra parte (misurato). Qui l'ancora
   diventa il BLOCCO DEI COMANDI — `position: relative` su di lui, `static` sul
   dropdown — e il pannello si stende da bordo a bordo, sotto l'intestazione:
   la sua larghezza non dipende piu' da quale comando lo ha aperto.
   ⚠️ Il blocco va disteso sulla riga (`width: 100%`): da solo prende la
   larghezza dei suoi comandi, e il pannello ancorato a lui la eredita. Misurato
   il 05/10/2026 a 375 px: blocco **120** px (78 sull'Anagrafica), ogni menu
   d'intestazione largo quanto lui, e sul «Periodo» di Ritenzione 5 controlli
   su 5 tagliati dall'`overflow: hidden` del pannello — eccedenza della pagina
   0, quindi nessun telaio se n'era accorto. E il «Periodo» non si distende a
   sua volta (`ui-timefilter-dropdown`, `width: 100%` sotto i 640): andava a
   capo da solo su una riga sua, che ora non serve piu'. */
@media (max-width: 640px) {
    .page-header-row > :last-child:not(:first-child) {
        justify-content: flex-start;
        position: relative;
        width: 100%;
    }

    .page-header-row .ui-dropdown {
        position: static;
    }

    .page-header-row .ui-timefilter-dropdown {
        width: auto;
    }

    /* ⚠️ `width` e `min-width` si azzerano INSIEME a `left`/`right`: le varianti
       li dichiarano (272 su `--filtri`, 180 di base) e altrimenti vincono. */
    .page-header-row .ui-dropdown-menu {
        left: 0;
        right: 0;
        width: auto;
        min-width: 0;
        max-width: none;
        transform-origin: top center;
    }
}

.page-header-divider .ui-internal-menu--compact.ui-internal-menu--inline .ui-internal-menu-item,
.page-header-divider .ui-dropdown-trigger {
    color: var(--iron);
}

.page-header-divider .ui-internal-menu--compact.ui-internal-menu--inline .ui-internal-menu-item.active {
    color: #fff;
}

.page-header-divider .ui-dropdown-trigger i {
    color: var(--iron);
}

.page-header-divider .ui-dropdown-trigger:hover,
.page-header-divider .ui-dropdown.is-open > .ui-dropdown-trigger {
    border-color: var(--accent);
    background: rgba(5, 8, 52, 0.04);
}

/* Nell'intestazione di pagina il pallino resta arancione: `--accent` la' vale
   gia' per il bordo del trigger, e usarlo anche per il pallino lo farebbe
   sparire dentro il proprio contorno. */

/* La voce attiva di un menu qui NON prende --accent, che pure e' il colore
   identitario di questa barra: misurato sul fondo della voce attiva da'
   2,21:1, contro il 4,5:1 che serve a un testo da 13px. Lo stato lo dicono
   gia' il fondo e il peso 600 (13,56:1), che funzionano ovunque. Un colore
   che non si legge non aggiunge un segnale: ne toglie uno. */

.page-header-divider .ui-date-preset:hover,
.page-header-divider .ui-date-preset.active {
    background: var(--accent-dark);
    border-color: var(--accent-dark);
    color: var(--white);
}

.page-header-divider .ui-daterange-picker:focus {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%2343536B'%3E%3Cpath d='M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z'/%3E%3C/svg%3E");
}

.flatpickr-calendar--header-accent .flatpickr-current-month .flatpickr-monthDropdown-months:hover,
.flatpickr-calendar--header-accent .flatpickr-months .flatpickr-prev-month:hover,
.flatpickr-calendar--header-accent .flatpickr-months .flatpickr-next-month:hover,
.flatpickr-calendar--header-accent .flatpickr-day:hover {
    background: rgba(5, 8, 52, 0.05);
}

.flatpickr-calendar--header-accent .flatpickr-months .flatpickr-prev-month svg,
.flatpickr-calendar--header-accent .flatpickr-months .flatpickr-next-month svg {
    fill: var(--accent);
}

.flatpickr-calendar--header-accent .flatpickr-months .flatpickr-prev-month:hover svg,
.flatpickr-calendar--header-accent .flatpickr-months .flatpickr-next-month:hover svg {
    fill: var(--accent-dark);
}

.flatpickr-calendar--header-accent .flatpickr-day:hover {
    color: var(--accent-dark);
}

.flatpickr-calendar--header-accent .flatpickr-day.selected,
.flatpickr-calendar--header-accent .flatpickr-day.startRange,
.flatpickr-calendar--header-accent .flatpickr-day.endRange,
.flatpickr-calendar--header-accent .flatpickr-day.selected.inRange,
.flatpickr-calendar--header-accent .flatpickr-day.startRange.inRange,
.flatpickr-calendar--header-accent .flatpickr-day.endRange.inRange,
.flatpickr-calendar--header-accent .flatpickr-day.selected:focus,
.flatpickr-calendar--header-accent .flatpickr-day.startRange:focus,
.flatpickr-calendar--header-accent .flatpickr-day.endRange:focus,
.flatpickr-calendar--header-accent .flatpickr-day.selected:hover,
.flatpickr-calendar--header-accent .flatpickr-day.startRange:hover,
.flatpickr-calendar--header-accent .flatpickr-day.endRange:hover,
.flatpickr-calendar--header-accent .flatpickr-day.selected.prevMonthDay,
.flatpickr-calendar--header-accent .flatpickr-day.startRange.prevMonthDay,
.flatpickr-calendar--header-accent .flatpickr-day.endRange.prevMonthDay,
.flatpickr-calendar--header-accent .flatpickr-day.selected.nextMonthDay,
.flatpickr-calendar--header-accent .flatpickr-day.startRange.nextMonthDay,
.flatpickr-calendar--header-accent .flatpickr-day.endRange.nextMonthDay {
    background: var(--accent-dark);
    border-color: var(--accent-dark);
    color: var(--white);
}

.flatpickr-calendar--header-accent .flatpickr-day.inRange {
    background: rgba(5, 8, 52, 0.06);
    color: var(--accent-dark);
}

/* ⚠️ L'UNICO posto in cui vive `--font-display` (Outfit, il carattere del
   marchio). Un primo giro lo aveva messo anche su titoli di widget, etichette
   KPI, voci di menu e bottoni: Outfit e' geometrico e a corpo piccolo, in
   maiuscoletto e su testo fitto, si legge peggio di Inter — misurabile sui
   caratteri per riga, visibile a occhio prima ancora. Qui invece il testo e'
   grande, corto e uno solo per schermata: e' la superficie giusta per un
   marchio, ed e' abbastanza isolata da non costare leggibilita' a niente.
   Da 22 a 25 px, che e' il gradino in cui `--tracking-display` (>= 24 px)
   diventa quello giusto invece di essere ereditato per approssimazione. */
.page-title {
    font-family: var(--font-display);
    font-size: 25px;
    font-weight: 700;
    color: var(--text-heading);
    letter-spacing: var(--tracking-display);
    line-height: 1.2;
}

.page-subtitle {
    font-size: 14px;
    color: var(--iron);
    margin-top: 4px;
}

.ui-timefilter-dropdown {
    flex-shrink: 0;
    width: auto;
    max-width: 100%;
}

.ui-timefilter-menu {
    box-sizing: border-box;
    top: calc(100% + 12px) !important;
    margin-top: 0 !important;
    left: auto !important;
    right: 0 !important;
    width: min(390px, calc(100vw - 24px)) !important;
    min-width: 0 !important;
    max-width: calc(100vw - 24px) !important;
    padding: 14px;
    overflow: hidden;
    z-index: 140;
    background: #fff;
    border-color: var(--border);
    display: grid;
    /* Una colonna che non scende sotto i figli: senza, la traccia implicita
       prende la larghezza intrinseca dei due select «Dal/al» e il contenuto
       esce dal pannello — 302 px in 258 utili sul «Periodo» di Ritenzione. */
    grid-template-columns: minmax(0, 1fr);
    gap: 12px;
}

.ui-timefilter-menu .ui-dropdown-header {
    padding: 0;
    margin: 0;
    color: var(--text-light);
    letter-spacing: var(--tracking-label);
}

.ui-timefilter-menu--narrow {
    width: min(280px, calc(100vw - 24px)) !important;
    max-width: calc(100vw - 24px) !important;
    gap: 8px;
    padding: 10px;
}

.ui-timefilter-menu--narrow .ui-dropdown-header.widget-title {
    margin-bottom: 0;
    color: var(--iron);
    letter-spacing: var(--tracking-label);
}

.ui-timefilter-menu--narrow .ui-date-range-presets {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 6px;
}

.ui-timefilter-menu--narrow .ui-date-preset {
    width: 100%;
    text-align: left;
}

/* Premium vertical rhythm for template page */
.template-library .space-y-4 > :not([hidden]) ~ :not([hidden]) {
    margin-top: 1.5rem;
}

.template-library .space-y-6 > :not([hidden]) ~ :not([hidden]) {
    margin-top: 2rem;
}

.template-library .grid {
    row-gap: 1.5rem;
}


@media (max-width: 960px) {

    .ui-timefilter-dropdown {
        width: auto;
        max-width: 100%;
    }

    .ui-timefilter-menu {
        left: auto !important;
        right: 0 !important;
        width: min(390px, calc(100vw - 20px)) !important;
        max-width: calc(100vw - 20px) !important;
    }
}

@media (max-width: 640px) {
    .ui-timefilter-dropdown {
        width: 100%;
    }

    .ui-timefilter-menu {
        left: 0 !important;
        right: 0 !important;
        width: 100% !important;
        max-width: none !important;
    }
}.widget-card .text-xl.font-bold,
.widget-card .text-2xl.font-bold,
.widget-card .text-3xl.font-bold,
.widget-card .text-4xl.font-bold,
.ui-contrast-value,
.ui-contrast-meta,
.ui-step-indicator,
.ui-date-preset,
.ui-badge,
.ui-pill{
    font-family: var(--font-mono);
    letter-spacing: 0;
    font-variant-numeric: tabular-nums;
}

/* Tables: numeric columns (td only — th headers keep sans-serif) */
table td.text-right,
.sortable-table td.text-right {
    font-family: var(--font-mono);
    letter-spacing: 0;
    font-variant-numeric: tabular-nums;
}

/* =============================================================================
   INTERACTIVE NORMALIZATION
   ============================================================================= */

/* Uniform border radius for interactive UI elements (excluding widget containers) */
.ui-btn,
.ui-btn-sm,
.ui-btn-lg,
.ui-btn-icon,
.ui-pill,
.ui-chip,
.chip,
.ui-tab,
.ui-tabs .ui-tablist,
.widget-body .ui-tabs .ui-tablist,
.ui-internal-menu,
.ui-internal-menu-item,
.ui-dropdown-trigger,
.ui-dropdown-menu,
.ui-dropdown-item,
.ui-accordion,
.ui-table-page-active,
.ui-date-preset,
.ui-input,
.ui-select {
    border-radius: var(--interactive-radius) !important;
}.ui-badge{
    border-radius: var(--badge-radius) !important;
    font-size: 11px !important;
}

.ui-btn-group .ui-btn:first-child {
    border-radius: var(--interactive-radius) 0 0 var(--interactive-radius) !important;
}

.ui-btn-group .ui-btn:last-child {
    border-radius: 0 var(--interactive-radius) var(--interactive-radius) 0 !important;
}

/* Remove elevation from UI; keep only main content shadow */
body *,
body *::before,
body *::after {
    box-shadow: none !important;
}

/* Pannelli che galleggiano sopra CONTENUTO CHIARO: qui l'elevazione serve, e
   non e' decorazione. Il !important e' obbligato: la regola globale qui sopra
   spegne ogni box-shadow, e senza di lui questa riga verrebbe ignorata in
   silenzio.
   Il criterio non e' «galleggia», e' «da cosa e' separato adesso» — misurato
   l'08/08/2026 sui quattro pannelli che esistono davvero nel codice:

     .nav-flyout            su pagina  pannello 1,10:1  bordo 1,21:1  -> serve
     .flatpickr-calendar    su pagina  pannello 1,10:1  bordo 1,21:1  -> serve
     .sidebar-user-dropdown su sidebar navy       10,46:1            -> no
     .ui-modal-card         su scrim rgb(140,145,154)  3,17:1        -> no

   Gli ultimi due sono gia' separati da cio' che hanno dietro, e su fondo scuro
   un'ombra scura non si vedrebbe comunque. */
.ui-dropdown-menu,
.ui-timefilter-menu,
.sidebar-container.is-collapsed .nav-flyout,
.flatpickr-calendar {
    box-shadow: var(--elevazione-pannello) !important;
}

/* La card porta la sua ombra da 1px nonostante lo spegnimento globale: senza,
   il filo piu' chiaro del 07/09/2026 lascerebbe le card senza nessun confine
   leggibile contro la pagina. Sta QUI e non nella regola di `.widget-card`
   perche' `body *` con `!important` vincerebbe comunque. */
.widget-card {
    box-shadow: var(--elevazione-card) !important;
}

.main-content {
    box-shadow: none !important;
}

/* Tooltip boxes: subtle elevation for readability */
.group .group-hover\:block.absolute.z-50.shadow-lg,
.group .group-hover\:block.absolute.z-50.shadow {
    box-shadow: var(--elevazione-tooltip) !important;
}


/* ── Global text size bump (+1px) ─────────────────────────────────────────── */

/* Table cells: 13px → 14px */
td[class*="text-[13px]"],
.text-\[13px\] { font-size: 14px !important; }

/* Meta/labels: 11px → 12px */
td[class*="text-[11px]"],
th[class*="text-[11px]"],
.text-\[11px\] { font-size: 12px !important; }

/* Hints: 10px → 11px */
.text-\[10px\] { font-size: 11px !important; }

/* ── Scheda Azienda compilabile — Task 7 ───────────────────────────────── */
.ui-block-title {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .5px;
    color: var(--stone);
    font-weight: 600;
}
.ui-dot--verde  { background: var(--seaweed); }
.ui-dot--arancio { background: var(--princeton); }
.ui-dot--rosso  { background: var(--cherry); }

.ui-mini-ring {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
}
.ui-mini-ring > span {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #fff;
    font-size: 9px;
    font-weight: 600;
    color: var(--text-heading);
}

.ui-field-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    padding: 6px 0;
    border-bottom: 1px solid var(--platinum);
}
.ui-field-row:last-child { border-bottom: none; }
.ui-field-row .k { color: var(--etichetta-colore); font-size: var(--etichetta-testo); font-weight: var(--etichetta-peso); }
.ui-field-row .v { font-family: 'Roboto Mono', monospace; font-size: var(--valore-testo); font-weight: var(--valore-peso); color: var(--valore-colore); }
.ui-field-row .v--empty { color: var(--stone); font-size: var(--aiuto-testo); font-style: italic; font-family: inherit; font-weight: 400; }

.ui-quality-sub { font-size: 11px; color: var(--stone); margin-top: 2px; }

/* Testata di una scheda — chi è il soggetto, in una fascia sola.
   Prima erano cinque `ui-field-row` dentro una card «Identità» larga un terzo:
   nome, categoria, tipo, P.IVA e provenienza sono l'INTESTAZIONE della pagina,
   non una sezione fra le altre, e come card competevano con l'Anagrafica invece
   di introdurla. */
.ui-scheda-testata {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.ui-scheda-chip-riga { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }

/* Un metadato: etichetta e valore attaccati. NON è `ui-chip`, che ha
   `cursor: pointer` ed è un comando: questo è un dato, e promettere un clic che
   non succede è peggio che non prometterlo.
   ⚠️ **Senza fondo, e senza icona**, ed è la correzione del 21/08/2026: quattro
   pill grigie in fila con quattro glifi decorativi mettevano quattro scatole
   uguali sopra la scheda, tutte allo stesso peso, e la riga si leggeva come
   rumore invece che come l'identità della voce. Qui la scatola è diventata
   informativa: **ce l'ha solo ciò che si può toccare** (`--controllo`, la
   categoria), quindi il fondo dice «questo si corregge» invece di ripetersi
   quattro volte. I metadati di sola lettura li separa un filo verticale. */
/* ⚠️ Dal 22/09/2026 alla scala delle coppie (etichetta 13 Iron, valore 15
   jet): a 11 px tutto — etichetta, valore, corsivo del vuoto, tendina — pesava
   uguale, e la testata della scheda si leggeva come una riga di note
   (Nicola: «pesi e dimensioni da aggiustare»). */
.ui-dato-chip {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    padding: 2px 0;
    font-size: var(--etichetta-testo);
    font-weight: var(--etichetta-peso);
    color: var(--etichetta-colore);
    white-space: nowrap;
    max-width: 100%;
}

/* Il filo fra un metadato e il successivo: la separazione è un tratto, non una
   cornice per ciascuno. Sul primo non c'è, e non serve `:first-child` — il
   contenitore è un flex con `gap`, e il tratto sta nel padding sinistro. */
.ui-scheda-chip-riga > .ui-dato-chip + .ui-dato-chip {
    padding-left: 14px;
    border-left: 1px solid var(--platinum);
}

/* Il metadato che si CORREGGE tiene la scatola: è l'unica cosa interattiva della
   riga, e senza cornice un `<select>` spogliato non si distingue dal testo.
   ⚠️ Dal 06/10/2026 la scatola è un CAMPO — bianco col filo — e non più un fondo
   grigio: col fondo `--bg-muted` la card aveva due superfici grigie (questa e
   la fascia dei numeri), e la tendina si leggeva come un dato evidenziato
   invece che come qualcosa da cambiare. Il filo è `--accent-dark` (5,95:1 sul
   bianco), non quello degli input (`--border`, 1,23:1, sotto il 3:1 di un
   componente non testuale). */
.ui-dato-chip--controllo {
    padding: 3px 10px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--accent-dark);
    background: var(--white);
}
.ui-scheda-chip-riga > .ui-dato-chip--controllo + .ui-dato-chip {
    padding-left: 8px;
    border-left: none;
}
.ui-dato-chip strong {
    font-size: var(--valore-testo);
    font-weight: var(--valore-peso);
    color: var(--valore-colore);
    overflow: hidden;
    text-overflow: ellipsis;
}
.ui-dato-chip i { font-size: 11px; color: var(--stone); }
.ui-dato-chip em { font-style: italic; font-weight: 400; color: var(--aiuto-colore); }
/* La provenienza della categoria («da ricerca sul nome») accanto alla tendina:
   dice da dove viene il valore, cioè è un aiuto e non un'etichetta. */
.ui-dato-chip--controllo > span { font-weight: 400; color: var(--aiuto-colore); }

/* ⚠️ Nella riga d'IDENTITÀ della scheda i metadati sono un sottotitolo, non
   dati (06/10/2026): etichetta 12 · 400, valore 13 · 500. Alla scala delle
   coppie (13 Iron / 15 · 600 jet) stadio, tipo, P.IVA e provenienza pesavano
   quanto la ragione sociale e la sede sotto, e la riga che introduce la scheda
   competeva coi dati che introduce. Lo dichiara la riga e non il token: la
   stessa coppia altrove resta alla scala di casa. */
.ui-scheda-chip-riga .ui-dato-chip { font-size: 12px; font-weight: 400; }
.ui-scheda-chip-riga .ui-dato-chip strong,
.ui-scheda-chip-riga .ui-dato-chip .ui-select { font-size: 13px; font-weight: 500; }
.ui-scheda-chip-riga .ui-dato-chip em,
.ui-scheda-chip-riga .ui-dato-chip--controllo > span { font-size: 12px; }

/* Sotto i 480 px di card i metadati vanno a capo, e il filo verticale finiva
   in testa alla riga successiva, attaccato al bordo della card: un trattino
   che non separa niente. Lì li separa il solo spazio. */
@container (max-width: 480px) {
    .ui-scheda-chip-riga { column-gap: 16px; row-gap: 4px; }
    .ui-scheda-chip-riga > .ui-dato-chip + .ui-dato-chip {
        padding-left: 0;
        border-left: none;
    }
}

/* Un campo modificabile DENTRO un chip: il controllo si spoglia della sua
   cornice e prende la tipografia del chip, o dentro un blocchetto da 11px
   entrerebbe una scatola bianca alta il doppio che sembra incollata lì. Resta
   un `<select>` vero — bordo al focus compreso, che `app.css` disegna con
   `outline` e non con l'ombra. */
.ui-dato-chip .ui-select {
    border: none;
    /* ⚠️ `background-color`, NON `background`: la scorciatoia cancella anche
       l'immagine di sfondo, che su `.ui-select` è la **freccia** — e un select
       senza freccia si legge come un'etichetta, quindi nessuno prova a
       cambiarlo. La categoria è l'unico campo derivato che una persona può
       correggere: deve sembrare correggibile. */
    background-color: transparent;
    background-position: right 0 center;
    padding: 0 16px 0 0;
    height: auto;
    min-height: 0;
    font-size: var(--valore-testo);
    font-weight: var(--valore-peso);
    color: var(--valore-colore);
}


/* Campi di anagrafica — DUE colonne, e l'etichetta SOPRA il valore.
   In una riga `space-between` a piena larghezza l'etichetta e il suo valore
   finiscono a 900 px di distanza: l'occhio deve attraversare la card per
   ricomporre una coppia, e su dodici righe non lo fa più. Impilati, la coppia
   sta in 200 px e le colonne raddoppiano la densità a parità di spazio. */
.ui-campi {
    display: grid;
    grid-template-columns: 1fr;
    gap: 14px 32px;
}

/* 640 px misurati sul corpo del widget: sotto, due colonne scendono a 260 px
   l'una — la soglia oltre cui l'occhio smette di ricomporre la coppia
   etichetta/valore (misurato 20/08/2026: la card a 546 px dentro lo split
   1/2+1/2 dava 240 px a colonna). Sopra, il corpo è largo almeno quanto la
   pagina intera sotto `lg` (828 px a 900 px di viewport) e due colonne
   restano leggibili. */
@container (min-width: 640px) {
    .ui-campi { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    /* Un valore lungo — una ragione sociale per esteso, un indirizzo — prende
       la riga intera invece di stringere anche la colonna accanto. */
    .ui-campi .ui-campo--largo { grid-column: 1 / -1; }
}

.ui-campo { min-width: 0; }

.ui-campo-etichetta {
    display: block;
    font-size: var(--etichetta-testo);
    font-weight: var(--etichetta-peso);
    color: var(--etichetta-colore);
    margin-bottom: 2px;
}

/* Il valore è TESTO, quindi sans: il monospace serve a incolonnare cifre, e su
   una ragione sociale allarga le parole senza dare niente in cambio. I numeri e
   gli identificatori lo tengono, con `.ui-campo-valore--mono`. */
.ui-campo-valore {
    display: block;
    font-size: var(--valore-testo);
    font-weight: var(--valore-peso);
    color: var(--valore-colore);
    overflow-wrap: anywhere;
}
/* Roboto Mono a 14 occupa quanto Inter a 15: il valore non cambia rango. */
.ui-campo-valore--mono { font-family: 'Roboto Mono', monospace; font-size: 14px; }
.ui-campo-valore--vuoto { color: var(--stone); font-size: var(--aiuto-testo); font-style: italic; font-weight: 400; }

/* ============================================================
 * Anagrafica in righe allineate — `.ui-metrics--aligned`
 * ============================================================
 * Etichetta a sinistra con la sua icona, valore a destra, un filo per riga,
 * due colonne. E' la forma delle schede del BI (`Dati azienda` in `lead.js`),
 * portata qui perche' Nicola l'ha indicata come termine di paragone.
 *
 * Perche' non `ui-campi` (etichetta SOPRA il valore), che e' quello che c'era:
 * a due colonne dentro mezza card, i valori di un'anagrafica sono corti —
 * «S.P.A.», «ATTIVA», «100», «2024» — e l'etichetta sopra raddoppia l'altezza
 * di ogni riga per incolonnare una parola. Con l'etichetta a sinistra la riga
 * e' una, e la coppia si legge perche' il filo la delimita.
 *
 * ⚠️ La distanza etichetta/valore qui NON e' il difetto chiuso il 21/08/2026 su
 * `ui-metrics--riga`: la' i gruppi si distribuivano su tutta la card e la
 * distanza cresceva con la finestra. Qui la riga vive in **mezza colonna di
 * mezza card** — misurata ~250 px a 1440 — e il filo sotto dice dove finisce la
 * coppia. La regola resta la stessa: e' il contenitore che deve essere
 * limitato, non la distanza a essere vietata.
 */
/* ⚠️ Le colonne hanno un TETTO e non si stirano — `minmax(280px, 480px)` con
   `justify-content: start` — ed è la lezione del 21/08/2026 applicata qui prima
   di ripetere l'errore. Con `repeat(2, 1fr)` su una card a piena larghezza
   (1554 px misurati) la distanza fra il testo dell'etichetta e il suo valore
   arrivava a **525-648 px** sui campi corti e **1258** su quelli larghi: la
   stessa famiglia del difetto chiuso su `ui-metrics--riga`, e non mitigabile
   dal filo. Col tetto le colonne restano ≤480 e la distanza sta sotto i ~320.
   `auto-fill` fa il resto: tre colonne su una card larga, due sotto i 1000 px,
   una sotto i 600 — nessuna soglia scritta a mano. */
.ui-metrics--aligned {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(var(--colonna-dato), 1fr));
    gap: 0 40px;
}

.ui-metrics--aligned .ui-metric.ui-metric--largo {
    grid-column: 1 / -1;
    /* Stessa colonna d'etichetta delle righe corte: il valore comincia
       sull'UNICO asse della card. Con `max-content` l'etichetta si adattava a
       sé stessa e ogni riga larga partiva da un'ascissa sua — misurate 373, 397
       e 505 px in una card sola, che si legge come un allineamento saltato. */
    grid-template-columns: var(--etichetta-dato) minmax(0, 1fr);
}
.ui-metrics--aligned .ui-metric.ui-metric--largo .ui-metric-etichetta span {
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
}

/* ⚠️ L'etichetta ha una colonna FISSA e il valore comincia subito dopo,
   allineato a sinistra — non `1fr auto` col valore a destra, che è quello che
   c'era. Con il valore appeso al bordo destro la distanza dal suo testo la
   decideva la lunghezza dell'etichetta, quindi cambiava riga per riga: misurate
   244 · 348 · 307 · 367 · 346 px sulla stessa card, cioè cinque distanze diverse
   dove l'occhio deve ogni volta ritrovare la coppia. Col la colonna fissa la
   distanza è costante per costruzione, e i valori si incolonnano fra loro —
   che è la seconda cosa che si legge scorrendo una colonna di dati. */
.ui-metrics--aligned .ui-metric {
    display: grid;
    grid-template-columns: minmax(0, var(--etichetta-dato)) minmax(0, 1fr);
    align-items: baseline;
    gap: 12px;
    min-width: 0;
    padding: 7px 0;
    border-bottom: 1px solid var(--platinum);
}

/* ⚠️ `:last-child` non basta: in griglia a due colonne l'ULTIMA di sinistra non
   e' l'ultima del DOM, e restava con il filo mentre quella accanto lo perdeva.
   Il filo si toglie sull'ultima riga di ciascuna colonna — `nth-last-child(-n+2)`
   sotto i due assieme — e sotto la rottura, dove la colonna e' una, sul solo
   ultimo elemento. */
/* ⚠️ Il filo NON si toglie all'ultima riga: con un numero di colonne che cambia
   con la larghezza, «l'ultima di ogni colonna» non è esprimibile in CSS senza
   indovinare quante sono — e una regola che indovina lascia il filo su una
   colonna e lo toglie sull'altra, che a schermo si legge come un errore di
   allineamento. Il filo su tutte è uniforme, e sotto l'ultima riga c'è il
   fondo della card, dove un filo in più non tocca niente.
   ⚠️ E va DICHIARATO, perché la regola generica `.ui-metrics .ui-metric:last-child`
   (tre classi contro le due di questo componente) toglie filo e padding
   all'ultima: finché l'ultima era la riga di coda dei vuoti non si notava, ma
   tolti i campi dell'albo l'ultima è diventata il sito, alta 29,5 contro i
   37,5 delle altre (misurato il 06/10/2026 su id 1040 a 1440). */
.ui-metrics--aligned .ui-metric:last-child {
    padding-bottom: 7px;
    border-bottom: 1px solid var(--platinum);
}

.ui-metrics--aligned .ui-metric-etichetta {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    font-size: var(--etichetta-testo);
    font-weight: var(--etichetta-peso);
    color: var(--etichetta-colore);
}

/* L'icona e' un ancoraggio visivo, non un'informazione: dice «riga di
   anagrafica» e aiuta a ritrovare il campo scorrendo, quindi resta al livello
   del testo terziario. Un'icona colorata quanto il valore competerebbe con lui. */
.ui-metrics--aligned .ui-metric-icona {
    font-size: 12px;
    color: var(--stone);
    flex-shrink: 0;
}

.ui-metrics--aligned .ui-metric-etichetta span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ⚠️ Peso 500 e non `--valore-peso` (600), dal 06/10/2026, e l'override sta
   QUI e non nel token, che leggono anche `ui-field-row`, `ui-dato-chip` e
   `ui-campo`. Sulla scheda i valori dell'anagrafica sono maiuscoli — ragione
   sociale e sede vengono così dal Registro — e a 600 il maiuscolo urlava: ogni
   riga pesava più del titolo della card, che è 13 · 700. A 500 la misura resta
   15 px (decisione del 22/09/2026) e cambia solo il peso. */
.ui-metrics--aligned .ui-metric strong {
    min-width: 0;
    font-size: var(--valore-testo);
    font-weight: 500;
    color: var(--valore-colore);
    text-align: left;
    overflow-wrap: anywhere;
}

/* Sotto i 480 px di CARD l'etichetta va SOPRA il valore. Con la colonna fissa
   da 184 px su una card da 320 il valore ne aveva 124: la sede andava su
   quattro righe (alta 105 a 375 px) e la ragione sociale su tre. Impilata, la
   coppia resta una coppia — 2 px fra le due righe contro i 14 del filo — e il
   valore prende la larghezza intera. Sul contenitore, non sul viewport: lo
   stesso corpo misura 320 a 375 px e più di 600 da 640 in su. */
@container (max-width: 480px) {
    .ui-metrics--aligned .ui-metric,
    .ui-metrics--aligned .ui-metric.ui-metric--largo {
        grid-template-columns: minmax(0, 1fr);
        gap: 2px;
    }
}

/* Un valore di VOCABOLARIO del Registro — forma giuridica, stato attività —
   arriva tutto maiuscolo («SOCIETA' A RESPONSABILITA' LIMITATA») e si rende in
   frase: maiuscola solo la prima lettera. Solo resa: `textContent`, ricerca ed
   export restano come in archivio. ⚠️ NON sui nomi propri — ragione sociale e
   sede restano maiuscole: una regola che rimette in minuscolo sbaglia sigle e
   «De'», e li farebbe divergere dalle liste e dal titolo della pagina. Né
   sull'ATECO, che arriva già in frase e comincia con il codice: la prima
   lettera sarebbe una cifra e la descrizione resterebbe in minuscolo.
   Modificatore di stato come `is-mono` e `is-vuoto`. */
.is-frase { text-transform: lowercase; }
.is-frase::first-letter { text-transform: uppercase; }

/* La fascia dei numeri di bilancio, in cima ai dati verificati: fondo tenue e
   nient'altro — nessun bordo, nessuna ombra. L'evidenza è **leggera apposta**:
   distingue tre misure dalle righe di testo che seguono senza diventare una
   card dentro la card, che a schermo si legge come un widget annidato. */
.ui-fascia-dati {
    padding: 18px 20px;
    border-radius: var(--radius-md);
    background: var(--bg-muted);
}

/* ⚠️ Il margine sotto lo prende solo se QUALCOSA SEGUE dentro lo stesso blocco:
   dove la fascia chiude il suo `widget-body` — il Rapporto commerciale — il
   distacco lo dà già il ritmo verticale fra blocchi (36 px), e i due si
   sommavano a 56. È la stessa forma del separatore che restava sospeso senza
   testo: una spaziatura che dipende da un vicino va condizionata al vicino. */
.ui-fascia-dati:not(:last-child) { margin-bottom: 20px; }

/* ⚠️ **16 px, non i 20 di `ui-metrics--riga`**, e l'override sta qui e non nel
   componente: là la banda è il contenuto principale di una card (il Rapporto
   commerciale), qui introduce una lista di righe da 13-14 px — quattro valori
   da 20 px sopra quelle righe si leggono come il titolo della card, e non lo
   sono. Il fondo tenue basta già a distinguerli; la dimensione in più era
   evidenza spesa due volte. */
.ui-fascia-dati .ui-metrics--riga .ui-metric strong { font-size: 16px; }
.ui-fascia-dati .ui-metrics--riga { column-gap: 40px; row-gap: 12px; }

/* I tre campi a riga intera — ragione sociale, sede, ATECO — non hanno più un
   punto in più: dal 22/09/2026 ogni valore è 15 px (`--valore-testo`), e un
   gradino sopra si leggeva come un titolo (Nicola: «i valori sono troppo
   grandi rispetto a tutto il resto»). Li distingue la riga intera. */

/* Un link dentro un valore (il sito web): al peso di un testo e nel blu dei
   link, con la sottolineatura sottile. A 600 la sottolineatura del browser
   ingrassa col peso e la riga pesava più di tutte le altre. */
.ui-metrics--aligned .ui-metric strong a {
    font-weight: 500;
    color: var(--accent-dark);
    text-decoration-thickness: 1px;
}

.ui-metrics--aligned .ui-metric strong.is-mono {
    font-family: 'Roboto Mono', monospace;
    font-size: 14px;
}

/* Un vuoto che dice PERCHE' e' vuoto resta un testo, non un valore: corsivo,
   grigio terziario, peso normale — la stessa resa di `.ui-campo-valore--vuoto`,
   perche' e' la stessa cosa in un componente diverso. */
.ui-metrics--aligned .ui-metric strong.is-vuoto {
    color: var(--stone);
    font-size: var(--aiuto-testo);
    font-style: italic;
    font-weight: 400;
}

/* ============================================================
 * Figure chiave in tabella — `.ui-persone`
 * ============================================================
 * Una persona per riga, le sue informazioni in colonna. Sostituisce il blocco
 * annidato (persona → gruppi per tipo → righe) che rendeva ogni figura alta
 * quattro righe per mostrare un telefono e un'email.
 *
 * Regge perche' i dati lo permettono, ed e' stato misurato prima di scriverlo:
 * **205 persone su 225** hanno esattamente due recapiti, uno per tipo; il
 * massimo per tipo e' **2**; e **217 soggetti su 218** hanno una persona sola.
 * Dove i valori dello stesso tipo sono piu' d'uno si impilano nella cella,
 * `.ui-persone-extra` — quattro persone in tutto l'archivio.
 */
/* ⚠️ L'unica eccezione al maiuscolo delle celle (`.ui-table tbody td`), e vale
   per gli IDENTIFICATORI DI MACCHINA: un'email o una URL in maiuscolo si legge
   come se andasse digitata così, ed è più difficile da leggere per davvero. La
   regola globale nasce dalle anagrafiche dei gestionali, che arrivano già
   maiuscole — un'email non è un'anagrafica. Il NOME della persona resta
   maiuscolo: quello è anagrafica, e uniformarlo è il motivo per cui la regola
   esiste. */
.ui-table tbody td .ui-recapito-valore,
.ui-table tbody td .ui-persone-extra,
.ui-table tbody td .ui-persone-fonte,
.ui-table tbody td .ui-recapito-fonte { text-transform: none; }

/* Una cella può portare due righe — la Provenienza aggiunge «recapiti da …»
   quando i recapiti vengono da un'altra fonte. Con l'allineamento al centro che
   `.ui-table td` applica, le altre celle galleggerebbero a metà invece di stare
   sulla prima riga, e la riga si leggerebbe sfalsata. */
.ui-persone tbody td { vertical-align: top; }

.ui-persone-nome { font-weight: 600; color: var(--jet); }

/* Dal 06/10/2026 la provenienza della figura ha una colonna sua; questa riga
   resta per la sola fonte dei recapiti quando e' ALTRA da quella della figura,
   sotto la provenienza nella stessa cella. */
.ui-persone-fonte {
    display: block;
    font-size: 10px;
    font-weight: 400;
    color: var(--stone);
    margin-top: 1px;
}

.ui-persone-extra {
    display: block;
    font-size: 11px;
    color: var(--text-muted);
    margin-top: 2px;
}

/* Recapiti — la TABELLA dei contatti dell'organizzazione, dal 21/08/2026.
   Prima era una lista di gruppi con un titolo per tipo, e prima ancora righe
   `ui-field-row`. Il passaggio a tabella non è cosmesi: nella stessa scheda la
   card Persone era già una tabella, e due liste con due impaginazioni diverse
   si leggono come due componenti presi da posti diversi. In tabella il tipo è
   una COLONNA — quindi si ordina, e non si ripete come intestazione di gruppo —
   e la provenienza smette di essere una chiosa del titolo per diventare un dato
   confrontabile in verticale, come «Affidabilità» fra le persone.
   Le classi del blocco annidato — `ui-org`, `ui-persona*`, `ui-recapito-gruppo`,
   `ui-recapito-tipo`, `ui-recapiti-coda`, `ui-fonte-blocco` — sono state
   rimosse con esso: erano CSS che nessuna vista usava più, cioè la forma in cui
   un catalogo comincia a insegnare la versione vecchia. */

/* La cella del recapito può portare due righe (il valore e, nel caso raro, la
   persona a cui appartiene) mentre le altre ne portano una: senza questo, tipo
   e provenienza galleggiano a metà del valore. È la stessa regola di
   `.ui-persone`. */
.ui-recapiti tbody td { vertical-align: top; }

.ui-recapito-valore {
    font-size: 13px;
    font-weight: 500;
    color: var(--jet);
    /* Un indirizzo lungo tronca invece di allargare la card: la larghezza della
       colonna e' la stessa per tutti, o l'incolonnamento salta di nuovo. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ui-recapito-valore--mono { font-family: 'Roboto Mono', monospace; }

.ui-recapito-fonte { font-size: 11px; color: var(--stone); white-space: nowrap; }

.ui-recapito-nota { font-size: 11px; color: var(--stone); }

/* Un modulo di scrittura che sta chiuso finché non serve.
   ⚠️ È l'ECCEZIONE: dove una card è vuota per costruzione, nascondere l'unico
   modo di riempirla la rende indistinguibile da una di sola lettura. Si usa
   dove la card ha già contenuto proprio e i moduli sono più d'uno. Il
   `<summary>` porta le classi di `ui-btn`: il marcatore nativo va tolto, o
   accanto al bottone resta un triangolo che non è del design system. */
/* ============================================================
 * Il corpo di un modulo in linea — `.ui-inline-form-corpo`
 * ============================================================
 * Aperto, il modulo è una SUPERFICIE DI LAVORO e non la continuazione della
 * lista sopra: il fondo rientrato lo dice senza aggiungere un bordo. Prima era
 * piatto — sommario, riga di stato, campi e nota tutti allineati alla stessa
 * colonna e allo stesso peso — e la sezione si leggeva come quattro righe di
 * testo invece che come un modulo.
 */
.ui-inline-form-corpo {
    display: grid;
    gap: 12px;
    padding: 14px 16px;
    background: var(--platinum);
    border-radius: var(--radius-sm);
}

/* I campi si raggruppano per SIGNIFICATO, e la distanza dice il gruppo: 8 px
   dentro (identità: nome, cognome, ruolo) e 20 fra un gruppo e l'altro
   (recapiti: telefono, email, LinkedIn). È la stessa regola della banda di
   metriche — a raggruppare è la prossimità, non una linea. */
.ui-form-campi {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px var(--space-4);
}
.ui-form-gruppo {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    min-width: 0;
}
.ui-form-gruppo--espandi { flex: 1 1 220px; }

/* I comandi in fondo, insieme: l'azione primaria e la sua via d'uscita si
   leggono come una coppia, non come due elementi capitati vicini. Vale dentro un
   modulo in linea (dove `margin-left: auto` li spinge in coda alla riga dei
   campi) e in fondo a un modulo in una card (il Profilo, l'anteprima dei
   Caricamenti). La TAGLIA dei bottoni la dichiara il contenitore — alti quanto
   i campi, vedi `.ui-btn-lg` — così nessuno deve ricordarsela. */
.ui-form-azioni {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-left: auto;
}

/* La riga che dichiara COSA si sta correggendo, dentro il modulo.
   ⚠️ Esiste perché il sommario del modulo NON deve cambiare testo: quello è il
   comando della card — «aggiungi una figura» — e vederlo diventare «correggi»
   mentre si clicca la matita di una riga si legge come se il comando fosse
   stato dirottato. Lo stato della correzione appartiene al modulo. */
.ui-form-corregge {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--border);
    font-size: 12px;
    color: var(--text-muted);
}
.ui-form-corregge strong { color: var(--text-heading); }

.ui-inline-form > summary { list-style: none; cursor: pointer; }
.ui-inline-form > summary::-webkit-details-marker { display: none; }
.ui-inline-form[open] > summary { margin-bottom: 10px; }

.ui-completeness-ring {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 3px solid var(--alabaster);
}
.ui-completeness-ring > span {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 600;
    color: var(--jet);
}

/* ============================================================
 * Tooltip di spiegazione — `.ui-tooltip` + `.ui-tooltip-panel`
 * ============================================================
 * Il pannello che si apre dalla «i» accanto a un titolo. Era una collana di
 * utility ricopiata in **81 punti** con **cinque** firme diverse — `w-48`,
 * `w-56`, `w-64`, `w-72`, e una con `rounded-sm` — cioè la larghezza di un
 * tooltip la decideva chi lo scriveva.
 *
 * ⚠️ La larghezza ora è UNA, ed è misurata invece che scelta: la riga di testo
 * si legge fra 45 e 75 caratteri, e a 12px Inter un carattere vale ~6,2 px.
 * `w-64` (256 px) dà **41 caratteri per riga** — sotto il pavimento — ed è il
 * motivo per cui un testo lungo si leggeva come un blocco unico. 320 px danno
 * ~52 caratteri, dentro la finestra.
 *
 * ⚠️ E la STRUTTURA è del pannello, non di chi scrive: `p + p` stacca i
 * capoversi, `ul` fa l'elenco. Senza, l'unico modo di andare a capo era un
 * `<br>` — e un `<br>` non è un capoverso, è una riga in più dentro lo stesso
 * blocco. Il testo lungo va scritto in capoversi: la regola sta in
 * `DESIGN_SYSTEM.md § Tono dei testi`.
 *
 * ⚠️ `box-shadow` non esiste in questa applicazione (`body * { box-shadow:
 * none !important }`), quindi il `shadow-lg` che quelle 81 copie portavano non
 * disegnava niente: a separare il pannello è il BORDO, e per questo è più
 * scuro del bordo di una card — bianco su bianco misura 1,10:1 e non separa.
 */
.ui-tooltip {
    position: relative;
    display: inline-flex;
    align-items: center;
}

/* La «i» è un comando, quindi si raggiunge da tastiera: `:focus-within` apre il
   pannello come l'hover. Senza, l'unica spiegazione di un widget è dietro un
   gesto che con la tastiera non esiste. */
.ui-tooltip > .bi {
    font-size: 11px;
    color: var(--text-light);
    cursor: help;
}
.ui-tooltip > .bi:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
    border-radius: 2px;
}

.ui-tooltip-panel {
    display: none;
    position: absolute;
    /* Lo scarto lo mette `initTooltipNelContenuto()` (ui.js) quando il pannello
       uscirebbe a sinistra di `.main-content`; di norma vale 0. */
    right: calc(-1 * var(--tooltip-scarto, 0px));
    top: 22px;
    z-index: 50;
    width: 320px;
    max-width: min(360px, calc(100vw - 32px));
    padding: 12px 14px;
    background: var(--white);
    border: 1px solid var(--silver);
    border-radius: var(--radius-sm);
    font-size: 12px;
    line-height: 1.55;
    color: var(--text-body);
    text-transform: none;
    letter-spacing: normal;
    font-weight: 400;
    text-align: left;
    white-space: normal;
}
.ui-tooltip:hover > .ui-tooltip-panel,
.ui-tooltip:focus-within > .ui-tooltip-panel {
    display: block;
}

/* ⚠️ Sotto i 640 px il pannello si aggancia alla FINESTRA, non alla «i».
   Ancorato alla «i» con `right: 0` si apre verso sinistra, ed è giusto finché
   la «i» sta a destra della card — ma quando l'intestazione va a capo la «i»
   scende a sinistra, e i 320 px del pannello finivano a **-181 px**, cioè fuori
   dalla finestra dal lato opposto (misurato a 500 px sulla scheda anagrafica).

   Agganciarlo alla card non basta: dentro un KPI da 189 px il pannello
   scendeva a **155**, cioè 25 caratteri per riga — meno della metà del
   pavimento di leggibilità, quindi il rimedio del bordo avrebbe riprodotto il
   difetto del muro. `position: fixed` con `left`/`right` in pixel lo lega alla
   finestra: non può uscire da nessun lato e non può stringersi sotto la
   larghezza della finestra meno i margini, qualunque sia il contenitore.
   `top: auto` lascia la posizione STATICA, cioè quella che il pannello avrebbe
   avuto nel flusso: resta sotto la sua «i». */
@media (max-width: 640px) {
    .ui-tooltip-panel {
        position: fixed;
        left: 16px;
        right: 16px;
        top: auto;
        width: auto;
        max-width: none;
    }
}

.ui-tooltip-panel p + p { margin-top: 8px; }
.ui-tooltip-panel strong { color: var(--text-heading); font-weight: 600; }
.ui-tooltip-panel em { font-style: italic; }

.ui-tooltip-panel ul {
    margin-top: 6px;
    display: grid;
    gap: 4px;
}
.ui-tooltip-panel li {
    position: relative;
    padding-left: 12px;
}
.ui-tooltip-panel li::before {
    content: '\2013';
    position: absolute;
    left: 0;
    color: var(--stone);
}

/* Table header th with text-xs: 12px (Tailwind default) */
thead th { font-size: 12px; }

/* Tables */
.guida-table-wrap {
    overflow-x: auto;
    margin: 12px 0 16px;
    border: 1px solid var(--alabaster);
    border-radius: 8px;
}
.guida-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}
.guida-table th,
.guida-table td {
    padding: 8px 12px;
    text-align: left;
    border-bottom: 1px solid var(--alabaster);
    vertical-align: top;
}
.guida-table th {
    background: var(--platinum);
    color: var(--jet);
    font-weight: 600;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    border-bottom: 2px solid var(--silver);
}
.guida-table tbody tr:last-child td { border-bottom: 0; }
.guida-table tbody tr:hover { background: var(--platinum); }

/* ==========================================================================
   Card modulo della home (?page=home)
   ==========================================================================
   La home disegna una card per modulo con header, descrizione, KPI e un piede
   di collegamenti separato da una riga. Il piede deve toccare i bordi, quindi
   il padding non sta sulla card ma sulle sue parti — e per ottenerlo `home.php`
   portava **sei attributi `style=` inline**, che CLAUDE.md vieta e che nessuno
   avrebbe trovato cercando in app.css.
   Le misure sono quelle che c'erano, riportate qui senza cambiarle. */
/* =============================================================================
   MOVIMENTO RIDOTTO
   Chi ha chiesto meno movimento non ha chiesto nessun feedback: restano
   opacita' e colore, che spiegano cosa sta cambiando. Via l'oscillazione
   continua — in pagina ce n'erano tredici, fra 0,4 e 0,7 Hz, senza via d'uscita.

   ⚠️ Questo blocco diceva la frase qui sopra e faceva il contrario:
   `transition-duration: 0.01ms` su `*` spegne anche le dissolvenze, cioe'
   proprio le due proprieta' che la riga prometteva di lasciare. Adesso si
   restringe l'ELENCO delle proprieta' invece della durata: cio' che si muove
   (transform, width, height) scatta e basta, cio' che cambia aspetto senza
   spostarsi continua a transitare, piu' corto.
   ============================================================================= */

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-property: opacity, color, background-color, border-color, fill, stroke !important;
        transition-duration: 150ms !important;
        scroll-behavior: auto !important;
    }

    /* Eccezione: lo spinner e' un segnale di attivita', e fermo non segnala niente. */
    .bi-spin,
    .ui-animate-spin {
        animation-duration: 2.4s !important;
        animation-iteration-count: infinite !important;
    }

    /* La barra indeterminata avanza per traslazione: ferma resterebbe fuori
       campo, cioe' invisibile. Diventa piena. */
    .ui-progress-fill.indeterminate {
        width: 100% !important;
        transform: none !important;
    }
}

/* Lo scrim del menu mobile: la sidebar scivola per 300 ms, il velo compariva
   e spariva di colpo. Stessa tecnica del modale, guidata dalla `.hidden` che
   initMobileSidebar() gia' commuta. */
#sidebarOverlay {
    opacity: 1;
    transition: opacity 300ms var(--ease-out),
                display 300ms allow-discrete;
}

#sidebarOverlay.hidden {
    display: none;
    opacity: 0;
}

@starting-style {
    #sidebarOverlay:not(.hidden) {
        opacity: 0;
    }
}

/* Righe del banner (#bi-banner): entrano e escono dallo stesso bordo, quello
   di sopra. Un percorso asimmetrico si legge come due elementi diversi.
   Transizione e non keyframes: le righe si accodano a raffica e un keyframe
   ripartirebbe da zero invece di ri-mirare.
   Percentuali e non pixel: l'altezza della riga dipende dal testo. */
.bi-banner-riga {
    opacity: 1;
    transform: translateY(0);
    transition: opacity 250ms var(--ease-out),
                transform 250ms var(--ease-out);
}

@starting-style {
    .bi-banner-riga {
        opacity: 0;
        transform: translateY(-100%);
    }
}

/* L'uscita e' piu' svelta dell'entrata: chi arriva si annuncia, chi se ne va
   non deve far aspettare. */
.bi-banner-riga--esce {
    opacity: 0;
    transform: translateY(-100%);
    transition-duration: 200ms;
}

/* Blocco in attesa di dati (BiUI.setBusy). Non nasconde il contenuto vecchio:
   lo smorza. Chi guardava un numero continua a vederlo mentre arriva quello
   nuovo, e intanto sa che non e' piu' quello buono. */
.is-busy {
    opacity: 0.45;
    pointer-events: none;
    transition: opacity 120ms var(--ease-out);
}

/* ============================================================
 * PAGINATORE — BiUI.paginate()
 * Il markup lo genera il wrapper: queste classi sono il suo
 * contratto. Cambiarle senza guardare helpers.js rompe ogni
 * tabella dell'applicazione in una volta.
 * ============================================================ */

.ui-paginator {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    padding: 0.75rem 0 0.25rem;
    border-top: 1px solid var(--border);
}

.ui-paginator-info {
    font-size: 12px;
    color: var(--text-muted);
}

/* Va a capo anche QUI, non solo sul paginatore. Il capo dichiarato sopra spezza
   fra l'informazione e i comandi, ma i comandi sono un blocco solo: sul telefono
   il selettore del taglio piu' le pagine misuravano 312 px in 269, cioe' il 16%
   fuori dal padding della card — e `.ui-paginator` non scorre (`overflow:
   visible`), quindi non era raggiungibile, era tagliato. Misurato il 22/09/2026
   a 375 px su `?page=lead` e `view=conferme`. */
.ui-paginator-controls {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem 0.75rem;
}

.ui-paginator-size {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 12px;
    color: var(--text-muted);
    white-space: nowrap;
}

.ui-paginator-pages {
    display: flex;
    align-items: center;
    gap: 0.125rem;
}

.ui-select-sm {
    padding: 0.2rem 1.5rem 0.2rem 0.5rem;
    font-size: 12px;
    height: auto;
    min-height: 0;
}

/* ============================================================
 * TABELLA BASE — .ui-table
 *
 * Prima non esisteva: `class="ui-table"` compariva in 5 viste del BI
 * e non applicava NIENTE, mentre lo stile stava spalmato su ogni
 * cella (px-4 py-3 text-right text-xs font-bold text-ui-body
 * uppercase tracking-wide — 8/12 classi per <th>). Nessuno ricopia
 * dodici classi identiche due volte: e' da li' che le tabelle
 * divergevano fra loro.
 *
 * Qui il markup torna a <th>/<td> nudi. Restano due modificatori,
 * perche' sono decisioni e non ripetizioni:
 *   .num     cella numerica: destra, tabellare, non spezzabile
 *   .fissa   colonna che resta ferma allo scroll orizzontale
 *
 * Ce n'era un terzo, .ui-table--compatta, tolto l'08/08/2026: nessuna
 * vista lo nominava, non stava nel catalogo e non stava nei documenti,
 * quindi era una decisione che nessuno poteva prendere. Se serviranno
 * righe piu' basse per una tabella lunga, si rifa' con la sua vetrina.
 *
 * Si combina con .sortable-table (ordinamento) e
 * .ui-table-sticky-head (intestazione ferma allo scroll verticale).
 * ============================================================ */

.ui-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--table-font-size, 13px);
}

.ui-table thead {
    background: var(--platinum);
    border-bottom: 1px solid var(--border);
}

.ui-table th {
    padding: var(--cella-y) 1rem;
    text-align: left;
    font-size: 11px;
    font-weight: 700;
    color: var(--text-body);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    white-space: nowrap;
}

.ui-table td {
    padding: var(--cella-y) 1rem;
    color: var(--text-body);
    vertical-align: middle;
}

.ui-table tbody tr {
    border-bottom: 1px solid var(--border);
    transition: background-color 120ms var(--ease-out);
}

.ui-table tbody tr:last-child { border-bottom: 0; }
.ui-table tbody tr:hover { background: var(--platinum); }

/* Testo delle celle SEMPRE maiuscolo. Le anagrafiche arrivano dai gestionali
   gia' in maiuscolo e le tassonomie scritte a mano no: due tabelle vicine
   finivano una in maiuscolo e una in minuscolo, e la differenza si legge come
   se le due colonne fossero di natura diversa. Uniformare qui costa una riga;
   normalizzare i dati a monte non si puo', sono anagrafiche di terzi.
   `text-transform` non tocca il valore: la ricerca, l'ordinamento e l'export
   continuano a vedere la stringa originale. */
.ui-table tbody td {
    text-transform: uppercase;
    /* Un punto in meno: il maiuscolo occupa piu' larghezza a parita' di
       caratteri, e senza questo la stessa colonna manda a capo cinque nomi in
       piu' e ne tronca cinque. Misurato sui 68 record di `portafoglio`:
       12px -> 5 troncati, 11px -> 0. */
    font-size: 11px;
}

/* Prima colonna: e' il nome della cosa, quindi porta il peso. */
.ui-table tbody td:first-child {
    font-weight: 600;
    color: var(--text-heading);
}

/* Numeri: a destra, cifre di larghezza uguale perche' le colonne si
   leggano incolonnate, e mai a capo — un importo spezzato fra due
   righe si legge come due numeri. */
.ui-table th.num,
.ui-table td.num {
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* Colonna ferma allo scroll orizzontale. `bg-inherit` sulla cella
   serve o il contenuto sottostante trasparirebbe. */
.ui-table th.fissa,
.ui-table td.fissa {
    position: sticky;
    left: 0;
    z-index: 1;
    background: inherit;
}
.ui-table thead th.fissa { background: var(--platinum); z-index: 2; }

/* Stato vuoto: una tabella senza righe deve dire perche', non
   restare bianca. */
.ui-table .ui-table-vuota td {
    padding: 2.5rem 1rem;
    text-align: center;
    color: var(--text-muted);
    font-weight: 400;
}

/* Stato di guasto: il caricamento non e' riuscito, e la riga ospita un
   `.ui-error-state` intero. La cella non mette padding proprio — quello lo
   porta gia' lo stato, e sommati farebbero 48+40 px.
   ⚠️ `text-transform` si spegne: `.ui-table tbody td` mette maiuscolo perche'
   le anagrafiche arrivano cosi' dai gestionali, ma qui dentro non c'e' un dato,
   c'e' una frase — e «IL SERVER NON HA RISPOSTO» urla invece di spiegare.

   ⚠️ Due selettori e non uno, perche' `BiUI.erroreTabella()` e' generica e
   funziona su qualunque `<table>`: senza il secondo, su una tabella priva di
   `.ui-table` restava il `padding: 10px 12px` di `table td` sommato ai 48 dello
   stato. E il primo non si puo' togliere: `.ui-table-errore td` da solo ha una
   classe e un tipo, mentre `.ui-table tbody td` ne ha uno e due, quindi
   perderebbe la cascata proprio dove il maiuscolo c'e'. */
.ui-table .ui-table-errore td,
.ui-table-errore td {
    padding: 0;
    text-transform: none;
}

/* Stesso trattamento quando il vuoto di una tabella ospita uno stato intero —
   icona, titolo e la frase che dice PERCHE' e' vuoto — invece del solo testo.
   ⚠️ Il selettore guarda il CONTENUTO (`:has`), non una classe nuova: le righe
   `ui-table-vuota` che portano il solo `—` reso dal server devono tenersi i
   loro 2,5rem, o si accartocciano. Chrome 150 lo supporta, ed e' gia' usato
   altrove in questo foglio. */
.ui-table .ui-table-vuota td:has(.ui-empty-state),
.ui-table-vuota td:has(.ui-empty-state) {
    padding: 0;
    text-transform: none;
    /* `.ui-table tbody td` porta peso 600 — le celle di una tabella sono dati —
       e la frase dello stato lo ereditava: stessa frase, grassetto qui e
       regolare nella card accanto. Il titolo tiene il suo 600, che lo dichiara
       da se'. */
    font-weight: 400;
}

/* ============================================================
 * BLOCCO DI CODICE — .ui-code-block
 * Usato nella palestra per mostrare il markup da copiare.
 * Era gia' nel markup di template.php ma senza alcuna regola:
 * il codice si vedeva con il font della pagina, indistinguibile
 * dal testo che lo commentava.
 * ============================================================ */

/* ⚠️ La superficie piu' estesa dell'applicazione: **775.806 px²** resi nella
   palestra, diciassette volte una card KPI scura. Per un giro ha avuto un token
   suo (un blu meno saturo) perche' col navy del marchio a
   saturazione piena un'area cosi' grande vibrava; col Midnight Express, che sta
   a L 11%, il problema non si pone e i due token sono tornati uno. */
.ui-code-block {
    display: block;
    padding: 0.875rem 1rem;
    background: var(--jet);
    color: var(--snow);
    border-radius: 6px;
    font-family: 'Roboto Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    line-height: 1.6;
    overflow-x: auto;
    white-space: pre;
    tab-size: 2;
}

.ui-code-block code {
    font: inherit;
    color: inherit;
    background: none;
    padding: 0;
}

/* Le due varianti che la famiglia .badge aveva e .ui-badge no, copiate
   identiche quando fu scritta l'unificazione. La famiglia vecchia non
   esiste piu' (07/08/2026); queste restano perche' sono in uso. */
/* ⚠️ Testo `--jet` e non `--iron`, ed e' un difetto che la palette del
   07/09/2026 ha creato e la sonda ha trovato: il nuovo alabaster e' piu' scuro
   (`#D5D5D5` contro `#D8DFE8`) e il nuovo iron piu' chiaro (`#5C6972` contro
   `#43536B`), quindi la coppia si e' stretta da entrambi i lati ed e' passata da
   **5,82** a **3,85**. Su questo fondo nessun grigio di casa arriva a 4,5 —
   iron 3,85, stone 3,41, Mariner 4,05 — e l'unico che regge e' il jet (13,12).
   Il badge resta distinguibile da `--light`, che ha lo stesso testo ma fondo
   `--platinum`. */
.ui-badge--neutral { background: var(--alabaster); color: var(--jet); }
.ui-badge--light   { background: var(--platinum);  color: var(--iron); border-color: var(--alabaster); }

/* ============================================================
 * TRE COMPONENTI CHE IL MARKUP USAVA SENZA CHE ESISTESSERO
 *
 * Misurato sulle viste del BI: comparivano nel markup e non
 * avevano alcuna regola CSS. Non davano errore — semplicemente
 * l'elemento restava quello di default del browser, che e' il
 * modo piu' silenzioso di avere un'interfaccia incoerente.
 *   .ui-checkbox  4 viste (servizi, lead, e i bulk-select in JS)
 *   .ui-btn-xs    lead.js, sui bottoni dentro le righe di tabella
 *   .ui-avatar    profile.php
 * ============================================================ */

/* Checkbox: il controllo nativo non si colora, e con accent-color
   si allinea alla palette senza reimplementarlo da zero — che
   significherebbe rifare anche focus, tastiera e stato indeterminato.
   La spunta è dello stato scelto (`--scelto-filo`) OVUNQUE dal 22/09/2026
   (sera): era jet fuori dai configuratori, e nella matrice dei permessi
   dodici quadrati quasi neri pesavano più dei nomi dei moduli. */
.ui-checkbox {
    width: 16px;
    height: 16px;
    accent-color: var(--scelto-filo);
    cursor: pointer;
    vertical-align: middle;
    flex: none;
}

.ui-checkbox:disabled {
    cursor: not-allowed;
    opacity: 0.5;
}

/* Bottone dentro una riga di tabella: piu' basso di ui-btn-sm,
   perche' non deve far crescere l'altezza della riga. */
.ui-btn-xs {
    height: 24px;
    padding: 0 8px;
    font-size: 11px;
    gap: 0.25rem;
}

/* Iniziali dell'utente. Cerchio pieno, non immagine: non c'e' un
   caricamento di foto e un cerchio vuoto sarebbe peggio di niente. */
.ui-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--jet);
    color: var(--snow);
    font-size: 14px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    flex: none;
}

.ui-avatar--sm { width: 28px; height: 28px; font-size: 11px; }
.ui-avatar--lg { width: 56px; height: 56px; font-size: 18px; }

/* ============================================================
 * CARD SELEZIONABILE — .ui-choice-card
 *
 * Accorpa .ui-cat-card e .ui-method-card, che facevano la stessa
 * cosa (una card cliccabile con stato attivo) con due nomi di
 * stato diversi: .active e .is-active. Due nomi per lo stesso
 * stato non si vedono leggendo il CSS, si scoprono a schermo
 * quando la selezione non si accende.
 *
 * Vince .is-active: e' il nome usato dai due soli file che nel BI
 * adoperano davvero una di queste card (lead.php, lead.js).
 *
 * Lo stato attivo NON usa box-shadow, e non e' una preferenza:
 * piu' in basso in questo file c'e'
 *     body *, body *::before, body *::after { box-shadow: none !important; }
 * cioe' l'elevazione e' spenta ovunque, con poche eccezioni riammesse
 * a forza di !important. .ui-method-card.is-active dichiara
 * `box-shadow: 0 0 0 1px var(--jet)` e non l'ha MAI disegnato:
 * misurato a schermo, computed = none. Una regola che c'e' e non fa
 * niente e' peggio di una che manca, perche' chi la legge crede che
 * il caso sia coperto.
 *
 * Quindi: bordo jet piu' fondo platinum. Due grigi, nessuna ombra,
 * niente da contrattare col !important globale. Il fondo seaweed di
 * .ui-cat-card resta fuori: la palette riserva il colore alla
 * semantica finanziaria e agli avvisi, e «questa e' la voce che hai
 * scelto» non e' ne' l'uno ne' l'altro — un fondo verde su una scelta
 * neutra la fa leggere come positiva.
 *
 * L'hover si ferma a silver: se anche lui arrivasse a jet, passare
 * col puntatore sembrerebbe aver gia' selezionato.
 * ============================================================ */

.ui-choice-card {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 16px;
    border: 1px solid var(--alabaster);
    border-radius: 8px;
    background: #fff;
    text-align: left;
    transition: border-color .15s var(--ease-out), background-color .15s var(--ease-out);
}

.ui-choice-card:hover {
    border-color: var(--silver);
}

.ui-choice-card.is-active {
    border-color: var(--jet);
    background: var(--platinum);
}

/* Variante per la riga orizzontale scorrevole (era .ui-cat-card):
   stessa card, meno respiro, perche' ne stanno otto in fila. */
.ui-choice-card--compact {
    padding: 8px 12px;
    gap: 2px;
}

/* ============================================================
 * PALESTRA — classi che sostituiscono gli stili inline.
 *
 * Vivono qui e non dentro template.php perche' la regola vale
 * anche per la pagina che la insegna: se il catalogo mostra
 * `style="background:#1AAD7E"`, chi copia copia quello.
 *
 * I campioni prendono il colore dalla VARIABILE, non
 * dall'esadecimale ricopiato: prima erano scritti a mano in
 * 22 punti, quindi potevano divergere dalla palette vera senza
 * che niente lo segnalasse.
 * ============================================================ */

/* La palette come TESSERE, una per colore: campione, nome, esadecimale, token
   e ruolo. Era una griglia ad altezza fissa (280 px) con l'etichetta scritta
   sopra il colore: aggiunti i blu, le righe non ci stavano più e gli ultimi
   campioni erano una striscia senza nome né valore. Qui il testo sta sotto il
   campione, sul bianco, quindi si legge su ogni colore. */
.ui-palette {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr));
    gap: 12px;
}
.ui-palette-tessera {
    display: flex;
    flex-direction: column;
    min-width: 0;
    overflow: hidden;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background-color: var(--white);
}
.ui-palette-campione {
    height: 64px;
    border-bottom: 1px solid var(--border);
}
.ui-palette-testo {
    display: grid;
    gap: 2px;
    min-width: 0;
    padding: 10px 12px 12px;
}
.ui-palette-nome { font-size: 13px; font-weight: 600; color: var(--text-heading); }
.ui-palette-hex { font-family: var(--font-mono); font-size: 12px; font-weight: 600; color: var(--text-body); }
.ui-palette-token { font-size: 11px; color: var(--text-light); overflow-wrap: anywhere; }
.ui-palette-ruolo { margin-top: 4px; font-size: 11px; line-height: 1.4; color: var(--text-body); }
/* Il campione piccolo della tabella dei ruoli. */
.ui-palette-punto {
    display: inline-block;
    width: 20px;
    height: 20px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    vertical-align: middle;
}
/* Lo script della palestra marca un campione che il browser rende diverso dal
   valore letto da `:root`. */
.ui-palette-campione.is-diverso,
.ui-palette-punto.is-diverso { outline: 2px solid var(--cherry); outline-offset: 2px; }

.ui-swatch--snow          { background: var(--snow); }
.ui-swatch--platinum      { background: var(--platinum); }
.ui-swatch--alabaster     { background: var(--alabaster); }
.ui-swatch--silver        { background: var(--silver); }
.ui-swatch--stone         { background: var(--stone); }
.ui-swatch--iron          { background: var(--iron); }
.ui-swatch--jet           { background: var(--jet); }
.ui-swatch--jet-black     { background: var(--jet-black); }
.ui-swatch--burgundy      { background: var(--burgundy); }
.ui-swatch--cherry        { background: var(--cherry); }
.ui-swatch--dark-emerald  { background: var(--dark-emerald); }
.ui-swatch--seaweed       { background: var(--seaweed); }
.ui-swatch--princeton     { background: var(--princeton); }
.ui-swatch--azure         { background: var(--azure); }
.ui-swatch--azure-dark    { background: var(--azure-dark); }
.ui-swatch--sky           { background: var(--sky); }
.ui-swatch--navy          { background: var(--navy); }
.ui-swatch--hairline      { background: var(--hairline); }
/* Il campione del BIANCO: e' il fondo delle card dal 07/09/2026, e senza una
   riga sua la palette lo dava per scontato mentre `--snow` si prendeva il suo
   posto nelle regole d'uso. Il bordo lo separa dalla pagina — su un campione
   bianco in una griglia bianca non c'e' niente da vedere. */
.ui-swatch--white         { background: var(--white); }
.ui-swatch--princeton-dark { background: var(--princeton-dark); }
/* Il velo dello stato scelto: e' un'alpha, quindi si vede sul bianco della
   tabella che lo ospita — lo stesso fondo su cui lo compone il linter. */
.ui-swatch--scelto-velo   { background-color: var(--scelto-velo); }


/* I due piu' chiari sparirebbero sul bianco: il bordo li rende visibili. */
.ui-swatch--snow,
.ui-swatch--platinum { border: 1px solid var(--alabaster); }


/* --- dimostrazione dei gradini di tracking (sezione Tipografia) --- */
/* Le due famiglie, nella palestra: si confrontano sulla STESSA stringa e allo
   stesso corpo, o il confronto misura la frase invece del carattere. */
.tpl-famiglia-display { font-family: var(--font-display); font-size: 28px; font-weight: 600; line-height: 1.2; letter-spacing: var(--tracking-display); color: var(--jet); }
.tpl-famiglia-sans    { font-family: var(--font-sans);    font-size: 28px; font-weight: 600; line-height: 1.2; letter-spacing: var(--tracking-display); color: var(--jet); }
.tpl-famiglia-riga    { font-family: var(--font-sans);    font-size: 14px; color: var(--iron); }
.tpl-famiglia-riga-display { font-family: var(--font-display); font-size: 14px; color: var(--iron); }

.tpl-tracking-display {
    font-size: 2.25rem;
    font-weight: 600;
    line-height: 1.05;
    letter-spacing: var(--tracking-display);
}
.tpl-tracking-display-kpi {
    font-family: var(--font-mono);
    font-size: 2.75rem;
    font-weight: 500;
    line-height: 1;
    letter-spacing: var(--tracking-display);
}
.tpl-tracking-body  { letter-spacing: var(--tracking-body); max-width: 62ch; }
.tpl-tracking-label { letter-spacing: var(--tracking-label); }
/* Il termine di paragone: il gradino spento. */
.tpl-tracking-zero  { letter-spacing: 0; }

/* --- stati dell'anello di completezza ---
   Erano `style="border-color: var(--seaweed)"` scritto a mano. Il colore
   dipende dalla percentuale, ma le soglie sono tre e hanno un nome: uno
   stato con un nome e' un modificatore, non un valore. */
.ui-completeness-ring--ok    { border-color: var(--seaweed); }
.ui-completeness-ring--warn  { border-color: var(--princeton); }
.ui-completeness-ring--vuoto { border-color: var(--alabaster); }

.ui-mini-ring--ok    { background: var(--seaweed); }
.ui-mini-ring--warn  { background: var(--princeton); }
.ui-mini-ring--vuoto { background: var(--alabaster); }

/* =============================================================================
   MATRICE DI RITENZIONE — .ui-matrice
   Una tabella dove il colore della cella E' il dato. Non e' un grafico perche'
   i numeri vanno letti uno per uno: quanti clienti di quale coorte, in che
   anno. Un grafico li renderebbe confrontabili e illeggibili.

   ⚠️ LA CELLA PORTA UNA CLASSE SUA, e non e' un vezzo: la zebratura globale
   di riga 709 e' `table tbody tr:nth-child(odd) td:not([class*="bg-"])`, cioe'
   DUE selettori di classe e QUATTRO di tipo. `.ui-matrice td[data-intensita]`
   ne ha due di classe e uno di tipo: pareggia sulle classi e perde sui tipi,
   quindi la zebra vince e le celle restano bianche e grigie. A schermo si
   legge come una tabella normale — nessun errore, nessuna riga in console, e
   la matrice sembra semplicemente non colorata.
   Con `td.ui-matrice-cella[data-intensita]` le classi diventano tre e la
   regola vince senza dipendere dall'ordine nel file.
   ============================================================================= */

/* Cinque gradini piu' lo zero. L'intensita' arriva da `data-intensita`, mai da
   uno `style` calcolato in JS: una soglia scritta nel foglio si rilegge, una
   scritta in una stringa di stile no. Le soglie stanno in una costante sola —
   `INTENSITA_MATRICE` in assets/js/portafoglio.js — e sono tarate contando le
   celle vere, non su numeri tondi. */
.ui-matrice td.ui-matrice-cella {
    text-align: center;
    font-variant-numeric: tabular-nums;
    transition: background-color 150ms var(--ease-out);
}
/* I gradini sono VELATURE DI --azure, non di --seaweed. La ritenzione e' una
   percentuale, non un importo: il verde le attribuiva un segno economico che
   non ha, e una matrice verde di 11 righe era la macchia di colore piu' grande
   della schermata. Il blu la riporta a essere una scala di intensita'.
   ⚠️ Il testo resta --jet su OGNI gradino, e ora e' una riga sola invece di due:
   sul blu i cinque fondi danno 17,25 · 15,01 · 12,35 · 9,82 · 7,30:1, cioe' il
   piu' carico e' ancora il triplo della soglia. Col verde i due gradini pieni
   avevano bisogno di una regola a parte.
   ⚠️ E ora --jet sta su TUTTI e cinque, non piu' dal quarto in su. I primi tre
   ereditavano --iron dalla tabella, e sul terzo gradino misuravano **3,62:1** —
   sotto 4,5. Non e' un difetto nato col blu: col verde lo stesso gradino dava
   **3,82**, cioe' era gia' sotto e nessuno l'aveva misurato, perche' la regola
   scritta parlava solo dei due fondi piu' carichi. La cella a intensita' ZERO
   resta --text-light: li' non c'e' nessun fondo. */
.ui-matrice td.ui-matrice-cella[data-intensita="0"] { background-color: transparent; color: var(--text-light); }
.ui-matrice td.ui-matrice-cella[data-intensita="1"] { background-color: rgba(67, 132, 247, 0.10); color: var(--jet); }
.ui-matrice td.ui-matrice-cella[data-intensita="2"] { background-color: rgba(67, 132, 247, 0.22); color: var(--jet); }
.ui-matrice td.ui-matrice-cella[data-intensita="3"] { background-color: rgba(67, 132, 247, 0.38); color: var(--jet); }
.ui-matrice td.ui-matrice-cella[data-intensita="4"] { background-color: rgba(67, 132, 247, 0.56); color: var(--jet); }
.ui-matrice td.ui-matrice-cella[data-intensita="5"] { background-color: rgba(67, 132, 247, 0.78); color: var(--jet); }

/* La cella non ancora misurabile. NON e' uno zero, e la differenza e' tutta:
   uno zero li' direbbe «nessuno e' tornato» dove la verita' e' «l'anno non e'
   ancora passato». Tratteggiata, cosi' non si confonde ne' con lo zero ne' con
   una cella che nessuno ha colorato. */
.ui-matrice td.ui-matrice-cella.ui-matrice-futura {
    background-color: transparent;
    background-image: repeating-linear-gradient(
        45deg,
        transparent, transparent 3px,
        var(--alabaster) 3px, var(--alabaster) 4px
    );
}

/* =============================================================================
   MENU DEI FILTRI — .ui-dropdown-menu--filtri
   Un dropdown che contiene controlli, non solo voci: piu' largo, e con una riga
   che ne affianca due senza farli sembrare due voci di menu.
   ============================================================================= */

.ui-dropdown-menu--filtri {
    min-width: 280px;
    max-height: min(70vh, 560px);
    overflow-y: auto;
}

/* La riga dei due anni. `label` inline e non sopra: sono due parole («Dal»,
   «al») e sopra il campo occuperebbero due righe per dire quanto una. */
.ui-dropdown-riga {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 8px 8px;
}

.ui-dropdown-riga .ui-select { flex: 1 1 0; min-width: 0; }
.ui-dropdown-riga .ui-form-label { margin: 0; white-space: nowrap; }

/* Il conteggio accanto al titolo del gruppo: dentro un menu lungo le voci
   scelte possono restare fuori campo, e il titolo si vede sempre. */
.ui-dropdown-header .ui-dropdown-conta {
    margin-left: 6px;
    padding: 1px 5px;
    border-radius: var(--radius-sm);
    background: var(--conteggio);
    color: var(--conteggio-testo);
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0;
}

/* La spunta compare solo sulla voce scelta: sempre visibile sarebbe un elenco
   di caselle tutte uguali, e lo stato si leggerebbe solo dal fondo. */
.ui-dropdown-item[data-filtro] > i { opacity: 0; }
.ui-dropdown-item[data-filtro].active > i { opacity: 1; }

/* Un comando dentro una frase. Non e' un bottone: sta in mezzo al testo di un
   avviso, e un bottone li' spezzerebbe la riga. */
.ui-link-inline {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}

.ui-link-inline:hover { text-decoration-thickness: 2px; }

/* Un indirizzo web che porta FUORI dall'applicazione (sito, LinkedIn): testo
   accorciato a host+percorso, freccia in coda che dice «nuova scheda». Tronca
   con l'ellissi dentro una cella stretta invece di allargarla. */
.ui-link-esterno {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    max-width: 100%;
    /* Il blu dei link, non `--table-link-color` (jet): in tabella un link
       esterno si deve distinguere dal nome di una voce, che resta interno. */
    color: var(--accent-dark);
    text-decoration: none;
}

.ui-link-esterno > span {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ui-link-esterno > i { flex: none; font-size: 11px; }

.ui-link-esterno:hover > span { text-decoration: underline; text-underline-offset: 2px; }

/* ============================================================
 * PANNELLO CRITERI — `.ui-criteri-*`
 * ============================================================
 * Il costruttore campagne compone un perimetro con molti criteri, e molti
 * criteri non stanno in una tendina: `.ui-dropdown-menu--filtri` regge otto
 * gruppi a valore singolo, non un albero di 64 voci con un operatore sopra.
 * Il pannello resta APERTO accanto alla lista perché è il posto dove si legge
 * cosa si sta chiedendo: un criterio che vive in un menu chiuso cambia i
 * numeri senza lasciare traccia a schermo (§n. 16, e l'avviso in pagina che
 * l'Anagrafica ha dovuto aggiungere per lo stesso motivo).
 *
 * ⚠️ I gruppi sono `<details>` e non pannelli pilotati dal JS: la chiusura è
 * nativa, sopravvive a un ridisegno del contenuto e funziona da tastiera senza
 * che nessuno la implementi. Il triangolo nativo si toglie, o accanto alla
 * freccia del design system ne resta una che non è nostra.
 */
/* Il pannello. La sola regola che gli appartiene e' il pareggio del ritmo in
   testa: il corpo della card da' gia' il distacco dall'intestazione, e i 10 px
   di `.ui-criteri-testa` sul PRIMO gruppo si sommavano a quello — il primo nome
   scendeva di dieci pixel rispetto a ogni altra card della pagina, che a
   schermo si legge come un allineamento sbagliato e non come una spaziatura.
   ⚠️ Misurata per contrasto (stesso markup con e senza la classe): 10 px
   contro 0. Una classe che non cambia niente e' peggio di una che non esiste. */
.ui-criteri .ui-criteri-gruppo:first-of-type > .ui-criteri-testa {
    padding-top: 0;
}

.ui-criteri-gruppo {
    border-bottom: 1px solid var(--border);
}
.ui-criteri-gruppo:last-child { border-bottom: 0; }
/* Dentro un PASSO i gruppi non si chiudono (`<div>`, non `<details>`): lì il
   filo fra due gruppi era un separatore in più su una card che ne ha già uno —
   il suo bordo. Li separa lo spazio, 28 px, che dice «altra domanda» senza
   disegnare niente. Il filo resta sui `<details>`, dove separa teste che si
   aprono e si chiudono. */
div.ui-criteri-gruppo { border-bottom: 0; }
div.ui-criteri-gruppo + .ui-criteri-gruppo,
div.ui-criteri-gruppo + .ui-criteri-spento { margin-top: var(--space-5); }

.ui-criteri-testa {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 0;
    cursor: pointer;
    list-style: none;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-heading);
}
.ui-criteri-testa::-webkit-details-marker { display: none; }
.ui-criteri-testa:hover { color: var(--primary-hover); }
.ui-criteri-testa:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

/* Il nome prende lo spazio, il resto si impacchetta a destra: `min-width: 0`
   perché un'etichetta lunga in un flex non scende sotto il proprio contenuto
   e spingerebbe fuori la freccia (best practice n. 5). */
.ui-criteri-nome { flex: 1 1 auto; min-width: 0; }

/* Quanti criteri di QUESTO gruppo sono attivi. Sta nella testa e non nel
   corpo: chiuso, il gruppo deve dire se sta restringendo — un pannello che
   nasconde un criterio attivo è il difetto contro cui il pannello esiste. */
.ui-criteri-conta {
    flex: none;
    min-width: 18px;
    padding: 1px 6px;
    border-radius: var(--radius-sm);
    background: var(--conteggio);
    color: var(--conteggio-testo);
    font-size: 10px;
    font-weight: 700;
    text-align: center;
}
.ui-criteri-conta.hidden { display: none; }
/* Il numero accanto al nome di una FAMIGLIA dice quante voci contiene, non
   quante ne sono scelte: è un dato fermo, e col fondo pieno del conteggio attivo
   era la cosa più scura del passo — sette pastiglie nere che non dicevano
   niente di nuovo. Resta il numero, in grigio. Il conteggio di un ramo chiuso
   (`data-conta-ramo`) sta FUORI dalla label e resta pieno: quello restringe. */
.ui-criteri-famiglia > .ui-criteri-conta {
    min-width: 0;
    padding: 0;
    background: transparent;
    color: var(--text-light);
    font-size: 11px;
    font-weight: 500;
}

/* Un criterio SCELTO si dichiara sul campo, non solo nel badge del gruppo: il
   badge dice QUANTI, non QUALI, e a gruppo aperto un campo con un valore era
   indistinguibile da uno vuoto — la differenza stava nel solo testo dentro la
   tendina, che va letto uno per uno.
   Misurati l'11/09/2026: bordo sull'accento 3,57:1 sul fondo bianco del campo
   (sopra il 3:1 che WCAG chiede a un indicatore non testuale), testo sulla
   velatura 5,29:1 (sopra 4,5).
   ⚠️ NON `--primary`: e' il bordo del `:focus`, e due stati con lo stesso bordo
   sono un solo stato. Azure contro jet misura 5,39:1, quindi «scelto» e «col
   cursore dentro» restano due cose.
   ⚠️ `background-color` e MAI `background`: la freccia di `.ui-select` e' una
   `background-image`, e la scorciatoia la cancella — un select senza freccia si
   legge come un'etichetta e nessuno prova a cambiarlo. */
.ui-criteri .ui-select.is-scelto,
.ui-criteri .ui-input.is-scelto {
    border-color: var(--accent);
    background-color: rgba(67, 132, 247, 0.06);
}

/* La riga che dichiara QUALE INSIEME descrive un widget, quando non e' quello
   della pagina. Nasce l'11/09/2026 da una domanda di Nicola: guardando i
   grafici del costruttore campagne ha concluso che fossero «globali del
   portafoglio». Non lo sono — misurato, seguono ogni criterio — ma due di loro
   mostrano TUTTA la storia delle voci selezionate invece dei soli dossier nella
   finestra scelta, e quella premessa stava solo nel tooltip, che e' nascosto.
   ⚠️ Un widget il cui insieme coincide con quello della pagina NON la porta: se
   ce l'avessero tutti smetterebbe di dire qualcosa. La si scrive dove l'insieme
   DIVERGE, ed e' l'unico caso in cui un numero plausibile puo' essere letto per
   un altro.
   Sta fra intestazione e corpo, e ripete il rapporto di gerarchia gia' in uso:
   8 px dal titolo (respiro dentro il blocco) contro i 36 fra blocchi. Colore
   `--text-light`, 5,01:1 sul fondo card. */
.ui-widget-perimetro {
    margin: 4px 0 8px;
    font-size: 11px;
    line-height: 1.35;
    color: var(--text-light);
}

.ui-criteri-freccia {
    flex: none;
    font-size: 10px;
    color: var(--text-light);
    transition: transform 160ms var(--ease-in-out);
}
.ui-criteri-gruppo[open] > .ui-criteri-testa .ui-criteri-freccia { transform: rotate(180deg); }

/* Le domande di un gruppo stanno una sotto l'altra, 20 px fra loro: quanto
   basta a leggerle come domande distinte, meno dei 28 fra due gruppi. */
.ui-criteri-corpo {
    display: grid;
    gap: 20px;
    padding: 2px 0 14px;
}

/* L'albero a due livelli. Scorre DENTRO di sé: 64 voci in una colonna sono
   ~1.800 px, e senza tetto il pannello diventerebbe più alto della lista che
   descrive — il resto dei gruppi finirebbe sotto la piega. */
.ui-criteri-albero {
    max-height: 280px;
    overflow-y: auto;
    padding-right: 4px;
}

/* L'albero A TUTTA LARGHEZZA (21/09/2026, il configuratore delle Liste lead):
   un passo intero è suo, quindi non scorre dentro di sé — scorre la pagina — e
   i rami si dispongono in colonne quante ne entrano: la larghezza minima la
   decide il testo, e la stessa regola dà una colonna sul telefono e due o tre a
   schermo pieno. `--colonne-strette` per famiglie dal nome corto (le regioni).
   ⚠️ GRIGLIA a righe e non colonne CSS (`column-width`), dal 22/09/2026 (Nicola:
   «quando si apre una lista si spagina tutta la vista»). Le colonne CSS
   ribilanciano le altezze: aprire «C Attività manifatturiere» (24 divisioni)
   spostava di colonna 8 sezioni su 19, e sulle regioni di Nuova campagna
   aprire la Sicilia muoveva anche 8 rami che la PRECEDONO — la voce sotto il
   puntatore cambiava posto. In griglia un ramo aperto allunga solo la sua riga:
   chi lo precede non si muove, chi lo segue scende nella stessa colonna.
   L'ordine di lettura diventa per righe (A B C / D E F), che per un elenco
   alfabetico si legge allo stesso modo. */
.ui-criteri-albero--colonne {
    max-height: none;
    overflow: visible;
    padding-right: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(22rem, 100%), 1fr));
    column-gap: 32px;
    align-items: start;
}
.ui-criteri-albero--colonne-strette { grid-template-columns: repeat(auto-fill, minmax(min(13rem, 100%), 1fr)); }
.ui-criteri-albero--colonne > * { min-width: 0; }

/* La riga intera è la label: il bersaglio è la riga, non la casella da 16. */
.ui-criteri-famiglia {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 0 3px;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-heading);
    cursor: pointer;
}
/* ⚠️ Il nome e NON il conteggio: `> span` nudo stirava anche la pastiglia del
   numero, che a 1280 misurava 45 px per scrivere «14». */
.ui-criteri-famiglia > span:not(.ui-criteri-conta) { flex: 1 1 auto; min-width: 0; }

/* Le foglie rientrano di quanto misura la casella del padre più il suo gap
   (16 + 8): così i due livelli si leggono come livelli e non come due elenchi. */
.ui-criteri-voce {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 3px 0 3px 24px;
    font-size: 11px;
    line-height: 1.35;
    color: var(--text-body);
    cursor: pointer;
}
.ui-criteri-voce:hover { color: var(--text-heading); }
.ui-criteri-voce > span { min-width: 0; }

/* Il RAMO chiuso: una famiglia che si apre a comando. Serve dove le famiglie
   sono tante e i figli ancora di più — venti regioni e 107 province, 22 sezioni
   e 87 divisioni — e mostrarli tutti aperti fa scorrere l'albero per metri
   prima di arrivare a ciò che si cerca. La testa porta la casella, il conteggio
   delle scelte dentro (visibile anche chiuso: un ramo chiuso che restringe deve
   dirlo) e un bottone vero per aprire, con `aria-expanded`. ⚠️ Il bottone e non
   un `<details>`: una casella dentro un `<summary>` apre e chiude il ramo a ogni
   spunta. I figli si nascondono con l'attributo `hidden`, e questa regola non
   dichiara `display` sul contenitore apposta — lo scavalcherebbe. */
.ui-criteri-ramo-testa {
    display: flex;
    align-items: center;
    gap: 6px;
}
.ui-criteri-ramo-testa > .ui-criteri-famiglia { flex: 1 1 auto; min-width: 0; }
/* Uno stacco sotto i figli aperti: senza, l'ultima provincia si attacca alla
   regione successiva e i due livelli si leggono come un elenco solo. */
.ui-criteri-ramo-figli { padding-bottom: 4px; }

.ui-criteri-espandi {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-body);
    font-size: 10px;
    cursor: pointer;
    transition: background-color var(--transition), color var(--transition);
}
.ui-criteri-espandi:hover {
    background-color: var(--platinum);
    color: var(--text-heading);
}
.ui-criteri-espandi:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 1px;
}
.ui-criteri-espandi > i { transition: transform 160ms var(--ease-in-out); }
.ui-criteri-espandi[aria-expanded="true"] > i { transform: rotate(180deg); }


/* ⚠️ Il gruppo il cui dato NON C'È ANCORA — non si nasconde e non si mostra
 * cliccabile e inerte: si mostra spento **col suo perché**. Nasconderlo
 * lascerebbe credere che quel criterio non esista; offrirlo lo farebbe
 * scegliere per non ricevere niente, che a schermo si legge come un guasto.
 * §n. 11: «non ancora rilevato» e «non rilevabile» non sono sinonimi.
 * ⚠️ E i campi non ci sono affatto: un `disabled` grigio su grigio è la coppia
 * che il controllo 8 del linter accusa, e sarebbe comunque un comando che
 * invita a un'azione impossibile.
 */
.ui-criteri-spento {
    padding: 10px 12px;
    margin: 8px 0;
    border-radius: var(--radius-sm);
    /* Misurato: Iron composto su Platinum, 5,12:1 — sopra la soglia di 4,5. */
    background: var(--platinum);
    color: var(--text-body);
}
.ui-criteri-spento-nome {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 4px;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-heading);
}
.ui-criteri-spento-perche {
    margin: 0;
    font-size: 11px;
    line-height: 1.45;
}

/* La copertura di un criterio che funziona SU UNA PARTE dell'anagrafica.
   Distinta dallo spento: qui il criterio si può usare, ma chi lo usa deve
   sapere che le voci senza quel dato spariscono — e non perché non siano di
   quella regione, ma perché non lo sappiamo. */
.ui-criteri-copertura {
    margin: 0;
    font-size: var(--aiuto-testo);
    line-height: 1.45;
    color: var(--aiuto-colore);
}

/* La DOMANDA di un gruppo di un configuratore — «Professione», «Materia», «Dove
   si cercano» —, sopra tessere, segmenti, pastiglie o un cursore (22/09/2026,
   Nicola: «anche le label»). Ha il rango del VALORE e non dell'etichetta di un
   campo: in un passo è ciò che si legge per primo, e a 12 px Iron si confondeva
   col sottotitolo del passo e con la nota sotto le tessere. La parte che la
   qualifica («una o entrambe», «facoltativa») sta sulla stessa riga al rango
   dell'etichetta (`ui-domanda-nota`), invece di un trattino che la fondeva nel
   nome. ⚠️ Un CAMPO di un modulo resta `ui-form-label`. */
.ui-domanda {
    display: block;
    margin: 0 0 var(--space-2);
    padding: 0;
    font-size: var(--valore-testo);
    font-weight: var(--valore-peso);
    line-height: 1.35;
    color: var(--valore-colore);
}
/* Una RIGA DI DOMANDE (`ui-domande`, 22/09/2026): piu' domande del passo
   affiancate. Le colonne le decide il CONTENUTO, non il breakpoint — come la
   griglia delle tessere, che e' `auto-fill` sulla larghezza della card.
   ⚠️ `lg:grid-cols-3` sembrava il pattern giusto e non lo era: e' la riga di
   tre WIDGET, che si stringono; un binario di risposte no. A 1024 px dava tre
   colonne da 209 px per tre binari che ne chiedono 251, e le tre risposte
   andavano a capo — tre riquadri da 76 px sopra «Stato dell'arricchimento», che
   ha SEI risposte e resta a 40 su 555. Sotto i 16 rem la riga scende a due
   colonne e poi a una, e ogni binario resta su una riga sola. */
.ui-domande {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: var(--space-4);
}

.ui-domanda-nota {
    margin-left: 6px;
    font-size: var(--etichetta-testo);
    font-weight: 400;
    color: var(--etichetta-colore);
}

/* La nota sotto un gruppo di risposte sta a 12 px dalle risposte: attaccata
   (0-4 px, Professionisti il 22/09/2026) si leggeva come la didascalia
   dell'ultima tessera invece che dell'intera domanda. Scala del passo: 8 fra la
   domanda e le sue risposte, 12 fra le risposte e la nota, 24 fra due domande. */
:is(.ui-tessere, .ui-segmentato, .ui-pastiglie) + :is(.ui-criteri-copertura, .ui-form-hint) {
    margin-top: var(--space-3);
}

/* Le TESSERE (`ui-tessere`, 22/09/2026): una domanda con poche risposte, ognuna
   una tessera da premere — titolo, una riga che dice cosa comporta, e la spunta
   quando le risposte sono più d'una. È la forma del configuratore: si vede
   subito quante sono le strade e quale si è presa, mentre in una tendina le
   risposte stanno nascoste finché non la si apre. Regola d'uso: da 2 a ~8
   risposte, e solo se la scelta è la domanda del passo; gli elenchi lunghi
   (64 problematiche, 107 province, 20 regioni) restano liste.
   ⚠️ UNA TAGLIA SOLA (22/09/2026, Nicola: «tutti uguali e versione grande»):
   stessa griglia, stessa altezza minima, titolo + UNA riga di descrizione su
   ogni tessera, «Qualsiasi» compreso. Prima c'erano tre taglie — compatte senza
   riga, grandi con due righe, e quattro colonne fisse che a 1024 px scendevano
   a 157 px — e passando da un passo all'altro lo stesso controllo cambiava
   misura. La griglia è `auto-fill` sulla larghezza della card: a parità di card
   ogni gruppo ha le stesse colonne, alla stessa larghezza.
   Sotto c'è un `<input>` VERO — radio per una risposta, checkbox per più di
   una — dentro la sua `<label>`: nome accessibile, tastiera (Tab, frecce,
   Spazio), stato letto dallo screen reader e `name`/`value` che il server già
   conosce arrivano gratis, e il JS che legge i criteri non cambia natura.
   ⚠️ L'input NON si nasconde con `display: none`: uscirebbe dall'ordine di
   tabulazione. Resta a 1 px e opacità zero; lo stato lo disegna la tessera con
   `:has()`. Nessun `aria-checked`: un input nativo lo espone da sé, e un
   attributo scritto a mano è una seconda copia dello stato che può mentire.
   Lo stato scelto è quello dei configuratori (`--scelto`, velato, col bordo
   `--scelto-filo` da 2 px), l'anello del fuoco `--focus-ring`: due colori e due
   forme. */
.ui-tessere {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
    gap: 10px;
}

/* L'altezza minima è quella di un titolo su DUE righe più la riga di
   descrizione (13 × 1,35 × 2 + 2 + 11 × 1,45 + 24 di padding + 2 di bordo ≈ 79):
   un titolo lungo che va a capo non fa crescere la sua tessera oltre le altre.
   Il contenuto sta in alto, così titoli e righe si allineano fra tessere. */
.ui-tessera {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    align-items: flex-start;
    gap: 2px;
    min-width: 0;
    min-height: 80px;
    padding: 12px 14px;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background-color: var(--white);
    cursor: pointer;
    transition: border-color var(--transition), background-color var(--transition);
}
/* Lo spazio a destra serve alla spunta, e la spunta c'è solo dove le risposte
   sono PIÙ d'una (checkbox): su una scelta singola lo stato lo dicono già bordo
   e fondo, e un cerchio in ogni tessera era un segno in più da leggere per
   dire la stessa cosa (22/09/2026). */
.ui-tessera:has(> .ui-tessera-spunta) { padding-right: 36px; }
.ui-tessera:hover { border-color: var(--silver); }
.ui-tessera-input {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    opacity: 0;
    pointer-events: none;
}
.ui-tessera-titolo {
    font-size: 13px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--text-heading);
}
/* UNA riga: la descrizione dice cosa comporta la scelta in ~30 caratteri, e il
   dettaglio sta nel sottotitolo o nel tooltip del passo. Non si tronca con
   l'ellissi — un testo tagliato non dice niente — si scrive corto: lo misura
   la sonda del configuratore a 1280 e 1024 px. */
.ui-tessera-testo {
    font-size: 11px;
    line-height: 1.45;
    color: var(--text-body);
}
/* La spunta: un cerchio per la risposta unica (radio), un quadrato per quelle
   multiple (checkbox) — la stessa distinzione dei controlli nativi, così la
   forma dice se le altre tessere si spengono. */
.ui-tessera-spunta {
    position: absolute;
    top: 11px;
    right: 11px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border: 1px solid var(--silver);
    border-radius: 50%;
    background-color: var(--white);
    font-size: 10px;
    transition: background-color var(--transition), border-color var(--transition);
}
.ui-tessera:has(.ui-tessera-input[type="checkbox"]) .ui-tessera-spunta { border-radius: 4px; }
.ui-tessera-spunta > i { display: none; }

.ui-tessera:has(.ui-tessera-input:checked),
.ui-tessera:has(.ui-tessera-input:indeterminate) {
    border-color: var(--scelto-filo);
    background-color: var(--scelto-velo);
}
/* Scelta = bordo da 2 px: il padding scende di 1 perché il contenuto non salti. */
.ui-tessera:has(.ui-tessera-input:checked) {
    border-width: 2px;
    padding: 11px 13px;
}
.ui-tessera:has(> .ui-tessera-spunta):has(.ui-tessera-input:checked) { padding-right: 35px; }
.ui-tessera:has(.ui-tessera-input:checked) .ui-tessera-spunta { top: 10px; right: 10px; }
.ui-tessera:has(.ui-tessera-input:checked) .ui-tessera-spunta,
.ui-tessera:has(.ui-tessera-input:indeterminate) .ui-tessera-spunta {
    border-color: var(--scelto-filo);
    background-color: var(--scelto);
    color: var(--scelto-testo);
}
.ui-tessera:has(.ui-tessera-input:checked) .ui-tessera-spunta > .bi-check-lg,
.ui-tessera:has(.ui-tessera-input:indeterminate) .ui-tessera-spunta > .bi-dash-lg { display: block; }
.ui-tessera:has(.ui-tessera-input:focus-visible) {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}
/* Una risposta che coi criteri scelti non può avere righe (lo stadio con un
   criterio sui dossier): resta a schermo, spenta, e il perché sta sotto il
   gruppo. Il testo resta leggibile — Iron su Platinum 5,12:1 — e il bordo
   tratteggiato dice «non disponibile» senza dipendere dal colore. */
.ui-tessera:has(.ui-tessera-input:disabled) {
    border-style: dashed;
    background-color: var(--platinum);
    cursor: not-allowed;
}
.ui-tessera:has(.ui-tessera-input:disabled) .ui-tessera-titolo { color: var(--text-body); }

/* Il SEGMENTATO (`ui-segmentato`, 22/09/2026): una domanda con risposte CORTE
   («Presente», «3», «Acquistate») in una riga sola, su un binario. Dove le
   tessere sono troppe per la domanda — sette finestre, sette tessere col loro
   cerchio — o dove in un passo le domande sono più d'una. Sotto c'è un
   `<input type="radio">` VERO per risposta, come nelle tessere: tastiera
   (frecce), lettore di schermo e `name`/`value` arrivano dal browser.
   La risposta scelta è un tassello velato `--scelto-velo` col filo
   `--scelto-filo`, le altre stanno sul binario `--platinum` in Iron. Il peso non cambia fra scelto e no, o le etichette
   cambierebbero larghezza e il binario tremerebbe a ogni clic.
   ⚠️ Va a capo quando non ci sta (`flex-wrap`): sei stati dell'arricchimento su
   343 px sono due righe, non una riga che sfonda la card.
   La riga che spiega la risposta scelta (`[data-segmentato-nota]`, puntata da
   `aria-describedby`) la aggiorna `BiUI.initSegmentati`.
   Taglia GRANDE come le tessere (22/09/2026): binario da 40 px (34 + 3 + 3),
   l'altezza dei comandi del piede (`ui-btn-lg`), e testo a 13 come il titolo
   di una tessera. Tutti i segmenti hanno quella, su ogni passo. */
.ui-segmentato {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 2px;
    max-width: 100%;
    padding: 3px;
    border-radius: var(--radius-md);
    background-color: var(--platinum);
}
.ui-segmento {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 40px;
    min-height: 34px;
    padding: 0 14px;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.2;
    color: var(--text-body);
    cursor: pointer;
    transition: background-color var(--transition), border-color var(--transition), color var(--transition);
}
.ui-segmento:hover { color: var(--text-heading); }
.ui-segmento-input {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    opacity: 0;
    pointer-events: none;
}
.ui-segmento:has(.ui-segmento-input:checked) {
    border-color: var(--scelto-filo);
    /* Il velo su bianco, non sul binario: su `--platinum` si sporcherebbe. */
    background: linear-gradient(var(--scelto-velo), var(--scelto-velo)), var(--white);
    color: var(--text-heading);
}
.ui-segmento:has(.ui-segmento-input:focus-visible) {
    outline: 2px solid var(--focus-ring);
    outline-offset: 1px;
}

/* Le PASTIGLIE (`ui-pastiglie`, 22/09/2026, il passo Dettaglio di Nuova
   campagna): una scelta MULTIPLA fra voci dal testo lungo, una pastiglia per
   voce su una riga sola — il testo va a capo dentro la pastiglia solo se non ci
   sta. Sostituiscono tre colonne di caselle in un riquadro che scorreva dentro
   di sé e tagliava le intestazioni di famiglia (Nicola: «rendere le checkbox
   più pulite e leggibili»). Sotto c'è una `<input type="checkbox">` VERA, come
   nelle tessere e nei segmenti: tastiera, lettore di schermo e `name`/`value`
   arrivano dal browser.
   Lo stato scelto è PIENO, `--scelto` col testo `--scelto-testo`, il filo
   `--scelto-filo` e la spunta: fra sessanta voci quelle scelte si contano a colpo d'occhio, e una
   velatura al 10% su voci lunghe si leggeva come un evidenziatore. Il peso non
   cambia fra scelto e no, o la riga si ricomporrebbe a ogni clic. Spenta (un
   tetto) è tratteggiata, come la tessera spenta. */
.ui-pastiglie {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    min-width: 0;
}
.ui-pastiglia {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    max-width: 100%;
    min-height: 28px;
    padding: 4px 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background-color: var(--white);
    font-size: 12px;
    line-height: 1.3;
    color: var(--text-body);
    cursor: pointer;
    transition: background-color var(--transition), border-color var(--transition), color var(--transition);
}
.ui-pastiglia > span { min-width: 0; }
.ui-pastiglia:hover { border-color: var(--scelto-filo); color: var(--text-heading); }
.ui-pastiglia-input {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    opacity: 0;
    pointer-events: none;
}
.ui-pastiglia-spunta { display: none; font-size: 12px; }
.ui-pastiglia:has(.ui-pastiglia-input:checked) {
    border-color: var(--scelto-filo);
    background-color: var(--scelto);
    color: var(--scelto-testo);
}
.ui-pastiglia:has(.ui-pastiglia-input:checked) .ui-pastiglia-spunta { display: inline; }
.ui-pastiglia:has(.ui-pastiglia-input:focus-visible) {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}
.ui-pastiglia:has(.ui-pastiglia-input:disabled) {
    border-style: dashed;
    background-color: var(--platinum);
    color: var(--text-body);
    cursor: not-allowed;
}

/* Il CURSORE A GRADINI (`ui-slider`, 22/09/2026): un valore numerico si
   sceglie, non si scrive. Un `input type=range` VERO per estremo — due sullo
   stesso binario per un intervallo — che si muove su un indice di gradini NON
   lineari (`App\Spark\Gradini`); il valore in chiaro sta sopra a destra, e agli
   estremi dice «Qualsiasi»: lì il parametro non parte. Il comportamento sta in
   `BiUI.initCursori` (helpers.js).
   Il binario è `--alabaster`; il tratto scelto è `--scelto-filo`, e il
   pomello è `--scelto` col filo `--scelto-filo`.
   ⚠️ Il tratto pieno si calcola sul CENTRO dei pomelli, non sul bordo del
   binario: un pomello da 20 px al 50% sta a `10px + (100% - 20px) * 0,5`, e un
   tratto calcolato sulla larghezza piena si staccherebbe dal pomello di 10 px
   agli estremi. Le due frazioni le scrive il JS su `--ui-slider-da`/`-a`.
   ⚠️ Due cursori sovrapposti: il binario degli input non riceve clic
   (`pointer-events: none`), i pomelli sì — o l'input di sopra coprirebbe
   quello di sotto e il minimo non si potrebbe più afferrare. */
.ui-slider {
    display: grid;
    gap: 4px;
    min-width: 0;
}
.ui-slider-testa {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
}
.ui-slider-testa > .ui-domanda { margin-bottom: 0; }
.ui-slider-valore {
    flex: none;
    font-size: 13px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--text-body);
}
.ui-slider.is-scelto .ui-slider-valore { color: var(--text-heading); }
.ui-slider-binario {
    position: relative;
    height: 28px;
}
.ui-slider-binario::before,
.ui-slider-binario::after {
    content: '';
    position: absolute;
    top: 50%;
    height: 4px;
    margin-top: -2px;
    border-radius: 2px;
}
.ui-slider-binario::before {
    left: 10px;
    right: 10px;
    background-color: var(--alabaster);
}
.ui-slider-binario::after {
    left: calc(10px + (100% - 20px) * var(--ui-slider-da, 0));
    right: calc(10px + (100% - 20px) * (1 - var(--ui-slider-a, 1)));
    background-color: var(--scelto-filo);
}
/* A «qualsiasi» il tratto non si colora: pieno da un capo all'altro diceva
   «scelto tutto», che a schermo si legge come un criterio attivo. */
.ui-slider:not(.is-scelto) .ui-slider-binario::after { background-color: transparent; }
.ui-slider-input {
    position: absolute;
    inset: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    margin: 0;
    background: transparent;
    -webkit-appearance: none;
    appearance: none;
    pointer-events: none;
}
.ui-slider-input:focus { outline: none; }
.ui-slider-input.is-sopra { z-index: 2; }
.ui-slider-input::-webkit-slider-runnable-track { height: 28px; background: transparent; }
.ui-slider-input::-moz-range-track { height: 28px; background: transparent; }
.ui-slider-input::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 20px;
    height: 20px;
    margin-top: 4px;
    border: 2px solid var(--scelto-filo);
    border-radius: 50%;
    background-color: var(--scelto);
    cursor: grab;
    pointer-events: auto;
    transition: border-width var(--transition);
}
.ui-slider-input::-moz-range-thumb {
    width: 16px;
    height: 16px;
    border: 2px solid var(--scelto-filo);
    border-radius: 50%;
    background-color: var(--scelto);
    cursor: grab;
    pointer-events: auto;
}
.ui-slider-input:active::-webkit-slider-thumb { cursor: grabbing; border-width: 6px; }
.ui-slider-input:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.ui-slider-input:focus-visible::-moz-range-thumb { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* I NUMERI VIVI di un configuratore (`ui-perimetro`, 22/09/2026): una FASCIA
   sotto la testata, in una card sua (`ui-fascia-numeri`), su una riga sola — il
   principale grande, poi i secondari in linea, separati da un punto. Dal
   22/09/2026 (sera, «Criteri dentro lo stepper») i numeri non stanno più nella
   card dello stepper: lì il riepilogo e lo stepper dicevano due volte gli stessi
   criteri, e la card si leggeva «confusa tra gli step e la parte sotto».
   Il principale è 36 px mono: il primo punto guardato, sotto i 44 dei KPI di
   cruscotto, che qui non sono la pagina. Il testo accanto dice l'unità («voci
   nel perimetro»), a 13 px Iron (5,65:1 sul bianco).
   Quando un numero cambia (`BiUI.segnalaCambio`) prende per 300 ms una velatura
   `--azure-light` che sfuma a niente, `--ease-out`: un segno discreto che dice
   «mi sono mosso» a chi sta scegliendo più in basso. Con movimento ridotto la
   regola globale la porta a 0,01 ms, e resta il testo nuovo. */
.ui-fascia-numeri { container-type: inline-size; padding-top: 14px; padding-bottom: 14px; }
.ui-perimetro {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: 28px;
    row-gap: 6px;
    min-width: 0;
}
.ui-perimetro-numero {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: 8px;
    min-width: 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-body);
}
.ui-perimetro-numero > .ui-perimetro-valore {
    font-family: var(--font-mono);
    font-size: 36px;
    font-weight: 500;
    line-height: 1.05;
    letter-spacing: -0.02em;
    color: var(--text-heading);
}
/* I secondari: «433 con telefono · 461 con email · 483 con figure chiave». Il
   punto è del contenitore largo; sotto i 480 px di card vanno a capo in blocchi
   interi, e un punto in testa a una riga si leggerebbe come un elenco. */
.ui-perimetro-secondari {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: 16px;
    row-gap: 2px;
    min-width: 0;
    font-size: 13px;
    color: var(--text-body);
}
.ui-perimetro-secondario { white-space: nowrap; }
.ui-perimetro-secondario > .ui-perimetro-valore {
    margin-right: 0;
    font-family: var(--font-mono);
    font-weight: 600;
    letter-spacing: -0.02em;
    color: var(--text-heading);
}
@container (min-width: 480px) {
    .ui-perimetro-secondari { column-gap: 0; }
    .ui-perimetro-secondario + .ui-perimetro-secondario::before {
        content: '·';
        margin: 0 10px;
        color: var(--text-light);
    }
}
.ui-perimetro-valore {
    margin: 0 -4px;
    padding: 0 4px;
    border-radius: var(--radius-sm);
}
.ui-perimetro-valore.is-cambiato { animation: ui-perimetro-cambio 300ms var(--ease-out); }
@keyframes ui-perimetro-cambio {
    from { background-color: var(--azure-light); }
    to   { background-color: rgba(67, 132, 247, 0); }
}

/* La TESTATA di un configuratore (`ui-testata`, 22/09/2026 sera — Nicola: «la
   testata è sempre confusa tra gli step e la parte sotto»; scelta fra tre
   proposte: «Criteri dentro lo stepper»). Il riepilogo separato NON c'è più:
   sotto il nome di ogni passo che ha un criterio sta il valore scelto, breve
   (`ui-step-valore`), e il passo intero resta il bottone che ci riporta. Un dato,
   un posto. In alto a destra, sulla riga dei cerchi, il comando che azzera
   (`ui-testata-azzera`), solo quando c'è qualcosa da azzerare.
   Decide sulla larghezza della CARD (container query), non della finestra:
   sotto i 640 px di card (il telefono; a 1024 la card ne misura 692) lo stepper
   tiene l'etichetta del solo passo corrente, e sotto va una riga coi comandi —
   «N criteri», che apre l'elenco completo, e «Azzera». */
.ui-testata { container-type: inline-size; }
.ui-testata-passi {
    display: flex;
    align-items: flex-start;
    gap: 12px 16px;
}
.ui-testata-passi > nav { flex: 1 1 auto; min-width: 0; }
.ui-testata-comandi {
    display: flex;
    flex: none;
    align-items: center;
    gap: 8px;
    /* Sul telefono sta nella riga sotto lo stepper; da 640 px di card è centrato
       sullo stepper dalla regola `@container` più sotto. */
    margin-top: 2px;
}
.ui-testata-comandi[hidden] { display: none; }
@container (max-width: 639px) {
    .ui-testata-passi { flex-wrap: wrap; }
    .ui-testata-comandi { flex: 1 1 100%; justify-content: space-between; margin-top: 0; }
}
/* Da 640 px di card i passi partono dal BORDO SINISTRO (22/09/2026, Nicola: «gli
   step tienili allineati a sinistra del widget», «tieni la larghezza attuale»).
   Ogni passo conserva la sua porzione della riga — la larghezza dello stepper
   non cambia — ma cerchio, nome e valore stanno a sinistra della porzione
   invece che al centro: il primo cerchio cadeva 83 px dentro la card a 1280, e
   la testata si leggeva sfalsata rispetto al contenuto sotto. Il filo va dal
   cerchio al cerchio del passo dopo, staccato di 4 px. A destra il comando,
   CENTRATO in verticale sullo stepper intero (cerchio, nome e valore), non più
   sulla riga dei cerchi. Sotto i 640 resta la forma del telefono. */
@container (min-width: 640px) {
    .ui-testata-passi { align-items: center; }
    .ui-testata-comandi { margin-top: 0; }
    .ui-testata .ui-step { align-items: flex-start; }
    .ui-testata .ui-step-label,
    .ui-testata .ui-step-valore { text-align: left; }
    .ui-testata .ui-stepper--leggero .ui-step:not(:last-child)::after {
        left: 36px;
        right: 4px;
    }
}

/* Il VALORE sotto il passo: 11 px, peso 600, jet — è ciò che si è scelto, e deve
   saltare fuori dal nome del passo (Stone, peso 500). Una riga sola, troncata
   con l'ellissi alla larghezza del passo: il testo intero sta nell'`aria-label`
   e nel `title` del bottone, perché è ciò che si esporta. Vuoto non c'è. */
.ui-stepper .ui-step { min-width: 0; }
.ui-step-valore {
    display: block;
    max-width: 100%;
    margin-top: 2px;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--text-heading);
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ui-step-valore:empty { display: none; }
/* Sul telefono il passo corrente misura una trentina di pixel: etichetta e
   valore si allargano al loro testo (18 caratteri al più, `BiUI.valorePasso`),
   e `BiUI.contieniPasso()` li sposta dentro il bordo dello stepper quando il
   passo sta a un'estremità — centrati sul primo passo uscirebbero dalla card. */
.ui-step-label,
.ui-step-valore { transform: translateX(var(--ui-step-sposta, 0px)); }
@media (max-width: 640px) {
    .ui-stepper:not(.ui-stepper-vertical) .ui-step.active .ui-step-label,
    .ui-stepper:not(.ui-stepper-vertical) .ui-step.active .ui-step-valore {
        width: max-content;
        max-width: none;
        white-space: nowrap;
    }
    .ui-stepper:not(.ui-stepper-vertical) .ui-step:not(.active) .ui-step-valore {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
    }
}

/* Il comando che azzera («Azzera», «Ricomincia»): TESTUALE con un filo
   `--accent-dark`, non un bottone pieno — azzerare è un'azione secondaria. Filo
   e testo `--accent-dark` sul bianco della card: 5,95:1, sopra il 3:1 di un
   componente non testuale e il 4,5 del testo (il filo di `ui-btn-outline` qui
   misura 1,23 e il bottone spariva: lo controlla il linter,
   `testata_senza_superficie`). Alto 28. */
.ui-testata-azzera {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 28px;
    padding: 0 10px;
    border: 1px solid var(--accent-dark);
    border-radius: var(--radius-sm);
    background-color: var(--white);
    color: var(--accent-dark);
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color var(--transition), color var(--transition);
}
.ui-testata-azzera[hidden] { display: none; }
.ui-testata-azzera:hover:not(:disabled) {
    background-color: var(--accent-dark);
    color: var(--white);
}
.ui-testata-azzera:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}
.ui-testata-azzera:disabled {
    border-style: dashed;
    color: var(--text-body);
    cursor: default;
}

/* Sul telefono i valori dei passi non correnti sono ritagliati: «N criteri»
   apre l'elenco completo, un passo per riga col valore intero, e ogni riga
   riporta al suo passo. Sopra i 640 px di card non c'è — i valori stanno sotto i
   passi — e l'elenco eventualmente aperto si richiude alla vista. */
.ui-testata-criteri,
.ui-testata-elenco { display: none; }
@container (max-width: 639px) {
    .ui-testata-criteri:not([hidden]) {
        display: inline-flex;
        align-items: center;
        gap: 6px;
        height: 28px;
        padding: 0 4px;
        border: 0;
        background: transparent;
        color: var(--accent-dark);
        font-size: 12px;
        font-weight: 600;
        cursor: pointer;
    }
    .ui-testata-elenco:not([hidden]) {
        display: flex;
        flex-direction: column;
        gap: 2px;
        margin-top: var(--space-3);
    }
}
.ui-testata-criteri > .bi { transition: transform 150ms var(--ease-in-out); }
.ui-testata-criteri[aria-expanded="true"] > .bi { transform: rotate(180deg); }
.ui-testata-criteri:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}
.ui-testata-criterio {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 6px 8px;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.ui-testata-criterio:hover { background-color: var(--bg-subtle); }
.ui-testata-criterio:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 1px;
}
.ui-testata-criterio-nome,
.ui-testata-altri-nome {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-muted);
}
.ui-testata-criterio-valore {
    font-size: 12px;
    font-weight: 600;
    color: var(--text-heading);
    overflow-wrap: anywhere;
}

/* Gli ALTRI FILTRI (provincia, estrazione col grado, ricerca): arrivano da un
   link e non hanno un passo, quindi non possono stare sotto un passo. Una riga
   di pastiglie sotto lo stepper, SOLO quando ce ne sono: un filtro applicato che
   non si vede fa uscire un file più stretto di ciò che si vede scelto. Ogni
   pastiglia ha la sua X da 20 px (`ui-scelta-togli`); il filo `--border` la
   contorna sul bianco della card. */
.ui-testata-altri {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 8px;
    margin-top: var(--space-4);
}
.ui-testata-altri[hidden] { display: none; }
.ui-testata-chip {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    max-width: 100%;
    padding: 0 1px 0 6px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--white);
    color: var(--text-heading);
    font-family: var(--font-mono);
    font-size: 11px;
    overflow-wrap: anywhere;
}
.ui-testata-chip .ui-scelta-togli:hover { background-color: var(--platinum); }

/* Il SOTTOTITOLO di un passo: una riga che dice cosa si sceglie, sotto il titolo. */
.ui-passo-sottotitolo {
    margin: 4px 0 0;
    font-size: 12px;
    line-height: 1.45;
    color: var(--text-body);
}

/* Le SCELTE di un albero lungo, sopra l'albero: quando le voci sono migliaia e
   la ricerca ne mostra una manciata alla volta, ciò che si è scelto esce dallo
   schermo alla ricerca successiva. Qui resta visibile, si toglie con la X, e
   sotto ogni scelta una nota dice cosa ne ha fatto il server («12 codici
   interrogati»). Colonna e non riga: una descrizione ATECO arriva a 120
   caratteri, e in fila a capo le pastiglie si leggerebbero come un paragrafo. */
.ui-scelte {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.ui-scelte:empty { display: none; }

.ui-scelta {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: 6px;
    align-items: start;
    padding: 5px 4px 5px 10px;
    border-radius: var(--radius-sm);
    /* Iron su Platinum: la stessa coppia di `.ui-criteri-spento`. */
    background: var(--platinum);
    color: var(--text-body);
}
.ui-scelta-testo {
    min-width: 0;
    font-size: 11px;
    line-height: 1.35;
    overflow-wrap: anywhere;
}
.ui-scelta-testo strong {
    font-family: var(--font-mono);
    font-weight: 600;
    color: var(--text-heading);
}
.ui-scelta-nota {
    grid-column: 1;
    margin-top: 2px;
    font-size: 10px;
    line-height: 1.35;
}
/* La X ha un'area sua da 20 px: un glifo nudo accanto al testo si prende per
   decorazione, e a quella taglia il dito lo manca. */
.ui-scelta-togli {
    grid-column: 2;
    grid-row: 1 / span 2;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-body);
    font-size: 11px;
    cursor: pointer;
    transition: background-color var(--transition), color var(--transition);
}
.ui-scelta-togli:hover {
    background-color: var(--white);
    color: var(--text-heading);
}
.ui-scelta-togli:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 1px;
}

/* Il PIEDE di un configuratore a passi: «Indietro» a sinistra, «Avanti» a
   destra, e fra i due il perché quando «Avanti» è spento — un comando spento
   senza la sua ragione si legge come un guasto. ⚠️ Niente filo sopra, dal
   22/09/2026: lo stacca dal passo lo spazio (36 px, il ritmo fra blocchi), e i
   due comandi `lg` bastano a dire che la riga è un'altra cosa. */
.ui-passi-piede {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    margin-top: var(--space-6);
}
.ui-passi-piede-perche {
    flex: 1 1 16rem;
    min-width: 0;
    margin: 0;
    font-size: 12px;
    color: var(--text-body);
    text-align: right;
}

/* La CARD DEL PASSO di un configuratore (`ui-passi` su `#pannelloCriteri` e sui
   due `#passoCard`). Il titolo del passo non è la maiuscola micro di ogni widget
   (11 px, la stessa voce di «Nel perimetro»): è la testata di ciò che si sta
   facendo, 15 px in tondo, e sotto una riga che dice cosa si sceglie
   (`ui-passo-sottotitolo`). ⚠️ Il resto della card ha la taglia di casa: il
   22/09/2026 titolo a 18, voci a 13, righe a 32 e campi a 40 sono stati giudicati
   «troppo grandi» (Nicola) — il configuratore si legge per le scelte in
   tessere (`ui-tessere`), non per un corpo più grande. */
.ui-passi > section > .widget-header {
    align-items: flex-start;
    margin-bottom: var(--space-4);
}
.ui-passi > section > .widget-header .widget-title {
    gap: 8px;
    font-size: 15px;
    font-weight: 600;
    line-height: 1.35;
    text-transform: none;
    letter-spacing: 0;
    color: var(--text-heading);
}
.ui-passi > section > .widget-header .widget-title i { font-size: 15px; }

@media (max-width: 640px) {
    /* Un comando `lg` col testo lungo («Esporta i recapiti (una riga per
       persona)») sul telefono non entra in 343 px: col `nowrap` di `.ui-btn`
       sfondava la card. Qui va a capo e cresce in altezza. */
    .ui-btn-lg,
    .ui-form-azioni .ui-btn,
    .ui-modal-footer .ui-btn,
    .ui-confirm-actions .ui-btn { height: auto; min-height: var(--campo-altezza); padding-top: 8px; padding-bottom: 8px; white-space: normal; text-align: center; }
}
