1. Tocca il giocatore
@@ -393,10 +393,10 @@ function ScoutBoard({
)}
>
{g.numero}
-
{g.nome}
+
{g.nome}
@@ -409,7 +409,7 @@ function ScoutBoard({
-
+
2. Tocca l'azione
@@ -447,7 +447,7 @@ function ScoutBoard({
-
+
Ultime azioni
{ultime.length === 0 ? (
@@ -467,7 +467,7 @@ function ScoutBoard({
{g ? `#${g.numero} ${g.nome}` : "Avversario"}
@@ -483,7 +483,7 @@ function ScoutBoard({
-
+
Badge sbloccati · {collezioneBadge(g).ottenuti}/{collezioneBadge(g).totali}
{sbloccati.length === 0 ? (
@@ -194,18 +194,13 @@ function Squadra() {
{b.def.nome}
-
+
{b.valore} {b.def.unita}
{b.prossimaSoglia
? ` · ${b.prossimaSoglia} per ${gradoMeta[b.prossimo!].label.toLowerCase()}`
: ""}
-
+
{meta.label}
@@ -241,7 +236,7 @@ function Squadra() {
type="button"
onClick={() => setCriterio(c.id)}
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
? "bg-accent text-accent-foreground shadow-pop"
: "bg-secondary text-muted-foreground",
@@ -264,7 +259,7 @@ function Squadra() {
{g.nome}
{i === 0 ? : null}
-
+
#{g.numero} · {g.ruolo} · {g.streak} presenze consecutive
@@ -290,7 +285,7 @@ function Squadra() {
-
+
Progresso collettivo
@@ -326,7 +321,7 @@ function Squadra() {
{microcopyObiettivo(o)}
- {o.impatto}
+ {o.impatto}
);
})}
@@ -358,9 +353,7 @@ function Squadra() {
{b.nome}
-
- {descrizioneSoglie(b)}
-
+
{descrizioneSoglie(b)}
{gradiOrdine.map((grado) => {
@@ -376,11 +369,11 @@ function Squadra() {
key={grado}
className={cn("rounded-2xl p-2 text-center ring-1", meta.bg, meta.ring)}
>
-
+
{meta.label}
{b.soglie[grado]}
-
+
{quanti}/{rosa.length}
diff --git a/src/styles.css b/src/styles.css
index 12eac5f..3fe4900 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -7,6 +7,57 @@
@theme {
--font-display: "Bebas Neue", ui-sans-serif, 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)
);
--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-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-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
@@ -155,9 +213,15 @@
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 {
font-family: var(--font-display);
- letter-spacing: 0.02em;
+ font-optical-sizing: auto;
}
/* --- Micro-animazioni (solo CSS standard, nessuna dipendenza esterna) --- */
@@ -265,7 +329,7 @@
/* Feedback al tocco sulle card interattive (+ hover leggero su desktop). */
@utility premi {
transition:
- transform 140ms ease-out,
+ transform 100ms ease-out,
box-shadow 140ms ease-out;
&:active {
@@ -280,36 +344,235 @@
}
}
-@media (prefers-reduced-motion: reduce) {
- *,
- *::before,
- *::after {
- animation-duration: 0.001ms !important;
- animation-iteration-count: 1 !important;
- transition-duration: 0.001ms !important;
+/*
+ * Porta l'area attiva a 44x44px (minimo iOS) senza toccare la grafica: il
+ * comando resta piccolo, il dito ha lo spazio che gli serve.
+ */
+@utility area-tocco {
+ position: relative;
+
+ &::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 {
- --background: oklch(0.129 0.042 264.695);
- --foreground: oklch(0.984 0.003 247.858);
- --card: oklch(0.208 0.042 265.755);
- --card-foreground: oklch(0.984 0.003 247.858);
- --popover: oklch(0.208 0.042 265.755);
- --popover-foreground: oklch(0.984 0.003 247.858);
- --primary: oklch(0.929 0.013 255.508);
- --primary-foreground: oklch(0.208 0.042 265.755);
- --secondary: oklch(0.279 0.041 260.031);
- --secondary-foreground: oklch(0.984 0.003 247.858);
- --muted: oklch(0.279 0.041 260.031);
- --muted-foreground: oklch(0.704 0.04 256.788);
- --accent: oklch(0.279 0.041 260.031);
- --accent-foreground: oklch(0.984 0.003 247.858);
- --destructive: oklch(0.704 0.191 22.216);
- --destructive-foreground: oklch(0.984 0.003 247.858);
- --border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
- --ring: oklch(0.551 0.027 264.364);
+ --background: oklch(0.16 0.008 265);
+ --foreground: oklch(0.97 0.002 250);
+ --card: oklch(0.21 0.011 265);
+ --card-foreground: oklch(0.97 0.002 250);
+ --popover: oklch(0.21 0.011 265);
+ --popover-foreground: oklch(0.97 0.002 250);
+ --primary: oklch(0.28 0.014 265);
+ --primary-foreground: oklch(0.98 0.002 250);
+ --secondary: oklch(0.27 0.012 265);
+ --secondary-foreground: oklch(0.97 0.002 250);
+ --muted: oklch(0.27 0.012 265);
+ --muted-foreground: oklch(0.73 0.015 260);
+ --accent: oklch(0.66 0.2 27);
+ --accent-foreground: oklch(0.99 0 0);
+ --destructive: oklch(0.66 0.2 27);
+ --destructive-foreground: oklch(0.99 0 0);
+ --border: oklch(1 0 0 / 12%);
+ --input: oklch(1 0 0 / 16%);
+ --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-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
@@ -336,4 +599,16 @@
font-family: var(--font-sans);
-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 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;
+ }
}