Files
SteUP_Dotnet/SteUp.Shared/wwwroot/css/components.css
T
MarcoE 101b68123c Sostituisci la tendina di reparto e motivo con un bottom sheet
La MudSelect apriva un riquadro bianco che galleggiava sopra le card bianche,
senza titolo, senza sfondo oscurato, con la barra di scorrimento del desktop e
le righe alte quanto una riga di testo. Era la cosa che piu' di tutte faceva
sembrare l'app una pagina web.

Ora il campo e' una riga tappabile che mostra il valore, e la scelta arriva da
un bottom sheet con il titolo del campo e righe da 56 px con il segno tondo
della scelta singola: la stessa forma degli altri selettori dell'app, usabile
in piedi e con i guanti.

Il confronto per capire quale opzione e' quella corrente passa per l'etichetta:
Scheda.Reparto ricostruisce l'oggetto al volo da codice e descrizione, quindi
non e' mai lo stesso oggetto di quelli in elenco e il confronto di riferimento
non avrebbe segnato mai nulla.

Reparto e motivo non passano piu' dalla validazione di MudForm, perche' non
sono piu' input: il controllo degli obbligatori e' esplicito e l'errore si vede
sul gruppo interessato, non solo nel messaggio in cima. Si accende dopo il primo
tentativo di salvataggio, non prima: un form rosso all'apertura accusa l'utente
di un errore che non ha ancora commesso.

La scelta fatta si segna col tono informativo: un bordo corallo intorno a un
riempimento azzurro si leggeva come un errore.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 12:46:48 +02:00

1126 lines
31 KiB
CSS

