Commit Graph
57 Commits
Author SHA1 Message Date
davideandClaude Sonnet 5 e99d853987 Rimuove la nota su chi vede i dati amministrativi dal profilo
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 14:10:00 +02:00
davideandClaude Sonnet 5 5569d08b59 Aggiunge il dettaglio esteso di partita: formazioni e scontri diretti dal CSI
In Campionato > Storico partite ogni gara diventa cliccabile: verso /partita/$id
se collegata a un evento CrAPP, verso la nuova /partita-csi/$id altrimenti (nessuna
creazione automatica di eventi dal calendario CSI, quindi molte gare ne sono prive).

Il dettaglio aggiunge formazioni (titolari/panchina/staff), storico scontri diretti
e probabilità di vittoria calcolata dal CSI, letti on-demand da tre nuovi endpoint
per-partita (match-main/players/stats.php) dietro /api/public/csi-partita/$id, con
cache server per-partita separata da quella di /api/public/csi — è un dato aperto a
richiesta, non precaricato per tutti come classifica e partite.

Include anche logo squadra e metadati leggeri (girone, n° gara, arbitro, link al
referto ufficiale) già presenti nel JSON esistente, a costo zero.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 12:07:03 +02:00
Ivan CacciariandCursor f1d61ebd6e Allinea le tab del Profilo a una sola riga a larghezza uguale.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 09:17:49 +02:00
Ivan CacciariandCursor dbf60d3a7e Affina Profilo e Squadra: logo home, tab Docs uguali, separatore classifica.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 09:10:22 +02:00
davideandClaude Sonnet 5 1fdc6720a0 Sistema tutti gli errori di lint (prettier) rimasti su main
npx eslint --fix su test/unit/obiettivi.test.ts, test/integration/obiettivi.test.ts,
BarraSottosezioni.tsx, EventoCard.tsx e calendario.tsx — solo formattazione,
nessuna modifica di comportamento. Verificato con la suite unit (32/32 verde).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 14:12:49 +02:00
davideandClaude Sonnet 5 8add483a5b Precarica il chunk delle pagine al touchstart sui tab della BottomNav
Nessuna rotta aveva preload configurato: il chunk JS di una pagina iniziava a
scaricarsi solo a tap completato, mai prima. Con 4 tab piccole (8-12KB l'una) e
sempre visibili, non c'e' motivo di aspettare il tap per iniziare.

preload="intent" sui 4 Link: parte al touchstart, prima che il tap sia completo.
Precarica solo il codice, non i dati (nessuna rotta usa loader), quindi nessun
costo aggiuntivo di letture verso Supabase.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 10:10:05 +02:00
davideandClaude Sonnet 5 3258a24f20 Estende il fix di useIo/useRosa a EventoCard e altri 13 usi non statistici
EventoCard monta un hook per ogni card mostrata: usava useGiocatoreCorrente
(= useIo = useRosa, le 6 statistiche pesanti) solo per leggere il proprio id.
Il Calendario ne rende diverse insieme (prossimi eventi, compleanni, drawer del
giorno), quindi ogni apertura moltiplicava il ricalcolo dell'intera rosa.

Audit di tutti gli altri usi di useGiocatoreCorrente: 12 su 13 leggevano solo
id/nome/verita', mai una statistica. Corretti allo stesso modo (-> useGiocatoreBase):
benvenuto.tsx, VotazioneMvp, SondaggioCacche, VotoSocial, eventi.tsx,
PromemoriaPalloni (Home), TurnoPalloni, ScoutEntry, Pagelle.

partita.$id.tsx: `io` era dichiarato e mai piu' usato, rimosso.

Stesso pattern trovato anche su useRosa (non solo useGiocatoreCorrente) in
scout.tsx e RosaPresenze.tsx (montata su partita e allenamento): entrambi
passati al nuovo useAnagraficaRosa, esteso con ruolo e numero.

Unica eccezione: CelebrazioneBadge.tsx usa davvero le statistiche complete
(badge, MVP, serie) ed e' montato globalmente in __root.tsx - non downgradabile,
resta il costo di base piu' alto rimasto in giro.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 10:00:52 +02:00
davideandClaude Sonnet 5 82b6c6b333 Evita di calcolare le statistiche di tutta la rosa dove serve solo l'anagrafica
LinkProfilo (header di ogni pagina) e il Calendario (compleanni del mese) usavano
useIo/useRosa, che calcola MVP, pagelle, cacche, palloni e infortuni per l'intera
squadra tramite 6 hook e un useMemo pesante - anche se a entrambi serviva solo
id/nome/nascita/iniziali. Il Calendario in particolare pagava questo costo ad ogni
apertura solo per i compleanni del mese.

- crapp-data.ts: esporta inizialiDa, già usata internamente per il fallback avatar.
- ui-bits.tsx: LinkProfilo usa useGiocatoreBase (sola anagrafica) invece di useIo.
- rosa.ts: nuovo hook useAnagraficaRosa (id, nome, nascita) senza gli altri 5 hook
  statistici ne' il useMemo su tutta la rosa.
- eventi.ts: compleanniEventi accetta solo i tre campi che usa, non l'intero
  Giocatore.
- calendario.tsx: usa useAnagraficaRosa al posto di useRosa.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 09:47:31 +02:00
davideandClaude Sonnet 5 08b00627e5 Riduce il lag su Android: tab memoizzate, vetro/blur/drag disattivati sui device deboli
- squadra.tsx e classifica.tsx: il contenuto di ogni tab di BarraSottosezioni è ora
  memoizzato con useMemo, cosi' uno stato locale o un refetch in background non
  ricalcolano piu' anche le sezioni nascoste.
- BottomNav, BarraSottosezioni, calendario: useMotoRidotto (RAM bassa o
  prefers-reduced-motion) disattiva sui device deboli la catena di filtri SVG piu'
  pesante di .vetro, il backdrop-blur della barra tab e il drag orizzontale;
  su iOS e device performanti resta tutto invariato.
- styles.css: rimossa l'utility .materiale, orfana da quando BottomNav e' passata
  a .vetro.
- ui-bits.tsx: decoding="async" su TeamLogo.
- todo.md: traccia le cause individuate e lo stato di applicazione di ciascuna.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 09:39:40 +02:00
Ivan CacciariandCursor fc65914f55 Fa riempire tutta la larghezza alle tab di Campionato.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 18:35:35 +02:00
Ivan CacciariandCursor 8e23f31685 Rinomina Classifica in Campionato e allinea i link azione in home.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 18:13:10 +02:00
Ivan CacciariandCursor f5aaa1cccb Evita che le tab Squadra allarghino la pagina: scroll solo sulla barra.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 17:49:40 +02:00
davideandClaude Sonnet 5 b4878b7795 Chiude il sondaggio cacche al fischio d'inizio, non solo il giorno dopo
sondaggioAperto() restava aperto per sempre una volta passate le 8:00 del
giorno della partita, anche nei giorni successivi. Ora chiude esattamente
all'ora di inizio (partita alle 21:00 → aperto fino alle 20:59, poi chiuso
anche nei giorni seguenti), richiedendo anche l'ora dell'evento oltre alla
data.

