392 lines
12 KiB
HTML
392 lines
12 KiB
HTML
<!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">← 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> |