Win banner

This commit is contained in:
2026-02-10 08:07:26 +01:00
parent 5e8fad02b0
commit f496dd722a

View File

@@ -4,7 +4,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Wolf & Schafe | Online</title> <title>Wolf & Schafe</title>
<style> <style>
:root { :root {
--bg: #f0f2f5; --bg: #f0f2f5;
@@ -91,7 +91,6 @@
color: white; color: white;
} }
/* Connection Dot */
.conn-dot { .conn-dot {
display: inline-block; display: inline-block;
width: 8px; width: 8px;
@@ -126,6 +125,7 @@
width: 100%; width: 100%;
border: 3px solid var(--black); border: 3px solid var(--black);
background: #fff; background: #fff;
overflow: hidden;
} }
svg { svg {
@@ -175,13 +175,56 @@
stroke-dasharray: 4; stroke-dasharray: 4;
} }
/* Victory Banner */
#victory-banner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(255, 255, 255, 0.95);
border: 5px solid var(--black);
padding: 20px;
z-index: 20;
text-align: center;
box-shadow: 8px 8px 0px var(--black);
pointer-events: none;
width: 70%;
}
#victory-banner h2 {
margin: 0;
font-size: 1.8rem;
text-transform: uppercase;
}
.winner-glow {
animation: winner-pulse 1.5s infinite;
}
@keyframes winner-pulse {
0% {
filter: drop-shadow(0 0 2px #ffd700);
stroke-width: 2.5;
}
50% {
filter: drop-shadow(0 0 12px #ffd700);
stroke-width: 6;
}
100% {
filter: drop-shadow(0 0 2px #ffd700);
stroke-width: 2.5;
}
}
#overlay { #overlay {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: rgba(255, 255, 255, 0.92); background: rgba(255, 255, 255, 0.9);
z-index: 10; z-index: 10;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -218,7 +261,26 @@
color: white; color: white;
} }
/* Toast Popup Styles */ .restart-pulse {
animation: btn-pulse 1.5s infinite;
background: var(--green) !important;
color: white;
}
@keyframes btn-pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
#toast { #toast {
visibility: hidden; visibility: hidden;
min-width: 200px; min-width: 200px;
@@ -273,8 +335,8 @@
<h1 style="text-align: center; margin: 0; border-bottom: 4px solid var(--black); padding-bottom: 10px;">WOLF & <h1 style="text-align: center; margin: 0; border-bottom: 4px solid var(--black); padding-bottom: 10px;">WOLF &
SCHAFE</h1> SCHAFE</h1>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px;"> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px;">
<button class="btn btn-green" onclick="createRoom()">+ ONLINE</button> <button class="btn btn-green" onclick="createRoom()">+ ONLINE SPIEL</button>
<button class="btn" onclick="startLocal()">+ LOKAL</button> <button class="btn" onclick="startLocal()">+ LOKAL SPIELEN</button>
</div> </div>
<h3 style="margin: 15px 0 5px 0;">Offene Räume:</h3> <h3 style="margin: 15px 0 5px 0;">Offene Räume:</h3>
<div id="roomList">Suche Räume...</div> <div id="roomList">Suche Räume...</div>
@@ -296,11 +358,17 @@
<div>WARTEN AUF GEGNER...</div> <div>WARTEN AUF GEGNER...</div>
<p style="font-size: 0.7rem; margin-top: 10px; padding: 0 10px;">Link kopieren und Freund einladen!</p> <p style="font-size: 0.7rem; margin-top: 10px; padding: 0 10px;">Link kopieren und Freund einladen!</p>
</div> </div>
<div id="victory-banner" class="hidden">
<h2 id="winner-text">GEWONNEN!</h2>
<p style="margin: 5px 0 0 0; font-size: 0.8rem;">SPIEL BEENDET</p>
</div>
<svg viewBox="0 0 600 420"></svg> <svg viewBox="0 0 600 420"></svg>
</div> </div>
<div class="controls"> <div class="controls">
<button class="btn" id="copyBtn">Link Kopieren</button> <button class="btn" id="copyBtn">Kopieren</button>
<button class="btn" id="restartBtn">Restart</button> <button class="btn" id="restartBtn">Restart</button>
<button class="btn" onclick="location.href='/'">Lobby</button> <button class="btn" onclick="location.href='/'">Lobby</button>
</div> </div>
@@ -324,7 +392,6 @@
function initLocalState() { function initLocalState() {
state = { wolf: 5, sheep: [0, 1, 3], turn: "sheep", moveCount: 0, winner: null, lastMove: null }; state = { wolf: 5, sheep: [0, 1, 3], turn: "sheep", moveCount: 0, winner: null, lastMove: null };
myRole = "sheep"; // Initial role, will toggle
document.getElementById("lobby").classList.add("hidden"); document.getElementById("lobby").classList.add("hidden");
document.getElementById("game").classList.remove("hidden"); document.getElementById("game").classList.remove("hidden");
document.getElementById("roomInfo").style.visibility = "hidden"; document.getElementById("roomInfo").style.visibility = "hidden";
@@ -335,7 +402,6 @@
function connect() { function connect() {
if (isLocal) return initLocalState(); if (isLocal) return initLocalState();
const protocol = location.protocol === "https:" ? "wss" : "ws"; const protocol = location.protocol === "https:" ? "wss" : "ws";
ws = new WebSocket(`${protocol}://${location.host}?room=${roomId}&sessionId=${sessionId}`); ws = new WebSocket(`${protocol}://${location.host}?room=${roomId}&sessionId=${sessionId}`);
@@ -358,14 +424,11 @@
}; };
} }
// Logic for local movement
function handleLocalMove(from, to) { function handleLocalMove(from, to) {
const allowed = (state.turn === "wolf") ? bWolf[from] : bSheep[from]; const allowed = (state.turn === "wolf") ? bWolf[from] : bSheep[from];
if (!allowed?.includes(to) || state.sheep.includes(to) || state.wolf === to) return; if (!allowed?.includes(to) || state.sheep.includes(to) || state.wolf === to) return;
if (state.turn === "wolf") { state.wolf = to; state.turn = "sheep"; } if (state.turn === "wolf") { state.wolf = to; state.turn = "sheep"; }
else { state.sheep[state.sheep.indexOf(from)] = to; state.turn = "wolf"; } else { state.sheep[state.sheep.indexOf(from)] = to; state.turn = "wolf"; }
state.moveCount++; state.moveCount++;
state.winner = checkWinConditions(state); state.winner = checkWinConditions(state);
render(); render();
@@ -392,14 +455,8 @@
}); });
} }
function createRoom() { function createRoom() { window.location.search = `?room=${Math.random().toString(36).substring(7)}`; }
const id = Math.random().toString(36).substring(7); function startLocal() { window.location.search = `?mode=local`; }
window.location.search = `?room=${id}`;
}
function startLocal() {
window.location.search = `?mode=local`;
}
document.getElementById("restartBtn").onclick = () => { document.getElementById("restartBtn").onclick = () => {
if (isLocal) initLocalState(); if (isLocal) initLocalState();
@@ -416,45 +473,53 @@
l.setAttribute("class", "edge"); svg.appendChild(l); l.setAttribute("class", "edge"); svg.appendChild(l);
}); });
// In local mode, "your role" is always the current turn's role
const effectiveRole = isLocal ? state.turn : myRole; const effectiveRole = isLocal ? state.turn : myRole;
const badge = document.getElementById("idBadge"); const badge = document.getElementById("idBadge");
badge.innerText = effectiveRole === "wolf" ? "WOLF" : "SCHAFE"; badge.innerText = effectiveRole === "wolf" ? "WOLF" : "SCHAFE";
badge.className = `id-box is-${effectiveRole}`; badge.className = `id-box is-${effectiveRole}`;
const statusEl = document.getElementById("status"); const statusEl = document.getElementById("status");
if (state.winner) statusEl.innerHTML = `SIEG: ${state.winner.toUpperCase()}!`; const banner = document.getElementById("victory-banner");
else statusEl.innerHTML = `${(isLocal || state.turn === myRole) ? 'DEIN ZUG' : 'WARTEN...'}<br><small>ZUG ${state.moveCount}/40</small>`; const restartBtn = document.getElementById("restartBtn");
if (state.winner) {
banner.classList.remove("hidden");
const winMsg = state.winner === "wolf" ? "WOLF GEWINNT!" : "SCHAFE GEWINNEN!";
document.getElementById("winner-text").innerText = winMsg;
document.getElementById("winner-text").style.color = state.winner === "wolf" ? "var(--wolf)" : "var(--sheep)";
statusEl.innerHTML = "ENDE";
restartBtn.classList.add("restart-pulse");
} else {
banner.classList.add("hidden");
restartBtn.classList.remove("restart-pulse");
statusEl.innerHTML = `${(isLocal || state.turn === myRole) ? 'DEIN ZUG' : 'WARTEN...'}<br><small>ZUG ${state.moveCount}/40</small>`;
}
for (let i = 0; i <= 10; i++) { for (let i = 0; i <= 10; i++) {
const [x, y] = pos[i]; const [x, y] = pos[i];
const isWolf = state.wolf === i; const isWolf = state.wolf === i;
const isSheep = state.sheep.includes(i); const isSheep = state.sheep.includes(i);
const type = isWolf ? "wolf" : (isSheep ? "sheep" : "empty"); const type = isWolf ? "wolf" : (isSheep ? "sheep" : "empty");
const isWinnerPiece = (state.winner === "wolf" && isWolf) || (state.winner === "sheep" && isSheep);
const isPossible = selected !== null && (effectiveRole === "wolf" ? bWolf[selected] : bSheep[selected]).includes(i) && !isWolf && !isSheep; const isPossible = selected !== null && (effectiveRole === "wolf" ? bWolf[selected] : bSheep[selected]).includes(i) && !isWolf && !isSheep;
const g = document.createElementNS("http://www.w3.org/2000/svg", "g"); const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
g.onclick = () => { g.onclick = () => {
if (!isLocal && (state.turn !== myRole || state.winner)) return; if (!isLocal && (state.turn !== myRole || state.winner)) return;
if (isLocal && state.winner) return; if (isLocal && state.winner) return;
const isMine = (effectiveRole === "wolf" && isWolf) || (effectiveRole === "sheep" && isSheep); const isMine = (effectiveRole === "wolf" && isWolf) || (effectiveRole === "sheep" && isSheep);
if (isMine) { if (isMine) selected = i;
selected = i; else if (isPossible) {
} else if (isPossible) {
if (isLocal) handleLocalMove(selected, i); if (isLocal) handleLocalMove(selected, i);
else ws.send(JSON.stringify({ from: selected, to: i })); else ws.send(JSON.stringify({ from: selected, to: i }));
selected = null; selected = null;
} else { } else selected = null;
selected = null;
}
render(); render();
}; };
const c = document.createElementNS("http://www.w3.org/2000/svg", "circle"); const c = document.createElementNS("http://www.w3.org/2000/svg", "circle");
c.setAttribute("cx", x); c.setAttribute("cy", y); c.setAttribute("r", 25); c.setAttribute("cx", x); c.setAttribute("cy", y); c.setAttribute("r", 25);
c.setAttribute("class", `node-circle ${type} ${selected === i ? 'selected' : ''} ${isPossible ? 'possible' : ''}`); c.setAttribute("class", `node-circle ${type} ${selected === i ? 'selected' : ''} ${isPossible ? 'possible' : ''} ${isWinnerPiece ? 'winner-glow' : ''}`);
g.appendChild(c); g.appendChild(c);
const t = document.createElementNS("http://www.w3.org/2000/svg", "text"); const t = document.createElementNS("http://www.w3.org/2000/svg", "text");
@@ -466,9 +531,8 @@
} }
} }
// Copy logic
document.getElementById("copyBtn").onclick = async () => { document.getElementById("copyBtn").onclick = async () => {
const url = window.location.href.split('&')[0].split('?')[0] + `?room=${roomId}`; const url = window.location.origin + window.location.pathname + `?room=${roomId}`;
try { await navigator.clipboard.writeText(url); showToast(); } catch (err) { } try { await navigator.clipboard.writeText(url); showToast(); } catch (err) { }
}; };
function showToast() { const t = document.getElementById("toast"); t.className = "show"; setTimeout(() => { t.className = ""; }, 3000); } function showToast() { const t = document.getElementById("toast"); t.className = "show"; setTimeout(() => { t.className = ""; }, 3000); }