Rifacimento della UI secondo STILE_UI.md

Adotta lo stile condiviso delle app Integry: superfici piatte, raggi ampi,
spaziature a scala fissa, gerarchia data dallo spazio prima che dalla
decorazione. La palette storica di SteUP resta intatta; sono stati aggiunti
solo i ruoli che mancavano (workspace, surfaceMuted, divider, inkMuted e le
triplette dei toni semantici).

Fondamenta
- tokens.css: ruoli colore, scale di spazio/raggi/tipografia, curve di
  movimento e scala z-index semantica. Unica fonte di verita': pilotano
  anche le variabili CSS di MudBlazor.
- Inter variabile ospitata in locale (133KB) al posto di Nunito da Google
  Fonts: l'app lavora offline, un font che arriva dalla rete in campo non
  arriva.
- base.css: shell applicativa e comportamenti da app nativa — niente
  selezione testo, niente menu al tocco lungo, niente lampo grigio sul tap,
  niente rimbalzo elastico, niente zoom a doppio tocco, scroll con inerzia.
- mudblazor-overrides.css: MudBlazor appiattito (elevation 0, nessun tint,
  target 44/48, etichette in caso naturale).
- Rimossi Bootstrap (CSS, icone e JS) e i 996 righe di CSS con ambito per
  componente del vecchio sistema: erano tutti inutilizzati.

Struttura
- Navigazione flottante in basso con icona piena sulla voce attiva; da 720px
  diventa colonna laterale. Il MudFabMenu e' sostituito da un bottom sheet:
  stessa funzione, bersagli molto piu' grandi.
- Testata alta col titolo a sinistra, fusa con la pagina.
- Nel form scheda il salvataggio sta in una barra ancorata in fondo, non in
  coda a uno scroll lungo.
- Scelta di punto vendita e articoli a schermo intero con ricerca; la
  selezione multipla usa righe da 56px invece di un menu a tendina.
- Urgenza scelta a pillole invece che da una select.

Stato e riscontro
- StatusBadgeHelper: tono, icona ed etichetta di ogni stato in un punto solo,
  cosi' lo stesso stato si presenta identico ovunque.
- Scheletri al posto degli spinner; l'overlay bloccante dice sempre cosa sta
  facendo; gli stati vuoti suggeriscono la mossa successiva.
- Rimosso il ritardo artificiale di 250ms in InspectionCard, che non
  attendeva nulla.

DESIGN.md riscritto sul nuovo sistema.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-17 11:09:26 +02:00
parent 3a9174f6fc
commit 2a0054aa30
77 changed files with 4595 additions and 3060 deletions
+304
View File
@@ -0,0 +1,304 @@
# 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 ` · ` (spaziopunto mediospazio) 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 ~110120 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.