Compare commits

...

2 Commits

Author SHA1 Message Date
33f27a278d Add mockup create & join forms
Some checks failed
Publish Docker image / Push Docker image to Docker Hub (push) Has been cancelled
2025-02-28 21:55:31 +01:00
d0d999a8db Move background.svg to external source 2025-02-28 21:18:07 +01:00
11 changed files with 384 additions and 64 deletions

View File

@ -0,0 +1,4 @@
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<path d="M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z"
fill="#ffffff" fill-opacity="0.05" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,12 @@
export const FormInput = ({
type = "text", placeholder, value, onChange,
error, maxLength, className = ""
}) => {
return (
<div className="input-group">
<input type={type} placeholder={placeholder} value={value} onChange={onChange}
className={`${error ? "error" : ""} ${className}`} maxLength={maxLength}/>
{error && <div className="error-message">{error}</div>}
</div>
);
};

View File

@ -0,0 +1 @@
export {FormInput as default} from "./FormInput";

View File

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

View File

@ -25,8 +25,8 @@ body
left: 0
right: 0
bottom: 0
background-image: url("data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z' fill='%23ffffff' fill-opacity='0.05' fill-rule='evenodd'/%3E%3C/svg%3E")
background-image: url("/background.svg")
.background-overlay
position: fixed
top: 0

View File

@ -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 (
<div className="home-page">
@ -13,14 +42,28 @@ export const Home = () => {
<div className="rotating-gradient"></div>
</div>
<div className="logo-container">
<div className={`logo-container ${homeState !== 'initial' ? 'shifted' : ''}`}>
<h1 className="logo">ToneGuessr</h1>
<p className="tagline">Das Frequenzspiel für Marco :3</p>
</div>
<div className="action-area">
<ActionCard title="Join Room" icon={faArrowRightToBracket} onClick={() => {}} />
<ActionCard title="Create Room" icon={faPlusSquare} onClick={() => setCurrentState("Game")} />
<div className={`content-container ${homeState !== 'initial' ? 'active' : ''}`}>
<div className={`action-area ${homeState !== 'initial' ? 'hidden' : ''}`}>
<ActionCard title="Join Room" icon={faArrowRightToBracket} onClick={handleJoinClick} />
<ActionCard title="Create Room" icon={faPlusSquare} onClick={handleCreateClick} />
</div>
<div className={`form-container ${homeState === 'joining' ? 'active' : ''}`}>
{homeState === 'joining' && (
<JoinForm onSubmit={handleJoinSubmit} onBack={handleBack} />
)}
</div>
<div className={`form-container ${homeState === 'creating' ? 'active' : ''}`}>
{homeState === 'creating' && (
<CreateForm onSubmit={handleCreateSubmit} onBack={handleBack} />
)}
</div>
</div>
</div>
);

View File

@ -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 (
<div className="form-base create-form">
<button className="back-button" onClick={onBack}>
<FontAwesomeIcon icon={faArrowLeft}/>
<span>Zurück</span>
</button>
<h2>Raum erstellen</h2>
<form onSubmit={handleSubmit}>
<FormInput placeholder="Benutzername" value={name} onChange={(e) => setName(e.target.value)}
error={errors.name}/>
<button type="submit" className="submit-button create-button">
<span>Raum erstellen</span>
<FontAwesomeIcon icon={faPlusSquare}/>
</button>
</form>
</div>
);
};

View File

@ -0,0 +1 @@
export {CreateForm as default} from "./CreateForm.jsx";

View File

@ -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 (
<div className="form-base join-form">
<button className="back-button" onClick={onBack}>
<FontAwesomeIcon icon={faArrowLeft}/>
<span>Zurück</span>
</button>
<h2>Beitreten</h2>
<form onSubmit={handleSubmit}>
<FormInput placeholder="Benutzername" value={name} onChange={(e) => setName(e.target.value)}
error={errors.name}/>
<FormInput placeholder="Raum-Code" value={roomCode}
onChange={(e) => setRoomCode(e.target.value.toUpperCase())} error={errors.roomCode}
maxLength={6}/>
<button type="submit" className="submit-button join-button">
<span>Beitreten</span>
<FontAwesomeIcon icon={faArrowRightToBracket}/>
</button>
</form>
</div>
);
};

View File

@ -0,0 +1,2 @@
export {JoinForm as default} from "./JoinForm.jsx";

View File

@ -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%