diff --git a/client/src/common/components/FormInput/FormInput.jsx b/client/src/common/components/FormInput/FormInput.jsx new file mode 100644 index 0000000..ca696f5 --- /dev/null +++ b/client/src/common/components/FormInput/FormInput.jsx @@ -0,0 +1,12 @@ +export const FormInput = ({ + type = "text", placeholder, value, onChange, + error, maxLength, className = "" + }) => { + return ( +
+ + {error &&
{error}
} +
+ ); +}; diff --git a/client/src/common/components/FormInput/index.js b/client/src/common/components/FormInput/index.js new file mode 100644 index 0000000..ed6c86a --- /dev/null +++ b/client/src/common/components/FormInput/index.js @@ -0,0 +1 @@ +export {FormInput as default} from "./FormInput"; \ No newline at end of file diff --git a/client/src/common/styles/forms.sass b/client/src/common/styles/forms.sass new file mode 100644 index 0000000..69e339a --- /dev/null +++ b/client/src/common/styles/forms.sass @@ -0,0 +1,157 @@ +@import "@/common/styles/colors" + +.form-base + background: rgba(255, 255, 255, 0.1) + backdrop-filter: blur(15px) + border: 1px solid rgba(255, 255, 255, 0.2) + border-radius: 20px + padding: 2.5rem + width: 100% + position: relative + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15), 0 0 20px rgba(255, 255, 255, 0.1) + + h2 + margin: 0 0 2rem + text-align: center + color: $white + font-size: 2.5rem + text-shadow: 0 0 10px rgba(255, 255, 255, 0.4) + + form + display: flex + flex-direction: column + gap: 1.5rem + +.input-group + position: relative + + input + width: 100% + padding: 1.2rem 1.5rem + background: rgba(255, 255, 255, 0.07) + border: 1px solid rgba(255, 255, 255, 0.1) + border-radius: 12px + color: $white + font-family: 'Bangers', sans-serif + font-size: 1.2rem + letter-spacing: 0.15rem + transition: all 0.3s ease + outline: none + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1) + + &::placeholder + color: rgba(255, 255, 255, 0.5) + + &:focus + border-color: rgba(255, 255, 255, 0.5) + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15), 0 0 10px rgba(255, 255, 255, 0.2) + + &.error + border-color: rgba(255, 0, 0, 0.5) + animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both + + .error-message + position: absolute + color: rgba(255, 100, 100, 0.9) + font-size: 0.85rem + bottom: -1.2rem + left: 0.2rem + animation: fade-in 0.3s ease + +.button + border: none + border-radius: 12px + padding: 1.2rem + color: $white + font-family: 'Bangers', sans-serif + font-size: 1.3rem + letter-spacing: 0.15rem + cursor: pointer + transition: all 0.3s ease + display: flex + justify-content: center + align-items: center + gap: 0.8rem + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15) + + &:hover + transform: translateY(-3px) + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2) + + &:active + transform: translateY(0) + +.submit-button + @extend .button + margin-top: 1rem + + &.join-button + background: linear-gradient(45deg, $blue, $purple) + + &:hover + background: linear-gradient(45deg, lighten($blue, 10%), lighten($purple, 10%)) + + &.create-button + background: linear-gradient(45deg, $pink, $purple) + + &:hover + background: linear-gradient(45deg, lighten($pink, 10%), lighten($purple, 10%)) + +.back-button + position: absolute + top: -4rem + left: 0 + background: rgba(255, 255, 255, 0.1) + border: 1px solid rgba(255, 255, 255, 0.2) + border-radius: 10px + display: flex + align-items: center + justify-content: center + gap: 0.7rem + color: rgba(255, 255, 255, 0.9) + cursor: pointer + font-family: 'Bangers', sans-serif + font-size: 1.1rem + padding: 0.7rem 1.3rem + transition: all 0.3s ease + letter-spacing: 0.1rem + backdrop-filter: blur(10px) + + svg + font-size: 1rem + transition: transform 0.3s ease + + &:hover + color: $white + background: rgba(255, 255, 255, 0.2) + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1) + + svg + transform: translateX(-5px) + +.glassy-card + background: rgba(255, 255, 255, 0.07) + backdrop-filter: blur(10px) + border: 1px solid rgba(255, 255, 255, 0.2) + border-radius: 20px + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1), 0 0 20px rgba(255, 255, 255, 0.1) + overflow: hidden + transition: all 0.3s ease + +@keyframes shake + 10%, 90% + transform: translate3d(-1px, 0, 0) + 20%, 80% + transform: translate3d(2px, 0, 0) + 30%, 50%, 70% + transform: translate3d(-4px, 0, 0) + 40%, 60% + transform: translate3d(4px, 0, 0) + +@keyframes fade-in + from + opacity: 0 + transform: translateY(-5px) + to + opacity: 1 + transform: translateY(0) diff --git a/client/src/pages/Home/Home.jsx b/client/src/pages/Home/Home.jsx index 269db92..ee2ee0a 100644 --- a/client/src/pages/Home/Home.jsx +++ b/client/src/pages/Home/Home.jsx @@ -1,11 +1,40 @@ import "./styles.sass"; import ActionCard from "@/pages/Home/components/ActionCard"; +import JoinForm from "@/pages/Home/components/JoinForm"; +import CreateForm from "@/pages/Home/components/CreateForm"; import {faArrowRightToBracket, faPlusSquare} from "@fortawesome/free-solid-svg-icons"; import {StateContext} from "@/common/contexts/StateContext"; -import {useContext} from "react"; +import {SocketContext} from "@/common/contexts/SocketContext"; +import {useContext, useState} from "react"; export const Home = () => { const {setCurrentState} = useContext(StateContext); + const {connect, send} = useContext(SocketContext); + const [homeState, setHomeState] = useState("initial"); // initial, joining, creating + + const handleJoinClick = () => { + setHomeState("joining"); + }; + + const handleCreateClick = () => { + setHomeState("creating"); + }; + + const handleBack = () => { + setHomeState("initial"); + }; + + const handleJoinSubmit = (name, roomCode) => { + connect(); + send("joinRoom", {name, roomCode}); + setCurrentState("Game"); + }; + + const handleCreateSubmit = (name) => { + connect(); + send("createRoom", {name}); + setCurrentState("Game"); + }; return (
@@ -13,14 +42,28 @@ export const Home = () => {
-
+

ToneGuessr

Das Frequenzspiel für Marco :3

-
- {}} /> - setCurrentState("Game")} /> +
+
+ + +
+ +
+ {homeState === 'joining' && ( + + )} +
+ +
+ {homeState === 'creating' && ( + + )} +
); diff --git a/client/src/pages/Home/components/CreateForm/CreateForm.jsx b/client/src/pages/Home/components/CreateForm/CreateForm.jsx new file mode 100644 index 0000000..0b83446 --- /dev/null +++ b/client/src/pages/Home/components/CreateForm/CreateForm.jsx @@ -0,0 +1,42 @@ +import "@/common/styles/forms.sass"; +import {useState} from "react"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {faArrowLeft, faPlusSquare} from "@fortawesome/free-solid-svg-icons"; +import FormInput from "@/common/components/FormInput"; + +export const CreateForm = ({onSubmit, onBack}) => { + const [name, setName] = useState(""); + const [errors, setErrors] = useState({}); + + const handleSubmit = (e) => { + e.preventDefault(); + + if (!name.trim()) { + setErrors({name: "Gib einen Namen ein"}); + return; + } + + onSubmit(name); + }; + + return ( +
+ + +

Raum erstellen

+ +
+ setName(e.target.value)} + error={errors.name}/> + + + +
+ ); +}; diff --git a/client/src/pages/Home/components/CreateForm/index.js b/client/src/pages/Home/components/CreateForm/index.js new file mode 100644 index 0000000..25690d0 --- /dev/null +++ b/client/src/pages/Home/components/CreateForm/index.js @@ -0,0 +1 @@ +export {CreateForm as default} from "./CreateForm.jsx"; \ No newline at end of file diff --git a/client/src/pages/Home/components/JoinForm/JoinForm.jsx b/client/src/pages/Home/components/JoinForm/JoinForm.jsx new file mode 100644 index 0000000..583d44b --- /dev/null +++ b/client/src/pages/Home/components/JoinForm/JoinForm.jsx @@ -0,0 +1,51 @@ +import "@/common/styles/forms.sass"; +import {useState} from "react"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {faArrowLeft, faArrowRightToBracket} from "@fortawesome/free-solid-svg-icons"; +import FormInput from "@/common/components/FormInput"; + +export const JoinForm = ({onSubmit, onBack}) => { + const [name, setName] = useState(""); + const [roomCode, setRoomCode] = useState(""); + const [errors, setErrors] = useState({}); + + const handleSubmit = (e) => { + e.preventDefault(); + + const newErrors = {}; + if (!name.trim()) newErrors.name = "Gib einen Namen an"; + if (!roomCode.trim()) newErrors.roomCode = "Gib einen Raum-Code an"; + + if (Object.keys(newErrors).length > 0) { + setErrors(newErrors); + return; + } + + onSubmit(name, roomCode); + }; + + return ( +
+ + +

Beitreten

+ +
+ setName(e.target.value)} + error={errors.name}/> + + setRoomCode(e.target.value.toUpperCase())} error={errors.roomCode} + maxLength={6}/> + + + +
+ ); +}; diff --git a/client/src/pages/Home/components/JoinForm/index.js b/client/src/pages/Home/components/JoinForm/index.js new file mode 100644 index 0000000..9b28307 --- /dev/null +++ b/client/src/pages/Home/components/JoinForm/index.js @@ -0,0 +1,2 @@ + +export {JoinForm as default} from "./JoinForm.jsx"; \ No newline at end of file diff --git a/client/src/pages/Home/styles.sass b/client/src/pages/Home/styles.sass index cbc97c4..480d113 100644 --- a/client/src/pages/Home/styles.sass +++ b/client/src/pages/Home/styles.sass @@ -8,62 +8,69 @@ height: 100vh width: 100vw position: relative - z-index: 1 - animation: page-fade-in 1s ease-in-out + z-index: 10 -.logo-container - text-align: center - margin-bottom: 2rem - animation: logo-entrance 1.2s cubic-bezier(0.175, 0.885, 0.32, 1.275) - position: relative - - .logo - font-size: 80pt - line-height: 1.1 - margin: 0 - padding: 0 15px - background: linear-gradient(135deg, $pink, $blue 45%, $mint-green 65%, $yellow 85%, $pink) - background-size: 300% 100% - animation: logo-shimmer 10s infinite alternate ease-in-out, logo-glow 5s infinite alternate ease-in-out - -webkit-background-clip: text - background-clip: text - -webkit-text-fill-color: transparent - filter: drop-shadow(0 0 12px rgba(255, 255, 255, 0.6)) - position: relative - letter-spacing: 0.1em - font-weight: bold + .logo-container + text-align: center + margin-bottom: 3rem + transform: translateY(0) + transition: transform 0.6s cubic-bezier(0.68, -0.55, 0.27, 1.55), margin-bottom 0.6s cubic-bezier(0.68, -0.55, 0.27, 1.55) + z-index: 5 - &:before - content: "ToneGuessr" - position: absolute - z-index: -1 - left: 0 - top: 0 - background: none - -webkit-text-fill-color: transparent - filter: blur(15px) brightness(1.2) - opacity: 0.7 - width: 100% - height: 100% - animation: text-pulse 5s infinite alternate ease-in-out - - .tagline - font-size: 24pt - margin: 5px 0 0 0 - color: rgba(255, 255, 255, 0.9) - text-shadow: 0 0 15px rgba(255, 255, 255, 0.4) - animation: tagline-fade 2s ease-in-out - letter-spacing: 0.1em + &.shifted + transform: translateY(-25vh) + margin-bottom: 0 + + .logo + font-size: 6rem + margin: 0 + color: $white + text-shadow: 0 0 20px rgba(255, 255, 255, 0.5) + letter-spacing: 0.5rem + animation: logo-glow 3s infinite alternate ease-in-out + + .tagline + font-size: 1.5rem + margin-top: 0.5rem + opacity: 0.8 + text-shadow: 0 0 10px rgba(255, 255, 255, 0.3) + font-weight: lighter + letter-spacing: 0.2rem -.action-area - margin-top: 2rem - display: flex - gap: 3rem - flex-wrap: wrap - justify-content: center - position: relative - z-index: 2 - animation: float-up 1.5s ease-out + .content-container + position: relative + width: 100% + display: flex + justify-content: center + align-items: center + transition: all 0.5s ease + + .action-area + display: flex + gap: 3rem + opacity: 1 + transform: scale(1) translateY(0) + transition: all 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55) + z-index: 4 + + &.hidden + opacity: 0 + transform: scale(0.8) translateY(30px) + pointer-events: none + + .form-container + position: absolute + opacity: 0 + transform: translateY(30px) scale(0.9) + transition: all 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55) + pointer-events: none + width: 400px + + &.active + opacity: 1 + transform: translateY(0) scale(1) + pointer-events: all + z-index: 5 @keyframes logo-entrance 0% @@ -75,11 +82,11 @@ @keyframes logo-glow 0%, 100% - filter: drop-shadow(0 0 12px rgba(255, 255, 255, 0.6)) - transform: scale(1) rotate(-1deg) + text-shadow: 0 0 20px rgba(255, 255, 255, 0.5) + transform: scale(1) 50% - filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.9)) drop-shadow(0 0 40px rgba(102, 204, 255, 0.5)) - transform: scale(1.05) rotate(1deg) + text-shadow: 0 0 30px rgba(255, 255, 255, 0.7), 0 0 50px rgba(255, 255, 255, 0.3) + transform: scale(1.02) @keyframes tagline-fade 0%