Files
xxx-sphere-web/bin/main/static/userhome.html
Mario 5ffb99c9b5
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
Weiter an den Locations gearbeitet
2026-04-06 22:48:34 +02:00

332 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/img/icon.png" type="image/png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home xXx Sphere</title>
<link rel="stylesheet" href="/css/variables.css">
<link rel="stylesheet" href="/css/style.css">
<style>
.game-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1.25rem;
margin-top: 1.5rem;
}
.game-card {
background: var(--color-secondary);
border: 1px solid var(--color-secondary);
border-radius: 12px;
padding: 1.5rem 1.25rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.game-card-icon { font-size: 2rem; line-height: 1; }
.game-card-title { font-size: 1.1rem; font-weight: 700; margin: 0; }
.game-card-desc { font-size: 0.88rem; color: var(--color-muted); line-height: 1.6; flex: 1; }
.game-card-btn { margin-top: 0.25rem; width: auto; align-self: flex-start; padding: 0.5rem 1.25rem; }
.welcome { font-size: 0.95rem; color: var(--color-muted); margin: 0.25rem 0 0; }
.section-label {
font-size: 0.8rem; font-weight: 600; color: var(--color-muted);
text-transform: uppercase; letter-spacing: 0.05em;
margin: 2rem 0 0.75rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--color-secondary);
}
.visitors-strip {
display: flex; flex-wrap: wrap; gap: 0.75rem;
}
.visitor-card {
display: flex; flex-direction: column; align-items: center; gap: 0.3rem;
text-decoration: none; color: var(--color-text);
width: 72px;
}
.visitor-card:hover .visitor-avatar { border-color: var(--color-primary); }
.visitor-avatar {
width: 56px; height: 56px; border-radius: 50%;
background: var(--color-secondary);
border: 2px solid var(--color-secondary);
display: flex; align-items: center; justify-content: center;
font-size: 1.4rem; overflow: hidden; flex-shrink: 0;
transition: border-color 0.15s;
}
.visitor-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
.visitor-name {
font-size: 0.75rem; text-align: center;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
width: 100%;
}
.visitor-time { font-size: 0.68rem; color: var(--color-muted); text-align: center; }
/* ── Dating: Likes & Matches ── */
.dating-strip { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.dating-card {
display: flex; flex-direction: column; align-items: center; gap: 0.3rem;
text-decoration: none; color: var(--color-text); width: 72px;
}
.dating-card:hover .dating-avatar { border-color: var(--color-primary); }
.dating-avatar {
width: 56px; height: 56px; border-radius: 50%;
background: var(--color-secondary);
border: 2px solid var(--color-secondary);
display: flex; align-items: center; justify-content: center;
font-size: 1.4rem; overflow: hidden; flex-shrink: 0;
transition: border-color 0.15s;
}
.dating-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
.dating-name {
font-size: 0.75rem; text-align: center;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 100%;
}
/* Verschwommene Karte für nicht-Premium */
.dating-card-locked {
width: 72px; display: flex; flex-direction: column; align-items: center; gap: 0.3rem;
cursor: default;
}
.dating-avatar-blurred {
width: 56px; height: 56px; border-radius: 50%;
background: var(--color-secondary);
border: 2px solid var(--color-secondary);
overflow: hidden; flex-shrink: 0; position: relative;
}
.dating-avatar-blurred img {
width: 100%; height: 100%; object-fit: cover; border-radius: 50%;
filter: blur(6px); transform: scale(1.1);
}
.dating-avatar-blurred .lock-icon {
position: absolute; inset: 0;
display: flex; align-items: center; justify-content: center;
font-size: 1.1rem;
}
.premium-hint {
font-size: 0.65rem; color: var(--color-primary); text-align: center; font-weight: 600;
}
.match-badge {
font-size: 0.65rem; color: var(--color-primary); text-align: center; font-weight: 600;
}
/* ── Location-Events ── */
.loc-event-list { display: flex; flex-direction: column; gap: 0.6rem; }
.loc-event-card {
display: flex; gap: 0.75rem; align-items: center;
background: var(--color-secondary); border: 1px solid var(--color-secondary);
border-radius: 10px; padding: 0.65rem 0.85rem;
text-decoration: none; color: inherit;
transition: border-color 0.15s;
}
.loc-event-card:hover { border-color: var(--color-primary); }
.loc-event-thumb {
width: 48px; height: 48px; border-radius: 8px; flex-shrink: 0;
background: var(--color-card); overflow: hidden;
display: flex; align-items: center; justify-content: center; font-size: 1.4rem;
}
.loc-event-thumb img { width: 100%; height: 100%; object-fit: cover; }
.loc-event-body { flex: 1; min-width: 0; }
.loc-event-location { font-size: 0.75rem; color: var(--color-muted); margin-bottom: 0.1rem;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.loc-event-title { font-size: 0.92rem; font-weight: 600;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.loc-event-date { font-size: 0.75rem; color: var(--color-primary); margin-top: 0.15rem; }
</style>
</head>
<body class="app">
<div class="main">
<div class="content">
<h1 style="margin:0 0 0.15rem;">Home</h1>
<p class="welcome" id="greeting"></p>
<!-- Profilbesucher -->
<div id="visitorsSection" style="display:none;">
<div class="section-label">Profilbesucher 👀</div>
<div class="visitors-strip" id="visitorsStrip"></div>
</div>
<!-- Wer hat mich geliked (Dating) -->
<div id="likesSection" style="display:none;">
<div class="section-label">Likes ❤️</div>
<div class="dating-strip" id="likesStrip"></div>
</div>
<!-- Matches -->
<div id="matchesSection" style="display:none;">
<div class="section-label">Matches 💕</div>
<div class="dating-strip" id="matchesStrip"></div>
</div>
<!-- Meine angemeldeten Events -->
<div id="myEventsSection" style="display:none;">
<div class="section-label">Meine Veranstaltungen 🎟</div>
<div class="loc-event-list" id="myEventsList"></div>
</div>
<!-- Nächste Events abonnierter Locations -->
<div id="locEventsSection" style="display:none;">
<div class="section-label">Nächste Veranstaltungen 📍</div>
<div class="loc-event-list" id="locEventsList"></div>
</div>
</div>
</div>
<script src="/js/icons.js"></script>
<script src="/js/sidebar.js"></script>
<script>
fetch('/login/me')
.then(r => {
if (r.status === 401) { window.location.href = '/login.html'; return null; }
return r.json();
})
.then(user => {
if (user) {
document.getElementById('greeting').textContent = 'Willkommen zurück, ' + user.name + '!';
loadVisitors();
loadMyEvents();
loadLocEvents();
if (user.datingAktiv) {
loadWhoLikesMe();
loadMatches();
}
}
})
.catch(() => { window.location.href = '/login.html'; });
function relativeTime(isoString) {
const diff = Math.floor((Date.now() - new Date(isoString)) / 1000);
if (diff < 60) return 'gerade eben';
if (diff < 3600) return 'vor ' + Math.floor(diff / 60) + ' Min.';
if (diff < 86400) return 'vor ' + Math.floor(diff / 3600) + ' Std.';
return 'vor ' + Math.floor(diff / 86400) + ' Tag' + (Math.floor(diff / 86400) === 1 ? '' : 'en');
}
async function loadWhoLikesMe() {
try {
const res = await fetch('/dating/who-likes-me');
if (!res.ok) return;
const data = await res.json();
if (data.total === 0) return;
const strip = document.getElementById('likesStrip');
strip.innerHTML = data.likers.map(l => {
const pic = l.profilePicture
? `<img src="data:image/png;base64,${l.profilePicture}" alt="">`
: '◉';
if (data.premium && l.userId) {
return `
<a class="dating-card" href="/community/benutzer.html?userId=${l.userId}">
<div class="dating-avatar">${pic}</div>
<span class="dating-name">${esc(l.name)}</span>
</a>`;
} else {
return `
<div class="dating-card-locked">
<div class="dating-avatar-blurred">
${pic}
<span class="lock-icon">🔒</span>
</div>
<span class="premium-hint">Premium</span>
</div>`;
}
}).join('');
document.getElementById('likesSection').style.display = '';
} catch (_) {}
}
async function loadMatches() {
try {
const res = await fetch('/dating/matches');
if (!res.ok) return;
const matches = await res.json();
if (!matches.length) return;
const strip = document.getElementById('matchesStrip');
strip.innerHTML = matches.map(m => `
<a class="dating-card" href="/community/benutzer.html?userId=${m.userId}">
<div class="dating-avatar">
${m.profilePicture
? `<img src="data:image/png;base64,${m.profilePicture}" alt="${esc(m.name)}">`
: '◉'}
</div>
<span class="dating-name">${esc(m.name)}</span>
<span class="match-badge">♥ Match</span>
</a>
`).join('');
document.getElementById('matchesSection').style.display = '';
} catch (_) {}
}
function esc(s) {
if (!s) return '';
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
function renderEventCards(events, listId, sectionId) {
if (!events.length) return;
const list = document.getElementById(listId);
list.innerHTML = events.map(e => {
const thumb = e.imageData
? `<img src="data:image/jpeg;base64,${e.imageData}" alt="">`
: '🗓';
const date = new Date(e.startAt);
const dateStr = date.toLocaleDateString('de-DE', { weekday:'short', day:'numeric', month:'short' })
+ ', ' + date.toLocaleTimeString('de-DE', { hour:'2-digit', minute:'2-digit' }) + ' Uhr';
return `
<a class="loc-event-card" href="/community/event-detail.html?id=${e.eventId}">
<div class="loc-event-thumb">${thumb}</div>
<div class="loc-event-body">
<div class="loc-event-location">${esc(e.locationName)}</div>
<div class="loc-event-title">${esc(e.title)}</div>
<div class="loc-event-date">${dateStr}</div>
</div>
</a>`;
}).join('');
document.getElementById(sectionId).style.display = '';
}
async function loadMyEvents() {
try {
const res = await fetch('/location-events/attending-next');
if (!res.ok) return;
const events = await res.json();
renderEventCards(events, 'myEventsList', 'myEventsSection');
} catch (_) {}
}
async function loadLocEvents() {
try {
const res = await fetch('/location-events/followed-next');
if (!res.ok) return;
const events = await res.json();
renderEventCards(events, 'locEventsList', 'locEventsSection');
} catch (_) {}
}
async function loadVisitors() {
try {
const res = await fetch('/social/profile-visits/my-visitors');
if (!res.ok) return;
const visitors = await res.json();
if (!visitors.length) return;
const strip = document.getElementById('visitorsStrip');
strip.innerHTML = visitors.map(v => `
<a class="visitor-card" href="/community/benutzer.html?userId=${v.userId}">
<div class="visitor-avatar">
${v.profilePicture
? `<img src="data:image/png;base64,${v.profilePicture}" alt="${v.name}">`
: '◉'}
</div>
<span class="visitor-name">${v.name}</span>
<span class="visitor-time">${relativeTime(v.visitedAt)}</span>
</a>
`).join('');
document.getElementById('visitorsSection').style.display = '';
} catch (_) {}
}
</script>
</body>
</html>