Compare commits
2 Commits
7a581749a9
...
8e418021ae
Author | SHA1 | Date | |
---|---|---|---|
8e418021ae | |||
7d5813b1c2 |
@ -1,13 +1,22 @@
|
|||||||
import Home from "./pages/Home";
|
import Game from "./pages/Game";
|
||||||
import {useContext} from "react";
|
import {useContext} from "react";
|
||||||
import {StateContext} from "@/common/contexts/StateContext";
|
import {StateContext} from "@/common/contexts/StateContext";
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
|
import {faMusic} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import Home from "@/pages/Home";
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const {currentState} = useContext(StateContext);
|
const {currentState} = useContext(StateContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div className="background-elements">
|
||||||
|
<FontAwesomeIcon icon={faMusic} className="music-note note-1" />
|
||||||
|
<FontAwesomeIcon icon={faMusic} className="music-note note-2" />
|
||||||
|
<FontAwesomeIcon icon={faMusic} className="music-note note-3" />
|
||||||
|
</div>
|
||||||
{currentState === "Home" && <Home />}
|
{currentState === "Home" && <Home />}
|
||||||
|
{currentState === "Game" && <Game />}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
$background: rgba(255, 255, 255, 0.14)
|
||||||
|
$border: rgba(255, 255, 255, 0.35)
|
||||||
|
|
||||||
|
$white: #ECECEC
|
||||||
|
|
||||||
|
$green: #26EE5E
|
@ -3,15 +3,58 @@
|
|||||||
*
|
*
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
|
|
||||||
body, html
|
body
|
||||||
margin: 0
|
margin: 0
|
||||||
padding: 0
|
padding: 0
|
||||||
background-color: #232529
|
|
||||||
letter-spacing: 0.2rem
|
letter-spacing: 0.2rem
|
||||||
color: #E0E0E0
|
color: #E0E0E0
|
||||||
height: 100%
|
|
||||||
font-family: 'Bangers', sans-serif
|
font-family: 'Bangers', sans-serif
|
||||||
|
height: 100vh
|
||||||
|
background: linear-gradient(135deg, #0f2027, #203a43, #2c5364)
|
||||||
|
user-select: none
|
||||||
|
overflow: hidden
|
||||||
|
position: relative
|
||||||
|
|
||||||
|
.glassy
|
||||||
|
background: $background
|
||||||
|
backdrop-filter: blur(10px)
|
||||||
|
border: 2px solid $border
|
||||||
|
border-radius: 0.8rem
|
||||||
|
|
||||||
::-webkit-scrollbar
|
::-webkit-scrollbar
|
||||||
width: 10px
|
width: 10px
|
||||||
|
|
||||||
|
.background-elements
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
overflow: hidden
|
||||||
|
z-index: 0
|
||||||
|
|
||||||
|
.music-note
|
||||||
|
position: absolute
|
||||||
|
font-size: 24pt
|
||||||
|
color: rgba(255, 255, 255, 0.5)
|
||||||
|
animation: float-notes 5s infinite ease-in-out
|
||||||
|
|
||||||
|
&.note-1
|
||||||
|
top: 20%
|
||||||
|
left: 30%
|
||||||
|
|
||||||
|
&.note-2
|
||||||
|
top: 60%
|
||||||
|
left: 80%
|
||||||
|
|
||||||
|
&.note-3
|
||||||
|
top: 90%
|
||||||
|
left: 50%
|
||||||
|
|
||||||
|
@keyframes float-notes
|
||||||
|
0%
|
||||||
|
transform: translateY(0)
|
||||||
|
50%
|
||||||
|
transform: translateY(-10px)
|
||||||
|
100%
|
||||||
|
transform: translateY(0)
|
66
client/src/pages/Game/Game.jsx
Normal file
66
client/src/pages/Game/Game.jsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import "./styles.sass";
|
||||||
|
import {StateContext} from "@/common/contexts/StateContext";
|
||||||
|
import {useContext, useState, useEffect, useRef} from "react";
|
||||||
|
import MusicSlider from "@/pages/Game/components/MusicSlider";
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
|
import {faMessage} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
export const Game = () => {
|
||||||
|
const {setCurrentState} = useContext(StateContext);
|
||||||
|
const [messages, setMessages] = useState([{sender: "Marco", text: "Hallo!"},]);
|
||||||
|
const [inputValue, setInputValue] = useState("");
|
||||||
|
const messageEndRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
messageEndRef.current?.scrollIntoView({behavior: "smooth"});
|
||||||
|
}, [messages]);
|
||||||
|
|
||||||
|
const handleSendMessage = () => {
|
||||||
|
if (inputValue.trim()) {
|
||||||
|
setMessages([...messages, {sender: "User", text: inputValue}]);
|
||||||
|
setInputValue("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="game-page">
|
||||||
|
<div className="main-content">
|
||||||
|
<div className="song-display">
|
||||||
|
<h2>ToneGuessr</h2>
|
||||||
|
<div className="song-card">
|
||||||
|
<img
|
||||||
|
src="https://mir-s3-cdn-cf.behance.net/project_modules/1400/fe529a64193929.5aca8500ba9ab.jpg"
|
||||||
|
alt="Song"/>
|
||||||
|
<div className="song-info">
|
||||||
|
<div className="song-names">Black Steam</div>
|
||||||
|
<div className="song-description">von Carrot Quest GmbH</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="chat-window">
|
||||||
|
<div className="chat-header">
|
||||||
|
<FontAwesomeIcon icon={faMessage} />
|
||||||
|
<div className="chat-title">Chat</div>
|
||||||
|
</div>
|
||||||
|
<div className="chat-messages">
|
||||||
|
{messages.map((message, index) => (
|
||||||
|
<div key={index} className="message">
|
||||||
|
<span className="message-sender">{message.sender}:</span>
|
||||||
|
<span className="message-text">{message.text}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div ref={messageEndRef}></div>
|
||||||
|
</div>
|
||||||
|
<div className="chat-input">
|
||||||
|
<input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)}
|
||||||
|
onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
|
||||||
|
placeholder="Gib eine Nachricht ein..."
|
||||||
|
/>
|
||||||
|
<button onClick={handleSendMessage}>Send</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MusicSlider/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
108
client/src/pages/Game/components/MusicSlider/MusicSlider.jsx
Normal file
108
client/src/pages/Game/components/MusicSlider/MusicSlider.jsx
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import {useEffect, useRef, useState} from "react";
|
||||||
|
import "./styles.sass";
|
||||||
|
|
||||||
|
export const MusicSlider = () => {
|
||||||
|
const [frequency, setFrequency] = useState(440);
|
||||||
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
|
const [dragging, setDragging] = useState(false);
|
||||||
|
const audioContextRef = useRef(null);
|
||||||
|
const oscillatorRef = useRef(null);
|
||||||
|
const gainNodeRef = useRef(null);
|
||||||
|
const sliderRef = useRef(null);
|
||||||
|
|
||||||
|
const handleMouseDown = (e) => {
|
||||||
|
setDragging(true);
|
||||||
|
startAudio();
|
||||||
|
handleFrequencyChange(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
setDragging(false);
|
||||||
|
stopAudio();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFrequencyChange = (e) => {
|
||||||
|
const rect = sliderRef.current.getBoundingClientRect();
|
||||||
|
const x = e.clientX - rect.left;
|
||||||
|
const clampedX = Math.max(0, Math.min(x, rect.width));
|
||||||
|
const newFrequency = 20 + (clampedX / rect.width) * 1980;
|
||||||
|
setFrequency(newFrequency);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleMouseMove = (e) => dragging && handleFrequencyChange(e);
|
||||||
|
const handleMouseUpGlobal = () => dragging && handleMouseUp();
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', handleMouseMove);
|
||||||
|
document.addEventListener('mouseup', handleMouseUpGlobal);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
document.removeEventListener('mouseup', handleMouseUpGlobal);
|
||||||
|
};
|
||||||
|
}, [dragging]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isPlaying) {
|
||||||
|
if (oscillatorRef.current) {
|
||||||
|
oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [frequency, isPlaying]);
|
||||||
|
|
||||||
|
const startAudio = () => {
|
||||||
|
if (!audioContextRef.current) {
|
||||||
|
audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
}
|
||||||
|
if (!oscillatorRef.current) {
|
||||||
|
oscillatorRef.current = audioContextRef.current.createOscillator();
|
||||||
|
oscillatorRef.current.type = 'sine';
|
||||||
|
oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime);
|
||||||
|
|
||||||
|
gainNodeRef.current = audioContextRef.current.createGain();
|
||||||
|
gainNodeRef.current.gain.setValueAtTime(0.5, audioContextRef.current.currentTime);
|
||||||
|
|
||||||
|
oscillatorRef.current.connect(gainNodeRef.current);
|
||||||
|
gainNodeRef.current.connect(audioContextRef.current.destination);
|
||||||
|
oscillatorRef.current.start();
|
||||||
|
} else {
|
||||||
|
oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime);
|
||||||
|
}
|
||||||
|
setIsPlaying(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopAudio = () => {
|
||||||
|
if (oscillatorRef.current) {
|
||||||
|
oscillatorRef.current.stop();
|
||||||
|
oscillatorRef.current.disconnect();
|
||||||
|
oscillatorRef.current = null;
|
||||||
|
}
|
||||||
|
setIsPlaying(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="otamatone-container">
|
||||||
|
<div className="otamatone" onMouseDown={handleMouseDown}>
|
||||||
|
<div className="otamatone-neck" ref={sliderRef}>
|
||||||
|
<div className="frequency-indicator" style={{left: `${(frequency - 20) / 1980 * 100}%`}}></div>
|
||||||
|
<div className="note-marker" style={{left: '10%', pointerEvents: 'none'}}>♩</div>
|
||||||
|
<div className="note-marker" style={{left: '50%', pointerEvents: 'none'}}>♪</div>
|
||||||
|
<div className="note-marker" style={{left: '90%', pointerEvents: 'none'}}>♫</div>
|
||||||
|
</div>
|
||||||
|
<div className="otamatone-face">
|
||||||
|
<div
|
||||||
|
className="otamatone-mouth"
|
||||||
|
style={{
|
||||||
|
height: `${10 + (frequency / 2000) * 40}px`,
|
||||||
|
width: `${10 + (frequency / 2000) * 40}px`
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<div className="otamatone-eyes">
|
||||||
|
<div className="eye left-eye"></div>
|
||||||
|
<div className="eye right-eye"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
1
client/src/pages/Game/components/MusicSlider/index.js
Normal file
1
client/src/pages/Game/components/MusicSlider/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {MusicSlider as default} from "./MusicSlider.jsx";
|
82
client/src/pages/Game/components/MusicSlider/styles.sass
Normal file
82
client/src/pages/Game/components/MusicSlider/styles.sass
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
.otamatone-container
|
||||||
|
display: flex
|
||||||
|
justify-content: center
|
||||||
|
align-items: center
|
||||||
|
position: fixed
|
||||||
|
left: 0
|
||||||
|
right: 0
|
||||||
|
bottom: 0
|
||||||
|
padding: 20px
|
||||||
|
background-color: rgba(30, 30, 30, 0.5)
|
||||||
|
backdrop-filter: blur(10px)
|
||||||
|
border-radius: 20px
|
||||||
|
margin: 20px
|
||||||
|
z-index: 1
|
||||||
|
|
||||||
|
.otamatone
|
||||||
|
display: flex
|
||||||
|
flex-direction: row
|
||||||
|
align-items: center
|
||||||
|
cursor: pointer
|
||||||
|
padding: 10px
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
.otamatone-neck
|
||||||
|
flex: 1
|
||||||
|
height: 20px
|
||||||
|
background: linear-gradient(135deg, #000, #444)
|
||||||
|
border-radius: 10px
|
||||||
|
position: relative
|
||||||
|
|
||||||
|
.frequency-indicator
|
||||||
|
position: absolute
|
||||||
|
top: -10px
|
||||||
|
width: 20px
|
||||||
|
height: 20px
|
||||||
|
background-color: #ff0000
|
||||||
|
border-radius: 50%
|
||||||
|
transform: translateX(-50%)
|
||||||
|
|
||||||
|
.note-marker
|
||||||
|
position: absolute
|
||||||
|
top: -30px
|
||||||
|
font-size: 24pt
|
||||||
|
color: #fff
|
||||||
|
|
||||||
|
.otamatone-face
|
||||||
|
width: 100px
|
||||||
|
height: 100px
|
||||||
|
background: radial-gradient(circle, #fff, #ddd)
|
||||||
|
border-radius: 50%
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
align-items: center
|
||||||
|
justify-content: center
|
||||||
|
position: absolute
|
||||||
|
left: 0
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2)
|
||||||
|
|
||||||
|
.otamatone-eyes
|
||||||
|
display: flex
|
||||||
|
justify-content: space-between
|
||||||
|
width: 60%
|
||||||
|
position: absolute
|
||||||
|
top: 30px
|
||||||
|
|
||||||
|
.eye
|
||||||
|
width: 15px
|
||||||
|
height: 15px
|
||||||
|
background-color: #000
|
||||||
|
border-radius: 50%
|
||||||
|
|
||||||
|
.left-eye
|
||||||
|
margin-right: 10px
|
||||||
|
|
||||||
|
.right-eye
|
||||||
|
margin-left: 10px
|
||||||
|
|
||||||
|
.otamatone-mouth
|
||||||
|
background-color: #000
|
||||||
|
border-radius: 50%
|
||||||
|
position: absolute
|
||||||
|
bottom: 10px
|
1
client/src/pages/Game/index.js
Normal file
1
client/src/pages/Game/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {Game as default} from "./Game.jsx";
|
129
client/src/pages/Game/styles.sass
Normal file
129
client/src/pages/Game/styles.sass
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
.game-page
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
align-items: center
|
||||||
|
justify-content: center
|
||||||
|
height: 100vh
|
||||||
|
padding: 20px
|
||||||
|
|
||||||
|
.main-content
|
||||||
|
display: flex
|
||||||
|
flex-direction: row
|
||||||
|
align-items: flex-start
|
||||||
|
justify-content: center
|
||||||
|
width: 100%
|
||||||
|
padding: 20px
|
||||||
|
z-index: 1
|
||||||
|
|
||||||
|
.song-display
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
align-items: center
|
||||||
|
justify-content: center
|
||||||
|
width: 50%
|
||||||
|
margin-right: 20px
|
||||||
|
color: #fff
|
||||||
|
text-align: center
|
||||||
|
|
||||||
|
h2
|
||||||
|
font-size: 36pt
|
||||||
|
color: #fff
|
||||||
|
margin-bottom: 20px
|
||||||
|
|
||||||
|
.song-card
|
||||||
|
display: flex
|
||||||
|
flex-direction: row
|
||||||
|
align-items: center
|
||||||
|
background-color: rgba(255, 255, 255, 0.1)
|
||||||
|
padding: 20px
|
||||||
|
border-radius: 20px
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5)
|
||||||
|
backdrop-filter: blur(10px)
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2)
|
||||||
|
max-width: 500px
|
||||||
|
|
||||||
|
img
|
||||||
|
width: 100px
|
||||||
|
height: 100px
|
||||||
|
border-radius: 10px
|
||||||
|
margin-right: 20px
|
||||||
|
|
||||||
|
.song-info
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
align-items: flex-start
|
||||||
|
|
||||||
|
.song-names
|
||||||
|
font-size: 24pt
|
||||||
|
color: #fff
|
||||||
|
margin-bottom: 10px
|
||||||
|
|
||||||
|
.song-description
|
||||||
|
font-size: 14pt
|
||||||
|
color: #aaa
|
||||||
|
|
||||||
|
.chat-window
|
||||||
|
width: 50%
|
||||||
|
height: 400px
|
||||||
|
background: rgba(255, 255, 255, 0.1)
|
||||||
|
backdrop-filter: blur(10px)
|
||||||
|
border-radius: 10px
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1)
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2)
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
overflow: hidden
|
||||||
|
|
||||||
|
.chat-header
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
padding: 10px
|
||||||
|
background: rgba(30, 30, 30, 0.5)
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.2)
|
||||||
|
|
||||||
|
.chat-title
|
||||||
|
margin-left: 10px
|
||||||
|
font-size: 16pt
|
||||||
|
color: #fff
|
||||||
|
|
||||||
|
.chat-messages
|
||||||
|
flex: 1
|
||||||
|
padding: 10px
|
||||||
|
overflow-y: auto
|
||||||
|
color: #fff
|
||||||
|
|
||||||
|
.message
|
||||||
|
margin-bottom: 10px
|
||||||
|
|
||||||
|
.message-sender
|
||||||
|
font-weight: bold
|
||||||
|
|
||||||
|
.message-text
|
||||||
|
margin-left: 10px
|
||||||
|
|
||||||
|
.chat-input
|
||||||
|
display: flex
|
||||||
|
padding: 10px
|
||||||
|
background: rgba(30, 30, 30, 0.5)
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.2)
|
||||||
|
|
||||||
|
input
|
||||||
|
flex: 1
|
||||||
|
padding: 10px
|
||||||
|
border: none
|
||||||
|
border-radius: 5px
|
||||||
|
outline: none
|
||||||
|
background: rgba(255, 255, 255, 0.2)
|
||||||
|
color: #fff
|
||||||
|
|
||||||
|
button
|
||||||
|
margin-left: 10px
|
||||||
|
padding: 10px
|
||||||
|
background-color: #203a43
|
||||||
|
color: #fff
|
||||||
|
border: none
|
||||||
|
border-radius: 5px
|
||||||
|
cursor: pointer
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background-color: #1e1e1e
|
@ -1,179 +1,18 @@
|
|||||||
import "./styles.sass";
|
import "./styles.sass";
|
||||||
|
import ActionCard from "@/pages/Home/components/ActionCard";
|
||||||
|
import {faArrowRightToBracket, faPlusSquare} from "@fortawesome/free-solid-svg-icons";
|
||||||
import {StateContext} from "@/common/contexts/StateContext";
|
import {StateContext} from "@/common/contexts/StateContext";
|
||||||
import {useContext, useState, useEffect, useRef} from "react";
|
import {useContext} from "react";
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { faMusic } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
|
|
||||||
export const Home = () => {
|
export const Home = () => {
|
||||||
const {setCurrentState} = useContext(StateContext);
|
const {setCurrentState} = useContext(StateContext);
|
||||||
const [frequency, setFrequency] = useState(440);
|
|
||||||
const [isPlaying, setIsPlaying] = useState(false);
|
|
||||||
const [dragging, setDragging] = useState(false);
|
|
||||||
const audioContextRef = useRef(null);
|
|
||||||
const oscillatorRef = useRef(null);
|
|
||||||
const gainNodeRef = useRef(null);
|
|
||||||
const sliderRef = useRef(null);
|
|
||||||
const [messages, setMessages] = useState([{ sender: "Marco", text: "Hallo!" },]);
|
|
||||||
const [inputValue, setInputValue] = useState("");
|
|
||||||
const messageEndRef = useRef(null);
|
|
||||||
|
|
||||||
const startAudio = () => {
|
|
||||||
if (!audioContextRef.current) {
|
|
||||||
audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)();
|
|
||||||
}
|
|
||||||
if (!oscillatorRef.current) {
|
|
||||||
oscillatorRef.current = audioContextRef.current.createOscillator();
|
|
||||||
oscillatorRef.current.type = 'sine';
|
|
||||||
oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime);
|
|
||||||
|
|
||||||
gainNodeRef.current = audioContextRef.current.createGain();
|
|
||||||
gainNodeRef.current.gain.setValueAtTime(0.5, audioContextRef.current.currentTime);
|
|
||||||
|
|
||||||
oscillatorRef.current.connect(gainNodeRef.current);
|
|
||||||
gainNodeRef.current.connect(audioContextRef.current.destination);
|
|
||||||
oscillatorRef.current.start();
|
|
||||||
} else {
|
|
||||||
oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime);
|
|
||||||
}
|
|
||||||
setIsPlaying(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const stopAudio = () => {
|
|
||||||
if (oscillatorRef.current) {
|
|
||||||
oscillatorRef.current.stop();
|
|
||||||
oscillatorRef.current.disconnect();
|
|
||||||
oscillatorRef.current = null;
|
|
||||||
}
|
|
||||||
setIsPlaying(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseDown = (e) => {
|
|
||||||
setDragging(true);
|
|
||||||
startAudio();
|
|
||||||
handleFrequencyChange(e);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
|
||||||
setDragging(false);
|
|
||||||
stopAudio();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFrequencyChange = (e) => {
|
|
||||||
const rect = sliderRef.current.getBoundingClientRect();
|
|
||||||
const x = e.clientX - rect.left;
|
|
||||||
const clampedX = Math.max(0, Math.min(x, rect.width));
|
|
||||||
const newFrequency = 20 + (clampedX / rect.width) * 1980;
|
|
||||||
setFrequency(newFrequency);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleMouseMove = (e) => {
|
|
||||||
if (dragging) {
|
|
||||||
handleFrequencyChange(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseUpGlobal = () => {
|
|
||||||
if (dragging) {
|
|
||||||
handleMouseUp();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('mousemove', handleMouseMove);
|
|
||||||
document.addEventListener('mouseup', handleMouseUpGlobal);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('mousemove', handleMouseMove);
|
|
||||||
document.removeEventListener('mouseup', handleMouseUpGlobal);
|
|
||||||
};
|
|
||||||
}, [dragging]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isPlaying) {
|
|
||||||
if (oscillatorRef.current) {
|
|
||||||
oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [frequency, isPlaying]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
messageEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
||||||
}, [messages]);
|
|
||||||
|
|
||||||
const handleSendMessage = () => {
|
|
||||||
if (inputValue.trim()) {
|
|
||||||
setMessages([...messages, { sender: "User", text: inputValue }]);
|
|
||||||
setInputValue("");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="home-page">
|
<div className="home-page">
|
||||||
<div className="background-elements">
|
<div className="action-area">
|
||||||
<FontAwesomeIcon icon={faMusic} className="music-note note-1" />
|
<ActionCard title="Raum beitreten" icon={faArrowRightToBracket} onClick={() => {}} />
|
||||||
<FontAwesomeIcon icon={faMusic} className="music-note note-2" />
|
<ActionCard title="Raum erstellen" icon={faPlusSquare} onClick={() => setCurrentState("Game")} />
|
||||||
<FontAwesomeIcon icon={faMusic} className="music-note note-3" />
|
|
||||||
</div>
|
|
||||||
<div className="main-content">
|
|
||||||
<div className="song-display">
|
|
||||||
<h2>ToneGuessr</h2>
|
|
||||||
<div className="song-card">
|
|
||||||
<img src="https://mir-s3-cdn-cf.behance.net/project_modules/1400/fe529a64193929.5aca8500ba9ab.jpg" alt="Song" />
|
|
||||||
<div className="song-info">
|
|
||||||
<div className="song-name">Black Steam</div>
|
|
||||||
<div className="song-description">von Carrot Quest GmbH</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="chat-window">
|
|
||||||
<div className="chat-header">
|
|
||||||
<div className="account-icon"></div>
|
|
||||||
<div className="chat-title">Chat</div>
|
|
||||||
</div>
|
|
||||||
<div className="chat-messages">
|
|
||||||
{messages.map((message, index) => (
|
|
||||||
<div key={index} className="message">
|
|
||||||
<span className="message-sender">{message.sender}:</span>
|
|
||||||
<span className="message-text">{message.text}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div ref={messageEndRef}></div>
|
|
||||||
</div>
|
|
||||||
<div className="chat-input">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={inputValue}
|
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
|
||||||
onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
|
|
||||||
placeholder="Gib eine Nachricht ein..."
|
|
||||||
/>
|
|
||||||
<button onClick={handleSendMessage}>Send</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="otamatone-container">
|
|
||||||
<div
|
|
||||||
className="otamatone"
|
|
||||||
onMouseDown={handleMouseDown}
|
|
||||||
>
|
|
||||||
<div className="otamatone-neck" ref={sliderRef}>
|
|
||||||
<div className="frequency-indicator" style={{ left: `${(frequency - 20) / 1980 * 100}%` }}></div>
|
|
||||||
<div className="note-marker" style={{left: '10%', pointerEvents: 'none'}}>♩</div>
|
|
||||||
<div className="note-marker" style={{left: '50%', pointerEvents: 'none'}}>♪</div>
|
|
||||||
<div className="note-marker" style={{left: '90%', pointerEvents: 'none'}}>♫</div>
|
|
||||||
</div>
|
|
||||||
<div className="otamatone-face">
|
|
||||||
<div
|
|
||||||
className="otamatone-mouth"
|
|
||||||
style={{ height: `${10 + (frequency / 2000) * 40}px`, width: `${10 + (frequency / 2000) * 40}px` }}
|
|
||||||
></div>
|
|
||||||
<div className="otamatone-eyes">
|
|
||||||
<div className="eye left-eye"></div>
|
|
||||||
<div className="eye right-eye"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
11
client/src/pages/Home/components/ActionCard/ActionCard.jsx
Normal file
11
client/src/pages/Home/components/ActionCard/ActionCard.jsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
|
import "./styles.sass";
|
||||||
|
|
||||||
|
export const ActionCard = ({ title, icon, onClick }) => {
|
||||||
|
return (
|
||||||
|
<div className="glassy action-card" onClick={onClick}>
|
||||||
|
<FontAwesomeIcon icon={icon} />
|
||||||
|
<h1>{title}</h1>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
1
client/src/pages/Home/components/ActionCard/index.js
Normal file
1
client/src/pages/Home/components/ActionCard/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {ActionCard as default} from "./ActionCard.jsx";
|
23
client/src/pages/Home/components/ActionCard/styles.sass
Normal file
23
client/src/pages/Home/components/ActionCard/styles.sass
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
@use "@/common/styles/colors" as *
|
||||||
|
|
||||||
|
.action-card
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
padding: 2rem 0
|
||||||
|
width: 13rem
|
||||||
|
text-align: center
|
||||||
|
gap: 2rem
|
||||||
|
transition: all 0.3s ease-in-out
|
||||||
|
cursor: pointer
|
||||||
|
user-select: none
|
||||||
|
|
||||||
|
svg
|
||||||
|
font-size: 52pt
|
||||||
|
color: $white
|
||||||
|
|
||||||
|
h1
|
||||||
|
margin: 0
|
||||||
|
color: $white
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
transform: scale(1.1) translate(0, -0.5rem) rotate(0.5deg)
|
@ -1 +1 @@
|
|||||||
export {Home as default} from "./Home";
|
export {Home as default} from "./Home.jsx";
|
@ -3,256 +3,13 @@
|
|||||||
flex-direction: column
|
flex-direction: column
|
||||||
align-items: center
|
align-items: center
|
||||||
justify-content: center
|
justify-content: center
|
||||||
height: 100vh
|
height: 100%
|
||||||
background: linear-gradient(135deg, #0f2027, #203a43, #2c5364)
|
width: 100%
|
||||||
padding: 20px
|
|
||||||
user-select: none
|
|
||||||
overflow: hidden
|
|
||||||
position: relative
|
|
||||||
|
|
||||||
.background-elements
|
|
||||||
position: absolute
|
|
||||||
top: 0
|
|
||||||
left: 0
|
|
||||||
width: 100%
|
|
||||||
height: 100%
|
|
||||||
overflow: hidden
|
|
||||||
z-index: 0
|
|
||||||
|
|
||||||
.music-note
|
.action-area
|
||||||
position: absolute
|
margin-top: 5rem
|
||||||
font-size: 24pt
|
display: flex
|
||||||
color: rgba(255, 255, 255, 0.5)
|
gap: 3rem
|
||||||
animation: float-notes 5s infinite ease-in-out
|
flex-wrap: wrap
|
||||||
|
justify-content: center
|
||||||
&.note-1
|
|
||||||
top: 20%
|
|
||||||
left: 30%
|
|
||||||
|
|
||||||
&.note-2
|
|
||||||
top: 60%
|
|
||||||
left: 80%
|
|
||||||
|
|
||||||
&.note-3
|
|
||||||
top: 90%
|
|
||||||
left: 50%
|
|
||||||
|
|
||||||
@keyframes float-notes
|
|
||||||
0%
|
|
||||||
transform: translateY(0)
|
|
||||||
50%
|
|
||||||
transform: translateY(-10px)
|
|
||||||
100%
|
|
||||||
transform: translateY(0)
|
|
||||||
|
|
||||||
.main-content
|
|
||||||
display: flex
|
|
||||||
flex-direction: row
|
|
||||||
align-items: flex-start
|
|
||||||
justify-content: center
|
|
||||||
width: 100%
|
|
||||||
padding: 20px
|
|
||||||
z-index: 1
|
|
||||||
|
|
||||||
.song-display
|
|
||||||
display: flex
|
|
||||||
flex-direction: column
|
|
||||||
align-items: center
|
|
||||||
justify-content: center
|
|
||||||
width: 50%
|
|
||||||
margin-right: 20px
|
|
||||||
color: #fff
|
|
||||||
text-align: center
|
|
||||||
|
|
||||||
h2
|
|
||||||
font-size: 36pt
|
|
||||||
color: #fff
|
|
||||||
margin-bottom: 20px
|
|
||||||
|
|
||||||
.song-card
|
|
||||||
display: flex
|
|
||||||
flex-direction: row
|
|
||||||
align-items: center
|
|
||||||
background-color: rgba(255, 255, 255, 0.1)
|
|
||||||
padding: 20px
|
|
||||||
border-radius: 20px
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5)
|
|
||||||
backdrop-filter: blur(10px)
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2)
|
|
||||||
max-width: 500px
|
|
||||||
|
|
||||||
img
|
|
||||||
width: 100px
|
|
||||||
height: 100px
|
|
||||||
border-radius: 10px
|
|
||||||
margin-right: 20px
|
|
||||||
|
|
||||||
.song-info
|
|
||||||
display: flex
|
|
||||||
flex-direction: column
|
|
||||||
align-items: flex-start
|
|
||||||
|
|
||||||
.song-name
|
|
||||||
font-size: 24pt
|
|
||||||
color: #fff
|
|
||||||
margin-bottom: 10px
|
|
||||||
|
|
||||||
.song-description
|
|
||||||
font-size: 14pt
|
|
||||||
color: #aaa
|
|
||||||
|
|
||||||
.chat-window
|
|
||||||
width: 50%
|
|
||||||
height: 400px
|
|
||||||
background: rgba(255, 255, 255, 0.1)
|
|
||||||
backdrop-filter: blur(10px)
|
|
||||||
border-radius: 10px
|
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1)
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2)
|
|
||||||
display: flex
|
|
||||||
flex-direction: column
|
|
||||||
overflow: hidden
|
|
||||||
|
|
||||||
.chat-header
|
|
||||||
display: flex
|
|
||||||
align-items: center
|
|
||||||
padding: 10px
|
|
||||||
background: rgba(30, 30, 30, 0.5)
|
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2)
|
|
||||||
|
|
||||||
.account-icon
|
|
||||||
width: 30px
|
|
||||||
height: 30px
|
|
||||||
background: url('https://place-hold.it/100x100') no-repeat center
|
|
||||||
background-size: cover
|
|
||||||
border-radius: 50%
|
|
||||||
margin-right: 10px
|
|
||||||
|
|
||||||
.chat-title
|
|
||||||
font-size: 16pt
|
|
||||||
color: #fff
|
|
||||||
|
|
||||||
.chat-messages
|
|
||||||
flex: 1
|
|
||||||
padding: 10px
|
|
||||||
overflow-y: auto
|
|
||||||
color: #fff
|
|
||||||
|
|
||||||
.message
|
|
||||||
margin-bottom: 10px
|
|
||||||
|
|
||||||
.message-sender
|
|
||||||
font-weight: bold
|
|
||||||
|
|
||||||
.message-text
|
|
||||||
margin-left: 10px
|
|
||||||
|
|
||||||
.chat-input
|
|
||||||
display: flex
|
|
||||||
padding: 10px
|
|
||||||
background: rgba(30, 30, 30, 0.5)
|
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.2)
|
|
||||||
|
|
||||||
input
|
|
||||||
flex: 1
|
|
||||||
padding: 10px
|
|
||||||
border: none
|
|
||||||
border-radius: 5px
|
|
||||||
outline: none
|
|
||||||
background: rgba(255, 255, 255, 0.2)
|
|
||||||
color: #fff
|
|
||||||
|
|
||||||
button
|
|
||||||
margin-left: 10px
|
|
||||||
padding: 10px
|
|
||||||
background-color: #203a43
|
|
||||||
color: #fff
|
|
||||||
border: none
|
|
||||||
border-radius: 5px
|
|
||||||
cursor: pointer
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
background-color: #1e1e1e
|
|
||||||
|
|
||||||
.otamatone-container
|
|
||||||
display: flex
|
|
||||||
justify-content: center
|
|
||||||
align-items: center
|
|
||||||
position: fixed
|
|
||||||
left: 0
|
|
||||||
right: 0
|
|
||||||
bottom: 0
|
|
||||||
padding: 20px
|
|
||||||
background-color: rgba(30, 30, 30, 0.5)
|
|
||||||
backdrop-filter: blur(10px)
|
|
||||||
border-radius: 20px
|
|
||||||
margin: 20px
|
|
||||||
z-index: 1
|
|
||||||
|
|
||||||
.otamatone
|
|
||||||
display: flex
|
|
||||||
flex-direction: row
|
|
||||||
align-items: center
|
|
||||||
cursor: pointer
|
|
||||||
padding: 10px
|
|
||||||
width: 100%
|
|
||||||
|
|
||||||
.otamatone-neck
|
|
||||||
flex: 1
|
|
||||||
height: 20px
|
|
||||||
background: linear-gradient(135deg, #000, #444)
|
|
||||||
border-radius: 10px
|
|
||||||
position: relative
|
|
||||||
|
|
||||||
.frequency-indicator
|
|
||||||
position: absolute
|
|
||||||
top: -10px
|
|
||||||
width: 20px
|
|
||||||
height: 20px
|
|
||||||
background-color: #ff0000
|
|
||||||
border-radius: 50%
|
|
||||||
transform: translateX(-50%)
|
|
||||||
|
|
||||||
.note-marker
|
|
||||||
position: absolute
|
|
||||||
top: -30px
|
|
||||||
font-size: 24pt
|
|
||||||
color: #fff
|
|
||||||
|
|
||||||
.otamatone-face
|
|
||||||
width: 100px
|
|
||||||
height: 100px
|
|
||||||
background: radial-gradient(circle, #fff, #ddd)
|
|
||||||
border-radius: 50%
|
|
||||||
display: flex
|
|
||||||
flex-direction: column
|
|
||||||
align-items: center
|
|
||||||
justify-content: center
|
|
||||||
position: absolute
|
|
||||||
left: 0
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2)
|
|
||||||
|
|
||||||
.otamatone-eyes
|
|
||||||
display: flex
|
|
||||||
justify-content: space-between
|
|
||||||
width: 60%
|
|
||||||
position: absolute
|
|
||||||
top: 30px
|
|
||||||
|
|
||||||
.eye
|
|
||||||
width: 15px
|
|
||||||
height: 15px
|
|
||||||
background-color: #000
|
|
||||||
border-radius: 50%
|
|
||||||
|
|
||||||
.left-eye
|
|
||||||
margin-right: 10px
|
|
||||||
|
|
||||||
.right-eye
|
|
||||||
margin-left: 10px
|
|
||||||
|
|
||||||
.otamatone-mouth
|
|
||||||
background-color: #000
|
|
||||||
border-radius: 50%
|
|
||||||
position: absolute
|
|
||||||
bottom: 10px
|
|
Loading…
x
Reference in New Issue
Block a user