Si comincia svegliandosi sulla panchina dei vecchietti dopo una notte brava: gli occhi che si aprono a fatica, la piazza sfocata e doppia; e dal Barone il telefonino col sito di San Piero, che si apre a tutto schermo, e il Muro del Sanpierino vero coi messaggi del 2007, «Aggiorna» compreso (Andrea)

- Il risveglio (src/engine/risveglio.js): primo piano sopra la scena, con le battute mentre le palpebre si aprono e
  i vecchietti che danno la notizia del palio; «Salta» lo salta.
- Il telefonino del Barone (src/engine/telefono.js): sale dal basso come il libro, la home di sanpiero50037.it dalla
  Wayback Machine nello schermo, il mattone del muro apre il muro.
- Il muro (assets/muro, tools/muro.py): la pagina vera rifatta statica dal muro riesumato, coi 24 messaggi del dump
  (senza email né IP) e quelli del gioco, che arrivano con la storia; «Scrivi» spento.
- Il gioco sgancia i service worker di altre app rimasti sul suo indirizzo (la Bussola su localhost:8080).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
This commit is contained in:
2026-10-31 09:52:34 +01:00
co-authored by Claude Opus 5.5
parent 1ea78161d1
commit c5953f04c0
93 changed files with 1112 additions and 68 deletions
+76
View File
@@ -0,0 +1,76 @@
// Il risveglio (story.closeups, type 'risveglio'; Andrea, 31 ottobre: «si ritrova aprendo piano piano gli occhi su
// una panchina, come svegliato da una notte brava, con effetto annebbiato»). Sopra la scena di adesso, vista da
// dentro gli occhi: le palpebre si aprono a fatica, ricascano, si riaprono; tutto è sfocato e doppio, e ondeggia.
// Le battute (`lines`, ognuna col suo `at` in secondi) partono mentre gli occhi si aprono; mentre Andrea parla il
// tempo del risveglio si ferma (la battuta va letta), l'ondeggiare no. Unità: i pixel della scena 640×360.
// Quanto sono aperti gli occhi (0 chiusi, 1 aperti) nel tempo: tre tentativi, l'ultimo è quello buono.
const OPEN = [[0, 0], [1.4, 0], [2.1, 0.22], [2.5, 0.04], [2.9, 0.04], [3.8, 0.5], [4.15, 0.12], [4.45, 0.2], [5.6, 0.85], [5.85, 0.55], [6.9, 1]];
export const END = 8.2;
const clamp01 = k => Math.max(0, Math.min(1, k));
const smooth = k => k * k * (3 - 2 * k);
export function openness(t) {
for (let i = 1; i < OPEN.length; i++) {
const [t1, o1] = OPEN[i];
if (t <= t1) { const [t0, o0] = OPEN[i - 1]; return o0 + (o1 - o0) * smooth(clamp01((t - t0) / (t1 - t0))); }
}
return 1;
}
// La nebbia: sfocatura e vista doppia, che si diradano fino a sparire alla fine.
export const haze = t => 1 - smooth(clamp01((t - 1.4) / (END - 1.6)));
export function newRisveglio(id) { return { id, t: 0, age: 0, fired: 0 }; }
// Il tempo che passa: torna le battute da dire adesso, e 'end' alla fine.
export function updateRisveglio(c, def, dt) {
c.t += dt;
const lines = def.lines ?? [], due = [];
while (c.fired < lines.length && c.t >= lines[c.fired].at) { const { at, ...e } = lines[c.fired++]; due.push(e); }
return { due, end: c.t >= END && c.fired >= lines.length };
}
let scratch = null;
export function drawRisveglio(ctx, c) {
const t = c.t, h = haze(t), open = openness(t), W = 640, H = 360;
ctx.save();
// La scena annebbiata: sfocata, e una seconda copia spostata che ondeggia (la vista doppia).
if (h > 0.01 && typeof document !== 'undefined') {
if (!scratch) { scratch = document.createElement('canvas'); scratch.width = W; scratch.height = H; }
const s = scratch.getContext('2d');
s.clearRect(0, 0, W, H);
s.drawImage(ctx.canvas, 0, 0, W, H);
const sway = 14 * h, dx = Math.sin(c.age * 1.3) * sway, dy = Math.sin(c.age * 0.9 + 1) * sway * 0.35;
ctx.filter = `blur(${(5 * h).toFixed(2)}px)`;
ctx.drawImage(scratch, 0, 0);
ctx.globalAlpha = 0.45 * h;
ctx.drawImage(scratch, dx, dy);
ctx.globalAlpha = 1;
ctx.filter = 'none';
// La luce del mattino che abbaglia.
ctx.fillStyle = `rgba(255,244,214,${0.22 * h})`;
ctx.fillRect(0, 0, W, H);
}
// Gli angoli scuri, che si aprono con gli occhi.
const v = ctx.createRadialGradient(W / 2, H / 2, 60 + 220 * open, W / 2, H / 2, 420);
v.addColorStop(0, 'rgba(10,2,4,0)');
v.addColorStop(1, `rgba(10,2,4,${0.85 * (1 - open * 0.75) * Math.max(h, 0.3)})`);
ctx.fillStyle = v;
ctx.fillRect(0, 0, W, H);
// Le palpebre: sopra e sotto, col bordo curvo e morbido; chiuse, si vede il rosso scuro della luce che passa.
if (open < 0.999) {
const gap = 200 * open, mid = H / 2, bulge = 70 + 60 * open;
ctx.filter = 'blur(6px)';
ctx.fillStyle = 'rgb(22,4,6)';
ctx.beginPath();
ctx.moveTo(-20, -20); ctx.lineTo(W + 20, -20); ctx.lineTo(W + 20, mid - gap + bulge);
ctx.quadraticCurveTo(W / 2, mid - gap - bulge, -20, mid - gap + bulge);
ctx.closePath(); ctx.fill();
ctx.beginPath();
ctx.moveTo(-20, H + 20); ctx.lineTo(W + 20, H + 20); ctx.lineTo(W + 20, mid + gap - bulge);
ctx.quadraticCurveTo(W / 2, mid + gap + bulge, -20, mid + gap - bulge);
ctx.closePath(); ctx.fill();
ctx.filter = 'none';
}
ctx.restore();
}