From 4f10d86fb0cf2c6ae860c3ba3f4cf9c59f492821 Mon Sep 17 00:00:00 2001 From: AyoubBenziza Date: Tue, 9 Jun 2026 10:50:17 +0200 Subject: [PATCH] feat(web): DiceBear lorelei avatars for players - Avatar component using DiceBear 9.x lorelei, seeded by the player name (stable, recognizable across screens) - shown in the lobby list, the in-game player cards (gold ring on leader), and the final leaderboard (large on the winner, small on runners-up) Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/web/src/components/avatar.tsx | 12 ++++++++++++ apps/web/src/components/game-end-view.tsx | 11 +++++++++-- apps/web/src/components/lobby-view.tsx | 6 +++++- apps/web/src/components/player-cards.tsx | 5 +++++ 4 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 apps/web/src/components/avatar.tsx diff --git a/apps/web/src/components/avatar.tsx b/apps/web/src/components/avatar.tsx new file mode 100644 index 0000000..b38dbda --- /dev/null +++ b/apps/web/src/components/avatar.tsx @@ -0,0 +1,12 @@ +/** Avatar DiceBear (style lorelei), seedé par le pseudo → stable et reconnaissable. */ +export function Avatar({ seed, className }: { seed: string; className?: string }) { + const url = `https://api.dicebear.com/9.x/lorelei/svg?seed=${encodeURIComponent(seed)}` + return ( + {`Avatar + ) +} diff --git a/apps/web/src/components/game-end-view.tsx b/apps/web/src/components/game-end-view.tsx index fb85092..d79fdfe 100644 --- a/apps/web/src/components/game-end-view.tsx +++ b/apps/web/src/components/game-end-view.tsx @@ -4,6 +4,7 @@ import { Crown } from "lucide-react" import type { PlayerScore, RoomSnapshot } from "@nerdware/shared" import { Button } from "@workspace/ui/components/button" import { useRoomStore } from "@/store/room" +import { Avatar } from "@/components/avatar" export function GameEndView({ snapshot }: { snapshot: RoomSnapshot }) { const playerId = useRoomStore((s) => s.playerId) @@ -53,12 +54,17 @@ export function GameEndView({ snapshot }: { snapshot: RoomSnapshot }) { initial={{ y: -28, rotate: -25, opacity: 0 }} animate={{ y: 0, rotate: 0, opacity: 1 }} transition={{ type: "spring", stiffness: 500, damping: 14, delay: 0.2 }} - className="absolute -top-5" + className="absolute -top-5 z-10" > - + + + {nameOf(winner.playerId)} {isMe(winner.playerId) && ( (toi) @@ -86,6 +92,7 @@ export function GameEndView({ snapshot }: { snapshot: RoomSnapshot }) { {i + 2} + {nameOf(s.playerId)} {isMe(s.playerId) && ( diff --git a/apps/web/src/components/lobby-view.tsx b/apps/web/src/components/lobby-view.tsx index d4c5eab..8093e7f 100644 --- a/apps/web/src/components/lobby-view.tsx +++ b/apps/web/src/components/lobby-view.tsx @@ -2,6 +2,7 @@ import { useState } from "react" import type { RoomSnapshot } from "@nerdware/shared" import { Button } from "@workspace/ui/components/button" import { useRoomStore } from "@/store/room" +import { Avatar } from "@/components/avatar" const QUESTION_OPTIONS = [3, 5, 10] @@ -38,7 +39,10 @@ export function LobbyView({ snapshot }: { snapshot: RoomSnapshot }) { key={p.id} className="bg-muted/40 flex items-center justify-between rounded-md px-3 py-2 text-sm" > - + + {p.name} {p.id === playerId && ( (toi) diff --git a/apps/web/src/components/player-cards.tsx b/apps/web/src/components/player-cards.tsx index bab3677..3b9dff8 100644 --- a/apps/web/src/components/player-cards.tsx +++ b/apps/web/src/components/player-cards.tsx @@ -1,6 +1,7 @@ import { AnimatePresence, motion } from "framer-motion" import { Check, Crown } from "lucide-react" import type { RoomSnapshot } from "@nerdware/shared" +import { Avatar } from "@/components/avatar" interface PlayerCardsProps { snapshot: RoomSnapshot @@ -58,6 +59,10 @@ export function PlayerCards({ )} + {p.name} {isMe && (toi)}