Aggiunta sondaggioTerminato() per distinguere nel messaggio "non ancora
aperto" da "già chiuso": altrimenti dopo la partita l'utente avrebbe
continuato a leggere "apre alle 8:00".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 16:59:50 +02:00
davideandClaude Sonnet 5 446403cdf2 Menu sottosezioni "sottolineatura": pillola piena sulla tab attiva, su tutta larghezza
La sola sottolineatura era troppo minimale sulla tab attiva. Ora è uno sfondo
pieno arrotondato come le pillole del Profilo, ma le tab restano giustificate
su tutta la larghezza dello schermo senza scroll orizzontale.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 12:27:47 +02:00
Ivan CacciariandCursor ed289b9ecd Ingrandisce i titoli delle sezioni a 20px.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 11:42:08 +02:00
Ivan CacciariandCursor a06c5ed281 Semplifica il calendario e mostra le note solo sulle card Evento.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 11:38:03 +02:00
Ivan CacciariandCursor 10915d04fc Affina EventoCard: stato sopra i bottoni, note solo in dettaglio, badge Evento.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 11:24:58 +02:00
Ivan CacciariandCursor a459579f8b Ingrandisce i testi del widget profilo e del link Calendario in home.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 11:03:47 +02:00
Ivan CacciariandCursor a6799b91d3 Ridisegna EventoCard e toglie infortunato dagli eventi extra-campo.
Card cliccabile con icone presenza compatte, senza barra percentuale; avatar home leggermente più grande.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 10:58:57 +02:00
Ivan CacciariandCursor a1d2599c47 Rinomina il link evento in Dettagli.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 08:54:03 +02:00
Ivan CacciariandCursor ca1eee5425 Ripulisce profilo e tab, e mette Apri evento sull'ultima riga delle presenze.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 08:48:21 +02:00
Ivan CacciariandCursor 6bc5e8f006 Ingrandisce i caratteri delle tab sottosezioni e dei controlli calendario.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 08:24:59 +02:00
davideandClaude Opus 5 f149d02e9a Apre il voto MVP due ore dopo l'inizio e lo riserva ai presenti.
La votazione non dipendeva dal tempo ma dal risultato: compariva solo con un
referto CSI o uno scout salvato, e chiunque avesse un profilo poteva votare.
Ora `votoMvpAperto()` la apre due ore dopo `data`+`ora` dell'evento, il pannello
sta in una sezione sua e votano — e sono votabili — solo i presenti (o in
ritardo) di quell'evento.

