# Specifiche grafiche — stile UI condiviso Documento di riferimento per **uniformare la grafica delle app Integry** allo stile adottato in Fixiy. Descrive *forma, ritmo, tipografia e composizione dei componenti*. **Non contiene una palette**: ogni app mantiene i propri colori e li mappa sui **ruoli semantici** elencati al §2. Lo stile è quello che rimane costante al variare della tinta: superfici piatte, raggi ampi, spaziature a scala fissa, gerarchia data dallo spazio prima che dalla decorazione. Riferimento d'implementazione: Flutter + Material 3 (`useMaterial3: true`). I nomi dei token qui sotto sono quelli reali del codice Fixiy (`lib/core/design_system.dart`, `lib/core/feedback.dart`); in un'altra app si rinominano col prefisso dell'app, ma **valori e semantica restano invariati**. --- ## 1. Principi 1. **Superfici piatte.** Niente ombre per costruire gerarchia: `elevation: 0` ovunque (card, AppBar, NavigationBar, NavigationRail). L'unica ombra ammessa è quella del toast (elevation 6) e dei livelli modali di sistema. La separazione si ottiene con *contrasto di superficie* (sfondo pagina più freddo delle superfici) e con lo **spazio**. 2. **Nessun tint automatico.** `surfaceTintColor: Colors.transparent` su AppBar, Card, Dialog, pannelli: le superfici non devono virare verso il colore primario quando si scrolla. 3. **Colore = significato.** L'accento primario serve solo ad azioni, selezione, focus e progresso — mai a decorare. I colori semantici (urgenza, stato, esito) compaiono sempre accompagnati da **icona + etichetta testuale**, mai da soli. 4. **Target generosi.** Ogni elemento interattivo ha lato minimo **44 px** (`minimumSize: Size(44, 44)`), 48 px d'altezza per i bottoni principali. L'app si usa in piedi, con i guanti, in negozio. 5. **Densità standard.** `visualDensity: VisualDensity.standard`: si rifiuta la densità desktop, anche su schermo largo. 6. **Lo spazio prima della linea.** I divisori si usano solo dove l'informazione cambia natura (testata di uno sheet, righe di una lista di navigazione); altrove separa la spaziatura. 7. **Nulla di stock.** Si evitano le firme visive Android di default: card con ombra, FAB circolare al centro, AppBar colorata con titolo centrato, snackbar di sistema. 8. **Scrivibile a voce alta.** Testi in lingua dell'app, brevi, orientati all'azione; gli enum passano da una mappa di etichette centralizzata (`labels.dart`), mai formattati inline. --- ## 2. Ruoli colore (da riempire con la palette dell'app) L'app ospite definisce i **propri** valori per questi ruoli e li usa con la stessa semantica. Nessun valore è fissato qui. | Ruolo | Uso | |---|---| | `primary` | accento unico di azione: bottone pieno, focus, selezione, indicatori di progresso, icone attive | | `primaryDark` | testo/icona sopra `primaryContainer`, valori numerici in evidenza | | `workspace` | sfondo dell'applicazione (`scaffoldBackgroundColor`), leggermente più freddo/scuro di `surface` | | `surface` | superficie di lavoro: card, pannelli, AppBar, barra di navigazione, dialog | | `surfaceMuted` | superficie secondaria dentro una superficie (campo di ricerca, chip non selezionato, riquadro informativo interno) | | `divider` | bordi 1 px, separatori, contorno dei controlli a riposo | | `ink` | testo principale | | `inkMuted` | testo secondario, hint, etichette di metadato, icone non attive | | `emergency` / `errorSurface` | tono **danger**: errori, azioni distruttive, priorità massima | | `high` / `warningSurface` | tono **warning**: attenzione, dati incompleti, priorità alta | | `success` / `successSurface` | tono **success**: conferme, completamenti | | `info` / `infoSurface` | tono **info**: stato informativo, pannello di riepilogo, indicatore di selezione | | `neutral` / `surfaceMuted` | tono **neutral**: stati senza carica semantica | **Regola delle coppie.** Ogni tono semantico è una coppia *(foreground forte, superficie tenue)*. Le superfici tenui riempiono badge, pannelli di feedback e cerchi-icona dei dialog; il foreground forte colora icona e testo sopra di esse. I **toast** invertono la regola: riempimento pieno col colore forte e testo bianco, per essere leggibili sopra qualunque contenuto. **Schema colore.** Si genera da un seme (`ColorScheme.fromSeed`) e poi si sovrascrivono esplicitamente `primary`, `onPrimary`, `primaryContainer` (= `infoSurface`), `onPrimaryContainer` (= `primaryDark`), `secondaryContainer` (= `surfaceMuted`), `outline` e `outlineVariant` (= `divider`). Non si lascia al seme la scelta dei contenitori: devono restare tenui e neutri. --- ## 3. Scale di token ### Spaziatura (`Space`) ``` xs = 8 sm = 12 md = 16 lg = 24 xl = 32 ``` Tutti i margini e le distanze sono multipli di 2 e, per quanto possibile, uno di questi cinque valori. Valori d'uso frequente derivati: 4 e 6 per le micro-distanze interne (titolo→sottotitolo, icona→testo), 10 tra le card di una lista, 18/20/22 per il padding dei pannelli di testata. ### Raggi (`Radius`) ``` compact = 8 riquadri informativi, barre di progresso, feedback inline control = 16 campi di testo, bottoni, toast surface = 20 card, pannelli, dialog, sheet pill = 999 badge, chip, segmented button, campo di ricerca, bottoni onboarding ``` Il contrasto tra i raggi è voluto: **superfici più arrotondate dei controlli**, elementi di stato completamente stondati (pill). ### Breakpoint ``` rail = 720 da qui la navigazione passa da barra inferiore a NavigationRail laterale twoColumn = 900 soglia per layout a due colonne contentMax = 1180 larghezza massima assoluta del contenuto ``` Larghezza tipica di una pagina: `maxWidth: 920`, centrata in alto (`PageWidth` = `Align(topCenter) + ConstrainedBox`). Su schermi larghi il contenuto **non si allarga all'infinito**: resta una colonna leggibile. --- ## 4. Tipografia Famiglia principale **Inter**, fallback `['SF Pro Display', 'Roboto', 'sans-serif']`. Scala fissa, pensata per la scansione rapida, non per l'effetto editoriale: | Stile | Dim. | Interlinea | Peso | Uso | |---|---|---|---|---| | `headlineMedium` | 34 | 1.2 | 700 | titoli di apertura | | `headlineSmall` | 28 | 1.25 | 700 | titolo dell'AppBar, valore percentuale in evidenza | | `titleLarge` | 22 | 1.3 | 700 | titolo di card e di sheet, testata di sezione | | `titleMedium` | 18 | 1.35 | 700 | sotto-titoli, valori metrica, titolo dialog | | `bodyLarge` | 16 | 1.5 | 400 | testo corrente, descrizioni | | `bodyMedium` | 15 | 1.45 | 400 | metadati, righe secondarie | | `labelLarge` | 14 | 1.25 | 600 | etichette dei bottoni | | `labelMedium` | 13 | 1.25 | 600 | etichette di navigazione, caption | Regole d'uso: - Il **titolo di una card è l'entità**, non il tipo di record (es. il negozio, non "Manutenzione"): `titleLarge` con peso 800 e interlinea compressa 1.15. - Gli **identificativi tecnici** (numero ticket, codice) vanno sopra il titolo, 12 px, peso 700, colore `inkMuted`, `letterSpacing: 0.3`. - Le **righe di metadato** si compongono con separatore ` · ` (spazio–punto medio–spazio) e colore `inkMuted`. - Il testo secondario non usa mai un peso inferiore a 400 né opacità: si distingue per **colore** (`inkMuted`), non per trasparenza. - Nessun maiuscoletto forzato, nessun `letterSpacing` ampio: le etichette restano in caso naturale. --- ## 5. Tema — impostazioni globali | Elemento | Configurazione | |---|---| | **Scaffold** | sfondo `workspace`; `extendBody: true` quando la barra di navigazione è flottante | | **AppBar** | `toolbarHeight: 80`, titolo a sinistra (`centerTitle: false`), `titleSpacing` 20 (28 su schermo largo), sfondo `workspace` (si fonde con la pagina), elevation 0, `scrolledUnderElevation: 0` | | **Card / pannelli** | elevation 0, `margin: EdgeInsets.zero`, sfondo `surface`, raggio `surface`, senza bordo | | **Campi di testo** | `filled: true` su `surface`, padding 16×16, raggio `control`; bordo a riposo 1 px `divider`, in focus 2 px `primary`, in errore 1 px `emergency`; hint e helper in `inkMuted` | | **Bottone pieno** | `minimumSize: (44, 48)`, sfondo `primary`, testo bianco `labelLarge`, raggio `control` | | **Bottone contornato** | `minimumSize: (44, 48)`, testo `ink`, bordo `divider`, raggio `control` | | **Bottone testuale / icona** | `minimumSize: (44, 44)` | | **Segmented button** | `StadiumBorder`, min `(52, 44)`; selezionato = riempimento `primary` + testo bianco; non selezionato = `surface` + testo `inkMuted`; bordo `divider` | | **NavigationBar** | altezza 68, elevation 0, sfondo `surface`, indicatore `infoSurface`, etichette sempre visibili (`alwaysShow`), stile `labelMedium` | | **NavigationRail** | sfondo `surface`, indicatore `primaryContainer`, etichette su tutte le voci (`labelType: all`), etichetta selezionata in `primary` | | **Snackbar** | comportamento `floating` (ma nell'app si usa il toast custom, §6.9) | | **Progress** | colore `primary` | **Localizzazione.** Locale unico dichiarato esplicitamente con i tre delegati globali (Material, Widgets, Cupertino), così date, picker e messaggi di sistema parlano la lingua dell'app. --- ## 6. Componenti ### 6.1 `PageWidth` — contenitore di pagina `Align(topCenter)` + `ConstrainedBox(maxWidth: 920)`. Avvolge **ogni** contenuto di pagina. Su telefono è trasparente; su tablet/desktop impedisce righe di testo lunghissime e mantiene la stessa composizione a tutte le larghezze. ### 6.2 `Panel` — superficie base `Material` con `color: surface`, `surfaceTintColor: transparent`, `clipBehavior: antiAlias`, forma arrotondata `surface` (20), padding di default 16. È il mattone di tutto: card di lista, riquadri di riepilogo, righe raggruppate, campi compositi. Varianti d'uso: - **con colore**: `color: infoSurface` per i pannelli di riepilogo/testata; `color: surfaceMuted` per i sotto-riquadri dentro un form; `color: errorSurface` per una card in stato critico. - **`padding: EdgeInsets.zero` + `InkWell` interno** quando l'intera superficie è toccabile (raggio dell'ink coerente con quello del pannello). ### 6.3 `SectionHeader` — testata di sezione Riga con, a sinistra, `titleLarge` + eventuale descrizione (`inkMuted`, 4 px sotto), e a destra un widget opzionale (bottone, badge, contatore) separato da 12 px. Apre ogni blocco tematico dentro una pagina lunga. ### 6.4 `Badge` — stato in pillola `DecoratedBox` con raggio 999, superficie tenue del tono, padding 9×5, contenuto = icona 15 px + 5 px + testo 12 px peso 700, entrambi nel foreground del tono. Avvolto in `Semantics(label:)`. I badge **si costruiscono da funzioni di mappatura** (`priorityBadge(priority)`, `statusBadge(status)`), non a mano nelle pagine: così ogni schermata mostra lo stesso stato con la stessa icona e lo stesso tono. Ogni nuovo stato si aggiunge a quella funzione. ### 6.5 Card di elenco Composizione canonica, dall'alto: 1. **Barra di priorità** verticale a tutta altezza sul bordo sinistro, larghezza **6 px**, colore dal tono di urgenza (`Stack` + `Positioned(top/bottom/left: 0)`). La card usa padding asimmetrico `(22, 16, 16, 16)` per far respirare la barra. 2. **Identificativo** (12 px, `inkMuted`, 700) con, a destra, l'eventuale maniglia di trascinamento. 3. **Titolo** = l'entità, `titleLarge` peso 800. 4. **Riga meta** con separatori ` · `, `bodyMedium` peso 600 `inkMuted`. 5. **Badge** (priorità, stato) in un `Wrap(spacing: 8, runSpacing: 8)`. 6. **Descrizione** integrale, `bodyLarge` interlinea 1.35 — niente troncamenti sul contenuto che serve a decidere. 7. **Riga oraria/assegnatario** preceduta da icona 16 px `inkMuted`. 8. **Barra azioni interna**: secondaria contornata + primaria piena, affiancate con `Expanded`; sotto i **350 px** di larghezza della card diventano una colonna a piena larghezza (`LayoutBuilder`). La card intera espone un `Semantics(label:)` che riassume entità, priorità e stato. ### 6.6 Pannello di riepilogo Primo elemento della pagina, `Panel` con superficie tenue (`primaryContainer`/`infoSurface`), padding 18: - riga di testata: titolo `titleLarge` + data/sottotitolo `inkMuted`, e a destra un **badge di stato** del documento ("Da salvare" / "Salvato" / "Concluso"); - eventuale fila di **chip** di scelta (`Wrap(spacing: 8, runSpacing: 8)`); - fila di **metriche** in `Expanded` uguali, ciascuna: icona 20 px in `primaryDark` → valore `titleMedium` → etichetta 12 px `inkMuted` su una riga sola con ellissi; - eventuale riga di stato in coda (`labelMedium` `inkMuted`), es. l'ora dell'ultimo salvataggio. Quando c'è un progresso, si aggiunge una `LinearProgressIndicator` con `minHeight: 8` e `borderRadius: 8`, preceduta dal conteggio in chiaro ("3 di 7 completate") e dalla percentuale in `headlineSmall`. ### 6.7 Chip e filtri - **Filtri**: `ChoiceChip` `StadiumBorder`, `showCheckmark: false`, **l'etichetta include il conteggio** (`Tutte · 12`). A riposo: superficie `surfaceMuted`, bordo `divider`, testo `inkMuted` peso 600. Selezionato: superficie `infoSurface`, bordo `primary`, testo `primaryDark`. Disposti in una riga orizzontale scorrevole alta **44 px**, spaziatura 8. - **Chip-entità** (selezione di un oggetto): `Material` + `InkWell` con `StadiumBorder`, padding 12×8, icona 16 px + etichetta peso 600 + dato secondario 12 px `inkMuted`. Selezionato = superficie `surface` e bordo `primaryDark`; non selezionato = `surfaceMuted` e bordo `divider`. ### 6.8 Campo di ricerca Non usa il tema dei campi: è **completamente stondato** (raggio 999), riempito in `surfaceMuted`, **senza bordo** a riposo e in enabled, bordo 1.5 px `primary` solo in focus. Padding 18×14, icona di ricerca `inkMuted` come prefisso, pulsante "cancella" come suffisso solo quando c'è testo. ### 6.9 Toast Sostituisce ogni snackbar di sistema. `SnackBar` `floating` con sfondo trasparente, elevation 0, padding zero e margine `(md, 0, md, md)`; il contenuto è un `Material` con **riempimento pieno del tono**, raggio `control`, elevation 6, ombra nera al 24 %, padding 14×12, e dentro: icona bianca 22 px → 12 px → testo bianco peso 600 interlinea 1.35 → eventuale azione testuale bianca peso 700. Prima di mostrarlo si chiama `hideCurrentSnackBar()`: mai una coda di toast. Durata 4 s. Si espone come scorciatoie per tono (`success`/`error`/`warning`/`info`) più una funzione generica. ### 6.10 Dialog `Dialog` su `surface`, raggio `surface`, `insetPadding` 24, `maxWidth: 420`, padding interno 24. Composizione verticale allineata a sinistra: 1. **cerchio-icona** 48×48 riempito con la superficie tenue del tono, icona del tono 26 px; 2. 16 px → **titolo** `titleMedium`; 3. 8 px → **messaggio** `inkMuted` interlinea 1.45; 4. 24 px → **azioni**: due bottoni in `Expanded` (annulla contornato a sinistra, conferma pieno a destra, 12 px di distanza); se non c'è annullamento, un solo bottone a piena larghezza. Le azioni distruttive colorano di `emergency` il bottone di conferma. Due sole funzioni di ingresso: conferma (ritorna booleano) e informativo (un solo bottone). ### 6.11 Feedback inline Riquadro non modale per messaggi persistenti dentro la pagina: superficie tenue del tono, raggio `compact` (8), padding 12, icona 20 px del tono + 10 px + testo peso 600 del tono, allineati in alto. Marcato `Semantics(liveRegion: true)`. Si usa per errori di caricamento e avvisi che devono restare visibili, a differenza del toast che è effimero. ### 6.12 Stato vuoto Centrato, padding 28, larghezza massima 420: icona 44 px `inkMuted` → 14 px → titolo `titleMedium` centrato → 6 px → messaggio `inkMuted` centrato → 18 px → azione facoltativa (bottone contornato). Il messaggio **suggerisce la mossa successiva**; se lo stato vuoto dipende da filtri, l'azione li azzera. ### 6.13 Barra azioni fissa Ancorata in fondo alla pagina: `SafeArea(top: false)`, superficie `surface`, **bordo superiore 1 px** `divider`, padding `(16, 12, 16, 16)`. I bottoni sono in riga con `Expanded` e 12 px di distanza; sotto i **390 px** diventano una colonna a piena larghezza con 8 px di distanza. ### 6.14 Bottom sheet Sempre `isScrollControlled: true`, `useSafeArea: true`, `showDragHandle: true`. Altezza **fissata a una frazione dello schermo**: ~0.78 per uno sheet di consultazione, ~0.92 per uno sheet di compilazione. Struttura: 1. **testata**: padding `(20, 0, 8, 12)`, titolo `titleLarge` + sottotitolo `inkMuted` a sinistra, `IconButton` di chiusura a destra; 2. eventuale **segmented button** a piena larghezza (`expandedInsets: EdgeInsets.zero`) quando lo sheet ha due modalità; 3. `Divider(height: 1)` — l'unica linea di struttura; 4. **corpo** in `Expanded`, scrollabile, padding 16. Con più modalità si usa `IndexedStack`, che preserva lo stato dei form. ### 6.15 Navigazione - **Telefono**: `NavigationBar` **flottante** — avvolta in `SafeArea(minimum: (16, 0, 16, 12))` e in un `ClipRRect(borderRadius: 24)`, con `extendBody: true` sullo Scaffold, così il contenuto scorre sotto. Le pagine lasciano ~110–120 px di padding inferiore per non finirci sotto. - **Da 720 px**: `NavigationRail` a sinistra con etichette su tutte le voci, seguito da `VerticalDivider(width: 1)`. - Le destinazioni usano **icona outline a riposo e piena da selezionata**, con la stessa etichetta in entrambe le modalità. - Le sezioni si tengono vive in un `IndexedStack`: cambiare tab non ricostruisce né perde lo scroll. - Le **notifiche** stanno nelle `actions` dell'AppBar: `IconButton` con icona outline dentro un `Badge` numerico visibile solo se ci sono elementi non letti, e 12 px di margine destro. ### 6.16 Liste di navigazione (profilo, impostazioni) Un `Panel` che contiene `ListTile` con `contentPadding: EdgeInsets.zero`, icona a sinistra, `Icon(chevron_right)` come trailing per le voci che aprono una pagina, e `Divider()` tra una voce e l'altra. Le voci distruttive (uscita, eliminazione) portano l'icona in `emergency` e non hanno chevron. L'intestazione del profilo è un `Panel` con padding 22: `CircleAvatar` raggio 30 su superficie tenue con l'iniziale in 22 px peso 800, 16 px, poi nome `titleLarge` e riga secondaria `inkMuted`. ### 6.17 Campo composito (tappabile) Per valori che si scelgono da un picker invece che digitare: `Panel` `surfaceMuted` con padding zero, `InkWell` interno, padding 14, dentro un'etichetta `labelMedium`, 5 px, e una riga icona 19 px + 7 px + valore con ellissi. Si comporta come un campo ma apre un selettore. --- ## 7. Tema di onboarding (login e schermate pre-accesso) Le schermate fuori dall'applicazione autenticata usano una **variante derivata** dello stesso tema (`base.copyWith(...)`), non un tema separato: cambia solo il registro, non la struttura. - Sfondo pagina uniforme e neutro; primario = colore d'inchiostro (accento cromatico assente: l'attenzione è sul marchio). - Campi con riempimento bianco e bordo neutro; in focus bordo 2 px del colore d'inchiostro. - Bottone principale **a pillola** (`StadiumBorder`), altezza minima 52. - Composizione: `SafeArea` → `Center` → `SingleChildScrollView(padding: 24)` → `ConstrainedBox(maxWidth: 440)` → colonna `stretch`. - Ordine: logo (altezza 128, dentro `Semantics(image: true, label:)`) → 32 px → campi a 16 px di distanza → 24 px → eventuale messaggio d'errore centrato in `colorScheme.error` e `liveRegion` → 12 px → bottone di invio → 20 px → footer con versione. - Il bottone di invio mostra il caricamento **al posto dell'icona** (`SizedBox.square(dimension: 18)` con `CircularProgressIndicator(strokeWidth: 2)`), restando della stessa dimensione; durante il caricamento `onPressed: null`. - I campi dichiarano `autofillHints` dentro un `AutofillGroup` e incatenano `textInputAction: next`, con invio sull'ultimo. --- ## 8. Comportamenti trasversali **Caricamento.** Al primo caricamento senza dati: `CircularProgressIndicator` centrato in un'area di almeno 48 px di padding verticale. Con dati già presenti non si sostituisce mai il contenuto con uno spinner: si aggiorna in silenzio. Ogni elenco è avvolto in un `RefreshIndicator` (pull-to-refresh). **Errore di caricamento.** Non svuota la pagina: compare un **feedback inline** (§6.11) in tono danger sopra il contenuto, con l'istruzione per riprovare ("Trascina verso il basso per riprovare"). Gli errori di rete non bloccano la navigazione. **Vuoto vs. filtrato.** Si distinguono: "non c'è nulla" e "i filtri non trovano nulla" hanno testi diversi, e il secondo offre l'azzeramento dei filtri. **Riordino.** Nelle liste riordinabili solo gli elementi *ammissibili* mostrano la maniglia (`ReorderableDragStartListener` con `Icon(drag_handle)`); il riordino è disabilitato quando è attivo un filtro o una ricerca, e un suggerimento testuale compare accanto al conteggio solo sopra i 520 px di larghezza. **Conteggi sempre visibili.** Titoli di sezione, filtri e badge portano il numero ("12 attività", "Allegati (3)", "Tutte · 12"): il tecnico deve sapere quanto ha davanti senza contare. **Adattamento locale.** L'adattamento non è solo a livello di pagina: i **singoli componenti** misurano il proprio spazio con `LayoutBuilder` e passano da riga a colonna sotto le soglie indicate (350 px per le azioni di una card, 390 px per la barra azioni). Così lo stesso componente funziona in colonna singola e affiancato. **Accessibilità.** - `Semantics(label:)` su card, badge e maniglie di riordino, con una descrizione che riassume l'entità e i suoi stati. - `Semantics(liveRegion: true)` su toast, feedback inline e messaggi d'errore dei form. - `tooltip` su ogni `IconButton` senza etichetta. - Nessuna informazione veicolata dal solo colore: sempre icona e/o testo. - Target ≥ 44 px anche per le icone in testata. **Testi e stati.** Le date estese si scrivono per intero nella lingua dell'app; gli orari in `HH:mm` con zero iniziale, e i valori mancanti come `--:--`; i dati incompleti si dichiarano ("km finali mancanti", "Partenza da compilare") invece di lasciare un vuoto. --- ## 9. Come adottare lo stile in un'altra app 1. **Copiare la struttura dei token**: quattro classi statiche `Colors`, `Space`, `Radius`, `Breakpoints` con i valori dei §3 (spazi, raggi e breakpoint **identici**) e i propri colori sui ruoli del §2. 2. **Ricostruire il tema** con le impostazioni del §5: sono la parte che rende immediatamente riconoscibile lo stile (AppBar alta e fusa con la pagina, card piatte a raggio 20, controlli a raggio 16, target 44/48). 3. **Portare i componenti condivisi** del §6 — `PageWidth`, `Panel`, `SectionHeader`, `Badge`, `Feedback`, `EmptyState`, `ActionBar`, toast e dialog — rinominandoli col prefisso dell'app. Sono ~600 righe senza dipendenze oltre a Material. 4. **Definire le mappature di stato** dell'app (`priorityBadge`, `statusBadge`, o gli equivalenti del dominio) in un unico punto, accanto ai componenti. 5. **Vietare i valori ad-hoc**: nelle pagine si usano solo i token. Un colore o un raggio scritto a mano in una pagina è un difetto da correggere, non una scorciatoia. 6. **Verificare la lista di controllo**: - [ ] nessuna ombra sulle superfici (elevation 0) e nessun `surfaceTint`; - [ ] sfondo pagina diverso dalle superfici; - [ ] ogni tocco ≥ 44 px; - [ ] ogni stato = icona + etichetta + tono; - [ ] contenuto limitato a ~920 px e centrato; - [ ] barra di navigazione flottante sotto i 720 px, rail sopra; - [ ] toast custom al posto delle snackbar di sistema; - [ ] stato vuoto, stato di caricamento e stato d'errore previsti per ogni elenco; - [ ] componenti che si riorganizzano da soli sotto le soglie di larghezza.