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:
@@ -0,0 +1,56 @@
|
||||
interface Props {
|
||||
name: string;
|
||||
/** Tricks won this round. */
|
||||
won: number;
|
||||
/** Bid for this round (null until the player has guessed). */
|
||||
guess: number | null;
|
||||
/** Whether it is this player's turn — the only state that highlights a circle. */
|
||||
active: boolean;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export default function PlayerCircle({ name, won, guess, active, size = 52 }: Props) {
|
||||
const nameFont = Math.max(9, Math.round(size * 0.17));
|
||||
const valueFont = Math.round(size * 0.32);
|
||||
// Oval: width = size, height a touch shorter so it reads as an ellipse.
|
||||
const height = Math.round(size * 0.78);
|
||||
|
||||
return (
|
||||
<div
|
||||
// Only the active player is highlighted (gold ring + glow) — colors come
|
||||
// from the velvet-table palette tokens (tailwind.config.js), not literals.
|
||||
className={`flex flex-col items-center justify-center rounded-[50%] ${
|
||||
active ? 'bg-circle-active border-2 border-gold' : 'bg-circle border-[1.5px] border-gold/20'
|
||||
}`}
|
||||
style={{
|
||||
width: size,
|
||||
height,
|
||||
boxShadow: '0 2px 10px rgba(0,0,0,.45)',
|
||||
animation: active ? 'ar 2.2s ease-in-out infinite' : undefined,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={`uppercase leading-tight text-center ${active ? 'text-gold' : 'text-green-circle'}`}
|
||||
style={{
|
||||
fontFamily: '"DM Sans",sans-serif',
|
||||
fontSize: nameFont,
|
||||
letterSpacing: '.09em',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
<span
|
||||
className={`leading-none ${active ? 'text-gold-bright' : 'text-gold'}`}
|
||||
style={{
|
||||
fontFamily: '"Playfair Display",serif',
|
||||
fontSize: valueFont,
|
||||
fontWeight: active ? 700 : 400,
|
||||
}}
|
||||
>
|
||||
{won}
|
||||
<span style={{ fontSize: valueFont * 0.6, color: '#b0a585' }}>/{guess ?? '?'}</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user