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
+35
View File
@@ -14,6 +14,8 @@ import * as micro from './micro.js';
import * as cerbottana from './cerbottana.js';
import { newCorsa, updateCorsa } from './corsa.js';
import { newIncanto, updateIncanto } from './incanto.js';
import { newRisveglio, updateRisveglio } from './risveglio.js';
import { newTelefono, updateTelefono, tapTelefono } from './telefono.js';
import * as condor from './condor.js';
import * as campana from './campana.js';
import * as poker from './poker.js';
@@ -312,6 +314,8 @@ export class Game {
this.queue.unshift(...(this.story.closeups[c.id].tap ?? []));
return true;
}
// Il telefonino del Barone: un tocco apre il sito, e sul sito il link apre il muro.
if (this.story.closeups[c.id].type === 'telefono') return tapTelefono(c, this.story.closeups[c.id]);
if (this.story.closeups[c.id].type) return false; // torcia e minigiochi non si sfogliano
if (c.step === 'cover') Object.assign(c, { step: 'title', t: 0 });
else if (c.step === 'title') Object.assign(c, { step: 'turn', t: 0 });
@@ -352,6 +356,14 @@ export class Game {
this.queue.unshift(...(def.complete ?? []));
return true;
}
// Il telefonino: chi ha visto il muro, chiudendo, lo commenta.
if (def?.type === 'telefono') { if (c.step === 'wall') this.queue.unshift(...(def.closing ?? [])); return true; }
// Il risveglio, saltato a metà (Esc), finisce comunque: le battute rimaste e poi quelle di dopo.
if (def?.type === 'risveglio') {
const left = (def.lines ?? []).slice(c.fired).map(({ at, ...e }) => e);
this.queue.unshift(...left, ...(def.complete ?? []));
return true;
}
// I minigiochi: a partita finita (risultato deciso, anche se il conteggio si sta ancora mostrando) la battuta
// di chi ha vinto; uscendo prima, quella di chi lascia.
if (MINIGAMES[def?.type]) {
@@ -364,6 +376,13 @@ export class Game {
return true;
}
// Il muro del telefonino (una pagina vera, assets/muro): «Home» torna alla home del sito.
closeupHome() {
const c = this.closeup;
if (!c || this.story.closeups[c.id].type !== 'telefono' || c.step !== 'wall') return false;
Object.assign(c, { step: 'site', t: 0 });
return true;
}
// Bocce e biliardo: si tiene premuto per caricare la forza e si lascia per tirare (mouse, dito o spazio).
minigame() { const c = this.closeup; return c ? MINIGAMES[this.story.closeups[c.id].type] ?? null : null; }
closeupPress(pointerId) { const m = this.minigame(); return !!m && !this.speech && !!m.press?.(this.closeup, pointerId); }
@@ -377,6 +396,8 @@ export class Game {
const c = this.closeup;
const m = this.minigame();
if (m) return !!m.aim?.(c, [x, y]);
// Sul sito del telefonino conta dove si tocca (il link del muro).
if (c && this.story.closeups[c.id].type === 'telefono') { c.aim = [x, y]; return true; }
if (!c || this.story.closeups[c.id].type !== 'torch' || !Number.isFinite(x) || !Number.isFinite(y)) return false;
c.beam = [Math.max(70, Math.min(570, x)), Math.max(65, Math.min(295, y))];
return true;
@@ -527,6 +548,18 @@ export class Game {
if (now === 'end') this.closeupClose();
return;
}
if (this.story.closeups[c.id].type === 'telefono') {
updateTelefono(c, dt);
if (c.step === 'wall' && !c.lines && c.t >= CLOSEUP_PAUSE) c.lines = [...(this.story.closeups[c.id].comment ?? [])];
if (c.lines?.length) this.run(c.lines.shift());
return;
}
if (this.story.closeups[c.id].type === 'risveglio') {
const { due, end } = updateRisveglio(c, this.story.closeups[c.id], dt);
for (const e of due) this.run(e);
if (end) this.closeupClose();
return;
}
const m = this.minigame();
if (m) {
m.update(c, dt);
@@ -571,6 +604,8 @@ export class Game {
: type === 'viaggio' ? { id: e.closeup, t: 0, age: 0, events: [], typed: 0 }
: type === 'corsa' ? newCorsa(e.closeup)
: type === 'incanto' ? newIncanto(e.closeup)
: type === 'risveglio' ? newRisveglio(e.closeup)
: type === 'telefono' ? newTelefono(e.closeup)
: type === 'poster' ? { id: e.closeup, step: 'poster', t: 0, age: 0 }
// `level`: «Alla bona», «Seria» o «A fassi male», scelto nel dialogo con l'avversario.
// `decks`: il mazzo da usare quando il suo flag è acceso (briscola: le napoletane al posto delle toscane).
+18 -2
View File
@@ -17,6 +17,8 @@ import { drawCampana } from './campana-render.js';
import { drawPoker } from './poker-render.js';
import { drawViaggio } from './viaggio.js';
import { drawIncanto } from './incanto.js';
import { drawRisveglio } from './risveglio.js';
import { drawTelefono, telefonoHint } from './telefono.js';
import { drawBriscola } from './briscola-render.js';
import { drawTressette } from './tressette-render.js';
import { drawScopa } from './scopa-render.js';
@@ -178,7 +180,9 @@ export function render(ctx, game, { hover, spots, debug, time = performance.now(
const andrea = sprites.get('andrea');
const drawAndreaNow = () => andrea ? drawSprite(ctx, andrea, game.pose(), s.x, s.y, game.scaleAt(s.y), s.facing)
: drawAndrea(ctx, s.x, s.y, game.scaleAt(s.y), s.facing, game.walking ? game.stride / 6 : null);
const queue = [{ y: s.y, draw: drawAndreaNow }];
// Durante il risveglio si guarda dagli occhi di Andrea: lui non si vede.
const pov = game.closeup && game.story.closeups?.[game.closeup.id]?.type === 'risveglio';
const queue = pov ? [] : [{ y: s.y, draw: drawAndreaNow }];
for (const p of game.people()) {
const sheet = sprites.get(p.who);
if (sheet) queue.push({ y: p.at[1], draw: () => drawSprite(ctx, sheet, game.personPose(p), ...p.at, game.scaleAt(p.at[1]), p.facing ?? 1, p.size) });
@@ -919,6 +923,15 @@ function drawCloseup(ctx, game, time) {
}
// L'incantesimo va sopra la scena (con Andrea dentro), già disegnata.
if (def.type === 'incanto') { drawIncanto(ctx, c); return; }
// Il risveglio, visto da dentro gli occhi: anche lui sopra la scena già disegnata.
if (def.type === 'risveglio') { drawRisveglio(ctx, c); return; }
// Il telefonino del Barone: sale come il libro, e il sito nello schermo si allarga a tutta la scena.
if (def.type === 'telefono') {
const pic = src => { if (!backgrounds.has(src)) loadBackground(src); return backgrounds.get(src); };
drawTelefono(ctx, c, def, { phone: pic(def.phone), site: pic(def.site) }, time);
closeupHint(ctx, game.speech ? null : telefonoHint(c), time);
return;
}
// Il manifesto ingrandito: la scena si scurisce e lui viene avanti, poi l'invito a toccarlo.
if (def.type === 'poster') {
if (!backgrounds.has(def.picture)) loadBackground(def.picture);
@@ -1043,7 +1056,10 @@ function drawCloseup(ctx, game, time) {
ctx.restore();
// Il suggerimento in alto, finché c'è qualcosa da sfogliare.
const hint = c.step === 'cover' ? 'Clic o tocco: apri il libro' : c.step === 'title' ? 'Clic o tocco: gira la pagina' : null;
if (hint && !game.speech && c.age > 0.5) {
closeupHint(ctx, !game.speech && c.age > 0.5 ? hint : null, time);
}
function closeupHint(ctx, hint, time) {
if (hint) {
ctx.save();
ctx.globalAlpha = 0.65 + 0.35 * Math.sin(time / 300);
ctx.font = `7px ${FONT}`;
+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();
}
+81
View File
@@ -0,0 +1,81 @@
// Il telefonino del Barone (story.closeups, type 'telefono'; Andrea, 31 ottobre: «prenda il cellulare, nello stile
// di come si sfoglia la storia infinita, e quando si clicca viene a tutto schermo il sito di San Piero»).
// Come il libro: la scena si scurisce e le mani alzano il telefonino dal basso; nello schermo c'è il sito
// (`site`, un'immagine 16:9 della home di sanpiero50037.it). Un tocco: lo schermo si allarga a tutta la scena.
// Sul sito si tocca il link (`link`, in pixel dell'immagine del sito) e si apre il muro: una pagina vera
// (assets/muro, la mette main.js sopra la scena), che qui sotto lascia solo il fondo. A muro aperto partono le
// battute `comment`, e chiudendo `closing`. Unità: i pixel della scena 640×360.
export const RISE = 0.45, ZOOM = 0.55;
const W = 640, H = 360;
const clamp01 = k => Math.max(0, Math.min(1, k));
const ease = k => (1 - Math.cos(Math.PI * clamp01(k))) / 2;
export function newTelefono(id) { return { id, step: 'phone', t: 0, age: 0, aim: null }; }
// Il tempo: lo zoom finisce sul sito; a muro aperto, dopo un attimo, le battute (una alla volta, come il libro).
export function updateTelefono(c, dt) {
c.t += dt;
if (c.step === 'zoom' && c.t >= ZOOM) Object.assign(c, { step: 'site', t: 0 });
}
// Dove sta il link del muro sulla scena, a sito aperto: il riquadro `link` riportato da immagine a scena.
export function linkBox(def, site) {
const [x, y, w, h] = def.link, kx = W / site[0], ky = H / site[1];
return [x * kx, y * ky, w * kx, h * ky];
}
const inside = ([x, y, w, h], [px, py]) => px >= x && px <= x + w && py >= y && py <= y + h;
// Un tocco: sul telefonino apre il sito; sul sito, solo sul link, apre il muro. Torna true se è successo qualcosa.
export function tapTelefono(c, def) {
if (c.step === 'phone') { if (c.age < RISE) return false; Object.assign(c, { step: 'zoom', t: 0 }); return true; }
if (c.step === 'site' && c.aim && inside(linkBox(def, def.siteSize), c.aim)) { Object.assign(c, { step: 'wall', t: 0 }); return true; }
return false;
}
// Il riquadro dello schermo sulla scena, col telefonino che sale dal basso.
function phoneRect(def, img, age) {
const rise = 1 - clamp01(age / RISE), k = def.phoneScale ?? 1;
const w = img.width * k, h = img.height * k, x = Math.round((W - w) / 2), y = Math.round(H - h + 140 * rise * rise);
return { x, y, w, h, k };
}
export function drawTelefono(ctx, c, def, { phone, site }, time) {
ctx.save();
ctx.fillStyle = `rgba(8, 5, 12, ${Math.min(0.78, c.age / 0.35 * 0.78)})`;
ctx.fillRect(0, 0, W, H);
if (!phone || !site) { ctx.restore(); return; }
const p = phoneRect(def, phone, c.age), [sx, sy, sw, sh] = def.screen;
const screen = [p.x + sx * p.k, p.y + sy * p.k, sw * p.k, sh * p.k];
const z = c.step === 'phone' ? 0 : c.step === 'zoom' ? ease(c.t / ZOOM) : 1;
// Il telefonino, finché lo schermo non ha preso tutta la scena; poi sfuma.
if (z < 1) {
ctx.globalAlpha = 1 - z;
ctx.imageSmoothingEnabled = false;
ctx.drawImage(phone, p.x, p.y, p.w, p.h);
ctx.globalAlpha = 1;
}
// Il sito: dentro lo schermo, poi sempre più grande fino a tutta la scena. Lo schermo ha le sue proporzioni:
// il sito ci sta intero, centrato, sul fondo del sito.
const r = screen.map((v, i) => v + ([0, 0, W, H][i] - v) * z);
const fit = Math.min(r[2] / site.width, r[3] / site.height), dw = site.width * fit, dh = site.height * fit;
ctx.fillStyle = def.paper ?? '#f7e0c0';
ctx.fillRect(r[0], r[1], r[2], r[3]);
ctx.imageSmoothingEnabled = true;
ctx.drawImage(site, r[0] + (r[2] - dw) / 2, r[1] + (r[3] - dh) / 2, dw, dh);
// Sotto il muro (la pagina vera) resta il fondo del sito.
if (c.step === 'wall') { ctx.fillStyle = def.paper ?? '#f7e0c0'; ctx.fillRect(0, 0, W, H); }
// Il link da toccare: un riquadro che pulsa intorno al mattone del muro.
if (c.step === 'site') {
const [x, y, w, h] = linkBox(def, def.siteSize), pulse = 0.5 + 0.5 * Math.sin(time / 260);
ctx.strokeStyle = `rgba(255, 210, 90, ${0.5 + 0.5 * pulse})`;
ctx.lineWidth = 2 + pulse;
ctx.strokeRect(Math.round(x) - 2, Math.round(y) - 2, Math.round(w) + 4, Math.round(h) + 4);
}
ctx.imageSmoothingEnabled = false;
ctx.restore();
}
// Il suggerimento in alto, finché c'è qualcosa da toccare.
export const telefonoHint = c => c.step === 'phone' && c.age > RISE ? 'Clic o tocco: guarda il sito'
: c.step === 'site' ? 'Clic o tocco sul Muro del Sanpierino' : null;
+13 -2
View File
@@ -65,6 +65,10 @@ $('effects-volume').addEventListener('input', e => { ambience.configure({ volume
showSound();
window.music = music; // per i test nel browser
// Il gioco non usa service worker: se ne è rimasto uno di un'altra app passata su questo indirizzo (la Bussola su
// localhost:8080, 31 ottobre), si sgancia, sennò intercetta le pagine aperte dal gioco (il muro nel telefonino).
navigator.serviceWorker?.getRegistrations().then(list => list.forEach(r => r.unregister())).catch(() => {});
fetch(versioned('assets/voices/index.json')).then(r => r.ok ? r.json() : {}).then(index => { voices.index = index; }).catch(() => {});
window.voices = voices;
let voiced = null;
@@ -75,7 +79,7 @@ function start(state, rione = null) {
game = new Game(story, { state, onSave: data => storage && writeSave(storage, data), voiceTime: (who, text) => voices.seconds(who, text) });
command = new Command(game);
if (!state && rione) game.state.flags['rione:' + rione] = true;
if (!state) game.go(story.start.scene, story.start.at);
if (!state) game.go(story.start.scene, story.start.at, story.start.face);
$('title').hidden = true;
$('ending').hidden = true;
running = true;
@@ -1094,7 +1098,14 @@ function updateUi() {
setProp($('spots-button'), 'hidden', !running || !!game.closeup);
setProp($('notebook-button'), 'hidden', !running || !story.notebook || !!game.closeup);
setProp($('closeup-close'), 'hidden', !running || !game.closeup);
setText($('closeup-close'), torch ? c.step === 'done' ? 'Continua' : 'Torna fuori' : mg ? c.step === 'result' ? mg.back : 'Lascia stare' : 'Chiudi');
// Il Muro del Sanpierino: a muro aperto sul telefonino del Barone, la pagina vera sopra la scena. Ogni volta che si
// riapre si ricarica (i messaggi nuovi della storia).
const muro = $('muro'), wall = running && c && story.closeups[c.id]?.type === 'telefono' && c.step === 'wall';
if (wall && muro.hidden) muro.src = versioned('assets/muro/index.html');
if (!wall && !muro.hidden) muro.src = 'about:blank';
setProp(muro, 'hidden', !wall);
if (wall) muro.style.setProperty('--muro-scale', String(canvas.clientWidth / 1067));
setText($('closeup-close'), torch ? c.step === 'done' ? 'Continua' : 'Torna fuori' : mg ? c.step === 'result' ? mg.back : 'Lascia stare' : c && story.closeups[c.id]?.type === 'risveglio' ? 'Salta' : 'Chiudi');
setProp($('panel'), 'inert', !!c);
canvas.classList.toggle('torch', !!torch);
canvas.classList.toggle('bocce', !!mg);
+77 -12
View File
@@ -64,7 +64,9 @@ export const story = {
title: 'La Leggenda del Regolo',
// La mappa (pulsante «Mappa»): ogni scena con `map: [x, y]` (percentuali della pianta) ci compare quando è
// stata visitata; le strade sono le uscite fra scene. Un posto nuovo entra con la sua riga `map`.
start: { scene: 'piazza', at: [250, 320] },
// Si comincia sulla panchina dei vecchietti, dopo una notte brava (Andrea, 31 ottobre): il risveglio è il primo
// piano `risveglio`, che parte entrando in piazza la prima volta.
start: { scene: 'piazza', at: [96, 328], face: 'left' },
// Le fasi del giorno (idea di Andrea, 24 ottobre): seguono la storia, non l'orologio. Il primo capitolo comincia
// di pomeriggio; con le impronte del Regolo viene il tramonto, e quando Andrea ha la torcia è sera. Sulle scene
// all'aperto un velo di colore (`tint`); luci ed effetti possono valere solo in certe fasi (`phase`).
@@ -120,7 +122,8 @@ export const story = {
cardetole: 'Io corro per Cardetole, i verdi: gioco in casa.',
campomigliaio: 'Io corro per Campomigliaio, i blu.',
} },
'E stamattina, in piazza, è successo qualcosa...',
// [DA ANDREA: la riga che porta al risveglio sulla panchina]
'Ieri sera s\'è fatto tardi. Molto tardi...',
],
// Colonna sonora di Andrea (fatta con Suno): titolo, paese, ricordi (fine e Trebbio).
// splash: la musichetta del cartello d'apertura (tools/jingle.py), suonata una volta sola.
@@ -340,6 +343,54 @@ export const story = {
{ closeup: 'ingorgo' },
],
},
// Il Muro del Sanpierino sul telefonino del Barone (Andrea, 31 ottobre): sale come il libro, nello schermo la
// home di sanpiero50037.it (la Wayback Machine, 31 dicembre 2007, senza la data in alto); un tocco la porta a
// tutto schermo, e il mattone «Nuovo muro del Sanpierino» apre il muro: la pagina vera (assets/muro, fatta da
// tools/muro.py dal muro riesumato in ~/Workspaces/misc/spswall), coi messaggi veri del 2007 (Andrea: «quelli
// veri»; niente email, «Scrivi» spento) e sopra quelli del gioco qui sotto, ognuno con la sua condizione:
// «Aggiorna» ricarica, e la storia che va avanti ne fa comparire di nuovi. [DA ANDREA: messaggi e battute]
muro_sanpierino: {
type: 'telefono',
phone: 'assets/closeups/telefonino-barone.png', // Codex, prompts/closeups/telefonino-barone.txt
screen: [120, 25, 106, 142], // lo schermo, in pixel del telefonino
site: 'assets/closeups/sanpiero50037-home.png', siteSize: [1067, 600],
link: [48, 42, 302, 99], // il mattone del muro, in pixel del sito
paper: '#f7e1c0',
messages: [
{ nick: 'Uno di Cardetole', date: '2007-05-26 00:55:12', text: 'Quest\'anno il palio è nostro!!! [bouncer]\nGli altri rioni si preparino a piangere. [14_bigmouth]' },
{ nick: 'Baron De Duke', date: '2007-05-26 01:40:37', text: 'Chi ha trovato un paio di mutande, le riporti al circolino.\nSenza fare domande. [15_weeping]' },
{ nick: 'Il Regolo', date: '2007-05-26 03:12:08', text: 'IL PALIO ME LO SONO RIPRESO IO. [22_yikes]\nChi lo rivuole, venga a prenderselo. Se ha coraggio.' },
// Quelli che arrivano con la storia: si vedono riaprendo il muro, o con «Aggiorna».
{ when: 'indizio_regolo', nick: 'Uno che passava', date: '2007-05-26 18:20:44', text: 'Ma avete visto le impronte sotto la Fortezza??? Grandi come una pizza!!! [24_shocked]' },
{ when: 'casetta_aperta', nick: 'Il Regolo', date: '2007-05-26 21:03:51', text: 'Ho sentito che qualcuno ha trovato una torcia. Io ho sempre la luna. [28_spin]' },
],
comment: [
{ say: '«Il palio me lo sono ripreso io». Firmato: il Regolo. Alle tre di notte.' },
{ say: 'E sotto ci sono io, con le mutande. Nessuno ha risposto.', who: 'barondeduke', pose: 'facepalm' },
],
closing: [
{ say: 'Barone, il sito è tuo: non puoi vedere chi l\'ha scritto?' },
{ say: 'Potrei. Ma ci vuole il computer di casa. Il telefonino, più di così, non fa.', who: 'barondeduke', pose: 'shrug' },
],
},
// L'inizio della partita: Andrea apre gli occhi sulla panchina dei vecchietti, dopo una notte brava (Andrea,
// 31 ottobre). Le battute `lines` arrivano mentre gli occhi si aprono; `complete` a occhi aperti.
// [DA ANDREA: le battute sono provvisorie]
risveglio: {
type: 'risveglio',
lines: [
{ at: 0.8, say: 'Mmmh... che botta.' },
{ at: 3.0, say: 'Ma... dove sono?' },
{ at: 5.9, say: 'Una panchina. In piazza. Coi vecchietti.' },
],
complete: [
{ say: 'Oh, s\'è svegliato il bello addormentato! Bella nottata, eh?', who: 'vecchietta', pose: 'laugh' },
{ say: 'Ma l\'hai saputo? Il palio è sparito!', who: 'vecchietto', pose: 'point' },
{ say: 'Il palio? Il quadro che hanno dipinto i ragazzi delle medie?' },
{ say: 'Lo tenevano a scuola: stamani la Gianna ha trovato il posto vuoto.', who: 'vecchietta', pose: 'explain' },
{ say: 'La festa è domani: se nessuno lo ritrova, salta tutto. Tocca a me.' },
],
},
// L'incantesimo dell'Ingorgo Sonoro, dal manifesto in piazza: luci, cassa, lampo, ed è la notte della festa.
ingorgo: {
type: 'incanto',
@@ -735,7 +786,7 @@ export const story = {
id: 'verso_calimara', name: 'Via Calimara, verso la farmacia', box: [180, 334, 380, 26], at: [360, 354], exit: { scene: 'via_calimara', at: [365, 212], face: 'down' },
},
],
enter: [{ if: '!intro', then: [{ set: 'intro' }, { say: 'Il palio è sparito! Il quadro che hanno dipinto i ragazzi delle medie.' }, { say: 'Lo tenevano a scuola: stamani la Gianna ha trovato il posto vuoto.' }, { say: 'La festa è domani: se nessuno lo ritrova, salta tutto. Tocca a me.' }] }],
enter: [{ if: '!intro', then: [{ set: 'intro' }, { closeup: 'risveglio' }] }],
},
fortezza: {
name: 'Sotto le mura della Fortezza',
@@ -2084,15 +2135,7 @@ export const story = {
},
actions: {
look: [{ say: 'Il BaronDeDuke, col cappello da cowboy. Ha l\'aria di uno che aspetta qualcuno, e che spera non lo veda nessuno.' }],
talk: [
{ say: 'Barone! Che ci fai qui?' },
{ say: 'Aspetto uno che mi deve riportare le mutande.', who: 'barondeduke' },
{ say: 'Le... mutande?' },
{ say: 'Le ho perse a poker, ieri sera.', who: 'barondeduke', pose: 'facepalm' },
{ say: 'Avevo due donne e due jack. Entra la terza donna, e penso: «Male male si divide».', who: 'barondeduke', pose: 'cards' },
{ say: 'E invece?' },
{ say: 'L\'altro aveva colore.', who: 'barondeduke', pose: 'shrug' },
],
talk: [{ dialogue: 'barondeduke' }],
},
},
],
@@ -3899,6 +3942,28 @@ export const story = {
},
},
},
// Il BaronDeDuke nella discesa verso la polisportiva: le mutande perse a poker, e il Muro del Sanpierino sul
// telefonino (Andrea, 31 ottobre: lui è il webmaster di sanpiero50037.it).
barondeduke: {
nodes: {
start: { options: [
{ text: 'Barone! Che ci fai qui?', then: [
{ say: 'Aspetto uno che mi deve riportare le mutande.', who: 'barondeduke' },
{ say: 'Le... mutande?' },
{ say: 'Le ho perse a poker, ieri sera.', who: 'barondeduke', pose: 'facepalm' },
{ say: 'Avevo due donne e due jack. Entra la terza donna, e penso: «Male male si divide».', who: 'barondeduke', pose: 'cards' },
{ say: 'E invece?' },
{ say: 'L\'altro aveva colore.', who: 'barondeduke', pose: 'shrug' },
] },
// [DA ANDREA: le battute sono provvisorie]
{ text: 'Hai visto cosa c\'era sul muro del Sanpierino?', next: 'end', then: [
{ say: 'Il muro? Aspetta, ce l\'ho qui sul telefonino.', who: 'barondeduke', pose: 'explain' },
{ closeup: 'muro_sanpierino' },
] },
{ text: 'Ci vediamo, Barone.', next: 'end' },
] },
},
},
// L'Arcu davanti al circolino (battute di Andrea, 25 ottobre). Lavora alla Casa dell'Armadio: «Armando» è apposta.
arcu: {
nodes: {