diff --git a/public/css/1v1.css b/public/css/1v1.css index f3bce43..3bb2a54 100644 --- a/public/css/1v1.css +++ b/public/css/1v1.css @@ -590,6 +590,53 @@ body { box-shadow: 0 0 calc(var(--s) * 16) rgba(255, 255, 255, 0.2); } +/* ── Zug-Timer (Top Bar Mitte) ── */ +#turn-timer-wrap { + position: absolute; + left: 50%; + transform: translateX(-50%); + display: flex; + align-items: center; + justify-content: center; +} + +#turn-timer-svg { + display: block; +} + +.tt-track { + fill: none; + stroke: rgba(255,255,255,0.1); + stroke-width: 4; +} + +.tt-fill { + fill: none; + stroke: #27ae60; + stroke-width: 4; + stroke-linecap: round; + transform: rotate(-90deg); + transform-origin: center; + stroke-dasharray: 113.1; /* 2π×18 */ + stroke-dashoffset: 0; + transition: stroke-dashoffset 0.9s linear, stroke 0.3s ease; +} + +#turn-timer-num { + position: absolute; + font-family: "Cinzel", serif; + font-size: calc(var(--s) * 13); + font-weight: 700; + color: #fff; + text-shadow: 0 1px 4px rgba(0,0,0,0.9); + line-height: 1; +} + +@keyframes tt-pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.6; transform: scale(1.15); } +} + /* ── Karten-Stats Overlay – exakt wie Slotmaschine ── */ .card-stat-overlay { position: absolute; diff --git a/views/1v1-battlefield.ejs b/views/1v1-battlefield.ejs index 64f902b..6bc9f01 100644 --- a/views/1v1-battlefield.ejs +++ b/views/1v1-battlefield.ejs @@ -100,6 +100,16 @@
<%= title || "Spielfeld" %>
+ + + +
🗺
📖
🏆
@@ -363,10 +373,139 @@ } })(); - /* ── Zug beenden: CD ticken + Karte ziehen ─────────── */ + /* ══════════════════════════════════════════════════════ + ZUG-TIMER (20 Sekunden) + ══════════════════════════════════════════════════════ */ + const TURN_SECONDS = 20; + const TT_CIRCUM = 2 * Math.PI * 18; // r=18 + let turnTimerInt = null; + let turnSecsLeft = TURN_SECONDS; + + function startTurnTimer() { + clearInterval(turnTimerInt); + turnSecsLeft = TURN_SECONDS; + updateTimerUI(turnSecsLeft); + + const wrap = document.getElementById("turn-timer-wrap"); + if (wrap) wrap.style.display = "flex"; + + turnTimerInt = setInterval(() => { + turnSecsLeft--; + updateTimerUI(turnSecsLeft); + if (turnSecsLeft <= 0) { + clearInterval(turnTimerInt); + // Zeit abgelaufen → Zug automatisch beenden + if (isMyTurn) { + tickHandCooldowns(); + drawNextCard(); + setTurnState(false); + socket.emit("end_turn", { matchId, slot: mySlot }); + } + } + }, 1000); + } + + function stopTurnTimer() { + clearInterval(turnTimerInt); + const wrap = document.getElementById("turn-timer-wrap"); + if (wrap) wrap.style.display = "none"; + } + + function updateTimerUI(secs) { + const num = document.getElementById("turn-timer-num"); + const circle = document.getElementById("tt-circle"); + if (num) num.textContent = secs; + if (circle) { + circle.style.strokeDashoffset = TT_CIRCUM * (1 - secs / TURN_SECONDS); + circle.style.stroke = secs > 10 ? "#27ae60" : secs > 5 ? "#f39c12" : "#e74c3c"; + if (secs <= 5) { + num.style.color = "#e74c3c"; + num.style.animation = "tt-pulse 0.5s ease-in-out infinite"; + } else { + num.style.color = "#fff"; + num.style.animation = "none"; + } + } + } + + /* ══════════════════════════════════════════════════════ + ZUG-SYSTEM + ══════════════════════════════════════════════════════ */ + + // Bin ich der linke Spieler? Wird nach Seitenzuweisung gesetzt. + let amILeftPlayer = null; + let isMyTurn = false; + + function setTurnState(myTurn) { + isMyTurn = myTurn; + const btn = document.getElementById("end-turn-btn"); + if (!btn) return; + + if (myTurn) { + btn.disabled = false; + btn.textContent = "Zug beenden"; + btn.style.opacity = "1"; + document.getElementById("turn-indicator")?.remove(); + startTurnTimer(); + } else { + btn.disabled = true; + btn.style.opacity = "0.4"; + stopTurnTimer(); + showTurnIndicator(); + } + } + + function showTurnIndicator() { + document.getElementById("turn-indicator")?.remove(); + const ind = document.createElement("div"); + ind.id = "turn-indicator"; + ind.style.cssText = ` + position:fixed; bottom:calc(var(--s)*200); left:50%; + transform:translateX(-50%); + background:linear-gradient(135deg,rgba(20,20,40,0.95),rgba(10,10,25,0.95)); + border:1px solid rgba(200,160,60,0.5); + border-radius:calc(var(--s)*8); + color:rgba(255,215,80,0.75); + font-family:'Cinzel',serif; + font-size:calc(var(--s)*11); + letter-spacing:calc(var(--s)*3); + padding:calc(var(--s)*8) calc(var(--s)*20); + z-index:100; + pointer-events:none; + text-transform:uppercase; + box-shadow:0 4px 20px rgba(0,0,0,0.6); + `; + ind.textContent = "⏳ Gegner ist am Zug"; + document.body.appendChild(ind); + } + + /* ── Zug beenden: CD ticken + Karte ziehen + Server informieren ── */ document.getElementById("end-turn-btn")?.addEventListener("click", () => { + if (!isMyTurn) return; + clearInterval(turnTimerInt); tickHandCooldowns(); drawNextCard(); + setTurnState(false); + socket.emit("end_turn", { matchId, slot: mySlot }); + }); + + /* ── Server: Zugwechsel ──────────────────────────────── */ + socket.on("turn_change", data => { + // data.activeSlot = "player1" | "player2" + const myActualSlot = amILeftPlayer === null + ? mySlot + : (amILeftPlayer ? "player1" : "player2"); + + const nowMyTurn = data.activeSlot === myActualSlot || + data.activeSlot === mySlot; + setTurnState(nowMyTurn); + }); + + // Fallback falls Server kein turn_change sendet: + // eigenes end_turn bestätigen wir nach kurzer Verzögerung selbst + socket.on("turn_started", data => { + const nowMyTurn = data.slot === mySlot; + setTurnState(nowMyTurn); }); /* ── Hilfsfunktion: Karte mit Stats in einen Slot rendern ── */ @@ -411,9 +550,14 @@ const socket = io(); /* Account-ID laden dann arena_join senden */ + let myIngameName = null; fetch("/arena/me") .then(r => r.json()) .then(me => { + // Eigenen Namen sofort im Avatar anzeigen + myIngameName = me.ingame_name || me.username || me.name || me.id || "Ich"; + const myNameEl = document.getElementById(amIPlayer1 ? "nameLeft" : "nameRight"); + if (myNameEl) myNameEl.textContent = myIngameName; socket.emit("arena_join", { matchId, slot: mySlot, accountId: me.id }); }) .catch(() => { @@ -487,22 +631,34 @@ document.getElementById("nameLeft").textContent = leftName; document.getElementById("nameRight").textContent = rightName; - // Platzhalter-Icon durch Namen ersetzen falls noch kein Bild + // Platzhalter: nur den Ingame-Namen anzeigen, kein Icon ["avLeft", "avRight"].forEach(avId => { const av = document.getElementById(avId); const ph = av?.querySelector(".av-placeholder"); const name = avId === "avLeft" ? leftName : rightName; if (ph) ph.innerHTML = ` -
- ${avId === "avLeft" ? "⚔" : "🛡"} -
-
${name}
`; +
${name}
`; }); document.getElementById("board-lock-overlay")?.remove(); - document.getElementById("end-turn-btn").disabled = false; + + // Festlegen ob ich der linke Spieler bin + // flip=false: player1=links, flip=true: player1=rechts + amILeftPlayer = flip ? (mySlot === "player2") : (mySlot === "player1"); + + // Linker Spieler beginnt + setTurnState(amILeftPlayer); + socket.emit("end_turn_init", { matchId, starterSlot: flip ? "player2" : "player1" }); } });