This commit is contained in:
cay 2026-04-14 08:46:50 +01:00
parent a31431ecaf
commit 49b571699b
2 changed files with 353 additions and 288 deletions

View File

@ -1,198 +1,208 @@
/* ============================================================
public/css/daily.css
Daily Herausforderung Karten-Pfad Overlay
public/css/daily.css Daily World Map (SVG-Overlay Ansatz)
============================================================ */
@keyframes dailyFadeIn { from{opacity:0} to{opacity:1} }
@keyframes dailyScaleIn { from{opacity:0;transform:translate(-50%,-50%) scale(.92)} to{opacity:1;transform:translate(-50%,-50%) scale(1)} }
/* ── Overlay ─────────────────────────────────────────────── */
#daily-map-overlay {
position: fixed;
inset: 0;
z-index: 10000;
background: rgba(0, 0, 0, 0.92);
background: rgba(0,0,0,.88);
display: flex;
align-items: center;
justify-content: center;
animation: dailyFadeIn 0.35s ease;
animation: dailyFadeIn .3s ease;
}
@keyframes dailyFadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes dailyPulse { 0%, 100% { box-shadow: 0 0 14px 4px rgba(80,160,255,0.7); } 50% { box-shadow: 0 0 28px 10px rgba(80,160,255,1); } }
@keyframes dailyGlow { 0%, 100% { opacity: 1; } 50% { opacity: 0.65; } }
/* ── Map Container ───────────────────────────────────────── */
#daily-map-wrap {
position: relative;
max-width: min(95vw, 1000px);
max-height: 90vh;
border-radius: 14px;
max-width: min(96vw, 1020px);
border-radius: 16px;
overflow: hidden;
box-shadow: 0 0 0 2px rgba(80, 140, 255, 0.4), 0 30px 80px rgba(0, 0, 0, 0.9);
animation: dailyScaleIn .3s cubic-bezier(.22,1,.36,1);
box-shadow:
0 0 0 1px rgba(80,140,255,.4),
0 0 0 3px rgba(80,140,255,.1),
0 30px 80px rgba(0,0,0,.9);
}
#daily-map-img {
display: block;
width: 100%;
height: auto;
max-height: 90vh;
object-fit: cover;
user-select: none;
pointer-events: none;
}
/* ── Close Button ────────────────────────────────────────── */
#daily-map-close {
/* ── SVG Overlay (liegt auf dem Bild) ────────────────────── */
#daily-map-svg {
position: absolute;
top: 12px;
right: 14px;
background: rgba(0, 0, 0, 0.7);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 50%;
width: 34px;
height: 34px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 16px;
cursor: pointer;
z-index: 10;
transition: background 0.2s;
inset: 0;
width: 100%;
height: 100%;
overflow: visible;
}
#daily-map-close:hover { background: rgba(200, 50, 50, 0.8); }
/* ── Title Banner ────────────────────────────────────────── */
/* ── Stationen (SVG-Gruppen) ─────────────────────────────── */
.ds-hit {
cursor: pointer;
fill: transparent;
}
.ds-locked .ds-hit { cursor: default; }
/* Pfadlinie */
.ds-path-line {
fill: none;
stroke: rgba(80,160,255,.25);
stroke-width: .7;
stroke-dasharray: 3 2.5;
}
.ds-path-line.done {
stroke: rgba(100,220,120,.4);
}
/* Locked: dunkle Abdunklung über dem Bildkreis */
.ds-locked .ds-overlay {
fill: rgba(0,0,5,.62);
stroke: rgba(50,60,100,.45);
stroke-width: .8;
}
.ds-locked .ds-num {
fill: rgba(120,130,160,.38);
font-family: "Cinzel", serif;
font-weight: 700;
}
/* Available: transparente Mitte, leuchtender Rand */
.ds-available .ds-overlay {
fill: rgba(40,100,255,.08);
stroke: rgba(100,200,255,.9);
stroke-width: 1.2;
}
.ds-available .ds-num {
fill: #fff;
font-family: "Cinzel", serif;
font-weight: 700;
filter: drop-shadow(0 0 3px rgba(180,220,255,.9));
}
/* Pulsierender Außenring */
.ds-available .ds-ring1 {
fill: none;
stroke: rgba(80,180,255,.65);
stroke-width: .9;
}
.ds-available .ds-ring2 {
fill: none;
stroke: rgba(80,180,255,.3);
stroke-width: .6;
}
/* Hover */
.ds-available:hover .ds-overlay {
fill: rgba(60,130,255,.18);
stroke: rgba(150,220,255,1);
stroke-width: 1.5;
}
.ds-available:hover .ds-num {
filter: drop-shadow(0 0 6px rgba(200,240,255,1));
}
/* Done: grüne Einfärbung */
.ds-done .ds-overlay {
fill: rgba(30,160,70,.35);
stroke: rgba(80,220,110,.85);
stroke-width: 1.2;
}
.ds-done .ds-check {
fill: #fff;
font-family: sans-serif;
font-weight: 700;
filter: drop-shadow(0 0 2px rgba(50,200,80,.8));
}
/* ── Titel ───────────────────────────────────────────────── */
#daily-map-title {
position: absolute;
top: 14px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.72);
border: 1px solid rgba(80, 140, 255, 0.45);
border-radius: 8px;
padding: 6px 22px;
background: linear-gradient(135deg, rgba(5,8,20,.9), rgba(8,12,28,.92));
border: 1px solid rgba(80,140,255,.45);
border-radius: 20px;
padding: 7px 26px;
font-family: "Cinzel", serif;
font-size: 15px;
font-size: 13px;
color: #a0c8ff;
letter-spacing: 3px;
letter-spacing: 4px;
white-space: nowrap;
z-index: 10;
pointer-events: none;
box-shadow: 0 4px 20px rgba(0,0,0,.6);
}
/* ── Circles ─────────────────────────────────────────────── */
.daily-circle {
/* ── Schließen ───────────────────────────────────────────── */
#daily-map-close {
position: absolute;
width: 7%; /* scales with image width */
aspect-ratio: 1;
top: 12px; right: 14px;
background: rgba(0,0,0,.65);
border: 1px solid rgba(255,255,255,.15);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-family: "Cinzel", serif;
font-size: clamp(12px, 1.8vw, 22px);
font-weight: bold;
transform: translate(-50%, -50%);
cursor: default;
transition: transform 0.15s ease;
z-index: 5;
user-select: none;
}
/* Completed station ─ grün */
.daily-circle.done {
background: radial-gradient(circle at 40% 35%, #4ecf6a, #1a7a30);
border: 3px solid #7af09a;
color: #fff;
box-shadow: 0 0 14px rgba(78, 207, 106, 0.6);
}
.daily-circle.done::after {
content: "✓";
font-size: clamp(10px, 1.5vw, 18px);
}
/* Available station ─ blaues Pulsieren */
.daily-circle.available {
background: radial-gradient(circle at 40% 35%, #5090ff, #1a3a9a);
border: 3px solid #90c0ff;
color: #fff;
width: 32px; height: 32px;
display: flex; align-items: center; justify-content: center;
color: rgba(255,255,255,.7);
font-size: 14px;
cursor: pointer;
animation: dailyPulse 2s ease-in-out infinite;
}
.daily-circle.available:hover {
transform: translate(-50%, -50%) scale(1.15);
animation: none;
box-shadow: 0 0 30px 8px rgba(80, 160, 255, 0.9);
z-index: 20;
transition: .2s;
}
#daily-map-close:hover { background: rgba(180,40,40,.8); color:#fff; }
/* Locked station ─ ausgegraut */
.daily-circle.locked {
background: radial-gradient(circle at 40% 35%, #2a2a3a, #141420);
border: 3px solid rgba(80, 100, 150, 0.4);
color: rgba(150, 160, 200, 0.35);
}
/* ── Progress Text ───────────────────────────────────────── */
/* ── Progress Bar ────────────────────────────────────────── */
#daily-progress-bar-wrap {
position: absolute;
bottom: 14px;
left: 50%;
bottom: 14px; left: 50%;
transform: translateX(-50%);
width: 60%;
background: rgba(0, 0, 0, 0.72);
border: 1px solid rgba(80, 140, 255, 0.35);
border-radius: 8px;
padding: 8px 16px;
z-index: 10;
text-align: center;
font-family: "Cinzel", serif;
background: rgba(5,8,20,.82);
border: 1px solid rgba(80,140,255,.3);
border-radius: 10px;
padding: 8px 18px 9px;
z-index: 10; pointer-events: none;
}
#daily-progress-text {
font-size: 12px;
color: #a0c8ff;
letter-spacing: 1px;
margin-bottom: 5px;
font-family: "Cinzel", serif;
font-size: 11px; color: rgba(160,200,255,.8);
letter-spacing: 1px; text-align: center; margin-bottom: 6px;
}
#daily-progress-track {
height: 6px;
background: rgba(80, 140, 255, 0.15);
border-radius: 3px;
overflow: hidden;
height: 5px; background: rgba(80,140,255,.12); border-radius: 3px; overflow: hidden;
}
#daily-progress-fill {
height: 100%;
background: linear-gradient(90deg, #3060cc, #60a0ff);
border-radius: 3px;
transition: width 0.6s ease;
background: linear-gradient(90deg, #2060dd, #60b0ff);
border-radius: 3px; transition: width .7s ease;
box-shadow: 0 0 8px rgba(80,160,255,.6);
}
/* ── All Done ────────────────────────────────────────────── */
#daily-all-done {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.78);
border-radius: 14px;
z-index: 20;
animation: dailyFadeIn 0.4s ease;
position: absolute; inset: 0;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
background: rgba(0,5,15,.8); border-radius: 16px;
z-index: 20; backdrop-filter: blur(4px);
animation: dailyFadeIn .4s ease;
}
#daily-all-done .done-icon { font-size: 56px; margin-bottom: 14px; }
#daily-all-done .done-title { font-family: "Cinzel", serif; font-size: 22px; color: #7af09a; letter-spacing: 4px; margin-bottom: 8px; }
#daily-all-done .done-sub { font-family: "Cinzel", serif; font-size: 12px; color: #a0c8a0; margin-bottom: 24px; }
#daily-all-done .done-icon { font-size:56px; margin-bottom:16px; }
#daily-all-done .done-title { font-family:"Cinzel",serif; font-size:22px; color:#7af09a; letter-spacing:4px; margin-bottom:8px; }
#daily-all-done .done-sub { font-family:"Cinzel",serif; font-size:12px; color:#a0c8a0; margin-bottom:26px; }
#daily-all-done .done-btn {
background: linear-gradient(135deg, #1a4a28, #27ae60);
border: 2px solid rgba(100, 220, 100, 0.6);
border-radius: 10px;
color: #fff;
font-family: "Cinzel", serif;
font-size: 13px;
letter-spacing: 3px;
padding: 11px 32px;
cursor: pointer;
transition: 0.2s;
background:linear-gradient(135deg,#1a4a28,#27ae60);
border:2px solid rgba(100,220,100,.6); border-radius:10px; color:#fff;
font-family:"Cinzel",serif; font-size:13px; letter-spacing:3px;
padding:11px 34px; cursor:pointer; transition:.2s;
}
#daily-all-done .done-btn:hover { border-color: #7af09a; background: linear-gradient(135deg, #2a6a38, #37be70); }
#daily-all-done .done-btn:hover { border-color:#7af09a; }

View File

@ -1,211 +1,266 @@
/* ============================================================
public/js/buildings/daily.js
Tagesherausforderung Karten-Pfad mit 7 Stationen gegen KI
============================================================ */
/* ── Stationen: Position auf dem Bild (% left, % top) ───── */
const DAILY_STATIONS = [
{ id: 1, left: 49.5, top: 86.5, label: "1" },
{ id: 2, left: 37.0, top: 73.0, label: "2" },
{ id: 3, left: 44.5, top: 62.0, label: "3" },
{ id: 4, left: 54.5, top: 53.5, label: "4" },
{ id: 5, left: 49.0, top: 46.0, label: "5" },
{ id: 6, left: 44.5, top: 37.5, label: "6" },
{ id: 7, left: 50.0, top: 22.0, label: "7" },
/* ── Positionen (% in viewBox 0 0 100 100) ───────────────── */
const STATIONS = [
{ id:1, cx:49.5, cy:86.5 },
{ id:2, cx:37.0, cy:73.0 },
{ id:3, cx:44.5, cy:62.0 },
{ id:4, cx:54.5, cy:53.5 },
{ id:5, cx:49.0, cy:46.0 },
{ id:6, cx:44.5, cy:37.5 },
{ id:7, cx:50.0, cy:22.0 },
];
const R = 4.8; // Kreis-Radius in SVG-Einheiten
let dailyDeckId = null;
let dailyOverlay = null;
/* ── Öffnet den Karten-Pfad ──────────────────────────────── */
/* ── Karten-Pfad öffnen ──────────────────────────────────── */
export async function openDailyMap(deckId) {
dailyDeckId = deckId;
document.getElementById('daily-map-overlay')?.remove();
// Kein Duplikat
document.getElementById("daily-map-overlay")?.remove();
/* Fortschritt vom Server laden */
let completed = [];
try {
const res = await fetch("/api/himmelstor/daily/progress");
if (res.ok) {
const data = await res.json();
completed = data.completed ?? [];
}
} catch (e) { console.error("[Daily] Fortschritt laden:", e); }
const r = await fetch('/api/himmelstor/daily/progress');
if (r.ok) completed = (await r.json()).completed ?? [];
} catch {}
/* Overlay aufbauen */
dailyOverlay = document.createElement("div");
dailyOverlay.id = "daily-map-overlay";
dailyOverlay = document.createElement('div');
dailyOverlay.id = 'daily-map-overlay';
dailyOverlay.innerHTML = `
<div id="daily-map-wrap">
<img id="daily-map-img" src="/images/KI_arena/daily.jpeg"
onerror="this.src='/images/items/rueckseite.png'" alt="Daily" />
<img id="daily-map-img"
src="/images/KI_arena/daily.jpeg"
onerror="this.src='/images/items/rueckseite.png'" />
${buildSvg(completed)}
<div id="daily-map-title"> TAGESHERAUSFORDERUNG</div>
<button id="daily-map-close" title="Schließen"></button>
${buildCircles(completed)}
<button id="daily-map-close"></button>
<div id="daily-progress-bar-wrap">
<div id="daily-progress-text">${completed.length} / 7 Stationen abgeschlossen</div>
<div id="daily-progress-track">
<div id="daily-progress-fill" style="width:${Math.round((completed.length / 7) * 100)}%"></div>
<div id="daily-progress-fill" style="width:${Math.round(completed.length/7*100)}%"></div>
</div>
</div>
${completed.length >= 7 ? buildAllDonePanel() : ""}
${completed.length >= 7 ? allDoneHtml() : ''}
</div>`;
document.body.appendChild(dailyOverlay);
document.getElementById('daily-map-close').addEventListener('click', closeMap);
dailyOverlay.addEventListener('click', e => { if (e.target === dailyOverlay) closeMap(); });
/* Event-Listener */
document.getElementById("daily-map-close").addEventListener("click", closeDailyMap);
dailyOverlay.addEventListener("click", e => { if (e.target === dailyOverlay) closeDailyMap(); });
/* Klickbare Kreise */
dailyOverlay.querySelectorAll(".daily-circle.available").forEach(circle => {
circle.addEventListener("click", () => {
const station = parseInt(circle.dataset.station, 10);
startDailyStation(station);
});
/* Klick-Listener auf SVG-Hitboxen */
dailyOverlay.querySelectorAll('.ds-hit[data-station]').forEach(el => {
el.addEventListener('click', () => startStation(parseInt(el.dataset.station, 10)));
});
}
/* ── HTML der Kreise ─────────────────────────────────────── */
function buildCircles(completed) {
const maxDone = completed.length; // nächster freier = maxDone + 1
/* ── SVG aufbauen ────────────────────────────────────────── */
function buildSvg(completed) {
const maxDone = completed.length;
const parts = [];
return DAILY_STATIONS.map(s => {
/* Pfadlinien zwischen Stationen */
for (let i = 0; i < STATIONS.length - 1; i++) {
const a = STATIONS[i];
const b = STATIONS[i + 1];
const done = completed.includes(a.id);
parts.push(`<line class="ds-path-line${done ? ' done' : ''}"
x1="${a.cx}" y1="${a.cy}" x2="${b.cx}" y2="${b.cy}"/>`);
}
/* Stationskreise */
STATIONS.forEach(s => {
const isDone = completed.includes(s.id);
const isAvailable = !isDone && s.id === maxDone + 1;
const isLocked = !isDone && !isAvailable;
const isAvail = !isDone && s.id === maxDone + 1;
const cls = isDone ? 'ds-done' : isAvail ? 'ds-available' : 'ds-locked';
const fSize = R * 0.72;
const cls = isDone ? "done" : isAvailable ? "available" : "locked";
const inner = isDone ? "" : s.label; // done zeigt ✓ per CSS ::after
if (isDone) {
parts.push(`
<g class="ds-station ${cls}" data-station="${s.id}">
<circle class="ds-overlay" cx="${s.cx}" cy="${s.cy}" r="${R}"/>
<text class="ds-check" x="${s.cx}" y="${s.cy}"
text-anchor="middle" dominant-baseline="central"
font-size="${fSize * 1.1}"></text>
</g>`);
return `<div
class="daily-circle ${cls}"
data-station="${s.id}"
style="left:${s.left}%;top:${s.top}%;"
title="${isDone ? "Abgeschlossen" : isAvailable ? `Station ${s.id} starten` : "Gesperrt"}"
>${inner}</div>`;
}).join("");
}
} else if (isAvail) {
parts.push(`
<g class="ds-station ${cls}" data-station="${s.id}">
<!-- Pulsierender Ring 1 -->
<circle class="ds-ring1" cx="${s.cx}" cy="${s.cy}" r="${R + 1.2}">
<animate attributeName="r"
values="${R+1.2};${R+2.6};${R+1.2}" dur="2s" repeatCount="indefinite"/>
<animate attributeName="opacity"
values=".7;0;.7" dur="2s" repeatCount="indefinite"/>
</circle>
<!-- Pulsierender Ring 2 (versetzt) -->
<circle class="ds-ring2" cx="${s.cx}" cy="${s.cy}" r="${R + 2}">
<animate attributeName="r"
values="${R+2};${R+3.6};${R+2}" dur="2s" begin=".7s" repeatCount="indefinite"/>
<animate attributeName="opacity"
values=".4;0;.4" dur="2s" begin=".7s" repeatCount="indefinite"/>
</circle>
<!-- Sichtbarer Kreis -->
<circle class="ds-overlay" cx="${s.cx}" cy="${s.cy}" r="${R}"/>
<!-- Nummer -->
<text class="ds-num" x="${s.cx}" y="${s.cy}"
text-anchor="middle" dominant-baseline="central"
font-size="${fSize}">${s.id}</text>
<!-- Unsichtbare Hitbox -->
<circle class="ds-hit" cx="${s.cx}" cy="${s.cy}" r="${R + 1.5}"
data-station="${s.id}"/>
</g>`);
function buildAllDonePanel() {
return `
<div id="daily-all-done">
<div class="done-icon">🏆</div>
<div class="done-title">TAGESQUEST ABGESCHLOSSEN!</div>
<div class="done-sub">Du hast alle 7 Stationen gemeistert.</div>
<button class="done-btn" onclick="document.getElementById('daily-map-overlay')?.remove()">
SCHLIESSEN
</button>
</div>`;
} else {
parts.push(`
<g class="ds-station ${cls}" data-station="${s.id}">
<circle class="ds-overlay" cx="${s.cx}" cy="${s.cy}" r="${R}"/>
<text class="ds-num" x="${s.cx}" y="${s.cy}"
text-anchor="middle" dominant-baseline="central"
font-size="${fSize}">${s.id}</text>
</g>`);
}
});
return `<svg id="daily-map-svg"
viewBox="0 0 100 100"
preserveAspectRatio="none">${parts.join('')}</svg>`;
}
/* ── Station starten ─────────────────────────────────────── */
function startDailyStation(station) {
if (!dailyDeckId) {
alert("Bitte zuerst ein Deck auswählen.");
return;
}
function startStation(station) {
if (!dailyDeckId) { alert('Bitte zuerst ein Deck auswählen.'); return; }
const socket = window._socket;
if (!socket) {
console.error("[Daily] Kein Socket.");
return;
}
if (!socket) return;
/* Kreis als "lädt" markieren */
const circle = dailyOverlay.querySelector(`.daily-circle[data-station="${station}"]`);
if (circle) {
circle.style.animation = "none";
circle.style.opacity = "0.5";
circle.style.cursor = "wait";
}
/* Hitbox deaktivieren während Laden */
dailyOverlay.querySelectorAll(`.ds-hit[data-station="${station}"]`).forEach(el => {
el.style.pointerEvents = 'none';
el.style.opacity = '.4';
});
/* Einmaligen Listener für Match-Gefunden */
socket.once("ht_daily_match_found", data => {
closeDailyMap();
openHtDailyPopup(
`/himmelstor/daily?match=${encodeURIComponent(data.matchId)}&slot=${encodeURIComponent(data.mySlot)}&deck=${encodeURIComponent(dailyDeckId)}&station=${station}&opponent=${encodeURIComponent("Wächter " + station)}`,
"Wächter " + station,
data.matchId
socket.once('ht_daily_match_found', data => {
closeMap();
openPopup(
`/himmelstor/daily?match=${encodeURIComponent(data.matchId)}`
+ `&slot=${encodeURIComponent(data.mySlot)}`
+ `&deck=${encodeURIComponent(dailyDeckId)}`
+ `&station=${station}`
+ `&opponent=${encodeURIComponent('Wächter ' + station)}`,
'Wächter ' + station, data.matchId
);
});
socket.once("ht_daily_error", data => {
if (circle) { circle.style.animation = ""; circle.style.opacity = ""; circle.style.cursor = ""; }
alert(data.message || "Fehler beim Starten.");
socket.once('ht_daily_error', data => {
dailyOverlay.querySelectorAll(`.ds-hit[data-station="${station}"]`).forEach(el => {
el.style.pointerEvents = '';
el.style.opacity = '';
});
alert(data.message || 'Fehler beim Starten.');
});
socket.emit("ht_join_daily", {
station,
deckId: dailyDeckId,
});
socket.emit('ht_join_daily', { station, deckId: dailyDeckId });
}
/* ── Popup öffnen (Iframe wie bei Arena) ─────────────────── */
function openHtDailyPopup(src, opponentName, matchId) {
document.getElementById("ht-daily-backdrop")?.remove();
document.getElementById("ht-daily-popup")?.remove();
/* ── Station als erledigt markieren ─────────────────────── */
export function markDailyStationDone(station) {
if (!dailyOverlay) return;
const backdrop = document.createElement("div");
backdrop.id = "ht-daily-backdrop";
backdrop.style.cssText = "position:fixed;inset:0;background:rgba(0,0,0,.82);backdrop-filter:blur(5px);z-index:9998;";
/* SVG-Gruppe aktualisieren */
const g = dailyOverlay.querySelector(`.ds-station[data-station="${station}"]`);
if (g) {
g.className.baseVal = 'ds-station ds-done';
g.innerHTML = `
<circle class="ds-overlay" cx="${STATIONS[station-1].cx}" cy="${STATIONS[station-1].cy}" r="${R}"/>
<text class="ds-check" x="${STATIONS[station-1].cx}" y="${STATIONS[station-1].cy}"
text-anchor="middle" dominant-baseline="central" font-size="${R*0.8}"></text>`;
}
const popup = document.createElement("div");
popup.id = "ht-daily-popup";
popup.style.cssText = "position:fixed;inset:50px;z-index:9999;display:flex;flex-direction:column;border-radius:14px;overflow:hidden;box-shadow:0 0 0 1px rgba(80,140,255,.4),0 30px 90px rgba(0,0,0,.85);";
popup.innerHTML = `
/* Pfadlinie grün */
const line = dailyOverlay.querySelector(`.ds-path-line:nth-of-type(${station})`);
if (line) line.classList.add('done');
/* Nächste Station freischalten */
const nextStation = STATIONS.find(s => s.id === station + 1);
const nextG = nextStation
? dailyOverlay.querySelector(`.ds-station[data-station="${station + 1}"]`)
: null;
if (nextG && nextStation) {
const fSize = R * 0.72;
nextG.className.baseVal = 'ds-station ds-available';
nextG.innerHTML = `
<circle class="ds-ring1" cx="${nextStation.cx}" cy="${nextStation.cy}" r="${R+1.2}">
<animate attributeName="r" values="${R+1.2};${R+2.6};${R+1.2}" dur="2s" repeatCount="indefinite"/>
<animate attributeName="opacity" values=".7;0;.7" dur="2s" repeatCount="indefinite"/>
</circle>
<circle class="ds-ring2" cx="${nextStation.cx}" cy="${nextStation.cy}" r="${R+2}">
<animate attributeName="r" values="${R+2};${R+3.6};${R+2}" dur="2s" begin=".7s" repeatCount="indefinite"/>
<animate attributeName="opacity" values=".4;0;.4" dur="2s" begin=".7s" repeatCount="indefinite"/>
</circle>
<circle class="ds-overlay" cx="${nextStation.cx}" cy="${nextStation.cy}" r="${R}"/>
<text class="ds-num" x="${nextStation.cx}" y="${nextStation.cy}"
text-anchor="middle" dominant-baseline="central" font-size="${fSize}">${station+1}</text>
<circle class="ds-hit" cx="${nextStation.cx}" cy="${nextStation.cy}" r="${R+1.5}"
data-station="${station+1}"/>`;
nextG.querySelector('.ds-hit')?.addEventListener('click', () => startStation(station + 1));
}
/* Fortschritt */
const done = dailyOverlay.querySelectorAll('.ds-done').length;
const pct = Math.round(done / 7 * 100);
const fill = document.getElementById('daily-progress-fill');
const text = document.getElementById('daily-progress-text');
if (fill) fill.style.width = pct + '%';
if (text) text.textContent = `${done} / 7 Stationen abgeschlossen`;
if (done >= 7) {
const wrap = document.getElementById('daily-map-wrap');
if (wrap && !document.getElementById('daily-all-done')) {
wrap.insertAdjacentHTML('beforeend', allDoneHtml());
}
}
}
/* ── Popup öffnen ────────────────────────────────────────── */
function openPopup(src, opponent, matchId) {
document.getElementById('ht-daily-backdrop')?.remove();
document.getElementById('ht-daily-popup')?.remove();
const bd = document.createElement('div');
bd.id = 'ht-daily-backdrop';
bd.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.82);backdrop-filter:blur(5px);z-index:9998;';
const pp = document.createElement('div');
pp.id = 'ht-daily-popup';
pp.style.cssText = 'position:fixed;inset:50px;z-index:9999;display:flex;flex-direction:column;border-radius:14px;overflow:hidden;box-shadow:0 0 0 1px rgba(80,140,255,.4),0 30px 90px rgba(0,0,0,.85);';
pp.innerHTML = `
<div style="display:flex;align-items:center;justify-content:space-between;background:rgba(5,8,20,.95);border-bottom:1px solid rgba(80,140,255,.3);padding:0 16px;height:42px;flex-shrink:0;">
<span style="font-family:'Cinzel',serif;font-size:13px;letter-spacing:4px;color:rgba(160,200,255,.85);text-transform:uppercase;"> Daily · vs ${opponentName}</span>
<span style="font-family:'Cinzel',serif;font-size:13px;letter-spacing:4px;color:rgba(160,200,255,.85);text-transform:uppercase;"> Daily · vs ${opponent}</span>
<span style="font-size:11px;color:rgba(255,255,255,.22);">${matchId}</span>
</div>
<iframe src="${src}" allowfullscreen style="flex:1;border:none;width:100%;display:block;"></iframe>`;
document.body.appendChild(backdrop);
document.body.appendChild(popup);
/* Cleanup wenn Iframe-Spiel beendet → closeToArena ruft parent auf */
document.body.appendChild(bd);
document.body.appendChild(pp);
}
/* ── Overlay schließen ───────────────────────────────────── */
function closeDailyMap() {
document.getElementById("daily-map-overlay")?.remove();
function closeMap() {
document.getElementById('daily-map-overlay')?.remove();
dailyOverlay = null;
}
/* ── Extern: nach gewonnenem Match Kreis aktualisieren ───── */
export function markDailyStationDone(station) {
if (!dailyOverlay) return;
const circle = dailyOverlay.querySelector(`.daily-circle[data-station="${station}"]`);
if (!circle) return;
circle.className = "daily-circle done";
circle.textContent = "";
circle.style = "";
/* Nächste Station freischalten */
const next = dailyOverlay.querySelector(`.daily-circle[data-station="${station + 1}"]`);
if (next) {
next.className = "daily-circle available";
next.textContent = String(station + 1);
next.addEventListener("click", () => startDailyStation(station + 1));
}
/* Fortschrittsbalken updaten */
const done = dailyOverlay.querySelectorAll(".daily-circle.done").length;
const pct = Math.round((done / 7) * 100);
const fill = document.getElementById("daily-progress-fill");
const text = document.getElementById("daily-progress-text");
if (fill) fill.style.width = pct + "%";
if (text) text.textContent = `${done} / 7 Stationen abgeschlossen`;
/* Alle 7 fertig? */
if (done >= 7) {
const wrap = document.getElementById("daily-map-wrap");
if (wrap && !document.getElementById("daily-all-done")) {
const panel = document.createElement("div");
panel.innerHTML = buildAllDonePanel();
wrap.appendChild(panel.firstElementChild);
}
}
function allDoneHtml() {
return `<div id="daily-all-done">
<div class="done-icon">🏆</div>
<div class="done-title">TAGESQUEST ABGESCHLOSSEN!</div>
<div class="done-sub">Du hast alle 7 Stationen gemeistert.</div>
<button class="done-btn" onclick="document.getElementById('daily-map-overlay')?.remove()"> SCHLIESSEN</button>
</div>`;
}