I voti passano dall'id scout/CSI a quello dell'evento CrAPP: home e classifica
leggono il vincitore tramite la mappa data → evento che avevano già. Le regole
sono applicative, non RLS: chi scrive su PostgREST le aggira, come già annotato
tra i limiti noti del modulo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 22:11:26 +02:00
davideandClaude Opus 5 f687322c3f Vieta l'autovoto, allinea la doc al codice e toglie tre riletture.
Rilettura completa della documentazione confrontata con il codice. Dove la doc
diceva il falso l'ho corretta; dove aveva ragione lei ho corretto il codice.

Autovoto (la doc aveva ragione)

- migration m12_niente_autovoto: vincoli mvp_no_autovoto e badge_social_no_autovoto,
  gli stessi che pagelle_voti ha dalla v1.0. Le righe che li violano vengono
  cancellate prima dell'ALTER, altrimenti fallisce; in locale non ce n'erano.
  M11 garantisce solo che il voto sia firmato con il proprio votante_id, non che il
  votato sia un altro: eleggersi MVP restava a un POST di distanza.
- VotazioneMvp non mostra più il votante nell'elenco, come già faceva VotoSocial.

Test che guardavano la colonna sbagliata

- scritture.test.ts verificava che aggiornato_il si muovesse, chiamandolo "quello che
  alimenta la serie di conferme". È l'opposto: la serie usa risposto_il, che il trigger
  di M9 deve tenere fermo. Ora il test prova a riscriverlo e controlla che il database
  abbia tenuto la prima risposta; prima passava anche senza trigger.
- destinatariSollecito() esce dalla route sollecita-presenze e diventa una funzione pura
  in presenze.ts, con i suoi test — stesso trattamento di avvisiPalloniEvento.

Tre riletture in meno

- giocatori-squadra, scout-store e avatar-store usavano invalidateQueries dove il dato
  scritto era già noto: ora setQueryData, come il resto dell'app. Resta scout-live, dove
  il lock può averlo vinto un altro dispositivo.

Documentazione riallineata

- presenze.md, badge.md, mvp.md: i limiti su RLS aperta e route non autenticata erano
  superati da M11 e DD-024;
- serie-presenze.md: il filtro è e.data < oggi, non <=, e l'evento di oggi non conta
  (conterebbe come assenza per tutti); aggiunta la tabella risposto_il/aggiornato_il;
- ARCHITECTURE.md ed EFFICIENZA_CLOUD.md: una sola eccezione a setQueryData;
- DATABASE.md: i vincoli delle tre tabelle di voto;
- PROJECT_STATE.md: fermo a M9, ora arriva a M12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 19:30:49 +02:00
Ivan CacciariandCursor 3d8cb22235 Compatta la classifica interna Squadra e allinea le tab a sottolineatura.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-06 18:50:11 +02:00
davideandClaude Opus 5 2b8ba340a6 Fa scadere i promemoria in coda e smette di dare la colpa alle iscrizioni.
Provando il promemoria palloni in produzione la notifica risultava inviata ma
non arrivava. La causa non era il codice: l'iscrizione di destinazione era
scaduta, FCM l'ha accettata con 2xx e ha buttato via il messaggio, e un minuto
dopo il dispositivo si è re-iscritto con un endpoint nuovo. Un 2xx dal server
push non significa consegnato, e non manda il 404/410 che farebbe pulire
push_subscriptions: è annotato fra i limiti noti, perché dal server non è
distinguibile.

