Si sceglie il rione all'inizio: Piazza, Pieve, Cardetole o Campomigliaio

Dopo «Nuova partita» la domanda «Per chi corri quest'anno?» con i quattro rioni nei loro colori; la scelta resta
nella partita (rione:<id>) e cambia la riga dell'introduzione e le battute sul rione di Andrea. Voci nuove.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ApqNBvNbNCwXqR2RLo8QJh
This commit is contained in:
2026-10-24 15:04:15 +02:00
co-authored by Claude Opus 5.5
parent ee6c03a04d
commit 94a5479645
20 changed files with 149 additions and 18 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+32
View File
@@ -7,6 +7,18 @@
"file": "andrea-fdeb17aa77.mp3",
"sec": 9.75
},
"andrea|Io corro per la Piazza, i rossi.": {
"file": "andrea-553cb74f23.mp3",
"sec": 2.37
},
"andrea|Io corro per la Pieve, i bianchi.": {
"file": "andrea-6637965548.mp3",
"sec": 2.09
},
"andrea|Io corro per Cardetole, i verdi: gioco in casa.": {
"file": "andrea-fdfc44c2df.mp3",
"sec": 3.28
},
"andrea|Io corro per Campomigliaio, i blu.": {
"file": "andrea-aa668b37c2.mp3",
"sec": 2.48
@@ -19,6 +31,10 @@
"file": "andrea-c0e71bfd36.mp3",
"sec": 7.43
},
"andrea|E qui è casa del mio rione: noi rossi.": {
"file": "andrea-9355f00b5d.mp3",
"sec": 3.16
},
"andrea|Ai piedi c'è una strana scia luccicante che va a sinistra, verso la salita di Villa Adami.": {
"file": "andrea-c57cc17e0e.mp3",
"sec": 6.06
@@ -247,6 +263,10 @@
"file": "andrea-59a49e494b.mp3",
"sec": 5.28
},
"andrea|La Pieve di San Pietro. Il mio rione: noi bianchi.": {
"file": "andrea-43fd5c2b27.mp3",
"sec": 4.46
},
"andrea|La Pieve di San Pietro. Il rione Pieve, quelli in bianco.": {
"file": "andrea-c41f9eaaf3.mp3",
"sec": 4.86
@@ -299,6 +319,10 @@
"file": "andrea-f07f295fa4.mp3",
"sec": 4.53
},
"andrea|La strada che scende a Cardetole: casa mia, e il mio rione, i verdi.": {
"file": "andrea-6bd444f8ea.mp3",
"sec": 6.22
},
"andrea|La strada che scende a Cardetole, il rione dei verdi.": {
"file": "andrea-d7882fce87.mp3",
"sec": 3.35
@@ -603,6 +627,14 @@
"file": "andrea-df05ecea32.mp3",
"sec": 4.54
},
"andrea|Più avanti c'è La Luna, e poi Campomigliaio, il rione dei blu.": {
"file": "andrea-85707a4fca.mp3",
"sec": 4.33
},
"andrea|Prima il palio. Le gite fuori porta dopo.": {
"file": "andrea-f753b8c79c.mp3",
"sec": 3.69
},
"andrea|La Provinciale va avanti verso La Luna e Campomigliaio.": {
"file": "andrea-7883437f7d.mp3",
"sec": 3.46
+4
View File
@@ -103,6 +103,10 @@ I prompt fissi (stile, scene, foglio sprite) sono in `prompts/STILE.md`, escluso
**ancora da collegare** (camminata, hotspot, uscite, posto sulla mappa). Andrea, 24 ottobre: ci si arriva dal bivio
della Via Provinciale (verso stazione e forno), andando a sinistra; ma prima viene **La Luna**, che Andrea deve
ancora fotografare: si collega bivio → La Luna → Campomigliaio.
- **Scelta del rione** (24 ottobre): dopo «Nuova partita», «Per chi corri quest'anno?» fra i quattro rioni
(`story.rioni`: Piazza rossi, Pieve bianchi, Cardetole verdi, Campomigliaio blu); resta nella partita come flag
`rione:<id>`. Cambiano la riga dell'introduzione e le battute su Pieve, Cardetole, Campomigliaio e la colonna.
Le parti future col rione (l'Arcu, il capitano) vanno scritte per ogni scelta.
- **Audio sul telefono**: passando a un'altra app (pagina nascosta) musica, voce e musichetta si fermano subito e la
partita va in pausa; la musica riparte con Riprendi.
- **Pronuncia** (`tools/pronuncia.json`): le parole che XTTS legge male, scritte come vanno lette; vale solo per la voce.
+5
View File
@@ -35,6 +35,11 @@
<p id="load-error" class="load-error" hidden role="alert"></p>
<p class="dedication">Al Lele, che mi ha fatto conoscere Monkey Island.</p>
</section>
<section id="rione" class="overlay" hidden aria-label="Scegli il rione">
<h2>Per chi corri quest'anno?</h2>
<div id="rione-buttons" class="buttons"></div>
<p class="hint">Il Palio della Fortezza: quattro rioni, un palio solo.</p>
</section>
<section id="intro" class="overlay" hidden>
<p id="intro-text" aria-live="polite"></p>
<p class="hint">clic o spazio per continuare</p>
+3
View File
@@ -35,6 +35,9 @@ const SMALL_AREA = 24 * 24;
// La miniatura di uno sfondo per la mappa (tools/miniature.py): assets/scenes/x.png -> assets/scenes/mini/x.png.
export const miniature = src => src?.replace(/([^/]+)$/, 'mini/$1') ?? null;
// Una riga dell'introduzione: un testo, oppure { rione: { id: testo } } con la riga del rione scelto.
export const introText = (line, rione) => typeof line === 'string' ? line : line.rione[rione] ?? Object.values(line.rione)[0];
export const sayTime = text => Math.max(1.6, 0.9 + text.length * 0.055);
export function newState(story) {
+1 -1
View File
@@ -14,7 +14,7 @@ export function voiceLines(story) {
if (typeof value.say === 'string') add(value.who ?? 'andrea', value.say);
Object.values(value).forEach(visit);
};
for (const line of story.intro ?? []) add('andrea', line);
for (const line of story.intro ?? []) for (const text of typeof line === 'string' ? [line] : Object.values(line.rione)) add('andrea', text);
visit(story.scenes);
visit(story.items);
visit(story.dialogues);
+35 -10
View File
@@ -1,6 +1,6 @@
// Avvio nel browser: collega motore, disegno e interfaccia HTML (verbi, inventario, dialoghi, fumetti).
import { story } from './story/story.js';
import { Game, VERBS, VERB_LABELS, checkSave } from './engine/game.js';
import { Game, VERBS, VERB_LABELS, checkSave, introText } from './engine/game.js';
import { Command } from './engine/command.js';
import { render, loadAssets, hasBackground, WIDTH, HEIGHT } from './engine/render.js';
import { readSave, writeSave, clearSave, readAudio, writeAudio, saveFile, saveFileName, readSaveFile } from './engine/storage.js';
@@ -45,9 +45,11 @@ window.voices = voices;
let voiced = null;
const say = (who, text) => voices.play(who, text, music.muted);
function start(state) {
// Una partita salvata (state) o nuova; la nuova col rione scelto, che resta nei flag (`rione:<id>`).
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);
$('title').hidden = true;
$('ending').hidden = true;
@@ -56,9 +58,28 @@ function start(state) {
}
// --- Introduzione: le righe di story.intro una alla volta sopra il panorama; poi comincia la partita.
// --- Il rione: dopo «Nuova partita», per chi corre Andrea (story.rioni); poi l'introduzione con la sua riga.
let rione = null;
$('rione-buttons').replaceChildren(...Object.entries(story.rioni ?? {}).map(([id, r]) => {
const b = document.createElement('button');
b.type = 'button';
b.dataset.rione = id;
b.style.setProperty('--rione', r.color);
b.innerHTML = '<span class="rione-name"></span><span class="rione-colors"></span>';
b.querySelector('.rione-name').textContent = r.name;
b.querySelector('.rione-colors').textContent = r.colors;
b.addEventListener('click', () => { rione = id; $('rione').hidden = true; showIntro(); });
return b;
}));
function chooseRione() {
if (!story.rioni) return showIntro();
$('title').hidden = true;
$('rione').hidden = false;
}
let introLine = -1;
function showIntro() {
if (!story.intro?.length) return start(null);
if (!story.intro?.length) return start(null, rione);
$('title').hidden = true;
$('intro').hidden = false;
introLine = -1;
@@ -66,10 +87,10 @@ function showIntro() {
}
function nextIntro() {
introLine++;
if (introLine >= story.intro.length) { voices.stop(); $('intro').hidden = true; return start(null); }
const p = $('intro-text');
p.textContent = story.intro[introLine];
say('andrea', story.intro[introLine]);
if (introLine >= story.intro.length) { voices.stop(); $('intro').hidden = true; return start(null, rione); }
const p = $('intro-text'), text = introText(story.intro[introLine], rione);
p.textContent = text;
say('andrea', text);
p.style.animation = 'none';
void p.offsetWidth; // riavvia la comparsa
p.style.animation = '';
@@ -419,7 +440,7 @@ function frame(now) {
// sotto zero, il browser lancerebbe un errore e il ciclo dei fotogrammi si fermerebbe (gioco congelato).
const dt = Math.max(0, Math.min(0.1, (now - last) / 1000));
last = now;
const screen = !$('title').hidden ? 'title' : !$('splash').hidden ? 'splash' : !$('intro').hidden ? 'intro' : !$('ending').hidden ? 'ending' : 'game';
const screen = !$('title').hidden ? 'title' : !$('splash').hidden ? 'splash' : !$('intro').hidden || !$('rione').hidden ? 'intro' : !$('ending').hidden ? 'ending' : 'game';
document.body.classList.toggle('full', screen === 'title' || screen === 'splash' || screen === 'intro' || !$('credits').hidden);
music.duck = voices.playing ? 0.45 : 1;
music.update(trackFor(story, { screen, scene: game?.state.scene }), dt);
@@ -437,9 +458,13 @@ function frame(now) {
requestAnimationFrame(frame);
}
$('new-game').addEventListener('click', () => { if (storage) clearSave(storage); showIntro(); });
$('new-game').addEventListener('click', () => { if (storage) clearSave(storage); chooseRione(); });
$('continue').addEventListener('click', () => start(readSave(storage)));
$('restart').addEventListener('click', () => { if (storage) clearSave(storage); start(null); });
// Ricomincia: stessa scelta di rione della partita appena finita.
$('restart').addEventListener('click', () => {
if (storage) clearSave(storage);
start(null, Object.keys(game.state.flags).find(f => f.startsWith('rione:'))?.slice(6) ?? rione);
});
// Dietro il titolo si vede già la piazza.
// --- Cartello d'apertura «Manzolo (Poca) Art Presents»: 5 secondi (un clic o un tasto lo salta), poi il titolo.
+23 -5
View File
@@ -36,11 +36,25 @@ export const story = {
// 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] },
// I quattro rioni del Palio: dopo «Nuova partita» si sceglie per chi corre Andrea, e la partita lo ricorda col
// flag `rione:<id>`. Le battute che parlano del rione di Andrea lo controllano con `if`.
rioni: {
piazza: { name: 'Piazza', colors: 'i rossi', color: '#d0453a' },
pieve: { name: 'Pieve', colors: 'i bianchi', color: '#f2eee2' },
cardetole: { name: 'Cardetole', colors: 'i verdi', color: '#3fa34d' },
campomigliaio: { name: 'Campomigliaio', colors: 'i blu', color: '#2f6fd0' },
},
// Le righe dell'introduzione, sopra il panorama del paese, prima della prima scena.
intro: [
'San Piero a Sieve, nel Mugello. Fine maggio 2007.',
'In paese si prepara il Palio della Fortezza: i quattro rioni si sfidano, e chi vince si porta a casa il palio, il quadro dipinto dai ragazzi delle medie.',
'Io corro per Campomigliaio, i blu.',
// La riga del rione: quella del rione scelto dopo «Nuova partita» (story.rioni).
{ rione: {
piazza: 'Io corro per la Piazza, i rossi.',
pieve: 'Io corro per la Pieve, i bianchi.',
cardetole: 'Io corro per Cardetole, i verdi: gioco in casa.',
campomigliaio: 'Io corro per Campomigliaio, i blu.',
} },
'E stamattina, in piazza, è successo qualcosa...',
],
// Colonna sonora di Andrea (fatta con Suno): titolo, paese, ricordi (fine e Trebbio).
@@ -219,7 +233,7 @@ export const story = {
{
id: 'colonna', name: 'colonna', box: [492, 134, 36, 140], at: [505, 290], face: 'up',
actions: {
look: [{ say: 'La colonna, il centro del paese. Da qui passa tutto: le notizie, il Palio, i pettegolezzi.' }, { say: 'Ai piedi c\'è una strana scia luccicante che va a sinistra, verso la salita di Villa Adami.' }],
look: [{ say: 'La colonna, il centro del paese. Da qui passa tutto: le notizie, il Palio, i pettegolezzi.' }, { if: 'rione:piazza', then: [{ say: 'E qui è casa del mio rione: noi rossi.' }] }, { say: 'Ai piedi c\'è una strana scia luccicante che va a sinistra, verso la salita di Villa Adami.' }],
take: [{ say: 'Se mi porto via la colonna, la piazza come la chiamano?' }],
use: [{ say: 'Arrampicarmi? Il palio lassù non c\'è comunque.' }],
},
@@ -408,7 +422,7 @@ export const story = {
{
id: 'chiesa', name: 'Pieve di San Pietro', box: [108, 78, 166, 132], at: [200, 290], face: 'up',
actions: {
look: [{ say: 'La Pieve di San Pietro. Il rione Pieve, quelli in bianco.' }],
look: [{ if: 'rione:pieve', then: [{ say: 'La Pieve di San Pietro. Il mio rione: noi bianchi.' }], else: [{ say: 'La Pieve di San Pietro. Il rione Pieve, quelli in bianco.' }] }],
open: [{ say: 'Il prete è a benedire le carrette del Palio. Torno dopo.' }],
},
},
@@ -462,7 +476,7 @@ export const story = {
{
id: 'verso_cardetole', name: 'strada per Cardetole', box: [0, 204, 82, 54], at: [95, 250], face: 'left', exit: { scene: 'carza', at: [300, 350], face: 'up' },
actions: {
look: [{ say: 'La strada che scende a Cardetole, il rione dei verdi.' }],
look: [{ if: 'rione:cardetole', then: [{ say: 'La strada che scende a Cardetole: casa mia, e il mio rione, i verdi.' }], else: [{ say: 'La strada che scende a Cardetole, il rione dei verdi.' }] }],
},
},
{
@@ -1018,7 +1032,11 @@ export const story = {
{
id: 'la_luna', name: 'a sinistra, verso La Luna e Campomigliaio', box: [0, 216, 44, 56], at: [12, 248], face: 'left', default: 'walk',
actions: {
walk: [{ say: 'Più avanti c\'è La Luna, e poi Campomigliaio, il mio rione: i blu.' }, { say: 'Ci torno quando ho ritrovato il palio. A mani vuote non mi ci faccio vedere.' }],
walk: [{
if: 'rione:campomigliaio',
then: [{ say: 'Più avanti c\'è La Luna, e poi Campomigliaio, il mio rione: i blu.' }, { say: 'Ci torno quando ho ritrovato il palio. A mani vuote non mi ci faccio vedere.' }],
else: [{ say: 'Più avanti c\'è La Luna, e poi Campomigliaio, il rione dei blu.' }, { say: 'Prima il palio. Le gite fuori porta dopo.' }],
}],
look: [{ say: 'La Provinciale va avanti verso La Luna e Campomigliaio.' }],
},
},
+11 -1
View File
@@ -84,7 +84,7 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h
come un'aggiunta a mano; dopo 4,3 s sfuma (il JavaScript lo toglie a 5 s). */
/* Cartello, titolo, introduzione e crediti a tutto schermo: sul telefono la scena è piccola e non si leggerebbe niente.
Il volume resta raggiungibile sopra di loro. */
#splash, #title, #intro { position: fixed; inset: 0; z-index: 10; }
#splash, #title, #intro, #rione { position: fixed; inset: 0; z-index: 10; }
#credits { position: fixed; inset: 0; z-index: 12; }
body.full #sound { position: fixed; z-index: 13; top: max(8px, env(safe-area-inset-top)); right: 8px; }
/* Le illustrazioni si vedono sempre intere (contain): sul telefono in verticale il titolo sta sopra e i pulsanti
@@ -212,3 +212,13 @@ body.full #sound { position: fixed; z-index: 13; top: max(8px, env(safe-area-ins
}
#error { position: fixed; inset: auto 16px 16px; padding: 12px; background: #7a1f1f; color: #fff; border-radius: 6px; }
/* La scelta del rione: quattro bandiere col colore del rione, sopra il panorama come l'introduzione. */
#rione { padding-top: 48px; background: linear-gradient(rgba(20, 12, 30, .72), rgba(20, 12, 30, .86)), url(assets/scenes/panorama-intro.png) center / cover; image-rendering: pixelated; }
#rione h2 { font-size: clamp(14px, 3vw, 28px); text-align: center; }
#rione-buttons { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; width: min(92%, 34em); }
#rione-buttons button { display: flex; flex-direction: column; align-items: center; gap: .5em; padding: 1em .5em; font: clamp(11px, 1.8vw, 16px) var(--font);
color: var(--ink); background: rgba(27, 20, 32, .85); border: 3px solid var(--rione); border-left-width: 14px; border-radius: 8px; cursor: pointer; }
#rione-buttons button:hover, #rione-buttons button:focus-visible { background: color-mix(in srgb, var(--rione) 30%, #1b1420); }
#rione-buttons .rione-colors { font-size: .7em; color: var(--rione); }
#rione .hint { color: var(--dim); font-size: clamp(8px, 1.2vw, 11px); text-align: center; }
+12
View File
@@ -77,8 +77,15 @@ try {
await page.keyboard.press('Escape');
assert.ok(await page.isHidden('#credits'), 'Esc chiude i crediti');
await page.click('#new-game');
// Il rione: quattro scelte; la Pieve cambia la riga dell'introduzione e resta nella partita.
await page.waitForSelector('#rione:not([hidden])');
assert.equal(await page.locator('#rione-buttons button').count(), 4);
await page.screenshot({ path: `${OUT}/01a-rione.png` });
await page.click('#rione [data-rione="pieve"]');
// L'introduzione: una riga per clic, poi comincia la partita.
await page.waitForSelector('#intro:not([hidden])');
for (let i = 0; i < 2; i++) await page.click('#intro', { position: { x: 40, y: 40 } });
assert.match(await page.textContent('#intro-text'), /Pieve, i bianchi/, 'la riga del rione scelto');
await page.screenshot({ path: `${OUT}/01b-intro.png` });
for (let i = 0; i < 10 && await page.isVisible('#intro'); i++) await page.click('#intro', { position: { x: 40, y: 40 } });
assert.ok(await page.isHidden('#intro'), 'l\'introduzione non finisce');
@@ -88,6 +95,7 @@ try {
assert.match(await page.evaluate(() => voices.audio.src), /assets\/voices\/andrea-[0-9a-f]+\.mp3$/);
await page.waitForFunction(() => music.duck === 0.45, null, { timeout: 2000 });
await page.screenshot({ path: `${OUT}/02-inizio.png` });
assert.ok(await page.evaluate(() => game.state.flags['rione:pieve']), 'il rione resta nella partita');
const fits = async p => p.evaluate(() => document.querySelector('#panel').getBoundingClientRect().bottom <= innerHeight);
assert.ok(await fits(page), 'verbi e inventario fuori dallo schermo');
await settle(page);
@@ -236,6 +244,7 @@ try {
await phone.click('#splash'); // un clic salta il cartello
assert.ok(await phone.isVisible('#title'), 'il clic sul cartello porta al titolo');
await phone.click('#new-game');
await phone.click('#rione [data-rione="campomigliaio"]');
await phone.click('#skip-intro');
await settle(phone);
const overflow = await phone.evaluate(() => document.documentElement.scrollWidth > innerWidth);
@@ -250,6 +259,7 @@ try {
await small.goto(`http://127.0.0.1:${PORT}/`);
await small.waitForSelector('#splash', { state: 'hidden', timeout: 15000 });
await small.click('#new-game');
await small.click('#rione [data-rione="campomigliaio"]');
await small.click('#skip-intro');
await settle(small);
assert.equal(await small.evaluate(() => innerWidth), 740, 'la pagina è più larga dello schermo del telefono');
@@ -258,6 +268,7 @@ try {
await small.screenshot({ path: `${OUT}/08b-telefono-stretto-pausa.png` });
const landscape = await open({ width: 844, height: 390 });
await landscape.click('#new-game');
await landscape.click('#rione [data-rione="campomigliaio"]');
await landscape.click('#skip-intro');
await settle(landscape);
assert.equal(await landscape.evaluate(() => document.documentElement.scrollWidth > innerWidth), false);
@@ -267,6 +278,7 @@ try {
// Debug (?debug): area calpestabile e hotspot disegnati; Maiusc+clic segna punti senza muovere Andrea, X li azzera.
const dbg = await open({ width: 1280, height: 860 }, '?debug');
await dbg.click('#new-game');
await dbg.click('#rione [data-rione="campomigliaio"]');
await dbg.click('#skip-intro');
await settle(dbg);
const start = await dbg.evaluate(() => [game.state.x, game.state.y]);
+18 -1
View File
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
import { backgroundOrder } from '../src/engine/render.js';
import { story } from '../src/story/story.js';
import { solution } from '../src/story/solution.js';
import { Game, VERBS, checkSave } from '../src/engine/game.js';
import { Game, VERBS, checkSave, introText } from '../src/engine/game.js';
import { inside } from '../src/engine/geometry.js';
import { existsSync, readFileSync } from 'node:fs';
import { settle } from './helpers.mjs';
@@ -391,3 +391,20 @@ test('ogni posto della mappa ha la sua miniatura (make miniature)', () => {
assert.ok(existsSync(new URL('../' + p.mini, import.meta.url)), `manca ${p.mini}: make miniature`);
}
});
test('i quattro rioni: ognuno ha la sua riga nell\'introduzione, e le battute sul rione di Andrea lo seguono', () => {
assert.deepEqual(Object.keys(story.rioni), ['piazza', 'pieve', 'cardetole', 'campomigliaio']);
const line = story.intro.find(l => typeof l !== 'string');
assert.deepEqual(Object.keys(line.rione).sort(), Object.keys(story.rioni).sort(), 'una riga per rione');
for (const [id, r] of Object.entries(story.rioni)) assert.ok(introText(line, id).includes(r.colors.replace('i ', '')), `${id}: ${introText(line, id)}`);
const look = (rione, scene, target) => {
const flags = rione ? { ['rione:' + rione]: true } : {};
const g = new Game(story, { state: { scene, x: 0, y: 0, facing: 1, inventory: [], flags, used: {}, ended: false } });
g.act('look', target); return settle(g).map(s => s.text).join(' ');
};
assert.match(look('pieve', 'pieve', 'chiesa'), /Il mio rione/);
assert.match(look('cardetole', 'pieve', 'chiesa'), /quelli in bianco/);
assert.match(look('piazza', 'piazza', 'colonna'), /noi rossi/);
assert.doesNotMatch(look('pieve', 'piazza', 'colonna'), /noi rossi/);
assert.match(look(null, 'pieve', 'chiesa'), /quelli in bianco/, 'un salvataggio di prima dei rioni funziona');
});
+3
View File
@@ -92,6 +92,9 @@ async function giro(modo) {
await page.waitForSelector('#splash', { state: 'hidden', timeout: 15000 }).catch(() => {});
await foto('titolo', 'titolo');
await premi('#new-game');
await page.waitForSelector('#rione:not([hidden])');
await foto('rione', 'rione');
await premi('#rione [data-rione="campomigliaio"]');
await page.waitForSelector('#intro:not([hidden])');
await foto('introduzione', 'introduzione');
await premi('#skip-intro');
+2
View File
@@ -100,6 +100,8 @@ export async function run(d) {
await d.hover(p.locator("#title .dedication"), 1.2);
await sleep(4500);
await d.click(p.locator("#new-game"));
await sleep(2500);
await d.click(p.locator('#rione [data-rione="campomigliaio"]'));
// 2. Introduzione: una riga per clic.
await d.cue("intro");