import { useEffect, useState } from "react" import { motion } from "framer-motion" import { Disc3, Pause, Play, Rewind } from "lucide-react" import type { BlindtestAnswer, BlindtestMode, BlindtestPerPlayerResult, BlindtestRevealTruth, BlindtestRoundPayload, RoomSnapshot, } from "@nerdware/shared" import { Button } from "@workspace/ui/components/button" import { useRoomStore } from "@/store/room" import { useYoutube, type YoutubeApi } from "@/lib/youtube" import { Countdown } from "@/components/countdown" import { Avatar } from "@/components/avatar" const inputClass = "border-input bg-background h-10 w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none disabled:opacity-50" function fmt(s: number): string { const m = Math.floor(s / 60) const sec = Math.floor(s % 60) return `${m}:${sec.toString().padStart(2, "0")}` } export function BlindtestView({ snapshot }: { snapshot: RoomSnapshot }) { const round = useRoomStore((s) => s.round) const reveal = useRoomStore((s) => s.reveal) const mediaSync = useRoomStore((s) => s.mediaSync) const playerId = useRoomStore((s) => s.playerId) const hasVoted = useRoomStore((s) => s.hasVoted) const voteBlindtest = useRoomStore((s) => s.voteBlindtest) const mediaControl = useRoomStore((s) => s.mediaControl) const track = round?.payload as BlindtestRoundPayload | undefined const { hostRef, ready, api } = useYoutube(track?.youtubeId ?? "") const isDj = !!playerId && round?.djId === playerId const truth = reveal ? (reveal.truth as BlindtestRevealTruth) : null const showReveal = truth !== null // Non-DJ : on suit le DJ via media:sync (avec compensation de latence). useEffect(() => { if (isDj || !ready || !mediaSync) { return } const elapsed = (Date.now() - mediaSync.atServerTs) / 1000 if (mediaSync.action === "play") { api.seek(mediaSync.positionSec + Math.max(0, elapsed)) api.play() } else if (mediaSync.action === "pause") { api.seek(mediaSync.positionSec) api.pause() } else { api.seek(mediaSync.positionSec) } }, [mediaSync, isDj, ready, api]) // Au reveal, on coupe le son. useEffect(() => { if (showReveal && ready) { api.pause() } }, [showReveal, ready, api]) return (
Blindtest {snapshot.currentRound + 1} / {snapshot.totalRounds} {!showReveal && round && ( )}
{/* Cadre 16:9 : le lecteur YouTube est caché derrière le disque pendant la manche (son seulement), puis révélé (bannière visible) au reveal. */}
{!showReveal && (
)}
{isDj && !showReveal && ( )} {!isDj && !showReveal && ( )} {showReveal && truth && ( )}
) } function DjControls({ ready, api, mediaControl, }: { ready: boolean api: YoutubeApi mediaControl: (action: "play" | "pause" | "seek", positionSec: number) => void }) { const [pos, setPos] = useState(0) const [dur, setDur] = useState(0) useEffect(() => { if (!ready) { return } const id = setInterval(() => { setPos(api.time()) setDur(api.duration()) }, 400) return () => clearInterval(id) }, [ready, api]) return (
Tu es le DJ 🎧 — c'est ton titre, fais-le deviner 🤫
{fmt(pos)} { const v = Number(e.target.value) setPos(v) api.seek(v) mediaControl("seek", v) }} className="accent-primary flex-1" /> {fmt(dur)}
) } function VoteForm({ mode, snapshot, playerId, disabled, onVote, }: { mode: BlindtestMode snapshot: RoomSnapshot playerId: string | null disabled: boolean onVote: (answer: BlindtestAnswer) => void }) { const [title, setTitle] = useState("") const [artist, setArtist] = useState("") const [guessed, setGuessed] = useState(null) const needsText = mode === "title_artist" || mode === "mixed" const needsWho = mode === "who_added" || mode === "mixed" const canSubmit = !disabled && (!needsText || title.trim().length > 0) && (!needsWho || guessed !== null) function submit() { const answer: { title?: string artist?: string guessedPlayerId?: string } = {} if (needsText) { answer.title = title.trim() answer.artist = artist.trim() } if (needsWho && guessed) { answer.guessedPlayerId = guessed } onVote(answer as BlindtestAnswer) } if (disabled) { return (

Réponse envoyée — en attente des autres

) } return (
{needsText && ( <> setTitle(e.target.value)} /> setArtist(e.target.value)} /> )} {needsWho && (
{snapshot.players.map((p) => ( ))}
)}
) } function RevealCard({ truth, isDj, result, }: { truth: BlindtestRevealTruth isDj: boolean result?: BlindtestPerPlayerResult[string] }) { return (

{truth.title}

{truth.artist}

Ajouté par {truth.submittedByName}

{result && (result.points > 0 || !isDj) && (

0 ? "text-green-500" : "text-red-500"}`} > {result.points > 0 ? `+${result.points} points ${isDj ? "🤫" : "🎉"}` : "Raté 💥"}

)}
) }