BaseFrontendBackend
@@ -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"]
|
||||
@@ -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">© 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>
|
||||
@@ -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();
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 795 KiB |
@@ -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();
|
||||
|
After Width: | Height: | Size: 8.8 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 334 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 116 KiB |
@@ -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);
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
});
|
||||