import { openDailyMap, markDailyStationDone } from './daily.js'; /* ============================================================ public/js/buildings/himmelstor.js Himmelstor – Tages- und Wochenherausforderung Gleiche Struktur wie arena.js, aber Daily & Weekly ============================================================ */ export async function loadHimmelstor() { const ui = document.querySelector(".building-ui"); if (!ui) return; /* ── Decks laden ─────────────────────────────────────────── */ let decks = []; try { const res = await fetch("/api/decks"); if (res.ok) decks = await res.json(); } catch (e) { console.error("[Himmelstor] Decks:", e); } const deckOptions = decks.length === 0 ? `` : `` + decks.filter(d => d.card_count > 0) .map(d => ``) .join("") + (decks.some(d => d.card_count === 0) ? decks.filter(d => d.card_count === 0) .map(d => ``) .join("") : ""); ui.innerHTML = `
🌤️ Himmelstor

Wähle Deck und Herausforderung

☀️
Daily
Tagesherausforderung
🌙
Weekly
Wochenherausforderung
${decks.length === 0 ? `
⚠️ Kein Deck vorhanden – gehe zur Burg → Kartendeck
` : ""}
`; injectHimmelstorStyles(); initHimmelstorModes(); } /* ── Styles ──────────────────────────────────────────────── */ function injectHimmelstorStyles() { if (document.getElementById("ht-popup-styles")) return; const style = document.createElement("style"); style.id = "ht-popup-styles"; style.textContent = ` @keyframes htFadeIn { from{opacity:0} to{opacity:1} } @keyframes htScaleIn { from{transform:scale(0.94);opacity:0} to{transform:scale(1);opacity:1} } @keyframes htPulse { 0%,100%{opacity:1} 50%{opacity:0.5} } #himmelstor-ui { display:flex; flex-direction:column; height:100%; } #himmelstor-mode-screen { display:flex; flex-direction:column; align-items:center; padding:16px; } .ht-title { font-family:"Cinzel",serif; font-size:18px; color:#f0d060; letter-spacing:3px; text-align:center; margin-bottom:4px; } .ht-subtitle { font-family:"Cinzel",serif; font-size:11px; color:#a08060; margin:0 0 14px; } .ht-modes { display:flex; gap:10px; justify-content:center; flex-wrap:wrap; width:100%; } .ht-mode-wrap { flex:1; min-width:120px; max-width:195px; display:flex; flex-direction:column; gap:6px; } .ht-mode-card { flex:1; min-width:120px; max-width:195px; background:linear-gradient(180deg,#1a1a2e,#0f0f1a); border:2px solid #4a3a6b; border-radius:12px; padding:21px 12px; cursor:pointer; text-align:center; transition:border-color .2s, transform .1s; box-shadow: inset 0 1px 0 rgba(160,120,255,0.1), 0 4px 16px rgba(0,0,0,0.6); } .ht-mode-card:hover { border-color:#9b72cf; transform:translateY(-2px); } .ht-mode-card.searching { opacity:.6; pointer-events:none; border-color:rgba(155,114,207,.5) !important; } .ht-mode-locked { opacity:.45; cursor:not-allowed !important; border-color:#2a1e40 !important; } .ht-mode-locked:hover { transform:none !important; border-color:#2a1e40 !important; } .ht-mode-icon { font-size:36px; margin-bottom:9px; } .ht-mode-label { font-family:"Cinzel",serif; font-size:19px; color:#c8a0ff; font-weight:bold; } .ht-mode-desc { font-size:15px; color:#6a5080; margin-top:6px; line-height:1.4; } .ht-deck-select { width:100%; background:#0f0f1a; border:1px solid #4a3a6b; border-radius:6px; color:#e0d0ff; font-family:"Cinzel",serif; font-size:15px; padding:8px 9px; cursor:pointer; appearance:none; -webkit-appearance:none; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23806090'/%3E%3C/svg%3E"); background-repeat:no-repeat; background-position:right 6px center; padding-right:28px; } .ht-deck-select:focus { outline:none; border-color:#9b72cf; } .ht-deck-select option { background:#0f0f1a; color:#e0d0ff; } .ht-no-deck-hint { margin-top:10px; padding:8px 12px; border-radius:7px; background:rgba(231,76,60,.1); border:1px solid rgba(231,76,60,.3); color:#e07060; font-family:"Cinzel",serif; font-size:11px; text-align:center; } .ht-no-deck-hint strong { color:#c8a0ff; } #ht-queue-status { margin-top:12px; padding:10px 18px; border-radius:10px; width:100%; box-sizing:border-box; background:rgba(155,114,207,.08); border:1px solid rgba(155,114,207,.3); color:#c8a0ff; font-family:"Cinzel",serif; font-size:12px; letter-spacing:1px; text-align:center; animation:htPulse 2s ease-in-out infinite; } .ht-cancel { display:inline-block; margin-top:6px; font-size:11px; color:rgba(255,100,100,.8); cursor:pointer; text-decoration:underline; animation:none; } .ht-cancel:hover { color:#e74c3c; } /* Match-Found Overlay */ #ht-match-found-overlay { position:fixed; inset:0; z-index:10000; background:rgba(0,0,0,.9); display:flex; flex-direction:column; align-items:center; justify-content:center; animation:htFadeIn .3s ease; } .ht-mfo-title { font-family:"Cinzel",serif; font-size:36px; color:#c8a0ff; text-shadow:0 0 30px rgba(155,114,207,.6); letter-spacing:6px; margin-bottom:12px; } .ht-mfo-vs { font-family:"Cinzel",serif; font-size:18px; color:rgba(255,255,255,.75); letter-spacing:3px; } .ht-mfo-bar { width:300px; height:4px; background:rgba(155,114,207,.2); border-radius:2px; margin-top:24px; overflow:hidden; } .ht-mfo-bar-fill { height:100%; background:#9b72cf; width:0%; border-radius:2px; transition:width 1.5s ease; } /* Popup iframe */ #ht-backdrop { position:fixed; inset:0; background:rgba(0,0,0,.82); backdrop-filter:blur(5px); z-index:9998; animation:htFadeIn .25s ease; } #ht-popup { position:fixed; inset:50px; z-index:9999; display:flex; flex-direction:column; border-radius:14px; overflow:hidden; box-shadow:0 0 0 1px rgba(155,114,207,.35),0 30px 90px rgba(0,0,0,.85); animation:htScaleIn .28s cubic-bezier(.22,1,.36,1); } #ht-popup-titlebar { display:flex; align-items:center; justify-content:space-between; background:rgba(10,8,20,.95); border-bottom:1px solid rgba(155,114,207,.3); padding:0 16px; height:42px; flex-shrink:0; } #ht-popup-titlebar .ht-ap-title { font-family:"Cinzel",serif; font-size:13px; letter-spacing:4px; color:rgba(200,160,255,.85); text-transform:uppercase; } #ht-popup-titlebar .ht-ap-url { font-size:11px; color:rgba(255,255,255,.22); } #ht-popup iframe { flex:1; border:none; width:100%; display:block; } `; document.head.appendChild(style); } /* ── State ───────────────────────────────────────────────── */ let htSelectedDeckId = null; /* ── Modus-Initialisierung ───────────────────────────────── */ function initHimmelstorModes() { /* Deck-Dropdown: Karte freischalten wenn Deck gewählt */ document.querySelectorAll(".ht-deck-select").forEach(select => { select.addEventListener("change", () => { const mode = select.dataset.mode; const card = document.querySelector(`.ht-mode-card[data-mode="${mode}"]`); if (!card) return; if (select.value) { card.classList.remove("ht-mode-locked"); htSelectedDeckId = Number(select.value); } else { card.classList.add("ht-mode-locked"); } }); }); /* Modus-Karte klicken */ document.querySelectorAll(".ht-mode-card").forEach(card => { card.addEventListener("click", () => { if (card.classList.contains("ht-mode-locked")) return; const mode = card.dataset.mode; const select = document.querySelector(`.ht-deck-select[data-mode="${mode}"]`); htSelectedDeckId = select ? Number(select.value) : null; if (!htSelectedDeckId) return; sessionStorage.setItem("selectedDeckId", htSelectedDeckId); if (mode === 'daily') { // Karten-Pfad öffnen statt direktes Matchmaking openDailyMap(htSelectedDeckId); return; } handleHtModeClick(card, mode); }); }); } /* ── Station-Complete Event vom Server ──────────────────── */ function setupHtSocketListeners() { const socket = window._socket; if (!socket) return; socket.off('ht_station_complete'); socket.on('ht_station_complete', data => { markDailyStationDone(data.station); }); } // Listener einrichten sobald Socket verfügbar if (window._socket) setupHtSocketListeners(); else document.addEventListener('socket_ready', setupHtSocketListeners); /* ── Modus klicken ───────────────────────────────────────── */ async function handleHtModeClick(card, mode) { if (card.classList.contains("searching")) return; let me; try { const res = await fetch("/arena/me"); if (!res.ok) throw new Error("Status " + res.status); me = await res.json(); } catch { showHtError("Spielerdaten konnten nicht geladen werden."); return; } setHtCardSearching(card, mode, true); showHtQueueStatus(mode); const socket = window._socket; if (!socket) { showHtError("Keine Verbindung zum Server."); return; } socket.off("ht_match_found"); socket.off("ht_queue_status"); socket.on("ht_queue_status", data => { if (data.status === "waiting") showHtQueueStatus(mode, data.poolSize); else if (data.status === "left") { setHtCardSearching(card, mode, false); hideHtQueueStatus(); } }); socket.once("ht_match_found", data => { socket.off("ht_queue_status"); setHtCardSearching(card, mode, false); hideHtQueueStatus(); showHtMatchFoundOverlay(me.name, data.opponent?.name || "Gegner", () => { openHtPopup( `/himmelstor/${mode}?match=${encodeURIComponent(data.matchId)}&slot=${encodeURIComponent(data.mySlot)}&deck=${encodeURIComponent(htSelectedDeckId || '')}&opponent=${encodeURIComponent(data.opponent?.name || '')}`, data.opponent?.name || "Gegner", data.matchId ); }); }); socket.emit("ht_join", { id: me.id, name: me.name, level: me.level, mode, }); } /* ── UI Hilfsfunktionen ──────────────────────────────────── */ const HT_LABELS = { daily: "Daily", weekly: "Weekly" }; const HT_DESCS = { daily: "Tagesherausforderung", weekly: "Wochenherausforderung" }; function setHtCardSearching(card, mode, searching) { const label = card.querySelector(".ht-mode-label"); const desc = card.querySelector(".ht-mode-desc"); if (searching) { card.classList.add("searching"); label.textContent = "⏳ Suche…"; desc.textContent = "Warte auf Gegner…"; } else { card.classList.remove("searching"); label.textContent = HT_LABELS[mode] || mode; desc.textContent = HT_DESCS[mode] || ""; } } function showHtQueueStatus(mode, poolSize) { const box = document.getElementById("ht-queue-status"); if (!box) return; const pool = poolSize ? ` · ${poolSize} im Pool` : ""; const label = mode === "daily" ? "Tagesherausforderung" : "Wochenherausforderung"; box.style.display = "block"; box.innerHTML = `⏳ Suche ${label}${pool}
Suche abbrechen`; document.getElementById("ht-cancel-btn")?.addEventListener("click", () => { cancelHtQueue(document.querySelector(`.ht-mode-card[data-mode="${mode}"]`), mode); }); } function hideHtQueueStatus() { const box = document.getElementById("ht-queue-status"); if (box) box.style.display = "none"; } function showHtError(msg) { const box = document.getElementById("ht-queue-status"); if (!box) return; box.style.cssText += ";display:block;animation:none;border-color:rgba(231,76,60,.5);color:#e74c3c;"; box.textContent = "❌ " + msg; setTimeout(() => { box.style.display = "none"; box.style.animation = ""; box.style.borderColor = ""; box.style.color = ""; }, 3000); } function cancelHtQueue(card, mode) { const socket = window._socket; if (socket) { socket.emit("ht_leave", { mode }); socket.off("ht_match_found"); socket.off("ht_queue_status"); } if (card) setHtCardSearching(card, mode, false); hideHtQueueStatus(); } function showHtMatchFoundOverlay(myName, opponentName, onDone) { if (document.getElementById("ht-match-found-overlay")) return; const overlay = document.createElement("div"); overlay.id = "ht-match-found-overlay"; overlay.innerHTML = `
🌤️ Herausforderung!
${myName}  vs  ${opponentName}
`; document.body.appendChild(overlay); requestAnimationFrame(() => { const b = document.getElementById("htMfBar"); if (b) b.style.width = "100%"; }); setTimeout(() => { overlay.remove(); onDone(); }, 1600); } function openHtPopup(src, opponentName, matchId) { document.getElementById("ht-backdrop")?.remove(); document.getElementById("ht-popup")?.remove(); const backdrop = document.createElement("div"); backdrop.id = "ht-backdrop"; const popup = document.createElement("div"); popup.id = "ht-popup"; popup.innerHTML = `
🌤️ Himmelstor · vs ${opponentName} ${matchId || src}
`; document.body.appendChild(backdrop); document.body.appendChild(popup); }