Stage 17: test-contour verification & defect fixes #19
@@ -48,6 +48,18 @@ test('a pending tile recalls on double-tap, not on a single tap', async ({ page
|
|||||||
await expect(page.locator('[data-cell].pending')).toHaveCount(0);
|
await expect(page.locator('[data-cell].pending')).toHaveCount(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the board is a gapless checkerboard by default; grid lines toggle in Settings', async ({ page }) => {
|
||||||
|
await openGame(page);
|
||||||
|
await expect(page.locator('.grid.gridless')).toBeVisible(); // lines off by default
|
||||||
|
|
||||||
|
await page.evaluate(() => (location.hash = '/settings'));
|
||||||
|
await page.locator('.gridlines input').check(); // turn grid lines on
|
||||||
|
await page.evaluate(() => (location.hash = '/game/g1'));
|
||||||
|
|
||||||
|
await expect(page.locator('.grid')).toBeVisible();
|
||||||
|
await expect(page.locator('.grid.gridless')).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
test('shuffle reorders the rack but keeps the same tiles', async ({ page }) => {
|
test('shuffle reorders the rack but keeps the same tiles', async ({ page }) => {
|
||||||
await openGame(page);
|
await openGame(page);
|
||||||
const before = await page.locator('.rack .tile').allTextContents();
|
const before = await page.locator('.rack .tile').allTextContents();
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
zoomed,
|
zoomed,
|
||||||
variant,
|
variant,
|
||||||
labelMode,
|
labelMode,
|
||||||
|
lines,
|
||||||
locale,
|
locale,
|
||||||
focus,
|
focus,
|
||||||
oncell,
|
oncell,
|
||||||
@@ -32,6 +33,8 @@
|
|||||||
zoomed: boolean;
|
zoomed: boolean;
|
||||||
variant: Variant;
|
variant: Variant;
|
||||||
labelMode: BoardLabelMode;
|
labelMode: BoardLabelMode;
|
||||||
|
/** Draw 1px grid lines between cells; when false the board is a gapless checkerboard. */
|
||||||
|
lines: boolean;
|
||||||
locale: Locale;
|
locale: Locale;
|
||||||
focus: { row: number; col: number } | null;
|
focus: { row: number; col: number } | null;
|
||||||
oncell: (row: number, col: number) => void;
|
oncell: (row: number, col: number) => void;
|
||||||
@@ -94,7 +97,7 @@
|
|||||||
|
|
||||||
<div class="viewport" class:zoomed bind:this={viewport}>
|
<div class="viewport" class:zoomed bind:this={viewport}>
|
||||||
<div class="scaler" style="--z: {z};">
|
<div class="scaler" style="--z: {z};">
|
||||||
<div class="grid">
|
<div class="grid" class:gridless={!lines}>
|
||||||
{#each board as rowCells, r (r)}
|
{#each board as rowCells, r (r)}
|
||||||
{#each rowCells as cell, c (c)}
|
{#each rowCells as cell, c (c)}
|
||||||
{@const p = pending.get(key(r, c))}
|
{@const p = pending.get(key(r, c))}
|
||||||
@@ -107,6 +110,7 @@
|
|||||||
class:pending={!!p && !cell}
|
class:pending={!!p && !cell}
|
||||||
class:hl={!!cell && highlight.has(key(r, c)) && !flash}
|
class:hl={!!cell && highlight.has(key(r, c)) && !flash}
|
||||||
class:flash={!!cell && flash && highlight.has(key(r, c))}
|
class:flash={!!cell && flash && highlight.has(key(r, c))}
|
||||||
|
class:dark={premium[r][c] === '' && !cell && !p && (r + c) % 2 === 1}
|
||||||
data-cell
|
data-cell
|
||||||
data-row={r}
|
data-row={r}
|
||||||
data-col={c}
|
data-col={c}
|
||||||
@@ -187,6 +191,28 @@
|
|||||||
.cell.pending {
|
.cell.pending {
|
||||||
background: var(--tile-pending);
|
background: var(--tile-pending);
|
||||||
}
|
}
|
||||||
|
/* Lines-off variant: a gapless checkerboard. The 1px grid gaps (and the cell-line they
|
||||||
|
reveal) collapse, saving ~14px of board width; plain cells alternate shades, and tiles
|
||||||
|
get rounded corners and a soft right-side shadow so adjacent gapless tiles still read
|
||||||
|
as separate pieces. */
|
||||||
|
.grid.gridless {
|
||||||
|
gap: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: var(--board-bg);
|
||||||
|
}
|
||||||
|
.grid.gridless .cell {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.grid.gridless .cell.dark {
|
||||||
|
background: color-mix(in srgb, var(--cell-bg) 88%, #000);
|
||||||
|
}
|
||||||
|
.grid.gridless .cell.filled,
|
||||||
|
.grid.gridless .cell.pending {
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 -2px 0 var(--tile-edge),
|
||||||
|
2px 0 3px -1px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
.cell.hl {
|
.cell.hl {
|
||||||
background: var(--tile-recent);
|
background: var(--tile-recent);
|
||||||
/* The bottom edge goes darker than the highlighted fill (not lighter, as the plain
|
/* The bottom edge goes darker than the highlighted fill (not lighter, as the plain
|
||||||
|
|||||||
@@ -576,6 +576,7 @@
|
|||||||
{zoomed}
|
{zoomed}
|
||||||
{variant}
|
{variant}
|
||||||
labelMode={app.boardLabels}
|
labelMode={app.boardLabels}
|
||||||
|
lines={app.boardLines}
|
||||||
locale={app.locale}
|
locale={app.locale}
|
||||||
{focus}
|
{focus}
|
||||||
oncell={onCell}
|
oncell={onCell}
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ export const app = $state<{
|
|||||||
locale: Locale;
|
locale: Locale;
|
||||||
reduceMotion: boolean;
|
reduceMotion: boolean;
|
||||||
boardLabels: BoardLabelMode;
|
boardLabels: BoardLabelMode;
|
||||||
|
/** Draw grid lines between board cells; off (default) is a gapless checkerboard. */
|
||||||
|
boardLines: boolean;
|
||||||
localeLocked: boolean;
|
localeLocked: boolean;
|
||||||
/** Pending incoming friend requests + invitations, for the lobby badge. */
|
/** Pending incoming friend requests + invitations, for the lobby badge. */
|
||||||
notifications: number;
|
notifications: number;
|
||||||
@@ -53,6 +55,7 @@ export const app = $state<{
|
|||||||
locale: 'en',
|
locale: 'en',
|
||||||
reduceMotion: false,
|
reduceMotion: false,
|
||||||
boardLabels: 'beginner',
|
boardLabels: 'beginner',
|
||||||
|
boardLines: false,
|
||||||
localeLocked: false,
|
localeLocked: false,
|
||||||
notifications: 0,
|
notifications: 0,
|
||||||
});
|
});
|
||||||
@@ -229,6 +232,7 @@ export async function bootstrap(): Promise<void> {
|
|||||||
app.theme = prefs.theme ?? 'auto';
|
app.theme = prefs.theme ?? 'auto';
|
||||||
app.reduceMotion = prefs.reduceMotion ?? false;
|
app.reduceMotion = prefs.reduceMotion ?? false;
|
||||||
app.boardLabels = prefs.boardLabels ?? 'beginner';
|
app.boardLabels = prefs.boardLabels ?? 'beginner';
|
||||||
|
app.boardLines = prefs.boardLines ?? false;
|
||||||
applyTheme(app.theme);
|
applyTheme(app.theme);
|
||||||
applyReduceMotion(app.reduceMotion);
|
applyReduceMotion(app.reduceMotion);
|
||||||
if (prefs.locale) {
|
if (prefs.locale) {
|
||||||
@@ -354,6 +358,7 @@ function persistPrefs(): void {
|
|||||||
locale: app.locale,
|
locale: app.locale,
|
||||||
reduceMotion: app.reduceMotion,
|
reduceMotion: app.reduceMotion,
|
||||||
boardLabels: app.boardLabels,
|
boardLabels: app.boardLabels,
|
||||||
|
boardLines: app.boardLines,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,6 +411,11 @@ export function setBoardLabels(mode: BoardLabelMode): void {
|
|||||||
persistPrefs();
|
persistPrefs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setBoardLines(on: boolean): void {
|
||||||
|
app.boardLines = on;
|
||||||
|
persistPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
// Background/foreground lifecycle: silence the reconnect banner during a suspend and
|
// Background/foreground lifecycle: silence the reconnect banner during a suspend and
|
||||||
// reconnect quietly on return (and refresh the lobby badge for any push missed while
|
// reconnect quietly on return (and refresh the lobby badge for any push missed while
|
||||||
// hidden, §10). Several signals cover the platforms: the page Visibility API, the
|
// hidden, §10). Several signals cover the platforms: the page Visibility API, the
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ export const en = {
|
|||||||
'settings.labelsBeginner': 'Beginner',
|
'settings.labelsBeginner': 'Beginner',
|
||||||
'settings.labelsClassic': 'Classic',
|
'settings.labelsClassic': 'Classic',
|
||||||
'settings.labelsNone': 'None',
|
'settings.labelsNone': 'None',
|
||||||
|
'settings.boardLines': 'Grid lines',
|
||||||
'settings.reduceMotion': 'Reduce motion',
|
'settings.reduceMotion': 'Reduce motion',
|
||||||
|
|
||||||
'about.title': 'About',
|
'about.title': 'About',
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ export const ru: Record<MessageKey, string> = {
|
|||||||
'settings.labelsBeginner': 'Новичок',
|
'settings.labelsBeginner': 'Новичок',
|
||||||
'settings.labelsClassic': 'Классика',
|
'settings.labelsClassic': 'Классика',
|
||||||
'settings.labelsNone': 'Без текста',
|
'settings.labelsNone': 'Без текста',
|
||||||
|
'settings.boardLines': 'Линии сетки',
|
||||||
'settings.reduceMotion': 'Меньше анимаций',
|
'settings.reduceMotion': 'Меньше анимаций',
|
||||||
|
|
||||||
'about.title': 'О программе',
|
'about.title': 'О программе',
|
||||||
|
|||||||
@@ -124,6 +124,8 @@ export interface Prefs {
|
|||||||
locale: Locale;
|
locale: Locale;
|
||||||
reduceMotion: boolean;
|
reduceMotion: boolean;
|
||||||
boardLabels: BoardLabelMode;
|
boardLabels: BoardLabelMode;
|
||||||
|
/** Draw the 1px grid lines between cells; off (default) shows a gapless checkerboard. */
|
||||||
|
boardLines: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadPrefs(): Promise<Partial<Prefs>> {
|
export async function loadPrefs(): Promise<Partial<Prefs>> {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import {
|
import {
|
||||||
app,
|
app,
|
||||||
setBoardLabels,
|
setBoardLabels,
|
||||||
|
setBoardLines,
|
||||||
setLocalePref,
|
setLocalePref,
|
||||||
setReduceMotion,
|
setReduceMotion,
|
||||||
setTheme,
|
setTheme,
|
||||||
@@ -63,6 +64,14 @@
|
|||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
<label class="row gridlines">
|
||||||
|
<span>{t('settings.boardLines')}</span>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={app.boardLines}
|
||||||
|
onchange={(e) => setBoardLines(e.currentTarget.checked)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
@@ -118,4 +127,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
.gridlines {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user