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