Guida pratica · livello esperto

Costruire siti immersivi scroll‑telling 3D
con Claude + Veo 3.1 + Gemini

Dalla teoria dell'effetto "la camera entra dentro la scena" fino a una Claude Skill che genera l'intero sito — assets inclusi — con un singolo prompt, e a due build lanciate in parallelo. Codice reale, non slide.

Spunto video: «Claude + Veo 3.1 = Siti Web 3D Con Un Solo Prompt» — Giovanni Beggiato (4:28) · Estratto e ampliato con JoeFastTubeAI · Immagini Full HD 1920×1080

00TL;DR & cosa costruisci

Uno scroll‑telling website (o sito immersivo) è una pagina in cui lo scroll non scorre del testo: pilota una "camera" che entra dentro una scena 3D — una porta che si apre, un treno in stazione, un fiore che si illumina — mentre il testo appare in sincronia. L'effetto 3D è in realtà una sequenza di immagini renderizzata a "scrub" sullo scroll, identica alla tecnica delle landing Apple.

La parte nuova: gli asset li genera l'AI (Gemini per i fotogrammi, Veo 3.1 per le clip) e l'intero sito lo assembla una Claude Skill con un prompt solo — risolvendo da sola i tre problemi che rendono questi siti di solito lenti e buggy.

Sito scroll-telling: stazione con treno ad alta velocità, navigazione RAIL/CABIN/DINING
Il risultato target: la camera "entra" nella stazione e il treno avanza mentre scrolli. Generato da un singolo prompt alla skill.
◆ Cosa imparerai a costruire
  • Il motore di scroll fluido (Lenis) + lo scrub dell'image‑sequence su <canvas> con GSAP ScrollTrigger
  • Un text‑reveal consistente e non "saltellante", sincronizzato allo scroll
  • La pipeline di asset AI: come generare N fotogrammi coerenti tra loro con Gemini e una clip con Veo 3.1
  • Una Claude Skill (SKILL.md completo) che fa tutto questo one‑shot
  • Il dispatch parallelo per sfornare due siti diversi in contemporanea

01L'illusione: come funziona davvero

Nessun WebGL obbligatorio, nessun modello 3D. Il "3D" percepito nasce da tre layer sincronizzati sulla stessa barra di scroll:

1 · Background scrubbed

Una sequenza di 60–180 fotogrammi (la camera che avanza) disegnata su un canvas pinnato. Lo scroll non scrolla la pagina: avanza l'indice del frame.

2 · Overlay di testo

Blocchi di copy posizionati a percentuali precise della timeline, che entrano con fade + translate coerenti, mai in ordine casuale.

3 · Navigazione "diegetica"

La nav (RAIL · CABIN · DINING) fa parte della scena: cliccando, scrolli a un certo frame, non cambi pagina.

4 · Smooth scroll

Lo scroll nativo è a scatti. Un motore di inertial smoothing (Lenis) rende il movimento della camera continuo e cinematografico.

▲ Perché image‑sequence e non un video

Un <video> non si lascia "scrubbare" frame‑per‑frame in modo affidabile cross‑browser (seek lento, decode a scatti su mobile). Una sequenza di JPG/WebP pre‑caricati e disegnati su canvas ti dà controllo esatto su quale frame mostrare ad ogni pixel di scroll. È il motivo per cui la skill genera immagini sequenziali, non solo un filmato.

02Stack tecnico

LayerStrumentoPerché
Smooth scrolllenis (studio‑freight)Inerzia configurabile, si aggancia a ScrollTrigger via raf
Scroll animationgsap + ScrollTriggerPin, scrub, timeline; lo standard de‑facto
Rendering camera<canvas> 2DDisegna l'image‑sequence, zero dipendenze 3D
Immagini fotogrammaGemini (Imagen / "Nano Banana")Genera frame coerenti per soggetto e luce
Clip videoVeo 3.1Loop ambientali, hero in background
Asset di prodottoHiggsfield (opz.)Render product‑shot da immagine di partenza
BuildVite (o file statico)Dev server + bundling; per un demo basta un singolo HTML
OrchestrazioneClaude Code + SkillGenera prompt, chiama le API, assembla il sito
⬢ Nota di campo

