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 { 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" import { Avatar } from "@/components/avatar"
import { celebrateLead } from "@/lib/confetti"
interface PlayerCardsProps { interface PlayerCardsProps {
snapshot: RoomSnapshot snapshot: RoomSnapshot
@ -27,6 +29,18 @@ export function PlayerCards({
(a, b) => scoreOf(b.id) - scoreOf(a.id) (a, b) => scoreOf(b.id) - scoreOf(a.id)
) )
const voted = new Set(votedIds) 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 ( return (
<div className="flex flex-wrap justify-center gap-2"> <div className="flex flex-wrap justify-center gap-2">

View file

@ -34,3 +34,15 @@ export function celebrate(): void {
} }
frame() 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 { Link } from "wouter"
import { AnimatePresence, motion } from "framer-motion"
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 { LobbyView } from "@/components/lobby-view" 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). // Phase grossière : on n'anime qu'aux frontières lobby ↔ jeu ↔ fin pour
const inGame = // préserver le lecteur YouTube entre in_round / reveal / scores.
snapshot.status === "in_round" || const phase =
snapshot.status === "reveal" || snapshot.status === "lobby"
snapshot.status === "scores" ? "lobby"
: snapshot.status === "ended"
? "ended"
: "game"
return ( return (
<div className="flex min-h-svh items-start justify-center p-6 pt-12"> <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> </header>
{inGame && ( <AnimatePresence mode="wait">
<PlayerCards <motion.div
snapshot={snapshot} key={phase}
playerId={playerId} className="flex flex-col gap-6"
showVoteStatus={snapshot.status === "in_round"} initial={{ opacity: 0, y: 12 }}
votedIds={voteProgress?.voted} animate={{ opacity: 1, y: 0 }}
/> exit={{ opacity: 0, y: -12 }}
)} transition={{ duration: 0.22, ease: "easeOut" }}
>
{snapshot.status === "lobby" && <LobbyView snapshot={snapshot} />} {phase === "lobby" && <LobbyView snapshot={snapshot} />}
{inGame && {phase === "game" && (
(round?.type === "blindtest" ? ( <>
<BlindtestView snapshot={snapshot} /> <PlayerCards
) : ( snapshot={snapshot}
<QuizView snapshot={snapshot} /> playerId={playerId}
))} showVoteStatus={snapshot.status === "in_round"}
{snapshot.status === "ended" && <GameEndView snapshot={snapshot} />} votedIds={voteProgress?.voted}
/>
{round?.type === "blindtest" ? (
<BlindtestView snapshot={snapshot} />
) : (
<QuizView snapshot={snapshot} />
)}
</>
)}
{phase === "ended" && <GameEndView snapshot={snapshot} />}
</motion.div>
</AnimatePresence>
</div> </div>
{boomKey > 0 && <Boom key={boomKey} />} {boomKey > 0 && <Boom key={boomKey} />}