L'Ingorgo più da discoteca: fasci più forti e numerosi, colpo di colore a ogni battuta, strobo a raffica, macchie di luce sulla folla, laser anche in piazza, palla a specchi su tutta la scena; alla festa si clicca solo per spostarsi; il manifesto più facile da toccare col dito; il video breve dell'Ingorgo (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:59:54 +01:00
co-authored by Claude Opus 5.5
parent 9f2b045ab0
commit 40d5780255
7 changed files with 241 additions and 19 deletions
+7 -1
View File
@@ -203,7 +203,13 @@ export class Game {
return { scene, x: Math.round(x), y: Math.round(y), facing, inventory: [...inventory], flags: { ...flags }, used: { ...used }, ended };
}
hotspots() { return (this.scene.hotspots ?? []).filter(h => check(h.when, this.state)); }
// In una zona accesa con `exitsOnly` (la festa dell'Ingorgo: Andrea, 30 ottobre) si clicca solo per andare da un
// posto all'altro: panchine, porte e statue restano dove sono, ma non rispondono.
hotspots() {
const list = (this.scene.hotspots ?? []).filter(h => check(h.when, this.state));
const busy = (this.story.zones ?? []).some(z => z.exitsOnly && this.state.flags[z.flag] && z.scenes.includes(this.state.scene));
return busy ? list.filter(h => h.exit) : list;
}
hotspot(id) { return this.hotspots().find(h => h.id === id); }
people() { return (this.scene.people ?? []).filter(p => check(p.when, this.state)); }
// Col mouse vale il punto preciso (l'ultimo della lista sta sopra). Col dito, `reach` allarga l'area: prima gli
+37 -10
View File
@@ -497,18 +497,18 @@ export const EFFECTS = {
if (e.wash) {
const [x, y, r] = e.wash, c = colors[bar % colors.length];
const g = ctx.createRadialGradient(x, y, 0, x, y, r);
g.addColorStop(0, `rgba(${c},${0.1 + 0.18 * hit ** 2})`);
g.addColorStop(0, `rgba(${c},${0.16 + 0.3 * hit ** 2})`);
g.addColorStop(1, `rgba(${c},0)`);
ctx.fillStyle = g;
ctx.fillRect(x - r, y - r, r * 2, r * 2);
}
for (const [k, [x, y]] of (e.from ?? []).entries()) {
for (let i = 0; i < (e.beams ?? 2); i++) {
const n = k * 3 + i, swing = Math.sin(time / (900 + n * 170) + n * 1.9) * (e.spread ?? 0.75);
const a = -Math.PI / 2 + swing + (k % 2 ? 0.25 : -0.25), len = e.len ?? 260, half = 0.045;
for (let i = 0; i < (e.beams ?? 3); i++) {
const n = k * 3 + i, swing = Math.sin(time / (700 + n * 150) + n * 1.9) * (e.spread ?? 0.85);
const a = -Math.PI / 2 + swing + (k % 2 ? 0.25 : -0.25), len = e.len ?? 340, half = 0.055;
const c = colors[(n + bar) % colors.length], tipX = x + Math.cos(a) * len, tipY = y + Math.sin(a) * len;
const g = ctx.createLinearGradient(x, y, tipX, tipY);
g.addColorStop(0, `rgba(${c},0.32)`);
g.addColorStop(0, `rgba(${c},${0.38 + 0.2 * hit})`);
g.addColorStop(1, `rgba(${c},0)`);
ctx.fillStyle = g;
ctx.beginPath();
@@ -521,19 +521,46 @@ export const EFFECTS = {
ctx.fillRect(Math.round(x) - 1, Math.round(y) - 1, 3, 3);
}
}
ctx.restore();
// Lo strobo: un lampo bianco brevissimo ogni `strobe` battute.
if (e.strobe && Math.floor(beat) % e.strobe === 0 && beat % 1 < 0.12) {
ctx.fillStyle = 'rgba(255,255,255,0.13)';
// Il colpo di colore su tutta la scena a ogni battuta (`pulse`: quanto forte).
if (e.pulse) {
ctx.fillStyle = `rgba(${colors[Math.floor(beat) % colors.length]},${e.pulse * hit ** 3})`;
ctx.fillRect(0, 0, WIDTH, HEIGHT);
}
ctx.restore();
// Lo strobo: un lampo bianco brevissimo ogni `strobe` battute, a raffica nell'ultima battuta di ogni frase.
const roll = Math.floor(beat) % 16 === 15 && (beat * 4) % 1 < 0.3;
if (e.strobe && ((Math.floor(beat) % e.strobe === 0 && beat % 1 < 0.12) || roll)) {
ctx.fillStyle = 'rgba(255,255,255,0.18)';
ctx.fillRect(0, 0, WIDTH, HEIGHT);
}
},
// Le macchie di luce che corrono sulla folla (`area`), colorate, a tempo: la pista da ballo.
floorspots(ctx, e, time) {
const [ax, ay, aw, ah] = e.area, colors = e.colors ?? ['255,60,200', '60,220,255', '120,255,90', '255,190,40', '170,90,255'];
const beat = time / (60000 / (e.bpm ?? 124)), hit = 1 - (beat % 1);
ctx.save();
ctx.globalCompositeOperation = 'lighter';
for (let i = 0; i < (e.count ?? 6); i++) {
const u = 0.5 + 0.5 * Math.sin(time / (1300 + i * 230) + i * 2.1), v = 0.5 + 0.5 * Math.sin(time / (1700 + i * 190) + i * 1.3);
const x = ax + u * aw, y = ay + v * ah, r = (e.size ?? 34) * (0.7 + 0.5 * v);
const c = colors[(i + Math.floor(beat / 2)) % colors.length];
const g = ctx.createRadialGradient(x, y, 0, x, y, r);
g.addColorStop(0, `rgba(${c},${0.22 + 0.2 * hit})`);
g.addColorStop(1, `rgba(${c},0)`);
ctx.fillStyle = g;
ctx.save();
ctx.translate(x, y); ctx.scale(1, 0.45); ctx.translate(-x, -y);
ctx.fillRect(x - r, y - r, r * 2, r * 2);
ctx.restore();
}
ctx.restore();
},
// I laser della tecno: righe sottili verdi (o del colore dato) che si aprono a ventaglio e si richiudono.
lasers(ctx, e, time) {
const [x, y] = e.at, n = e.count ?? 7, open = 0.35 + 0.35 * Math.sin(time / 1300), tilt = Math.sin(time / 2100) * 0.4;
ctx.save();
ctx.globalCompositeOperation = 'lighter';
ctx.strokeStyle = `rgba(${e.color ?? '90,255,110'},${0.35 + 0.2 * Math.sin(time / 90) ** 2})`;
ctx.strokeStyle = `rgba(${e.color ?? '90,255,110'},${0.45 + 0.25 * Math.sin(time / 90) ** 2})`;
ctx.lineWidth = 1;
ctx.beginPath();
for (let i = 0; i < n; i++) {