import { t, setLanguage, currentLang } from './i18n.js'; import { Assets, Sprite, NineSliceSprite, Container, TilingSprite, Text, wordWrap, fragmentGPUTemplate, Graphics, mapFormatToGlFormat } from 'pixi.js'; import { loginUser, registerUser, verifyEmailUser, resendOTPCode, requestPasswordResetUser, resetPasswordUser, refreshAccessTokenUser } from './api/auth.js'; import { initDropdowns, updateTranslations } from './ui/dropdowns.js'; import { initForms, formState, setOtpMode, setResetPasswordMode, startVisualCountdown, setRegisterMode } from './ui/forms.js'; import { fetchSectorsList, joinSectorUser } from './api/sectors.js'; import { app, initPixi, setupResize, triggerResize } from './core/pixi-app.js'; import { buildLoginScene, LOGIN_COLORS } from './scenes/login-scene.js'; import { buildLobbyBase, createSectorTabPixi, createSectorCardPixi, buildOnboardingModalPixi } from './scenes/lobby-scene.js'; let globalSheet = null; let background = null; let uiContainer = null; let lobbyPixiContainer = null; let sectorPixiContainer = null; let sectorTabsPixiContainer = null; let footerBackground = null; let footerPattern = null; let footerBackgroundContainer = null; let onboardingPixiContainer = null; let onboardingPixiRefs = null; let selectedSectorId = null; let selectedOnboardingGender = 'male'; let sectorPollingInterval = null; async function initGame() { await initPixi('pixi-container'); try { const assetsToLoad = { background: 'src/raw-assets/backgrounds/Background_A.png', uiAtlas: 'src/assets/ui-atlas.json' }; const backgroundTexture = await Assets.load(assetsToLoad.background); const uiSpritesheet = await Assets.load(assetsToLoad.uiAtlas); await document.fonts.load('16px "UI Font"'); await document.fonts.load('16px "Logo Font"'); updateTranslations(); buildScene(backgroundTexture, uiSpritesheet); document.getElementById('username').placeholder = t('ph_username_reg'); document.getElementById('email').placeholder = t('ph_email'); document.getElementById('password').placeholder = t('ph_password'); document.getElementById('btn-register').textContent = t('btn_register'); await checkAuthOnStartup(); } catch (error) { console.error("Fehler beim Laden der Assets:", error); } } function buildScene(bgTexture, sheet) { globalSheet = sheet; // --- Hintergrund --- background = new Sprite(bgTexture); app.stage.addChild(background); // --- Haupt-UI-Container --- uiContainer = new Container(); app.stage.addChild(uiContainer); lobbyPixiContainer = new Container(); lobbyPixiContainer.visible = false; app.stage.addChild(lobbyPixiContainer); sectorPixiContainer = buildLobbyBase(lobbyPixiContainer, sheet); const htmlSectorList = document.getElementById('sector-list-container'); if (htmlSectorList) { htmlSectorList.addEventListener('scroll', (e) => { sectorPixiContainer.y = -e.target.scrollTop; }); } // --- Footer Hintergund --- footerBackgroundContainer = new Container(); footerBackground = new TilingSprite({ texture: sheet.textures['SPR_SciFiMenus_Background_01'], height: 40, }); footerBackground.tileScale.set(1.0); footerBackground.tint = 0x000000; footerBackground.alpha = 1.0; footerBackgroundContainer.addChild(footerBackground); footerPattern = new TilingSprite({ texture: sheet.textures['SPR_SciFiMenus_Background_Hexagons_01'], height: 40, }); footerPattern.tileScale.set(0.3); footerPattern.tint = LOGIN_COLORS.PATTERN; footerPattern.alpha = 0.01; footerBackgroundContainer.addChild(footerPattern); app.stage.addChild(footerBackgroundContainer); onboardingPixiRefs = buildOnboardingModalPixi(app.stage, sheet); onboardingPixiContainer = onboardingPixiRefs.container; const loginRefs = buildLoginScene(uiContainer, sheet); initForms({ tabLogin: loginRefs.tabLogin, tabRegister: loginRefs.tabRegister, inputUser: loginRefs.inputUser, inputEmail: loginRefs.inputEmail, inputLock: loginRefs.inputLock, btnRegister: loginRefs.btnRegister, discordIcon: loginRefs.discordIcon, discordText: loginRefs.discordText, COLOR_TAB_ACTIVE: LOGIN_COLORS.TAB_ACTIVE, COLOR_TAB_INACTIVE: LOGIN_COLORS.TAB_INACTIVE, COLOR_TAB_HOVER: LOGIN_COLORS.TAB_HOVER, COLOR_BTN_HOVER: LOGIN_COLORS.BTN_HOVER, COLOR_BTN_DEFAULT: LOGIN_COLORS.BTN_DEFAULT }); setupResize({ background, discordContainer: loginRefs.discordContainer, logoContainer: loginRefs.logoContainer, uiContainer, panelFrame: loginRefs.panelFrame, lobbyPixiContainer, footerBackground, footerPattern, footerBackgroundContainer, onboardingPixiContainer }); formState.isLoginMode = true; setRegisterMode(); } const form = document.getElementById('register-form'); const msgBox = document.getElementById('msg-box'); form.addEventListener('submit', async (e) => { e.preventDefault(); const usernameVal = document.getElementById('username').value; const emailVal = document.getElementById('email').value; const passwordVal = document.getElementById('password').value; msgBox.textContent = '...'; msgBox.style.color = 'var(--color-text-main)'; if (formState.isForgotPasswordMode) { try { const result = await requestPasswordResetUser(emailVal); if (!result.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(result.data.error) || t('ERR_INTERNAL_SERVER'); return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t(result.data.message); setTimeout(() => { setResetPasswordMode(emailVal); }, 1500); } catch (error) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } return; } if (formState.isResetPasswordMode) { try { const result = await resetPasswordUser(formState.resetEmail, emailVal, passwordVal); if (!result.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(result.data.error) || t('ERR_INTERNAL_SERVER'); return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t(result.data.message); setTimeout(() => { window.location.reload(); }, 1500); } catch (error) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } return; } if (formState.isOtpMode) { try { const result = await verifyEmailUser(formState.pendingVerificationEmail, passwordVal); if (!result.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(result.data.error) || t('ERR_INTERNAL_SERVER'); return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t('msg_verify_success'); setTimeout(() => { window.location.reload(); }, 1500); } catch (error) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } return; } if (formState.isLoginMode) { try { const result = await loginUser(usernameVal, passwordVal); if (!result.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(result.data.error) || t('ERR_INTERNAL_SERVER'); if (result.data.error === 'ERR_EMAIL_NOT_VERIFIED') { setTimeout(() => { setOtpMode(result.data.email); }, 1500); } return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t(result.data.message); const rememberMe = document.getElementById('remember').checked; sessionStorage.setItem('void_access_token', result.data.accessToken); sessionStorage.setItem('void_user', JSON.stringify(result.data.user)); if (rememberMe) { localStorage.setItem('void_refresh_token', result.data.refreshToken); sessionStorage.removeItem('void_refresh_token'); } else { sessionStorage.setItem('void_refresh_token', result.data.refreshToken); localStorage.removeItem('void_refresh_token'); } transitionToLobby(); } catch (error) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } } else { // Register Mode try { const result = await registerUser(usernameVal, emailVal, passwordVal); if (!result.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(result.data.error)|| t('ERR_INTERNAL_SERVER'); return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t(result.data.message); setTimeout(() => { setOtpMode(emailVal); }, 1500); } catch (error) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } } }); const resendOtpLink = document.getElementById('resend-otp-link'); if (resendOtpLink) { resendOtpLink.addEventListener('click', async (e) => { e.preventDefault(); if (!pendingVerificationEmail) return; msgBox.textContent = '...'; msgBox.style.color = 'var(--color-text-main)'; try { const result = await resendOTPCode(pendingVerificationEmail); if (!result.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(result.data.error) || t('ERR_INTERNAL_SERVER'); return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t(result.data.message); startVisualCountdown(resendOtpLink, 'link_resend_otp', 120); } catch (error) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } }); } /** * Diese Funktion wird aufgerufenm wenn der Login erfolgreich war und die Tokens * sicher gespeichert sind, Sie baut das HTML-Formulat ab und bereitet PixiJS * auf die Lobby-Ansicht vor */ async function transitionToLobby() { const formElement = document.getElementById('register-form'); if (formElement) { formElement.style.display = 'none'; } uiContainer.visible = false; lobbyPixiContainer.visible = true; try { const lobbyBGPath = 'src/raw-assets/backgrounds/SPR_SciFiMenus_Example_Image_SpookyComputer_01.png'; const lobbyBGTexture = await Assets.load(lobbyBGPath); background.texture = lobbyBGTexture; triggerResize(); buildLobbyUI(); } catch (error) { console.error("Fehler beim Laden des Lobby-Hintergrunds:", error); } } /** * Blendet die Top-Bar ein, setzt den Spilernamen und aktiviert den Logout */ async function buildLobbyUI() { const userDropdown = document.getElementById('user-dropdown'); if (userDropdown) { userDropdown.style.display = 'block'; } const userDataStr = sessionStorage.getItem('void_user'); if (userDataStr) { try { const userData = JSON.parse(userDataStr); const usernameEl = document.getElementById('lobby-username'); if (usernameEl && userData.username) { usernameEl.textContent = userData.username; } const emailEl = document.getElementById('lobby-email'); if (emailEl && userData.email) { emailEl.textContent = userData.email; } const premiumEl = document.getElementById('lobby-premium'); if (premiumEl && userData.res_premium !== undefined) { premiumEl.textContent = userData.res_premium; } } catch (e) { console.error("Fehler beim Auslesen der User-Daten:", e); } } await loadAndRenderSectorBrowser(); } /** * Läd die Sektordaten von der API und steuert die Tab-Sichtbarkeiten */ async function loadAndRenderSectorBrowser() { const token = sessionStorage.getItem('void_access_token') || localStorage.getItem('void_access_token'); if (!token) return; try { const result = await fetchSectorsList(); if (!result.ok || !result.data.success) { console.error("Fehler beim Laden der Sektoren:", result.data.error); return; } const data = result.data; const layer = document.getElementById('sector-browser-layer'); layer.style.display = 'block'; const tabLastSector = document.getElementById('tab-last-sector'); const tabMySectors = document.getElementById('tab-my-sectors'); const tabNewSectors = document.getElementById('tab-new-sectors'); if (sectorTabsPixiContainer) { lobbyPixiContainer.removeChild(sectorTabsPixiContainer); sectorTabsPixiContainer.destroy({ children: true }); } sectorTabsPixiContainer = new Container(); lobbyPixiContainer.addChild(sectorTabsPixiContainer); const isMobilePortrait = window.innerWidth <= 800 && window.innerHeight > window.innerWidth; const centerPos = isMobilePortrait ? 325 : 620; if (data.mySectors.length === 0) { // Szenario A: Keine eigenen Sektoren tabLastSector.style.display = 'none'; tabMySectors.style.display = 'none'; tabNewSectors.classList.add('active'); const singleTabX = centerPos - 90; const tabNewBg = createSectorTabPixi(globalSheet, singleTabX, 20, LOGIN_COLORS.TAB_ACTIVE, 0.6); sectorTabsPixiContainer.addChild(tabNewBg.container); renderSectorList(data.newSectors, 'new'); } else { // Szenario B: Eigene Sektoren vorhanden tabLastSector.style.display = 'inline-block'; tabMySectors.style.display = 'inline-block'; tabNewSectors.style.display = 'inline-block'; const lastPlayedSectorArray = [data.mySectors[0]]; const otherMySectorsArray = data.mySectors.slice(1); const leftTabX = centerPos - 290; const midTabX = centerPos - 90; const rightTabX = centerPos + 110; const tabLastBg = createSectorTabPixi(globalSheet, leftTabX, 20, LOGIN_COLORS.TAB_ACTIVE, 0.6); const tabMyBg = createSectorTabPixi(globalSheet, midTabX, 20, LOGIN_COLORS.TAB_INACTIVE, 0.4); const tabNewBg = createSectorTabPixi(globalSheet, rightTabX, 20, LOGIN_COLORS.TAB_INACTIVE, 0.4); sectorTabsPixiContainer.addChild(tabLastBg.container, tabMyBg.container, tabNewBg.container); const updateTabVisuals = (activeHtmlId) => { tabLastSector.classList.remove('active'); tabMySectors.classList.remove('active'); tabNewSectors.classList.remove('active'); document.getElementById(activeHtmlId).classList.add('active'); tabLastBg.fill.tint = LOGIN_COLORS.TAB_INACTIVE; tabLastBg.fill.alpha = 0.4; tabMyBg.fill.tint = LOGIN_COLORS.TAB_INACTIVE; tabMyBg.fill.alpha = 0.4; tabNewBg.fill.tint = LOGIN_COLORS.TAB_INACTIVE; tabNewBg.fill.alpha = 0.4; if (activeHtmlId === 'tab-last-sector') { tabLastBg.fill.tint = LOGIN_COLORS.TAB_ACTIVE; tabLastBg.fill.alpha = 0.6; } if (activeHtmlId === 'tab-my-sectors') { tabMyBg.fill.tint = LOGIN_COLORS.TAB_ACTIVE; tabMyBg.fill.alpha = 0.6; } if (activeHtmlId === 'tab-new-sectors') { tabNewBg.fill.tint = LOGIN_COLORS.TAB_ACTIVE; tabNewBg.fill.alpha = 0.6; } }; tabLastSector.onclick = () => { updateTabVisuals('tab-last-sector'); renderSectorList(lastPlayedSectorArray, 'my'); }; tabMySectors.onclick = () => { updateTabVisuals('tab-my-sectors'); renderSectorList(otherMySectorsArray, 'my'); }; tabNewSectors.onclick = () => { updateTabVisuals('tab-new-sectors'); renderSectorList(data.newSectors, 'new'); }; const setupTabHover = (htmlEl, pixiRef) => { htmlEl.onmouseenter = () => { if (!htmlEl.classList.contains('active')) { pixiRef.fill.tint = LOGIN_COLORS.TAB_HOVER; pixiRef.fill.alpha = 0.8; } }; htmlEl.onmouseleave = () => { if (!htmlEl.classList.contains('active')) { pixiRef.fill.tint = LOGIN_COLORS.TAB_INACTIVE; pixiRef.fill.alpha = 0.4; } }; }; setupTabHover(tabLastSector, tabLastBg); setupTabHover(tabMySectors, tabMyBg); setupTabHover(tabNewSectors, tabNewBg); renderSectorList(lastPlayedSectorArray, 'my'); } console.log("[Polling] Initiales Rendern beendet. Aktiviere Hintergrund-Schleife..."); startSectorPolling(); } catch (error) { console.error("Netzwerkfehler beim Sektor-Abruf:", error); } } /** * Rendert die HTML-Karte und die dazugehöhrigen PixiJS-Hintergrundboxen */ function renderSectorList(sectors, type) { const listContainer = document.getElementById('sector-list-container'); listContainer.innerHTML = ''; if (sectorPixiContainer) { sectorPixiContainer.removeChildren(); sectorPixiContainer.y = 0; } activePixiCardsMap.clear(); const htmlSectorList = document.getElementById('sector-list-container'); if (htmlSectorList) htmlSectorList.scrollTop = 0; if (sectors.length === 0) { listContainer.innerHTML = '

Keine Sektoren in dieser Kategorie verfügbar.

'; return; } sectors.forEach((sector, index) => { const cardLink = document.createElement('a'); cardLink.className = 'sector-card-link'; cardLink.href = `#`; // HTML-Karte im DOM blitzschnell zu finden, ohne das Grid neu aufzubauen. cardLink.setAttribute('data-sector-id', sector.id); // --- Temporäre Platzhalter für das neue UI (ersetzen wir später durch echte DB-Werte) --- const ageDays = sector.age_days || Math.floor(Math.random() * 30) + 1; const totalPlayers = sector.total_players || Math.floor(Math.random() * 500) + 150; let bottomRowHtml = ''; if (type === 'my' && sector.local_username) { bottomRowHtml = `
Spieler: ${sector.local_username}
`; } cardLink.innerHTML = `
${sector.name} ${ageDays} Tage ${sector.status === 'online' ? 'online' : 'offline'} Online: ${sector.online_players}/${totalPlayers} Planeten: ${sector.planets_count} ${bottomRowHtml}
`; cardLink.onclick = (e) => { e.preventDefault(); console.log(`Starte Onboarding für Sektor: ${sector.name} (ID: ${sector.id})`); selectedSectorId = sector.id; const sectorBrowserLayer = document.getElementById('sector-browser-layer'); if (sectorBrowserLayer) { sectorBrowserLayer.style.opacity = 0.05; sectorBrowserLayer.style.pointerEvents = 'none'; } const onboardingHtmlLayer = document.getElementById('sector-onboarding-layer'); if (onboardingHtmlLayer) onboardingHtmlLayer.style.display = 'flex'; if (onboardingPixiContainer) onboardingPixiContainer.visible = true; const localUsernameInput = document.getElementById('local-username'); if (localUsernameInput) { const userDataStr = sessionStorage.getItem('void_user') || localStorage.getItem('void_user'); if (userDataStr) { try { const userData = JSON.parse(userDataStr); if (userData.username) { localUsernameInput.value = userData.username; } } catch (err) { console.error("Fehler beim Auslesen der User-Daten für das Onboarding:", err); } } } }; listContainer.appendChild(cardLink); const isMobilePortrait = window.innerWidth <= 800 && window.innerHeight > window.innerWidth; const columns = isMobilePortrait ? 2 : 4; const cardWidth = 270; const cardHeight = 90; const gapX = 25; const gapY = 20; const listContainerDOM = document.getElementById('sector-list-container'); const containerClientWidth = listContainerDOM ? listContainerDOM.clientWidth : (isMobilePortrait ? 650 : 1240); const gridBlockWidth = (columns * cardWidth) + ((columns - 1) * gapX); const startX = (containerClientWidth - gridBlockWidth) / 2; const startY = 100; // 80px Top-Bar-Offset + 20px Padding oben const col = index % columns; const row = Math.floor(index / columns); const posX = startX + col * (cardWidth + gapX); const posY = startY + row * (cardHeight + gapY); const cardRefs = createSectorCardPixi(globalSheet, sectorPixiContainer, posX, posY); activePixiCardsMap.set(sector.id, cardRefs); if (sector.status !== 'online') { cardRefs.bgSprite.tint = 0x3a0b0b; cardRefs.frameSprite.alpha = 0.4; } cardLink.addEventListener('mouseenter', () => { cardRefs.frameSprite.alpha = 1.0; cardRefs.bgSprite.alpha = 0.8; cardRefs.iconDetails.tint = 0x00f0ff; cardRefs.iconDetails.scale.set(0.09); cardRefs.iconDetails.rotation = 0.2; cardRefs.iconPlayers.tint = 0xffffff; cardRefs.iconPlanets.tint = 0xffffff; }); cardLink.addEventListener('mouseleave', () => { cardRefs.frameSprite.alpha = 0.8; cardRefs.bgSprite.alpha = 0.6; cardRefs.iconDetails.tint = 0xaaaaaa; cardRefs.iconDetails.scale.set(0.08); cardRefs.iconDetails.rotation = 0; cardRefs.iconPlayers.tint = 0xaaaaaa; cardRefs.iconPlanets.tint = 0xaaaaaa; }); }); } async function checkAuthOnStartup() { const refreshToken = localStorage.getItem('void_refresh_token') || sessionStorage.getItem('void_refresh_token'); if (!refreshToken) { return } try { const result = await refreshAccessTokenUser(refreshToken); if (result.ok && result.data.accessToken) { sessionStorage.setItem('void_access_token', result.data.accessToken); if (result.data.user) sessionStorage.setItem('void_user', JSON.stringify(result.data.user)); transitionToLobby(); } else { sessionStorage.removeItem('void_access_token'); sessionStorage.removeItem('void_refresh_token'); localStorage.removeItem('void_refresh_token'); sessionStorage.removeItem('void_user'); } } catch (error) { console.error("Netzwerkfehler beim Auto-Login:", error); } } initDropdowns(() => { const userField = document.getElementById('username'); if (userField) userField.placeholder = formState.isLoginMode ? t('ph_username_login') : t('ph_username_reg'); const emailField = document.getElementById('email'); if (emailField) emailField.placeholder = t('ph_email'); const passField = document.getElementById('password'); if (passField) passField.placeholder = formState.isOtpMode || formState.isResetPasswordMode ? t('ph_otp') : t('ph_password'); }); const activePixiCardsMap = new Map(); /** * Startet das automatische Short Polling für die Sektordaten. * Die Schlöeife läuft lautlos im Hintergrund, solange der Spieler in der lobby ist. */ function startSectorPolling() { if (sectorPollingInterval) { clearInterval(sectorPollingInterval) } sectorPollingInterval = setInterval(async () => { if (!lobbyPixiContainer || !lobbyPixiContainer.visible) { return; } console.log("[Polling] Starte lautlos API-Abruf f+r Sektor-Updates..."); await refreshSectorDataSilent(); }, 30000); } /** * Stoppt das Sektor-Polling und bereinigt Referenzen. * Wird beim Logount oder bei Verbindungsfehlern aufgerufen. */ function stopSectorPolling() { if (sectorPollingInterval) { clearInterval(sectorPollingInterval); sectorPollingInterval = null; console.log("[Polling] Sektor-Polling erfolgreich gestoppt."); } activePixiCardsMap.clear(); } /** * Holt die aktuellen Sektordaten lautlos vom Server und aktualisiert * die bestehenden UI-Elemente, ohne das Grid oder Canvas neu aufzubauen. */ async function refreshSectorDataSilent() { const token = sessionStorage.getItem('void_access_token') || localStorage.getItem('void_access_token'); if (!token) { stopSectorPolling(); return; } try { const result = await fetchSectorsList(); if (!result.ok || !result.data.success) { console.error("[Polling] Fehler beim lautlosen Abruf:", result.data.error); return; } const data = result.data; // --- ALLE SEKTOREN ZUSAMMENFASSEN --- // Es ist uns völlig egal, ob ein Sektor unter 'mySectors' oder 'newSectors' läuft. // Wir werfen beide Arrays mit dem Spread-Operator (...) in eine einzige Liste. const allSectors = [ ...(data.mySectors || []), ...(data.newSectors || []) ]; // Wir übergeben einfach die komplette Liste an die Update-Funktion. // Diese sucht sich dann selbstständig die Karten aus dem HTML, die gerade da sind. updateLiveStatus(allSectors, 'all'); } catch (error) { console.error("[Polling] Netzwerkfehler während des Pollings:", error); } } /** * Iteriert durch die bestehenden DOM-Elemente und Pixi-Sprites, * um ausschließlich Status, Spielerzahlen und Planeten-Counts zu aktualisieren. * @param {Array} sectors - Das aktualisierte Sektoren-Array aus dem API-Response * @param {string} type - 'my' oder 'new' */ function updateLiveStatus(sectors, type) { sectors.forEach((sector) => { // Findet die HTML-Karte über das Sektor-ID-Attribut const cardLink = document.querySelector(`.sector-card-link[data-sector-id="${sector.id}"]`); if (cardLink) { // 1. HTML-Status Text & Klasse aktualisieren (Online / Offline) const statusEl = cardLink.querySelector('.sector-status'); if (statusEl) { statusEl.textContent = sector.status === 'online' ? 'online' : 'offline'; statusEl.className = `sector-status ${sector.status === 'online' ? 'status-online' : 'status-maintenance'}`; } // 2. HTML-Spielerzahlen über die neue, eindeutige Klasse aktualisieren const midRowEl = cardLink.querySelector('.sector-online-count'); if (midRowEl) { // Wir behalten deine Logik bei, falls die Gesamtzahl temporär berechnet werden muss const totalPlayers = sector.total_players || Math.floor(Math.random() * 500) + 150; midRowEl.textContent = `Online: ${sector.online_players}/${totalPlayers}`; } // 3. HTML-Planetenanzahl über die neue, eindeutige Klasse aktualisieren const planetsEl = cardLink.querySelector('.sector-planets-count'); if (planetsEl) { planetsEl.textContent = `Planeten: ${sector.planets_count}`; } } // 4. PixiJS Texturen und Tints über die Map-Referenz manipulieren const cardRefs = activePixiCardsMap.get(sector.id); if (cardRefs) { if (sector.status === 'online') { // Standard Sci-Fi Blau-Dunkel für den Hintergrund cardRefs.bgSprite.tint = 0x0b192c; // Rahmen wieder voll sichtbar schalten, wenn er aus der Wartung kommt cardRefs.frameSprite.alpha = sector.status === 'online' ? 0.8 : 0.4; } else { // Bei Offline-Status färben wir das Pixi-Feld düster Rot ein cardRefs.bgSprite.tint = 0x3a0b0b; cardRefs.frameSprite.alpha = 0.4; } } }); } const btnGenderMale = document.getElementById('btn-gender-male'); const btnGenderFemale = document.getElementById('btn-gender-female'); const btnSubmitOnboarding = document.getElementById('btn-submit-onboarding'); const onboardingForm = document.getElementById('onboarding-form'); const btnCancelOnboarding = document.getElementById('btn-cancel-onboarding'); if (btnCancelOnboarding) { btnCancelOnboarding.addEventListener('click', (e) => { e.preventDefault(); // 1. Sektor-Browser HTML wieder voll sichtbar machen const sectorBrowserLayer = document.getElementById('sector-browser-layer'); if (sectorBrowserLayer) { sectorBrowserLayer.style.opacity = '1.0'; sectorBrowserLayer.style.pointerEvents = 'auto'; // Klicks wieder erlauben } // 2. HTML-Layer des Modals ausblenden const onboardingHtmlLayer = document.getElementById('sector-onboarding-layer'); if (onboardingHtmlLayer) onboardingHtmlLayer.style.display = 'none'; // 3. PixiJS-Layer ausblenden if (onboardingPixiContainer) onboardingPixiContainer.visible = false; // 4. Formular zurücksetzen (damit alte Eingaben verschwinden) const onboardingForm = document.getElementById('onboarding-form'); if (onboardingForm) onboardingForm.reset(); // Optional: Hier auch noch die visuelle 'active' Klasse von deinen // Geschlechter-Buttons entfernen, falls nötig. }); } // --- Hilfsfunktion: Hover-Effekte synchronisieren --- // Genau wie beim Login übertragen wir den Mauszeiger vom unsichtbaren HTML auf PixiJS function syncHover(htmlElement, pixiRef, defaultTint, hoverTint = LOGIN_COLORS.TAB_HOVER) { if (!htmlElement || !pixiRef) return; htmlElement.addEventListener('mouseenter', () => { // Wenn der Button gerade "aktiv" ist (z.B. gewähltes Geschlecht), ignorieren wir den Hover if (!htmlElement.classList.contains('active')) { pixiRef.fill.tint = hoverTint; pixiRef.fill.alpha = 0.8; } }); htmlElement.addEventListener('mouseleave', () => { if (!htmlElement.classList.contains('active')) { pixiRef.fill.tint = defaultTint; pixiRef.fill.alpha = 0.6; // Zurück zur Standard-Transparenz } }); } // Hover-Effekte auf die Buttons anwenden (falls onboardingPixiRefs schon existiert) // Wir prüfen hier sicherheitshalber ab, da die Refs asynchron generiert werden const applyHoverEffects = setInterval(() => { if (onboardingPixiRefs) { clearInterval(applyHoverEffects); // Sobald geladen, Interval stoppen // Geschlechter-Buttons (Standard-Farbe ist Inactive, Hover ist Weiß) syncHover(btnGenderMale, onboardingPixiRefs.btnMale, LOGIN_COLORS.TAB_INACTIVE); syncHover(btnGenderFemale, onboardingPixiRefs.btnFemale, LOGIN_COLORS.TAB_INACTIVE); // Aktions-Buttons (Standard-Farbe ist Default-Grün) syncHover(btnCancelOnboarding, onboardingPixiRefs.btnCancel, LOGIN_COLORS.BTN_DEFAULT, LOGIN_COLORS.BTN_HOVER); syncHover(btnSubmitOnboarding, onboardingPixiRefs.btnJoin, LOGIN_COLORS.BTN_DEFAULT, LOGIN_COLORS.BTN_HOVER); } }, 100); // --- Geschlecht Umschalten (Toggle-Logik) --- if (btnGenderMale && btnGenderFemale) { btnGenderMale.addEventListener('click', (e) => { e.preventDefault(); selectedOnboardingGender = 'male'; // HTML Klassen updaten (für Text-Farbe) btnGenderMale.classList.add('active'); btnGenderFemale.classList.remove('active'); // PixiJS Farben updaten if (onboardingPixiRefs) { onboardingPixiRefs.btnMale.fill.tint = LOGIN_COLORS.TAB_ACTIVE; onboardingPixiRefs.btnMale.fill.alpha = 0.6; onboardingPixiRefs.btnFemale.fill.tint = LOGIN_COLORS.TAB_INACTIVE; onboardingPixiRefs.btnFemale.fill.alpha = 0.4; } }); btnGenderFemale.addEventListener('click', (e) => { e.preventDefault(); selectedOnboardingGender = 'female'; // HTML Klassen updaten btnGenderFemale.classList.add('active'); btnGenderMale.classList.remove('active'); // PixiJS Farben updaten if (onboardingPixiRefs) { onboardingPixiRefs.btnFemale.fill.tint = LOGIN_COLORS.TAB_ACTIVE; onboardingPixiRefs.btnFemale.fill.alpha = 0.6; onboardingPixiRefs.btnMale.fill.tint = LOGIN_COLORS.TAB_INACTIVE; onboardingPixiRefs.btnMale.fill.alpha = 0.4; } }); } // --- Formular Absenden (Beitreten) --- if (onboardingForm) { onboardingForm.addEventListener('submit', async (e) => { e.preventDefault(); // Verhindert das Neuladen der Seite if (!selectedSectorId) return; const localUsername = document.getElementById('local-username').value; const msgBox = document.getElementById('onboarding-msg-box'); msgBox.textContent = '...'; msgBox.style.color = 'var(--color-text-main)'; const token = sessionStorage.getItem('void_access_token') || localStorage.getItem('void_access_token'); try { const result = await joinSectorUser(token, selectedSectorId, localUsername, selectedOnboardingGender); if (!result.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(result.data.error) || t('ERR_INTERNAL_SERVER'); return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t(result.data.message); setTimeout(() => { if (btnCancelOnboarding) btnCancelOnboarding.click(); loadAndRenderSectorBrowser(); }, 1500); } catch (error) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } }); } initGame();