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
parent 30c32b7714
commit 2c2f07c2ec
28 changed files with 1472 additions and 395 deletions
+9 -7
View File
@@ -20,25 +20,27 @@ export default function GameOver({ players, standings }: Props) {
const medals = ['🥇', '🥈', '🥉', ''];
return (
<div className="max-w-md mx-auto p-4 pt-12 flex flex-col items-center gap-6">
<h1 className="text-3xl font-bold">Koniec hry!</h1>
<div className="bg-slate-800 rounded-2xl w-full overflow-hidden">
<div className="max-w-md mx-auto p-4 pt-12 flex flex-col items-center gap-6 min-h-screen">
<h1 className="font-serif text-3xl text-gold tracking-wide">Koniec hry</h1>
<div className="w-full rounded-2xl overflow-hidden bg-header border border-[#142018]">
{totals.map((p, i) => (
<div
key={p.order}
className="flex items-center justify-between px-5 py-3 border-b border-slate-700 last:border-0"
className="flex items-center justify-between px-5 py-3 border-b border-gold/[.08] last:border-0"
>
<div className="flex items-center gap-3">
<span className="text-2xl w-8">{medals[i]}</span>
<span className="font-semibold">{p.name}</span>
<span className="font-serif text-green-score">{p.name}</span>
</div>
<span className="text-xl font-bold text-yellow-300">{p.total}</span>
<span className={`font-serif text-xl ${i === 0 ? 'text-gold-bright' : 'text-gold'}`}>
{p.total}
</span>
</div>
))}
</div>
<button
onClick={handleLeave}
className="w-full py-3 rounded-xl bg-blue-600 hover:bg-blue-500 font-bold text-lg"
className="w-full py-3 rounded-xl bg-gold text-table font-serif font-semibold text-lg hover:bg-gold-bright transition-colors"
>
Domov
</button>