Upload via GUI (123 Dateien)
This commit is contained in:
@@ -0,0 +1,86 @@
|
|||||||
|
"""
|
||||||
|
make-mc-textures.py – erzeugt die 16×16-Kacheln für das Minecraft-Design
|
||||||
|
(src/assets/design/*.png). Eigene, zufällig erzeugte Muster im Block-Stil –
|
||||||
|
keine Original-Texturen aus dem Spiel.
|
||||||
|
|
||||||
|
Aufruf: python scripts/make-mc-textures.py
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
ZIEL = os.path.join(os.path.dirname(__file__), '..', 'src', 'assets', 'design')
|
||||||
|
os.makedirs(ZIEL, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def klemme(v):
|
||||||
|
return max(0, min(255, int(v)))
|
||||||
|
|
||||||
|
|
||||||
|
def rauschen(basis, streuung, seed, flecken=None):
|
||||||
|
"""Grundfarbe mit Pixelrauschen; flecken = (anteil, farbe, streuung)."""
|
||||||
|
rnd = random.Random(seed)
|
||||||
|
img = Image.new('RGB', (16, 16))
|
||||||
|
px = img.load()
|
||||||
|
for y in range(16):
|
||||||
|
for x in range(16):
|
||||||
|
d = rnd.randint(-streuung, streuung)
|
||||||
|
farbe = basis
|
||||||
|
if flecken and rnd.random() < flecken[0]:
|
||||||
|
farbe = flecken[1]
|
||||||
|
d = rnd.randint(-flecken[2], flecken[2])
|
||||||
|
px[x, y] = tuple(klemme(c + d) for c in farbe)
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def speichere(img, name):
|
||||||
|
img.save(os.path.join(ZIEL, name))
|
||||||
|
print(name)
|
||||||
|
|
||||||
|
|
||||||
|
# Hintergrund der Instanzliste: dunkle Erde (wie hinter Menüs im Spiel)
|
||||||
|
speichere(rauschen((44, 32, 23), 6, 11, (0.16, (31, 22, 15), 4)), 'erde-dunkel.png')
|
||||||
|
|
||||||
|
# Leisten (Werkzeugleiste, Seitenleiste, Statusleiste): dunkler Schiefer
|
||||||
|
schiefer = rauschen((46, 46, 50), 6, 23, (0.12, (34, 34, 38), 3))
|
||||||
|
px = schiefer.load()
|
||||||
|
rnd = random.Random(5)
|
||||||
|
for _ in range(5): # ein paar waagerechte Bruchkanten wie bei Tiefenschiefer
|
||||||
|
y = rnd.randrange(16)
|
||||||
|
x0 = rnd.randrange(16)
|
||||||
|
for x in range(x0, min(16, x0 + rnd.randint(3, 7))):
|
||||||
|
px[x, y] = (30, 30, 33)
|
||||||
|
speichere(schiefer, 'schiefer.png')
|
||||||
|
|
||||||
|
# Dialoge: Stein
|
||||||
|
stein = rauschen((78, 78, 80), 8, 37, (0.10, (60, 60, 62), 4))
|
||||||
|
speichere(stein, 'stein.png')
|
||||||
|
|
||||||
|
# Holzbretter (z. B. Kopf der Dialoge): vier Bretter zu je 4 Pixeln
|
||||||
|
bretter = Image.new('RGB', (16, 16))
|
||||||
|
px = bretter.load()
|
||||||
|
rnd = random.Random(41)
|
||||||
|
farben = [(122, 92, 56), (114, 86, 52), (128, 97, 60), (118, 89, 54)]
|
||||||
|
fugen = [3, 11, 7, 14]
|
||||||
|
for brett in range(4):
|
||||||
|
for y in range(brett * 4, brett * 4 + 4):
|
||||||
|
for x in range(16):
|
||||||
|
c = farben[brett]
|
||||||
|
d = rnd.randint(-5, 5)
|
||||||
|
if y == brett * 4 + 3: # dunkle Fuge unter jedem Brett
|
||||||
|
c, d = (74, 55, 33), rnd.randint(-2, 2)
|
||||||
|
elif x == fugen[brett]: # Stoßfuge
|
||||||
|
c, d = (88, 66, 40), 0
|
||||||
|
px[x, y] = tuple(klemme(v + d) for v in c)
|
||||||
|
speichere(bretter, 'bretter.png')
|
||||||
|
|
||||||
|
# Grasnarbe: oberer Rand der Werkzeugleiste (16×4, grün mit "Tropfen")
|
||||||
|
gras = Image.new('RGBA', (16, 4), (0, 0, 0, 0))
|
||||||
|
px = gras.load()
|
||||||
|
rnd = random.Random(53)
|
||||||
|
tiefe = [2, 3, 2, 2, 4, 3, 2, 3, 2, 2, 3, 4, 2, 3, 2, 3]
|
||||||
|
for x in range(16):
|
||||||
|
for y in range(tiefe[x]):
|
||||||
|
g = rnd.randint(-10, 10)
|
||||||
|
px[x, y] = (klemme(84 + g), klemme(140 + g), klemme(52 + g // 2), 255)
|
||||||
|
speichere(gras, 'gras.png')
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* make-pixelfont.js – erzeugt src/assets/aeropixel.ttf
|
||||||
|
* -----------------------------------------------------
|
||||||
|
* Eigene Pixel-Schrift für das Minecraft-Design (keine fremde Schrift, also
|
||||||
|
* keine Lizenzfragen). Jede Glyphe ist ein Raster aus '#' (Pixel) und ' ':
|
||||||
|
* Zeile 0 ist die Oberkante der Großbuchstaben, Zeile 6 sitzt auf der
|
||||||
|
* Grundlinie, Zeilen 7–8 sind Unterlängen (g, j, p, q, y, Komma).
|
||||||
|
* Fehlende Zeichen fallen im Launcher automatisch auf Segoe UI zurück.
|
||||||
|
*
|
||||||
|
* Aufruf: node scripts/make-pixelfont.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const P = 125; // ein Pixel in Font-Einheiten (8 Pixel = 1 em)
|
||||||
|
const EM = 1000;
|
||||||
|
const ASC = 1000; // 7 Zeilen Großbuchstaben + 1 Zeile Luft
|
||||||
|
const DESC = 250; // 2 Zeilen Unterlänge
|
||||||
|
|
||||||
|
const G = {
|
||||||
|
A: [' ### ', '# #', '# #', '#####', '# #', '# #', '# #'],
|
||||||
|
B: ['#### ', '# #', '# #', '#### ', '# #', '# #', '#### '],
|
||||||
|
C: [' ### ', '# #', '# ', '# ', '# ', '# #', ' ### '],
|
||||||
|
D: ['#### ', '# #', '# #', '# #', '# #', '# #', '#### '],
|
||||||
|
E: ['#####', '# ', '# ', '#### ', '# ', '# ', '#####'],
|
||||||
|
F: ['#####', '# ', '# ', '#### ', '# ', '# ', '# '],
|
||||||
|
G: [' ####', '# ', '# ', '# ##', '# #', '# #', ' ####'],
|
||||||
|
H: ['# #', '# #', '# #', '#####', '# #', '# #', '# #'],
|
||||||
|
I: ['###', ' # ', ' # ', ' # ', ' # ', ' # ', '###'],
|
||||||
|
J: [' #', ' #', ' #', ' #', ' #', '# #', ' ### '],
|
||||||
|
K: ['# #', '# # ', '# # ', '## ', '# # ', '# # ', '# #'],
|
||||||
|
L: ['# ', '# ', '# ', '# ', '# ', '# ', '#####'],
|
||||||
|
M: ['# #', '## ##', '# # #', '# #', '# #', '# #', '# #'],
|
||||||
|
N: ['# #', '## #', '# # #', '# ##', '# #', '# #', '# #'],
|
||||||
|
O: [' ### ', '# #', '# #', '# #', '# #', '# #', ' ### '],
|
||||||
|
P: ['#### ', '# #', '# #', '#### ', '# ', '# ', '# '],
|
||||||
|
Q: [' ### ', '# #', '# #', '# #', '# #', '# # ', ' ## #'],
|
||||||
|
R: ['#### ', '# #', '# #', '#### ', '# # ', '# #', '# #'],
|
||||||
|
S: [' ####', '# ', '# ', ' ### ', ' #', ' #', '#### '],
|
||||||
|
T: ['#####', ' # ', ' # ', ' # ', ' # ', ' # ', ' # '],
|
||||||
|
U: ['# #', '# #', '# #', '# #', '# #', '# #', ' ### '],
|
||||||
|
V: ['# #', '# #', '# #', '# #', '# #', ' # # ', ' # '],
|
||||||
|
W: ['# #', '# #', '# #', '# #', '# # #', '## ##', '# #'],
|
||||||
|
X: ['# #', '# #', ' # # ', ' # ', ' # # ', '# #', '# #'],
|
||||||
|
Y: ['# #', '# #', ' # # ', ' # ', ' # ', ' # ', ' # '],
|
||||||
|
Z: ['#####', ' #', ' # ', ' # ', ' # ', '# ', '#####'],
|
||||||
|
|
||||||
|
a: [' ', ' ', ' ### ', ' #', ' ####', '# #', ' ####'],
|
||||||
|
b: ['# ', '# ', '# ## ', '## #', '# #', '# #', '#### '],
|
||||||
|
c: [' ', ' ', ' ### ', '# #', '# ', '# #', ' ### '],
|
||||||
|
d: [' #', ' #', ' ## #', '# ##', '# #', '# #', ' ####'],
|
||||||
|
e: [' ', ' ', ' ### ', '# #', '#####', '# ', ' ####'],
|
||||||
|
f: [' ##', ' # ', '####', ' # ', ' # ', ' # ', ' # '],
|
||||||
|
g: [' ', ' ', ' ####', '# #', '# #', '# #', ' ####', ' #', ' ### '],
|
||||||
|
h: ['# ', '# ', '# ## ', '## #', '# #', '# #', '# #'],
|
||||||
|
i: ['#', ' ', '#', '#', '#', '#', '#'],
|
||||||
|
j: [' #', ' ', ' #', ' #', ' #', ' #', ' #', '# #', ' ## '],
|
||||||
|
k: ['# ', '# ', '# #', '# # ', '## ', '# # ', '# #'],
|
||||||
|
l: ['# ', '# ', '# ', '# ', '# ', '# ', ' #'],
|
||||||
|
m: [' ', ' ', '## # ', '# # #', '# # #', '# #', '# #'],
|
||||||
|
n: [' ', ' ', '#### ', '# #', '# #', '# #', '# #'],
|
||||||
|
o: [' ', ' ', ' ### ', '# #', '# #', '# #', ' ### '],
|
||||||
|
p: [' ', ' ', '# ## ', '## #', '# #', '# #', '#### ', '# ', '# '],
|
||||||
|
q: [' ', ' ', ' ## #', '# ##', '# #', '# #', ' ####', ' #', ' #'],
|
||||||
|
r: [' ', ' ', '# ## ', '## #', '# ', '# ', '# '],
|
||||||
|
s: [' ', ' ', ' ####', '# ', ' ### ', ' #', '#### '],
|
||||||
|
t: [' # ', ' # ', '###', ' # ', ' # ', ' # ', ' #'],
|
||||||
|
u: [' ', ' ', '# #', '# #', '# #', '# #', ' ####'],
|
||||||
|
v: [' ', ' ', '# #', '# #', '# #', ' # # ', ' # '],
|
||||||
|
w: [' ', ' ', '# #', '# #', '# # #', '# # #', ' ####'],
|
||||||
|
x: [' ', ' ', '# #', ' # # ', ' # ', ' # # ', '# #'],
|
||||||
|
y: [' ', ' ', '# #', '# #', '# #', '# #', ' ####', ' #', '#### '],
|
||||||
|
z: [' ', ' ', '#####', ' # ', ' # ', ' # ', '#####'],
|
||||||
|
|
||||||
|
0: [' ### ', '# #', '# ##', '# # #', '## #', '# #', ' ### '],
|
||||||
|
1: [' # ', ' ## ', ' # ', ' # ', ' # ', ' # ', '#####'],
|
||||||
|
2: [' ### ', '# #', ' #', ' ## ', ' # ', '# ', '#####'],
|
||||||
|
3: [' ### ', '# #', ' #', ' ## ', ' #', '# #', ' ### '],
|
||||||
|
4: [' ##', ' # #', ' # #', '# #', '#####', ' #', ' #'],
|
||||||
|
5: ['#####', '# ', '#### ', ' #', ' #', '# #', ' ### '],
|
||||||
|
6: [' ## ', ' # ', '# ', '#### ', '# #', '# #', ' ### '],
|
||||||
|
7: ['#####', '# #', ' #', ' # ', ' # ', ' # ', ' # '],
|
||||||
|
8: [' ### ', '# #', '# #', ' ### ', '# #', '# #', ' ### '],
|
||||||
|
9: [' ### ', '# #', '# #', ' ####', ' #', ' # ', ' ## '],
|
||||||
|
|
||||||
|
'Ä': ['# #', ' ### ', '# #', '# #', '#####', '# #', '# #'],
|
||||||
|
'Ö': ['# #', ' ### ', '# #', '# #', '# #', '# #', ' ### '],
|
||||||
|
'Ü': ['# #', ' ', '# #', '# #', '# #', '# #', ' ### '],
|
||||||
|
'ä': [' # # ', ' ', ' ### ', ' #', ' ####', '# #', ' ####'],
|
||||||
|
'ö': [' # # ', ' ', ' ### ', '# #', '# #', '# #', ' ### '],
|
||||||
|
'ü': [' # # ', ' ', '# #', '# #', '# #', '# #', ' ####'],
|
||||||
|
'ß': [' ### ', '# #', '# # ', '# # ', '# # ', '# #', '# ## '],
|
||||||
|
'é': [' # ', ' # ', ' ### ', '# #', '#####', '# ', ' ####'],
|
||||||
|
|
||||||
|
'.': [' ', ' ', ' ', ' ', ' ', ' ', '#'],
|
||||||
|
',': [' ', ' ', ' ', ' ', ' ', ' ', '#', '#'],
|
||||||
|
':': [' ', ' ', '#', ' ', ' ', ' ', '#'],
|
||||||
|
';': [' ', ' ', '#', ' ', ' ', ' ', '#', '#'],
|
||||||
|
'!': ['#', '#', '#', '#', '#', ' ', '#'],
|
||||||
|
'?': [' ### ', '# #', ' #', ' # ', ' # ', ' ', ' # '],
|
||||||
|
'-': [' ', ' ', ' ', '####', ' ', ' ', ' '],
|
||||||
|
'–': [' ', ' ', ' ', '#####', ' ', ' ', ' '],
|
||||||
|
'_': [' ', ' ', ' ', ' ', ' ', ' ', ' ', '#####'],
|
||||||
|
'(': [' #', ' # ', '# ', '# ', '# ', ' # ', ' #'],
|
||||||
|
')': ['# ', ' # ', ' #', ' #', ' #', ' # ', '# '],
|
||||||
|
'[': ['##', '# ', '# ', '# ', '# ', '# ', '##'],
|
||||||
|
']': ['##', ' #', ' #', ' #', ' #', ' #', '##'],
|
||||||
|
'/': [' #', ' # ', ' # ', ' # ', ' # ', ' # ', '# '],
|
||||||
|
'\\': ['# ', ' # ', ' # ', ' # ', ' # ', ' # ', ' #'],
|
||||||
|
'+': [' ', ' # ', ' # ', '#####', ' # ', ' # ', ' '],
|
||||||
|
'=': [' ', ' ', '#####', ' ', '#####', ' ', ' '],
|
||||||
|
'*': [' ', ' ', '# # #', ' ### ', '# # #', ' ', ' '],
|
||||||
|
'%': ['# #', ' #', ' # ', ' # ', ' # ', '# ', '# #'],
|
||||||
|
'&': [' ## ', '# # ', ' ## ', ' ## #', '# # ', '# # ', ' ## #'],
|
||||||
|
'#': [' # # ', '#####', ' # # ', ' # # ', ' # # ', '#####', ' # # '],
|
||||||
|
'<': [' #', ' # ', ' # ', '# ', ' # ', ' # ', ' #'],
|
||||||
|
'>': ['# ', ' # ', ' # ', ' #', ' # ', ' # ', '# '],
|
||||||
|
"'": ['#', '#'],
|
||||||
|
'"': ['# #', '# #'],
|
||||||
|
'|': ['#', '#', '#', '#', '#', '#', '#', '#', '#'],
|
||||||
|
'·': [' ', ' ', ' ', '#', ' ', ' ', ' '],
|
||||||
|
'…': [' ', ' ', ' ', ' ', ' ', ' ', '# # #'],
|
||||||
|
'@': [' ### ', '# #', '# ###', '# # #', '# ###', '# ', ' ####'],
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------- Glyphen in Umrisse umwandeln (waagerechte Pixel-Läufe als Rechtecke) ----------
|
||||||
|
function umrisse(zeilen) {
|
||||||
|
const konturen = [];
|
||||||
|
zeilen.forEach((zeile, r) => {
|
||||||
|
const yOben = (7 - r) * P;
|
||||||
|
const yUnten = yOben - P;
|
||||||
|
let x = 0;
|
||||||
|
while (x < zeile.length) {
|
||||||
|
if (zeile[x] !== '#') { x++; continue; }
|
||||||
|
let ende = x;
|
||||||
|
while (ende < zeile.length && zeile[ende] === '#') ende++;
|
||||||
|
// im Uhrzeigersinn (TrueType-Außenkontur)
|
||||||
|
konturen.push([[x * P, yUnten], [x * P, yOben], [ende * P, yOben], [ende * P, yUnten]]);
|
||||||
|
x = ende;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return konturen;
|
||||||
|
}
|
||||||
|
|
||||||
|
const glyphen = [{ name: '.notdef', code: null, breite: 4, konturen: [[[0, 0], [0, 875], [500, 875], [500, 0]], [[125, 125], [375, 125], [375, 750], [125, 750]]] },
|
||||||
|
{ name: 'space', code: 32, breite: 3, konturen: [] }];
|
||||||
|
for (const [zeichen, zeilen] of Object.entries(G)) {
|
||||||
|
const breite = Math.max(...zeilen.map((z) => z.length));
|
||||||
|
glyphen.push({ code: zeichen.codePointAt(0), breite, konturen: umrisse(zeilen) });
|
||||||
|
}
|
||||||
|
const sortiert = [glyphen[0], glyphen[1], ...glyphen.slice(2).sort((a, b) => a.code - b.code)];
|
||||||
|
|
||||||
|
// ---------- Hilfen zum Schreiben ----------
|
||||||
|
class Puffer {
|
||||||
|
constructor() { this.teile = []; this.laenge = 0; }
|
||||||
|
u8(v) { const b = Buffer.alloc(1); b.writeUInt8(v); return this.roh(b); }
|
||||||
|
u16(v) { const b = Buffer.alloc(2); b.writeUInt16BE(v & 0xffff); return this.roh(b); }
|
||||||
|
i16(v) { const b = Buffer.alloc(2); b.writeInt16BE(v); return this.roh(b); }
|
||||||
|
u32(v) { const b = Buffer.alloc(4); b.writeUInt32BE(v >>> 0); return this.roh(b); }
|
||||||
|
i64(v) { const b = Buffer.alloc(8); b.writeBigInt64BE(BigInt(v)); return this.roh(b); }
|
||||||
|
roh(b) { this.teile.push(b); this.laenge += b.length; return this; }
|
||||||
|
fertig() { return Buffer.concat(this.teile); }
|
||||||
|
}
|
||||||
|
const auf4 = (b) => (b.length % 4 ? Buffer.concat([b, Buffer.alloc(4 - (b.length % 4))]) : b);
|
||||||
|
function pruefsumme(b) {
|
||||||
|
const p = auf4(b);
|
||||||
|
let s = 0;
|
||||||
|
for (let i = 0; i < p.length; i += 4) s = (s + p.readUInt32BE(i)) >>> 0;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- glyf + loca + hmtx ----------
|
||||||
|
const glyf = new Puffer();
|
||||||
|
const loca = [];
|
||||||
|
const hmtx = new Puffer();
|
||||||
|
let maxPunkte = 0, maxKonturen = 0;
|
||||||
|
let gxMin = 0, gyMin = 0, gxMax = 0, gyMax = 0;
|
||||||
|
let maxVorschub = 0, minLsb = 1e9, minRsb = 1e9, maxXAusdehnung = 0;
|
||||||
|
for (const g of sortiert) {
|
||||||
|
loca.push(glyf.laenge);
|
||||||
|
const vorschub = (g.breite + 1) * P;
|
||||||
|
maxVorschub = Math.max(maxVorschub, vorschub);
|
||||||
|
if (!g.konturen.length) { hmtx.u16(vorschub).i16(0); continue; }
|
||||||
|
const punkte = g.konturen.flat();
|
||||||
|
const xs = punkte.map((p) => p[0]), ys = punkte.map((p) => p[1]);
|
||||||
|
const xMin = Math.min(...xs), xMax = Math.max(...xs), yMin = Math.min(...ys), yMax = Math.max(...ys);
|
||||||
|
gxMin = Math.min(gxMin, xMin); gyMin = Math.min(gyMin, yMin); gxMax = Math.max(gxMax, xMax); gyMax = Math.max(gyMax, yMax);
|
||||||
|
minLsb = Math.min(minLsb, xMin); minRsb = Math.min(minRsb, vorschub - xMax); maxXAusdehnung = Math.max(maxXAusdehnung, xMax);
|
||||||
|
maxPunkte = Math.max(maxPunkte, punkte.length); maxKonturen = Math.max(maxKonturen, g.konturen.length);
|
||||||
|
const b = new Puffer();
|
||||||
|
b.i16(g.konturen.length).i16(xMin).i16(yMin).i16(xMax).i16(yMax);
|
||||||
|
let ende = -1;
|
||||||
|
for (const k of g.konturen) { ende += k.length; b.u16(ende); }
|
||||||
|
b.u16(0); // keine Hinting-Anweisungen
|
||||||
|
for (let i = 0; i < punkte.length; i++) b.u8(0x01); // alle Punkte auf der Kurve, Koordinaten als int16
|
||||||
|
let px = 0, py = 0;
|
||||||
|
for (const [x] of punkte) { b.i16(x - px); px = x; }
|
||||||
|
for (const [, y] of punkte) { b.i16(y - py); py = y; }
|
||||||
|
glyf.roh(auf4(b.fertig()));
|
||||||
|
hmtx.u16(vorschub).i16(xMin);
|
||||||
|
}
|
||||||
|
loca.push(glyf.laenge);
|
||||||
|
const anzahl = sortiert.length;
|
||||||
|
|
||||||
|
const locaB = new Puffer();
|
||||||
|
for (const o of loca) locaB.u32(o);
|
||||||
|
|
||||||
|
// ---------- cmap (Format 4, ein Segment je Zeichen) ----------
|
||||||
|
const abbildung = sortiert.map((g, i) => [g.code, i]).filter(([c]) => c !== null).sort((a, b) => a[0] - b[0]);
|
||||||
|
const segmente = abbildung.map(([c, i]) => ({ start: c, ende: c, delta: (i - c) & 0xffff }));
|
||||||
|
segmente.push({ start: 0xffff, ende: 0xffff, delta: 1 });
|
||||||
|
const segX2 = segmente.length * 2;
|
||||||
|
const suchbereich = 2 * 2 ** Math.floor(Math.log2(segmente.length));
|
||||||
|
const sub = new Puffer();
|
||||||
|
sub.u16(4).u16(16 + segmente.length * 8).u16(0).u16(segX2).u16(suchbereich)
|
||||||
|
.u16(Math.log2(suchbereich / 2)).u16(segX2 - suchbereich);
|
||||||
|
for (const s of segmente) sub.u16(s.ende);
|
||||||
|
sub.u16(0);
|
||||||
|
for (const s of segmente) sub.u16(s.start);
|
||||||
|
for (const s of segmente) sub.u16(s.delta);
|
||||||
|
for (const s of segmente) sub.u16(0);
|
||||||
|
const cmap = new Puffer().u16(0).u16(1).u16(3).u16(1).u32(12).roh(sub.fertig());
|
||||||
|
|
||||||
|
// ---------- head / hhea / maxp / OS/2 / name / post ----------
|
||||||
|
const zeit = Math.floor(Date.UTC(2026, 9, 3) / 1000) + 2082844800; // Sekunden seit 1904
|
||||||
|
const head = new Puffer().u32(0x00010000).u32(0x00010000).u32(0).u32(0x5f0f3cf5).u16(0x000b).u16(EM)
|
||||||
|
.i64(zeit).i64(zeit).i16(gxMin).i16(gyMin).i16(gxMax).i16(gyMax).u16(0).u16(8).i16(2).i16(1).i16(0);
|
||||||
|
const hhea = new Puffer().u32(0x00010000).i16(ASC).i16(-DESC).i16(0).u16(maxVorschub).i16(minLsb).i16(minRsb)
|
||||||
|
.i16(maxXAusdehnung).i16(1).i16(0).i16(0).i16(0).i16(0).i16(0).i16(0).i16(0).u16(anzahl);
|
||||||
|
const maxp = new Puffer().u32(0x00010000).u16(anzahl).u16(maxPunkte).u16(maxKonturen).u16(0).u16(0)
|
||||||
|
.u16(2).u16(0).u16(0).u16(0).u16(0).u16(0).u16(0).u16(0).u16(0);
|
||||||
|
const codes = abbildung.map(([c]) => c);
|
||||||
|
const os2 = new Puffer().u16(4).i16(5 * P).u16(400).u16(5).u16(0)
|
||||||
|
.i16(650).i16(600).i16(0).i16(75).i16(650).i16(600).i16(0).i16(350).i16(P).i16(3 * P)
|
||||||
|
.i16(0).roh(Buffer.alloc(10)).u32(3).u32(0).u32(0).u32(0).roh(Buffer.from('AERO'))
|
||||||
|
.u16(0x40).u16(Math.min(...codes)).u16(Math.min(0xffff, Math.max(...codes)))
|
||||||
|
.i16(ASC).i16(-DESC).i16(0).u16(ASC).u16(DESC).u32(1).u32(0).i16(5 * P).i16(7 * P).u16(0).u16(32).u16(1);
|
||||||
|
const namen = { 1: 'AeroPixel', 2: 'Regular', 3: 'AeroPixel Regular', 4: 'AeroPixel', 5: 'Version 1.0', 6: 'AeroPixel-Regular' };
|
||||||
|
const nameDaten = Object.entries(namen).map(([id, text]) => [Number(id), Buffer.from(text, 'utf16le').swap16()]);
|
||||||
|
const name = new Puffer().u16(0).u16(nameDaten.length).u16(6 + nameDaten.length * 12);
|
||||||
|
let versatz = 0;
|
||||||
|
for (const [id, b] of nameDaten) { name.u16(3).u16(1).u16(0x409).u16(id).u16(b.length).u16(versatz); versatz += b.length; }
|
||||||
|
for (const [, b] of nameDaten) name.roh(b);
|
||||||
|
const post = new Puffer().u32(0x00030000).u32(0).i16(-P).i16(P).u32(0).u32(0).u32(0).u32(0).u32(0);
|
||||||
|
|
||||||
|
// ---------- zusammensetzen ----------
|
||||||
|
const tabellen = [
|
||||||
|
['OS/2', os2], ['cmap', cmap], ['glyf', glyf], ['head', head], ['hhea', hhea],
|
||||||
|
['hmtx', hmtx], ['loca', locaB], ['maxp', maxp], ['name', name], ['post', post],
|
||||||
|
].map(([tag, p]) => [tag, p.fertig()]);
|
||||||
|
const n = tabellen.length;
|
||||||
|
const sb = 16 * 2 ** Math.floor(Math.log2(n));
|
||||||
|
const kopf = new Puffer().u32(0x00010000).u16(n).u16(sb).u16(Math.log2(sb / 16)).u16(n * 16 - sb);
|
||||||
|
let offset = 12 + n * 16;
|
||||||
|
const verzeichnis = new Puffer();
|
||||||
|
for (const [tag, daten] of tabellen) {
|
||||||
|
verzeichnis.roh(Buffer.from(tag, 'latin1')).u32(pruefsumme(daten)).u32(offset).u32(daten.length);
|
||||||
|
offset += auf4(daten).length;
|
||||||
|
}
|
||||||
|
let schrift = Buffer.concat([kopf.fertig(), verzeichnis.fertig(), ...tabellen.map(([, d]) => auf4(d))]);
|
||||||
|
// checkSumAdjustment im head-Eintrag setzen
|
||||||
|
const headOffset = 12 + n * 16 + tabellen.slice(0, 3).reduce((s, [, d]) => s + auf4(d).length, 0);
|
||||||
|
schrift.writeUInt32BE((0xb1b0afba - pruefsumme(schrift)) >>> 0, headOffset + 8);
|
||||||
|
|
||||||
|
const ziel = path.join(__dirname, '..', 'src', 'assets', 'aeropixel.ttf');
|
||||||
|
fs.writeFileSync(ziel, schrift);
|
||||||
|
console.log(`${ziel}: ${anzahl} Glyphen, ${schrift.length} Bytes`);
|
||||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 484 B |
Binary file not shown.
|
After Width: | Height: | Size: 542 B |
Binary file not shown.
|
After Width: | Height: | Size: 211 B |
Binary file not shown.
|
After Width: | Height: | Size: 536 B |
Binary file not shown.
|
After Width: | Height: | Size: 572 B |
+424
@@ -0,0 +1,424 @@
|
|||||||
|
/*
|
||||||
|
* designs.css – wählbare Designs (Optionen → Darstellung → Design)
|
||||||
|
* ---------------------------------------------------------------
|
||||||
|
* Klassisch (hell/dunkel) steht in styles.css. Die Designs hier bauen
|
||||||
|
* auf den dunklen Regeln auf (data-theme="dark") und überschreiben sie über
|
||||||
|
* data-design – gleiche Spezifität, deshalb muss diese Datei NACH styles.css
|
||||||
|
* geladen werden.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ===================== Design-Auswahl in den Optionen ===================== */
|
||||||
|
.design-raster{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
|
||||||
|
.settings-card-wide .design-raster{grid-template-columns:repeat(5,minmax(0,1fr))}
|
||||||
|
@media (max-width:900px){.settings-card-wide .design-raster{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||||
|
.design-wahl{display:flex;flex-direction:column;gap:5px;padding:6px;border:1px solid var(--border);border-radius:8px;background:none;color:var(--text);font-family:inherit;font-size:12px;cursor:pointer;text-align:left}
|
||||||
|
.design-wahl:hover{border-color:var(--sel-br)}
|
||||||
|
.design-wahl.on{border-color:var(--sel-br);box-shadow:0 0 0 1px var(--sel-br);background:var(--sel)}
|
||||||
|
.design-wahl small{font-size:12px;font-weight:600;padding:0 2px}
|
||||||
|
.dv{position:relative;display:block;width:100%;align-self:stretch;height:58px;border-radius:5px;overflow:hidden;border:1px solid rgba(0,0,0,.25)}
|
||||||
|
.dv i,.dv b,.dv em{position:absolute;display:block}
|
||||||
|
.dv i{left:0;right:0;top:0;height:11px}
|
||||||
|
.dv em{top:11px;right:0;bottom:0;width:24%}
|
||||||
|
.dv b{left:10px;top:20px;width:22px;height:22px;border-radius:3px}
|
||||||
|
.dv-dark{background:#242426}.dv-dark i{background:#333337}.dv-dark em{background:#2d2d30;border-left:1px solid #3f3f46}.dv-dark b{background:#0e639c;border:1px solid #1177bb}
|
||||||
|
.dv-light{background:#e2e2e2}.dv-light i{background:#f0f0f0;border-bottom:1px solid #c4c7cc}.dv-light em{background:#f0f0f0;border-left:1px solid #c4c7cc}.dv-light b{background:#dce8f6;border:1px solid #3a6ea5}
|
||||||
|
.dv-aero{background:radial-gradient(60px 30px at 35% 10px,rgba(64,126,232,.55),transparent 70%),linear-gradient(to right,rgba(148,175,255,.09) 1px,transparent 1px) 0 0/9px 9px,linear-gradient(to bottom,rgba(148,175,255,.09) 1px,transparent 1px) 0 0/9px 9px,#05070d}
|
||||||
|
.dv-aero i{background:rgba(10,14,26,.95);border-bottom:1px solid rgba(148,175,255,.18)}
|
||||||
|
.dv-aero em{background:#0a0e1a;border-left:1px solid rgba(148,175,255,.18)}
|
||||||
|
.dv-aero b{border-radius:7px;background:linear-gradient(135deg,#8fd8ff,#407ee8 60%,#2658be);box-shadow:0 4px 12px -3px rgba(64,126,232,.9)}
|
||||||
|
.dv-lila{background:radial-gradient(60px 30px at 35% 10px,rgba(139,92,246,.6),transparent 70%),linear-gradient(to right,rgba(196,165,255,.09) 1px,transparent 1px) 0 0/9px 9px,linear-gradient(to bottom,rgba(196,165,255,.09) 1px,transparent 1px) 0 0/9px 9px,#08050f}
|
||||||
|
.dv-lila i{background:rgba(15,10,26,.95);border-bottom:1px solid rgba(196,165,255,.18)}
|
||||||
|
.dv-lila em{background:#0f0a1a;border-left:1px solid rgba(196,165,255,.18)}
|
||||||
|
.dv-lila b{border-radius:7px;background:linear-gradient(135deg,#e8beff,#8b5cf6 60%,#6d28d9);box-shadow:0 4px 12px -3px rgba(139,92,246,.9)}
|
||||||
|
.dv-mc{background:url(assets/design/erde-dunkel.png) 0 0/24px 24px;image-rendering:pixelated}
|
||||||
|
.dv-mc i{height:12px;background:url(assets/design/gras.png) 0 0/16px 4px repeat-x,url(assets/design/schiefer.png) 0 0/24px 24px;border-bottom:1px solid #000}
|
||||||
|
.dv-mc em{background:url(assets/design/schiefer.png) 0 0/24px 24px;border-left:1px solid #000}
|
||||||
|
.dv-mc b{border-radius:0;background:#3c8527;border:1px solid #000;box-shadow:inset 1px 1px 0 #6cc349,inset -1px -1px 0 #1d4d13}
|
||||||
|
|
||||||
|
/* ===================================================================== */
|
||||||
|
/* ============ Designs: AeroMC (Blau, wie die Webseite) & Nachtlila ===== */
|
||||||
|
/* ===================================================================== */
|
||||||
|
/* Beide Designs teilen sich alle Regeln unten; sie unterscheiden sich nur in
|
||||||
|
der Palette. --p-* ohne "#" sind RGB-Tripel, damit rgba(var(--p-…),.5) geht. */
|
||||||
|
:root[data-design="aero"]{
|
||||||
|
--p-akzent:64,126,232; --p-tief:38,88,190; --p-akzent2:104,196,250; --p-hell:143,216,255;
|
||||||
|
--p-linie:148,175,255; --p-win:10,14,26; --p-surface:13,20,40; --p-surface2:16,26,53; --p-schatten:2,4,10;
|
||||||
|
--p-akzent-hover:#4f8df0; --p-tief-hover:#2c63cc; --p-aktiv:#0b1226; --p-konsole:#03050a;
|
||||||
|
--win:#0a0e1a; --area:#05070d; --bar:#0d1428; --surface:#0d1428; --surface2:#101a35; --input:#070b16;
|
||||||
|
--text:#e8ecfb; --dim:#93a0c4; --link:#8fd8ff;
|
||||||
|
}
|
||||||
|
:root[data-design="lila"]{
|
||||||
|
--p-akzent:139,92,246; --p-tief:109,40,217; --p-akzent2:192,132,252; --p-hell:232,190,255;
|
||||||
|
--p-linie:196,165,255; --p-win:15,10,26; --p-surface:24,15,41; --p-surface2:32,20,56; --p-schatten:6,3,12;
|
||||||
|
--p-akzent-hover:#9d72ff; --p-tief-hover:#7c3aed; --p-aktiv:#140c24; --p-konsole:#050309;
|
||||||
|
--win:#0f0a1a; --area:#08050f; --bar:#180f29; --surface:#180f29; --surface2:#201438; --input:#0b0715;
|
||||||
|
--text:#f0eafb; --dim:#a99cc6; --link:#e8beff;
|
||||||
|
}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]){
|
||||||
|
--border:rgba(var(--p-linie),.14); --border2:rgba(var(--p-linie),.09);
|
||||||
|
--sel:rgba(var(--p-akzent),.20); --sel-br:var(--p-akzent-hover); --accent:rgb(var(--p-akzent));
|
||||||
|
--danger:#ff7a8a; --play:#4ade80;
|
||||||
|
--cyan:rgb(var(--p-hell)); --cyan-soft:rgb(var(--p-akzent2));
|
||||||
|
--stat-bar:rgb(var(--p-akzent2));
|
||||||
|
--titel-bg:var(--win); --titel-fg:var(--text);
|
||||||
|
}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) ::selection{background:rgb(var(--p-akzent));color:#fff}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .titel-text{font-weight:600;background:linear-gradient(120deg,rgb(var(--p-hell)),rgb(var(--p-akzent)) 70%);-webkit-background-clip:text;background-clip:text;color:transparent}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) body{background:var(--area)}
|
||||||
|
|
||||||
|
/* Werkzeugleiste */
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .toolbar{background:rgba(var(--p-win),.94);border-bottom:1px solid var(--border);padding:7px 8px;gap:2px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .tbtn{border-radius:999px;padding:6px 10px;color:var(--text)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .tbtn:hover{background:var(--surface);border-color:var(--border)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .tbtn:active{background:var(--surface2)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .tbtn svg,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) #btn-new svg,:root:is([data-design="aero"],[data-design="lila"]) #btn-import svg,:root:is([data-design="aero"],[data-design="lila"]) #btn-open-dir svg,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) #btn-console svg,:root:is([data-design="aero"],[data-design="lila"]) #btn-account svg,:root:is([data-design="aero"],[data-design="lila"]) #btn-settings svg,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .acct-av svg{color:var(--cyan-soft)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .tbsep{background:var(--border)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .search-wrap input{border-radius:999px;padding:5px 12px;width:150px}
|
||||||
|
|
||||||
|
/* Instanzliste: Raster und blaues Leuchten wie im Kopf der Webseite */
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .workspace{background:var(--area)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .list-area{background:
|
||||||
|
radial-gradient(520px 260px at 32% -60px,rgba(var(--p-akzent),.30),transparent 70%),
|
||||||
|
linear-gradient(to right,rgba(var(--p-linie),.05) 1px,transparent 1px) 0 0/44px 44px,
|
||||||
|
linear-gradient(to bottom,rgba(var(--p-linie),.05) 1px,transparent 1px) 0 0/44px 44px,
|
||||||
|
var(--area)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .group-header{color:var(--text);padding:8px 4px 4px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .group-header .chev{color:var(--cyan-soft)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .group-header::after{background:linear-gradient(to right,rgba(var(--p-linie),.22),rgba(var(--p-linie),.04))}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .group-grid{gap:6px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .card{border-radius:14px;padding:8px 4px 6px;transition:background .15s,border-color .15s,box-shadow .15s}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .card:hover{background:rgba(var(--p-surface),.85);border-color:var(--border)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .card.sel{background:linear-gradient(180deg,rgba(var(--p-akzent),.24),rgba(var(--p-tief),.14));border-color:rgba(var(--p-akzent2),.55);box-shadow:0 10px 28px -14px rgba(var(--p-akzent),.95),0 0 0 1px rgba(var(--p-hell),.08) inset}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .card.sel .card-name{background:linear-gradient(120deg,rgb(var(--p-akzent2)),rgb(var(--p-akzent)) 60%,rgb(var(--p-tief)));color:#fff;border-radius:999px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .card-icon-img{border-radius:10px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .card-taste{border-radius:999px;background:linear-gradient(120deg,rgb(var(--p-akzent2)),rgb(var(--p-akzent)))}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .empty h2{background:linear-gradient(120deg,rgb(var(--p-hell)),rgb(var(--p-akzent)) 60%,rgb(var(--p-tief)));-webkit-background-clip:text;background-clip:text;color:transparent;font-size:26px}
|
||||||
|
|
||||||
|
/* Seitenleiste */
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .side{background:var(--win);border-left:1px solid var(--border);padding:10px 6px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .side-ico img{border-radius:12px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .side-grp{color:var(--cyan-soft)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .hr{background:var(--border);margin:8px 4px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .sbtn{border-radius:999px;margin-bottom:1px;padding:5px 2px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .sbtn:hover{background:var(--surface);border-color:var(--border)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .sbtn.play{color:#fff;background:linear-gradient(180deg,rgb(var(--p-akzent)),rgb(var(--p-tief)));border-color:transparent;box-shadow:0 0 0 1px rgba(255,255,255,.08) inset,0 10px 24px -12px rgba(var(--p-akzent),.95);padding:7px 4px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .sbtn.play:hover{background:linear-gradient(180deg,var(--p-akzent-hover),var(--p-tief-hover))}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .sbtn.del:hover{color:var(--danger);border-color:rgba(255,122,138,.35)}
|
||||||
|
|
||||||
|
/* Statusleiste */
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .statusbar{background:var(--win);border-top:1px solid var(--border)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .st-row{color:var(--dim)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .st-row2{background:var(--input);border-top-color:var(--border2)}
|
||||||
|
|
||||||
|
/* Dialoge */
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .modal{background:rgba(var(--p-schatten),.68);backdrop-filter:blur(3px)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .modal-card{background:var(--win);border:1px solid var(--border);border-radius:16px;box-shadow:0 30px 80px -20px rgba(0,0,0,.85),0 0 0 1px rgba(var(--p-hell),.04)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .modal-head{background:linear-gradient(180deg,rgba(var(--p-surface2),.9),rgba(var(--p-win),.9));border-bottom:1px solid var(--border);padding:12px 16px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .modal-body{background:var(--win)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .modal-foot{background:var(--win);border-top:1px solid var(--border);padding:12px 16px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .modal-close{color:var(--dim)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .modal-close:hover{color:var(--cyan)}
|
||||||
|
|
||||||
|
/* Eingabefelder */
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .search-wrap input,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .field input,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .field select,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .field textarea,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .mods-search input,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .mods-search select,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .mods-filters select,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .mmc-bar select,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .clip-length select,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .path-row input,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .se-farbzeile input[type=text],
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .se-feld select{background:var(--input);color:var(--text);border-color:var(--border);border-radius:10px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .search-wrap input{border-radius:999px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .search-wrap input:focus,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .field input:focus,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .field select:focus,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .field textarea:focus,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .mods-search input:focus,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .path-row input:focus{border-color:var(--cyan-soft);box-shadow:0 0 0 3px rgba(var(--p-akzent2),.15)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) input[type=checkbox],:root:is([data-design="aero"],[data-design="lila"]) input[type=radio],:root:is([data-design="aero"],[data-design="lila"]) input[type=range]{accent-color:rgb(var(--p-akzent))}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .login-usercode{background:var(--input);color:var(--text);border-color:var(--border);border-radius:12px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .upd-notes{background:var(--input);border-radius:10px}
|
||||||
|
|
||||||
|
/* Knöpfe – rund wie auf der Webseite */
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .btn{border-radius:999px;padding:7px 15px;background:var(--surface);border-color:var(--border);color:var(--text);transition:background .15s,border-color .15s,transform .15s}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .btn:hover{background:var(--surface2);border-color:rgba(var(--p-linie),.32)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .btn:active{background:var(--p-aktiv)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .btn-primary{background:linear-gradient(180deg,rgb(var(--p-akzent)),rgb(var(--p-tief)));border-color:transparent;color:#fff;box-shadow:0 0 0 1px rgba(255,255,255,.08) inset,0 10px 28px -12px rgba(var(--p-akzent),.9)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .btn-primary:hover{background:linear-gradient(180deg,var(--p-akzent-hover),var(--p-tief-hover));transform:translateY(-1px)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .btn-primary:disabled{transform:none}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .btn-danger{color:var(--danger);border-color:rgba(255,122,138,.35)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .btn-danger:hover{background:rgba(255,122,138,.10);border-color:rgba(255,122,138,.6)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .btn-mini{padding:4px 10px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .badge{background:var(--surface2);color:var(--dim);border-color:var(--border);border-radius:999px}
|
||||||
|
|
||||||
|
/* Listen, Kacheln, Tabs */
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .mods-list,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .account-item,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .screen-thumb{background:var(--input);border-color:var(--border);border-radius:12px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .mod-icon,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .account-av,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .icon-preview{background:var(--surface2);border-radius:8px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .mod-item{border-radius:10px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .mod-item+.mod-item{border-color:var(--border2)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .mod-item:hover{background:var(--surface)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .mtab{border-radius:10px 10px 0 0}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .mtab.on{background:var(--surface);border-color:var(--border);color:var(--cyan)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .advanced{background:var(--surface);border-color:var(--border);border-radius:12px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .console-out{background:var(--p-konsole);border-color:var(--border);border-radius:12px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .stat-tile,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .crash-fund,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .share-code-box,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .share-preview,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .clip-bar,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .wahl-option{border-radius:12px;background:var(--surface)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .chat-verlauf{background:var(--input);border-radius:12px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .chat-blase{background:var(--surface2);border-radius:14px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .chat-blase.ich{background:linear-gradient(180deg,rgb(var(--p-akzent)),rgb(var(--p-tief)));border-color:transparent}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .stat-bar{border-radius:0 6px 6px 0;background:linear-gradient(90deg,rgb(var(--p-akzent)),rgb(var(--p-akzent2)))}
|
||||||
|
|
||||||
|
/* Einstellungen */
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .settings-body{background:var(--area)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .settings-nav{background:var(--win);border-right-color:var(--border)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .snav{border-radius:10px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .snav:hover{background:var(--surface)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .snav.on{background:linear-gradient(120deg,rgba(var(--p-akzent),.30),rgba(var(--p-tief),.18));border-color:rgba(var(--p-akzent2),.40);box-shadow:none}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .settings-card{background:var(--surface);border-color:var(--border);border-radius:16px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .settings-kv-row,
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .about-brand{border-bottom-color:var(--border2)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .appinfo-item{background:rgba(var(--p-linie),.04);border-color:var(--border2);border-radius:12px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .info-meta-link{color:var(--cyan)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .settings-hero h4{background:linear-gradient(120deg,rgb(var(--p-hell)),rgb(var(--p-akzent)) 70%);-webkit-background-clip:text;background-clip:text;color:transparent}
|
||||||
|
|
||||||
|
/* Kontextmenü, Hinweis, Bildlaufleisten */
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .ctx{background:var(--surface);border-color:var(--border);border-radius:12px;box-shadow:0 18px 40px -12px rgba(0,0,0,.8)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .ctx-item{border-radius:8px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .ctx-item:hover{background:rgba(var(--p-akzent),.22)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .toast{background:var(--surface2);border:1px solid var(--border);border-radius:999px;padding:10px 18px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) ::-webkit-scrollbar-thumb{background:rgba(var(--p-linie),.18);border-color:var(--area)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) ::-webkit-scrollbar-thumb:hover{background:rgba(var(--p-linie),.3)}
|
||||||
|
|
||||||
|
/* Skin-Editor */
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .se-modus{border-radius:999px;background:var(--surface)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .se-modus-btn.on{background:linear-gradient(180deg,rgb(var(--p-akzent)),rgb(var(--p-tief)))}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .se-tool,:root:is([data-design="aero"],[data-design="lila"]) .se-ebene{background:var(--surface)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .se-tool{border-radius:10px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .se-ebenen{border-radius:10px}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .se-buehne,:root:is([data-design="aero"],[data-design="lila"]) .se-mini{border-radius:14px;background:var(--input)}
|
||||||
|
:root:is([data-design="aero"],[data-design="lila"]) .design-wahl{border-radius:12px}
|
||||||
|
|
||||||
|
/* ===================================================================== */
|
||||||
|
/* ===================== Design: Minecraft ============================= */
|
||||||
|
/* ===================================================================== */
|
||||||
|
@font-face{font-family:"AeroPixel";src:url(assets/aeropixel.ttf) format("truetype");font-display:block}
|
||||||
|
:root[data-design="minecraft"]{
|
||||||
|
--win:#313233; --area:#1e1e1f; --bar:#48494a;
|
||||||
|
--border:#000; --border2:#1a1a1b;
|
||||||
|
--text:#ffffff; --dim:#b8b9bc;
|
||||||
|
--sel:#2f6b1f; --sel-br:#6cc349; --link:#86d75e; --accent:#3c8527;
|
||||||
|
--danger:#ff5555; --play:#6cc349; --input:#000;
|
||||||
|
--stat-bar:#6cc349;
|
||||||
|
--pixel:"AeroPixel","Segoe UI",sans-serif;
|
||||||
|
--mc-schatten:2px 2px 0 #3f3f3f;
|
||||||
|
}
|
||||||
|
:root[data-design="minecraft"] body{background:var(--area)}
|
||||||
|
:root[data-design="minecraft"] ::selection{background:#3c8527;color:#fff}
|
||||||
|
:root[data-design="minecraft"]{--titel-bg:#1e1e1f;--titel-fg:#ffffff}
|
||||||
|
:root[data-design="minecraft"] .titel-text{font-family:var(--pixel);font-size:16px;text-shadow:var(--mc-schatten);padding-bottom:2px}
|
||||||
|
|
||||||
|
/* Wiederkehrender Block-Knopf: grauer Stein mit hellem/dunklem Rand */
|
||||||
|
:root[data-design="minecraft"] .btn,
|
||||||
|
:root[data-design="minecraft"] .sbtn,
|
||||||
|
:root[data-design="minecraft"] .snav,
|
||||||
|
:root[data-design="minecraft"] .se-tool,
|
||||||
|
:root[data-design="minecraft"] .wahl-option{
|
||||||
|
background:#58595b;color:#fff;border:2px solid #000;border-radius:0;
|
||||||
|
box-shadow:inset 2px 2px 0 #8b8c8f,inset -2px -2px 0 #36373a;
|
||||||
|
text-shadow:1px 1px 0 #2a2a2a}
|
||||||
|
:root[data-design="minecraft"] .btn:hover,
|
||||||
|
:root[data-design="minecraft"] .sbtn:hover,
|
||||||
|
:root[data-design="minecraft"] .snav:hover,
|
||||||
|
:root[data-design="minecraft"] .se-tool:hover:not(:disabled),
|
||||||
|
:root[data-design="minecraft"] .wahl-option:hover{background:#6a6b6f;border-color:#fff;box-shadow:inset 2px 2px 0 #a3a4a8,inset -2px -2px 0 #3e3f42}
|
||||||
|
:root[data-design="minecraft"] .btn:active,
|
||||||
|
:root[data-design="minecraft"] .sbtn:active{background:#48494b;box-shadow:inset -2px -2px 0 #8b8c8f,inset 2px 2px 0 #36373a}
|
||||||
|
:root[data-design="minecraft"] .btn:disabled,
|
||||||
|
:root[data-design="minecraft"] .sbtn:disabled{background:#2b2b2c;color:#8b8b8b;box-shadow:none;border-color:#000;text-shadow:none;opacity:1}
|
||||||
|
:root[data-design="minecraft"] .sbtn:disabled:hover{background:#2b2b2c;border-color:#000}
|
||||||
|
/* grün wie der Spielen-Knopf */
|
||||||
|
:root[data-design="minecraft"] .btn-primary,
|
||||||
|
:root[data-design="minecraft"] .sbtn.play,
|
||||||
|
:root[data-design="minecraft"] .snav.on,
|
||||||
|
:root[data-design="minecraft"] .se-tool.on,
|
||||||
|
:root[data-design="minecraft"] .wahl-option.an{background:#3c8527;color:#fff;box-shadow:inset 2px 2px 0 #6cc349,inset -2px -2px 0 #1d4d13}
|
||||||
|
:root[data-design="minecraft"] .btn-primary:hover,
|
||||||
|
:root[data-design="minecraft"] .sbtn.play:hover{background:#4a9a33;box-shadow:inset 2px 2px 0 #82d65c,inset -2px -2px 0 #24601a;border-color:#fff}
|
||||||
|
:root[data-design="minecraft"] .btn-danger{background:#8c2b2b;box-shadow:inset 2px 2px 0 #c55050,inset -2px -2px 0 #561616}
|
||||||
|
:root[data-design="minecraft"] .btn-danger:hover{background:#a33434;border-color:#fff;box-shadow:inset 2px 2px 0 #d86262,inset -2px -2px 0 #651b1b}
|
||||||
|
:root[data-design="minecraft"] .sbtn.del:hover{background:#8c2b2b;color:#fff;box-shadow:inset 2px 2px 0 #c55050,inset -2px -2px 0 #561616}
|
||||||
|
|
||||||
|
/* Werkzeugleiste: Schiefer mit Grasnarbe */
|
||||||
|
:root[data-design="minecraft"] .toolbar{
|
||||||
|
background:url(assets/design/gras.png) 0 0/32px 8px repeat-x,url(assets/design/schiefer.png) 0 0/64px 64px;
|
||||||
|
image-rendering:pixelated;border-bottom:2px solid #000;padding:11px 8px 6px}
|
||||||
|
:root[data-design="minecraft"] .tbtn{color:#fff;border-radius:0;text-shadow:1px 1px 0 #1a1a1a;image-rendering:auto}
|
||||||
|
:root[data-design="minecraft"] .tbtn:hover{background:rgba(0,0,0,.35);border-color:#fff}
|
||||||
|
:root[data-design="minecraft"] .tbtn:active{background:rgba(0,0,0,.5)}
|
||||||
|
:root[data-design="minecraft"] .tbtn svg,:root[data-design="minecraft"] #btn-console svg,:root[data-design="minecraft"] .acct-av svg{color:#e6e6e6}
|
||||||
|
:root[data-design="minecraft"] #btn-new svg,:root[data-design="minecraft"] #btn-account svg{color:#7fd858}
|
||||||
|
:root[data-design="minecraft"] #btn-import svg{color:#6db8ff}
|
||||||
|
:root[data-design="minecraft"] #btn-open-dir svg{color:#f2c94c}
|
||||||
|
:root[data-design="minecraft"] #btn-settings svg{color:#d0d0d0}
|
||||||
|
:root[data-design="minecraft"] .tbsep{background:#000;width:2px}
|
||||||
|
:root[data-design="minecraft"] .tb-badge{border-radius:0;background:#ff5555;box-shadow:inset -1px -1px 0 #a32a2a}
|
||||||
|
|
||||||
|
/* Eingabefelder: schwarz mit grauem Rand wie im Spiel */
|
||||||
|
:root[data-design="minecraft"] .search-wrap input,
|
||||||
|
:root[data-design="minecraft"] .field input,
|
||||||
|
:root[data-design="minecraft"] .field select,
|
||||||
|
:root[data-design="minecraft"] .field textarea,
|
||||||
|
:root[data-design="minecraft"] .mods-search input,
|
||||||
|
:root[data-design="minecraft"] .mods-search select,
|
||||||
|
:root[data-design="minecraft"] .mods-filters select,
|
||||||
|
:root[data-design="minecraft"] .mmc-bar select,
|
||||||
|
:root[data-design="minecraft"] .clip-length select,
|
||||||
|
:root[data-design="minecraft"] .path-row input,
|
||||||
|
:root[data-design="minecraft"] .login-usercode,
|
||||||
|
:root[data-design="minecraft"] .upd-notes,
|
||||||
|
:root[data-design="minecraft"] .se-farbzeile input[type=text],
|
||||||
|
:root[data-design="minecraft"] .se-feld select{background:#000;color:#fff;border:1px solid #a0a0a0;border-radius:0}
|
||||||
|
:root[data-design="minecraft"] .search-wrap input:focus,
|
||||||
|
:root[data-design="minecraft"] .field input:focus,
|
||||||
|
:root[data-design="minecraft"] .field select:focus,
|
||||||
|
:root[data-design="minecraft"] .field textarea:focus,
|
||||||
|
:root[data-design="minecraft"] .mods-search input:focus,
|
||||||
|
:root[data-design="minecraft"] .path-row input:focus{border-color:#fff;box-shadow:none}
|
||||||
|
:root[data-design="minecraft"] input[type=checkbox],:root[data-design="minecraft"] input[type=radio],:root[data-design="minecraft"] input[type=range]{accent-color:#3c8527}
|
||||||
|
|
||||||
|
/* Instanzliste auf dunkler Erde */
|
||||||
|
:root[data-design="minecraft"] .workspace{background:var(--area)}
|
||||||
|
:root[data-design="minecraft"] .list-area{background:url(assets/design/erde-dunkel.png) 0 0/64px 64px;image-rendering:pixelated}
|
||||||
|
:root[data-design="minecraft"] .list-area::after,
|
||||||
|
:root[data-design="minecraft"] .list{image-rendering:auto}
|
||||||
|
:root[data-design="minecraft"] .group-header{font-family:var(--pixel);font-size:16px;font-weight:400;color:#fff;text-shadow:var(--mc-schatten);padding:8px 4px 4px}
|
||||||
|
:root[data-design="minecraft"] .group-count{color:#b8b9bc}
|
||||||
|
:root[data-design="minecraft"] .group-header .chev{color:#fff;font-family:"Segoe UI",sans-serif;font-size:10px}
|
||||||
|
:root[data-design="minecraft"] .group-header::after{height:2px;background:rgba(0,0,0,.55);box-shadow:0 1px 0 rgba(255,255,255,.08)}
|
||||||
|
:root[data-design="minecraft"] .card{border-radius:0}
|
||||||
|
:root[data-design="minecraft"] .card-name{color:#fff;text-shadow:1px 1px 0 #1a1a1a}
|
||||||
|
:root[data-design="minecraft"] .card:hover{background:rgba(0,0,0,.35);border-color:rgba(255,255,255,.35)}
|
||||||
|
:root[data-design="minecraft"] .card.sel{background:rgba(0,0,0,.6);border-color:#fff;outline:1px solid #fff;outline-offset:-2px}
|
||||||
|
:root[data-design="minecraft"] .card.sel .card-name{background:none;color:#ffff7f;border-radius:0}
|
||||||
|
:root[data-design="minecraft"] .card-taste{border-radius:0;background:#3c8527;box-shadow:inset -1px -1px 0 #1d4d13}
|
||||||
|
:root[data-design="minecraft"] .card-icon-img{border-radius:0}
|
||||||
|
:root[data-design="minecraft"] .empty h2{font-family:var(--pixel);font-weight:400;font-size:24px;color:#fff;text-shadow:3px 3px 0 #3f3f3f}
|
||||||
|
:root[data-design="minecraft"] .empty p{color:#d0d0d0}
|
||||||
|
|
||||||
|
/* Seitenleiste: Knöpfe gestapelt wie im Pausenmenü */
|
||||||
|
:root[data-design="minecraft"] .side{background:url(assets/design/schiefer.png) 0 0/64px 64px;image-rendering:pixelated;border-left:2px solid #000;padding:10px 6px}
|
||||||
|
:root[data-design="minecraft"] .side>*{image-rendering:auto}
|
||||||
|
:root[data-design="minecraft"] .side-title{font-family:var(--pixel);font-size:16px;font-weight:400;text-shadow:var(--mc-schatten)}
|
||||||
|
:root[data-design="minecraft"] .side-grp{color:#86d75e}
|
||||||
|
:root[data-design="minecraft"] .side-ico img{border-radius:0}
|
||||||
|
:root[data-design="minecraft"] .side-empty{color:#d0d0d0}
|
||||||
|
:root[data-design="minecraft"] .hr{background:#000;height:2px;margin:6px 0}
|
||||||
|
:root[data-design="minecraft"] .sbtn{margin-bottom:3px;padding:5px 2px}
|
||||||
|
:root[data-design="minecraft"] .sbtn.play{font-family:var(--pixel);font-size:16px;font-weight:400;padding:6px 4px 7px;text-shadow:var(--mc-schatten)}
|
||||||
|
|
||||||
|
/* Statusleiste */
|
||||||
|
:root[data-design="minecraft"] .statusbar{background:url(assets/design/schiefer.png) 0 0/64px 64px;image-rendering:pixelated;border-top:2px solid #000}
|
||||||
|
:root[data-design="minecraft"] .st-row{color:#e6e6e6;text-shadow:1px 1px 0 #1a1a1a}
|
||||||
|
:root[data-design="minecraft"] .st-row2{background:rgba(0,0,0,.4);border-top:2px solid #000}
|
||||||
|
|
||||||
|
/* Dialoge: dunkles Steinfenster mit Holzbalken als Kopf */
|
||||||
|
:root[data-design="minecraft"] .modal{background:rgba(0,0,0,.62)}
|
||||||
|
:root[data-design="minecraft"] .modal-card{background:#313233;border:2px solid #000;border-radius:0;box-shadow:inset 2px 2px 0 #5a5b5d,inset -2px -2px 0 #1e1e1f,0 10px 30px rgba(0,0,0,.6)}
|
||||||
|
:root[data-design="minecraft"] .modal-head{background:url(assets/design/bretter.png) 0 0/64px 64px;image-rendering:pixelated;border-bottom:2px solid #000;margin:2px 2px 0}
|
||||||
|
:root[data-design="minecraft"] .modal-head h3{font-family:var(--pixel);font-size:16px;font-weight:400;color:#fff;text-shadow:var(--mc-schatten)}
|
||||||
|
:root[data-design="minecraft"] .modal-head>*{image-rendering:auto}
|
||||||
|
:root[data-design="minecraft"] .modal-body{background:#313233;margin:0 2px}
|
||||||
|
:root[data-design="minecraft"] .modal-foot{background:#262627;border-top:2px solid #000;margin:0 2px 2px}
|
||||||
|
:root[data-design="minecraft"] .modal-close{color:#fff;text-shadow:1px 1px 0 #3f3f3f;font-size:16px}
|
||||||
|
:root[data-design="minecraft"] .modal-close:hover{color:#ffff7f}
|
||||||
|
:root[data-design="minecraft"] .popout-host .modal-card{box-shadow:inset 2px 2px 0 #5a5b5d,inset -2px -2px 0 #1e1e1f}
|
||||||
|
|
||||||
|
/* Listen, Kacheln, Tabs */
|
||||||
|
:root[data-design="minecraft"] .mods-list,
|
||||||
|
:root[data-design="minecraft"] .account-item,
|
||||||
|
:root[data-design="minecraft"] .screen-thumb,
|
||||||
|
:root[data-design="minecraft"] .chat-verlauf,
|
||||||
|
:root[data-design="minecraft"] .console-out{background:#000;border:2px solid #000;border-radius:0;box-shadow:inset 1px 1px 0 #3a3a3c}
|
||||||
|
:root[data-design="minecraft"] .account-item{background:#1e1e1f}
|
||||||
|
:root[data-design="minecraft"] .account-item.active{border-color:#6cc349}
|
||||||
|
:root[data-design="minecraft"] .mod-icon,
|
||||||
|
:root[data-design="minecraft"] .account-av,
|
||||||
|
:root[data-design="minecraft"] .icon-preview{background:#48494a;border-radius:0}
|
||||||
|
:root[data-design="minecraft"] .mod-item{border-radius:0}
|
||||||
|
:root[data-design="minecraft"] .mod-item+.mod-item{border-color:#2a2a2b}
|
||||||
|
:root[data-design="minecraft"] .mod-item:hover{background:#262627;outline:1px solid #8b8b8b;outline-offset:-1px}
|
||||||
|
:root[data-design="minecraft"] .mods-tabs{border-bottom:2px solid #000}
|
||||||
|
:root[data-design="minecraft"] .mtab{border-radius:0;color:#c8c8c8;margin-bottom:-2px;border-width:2px}
|
||||||
|
:root[data-design="minecraft"] .mtab.on{background:#48494a;border-color:#000;color:#fff;text-shadow:1px 1px 0 #2a2a2a}
|
||||||
|
:root[data-design="minecraft"] .advanced,
|
||||||
|
:root[data-design="minecraft"] .stat-tile,
|
||||||
|
:root[data-design="minecraft"] .crash-fund,
|
||||||
|
:root[data-design="minecraft"] .share-code-box,
|
||||||
|
:root[data-design="minecraft"] .share-preview,
|
||||||
|
:root[data-design="minecraft"] .clip-bar,
|
||||||
|
:root[data-design="minecraft"] .crash-unklar{background:#262627;border:2px solid #000;border-radius:0;box-shadow:inset 1px 1px 0 #48494a}
|
||||||
|
:root[data-design="minecraft"] .crash-fund{border-left:4px solid #3c8527}
|
||||||
|
:root[data-design="minecraft"] .badge{border-radius:0;background:#48494a;color:#e6e6e6;border-color:#000}
|
||||||
|
:root[data-design="minecraft"] .chat-blase{border-radius:0;background:#48494a;border:2px solid #000}
|
||||||
|
:root[data-design="minecraft"] .chat-blase.ich{background:#3c8527;border-color:#000}
|
||||||
|
:root[data-design="minecraft"] .stat-bar{border-radius:0;box-shadow:inset 0 -2px 0 #3c8527}
|
||||||
|
:root[data-design="minecraft"] .mod-new{border-radius:0}
|
||||||
|
:root[data-design="minecraft"] .friend-avatar{border-radius:0}
|
||||||
|
|
||||||
|
/* Einstellungen */
|
||||||
|
:root[data-design="minecraft"] .settings-body{background:url(assets/design/erde-dunkel.png) 0 0/64px 64px;image-rendering:pixelated}
|
||||||
|
:root[data-design="minecraft"] .settings-body>*{image-rendering:auto}
|
||||||
|
:root[data-design="minecraft"] .settings-nav{background:rgba(0,0,0,.45);border-right:2px solid #000;gap:4px}
|
||||||
|
:root[data-design="minecraft"] .settings-nav-label{color:#d0d0d0;text-shadow:1px 1px 0 #1a1a1a}
|
||||||
|
:root[data-design="minecraft"] .snav{padding:8px 12px}
|
||||||
|
:root[data-design="minecraft"] .settings-hero h4{font-family:var(--pixel);font-size:24px;font-weight:400;text-shadow:3px 3px 0 #3f3f3f}
|
||||||
|
:root[data-design="minecraft"] .settings-hero p{color:#d0d0d0}
|
||||||
|
:root[data-design="minecraft"] .settings-card{background:#313233;border:2px solid #000;border-radius:0;box-shadow:inset 2px 2px 0 #5a5b5d,inset -2px -2px 0 #1e1e1f}
|
||||||
|
:root[data-design="minecraft"] .settings-card h5{font-family:var(--pixel);font-size:16px;font-weight:400;text-shadow:var(--mc-schatten)}
|
||||||
|
:root[data-design="minecraft"] .settings-kv-row,
|
||||||
|
:root[data-design="minecraft"] .about-brand{border-bottom-color:#1a1a1b}
|
||||||
|
:root[data-design="minecraft"] .appinfo-item{background:#262627;border:2px solid #000;border-radius:0}
|
||||||
|
:root[data-design="minecraft"] .info-meta-link{color:#86d75e}
|
||||||
|
:root[data-design="minecraft"] .spane h4{font-family:var(--pixel);font-size:16px;font-weight:400;text-shadow:var(--mc-schatten)}
|
||||||
|
|
||||||
|
/* Kontextmenü, Hinweis, Bildlaufleisten */
|
||||||
|
:root[data-design="minecraft"] .ctx{background:#1e1e1f;border:2px solid #000;border-radius:0;box-shadow:inset 1px 1px 0 #48494a,0 8px 24px rgba(0,0,0,.5)}
|
||||||
|
:root[data-design="minecraft"] .ctx-item{border-radius:0;text-shadow:1px 1px 0 #000}
|
||||||
|
:root[data-design="minecraft"] .ctx-item:hover{background:#3c8527}
|
||||||
|
:root[data-design="minecraft"] .ctx-danger:hover{background:#8c2b2b}
|
||||||
|
:root[data-design="minecraft"] .ctx-sep{background:#000;height:2px}
|
||||||
|
:root[data-design="minecraft"] .toast{background:#212122;border:2px solid #000;border-radius:0;box-shadow:inset 2px 2px 0 #4a4a4c,inset -2px -2px 0 #111,0 6px 20px rgba(0,0,0,.5);text-shadow:1px 1px 0 #000}
|
||||||
|
:root[data-design="minecraft"] .toast.err{background:#5c1a1a}
|
||||||
|
:root[data-design="minecraft"] ::-webkit-scrollbar-thumb{background:#8b8b8b;border:3px solid var(--area);border-radius:0;box-shadow:inset -2px -2px 0 #555}
|
||||||
|
:root[data-design="minecraft"] ::-webkit-scrollbar-thumb:hover{background:#a8a8a8}
|
||||||
|
|
||||||
|
/* Skin-Editor */
|
||||||
|
:root[data-design="minecraft"] .se-modus{border:2px solid #000;border-radius:0;background:#2b2b2c}
|
||||||
|
:root[data-design="minecraft"] .se-modus-btn{color:#d0d0d0;text-shadow:1px 1px 0 #000}
|
||||||
|
:root[data-design="minecraft"] .se-modus-btn+.se-modus-btn{border-left:2px solid #000}
|
||||||
|
:root[data-design="minecraft"] .se-modus-btn.on{background:#3c8527;color:#fff;box-shadow:inset 2px 2px 0 #6cc349,inset -2px -2px 0 #1d4d13}
|
||||||
|
:root[data-design="minecraft"] .se-ebenen{border:2px solid #000;border-radius:0}
|
||||||
|
:root[data-design="minecraft"] .se-ebene{background:#2b2b2c;color:#d0d0d0}
|
||||||
|
:root[data-design="minecraft"] .se-ebene+.se-ebene{border-left:2px solid #000}
|
||||||
|
:root[data-design="minecraft"] .se-ebene.on{background:#3c8527;color:#fff}
|
||||||
|
:root[data-design="minecraft"] .se-buehne,:root[data-design="minecraft"] .se-mini{border:2px solid #000;border-radius:0;background:#1e1e1f}
|
||||||
|
:root[data-design="minecraft"] .se-farbfeld{border-radius:0;border-color:#000}
|
||||||
|
:root[data-design="minecraft"] .se-titel{color:#d0d0d0}
|
||||||
|
:root[data-design="minecraft"] .design-wahl{border-radius:0;border:2px solid #000;background:#262627}
|
||||||
|
:root[data-design="minecraft"] .design-wahl.on{border-color:#fff;box-shadow:none;background:#2f6b1f}
|
||||||
|
:root[data-design="minecraft"] .dv{border-radius:0}
|
||||||
+157
-8
@@ -6,8 +6,15 @@
|
|||||||
content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; img-src 'self' data: https:; connect-src 'self';" />
|
content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; img-src 'self' data: https:; connect-src 'self';" />
|
||||||
<title>AeroMC</title>
|
<title>AeroMC</title>
|
||||||
<link rel="stylesheet" href="styles.css" />
|
<link rel="stylesheet" href="styles.css" />
|
||||||
|
<link rel="stylesheet" href="designs.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- Eigene Titelleiste (Fensterknöpfe zeichnet Windows rechts darüber) -->
|
||||||
|
<div class="titelleiste">
|
||||||
|
<img src="assets/logo.png" alt="" class="titel-logo" />
|
||||||
|
<span class="titel-text">AeroMC</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Obere Werkzeugleiste (MultiMC-Stil: Icon + Text) -->
|
<!-- Obere Werkzeugleiste (MultiMC-Stil: Icon + Text) -->
|
||||||
<header class="toolbar">
|
<header class="toolbar">
|
||||||
<button id="btn-new" class="tbtn" data-ic="plus" title="Neue Instanz (Strg+N)">Instanz hinzufügen</button>
|
<button id="btn-new" class="tbtn" data-ic="plus" title="Neue Instanz (Strg+N)">Instanz hinzufügen</button>
|
||||||
@@ -250,14 +257,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="settings-grid">
|
<div class="settings-grid">
|
||||||
<section class="settings-card">
|
<section class="settings-card">
|
||||||
<h5>Oberfläche</h5>
|
<h5>Instanzliste</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">
|
<label class="field">
|
||||||
<span>Instanzen sortieren</span>
|
<span>Instanzen sortieren</span>
|
||||||
<select id="s-sortby">
|
<select id="s-sortby">
|
||||||
@@ -323,6 +323,37 @@
|
|||||||
<p>Optik der Oberfläche und visuelle Extras für die Instanzansicht.</p>
|
<p>Optik der Oberfläche und visuelle Extras für die Instanzansicht.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-grid">
|
<div class="settings-grid">
|
||||||
|
<section class="settings-card settings-card-wide">
|
||||||
|
<h5>Design</h5>
|
||||||
|
<div class="field">
|
||||||
|
<select id="s-theme" class="hidden">
|
||||||
|
<option value="dark">Klassisch – Dunkel</option>
|
||||||
|
<option value="light">Klassisch – Hell</option>
|
||||||
|
<option value="aero">AeroMC</option>
|
||||||
|
<option value="lila">Nachtlila</option>
|
||||||
|
<option value="minecraft">Minecraft</option>
|
||||||
|
</select>
|
||||||
|
<div class="design-raster">
|
||||||
|
<button type="button" class="design-wahl" data-design="dark" title="Der bewährte Launcher-Look in Dunkelgrau">
|
||||||
|
<span class="dv dv-dark"><i></i><b></b><em></em></span><small>Klassisch Dunkel</small>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="design-wahl" data-design="light" title="Der bewährte Launcher-Look in Hell">
|
||||||
|
<span class="dv dv-light"><i></i><b></b><em></em></span><small>Klassisch Hell</small>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="design-wahl" data-design="aero" title="Nachtblau mit Cyan-Akzenten – wie die AeroMC-Webseite">
|
||||||
|
<span class="dv dv-aero"><i></i><b></b><em></em></span><small>AeroMC</small>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="design-wahl" data-design="lila" title="Wie AeroMC, nur in dunklem Lila">
|
||||||
|
<span class="dv dv-lila"><i></i><b></b><em></em></span><small>Nachtlila</small>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="design-wahl" data-design="minecraft" title="Block-Optik wie im Spiel">
|
||||||
|
<span class="dv dv-mc"><i></i><b></b><em></em></span><small>Minecraft</small>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="hint">Ein Klick zeigt das Design sofort an – mit „Speichern“ gilt es in allen Fenstern.</small>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="settings-card">
|
<section class="settings-card">
|
||||||
<h5>Icons</h5>
|
<h5>Icons</h5>
|
||||||
<label class="checkbox-field">
|
<label class="checkbox-field">
|
||||||
@@ -350,6 +381,14 @@
|
|||||||
<option value="spooky">Unheimlich</option>
|
<option value="spooky">Unheimlich</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Armform</span>
|
||||||
|
<select id="s-bgskin-model" title="Bei „Automatisch“ wird die Armform übernommen, die beim Konto eingestellt ist">
|
||||||
|
<option value="auto">Automatisch (vom Konto)</option>
|
||||||
|
<option value="classic">Normal (breite Arme, Steve)</option>
|
||||||
|
<option value="slim">Schmal (Alex)</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Deckkraft: <span id="s-bgskin-val">55</span> %</span>
|
<span>Deckkraft: <span id="s-bgskin-val">55</span> %</span>
|
||||||
<input id="s-bgskin-opacity" type="range" min="10" max="100" step="5" />
|
<input id="s-bgskin-opacity" type="range" min="10" max="100" step="5" />
|
||||||
@@ -1213,6 +1252,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-foot">
|
<div class="modal-foot">
|
||||||
<button id="skin-reset" class="btn btn-danger">Auf Standard zurücksetzen</button>
|
<button id="skin-reset" class="btn btn-danger">Auf Standard zurücksetzen</button>
|
||||||
|
<button id="skin-editor" class="btn" title="Skin selbst pixeln – mit Grundschicht und 2. Schicht">Im Editor bearbeiten…</button>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<button class="btn" data-close>Schließen</button>
|
<button class="btn" data-close>Schließen</button>
|
||||||
<button id="skin-apply" class="btn btn-primary" disabled>Skin setzen</button>
|
<button id="skin-apply" class="btn btn-primary" disabled>Skin setzen</button>
|
||||||
@@ -1220,6 +1260,86 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ===================== Dialog: Skin-Editor ===================== -->
|
||||||
|
<div id="modal-skineditor" class="modal hidden">
|
||||||
|
<div class="modal-card se-karte">
|
||||||
|
<div class="modal-head">
|
||||||
|
<h3>Skin-Editor</h3>
|
||||||
|
<div class="se-modus">
|
||||||
|
<button class="se-modus-btn on" data-se-modus="2d" title="Auf der aufgeklappten Vorlage malen">2D-Vorlage</button>
|
||||||
|
<button class="se-modus-btn" data-se-modus="3d" title="Direkt auf der drehbaren Figur malen">3D-Figur</button>
|
||||||
|
</div>
|
||||||
|
<button class="modal-close" data-close>✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body se-body">
|
||||||
|
<aside class="se-werkzeuge">
|
||||||
|
<div class="se-gruppe">
|
||||||
|
<div class="se-titel">Werkzeug</div>
|
||||||
|
<div class="se-tools">
|
||||||
|
<button class="se-tool on" data-se-tool="stift" title="Stift (B)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h4L18 10l-4-4L4 16v4zM14 6l4 4"/></svg><span>Stift</span></button>
|
||||||
|
<button class="se-tool" data-se-tool="radierer" title="Radierer (E)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 20h12M5.5 14.5l8-8a2 2 0 012.8 0l2.2 2.2a2 2 0 010 2.8L11 19H8.5l-3-3a1.1 1.1 0 010-1.5zM9 11l5 5"/></svg><span>Radierer</span></button>
|
||||||
|
<button class="se-tool" data-se-tool="pipette" title="Pipette (I) – auch Alt+Klick"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 7l3 3M5 19l1-4 8.5-8.5a2.1 2.1 0 013 3L9 18l-4 1zM15.5 5.5l1.5-1.5a2.1 2.1 0 013 3l-1.5 1.5"/></svg><span>Pipette</span></button>
|
||||||
|
<button class="se-tool" data-se-tool="fuellen" title="Füllen (G) – färbt gleichfarbige Pixel einer Seite"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 11l7-7 7 7-7 7zM5 11h14M20 16s1.5 2 1.5 3a1.5 1.5 0 01-3 0c0-1 1.5-3 1.5-3z"/></svg><span>Füllen</span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="se-gruppe">
|
||||||
|
<div class="se-titel">Farbe</div>
|
||||||
|
<div class="se-farbzeile">
|
||||||
|
<input id="se-farbe" type="color" value="#3a6ea5" title="Farbe wählen" />
|
||||||
|
<input id="se-hex" type="text" maxlength="7" spellcheck="false" value="#3a6ea5" />
|
||||||
|
</div>
|
||||||
|
<div id="se-zuletzt" class="se-zuletzt"></div>
|
||||||
|
</div>
|
||||||
|
<div class="se-gruppe">
|
||||||
|
<div class="se-titel">Ebene</div>
|
||||||
|
<div class="se-ebenen">
|
||||||
|
<button class="se-ebene on" data-se-schicht="0" title="Die eigentliche Figur (L wechselt)">Grundschicht</button>
|
||||||
|
<button class="se-ebene" data-se-schicht="1" title="Hülle über der Figur – Haare, Jacke, Ärmel … (L wechselt)">2. Schicht</button>
|
||||||
|
</div>
|
||||||
|
<label class="se-check"><input id="se-aussen" type="checkbox" checked /> 2. Schicht zeigen</label>
|
||||||
|
<label class="se-check"><input id="se-durch" type="checkbox" checked /> Grundschicht durchscheinen</label>
|
||||||
|
<button id="se-leeren" class="btn btn-mini">2. Schicht leeren</button>
|
||||||
|
</div>
|
||||||
|
<div class="se-gruppe">
|
||||||
|
<div class="se-titel">Optionen</div>
|
||||||
|
<label class="se-check" title="Malt gleichzeitig auf der anderen Körperseite (M)"><input id="se-spiegeln" type="checkbox" /> Spiegeln (links ↔ rechts)</label>
|
||||||
|
<label class="se-feld"><span>Armform</span>
|
||||||
|
<select id="se-variante">
|
||||||
|
<option value="classic">Klassisch (Steve)</option>
|
||||||
|
<option value="slim">Schmal (Alex)</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div class="se-undo">
|
||||||
|
<button id="se-undo" class="btn btn-mini" title="Rückgängig (Strg+Z)" disabled>↶ Rückgängig</button>
|
||||||
|
<button id="se-redo" class="btn btn-mini" title="Wiederholen (Strg+Y)" disabled>↷ Wiederholen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
<section class="se-mitte">
|
||||||
|
<div id="se-buehne" class="se-buehne">
|
||||||
|
<div id="se-2d" class="se-ansicht se-2d"><canvas id="se-2d-canvas"></canvas></div>
|
||||||
|
</div>
|
||||||
|
<div id="se-info" class="se-info hint"></div>
|
||||||
|
</section>
|
||||||
|
<aside class="se-seite">
|
||||||
|
<div id="se-mini-titel" class="se-titel">3D-Vorschau</div>
|
||||||
|
<div id="se-mini" class="se-mini">
|
||||||
|
<div id="se-3d" class="se-ansicht se-3d"><canvas id="se-3d-canvas"></canvas><span id="se-3d-leer" class="hint se-3d-leer">wird geladen …</span></div>
|
||||||
|
</div>
|
||||||
|
<p class="hint se-tipp">Beide Ansichten sind bemalbar. Die <b>2. Schicht</b> liegt wie eine Hülle über der Figur – lässt man Pixel darin frei, wirken Haare, Kapuzen oder Ärmel plastisch.</p>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
<div class="modal-foot se-fuss">
|
||||||
|
<button id="se-neu" class="btn" title="Mit einer leeren Vorlage beginnen">Neu</button>
|
||||||
|
<button id="se-laden" class="btn" title="Den Skin deines angemeldeten Kontos laden">Aktuellen Skin laden</button>
|
||||||
|
<button id="se-oeffnen" class="btn">PNG öffnen…</button>
|
||||||
|
<button id="se-speichern" class="btn">Als PNG speichern…</button>
|
||||||
|
<span id="se-status" class="hint se-status"></span>
|
||||||
|
<button id="se-uebernehmen" class="btn btn-primary">Als Skin übernehmen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ===================== Dialog: Logdateien ===================== -->
|
<!-- ===================== Dialog: Logdateien ===================== -->
|
||||||
<div id="modal-logs" class="modal hidden">
|
<div id="modal-logs" class="modal hidden">
|
||||||
<div class="modal-card modal-lg">
|
<div class="modal-card modal-lg">
|
||||||
@@ -1364,6 +1484,7 @@
|
|||||||
<button id="import-tab-modpack" class="mtab" data-kind="modpack">Modpack</button>
|
<button id="import-tab-modpack" class="mtab" data-kind="modpack">Modpack</button>
|
||||||
<button id="import-tab-mc" class="mtab" data-kind="mc">Minecraft Launcher</button>
|
<button id="import-tab-mc" class="mtab" data-kind="mc">Minecraft Launcher</button>
|
||||||
<button id="import-tab-cf" class="mtab" data-kind="cf">CurseForge</button>
|
<button id="import-tab-cf" class="mtab" data-kind="cf">CurseForge</button>
|
||||||
|
<button id="import-tab-mr" class="mtab" data-kind="mr">Modrinth App</button>
|
||||||
<button id="import-tab-mmc" class="mtab" data-kind="mmc">MultiMC / Prism</button>
|
<button id="import-tab-mmc" class="mtab" data-kind="mmc">MultiMC / Prism</button>
|
||||||
<button id="import-tab-technic" class="mtab" data-kind="technic">Technic</button>
|
<button id="import-tab-technic" class="mtab" data-kind="technic">Technic</button>
|
||||||
<button id="import-tab-code" class="mtab" data-kind="code">Per Code</button>
|
<button id="import-tab-code" class="mtab" data-kind="code">Per Code</button>
|
||||||
@@ -1460,6 +1581,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="import-pane-mr" class="import-pane hidden">
|
||||||
|
<label class="field">
|
||||||
|
<span>Datenordner der Modrinth App</span>
|
||||||
|
<div class="path-row">
|
||||||
|
<input id="mr-path" type="text" placeholder="leer lassen = Standardort (%APPDATA%\ModrinthApp)" />
|
||||||
|
<button id="mr-pick" class="btn">Wählen…</button>
|
||||||
|
<button id="mr-scan" class="btn btn-primary">Suchen</button>
|
||||||
|
</div>
|
||||||
|
<small id="mr-status" class="hint">Leer lassen nutzt automatisch den Standardort der Modrinth App.</small>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div id="mr-result" class="hidden">
|
||||||
|
<div class="mmc-bar">
|
||||||
|
<label class="checkbox-field" style="margin:0">
|
||||||
|
<input id="mr-all" type="checkbox" checked /> <span>Alle auswählen</span>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox-field" style="margin:0">
|
||||||
|
<input id="mr-copy" type="checkbox" checked /> <span>Spieldaten mitkopieren (Welten, Mods, Konfiguration)</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div id="mr-list" class="mods-list"></div>
|
||||||
|
<p class="hint" style="margin-top:8px">Übernommen werden Name, Gruppe, Symbol, Minecraft-Version, Loader, Arbeitsspeicher, Java-Einstellungen und die Spielzeit. Modrinth-Modpacks bleiben mit ihrem Paket verknüpft und lassen sich in AeroMC weiter aktualisieren.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="import-pane-mmc" class="import-pane hidden">
|
<div id="import-pane-mmc" class="import-pane hidden">
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>MultiMC- oder Prism-Ordner</span>
|
<span>MultiMC- oder Prism-Ordner</span>
|
||||||
@@ -1540,6 +1686,7 @@
|
|||||||
<div id="mc-progress" class="hint hidden"></div>
|
<div id="mc-progress" class="hint hidden"></div>
|
||||||
<div id="mmc-progress" class="hint hidden"></div>
|
<div id="mmc-progress" class="hint hidden"></div>
|
||||||
<div id="cf-progress" class="hint hidden"></div>
|
<div id="cf-progress" class="hint hidden"></div>
|
||||||
|
<div id="mr-progress" class="hint hidden"></div>
|
||||||
<div id="technic-progress" class="hint hidden"></div>
|
<div id="technic-progress" class="hint hidden"></div>
|
||||||
<div id="code-progress" class="hint hidden"></div>
|
<div id="code-progress" class="hint hidden"></div>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
@@ -1548,6 +1695,7 @@
|
|||||||
<button id="import-modpack-do" class="btn btn-primary hidden">Modpack wählen…</button>
|
<button id="import-modpack-do" class="btn btn-primary hidden">Modpack wählen…</button>
|
||||||
<button id="mc-do" class="btn btn-primary hidden" disabled>Übernehmen</button>
|
<button id="mc-do" class="btn btn-primary hidden" disabled>Übernehmen</button>
|
||||||
<button id="cf-do" class="btn btn-primary hidden" disabled>Übernehmen</button>
|
<button id="cf-do" class="btn btn-primary hidden" disabled>Übernehmen</button>
|
||||||
|
<button id="mr-do" class="btn btn-primary hidden" disabled>Übernehmen</button>
|
||||||
<button id="mmc-do" class="btn btn-primary hidden" disabled>Übernehmen</button>
|
<button id="mmc-do" class="btn btn-primary hidden" disabled>Übernehmen</button>
|
||||||
<button id="technic-do" class="btn btn-primary hidden" disabled>Übernehmen</button>
|
<button id="technic-do" class="btn btn-primary hidden" disabled>Übernehmen</button>
|
||||||
<button id="code-do" class="btn btn-primary hidden" disabled>Instanz anlegen</button>
|
<button id="code-do" class="btn btn-primary hidden" disabled>Instanz anlegen</button>
|
||||||
@@ -1656,5 +1804,6 @@
|
|||||||
<canvas id="skin-canvas" width="420" height="700" style="display:none"></canvas>
|
<canvas id="skin-canvas" width="420" height="700" style="display:none"></canvas>
|
||||||
<script src="vendor/skinview3d.js"></script>
|
<script src="vendor/skinview3d.js"></script>
|
||||||
<script src="renderer.js"></script>
|
<script src="renderer.js"></script>
|
||||||
|
<script src="skineditor.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+211
-8
@@ -29,6 +29,7 @@ const bedrock = require('./bedrock');
|
|||||||
const backup = require('./backup');
|
const backup = require('./backup');
|
||||||
const mmcimport = require('./mmcimport');
|
const mmcimport = require('./mmcimport');
|
||||||
const cfimport = require('./cfimport');
|
const cfimport = require('./cfimport');
|
||||||
|
const mrimport = require('./mrimport');
|
||||||
const mcimport = require('./mcimport');
|
const mcimport = require('./mcimport');
|
||||||
const technicimport = require('./technicimport');
|
const technicimport = require('./technicimport');
|
||||||
const technicdiscover = require('./technicdiscover');
|
const technicdiscover = require('./technicdiscover');
|
||||||
@@ -112,6 +113,7 @@ const POPOUT_SPEC = {
|
|||||||
screens: { title: 'AeroMC – Bildschirmfotos', width: 860, height: 680, minWidth: 640, minHeight: 480 },
|
screens: { title: 'AeroMC – Bildschirmfotos', width: 860, height: 680, minWidth: 640, minHeight: 480 },
|
||||||
servers: { title: 'AeroMC – Server', width: 860, height: 680, minWidth: 640, minHeight: 480 },
|
servers: { title: 'AeroMC – Server', width: 860, height: 680, minWidth: 640, minHeight: 480 },
|
||||||
skin: { title: 'AeroMC – Skin', width: 860, height: 680, minWidth: 640, minHeight: 480 },
|
skin: { title: 'AeroMC – Skin', width: 860, height: 680, minWidth: 640, minHeight: 480 },
|
||||||
|
skineditor: { title: 'AeroMC – Skin-Editor', width: 1180, height: 800, minWidth: 920, minHeight: 600 },
|
||||||
bedrock: { title: 'AeroMC – Bedrock', width: 860, height: 680, minWidth: 640, minHeight: 480 },
|
bedrock: { title: 'AeroMC – Bedrock', width: 860, height: 680, minWidth: 640, minHeight: 480 },
|
||||||
login: { title: 'AeroMC – Konten', width: 460, height: 460, minWidth: 420, minHeight: 380 },
|
login: { title: 'AeroMC – Konten', width: 460, height: 460, minWidth: 420, minHeight: 380 },
|
||||||
import: { title: 'AeroMC – Import', width: 860, height: 680, minWidth: 640, minHeight: 480 },
|
import: { title: 'AeroMC – Import', width: 860, height: 680, minWidth: 640, minHeight: 480 },
|
||||||
@@ -171,7 +173,7 @@ function openPopout(modalId, params) {
|
|||||||
minWidth: spec.minWidth,
|
minWidth: spec.minWidth,
|
||||||
minHeight: spec.minHeight,
|
minHeight: spec.minHeight,
|
||||||
title: spec.title || POPOUT_DEFAULT.title,
|
title: spec.title || POPOUT_DEFAULT.title,
|
||||||
backgroundColor: '#1b1e24',
|
backgroundColor: fensterFarbe(),
|
||||||
icon: windowIcon(),
|
icon: windowIcon(),
|
||||||
frame: false, // eigener dunkler Kopfbereich (Titel + ✕) ersetzt die native Titelleiste
|
frame: false, // eigener dunkler Kopfbereich (Titel + ✕) ersetzt die native Titelleiste
|
||||||
show: false,
|
show: false,
|
||||||
@@ -211,6 +213,12 @@ ipcMain.on('recording:guard', (_e, active) => { recordingGuardActive = !!active;
|
|||||||
|
|
||||||
ipcMain.on('debug:rendererError', (_e, msg) => console.error('[Renderer-Fehler]', msg));
|
ipcMain.on('debug:rendererError', (_e, msg) => console.error('[Renderer-Fehler]', msg));
|
||||||
ipcMain.on('popout:open', (_e, modalId, params) => openPopout(modalId, params));
|
ipcMain.on('popout:open', (_e, modalId, params) => openPopout(modalId, params));
|
||||||
|
// Design gewechselt -> Fensterknöpfe oben rechts umfärben (nur Hauptfenster,
|
||||||
|
// die Popup-Fenster haben ihren eigenen Kopf)
|
||||||
|
ipcMain.on('window:titleBar', (e, design) => {
|
||||||
|
if (!mainWindow || mainWindow.isDestroyed() || e.sender !== mainWindow.webContents) return;
|
||||||
|
try { mainWindow.setTitleBarOverlay(titelleistenFarben(design)); } catch { /* ältere Windows-Fassung */ }
|
||||||
|
});
|
||||||
ipcMain.on('popout:hide', (e) => {
|
ipcMain.on('popout:hide', (e) => {
|
||||||
const w = BrowserWindow.fromWebContents(e.sender);
|
const w = BrowserWindow.fromWebContents(e.sender);
|
||||||
if (w && !w.isDestroyed()) w.hide();
|
if (w && !w.isDestroyed()) w.hide();
|
||||||
@@ -714,6 +722,28 @@ function verknuepfungsIcon(ziel) {
|
|||||||
return windowIcon() || ziel;
|
return windowIcon() || ziel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Eigene Titelleiste im Hauptfenster: Minimieren/Maximieren/Schließen bleiben
|
||||||
|
// echte Windows-Knöpfe (Andocken, Fensterlayouts …), Windows zeichnet sie aber
|
||||||
|
// in den Farben des Designs. Die Farben müssen zu --titel-bg/--titel-fg in
|
||||||
|
// styles.css bzw. designs.css passen.
|
||||||
|
const TITELLEISTE = {
|
||||||
|
dark: { color: '#2d2d30', symbolColor: '#e4e4e6' },
|
||||||
|
light: { color: '#f0f0f0', symbolColor: '#1c1c1c' },
|
||||||
|
aero: { color: '#0a0e1a', symbolColor: '#e8ecfb' },
|
||||||
|
lila: { color: '#0f0a1a', symbolColor: '#f0eafb' },
|
||||||
|
minecraft: { color: '#1e1e1f', symbolColor: '#ffffff' },
|
||||||
|
};
|
||||||
|
function titelleistenFarben(design) {
|
||||||
|
return Object.assign({ height: 32 }, TITELLEISTE[design] || TITELLEISTE.dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grundfarbe eines neuen Fensters passend zum Design – sonst blitzt beim
|
||||||
|
// Öffnen kurz die falsche Farbe auf, bis die Oberfläche gezeichnet ist
|
||||||
|
function fensterFarbe() {
|
||||||
|
const design = (store.getSettings() || {}).theme;
|
||||||
|
return { light: '#f0f0f0', aero: '#05070d', lila: '#08050f', minecraft: '#313233' }[design] || '#1b1e24';
|
||||||
|
}
|
||||||
|
|
||||||
function windowIcon() {
|
function windowIcon() {
|
||||||
const png = path.join(__dirname, 'assets', 'logo.png');
|
const png = path.join(__dirname, 'assets', 'logo.png');
|
||||||
const ico = path.join(__dirname, 'assets', 'icon.ico');
|
const ico = path.join(__dirname, 'assets', 'icon.ico');
|
||||||
@@ -728,10 +758,12 @@ function createWindow(startHidden) {
|
|||||||
height: 820,
|
height: 820,
|
||||||
minWidth: 940,
|
minWidth: 940,
|
||||||
minHeight: 640,
|
minHeight: 640,
|
||||||
backgroundColor: '#1b1e24',
|
backgroundColor: fensterFarbe(),
|
||||||
title: 'AeroMC',
|
title: 'AeroMC',
|
||||||
icon: windowIcon(),
|
icon: windowIcon(),
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
|
titleBarStyle: 'hidden',
|
||||||
|
titleBarOverlay: titelleistenFarben((store.getSettings() || {}).theme),
|
||||||
show: !startHidden,
|
show: !startHidden,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: path.join(__dirname, 'preload.js'),
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
@@ -800,7 +832,7 @@ function oeffneVorbereitetenImport() {
|
|||||||
try {
|
try {
|
||||||
const daten = JSON.parse(fs.readFileSync(datei, 'utf8').replace(/^/, ''));
|
const daten = JSON.parse(fs.readFileSync(datei, 'utf8').replace(/^/, ''));
|
||||||
quellen = (Array.isArray(daten.sources) ? daten.sources : [])
|
quellen = (Array.isArray(daten.sources) ? daten.sources : [])
|
||||||
.filter((q) => q && ['mc', 'cf', 'mmc', 'technic'].includes(q.kind))
|
.filter((q) => q && ['mc', 'cf', 'mr', 'mmc', 'technic'].includes(q.kind))
|
||||||
.map((q) => ({ kind: q.kind, path: typeof q.path === 'string' ? q.path : '' }));
|
.map((q) => ({ kind: q.kind, path: typeof q.path === 'string' ? q.path : '' }));
|
||||||
} catch { /* beschädigt -> verwerfen */ }
|
} catch { /* beschädigt -> verwerfen */ }
|
||||||
try { fs.unlinkSync(datei); } catch { /* egal */ }
|
try { fs.unlinkSync(datei); } catch { /* egal */ }
|
||||||
@@ -1028,9 +1060,8 @@ ipcMain.handle('settings:save', (_e, patch) => {
|
|||||||
// (die speichert bei jedem Klick auf eine Instanz mit) – sonst baut das
|
// (die speichert bei jedem Klick auf eine Instanz mit) – sonst baut das
|
||||||
// Hauptfenster bei jedem Klick seine ganze Liste neu auf und die flackert.
|
// Hauptfenster bei jedem Klick seine ganze Liste neu auf und die flackert.
|
||||||
const nurLetzteInstanz = patch && Object.keys(patch).every((k) => k === 'lastSelectedId');
|
const nurLetzteInstanz = patch && Object.keys(patch).every((k) => k === 'lastSelectedId');
|
||||||
if (!nurLetzteInstanz && mainWindow && !mainWindow.isDestroyed()) {
|
// an alle Fenster: das Design muss auch in offenen Popup-Fenstern wechseln
|
||||||
mainWindow.webContents.send('settings:changed', s);
|
if (!nurLetzteInstanz) broadcast('settings:changed', s);
|
||||||
}
|
|
||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1733,6 +1764,30 @@ async function aktivesToken() {
|
|||||||
return acc && acc.mcAccessToken ? acc.mcAccessToken : null;
|
return acc && acc.mcAccessToken ? acc.mcAccessToken : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Armform (breit/schmal) eines beliebigen Kontos – steht in den Textur-Angaben
|
||||||
|
// des öffentlichen Profils bei Mojang (SKIN.metadata.model = "slim").
|
||||||
|
// Ergebnis 10 Minuten merken, nach eigenen Skin-Änderungen sofort verwerfen.
|
||||||
|
const armformCache = new Map(); // uuid -> { modell, zeit }
|
||||||
|
ipcMain.handle('skin:modelFor', async (_e, uuid) => {
|
||||||
|
const id = String(uuid || '').replace(/-/g, '').toLowerCase();
|
||||||
|
if (!/^[0-9a-f]{32}$/.test(id)) return 'classic';
|
||||||
|
const gemerkt = armformCache.get(id);
|
||||||
|
if (gemerkt && Date.now() - gemerkt.zeit < 10 * 60 * 1000) return gemerkt.modell;
|
||||||
|
try {
|
||||||
|
const res = await fetch('https://sessionserver.mojang.com/session/minecraft/profile/' + id, { headers: { 'User-Agent': 'AeroMC-Launcher' } });
|
||||||
|
if (!res.ok) return gemerkt ? gemerkt.modell : 'classic';
|
||||||
|
const profil = await res.json();
|
||||||
|
const eigenschaft = (profil.properties || []).find((p) => p.name === 'textures');
|
||||||
|
const texturen = eigenschaft ? JSON.parse(Buffer.from(eigenschaft.value, 'base64').toString('utf8')) : {};
|
||||||
|
const skin = texturen.textures && texturen.textures.SKIN;
|
||||||
|
const modell = skin && skin.metadata && skin.metadata.model === 'slim' ? 'slim' : 'classic';
|
||||||
|
armformCache.set(id, { modell, zeit: Date.now() });
|
||||||
|
return modell;
|
||||||
|
} catch {
|
||||||
|
return gemerkt ? gemerkt.modell : 'classic';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('skin:profile', async () => {
|
ipcMain.handle('skin:profile', async () => {
|
||||||
const token = await aktivesToken();
|
const token = await aktivesToken();
|
||||||
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
|
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
|
||||||
@@ -1764,7 +1819,7 @@ ipcMain.handle('skin:apply', async (_e, dateiPfad, variante) => {
|
|||||||
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
|
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
|
||||||
try {
|
try {
|
||||||
const res = await skins.setzeSkin(token, dateiPfad, variante);
|
const res = await skins.setzeSkin(token, dateiPfad, variante);
|
||||||
if (res && res.ok) broadcast('skin:changed');
|
if (res && res.ok) { armformCache.clear(); broadcast('skin:changed'); }
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
catch (err) { return { ok: false, message: 'Skin konnte nicht gesetzt werden: ' + err.message }; }
|
catch (err) { return { ok: false, message: 'Skin konnte nicht gesetzt werden: ' + err.message }; }
|
||||||
@@ -1775,11 +1830,78 @@ ipcMain.handle('skin:reset', async () => {
|
|||||||
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
|
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
|
||||||
try {
|
try {
|
||||||
const res = await skins.setzeSkinZurueck(token);
|
const res = await skins.setzeSkinZurueck(token);
|
||||||
if (res && res.ok) broadcast('skin:changed');
|
if (res && res.ok) { armformCache.clear(); broadcast('skin:changed'); }
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
catch (err) { return { ok: false, message: 'Zurücksetzen fehlgeschlagen: ' + err.message }; }
|
catch (err) { return { ok: false, message: 'Zurücksetzen fehlgeschlagen: ' + err.message }; }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------- Skin-Editor ----------
|
||||||
|
// Der Editor malt in eine Canvas – fremde Bildadressen (textures.minecraft.net)
|
||||||
|
// würden sie "verunreinigen" und das Auslesen der Pixel verbieten. Deshalb holt
|
||||||
|
// der Hauptprozess die Datei und reicht sie als data:-Adresse weiter.
|
||||||
|
const PNG_KENNUNG = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
||||||
|
function skinPngAusBase64(base64) {
|
||||||
|
const daten = Buffer.from(String(base64 || ''), 'base64');
|
||||||
|
if (daten.length < 33 || !daten.subarray(0, 8).equals(PNG_KENNUNG)) return null;
|
||||||
|
if (daten.readUInt32BE(16) !== 64 || daten.readUInt32BE(20) !== 64) return null;
|
||||||
|
return daten;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.handle('skin:currentData', async () => {
|
||||||
|
const token = await aktivesToken();
|
||||||
|
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
|
||||||
|
try {
|
||||||
|
const p = await skins.holeProfil(token);
|
||||||
|
if (!p.ok) return p;
|
||||||
|
if (!p.skinUrl) return { ok: false, standard: true, variante: p.variante, message: 'Das Konto nutzt den Standard-Skin.' };
|
||||||
|
const res = await fetch(p.skinUrl);
|
||||||
|
if (!res.ok) return { ok: false, message: 'Skin-Datei nicht abrufbar (HTTP ' + res.status + ').' };
|
||||||
|
const daten = Buffer.from(await res.arrayBuffer());
|
||||||
|
return { ok: true, name: p.name, variante: p.variante, dataUri: 'data:image/png;base64,' + daten.toString('base64') };
|
||||||
|
} catch (err) {
|
||||||
|
return { ok: false, message: 'Skin konnte nicht geladen werden: ' + err.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('skin:readData', (_e, dateiPfad) => {
|
||||||
|
const geprueft = skins.pruefeDatei(dateiPfad);
|
||||||
|
if (!geprueft.ok) return geprueft;
|
||||||
|
try { return { ok: true, dataUri: 'data:image/png;base64,' + fs.readFileSync(dateiPfad).toString('base64') }; }
|
||||||
|
catch (err) { return { ok: false, message: 'Datei nicht lesbar: ' + err.message }; }
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('skin:saveData', async (e, base64) => {
|
||||||
|
const daten = skinPngAusBase64(base64);
|
||||||
|
if (!daten) return { ok: false, message: 'Ungültige Skin-Daten.' };
|
||||||
|
const res = await dialog.showSaveDialog(BrowserWindow.fromWebContents(e.sender) || mainWindow, {
|
||||||
|
title: 'Skin speichern',
|
||||||
|
defaultPath: path.join(app.getPath('pictures'), 'skin.png'),
|
||||||
|
filters: [{ name: 'PNG-Bild', extensions: ['png'] }],
|
||||||
|
});
|
||||||
|
if (res.canceled || !res.filePath) return { ok: false, canceled: true };
|
||||||
|
try { fs.writeFileSync(res.filePath, daten); return { ok: true, pfad: res.filePath }; }
|
||||||
|
catch (err) { return { ok: false, message: 'Speichern fehlgeschlagen: ' + err.message }; }
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('skin:applyData', async (_e, base64, variante) => {
|
||||||
|
const daten = skinPngAusBase64(base64);
|
||||||
|
if (!daten) return { ok: false, message: 'Ungültige Skin-Daten.' };
|
||||||
|
const token = await aktivesToken();
|
||||||
|
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
|
||||||
|
const tmp = path.join(app.getPath('temp'), 'aeromc-skin-editor.png');
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(tmp, daten);
|
||||||
|
const res = await skins.setzeSkin(token, tmp, variante);
|
||||||
|
if (res && res.ok) { armformCache.clear(); broadcast('skin:changed', { quelle: 'editor' }); }
|
||||||
|
return res;
|
||||||
|
} catch (err) {
|
||||||
|
return { ok: false, message: 'Skin konnte nicht gesetzt werden: ' + err.message };
|
||||||
|
} finally {
|
||||||
|
try { fs.unlinkSync(tmp); } catch { /* egal */ }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('auth:remove', (_e, id) => auth.removeAccount(id));
|
ipcMain.handle('auth:remove', (_e, id) => auth.removeAccount(id));
|
||||||
ipcMain.handle('auth:logout', () => { auth.clearAccount(); return true; });
|
ipcMain.handle('auth:logout', () => { auth.clearAccount(); return true; });
|
||||||
|
|
||||||
@@ -3732,6 +3854,87 @@ ipcMain.handle('cf:import', (_e, entries, copyData) => {
|
|||||||
return { ok: true, imported: done, failed };
|
return { ok: true, imported: done, failed };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------- Modrinth App ----------
|
||||||
|
ipcMain.handle('mr:pickFolder', async (e) => {
|
||||||
|
const res = await dialog.showOpenDialog(BrowserWindow.fromWebContents(e.sender) || mainWindow, {
|
||||||
|
title: 'Datenordner der Modrinth App wählen (meist %APPDATA%\\ModrinthApp)',
|
||||||
|
defaultPath: mrimport.standardOrdner(),
|
||||||
|
properties: ['openDirectory'],
|
||||||
|
});
|
||||||
|
return res.canceled ? null : res.filePaths[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('mr:scan', async (_e, folder) => {
|
||||||
|
try { return await mrimport.scan(folder); }
|
||||||
|
catch (err) { return { ok: false, reason: err.message }; }
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* War die Instanz in der Modrinth App mit einem Modrinth-Modpack verknüpft,
|
||||||
|
* übernehmen wir die Verknüpfung – samt der Liste der Dateien, die zum Paket
|
||||||
|
* gehören. Ohne diese Liste würde "Modpack aktualisieren" alte Mods liegen
|
||||||
|
* lassen; deshalb wird die Paketdatei einmal geladen und nur ihr Inhaltsverzeichnis
|
||||||
|
* gelesen. Klappt das nicht (offline …), bleibt die Instanz ohne Verknüpfung.
|
||||||
|
*/
|
||||||
|
async function verknuepfeModrinthModpack(instId, link) {
|
||||||
|
const kopf = { headers: { 'User-Agent': 'AeroMC-Launcher', Accept: 'application/json' } };
|
||||||
|
const vres = await fetch(`https://api.modrinth.com/v2/version/${encodeURIComponent(link.versionId)}`, kopf);
|
||||||
|
if (!vres.ok) return false;
|
||||||
|
const version = await vres.json();
|
||||||
|
const datei = (version.files || []).find((f) => f.primary) || (version.files || [])[0];
|
||||||
|
if (!datei || !datei.url) return false;
|
||||||
|
const dl = await fetch(datei.url, { headers: { 'User-Agent': 'AeroMC-Launcher' } });
|
||||||
|
if (!dl.ok) return false;
|
||||||
|
const buf = Buffer.from(await dl.arrayBuffer());
|
||||||
|
const zip = forge.readZipIndex(buf);
|
||||||
|
if (!zip['modrinth.index.json']) return false;
|
||||||
|
const index = JSON.parse(forge.readZipEntry(buf, zip['modrinth.index.json']).toString('utf8').replace(/^\uFEFF/, ''));
|
||||||
|
const dateien = (index.files || [])
|
||||||
|
.filter((f) => f.path && !(f.env && f.env.client === 'unsupported'))
|
||||||
|
.map((f) => String(f.path).replace(/\\/g, '/'));
|
||||||
|
for (const prefix of ['overrides/', 'client-overrides/']) {
|
||||||
|
for (const name of Object.keys(zip)) if (name.startsWith(prefix) && !name.endsWith('/')) dateien.push(name.slice(prefix.length));
|
||||||
|
}
|
||||||
|
let titel = index.name || '';
|
||||||
|
try {
|
||||||
|
const pres = await fetch(`https://api.modrinth.com/v2/project/${encodeURIComponent(link.projectId)}`, kopf);
|
||||||
|
if (pres.ok) titel = (await pres.json()).title || titel;
|
||||||
|
} catch { /* Titel ist nur Zierde */ }
|
||||||
|
store.updateInstance(instId, {
|
||||||
|
modpack: {
|
||||||
|
quelle: 'modrinth',
|
||||||
|
name: index.name || titel,
|
||||||
|
title: titel,
|
||||||
|
projectId: link.projectId,
|
||||||
|
versionId: link.versionId,
|
||||||
|
versionNummer: version.version_number || '',
|
||||||
|
dateien,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.handle('mr:import', async (_e, entries, copyData) => {
|
||||||
|
const done = [];
|
||||||
|
const failed = [];
|
||||||
|
const liste = entries || [];
|
||||||
|
for (const entry of liste) {
|
||||||
|
try {
|
||||||
|
const imported = mrimport.importOne(store, entry, copyData !== false);
|
||||||
|
if (entry.modpack && entry.modpack.projectId && entry.modpack.versionId) {
|
||||||
|
broadcast('mr:progress', { current: done.length + failed.length, total: liste.length, detail: 'Modpack-Verknüpfung für ' + entry.name });
|
||||||
|
try { imported.modpack = await verknuepfeModrinthModpack(imported.id, entry.modpack); }
|
||||||
|
catch { imported.modpack = false; }
|
||||||
|
}
|
||||||
|
done.push(imported);
|
||||||
|
}
|
||||||
|
catch (err) { failed.push({ name: entry.name, message: err.message }); }
|
||||||
|
broadcast('mr:progress', { current: done.length + failed.length, total: liste.length });
|
||||||
|
}
|
||||||
|
if (done.length) broadcast('instances:changed');
|
||||||
|
return { ok: true, imported: done, failed };
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('mc:pickFolder', async (e) => {
|
ipcMain.handle('mc:pickFolder', async (e) => {
|
||||||
const res = await dialog.showOpenDialog(BrowserWindow.fromWebContents(e.sender) || mainWindow, {
|
const res = await dialog.showOpenDialog(BrowserWindow.fromWebContents(e.sender) || mainWindow, {
|
||||||
title: 'Minecraft-Launcher-Ordner wählen (meist .minecraft)',
|
title: 'Minecraft-Launcher-Ordner wählen (meist .minecraft)',
|
||||||
|
|||||||
Reference in New Issue
Block a user