Il difetto vero l'ha fatto emergere quella caccia. promemoria_push si svuota
solo quando il dispositivo legge il messaggio, quindi se la push non arriva mai
la riga resta per sempre — e push-messaggio serve la coda con priorità sul testo
calcolato. In produzione ce n'erano dieci, la più vecchia del 2 settembre: alla
notifica successiva, di qualunque tipo, quel telefono avrebbe mostrato un
sollecito presenze per un evento già passato.

Ora un promemoria vale 12 ore. La riga si cancella comunque alla prima lettura,
scaduta o no: cancellare solo le fresche lascerebbe le vecchie in coda a
dirottare ogni notifica futura, cioè il bug. Così la coda si smaltisce da sola e
le righe orfane già in produzione non vanno ripulite a mano.

Il messaggio del pulsante distingue infine i due casi che prima confondeva:
nessuna iscrizione fra gli incaricati, oppure iscrizioni presenti e invio non
riuscito. Il primo è informativo, il secondo è un errore — dirlo sbagliato
manda a cercare il problema dalla parte opposta.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 12:25:21 +02:00
davideandClaude Opus 5 ee9f3f1b8c Il promemoria palloni lo manda un admin dalla pagina evento (DD-025).
La route era disegnata per un cron quotidiano: calcolava chi è di turno oggi e
gli mandava una push. Ma nel repository nessun cron esiste, e palloni.md lo
annotava già come "da verificare lato hosting": nei fatti quel promemoria non è
mai partito. Il segreto condiviso introdotto ieri proteggeva una porta che
nessuno apriva, al prezzo di una variabile d'ambiente da configurare ovunque.

Ora la fa partire un amministratore dal pulsante "Avvisa chi è di turno", dentro
il riquadro palloni dell'evento. La route accetta un eventoId e avvisa i
destinatari di quell'evento invece della giornata corrente: chi deve prendere i
palloni e chi deve riportarli, con un testo diverso per ciascuno. Stesso
precedente di apri-sondaggio, manuale fin dalla v1.0.6.

Il testo va in coda su promemoria_push prima dell'invio. Serve: la push parte
vuota e il service worker chiede a push-messaggio cosa mostrare, ma quella route
sa raccontare solo la giornata corrente, quindi un avviso mandato il martedì per
il sabato arriverebbe con il testo generico.

avvisiPalloniEvento() è la nuova funzione pura, con i suoi test: due destinatari
con testi diversi, un avviso solo quando sono la stessa persona, niente avvisi
per un evento inesistente o senza turni. Un'asserzione verifica che il testo non
dica mai "oggi", perché può arrivare giorni prima.

richiediSegreto e CRON_SEGRETO spariscono: tutte e tre le route di notifica
usano ora richiediAdmin, e non resta nessuna variabile da configurare.
destinatariPromemoriaPalloni() resta in palloni-core.ts con i suoi test perché
push-messaggio continua a usarla per il testo calcolato al volo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 12:15:39 +02:00
davideandClaude Opus 5 847972b582 Chiede le credenziali alle route che avvisano tutta la squadra (DD-024).
Le route in src/routes/api/public/ girano con la service role e saltano la RLS,
quindi DD-023 non le copre. Nessuna faceva un controllo di accesso: cercando
"authorization" in quella cartella l'unico header era lo User-Agent con cui
csi.ts chiama il portale CSI. Chiunque conoscesse l'URL poteva far suonare i
telefoni della squadra, e promemoria-palloni accetta perfino una POST con il
corpo vuoto.

La difesa apparente delle altre due — serve un id evento valido — non è una
difesa: l'id è "e" più il timestamp in base 36, compare negli URL che la squadra
si scambia ed è elencabile da qualsiasi utente loggato.

