Rifinisce l'interfaccia su safe area, materiali, tipografia e accessibilità.
Nessun cambio di funzionalità: solo resa, accessibilità e correzioni al layout su iPhone. - Aggiunge `viewport-fit=cover`: senza, su iOS `env(safe-area-inset-*)` valeva 0 e il margine per la barra gesture già presente nella bottom nav non aveva effetto. Aggiunge il margine superiore su hero, PageHeader e header dello scout, che in modalità standalone finivano sotto il notch. - Attiva il tema scuro sulla preferenza di sistema e ne riscrive i valori: l'accento resta il rosso del brand (prima diventava grigio-blu) e i token semantici che mancavano ora ci sono. `theme-color` ha una tinta per tema. - Sposta il feedback dal rilascio alla pressione su Button, bottom nav e comandi principali; aggiunge l'anello di focus da tastiera in `@layer base`, che mancava su tutti i bottoni scritti a mano fuori da components/ui/. - Rende la bottom nav una superficie traslucida con sfumatura di scorrimento al posto della riga netta, alleggerisce il velo dei drawer e sostituisce l'alone rosso delle ombre con ombre neutre. - Introduce una scala tipografica in rem con crenatura e interlinea per taglia, al posto dei 109 `text-[Npx]` fissi che non scalavano con l'impostazione di testo del sistema. - Porta a 44px le aree di tocco dei comandi piccoli, mostra uno scheletro di pagina al posto della schermata bianca in home e profilo, conserva le dissolvenze con movimento ridotto e aggiunge il supporto a `prefers-reduced-transparency` e `prefers-contrast`. - Anima l'apertura delle sezioni a tendina con Radix Collapsible, già fra le dipendenze, al posto di un montaggio secco. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
952f7b43d3
commit
f3b22583ed
30 files changed
+555
-202
No files matched your search
@@ -6,6 +6,31 @@ qui: sta in [ROADMAP.md](ROADMAP.md).
|
|||||||
|
|
||||||
## Versione attuale — agosto 2026
|
## Versione attuale — agosto 2026
|
||||||
|
|
||||||
|
### Rifinitura dell'interfaccia (settembre 2026)
|
||||||
|
|
||||||
|
Nessun cambio di funzionalità: solo resa, accessibilità e correzioni al layout su iPhone.
|
||||||
|
|
||||||
|
- **Safe area.** Aggiunto `viewport-fit=cover`: senza, su iOS `env(safe-area-inset-*)`
|
||||||
|
valeva 0 e il margine per la barra gesture già presente nella bottom nav non aveva
|
||||||
|
effetto. Aggiunto anche il margine superiore su hero, `PageHeader` e header dello scout,
|
||||||
|
che in modalità standalone finivano sotto il notch.
|
||||||
|
- **Tema scuro attivo.** La classe `dark` segue `prefers-color-scheme` da uno script nel
|
||||||
|
`head`. I valori del tema scuro sono stati riscritti: l'accento resta il rosso del brand
|
||||||
|
(prima diventava grigio-blu) e sono stati definiti i token semantici che mancavano.
|
||||||
|
`theme-color` ora ha una tinta per tema.
|
||||||
|
- **Feedback al tocco.** `Button`, voci della bottom nav e comandi principali reagiscono
|
||||||
|
alla pressione e non al rilascio. Anello di focus da tastiera su tutti gli elementi
|
||||||
|
attivabili (`styles.css`, `@layer base`): prima era assente sui bottoni scritti a mano.
|
||||||
|
- **Materiali.** La bottom nav è una superficie traslucida con sfumatura di scorrimento al
|
||||||
|
posto della riga netta; velo dei drawer alleggerito; ombre neutre al posto dell'alone
|
||||||
|
rosso.
|
||||||
|
- **Tipografia.** Scala in `rem` con crenatura e interlinea per taglia; eliminati i 109
|
||||||
|
`text-[Npx]` fissi, che non scalavano con l'impostazione di testo del sistema.
|
||||||
|
- **Accessibilità e caricamento.** Aree di tocco portate a 44px sui comandi piccoli,
|
||||||
|
scheletro di pagina al posto della schermata bianca in home e profilo, movimento ridotto
|
||||||
|
che conserva le dissolvenze, supporto a `prefers-reduced-transparency` e
|
||||||
|
`prefers-contrast`.
|
||||||
|
|
||||||
### Autenticazione e dashboard amministratore (in produzione)
|
### Autenticazione e dashboard amministratore (in produzione)
|
||||||
|
|
||||||
- Login con Google tramite Supabase Auth (DD-011). Al primo accesso l'account si collega a
|
- Login con Google tramite Supabase Auth (DD-011). Al primo accesso l'account si collega a
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
"description": "L'app della squadra CRAP Volley: presenze, calendario, statistiche, classifica e scout live.",
|
"description": "L'app della squadra CRAP Volley: presenze, calendario, statistiche, classifica e scout live.",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#111111",
|
"background_color": "#0c0d11",
|
||||||
"theme_color": "#111111",
|
"theme_color": "#f9fafb",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -42,15 +42,13 @@ function DettaglioBadge({ def, stato }: { def: BadgeDef; stato?: BadgeStato }) {
|
|||||||
{stato ? (
|
{stato ? (
|
||||||
<div className="rounded-2xl bg-card p-4 shadow-card ring-1 ring-border">
|
<div className="rounded-2xl bg-card p-4 shadow-card ring-1 ring-border">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="text-2xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
Stato attuale
|
Stato attuale
|
||||||
</p>
|
</p>
|
||||||
{grado ? (
|
{grado ? (
|
||||||
<span className={cn("text-[11px] font-bold uppercase", meta?.text)}>
|
<span className={cn("text-2xs font-bold uppercase", meta?.text)}>{meta?.label}</span>
|
||||||
{meta?.label}
|
|
||||||
</span>
|
|
||||||
) : (
|
) : (
|
||||||
<span className="text-[11px] font-bold uppercase text-muted-foreground">
|
<span className="text-2xs font-bold uppercase text-muted-foreground">
|
||||||
In progresso
|
In progresso
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -73,7 +71,7 @@ function DettaglioBadge({ def, stato }: { def: BadgeDef; stato?: BadgeStato }) {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="mb-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="mb-2 text-2xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
Soglie
|
Soglie
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="grid grid-cols-3 gap-2">
|
||||||
@@ -90,7 +88,7 @@ function DettaglioBadge({ def, stato }: { def: BadgeDef; stato?: BadgeStato }) {
|
|||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-[10px] font-bold uppercase",
|
"text-3xs font-bold uppercase",
|
||||||
raggiunto ? gm.text : "text-muted-foreground",
|
raggiunto ? gm.text : "text-muted-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -104,7 +102,7 @@ function DettaglioBadge({ def, stato }: { def: BadgeDef; stato?: BadgeStato }) {
|
|||||||
>
|
>
|
||||||
{def.soglie[g]}
|
{def.soglie[g]}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] text-muted-foreground">{def.unita}</p>
|
<p className="text-3xs text-muted-foreground">{def.unita}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ const items = [
|
|||||||
|
|
||||||
export function BottomNav() {
|
export function BottomNav() {
|
||||||
return (
|
return (
|
||||||
<nav className="fixed inset-x-0 bottom-0 z-40 border-t border-border bg-card/95 backdrop-blur-md">
|
<nav className="materiale bordo-scroll-basso fixed inset-x-0 bottom-0 z-40 border-t">
|
||||||
<div className="mx-auto grid max-w-md grid-cols-5 px-1 pb-[env(safe-area-inset-bottom)]">
|
<div className="mx-auto grid max-w-md grid-cols-5 px-1 pb-[env(safe-area-inset-bottom)]">
|
||||||
{items.map(({ to, label, icon: Icon }) => (
|
{items.map(({ to, label, icon: Icon }) => (
|
||||||
<Link
|
<Link
|
||||||
key={to}
|
key={to}
|
||||||
to={to}
|
to={to}
|
||||||
activeOptions={{ exact: to === "/" }}
|
activeOptions={{ exact: to === "/" }}
|
||||||
className="group flex flex-col items-center gap-1 py-2.5 text-[10px] font-semibold text-muted-foreground transition-colors data-[status=active]:text-accent"
|
className="group flex flex-col items-center gap-1 py-2.5 text-3xs font-semibold text-muted-foreground transition-[color,transform] duration-100 ease-out active:scale-[0.92] data-[status=active]:text-accent"
|
||||||
>
|
>
|
||||||
<Icon className="h-5 w-5" strokeWidth={2.2} />
|
<Icon className="h-5 w-5" strokeWidth={2.2} />
|
||||||
{label}
|
{label}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function CelebrazioneBadge() {
|
|||||||
{notifica.emoji}
|
{notifica.emoji}
|
||||||
</span>
|
</span>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="text-[11px] font-bold uppercase tracking-wide text-accent">
|
<p className="text-2xs font-bold uppercase tracking-wide text-accent">
|
||||||
{notifica.tono === "segreto" ? "Badge segreto sbloccato" : "Traguardo raggiunto"}
|
{notifica.tono === "segreto" ? "Badge segreto sbloccato" : "Traguardo raggiunto"}
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-0.5 font-display text-2xl leading-none">{notifica.titolo}</p>
|
<p className="mt-0.5 font-display text-2xl leading-none">{notifica.titolo}</p>
|
||||||
|
|||||||
@@ -45,21 +45,21 @@ function CardBadge({ b, opaco, indice = 0 }: { b: BadgeStato; opaco?: boolean; i
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Icon className={cn("h-6 w-6", meta ? meta.text : "text-muted-foreground/50")} />
|
<Icon className={cn("h-6 w-6", meta ? meta.text : "text-muted-foreground/50")} />
|
||||||
{meta ? (
|
{meta ? (
|
||||||
<span className={cn("text-[10px] font-bold uppercase", meta.text)}>{meta.label}</span>
|
<span className={cn("text-3xs font-bold uppercase", meta.text)}>{meta.label}</span>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-1.5 text-sm font-bold leading-tight">{b.def.nome}</p>
|
<p className="mt-1.5 text-sm font-bold leading-tight">{b.def.nome}</p>
|
||||||
<p className="text-[11px] text-muted-foreground">
|
<p className="text-2xs text-muted-foreground">
|
||||||
{b.valore} {b.def.unita}
|
{b.valore} {b.def.unita}
|
||||||
</p>
|
</p>
|
||||||
{b.prossimaSoglia ? (
|
{b.prossimaSoglia ? (
|
||||||
<>
|
<>
|
||||||
<Barra percentuale={b.progresso} altezza="h-1.5" trackClassName="mt-2" />
|
<Barra percentuale={b.progresso} altezza="h-1.5" trackClassName="mt-2" />
|
||||||
<p className="mt-1 text-[10px] font-semibold text-accent">{mancanoPer(b)}</p>
|
<p className="mt-1 text-3xs font-semibold text-accent">{mancanoPer(b)}</p>
|
||||||
<p className="text-[10px] text-muted-foreground">{microcopyBadge(b)}</p>
|
<p className="text-3xs text-muted-foreground">{microcopyBadge(b)}</p>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<p className="mt-2 text-[10px] text-muted-foreground">{microcopyBadge(b)}</p>
|
<p className="mt-2 text-3xs text-muted-foreground">{microcopyBadge(b)}</p>
|
||||||
)}
|
)}
|
||||||
</Reveal>
|
</Reveal>
|
||||||
);
|
);
|
||||||
@@ -96,7 +96,7 @@ function SocialDrawer({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl bg-card p-4 shadow-card ring-1 ring-border">
|
<div className="rounded-2xl bg-card p-4 shadow-card ring-1 ring-border">
|
||||||
<p className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="text-2xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
Vinto
|
Vinto
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 font-display text-3xl leading-none">
|
<p className="mt-1 font-display text-3xl leading-none">
|
||||||
@@ -141,7 +141,7 @@ export function CollezioneBadge({
|
|||||||
<div className="rounded-3xl bg-hero p-4 text-primary-foreground shadow-card">
|
<div className="rounded-3xl bg-hero p-4 text-primary-foreground shadow-card">
|
||||||
<div className="flex items-end justify-between gap-3">
|
<div className="flex items-end justify-between gap-3">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="text-[11px] font-semibold uppercase tracking-wide text-primary-foreground/60">
|
<p className="text-2xs font-semibold uppercase tracking-wide text-primary-foreground/60">
|
||||||
Collezione badge
|
Collezione badge
|
||||||
</p>
|
</p>
|
||||||
<p className="font-display text-4xl leading-none">
|
<p className="font-display text-4xl leading-none">
|
||||||
@@ -158,7 +158,7 @@ export function CollezioneBadge({
|
|||||||
|
|
||||||
{vicino ? (
|
{vicino ? (
|
||||||
<div className="rounded-3xl bg-card p-4 shadow-card ring-1 ring-accent/30">
|
<div className="rounded-3xl bg-card p-4 shadow-card ring-1 ring-accent/30">
|
||||||
<p className="text-[11px] font-bold uppercase tracking-wide text-accent">
|
<p className="text-2xs font-bold uppercase tracking-wide text-accent">
|
||||||
Prossimo traguardo
|
Prossimo traguardo
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 text-sm font-bold leading-tight">{vicino.def.nome}</p>
|
<p className="mt-1 text-sm font-bold leading-tight">{vicino.def.nome}</p>
|
||||||
@@ -171,7 +171,7 @@ export function CollezioneBadge({
|
|||||||
|
|
||||||
{c.sbloccati.length > 0 ? (
|
{c.sbloccati.length > 0 ? (
|
||||||
<div>
|
<div>
|
||||||
<p className="mb-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="mb-2 text-2xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
Sbloccati ({c.sbloccati.length})
|
Sbloccati ({c.sbloccati.length})
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
@@ -186,7 +186,7 @@ export function CollezioneBadge({
|
|||||||
|
|
||||||
{c.inProgresso.length > 0 ? (
|
{c.inProgresso.length > 0 ? (
|
||||||
<div>
|
<div>
|
||||||
<p className="mb-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="mb-2 text-2xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
In progresso ({c.inProgresso.length})
|
In progresso ({c.inProgresso.length})
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
@@ -201,7 +201,7 @@ export function CollezioneBadge({
|
|||||||
|
|
||||||
{socialVinti.length > 0 ? (
|
{socialVinti.length > 0 ? (
|
||||||
<div>
|
<div>
|
||||||
<p className="mb-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="mb-2 text-2xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
Votati dai compagni
|
Votati dai compagni
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
@@ -210,7 +210,7 @@ export function CollezioneBadge({
|
|||||||
<div className="rounded-2xl bg-card p-3 shadow-card ring-1 ring-accent/25">
|
<div className="rounded-2xl bg-card p-3 shadow-card ring-1 ring-accent/25">
|
||||||
<p className="text-lg leading-none">{cat.emoji}</p>
|
<p className="text-lg leading-none">{cat.emoji}</p>
|
||||||
<p className="mt-1 text-sm font-bold leading-tight">{cat.nome}</p>
|
<p className="mt-1 text-sm font-bold leading-tight">{cat.nome}</p>
|
||||||
<p className="text-[11px] text-muted-foreground">
|
<p className="text-2xs text-muted-foreground">
|
||||||
Vinto {social[cat.id]} {social[cat.id] === 1 ? "volta" : "volte"}
|
Vinto {social[cat.id]} {social[cat.id] === 1 ? "volta" : "volte"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -221,7 +221,7 @@ export function CollezioneBadge({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="mb-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="mb-2 text-2xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
Badge segreti
|
Badge segreti
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
@@ -232,7 +232,7 @@ export function CollezioneBadge({
|
|||||||
<div className="rounded-2xl bg-card p-3 shadow-card ring-1 ring-oro/40">
|
<div className="rounded-2xl bg-card p-3 shadow-card ring-1 ring-oro/40">
|
||||||
<Icon className="h-6 w-6 text-oro" />
|
<Icon className="h-6 w-6 text-oro" />
|
||||||
<p className="mt-1.5 text-sm font-bold leading-tight">{b.def.nome}</p>
|
<p className="mt-1.5 text-sm font-bold leading-tight">{b.def.nome}</p>
|
||||||
<p className="text-[11px] text-muted-foreground">
|
<p className="text-2xs text-muted-foreground">
|
||||||
{b.def.celebrazione ?? "Badge segreto sbloccato."}
|
{b.def.celebrazione ?? "Badge segreto sbloccato."}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -246,7 +246,7 @@ export function CollezioneBadge({
|
|||||||
>
|
>
|
||||||
<Lucchetto className="h-6 w-6 text-muted-foreground/50" />
|
<Lucchetto className="h-6 w-6 text-muted-foreground/50" />
|
||||||
<p className="mt-1.5 text-sm font-bold leading-tight text-muted-foreground">???</p>
|
<p className="mt-1.5 text-sm font-bold leading-tight text-muted-foreground">???</p>
|
||||||
<p className="text-[11px] text-muted-foreground/70">Badge segreto da scoprire</p>
|
<p className="text-2xs text-muted-foreground/70">Badge segreto da scoprire</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export function EventoCard({
|
|||||||
</div>
|
</div>
|
||||||
<div className="shrink-0 rounded-2xl bg-secondary px-3 py-2 text-center">
|
<div className="shrink-0 rounded-2xl bg-secondary px-3 py-2 text-center">
|
||||||
<p className="font-display text-xl leading-none">{evento.data.slice(8, 10)}</p>
|
<p className="font-display text-xl leading-none">{evento.data.slice(8, 10)}</p>
|
||||||
<p className="text-[10px] font-semibold uppercase text-muted-foreground">
|
<p className="text-3xs font-semibold uppercase text-muted-foreground">
|
||||||
{formatData(evento.data).split(" ")[2]?.slice(0, 3)}
|
{formatData(evento.data).split(" ")[2]?.slice(0, 3)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -77,7 +77,7 @@ export function EventoCard({
|
|||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-block rounded-full px-2.5 py-0.5 text-[10px] font-bold uppercase tracking-wide",
|
"inline-block rounded-full px-2.5 py-0.5 text-3xs font-bold uppercase tracking-wide",
|
||||||
tipo.className,
|
tipo.className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -87,7 +87,7 @@ export function EventoCard({
|
|||||||
</div>
|
</div>
|
||||||
<div className="shrink-0 rounded-2xl bg-secondary px-3 py-2 text-center">
|
<div className="shrink-0 rounded-2xl bg-secondary px-3 py-2 text-center">
|
||||||
<p className="font-display text-xl leading-none">{evento.data.slice(8, 10)}</p>
|
<p className="font-display text-xl leading-none">{evento.data.slice(8, 10)}</p>
|
||||||
<p className="text-[10px] font-semibold uppercase text-muted-foreground">
|
<p className="text-3xs font-semibold uppercase text-muted-foreground">
|
||||||
{formatData(evento.data).split(" ")[2]?.slice(0, 3)}
|
{formatData(evento.data).split(" ")[2]?.slice(0, 3)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,9 +98,9 @@ export function EventoCard({
|
|||||||
<Link
|
<Link
|
||||||
to={linkTo.to}
|
to={linkTo.to}
|
||||||
params={linkTo.params}
|
params={linkTo.params}
|
||||||
className="inline-flex items-center gap-1 rounded-full bg-primary px-3 py-1.5 text-[10px] font-bold uppercase tracking-wide text-primary-foreground transition-transform active:scale-95"
|
className="inline-flex items-center gap-1 rounded-full bg-primary px-3 py-1.5 text-3xs font-bold uppercase tracking-wide text-primary-foreground transition-transform active:scale-95"
|
||||||
>
|
>
|
||||||
{linkTo.label} <ArrowRight className="h-3 w-3" />
|
{linkTo.label} <ArrowRight className="h-3.5 w-3.5" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -138,7 +138,7 @@ export function EventoCard({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-full border border-border px-2.5 py-1.5 text-[11px] font-semibold transition-all active:scale-95",
|
"rounded-full border border-border px-2.5 py-1.5 text-2xs font-semibold transition-all active:scale-95",
|
||||||
attivo
|
attivo
|
||||||
? cn(meta.className, "border-transparent shadow-card")
|
? cn(meta.className, "border-transparent shadow-card")
|
||||||
: "bg-background text-muted-foreground",
|
: "bg-background text-muted-foreground",
|
||||||
|
|||||||
@@ -43,14 +43,14 @@ export function Pagelle({
|
|||||||
return (
|
return (
|
||||||
<div className="rounded-3xl bg-card p-4 shadow-card">
|
<div className="rounded-3xl bg-card p-4 shadow-card">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<p className="inline-flex items-center gap-1.5 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="inline-flex items-center gap-1.5 text-2xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
<ClipboardCheck className="h-3.5 w-3.5" /> Pagelle anonime
|
<ClipboardCheck className="h-3.5 w-3.5" /> Pagelle anonime
|
||||||
</p>
|
</p>
|
||||||
<span className="rounded-full bg-secondary px-2.5 py-1 text-[10px] font-bold uppercase text-muted-foreground">
|
<span className="rounded-full bg-secondary px-2.5 py-1 text-3xs font-bold uppercase text-muted-foreground">
|
||||||
{chiuse ? "Votazioni chiuse" : `${fatti}/${daVotare.length} votati`}
|
{chiuse ? "Votazioni chiuse" : `${fatti}/${daVotare.length} votati`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-1 text-[11px] text-muted-foreground">
|
<p className="mt-1 text-2xs text-muted-foreground">
|
||||||
Dai un voto da 1 a 10 ai compagni: nessuno vedrà chi ha votato cosa, solo la media.
|
Dai un voto da 1 a 10 ai compagni: nessuno vedrà chi ha votato cosa, solo la media.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -69,12 +69,12 @@ export function Pagelle({
|
|||||||
<Avatar id={g.id} fallback={String(g.numero)} className="h-8 w-8 text-xs" />
|
<Avatar id={g.id} fallback={String(g.numero)} className="h-8 w-8 text-xs" />
|
||||||
<span className="min-w-0 flex-1">
|
<span className="min-w-0 flex-1">
|
||||||
<span className="block truncate text-sm font-bold leading-tight">{g.nome}</span>
|
<span className="block truncate text-sm font-bold leading-tight">{g.nome}</span>
|
||||||
<span className="text-[11px] text-muted-foreground">
|
<span className="text-2xs text-muted-foreground">
|
||||||
{media ? `Media ${media.media} · ${media.voti} voti` : "Nessun voto"}
|
{media ? `Media ${media.media} · ${media.voti} voti` : "Nessun voto"}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{sonoIo ? (
|
{sonoIo ? (
|
||||||
<span className="shrink-0 text-[10px] font-semibold uppercase text-muted-foreground">
|
<span className="shrink-0 text-3xs font-semibold uppercase text-muted-foreground">
|
||||||
Sei tu
|
Sei tu
|
||||||
</span>
|
</span>
|
||||||
) : chiuse ? (
|
) : chiuse ? (
|
||||||
@@ -85,7 +85,7 @@ export function Pagelle({
|
|||||||
disabled={!io || vota.isPending}
|
disabled={!io || vota.isPending}
|
||||||
onClick={() => setApertoPer(aperto ? null : g.id)}
|
onClick={() => setApertoPer(aperto ? null : g.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"shrink-0 rounded-full px-3 py-1 text-[10px] font-bold uppercase transition-colors disabled:opacity-50",
|
"shrink-0 rounded-full px-3 py-1 text-3xs font-bold uppercase transition-colors disabled:opacity-50",
|
||||||
mio !== undefined
|
mio !== undefined
|
||||||
? "bg-accent text-accent-foreground"
|
? "bg-accent text-accent-foreground"
|
||||||
: "bg-card text-foreground",
|
: "bg-card text-foreground",
|
||||||
@@ -103,7 +103,7 @@ export function Pagelle({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => invia(g.id, v)}
|
onClick={() => invia(g.id, v)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-lg py-1.5 text-[11px] font-bold tabular-nums transition-transform active:scale-90",
|
"rounded-lg py-1.5 text-2xs font-bold tabular-nums transition-transform active:scale-90",
|
||||||
mio === v
|
mio === v
|
||||||
? "bg-accent text-accent-foreground"
|
? "bg-accent text-accent-foreground"
|
||||||
: "bg-card text-foreground",
|
: "bg-card text-foreground",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const classiInput = "w-full rounded-xl border border-border bg-background px-3 p
|
|||||||
function Campo({ label, children }: { label: string; children: React.ReactNode }) {
|
function Campo({ label, children }: { label: string; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<label className="block">
|
<label className="block">
|
||||||
<span className="text-[10px] font-bold uppercase tracking-wide text-muted-foreground">
|
<span className="text-3xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
<span className="mt-1 block">{children}</span>
|
<span className="mt-1 block">{children}</span>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function RosaPresenze({ eventoId }: { eventoId: string }) {
|
|||||||
<span
|
<span
|
||||||
key={s}
|
key={s}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-full px-2.5 py-1 text-[11px] font-bold",
|
"rounded-full px-2.5 py-1 text-2xs font-bold",
|
||||||
n > 0 ? statoMeta[s].className : "bg-secondary text-muted-foreground",
|
n > 0 ? statoMeta[s].className : "bg-secondary text-muted-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -73,14 +73,14 @@ export function RosaPresenze({ eventoId }: { eventoId: string }) {
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<span className="rounded-full bg-secondary px-2.5 py-1 text-[11px] font-bold text-muted-foreground">
|
<span className="rounded-full bg-secondary px-2.5 py-1 text-2xs font-bold text-muted-foreground">
|
||||||
❔ {mancanti.length} da rispondere
|
❔ {mancanti.length} da rispondere
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{io ? (
|
{io ? (
|
||||||
<div className="mt-4 border-t border-border pt-3">
|
<div className="mt-4 border-t border-border pt-3">
|
||||||
<p className="text-[10px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="text-3xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
La tua risposta
|
La tua risposta
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-2 flex flex-wrap gap-1.5">
|
<div className="mt-2 flex flex-wrap gap-1.5">
|
||||||
@@ -95,7 +95,7 @@ export function RosaPresenze({ eventoId }: { eventoId: string }) {
|
|||||||
salva.mutate({ eventoId, giocatoreId: io.id, stato: attivo ? null : s })
|
salva.mutate({ eventoId, giocatoreId: io.id, stato: attivo ? null : s })
|
||||||
}
|
}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-full border border-border px-2.5 py-1.5 text-[11px] font-semibold transition-all active:scale-95",
|
"rounded-full border border-border px-2.5 py-1.5 text-2xs font-semibold transition-all active:scale-95",
|
||||||
attivo
|
attivo
|
||||||
? cn(statoMeta[s].className, "border-transparent shadow-card")
|
? cn(statoMeta[s].className, "border-transparent shadow-card")
|
||||||
: "bg-background text-muted-foreground",
|
: "bg-background text-muted-foreground",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export function ScoutEntry({ variante = "grande" }: { variante?: "grande" | "com
|
|||||||
{occupato ? <Lock className="h-5 w-5 shrink-0" /> : <Radio className="h-5 w-5 shrink-0" />}
|
{occupato ? <Lock className="h-5 w-5 shrink-0" /> : <Radio className="h-5 w-5 shrink-0" />}
|
||||||
<span className="min-w-0 flex-1">
|
<span className="min-w-0 flex-1">
|
||||||
<span className="block font-display text-lg uppercase leading-none">{titolo}</span>
|
<span className="block font-display text-lg uppercase leading-none">{titolo}</span>
|
||||||
<span className="block text-[11px] opacity-80">{sottotitolo}</span>
|
<span className="block text-2xs opacity-80">{sottotitolo}</span>
|
||||||
</span>
|
</span>
|
||||||
{disponibile ? <ChevronRight className="h-5 w-5 shrink-0" /> : null}
|
{disponibile ? <ChevronRight className="h-5 w-5 shrink-0" /> : null}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function SerieGriglia({ g }: { g: Giocatore }) {
|
|||||||
</span>
|
</span>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="text-sm font-bold leading-tight">{s.def.label}</p>
|
<p className="text-sm font-bold leading-tight">{s.def.label}</p>
|
||||||
<p className="text-[11px] text-muted-foreground">{s.def.descrizione}</p>
|
<p className="text-2xs text-muted-foreground">{s.def.descrizione}</p>
|
||||||
</div>
|
</div>
|
||||||
<span className="inline-flex items-center gap-1 font-display text-2xl leading-none">
|
<span className="inline-flex items-center gap-1 font-display text-2xl leading-none">
|
||||||
<Flame
|
<Flame
|
||||||
@@ -38,7 +38,7 @@ export function SerieGriglia({ g }: { g: Giocatore }) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Barra percentuale={s.progresso} trackClassName="mt-3" />
|
<Barra percentuale={s.progresso} trackClassName="mt-3" />
|
||||||
<p className="mt-2 text-[11px] text-muted-foreground">
|
<p className="mt-2 text-2xs text-muted-foreground">
|
||||||
{s.prossimo ? `${s.valore}/${s.prossimo} · ` : ""}
|
{s.prossimo ? `${s.valore}/${s.prossimo} · ` : ""}
|
||||||
{s.messaggio}
|
{s.messaggio}
|
||||||
</p>
|
</p>
|
||||||
@@ -63,14 +63,14 @@ export function SerieHome({ g }: { g: Giocatore }) {
|
|||||||
<p className="text-sm font-bold leading-tight">
|
<p className="text-sm font-bold leading-tight">
|
||||||
Serie {top.def.label.toLowerCase()}: {top.valore}
|
Serie {top.def.label.toLowerCase()}: {top.valore}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[11px] text-muted-foreground">{top.messaggio}</p>
|
<p className="text-2xs text-muted-foreground">{top.messaggio}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 grid grid-cols-3 gap-2">
|
<div className="mt-3 grid grid-cols-3 gap-2">
|
||||||
{serieGiocatore(g).map((s) => (
|
{serieGiocatore(g).map((s) => (
|
||||||
<div key={s.def.tipo} className="rounded-2xl bg-secondary p-2 text-center">
|
<div key={s.def.tipo} className="rounded-2xl bg-secondary p-2 text-center">
|
||||||
<p className="font-display text-xl leading-none">{s.valore}</p>
|
<p className="font-display text-xl leading-none">{s.valore}</p>
|
||||||
<p className="mt-1 text-[10px] font-semibold uppercase text-muted-foreground">
|
<p className="mt-1 text-3xs font-semibold uppercase text-muted-foreground">
|
||||||
{s.def.label}
|
{s.def.label}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -37,14 +37,14 @@ export function SondaggioCacche({ eventoId }: { eventoId: string }) {
|
|||||||
return (
|
return (
|
||||||
<div className="rounded-3xl bg-card p-4 shadow-card">
|
<div className="rounded-3xl bg-card p-4 shadow-card">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<p className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="text-2xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
💩 Sondaggio pre-partita
|
💩 Sondaggio pre-partita
|
||||||
</p>
|
</p>
|
||||||
<span className="rounded-full bg-secondary px-2.5 py-1 text-[10px] font-bold uppercase text-muted-foreground">
|
<span className="rounded-full bg-secondary px-2.5 py-1 text-3xs font-bold uppercase text-muted-foreground">
|
||||||
{dellaPartita.length} risposte
|
{dellaPartita.length} risposte
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-1 text-[11px] text-muted-foreground">
|
<p className="mt-1 text-2xs text-muted-foreground">
|
||||||
Quante cacche hai fatto prima di questa partita? Dato scientifico fondamentale.
|
Quante cacche hai fatto prima di questa partita? Dato scientifico fondamentale.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ export function SondaggioCacche({ eventoId }: { eventoId: string }) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-3 flex flex-wrap items-center gap-2 text-[11px]">
|
<div className="mt-3 flex flex-wrap items-center gap-2 text-2xs">
|
||||||
<span className="rounded-full bg-secondary px-2.5 py-1 font-semibold">
|
<span className="rounded-full bg-secondary px-2.5 py-1 font-semibold">
|
||||||
Media squadra {media}
|
Media squadra {media}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function TurnoPalloni({ eventoId }: { eventoId: string }) {
|
|||||||
<CircleDot className="h-4 w-4" />
|
<CircleDot className="h-4 w-4" />
|
||||||
</span>
|
</span>
|
||||||
<span className="min-w-0 flex-1">
|
<span className="min-w-0 flex-1">
|
||||||
<span className="block text-[10px] font-bold uppercase tracking-wide text-muted-foreground">
|
<span className="block text-3xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
Palloni
|
Palloni
|
||||||
</span>
|
</span>
|
||||||
<span className="flex items-center gap-1.5 text-sm font-bold leading-tight">
|
<span className="flex items-center gap-1.5 text-sm font-bold leading-tight">
|
||||||
@@ -50,7 +50,7 @@ export function TurnoPalloni({ eventoId }: { eventoId: string }) {
|
|||||||
) : null}
|
) : null}
|
||||||
<span className="truncate">{giocatore ? nomeCompleto(giocatore) : "Da assegnare"}</span>
|
<span className="truncate">{giocatore ? nomeCompleto(giocatore) : "Da assegnare"}</span>
|
||||||
{proposto && giocatore ? (
|
{proposto && giocatore ? (
|
||||||
<span className="shrink-0 rounded-full bg-card px-1.5 py-0.5 text-[9px] font-bold uppercase text-muted-foreground">
|
<span className="shrink-0 rounded-full bg-card px-1.5 py-0.5 text-3xs font-bold uppercase text-muted-foreground">
|
||||||
proposto
|
proposto
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -41,14 +41,14 @@ export function VotazioneMvp({ matchId }: { matchId: string }) {
|
|||||||
return (
|
return (
|
||||||
<div className="mt-3 rounded-2xl bg-secondary/60 p-3">
|
<div className="mt-3 rounded-2xl bg-secondary/60 p-3">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<p className="inline-flex items-center gap-1.5 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="inline-flex items-center gap-1.5 text-2xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
<Vote className="h-3.5 w-3.5" /> Voto MVP · {totale} {totale === 1 ? "voto" : "voti"}
|
<Vote className="h-3.5 w-3.5" /> Voto MVP · {totale} {totale === 1 ? "voto" : "voti"}
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setAperto((v) => !v)}
|
onClick={() => setAperto((v) => !v)}
|
||||||
disabled={!io}
|
disabled={!io}
|
||||||
className="rounded-full bg-accent px-3 py-1 text-[10px] font-bold uppercase text-accent-foreground disabled:opacity-50"
|
className="area-tocco rounded-full bg-accent px-3 py-1 text-3xs font-bold uppercase text-accent-foreground disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{mio ? "Cambia voto" : "Vota"}
|
{mio ? "Cambia voto" : "Vota"}
|
||||||
</button>
|
</button>
|
||||||
@@ -66,7 +66,7 @@ export function VotazioneMvp({ matchId }: { matchId: string }) {
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
{mio ? (
|
{mio ? (
|
||||||
<p className="mt-1 text-[11px] text-muted-foreground">Hai votato {mio.votato_nome}</p>
|
<p className="mt-1 text-2xs text-muted-foreground">Hai votato {mio.votato_nome}</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{aperto ? (
|
{aperto ? (
|
||||||
@@ -91,7 +91,7 @@ export function VotazioneMvp({ matchId }: { matchId: string }) {
|
|||||||
) : conteggio.length > 0 ? (
|
) : conteggio.length > 0 ? (
|
||||||
<div className="mt-2 flex flex-wrap gap-1.5">
|
<div className="mt-2 flex flex-wrap gap-1.5">
|
||||||
{conteggio.map((c) => (
|
{conteggio.map((c) => (
|
||||||
<span key={c.id} className="rounded-lg bg-card px-2 py-1 text-[11px] font-semibold">
|
<span key={c.id} className="rounded-lg bg-card px-2 py-1 text-2xs font-semibold">
|
||||||
{c.nome} · {c.voti}
|
{c.nome} · {c.voti}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function VotoSocial({ matchId }: { matchId: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="rounded-2xl bg-secondary/60 px-3 py-2 text-[11px] font-semibold text-muted-foreground">
|
<div className="rounded-2xl bg-secondary/60 px-3 py-2 text-2xs font-semibold text-muted-foreground">
|
||||||
<span className="inline-flex items-center gap-1.5">
|
<span className="inline-flex items-center gap-1.5">
|
||||||
<Sparkles className="h-3.5 w-3.5 text-accent" />
|
<Sparkles className="h-3.5 w-3.5 text-accent" />
|
||||||
Hai votato {fatti}/{categorieSocial.length} categorie · un solo voto per categoria
|
Hai votato {fatti}/{categorieSocial.length} categorie · un solo voto per categoria
|
||||||
@@ -79,17 +79,17 @@ export function VotoSocial({ matchId }: { matchId: string }) {
|
|||||||
</span>
|
</span>
|
||||||
<span className="min-w-0 flex-1">
|
<span className="min-w-0 flex-1">
|
||||||
<span className="block truncate text-sm font-bold leading-tight">{cat.nome}</span>
|
<span className="block truncate text-sm font-bold leading-tight">{cat.nome}</span>
|
||||||
<span className="block truncate text-[11px] text-muted-foreground">
|
<span className="block truncate text-2xs text-muted-foreground">
|
||||||
{mio ? `Hai votato ${mio.votato_nome}` : cat.descrizione}
|
{mio ? `Hai votato ${mio.votato_nome}` : cat.descrizione}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"shrink-0 rounded-full px-2.5 py-1 text-[10px] font-bold uppercase",
|
"shrink-0 rounded-full px-2.5 py-1 text-3xs font-bold uppercase",
|
||||||
mio ? "bg-success text-success-foreground" : "bg-accent text-accent-foreground",
|
mio ? "bg-success text-success-foreground" : "bg-accent text-accent-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{mio ? <Check className="h-3 w-3" /> : "Vota"}
|
{mio ? <Check className="h-3.5 w-3.5" /> : "Vota"}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ export function VotoSocial({ matchId }: { matchId: string }) {
|
|||||||
) : (
|
) : (
|
||||||
<div className="border-t border-border px-3 py-2">
|
<div className="border-t border-border px-3 py-2">
|
||||||
{totale === 0 ? (
|
{totale === 0 ? (
|
||||||
<p className="text-[11px] text-muted-foreground">
|
<p className="text-2xs text-muted-foreground">
|
||||||
Nessun voto ancora: apri e scegli un compagno.
|
Nessun voto ancora: apri e scegli un compagno.
|
||||||
</p>
|
</p>
|
||||||
) : vincitore ? (
|
) : vincitore ? (
|
||||||
@@ -127,7 +127,7 @@ export function VotoSocial({ matchId }: { matchId: string }) {
|
|||||||
{vincitore.nome} · {vincitore.voti} {vincitore.voti === 1 ? "voto" : "voti"}
|
{vincitore.nome} · {vincitore.voti} {vincitore.voti === 1 ? "voto" : "voti"}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-[11px] text-muted-foreground">
|
<p className="text-2xs text-muted-foreground">
|
||||||
Parità con {totale} voti: servono altri voti per assegnare il badge.
|
Parità con {totale} voti: servono altri voti per assegnare il badge.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { useState, type ReactNode } from "react";
|
|||||||
import { ChevronDown } from "lucide-react";
|
import { ChevronDown } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { statoMeta, type Stato } from "@/lib/crapp-data";
|
import { statoMeta, type Stato } from "@/lib/crapp-data";
|
||||||
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { Reveal } from "@/components/motion/Reveal";
|
import { Reveal } from "@/components/motion/Reveal";
|
||||||
import { Numero } from "@/components/motion/Numero";
|
import { Numero } from "@/components/motion/Numero";
|
||||||
|
|
||||||
@@ -17,7 +19,7 @@ export function TeamLogo({ className }: { className?: string }) {
|
|||||||
|
|
||||||
export function PageHeader({ titolo, sottotitolo }: { titolo: string; sottotitolo?: string }) {
|
export function PageHeader({ titolo, sottotitolo }: { titolo: string; sottotitolo?: string }) {
|
||||||
return (
|
return (
|
||||||
<header className="bg-hero px-5 pb-8 pt-7 text-primary-foreground">
|
<header className="bg-hero px-5 pb-8 pt-[calc(1.75rem+env(safe-area-inset-top))] text-primary-foreground">
|
||||||
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-4">
|
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-4">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<h1 className="truncate font-display text-3xl uppercase">{titolo}</h1>
|
<h1 className="truncate font-display text-3xl uppercase">{titolo}</h1>
|
||||||
@@ -31,6 +33,33 @@ export function PageHeader({ titolo, sottotitolo }: { titolo: string; sottotitol
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scheletro di pagina mostrato mentre i dati arrivano. Riserva lo spazio della
|
||||||
|
* struttura reale: meglio di `return null`, che lascia la schermata bianca e poi
|
||||||
|
* fa saltare il layout quando il contenuto compare.
|
||||||
|
*/
|
||||||
|
export function ScheletroPagina() {
|
||||||
|
return (
|
||||||
|
<div aria-busy="true" aria-live="polite">
|
||||||
|
<div className="bg-hero px-5 pb-10 pt-[calc(1.75rem+env(safe-area-inset-top))]">
|
||||||
|
<Skeleton className="h-3 w-24 bg-primary-foreground/20" />
|
||||||
|
<Skeleton className="mt-2 h-8 w-40 bg-primary-foreground/20" />
|
||||||
|
<div className="mt-6 grid grid-cols-3 gap-2">
|
||||||
|
{[0, 1, 2].map((i) => (
|
||||||
|
<Skeleton key={i} className="h-16 rounded-2xl bg-primary-foreground/10" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3 px-5 py-4">
|
||||||
|
{[0, 1, 2].map((i) => (
|
||||||
|
<Skeleton key={i} className="h-24 rounded-3xl" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<span className="sr-only">Caricamento in corso</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function Section({
|
export function Section({
|
||||||
titolo,
|
titolo,
|
||||||
azione,
|
azione,
|
||||||
@@ -72,25 +101,24 @@ export function SezioneTendina({
|
|||||||
const [aperta, setAperta] = useState(defaultAperta);
|
const [aperta, setAperta] = useState(defaultAperta);
|
||||||
return (
|
return (
|
||||||
<Reveal as="section" indice={indice} className="px-5 py-4">
|
<Reveal as="section" indice={indice} className="px-5 py-4">
|
||||||
<button
|
{/* Radix Collapsible (gia' fra le dipendenze) al posto di un `aperta ? ... : null`:
|
||||||
type="button"
|
l'altezza viene animata, cosi' l'apertura non e' uno scatto secco. */}
|
||||||
onClick={() => setAperta((v) => !v)}
|
<Collapsible open={aperta} onOpenChange={setAperta}>
|
||||||
className="mb-3 flex w-full items-center justify-between gap-3 text-left active:scale-[0.99]"
|
<CollapsibleTrigger className="mb-3 flex w-full items-center justify-between gap-3 text-left transition-transform duration-100 ease-out active:scale-[0.99]">
|
||||||
aria-expanded={aperta}
|
<h2 className="font-display text-lg uppercase tracking-wide">{titolo}</h2>
|
||||||
>
|
<span className="flex shrink-0 items-center gap-2">
|
||||||
<h2 className="font-display text-lg uppercase tracking-wide">{titolo}</h2>
|
{azione}
|
||||||
<span className="flex shrink-0 items-center gap-2">
|
<ChevronDown
|
||||||
{azione}
|
className={cn(
|
||||||
<ChevronDown
|
"h-4 w-4 text-muted-foreground transition-transform duration-200 ease-out",
|
||||||
className={cn(
|
aperta && "rotate-180",
|
||||||
"h-4 w-4 text-muted-foreground transition-transform",
|
)}
|
||||||
aperta && "rotate-180",
|
/>
|
||||||
)}
|
</span>
|
||||||
/>
|
</CollapsibleTrigger>
|
||||||
</span>
|
{anteprima}
|
||||||
</button>
|
<CollapsibleContent className="anim-tendina">{children}</CollapsibleContent>
|
||||||
{anteprima}
|
</Collapsible>
|
||||||
{aperta ? children : null}
|
|
||||||
</Reveal>
|
</Reveal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -100,7 +128,7 @@ export function StatoBadge({ stato, className }: { stato: Stato; className?: str
|
|||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-[11px] font-bold uppercase",
|
"inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-2xs font-bold uppercase",
|
||||||
meta.className,
|
meta.className,
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
@@ -124,10 +152,10 @@ export function StatTile({
|
|||||||
<p className="font-display text-2xl leading-none">
|
<p className="font-display text-2xl leading-none">
|
||||||
{typeof valore === "number" ? <Numero valore={valore} /> : valore}
|
{typeof valore === "number" ? <Numero valore={valore} /> : valore}
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
|
<p className="mt-1 text-2xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||||
{label}
|
{label}
|
||||||
</p>
|
</p>
|
||||||
{hint ? <p className="mt-0.5 text-[11px] text-accent">{hint}</p> : null}
|
{hint ? <p className="mt-0.5 text-2xs text-accent">{hint}</p> : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,9 @@ import { cva, type VariantProps } from "class-variance-authority";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
// Il feedback vive sulla pressione, non sul rilascio: su touch `hover` non
|
||||||
|
// esiste e senza `active:` il bottone sembra morto finche' non alzi il dito.
|
||||||
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium cursor-pointer transition-[transform,background-color,color,border-color,box-shadow] duration-100 ease-out active:scale-[0.97] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed disabled:active:scale-100 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ const DrawerOverlay = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<DrawerPrimitive.Overlay
|
<DrawerPrimitive.Overlay
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("fixed inset-0 z-50 bg-black/80", className)}
|
// 40%: la messa a fuoco la fa gia' il rientro in scala dello sfondo
|
||||||
|
// (shouldScaleBackground). Un velo all'80% cancella il contesto.
|
||||||
|
className={cn("fixed inset-0 z-50 bg-black/40", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
@@ -38,12 +40,13 @@ const DrawerContent = React.forwardRef<
|
|||||||
<DrawerPrimitive.Content
|
<DrawerPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
|
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-2xl border bg-background",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
{/* Maniglia nelle proporzioni di sistema (~5x36pt), non una barra spessa. */}
|
||||||
|
<div className="mx-auto mt-2.5 h-1.5 w-9 rounded-full bg-muted-foreground/40" />
|
||||||
{children}
|
{children}
|
||||||
</DrawerPrimitive.Content>
|
</DrawerPrimitive.Content>
|
||||||
</DrawerPortal>
|
</DrawerPortal>
|
||||||
|
|||||||
+30
-4
@@ -84,7 +84,12 @@ export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()(
|
|||||||
head: () => ({
|
head: () => ({
|
||||||
meta: [
|
meta: [
|
||||||
{ charSet: "utf-8" },
|
{ charSet: "utf-8" },
|
||||||
{ name: "viewport", content: "width=device-width, initial-scale=1" },
|
// `viewport-fit=cover` non e' opzionale: senza, su iOS `env(safe-area-inset-*)`
|
||||||
|
// vale 0 e tutti i margini per notch e barra gesture diventano codice morto.
|
||||||
|
{
|
||||||
|
name: "viewport",
|
||||||
|
content: "width=device-width, initial-scale=1, viewport-fit=cover",
|
||||||
|
},
|
||||||
{ title: "CrAPP — L'app del CRAP Volley" },
|
{ title: "CrAPP — L'app del CRAP Volley" },
|
||||||
{
|
{
|
||||||
name: "description",
|
name: "description",
|
||||||
@@ -92,7 +97,18 @@ export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()(
|
|||||||
"Convocazioni, presenze, statistiche e classifica del CRAP Volley in un'unica app mobile.",
|
"Convocazioni, presenze, statistiche e classifica del CRAP Volley in un'unica app mobile.",
|
||||||
},
|
},
|
||||||
{ name: "author", content: "CRAP Volley" },
|
{ name: "author", content: "CRAP Volley" },
|
||||||
{ name: "theme-color", content: "#111111" },
|
// Una tinta per tema: con un valore solo la barra di sistema resta nera
|
||||||
|
// anche in chiaro. I valori corrispondono a --background di styles.css.
|
||||||
|
{
|
||||||
|
name: "theme-color",
|
||||||
|
content: "#f9fafb",
|
||||||
|
media: "(prefers-color-scheme: light)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "theme-color",
|
||||||
|
content: "#0c0d11",
|
||||||
|
media: "(prefers-color-scheme: dark)",
|
||||||
|
},
|
||||||
{ property: "og:title", content: "CrAPP — L'app del CRAP Volley" },
|
{ property: "og:title", content: "CrAPP — L'app del CRAP Volley" },
|
||||||
{
|
{
|
||||||
property: "og:description",
|
property: "og:description",
|
||||||
@@ -143,9 +159,19 @@ export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()(
|
|||||||
|
|
||||||
function RootShell({ children }: { children: ReactNode }) {
|
function RootShell({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="it">
|
||||||
<head>
|
<head>
|
||||||
<HeadContent />
|
<HeadContent />
|
||||||
|
{/* Tema scuro allineato al sistema. Sta nell'`head` come script bloccante
|
||||||
|
perche' applicato dopo il primo paint darebbe un lampo di chiaro. */}
|
||||||
|
<script
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html:
|
||||||
|
"(function(){try{var m=matchMedia('(prefers-color-scheme: dark)');" +
|
||||||
|
"var a=function(){document.documentElement.classList.toggle('dark',m.matches)};" +
|
||||||
|
"a();m.addEventListener('change',a)}catch(e){}})()",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{children}
|
{children}
|
||||||
@@ -194,7 +220,7 @@ function AppShell() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mx-auto min-h-screen max-w-md bg-background pb-24">
|
<div className="mx-auto min-h-screen max-w-md bg-background pb-[calc(6rem+env(safe-area-inset-bottom))]">
|
||||||
{/* Required: nested routes render here. Removing <Outlet /> breaks all child routes. */}
|
{/* Required: nested routes render here. Removing <Outlet /> breaks all child routes. */}
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ function messaggioErrore(e: unknown, fallback: string): string {
|
|||||||
function Campo({ label, children }: { label: string; children: React.ReactNode }) {
|
function Campo({ label, children }: { label: string; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<label className="block">
|
<label className="block">
|
||||||
<span className="text-[10px] font-bold uppercase tracking-wide text-muted-foreground">
|
<span className="text-3xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
<span className="mt-1 block">{children}</span>
|
<span className="mt-1 block">{children}</span>
|
||||||
@@ -375,13 +375,13 @@ function Documento({
|
|||||||
onClick={scarica}
|
onClick={scarica}
|
||||||
disabled={!path || inCorso}
|
disabled={!path || inCorso}
|
||||||
className={cn(
|
className={cn(
|
||||||
"premi flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-bold uppercase disabled:opacity-60",
|
"premi flex items-center gap-1.5 rounded-full px-2.5 py-1 text-2xs font-bold uppercase disabled:opacity-60",
|
||||||
statoClasse[stato],
|
statoClasse[stato],
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{inCorso ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : icona}
|
{inCorso ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : icona}
|
||||||
{label}
|
{label}
|
||||||
{path ? <Download className="h-3 w-3" /> : null}
|
{path ? <Download className="h-3.5 w-3.5" /> : null}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -464,7 +464,7 @@ function SchedaGiocatore({
|
|||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-bold uppercase",
|
"flex items-center gap-1.5 rounded-full px-2.5 py-1 text-2xs font-bold uppercase",
|
||||||
statoClasse[g.numeroTessera ? "presente" : "assente"],
|
statoClasse[g.numeroTessera ? "presente" : "assente"],
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -129,12 +129,8 @@ function Calendario() {
|
|||||||
: [];
|
: [];
|
||||||
|
|
||||||
// Prossimi 4 eventi da oggi in avanti (indipendenti dal mese selezionato nella griglia).
|
// Prossimi 4 eventi da oggi in avanti (indipendenti dal mese selezionato nella griglia).
|
||||||
const oggiIso = oggi
|
const oggiIso = oggi ? `${oggi.anno}-${pad2(oggi.mese + 1)}-${pad2(oggi.giorno)}` : null;
|
||||||
? `${oggi.anno}-${pad2(oggi.mese + 1)}-${pad2(oggi.giorno)}`
|
const prossimiEventi = oggiIso ? eventi.filter((e) => e.data >= oggiIso).slice(0, 4) : [];
|
||||||
: null;
|
|
||||||
const prossimiEventi = oggiIso
|
|
||||||
? eventi.filter((e) => e.data >= oggiIso).slice(0, 4)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -183,7 +179,7 @@ function Calendario() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-7 gap-1 text-center text-[11px] font-bold text-muted-foreground">
|
<div className="grid grid-cols-7 gap-1 text-center text-2xs font-bold text-muted-foreground">
|
||||||
{giorniIT.map((g, i) => (
|
{giorniIT.map((g, i) => (
|
||||||
<span key={i}>{g}</span>
|
<span key={i}>{g}</span>
|
||||||
))}
|
))}
|
||||||
@@ -244,7 +240,7 @@ function Calendario() {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 flex flex-wrap gap-3 text-[11px] font-semibold text-muted-foreground">
|
<div className="mt-4 flex flex-wrap gap-3 text-2xs font-semibold text-muted-foreground">
|
||||||
<span className="inline-flex items-center gap-1">
|
<span className="inline-flex items-center gap-1">
|
||||||
<i className="h-2.5 w-2.5 rounded-full bg-accent" /> Partita
|
<i className="h-2.5 w-2.5 rounded-full bg-accent" /> Partita
|
||||||
</span>
|
</span>
|
||||||
@@ -306,7 +302,7 @@ function Calendario() {
|
|||||||
<DrawerTitle className="font-display text-2xl uppercase tracking-wide">
|
<DrawerTitle className="font-display text-2xl uppercase tracking-wide">
|
||||||
{giornoSelezionato ? `${giornoSelezionato} ${mesiIT[mese]}` : "Eventi"}
|
{giornoSelezionato ? `${giornoSelezionato} ${mesiIT[mese]}` : "Eventi"}
|
||||||
</DrawerTitle>
|
</DrawerTitle>
|
||||||
<DrawerClose className="absolute right-0 top-1 grid h-8 w-8 place-items-center rounded-full bg-secondary text-foreground transition-transform active:scale-90">
|
<DrawerClose className="area-tocco absolute right-0 top-1 grid h-8 w-8 place-items-center rounded-full bg-secondary text-foreground transition-transform active:scale-90">
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
<span className="sr-only">Chiudi</span>
|
<span className="sr-only">Chiudi</span>
|
||||||
</DrawerClose>
|
</DrawerClose>
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ function Classifica() {
|
|||||||
|
|
||||||
<Section titolo="Classifica">
|
<Section titolo="Classifica">
|
||||||
<div className="overflow-hidden rounded-3xl bg-card shadow-card">
|
<div className="overflow-hidden rounded-3xl bg-card shadow-card">
|
||||||
<div className="grid grid-cols-[2rem_minmax(0,1fr)_2rem_2.5rem_2.5rem] gap-2 border-b border-border px-3 py-2 text-[10px] font-bold uppercase text-muted-foreground">
|
<div className="grid grid-cols-[2rem_minmax(0,1fr)_2rem_2.5rem_2.5rem] gap-2 border-b border-border px-3 py-2 text-3xs font-bold uppercase text-muted-foreground">
|
||||||
<span>#</span>
|
<span>#</span>
|
||||||
<span>Squadra</span>
|
<span>Squadra</span>
|
||||||
<span className="text-center">G</span>
|
<span className="text-center">G</span>
|
||||||
@@ -141,7 +141,7 @@ function Classifica() {
|
|||||||
{m.casa ? "CRAP Volley" : m.avversario} vs{" "}
|
{m.casa ? "CRAP Volley" : m.avversario} vs{" "}
|
||||||
{m.casa ? m.avversario : "CRAP Volley"}
|
{m.casa ? m.avversario : "CRAP Volley"}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[11px] text-muted-foreground">
|
<p className="text-2xs text-muted-foreground">
|
||||||
{formatData(m.data)} · MVP {m.mvp || "da votare"}
|
{formatData(m.data)} · MVP {m.mvp || "da votare"}
|
||||||
{m.scout ? " · scoutata" : ""}
|
{m.scout ? " · scoutata" : ""}
|
||||||
</p>
|
</p>
|
||||||
@@ -162,7 +162,7 @@ function Classifica() {
|
|||||||
<span
|
<span
|
||||||
key={i}
|
key={i}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-lg px-2 py-1 text-[11px] font-semibold tabular-nums",
|
"rounded-lg px-2 py-1 text-2xs font-semibold tabular-nums",
|
||||||
p[0] > p[1] ? "bg-secondary" : "bg-muted text-muted-foreground",
|
p[0] > p[1] ? "bg-secondary" : "bg-muted text-muted-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -170,7 +170,7 @@ function Classifica() {
|
|||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{eventoId && !m.mvp ? (
|
{eventoId && !m.mvp ? (
|
||||||
<span className="ml-auto inline-flex items-center gap-0.5 text-[11px] font-bold uppercase text-accent">
|
<span className="ml-auto inline-flex items-center gap-0.5 text-2xs font-bold uppercase text-accent">
|
||||||
Vota MVP <ChevronRight className="h-3.5 w-3.5" />
|
Vota MVP <ChevronRight className="h-3.5 w-3.5" />
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ function GestioneEventi() {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => aggiorna(daCategoria(t.id))}
|
onClick={() => aggiorna(daCategoria(t.id))}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-full py-2 text-[11px] font-bold uppercase transition-colors",
|
"rounded-full py-2 text-2xs font-bold uppercase transition-colors",
|
||||||
categoriaEvento(bozza) === t.id
|
categoriaEvento(bozza) === t.id
|
||||||
? "bg-card shadow-card text-foreground"
|
? "bg-card shadow-card text-foreground"
|
||||||
: "text-muted-foreground",
|
: "text-muted-foreground",
|
||||||
@@ -291,7 +291,7 @@ function GestioneEventi() {
|
|||||||
>
|
>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="truncate text-sm font-bold leading-tight">{e.titolo}</p>
|
<p className="truncate text-sm font-bold leading-tight">{e.titolo}</p>
|
||||||
<p className="text-[11px] text-muted-foreground">
|
<p className="text-2xs text-muted-foreground">
|
||||||
{formatData(e.data)} · {e.ora} · {e.luogo || "luogo da definire"}
|
{formatData(e.data)} · {e.ora} · {e.luogo || "luogo da definire"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -323,7 +323,7 @@ function GestioneEventi() {
|
|||||||
function Campo({ label, children }: { label: string; children: React.ReactNode }) {
|
function Campo({ label, children }: { label: string; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<label className="block">
|
<label className="block">
|
||||||
<span className="text-[10px] font-bold uppercase tracking-wide text-muted-foreground">
|
<span className="text-3xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
<span className="mt-1 block">{children}</span>
|
<span className="mt-1 block">{children}</span>
|
||||||
@@ -345,7 +345,7 @@ function Interruttore({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-full px-3 py-1.5 text-[11px] font-bold uppercase transition-colors",
|
"rounded-full px-3 py-1.5 text-2xs font-bold uppercase transition-colors",
|
||||||
attivo ? "bg-accent text-accent-foreground" : "bg-secondary text-muted-foreground",
|
attivo ? "bg-accent text-accent-foreground" : "bg-secondary text-muted-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
+12
-9
@@ -2,7 +2,7 @@ import { createFileRoute, Link } from "@tanstack/react-router";
|
|||||||
import { Flame, ChevronRight } from "lucide-react";
|
import { Flame, ChevronRight } from "lucide-react";
|
||||||
import { EventoCard, linkPerEvento } from "@/components/crapp/EventoCard";
|
import { EventoCard, linkPerEvento } from "@/components/crapp/EventoCard";
|
||||||
import { PromemoriaPalloni } from "@/components/crapp/PromemoriaPalloni";
|
import { PromemoriaPalloni } from "@/components/crapp/PromemoriaPalloni";
|
||||||
import { Section, StatTile, TeamLogo } from "@/components/crapp/ui-bits";
|
import { ScheletroPagina, Section, StatTile, TeamLogo } from "@/components/crapp/ui-bits";
|
||||||
import { CompletaProfilo } from "@/components/crapp/ProfiloAmministrativo";
|
import { CompletaProfilo } from "@/components/crapp/ProfiloAmministrativo";
|
||||||
import { Reveal } from "@/components/motion/Reveal";
|
import { Reveal } from "@/components/motion/Reveal";
|
||||||
import { Barra } from "@/components/motion/Barra";
|
import { Barra } from "@/components/motion/Barra";
|
||||||
@@ -59,11 +59,14 @@ function Index() {
|
|||||||
? (eventi.find((e) => e.tipo === "partita" && e.data === ultima.data) ?? null)
|
? (eventi.find((e) => e.tipo === "partita" && e.data === ultima.data) ?? null)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (!giocatore) return null;
|
if (!giocatore) return <ScheletroPagina />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Reveal as="section" className="bg-hero px-5 pb-10 pt-7 text-primary-foreground">
|
<Reveal
|
||||||
|
as="section"
|
||||||
|
className="bg-hero px-5 pb-10 pt-[calc(1.75rem+env(safe-area-inset-top))] text-primary-foreground"
|
||||||
|
>
|
||||||
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-4">
|
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-4">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary-foreground/60">
|
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary-foreground/60">
|
||||||
@@ -77,20 +80,20 @@ function Index() {
|
|||||||
<div className="mt-6 grid grid-cols-3 gap-2 text-center">
|
<div className="mt-6 grid grid-cols-3 gap-2 text-center">
|
||||||
<div className="rounded-2xl bg-primary-foreground/10 p-3">
|
<div className="rounded-2xl bg-primary-foreground/10 p-3">
|
||||||
<p className="font-display text-2xl leading-none">{noi ? `${noi.pos}º` : "—"}</p>
|
<p className="font-display text-2xl leading-none">{noi ? `${noi.pos}º` : "—"}</p>
|
||||||
<p className="text-[10px] uppercase text-primary-foreground/60">In classifica</p>
|
<p className="text-3xs uppercase text-primary-foreground/60">In classifica</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl bg-primary-foreground/10 p-3">
|
<div className="rounded-2xl bg-primary-foreground/10 p-3">
|
||||||
<p className="font-display text-2xl leading-none">
|
<p className="font-display text-2xl leading-none">
|
||||||
{noi ? `${noi.vinte}-${noi.perse}` : "—"}
|
{noi ? `${noi.vinte}-${noi.perse}` : "—"}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] uppercase text-primary-foreground/60">Bilancio</p>
|
<p className="text-3xs uppercase text-primary-foreground/60">Bilancio</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl bg-primary-foreground/10 p-3">
|
<div className="rounded-2xl bg-primary-foreground/10 p-3">
|
||||||
<p className="inline-flex items-center gap-1 font-display text-2xl leading-none">
|
<p className="inline-flex items-center gap-1 font-display text-2xl leading-none">
|
||||||
<Flame className="h-4 w-4 text-accent" />
|
<Flame className="h-4 w-4 text-accent" />
|
||||||
{giocatore.streak}
|
{giocatore.streak}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] uppercase text-primary-foreground/60">Streak</p>
|
<p className="text-3xs uppercase text-primary-foreground/60">Streak</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
@@ -161,7 +164,7 @@ function Index() {
|
|||||||
{ultima.parziali.map((p, i) => (
|
{ultima.parziali.map((p, i) => (
|
||||||
<span
|
<span
|
||||||
key={i}
|
key={i}
|
||||||
className="rounded-lg bg-secondary px-2 py-1 text-[11px] font-semibold tabular-nums"
|
className="rounded-lg bg-secondary px-2 py-1 text-2xs font-semibold tabular-nums"
|
||||||
>
|
>
|
||||||
{p[0]}-{p[1]}
|
{p[0]}-{p[1]}
|
||||||
</span>
|
</span>
|
||||||
@@ -196,7 +199,7 @@ function Index() {
|
|||||||
to="/squadra"
|
to="/squadra"
|
||||||
className="inline-flex items-center text-xs font-semibold text-accent"
|
className="inline-flex items-center text-xs font-semibold text-accent"
|
||||||
>
|
>
|
||||||
Tutti <ChevronRight className="h-3 w-3" />
|
Tutti <ChevronRight className="h-3.5 w-3.5" />
|
||||||
</Link>
|
</Link>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -213,7 +216,7 @@ function Index() {
|
|||||||
<p className="mt-1 text-xs font-semibold text-accent">
|
<p className="mt-1 text-xs font-semibold text-accent">
|
||||||
{microcopyObiettivo(obiettivo)}
|
{microcopyObiettivo(obiettivo)}
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 text-[11px] text-muted-foreground">{obiettivo.impatto}</p>
|
<p className="mt-1 text-2xs text-muted-foreground">{obiettivo.impatto}</p>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</Section>
|
</Section>
|
||||||
|
|||||||
@@ -220,13 +220,13 @@ function PartitaDetail() {
|
|||||||
].map((t) => (
|
].map((t) => (
|
||||||
<div key={t.l} className="rounded-2xl bg-secondary p-2.5">
|
<div key={t.l} className="rounded-2xl bg-secondary p-2.5">
|
||||||
<p className="font-display text-xl leading-none">{t.v}</p>
|
<p className="font-display text-xl leading-none">{t.v}</p>
|
||||||
<p className="mt-1 text-[10px] font-semibold uppercase text-muted-foreground">
|
<p className="mt-1 text-3xs font-semibold uppercase text-muted-foreground">
|
||||||
{t.l}
|
{t.l}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-4 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="mt-4 text-2xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
Dettaglio giocatori (uso interno allenatori)
|
Dettaglio giocatori (uso interno allenatori)
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-2 space-y-1">
|
<div className="mt-2 space-y-1">
|
||||||
|
|||||||
+11
-7
@@ -3,7 +3,13 @@ import { useEffect, useRef, useState } from "react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Flame, Camera, Trash2, Bell, LogOut, ShieldCheck, Bug, Lightbulb } from "lucide-react";
|
import { Flame, Camera, Trash2, Bell, LogOut, ShieldCheck, Bug, Lightbulb } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { PageHeader, Section, SezioneTendina, StatTile } from "@/components/crapp/ui-bits";
|
import {
|
||||||
|
PageHeader,
|
||||||
|
ScheletroPagina,
|
||||||
|
Section,
|
||||||
|
SezioneTendina,
|
||||||
|
StatTile,
|
||||||
|
} from "@/components/crapp/ui-bits";
|
||||||
import { Avatar } from "@/components/crapp/Avatar";
|
import { Avatar } from "@/components/crapp/Avatar";
|
||||||
import {
|
import {
|
||||||
caricaAvatar,
|
caricaAvatar,
|
||||||
@@ -66,7 +72,7 @@ function Profilo() {
|
|||||||
.catch(() => setNotifiche(false));
|
.catch(() => setNotifiche(false));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!g) return null;
|
if (!g) return <ScheletroPagina />;
|
||||||
|
|
||||||
async function cambiaNotifiche() {
|
async function cambiaNotifiche() {
|
||||||
if (!g || inCorso) return;
|
if (!g || inCorso) return;
|
||||||
@@ -97,9 +103,7 @@ function Profilo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const percPresenze = g.totaliEventi
|
const percPresenze = g.totaliEventi ? Math.round((g.presenze / g.totaliEventi) * 100) : 0;
|
||||||
? Math.round((g.presenze / g.totaliEventi) * 100)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
async function onFile(e: React.ChangeEvent<HTMLInputElement>) {
|
async function onFile(e: React.ChangeEvent<HTMLInputElement>) {
|
||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0];
|
||||||
@@ -135,7 +139,7 @@ function Profilo() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => inputRef.current?.click()}
|
onClick={() => inputRef.current?.click()}
|
||||||
className="absolute -bottom-1 -right-1 grid h-8 w-8 place-items-center rounded-full bg-accent-grad text-accent-foreground shadow-pop"
|
className="area-tocco absolute -bottom-1 -right-1 grid h-8 w-8 place-items-center rounded-full bg-accent-grad text-accent-foreground shadow-pop"
|
||||||
aria-label="Cambia foto"
|
aria-label="Cambia foto"
|
||||||
>
|
>
|
||||||
<Camera className="h-4 w-4" />
|
<Camera className="h-4 w-4" />
|
||||||
@@ -217,7 +221,7 @@ function Profilo() {
|
|||||||
>
|
>
|
||||||
<span className="min-w-0">
|
<span className="min-w-0">
|
||||||
<span className="block truncate">Notifiche turno palloni</span>
|
<span className="block truncate">Notifiche turno palloni</span>
|
||||||
<span className="block text-[11px] text-muted-foreground">
|
<span className="block text-2xs text-muted-foreground">
|
||||||
{supportate
|
{supportate
|
||||||
? notifiche
|
? notifiche
|
||||||
? "Attive su questo dispositivo"
|
? "Attive su questo dispositivo"
|
||||||
|
|||||||
+14
-14
@@ -320,9 +320,9 @@ function ScoutBoard({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<header className="sticky top-0 z-30 bg-hero px-5 pb-4 pt-6 text-primary-foreground">
|
<header className="sticky top-0 z-30 bg-hero px-5 pb-4 pt-[calc(1.5rem+env(safe-area-inset-top))] text-primary-foreground">
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<span className="inline-flex items-center gap-1.5 rounded-full bg-primary-foreground/10 px-2.5 py-1 text-[10px] font-bold uppercase tracking-wide">
|
<span className="inline-flex items-center gap-1.5 rounded-full bg-primary-foreground/10 px-2.5 py-1 text-3xs font-bold uppercase tracking-wide">
|
||||||
<Radio className="h-3 w-3 text-accent" /> Scout live · Set {setCorrente}
|
<Radio className="h-3 w-3 text-accent" /> Scout live · Set {setCorrente}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -330,14 +330,14 @@ function ScoutBoard({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={annulla}
|
onClick={annulla}
|
||||||
disabled={azioniSet.length === 0}
|
disabled={azioniSet.length === 0}
|
||||||
className="inline-flex items-center gap-1 rounded-full bg-primary-foreground/10 px-3 py-1.5 text-xs font-bold disabled:opacity-40"
|
className="area-tocco inline-flex items-center gap-1 rounded-full bg-primary-foreground/10 px-3 py-1.5 text-xs font-bold disabled:opacity-40"
|
||||||
>
|
>
|
||||||
<Undo2 className="h-3.5 w-3.5" /> Annulla
|
<Undo2 className="h-3.5 w-3.5" /> Annulla
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onFine}
|
onClick={onFine}
|
||||||
className="inline-flex items-center gap-1 rounded-full bg-primary-foreground/10 px-3 py-1.5 text-xs font-bold"
|
className="area-tocco inline-flex items-center gap-1 rounded-full bg-primary-foreground/10 px-3 py-1.5 text-xs font-bold"
|
||||||
>
|
>
|
||||||
<LogOut className="h-3.5 w-3.5" /> Rilascia
|
<LogOut className="h-3.5 w-3.5" /> Rilascia
|
||||||
</button>
|
</button>
|
||||||
@@ -346,14 +346,14 @@ function ScoutBoard({
|
|||||||
|
|
||||||
<div className="mt-3 grid grid-cols-[1fr_auto_1fr] items-center gap-2 text-center">
|
<div className="mt-3 grid grid-cols-[1fr_auto_1fr] items-center gap-2 text-center">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="truncate text-[11px] uppercase text-primary-foreground/60">CRAP Volley</p>
|
<p className="truncate text-2xs uppercase text-primary-foreground/60">CRAP Volley</p>
|
||||||
<p className="font-display text-5xl leading-none text-accent">{puntiNoi}</p>
|
<p className="font-display text-5xl leading-none text-accent">{puntiNoi}</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="font-display text-2xl leading-none text-primary-foreground/50">
|
<p className="font-display text-2xl leading-none text-primary-foreground/50">
|
||||||
{setNostri}-{setLoro}
|
{setNostri}-{setLoro}
|
||||||
</p>
|
</p>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="truncate text-[11px] uppercase text-primary-foreground/60">
|
<p className="truncate text-2xs uppercase text-primary-foreground/60">
|
||||||
{avversario || "Avversario"}
|
{avversario || "Avversario"}
|
||||||
</p>
|
</p>
|
||||||
<p className="font-display text-5xl leading-none">{puntiLoro}</p>
|
<p className="font-display text-5xl leading-none">{puntiLoro}</p>
|
||||||
@@ -365,7 +365,7 @@ function ScoutBoard({
|
|||||||
{setChiusi.map((p, i) => (
|
{setChiusi.map((p, i) => (
|
||||||
<span
|
<span
|
||||||
key={i}
|
key={i}
|
||||||
className="rounded-lg bg-primary-foreground/10 px-2 py-0.5 text-[11px] font-semibold tabular-nums"
|
className="rounded-lg bg-primary-foreground/10 px-2 py-0.5 text-2xs font-semibold tabular-nums"
|
||||||
>
|
>
|
||||||
{p[0]}-{p[1]}
|
{p[0]}-{p[1]}
|
||||||
</span>
|
</span>
|
||||||
@@ -375,7 +375,7 @@ function ScoutBoard({
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section className="px-5 pt-4">
|
<section className="px-5 pt-4">
|
||||||
<p className="mb-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="mb-2 text-2xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
1. Tocca il giocatore
|
1. Tocca il giocatore
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
@@ -393,10 +393,10 @@ function ScoutBoard({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<p className="font-display text-2xl leading-none">{g.numero}</p>
|
<p className="font-display text-2xl leading-none">{g.numero}</p>
|
||||||
<p className="mt-1 text-[10px] font-bold leading-tight">{g.nome}</p>
|
<p className="mt-1 text-3xs font-bold leading-tight">{g.nome}</p>
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-[10px] tabular-nums",
|
"text-3xs tabular-nums",
|
||||||
attivo ? "text-accent-foreground/70" : "text-muted-foreground",
|
attivo ? "text-accent-foreground/70" : "text-muted-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -409,7 +409,7 @@ function ScoutBoard({
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="px-5 pt-4">
|
<section className="px-5 pt-4">
|
||||||
<p className="mb-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="mb-2 text-2xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
2. Tocca l'azione
|
2. Tocca l'azione
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
@@ -447,7 +447,7 @@ function ScoutBoard({
|
|||||||
|
|
||||||
<section className="px-5 pt-4">
|
<section className="px-5 pt-4">
|
||||||
<div className="rounded-3xl bg-card p-3 shadow-card">
|
<div className="rounded-3xl bg-card p-3 shadow-card">
|
||||||
<p className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="text-2xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
Ultime azioni
|
Ultime azioni
|
||||||
</p>
|
</p>
|
||||||
{ultime.length === 0 ? (
|
{ultime.length === 0 ? (
|
||||||
@@ -467,7 +467,7 @@ function ScoutBoard({
|
|||||||
<span className="truncate">{g ? `#${g.numero} ${g.nome}` : "Avversario"}</span>
|
<span className="truncate">{g ? `#${g.numero} ${g.nome}` : "Avversario"}</span>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"shrink-0 rounded-full px-2 py-0.5 text-[10px] font-bold uppercase",
|
"shrink-0 rounded-full px-2 py-0.5 text-3xs font-bold uppercase",
|
||||||
azioniMeta[a.tipo].className,
|
azioniMeta[a.tipo].className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -483,7 +483,7 @@ function ScoutBoard({
|
|||||||
|
|
||||||
<section className="px-5 pt-4">
|
<section className="px-5 pt-4">
|
||||||
<div className="rounded-3xl bg-card p-4 shadow-card">
|
<div className="rounded-3xl bg-card p-4 shadow-card">
|
||||||
<label className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<label className="text-2xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
Avversario
|
Avversario
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
|||||||
+15
-22
@@ -30,7 +30,7 @@ import {
|
|||||||
|
|
||||||
function RuoloBadge({ ruolo }: { ruolo: string }) {
|
function RuoloBadge({ ruolo }: { ruolo: string }) {
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center rounded-full bg-accent px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-accent-foreground">
|
<span className="inline-flex items-center rounded-full bg-accent px-2 py-0.5 text-3xs font-bold uppercase tracking-wide text-accent-foreground">
|
||||||
{ruolo}
|
{ruolo}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -125,7 +125,7 @@ function Squadra() {
|
|||||||
<span className="block truncate text-sm font-bold leading-tight">{g.nome}</span>
|
<span className="block truncate text-sm font-bold leading-tight">{g.nome}</span>
|
||||||
<span className="mt-1 flex items-center gap-2">
|
<span className="mt-1 flex items-center gap-2">
|
||||||
<RuoloBadge ruolo={g.ruolo} />
|
<RuoloBadge ruolo={g.ruolo} />
|
||||||
<span className="text-[11px] text-muted-foreground">
|
<span className="text-2xs text-muted-foreground">
|
||||||
{g.numero ? `#${g.numero}` : "n° da definire"}
|
{g.numero ? `#${g.numero}` : "n° da definire"}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -136,7 +136,7 @@ function Squadra() {
|
|||||||
const Icon = b.def.icon;
|
const Icon = b.def.icon;
|
||||||
return (
|
return (
|
||||||
<span key={b.def.id} className="cursor-help" title={b.def.nome}>
|
<span key={b.def.id} className="cursor-help" title={b.def.nome}>
|
||||||
<Icon className={cn("h-3 w-3", gradoMeta[b.grado!].text)} />
|
<Icon className={cn("h-3.5 w-3.5", gradoMeta[b.grado!].text)} />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -170,14 +170,14 @@ function Squadra() {
|
|||||||
].map((s) => (
|
].map((s) => (
|
||||||
<div key={s.l} className="rounded-2xl bg-secondary p-2.5 text-center">
|
<div key={s.l} className="rounded-2xl bg-secondary p-2.5 text-center">
|
||||||
<p className="font-display text-xl leading-none">{s.v}</p>
|
<p className="font-display text-xl leading-none">{s.v}</p>
|
||||||
<p className="mt-1 text-[10px] font-semibold uppercase text-muted-foreground">
|
<p className="mt-1 text-3xs font-semibold uppercase text-muted-foreground">
|
||||||
{s.l}
|
{s.l}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="mt-4 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="mt-4 text-2xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
Badge sbloccati · {collezioneBadge(g).ottenuti}/{collezioneBadge(g).totali}
|
Badge sbloccati · {collezioneBadge(g).ottenuti}/{collezioneBadge(g).totali}
|
||||||
</p>
|
</p>
|
||||||
{sbloccati.length === 0 ? (
|
{sbloccati.length === 0 ? (
|
||||||
@@ -194,18 +194,13 @@ function Squadra() {
|
|||||||
<div className={cn("rounded-2xl p-2.5 ring-1", meta.bg, meta.ring)}>
|
<div className={cn("rounded-2xl p-2.5 ring-1", meta.bg, meta.ring)}>
|
||||||
<Icon className={cn("h-4 w-4", meta.text)} />
|
<Icon className={cn("h-4 w-4", meta.text)} />
|
||||||
<p className="mt-1 text-xs font-bold leading-tight">{b.def.nome}</p>
|
<p className="mt-1 text-xs font-bold leading-tight">{b.def.nome}</p>
|
||||||
<p className="text-[10px] text-muted-foreground">
|
<p className="text-3xs text-muted-foreground">
|
||||||
{b.valore} {b.def.unita}
|
{b.valore} {b.def.unita}
|
||||||
{b.prossimaSoglia
|
{b.prossimaSoglia
|
||||||
? ` · ${b.prossimaSoglia} per ${gradoMeta[b.prossimo!].label.toLowerCase()}`
|
? ` · ${b.prossimaSoglia} per ${gradoMeta[b.prossimo!].label.toLowerCase()}`
|
||||||
: ""}
|
: ""}
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p className={cn("mt-1.5 text-3xs font-bold uppercase", meta.text)}>
|
||||||
className={cn(
|
|
||||||
"mt-1.5 text-[10px] font-bold uppercase",
|
|
||||||
meta.text,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{meta.label}
|
{meta.label}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -241,7 +236,7 @@ function Squadra() {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => setCriterio(c.id)}
|
onClick={() => setCriterio(c.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"min-w-0 flex-1 truncate rounded-full px-1.5 py-1.5 text-center text-[10px] font-bold uppercase transition-colors",
|
"min-w-0 flex-1 truncate rounded-full px-1.5 py-1.5 text-center text-3xs font-bold uppercase transition-colors",
|
||||||
criterio === c.id
|
criterio === c.id
|
||||||
? "bg-accent text-accent-foreground shadow-pop"
|
? "bg-accent text-accent-foreground shadow-pop"
|
||||||
: "bg-secondary text-muted-foreground",
|
: "bg-secondary text-muted-foreground",
|
||||||
@@ -264,7 +259,7 @@ function Squadra() {
|
|||||||
{g.nome}
|
{g.nome}
|
||||||
{i === 0 ? <Crown className="ml-1 inline h-3.5 w-3.5 text-warning" /> : null}
|
{i === 0 ? <Crown className="ml-1 inline h-3.5 w-3.5 text-warning" /> : null}
|
||||||
</p>
|
</p>
|
||||||
<p className="truncate text-[11px] text-muted-foreground">
|
<p className="truncate text-2xs text-muted-foreground">
|
||||||
#{g.numero} · {g.ruolo} · {g.streak} presenze consecutive
|
#{g.numero} · {g.ruolo} · {g.streak} presenze consecutive
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -290,7 +285,7 @@ function Squadra() {
|
|||||||
<div className="mb-3 rounded-3xl bg-hero p-4 text-primary-foreground shadow-card">
|
<div className="mb-3 rounded-3xl bg-hero p-4 text-primary-foreground shadow-card">
|
||||||
<div className="flex items-end justify-between gap-3">
|
<div className="flex items-end justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-[11px] font-semibold uppercase tracking-wide text-primary-foreground/60">
|
<p className="text-2xs font-semibold uppercase tracking-wide text-primary-foreground/60">
|
||||||
Progresso collettivo
|
Progresso collettivo
|
||||||
</p>
|
</p>
|
||||||
<p className="font-display text-4xl leading-none">
|
<p className="font-display text-4xl leading-none">
|
||||||
@@ -326,7 +321,7 @@ function Squadra() {
|
|||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-full px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide",
|
"rounded-full px-2 py-0.5 text-3xs font-bold uppercase tracking-wide",
|
||||||
fatto
|
fatto
|
||||||
? "bg-success text-success-foreground"
|
? "bg-success text-success-foreground"
|
||||||
: "bg-secondary text-muted-foreground",
|
: "bg-secondary text-muted-foreground",
|
||||||
@@ -341,7 +336,7 @@ function Squadra() {
|
|||||||
{o.scadenza ? ` · entro il ${formatData(o.scadenza)}` : ""}
|
{o.scadenza ? ` · entro il ${formatData(o.scadenza)}` : ""}
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 text-xs font-semibold text-accent">{microcopyObiettivo(o)}</p>
|
<p className="mt-1 text-xs font-semibold text-accent">{microcopyObiettivo(o)}</p>
|
||||||
<p className="mt-0.5 text-[11px] text-muted-foreground">{o.impatto}</p>
|
<p className="mt-0.5 text-2xs text-muted-foreground">{o.impatto}</p>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -358,9 +353,7 @@ function Squadra() {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Icon className="h-5 w-5 text-accent" />
|
<Icon className="h-5 w-5 text-accent" />
|
||||||
<p className="text-sm font-bold leading-tight">{b.nome}</p>
|
<p className="text-sm font-bold leading-tight">{b.nome}</p>
|
||||||
<p className="ml-auto text-[10px] text-muted-foreground">
|
<p className="ml-auto text-3xs text-muted-foreground">{descrizioneSoglie(b)}</p>
|
||||||
{descrizioneSoglie(b)}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 grid grid-cols-3 gap-1.5">
|
<div className="mt-2 grid grid-cols-3 gap-1.5">
|
||||||
{gradiOrdine.map((grado) => {
|
{gradiOrdine.map((grado) => {
|
||||||
@@ -376,11 +369,11 @@ function Squadra() {
|
|||||||
key={grado}
|
key={grado}
|
||||||
className={cn("rounded-2xl p-2 text-center ring-1", meta.bg, meta.ring)}
|
className={cn("rounded-2xl p-2 text-center ring-1", meta.bg, meta.ring)}
|
||||||
>
|
>
|
||||||
<p className={cn("text-[10px] font-bold uppercase", meta.text)}>
|
<p className={cn("text-3xs font-bold uppercase", meta.text)}>
|
||||||
{meta.label}
|
{meta.label}
|
||||||
</p>
|
</p>
|
||||||
<p className="font-display text-lg leading-none">{b.soglie[grado]}</p>
|
<p className="font-display text-lg leading-none">{b.soglie[grado]}</p>
|
||||||
<p className="text-[10px] text-muted-foreground">
|
<p className="text-3xs text-muted-foreground">
|
||||||
{quanti}/{rosa.length}
|
{quanti}/{rosa.length}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+304
-29
@@ -7,6 +7,57 @@
|
|||||||
@theme {
|
@theme {
|
||||||
--font-display: "Bebas Neue", ui-sans-serif, sans-serif;
|
--font-display: "Bebas Neue", ui-sans-serif, sans-serif;
|
||||||
--font-sans: "Barlow", ui-sans-serif, system-ui, sans-serif;
|
--font-sans: "Barlow", ui-sans-serif, system-ui, sans-serif;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scala tipografica: dimensioni in rem (scalano con l'impostazione di testo
|
||||||
|
* del sistema) con crenatura e interlinea *specifiche per taglia*.
|
||||||
|
* Regola: piu' il testo e' grande, piu' le lettere vanno strette e le righe
|
||||||
|
* vicine; piu' e' piccolo, piu' serve aria fra le lettere per leggerlo.
|
||||||
|
* Non usare `text-[Npx]`: aggiungere semmai una taglia qui.
|
||||||
|
*
|
||||||
|
* Icone (classi Tailwind sui componenti lucide): quattro misure, nessun
|
||||||
|
* valore fuori scala — h-3.5 inline nel testo piccolo, h-4 standard,
|
||||||
|
* h-5 nelle barre di navigazione, h-6 nei blocchi grandi.
|
||||||
|
*/
|
||||||
|
--text-3xs: 0.625rem; /* 10px */
|
||||||
|
--text-3xs--line-height: 1.4;
|
||||||
|
--text-3xs--letter-spacing: 0.015em;
|
||||||
|
--text-2xs: 0.6875rem; /* 11px */
|
||||||
|
--text-2xs--line-height: 1.4;
|
||||||
|
--text-2xs--letter-spacing: 0.01em;
|
||||||
|
--text-xs: 0.75rem;
|
||||||
|
--text-xs--line-height: 1.4;
|
||||||
|
--text-xs--letter-spacing: 0.005em;
|
||||||
|
--text-sm: 0.875rem;
|
||||||
|
--text-sm--line-height: 1.45;
|
||||||
|
--text-sm--letter-spacing: 0em;
|
||||||
|
--text-base: 1rem;
|
||||||
|
--text-base--line-height: 1.5;
|
||||||
|
--text-base--letter-spacing: 0em;
|
||||||
|
--text-lg: 1.125rem;
|
||||||
|
--text-lg--line-height: 1.4;
|
||||||
|
--text-lg--letter-spacing: -0.005em;
|
||||||
|
--text-xl: 1.25rem;
|
||||||
|
--text-xl--line-height: 1.3;
|
||||||
|
--text-xl--letter-spacing: -0.01em;
|
||||||
|
--text-2xl: 1.5rem;
|
||||||
|
--text-2xl--line-height: 1.2;
|
||||||
|
--text-2xl--letter-spacing: -0.015em;
|
||||||
|
--text-3xl: 1.875rem;
|
||||||
|
--text-3xl--line-height: 1.1;
|
||||||
|
--text-3xl--letter-spacing: -0.02em;
|
||||||
|
--text-4xl: 2.25rem;
|
||||||
|
--text-4xl--line-height: 1.05;
|
||||||
|
--text-4xl--letter-spacing: -0.022em;
|
||||||
|
--text-5xl: 3rem;
|
||||||
|
--text-5xl--line-height: 1;
|
||||||
|
--text-5xl--letter-spacing: -0.025em;
|
||||||
|
--text-6xl: 3.75rem;
|
||||||
|
--text-6xl--line-height: 1;
|
||||||
|
--text-6xl--letter-spacing: -0.028em;
|
||||||
|
--text-7xl: 4.5rem;
|
||||||
|
--text-7xl--line-height: 1;
|
||||||
|
--text-7xl--letter-spacing: -0.03em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -105,8 +156,15 @@
|
|||||||
oklch(0.45 0.19 27)
|
oklch(0.45 0.19 27)
|
||||||
);
|
);
|
||||||
--gradient-accent: linear-gradient(120deg, oklch(0.58 0.22 27), oklch(0.68 0.19 40));
|
--gradient-accent: linear-gradient(120deg, oklch(0.58 0.22 27), oklch(0.68 0.19 40));
|
||||||
|
/*
|
||||||
|
* Ombre neutre: la profondita' la danno distanza e diffusione, non il colore.
|
||||||
|
* Le superfici piu' grandi ricevono un'ombra piu' ampia e morbida.
|
||||||
|
*/
|
||||||
--shadow-card: 0 1px 2px oklch(0.16 0.01 260 / 6%), 0 12px 28px -18px oklch(0.16 0.01 260 / 35%);
|
--shadow-card: 0 1px 2px oklch(0.16 0.01 260 / 6%), 0 12px 28px -18px oklch(0.16 0.01 260 / 35%);
|
||||||
--shadow-pop: 0 18px 40px -20px oklch(0.58 0.22 27 / 55%);
|
--shadow-pop: 0 2px 6px oklch(0.16 0.01 260 / 8%), 0 20px 44px -22px oklch(0.16 0.01 260 / 45%);
|
||||||
|
/* Materiale traslucido della chrome fissa (nav, barre). */
|
||||||
|
--materiale-sfondo: color-mix(in oklab, var(--card) 72%, transparent);
|
||||||
|
--materiale-bordo: color-mix(in oklab, var(--foreground) 10%, transparent);
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
@@ -155,9 +213,15 @@
|
|||||||
box-shadow: var(--shadow-pop);
|
box-shadow: var(--shadow-pop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Nessuna crenatura fissa qui: la imposta la scala tipografica in @theme, che
|
||||||
|
* la calibra per taglia (stretta sui titoli grandi, larga sulle etichette
|
||||||
|
* piccole). Un valore unico per tutte le dimensioni sarebbe sbagliato da
|
||||||
|
* qualche parte per definizione.
|
||||||
|
*/
|
||||||
@utility font-display {
|
@utility font-display {
|
||||||
font-family: var(--font-display);
|
font-family: var(--font-display);
|
||||||
letter-spacing: 0.02em;
|
font-optical-sizing: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Micro-animazioni (solo CSS standard, nessuna dipendenza esterna) --- */
|
/* --- Micro-animazioni (solo CSS standard, nessuna dipendenza esterna) --- */
|
||||||
@@ -265,7 +329,7 @@
|
|||||||
/* Feedback al tocco sulle card interattive (+ hover leggero su desktop). */
|
/* Feedback al tocco sulle card interattive (+ hover leggero su desktop). */
|
||||||
@utility premi {
|
@utility premi {
|
||||||
transition:
|
transition:
|
||||||
transform 140ms ease-out,
|
transform 100ms ease-out,
|
||||||
box-shadow 140ms ease-out;
|
box-shadow 140ms ease-out;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@@ -280,36 +344,235 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
/*
|
||||||
*,
|
* Porta l'area attiva a 44x44px (minimo iOS) senza toccare la grafica: il
|
||||||
*::before,
|
* comando resta piccolo, il dito ha lo spazio che gli serve.
|
||||||
*::after {
|
*/
|
||||||
animation-duration: 0.001ms !important;
|
@utility area-tocco {
|
||||||
animation-iteration-count: 1 !important;
|
position: relative;
|
||||||
transition-duration: 0.001ms !important;
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: max(100%, 44px);
|
||||||
|
height: max(100%, 44px);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Materiali traslucidi --- */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chrome traslucida: il contenuto scorre sotto e si intravede sfocato.
|
||||||
|
* L'opacita' sta sotto il 75%, altrimenti la sfocatura non si vede e tanto
|
||||||
|
* vale una barra opaca.
|
||||||
|
*/
|
||||||
|
@utility materiale {
|
||||||
|
background-color: var(--materiale-sfondo);
|
||||||
|
backdrop-filter: blur(20px) saturate(180%);
|
||||||
|
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
||||||
|
border-color: var(--materiale-bordo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Bordo di scorrimento: al posto della riga netta sopra la nav, una striscia
|
||||||
|
* che sfuma il contenuto mentre ci passa sotto.
|
||||||
|
*/
|
||||||
|
@utility bordo-scroll-basso {
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset-inline: 0;
|
||||||
|
bottom: 100%;
|
||||||
|
height: 1.25rem;
|
||||||
|
pointer-events: none;
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
-webkit-backdrop-filter: blur(6px);
|
||||||
|
mask-image: linear-gradient(to top, black, transparent);
|
||||||
|
-webkit-mask-image: linear-gradient(to top, black, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Apertura/chiusura dei contenuti collassabili (Radix Collapsible) --- */
|
||||||
|
|
||||||
|
@keyframes crapp-apri {
|
||||||
|
from {
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
height: var(--radix-collapsible-content-height);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes crapp-chiudi {
|
||||||
|
from {
|
||||||
|
height: var(--radix-collapsible-content-height);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility anim-tendina {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&[data-state="open"] {
|
||||||
|
animation: crapp-apri 260ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-state="closed"] {
|
||||||
|
animation: crapp-chiudi 220ms cubic-bezier(0.4, 0, 0.6, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes crapp-dissolvenza {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Preferenze di accessibilita' --- */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Movimento ridotto non vuol dire "nessun feedback": gli spostamenti e i
|
||||||
|
* rimbalzi spariscono, le dissolvenze restano perche' aiutano a capire cosa e'
|
||||||
|
* cambiato. Azzerare *tutte* le animazioni toglieva anche quelle.
|
||||||
|
*/
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.anim-reveal,
|
||||||
|
.anim-pop,
|
||||||
|
.anim-riga {
|
||||||
|
animation: crapp-dissolvenza 180ms ease-out both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anim-tendina[data-state="open"],
|
||||||
|
.anim-tendina[data-state="closed"] {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anim-battito,
|
||||||
|
.anim-oscilla {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anim-barra {
|
||||||
|
transition-duration: 1ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.premi {
|
||||||
|
transition-property: box-shadow, background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.premi:active,
|
||||||
|
.premi:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Trasparenza ridotta: superfici piene, niente sfocatura da leggere sotto. */
|
||||||
|
@media (prefers-reduced-transparency: reduce) {
|
||||||
|
:root,
|
||||||
|
.dark {
|
||||||
|
--materiale-sfondo: var(--card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.materiale {
|
||||||
|
backdrop-filter: none;
|
||||||
|
-webkit-backdrop-filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bordo-scroll-basso::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Contrasto alto: fondi pieni, bordi definiti, testo secondario piu' scuro. */
|
||||||
|
@media (prefers-contrast: more) {
|
||||||
|
:root {
|
||||||
|
--muted-foreground: oklch(0.4 0.015 260);
|
||||||
|
--border: oklch(0.76 0.01 255);
|
||||||
|
--materiale-sfondo: var(--card);
|
||||||
|
--materiale-bordo: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--muted-foreground: oklch(0.86 0.01 260);
|
||||||
|
--border: oklch(1 0 0 / 32%);
|
||||||
|
--materiale-sfondo: var(--card);
|
||||||
|
--materiale-bordo: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.materiale {
|
||||||
|
backdrop-filter: none;
|
||||||
|
-webkit-backdrop-filter: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tema scuro. Regole:
|
||||||
|
* - l'accento resta il rosso del brand, solo schiarito per reggere il fondo
|
||||||
|
* scuro (nella versione precedente diventava grigio-blu e il brand spariva);
|
||||||
|
* - `--primary` NON si inverte: l'app usa `text-primary-foreground` come
|
||||||
|
* "testo sopra il gradiente hero", quindi primary-foreground deve restare
|
||||||
|
* chiaro anche al buio;
|
||||||
|
* - ogni token semantico (success/warning/info/training/metalli/gradienti/
|
||||||
|
* ombre) va ridefinito qui, altrimenti resta il valore chiaro.
|
||||||
|
*/
|
||||||
.dark {
|
.dark {
|
||||||
--background: oklch(0.129 0.042 264.695);
|
--background: oklch(0.16 0.008 265);
|
||||||
--foreground: oklch(0.984 0.003 247.858);
|
--foreground: oklch(0.97 0.002 250);
|
||||||
--card: oklch(0.208 0.042 265.755);
|
--card: oklch(0.21 0.011 265);
|
||||||
--card-foreground: oklch(0.984 0.003 247.858);
|
--card-foreground: oklch(0.97 0.002 250);
|
||||||
--popover: oklch(0.208 0.042 265.755);
|
--popover: oklch(0.21 0.011 265);
|
||||||
--popover-foreground: oklch(0.984 0.003 247.858);
|
--popover-foreground: oklch(0.97 0.002 250);
|
||||||
--primary: oklch(0.929 0.013 255.508);
|
--primary: oklch(0.28 0.014 265);
|
||||||
--primary-foreground: oklch(0.208 0.042 265.755);
|
--primary-foreground: oklch(0.98 0.002 250);
|
||||||
--secondary: oklch(0.279 0.041 260.031);
|
--secondary: oklch(0.27 0.012 265);
|
||||||
--secondary-foreground: oklch(0.984 0.003 247.858);
|
--secondary-foreground: oklch(0.97 0.002 250);
|
||||||
--muted: oklch(0.279 0.041 260.031);
|
--muted: oklch(0.27 0.012 265);
|
||||||
--muted-foreground: oklch(0.704 0.04 256.788);
|
--muted-foreground: oklch(0.73 0.015 260);
|
||||||
--accent: oklch(0.279 0.041 260.031);
|
--accent: oklch(0.66 0.2 27);
|
||||||
--accent-foreground: oklch(0.984 0.003 247.858);
|
--accent-foreground: oklch(0.99 0 0);
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
--destructive: oklch(0.66 0.2 27);
|
||||||
--destructive-foreground: oklch(0.984 0.003 247.858);
|
--destructive-foreground: oklch(0.99 0 0);
|
||||||
--border: oklch(1 0 0 / 10%);
|
--border: oklch(1 0 0 / 12%);
|
||||||
--input: oklch(1 0 0 / 15%);
|
--input: oklch(1 0 0 / 16%);
|
||||||
--ring: oklch(0.551 0.027 264.364);
|
--ring: oklch(0.66 0.2 27);
|
||||||
|
--success: oklch(0.72 0.15 152);
|
||||||
|
--success-foreground: oklch(0.16 0.01 260);
|
||||||
|
--warning: oklch(0.83 0.14 78);
|
||||||
|
--warning-foreground: oklch(0.18 0.02 60);
|
||||||
|
--info: oklch(0.7 0.12 245);
|
||||||
|
--info-foreground: oklch(0.16 0.01 260);
|
||||||
|
--training: oklch(0.7 0.16 245);
|
||||||
|
--training-foreground: oklch(0.16 0.01 260);
|
||||||
|
--bronzo: oklch(0.7 0.11 55);
|
||||||
|
--argento: oklch(0.82 0.02 260);
|
||||||
|
--oro: oklch(0.85 0.14 85);
|
||||||
|
--gradient-hero: linear-gradient(
|
||||||
|
140deg,
|
||||||
|
oklch(0.2 0.012 265),
|
||||||
|
oklch(0.26 0.04 20) 55%,
|
||||||
|
oklch(0.46 0.19 27)
|
||||||
|
);
|
||||||
|
--gradient-accent: linear-gradient(120deg, oklch(0.62 0.21 27), oklch(0.7 0.18 40));
|
||||||
|
--shadow-card: 0 1px 2px oklch(0 0 0 / 40%), 0 12px 28px -18px oklch(0 0 0 / 70%);
|
||||||
|
--shadow-pop: 0 2px 6px oklch(0 0 0 / 45%), 0 20px 44px -22px oklch(0 0 0 / 80%);
|
||||||
|
--materiale-sfondo: color-mix(in oklab, var(--card) 70%, transparent);
|
||||||
|
--materiale-bordo: color-mix(in oklab, white 12%, transparent);
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
--chart-3: oklch(0.769 0.188 70.08);
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
@@ -336,4 +599,16 @@
|
|||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Anello di focus da tastiera su tutto cio' che e' attivabile. Sta qui e non
|
||||||
|
* sui singoli componenti perche' l'app ha una sessantina di <button> scritti
|
||||||
|
* a mano fuori da components/ui/, nessuno dei quali lo definiva.
|
||||||
|
* `:where()` ha specificita' zero: qualsiasi `focus-visible:` di Tailwind sul
|
||||||
|
* componente continua a vincere su questa regola.
|
||||||
|
*/
|
||||||
|
:where(a, button, input, select, textarea, summary, [role="button"], [tabindex]):focus-visible {
|
||||||
|
outline: 2px solid var(--ring);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user