Beggiato sottolinea che Google è oggi sottovalutato per i siti immersivi: con l'API di Gemini per le immagini e Veo 3.1 per il video copri l'intera pipeline visiva senza uscire dall'ecosistema Google. Higgsfield resta utile quando parti da un'immagine reale di prodotto da animare.

Sito scroll-telling con fiore che si illumina, navigazione BLOOM/PETALS/NECTAR/CONTACT
Stesso motore, soggetto diverso: il fiore si illumina e la camera lo attraversa. Il design si "generalizza" tra temi.

03Pipeline asset AI

Il punto critico non è "fare una bella immagine": è fare N immagini che sembrino lo stesso mondo, in progressione logica (la camera che avanza). Tre regole:

  1. Blocca il soggetto e l'illuminazione. Un solo seed/stile, e un prompt che descrive la stessa scena con la camera che avanza di step costanti ("dolly‑in 5%").
  2. Genera in sequenza, non in parallelo per i frame di una stessa scena: ogni frame referenzia il precedente per continuità (image‑to‑image), così la camera non "salta".
  3. Esporta numerato e zero‑padded (frame_001.webpframe_120.webp) — il canvas li indicizza per numero.

Generare i frame con Gemini (REST)

# Genera un frame con il modello immagini di Gemini.
# Per una camera "dolly-in", varia solo la clausola di camera mantenendo soggetto+luce.
curl -s "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent" \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contents":[{"parts":[{
      "text":"Cinematic interior of a grand glass train station at golden hour,
               high-speed train at platform, volumetric light, ultra-detailed,
               photoreal. CAMERA: slow dolly-in, step 14 of 120, 5% closer than previous frame.
               Consistent subject, lighting and color grade across the sequence."
    }]}],
    "generationConfig":{"responseModalities":["IMAGE"]}
  }' | jq -r '.candidates[0].content.parts[0].inlineData.data' | base64 -d > frame_014.webp
■ Attenzione (lezione dal video precedente)

Le API Google/OpenAI da Python su macOS python.org possono fallire con CERTIFICATE_VERIFY_FAILED: usa il bundle certifi nel contesto SSL. E ricorda i limiti di rate/size: genera i frame a batch con backoff, non tutti insieme.

Una clip ambientale con Veo 3.1

# Veo genera una breve clip (loop di sfondo o hero). Operazione asincrona: si fa polling.
POST https://generativelanguage.googleapis.com/v1beta/models/veo-3.1:predictLongRunning
# body: { "instances":[{ "prompt":"camera glides through a blooming flower field,
#         sunrise, dreamy, seamless loop" }], "parameters":{"durationSeconds":6} }
# → ritorna un operation name; fai polling finché done:true, poi scarichi l'MP4.

Usa Veo per il loop di background a bassa priorità (dietro al canvas) e l'image‑sequence per l'azione scrubbata in primo piano: video per l'atmosfera, frame per il controllo.

04Smooth scroll con Lenis

Senza smoothing, lo scrub sembra a scatti. Lenis interpola lo scroll e lo passa a GSAP nello stesso loop requestAnimationFrame — un solo loop, niente jitter da loop concorrenti.

import Lenis from 'lenis'
import gsap from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
gsap.registerPlugin(ScrollTrigger)

const lenis = new Lenis({ lerp: 0.1, smoothWheel: true })

// 1 solo rAF: Lenis avanza, poi ScrollTrigger si aggiorna
lenis.on('scroll', ScrollTrigger.update)
gsap.ticker.add((time) => lenis.raf(time * 1000))
gsap.ticker.lagSmoothing(0)
▲ Tuning

lerp: 0.1 è cinematografico; alza a 0.15–0.2 se l'inerzia sembra "molle". Su mobile valuta smoothTouch:false — lo smoothing del touch nativo è già buono e quello forzato può dare lag.

