uploads
This commit is contained in:
14
web/mobile/public/assets/avatars/avatar-01.svg
Normal file
14
web/mobile/public/assets/avatars/avatar-01.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<defs>
|
||||
<linearGradient id="bg1" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#28f0ff"/>
|
||||
<stop offset="1" stop-color="#ff3df2"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="20" fill="url(#bg1)"/>
|
||||
<circle cx="64" cy="54" r="26" fill="rgba(0,0,0,0.35)"/>
|
||||
<path d="M28 108c8-18 24-28 36-28h0c12 0 28 10 36 28" fill="rgba(0,0,0,0.35)"/>
|
||||
<circle cx="64" cy="54" r="22" fill="rgba(255,255,255,0.85)"/>
|
||||
<rect x="48" y="42" width="32" height="8" rx="4" fill="#0d1b26"/>
|
||||
<rect x="52" y="58" width="24" height="6" rx="3" fill="#0d1b26"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 663 B |
15
web/mobile/public/assets/avatars/avatar-02.svg
Normal file
15
web/mobile/public/assets/avatars/avatar-02.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<defs>
|
||||
<linearGradient id="bg2" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#ffb347"/>
|
||||
<stop offset="1" stop-color="#ff5f6d"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="20" fill="url(#bg2)"/>
|
||||
<circle cx="64" cy="52" r="26" fill="rgba(0,0,0,0.3)"/>
|
||||
<path d="M24 108c6-16 22-26 40-26h0c18 0 34 10 40 26" fill="rgba(0,0,0,0.3)"/>
|
||||
<circle cx="64" cy="52" r="22" fill="rgba(255,255,255,0.86)"/>
|
||||
<path d="M44 56h40" stroke="#2a0d0d" stroke-width="6" stroke-linecap="round"/>
|
||||
<circle cx="54" cy="48" r="5" fill="#2a0d0d"/>
|
||||
<circle cx="74" cy="48" r="5" fill="#2a0d0d"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 704 B |
14
web/mobile/public/assets/avatars/avatar-03.svg
Normal file
14
web/mobile/public/assets/avatars/avatar-03.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<defs>
|
||||
<linearGradient id="bg3" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#3dffb5"/>
|
||||
<stop offset="1" stop-color="#2b8cff"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="20" fill="url(#bg3)"/>
|
||||
<circle cx="64" cy="50" r="26" fill="rgba(0,0,0,0.28)"/>
|
||||
<path d="M22 108c10-18 26-28 42-28h0c16 0 32 10 42 28" fill="rgba(0,0,0,0.28)"/>
|
||||
<circle cx="64" cy="50" r="22" fill="rgba(255,255,255,0.88)"/>
|
||||
<rect x="46" y="44" width="36" height="10" rx="5" fill="#092022"/>
|
||||
<rect x="48" y="60" width="32" height="6" rx="3" fill="#092022"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 665 B |
14
web/mobile/public/assets/avatars/avatar-04.svg
Normal file
14
web/mobile/public/assets/avatars/avatar-04.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<defs>
|
||||
<linearGradient id="bg4" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#6dd5fa"/>
|
||||
<stop offset="1" stop-color="#5b8cff"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="20" fill="url(#bg4)"/>
|
||||
<circle cx="64" cy="52" r="26" fill="rgba(0,0,0,0.3)"/>
|
||||
<path d="M26 108c8-18 24-28 38-28h0c14 0 30 10 38 28" fill="rgba(0,0,0,0.3)"/>
|
||||
<circle cx="64" cy="52" r="22" fill="rgba(255,255,255,0.86)"/>
|
||||
<path d="M46 56h36" stroke="#0b1b2e" stroke-width="6" stroke-linecap="round"/>
|
||||
<path d="M52 44h24" stroke="#0b1b2e" stroke-width="6" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 687 B |
15
web/mobile/public/assets/avatars/avatar-05.svg
Normal file
15
web/mobile/public/assets/avatars/avatar-05.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<defs>
|
||||
<linearGradient id="bg5" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#ff6b6b"/>
|
||||
<stop offset="1" stop-color="#f7b733"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="20" fill="url(#bg5)"/>
|
||||
<circle cx="64" cy="50" r="26" fill="rgba(0,0,0,0.32)"/>
|
||||
<path d="M22 108c10-16 26-26 42-26h0c16 0 32 10 42 26" fill="rgba(0,0,0,0.32)"/>
|
||||
<circle cx="64" cy="50" r="22" fill="rgba(255,255,255,0.86)"/>
|
||||
<circle cx="54" cy="50" r="5" fill="#2b0d0d"/>
|
||||
<circle cx="74" cy="50" r="5" fill="#2b0d0d"/>
|
||||
<rect x="50" y="62" width="28" height="6" rx="3" fill="#2b0d0d"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 694 B |
14
web/mobile/public/assets/avatars/avatar-06.svg
Normal file
14
web/mobile/public/assets/avatars/avatar-06.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<defs>
|
||||
<linearGradient id="bg6" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#00f5d4"/>
|
||||
<stop offset="1" stop-color="#00b4d8"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="20" fill="url(#bg6)"/>
|
||||
<circle cx="64" cy="52" r="26" fill="rgba(0,0,0,0.28)"/>
|
||||
<path d="M24 108c8-18 24-28 40-28h0c16 0 32 10 40 28" fill="rgba(0,0,0,0.28)"/>
|
||||
<circle cx="64" cy="52" r="22" fill="rgba(255,255,255,0.88)"/>
|
||||
<rect x="46" y="46" width="36" height="8" rx="4" fill="#072127"/>
|
||||
<path d="M52 60h24" stroke="#072127" stroke-width="6" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 676 B |
@@ -416,6 +416,53 @@ button:active, .btn:active, input[type="submit"]:active{ transform: translateY(0
|
||||
|
||||
.actions{ display:flex; gap: 12px; margin-top: 14px; flex-wrap: wrap; }
|
||||
|
||||
/* Auth */
|
||||
.auth-view{ display:flex; flex-direction:column; gap: 16px; }
|
||||
.auth-card{ max-width: 720px; margin: 0 auto; width: 100%; }
|
||||
.form-row{ margin-top: 12px; }
|
||||
.input{
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(145,220,255,.22);
|
||||
background: rgba(0,0,0,.28);
|
||||
color: var(--text);
|
||||
}
|
||||
.input:focus{ outline: 2px solid rgba(66,245,255,.35); }
|
||||
.auth-grid{
|
||||
display:grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
margin-top: 12px;
|
||||
}
|
||||
.auth-choice{
|
||||
border: 1px solid rgba(145,220,255,.18);
|
||||
background: rgba(0,0,0,.18);
|
||||
border-radius: 14px;
|
||||
padding: 12px;
|
||||
color: var(--text);
|
||||
text-align: left;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
gap: 6px;
|
||||
cursor:pointer;
|
||||
}
|
||||
.auth-choice:hover{ border-color: rgba(66,245,255,.45); box-shadow: var(--glow-cyan); transform: translateY(-1px); }
|
||||
.auth-choice.is-selected{
|
||||
border-color: rgba(66,245,255,.55);
|
||||
box-shadow: var(--glow-cyan);
|
||||
background: linear-gradient(180deg, rgba(66,245,255,.16), rgba(91,124,255,.10));
|
||||
}
|
||||
.auth-choice-title{ font-family: var(--font-sci); letter-spacing: .6px; }
|
||||
.auth-choice-meta{ color: var(--muted); font-size: .85rem; }
|
||||
.auth-message{ min-height: 18px; margin-top: 8px; }
|
||||
.auth-avatar{
|
||||
width: 100%;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(145,220,255,.18);
|
||||
background: rgba(0,0,0,.22);
|
||||
}
|
||||
|
||||
/* Resourcebar sticky */
|
||||
.resourcebar{
|
||||
position: sticky;
|
||||
|
||||
@@ -119,28 +119,31 @@
|
||||
});
|
||||
});
|
||||
|
||||
const authView = document.getElementById("authView");
|
||||
const gameView = document.getElementById("gameView");
|
||||
let stateTimer = null;
|
||||
const authPanels = {
|
||||
login: document.getElementById("authLogin"),
|
||||
"register-step1": document.getElementById("authRegisterStep1"),
|
||||
"register-step2": document.getElementById("authRegisterStep2"),
|
||||
"register-step3": document.getElementById("authRegisterStep3")
|
||||
};
|
||||
let racesCache = null;
|
||||
let avatarsCache = null;
|
||||
const regDraft = { race_key: null, avatar_key: null };
|
||||
|
||||
function formatNumber(val){
|
||||
const num = Number.isFinite(val) ? val : 0;
|
||||
return Math.round(num).toLocaleString("de-DE");
|
||||
}
|
||||
|
||||
const resourceMap = {
|
||||
"Metall": "metal",
|
||||
"Kristall": "crystals",
|
||||
"Deuterium": "deuterium",
|
||||
"Energie": "energy"
|
||||
};
|
||||
|
||||
function updateResourceBar(state){
|
||||
const stats = document.querySelectorAll(".resource-row .stat");
|
||||
stats.forEach((stat)=>{
|
||||
const label = stat.querySelector(".stat-k")?.textContent?.trim();
|
||||
const key = resourceMap[label];
|
||||
const values = state?.resources || {};
|
||||
document.querySelectorAll("[data-resource-value]").forEach((valueEl)=>{
|
||||
const key = valueEl.dataset.resourceValue;
|
||||
if(!key) return;
|
||||
const value = state?.resources?.[key];
|
||||
const value = values[key];
|
||||
if(typeof value !== "number") return;
|
||||
const valueEl = stat.querySelector(".stat-v");
|
||||
if(!valueEl) return;
|
||||
const dot = valueEl.querySelector(".dot");
|
||||
const display = key === "energy" ? Math.round(value) : Math.floor(value);
|
||||
const prefix = key === "energy" && display >= 0 ? "+" : "";
|
||||
@@ -150,19 +153,189 @@
|
||||
});
|
||||
}
|
||||
|
||||
function updateQueuePanel(state){
|
||||
const slotsEl = document.getElementById("queueSlots");
|
||||
if(slotsEl){
|
||||
const slots = Number.isFinite(state?.queue_slots) ? state.queue_slots : null;
|
||||
slotsEl.textContent = slots === null ? "–" : String(slots);
|
||||
}
|
||||
const list = document.getElementById("queueList");
|
||||
if(!list) return;
|
||||
list.textContent = "";
|
||||
const jobs = Array.isArray(state?.active_build_jobs) ? state.active_build_jobs : [];
|
||||
if(jobs.length === 0){
|
||||
const li = document.createElement("li");
|
||||
li.className = "muted";
|
||||
li.textContent = "Keine aktiven Baujobs.";
|
||||
list.appendChild(li);
|
||||
return;
|
||||
}
|
||||
jobs.forEach((job)=>{
|
||||
const li = document.createElement("li");
|
||||
const slotIndex = Number.isFinite(Number(job?.slot_index)) ? Number(job.slot_index) + 1 : null;
|
||||
const slotLabel = slotIndex ? `Slot ${slotIndex}: ` : "";
|
||||
const key = job?.building_key ? String(job.building_key) : "Unbekannt";
|
||||
const finish = job?.finish_at ? ` · fertig ${job.finish_at}` : "";
|
||||
li.textContent = `${slotLabel}${key}${finish}`;
|
||||
list.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function showAuthView(){
|
||||
if(authView) authView.removeAttribute("hidden");
|
||||
if(gameView) gameView.setAttribute("hidden", "");
|
||||
}
|
||||
|
||||
function showGameView(){
|
||||
if(authView) authView.setAttribute("hidden", "");
|
||||
if(gameView) gameView.removeAttribute("hidden");
|
||||
}
|
||||
|
||||
function showAuthPanel(key){
|
||||
Object.values(authPanels).forEach((panel)=>{
|
||||
if(panel) panel.setAttribute("hidden", "");
|
||||
});
|
||||
const panel = authPanels[key];
|
||||
if(panel) panel.removeAttribute("hidden");
|
||||
}
|
||||
|
||||
function setMessage(el, message){
|
||||
if(!el) return;
|
||||
el.textContent = message || "";
|
||||
}
|
||||
|
||||
async function fetchJson(url, options){
|
||||
try{
|
||||
const res = await fetch(url, options);
|
||||
const data = await res.json().catch(()=> ({}));
|
||||
return { ok: res.ok, status: res.status, data };
|
||||
}catch(e){
|
||||
return { ok: false, status: 0, data: {} };
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRaces(){
|
||||
if(racesCache) return racesCache;
|
||||
const result = await fetchJson("/api/meta/races");
|
||||
if(result.ok){
|
||||
racesCache = result.data.races || [];
|
||||
}else{
|
||||
racesCache = [];
|
||||
}
|
||||
return racesCache;
|
||||
}
|
||||
|
||||
async function loadAvatars(){
|
||||
if(avatarsCache) return avatarsCache;
|
||||
const result = await fetchJson("/api/meta/avatars");
|
||||
if(result.ok){
|
||||
avatarsCache = result.data.avatars || [];
|
||||
}else{
|
||||
avatarsCache = [];
|
||||
}
|
||||
return avatarsCache;
|
||||
}
|
||||
|
||||
function renderRaces(races){
|
||||
const list = document.getElementById("raceList");
|
||||
if(!list) return;
|
||||
list.textContent = "";
|
||||
if(!Array.isArray(races) || races.length === 0){
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "muted";
|
||||
empty.textContent = "Keine Rassen verfügbar.";
|
||||
list.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
races.forEach((race)=>{
|
||||
const btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.className = "auth-choice";
|
||||
btn.dataset.raceKey = race.key;
|
||||
if(regDraft.race_key === race.key) btn.classList.add("is-selected");
|
||||
const summary = Array.isArray(race.modifier_summary) ? race.modifier_summary : [];
|
||||
btn.innerHTML = `
|
||||
<div class="auth-choice-title">${escapeHtml(race.name || race.key)}</div>
|
||||
<div class="auth-choice-meta">${escapeHtml(race.description || "")}</div>
|
||||
${summary.length ? `<div class="auth-choice-meta">${summary.map(s=>escapeHtml(s)).join("<br>")}</div>` : ""}
|
||||
`;
|
||||
btn.addEventListener("click", ()=>{
|
||||
regDraft.race_key = race.key;
|
||||
list.querySelectorAll(".auth-choice").forEach(el=>el.classList.remove("is-selected"));
|
||||
btn.classList.add("is-selected");
|
||||
});
|
||||
list.appendChild(btn);
|
||||
});
|
||||
}
|
||||
|
||||
function renderAvatars(avatars){
|
||||
const list = document.getElementById("avatarList");
|
||||
if(!list) return;
|
||||
list.textContent = "";
|
||||
if(!Array.isArray(avatars) || avatars.length === 0){
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "muted";
|
||||
empty.textContent = "Keine Avatare verfügbar.";
|
||||
list.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
avatars.forEach((avatar)=>{
|
||||
const btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.className = "auth-choice";
|
||||
btn.dataset.avatarKey = avatar.key;
|
||||
if(regDraft.avatar_key === avatar.key) btn.classList.add("is-selected");
|
||||
btn.innerHTML = `
|
||||
<img class="auth-avatar" src="${escapeHtml(avatar.image || "")}" alt="${escapeHtml(avatar.label || avatar.key)}">
|
||||
<div class="auth-choice-title">${escapeHtml(avatar.label || avatar.key)}</div>
|
||||
`;
|
||||
btn.addEventListener("click", ()=>{
|
||||
regDraft.avatar_key = avatar.key;
|
||||
list.querySelectorAll(".auth-choice").forEach(el=>el.classList.remove("is-selected"));
|
||||
btn.classList.add("is-selected");
|
||||
});
|
||||
list.appendChild(btn);
|
||||
});
|
||||
}
|
||||
|
||||
function startPolling(){
|
||||
if(stateTimer) return;
|
||||
stateTimer = setInterval(refreshState, 15000);
|
||||
}
|
||||
|
||||
function stopPolling(){
|
||||
if(!stateTimer) return;
|
||||
clearInterval(stateTimer);
|
||||
stateTimer = null;
|
||||
}
|
||||
|
||||
async function fetchState(){
|
||||
try{
|
||||
const res = await fetch("/api/state");
|
||||
if(!res.ok) return null;
|
||||
return await res.json();
|
||||
const res = await fetch("/api/state", { credentials: "same-origin" });
|
||||
if(res.status === 401) return { status: 401 };
|
||||
if(!res.ok) return { status: res.status };
|
||||
const data = await res.json();
|
||||
return { status: 200, data };
|
||||
}catch(e){
|
||||
return null;
|
||||
return { status: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshState(){
|
||||
const state = await fetchState();
|
||||
if(state) updateResourceBar(state);
|
||||
const result = await fetchState();
|
||||
if(result.status === 200){
|
||||
showGameView();
|
||||
updateResourceBar(result.data);
|
||||
updateQueuePanel(result.data);
|
||||
ensureBuildButton();
|
||||
startPolling();
|
||||
return;
|
||||
}
|
||||
if(result.status === 401){
|
||||
showAuthView();
|
||||
showAuthPanel("login");
|
||||
stopPolling();
|
||||
}
|
||||
}
|
||||
|
||||
function ensureBuildButton(){
|
||||
@@ -198,8 +371,130 @@
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", ()=>{
|
||||
document.querySelectorAll("[data-auth-switch]").forEach((btn)=>{
|
||||
btn.addEventListener("click", async ()=>{
|
||||
const target = btn.dataset.authSwitch;
|
||||
if(!target) return;
|
||||
showAuthView();
|
||||
showAuthPanel(target);
|
||||
if(target === "register-step1"){
|
||||
const races = await loadRaces();
|
||||
renderRaces(races);
|
||||
}
|
||||
if(target === "register-step2"){
|
||||
const avatars = await loadAvatars();
|
||||
renderAvatars(avatars);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const loginForm = document.getElementById("loginForm");
|
||||
if(loginForm){
|
||||
loginForm.addEventListener("submit", async (e)=>{
|
||||
e.preventDefault();
|
||||
const identifier = document.getElementById("loginIdentifier")?.value?.trim() || "";
|
||||
const password = document.getElementById("loginPassword")?.value || "";
|
||||
const message = document.getElementById("loginMessage");
|
||||
setMessage(message, "");
|
||||
const result = await fetchJson("/api/auth/login", {
|
||||
method: "POST",
|
||||
headers: {"Content-Type":"application/json"},
|
||||
body: JSON.stringify({ username_or_email: identifier, password })
|
||||
});
|
||||
if(result.ok){
|
||||
showGameView();
|
||||
refreshState();
|
||||
}else{
|
||||
setMessage(message, result.data?.message || "Login fehlgeschlagen.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const raceNext = document.getElementById("raceNext");
|
||||
if(raceNext){
|
||||
raceNext.addEventListener("click", async ()=>{
|
||||
const message = document.getElementById("raceMessage");
|
||||
setMessage(message, "");
|
||||
if(!regDraft.race_key){
|
||||
setMessage(message, "Bitte eine Rasse wählen.");
|
||||
return;
|
||||
}
|
||||
const result = await fetchJson("/api/auth/register/step1", {
|
||||
method: "POST",
|
||||
headers: {"Content-Type":"application/json"},
|
||||
body: JSON.stringify({ race_key: regDraft.race_key })
|
||||
});
|
||||
if(result.ok){
|
||||
showAuthPanel("register-step2");
|
||||
const avatars = await loadAvatars();
|
||||
renderAvatars(avatars);
|
||||
}else{
|
||||
setMessage(message, result.data?.message || "Schritt 1 fehlgeschlagen.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const avatarNext = document.getElementById("avatarNext");
|
||||
if(avatarNext){
|
||||
avatarNext.addEventListener("click", async ()=>{
|
||||
const title = document.getElementById("regTitle")?.value?.trim() || "";
|
||||
const message = document.getElementById("avatarMessage");
|
||||
setMessage(message, "");
|
||||
if(!regDraft.avatar_key){
|
||||
setMessage(message, "Bitte einen Avatar wählen.");
|
||||
return;
|
||||
}
|
||||
if(title.length < 2){
|
||||
setMessage(message, "Titel ist zu kurz.");
|
||||
return;
|
||||
}
|
||||
const result = await fetchJson("/api/auth/register/step2", {
|
||||
method: "POST",
|
||||
headers: {"Content-Type":"application/json"},
|
||||
body: JSON.stringify({ avatar_key: regDraft.avatar_key, title })
|
||||
});
|
||||
if(result.ok){
|
||||
showAuthPanel("register-step3");
|
||||
}else{
|
||||
setMessage(message, result.data?.message || "Schritt 2 fehlgeschlagen.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const registerForm = document.getElementById("registerForm");
|
||||
if(registerForm){
|
||||
registerForm.addEventListener("submit", async (e)=>{
|
||||
e.preventDefault();
|
||||
const username = document.getElementById("regUsername")?.value?.trim() || "";
|
||||
const email = document.getElementById("regEmail")?.value?.trim() || "";
|
||||
const password = document.getElementById("regPassword")?.value || "";
|
||||
const message = document.getElementById("registerMessage");
|
||||
setMessage(message, "");
|
||||
const result = await fetchJson("/api/auth/register/step3", {
|
||||
method: "POST",
|
||||
headers: {"Content-Type":"application/json"},
|
||||
body: JSON.stringify({ username, email, password })
|
||||
});
|
||||
if(result.ok){
|
||||
showGameView();
|
||||
refreshState();
|
||||
}else{
|
||||
setMessage(message, result.data?.message || "Registrierung fehlgeschlagen.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const logoutBtn = document.getElementById("logoutBtn");
|
||||
if(logoutBtn){
|
||||
logoutBtn.addEventListener("click", async ()=>{
|
||||
await fetchJson("/api/auth/logout", { method: "POST" });
|
||||
stopPolling();
|
||||
showAuthView();
|
||||
showAuthPanel("login");
|
||||
});
|
||||
}
|
||||
|
||||
showAuthPanel("login");
|
||||
refreshState();
|
||||
ensureBuildButton();
|
||||
setInterval(refreshState, 30000);
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -103,7 +103,14 @@ $partialsPath = __DIR__ . '/../src/partials';
|
||||
<div class="space-bg" aria-hidden="true"></div>
|
||||
|
||||
<div class="container">
|
||||
<div class="app">
|
||||
<div class="auth-view" id="authView" hidden>
|
||||
<?php include $partialsPath . '/auth-login.php'; ?>
|
||||
<?php include $partialsPath . '/auth-register-step1.php'; ?>
|
||||
<?php include $partialsPath . '/auth-register-step2.php'; ?>
|
||||
<?php include $partialsPath . '/auth-register-step3.php'; ?>
|
||||
</div>
|
||||
|
||||
<div class="app" id="gameView">
|
||||
|
||||
<!-- LINKS: Sidebar -->
|
||||
<aside class="sidebar" aria-label="Seitenleiste">
|
||||
@@ -132,6 +139,7 @@ $partialsPath = __DIR__ . '/../src/partials';
|
||||
🔔 <span class="badge" id="notifBadge">3</span>
|
||||
</button>
|
||||
<button class="btn btn-primary" type="button" onclick="toast('success','Beacon','Signal empfangen ✅')">Test Toast</button>
|
||||
<button class="btn" type="button" id="logoutBtn">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -211,6 +219,14 @@ $partialsPath = __DIR__ . '/../src/partials';
|
||||
</div>
|
||||
|
||||
<?php include $partialsPath . '/site.php'; ?>
|
||||
|
||||
<section class="card inner panel" id="queuePanel" style="margin-top:14px;">
|
||||
<h2 class="h2">Bauqueue (Live)</h2>
|
||||
<div class="muted">Slots: <span id="queueSlots">0</span></div>
|
||||
<ul id="queueList">
|
||||
<li class="muted">Keine aktiven Baujobs.</li>
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
|
||||
23
web/mobile/src/partials/auth-login.php
Normal file
23
web/mobile/src/partials/auth-login.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<section class="card panel auth-card" id="authLogin">
|
||||
<div class="panel-title">LOGIN</div>
|
||||
<p class="muted">Melde dich an, um deine Kolonie zu laden.</p>
|
||||
|
||||
<form id="loginForm">
|
||||
<div class="form-row">
|
||||
<label class="label" for="loginIdentifier">Username oder E-Mail</label>
|
||||
<input class="input" id="loginIdentifier" name="username_or_email" type="text" autocomplete="username" required>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="label" for="loginPassword">Passwort</label>
|
||||
<input class="input" id="loginPassword" name="password" type="password" autocomplete="current-password" required>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn btn-primary" type="submit">Login</button>
|
||||
<button class="btn" type="button" data-auth-switch="register-step1">Registrieren</button>
|
||||
</div>
|
||||
|
||||
<div class="auth-message muted tiny" id="loginMessage" aria-live="polite"></div>
|
||||
</form>
|
||||
</section>
|
||||
16
web/mobile/src/partials/auth-register-step1.php
Normal file
16
web/mobile/src/partials/auth-register-step1.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<section class="card panel auth-card" id="authRegisterStep1" hidden>
|
||||
<div class="panel-title">REGISTRIERUNG 1/3</div>
|
||||
<h2 class="h2">Wähle deine Rasse</h2>
|
||||
<p class="muted">Rasse bestimmt Startboni. Du kannst später über Forschungen nachsteuern.</p>
|
||||
|
||||
<div class="auth-grid" id="raceList">
|
||||
<div class="muted">Lade Rassen ...</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn" type="button" data-auth-switch="login">Zurück</button>
|
||||
<button class="btn btn-primary" type="button" id="raceNext">Weiter</button>
|
||||
</div>
|
||||
|
||||
<div class="auth-message muted tiny" id="raceMessage" aria-live="polite"></div>
|
||||
</section>
|
||||
22
web/mobile/src/partials/auth-register-step2.php
Normal file
22
web/mobile/src/partials/auth-register-step2.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<section class="card panel auth-card" id="authRegisterStep2" hidden>
|
||||
<div class="panel-title">REGISTRIERUNG 2/3</div>
|
||||
<h2 class="h2">Avatar & Titel</h2>
|
||||
<p class="muted">Wähle einen Avatar und gib deinem Captain einen Titel.</p>
|
||||
|
||||
<div class="auth-grid avatar-grid" id="avatarList">
|
||||
<div class="muted">Lade Avatare ...</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="label" for="regTitle">Titel</label>
|
||||
<input class="input" id="regTitle" name="title" type="text" maxlength="40" placeholder="z.B. Pionier, Architekt, Navigator" required>
|
||||
<div class="muted tiny">2–40 Zeichen.</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn" type="button" data-auth-switch="register-step1">Zurück</button>
|
||||
<button class="btn btn-primary" type="button" id="avatarNext">Weiter</button>
|
||||
</div>
|
||||
|
||||
<div class="auth-message muted tiny" id="avatarMessage" aria-live="polite"></div>
|
||||
</section>
|
||||
30
web/mobile/src/partials/auth-register-step3.php
Normal file
30
web/mobile/src/partials/auth-register-step3.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<section class="card panel auth-card" id="authRegisterStep3" hidden>
|
||||
<div class="panel-title">REGISTRIERUNG 3/3</div>
|
||||
<h2 class="h2">Account erstellen</h2>
|
||||
<p class="muted">Wähle deinen Accountnamen und sichere dein Profil.</p>
|
||||
|
||||
<form id="registerForm">
|
||||
<div class="form-row">
|
||||
<label class="label" for="regUsername">Username</label>
|
||||
<input class="input" id="regUsername" name="username" type="text" autocomplete="username" required>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="label" for="regEmail">E-Mail</label>
|
||||
<input class="input" id="regEmail" name="email" type="email" autocomplete="email" required>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="label" for="regPassword">Passwort</label>
|
||||
<input class="input" id="regPassword" name="password" type="password" autocomplete="new-password" required>
|
||||
<div class="muted tiny">Mindestens 8 Zeichen.</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn" type="button" data-auth-switch="register-step2">Zurück</button>
|
||||
<button class="btn btn-primary" type="submit">Account erstellen</button>
|
||||
</div>
|
||||
|
||||
<div class="auth-message muted tiny" id="registerMessage" aria-live="polite"></div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -5,24 +5,49 @@
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat">
|
||||
<div class="stat" data-resource="metal">
|
||||
<div class="stat-k">Metall</div>
|
||||
<div class="stat-v"><span class="dot dot-cyan"></span> 12.340</div>
|
||||
<div class="stat-v" id="res-metal" data-resource-value="metal"><span class="dot dot-cyan"></span> 12.340</div>
|
||||
<div class="stat-bar"><span style="width:72%"></span></div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat" data-resource="crystals">
|
||||
<div class="stat-k">Kristall</div>
|
||||
<div class="stat-v"><span class="dot dot-pink"></span> 6.120</div>
|
||||
<div class="stat-v" id="res-crystals" data-resource-value="crystals"><span class="dot dot-pink"></span> 6.120</div>
|
||||
<div class="stat-bar"><span style="width:44%"></span></div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat" data-resource="deuterium">
|
||||
<div class="stat-k">Deuterium</div>
|
||||
<div class="stat-v"><span class="dot dot-green"></span> 3.880</div>
|
||||
<div class="stat-v" id="res-deuterium" data-resource-value="deuterium"><span class="dot dot-green"></span> 3.880</div>
|
||||
<div class="stat-bar"><span style="width:18%"></span></div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat" data-resource="energy">
|
||||
<div class="stat-k">Energie</div>
|
||||
<div class="stat-v"><span class="dot dot-warn"></span> +120</div>
|
||||
<div class="stat-v" id="res-energy" data-resource-value="energy"><span class="dot dot-warn"></span> +120</div>
|
||||
<div class="stat-bar"><span style="width:60%"></span></div>
|
||||
</div>
|
||||
<div class="stat" data-resource="alloy">
|
||||
<div class="stat-k">Legierung</div>
|
||||
<div class="stat-v" id="res-alloy" data-resource-value="alloy"><span class="dot dot-cyan"></span> 0</div>
|
||||
<div class="stat-bar"><span style="width:40%"></span></div>
|
||||
</div>
|
||||
<div class="stat" data-resource="credits">
|
||||
<div class="stat-k">Credits</div>
|
||||
<div class="stat-v" id="res-credits" data-resource-value="credits"><span class="dot dot-pink"></span> 0</div>
|
||||
<div class="stat-bar"><span style="width:35%"></span></div>
|
||||
</div>
|
||||
<div class="stat" data-resource="population">
|
||||
<div class="stat-k">Bevölkerung</div>
|
||||
<div class="stat-v" id="res-population" data-resource-value="population"><span class="dot dot-green"></span> 0</div>
|
||||
<div class="stat-bar"><span style="width:55%"></span></div>
|
||||
</div>
|
||||
<div class="stat" data-resource="water">
|
||||
<div class="stat-k">Wasser</div>
|
||||
<div class="stat-v" id="res-water" data-resource-value="water"><span class="dot dot-cyan"></span> 0</div>
|
||||
<div class="stat-bar"><span style="width:30%"></span></div>
|
||||
</div>
|
||||
<div class="stat" data-resource="food">
|
||||
<div class="stat-k">Nahrung</div>
|
||||
<div class="stat-v" id="res-food" data-resource-value="food"><span class="dot dot-green"></span> 0</div>
|
||||
<div class="stat-bar"><span style="width:60%"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user