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>
+88 -12
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,25 +66,46 @@ export default function Podium({ data: { subject, top } }) {
}, 2000)
return () => clearInterval(interval)
}, [])
}, [apparition])
return (
<>
{apparition >= 4 && (
<ReactConfetti
width={width}
height={height}
className="h-full w-full"
/>
)}
{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={`grid max-w-[500px] flex-1 grid-cols-${top.length} w-full items-end justify-center justify-self-end overflow-x-visible`}
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`}
>
{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 },
"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,
},
)}
>
<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">
@@ -53,9 +121,9 @@ export default function Podium({ data: { subject, top } }) {
<div
className={clsx(
"z-30 flex h-[60%] 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 >= 3,
"!translate-y-0 opacity-100": apparition >= 3,
},
{
"md:min-w-64": top.length < 2,
@@ -64,8 +132,8 @@ export default function Podium({ data: { subject, top } }) {
>
<p
className={clsx(
"overflow-visible whitespace-nowrap text-center text-2xl font-bold text-white opacity-0 drop-shadow-lg",
{ "opacity-100": apparition >= 4 },
"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}
@@ -83,13 +151,20 @@ export default function Podium({ data: { subject, top } }) {
{top[2] && (
<div
className={clsx(
"z-10 flex h-[40%] w-full flex-col items-center gap-3 opacity-0",
"z-10 flex h-[40%] 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 >= 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,
},
)}
>
<p className="overflow-visible whitespace-nowrap text-center text-2xl font-bold text-white drop-shadow-lg">
{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">
@@ -105,5 +180,6 @@ export default function Podium({ data: { subject, top } }) {
)}
</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);