import { t, setLanguage, currentLang } from './i18n.js'; import { Application, Assets, Sprite, NineSliceSprite, Container, TilingSprite, Text, wordWrap, fragmentGPUTemplate } from 'pixi.js'; async function initGame() { const app = new Application(); await app.init({ resizeTo: window, backgroundAlpha: 0, antialias: true, resolution: window.devicePixelRatio || 1, autoDensity: true, }); document.getElementById('pixi-container').appendChild(app.canvas); try { const assetsToLoad = { //background: 'src/raw-assets/backgrounds/SPR_SciFiMenus_Example_Image_SpookyComputer_01.png', //background: 'src/raw-assets/backgrounds/SPR_SciFiMenus_Example_Image_SpaceStation_01.png', 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(app, 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'); } catch (error) { console.error("Fehler beim Laden der Assets:", error); } } function updateTranslations() { const elements = document.querySelectorAll('[data-i18n]'); elements.forEach(el => { const key = el.getAttribute('data-i18n'); el.textContent = t(key); }); } function buildScene(app, bgTexture, sheet) { // --- Farb-Variablen (pixiJS) --- const COLOR_PANEL_BG = 0x0b192c; // Dunkelblau für Panel & Inputs const COLOR_PATTERN = 0x00f0ff; // Cyan für das Hexagon-Muster const COLOR_TAB_ACTIVE = 0x00f0ff; // Cyan für aktiven Tab const COLOR_TAB_INACTIVE = 0xaaaaaa; // Grau für inaktiven Tab const COLOR_TAB_HOVER = 0xffffff; // Weiß beim Hovern der Tabs const COLOR_BTN_DEFAULT = 0x00aa00; // Dunkelgrün für Registrieren Button const COLOR_BTN_HOVER = 0x00ff00; // Hellgrün beim Hovern des Buttons const COLOR_LOGO = 0x00f0ff; // Farbe des Logos const COLOR_DISCORD_TEXT = 0xdaffde; // Hellgrün für Discord Text const COLOR_DISCORD_TEXT_HOVER = 0xffffff; // Weiß beim Hovern des Discord Links // --- Asset-Namen --- const frameInput = 'SPR_SciFiMenus_Menu_Item_Selected_19'; // Rahmen für Eingabefelder const frameBtn = 'SPR_SciFiMenus_Menu_Item_Selected_10'; // Rahmen für Tabs/Buttons const menuBgName = 'SPR_SciFiMenus_Menu_Item_Background_07'; // Füll Asset für die Rahmen const panelBgName = 'SPR_SciFiMenus_Frame_Box_Large_02_Background'; // Füll Asset für das große Panel const patternBgName = 'SPR_SciFiMenus_Background_Hexagons_01'; // Kachelbares Pattern const panelFmName = 'SPR_SciFiMenus_Frame_Box_Large_02'; // Rahmen für das große Panel // --- Icons --- const iconUserName = 'ICON_SciFiMenus_Menu_Solo_01_Clean'; const iconEmailName = 'ICON_SciFi_Map_Message'; const iconLockName = 'ICON_SciFiMenus_Menu_Lock_Closed_01_Clean'; // --- Logo Ring --- const logoRingName = 'SPR_SciFiMenus_Ring_Medium_13'; // --- Discord Panel --- const discordFrameName = 'SPR_SciFiMenus_Frame_Box_Large_12'; const discordBgName = 'SPR_SciFiMenus_Frame_Box_Large_12_Background'; const discordIconName = 'ICON_Social_Discord'; // --- Hintergrund --- const background = new Sprite(bgTexture); app.stage.addChild(background); // --- Haupt-UI-Container --- const uiContainer = new Container(); app.stage.addChild(uiContainer); const panelBackground = new Sprite(sheet.textures[panelBgName]); panelBackground.tint = COLOR_PANEL_BG; panelBackground.width = 500; panelBackground.height = 520; panelBackground.position.set(0, -10); uiContainer.addChild(panelBackground); // --- Footer Hintergund in PixiJS kacheln const footerBackgroundContainer = new Container(); const 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); app.stage.addChild(footerBackgroundContainer); const footerPattern = new TilingSprite({ texture: sheet.textures[patternBgName], height: 40, }); footerPattern.tileScale.set(0.3); footerPattern.tint = COLOR_PATTERN; footerPattern.alpha = 0.01; footerBackgroundContainer.addChild(footerPattern); app.stage.addChild(footerBackgroundContainer); // --- Hexagon pattern const pattern = new TilingSprite({ texture: sheet.textures[patternBgName], width: 430, height: 520, }); pattern.tileScale.set(0.3); pattern.position.set(25, -10); pattern.alpha = 0.05; pattern.tint = COLOR_PATTERN; uiContainer.addChild(pattern); // --- Main UI const panelFrame = new NineSliceSprite({ texture: sheet.textures[panelFmName], leftWidth: 510, topHeight: 510, rightWidth: 510, bottomHeight: 510 }); panelFrame.width = 500; panelFrame.height = 500; uiContainer.addChild(panelFrame); // --- Logo --- const logoContainer = new Container(); logoContainer.position.set(250, -120); const logoRing = new Sprite(sheet.textures[logoRingName]); logoRing.anchor.set(0.5); logoRing.tint = COLOR_LOGO; logoRing.alpha = 1.0; logoRing.scale.set(0.2); logoContainer.addChild(logoRing); const logoText = new Text({ text: 'Void-Genesis', style: { fontFamily: 'Logo Font', fontSize: 50, fill: COLOR_LOGO, letterSpacing: 8, } }); logoText.anchor.set(0.5); logoContainer.addChild(logoText); uiContainer.addChild(logoContainer); // --- UI Elemente --- const tabLogin = createUIItem(65, 60, 180, 40, frameBtn, null, COLOR_TAB_INACTIVE, 0.6); const tabRegister = createUIItem(255, 60, 180, 40, frameBtn, null, COLOR_TAB_ACTIVE, 0.4); const inputUser = createUIItem(115, 135, 300, 50, frameInput, iconUserName, COLOR_PANEL_BG, 0.8); const inputEmail = createUIItem(115, 200, 300, 50, frameInput, iconEmailName, COLOR_PANEL_BG, 0.8); const inputLock = createUIItem(115, 265, 300, 50, frameInput, iconLockName, COLOR_PANEL_BG, 0.8); const btnRegister = createUIItem(65, 390, 370, 55, frameBtn, null, COLOR_BTN_DEFAULT, 0.6); // --- Discord Panel --- const discordContainer = new Container(); discordContainer.position.set(640, 380); const discordBackground = new NineSliceSprite({ texture: sheet.textures[discordBgName], leftWidth: 0, topHeight: 0, rightWidth: 0, bottomHeight: 0 }); discordBackground.tint = COLOR_PANEL_BG; discordBackground.width = 255; discordBackground.height = 75; discordContainer.addChild(discordBackground); const discordPattern = new TilingSprite({ texture: sheet.textures[patternBgName], width: 255, height: 75, }); discordPattern.tileScale.set(0.3); discordPattern.tint = COLOR_PATTERN; discordPattern.alpha = 0.05; discordContainer.addChild(discordPattern); const discordFrame = new NineSliceSprite({ texture: sheet.textures[discordFrameName], leftWidth: 0, topHeight: 0, rightWidth: 0, bottomHeight: 0 }); discordFrame.width = 280; discordFrame.height = 85; discordFrame.position.set(-15, -10); discordContainer.addChild(discordFrame); const discordIcon = new Sprite(sheet.textures[discordIconName]); discordIcon.position.set(10, 15); discordIcon.scale.set(0.15); discordIcon.tint = COLOR_TAB_INACTIVE; discordContainer.addChild(discordIcon); const discordText = new Text({ text: t('discord_text'), style: { fontFamily: 'UI Font', fontSize: 20, fill: COLOR_TAB_INACTIVE, wordWrap: false, wordWrapWidth: 120 } }); discordText.position.set(60, 20); discordContainer.addChild(discordText); uiContainer.addChild(discordContainer); const htmlTabLogin = document.getElementById('tab-login'); const htmlTabRegistert = document.getElementById('tab-register'); const htmlButton = document.getElementById('btn-register'); let isLoginMode = false; let isForgotPasswordMode = false; let isResetPasswordMode = false; let isOtpMode = false; let pendingVerificationEmail = ''; let resetEmail = ''; let cooldownTimer = null; htmlTabLogin.addEventListener('mouseenter', () => { if (!isLoginMode) { tabLogin.fill.tint = COLOR_TAB_HOVER; tabLogin.fill.alpha = 0.8; } }); htmlTabLogin.addEventListener('mouseleave', () => { if (!isLoginMode) { tabLogin.fill.tint = COLOR_TAB_INACTIVE; tabLogin.fill.alpha = 0.4; } }); htmlTabRegistert.addEventListener('mouseenter', () => { if (isLoginMode) { tabRegister.fill.tint = COLOR_TAB_HOVER; tabRegister.fill.alpha = 0.8; } }); htmlTabRegistert.addEventListener('mouseleave', () => { if (isLoginMode) { tabRegister.fill.tint = COLOR_TAB_INACTIVE; tabRegister.fill.alpha = 0.4; } }); htmlButton.addEventListener('mouseenter', () => { btnRegister.fill.tint = COLOR_BTN_HOVER; btnRegister.fill.alpha = 1.0; }); htmlButton.addEventListener('mouseleave', () => { btnRegister.fill.tint = COLOR_BTN_DEFAULT; btnRegister.fill.alpha = 0.6; }); const htmlDiscord = document.getElementById('discord-link'); if (htmlDiscord) { htmlDiscord.addEventListener('mouseenter', () => { discordIcon.tint = COLOR_TAB_HOVER; discordText.style.fill = COLOR_TAB_HOVER; }); htmlDiscord.addEventListener('mouseleave', () => { discordIcon.tint = COLOR_TAB_INACTIVE; discordText.style.fill = COLOR_TAB_INACTIVE; }); } function setLoginMode() { if (isLoginMode) return; isLoginMode = true; clearForm(); tabLogin.fill.tint = COLOR_TAB_ACTIVE; tabLogin.fill.alpha = 0.6; tabRegister.fill.tint = COLOR_TAB_INACTIVE; tabRegister.fill.alpha = 0.4; htmlTabLogin.classList.add('active'); htmlTabRegistert.classList.remove('active'); inputEmail.container.visible = false; document.getElementById('group-email').style.display = 'none'; inputLock.container.y = 200; document.getElementById('password').style.top = '200px'; document.getElementById('forgot-password-link').style.display = 'block'; document.getElementById('email').required = false; document.getElementById('group-remember').style.display = 'flex'; document.getElementById('group-agb').style.display = 'none'; document.getElementById('agb').required = false; htmlButton.textContent = t('btn_login'); document.getElementById('username').placeholder = t('ph_username_login'); document.getElementById('password').placeholder = t('ph_password'); document.getElementById('msg-box').textContent = ''; } function setRegisterMode() { if (!isLoginMode) return; isLoginMode = false; clearForm(); tabRegister.fill.tint = COLOR_TAB_ACTIVE; tabRegister.fill.alpha = 0.6; tabLogin.fill.tint = COLOR_TAB_INACTIVE; tabLogin.fill.alpha = 0.4; htmlTabRegistert.classList.add('active'); htmlTabLogin.classList.remove('active'); inputEmail.container.visible = true; document.getElementById('group-email').style.display = 'block'; inputLock.container.y = 265; document.getElementById('password').style.top = '265px'; document.getElementById('email').required = true; document.getElementById('group-remember').style.display = 'none'; document.getElementById('group-agb').style.display = 'flex'; document.getElementById('agb').required = true; htmlButton.textContent = t('btn_register'); document.getElementById('username').placeholder = t('ph_username_reg'); document.getElementById('email').placeholder = t('ph_email'); document.getElementById('password').placeholder = t('ph_password'); document.getElementById('msg-box').textContent = ''; } function setOtpMode(email) { isOtpMode = true; pendingVerificationEmail = email; clearForm(); // Tabs verstecken document.getElementById('tab-container').style.display = 'none'; tabLogin.container.visible = false; tabRegister.container.visible = false; // Username-Feld verstecken document.getElementById('username').style.display = 'none'; document.getElementById('username').required = false; inputUser.container.visible = false; // Email-Feld verstecken document.getElementById('group-email').style.display = 'none'; document.getElementById('email').required = false; inputEmail.container.visible = false; // Das Erklärungstext-Feld einblenden const instructionEl = document.getElementById('otp-instruction'); instructionEl.style.display = 'block'; instructionEl.textContent = t('txt_otp_instruction'); // Den "Code erneut senden" Link einblenden document.getElementById('resend-otp-link').style.display = 'block'; // Das Passwort-Feld in die Mitte rücken und zum OTP-Feld umwandeln // Wir nutzen das Schluss-Icon, das passt perfekt zum otp inputLock.container.y = 200; document.getElementById('password').style.top = '200px'; // WICHTIG: Typ auf 'text äbderb damit der Nutzer die Zahlen sieht document.getElementById('password').type = 'text'; document.getElementById('password').placeholder = t('ph_otp'); document.getElementById('password').value = ''; // Checkboxen aufräumen document.getElementById('group-agb').style.display = 'none'; document.getElementById('agb').required = false; // Die "Angemeldet bleiben" Checkbox lassen wir optional drin document.getElementById('group-remember').style.display = 'none'; // Button aktuallisieren htmlButton.textContent = t('btn_verify'); document.getElementById('msg-box').textContent = ''; document.getElementById('back-link').style.display = 'block'; // Debug Only //console.log("OTP EMAIL", email); } function setForgotPasswordMode() { isLoginMode = false; isOtpMode = false; isForgotPasswordMode = true; isResetPasswordMode = false; clearForm(); // Tabs verstecken document.getElementById('tab-container').style.display = 'none'; tabLogin.container.visible = false; tabRegister.container.visible = false; // Username-Feld verstecken document.getElementById('username').style.display = 'none'; document.getElementById('username').required = false; inputUser.container.visible = false; // Passwort-Feld verstecken document.getElementById('password').style.display = 'none'; document.getElementById('password').required = false; inputLock.container.visible = false; document.getElementById('group-email').style.display = 'block'; document.getElementById('email').type = 'email'; document.getElementById('email').required = true; document.getElementById('email').style.top = '200px'; inputEmail.container.visible = true; inputEmail.container.y = 200; document.getElementById('group-remember').style.display = 'none'; document.getElementById('group-agb').style.display = 'none'; document.getElementById('forgot-password-link').style.display = 'none'; const instructionEl = document.getElementById('otp-instruction'); instructionEl.style.display = 'block'; instructionEl.textContent = t('txt_reset_instruction_1'); document.getElementById('back-link').style.display = 'block'; htmlButton.textContent = t('btn_reset_request'); document.getElementById('msg-box').textContent = ''; } function setResetPasswordMode(email) { isForgotPasswordMode = false; isResetPasswordMode = true; resetEmail = email; clearForm(); // Tabs verstecken document.getElementById('tab-container').style.display = 'none'; tabLogin.container.visible = false; tabRegister.container.visible = false; // Username-Feld verstecken document.getElementById('username').style.display = 'none'; document.getElementById('username').required = false; inputUser.container.visible = false; document.getElementById('group-email').style.display = 'block'; document.getElementById('email').type = 'text'; document.getElementById('email').placeholder = t('ph_otp'); document.getElementById('email').required = true; document.getElementById('email').style.top = '200px'; inputEmail.container.y = 200; document.getElementById('password').style.display = 'block'; document.getElementById('password').required = true; document.getElementById('password').placeholder = t('ph_password'); document.getElementById('password').style.top = '265px'; inputLock.container.visible = true; inputLock.container.y = 265; const instructionEl = document.getElementById('otp-instruction'); instructionEl.style.display = 'block'; instructionEl.style.top = '150px'; instructionEl.textContent = t('txt_reset_instruction_2'); document.getElementById('back-link').style.display = 'block'; htmlButton.textContent = t('btn_reset_password'); document.getElementById('msg-box').textContent = ''; } function createUIItem(x, y, width, height, frameTextureName, iconTextureName, fillTint, fillAlpha) { const itemContainer = new Container(); itemContainer.position.set(x, y); const fill = new NineSliceSprite({ texture: sheet.textures[menuBgName], leftWidth: 120, topHeight: 120, rightWidth: 120, bottomHeight: 120 }); fill.width = width; fill.height = height; fill.tint = fillTint; fill.alpha = fillAlpha; itemContainer.addChild(fill); const frame = new NineSliceSprite({ texture: sheet.textures[frameTextureName], leftWidth: 100, topHeight: 120, rightWidth: 140, bottomHeight: 120 }); frame.width = width; frame.height = height; frame.alpha = 0.8; itemContainer.addChild(frame); if (iconTextureName) { const icon = new Sprite(sheet.textures[iconTextureName]); icon.position.set(-45, (height - (icon.height * 0.15)) / 2 - 5); icon.scale.set(0.15); itemContainer.addChild(icon); } uiContainer.addChild(itemContainer); return { container: itemContainer, fill, frame }; } function resize() { const screenWidth = window.innerWidth; const screenHeight = window.innerHeight; const scaleX = screenWidth / background.texture.width; const scaleY = screenHeight / background.texture.height; const scale = Math.max(scaleX, scaleY); background.scale.set(scale); background.x = (screenWidth - background.width) / 2; background.y = (screenHeight - background.height) / 2; let panelScale = 1.0; let panelOffsetY = 0; if (screenHeight <= 500){ panelScale = 0.4; panelOffsetY = 0; discordContainer.position.set(-420, 350); logoContainer.position.set(-300, 150); logoContainer.scale.set(1.3); } else if (screenWidth <= 600) { panelScale = 0.7; panelOffsetY = 0; discordContainer.position.set(125, 520); logoContainer.position.set(250, -120); logoContainer.scale.set(0.7); } else { panelScale = 1.0; panelOffsetY = 0; discordContainer.position.set(640, 380); logoContainer.position.set(250, -120); logoContainer.scale.set(1.0); if (screenHeight <= 1050) { panelOffsetY = 80; } } uiContainer.scale.set(panelScale); uiContainer.x = (screenWidth - (panelFrame.width * panelScale)) / 2; uiContainer.y = ((screenHeight - (panelFrame.height * panelScale)) / 2) + panelOffsetY; const htmlFooter = document.getElementById('game-footer'); const footerHeight = (htmlFooter && htmlFooter.style.display !== 'none') ? htmlFooter.offsetHeight : 0; const targetFooterHeight = Math.max(footerHeight, 40); footerBackground.height = targetFooterHeight; footerPattern.height = targetFooterHeight; footerBackground.width = screenWidth; footerPattern.width = screenWidth; footerBackgroundContainer.y = screenHeight - footerHeight; } htmlTabLogin.addEventListener('click', setLoginMode); htmlTabRegistert.addEventListener('click', setRegisterMode); app.renderer.on('resize', resize); resize(); // API-Anbindung (login & Registrierung) const API_BASE_URL = 'https://api-dev.void-genesis.de/api/auth'; const form = document.getElementById('register-form'); const msgBox = document.getElementById('msg-box'); // Wir fangen das 'submit' Event des HTML-Formulars ab // Das Event feuert, wenn der Nutzer auf den Button klickt oder Enter drückt 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 (isForgotPasswordMode) { try { const response = await fetch (`${API_BASE_URL}/forgot-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: emailVal }) }); const data = await response.json(); if (!response.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(data.error) || t('ERR_INTERNAL_SERVER'); return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t(data.message); setTimeout(() => { setResetPasswordMode(emailVal); }, 1500); } catch (error) { console.error("Netzwerkfehler bei Passwort-Reset-Anfrage:". error); msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } return; } if (isResetPasswordMode) { try { const response = await fetch (`${API_BASE_URL}/reset-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: resetEmail, newPassword: passwordVal }) }); const data = await response.json(); if (!response.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(data.error) || t('ERR_INTERNAL_SERVER'); return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t(data.message); setTimeout(() => { window.location.reload(); }, 1500); } catch (error) { console.error("Netzwerkfehler bei Passwort-Reset-Anfrage:". error); msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } return; } if (isOtpMode) { try { const response = await fetch (`${API_BASE_URL}/verify-email`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: pendingVerificationEmail, otpCode: passwordVal }) }); const data = await response.json(); if (!response.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(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) { console.error("Netzwerkfehler bei Verifizieung:". error); msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } return; } if (isLoginMode) { try { const response = await fetch (`${API_BASE_URL}/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identifier: usernameVal, password: passwordVal }) }); const data = await response.json(); if (!response.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(data.error) || t('ERR_INTERNAL_SERVER'); if (data.error === 'ERR_EMAIL_NOT_VERIFIED') { setTimeout(() => { setOtpMode(data.email); }, 1000); } return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t(data.message); // TODO JWT Tokens im LocalStorage/SessionStorage ablegen und Szene wechseln const rememberMe = document.getElementById('remember').checked; sessionStorage.setItem('void_access_token', data.accessToken); sessionStorage.setItem('void_user', JSON.stringify(data.user)); if (rememberMe) { localStorage.setItem('void_refresh_token', data.refreshToken); sessionStorage.removeItem('void_refresh_token'); } else { sessionStorage.setItem('void_refresh_token', data.refreshToken); localStorage.removeItem('void_refresh_token'); } // Debug only //console.log("Login erfolgreich! Tokens wurden verarbeitet."); transitionToLobby(); } catch (error) { console.error("Netzwerkfehler beim Login:". error); msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } } else { // Register Mode try { const response = await fetch (`${API_BASE_URL}/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: usernameVal, email: emailVal, password: passwordVal }) }); const data = await response.json(); if (!response.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(data.error) || t('ERR_INTERNAL_SERVER'); return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t(data.message); setTimeout(() => { setOtpMode(emailVal); }, 1000); // Debug only console.log("Registrierung erfolgreich! Account ID:", data.accountId); } catch (error) { console.error("Netzwerkfehler bei Registrierung:". error); msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } } }); function clearForm() { document.getElementById('username').value = ''; document.getElementById('email').value = ''; document.getElementById('password').value = ''; document.getElementById('remember').checked = false; document.getElementById('agb').checked = false; document.getElementById('tab-container').style.display = 'block'; tabLogin.container.visible = true; tabRegister.container.visible = true; document.getElementById('username').style.display = 'block'; inputUser.container.visible = true; const pwField = document.getElementById('password'); pwField.style.display = 'block'; pwField.type = 'password'; inputLock.container.visible = true; document.getElementById('otp-instruction').style.display = 'none'; document.getElementById('resend-otp-link').style.display = 'none'; document.getElementById('forgot-password-link').style.display = 'none'; document.getElementById('back-link').style.display = 'none'; if (cooldownTimer) { clearInterval(cooldownTimer); cooldownTimer = null; } const resendLink = document.getElementById('resend-otp-link'); if(resendLink) { resendLink.style.pointerEvents = 'auto'; resendLink.style.opacity = '1.0'; resendLink.textContent = t('link_resend_otp'); } const msgBox = document.getElementById('msg-box'); msgBox.textContent = ''; msgBox.style.color = 'var(--color-text-main)'; } 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 response = await fetch(`${API_BASE_URL}/resend-otp`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: pendingVerificationEmail }) }); const data = await response.json(); if (!response.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(data.error) || t('ERR_INTERNAL_SERVER'); return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t(data.message); startVisualCountdown(resendOtpLink, 'link_resend_otp', 120); } catch (error) { console.error("Netzwerkfehler beim erneuten Senden des OTP:", error); msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } }); } const forgotPasswordLink = document.getElementById('forgot-password-link'); if (forgotPasswordLink) { forgotPasswordLink.addEventListener('click', (e) => { e.preventDefault(); setForgotPasswordMode(); }); } const backLink = document.getElementById('back-link'); if (backLink) { backLink.addEventListener('click', (e) => { e.preventDefault(); pendingVerificationEmail = ''; resetEmail = ''; isOtpMode = false; isForgotPasswordMode = false; isResetPasswordMode = false; isLoginMode = false; setLoginMode(); }); } /** * Startet einen visuellen Countdown auf einem übergebenen Link-Element. * Sperrt währenddessen die Klickbarkeit * * @param {HTMLElement} linkElement - Das HTML A-Tag (zB resendOtpLink) * @param {string} orginalTextKey - Der i18n Key für den ursprünglichen Text * @param {number} durationSconds - Die Dauer in Sekunden (hier 120 für 2Minuten) */ function startVisualCountdown(linkElement, orginalTextKey, durationSconds) { if (cooldownTimer) clearInterval(cooldownTimer); let remaining = durationSconds; linkElement.style.pointerEvents = 'none'; linkElement.style.opacity = '0.5'; const updateText = () => { const minutes = Math.floor(remaining / 60); const seconds = remaining % 60; const timeString = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}` linkElement.textContent = `${t(orginalTextKey)} (${timeString})`; }; updateText(); cooldownTimer = setInterval(() => { remaining--; if (remaining <= 0) { clearInterval(cooldownTimer); cooldownTimer = null; linkElement.style.pointerEvents = 'auto'; linkElement.style.opacity = '1.0'; linkElement.textContent = t(orginalTextKey); } else { updateText(); } }, 1000); } /** * 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'; } //const footerElement = document.getElementById('game-footer'); //if (footerElement) { // footerElement.style.display = 'none'; //} uiContainer.visible = false; //footerBackgroundContainer.visible = false; try { const lobbyBGPath = 'src/raw-assets/backgrounds/SPR_SciFiMenus_Example_Image_SpookyComputer_01.png'; const lobbyBGTexture = await Assets.load(lobbyBGPath); background.texture = lobbyBGTexture; resize(); //console.log("Szenenwechsel zur Lobby erfolgreich abgeschlossen!"); buildLobbyUI(); } catch (error) { console.error("Fehler beim Laden des Lobby-Hintergrunds:", error); } } /** * Blendet die Top-Bar ein, setzt den Spilernamen und aktiviert den Logout */ function buildLobbyUI() { // User-Menü (das beim Login versteckt war) einblenden const userDropdown = document.getElementById('user-dropdown'); if (userDropdown) { userDropdown.style.display = 'block'; } // Gespeicherte User-Daten einsetzen 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; } // Platzhalter, bis das Backend die echten Werte im JWT/Login mitsendet 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); } } } /** * Prüft beim Start der Anwendung, ob der Nutzer bereits eingeloggt ist. * Nutz das Refresh-Token, um ein neues Access-Token anzufordern. */ async function checkAuthOnStartup() { const refreshToken = localStorage.getItem('void_refresh_token') || sessionStorage.getItem('void_refresh_token'); if (!refreshToken) { return } try { const response = await fetch (`${API_BASE_URL}/refresh`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken: refreshToken }) }); const data = await response.json(); if (response.ok && data.accessToken) { sessionStorage.setItem('void_access_token', data.accessToken); if (data.user) { sessionStorage.setItem('void_user', JSON.stringify(data.user)); } transitionToLobby(); } else { // Fallback: Das Backend hat das Token abgelehnt (abgelaufen, ungültig oder in der DB gelöscht) // Wir putzen die ungültigen Tokens aus dem Speicher, um Fehler zu vermeiden. console.warn("Auto-Login fehlgeschlagen. Token sind ungültig und werden gelöscht."); 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); } } /** * Initialisiert alle Dropdowns und die Sprachauswahl. * Diese Funktion wird direkt beim Start der App ausgeführt, * damit das Sprach-Menü auch auf dem Login-Screen funktioniert. */ function initDropdowns() { const langToggle = document.getElementById('lang-toggle'); const langMenu = document.getElementById('lang-menu'); const userToggle = document.getElementById('user-toggle'); const userMenu = document.getElementById('user-menu'); const closeAllDropdowns = () => { if (langMenu) langMenu.classList.remove('show'); if (userMenu) userMenu.classList.remove('show'); }; if (langToggle && langMenu) { langToggle.onclick = (e) => { e.stopPropagation(); // Stoppt das Event, damit der document-Klick (unten) es nicht sofort wieder schließt const isShowing = langMenu.classList.contains('show'); closeAllDropdowns(); if (!isShowing) langMenu.classList.add('show'); }; // Ein Klick IN das geöffnete Menü soll es nicht sofort schließen langMenu.onclick = (e) => e.stopPropagation(); } if (userToggle && userMenu) { userToggle.onclick = (e) => { e.stopPropagation(); const isShowing = userMenu.classList.contains('show'); closeAllDropdowns(); if (!isShowing) userMenu.classList.add('show'); }; userMenu.onclick = (e) => e.stopPropagation(); } document.addEventListener('click', closeAllDropdowns); if (langMenu) { const langItems = langMenu.querySelectorAll('.dropdown-item'); langItems.forEach(item => { item.onclick = () => { const selectedLang = item.getAttribute('data-lang'); document.getElementById('current-lang-text').textContent = selectedLang.toUpperCase(); document.getElementById('current-flag').textContent = item.querySelector('.flag-icon').textContent; setLanguage(selectedLang); updateTranslations(); // 4. Platzhaltertexte der Input-Felder dynamisch aktualisieren const userField = document.getElementById('username'); if (userField) userField.placeholder = 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 = isOtpMode || isResetPasswordMode ? t('ph_otp') : t('ph_password'); closeAllDropdowns(); //console.log("Sprache live gewechselt zu:", selectedLang); }; }); } // --- Logout-Logik --- const btnLogout = document.getElementById('btn-logout'); if (btnLogout) { btnLogout.onclick = () => { sessionStorage.removeItem('void_access_token'); sessionStorage.removeItem('void_refresh_token'); localStorage.removeItem('void_refresh_token'); sessionStorage.removeItem('void_user'); window.location.reload(); }; } if (langMenu) { const activeLangItem = langMenu.querySelector(`.dropdown-item[data-lang="${currentLang}"]`); if (activeLangItem) { // Flagge und Text auslesen und in die sichtbare Top-Bar setzen const flag = activeLangItem.querySelector('.flag-icon').textContent; const langText = document.getElementById('current-lang-text'); const flagIcon = document.getElementById('current-flag'); if (langText) langText.textContent = currentLang.toUpperCase(); if (flagIcon) flagIcon.textContent = flag; } } } checkAuthOnStartup(); initDropdowns(); } initGame();