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

39
package-lock.json generated
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",

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.

BIN
public/sounds/boump.mp3 Normal file

Binary file not shown.

BIN
public/sounds/first.mp3 Normal file

Binary file not shown.

BIN
public/sounds/results.mp3 Normal file

Binary file not shown.

BIN
public/sounds/second.mp3 Normal file

Binary file not shown.

BIN
public/sounds/show.mp3 Normal file

Binary file not shown.

BIN
public/sounds/snearRoll.mp3 Normal file

Binary file not shown.

BIN
public/sounds/three.mp3 Normal file

Binary file not shown.

View File

@@ -18,7 +18,7 @@ export const GAME_STATE_INIT = {
"Bill Gate",
],
solution: 1,
cooldow: 5,
cooldown: 5,
time: 15,
},
{

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

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>
*/

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>

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>
</>
)
}

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>

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">

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">

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
src/hook/useScreenSize.js Normal file
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

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);