440 lines
15 KiB
CSS
440 lines
15 KiB
CSS
@import "tailwindcss";
|
|
|
|
/* ─── TerManager2 Custom UI ─── */
|
|
|
|
/* Alpine nasconde con x-cloak solo se il foglio di stile lo dice: la regola non
|
|
arriva col pacchetto. Senza, gli elementi marcati x-cloak (le barre di
|
|
avanzamento, i messaggi d'errore) lampeggiano a ogni caricamento prima che
|
|
Alpine parta. */
|
|
[x-cloak] { display: none !important; }
|
|
|
|
/* Card hover lift effect */
|
|
.card-hover {
|
|
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
|
}
|
|
.card-hover:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 25px -5px rgba(0, 0, 0, 0.1), 0 4px 10px -5px rgba(0, 0, 0, 0.04);
|
|
}
|
|
|
|
/* Colored left accent border for stat cards */
|
|
.card-accent-indigo { border-left: 4px solid #6366f1; }
|
|
.card-accent-green { border-left: 4px solid #22c55e; }
|
|
.card-accent-blue { border-left: 4px solid #3b82f6; }
|
|
.card-accent-red { border-left: 4px solid #ef4444; }
|
|
.card-accent-amber { border-left: 4px solid #f59e0b; }
|
|
|
|
/* Table zebra striping */
|
|
.table-striped tbody tr:nth-child(even) {
|
|
background-color: #f9fafb;
|
|
}
|
|
|
|
/* Gradient header */
|
|
.header-gradient {
|
|
background: linear-gradient(135deg, #4338ca 0%, #6366f1 50%, #818cf8 100%);
|
|
}
|
|
|
|
/* Sidebar active state */
|
|
.sidebar-active {
|
|
background: linear-gradient(90deg, #eef2ff, #e0e7ff);
|
|
border-right: 3px solid #6366f1;
|
|
color: #4338ca;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Smooth page transitions */
|
|
.page-enter {
|
|
animation: fadeSlideIn 0.25s ease-out;
|
|
}
|
|
@keyframes fadeSlideIn {
|
|
from { opacity: 0; transform: translateY(8px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
/* Empty state styling */
|
|
.empty-state {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 2rem 1rem;
|
|
color: #9ca3af;
|
|
}
|
|
.empty-state svg {
|
|
width: 48px;
|
|
height: 48px;
|
|
opacity: 0.4;
|
|
}
|
|
|
|
/* Badge pulse for active items */
|
|
.badge-pulse {
|
|
animation: pulse 2s infinite;
|
|
}
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.7; }
|
|
}
|
|
|
|
/* ─── Pulsanti ─────────────────────────────────────────────────────────────
|
|
Un solo sistema per tutta l'applicazione, definito qui e in nessun altro
|
|
posto. Si usa dal componente <x-btn>, mai scrivendo queste classi a mano.
|
|
|
|
Da dove veniamo: quattro vocabolari in circolazione contemporaneamente —
|
|
style= inline con i colori scritti a mano (piu' onmouseover per l'hover),
|
|
catene di utility Tailwind ripetute a memoria, .btn-action* in sei tinte, e
|
|
.btn-primary-* in due. La stessa azione cambiava aspetto da una pagina
|
|
all'altra, un "Rientra" era rosso quanto un "Elimina", e nessuno di questi
|
|
pulsanti aveva uno stato di focus: chi naviga da tastiera non vedeva dove si
|
|
trovava.
|
|
|
|
Due assi, ortogonali:
|
|
|
|
variante primary l'azione principale. Una sola per schermata o zona.
|
|
secondary tutto il resto (dettaglio, modifica, scarica, filtra)
|
|
danger SOLO dove si cancellano o si sovrascrivono dati
|
|
ghost annulla, chiudi, pulisci
|
|
|
|
tono solido il pieno. Per le azioni isolate.
|
|
tenue la tinta chiara. Per le azioni dentro le righe di un
|
|
elenco, dove cinquanta pulsanti pieni urlerebbero.
|
|
|
|
La dimensione `sm` e' quella delle righe di elenco; `icona` toglie il testo e
|
|
lascia il quadrato.
|
|
|
|
Le regole stanno fuori da @layer, quindi vincono sulle utility di Tailwind:
|
|
un pulsante non puo' essere ritoccato per meta' con le classi. */
|
|
.tm-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 6px;
|
|
min-height: 40px;
|
|
padding: 10px 16px;
|
|
border-radius: 10px;
|
|
border: 1px solid transparent;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
line-height: 1.2;
|
|
white-space: nowrap;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
transition: background-color .15s ease, border-color .15s ease, color .15s ease;
|
|
}
|
|
.tm-btn svg {
|
|
width: 16px;
|
|
height: 16px;
|
|
flex: none;
|
|
}
|
|
|
|
/* Un anello unico per ogni variante: il colore del focus non deve dipendere
|
|
da quello del pulsante, altrimenti su rosso o su bianco cambia visibilita'
|
|
proprio dove serve di piu'. Il doppio contorno (chiaro + scuro) lo tiene
|
|
leggibile sia sulle card bianche sia sui riquadri colorati. */
|
|
.tm-btn:focus-visible,
|
|
.tm-console-btn:focus-visible,
|
|
.tm-nav-zona:focus-visible {
|
|
outline: 3px solid #1d4ed8;
|
|
outline-offset: 2px;
|
|
box-shadow: 0 0 0 5px #fff;
|
|
}
|
|
|
|
.tm-btn:disabled,
|
|
.tm-btn[aria-disabled="true"] {
|
|
opacity: .45;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.tm-btn-primary { background: #4f46e5; border-color: #3730a3; color: #fff; }
|
|
.tm-btn-primary:hover:not(:disabled) { background: #4338ca; }
|
|
|
|
.tm-btn-danger { background: #dc2626; border-color: #991b1b; color: #fff; font-weight: 600; }
|
|
.tm-btn-danger:hover:not(:disabled) { background: #b91c1c; }
|
|
|
|
.tm-btn-secondary { background: #fff; border-color: #d1d5db; color: #374151; }
|
|
.tm-btn-secondary:hover:not(:disabled) { background: #f9fafb; border-color: #9ca3af; }
|
|
|
|
.tm-btn-ghost {
|
|
background: transparent;
|
|
border-color: transparent;
|
|
color: #4b5563;
|
|
min-height: 32px;
|
|
padding: 4px 8px;
|
|
font-size: 13px;
|
|
text-decoration: underline;
|
|
}
|
|
.tm-btn-ghost:hover:not(:disabled) { color: #111827; background: #f3f4f6; }
|
|
|
|
/* Tono tenue: stessa gerarchia, voce piu' bassa. Nelle righe di un elenco i
|
|
pulsanti sono tanti quanti i record, e il pieno li trasforma in rumore —
|
|
soprattutto il rosso, che smette di significare "attenzione" quando compare
|
|
cinquanta volte di fila. */
|
|
.tm-btn-tenue { border-color: transparent; font-weight: 600; }
|
|
.tm-btn-tenue.tm-btn-primary { background: #e0e7ff; color: #4338ca; }
|
|
.tm-btn-tenue.tm-btn-primary:hover:not(:disabled) { background: #c7d2fe; }
|
|
.tm-btn-tenue.tm-btn-danger { background: #fee2e2; color: #991b1b; }
|
|
.tm-btn-tenue.tm-btn-danger:hover:not(:disabled) { background: #fecaca; }
|
|
.tm-btn-tenue.tm-btn-secondary { background: #f3f4f6; color: #374151; }
|
|
.tm-btn-tenue.tm-btn-secondary:hover:not(:disabled) { background: #e5e7eb; }
|
|
|
|
/* Tono velato: per i comandi che stanno SOPRA una superficie colorata — oggi
|
|
solo la testata, che ha il suo gradiente. Un pulsante bianco o indaco lì sopra
|
|
sarebbe una toppa; qui il colore lo dà lo sfondo e il pulsante si limita a
|
|
velarlo. Il focus diventa bianco, perche' l'anello blu su indaco non si
|
|
distingue. */
|
|
.tm-btn-velato {
|
|
background: rgba(255, 255, 255, .12);
|
|
border-color: transparent;
|
|
color: rgba(255, 255, 255, .92);
|
|
}
|
|
.tm-btn-velato:hover:not(:disabled) { background: rgba(255, 255, 255, .22); color: #fff; }
|
|
.tm-btn-velato:focus-visible {
|
|
outline: 3px solid #fff;
|
|
outline-offset: 2px;
|
|
box-shadow: 0 0 0 5px rgba(0, 0, 0, .35);
|
|
}
|
|
|
|
.tm-btn-sm { min-height: 32px; padding: 6px 12px; font-size: 13px; gap: 4px; }
|
|
.tm-btn-sm svg { width: 14px; height: 14px; }
|
|
|
|
/* Solo icona: quadrato, senza testo. Serve sempre un aria-label, altrimenti chi
|
|
usa un lettore di schermo trova un pulsante senza nome. */
|
|
.tm-btn-icona { padding-left: 8px; padding-right: 8px; }
|
|
.tm-btn-sm.tm-btn-icona { padding-left: 7px; padding-right: 7px; }
|
|
|
|
/* ─── Zone della pagina ────────────────────────────────────────────────────
|
|
Ogni sezione e' un bersaglio di navigazione: premendo un pulsante o una voce
|
|
dell'indice la pagina ci si porta sopra e la zona lampeggia, cosi' e' chiaro
|
|
*dove* e' finita l'attenzione invece di dover indovinare cosa e' cambiato.
|
|
|
|
scroll-margin-top tiene la testata della zona sotto la barra dell'indice,
|
|
che e' appiccicata in alto: senza, l'ancora finirebbe coperta. */
|
|
.tm-zona {
|
|
scroll-margin-top: 84px;
|
|
}
|
|
|
|
/* Lampeggio d'arrivo. Non usa `outline` perche' si somma male ai bordi delle
|
|
card; box-shadow non occupa spazio e non fa saltare il layout. */
|
|
.tm-zona-evidenziata {
|
|
animation: tmEvidenzia 1.6s ease-out;
|
|
}
|
|
@keyframes tmEvidenzia {
|
|
0% { box-shadow: 0 0 0 0 rgba(29, 78, 216, .6); }
|
|
12% { box-shadow: 0 0 0 7px rgba(29, 78, 216, .35); }
|
|
100% { box-shadow: 0 0 0 0 rgba(29, 78, 216, 0); }
|
|
}
|
|
|
|
/* Zona con un'operazione in corso: resta marcata finche' dura, cosi' anche
|
|
tornando sulla pagina dopo un ricaricamento si vede subito chi sta lavorando. */
|
|
.tm-zona-attiva {
|
|
box-shadow: 0 0 0 2px #4f46e5;
|
|
}
|
|
|
|
/* Chi ha chiesto meno animazioni riceve comunque il segnale, fermo. */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.tm-zona-evidenziata { animation: none; box-shadow: 0 0 0 3px rgba(29, 78, 216, .5); }
|
|
.tm-btn { transition: none; }
|
|
}
|
|
|
|
/* Indice delle zone, appiccicato in alto. */
|
|
.tm-nav-zone {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 20;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
padding: 10px 12px;
|
|
margin: -4px 0 0;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 12px;
|
|
background: rgba(255, 255, 255, .92);
|
|
backdrop-filter: blur(6px);
|
|
}
|
|
.tm-nav-zona {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 5px 10px;
|
|
border-radius: 999px;
|
|
border: 1px solid #e5e7eb;
|
|
background: #fff;
|
|
color: #374151;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
}
|
|
.tm-nav-zona:hover { background: #eef2ff; border-color: #c7d2fe; color: #3730a3; }
|
|
.tm-nav-zona-pericolo { color: #991b1b; border-color: #fecaca; }
|
|
.tm-nav-zona-pericolo:hover { background: #fef2f2; border-color: #fca5a5; color: #7f1d1d; }
|
|
|
|
/* ─── Console delle operazioni ─────────────────────────────────────────────
|
|
Una sola finestra per tutte le operazioni lunghe, agganciata in fondo alla
|
|
finestra del browser: quello che sta succedendo non deve uscire dallo schermo
|
|
mentre si scorre la pagina. Chiusa e' una striscia alta 44px.
|
|
|
|
Il posto in cui vive e' il corpo della pagina, ma la posizione e' fissa,
|
|
quindi in fondo alla pagina c'e' uno spaziatore della stessa altezza: senza,
|
|
coprirebbe l'ultima zona — che e' quella pericolosa. */
|
|
.tm-console {
|
|
position: fixed;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
z-index: 45;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: #0b1020;
|
|
border-top: 1px solid #1f2937;
|
|
box-shadow: 0 -8px 24px -12px rgba(0, 0, 0, .5);
|
|
color: #e5e7eb;
|
|
}
|
|
.tm-console-testata {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 8px 12px;
|
|
min-height: 44px;
|
|
}
|
|
.tm-console-titolo {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
background: none;
|
|
border: 0;
|
|
padding: 0;
|
|
color: #e5e7eb;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
}
|
|
.tm-console-btn {
|
|
background: transparent;
|
|
border: 1px solid #374151;
|
|
border-radius: 8px;
|
|
padding: 4px 10px;
|
|
color: #d1d5db;
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
}
|
|
.tm-console-btn:hover { background: #1f2937; color: #fff; }
|
|
|
|
/* Il corpo scorre; l'altezza e' in vh perche' su un portatile basso una
|
|
finestra fissa in pixel si mangerebbe mezza pagina. */
|
|
.tm-console-corpo {
|
|
max-height: min(38vh, 340px);
|
|
overflow-y: auto;
|
|
padding: 4px 12px 12px;
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
font-size: 12px;
|
|
line-height: 1.55;
|
|
}
|
|
.tm-console-riga { display: flex; gap: 8px; white-space: pre-wrap; word-break: break-word; }
|
|
.tm-console-ora { color: #6b7280; flex: none; }
|
|
.tm-console-sorgente { color: #818cf8; flex: none; }
|
|
.tm-console-riga-errore .tm-console-testo { color: #fca5a5; font-weight: 600; }
|
|
.tm-console-riga-ok .tm-console-testo { color: #6ee7b7; }
|
|
.tm-console-riga-attesa .tm-console-testo { color: #fcd34d; }
|
|
|
|
/* Barra di avanzamento nella testata: sottile, sempre allo stesso posto. */
|
|
.tm-console-barra {
|
|
flex: 1;
|
|
height: 6px;
|
|
min-width: 60px;
|
|
border-radius: 999px;
|
|
background: #1f2937;
|
|
overflow: hidden;
|
|
}
|
|
.tm-console-barra > div {
|
|
height: 100%;
|
|
border-radius: 999px;
|
|
background: #6366f1;
|
|
transition: width .3s ease;
|
|
}
|
|
.tm-console-barra-ok > div { background: #10b981; }
|
|
.tm-console-barra-errore > div { background: #ef4444; }
|
|
|
|
/* "Sei tornato indietro a leggere": lo scorrimento automatico si sospende e
|
|
compare il modo per riagganciarsi. */
|
|
.tm-console-torna {
|
|
align-self: center;
|
|
margin: 0 12px 8px;
|
|
background: #312e81;
|
|
border: 1px solid #4f46e5;
|
|
border-radius: 999px;
|
|
padding: 3px 12px;
|
|
color: #e0e7ff;
|
|
font-size: 11px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.tm-console-corpo { max-height: 45vh; font-size: 11px; }
|
|
.tm-console-sorgente { display: none; }
|
|
}
|
|
|
|
/* ─── Responsive list tables ───────────────────────────────────────────────
|
|
Under 640px (a phone held in portrait; rotate to landscape and the width
|
|
grows past the breakpoint, restoring the normal table) every table with the
|
|
`table-cards` class reflows: the header is hidden and each row becomes a
|
|
card, with each cell shown as "LABEL … value" (the label comes from the
|
|
cell's data-label attribute). Cells with no data-label (the actions column,
|
|
marked `.cell-actions`) span the row full width. */
|
|
@media (max-width: 640px) {
|
|
table.table-cards,
|
|
table.table-cards tbody {
|
|
display: block;
|
|
width: 100%;
|
|
}
|
|
table.table-cards thead {
|
|
display: none;
|
|
}
|
|
table.table-cards tr {
|
|
display: block;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 10px;
|
|
margin-bottom: 12px;
|
|
padding: 4px 2px;
|
|
background: #fff;
|
|
}
|
|
/* zebra striping would fight the card borders */
|
|
table.table-cards.table-striped tbody tr:nth-child(even) {
|
|
background: #fff;
|
|
}
|
|
table.table-cards td {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
width: 100%;
|
|
text-align: right;
|
|
padding: 6px 12px;
|
|
border: none !important;
|
|
}
|
|
table.table-cards td[data-label]::before {
|
|
content: attr(data-label);
|
|
flex: 0 0 auto;
|
|
margin-right: auto;
|
|
text-align: left;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
color: #6b7280;
|
|
text-transform: uppercase;
|
|
letter-spacing: .3px;
|
|
}
|
|
/* Actions row: full width, buttons wrapped to the right */
|
|
table.table-cards td.cell-actions {
|
|
justify-content: flex-end;
|
|
flex-wrap: wrap;
|
|
margin-top: 2px;
|
|
padding-top: 10px;
|
|
border-top: 1px solid #f3f4f6 !important;
|
|
}
|
|
/* Cells spanning all columns (empty state, inline edit form) stay full
|
|
width and block, so their inner layout isn't squeezed by the flex row. */
|
|
table.table-cards td[colspan] {
|
|
display: block;
|
|
text-align: center;
|
|
}
|
|
}
|