++ fix e controlli

This commit is contained in:
2026-07-30 17:05:39 +02:00
parent 712336d652
commit 440d63a6b9
54 changed files with 4921 additions and 1322 deletions

View File

@@ -2,6 +2,12 @@
/* ─── 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;
@@ -23,39 +29,6 @@
background-color: #f9fafb;
}
/* Action buttons */
.btn-action {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
transition: all 0.15s ease;
border: none;
cursor: pointer;
text-decoration: none;
}
.btn-action-green { background: #dcfce7; color: #166534; }
.btn-action-green:hover { background: #bbf7d0; }
.btn-action-red { background: #fee2e2; color: #991b1b; }
.btn-action-red:hover { background: #fecaca; }
.btn-action-blue { background: #dbeafe; color: #1e40af; }
.btn-action-blue:hover { background: #bfdbfe; }
.btn-action-amber { background: #fef3c7; color: #92400e; }
.btn-action-amber:hover { background: #fde68a; }
.btn-action-gray { background: #f3f4f6; color: #374151; }
.btn-action-gray:hover { background: #e5e7eb; }
.btn-action-indigo { background: #e0e7ff; color: #4338ca; }
.btn-action-indigo:hover { background: #c7d2fe; }
/* Primary action buttons (filled) */
.btn-primary-green { background: #16a34a; color: #fff; }
.btn-primary-green:hover { background: #15803d; }
.btn-primary-red { background: #dc2626; color: #fff; }
.btn-primary-red:hover { background: #b91c1c; }
/* Gradient header */
.header-gradient {
background: linear-gradient(135deg, #4338ca 0%, #6366f1 50%, #818cf8 100%);
@@ -102,6 +75,304 @@
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