BaseFrontendBackend

This commit is contained in:
2026-05-26 20:18:12 +02:00
commit 7fadab8cd6
49 changed files with 6186 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
# ==============================================================================
# Void-Genesis - Lobby Frontend (Dev Environment)
# ==============================================================================
# Auch für das Frontend nutzen wir die kleine Alpine-Version von Node.js
FROM node:22-alpine
# Arbeitsverzeichnis für das Frontend
WORKDIR /app
# Auch hier: Erst die package-Dateien kopieren für optimales Docker-Caching
COPY package*.json ./
# Installiere die Frontend-Abhängigkeiten (Vite, PixiJS, etc.)
RUN npm install
# Vite nutzt im Hintergrund 'Chokidar', um Dateiänderungen zu überwachen (Hot Reload).
# In Docker-Umgebungen klappt das manchmal nicht sofort. Durch USEPOLLING zwingen
# wir Chokidar, aktiv nach Änderungen zu suchen. Das macht das Hot-Reloading robuster.
ENV CHOKIDAR_USEPOLLING=true
# Wir dokumentieren den Standard-Port von Vite
EXPOSE 5173
# Starte den Vite Dev-Server.
# WICHTIG: Das '--host' Flag ist entscheidend! Standardmäßig lauscht Vite nur auf
# localhost (127.0.0.1) im Container. Mit '--host' lauscht Vite auf 0.0.0.0 und
# erlaubt so, dass Anfragen von außerhalb des Containers (z.B. vom Browser) durchkommen.
CMD ["npm", "run", "dev", "--", "--host"]
+64
View File
@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Void-Genesis | Login</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="game-wrapper">
<div id="pixi-container"></div>
<div id="ui-layer">
<form id="register-form" autocomplete="off">
<div id="tab-container">
<button type="button" class="tab-btn active" id="tab-login">Login</button>
<button type="button" class="tab-btn" id="tab-register">Registrieren</button>
</div>
<div class="input-group">
<label for="username">Benutzername</label>
<input type="text" id="username" placeholder="Benutzername / E-Mail" required>
</div>
<div class="input-group" id="group-email">
<label for="email">E-Mail</label>
<input type="email" id="email" placeholder="Enter E-Mail" required>
</div>
<div class="input-group">
<label for="password">Passwort</label>
<input type="password" id="password" placeholder="Enter Password" required>
</div>
<div class="checkbox-group" id="group-remember" style="display: none;">
<input type="checkbox" id="remember">
<label for="remember">Angemeldet bleiben</label>
</div>
<div id="msg-box"></div>
<button type="submit" id="btn-register">REGISTRIEREN</button>
<a href="https://discord.gg/ppjXBE4DbP" target="_blank" id="discord-link"></a>
</form>
</div>
<footer id="game-footer">
<div class="footer-left">v 0.0.1.0-dev</div>
<div class="footer-center">&copy; 2026 Void-Genesis</div>
<div class="footer-right">
<a href="https://void-genesis.de/" target="_blank">Impressum</a>
</div>
</footer>
</div>
<script type="module" src="src/main.js"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
{
"name": "void-genesis-lobby-frontend",
"version": "1.0.0",
"description": "Frontend für die Master-Account-Lobby",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"pixi.js": "^8.0.4"
},
"devDependencies": {
"free-tex-packer-core": "^0.3.5",
"vite": "^5.2.8"
}
}
@@ -0,0 +1,98 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import texturePacker from 'free-tex-packer-core';
// ----------------------------------------------------------------------
// ES-Modul Workaround für __dirname
// Da Vite standardmäßig ES-Module verwendet (type: "module" in package.json),
// müssen wir __dirname manuell nachbauen, um absolute Pfade zu generieren.
// ----------------------------------------------------------------------
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// ----------------------------------------------------------------------
// Pfad-Konfiguration
// ----------------------------------------------------------------------
// Hier definieren wir, woher die rohen Bilder kommen und wohin der
// fertige Textur-Atlas gespeichert werden soll.
const RAW_ASSETS_DIR = path.resolve(__dirname, '../src/raw-assets/ui');
const OUTPUT_DIR = path.resolve(__dirname, '../src/assets');
const ATLAS_NAME = 'ui-atlas'; // Der Basisname für unsere Ausgabe (ui-atlas.png / ui-atlas.json)
// ----------------------------------------------------------------------
// Hauptfunktion zum Generieren des Sprite-Sheets
// ----------------------------------------------------------------------
async function buildSpriteSheet() {
console.log(`[Packer] Lese Bilder aus: ${RAW_ASSETS_DIR}`);
// Wir prüfen zunächst, ob der Quellordner existiert. Falls nicht,
// legen wir ihn an und brechen ab, da noch keine Bilder da sind.
if (!fs.existsSync(RAW_ASSETS_DIR)) {
fs.mkdirSync(RAW_ASSETS_DIR, { recursive: true });
console.log('[Packer] Quellordner existierte nicht und wurde erstellt. Bitte füge Bilder hinzu und starte neu.');
return;
}
// Wir lesen alle Dateien im Quellordner aus.
const files = fs.readdirSync(RAW_ASSETS_DIR);
// Wir filtern nach Bilddateien (z. B. PNG) und bereiten sie für den Packer vor.
const images = [];
for (const file of files) {
if (file.endsWith('.png')) {
const filePath = path.join(RAW_ASSETS_DIR, file);
// Der Packer benötigt den Dateipfad als "path" und den Dateiinhalt als "contents"
images.push({
path: file,
contents: fs.readFileSync(filePath)
});
}
}
if (images.length === 0) {
console.log('[Packer] Keine PNG-Bilder im Quellordner gefunden. Abbruch.');
return;
}
// ----------------------------------------------------------------------
// Packer-Konfiguration
// ----------------------------------------------------------------------
const options = {
textureName: ATLAS_NAME,
width: 4096, // Maximale Breite des Atlases
height: 4096, // Maximale Höhe des Atlases
padding: 2, // Pixel-Abstand zwischen den Bildern (verhindert Rand-Bluten)
allowRotation: false, // Für UI-Elemente oft besser deaktiviert, damit sie in PixiJS nicht gedreht geladen werden
exporter: 'Pixi', // Generiert direkt ein Format, das PixiJS nativ lesen kann
removeFileExtension: true // Entfernt das ".png" in den JSON-Keys (leichter in PixiJS aufzurufen)
};
console.log(`[Packer] Verpacke ${images.length} Bilder...`);
// Wir rufen den Packer asynchron auf
texturePacker(images, options, (files, error) => {
if (error) {
console.error('[Packer] Fehler beim Erstellen des Sprite-Sheets:', error);
return;
}
// Zielordner erstellen, falls er nicht existiert
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
// Der Packer gibt ein Array von Dateien zurück (Bild + JSON).
// Wir speichern diese direkt im Zielordner.
for (const item of files) {
const outPath = path.join(OUTPUT_DIR, item.name);
fs.writeFileSync(outPath, item.buffer);
console.log(`[Packer] Erfolgreich gespeichert: ${outPath}`);
}
console.log('[Packer] Sprite-Sheet erfolgreich erstellt!');
});
}
// Skript ausführen
buildSpriteSheet();
Binary file not shown.
+423
View File
@@ -0,0 +1,423 @@
{
"frames": {
"SPR_SciFiMenus_Frame_Box_Large_02": {
"frame": {
"x": 2,
"y": 2,
"w": 997,
"h": 984
},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {
"x": 14,
"y": 21,
"w": 997,
"h": 984
},
"sourceSize": {
"w": 1024,
"h": 1024
},
"pivot": {
"x": 0.5,
"y": 0.5
}
},
"SPR_SciFiMenus_Frame_Box_Large_12": {
"frame": {
"x": 1003,
"y": 2,
"w": 984,
"h": 481
},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {
"x": 20,
"y": 17,
"w": 984,
"h": 481
},
"sourceSize": {
"w": 1024,
"h": 512
},
"pivot": {
"x": 0.5,
"y": 0.5
}
},
"SPR_SciFiMenus_Frame_Box_Large_12_Background": {
"frame": {
"x": 1991,
"y": 2,
"w": 898,
"h": 393
},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {
"x": 64,
"y": 73,
"w": 898,
"h": 393
},
"sourceSize": {
"w": 1024,
"h": 512
},
"pivot": {
"x": 0.5,
"y": 0.5
}
},
"SPR_SciFiMenus_Ring_Medium_13": {
"frame": {
"x": 2893,
"y": 2,
"w": 826,
"h": 853
},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {
"x": 97,
"y": 85,
"w": 826,
"h": 853
},
"sourceSize": {
"w": 1024,
"h": 1024
},
"pivot": {
"x": 0.5,
"y": 0.5
}
},
"SPR_SciFiMenus_Frame_Box_Large_02_Background": {
"frame": {
"x": 1991,
"y": 399,
"w": 825,
"h": 895
},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {
"x": 98,
"y": 63,
"w": 825,
"h": 895
},
"sourceSize": {
"w": 1024,
"h": 1024
},
"pivot": {
"x": 0.5,
"y": 0.5
}
},
"SPR_SciFiMenus_Background_01": {
"frame": {
"x": 3723,
"y": 2,
"w": 256,
"h": 256
},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 256,
"h": 256
},
"sourceSize": {
"w": 256,
"h": 256
},
"pivot": {
"x": 0.5,
"y": 0.5
}
},
"SPR_SciFiMenus_Menu_Item_Selected_20": {
"frame": {
"x": 3723,
"y": 262,
"w": 256,
"h": 256
},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 256,
"h": 256
},
"sourceSize": {
"w": 256,
"h": 256
},
"pivot": {
"x": 0.5,
"y": 0.5
}
},
"SPR_SciFiMenus_Menu_Item_Selected_10": {
"frame": {
"x": 3723,
"y": 522,
"w": 228,
"h": 234
},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {
"x": 14,
"y": 10,
"w": 228,
"h": 234
},
"sourceSize": {
"w": 256,
"h": 256
},
"pivot": {
"x": 0.5,
"y": 0.5
}
},
"ICON_SciFiMenus_Menu_Lock_Closed_01_Clean": {
"frame": {
"x": 3955,
"y": 522,
"w": 132,
"h": 164
},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {
"x": 62,
"y": 45,
"w": 132,
"h": 164
},
"sourceSize": {
"w": 256,
"h": 256
},
"pivot": {
"x": 0.5,
"y": 0.5
}
},
"ICON_SciFi_Map_Message": {
"frame": {
"x": 3955,
"y": 690,
"w": 130,
"h": 88
},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {
"x": 64,
"y": 84,
"w": 130,
"h": 88
},
"sourceSize": {
"w": 256,
"h": 256
},
"pivot": {
"x": 0.5,
"y": 0.5
}
},
"SPR_SciFiMenus_Menu_Item_Selected_19": {
"frame": {
"x": 3723,
"y": 760,
"w": 228,
"h": 234
},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {
"x": 14,
"y": 10,
"w": 228,
"h": 234
},
"sourceSize": {
"w": 256,
"h": 256
},
"pivot": {
"x": 0.5,
"y": 0.5
}
},
"ICON_SciFiMenus_Menu_Solo_01_Clean": {
"frame": {
"x": 3955,
"y": 782,
"w": 125,
"h": 194
},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {
"x": 65,
"y": 31,
"w": 125,
"h": 194
},
"sourceSize": {
"w": 256,
"h": 256
},
"pivot": {
"x": 0.5,
"y": 0.5
}
},
"SPR_SciFiMenus_Background_Hexagons_01": {
"frame": {
"x": 2820,
"y": 859,
"w": 512,
"h": 512
},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 512,
"h": 512
},
"sourceSize": {
"w": 512,
"h": 512
},
"pivot": {
"x": 0.5,
"y": 0.5
}
},
"ICON_Social_Discord": {
"frame": {
"x": 3336,
"y": 859,
"w": 226,
"h": 175
},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {
"x": 14,
"y": 41,
"w": 226,
"h": 175
},
"sourceSize": {
"w": 256,
"h": 256
},
"pivot": {
"x": 0.5,
"y": 0.5
}
},
"SPR_SciFiMenus_Menu_Item_Background_07": {
"frame": {
"x": 3566,
"y": 998,
"w": 224,
"h": 230
},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {
"x": 16,
"y": 12,
"w": 224,
"h": 230
},
"sourceSize": {
"w": 256,
"h": 256
},
"pivot": {
"x": 0.5,
"y": 0.5
}
},
"ICON_SciFiMenus_Menu_Warning_02_Clean": {
"frame": {
"x": 3336,
"y": 1038,
"w": 222,
"h": 193
},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {
"x": 17,
"y": 31,
"w": 222,
"h": 193
},
"sourceSize": {
"w": 256,
"h": 256
},
"pivot": {
"x": 0.5,
"y": 0.5
}
},
"ICON_SciFiMenus_Menu_Tick_01_Clean": {
"frame": {
"x": 3794,
"y": 998,
"w": 191,
"h": 144
},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {
"x": 32,
"y": 56,
"w": 191,
"h": 144
},
"sourceSize": {
"w": 256,
"h": 256
},
"pivot": {
"x": 0.5,
"y": 0.5
}
}
},
"meta": {
"app": "http://github.com/odrick/free-tex-packer-core",
"version": "0.3.5",
"image": "ui-atlas.png",
"format": "RGBA8888",
"size": {
"w": 4089,
"h": 1373
},
"scale": 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 795 KiB

+400
View File
@@ -0,0 +1,400 @@
import { Application, Assets, Sprite, NineSliceSprite, Container, TilingSprite, Text, wordWrap } 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',
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"');
buildScene(app, backgroundTexture, uiSpritesheet);
} catch (error) {
console.error("Fehler beim Laden der Assets:", error);
}
}
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: 500,
height: 520,
});
pattern.tileScale.set(0.3);
pattern.position.set(0, -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, 370, 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: 'Offizieller Discord Server',
style: {
fontFamily: 'UI Font',
fontSize: 14,
fill: COLOR_TAB_INACTIVE,
wordWrap: false,
wordWrapWidth: 120
}
});
discordText.position.set(60, 25);
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;
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;
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('group-remember').style.display = 'flex';
htmlButton.textContent = 'LOGIN';
document.getElementById('username').placeholder = 'Username / E-Mail';
document.getElementById('msg-box').textContent = '';
}
function setRegisterMode() {
if (!isLoginMode) return;
isLoginMode = false;
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('group-remember').style.display = 'none';
htmlButton.textContent = 'REGISTRIEREN';
document.getElementById('username').placeholder = 'Enter Username';
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 = app.screen.width;
const screenHeight = app.screen.height;
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;
uiContainer.x = (screenWidth - panelFrame.width) / 2;
uiContainer.y = (screenHeight - panelFrame.height) / 2;
footerBackground.width = screenWidth;
footerPattern.width = screenWidth;
footerBackgroundContainer.y = screenHeight - 40;
}
htmlTabLogin.addEventListener('click', setLoginMode);
htmlTabRegistert.addEventListener('click', setRegisterMode);
app.renderer.on('resize', resize);
resize();
}
initGame();
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

