L'Ingorgo Sonoro: dal manifesto in piazza, ingrandito e toccato, «Quanto darei per essere all'Ingorgo stasera?!?» e l'effetto speciale; il centro di notte pieno zeppo di gente (sfondi di Codex), le console di Andrea (Suno) che si mescolano camminando, Andrea più grande alla festa, uscendo si torna a maggio. Baldoria al campo sportivo col bandierone della Sampdoria (Codex) (Andrea)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9Quo9nEdXyjXYF8ma
This commit is contained in:
2026-10-30 22:17:21 +01:00
co-authored by Claude Opus 5.5
parent 9cd8027256
commit c132e94ed8
47 changed files with 962 additions and 39 deletions
+83
View File
@@ -0,0 +1,83 @@
// L'incantesimo (story.closeups, type 'incanto'; Andrea, 30 ottobre: «Quanto darei per essere all'Ingorgo stasera?!?
// Effetto speciale e compare»). Sopra la scena di adesso, con Andrea dentro: i fasci delle luci da palco che girano
// sempre più svelti, i colpi di cassa che fanno pulsare l'aria, il lampo bianco; nel bianco cambia tutto (`swap`, di
// solito il flag della festa: lo sfondo diventa quello della notte), poi il bianco sfuma e piovono i coriandoli.
// Unità: i pixel della scena 640×360.
// I tempi, in secondi: i fasci, il lampo che sale, il cambio (a bianco pieno), il bianco che sfuma, la fine.
export const T = { beams: 0.2, flash: 1.9, swap: 2.2, clear: 3.0, end: 3.8 };
const COLORS = ['255,60,200', '60,220,255', '120,255,90', '255,190,40', '170,90,255'];
const clamp01 = k => Math.max(0, Math.min(1, k));
// Un numero a caso ma sempre lo stesso per lo stesso indice: i coriandoli non tremano da un fotogramma all'altro.
const hash = n => { const x = Math.sin(n * 127.1 + 311.7) * 43758.5453; return x - Math.floor(x); };
export function newIncanto(id) { return { id, t: 0, age: 0, events: [], beats: 0, swapped: false }; }
// Il tempo che passa e i suoni (un colpo di cassa a ogni battito, sempre più fitti; il lampo). Torna 'swap' nel
// momento del cambio, 'end' alla fine.
export function updateIncanto(c, dt) {
c.t += dt;
const beats = Math.floor(beatAt(Math.min(c.t, T.flash)));
for (; c.beats < beats; c.beats++) c.events.push({ kind: 'incanto-cassa', power: 0.5 + 0.5 * c.beats / 8 });
if (!c.boom && c.t >= T.flash) { c.boom = true; c.events.push({ kind: 'incanto-lampo', power: 1 }); }
if (!c.swapped && c.t >= T.swap) { c.swapped = true; return 'swap'; }
return c.t >= T.end ? 'end' : null;
}
// Quanti battiti sono passati: accelerano, da uno ogni mezzo secondo a uno ogni decimo.
export const beatAt = t => t <= 0 ? 0 : 2 * t + 1.6 * t * t;
// Quanto è bianco lo schermo: sale fino al cambio, resta pieno un attimo, poi sfuma.
export function whiteness(t) {
if (t < T.flash) return 0;
if (t < T.swap) return clamp01((t - T.flash) / (T.swap - T.flash));
return clamp01(1 - (t - T.swap) / (T.clear - T.swap));
}
export function drawIncanto(ctx, c) {
const t = c.t, before = t < T.swap;
ctx.save();
// Il colpo di cassa: un velo scuro che si apre e si chiude, l'aria che pulsa.
if (before) {
const b = beatAt(t), pulse = 1 - (b - Math.floor(b));
ctx.fillStyle = `rgba(10,0,30,${0.25 * clamp01(t / 0.6) * pulse})`;
ctx.fillRect(0, 0, 640, 360);
}
// I fasci: partono da sotto, ai lati, e spazzano il cielo; più passa il tempo, più sono e più girano svelti.
const strength = before ? clamp01((t - T.beams) / 0.5) : clamp01(1 - (t - T.swap) / 0.6);
if (strength > 0) {
ctx.globalCompositeOperation = 'lighter';
const n = 2 + Math.min(6, Math.floor(t * 3));
for (let i = 0; i < n; i++) {
const from = [i % 2 ? 560 : 80, 372], speed = 0.9 + i * 0.35 + t * 0.9;
const a = -Math.PI / 2 + Math.sin(t * speed + i * 1.7) * 0.9, len = 520, half = 0.07;
ctx.fillStyle = `rgba(${COLORS[i % COLORS.length]},${0.22 * strength})`;
ctx.beginPath();
ctx.moveTo(...from);
ctx.lineTo(from[0] + Math.cos(a - half) * len, from[1] + Math.sin(a - half) * len);
ctx.lineTo(from[0] + Math.cos(a + half) * len, from[1] + Math.sin(a + half) * len);
ctx.closePath();
ctx.fill();
}
// Le scintille che salgono da terra, a quadratini.
for (let i = 0; i < 40; i++) {
const life = (t * (0.5 + hash(i)) + hash(i + 50)) % 1, x = 20 + hash(i + 100) * 600, y = 360 - life * 300;
ctx.fillStyle = `rgba(${COLORS[i % COLORS.length]},${0.9 * strength * (1 - life)})`;
ctx.fillRect(Math.round(x), Math.round(y), 2, 2);
}
ctx.globalCompositeOperation = 'source-over';
}
// Il lampo.
const white = whiteness(t);
if (white > 0) { ctx.fillStyle = `rgba(255,255,250,${white})`; ctx.fillRect(0, 0, 640, 360); }
// Dopo il cambio, i coriandoli che cadono sulla festa.
if (!before) {
const k = (t - T.swap) / (T.end - T.swap);
for (let i = 0; i < 70; i++) {
const x = (hash(i) * 640 + Math.sin(t * 3 + i) * 8) % 640, y = -10 + (hash(i + 7) * 120 + k * 420 * (0.6 + hash(i + 3) * 0.6));
if (y > 360) continue;
ctx.fillStyle = `rgba(${COLORS[i % COLORS.length]},${clamp01(1.3 - k)})`;
ctx.fillRect(Math.round(x), Math.round(y), 3, 2);
}
}
ctx.restore();
}