05Il cuore: image‑sequence scrubbata su canvas

Questo è il pezzo che crea l'effetto "la camera entra". Si pinna una sezione alta (es. 500vh): mentre scrolli quei 5 viewport, l'indice del frame va da 0 all'ultimo, e il canvas ridisegna.

HTML

<section class="scene">
  <canvas id="seq"></canvas>
</section>
<style>
  .scene{ height:500vh; }              /* lunghezza dello scrub */
  .scene canvas{ position:sticky; top:0; width:100vw; height:100vh; object-fit:cover; }
</style>

JS — preload + scrub

const FRAMES = 120
const url = i => `/img/seq/frame_${String(i+1).padStart(3,'0')}.webp`
const canvas = document.getElementById('seq')
const ctx = canvas.getContext('2d')

// 1) Preload sequenziale e ORDINATO (continuità garantita)
const images = []
async function preload(){
  for (let i=0; i<FRAMES; i++){
    const img = new Image()
    img.src = url(i)
    await img.decode().catch(()=>{})  // decode esplicito = niente flash al primo draw
    images[i] = img
  }
}

function render(i){
  const img = images[i | 0]; if (!img) return
  // cover responsivo
  const s = Math.max(canvas.width/img.width, canvas.height/img.height)
  const w = img.width*s, h = img.height*s
  ctx.drawImage(img, (canvas.width-w)/2, (canvas.height-h)/2, w, h)
}

function resize(){ canvas.width = innerWidth*devicePixelRatio; canvas.height = innerHeight*devicePixelRatio; render(state.f) }
addEventListener('resize', resize)

// 2) Lo scroll guida l'indice del frame
const state = { f: 0 }
preload().then(()=>{
  resize()
  gsap.to(state, {
    f: FRAMES-1, ease: 'none', snap: 'f',
    scrollTrigger:{ trigger:'.scene', start:'top top', end:'bottom bottom', scrub:0.5 },
    onUpdate:()=>render(state.f)
  })
})
Builder con preview di una landing 'Dreamcore' immersiva
«Fall into Reverie»: lo stesso pattern canvas+scrub applicato a una landing onirica.
◆ I 3 dettagli che fanno la differenza
  • img.decode() prima di disegnare → niente frame bianco al primo paint.
  • scrub: 0.5 → un filo di inerzia che "insegue" lo scroll, più morbido di scrub:true.
  • devicePixelRatio sul canvas → niente immagine sfocata su display Retina.

06Text reveal consistente

Il difetto tipico dei siti "fatti dalla skill prefatta": il testo appare in modo incoerente — alcuni blocchi saltano, altri no. La regola è una sola: stesso trigger, stesso easing, stesso offset per tutti i blocchi, agganciati alla timeline della scena, non al tempo.

gsap.utils.toArray('.reveal').forEach((el)=>{
  gsap.fromTo(el,
    { autoAlpha:0, y:40 },
    { autoAlpha:1, y:0, ease:'power2.out', duration:0.6,
      scrollTrigger:{ trigger:el, start:'top 80%', toggleActions:'play none none reverse' }
    })
})
▲ Per legare il testo a un frame preciso

Se vuoi che la frase appaia esattamente quando la camera arriva alla cabina, non usare top 80%: aggiungi label sulla timeline della scena e posiziona i reveal a scrub con quella, così copy e immagine sono perfettamente sincronizzati. Coerenza > sorpresa.

Lo stesso vale per le micro‑animazioni di nav e per il banner finale (quel blocco a tutta larghezza che Beggiato ama): un componente riusabile, con la stessa curva di entrata di tutto il resto del sito.

07Le 3 cose che la skill aggiusta in automatico

Il valore della skill non è "scrive HTML": è che codifica le decisioni difficili così ogni sito esce production‑grade. Sono esattamente i tre punti che, fatti a mano da template generici, "diventano un disastro: lenti, super buggy".

