diff --git a/ui/src/game/Board.svelte b/ui/src/game/Board.svelte index 1a6df98..7f6637b 100644 --- a/ui/src/game/Board.svelte +++ b/ui/src/game/Board.svelte @@ -1,4 +1,5 @@ - + {#snippet menu()} {/snippet} @@ -600,6 +630,7 @@ lines={app.boardLines} locale={app.locale} {focus} + {dropTarget} oncell={onCell} ontogglezoom={(r, c) => { focus = { row: r, col: c }; if (!gameOver) zoomed = !zoomed; }} onrecall={onRecall} @@ -624,10 +655,10 @@ a finished game shows the final rack greyed out and the controls disabled. -->
- +
{#if !gameOver && placement.pending.length > 0} - + {/if}
{:else} @@ -883,18 +914,19 @@ flex: 1; min-width: 0; } + /* A borderless icon button (like the tab bar), not a filled accent button — and disabled + while the pending word is known to be illegal (Stage 17). */ .make { min-width: 56px; - background: var(--accent); - color: var(--accent-text); + background: none; + color: var(--text); border: none; - border-radius: var(--radius-sm); display: grid; place-items: center; - font-size: 1.6rem; + font-size: 1.8rem; } .make:disabled { - opacity: 0.55; + opacity: 0.4; } .pop { padding: 9px 14px; diff --git a/ui/src/game/Rack.svelte b/ui/src/game/Rack.svelte index 369e320..0a50e1b 100644 --- a/ui/src/game/Rack.svelte +++ b/ui/src/game/Rack.svelte @@ -26,8 +26,9 @@ // hop flies a tile to its shuffled position along a low parabola (apogee ≈ half a tile // height). The duration scales with the horizontal distance — i.e. the arc length — so - // the longest swap (slot 1 ↔ 7) takes ~0.5s and shorter swaps land sooner. It runs only - // while a shuffle is in progress; ordinary reflow (placing/recalling a tile) is instant. + // the longest swap (slot 1 ↔ 7) takes ~0.3s and shorter swaps land sooner. It runs only + // while a shuffle is in progress (and motion is not reduced); ordinary reflow + // (placing/recalling a tile) is instant. function hop(node: HTMLElement, { from, to }: { from: DOMRect; to: DOMRect }, active: boolean) { const dx = from.left - to.left; const dy = from.top - to.top; @@ -36,7 +37,7 @@ const span = node.parentElement?.getBoundingClientRect().width || dist; const lift = (to.height || from.height) * 0.5; return { - duration: Math.max(160, Math.min(500, (dist / span) * 560)), + duration: Math.max(120, Math.min(300, (dist / span) * 340)), css: (t: number, u: number) => `transform: translate(${dx * u}px, ${dy * u - Math.sin(Math.PI * t) * lift}px);`, }; diff --git a/ui/src/lib/i18n/en.ts b/ui/src/lib/i18n/en.ts index 63e67c5..c3f0b55 100644 --- a/ui/src/lib/i18n/en.ts +++ b/ui/src/lib/i18n/en.ts @@ -40,9 +40,9 @@ export const en = { 'new.title': 'New game', 'new.subtitle': 'Auto-match with another player', - 'new.english': 'English', - 'new.russian': 'Russian', - 'new.erudit': 'Эрудит', + 'new.english': 'Scrabble', + 'new.russian': 'Scrabble', + 'new.erudit': 'Erudite', 'new.find': 'Find a game', 'new.searching': 'Looking for an opponent…', diff --git a/ui/src/lib/i18n/ru.ts b/ui/src/lib/i18n/ru.ts index 319c606..f7fdeac 100644 --- a/ui/src/lib/i18n/ru.ts +++ b/ui/src/lib/i18n/ru.ts @@ -41,8 +41,8 @@ export const ru: Record = { 'new.title': 'Новая игра', 'new.subtitle': 'Автоподбор соперника', - 'new.english': 'Английский', - 'new.russian': 'Русский', + 'new.english': 'Скрэббл', + 'new.russian': 'Скрэббл', 'new.erudit': 'Эрудит', 'new.find': 'Найти игру', 'new.searching': 'Ищем соперника…', diff --git a/ui/src/lib/variants.ts b/ui/src/lib/variants.ts index 31ad921..1f21dfd 100644 --- a/ui/src/lib/variants.ts +++ b/ui/src/lib/variants.ts @@ -11,13 +11,22 @@ export interface VariantOption { label: MessageKey; } -// ALL_VARIANTS lists every variant in display order. +// ALL_VARIANTS lists every variant in display order. The labels are display names, not +// language names: both Scrabble variants render as "Scrabble"/"Скрэббл" and Erudit as +// "Erudite"/"Эрудит" (Stage 17) — the offered list is language-gated, so within one +// language the names stay distinct. export const ALL_VARIANTS: VariantOption[] = [ { id: 'english', label: 'new.english' }, { id: 'russian_scrabble', label: 'new.russian' }, { id: 'erudit', label: 'new.erudit' }, ]; +// variantNameKey returns the i18n key for a variant's display name (used by the in-game +// title and the lobby cards). +export function variantNameKey(v: Variant): MessageKey { + return ALL_VARIANTS.find((o) => o.id === v)?.label ?? 'new.english'; +} + // VARIANT_LANGUAGE maps each variant to its game language. en -> English; // ru -> Russian + Эрудит. export const VARIANT_LANGUAGE: Record = { english: 'en', russian_scrabble: 'ru', erudit: 'ru' };