feature/quiz-mode #4
4 changed files with 31 additions and 3 deletions
12
apps/web/src/components/avatar.tsx
Normal file
12
apps/web/src/components/avatar.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<img
|
||||||
|
src={url}
|
||||||
|
alt={`Avatar de ${seed}`}
|
||||||
|
loading="lazy"
|
||||||
|
className={`bg-muted shrink-0 rounded-full ${className ?? ""}`}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import { Crown } from "lucide-react"
|
||||||
import type { PlayerScore, RoomSnapshot } from "@nerdware/shared"
|
import type { PlayerScore, RoomSnapshot } from "@nerdware/shared"
|
||||||
import { Button } from "@workspace/ui/components/button"
|
import { Button } from "@workspace/ui/components/button"
|
||||||
import { useRoomStore } from "@/store/room"
|
import { useRoomStore } from "@/store/room"
|
||||||
|
import { Avatar } from "@/components/avatar"
|
||||||
|
|
||||||
export function GameEndView({ snapshot }: { snapshot: RoomSnapshot }) {
|
export function GameEndView({ snapshot }: { snapshot: RoomSnapshot }) {
|
||||||
const playerId = useRoomStore((s) => s.playerId)
|
const playerId = useRoomStore((s) => s.playerId)
|
||||||
|
|
@ -53,12 +54,17 @@ export function GameEndView({ snapshot }: { snapshot: RoomSnapshot }) {
|
||||||
initial={{ y: -28, rotate: -25, opacity: 0 }}
|
initial={{ y: -28, rotate: -25, opacity: 0 }}
|
||||||
animate={{ y: 0, rotate: 0, opacity: 1 }}
|
animate={{ y: 0, rotate: 0, opacity: 1 }}
|
||||||
transition={{ type: "spring", stiffness: 500, damping: 14, delay: 0.2 }}
|
transition={{ type: "spring", stiffness: 500, damping: 14, delay: 0.2 }}
|
||||||
className="absolute -top-5"
|
className="absolute -top-5 z-10"
|
||||||
>
|
>
|
||||||
<Crown className="size-9 fill-amber-400 text-amber-400 drop-shadow" />
|
<Crown className="size-9 fill-amber-400 text-amber-400 drop-shadow" />
|
||||||
</motion.span>
|
</motion.span>
|
||||||
|
|
||||||
<span className="font-heading mt-2 text-xl font-bold text-balance">
|
<Avatar
|
||||||
|
seed={nameOf(winner.playerId)}
|
||||||
|
className="size-20 ring-2 ring-amber-400"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span className="font-heading mt-1 text-xl font-bold text-balance">
|
||||||
{nameOf(winner.playerId)}
|
{nameOf(winner.playerId)}
|
||||||
{isMe(winner.playerId) && (
|
{isMe(winner.playerId) && (
|
||||||
<span className="text-muted-foreground text-sm"> (toi)</span>
|
<span className="text-muted-foreground text-sm"> (toi)</span>
|
||||||
|
|
@ -86,6 +92,7 @@ export function GameEndView({ snapshot }: { snapshot: RoomSnapshot }) {
|
||||||
<span className="text-muted-foreground w-5 text-right tabular-nums">
|
<span className="text-muted-foreground w-5 text-right tabular-nums">
|
||||||
{i + 2}
|
{i + 2}
|
||||||
</span>
|
</span>
|
||||||
|
<Avatar seed={nameOf(s.playerId)} className="size-7" />
|
||||||
<span>
|
<span>
|
||||||
{nameOf(s.playerId)}
|
{nameOf(s.playerId)}
|
||||||
{isMe(s.playerId) && (
|
{isMe(s.playerId) && (
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useState } from "react"
|
||||||
import type { RoomSnapshot } from "@nerdware/shared"
|
import type { RoomSnapshot } from "@nerdware/shared"
|
||||||
import { Button } from "@workspace/ui/components/button"
|
import { Button } from "@workspace/ui/components/button"
|
||||||
import { useRoomStore } from "@/store/room"
|
import { useRoomStore } from "@/store/room"
|
||||||
|
import { Avatar } from "@/components/avatar"
|
||||||
|
|
||||||
const QUESTION_OPTIONS = [3, 5, 10]
|
const QUESTION_OPTIONS = [3, 5, 10]
|
||||||
|
|
||||||
|
|
@ -38,7 +39,10 @@ export function LobbyView({ snapshot }: { snapshot: RoomSnapshot }) {
|
||||||
key={p.id}
|
key={p.id}
|
||||||
className="bg-muted/40 flex items-center justify-between rounded-md px-3 py-2 text-sm"
|
className="bg-muted/40 flex items-center justify-between rounded-md px-3 py-2 text-sm"
|
||||||
>
|
>
|
||||||
<span className={p.connected ? "" : "opacity-40"}>
|
<span
|
||||||
|
className={`flex items-center gap-2 ${p.connected ? "" : "opacity-40"}`}
|
||||||
|
>
|
||||||
|
<Avatar seed={p.name} className="size-7" />
|
||||||
{p.name}
|
{p.name}
|
||||||
{p.id === playerId && (
|
{p.id === playerId && (
|
||||||
<span className="text-muted-foreground"> (toi)</span>
|
<span className="text-muted-foreground"> (toi)</span>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { AnimatePresence, motion } from "framer-motion"
|
import { AnimatePresence, motion } from "framer-motion"
|
||||||
import { Check, Crown } from "lucide-react"
|
import { Check, Crown } from "lucide-react"
|
||||||
import type { RoomSnapshot } from "@nerdware/shared"
|
import type { RoomSnapshot } from "@nerdware/shared"
|
||||||
|
import { Avatar } from "@/components/avatar"
|
||||||
|
|
||||||
interface PlayerCardsProps {
|
interface PlayerCardsProps {
|
||||||
snapshot: RoomSnapshot
|
snapshot: RoomSnapshot
|
||||||
|
|
@ -58,6 +59,10 @@ export function PlayerCards({
|
||||||
<Crown className="size-4 fill-amber-400" />
|
<Crown className="size-4 fill-amber-400" />
|
||||||
</motion.span>
|
</motion.span>
|
||||||
)}
|
)}
|
||||||
|
<Avatar
|
||||||
|
seed={p.name}
|
||||||
|
className={`size-9 ${isLeader ? "ring-2 ring-amber-400" : ""}`}
|
||||||
|
/>
|
||||||
<span className="max-w-24 truncate text-xs font-medium">
|
<span className="max-w-24 truncate text-xs font-medium">
|
||||||
{p.name}
|
{p.name}
|
||||||
{isMe && <span className="text-muted-foreground"> (toi)</span>}
|
{isMe && <span className="text-muted-foreground"> (toi)</span>}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue