Campomigliaio collegato (strada, circolino, ponte), il Poggio verso la Fortezza; Ringraziamenti coi ritratti e il carosello
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ApqNBvNbNCwXqR2RLo8QJh
@@ -10,6 +10,7 @@ COPY assets/audio /usr/share/nginx/html/assets/audio
|
||||
COPY assets/icons /usr/share/nginx/html/assets/icons
|
||||
COPY assets/fonts /usr/share/nginx/html/assets/fonts
|
||||
COPY assets/voices /usr/share/nginx/html/assets/voices
|
||||
COPY assets/credits /usr/share/nginx/html/assets/credits
|
||||
|
||||
EXPOSE 80
|
||||
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://127.0.0.1/ >/dev/null || exit 1
|
||||
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 285 KiB |
@@ -547,9 +547,13 @@
|
||||
"file": "andrea-78a5ef65f0.mp3",
|
||||
"sec": 7.84
|
||||
},
|
||||
"andrea|Sotto il ponte la Sieve. Nel 1758 la piena arrivò fino a Cardetole: in casa mia, di sicuro, c'erano i pesci.": {
|
||||
"file": "andrea-7301f8df3d.mp3",
|
||||
"sec": 11.86
|
||||
"andrea|Sotto il ponte la Carza si butta nella Sieve, e da qui vanno avanti insieme.": {
|
||||
"file": "andrea-f322731846.mp3",
|
||||
"sec": 5.04
|
||||
},
|
||||
"andrea|Nel 1758 la piena arrivò fino a Cardetole: in casa mia, di sicuro, c'erano i pesci.": {
|
||||
"file": "andrea-5bf82d4aaa.mp3",
|
||||
"sec": 6.78
|
||||
},
|
||||
"andrea|Il campo secondario della polisportiva. Qui si allenano i piccoli, e i grandi fanno finta di non guardare.": {
|
||||
"file": "andrea-86efb05e99.mp3",
|
||||
@@ -635,26 +639,6 @@
|
||||
"file": "andrea-5bd253332d.mp3",
|
||||
"sec": 6.36
|
||||
},
|
||||
"andrea|Più avanti c'è La Luna, e poi Campomigliaio, il mio rione: i blu.": {
|
||||
"file": "andrea-a96e07a9f3.mp3",
|
||||
"sec": 5.92
|
||||
},
|
||||
"andrea|Ci torno quando ho ritrovato il palio. A mani vuote non mi ci faccio vedere.": {
|
||||
"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
|
||||
},
|
||||
"andrea|La Carza, la stessa che passa sotto il ponticino per Cardetole. Più sassi che acqua.": {
|
||||
"file": "andrea-6248354f0f.mp3",
|
||||
"sec": 6.38
|
||||
@@ -683,6 +667,86 @@
|
||||
"file": "andrea-dc49e0c100.mp3",
|
||||
"sec": 6.08
|
||||
},
|
||||
"andrea|In cima alla salita, la casa con la torretta. Da quaggiù sembra il castello di Campomigliaio.": {
|
||||
"file": "andrea-b3120ac9f3.mp3",
|
||||
"sec": 6.3
|
||||
},
|
||||
"andrea|Un muro di sassi a secco, con la ringhiera sopra. Ne ha visti passare di pali.": {
|
||||
"file": "andrea-9f914e2a8b.mp3",
|
||||
"sec": 5.35
|
||||
},
|
||||
"andrea|Una porta marrone, chiusa.": {
|
||||
"file": "andrea-29c2c930b4.mp3",
|
||||
"sec": 1.79
|
||||
},
|
||||
"andrea|Chiusa. Saranno tutti al circolino.": {
|
||||
"file": "andrea-00f2e70a4d.mp3",
|
||||
"sec": 2.95
|
||||
},
|
||||
"andrea|Il circolino: il Circolo SMS di Campomigliaio. Un'istituzione, qui i volontari hanno sempre fatto miracoli.": {
|
||||
"file": "andrea-90bbad0de0.mp3",
|
||||
"sec": 9.59
|
||||
},
|
||||
"andrea|Il sabato sera ci si mangia una pizza ottima, e si sta meglio che a casa. Si respira fratellanza.": {
|
||||
"file": "andrea-d44bcc0c68.mp3",
|
||||
"sec": 6.8
|
||||
},
|
||||
"andrea|Se entro ora, mi chiedono tutti del palio. Prima lo ritrovo, poi entro a testa alta.": {
|
||||
"file": "andrea-33857840d8.mp3",
|
||||
"sec": 6.55
|
||||
},
|
||||
"andrea|Il circolino dei blu, il giorno prima del palio? Meglio di no.": {
|
||||
"file": "andrea-1b9468d963.mp3",
|
||||
"sec": 5.05
|
||||
},
|
||||
"andrea|Una fontanella di ghisa. Acqua fresca e gratis, la cosa più preziosa di maggio.": {
|
||||
"file": "andrea-8d9bda2db8.mp3",
|
||||
"sec": 5.87
|
||||
},
|
||||
"andrea|Un sorso d'acqua. Ora sì che si ragiona.": {
|
||||
"file": "andrea-78ffacf27b.mp3",
|
||||
"sec": 3.47
|
||||
},
|
||||
"andrea|L'insegna del circolino. Senza scritte: tanto qui lo sanno tutti dov'è.": {
|
||||
"file": "andrea-04c0898047.mp3",
|
||||
"sec": 5.92
|
||||
},
|
||||
"andrea|La panchina davanti al circolino. Di solito è occupata da chi sa tutto di tutti.": {
|
||||
"file": "andrea-023905aed9.mp3",
|
||||
"sec": 5.14
|
||||
},
|
||||
"andrea|Non è il momento di sedersi. Il palio non si cerca da solo.": {
|
||||
"file": "andrea-914e32fcca.mp3",
|
||||
"sec": 4.04
|
||||
},
|
||||
"andrea|A diritto si va a Tagliaferro. Il palio non sarà mica scappato fin laggiù.": {
|
||||
"file": "andrea-fea8986ed0.mp3",
|
||||
"sec": 5.97
|
||||
},
|
||||
"andrea|La strada va avanti verso Tagliaferro.": {
|
||||
"file": "andrea-bb70dea2a6.mp3",
|
||||
"sec": 2.15
|
||||
},
|
||||
"andrea|Il ponte vecchio, coi suoi due archi. Chissà quante piene ha visto passare: io al primo temporale cerco riparo.": {
|
||||
"file": "andrea-01ec1d03ec.mp3",
|
||||
"sec": 7.67
|
||||
},
|
||||
"andrea|L'acqua è bassa e limpida. Si vedono i sassi del fondo, e nessun palio.": {
|
||||
"file": "andrea-93acb42cf7.mp3",
|
||||
"sec": 5.98
|
||||
},
|
||||
"andrea|Mi bagno le scarpe e poi chi la sente, la mamma.": {
|
||||
"file": "andrea-74470f90d0.mp3",
|
||||
"sec": 3.9
|
||||
},
|
||||
"andrea|Un tronco bianco portato giù dalla piena, piantato in mezzo all'acqua.": {
|
||||
"file": "andrea-f07cb45320.mp3",
|
||||
"sec": 4.08
|
||||
},
|
||||
"andrea|Troppo pesante, e troppo bagnato. Lo lascio ai girini.": {
|
||||
"file": "andrea-b814f7d1a0.mp3",
|
||||
"sec": 4.38
|
||||
},
|
||||
"andrea|Il pallaio, dove si gioca a bocce. Qui un punto si discute più di un rigore.": {
|
||||
"file": "andrea-e94382adb6.mp3",
|
||||
"sec": 5.76
|
||||
@@ -723,13 +787,13 @@
|
||||
"file": "andrea-2de291f241.mp3",
|
||||
"sec": 4.09
|
||||
},
|
||||
"andrea|Laggiù c'è la casa del maestro Bacci. Mezzo paese ha imparato a leggere e scrivere da lui.": {
|
||||
"file": "andrea-57fb036fc1.mp3",
|
||||
"sec": 6.54
|
||||
"andrea|Giù a destra, oltre le vigne, c'è la casa del maestro Bacci. Mezzo paese ha imparato a leggere e scrivere da lui.": {
|
||||
"file": "andrea-060513a152.mp3",
|
||||
"sec": 8.65
|
||||
},
|
||||
"andrea|Lassù in cima, la torre del Trebbio. Da qui sembra piccola come una pedina.": {
|
||||
"file": "andrea-5536ba1bf5.mp3",
|
||||
"sec": 5.65
|
||||
"andrea|Eccolo lì, il castello del Trebbio, in tutto il suo splendore. Da quaggiù sembra una pedina.": {
|
||||
"file": "andrea-4f89e093b5.mp3",
|
||||
"sec": 7.07
|
||||
},
|
||||
"andrea|Le vigne in fila. A settembre qui si vendemmia, e tutto il paese ha le mani appiccicose.": {
|
||||
"file": "andrea-b987450513.mp3",
|
||||
@@ -771,6 +835,26 @@
|
||||
"file": "andrea-35430efef3.mp3",
|
||||
"sec": 6.34
|
||||
},
|
||||
"andrea|Da quassù si vede tutto: i tetti del paese, i campi, e in fondo i monti del Mugello.": {
|
||||
"file": "andrea-23d87e6493.mp3",
|
||||
"sec": 5.69
|
||||
},
|
||||
"andrea|Il palio però da qui non si vede. Ci avevo sperato.": {
|
||||
"file": "andrea-2e9330c511.mp3",
|
||||
"sec": 4.45
|
||||
},
|
||||
"andrea|Un fico che si sporge dal muretto. A fine maggio i fichi sono ancora duri: torno ad agosto.": {
|
||||
"file": "andrea-86d675f890.mp3",
|
||||
"sec": 7.04
|
||||
},
|
||||
"andrea|Acerbi. E poi non sono miei.": {
|
||||
"file": "andrea-2f1517d79d.mp3",
|
||||
"sec": 3.77
|
||||
},
|
||||
"andrea|Le case del Poggio, che salgono verso la Fortezza coi fiori alle finestre e i muri di sasso.": {
|
||||
"file": "andrea-e044dea7f2.mp3",
|
||||
"sec": 6.29
|
||||
},
|
||||
"andrea|Un leone di terracotta che fa la guardia alla salita. Di serpenti, dice, non ne ha visti passare.": {
|
||||
"file": "andrea-59a60dc5a7.mp3",
|
||||
"sec": 6.83
|
||||
@@ -855,9 +939,9 @@
|
||||
"file": "andrea-e549a3c205.mp3",
|
||||
"sec": 4.69
|
||||
},
|
||||
"andrea|La piccola edicola in fondo al ponticino. C'è sempre un fiore fresco, ma nessuno l'ha mai visto metterlo.": {
|
||||
"file": "andrea-59afc50d80.mp3",
|
||||
"sec": 7.04
|
||||
"andrea|Il piccolo tabernacolo in fondo al ponticino. C'è sempre un fiore fresco, ma nessuno l'ha mai visto metterlo.": {
|
||||
"file": "andrea-ed03a4f29a.mp3",
|
||||
"sec": 7.41
|
||||
},
|
||||
"andrea|La panchina del parco. Ci si sedevano i camperisti a guardare le montagne, con la cartina al contrario.": {
|
||||
"file": "andrea-e7f720b94d.mp3",
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<section id="title" class="overlay" hidden>
|
||||
<h1>La Leggenda<br>del <span>Regolo</span></h1>
|
||||
<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><button id="load-game" class="secondary">Carica</button><button class="open-credits secondary">Crediti</button></div>
|
||||
<div class="buttons"><button id="continue" hidden>Continua</button><button id="new-game">Nuova partita</button><button id="load-game" class="secondary">Carica</button><button class="open-credits secondary">Ringraziamenti</button></div>
|
||||
<input id="load-file" type="file" accept=".json,application/json" hidden>
|
||||
<p id="load-error" class="load-error" hidden role="alert"></p>
|
||||
<p class="dedication">Al Lele, che mi ha fatto conoscere Monkey Island.</p>
|
||||
@@ -49,7 +49,7 @@
|
||||
<h2>Fine del primo capitolo</h2>
|
||||
<p>Il palio è laggiù, nel buio della Fortezza. E il Regolo pure. Il resto della storia è ancora da scrivere.</p>
|
||||
<p class="dedication">Al Lele, che mi ha fatto conoscere Monkey Island.</p>
|
||||
<div class="buttons"><button id="restart">Ricomincia</button><button class="open-credits secondary">Crediti</button></div>
|
||||
<div class="buttons"><button id="restart">Ricomincia</button><button class="open-credits secondary">Ringraziamenti</button></div>
|
||||
</section>
|
||||
<section id="map" class="overlay" hidden aria-label="Mappa del paese">
|
||||
<div id="map-head"><h2>San Piero a Sieve</h2><input id="map-search" type="search" placeholder="Cerca un posto…" autocomplete="off" spellcheck="false" aria-label="Cerca un posto"></div>
|
||||
@@ -63,17 +63,19 @@
|
||||
<button id="resume">Riprendi</button>
|
||||
<button id="pause-map" class="secondary">Mappa</button>
|
||||
<button id="save-file" class="secondary" title="Scarica la partita in un file da aprire su un altro dispositivo">Salva su file</button>
|
||||
<button class="open-credits secondary">Crediti</button>
|
||||
<button class="open-credits secondary">Ringraziamenti</button>
|
||||
<button id="to-title" class="secondary">Torna al titolo</button>
|
||||
</div>
|
||||
<p class="hint">La partita è salvata: dal titolo si riprende con «Continua». Con «Salva su file» la porti su un altro dispositivo e la apri con «Carica».</p>
|
||||
</section>
|
||||
<section id="credits" class="overlay" hidden aria-label="Crediti">
|
||||
<h2>Crediti</h2>
|
||||
<section id="credits" class="overlay" hidden aria-label="Ringraziamenti">
|
||||
<h2>Ringraziamenti</h2>
|
||||
<p class="dedication credits-dedication">Al Lele, che mi ha fatto conoscere Monkey Island.</p>
|
||||
<dl id="credits-list"></dl>
|
||||
<div id="credits-reel" aria-hidden="true"><div class="track"></div></div>
|
||||
<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>
|
||||
<figure id="face" hidden><img alt=""><figcaption></figcaption></figure>
|
||||
</section>
|
||||
</div>
|
||||
<div id="side">
|
||||
|
||||
@@ -106,11 +106,61 @@ function openCredits() {
|
||||
$('credits-list').replaceChildren(...shuffledCredits(story.credits ?? []).flatMap(({ role, names }) => {
|
||||
const dt = document.createElement('dt'), dd = document.createElement('dd');
|
||||
dt.textContent = role;
|
||||
dd.replaceChildren(...names.map(n => Object.assign(document.createElement('span'), { textContent: n })));
|
||||
dd.replaceChildren(...names.map(n => {
|
||||
if (typeof n === 'string') return Object.assign(document.createElement('span'), { textContent: n });
|
||||
// Chi ha il ritratto: il nome è un pulsante che lo mostra.
|
||||
const b = Object.assign(document.createElement('button'), { type: 'button', className: 'credit-face', textContent: n.name });
|
||||
b.addEventListener('click', () => showFace(n));
|
||||
return b;
|
||||
}));
|
||||
return [dt, dd];
|
||||
}));
|
||||
fillReel();
|
||||
$('credits').hidden = false;
|
||||
rollCredits();
|
||||
}
|
||||
// Sotto i nomi, un carosello che scorre da solo: gli sfondi del paese e i ritratti, due giri uguali in fila per non
|
||||
// vedere mai la cucitura.
|
||||
function fillReel() {
|
||||
const track = $('credits-reel').querySelector('.track');
|
||||
if (track.children.length) return;
|
||||
const pictures = [...new Set(Object.values(story.scenes).map(s => s.background).filter(Boolean))];
|
||||
const faces = (story.credits ?? []).flatMap(c => c.names).filter(n => n.face).map(n => n.face);
|
||||
// I ritratti sparsi fra gli sfondi, uno ogni quattro.
|
||||
const all = pictures.flatMap((p, i) => i % 4 === 3 && faces.length ? [p, faces[(i >> 2) % faces.length]] : [p]);
|
||||
for (const src of [...all, ...all]) {
|
||||
const img = Object.assign(document.createElement('img'), { src, alt: '', loading: 'lazy', decoding: 'async' });
|
||||
if (src.includes('/credits/')) img.className = 'face';
|
||||
track.append(img);
|
||||
}
|
||||
}
|
||||
// I nomi salgono piano da soli; si fermano se ci si passa sopra o si tocca, e ricominciano dall'alto in fondo.
|
||||
let rolling = 0, rollWait = 0;
|
||||
$('credits-list').addEventListener('pointerdown', () => { rollWait = 4000; });
|
||||
$('credits-list').addEventListener('wheel', () => { rollWait = 4000; }, { passive: true });
|
||||
function rollCredits() {
|
||||
const list = $('credits-list');
|
||||
list.scrollTop = 0;
|
||||
cancelAnimationFrame(rolling);
|
||||
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||
let last = performance.now(), pos = 0;
|
||||
rollWait = 1500;
|
||||
const step = now => {
|
||||
if ($('credits').hidden) return;
|
||||
const dt = now - last; last = now;
|
||||
if (list.matches(':hover') || !$('face').hidden || (rollWait -= dt) > 0) pos = list.scrollTop;
|
||||
else if (list.scrollTop + list.clientHeight >= list.scrollHeight - 1) { rollWait = 2500; list.scrollTop = pos = 0; }
|
||||
else list.scrollTop = pos += dt * 0.025;
|
||||
rolling = requestAnimationFrame(step);
|
||||
};
|
||||
rolling = requestAnimationFrame(step);
|
||||
}
|
||||
function showFace({ name, face }) {
|
||||
$('face').querySelector('img').src = face;
|
||||
$('face').querySelector('figcaption').textContent = name;
|
||||
$('face').hidden = false;
|
||||
}
|
||||
$('face').addEventListener('click', () => { $('face').hidden = true; });
|
||||
for (const b of document.querySelectorAll('.open-credits')) b.addEventListener('click', openCredits);
|
||||
$('close-credits').addEventListener('click', () => { $('credits').hidden = true; });
|
||||
|
||||
@@ -414,7 +464,7 @@ window.addEventListener('keydown', e => {
|
||||
return;
|
||||
}
|
||||
if (debugKey(e)) return;
|
||||
if (!$('credits').hidden) { if (e.key === 'Escape') $('credits').hidden = true; return; }
|
||||
if (!$('credits').hidden) { if (e.key === 'Escape') { if (!$('face').hidden) $('face').hidden = true; else $('credits').hidden = true; } return; }
|
||||
if (!$('pause').hidden) { if (e.key === 'Escape') closePause(); return; }
|
||||
if (!$('map').hidden) { if (e.key === 'Escape' || e.key === 'p' || e.key === 'P') $('map').hidden = true; return; }
|
||||
if ((e.key === 'p' || e.key === 'P') && running) { openMap(); return; }
|
||||
|
||||
@@ -63,18 +63,19 @@ export const story = {
|
||||
// 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.
|
||||
// Un nome può avere il suo ritratto a pixel (face): nei Ringraziamenti si tocca il nome e compare il volto.
|
||||
credits: [
|
||||
{ role: 'Un gioco di', names: ['Andrea Manzi «Manzolo»'] },
|
||||
{ role: 'Idee e consigli', names: ['Yuri Baldi', 'BaronDeDuke (Gabriele Rossi)', 'Tozzino (Francesco Tozzi)', 'Marco Arcuri «l\'Arcu»'] },
|
||||
{ role: 'Storia e aneddoti di San Piero', names: ['Francesco Bacci'] },
|
||||
{ role: 'Musica', names: ['Andrea Manzi, con Suno'] },
|
||||
{ role: 'Un gioco di', names: [{ name: 'Manzolo (Andrea Manzi)', face: 'assets/credits/manzolo.png' }] },
|
||||
{ role: 'Idee e consigli', names: [{ name: 'Baldoria (Yuri Baldi)', face: 'assets/credits/baldoria.png' }, 'BaronDeDuke (Gabriele Rossi)', { name: 'Tozzino (Francesco Tozzi)', face: 'assets/credits/tozzino.png' }, { name: 'l\'Arcu (Marco Arcuri)', face: 'assets/credits/arcu.png' }] },
|
||||
{ role: 'Storia e aneddoti di San Piero', names: [{ name: 'Francesco Bacci', face: 'assets/credits/bacci.png' }] },
|
||||
{ role: 'Musica', names: ['Manzolo, con Suno'] },
|
||||
{ role: 'Sfondi', names: ['dalle fotografie di San Piero di Andrea'] },
|
||||
// Chi ci ha mandato foto, ricordi o idee: una riga per contributo, si allunga man mano.
|
||||
{ role: 'Fotografie', names: ['Mariano Mirannalti (l\'interno della Pieve)'] },
|
||||
{ role: 'Fotografie', names: [{ name: 'Mariano Mirannalti (l\'interno della Pieve)', face: 'assets/credits/mariano.png' }] },
|
||||
{ role: 'Opere d\'arte nel gioco', names: ['Simone Rocchi (l\'omino di ferro del tennis della chiesa)'] },
|
||||
{ role: 'Voci', names: ['XTTS-v2 di Coqui'] },
|
||||
{ role: 'Carattere', names: ['Press Start 2P di CodeMan38 (licenza OFL)'] },
|
||||
{ role: 'Programmazione', names: ['Andrea Manzi, con Claude e Codex'] },
|
||||
{ role: 'Programmazione', names: ['Manzolo, con Claude e Codex'] },
|
||||
],
|
||||
characters: {
|
||||
andrea: { name: 'Andrea', color: '#ffffff', sprite: 'assets/characters/andrea.json' },
|
||||
@@ -406,7 +407,7 @@ export const story = {
|
||||
id: 'sentiero_trebbio', name: 'sentiero per il Trebbio', box: [0, 200, 90, 160], at: [20, 330], exit: { scene: 'casa_maestro_bacci', at: [120, 350], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'verso_villa_adami', name: 'discesa verso Villa Adami', box: [180, 326, 340, 34], at: [330, 352], exit: { scene: 'salita_villa_adami', at: [330, 300], face: 'left' },
|
||||
id: 'verso_villa_adami', name: 'giù per il Poggio, verso Villa Adami', box: [180, 326, 340, 34], at: [330, 352], exit: { scene: 'salita_fortezza', at: [150, 318], face: 'up' },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -866,7 +867,7 @@ export const story = {
|
||||
entry: [300, 350],
|
||||
hotspots: [
|
||||
{
|
||||
id: 'edicola', name: 'edicola', box: [138, 148, 36, 60], at: [180, 236], face: 'up',
|
||||
id: 'tabernacolo', name: 'tabernacolo', box: [138, 148, 36, 60], at: [180, 236], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'Il tabernacolo prima del ponte. Chi passa per andare via dal paese, un saluto glielo fa sempre.' }],
|
||||
},
|
||||
@@ -874,7 +875,7 @@ export const story = {
|
||||
{
|
||||
id: 'sieve', name: 'la Sieve', box: [380, 170, 260, 40], at: [340, 210], face: 'right',
|
||||
actions: {
|
||||
look: [{ say: 'Sotto il ponte la Sieve. Nel 1758 la piena arrivò fino a Cardetole: in casa mia, di sicuro, c\'erano i pesci.' }],
|
||||
look: [{ say: 'Sotto il ponte la Carza si butta nella Sieve, e da qui vanno avanti insieme.' }, { say: 'Nel 1758 la piena arrivò fino a Cardetole: in casa mia, di sicuro, c\'erano i pesci.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1088,15 +1089,7 @@ export const story = {
|
||||
entry: [150, 206],
|
||||
hotspots: [
|
||||
{
|
||||
id: 'verso_la_luna', name: 'avanti, verso La Luna e Campomigliaio', box: [0, 334, 330, 26], at: [150, 352], face: 'down', default: 'walk',
|
||||
actions: {
|
||||
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.' }],
|
||||
},
|
||||
id: 'verso_la_luna', name: 'avanti, verso La Luna e Campomigliaio', box: [0, 334, 330, 26], at: [150, 352], exit: { scene: 'campomigliaio_strada', at: [300, 350], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'carza', name: 'la Carza', box: [340, 205, 300, 155], at: [250, 300], face: 'right',
|
||||
@@ -1129,6 +1122,129 @@ export const story = {
|
||||
},
|
||||
],
|
||||
},
|
||||
// Campomigliaio, il rione dei blu (foto dell'Arcu): la strada in salita, il Circolo, il ponte vecchio.
|
||||
campomigliaio_strada: {
|
||||
name: 'Campomigliaio, la strada',
|
||||
map: [60, 78],
|
||||
background: 'assets/scenes/campomigliaio-strada.png',
|
||||
walk: [[0, 360], [0, 300], [250, 222], [300, 210], [350, 210], [420, 240], [640, 345], [640, 360]],
|
||||
depth: [[210, 0.35], [360, 1.7]],
|
||||
entry: [300, 350],
|
||||
hotspots: [
|
||||
{
|
||||
id: 'al_circolo', name: 'su, verso il circolino', box: [270, 195, 90, 25], at: [320, 214], exit: { scene: 'campomigliaio_circolo', at: [150, 345], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'torretta', name: 'casa con la torretta', box: [280, 120, 80, 62], at: [320, 222], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'In cima alla salita, la casa con la torretta. Da quaggiù sembra il castello di Campomigliaio.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'muro', name: 'muro di pietra', box: [430, 190, 210, 140], at: [420, 300], face: 'right',
|
||||
actions: {
|
||||
look: [{ say: 'Un muro di sassi a secco, con la ringhiera sopra. Ne ha visti passare di pali.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'porta', name: 'porta marrone', box: [150, 165, 32, 56], at: [180, 270], face: 'left',
|
||||
actions: {
|
||||
look: [{ say: 'Una porta marrone, chiusa.' }],
|
||||
open: [{ say: 'Chiusa. Saranno tutti al circolino.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'alla_carza', name: 'indietro, verso la Provinciale', box: [0, 334, 640, 26], at: [300, 354], exit: { scene: 'verso_luna', at: [150, 330], face: 'up' },
|
||||
},
|
||||
],
|
||||
},
|
||||
campomigliaio_circolo: {
|
||||
name: 'Campomigliaio, il circolino',
|
||||
map: [55, 82],
|
||||
background: 'assets/scenes/campomigliaio-circolo.png',
|
||||
walk: [[0, 360], [0, 335], [60, 300], [400, 280], [640, 250], [640, 360]],
|
||||
depth: [[250, 1.0], [360, 1.5]],
|
||||
entry: [320, 330],
|
||||
hotspots: [
|
||||
{
|
||||
id: 'circolo', name: 'il circolino', box: [100, 0, 380, 205], at: [300, 290], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'Il circolino: il Circolo SMS di Campomigliaio. Un\'istituzione, qui i volontari hanno sempre fatto miracoli.' }, { say: 'Il sabato sera ci si mangia una pizza ottima, e si sta meglio che a casa. Si respira fratellanza.' }],
|
||||
open: [{
|
||||
if: 'rione:campomigliaio',
|
||||
then: [{ say: 'Se entro ora, mi chiedono tutti del palio. Prima lo ritrovo, poi entro a testa alta.' }],
|
||||
else: [{ say: 'Il circolino dei blu, il giorno prima del palio? Meglio di no.' }],
|
||||
}],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'fontanella', name: 'fontanella', box: [364, 212, 26, 58], at: [376, 286], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'Una fontanella di ghisa. Acqua fresca e gratis, la cosa più preziosa di maggio.' }],
|
||||
use: [{ say: 'Un sorso d\'acqua. Ora sì che si ragiona.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'insegna', name: 'insegna del circolino', box: [484, 86, 28, 62], at: [500, 272], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'L\'insegna del circolino. Senza scritte: tanto qui lo sanno tutti dov\'è.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'panchina', name: 'panchina', box: [520, 206, 52, 30], at: [545, 262], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'La panchina davanti al circolino. Di solito è occupata da chi sa tutto di tutti.' }],
|
||||
use: [{ say: 'Non è il momento di sedersi. Il palio non si cerca da solo.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'alla_strada', name: 'giù per la strada', box: [0, 334, 300, 26], at: [150, 352], exit: { scene: 'campomigliaio_strada', at: [320, 216], face: 'down' },
|
||||
},
|
||||
{
|
||||
id: 'al_ponte', name: 'a sinistra, verso il ponte', box: [44, 220, 40, 80], at: [70, 312], exit: { scene: 'campomigliaio_ponte', at: [80, 330], face: 'right' },
|
||||
},
|
||||
{
|
||||
id: 'tagliaferro', name: 'a diritto, verso Tagliaferro', box: [596, 210, 44, 60], at: [622, 258], face: 'right', default: 'walk',
|
||||
actions: {
|
||||
walk: [{ say: 'A diritto si va a Tagliaferro. Il palio non sarà mica scappato fin laggiù.' }],
|
||||
look: [{ say: 'La strada va avanti verso Tagliaferro.' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
campomigliaio_ponte: {
|
||||
name: 'Campomigliaio, il ponte vecchio',
|
||||
map: [50, 86],
|
||||
background: 'assets/scenes/campomigliaio-ponte.png',
|
||||
walk: [[0, 360], [0, 232], [100, 225], [140, 240], [170, 275], [260, 330], [300, 360]],
|
||||
depth: [[225, 0.6], [360, 1.5]],
|
||||
entry: [80, 330],
|
||||
hotspots: [
|
||||
{
|
||||
id: 'ponte', name: 'il ponte vecchio', box: [60, 100, 290, 95], at: [110, 240], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'Il ponte vecchio, coi suoi due archi. Chissà quante piene ha visto passare: io al primo temporale cerco riparo.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'torrente', name: 'il torrente', box: [150, 200, 320, 160], at: [200, 300], face: 'right',
|
||||
actions: {
|
||||
look: [{ say: 'L\'acqua è bassa e limpida. Si vedono i sassi del fondo, e nessun palio.' }],
|
||||
use: [{ say: 'Mi bagno le scarpe e poi chi la sente, la mamma.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'tronco', name: 'tronco', box: [318, 230, 74, 56], at: [215, 315], face: 'right',
|
||||
actions: {
|
||||
look: [{ say: 'Un tronco bianco portato giù dalla piena, piantato in mezzo all\'acqua.' }],
|
||||
take: [{ say: 'Troppo pesante, e troppo bagnato. Lo lascio ai girini.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'al_circolo', name: 'indietro, verso il circolino', box: [0, 334, 280, 26], at: [120, 354], exit: { scene: 'campomigliaio_circolo', at: [76, 314], face: 'right' },
|
||||
},
|
||||
],
|
||||
},
|
||||
// «Il tennis della chiesa»: il campo dietro la Pieve, dal cancello verde della Carza.
|
||||
tennis_chiesa: {
|
||||
name: 'Il tennis della chiesa',
|
||||
@@ -1185,26 +1301,26 @@ export const story = {
|
||||
entry: [120, 350],
|
||||
hotspots: [
|
||||
{
|
||||
id: 'casa', name: 'casa del maestro Bacci', box: [186, 186, 100, 30], at: [140, 240], face: 'up',
|
||||
id: 'casa', name: 'giù a destra, la casa del maestro Bacci', box: [500, 296, 140, 64], at: [200, 340], face: 'right',
|
||||
actions: {
|
||||
look: [{ say: 'Laggiù c\'è la casa del maestro Bacci. Mezzo paese ha imparato a leggere e scrivere da lui.' }],
|
||||
look: [{ say: 'Giù a destra, oltre le vigne, c\'è la casa del maestro Bacci. Mezzo paese ha imparato a leggere e scrivere da lui.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'trebbio', name: 'castello del Trebbio', box: [400, 112, 50, 30], at: [140, 240], face: 'up',
|
||||
id: 'trebbio', name: 'castello del Trebbio', box: [298, 76, 36, 22], at: [140, 240], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'Lassù in cima, la torre del Trebbio. Da qui sembra piccola come una pedina.' }],
|
||||
look: [{ say: 'Eccolo lì, il castello del Trebbio, in tutto il suo splendore. Da quaggiù sembra una pedina.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'vigne', name: 'vigne', box: [160, 250, 480, 110], at: [190, 320], face: 'right',
|
||||
id: 'vigne', name: 'vigne', box: [160, 250, 340, 110], at: [190, 320], face: 'right',
|
||||
actions: {
|
||||
look: [{ say: 'Le vigne in fila. A settembre qui si vendemmia, e tutto il paese ha le mani appiccicose.' }],
|
||||
take: [{ say: 'L\'uva a maggio? È ancora un\'idea d\'uva.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'ai_cipressi', name: 'avanti, verso i cipressi e il Trebbio', box: [110, 200, 60, 34], at: [138, 226], exit: { scene: 'viale_cipressi', at: [300, 350], face: 'up' },
|
||||
id: 'ai_cipressi', name: 'avanti, verso i cipressi e il Trebbio', box: [120, 150, 56, 30], at: [138, 226], exit: { scene: 'viale_cipressi', at: [300, 350], face: 'up' },
|
||||
},
|
||||
{
|
||||
id: 'alla_fortezza', name: 'indietro, verso la Fortezza', box: [0, 330, 240, 30], at: [120, 354], exit: { scene: 'fortezza', at: [20, 330], face: 'right' },
|
||||
@@ -1275,6 +1391,42 @@ export const story = {
|
||||
},
|
||||
],
|
||||
},
|
||||
// Il Poggio, dalla salita del leone alla Fortezza: la stradina col muretto e tutto il Mugello sotto (foto di Andrea, 24 ottobre).
|
||||
salita_fortezza: {
|
||||
name: 'Il Poggio',
|
||||
map: [37, 12],
|
||||
background: 'assets/scenes/salita-fortezza.png',
|
||||
walk: [[0, 360], [0, 322], [80, 264], [150, 246], [195, 246], [222, 262], [296, 360]],
|
||||
depth: [[246, 0.45], [360, 1.4]],
|
||||
entry: [150, 345],
|
||||
hotspots: [
|
||||
{
|
||||
id: 'giu_leone', name: 'giù, verso Villa Adami', box: [130, 222, 90, 30], at: [175, 250], exit: { scene: 'salita_villa_adami', at: [326, 259], face: 'right' },
|
||||
},
|
||||
{
|
||||
id: 'su_fortezza', name: 'su, alla Fortezza', box: [0, 334, 296, 26], at: [150, 352], exit: { scene: 'fortezza', at: [330, 300], face: 'left' },
|
||||
},
|
||||
{
|
||||
id: 'panorama', name: 'il Mugello', box: [300, 120, 340, 80], at: [190, 300], face: 'right',
|
||||
actions: {
|
||||
look: [{ say: 'Da quassù si vede tutto: i tetti del paese, i campi, e in fondo i monti del Mugello.' }, { say: 'Il palio però da qui non si vede. Ci avevo sperato.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'fico', name: 'fico', box: [460, 200, 180, 160], at: [240, 320], face: 'right',
|
||||
actions: {
|
||||
look: [{ say: 'Un fico che si sporge dal muretto. A fine maggio i fichi sono ancora duri: torno ad agosto.' }],
|
||||
take: [{ say: 'Acerbi. E poi non sono miei.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'case', name: 'case', box: [40, 0, 100, 240], at: [60, 320], face: 'left',
|
||||
actions: {
|
||||
look: [{ say: 'Le case del Poggio, che salgono verso la Fortezza coi fiori alle finestre e i muri di sasso.' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Tra il livello 1 e il 2: la salita di Villa Adami, con la casa del leone. Si sale alla Fortezza.
|
||||
salita_villa_adami: {
|
||||
name: 'Salita di Villa Adami',
|
||||
@@ -1300,7 +1452,7 @@ export const story = {
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'su_fortezza', name: 'salita verso la Fortezza', box: [168, 196, 182, 46], at: [326, 259], face: 'left', exit: { scene: 'fortezza', at: [175, 352], face: 'right' },
|
||||
id: 'su_fortezza', name: 'su per il Poggio, verso la Fortezza', box: [168, 196, 182, 46], at: [326, 259], face: 'left', exit: { scene: 'salita_fortezza', at: [175, 252], face: 'down' },
|
||||
},
|
||||
{
|
||||
id: 'verso_villa_adami', name: 'discesa verso Villa Adami', box: [200, 334, 280, 26], at: [340, 356], exit: { scene: 'villa_adami', at: [300, 345], face: 'right' },
|
||||
@@ -1365,9 +1517,9 @@ export const story = {
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'edicola', name: 'edicola della Madonna', box: [442, 148, 36, 44], at: [458, 204], face: 'up',
|
||||
id: 'tabernacolo', name: 'tabernacolo della Madonna', box: [442, 148, 36, 44], at: [458, 204], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'La piccola edicola in fondo al ponticino. C\'è sempre un fiore fresco, ma nessuno l\'ha mai visto metterlo.' }],
|
||||
look: [{ say: 'Il piccolo tabernacolo in fondo al ponticino. C\'è sempre un fiore fresco, ma nessuno l\'ha mai visto metterlo.' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -173,9 +173,24 @@ body.full #sound { position: fixed; z-index: 13; top: max(8px, env(safe-area-ins
|
||||
#credits { justify-content: flex-start; overflow: hidden; padding-top: 4%; background: rgba(15, 10, 20, .96); }
|
||||
#credits .credits-dedication { margin: .2em 0 .5em !important; font-size: clamp(10px, 1.6vw, 17px); }
|
||||
/* Due colonne: il ruolo a destra della sua colonna, il nome a sinistra della sua, allineati sulla riga. */
|
||||
#credits dl { flex: 1 1 auto; min-height: 0; overflow-y: auto; align-content: start; padding-right: .4em; 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 dl { flex: 1 1 auto; min-height: 0; overflow-y: auto; align-content: start; padding-right: .4em; margin: .2em 0 0; display: grid; grid-template-columns: 1fr 1fr; column-gap: 1em; row-gap: .45em; align-items: baseline; width: min(100%, 44em); scrollbar-width: thin; }
|
||||
#credits dt { text-align: right; color: var(--dim); font-size: clamp(7.2px, 1.01vw, 10.08px); letter-spacing: .12em; text-transform: uppercase; }
|
||||
#credits dd span { display: block; }
|
||||
/* I nomi col ritratto: si toccano e compare il volto a pixel sopra i Ringraziamenti. */
|
||||
#credits-reel { flex: 0 0 auto; height: clamp(56px, 24vh, 200px); width: 100%; overflow: hidden; margin: .5em 0 .3em;
|
||||
-webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
|
||||
#credits-reel .track { display: flex; gap: 10px; height: 100%; width: max-content; animation: reel 240s linear infinite; }
|
||||
#credits-reel img { flex: none; height: 100%; aspect-ratio: 16 / 9; object-fit: cover; image-rendering: pixelated; border-radius: 6px; border: 2px solid #3a2d42; }
|
||||
#credits-reel img.face { aspect-ratio: 1; border-color: var(--accent); }
|
||||
@keyframes reel { to { transform: translateX(-50%); } }
|
||||
@media (prefers-reduced-motion: reduce) { #credits-reel .track { animation: none; } }
|
||||
#credits .credit-face { all: unset; display: block; cursor: pointer; color: var(--accent); text-decoration: underline dotted; text-underline-offset: .25em; }
|
||||
#credits .credit-face:hover, #credits .credit-face:focus-visible { color: #fff; }
|
||||
#face { position: absolute; inset: 0; margin: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .8em;
|
||||
background: rgba(15, 10, 20, .92); cursor: pointer; z-index: 1; }
|
||||
#face[hidden] { display: none; }
|
||||
#face img { width: min(60vmin, 384px); aspect-ratio: 1; image-rendering: pixelated; border: 3px solid var(--accent); border-radius: 8px; }
|
||||
#face figcaption { color: var(--ink); font-size: clamp(9px, 1.4vw, 14px); text-align: center; padding: 0 1em; }
|
||||
#credits dd { margin: 0; text-align: left; color: var(--ink); font-size: clamp(8px, 1.15vw, 12px); line-height: 1.6; font-weight: bold; }
|
||||
#credits .legal { color: var(--dim); font-size: clamp(7.2px, 0.86vw, 9.36px); opacity: .8; }
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { chromium } from 'playwright';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { mkdirSync, readFileSync } from 'node:fs';
|
||||
import assert from 'node:assert/strict';
|
||||
import { solution } from '../src/story/solution.js';
|
||||
import { solution, route } from '../src/story/solution.js';
|
||||
|
||||
const OUT = process.env.OUT ?? '/tmp/regolo-shots', PORT = 8000 + Math.floor(Math.random() * 900);
|
||||
mkdirSync(OUT, { recursive: true });
|
||||
@@ -64,9 +64,16 @@ try {
|
||||
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'), /Ringraziamenti\s*Al Lele/, 'la dedica al Lele viene per prima');
|
||||
assert.match(await page.textContent('#credits'), /Baldoria \(Yuri Baldi\)/);
|
||||
assert.match(await page.textContent('#credits'), /Francesco Bacci/);
|
||||
// I nomi col ritratto: si tocca il nome, compare il volto; un altro tocco lo chiude.
|
||||
await page.click('#credits .credit-face >> text=Mariano');
|
||||
assert.ok(await page.isVisible('#face img'));
|
||||
await page.waitForFunction(() => document.querySelector('#face img').naturalWidth > 0, null, { timeout: 5000 });
|
||||
await page.screenshot({ path: `${OUT}/01d-ritratto.png` });
|
||||
await page.click('#face');
|
||||
assert.ok(await page.isHidden('#face'));
|
||||
const onTop = await page.evaluate(() => { const r = document.getElementById('close-credits').getBoundingClientRect();
|
||||
return document.elementFromPoint(r.x + r.width / 2, r.y + r.height / 2)?.id === 'close-credits'; });
|
||||
assert.ok(onTop, 'i crediti stanno sopra il titolo, non dietro');
|
||||
@@ -214,7 +221,7 @@ try {
|
||||
// Una partita a metà riprende da dove era rimasta.
|
||||
await page.click('#restart');
|
||||
await settle(page);
|
||||
for (const exit of ['verso_villa_adami', 'su_villa_adami', 'salita', 'su_fortezza', 'sentiero_trebbio', 'ai_cipressi', 'al_trebbio']) {
|
||||
for (const [, exit] of route('piazza', 'trebbio')) {
|
||||
await clickScene(page, await hotspotCenter(page, exit));
|
||||
await settle(page);
|
||||
}
|
||||
|
||||
@@ -77,14 +77,17 @@ test('la storia è coerente: scene, oggetti, dialoghi e punti calpestabili esist
|
||||
}
|
||||
});
|
||||
|
||||
const nameOf = n => typeof n === 'string' ? n : n.name;
|
||||
|
||||
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}`);
|
||||
assert.ok(c.names?.length && c.names.every(n => nameOf(n).trim()), `nomi vuoti in ${c.role}`);
|
||||
for (const n of c.names) if (n.face) assert.ok(existsSync(new URL('../' + n.face, import.meta.url)), `manca il ritratto ${n.face}`);
|
||||
}
|
||||
const all = story.credits.flatMap(c => c.names);
|
||||
for (const name of ['Yuri Baldi', 'Francesco Bacci']) assert.ok(all.includes(name), `manca ${name}`);
|
||||
const all = story.credits.flatMap(c => c.names.map(nameOf));
|
||||
for (const name of ['Baldoria (Yuri Baldi)', 'l\'Arcu (Marco Arcuri)', 'Francesco Bacci']) assert.ok(all.includes(name), `manca ${name}`);
|
||||
});
|
||||
|
||||
test('i crediti: i nomi di ogni voce escono in ordine casuale, sempre tutti', () => {
|
||||
@@ -95,8 +98,8 @@ test('i crediti: i nomi di ogni voce escono in ordine casuale, sempre tutti', ()
|
||||
const random = () => ((x = (x * 16807) % 2147483647) / 2147483647);
|
||||
const out = shuffledCredits(story.credits, random);
|
||||
assert.deepEqual(out.map(c => c.role), story.credits.map(c => c.role), 'le voci restano nel loro ordine');
|
||||
out.forEach((c, i) => assert.deepEqual([...c.names].sort(), [...story.credits[i].names].sort()));
|
||||
orders.add(out.find(c => c.role === idee).names.join('|'));
|
||||
out.forEach((c, i) => assert.deepEqual(c.names.map(nameOf).sort(), story.credits[i].names.map(nameOf).sort()));
|
||||
orders.add(out.find(c => c.role === idee).names.map(nameOf).join('|'));
|
||||
}
|
||||
assert.ok(orders.size > 3, `${idee}: l'ordine non cambia`);
|
||||
});
|
||||
|
||||