Upload via GUI (22 Dateien)

This commit is contained in:
Git Manager GUI
2026-08-01 21:22:02 +02:00
parent 7a6b31fb11
commit 58b5f8d785
7 changed files with 656 additions and 233 deletions

View File

@@ -2,16 +2,24 @@
"name": "aeromc",
"version": "0.37.1",
"description": "AeroMC Instanz-basierter Minecraft-Launcher (à la MultiMC/Prism)",
"homepage": "https://M_Viper.de",
"main": "src/main.js",
"author": "AeroMC",
"author": "M_Viper",
"repository": {
"type": "git",
"url": "https://git.viper.ipv64.net"
},
"license": "MIT",
"scripts": {
"start": "electron .",
"build": "electron-builder --win --x64"
"build": "electron-builder --win nsis --x64 && node -e \"const fs=require('fs');const path=require('path');['dist/builder-debug.yml','dist/builder-effective-config.yaml','dist/win-unpacked'].forEach((target)=>fs.rmSync(target,{recursive:true,force:true}));if(fs.existsSync('dist')){const source=path.join('dist','AeroMC.exe');const target=path.join('dist','AeroMC-Setup.exe');if(fs.existsSync(target))fs.rmSync(target,{force:true});if(fs.existsSync(source))fs.renameSync(source,target);for(const name of fs.readdirSync('dist')){if((name.endsWith('.exe')&&name!=='AeroMC-Setup.exe')||name.endsWith('.blockmap')){fs.rmSync(path.join('dist',name),{force:true})}}}\"",
"build:portable": "electron-builder --win portable --x64 && node -e \"const fs=require('fs');const path=require('path');['dist/builder-debug.yml','dist/builder-effective-config.yaml','dist/win-unpacked'].forEach((target)=>fs.rmSync(target,{recursive:true,force:true}));if(fs.existsSync('dist')){for(const name of fs.readdirSync('dist')){if((name.endsWith('.exe')&&name!=='AeroMC.exe')||name.endsWith('.blockmap')){fs.rmSync(path.join('dist',name),{force:true})}}}\""
},
"build": {
"appId": "com.aeromc.launcher",
"productName": "AeroMC",
"artifactName": "AeroMC.${ext}",
"compression": "store",
"files": [
"src/**/*",
"package.json"

BIN
src/assets/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -34,6 +34,15 @@ const WANTED = [
['iron', 'item/iron_ingot.png', 'Eisenbarren'],
['redstone', 'item/redstone.png', 'Redstone'],
['coal', 'item/coal.png', 'Kohle'],
['potion', 'item/potion.png', 'Trank'],
['paper', 'item/paper.png', 'Papier'],
['feather', 'item/feather.png', 'Feder'],
['slimeball', 'item/slime_ball.png', 'Schleimball'],
['magma', 'item/magma_cream.png', 'Magmacreme'],
['quartz', 'item/quartz.png', 'Netherquarz'],
['brick', 'item/brick.png', 'Ziegel'],
['cookie', 'item/cookie.png', 'Keks'],
['snowball', 'item/snowball.png', 'Schneeball'],
['sword', 'item/diamond_sword.png', 'Diamantschwert'],
['pickaxe', 'item/diamond_pickaxe.png', 'Diamantspitzhacke'],
['axe', 'item/diamond_axe.png', 'Diamantaxt'],
@@ -51,6 +60,8 @@ const WANTED = [
['cake', 'item/cake.png', 'Kuchen'],
['bucket', 'item/water_bucket.png', 'Wassereimer'],
['lava', 'item/lava_bucket.png', 'Lavaeimer'],
['chest', 'item/chest.png', 'Truhe'],
['creeper', 'item/creeper_head.png', 'Creeper-Kopf'],
['tnt_item', 'item/gunpowder.png', 'Schwarzpulver'],
];

View File

@@ -184,199 +184,263 @@
<button class="modal-close" data-close></button>
</div>
<div class="modal-body settings-body">
<nav class="settings-nav">
<button class="snav on" data-pane="pane-launcher">Launcher</button>
<button class="snav" data-pane="pane-minecraft">Minecraft</button>
<button class="snav" data-pane="pane-java">Java</button>
<button class="snav" data-pane="pane-console">Konsole</button>
<button class="snav" data-pane="pane-commands">Befehle</button>
<button class="snav" data-pane="pane-external">Externe Programme</button>
<button class="snav" data-pane="pane-proxy">Proxy</button>
<nav class="settings-nav" aria-label="Einstellungsbereiche">
<div class="settings-nav-label">Bereiche</div>
<button class="snav on" data-pane="pane-general">Allgemein</button>
<button class="snav" data-pane="pane-appearance">Darstellung</button>
<button class="snav" data-pane="pane-minecraft">Spiel</button>
<button class="snav" data-pane="pane-start">Start &amp; Java</button>
<button class="snav" data-pane="pane-network">Netzwerk</button>
<button class="snav" data-pane="pane-accounts">Konten</button>
<button class="snav" data-pane="pane-info">Info</button>
</nav>
<div class="settings-content">
<div id="pane-launcher" class="spane">
<h4>Launcher</h4>
<label class="field">
<span>Design</span>
<select id="s-theme">
<option value="dark">Dunkel</option>
<option value="light">Hell</option>
</select>
</label>
<label class="field">
<span>Instanz-Verzeichnis</span>
<div class="path-row">
<input id="s-instances-dir" type="text" readonly />
<button id="s-pick-dir" class="btn">Wählen…</button>
</div>
<small class="hint">Hier werden alle Instanzen als Unterordner (mit ihrem Namen) gespeichert.</small>
</label>
<div id="pane-general" class="spane">
<div class="settings-hero">
<h4>Allgemein</h4>
<p>Globale Launcher-Optionen, Instanzverwaltung und sichtbare Bereiche.</p>
</div>
<div class="settings-grid">
<section class="settings-card">
<h5>Oberfläche</h5>
<label class="field">
<span>Design</span>
<select id="s-theme">
<option value="dark">Dunkel</option>
<option value="light">Hell</option>
</select>
</label>
<label class="field">
<span>Instanzen sortieren</span>
<select id="s-sortby">
<option value="lastPlayed">Nach letztem Start</option>
<option value="name">Nach Name</option>
</select>
</label>
</section>
<label class="field">
<span>Instanzen übernehmen</span>
<button id="btn-mmc" class="btn">Aus MultiMC importieren…</button>
<small class="hint">Übernimmt Instanzen samt Version, Loader, Gruppe und Spieldaten du gibst nur den MultiMC-Ordner an.</small>
</label>
<section class="settings-card settings-card-wide">
<h5>Instanzen</h5>
<label class="field">
<span>Instanz-Verzeichnis</span>
<div class="path-row">
<input id="s-instances-dir" type="text" readonly />
<button id="s-pick-dir" class="btn">Wählen…</button>
</div>
<small class="hint">Hier werden alle Instanzen als Unterordner mit ihrem Namen gespeichert.</small>
</label>
<label class="field">
<span>Instanzen sortieren</span>
<select id="s-sortby">
<option value="lastPlayed">Nach letztem Start</option>
<option value="name">Nach Name</option>
</select>
</label>
<label class="field">
<span>Instanzen übernehmen</span>
<button id="btn-mmc" class="btn">Aus MultiMC importieren…</button>
<small class="hint">Übernimmt Instanzen samt Version, Loader, Gruppe und Spieldaten aus einem MultiMC-Ordner.</small>
</label>
</section>
<label class="checkbox-field">
<input id="s-iso3d" type="checkbox" /> <span>Block-Symbole räumlich darstellen (3D)</span>
</label>
<section class="settings-card">
<h5>Bereiche</h5>
<label class="checkbox-field">
<input id="s-bedrock" type="checkbox" /> <span>Bedrock-Bereich anzeigen</span>
</label>
<small id="s-bedrock-hint" class="hint">Wird automatisch ausgeblendet, wenn Bedrock nicht installiert ist.</small>
</section>
</div>
</div>
<label class="checkbox-field">
<input id="s-bedrock" type="checkbox" /> <span>Bedrock-Bereich anzeigen</span>
</label>
<small id="s-bedrock-hint" class="hint">Wird automatisch ausgeblendet, wenn Bedrock nicht installiert ist.</small>
<div id="pane-appearance" class="spane hidden">
<div class="settings-hero">
<h4>Darstellung</h4>
<p>Optik der Oberfläche und visuelle Extras für die Instanzansicht.</p>
</div>
<div class="settings-grid">
<section class="settings-card">
<h5>Icons</h5>
<label class="checkbox-field">
<input id="s-iso3d" type="checkbox" /> <span>Block-Symbole räumlich darstellen (3D)</span>
</label>
</section>
<label class="field">
<span>Skin-Hintergrund <small class="opt">(optional)</small></span>
<select id="s-bgskin"><option value="">Aus</option></select>
<small class="hint">Zeigt den Minecraft-Skin des gewählten Kontos als Hintergrund im Instanzbereich.</small>
</label>
<label class="field">
<span>Pose</span>
<select id="s-bgskin-pose">
<option value="walking">Laufend</option>
<option value="standing">Stehend</option>
<option value="legends">Heldenhaft</option>
<option value="bedwars">Kampfhaltung</option>
<option value="hoverboard">Schwebend</option>
<option value="sitting">Sitzend</option>
<option value="spooky">Unheimlich</option>
</select>
</label>
<label class="field">
<span>Deckkraft: <span id="s-bgskin-val">55</span> %</span>
<input id="s-bgskin-opacity" type="range" min="10" max="100" step="5" />
</label>
<section class="settings-card settings-card-wide">
<h5>Skin-Hintergrund</h5>
<label class="field">
<span>Quelle <small class="opt">(optional)</small></span>
<select id="s-bgskin"><option value="">Aus</option></select>
<small class="hint">Zeigt den Minecraft-Skin des gewählten Kontos als Hintergrund im Instanzbereich.</small>
</label>
<div class="field-row">
<label class="field">
<span>Pose</span>
<select id="s-bgskin-pose">
<option value="walking">Laufend</option>
<option value="standing">Stehend</option>
<option value="legends">Heldenhaft</option>
<option value="bedwars">Kampfhaltung</option>
<option value="hoverboard">Schwebend</option>
<option value="sitting">Sitzend</option>
<option value="spooky">Unheimlich</option>
</select>
</label>
<label class="field">
<span>Deckkraft: <span id="s-bgskin-val">55</span> %</span>
<input id="s-bgskin-opacity" type="range" min="10" max="100" step="5" />
</label>
</div>
</section>
</div>
</div>
<div id="pane-minecraft" class="spane hidden">
<h4>Minecraft</h4>
<div class="field-row">
<label class="field">
<span>Standard-RAM min. (MB)</span>
<input id="s-min-mem" type="number" min="512" step="256" />
</label>
<label class="field">
<span>Standard-RAM max. (MB)</span>
<input id="s-max-mem" type="number" min="512" step="256" />
</label>
<div class="settings-hero">
<h4>Spiel</h4>
<p>Standardwerte für neue und bestehende Instanzen ohne eigene Überschreibung.</p>
</div>
<small class="hint">Gilt für alle Instanzen ohne eigene RAM-Einstellung.</small>
<div class="settings-grid">
<section class="settings-card">
<h5>Speicher</h5>
<div class="field-row">
<label class="field">
<span>Standard-RAM min. (MB)</span>
<input id="s-min-mem" type="number" min="512" step="256" />
</label>
<label class="field">
<span>Standard-RAM max. (MB)</span>
<input id="s-max-mem" type="number" min="512" step="256" />
</label>
</div>
<small class="hint">Gilt für alle Instanzen ohne eigene RAM-Einstellung.</small>
</section>
<h4 style="margin-top:18px">Fenstergröße</h4>
<label class="checkbox-field">
<input id="s-mc-max" type="checkbox" /> <span>Minecraft maximiert starten</span>
</label>
<div class="field-row">
<label class="field"><span>Fensterbreite</span><input id="s-mc-width" type="number" min="320" step="10" /></label>
<label class="field"><span>Fensterhöhe</span><input id="s-mc-height" type="number" min="240" step="10" /></label>
<section class="settings-card">
<h5>Fenster</h5>
<label class="checkbox-field">
<input id="s-mc-max" type="checkbox" /> <span>Minecraft maximiert starten</span>
</label>
<div class="field-row">
<label class="field"><span>Fensterbreite</span><input id="s-mc-width" type="number" min="320" step="10" /></label>
<label class="field"><span>Fensterhöhe</span><input id="s-mc-height" type="number" min="240" step="10" /></label>
</div>
</section>
</div>
</div>
<div id="pane-java" class="spane hidden">
<h4>Java</h4>
<label class="field">
<span>Java (java.exe)</span>
<div class="path-row">
<input id="s-java" type="text" placeholder="leer = java aus PATH" />
<button id="s-pick-java" class="btn">Wählen…</button>
</div>
<div class="path-row">
<select id="s-java-detected"><option value=""> gefundene Java-Installationen </option></select>
<button id="s-scan-java" class="btn">Erkennen</button>
</div>
<small id="s-java-status" class="hint"></small>
</label>
</div>
<div id="pane-console" class="spane hidden">
<h4>Konsole</h4>
<label class="checkbox-field">
<input id="s-console-launch" type="checkbox" /> <span>Konsole anzeigen, während das Spiel läuft</span>
</label>
<label class="checkbox-field">
<input id="s-console-autoclose" type="checkbox" /> <span>Konsole automatisch schließen, nachdem das Spiel beendet wurde</span>
</label>
<label class="checkbox-field">
<input id="s-console-crash" type="checkbox" /> <span>Konsole anzeigen, wenn das Spiel abstürzt</span>
</label>
<label class="field">
<span>Historie-Limit (Zeilen)</span>
<input id="s-console-lines" type="number" min="1000" step="1000" />
</label>
</div>
<div id="pane-commands" class="spane hidden">
<h4>Benutzerdefinierte Befehle</h4>
<label class="field">
<span>Pre-Launch-Befehl (global)</span>
<input id="s-global-pre" type="text" placeholder="läuft vor jedem Spielstart" />
</label>
<label class="field">
<span>Post-Exit-Befehl (global)</span>
<input id="s-global-post" type="text" placeholder="läuft nach jedem Beenden" />
</label>
<small class="hint">Gilt für alle Instanzen. Eine Instanz mit eigenem Befehl (im Bearbeiten-Dialog) hat Vorrang.</small>
</div>
<div id="pane-external" class="spane hidden">
<h4>Externe Programme</h4>
<label class="field">
<span>Wrapper-Befehl</span>
<input id="s-wrapper" type="text" placeholder="z. B. prime-run (leer = kein Wrapper)" />
</label>
<small class="hint">Startet Java über dieses Programm (z. B. GPU-Auswahl). Java-Pfad und Argumente werden automatisch angehängt.</small>
</div>
<div id="pane-proxy" class="spane hidden">
<h4>Proxy</h4>
<label class="checkbox-field">
<input id="s-proxy-enabled" type="checkbox" /> <span>HTTP-Proxy für Downloads verwenden</span>
</label>
<div class="field-row">
<label class="field"><span>Host</span><input id="s-proxy-host" type="text" placeholder="proxy.firma.de" /></label>
<label class="field field-port"><span>Port</span><input id="s-proxy-port" type="text" placeholder="8080" /></label>
<div id="pane-start" class="spane hidden">
<div class="settings-hero">
<h4>Start &amp; Java</h4>
<p>Runtime-Auswahl, Startverhalten und globale Befehle für alle Instanzen.</p>
</div>
<div class="field-row">
<label class="field"><span>Benutzer <small class="opt">(optional)</small></span><input id="s-proxy-user" type="text" /></label>
<label class="field"><span>Passwort <small class="opt">(optional)</small></span><input id="s-proxy-pass" type="password" /></label>
<div class="settings-grid settings-grid-stack">
<section class="settings-card settings-card-wide">
<h5>Java</h5>
<label class="field">
<span>Java (java.exe)</span>
<div class="path-row">
<input id="s-java" type="text" placeholder="leer = java aus PATH" />
<button id="s-pick-java" class="btn">Wählen…</button>
</div>
<div class="path-row">
<select id="s-java-detected"><option value=""> gefundene Java-Installationen </option></select>
<button id="s-scan-java" class="btn">Erkennen</button>
</div>
<small id="s-java-status" class="hint"></small>
</label>
</section>
<section class="settings-card">
<h5>Konsole</h5>
<label class="checkbox-field">
<input id="s-console-launch" type="checkbox" /> <span>Konsole anzeigen, während das Spiel läuft</span>
</label>
<label class="checkbox-field">
<input id="s-console-autoclose" type="checkbox" /> <span>Konsole automatisch schließen, nachdem das Spiel beendet wurde</span>
</label>
<label class="checkbox-field">
<input id="s-console-crash" type="checkbox" /> <span>Konsole anzeigen, wenn das Spiel abstürzt</span>
</label>
<label class="field">
<span>Historie-Limit (Zeilen)</span>
<input id="s-console-lines" type="number" min="1000" step="1000" />
</label>
</section>
<section class="settings-card">
<h5>Globale Befehle</h5>
<label class="field">
<span>Pre-Launch-Befehl</span>
<input id="s-global-pre" type="text" placeholder="läuft vor jedem Spielstart" />
</label>
<label class="field">
<span>Post-Exit-Befehl</span>
<input id="s-global-post" type="text" placeholder="läuft nach jedem Beenden" />
</label>
<small class="hint">Instanzspezifische Befehle aus dem Bearbeiten-Dialog haben Vorrang.</small>
</section>
<section class="settings-card settings-card-wide">
<h5>Externe Programme</h5>
<label class="field">
<span>Wrapper-Befehl</span>
<input id="s-wrapper" type="text" placeholder="z. B. prime-run (leer = kein Wrapper)" />
</label>
<small class="hint">Startet Java über dieses Programm. Java-Pfad und Argumente werden automatisch angehängt.</small>
</section>
</div>
</div>
<div id="pane-network" class="spane hidden">
<div class="settings-hero">
<h4>Netzwerk</h4>
<p>Verbindungsoptionen für Downloads und externe Dienste.</p>
</div>
<div class="settings-grid">
<section class="settings-card settings-card-wide">
<h5>Proxy</h5>
<label class="checkbox-field">
<input id="s-proxy-enabled" type="checkbox" /> <span>HTTP-Proxy für Downloads verwenden</span>
</label>
<div class="field-row">
<label class="field"><span>Host</span><input id="s-proxy-host" type="text" placeholder="proxy.firma.de" /></label>
<label class="field field-port"><span>Port</span><input id="s-proxy-port" type="text" placeholder="8080" /></label>
</div>
<div class="field-row">
<label class="field"><span>Benutzer <small class="opt">(optional)</small></span><input id="s-proxy-user" type="text" /></label>
<label class="field"><span>Passwort <small class="opt">(optional)</small></span><input id="s-proxy-pass" type="password" /></label>
</div>
</section>
</div>
</div>
<div id="pane-accounts" class="spane hidden">
<h4>Konten</h4>
<div id="s-accounts" class="account-list"></div>
<div class="path-row">
<button id="s-add-account" class="btn btn-primary"> Konto hinzufügen</button>
<button id="s-open-accounts" class="btn">Verwaltung öffnen</button>
<div class="settings-hero">
<h4>Konten</h4>
<p>Microsoft-Konten verwalten und das aktive Spielkonto wechseln.</p>
</div>
<div class="settings-grid">
<section class="settings-card settings-card-wide">
<h5>Verknüpfte Konten</h5>
<div id="s-accounts" class="account-list"></div>
<div class="path-row">
<button id="s-add-account" class="btn btn-primary"> Konto hinzufügen</button>
<button id="s-open-accounts" class="btn">Verwaltung öffnen</button>
</div>
</section>
</div>
</div>
<div id="pane-info" class="spane hidden">
<h4>Über AeroMC</h4>
<div class="field">
<button id="s-check-update" class="btn">Nach Updates suchen</button>
<small id="s-update-status" class="hint"></small>
<div class="settings-hero">
<h4>Info</h4>
</div>
<div class="about-brand">
<img src="assets/logo.png" alt="" class="about-logo" />
<div>
<div class="about-name">AeroMC</div>
<div class="hint">Instanz-basierter Minecraft-Launcher</div>
</div>
<div class="settings-grid">
<section class="settings-card settings-card-wide info-card">
<div class="about-brand">
<img src="assets/logo.png" alt="" class="about-logo" />
<div>
<div class="about-name">AeroMC</div>
</div>
</div>
<div id="s-appinfo" class="settings-kv info-meta"></div>
</section>
</div>
<div id="s-appinfo" class="appinfo"></div>
</div>
</div>
</div>

View File

@@ -10,6 +10,7 @@ const { app, BrowserWindow, ipcMain, dialog, shell, safeStorage } = require('ele
const path = require('path');
const fs = require('fs');
const { execFile } = require('child_process');
const pkg = require('../package.json');
const store = require('./store');
const services = require('./services');
const launcher = require('./launcher');
@@ -750,8 +751,9 @@ ipcMain.handle('update:install', async (_e, asset) => {
});
ipcMain.handle('app:info', () => ({
name: pkg.build?.productName || pkg.name || 'AeroMC',
version: app.getVersion(),
electron: process.versions.electron,
node: process.versions.node,
userData: app.getPath('userData'),
author: pkg.author || '',
homepage: pkg.homepage || '',
gitpage: typeof pkg.repository === 'string' ? pkg.repository : (pkg.repository?.url || ''),
}));

View File

@@ -97,18 +97,262 @@ const PIX = {
] },
};
// baut aus dem Raster ein SVG (skaliert scharf, da reine Rechtecke)
function pixelIconSvg(key, size) {
// baut aus dem Raster ein SVG
function pixelIconRects(key, scale = 1, offsetX = 0, offsetY = 0) {
const def = PIX[key] || PIX.grass;
let rects = '';
def.r.forEach((row, y) => {
for (let x = 0; x < 16 && x < row.length; x++) {
const col = def.p[row[x]];
if (!col || col === '#00000000') continue;
rects += `<rect x="${x}" y="${y}" width="1" height="1" fill="${col}"/>`;
rects += `<rect x="${offsetX + (x * scale)}" y="${offsetY + (y * scale)}" width="${scale}" height="${scale}" fill="${col}"/>`;
}
});
return `<svg viewBox="0 0 16 16" width="${size}" height="${size}" shape-rendering="crispEdges">${rects}</svg>`;
return rects;
}
function pixelIconSvg(key, size) {
const rects = pixelIconRects(key);
return `<svg viewBox="0 0 16 16" width="${size}" height="${size}">${rects}</svg>`;
}
const HD_ICON_THEMES = {
grass: { glyph: 'loop', bg: '#46b96c', shade: '#1f5b33', glow: '#95f08a' },
dirt: { glyph: 'cube', bg: '#a96d43', shade: '#58311a', glow: '#d9a56e' },
stone: { glyph: 'cube', bg: '#98a3b3', shade: '#495364', glow: '#dfe5ef' },
planks: { glyph: 'cube', bg: '#cf9b61', shade: '#704521', glow: '#f2cb97' },
tnt: { glyph: 'crate', bg: '#db4d45', shade: '#6d1d18', glow: '#fff2d2' },
obsidian: { glyph: 'cube', bg: '#6f5cd7', shade: '#25184d', glow: '#b8abff' },
netherrack: { glyph: 'cube', bg: '#c14f4f', shade: '#5d1717', glow: '#ff9a7b' },
crafting: { glyph: 'grid', bg: '#bf8750', shade: '#613a20', glow: '#f5d5a4' },
furnace: { glyph: 'forge', bg: '#7e8997', shade: '#38414c', glow: '#ffbd67' },
bookshelf: { glyph: 'books', bg: '#b77a4d', shade: '#5b331f', glow: '#ffd966' },
diamondblock: { glyph: 'cube', bg: '#42d0d7', shade: '#14606d', glow: '#dfffff' },
goldblock: { glyph: 'cube', bg: '#f0bf3c', shade: '#8b5a09', glow: '#fff1a1' },
emeraldblock: { glyph: 'cube', bg: '#2ec97b', shade: '#0b5a35', glow: '#b4ffd7' },
redstoneblock: { glyph: 'cube', bg: '#e14d51', shade: '#75191d', glow: '#ffb1b4' },
diamond: { glyph: 'gem', bg: '#47dce0', shade: '#126b79', glow: '#defcff' },
emerald: { glyph: 'gem', bg: '#2fd485', shade: '#0d683c', glow: '#d8ffe8' },
gold: { glyph: 'ingot', bg: '#f2c44e', shade: '#8d6208', glow: '#fff3b4' },
iron: { glyph: 'ingot', bg: '#b9c3d1', shade: '#57606d', glow: '#f5f8fc' },
redstone: { glyph: 'spark', bg: '#ea5956', shade: '#7e1717', glow: '#ffc8c6' },
coal: { glyph: 'coal', bg: '#59606b', shade: '#1f242b', glow: '#cfd5df' },
sword: { glyph: 'sword', bg: '#5d9ff4', shade: '#1f477c', glow: '#eef6ff' },
pickaxe: { glyph: 'pickaxe', bg: '#57a8de', shade: '#1f5276', glow: '#ebf8ff' },
axe: { glyph: 'axe', bg: '#6997df', shade: '#27456c', glow: '#eef4ff' },
bow: { glyph: 'bow', bg: '#b97a58', shade: '#5c3522', glow: '#ffe1cb' },
apple: { glyph: 'apple', bg: '#ea5353', shade: '#7e1717', glow: '#ffd3d3' },
goldenapple: { glyph: 'apple', bg: '#f0c44f', shade: '#8c6208', glow: '#fff1b2' },
book: { glyph: 'book', bg: '#7a5ad3', shade: '#33205f', glow: '#ece4ff' },
enchantedbook: { glyph: 'bookSpark', bg: '#6f63e7', shade: '#272266', glow: '#d9fcff' },
map: { glyph: 'map', bg: '#5aa67d', shade: '#1d563f', glow: '#f0f1c9' },
star: { glyph: 'star', bg: '#5f7ce0', shade: '#22357c', glow: '#ffffff' },
endereye: { glyph: 'eye', bg: '#3da984', shade: '#124a40', glow: '#ccfff0' },
enderpearl: { glyph: 'pearl', bg: '#4cc0c7', shade: '#155b62', glow: '#e6ffff' },
blaze: { glyph: 'flame', bg: '#ef8b38', shade: '#7c3a06', glow: '#ffe3a3' },
xp: { glyph: 'bottle', bg: '#3ea977', shade: '#15503a', glow: '#dcffe0' },
cake: { glyph: 'cake', bg: '#f08cab', shade: '#8a3453', glow: '#fff6dc' },
bucket: { glyph: 'bucket', bg: '#5b94dc', shade: '#1f4a79', glow: '#d8f1ff' },
lava: { glyph: 'bucket', bg: '#ec7134', shade: '#862d08', glow: '#ffd4ab' },
tnt_item: { glyph: 'powder', bg: '#9ba2af', shade: '#404753', glow: '#fff4ce' },
creeper: { glyph: 'creeper', bg: '#67c35b', shade: '#234f1c', glow: '#d7ffd0' },
chest: { glyph: 'chest', bg: '#c4884f', shade: '#653a1d', glow: '#f5d28f' },
potion: { glyph: 'potion', bg: '#8d63d9', shade: '#3b2466', glow: '#ffd7f5' },
nether: { glyph: 'flame', bg: '#d65a3b', shade: '#6b2111', glow: '#ffd1a0' },
end: { glyph: 'eye', bg: '#3f8d7d', shade: '#153f39', glow: '#d6fff4' },
};
const LEGACY_REAL_ICON_MAP = {
chest: 'chest',
creeper: 'creeper',
potion: 'potion',
nether: 'blaze',
end: 'endereye',
tnt_item: 'tnt_item',
};
function hdIconTheme(key) {
return HD_ICON_THEMES[key] || { glyph: 'letter', bg: '#5b6474', shade: '#252b34', glow: '#e8edf7' };
}
function hdIconGlyph(key, theme) {
switch (theme.glyph) {
case 'cube':
return `
<path d="M32 18 44 24 32 30 20 24Z"/>
<path d="M20 24v14l12 7 12-7V24"/>
<path d="M32 30v15"/>
`;
case 'loop':
return `
<path d="M22 33c0-4 3-7 7-7 5 0 7 6 11 6 3 0 6-3 6-7s-3-7-7-7c-5 0-7 6-11 6-3 0-6-3-6-7s3-7 7-7"/>
`;
case 'crate':
return `
<rect x="19" y="20" width="26" height="24" rx="5"/>
<path d="M19 28h26M32 20v24M24 24h16M24 40h16"/>
`;
case 'grid':
return `
<rect x="18" y="18" width="28" height="28" rx="7"/>
<path d="M27 18v28M37 18v28M18 27h28M18 37h28"/>
`;
case 'forge':
return `
<rect x="18" y="18" width="28" height="28" rx="7"/>
<path d="M25 27h14v9H25zM24 40h16"/>
`;
case 'books':
return `
<rect x="18" y="17" width="9" height="30" rx="3"/>
<rect x="28" y="15" width="8" height="32" rx="3"/>
<rect x="37" y="19" width="9" height="28" rx="3"/>
`;
case 'gem':
return `
<path d="M32 17 45 29 32 46 19 29Z"/>
<path d="M24 22h16M20 29h24M32 17v29"/>
`;
case 'ingot':
return `
<path d="M22 26h20l5 12H17Z"/>
<path d="M26 21h12l5 5H21Z"/>
`;
case 'spark':
return `
<path d="M32 17 35 26 45 28 37 34 39 44 32 39 25 44 27 34 19 28 29 26Z"/>
`;
case 'coal':
return `
<path d="M24 19h16l7 11-7 15H24l-7-15Z"/>
<path d="M24 19 32 30 40 19"/>
`;
case 'sword':
return `
<path d="M37 18 46 27 35 38 26 29Z"/>
<path d="M28 31 18 41M24 45l-4 4M27 42l-7 2"/>
`;
case 'pickaxe':
return `
<path d="M19 26c7-7 16-9 26-6"/>
<path d="M33 20 22 45"/>
`;
case 'axe':
return `
<path d="M25 19c8 0 15 5 17 13-8 2-14-1-18-8"/>
<path d="M31 19 22 46"/>
`;
case 'bow':
return `
<path d="M24 18c9 8 9 20 0 28"/>
<path d="M39 18v28M27 22 39 32 27 42"/>
`;
case 'apple':
return `
<path d="M32 23c7 0 13 5 13 12s-6 13-13 13-13-6-13-13 6-12 13-12Z"/>
<path d="M32 19c0-3 2-5 5-6M36 18c3 0 5 1 7 3"/>
`;
case 'book':
return `
<path d="M20 19h12c3 0 5 2 5 5v21H24c-2 0-4 1-4 2Z"/>
<path d="M44 19H32c-3 0-5 2-5 5v21h13c2 0 4 1 4 2Z"/>
`;
case 'bookSpark':
return `
<path d="M20 19h12c3 0 5 2 5 5v21H24c-2 0-4 1-4 2Z"/>
<path d="M44 19H32c-3 0-5 2-5 5v21h13c2 0 4 1 4 2Z"/>
<path d="M45 17 47 22 52 24 47 26 45 31 43 26 38 24 43 22Z"/>
`;
case 'map':
return `
<path d="M18 20 28 16 38 20 46 17v27l-8 3-10-4-10 4Z"/>
<path d="M28 16v27M38 20v27"/>
`;
case 'star':
return `
<path d="M32 17 36 26 46 27 38 34 40 44 32 39 24 44 26 34 18 27 28 26Z"/>
`;
case 'eye':
return `
<path d="M16 32c5-8 10-12 16-12s11 4 16 12c-5 8-10 12-16 12s-11-4-16-12Z"/>
<circle cx="32" cy="32" r="5"/>
`;
case 'pearl':
return `
<circle cx="32" cy="32" r="13"/>
<path d="M26 27c2-2 4-3 7-3"/>
`;
case 'flame':
return `
<path d="M34 17c5 8 9 12 9 19 0 7-4 13-11 13s-11-5-11-12c0-7 5-11 10-17 0 4 1 6 4 10 2-6 1-9-1-13Z"/>
`;
case 'bottle':
return `
<path d="M28 18h8v6l4 6v12c0 5-3 8-8 8s-8-3-8-8V30l4-6Z"/>
<path d="M25 39h14"/>
`;
case 'cake':
return `
<path d="M18 30h28v14H18Z"/>
<path d="M18 30c3-4 7-6 14-6s11 2 14 6M26 24v6M32 23v7M38 24v6"/>
`;
case 'bucket':
return `
<path d="M22 22h20l-3 22H25Z"/>
<path d="M26 22c0-4 2-7 6-7s6 3 6 7"/>
`;
case 'powder':
return `
<circle cx="24" cy="34" r="4"/>
<circle cx="33" cy="28" r="5"/>
<circle cx="41" cy="36" r="3"/>
`;
case 'creeper':
return `
<rect x="21" y="18" width="22" height="28" rx="6"/>
<rect x="25" y="24" width="4" height="5" rx="1" fill="currentColor" stroke="none"/>
<rect x="35" y="24" width="4" height="5" rx="1" fill="currentColor" stroke="none"/>
<path d="M26 40h12l-2-7h-8Z" fill="currentColor" stroke="none"/>
`;
case 'chest':
return `
<rect x="18" y="22" width="28" height="22" rx="5"/>
<path d="M18 29h28M32 29v8"/>
`;
case 'potion':
return `
<path d="M28 17h8v5l4 6v13c0 6-3 9-8 9s-8-3-8-9V28l4-6Z"/>
<path d="M25 39h14"/>
`;
case 'letter':
return `<text x="32" y="40" text-anchor="middle" font-size="22" font-weight="700" font-family="Segoe UI, sans-serif" fill="currentColor" stroke="none">${escapeHtml((key || '?').slice(0, 1).toUpperCase())}</text>`;
default:
return pixelIconSvg(key, 44);
}
}
function hdIconSvg(key, size = 44) {
const theme = hdIconTheme(key);
const gid = `g-${String(key || 'icon').replace(/[^a-z0-9_-]/gi, '-')}-${size}`;
return `
<svg viewBox="0 0 64 64" width="${size}" height="${size}" aria-hidden="true">
<defs>
<clipPath id="${gid}-clip">
<rect x="8" y="8" width="48" height="48" rx="12"/>
</clipPath>
<filter id="${gid}-shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="rgba(0,0,0,.28)"/>
</filter>
</defs>
<g filter="url(#${gid}-shadow)">
<g clip-path="url(#${gid}-clip)">${pixelIconRects('grass', 3, 8, 8)}</g>
<rect x="8" y="8" width="48" height="48" rx="12" fill="rgba(18,28,16,.10)"/>
<rect x="8" y="8" width="48" height="48" rx="12" fill="none" stroke="rgba(255,255,255,.18)" stroke-width="1.5"/>
</g>
<g fill="none" stroke="${theme.glow}" stroke-width="3.2" stroke-linecap="round" stroke-linejoin="round">
${hdIconGlyph(key, theme)}
</g>
</svg>`;
}
const ICON_KEYS = Object.keys(PIX);
@@ -122,15 +366,40 @@ let mcIcons = {};
async function loadMcIcons() {
try {
let list = await window.api.iconsList();
if (!list.length) {
const needRefresh = !list.length || !['potion', 'chest', 'creeper', 'paper', 'slimeball', 'cookie'].every((key) => list.some((i) => i.key === key));
if (needRefresh) {
const res = await window.api.iconsExtract();
list = (res && res.ok) ? res.icons : [];
if (res && res.ok && Array.isArray(res.icons) && res.icons.length) list = res.icons;
}
mcIcons = Object.fromEntries(list.map((i) => [i.key, i]));
await buildIsoCubes(); // räumliche Block-Symbole vorbereiten
} catch { mcIcons = {}; }
}
function resolveRealIconKey(key) {
if (mcIcons[key]) return key;
const mapped = LEGACY_REAL_ICON_MAP[key];
if (mapped && mcIcons[mapped]) return mapped;
return '';
}
function realItemIconSvg(key, size = 44) {
const realKey = resolveRealIconKey(key);
if (!realKey) return '';
const real = mcIcons[realKey];
if (!real) return '';
const theme = hdIconTheme(realKey);
const src = iso3dEnabled && BLOCK_KEYS.has(realKey) && isoCache[realKey] ? isoCache[realKey] : real.dataUri;
const plate = BLOCK_KEYS.has(realKey)
? `<rect x="8" y="8" width="48" height="48" rx="15" fill="${theme.bg}" opacity=".16"/>`
: `<circle cx="32" cy="32" r="22" fill="${theme.bg}" opacity=".14"/>`;
return `
<svg class="mc-item-icon" viewBox="0 0 64 64" width="${size}" height="${size}" aria-hidden="true">
${plate}
<image href="${src}" x="10" y="10" width="44" height="44" preserveAspectRatio="xMidYMid meet"/>
</svg>`;
}
// erzeugt die Würfel-Darstellungen einmalig im Voraus
async function buildIsoCubes() {
const jobs = Object.values(mcIcons)
@@ -157,10 +426,11 @@ function renderIsoCube(key) {
const img = isoSources[key];
if (!img) return null;
const S = 64, c = document.createElement('canvas');
const S = 96, c = document.createElement('canvas');
c.width = S; c.height = S;
const g = c.getContext('2d');
g.imageSmoothingEnabled = false;
g.imageSmoothingEnabled = true;
if ('imageSmoothingQuality' in g) g.imageSmoothingQuality = 'high';
const w = 26, h = 15, cx = S / 2, top = 8;
const face = (m, bright) => {
@@ -187,15 +457,12 @@ function renderIsoCube(key) {
return out;
}
// liefert das Icon-Markup: echte Textur bevorzugt, sonst eigene Pixel-Grafik
// liefert das Icon-Markup als skalierbares HD-SVG; eigene Bilder der Instanz bleiben separat erhalten
function iconOf(key, size = 44) {
const real = mcIcons[key];
if (real) {
let src = real.dataUri;
if (iso3dEnabled && BLOCK_KEYS.has(key) && isoCache[key]) src = isoCache[key];
return `<img src="${src}" width="${size}" height="${size}" style="image-rendering:pixelated;display:block" alt="" />`;
}
return pixelIconSvg(key, size);
const real = realItemIconSvg(key, size);
if (real) return real;
if (PIX[key]) return pixelIconSvg(key, size);
return hdIconSvg(key, size);
}
const LOADER_LABEL = {
@@ -687,7 +954,7 @@ function buildCard(inst) {
const iconHtml = inst.iconData
? `<img class="card-icon-img" src="${inst.iconData}" alt="" />`
: `<span class="card-icon">${iconOf(inst.icon)}</span>`;
: `<span class="card-icon">${iconOf(inst.icon, 60)}</span>`;
// Tooltip: Name, Version/Loader und falls vorhanden die Notizen
const loaderTxt = inst.minecraft.loader && inst.minecraft.loader !== 'vanilla'
? ' · ' + (LOADER_LABEL[inst.minecraft.loader] || inst.minecraft.loader) : '';
@@ -867,6 +1134,7 @@ async function updateLoaderVersions(preselect) {
// alle wählbaren Icons: echte Minecraft-Texturen zuerst, dann die eigenen
function availableIcons() {
const real = Object.values(mcIcons).map((i) => ({ key: i.key, label: i.label, real: true }));
if (real.length) return real;
const own = ICON_KEYS
.filter((k) => !mcIcons[k])
.map((k) => ({ key: k, label: ICON_LABELS[k] || k, real: false }));
@@ -1045,7 +1313,7 @@ async function selectInstance(id) {
const iconData = inst.customImage ? await window.api.instanceIcon(id) : null;
if (iconData) el('p-icon').innerHTML = `<img src="${iconData}" alt="" />`;
else { el('p-icon').innerHTML = iconOf(inst.icon, 56); }
else { el('p-icon').innerHTML = iconOf(inst.icon, 60); }
el('p-name').textContent = inst.name;
const loaderTxt = inst.minecraft.loader && inst.minecraft.loader !== 'vanilla'
@@ -1386,10 +1654,13 @@ async function installMod(mod, btn, item) {
}
// ---------- Einstellungen ----------
function setSettingsPane(paneId) {
document.querySelectorAll('.snav').forEach((button) => button.classList.toggle('on', button.dataset.pane === paneId));
document.querySelectorAll('.spane').forEach((pane) => pane.classList.toggle('hidden', pane.id !== paneId));
}
async function openSettings() {
// immer mit der ersten Sektion starten
document.querySelectorAll('.snav').forEach((x, i) => x.classList.toggle('on', i === 0));
document.querySelectorAll('.spane').forEach((p) => p.classList.toggle('hidden', p.id !== 'pane-launcher'));
setSettingsPane('pane-general');
const s = await window.api.getSettings();
el('s-theme').value = s.theme || 'dark';
@@ -1433,8 +1704,25 @@ async function openSettings() {
await refreshJavaStatus(s.javaPath);
const info = await window.api.appInfo();
el('s-appinfo').innerHTML =
`Version ${info.version}<br><span class="mono">${escapeHtml(info.userData)}</span>`;
el('s-appinfo').innerHTML = [
['Name', info.name || 'AeroMC'],
['Version', info.version || ''],
['Autor', info.author || 'Nicht hinterlegt'],
['Webseite', info.homepage || 'Nicht hinterlegt', info.homepage || ''],
['Gitseite', info.gitpage || 'Nicht hinterlegt', info.gitpage || ''],
].map(([label, value, url]) => {
const renderedValue = url
? `<a href="#" class="info-meta-link" data-url="${escapeHtml(url)}">${escapeHtml(value)}</a>`
: escapeHtml(value || '');
return `<div class="settings-kv-row"><span class="settings-kv-label">${escapeHtml(label)}</span><span class="settings-kv-value">${renderedValue}</span></div>`;
}).join('');
for (const link of el('s-appinfo').querySelectorAll('.info-meta-link')) {
link.addEventListener('click', (evt) => {
evt.preventDefault();
const url = link.dataset.url;
if (url) window.api.openExternal(url);
});
}
show('modal-settings');
await renderAccounts('s-accounts'); // Konten direkt in den Einstellungen zeigen
applyBedrockVisibility(); // aktualisiert auch den Hinweistext
@@ -1867,29 +2155,28 @@ function wire() {
});
el('s-java').addEventListener('change', (e) => refreshJavaStatus(e.target.value.trim()));
el('btn-save-settings').addEventListener('click', saveSettings);
document.querySelectorAll('.snav').forEach((b) => b.addEventListener('click', () => {
document.querySelectorAll('.snav').forEach((x) => x.classList.toggle('on', x === b));
document.querySelectorAll('.spane').forEach((p) => p.classList.toggle('hidden', p.id !== b.dataset.pane));
}));
document.querySelectorAll('.snav').forEach((b) => b.addEventListener('click', () => setSettingsPane(b.dataset.pane)));
el('s-open-accounts').addEventListener('click', () => { hide('modal-settings'); openLogin(); });
el('s-add-account').addEventListener('click', () => { hide('modal-settings'); openLogin().then(addAccount); });
// Updates
el('upd-install').addEventListener('click', startUpdate);
el('s-check-update').addEventListener('click', async () => {
const st = el('s-update-status');
st.textContent = 'Suche nach Updates …';
const info = await window.api.updateCheck();
if (info.error) { st.textContent = '✖ ' + info.error; return; }
if (info.noReleases) { st.textContent = 'Noch keine Veröffentlichung vorhanden.'; return; }
if (info.available) {
st.textContent = `Version ${info.latestVersion} verfügbar.`;
hide('modal-settings');
showUpdateDialog(info);
} else {
st.textContent = `Du hast bereits die neueste Version (v${info.currentVersion}).`;
}
});
if (el('s-check-update')) {
el('s-check-update').addEventListener('click', async () => {
const st = el('s-update-status');
st.textContent = 'Suche nach Updates …';
const info = await window.api.updateCheck();
if (info.error) { st.textContent = '✖ ' + info.error; return; }
if (info.noReleases) { st.textContent = 'Noch keine Veröffentlichung vorhanden.'; return; }
if (info.available) {
st.textContent = `Version ${info.latestVersion} verfügbar.`;
hide('modal-settings');
showUpdateDialog(info);
} else {
st.textContent = `Du hast bereits die neueste Version (v${info.currentVersion}).`;
}
});
}
// Microsoft-Login / Kontoverwaltung
el('btn-account').addEventListener('click', openLogin);

View File

@@ -93,7 +93,7 @@ body{display:grid;grid-template-rows:auto 1fr auto;height:100vh;overflow:hidden}
/* ---------- Arbeitsbereich ---------- */
.workspace{display:grid;grid-template-columns:1fr 172px;overflow:hidden;background:var(--area)}
.list-area{position:relative;overflow:auto}
.list-area{position:relative;overflow:hidden;min-height:0}
/* Skin-Hintergrund (wie MultiMCs Katze) liegt hinter den Instanzen */
.list-area::after{
content:'';position:absolute;right:24px;bottom:8px;width:52%;height:88%;
@@ -101,8 +101,7 @@ body{display:grid;grid-template-rows:auto 1fr auto;height:100vh;overflow:hidden}
background-position:right bottom;background-size:contain;
opacity:var(--bg-skin-opacity,.55);pointer-events:none;z-index:0;
}
.list{position:relative;z-index:1}
.list{padding:4px 8px}
.list{position:relative;z-index:1;height:100%;min-height:100%;overflow:auto;padding:4px 8px}
.side{background:var(--win);border-left:1px solid var(--border);overflow-y:auto;overflow-x:hidden;padding:10px 8px}
.side-empty{text-align:center;color:var(--dim);padding:24px 8px}
.side-ico{width:52px;height:52px;margin:0 auto 5px;display:grid;place-items:center;font-size:38px}
@@ -117,21 +116,22 @@ body{display:grid;grid-template-rows:auto 1fr auto;height:100vh;overflow:hidden}
.sbtn.del:hover{color:var(--danger)}
/* ---------- Instanzliste / Gruppen / Kacheln ---------- */
.group-section{margin-bottom:4px}
.group-header{display:flex;align-items:center;gap:7px;padding:7px 4px 5px;color:#333;font-weight:600;font-size:12px;cursor:pointer;user-select:none}
.group-section{margin-bottom:2px}
.group-header{display:flex;align-items:center;gap:7px;padding:5px 4px 2px;color:#333;font-weight:600;font-size:12px;cursor:pointer;user-select:none}
.group-header::after{content:'';flex:1;height:1px;background:var(--border);margin-left:6px}
.group-header .chev{font-size:9px;color:#888;width:10px;text-align:center}
.group-header.collapsed .chev{transform:none}
.group-count{color:var(--dim);font-weight:400}
.group-grid{display:grid;grid-template-columns:repeat(auto-fill,86px);gap:2px;padding:2px 4px 8px;justify-content:start}
.group-grid{display:grid;grid-template-columns:repeat(auto-fill,104px);gap:2px;padding:0 4px 4px;justify-content:start}
.group-section.collapsed .group-grid{display:none}
.card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:8px 4px 5px;border:1px solid transparent;border-radius:2px;cursor:pointer;position:relative}
.card{display:flex;flex-direction:column;align-items:center;gap:3px;padding:6px 4px 4px;border:1px solid transparent;border-radius:2px;cursor:pointer;position:relative}
.card:hover{background:#e3ecf7}
.card.sel{background:var(--sel);border-color:var(--sel-br)}
.card-icon{width:44px;height:44px;display:grid;place-items:center;font-size:30px}
.card-icon svg,.side-ico svg{display:block;image-rendering:pixelated}
.card-icon-img{width:44px;height:44px;object-fit:cover;border-radius:3px}
.card-name{font-size:11px;text-align:center;line-height:1.2;max-width:82px;padding:1px 4px;word-break:break-word}
.card-icon{width:60px;height:60px;display:grid;place-items:center;font-size:40px}
.card-icon svg,.side-ico svg{display:block}
.mc-texture-icon{display:block;object-fit:contain;image-rendering:auto;filter:drop-shadow(0 1px 1px rgba(0,0,0,.18))}
.card-icon-img{width:60px;height:60px;object-fit:cover;border-radius:3px}
.card-name{width:100%;max-width:88px;box-sizing:border-box;font-size:12px;text-align:center;line-height:1.25;padding:1px 4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.card.sel .card-name{background:var(--sel-br);color:#fff;border-radius:2px}
.card-badges{display:none}
.card-play{display:none}
@@ -161,6 +161,7 @@ body{display:grid;grid-template-rows:auto 1fr auto;height:100vh;overflow:hidden}
.modal{position:fixed;inset:0;background:rgba(0,0,0,.35);display:flex;align-items:center;justify-content:center;z-index:50}
.modal-card{width:560px;max-width:calc(100vw - 40px);max-height:calc(100vh - 60px);background:#f6f6f6;border:1px solid #a9a9a9;border-radius:4px;box-shadow:0 10px 30px rgba(0,0,0,.3);display:flex;flex-direction:column;overflow:hidden}
.modal-sm{width:420px}.modal-lg{width:820px}
#modal-settings .modal-card{width:960px;max-width:calc(100vw - 32px)}
.modal-head{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:#e8e8e8;border-bottom:1px solid var(--border)}
.modal-head h3{margin:0;font-size:14px;font-weight:600}
.modal-close{background:none;border:none;color:#666;font-size:15px;cursor:pointer;line-height:1}
@@ -184,7 +185,7 @@ body{display:grid;grid-template-rows:auto 1fr auto;height:100vh;overflow:hidden}
.icon-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(30px,1fr));gap:3px;margin-top:6px;
width:100%;max-width:100%;max-height:132px;overflow-y:auto;overflow-x:hidden;box-sizing:border-box}
.icon-choice{display:flex;align-items:center;justify-content:center;padding:2px;border:1px solid transparent;border-radius:3px;background:none;cursor:pointer;min-width:0;overflow:hidden}
.icon-choice img{width:24px;height:24px;image-rendering:pixelated;display:block}
.icon-choice img{width:24px;height:24px;display:block;object-fit:contain;image-rendering:auto}
.icon-choice:hover{background:rgba(128,128,128,.18)}
.icon-choice.on{border-color:var(--accent);background:var(--sel)}
.icon-choice svg{display:block}
@@ -192,7 +193,7 @@ body{display:grid;grid-template-rows:auto 1fr auto;height:100vh;overflow:hidden}
.icon-preview{width:34px;height:34px;border-radius:3px;overflow:hidden;background:#e4e4e4;border:1px solid var(--border)}
.icon-preview img{width:100%;height:100%;object-fit:cover}
.appinfo{margin-top:8px;font-size:12px;color:var(--dim);line-height:1.6}
.about-brand{display:flex;align-items:center;gap:14px;margin-bottom:12px}
.about-brand{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;margin:0 0 18px;padding:6px 0 14px;border-bottom:1px solid var(--border2);text-align:center}
.about-logo{width:56px;height:56px;border-radius:12px}
.about-name{font-size:18px;font-weight:600}
@@ -269,7 +270,7 @@ body{display:grid;grid-template-rows:auto 1fr auto;height:100vh;overflow:hidden}
.account-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:3px;background:#fff;border:1px solid var(--border)}
.account-item.active{border-color:var(--accent)}
.account-av{width:34px;height:34px;border-radius:3px;flex:none;background:#e4e4e4;display:flex;align-items:center;justify-content:center;font-size:18px}
img.account-av{object-fit:cover;image-rendering:pixelated}
img.account-av{object-fit:cover;image-rendering:auto}
.account-main{flex:1;min-width:0}
.account-name{font-weight:600}
.account-tag{font-size:11px;color:var(--link)}
@@ -290,13 +291,40 @@ img.account-av{object-fit:cover;image-rendering:pixelated}
.log-line{white-space:pre-wrap}
.log-error{color:#f48771}.log-warn{color:#dcdcaa}.log-info{color:#569cd6}
/* Einstellungen (MultiMC-Stil: linke Sektions-Navigation) */
.settings-body{display:flex;padding:0;min-height:360px}
.settings-nav{width:170px;flex:none;background:var(--bar);border-right:1px solid var(--border);padding:8px;display:flex;flex-direction:column;gap:2px}
.snav{text-align:left;padding:8px 11px;border:1px solid transparent;border-radius:3px;background:none;color:var(--text);cursor:pointer;font-family:inherit;font-size:13px}
.snav:hover{background:rgba(128,128,128,.16)}
.snav.on{background:var(--sel);border-color:var(--sel-br);color:var(--text)}
.settings-content{flex:1;padding:16px 18px;overflow:auto}
/* Einstellungen */
.settings-body{display:flex;padding:0;min-height:0;background:var(--area)}
.settings-nav{width:190px;flex:none;background:linear-gradient(180deg,var(--bar),rgba(128,128,128,.08));border-right:1px solid var(--border);padding:14px 10px;display:flex;flex-direction:column;gap:4px}
.settings-nav-label{padding:2px 10px 8px;color:var(--dim);font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}
.snav{text-align:left;padding:10px 12px;border:1px solid transparent;border-radius:8px;background:none;color:var(--text);cursor:pointer;font-family:inherit;font-size:13px;font-weight:600;transition:background .15s,border-color .15s,transform .15s}
.snav:hover{background:rgba(128,128,128,.12)}
.snav.on{background:var(--sel);border-color:var(--sel-br);color:var(--text);box-shadow:inset 0 0 0 1px rgba(255,255,255,.14)}
.settings-content{flex:1;padding:20px 22px;overflow:auto}
.settings-hero{margin-bottom:18px}
.settings-hero h4{margin:0 0 6px;font-size:20px;font-weight:700}
.settings-hero p{margin:0;color:var(--dim);font-size:13px;line-height:1.5}
.settings-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;align-items:start}
.settings-grid-stack{grid-template-columns:repeat(2,minmax(0,1fr))}
.settings-card{background:rgba(255,255,255,.7);border:1px solid var(--border);border-radius:12px;padding:16px 16px 12px;box-shadow:0 1px 0 rgba(255,255,255,.5)}
.settings-card h5{margin:0 0 14px;font-size:13px;font-weight:700;color:var(--text)}
.settings-card-wide{grid-column:span 2}
.settings-card .field:last-child,.settings-card .checkbox-field:last-of-type{margin-bottom:0}
.settings-kv{display:grid;gap:10px}
.settings-kv-row{display:flex;justify-content:space-between;gap:14px;padding-bottom:10px;border-bottom:1px solid var(--border2)}
.settings-kv-row:last-child{padding-bottom:0;border-bottom:none}
.settings-kv-label{color:var(--dim);font-size:12px}
.settings-kv-value{text-align:right;font-weight:600;font-size:12px;word-break:break-word}
.appinfo{margin-top:8px;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 16px;font-size:12px;line-height:1.6}
.appinfo-item{padding:10px 12px;border:1px solid var(--border2);border-radius:10px;background:rgba(128,128,128,.06)}
.appinfo-item.full{grid-column:span 2}
.appinfo-label{display:block;color:var(--dim);font-size:11px;text-transform:uppercase;letter-spacing:.04em}
.appinfo-value{display:block;margin-top:4px;font-weight:600;word-break:break-word}
.info-card{max-width:760px}
.info-meta{margin-top:10px}
.info-meta .settings-kv-row{align-items:center}
.info-meta .settings-kv-label{text-transform:uppercase;letter-spacing:.04em;font-size:11px}
.info-meta .settings-kv-value{text-align:left;font-size:13px;font-weight:600}
.info-meta-link{color:var(--link);text-decoration:none}
.info-meta-link:hover{text-decoration:underline}
.spane h4{margin:0 0 14px;font-size:14px;font-weight:600}
.status-ok{color:#2e8b2e!important;font-weight:600}
.status-bad{color:var(--danger)!important;font-weight:600}
@@ -305,11 +333,34 @@ img.account-av{object-fit:cover;image-rendering:pixelated}
.checkbox-field input{width:16px;height:16px}
.field-port{max-width:110px;flex:none}
:root[data-theme="dark"] .settings-body{background:#252528}
:root[data-theme="dark"] .settings-nav{background:linear-gradient(180deg,#2f2f34,#242428)}
:root[data-theme="dark"] .settings-card{background:rgba(30,30,30,.88);border-color:#3b3b42;box-shadow:none}
:root[data-theme="dark"] .settings-kv-row{border-bottom-color:#36363c}
:root[data-theme="dark"] .about-brand{border-bottom-color:#36363c}
:root[data-theme="dark"] .appinfo-item{background:rgba(255,255,255,.03);border-color:#36363c}
:root[data-theme="dark"] .info-meta-link{color:#7fc7ff}
@media (max-width: 900px){
#modal-settings .modal-card{max-width:calc(100vw - 20px);max-height:calc(100vh - 20px)}
.settings-body{flex-direction:column;min-height:0}
.settings-nav{width:auto;border-right:none;border-bottom:1px solid var(--border);padding:10px;flex-direction:row;flex-wrap:wrap}
.settings-nav-label{width:100%;padding:0 4px 6px}
.snav{flex:1 1 150px}
.settings-content{padding:16px}
.settings-grid,.settings-grid-stack{grid-template-columns:1fr}
.settings-card-wide{grid-column:auto}
.appinfo{grid-template-columns:1fr}
.appinfo-item.full{grid-column:auto}
.field-row{flex-direction:column;gap:0}
.path-row{flex-direction:column}
}
/* Konto-Button mit 3D-Skin-Kopf */
.acct-btn{gap:8px}
.acct-av{display:inline-flex;align-items:center;justify-content:center}
.acct-av svg{width:18px;height:18px;color:#4b5563}
.acct-head{width:24px;height:26px;object-fit:contain;image-rendering:pixelated;margin-top:-2px;margin-bottom:-2px}
.acct-head{width:24px;height:26px;object-fit:contain;image-rendering:auto;margin-top:-2px;margin-bottom:-2px}
/* Update-Dialog */
.upd-version{display:flex;align-items:center;justify-content:center;gap:12px;margin:4px 0 14px;font-size:15px}