Erster Commit

This commit is contained in:
2026-08-22 08:40:29 +02:00
commit 875477d425
1961 changed files with 930336 additions and 0 deletions
+392
View File
@@ -0,0 +1,392 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Mein Profil</title>
<style>
body {
margin: 0;
background: #111;
color: #eee;
font-family: Arial, sans-serif;
padding: 20px;
}
h1 { margin-top: 0; }
a.back { color: #6fbf73; text-decoration: none; font-size: 14px; }
a.back:hover { text-decoration: underline; }
main {
max-width: 500px;
margin: 20px auto 0;
}
.panel {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.panel h2 {
margin-top: 0;
border-bottom: 2px solid #333;
padding-bottom: 8px;
}
.stat-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #262626;
font-size: 15px;
}
.stat-row:last-child { border-bottom: none; }
.stat-row .label { color: #999; }
.badge {
display: inline-block;
border-radius: 4px;
padding: 2px 8px;
font-size: 12px;
background: #2f2a5e;
color: #b3a6ff;
}
.badge.online { background: #1a4d2a; color: #6fbf73; }
.badge.offline { background: #333; color: #999; }
.skin-btn {
width: 48px;
height: 48px;
font-size: 24px;
background: #222;
border: 2px solid #444;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.skin-btn.selected { border-color: #2c7a3d; background: #1a4d2a; }
.skin-btn:hover { border-color: #6fbf73; }
input {
width: 100%;
background: #222;
border: 1px solid #444;
color: #eee;
padding: 8px 10px;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
margin-bottom: 8px;
}
button {
background: #2c7a3d;
border: none;
color: white;
padding: 8px 14px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
button:hover { opacity: 0.85; }
.msg { font-size: 13px; color: #6fbf73; min-height: 18px; margin-top: 6px; }
</style>
</head>
<body>
<a class="back" href="/index.html">&larr; zurück zur Startseite</a>
<h1>👤 Mein Profil</h1>
<main>
<div class="panel">
<h2 id="profileUsername">...</h2>
<div class="stat-row"><span class="label">Status</span> <span id="onlineBadge"></span></div>
<div class="stat-row"><span class="label">Bargeld</span> <span id="statMoney"></span></div>
<div class="stat-row"><span class="label">Bank</span> <span id="statBank"></span></div>
<div class="stat-row"><span class="label">Leben</span> <span id="statHealth"></span></div>
<div class="stat-row"><span class="label">Hunger</span> <span id="statHunger"></span></div>
<div class="stat-row"><span class="label">Durst</span> <span id="statThirst"></span></div>
<div class="stat-row"><span class="label">Aktuelle Welt</span> <span id="statWorld"></span></div>
<div class="stat-row"><span class="label">Spielzeit</span> <span id="statPlaytime"></span></div>
<div class="stat-row"><span class="label">Rolle</span> <span id="statRole"></span></div>
</div>
<div class="panel">
<h2>Charakterfarbe</h2>
<p style="color:#888; font-size:13px; margin-top:0;">
So wirst du für andere Spieler im Spiel dargestellt.
</p>
<div style="display:flex; align-items:center; gap:12px;">
<input type="color" id="colorPicker" value="#f1c40f" style="width:60px; height:40px; padding:2px; cursor:pointer;">
<div id="colorPreview" style="width:32px; height:32px; border:2px solid #444; border-radius:4px; background:#f1c40f;"></div>
<button onclick="saveColor()">Speichern</button>
</div>
<div class="msg" id="colorMsg"></div>
</div>
<div class="panel">
<h2>Skin</h2>
<p style="color:#888; font-size:13px; margin-top:0;">
Wird zusätzlich zur Farbe über deinem Charakter angezeigt.
</p>
<div id="skinGrid" style="display:flex; flex-wrap:wrap; gap:8px;"></div>
<div class="msg" id="skinMsg"></div>
<h3 style="margin-top:20px; font-size:14px; color:#f5d90a;">🎨 Gemalte Skins</h3>
<p style="color:#888; font-size:13px; margin-top:0;">
Ersetzt das Emoji durch ein selbst gemaltes Bild über dem ganzen Körper.
</p>
<div id="paintedSkinGrid" style="display:flex; flex-wrap:wrap; gap:8px;"></div>
<div class="msg" id="paintedSkinMsg"></div>
</div>
<div class="panel">
<h2>Passwort ändern</h2>
<input type="password" id="currentPassword" placeholder="Aktuelles Passwort">
<input type="password" id="newPassword" placeholder="Neues Passwort">
<button onclick="changePassword()">Passwort ändern</button>
<div class="msg" id="pwMsg"></div>
</div>
</main>
<script>
const token = localStorage.getItem("token");
if (!token) {
alert("Bitte zuerst auf der Startseite einloggen.");
location.href = "/index.html";
}
async function authFetch(url, options = {}) {
options.headers = { ...(options.headers || {}), "Authorization": "Bearer " + token };
const res = await fetch(url, options);
if (res.status === 401) {
alert("Sitzung abgelaufen. Bitte erneut einloggen.");
location.href = "/index.html";
throw new Error("Nicht autorisiert");
}
return res;
}
async function loadProfile() {
const res = await authFetch("/api/me");
const data = await res.json();
if (!data.ok) {
alert("Profil konnte nicht geladen werden.");
return;
}
const p = data.player;
document.getElementById("profileUsername").textContent = p.username;
document.getElementById("onlineBadge").innerHTML = p.online
? `<span class="badge online">Online</span>`
: `<span class="badge offline">Offline</span>`;
document.getElementById("statMoney").textContent = p.money + "$";
document.getElementById("statBank").textContent = p.bank + "$";
document.getElementById("statHealth").textContent = Math.round(p.health);
document.getElementById("statHunger").textContent = Math.round(p.hunger);
document.getElementById("statThirst").textContent = Math.round(p.thirst);
document.getElementById("statWorld").textContent = p.world;
const playtimeSec = p.playtimeSeconds || 0;
const ph = Math.floor(playtimeSec / 3600);
const pm = Math.floor((playtimeSec % 3600) / 60);
document.getElementById("statPlaytime").textContent = `${ph}h ${pm}min`;
document.getElementById("statRole").innerHTML = p.isAdmin
? `<span class="badge">Admin</span>`
: "Spieler";
const color = p.color || "#f1c40f";
document.getElementById("colorPicker").value = color;
document.getElementById("colorPreview").style.background = color;
currentSkin = p.skin || "none";
renderSkinGrid();
currentSkinItemId = p.skinItemId || null;
loadPaintedSkins();
}
document.getElementById("colorPicker").addEventListener("input", e => {
document.getElementById("colorPreview").style.background = e.target.value;
});
// -------------------------------------------------------------
// SKIN
// -------------------------------------------------------------
let allSkins = ["none"];
let currentSkin = "none";
async function loadSkins() {
const res = await fetch("/api/skins");
const data = await res.json();
allSkins = data.skins || ["none"];
renderSkinGrid();
}
function renderSkinGrid() {
const grid = document.getElementById("skinGrid");
grid.innerHTML = "";
allSkins.forEach(skin => {
const btn = document.createElement("button");
btn.className = "skin-btn" + (skin === currentSkin ? " selected" : "");
btn.textContent = skin === "none" ? "❌" : skin;
btn.title = skin === "none" ? "Kein Skin" : skin;
btn.onclick = () => saveSkin(skin);
grid.appendChild(btn);
});
}
async function saveSkin(skin) {
const res = await authFetch("/api/me/skin", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ skin })
});
const data = await res.json();
if (data.ok) {
currentSkin = skin;
renderSkinGrid();
showSkinMsg("Skin gespeichert.");
} else {
showSkinMsg("Fehler: " + (data.error || "unbekannt"), true);
}
}
// -------------------------------------------------------------
// GEMALTE SKINS (Katalog, ersetzt das Emoji)
// -------------------------------------------------------------
let allPaintedSkins = [];
let currentSkinItemId = null;
async function loadPaintedSkins() {
const res = await fetch("/api/skin_catalog");
const data = await res.json();
allPaintedSkins = data.items || [];
renderPaintedSkinGrid();
}
function renderPaintedSkinGrid() {
const grid = document.getElementById("paintedSkinGrid");
grid.innerHTML = "";
const noneBtn = document.createElement("button");
noneBtn.className = "skin-btn" + (currentSkinItemId === null ? " selected" : "");
noneBtn.textContent = "❌";
noneBtn.title = "Keinen gemalten Skin nutzen (zurück zum Emoji)";
noneBtn.onclick = () => savePaintedSkin(null);
grid.appendChild(noneBtn);
allPaintedSkins.forEach(item => {
const btn = document.createElement("button");
btn.className = "skin-btn" + (item.id === currentSkinItemId ? " selected" : "");
btn.style.backgroundImage = `url(${item.image})`;
btn.style.backgroundSize = "cover";
btn.style.imageRendering = "pixelated";
btn.title = item.name;
btn.onclick = () => savePaintedSkin(item.id);
grid.appendChild(btn);
});
}
async function savePaintedSkin(skinId) {
const res = await authFetch("/api/me/skin_item", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ skinId })
});
const data = await res.json();
if (data.ok) {
currentSkinItemId = skinId;
renderPaintedSkinGrid();
showPaintedSkinMsg("Gespeichert.");
} else {
showPaintedSkinMsg("Fehler: " + (data.error || "unbekannt"), true);
}
}
function showPaintedSkinMsg(text, isError) {
const el = document.getElementById("paintedSkinMsg");
el.style.color = isError ? "#e06c6c" : "#6fbf73";
el.textContent = text;
setTimeout(() => { el.textContent = ""; }, 4000);
}
function showSkinMsg(text, isError) {
const el = document.getElementById("skinMsg");
el.style.color = isError ? "#e06c6c" : "#6fbf73";
el.textContent = text;
setTimeout(() => { el.textContent = ""; }, 4000);
}
async function saveColor() {
const color = document.getElementById("colorPicker").value;
const res = await authFetch("/api/me/color", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ color })
});
const data = await res.json();
if (data.ok) {
showColorMsg("Farbe gespeichert.");
} else {
showColorMsg("Fehler: " + (data.error || "unbekannt"), true);
}
}
function showColorMsg(text, isError) {
const el = document.getElementById("colorMsg");
el.style.color = isError ? "#e06c6c" : "#6fbf73";
el.textContent = text;
setTimeout(() => { el.textContent = ""; }, 4000);
}
async function changePassword() {
const currentPassword = document.getElementById("currentPassword").value;
const newPassword = document.getElementById("newPassword").value;
if (!currentPassword || !newPassword) {
showMsg("Bitte beide Felder ausfüllen.", true);
return;
}
const res = await authFetch("/api/me/password", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ currentPassword, newPassword })
});
const data = await res.json();
if (data.ok) {
showMsg("Passwort geändert.");
document.getElementById("currentPassword").value = "";
document.getElementById("newPassword").value = "";
} else {
showMsg("Fehler: " + (data.error || "unbekannt"), true);
}
}
function showMsg(text, isError) {
const el = document.getElementById("pwMsg");
el.style.color = isError ? "#e06c6c" : "#6fbf73";
el.textContent = text;
setTimeout(() => { el.textContent = ""; }, 4000);
}
loadSkins();
loadProfile();
</script>
</body>
</html>