#Problema dei template genericiCosa impone la skill
1Fluidità: scroll lento e a scattiUn solo loop rAF (Lenis↔GSAP), scrub con inerzia, will-change mirato, canvas a DPR corretto
2Sequenzialità: frame che "saltano", scena incoerenteGenerazione immagini in ordine logico (image‑to‑image incatenato), numerazione zero‑padded, preload ordinato
3Reveal: testo che appare a casoStesso trigger/easing/offset per ogni blocco, agganciato alla timeline della scena
◆ In più

La skill aggiunge anche il banner a tutta larghezza come componente firma, e generalizza il design tra temi diversi (treno, astronave, fiore) riusando lo stesso scheletro: cambi soggetto e palette, non l'architettura.

08Costruire la Claude Skill scroll‑journey

Una skill è una cartella con un SKILL.md: front‑matter (nome + descrizione che fa da trigger) e istruzioni operative. Claude la attiva quando il task corrisponde alla descrizione. Ecco uno scheletro pensato per il one‑shot.

SKILL.md aperto nell'editor accanto al pannello di Claude Code
Il SKILL.md nell'editor: front‑matter in cima, poi gli step che Claude esegue (download, build, generazione assets, fix fluidità/reveal).

Struttura cartella

scroll-journey/
├─ SKILL.md            # istruzioni + trigger
├─ scripts/
│  ├─ gen_frames.py    # Gemini: N frame sequenziali
│  ├─ gen_video.py     # Veo 3.1: clip ambientale
│  └─ scaffold.py      # copia il template e inietta gli assets
└─ template/           # index.html + main.js (Lenis+GSAP) già cablati

SKILL.md

---
name: scroll-journey
description: >-
  Genera un sito immersivo scroll-telling 3D one-shot: crea i frame con Gemini,
  una clip con Veo 3.1, e assembla un template Lenis+GSAP con scroll fluido,
  sequenza immagini coerente e text-reveal consistente. Usare quando l'utente
  chiede un sito immersivo, scroll-telling, "camera che entra nella scena" o simili.
---

# scroll-journey

## Input
Dall'utente ricava: soggetto/scena, sezioni (nav), palette, e n. di frame (default 120).

## Step 1 — Storyboard
Scrivi un prompt-base che fissa soggetto, luce e color grade. Definisci il movimento
camera come dolly-in in N step costanti. NON cambiare lo stile tra i frame.

## Step 2 — Frame sequenziali (CRUCIALE)
Esegui scripts/gen_frames.py: genera i frame IN ORDINE, ognuno in image-to-image dal
precedente per continuità. Salva img/seq/frame_001.webp ... zero-padded.

## Step 3 — Clip ambientale
Esegui scripts/gen_video.py (Veo 3.1) per il loop di background. Polling fino a done.

## Step 4 — Scaffold
Copia template/, inietta gli assets e le sezioni. Il template ha GIÀ:
  - 1 solo loop rAF (Lenis↔ScrollTrigger)
  - canvas sticky a devicePixelRatio, preload+decode ordinato
  - reveal con trigger/easing/offset UNIFORMI
  - banner full-width finale

## Step 5 — Verifica
Apri in preview, controlla: nessun frame mancante, scroll fluido, reveal coerente.
Se un frame "salta", rigeneralo in image-to-image dal vicino. Riporta il path finale.
⬢ Principio di design della skill

Metti la conoscenza non‑ovvia nel template (il loop rAF unico, il DPR, il preload ordinato) e lascia al modello solo le decisioni creative (soggetto, palette, copy). Così il rischio "buggy" è già neutralizzato a monte: il modello non re‑inventa l'engine ad ogni run.

09Dispatch parallelo: due siti insieme

Nel demo vengono lanciati due siti in parallelo (treno + astronave), pronti in 2–4 minuti. Con Claude Code li mandi in background e li lasci correre insieme — ognuno è un'invocazione indipendente della skill.

Claude Code che costruisce due siti scroll-journey in parallelo
«Entrambi lanciati in parallelo»: due build scroll‑journey indipendenti, ~2–4 min ciascuna.
# Prompt unico alla skill, due scene:
"Usa scroll-journey e generami DUE siti in parallelo:
  A) lo scroll entra dentro un treno in stazione, sezioni RAIL/CABIN/DINING;
  B) lo scroll entra dentro un'astronave e ne vende i vari pezzi.
 Lanciali in background, riportami i due path quando pronti."
