dok/public/js/buildings/daily.js
2026-04-14 10:46:01 +01:00

261 lines
11 KiB
JavaScript

/* ============================================================
public/js/buildings/daily.js
/* ── Positionen (% in viewBox 0 0 100 100) ───────────────── */
const STATIONS = [
{ id:1, cx:48.5, cy:87.0 },
{ id:2, cx:36.5, cy:73.5 },
{ id:3, cx:44.0, cy:63.0 },
{ id:4, cx:53.5, cy:54.5 },
{ id:5, cx:47.5, cy:46.5 },
{ id:6, cx:43.5, cy:38.5 },
{ id:7, cx:49.5, cy:23.0 },
];
const R = 4.8; // Kreis-Radius in SVG-Einheiten
let dailyDeckId = null;
let dailyOverlay = null;
/* ── Karten-Pfad öffnen ──────────────────────────────────── */
export async function openDailyMap(deckId) {
dailyDeckId = deckId;
document.getElementById('daily-map-overlay')?.remove();
let completed = [];
try {
const r = await fetch('/api/himmelstor/daily/progress');
if (r.ok) completed = (await r.json()).completed ?? [];
} catch {}
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'" />
${buildSvg(completed)}
<div id="daily-map-title">☀️ TAGESHERAUSFORDERUNG</div>
<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>
</div>
${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(); });
/* Klick-Listener auf SVG-Hitboxen */
dailyOverlay.querySelectorAll('.ds-hit[data-station]').forEach(el => {
el.addEventListener('click', () => startStation(parseInt(el.dataset.station, 10)));
});
}
/* ── SVG aufbauen ────────────────────────────────────────── */
function buildSvg(completed) {
const maxDone = completed.length;
const parts = [];
/* 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 isAvail = !isDone && s.id === maxDone + 1;
const cls = isDone ? 'ds-done' : isAvail ? 'ds-available' : 'ds-locked';
const fSize = R * 0.72;
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>`);
} else if (isAvail) {
// Available: nur pulsierende Ringe über dem Bild-Kreis, kein eigener Kreis
parts.push(`
<g class="ds-station ${cls}" data-station="${s.id}">
<circle class="ds-ring1" cx="${s.cx}" cy="${s.cy}" r="${R + 1.2}"
fill="none" stroke="rgba(80,200,255,.8)" stroke-width="1.2">
<animate attributeName="r"
values="${R+1.2};${R+2.8};${R+1.2}" dur="2s" repeatCount="indefinite"/>
<animate attributeName="opacity"
values=".8;0;.8" dur="2s" repeatCount="indefinite"/>
</circle>
<circle class="ds-ring2" cx="${s.cx}" cy="${s.cy}" r="${R + 2}"
fill="none" stroke="rgba(80,200,255,.4)" stroke-width="0.8">
<animate attributeName="r"
values="${R+2};${R+4};${R+2}" dur="2s" begin=".7s" repeatCount="indefinite"/>
<animate attributeName="opacity"
values=".5;0;.5" dur="2s" begin=".7s" repeatCount="indefinite"/>
</circle>
<!-- Transparente Hitbox -->
<circle class="ds-hit" cx="${s.cx}" cy="${s.cy}" r="${R + 1.5}"
fill="transparent" stroke="none" data-station="${s.id}"/>
</g>`);
} else {
// Locked: unsichtbare Hitfläche, kein eigener Kreis (Bild-Kreis bleibt sichtbar)
parts.push(`
<g class="ds-station ${cls}" data-station="${s.id}">
<circle fill="rgba(0,0,0,0.35)" stroke="none" cx="${s.cx}" cy="${s.cy}" r="${R}"/>
<circle fill="transparent" stroke="none" cx="${s.cx}" cy="${s.cy}" r="${R + 1}"/>
</g>`);
}
});
return `<svg id="daily-map-svg"
viewBox="0 0 100 100"
preserveAspectRatio="none">${parts.join('')}</svg>`;
}
/* ── Station starten ─────────────────────────────────────── */
function startStation(station) {
if (!dailyDeckId) { alert('Bitte zuerst ein Deck auswählen.'); return; }
const socket = window._socket;
if (!socket) return;
/* Hitbox deaktivieren während Laden */
dailyOverlay.querySelectorAll(`.ds-hit[data-station="${station}"]`).forEach(el => {
el.style.pointerEvents = 'none';
el.style.opacity = '.4';
});
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 => {
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 });
}
/* ── Station als erledigt markieren ─────────────────────── */
export function markDailyStationDone(station) {
if (!dailyOverlay) return;
/* 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>`;
}
/* 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 ${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(bd);
document.body.appendChild(pp);
}
function closeMap() {
document.getElementById('daily-map-overlay')?.remove();
dailyOverlay = null;
}
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>`;
}