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 @@ +
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) });