auth-route.server.ts porta i due controlli, diversi perché i chiamanti sono
diversi. apri-sondaggio e sollecita-presenze usano richiediAdmin: token della
sessione verificato con auth.getUser, poi ruolo admin da user_roles, la stessa
fonte di ruoli.ts. Il controllo precede la validazione dell'input, così la
risposta non rivela nemmeno se un evento esiste. promemoria-palloni usa
richiediSegreto, perché la chiama un cron che una sessione non ce l'ha: se
CRON_SEGRETO non è configurata la route resta chiusa con 503, perché una porta
che si riapre da sola quando manca una variabile non se ne accorge nessuno.

csi, push-config, push-subscribe e push-messaggio restano aperte: le chiamano il
browser prima del login e il service worker, dove qualsiasi segreto finirebbe
nel bundle.

Lato client i due pulsanti admin mandano il token con intestazioniAutenticate(),
letto al momento della chiamata e non da uno stato React.

permessi-route.test.ts copre il giro intero — nessun token, giocatore, admin —
avviando il server di sviluppo puntato al database locale, perché servono utenti
veri. Il controllo positivo è il 404: l'admin supera l'accesso e arriva alla
validazione. In api.test.ts restano i rifiuti che non richiedono un utente e
sparisce la verifica della validazione di sollecita-presenze, che ora sta dietro
all'accesso.

I limiti noti di palloni.md sono aggiornati: il secret che il piano originale
prevedeva ora c'è. Resta vero che nessun cron chiama la route, quindi il
promemoria quotidiano non parte da solo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 12:05:39 +02:00
davideandClaude Sonnet 5 50a712c276 Allinea l'altezza di input, select e date nei form a un'unica misura.
input[type="date"] resta più alto di un input di testo anche con
appearance-none: h-10 esplicito su classiInput allinea tutti i campi.
La textarea note usa h-auto per non perdere le due righe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 11:07:15 +02:00
davideandClaude Sonnet 5 77f587b53b Uniforma l'altezza dei <select> agli altri campi dei form su Android.
Su Android il controllo nativo di <select> ignora parte del padding di
classiInput e risultava più alto o più basso degli input accanto.
appearance-none lo riporta a una scatola CSS normale; la freccia va
ridisegnata a mano perché appearance-none la fa sparire.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 10:57:05 +02:00
davideandClaude Sonnet 5 208dd5f2f6 Blocca la modifica delle presenze e il sollecito per eventi già passati.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 10:27:12 +02:00
davideandClaude Opus 5 0c136f8f53 Apre lo Scout Live dalla pagina partita e a tutta la squadra.
Tolto dalla home e poi da Squadra, `ScoutEntry` era rimasto orfano:
`/scout` si raggiungeva solo scrivendo l'URL a mano. Ora la card sta
nella sezione «Scout live» di `/partita/$id`. La prop `eventoId` la
accende solo se la partita aperta è quella di oggi: senza, da una
partita futura o passata si sarebbe finiti sullo scout di un'altra.

Cade anche la riserva agli admin, in `ScoutEntry` e nella route: può
scoutare chiunque sia autenticato, uno per volta grazie al lock di
sessione. È anche l'unico controllo che c'era, visto che le policy RLS
sono sempre state aperte a tutti gli autenticati.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 00:24:39 +02:00
davideandClaude Opus 5 73902cc0a4 Apre il sondaggio pre-partita alle 8:00 con avviso push manuale.
Il sondaggio cacche era votabile in qualsiasi momento, anche settimane
prima della partita. Ora `sondaggioAperto()` lo sblocca alle 8:00 del
giorno della partita e prima la card mostra solo l'avviso di apertura.

Aggiunge la route `POST /api/public/apri-sondaggio` e, per gli
amministratori, il pulsante «Avvisa tutti del sondaggio» nella card:
manda la push a tutti i dispositivi iscritti, con lo stesso meccanismo
del sollecito presenze. Nessun invio automatico.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 00:08:58 +02:00
davideandClaude Opus 5 2a1a0839c0 Estende alla card degli eventi il fix delle date su iOS.
Il `min-w-0` di 366928b era finito solo in `ProfiloAmministrativo` perché
`Campo` e le classi degli input erano ricopiati identici in tre file: il
form «Nuovo evento» e la dashboard admin («Data tessera» in `grid-cols-2`)
sfondavano ancora la card. Ora `Campo` e `classiInput` stanno una volta
sola in `ui-bits` e le tre copie spariscono.

