Bleibende Sektor Elemente für UI fix
@@ -34,129 +34,34 @@ export const getSectorsList = async (req, res) => {
|
||||
* @param {import('express').Response} res - Das Express Response-Objekt
|
||||
*/
|
||||
export const joinSector = async (req, res) => {
|
||||
const accountId = req.user.accountId;
|
||||
const { sectorId, localUsername, gender } = req.body;
|
||||
|
||||
const lobby_uuid = req.user.lobby_uuid;
|
||||
const { sector_id } = req.body;
|
||||
|
||||
if (!sector_id) {
|
||||
// Basis-Validierung: Fehlt ein Feld, brechen wir ab
|
||||
if (!sectorId || !localUsername || !gender) {
|
||||
return res.status(400).json({
|
||||
error: 'ERR_MISSING_SECTOR_ID'
|
||||
error: 'ERR_MISSING_FIELDS'
|
||||
});
|
||||
}
|
||||
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
// Spieler-Daten aus der Lobby-Datenbank laden
|
||||
const userQuery = await client.query(`
|
||||
SELECT username, gender
|
||||
FROM lobby_accounts
|
||||
WHERE lobby_uuid = $1
|
||||
`, [lobby_uuid]);
|
||||
|
||||
if (userQuery.rows.length === 0) {
|
||||
return res.status(404).json({
|
||||
error: 'ERR_USER_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
const { username, gender } = userQuery.rows[0];
|
||||
|
||||
// Sektor-Daten laden, um die korrekte interne URL zu erfahren
|
||||
const sectorQuery = await client.query(`
|
||||
SELECT internal_url, registration_open
|
||||
FROM sectors
|
||||
WHERE id = $1
|
||||
`, [sector_id]);
|
||||
|
||||
if (sectorQuery.rows.length === 0) {
|
||||
return res.status(404).json({
|
||||
error: 'ERR_SECTOR_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
const sector = sectorQuery.rows[0];
|
||||
|
||||
// Sicherheits-Check: Ist die Registrierung für diesen Sektor noch offen?
|
||||
if (!sector.registration_open) {
|
||||
return res.status(403).json({
|
||||
error: 'ERR_SECTOR_REGISTRATION_CLOSED'
|
||||
});
|
||||
}
|
||||
|
||||
// Den internen POST-Request an das Sektor-Backend feuern
|
||||
const sectorApiUrl = `${sector.internal_url}/api/internal/accounts`;
|
||||
|
||||
const response = await fetch(sectorApiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
lobby_uuid,
|
||||
username,
|
||||
gender
|
||||
})
|
||||
});
|
||||
|
||||
// Fehlerbehandlung bei negativen HTTP-Statuscodes
|
||||
// fetch springt bei HTTP-Fehlern nicht in den catch-Block, daher prüfen wir response.ok
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const sectorError = errorData.error;
|
||||
|
||||
// Wenn der Sektor exakt in diesem Moment voll geworden ist
|
||||
if (sectorError === 'ERR_SECTOR_FULL') {
|
||||
// Sektor sicherheitshalber in der Lobby sofort schließen
|
||||
await client.query(`
|
||||
UPDATE sectors SET registration_open = false WHERE id = $1
|
||||
`, [sector_id]);
|
||||
return res.status(503).json({
|
||||
error: 'ERR_SECTOR_FULL'
|
||||
});
|
||||
}
|
||||
|
||||
if (sectorError === 'ERR_ACCOUNT_ALREADY_EXISTS') {
|
||||
return res.status(409).json({
|
||||
error: 'ERR_ACCOUNT_ALREADY_EXISTS'
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(`Sektor-Backend meldet unerwarteten Fehler: ${sectorError}`);
|
||||
}
|
||||
|
||||
const responseData = await response.json();
|
||||
const { triggerSectorClose } = responseData;
|
||||
|
||||
await client.query('BEGIN');
|
||||
|
||||
await client.query(`
|
||||
INSERT INTO account_sectors (lobby_uuid, sector_id)
|
||||
VALUES ($1, $2)
|
||||
`, [lobby_uuid, sector_id]);
|
||||
|
||||
// Auswertung der Notfall-Schließung
|
||||
// Wenn der Sektor uns meldet, dass das Puffer-Limit erreicht ist,
|
||||
// machen wir ihn dicht, damit er im Frontend verschwindet.
|
||||
if (triggerSectorClose) {
|
||||
console.warn(`[Lobby] Sektor ${sector_id} meldet Voll-Auslastung. Schließe Registrierung...`);
|
||||
await client.query(`
|
||||
UPDATE sectors
|
||||
SET registration_open = false
|
||||
WHERE id = $1
|
||||
`, [sector_id]);
|
||||
}
|
||||
|
||||
await client.query('COMMIT');
|
||||
await sectorService.joinSector(accountId, sectorId, localUsername, gender);
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'MSG_SECTOR_JOINED_SUCCESSFULLY'
|
||||
message: 'MSG_SECTOR_JOINED_SUCCESS'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
console.error('[Lobby] Fehler beim Beitritt zum Sektor:', error.message);
|
||||
return res.status(500).json({ error: 'ERR_INTERNAL_SERVER_ERROR' });
|
||||
} finally {
|
||||
client.release();
|
||||
console.error('[SectorController] Fehler beim Beitritt zum Sektor:', error.message);
|
||||
|
||||
if (error.code) {
|
||||
return res.status(error.code === 'ERR_SECTOR_FULL' ? 503 : 409).json({
|
||||
error: error.code
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(500).json({
|
||||
error: 'ERR_INTERNAL_SERVER'
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -21,9 +21,9 @@ export const initLobbyCronJobs = () => {
|
||||
try {
|
||||
// Alle Sektoren finden, deren Registrierung aktuell geschlossen ist
|
||||
const closedSectorsQuery = await client.query(`
|
||||
SELECT id, internal_url, name
|
||||
SELECT id, name, api_url
|
||||
FROM sectors
|
||||
WHERE registration_open = false
|
||||
WHERE is_registration_open = false
|
||||
`);
|
||||
|
||||
const closedSectors = closedSectorsQuery.rows;
|
||||
@@ -36,7 +36,7 @@ export const initLobbyCronJobs = () => {
|
||||
// Jeden geschlossenen Sektor einzeln über das Netzwerk abfragen
|
||||
for (const sector of closedSectors) {
|
||||
try {
|
||||
const response = await fetch(`${sector.internal_url}/api/internal/slots`);
|
||||
const response = await fetch(`${sector.api_url}/api/internal/slots`);
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn(`⚠️ [CRON] Sektor ${sector.name} hat einen Fehler gemeldet (Status: ${response.status}). Überspringe.`);
|
||||
@@ -53,7 +53,7 @@ export const initLobbyCronJobs = () => {
|
||||
|
||||
await client.query(`
|
||||
UPDATE sectors
|
||||
SET registration_open = true
|
||||
SET is_registration_open = true
|
||||
WHERE id = $1
|
||||
`, [sector.id]);
|
||||
} else {
|
||||
|
||||
@@ -4,11 +4,21 @@ import pool from '../db.js';
|
||||
// Void-Genesis - Heartbeat Service
|
||||
// ==============================================================================
|
||||
// Dieser Service pingt alle registrierten Sektoren regelmäßig an.
|
||||
|
||||
export const pingSectors = async () => {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
const result = await client.query('SELECT id, name, api_url FROM sectors WHERE api_url IS NOT NULL');
|
||||
const result = await client.query(`
|
||||
SELECT
|
||||
id, name, api_url, status,
|
||||
online_players, current_players, planets_count,
|
||||
max_galaxies, speed_economy, research_speed,
|
||||
fleet_speed_hostile, fleet_speed_peaceful, fleet_speed_friendly,
|
||||
fuel_consumption_factor, fleet_to_debris_percentage,
|
||||
defense_to_debris_percentage, fuel_included_in_debris,
|
||||
initial_res_premium, bonus_fields
|
||||
FROM sectors
|
||||
WHERE api_url IS NOT NULL
|
||||
`);
|
||||
const sectors = result.rows;
|
||||
|
||||
for (const sector of sectors) {
|
||||
@@ -23,63 +33,67 @@ export const pingSectors = async () => {
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === 'ok') {
|
||||
await client.query(
|
||||
`UPDATE sectors SET
|
||||
status = 'online',
|
||||
online_players = $1,
|
||||
current_players = $2,
|
||||
planets_count = $3,
|
||||
max_galaxies = $4,
|
||||
speed_economy = $5,
|
||||
research_speed = $6,
|
||||
fleet_speed_hostile = $7,
|
||||
fleet_speed_peaceful = $8,
|
||||
fleet_speed_friendly = $9,
|
||||
fuel_consumption_factor = $10,
|
||||
fleet_to_debris_percentage = $11,
|
||||
defense_to_debris_percentage = $12,
|
||||
fuel_included_in_debris = $13,
|
||||
initial_res_premium = $14,
|
||||
bonus_fields = $15
|
||||
WHERE id = $16`,
|
||||
[
|
||||
data.stats.online_players,
|
||||
data.stats.total_players,
|
||||
data.stats.planets_count,
|
||||
data.config.max_galaxies,
|
||||
data.config.speed_economy,
|
||||
data.config.research_speed,
|
||||
data.config.fleet_speed_hostile,
|
||||
data.config.fleet_speed_peaceful,
|
||||
data.config.fleet_speed_friendly,
|
||||
data.config.fuel_consumption_factor,
|
||||
data.config.fleet_to_debris_percentage,
|
||||
data.config.defense_to_debris_percentage,
|
||||
data.config.fuel_included_in_debris,
|
||||
data.config.initial_res_premium,
|
||||
data.config.bonus_fields,
|
||||
sector.id
|
||||
]
|
||||
);
|
||||
|
||||
const hasChanged = checkSectorChanges(sector, data);
|
||||
|
||||
if (hasChanged) {
|
||||
await client.query(
|
||||
`UPDATE sectors SET
|
||||
status = 'online',
|
||||
online_players = $1,
|
||||
current_players = $2,
|
||||
planets_count = $3,
|
||||
max_galaxies = $4,
|
||||
speed_economy = $5,
|
||||
research_speed = $6,
|
||||
fleet_speed_hostile = $7,
|
||||
fleet_speed_peaceful = $8,
|
||||
fleet_speed_friendly = $9,
|
||||
fuel_consumption_factor = $10,
|
||||
fleet_to_debris_percentage = $11,
|
||||
defense_to_debris_percentage = $12,
|
||||
fuel_included_in_debris = $13,
|
||||
initial_res_premium = $14,
|
||||
bonus_fields = $15
|
||||
WHERE id = $16`,
|
||||
[
|
||||
data.stats.online_players,
|
||||
data.stats.total_players,
|
||||
data.stats.planets_count,
|
||||
data.config.max_galaxies,
|
||||
data.config.speed_economy,
|
||||
data.config.research_speed,
|
||||
data.config.fleet_speed_hostile,
|
||||
data.config.fleet_speed_peaceful,
|
||||
data.config.fleet_speed_friendly,
|
||||
data.config.fuel_consumption_factor,
|
||||
data.config.fleet_to_debris_percentage,
|
||||
data.config.defense_to_debris_percentage,
|
||||
data.config.fuel_included_in_debris,
|
||||
data.config.initial_res_premium,
|
||||
data.config.bonus_fields,
|
||||
sector.id
|
||||
]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (sector.name === 'Development') {
|
||||
console.warn(`[Heartbeat-Trace] ⚠️ Fehler: JSON-Status war nicht 'ok', sondern:`, data.status);
|
||||
}
|
||||
await setSectorOffline(client, sector.id);
|
||||
await setSectorOffline(client, sector);
|
||||
}
|
||||
} else {
|
||||
if (sector.name === 'Development') {
|
||||
console.warn(`[Heartbeat-Trace] ⚠️ HTTP-Response nicht OK! Status-Code:`, response.status);
|
||||
}
|
||||
await setSectorOffline(client, sector.id);
|
||||
await setSectorOffline(client, sector);
|
||||
}
|
||||
} catch (error) {
|
||||
if (sector.name === 'Development') {
|
||||
console.error(`[Heartbeat-Trace] ❌ Crash bei Sektor 'Development'. Grund:`, error.message);
|
||||
}
|
||||
await setSectorOffline(client, sector.id);
|
||||
await setSectorOffline(client, sector);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -90,11 +104,55 @@ export const pingSectors = async () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Hilfsfunktion, um den Status eines Sektors auf 'offline' zu setzen.
|
||||
* Hilfsfunktion zum Vergleichen des alten DB-Stands mit den frischen API-Daten.
|
||||
* Wir nutzen Number() und Boolean(), da PostgreSQL manche Datentypen
|
||||
* (wie NUMERIC oder BIGINT) im Node.js-Treiber als String zurückgibt.
|
||||
*/
|
||||
const setSectorOffline = async (client, sectorId) => {
|
||||
await client.query(
|
||||
"UPDATE sectors SET status = 'offline' WHERE id = $1",
|
||||
[sectorId]
|
||||
);
|
||||
const checkSectorChanges = (oldData, newData) => {
|
||||
// Wenn der Sektor vorher als offline oder in Wartung markiert war,
|
||||
// muss er definitiv geupdatet werden, da er jetzt wieder erreichbar ist.
|
||||
if (oldData.status !== 'online') return true;
|
||||
|
||||
// Spielerzahlen und Planeten prüfen
|
||||
if (Number(oldData.online_players) !== newData.stats.online_players) return true;
|
||||
if (Number(oldData.current_players) !== newData.stats.total_players) return true;
|
||||
if (Number(oldData.planets_count) !== newData.stats.planets_count) return true;
|
||||
|
||||
// Globale Sektor-Einstellungen auf Änderungen prüfen
|
||||
if (Number(oldData.max_galaxies) !== newData.config.max_galaxies) return true;
|
||||
if (Number(oldData.speed_economy) !== newData.config.speed_economy) return true;
|
||||
if (Number(oldData.research_speed) !== newData.config.research_speed) return true;
|
||||
if (Number(oldData.fleet_speed_hostile) !== newData.config.fleet_speed_hostile) return true;
|
||||
if (Number(oldData.fleet_speed_peaceful) !== newData.config.fleet_speed_peaceful) return true;
|
||||
if (Number(oldData.fleet_speed_friendly) !== newData.config.fleet_speed_friendly) return true;
|
||||
|
||||
// Float-Werte
|
||||
if (Number(oldData.fuel_consumption_factor) !== Number(newData.config.fuel_consumption_factor)) return true;
|
||||
if (Number(oldData.fleet_to_debris_percentage) !== newData.config.fleet_to_debris_percentage) return true;
|
||||
if (Number(oldData.defense_to_debris_percentage) !== newData.config.defense_to_debris_percentage) return true;
|
||||
|
||||
// Boolean-Werte
|
||||
if (Boolean(oldData.fuel_included_in_debris) !== Boolean(newData.config.fuel_included_in_debris)) return true;
|
||||
|
||||
// Start-Boni
|
||||
if (Number(oldData.initial_res_premium) !== newData.config.initial_res_premium) return true;
|
||||
if (Number(oldData.bonus_fields) !== newData.config.bonus_fields) return true;
|
||||
|
||||
// Wurde kein Unterschied gefunden, geben wir false zurück
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Setzt den Status eines Sektors auf 'offline'.
|
||||
* Prüft vorher, ob der Sektor nicht ohnehin schon als offline in der DB steht,
|
||||
* um auch hier sinnlose Schreibzugriffe und Checkpoint-Logs zu verhindern.
|
||||
*/
|
||||
const setSectorOffline = async (client, sector) => {
|
||||
if (sector.status !== 'offline') {
|
||||
await client.query(
|
||||
"UPDATE sectors SET status = 'offline' WHERE id = $1",
|
||||
[sector.id]
|
||||
);
|
||||
sector.status = 'offline';
|
||||
}
|
||||
};
|
||||
@@ -591,32 +591,40 @@ function renderSectorList(sectors, type) {
|
||||
`;
|
||||
cardLink.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
console.log(`Starte Onboarding für Sektor: ${sector.name} (ID: ${sector.id})`);
|
||||
|
||||
selectedSectorId = sector.id;
|
||||
if (type === 'my') {
|
||||
console.log(`Betrete bekannten Sektor: ${sector.name}`);
|
||||
// todo für später, hier muss dann variable der link stehen oder man müsste ihn aus der datenbank auslesen
|
||||
window.open('https://dev.void-genesis.de', '_blank');
|
||||
|
||||
const sectorBrowserLayer = document.getElementById('sector-browser-layer');
|
||||
if (sectorBrowserLayer) {
|
||||
sectorBrowserLayer.style.opacity = 0.05;
|
||||
sectorBrowserLayer.style.pointerEvents = 'none';
|
||||
}
|
||||
} else {
|
||||
console.log(`Starte Onboarding für Sektor: ${sector.name} (ID: ${sector.id})`);
|
||||
|
||||
const onboardingHtmlLayer = document.getElementById('sector-onboarding-layer');
|
||||
if (onboardingHtmlLayer) onboardingHtmlLayer.style.display = 'flex';
|
||||
selectedSectorId = sector.id;
|
||||
|
||||
if (onboardingPixiContainer) onboardingPixiContainer.visible = true;
|
||||
const sectorBrowserLayer = document.getElementById('sector-browser-layer');
|
||||
if (sectorBrowserLayer) {
|
||||
sectorBrowserLayer.style.opacity = 0.05;
|
||||
sectorBrowserLayer.style.pointerEvents = 'none';
|
||||
}
|
||||
|
||||
const localUsernameInput = document.getElementById('local-username');
|
||||
if (localUsernameInput) {
|
||||
const userDataStr = sessionStorage.getItem('void_user') || localStorage.getItem('void_user');
|
||||
if (userDataStr) {
|
||||
try {
|
||||
const userData = JSON.parse(userDataStr);
|
||||
if (userData.username) {
|
||||
localUsernameInput.value = userData.username;
|
||||
const onboardingHtmlLayer = document.getElementById('sector-onboarding-layer');
|
||||
if (onboardingHtmlLayer) onboardingHtmlLayer.style.display = 'flex';
|
||||
|
||||
if (onboardingPixiContainer) onboardingPixiContainer.visible = true;
|
||||
|
||||
const localUsernameInput = document.getElementById('local-username');
|
||||
if (localUsernameInput) {
|
||||
const userDataStr = sessionStorage.getItem('void_user') || localStorage.getItem('void_user');
|
||||
if (userDataStr) {
|
||||
try {
|
||||
const userData = JSON.parse(userDataStr);
|
||||
if (userData.username) {
|
||||
localUsernameInput.value = userData.username;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Fehler beim Auslesen der User-Daten für das Onboarding:", err);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Fehler beim Auslesen der User-Daten für das Onboarding:", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -971,6 +979,7 @@ if (onboardingForm) {
|
||||
if (btnCancelOnboarding) btnCancelOnboarding.click();
|
||||
|
||||
loadAndRenderSectorBrowser();
|
||||
window.open('https://dev.void-genesis.de', '_blank');
|
||||
}, 1500);
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Void-Genesis | Sektor</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="game-wrapper">
|
||||
<div id="pixi-container"></div>
|
||||
|
||||
<div id="ui-layer">
|
||||
|
||||
<div id="sector-top-bar">
|
||||
<div class="top-bar-left">
|
||||
<span class="sector-name">Sektor: Development</span> |
|
||||
<a href="#" class="inactive-link">Account Name</a> |
|
||||
<a href="#" class="inactive-link">Profil bearbeiten</a>
|
||||
</div>
|
||||
|
||||
<div class="top-bar-right">
|
||||
<a href="#" class="inactive-link">Einstellungen</a> |
|
||||
<a href="#" class="inactive-link">Support</a> |
|
||||
<a href="http://lobby.void-genesis.de" id="btn-back-to-lobby">Zurück zur Lobby</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="resource-header">
|
||||
<div id="resource-bar">
|
||||
<div class="res-col" id="wrapper_res_primary" title="Lädt...">
|
||||
<div class="res-box" id="box_primary"><span class="res-icon" id="icon_primary"></span></div>
|
||||
<span class="res-val" id="val_res_primary">0</span>
|
||||
</div>
|
||||
<div class="res-col" id="wrapper_res_secondary" title="Lädt...">
|
||||
<div class="res-box" id="box_secondary"><span class="res-icon" id="icon_secondary"></span></div>
|
||||
<span class="res-val" id="val_res_secondary">0</span>
|
||||
</div>
|
||||
<div class="res-col" id="wrapper_res_fuel_base" title="Lädt...">
|
||||
<div class="res-box" id="box_fuel_base"><span class="res-icon" id="icon_fuel_base"></span></div>
|
||||
<span class="res-val" id="val_res_fuel_base">0</span>
|
||||
</div>
|
||||
<div class="res-col" id="wrapper_res_fuel_adv" title="Lädt...">
|
||||
<div class="res-box" id="box_fuel_adv"><span class="res-icon" id="icon_fuel_adv"></span></div>
|
||||
<span class="res-val" id="val_res_fuel_adv">0</span>
|
||||
</div>
|
||||
<div class="res-divider"></div>
|
||||
<div class="res-col" title="Energie">
|
||||
<div class="res-box" id="box_energy"><span class="res-icon" id="icon_energy"></span></div>
|
||||
<span class="res-val" id="val_energy">0</span>
|
||||
</div>
|
||||
<div class="res-divider"></div>
|
||||
<div class="res-col" id="wrapper_pop" title="Lädt...">
|
||||
<div class="res-box" id="box_pop_total"><span class="res-icon" id="icon_pop_total"></span></div>
|
||||
<span class="res-val" id="val_pop_total">0</span>
|
||||
</div>
|
||||
<div class="res-col" title="Zufriedenheit">
|
||||
<div class="res-box" id="box_pop_happiness"><span class="res-icon" id="icon_happiness">😊</span></div>
|
||||
<span class="res-val" id="val_pop_happiness">100%</span>
|
||||
</div>
|
||||
<div class="res-divider"></div>
|
||||
<div class="res-col" id="wrapper_food" title="Lädt...">
|
||||
<div class="res-box" id="box_food_total"><span class="res-icon" id="icon_food_total"></span></div>
|
||||
<span class="res-val" id="val_res_food_total">0</span>
|
||||
</div>
|
||||
<div class="res-divider"></div>
|
||||
<div class="res-col premium" id="wrapper_premium" title="Lädt...">
|
||||
<div class="res-box" id="box_premium"><span class="res-icon" id="icon_premium"></span></div>
|
||||
<span class="res-val" id="val_res_premium">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav id="side-menu-container">
|
||||
</nav>
|
||||
|
||||
<div id="planet-limit-container">
|
||||
<span class="planet-limit-text">Planeten: <span id="val_planet_count">10</span> / <span id="val_planet_max">12</span></span>
|
||||
</div>
|
||||
|
||||
<aside id="right-sidebar-container">
|
||||
</aside>
|
||||
|
||||
<footer id="game-footer">
|
||||
<div class="footer-left">v 0.0.1.0-dev (Sector)</div>
|
||||
<div class="footer-center">© 2026 Void-Genesis</div>
|
||||
<div class="footer-right">
|
||||
<a href="https://discord.gg/agQhwDdVfK" target="_blank">Discord</a> |
|
||||
<a href="https://lobby.void-genesis.de/impressum.html" target="_blank">Impressum</a> |
|
||||
<a href="https://lobby.void-genesis.de/datenschutz.html" target="_blank">Datenschutz</a> |
|
||||
<a href="https://lobby.void-genesis.de/agb.html" target="_blank">AGB</a> |
|
||||
<a href="https://lobby.void-genesis.de/regeln.html" target="_blank">Spielregeln</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -11,6 +11,7 @@
|
||||
"pixi.js": "^8.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.2.11"
|
||||
"free-tex-packer-core": "^0.3.8",
|
||||
"vite": "^8.0.16"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// === File: sector-frontend/scripts/generate-spritesheets.js ===
|
||||
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
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Pfad-Konfiguration
|
||||
// ----------------------------------------------------------------------
|
||||
// Der Hauptordner, in dem wir Unterordner für die verschiedenen Kategorien anlegen.
|
||||
const RAW_ASSETS_DIR = path.resolve(__dirname, '../src/raw-assets');
|
||||
// Zielordner für die fertigen Atlanten, die dann in der App geladen werden.
|
||||
const OUTPUT_DIR = path.resolve(__dirname, '../src/assets');
|
||||
|
||||
/**
|
||||
* Durchsucht den RAW_ASSETS_DIR nach Unterordnern und generiert
|
||||
* für jeden gefundenen Ordner ein separates Sprite-Sheet.
|
||||
*/
|
||||
async function buildAllSpriteSheets() {
|
||||
console.log(`[Packer] Prüfe Hauptverzeichnis: ${RAW_ASSETS_DIR}`);
|
||||
|
||||
// Hauptordner erstellen, falls er nicht existiert
|
||||
if (!fs.existsSync(RAW_ASSETS_DIR)) {
|
||||
fs.mkdirSync(RAW_ASSETS_DIR, { recursive: true });
|
||||
console.log('[Packer] Quellordner existierte nicht und wurde erstellt. Bitte Unterordner (z.B. "icons") anlegen und Bilder einfügen.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Lese alle Einträge im Hauptordner aus
|
||||
const entries = fs.readdirSync(RAW_ASSETS_DIR, { withFileTypes: true });
|
||||
|
||||
// Filtere nur die Verzeichnisse (Unterordner) heraus
|
||||
const directories = entries.filter(entry => entry.isDirectory()).map(dir => dir.name);
|
||||
|
||||
if (directories.length === 0) {
|
||||
console.log('[Packer] Keine Unterordner in raw-assets gefunden. Bitte lege Ordner wie "icons" oder "frames" an.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Zielordner erstellen, falls nicht vorhanden
|
||||
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
// Schleife durch alle gefundenen Unterordner
|
||||
for (const folderName of directories) {
|
||||
const currentDirPath = path.join(RAW_ASSETS_DIR, folderName);
|
||||
const files = fs.readdirSync(currentDirPath);
|
||||
|
||||
const images = [];
|
||||
|
||||
// Suche nach PNG-Dateien im aktuellen Unterordner
|
||||
for (const file of files) {
|
||||
if (file.endsWith('.png')) {
|
||||
const filePath = path.join(currentDirPath, file);
|
||||
images.push({
|
||||
path: file,
|
||||
contents: fs.readFileSync(filePath)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (images.length === 0) {
|
||||
console.log(`[Packer] Überspringe Ordner '${folderName}' (keine PNG-Dateien gefunden).`);
|
||||
continue; // Zum nächsten Ordner springen
|
||||
}
|
||||
|
||||
// Konfiguration für DIESEN spezifischen Atlas
|
||||
const options = {
|
||||
textureName: folderName, // Der Name des Ordners wird zum Namen der generierten Dateien
|
||||
width: 4096,
|
||||
height: 4096,
|
||||
padding: 2,
|
||||
allowRotation: false,
|
||||
exporter: 'Pixi',
|
||||
removeFileExtension: true
|
||||
};
|
||||
|
||||
console.log(`[Packer] Verpacke ${images.length} Bilder aus dem Ordner '${folderName}'...`);
|
||||
|
||||
// Packer-Aufruf in ein Promise wickeln, um asynchrones Verarbeiten sauber abzuarbeiten
|
||||
await new Promise((resolve, reject) => {
|
||||
texturePacker(images, options, (packedFiles, error) => {
|
||||
if (error) {
|
||||
console.error(`[Packer] Fehler beim Erstellen des Atlases für '${folderName}':`, error);
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generierte Dateien speichern (.png und .json)
|
||||
for (const item of packedFiles) {
|
||||
const outPath = path.join(OUTPUT_DIR, item.name);
|
||||
fs.writeFileSync(outPath, item.buffer);
|
||||
console.log(`[Packer] Gespeichert: ${outPath}`);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[Packer] Alle Sprite-Sheets wurden erfolgreich generiert!');
|
||||
}
|
||||
|
||||
// Skript ausführen
|
||||
buildAllSpriteSheets();
|
||||
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
@@ -0,0 +1,351 @@
|
||||
{
|
||||
"frames": {
|
||||
"SPR_SciFiMenus_Button_Frame_01": {
|
||||
"frame": {
|
||||
"x": 2,
|
||||
"y": 2,
|
||||
"w": 1005,
|
||||
"h": 236
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 11,
|
||||
"y": 13,
|
||||
"w": 1005,
|
||||
"h": 236
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 1024,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"SPR_SciFiMenus_Frame_Box_Large_12": {
|
||||
"frame": {
|
||||
"x": 1011,
|
||||
"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_08": {
|
||||
"frame": {
|
||||
"x": 2,
|
||||
"y": 242,
|
||||
"w": 974,
|
||||
"h": 990
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 25,
|
||||
"y": 19,
|
||||
"w": 974,
|
||||
"h": 990
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 1024,
|
||||
"h": 1024
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"SPR_SciFiMenus_Frame_Box_Large_12_Background": {
|
||||
"frame": {
|
||||
"x": 1999,
|
||||
"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_Frame_Box_Large_08_Background": {
|
||||
"frame": {
|
||||
"x": 2901,
|
||||
"y": 2,
|
||||
"w": 874,
|
||||
"h": 875
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 73,
|
||||
"y": 73,
|
||||
"w": 874,
|
||||
"h": 875
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 1024,
|
||||
"h": 1024
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"SPR_SciFiMenus_Background_01": {
|
||||
"frame": {
|
||||
"x": 3779,
|
||||
"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": 3779,
|
||||
"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_Ring_Medium_13": {
|
||||
"frame": {
|
||||
"x": 1999,
|
||||
"y": 399,
|
||||
"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_Menu_Item_Selected_10": {
|
||||
"frame": {
|
||||
"x": 3779,
|
||||
"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
|
||||
}
|
||||
},
|
||||
"SPR_SciFiMenus_Menu_Item_Selected_19": {
|
||||
"frame": {
|
||||
"x": 3779,
|
||||
"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_Button_Frame_01_Background": {
|
||||
"frame": {
|
||||
"x": 2829,
|
||||
"y": 881,
|
||||
"w": 805,
|
||||
"h": 203
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 112,
|
||||
"y": 28,
|
||||
"w": 805,
|
||||
"h": 203
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 1024,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"SPR_SciFiMenus_Frame_Box_Small_10_Background": {
|
||||
"frame": {
|
||||
"x": 3638,
|
||||
"y": 881,
|
||||
"w": 127,
|
||||
"h": 123
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 64,
|
||||
"y": 67,
|
||||
"w": 127,
|
||||
"h": 123
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"SPR_SciFiMenus_Menu_Item_Background_07": {
|
||||
"frame": {
|
||||
"x": 3769,
|
||||
"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
|
||||
}
|
||||
},
|
||||
"SPR_SciFiMenus_Background_Hexagons_01": {
|
||||
"frame": {
|
||||
"x": 2829,
|
||||
"y": 1088,
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"app": "http://github.com/odrick/free-tex-packer-core",
|
||||
"version": "0.3.8",
|
||||
"image": "frames.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 4037,
|
||||
"h": 1602
|
||||
},
|
||||
"scale": 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.0 MiB |
@@ -0,0 +1,735 @@
|
||||
{
|
||||
"frames": {
|
||||
"ICON_SM_Item_Crystal_03": {
|
||||
"frame": {
|
||||
"x": 2,
|
||||
"y": 2,
|
||||
"w": 208,
|
||||
"h": 242
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 24,
|
||||
"y": 7,
|
||||
"w": 208,
|
||||
"h": 242
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_SciFiMenus_Menu_Message_01_Clean": {
|
||||
"frame": {
|
||||
"x": 2,
|
||||
"y": 248,
|
||||
"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
|
||||
}
|
||||
},
|
||||
"ICON_SciFi_Status_Hunger": {
|
||||
"frame": {
|
||||
"x": 2,
|
||||
"y": 470,
|
||||
"w": 210,
|
||||
"h": 212
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 22,
|
||||
"y": 22,
|
||||
"w": 210,
|
||||
"h": 212
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_SciFiMenus_Menu_Delete_01_Clean": {
|
||||
"frame": {
|
||||
"x": 183,
|
||||
"y": 248,
|
||||
"w": 166,
|
||||
"h": 209
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 45,
|
||||
"y": 23,
|
||||
"w": 166,
|
||||
"h": 209
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_SciFiMenus_Menu_DNA_01_Clean": {
|
||||
"frame": {
|
||||
"x": 214,
|
||||
"y": 2,
|
||||
"w": 102,
|
||||
"h": 209
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 77,
|
||||
"y": 24,
|
||||
"w": 102,
|
||||
"h": 209
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_Inventory_Notes": {
|
||||
"frame": {
|
||||
"x": 320,
|
||||
"y": 2,
|
||||
"w": 147,
|
||||
"h": 205
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 54,
|
||||
"y": 26,
|
||||
"w": 147,
|
||||
"h": 205
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_SciFiMenus_Menu_Refresh_01_Clean": {
|
||||
"frame": {
|
||||
"x": 2,
|
||||
"y": 686,
|
||||
"w": 175,
|
||||
"h": 200
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 41,
|
||||
"y": 28,
|
||||
"w": 175,
|
||||
"h": 200
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_SciFiMenus_Menu_Shop_01_Clean": {
|
||||
"frame": {
|
||||
"x": 2,
|
||||
"y": 890,
|
||||
"w": 196,
|
||||
"h": 199
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 22,
|
||||
"y": 29,
|
||||
"w": 196,
|
||||
"h": 199
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_SciFiMenus_Menu_Solo_01_Clean": {
|
||||
"frame": {
|
||||
"x": 181,
|
||||
"y": 686,
|
||||
"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
|
||||
}
|
||||
},
|
||||
"ICON_SciFiMenus_Menu_Warning_02_Clean": {
|
||||
"frame": {
|
||||
"x": 216,
|
||||
"y": 461,
|
||||
"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_Notification_01_Clean": {
|
||||
"frame": {
|
||||
"x": 353,
|
||||
"y": 211,
|
||||
"w": 61,
|
||||
"h": 193
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 97,
|
||||
"y": 32,
|
||||
"w": 61,
|
||||
"h": 193
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_SciFi_Inventory_Minerals": {
|
||||
"frame": {
|
||||
"x": 418,
|
||||
"y": 211,
|
||||
"w": 198,
|
||||
"h": 188
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 29,
|
||||
"y": 34,
|
||||
"w": 198,
|
||||
"h": 188
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_SciFiMenus_Menu_Settings_04_Clean": {
|
||||
"frame": {
|
||||
"x": 471,
|
||||
"y": 2,
|
||||
"w": 183,
|
||||
"h": 183
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 36,
|
||||
"y": 36,
|
||||
"w": 183,
|
||||
"h": 183
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_SciFiMenus_Menu_Settings_05_Clean": {
|
||||
"frame": {
|
||||
"x": 2,
|
||||
"y": 1093,
|
||||
"w": 183,
|
||||
"h": 183
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 36,
|
||||
"y": 36,
|
||||
"w": 183,
|
||||
"h": 183
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_SciFiMenus_Menu_Star_01_Clean": {
|
||||
"frame": {
|
||||
"x": 2,
|
||||
"y": 1280,
|
||||
"w": 180,
|
||||
"h": 181
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 38,
|
||||
"y": 38,
|
||||
"w": 180,
|
||||
"h": 181
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_Inventory_Healing": {
|
||||
"frame": {
|
||||
"x": 2,
|
||||
"y": 1465,
|
||||
"w": 188,
|
||||
"h": 174
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 33,
|
||||
"y": 40,
|
||||
"w": 188,
|
||||
"h": 174
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_Map_Lightning": {
|
||||
"frame": {
|
||||
"x": 186,
|
||||
"y": 1280,
|
||||
"w": 116,
|
||||
"h": 170
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 70,
|
||||
"y": 43,
|
||||
"w": 116,
|
||||
"h": 170
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_SciFiMenus_Menu_Settings_03_Clean": {
|
||||
"frame": {
|
||||
"x": 189,
|
||||
"y": 1093,
|
||||
"w": 154,
|
||||
"h": 169
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 51,
|
||||
"y": 44,
|
||||
"w": 154,
|
||||
"h": 169
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_SciFiMenus_Menu_Lock_Closed_01_Clean": {
|
||||
"frame": {
|
||||
"x": 202,
|
||||
"y": 884,
|
||||
"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_SciFiMenus_Menu_Settings_02_Clean": {
|
||||
"frame": {
|
||||
"x": 338,
|
||||
"y": 884,
|
||||
"w": 180,
|
||||
"h": 155
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 38,
|
||||
"y": 51,
|
||||
"w": 180,
|
||||
"h": 155
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_SciFiMenus_Menu_Planet_02_Clean": {
|
||||
"frame": {
|
||||
"x": 310,
|
||||
"y": 658,
|
||||
"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_Moon_01_Clean": {
|
||||
"frame": {
|
||||
"x": 522,
|
||||
"y": 403,
|
||||
"w": 214,
|
||||
"h": 147
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 20,
|
||||
"y": 55,
|
||||
"w": 214,
|
||||
"h": 147
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_SciFiMenus_Menu_Planet_03_Clean": {
|
||||
"frame": {
|
||||
"x": 620,
|
||||
"y": 189,
|
||||
"w": 214,
|
||||
"h": 147
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 20,
|
||||
"y": 55,
|
||||
"w": 214,
|
||||
"h": 147
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_SciFiMenus_Menu_Tick_01_Clean": {
|
||||
"frame": {
|
||||
"x": 740,
|
||||
"y": 2,
|
||||
"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
|
||||
}
|
||||
},
|
||||
"ICON_SciFiMenus_Menu_Multiplayer_01_Clean": {
|
||||
"frame": {
|
||||
"x": 935,
|
||||
"y": 2,
|
||||
"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_Cancel_01_Clean": {
|
||||
"frame": {
|
||||
"x": 1157,
|
||||
"y": 2,
|
||||
"w": 140,
|
||||
"h": 140
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {
|
||||
"x": 58,
|
||||
"y": 58,
|
||||
"w": 140,
|
||||
"h": 140
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 256,
|
||||
"h": 256
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_Deuterium": {
|
||||
"frame": {
|
||||
"x": 1301,
|
||||
"y": 2,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_Silizium": {
|
||||
"frame": {
|
||||
"x": 1433,
|
||||
"y": 2,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_Titanium": {
|
||||
"frame": {
|
||||
"x": 2,
|
||||
"y": 1643,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
},
|
||||
"ICON_Tritium": {
|
||||
"frame": {
|
||||
"x": 2,
|
||||
"y": 1775,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"sourceSize": {
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"pivot": {
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"app": "http://github.com/odrick/free-tex-packer-core",
|
||||
"version": "0.3.8",
|
||||
"image": "icons.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 1563,
|
||||
"h": 1905
|
||||
},
|
||||
"scale": 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 277 KiB |
@@ -0,0 +1,393 @@
|
||||
/* ==========================================================================
|
||||
TOP-BAR (Ganz oben, Account- & Sektor-Infos)
|
||||
========================================================================== */
|
||||
#sector-top-bar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
box-sizing: border-box;
|
||||
font-size: 12px;
|
||||
color: var(--color-text-inactive);
|
||||
}
|
||||
|
||||
#sector-top-bar a {
|
||||
color: var(--color-text-inactive);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease, text-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
#sector-top-bar a:hover:not(.inactive-link) {
|
||||
color: var(--color-cyan-bright);
|
||||
text-shadow: 0 0 5px var(--color-cyan-bright);
|
||||
}
|
||||
|
||||
/* Links, die derzeit noch keine Funktion haben, visuell abstufen */
|
||||
.inactive-link {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.sector-name {
|
||||
color: var(--color-text-main);
|
||||
font-weight: bold;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
RESSOURCEN-HEADER (Unter der Top-Bar)
|
||||
========================================================================== */
|
||||
#resource-header {
|
||||
position: absolute;
|
||||
top: 45px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
box-sizing: border-box;
|
||||
pointer-events: auto;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
/* --- RESSOURCEN MITTIG --- */
|
||||
#resource-bar {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.res-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
background: rgba(0, 0, 0, 0.01);
|
||||
padding: 5px 10px;
|
||||
border-radius: 8px;
|
||||
cursor: help;
|
||||
pointer-events: auto;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
/* Der Rahmen für das Icon */
|
||||
.res-box {
|
||||
background: transparent;
|
||||
border: 1px solid var(--color-res-border);
|
||||
border-radius: 6px;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
||||
}
|
||||
|
||||
/* Hover-Effekt anpassen */
|
||||
.res-col:hover .res-box {
|
||||
border-color: var(--color-cyan-bright);
|
||||
box-shadow: 0 0 10px rgba(0, 240, 255, 0.4);
|
||||
background-color: rgba(0, 240, 255, 0.15);
|
||||
}
|
||||
|
||||
.res-val {
|
||||
font-size: 13px;
|
||||
color: var(--color-text-main);
|
||||
font-weight: bold;
|
||||
text-shadow: 0 0 4px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.res-icon {
|
||||
font-size: 24px;
|
||||
color: var(--color-cyan-bright);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.res-col.premium .res-box {
|
||||
border-color: rgba(255, 0, 255, 0.4);
|
||||
}
|
||||
.res-col.premium .res-icon {
|
||||
color: #ff00ff;
|
||||
}
|
||||
.res-col.premium:hover .res-box {
|
||||
border-color: #ff00ff;
|
||||
box-shadow: 0 0 10px rgba(255, 0, 255, 0.5);
|
||||
background-color: rgba(255, 0, 255, 0.15);
|
||||
}
|
||||
|
||||
.res-divider {
|
||||
/*width: 1px;*/
|
||||
height: 60px;
|
||||
background: var(--color-cyan-dark);
|
||||
opacity: 0.3;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
SIDE-MENU (Linke Navigation)
|
||||
========================================================================== */
|
||||
#side-menu-container {
|
||||
position: absolute;
|
||||
top: 160px;
|
||||
left: 20px;
|
||||
width: 210px;
|
||||
bottom: 60px;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
pointer-events: auto;
|
||||
z-index: 20;
|
||||
padding-right: 15px;
|
||||
box-sizing: border-box;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
/* Der einzelne, klickbare Wrapper für einen Menüpunkt */
|
||||
.menu-item {
|
||||
position: relative;
|
||||
width: 200px;
|
||||
height: 30px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 70px;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Der Text innerhalb des Menüpunkts */
|
||||
.menu-text {
|
||||
color: var(--color-text-main);
|
||||
font-size: 14px;
|
||||
text-shadow: 0 0 4px rgba(0, 0, 0, 0.8);
|
||||
transition: color 0.2s ease, text-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
/* Schöner Hover-Effekt: Text leuchtet in unserem Cyan auf */
|
||||
.menu-item:hover .menu-text {
|
||||
color: var(--color-text-main);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
PLANETEN-LIMIT (Zähler über der rechten Sidebar)
|
||||
========================================================================== */
|
||||
#planet-limit-container {
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
right: 20px;
|
||||
width: 215px;
|
||||
height: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 20;
|
||||
/* Da es ein reines Info-Element ist, deaktivieren wir die Klicks */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.planet-limit-text {
|
||||
color: var(--color-text-main);
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 0 4px rgba(0, 0, 0, 0.8);
|
||||
/* Ein kleiner Abstand zwischen Buchstaben (Letter-Spacing) lässt
|
||||
Sci-Fi UIs oft etwas hochwertiger wirken */
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
#val_planet_count {
|
||||
color: var(--color-cyan-bright); /* Aktuelle Planetenanzahl hervorheben */
|
||||
}
|
||||
|
||||
#val_planet_max {
|
||||
color: var(--color-text-inactive); /* Das Limit etwas abdunkeln */
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
RIGHT-SIDEBAR (Rechte Planeten-Navigation)
|
||||
========================================================================== */
|
||||
#right-sidebar-container {
|
||||
position: absolute;
|
||||
top: 160px;
|
||||
right: 20px;
|
||||
width: 215px;
|
||||
|
||||
bottom: 60px;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
pointer-events: auto;
|
||||
z-index: 20;
|
||||
padding-left: 5px;
|
||||
padding-right: 30px;
|
||||
|
||||
box-sizing: border-box;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
PLANETEN-EINTRÄGE (70/30 HTML-Struktur)
|
||||
========================================================================== */
|
||||
.planet-item {
|
||||
position: relative;
|
||||
width: 200px;
|
||||
height: 90px;
|
||||
flex-shrink: 0;
|
||||
/* Wir nutzen jetzt eine Reihe (Row), um die Bereiche nebeneinander zu legen */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch; /* Beide Bereiche füllen die volle Höhe von 90px aus */
|
||||
justify-content: flex-start;
|
||||
box-sizing: border-box;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Der 70% Hauptbereich für den Planeten */
|
||||
.planet-click-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Der 30% Nebenbereich für den Mond */
|
||||
.moon-click-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
/* Standardmäßig kein Pointer, da die Box leer sein könnte */
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Trennlinie und Klick-Cursor NUR anzeigen, wenn auch wirklich ein Mond da ist */
|
||||
.moon-click-area.has-moon {
|
||||
cursor: pointer;
|
||||
border-left: 1px solid rgba(0, 240, 255, 0.1);
|
||||
}
|
||||
|
||||
/* HTML-Anker für PixiJS */
|
||||
.planet-icon-anchor {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.moon-icon-anchor {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
z-index: 25;
|
||||
border-radius: 50%;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
/* Hover-Effekte skalieren das Icon, wenn der Spieler im 30%-Feld ist */
|
||||
.moon-click-area:hover .moon-icon-anchor {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
|
||||
.planet-text-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.planet-name {
|
||||
color: var(--color-text-main);
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 0 4px rgba(0, 0, 0, 0.8);
|
||||
transition: color 0.2s ease, text-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.planet-coords {
|
||||
color: var(--color-text-inactive);
|
||||
font-size: 11px;
|
||||
text-shadow: 0 0 4px rgba(0, 0, 0, 0.8);
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
SCROLLBAR-STYLING FÜR WEBKIT (Chrome, Safari, Edge)
|
||||
========================================================================== */
|
||||
/* Definiert die Breite der Scrollbar für beide Menüs */
|
||||
#side-menu-container::-webkit-scrollbar,
|
||||
#right-sidebar-container::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
/* Die sichtbare "Spur" (Track), auf der der Scrollbalken läuft */
|
||||
#side-menu-container::-webkit-scrollbar-track,
|
||||
#right-sidebar-container::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Der sichtbare, greifbare Teil des Scrollbalkens (Thumb) */
|
||||
#side-menu-container::-webkit-scrollbar-thumb,
|
||||
#right-sidebar-container::-webkit-scrollbar-thumb {
|
||||
background: var(--color-cyan-dark);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Hover-Effekt: Der Anfasser leuchtet im helleren Cyan auf */
|
||||
#side-menu-container::-webkit-scrollbar-thumb:hover,
|
||||
#right-sidebar-container::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-cyan-bright);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
FOOTER
|
||||
========================================================================== */
|
||||
#game-footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background: var(--color-panel-bg);
|
||||
border-top: 1px solid var(--color-cyan-dark);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 40px;
|
||||
box-sizing: border-box;
|
||||
color: var(--color-text-inactive);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
#game-footer a:hover {
|
||||
color: var(--color-cyan-bright);
|
||||
text-shadow: 0 0 5px var(--color-cyan-bright);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Beinhaltet das absolute Grundgerüst der Sektor-Seite.
|
||||
* Reset-Regeln und die Positionierung der Haupt-Container (Z-Index Ebenen).
|
||||
* Dies stellt sicher, dass das UI immer über dem PixiJS-Canvas liegt.
|
||||
*/
|
||||
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: var(--color-bg-page);
|
||||
font-family: var(--font-ui);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
position: fixed;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
#game-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Der Container für den PixiJS-Hintergrund und die Spielwelt */
|
||||
#pixi-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Die UI-Ebene liegt über dem Pixi-Canvas */
|
||||
#ui-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* * Alle interaktiven HUD-Elemente müssen pointer-events wieder aktivieren,
|
||||
* sonst können wir keine Links klicken oder Hover-Effekte auslösen.
|
||||
*/
|
||||
#sector-top-bar,
|
||||
#resource-header,
|
||||
#game-footer {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* Verhindert den unsichtbaren Scroll-Abstand (Baseline-Gap) bei Canvas-Elementen */
|
||||
#pixi-container canvas {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Globale Variablen für das Sektor-Frontend.
|
||||
* Wir nutzen eine temporäre Standard-Schriftart für das UI und die bekannte Logo-Font.
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: 'UI Font';
|
||||
src: url('../assets/fonts/Oswald-Regular.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Logo Font';
|
||||
src: url('../assets/fonts/Quantico-Bold.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
:root {
|
||||
--font-ui: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
--font-ui: 'UI Font', sans-serif;
|
||||
--font-logo: 'Logo Font', sans-serif;
|
||||
--color-bg-page: #000000;
|
||||
--color-panel-bg: rgba(11, 25, 44, 0.85);
|
||||
--color-text-main: #ffffff;
|
||||
--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-res-border: rgba(0, 240, 255, 0.3);
|
||||
--color-res-bg: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 358 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 16 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 |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
@@ -0,0 +1,3 @@
|
||||
@import url('./src/css/layout.css');
|
||||
@import url('./src/css/variables.css');
|
||||
@import url('./src/css/hud.css');
|
||||
@@ -0,0 +1,31 @@
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
server: {
|
||||
// Erlaubt Vite, Verbindungen über diese spezifische Domain anzunehmen.
|
||||
// Verhindert Blockaden, wenn wir hinter dem Nginx Proxy Manager arbeiten.
|
||||
allowedHosts: [
|
||||
'sector.void-genesis.de',
|
||||
'dev.void-genesis.de'
|
||||
],
|
||||
// 'host: true' stellt sicher, dass Vite auf allen Netzwerk-Interfaces (0.0.0.0) lauscht.
|
||||
// Das ist zwingend notwendig, damit wir aus dem Docker-Container heraus zugreifen können.
|
||||
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.
|
||||
// Dadurch zwingen wir Vite dazu, sämtliche importierten Grafiken (PixiJS Assets)
|
||||
// als Base64-Strings direkt in die generierten JS/CSS-Dateien einzubetten.
|
||||
// So liegen nach dem Build keine rohen Bilddateien mehr auf dem Server.
|
||||
// ----------------------------------------------------------------------
|
||||
assetsInlineLimit: 100000000,
|
||||
|
||||
// Wir deaktivieren das Generieren von Source Maps für den produktiven Build,
|
||||
// da diese durch die Base64-Strings gigantisch groß werden würden und wir
|
||||
// unsere Code-Struktur nicht nach außen freigeben wollen.
|
||||
sourcemap: false,
|
||||
}
|
||||
});
|
||||