Verschiebung nach anderem RePo - nun pro Projekt getrennt

This commit is contained in:
2026-04-01 10:41:19 +02:00
commit 7b9eda1d62
1048 changed files with 93351 additions and 0 deletions

View File

@@ -0,0 +1,307 @@
<!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>Lock-Einladung xXx Sphere</title>
<link rel="stylesheet" href="/css/variables.css">
<link rel="stylesheet" href="/css/style.css">
<style>
.invite-card {
background: var(--color-card);
border: 1px solid var(--color-secondary);
border-radius: 12px;
padding: 2rem 1.5rem;
width: 100%;
text-align: center;
}
.invite-icon { font-size: 3rem; margin-bottom: 1rem; }
.invite-title { font-size: 1.3rem; font-weight: 700; margin-bottom: 0.4rem; }
.invite-sub { color: var(--color-muted); font-size: 0.9rem; margin-bottom: 1.5rem; }
.invite-detail {
background: var(--color-secondary);
border-radius: 8px;
padding: 0.75rem 1rem;
margin-bottom: 1.5rem;
text-align: left;
font-size: 0.9rem;
}
.invite-detail dt { color: var(--color-muted); font-size: 0.78rem; margin-bottom: 0.1rem; }
.invite-detail dd { font-weight: 600; margin: 0 0 0.5rem 0; }
.invite-detail dd:last-child { margin-bottom: 0; }
.invite-actions { display: flex; gap: 0.75rem; justify-content: center; flex-wrap: wrap; }
.invite-actions button { width: auto; padding: 0.65rem 1.5rem; }
.btn-danger { background: #c0392b !important; }
.btn-danger:hover { background: #a93226 !important; }
.code-lines-row {
display: flex;
align-items: center;
gap: 0.5rem;
justify-content: center;
margin-bottom: 1.25rem;
}
.code-lines-row input { width: 80px; text-align: center; }
.code-lines-row span { color: var(--color-text); font-size: 0.9rem; }
/* Unlock-Code-Modal */
.unlock-modal-bg {
display: none;
position: fixed;
inset: 0;
z-index: 400;
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.55);
}
.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%;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
z-index: 1;
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;
}
#stateLoading { display: none; }
#stateError { display: none; }
#stateAlready { display: none; }
#stateDeclined { display: none; }
</style>
</head>
<body class="app">
<div class="main">
<div class="content">
<div id="stateLoading" style="text-align:center;padding:3rem 1rem;color:var(--color-muted);">Lade Einladung…</div>
<div id="stateError" style="text-align:center;padding:3rem 1rem;">
<div style="font-size:2.5rem;margin-bottom:1rem;">⚠️</div>
<h2 style="margin-bottom:0.5rem;">Einladung nicht gefunden</h2>
<p style="color:var(--color-muted);">Diese Einladung existiert nicht oder wurde bereits bearbeitet.</p>
<a href="/games/common/einladungen.html" style="display:inline-block;margin-top:1.5rem;padding:0.65rem 1.5rem;background:var(--color-primary);color:#fff;border-radius:8px;text-decoration:none;font-weight:600;">Zu meinen Einladungen</a>
</div>
<div id="stateAlready" style="text-align:center;padding:3rem 1rem;">
<div style="font-size:2.5rem;margin-bottom:1rem;">🔒</div>
<h2 style="margin-bottom:0.5rem;">Lock bereits aktiv</h2>
<p style="color:var(--color-muted);">Diese Einladung wurde bereits angenommen.</p>
</div>
<div id="stateDeclined" style="text-align:center;padding:3rem 1rem;">
<div style="font-size:2.5rem;margin-bottom:1rem;"></div>
<h2 style="margin-bottom:0.5rem;">Einladung abgelehnt</h2>
<p style="color:var(--color-muted);">Du hast die Einladung abgelehnt. Der Keyholder wurde benachrichtigt.</p>
<a href="/games/common/einladungen.html" style="display:inline-block;margin-top:1.5rem;padding:0.65rem 1.5rem;background:var(--color-primary);color:#fff;border-radius:8px;text-decoration:none;font-weight:600;">Zu meinen Einladungen</a>
</div>
<div id="stateInvite" style="display:none;">
<div class="invite-card">
<div class="invite-icon">🔒</div>
<div class="invite-title">Lock-Einladung</div>
<div class="invite-sub" id="invSubtitle"></div>
<dl class="invite-detail" id="invDetail"></dl>
<div id="acceptSection">
<p style="font-size:0.88rem;color:var(--color-muted);margin-bottom:0.75rem;">
Wie viele Ziffern soll dein Entsperrcode haben?
</p>
<div class="code-lines-row">
<input type="number" id="codeLines" min="1" max="20" value="5">
<span>Ziffern</span>
</div>
<div class="invite-actions">
<button class="btn-danger" onclick="declineInvitation()">✕ Ablehnen</button>
<button onclick="acceptInvitation()">✓ Annehmen</button>
</div>
</div>
<div id="errorMsg" style="color:#e74c3c;font-size:0.85rem;margin-top:0.5rem;display:none;"></div>
</div>
</div>
</div>
</div>
<!-- Unlock-Code-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/icons.js"></script>
<script src="/js/sidebar.js"></script>
<script src="/js/social-sidebar.js"></script>
<script>
const params = new URLSearchParams(window.location.search);
const token = params.get('token');
let lockId = null;
document.getElementById('stateLoading').style.display = '';
async function load() {
if (!token) { showState('stateError'); return; }
try {
const res = await fetch('/lockee/invitation/' + encodeURIComponent(token));
if (res.status === 409) { showState('stateAlready'); return; }
if (!res.ok) { showState('stateError'); return; }
const inv = await res.json();
lockId = inv.lockId;
document.getElementById('invSubtitle').textContent =
inv.keyholderName + ' hat dich als Lockee eingeladen';
const dl = document.getElementById('invDetail');
dl.innerHTML = `
<dt>Lock-Name</dt><dd>${esc(inv.lockName)}</dd>
<dt>Keyholder</dt><dd>${esc(inv.keyholderName)}</dd>`;
showState('stateInvite');
} catch(e) {
showState('stateError');
}
}
function showState(id) {
document.getElementById('stateLoading').style.display = 'none';
['stateError','stateAlready','stateInvite','stateDeclined'].forEach(s => {
document.getElementById(s).style.display = s === id ? '' : 'none';
});
}
function esc(s) { const d = document.createElement('div'); d.textContent = s ?? ''; return d.innerHTML; }
async function acceptInvitation() {
const lines = parseInt(document.getElementById('codeLines').value);
if (!lines || lines < 1) { showError('Bitte eine Ziffernanzahl eingeben.'); return; }
const btn = document.querySelector('#acceptSection button:last-child');
btn.disabled = true;
document.getElementById('errorMsg').style.display = 'none';
try {
const res = await fetch('/lockee/invitation/' + encodeURIComponent(token) + '/accept', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ unlockCodeLines: lines })
});
if (!res.ok) { btn.disabled = false; showError('Fehler beim Annehmen der Einladung.'); return; }
const data = await res.json();
showUnlockCodeModal(data.unlockCode, data.lockId);
} catch(e) {
btn.disabled = false;
showError('Fehler beim Annehmen der Einladung.');
}
}
async function declineInvitation() {
if (!confirm('Bist du sicher, dass du diese Einladung ablehnen möchtest? Das Lock wird gelöscht.')) return;
const btn = document.querySelector('.btn-danger');
btn.disabled = true;
try {
const res = await fetch('/lockee/invitation/' + encodeURIComponent(token), { method: 'DELETE' });
if (res.ok || res.status === 204) {
showState('stateDeclined');
} else {
btn.disabled = false;
showError('Fehler beim Ablehnen der Einladung.');
}
} catch(e) {
btn.disabled = false;
showError('Fehler beim Ablehnen der Einladung.');
}
}
function showError(msg) {
const el = document.getElementById('errorMsg');
el.textContent = msg;
el.style.display = '';
}
function showUnlockCodeModal(code, lid) {
document.getElementById('unlockCodeDisplay').textContent = code;
const url = '/games/chastity/activelock.html?lockId=' + lid;
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);
}
load();
</script>
</body>
</html>