Files
xxx-sphere-web/bin/main/static/games/chastity/taskgame.html
Mario 843acea652
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
Light- und Darkmode hinzugefügt
2026-04-28 14:07:32 +02:00

449 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>Task Game xXx Sphere</title>
<link rel="stylesheet" href="/css/variables.css">
<link rel="stylesheet" href="/css/style.css">
<style>
.game-card {
background: var(--color-card);
border: 1px solid var(--color-secondary);
border-radius: 14px;
padding: 1.5rem;
margin-bottom: 1.25rem;
}
.game-label {
font-size: 0.72rem;
font-weight: 700;
color: var(--color-muted);
text-transform: uppercase;
letter-spacing: 0.08em;
margin-bottom: 0.5rem;
}
.game-text {
font-size: 1rem;
line-height: 1.6;
color: var(--color-text);
white-space: pre-wrap;
}
.game-timer {
font-size: 2.2rem;
font-weight: 700;
color: var(--color-primary);
text-align: center;
margin: 0.75rem 0;
letter-spacing: 0.04em;
}
.game-timer.urgent { color: #e74c3c; }
.level-display {
display: flex;
justify-content: center;
margin-bottom: 1.25rem;
}
.level-display img {
width: 72px;
height: 72px;
object-fit: contain;
}
.lock-messages {
background: rgba(233,69,96,0.1);
border: 1px solid rgba(233,69,96,0.3);
border-radius: 10px;
padding: 1rem 1.1rem;
margin-bottom: 1.25rem;
}
.lock-messages p { margin: 0 0 0.5rem; font-size: 0.9rem; line-height: 1.5; }
.lock-messages p:last-child { margin: 0; }
.init-box {
background: var(--color-card);
border: 1px solid var(--color-secondary);
border-radius: 14px;
padding: 1.5rem;
}
.init-box h2 { font-size: 1.1rem; margin: 0 0 0.75rem; }
.group-list { display: flex; flex-direction: column; gap: 0.6rem; margin-bottom: 1rem; }
.group-item {
display: flex; align-items: center; gap: 0.75rem;
padding: 0.75rem 1rem;
border: 1px solid var(--color-secondary);
border-radius: 10px;
cursor: pointer;
transition: border-color 0.2s, background 0.2s;
}
.group-item:hover, .group-item.selected {
border-color: var(--color-primary);
background: rgba(var(--color-primary-rgb, 233,69,96), 0.06);
}
.group-item input[type=radio] { accent-color: var(--color-primary); }
.btn-primary {
width: 100%;
padding: 0.75rem;
border-radius: 10px;
border: none;
background: var(--color-primary);
color: #fff;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
}
.btn-primary:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-secondary {
width: 100%;
padding: 0.65rem;
border-radius: 10px;
border: 1px solid var(--color-secondary);
background: transparent;
color: var(--color-text);
font-size: 0.92rem;
cursor: pointer;
margin-top: 0.6rem;
}
#finisherBox {
background: linear-gradient(135deg, rgba(233,69,96,0.15), rgba(155,89,182,0.12));
border: 1px solid rgba(233,69,96,0.4);
border-radius: 14px;
padding: 1.5rem;
text-align: center;
}
#finisherBox .trophy { font-size: 2.5rem; margin-bottom: 0.5rem; }
#finisherBox h2 { font-size: 1.1rem; margin: 0 0 0.75rem; color: var(--color-primary); }
</style>
</head>
<body class="app">
<div class="main">
<div class="content">
<h2 style="margin-bottom:1.25rem;">🎯 Task Game</h2>
<!-- Level-Anzeige -->
<div class="level-display" id="levelDisplay" style="display:none;">
<img id="levelImg" src="" alt="Level">
</div>
<!-- Freigegebene Locks (checkLocks-Meldungen) -->
<div id="lockMessages" class="lock-messages" style="display:none;"></div>
<!-- Initialisierung: Gruppe wählen -->
<div id="initBox" class="init-box" style="display:none;">
<h2>Spiel-Set auswählen</h2>
<p style="font-size:0.85rem;color:var(--color-muted);margin:0 0 1rem;">
Wähle die Aufgabengruppe für dieses Spiel.
</p>
<div id="groupList" class="group-list"></div>
<button class="btn-primary" id="btnStart" disabled onclick="startGame()">▶ Spiel starten</button>
</div>
<!-- Laufendes Spiel -->
<div id="gameBox" style="display:none;">
<!-- Task oder Lock in Queue -->
<div id="queueBox" class="game-card" style="display:none;">
<div class="game-label" id="queueLabel"></div>
<div class="game-text" id="queueText"></div>
<div style="display:flex;gap:0.6rem;margin-top:1.1rem;">
<button class="btn-primary" id="btnOk" onclick="handleOk()">OK</button>
</div>
</div>
<!-- Aktive Aufgabe (läuft) -->
<div id="activeBox" class="game-card" style="display:none;">
<div class="game-label">Aktive Aufgabe</div>
<div class="game-text" id="activeText"></div>
<div class="game-timer" id="activeTimer" style="display:none;"></div>
<div style="display:flex;gap:0.6rem;margin-top:1.1rem;">
<button class="btn-primary" id="btnErledigt" onclick="handleErledigt()">✓ Erledigt</button>
</div>
</div>
<!-- Finisher -->
<div id="finisherBox" style="display:none;">
<div class="trophy">🏆</div>
<h2>Level 6 erreicht!</h2>
<div class="game-label" id="finisherTitle"></div>
<div class="game-text" id="finisherText" style="margin-top:0.5rem;text-align:left;"></div>
<button class="btn-primary" onclick="completeGame()" style="margin-top:1.25rem;">✓ Spiel beenden</button>
</div>
</div>
<div id="loadingHint" style="text-align:center;color:var(--color-muted);padding:2rem 0;font-size:0.9rem;">
Wird geladen…
</div>
<div id="errorBox" style="display:none;background:rgba(231,76,60,0.1);border:1px solid rgba(231,76,60,0.3);
border-radius:10px;padding:1rem;font-size:0.88rem;color:#e74c3c;margin-top:1rem;"></div>
</div>
</div>
<script src="/js/icons.js"></script>
<script src="/js/nav.js"></script>
<script>
const params = new URLSearchParams(location.search);
const lockId = params.get('lockId');
const autoGameSetId = params.get('gameSetId');
let _state = null;
let _timerInt = null;
let _pendingIsLock = false;
let _pendingHasDuration = false;
function goBack() {
if (lockId) location.href = '/games/chastity/activelock.html?lockId=' + lockId;
else history.back();
}
async function completeGame() {
const url = '/lock-game/complete' + (lockId ? '?lockId=' + lockId : '');
await fetch(url, { method: 'POST' });
goBack();
}
// ── Init ──────────────────────────────────────────────────────────────────
async function boot() {
try {
const r = await fetch('/lock-game/state');
if (r.status === 404) {
if (autoGameSetId) {
await autoStartGame(autoGameSetId);
} else {
await loadGroups();
}
return;
}
if (!r.ok) throw new Error('Fehler beim Laden des Spielzustands');
_state = await r.json();
hide('loadingHint');
await runGameLoop();
} catch (e) {
showError(e.message);
}
}
async function autoStartGame(gameSetId) {
try {
const r = await fetch('/lock-game/init?aufgabenGruppeId=' + gameSetId, { method: 'POST' });
if (!r.ok) throw new Error('Initialisierung fehlgeschlagen');
const stateR = await fetch('/lock-game/state');
_state = await stateR.json();
hide('loadingHint');
await runGameLoop();
} catch (e) {
showError(e.message);
}
}
async function loadGroups() {
const r = await fetch('/lock-game/groups');
const groups = r.ok ? await r.json() : [];
hide('loadingHint');
const list = document.getElementById('groupList');
if (groups.length === 0) {
list.innerHTML = '<p style="font-size:0.85rem;color:var(--color-muted);">Keine passenden Aufgabengruppen gefunden.<br>Erstelle zuerst eine Gruppe vom Typ „Chastity Only".</p>';
} else {
list.innerHTML = groups.map(g => `
<label class="group-item" onclick="selectGroup(this)">
<input type="radio" name="gruppe" value="${g.gruppenId}" style="display:none;">
<div>
<div style="font-weight:600;font-size:0.95rem;">${esc(g.name)}</div>
${g.beschreibung ? `<div style="font-size:0.8rem;color:var(--color-muted);">${esc(g.beschreibung)}</div>` : ''}
</div>
</label>`).join('');
}
show('initBox');
}
function selectGroup(el) {
document.querySelectorAll('.group-item').forEach(i => i.classList.remove('selected'));
el.classList.add('selected');
el.querySelector('input').checked = true;
document.getElementById('btnStart').disabled = false;
}
async function startGame() {
const sel = document.querySelector('input[name="gruppe"]:checked');
if (!sel) return;
hide('initBox');
show('loadingHint');
try {
const r = await fetch('/lock-game/init?aufgabenGruppeId=' + sel.value, { method: 'POST' });
if (!r.ok) throw new Error('Initialisierung fehlgeschlagen');
const stateR = await fetch('/lock-game/state');
_state = await stateR.json();
hide('loadingHint');
await runGameLoop();
} catch (e) {
showError(e.message);
}
}
// ── Game Loop ─────────────────────────────────────────────────────────────
async function runGameLoop() {
hide('queueBox');
hide('activeBox');
hide('finisherBox');
clearTimer();
if (_state.level >= 6) {
await showFinisher();
return;
}
renderLevelBar(_state.level);
// Aktive Aufgabe läuft noch
if (_state.activeTask) {
showActiveTask(_state.activeTask, _state.activeTaskEnd);
return;
}
// Queue leer → nächsten Task holen
if (!_state.taskInQueue && !_state.lockInQueue) {
await fetch('/lock-game/next-task', { method: 'POST' });
const r = await fetch('/lock-game/state');
_state = await r.json();
}
showQueue();
}
function showQueue() {
if (_state.lockInQueue) {
let sperre;
try { sperre = JSON.parse(_state.lockInQueue); } catch { sperre = {}; }
_pendingIsLock = true;
_pendingHasDuration = !!(sperre.minutenVon || sperre.minutenBis);
document.getElementById('queueLabel').textContent = '🔒 Neue Sperre';
document.getElementById('queueText').textContent = sperre.text || _state.lockInQueue;
} else if (_state.taskInQueue) {
let aufgabe;
try { aufgabe = JSON.parse(_state.taskInQueue); } catch { aufgabe = {}; }
_pendingIsLock = false;
_pendingHasDuration = !!(aufgabe.sekundenVon || aufgabe.sekundenBis);
document.getElementById('queueLabel').textContent = '🎯 Neue Aufgabe';
document.getElementById('queueText').textContent = aufgabe.text || _state.taskInQueue;
}
show('gameBox');
show('queueBox');
}
function showActiveTask(text, endIso) {
document.getElementById('activeText').textContent = text;
show('gameBox');
show('activeBox');
if (endIso) {
const end = new Date(endIso);
startTimer(end, document.getElementById('activeTimer'));
}
}
async function handleOk() {
// Locks prüfen (nach jeder Aktion)
await checkAndShowLocks();
if (_pendingIsLock || _pendingHasDuration) {
// Task/Sperre aktivieren und Timer starten
const r = await fetch('/lock-game/apply-task', { method: 'POST' });
if (!r.ok) { showError('Fehler'); return; }
const stateR = await fetch('/lock-game/state');
_state = await stateR.json();
} else {
// Keine Dauer → sofort nächsten Task
await fetch('/lock-game/next-task', { method: 'POST' });
const r = await fetch('/lock-game/state');
_state = await r.json();
}
await runGameLoop();
}
async function handleErledigt() {
await checkAndShowLocks();
if (_state.level >= 6) {
await showFinisher();
return;
}
await fetch('/lock-game/next-task', { method: 'POST' });
const r = await fetch('/lock-game/state');
_state = await r.json();
await runGameLoop();
}
async function checkAndShowLocks() {
const r = await fetch('/lock-game/check-locks', { method: 'POST' });
if (!r.ok) return;
const texts = await r.json();
if (texts && texts.length > 0) {
const box = document.getElementById('lockMessages');
box.innerHTML = texts.map(t => `<p>🔓 ${esc(t)}</p>`).join('');
show('lockMessages');
await new Promise(res => setTimeout(res, 2000));
}
}
async function showFinisher() {
show('gameBox');
const r = await fetch('/lock-game/finisher');
if (!r.ok) {
document.getElementById('finisherTitle').textContent = '';
document.getElementById('finisherText').textContent = 'Glückwunsch du hast Level 6 erreicht!';
} else {
const f = await r.json();
document.getElementById('finisherTitle').textContent = f.kurzText || '';
document.getElementById('finisherText').textContent = f.text || '';
}
show('finisherBox');
}
// ── Level-Bar ─────────────────────────────────────────────────────────────
function renderLevelBar(level) {
const lvl = Math.min(Math.max(level, 1), 5);
document.getElementById('levelImg').src = `/img/lvl${lvl}.png`;
show('levelDisplay');
}
// ── Timer ─────────────────────────────────────────────────────────────────
function startTimer(endDate, el) {
el.style.display = '';
clearTimer();
_timerInt = setInterval(() => {
const diff = Math.max(0, Math.round((endDate - Date.now()) / 1000));
const m = String(Math.floor(diff / 60)).padStart(2, '0');
const s = String(diff % 60).padStart(2, '0');
el.textContent = m + ':' + s;
el.classList.toggle('urgent', diff < 30);
if (diff === 0) clearTimer();
}, 1000);
}
function clearTimer() {
if (_timerInt) { clearInterval(_timerInt); _timerInt = null; }
}
// ── Hilfsfunktionen ───────────────────────────────────────────────────────
function show(id) { const el = document.getElementById(id); if (el) el.style.display = ''; }
function hide(id) { const el = document.getElementById(id); if (el) el.style.display = 'none'; }
function esc(s) { return String(s).replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
function showError(msg) {
hide('loadingHint');
const box = document.getElementById('errorBox');
box.textContent = msg || 'Ein Fehler ist aufgetreten.';
box.style.display = '';
}
boot();
</script>
</body>
</html>