La scena prende la finestra (Andrea: «come mai non sfruttiamo tutto lo schermo?»): la riserva sotto non è più 250 px fissi ma l'altezza vera del pannello, in unità --u legate alla larghezza del gioco (cqw, #app è un container); frase, verbi, inventario, risposte, HUD e fumetti scalano col gioco e non con la finestra, i verbi non si tagliano più; pannello alto sempre uguale (quattro risposte piene, da cinque il carattere cala) così la scena non si muove nei dialoghi; frase su una riga coi puntini; «Schermo intero» nella Pausa; la Pausa parte dall'alto se non ci sta. A 1040×590 la scena da 604 a 842 px. Prove nel browser a 1280×860 e 1040×590, con due dialoghi (Andrea)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
This commit is contained in:
@@ -302,6 +302,14 @@ versione nuova.
|
||||
- **Test**: `make test` (29 test, compresa la soluzione completa da `src/story/solution.js`) e `make browser`
|
||||
(partita completa con clic veri in Chromium, ripresa del salvataggio, telefono).
|
||||
|
||||
- **Impaginazione (1 novembre, Andrea: «come mai non sfruttiamo tutto lo schermo?»):** la scena prende la finestra. La
|
||||
riserva sotto non è più 250 px fissi ma l'altezza vera del pannello, 13,3 unità `--u` (un centesimo della larghezza
|
||||
del gioco, `cqw`, fra 8,75 e 12,8 px: `#app` è un container). Frase, verbi, inventario, risposte, HUD e fumetti scalano
|
||||
col gioco, non con la finestra: i verbi non si tagliano più nelle finestre piccole. Il pannello è alto sempre uguale
|
||||
(ci stanno quattro risposte; da cinque il carattere cala, da sette ancora) così la scena non si muove nei dialoghi;
|
||||
la frase è una riga sola coi puntini. «Schermo intero» nella Pausa (dove il browser lo permette). A 1040×590 la scena
|
||||
passa da 604 a 842 px di larghezza, a 1366×650 da 711 a 935; sui telefoni niente cambia. Prova in `browser.mjs`.
|
||||
|
||||
## Materiale ricevuto da Andrea (in `assets/raw/`, escluso da git)
|
||||
|
||||
| File | Cosa | Uso previsto |
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
<div class="buttons pause-buttons">
|
||||
<button id="resume">Riprendi</button>
|
||||
<button id="pause-map" class="secondary">Mappa</button>
|
||||
<button id="fullscreen" class="secondary" title="Il gioco su tutto lo schermo; Esc per uscire">Schermo intero</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">Ringraziamenti</button>
|
||||
<button id="to-title" class="secondary">Torna al titolo</button>
|
||||
|
||||
+12
@@ -399,6 +399,18 @@ addEventListener('pagehide', () => leave(true));
|
||||
addEventListener('pageshow', () => leave(document.hidden));
|
||||
$('resume').addEventListener('click', closePause);
|
||||
$('pause-map').addEventListener('click', () => { closePause(); openMap(); });
|
||||
// Schermo intero dalla Pausa, dove il browser lo permette (su iPhone no: il pulsante sparisce); Esc o il pulsante per uscire.
|
||||
{
|
||||
const full = $('fullscreen');
|
||||
if (!document.fullscreenEnabled) full.hidden = true;
|
||||
else {
|
||||
full.addEventListener('click', () => {
|
||||
if (document.fullscreenElement) document.exitFullscreen();
|
||||
else document.documentElement.requestFullscreen({ navigationUI: 'hide' }).catch(() => {});
|
||||
});
|
||||
document.addEventListener('fullscreenchange', () => { full.textContent = document.fullscreenElement ? 'Esci da schermo intero' : 'Schermo intero'; });
|
||||
}
|
||||
}
|
||||
$('to-title').addEventListener('click', () => {
|
||||
$('pause').hidden = true;
|
||||
music.hold = false;
|
||||
|
||||
@@ -20,7 +20,14 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
||||
button, input { font-family: var(--font); }
|
||||
body { line-height: 1.5; }
|
||||
body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-highlight-color: transparent; }
|
||||
#app { width: min(100vw, 1280px, calc((100dvh - 250px) * 16 / 9)); min-width: min(100vw, 320px); margin-block: auto; display: flex; flex-direction: column; padding: env(safe-area-inset-top) 0 env(safe-area-inset-bottom); }
|
||||
/* Il gioco è largo quanto può: tutta la finestra fino a 1280px, oppure quanto ci sta in altezza con il pannello sotto la
|
||||
scena 16:9. Il pannello (frase, poi verbi e inventario o le risposte di un dialogo) è alto --side-h unità --u, dove --u
|
||||
è un centesimo della larghezza del gioco (1cqw) fra 8,75px e 12,8px: tutto il pannello cresce e cala col gioco, non con
|
||||
la finestra, e la riserva in altezza è esatta (prima erano 250px fissi, un centinaio di troppo, che in una finestra
|
||||
1040×590 costavano un terzo della scena). I due calc sono i due regimi di --u: proporzionale, e al minimo. */
|
||||
#app { --u: clamp(8.75px, 1cqw, 12.8px); --side-h: 13.3; container-type: inline-size;
|
||||
width: min(100vw, 1280px, calc(100dvh / (9 / 16 + var(--side-h) / 100)), calc((100dvh - var(--side-h) * 8.75px) * 16 / 9));
|
||||
min-width: min(100vw, 320px); margin-block: auto; display: flex; flex-direction: column; padding: env(safe-area-inset-top) 0 env(safe-area-inset-bottom); }
|
||||
|
||||
#stage { position: relative; width: 100%; aspect-ratio: 16 / 9; background: #000; user-select: none; touch-action: manipulation; }
|
||||
#scene { width: 100%; height: 100%; display: block; image-rendering: pixelated; cursor: crosshair; }
|
||||
@@ -80,7 +87,7 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h
|
||||
|
||||
/* max-content: vicino al bordo destro il fumetto non si stringe a colonna (lo spazio dopo `left` non conta). */
|
||||
#speech { position: absolute; left: 0; top: 0; width: max-content; max-width: 46%; transform: translate(-50%, -100%); pointer-events: none;
|
||||
font-size: clamp(10.08px, 1.66vw, 18.72px); font-weight: bold; line-height: 1.2; text-align: center;
|
||||
font-size: calc(1.4625 * var(--u)); font-weight: bold; line-height: 1.2; text-align: center;
|
||||
text-shadow: 2px 0 #000, -2px 0 #000, 0 2px #000, 0 -2px #000, 2px 2px #000, -2px -2px #000; }
|
||||
#speech:empty { display: none; }
|
||||
#speech { z-index: 3; }
|
||||
@@ -89,7 +96,7 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h
|
||||
#muro { position: absolute; left: 0; top: 0; width: 1067px; height: 600px; border: 0; z-index: 1; background: #f7e1c0;
|
||||
transform: scale(var(--muro-scale, 0.6)); transform-origin: 0 0; }
|
||||
/* Il primo piano del libro si chiude da qui (o con Esc). */
|
||||
#closeup-close { position: absolute; right: 10px; bottom: 10px; z-index: 2; font: bold clamp(10px, 1.5vw, 15px) var(--font);
|
||||
#closeup-close { position: absolute; right: 10px; bottom: 10px; z-index: 2; font: bold calc(1.17 * var(--u)) var(--font);
|
||||
padding: .45em 1.2em; border-radius: 6px; border: 0; cursor: pointer; color: var(--bg); background: var(--accent); }
|
||||
/* Al biliardo audio, pausa e uscita hanno spazio proprio: nessun comando copre biglie o punteggio. */
|
||||
#stage.biliardo { padding-top: 44px; aspect-ratio: auto; background: #17110c; }
|
||||
@@ -104,24 +111,27 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h
|
||||
background: rgba(27, 20, 32, .62); border-radius: 18px; }
|
||||
#mute { width: 30px; height: 30px; padding: 0; font: bold 12.96px var(--font); color: var(--accent); background: none; border: 0; cursor: pointer; }
|
||||
#mute[aria-pressed="true"] { color: var(--dim); text-decoration: line-through; }
|
||||
#volume, #voice-volume { width: clamp(50px, 9vw, 100px); accent-color: var(--accent); cursor: pointer; }
|
||||
#volume, #voice-volume { width: calc(7.8 * var(--u)); accent-color: var(--accent); cursor: pointer; }
|
||||
.sound-label { margin-left: 4px; font-size: 9px; color: var(--accent); }
|
||||
|
||||
#sentence { min-height: 1.8em; padding: .25em .6em; text-align: center; color: var(--blue); font-size: clamp(10.8px, 1.44vw, 15.84px); font-weight: bold; background: var(--panel); }
|
||||
/* La frase: una riga sola, alta sempre uguale (2,5 --u); una frase lunghissima finisce coi puntini invece di spostare la scena. */
|
||||
#sentence { height: calc(2.5 * var(--u)); line-height: 1.5; padding: .25em .6em; text-align: center; color: var(--blue); font-size: calc(1.2375 * var(--u)); font-weight: bold; background: var(--panel);
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
|
||||
#panel { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 8px; padding: 8px; background: var(--panel); }
|
||||
/* Verbi e inventario: alti sempre uguale (10,8 --u, dove stanno anche quattro risposte di dialogo: vedi #choices). */
|
||||
#panel { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: calc(.625 * var(--u)); padding: calc(.625 * var(--u)); height: calc(10.8 * var(--u)); background: var(--panel); }
|
||||
/* Otto verbi su due righe piene, quattro per riga. */
|
||||
#verbs { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px; align-content: start; }
|
||||
#verbs { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: calc(.3125 * var(--u)); align-content: stretch; }
|
||||
#verbs button, #inventory button { font: inherit; color: var(--dim); background: #3a2d42; border: 2px solid #4b3a55; border-radius: 4px;
|
||||
padding: .35em .2em; font-size: clamp(9.36px, 1.22vw, 13.68px); font-weight: bold; cursor: pointer; }
|
||||
padding: .35em .2em; font-size: calc(1.069 * var(--u)); font-weight: bold; cursor: pointer; }
|
||||
#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, minmax(0, 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;
|
||||
#inventory { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-rows: minmax(2.4em, auto); gap: calc(.3125 * var(--u)); align-content: start;
|
||||
min-height: 0; padding: calc(.3125 * var(--u)); 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; }
|
||||
font-size: calc(1.069 * var(--u)); font-weight: bold; letter-spacing: .08em; }
|
||||
#inventory button { display: flex; align-items: center; gap: .35em; justify-content: center; color: var(--ink); }
|
||||
#inventory button { min-width: 0; overflow: hidden; }
|
||||
#inventory button span.long { min-width: 0; font-size: .72em; line-height: 1.15; text-align: left; }
|
||||
@@ -130,8 +140,14 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h
|
||||
#inventory img { width: 1.9em; height: 1.9em; flex: none; image-rendering: pixelated; }
|
||||
|
||||
/* Il carattere sta sull'elenco, non sulle righe: il rientro in em segue il carattere. «1. » nel carattere pixel è largo tre caratteri e mezzo. */
|
||||
#choices { margin: 0; padding: 10px 14px 12px 3.6em; background: var(--panel); min-height: 6.5em; font-size: clamp(10.8px, 1.44vw, 15.84px); }
|
||||
#choices li { padding: .25em 0; font-weight: bold; color: var(--dim); cursor: pointer; }
|
||||
/* Le risposte: alte quanto il pannello dei verbi, così la scena non si muove quando parte un dialogo. Quattro risposte ci
|
||||
stanno al carattere pieno; da cinque in su il carattere cala (e se mai non bastasse, si scorre). */
|
||||
#choices { margin: 0; padding: calc(.78 * var(--u)) calc(1.1 * var(--u)) calc(.94 * var(--u)) 3.6em; background: var(--panel); height: calc(10.8 * var(--u));
|
||||
overflow-y: auto; scrollbar-width: thin; font-size: calc(1.2375 * var(--u)); line-height: 1.4; }
|
||||
#choices li { padding: .2em 0; font-weight: bold; color: var(--dim); cursor: pointer; }
|
||||
#choices:has(li:nth-child(5)) { font-size: calc(.96 * var(--u)); line-height: 1.25; }
|
||||
#choices:has(li:nth-child(5)) li { padding: .15em 0; }
|
||||
#choices:has(li:nth-child(7)) { font-size: calc(.72 * var(--u)); }
|
||||
#choices li:hover, #choices li:focus { color: var(--accent); outline: none; }
|
||||
|
||||
.overlay { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .6em;
|
||||
@@ -223,10 +239,11 @@ body.full #sound { position: fixed; z-index: 13; top: max(8px, env(safe-area-ins
|
||||
/* Mappa: la pianta del paese a gradini, i posti visti come cartellini, le strade come linee. */
|
||||
/* In alto a sinistra: Pausa e Mappa. */
|
||||
#corner { position: absolute; top: 6px; left: 6px; z-index: 2; display: flex; gap: 6px; }
|
||||
#map-button, #pause-button, #notebook-button, #spots-button, #back-button { font: bold clamp(8.64px, 1.15vw, 11.52px) var(--font); padding: .35em .8em;
|
||||
#map-button, #pause-button, #notebook-button, #spots-button, #back-button { font: bold calc(.9 * var(--u)) var(--font); padding: .35em .8em;
|
||||
color: var(--accent); background: rgba(27, 20, 32, .62); border: 0; border-radius: 18px; cursor: pointer; }
|
||||
#back-button { color: #1b1420; background: #9fd0ff; }
|
||||
#pause { z-index: 3; background: rgba(15, 10, 20, .9); }
|
||||
/* Con molti pulsanti la Pausa può essere più alta della scena: «safe» la fa partire dall'alto e scorrere, invece di tagliare il titolo. */
|
||||
#pause { z-index: 3; background: rgba(15, 10, 20, .9); justify-content: safe center; }
|
||||
/* Il player dei video «Come si gioca»: sopra a tutto, il video grande quanto può. */
|
||||
#tutorial { position: fixed; inset: 0; z-index: 20; background: rgba(10, 8, 14, .97); padding: 8px; }
|
||||
#tutorial video { max-width: min(100%, 1100px); max-height: calc(100dvh - 9em); background: #000; }
|
||||
@@ -323,6 +340,9 @@ body.full #sound { position: fixed; z-index: 13; top: max(8px, env(safe-area-ins
|
||||
#pause { position: fixed; inset: 0; z-index: 10; overflow-y: auto; background: rgba(15, 10, 20, .97); } }
|
||||
/* Schermi stretti: il carattere a pixel è largo, i verbi vanno sopra l'inventario e su due righe da quattro. */
|
||||
@media (max-width: 600px) {
|
||||
#sentence { height: auto; min-height: 1.8em; white-space: normal; }
|
||||
#panel, #choices { height: auto; }
|
||||
#choices { min-height: 6.5em; }
|
||||
#panel { grid-template-columns: 1fr; gap: 6px; padding: 6px; }
|
||||
#verbs button { font-size: 9px; padding: .7em .1em; }
|
||||
#inventory { height: 7.6em; }
|
||||
@@ -341,7 +361,8 @@ body.full #sound { position: fixed; z-index: 13; top: max(8px, env(safe-area-ins
|
||||
.card-hand-many button, .card-buttons button { flex-basis: 40%; overflow-wrap: anywhere; }
|
||||
.card-hand { flex-direction: column; gap: 4px; }
|
||||
#side { flex: 1; min-width: 0; display: flex; flex-direction: column; background: var(--panel); }
|
||||
#sentence { font-size: 10.08px; }
|
||||
#sentence { height: auto; min-height: 1.8em; white-space: normal; font-size: 10.08px; }
|
||||
#panel, #choices { height: auto; }
|
||||
/* Il fumetto: più largo e più piccolo, sennò una battuta lunga diventa una colonna alta quanto la scena. */
|
||||
#speech { max-width: 72%; font-size: 11px; }
|
||||
#panel { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr); flex: 1; min-height: 0; gap: 6px; padding: 6px; }
|
||||
@@ -358,6 +379,7 @@ body.full #sound { position: fixed; z-index: 13; top: max(8px, env(safe-area-ins
|
||||
.buttons { gap: 6px; margin-top: .2em; }
|
||||
.buttons button { font-size: 10px; padding: .45em 1em; }
|
||||
.pause-buttons { min-width: 12em; }
|
||||
#pause .hint { display: none; }
|
||||
/* Mappa: schede più piccole, ne stanno cinque o sei per riga. */
|
||||
#map-board button { width: 68px; }
|
||||
#map-board button img { width: 60px; }
|
||||
|
||||
+32
-1
@@ -248,9 +248,33 @@ async function minigiochi() {
|
||||
}
|
||||
|
||||
async function telefoni() {
|
||||
// La finestra di Andrea (1040×590: Firefox con le sue barre su un portatile): la scena era larga 604 px con 100 px
|
||||
// vuoti sotto, ora prende l'altezza; i verbi stanno nei pulsanti; un dialogo a quattro risposte e uno a cinque
|
||||
// non muovono la scena e stanno nel pannello.
|
||||
{
|
||||
const win = await open({ width: 1040, height: 590 });
|
||||
await win.click('#splash'); await win.click('#new-game'); await win.click('#rione [data-rione="campomigliaio"]'); await win.click('#skip-intro');
|
||||
await settle(win);
|
||||
const box = async () => { const r = await win.locator('#stage').boundingBox(); return [r.x, r.y, r.width, r.height].map(Math.round); };
|
||||
const b0 = await box();
|
||||
assert.ok(b0[2] >= 820 && b0[1] >= 0, `a 1040×590 la scena è larga ${b0[2]} px e comincia a ${b0[1]}`);
|
||||
assert.ok(await win.evaluate(() => document.documentElement.scrollHeight <= innerHeight), 'la pagina scorre a 1040×590');
|
||||
assert.ok(await win.evaluate(() => [...document.querySelectorAll('#verbs button')].every(b => b.scrollWidth <= b.clientWidth + 1 && b.scrollHeight <= b.clientHeight + 1)), 'verbi tagliati a 1040×590');
|
||||
for (const id of ['giocatore_carte', 'arcu']) {
|
||||
await win.evaluate(id => { game.dialogue = { id, node: 'start' }; }, id);
|
||||
await win.waitForFunction(() => !document.getElementById('choices').hidden);
|
||||
const n = await win.evaluate(() => game.options().length);
|
||||
assert.deepEqual(await box(), b0, `la scena si muove col dialogo ${id}`);
|
||||
assert.ok(await win.evaluate(() => { const c = document.getElementById('choices'); return c.scrollHeight <= c.clientHeight + 1; }), `le ${n} risposte di ${id} non stanno nel pannello`);
|
||||
if (id === 'arcu') await win.screenshot({ path: `${OUT}/11-finestra-1040-dialogo.png` });
|
||||
await win.evaluate(() => { game.dialogue = null; });
|
||||
}
|
||||
await win.screenshot({ path: `${OUT}/11-finestra-1040.png` });
|
||||
await win.close();
|
||||
}
|
||||
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');
|
||||
assert.ok(await phone.waitForSelector('#title:not([hidden])', { timeout: 5000 }).then(() => true, () => false), 'il clic sul cartello porta al titolo');
|
||||
await phone.click('#new-game');
|
||||
await phone.click('#rione [data-rione="campomigliaio"]');
|
||||
await phone.click('#skip-intro');
|
||||
@@ -476,10 +500,17 @@ try {
|
||||
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]; });
|
||||
// Il gioco prende la finestra: a 1280×860 la scena non si ferma più ai 1084 px di una riserva fissa di 250 px sotto,
|
||||
// la pagina non scorre e i verbi stanno interi nei pulsanti (la finestra di Andrea, 1040×590, è provata più sotto).
|
||||
const stage0 = await page.locator('#stage').boundingBox();
|
||||
assert.ok(stage0.width >= 1200, `a 1280×860 la scena è larga ${stage0.width} px`);
|
||||
assert.ok(await page.evaluate(() => document.documentElement.scrollHeight <= innerHeight), 'la pagina scorre in verticale a 1280×860');
|
||||
assert.ok(await page.evaluate(() => [...document.querySelectorAll('#verbs button')].every(b => b.scrollWidth <= b.clientWidth + 1 && b.scrollHeight <= b.clientHeight + 1)), 'verbi tagliati a 1280×860');
|
||||
// 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])');
|
||||
await page.screenshot({ path: `${OUT}/02b-pausa.png` });
|
||||
assert.equal(await page.evaluate(() => document.getElementById('fullscreen').hidden), await page.evaluate(() => !document.fullscreenEnabled), 'il pulsante Schermo intero c\'è se il browser lo permette');
|
||||
// Salva su file: si scarica la partita; dal titolo «Carica» la riapre anche senza il salvataggio del browser.
|
||||
const [download] = await Promise.all([page.waitForEvent('download'), page.click('#save-file')]);
|
||||
assert.match(download.suggestedFilename(), /^regolo-\d{4}-\d\d-\d\d-\d{4}\.json$/);
|
||||
|
||||
Reference in New Issue
Block a user