Tutti i case study

Questo sito

Portfolio come prova di competenza invece che brochure: statico, 99 KB gzip di bundle iniziale, ogni animazione con un cancello e un ripiego che funziona senza JavaScript.

Ruolo
Analisi, direzione artistica, design system, sviluppo, SEO — autore unico
Periodo
luglio 2026 → oggi
Stato
Online, in evoluzione
  • Angular 22
  • SSG
  • Sass
  • GSAP
  • Three.js
  • Azure Static Web Apps

Perché il sito è il case study

Quattro anni in azienda spesi su software enterprise che non posso mostrare: portali di telemetrie, ERP, gestione budget, prenotazione scrivanie. Tutti dietro autenticazione, tutti coperti da riservatezza. Il risultato è un profilo con esperienza reale e nessuna vetrina.

Questo sito è la risposta: se i progetti non si possono mostrare, si mostra come è fatta la cosa che stai guardando. Il sorgente è pubblico, la storia dei commit è leggibile, e ogni vincolo dichiarato qui è verificabile aprendo i DevTools sulla pagina che stai leggendo.

L'altra risposta è nel concetto: la home non è una pagina di presentazione, è una dashboard — timeline, matrice di competenze, progetti per dominio. Costruisco gestionali per lavoro; un portfolio che è un gestionale dimostra quella competenza invece di descriverla, e il problema della riservatezza si dissolve perché i dati mostrati sono i miei.

Il budget è una condizione di build, non un buon proposito

Vincolo dichiarato all'inizio: meno di 180 KB gzip di JavaScript iniziale. Il valore reale oggi è 98,9 KB (339 KB non compressi), e la build fallisce oltre soglia.

Ha smesso di essere un buon proposito il giorno in cui ho guardato i numeri: il budget predefinito era 500 KB di avviso e 1 MB di errore sul non compresso, cioè passava verde a bundle già due volte oltre il vincolo vero, perché 1 MB non compresso non ha nessuna relazione con 180 KB gzip. Ora la soglia è 400/550 KB non compressi, che corrispondono circa a 180-200 gzip: la build si ferma prima di sforare.

Il resto segue da lì. Ogni pagina è prerenderizzata come file statico, quindi il contenuto è nell'HTML e non dipende dal JavaScript. GSAP (70 KB) e ScrollTrigger (44 KB) sono chunk separati; Three.js (153 KB gzip) è un chunk a parte che scarica solo entrando nel Lab. La prova che gli adapter lazy funzionano non è l'intenzione, è il numero del bundle iniziale rimasto identico dopo averli aggiunti.

PageSpeed Insights sul sito vivo: 98 / 100 / 100 / 100 su mobile, 100 / 100 / 100 / 100 su desktop.

Ogni animazione ha un cancello

Nessun componente importa GSAP o Three.js direttamente: passano da due adapter, e l'adapter è l'unico posto autorizzato a fare l'import dinamico.

/** Loads gsap lazily and hands it to `task`, unless `disabled()` says to skip. */
export async function withGsap(
  task: (gsap: Gsap) => void,
  options: GsapTaskOptions = {},
): Promise<void> {
  if (options.disabled?.()) return;
  const gsap = await loadGsap();
  // Re-check: the preference can change while the chunk is in flight.
  if (options.disabled?.()) return;
  task(gsap);
}

Quel doppio controllo è il punto: la preferenza «riduci animazioni» può cambiare mentre il chunk è in volo, e un cancello controllato solo all'inizio la perderebbe. La scena WebGL del Lab va oltre: monta solo se il dispositivo dichiara di poterla sostenere (WebGL, niente reduced-motion, niente risparmio dati, RAM sufficiente), e una volta montata si ferma da sola fuori dallo schermo, a tab nascosta, e se l'utente attiva la preferenza a scena già in esecuzione.

Il ripiego non è un elemento separato: sotto il canvas trasparente c'è uno sfondo CSS con lo stesso gradiente. Se il canvas non parte mai, si vede la stessa immagine in CSS puro — e la timeline della home si anima in CSS nativo, 0 KB, perché un reveal semplice non giustifica una libreria e un canvas cancellerebbe il testo indicizzabile.

prefers-reduced-motion è rispettato senza eccezioni: un sito pieno di motion che ignora quella preferenza è un errore tecnico, non una scelta estetica.

Il vincolo NDA come hook, non come promemoria

Il repository è pubblico e la regola «nessun nome di cliente reale» non può dipendere dal fatto che io me la ricordi. È un hook che gira a ogni scrittura di file: se il testo contiene un termine coperto o un pattern di chiave segreta, la scrittura è bloccata. L'elenco dei termini veri sta in un file non versionato — i nomi coperti da riservatezza non possono stare in un repository pubblico nemmeno dentro il controllo che li vieta.

Stessa filosofia altrove: i rapporti di contrasto del design system sono uno script che gira nella verifica, non una tabella copiata a mano in un documento.

Tre bug che valgono più di tre feature

La transizione di pagina che faceva sobbalzare la nav. L'animazione era su translateY, ma il gruppo «root» delle View Transitions è l'intera pagina in un solo fotogramma, nav sticky compresa: la nav scorreva col contenuto. Ho isolato la nav in un gruppo proprio, e ho ottenuto un bug peggiore — per specifica, un elemento con un nome di transizione non è cliccabile finché la sua animazione non finisce, quindi il link di nav restava morto per mezzo secondo dopo l'atterraggio. Non esiste un rimedio per gli elementi con nome proprio. La soluzione è stata togliere il transform e tenere solo un crossfade di opacità: senza trasformazione la nav non si muove, e senza elementi nominati il limite non si applica a nessuno.

La timeline che si disegnava già finita su schermi alti. Lo scrub era ancorato a posizioni di scroll assolute: su un viewport abbastanza alto il widget entrava tutto nella prima schermata, quindi il punto di fine cadeva a scroll ≤ 0 — un punto irraggiungibile, perché non si scrolla sotto zero. Il progresso restava bloccato a 1 per sempre. Ora, se il range calcolato è irraggiungibile, lo scrub si spegne e si passa al ripiego a scatto singolo che esisteva già per reduced-motion.

Il colore che non esisteva. Un token era definito come var(--text-muted): var(--text-muted) — proprietà circolare, quindi invalida ovunque. Sulle proprietà non ereditate spariva in silenzio; su color ripiegava sul colore del genitore. È rimasto invisibile per giorni, finché un bordo tratteggiato non l'ha reso evidente.

Li scrivo perché sono i tre punti in cui ho imparato qualcosa che non sapevo prima di questo progetto. Un case study che elenca solo cose andate bene racconta metà del lavoro.