diff --git a/docs/IDEE.md b/docs/IDEE.md
index 94571f8..c7c5fca 100644
--- a/docs/IDEE.md
+++ b/docs/IDEE.md
@@ -3,8 +3,29 @@
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.
+## 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
+- **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
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.
diff --git a/index.html b/index.html
index 4fd321a..2d7f640 100644
--- a/index.html
+++ b/index.html
@@ -23,7 +23,7 @@
La Leggenda del Regolo
San Piero a Sieve · Palio della Fortezza · 2007
-
+
Al Lele, che mi ha fatto conoscere Monkey Island.
@@ -35,7 +35,14 @@
Fine del prototipo
Il Regolo è passato di qui. Il resto della storia è ancora da scrivere.
Al Lele, che mi ha fatto conoscere Monkey Island.
-
+
+
+
+
Crediti
+
Al Lele, che mi ha fatto conoscere Monkey Island.
+
+
Monkey Island è un marchio di Lucasfilm Ltd., citato solo nella dedica: questo gioco non è affiliato né approvato da Lucasfilm.
+
diff --git a/src/main.js b/src/main.js
index 3da9a7e..fe25d78 100644
--- a/src/main.js
+++ b/src/main.js
@@ -68,6 +68,16 @@ function nextIntro() {
$('intro').addEventListener('click', e => { if (e.target !== $('skip-intro')) 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() {
running = false;
const saved = storage && readSave(storage);
@@ -159,6 +169,7 @@ function updateChoices() {
window.addEventListener('keydown', e => {
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 (e.key === 'm' || e.key === 'M') { music.toggle(); saveAudio(); showSound(); return; }
if (!running) return;
diff --git a/src/story/story.js b/src/story/story.js
index 3a6bde9..34f9327 100644
--- a/src/story/story.js
+++ b/src/story/story.js
@@ -15,6 +15,16 @@ export const story = {
// 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.
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: {
andrea: { name: 'Andrea', color: '#ffffff', sprite: 'assets/characters/andrea.json' },
vecchietta: { name: 'Vecchietta', color: '#ffd27a', sprite: 'assets/characters/vecchietta.json' },
diff --git a/style.css b/style.css
index 627a216..2804f21 100644
--- a/style.css
+++ b/style.css
@@ -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;
color: var(--bg); background: var(--accent); border: 0; }
#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); }
@media (orientation: portrait) and (max-width: 700px) { #rotate { display: block; } }
diff --git a/tests/browser.mjs b/tests/browser.mjs
index 424c95f..89fbfd1 100644
--- a/tests/browser.mjs
+++ b/tests/browser.mjs
@@ -58,6 +58,16 @@ try {
await page.screenshot({ path: `${OUT}/00b-dissolvenza.png` });
await page.waitForSelector('#splash', { state: 'hidden', timeout: 3000 });
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');
// L'introduzione: una riga per clic, poi comincia la partita.
await page.waitForSelector('#intro:not([hidden])');
@@ -99,6 +109,10 @@ try {
}
await page.waitForSelector('#ending:not([hidden])');
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')));
assert.ok(saved.ended && saved.inventory.includes('squama'), 'salvataggio finale');
diff --git a/tests/story.test.mjs b/tests/story.test.mjs
index e093a4e..98edfc4 100644
--- a/tests/story.test.mjs
+++ b/tests/story.test.mjs
@@ -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', () => {
const saves = [];
const g = new Game(story, { onSave: s => saves.push(s) });