initialize project structure with essential files and configurations from ToneGuessr

This commit is contained in:
Mathias Wagner 2025-04-24 11:13:29 +02:00
parent 569e390a89
commit e818af6900
20 changed files with 5273 additions and 0 deletions

33
.github/workflows/deploy_docker.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: Publish Docker image
on:
push:
branches: [ "main" ]
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v4
- name: Set up docker buildx
uses: docker/setup-buildx-action@v1
- name: Log in to Docker Hub
uses: docker/login-action@v1
with:
registry: git.gnm.dev
username: ${{ github.actor }}
password: ${{ secrets.GT_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: git.gnm.dev/websiteprojects/liedkampf:main
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}

2
.gitignore vendored
View File

@ -130,3 +130,5 @@ dist
.yarn/install-state.gz
.pnp.*
# editors
.idea

30
Dockerfile Normal file
View File

@ -0,0 +1,30 @@
FROM node:20-alpine
RUN npm install -g pnpm
WORKDIR /app
COPY --chown=node:node ./package.json ./pnpm-lock.yaml /app/
COPY --chown=node:node ./client/package.json ./client/pnpm-lock.yaml /client/
WORKDIR /client
RUN NODE_ENV=development pnpm install
COPY --chown=node:node ./server /app
COPY --chown=node:node ./client /client
RUN pnpm run build
RUN cp -r /client/dist /app/dist
WORKDIR /app
RUN pnpm install --production --frozen-lockfile
RUN chown -R node:node /app
ENV NODE_ENV=production
USER node
EXPOSE 5287
CMD ["node", "index.js"]

15
client/.eslintrc.cjs Normal file
View File

@ -0,0 +1,15 @@
module.exports = {
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
settings: { react: { version: '18.2' } },
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': 'warn',
},
}

24
client/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

13
client/index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ToneGuessr</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

10
client/jsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
}
}
}

32
client/package.json Normal file
View File

@ -0,0 +1,32 @@
{
"name": "webui",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@fontsource/bangers": "^5.1.1",
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"sass": "^1.85.1",
"socket.io-client": "^4.8.1"
},
"devDependencies": {
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.21.0",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"vite": "^6.2.0"
}
}

3194
client/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

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

BIN
client/public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

61
client/src/App.jsx Normal file
View File

