feat(ui): landscape iteration — reorder left panel, enable board zoom
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 45s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m13s

Left panel order is now score / history / status / rack / controls (the history
fills the middle and scrolls). Board zoom is re-enabled in landscape with the
same gestures as portrait, but height-driven: the viewport is the full right
pane, the board fits by height as a centred square when zoomed out, and on
zoom-in it magnifies past the pane and pans within it (focus-centred scroll that
rides the magnify transition), occupying the full width up to the left panel.
Board.svelte gains a landscape prop; portrait stays width-driven and unchanged.
landscape.spec.ts now asserts zoom works (the zoomed board overflows the pane).
This commit is contained in:
Ilia Denisov
2026-06-14 19:39:06 +02:00
parent 9ec72c8377
commit 02ef31c464
4 changed files with 90 additions and 33 deletions
+17 -11
View File
@@ -459,7 +459,7 @@
hoverKey = ck;
if (hoverTimer) clearTimeout(hoverTimer);
hoverTimer =
c && !zoomed && !landscape
c && !zoomed
? setTimeout(() => {
// Still holding the tile over this cell: magnify into it. Only the first
// (zoom-in) hold centres; once zoomed we never move the board on hover.
@@ -561,7 +561,7 @@
if (board[row]?.[col]) return;
if (pendingMap.has(`${row},${col}`)) return;
focus = { row, col };
if (isCoarse() && !zoomed && !landscape) zoomed = true;
if (isCoarse() && !zoomed) zoomed = true;
if (placement.rack[index] === BLANK) {
blankPrompt = { rackIndex: index, row, col };
return;
@@ -675,7 +675,7 @@
// unchanged zoom state would otherwise leave it parked where the player was looking.
recenter++;
}
if (isCoarse() && !landscape) zoomed = true;
if (isCoarse()) zoomed = true;
view = { ...view, hintsRemaining: h.hintsRemaining };
recompute();
}
@@ -901,10 +901,10 @@
{#if landscape}
<div class="game-land">
<div class="leftpane">
{@render rackRow()}
{@render statusBlock()}
{@render scoreboardBlock()}
<div class="history land">{@render historyBody()}</div>
{@render statusBlock()}
{@render rackRow()}
<TabBar>{@render controlButtons()}</TabBar>
</div>
<div class="rightpane">{@render boardBlock()}</div>
@@ -1013,6 +1013,7 @@
flash={recentFlash}
centre={ctr}
{zoomed}
{landscape}
{variant}
labelMode={app.boardLabels}
lines={app.boardLines}
@@ -1021,7 +1022,7 @@
{recenter}
{dropTarget}
oncell={onCell}
ontogglezoom={(r, c) => { focus = { row: r, col: c }; if (!gameOver && !landscape) zoomed = !zoomed; }}
ontogglezoom={(r, c) => { focus = { row: r, col: c }; if (!gameOver) zoomed = !zoomed; }}
onrecall={onRecall}
onpenddown={onBoardDown}
/>
@@ -1502,17 +1503,21 @@
min-height: 0;
}
.rightpane {
/* A size container so the board can be the largest square that fits — by height, shrinking
by width when the column is narrow (the board has the lowest priority, per the design). */
/* A size container spanning the whole right area: the board (Board.svelte's .viewport.land)
fills it and fits the square board by HEIGHT via min(100cqw,100cqh); on zoom-in the board
grows past the pane and pans within it, occupying the full width up to the left panel. The
board has the lowest priority, so the pane shrinks the board (by width) when narrow. */
container-type: size;
display: grid;
place-items: center;
min-width: 0;
min-height: 0;
}
.rightpane .boardwrap {
padding: 0;
width: min(100cqw, 100cqh);
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
}
/* The history docked open in the left panel: a normal flex child that fills the remaining
height and scrolls, its sticky header staying pinned — no slide-down drawer, no transform. */
@@ -1522,7 +1527,8 @@
flex: 1 1 auto;
min-height: 0;
box-shadow: none;
border-bottom: none;
/* Sits between the score plaques (above) and the status/rack/controls (below): rule it off on
both edges (the base rule already draws the bottom border). */
border-top: 1px solid var(--border);
}
/* The score plaques do not toggle the history in landscape (it is always open). */