diff --git a/Dockerfile b/Dockerfile
index eacd705..1f9eb20 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -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
diff --git a/assets/credits/arcu.png b/assets/credits/arcu.png
new file mode 100644
index 0000000..921cc06
Binary files /dev/null and b/assets/credits/arcu.png differ
diff --git a/assets/credits/bacci.png b/assets/credits/bacci.png
new file mode 100644
index 0000000..bb00c53
Binary files /dev/null and b/assets/credits/bacci.png differ
diff --git a/assets/credits/baldoria.png b/assets/credits/baldoria.png
new file mode 100644
index 0000000..8a75569
Binary files /dev/null and b/assets/credits/baldoria.png differ
diff --git a/assets/credits/manzolo.png b/assets/credits/manzolo.png
new file mode 100644
index 0000000..2753ee3
Binary files /dev/null and b/assets/credits/manzolo.png differ
diff --git a/assets/credits/mariano.png b/assets/credits/mariano.png
new file mode 100644
index 0000000..abfffbc
Binary files /dev/null and b/assets/credits/mariano.png differ
diff --git a/assets/credits/tozzino.png b/assets/credits/tozzino.png
new file mode 100644
index 0000000..486eddd
Binary files /dev/null and b/assets/credits/tozzino.png differ
diff --git a/assets/scenes/mini/salita-fortezza.png b/assets/scenes/mini/salita-fortezza.png
new file mode 100644
index 0000000..5e00e19
Binary files /dev/null and b/assets/scenes/mini/salita-fortezza.png differ
diff --git a/assets/scenes/salita-fortezza.png b/assets/scenes/salita-fortezza.png
new file mode 100644
index 0000000..82ae604
Binary files /dev/null and b/assets/scenes/salita-fortezza.png differ
diff --git a/assets/voices/andrea-00f2e70a4d.mp3 b/assets/voices/andrea-00f2e70a4d.mp3
new file mode 100644
index 0000000..f96634d
Binary files /dev/null and b/assets/voices/andrea-00f2e70a4d.mp3 differ
diff --git a/assets/voices/andrea-01ec1d03ec.mp3 b/assets/voices/andrea-01ec1d03ec.mp3
new file mode 100644
index 0000000..e1ff226
Binary files /dev/null and b/assets/voices/andrea-01ec1d03ec.mp3 differ
diff --git a/assets/voices/andrea-023905aed9.mp3 b/assets/voices/andrea-023905aed9.mp3
new file mode 100644
index 0000000..9418087
Binary files /dev/null and b/assets/voices/andrea-023905aed9.mp3 differ
diff --git a/assets/voices/andrea-04c0898047.mp3 b/assets/voices/andrea-04c0898047.mp3
new file mode 100644
index 0000000..59525f2
Binary files /dev/null and b/assets/voices/andrea-04c0898047.mp3 differ
diff --git a/assets/voices/andrea-060513a152.mp3 b/assets/voices/andrea-060513a152.mp3
new file mode 100644
index 0000000..393be3f
Binary files /dev/null and b/assets/voices/andrea-060513a152.mp3 differ
diff --git a/assets/voices/andrea-1b9468d963.mp3 b/assets/voices/andrea-1b9468d963.mp3
new file mode 100644
index 0000000..c2672c9
Binary files /dev/null and b/assets/voices/andrea-1b9468d963.mp3 differ
diff --git a/assets/voices/andrea-23d87e6493.mp3 b/assets/voices/andrea-23d87e6493.mp3
new file mode 100644
index 0000000..e1f5c1e
Binary files /dev/null and b/assets/voices/andrea-23d87e6493.mp3 differ
diff --git a/assets/voices/andrea-29c2c930b4.mp3 b/assets/voices/andrea-29c2c930b4.mp3
new file mode 100644
index 0000000..b0748d4
Binary files /dev/null and b/assets/voices/andrea-29c2c930b4.mp3 differ
diff --git a/assets/voices/andrea-2e9330c511.mp3 b/assets/voices/andrea-2e9330c511.mp3
new file mode 100644
index 0000000..3508760
Binary files /dev/null and b/assets/voices/andrea-2e9330c511.mp3 differ
diff --git a/assets/voices/andrea-2f1517d79d.mp3 b/assets/voices/andrea-2f1517d79d.mp3
new file mode 100644
index 0000000..5a1fdac
Binary files /dev/null and b/assets/voices/andrea-2f1517d79d.mp3 differ
diff --git a/assets/voices/andrea-33857840d8.mp3 b/assets/voices/andrea-33857840d8.mp3
new file mode 100644
index 0000000..5cc32e9
Binary files /dev/null and b/assets/voices/andrea-33857840d8.mp3 differ
diff --git a/assets/voices/andrea-4f89e093b5.mp3 b/assets/voices/andrea-4f89e093b5.mp3
new file mode 100644
index 0000000..0eb9e92
Binary files /dev/null and b/assets/voices/andrea-4f89e093b5.mp3 differ
diff --git a/assets/voices/andrea-5536ba1bf5.mp3 b/assets/voices/andrea-5536ba1bf5.mp3
deleted file mode 100644
index aa59616..0000000
Binary files a/assets/voices/andrea-5536ba1bf5.mp3 and /dev/null differ
diff --git a/assets/voices/andrea-57fb036fc1.mp3 b/assets/voices/andrea-57fb036fc1.mp3
deleted file mode 100644
index 7388220..0000000
Binary files a/assets/voices/andrea-57fb036fc1.mp3 and /dev/null differ
diff --git a/assets/voices/andrea-59afc50d80.mp3 b/assets/voices/andrea-59afc50d80.mp3
deleted file mode 100644
index a62699f..0000000
Binary files a/assets/voices/andrea-59afc50d80.mp3 and /dev/null differ
diff --git a/assets/voices/andrea-5bf82d4aaa.mp3 b/assets/voices/andrea-5bf82d4aaa.mp3
new file mode 100644
index 0000000..560786b
Binary files /dev/null and b/assets/voices/andrea-5bf82d4aaa.mp3 differ
diff --git a/assets/voices/andrea-7301f8df3d.mp3 b/assets/voices/andrea-7301f8df3d.mp3
deleted file mode 100644
index b798a8e..0000000
Binary files a/assets/voices/andrea-7301f8df3d.mp3 and /dev/null differ
diff --git a/assets/voices/andrea-74470f90d0.mp3 b/assets/voices/andrea-74470f90d0.mp3
new file mode 100644
index 0000000..ae4e7a9
Binary files /dev/null and b/assets/voices/andrea-74470f90d0.mp3 differ
diff --git a/assets/voices/andrea-7883437f7d.mp3 b/assets/voices/andrea-7883437f7d.mp3
deleted file mode 100644
index 255a6c8..0000000
Binary files a/assets/voices/andrea-7883437f7d.mp3 and /dev/null differ
diff --git a/assets/voices/andrea-78ffacf27b.mp3 b/assets/voices/andrea-78ffacf27b.mp3
new file mode 100644
index 0000000..530cf9d
Binary files /dev/null and b/assets/voices/andrea-78ffacf27b.mp3 differ
diff --git a/assets/voices/andrea-85707a4fca.mp3 b/assets/voices/andrea-85707a4fca.mp3
deleted file mode 100644
index cbe2e44..0000000
Binary files a/assets/voices/andrea-85707a4fca.mp3 and /dev/null differ
diff --git a/assets/voices/andrea-86d675f890.mp3 b/assets/voices/andrea-86d675f890.mp3
new file mode 100644
index 0000000..253b439
Binary files /dev/null and b/assets/voices/andrea-86d675f890.mp3 differ
diff --git a/assets/voices/andrea-8d9bda2db8.mp3 b/assets/voices/andrea-8d9bda2db8.mp3
new file mode 100644
index 0000000..1fb637f
Binary files /dev/null and b/assets/voices/andrea-8d9bda2db8.mp3 differ
diff --git a/assets/voices/andrea-90bbad0de0.mp3 b/assets/voices/andrea-90bbad0de0.mp3
new file mode 100644
index 0000000..87f3123
Binary files /dev/null and b/assets/voices/andrea-90bbad0de0.mp3 differ
diff --git a/assets/voices/andrea-914e32fcca.mp3 b/assets/voices/andrea-914e32fcca.mp3
new file mode 100644
index 0000000..4075f77
Binary files /dev/null and b/assets/voices/andrea-914e32fcca.mp3 differ
diff --git a/assets/voices/andrea-93acb42cf7.mp3 b/assets/voices/andrea-93acb42cf7.mp3
new file mode 100644
index 0000000..6eeb380
Binary files /dev/null and b/assets/voices/andrea-93acb42cf7.mp3 differ
diff --git a/assets/voices/andrea-9f914e2a8b.mp3 b/assets/voices/andrea-9f914e2a8b.mp3
new file mode 100644
index 0000000..9e20f07
Binary files /dev/null and b/assets/voices/andrea-9f914e2a8b.mp3 differ
diff --git a/assets/voices/andrea-a96e07a9f3.mp3 b/assets/voices/andrea-a96e07a9f3.mp3
deleted file mode 100644
index 2128264..0000000
Binary files a/assets/voices/andrea-a96e07a9f3.mp3 and /dev/null differ
diff --git a/assets/voices/andrea-b3120ac9f3.mp3 b/assets/voices/andrea-b3120ac9f3.mp3
new file mode 100644
index 0000000..4386483
Binary files /dev/null and b/assets/voices/andrea-b3120ac9f3.mp3 differ
diff --git a/assets/voices/andrea-b814f7d1a0.mp3 b/assets/voices/andrea-b814f7d1a0.mp3
new file mode 100644
index 0000000..f5c257c
Binary files /dev/null and b/assets/voices/andrea-b814f7d1a0.mp3 differ
diff --git a/assets/voices/andrea-bb70dea2a6.mp3 b/assets/voices/andrea-bb70dea2a6.mp3
new file mode 100644
index 0000000..c38a50e
Binary files /dev/null and b/assets/voices/andrea-bb70dea2a6.mp3 differ
diff --git a/assets/voices/andrea-d44bcc0c68.mp3 b/assets/voices/andrea-d44bcc0c68.mp3
new file mode 100644
index 0000000..9717bcb
Binary files /dev/null and b/assets/voices/andrea-d44bcc0c68.mp3 differ
diff --git a/assets/voices/andrea-df05ecea32.mp3 b/assets/voices/andrea-df05ecea32.mp3
deleted file mode 100644
index 5a13cc0..0000000
Binary files a/assets/voices/andrea-df05ecea32.mp3 and /dev/null differ
diff --git a/assets/voices/andrea-e044dea7f2.mp3 b/assets/voices/andrea-e044dea7f2.mp3
new file mode 100644
index 0000000..3230cd8
Binary files /dev/null and b/assets/voices/andrea-e044dea7f2.mp3 differ
diff --git a/assets/voices/andrea-ed03a4f29a.mp3 b/assets/voices/andrea-ed03a4f29a.mp3
new file mode 100644
index 0000000..66efbba
Binary files /dev/null and b/assets/voices/andrea-ed03a4f29a.mp3 differ
diff --git a/assets/voices/andrea-f07cb45320.mp3 b/assets/voices/andrea-f07cb45320.mp3
new file mode 100644
index 0000000..1537744
Binary files /dev/null and b/assets/voices/andrea-f07cb45320.mp3 differ
diff --git a/assets/voices/andrea-f322731846.mp3 b/assets/voices/andrea-f322731846.mp3
new file mode 100644
index 0000000..8f5ce4a
Binary files /dev/null and b/assets/voices/andrea-f322731846.mp3 differ
diff --git a/assets/voices/andrea-f753b8c79c.mp3 b/assets/voices/andrea-f753b8c79c.mp3
deleted file mode 100644
index 5486189..0000000
Binary files a/assets/voices/andrea-f753b8c79c.mp3 and /dev/null differ
diff --git a/assets/voices/andrea-fea8986ed0.mp3 b/assets/voices/andrea-fea8986ed0.mp3
new file mode 100644
index 0000000..a2d46b2
Binary files /dev/null and b/assets/voices/andrea-fea8986ed0.mp3 differ
diff --git a/assets/voices/index.json b/assets/voices/index.json
index 738fcfd..51af1b4 100644
--- a/assets/voices/index.json
+++ b/assets/voices/index.json
@@ -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",
diff --git a/index.html b/index.html
index a0902f3..f2860fb 100644
--- a/index.html
+++ b/index.html
@@ -30,7 +30,7 @@
La Leggenda del Regolo
San Piero a Sieve · Palio della Fortezza · 2007
-
+
Al Lele, che mi ha fatto conoscere Monkey Island.
@@ -49,7 +49,7 @@
Fine del primo capitolo
Il palio è laggiù, nel buio della Fortezza. E il Regolo pure. Il resto della storia è ancora da scrivere.
Al Lele, che mi ha fatto conoscere Monkey Island.
-
+
San Piero a Sieve
@@ -63,17 +63,19 @@
-
+
La partita è salvata: dal titolo si riprende con «Continua». Con «Salva su file» la porti su un altro dispositivo e la apri con «Carica».
-
-
Crediti
+
+
Ringraziamenti
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 54bf11b..3779199 100644
--- a/src/main.js
+++ b/src/main.js
@@ -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; }
diff --git a/src/story/story.js b/src/story/story.js
index 7462dbc..b78f002 100644
--- a/src/story/story.js
+++ b/src/story/story.js
@@ -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.' }],
},
},
{
diff --git a/style.css b/style.css
index 3080a11..421bb70 100644
--- a/style.css
+++ b/style.css
@@ -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; }
diff --git a/tests/browser.mjs b/tests/browser.mjs
index 269cd8e..2d38ff3 100644
--- a/tests/browser.mjs
+++ b/tests/browser.mjs
@@ -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);
}
diff --git a/tests/story.test.mjs b/tests/story.test.mjs
index 46648c5..abdea21 100644
--- a/tests/story.test.mjs
+++ b/tests/story.test.mjs
@@ -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`);
});