Manzolo
+(Poca) Art
+Presents
+San Piero a Sieve · Palio della Fortezza · 2007
diff --git a/src/main.js b/src/main.js index 3186a7b..8a150c4 100644 --- a/src/main.js +++ b/src/main.js @@ -187,9 +187,11 @@ function updateUi() { let last = performance.now(); function frame(now) { - const dt = Math.min(0.1, (now - last) / 1000); + // Il primo timestamp di requestAnimationFrame può precedere `last`: un dt negativo darebbe alla musica un volume + // sotto zero, il browser lancerebbe un errore e il ciclo dei fotogrammi si fermerebbe (gioco congelato). + const dt = Math.max(0, Math.min(0.1, (now - last) / 1000)); last = now; - const screen = !$('title').hidden ? 'title' : !$('intro').hidden ? 'intro' : !$('ending').hidden ? 'ending' : 'game'; + const screen = !$('title').hidden || !$('splash').hidden ? 'title' : !$('intro').hidden ? 'intro' : !$('ending').hidden ? 'ending' : 'game'; music.update(trackFor(story, { screen, scene: game?.state.scene }), dt); if (game) { if (running) game.update(dt); @@ -204,8 +206,22 @@ $('continue').addEventListener('click', () => start(readSave(storage))); $('restart').addEventListener('click', () => { if (storage) clearSave(storage); start(null); }); // Dietro il titolo si vede già la piazza. +// --- Cartello d'apertura «Manzolo (Poca) Art Presents»: 5 secondi (un clic o un tasto lo salta), poi il titolo. +function showSplash() { + let done = false; + const end = () => { + if (done) return; + done = true; + $('splash').hidden = true; + showTitle(); + }; + setTimeout(end, 5000); + $('splash').addEventListener('click', end); + addEventListener('keydown', end, { once: true }); +} + game = new Game(story); command = new Command(game); -showTitle(); +showSplash(); requestAnimationFrame(frame); loadAssets(story).then(ok => { if (ok.includes(false)) console.warn('Alcune immagini non sono state caricate.'); }); diff --git a/style.css b/style.css index 246d710..627a216 100644 --- a/style.css +++ b/style.css @@ -68,6 +68,21 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h #skip-intro { position: absolute; right: 10px; bottom: 10px; font: bold 14px var(--font); padding: .3em .9em; border-radius: 6px; border: 0; color: var(--bg); background: var(--dim); cursor: pointer; } @keyframes appear { from { opacity: 0; transform: translateY(6px); } } +/* Cartello d'apertura «Manzolo (Poca) Art Presents»: le parole entrano una alla volta, il «(Poca)» cade dall'alto + come un'aggiunta a mano; dopo 4,3 s sfuma (il JavaScript lo toglie a 5 s). */ +#splash { z-index: 3; gap: .15em; cursor: pointer; background: radial-gradient(circle at 50% 45%, #2a2030, #0d0910 70%); + animation: splash-out .7s ease-in 4.3s forwards; } +#splash p { margin: 0; max-width: none; font-weight: bold; text-shadow: 3px 3px 0 #000; opacity: 0; } +#splash .splash-name { font-size: clamp(34px, 8vw, 88px); letter-spacing: .14em; text-transform: uppercase; color: var(--ink); + animation: splash-in .9s ease-out .2s forwards; } +#splash .splash-art { font-size: clamp(22px, 4.5vw, 50px); color: var(--accent); animation: splash-in .9s ease-out 1s forwards; } +#splash .splash-art span { display: inline-block; font-size: .7em; font-style: italic; color: #7fe08a; opacity: 0; + animation: splash-poca .45s cubic-bezier(.3, 1.6, .6, 1) 1.9s forwards; } +#splash .splash-present { font-size: clamp(14px, 2.4vw, 26px); letter-spacing: .5em; text-transform: uppercase; color: var(--dim); + animation: splash-in .9s ease-out 2.6s forwards; } +@keyframes splash-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } } +@keyframes splash-poca { from { opacity: 0; transform: translateY(-1.5em) rotate(-8deg) scale(1.8); } to { opacity: 1; transform: rotate(-8deg); } } +@keyframes splash-out { to { opacity: 0; } } .overlay h1 { margin: 0; font-size: clamp(30px, 7vw, 76px); line-height: 1; letter-spacing: .02em; color: var(--ink); text-shadow: 3px 3px 0 #000; } .overlay h1 span { color: #7fe08a; } .overlay h2 { margin: 0; font-size: clamp(24px, 5vw, 52px); color: var(--accent); } diff --git a/tests/browser.mjs b/tests/browser.mjs index 8d60b55..ea48b80 100644 --- a/tests/browser.mjs +++ b/tests/browser.mjs @@ -44,7 +44,13 @@ const hotspotCenter = (page, id) => page.evaluate(id => { const h = game.hotspot try { const page = await open({ width: 1280, height: 860 }); - await page.waitForSelector('#title:not([hidden])'); + // Il cartello d'apertura, poi da solo il titolo. + assert.match(await page.textContent('#splash'), /Manzolo\s*\(Poca\)\s*Art\s*Presents/); + assert.ok(await page.isHidden('#title'), 'il titolo aspetta la fine del cartello'); + await page.waitForTimeout(2800); + await page.screenshot({ path: `${OUT}/00-cartello.png` }); + await page.waitForSelector('#title:not([hidden])', { timeout: 8000 }); + assert.ok(await page.isHidden('#splash'), 'il cartello sparisce'); await page.screenshot({ path: `${OUT}/01-titolo.png` }); await page.click('#new-game'); // L'introduzione: una riga per clic, poi comincia la partita. @@ -108,6 +114,8 @@ try { assert.equal(await page.evaluate(() => game.state.scene), 'trebbio'); const phone = await open({ width: 390, height: 844 }); + await phone.click('#splash'); // un clic salta il cartello + assert.ok(await phone.isVisible('#title'), 'il clic sul cartello porta al titolo'); await phone.click('#new-game'); await phone.click('#skip-intro'); await settle(phone);