@ -0,0 +1,61 @@
import {useEffect, useState} from "react";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faDrum, faGuitar, faHeadphones, faMusic} from "@fortawesome/free-solid-svg-icons";
const App = () => {
const [cursorPos, setCursorPos] = useState({x: 0, y: 0});
const musicNotes = [
{id: 1, top: "8%", left: "20%", icon: faMusic, scale: 1.2},
{id: 2, top: "75%", left: "85%", icon: faGuitar, scale: 1},
{id: 3, top: "65%", left: "12%", icon: faHeadphones, scale: 1.1},
{id: 4, top: "20%", left: "70%", icon: faDrum, scale: 0.9}
];
useEffect(() => {
const handleMouseMove = (e) => {
if (!handleMouseMove.ticking) {
handleMouseMove.ticking = true;
requestAnimationFrame(() => {
setCursorPos({x: e.clientX, y: e.clientY});
handleMouseMove.ticking = false;
});
}
};
handleMouseMove.ticking = false;
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return (
<>
<div className="background-elements">
<div className="glow-point point-1" style={{
transform: `translate(${(cursorPos.x / window.innerWidth - 0.5) * -20}px, ${(cursorPos.y / window.innerHeight - 0.5) * -20}px)`
}}></div>
<div className="glow-point point-2" style={{
transform: `translate(${(cursorPos.x / window.innerWidth - 0.5) * 15}px, ${(cursorPos.y / window.innerHeight - 0.5) * 15}px)`
}}></div>
<div className="glow-point point-3" style={{
transform: `translate(${(cursorPos.x / window.innerWidth - 0.5) * -10}px, ${(cursorPos.y / window.innerHeight - 0.5) * -10}px)`
}}></div>
{musicNotes.map((note) => (
<div key={`note-${note.id}`} className={`music-note note-${note.id}`}
style={{
top: note.top,
left: note.left,
fontSize: `${note.scale * 60}pt`,
transform: `translate(${(cursorPos.x / window.innerWidth - 0.5) * -5 * note.scale}px, ${(cursorPos.y / window.innerHeight - 0.5) * -5 * note.scale}px)`
}}
>
<FontAwesomeIcon icon={note.icon}/>
</div>
))}
</div>
</>
)
}
export default App;

View File

@ -0,0 +1,16 @@
$background: rgba(255, 255, 255, 0.14)
$border: rgba(255, 255, 255, 0.35)
$white: #ECECEC
$green: #26EE5E
$black: #000
$dark-gray: #1e1e1e
$light-gray: #aaa
$red: #ff0000
$pink: #ff6bb3
$blue: #4d9dff
$purple: #9c6bff
$cyan: #6bffea
$orange: #ff9b6b
$yellow: #ffde6b
$mint-green: #85ffbd

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

@ -0,0 +1,230 @@
@use "@/common/styles/colors" as *
*
box-sizing: border-box
body
margin: 0
padding: 0
letter-spacing: 0.2rem
color: #E0E0E0
font-family: 'Bangers', sans-serif
height: 100vh
width: 100vw
background-size: 300% 300%
animation: shifting-background 30s ease infinite
background: linear-gradient(45deg, #4d3ae7 0%, #b84bc3 50%, #ff6a88 100%) fixed
user-select: none
overflow: hidden
position: relative
&:before
content: ""
position: fixed
top: 0
left: 0
right: 0
bottom: 0
background-image: url("/background.svg")
.background-overlay
position: fixed
top: 0
left: 0
width: 100vw
height: 100vh
z-index: 0
pointer-events: none
.rotating-gradient
position: absolute
top: 50%
left: 50%
width: 250vh
height: 250vh
transform: translate(-50%, -50%)
background: conic-gradient(from 0deg, rgba(255, 102, 196, 0.2), rgba(102, 204, 255, 0.2), rgba(255, 209, 128, 0.2), rgba(133, 255, 189, 0.2), rgba(255, 102, 196, 0.2))
border-radius: 50%
animation: rotate-background 180s linear infinite
will-change: transform
transform-origin: center center
opacity: 0.7
filter: blur(40px)
&:after
content: ""
position: fixed
top: 0
left: 0
right: 0
bottom: 0
background: radial-gradient(circle at center, transparent 0%, rgba(0, 0, 0, 0.1) 60%, rgba(0, 0, 0, 0.4) 100%)
z-index: 1
pointer-events: none
::-webkit-scrollbar
width: 8px
background-color: rgba(0, 0, 0, 0.2)
border-radius: 4px
::-webkit-scrollbar-thumb
background-color: rgba(255, 255, 255, 0.15)
border-radius: 4px
border: 1px solid rgba(255, 255, 255, 0.05)
&:hover
background-color: rgba(255, 255, 255, 0.25)
.glassy
background: $background
backdrop-filter: blur(10px)
border: 2px solid $border
border-radius: 0.8rem
.background-elements
position: fixed
top: 0
left: 0
width: 100vw
height: 100vh
overflow: hidden
z-index: 2
pointer-events: none
.glow-point
position: absolute
width: 250px
height: 250px
border-radius: 50%
filter: blur(100px)
opacity: 0.4
will-change: transform
transform: translateZ(0)
&.point-1
top: 20%
left: 10%
background-color: rgba(255, 102, 196, 0.8)
animation: float-glow 15s infinite alternate ease-in-out
&.point-2
top: 70%
left: 80%
background-color: rgba(102, 204, 255, 0.8)
animation: float-glow 18s infinite alternate-reverse ease-in-out
&.point-3
top: 80%
left: 20%
background-color: rgba(133, 255, 189, 0.8)
animation: float-glow 12s infinite alternate ease-in-out 2s
.music-note
position: absolute
font-size: 60pt
opacity: 0.7
will-change: transform, opacity, filter
filter: drop-shadow(0 0 15px rgba(255, 255, 255, 0.7))
transform: translateZ(0)
backface-visibility: hidden
@for $i from 1 through 5
&.note-#{$i}
animation-name: float-note-#{$i}, pulse-note
animation-duration: #{10 + ($i * 1)}s, 5s
animation-timing-function: ease-in-out, ease-in-out
animation-iteration-count: infinite, infinite
animation-direction: alternate, alternate
animation-delay: #{$i * 0.7}s, #{$i * 0.4}s
@if $i % 4 == 0
color: rgba(255, 102, 196, 0.8)
@else if $i % 4 == 1
color: rgba(102, 204, 255, 0.8)
@else if $i % 4 == 2
color: rgba(255, 209, 128, 0.8)
@else
color: rgba(133, 255, 189, 0.8)
.card-element
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 cubic-bezier(0.175, 0.885, 0.32, 1.275)
animation: card-float 6s infinite ease-in-out alternate
&:hover
transform: translateY(-10px) scale(1.02)
box-shadow: 0 15px 50px rgba(0, 0, 0, 0.2), 0 0 40px rgba(255, 255, 255, 0.2)
border: 1px solid rgba(255, 255, 255, 0.4)
@keyframes shifting-background
0%
background-position: 0% 0%
50%
background-position: 100% 100%
100%
background-position: 0% 0%
@keyframes rotate-background
0%
transform: translate(-50%, -50%) rotate(0deg)
100%
transform: translate(-50%, -50%) rotate(360deg)
@keyframes float-glow
0%, 100%
transform: translate(0, 0) scale(1)
filter: blur(100px)
50%
transform: translate(30px, -20px) scale(1.2)
filter: blur(80px)
@keyframes card-float
0%, 100%
transform: translateY(0)
50%
transform: translateY(-8px)
@keyframes float-note-1
0%, 100%
transform: translateY(0) rotate(0deg)
50%
transform: translateY(-40px) rotate(10deg)
@keyframes float-note-2
0%, 100%
transform: translateY(0) rotate(0deg)
50%
transform: translateY(-30px) rotate(-8deg)
@keyframes float-note-3
0%, 100%
transform: translateY(0) rotate(0deg)
50%
transform: translateY(-50px) rotate(15deg)
@keyframes float-note-4
0%, 100%
transform: translateY(0) rotate(0deg)
33%
transform: translateY(-25px) rotate(-5deg)
66%
transform: translateY(-40px) rotate(5deg)
@keyframes float-note-5
0%, 100%
transform: translateY(0) rotate(0deg)
50%
transform: translateY(-35px) rotate(-12deg)
@keyframes pulse-note
0%, 100%
filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.5))
opacity: 0.6
50%
filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.7))
opacity: 0.9

