From 10d48884ac45f97e05c35cc86e848f73c562be44 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Wed, 3 Jun 2026 15:52:28 +0200 Subject: [PATCH] Stage 7 polish (round 3): zoom magnifies labels, popover edge-anchor, flash x2 - item 5: move container-type to the zoom-scaled .scaler so cqw labels grow WITH the board (magnifying-glass zoom); new e2e measures the font grows ~1.85x - item 8: confirm popovers anchor to the trigger's right edge (no longer run off-screen) - item 9: last-word flash runs 2 cycles then settles to normal (was infinite) --- ui/e2e/zoom.spec.ts | 26 ++++++++++++++++++++++++++ ui/src/components/HoldConfirm.svelte | 3 +-- ui/src/game/Board.svelte | 9 ++++++--- 3 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 ui/e2e/zoom.spec.ts diff --git a/ui/e2e/zoom.spec.ts b/ui/e2e/zoom.spec.ts new file mode 100644 index 0000000..866452a --- /dev/null +++ b/ui/e2e/zoom.spec.ts @@ -0,0 +1,26 @@ +import { expect, test } from '@playwright/test'; + +// Item 5: zooming the board must enlarge the labels too (a magnifying-glass zoom). +// cqw is sized against the zoom-scaled board, so the font grows with the cells. +test('zoom enlarges the board labels with the board', async ({ page }) => { + await page.goto('/'); + await page.getByRole('button', { name: /guest/i }).click(); + await page.getByRole('button', { name: /Ann/ }).click(); + + const letter = page.locator('[data-cell] .letter').first(); + await expect(letter).toBeVisible(); + const base = await letter.evaluate((el) => parseFloat(getComputedStyle(el).fontSize)); + + // Double-tap an empty cell to zoom in (two synchronous clicks = a double-tap). + await page + .locator('[data-cell]:not(.filled)') + .nth(20) + .evaluate((el: HTMLElement) => { + el.click(); + el.click(); + }); + await page.waitForTimeout(400); // let the width transition settle + + const zoomed = await letter.evaluate((el) => parseFloat(getComputedStyle(el).fontSize)); + expect(zoomed).toBeGreaterThan(base * 1.4); +}); diff --git a/ui/src/components/HoldConfirm.svelte b/ui/src/components/HoldConfirm.svelte index 4f0e156..6db8eb6 100644 --- a/ui/src/components/HoldConfirm.svelte +++ b/ui/src/components/HoldConfirm.svelte @@ -92,8 +92,7 @@ .popover { position: absolute; bottom: calc(100% + 6px); - left: 50%; - transform: translateX(-50%); + right: 0; z-index: 19; display: flex; flex-direction: column; diff --git a/ui/src/game/Board.svelte b/ui/src/game/Board.svelte index 17f307e..2f2688a 100644 --- a/ui/src/game/Board.svelte +++ b/ui/src/game/Board.svelte @@ -84,7 +84,7 @@ class="cell {premClass[premium[r][c]]}" class:filled={!!cell} class:pending={!!p && !cell} - class:hl={!!cell && highlight.has(key(r, c))} + class:hl={!!cell && highlight.has(key(r, c)) && !flash} class:flash={!!cell && flash && highlight.has(key(r, c))} data-cell data-row={r} @@ -115,14 +115,16 @@ overflow: hidden; background: var(--board-bg); border-radius: var(--radius-sm); - container-type: inline-size; } .viewport.zoomed { overflow: auto; } + /* The query container is the (zoom-scaled) board, so cqw labels scale WITH the board + — a magnifying-glass zoom. */ .scaler { width: calc(100% * var(--z)); transition: width 0.25s ease; + container-type: inline-size; } .grid { display: grid; @@ -167,7 +169,8 @@ background: var(--tile-recent); } .cell.flash { - animation: tileflash 1s ease-in-out infinite; + /* Two flashes to draw the eye, then settle back to normal so it does not distract. */ + animation: tileflash 1s ease-in-out 2; } @keyframes tileflash { 0%,