Apply velvet-table redesign, fix game lifecycle and history bugs

Frontend:
- Dark green/gold "velvet table" visual redesign across the whole app
  (Auth, Lobby, GameList, GameTable, History, GameOver, modals), with
  Playfair Display/DM Sans typography and a centralized Tailwind palette.
- Desktop game table fit-scales to fill the window; mobile gets
  overlapping hand/trick layouts and larger touch-friendly cards.
- Standings sidebar now groups completed rounds by series with a
  per-series subtotal row, struck-through tips on missed bids.
- History page rewritten into a scoreboard-style detail view (player
  totals beside names, series grouped 2-up on desktop / stacked on
  mobile) and gained game names, completed/abandoned status, and a
  button to reopen a prematurely-ended game back into the lobby.

Backend:
- Fix started games being deleted from memory (and vanishing from
  everyone's lobby) when all players disconnect; only `end_game` tears
  down a started game now.
- Fix a crash writing a timezone-aware datetime into the naive
  `ended_at` Postgres column.
- Add `reopen_game`/`restore_game` to un-end a prematurely-ended game
  from history and resume it from the lobby.
- Let any seated player end an abandoned game once the host is
  offline, not just the host, so the game isn't stuck forever.
- Expose SERIES_PER_GAME/ROUNDS_PER_SERIES as named constants on the
  engine so the persistence layer derives game-completion rules from
  bridzik.py instead of re-encoding them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
tim
2026-07-01 00:11:42 +02:00
co-authored by Claude Sonnet 5
parent 30c32b7714
commit 2c2f07c2ec
28 changed files with 1472 additions and 395 deletions
+7
View File
@@ -32,6 +32,9 @@ export interface GameStatusDetail {
active_stash?: StashData;
previous_stash?: StashData;
standings: number[][][];
/** Tips per series/round/seat, same shape as standings. Used to show the
* struck-through tip in place of 0 when a tip failed. */
standings_guesses?: number[][][];
}
export interface GameStatusPayload {
@@ -70,10 +73,13 @@ export interface Registration {
export interface HistoryGame {
gid: string;
name: string;
created_at: string | null;
ended_at: string | null;
players: string[];
my_points: number;
/** True = dohraná naplno; false = predčasne ukončená (dá sa obnoviť do lobby). */
completed: boolean;
}
export interface GameDetailRound {
@@ -88,6 +94,7 @@ export interface GameDetailRound {
export interface GameDetail {
gid: string;
name: string;
created_at: string | null;
ended_at: string | null;
players: { player_id: number; username: string }[];