Weiter an der Oberfläche getüftelt
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
This commit is contained in:
@@ -1,982 +0,0 @@
|
||||
<!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>
|
||||
/* Tabs */
|
||||
.tabs-bar {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
border-bottom: 2px solid var(--color-secondary);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.tab-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
padding: 0.6rem 1.25rem;
|
||||
font-size: 0.92rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-muted);
|
||||
cursor: pointer;
|
||||
width: auto;
|
||||
border-radius: 0;
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
}
|
||||
.tab-btn:hover { color: var(--color-text); background: none; }
|
||||
.tab-btn.active { color: var(--color-primary); border-bottom-color: var(--color-primary); }
|
||||
.tab-panel { display: none; }
|
||||
.tab-panel.active { display: block; }
|
||||
|
||||
/* Liste */
|
||||
.inv-list { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
|
||||
.inv-card {
|
||||
background: var(--color-card);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: 10px;
|
||||
display: flex; align-items: center; gap: 0.9rem;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
/* Avatar mit Typ-Badge */
|
||||
.inv-avatar-wrap {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.inv-avatar {
|
||||
width: 52px; height: 52px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-secondary);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 1.4rem; overflow: hidden;
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
}
|
||||
.inv-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.inv-type-badge {
|
||||
position: absolute;
|
||||
top: -6px; left: -6px;
|
||||
width: 26px; height: 26px;
|
||||
background: var(--color-card);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 1.08rem;
|
||||
z-index: 1;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.inv-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.15rem; }
|
||||
.inv-line1 { font-size: 0.78rem; color: var(--color-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.inv-line2 { font-weight: 700; font-size: 0.95rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.inv-line3 { font-size: 0.78rem; color: var(--color-muted); }
|
||||
.empty-hint { color: var(--color-muted); font-size: 0.9rem; margin-top: 0.25rem; }
|
||||
|
||||
/* Paging */
|
||||
.paging-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
margin-top: 1rem;
|
||||
font-size: 0.88rem;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.paging-bar button {
|
||||
width: auto;
|
||||
padding: 0.4rem 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.paging-bar button:disabled {
|
||||
opacity: 0.35;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Lockee-Einladungs-Dialog */
|
||||
.lockee-dialog-bg {
|
||||
display: none; position: fixed; inset: 0; z-index: 400;
|
||||
align-items: center; justify-content: center;
|
||||
}
|
||||
.lockee-dialog-bg.open { display: flex; }
|
||||
.lockee-dialog-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.55); }
|
||||
.lockee-dialog-box {
|
||||
position: relative; background: var(--color-card);
|
||||
border: 1px solid var(--color-secondary); border-radius: 12px;
|
||||
padding: 1.75rem 1.5rem 1.5rem; max-width: 420px; width: 92%; z-index: 1;
|
||||
display: flex; flex-direction: column; gap: 1rem;
|
||||
max-height: 90vh; overflow-y: auto;
|
||||
}
|
||||
.lockee-dialog-header { display: flex; align-items: center; gap: 0.75rem; }
|
||||
.lockee-dialog-avatar {
|
||||
width: 48px; height: 48px; border-radius: 50%;
|
||||
background: var(--color-secondary);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 1.3rem; flex-shrink: 0; overflow: hidden;
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
}
|
||||
.lockee-dialog-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.lockee-dialog-title { font-weight: 700; font-size: 1rem; }
|
||||
.lockee-dialog-sub { font-size: 0.82rem; color: var(--color-muted); margin-top: 0.1rem; }
|
||||
.lockee-dialog-detail {
|
||||
background: var(--color-secondary); border-radius: 8px;
|
||||
padding: 0.75rem 1rem; font-size: 0.88rem;
|
||||
}
|
||||
.lockee-dialog-detail dt { color: var(--color-muted); font-size: 0.75rem; margin-bottom: 0.1rem; }
|
||||
.lockee-dialog-detail dd { font-weight: 600; margin: 0 0 0.5rem 0; }
|
||||
.lockee-dialog-detail dd:last-child { margin-bottom: 0; }
|
||||
.lockee-dialog-codelines { display: flex; align-items: center; gap: 0.6rem; }
|
||||
.lockee-dialog-codelines label { font-size: 0.88rem; font-weight: 600; white-space: nowrap; }
|
||||
.lockee-dialog-codelines input { width: 72px; text-align: center; }
|
||||
.lockee-dialog-codelines span { font-size: 0.88rem; color: var(--color-muted); }
|
||||
.lockee-dialog-actions { display: flex; gap: 0.6rem; justify-content: flex-end; }
|
||||
.lockee-dialog-actions button { width: auto; padding: 0.6rem 1.3rem; font-size: 0.9rem; }
|
||||
.btn-accept { background: var(--color-success, #27ae60) !important; }
|
||||
.btn-accept:hover { background: #219150 !important; }
|
||||
.btn-decline { background: #c0392b !important; }
|
||||
.btn-decline:hover { background: #a93226 !important; }
|
||||
.lockee-dialog-error { color: #e74c3c; font-size: 0.82rem; display: none; }
|
||||
|
||||
/* Lock-Details im Dialog */
|
||||
.lock-details-section { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
.lock-details-cards {
|
||||
display: grid; grid-template-columns: repeat(auto-fill, minmax(68px, 1fr)); gap: 0.4rem;
|
||||
}
|
||||
.lock-details-card-item {
|
||||
background: var(--color-secondary); border-radius: 6px;
|
||||
padding: 0.4rem 0.3rem;
|
||||
display: flex; flex-direction: column; align-items: center; gap: 0.2rem; text-align: center;
|
||||
}
|
||||
.lock-details-card-item img { width: 36px; height: auto; border-radius: 3px; }
|
||||
.lock-details-card-item .ldc-count { font-weight: 700; font-size: 0.9rem; }
|
||||
.lock-details-card-item .ldc-name { font-size: 0.65rem; color: var(--color-muted); line-height: 1.2; }
|
||||
.lock-details-meta { display: flex; flex-wrap: wrap; gap: 0.35rem; }
|
||||
.lock-details-badge {
|
||||
background: var(--color-secondary); border-radius: 20px;
|
||||
padding: 0.2rem 0.6rem; font-size: 0.75rem; color: var(--color-muted);
|
||||
}
|
||||
.blind-hint {
|
||||
background: var(--color-secondary); border-radius: 8px; padding: 0.9rem 1rem;
|
||||
display: flex; gap: 0.6rem; align-items: flex-start;
|
||||
font-size: 0.85rem; color: var(--color-muted); line-height: 1.5;
|
||||
}
|
||||
.blind-hint-icon { font-size: 1.4rem; flex-shrink: 0; }
|
||||
|
||||
/* Bestätigungs-Modal */
|
||||
.confirm-modal-bg {
|
||||
display: none; position: fixed; inset: 0; z-index: 600;
|
||||
align-items: center; justify-content: center;
|
||||
}
|
||||
.confirm-modal-bg.open { display: flex; }
|
||||
.confirm-modal-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.6); }
|
||||
.confirm-modal-box {
|
||||
position: relative; background: var(--color-card);
|
||||
border: 1px solid var(--color-secondary); border-radius: 12px;
|
||||
padding: 1.75rem 1.5rem 1.5rem; max-width: 380px; width: 92%; z-index: 1;
|
||||
display: flex; flex-direction: column; gap: 1rem;
|
||||
}
|
||||
.confirm-modal-title {
|
||||
font-weight: 700; font-size: 1rem; padding-right: 1.5rem;
|
||||
}
|
||||
.confirm-modal-text {
|
||||
font-size: 0.9rem; color: var(--color-muted); line-height: 1.5;
|
||||
}
|
||||
.confirm-modal-actions {
|
||||
display: flex; gap: 0.6rem; justify-content: flex-end; margin-top: 0.25rem;
|
||||
}
|
||||
.confirm-modal-actions button { width: auto; padding: 0.6rem 1.3rem; font-size: 0.9rem; }
|
||||
.confirm-modal-cancel { background: var(--color-secondary) !important; color: var(--color-text) !important; }
|
||||
.confirm-modal-ok { background: #c0392b !important; }
|
||||
.confirm-modal-ok:hover { background: #a93226 !important; }
|
||||
|
||||
/* Entsperrcode-Modal */
|
||||
.unlock-modal-bg {
|
||||
display: none; position: fixed; inset: 0; z-index: 500;
|
||||
align-items: center; justify-content: center;
|
||||
}
|
||||
.unlock-modal-bg.open { display: flex; }
|
||||
.unlock-modal-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.6); }
|
||||
.unlock-modal-box {
|
||||
position: relative; background: var(--color-card);
|
||||
border: 1px solid var(--color-secondary); border-radius: 12px;
|
||||
padding: 1.5rem 1.5rem 1.25rem; max-width: 380px; width: 90%; z-index: 1;
|
||||
display: flex; flex-direction: column; align-items: center; gap: 0.75rem; text-align: center;
|
||||
}
|
||||
.unlock-code-display {
|
||||
font-family: monospace; font-size: 2rem; letter-spacing: 0.3em;
|
||||
background: var(--color-secondary); border-radius: 8px;
|
||||
padding: 1rem 1.5rem; color: var(--color-primary);
|
||||
line-height: 1.8; word-break: break-all; width: 100%; box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="app">
|
||||
<div class="main">
|
||||
<div class="content">
|
||||
<h1 style="margin-bottom:1.25rem;">Einladungen</h1>
|
||||
|
||||
<div class="tabs-bar">
|
||||
<button class="tab-btn active" data-tab="empfangen" onclick="switchTab('empfangen')">Empfangen</button>
|
||||
<button class="tab-btn" data-tab="gesendet" onclick="switchTab('gesendet')">Gesendet</button>
|
||||
</div>
|
||||
|
||||
<!-- Tab: Empfangen -->
|
||||
<div id="tab-empfangen" class="tab-panel active">
|
||||
<div class="inv-list" id="recvList"></div>
|
||||
<p class="empty-hint" id="recvEmpty" style="display:none;">Keine ausstehenden Einladungen.</p>
|
||||
<div class="paging-bar" id="recvPaging" style="display:none;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Tab: Gesendet -->
|
||||
<div id="tab-gesendet" class="tab-panel">
|
||||
<div class="inv-list" id="sentList"></div>
|
||||
<p class="empty-hint" id="sentEmpty" style="display:none;">Keine ausstehenden gesendeten Einladungen.</p>
|
||||
<div class="paging-bar" id="sentPaging" style="display:none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bestätigungs-Modal -->
|
||||
<div class="confirm-modal-bg" id="confirmModal">
|
||||
<div class="confirm-modal-overlay" onclick="confirmCancel()"></div>
|
||||
<div class="confirm-modal-box">
|
||||
<button onclick="confirmCancel()" style="position:absolute;top:0.75rem;right:0.75rem;background:none;border:none;color:var(--color-muted);font-size:1.3rem;cursor:pointer;padding:0.2rem 0.5rem;line-height:1;" title="Schließen">✕</button>
|
||||
<div class="confirm-modal-title" id="confirmTitle"></div>
|
||||
<div class="confirm-modal-text" id="confirmText"></div>
|
||||
<div class="confirm-modal-actions">
|
||||
<button class="confirm-modal-cancel" onclick="confirmCancel()">Abbrechen</button>
|
||||
<button class="confirm-modal-ok" id="confirmOkBtn">Bestätigen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vanilla-Einladungs-Dialog -->
|
||||
<div class="lockee-dialog-bg" id="vanillaInviteDialog">
|
||||
<div class="lockee-dialog-overlay" onclick="closeVanillaInviteDialog()"></div>
|
||||
<div class="lockee-dialog-box">
|
||||
<button onclick="closeVanillaInviteDialog()" style="position:absolute;top:0.75rem;right:0.75rem;background:none;border:none;color:var(--color-muted);font-size:1.3rem;cursor:pointer;padding:0.2rem 0.5rem;line-height:1;" title="Schließen">✕</button>
|
||||
<div class="lockee-dialog-header">
|
||||
<div class="lockee-dialog-avatar">🎲</div>
|
||||
<div>
|
||||
<div class="lockee-dialog-title" id="vanillaDialogTitle"></div>
|
||||
<div class="lockee-dialog-sub">Vanilla Game – Einladung</div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="font-size:0.88rem;color:var(--color-muted);line-height:1.5;margin:0;">
|
||||
Du wurdest zu einem Vanilla Game eingeladen. Wie möchtest du mitspielen?
|
||||
</p>
|
||||
<div class="lockee-dialog-error" id="vanillaDialogError"></div>
|
||||
<div class="lockee-dialog-actions" style="flex-direction:column;gap:0.5rem;">
|
||||
<button class="btn-accept" style="width:100%;" onclick="acceptVanillaOwnDevice()">Am eigenen Gerät mitspielen</button>
|
||||
<button class="btn-accept" style="width:100%;background:#1a5c8a!important;" onclick="acceptVanillaHostDevice()">Am Gerät des Hosts mitspielen</button>
|
||||
<button class="btn-decline" style="width:100%;" onclick="declineVanillaFromDialog()">Einladung ablehnen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BDSM-Einladungs-Dialog -->
|
||||
<div class="lockee-dialog-bg" id="bdsmInviteDialog">
|
||||
<div class="lockee-dialog-overlay" onclick="closeBdsmInviteDialog()"></div>
|
||||
<div class="lockee-dialog-box">
|
||||
<button onclick="closeBdsmInviteDialog()" style="position:absolute;top:0.75rem;right:0.75rem;background:none;border:none;color:var(--color-muted);font-size:1.3rem;cursor:pointer;padding:0.2rem 0.5rem;line-height:1;" title="Schließen">✕</button>
|
||||
<div class="lockee-dialog-header">
|
||||
<div class="lockee-dialog-avatar" id="bdsmDialogAvatar">⛓️</div>
|
||||
<div>
|
||||
<div class="lockee-dialog-title" id="bdsmDialogTitle"></div>
|
||||
<div class="lockee-dialog-sub">BDSM Game – Einladung</div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="font-size:0.88rem;color:var(--color-muted);line-height:1.5;margin:0;">
|
||||
Du wurdest zu einem BDSM Game eingeladen. Wie möchtest du mitspielen?
|
||||
</p>
|
||||
<div class="lockee-dialog-error" id="bdsmDialogError"></div>
|
||||
<div class="lockee-dialog-actions" style="flex-direction:column;gap:0.5rem;">
|
||||
<button class="btn-accept" style="width:100%;" onclick="acceptBdsmOwnDevice()">Am eigenen Gerät mitspielen</button>
|
||||
<button class="btn-accept" style="width:100%;background:#1a5c8a!important;" onclick="acceptBdsmHostDevice()">Am Gerät des Hosts mitspielen</button>
|
||||
<button class="btn-decline" style="width:100%;" onclick="declineBdsmFromDialog()">Einladung ablehnen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lockee-Einladungs-Dialog -->
|
||||
<div class="lockee-dialog-bg" id="lockeeInviteDialog">
|
||||
<div class="lockee-dialog-overlay" onclick="closeLockeeInviteDialog()"></div>
|
||||
<div class="lockee-dialog-box">
|
||||
<button onclick="closeLockeeInviteDialog()" style="position:absolute;top:0.75rem;right:0.75rem;background:none;border:none;color:var(--color-muted);font-size:1.3rem;cursor:pointer;padding:0.2rem 0.5rem;line-height:1;" title="Schließen">✕</button>
|
||||
<div class="lockee-dialog-header">
|
||||
<div class="lockee-dialog-avatar" id="dialogAvatar">🔒</div>
|
||||
<div>
|
||||
<div class="lockee-dialog-title" id="dialogTitle"></div>
|
||||
<div class="lockee-dialog-sub" id="dialogSub"></div>
|
||||
</div>
|
||||
</div>
|
||||
<dl class="lockee-dialog-detail" id="dialogDetail"></dl>
|
||||
<div id="dialogDetailsArea"></div>
|
||||
<div>
|
||||
<div class="lockee-dialog-codelines">
|
||||
<label for="dialogCodeLines">Ziffern des Entsperrcodes:</label>
|
||||
<input type="number" id="dialogCodeLines" min="1" max="20" value="5">
|
||||
<span>Ziffern</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lockee-dialog-error" id="dialogError"></div>
|
||||
<div class="lockee-dialog-actions">
|
||||
<button class="btn-decline" onclick="declineLockeeInviteDialog()">✕ Ablehnen</button>
|
||||
<button class="btn-accept" onclick="acceptLockeeInviteDialog()">✓ Annehmen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Entsperrcode-Modal -->
|
||||
<div class="unlock-modal-bg" id="unlockModal">
|
||||
<div class="unlock-modal-overlay"></div>
|
||||
<div class="unlock-modal-box">
|
||||
<div style="font-size:2rem;">🔒</div>
|
||||
<h3 id="unlockModalTitle" style="margin:0;">Dein Entsperrcode</h3>
|
||||
<p id="unlockModalHint" style="color:var(--color-muted);font-size:0.85rem;margin:0;">
|
||||
Stelle die Kombination deines Tresors auf den folgenden Code ein und verschließe deinen Schlüssel in diesem.
|
||||
</p>
|
||||
<div class="unlock-code-display" id="unlockCodeDisplay"></div>
|
||||
<div id="unlockModalCountdown" style="display:none;font-size:0.82rem;color:var(--color-muted);font-family:monospace;"></div>
|
||||
<button id="unlockModalBtn" style="width:100%;margin-top:0.25rem;">Weiter</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/card-defs.js"></script>
|
||||
<script src="/js/icons.js"></script>
|
||||
<script src="/js/nav.js"></script>
|
||||
<script src="/js/social-sidebar.js"></script>
|
||||
<script>
|
||||
function esc(s) { const d = document.createElement('div'); d.textContent = s ?? ''; return d.innerHTML; }
|
||||
|
||||
// ── Tabs ──
|
||||
function switchTab(name) {
|
||||
document.querySelectorAll('.tab-btn').forEach(b => b.classList.toggle('active', b.dataset.tab === name));
|
||||
document.querySelectorAll('.tab-panel').forEach(p => p.classList.toggle('active', p.id === 'tab-' + name));
|
||||
history.replaceState(null, '', '?tab=' + name);
|
||||
}
|
||||
const urlTab = new URLSearchParams(window.location.search).get('tab');
|
||||
if (urlTab === 'gesendet') switchTab('gesendet');
|
||||
|
||||
// ── Konstanten ──
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
// ── State ──
|
||||
let recvItems = [];
|
||||
let sentItems = [];
|
||||
let recvPage = 0;
|
||||
let sentPage = 0;
|
||||
|
||||
// ── Hilfsfunktionen ──
|
||||
function fmtDate(iso) {
|
||||
const dt = new Date(iso);
|
||||
return dt.toLocaleDateString('de-DE') + ', ' + dt.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) + ' Uhr';
|
||||
}
|
||||
|
||||
function buildAvatarHtml(picBase64, type) {
|
||||
const badge = type === 'keyholder' ? '🔑' : type === 'bdsm' ? '⛓️' : type === 'vanilla' ? '🎲' : '🔒';
|
||||
const inner = picBase64
|
||||
? `<div class="inv-avatar"><img src="data:image/jpeg;base64,${picBase64}" alt=""></div>`
|
||||
: `<div class="inv-avatar">👤</div>`;
|
||||
return `<div class="inv-avatar-wrap"><span class="inv-type-badge">${badge}</span>${inner}</div>`;
|
||||
}
|
||||
|
||||
function renderPaging(barId, page, total, onNav) {
|
||||
const bar = document.getElementById(barId);
|
||||
if (total <= 1) { bar.style.display = 'none'; return; }
|
||||
bar.style.display = 'flex';
|
||||
bar.innerHTML = `
|
||||
<button onclick="${onNav}(${page - 1})" ${page === 0 ? 'disabled' : ''}>‹ Zurück</button>
|
||||
<span>Seite ${page + 1} von ${total}</span>
|
||||
<button onclick="${onNav}(${page + 1})" ${page >= total - 1 ? 'disabled' : ''}>Weiter ›</button>`;
|
||||
}
|
||||
|
||||
// ── Empfangen laden ──
|
||||
async function loadReceivedInvitations() {
|
||||
try {
|
||||
const [lockeeRes, khRes, bdsmRes, vanillaRes] = await Promise.all([
|
||||
fetch('/lockee/invitations/mine'),
|
||||
fetch('/keyholder/invitations/mine'),
|
||||
fetch('/bdsm/einladung/pending'),
|
||||
fetch('/vanilla/einladung/pending'),
|
||||
]);
|
||||
const lockeeInvs = lockeeRes.ok ? await lockeeRes.json() : [];
|
||||
const khInvs = khRes.ok ? await khRes.json() : [];
|
||||
const bdsmInvs = bdsmRes.ok ? await bdsmRes.json() : [];
|
||||
const vanillaInvs = vanillaRes.ok ? await vanillaRes.json() : [];
|
||||
|
||||
lockeeInvs.forEach(inv => { inv._type = 'lockee'; inv._key = inv.token; inv._otherName = inv.keyholderName; inv._otherPic = inv.keyholderProfilePic; });
|
||||
khInvs.forEach(inv => { inv._type = 'keyholder'; inv._key = inv.token; inv._otherName = inv.lockeeName; inv._otherPic = inv.lockeeProfilePic; });
|
||||
bdsmInvs.forEach(inv => { inv._type = 'bdsm'; inv._key = inv.einladungId; inv._otherName = inv.inviterName; inv._otherPic = inv.inviterAvatar; });
|
||||
vanillaInvs.forEach(inv => { inv._type = 'vanilla'; inv._key = inv.einladungId; inv._otherName = inv.inviterName; inv._otherPic = inv.inviterAvatar || ''; });
|
||||
|
||||
recvItems = [...lockeeInvs, ...khInvs, ...bdsmInvs, ...vanillaInvs].sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
||||
recvPage = 0;
|
||||
renderRecvPage();
|
||||
} catch(e) { console.error(e); }
|
||||
}
|
||||
|
||||
function renderRecvPage() {
|
||||
const list = document.getElementById('recvList');
|
||||
const empty = document.getElementById('recvEmpty');
|
||||
list.innerHTML = '';
|
||||
|
||||
if (recvItems.length === 0) {
|
||||
empty.style.display = '';
|
||||
document.getElementById('recvPaging').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
empty.style.display = 'none';
|
||||
|
||||
const totalPages = Math.ceil(recvItems.length / PAGE_SIZE);
|
||||
const start = recvPage * PAGE_SIZE;
|
||||
const pageItems = recvItems.slice(start, start + PAGE_SIZE);
|
||||
|
||||
pageItems.forEach(inv => {
|
||||
const av = buildAvatarHtml(inv._otherPic, inv._type);
|
||||
const card = document.createElement('div');
|
||||
card.className = 'inv-card';
|
||||
card.id = 'recvinv-' + inv._key;
|
||||
if (inv._type === 'lockee') card.dataset.detailsVisible = inv.detailsVisible ? '1' : '0';
|
||||
|
||||
let typeLabel, line2, actions;
|
||||
if (inv._type === 'lockee') {
|
||||
typeLabel = 'Lockee-Einladung';
|
||||
line2 = 'Lockee: ' + esc(inv.lockName);
|
||||
actions = `
|
||||
<div style="display:flex;flex-direction:column;gap:0.4rem;flex-shrink:0;">
|
||||
<button onclick="declineLockeeInvitation('${esc(inv.token)}', this)" style="margin:0;padding:0.45rem 1rem;font-size:0.85rem;width:auto;background:#c0392b;border:none;color:#fff;border-radius:6px;font-weight:600;cursor:pointer;">✕ Ablehnen</button>
|
||||
<button onclick="openLockeeInviteDialog('${esc(inv.token)}')" style="margin:0;padding:0.45rem 1rem;font-size:0.85rem;width:auto;background:var(--color-success,#27ae60);border:none;color:#fff;border-radius:6px;font-weight:600;cursor:pointer;">✓ Details</button>
|
||||
</div>`;
|
||||
} else if (inv._type === 'keyholder') {
|
||||
typeLabel = 'Keyholder-Einladung';
|
||||
line2 = 'Keyholder: ' + esc(inv.lockName);
|
||||
actions = `
|
||||
<div style="display:flex;flex-direction:column;gap:0.4rem;flex-shrink:0;">
|
||||
<button onclick="declineKhInvitation('${esc(inv.token)}', this)" style="margin:0;padding:0.45rem 1rem;font-size:0.85rem;width:auto;background:#c0392b;border:none;color:#fff;border-radius:6px;font-weight:600;cursor:pointer;">✕ Ablehnen</button>
|
||||
<a href="/keyholder/invitation/${esc(inv.token)}" style="display:block;text-align:center;padding:0.45rem 1rem;font-size:0.85rem;background:var(--color-success);color:#fff;border-radius:6px;text-decoration:none;font-weight:600;">✓ Annehmen</a>
|
||||
</div>`;
|
||||
} else if (inv._type === 'vanilla') {
|
||||
typeLabel = 'Vanilla Game';
|
||||
line2 = 'Spieleinladung';
|
||||
actions = `
|
||||
<div style="display:flex;flex-direction:column;gap:0.4rem;flex-shrink:0;">
|
||||
<button onclick="openVanillaInviteDialog('${esc(inv.einladungId)}', '${esc(inv._otherName)}')" style="margin:0;padding:0.45rem 1rem;font-size:0.85rem;width:auto;background:var(--color-success,#27ae60);border:none;color:#fff;border-radius:6px;font-weight:600;cursor:pointer;">🎲 Details</button>
|
||||
</div>`;
|
||||
} else {
|
||||
typeLabel = 'BDSM Game';
|
||||
line2 = 'Spieleinladung';
|
||||
actions = `
|
||||
<div style="display:flex;flex-direction:column;gap:0.4rem;flex-shrink:0;">
|
||||
<button onclick="openBdsmInviteDialog('${esc(inv.einladungId)}', '${esc(inv._otherName)}', '${esc(inv._otherPic || '')}')" style="margin:0;padding:0.45rem 1rem;font-size:0.85rem;width:auto;background:var(--color-success,#27ae60);border:none;color:#fff;border-radius:6px;font-weight:600;cursor:pointer;">⛓️ Details</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
card.innerHTML = `
|
||||
${av}
|
||||
<div class="inv-body">
|
||||
<div class="inv-line1">${esc(inv._otherName)}</div>
|
||||
<div class="inv-line2">${line2}</div>
|
||||
<div class="inv-line3">${typeLabel} · ${fmtDate(inv.createdAt)}</div>
|
||||
</div>
|
||||
${actions}`;
|
||||
list.appendChild(card);
|
||||
});
|
||||
|
||||
renderPaging('recvPaging', recvPage, totalPages, 'goRecvPage');
|
||||
}
|
||||
|
||||
function goRecvPage(page) {
|
||||
const total = Math.ceil(recvItems.length / PAGE_SIZE);
|
||||
if (page < 0 || page >= total) return;
|
||||
recvPage = page;
|
||||
renderRecvPage();
|
||||
document.getElementById('recvList').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
function removeRecvItem(key) {
|
||||
recvItems = recvItems.filter(i => i._key !== key);
|
||||
const total = Math.ceil(recvItems.length / PAGE_SIZE);
|
||||
if (recvPage >= total && recvPage > 0) recvPage = total - 1;
|
||||
renderRecvPage();
|
||||
}
|
||||
|
||||
// ── Gesendet laden ──
|
||||
async function loadSentInvitations() {
|
||||
try {
|
||||
const [lockeeRes, khRes, vanillaRes] = await Promise.all([
|
||||
fetch('/lockee/invitations/sent'),
|
||||
fetch('/keyholder/invitations/sent'),
|
||||
fetch('/vanilla/einladung/sent'),
|
||||
]);
|
||||
const lockeeInvs = lockeeRes.ok ? await lockeeRes.json() : [];
|
||||
const khInvs = khRes.ok ? await khRes.json() : [];
|
||||
const vanillaInvs = vanillaRes.ok ? await vanillaRes.json() : [];
|
||||
|
||||
lockeeInvs.forEach(inv => { inv._type = 'lockee'; inv._key = inv.token; inv._otherName = inv.lockeeName; inv._otherPic = inv.lockeeProfilePic; });
|
||||
khInvs.forEach(inv => { inv._type = 'keyholder'; inv._key = inv.token; inv._otherName = inv.keyholderName; inv._otherPic = inv.keyholderProfilePic; });
|
||||
vanillaInvs.forEach(inv => { inv._type = 'vanilla'; inv._key = inv.einladungId; inv._otherName = inv.inviteeName; inv._otherPic = ''; });
|
||||
|
||||
sentItems = [...lockeeInvs, ...khInvs, ...vanillaInvs].sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
||||
sentPage = 0;
|
||||
renderSentPage();
|
||||
} catch(e) { console.error(e); }
|
||||
}
|
||||
|
||||
function renderSentPage() {
|
||||
const list = document.getElementById('sentList');
|
||||
const empty = document.getElementById('sentEmpty');
|
||||
list.innerHTML = '';
|
||||
|
||||
if (sentItems.length === 0) {
|
||||
empty.style.display = '';
|
||||
document.getElementById('sentPaging').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
empty.style.display = 'none';
|
||||
|
||||
const totalPages = Math.ceil(sentItems.length / PAGE_SIZE);
|
||||
const start = sentPage * PAGE_SIZE;
|
||||
const pageItems = sentItems.slice(start, start + PAGE_SIZE);
|
||||
|
||||
pageItems.forEach(inv => {
|
||||
const av = buildAvatarHtml(inv._otherPic, inv._type);
|
||||
const card = document.createElement('div');
|
||||
card.className = 'inv-card';
|
||||
card.id = 'sentinv-' + inv._key;
|
||||
|
||||
let typeLabel, line2sent, extra = '';
|
||||
if (inv._type === 'lockee') {
|
||||
typeLabel = 'Lockee-Einladung';
|
||||
line2sent = 'Lockee: ' + esc(inv.lockName);
|
||||
extra = inv.detailsVisible
|
||||
? ' <span style="font-size:0.72rem;">👁 Details sichtbar</span>'
|
||||
: ' <span style="font-size:0.72rem;">🙈 Details verborgen</span>';
|
||||
} else if (inv._type === 'vanilla') {
|
||||
typeLabel = 'Vanilla Game';
|
||||
line2sent = 'Spieleinladung';
|
||||
} else {
|
||||
typeLabel = 'Keyholder-Einladung';
|
||||
line2sent = 'Keyholder: ' + esc(inv.lockName);
|
||||
}
|
||||
card.innerHTML = `
|
||||
${av}
|
||||
<div class="inv-body">
|
||||
<div class="inv-line1">${esc(inv._otherName)}</div>
|
||||
<div class="inv-line2">${line2sent}</div>
|
||||
<div class="inv-line3">${typeLabel} · ${fmtDate(inv.createdAt)}${extra}</div>
|
||||
</div>
|
||||
<div style="flex-shrink:0;">
|
||||
<button onclick="cancelSentInvitation('${esc(inv._key)}', '${inv._type}', this)" style="margin:0;padding:0.45rem 1rem;font-size:0.85rem;width:auto;background:#c0392b;border:none;color:#fff;border-radius:6px;font-weight:600;cursor:pointer;">✕ Zurückziehen</button>
|
||||
</div>`;
|
||||
list.appendChild(card);
|
||||
});
|
||||
|
||||
renderPaging('sentPaging', sentPage, totalPages, 'goSentPage');
|
||||
}
|
||||
|
||||
function goSentPage(page) {
|
||||
const total = Math.ceil(sentItems.length / PAGE_SIZE);
|
||||
if (page < 0 || page >= total) return;
|
||||
sentPage = page;
|
||||
renderSentPage();
|
||||
document.getElementById('sentList').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
function removeSentItem(key) {
|
||||
sentItems = sentItems.filter(i => i._key !== key);
|
||||
const total = Math.ceil(sentItems.length / PAGE_SIZE);
|
||||
if (sentPage >= total && sentPage > 0) sentPage = total - 1;
|
||||
renderSentPage();
|
||||
}
|
||||
|
||||
// ── Bestätigungs-Modal ──
|
||||
let _confirmResolve = null;
|
||||
|
||||
function showConfirm(title, text) {
|
||||
document.getElementById('confirmTitle').textContent = title;
|
||||
document.getElementById('confirmText').textContent = text;
|
||||
document.getElementById('confirmModal').classList.add('open');
|
||||
document.querySelector('#confirmModal .confirm-modal-cancel').style.display = '';
|
||||
return new Promise(resolve => {
|
||||
_confirmResolve = resolve;
|
||||
document.getElementById('confirmOkBtn').onclick = () => { confirmClose(true); };
|
||||
});
|
||||
}
|
||||
|
||||
function showInfo(title, text) {
|
||||
document.getElementById('confirmTitle').textContent = title;
|
||||
document.getElementById('confirmText').textContent = text;
|
||||
document.getElementById('confirmModal').classList.add('open');
|
||||
document.querySelector('#confirmModal .confirm-modal-cancel').style.display = 'none';
|
||||
return new Promise(resolve => {
|
||||
_confirmResolve = resolve;
|
||||
document.getElementById('confirmOkBtn').onclick = () => { confirmClose(true); };
|
||||
});
|
||||
}
|
||||
|
||||
function confirmCancel() { confirmClose(false); }
|
||||
|
||||
function confirmClose(result) {
|
||||
document.getElementById('confirmModal').classList.remove('open');
|
||||
if (_confirmResolve) { _confirmResolve(result); _confirmResolve = null; }
|
||||
}
|
||||
|
||||
// ── Aktionen: Empfangen ──
|
||||
async function declineLockeeInvitation(token, btn) {
|
||||
if (!await showConfirm('Einladung ablehnen', 'Bist du sicher, dass du diese Einladung ablehnen möchtest?')) return;
|
||||
btn.disabled = true;
|
||||
try {
|
||||
const res = await fetch('/lockee/invitation/' + encodeURIComponent(token), { method: 'DELETE' });
|
||||
if (res.ok || res.status === 204) { removeRecvItem(token); }
|
||||
else { btn.disabled = false; }
|
||||
} catch(e) { btn.disabled = false; }
|
||||
}
|
||||
|
||||
async function declineKhInvitation(token, btn) {
|
||||
if (!await showConfirm('Einladung ablehnen', 'Bist du sicher, dass du diese Einladung ablehnen möchtest?')) return;
|
||||
btn.disabled = true;
|
||||
try {
|
||||
const res = await fetch('/keyholder/invitations/mine/' + encodeURIComponent(token), { method: 'DELETE' });
|
||||
if (res.ok || res.status === 204) { removeRecvItem(token); }
|
||||
else { btn.disabled = false; }
|
||||
} catch(e) { btn.disabled = false; }
|
||||
}
|
||||
|
||||
// ── Aktionen: Gesendet ──
|
||||
async function cancelSentInvitation(key, type, btn) {
|
||||
const title = 'Einladung zurückziehen';
|
||||
const text = type === 'lockee'
|
||||
? 'Das Lock wird gelöscht und der Lockee wird benachrichtigt.'
|
||||
: type === 'vanilla'
|
||||
? 'Der eingeladene Spieler wird benachrichtigt.'
|
||||
: 'Der Keyholder wird benachrichtigt.';
|
||||
if (!await showConfirm(title, text)) return;
|
||||
btn.disabled = true;
|
||||
const url = type === 'lockee'
|
||||
? '/lockee/invitations/sent/' + encodeURIComponent(key)
|
||||
: type === 'vanilla'
|
||||
? '/vanilla/einladung/' + encodeURIComponent(key)
|
||||
: '/keyholder/invitations/sent/' + encodeURIComponent(key);
|
||||
try {
|
||||
const res = await fetch(url, { method: 'DELETE' });
|
||||
if (res.ok || res.status === 204) { removeSentItem(key); }
|
||||
else { btn.disabled = false; }
|
||||
} catch(e) { btn.disabled = false; }
|
||||
}
|
||||
|
||||
// ── Lockee-Einladungs-Dialog ──
|
||||
// CARD_DEFS wird von /js/card-defs.js bereitgestellt.
|
||||
|
||||
function fmtMinutes(min) {
|
||||
if (!min) return '–';
|
||||
const d = Math.floor(min / (24 * 60));
|
||||
const h = Math.floor((min % (24 * 60)) / 60);
|
||||
const m = min % 60;
|
||||
const parts = [];
|
||||
if (d) parts.push(d + 'd');
|
||||
if (h) parts.push(h + 'h');
|
||||
if (m) parts.push(m + 'min');
|
||||
return parts.join(' ') || '–';
|
||||
}
|
||||
|
||||
function renderLockDetails(inv) {
|
||||
if (!inv.detailsVisible) {
|
||||
return `<div class="blind-hint">
|
||||
<span class="blind-hint-icon">🙈</span>
|
||||
<span>Der Keyholder hat die Lock-Details nicht freigegeben. Du weißt nicht, worauf du dich einlässt.</span>
|
||||
</div>`;
|
||||
}
|
||||
const cardCounts = inv.cardCounts || {};
|
||||
const totalCards = Object.values(cardCounts).reduce((a, b) => a + b, 0);
|
||||
const cardsHtml = CARD_DEFS
|
||||
.filter(c => cardCounts[c.id] > 0)
|
||||
.map(c => `<div class="lock-details-card-item">
|
||||
<img src="${c.img}" alt="${c.name}">
|
||||
<span class="ldc-count">${cardCounts[c.id]}×</span>
|
||||
<span class="ldc-name">${c.name}</span>
|
||||
</div>`).join('');
|
||||
const badges = [];
|
||||
badges.push(`🃏 ${totalCards} Karten`);
|
||||
badges.push(`⏱ Ziehen alle ${fmtMinutes(inv.pickEveryMinute)}`);
|
||||
if (inv.accumulatePicks) badges.push('📦 Picks akkumulieren');
|
||||
if (inv.showRemainingCards) badges.push('👁 Karten sichtbar');
|
||||
if (inv.hygineOpeningEveryMinites) badges.push(`🚿 Hygiene alle ${fmtMinutes(inv.hygineOpeningEveryMinites)} (${fmtMinutes(inv.hygineOpeningDurationMinutes)})`);
|
||||
if (inv.taskCount > 0) badges.push(`✅ ${inv.taskCount} Aufgabe${inv.taskCount !== 1 ? 'n' : ''}`);
|
||||
if (inv.requiresVerification) badges.push('🔍 Verifikation erforderlich');
|
||||
return `<div class="lock-details-section">
|
||||
<div class="lock-details-cards">${cardsHtml}</div>
|
||||
<div class="lock-details-meta">${badges.map(b => `<span class="lock-details-badge">${b}</span>`).join('')}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
let activeDialogToken = null;
|
||||
|
||||
async function openLockeeInviteDialog(token) {
|
||||
activeDialogToken = token;
|
||||
document.getElementById('dialogError').style.display = 'none';
|
||||
document.getElementById('dialogCodeLines').value = '5';
|
||||
document.getElementById('dialogDetailsArea').innerHTML = '<div style="color:var(--color-muted);font-size:0.85rem;">Lade Details…</div>';
|
||||
document.getElementById('lockeeInviteDialog').classList.add('open');
|
||||
|
||||
const card = document.getElementById('recvinv-' + token);
|
||||
const line1 = card?.querySelector('.inv-line1')?.textContent || '';
|
||||
const line2 = card?.querySelector('.inv-line2')?.textContent || '';
|
||||
const line3 = card?.querySelector('.inv-line3')?.textContent || '';
|
||||
const imgEl = card?.querySelector('.inv-avatar img');
|
||||
|
||||
const avatarEl = document.getElementById('dialogAvatar');
|
||||
avatarEl.innerHTML = imgEl ? `<img src="${imgEl.src}" alt="">` : '👤';
|
||||
document.getElementById('dialogTitle').textContent = line2;
|
||||
document.getElementById('dialogSub').textContent = line1 + ' lädt dich als Lockee ein';
|
||||
document.getElementById('dialogDetail').innerHTML =
|
||||
`<dt>Keyholder</dt><dd>${esc(line1)}</dd>` +
|
||||
`<dt>Lock-Name</dt><dd>${esc(line2)}</dd>` +
|
||||
`<dt>Datum</dt><dd>${esc(line3)}</dd>`;
|
||||
|
||||
try {
|
||||
const res = await fetch('/lockee/invitation/' + encodeURIComponent(token));
|
||||
if (res.ok) {
|
||||
document.getElementById('dialogDetailsArea').innerHTML = renderLockDetails(await res.json());
|
||||
} else {
|
||||
document.getElementById('dialogDetailsArea').innerHTML = '';
|
||||
}
|
||||
} catch(e) { document.getElementById('dialogDetailsArea').innerHTML = ''; }
|
||||
}
|
||||
|
||||
function closeLockeeInviteDialog() {
|
||||
document.getElementById('lockeeInviteDialog').classList.remove('open');
|
||||
activeDialogToken = null;
|
||||
}
|
||||
|
||||
async function acceptLockeeInviteDialog() {
|
||||
if (!activeDialogToken) return;
|
||||
const lines = parseInt(document.getElementById('dialogCodeLines').value);
|
||||
if (!lines || lines < 1) { showDialogError('Bitte eine Ziffernanzahl eingeben.'); return; }
|
||||
const acceptBtn = document.querySelector('.btn-accept');
|
||||
acceptBtn.disabled = true;
|
||||
document.getElementById('dialogError').style.display = 'none';
|
||||
try {
|
||||
const res = await fetch('/lockee/invitation/' + encodeURIComponent(activeDialogToken) + '/accept', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ unlockCodeLines: lines })
|
||||
});
|
||||
if (!res.ok) {
|
||||
acceptBtn.disabled = false;
|
||||
if (res.status === 409) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
showDialogError(data.error === 'active_lock_exists'
|
||||
? 'Du hast bereits ein aktives Lock als Lockee. Erst das bestehende Lock beenden, bevor ein neues angenommen werden kann.'
|
||||
: 'Diese Einladung wurde bereits angenommen.');
|
||||
} else {
|
||||
showDialogError('Fehler beim Annehmen der Einladung.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
document.getElementById('lockeeInviteDialog').classList.remove('open');
|
||||
removeRecvItem(activeDialogToken);
|
||||
showUnlockCodeModal(data.unlockCode, data.lockId);
|
||||
} catch(e) {
|
||||
acceptBtn.disabled = false;
|
||||
showDialogError('Fehler beim Annehmen der Einladung.');
|
||||
}
|
||||
}
|
||||
|
||||
async function declineLockeeInviteDialog() {
|
||||
if (!activeDialogToken) return;
|
||||
if (!await showConfirm('Einladung ablehnen', 'Bist du sicher, dass du diese Einladung ablehnen möchtest? Das Lock wird gelöscht.')) return;
|
||||
const declineBtn = document.querySelector('.btn-decline');
|
||||
declineBtn.disabled = true;
|
||||
try {
|
||||
const res = await fetch('/lockee/invitation/' + encodeURIComponent(activeDialogToken), { method: 'DELETE' });
|
||||
if (res.ok || res.status === 204) {
|
||||
removeRecvItem(activeDialogToken);
|
||||
closeLockeeInviteDialog();
|
||||
} else {
|
||||
declineBtn.disabled = false;
|
||||
showDialogError('Fehler beim Ablehnen der Einladung.');
|
||||
}
|
||||
} catch(e) { declineBtn.disabled = false; showDialogError('Fehler beim Ablehnen der Einladung.'); }
|
||||
}
|
||||
|
||||
function showDialogError(msg) {
|
||||
const el = document.getElementById('dialogError');
|
||||
el.textContent = msg;
|
||||
el.style.display = '';
|
||||
}
|
||||
|
||||
// ── Entsperrcode-Modal ──
|
||||
function showUnlockCodeModal(code, lockId) {
|
||||
document.getElementById('unlockCodeDisplay').textContent = code;
|
||||
const url = '/games/chastity/activelock.html?lockId=' + lockId;
|
||||
const btn = document.getElementById('unlockModalBtn');
|
||||
btn.onclick = () => startCodeScramble(code, url);
|
||||
document.getElementById('unlockModal').classList.add('open');
|
||||
}
|
||||
|
||||
function startCodeScramble(realCode, url) {
|
||||
const display = document.getElementById('unlockCodeDisplay');
|
||||
const btn = document.getElementById('unlockModalBtn');
|
||||
const hint = document.getElementById('unlockModalHint');
|
||||
const countdown = document.getElementById('unlockModalCountdown');
|
||||
const len = realCode.length;
|
||||
const DURATION = 3 * 60;
|
||||
let remaining = DURATION;
|
||||
let stopped = false;
|
||||
|
||||
function randomCode() {
|
||||
return Array.from({ length: len }, () => Math.floor(Math.random() * 10)).join('');
|
||||
}
|
||||
function finish() {
|
||||
stopped = true;
|
||||
clearInterval(scrambleInterval);
|
||||
clearInterval(countdownInterval);
|
||||
window.location.href = url;
|
||||
}
|
||||
if (hint) hint.style.display = 'none';
|
||||
countdown.style.display = '';
|
||||
document.getElementById('unlockModalTitle').textContent = 'Nun vergessen wir den Code…';
|
||||
btn.textContent = 'Abbrechen';
|
||||
btn.onclick = finish;
|
||||
|
||||
function updateCountdown() {
|
||||
const m = Math.floor(remaining / 60);
|
||||
const s = remaining % 60;
|
||||
countdown.textContent = `${m}:${String(s).padStart(2, '0')}`;
|
||||
}
|
||||
updateCountdown();
|
||||
const scrambleInterval = setInterval(() => { if (!stopped) display.textContent = randomCode(); }, 1000);
|
||||
const countdownInterval = setInterval(() => {
|
||||
if (stopped) return;
|
||||
remaining--;
|
||||
updateCountdown();
|
||||
if (remaining <= 0) finish();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// ── BDSM-Einladungs-Dialog ──
|
||||
let activeBdsmEinladungId = null;
|
||||
|
||||
function openBdsmInviteDialog(einladungId, inviterName, inviterPic) {
|
||||
activeBdsmEinladungId = einladungId;
|
||||
document.getElementById('bdsmDialogTitle').textContent = inviterName + ' lädt dich ein';
|
||||
document.getElementById('bdsmDialogError').style.display = 'none';
|
||||
const avatarEl = document.getElementById('bdsmDialogAvatar');
|
||||
avatarEl.innerHTML = inviterPic
|
||||
? `<img src="data:image/jpeg;base64,${inviterPic}" alt="" style="width:100%;height:100%;object-fit:cover;">`
|
||||
: '⛓️';
|
||||
document.getElementById('bdsmInviteDialog').classList.add('open');
|
||||
}
|
||||
|
||||
function closeBdsmInviteDialog() {
|
||||
document.getElementById('bdsmInviteDialog').classList.remove('open');
|
||||
activeBdsmEinladungId = null;
|
||||
}
|
||||
|
||||
async function _bdsmAntworten(mode) {
|
||||
if (!activeBdsmEinladungId) return;
|
||||
const accepted = mode !== null;
|
||||
const errEl = document.getElementById('bdsmDialogError');
|
||||
errEl.style.display = 'none';
|
||||
try {
|
||||
const res = await fetch(`/bdsm/einladung/${activeBdsmEinladungId}/antwort`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ accepted, mode }),
|
||||
});
|
||||
if (!res.ok) throw new Error();
|
||||
const key = activeBdsmEinladungId;
|
||||
closeBdsmInviteDialog();
|
||||
removeRecvItem(key);
|
||||
if (mode === 'OWN_DEVICE') {
|
||||
window.location.href = `/games/bdsm/neubdsm.html`;
|
||||
} else if (mode === 'HOST_DEVICE') {
|
||||
await showInfo('Einladung angenommen', 'Das Spiel findet am Gerät des Hosts statt. Du wirst zur Startseite weitergeleitet.');
|
||||
window.location.href = '/userhome.html';
|
||||
}
|
||||
} catch (_) {
|
||||
errEl.textContent = 'Fehler beim Speichern der Antwort.';
|
||||
errEl.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
function acceptBdsmOwnDevice() { _bdsmAntworten('OWN_DEVICE'); }
|
||||
function acceptBdsmHostDevice() { _bdsmAntworten('HOST_DEVICE'); }
|
||||
|
||||
async function declineBdsmFromDialog() {
|
||||
if (!await showConfirm('Einladung ablehnen', 'Möchtest du diese BDSM-Game-Einladung wirklich ablehnen?')) return;
|
||||
_bdsmAntworten(null);
|
||||
}
|
||||
|
||||
// ── Vanilla-Einladungs-Dialog ──
|
||||
let activeVanillaEinladungId = null;
|
||||
|
||||
function openVanillaInviteDialog(einladungId, inviterName) {
|
||||
activeVanillaEinladungId = einladungId;
|
||||
document.getElementById('vanillaDialogTitle').textContent = inviterName + ' lädt dich ein';
|
||||
document.getElementById('vanillaDialogError').style.display = 'none';
|
||||
document.getElementById('vanillaInviteDialog').classList.add('open');
|
||||
}
|
||||
|
||||
function closeVanillaInviteDialog() {
|
||||
document.getElementById('vanillaInviteDialog').classList.remove('open');
|
||||
activeVanillaEinladungId = null;
|
||||
}
|
||||
|
||||
async function _vanillaAntworten(mode) {
|
||||
if (!activeVanillaEinladungId) return;
|
||||
const accepted = mode !== null;
|
||||
const errEl = document.getElementById('vanillaDialogError');
|
||||
errEl.style.display = 'none';
|
||||
try {
|
||||
const res = await fetch(`/vanilla/einladung/${activeVanillaEinladungId}/antwort`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ accepted, mode }),
|
||||
});
|
||||
if (!res.ok) throw new Error();
|
||||
const key = activeVanillaEinladungId;
|
||||
closeVanillaInviteDialog();
|
||||
removeRecvItem(key);
|
||||
if (mode === 'OWN_DEVICE') {
|
||||
window.location.href = '/games/vanilla/neuvanilla.html';
|
||||
} else if (mode === 'HOST_DEVICE') {
|
||||
await showInfo('Einladung angenommen', 'Das Spiel findet am Gerät des Hosts statt. Du wirst zur Startseite weitergeleitet.');
|
||||
window.location.href = '/userhome.html';
|
||||
}
|
||||
} catch (_) {
|
||||
errEl.textContent = 'Fehler beim Speichern der Antwort.';
|
||||
errEl.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
function acceptVanillaOwnDevice() { _vanillaAntworten('OWN_DEVICE'); }
|
||||
function acceptVanillaHostDevice() { _vanillaAntworten('HOST_DEVICE'); }
|
||||
|
||||
async function declineVanillaFromDialog() {
|
||||
if (!await showConfirm('Einladung ablehnen', 'Möchtest du diese Vanilla-Game-Einladung wirklich ablehnen?')) return;
|
||||
_vanillaAntworten(null);
|
||||
}
|
||||
|
||||
// ── Esc schließt Dialog ──
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape') {
|
||||
if (document.getElementById('vanillaInviteDialog').classList.contains('open')) closeVanillaInviteDialog();
|
||||
if (document.getElementById('bdsmInviteDialog').classList.contains('open')) closeBdsmInviteDialog();
|
||||
if (document.getElementById('lockeeInviteDialog').classList.contains('open')) closeLockeeInviteDialog();
|
||||
}
|
||||
});
|
||||
|
||||
// ── Alles laden ──
|
||||
loadReceivedInvitations();
|
||||
loadSentInvitations();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user