Merge pull request #1 from Ralex91/dev

Update Podium & Sound
This commit is contained in:
Ralex
2024-02-08 15:52:56 +01:00
committed by GitHub
22 changed files with 369 additions and 86 deletions
+38 -1
View File
@@ -11,10 +11,12 @@
"clsx": "^2.1.0",
"next": "14.1.0",
"react": "^18",
"react-confetti": "^6.1.0",
"react-dom": "^18",
"react-hot-toast": "^2.4.1",
"socket.io": "^4.7.4",
"socket.io-client": "^4.7.4"
"socket.io-client": "^4.7.4",
"use-sound": "^4.0.1"
},
"devDependencies": {
"autoprefixer": "^10.0.1",
@@ -2422,6 +2424,11 @@
"node": ">= 0.4"
}
},
"node_modules/howler": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/howler/-/howler-2.2.4.tgz",
"integrity": "sha512-iARIBPgcQrwtEr+tALF+rapJ8qSc+Set2GJQl7xT1MQzWaVkFebdJhR3alVlSiUf5U7nAANKuj3aWpwerocD5w=="
},
"node_modules/ignore": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
@@ -3823,6 +3830,20 @@
"node": ">=0.10.0"
}
},
"node_modules/react-confetti": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/react-confetti/-/react-confetti-6.1.0.tgz",
"integrity": "sha512-7Ypx4vz0+g8ECVxr88W9zhcQpbeujJAVqL14ZnXJ3I23mOI9/oBVTQ3dkJhUmB0D6XOtCZEM6N0Gm9PMngkORw==",
"dependencies": {
"tween-functions": "^1.2.0"
},
"engines": {
"node": ">=10.18"
},
"peerDependencies": {
"react": "^16.3.0 || ^17.0.1 || ^18.0.0"
}
},
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@@ -4610,6 +4631,11 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/tween-functions": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz",
"integrity": "sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA=="
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -4772,6 +4798,17 @@
"punycode": "^2.1.0"
}
},
"node_modules/use-sound": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/use-sound/-/use-sound-4.0.1.tgz",
"integrity": "sha512-hykJ86kNcu6y/FzlSHcQxhjSGMslZx2WlfLpZNoPbvueakv4OF3xPxEtGV2YmculrIaH0tPp9LtG4jgy17xMWg==",
"dependencies": {
"howler": "^2.1.3"
},
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+3 -1
View File
@@ -12,10 +12,12 @@
"clsx": "^2.1.0",
"next": "14.1.0",
"react": "^18",
"react-confetti": "^6.1.0",
"react-dom": "^18",
"react-hot-toast": "^2.4.1",
"socket.io": "^4.7.4",
"socket.io-client": "^4.7.4"
"socket.io-client": "^4.7.4",
"use-sound": "^4.0.1"
},
"devDependencies": {
"autoprefixer": "^10.0.1",
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -18,7 +18,7 @@ export const GAME_STATE_INIT = {
"Bill Gate",
],
solution: 1,
cooldow: 5,
cooldown: 5,
time: 15,
},
{
+5
View File
@@ -32,6 +32,11 @@ const Player = {
return
}
if (game.players.find((p) => p.username === player.username)) {
socket.emit("game:errorMessage", "Username already exists")
return
}
if (game.started) {
socket.emit("game:errorMessage", "Game already started")
return
+48 -10
View File
@@ -2,7 +2,14 @@ import AnswerButton from "../../AnswerButton"
import { useSocketContext } from "@/context/socket"
import { useEffect, useRef, useState } from "react"
import clsx from "clsx"
import { ANSWERS_COLORS, ANSWERS_ICONS } from "@/constants"
import {
ANSWERS_COLORS,
ANSWERS_ICONS,
SFX_ANSWERS_MUSIC,
SFX_ANSWERS_SOUND,
SFX_RESULTS_SOUND,
} from "@/constants"
import useSound from "use-sound"
const calculatePercentages = (objectResponses) => {
const keys = Object.keys(objectResponses)
@@ -35,13 +42,49 @@ export default function Answers({
const [cooldown, setCooldown] = useState(time)
const [totalAnswer, setTotalAnswer] = useState(0)
const [sfxPop] = useSound(SFX_ANSWERS_SOUND, {
volume: 0.1,
})
const [sfxResults] = useSound(SFX_RESULTS_SOUND, {
volume: 0.2,
})
const [playMusic, { stop: stopMusic, isPlaying }] = useSound(
SFX_ANSWERS_MUSIC,
{
volume: 0.2,
},
)
const handleAnswer = (answer) => {
socket.emit("player:selectedAnswer", answer)
sfxPop()
}
useEffect(() => {
if (!responses) {
playMusic()
return
}
stopMusic()
sfxResults()
setPercentages(calculatePercentages(responses))
}, [responses])
}, [responses, playMusic, stopMusic])
useEffect(() => {
if (!isPlaying) {
playMusic()
}
}, [isPlaying])
useEffect(() => {
return () => {
stopMusic()
}
}, [playMusic, stopMusic])
useEffect(() => {
socket.on("game:cooldown", (sec) => {
@@ -50,13 +93,14 @@ export default function Answers({
socket.on("game:playerAnswer", (count) => {
setTotalAnswer(count)
sfxPop()
})
return () => {
socket.off("game:cooldown")
socket.off("game:playerAnswer")
}
}, [])
}, [sfxPop])
return (
<div className="flex h-full flex-1 flex-col justify-between">
@@ -113,7 +157,7 @@ export default function Answers({
"opacity-65": responses && correct !== key,
})}
icon={ANSWERS_ICONS[key]}
onClick={() => socket.emit("player:selectedAnswer", key)}
onClick={() => handleAnswer(key)}
>
{answer}
</AnswerButton>
@@ -123,9 +167,3 @@ export default function Answers({
</div>
)
}
/* OLD Timer
<div className="absolute left-8 -translate-y-1/2 top-2/4 text-white font-bold text-6xl rounded-full justify-center items-center bg-orange-400 p-8 aspect-square hidden 2xl:flex">
<span </div>className="drop-shadow-md">20</span>
</div>
*/
+5 -2
View File
@@ -5,8 +5,11 @@ export default function Leaderboard({ data: { leaderboard } }) {
Leaderboard
</h2>
<div className="flex w-full flex-col gap-2">
{leaderboard.map(({ username, points }) => (
<div className="flex w-full justify-between rounded-md bg-primary p-3 text-2xl font-bold text-white">
{leaderboard.map(({ username, points }, key) => (
<div
key={key}
className="flex w-full justify-between rounded-md bg-primary p-3 text-2xl font-bold text-white"
>
<span className="drop-shadow-md">{username}</span>
<span className="drop-shadow-md">{points}</span>
</div>
+142 -66
View File
@@ -1,13 +1,60 @@
import Loader from "@/components/Loader"
import {
SFX_PODIUM_FIRST,
SFX_PODIUM_SECOND,
SFX_PODIUM_THREE,
SFX_SNEAR_ROOL,
} from "@/constants"
import useScreenSize from "@/hook/useScreenSize"
import clsx from "clsx"
import { useEffect, useState } from "react"
import ReactConfetti from "react-confetti"
import useSound from "use-sound"
export default function Podium({ data: { subject, top } }) {
const [apparition, setApparition] = useState(0)
const { width, height } = useScreenSize()
const [sfxtThree] = useSound(SFX_PODIUM_THREE, {
volume: 0.2,
})
const [sfxSecond] = useSound(SFX_PODIUM_SECOND, {
volume: 0.2,
})
const [sfxRool, { stop: sfxRoolStop }] = useSound(SFX_SNEAR_ROOL, {
volume: 0.2,
})
const [sfxFirst] = useSound(SFX_PODIUM_FIRST, {
volume: 0.2,
})
useEffect(() => {
console.log(apparition)
switch (apparition) {
case 4:
sfxRoolStop()
sfxFirst()
break
case 3:
sfxRool()
break
case 2:
sfxSecond()
break
case 1:
sfxtThree()
break
}
}, [apparition, sfxFirst, sfxSecond, sfxtThree, sfxRool])
useEffect(() => {
if (top.length < 3) {
setApparition(4)
return
}
const interval = setInterval(() => {
@@ -19,91 +66,120 @@ export default function Podium({ data: { subject, top } }) {
}, 2000)
return () => clearInterval(interval)
}, [])
}, [apparition])
return (
<section className="relative mx-auto flex w-full max-w-7xl flex-1 flex-col items-center justify-between">
<h2 className="anim-show text-center text-3xl font-bold text-white drop-shadow-lg md:text-4xl lg:text-5xl">
{subject}
</h2>
<>
{apparition >= 4 && (
<ReactConfetti
width={width}
height={height}
className="h-full w-full"
/>
)}
<div
className={`grid max-w-[500px] flex-1 grid-cols-${top.length} w-full items-end justify-center justify-self-end overflow-x-visible`}
>
{top[1] && (
<div
className={clsx(
"z-20 flex h-[50%] w-full flex-col items-center justify-center gap-3 opacity-0",
{ "opacity-100": apparition >= 2 },
)}
>
<p className="overflow-visible whitespace-nowrap text-center text-2xl font-bold text-white drop-shadow-lg">
{top[1].username}
</p>
<div className="flex h-full w-full flex-col items-center gap-4 rounded-t-md bg-primary pt-6 text-center shadow-2xl">
<p className="flex aspect-square h-14 items-center justify-center rounded-full border-4 border-zinc-400 bg-zinc-500 text-3xl font-bold text-white drop-shadow-lg">
<span className="drop-shadow-md">2</span>
</p>
<p className="text-2xl font-bold text-white drop-shadow-lg">
{top[1].points}
</p>
</div>
</div>
)}
{apparition >= 3 && top.length >= 3 && (
<div className="absolute min-h-screen w-full overflow-hidden">
<div className="spotlight"></div>{" "}
</div>
)}
<section className="relative mx-auto flex w-full max-w-7xl flex-1 flex-col items-center justify-between">
<h2 className="anim-show text-center text-3xl font-bold text-white drop-shadow-lg md:text-4xl lg:text-5xl">
{subject}
</h2>
<div
className={clsx(
"z-30 flex h-[60%] w-full flex-col items-center gap-3 opacity-0",
{
"opacity-100": apparition >= 3,
},
{
"md:min-w-64": top.length < 2,
},
)}
className={`grid w-full max-w-[800px] flex-1 grid-cols-${top.length} items-end justify-center justify-self-end overflow-y-hidden overflow-x-visible`}
>
<p
className={clsx(
"overflow-visible whitespace-nowrap text-center text-2xl font-bold text-white opacity-0 drop-shadow-lg",
{ "opacity-100": apparition >= 4 },
)}
>
{top[0].username}
</p>
<div className="flex h-full w-full flex-col items-center gap-4 rounded-t-md bg-primary pt-6 text-center shadow-2xl">
<p className="flex aspect-square h-14 items-center justify-center rounded-full border-4 border-amber-400 bg-amber-300 text-3xl font-bold text-white drop-shadow-lg">
<span className="drop-shadow-md">1</span>
</p>
<p className="text-2xl font-bold text-white drop-shadow-lg">
{top[0].points}
</p>
</div>
</div>
{top[1] && (
<div
className={clsx(
"z-20 flex h-[50%] w-full translate-y-full flex-col items-center justify-center gap-3 opacity-0 transition-all",
{ "!translate-y-0 opacity-100": apparition >= 2 },
)}
>
<p
className={clsx(
"overflow-visible whitespace-nowrap text-center text-2xl font-bold text-white drop-shadow-lg md:text-4xl",
{
"anim-balanced": apparition >= 4,
},
)}
>
{top[1].username}
</p>
<div className="flex h-full w-full flex-col items-center gap-4 rounded-t-md bg-primary pt-6 text-center shadow-2xl">
<p className="flex aspect-square h-14 items-center justify-center rounded-full border-4 border-zinc-400 bg-zinc-500 text-3xl font-bold text-white drop-shadow-lg">
<span className="drop-shadow-md">2</span>
</p>
<p className="text-2xl font-bold text-white drop-shadow-lg">
{top[1].points}
</p>
</div>
</div>
)}
{top[2] && (
<div
className={clsx(
"z-10 flex h-[40%] w-full flex-col items-center gap-3 opacity-0",
"z-30 flex h-[60%] w-full translate-y-full flex-col items-center gap-3 opacity-0 transition-all",
{
"opacity-100": apparition >= 1,
"!translate-y-0 opacity-100": apparition >= 3,
},
{
"md:min-w-64": top.length < 2,
},
)}
>
<p className="overflow-visible whitespace-nowrap text-center text-2xl font-bold text-white drop-shadow-lg">
{top[2].username}
<p
className={clsx(
"overflow-visible whitespace-nowrap text-center text-2xl font-bold text-white opacity-0 drop-shadow-lg md:text-4xl",
{ "anim-balanced opacity-100": apparition >= 4 },
)}
>
{top[0].username}
</p>
<div className="flex h-full w-full flex-col items-center gap-4 rounded-t-md bg-primary pt-6 text-center shadow-2xl">
<p className="flex aspect-square h-14 items-center justify-center rounded-full border-4 border-amber-800 bg-amber-700 text-3xl font-bold text-white drop-shadow-lg">
<span className="drop-shadow-md">3</span>
<p className="flex aspect-square h-14 items-center justify-center rounded-full border-4 border-amber-400 bg-amber-300 text-3xl font-bold text-white drop-shadow-lg">
<span className="drop-shadow-md">1</span>
</p>
<p className="text-2xl font-bold text-white drop-shadow-lg">
{top[2].points}
{top[0].points}
</p>
</div>
</div>
)}
</div>
</section>
{top[2] && (
<div
className={clsx(
"z-10 flex h-[40%] w-full translate-y-full flex-col items-center gap-3 opacity-0 transition-all",
{
"!translate-y-0 opacity-100": apparition >= 1,
},
)}
>
<p
className={clsx(
"overflow-visible whitespace-nowrap text-center text-2xl font-bold text-white drop-shadow-lg md:text-4xl",
{
"anim-balanced": apparition >= 4,
},
)}
>
{top[2].username}
</p>
<div className="flex h-full w-full flex-col items-center gap-4 rounded-t-md bg-primary pt-6 text-center shadow-2xl">
<p className="flex aspect-square h-14 items-center justify-center rounded-full border-4 border-amber-800 bg-amber-700 text-3xl font-bold text-white drop-shadow-lg">
<span className="drop-shadow-md">3</span>
</p>
<p className="text-2xl font-bold text-white drop-shadow-lg">
{top[2].points}
</p>
</div>
</div>
)}
</div>
</section>
</>
)
}
+8 -3
View File
@@ -1,7 +1,13 @@
import { useRef } from "react"
import { SFX_SHOW_SOUND } from "@/constants"
import { useEffect, useRef } from "react"
import useSound from "use-sound"
export default function Question({ data: { question, image, cooldown } }) {
const barRef = useRef(null)
const [sfxShow] = useSound(SFX_SHOW_SOUND, { volume: 0.5 })
useEffect(() => {
sfxShow()
}, [sfxShow])
return (
<section className="relative mx-auto flex h-full w-full max-w-7xl flex-1 flex-col items-center px-4">
@@ -15,7 +21,6 @@ export default function Question({ data: { question, image, cooldown } }) {
)}
</div>
<div
ref={barRef}
className="mb-20 h-4 self-start justify-self-end rounded-full bg-primary"
style={{ animation: `progressBar ${cooldown}s linear forwards` }}
></div>
+9 -1
View File
@@ -1,19 +1,27 @@
import CricleCheck from "@/components/icons/CricleCheck"
import CricleXmark from "@/components/icons/CricleXmark"
import { SFX_RESULTS_SOUND } from "@/constants"
import { usePlayerContext } from "@/context/player"
import { useEffect } from "react"
import useSound from "use-sound"
export default function Result({
data: { correct, message, points, myPoints, totalPlayer, rank, aheadOfMe },
}) {
const { dispatch } = usePlayerContext()
const [sfxResults] = useSound(SFX_RESULTS_SOUND, {
volume: 0.2,
})
useEffect(() => {
dispatch({
type: "UPDATE",
payload: { points: myPoints },
})
}, [])
sfxResults()
}, [sfxResults])
return (
<section className="anim-show relative mx-auto flex w-full max-w-7xl flex-1 flex-col items-center justify-center">
+9 -1
View File
@@ -1,18 +1,26 @@
import { SFX_BOUMP_SOUND } from "@/constants"
import { useSocketContext } from "@/context/socket"
import clsx from "clsx"
import { useEffect, useState } from "react"
import useSound from "use-sound"
export default function Start({ data: { time, subject } }) {
const { socket } = useSocketContext()
const [showTitle, setShowTitle] = useState(true)
const [cooldown, setCooldown] = useState(time)
const [sfxBoump] = useSound(SFX_BOUMP_SOUND, {
volume: 0.2,
})
useEffect(() => {
socket.on("game:startCooldown", () => {
sfxBoump()
setShowTitle(false)
})
socket.on("game:cooldown", (sec) => {
sfxBoump()
setCooldown(sec)
})
@@ -20,7 +28,7 @@ export default function Start({ data: { time, subject } }) {
socket.off("game:startCooldown")
socket.off("game:cooldown")
}
}, [])
}, [sfxBoump])
return (
<section className="relative mx-auto flex w-full max-w-7xl flex-1 flex-col items-center justify-center">
+10
View File
@@ -51,3 +51,13 @@ export const GAME_STATE_COMPONENTS_MANAGER = {
SHOW_LEADERBOARD: Leaderboard,
FINISH: Podium,
}
export const SFX_ANSWERS_MUSIC = "/sounds/answersMusic.mp3"
export const SFX_ANSWERS_SOUND = "/sounds/answersSound.mp3"
export const SFX_RESULTS_SOUND = "/sounds/results.mp3"
export const SFX_SHOW_SOUND = "/sounds/show.mp3"
export const SFX_BOUMP_SOUND = "/sounds/boump.mp3"
export const SFX_PODIUM_THREE = "/sounds/three.mp3"
export const SFX_PODIUM_SECOND = "/sounds/second.mp3"
export const SFX_PODIUM_FIRST = "/sounds/first.mp3"
export const SFX_SNEAR_ROOL = "/sounds/snearRoll.mp3"
+26
View File
@@ -0,0 +1,26 @@
import { useState, useEffect } from "react"
const useScreenSize = () => {
const [screenSize, setScreenSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
})
useEffect(() => {
const handleResize = () => {
setScreenSize({
width: window.innerWidth,
height: window.innerHeight,
})
}
window.addEventListener("resize", handleResize)
return () => {
window.removeEventListener("resize", handleResize)
}
}, [])
return screenSize
}
export default useScreenSize
+65
View File
@@ -32,6 +32,49 @@
box-shadow: rgba(0, 0, 0, 0.25) 0px -4px inset;
}
.spotlight {
position: absolute;
height: 200%;
width: 200%;
z-index: 100;
background-image: radial-gradient(
circle,
transparent 180px,
rgba(0, 0, 0, 0.6) 200px
);
opacity: 0;
left: -50%;
top: -50%;
transition: all 0.5s;
animation: spotlightAnim 2.5s ease-in;
}
@keyframes spotlightAnim {
0% {
left: -20%;
top: -20%;
}
30% {
opacity: 100;
top: -80%;
left: -80%;
}
60% {
top: -50%;
left: -20%;
}
80% {
top: -50%;
left: -50%;
}
98% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.anim-show {
animation: show 0.5s ease-out;
}
@@ -52,6 +95,28 @@
animation: quizzButton 0.8s ease-out;
}
.anim-balanced {
animation: balanced 0.8s linear infinite;
}
@keyframes balanced {
0% {
transform: rotate(0deg);
}
25% {
transform: rotate(-10deg) translateY(-10px);
}
50% {
transform: rotate(0deg) translateY(0px);
}
75% {
transform: rotate(10deg) translateY(-10px);
}
100% {
transform: rotate(0deg);
}
}
@keyframes show {
0% {
transform: scale(0);