La regola CSS che rende ridimensionabili i controlli nativi copre anche
`input[type="time"]`, che nel form eventi sta affiancato alla data.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 23:41:20 +02:00
davideandClaude Opus 5 107407b854 Rinomina le tab del profilo in Documenti e Opzioni.
«Tesseramento» nominava lo scopo invece del contenuto (dati personali,
documento, certificato medico, foto tessera); cambia anche la rotta in
/profilo?tab=documenti. «Impostazioni» diventa «Opzioni» così le quattro
etichette stanno in uno schermo da telefono: le voci della barra si
dividono la riga in parti uguali (grow basis-0) e tornano a scorrere solo
se non ci stanno, quindi vale anche per Squadra e Classifica.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 23:37:41 +02:00
davideandClaude Opus 5 366928b61f Impedisce ai campi data di sfondare la card del tesseramento su iOS.
In `grid-cols-2` l'elemento di griglia è la label di `Campo`, che ha
`min-width: auto` e quindi non scende sotto la larghezza del contenuto. Su iOS
`input[type="date"]` è un controllo nativo con una larghezza intrinseca che
`width: 100%` non riduce: le due date affiancate sfondavano la card e
l'`overflow-hidden` del pannello a tab tagliava via quello che usciva.

`min-w-0` sulla label e sulla classe condivisa degli input, più `min-width: 0`
e `appearance: none` sulle sole date — è `appearance` a rendere il controllo
davvero ridimensionabile, e restando sulle date il select tiene la sua freccia.

Il fix sta in `CampiProfilo`, quindi copre anche la dashboard amministratore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 23:22:49 +02:00
davideandClaude Opus 5 84c4c0fc25 Revert "Corregge il rendering sfasato del tesseramento su iOS."
Annulla a5e627a: la diagnosi era sbagliata. I bordi sfalsati nella foto erano
distorsione prospettica dello scatto, non un artefatto di composizione, e il
sintomo vero era un altro (le card escono dallo spazio). Né il will-change del
pannello né il backdrop-blur della barra tab c'entravano.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 23:22:39 +02:00
davideandClaude Opus 5 a5e627a57b Corregge il rendering sfasato del tesseramento su iOS.
Il pannello delle sottosezioni è trascinabile: Motion gli lasciava
`will-change: transform` in permanenza e su iOS il sottoalbero finiva in un
layer composito. Sul tab Tesseramento, il più alto, il layer supera il tile di
WebKit e il contenuto veniva ridipinto a fasce sfalsate.

Disattiva la gestione automatica del will-change e toglie il backdrop-blur
dalla barra tab: non è sticky, quindi non sfocava nulla, ma il backdrop root
forzava il rendering a layer degli antenati.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 23:12:20 +02:00
Ivan CacciariandCursor 033c45df4d Apre Tesseramento dal widget Completa profilo in home.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-05 22:38:48 +02:00
Ivan CacciariandCursor ce1c7cfd4b Alleggerisce il cambio tab delle sottosezioni togliendo exit e molle.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-05 22:24:57 +02:00
Ivan CacciariandCursor e9755d364c Aggiunge sottosezioni a swipe nel profilo, con stagione e serie unite.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-05 22:07:58 +02:00
Ivan CacciariandCursor ede02b7575 Aggiunge sottosezioni a swipe su Squadra e Classifica.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-05 21:18:19 +02:00
davideandClaude Opus 5 c089d9e8ba Dà profondità alle superfici e trasforma la barra in vetro
Fondo pagina, carte e `secondary` stavano dentro tre punti di luminosità:
carta e fondo avevano un rapporto di contrasto di 1.04, cioè erano lo stesso
bianco. Le carte non si leggevano come carte, e l'unica cosa che le staccava
era `shadow-card` — 6% di nero, che su un telefono al sole non esiste. Da qui
l'impressione che fondo, barra ed elementi fossero «tre bianchi».

La rampa dei neutri è ridistanziata: fondo 0.93, carta 1.0, `secondary` 0.90.
Carta e fondo passano a 1.23, un elemento dentro una carta sta a 1.35 dalla
carta ed è più scuro di essa, come nelle liste raggruppate di iOS. 0.93 è il
fondo più scuro possibile senza toccare altro: il limite lo impone
`text-accent` appoggiato sul fondo, che a 0.92 scenderebbe sotto 4.5.

