Files
SteUP_Dotnet/SteUp.Shared/wwwroot/css/components.css
T
MarcoE 8cfce316fa Compatta i campi di accesso e togli il doppio bordo sul fuoco
Sul fuoco si vedevano due bordi: quello del campo piu' l'anello globale che
l'input riceve per conto suo, con raggio e colore diversi. Dove e' il
contenitore a segnare il fuoco — accesso, ricerca, barcode, gruppi del form —
l'anello sull'input non serve: il fuoco resta visibile dal bordo del campo.

Campi da 74 a 56 punti: padding piu' stretto, riga del valore alta quanto il
testo che contiene. Il bottone che mostra la password sborda nel padding invece
di alzare la riga, cosi' il campo password non e' piu' alto degli altri due e
il bersaglio resta da 44.

"Accedi" disabilitato era navy pieno con testo bianco: sembrava pronto, e il
tocco a vuoto sembrava un guasto. Ora e' chiaramente inerte.

Con la tastiera aperta il marchio finiva tagliato sopra e non si poteva
risalire: il pannello si centra con i margini automatici invece che con
align-items, cosi' lo scroll arriva anche in cima. Via anche la barra di
scorrimento, come nel resto delle schermate.

Verificata sul terminale, a riposo e col campo attivo.

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

1250 lines
35 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: 500;
}
/* Icona piena e pastiglia di selezione sulla voce attiva. */
.navbar__item.active { color: var(--primary-dark); }
.navbar__item.active .navbar__label { font-weight: 600; }
.navbar__item.active .navbar__icon { background-color: var(--info-surface); }
.navbar__item:active .navbar__icon { transform: scale(0.94); }
@media (prefers-reduced-motion: reduce) {
.navbar__item:active .navbar__icon { 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); }
.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;
justify-content: center;
min-height: 100dvh;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
/* La barra di scorrimento del browser e' uno dei segnali che tradiscono
una pagina web dentro un'app, come nel resto delle schermate. */
scrollbar-width: none;
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(--workspace);
}
.onboarding::-webkit-scrollbar { display: none; }
.onboarding__panel {
width: 100%;
max-width: 440px;
display: flex;
flex-direction: column;
/* Centrato con i margini automatici invece che con align-items: quando la
tastiera accorcia la finestra il contenuto eccede da entrambi i lati, e
con align-items: center la parte sopra finisce fuori dalla portata dello
scroll — il marchio si vedeva tagliato e non si poteva risalire. */
margin-block: auto;
}
.onboarding__logo {
height: 104px;
object-fit: contain;
margin-inline: auto;
}
/* Un pannello solo, tre righe divise da una linea: la stessa forma delle
liste dell'app, invece di tre riquadri che galleggiano separati. */
/* Tre campi distinti, non tre righe di un blocco unico: qui non si consulta un
elenco, si compilano tre dati diversi uno dopo l'altro, e lo stacco fra uno
e l'altro dice dove finisce quello che stai scrivendo. */
.onboarding__fields {
display: flex;
flex-direction: column;
gap: var(--space-xs);
margin-top: var(--space-lg);
}
.onboarding__field {
display: block;
padding: var(--space-xs) var(--space-14);
background-color: var(--surface);
border-radius: var(--radius-control);
border: 1.5px solid transparent;
transition: border-color var(--duration-fast) var(--ease-out);
}
/* Il campo attivo si segna col bordo, non cambiando fondo: lo stacco dallo
sfondo di pagina lo fa gia' la superficie bianca. */
.onboarding__field:focus-within { border-color: var(--primary-dark); }
/* L'etichetta sta sopra il campo e non si muove: niente testo che scivola
quando appoggi il dito. */
.onboarding__label {
display: block;
font-size: var(--label-md-size);
line-height: 1.2;
font-weight: 500;
color: var(--ink-muted);
}
.onboarding__input-row {
display: flex;
align-items: center;
gap: var(--space-xs);
}
.onboarding__input {
width: 100%;
min-width: 0;
min-height: 24px;
border: none;
background: none;
outline: none;
font-family: inherit;
font-size: var(--body-lg-size);
font-weight: 500;
color: var(--ink);
}
.onboarding__reveal {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: center;
width: var(--touch-min);
height: var(--touch-min);
/* Il bersaglio resta da 44, ma sborda nel padding invece di alzare la riga:
cosi' il campo password non e' piu' alto degli altri due. */
margin-block: -10px;
margin-right: calc(var(--space-6) * -1);
border: none;
border-radius: var(--radius-pill);
background: none;
color: var(--ink-muted);
font-size: 20px;
}
/* L'anello del fuoco resta dentro il bottone: fuori sbatterebbe contro il
bordo del campo. */
.onboarding__reveal:focus-visible { outline-offset: -2px; }
.onboarding__reveal:active { background-color: var(--surface-muted); }
.onboarding__error { margin-top: var(--space-md); }
/* 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); }
/* Disabilitato deve sembrare inerte: il navy pieno con testo bianco si legge
come un bottone pronto, e il tocco a vuoto sembra un guasto. */
.onboarding__submit:disabled { background-color: var(--divider); 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); }
/* -----------------------------------------------------------------------------
Azioni della testata
L'azione primaria di una schermata sta qui, con il suo nome: dice cosa fa
prima di essere toccata, e resta la stessa cosa in ogni pagina. Il vecchio
"+" dentro la barra di navigazione cambiava significato da una schermata
all'altra e non lo dichiarava mai.
-------------------------------------------------------------------------- */
.appbar__action {
display: inline-flex;
align-items: center;
gap: var(--space-6);
min-height: 40px;
padding: 0 var(--space-14) 0 var(--space-sm);
border: none;
border-radius: var(--radius-pill);
background-color: var(--primary);
color: var(--on-primary);
font-family: inherit;
font-size: var(--label-lg-size);
font-weight: 600;
white-space: nowrap;
transition: transform var(--duration-fast) var(--ease-out),
background-color var(--duration-fast) var(--ease-out);
}
.appbar__action i { font-size: 20px; }
.appbar__action:active { transform: scale(0.96); background-color: var(--primary-pressed); }
.appbar__action:disabled { background-color: var(--divider); color: var(--ink-muted); }
/* Variante a sola icona, per le schermate dove le azioni sono piu' d'una e
vivono in un bottom sheet. */
.appbar__icon-action {
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--touch-min);
height: var(--touch-min);
border: none;
border-radius: var(--radius-pill);
background: none;
color: var(--ink);
font-size: 22px;
transition: background-color var(--duration-fast) var(--ease-out);
}
.appbar__icon-action:active { background-color: var(--surface-muted); }
@media (prefers-reduced-motion: reduce) {
.appbar__action:active { transform: none; }
}
/* -----------------------------------------------------------------------------
Riga di scelta di un punto vendita
E' una card, ma si comporta da riga: testo a sinistra, chevron a destra.
-------------------------------------------------------------------------- */
.shop-card {
display: flex;
align-items: center;
gap: var(--space-sm);
width: 100%;
border: none;
font-family: inherit;
text-align: left;
}
.shop-card__text {
flex: 1 1 auto;
min-width: 0;
display: flex;
flex-direction: column;
}
.shop-card__chevron {
flex: 0 0 auto;
font-size: 22px;
color: var(--ink-muted);
}
/* L'indirizzo serve a distinguere due negozi con lo stesso nome, non a essere
letto per intero: due righe bastano e tengono l'elenco scorrevole. */
.shop-card .list-card__meta {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}