8cfce316fa
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>
1250 lines
35 KiB
CSS
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;
|
|
}
|