Neue GUI
parent
3d633aff70
commit
6a067d0c1e
|
|
@ -2,42 +2,158 @@
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Spotify Roulette – Spiel</title>
|
<title>Spotify Roulette – Spiel</title>
|
||||||
<style>
|
<style>
|
||||||
body { font-family: sans-serif; max-width: 600px; margin: auto; padding: 1rem; }
|
:root {
|
||||||
#options button { margin: 0.5rem; }
|
--primary-color: #1DB954;
|
||||||
#scoreboard { margin-top: 1rem; }
|
--bg-light: #f2f2f2;
|
||||||
|
--bg-dark: #121212;
|
||||||
|
--text-light: #ffffff;
|
||||||
|
--text-dark: #333333;
|
||||||
|
--accent: #191414;
|
||||||
|
--option-circle-size: 250px;
|
||||||
|
--option-radius: 120px;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: var(--bg-light);
|
||||||
|
color: var(--text-dark);
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas:
|
||||||
|
"header header"
|
||||||
|
"main aside"
|
||||||
|
"footer footer";
|
||||||
|
grid-template-columns: 3fr 1fr;
|
||||||
|
grid-template-rows: auto 1fr auto;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
grid-area: header;
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: var(--text-light);
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
main { grid-area: main; padding: 2rem; }
|
||||||
|
aside {
|
||||||
|
grid-area: aside;
|
||||||
|
background: var(--accent);
|
||||||
|
color: var(--text-light);
|
||||||
|
padding: 1.5rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
grid-area: footer;
|
||||||
|
background: var(--accent);
|
||||||
|
color: var(--text-light);
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.5rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.section-title {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
border-bottom: 2px solid var(--primary-color);
|
||||||
|
padding-bottom: 0.3rem;
|
||||||
|
}
|
||||||
|
ul { list-style: none; }
|
||||||
|
#controls, .round-controls { margin: 1rem 0; text-align: center; }
|
||||||
|
button {
|
||||||
|
background: var(--primary-color);
|
||||||
|
border: none;
|
||||||
|
color: var(--text-light);
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
|
margin: 0.3rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
button:hover:not(:disabled) { background: #0c903a; }
|
||||||
|
button:disabled { opacity: 0.6; cursor: not-allowed; }
|
||||||
|
|
||||||
|
/* Rundenbereich */
|
||||||
|
#roundArea {
|
||||||
|
margin-top: 1rem;
|
||||||
|
background: var(--text-light);
|
||||||
|
padding: 0.7rem 1rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#songEmbed iframe { border-radius: 6px; }
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* 80% der Haupt-Container-Breite, max. 400px */
|
||||||
|
--option-circle-size: 80%;
|
||||||
|
--option-max-size: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#options {
|
||||||
|
position: relative;
|
||||||
|
width: var(--option-circle-size);
|
||||||
|
max-width: var(--option-max-size);
|
||||||
|
aspect-ratio: 1; /* Höhe = Breite */
|
||||||
|
margin: 2rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#options .player-option {
|
||||||
|
position: absolute;
|
||||||
|
width: 100px;
|
||||||
|
height: 40px;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform-origin: center calc(-50% - 10px);
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
#options .player-option:nth-child(1) { transform: rotate(0deg) translate(0, var(--option-radius)); }
|
||||||
|
#options .player-option:nth-child(2) { transform: rotate(90deg) translate(0, var(--option-radius)); }
|
||||||
|
#options .player-option:nth-child(3) { transform: rotate(180deg) translate(0, var(--option-radius)); }
|
||||||
|
#options .player-option:nth-child(4) { transform: rotate(270deg) translate(0, var(--option-radius)); }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<header>
|
||||||
<h1>Spotify Roulette</h1>
|
<h1>Spotify Roulette</h1>
|
||||||
<p>Spiel-Code: <strong id="gameId"></strong></p>
|
<div>Spiel-Code: <span id="gameId" class="game-code"></span></div>
|
||||||
|
</header>
|
||||||
<div id="songListArea" style="position:fixed; right:0; top:0; width:200px; height:100vh; overflow-y:auto; background:#f8f8f8; border-left:1px solid #ccc; padding:1rem; z-index:10;">
|
<main>
|
||||||
<h3>Geladene Songs</h3>
|
<section>
|
||||||
<ul id="songList"></ul>
|
<div class="section-title">Teilnehmer</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>Teilnehmer</h2>
|
|
||||||
<ul id="playersList"></ul>
|
<ul id="playersList"></ul>
|
||||||
|
</section>
|
||||||
<div id="controls">
|
<div id="controls">
|
||||||
<button id="startRound">Runde starten</button>
|
<button id="startRound">Runde starten</button>
|
||||||
</div>
|
</div>
|
||||||
|
<section id="roundArea" hidden>
|
||||||
<div id="roundArea" hidden>
|
<div class="section-title">Wer hat’s gehört?</div>
|
||||||
<h2>Wer hat’s gehört?</h2>
|
|
||||||
<div id="songEmbed"></div>
|
<div id="songEmbed"></div>
|
||||||
<div id="options"></div>
|
<div class="round-controls" id="options">
|
||||||
<p id="result"></p>
|
<!-- Buttons zu Auswahl verteilen: JS erzeugt <button class="player-option">Name</button> -->
|
||||||
</div>
|
</div>
|
||||||
|
<div id="result"></div>
|
||||||
<h2>Scoreboard</h2>
|
</section>
|
||||||
|
<section>
|
||||||
|
<div class="section-title">Scoreboard</div>
|
||||||
<ul id="scoreboard"></ul>
|
<ul id="scoreboard"></ul>
|
||||||
|
</section>
|
||||||
<script type="module" src="/js/game.js"></script>
|
</main>
|
||||||
|
<aside>
|
||||||
|
<div class="section-title">Geladene Songs</div>
|
||||||
|
<ul id="songList"></ul>
|
||||||
<div id="deviceSelectArea"></div>
|
<div id="deviceSelectArea"></div>
|
||||||
|
</aside>
|
||||||
|
<footer> Spotify Roulette – alles ausser arbeiten </footer>
|
||||||
|
<script type="module" src="/js/utils.js"></script>
|
||||||
|
<script type="module" src="/js/start-round.js"></script>
|
||||||
|
<script type="module" src="/js/game.js"></script>
|
||||||
<script type="module" src="/js/device-select.js"></script>
|
<script type="module" src="/js/device-select.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ function connectWebSocket() {
|
||||||
}
|
}
|
||||||
connectWebSocket();
|
connectWebSocket();
|
||||||
|
|
||||||
// 8) Funktion zum Anzeigen einer neuen Runde
|
// Zugriff auf DOM-Elemente
|
||||||
const startBtn = document.getElementById("startRound");
|
const startBtn = document.getElementById("startRound");
|
||||||
const roundArea = document.getElementById("roundArea");
|
const roundArea = document.getElementById("roundArea");
|
||||||
const songEmbed = document.getElementById("songEmbed");
|
const songEmbed = document.getElementById("songEmbed");
|
||||||
|
|
@ -106,6 +106,7 @@ const optionsDiv = document.getElementById("options");
|
||||||
const resultP = document.getElementById("result");
|
const resultP = document.getElementById("result");
|
||||||
const scoreboard = document.getElementById("scoreboard");
|
const scoreboard = document.getElementById("scoreboard");
|
||||||
|
|
||||||
|
// 8) Funktion zum Anzeigen einer neuen Runde
|
||||||
function handleRoundStart({ ownerOptions, songUri, allTracks, trackInfos }) {
|
function handleRoundStart({ ownerOptions, songUri, allTracks, trackInfos }) {
|
||||||
// UI zurücksetzen
|
// UI zurücksetzen
|
||||||
resultP.textContent = "";
|
resultP.textContent = "";
|
||||||
|
|
@ -120,36 +121,44 @@ function handleRoundStart({ ownerOptions, songUri, allTracks, trackInfos }) {
|
||||||
width="100%" height="80" frameborder="0"
|
width="100%" height="80" frameborder="0"
|
||||||
allow="encrypted-media">
|
allow="encrypted-media">
|
||||||
</iframe>`;
|
</iframe>`;
|
||||||
// Song automatisch abspielen (sofern deviceId bereit und Username vorhanden)
|
|
||||||
if (window.playOnSpotify && typeof window.playOnSpotify === "function") {
|
if (window.playOnSpotify && typeof window.playOnSpotify === "function") {
|
||||||
window.playOnSpotify(songUri, username);
|
window.playOnSpotify(songUri, username);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tipp-Buttons erzeugen
|
// Dynamische Kreisverteilung der Buttons
|
||||||
ownerOptions.forEach(user => {
|
// Warten, bis #options gerendert ist
|
||||||
|
setTimeout(() => {
|
||||||
|
const optsRect = optionsDiv.getBoundingClientRect();
|
||||||
|
const radius = Math.min(optsRect.width, optsRect.height) / 2 - 50; // 50px Abstand zum Rand
|
||||||
|
|
||||||
|
ownerOptions.forEach((user, i) => {
|
||||||
const btn = document.createElement("button");
|
const btn = document.createElement("button");
|
||||||
btn.textContent = user;
|
btn.textContent = user;
|
||||||
|
btn.classList.add("player-option");
|
||||||
|
const angle = 360 * i / ownerOptions.length;
|
||||||
|
btn.style.transform = `rotate(${angle}deg) translateY(-${radius}px) rotate(${-angle}deg)`;
|
||||||
btn.addEventListener("click", () => {
|
btn.addEventListener("click", () => {
|
||||||
socket.send(JSON.stringify({
|
socket.send(JSON.stringify({
|
||||||
type: "guess",
|
type: "guess",
|
||||||
username: username,
|
username: username,
|
||||||
guess: user
|
guess: user
|
||||||
}));
|
}));
|
||||||
// Nach Tipp alle Buttons deaktivieren
|
|
||||||
optionsDiv.querySelectorAll("button").forEach(b => b.disabled = true);
|
optionsDiv.querySelectorAll("button").forEach(b => b.disabled = true);
|
||||||
});
|
});
|
||||||
optionsDiv.append(btn);
|
optionsDiv.appendChild(btn);
|
||||||
});
|
});
|
||||||
|
}, 0);
|
||||||
|
|
||||||
// UI anzeigen
|
|
||||||
startBtn.hidden = true;
|
startBtn.hidden = true;
|
||||||
roundArea.hidden = false;
|
roundArea.hidden = false;
|
||||||
|
|
||||||
const songList = document.getElementById("songList");
|
const songList = document.getElementById("songList");
|
||||||
songList.innerHTML = "";
|
songList.innerHTML = "";
|
||||||
if (Array.isArray(trackInfos)) {
|
if (Array.isArray(trackInfos)) {
|
||||||
trackInfos.forEach(trackInfo => {
|
trackInfos.forEach(trackInfo => {
|
||||||
const li = document.createElement("li");
|
const li = document.createElement("li");
|
||||||
li.textContent = trackInfo
|
li.textContent = trackInfo;
|
||||||
songList.appendChild(li);
|
songList.appendChild(li);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -166,28 +175,25 @@ function renderScoreboard(scores) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRoundResult({ scores, guesses, owner }) {
|
function handleRoundResult({ scores, guesses, owner }) {
|
||||||
// Scoreboard updaten
|
|
||||||
renderScoreboard(scores);
|
renderScoreboard(scores);
|
||||||
|
|
||||||
// Ergebnis für alle Spieler anzeigen
|
resultP.innerHTML = "";
|
||||||
resultP.innerHTML = ""; // Vorher leeren
|
|
||||||
Object.entries(guesses).forEach(([user, guess]) => {
|
Object.entries(guesses).forEach(([user, guess]) => {
|
||||||
const correct = guess === owner;
|
const correct = guess === owner;
|
||||||
const icon = correct ? "✅" : "❌";
|
const icon = correct ? "✅" : "❌";
|
||||||
const msg = `${icon} ${user} hat auf ${guess} getippt${correct ? " (richtig!)" : " (falsch)"}`;
|
const delta = correct ? "+3" : "-1";
|
||||||
|
const msg = `${icon} ${user} hat auf ${guess} getippt${correct ? " (richtig!)" : " (falsch)"} [${delta}]`;
|
||||||
const p = document.createElement("p");
|
const p = document.createElement("p");
|
||||||
p.textContent = msg;
|
p.textContent = msg;
|
||||||
resultP.appendChild(p);
|
resultP.appendChild(p);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Nach kurzer Pause für die nächste Runde vorbereiten
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resultP.textContent = "";
|
resultP.textContent = "";
|
||||||
startBtn.hidden = true;
|
startBtn.hidden = true;
|
||||||
startBtn.disabled = true;
|
startBtn.disabled = true;
|
||||||
roundArea.hidden = true;
|
roundArea.hidden = true;
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleGameEnd({winner}) {
|
function handleGameEnd({winner}) {
|
||||||
|
|
@ -196,13 +202,11 @@ function handleGameEnd({winner}) {
|
||||||
startBtn.hidden = false;
|
startBtn.hidden = false;
|
||||||
roundArea.hidden = true;
|
roundArea.hidden = true;
|
||||||
startBtn.disabled = false;
|
startBtn.disabled = false;
|
||||||
// scoreboard leeren
|
|
||||||
scoreboard.innerHTML = "";
|
scoreboard.innerHTML = "";
|
||||||
}, 6000);
|
}, 6000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// public/js/game.js
|
// Spotify-Playback Funktion
|
||||||
|
|
||||||
async function playOnSpotify(trackUri, username) {
|
async function playOnSpotify(trackUri, username) {
|
||||||
const deviceId = document.getElementById("deviceSelect")?.value;
|
const deviceId = document.getElementById("deviceSelect")?.value;
|
||||||
if (!deviceId) {
|
if (!deviceId) {
|
||||||
|
|
@ -213,11 +217,7 @@ async function playOnSpotify(trackUri, username) {
|
||||||
const response = await fetch("/api/spotify/play", {
|
const response = await fetch("/api/spotify/play", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({ username, device_id: deviceId, track_uri: trackUri })
|
||||||
username,
|
|
||||||
device_id: deviceId,
|
|
||||||
track_uri: trackUri
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = await response.text();
|
const error = await response.text();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue