release/0.1.0 #18

Merged
ayoub merged 68 commits from release/0.1.0 into main 2026-06-11 18:36:42 +00:00
3 changed files with 63 additions and 22 deletions
Showing only changes of commit e490f23dec - Show all commits

View file

@ -1,7 +1,9 @@
import { useEffect, useRef } from "react"
import { AnimatePresence, motion } from "framer-motion"
import { Check, Crown } from "lucide-react"
import type { RoomSnapshot } from "@nerdware/shared"
import { Avatar } from "@/components/avatar"
import { celebrateLead } from "@/lib/confetti"
interface PlayerCardsProps {
snapshot: RoomSnapshot
@ -27,6 +29,18 @@ export function PlayerCards({
(a, b) => scoreOf(b.id) - scoreOf(a.id)
)
const voted = new Set(votedIds)
const leaderId = max > 0 ? ranked[0].id : null
// Confettis quand un joueur DÉPASSE pour prendre la tête (pas à la 1re prise).
const prevLeader = useRef<string | null>(null)
useEffect(() => {
if (leaderId && leaderId !== prevLeader.current) {
if (prevLeader.current !== null) {
celebrateLead()
}
prevLeader.current = leaderId
}
}, [leaderId])
return (
<div className="flex flex-wrap justify-center gap-2">

View file

@ -34,3 +34,15 @@ export function celebrate(): void {
}
frame()
}
/** Petite salve quand un joueur prend la tête en cours de partie. */
export function celebrateLead(): void {
confetti({
particleCount: 45,
spread: 70,
startVelocity: 30,
scalar: 0.8,
origin: { y: 0.25 },
colors: COLORS,
})
}

View file

@ -1,4 +1,5 @@
import { Link } from "wouter"
import { AnimatePresence, motion } from "framer-motion"
import { Button } from "@workspace/ui/components/button"
import { useRoomStore } from "@/store/room"
import { LobbyView } from "@/components/lobby-view"
@ -51,11 +52,14 @@ export function RoomPage({ code }: { code: string }) {
)
}
// Cards de scores visibles en permanence pendant le jeu (suivi continu).
const inGame =
snapshot.status === "in_round" ||
snapshot.status === "reveal" ||
snapshot.status === "scores"
// Phase grossière : on n'anime qu'aux frontières lobby ↔ jeu ↔ fin pour
// préserver le lecteur YouTube entre in_round / reveal / scores.
const phase =
snapshot.status === "lobby"
? "lobby"
: snapshot.status === "ended"
? "ended"
: "game"
return (
<div className="flex min-h-svh items-start justify-center p-6 pt-12">
@ -68,23 +72,34 @@ export function RoomPage({ code }: { code: string }) {
/>
</header>
{inGame && (
<PlayerCards
snapshot={snapshot}
playerId={playerId}
showVoteStatus={snapshot.status === "in_round"}
votedIds={voteProgress?.voted}
/>
)}
{snapshot.status === "lobby" && <LobbyView snapshot={snapshot} />}
{inGame &&
(round?.type === "blindtest" ? (
<BlindtestView snapshot={snapshot} />
) : (
<QuizView snapshot={snapshot} />
))}
{snapshot.status === "ended" && <GameEndView snapshot={snapshot} />}
<AnimatePresence mode="wait">
<motion.div
key={phase}
className="flex flex-col gap-6"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
transition={{ duration: 0.22, ease: "easeOut" }}
>
{phase === "lobby" && <LobbyView snapshot={snapshot} />}
{phase === "game" && (
<>
<PlayerCards
snapshot={snapshot}
playerId={playerId}
showVoteStatus={snapshot.status === "in_round"}
votedIds={voteProgress?.voted}
/>
{round?.type === "blindtest" ? (
<BlindtestView snapshot={snapshot} />
) : (
<QuizView snapshot={snapshot} />
)}
</>
)}
{phase === "ended" && <GameEndView snapshot={snapshot} />}
</motion.div>
</AnimatePresence>
</div>
{boomKey > 0 && <Boom key={boomKey} />}