15
client/src/main.jsx Normal file
View File

@ -0,0 +1,15 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "@fontsource/bangers";
import "@/common/styles/main.sass";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<div className="app">
<App/>
</div>
</React.StrictMode>,
);

21
client/vite.config.js Normal file
View File

@ -0,0 +1,21 @@
import {defineConfig} from "vite";
import react from "@vitejs/plugin-react";
import * as path from "path";
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
server: {
proxy: {
"/socket.io": {
target: "http://localhost:5287",
changeOrigin: true,
ws: true,
},
},
},
})

23
package.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "toneguessr",
"version": "1.0.0-ALPHA",
"description": "The server of the ToneGuessr game",
"main": "server/index.js",
"repository": "https://git.gnm.dev/WebsiteProjects/ToneGuessr",
"author": "Mathias Wagner",
"license": "MIT",
"scripts": {
"webui": "cd client && yarn dev",
"server": "nodemon server",
"dev": "concurrently --kill-others \"yarn server\" \"yarn webui\""
},
"dependencies": {
"express": "^4.21.2",
"googleapis": "^146.0.0",
"socket.io": "^4.8.1"
},
"devDependencies": {
"concurrently": "^9.1.2",
"nodemon": "^3.1.9"
}
}

1358
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

35
server/index.js Normal file
View File

@ -0,0 +1,35 @@
const express = require("express");
const { Server } = require("socket.io");
const http = require("http");
const app = express();
const path = require("path");
app.use(express.static(path.join(__dirname, './dist')));
app.disable("x-powered-by");
app.get('*', (req, res) => res.sendFile(path.join(__dirname, './dist', 'index.html')));
const server = http.createServer(app);
const io = new Server(server, {
cors: {origin: "*"},
pingTimeout: 30000,
pingInterval: 10000
});
server.on('error', (error) => {
console.error('Server error:', error);
});
const PORT = process.env.PORT || 5237;
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
process.on('SIGINT', () => {
console.log('Server shutting down...');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});