+299
View File
@@ -0,0 +1,299 @@
/* --- LOKALE SCHRIFTART LADEN --- */
@font-face {
font-family: 'UI Font';
/* .otf Dateien bekommen das Format 'opentype', .ttf Dateien 'truetype' */
src: url('src/assets/fonts/Gugi-Regular.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
/* 2. Die exklusive Schrift für das Void-Genesis Logo */
@font-face {
font-family: 'Logo Font';
src: url('src/assets/fonts/Quantico-Bold.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
/* --- FARB- & FONT-VARIABLEN (CSS) --- */
:root {
/* ... deine bisherigen Farb-Variablen ... */
--color-bg-page: #000000;
--color-text-main: #ffffff;
--color-text-placeholder: rgba(255, 255, 255, 0.4);
--color-text-inactive: rgba(255, 255, 255, 0.5);
--color-cyan-bright: #00f0ff;
--color-cyan-dark: #00a0aa;
--color-green-bright: #00ff00;
--color-error: #ff4444;
--color-checkbox-bg: rgba(11, 25, 44, 0.8);
/* NEU: Wir definieren die Schriftart zentral */
--font-main: 'UI Font', sans-serif;
}
/* Reset von Standard-Abständen */
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: var(--color-bg-page);
/* Wir nutzen nun unsere lokale Variable */
font-family: var(--font-main);
}
#game-wrapper {
position: relative;
width: 100vw;
height: 100vh;
}
#pixi-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
#ui-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
pointer-events: none;
display: flex;
justify-content: center;
align-items: center;
}
#register-form {
pointer-events: auto;
width: 500px;
height: 500px;
position: relative;
}
.input-group label {
display: none;
}
.input-group input {
position: absolute;
width: 300px;
height: 50px;
left: 115px;
box-sizing: border-box;
padding: 10px 15px;
background: transparent;
border: none;
outline: none;
color: var(--color-text-main);
font-size: 16px;
font-family: inherit;
transition: text-shadow 0.2s ease;
}
#username {
top: 135px;
}
#email {
top: 200px;
}
#password {
top: 265px;
}
.input-group input::placeholder {
color: var(--color-text-placeholder);
}
.input-group input:focus {
text-shadow: 0 0 8px var(--color-cyan-dark);
background: transparent;
box-shadow: none;
}
#btn-register {
position: absolute;
width: 370px;
height: 55px;
left: 65px;
top: 370px;
background: transparent;
border: none;
color: var(--color-text-main);
font-size: 18px;
font-weight: bold;
font-family: inherit;
text-transform: uppercase;
letter-spacing: 2px;
cursor: pointer;
transition: all 0.2s ease;
}
#btn-register:hover {
background: transparent;
color: var(--color-text-main);
text-shadow: 0 0 15px var(--color-text-main), 0 0 30px var(--color-green-bright);
}
.tab-btn {
position: absolute;
height: 40px;
width: 180px;
top: 60px;
background: transparent;
border: none;
color: var(--color-text-inactive);
font-size: 14px;
font-family: inherit;
text-transform: uppercase;
cursor: pointer;
outline: none;
transition: color 0.2s ease;
}
#tab-login {
left: 65px;
}
#tab-register {
left: 255px;
}
.tab-btn.active {
color: var(--color-text-main);
text-shadow: 0 0 8px var(--color-cyan-bright);
}
.tab-btn:hover:not(.active) {
color: var(--color-text-main);
}
.checkbox-group {
position: absolute;
left: 115px;
top: 275px;
display: flex;
align-items: center;
gap: 10px;
}
.checkbox-group label {
color: var(--color-text-main);
font-size: 14px;
cursor: pointer;
}
.checkbox-group input[type="checkbox"] {
appearance: none;
width: 18px;
height: 18px;
border: 1px solid var(--color-cyan-dark);
background: var(--color-checkbox-bg);
cursor: pointer;
position: relative;
}
.checkbox-group input[type="checkbox"]:checked::after {
content: '✔';
position: absolute;
color: var(--color-cyan-bright);
font-size: 14px;
top: -2px;
left: 2px;
}
#msg-box {
position: absolute;
top: 330px;
left: 65px;
width: 370px;
height: 30px;
color: var(--color-error);
font-size: 14px;
text-align: center;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
/* --- NEU: DISCORD LINK --- */
#discord-link {
position: absolute;
/* WICHTIG: Trage hier exakt die gleichen Werte ein,
die du in der main.js beim 'discordContainer.position.set(X, Y)' hast! */
left: 640px; /* Positiver Wert, da es jetzt rechts vom 500px Panel liegt */
top: 380px; /* Y-Koordinate deines Panels */
/* Die Größe deines PixiJS Discord-Panels */
width: 280px;
height: 85px;
/* Komplett unsichtbar machen, wir wollen nur das Pixi-Bild darunter sehen */
background: transparent;
text-decoration: none;
/* Sorgt dafür, dass die Maus zur Klick-Hand wird */
cursor: pointer;
/* Aktiviert die Klickbarkeit für diese Schicht */
pointer-events: auto;
}
/* --- NEU: FOOTER STYLING --- */
#game-footer {
position: absolute; bottom: 0; left: 0; width: 100%;
/* Höhe des Footers (wird auch im Pixi Kachel-Sprite genutzt) */
height: 40px;
/* Z-Index hochsetzen, damit der Text über dem Pixi-Hintergrund liegt */
z-index: 20;
/* pointer-events: none deaktivieren, damit wir den Impressum-Link anklicken können! */
pointer-events: none;
display: flex; justify-content: space-between; align-items: center;
padding: 0 40px; box-sizing: border-box;
color: var(--color-text-inactive);
font-size: 12px;
/* Wir nutzen nicht mehr 'Courier New', sondern unsere UI-Schrift */
font-family: inherit;
/* GRAFISCHE ABSETZUNG: Wir fügen eine leuchtende Cyan-Neon-Linie oben hinzu. */
border-top: 1px solid var(--color-text-inactive);
/* Und nutzen den Pixi Kachel-Hintergrund, um den Sci-Fi Look zu übernehmen. */
background: transparent;
}
/* Flexbox-Verteilung für die drei Footer-Bereiche */
.footer-left { flex: 1; text-align: left; }
.footer-center { flex: 1; text-align: center; }
.footer-right { flex: 1; text-align: right; }
#game-footer a {
color: var(--color-text-inactive); text-decoration: none;
transition: color 0.2s ease, text-shadow 0.2s ease;
/* Wir reaktivieren Klicks für den Link */
pointer-events: auto;
}
#game-footer a:hover {
color: var(--color-cyan-bright);
text-shadow: 0 0 8px var(--color-cyan-bright);
}
+44
View File
@@ -0,0 +1,44 @@
// ==============================================================================
// Void-Genesis - Vite Konfiguration
// ==============================================================================
import { defineConfig } from 'vite';
export default defineConfig({
server: {
// Erlaubt Vite, Verbindungen über diese spezifische Domain anzunehmen.
// Verhindert den "Blocked request" Fehler hinter dem Nginx Proxy Manager.
allowedHosts: [
'dev.void-genesis.de'
],
// 'host: true' stellt sicher, dass Vite auf 0.0.0.0 lauscht
// (haben wir zwar im Dockerfile schon als Flag, ist hier aber sicherer)
host: true,
},
build: {
// ----------------------------------------------------------------------
// EULA-Konformität: Base64-Zwang für alle Assets
// ----------------------------------------------------------------------
// Wir setzen das Limit für das Inlining von Assets auf einen extrem hohen
// Wert (hier ca. 100 MB). Standardmäßig liegt dieser Wert bei 4096 (4 KB).
// Alles, was größer ist, wird von Vite normalerweise als separate Datei
// (z. B. .png oder .jpg) im Ausgabeordner gespeichert.
//
// Durch diesen hohen Wert zwingen wir Vite dazu, sämtliche importierten
// Assets zwingend als Base64-Strings direkt in die generierten JavaScript-
// oder CSS-Dateien einzubetten. Dadurch liegen nach dem Build absolut
// keine rohen, extrahierbaren Bilddateien mehr auf unserem Server.
// ----------------------------------------------------------------------
assetsInlineLimit: 100000000,
// Wir deaktivieren das Generieren von Source Maps für den produktiven Build.
// Das ist wichtig, da Source Maps extrem groß werden können, wenn sie
// riesige Base64-Strings enthalten. Außerdem erschwert es das Auslesen
// unserer Struktur von außen.
sourcemap: false,
// Optional: Falls wir den Output noch weiter minimieren wollen,
// können wir hier auch die CSS-Code-Splitting-Optionen anpassen,
// aber für den Base64-Zwang reicht das assetsInlineLimit völlig aus.
}
});