Il risveglio leggero per davvero: palpebre disegnate su una tela piccola e allargate con l'interpolazione (bordo morbido senza filtri né passate), tutto sulla tela principale, una versione sola; l'arretrato grafico dopo il risveglio passa da 24 secondi a un decimo. «Fanfara» si legge senza accenti (Andrea)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
This commit is contained in:
2026-10-31 13:40:43 +01:00
co-authored by Claude Fable 5.1
parent 4d2278a8f8
commit b10f031133
10 changed files with 35 additions and 62 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4 -4
View File
@@ -1984,8 +1984,8 @@
"sec": 2.79
},
"andrea|La fanfara degli alpini. Quando sfilavano per il paese suonando, s'affacciava tutto San Piero.": {
"file": "andrea-9904de279c.mp3",
"sec": 6.69
"file": "andrea-a372a71849.mp3",
"sec": 6.3
},
"fanfara|Un'altra? Questa la sanno anche i sassi!": {
"file": "fanfara-d1179f98fa.mp3",
@@ -3676,8 +3676,8 @@
"sec": 5.94
},
"andrea|La Smarronata del 1993! Gli alpini, sotto il ponte: le bruciate, la polenta, il vino, la fanfara...": {
"file": "andrea-2c7369689a.mp3",
"sec": 10.99
"file": "andrea-4266372068.mp3",
"sec": 10.39
},
"andrea|La macchina del tempo è sempre lì che aspetta.": {
"file": "andrea-81cad76b69.mp3",
+1 -19
View File
@@ -182,7 +182,6 @@ export function render(ctx, game, { hover, spots, debug, time = performance.now(
: drawAndrea(ctx, s.x, s.y, game.scaleAt(s.y), s.facing, game.walking ? game.stride / 6 : null);
// 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';
if (!pov) risveglioLayer(ctx, false);
const queue = pov ? [] : [{ y: s.y, draw: drawAndreaNow }];
for (const p of game.people()) {
const sheet = sprites.get(p.who);
@@ -912,18 +911,6 @@ function drawDebug(ctx, game, { mouse, points = [], help }) {
ctx.restore();
}
// La tela del risveglio (risveglio.js): una tela a parte sopra la scena, che c'è solo finché il risveglio dura.
function risveglioLayer(ctx, on = true) {
if (typeof document === 'undefined' || !ctx.canvas?.after) return null;
let layer = document.getElementById('risveglio');
if (!on) { layer?.remove(); return null; }
if (!layer) {
layer = Object.assign(document.createElement('canvas'), { id: 'risveglio', width: WIDTH, height: HEIGHT });
ctx.canvas.after(layer);
}
return layer.getContext('2d');
}
// --- Il primo piano di un libro (story.closeups, game.closeup): la scena si oscura, le mani alzano il libro dal basso.
// Le immagini hanno etichetta e pagine vuote: i testi si scrivono qui, nitidi. Una pagina sola gira, a fisarmonica
// sulla piega; sotto c'è già l'ultima pagina.
@@ -962,12 +949,7 @@ 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.
// Sul telefono (schermo touch) la versione leggera, senza filtri; sul computer quella con la sfocatura vera.
if (def.type === 'risveglio') {
const layer = risveglioLayer(ctx), light = matchMedia('(pointer: coarse)').matches;
if (layer) drawRisveglio(layer, c, backgrounds.get(game.backdrop()), { light });
return;
}
if (def.type === 'risveglio') { drawRisveglio(ctx, c, backgrounds.get(game.backdrop())); 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); };
+29 -34
View File
@@ -30,12 +30,12 @@ export function updateRisveglio(c, def, dt) {
return { due, end: c.t >= END && c.fired >= lines.length };
}
// Il risveglio si disegna su una tela sua, appoggiata sopra la scena solo finché dura (render.js, `#risveglio`).
// Due versioni (Andrea, 31 ottobre): sul computer quella bella, con la sfocatura vera (ctx.filter); sul telefono
// (`light`) una leggera senza filtri, perché senza una scheda video la sfocatura a ogni fotogramma pesa: la nebbia è
// lo sfondo rimpicciolito e riallargato morbido, il bordo delle palpebre tante passate trasparenti.
const SMALL = [80, 45];
let small = null, smallOf = null;
// Niente filtri e niente passate a tutto schermo (ctx.filter, il filter CSS e sedici palpebre trasparenti a ogni
// fotogramma lasciavano alla grafica un arretrato che si pagava dopo, a risveglio finito: il gioco si inceppava).
// La nebbia è lo sfondo rimpicciolito e riallargato morbido; le palpebre si disegnano nette su una tela piccola e
// si allargano di cinque volte con l'interpolazione, che sfuma il bordo da sé. Una versione sola, computer e telefono.
const SMALL = [80, 45], LIDS = [128, 72];
let small = null, smallOf = null, lids = null;
function blurred(bg) {
if (smallOf !== bg) {
small ??= Object.assign(document.createElement('canvas'), { width: SMALL[0], height: SMALL[1] });
@@ -47,23 +47,19 @@ function blurred(bg) {
return small;
}
export function drawRisveglio(ctx, c, bg, { light = false } = {}) {
export function drawRisveglio(ctx, c, bg) {
const t = c.t, h = haze(t), open = openness(t), W = 640, H = 360;
ctx.save();
ctx.clearRect(0, 0, W, H);
// La scena annebbiata: lo sfondo sfocato sopra a quello nitido, più una seconda copia spostata che ondeggia (la
// vista doppia); tutto si dirada col passare del tempo.
if (h > 0.01 && bg) {
const sway = 14 * h, dx = Math.sin(c.age * 1.3) * sway, dy = Math.sin(c.age * 0.9 + 1) * sway * 0.35;
const fog = light ? blurred(bg) : bg;
// La scena annebbiata (lo sfondo sfocato sopra) e la vista doppia (una seconda copia, spostata, che ondeggia).
if (h > 0.01 && bg && typeof document !== 'undefined') {
const fog = blurred(bg), sway = 14 * h, dx = Math.sin(c.age * 1.3) * sway, dy = Math.sin(c.age * 0.9 + 1) * sway * 0.35;
ctx.imageSmoothingEnabled = true;
if (!light) ctx.filter = `blur(${(5 * h).toFixed(2)}px)`;
ctx.globalAlpha = h;
ctx.globalAlpha = 0.85 * h;
ctx.drawImage(fog, 0, 0, W, H);
ctx.globalAlpha = 0.45 * h;
ctx.globalAlpha = 0.4 * h;
ctx.drawImage(fog, dx, dy, W, H);
ctx.globalAlpha = 1;
ctx.filter = 'none';
ctx.imageSmoothingEnabled = false;
// La luce del mattino che abbaglia.
ctx.fillStyle = `rgba(255,244,214,${0.22 * h})`;
ctx.fillRect(0, 0, W, H);
@@ -74,23 +70,22 @@ export function drawRisveglio(ctx, c, bg, { light = false } = {}) {
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 sfumato; chiuse, si vede il rosso scuro della luce che passa.
if (open < 0.999) {
const mid = H / 2, bulge = 70 + 60 * open, lid = (gap, sign) => {
const edge = mid + sign * gap, side = edge - sign * bulge, far = sign < 0 ? -20 : H + 20;
ctx.beginPath();
ctx.moveTo(-20, far); ctx.lineTo(W + 20, far); ctx.lineTo(W + 20, side);
ctx.quadraticCurveTo(W / 2, edge * 2 - side, -20, side);
ctx.closePath(); ctx.fill();
};
if (light) {
// Il bordo morbido: passate trasparenti che sporgono verso l'occhio aperto, un pixel di più l'una.
ctx.fillStyle = 'rgba(22,4,6,0.11)';
for (let i = 16; i > 0; i--) { lid(200 * open - i, -1); lid(200 * open - i, 1); }
} else ctx.filter = 'blur(6px)';
ctx.fillStyle = 'rgb(22,4,6)';
lid(200 * open, -1); lid(200 * open, 1);
ctx.filter = 'none';
// Le palpebre: sopra e sotto, col bordo curvo; chiuse, si vede il rosso scuro della luce che passa.
if (open < 0.999 && typeof document !== 'undefined') {
lids ??= Object.assign(document.createElement('canvas'), { width: LIDS[0], height: LIDS[1] });
const l = lids.getContext('2d'), k = LIDS[0] / W, mid = H / 2 * k, gap = 200 * open * k, bulge = (70 + 60 * open) * k;
l.clearRect(0, 0, ...LIDS);
l.fillStyle = 'rgb(22,4,6)';
for (const sign of [-1, 1]) {
const edge = mid + sign * gap, side = edge - sign * bulge, far = sign < 0 ? -4 : LIDS[1] + 4;
l.beginPath();
l.moveTo(-4, far); l.lineTo(LIDS[0] + 4, far); l.lineTo(LIDS[0] + 4, side);
l.quadraticCurveTo(LIDS[0] / 2, edge * 2 - side, -4, side);
l.closePath(); l.fill();
}
ctx.imageSmoothingEnabled = true;
ctx.drawImage(lids, 0, 0, W, H);
ctx.imageSmoothingEnabled = false;
}
ctx.restore();
}
-2
View File
@@ -82,8 +82,6 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h
text-shadow: 2px 0 #000, -2px 0 #000, 0 2px #000, 0 -2px #000, 2px 2px #000, -2px -2px #000; }
#speech:empty { display: none; }
#speech { z-index: 3; }
/* La tela del risveglio, sopra la scena solo finché dura (render.js). */
#risveglio { position: absolute; left: 0; top: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; }
/* Il Muro del Sanpierino sul telefonino del Barone: la pagina vera (assets/muro) larga 1067 pixel come la home del
sito, rimpicciolita sulla scena da main.js (--muro-scale). */
#muro { position: absolute; left: 0; top: 0; width: 1067px; height: 600px; border: 0; z-index: 1; background: #f7e1c0;
-1
View File
@@ -28,7 +28,6 @@ try {
}
await page.waitForFunction(() => !game.closeup && game.state.flags.intro, null, { timeout: 15000 });
await page.screenshot({ path: `${out}/${tag}-5-vecchietti.png` });
assert.equal(await page.locator('#risveglio').count(), 0, 'finito il risveglio, la sua tela se ne va');
}
assert.deepEqual(errors, []);
console.log(`ok: schermate in ${out}`);
+1 -2
View File
@@ -21,6 +21,5 @@
"Libici": "Lìbici",
"mancano": "màncano",
"console": "consòl",
"Mahhh... è l'INGORGOOOO!!!": "Maaaa, è l'Ingorgo! L'Ingorgooo!",
"fanfara": "fanfàra"
"Mahhh... è l'INGORGOOOO!!!": "Maaaa, è l'Ingorgo! L'Ingorgooo!"
}