feature/polish #9

Merged
ayoub merged 3 commits from feature/polish into dev 2026-06-10 19:22:06 +00:00
4 changed files with 55 additions and 1 deletions
Showing only changes of commit 7093ce8cd9 - Show all commits

View file

@ -18,6 +18,7 @@
"@nerdware/shared": "workspace:*", "@nerdware/shared": "workspace:*",
"@tanstack/react-query": "^5.101.0", "@tanstack/react-query": "^5.101.0",
"@workspace/ui": "workspace:*", "@workspace/ui": "workspace:*",
"canvas-confetti": "^1.9.4",
"framer-motion": "^11", "framer-motion": "^11",
"lucide-react": "^1.17.0", "lucide-react": "^1.17.0",
"react": "^19.2.6", "react": "^19.2.6",
@ -29,6 +30,7 @@
"devDependencies": { "devDependencies": {
"@eslint/js": "^10", "@eslint/js": "^10",
"@tailwindcss/vite": "^4", "@tailwindcss/vite": "^4",
"@types/canvas-confetti": "^1.9.0",
"@types/node": "^24", "@types/node": "^24",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",

View file

@ -1,4 +1,4 @@
import { useState } from "react" import { useEffect, useRef, useState } from "react"
import { Link } from "wouter" import { Link } from "wouter"
import { motion } from "framer-motion" import { motion } from "framer-motion"
import { Check, ClipboardList, Crown, Music } from "lucide-react" import { Check, ClipboardList, Crown, Music } from "lucide-react"
@ -7,6 +7,7 @@ import type { BlindtestTrackInfo, PlayerScore, RoomSnapshot } from "@nerdware/sh
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" import { Avatar } from "@/components/avatar"
import { celebrate } from "@/lib/confetti"
/** Lien de recherche Spotify (le titre vidéo contient en général artiste + chanson). */ /** Lien de recherche Spotify (le titre vidéo contient en général artiste + chanson). */
function spotifySearch(track: BlindtestTrackInfo): string { function spotifySearch(track: BlindtestTrackInfo): string {
@ -125,6 +126,15 @@ export function GameEndView({ snapshot }: { snapshot: RoomSnapshot }) {
const returnToLobby = useRoomStore((s) => s.returnToLobby) const returnToLobby = useRoomStore((s) => s.returnToLobby)
const isHost = snapshot.hostId === playerId const isHost = snapshot.hostId === playerId
// Confettis de victoire (une fois à l'arrivée sur l'écran de fin).
const celebrated = useRef(false)
useEffect(() => {
if (!celebrated.current) {
celebrated.current = true
celebrate()
}
}, [])
const scores: PlayerScore[] = finalScores ?? snapshot.scores const scores: PlayerScore[] = finalScores ?? snapshot.scores
const ranked = [...scores].sort((a, b) => b.score - a.score) const ranked = [...scores].sort((a, b) => b.score - a.score)
const nameOf = (id: string) => const nameOf = (id: string) =>

View file

@ -0,0 +1,36 @@
import confetti from "canvas-confetti"
const COLORS = ["#a855f7", "#ec4899", "#22d3ee", "#fbbf24", "#f97316"]
/** Salve de confettis de victoire : gros pop central + jets latéraux ~1s. */
export function celebrate(): void {
confetti({
particleCount: 140,
spread: 100,
startVelocity: 45,
origin: { y: 0.6 },
colors: COLORS,
})
const end = Date.now() + 900
const frame = () => {
confetti({
particleCount: 5,
angle: 60,
spread: 55,
origin: { x: 0 },
colors: COLORS,
})
confetti({
particleCount: 5,
angle: 120,
spread: 55,
origin: { x: 1 },
colors: COLORS,
})
if (Date.now() < end) {
requestAnimationFrame(frame)
}
}
frame()
}

View file

@ -44,6 +44,7 @@
"@nerdware/shared": "workspace:*", "@nerdware/shared": "workspace:*",
"@tanstack/react-query": "^5.101.0", "@tanstack/react-query": "^5.101.0",
"@workspace/ui": "workspace:*", "@workspace/ui": "workspace:*",
"canvas-confetti": "^1.9.4",
"framer-motion": "^11", "framer-motion": "^11",
"lucide-react": "^1.17.0", "lucide-react": "^1.17.0",
"react": "^19.2.6", "react": "^19.2.6",
@ -55,6 +56,7 @@
"devDependencies": { "devDependencies": {
"@eslint/js": "^10", "@eslint/js": "^10",
"@tailwindcss/vite": "^4", "@tailwindcss/vite": "^4",
"@types/canvas-confetti": "^1.9.0",
"@types/node": "^24", "@types/node": "^24",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
@ -584,6 +586,8 @@
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
"@types/canvas-confetti": ["@types/canvas-confetti@1.9.0", "", {}, "sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg=="],
"@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="], "@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="],
"@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="],
@ -690,6 +694,8 @@
"caniuse-lite": ["caniuse-lite@1.0.30001797", "", {}, "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w=="], "caniuse-lite": ["caniuse-lite@1.0.30001797", "", {}, "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w=="],
"canvas-confetti": ["canvas-confetti@1.9.4", "", {}, "sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw=="],
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
"chardet": ["chardet@2.1.1", "", {}, "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ=="], "chardet": ["chardet@2.1.1", "", {}, "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ=="],