Make it full-screen: no scrolling, ever
Lock the viewport (100dvh, overflow:hidden, grid-centered) and scale the active card to fit via a --fit custom property set by fitActiveScreen(). Every screen — setup, game, winner — now always fits the browser at any window size; the card shrinks only when it would otherwise overflow. Refit on screen change, stage switch, resize, orientation, and font load. Verified no vertical/horizontal scroll on all three screens at short-laptop (1366x640) and mobile-portrait (390x844). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bba0a06a71
commit
aa2c3b989d
2 changed files with 42 additions and 7 deletions
29
script.js
29
script.js
|
|
@ -230,6 +230,7 @@ function applyStage(stage, { persist = true } = {}) {
|
|||
opt.setAttribute('aria-checked', opt.dataset.stage === stage ? 'true' : 'false'));
|
||||
startLabel.textContent = STAGES[stage].startLabel;
|
||||
if (persist) saveState();
|
||||
scheduleFit(); // font swap (e.g. Metal Mania) changes height → refit
|
||||
}
|
||||
|
||||
/* ---------- Setup interactions ---------- */
|
||||
|
|
@ -424,9 +425,34 @@ function applyPlayerCamps() {
|
|||
function showScreen(screen) {
|
||||
[nameScreen, gameScreen, winnerScreen].forEach((s) => s.classList.remove('active'));
|
||||
screen.classList.add('active');
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
scheduleFit();
|
||||
}
|
||||
|
||||
/* ---------- Fit-to-viewport ----------
|
||||
Scale the active card (via the --fit custom prop) so the whole screen
|
||||
always fits — it's tic-tac-toe, no scrolling, ever. */
|
||||
let fitRAF = 0;
|
||||
function scheduleFit() {
|
||||
if (fitRAF) cancelAnimationFrame(fitRAF);
|
||||
fitRAF = requestAnimationFrame(() => { fitRAF = 0; fitActiveScreen(); });
|
||||
}
|
||||
function fitActiveScreen() {
|
||||
const screen = document.querySelector('.screen.active');
|
||||
if (!screen) return;
|
||||
const card = screen.querySelector('.card');
|
||||
if (!card) return;
|
||||
// offsetWidth/Height ignore transforms, so we always read the natural size.
|
||||
const w = card.offsetWidth;
|
||||
const h = card.offsetHeight;
|
||||
if (!w || !h) return;
|
||||
const margin = 16; // small breathing room around the card
|
||||
const scale = Math.min(1, (window.innerWidth - margin) / w, (window.innerHeight - margin) / h);
|
||||
card.style.setProperty('--fit', String(scale));
|
||||
}
|
||||
window.addEventListener('resize', scheduleFit);
|
||||
window.addEventListener('orientationchange', scheduleFit);
|
||||
if (document.fonts && document.fonts.ready) document.fonts.ready.then(scheduleFit);
|
||||
|
||||
/* ---------- Lifecycle ---------- */
|
||||
function startGame() {
|
||||
player1Name = player1Input.value.trim() || 'Player 1';
|
||||
|
|
@ -520,5 +546,6 @@ function init() {
|
|||
applyStage(currentStage, { persist: false });
|
||||
applyPlayerCamps();
|
||||
updatePlayerHighlight();
|
||||
scheduleFit();
|
||||
}
|
||||
init();
|
||||
|
|
|
|||
20
style.css
20
style.css
|
|
@ -110,18 +110,22 @@
|
|||
.camp-sparkle { --pc: var(--sparkle-color); --pg: var(--sparkle-glow); --pt: var(--sparkle-tint); }
|
||||
.camp-shred { --pc: var(--shred-color); --pg: var(--shred-glow); --pt: var(--shred-tint); }
|
||||
|
||||
/* ---------- Base layout (scroll-safe centering) ---------- */
|
||||
/* ---------- Base layout (full-screen, never scroll) ----------
|
||||
The viewport is locked to the screen; the active card is scaled by JS
|
||||
(--fit) to always fit. It's tic-tac-toe — everything stays in view. */
|
||||
html { height: 100%; }
|
||||
body {
|
||||
font-family: var(--body-font);
|
||||
background: var(--bg);
|
||||
background-size: var(--bg-size);
|
||||
animation: gradientShift 15s ease infinite;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
height: 100dvh;
|
||||
min-height: 100dvh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
padding: 28px 16px;
|
||||
padding: 8px;
|
||||
color: var(--text);
|
||||
transition: color 0.4s ease;
|
||||
}
|
||||
|
|
@ -230,6 +234,10 @@ body {
|
|||
backdrop-filter: blur(10px);
|
||||
border: 3px solid var(--surface-border);
|
||||
transition: background 0.4s ease, border-color 0.4s ease, box-shadow 0.4s ease;
|
||||
/* Scaled to fit the viewport by fitActiveScreen() in script.js */
|
||||
transform: scale(var(--fit, 1));
|
||||
transform-origin: center center;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.name-card { padding: 30px 30px 34px; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue