Files
xxx-sphere-web/bin/main/static/games/common/einladungen.html
Mario e2a71ab096
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
Hashtags eingeführt
2026-04-11 01:14:33 +02:00

372 lines
17 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>Einladungen xXx Sphere</title>
<link rel="stylesheet" href="/css/variables.css">
<link rel="stylesheet" href="/css/style.css">
<style>
.inv-tabs {
display: flex;
gap: 0;
margin-bottom: 1.5rem;
border-bottom: 1px solid var(--color-secondary);
}
.inv-tab {
background: none;
border: none;
border-bottom: 2px solid transparent;
border-radius: 0;
padding: 0.5rem 1.1rem;
font-size: 0.95rem;
font-weight: 600;
color: var(--color-muted);
cursor: pointer;
margin: 0 0 -1px;
width: auto;
transition: color 0.15s, border-color 0.15s;
}
.inv-tab.active { color: var(--color-primary); border-bottom-color: var(--color-primary); }
.inv-tab:hover:not(.active) { color: var(--color-text); background: none; }
.inv-section-label {
font-size: 0.8rem; font-weight: 600; color: var(--color-muted);
text-transform: uppercase; letter-spacing: 0.05em;
margin: 1.5rem 0 0.65rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--color-secondary);
}
.inv-card {
display: flex; gap: 0.85rem; align-items: center;
background: var(--color-card);
border: 1px solid var(--color-secondary);
border-radius: 12px;
padding: 0.85rem 1rem;
margin-bottom: 0.6rem;
text-decoration: none;
color: inherit;
transition: border-color 0.15s;
}
.inv-card:hover { border-color: var(--color-primary); }
.inv-avatar {
width: 44px; height: 44px; border-radius: 50%;
background: var(--color-secondary);
display: flex; align-items: center; justify-content: center;
font-size: 1.2rem; overflow: hidden; flex-shrink: 0;
}
.inv-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
.inv-body { flex: 1; min-width: 0; }
.inv-from { font-weight: 600; font-size: 0.95rem;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.inv-type { font-size: 0.78rem; color: var(--color-muted); margin-top: 0.15rem; }
.inv-actions { display: flex; gap: 0.5rem; flex-shrink: 0; flex-wrap: wrap; align-items: center; }
.inv-btn {
background: var(--color-primary);
color: #fff; border: none; border-radius: 8px;
padding: 0.38rem 0.9rem; font-size: 0.85rem; font-weight: 600;
cursor: pointer; margin: 0; width: auto;
text-decoration: none; display: inline-flex; align-items: center;
transition: opacity 0.15s;
}
.inv-btn:hover { opacity: 0.85; }
.inv-btn.outline {
background: none; border: 1px solid var(--color-secondary);
color: var(--color-muted);
}
.inv-btn.outline:hover { border-color: var(--color-primary); color: var(--color-primary); opacity: 1; }
.inv-arrow { font-size: 0.85rem; color: var(--color-primary); font-weight: 600; flex-shrink: 0; }
.inv-status { font-size: 0.8rem; color: var(--color-muted); }
.inv-empty { text-align: center; color: var(--color-muted); padding: 3rem 1rem; font-size: 0.95rem; }
</style>
</head>
<body class="app">
<div class="main">
<div class="content">
<h1 style="margin:0 0 1.25rem;">Einladungen</h1>
<div class="inv-tabs">
<button class="inv-tab" id="tabBtnErhalten" onclick="switchTab('erhalten')">Erhalten</button>
<button class="inv-tab" id="tabBtnGesendet" onclick="switchTab('gesendet')">Gesendet</button>
</div>
<!-- ── Erhalten ─────────────────────────────────────────────────── -->
<div id="panelErhalten">
<div id="loadingErhalten" style="text-align:center;color:var(--color-muted);padding:2rem 0;">Wird geladen…</div>
<div id="secVanilla" style="display:none;">
<div class="inv-section-label">🎭 Vanilla Game</div>
<div id="listVanilla"></div>
</div>
<div id="secBdsm" style="display:none;">
<div class="inv-section-label">⛓ BDSM Game</div>
<div id="listBdsm"></div>
</div>
<div id="secChastity" style="display:none;">
<div class="inv-section-label">🔒 Chastity Game</div>
<div id="listChastity"></div>
</div>
<div id="emptyErhalten" style="display:none;">
<div class="inv-empty">Du hast aktuell keine offenen Einladungen.</div>
</div>
</div>
<!-- ── Gesendet ─────────────────────────────────────────────────── -->
<div id="panelGesendet" style="display:none;">
<div id="loadingGesendet" style="text-align:center;color:var(--color-muted);padding:2rem 0;">Wird geladen…</div>
<div id="secSentVanilla" style="display:none;">
<div class="inv-section-label">🎭 Vanilla Game</div>
<div id="listSentVanilla"></div>
</div>
<div id="secSentBdsm" style="display:none;">
<div class="inv-section-label">⛓ BDSM Game</div>
<div id="listSentBdsm"></div>
</div>
<div id="secSentChastity" style="display:none;">
<div class="inv-section-label">🔒 Chastity Game</div>
<div id="listSentChastity"></div>
</div>
<div id="emptySent" style="display:none;">
<div class="inv-empty">Du hast aktuell keine gesendeten Einladungen.</div>
</div>
</div>
</div>
</div>
<script src="/js/icons.js"></script>
<script src="/js/nav.js"></script>
<script>
function esc(s) {
return String(s ?? '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
// ── Tab-Switching ────────────────────────────────────────────────────────
function switchTab(tab) {
const erhalten = tab === 'erhalten';
document.getElementById('panelErhalten').style.display = erhalten ? '' : 'none';
document.getElementById('panelGesendet').style.display = erhalten ? 'none' : '';
document.getElementById('tabBtnErhalten').classList.toggle('active', erhalten);
document.getElementById('tabBtnGesendet').classList.toggle('active', !erhalten);
const url = new URL(location.href);
erhalten ? url.searchParams.delete('tab') : url.searchParams.set('tab', tab);
history.replaceState(null, '', url);
}
const initTab = new URLSearchParams(location.search).get('tab') || 'erhalten';
switchTab(initTab);
// ── Erhalten ──────────────────────────────────────────────────────────────
async function loadErhalten() {
let shown = 0;
try {
const [vRes, bRes, cRes] = await Promise.all([
fetch('/vanilla/einladung/pending'),
fetch('/bdsm/einladung/pending'),
fetch('/lockee/invitations/mine'),
]);
// Vanilla
if (vRes.ok) {
const list = await vRes.json();
if (list.length) {
document.getElementById('secVanilla').style.display = '';
document.getElementById('listVanilla').innerHTML = list.map(e => `
<div class="inv-card" id="vcard-${esc(e.id)}">
<div class="inv-avatar">
${e.inviterAvatar
? `<img src="data:image/png;base64,${e.inviterAvatar}" alt="">`
: '🎭'}
</div>
<div class="inv-body">
<div class="inv-from">${esc(e.inviterName || 'Jemand')}</div>
<div class="inv-type">Vanilla-Spieleinladung</div>
</div>
<div class="inv-actions">
<button class="inv-btn" onclick="acceptVanilla(${esc(e.id)})">Annehmen</button>
<button class="inv-btn outline" onclick="declineVanilla(${esc(e.id)})">Ablehnen</button>
</div>
</div>`).join('');
shown += list.length;
}
}
// BDSM
if (bRes.ok) {
const list = await bRes.json();
if (list.length) {
document.getElementById('secBdsm').style.display = '';
document.getElementById('listBdsm').innerHTML = list.map(e => `
<a class="inv-card" href="/games/bdsm/bdsm-einladung.html?id=${esc(e.id)}">
<div class="inv-avatar">
${e.inviterAvatar
? `<img src="data:image/png;base64,${e.inviterAvatar}" alt="">`
: '⛓'}
</div>
<div class="inv-body">
<div class="inv-from">${esc(e.inviterName || 'Jemand')}</div>
<div class="inv-type">BDSM-Spieleinladung</div>
</div>
<span class="inv-arrow">Ansehen →</span>
</a>`).join('');
shown += list.length;
}
}
// Chastity
if (cRes.ok) {
const list = await cRes.json();
if (list.length) {
document.getElementById('secChastity').style.display = '';
document.getElementById('listChastity').innerHTML = list.map(e => `
<a class="inv-card" href="/games/chastity/joinlock.html?token=${esc(e.token)}">
<div class="inv-avatar">
${e.keyholderProfilePic
? `<img src="data:image/png;base64,${e.keyholderProfilePic}" alt="">`
: '🔒'}
</div>
<div class="inv-body">
<div class="inv-from">${esc(e.keyholderName || 'Jemand')}</div>
<div class="inv-type">Keuschheitslock: ${esc(e.lockName || '')}</div>
</div>
<span class="inv-arrow">Ansehen →</span>
</a>`).join('');
shown += list.length;
}
}
} catch (_) {}
document.getElementById('loadingErhalten').style.display = 'none';
if (!shown) document.getElementById('emptyErhalten').style.display = '';
}
// Vanilla inline annehmen / ablehnen
async function acceptVanilla(id) {
const card = document.getElementById('vcard-' + id);
if (card) card.querySelector('.inv-actions').innerHTML = '<span class="inv-status">Wird gespeichert…</span>';
try {
const res = await fetch(`/vanilla/einladung/${id}/antwort`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ accepted: true, mode: 'OWN_DEVICE' }),
});
if (res.ok) {
window.location.href = '/games/vanilla/neuvanilla.html';
} else {
if (card) card.querySelector('.inv-actions').innerHTML = '<span class="inv-status" style="color:var(--color-primary)">Fehler beim Annehmen.</span>';
}
} catch (_) {
if (card) card.querySelector('.inv-actions').innerHTML = '<span class="inv-status" style="color:var(--color-primary)">Fehler.</span>';
}
}
async function declineVanilla(id) {
const card = document.getElementById('vcard-' + id);
if (card) card.querySelector('.inv-actions').innerHTML = '<span class="inv-status">Wird gespeichert…</span>';
try {
await fetch(`/vanilla/einladung/${id}/antwort`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ accepted: false, mode: null }),
});
if (card) card.remove();
if (!document.getElementById('listVanilla').children.length) {
document.getElementById('secVanilla').style.display = 'none';
}
checkEmptyErhalten();
} catch (_) {}
}
function checkEmptyErhalten() {
const anyVisible = ['secVanilla','secBdsm','secChastity']
.some(id => document.getElementById(id).style.display !== 'none');
document.getElementById('emptyErhalten').style.display = anyVisible ? 'none' : '';
}
// ── Gesendet ──────────────────────────────────────────────────────────────
async function loadGesendet() {
let shown = 0;
try {
const [bRes, vRes, cRes] = await Promise.all([
fetch('/bdsm/einladung/meine-aktive'),
fetch('/vanilla/einladung/meine-aktive'),
fetch('/keyholder/invitations/mine'),
]);
// Vanilla gesendete Einladung
if (vRes.ok) {
const e = await vRes.json();
if (e) {
document.getElementById('secSentVanilla').style.display = '';
document.getElementById('listSentVanilla').innerHTML = `
<div class="inv-card">
<div class="inv-avatar">🎭</div>
<div class="inv-body">
<div class="inv-from">Vanilla-Einladung gesendet</div>
<div class="inv-type">Warte auf Antwort…</div>
</div>
<a class="inv-btn" href="/games/vanilla/neuvanilla.html">Zum Spiel →</a>
</div>`;
shown++;
}
}
// BDSM gesendete Einladung
if (bRes.ok) {
const e = await bRes.json();
if (e) {
document.getElementById('secSentBdsm').style.display = '';
document.getElementById('listSentBdsm').innerHTML = `
<div class="inv-card">
<div class="inv-avatar">⛓</div>
<div class="inv-body">
<div class="inv-from">BDSM-Einladung gesendet</div>
<div class="inv-type">Warte auf Antwort…</div>
</div>
<a class="inv-btn" href="/games/bdsm/neubdsm.html">Zum Spiel →</a>
</div>`;
shown++;
}
}
// Chastity gesendete Einladungen (als Keyholder)
if (cRes.ok) {
const list = await cRes.json();
if (Array.isArray(list) && list.length) {
document.getElementById('secSentChastity').style.display = '';
document.getElementById('listSentChastity').innerHTML = list.map(e => `
<div class="inv-card">
<div class="inv-avatar">🔒</div>
<div class="inv-body">
<div class="inv-from">${esc(e.lockeeName || e.lockeeEmail || 'Eingeladene Person')}</div>
<div class="inv-type">Lock: ${esc(e.lockName || '')} · ${esc(e.status || 'Ausstehend')}</div>
</div>
<a class="inv-btn outline" href="/games/chastity/keyholder.html">Keyholder →</a>
</div>`).join('');
shown += list.length;
}
}
} catch (_) {}
document.getElementById('loadingGesendet').style.display = 'none';
if (!shown) document.getElementById('emptySent').style.display = '';
}
// Auth-Check & Start
fetch('/login/me')
.then(r => { if (r.status === 401) window.location.href = '/login.html'; return r.ok ? r.json() : null; })
.then(user => {
if (user) { loadErhalten(); loadGesendet(); }
})
.catch(() => { window.location.href = '/login.html'; });
</script>
</body>
</html>