Sektor-Browser
@@ -0,0 +1,62 @@
|
|||||||
|
import pool from '../db.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert alle Sektoren für das Frontend.
|
||||||
|
* Unterteilt in "Meine Sektoren" (wo der User schon spielt)
|
||||||
|
* und "Alle Sektoren" (die generell verfügbar sind)
|
||||||
|
*/
|
||||||
|
export const getSectorsList = async(req, res) => {
|
||||||
|
const accountId = req.user.accountId;
|
||||||
|
const client = await pool.connect();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// "Meine Sektoren" abfragen
|
||||||
|
const mySectorsResult = await client.query(`
|
||||||
|
SELECT
|
||||||
|
s.id, s.name, s.status, s.speed_fleet, s.speed_economy,
|
||||||
|
las.local_username,
|
||||||
|
las.created_at as joined_at,
|
||||||
|
las.updated_at as last_played
|
||||||
|
FROM sectors s
|
||||||
|
JOIN lobby_accounts_sectors las ON s.id = las.sector_id
|
||||||
|
WHERE las.lobby_account_id = $1
|
||||||
|
ORDER BY las.updated_at DESC
|
||||||
|
`, [accountId]);
|
||||||
|
|
||||||
|
// "Neue Sektoren"
|
||||||
|
const newSectorsResult = await client.query(`
|
||||||
|
SELECT
|
||||||
|
id, name, status, speed_fleet, speed_economy
|
||||||
|
FROM sectors
|
||||||
|
WHERE status != 'offline'
|
||||||
|
AND id NOT IN (
|
||||||
|
SELECT sector_id
|
||||||
|
FROM lobby_accounts_sectors
|
||||||
|
WHERE lobby_account_id = $1
|
||||||
|
)
|
||||||
|
ORDER BY created_at ASC
|
||||||
|
`, [accountId]);
|
||||||
|
|
||||||
|
// Daten platzhalter
|
||||||
|
const mapSectorData = (sector) => ({
|
||||||
|
...sector,
|
||||||
|
online_players: Math.floor(Math.random() * 150) + 10,
|
||||||
|
planets_count: 1337
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
mySectors: mySectorsResult.rows.map(mapSectorData),
|
||||||
|
newSectors: newSectorsResult.rows.map(mapSectorData)
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[SectorController] Fehler beim Abrufen der Sektoren:', error);
|
||||||
|
return res.status(500).json({
|
||||||
|
error: 'ERR_INTERNAL_SERVER'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware zum Schützen privater API-Routen.
|
||||||
|
* Prüft, ob ein gültiges Access_Token im Aithorization-Headder mitgesendet wurde.
|
||||||
|
*/
|
||||||
|
export const requireAuth = (req, res, next) => {
|
||||||
|
// Der Standard für Tokens ist der Header: "Authorization: Bearer <token>"
|
||||||
|
const authHeader = req.headers.authorization;
|
||||||
|
|
||||||
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||||
|
return res.status(401).json({
|
||||||
|
error: 'ERR_UNAUTHORIZED_NO_TOKEN'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = authHeader.split(' ')[1];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(token, process.env.JWT_ACCESS_SECRET);
|
||||||
|
|
||||||
|
req.user = decoded;
|
||||||
|
|
||||||
|
next();
|
||||||
|
} catch (error) {
|
||||||
|
return res.status(401).json({
|
||||||
|
error: 'ERR_UNAUTHORIZED_INVALID_TOKEN'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import * as sectorController from '../controllers/sector.controller.js';
|
||||||
|
import { requireAuth } from '../middlewares/auth.middleware.js';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.get('/list', requireAuth, sectorController.getSectorsList);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -7,40 +7,72 @@ import pool from '../db.js';
|
|||||||
|
|
||||||
const createTablesQuery = `
|
const createTablesQuery = `
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------------------
|
||||||
|
-- DROP TABLES
|
||||||
|
-- WICHTIG: Die Reihenfolge beim Löschen ist entscheidend!
|
||||||
|
-- Tabellen mit Fremdschlüsseln (Foreign Keys) müssen zuerst gelöscht werden.
|
||||||
|
-- ------------------------------------------------------------------------------
|
||||||
DROP TABLE IF EXISTS refresh_tokens;
|
DROP TABLE IF EXISTS refresh_tokens;
|
||||||
DROP TABLE IF EXISTS auth_otps;
|
DROP TABLE IF EXISTS auth_otps;
|
||||||
DROP TABLE IF EXISTS lobby_accounts;
|
DROP TABLE IF EXISTS lobby_accounts_sectors; -- Zuerst die Brückentabelle löschen
|
||||||
|
DROP TABLE IF EXISTS sectors; -- Dann die Sektoren-Tabelle
|
||||||
|
DROP TABLE IF EXISTS lobby_accounts; -- Zuletzt die Haupt-Nutzer-Tabelle
|
||||||
|
|
||||||
-- ------------------------------------------------------------------------------
|
-- ------------------------------------------------------------------------------
|
||||||
-- TABLE: lobby_accounts
|
-- TABLE: lobby_accounts
|
||||||
-- Base user data for the entire Void-Genesis universe.
|
-- Basis-Nutzerdaten für das gesamte Void-Genesis Universum.
|
||||||
-- ------------------------------------------------------------------------------
|
-- ------------------------------------------------------------------------------
|
||||||
CREATE TABLE IF NOT EXISTS lobby_accounts (
|
CREATE TABLE IF NOT EXISTS lobby_accounts (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
username VARCHAR(50) UNIQUE NOT NULL,
|
username VARCHAR(50) UNIQUE NOT NULL,
|
||||||
email VARCHAR(255) UNIQUE NOT NULL,
|
email VARCHAR(255) UNIQUE NOT NULL,
|
||||||
password_hash VARCHAR(255) NOT NULL,
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
|
||||||
-- Status flags and balances (English naming)
|
|
||||||
is_email_verified BOOLEAN DEFAULT false,
|
is_email_verified BOOLEAN DEFAULT false,
|
||||||
res_premium INT DEFAULT 0,
|
res_premium INT DEFAULT 0,
|
||||||
is_admin BOOLEAN DEFAULT false,
|
is_admin BOOLEAN DEFAULT false,
|
||||||
|
|
||||||
-- Timestamps for record keeping
|
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------------------
|
||||||
|
-- TABLE: sectors
|
||||||
|
-- Beinhaltet die globalen Metadaten zu allen verfügbaren Spiel-Sektoren.
|
||||||
|
-- Hier definieren wir, welche Server existieren und welche Eigenschaften sie haben.
|
||||||
|
-- ------------------------------------------------------------------------------
|
||||||
|
CREATE TABLE IF NOT EXISTS sectors (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
status VARCHAR(20) DEFAULT 'offline',
|
||||||
|
speed_fleet INT DEFAULT 1,
|
||||||
|
speed_economy INT DEFAULT 1,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------------------
|
||||||
|
-- TABLE: lobby_accounts_sectors (Brückentabelle)
|
||||||
|
-- Verknüpft einen Lobby-Account mit einem Sektor.
|
||||||
|
-- Existiert hier ein Eintrag, bedeutet das: Der Nutzer hat in diesem Sektor einen Spielstand.
|
||||||
|
-- ------------------------------------------------------------------------------
|
||||||
|
CREATE TABLE IF NOT EXISTS lobby_accounts_sectors (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
lobby_account_id UUID REFERENCES lobby_accounts(id) ON DELETE CASCADE,
|
||||||
|
sector_id UUID REFERENCES sectors(id) ON DELETE CASCADE,
|
||||||
|
local_username VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
res_premium INT DEFAULT 0,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
UNIQUE(lobby_account_id, sector_id)
|
||||||
|
);
|
||||||
|
|
||||||
-- ------------------------------------------------------------------------------
|
-- ------------------------------------------------------------------------------
|
||||||
-- TABLE: auth_otps
|
-- TABLE: auth_otps
|
||||||
-- Stores temporary 6-digit codes for registration or password resets.
|
-- Speichert temporäre 6-stellige Codes für Registrierung oder Passwort-Reset.
|
||||||
-- ------------------------------------------------------------------------------
|
-- ------------------------------------------------------------------------------
|
||||||
CREATE TABLE IF NOT EXISTS auth_otps (
|
CREATE TABLE IF NOT EXISTS auth_otps (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
account_id UUID REFERENCES lobby_accounts(id) ON DELETE CASCADE,
|
account_id UUID REFERENCES lobby_accounts(id) ON DELETE CASCADE,
|
||||||
otp_code VARCHAR(6) NOT NULL,
|
otp_code VARCHAR(6) NOT NULL,
|
||||||
|
|
||||||
-- Defines the purpose of the OTP (e.g., 'register', 'password_reset')
|
|
||||||
type VARCHAR(20) NOT NULL,
|
type VARCHAR(20) NOT NULL,
|
||||||
expires_at TIMESTAMPTZ NOT NULL,
|
expires_at TIMESTAMPTZ NOT NULL,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
@@ -48,7 +80,7 @@ CREATE TABLE IF NOT EXISTS auth_otps (
|
|||||||
|
|
||||||
-- ------------------------------------------------------------------------------
|
-- ------------------------------------------------------------------------------
|
||||||
-- TABLE: refresh_tokens
|
-- TABLE: refresh_tokens
|
||||||
-- Stores long-lived tokens for the "Keep me logged in" and single-session feature.
|
-- Speichert langlebige Tokens für die "Angemeldet bleiben"-Funktion.
|
||||||
-- ------------------------------------------------------------------------------
|
-- ------------------------------------------------------------------------------
|
||||||
CREATE TABLE IF NOT EXISTS refresh_tokens (
|
CREATE TABLE IF NOT EXISTS refresh_tokens (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
@@ -60,8 +92,8 @@ CREATE TABLE IF NOT EXISTS refresh_tokens (
|
|||||||
|
|
||||||
-- ------------------------------------------------------------------------------
|
-- ------------------------------------------------------------------------------
|
||||||
-- TRIGGER: Auto-update 'updated_at' timestamp
|
-- TRIGGER: Auto-update 'updated_at' timestamp
|
||||||
|
-- Diese Funktion setzt das 'updated_at' Feld bei jeder Änderung auf die aktuelle Zeit.
|
||||||
-- ------------------------------------------------------------------------------
|
-- ------------------------------------------------------------------------------
|
||||||
-- This function sets the 'updated_at' field to the current time whenever a row is modified.
|
|
||||||
CREATE OR REPLACE FUNCTION update_modified_column()
|
CREATE OR REPLACE FUNCTION update_modified_column()
|
||||||
RETURNS TRIGGER AS $$
|
RETURNS TRIGGER AS $$
|
||||||
BEGIN
|
BEGIN
|
||||||
@@ -70,15 +102,32 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$ language 'plpgsql';
|
$$ language 'plpgsql';
|
||||||
|
|
||||||
-- We only create the trigger if it doesn't exist to avoid errors on multiple runs.
|
-- Wir wenden den Trigger nur an, wenn er noch nicht existiert.
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
|
-- Trigger für lobby_accounts
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'update_lobby_accounts_modtime') THEN
|
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'update_lobby_accounts_modtime') THEN
|
||||||
CREATE TRIGGER update_lobby_accounts_modtime
|
CREATE TRIGGER update_lobby_accounts_modtime
|
||||||
BEFORE UPDATE ON lobby_accounts
|
BEFORE UPDATE ON lobby_accounts
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
EXECUTE FUNCTION update_modified_column();
|
EXECUTE FUNCTION update_modified_column();
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
|
-- Trigger für sectors
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'update_sectors_modtime') THEN
|
||||||
|
CREATE TRIGGER update_sectors_modtime
|
||||||
|
BEFORE UPDATE ON sectors
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_modified_column();
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Trigger für lobby_accounts_sectors (WICHTIG für unsere "Zuletzt gespielt" Funktion)
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'update_lobby_accounts_sectors_modtime') THEN
|
||||||
|
CREATE TRIGGER update_lobby_accounts_sectors_modtime
|
||||||
|
BEFORE UPDATE ON lobby_accounts_sectors
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_modified_column();
|
||||||
|
END IF;
|
||||||
END $$;
|
END $$;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -90,11 +139,37 @@ async function initDatabase() {
|
|||||||
|
|
||||||
// Wir holen uns eine Verbindung aus dem Pool
|
// Wir holen uns eine Verbindung aus dem Pool
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Wir senden den kompletten SQL-String an die Datenbank
|
// Wir senden den kompletten SQL-String an die Datenbank
|
||||||
await client.query(createTablesQuery);
|
await client.query(createTablesQuery);
|
||||||
console.log('✅ Tabellen wurden erfolgreich erstellt (oder existierten bereits).');
|
console.log('✅ Tabellen wurden erfolgreich erstellt (oder existierten bereits).');
|
||||||
|
|
||||||
|
const sectorCheck = await client.query('SELECT count(*) FROM sectors');
|
||||||
|
if (parseInt(sectorCheck.rows[0].count) === 0) {
|
||||||
|
console.log('🌱 Füge 24 Test-Sektoren ein...');
|
||||||
|
|
||||||
|
const greekAlphabet = [
|
||||||
|
'Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon', 'Zeta',
|
||||||
|
'Eta', 'Theta', 'Iota', 'Kappa', 'Lambda', 'Mu',
|
||||||
|
'Nu', 'Xi', 'Omicron', 'Pi', 'Rho', 'Sigma',
|
||||||
|
'Tau', 'Upsilon', 'Phi', 'Chi', 'Psi', 'Omega'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Wir generieren für jeden Namen ein dynamisches INSERT
|
||||||
|
const insertPromises = greekAlphabet.map((name, index) => {
|
||||||
|
// Wir variieren die Geschwindigkeiten leicht für optische Vielfalt
|
||||||
|
const speed = (index % 4) + 1;
|
||||||
|
return client.query(`
|
||||||
|
INSERT INTO sectors (name, status, speed_fleet, speed_economy)
|
||||||
|
VALUES ($1, 'online', $2, $2)
|
||||||
|
`, [name, speed]);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(insertPromises);
|
||||||
|
console.log('✅ 24 Test-Sektoren erfolgreich eingefügt.');
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Fehler beim Erstellen der Tabellen:', error);
|
console.error('❌ Fehler beim Erstellen der Tabellen:', error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import cors from 'cors';
|
|||||||
import pool from './db.js';
|
import pool from './db.js';
|
||||||
|
|
||||||
import authRoutes from './routes/auth.routes.js';
|
import authRoutes from './routes/auth.routes.js';
|
||||||
|
import sectorRoutes from './routes/sector.routes.js';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
@@ -16,6 +17,7 @@ app.use(cors());
|
|||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
app.use('/api/auth', authRoutes);
|
app.use('/api/auth', authRoutes);
|
||||||
|
app.use('/api/sectors', sectorRoutes);
|
||||||
|
|
||||||
// Unsere bestehende Health-Route
|
// Unsere bestehende Health-Route
|
||||||
app.get('/api/health', (req, res) => {
|
app.get('/api/health', (req, res) => {
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ export const refreshAccessToken = async (refreshToken) => {
|
|||||||
id: account.id,
|
id: account.id,
|
||||||
username: account.username,
|
username: account.username,
|
||||||
email: account.email,
|
email: account.email,
|
||||||
res_premium: res_premium
|
res_premium: account.res_premium
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -9,110 +9,115 @@
|
|||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="game-wrapper">
|
<div id="game-wrapper">
|
||||||
|
|
||||||
<div id="pixi-container"></div>
|
<div id="pixi-container"></div>
|
||||||
|
|
||||||
<div id="ui-layer">
|
<div id="ui-layer">
|
||||||
|
|
||||||
<form id="register-form" autocomplete="off">
|
<form id="register-form" autocomplete="off">
|
||||||
|
|
||||||
<div id="tab-container">
|
<div id="tab-container">
|
||||||
<button type="button" class="tab-btn active" id="tab-login" data-i18n="tab_login">Login</button>
|
<button type="button" class="tab-btn active" id="tab-login" data-i18n="tab_login">Login</button>
|
||||||
<button type="button" class="tab-btn" id="tab-register" data-i18n="tab_register">Registrieren</button> </div>
|
<button type="button" class="tab-btn" id="tab-register" data-i18n="tab_register">Registrieren</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p id="otp-instruction" data-i18n="txt_otp_instruction">
|
<p id="otp-instruction" data-i18n="txt_otp_instruction">
|
||||||
Bitte gib den 6-stelligen Code aus deiner E-Mail ein.
|
Bitte gib den 6-stelligen Code aus deiner E-Mail ein.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label for="username">Benutzername</label>
|
<label for="username">Benutzername</label>
|
||||||
<input type="text" id="username" required>
|
<input type="text" id="username" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-group" id="group-email">
|
<div class="input-group" id="group-email">
|
||||||
<label for="email">E-Mail</label>
|
<label for="email">E-Mail</label>
|
||||||
<input type="email" id="email" required>
|
<input type="email" id="email" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label for="password">Passwort</label>
|
<label for="password">Passwort</label>
|
||||||
<input type="password" id="password" required>
|
<input type="password" id="password" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="checkbox-group" id="group-remember" style="display: none;">
|
<div class="checkbox-group" id="group-remember" style="display: none;">
|
||||||
<input type="checkbox" id="remember">
|
<input type="checkbox" id="remember">
|
||||||
<label for="remember" data-i18n="lbl_remember">Angemeldet bleiben</label>
|
<label for="remember" data-i18n="lbl_remember">Angemeldet bleiben</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="checkbox-group" id="group-agb">
|
<div class="checkbox-group" id="group-agb">
|
||||||
<input type="checkbox" id="agb" required>
|
<input type="checkbox" id="agb" required>
|
||||||
<label for="agb" data-i18n="lbl_agb">Ich akzeptiere die AGB & Spielregeln</label>
|
<label for="agb" data-i18n="lbl_agb">Ich akzeptiere die AGB & Spielregeln</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="msg-box"></div>
|
<div id="msg-box"></div>
|
||||||
|
|
||||||
<button type="submit" id="btn-register" data-i18n="btn_register">Jetzt kostenlos registrieren</button>
|
<button type="submit" id="btn-register" data-i18n="btn_register">Jetzt kostenlos registrieren</button>
|
||||||
|
|
||||||
<a href="#" id="resend-otp-link" data-i18n="link_resend_otp" style="display: none;">Code erneut senden</a>
|
<a href="#" id="resend-otp-link" data-i18n="link_resend_otp" style="display: none;">Code erneut senden</a>
|
||||||
<a href="#" id="forgot-password-link" data-i18n="link_forgot_password" style="display: none;">Passwort vergessen?</a>
|
<a href="#" id="forgot-password-link" data-i18n="link_forgot_password" style="display: none;">Passwort vergessen?</a>
|
||||||
<a href="#" id="back-link" data-i18n="link_back" style="display: none;">« Zurück</a>
|
<a href="#" id="back-link" data-i18n="link_back" style="display: none;">« Zurück</a>
|
||||||
|
|
||||||
<a href="https://discord.gg/ppjXBE4DbP" target="_blank" id="discord-link"></a>
|
<a href="https://discord.gg/ppjXBE4DbP" target="_blank" id="discord-link"></a>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="sector-browser-layer" style="display: none;">
|
||||||
|
|
||||||
|
<div id="sector-tab-container">
|
||||||
|
<button type="button" class="tab-btn active" id="tab-my-sectors">Meine Sektoren</button>
|
||||||
|
<button type="button" class="tab-btn" id="tab-new-sectors">Neue Sektoren</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="lobby-ui">
|
<div id="sector-list-container"></div>
|
||||||
<div id="top-bar">
|
|
||||||
|
|
||||||
<div class="top-bar-left"></div>
|
|
||||||
|
|
||||||
<div class="top-bar-right">
|
</div>
|
||||||
|
|
||||||
<div class="custom-dropdown" id="user-dropdown" style="display: none;">
|
<div id="lobby-ui">
|
||||||
<div class="dropdown-toggle" id="user-toggle">
|
<div id="top-bar">
|
||||||
<span id="lobby-username">Commander</span>
|
<div class="top-bar-left"></div>
|
||||||
<span class="dropdown-arrow">▼</span>
|
<div class="top-bar-right">
|
||||||
</div>
|
<div class="custom-dropdown" id="user-dropdown" style="display: none;">
|
||||||
<ul class="dropdown-menu user-menu" id="user-menu">
|
<div class="dropdown-toggle" id="user-toggle">
|
||||||
<li class="dropdown-header disabled">
|
<span id="lobby-username">Commander</span>
|
||||||
<small>E-Mail</small><br>
|
<span class="dropdown-arrow">▼</span>
|
||||||
<span id="lobby-email">ladend...</span>
|
|
||||||
</li>
|
|
||||||
<li class="dropdown-item disabled">E-Mail ändern</li>
|
|
||||||
<li class="dropdown-item disabled">Benutzername ändern</li>
|
|
||||||
<li class="dropdown-divider"></li>
|
|
||||||
<li class="dropdown-item" id="btn-change-pw">Passwort ändern</li>
|
|
||||||
<li class="dropdown-divider"></li>
|
|
||||||
<li class="dropdown-header disabled">
|
|
||||||
<small>Premium Währung</small><br>
|
|
||||||
<span class="premium-text">Res_Premium: <span id="lobby-premium">-1</span></span>
|
|
||||||
</li>
|
|
||||||
<li class="dropdown-item disabled">Res_Premium aufladen</li>
|
|
||||||
<li class="dropdown-divider"></li>
|
|
||||||
<li class="dropdown-item logout-item" id="btn-logout">Logout</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ul class="dropdown-menu user-menu" id="user-menu">
|
||||||
<div class="custom-dropdown" id="lang-dropdown">
|
<li class="dropdown-header disabled">
|
||||||
<div class="dropdown-toggle" id="lang-toggle">
|
<small>E-Mail</small><br>
|
||||||
<span class="flag-icon" id="current-flag">🇩🇪</span>
|
<span id="lobby-email">ladend...</span>
|
||||||
<span id="current-lang-text">DE</span>
|
</li>
|
||||||
<span class="dropdown-arrow">▼</span>
|
<li class="dropdown-item disabled">E-Mail ändern</li>
|
||||||
</div>
|
<li class="dropdown-item disabled">Benutzername ändern</li>
|
||||||
<ul class="dropdown-menu" id="lang-menu">
|
<li class="dropdown-divider"></li>
|
||||||
<li class="dropdown-item" data-lang="de">
|
<li class="dropdown-item" id="btn-change-pw">Passwort ändern</li>
|
||||||
<span class="flag-icon">🇩🇪</span> Deutsch
|
<li class="dropdown-divider"></li>
|
||||||
</li>
|
<li class="dropdown-header disabled">
|
||||||
<li class="dropdown-item" data-lang="en">
|
<small>Premium Währung</small><br>
|
||||||
<span class="flag-icon">🇬🇧</span> English
|
<span class="premium-text">Res_Premium: <span id="lobby-premium">-1</span></span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
<li class="dropdown-item disabled">Res_Premium aufladen</li>
|
||||||
|
<li class="dropdown-divider"></li>
|
||||||
|
<li class="dropdown-item logout-item" id="btn-logout">Logout</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="custom-dropdown" id="lang-dropdown">
|
||||||
|
<div class="dropdown-toggle" id="lang-toggle">
|
||||||
|
<span class="flag-icon" id="current-flag">🇩🇪</span>
|
||||||
|
<span id="current-lang-text">DE</span>
|
||||||
|
<span class="dropdown-arrow">▼</span>
|
||||||
</div>
|
</div>
|
||||||
|
<ul class="dropdown-menu" id="lang-menu">
|
||||||
|
<li class="dropdown-item" data-lang="de">
|
||||||
|
<span class="flag-icon">🇩🇪</span> Deutsch
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item" data-lang="en">
|
||||||
|
<span class="flag-icon">🇬🇧</span> English
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<footer id="game-footer">
|
<footer id="game-footer">
|
||||||
<div class="footer-left">v 0.0.3.0-dev</div>
|
<div class="footer-left">v 0.0.3.0-dev</div>
|
||||||
<div class="footer-center">© 2026 Void-Genesis</div>
|
<div class="footer-center">© 2026 Void-Genesis</div>
|
||||||
|
|||||||
@@ -1,9 +1,33 @@
|
|||||||
{
|
{
|
||||||
"frames": {
|
"frames": {
|
||||||
"SPR_SciFiMenus_Frame_Box_Large_02": {
|
"SPR_SciFiMenus_Frame_Box_Large_11": {
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 2,
|
"x": 2,
|
||||||
"y": 2,
|
"y": 2,
|
||||||
|
"w": 998,
|
||||||
|
"h": 494
|
||||||
|
},
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": true,
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 13,
|
||||||
|
"y": 11,
|
||||||
|
"w": 998,
|
||||||
|
"h": 494
|
||||||
|
},
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 1024,
|
||||||
|
"h": 512
|
||||||
|
},
|
||||||
|
"pivot": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SPR_SciFiMenus_Frame_Box_Large_02": {
|
||||||
|
"frame": {
|
||||||
|
"x": 1004,
|
||||||
|
"y": 2,
|
||||||
"w": 997,
|
"w": 997,
|
||||||
"h": 984
|
"h": 984
|
||||||
},
|
},
|
||||||
@@ -26,8 +50,8 @@
|
|||||||
},
|
},
|
||||||
"SPR_SciFiMenus_Frame_Box_Large_12": {
|
"SPR_SciFiMenus_Frame_Box_Large_12": {
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 1003,
|
"x": 2,
|
||||||
"y": 2,
|
"y": 500,
|
||||||
"w": 984,
|
"w": 984,
|
||||||
"h": 481
|
"h": 481
|
||||||
},
|
},
|
||||||
@@ -48,9 +72,33 @@
|
|||||||
"y": 0.5
|
"y": 0.5
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"SPR_SciFiMenus_Frame_Box_Large_11_Background": {
|
||||||
|
"frame": {
|
||||||
|
"x": 2,
|
||||||
|
"y": 985,
|
||||||
|
"w": 919,
|
||||||
|
"h": 427
|
||||||
|
},
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": true,
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 52,
|
||||||
|
"y": 34,
|
||||||
|
"w": 919,
|
||||||
|
"h": 427
|
||||||
|
},
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 1024,
|
||||||
|
"h": 512
|
||||||
|
},
|
||||||
|
"pivot": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
}
|
||||||
|
},
|
||||||
"SPR_SciFiMenus_Frame_Box_Large_12_Background": {
|
"SPR_SciFiMenus_Frame_Box_Large_12_Background": {
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 1991,
|
"x": 2005,
|
||||||
"y": 2,
|
"y": 2,
|
||||||
"w": 898,
|
"w": 898,
|
||||||
"h": 393
|
"h": 393
|
||||||
@@ -74,7 +122,7 @@
|
|||||||
},
|
},
|
||||||
"SPR_SciFiMenus_Ring_Medium_13": {
|
"SPR_SciFiMenus_Ring_Medium_13": {
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 2893,
|
"x": 2907,
|
||||||
"y": 2,
|
"y": 2,
|
||||||
"w": 826,
|
"w": 826,
|
||||||
"h": 853
|
"h": 853
|
||||||
@@ -98,7 +146,7 @@
|
|||||||
},
|
},
|
||||||
"SPR_SciFiMenus_Frame_Box_Large_02_Background": {
|
"SPR_SciFiMenus_Frame_Box_Large_02_Background": {
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 1991,
|
"x": 2005,
|
||||||
"y": 399,
|
"y": 399,
|
||||||
"w": 825,
|
"w": 825,
|
||||||
"h": 895
|
"h": 895
|
||||||
@@ -122,7 +170,7 @@
|
|||||||
},
|
},
|
||||||
"SPR_SciFiMenus_Background_01": {
|
"SPR_SciFiMenus_Background_01": {
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 3723,
|
"x": 3737,
|
||||||
"y": 2,
|
"y": 2,
|
||||||
"w": 256,
|
"w": 256,
|
||||||
"h": 256
|
"h": 256
|
||||||
@@ -146,7 +194,7 @@
|
|||||||
},
|
},
|
||||||
"SPR_SciFiMenus_Menu_Item_Selected_20": {
|
"SPR_SciFiMenus_Menu_Item_Selected_20": {
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 3723,
|
"x": 3737,
|
||||||
"y": 262,
|
"y": 262,
|
||||||
"w": 256,
|
"w": 256,
|
||||||
"h": 256
|
"h": 256
|
||||||
@@ -170,7 +218,7 @@
|
|||||||
},
|
},
|
||||||
"SPR_SciFiMenus_Menu_Item_Selected_10": {
|
"SPR_SciFiMenus_Menu_Item_Selected_10": {
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 3723,
|
"x": 3737,
|
||||||
"y": 522,
|
"y": 522,
|
||||||
"w": 228,
|
"w": 228,
|
||||||
"h": 234
|
"h": 234
|
||||||
@@ -192,82 +240,10 @@
|
|||||||
"y": 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": {
|
"ICON_SciFiMenus_Menu_Solo_01_Clean": {
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 3955,
|
"x": 3969,
|
||||||
"y": 782,
|
"y": 522,
|
||||||
"w": 125,
|
"w": 125,
|
||||||
"h": 194
|
"h": 194
|
||||||
},
|
},
|
||||||
@@ -288,9 +264,33 @@
|
|||||||
"y": 0.5
|
"y": 0.5
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"SPR_SciFiMenus_Menu_Item_Selected_19": {
|
||||||
|
"frame": {
|
||||||
|
"x": 3737,
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
},
|
||||||
"SPR_SciFiMenus_Background_Hexagons_01": {
|
"SPR_SciFiMenus_Background_Hexagons_01": {
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 2820,
|
"x": 2834,
|
||||||
"y": 859,
|
"y": 859,
|
||||||
"w": 512,
|
"w": 512,
|
||||||
"h": 512
|
"h": 512
|
||||||
@@ -314,7 +314,7 @@
|
|||||||
},
|
},
|
||||||
"ICON_Social_Discord": {
|
"ICON_Social_Discord": {
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 3336,
|
"x": 3350,
|
||||||
"y": 859,
|
"y": 859,
|
||||||
"w": 226,
|
"w": 226,
|
||||||
"h": 175
|
"h": 175
|
||||||
@@ -336,9 +336,33 @@
|
|||||||
"y": 0.5
|
"y": 0.5
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ICON_SciFiMenus_Menu_Lock_Closed_01_Clean": {
|
||||||
|
"frame": {
|
||||||
|
"x": 3580,
|
||||||
|
"y": 859,
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
},
|
||||||
"SPR_SciFiMenus_Menu_Item_Background_07": {
|
"SPR_SciFiMenus_Menu_Item_Background_07": {
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 3566,
|
"x": 3716,
|
||||||
"y": 998,
|
"y": 998,
|
||||||
"w": 224,
|
"w": 224,
|
||||||
"h": 230
|
"h": 230
|
||||||
@@ -360,9 +384,33 @@
|
|||||||
"y": 0.5
|
"y": 0.5
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ICON_SciFi_Map_Message": {
|
||||||
|
"frame": {
|
||||||
|
"x": 3580,
|
||||||
|
"y": 1027,
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
},
|
||||||
"ICON_SciFiMenus_Menu_Warning_02_Clean": {
|
"ICON_SciFiMenus_Menu_Warning_02_Clean": {
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 3336,
|
"x": 3350,
|
||||||
"y": 1038,
|
"y": 1038,
|
||||||
"w": 222,
|
"w": 222,
|
||||||
"h": 193
|
"h": 193
|
||||||
@@ -384,10 +432,58 @@
|
|||||||
"y": 0.5
|
"y": 0.5
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ICON_SciFiMenus_Menu_Multiplayer_01_Clean": {
|
||||||
|
"frame": {
|
||||||
|
"x": 3576,
|
||||||
|
"y": 1232,
|
||||||
|
"w": 218,
|
||||||
|
"h": 141
|
||||||
|
},
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": true,
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 19,
|
||||||
|
"y": 57,
|
||||||
|
"w": 218,
|
||||||
|
"h": 141
|
||||||
|
},
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 256,
|
||||||
|
"h": 256
|
||||||
|
},
|
||||||
|
"pivot": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ICON_SciFiMenus_Menu_Planet_02_Clean": {
|
||||||
|
"frame": {
|
||||||
|
"x": 3350,
|
||||||
|
"y": 1235,
|
||||||
|
"w": 216,
|
||||||
|
"h": 149
|
||||||
|
},
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": true,
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 20,
|
||||||
|
"y": 54,
|
||||||
|
"w": 216,
|
||||||
|
"h": 149
|
||||||
|
},
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 256,
|
||||||
|
"h": 256
|
||||||
|
},
|
||||||
|
"pivot": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
}
|
||||||
|
},
|
||||||
"ICON_SciFiMenus_Menu_Tick_01_Clean": {
|
"ICON_SciFiMenus_Menu_Tick_01_Clean": {
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 3794,
|
"x": 3798,
|
||||||
"y": 998,
|
"y": 1232,
|
||||||
"w": 191,
|
"w": 191,
|
||||||
"h": 144
|
"h": 144
|
||||||
},
|
},
|
||||||
@@ -407,6 +503,54 @@
|
|||||||
"x": 0.5,
|
"x": 0.5,
|
||||||
"y": 0.5
|
"y": 0.5
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"ICON_SciFiMenus_Menu_Settings_01_Clean": {
|
||||||
|
"frame": {
|
||||||
|
"x": 3570,
|
||||||
|
"y": 1377,
|
||||||
|
"w": 182,
|
||||||
|
"h": 199
|
||||||
|
},
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": true,
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 37,
|
||||||
|
"y": 28,
|
||||||
|
"w": 182,
|
||||||
|
"h": 199
|
||||||
|
},
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 256,
|
||||||
|
"h": 256
|
||||||
|
},
|
||||||
|
"pivot": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ICON_SciFiMenus_Menu_Message_01_Clean": {
|
||||||
|
"frame": {
|
||||||
|
"x": 3570,
|
||||||
|
"y": 1580,
|
||||||
|
"w": 177,
|
||||||
|
"h": 218
|
||||||
|
},
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": true,
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 39,
|
||||||
|
"y": 17,
|
||||||
|
"w": 177,
|
||||||
|
"h": 218
|
||||||
|
},
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 256,
|
||||||
|
"h": 256
|
||||||
|
},
|
||||||
|
"pivot": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
@@ -415,8 +559,8 @@
|
|||||||
"image": "ui-atlas.png",
|
"image": "ui-atlas.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 4089,
|
"w": 4096,
|
||||||
"h": 1373
|
"h": 1800
|
||||||
},
|
},
|
||||||
"scale": 1
|
"scale": 1
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 795 KiB After Width: | Height: | Size: 974 KiB |
@@ -1,5 +1,8 @@
|
|||||||
import { t, setLanguage, currentLang } from './i18n.js';
|
import { t, setLanguage, currentLang } from './i18n.js';
|
||||||
import { Application, Assets, Sprite, NineSliceSprite, Container, TilingSprite, Text, wordWrap, fragmentGPUTemplate } from 'pixi.js';
|
import { Application, Assets, Sprite, NineSliceSprite, Container, TilingSprite, Text, wordWrap, fragmentGPUTemplate, Graphics } from 'pixi.js';
|
||||||
|
|
||||||
|
let globalSheet = null;
|
||||||
|
let sectorTabsPixiContainer = null;
|
||||||
|
|
||||||
async function initGame() {
|
async function initGame() {
|
||||||
const app = new Application();
|
const app = new Application();
|
||||||
@@ -52,6 +55,8 @@ function updateTranslations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildScene(app, bgTexture, sheet) {
|
function buildScene(app, bgTexture, sheet) {
|
||||||
|
globalSheet = sheet;
|
||||||
|
|
||||||
// --- Farb-Variablen (pixiJS) ---
|
// --- Farb-Variablen (pixiJS) ---
|
||||||
const COLOR_PANEL_BG = 0x0b192c; // Dunkelblau für Panel & Inputs
|
const COLOR_PANEL_BG = 0x0b192c; // Dunkelblau für Panel & Inputs
|
||||||
const COLOR_PATTERN = 0x00f0ff; // Cyan für das Hexagon-Muster
|
const COLOR_PATTERN = 0x00f0ff; // Cyan für das Hexagon-Muster
|
||||||
@@ -93,6 +98,42 @@ function buildScene(app, bgTexture, sheet) {
|
|||||||
const uiContainer = new Container();
|
const uiContainer = new Container();
|
||||||
app.stage.addChild(uiContainer);
|
app.stage.addChild(uiContainer);
|
||||||
|
|
||||||
|
// --- Separater Container für die Lobby-Grafiken (Sektor-Browser) ---
|
||||||
|
const lobbyPixiContainer = new Container();
|
||||||
|
lobbyPixiContainer.visible = false;
|
||||||
|
app.stage.addChild(lobbyPixiContainer);
|
||||||
|
|
||||||
|
// 1. Der große Haupt-Hintergrund für den Sektor-Browser
|
||||||
|
const sectorBrowserBg = new NineSliceSprite({
|
||||||
|
texture: sheet.textures['SPR_SciFiMenus_Frame_Box_Large_11_Background'], // panelBgName ist SPR_SciFiMenus_Frame_Box_Large_02_Background
|
||||||
|
leftWidth: 500, topHeight: 250, rightWidth: 500, bottomHeight: 250
|
||||||
|
});
|
||||||
|
sectorBrowserBg.width = 1240;
|
||||||
|
sectorBrowserBg.height = 650;
|
||||||
|
sectorBrowserBg.alpha = 0.8;
|
||||||
|
sectorBrowserBg.tint = COLOR_PANEL_BG;
|
||||||
|
lobbyPixiContainer.addChild(sectorBrowserBg);
|
||||||
|
|
||||||
|
// 2. Container für die kleinen Sektor-Karten, der scrollen kann
|
||||||
|
let sectorPixiContainer = new Container();
|
||||||
|
lobbyPixiContainer.addChild(sectorPixiContainer);
|
||||||
|
|
||||||
|
// 3. Maske, damit die Karten beim Scrollen nicht über den Rahmen hinausschießen (PixiJS v8 Syntax)
|
||||||
|
const scrollMask = new Graphics();
|
||||||
|
scrollMask.rect(30, 80, 1180, 540); // Entspricht exakt der Position des HTML Scroll-Bereichs
|
||||||
|
scrollMask.fill(0xffffff);
|
||||||
|
lobbyPixiContainer.addChild(scrollMask);
|
||||||
|
sectorPixiContainer.mask = scrollMask;
|
||||||
|
|
||||||
|
// 4. HTML Scroll-Event an PixiJS Y-Koordinate binden!
|
||||||
|
const htmlSectorList = document.getElementById('sector-list-container');
|
||||||
|
if (htmlSectorList) {
|
||||||
|
htmlSectorList.addEventListener('scroll', (e) => {
|
||||||
|
// Wenn HTML nach unten scrollt, schieben wir den PixiJS-Container nach oben
|
||||||
|
sectorPixiContainer.y = -e.target.scrollTop;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const panelBackground = new Sprite(sheet.textures[panelBgName]);
|
const panelBackground = new Sprite(sheet.textures[panelBgName]);
|
||||||
panelBackground.tint = COLOR_PANEL_BG;
|
panelBackground.tint = COLOR_PANEL_BG;
|
||||||
panelBackground.width = 500;
|
panelBackground.width = 500;
|
||||||
@@ -594,6 +635,28 @@ function buildScene(app, bgTexture, sheet) {
|
|||||||
uiContainer.x = (screenWidth - (panelFrame.width * panelScale)) / 2;
|
uiContainer.x = (screenWidth - (panelFrame.width * panelScale)) / 2;
|
||||||
uiContainer.y = ((screenHeight - (panelFrame.height * panelScale)) / 2) + panelOffsetY;
|
uiContainer.y = ((screenHeight - (panelFrame.height * panelScale)) / 2) + panelOffsetY;
|
||||||
|
|
||||||
|
let lobbyScale = 1.0;
|
||||||
|
// Wenn der Bildschirm schmaler als das Panel ist
|
||||||
|
if (screenWidth <= 1300) {
|
||||||
|
lobbyScale = screenWidth / 1300;
|
||||||
|
}
|
||||||
|
// Wenn der Bildschirm in der Höhe zu klein ist (z.B. Laptops)
|
||||||
|
if (screenHeight <= 750) {
|
||||||
|
const hScale = screenHeight / 750;
|
||||||
|
if (hScale < lobbyScale) lobbyScale = hScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skaliert die PixiJS-Hintergründe
|
||||||
|
lobbyPixiContainer.scale.set(lobbyScale);
|
||||||
|
lobbyPixiContainer.x = (screenWidth - (1240 * lobbyScale)) / 2;
|
||||||
|
lobbyPixiContainer.y = (screenHeight - (650 * lobbyScale)) / 2;
|
||||||
|
|
||||||
|
// Skaliert die HTML-Texte ABSOLUT SYNCHRON dazu
|
||||||
|
const sectorHtmlLayer = document.getElementById('sector-browser-layer');
|
||||||
|
if (sectorHtmlLayer) {
|
||||||
|
sectorHtmlLayer.style.transform = `translate(-50%, -50%) scale(${lobbyScale})`;
|
||||||
|
}
|
||||||
|
|
||||||
const htmlFooter = document.getElementById('game-footer');
|
const htmlFooter = document.getElementById('game-footer');
|
||||||
const footerHeight = (htmlFooter && htmlFooter.style.display !== 'none') ? htmlFooter.offsetHeight : 0;
|
const footerHeight = (htmlFooter && htmlFooter.style.display !== 'none') ? htmlFooter.offsetHeight : 0;
|
||||||
|
|
||||||
@@ -973,13 +1036,8 @@ function buildScene(app, bgTexture, sheet) {
|
|||||||
formElement.style.display = 'none';
|
formElement.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
//const footerElement = document.getElementById('game-footer');
|
|
||||||
//if (footerElement) {
|
|
||||||
// footerElement.style.display = 'none';
|
|
||||||
//}
|
|
||||||
|
|
||||||
uiContainer.visible = false;
|
uiContainer.visible = false;
|
||||||
//footerBackgroundContainer.visible = false;
|
lobbyPixiContainer.visible = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const lobbyBGPath = 'src/raw-assets/backgrounds/SPR_SciFiMenus_Example_Image_SpookyComputer_01.png';
|
const lobbyBGPath = 'src/raw-assets/backgrounds/SPR_SciFiMenus_Example_Image_SpookyComputer_01.png';
|
||||||
@@ -998,7 +1056,7 @@ function buildScene(app, bgTexture, sheet) {
|
|||||||
/**
|
/**
|
||||||
* Blendet die Top-Bar ein, setzt den Spilernamen und aktiviert den Logout
|
* Blendet die Top-Bar ein, setzt den Spilernamen und aktiviert den Logout
|
||||||
*/
|
*/
|
||||||
function buildLobbyUI() {
|
async function buildLobbyUI() {
|
||||||
// User-Menü (das beim Login versteckt war) einblenden
|
// User-Menü (das beim Login versteckt war) einblenden
|
||||||
const userDropdown = document.getElementById('user-dropdown');
|
const userDropdown = document.getElementById('user-dropdown');
|
||||||
if (userDropdown) {
|
if (userDropdown) {
|
||||||
@@ -1030,8 +1088,249 @@ function buildScene(app, bgTexture, sheet) {
|
|||||||
console.error("Fehler beim Auslesen der User-Daten:", e);
|
console.error("Fehler beim Auslesen der User-Daten:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await loadAndRenderSectorBrowser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Läd die Sektordaten von der API und steuert die Tab-Sichtbarkeiten
|
||||||
|
*/
|
||||||
|
async function loadAndRenderSectorBrowser() {
|
||||||
|
const token = sessionStorage.getItem('void_access_token') || localStorage.getItem('void_access_token');
|
||||||
|
if (!token) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://api-dev.void-genesis.de/api/sectors/list', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (!response.ok || !data.success) {
|
||||||
|
console.error("Fehler beim Laden der Sektoren:", data.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const layer = document.getElementById('sector-browser-layer');
|
||||||
|
layer.style.display = 'block';
|
||||||
|
|
||||||
|
const tabMySectors = document.getElementById('tab-my-sectors');
|
||||||
|
const tabNewSectors = document.getElementById('tab-new-sectors');
|
||||||
|
|
||||||
|
if (sectorTabsPixiContainer) {
|
||||||
|
lobbyPixiContainer.removeChild(sectorTabsPixiContainer);
|
||||||
|
sectorTabsPixiContainer.destroy({ children: true });
|
||||||
|
}
|
||||||
|
sectorTabsPixiContainer = new Container();
|
||||||
|
lobbyPixiContainer.addChild(sectorTabsPixiContainer);
|
||||||
|
|
||||||
|
if (data.mySectors.length === 0) {
|
||||||
|
tabMySectors.style.display = 'none';
|
||||||
|
tabNewSectors.classList.add('active');
|
||||||
|
|
||||||
|
// 1 HTML-Tab mittig. PixiJS-Rahmen passend zentrieren: (1240 - 180) / 2 = 530
|
||||||
|
const tabNewBg = createSectorTabPixi(530, 20, 0x00f0ff, 0.6); // Active Farbe
|
||||||
|
sectorTabsPixiContainer.addChild(tabNewBg);
|
||||||
|
|
||||||
|
renderSectorList(data.newSectors, 'new');
|
||||||
|
} else {
|
||||||
|
tabMySectors.style.display = 'inline-block';
|
||||||
|
tabNewSectors.style.display = 'inline-block';
|
||||||
|
tabMySectors.classList.add('active');
|
||||||
|
tabNewSectors.classList.remove('active');
|
||||||
|
|
||||||
|
// 2 HTML-Tabs mittig mit 20px Lücke. Gesamtbreite 380. Startpunkt: (1240 - 380) / 2 = 430
|
||||||
|
const tabMyBg = createSectorTabPixi(430, 20, 0x00f0ff, 0.6); // Active Farbe
|
||||||
|
const tabNewBg = createSectorTabPixi(630, 20, 0xaaaaaa, 0.4); // Inactive Farbe
|
||||||
|
sectorTabsPixiContainer.addChild(tabMyBg, tabNewBg);
|
||||||
|
|
||||||
|
renderSectorList(data.mySectors, 'my');
|
||||||
|
}
|
||||||
|
|
||||||
|
tabMySectors.onclick = () => {
|
||||||
|
tabMySectors.classList.add('active');
|
||||||
|
tabNewSectors.classList.remove('active');
|
||||||
|
renderSectorList(data.mySectors, 'my');
|
||||||
|
};
|
||||||
|
|
||||||
|
tabNewSectors.onclick = () => {
|
||||||
|
tabNewSectors.classList.add('active');
|
||||||
|
tabMySectors.classList.remove('active');
|
||||||
|
renderSectorList(data.newSectors, 'new');
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Netfwerkfehler beim Sektor-Abruf:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rendert die HTML-Karte und die dazugehöhrigen PixiJS-Hintergrundboxen
|
||||||
|
*/
|
||||||
|
function renderSectorList(sectors, type) {
|
||||||
|
const listContainer = document.getElementById('sector-list-container');
|
||||||
|
listContainer.innerHTML = '';
|
||||||
|
|
||||||
|
if (sectorPixiContainer) {
|
||||||
|
sectorPixiContainer.removeChildren();
|
||||||
|
// Scrollposition sicherheitshalber auf 0 zurücksetzen
|
||||||
|
sectorPixiContainer.y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const htmlSectorList = document.getElementById('sector-list-container');
|
||||||
|
if (htmlSectorList) htmlSectorList.scrollTop = 0;
|
||||||
|
if (sectors.length === 0) {
|
||||||
|
listContainer.innerHTML = '<p style="text-align:center; color:var(--color-text-inactive);">Keine Sektoren in dieser Kategorie verfügbar.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sectors.forEach((sector, index) => {
|
||||||
|
const cardLink = document.createElement('a');
|
||||||
|
cardLink.className = 'sector-card-link';
|
||||||
|
cardLink.href = `#`;
|
||||||
|
|
||||||
|
// --- 0. Temporäre Platzhalter für das neue UI (ersetzen wir später durch echte DB-Werte) ---
|
||||||
|
const ageDays = sector.age_days || Math.floor(Math.random() * 30) + 1;
|
||||||
|
const totalPlayers = sector.total_players || Math.floor(Math.random() * 500) + 150;
|
||||||
|
|
||||||
|
// --- 1. HTML Layer mit absoluten Koordinaten ---
|
||||||
|
let bottomRowHtml = '';
|
||||||
|
if (type === 'my' && sector.local_username) {
|
||||||
|
bottomRowHtml = `<div style="position: absolute; left: 20px; bottom: 8px; font-size: 12px; color: var(--color-cyan-bright); font-style: italic;">Commander: ${sector.local_username}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
cardLink.innerHTML = `
|
||||||
|
<div class="sector-card-content" style="position: relative; width: 100%; height: 100%; padding: 0;">
|
||||||
|
|
||||||
|
<span style="position: absolute; left: 10px; top: 15px; width: 25px; height: 27px; cursor: help;" title="Sektor-Details (Flotte: ${sector.speed_fleet}x, Öko: ${sector.speed_economy}x)"></span>
|
||||||
|
|
||||||
|
<span style="position: absolute; left: 40px; top: 15px; width: 140px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 18px; font-weight: bold;" title="${sector.name}">
|
||||||
|
${sector.name}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span style="position: absolute; left: 150px; top: 18px; font-size: 12px; color: var(--color-text-placeholder);" title="Sektor-Alter in Tagen">${ageDays} Tage</span>
|
||||||
|
|
||||||
|
<span class="sector-status ${sector.status === 'online' ? 'status-online' : 'status-maintenance'}" style="position: absolute; right: 20px; top: 17px;">
|
||||||
|
${sector.status === 'online' ? 'online' : 'wartung'}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span style="position: absolute; left: 35px; top: 54px; font-size: 13px; color: var(--color-text-placeholder);">Online: ${sector.online_players}/${totalPlayers}</span>
|
||||||
|
|
||||||
|
<span style="position: absolute; left: 170px; top: 54px; font-size: 13px; color: var(--color-text-placeholder);">Planeten: ${sector.planets_count}</span>
|
||||||
|
|
||||||
|
${bottomRowHtml}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
cardLink.onclick = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log(`Betrete Sektor: ${sector.name} (ID: ${sector.id})`);
|
||||||
|
};
|
||||||
|
|
||||||
|
listContainer.appendChild(cardLink);
|
||||||
|
|
||||||
|
// --- 2. PixiJS-Hintergrund-Boxen synchron zum CSS-Grid rendern ---
|
||||||
|
const columns = 4;
|
||||||
|
const cardWidth = 270;
|
||||||
|
const cardHeight = 90;
|
||||||
|
const gapX = 25;
|
||||||
|
const gapY = 20;
|
||||||
|
const startX = 40;
|
||||||
|
const startY = 80 + 20;
|
||||||
|
|
||||||
|
const col = index % columns;
|
||||||
|
const row = Math.floor(index / columns);
|
||||||
|
|
||||||
|
const posX = startX + col * (cardWidth + gapX);
|
||||||
|
const posY = startY + row * (cardHeight + gapY);
|
||||||
|
|
||||||
|
// Hintergrund (Deine Textur)
|
||||||
|
const bgSprite = new NineSliceSprite({
|
||||||
|
texture: globalSheet.textures['SPR_SciFiMenus_Frame_Box_Large_11_Background'],
|
||||||
|
leftWidth: 200, topHeight: 200, rightWidth: 200, bottomHeight: 200
|
||||||
|
});
|
||||||
|
bgSprite.width = cardWidth;
|
||||||
|
bgSprite.height = cardHeight;
|
||||||
|
bgSprite.position.set(posX, posY);
|
||||||
|
bgSprite.tint = 0x0b192c;
|
||||||
|
bgSprite.alpha = 0.6;
|
||||||
|
sectorPixiContainer.addChild(bgSprite);
|
||||||
|
|
||||||
|
// Rahmen (Deine Textur)
|
||||||
|
const frameSprite = new NineSliceSprite({
|
||||||
|
texture: globalSheet.textures['SPR_SciFiMenus_Frame_Box_Large_11'],
|
||||||
|
leftWidth: 200, topHeight: 200, rightWidth: 200, bottomHeight: 200
|
||||||
|
});
|
||||||
|
frameSprite.width = cardWidth;
|
||||||
|
frameSprite.height = cardHeight;
|
||||||
|
frameSprite.position.set(posX, posY);
|
||||||
|
frameSprite.alpha = 0.8;
|
||||||
|
sectorPixiContainer.addChild(frameSprite);
|
||||||
|
|
||||||
|
// --- 3. PixiJS Icons (Exakt auf das absolute HTML abgestimmt) ---
|
||||||
|
|
||||||
|
// Icon: Lupe/Details (Jetzt ganz links! Zentrum bei x:20 -> passend zum Tooltip left:10)
|
||||||
|
const iconDetails = new Sprite(globalSheet.textures['ICON_SciFiMenus_Menu_Message_01_Clean']);
|
||||||
|
iconDetails.anchor.set(0.5);
|
||||||
|
iconDetails.position.set(posX + 25, posY + 27);
|
||||||
|
iconDetails.scale.set(0.08);
|
||||||
|
iconDetails.tint = 0xaaaaaa;
|
||||||
|
sectorPixiContainer.addChild(iconDetails);
|
||||||
|
|
||||||
|
// NEU Icon: Uhr/Alter
|
||||||
|
const iconAge = new Sprite(globalSheet.textures['ICON_SciFiMenus_Menu_Settings_01_Clean']);
|
||||||
|
iconAge.anchor.set(0.5);
|
||||||
|
iconAge.position.set(posX + 130, posY + 26);
|
||||||
|
iconAge.scale.set(0.08); // Exakt so klein wie die Lupe
|
||||||
|
iconAge.tint = 0xaaaaaa;
|
||||||
|
sectorPixiContainer.addChild(iconAge);
|
||||||
|
|
||||||
|
// Icon: Spieler (Zentrum bei x:20 -> passend zum Text left:35)
|
||||||
|
const iconPlayers = new Sprite(globalSheet.textures['ICON_SciFiMenus_Menu_Multiplayer_01_Clean']);
|
||||||
|
iconPlayers.anchor.set(0.5);
|
||||||
|
iconPlayers.position.set(posX + 25, posY + 62);
|
||||||
|
iconPlayers.scale.set(0.08);
|
||||||
|
iconPlayers.tint = 0xaaaaaa;
|
||||||
|
sectorPixiContainer.addChild(iconPlayers);
|
||||||
|
|
||||||
|
// Icon: Planeten (Weiter nach rechts! Zentrum bei x:165 -> passend zum Text left:180)
|
||||||
|
const iconPlanets = new Sprite(globalSheet.textures['ICON_SciFiMenus_Menu_Planet_02_Clean']);
|
||||||
|
iconPlanets.anchor.set(0.5);
|
||||||
|
iconPlanets.position.set(posX + 155, posY + 62);
|
||||||
|
iconPlanets.scale.set(0.08);
|
||||||
|
iconPlanets.tint = 0xaaaaaa;
|
||||||
|
sectorPixiContainer.addChild(iconPlanets);
|
||||||
|
|
||||||
|
// --- 4. Hover-Effekte ---
|
||||||
|
cardLink.addEventListener('mouseenter', () => {
|
||||||
|
frameSprite.alpha = 1.0;
|
||||||
|
bgSprite.alpha = 0.8;
|
||||||
|
|
||||||
|
iconDetails.tint = 0x00f0ff;
|
||||||
|
iconDetails.scale.set(0.09);
|
||||||
|
iconDetails.rotation = 0.2;
|
||||||
|
|
||||||
|
iconPlayers.tint = 0xffffff;
|
||||||
|
iconPlanets.tint = 0xffffff;
|
||||||
|
});
|
||||||
|
|
||||||
|
cardLink.addEventListener('mouseleave', () => {
|
||||||
|
frameSprite.alpha = 0.8;
|
||||||
|
bgSprite.alpha = 0.6;
|
||||||
|
|
||||||
|
iconDetails.tint = 0xaaaaaa;
|
||||||
|
iconDetails.scale.set(0.08);
|
||||||
|
iconDetails.rotation = 0;
|
||||||
|
|
||||||
|
iconPlayers.tint = 0xaaaaaa;
|
||||||
|
iconPlanets.tint = 0xaaaaaa;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prüft beim Start der Anwendung, ob der Nutzer bereits eingeloggt ist.
|
* Prüft beim Start der Anwendung, ob der Nutzer bereits eingeloggt ist.
|
||||||
* Nutz das Refresh-Token, um ein neues Access-Token anzufordern.
|
* Nutz das Refresh-Token, um ein neues Access-Token anzufordern.
|
||||||
@@ -1163,6 +1462,27 @@ function buildScene(app, bgTexture, sheet) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createSectorTabPixi(x, y, tint, alpha) {
|
||||||
|
const itemContainer = new Container();
|
||||||
|
itemContainer.position.set(x, y);
|
||||||
|
|
||||||
|
const fill = new NineSliceSprite({
|
||||||
|
texture: globalSheet.textures['SPR_SciFiMenus_Menu_Item_Background_07'],
|
||||||
|
leftWidth: 120, topHeight: 120, rightWidth: 120, bottomHeight: 120
|
||||||
|
});
|
||||||
|
fill.width = 180; fill.height = 40; fill.tint = tint; fill.alpha = alpha;
|
||||||
|
itemContainer.addChild(fill);
|
||||||
|
|
||||||
|
const frame = new NineSliceSprite({
|
||||||
|
texture: globalSheet.textures['SPR_SciFiMenus_Menu_Item_Selected_10'],
|
||||||
|
leftWidth: 100, topHeight: 120, rightWidth: 140, bottomHeight: 120
|
||||||
|
});
|
||||||
|
frame.width = 180; frame.height = 40; frame.alpha = 0.8;
|
||||||
|
itemContainer.addChild(frame);
|
||||||
|
|
||||||
|
return itemContainer;
|
||||||
|
}
|
||||||
|
|
||||||
checkAuthOnStartup();
|
checkAuthOnStartup();
|
||||||
initDropdowns();
|
initDropdowns();
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 44 KiB |
@@ -611,3 +611,149 @@ body, html {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Haupt-Container für die Sektor-Auswahl */
|
||||||
|
#sector-browser-layer {
|
||||||
|
position: absolute;
|
||||||
|
width: 1240px; /* Breit genug für 4 Karten nebeneinander */
|
||||||
|
height: 650px;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
/* Der Scale wird jetzt dynamisch von JS gesteuert! */
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Positionierung der Sektor-Tabs */
|
||||||
|
#sector-tab-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
|
||||||
|
/* Flexbox übernimmt das automatische Zentrieren */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wir überschreiben die festen Koordinaten vom Login-Screen */
|
||||||
|
#sector-tab-container .tab-btn {
|
||||||
|
position: relative;
|
||||||
|
left: 0 !important;
|
||||||
|
top: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tab-my-sectors { left: 45px; }
|
||||||
|
#tab-new-sectors { left: 235px; }
|
||||||
|
|
||||||
|
/* Grid-Bereich für die Sektor-Karten (Scrollbar bei Überlauf) */
|
||||||
|
#sector-list-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 80px;
|
||||||
|
width: 100%;
|
||||||
|
height: 540px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden; /* NEU: Verhindert horizontales Scrollen strikt */
|
||||||
|
|
||||||
|
/* KORREKTUR: Von 45px auf 40px reduziert, damit wir bei exakt 1235px landen */
|
||||||
|
padding: 20px 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 270px);
|
||||||
|
gap: 20px 25px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling der einzelnen Sektor-Karte als vollwertiger Link */
|
||||||
|
.sector-card-link {
|
||||||
|
display: block;
|
||||||
|
width: 270px;
|
||||||
|
height: 90px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--color-text-main);
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flex-Layout für den Inhalt der Karte */
|
||||||
|
.sector-card-content {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 15px 25px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zeile 1: Name, Lupe und Status */
|
||||||
|
.sector-row-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Das Unicode-Lupen-Symbol mit speziellem Hover-Effekt */
|
||||||
|
.sector-details-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-inactive);
|
||||||
|
transition: transform 0.2s ease, color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bei Hover rotiert und leuchtet die Lupe leicht auf */
|
||||||
|
.sector-card-link:hover .sector-details-icon {
|
||||||
|
color: var(--color-cyan-bright);
|
||||||
|
transform: scale(1.2) rotate(10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status-Anzeige (Online / Wartung) */
|
||||||
|
.sector-status {
|
||||||
|
margin-left: auto; /* Schiebt den Status ganz nach rechts */
|
||||||
|
font-size: 14px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
.status-online {
|
||||||
|
color: var(--color-green-bright);
|
||||||
|
text-shadow: 0 0 8px rgba(0, 255, 0, 0.6);
|
||||||
|
}
|
||||||
|
.status-maintenance {
|
||||||
|
color: var(--color-error);
|
||||||
|
text-shadow: 0 0 8px rgba(255, 68, 68, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zeile 2: Spieleranzahl & Planetenanzahl */
|
||||||
|
.sector-row-mid {
|
||||||
|
display: flex;
|
||||||
|
gap: 30px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-placeholder);
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zeile 3: Lokaler Username (falls vorhanden) */
|
||||||
|
.sector-row-bottom {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-cyan-bright);
|
||||||
|
margin-top: 4px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom Scrollbar für die Sektor-Liste im Sci-Fi Look */
|
||||||
|
#sector-list-container::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px; /* NEU: Falls je wieder ein x-Scrollbar auftaucht, ist er wenigstens nur 6px hoch */
|
||||||
|
}
|
||||||
|
#sector-list-container::-webkit-scrollbar-track {
|
||||||
|
background: rgba(11, 25, 44, 0.3);
|
||||||
|
}
|
||||||
|
#sector-list-container::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--color-cyan-dark);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||