Void-Genesis: JWT-Handling, Szenenwechsel & Lobby-UI
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Dieses Objekt enthält alle Textbausteine für das Frontend.
|
||||
* Wenn wir später weitere Sprachen hinzufügen (z.B. 'es' für Spanisch),
|
||||
* müssen wir nur dieses Objekt erweitern, ohne den restlichen Code anzufassen.
|
||||
*/
|
||||
export const translations = {
|
||||
de: {
|
||||
// --- UI Elemente ---
|
||||
tab_login: "Login",
|
||||
tab_register: "Registrieren",
|
||||
btn_login: "Login",
|
||||
btn_register: "Jetzt kostenlos registrieren",
|
||||
link_forgot_password: "Passwort vergessen?",
|
||||
btn_reset_request: "Code anfordern",
|
||||
btn_reset_password: "Passwort ändern",
|
||||
discord_text: "Offizieller Discord Server",
|
||||
link_back: "« Zurück",
|
||||
|
||||
// --- Platzhalter (Placeholders) ---
|
||||
ph_username_login: "Benutzername / E-Mail",
|
||||
ph_username_reg: "Benutzername eingeben",
|
||||
ph_email: "E-Mail eingeben",
|
||||
ph_password: "Passwort eingeben",
|
||||
ph_otp: "6-stelliger Code",
|
||||
|
||||
// --- Labels ---
|
||||
lbl_agb: "Ich akzeptiere die AGB & Spielregeln",
|
||||
lbl_remember: "Angemeldet bleiben",
|
||||
|
||||
// --- OTP Modus ---
|
||||
txt_otp_instruction: "Bitte gib den 6-stelligen Code aus deiner E-Mail ein.",
|
||||
txt_reset_instruction_1: "Um dein Passwort zurückzusetzen, gib bitte deine E-Mail-Adresse ein.",
|
||||
txt_reset_instruction_2: "Bitte gib den Code aus der E-Mail sowie dein neues Passwort ein.",
|
||||
btn_verify: "BESTÄTIGEN",
|
||||
msg_verify_success: "Verifizierung erfolgreich! Du kannst dich nun einloggen.",
|
||||
link_resend_otp: "Code erneut senden",
|
||||
|
||||
// --- Footer ---
|
||||
link_imprint: "Impressum",
|
||||
link_privacy: "Datenschutz",
|
||||
|
||||
// --- Backend Fehlermeldungen (Mapping) ---
|
||||
ERR_MISSING_FIELDS: "Bitte alle Felder ausfüllen.",
|
||||
ERR_PASSWORD_TOO_SHORT: "Das Passwort muss mindestens 8 Zeichen lang sein.",
|
||||
ERR_USERNAME_TOO_SHORT: "Der Benutzername muss mindestens 3 Zeichen lang sein.",
|
||||
ERR_INVALID_EMAIL: "Das E-Mail-Format ist ungültig.",
|
||||
ERR_USERNAME_TAKEN: "Dieser Benutzername ist bereits vergeben.",
|
||||
ERR_EMAIL_TAKEN: "Diese E-Mail-Adresse ist bereits vergeben.",
|
||||
ERR_INVALID_CREDENTIALS: "Benutzername/E-Mail oder Passwort falsch.",
|
||||
ERR_EMAIL_NOT_VERIFIED: "Bitte bestätige zuerst deine E-Mail-Adresse.",
|
||||
ERR_INTERNAL_SERVER: "Ein interner Serverfehler ist aufgetreten.",
|
||||
ERR_INVALID_OTP: "Der eingegebene Code ist falsch. Bitte überprüfe deine Eingabe.",
|
||||
ERR_OTP_EXPIRED: "Dieser Code ist abgelaufen. Bitte fordere einen neuen Code an.",
|
||||
ERR_COOLDOWN_ACTIVE: "Bitte warte 2 Minuten, bevor du einen neuen Code anforderst.",
|
||||
|
||||
// --- Erfolgsmeldungen ---
|
||||
MSG_REGISTER_SUCCESS: "Registrierung erfolgreich. Bitte prüfe deine E-Mails.",
|
||||
MSG_LOGIN_SUCCESS: "Erfolgreich eingeloggt!",
|
||||
MSG_OTP_RESENT: "Ein neuer Code wurde an deine E-Mail gesendet.",
|
||||
MSG_RESET_EMAIL_SENT: "Code gesendet. Bitte prüfe deine E-Mails.",
|
||||
MSG_PASSWORD_RESET_SUCCESS: "Passwort geändert! Du kannst dich nun einloggen.",
|
||||
},
|
||||
en: {
|
||||
// --- UI Elements ---
|
||||
tab_login: "Login",
|
||||
tab_register: "Register",
|
||||
btn_login: "LOGIN",
|
||||
btn_register: "REGISTER",
|
||||
discord_text: "Official Discord Server",
|
||||
link_forgot_password: "Forgot Password?",
|
||||
btn_reset_request: "REQUEST CODE",
|
||||
btn_reset_password: "CHANGE PASSWORD",
|
||||
link_back: "« back",
|
||||
|
||||
// --- Placeholders ---
|
||||
ph_username_login: "Username / E-Mail",
|
||||
ph_username_reg: "Enter Username",
|
||||
ph_email: "Enter E-Mail",
|
||||
ph_password: "Enter Password",
|
||||
ph_otp: "6-digit code",
|
||||
|
||||
// --- Labels ---
|
||||
lbl_agb: "I accept the Terms & Rules",
|
||||
lbl_remember: "Keep me logged in",
|
||||
|
||||
// --- OTP Modus ---
|
||||
txt_otp_instruction: "Please enter the 6-digit code from your e-mail.",
|
||||
txt_reset_instruction_1: "To reset your password, please enter your e-mail address.",
|
||||
txt_reset_instruction_2: "Please enter the code from the e-mail and your new password.",
|
||||
btn_verify: "VERIFY",
|
||||
msg_verify_success: "Verification successful! You can now log in.",
|
||||
link_resend_otp: "Resend Code",
|
||||
|
||||
// --- Footer ---
|
||||
link_imprint: "Imprint",
|
||||
link_privacy: "Privacy Policy",
|
||||
|
||||
// --- Backend Error Messages (Mapping) ---
|
||||
ERR_MISSING_FIELDS: "Please fill in all fields.",
|
||||
ERR_PASSWORD_TOO_SHORT: "Password must be at least 8 characters long.",
|
||||
ERR_USERNAME_TOO_SHORT: "Username must be at least 3 characters long.",
|
||||
ERR_INVALID_EMAIL: "Invalid e-mail format.",
|
||||
ERR_USERNAME_TAKEN: "This username is already taken.",
|
||||
ERR_EMAIL_TAKEN: "This e-mail address is already taken.",
|
||||
ERR_INVALID_CREDENTIALS: "Invalid username/e-mail or password.",
|
||||
ERR_EMAIL_NOT_VERIFIED: "Please verify your e-mail address first.",
|
||||
ERR_INTERNAL_SERVER: "An internal server error occurred.",
|
||||
ERR_INVALID_OTP: "The entered code is incorrect. Please check your input.",
|
||||
ERR_OTP_EXPIRED: "This code has expired. Please request a new code.",
|
||||
ERR_COOLDOWN_ACTIVE: "Please wait 2 minutes before requesting a new code.",
|
||||
|
||||
// --- Success Messages ---
|
||||
MSG_REGISTER_SUCCESS: "Registration successful. Please check your e-mails.",
|
||||
MSG_LOGIN_SUCCESS: "Successfully logged in!",
|
||||
MSG_OTP_RESENT: "A new code has been sent to your e-mail.",
|
||||
MSG_RESET_EMAIL_SENT: "Code sent. Please check your e-mails.",
|
||||
MSG_PASSWORD_RESET_SUCCESS: "Password changed! You can now log in.",
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Die aktuell gewählte Sprache.
|
||||
* Später können wir das auch im LocalStorage des Browsers speichern,
|
||||
* damit sich das Spiel die Sprache des Nutzers merkt.
|
||||
*/
|
||||
export let currentLang = localStorage.getItem('void_language') || 'de';
|
||||
|
||||
/**
|
||||
* Hilfsfunktion, um die Sprache zu wechseln.
|
||||
*/
|
||||
export function setLanguage(lang) {
|
||||
if (translations[lang]) {
|
||||
currentLang = lang;
|
||||
localStorage.setItem('void_language', lang);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt den passenden Text für den übergebenen Schlüssel (Key).
|
||||
* Fällt auf den Key selbst zurück, falls keine Übersetzung gefunden wird
|
||||
* (hilft beim Debuggen, um fehlende Übersetzungen zu erkennen).
|
||||
*/
|
||||
export function t(key) {
|
||||
return translations[currentLang][key] || key;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Application, Assets, Sprite, NineSliceSprite, Container, TilingSprite, Text, wordWrap } from 'pixi.js';
|
||||
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();
|
||||
@@ -16,7 +17,8 @@ async function initGame() {
|
||||
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/SPR_SciFiMenus_Example_Image_SpaceStation_01.png',
|
||||
background: 'src/raw-assets/backgrounds/Background_A.png',
|
||||
|
||||
uiAtlas: 'src/assets/ui-atlas.json'
|
||||
};
|
||||
@@ -27,13 +29,28 @@ async function initGame() {
|
||||
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
|
||||
@@ -109,11 +126,11 @@ function buildScene(app, bgTexture, sheet) {
|
||||
// --- Hexagon pattern
|
||||
const pattern = new TilingSprite({
|
||||
texture: sheet.textures[patternBgName],
|
||||
width: 500,
|
||||
width: 430,
|
||||
height: 520,
|
||||
});
|
||||
pattern.tileScale.set(0.3);
|
||||
pattern.position.set(0, -10);
|
||||
pattern.position.set(25, -10);
|
||||
pattern.alpha = 0.05;
|
||||
pattern.tint = COLOR_PATTERN;
|
||||
uiContainer.addChild(pattern);
|
||||
@@ -163,7 +180,7 @@ function buildScene(app, bgTexture, sheet) {
|
||||
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, 370, 370, 55, frameBtn, null, COLOR_BTN_DEFAULT, 0.6);
|
||||
const btnRegister = createUIItem(65, 390, 370, 55, frameBtn, null, COLOR_BTN_DEFAULT, 0.6);
|
||||
|
||||
// --- Discord Panel ---
|
||||
const discordContainer = new Container();
|
||||
@@ -210,16 +227,16 @@ function buildScene(app, bgTexture, sheet) {
|
||||
discordContainer.addChild(discordIcon);
|
||||
|
||||
const discordText = new Text({
|
||||
text: 'Offizieller Discord Server',
|
||||
text: t('discord_text'),
|
||||
style: {
|
||||
fontFamily: 'UI Font',
|
||||
fontSize: 14,
|
||||
fontSize: 20,
|
||||
fill: COLOR_TAB_INACTIVE,
|
||||
wordWrap: false,
|
||||
wordWrapWidth: 120
|
||||
}
|
||||
});
|
||||
discordText.position.set(60, 25);
|
||||
discordText.position.set(60, 20);
|
||||
discordContainer.addChild(discordText);
|
||||
|
||||
uiContainer.addChild(discordContainer);
|
||||
@@ -228,6 +245,12 @@ function buildScene(app, bgTexture, sheet) {
|
||||
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) {
|
||||
@@ -282,6 +305,7 @@ function buildScene(app, bgTexture, sheet) {
|
||||
function setLoginMode() {
|
||||
if (isLoginMode) return;
|
||||
isLoginMode = true;
|
||||
clearForm();
|
||||
|
||||
tabLogin.fill.tint = COLOR_TAB_ACTIVE;
|
||||
tabLogin.fill.alpha = 0.6;
|
||||
@@ -295,11 +319,17 @@ function buildScene(app, bgTexture, sheet) {
|
||||
|
||||
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 = 'LOGIN';
|
||||
document.getElementById('username').placeholder = 'Username / E-Mail';
|
||||
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 = '';
|
||||
}
|
||||
@@ -307,6 +337,7 @@ function buildScene(app, bgTexture, sheet) {
|
||||
function setRegisterMode() {
|
||||
if (!isLoginMode) return;
|
||||
isLoginMode = false;
|
||||
clearForm();
|
||||
|
||||
tabRegister.fill.tint = COLOR_TAB_ACTIVE;
|
||||
tabRegister.fill.alpha = 0.6;
|
||||
@@ -321,11 +352,160 @@ function buildScene(app, bgTexture, sheet) {
|
||||
inputLock.container.y = 265;
|
||||
document.getElementById('password').style.top = '265px';
|
||||
|
||||
document.getElementById('email').required = true;
|
||||
|
||||
|
||||
document.getElementById('group-remember').style.display = 'none';
|
||||
|
||||
htmlButton.textContent = 'REGISTRIEREN';
|
||||
document.getElementById('username').placeholder = 'Enter Username';
|
||||
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 = '';
|
||||
}
|
||||
|
||||
@@ -373,8 +553,8 @@ function buildScene(app, bgTexture, sheet) {
|
||||
}
|
||||
|
||||
function resize() {
|
||||
const screenWidth = app.screen.width;
|
||||
const screenHeight = app.screen.height;
|
||||
const screenWidth = window.innerWidth;
|
||||
const screenHeight = window.innerHeight;
|
||||
|
||||
const scaleX = screenWidth / background.texture.width;
|
||||
const scaleY = screenHeight / background.texture.height;
|
||||
@@ -383,18 +563,608 @@ function buildScene(app, bgTexture, sheet) {
|
||||
background.x = (screenWidth - background.width) / 2;
|
||||
background.y = (screenHeight - background.height) / 2;
|
||||
|
||||
uiContainer.x = (screenWidth - panelFrame.width) / 2;
|
||||
uiContainer.y = (screenHeight - panelFrame.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 - 40;
|
||||
|
||||
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();
|
||||
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.3 MiB |