/* =============================================================================
SteUP — Componenti condivisi (STILE_UI §6)
Mattoni riusabili. Le schermate compongono questi, non inventano stili.
========================================================================== */
/* -----------------------------------------------------------------------------
§5 AppBar — alta, titolo a sinistra, fusa con la pagina
-------------------------------------------------------------------------- */
.appbar {
flex: 0 0 auto;
display: flex;
align-items: center;
gap: var(--space-xs);
min-height: var(--appbar-height);
padding: var(--space-xs) var(--space-md);
padding-top: calc(var(--space-xs) + env(safe-area-inset-top, 0px));
background-color: var(--workspace);
}
.appbar__lead {
display: flex;
align-items: center;
flex: 0 0 auto;
}
.appbar__titles {
flex: 1 1 auto;
min-width: 0;
}
/* Il titolo puo' andare su due righe prima di troncare: il nome del punto
vendita e' l'ancora del compito, tagliarlo a meta' non aiuta nessuno. */
.appbar__title {
font-size: var(--headline-sm-size);
line-height: var(--headline-sm-height);
font-weight: 700;
letter-spacing: -0.02em;
color: var(--ink);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.appbar__subtitle {
font-size: var(--label-md-size);
line-height: 1.3;
font-weight: 500;
color: var(--ink-muted);
margin-top: var(--space-2);
}
.appbar__actions {
display: flex;
align-items: center;
gap: var(--space-4);
flex: 0 0 auto;
}
/* Titolo compatto per i form a schermo pieno, dove il compito e' gia' noto. */
.appbar--compact {
min-height: 64px;
}
.appbar--compact .appbar__title {
font-size: var(--title-lg-size);
line-height: var(--title-lg-height);
}
@media (min-width: 720px) {
.appbar {
padding-inline: 28px;
}
}
/* -----------------------------------------------------------------------------
§6.2 Panel — la superficie base di tutto
-------------------------------------------------------------------------- */
.panel {
background-color: var(--surface);
border-radius: var(--radius-surface);
padding: var(--space-md);
overflow: hidden;
}
.panel--flush { padding: 0; }
.panel--lg { padding: var(--space-18); }
.panel--info { background-color: var(--info-surface); }
.panel--muted { background-color: var(--surface-muted); }
.panel--danger { background-color: var(--danger-surface); }
/* Pannello interamente toccabile. */
.panel--tappable {
cursor: pointer;
-webkit-tap-highlight-color: transparent;
transition: transform var(--duration-fast) var(--ease-out),
background-color var(--duration-fast) var(--ease-out);
}
.panel--tappable:active {
transform: scale(0.985);
background-color: var(--surface-muted);
}
@media (prefers-reduced-motion: reduce) {
.panel--tappable:active { transform: none; }
}
/* -----------------------------------------------------------------------------
§6.3 SectionHeader
-------------------------------------------------------------------------- */
.section-header {
display: flex;
align-items: flex-start;
gap: var(--space-sm);
margin-bottom: var(--space-sm);
}
.section-header__text { flex: 1 1 auto; min-width: 0; }
.section-header__title {
font-size: var(--title-lg-size);
line-height: var(--title-lg-height);
font-weight: 700;
letter-spacing: -0.01em;
color: var(--ink);
}
.section-header__desc {
margin-top: var(--space-4);
font-size: var(--body-md-size);
line-height: var(--body-md-height);
color: var(--ink-muted);
}
.section-header__aside { flex: 0 0 auto; }
/* -----------------------------------------------------------------------------
§6.4 Badge — stato in pillola
Mai il solo colore: sempre icona + etichetta.
-------------------------------------------------------------------------- */
.badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 5px 9px;
border-radius: var(--radius-pill);
font-size: var(--label-sm-size);
line-height: 1.2;
font-weight: 600;
white-space: nowrap;
}
.badge__icon {
font-size: 15px;
line-height: 1;
flex: 0 0 auto;
}
.badge--danger { background-color: var(--danger-surface); color: var(--danger-ink); }
.badge--warning { background-color: var(--warning-surface); color: var(--warning-ink); }
.badge--success { background-color: var(--success-surface); color: var(--success-ink); }
.badge--info { background-color: var(--info-surface); color: var(--info-ink); }
.badge--neutral { background-color: var(--neutral-surface); color: var(--neutral-ink); }
.badge-row {
display: flex;
flex-wrap: wrap;
gap: var(--space-xs);
}
/* -----------------------------------------------------------------------------
§6.5 Card di elenco
-------------------------------------------------------------------------- */
.list-card {
position: relative;
background-color: var(--surface);
border-radius: var(--radius-surface);
padding: var(--space-md);
overflow: hidden;
}
.list-card__head {
display: flex;
align-items: flex-start;
gap: var(--space-xs);
}
.list-card__ident { flex: 1 1 auto; min-width: 0; }
/* Il titolo e' l'entita' (il punto vendita), non il tipo di record. */
.list-card__title {
font-size: var(--title-lg-size);
line-height: 1.15;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--ink);
margin-top: var(--space-2);
}
.list-card__meta {
margin-top: var(--space-6);
font-size: var(--body-md-size);
line-height: var(--body-md-height);
font-weight: 400;
color: var(--ink-muted);
}
.list-card__badges { margin-top: var(--space-sm); }
.list-card__footer {
display: flex;
align-items: center;
gap: var(--space-6);
margin-top: var(--space-sm);
font-size: var(--body-md-size);
font-weight: 400;
color: var(--ink-muted);
}
.list-card__footer .icon { font-size: 16px; }
/* Barra azioni interna: affiancate, in colonna quando la card e' stretta. */
.list-card__actions {
display: flex;
gap: var(--space-xs);
margin-top: var(--space-md);
}
.list-card__actions > * { flex: 1 1 0; min-width: 0; }
@container list-card (max-width: 350px) {
.list-card__actions { flex-direction: column; }
}
.list-card { container-type: inline-size; container-name: list-card; }
.card-list {
display: flex;
flex-direction: column;
gap: var(--space-10);
}
/* -----------------------------------------------------------------------------
§6.6 Pannello di riepilogo
-------------------------------------------------------------------------- */
.summary__head {
display: flex;
align-items: flex-start;
gap: var(--space-sm);
}
.summary__title {
font-size: var(--title-lg-size);
line-height: var(--title-lg-height);
font-weight: 700;
letter-spacing: -0.01em;
color: var(--ink);
}
.summary__subtitle {
margin-top: var(--space-2);
font-size: var(--body-md-size);
font-weight: 400;
color: var(--ink-muted);
}
.summary__metrics {
display: flex;
gap: var(--space-sm);
margin-top: var(--space-md);
}
.summary__metric {
flex: 1 1 0;
min-width: 0;
}
.summary__metric-icon {
font-size: 20px;
line-height: 1;
color: var(--primary-dark);
}
.summary__metric-value {
margin-top: var(--space-4);
font-size: var(--title-md-size);
line-height: var(--title-md-height);
font-weight: 700;
color: var(--ink);
}
.summary__metric-label {
font-size: var(--label-sm-size);
line-height: 1.3;
font-weight: 500;
color: var(--ink-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.summary__progress-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--space-xs);
margin-top: var(--space-md);
}
.summary__progress-count {
font-size: var(--body-md-size);
font-weight: 400;
color: var(--ink-muted);
}
.summary__progress-pct {
font-size: var(--headline-sm-size);
line-height: 1;
font-weight: 700;
letter-spacing: -0.02em;
color: var(--primary-dark);
}
.progress-track {
height: 8px;
border-radius: var(--radius-compact);
background-color: rgba(0, 35, 57, 0.12);
overflow: hidden;
margin-top: var(--space-xs);
}
/* Si anima con scaleX invece che con width: la larghezza rifa' il layout a
ogni frame, la trasformazione no. La percentuale arriva come --progress. */
.progress-fill {
height: 100%;
width: 100%;
border-radius: var(--radius-compact);
background-color: var(--primary);
transform: scaleX(var(--progress, 0));
transform-origin: left center;
transition: transform var(--duration-slow) var(--ease-out);
}
/* -----------------------------------------------------------------------------
§6.7 Chip e filtri
-------------------------------------------------------------------------- */
.chip-row {
display: flex;
gap: var(--space-xs);
align-items: center;
min-height: var(--touch-min);
overflow-x: auto;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
padding-inline: var(--space-md);
margin-inline: calc(var(--space-md) * -1);
}
.chip-row::-webkit-scrollbar { display: none; }
.chip {
display: inline-flex;
align-items: center;
gap: var(--space-6);
min-height: 36px;
padding: var(--space-xs) var(--space-sm);
border-radius: var(--radius-pill);
border: 1px solid var(--divider);
background-color: var(--surface-muted);
color: var(--ink-muted);
font-size: var(--label-lg-size);
font-weight: 500;
white-space: nowrap;
transition: background-color var(--duration-fast) var(--ease-out),
border-color var(--duration-fast) var(--ease-out),
color var(--duration-fast) var(--ease-out);
}
.chip[aria-pressed="true"],
.chip--selected {
background-color: var(--info-surface);
border-color: var(--info-solid);
color: var(--info-ink);
}
/* -----------------------------------------------------------------------------
§6.8 Campo di ricerca — completamente stondato, senza bordo a riposo
-------------------------------------------------------------------------- */
.search-field {
display: flex;
align-items: center;
gap: var(--space-xs);
min-height: var(--touch-min);
padding: var(--space-14) var(--space-18);
border-radius: var(--radius-pill);
background-color: var(--surface-muted);
border: 1.5px solid transparent;
transition: border-color var(--duration-fast) var(--ease-out);
}
.search-field:focus-within { border-color: var(--primary); }
.search-field__icon { color: var(--ink-muted); font-size: 20px; flex: 0 0 auto; }
.search-field input {
flex: 1 1 auto;
min-width: 0;
border: none;
background: none;
outline: none;
font: inherit;
font-size: var(--body-lg-size);
color: var(--ink);
}
.search-field input::placeholder { color: var(--ink-muted); }
/* -----------------------------------------------------------------------------
§6.11 Feedback inline — messaggi persistenti dentro la pagina
-------------------------------------------------------------------------- */
.feedback {
display: flex;
align-items: flex-start;
gap: var(--space-10);
padding: var(--space-sm);
border-radius: var(--radius-compact);
font-size: var(--body-lg-size);
line-height: 1.4;
font-weight: 500;
}
.feedback__icon { font-size: 20px; line-height: 1.2; flex: 0 0 auto; }
.feedback--danger { background-color: var(--danger-surface); color: var(--danger-ink); }
.feedback--warning { background-color: var(--warning-surface); color: var(--warning-ink); }
.feedback--success { background-color: var(--success-surface); color: var(--success-ink); }
.feedback--info { background-color: var(--info-surface); color: var(--info-ink); }
/* -----------------------------------------------------------------------------
§6.12 Stato vuoto — insegna la mossa successiva, non dice "niente qui"
-------------------------------------------------------------------------- */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
max-width: 420px;
margin-inline: auto;
padding: 28px;
}
.empty-state__icon {
font-size: 44px;
line-height: 1;
color: var(--ink-muted);
}
.empty-state__title {
margin-top: var(--space-14);
font-size: var(--title-md-size);
line-height: var(--title-md-height);
font-weight: 700;
color: var(--ink);
}
.empty-state__text {
margin-top: var(--space-6);
font-size: var(--body-lg-size);
line-height: 1.45;
color: var(--ink-muted);
}
.empty-state__action { margin-top: var(--space-18); }
/* -----------------------------------------------------------------------------
§6.13 Barra azioni fissa
-------------------------------------------------------------------------- */
.action-bar {
flex: 0 0 auto;
display: flex;
gap: var(--space-sm);
padding: var(--space-sm) var(--space-md) var(--space-md);
padding-bottom: calc(var(--space-md) + env(safe-area-inset-bottom, 0px));
background-color: var(--surface);
border-top: 1px solid var(--divider);
}
.action-bar > * { flex: 1 1 0; min-width: 0; }
@media (max-width: 390px) {
.action-bar {
flex-direction: column;
gap: var(--space-xs);
padding: var(--space-xs) var(--space-md) var(--space-sm);
padding-bottom: calc(var(--space-sm) + env(safe-area-inset-bottom, 0px));
}
}
/* -----------------------------------------------------------------------------
§6.15 Navigazione — barra flottante in basso
-------------------------------------------------------------------------- */
.navbar {
position: fixed;
left: var(--space-md);
right: var(--space-md);
bottom: calc(var(--space-sm) + env(safe-area-inset-bottom, 0px));
z-index: var(--z-navbar);
display: flex;
align-items: stretch;
height: var(--navbar-height);
border-radius: 24px;
background-color: var(--surface);
border: 1px solid var(--divider);
overflow: hidden;
transition: transform var(--duration-base) var(--ease-out),
opacity var(--duration-base) var(--ease-out);
}
.navbar--hidden {
transform: translateY(calc(100% + var(--space-lg)));
opacity: 0;
pointer-events: none;
}
.navbar__item {
flex: 1 1 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-2);
min-width: var(--touch-min);
color: var(--ink-muted);
text-decoration: none;
background: none;
border: none;
font-family: inherit;
transition: color var(--duration-fast) var(--ease-out);
}
.navbar__icon {
display: flex;
align-items: center;
justify-content: center;
width: 56px;
height: 30px;
border-radius: var(--radius-pill);
font-size: 20px;
transition: background-color var(--duration-base) var(--ease-out);
}
.navbar__label {
font-size: var(--label-md-size);
line-height: 1.2;
font-weight: 600;
}
/* Icona piena e pastiglia di selezione sulla voce attiva. */
.navbar__item.active { color: var(--primary-dark); }
.navbar__item.active .navbar__icon { background-color: var(--info-surface); }
.navbar__item:active .navbar__icon { transform: scale(0.94); }
/* Azione primaria della schermata, dentro la barra e a portata di pollice. */
.navbar__fab {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: center;
width: 56px;
height: 44px;
margin: 12px var(--space-sm) 12px 0;
border: none;
border-radius: var(--radius-control);
background-color: var(--primary);
color: var(--on-primary);
font-size: 26px;
transition: transform var(--duration-fast) var(--ease-out),
background-color var(--duration-fast) var(--ease-out);
}
.navbar__fab:active { transform: scale(0.94); background-color: var(--primary-pressed); }
.navbar__fab:disabled { background-color: var(--divider); color: var(--ink-muted); }
@media (prefers-reduced-motion: reduce) {
.navbar__item:active .navbar__icon,
.navbar__fab:active { transform: none; }
}
/* Da 720px la navigazione diventa una colonna laterale (rail). */
@media (min-width: 720px) {
.navbar {
left: var(--space-md);
right: auto;
top: calc(var(--space-md) + env(safe-area-inset-top, 0px));
bottom: calc(var(--space-md) + env(safe-area-inset-bottom, 0px));
height: auto;
width: 96px;
flex-direction: column;
justify-content: flex-start;
gap: var(--space-xs);
padding: var(--space-sm) 0;
}
.navbar__item { flex: 0 0 auto; padding-block: var(--space-xs); }
.navbar__fab {
order: -1;
width: 56px;
height: 48px;
margin: var(--space-xs) auto var(--space-sm);
}
.app-shell { padding-left: calc(96px + var(--space-lg)); }
.page-body { padding-bottom: calc(var(--space-lg) + env(safe-area-inset-bottom, 0px)); }
}
/* -----------------------------------------------------------------------------
§6.14 Bottom sheet
Sale dal basso, con la maniglia di trascinamento e un'unica linea di
struttura sotto la testata.
-------------------------------------------------------------------------- */
.sheet-scrim {
position: fixed;
inset: 0;
z-index: var(--z-modal);
background-color: rgba(0, 12, 20, 0.45);
animation: scrim-in var(--duration-base) var(--ease-out);
}
.sheet {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: var(--z-modal);
display: flex;
flex-direction: column;
max-height: 78dvh;
padding-bottom: env(safe-area-inset-bottom, 0px);
background-color: var(--surface);
border-radius: var(--radius-surface) var(--radius-surface) 0 0;
animation: sheet-in var(--duration-slow) var(--ease-out);
}
.sheet__handle {
width: 36px;
height: 4px;
margin: var(--space-sm) auto var(--space-xs);
border-radius: var(--radius-pill);
background-color: var(--divider);
flex: 0 0 auto;
}
.sheet__head {
display: flex;
align-items: flex-start;
gap: var(--space-xs);
padding: 0 var(--space-xs) var(--space-sm) var(--space-20);
flex: 0 0 auto;
}
.sheet__titles { flex: 1 1 auto; min-width: 0; }
.sheet__title {
font-size: var(--title-lg-size);
line-height: var(--title-lg-height);
font-weight: 700;
letter-spacing: -0.01em;
color: var(--ink);
}
.sheet__subtitle {
margin-top: var(--space-2);
font-size: var(--body-md-size);
font-weight: 400;
color: var(--ink-muted);
}
.sheet__divider {
height: 1px;
background-color: var(--divider);
flex: 0 0 auto;
}
.sheet__body {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
padding: var(--space-md);
scrollbar-width: none;
}
.sheet__body::-webkit-scrollbar { display: none; }
.sheet--tall { height: 92dvh; max-height: 92dvh; }
@keyframes sheet-in {
from { transform: translateY(100%); }
to { transform: none; }
}
@keyframes scrim-in {
from { opacity: 0; }
to { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.sheet { animation: scrim-in var(--duration-fast) var(--ease-out); }
}
/* -----------------------------------------------------------------------------
§6.16 Liste di navigazione (profilo, impostazioni)
-------------------------------------------------------------------------- */
.nav-list__item {
display: flex;
align-items: center;
gap: var(--space-sm);
width: 100%;
min-height: var(--touch-min);
padding: var(--space-sm) 0;
background: none;
border: none;
font-family: inherit;
font-size: var(--body-lg-size);
font-weight: 400;
color: var(--ink);
text-align: left;
}
.nav-list__item + .nav-list__item { border-top: 1px solid var(--divider); }
.nav-list__icon { font-size: 22px; color: var(--ink-muted); flex: 0 0 auto; }
.nav-list__label { flex: 1 1 auto; min-width: 0; }
.nav-list__chevron { font-size: 20px; color: var(--ink-muted); flex: 0 0 auto; }
.nav-list__item--danger { color: var(--danger-ink); }
.nav-list__item--danger .nav-list__icon { color: var(--danger-ink); }
/* Intestazione profilo */
.profile-head { display: flex; align-items: center; gap: var(--space-md); }
.profile-avatar {
display: flex;
align-items: center;
justify-content: center;
width: 60px;
height: 60px;
border-radius: var(--radius-pill);
background-color: var(--info-surface);
color: var(--primary-dark);
font-size: 22px;
font-weight: 700;
flex: 0 0 auto;
}
/* -----------------------------------------------------------------------------
§6.17 Campo composito (tappabile)
-------------------------------------------------------------------------- */
.field-tap {
display: block;
width: 100%;
padding: var(--space-14);
border: none;
border-radius: var(--radius-control);
background-color: var(--surface-muted);
font-family: inherit;
text-align: left;
transition: transform var(--duration-fast) var(--ease-out);
}
.field-tap:active { transform: scale(0.99); }
.field-tap__label {
font-size: var(--label-md-size);
line-height: 1.25;
font-weight: 500;
color: var(--ink-muted);
}
.field-tap__value {
display: flex;
align-items: center;
gap: 7px;
margin-top: 5px;
font-size: var(--body-lg-size);
font-weight: 500;
color: var(--ink);
overflow: hidden;
}
.field-tap__value span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.field-tap__icon { font-size: 20px; color: var(--ink-muted); flex: 0 0 auto; }
/* -----------------------------------------------------------------------------
Barra di stato connessione — la firma visiva di "Offline e' la verita'"
-------------------------------------------------------------------------- */
.connection {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: var(--z-connection);
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-6);
padding: var(--space-6) var(--space-md);
padding-top: calc(var(--space-6) + env(safe-area-inset-top, 0px));
font-size: var(--label-md-size);
font-weight: 600;
color: #ffffff;
transform: translateY(-100%);
transition: transform var(--duration-slow) var(--ease-out);
}
.connection--show { transform: translateY(0); }
.connection--ok { background-color: var(--success-solid); }
.connection--down { background-color: var(--warning-solid); }
.connection--offline { background-color: var(--danger-solid); }
/* -----------------------------------------------------------------------------
§6.10 Dialog — composizione verticale allineata a sinistra
-------------------------------------------------------------------------- */
.dialog-icon {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: var(--radius-pill);
font-size: 26px;
margin-bottom: var(--space-md);
}
.dialog-icon--danger { background-color: var(--danger-surface); color: var(--danger-ink); }
.dialog-icon--warning { background-color: var(--warning-surface); color: var(--warning-ink); }
.dialog-icon--success { background-color: var(--success-surface); color: var(--success-ink); }
.dialog-icon--info { background-color: var(--info-surface); color: var(--info-ink); }
.dialog-title {
font-size: var(--title-md-size);
line-height: var(--title-md-height);
font-weight: 700;
color: var(--ink);
}
.dialog-message {
margin-top: var(--space-xs);
font-size: var(--body-lg-size);
line-height: 1.45;
color: var(--ink-muted);
}
/* Il dettaglio tecnico non e' il messaggio: sta sotto, in secondo piano, per
chi deve riferirlo all'assistenza. */
.dialog-detail {
margin-top: var(--space-sm);
padding: var(--space-xs) var(--space-10);
border-radius: var(--radius-compact);
background-color: var(--surface-muted);
font-size: var(--label-md-size);
line-height: 1.4;
color: var(--ink-muted);
word-break: break-word;
user-select: text;
-webkit-user-select: text;
}
/* -----------------------------------------------------------------------------
§7 Accesso — variante derivata, non un tema a parte
Cambia il registro, non la struttura: l'accento cromatico sparisce e
l'attenzione va al marchio. Il corallo torna dentro l'app, dove indica
le azioni.
-------------------------------------------------------------------------- */
.onboarding {
display: flex;
align-items: center;
justify-content: center;
min-height: 100dvh;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
padding: var(--space-lg);
padding-top: calc(var(--space-lg) + env(safe-area-inset-top, 0px));
padding-bottom: calc(var(--space-lg) + env(safe-area-inset-bottom, 0px));
background-color: var(--surface-muted);
}
.onboarding__panel {
width: 100%;
max-width: 440px;
display: flex;
flex-direction: column;
}
.onboarding__logo {
height: 128px;
object-fit: contain;
margin-inline: auto;
}
.onboarding__fields {
display: flex;
flex-direction: column;
gap: var(--space-md);
margin-top: var(--space-xl);
}
.onboarding__field {
background-color: var(--surface);
border: 1px solid var(--divider);
border-radius: var(--radius-control);
padding: var(--space-xs) var(--space-md);
transition: border-color var(--duration-fast) var(--ease-out),
box-shadow var(--duration-fast) var(--ease-out);
}
.onboarding__field:focus-within {
border-color: var(--primary-dark);
box-shadow: inset 0 0 0 1px var(--primary-dark);
}
.onboarding__error {
margin-top: var(--space-lg);
text-align: center;
font-size: var(--body-lg-size);
font-weight: 600;
color: var(--danger-ink);
}
/* Bottone a pillola, alto 52: e' l'unico gesto della schermata. */
.onboarding__submit {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-xs);
width: 100%;
min-height: 52px;
margin-top: var(--space-sm);
border: none;
border-radius: var(--radius-pill);
background-color: var(--primary-dark);
color: #ffffff;
font-family: inherit;
font-size: var(--body-lg-size);
font-weight: 600;
transition: transform var(--duration-fast) var(--ease-out),
background-color var(--duration-fast) var(--ease-out);
}
.onboarding__submit:active { transform: scale(0.98); }
.onboarding__submit:disabled { background-color: var(--ink-muted); }
/* Il caricamento prende il posto dell'icona, stessa dimensione: il bottone
non cambia forma mentre lavora. */
.onboarding__spinner {
width: 18px;
height: 18px;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.35);
border-top-color: #ffffff;
animation: boot-spin 0.7s linear infinite;
}
@keyframes boot-spin {
to { transform: rotate(360deg); }
}
.onboarding__footer {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-xs);
margin-top: var(--space-20);
font-size: var(--label-md-size);
font-weight: 400;
color: var(--ink-muted);
}
.onboarding__footer img { height: 18px; }
@media (prefers-reduced-motion: reduce) {
.onboarding__submit:active { transform: none; }
.onboarding__spinner { animation-duration: 2s; }
}
/* -----------------------------------------------------------------------------
Caricamento — scheletri, non spinner in mezzo al contenuto
-------------------------------------------------------------------------- */
.skeleton {
border-radius: var(--radius-compact);
background-color: var(--surface-muted);
position: relative;
overflow: hidden;
}
.skeleton::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.75), transparent);
animation: skeleton-sweep 1.4s var(--ease-in-out) infinite;
}
@keyframes skeleton-sweep {
from { transform: translateX(-100%); }
to { transform: translateX(100%); }
}
@media (prefers-reduced-motion: reduce) {
.skeleton::after { animation: none; }
}
/* -----------------------------------------------------------------------------
Overlay di lavoro in corso
Copre solo quando l'operazione e' bloccante (invio al server): altrove il
contenuto si aggiorna in silenzio, senza sostituirsi con uno spinner.
-------------------------------------------------------------------------- */
.overlay {
position: fixed;
inset: 0;
z-index: var(--z-overlay);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-md);
padding: var(--space-xl);
background-color: rgba(233, 237, 242, 0.92);
animation: scrim-in var(--duration-fast) var(--ease-out);
}
.overlay__message {
max-width: 320px;
text-align: center;
font-size: var(--body-lg-size);
line-height: 1.4;
font-weight: 500;
color: var(--ink);
}
.spinner {
width: 34px;
height: 34px;
border-radius: 50%;
border: 3px solid rgba(0, 35, 57, 0.15);
border-top-color: var(--primary);
animation: boot-spin 0.7s linear infinite;
}
.spinner--lg { width: 44px; height: 44px; border-width: 4px; }
/* Spunta di conferma: si disegna, non lampeggia. E' il riscontro del momento
piu' importante dell'app, quando il lavoro arriva al server. */
.checkmark {
width: 56px;
height: 56px;
border-radius: 50%;
background-color: var(--success-surface);
display: flex;
align-items: center;
justify-content: center;
animation: check-pop var(--duration-slow) var(--ease-out);
}
.checkmark svg { width: 30px; height: 30px; }
.checkmark path {
stroke: var(--success-ink);
stroke-width: 3.5;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
stroke-dasharray: 32;
stroke-dashoffset: 32;
animation: check-draw 420ms var(--ease-out) 80ms forwards;
}
@keyframes check-pop {
from { transform: scale(0.85); opacity: 0; }
to { transform: none; opacity: 1; }
}
@keyframes check-draw {
to { stroke-dashoffset: 0; }
}
@media (prefers-reduced-motion: reduce) {
.spinner { animation-duration: 2s; }
.checkmark { animation: none; }
.checkmark path { animation: none; stroke-dashoffset: 0; }
}
.skeleton--title { height: 22px; width: 60%; }
.skeleton--line { height: 15px; width: 100%; }
.skeleton--line-short { height: 15px; width: 40%; }
.skeleton--badge { height: 24px; width: 110px; border-radius: var(--radius-pill); }