Files
xxx-sphere-web/bin/main/static/dating/matches.html
Mario 2b0ce62d33
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
Menp überarbeitet
2026-04-08 16:52:43 +02:00

140 lines
8.1 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>Matches xXx Sphere</title>
<link rel="stylesheet" href="/css/variables.css">
<link rel="stylesheet" href="/css/style.css">
<style>
.profiles-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; }
@media (max-width: 500px) { .profiles-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); } }
.profile-card { background: var(--color-card); border: 1px solid var(--color-secondary); border-radius: 12px; overflow: hidden; transition: border-color 0.15s, box-shadow 0.15s; text-decoration: none; color: var(--color-text); display: flex; flex-direction: column; }
.profile-card:hover { border-color: var(--color-primary); box-shadow: 0 4px 18px rgba(0,0,0,0.35); }
.profile-card-img-wrap { position: relative; width: 100%; aspect-ratio: 1; flex-shrink: 0; overflow: hidden; background: var(--color-secondary); display: flex; align-items: center; justify-content: center; font-size: 3rem; }
.profile-card-img-wrap img { width: 100%; height: 100%; object-fit: cover; }
.profile-card-body { padding: 0.75rem; display: flex; flex-direction: column; gap: 0.3rem; flex: 1; }
.profile-card-name { font-weight: 700; font-size: 1rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.profile-card-meta { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.meta-chip { padding: 0.1rem 0.45rem; border-radius: 20px; background: var(--color-secondary); font-size: 0.73rem; color: var(--color-muted); }
.meta-chip.accent { color: var(--color-primary); }
.profile-card-desc { font-size: 0.78rem; color: var(--color-muted); line-height: 1.4; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; margin-top: 0.15rem; }
.profile-card-actions { padding: 0 0.75rem 0.75rem; display: flex; gap: 0.5rem; }
.profile-card-actions .btn { flex: 1; padding: 0.4rem 0.5rem; font-size: 0.8rem; text-align: center; }
.profile-card-skeleton { background: var(--color-card); border: 1px solid var(--color-secondary); border-radius: 12px; overflow: hidden; display: flex; flex-direction: column; }
.skeleton-img { width: 100%; aspect-ratio: 1; background: linear-gradient(90deg, var(--color-secondary) 25%, var(--color-card) 50%, var(--color-secondary) 75%); background-size: 200% 100%; animation: shimmer 1.2s infinite; }
.skeleton-body { padding: 0.75rem; display: flex; flex-direction: column; gap: 0.5rem; }
.skeleton-line { height: 0.75rem; border-radius: 4px; background: linear-gradient(90deg, var(--color-secondary) 25%, var(--color-card) 50%, var(--color-secondary) 75%); background-size: 200% 100%; animation: shimmer 1.2s infinite; }
@keyframes shimmer { to { background-position: -200% 0; } }
.empty-state { text-align: center; padding: 3rem 1rem; color: var(--color-muted); grid-column: 1 / -1; }
.empty-state .icon { font-size: 2.5rem; margin-bottom: 0.75rem; }
#sentinel { height: 1px; margin-top: 1rem; }
.results-count { font-size: 0.85rem; color: var(--color-muted); display: block; margin-bottom: 0.75rem; }
</style>
</head>
<body class="app">
<div class="main">
<div class="content">
<h1 style="margin:0 0 0.15rem;">Matches</h1>
<span class="results-count" id="resultsCount"></span>
<div class="profiles-grid" id="profilesGrid"></div>
<div id="sentinel"></div>
</div>
</div>
<script src="/js/icons.js"></script>
<script src="/js/nav.js"></script>
<script>
const BATCH = 12;
let allEntries = [];
let loadedCount = 0;
let loading = false;
function esc(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
function skeleton() {
return `<div class="profile-card-skeleton"><div class="skeleton-img"></div><div class="skeleton-body"><div class="skeleton-line" style="width:70%"></div><div class="skeleton-line" style="width:50%"></div></div></div>`;
}
function appendProfiles(profiles) {
const grid = document.getElementById('profilesGrid');
profiles.forEach(p => {
const pic = p.profilePictureHq || p.profilePicture;
const img = pic ? `<img src="data:image/png;base64,${pic}" alt="${esc(p.name)}" loading="lazy">` : '👤';
const chips = [
p.alter ? `<span class="meta-chip">${p.alter} J.</span>` : '',
p.geschlecht ? `<span class="meta-chip">${esc(p.geschlecht)}</span>` : '',
p.datingStadt ? `<span class="meta-chip">${esc(p.datingStadt)}</span>` : '',
`<span class="meta-chip accent">💕 Match</span>`,
].filter(Boolean).join('');
const desc = p.beschreibung ? `<div class="profile-card-desc">${esc(p.beschreibung)}</div>` : '';
const card = document.createElement('div');
card.className = 'profile-card';
card.innerHTML = `
<a href="/community/benutzer.html?userId=${p.userId}" style="text-decoration:none;color:inherit;display:contents;">
<div class="profile-card-img-wrap">${img}</div>
<div class="profile-card-body">
<div class="profile-card-name">${esc(p.name)}</div>
<div class="profile-card-meta">${chips}</div>
${desc}
</div>
</a>
<div class="profile-card-actions">
<a class="btn" href="/community/nachrichten.html?userId=${p.userId}">Nachricht</a>
<a class="btn" href="/community/benutzer.html?userId=${p.userId}" style="background:var(--color-secondary);color:var(--color-text);">Profil</a>
</div>`;
grid.appendChild(card);
});
}
async function loadNextBatch() {
if (loading || loadedCount >= allEntries.length) return;
loading = true;
const batch = allEntries.slice(loadedCount, loadedCount + BATCH);
const grid = document.getElementById('profilesGrid');
const skelDiv = document.createElement('div');
skelDiv.style.display = 'contents';
skelDiv.innerHTML = Array.from({length: batch.length}, () => skeleton()).join('');
grid.appendChild(skelDiv);
try {
const res = await fetch('/dating/profiles/batch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(batch.map(e => e.userId))
});
grid.removeChild(skelDiv);
if (res.ok) appendProfiles(await res.json());
} catch { grid.removeChild(skelDiv); }
loadedCount += batch.length;
loading = false;
}
(async () => {
try {
const res = await fetch('/dating/matches');
if (!res.ok) throw new Error();
const matches = await res.json();
allEntries = matches.map(m => ({ userId: m.userId }));
document.getElementById('resultsCount').textContent =
allEntries.length === 0 ? 'Noch keine Matches'
: allEntries.length + (allEntries.length === 1 ? ' Match' : ' Matches');
if (!allEntries.length) {
document.getElementById('profilesGrid').innerHTML =
'<div class="empty-state"><div class="icon">💕</div><p>Noch keine Matches. Schau öfter vorbei!</p></div>';
return;
}
loadNextBatch();
} catch {
document.getElementById('profilesGrid').innerHTML =
'<div class="empty-state"><div class="icon">⚠️</div><p>Fehler beim Laden.</p></div>';
}
})();
new IntersectionObserver(
e => { if (e[0].isIntersecting) loadNextBatch(); },
{ rootMargin: '300px' }
).observe(document.getElementById('sentinel'));
</script>
</body>
</html>