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:
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user