Il carosello dei Ringraziamenti si ferma, si sfoglia e ingrandisce le foto
Tutti gli sfondi del paese (game.reelPictures, uno per scena, col nome del posto) e ogni ritratto una volta sola (prima tre si ripetevano). Scorre da solo ma si ferma sotto il mouse, si trascina col mouse o col dito, si spinge con la rotella, con le frecce ai lati e con quelle della tastiera; un tocco su una foto la mostra grande col nome (il tocco si legge al rilascio: con la cattura del puntatore il clic non arriva all'immagine). Test della fila e prova nel browser. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECdBYE8csKd4tcFvwntT
This commit is contained in:
+65
-18
@@ -1,6 +1,6 @@
|
||||
// Avvio nel browser: collega motore, disegno e interfaccia HTML (verbi, inventario, dialoghi, fumetti).
|
||||
import { story } from './story/story.js';
|
||||
import { Game, VERBS, VERB_LABELS, checkSave, introText, shuffledCredits, matchPlace } from './engine/game.js';
|
||||
import { Game, VERBS, VERB_LABELS, checkSave, introText, shuffledCredits, matchPlace, reelPictures, facePicture } from './engine/game.js';
|
||||
import { Command } from './engine/command.js';
|
||||
import { render, loadAssets, hasBackground, WIDTH, HEIGHT } from './engine/render.js';
|
||||
import { readSave, writeSave, clearSave, readAudio, writeAudio, saveFile, saveFileName, readSaveFile } from './engine/storage.js';
|
||||
@@ -125,7 +125,7 @@ function openCredits() {
|
||||
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));
|
||||
b.addEventListener('click', () => showFace(facePicture(n)));
|
||||
return b;
|
||||
}));
|
||||
return [dt, dd];
|
||||
@@ -133,22 +133,64 @@ function openCredits() {
|
||||
fillReel();
|
||||
$('credits').hidden = false;
|
||||
rollCredits();
|
||||
rollReel();
|
||||
}
|
||||
// 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.
|
||||
// Sotto i nomi, il carosello: tutti gli sfondi del paese e i ritratti (game.reelPictures), due giri uguali in fila per
|
||||
// non vedere mai la cucitura. Scorre da solo; si ferma sotto il mouse, si trascina, si spinge con la rotella, le
|
||||
// frecce ai lati o quelle della tastiera, e toccando una foto la si vede grande (Andrea, 27 ottobre).
|
||||
const reelTrack = $('credits-reel').querySelector('.track');
|
||||
let reeling = 0, reelWait = 0, reelDrag = null, reelDragged = false, reelTap = null;
|
||||
const reelPics = new WeakMap();
|
||||
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 = src.endsWith('palio.png') ? 'palio' : 'face';
|
||||
track.append(img);
|
||||
if (reelTrack.children.length) return;
|
||||
const all = reelPictures(story);
|
||||
for (const p of [...all, ...all]) {
|
||||
const img = Object.assign(document.createElement('img'), { src: versioned(p.src), alt: p.name, title: p.name, className: p.kind, loading: 'lazy', decoding: 'async', draggable: false });
|
||||
reelPics.set(img, p);
|
||||
reelTrack.append(img);
|
||||
}
|
||||
}
|
||||
function rollReel() {
|
||||
cancelAnimationFrame(reeling);
|
||||
reelTrack.scrollLeft = 0;
|
||||
reelWait = 0;
|
||||
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||
let last = performance.now(), pos = 0;
|
||||
const step = now => {
|
||||
if ($('credits').hidden) return;
|
||||
const dt = now - last; last = now;
|
||||
const half = reelTrack.scrollWidth / 2;
|
||||
if (reelTrack.matches(':hover') || reelDrag || !$('face').hidden || (reelWait -= dt) > 0) pos = reelTrack.scrollLeft;
|
||||
else { pos += dt * 0.05; if (pos >= half) pos -= half; reelTrack.scrollLeft = pos; }
|
||||
reeling = requestAnimationFrame(step);
|
||||
};
|
||||
reeling = requestAnimationFrame(step);
|
||||
}
|
||||
// Di una foto alla volta, con le frecce.
|
||||
function nudgeReel(dir) {
|
||||
reelWait = 4000;
|
||||
reelTrack.scrollBy({ left: dir * ((reelTrack.firstElementChild?.offsetWidth ?? 300) + 10), behavior: 'smooth' });
|
||||
}
|
||||
for (const b of $('credits-reel').querySelectorAll('.arrow')) b.addEventListener('click', () => nudgeReel(b.classList.contains('next') ? 1 : -1));
|
||||
// Col dito si trascina da sé (touch-action: pan-x); col mouse lo facciamo noi. Un tocco fermo su una foto la mostra
|
||||
// grande: si guarda al rilascio, perché con la cattura del puntatore il clic non arriva più all'immagine.
|
||||
reelTrack.addEventListener('pointerdown', e => {
|
||||
reelDragged = false;
|
||||
reelTap = reelPics.get(e.target) ?? null;
|
||||
if (e.pointerType !== 'mouse') { reelWait = 4000; return; }
|
||||
reelDrag = { x: e.clientX, left: reelTrack.scrollLeft, id: e.pointerId };
|
||||
reelTrack.setPointerCapture(e.pointerId);
|
||||
});
|
||||
reelTrack.addEventListener('pointermove', e => {
|
||||
if (!reelDrag) return;
|
||||
const dx = e.clientX - reelDrag.x;
|
||||
if (Math.abs(dx) > 6) reelDragged = true;
|
||||
reelTrack.scrollLeft = reelDrag.left - dx;
|
||||
});
|
||||
const endReelDrag = () => { if (reelDrag && reelDragged) reelWait = 4000; reelDrag = null; };
|
||||
reelTrack.addEventListener('pointerup', () => { if (!reelDragged && reelTap) showFace(reelTap); reelTap = null; endReelDrag(); });
|
||||
reelTrack.addEventListener('pointercancel', () => { reelTap = null; endReelDrag(); });
|
||||
reelTrack.addEventListener('wheel', e => { reelWait = 4000; if (e.deltaY && !e.deltaX) { reelTrack.scrollLeft += e.deltaY; e.preventDefault(); } }, { passive: false });
|
||||
// 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; });
|
||||
@@ -170,9 +212,10 @@ function rollCredits() {
|
||||
};
|
||||
rolling = requestAnimationFrame(step);
|
||||
}
|
||||
function showFace({ name, face }) {
|
||||
$('face').querySelector('img').src = versioned(face);
|
||||
$('face').querySelector('img').classList.toggle('palio', face.endsWith('palio.png'));
|
||||
function showFace({ name, src, kind }) {
|
||||
const img = $('face').querySelector('img');
|
||||
img.src = versioned(src);
|
||||
img.className = kind;
|
||||
$('face').querySelector('figcaption').textContent = name;
|
||||
$('face').hidden = false;
|
||||
}
|
||||
@@ -631,7 +674,11 @@ window.addEventListener('keydown', e => {
|
||||
}
|
||||
if (debugKey(e)) return;
|
||||
if (!$('tutorial').hidden) { if (e.key === 'Escape') closeTutorial(); return; }
|
||||
if (!$('credits').hidden) { if (e.key === 'Escape') { if (!$('face').hidden) $('face').hidden = true; else $('credits').hidden = true; } return; }
|
||||
if (!$('credits').hidden) {
|
||||
if (e.key === 'Escape') { if (!$('face').hidden) $('face').hidden = true; else $('credits').hidden = true; }
|
||||
else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') { if ($('face').hidden) nudgeReel(e.key === 'ArrowRight' ? 1 : -1); }
|
||||
return;
|
||||
}
|
||||
if (!$('pause').hidden) { if (e.key === 'Escape') closePause(); return; }
|
||||
if (!$('notebook').hidden) { if (['Escape', 'n', 'N'].includes(e.key)) $('notebook').hidden = true; return; }
|
||||
if (!$('map').hidden) { if (e.key === 'Escape' || e.key === 'p' || e.key === 'P') $('map').hidden = true; return; }
|
||||
|
||||
Reference in New Issue
Block a user