diff --git a/dev/lobby/lobby-backend/src/controllers/sector.controller.js b/dev/lobby/lobby-backend/src/controllers/sector.controller.js
index 3d0e636..8b6ef84 100644
--- a/dev/lobby/lobby-backend/src/controllers/sector.controller.js
+++ b/dev/lobby/lobby-backend/src/controllers/sector.controller.js
@@ -34,129 +34,34 @@ export const getSectorsList = async (req, res) => {
* @param {import('express').Response} res - Das Express Response-Objekt
*/
export const joinSector = async (req, res) => {
+ const accountId = req.user.accountId;
+ const { sectorId, localUsername, gender } = req.body;
- const lobby_uuid = req.user.lobby_uuid;
- const { sector_id } = req.body;
-
- if (!sector_id) {
+ // Basis-Validierung: Fehlt ein Feld, brechen wir ab
+ if (!sectorId || !localUsername || !gender) {
return res.status(400).json({
- error: 'ERR_MISSING_SECTOR_ID'
+ error: 'ERR_MISSING_FIELDS'
});
}
- const client = await pool.connect();
-
try {
- // Spieler-Daten aus der Lobby-Datenbank laden
- const userQuery = await client.query(`
- SELECT username, gender
- FROM lobby_accounts
- WHERE lobby_uuid = $1
- `, [lobby_uuid]);
-
- if (userQuery.rows.length === 0) {
- return res.status(404).json({
- error: 'ERR_USER_NOT_FOUND'
- });
- }
- const { username, gender } = userQuery.rows[0];
-
- // Sektor-Daten laden, um die korrekte interne URL zu erfahren
- const sectorQuery = await client.query(`
- SELECT internal_url, registration_open
- FROM sectors
- WHERE id = $1
- `, [sector_id]);
-
- if (sectorQuery.rows.length === 0) {
- return res.status(404).json({
- error: 'ERR_SECTOR_NOT_FOUND'
- });
- }
-
- const sector = sectorQuery.rows[0];
-
- // Sicherheits-Check: Ist die Registrierung für diesen Sektor noch offen?
- if (!sector.registration_open) {
- return res.status(403).json({
- error: 'ERR_SECTOR_REGISTRATION_CLOSED'
- });
- }
-
- // Den internen POST-Request an das Sektor-Backend feuern
- const sectorApiUrl = `${sector.internal_url}/api/internal/accounts`;
+ await sectorService.joinSector(accountId, sectorId, localUsername, gender);
- const response = await fetch(sectorApiUrl, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- lobby_uuid,
- username,
- gender
- })
- });
-
- // Fehlerbehandlung bei negativen HTTP-Statuscodes
- // fetch springt bei HTTP-Fehlern nicht in den catch-Block, daher prüfen wir response.ok
- if (!response.ok) {
- const errorData = await response.json();
- const sectorError = errorData.error;
-
- // Wenn der Sektor exakt in diesem Moment voll geworden ist
- if (sectorError === 'ERR_SECTOR_FULL') {
- // Sektor sicherheitshalber in der Lobby sofort schließen
- await client.query(`
- UPDATE sectors SET registration_open = false WHERE id = $1
- `, [sector_id]);
- return res.status(503).json({
- error: 'ERR_SECTOR_FULL'
- });
- }
-
- if (sectorError === 'ERR_ACCOUNT_ALREADY_EXISTS') {
- return res.status(409).json({
- error: 'ERR_ACCOUNT_ALREADY_EXISTS'
- });
- }
-
- throw new Error(`Sektor-Backend meldet unerwarteten Fehler: ${sectorError}`);
- }
-
- const responseData = await response.json();
- const { triggerSectorClose } = responseData;
-
- await client.query('BEGIN');
-
- await client.query(`
- INSERT INTO account_sectors (lobby_uuid, sector_id)
- VALUES ($1, $2)
- `, [lobby_uuid, sector_id]);
-
- // Auswertung der Notfall-Schließung
- // Wenn der Sektor uns meldet, dass das Puffer-Limit erreicht ist,
- // machen wir ihn dicht, damit er im Frontend verschwindet.
- if (triggerSectorClose) {
- console.warn(`[Lobby] Sektor ${sector_id} meldet Voll-Auslastung. Schließe Registrierung...`);
- await client.query(`
- UPDATE sectors
- SET registration_open = false
- WHERE id = $1
- `, [sector_id]);
- }
-
- await client.query('COMMIT');
-
return res.status(200).json({
- message: 'MSG_SECTOR_JOINED_SUCCESSFULLY'
+ message: 'MSG_SECTOR_JOINED_SUCCESS'
});
} catch (error) {
- await client.query('ROLLBACK');
- console.error('[Lobby] Fehler beim Beitritt zum Sektor:', error.message);
- return res.status(500).json({ error: 'ERR_INTERNAL_SERVER_ERROR' });
- } finally {
- client.release();
+ console.error('[SectorController] Fehler beim Beitritt zum Sektor:', error.message);
+
+ if (error.code) {
+ return res.status(error.code === 'ERR_SECTOR_FULL' ? 503 : 409).json({
+ error: error.code
+ });
+ }
+
+ return res.status(500).json({
+ error: 'ERR_INTERNAL_SERVER'
+ });
}
};
\ No newline at end of file
diff --git a/dev/lobby/lobby-backend/src/services/cron.service.js b/dev/lobby/lobby-backend/src/services/cron.service.js
index fefbc28..9e36cd7 100644
--- a/dev/lobby/lobby-backend/src/services/cron.service.js
+++ b/dev/lobby/lobby-backend/src/services/cron.service.js
@@ -21,9 +21,9 @@ export const initLobbyCronJobs = () => {
try {
// Alle Sektoren finden, deren Registrierung aktuell geschlossen ist
const closedSectorsQuery = await client.query(`
- SELECT id, internal_url, name
+ SELECT id, name, api_url
FROM sectors
- WHERE registration_open = false
+ WHERE is_registration_open = false
`);
const closedSectors = closedSectorsQuery.rows;
@@ -36,7 +36,7 @@ export const initLobbyCronJobs = () => {
// Jeden geschlossenen Sektor einzeln über das Netzwerk abfragen
for (const sector of closedSectors) {
try {
- const response = await fetch(`${sector.internal_url}/api/internal/slots`);
+ const response = await fetch(`${sector.api_url}/api/internal/slots`);
if (!response.ok) {
console.warn(`⚠️ [CRON] Sektor ${sector.name} hat einen Fehler gemeldet (Status: ${response.status}). Überspringe.`);
@@ -53,7 +53,7 @@ export const initLobbyCronJobs = () => {
await client.query(`
UPDATE sectors
- SET registration_open = true
+ SET is_registration_open = true
WHERE id = $1
`, [sector.id]);
} else {
diff --git a/dev/lobby/lobby-backend/src/services/heartbeat.service.js b/dev/lobby/lobby-backend/src/services/heartbeat.service.js
index 625010b..3757c38 100644
--- a/dev/lobby/lobby-backend/src/services/heartbeat.service.js
+++ b/dev/lobby/lobby-backend/src/services/heartbeat.service.js
@@ -4,11 +4,21 @@ import pool from '../db.js';
// Void-Genesis - Heartbeat Service
// ==============================================================================
// Dieser Service pingt alle registrierten Sektoren regelmäßig an.
-
export const pingSectors = async () => {
const client = await pool.connect();
try {
- const result = await client.query('SELECT id, name, api_url FROM sectors WHERE api_url IS NOT NULL');
+ const result = await client.query(`
+ SELECT
+ id, name, api_url, status,
+ online_players, current_players, planets_count,
+ max_galaxies, speed_economy, research_speed,
+ fleet_speed_hostile, fleet_speed_peaceful, fleet_speed_friendly,
+ fuel_consumption_factor, fleet_to_debris_percentage,
+ defense_to_debris_percentage, fuel_included_in_debris,
+ initial_res_premium, bonus_fields
+ FROM sectors
+ WHERE api_url IS NOT NULL
+ `);
const sectors = result.rows;
for (const sector of sectors) {
@@ -23,63 +33,67 @@ export const pingSectors = async () => {
if (response.ok) {
const data = await response.json();
-
if (data.status === 'ok') {
- await client.query(
- `UPDATE sectors SET
- status = 'online',
- online_players = $1,
- current_players = $2,
- planets_count = $3,
- max_galaxies = $4,
- speed_economy = $5,
- research_speed = $6,
- fleet_speed_hostile = $7,
- fleet_speed_peaceful = $8,
- fleet_speed_friendly = $9,
- fuel_consumption_factor = $10,
- fleet_to_debris_percentage = $11,
- defense_to_debris_percentage = $12,
- fuel_included_in_debris = $13,
- initial_res_premium = $14,
- bonus_fields = $15
- WHERE id = $16`,
- [
- data.stats.online_players,
- data.stats.total_players,
- data.stats.planets_count,
- data.config.max_galaxies,
- data.config.speed_economy,
- data.config.research_speed,
- data.config.fleet_speed_hostile,
- data.config.fleet_speed_peaceful,
- data.config.fleet_speed_friendly,
- data.config.fuel_consumption_factor,
- data.config.fleet_to_debris_percentage,
- data.config.defense_to_debris_percentage,
- data.config.fuel_included_in_debris,
- data.config.initial_res_premium,
- data.config.bonus_fields,
- sector.id
- ]
- );
+
+ const hasChanged = checkSectorChanges(sector, data);
+
+ if (hasChanged) {
+ await client.query(
+ `UPDATE sectors SET
+ status = 'online',
+ online_players = $1,
+ current_players = $2,
+ planets_count = $3,
+ max_galaxies = $4,
+ speed_economy = $5,
+ research_speed = $6,
+ fleet_speed_hostile = $7,
+ fleet_speed_peaceful = $8,
+ fleet_speed_friendly = $9,
+ fuel_consumption_factor = $10,
+ fleet_to_debris_percentage = $11,
+ defense_to_debris_percentage = $12,
+ fuel_included_in_debris = $13,
+ initial_res_premium = $14,
+ bonus_fields = $15
+ WHERE id = $16`,
+ [
+ data.stats.online_players,
+ data.stats.total_players,
+ data.stats.planets_count,
+ data.config.max_galaxies,
+ data.config.speed_economy,
+ data.config.research_speed,
+ data.config.fleet_speed_hostile,
+ data.config.fleet_speed_peaceful,
+ data.config.fleet_speed_friendly,
+ data.config.fuel_consumption_factor,
+ data.config.fleet_to_debris_percentage,
+ data.config.defense_to_debris_percentage,
+ data.config.fuel_included_in_debris,
+ data.config.initial_res_premium,
+ data.config.bonus_fields,
+ sector.id
+ ]
+ );
+ }
} else {
if (sector.name === 'Development') {
console.warn(`[Heartbeat-Trace] ⚠️ Fehler: JSON-Status war nicht 'ok', sondern:`, data.status);
}
- await setSectorOffline(client, sector.id);
+ await setSectorOffline(client, sector);
}
} else {
if (sector.name === 'Development') {
console.warn(`[Heartbeat-Trace] ⚠️ HTTP-Response nicht OK! Status-Code:`, response.status);
}
- await setSectorOffline(client, sector.id);
+ await setSectorOffline(client, sector);
}
} catch (error) {
if (sector.name === 'Development') {
console.error(`[Heartbeat-Trace] ❌ Crash bei Sektor 'Development'. Grund:`, error.message);
}
- await setSectorOffline(client, sector.id);
+ await setSectorOffline(client, sector);
}
}
} catch (error) {
@@ -90,11 +104,55 @@ export const pingSectors = async () => {
};
/**
- * Hilfsfunktion, um den Status eines Sektors auf 'offline' zu setzen.
+ * Hilfsfunktion zum Vergleichen des alten DB-Stands mit den frischen API-Daten.
+ * Wir nutzen Number() und Boolean(), da PostgreSQL manche Datentypen
+ * (wie NUMERIC oder BIGINT) im Node.js-Treiber als String zurückgibt.
*/
-const setSectorOffline = async (client, sectorId) => {
- await client.query(
- "UPDATE sectors SET status = 'offline' WHERE id = $1",
- [sectorId]
- );
+const checkSectorChanges = (oldData, newData) => {
+ // Wenn der Sektor vorher als offline oder in Wartung markiert war,
+ // muss er definitiv geupdatet werden, da er jetzt wieder erreichbar ist.
+ if (oldData.status !== 'online') return true;
+
+ // Spielerzahlen und Planeten prüfen
+ if (Number(oldData.online_players) !== newData.stats.online_players) return true;
+ if (Number(oldData.current_players) !== newData.stats.total_players) return true;
+ if (Number(oldData.planets_count) !== newData.stats.planets_count) return true;
+
+ // Globale Sektor-Einstellungen auf Änderungen prüfen
+ if (Number(oldData.max_galaxies) !== newData.config.max_galaxies) return true;
+ if (Number(oldData.speed_economy) !== newData.config.speed_economy) return true;
+ if (Number(oldData.research_speed) !== newData.config.research_speed) return true;
+ if (Number(oldData.fleet_speed_hostile) !== newData.config.fleet_speed_hostile) return true;
+ if (Number(oldData.fleet_speed_peaceful) !== newData.config.fleet_speed_peaceful) return true;
+ if (Number(oldData.fleet_speed_friendly) !== newData.config.fleet_speed_friendly) return true;
+
+ // Float-Werte
+ if (Number(oldData.fuel_consumption_factor) !== Number(newData.config.fuel_consumption_factor)) return true;
+ if (Number(oldData.fleet_to_debris_percentage) !== newData.config.fleet_to_debris_percentage) return true;
+ if (Number(oldData.defense_to_debris_percentage) !== newData.config.defense_to_debris_percentage) return true;
+
+ // Boolean-Werte
+ if (Boolean(oldData.fuel_included_in_debris) !== Boolean(newData.config.fuel_included_in_debris)) return true;
+
+ // Start-Boni
+ if (Number(oldData.initial_res_premium) !== newData.config.initial_res_premium) return true;
+ if (Number(oldData.bonus_fields) !== newData.config.bonus_fields) return true;
+
+ // Wurde kein Unterschied gefunden, geben wir false zurück
+ return false;
+};
+
+/**
+ * Setzt den Status eines Sektors auf 'offline'.
+ * Prüft vorher, ob der Sektor nicht ohnehin schon als offline in der DB steht,
+ * um auch hier sinnlose Schreibzugriffe und Checkpoint-Logs zu verhindern.
+ */
+const setSectorOffline = async (client, sector) => {
+ if (sector.status !== 'offline') {
+ await client.query(
+ "UPDATE sectors SET status = 'offline' WHERE id = $1",
+ [sector.id]
+ );
+ sector.status = 'offline';
+ }
};
\ No newline at end of file
diff --git a/dev/lobby/lobby-frontend/src/assets/fonts/Oswald-Light.ttf b/dev/lobby/lobby-frontend/src/assets/fonts/Oswald-Light.ttf
new file mode 100644
index 0000000..d6e1171
Binary files /dev/null and b/dev/lobby/lobby-frontend/src/assets/fonts/Oswald-Light.ttf differ
diff --git a/dev/lobby/lobby-frontend/src/assets/fonts/Oswald-Medium.ttf b/dev/lobby/lobby-frontend/src/assets/fonts/Oswald-Medium.ttf
new file mode 100644
index 0000000..9ef04ac
Binary files /dev/null and b/dev/lobby/lobby-frontend/src/assets/fonts/Oswald-Medium.ttf differ
diff --git a/dev/lobby/lobby-frontend/src/assets/fonts/Oswald-Regular.ttf b/dev/lobby/lobby-frontend/src/assets/fonts/Oswald-Regular.ttf
new file mode 100644
index 0000000..5cc7b31
Binary files /dev/null and b/dev/lobby/lobby-frontend/src/assets/fonts/Oswald-Regular.ttf differ
diff --git a/dev/lobby/lobby-frontend/src/main.js b/dev/lobby/lobby-frontend/src/main.js
index 2b5bb8e..7c1e7f1 100644
--- a/dev/lobby/lobby-frontend/src/main.js
+++ b/dev/lobby/lobby-frontend/src/main.js
@@ -591,32 +591,40 @@ function renderSectorList(sectors, type) {
`;
cardLink.onclick = (e) => {
e.preventDefault();
- console.log(`Starte Onboarding für Sektor: ${sector.name} (ID: ${sector.id})`);
- selectedSectorId = sector.id;
+ if (type === 'my') {
+ console.log(`Betrete bekannten Sektor: ${sector.name}`);
+ // todo für später, hier muss dann variable der link stehen oder man müsste ihn aus der datenbank auslesen
+ window.open('https://dev.void-genesis.de', '_blank');
+
+ } else {
+ console.log(`Starte Onboarding für Sektor: ${sector.name} (ID: ${sector.id})`);
- const sectorBrowserLayer = document.getElementById('sector-browser-layer');
- if (sectorBrowserLayer) {
- sectorBrowserLayer.style.opacity = 0.05;
- sectorBrowserLayer.style.pointerEvents = 'none';
- }
+ selectedSectorId = sector.id;
- const onboardingHtmlLayer = document.getElementById('sector-onboarding-layer');
- if (onboardingHtmlLayer) onboardingHtmlLayer.style.display = 'flex';
-
- if (onboardingPixiContainer) onboardingPixiContainer.visible = true;
+ const sectorBrowserLayer = document.getElementById('sector-browser-layer');
+ if (sectorBrowserLayer) {
+ sectorBrowserLayer.style.opacity = 0.05;
+ sectorBrowserLayer.style.pointerEvents = 'none';
+ }
- const localUsernameInput = document.getElementById('local-username');
- if (localUsernameInput) {
- const userDataStr = sessionStorage.getItem('void_user') || localStorage.getItem('void_user');
- if (userDataStr) {
- try {
- const userData = JSON.parse(userDataStr);
- if (userData.username) {
- localUsernameInput.value = userData.username;
+ const onboardingHtmlLayer = document.getElementById('sector-onboarding-layer');
+ if (onboardingHtmlLayer) onboardingHtmlLayer.style.display = 'flex';
+
+ if (onboardingPixiContainer) onboardingPixiContainer.visible = true;
+
+ const localUsernameInput = document.getElementById('local-username');
+ if (localUsernameInput) {
+ const userDataStr = sessionStorage.getItem('void_user') || localStorage.getItem('void_user');
+ if (userDataStr) {
+ try {
+ const userData = JSON.parse(userDataStr);
+ if (userData.username) {
+ localUsernameInput.value = userData.username;
+ }
+ } catch (err) {
+ console.error("Fehler beim Auslesen der User-Daten für das Onboarding:", err);
}
- } catch (err) {
- console.error("Fehler beim Auslesen der User-Daten für das Onboarding:", err);
}
}
}
@@ -971,6 +979,7 @@ if (onboardingForm) {
if (btnCancelOnboarding) btnCancelOnboarding.click();
loadAndRenderSectorBrowser();
+ window.open('https://dev.void-genesis.de', '_blank');
}, 1500);
} catch (error) {
diff --git a/dev/sector-dev/sector-frontend/index.html b/dev/sector-dev/sector-frontend/index.html
new file mode 100644
index 0000000..69ba643
--- /dev/null
+++ b/dev/sector-dev/sector-frontend/index.html
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+ Void-Genesis | Sektor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Planeten: 10 / 12
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dev/sector-dev/sector-frontend/package-lock.json b/dev/sector-dev/sector-frontend/package-lock.json
new file mode 100644
index 0000000..63c1c6d
--- /dev/null
+++ b/dev/sector-dev/sector-frontend/package-lock.json
@@ -0,0 +1,2473 @@
+{
+ "name": "void-genesis-sector-frontend",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "void-genesis-sector-frontend",
+ "version": "0.0.0",
+ "dependencies": {
+ "pixi.js": "^8.1.0"
+ },
+ "devDependencies": {
+ "free-tex-packer-core": "^0.3.8",
+ "vite": "^8.0.16"
+ }
+ },
+ "node_modules/@borewit/text-codec": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz",
+ "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
+ "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.1",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
+ "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+ "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@img/colour": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
+ "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
+ "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
+ "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
+ "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
+ "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
+ "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
+ "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
+ "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
+ "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
+ "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
+ "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
+ "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
+ "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
+ "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
+ "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-ppc64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-riscv64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
+ "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-riscv64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
+ "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
+ "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
+ "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
+ "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
+ "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.7.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
+ "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
+ "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
+ "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@jimp/core": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.1.tgz",
+ "integrity": "sha512-+BoKC5G6hkrSy501zcJ2EpfnllP+avPevcBfRcZe/CW+EwEfY6X1EZ8QWyT7NpDIvEEJb1fdJnMMfUnFkxmw9A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/file-ops": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "@jimp/utils": "1.6.1",
+ "await-to-js": "^3.0.0",
+ "exif-parser": "^0.1.12",
+ "file-type": "^21.3.3",
+ "mime": "3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/diff": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.1.tgz",
+ "integrity": "sha512-YkKDPdHjLgo1Api3+Bhc0GLAygldlpt97NfOKoNg1U6IUNXA6X2MgosCjPfSBiSvJvrrz1fsIR+/4cfYXBI/HQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/plugin-resize": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "@jimp/utils": "1.6.1",
+ "pixelmatch": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/file-ops": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.1.tgz",
+ "integrity": "sha512-T+gX6osHjprbDRad0/B71Evyre7ZdVY1z/gFGEG9Z8KOtZPKboWvPeP2UjbZYWQLy9UKCPQX1FNAnDiOPkJL7w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/js-bmp": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.1.tgz",
+ "integrity": "sha512-xzWzNT4/u5zGrTT3Tme9sGU7YzIKxi13+BCQwLqACbt5DXf9SAfdzRkopZQnmDko+6In5nqaT89Gjs43/WdnYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/core": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "@jimp/utils": "1.6.1",
+ "bmp-ts": "^1.0.9"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/js-gif": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.1.tgz",
+ "integrity": "sha512-YjY2W26rQa05XhanYhRZ7dingCiNN+T2Ymb1JiigIbABY0B28wHE3v3Cf1/HZPWGu0hOg36ylaKgV5KxF2M58w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/core": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "gifwrap": "^0.10.1",
+ "omggif": "^1.0.10"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/js-jpeg": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.1.tgz",
+ "integrity": "sha512-HT9H3yOmlOFzYmdI15IYdfy6ggQhSRIaHeA+OTJSEORXBqEo97sUZu/DsgHIcX5NJ7TkJBTgZ9BZXsV6UbsyMg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/core": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "jpeg-js": "^0.4.4"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/js-png": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.1.tgz",
+ "integrity": "sha512-SZ/KVhI5UjcSzzlXsXdIi/LhJ7UShf2NkMOtVrbZQcGzsqNtynAelrOXeoTxcanfVqmNhAoVHg8yR2cYoqrYjA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/core": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "pngjs": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/js-tiff": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.1.tgz",
+ "integrity": "sha512-jDG/eJquID1M4MBlKMmDRBmz2TpXMv7TUyu2nIRUxhlUc2ogC82T+VQUkca9GJH1BBJ9dx5sSE5dGkWNjIbZxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/core": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "utif2": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-blit": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.1.tgz",
+ "integrity": "sha512-MwnI7C7K81uWddY9FLw1fCOIy6SsPIUftUz36Spt7jisCn8/40DhQMlSxpxTNelnZb/2SnloFimQfRZAmHLOqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/types": "1.6.1",
+ "@jimp/utils": "1.6.1",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-blur": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.1.tgz",
+ "integrity": "sha512-lIo7Tzp5jQu30EFFSK/phXANK3citKVEjepDjQ6ljHoIFtuMRrnybnmI2Md24ulvWlDaz+hh3n6qrMb8ydwhZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/core": "1.6.1",
+ "@jimp/utils": "1.6.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-circle": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.1.tgz",
+ "integrity": "sha512-kK1PavY6cKHNNKce37vdV4Tmpc1/zDKngGoeOV3j+EMatoHFZUinV3s6F9aWryPs3A0xhCLZgdJ6Zeea1d5LCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/types": "1.6.1",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-color": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.1.tgz",
+ "integrity": "sha512-LtUN1vAP+LRlZAtTNVhDRSiXx+26Kbz3zJaG6a5k59gQ95jgT5mknnF8lxkHcqJthM4MEk3/tPxkdJpEybyF/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/core": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "@jimp/utils": "1.6.1",
+ "tinycolor2": "^1.6.0",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-contain": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.1.tgz",
+ "integrity": "sha512-m0qhrfA8jkTqretGv4w+T/ADFR4GwBpE0sCOC2uJ0dzr44/ddOMsIdrpi89kabqYiPYIrxkgdCVCLm3zn1Vkkg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/core": "1.6.1",
+ "@jimp/plugin-blit": "1.6.1",
+ "@jimp/plugin-resize": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "@jimp/utils": "1.6.1",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-cover": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.1.tgz",
+ "integrity": "sha512-hZytnsth0zoll6cPf434BrT+p/v569Wr5tyO6Dp0dH1IDPhzhB5F38sZGMLDo7bzQiN9JFVB3fxkcJ/WYCJ3Mg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/core": "1.6.1",
+ "@jimp/plugin-crop": "1.6.1",
+ "@jimp/plugin-resize": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-crop": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.1.tgz",
+ "integrity": "sha512-EerRSLlclXyKDnYc/H9w/1amZW7b7v3OGi/VlerPd2M/pAu5X8TkyYWtfqYCXnNp1Ixtd8oCo9zGfY9zoXT4rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/core": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "@jimp/utils": "1.6.1",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-displace": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.1.tgz",
+ "integrity": "sha512-K07QVl7xQwIfD6KfxRV/c3E9e7ZBXxUXdWuvoTWcKHL2qV48MOF5Nqbz/aJW4ThnQARIsxvYlZjPFiqkCjlU+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/types": "1.6.1",
+ "@jimp/utils": "1.6.1",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-dither": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.1.tgz",
+ "integrity": "sha512-+2V+GCV2WycMoX1/z977TkZ8Zq/4MVSKElHYatgUqtwXMi2fDK2gKYU2g9V39IqFvTJsTIsK0+58VFz/ROBVew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/types": "1.6.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-fisheye": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.1.tgz",
+ "integrity": "sha512-XtS5ZyoZ0vxZxJ6gkqI63SivhtI58vX95foMPM+cyzYkRsJXMOYCr8DScxF5bp4Xr003NjYm/P+7+08tibwzHA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/types": "1.6.1",
+ "@jimp/utils": "1.6.1",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-flip": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.1.tgz",
+ "integrity": "sha512-ws38W/sGj7LobNRayQ83garxiktOyWxM5vO/y4a/2cy9v65SLEUzVkrj+oeAaUSSObdz4HcCEla7XtGlnAGAaA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/types": "1.6.1",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-hash": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.1.tgz",
+ "integrity": "sha512-sZt6ZcMX6i8vFWb4GYnw0pR/o9++ef0dTVcboTB5B/g7nrxCODIB4wfEkJ/YqZM5wUvol77K1qeS0/rVO6z21A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/core": "1.6.1",
+ "@jimp/js-bmp": "1.6.1",
+ "@jimp/js-jpeg": "1.6.1",
+ "@jimp/js-png": "1.6.1",
+ "@jimp/js-tiff": "1.6.1",
+ "@jimp/plugin-color": "1.6.1",
+ "@jimp/plugin-resize": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "@jimp/utils": "1.6.1",
+ "any-base": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-mask": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.1.tgz",
+ "integrity": "sha512-SIG0/FcmEj3tkwFxc7fAGLO8o4uNzMpSOdQOhbCgxefQKq5wOVMk9BQx/sdMPBwtMLr9WLq0GzLA/rk6t2v20A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/types": "1.6.1",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-print": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.1.tgz",
+ "integrity": "sha512-BYVz/X3Xzv8XYilVeDy11NOp0h7BTDjlOtu0BekIFHP1yHVd24AXNzbOy52XlzYZWQ0Dl36HOHEpl/nSNrzc6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/core": "1.6.1",
+ "@jimp/js-jpeg": "1.6.1",
+ "@jimp/js-png": "1.6.1",
+ "@jimp/plugin-blit": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "parse-bmfont-ascii": "^1.0.6",
+ "parse-bmfont-binary": "^1.0.6",
+ "parse-bmfont-xml": "^1.1.6",
+ "simple-xml-to-json": "^1.2.2",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-quantize": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.1.tgz",
+ "integrity": "sha512-J2En9PLURfP+vwYDtuZ9T8yBW6BWYZBScydAjRiPBmJfEhTcNQqiiQODrZf7EqbbX/Sy5H6dAeRiqkgoV9N6Ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "image-q": "^4.0.0",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-resize": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.1.tgz",
+ "integrity": "sha512-CLkrtJoIz2HdWnpYiN6p8KYcPc00rCH/SUu6o+lfZL05Q4uhecJlnvXuj9x+U6mDn3ldPmJj6aZqMHuUJzdVqg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/core": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-rotate": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.1.tgz",
+ "integrity": "sha512-nOjVjbbj705B02ksysKnh0POAwEBXZtJ9zQ5qC+X7Tavl3JNn+P3BzQovbBxLPSbUSld6XID9z5ijin4PtOAUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/core": "1.6.1",
+ "@jimp/plugin-crop": "1.6.1",
+ "@jimp/plugin-resize": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "@jimp/utils": "1.6.1",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/plugin-threshold": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.1.tgz",
+ "integrity": "sha512-JOKv9F8s6tnVLf4sB/2fF0F339EFnHvgEdFYugO6VhowKLsap0pEZmLyE/DlRnYtIj2RddHZVxVMp/eKJ04l2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/core": "1.6.1",
+ "@jimp/plugin-color": "1.6.1",
+ "@jimp/plugin-hash": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "@jimp/utils": "1.6.1",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/types": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.1.tgz",
+ "integrity": "sha512-leI7YbveTNi565m910XgIOwXyuu074H5qazAD1357HImJSv2hqxnWXpwxQbadGWZ7goZRYBDZy5lpqud0p7q5w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jimp/utils": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.1.tgz",
+ "integrity": "sha512-veFPRd93FCnS7AgmCkPgARVGoDRrJ9cm1ujuNyA+UfQ5VKbED2002sm5XfFLFwTsKC8j04heTrwe+tU1dluXOw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/types": "1.6.1",
+ "tinycolor2": "^1.6.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jvitela/mustache-wax": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@jvitela/mustache-wax/-/mustache-wax-1.0.4.tgz",
+ "integrity": "sha512-cGugVYp0WO7w89KQlyyt2rQyCO5MbrKeqtIAkG8oBfOaiHoeGeKG7WYdBFHftvoZUuK4WFbWyn3hgFipdetC+Q==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "mustache": "^4.2.0"
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz",
+ "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.133.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
+ "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@pixi/colord": {
+ "version": "2.9.6",
+ "license": "MIT"
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
+ "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
+ "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
+ "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
+ "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
+ "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
+ "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
+ "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
+ "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
+ "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
+ "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
+ "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
+ "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
+ "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "1.10.0",
+ "@emnapi/runtime": "1.10.0",
+ "@napi-rs/wasm-runtime": "^1.1.4"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
+ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
+ "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
+ "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tokenizer/inflate": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz",
+ "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "token-types": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
+ "node_modules/@tokenizer/token": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
+ "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
+ "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/earcut": {
+ "version": "3.0.0",
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "25.9.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz",
+ "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "undici-types": ">=7.24.0 <7.24.7"
+ }
+ },
+ "node_modules/@webgpu/types": {
+ "version": "0.1.70",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@xmldom/xmldom": {
+ "version": "0.8.13",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/any-base": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz",
+ "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/await-to-js": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz",
+ "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/bmp-ts": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz",
+ "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/earcut": {
+ "version": "3.0.2",
+ "license": "ISC"
+ },
+ "node_modules/eventemitter3": {
+ "version": "5.0.4",
+ "license": "MIT"
+ },
+ "node_modules/exif-parser": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz",
+ "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==",
+ "dev": true
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-type": {
+ "version": "21.3.4",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz",
+ "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tokenizer/inflate": "^0.4.1",
+ "strtok3": "^10.3.4",
+ "token-types": "^6.1.1",
+ "uint8array-extras": "^1.4.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/file-type?sponsor=1"
+ }
+ },
+ "node_modules/free-tex-packer-core": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/free-tex-packer-core/-/free-tex-packer-core-0.3.8.tgz",
+ "integrity": "sha512-Xi8VRwXWbMO7egcPumYCY5UiEk4TKQbJYNHyqS0r2dilgaepP68mS8oTkXW0+0fG/GOja+nkO3sbXGS3UPp+7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jvitela/mustache-wax": "^1.0.1",
+ "jimp": "^1.6.1",
+ "maxrects-packer": "2.7.3",
+ "mustache": "^4.2.0",
+ "sharp": "^0.34.5",
+ "tinify": "^1.5.0"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gifuct-js": {
+ "version": "2.1.2",
+ "license": "MIT",
+ "dependencies": {
+ "js-binary-schema-parser": "^2.0.3"
+ }
+ },
+ "node_modules/gifwrap": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz",
+ "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "image-q": "^4.0.0",
+ "omggif": "^1.0.10"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/image-q": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz",
+ "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "16.9.1"
+ }
+ },
+ "node_modules/image-q/node_modules/@types/node": {
+ "version": "16.9.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz",
+ "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ismobilejs": {
+ "version": "1.1.1",
+ "license": "MIT"
+ },
+ "node_modules/jimp": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.1.tgz",
+ "integrity": "sha512-hNQh6rZtWfSVWSNVmvq87N5BPJsNH7k7I7qyrXf9DOma9xATQk3fsyHazCQe51nCjdkoWdTmh0vD7bjVSLoxxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jimp/core": "1.6.1",
+ "@jimp/diff": "1.6.1",
+ "@jimp/js-bmp": "1.6.1",
+ "@jimp/js-gif": "1.6.1",
+ "@jimp/js-jpeg": "1.6.1",
+ "@jimp/js-png": "1.6.1",
+ "@jimp/js-tiff": "1.6.1",
+ "@jimp/plugin-blit": "1.6.1",
+ "@jimp/plugin-blur": "1.6.1",
+ "@jimp/plugin-circle": "1.6.1",
+ "@jimp/plugin-color": "1.6.1",
+ "@jimp/plugin-contain": "1.6.1",
+ "@jimp/plugin-cover": "1.6.1",
+ "@jimp/plugin-crop": "1.6.1",
+ "@jimp/plugin-displace": "1.6.1",
+ "@jimp/plugin-dither": "1.6.1",
+ "@jimp/plugin-fisheye": "1.6.1",
+ "@jimp/plugin-flip": "1.6.1",
+ "@jimp/plugin-hash": "1.6.1",
+ "@jimp/plugin-mask": "1.6.1",
+ "@jimp/plugin-print": "1.6.1",
+ "@jimp/plugin-quantize": "1.6.1",
+ "@jimp/plugin-resize": "1.6.1",
+ "@jimp/plugin-rotate": "1.6.1",
+ "@jimp/plugin-threshold": "1.6.1",
+ "@jimp/types": "1.6.1",
+ "@jimp/utils": "1.6.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/jpeg-js": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
+ "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/js-binary-schema-parser": {
+ "version": "2.0.3",
+ "license": "MIT"
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/maxrects-packer": {
+ "version": "2.7.3",
+ "resolved": "https://registry.npmjs.org/maxrects-packer/-/maxrects-packer-2.7.3.tgz",
+ "integrity": "sha512-bG6qXujJ1QgttZVIH4WDanhoJtvbud/xP/XPyf6A69C9RdA61BM4TomFALCq2nrTa+tARRIBB4LuIFsnUQU2wA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mime": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
+ "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mustache": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
+ "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mustache": "bin/mustache"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.12",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/omggif": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz",
+ "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "dev": true,
+ "license": "(MIT AND Zlib)"
+ },
+ "node_modules/parse-bmfont-ascii": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz",
+ "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/parse-bmfont-binary": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz",
+ "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/parse-bmfont-xml": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz",
+ "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "xml-parse-from-string": "^1.0.0",
+ "xml2js": "^0.5.0"
+ }
+ },
+ "node_modules/parse-svg-path": {
+ "version": "0.2.0",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pixelmatch": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz",
+ "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "pngjs": "^6.0.0"
+ },
+ "bin": {
+ "pixelmatch": "bin/pixelmatch"
+ }
+ },
+ "node_modules/pixelmatch/node_modules/pngjs": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",
+ "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.13.0"
+ }
+ },
+ "node_modules/pixi.js": {
+ "version": "8.19.0",
+ "license": "MIT",
+ "workspaces": [
+ "examples",
+ "playground"
+ ],
+ "dependencies": {
+ "@pixi/colord": "^2.9.6",
+ "@types/earcut": "^3.0.0",
+ "@webgpu/types": "^0.1.69",
+ "@xmldom/xmldom": "^0.8.13",
+ "earcut": "^3.0.2",
+ "eventemitter3": "^5.0.1",
+ "gifuct-js": "^2.1.2",
+ "ismobilejs": "^1.1.1",
+ "parse-svg-path": "^0.2.0",
+ "tiny-lru": "^11.4.7"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/pixijs"
+ }
+ },
+ "node_modules/pngjs": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
+ "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.19.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.15",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.12",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/promise-nodeify": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/promise-nodeify/-/promise-nodeify-3.0.1.tgz",
+ "integrity": "sha512-ghsSuzZXJX8iO7WVec2z7GI+Xk/EyiD+JZK7AZKhUqYfpLa/Zs4ylUD+CwwnKlG6G3HnkUPMAi6PO7zeqGKssg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6",
+ "npm": ">=1.3.7"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
+ "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.133.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.0.3",
+ "@rolldown/binding-darwin-arm64": "1.0.3",
+ "@rolldown/binding-darwin-x64": "1.0.3",
+ "@rolldown/binding-freebsd-x64": "1.0.3",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.3",
+ "@rolldown/binding-linux-arm64-musl": "1.0.3",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.3",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.3",
+ "@rolldown/binding-linux-x64-gnu": "1.0.3",
+ "@rolldown/binding-linux-x64-musl": "1.0.3",
+ "@rolldown/binding-openharmony-arm64": "1.0.3",
+ "@rolldown/binding-wasm32-wasi": "1.0.3",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.3",
+ "@rolldown/binding-win32-x64-msvc": "1.0.3"
+ }
+ },
+ "node_modules/sax": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
+ "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=11.0.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz",
+ "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/sharp": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
+ "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@img/colour": "^1.0.0",
+ "detect-libc": "^2.1.2",
+ "semver": "^7.7.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.34.5",
+ "@img/sharp-darwin-x64": "0.34.5",
+ "@img/sharp-libvips-darwin-arm64": "1.2.4",
+ "@img/sharp-libvips-darwin-x64": "1.2.4",
+ "@img/sharp-libvips-linux-arm": "1.2.4",
+ "@img/sharp-libvips-linux-arm64": "1.2.4",
+ "@img/sharp-libvips-linux-ppc64": "1.2.4",
+ "@img/sharp-libvips-linux-riscv64": "1.2.4",
+ "@img/sharp-libvips-linux-s390x": "1.2.4",
+ "@img/sharp-libvips-linux-x64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
+ "@img/sharp-linux-arm": "0.34.5",
+ "@img/sharp-linux-arm64": "0.34.5",
+ "@img/sharp-linux-ppc64": "0.34.5",
+ "@img/sharp-linux-riscv64": "0.34.5",
+ "@img/sharp-linux-s390x": "0.34.5",
+ "@img/sharp-linux-x64": "0.34.5",
+ "@img/sharp-linuxmusl-arm64": "0.34.5",
+ "@img/sharp-linuxmusl-x64": "0.34.5",
+ "@img/sharp-wasm32": "0.34.5",
+ "@img/sharp-win32-arm64": "0.34.5",
+ "@img/sharp-win32-ia32": "0.34.5",
+ "@img/sharp-win32-x64": "0.34.5"
+ }
+ },
+ "node_modules/simple-xml-to-json": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.7.tgz",
+ "integrity": "sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.12.2"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strtok3": {
+ "version": "10.3.5",
+ "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz",
+ "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tokenizer/token": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
+ "node_modules/tinify": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/tinify/-/tinify-1.8.2.tgz",
+ "integrity": "sha512-wkXLOj3/D2MdgWLI7TOXSbeD9xRu3GVxqt89ZWfrOyr8c4Ajd6gdowHY5ak7ASqMUu54+icHO/0BIiBg8pFIvg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "https-proxy-agent": "^7.0.6",
+ "promise-nodeify": ">= 0.1"
+ },
+ "engines": {
+ "node": ">= 14.0"
+ }
+ },
+ "node_modules/tiny-lru": {
+ "version": "11.4.7",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/tinycolor2": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
+ "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/token-types": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz",
+ "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@borewit/text-codec": "^0.2.1",
+ "@tokenizer/token": "^0.3.0",
+ "ieee754": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ },
+ "node_modules/uint8array-extras": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz",
+ "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.24.6",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
+ "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true
+ },
+ "node_modules/utif2": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz",
+ "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pako": "^1.0.11"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.0.16",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
+ "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.32.0",
+ "picomatch": "^4.0.4",
+ "postcss": "^8.5.15",
+ "rolldown": "1.0.3",
+ "tinyglobby": "^0.2.17"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.1.18",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xml-parse-from-string": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz",
+ "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/xml2js": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
+ "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/dev/sector-dev/sector-frontend/package.json b/dev/sector-dev/sector-frontend/package.json
index 73702e6..648ba6e 100644
--- a/dev/sector-dev/sector-frontend/package.json
+++ b/dev/sector-dev/sector-frontend/package.json
@@ -11,6 +11,7 @@
"pixi.js": "^8.1.0"
},
"devDependencies": {
- "vite": "^5.2.11"
+ "free-tex-packer-core": "^0.3.8",
+ "vite": "^8.0.16"
}
-}
\ No newline at end of file
+}
diff --git a/dev/sector-dev/sector-frontend/scripts/generate-spritesheets.js b/dev/sector-dev/sector-frontend/scripts/generate-spritesheets.js
new file mode 100644
index 0000000..2d0b4ac
--- /dev/null
+++ b/dev/sector-dev/sector-frontend/scripts/generate-spritesheets.js
@@ -0,0 +1,109 @@
+// === File: sector-frontend/scripts/generate-spritesheets.js ===
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+import texturePacker from 'free-tex-packer-core';
+
+// ES-Modul Workaround für __dirname
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// ----------------------------------------------------------------------
+// Pfad-Konfiguration
+// ----------------------------------------------------------------------
+// Der Hauptordner, in dem wir Unterordner für die verschiedenen Kategorien anlegen.
+const RAW_ASSETS_DIR = path.resolve(__dirname, '../src/raw-assets');
+// Zielordner für die fertigen Atlanten, die dann in der App geladen werden.
+const OUTPUT_DIR = path.resolve(__dirname, '../src/assets');
+
+/**
+ * Durchsucht den RAW_ASSETS_DIR nach Unterordnern und generiert
+ * für jeden gefundenen Ordner ein separates Sprite-Sheet.
+ */
+async function buildAllSpriteSheets() {
+ console.log(`[Packer] Prüfe Hauptverzeichnis: ${RAW_ASSETS_DIR}`);
+
+ // Hauptordner erstellen, falls er nicht existiert
+ if (!fs.existsSync(RAW_ASSETS_DIR)) {
+ fs.mkdirSync(RAW_ASSETS_DIR, { recursive: true });
+ console.log('[Packer] Quellordner existierte nicht und wurde erstellt. Bitte Unterordner (z.B. "icons") anlegen und Bilder einfügen.');
+ return;
+ }
+
+ // Lese alle Einträge im Hauptordner aus
+ const entries = fs.readdirSync(RAW_ASSETS_DIR, { withFileTypes: true });
+
+ // Filtere nur die Verzeichnisse (Unterordner) heraus
+ const directories = entries.filter(entry => entry.isDirectory()).map(dir => dir.name);
+
+ if (directories.length === 0) {
+ console.log('[Packer] Keine Unterordner in raw-assets gefunden. Bitte lege Ordner wie "icons" oder "frames" an.');
+ return;
+ }
+
+ // Zielordner erstellen, falls nicht vorhanden
+ if (!fs.existsSync(OUTPUT_DIR)) {
+ fs.mkdirSync(OUTPUT_DIR, { recursive: true });
+ }
+
+ // Schleife durch alle gefundenen Unterordner
+ for (const folderName of directories) {
+ const currentDirPath = path.join(RAW_ASSETS_DIR, folderName);
+ const files = fs.readdirSync(currentDirPath);
+
+ const images = [];
+
+ // Suche nach PNG-Dateien im aktuellen Unterordner
+ for (const file of files) {
+ if (file.endsWith('.png')) {
+ const filePath = path.join(currentDirPath, file);
+ images.push({
+ path: file,
+ contents: fs.readFileSync(filePath)
+ });
+ }
+ }
+
+ if (images.length === 0) {
+ console.log(`[Packer] Überspringe Ordner '${folderName}' (keine PNG-Dateien gefunden).`);
+ continue; // Zum nächsten Ordner springen
+ }
+
+ // Konfiguration für DIESEN spezifischen Atlas
+ const options = {
+ textureName: folderName, // Der Name des Ordners wird zum Namen der generierten Dateien
+ width: 4096,
+ height: 4096,
+ padding: 2,
+ allowRotation: false,
+ exporter: 'Pixi',
+ removeFileExtension: true
+ };
+
+ console.log(`[Packer] Verpacke ${images.length} Bilder aus dem Ordner '${folderName}'...`);
+
+ // Packer-Aufruf in ein Promise wickeln, um asynchrones Verarbeiten sauber abzuarbeiten
+ await new Promise((resolve, reject) => {
+ texturePacker(images, options, (packedFiles, error) => {
+ if (error) {
+ console.error(`[Packer] Fehler beim Erstellen des Atlases für '${folderName}':`, error);
+ reject(error);
+ return;
+ }
+
+ // Generierte Dateien speichern (.png und .json)
+ for (const item of packedFiles) {
+ const outPath = path.join(OUTPUT_DIR, item.name);
+ fs.writeFileSync(outPath, item.buffer);
+ console.log(`[Packer] Gespeichert: ${outPath}`);
+ }
+ resolve();
+ });
+ });
+ }
+
+ console.log('[Packer] Alle Sprite-Sheets wurden erfolgreich generiert!');
+}
+
+// Skript ausführen
+buildAllSpriteSheets();
\ No newline at end of file
diff --git a/dev/sector-dev/sector-frontend/src/assets/backgrounds/Background_A.png b/dev/sector-dev/sector-frontend/src/assets/backgrounds/Background_A.png
new file mode 100644
index 0000000..88be743
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/assets/backgrounds/Background_A.png differ
diff --git a/dev/sector-dev/sector-frontend/src/assets/backgrounds/Background_B.png b/dev/sector-dev/sector-frontend/src/assets/backgrounds/Background_B.png
new file mode 100644
index 0000000..79b0165
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/assets/backgrounds/Background_B.png differ
diff --git a/dev/sector-dev/sector-frontend/src/assets/backgrounds/Background_C.png b/dev/sector-dev/sector-frontend/src/assets/backgrounds/Background_C.png
new file mode 100644
index 0000000..5eb8599
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/assets/backgrounds/Background_C.png differ
diff --git a/dev/sector-dev/sector-frontend/src/assets/backgrounds/Background_D.png b/dev/sector-dev/sector-frontend/src/assets/backgrounds/Background_D.png
new file mode 100644
index 0000000..cfdd3b5
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/assets/backgrounds/Background_D.png differ
diff --git a/dev/sector-dev/sector-frontend/src/assets/fonts/Bungee-Regular.ttf b/dev/sector-dev/sector-frontend/src/assets/fonts/Bungee-Regular.ttf
new file mode 100644
index 0000000..e1721ae
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/assets/fonts/Bungee-Regular.ttf differ
diff --git a/dev/sector-dev/sector-frontend/src/assets/fonts/Exo2.0-Regular.otf b/dev/sector-dev/sector-frontend/src/assets/fonts/Exo2.0-Regular.otf
new file mode 100644
index 0000000..4bd82f9
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/assets/fonts/Exo2.0-Regular.otf differ
diff --git a/dev/sector-dev/sector-frontend/src/assets/fonts/Exo2.0-SemiBold.otf b/dev/sector-dev/sector-frontend/src/assets/fonts/Exo2.0-SemiBold.otf
new file mode 100644
index 0000000..9df61a5
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/assets/fonts/Exo2.0-SemiBold.otf differ
diff --git a/dev/sector-dev/sector-frontend/src/assets/fonts/Gugi-Regular.ttf b/dev/sector-dev/sector-frontend/src/assets/fonts/Gugi-Regular.ttf
new file mode 100644
index 0000000..b500dbf
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/assets/fonts/Gugi-Regular.ttf differ
diff --git a/dev/sector-dev/sector-frontend/src/assets/fonts/Oswald-Light.ttf b/dev/sector-dev/sector-frontend/src/assets/fonts/Oswald-Light.ttf
new file mode 100644
index 0000000..d6e1171
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/assets/fonts/Oswald-Light.ttf differ
diff --git a/dev/sector-dev/sector-frontend/src/assets/fonts/Oswald-Medium.ttf b/dev/sector-dev/sector-frontend/src/assets/fonts/Oswald-Medium.ttf
new file mode 100644
index 0000000..9ef04ac
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/assets/fonts/Oswald-Medium.ttf differ
diff --git a/dev/sector-dev/sector-frontend/src/assets/fonts/Oswald-Regular.ttf b/dev/sector-dev/sector-frontend/src/assets/fonts/Oswald-Regular.ttf
new file mode 100644
index 0000000..5cc7b31
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/assets/fonts/Oswald-Regular.ttf differ
diff --git a/dev/sector-dev/sector-frontend/src/assets/fonts/Quantico-Bold.ttf b/dev/sector-dev/sector-frontend/src/assets/fonts/Quantico-Bold.ttf
new file mode 100644
index 0000000..4157958
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/assets/fonts/Quantico-Bold.ttf differ
diff --git a/dev/sector-dev/sector-frontend/src/assets/fonts/WDXLLubrifontSC-Regular.ttf b/dev/sector-dev/sector-frontend/src/assets/fonts/WDXLLubrifontSC-Regular.ttf
new file mode 100644
index 0000000..ab7340b
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/assets/fonts/WDXLLubrifontSC-Regular.ttf differ
diff --git a/dev/sector-dev/sector-frontend/src/assets/fonts/ZenDots-Regular.ttf b/dev/sector-dev/sector-frontend/src/assets/fonts/ZenDots-Regular.ttf
new file mode 100644
index 0000000..aa4654d
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/assets/fonts/ZenDots-Regular.ttf differ
diff --git a/dev/sector-dev/sector-frontend/src/assets/frames.json b/dev/sector-dev/sector-frontend/src/assets/frames.json
new file mode 100644
index 0000000..2d3c69c
--- /dev/null
+++ b/dev/sector-dev/sector-frontend/src/assets/frames.json
@@ -0,0 +1,351 @@
+{
+ "frames": {
+ "SPR_SciFiMenus_Button_Frame_01": {
+ "frame": {
+ "x": 2,
+ "y": 2,
+ "w": 1005,
+ "h": 236
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 11,
+ "y": 13,
+ "w": 1005,
+ "h": 236
+ },
+ "sourceSize": {
+ "w": 1024,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "SPR_SciFiMenus_Frame_Box_Large_12": {
+ "frame": {
+ "x": 1011,
+ "y": 2,
+ "w": 984,
+ "h": 481
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 20,
+ "y": 17,
+ "w": 984,
+ "h": 481
+ },
+ "sourceSize": {
+ "w": 1024,
+ "h": 512
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "SPR_SciFiMenus_Frame_Box_Large_08": {
+ "frame": {
+ "x": 2,
+ "y": 242,
+ "w": 974,
+ "h": 990
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 25,
+ "y": 19,
+ "w": 974,
+ "h": 990
+ },
+ "sourceSize": {
+ "w": 1024,
+ "h": 1024
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "SPR_SciFiMenus_Frame_Box_Large_12_Background": {
+ "frame": {
+ "x": 1999,
+ "y": 2,
+ "w": 898,
+ "h": 393
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 64,
+ "y": 73,
+ "w": 898,
+ "h": 393
+ },
+ "sourceSize": {
+ "w": 1024,
+ "h": 512
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "SPR_SciFiMenus_Frame_Box_Large_08_Background": {
+ "frame": {
+ "x": 2901,
+ "y": 2,
+ "w": 874,
+ "h": 875
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 73,
+ "y": 73,
+ "w": 874,
+ "h": 875
+ },
+ "sourceSize": {
+ "w": 1024,
+ "h": 1024
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "SPR_SciFiMenus_Background_01": {
+ "frame": {
+ "x": 3779,
+ "y": 2,
+ "w": 256,
+ "h": 256
+ },
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {
+ "x": 0,
+ "y": 0,
+ "w": 256,
+ "h": 256
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "SPR_SciFiMenus_Menu_Item_Selected_20": {
+ "frame": {
+ "x": 3779,
+ "y": 262,
+ "w": 256,
+ "h": 256
+ },
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {
+ "x": 0,
+ "y": 0,
+ "w": 256,
+ "h": 256
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "SPR_SciFiMenus_Ring_Medium_13": {
+ "frame": {
+ "x": 1999,
+ "y": 399,
+ "w": 826,
+ "h": 853
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 97,
+ "y": 85,
+ "w": 826,
+ "h": 853
+ },
+ "sourceSize": {
+ "w": 1024,
+ "h": 1024
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "SPR_SciFiMenus_Menu_Item_Selected_10": {
+ "frame": {
+ "x": 3779,
+ "y": 522,
+ "w": 228,
+ "h": 234
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 14,
+ "y": 10,
+ "w": 228,
+ "h": 234
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "SPR_SciFiMenus_Menu_Item_Selected_19": {
+ "frame": {
+ "x": 3779,
+ "y": 760,
+ "w": 228,
+ "h": 234
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 14,
+ "y": 10,
+ "w": 228,
+ "h": 234
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "SPR_SciFiMenus_Button_Frame_01_Background": {
+ "frame": {
+ "x": 2829,
+ "y": 881,
+ "w": 805,
+ "h": 203
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 112,
+ "y": 28,
+ "w": 805,
+ "h": 203
+ },
+ "sourceSize": {
+ "w": 1024,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "SPR_SciFiMenus_Frame_Box_Small_10_Background": {
+ "frame": {
+ "x": 3638,
+ "y": 881,
+ "w": 127,
+ "h": 123
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 64,
+ "y": 67,
+ "w": 127,
+ "h": 123
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "SPR_SciFiMenus_Menu_Item_Background_07": {
+ "frame": {
+ "x": 3769,
+ "y": 998,
+ "w": 224,
+ "h": 230
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 16,
+ "y": 12,
+ "w": 224,
+ "h": 230
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "SPR_SciFiMenus_Background_Hexagons_01": {
+ "frame": {
+ "x": 2829,
+ "y": 1088,
+ "w": 512,
+ "h": 512
+ },
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {
+ "x": 0,
+ "y": 0,
+ "w": 512,
+ "h": 512
+ },
+ "sourceSize": {
+ "w": 512,
+ "h": 512
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ }
+ },
+ "meta": {
+ "app": "http://github.com/odrick/free-tex-packer-core",
+ "version": "0.3.8",
+ "image": "frames.png",
+ "format": "RGBA8888",
+ "size": {
+ "w": 4037,
+ "h": 1602
+ },
+ "scale": 1
+ }
+}
\ No newline at end of file
diff --git a/dev/sector-dev/sector-frontend/src/assets/frames.png b/dev/sector-dev/sector-frontend/src/assets/frames.png
new file mode 100644
index 0000000..1c36ccd
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/assets/frames.png differ
diff --git a/dev/sector-dev/sector-frontend/src/assets/icons.json b/dev/sector-dev/sector-frontend/src/assets/icons.json
new file mode 100644
index 0000000..8b5c727
--- /dev/null
+++ b/dev/sector-dev/sector-frontend/src/assets/icons.json
@@ -0,0 +1,735 @@
+{
+ "frames": {
+ "ICON_SM_Item_Crystal_03": {
+ "frame": {
+ "x": 2,
+ "y": 2,
+ "w": 208,
+ "h": 242
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 24,
+ "y": 7,
+ "w": 208,
+ "h": 242
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Message_01_Clean": {
+ "frame": {
+ "x": 2,
+ "y": 248,
+ "w": 177,
+ "h": 218
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 39,
+ "y": 17,
+ "w": 177,
+ "h": 218
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFi_Status_Hunger": {
+ "frame": {
+ "x": 2,
+ "y": 470,
+ "w": 210,
+ "h": 212
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 22,
+ "y": 22,
+ "w": 210,
+ "h": 212
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Delete_01_Clean": {
+ "frame": {
+ "x": 183,
+ "y": 248,
+ "w": 166,
+ "h": 209
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 45,
+ "y": 23,
+ "w": 166,
+ "h": 209
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_DNA_01_Clean": {
+ "frame": {
+ "x": 214,
+ "y": 2,
+ "w": 102,
+ "h": 209
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 77,
+ "y": 24,
+ "w": 102,
+ "h": 209
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_Inventory_Notes": {
+ "frame": {
+ "x": 320,
+ "y": 2,
+ "w": 147,
+ "h": 205
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 54,
+ "y": 26,
+ "w": 147,
+ "h": 205
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Refresh_01_Clean": {
+ "frame": {
+ "x": 2,
+ "y": 686,
+ "w": 175,
+ "h": 200
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 41,
+ "y": 28,
+ "w": 175,
+ "h": 200
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Shop_01_Clean": {
+ "frame": {
+ "x": 2,
+ "y": 890,
+ "w": 196,
+ "h": 199
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 22,
+ "y": 29,
+ "w": 196,
+ "h": 199
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Solo_01_Clean": {
+ "frame": {
+ "x": 181,
+ "y": 686,
+ "w": 125,
+ "h": 194
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 65,
+ "y": 31,
+ "w": 125,
+ "h": 194
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Warning_02_Clean": {
+ "frame": {
+ "x": 216,
+ "y": 461,
+ "w": 222,
+ "h": 193
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 17,
+ "y": 31,
+ "w": 222,
+ "h": 193
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Notification_01_Clean": {
+ "frame": {
+ "x": 353,
+ "y": 211,
+ "w": 61,
+ "h": 193
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 97,
+ "y": 32,
+ "w": 61,
+ "h": 193
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFi_Inventory_Minerals": {
+ "frame": {
+ "x": 418,
+ "y": 211,
+ "w": 198,
+ "h": 188
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 29,
+ "y": 34,
+ "w": 198,
+ "h": 188
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Settings_04_Clean": {
+ "frame": {
+ "x": 471,
+ "y": 2,
+ "w": 183,
+ "h": 183
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 36,
+ "y": 36,
+ "w": 183,
+ "h": 183
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Settings_05_Clean": {
+ "frame": {
+ "x": 2,
+ "y": 1093,
+ "w": 183,
+ "h": 183
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 36,
+ "y": 36,
+ "w": 183,
+ "h": 183
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Star_01_Clean": {
+ "frame": {
+ "x": 2,
+ "y": 1280,
+ "w": 180,
+ "h": 181
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 38,
+ "y": 38,
+ "w": 180,
+ "h": 181
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_Inventory_Healing": {
+ "frame": {
+ "x": 2,
+ "y": 1465,
+ "w": 188,
+ "h": 174
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 33,
+ "y": 40,
+ "w": 188,
+ "h": 174
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_Map_Lightning": {
+ "frame": {
+ "x": 186,
+ "y": 1280,
+ "w": 116,
+ "h": 170
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 70,
+ "y": 43,
+ "w": 116,
+ "h": 170
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Settings_03_Clean": {
+ "frame": {
+ "x": 189,
+ "y": 1093,
+ "w": 154,
+ "h": 169
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 51,
+ "y": 44,
+ "w": 154,
+ "h": 169
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Lock_Closed_01_Clean": {
+ "frame": {
+ "x": 202,
+ "y": 884,
+ "w": 132,
+ "h": 164
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 62,
+ "y": 45,
+ "w": 132,
+ "h": 164
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Settings_02_Clean": {
+ "frame": {
+ "x": 338,
+ "y": 884,
+ "w": 180,
+ "h": 155
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 38,
+ "y": 51,
+ "w": 180,
+ "h": 155
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Planet_02_Clean": {
+ "frame": {
+ "x": 310,
+ "y": 658,
+ "w": 216,
+ "h": 149
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 20,
+ "y": 54,
+ "w": 216,
+ "h": 149
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Moon_01_Clean": {
+ "frame": {
+ "x": 522,
+ "y": 403,
+ "w": 214,
+ "h": 147
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 20,
+ "y": 55,
+ "w": 214,
+ "h": 147
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Planet_03_Clean": {
+ "frame": {
+ "x": 620,
+ "y": 189,
+ "w": 214,
+ "h": 147
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 20,
+ "y": 55,
+ "w": 214,
+ "h": 147
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Tick_01_Clean": {
+ "frame": {
+ "x": 740,
+ "y": 2,
+ "w": 191,
+ "h": 144
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 32,
+ "y": 56,
+ "w": 191,
+ "h": 144
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Multiplayer_01_Clean": {
+ "frame": {
+ "x": 935,
+ "y": 2,
+ "w": 218,
+ "h": 141
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 19,
+ "y": 57,
+ "w": 218,
+ "h": 141
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_SciFiMenus_Menu_Cancel_01_Clean": {
+ "frame": {
+ "x": 1157,
+ "y": 2,
+ "w": 140,
+ "h": 140
+ },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {
+ "x": 58,
+ "y": 58,
+ "w": 140,
+ "h": 140
+ },
+ "sourceSize": {
+ "w": 256,
+ "h": 256
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_Deuterium": {
+ "frame": {
+ "x": 1301,
+ "y": 2,
+ "w": 128,
+ "h": 128
+ },
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {
+ "x": 0,
+ "y": 0,
+ "w": 128,
+ "h": 128
+ },
+ "sourceSize": {
+ "w": 128,
+ "h": 128
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_Silizium": {
+ "frame": {
+ "x": 1433,
+ "y": 2,
+ "w": 128,
+ "h": 128
+ },
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {
+ "x": 0,
+ "y": 0,
+ "w": 128,
+ "h": 128
+ },
+ "sourceSize": {
+ "w": 128,
+ "h": 128
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_Titanium": {
+ "frame": {
+ "x": 2,
+ "y": 1643,
+ "w": 128,
+ "h": 128
+ },
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {
+ "x": 0,
+ "y": 0,
+ "w": 128,
+ "h": 128
+ },
+ "sourceSize": {
+ "w": 128,
+ "h": 128
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ },
+ "ICON_Tritium": {
+ "frame": {
+ "x": 2,
+ "y": 1775,
+ "w": 128,
+ "h": 128
+ },
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {
+ "x": 0,
+ "y": 0,
+ "w": 128,
+ "h": 128
+ },
+ "sourceSize": {
+ "w": 128,
+ "h": 128
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 0.5
+ }
+ }
+ },
+ "meta": {
+ "app": "http://github.com/odrick/free-tex-packer-core",
+ "version": "0.3.8",
+ "image": "icons.png",
+ "format": "RGBA8888",
+ "size": {
+ "w": 1563,
+ "h": 1905
+ },
+ "scale": 1
+ }
+}
\ No newline at end of file
diff --git a/dev/sector-dev/sector-frontend/src/assets/icons.png b/dev/sector-dev/sector-frontend/src/assets/icons.png
new file mode 100644
index 0000000..2e84e5f
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/assets/icons.png differ
diff --git a/dev/sector-dev/sector-frontend/src/css/hud.css b/dev/sector-dev/sector-frontend/src/css/hud.css
new file mode 100644
index 0000000..d940f7d
--- /dev/null
+++ b/dev/sector-dev/sector-frontend/src/css/hud.css
@@ -0,0 +1,393 @@
+/* ==========================================================================
+ TOP-BAR (Ganz oben, Account- & Sektor-Infos)
+ ========================================================================== */
+#sector-top-bar {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 30px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0 20px;
+ box-sizing: border-box;
+ font-size: 12px;
+ color: var(--color-text-inactive);
+}
+
+#sector-top-bar a {
+ color: var(--color-text-inactive);
+ text-decoration: none;
+ transition: color 0.2s ease, text-shadow 0.2s ease;
+}
+
+#sector-top-bar a:hover:not(.inactive-link) {
+ color: var(--color-cyan-bright);
+ text-shadow: 0 0 5px var(--color-cyan-bright);
+}
+
+/* Links, die derzeit noch keine Funktion haben, visuell abstufen */
+.inactive-link {
+ cursor: not-allowed;
+ opacity: 0.7;
+}
+
+.sector-name {
+ color: var(--color-text-main);
+ font-weight: bold;
+ letter-spacing: 1px;
+}
+
+/* ==========================================================================
+ RESSOURCEN-HEADER (Unter der Top-Bar)
+ ========================================================================== */
+#resource-header {
+ position: absolute;
+ top: 45px;
+ left: 0;
+ width: 100%;
+ height: 80px;
+ display: flex;
+ align-items: center;
+ padding: 0 20px;
+ box-sizing: border-box;
+ pointer-events: auto;
+ z-index: 20;
+}
+
+/* --- RESSOURCEN MITTIG --- */
+#resource-bar {
+ flex: 1 1 auto;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 15px;
+}
+
+.res-col {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ background: rgba(0, 0, 0, 0.01);
+ padding: 5px 10px;
+ border-radius: 8px;
+ cursor: help;
+ pointer-events: auto;
+ transition: background-color 0.2s ease;
+}
+
+/* Der Rahmen für das Icon */
+.res-box {
+ background: transparent;
+ border: 1px solid var(--color-res-border);
+ border-radius: 6px;
+ width: 45px;
+ height: 45px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ pointer-events: none;
+ transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
+}
+
+/* Hover-Effekt anpassen */
+.res-col:hover .res-box {
+ border-color: var(--color-cyan-bright);
+ box-shadow: 0 0 10px rgba(0, 240, 255, 0.4);
+ background-color: rgba(0, 240, 255, 0.15);
+}
+
+.res-val {
+ font-size: 13px;
+ color: var(--color-text-main);
+ font-weight: bold;
+ text-shadow: 0 0 4px rgba(0, 0, 0, 0.8);
+}
+
+.res-icon {
+ font-size: 24px;
+ color: var(--color-cyan-bright);
+ line-height: 1;
+}
+
+.res-col.premium .res-box {
+ border-color: rgba(255, 0, 255, 0.4);
+}
+.res-col.premium .res-icon {
+ color: #ff00ff;
+}
+.res-col.premium:hover .res-box {
+ border-color: #ff00ff;
+ box-shadow: 0 0 10px rgba(255, 0, 255, 0.5);
+ background-color: rgba(255, 0, 255, 0.15);
+}
+
+.res-divider {
+ /*width: 1px;*/
+ height: 60px;
+ background: var(--color-cyan-dark);
+ opacity: 0.3;
+ margin: 0 5px;
+}
+
+/* ==========================================================================
+ SIDE-MENU (Linke Navigation)
+ ========================================================================== */
+#side-menu-container {
+ position: absolute;
+ top: 160px;
+ left: 20px;
+ width: 210px;
+ bottom: 60px;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ pointer-events: auto;
+ z-index: 20;
+ padding-right: 15px;
+ box-sizing: border-box;
+ scrollbar-gutter: stable;
+}
+
+/* Der einzelne, klickbare Wrapper für einen Menüpunkt */
+.menu-item {
+ position: relative;
+ width: 200px;
+ height: 30px;
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ padding-left: 70px;
+ cursor: pointer;
+ box-sizing: border-box;
+ background: transparent;
+}
+
+/* Der Text innerhalb des Menüpunkts */
+.menu-text {
+ color: var(--color-text-main);
+ font-size: 14px;
+ text-shadow: 0 0 4px rgba(0, 0, 0, 0.8);
+ transition: color 0.2s ease, text-shadow 0.2s ease;
+}
+
+/* Schöner Hover-Effekt: Text leuchtet in unserem Cyan auf */
+.menu-item:hover .menu-text {
+ color: var(--color-text-main);
+ text-shadow: none;
+}
+
+/* ==========================================================================
+ PLANETEN-LIMIT (Zähler über der rechten Sidebar)
+ ========================================================================== */
+#planet-limit-container {
+ position: absolute;
+ top: 100px;
+ right: 20px;
+ width: 215px;
+ height: 45px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 20;
+ /* Da es ein reines Info-Element ist, deaktivieren wir die Klicks */
+ pointer-events: none;
+}
+
+.planet-limit-text {
+ color: var(--color-text-main);
+ font-size: 13px;
+ font-weight: bold;
+ text-shadow: 0 0 4px rgba(0, 0, 0, 0.8);
+ /* Ein kleiner Abstand zwischen Buchstaben (Letter-Spacing) lässt
+ Sci-Fi UIs oft etwas hochwertiger wirken */
+ letter-spacing: 0.5px;
+}
+
+#val_planet_count {
+ color: var(--color-cyan-bright); /* Aktuelle Planetenanzahl hervorheben */
+}
+
+#val_planet_max {
+ color: var(--color-text-inactive); /* Das Limit etwas abdunkeln */
+}
+
+/* ==========================================================================
+ RIGHT-SIDEBAR (Rechte Planeten-Navigation)
+ ========================================================================== */
+#right-sidebar-container {
+ position: absolute;
+ top: 160px;
+ right: 20px;
+ width: 215px;
+
+ bottom: 60px;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ display: flex;
+ flex-direction: column;
+ gap: 25px;
+ pointer-events: auto;
+ z-index: 20;
+ padding-left: 5px;
+ padding-right: 30px;
+
+ box-sizing: border-box;
+ scrollbar-gutter: stable;
+}
+
+/* ==========================================================================
+ PLANETEN-EINTRÄGE (70/30 HTML-Struktur)
+ ========================================================================== */
+.planet-item {
+ position: relative;
+ width: 200px;
+ height: 90px;
+ flex-shrink: 0;
+ /* Wir nutzen jetzt eine Reihe (Row), um die Bereiche nebeneinander zu legen */
+ display: flex;
+ flex-direction: row;
+ align-items: stretch; /* Beide Bereiche füllen die volle Höhe von 90px aus */
+ justify-content: flex-start;
+ box-sizing: border-box;
+ background: transparent;
+}
+
+/* Der 70% Hauptbereich für den Planeten */
+.planet-click-area {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ box-sizing: border-box;
+ cursor: pointer;
+}
+
+/* Der 30% Nebenbereich für den Mond */
+.moon-click-area {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ /* Standardmäßig kein Pointer, da die Box leer sein könnte */
+ cursor: default;
+}
+
+/* Trennlinie und Klick-Cursor NUR anzeigen, wenn auch wirklich ein Mond da ist */
+.moon-click-area.has-moon {
+ cursor: pointer;
+ border-left: 1px solid rgba(0, 240, 255, 0.1);
+}
+
+/* HTML-Anker für PixiJS */
+.planet-icon-anchor {
+ width: 30px;
+ height: 30px;
+}
+
+.moon-icon-anchor {
+ width: 16px;
+ height: 16px;
+ z-index: 25;
+ border-radius: 50%;
+ transition: transform 0.2s ease;
+}
+
+/* Hover-Effekte skalieren das Icon, wenn der Spieler im 30%-Feld ist */
+.moon-click-area:hover .moon-icon-anchor {
+ transform: scale(1.25);
+}
+
+.planet-text-wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 2px;
+}
+
+.planet-name {
+ color: var(--color-text-main);
+ font-size: 14px;
+ font-weight: bold;
+ text-shadow: 0 0 4px rgba(0, 0, 0, 0.8);
+ transition: color 0.2s ease, text-shadow 0.2s ease;
+}
+
+.planet-coords {
+ color: var(--color-text-inactive);
+ font-size: 11px;
+ text-shadow: 0 0 4px rgba(0, 0, 0, 0.8);
+ transition: color 0.2s ease;
+}
+
+/* ==========================================================================
+ SCROLLBAR-STYLING FÜR WEBKIT (Chrome, Safari, Edge)
+ ========================================================================== */
+/* Definiert die Breite der Scrollbar für beide Menüs */
+#side-menu-container::-webkit-scrollbar,
+#right-sidebar-container::-webkit-scrollbar {
+ width: 6px;
+}
+
+/* Die sichtbare "Spur" (Track), auf der der Scrollbalken läuft */
+#side-menu-container::-webkit-scrollbar-track,
+#right-sidebar-container::-webkit-scrollbar-track {
+ background: rgba(0, 0, 0, 0.2);
+ border-radius: 4px;
+}
+
+/* Der sichtbare, greifbare Teil des Scrollbalkens (Thumb) */
+#side-menu-container::-webkit-scrollbar-thumb,
+#right-sidebar-container::-webkit-scrollbar-thumb {
+ background: var(--color-cyan-dark);
+ border-radius: 4px;
+}
+
+/* Hover-Effekt: Der Anfasser leuchtet im helleren Cyan auf */
+#side-menu-container::-webkit-scrollbar-thumb:hover,
+#right-sidebar-container::-webkit-scrollbar-thumb:hover {
+ background: var(--color-cyan-bright);
+}
+
+/* ==========================================================================
+ FOOTER
+ ========================================================================== */
+#game-footer {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 40px;
+ background: var(--color-panel-bg);
+ border-top: 1px solid var(--color-cyan-dark);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0 40px;
+ box-sizing: border-box;
+ color: var(--color-text-inactive);
+ font-size: 12px;
+}
+
+.footer-left { flex: 1; text-align: left; }
+.footer-center { flex: 1; text-align: center; }
+.footer-right { flex: 1; text-align: right; }
+
+#game-footer a {
+ color: var(--color-text-inactive);
+ text-decoration: none;
+ transition: color 0.2s ease, text-shadow 0.2s ease;
+}
+
+#game-footer a:hover {
+ color: var(--color-cyan-bright);
+ text-shadow: 0 0 5px var(--color-cyan-bright);
+}
+
diff --git a/dev/sector-dev/sector-frontend/src/css/layout.css b/dev/sector-dev/sector-frontend/src/css/layout.css
new file mode 100644
index 0000000..a13169f
--- /dev/null
+++ b/dev/sector-dev/sector-frontend/src/css/layout.css
@@ -0,0 +1,65 @@
+/*
+ * Beinhaltet das absolute Grundgerüst der Sektor-Seite.
+ * Reset-Regeln und die Positionierung der Haupt-Container (Z-Index Ebenen).
+ * Dies stellt sicher, dass das UI immer über dem PixiJS-Canvas liegt.
+ */
+
+body, html {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ background-color: var(--color-bg-page);
+ font-family: var(--font-ui);
+ -webkit-text-size-adjust: 100%;
+ text-size-adjust: 100%;
+ position: fixed;
+ overscroll-behavior: none;
+}
+
+#game-wrapper {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
+/* Der Container für den PixiJS-Hintergrund und die Spielwelt */
+#pixi-container {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 0;
+}
+
+/* Die UI-Ebene liegt über dem Pixi-Canvas */
+#ui-layer {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 10;
+ pointer-events: none;
+}
+
+/* * Alle interaktiven HUD-Elemente müssen pointer-events wieder aktivieren,
+ * sonst können wir keine Links klicken oder Hover-Effekte auslösen.
+ */
+#sector-top-bar,
+#resource-header,
+#game-footer {
+ pointer-events: auto;
+}
+
+/* Verhindert den unsichtbaren Scroll-Abstand (Baseline-Gap) bei Canvas-Elementen */
+#pixi-container canvas {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/dev/sector-dev/sector-frontend/src/css/variables.css b/dev/sector-dev/sector-frontend/src/css/variables.css
new file mode 100644
index 0000000..5e698d2
--- /dev/null
+++ b/dev/sector-dev/sector-frontend/src/css/variables.css
@@ -0,0 +1,34 @@
+/*
+ * Globale Variablen für das Sektor-Frontend.
+ * Wir nutzen eine temporäre Standard-Schriftart für das UI und die bekannte Logo-Font.
+ */
+
+@font-face {
+ font-family: 'UI Font';
+ src: url('../assets/fonts/Oswald-Regular.ttf') format('truetype');
+ font-weight: normal;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Logo Font';
+ src: url('../assets/fonts/Quantico-Bold.ttf') format('truetype');
+ font-weight: normal;
+ font-style: normal;
+}
+
+:root {
+ --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ --font-ui: 'UI Font', sans-serif;
+ --font-logo: 'Logo Font', sans-serif;
+ --color-bg-page: #000000;
+ --color-panel-bg: rgba(11, 25, 44, 0.85);
+ --color-text-main: #ffffff;
+ --color-text-inactive: rgba(255, 255, 255, 0.5);
+ --color-cyan-bright: #00f0ff;
+ --color-cyan-dark: #00a0aa;
+ --color-green-bright: #00ff00;
+ --color-error: #ff4444;
+ --color-res-border: rgba(0, 240, 255, 0.3);
+ --color-res-bg: rgba(0, 0, 0, 0.6);
+}
\ 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
new file mode 100644
index 0000000..2239010
--- /dev/null
+++ b/dev/sector-dev/sector-frontend/src/main.js
@@ -0,0 +1,1066 @@
+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;
+
+// ============================================================================
+// 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();
+
+ await app.init({
+ resizeTo: window,
+ backgroundAlpha: 0,
+ antialias: true,
+ resolution: window.devicePixelRatio || 1,
+ autoDensity: true
+ });
+
+ document.getElementById('pixi-container').appendChild(app.canvas);
+
+ // --- Asset Loading ---
+ try {
+ console.log("[PixiJS] Lade Assets und Schriftarten...");
+ await document.fonts.load('16px "Logo Font"');
+ await document.fonts.load('16px "UI Font"');
+
+ // Wir laden nun auch explizit den Hintergrund für den Sektor
+ const assetsToLoad = {
+ bg: 'src/assets/backgrounds/Background_D.png',
+ icons: 'src/assets/icons.json',
+ frames: 'src/assets/frames.json'
+ };
+
+ const bgTexture = await Assets.load(assetsToLoad.bg);
+ iconSheet = await Assets.load(assetsToLoad.icons);
+ frameSheet = await Assets.load(assetsToLoad.frames);
+
+ // --- Hintergrund rendern ---
+ background = new Sprite(bgTexture);
+ app.stage.addChild(background);
+
+ // --- 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 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);
+
+ // --- 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();
+
+ // ====================================================================
+ // 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;
+ }
+ }
+ });
+
+ // ====================================================================
+ // 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);
+}
+
+initSector();
\ No newline at end of file
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Background_01.png b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Background_01.png
new file mode 100644
index 0000000..89d0139
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Background_01.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Background_Hexagons_01.png b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Background_Hexagons_01.png
new file mode 100644
index 0000000..ab03f8c
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Background_Hexagons_01.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Button_Frame_01.png b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Button_Frame_01.png
new file mode 100644
index 0000000..9aa8a3f
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Button_Frame_01.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Button_Frame_01_Background.png b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Button_Frame_01_Background.png
new file mode 100644
index 0000000..66c877c
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Button_Frame_01_Background.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Frame_Box_Large_08.png b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Frame_Box_Large_08.png
new file mode 100644
index 0000000..9a031ab
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Frame_Box_Large_08.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Frame_Box_Large_08_Background.png b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Frame_Box_Large_08_Background.png
new file mode 100644
index 0000000..c883a1b
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Frame_Box_Large_08_Background.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Frame_Box_Large_12.png b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Frame_Box_Large_12.png
new file mode 100644
index 0000000..ce8c5b1
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Frame_Box_Large_12.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Frame_Box_Large_12_Background.png b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Frame_Box_Large_12_Background.png
new file mode 100644
index 0000000..1cf76b0
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Frame_Box_Large_12_Background.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Frame_Box_Small_10_Background.png b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Frame_Box_Small_10_Background.png
new file mode 100644
index 0000000..12f0bcd
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Frame_Box_Small_10_Background.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Menu_Item_Background_07.png b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Menu_Item_Background_07.png
new file mode 100644
index 0000000..065ba2f
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Menu_Item_Background_07.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Menu_Item_Selected_10.png b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Menu_Item_Selected_10.png
new file mode 100644
index 0000000..37d4e66
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Menu_Item_Selected_10.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Menu_Item_Selected_19.png b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Menu_Item_Selected_19.png
new file mode 100644
index 0000000..1b84ba9
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Menu_Item_Selected_19.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Menu_Item_Selected_20.png b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Menu_Item_Selected_20.png
new file mode 100644
index 0000000..016000a
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Menu_Item_Selected_20.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Ring_Medium_13.png b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Ring_Medium_13.png
new file mode 100644
index 0000000..87f929a
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/frames/SPR_SciFiMenus_Ring_Medium_13.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Deuterium.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Deuterium.png
new file mode 100644
index 0000000..b6a8d19
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Deuterium.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Inventory_Healing.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Inventory_Healing.png
new file mode 100644
index 0000000..3769171
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Inventory_Healing.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Inventory_Notes.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Inventory_Notes.png
new file mode 100644
index 0000000..f8af518
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Inventory_Notes.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Map_Lightning.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Map_Lightning.png
new file mode 100644
index 0000000..0d9bd7e
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Map_Lightning.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SM_Item_Crystal_03.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SM_Item_Crystal_03.png
new file mode 100644
index 0000000..5f3ef93
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SM_Item_Crystal_03.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Cancel_01_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Cancel_01_Clean.png
new file mode 100644
index 0000000..79a135b
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Cancel_01_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_DNA_01_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_DNA_01_Clean.png
new file mode 100644
index 0000000..e93b183
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_DNA_01_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Delete_01_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Delete_01_Clean.png
new file mode 100644
index 0000000..440eeda
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Delete_01_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Lock_Closed_01_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Lock_Closed_01_Clean.png
new file mode 100644
index 0000000..8d2fabe
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Lock_Closed_01_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Message_01_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Message_01_Clean.png
new file mode 100644
index 0000000..5a29168
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Message_01_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Moon_01_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Moon_01_Clean.png
new file mode 100644
index 0000000..d9248a8
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Moon_01_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Multiplayer_01_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Multiplayer_01_Clean.png
new file mode 100644
index 0000000..e57513b
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Multiplayer_01_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Notification_01_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Notification_01_Clean.png
new file mode 100644
index 0000000..94d8beb
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Notification_01_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Planet_02_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Planet_02_Clean.png
new file mode 100644
index 0000000..52423a6
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Planet_02_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Planet_03_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Planet_03_Clean.png
new file mode 100644
index 0000000..9a56b9f
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Planet_03_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Refresh_01_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Refresh_01_Clean.png
new file mode 100644
index 0000000..86ccb9f
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Refresh_01_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Settings_02_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Settings_02_Clean.png
new file mode 100644
index 0000000..a294a3e
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Settings_02_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Settings_03_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Settings_03_Clean.png
new file mode 100644
index 0000000..5e2f906
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Settings_03_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Settings_04_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Settings_04_Clean.png
new file mode 100644
index 0000000..9c02e97
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Settings_04_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Settings_05_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Settings_05_Clean.png
new file mode 100644
index 0000000..becb79b
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Settings_05_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Shop_01_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Shop_01_Clean.png
new file mode 100644
index 0000000..e946d12
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Shop_01_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Solo_01_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Solo_01_Clean.png
new file mode 100644
index 0000000..05899a9
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Solo_01_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Star_01_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Star_01_Clean.png
new file mode 100644
index 0000000..cbf643e
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Star_01_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Tick_01_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Tick_01_Clean.png
new file mode 100644
index 0000000..d37a03f
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Tick_01_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Warning_02_Clean.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Warning_02_Clean.png
new file mode 100644
index 0000000..0087b6d
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFiMenus_Menu_Warning_02_Clean.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFi_Inventory_Minerals.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFi_Inventory_Minerals.png
new file mode 100644
index 0000000..eed0ff8
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFi_Inventory_Minerals.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFi_Status_Hunger.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFi_Status_Hunger.png
new file mode 100644
index 0000000..4f1f7b2
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_SciFi_Status_Hunger.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Silizium.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Silizium.png
new file mode 100644
index 0000000..959ffa0
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Silizium.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Titanium.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Titanium.png
new file mode 100644
index 0000000..1c0fb2f
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Titanium.png differ
diff --git a/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Tritium.png b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Tritium.png
new file mode 100644
index 0000000..9ed13ca
Binary files /dev/null and b/dev/sector-dev/sector-frontend/src/raw-assets/icons/ICON_Tritium.png differ
diff --git a/dev/sector-dev/sector-frontend/style.css b/dev/sector-dev/sector-frontend/style.css
new file mode 100644
index 0000000..88e661c
--- /dev/null
+++ b/dev/sector-dev/sector-frontend/style.css
@@ -0,0 +1,3 @@
+@import url('./src/css/layout.css');
+@import url('./src/css/variables.css');
+@import url('./src/css/hud.css');
diff --git a/dev/sector-dev/sector-frontend/vite.config.js b/dev/sector-dev/sector-frontend/vite.config.js
new file mode 100644
index 0000000..f8ef544
--- /dev/null
+++ b/dev/sector-dev/sector-frontend/vite.config.js
@@ -0,0 +1,31 @@
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ server: {
+ // Erlaubt Vite, Verbindungen über diese spezifische Domain anzunehmen.
+ // Verhindert Blockaden, wenn wir hinter dem Nginx Proxy Manager arbeiten.
+ allowedHosts: [
+ 'sector.void-genesis.de',
+ 'dev.void-genesis.de'
+ ],
+ // 'host: true' stellt sicher, dass Vite auf allen Netzwerk-Interfaces (0.0.0.0) lauscht.
+ // Das ist zwingend notwendig, damit wir aus dem Docker-Container heraus zugreifen können.
+ host: true,
+ },
+ build: {
+ // ----------------------------------------------------------------------
+ // EULA-Konformität: Base64-Zwang für alle Assets
+ // ----------------------------------------------------------------------
+ // Wir setzen das Limit für das Inlining von Assets auf einen extrem hohen Wert.
+ // Dadurch zwingen wir Vite dazu, sämtliche importierten Grafiken (PixiJS Assets)
+ // als Base64-Strings direkt in die generierten JS/CSS-Dateien einzubetten.
+ // So liegen nach dem Build keine rohen Bilddateien mehr auf dem Server.
+ // ----------------------------------------------------------------------
+ assetsInlineLimit: 100000000,
+
+ // Wir deaktivieren das Generieren von Source Maps für den produktiven Build,
+ // da diese durch die Base64-Strings gigantisch groß werden würden und wir
+ // unsere Code-Struktur nicht nach außen freigeben wollen.
+ sourcemap: false,
+ }
+});
\ No newline at end of file