diff --git a/src/main.js b/src/main.js index 2c13020..1cabcb8 100644 --- a/src/main.js +++ b/src/main.js @@ -196,6 +196,7 @@ let inventoryKey = '', hoverItem = null; function updateInventory() { const key = game.state.inventory.join(',') + '|' + command.item; if (key === inventoryKey) return; + const grew = game.state.inventory.length > inventoryKey.split('|')[0].split(',').filter(Boolean).length; inventoryKey = key; const box = $('inventory'); box.replaceChildren(...game.state.inventory.map(id => { @@ -223,6 +224,8 @@ function updateInventory() { b.addEventListener('pointerleave', () => { if (hoverItem === id) hoverItem = null; }); return b; })); + // Un oggetto nuovo finisce in fondo: si scorre fin lì, così si vede subito. + if (grew) box.scrollTop = box.scrollHeight; } // --- Dialoghi: scelte numerate, anche da tastiera. @@ -303,6 +306,7 @@ function frame(now) { const dt = Math.max(0, Math.min(0.1, (now - last) / 1000)); last = now; const screen = !$('title').hidden ? 'title' : !$('splash').hidden ? 'splash' : !$('intro').hidden ? 'intro' : !$('ending').hidden ? 'ending' : 'game'; + document.body.classList.toggle('full', screen === 'title' || screen === 'splash' || screen === 'intro' || !$('credits').hidden); music.duck = voices.playing ? 0.45 : 1; music.update(trackFor(story, { screen, scene: game?.state.scene }), dt); // Un minuto senza toccare niente, a gioco fermo: pausa da sola. diff --git a/style.css b/style.css index e8f671d..45b0119 100644 --- a/style.css +++ b/style.css @@ -47,7 +47,9 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h #verbs button:hover, #inventory button:hover { color: var(--ink); } #verbs button[aria-pressed="true"] { color: var(--bg); background: var(--accent); border-color: var(--accent); } #inventory { display: grid; grid-template-columns: repeat(3, 1fr); grid-auto-rows: minmax(2.4em, auto); gap: 4px; align-content: start; - min-height: 5.4em; padding: 4px; background: #231a29; border: 2px solid #4b3a55; border-radius: 4px; } + min-height: 5.4em; padding: 4px; background: #231a29; border: 2px solid #4b3a55; border-radius: 4px; + /* L'inventario non allunga mai il pannello: è alto quanto i verbi, e se gli oggetti sono tanti si scorre. */ + contain: size; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: #6a557a transparent; } #inventory:empty::before { content: 'Inventario'; grid-column: 1 / -1; align-self: center; text-align: center; color: var(--dim); opacity: .45; font-size: clamp(9.36px, 1.22vw, 13.68px); font-weight: bold; letter-spacing: .08em; } #inventory button { display: flex; align-items: center; gap: .35em; justify-content: center; color: var(--ink); } @@ -64,7 +66,7 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h .overlay[hidden] { display: none; } /* Titolo e introduzione: le illustrazioni a pixel netti, con una velatura per leggere il testo. */ #title { justify-content: flex-start; padding-top: 6%; image-rendering: pixelated; - background: linear-gradient(rgba(20, 12, 30, .55), rgba(20, 12, 30, .05) 45%, rgba(20, 12, 30, .7)), url(assets/scenes/titolo.png) center / cover; } + background: linear-gradient(rgba(20, 12, 30, .55), rgba(20, 12, 30, .05) 45%, rgba(20, 12, 30, .7)), url(assets/scenes/titolo.png) 86% center / cover; } #title .buttons { margin-top: auto; } #title .dedication { margin-bottom: 2%; text-shadow: 2px 2px 0 #000; } #title .subtitle { color: var(--ink); text-shadow: 2px 2px 0 #000; } @@ -72,13 +74,26 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h background: linear-gradient(rgba(20, 12, 30, .6), rgba(20, 12, 30, .1) 55%), url(assets/scenes/panorama-intro.png) center / cover, #2a2030; } #intro-text { min-height: 4.2em; max-width: 30em; font-size: clamp(11.52px, 1.87vw, 21.6px); font-weight: bold; line-height: 1.3; color: var(--ink); text-shadow: 2px 0 #000, -2px 0 #000, 0 2px #000, 0 -2px #000; animation: appear .8s ease-out; } -#intro .hint { margin-top: auto; color: var(--dim); font-size: clamp(8.64px, 1.08vw, 11.52px); text-shadow: 1px 1px 0 #000; } +#intro .hint { margin-top: auto; margin-bottom: 40px; color: var(--dim); font-size: clamp(8.64px, 1.08vw, 11.52px); text-shadow: 1px 1px 0 #000; } #skip-intro { position: absolute; right: 10px; bottom: 10px; font: bold 10.08px 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%); +/* Cartello, titolo, introduzione e crediti a tutto schermo: sul telefono la scena è piccola e non si leggerebbe niente. + Il volume resta raggiungibile sopra di loro. */ +#splash, #title, #intro { position: fixed; inset: 0; z-index: 10; } +#credits { position: fixed; inset: 0; z-index: 12; } +body.full #sound { position: fixed; z-index: 13; top: max(8px, env(safe-area-inset-top)); right: 8px; } +#title h1 { font-size: clamp(28px, 7vw, 84px); } +/* Sotto il controllo del volume, che sta in alto a destra. */ +#title, #intro { padding-top: max(6%, 56px); } +#splash .splash-name { font-size: clamp(30px, 9vw, 88px); } +#splash .splash-art { font-size: clamp(20px, 5vw, 50px); } +#splash .splash-present { font-size: clamp(12px, 2.6vw, 26px); } +#title .subtitle { font-size: clamp(11px, 1.8vw, 20px); } +#title .buttons button { font-size: clamp(12px, 2vw, 20px); } +#splash { z-index: 11; 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(24.48px, 5.76vw, 63.36px); letter-spacing: .14em; text-transform: uppercase; color: var(--ink); @@ -140,6 +155,7 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h @media (max-width: 600px) { #panel { grid-template-columns: 1fr; gap: 6px; padding: 6px; } #verbs button { font-size: 9px; padding: .7em .1em; } + #inventory { height: 7.6em; } } /* Telefono in orizzontale: la scena prende tutta l'altezza, verbi e inventario vanno di lato. */ @@ -148,8 +164,8 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h #stage { flex: none; width: min(calc(100dvh * 16 / 9), 68vw); align-self: flex-start; } #side { flex: 1; min-width: 0; display: flex; flex-direction: column; background: var(--panel); } #sentence { font-size: 10.08px; } - #panel { grid-template-columns: 1fr; gap: 6px; padding: 6px; } - #inventory { grid-template-columns: repeat(2, 1fr); min-height: 0; } + #panel { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr); flex: 1; min-height: 0; gap: 6px; padding: 6px; } + #inventory { grid-template-columns: repeat(2, 1fr); min-height: 0; height: auto; } #verbs button, #inventory button, #choices li { font-size: 10.08px; } /* La colonna di lato è stretta: verbi piccoli che vanno a capo, mai più larghi della colonna (sennò il telefono allarga la pagina e taglia tutto a destra). */ diff --git a/tests/browser.mjs b/tests/browser.mjs index d4b3064..f98987f 100644 --- a/tests/browser.mjs +++ b/tests/browser.mjs @@ -93,6 +93,17 @@ try { // «?» mostra per qualche secondo cosa si può toccare. await page.click('#spots-button'); await page.screenshot({ path: `${OUT}/02c-punti.png` }); + // Inventario pieno (16 oggetti di prova): resta alto quanto i verbi, dentro lo schermo, si scorre e mostra l'ultimo preso. + await page.evaluate(() => { + const items = game.story.items, kinds = ['chiave_oratorio', 'libro', 'squama']; + for (let i = 1; i <= 16; i++) { items['prova' + i] = { name: 'prova ' + i, icon: items[kinds[i % 3]].icon }; game.state.inventory.push('prova' + i); } + }); + await page.waitForTimeout(150); + const inv = await page.evaluate(() => { const e = document.getElementById('inventory'), r = e.getBoundingClientRect(), last = e.lastElementChild.getBoundingClientRect(); + return { inside: r.bottom <= innerHeight, scrolls: e.scrollHeight > e.clientHeight, lastVisible: last.bottom <= r.bottom + 1 && last.top >= r.top - 1 }; }); + assert.deepEqual(inv, { inside: true, scrolls: true, lastVisible: true }, 'inventario pieno'); + await page.screenshot({ path: `${OUT}/02d-inventario-pieno.png` }); + await page.evaluate(() => { game.state.inventory = game.state.inventory.filter(i => !i.startsWith('prova')); for (let i = 1; i <= 16; i++) delete game.story.items['prova' + i]; }); // Pausa: Esc o il pulsante fermano il gioco; Riprendi torna dov'eri; il titolo offre «Continua». await page.keyboard.press('Escape'); await page.waitForSelector('#pause:not([hidden])');