Due colori vanno corretti di conseguenza, altrimenti la modifica peggiora la
leggibilità: l'accento scende a 0.54 (a 0.58 come testo faceva 4.07 sul nuovo
fondo; ora fa 4.62, e migliora anche il bianco che ci sta sopra, da 4.65 a
5.51) e `muted-foreground` a 0.48. Verificati convertendo le oklch in sRGB e
calcolando i rapporti, non a occhio.

`--destructive` e `--ring` erano *lo stesso identico colore* dell'accento:
«elimina», «selezionato» e «ho il focus qui» davano il medesimo segnale. Il
distruttivo resta rosso ma più scuro e cupo, il focus viene dal neutro.

Le ombre passano da una a tre quote: `shadow-card` per il contenuto,
`shadow-chrome` per barra, drawer e toast (più profonda, più il filo chiaro sul
bordo alto), `shadow-alza` neutra per l'hover — `premi` metteva un alone rosso
sotto ogni riga di classifica.

La barra ora usa `vetro`: fondo al 62% invece dell'85%, così ci si vede
attraverso, più tre ombre interne che sono il bordo illuminato. L'85% di
`materiale` esiste per tenere allineati WebKit e Blink (608f2ed) su una fascia
larga quanto lo schermo; su una pillola di 60 px lo scarto non si legge.

Dietro `@supports`, un `feDisplacementMap` distorce davvero il fondale: solo
Blink accetta un filtro SVG in `backdrop-filter`, e WebKit ignorando l'url
scarterebbe anche il blur. Miglioria progressiva, non l'effetto principale.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 20:02:13 +02:00
davideandClaude Opus 5 bb4fb624f5 Mette lo stemma della squadra nell'intestazione del profilo
In alto a destra sul profilo c'era `/icon-192.png`, cioè l'icona della PWA: ha
dentro il nome dell'app, e come marchio in una schermata dell'app stessa non
dice niente. Lì serve lo stemma della squadra.

`TeamLogo` prende una prop `src` con l'icona come default, così cambia solo la
chiamata del profilo. L'icona della PWA resta quella dappertutto altrove
(ripiego di `LinkProfilo`, schermata di caricamento, manifest, apple-touch-icon):
sulla home dello schermo è giusto che l'app si presenti come «CrAPP».

Il file arrivava come `logo_nerorosso.jpg` ma è un SVG: rinominato con
l'estensione giusta, altrimenti verrebbe servito come JPEG e non si vedrebbe.
Sta in `public/`, che è la cartella servita così com'è.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 19:35:44 +02:00
davideandClaude Opus 5 37a04e5103 Rimette «Home» come etichetta della prima voce
`5c49294` aveva rinominato la voce in «Oggi» per dire cosa contiene la
schermata, ma il nome che la squadra usa è «Home».

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 19:24:54 +02:00
davideandClaude Opus 5 383643392a Porta il profilo in alto a destra e lascia quattro voci nella barra
La barra aveva cinque voci e la quinta era il profilo, che non è una
destinazione di pari livello con Oggi, Calendario, Squadra e Classifica: è la
propria scheda, e su iOS si cerca in alto a destra. Toglierla dalla barra
restituisce anche un quinto di larghezza alle altre quattro.

`LinkProfilo` mostra l'avatar del giocatore e porta a `/profilo`. Sta in
`PageHeader`, quindi compare su calendario, squadra, classifica e profilo, più
l'hero della home che ha un'intestazione propria. Se l'avatar non è stato
caricato `Avatar` ricade sulle iniziali; se il giocatore non c'è ancora (primo
render) resta il logo, così il posto non resta vuoto.

Sulla pagina del profilo l'avatar sarebbe un link a sé stessa: `PageHeader`
prende una prop `azione` e lì si passa il logo della squadra.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 19:22:08 +02:00
davideandClaude Opus 5 fabee4df1d Stacca la barra di navigazione dal fondo
La barra era una striscia a filo schermo con la sfumatura al bordo superiore
(scroll edge effect). Ora è una pillola staccata: il `<nav>` esterno tiene solo
posizione, safe area e margine laterale, mentre il materiale, il raggio, il bordo
e l'ombra passano al contenitore interno.

`pointer-events-none` sul wrapper e `pointer-events-auto` sulla pillola: ai lati
della barra i tocchi devono raggiungere il contenuto sotto, non una striscia
trasparente larga quanto lo schermo.

