From ad91bc728ba886794607ec29ae28f6362f26da48 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Thu, 11 Jun 2026 15:12:40 +0200 Subject: [PATCH] UI: taller tg-fullscreen header + labelled hub tabs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tg-fullscreen: +20px header height — without the (removed) hamburger the title bar lost its bulk and sat flush on Telegram's native nav band. - Settings/Comms hub tabs gain text labels under the icons (Settings / Profile / Friends / Info and Chat / Dictionary); the icon is aria-hidden so the label names the button. New i18n keys about.tab, game.dictionary. --- docs/UI_DESIGN.md | 5 +++-- ui/e2e/game.spec.ts | 4 ++-- ui/e2e/social.spec.ts | 6 +++--- ui/src/components/Header.svelte | 10 +++++----- ui/src/game/CommsHub.svelte | 8 ++++---- ui/src/lib/i18n/en.ts | 2 ++ ui/src/lib/i18n/ru.ts | 2 ++ ui/src/screens/SettingsHub.svelte | 16 ++++++++-------- 8 files changed, 29 insertions(+), 24 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index adb2e9d..f3a8a3b 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -35,8 +35,9 @@ Login uses `Screen`. routes `/settings|/profile|/friends|/about` and `/game/:id/{chat,check}` survive as hub entry points (so a Telegram friend-code deep-link still lands on the Friends tab). - **Tab bar** (`TabBar.svelte`): square, borderless, evenly distributed buttons — a large - emoji icon over a tiny truncated label (hub tabs are **icon-only**). A press highlights a - rounded **square** behind the icon; a hub's **selected** tab stays highlighted (a filled + emoji icon over a tiny truncated label (the icon is `aria-hidden`, so the label names the + button). A press highlights a rounded **square** behind the icon; a hub's **selected** tab + stays highlighted (a filled square with an accent underline). A red count **badge** rides the icon's corner — on the lobby ⚙️ tab and the hub's 🤝 Friends tab for pending incoming friend requests (invitations keep their own lobby section), and on the Hint tab for the remaining count. No text diff --git a/ui/e2e/game.spec.ts b/ui/e2e/game.spec.ts index d5d273e..aa0c280 100644 --- a/ui/e2e/game.spec.ts +++ b/ui/e2e/game.spec.ts @@ -172,7 +172,7 @@ test('check-word sanitises input and shows a verdict', async ({ page }) => { await page.locator('.scoreboard').click(); // open the history await page.getByRole('button', { name: 'Chat' }).click(); // 💬 -> comms hub await expect(page.locator('.pane')).toHaveCount(1); - await page.getByRole('button', { name: 'Check word' }).click(); // 🔎 -> dictionary tab + await page.getByRole('button', { name: 'Dictionary' }).click(); // 🔎 -> dictionary tab const input = page.locator('.check input'); await input.fill('qz9!a'); // digits/punctuation dropped, letters upper-cased @@ -222,7 +222,7 @@ test('comms hub: chat and dictionary share a screen, back returns to the game', await expect(page.locator('.chat')).toBeVisible(); // The Dictionary tab switches in place (same screen, no navigation). - await page.getByRole('button', { name: 'Check word' }).click(); + await page.getByRole('button', { name: 'Dictionary' }).click(); await expect(page.locator('.check input')).toBeVisible(); // The header back chevron returns to the game. diff --git a/ui/e2e/social.spec.ts b/ui/e2e/social.spec.ts index 785d1f3..6f3d1f4 100644 --- a/ui/e2e/social.spec.ts +++ b/ui/e2e/social.spec.ts @@ -12,7 +12,7 @@ async function loginLobby(page: Page): Promise { // The Settings hub (the lobby ⚙️ tab) hosts Settings / Profile / Friends / About as in-place // tabs; the back control always returns to the lobby. Tabs are icon-only with an aria-label. -async function openSettingsTab(page: Page, tab: 'Profile' | 'Friends' | 'About'): Promise { +async function openSettingsTab(page: Page, tab: 'Profile' | 'Friends' | 'Info'): Promise { await page.getByRole('button', { name: /Settings/ }).click(); // lobby ⚙️ tab await expect(page.locator('.pane')).toHaveCount(1); // let the slide settle await page.getByRole('button', { name: tab, exact: true }).click(); @@ -70,7 +70,7 @@ test('settings hub: tabs switch in place and back returns to the lobby', async ( await expect(page.getByText('Friend requests')).toBeVisible(); await expect(page.locator('.pane')).toHaveCount(1); - await page.getByRole('button', { name: 'About', exact: true }).click(); + await page.getByRole('button', { name: 'Info', exact: true }).click(); await expect(page.getByText(/Version/)).toBeVisible(); // Back returns to the lobby from any tab. @@ -114,7 +114,7 @@ test('finished game draws an inert footer and trims live-only controls', async ( // The comms hub offers Chat only — the Dictionary tab is hidden for a finished game. await page.getByRole('button', { name: 'Chat' }).click(); // 💬 await expect(page.locator('.pane')).toHaveCount(1); - await expect(page.getByRole('button', { name: 'Check word' })).toHaveCount(0); + await expect(page.getByRole('button', { name: 'Dictionary' })).toHaveCount(0); }); test('lobby: hiding a finished game removes it (kebab → ❌), keeping the others', async ({ page }) => { diff --git a/ui/src/components/Header.svelte b/ui/src/components/Header.svelte index ace582b..559e974 100644 --- a/ui/src/components/Header.svelte +++ b/ui/src/components/Header.svelte @@ -105,14 +105,14 @@ is unchanged) and centres the title within it, BELOW the notch — lining it up vertically with Telegram's own back/menu controls, which sit in the band's corners. */ :global(html.tg-fullscreen) .bar { - min-height: var(--tg-content-top); + /* The bar spans the Telegram nav band and then **+20px** past --tg-content-top, so the + native controls aren't flush against our content. Without the (removed) hamburger the + title alone left the bar sitting right on the band; the extra height restores the gap. + padding-top clears the notch; the title centres in the band. (Owner-tunable height.) */ + min-height: calc(var(--tg-content-top) + 20px); box-sizing: border-box; align-items: center; justify-content: center; - /* +12px of vertical breathing room (6 above / 6 below the centred content, on top of the - notch) so Telegram's native controls aren't flush against our header. Applied as - padding because the bar is sized by its content here, not by min-height (owner review - tweaks). */ padding-top: calc(var(--tg-safe-top) + 6px); padding-bottom: 6px; } diff --git a/ui/src/game/CommsHub.svelte b/ui/src/game/CommsHub.svelte index 1d184bd..0c0fecc 100644 --- a/ui/src/game/CommsHub.svelte +++ b/ui/src/game/CommsHub.svelte @@ -38,12 +38,12 @@ {#snippet tabbar()} - {#if active} - {/if} diff --git a/ui/src/lib/i18n/en.ts b/ui/src/lib/i18n/en.ts index 53aedc4..73f7d38 100644 --- a/ui/src/lib/i18n/en.ts +++ b/ui/src/lib/i18n/en.ts @@ -65,6 +65,7 @@ export const en = { 'game.hint': 'Hint', 'game.chat': 'Chat', 'game.checkWord': 'Check word', + 'game.dictionary': 'Dictionary', 'game.dropGame': 'Drop game', 'game.preview': 'Scores {n}', 'game.previewIllegal': 'Not a legal move', @@ -149,6 +150,7 @@ export const en = { 'settings.reduceMotion': 'Reduce motion', 'about.title': 'About', + 'about.tab': 'Info', 'about.description': 'A multiplatform Scrabble game.', 'about.version': 'Version {v}', diff --git a/ui/src/lib/i18n/ru.ts b/ui/src/lib/i18n/ru.ts index aefb2f1..862b122 100644 --- a/ui/src/lib/i18n/ru.ts +++ b/ui/src/lib/i18n/ru.ts @@ -66,6 +66,7 @@ export const ru: Record = { 'game.hint': 'Подсказка', 'game.chat': 'Чат', 'game.checkWord': 'Проверить слово', + 'game.dictionary': 'Словарь', 'game.dropGame': 'Покинуть игру', 'game.preview': 'Очков: {n}', 'game.previewIllegal': 'Недопустимый ход', @@ -150,6 +151,7 @@ export const ru: Record = { 'settings.reduceMotion': 'Меньше анимаций', 'about.title': 'О программе', + 'about.tab': 'Инфо', 'about.description': 'Мультиплатформенная игра в скрабл.', 'about.version': 'Версия {v}', diff --git a/ui/src/screens/SettingsHub.svelte b/ui/src/screens/SettingsHub.svelte index 97a1d07..1bd18fa 100644 --- a/ui/src/screens/SettingsHub.svelte +++ b/ui/src/screens/SettingsHub.svelte @@ -45,19 +45,19 @@ {#snippet tabbar()} - - {#if !guest} - {/if} - {/snippet}