Files
scrabble-game/ui/src/screens/Lobby.svelte
T
Ilia Denisov 38be7fea96 Stage 7 polish: app shell + nav + lobby + settings (Parts A/B/C)
- Screen.svelte shell: nav bar grows, ad+content+tabbar pinned bottom (mobile feel)
- AdBanner.svelte + banner.ts rotator (params, mock long/short, linkify); Header CSS chevron + grow; Menu (bigger CSS hamburger); TabBar + HoldConfirm shared components; user-select:none
- Lobby: hide-empty sections, tab order New/Tournaments/Stats, place-based result badges (result.ts)
- Settings: Board style > Labels (beginner/classic/none) + prefs plumbing (boardlabels.ts); i18n keys + ru mirror
2026-06-03 13:20:56 +02:00

151 lines
4.1 KiB
Svelte

<script lang="ts">
import { onMount } from 'svelte';
import Screen from '../components/Screen.svelte';
import Menu from '../components/Menu.svelte';
import TabBar from '../components/TabBar.svelte';
import { app, handleError, showToast } from '../lib/app.svelte';
import { gateway } from '../lib/gateway';
import { navigate } from '../lib/router.svelte';
import { t } from '../lib/i18n/index.svelte';
import { resultBadge } from '../lib/result';
import type { GameView } from '../lib/model';
let games = $state<GameView[]>([]);
async function load() {
try {
games = (await gateway.gamesList()).games;
} catch (e) {
handleError(e);
}
}
onMount(load);
$effect(() => {
if (app.lastEvent) void load();
});
const myId = $derived(app.session?.userId ?? '');
const active = $derived(games.filter((g) => g.status === 'active'));
const finished = $derived(games.filter((g) => g.status !== 'active'));
function opponents(g: GameView): string {
return g.seats
.filter((s) => s.accountId !== myId)
.map((s) => s.displayName)
.join(', ');
}
function scoreline(g: GameView): string {
const me = g.seats.find((s) => s.accountId === myId);
const opp = g.seats.filter((s) => s.accountId !== myId).map((s) => s.score);
return `${me?.score ?? 0} : ${opp.join(', ')}`;
}
const menuItems = $derived([
{ label: t('lobby.profile'), onclick: () => navigate('/profile') },
{ label: t('lobby.settings'), onclick: () => navigate('/settings') },
{ label: t('lobby.about'), onclick: () => navigate('/about') },
]);
</script>
<Screen title={app.profile?.displayName ?? t('app.title')}>
{#snippet menu()}
<Menu items={menuItems} />
{/snippet}
<div class="lobby">
{#each [{ h: 'lobby.activeGames', list: active }, { h: 'lobby.finishedGames', list: finished }] as group (group.h)}
{#if group.list.length}
<section>
<h2>{t(group.h as 'lobby.activeGames')}</h2>
{#each group.list as g (g.id)}
{@const b = resultBadge(g, myId)}
<button class="row" onclick={() => navigate(`/game/${g.id}`)}>
<span class="info">
<span class="who">{opponents(g) || '—'}</span>
<span class="sub">{t(b.key)} · {scoreline(g)}</span>
</span>
<span class="emoji">{b.emoji}</span>
</button>
{/each}
</section>
{/if}
{/each}
{#if !active.length && !finished.length}
<p class="empty">{t('lobby.noActive')}</p>
{/if}
</div>
{#snippet tabbar()}
<TabBar>
<button class="tab" onclick={() => navigate('/new')}>
<span class="sq">🎲</span><span class="lbl">{t('lobby.new')}</span>
</button>
<button class="tab" onclick={() => showToast(t('lobby.soon'))}>
<span class="sq">⚔️</span><span class="lbl">{t('lobby.tournaments')}</span>
</button>
<button class="tab" onclick={() => showToast(t('lobby.soon'))}>
<span class="sq">🧮</span><span class="lbl">{t('lobby.stats')}</span>
</button>
</TabBar>
{/snippet}
</Screen>
<style>
.lobby {
padding: var(--pad);
display: flex;
flex-direction: column;
gap: 18px;
}
h2 {
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--text-muted);
margin: 0 0 8px;
}
.empty {
color: var(--text-muted);
font-size: 0.9rem;
margin: 0;
}
.row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
width: 100%;
text-align: left;
padding: 12px 14px;
margin-bottom: 8px;
border: 1px solid var(--border);
background: var(--surface);
color: var(--text);
border-radius: var(--radius);
user-select: none;
}
.info {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.who {
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sub {
font-size: 0.85rem;
color: var(--text-muted);
}
.emoji {
font-size: 1.8rem;
line-height: 1;
flex: 0 0 auto;
}
</style>