diff --git a/ui/e2e/social.spec.ts b/ui/e2e/social.spec.ts index 331df8b..33cdd70 100644 --- a/ui/e2e/social.spec.ts +++ b/ui/e2e/social.spec.ts @@ -165,12 +165,14 @@ test('link account: the Telegram web sign-in control is offered in a browser', a await expect(page.getByRole('button', { name: 'Link Telegram' })).toBeVisible(); }); -test('chat send and nudge are icon buttons', async ({ page }) => { +test('chat: the message field shows on your turn, the nudge replaces it otherwise', async ({ page }) => { await loginLobby(page); - await page.getByRole('button', { name: /Ann/ }).click(); + await page.getByRole('button', { name: /Ann/ }).click(); // g1: your turn await page.locator('.burger').first().click(); await page.getByRole('button', { name: 'Chat' }).click(); - // Icon-only controls expose their action through the aria-label. + // On your turn the message field + Send are shown and the nudge is hidden (Stage 17); + // chat and nudge are mutually exclusive by turn. Icon-only controls expose their action + // through the aria-label. await expect(page.getByRole('button', { name: 'Send' })).toBeVisible(); - await expect(page.getByRole('button', { name: 'Nudge' })).toBeVisible(); + await expect(page.getByRole('button', { name: 'Nudge' })).toHaveCount(0); }); diff --git a/ui/src/game/Board.svelte b/ui/src/game/Board.svelte index 7f6637b..a434519 100644 --- a/ui/src/game/Board.svelte +++ b/ui/src/game/Board.svelte @@ -250,6 +250,9 @@ border-radius: 1px; background: var(--cell-bg); color: var(--prem-text); + /* No mobile tap flash on a cell tap (parity with the web click; the only intentional + cell animation is the last-word .flash highlight). */ + -webkit-tap-highlight-color: transparent; padding: 0; overflow: hidden; font-size: 0; diff --git a/ui/src/game/Chat.svelte b/ui/src/game/Chat.svelte index 7d12318..fb1ddb9 100644 --- a/ui/src/game/Chat.svelte +++ b/ui/src/game/Chat.svelte @@ -6,16 +6,20 @@ messages, myId, busy, - canNudge = true, + myTurn = false, + nudgeOnCooldown = false, onsend, onnudge, }: { messages: ChatMessage[]; myId: string; busy: boolean; - // Nudging only makes sense while waiting on the opponent; it is disabled on the - // player's own turn (there is no one to hurry along). - canNudge?: boolean; + // Chat and nudge are mutually exclusive by turn (Stage 17): on the player's own turn the + // message field + send are shown (and nudging makes no sense — there is no one to + // hurry); on the opponent's turn only the nudge button shows. While the hourly nudge + // cooldown is active the nudge is disabled with an "awaiting reply" caption. + myTurn?: boolean; + nudgeOnCooldown?: boolean; onsend: (text: string) => void; onnudge: () => void; } = $props(); @@ -44,14 +48,18 @@ {/each}
- e.key === 'Enter' && send()} - /> - - + {#if myTurn} + e.key === 'Enter' && send()} + /> + + {:else} + {#if nudgeOnCooldown}{t('chat.awaitingReply')}{/if} + + {/if}
@@ -99,6 +107,14 @@ .input { display: flex; gap: 6px; + align-items: center; + } + /* The cooldown caption sits to the left of the disabled nudge button. */ + .cooldown { + flex: 1; + text-align: right; + color: var(--text-muted); + font-size: 0.85rem; } .input input { flex: 1; diff --git a/ui/src/game/Game.svelte b/ui/src/game/Game.svelte index 9fa01c9..7c07d2a 100644 --- a/ui/src/game/Game.svelte +++ b/ui/src/game/Game.svelte @@ -760,7 +760,7 @@ {#if panel === 'chat'} (panel = 'none')}> - + {/if} diff --git a/ui/src/lib/i18n/en.ts b/ui/src/lib/i18n/en.ts index c3f0b55..3261d38 100644 --- a/ui/src/lib/i18n/en.ts +++ b/ui/src/lib/i18n/en.ts @@ -41,7 +41,7 @@ export const en = { 'new.title': 'New game', 'new.subtitle': 'Auto-match with another player', 'new.english': 'Scrabble', - 'new.russian': 'Scrabble', + 'new.russian': 'Скрэббл', 'new.erudit': 'Erudite', 'new.find': 'Find a game', 'new.searching': 'Looking for an opponent…', @@ -96,6 +96,7 @@ export const en = { 'chat.send': 'Send', 'chat.nudge': 'Waiting for your move!', 'chat.nudgeAction': 'Nudge', + 'chat.awaitingReply': "Waiting for the opponent's reply", 'chat.empty': 'No messages yet.', 'chat.nudged': '{name} nudged you', diff --git a/ui/src/lib/i18n/ru.ts b/ui/src/lib/i18n/ru.ts index f7fdeac..10846f5 100644 --- a/ui/src/lib/i18n/ru.ts +++ b/ui/src/lib/i18n/ru.ts @@ -41,7 +41,7 @@ export const ru: Record = { 'new.title': 'Новая игра', 'new.subtitle': 'Автоподбор соперника', - 'new.english': 'Скрэббл', + 'new.english': 'Scrabble', 'new.russian': 'Скрэббл', 'new.erudit': 'Эрудит', 'new.find': 'Найти игру', @@ -97,6 +97,7 @@ export const ru: Record = { 'chat.send': 'Отправить', 'chat.nudge': 'Жду вашего хода!', 'chat.nudgeAction': 'Поторопить', + 'chat.awaitingReply': 'Ждём реакцию соперника', 'chat.empty': 'Сообщений пока нет.', 'chat.nudged': '{name} торопит вас', diff --git a/ui/src/lib/variants.ts b/ui/src/lib/variants.ts index 1f21dfd..a622c2b 100644 --- a/ui/src/lib/variants.ts +++ b/ui/src/lib/variants.ts @@ -11,10 +11,11 @@ export interface VariantOption { label: MessageKey; } -// 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. +// ALL_VARIANTS lists every variant in display order. The labels are display names keyed by +// the game's alphabet, not the interface language: the English-alphabet game is always +// "Scrabble" and the Russian-alphabet Scrabble always "Скрэббл" (both unlocalized, so the +// two never collide whatever the UI language); Erudit is localized "Erudite"/"Эрудит" +// (Stage 17). export const ALL_VARIANTS: VariantOption[] = [ { id: 'english', label: 'new.english' }, { id: 'russian_scrabble', label: 'new.russian' }, diff --git a/ui/src/screens/Profile.svelte b/ui/src/screens/Profile.svelte index 999e10e..8cc48d2 100644 --- a/ui/src/screens/Profile.svelte +++ b/ui/src/screens/Profile.svelte @@ -166,8 +166,6 @@
{p.displayName}
{#if p.isGuest}{t('profile.guest')}{/if} -
{t('profile.hintBalance')}{p.hintBalance}
- {#if p.isGuest}

{t('profile.guestLocked')}

{:else} @@ -284,15 +282,6 @@ color: var(--text-muted); font-size: 0.8rem; } - .hintbal { - display: flex; - justify-content: space-between; - color: var(--text-muted); - } - .hintbal b { - color: var(--text); - font-weight: 600; - } .muted { color: var(--text-muted); font-size: 0.9rem;