Leaderboard avatar+level+XP bar; mobile table overlap fixes
Leaderboard: each row now shows the player avatar (photo or emoji) with a level badge ring and a progress-to-next-level bar (LeaderboardEntry gained levelProgress + avatarImage; mock fills real XP for you, random for others). Mobile table: the played-card pile now scales inward on narrow screens so it no longer overlaps the opponents' side stacks (trickScale by viewport); seat avatars render above the stacks (z-20) so the side player isn't hidden; side hands nudged to the edges + top hand raised slightly on phones. Verified: tsc + next build clean; web image rebuilt on :1500. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,9 @@ export function GameTable({
|
||||
const muted = !sfx && !music;
|
||||
|
||||
const exit = onExit ?? reset;
|
||||
const vw = useViewportWidth();
|
||||
// Pull the played-card pile inward on narrow screens so it clears the side stacks.
|
||||
const trickScale = vw < 360 ? 0.5 : vw < 460 ? 0.64 : 1;
|
||||
const { phase, players, hakem, trump, turn, currentTrick } = game;
|
||||
|
||||
const legalIds = new Set(
|
||||
@@ -149,12 +152,12 @@ export function GameTable({
|
||||
<SeatAvatar seat={3} className="absolute top-1/2 left-3 -translate-y-1/2" />
|
||||
|
||||
{/* opponents' face-down hands */}
|
||||
<OpponentHand seat={2} className="absolute top-20 left-1/2 -translate-x-1/2" horizontal />
|
||||
<OpponentHand seat={1} className="absolute top-1/2 right-16 -translate-y-1/2" />
|
||||
<OpponentHand seat={3} className="absolute top-1/2 left-16 -translate-y-1/2" />
|
||||
<OpponentHand seat={2} className="absolute top-16 sm:top-20 left-1/2 -translate-x-1/2" horizontal />
|
||||
<OpponentHand seat={1} className="absolute top-1/2 right-14 sm:right-16 -translate-y-1/2" />
|
||||
<OpponentHand seat={3} className="absolute top-1/2 left-14 sm:left-16 -translate-y-1/2" />
|
||||
|
||||
{/* center trick area */}
|
||||
<TrickArea trick={currentTrick} winner={game.lastTrickWinner} phase={phase} />
|
||||
{/* center trick area (offsets scale down on narrow screens) */}
|
||||
<TrickArea trick={currentTrick} winner={game.lastTrickWinner} phase={phase} scale={trickScale} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -273,7 +276,7 @@ function SeatAvatar({ seat, className }: { seat: Seat; className?: string }) {
|
||||
const name = sp?.name ?? player.name;
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col items-center gap-1", className)}>
|
||||
<div className={cn("z-20 flex flex-col items-center gap-1", className)}>
|
||||
<motion.div
|
||||
animate={
|
||||
active
|
||||
@@ -354,10 +357,12 @@ function TrickArea({
|
||||
trick,
|
||||
winner,
|
||||
phase,
|
||||
scale = 1,
|
||||
}: {
|
||||
trick: { seat: Seat; card: Card }[];
|
||||
winner: Seat | null;
|
||||
phase: string;
|
||||
scale?: number;
|
||||
}) {
|
||||
const { front } = useCardSkins();
|
||||
return (
|
||||
@@ -365,7 +370,7 @@ function TrickArea({
|
||||
<div className="relative size-1 ">
|
||||
<AnimatePresence>
|
||||
{trick.map((pc) => {
|
||||
const off = TRICK_OFFSET[pc.seat];
|
||||
const off = { x: TRICK_OFFSET[pc.seat].x * scale, y: TRICK_OFFSET[pc.seat].y * scale };
|
||||
const enter = TRICK_ENTER[pc.seat];
|
||||
const isWinner =
|
||||
phase === "trick-complete" && winner === pc.seat;
|
||||
|
||||
Reference in New Issue
Block a user