Stage 7 UI polish: fix ad-marquee end, drop-time zoom, focus centring, sticky rack selection
- AdBanner: move the side inset onto the scrolling track so the long message scrolls to its very end; pin body text-size-adjust:100% so iOS/Safari stops inflating the long marquee text. - Game: do not zoom on drag start (the player may change their mind) — zoom and centre happen on drop, in attemptPlace; a stray tap on an occupied cell no longer cancels the rack selection (wait for an empty cell). - Board: centre the focus cell after the zoom width transition finishes (was clamping to top-left mid-transition); compute the cell from the rendered scrollWidth.
This commit is contained in:
@@ -123,6 +123,9 @@ body {
|
||||
line-height: 1.4;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
/* Stop iOS/Safari from auto-inflating text (e.g. the long marquee message). */
|
||||
text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
/* never let the page scroll/zoom out from under the board */
|
||||
overscroll-behavior: none;
|
||||
touch-action: manipulation;
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
.ad {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
padding: 6px var(--pad);
|
||||
padding: 6px 0;
|
||||
background: var(--surface-2);
|
||||
color: var(--text-muted);
|
||||
font-size: 0.85rem;
|
||||
@@ -68,6 +68,9 @@
|
||||
}
|
||||
.track {
|
||||
display: inline-block;
|
||||
/* The side inset lives on the track (not the clipping .ad) so the scroll distance
|
||||
(scrollWidth - viewport.clientWidth) reaches the very end of a long message. */
|
||||
padding: 0 var(--pad);
|
||||
will-change: transform;
|
||||
}
|
||||
.track :global(a) {
|
||||
|
||||
@@ -47,12 +47,29 @@
|
||||
$effect(() => {
|
||||
const vp = viewport;
|
||||
if (!vp || !zoomed || !focus) return;
|
||||
const cell = (vp.clientWidth * Z) / 15;
|
||||
vp.scrollTo({
|
||||
left: (focus.col + 0.5) * cell - vp.clientWidth / 2,
|
||||
top: (focus.row + 0.5) * cell - vp.clientHeight / 2,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
const f = focus;
|
||||
const scaler = vp.firstElementChild as HTMLElement | null;
|
||||
const center = () => {
|
||||
// Use the rendered scrollable width so the maths stays correct (gaps, padding).
|
||||
const cell = vp.scrollWidth / 15;
|
||||
vp.scrollTo({
|
||||
left: (f.col + 0.5) * cell - vp.clientWidth / 2,
|
||||
top: (f.row + 0.5) * cell - vp.clientHeight / 2,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
// When zoom has just turned on the board is still widening; centring now would
|
||||
// clamp to the still-small scroll range and land top-left. Wait for the width
|
||||
// transition to finish. If already zoomed (only the focus changed), centre at once.
|
||||
if (scaler && scaler.clientWidth < vp.clientWidth * Z - 1) {
|
||||
const onEnd = () => {
|
||||
scaler.removeEventListener('transitionend', onEnd);
|
||||
center();
|
||||
};
|
||||
scaler.addEventListener('transitionend', onEnd);
|
||||
return () => scaler.removeEventListener('transitionend', onEnd);
|
||||
}
|
||||
center();
|
||||
});
|
||||
|
||||
// Double-tap toggles zoom (pinch was dropped — it conflicts with native scroll).
|
||||
|
||||
@@ -132,7 +132,8 @@
|
||||
dragMoved = true;
|
||||
const slot = placement.rack[downInfo.index];
|
||||
drag = { letter: slot, blank: slot === BLANK, x: e.clientX, y: e.clientY };
|
||||
if (isCoarse() && !zoomed) zoomed = true;
|
||||
// No zoom on drag start: the player may still change their mind. The zoom
|
||||
// (and centring) happens on drop, in attemptPlace.
|
||||
}
|
||||
if (drag) drag = { ...drag, x: e.clientX, y: e.clientY };
|
||||
}
|
||||
@@ -169,6 +170,9 @@
|
||||
return;
|
||||
}
|
||||
if (selected != null) {
|
||||
// A committed tile already sits here: keep the rack selection so a stray tap
|
||||
// on an occupied cell doesn't cancel placement — wait for an empty cell.
|
||||
if (board[row]?.[col]) return;
|
||||
attemptPlace(selected, row, col);
|
||||
selected = null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user