Via `bordo-sfumato`: la sfumatura serve a mascherare il taglio netto di una barra
attaccata al bordo, e su una che galleggia non ha più niente da mascherare.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 19:17:36 +02:00
davideandClaude Opus 5 78ae3fbbd7 Allinea la barra di navigazione fra iOS e Android
L'altezza della barra era dedotta dai padding delle voci e poi ricopiata a
occhio altrove: `pb-24` nel contenitore delle pagine, `pb-28` nella
celebrazione badge. Su iPhone i conti tornavano per 2 px — barra ~60 px più
34 px di safe area contro 96 px riservati — quindi bastava un inset diverso
(orizzontale, o un modello con home indicator più alta) perché l'ultimo
elemento della pagina finisse sotto la barra. Solo su iOS: su Android
avanzavano 36 px.

Ora la geometria sta in due token, `--altezza-nav` e `--pad-sicura-fondo`, e
le utility `spazio-nav` e `pad-sicura-fondo` la leggono da lì. La striscia
toccabile ha altezza fissa, così misura uguale sui due sistemi e sotto varia
solo l'inset: `max(env(safe-area-inset-bottom), 0.5rem)` dà ad Android un
respiro minimo dove iOS mette la home indicator. Identiche al pixel non
possono essere — quell'inset esiste per un motivo fisico che Android non ha —
ma la differenza è ora un margine in più, non un allineamento diverso.

`100vh` diventa `100dvh` ovunque: su iOS Safari `vh` misura il viewport con la
toolbar collassata, cioè più alto di quello visibile, e le schermate a tutta
altezza risultavano più lunghe dello schermo. `dvh` segue la toolbar.

Aggiunto il ripiego per Chrome su Android, che disattiva `backdrop-filter`
senza accelerazione hardware: senza, su quei device la barra restava
semitrasparente e il contenuto si leggeva attraverso.

Resta fuori portata una differenza sola: in Safari non installata la toolbar
di sistema sta in basso e collassa allo scroll, quindi la barra si muove con
essa. Su Chrome la toolbar è in alto. Si risolve installando la PWA.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 14:50:32 +02:00
davideandClaude Opus 5 5c49294ad1 Introduce la primitiva Card e sistema tocco e accessibilità dei componenti
`rounded-3xl bg-card p-4 shadow-card` era ricopiato a mano 22 volte: cambiare
raggio od ombra voleva dire toccare venti file. `Card` in `ui-bits.tsx` è ora
l'unica definizione delle superfici, con la gerarchia dei raggi scritta invece
che implicita (contenitore 3xl, elemento interno 2xl, controllo full).

Tocco e tastiera:
- i chip presenza di `EventoCard` erano alti ~28 px ed è il controllo più
  toccato dell'app: ora `min-h-11`, con 8 px di spazio fra l'uno e l'altro;
- stessa cura per il link «Apri partita», la chiusura di `CelebrazioneBadge` e
  il titolo di `SezioneTendina`;
- `aria-pressed` sui controlli a stato, `aria-controls` sulle tendine (che
  avevano `aria-expanded` senza il pannello a cui si riferisce), `aria-busy`
  sui caricamenti;
- `BottomNav`: lo stato attivo era comunicato solo dal colore. Ora cambia
  anche il peso del testo, compare una barretta sopra l'icona e c'è
  `aria-current="page"`. La voce «Home» diventa «Oggi», che dice cosa
  contiene. La barra è un materiale traslucido con bordo sfumato al posto
  della riga netta.
- `Barra` espone `role="progressbar"` con i valori.

Il testo sotto i 12 px è sparito (108 occorrenze fra `text-[9px]`,
`text-[10px]` e `text-[11px]`): sotto quella soglia la leggibilità cala e su
iOS non scala con Dynamic Type. La gerarchia la fanno peso e maiuscolo.

`Avatar` aveva `alt="Foto di 12"` quando il fallback è il numero di maglia —
non descrive niente e il nome è già scritto accanto, quindi alt vuoto. Aggiunti
`width`/`height` per non far saltare il layout al caricamento.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 12:42:24 +02:00
Ivan CacciariandCursor 438076c572 Rende collassabili badge e tesseramento nel profilo e toglie Scout Live dalla home.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-04 12:43:53 +02:00