Crediti dal titolo e dalla fine, con la dedica al Lele in testa; le idee di Yuri e Francesco

I crediti stanno in story.credits (un ruolo, i nomi): Yuri Baldi per idee e consigli, Francesco
Bacci per storia e aneddoti di San Piero. In piccolo, la nota che Monkey Island è un marchio
di Lucasfilm citato solo nella dedica. In docs/IDEE.md chi ci aiuta, il gioco in due parti con
i giochi del paese dopo la storia (Yuri) e il Maestro Bacci.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UdeKd62opy9PKgpQXtVctF
This commit is contained in:
2026-10-23 15:40:20 +02:00
co-authored by Claude Opus 5.5
parent 5e6d7d4cfb
commit e743315f0f
7 changed files with 84 additions and 2 deletions
+21
View File
@@ -3,8 +3,29 @@
Battute, oggetti e scherzi proposti da Andrea, in attesa di un posto nella storia. Quando un'idea entra nel gioco Battute, oggetti e scherzi proposti da Andrea, in attesa di un posto nella storia. Quando un'idea entra nel gioco
si segna qui con la data e il punto di `src/story/story.js` dove sta; le persone vere entrano solo col loro consenso. si segna qui con la data e il punto di `src/story/story.js` dove sta; le persone vere entrano solo col loro consenso.
## Chi ci aiuta
- **Yuri Baldi** (dal 2026-10-23): manda idee per sviluppare il gioco; vanno qui sotto, col suo nome. Nei crediti
sotto «Idee e consigli».
- **Francesco Bacci** (dal 2026-10-23): vuole aiutare con la storia vera di San Piero e con gli aneddoti del paese.
Suo nonno era il Maestro Bacci. Nei crediti sotto «Storia e aneddoti di San Piero». I suoi racconti sono la fonte
per le parti segnate [DA ANDREA] di `docs/PROGETTO.md`.
## In attesa ## In attesa
- **Il gioco in due parti** (Yuri Baldi, 2026-10-23). Prima la storia, l'avventura che stiamo facendo; finita
quella, si aprono i giochi del paese, uno per uno:
- il gioco del Regolo;
- il gioco del Palio;
- le ragazze col gioco delle bandierine;
- la caccia al tesoro per il paese;
- le carrette che portano i mattoni su in Fortezza;
- il passamano dei mattoni.
Va d'accordo con `docs/PROGETTO.md` (le sfide del Palietto: passamano, carretta, vaglio della rena): da decidere
se le sfide restano dentro la storia, diventano giochi a parte dopo il finale, o tutte e due le cose.
- **Il Maestro Bacci** (Francesco Bacci, 2026-10-23): il nonno di Francesco. Se e come farlo comparire (un
personaggio, una citazione, un luogo) lo decidono Andrea e Francesco.
- **Oratorio, la pastasciutta di Mariano** (2026-10-23). Alle tavolate dell'oratorio Andrea dice che se aspetta che - **Oratorio, la pastasciutta di Mariano** (2026-10-23). Alle tavolate dell'oratorio Andrea dice che se aspetta che
Mariano gli porti un piatto di pasta, addio: Mariano è sempre a Novoli. Andrebbe in «Usa tavolate» al posto Mariano gli porti un piatto di pasta, addio: Mariano è sempre a Novoli. Andrebbe in «Usa tavolate» al posto
della battuta provvisoria sulla pastasciutta. Prima: il consenso di Mariano a comparire col suo nome. della battuta provvisoria sulla pastasciutta. Prima: il consenso di Mariano a comparire col suo nome.
+9 -2
View File
@@ -23,7 +23,7 @@
<section id="title" class="overlay" hidden> <section id="title" class="overlay" hidden>
<h1>La Leggenda<br>del <span>Regolo</span></h1> <h1>La Leggenda<br>del <span>Regolo</span></h1>
<p class="subtitle">San Piero a Sieve · Palio della Fortezza · 2007</p> <p class="subtitle">San Piero a Sieve · Palio della Fortezza · 2007</p>
<div class="buttons"><button id="continue" hidden>Continua</button><button id="new-game">Nuova partita</button></div> <div class="buttons"><button id="continue" hidden>Continua</button><button id="new-game">Nuova partita</button><button class="open-credits secondary">Crediti</button></div>
<p class="dedication">Al Lele, che mi ha fatto conoscere Monkey Island.</p> <p class="dedication">Al Lele, che mi ha fatto conoscere Monkey Island.</p>
</section> </section>
<section id="intro" class="overlay" hidden> <section id="intro" class="overlay" hidden>
@@ -35,7 +35,14 @@
<h2>Fine del prototipo</h2> <h2>Fine del prototipo</h2>
<p>Il Regolo è passato di qui. Il resto della storia è ancora da scrivere.</p> <p>Il Regolo è passato di qui. Il resto della storia è ancora da scrivere.</p>
<p class="dedication">Al Lele, che mi ha fatto conoscere Monkey Island.</p> <p class="dedication">Al Lele, che mi ha fatto conoscere Monkey Island.</p>
<div class="buttons"><button id="restart">Ricomincia</button></div> <div class="buttons"><button id="restart">Ricomincia</button><button class="open-credits secondary">Crediti</button></div>
</section>
<section id="credits" class="overlay" hidden aria-label="Crediti">
<h2>Crediti</h2>
<p class="dedication credits-dedication">Al Lele, che mi ha fatto conoscere Monkey Island.</p>
<dl id="credits-list"></dl>
<p class="legal">Monkey Island è un marchio di Lucasfilm Ltd., citato solo nella dedica: questo gioco non è affiliato né approvato da Lucasfilm.</p>
<div class="buttons"><button id="close-credits">Chiudi</button></div>
</section> </section>
</div> </div>
<div id="side"> <div id="side">
+11
View File
@@ -68,6 +68,16 @@ function nextIntro() {
$('intro').addEventListener('click', e => { if (e.target !== $('skip-intro')) nextIntro(); }); $('intro').addEventListener('click', e => { if (e.target !== $('skip-intro')) nextIntro(); });
$('skip-intro').addEventListener('click', () => { introLine = story.intro.length; nextIntro(); }); $('skip-intro').addEventListener('click', () => { introLine = story.intro.length; nextIntro(); });
// --- Crediti: costruiti da story.credits; si aprono dal titolo e dalla fine, Chiudi o Esc tornano indietro.
$('credits-list').replaceChildren(...(story.credits ?? []).flatMap(({ role, names }) => {
const dt = document.createElement('dt'), dd = document.createElement('dd');
dt.textContent = role;
dd.textContent = names.join(' · ');
return [dt, dd];
}));
for (const b of document.querySelectorAll('.open-credits')) b.addEventListener('click', () => { $('credits').hidden = false; });
$('close-credits').addEventListener('click', () => { $('credits').hidden = true; });
function showTitle() { function showTitle() {
running = false; running = false;
const saved = storage && readSave(storage); const saved = storage && readSave(storage);
@@ -159,6 +169,7 @@ function updateChoices() {
window.addEventListener('keydown', e => { window.addEventListener('keydown', e => {
if (e.target === $('volume')) return; if (e.target === $('volume')) return;
if (!$('credits').hidden) { if (e.key === 'Escape') $('credits').hidden = true; return; }
if (!$('intro').hidden && [' ', 'Enter'].includes(e.key)) { e.preventDefault(); nextIntro(); return; } if (!$('intro').hidden && [' ', 'Enter'].includes(e.key)) { e.preventDefault(); nextIntro(); return; }
if (e.key === 'm' || e.key === 'M') { music.toggle(); saveAudio(); showSound(); return; } if (e.key === 'm' || e.key === 'M') { music.toggle(); saveAudio(); showSound(); return; }
if (!running) return; if (!running) return;
+10
View File
@@ -15,6 +15,16 @@ export const story = {
// Colonna sonora di Andrea (fatta con Suno): titolo, paese, ricordi (fine e Trebbio). // 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. // splash: la musichetta del cartello d'apertura (tools/jingle.py), suonata una volta sola.
music: { splash: 'assets/audio/cartello.mp3', title: 'assets/audio/titolo.mp3', game: 'assets/audio/paese.mp3', ending: 'assets/audio/ricordi.mp3' }, music: { splash: 'assets/audio/cartello.mp3', title: 'assets/audio/titolo.mp3', game: 'assets/audio/paese.mp3', ending: 'assets/audio/ricordi.mp3' },
// I crediti, schermata «Crediti» dal titolo e dalla fine: un ruolo e chi l'ha fatto. Arriveranno man mano.
credits: [
{ role: 'Un gioco di', names: ['Andrea Manzi «Manzolo»'] },
{ role: 'Idee e consigli', names: ['Yuri Baldi'] },
{ role: 'Storia e aneddoti di San Piero', names: ['Francesco Bacci'] },
{ role: 'Musica', names: ['Andrea Manzi, con Suno'] },
{ role: 'Sfondi', names: ['dalle fotografie di San Piero di Andrea'] },
{ role: 'Voci', names: ['XTTS-v2 di Coqui'] },
{ role: 'Programmazione', names: ['Andrea Manzi, con Claude'] },
],
characters: { characters: {
andrea: { name: 'Andrea', color: '#ffffff', sprite: 'assets/characters/andrea.json' }, andrea: { name: 'Andrea', color: '#ffffff', sprite: 'assets/characters/andrea.json' },
vecchietta: { name: 'Vecchietta', color: '#ffd27a', sprite: 'assets/characters/vecchietta.json' }, vecchietta: { name: 'Vecchietta', color: '#ffd27a', sprite: 'assets/characters/vecchietta.json' },
+9
View File
@@ -93,6 +93,15 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h
.buttons button { font: inherit; font-weight: bold; font-size: clamp(15px, 2.2vw, 22px); padding: .45em 1.2em; border-radius: 6px; cursor: pointer; .buttons button { font: inherit; font-weight: bold; font-size: clamp(15px, 2.2vw, 22px); padding: .45em 1.2em; border-radius: 6px; cursor: pointer;
color: var(--bg); background: var(--accent); border: 0; } color: var(--bg); background: var(--accent); border: 0; }
#continue { background: #7fe08a; } #continue { background: #7fe08a; }
.buttons button.secondary { color: var(--ink); background: rgba(42, 32, 48, .85); box-shadow: inset 0 0 0 2px var(--dim); }
/* Crediti: sopra titolo e fine (z-index), una voce per ruolo; scorrono se non ci stanno. */
#credits { z-index: 3; justify-content: flex-start; overflow-y: auto; padding-top: 4%; background: rgba(15, 10, 20, .96); }
#credits .credits-dedication { margin: .2em 0 .5em !important; font-size: clamp(16px, 2.6vw, 28px); }
/* Due colonne: il ruolo a destra della sua colonna, il nome a sinistra della sua, allineati sulla riga. */
#credits dl { margin: .2em 0 0; display: grid; grid-template-columns: 1fr 1fr; column-gap: 1em; row-gap: .45em; align-items: baseline; width: min(100%, 44em); }
#credits dt { text-align: right; color: var(--dim); font-size: clamp(10px, 1.4vw, 14px); letter-spacing: .12em; text-transform: uppercase; }
#credits dd { margin: 0; text-align: left; color: var(--ink); font-size: clamp(13px, 2vw, 20px); font-weight: bold; }
#credits .legal { color: var(--dim); font-size: clamp(10px, 1.2vw, 13px); opacity: .8; }
#rotate { display: none; padding: 1em; text-align: center; color: var(--dim); } #rotate { display: none; padding: 1em; text-align: center; color: var(--dim); }
@media (orientation: portrait) and (max-width: 700px) { #rotate { display: block; } } @media (orientation: portrait) and (max-width: 700px) { #rotate { display: block; } }
+14
View File
@@ -58,6 +58,16 @@ try {
await page.screenshot({ path: `${OUT}/00b-dissolvenza.png` }); await page.screenshot({ path: `${OUT}/00b-dissolvenza.png` });
await page.waitForSelector('#splash', { state: 'hidden', timeout: 3000 }); await page.waitForSelector('#splash', { state: 'hidden', timeout: 3000 });
await page.screenshot({ path: `${OUT}/01-titolo.png` }); await page.screenshot({ path: `${OUT}/01-titolo.png` });
// I crediti dal titolo: si aprono, mostrano i nomi, Esc li chiude.
await page.click('#title .open-credits');
assert.match(await page.textContent('#credits'), /Crediti\s*Al Lele/, 'la dedica al Lele viene per prima');
assert.match(await page.textContent('#credits'), /Yuri Baldi/);
assert.match(await page.textContent('#credits'), /Francesco Bacci/);
await page.screenshot({ path: `${OUT}/01c-crediti.png` });
const fitsCredits = await page.evaluate(() => { const c = document.getElementById('credits'); return c.scrollHeight <= c.clientHeight; });
assert.ok(fitsCredits, 'i crediti stanno in una schermata');
await page.keyboard.press('Escape');
assert.ok(await page.isHidden('#credits'), 'Esc chiude i crediti');
await page.click('#new-game'); await page.click('#new-game');
// L'introduzione: una riga per clic, poi comincia la partita. // L'introduzione: una riga per clic, poi comincia la partita.
await page.waitForSelector('#intro:not([hidden])'); await page.waitForSelector('#intro:not([hidden])');
@@ -99,6 +109,10 @@ try {
} }
await page.waitForSelector('#ending:not([hidden])'); await page.waitForSelector('#ending:not([hidden])');
await page.screenshot({ path: `${OUT}/06-fine.png` }); await page.screenshot({ path: `${OUT}/06-fine.png` });
await page.click('#ending .open-credits');
assert.ok(await page.isVisible('#credits'), 'crediti anche dalla fine');
await page.click('#close-credits');
assert.ok(await page.isHidden('#credits'));
const saved = await page.evaluate(() => JSON.parse(localStorage.getItem('leggenda-regolo-save'))); const saved = await page.evaluate(() => JSON.parse(localStorage.getItem('leggenda-regolo-save')));
assert.ok(saved.ended && saved.inventory.includes('squama'), 'salvataggio finale'); assert.ok(saved.ended && saved.inventory.includes('squama'), 'salvataggio finale');
+10
View File
@@ -76,6 +76,16 @@ test('la storia è coerente: scene, oggetti, dialoghi e punti calpestabili esist
} }
}); });
test('i crediti: ogni voce ha un ruolo e almeno un nome', () => {
assert.ok(story.credits.length > 0);
for (const c of story.credits) {
assert.ok(c.role?.trim(), 'ruolo vuoto');
assert.ok(c.names?.length && c.names.every(n => n.trim()), `nomi vuoti in ${c.role}`);
}
const all = story.credits.flatMap(c => c.names);
for (const name of ['Yuri Baldi', 'Francesco Bacci']) assert.ok(all.includes(name), `manca ${name}`);
});
test('la soluzione si percorre tutta dall\'inizio alla fine', () => { test('la soluzione si percorre tutta dall\'inizio alla fine', () => {
const saves = []; const saves = [];
const g = new Game(story, { onSave: s => saves.push(s) }); const g = new Game(story, { onSave: s => saves.push(s) });