feat(game): scrollable viewport with camera-follow for large grids

Fix cellSize at 50 and wrap board-container with overflow:auto + smooth
scrollTo effect centered on player. Unlocks grid sizes beyond 9x9 without
shrinking cells below readable size.
This commit is contained in:
tiennm99 committed 2026-04-21 21:20:04 +07:00
1 parent 6e2114162f
commit 0bf72c50d8
1 file changed
+24 -2
+24 -2
View File
@@ -56,7 +56,9 @@
// Derived rendering data (depend on renderVersion to pick up class mutations)
let cells = $derived((renderVersion, grid ? grid.getAllCells() : []));
let turns = $derived((renderVersion, turnManager.turnCount));
let cellSize = $derived(grid ? Math.min(50, Math.floor(500 / grid.rows)) : 50);
// Fixed cellSize. Grids larger than the viewport scroll instead of shrinking cells,
// preserving readability on 11x11+ arenas. Viewport handles scroll-follow (see boardEl $effect below).
let cellSize = $derived(50);
let playerRow = $derived((renderVersion, player ? player.row : 0));
let playerCol = $derived((renderVersion, player ? player.col : 0));
let guardSnapshots = $derived((renderVersion, guards.map(g => ({
@@ -71,6 +73,21 @@
// Detection feedback timer — tracked so we can clear on unmount / re-init
let detectionTimeout = null;
// Viewport scroll-follow — scrolls the board container so the player stays in view
// on grids larger than the viewport. Binds to .board-container element.
let boardEl = $state(null);
$effect(() => {
if (!boardEl || !grid) return;
// Read player position to establish reactivity
const pr = playerRow;
const pc = playerCol;
const target = {
left: pc * cellSize + cellSize / 2 - boardEl.clientWidth / 2,
top: pr * cellSize + cellSize / 2 - boardEl.clientHeight / 2,
};
boardEl.scrollTo({ left: Math.max(0, target.left), top: Math.max(0, target.top), behavior: 'smooth' });
});
// Initialize level
function initLevel() {
const state = loadLevel(currentLevel);
@@ -300,7 +317,7 @@
{#if grid && player}
<div class="board-wrapper">
<div class="board-container" style="position: relative;">
<div class="board-container" bind:this={boardEl} style="position: relative;">
<GameBoard
{cells}
rows={grid.rows}
@@ -390,6 +407,11 @@
padding: 8px;
border-radius: 6px;
box-shadow: 0 0 24px rgba(0, 0, 0, 0.6);
/* Cap viewport; grids larger than this scroll with camera-follow */
max-width: min(720px, 85vw, 85vh);
max-height: min(720px, 85vw, 85vh);
overflow: auto;
scroll-behavior: smooth;
}
.final-message {
position: absolute;