▲ Isolamento

Fai girare ogni build in una cartella/worktree separata: gli assets generati (decine di frame) non devono collidere. Se usi sub‑agent, dà a ciascuno il proprio output dir e raccogli i due path alla fine.

L'agente mostra il prompt scroll-journey e i passaggi di generazione
L'agente espande il prompt in step: storyboard → frame → assembla → fix fluidità e reveal.

10Performance & gotchas

Peso degli assets

120 frame possono pesare. Usa WebP/AVIF, dimensiona al viewport reale, e considera due set (mobile/desktop) con matchMedia.

Preload progressivo

Mostra i primi 10–15 frame subito e continua a caricare il resto in background; blocca lo scrub finché non sei oltre la soglia di sicurezza.

Memoria su mobile

Tenere 120 Image decodificate satura la RAM. Riduci il frame‑count su mobile (es. 60) e/o scarica i frame fuori range.

will-change

Solo sugli elementi animati e solo durante l'animazione: abusarne aumenta il consumo GPU invece di ridurlo.

Accessibilità

Rispetta prefers-reduced-motion: in quel caso mostra un frame statico + testo scrollabile normale.

SEO

Il canvas non è indicizzabile: tieni il copy reale nel DOM (i blocchi .reveal), non dentro le immagini.

■ Il gotcha numero uno

Far girare due loop rAF (uno di Lenis, uno tuo) è la causa #1 di scroll a scatti. Un solo gsap.ticker che pilota lenis.raf(), come nello snippet del §04. Mai due.

11Dal demo al business

Beggiato chiude con l'osservazione più importante per chi vende questi siti: ora che generarli è quasi una commodity, il valore non è l'animazione — è il risultato di business. La metafora della vetrina:

Domanda 1 — Traffico

«Con una vetrina di un tipo o di un altro, entrano più clienti nel negozio?» → il sito porta effettivamente visite/lead qualificati?

Domanda 2 — Conversione

«Una volta entrati, qual è la disposizione ottimale degli oggetti perché si convertano in clienti paganti?» → CTA, gerarchia, frizione.

◆ Tradotto in pratica
  • Strumenta tutto: scroll‑depth, tempo sulla scena, click sulle CTA diegetiche.
  • A/B testa la prima scena (la "vetrina"): è ciò che decide se restano.
  • L'immersività deve servire il messaggio, non sostituirlo: una CTA chiara batte un frame in più.
  • Vendi l'esito (più lead, più conversioni), non "il sito 3D". Il 3D è il mezzo.
Sito scroll-telling con transizioni di camera tra sezioni
Il secondo sito: tre transizioni in cui la camera si muove. Stesso engine, nuovo racconto commerciale.

12Checklist & risorse

Checklist di build

  • ☐ Lenis + GSAP su un solo loop rAF
  • ☐ Canvas sticky, devicePixelRatio, object-fit:cover via drawImage
  • ☐ Frame zero‑padded, generati in ordine (image‑to‑image)
  • ☐ Preload + decode() prima del primo render; scrub bloccato finché non pronto
  • ☐ Reveal con trigger/easing/offset uniformi
  • ☐ Set asset ridotto per mobile + prefers-reduced-motion
  • ☐ Copy reale nel DOM (SEO/accessibilità)
  • ☐ Banner finale come componente riusabile
  • ☐ Analytics di scroll‑depth e CTA

Stack

lenisgsapScrollTrigger Gemini image APIVeo 3.1Higgsfield Claude Code + SkillViteWebP/AVIF

▲ Prossimo passo

Crea la cartella scroll-journey/ con il SKILL.md del §08 e un template/ che incorpora gli snippet dei §04–06. Dopodiché un singolo prompt — "entra in un treno, sezioni RAIL/CABIN/DINING" — ti produce il sito completo, assets inclusi.