diff --git a/dev/sector-dev/sector-frontend/index.html b/dev/sector-dev/sector-frontend/index.html index 69ba643..45888e7 100644 --- a/dev/sector-dev/sector-frontend/index.html +++ b/dev/sector-dev/sector-frontend/index.html @@ -13,87 +13,92 @@
+
-
-
- Sektor: Development | - Account Name | - Profil bearbeiten +
+
+ Sektor: Development | + Account Name | + Profil bearbeiten +
+ +
-
- Einstellungen | - Support | - Zurück zur Lobby +
+
+ +
+
+
+ 0 +
+
+
+ 0 +
+
+
+ 0 +
+
+
+ 0 +
+
+
+
+ 0 +
+
+
+
+ 0 +
+
+
😊
+ 100% +
+
+
+
+ 0 +
+
+
+
+ 0 +
+
+ + + +
+ Planeten: 10 / 12 +
+ + +
-
-
-
-
- 0 +
+ -
-
- 0 -
-
-
- 0 -
-
-
- 0 -
-
-
-
- 0 -
-
-
-
- 0 -
-
-
😊
- 100% -
-
-
-
- 0 -
-
-
-
- 0 -
-
-
- - - -
- Planeten: 10 / 12 -
- - - - +
diff --git a/dev/sector-dev/sector-frontend/src/components/footer/footer.css b/dev/sector-dev/sector-frontend/src/components/footer/footer.css new file mode 100644 index 0000000..e69f3bf --- /dev/null +++ b/dev/sector-dev/sector-frontend/src/components/footer/footer.css @@ -0,0 +1,42 @@ +#game-footer { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + min-width: 1200px; + height: 40px; + background: var(--color-panel-bg); + border-top: 1px solid var(--color-cyan-dark); + pointer-events: auto; + z-index: 20; +} + +.footer-safe-area { + width: 100%; + min-width: 1200px; + max-width: 1600px; + height: 100%; + margin: 0 auto; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 40px; + box-sizing: border-box; + color: var(--color-text-inactive); + font-size: 12px; +} + +#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); +} + +.footer-left { flex: 1; text-align: left; } +.footer-center { flex: 1; text-align: center; } +.footer-right { flex: 1; text-align: right; } diff --git a/dev/sector-dev/sector-frontend/src/components/footer/footer.js b/dev/sector-dev/sector-frontend/src/components/footer/footer.js new file mode 100644 index 0000000..189f298 --- /dev/null +++ b/dev/sector-dev/sector-frontend/src/components/footer/footer.js @@ -0,0 +1,15 @@ +/** + * Initialisiert die Footer-Komponente. + * Hier binden wir später Event-Listener für die externen Links + * oder rendern dynamisch die aktuelle Spielversion aus einer Config. + */ +export function initFooter() { + const footerElement = document.getElementById('game-footer'); + + if (!footerElement) { + console.warn('[Footer] Konnte #game-footer nicht im DOM finden.'); + return; + } + + //console.log('[Footer] Erfolgreich initialisiert.'); +} \ No newline at end of file diff --git a/dev/sector-dev/sector-frontend/src/components/planet-menu/planet-menu.css b/dev/sector-dev/sector-frontend/src/components/planet-menu/planet-menu.css new file mode 100644 index 0000000..487d4d2 --- /dev/null +++ b/dev/sector-dev/sector-frontend/src/components/planet-menu/planet-menu.css @@ -0,0 +1,143 @@ +#planet-limit-container { + position: absolute; + top: 150px; + right: 20px; + width: 215px; + height: 45px; + display: flex; + align-items: center; + justify-content: center; + z-index: 20; + 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); + letter-spacing: 0.5px; +} + +#val_planet_count { + color: var(--color-cyan-bright); +} + +#val_planet_max { + color: var(--color-text-inactive); +} + +#right-sidebar-container { + position: absolute; + top: 220px; + right: 20px; + width: 215px; + + bottom: 60px; + overflow-y: auto; + 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; +} + +.planet-item { + position: relative; + width: 200px; + height: 90px; + flex-shrink: 0; + display: flex; + flex-direction: row; + align-items: stretch; + justify-content: flex-start; + box-sizing: border-box; + background: transparent; +} + +.planet-click-area { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 6px; + box-sizing: border-box; + cursor: pointer; +} + +.moon-click-area { + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + cursor: default; +} + +.moon-click-area.has-moon { + cursor: pointer; + border-left: 1px solid rgba(0, 240, 255, 0.1); +} + +.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; +} + +.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; +} + +#right-sidebar-container::-webkit-scrollbar { + width: 6px; +} + +#right-sidebar-container::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.2); + border-radius: 4px; +} + +#right-sidebar-container::-webkit-scrollbar-thumb { + background: var(--color-cyan-dark); + border-radius: 4px; +} + +#right-sidebar-container::-webkit-scrollbar-thumb:hover { + background: var(--color-cyan-bright); +} + diff --git a/dev/sector-dev/sector-frontend/src/components/planet-menu/planet-menu.js b/dev/sector-dev/sector-frontend/src/components/planet-menu/planet-menu.js new file mode 100644 index 0000000..c891443 --- /dev/null +++ b/dev/sector-dev/sector-frontend/src/components/planet-menu/planet-menu.js @@ -0,0 +1,190 @@ +// ============================================================================ +// PLANETEN-KONFIGURATION (Dummy-Daten) +// ============================================================================ + +/** + * Das aktualisierte datengetriebene Array für unsere Planetenleiste. + * + * Eigenschaften: + * - id: Eindeutige HTML-ID für die PixiJS-Synchronisation + * - name: Der Anzeigename der Welt (Alpha, Beta, etc.) + * - coords: Die Koordinaten + * - isActive: Boolean; markiert den aktuell ausgewählten Planeten + * - hasMoon: Boolean; steuert, ob das kleine Mond-Icon gerendert werden soll + * - planetColor: Hex-Wert; damit färben wir später das Planet-Icon (tint) dynamisch ein + */ +export const planetConfig = [ + { + id: 'planet_btn_alpha', + name: 'Alpha', + coords: '1:15:6', + isActive: true, + hasMoon: true, + planetColor: 0x40a0b0 // Dezentere Cyan-Variante + }, + { + id: 'planet_btn_beta', + name: 'Beta', + coords: '1:15:8', + isActive: false, + hasMoon: false, + planetColor: 0xc08040 // Dezentere Orange/Amber-Variante + }, + { + id: 'planet_btn_gamma', + name: 'Gamma', + coords: '1:16:7', + isActive: false, + hasMoon: true, + planetColor: 0x40a050 // Dezentere Salbei-Grün-Variante + }, + { + id: 'planet_btn_delta', + name: 'Delta', + coords: '1:16:8', + isActive: false, + hasMoon: false, + planetColor: 0xa050a0 // Dezentere Violett-Variante + }, + { + id: 'planet_btn_epsilon', + name: 'Epsilon', + coords: '1:16:9', + isActive: false, + hasMoon: false, + planetColor: 0xc08040 // Dezentere Orange/Amber-Variante + }, + { + id: 'planet_btn_zeta', + name: 'Zeta', + coords: '1:17:10', + isActive: false, + hasMoon: true, + planetColor: 0x40a050 // Dezentere Salbei-Grün-Variante + }, + { + id: 'planet_btn_eta', + name: 'Eta', + coords: '1:17:12', + isActive: false, + hasMoon: false, + planetColor: 0xa050a0 // Dezentere Violett-Variante + }, + { + id: 'planet_btn_theta', + name: 'Theta', + coords: '1:20:4', + isActive: false, + hasMoon: false, + planetColor: 0xc08040 // Dezentere Orange/Amber-Variante + }, + { + id: 'planet_btn_iota', + name: 'Iota', + coords: '1:20:10', + isActive: false, + hasMoon: true, + planetColor: 0x40a050 // Dezentere Salbei-Grün-Variante + }, + { + id: 'planet_btn_kappa', + name: 'Kappa', + coords: '1:22:8', + isActive: false, + hasMoon: false, + planetColor: 0xa050a0 // Dezentere Violett-Variante + } +]; + + +// ============================================================================ +// RECHTES MENÜ (PLANETEN-LISTE) RENDER-LOGIK +// ============================================================================ + +/** + * Baut die HTML-Struktur auf. Neues Layout: Strikter 70/30 Split für ALLE Einträge. + * Linke Seite (70%): Planeten-Icon, Name, Koordinaten. + * Rechte Seite (30%): Mond-Icon (falls vorhanden) oder leerer Platzhalter. + * Dadurch bleiben alle Planeten vertikal perfekt in einer Linie. + */ +export function renderPlanetMenu(pixiPlanetItems) { + const container = document.getElementById('right-sidebar-container'); + if (!container) return; + + container.innerHTML = ''; + + planetConfig.forEach(planet => { + const planetItem = document.createElement('div'); + planetItem.className = 'planet-item'; + planetItem.id = planet.id; + + // --- LINKE SEITE (70%) --- + const planetArea = document.createElement('div'); + planetArea.className = 'planet-click-area'; + planetArea.id = `planet_area_${planet.id}`; + planetArea.style.width = '70%'; + + const planetAnchor = document.createElement('div'); + planetAnchor.className = 'planet-icon-anchor'; + planetAnchor.id = `planet_anchor_${planet.id}`; + + const textWrapper = document.createElement('div'); + textWrapper.className = 'planet-text-wrapper'; + + const nameSpan = document.createElement('span'); + nameSpan.className = 'planet-name'; + nameSpan.textContent = planet.name; + + const coordsSpan = document.createElement('span'); + coordsSpan.className = 'planet-coords'; + coordsSpan.textContent = `[${planet.coords}]`; + + textWrapper.appendChild(nameSpan); + textWrapper.appendChild(coordsSpan); + planetArea.appendChild(planetAnchor); + planetArea.appendChild(textWrapper); + + planetArea.addEventListener('click', () => { + console.log(`[Planeten-Bereich] Klick auf Planet: ${planet.name}`); + }); + + planetItem.appendChild(planetArea); + + // --- RECHTE SEITE (30%) --- + const moonArea = document.createElement('div'); + moonArea.className = 'moon-click-area'; + moonArea.id = `moon_area_${planet.id}`; + moonArea.style.width = '30%'; + + if (planet.hasMoon) { + moonArea.classList.add('has-moon'); + const moonAnchor = document.createElement('div'); + moonAnchor.className = 'moon-icon-anchor'; + moonAnchor.id = `moon_anchor_${planet.id}`; + moonArea.appendChild(moonAnchor); + + moonArea.addEventListener('click', (e) => { + e.stopPropagation(); + console.log(`[Mond-Bereich] Klick auf Mond von: ${planet.name}`); + }); + } + + planetItem.appendChild(moonArea); + + // --- HOVER-LOGIK (PixiJS-Rahmen) --- + planetItem.addEventListener('mouseenter', () => { + if (pixiPlanetItems && pixiPlanetItems[planet.id]) { + pixiPlanetItems[planet.id].alpha = 0.6; + } + }); + + planetItem.addEventListener('mouseleave', () => { + if (pixiPlanetItems && pixiPlanetItems[planet.id]) { + // Aktive Planeten behalten eine Grund-Deckkraft + pixiPlanetItems[planet.id].alpha = planet.isActive ? 0.45 : 0; + } + }); + + container.appendChild(planetItem); + }); +} \ No newline at end of file diff --git a/dev/sector-dev/sector-frontend/src/components/resource-header/resource-header.css b/dev/sector-dev/sector-frontend/src/components/resource-header/resource-header.css new file mode 100644 index 0000000..57cfa62 --- /dev/null +++ b/dev/sector-dev/sector-frontend/src/components/resource-header/resource-header.css @@ -0,0 +1,96 @@ +#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; +} + +#resource-bar { + flex: 1 1 auto; + display: flex; + justify-content: flex-start; + align-items: center; + gap: 15px; +} + +#logo-anchor { + width: 250px; + height: 60px; + flex-shrink: 0; + margin-right: 60px; /* Platz wischen Logo und der Ressourcenbar*/ +} + +.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; +} + +.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; +} diff --git a/dev/sector-dev/sector-frontend/src/components/resource-header/resource-header.js b/dev/sector-dev/sector-frontend/src/components/resource-header/resource-header.js new file mode 100644 index 0000000..3424b02 --- /dev/null +++ b/dev/sector-dev/sector-frontend/src/components/resource-header/resource-header.js @@ -0,0 +1,133 @@ +/** + * Formatiert Ressourcen-Werte nach unseren Vorgaben: + * - Unter 10.000.000: Normale Darstellung mit Tausendertrennzeichen + * - Ab 10.000.000: Abgekürzte Darstellung (z.B. 10,5 Mio) + * - Ab 1.000.000.000: Abgekürzte Darstellung (z.B. 1,2 Mrd) + * * @param {number} value - Der rohe Zahlenwert aus der Datenbank/Engine + * @returns {string} - Der fertig formatierte String für das HTML-HUD + */ +export function formatResourceNumber(value) { + if (value === undefined || value === null || isNaN(value)) { + return '0'; + } + + if (value < 10000000) { + return value.toLocaleString('de-DE'); + } else if (value >= 1000000000) { + const formatted = (value / 1000000000).toFixed(1); + return `${formatted.replace('.', ',')} Mrd`; + } else { + const formatted = (value / 1000000).toFixed(1); + return `${formatted.replace('.', ',')} Mio`; + } +} + + +/** + * Formatiert Zahlen exakt (ohne Abkürzungen wie "Mio"), + * aber mit deutschen Tausendertrennzeichen für die Tooltips. + */ +export function formatExactNumber(value) { + if (value === undefined || value === null || isNaN(value)) return '0'; + return value.toLocaleString('de-DE'); +} + +// ============================================================================ +// UPDATE-LOGIK +// ============================================================================ + +/** + * Sucht die passenden HTML-Elemente anhand ihrer IDs und schreibt + * die formatierten Werte aus einem Daten-Objekt hinein. + * * @param {Object} data - Das Objekt mit den aktuellen Sektor-Daten + */ +export function updateResourceUI(data) { + // Aktualisiert Wert, Tooltip und prüft auf Lager-Überlauf + const updateMainRes = (idKey, name, val, cap) => { + const valEl = document.getElementById(`val_${idKey}`); + const wrapperEl = document.getElementById(`wrapper_${idKey}`); + + // Gekürzte Zahl im UI anzeigen (z.B. 14,5 Mio) + if (valEl) valEl.textContent = formatResourceNumber(val); + + // Tooltip mit exakten Zahlen generieren + // Fallback auf 0, falls noch keine Kapazität vom Backend geliefert wurde + const safeCap = cap || 0; + const tooltipText = `${name}\nMenge: ${formatExactNumber(val)}\nLagerkapazität: ${formatExactNumber(safeCap)}`; + if (wrapperEl) wrapperEl.setAttribute('title', tooltipText); + + // Farbe prüfen: Rot, wenn Menge die Kapazität übersteigt + if (valEl) { + if (val >= safeCap) { + valEl.style.color = 'var(--color-error)'; + } else { + valEl.style.color = 'var(--color-text-main)'; + } + } + }; + + // Aufruf der Hilfsfunktion für die 4 Hauptressourcen + updateMainRes('res_primary', 'Titanium', data.res_primary, data.cap_primary); + updateMainRes('res_secondary', 'Silizium', data.res_secondary, data.cap_secondary); + updateMainRes('res_fuel_base', 'Deuterium', data.res_fuel_base, data.cap_fuel_base); + updateMainRes('res_fuel_adv', 'Tritium', data.res_fuel_adv, data.cap_fuel_adv); + + // --- Energie --- + const energyEl = document.getElementById('val_energy'); + if (energyEl) { + energyEl.textContent = formatResourceNumber(data.energy); + // Rot färben, wenn wir im Minus sind + if (data.energy < 0) { + energyEl.style.color = 'var(--color-error)'; + } else { + energyEl.style.color = 'var(--color-text-main)'; + } + } + + // --- Bevölkerung & Zufriedenheit (Aggregiert) --- + // Gesamtsumme berechnen + const popTotal = (data.pop_idle || 0) + (data.pop_work || 0) + (data.pop_injured || 0); + document.getElementById('val_pop_total').textContent = formatResourceNumber(popTotal); + + // Tooltip-String aufbauen + const popTooltip = `Bevölkerung Details:\nIdle: ${formatResourceNumber(data.pop_idle)}\nArbeitend: ${formatResourceNumber(data.pop_work)}\nVerletzt: ${formatResourceNumber(data.pop_injured)}`; + document.getElementById('wrapper_pop').setAttribute('title', popTooltip); + + // Zufriedenheit verarbeiten + const happiness = data.pop_happiness !== undefined ? data.pop_happiness : 100; + document.getElementById('val_pop_happiness').textContent = `${happiness}%`; + + // Dynamische Emote-Logik basierend auf deinen Vorgaben + let happyIcon = '😊'; // Standard-Wert + if (happiness <= 20) { + happyIcon = '😠'; // Sauer + } else if (happiness <= 40) { + happyIcon = '😢'; // Weinend + } else if (happiness <= 60) { + happyIcon = '😐'; // Neutral + } else if (happiness <= 80) { + happyIcon = '🙂'; // Leicht lächelnd + } else if (happiness <= 100) { + happyIcon = '😊'; // Glücklich + } else { + happyIcon = '😍'; // Herzen in den Augen (> 100) + } + document.getElementById('icon_happiness').textContent = happyIcon; + + // --- Nahrung (Aggregiert) --- + // Gesamtsumme berechnen + const foodTotal = (data.res_food_base || 0) + (data.res_food_adv || 0); + document.getElementById('val_res_food_total').textContent = formatResourceNumber(foodTotal); + + // Tooltip-String aufbauen + const foodTooltip = `Nahrung Details:\nGrundnahrung: ${formatResourceNumber(data.res_food_base)}\nVerarbeitet: ${formatResourceNumber(data.res_food_adv)}`; + document.getElementById('wrapper_food').setAttribute('title', foodTooltip); + + // --- Premium-Währung (Void Kristalle) --- + // Wir zeigen die Gesamtsumme im UI an + document.getElementById('val_res_premium').textContent = formatResourceNumber(data.res_premium); + + // Tooltip-String für die Aufschlüsselung (Global vs Lokal) aufbauen + const premiumTooltip = `Void Kristalle:\nGlobal: ${formatResourceNumber(data.res_premium_global)}\nLokal: ${formatResourceNumber(data.res_premium_local)}`; + document.getElementById('wrapper_premium').setAttribute('title', premiumTooltip); +} diff --git a/dev/sector-dev/sector-frontend/src/components/side-menu/side-menu.css b/dev/sector-dev/sector-frontend/src/components/side-menu/side-menu.css new file mode 100644 index 0000000..e2975fe --- /dev/null +++ b/dev/sector-dev/sector-frontend/src/components/side-menu/side-menu.css @@ -0,0 +1,61 @@ +#side-menu-container { + position: absolute; + top: 160px; + left: 20px; + width: 210px; + bottom: 60px; + overflow-y: auto; + 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; +} + +.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; +} + +.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; +} + +.menu-item:hover .menu-text { + color: var(--color-text-main); + text-shadow: none; +} + +/* Scrollbar-Styling exklusiv für das Seitenmenü */ +#side-menu-container::-webkit-scrollbar { + width: 6px; +} + +#side-menu-container::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.2); + border-radius: 4px; +} + +#side-menu-container::-webkit-scrollbar-thumb { + background: var(--color-cyan-dark); + border-radius: 4px; +} + +#side-menu-container::-webkit-scrollbar-thumb:hover { + background: var(--color-cyan-bright); +} \ No newline at end of file diff --git a/dev/sector-dev/sector-frontend/src/components/side-menu/side-menu.js b/dev/sector-dev/sector-frontend/src/components/side-menu/side-menu.js new file mode 100644 index 0000000..83f35b0 --- /dev/null +++ b/dev/sector-dev/sector-frontend/src/components/side-menu/side-menu.js @@ -0,0 +1,154 @@ +/** + * Das datengetriebene Array für unsere linke Navigationsleiste. + * Wir haben die Liste optimiert: Kernfunktionen nutzen Tabs, + * während oft genutzte Features (Galaxie, Netzwerk) als Schnellzugriff bleiben. + * * Eigenschaften: + * - id: Eindeutige HTML-ID für die PixiJS-Synchronisation + * - label: Der angezeigte Text im UI + * - unlocked: Steuert die Sichtbarkeit im Menü + * - actionId: Der Identifier für den Klick-Event-Listener + */ +export const menuConfig = [ + { + id: 'menu_btn_dashboard', + label: 'Dashboard', + unlocked: true, + actionId: 'view_dashboard' + }, + { + id: 'menu_btn_communication', + label: 'Kommunikation', + // Beinhaltet später Tabs: Nachrichten, Chat + unlocked: true, + actionId: 'view_communication' + }, + { + id: 'menu_btn_infrastructure', + label: 'Infrastruktur', + // Beinhaltet später Tabs: Verwaltung, Produktion, Zivile Gebäude, Anlagen + unlocked: true, + actionId: 'view_infrastructure' + }, + { + id: 'menu_btn_military', + label: 'Militär', + // Beinhaltet NUR Bau-Aspekte: Schiffswerft (Flotte bauen), Verteidigung (Anlagen bauen) + unlocked: true, + actionId: 'view_military' + }, + { + id: 'menu_btn_research', + label: 'Forschung', + // Beinhaltet später Tabs: Lokale Forschung, Globale Forschung + unlocked: true, + actionId: 'view_research' + }, + { + id: 'menu_btn_dispatch', + label: 'Flotte versenden', + // Beinhaltet den aktiven Flottenversand: Transporte, Angriffe, Stationierungen + // und Einsätze wie die "Lokale Erkundung" + unlocked: true, + actionId: 'view_dispatch' + }, + { + id: 'menu_btn_galaxy', + label: 'Galaxie', + // Direkter Absprung auf die Sternenkarte + unlocked: true, + actionId: 'view_galaxy' + }, + { + id: 'menu_btn_alliance', + label: 'Allianz', + // Direkter Zugriff auf die Allianz-Verwaltung + unlocked: true, + actionId: 'view_alliance' + }, + { + id: 'menu_btn_friends', + label: 'Freunde', + unlocked: true, + actionId: 'view_friends' + }, + { + id: 'menu_btn_highscore', + label: 'Highscore', + unlocked: true, + actionId: 'view_highscore' + }, + { + id: 'menu_btn_search', + label: 'Suche', + unlocked: true, + actionId: 'view_search' + }, + { + id: 'menu_btn_void_gate', + label: 'Void Gate', + // Zugang zu PvE und Händler-Tauschbörse. + // Kann z.B. false sein, bis eine bestimmte Forschung abgeschlossen ist. + unlocked: true, + actionId: 'view_void_gate' + }, + { + id: 'menu_btn_shop', + label: 'Shop', + unlocked: true, + actionId: 'view_shop' + } +]; + +/** + * Liest das menuConfig-Array aus und baut die HTML-Struktur für die linke Navigation. + * Nur Einträge, die 'unlocked: true' haben, werden gerendert. + * * Jeder Menüpunkt bekommt: + * 1. Die exakte ID aus der Config (damit PixiJS später weiß, wohin die Grafik muss). + * 2. Ein data-Attribut für die auszuführende Aktion beim Klick. + * 3. Ein Span-Element für den formatierten Text. + */ +export function renderSideMenu(pixiMenuItems) { + const container = document.getElementById('side-menu-container'); + + if (!container) return; + container.innerHTML = ''; + + menuConfig.forEach(item => { + if (item.unlocked) { + const menuItem = document.createElement('div'); + menuItem.className = 'menu-item'; + menuItem.id = item.id; + menuItem.dataset.action = item.actionId; + + const textSpan = document.createElement('span'); + textSpan.className = 'menu-text'; + textSpan.textContent = item.label; + + menuItem.appendChild(textSpan); + + // Klick-Logik + menuItem.addEventListener('click', () => { + console.log(`[Menü] Klick auf: ${item.label} (Action: ${item.actionId})`); + }); + + // Hover-In Logik (Hintergrund leuchtet auf) + menuItem.addEventListener('mouseenter', () => { + // Sicherheitsprüfung: Wurden die Pixi-Sprites bereits generiert? + if (pixiMenuItems && pixiMenuItems[item.id] && pixiMenuItems[item.id].bgSprite) { + pixiMenuItems[item.id].bgSprite.tint = 0x00f0ff; + pixiMenuItems[item.id].bgSprite.alpha = 0.9; + } + }); + + // Hover-Out Logik (Hintergrund zurück auf Standard) + menuItem.addEventListener('mouseleave', () => { + if (pixiMenuItems && pixiMenuItems[item.id] && pixiMenuItems[item.id].bgSprite) { + pixiMenuItems[item.id].bgSprite.tint = 0x00a0aa; + pixiMenuItems[item.id].bgSprite.alpha = 0.6; + } + }); + + container.appendChild(menuItem); + } + }); +} \ No newline at end of file diff --git a/dev/sector-dev/sector-frontend/src/components/topbar/topbar.css b/dev/sector-dev/sector-frontend/src/components/topbar/topbar.css new file mode 100644 index 0000000..368dbb6 --- /dev/null +++ b/dev/sector-dev/sector-frontend/src/components/topbar/topbar.css @@ -0,0 +1,40 @@ +/* ========================================================================== + 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; +} diff --git a/dev/sector-dev/sector-frontend/src/components/topbar/topbar.js b/dev/sector-dev/sector-frontend/src/components/topbar/topbar.js new file mode 100644 index 0000000..2a87ec7 --- /dev/null +++ b/dev/sector-dev/sector-frontend/src/components/topbar/topbar.js @@ -0,0 +1,23 @@ +/** + * Initialisiert die Top-Bar-Komponente. + * Hier werden wir später Logik hinzufügen, um z.B. den + * Account-Namen oder den Sektor-Namen aus den echten Backend-Daten + * zu aktualisieren. + */ +export function initTopbar() { + const topbarElement = document.getElementById('sector-top-bar'); + + if (!topbarElement) { + console.warn('[Topbar] Konnte #sector-top-bar nicht im DOM finden.'); + return; + } + + const backBtn = document.getElementById('btn-back-to-lobby'); + if (backBtn) { + // Hier könnten wir später vorm Verlassen des Sektors + // noch Daten speichern oder abmelden (Logout-Logik). + //console.log('[Topbar] Lobby-Button ist einsatzbereit.'); + } + + //console.log('[Topbar] Erfolgreich initialisiert.'); +} \ No newline at end of file diff --git a/dev/sector-dev/sector-frontend/src/css/hud.css b/dev/sector-dev/sector-frontend/src/css/hud.css deleted file mode 100644 index d940f7d..0000000 --- a/dev/sector-dev/sector-frontend/src/css/hud.css +++ /dev/null @@ -1,393 +0,0 @@ -/* ========================================================================== - 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); -} - diff --git a/dev/sector-dev/sector-frontend/src/css/layout.css b/dev/sector-dev/sector-frontend/src/css/layout.css index a13169f..34adb17 100644 --- a/dev/sector-dev/sector-frontend/src/css/layout.css +++ b/dev/sector-dev/sector-frontend/src/css/layout.css @@ -9,24 +9,27 @@ body, html { padding: 0; width: 100%; height: 100%; - overflow: hidden; + overflow-x: auto; + overflow-y: 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; +position: absolute; + top: 0; + left: 0; width: 100%; - height: 100%; + min-width: 1200px; + height: 100vh; } /* Der Container für den PixiJS-Hintergrund und die Spielwelt */ #pixi-container { - position: absolute; + position: fixed; top: 0; left: 0; width: 100%; @@ -38,13 +41,25 @@ body, html { #ui-layer { position: absolute; top: 0; + bottom: 0; left: 0; - width: 100%; - height: 100%; + right: 0; z-index: 10; pointer-events: none; } +#ui-safe-zone { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + min-width: 1200px; + max-width: 1600px; + margin: 0 auto; + 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. */ diff --git a/dev/sector-dev/sector-frontend/src/game/simulation.js b/dev/sector-dev/sector-frontend/src/game/simulation.js new file mode 100644 index 0000000..1df0aa0 --- /dev/null +++ b/dev/sector-dev/sector-frontend/src/game/simulation.js @@ -0,0 +1,72 @@ +import { updateResourceUI } from '../components/resource-header/resource-header.js'; + +// Wir lagern die Daten in ein reaktives Zustandsobjekt aus. +// Später wird dieses Objekt durch die echte Backend-API befüllt +// (z.B. über WebSockets oder Polling, ähnlich wie in der Lobby). +const sectorState = { + res_primary: 1000, cap_primary: 10000, + res_secondary: 500, cap_secondary: 10000, + res_fuel_base: 0,cap_fuel_base: 10000, + res_fuel_adv: 0, cap_fuel_adv: 10000, + + energy: 0, + + pop_idle: 500, + pop_work: 0, + pop_injured: 500, + pop_happiness: 50, + res_food_base: 10000, + res_food_adv: 0, + res_premium: 150, + res_premium_global: 100, + res_premium_local: 50 +}; + +/** + * Hilfsfunktion für gedeckelte Ressourcen-Produktion. + * Erhöht den aktuellen Wert um 'amount', stoppt aber exakt am 'cap'. + * Ist der Wert bereits über dem Cap (z.B. durch Startwerte oder Boni), + * wird nichts abgezogen, aber die Produktion pausiert. + * + * @param {number} currentValue - Der derzeitige Bestand + * @param {number} productionAmount - Menge, die hinzugefügt wird + * @param {number} capacity - Das maximale Lagerlimit + * + * @returns {number} - Der neue berechnete Bestand + */ +function addResourceWithCap(currentValue, productionAmount, capacity) { + if (currentValue >= capacity) { + return currentValue; + } + + const newValue = currentValue + productionAmount; + if (newValue > capacity) { + return capacity; + } + + return newValue; +} + +/** + * Startet die Simulation und den Interval-Timer für die Ressourcen-Produktion. + * Initiiert zudem das erste sofortige UI-Update. + */ +export function startSimulation() { + // 1. Initiales Update beim Start, um die Nullen im HUD zu überschreiben + updateResourceUI(sectorState); + + // 2. Der simulierte Game-Loop (1 Tick pro Sekunde) + setInterval(() => { + // Ressourcen berechnen + sectorState.res_primary = addResourceWithCap(sectorState.res_primary, 15, sectorState.cap_primary); + sectorState.res_secondary = addResourceWithCap(sectorState.res_secondary, 5, sectorState.cap_secondary); + + // Nahrung wird hier beispielhaft verbraucht + sectorState.res_food_base -= 10; + + // 3. UI mit den neuen Werten aktualisieren + updateResourceUI(sectorState); + }, 1000); + + console.log('[Simulation] Game-Loop gestartet.'); +} \ No newline at end of file diff --git a/dev/sector-dev/sector-frontend/src/main.js b/dev/sector-dev/sector-frontend/src/main.js index 2239010..2c48cf3 100644 --- a/dev/sector-dev/sector-frontend/src/main.js +++ b/dev/sector-dev/sector-frontend/src/main.js @@ -1,602 +1,13 @@ import { Application, Assets, Sprite, Container, Text, Graphics, NineSliceSprite } from 'pixi.js'; -let iconSheet = null; -let frameSheet = null; -let background = null; -let logoContainer = null; -let pixiIcons = {}; -let pixiMenuItems = {}; -let pixiMenuContainer = null; -let pixiMenuMask = null; -let pixiPlanetItems = {}; -let pixiPlanetIcons = {}; -let pixiMoonIcons = {}; -let pixiPlanetContainer = null; -let pixiPlanetMask = null; -let pixiPlanetLimitContainer = null; +import { initFooter } from './components/footer/footer.js'; +import { initTopbar } from './components/topbar/topbar.js'; +import { menuConfig, renderSideMenu } from './components/side-menu/side-menu.js'; +import { planetConfig, renderPlanetMenu } from './components/planet-menu/planet-menu.js'; +import { setupResize } from './utils/resize.js'; +import { startDomSync } from './utils/domSync.js'; +import { startSimulation } from './game/simulation.js'; +import { buildBackground, buildLogo, buildResourceIcons, buildSideMenuGraphics, buildPlanetMenuGraphics } from './pixi/uiBuilder.js'; -// ============================================================================ -// FORMATIERUNGS-LOGIK -// ============================================================================ - -/** - * Formatiert Ressourcen-Werte nach unseren Vorgaben: - * - Unter 10.000.000: Normale Darstellung mit Tausendertrennzeichen - * - Ab 10.000.000: Abgekürzte Darstellung (z.B. 10,5 Mio) - * - Ab 1.000.000.000: Abgekürzte Darstellung (z.B. 1,2 Mrd) - * * @param {number} value - Der rohe Zahlenwert aus der Datenbank/Engine - * @returns {string} - Der fertig formatierte String für das HTML-HUD - */ -export function formatResourceNumber(value) { - if (value === undefined || value === null || isNaN(value)) { - return '0'; - } - - if (value < 10000000) { - return value.toLocaleString('de-DE'); - } else if (value >= 1000000000) { - const formatted = (value / 1000000000).toFixed(1); - return `${formatted.replace('.', ',')} Mrd`; - } else { - const formatted = (value / 1000000).toFixed(1); - return `${formatted.replace('.', ',')} Mio`; - } -} - - -/** - * Formatiert Zahlen exakt (ohne Abkürzungen wie "Mio"), - * aber mit deutschen Tausendertrennzeichen für die Tooltips. - */ -export function formatExactNumber(value) { - if (value === undefined || value === null || isNaN(value)) return '0'; - return value.toLocaleString('de-DE'); -} - -// ============================================================================ -// MENÜ-KONFIGURATION -// ============================================================================ - -/** - * Das datengetriebene Array für unsere linke Navigationsleiste. - * Wir haben die Liste optimiert: Kernfunktionen nutzen Tabs, - * während oft genutzte Features (Galaxie, Netzwerk) als Schnellzugriff bleiben. - * * Eigenschaften: - * - id: Eindeutige HTML-ID für die PixiJS-Synchronisation - * - label: Der angezeigte Text im UI - * - unlocked: Steuert die Sichtbarkeit im Menü - * - actionId: Der Identifier für den Klick-Event-Listener - */ -const menuConfig = [ - { - id: 'menu_btn_dashboard', - label: 'Dashboard', - unlocked: true, - actionId: 'view_dashboard' - }, - { - id: 'menu_btn_communication', - label: 'Kommunikation', - // Beinhaltet später Tabs: Nachrichten, Chat - unlocked: true, - actionId: 'view_communication' - }, - { - id: 'menu_btn_infrastructure', - label: 'Infrastruktur', - // Beinhaltet später Tabs: Verwaltung, Produktion, Zivile Gebäude, Anlagen - unlocked: true, - actionId: 'view_infrastructure' - }, - { - id: 'menu_btn_military', - label: 'Militär', - // Beinhaltet NUR Bau-Aspekte: Schiffswerft (Flotte bauen), Verteidigung (Anlagen bauen) - unlocked: true, - actionId: 'view_military' - }, - { - id: 'menu_btn_research', - label: 'Forschung', - // Beinhaltet später Tabs: Lokale Forschung, Globale Forschung - unlocked: true, - actionId: 'view_research' - }, - { - id: 'menu_btn_dispatch', - label: 'Flotte versenden', - // Beinhaltet den aktiven Flottenversand: Transporte, Angriffe, Stationierungen - // und Einsätze wie die "Lokale Erkundung" - unlocked: true, - actionId: 'view_dispatch' - }, - { - id: 'menu_btn_galaxy', - label: 'Galaxie', - // Direkter Absprung auf die Sternenkarte - unlocked: true, - actionId: 'view_galaxy' - }, - { - id: 'menu_btn_alliance', - label: 'Allianz', - // Direkter Zugriff auf die Allianz-Verwaltung - unlocked: true, - actionId: 'view_alliance' - }, - { - id: 'menu_btn_friends', - label: 'Freunde', - unlocked: true, - actionId: 'view_friends' - }, - { - id: 'menu_btn_highscore', - label: 'Highscore', - unlocked: true, - actionId: 'view_highscore' - }, - { - id: 'menu_btn_search', - label: 'Suche', - unlocked: true, - actionId: 'view_search' - }, - { - id: 'menu_btn_void_gate', - label: 'Void Gate', - // Zugang zu PvE und Händler-Tauschbörse. - // Kann z.B. false sein, bis eine bestimmte Forschung abgeschlossen ist. - unlocked: true, - actionId: 'view_void_gate' - }, - { - id: 'menu_btn_shop', - label: 'Shop', - unlocked: true, - actionId: 'view_shop' - } -]; - -// ============================================================================ -// PLANETEN-KONFIGURATION (Dummy-Daten) -// ============================================================================ - -/** - * Das aktualisierte datengetriebene Array für unsere Planetenleiste. - * Neu: Die "Running Gag" Sektornamen sowie Flags für Monde und Icon-Farben. - * - * Eigenschaften: - * - id: Eindeutige HTML-ID für die PixiJS-Synchronisation - * - name: Der Anzeigename der Welt (Alpha, Beta, etc.) - * - coords: Die Koordinaten - * - isActive: Boolean; markiert den aktuell ausgewählten Planeten - * - hasMoon: Boolean; steuert, ob das kleine Mond-Icon gerendert werden soll - * - planetColor: Hex-Wert; damit färben wir später das Planet-Icon (tint) dynamisch ein - */ -const planetConfig = [ - { - id: 'planet_btn_alpha', - name: 'Alpha', - coords: '1:15:6', - isActive: true, - hasMoon: true, - planetColor: 0x40a0b0 // Dezentere Cyan-Variante - }, - { - id: 'planet_btn_beta', - name: 'Beta', - coords: '1:15:8', - isActive: false, - hasMoon: false, - planetColor: 0xc08040 // Dezentere Orange/Amber-Variante - }, - { - id: 'planet_btn_gamma', - name: 'Gamma', - coords: '1:16:7', - isActive: false, - hasMoon: true, - planetColor: 0x40a050 // Dezentere Salbei-Grün-Variante - }, - { - id: 'planet_btn_delta', - name: 'Delta', - coords: '1:16:8', - isActive: false, - hasMoon: false, - planetColor: 0xa050a0 // Dezentere Violett-Variante - }, - { - id: 'planet_btn_epsilon', - name: 'Epsilon', - coords: '1:16:9', - isActive: false, - hasMoon: false, - planetColor: 0xc08040 // Dezentere Orange/Amber-Variante - }, - { - id: 'planet_btn_zeta', - name: 'Zeta', - coords: '1:17:10', - isActive: false, - hasMoon: true, - planetColor: 0x40a050 // Dezentere Salbei-Grün-Variante - }, - { - id: 'planet_btn_eta', - name: 'Eta', - coords: '1:17:12', - isActive: false, - hasMoon: false, - planetColor: 0xa050a0 // Dezentere Violett-Variante - }, - { - id: 'planet_btn_theta', - name: 'Theta', - coords: '1:20:4', - isActive: false, - hasMoon: false, - planetColor: 0xc08040 // Dezentere Orange/Amber-Variante - }, - { - id: 'planet_btn_iota', - name: 'Iota', - coords: '1:20:10', - isActive: false, - hasMoon: true, - planetColor: 0x40a050 // Dezentere Salbei-Grün-Variante - }, - { - id: 'planet_btn_kappa', - name: 'Kappa', - coords: '1:22:8', - isActive: false, - hasMoon: false, - planetColor: 0xa050a0 // Dezentere Violett-Variante - } -]; - -/** - * Liest das menuConfig-Array aus und baut die HTML-Struktur für die linke Navigation. - * Nur Einträge, die 'unlocked: true' haben, werden gerendert. - * * Jeder Menüpunkt bekommt: - * 1. Die exakte ID aus der Config (damit PixiJS später weiß, wohin die Grafik muss). - * 2. Ein data-Attribut für die auszuführende Aktion beim Klick. - * 3. Ein Span-Element für den formatierten Text. - */ -function renderSideMenu() { - const container = document.getElementById('side-menu-container'); - - if (!container) return; - container.innerHTML = ''; - - menuConfig.forEach(item => { - if (item.unlocked) { - - const menuItem = document.createElement('div'); - menuItem.className = 'menu-item'; - menuItem.id = item.id; - menuItem.dataset.action = item.actionId; - - const textSpan = document.createElement('span'); - textSpan.className = 'menu-text'; - textSpan.textContent = item.label; - - menuItem.appendChild(textSpan); - - // Klick-Logik - menuItem.addEventListener('click', () => { - console.log(`[Menü] Klick auf: ${item.label} (Action: ${item.actionId})`); - }); - - // NEU: Hover-In Logik (Hintergrund leuchtet auf) - menuItem.addEventListener('mouseenter', () => { - if (pixiMenuItems[item.id] && pixiMenuItems[item.id].bgSprite) { - // Wir wechseln zu einem helleren Cyan und machen ihn etwas deckender - pixiMenuItems[item.id].bgSprite.tint = 0x00f0ff; - pixiMenuItems[item.id].bgSprite.alpha = 0.9; - } - }); - - // NEU: Hover-Out Logik (Hintergrund zurück auf Standard) - menuItem.addEventListener('mouseleave', () => { - if (pixiMenuItems[item.id] && pixiMenuItems[item.id].bgSprite) { - // Zurück zum dunkleren Cyan und weniger Deckkraft - pixiMenuItems[item.id].bgSprite.tint = 0x00a0aa; - pixiMenuItems[item.id].bgSprite.alpha = 0.6; - } - }); - - container.appendChild(menuItem); - } - }); -} - -// ============================================================================ -// RECHTES MENÜ (PLANETEN-LISTE) RENDER-LOGIK -// ============================================================================ - -/** - * Baut die HTML-Struktur auf. Neues Layout: Strikter 70/30 Split für ALLE Einträge. - * Linke Seite (70%): Planeten-Icon, Name, Koordinaten. - * Rechte Seite (30%): Mond-Icon (falls vorhanden) oder leerer Platzhalter. - * Dadurch bleiben alle Planeten vertikal perfekt in einer Linie. - */ -function renderPlanetMenu() { - const container = document.getElementById('right-sidebar-container'); - if (!container) return; - - container.innerHTML = ''; - - planetConfig.forEach(planet => { - // Haupt-Wrapper (Flex-Row) - const planetItem = document.createElement('div'); - planetItem.className = 'planet-item'; - planetItem.id = planet.id; - - // --- LINKE SEITE: PLANETEN-BEREICH (IMMER 70%) --- - const planetArea = document.createElement('div'); - planetArea.className = 'planet-click-area'; - planetArea.id = `planet_area_${planet.id}`; - - // ZWINGEND: Immer 70% Breite, damit die Zentrierung für alle Welten identisch ist - planetArea.style.width = '70%'; - - // HTML-Anker für PixiJS Planeten-Icon - const planetAnchor = document.createElement('div'); - planetAnchor.className = 'planet-icon-anchor'; - planetAnchor.id = `planet_anchor_${planet.id}`; - - // Textelemente - const textWrapper = document.createElement('div'); - textWrapper.className = 'planet-text-wrapper'; - - const nameSpan = document.createElement('span'); - nameSpan.className = 'planet-name'; - nameSpan.textContent = planet.name; - - const coordsSpan = document.createElement('span'); - coordsSpan.className = 'planet-coords'; - coordsSpan.textContent = `[${planet.coords}]`; - - textWrapper.appendChild(nameSpan); - textWrapper.appendChild(coordsSpan); - - planetArea.appendChild(planetAnchor); - planetArea.appendChild(textWrapper); - - // Klick-Event für den Planeten - planetArea.addEventListener('click', () => { - console.log(`[Planeten-Bereich] Klick auf Planet: ${planet.name}`); - }); - - planetItem.appendChild(planetArea); - - // --- RECHTE SEITE: MOND-BEREICH (IMMER 30%) --- - const moonArea = document.createElement('div'); - moonArea.className = 'moon-click-area'; - moonArea.id = `moon_area_${planet.id}`; - - // ZWINGEND: Immer 30% Breite als fester Platzhalter - moonArea.style.width = '30%'; - - // Nur wenn ein Mond existiert, fügen wir den Inhalt und die Klick-Logik hinzu - if (planet.hasMoon) { - // Eine CSS-Klasse hinzufügen, damit wir z.B. den Rahmen nur hier anzeigen - moonArea.classList.add('has-moon'); - - // HTML-Anker für das PixiJS Mond-Icon - const moonAnchor = document.createElement('div'); - moonAnchor.className = 'moon-icon-anchor'; - moonAnchor.id = `moon_anchor_${planet.id}`; - - moonArea.appendChild(moonAnchor); - - // Klick-Event für den Mond - moonArea.addEventListener('click', (e) => { - e.stopPropagation(); - console.log(`[Mond-Bereich] Klick auf Mond von: ${planet.name}`); - }); - } - - planetItem.appendChild(moonArea); - - // --- GEMEINSAME HOVER-LOGIK (Für den PixiJS-Rahmen) --- - // Wir legen das Hover-Event jetzt auf das gesamte Item (100%), - // damit der PixiJS-Hintergrund für die ganze Zeile aufleuchtet. - planetItem.addEventListener('mouseenter', () => { - if (pixiPlanetItems[planet.id]) { - pixiPlanetItems[planet.id].alpha = 0.6; - } - }); - - planetItem.addEventListener('mouseleave', () => { - if (pixiPlanetItems[planet.id]) { - pixiPlanetItems[planet.id].alpha = planet.isActive ? 0.45 : 0; - } - }); - - container.appendChild(planetItem); - }); -} - -// ============================================================================ -// HUD-UPDATE-LOGIK -// ============================================================================ - -/** - * Sucht die passenden HTML-Elemente anhand ihrer IDs und schreibt - * die formatierten Werte aus einem Daten-Objekt hinein. - * * @param {Object} data - Das Objekt mit den aktuellen Sektor-Daten - */ -export function updateResourceUI(data) { - // Aktualisiert Wert, Tooltip und prüft auf Lager-Überlauf - const updateMainRes = (idKey, name, val, cap) => { - const valEl = document.getElementById(`val_${idKey}`); - const wrapperEl = document.getElementById(`wrapper_${idKey}`); - - // Gekürzte Zahl im UI anzeigen (z.B. 14,5 Mio) - if (valEl) valEl.textContent = formatResourceNumber(val); - - // Tooltip mit exakten Zahlen generieren - // Fallback auf 0, falls noch keine Kapazität vom Backend geliefert wurde - const safeCap = cap || 0; - const tooltipText = `${name}\nMenge: ${formatExactNumber(val)}\nLagerkapazität: ${formatExactNumber(safeCap)}`; - if (wrapperEl) wrapperEl.setAttribute('title', tooltipText); - - // Farbe prüfen: Rot, wenn Menge die Kapazität übersteigt - if (valEl) { - if (val >= safeCap) { - valEl.style.color = 'var(--color-error)'; - } else { - valEl.style.color = 'var(--color-text-main)'; - } - } - }; - - // Aufruf der Hilfsfunktion für die 4 Hauptressourcen - updateMainRes('res_primary', 'Titanium', data.res_primary, data.cap_primary); - updateMainRes('res_secondary', 'Silizium', data.res_secondary, data.cap_secondary); - updateMainRes('res_fuel_base', 'Deuterium', data.res_fuel_base, data.cap_fuel_base); - updateMainRes('res_fuel_adv', 'Tritium', data.res_fuel_adv, data.cap_fuel_adv); - - // --- Energie --- - const energyEl = document.getElementById('val_energy'); - if (energyEl) { - energyEl.textContent = formatResourceNumber(data.energy); - // Rot färben, wenn wir im Minus sind - if (data.energy < 0) { - energyEl.style.color = 'var(--color-error)'; - } else { - energyEl.style.color = 'var(--color-text-main)'; - } - } - - // --- Bevölkerung & Zufriedenheit (Aggregiert) --- - // Gesamtsumme berechnen - const popTotal = (data.pop_idle || 0) + (data.pop_work || 0) + (data.pop_injured || 0); - document.getElementById('val_pop_total').textContent = formatResourceNumber(popTotal); - - // Tooltip-String aufbauen - const popTooltip = `Bevölkerung Details:\nIdle: ${formatResourceNumber(data.pop_idle)}\nArbeitend: ${formatResourceNumber(data.pop_work)}\nVerletzt: ${formatResourceNumber(data.pop_injured)}`; - document.getElementById('wrapper_pop').setAttribute('title', popTooltip); - - // Zufriedenheit verarbeiten - const happiness = data.pop_happiness !== undefined ? data.pop_happiness : 100; - document.getElementById('val_pop_happiness').textContent = `${happiness}%`; - - // Dynamische Emote-Logik basierend auf deinen Vorgaben - let happyIcon = '😊'; // Standard-Wert - if (happiness <= 20) { - happyIcon = '😠'; // Sauer - } else if (happiness <= 40) { - happyIcon = '😢'; // Weinend - } else if (happiness <= 60) { - happyIcon = '😐'; // Neutral - } else if (happiness <= 80) { - happyIcon = '🙂'; // Leicht lächelnd - } else if (happiness <= 100) { - happyIcon = '😊'; // Glücklich - } else { - happyIcon = '😍'; // Herzen in den Augen (> 100) - } - document.getElementById('icon_happiness').textContent = happyIcon; - - // --- Nahrung (Aggregiert) --- - // Gesamtsumme berechnen - const foodTotal = (data.res_food_base || 0) + (data.res_food_adv || 0); - document.getElementById('val_res_food_total').textContent = formatResourceNumber(foodTotal); - - // Tooltip-String aufbauen - const foodTooltip = `Nahrung Details:\nGrundnahrung: ${formatResourceNumber(data.res_food_base)}\nVerarbeitet: ${formatResourceNumber(data.res_food_adv)}`; - document.getElementById('wrapper_food').setAttribute('title', foodTooltip); - - // --- Premium-Währung (Void Kristalle) --- - // Wir zeigen die Gesamtsumme im UI an - document.getElementById('val_res_premium').textContent = formatResourceNumber(data.res_premium); - - // Tooltip-String für die Aufschlüsselung (Global vs Lokal) aufbauen - const premiumTooltip = `Void Kristalle:\nGlobal: ${formatResourceNumber(data.res_premium_global)}\nLokal: ${formatResourceNumber(data.res_premium_local)}`; - document.getElementById('wrapper_premium').setAttribute('title', premiumTooltip); -} - -// ============================================================================ -// PIXI-ENGINE & RESIZE LOGIK -// ============================================================================ - -/** - * Übernimmt die Skalierungslogik für PixiJS. - * Wird jedes Mal aufgerufen, wenn der Spieler das Browserfenster vergrößert/verkleinert. - * @param {Application} app - Die laufende PixiJS Instanz - */ -function setupResize(app) { - function resize() { - const screenWidth = window.innerWidth; - const screenHeight = window.innerHeight; - - // --- Hintergrund responsiv anpassen (Cover-Effekt) --- - if (background && background.texture) { - const scaleX = screenWidth / background.texture.width; - const scaleY = screenHeight / background.texture.height; - - const scale = Math.max(scaleX, scaleY); - background.scale.set(scale); - - background.x = (screenWidth - background.width) / 2; - background.y = (screenHeight - background.height) / 2; - } - - // --- Logo positionieren --- - if (logoContainer) { - logoContainer.position.set(150, 90); - } - - // --- Menü-Maske dynamisch anpassen --- - if (pixiMenuMask) { - pixiMenuMask.clear(); - - // Die Koordinaten entsprechen exakt unserem CSS (#side-menu-container) - const maskX = 20; - const maskY = 160; - const maskWidth = 220; - - // Die Höhe berechnet sich aus: Fensterhöhe MINUS Top-Abstand (160) MINUS Bottom-Abstand (60) - const maskHeight = screenHeight - 160 - 60; - - pixiMenuMask.rect(maskX, maskY, maskWidth, maskHeight); - pixiMenuMask.fill(0xffffff); - } - - // --- Rechte Menü-Maske dynamisch anpassen --- - if (pixiPlanetMask) { - pixiPlanetMask.clear(); - - // Die Parameter orientieren sich an unserem CSS für #right-sidebar-container - const maskWidth = 235; - // X-Koordinate: Fensterbreite minus Containerbreite minus den rechten Abstand (right: 20px) - const maskX = screenWidth - maskWidth - 20; - const maskY = 160; // Entspricht top: 160px - - // Die Höhe berechnet sich aus: Fensterhöhe MINUS Top-Abstand (160) MINUS Bottom-Abstand (60) - const maskHeight = screenHeight - 160 - 60; - - // Maske zeichnen - pixiPlanetMask.rect(maskX, maskY, maskWidth, maskHeight); - pixiPlanetMask.fill(0xffffff); - } - } - - app.renderer.on('resize', resize); - resize(); -} - -// ============================================================================ -// INITIALISIERUNG -// ============================================================================ - -/** - * Startet die PixiJS-Engine, lädt die Assets und baut die Basis-Szene auf. - */ async function initSector() { const app = new Application(); @@ -610,9 +21,21 @@ async function initSector() { document.getElementById('pixi-container').appendChild(app.canvas); + let background = null; + let logoContainer = null; + let pixiIcons = {}; + let pixiMenuMask = null; + let pixiMenuItems = {}; + let pixiPlanetLimitContainer = null; + let pixiPlanetMask = null; + let pixiPlanetItems = {}; + let pixiPlanetIcons = {}; + let pixiMoonIcons = {}; + let iconSheet = null; + let frameSheet = null; + // --- Asset Loading --- try { - console.log("[PixiJS] Lade Assets und Schriftarten..."); await document.fonts.load('16px "Logo Font"'); await document.fonts.load('16px "UI Font"'); @@ -627,440 +50,44 @@ async function initSector() { iconSheet = await Assets.load(assetsToLoad.icons); frameSheet = await Assets.load(assetsToLoad.frames); - // --- Hintergrund rendern --- - background = new Sprite(bgTexture); - app.stage.addChild(background); + background = buildBackground(app, bgTexture); + logoContainer = buildLogo(app, frameSheet); + pixiIcons = buildResourceIcons(app, iconSheet); - // --- Logo rendern --- - logoContainer = new Container(); - - const ringTexture = frameSheet.textures['SPR_SciFiMenus_Ring_Medium_13']; - if (ringTexture) { - const logoRing = new Sprite(ringTexture); - logoRing.anchor.set(0.5); - logoRing.tint = 0x00f0ff; - logoRing.scale.set(0.12); - logoRing.alpha = 0.8; - logoContainer.addChild(logoRing); - } + const sideMenuData = buildSideMenuGraphics(app, frameSheet, menuConfig); + pixiMenuMask = sideMenuData.pixiMenuMask; + pixiMenuItems = sideMenuData.pixiMenuItems; - const logoText = new Text({ - text: 'Void-Genesis', - style: { - fontFamily: 'Logo Font', - fontSize: 33, - fill: 0x00f0ff, - letterSpacing: 6, - } - }); - logoText.anchor.set(0.5); - logoContainer.addChild(logoText); - - app.stage.addChild(logoContainer); + const planetMenuData = buildPlanetMenuGraphics(app, frameSheet, iconSheet, planetConfig); + pixiPlanetLimitContainer = planetMenuData.pixiPlanetLimitContainer; + pixiPlanetMask = planetMenuData.pixiPlanetMask; + pixiPlanetItems = planetMenuData.pixiPlanetItems; + pixiPlanetIcons = planetMenuData.pixiPlanetIcons; + pixiMoonIcons = planetMenuData.pixiMoonIcons; - // --- Ressourcen-Icons generieren --- - // Zuordnung: HTML-ID der Box -> Textur-Name im Sprite-Sheet - const iconMapping = { - 'box_primary': 'ICON_Titanium', - 'box_secondary': 'ICON_Silizium', - 'box_fuel_base': 'ICON_Deuterium', - 'box_fuel_adv': 'ICON_Tritium', - 'box_energy': 'ICON_Map_Lightning', - 'box_pop_total': 'ICON_SciFiMenus_Menu_Multiplayer_01_Clean', - 'box_food_total': 'ICON_SciFi_Status_Hunger', - 'box_premium': 'ICON_SM_Item_Crystal_03' - }; - - if (iconSheet) { - for (const boxId in iconMapping) { - const texName = iconMapping[boxId]; - if (iconSheet.textures[texName]) { - - const boxContainer = new Container(); - const bgGraphic = new Graphics(); - bgGraphic.roundRect(-22.5, -22.5, 45, 45, 6); - bgGraphic.fill({ color: 0x000000, alpha: 0.6 }); - boxContainer.addChild(bgGraphic); - const sprite = new Sprite(iconSheet.textures[texName]); - sprite.anchor.set(0.5); - - if (boxId == 'box_primary' || boxId == 'box_secondary' || boxId == 'box_fuel_base' || boxId == 'box_fuel_adv') { - sprite.scale.set(0.50); - const mask = new Graphics(); - mask.roundRect(-22.5, -22.5, 45, 45, 6); - mask.fill(0xffffff); - sprite.mask = mask; - boxContainer.addChild(mask); - } - else if (boxId == 'box_pop_total' || boxId == 'box_food_total') { - sprite.scale.set(0.15); - } - else if (boxId === 'box_energy') { - sprite.scale.set(0.15); - sprite.tint = 0xffff00; - } - else if (boxId === 'box_premium') { - sprite.scale.set(0.15); - sprite.tint = 0xff00ff; - } - else { - sprite.scale.set(0.12); // Fallback - } - - boxContainer.addChild(sprite); - - app.stage.addChild(boxContainer); - - pixiIcons[boxId] = boxContainer; - } else { - console.warn(`[PixiJS] Icon-Textur nicht gefunden: ${texName}`); - } - } - } - - // --- Absolute HTML/PixiJS Synchronisation (Ticker) --- - // Der Ticker läuft mit bis zu 60 FPS. Er garantiert, dass die PixiJS-Icons - // immer exakt den HTML-Rahmen folgen, egal ob Schriften nachladen - // oder Flexbox-Abstände sich dynamisch ändern. - app.ticker.add(() => { - for (const boxId in pixiIcons) { - const container = pixiIcons[boxId]; - const htmlBox = document.getElementById(boxId); - - if (htmlBox && container) { - const rect = htmlBox.getBoundingClientRect(); - container.position.set( - rect.left + (rect.width / 2), - rect.top + (rect.height / 2) - ); - } - } - - for (const menuId in pixiMenuItems) { - const container = pixiMenuItems[menuId]; - const htmlBox = document.getElementById(menuId); - - if (htmlBox && container) { - const rect = htmlBox.getBoundingClientRect(); - container.position.set( - rect.left + (rect.width / 2), - rect.top + (rect.height / 2) - ); - } - } - - // Synchronisation der Planeten-Box-Hintergründe - for (const planetId in pixiPlanetItems) { - const container = pixiPlanetItems[planetId]; - const htmlBox = document.getElementById(planetId); - - if (htmlBox && container) { - const rect = htmlBox.getBoundingClientRect(); - container.position.set( - rect.left + (rect.width / 2), - rect.top + (rect.height / 2) - ); - } - } - - // Synchronisation der Planeten-Icons auf ihren spezifischen Anker - for (const planetId in pixiPlanetIcons) { - const container = pixiPlanetIcons[planetId]; - const htmlAnchor = document.getElementById(`planet_anchor_${planetId}`); - - if (htmlAnchor && container) { - const rect = htmlAnchor.getBoundingClientRect(); - container.position.set( - rect.left + (rect.width / 2), - rect.top + (rect.height / 2) - ); - } - } - - // Synchronisation der Monde auf den separat verschobenen Mond-Anker - for (const planetId in pixiMoonIcons) { - const container = pixiMoonIcons[planetId]; - const htmlAnchor = document.getElementById(`moon_anchor_${planetId}`); - - if (htmlAnchor && container) { - const rect = htmlAnchor.getBoundingClientRect(); - container.position.set( - rect.left + (rect.width / 2), - rect.top + (rect.height / 2) - ); - } - } - - // Planeten-Limit Container an HTML heften - if (pixiPlanetLimitContainer) { - const limitHtmlBox = document.getElementById('planet-limit-container'); - if (limitHtmlBox) { - const rect = limitHtmlBox.getBoundingClientRect(); - pixiPlanetLimitContainer.position.set( - rect.left + (rect.width / 2), - rect.top + (rect.height / 2) - ); - } - } - }); - - console.log("[PixiJS] Assets erfolgreich geladen und gerendert!"); } catch (error) { console.error("[PixiJS] Fehler beim Laden der Assets:", error); } - renderSideMenu(); - renderPlanetMenu(); + renderSideMenu(pixiMenuItems); + renderPlanetMenu(pixiPlanetItems); + initFooter(); + initTopbar(); - // ==================================================================== - // LINKE SEITENMENÜ GRAFIKEN (PIXIJS) - // ==================================================================== - - pixiMenuContainer = new Container(); - app.stage.addChild(pixiMenuContainer); - pixiMenuMask = new Graphics(); - app.stage.addChild(pixiMenuMask); - pixiMenuContainer.mask = pixiMenuMask; - - menuConfig.forEach(item => { - if (item.unlocked && frameSheet) { - const bgTexture = frameSheet.textures['SPR_SciFiMenus_Menu_Item_Background_07']; - const frameTexture = frameSheet.textures['SPR_SciFiMenus_Menu_Item_Selected_10']; - - if (bgTexture && frameTexture) { - const menuContainer = new Container(); - - const bgSprite = new NineSliceSprite({ - texture: bgTexture, - leftWidth: 120, - topHeight: 120, - rightWidth: 120, - bottomHeight: 120 - }); - - bgSprite.width = 150; - bgSprite.height = 30; - bgSprite.x = -50; - bgSprite.y = -15; - bgSprite.tint = 0x00a0aa; - bgSprite.alpha = 0.6; - - const frameSprite = new NineSliceSprite({ - texture: frameTexture, - leftWidth: 100, - topHeight: 120, - rightWidth: 140, - bottomHeight: 120 - }); - - frameSprite.width = 150; - frameSprite.height = 30; - frameSprite.x = -50; - frameSprite.y = -15; - frameSprite.tint = 0x00f0ff; - frameSprite.alpha = 1; - - menuContainer.bgSprite = bgSprite; - - menuContainer.addChild(bgSprite); - menuContainer.addChild(frameSprite); - - pixiMenuContainer.addChild(menuContainer); - pixiMenuItems[item.id] = menuContainer; - } - } + startDomSync(app, { + logoContainer, + pixiMenuMask, + pixiPlanetMask, + pixiIcons, + pixiMenuItems, + pixiPlanetItems, + pixiPlanetIcons, + pixiMoonIcons, + pixiPlanetLimitContainer }); - // ==================================================================== - // PLANETEN-LIMIT GRAFIK (PIXIJS) - // ==================================================================== - if (frameSheet) { - // Die von dir vorgegebenen Texturen laden - const limitBgTexture = frameSheet.textures['SPR_SciFiMenus_Button_Frame_01_Background']; - const limitFrameTexture = frameSheet.textures['SPR_SciFiMenus_Button_Frame_01']; - - if (limitBgTexture && limitFrameTexture) { - pixiPlanetLimitContainer = new Container(); - - // 1. Hintergrund - const limitBgSprite = new NineSliceSprite({ - texture: limitBgTexture, - leftWidth: 500, - topHeight: 100, - rightWidth: 500, - bottomHeight: 100 - }); - limitBgSprite.width = 165; - limitBgSprite.height = 45; - // Exakt um die Hälfte verschieben, da wir den Container auf die HTML-Mitte zentrieren - limitBgSprite.x = -85.5; - limitBgSprite.y = -22.5; - // Leicht abgedunkeltes Cyan für den Hintergrund - limitBgSprite.tint = 0x00a0aa; - limitBgSprite.alpha = 0.5; - - // 2. Rahmen (liegt über dem Hintergrund) - const limitFrameSprite = new NineSliceSprite({ - texture: limitFrameTexture, - leftWidth: 500, - topHeight: 100, - rightWidth: 500, - bottomHeight: 100 - }); - limitFrameSprite.width = 200; - limitFrameSprite.height = 45; - limitFrameSprite.x = -100; - limitFrameSprite.y = -22.5; - limitFrameSprite.alpha = 1.0; - - pixiPlanetLimitContainer.addChild(limitBgSprite); - pixiPlanetLimitContainer.addChild(limitFrameSprite); - - // WICHTIG: Direkt an app.stage hängen, NICHT in den scrollbaren Masken-Container! - app.stage.addChild(pixiPlanetLimitContainer); - } - } - - // ==================================================================== - // RECHTE SEITENMENÜ GRAFIKEN & ICONS (PIXIJS) - // ==================================================================== - - // NEU: Wir erstellen einen Haupt-Container für alle rechten PixiJS-Elemente - pixiPlanetContainer = new Container(); - app.stage.addChild(pixiPlanetContainer); - - // NEU: Wir erstellen die Maske, fügen sie der Stage hinzu und weisen sie dem Container zu - pixiPlanetMask = new Graphics(); - app.stage.addChild(pixiPlanetMask); - pixiPlanetContainer.mask = pixiPlanetMask; - - planetConfig.forEach(planet => { - if (frameSheet && iconSheet) { - - // --- 1. Hintergrund-Rahmen --- - const bgTexture = frameSheet.textures['SPR_SciFiMenus_Frame_Box_Small_10_Background']; - if (bgTexture) { - const bgContainer = new Container(); - - const bgSprite = new NineSliceSprite({ - texture: bgTexture, - leftWidth: 30, - topHeight: 30, - rightWidth: 30, - bottomHeight: 30 - }); - - bgSprite.width = 195; - bgSprite.height = 105; - bgSprite.x = -97.5; - bgSprite.y = -52.5; - bgSprite.tint = 0x00f0ff; - - bgContainer.addChild(bgSprite); - - // ÄNDERUNG: Statt app.stage packen wir es in unseren maskierten Container - pixiPlanetContainer.addChild(bgContainer); - - bgContainer.alpha = planet.isActive ? 0.45 : 0; - pixiPlanetItems[planet.id] = bgContainer; - } - - // --- 2. Haupt-Planet Icon --- - const planetTexture = iconSheet.textures['ICON_SciFiMenus_Menu_Planet_03_Clean']; - if (planetTexture) { - const planetContainer = new Container(); - const planetSprite = new Sprite(planetTexture); - - planetSprite.anchor.set(0.5); - planetSprite.scale.set(0.24); - planetSprite.tint = planet.planetColor; - planetSprite.alpha = 0.8; - - planetContainer.addChild(planetSprite); - - // ÄNDERUNG: In den maskierten Container packen - pixiPlanetContainer.addChild(planetContainer); - - pixiPlanetIcons[planet.id] = planetContainer; - } - - // --- 3. Mond Icon --- - const moonTexture = iconSheet.textures['ICON_SciFiMenus_Menu_Moon_01_Clean']; - if (planet.hasMoon && moonTexture) { - const moonContainer = new Container(); - const moonSprite = new Sprite(moonTexture); - - moonSprite.anchor.set(0.5); - moonSprite.scale.set(0.12); - moonSprite.alpha = 0.75; - - moonContainer.addChild(moonSprite); - - // ÄNDERUNG: In den maskierten Container packen - pixiPlanetContainer.addChild(moonContainer); - - pixiMoonIcons[planet.id] = moonContainer; - } - } - }); - - setupResize(app); - - // ======================================================================== - // LIVE-STATE & GAME-LOOP SIMULATION - // ======================================================================== - - // Wir lagern die Daten in ein reaktives Zustandsobjekt aus. - // Später wird dieses Objekt durch die echte Backend-API befüllt - // (z.B. über WebSockets oder Polling, ähnlich wie in der Lobby). - const sectorState = { - res_primary: 1000, cap_primary: 10000, - res_secondary: 500, cap_secondary: 10000, - res_fuel_base: 0,cap_fuel_base: 10000, - res_fuel_adv: 0, cap_fuel_adv: 10000, - - energy: 0, - - pop_idle: 500, - pop_work: 0, - pop_injured: 500, - pop_happiness: 50, - res_food_base: 10000, - res_food_adv: 0, - res_premium: 150, - res_premium_global: 100, - res_premium_local: 50 - }; - - updateResourceUI(sectorState); - - /** - * Hilfsfunktion für gedeckelte Ressourcen-Produktion. - * Erhöht den aktuellen Wert um 'amount', stoppt aber exakt am 'cap'. - * Ist der Wert bereits über dem Cap (z.B. durch Startwerte oder Boni), - * wird nichts abgezogen, aber die Produktion pausiert. - */ - function addResourceWithCap(currentValue, productionAmount, capacity) { - if (currentValue >= capacity) { - return currentValue; - } - - const newValue = currentValue + productionAmount; - - if (newValue > capacity) { - return capacity; - } - - return newValue; - } - - // --- Der simulierte Game-Loop --- - setInterval(() => { - sectorState.res_primary = addResourceWithCap(sectorState.res_primary, 15, sectorState.cap_primary); - sectorState.res_secondary = addResourceWithCap(sectorState.res_secondary, 5, sectorState.cap_secondary); - sectorState.res_food_base -= 10; - updateResourceUI(sectorState); - }, 1000); + setupResize(app, background); + startSimulation(); } initSector(); \ No newline at end of file diff --git a/dev/sector-dev/sector-frontend/src/pixi/uiBuilder.js b/dev/sector-dev/sector-frontend/src/pixi/uiBuilder.js new file mode 100644 index 0000000..676fdb7 --- /dev/null +++ b/dev/sector-dev/sector-frontend/src/pixi/uiBuilder.js @@ -0,0 +1,356 @@ +import { Sprite, Container, Text, Graphics, NineSliceSprite } from 'pixi.js'; + +/** + * Erstellt das Sprite für den Sektor-Hintergrund und fügt es der Stage hinzu. + * + * @param {Application} app - Die laufende PixiJS Instanz. + * @param {Texture} bgTexture - Die zuvor geladene Hintergrund-Textur. + * + * @returns {Sprite} - Das erstellte Hintergrund-Sprite (wird für Resize benötigt). + */ +export function buildBackground(app, bgTexture) { + const background = new Sprite(bgTexture); + app.stage.addChild(background); + + console.log('[PixiBuilder] Hintergrund generiert.'); + return background; +} + +/** + * Baut den Logo-Container (Ring-Grafik + Text) zusammen und fügt ihn der Stage hinzu. + * + * @param {Application} app - Die laufende PixiJS Instanz. + * @param {Object} frameSheet - Das geladene Sprite-Sheet für die UI-Rahmen. + * + * @returns {Container} - Der fertige Logo-Container (wird für DOM-Sync benötigt). + */ +export function buildLogo(app, frameSheet) { + const logoContainer = new Container(); + + // 1. Den leuchtenden Ring im Hintergrund des Logos erstellen + const ringTexture = frameSheet.textures['SPR_SciFiMenus_Ring_Medium_13']; + if (ringTexture) { + const logoRing = new Sprite(ringTexture); + logoRing.anchor.set(0.5); // Anker in die Mitte für saubere Zentrierung + logoRing.tint = 0x00f0ff; // Unser charakteristisches Cyan + logoRing.scale.set(0.12); + logoRing.alpha = 0.8; + logoContainer.addChild(logoRing); + } + + // 2. Den Schriftzug erstellen + const logoText = new Text({ + text: 'Void-Genesis', + style: { + fontFamily: 'Logo Font', + fontSize: 33, + fill: 0x00f0ff, + letterSpacing: 6, + } + }); + logoText.anchor.set(0.5); + logoContainer.addChild(logoText); + + // 3. Den gesamten Container auf die Bühne legen + app.stage.addChild(logoContainer); + + console.log('[PixiBuilder] Logo generiert.'); + return logoContainer; +} + +/** + * Generiert die PixiJS-Icons für die Ressourcenleiste (Titanium, Energie, Pop, etc.). + * Erstellt die Hintergründe, lädt die Sprites aus dem Sheet und wendet Masken an. + * + * @param {Application} app - Die laufende PixiJS Instanz. + * @param {Object} iconSheet - Das geladene Sprite-Sheet für die Icons. + * + * @returns {Object} - Ein Dictionary mit den fertigen Containern { 'html_id': Container } für den DOM-Sync Ticker. + */ +export function buildResourceIcons(app, iconSheet) { + const pixiIcons = {}; + + // Zuordnung: HTML-ID der Box -> Textur-Name im Sprite-Sheet + const iconMapping = { + 'box_primary': 'ICON_Titanium', + 'box_secondary': 'ICON_Silizium', + 'box_fuel_base': 'ICON_Deuterium', + 'box_fuel_adv': 'ICON_Tritium', + 'box_energy': 'ICON_Map_Lightning', + 'box_pop_total': 'ICON_SciFiMenus_Menu_Multiplayer_01_Clean', + 'box_food_total': 'ICON_SciFi_Status_Hunger', + 'box_premium': 'ICON_SM_Item_Crystal_03' + }; + + if (iconSheet) { + for (const boxId in iconMapping) { + const texName = iconMapping[boxId]; + + if (iconSheet.textures[texName]) { + const boxContainer = new Container(); + + // Dunkler Hintergrund für das Icon + const bgGraphic = new Graphics(); + bgGraphic.roundRect(-22.5, -22.5, 45, 45, 6); + bgGraphic.fill({ color: 0x000000, alpha: 0.6 }); + boxContainer.addChild(bgGraphic); + + // Das eigentliche Icon-Sprite + const sprite = new Sprite(iconSheet.textures[texName]); + sprite.anchor.set(0.5); + + // Spezifische Skalierungen und Masken je nach Ressourcen-Typ + if (boxId == 'box_primary' || boxId == 'box_secondary' || boxId == 'box_fuel_base' || boxId == 'box_fuel_adv') { + sprite.scale.set(0.50); + // Die 4 Hauptressourcen bekommen eine Schnittmaske + const mask = new Graphics(); + mask.roundRect(-22.5, -22.5, 45, 45, 6); + mask.fill(0xffffff); + sprite.mask = mask; + boxContainer.addChild(mask); + } + else if (boxId == 'box_pop_total' || boxId == 'box_food_total') { + sprite.scale.set(0.15); + } + else if (boxId === 'box_energy') { + sprite.scale.set(0.15); + sprite.tint = 0xffff00; // Gelb färben + } + else if (boxId === 'box_premium') { + sprite.scale.set(0.15); + sprite.tint = 0xff00ff; // Magenta färben + } + else { + sprite.scale.set(0.12); // Fallback Skalierung + } + + boxContainer.addChild(sprite); + app.stage.addChild(boxContainer); + + // Den fertigen Container in unser Dictionary speichern + pixiIcons[boxId] = boxContainer; + } else { + console.warn(`[PixiBuilder] Icon-Textur nicht gefunden: ${texName}`); + } + } + } + + console.log('[PixiBuilder] Ressourcen-Icons generiert.'); + return pixiIcons; +} + +/** + * Generiert die Hintergrund-Grafiken und leuchtenden Rahmen für das linke Seitenmenü. + * + * @param {Application} app - Die laufende PixiJS Instanz. + * @param {Object} frameSheet - Das geladene Sprite-Sheet für die UI-Rahmen. + * @param {Array} menuConfig - Die Konfiguration der Menüpunkte (aus side-menu.js). + * + * @returns {Object} - Ein Objekt mit der Maske und dem Dictionary der Menü-Container { pixiMenuMask, pixiMenuItems } für den DOM-Sync Ticker. + */ +export function buildSideMenuGraphics(app, frameSheet, menuConfig) { + const pixiMenuItems = {}; + + // Haupt-Container für das Menü + const pixiMenuContainer = new Container(); + app.stage.addChild(pixiMenuContainer); + + // Maske erstellen, damit die Sprites beim Scrollen abgeschnitten werden + const pixiMenuMask = new Graphics(); + app.stage.addChild(pixiMenuMask); + pixiMenuContainer.mask = pixiMenuMask; + + // Für jeden freigeschalteten Menüpunkt einen Rahmen generieren + menuConfig.forEach(item => { + if (item.unlocked && frameSheet) { + const bgTexture = frameSheet.textures['SPR_SciFiMenus_Menu_Item_Background_07']; + const frameTexture = frameSheet.textures['SPR_SciFiMenus_Menu_Item_Selected_10']; + + if (bgTexture && frameTexture) { + const menuContainer = new Container(); + + // Der statische Hintergrund (abgedunkeltes Cyan) + const bgSprite = new NineSliceSprite({ + texture: bgTexture, + leftWidth: 120, + topHeight: 120, + rightWidth: 120, + bottomHeight: 120 + }); + bgSprite.width = 150; + bgSprite.height = 30; + bgSprite.x = -50; + bgSprite.y = -15; + bgSprite.tint = 0x00a0aa; + bgSprite.alpha = 0.6; + + // Der leuchtende Rahmen (helles Cyan, wird drübergelegt) + const frameSprite = new NineSliceSprite({ + texture: frameTexture, + leftWidth: 100, + topHeight: 120, + rightWidth: 140, + bottomHeight: 120 + }); + frameSprite.width = 150; + frameSprite.height = 30; + frameSprite.x = -50; + frameSprite.y = -15; + frameSprite.tint = 0x00f0ff; + frameSprite.alpha = 1; + + // Referenz speichern, damit wir beim Hovern (im DOM) die Farbe ändern können + menuContainer.bgSprite = bgSprite; + + menuContainer.addChild(bgSprite); + menuContainer.addChild(frameSprite); + + pixiMenuContainer.addChild(menuContainer); + pixiMenuItems[item.id] = menuContainer; + } + } + }); + + console.log('[PixiBuilder] Linkes Seitenmenü (Grafiken) generiert.'); + + // Wir geben beide Objekte zurück, da der Ticker sie benötigt + return { pixiMenuMask, pixiMenuItems }; +} + +/** + * Generiert die PixiJS-Grafiken für die rechte Seite: Das Planeten-Limit-Feld + * sowie die scrollbare Liste der Planeten (Hintergründe, Planeten-Icons, Monde). + * @param {Application} app - Die laufende PixiJS Instanz. + * @param {Object} frameSheet - Das geladene Sprite-Sheet für Rahmen. + * @param {Object} iconSheet - Das geladene Sprite-Sheet für die Icons (Planeten/Monde). + * @param {Array} planetConfig - Die Konfiguration der Planeten (aus planet-menu.js). + * + * @returns {Object} - Ein Objekt mit allen benötigten Referenzen für den DOM-Sync Ticker. + */ +export function buildPlanetMenuGraphics(app, frameSheet, iconSheet, planetConfig) { + let pixiPlanetLimitContainer = null; + const pixiPlanetItems = {}; + const pixiPlanetIcons = {}; + const pixiMoonIcons = {}; + + // ==================================================================== + // 1. PLANETEN-LIMIT GRAFIK + // ==================================================================== + if (frameSheet) { + const limitBgTexture = frameSheet.textures['SPR_SciFiMenus_Button_Frame_01_Background']; + const limitFrameTexture = frameSheet.textures['SPR_SciFiMenus_Button_Frame_01']; + + if (limitBgTexture && limitFrameTexture) { + pixiPlanetLimitContainer = new Container(); + + // Hintergrund + const limitBgSprite = new NineSliceSprite({ + texture: limitBgTexture, + leftWidth: 500, topHeight: 100, rightWidth: 500, bottomHeight: 100 + }); + limitBgSprite.width = 165; + limitBgSprite.height = 45; + limitBgSprite.x = -85.5; // Zentrierung + limitBgSprite.y = -22.5; + limitBgSprite.tint = 0x00a0aa; + limitBgSprite.alpha = 0.5; + + // Leuchtender Rahmen + const limitFrameSprite = new NineSliceSprite({ + texture: limitFrameTexture, + leftWidth: 500, topHeight: 100, rightWidth: 500, bottomHeight: 100 + }); + limitFrameSprite.width = 200; + limitFrameSprite.height = 45; + limitFrameSprite.x = -100; + limitFrameSprite.y = -22.5; + limitFrameSprite.alpha = 1.0; + + pixiPlanetLimitContainer.addChild(limitBgSprite); + pixiPlanetLimitContainer.addChild(limitFrameSprite); + app.stage.addChild(pixiPlanetLimitContainer); // Direkt auf die Stage + } + } + + // ==================================================================== + // 2. RECHTE PLANETEN-LISTE GRAFIKEN + // ==================================================================== + + // Haupt-Container und Maske für das Scrolling erstellen + const pixiPlanetContainer = new Container(); + app.stage.addChild(pixiPlanetContainer); + + const pixiPlanetMask = new Graphics(); + app.stage.addChild(pixiPlanetMask); + pixiPlanetContainer.mask = pixiPlanetMask; + + planetConfig.forEach(planet => { + if (frameSheet && iconSheet) { + + // --- A. Hintergrund-Rahmen (NineSlice) --- + const bgTexture = frameSheet.textures['SPR_SciFiMenus_Frame_Box_Small_10_Background']; + if (bgTexture) { + const bgContainer = new Container(); + const bgSprite = new NineSliceSprite({ + texture: bgTexture, + leftWidth: 30, topHeight: 30, rightWidth: 30, bottomHeight: 30 + }); + + bgSprite.width = 195; + bgSprite.height = 105; + bgSprite.x = -97.5; + bgSprite.y = -52.5; + bgSprite.tint = 0x00f0ff; + + bgContainer.addChild(bgSprite); + pixiPlanetContainer.addChild(bgContainer); + + // Aktive Planeten leuchten leicht auf, inaktive sind unsichtbar (bis Hover) + bgContainer.alpha = planet.isActive ? 0.45 : 0; + pixiPlanetItems[planet.id] = bgContainer; + } + + // --- B. Haupt-Planet Icon --- + const planetTexture = iconSheet.textures['ICON_SciFiMenus_Menu_Planet_03_Clean']; + if (planetTexture) { + const planetContainer = new Container(); + const planetSprite = new Sprite(planetTexture); + + planetSprite.anchor.set(0.5); + planetSprite.scale.set(0.24); + planetSprite.tint = planet.planetColor; // Individuelle Planetenfarbe aus Config + planetSprite.alpha = 0.8; + + planetContainer.addChild(planetSprite); + pixiPlanetContainer.addChild(planetContainer); + pixiPlanetIcons[planet.id] = planetContainer; + } + + // --- C. Mond Icon --- + const moonTexture = iconSheet.textures['ICON_SciFiMenus_Menu_Moon_01_Clean']; + if (planet.hasMoon && moonTexture) { + const moonContainer = new Container(); + const moonSprite = new Sprite(moonTexture); + + moonSprite.anchor.set(0.5); + moonSprite.scale.set(0.12); + moonSprite.alpha = 0.75; + + moonContainer.addChild(moonSprite); + pixiPlanetContainer.addChild(moonContainer); + pixiMoonIcons[planet.id] = moonContainer; + } + } + }); + + console.log('[PixiBuilder] Rechte Planetenliste generiert.'); + + // Wir packen alles in ein Objekt und geben es zurück + return { + pixiPlanetLimitContainer, + pixiPlanetMask, + pixiPlanetItems, + pixiPlanetIcons, + pixiMoonIcons + }; +} \ No newline at end of file diff --git a/dev/sector-dev/sector-frontend/src/utils/domSync.js b/dev/sector-dev/sector-frontend/src/utils/domSync.js new file mode 100644 index 0000000..e5f29fa --- /dev/null +++ b/dev/sector-dev/sector-frontend/src/utils/domSync.js @@ -0,0 +1,135 @@ +/** + * Synchronisiert die PixiJS-Elemente kontinuierlich (bis zu 60 FPS) + * mit den absoluten Koordinaten der entsprechenden HTML-DOM-Elemente. + * Garantiert, dass die Pixi-Grafiken exakt hinter/über den HTML-Boxen liegen, + * auch wenn sich das Layout durch nachladende Schriften oder Flexbox verschiebt. + * + * @param {Application} app - Die laufende PixiJS Instanz (für den Ticker) + * @param {Object} uiElements - Ein Objekt, das alle zu synchronisierenden Pixi-Container hält + */ +export function startDomSync(app, uiElements) { + const { + logoContainer, + pixiMenuMask, + pixiPlanetMask, + pixiIcons, + pixiMenuItems, + pixiPlanetItems, + pixiPlanetIcons, + pixiMoonIcons, + pixiPlanetLimitContainer + } = uiElements; + + app.ticker.add(() => { + // --- Logo Synchronisation --- + if (logoContainer) { + const logoAnchor = document.getElementById('logo-anchor'); + if (logoAnchor) { + const rect = logoAnchor.getBoundingClientRect(); + logoContainer.position.set( + rect.left + (rect.width / 2), + rect.top + (rect.height / 2) + ); + } + } + + // --- Linkes Menü (Maske) --- + if (pixiMenuMask) { + const leftMenuHtml = document.getElementById('side-menu-container'); + if (leftMenuHtml) { + const rect = leftMenuHtml.getBoundingClientRect(); + pixiMenuMask.clear(); + pixiMenuMask.rect(rect.left, rect.top, rect.width, rect.height); + pixiMenuMask.fill(0xffffff); + } + } + + // --- Rechtes Menü (Maske) --- + if (pixiPlanetMask) { + const rightMenuHtml = document.getElementById('right-sidebar-container'); + if (rightMenuHtml) { + const rect = rightMenuHtml.getBoundingClientRect(); + pixiPlanetMask.clear(); + pixiPlanetMask.rect(rect.left, rect.top, rect.width, rect.height); + pixiPlanetMask.fill(0xffffff); + } + } + + // --- Ressourcen Icons --- + for (const boxId in pixiIcons) { + const container = pixiIcons[boxId]; + const htmlBox = document.getElementById(boxId); + if (htmlBox && container) { + const rect = htmlBox.getBoundingClientRect(); + container.position.set( + rect.left + (rect.width / 2), + rect.top + (rect.height / 2) + ); + } + } + + // --- Linke Menüpunkte (Hintergründe) --- + for (const menuId in pixiMenuItems) { + const container = pixiMenuItems[menuId]; + const htmlBox = document.getElementById(menuId); + if (htmlBox && container) { + const rect = htmlBox.getBoundingClientRect(); + container.position.set( + rect.left + (rect.width / 2), + rect.top + (rect.height / 2) + ); + } + } + + // --- Rechte Planeten (Box-Hintergründe) --- + for (const planetId in pixiPlanetItems) { + const container = pixiPlanetItems[planetId]; + const htmlBox = document.getElementById(planetId); + if (htmlBox && container) { + const rect = htmlBox.getBoundingClientRect(); + container.position.set( + rect.left + (rect.width / 2), + rect.top + (rect.height / 2) + ); + } + } + + // --- Rechte Planeten (Haupt-Icons) --- + for (const planetId in pixiPlanetIcons) { + const container = pixiPlanetIcons[planetId]; + const htmlAnchor = document.getElementById(`planet_anchor_${planetId}`); + if (htmlAnchor && container) { + const rect = htmlAnchor.getBoundingClientRect(); + container.position.set( + rect.left + (rect.width / 2), + rect.top + (rect.height / 2) + ); + } + } + + // --- Rechte Planeten (Mond-Icons) --- + for (const planetId in pixiMoonIcons) { + const container = pixiMoonIcons[planetId]; + const htmlAnchor = document.getElementById(`moon_anchor_${planetId}`); + if (htmlAnchor && container) { + const rect = htmlAnchor.getBoundingClientRect(); + container.position.set( + rect.left + (rect.width / 2), + rect.top + (rect.height / 2) + ); + } + } + + // --- Planeten-Limit Container --- + if (pixiPlanetLimitContainer) { + const limitHtmlBox = document.getElementById('planet-limit-container'); + if (limitHtmlBox) { + const rect = limitHtmlBox.getBoundingClientRect(); + pixiPlanetLimitContainer.position.set( + rect.left + (rect.width / 2), + rect.top + (rect.height / 2) + ); + } + } + }); +} \ No newline at end of file diff --git a/dev/sector-dev/sector-frontend/src/utils/resize.js b/dev/sector-dev/sector-frontend/src/utils/resize.js new file mode 100644 index 0000000..7cff2a3 --- /dev/null +++ b/dev/sector-dev/sector-frontend/src/utils/resize.js @@ -0,0 +1,28 @@ +/** + * Übernimmt die Skalierungslogik für PixiJS. + * Wird jedes Mal aufgerufen, wenn der Spieler das Browserfenster vergrößert/verkleinert. + * Sorgt für einen responsiven Cover-Effekt des Hintergrundbildes. + * + * @param {Application} app - Die laufende PixiJS Instanz + * @param {Sprite} backgroundSprite - Das PixiJS-Sprite des Hintergrunds + */ +export function setupResize(app, backgroundSprite) { + function resize() { + const screenWidth = window.innerWidth; + const screenHeight = window.innerHeight; + + if (backgroundSprite && backgroundSprite.texture) { + const scaleX = screenWidth / backgroundSprite.texture.width; + const scaleY = screenHeight / backgroundSprite.texture.height; + + const scale = Math.max(scaleX, scaleY); + backgroundSprite.scale.set(scale); + + backgroundSprite.x = (screenWidth - backgroundSprite.width) / 2; + backgroundSprite.y = (screenHeight - backgroundSprite.height) / 2; + } + } + + app.renderer.on('resize', resize); + resize(); +} \ No newline at end of file diff --git a/dev/sector-dev/sector-frontend/style.css b/dev/sector-dev/sector-frontend/style.css index 88e661c..aff4b9c 100644 --- a/dev/sector-dev/sector-frontend/style.css +++ b/dev/sector-dev/sector-frontend/style.css @@ -1,3 +1,8 @@ @import url('./src/css/layout.css'); @import url('./src/css/variables.css'); -@import url('./src/css/hud.css'); + +@import url('./src/components/footer/footer.css'); +@import url('./src/components/topbar/topbar.css'); +@import url('./src/components/resource-header/resource-header.css'); +@import url('./src/components/side-menu/side-menu.css'); +@import url('./src/components/planet-menu/planet-menu.css'); \ No newline at end of file