Weitere Fixes am Taskgame
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:
@@ -23,6 +23,7 @@
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 0.5rem;
|
||||
height: 1.2rem;
|
||||
text-align: center;
|
||||
}
|
||||
.game-text {
|
||||
font-size: 1rem;
|
||||
@@ -31,6 +32,7 @@
|
||||
white-space: pre-wrap;
|
||||
height: 14rem;
|
||||
overflow-y: auto;
|
||||
text-align: center;
|
||||
}
|
||||
.game-timer {
|
||||
font-size: 2.2rem;
|
||||
@@ -52,48 +54,9 @@
|
||||
margin-top: 1rem;
|
||||
height: 2.75rem;
|
||||
}
|
||||
#confirmModal { display:none; }
|
||||
#confirmModal.open { display:flex; }
|
||||
|
||||
.game-requirements {
|
||||
margin: 0.75rem 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.game-requirements-label {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.07em;
|
||||
color: var(--color-muted);
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
||||
.req-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
padding: 0.4rem 0.6rem;
|
||||
border-radius: 7px;
|
||||
background: rgba(255,255,255,0.03);
|
||||
border: 1px solid var(--color-secondary);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text);
|
||||
}
|
||||
.req-check input[type="checkbox"] {
|
||||
accent-color: var(--color-primary);
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.req-check.done {
|
||||
border-color: var(--color-primary);
|
||||
background: rgba(233,69,96,0.07);
|
||||
color: var(--color-muted);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.level-display {
|
||||
display: flex;
|
||||
@@ -106,16 +69,6 @@
|
||||
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);
|
||||
@@ -176,15 +129,13 @@
|
||||
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;
|
||||
#levelTrophy {
|
||||
font-size: 3.5rem;
|
||||
line-height: 72px;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
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">
|
||||
@@ -195,11 +146,9 @@
|
||||
<!-- Level-Anzeige -->
|
||||
<div class="level-display" id="levelDisplay" style="display:none;">
|
||||
<img id="levelImg" src="" alt="Level">
|
||||
<span id="levelTrophy" style="display:none;">🏆</span>
|
||||
</div>
|
||||
|
||||
<!-- Freigegebene Locks (checkLocks-Meldungen) -->
|
||||
<div id="lockMessages" class="lock-messages" style="display:none;"></div>
|
||||
|
||||
<!-- Toy-Auswahl vor Spielstart -->
|
||||
<div id="toyBox" style="display:none;">
|
||||
<div class="game-card">
|
||||
@@ -232,8 +181,7 @@
|
||||
<div id="gameCard" class="game-card" style="display:none;">
|
||||
<div class="game-label" id="gameLabel"></div>
|
||||
<div class="game-text" id="gameText"></div>
|
||||
<div id="gameRequirements" class="game-requirements" style="display:none;"></div>
|
||||
<div class="game-timer" id="gameTimer"></div>
|
||||
<div class="game-timer" id="gameTimer"></div>
|
||||
<div class="game-btn-row">
|
||||
<button class="btn-primary" id="gameBtn" onclick="handleGameBtn()" style="width:100%;height:100%;"></button>
|
||||
</div>
|
||||
@@ -241,38 +189,50 @@
|
||||
|
||||
<!-- Release-Text (Sperren) -->
|
||||
<div id="lockReleaseBox" class="game-card" style="display:none;">
|
||||
<div class="game-label">🔓 Sperre aufgehoben</div>
|
||||
<div class="game-label">🔓 Zeitstrafe verbüßt</div>
|
||||
<div class="game-text" id="releaseText"></div>
|
||||
<div style="margin-top:1.1rem;">
|
||||
<button class="btn-primary" id="btnReleaseOk">OK</button>
|
||||
<div class="game-timer"></div>
|
||||
<div class="game-btn-row">
|
||||
<button class="btn-primary" id="btnReleaseOk" style="width:100%;height:100%;">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Temporäre Öffnung -->
|
||||
<div id="tempOpeningBox" class="game-card" style="display:none;">
|
||||
<div class="game-label">🔓 Temporäre Öffnung erforderlich</div>
|
||||
<div class="game-text" id="tempOpeningTask"></div>
|
||||
<div id="tempOpeningCodeRow" style="display:none; margin-top:1rem; text-align:center;">
|
||||
<div class="game-label">Entsperrcode</div>
|
||||
<div id="tempOpeningCode" style="font-size:1.8rem; font-weight:700; letter-spacing:0.18em; padding:0.6rem 0; color:var(--color-primary);"></div>
|
||||
<!-- Phase 1: Öffnung aktiv -->
|
||||
<div id="tempPhase1">
|
||||
<div class="game-label" style="text-align:center;">🔓 Entsperrcode</div>
|
||||
<div class="game-text" id="tempOpeningCode"
|
||||
style="display:flex;align-items:center;justify-content:center;
|
||||
font-size:1.8rem;font-weight:700;letter-spacing:0.18em;
|
||||
color:var(--color-primary);white-space:normal;"></div>
|
||||
<div class="game-timer" id="tempOpeningTimer" style="margin-top:0.75rem;"></div>
|
||||
<div class="game-btn-row">
|
||||
<button class="btn-primary" style="width:100%;height:100%;" onclick="doEndTempOpening()">✓ Erledigt</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="game-btn-row">
|
||||
<button class="btn-primary" onclick="doEndTempOpening()">✓ Erledigt</button>
|
||||
<!-- Phase 2: Neuer Code zum Schließen -->
|
||||
<div id="tempPhase2" style="display:none;">
|
||||
<div class="game-label" style="text-align:center;">🔒 Lock schließen</div>
|
||||
<div class="game-text" style="display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0.75rem;white-space:normal;">
|
||||
<span style="font-size:0.9rem;color:var(--color-muted);text-align:center;">Nutze den Entsperrcode um den Schlüssel wieder zu verschließen.</span>
|
||||
<div id="tempNewCode" style="font-size:1.8rem;font-weight:700;letter-spacing:0.18em;color:var(--color-primary);text-align:center;"></div>
|
||||
<div id="tempScrambleCountdown" style="display:none;font-size:0.82rem;color:var(--color-muted);font-family:monospace;text-align:center;"></div>
|
||||
</div>
|
||||
<div class="game-timer"></div>
|
||||
<div class="game-btn-row">
|
||||
<button class="btn-primary" id="tempPhase2Btn" style="width:100%;height:100%;" onclick="startTempScramble()">OK</button>
|
||||
</div>
|
||||
</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>
|
||||
<div id="finisherStart" style="margin-top:1.25rem;">
|
||||
<button class="btn-primary" onclick="doStartFinisher()">▶ Starten</button>
|
||||
</div>
|
||||
<div id="finisherRunning" style="display:none;margin-top:1.25rem;">
|
||||
<div class="game-timer active" id="finisherTimer">00:00</div>
|
||||
<button class="btn-primary" onclick="doEndFinisher()" style="margin-top:1rem;">✓ Erledigt</button>
|
||||
<div id="finisherBox" class="game-card" style="display:none;">
|
||||
<div class="game-label" id="finisherLabel"></div>
|
||||
<div class="game-text" id="finisherText"></div>
|
||||
<div class="game-timer" id="finisherTimer"></div>
|
||||
<div class="game-btn-row">
|
||||
<button class="btn-primary" id="finisherBtn" style="width:100%;height:100%;"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -291,6 +251,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-backdrop" id="confirmModal">
|
||||
<div class="modal" style="max-width:420px;">
|
||||
<h2>Bestätigung</h2>
|
||||
<p id="confirmModalText" style="color:var(--color-text);margin-bottom:1.25rem;line-height:1.5;"></p>
|
||||
<div class="modal-actions">
|
||||
<button class="btn-cancel" id="confirmModalCancel">Nein</button>
|
||||
<button class="btn-save" id="confirmModalOk" style="background:var(--color-danger,#e74c3c);">Ja, abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/icons.js"></script>
|
||||
<script src="/js/nav.js"></script>
|
||||
<script>
|
||||
@@ -302,6 +273,10 @@
|
||||
let _state = null;
|
||||
let _timerInt = null;
|
||||
let _gameAction = null; // 'queue-start' | 'queue-done' | 'active-running' | 'active-done'
|
||||
let _tempOpeningTimerInt = null;
|
||||
let _tempScrambleTimer = null;
|
||||
let _tempScrambleCd = null;
|
||||
let _tempOpeningFromQueue = false; // true only when opening was triggered from a queued Zeitstrafe (doQueueStart)
|
||||
|
||||
function goBack() {
|
||||
if (lockId) location.href = '/games/chastity/activelock.html?lockId=' + lockId;
|
||||
@@ -416,6 +391,13 @@
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
hide('loadingHint');
|
||||
|
||||
// Remove ?fresh=1 from URL so F5 resumes instead of restarting.
|
||||
const cleanParams = new URLSearchParams(location.search);
|
||||
cleanParams.delete('fresh');
|
||||
const cleanSearch = cleanParams.toString();
|
||||
history.replaceState(null, '', location.pathname + (cleanSearch ? '?' + cleanSearch : ''));
|
||||
|
||||
await runGameLoop();
|
||||
}
|
||||
|
||||
@@ -482,26 +464,7 @@
|
||||
|
||||
// ── Benötigt-Checkboxen ───────────────────────────────────────────────────
|
||||
|
||||
const WERKZEUG_LABEL = {
|
||||
MUND: 'Mund', VAGINA: 'Vagina', PENIS: 'Penis',
|
||||
ANUS: 'Anus', UMSCHNALLDILDO: 'Umschnall-Dildo'
|
||||
};
|
||||
|
||||
function renderRequirements(list) {
|
||||
const box = document.getElementById('gameRequirements');
|
||||
if (!list || list.length === 0) { box.style.display = 'none'; box.innerHTML = ''; return; }
|
||||
box.innerHTML = '<div class="game-requirements-label">Benötigt</div>' +
|
||||
list.map(w => {
|
||||
const label = WERKZEUG_LABEL[w] || w;
|
||||
return `<label class="req-check" onclick="this.classList.toggle('done',this.querySelector('input').checked)">
|
||||
<input type="checkbox" onchange="this.closest('.req-check').classList.toggle('done',this.checked)">
|
||||
<span>${label}</span>
|
||||
</label>`;
|
||||
}).join('');
|
||||
box.style.display = 'flex';
|
||||
}
|
||||
|
||||
// ── Game Loop ─────────────────────────────────────────────────────────────
|
||||
// ── Game Loop ─────────────────────────────────────────────────────────────
|
||||
|
||||
function setGameCard(label, text, action, btnLabel) {
|
||||
document.getElementById('gameLabel').textContent = label;
|
||||
@@ -514,20 +477,26 @@
|
||||
}
|
||||
|
||||
async function runGameLoop() {
|
||||
hide('gameCard');
|
||||
hide('finisherBox');
|
||||
hide('tempOpeningBox');
|
||||
clearTimer();
|
||||
|
||||
if (_state.tempOpeningTime) {
|
||||
hide('finisherBox');
|
||||
showTempOpeningDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_state.finisher) {
|
||||
hide('tempOpeningBox');
|
||||
showFinisherUI();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_state.tempOpeningTime) {
|
||||
showTempOpeningDialog();
|
||||
return;
|
||||
}
|
||||
// Normal game states all use gameCard — show it once here so the card
|
||||
// frame stays stable across transitions (queue ↔ active ↔ active-done).
|
||||
show('gameBox');
|
||||
show('gameCard');
|
||||
hide('tempOpeningBox');
|
||||
hide('finisherBox');
|
||||
|
||||
renderLevelBar(_state.level);
|
||||
|
||||
@@ -549,29 +518,25 @@
|
||||
if (_state.lockInQueue) {
|
||||
let sperre;
|
||||
try { sperre = JSON.parse(_state.lockInQueue); } catch { sperre = {}; }
|
||||
setGameCard('🔒 Neue Sperre', sperre.text || sperre.kurzText || '', 'queue-start', '▶ Starten');
|
||||
renderRequirements(null);
|
||||
const btnLabel = sperre.tempUnlockRequired ? '⏱ Weiter zur temporären Öffnung' : '✓ Erledigt';
|
||||
setGameCard('🔒 Neue Sperre', sperre.text || sperre.kurzText || '', 'queue-start', btnLabel);
|
||||
|
||||
} else if (_state.taskInQueue) {
|
||||
let aufgabe;
|
||||
try { aufgabe = JSON.parse(_state.taskInQueue); } catch { aufgabe = {}; }
|
||||
const hasDuration = !!(aufgabe.sekundenVon || aufgabe.sekundenBis);
|
||||
setGameCard('🎯 Neue Aufgabe', aufgabe.text || '', hasDuration ? 'queue-start' : 'queue-done',
|
||||
hasDuration ? '▶ Starten' : '✓ Erledigt');
|
||||
renderRequirements(aufgabe.benoetigtAktiv);
|
||||
|
||||
}
|
||||
show('gameBox');
|
||||
show('gameCard');
|
||||
}
|
||||
|
||||
function showActiveTask(text, endIso) {
|
||||
show('gameBox');
|
||||
show('gameCard');
|
||||
const timerEl = document.getElementById('gameTimer');
|
||||
timerEl.classList.remove('active', 'urgent');
|
||||
timerEl.textContent = '';
|
||||
document.getElementById('gameLabel').textContent = 'Aktive Aufgabe';
|
||||
document.getElementById('gameText').textContent = text;
|
||||
renderRequirements(_state.activeTaskBenoetigtAktiv);
|
||||
|
||||
if (endIso) {
|
||||
const end = new Date(endIso);
|
||||
@@ -590,7 +555,7 @@
|
||||
switch (_gameAction) {
|
||||
case 'queue-start': doQueueStart(); break;
|
||||
case 'queue-done': doQueueDone(); break;
|
||||
case 'active-running': doCancelCountdown(); break;
|
||||
case 'active-running': openConfirmModal('Aufgabe wirklich abbrechen?', () => doCancelCountdown()); break;
|
||||
case 'active-done': doErledigt(); break;
|
||||
}
|
||||
}
|
||||
@@ -603,21 +568,21 @@
|
||||
try { tempUnlockRequired = JSON.parse(_state.lockInQueue).tempUnlockRequired === true; } catch (_) {}
|
||||
}
|
||||
|
||||
const r = await fetch('/lock-game/apply-task', { method: 'POST' });
|
||||
if (!r.ok) { showError('Fehler beim Starten'); return; }
|
||||
|
||||
if (wasLock && tempUnlockRequired) {
|
||||
await fetch('/lock-game/start-temp-opening', { method: 'POST' });
|
||||
// Erst Öffnung starten, Zeitstrafe wird nach der Öffnung angewendet
|
||||
const r = await fetch('/lock-game/start-temp-opening', { method: 'POST' });
|
||||
if (!r.ok) { showError('Fehler beim Starten der Öffnung'); return; }
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
_tempOpeningFromQueue = true;
|
||||
showTempOpeningDialog();
|
||||
} else if (wasLock) {
|
||||
const nextR = await fetch('/lock-game/abandon-task', { method: 'POST' });
|
||||
if (!nextR.ok) { showError('Fehler beim Ziehen'); return; }
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
await runGameLoop();
|
||||
} else {
|
||||
const r = await fetch('/lock-game/apply-task', { method: 'POST' });
|
||||
if (!r.ok) { showError('Fehler beim Starten'); return; }
|
||||
if (wasLock) {
|
||||
const nextR = await fetch('/lock-game/abandon-task', { method: 'POST' });
|
||||
if (!nextR.ok) { showError('Fehler beim Ziehen'); return; }
|
||||
}
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
await runGameLoop();
|
||||
@@ -627,7 +592,13 @@
|
||||
|
||||
async function doQueueDone() {
|
||||
try {
|
||||
await checkAndShowLocks();
|
||||
const lockR = await fetch('/lock-game/check-locks', { method: 'POST' });
|
||||
if (lockR.ok) {
|
||||
const sperren = await lockR.json();
|
||||
for (const sperre of (sperren || [])) {
|
||||
if (sperre.releaseText) await waitForReleaseOk(sperre.releaseText);
|
||||
}
|
||||
}
|
||||
const applyR = await fetch('/lock-game/apply-task', { method: 'POST' });
|
||||
if (!applyR.ok) { showError('Fehler beim Anwenden'); return; }
|
||||
const nextR = await fetch('/lock-game/next-task', { method: 'POST' });
|
||||
@@ -642,9 +613,9 @@
|
||||
clearTimer();
|
||||
const lockR = await fetch('/lock-game/check-locks', { method: 'POST' });
|
||||
if (lockR.ok) {
|
||||
const texts = await lockR.json();
|
||||
for (const text of (texts || [])) {
|
||||
if (text != null && text !== '') await waitForReleaseOk(text);
|
||||
const sperren = await lockR.json();
|
||||
for (const sperre of (sperren || [])) {
|
||||
if (sperre.releaseText) await waitForReleaseOk(sperre.releaseText);
|
||||
}
|
||||
}
|
||||
_gameAction = 'active-done';
|
||||
@@ -655,9 +626,9 @@
|
||||
try {
|
||||
const lockR = await fetch('/lock-game/check-locks', { method: 'POST' });
|
||||
if (lockR.ok) {
|
||||
const texts = await lockR.json();
|
||||
for (const text of (texts || [])) {
|
||||
if (text != null && text !== '') await waitForReleaseOk(text);
|
||||
const sperren = await lockR.json();
|
||||
for (const sperre of (sperren || [])) {
|
||||
if (sperre.releaseText) await waitForReleaseOk(sperre.releaseText);
|
||||
}
|
||||
}
|
||||
const r = await fetch('/lock-game/next-task', { method: 'POST' });
|
||||
@@ -668,64 +639,149 @@
|
||||
} catch (e) { showError(e.message || 'Fehler (Erledigt)'); }
|
||||
}
|
||||
|
||||
async function checkAndShowLocks() {
|
||||
const r = await fetch('/lock-game/check-locks', { method: 'POST' });
|
||||
if (!r.ok) return;
|
||||
const texts = await r.json();
|
||||
const valid = texts ? texts.filter(t => t != null && t !== '') : [];
|
||||
if (valid.length > 0) {
|
||||
const box = document.getElementById('lockMessages');
|
||||
box.innerHTML = valid.map(t => `<p>🔓 ${esc(t)}</p>`).join('');
|
||||
show('lockMessages');
|
||||
await new Promise(res => setTimeout(res, 2000));
|
||||
}
|
||||
}
|
||||
|
||||
function showTempOpeningDialog() {
|
||||
show('gameBox');
|
||||
hide('gameCard');
|
||||
hide('lockReleaseBox');
|
||||
hide('finisherBox');
|
||||
|
||||
document.getElementById('tempOpeningTask').textContent = _state.activeTask || '';
|
||||
const code = _state.tempOpeningCode;
|
||||
if (code) {
|
||||
document.getElementById('tempOpeningCode').textContent = code;
|
||||
show('tempOpeningCodeRow');
|
||||
} else {
|
||||
hide('tempOpeningCodeRow');
|
||||
document.getElementById('tempOpeningCode').textContent = _state.tempOpeningCode || '';
|
||||
|
||||
show('tempPhase1');
|
||||
hide('tempPhase2');
|
||||
|
||||
const timerEl = document.getElementById('tempOpeningTimer');
|
||||
timerEl.textContent = '';
|
||||
timerEl.classList.remove('active', 'urgent');
|
||||
if (_state.tempOpeningTime && _state.tempOpeningDuration) {
|
||||
const endTime = new Date(new Date(_state.tempOpeningTime).getTime() + _state.tempOpeningDuration * 60000);
|
||||
if (endTime > Date.now()) startTempOpeningTimer(endTime, timerEl);
|
||||
}
|
||||
|
||||
show('tempOpeningBox');
|
||||
}
|
||||
|
||||
function startTempOpeningTimer(endDate, el) {
|
||||
if (_tempOpeningTimerInt) { clearInterval(_tempOpeningTimerInt); _tempOpeningTimerInt = null; }
|
||||
el.classList.add('active');
|
||||
function tick() {
|
||||
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) { clearInterval(_tempOpeningTimerInt); _tempOpeningTimerInt = null; }
|
||||
}
|
||||
tick();
|
||||
_tempOpeningTimerInt = setInterval(tick, 1000);
|
||||
}
|
||||
|
||||
async function doEndTempOpening() {
|
||||
try {
|
||||
await fetch('/lock-game/end-temp-opening', { method: 'POST' });
|
||||
await fetch('/lock-game/abandon-task', { method: 'POST' });
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
hide('tempOpeningBox');
|
||||
await runGameLoop();
|
||||
if (_tempOpeningTimerInt) { clearInterval(_tempOpeningTimerInt); _tempOpeningTimerInt = null; }
|
||||
|
||||
// Only apply the queued Zeitstrafe when the opening was explicitly started from one (Path 1).
|
||||
// When triggered by a background lock expiry via checkLocks() (Path 2), lockInQueue may be set
|
||||
// to the *next* randomly-picked item — applying it would incorrectly create another background
|
||||
// lock with tempUnlockRequired and produce an infinite opening loop.
|
||||
const fromQueue = _tempOpeningFromQueue;
|
||||
_tempOpeningFromQueue = false;
|
||||
const hasLockInQueue = fromQueue && !!_state.lockInQueue;
|
||||
if (hasLockInQueue) {
|
||||
const applyR = await fetch('/lock-game/apply-task', { method: 'POST' });
|
||||
if (!applyR.ok) { showError('Fehler beim Anwenden der Zeitstrafe'); return; }
|
||||
}
|
||||
|
||||
const endR = await fetch('/lock-game/end-temp-opening', { method: 'POST' });
|
||||
if (!endR.ok) { showError('Fehler beim Beenden der Öffnung'); return; }
|
||||
const endData = await endR.json();
|
||||
|
||||
const newCode = endData.newUnlockCode;
|
||||
if (newCode) {
|
||||
document.getElementById('tempNewCode').textContent = newCode;
|
||||
document.getElementById('tempScrambleCountdown').style.display = 'none';
|
||||
document.getElementById('tempPhase2Btn').textContent = 'OK';
|
||||
document.getElementById('tempPhase2Btn').onclick = startTempScramble;
|
||||
hide('tempPhase1');
|
||||
show('tempPhase2');
|
||||
} else {
|
||||
await finishTempOpening();
|
||||
}
|
||||
} catch (e) { showError(e.message || 'Fehler beim Abschluss der temporären Öffnung'); }
|
||||
}
|
||||
|
||||
async function finishTempOpening() {
|
||||
hide('tempOpeningBox');
|
||||
await fetch('/lock-game/abandon-task', { method: 'POST' });
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
await runGameLoop();
|
||||
}
|
||||
|
||||
function startTempScramble() {
|
||||
const codeEl = document.getElementById('tempNewCode');
|
||||
const cdEl = document.getElementById('tempScrambleCountdown');
|
||||
const btnEl = document.getElementById('tempPhase2Btn');
|
||||
const len = codeEl.textContent.length;
|
||||
const DURATION = 3 * 60;
|
||||
let remaining = DURATION;
|
||||
let stopped = false;
|
||||
|
||||
function randomCode() {
|
||||
return Array.from({ length: len }, () => Math.floor(Math.random() * 10)).join('');
|
||||
}
|
||||
async function finish() {
|
||||
stopped = true;
|
||||
clearInterval(_tempScrambleTimer); _tempScrambleTimer = null;
|
||||
clearInterval(_tempScrambleCd); _tempScrambleCd = null;
|
||||
await finishTempOpening();
|
||||
}
|
||||
|
||||
cdEl.style.display = '';
|
||||
btnEl.textContent = 'Abbrechen';
|
||||
btnEl.onclick = finish;
|
||||
|
||||
function updateCd() {
|
||||
const m = Math.floor(remaining / 60);
|
||||
const s = remaining % 60;
|
||||
cdEl.textContent = `${m}:${String(s).padStart(2, '0')}`;
|
||||
}
|
||||
updateCd();
|
||||
|
||||
_tempScrambleTimer = setInterval(() => { if (!stopped) codeEl.textContent = randomCode(); }, 1000);
|
||||
_tempScrambleCd = setInterval(() => {
|
||||
if (stopped) return;
|
||||
if (--remaining <= 0) finish();
|
||||
else updateCd();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function showFinisherUI() {
|
||||
show('gameBox');
|
||||
hide('gameCard');
|
||||
hide('lockReleaseBox');
|
||||
hide('tempOpeningBox');
|
||||
|
||||
renderLevelBar(6);
|
||||
|
||||
let finisher = {};
|
||||
try { finisher = JSON.parse(_state.finisher); } catch (_) {}
|
||||
document.getElementById('finisherTitle').textContent = finisher.kurzText || '';
|
||||
document.getElementById('finisherLabel').textContent = finisher.kurzText || '';
|
||||
document.getElementById('finisherText').textContent = finisher.text || '';
|
||||
|
||||
const timerEl = document.getElementById('finisherTimer');
|
||||
const btnEl = document.getElementById('finisherBtn');
|
||||
timerEl.classList.remove('active', 'urgent');
|
||||
timerEl.textContent = '';
|
||||
|
||||
if (_state.finisherStartedAt) {
|
||||
hide('finisherStart');
|
||||
show('finisherRunning');
|
||||
timerEl.classList.add('active');
|
||||
startElapsedTimer(new Date(_state.finisherStartedAt));
|
||||
btnEl.textContent = '✓ Erledigt';
|
||||
btnEl.onclick = doEndFinisher;
|
||||
} else {
|
||||
show('finisherStart');
|
||||
hide('finisherRunning');
|
||||
btnEl.textContent = '▶ Starten';
|
||||
btnEl.onclick = doStartFinisher;
|
||||
}
|
||||
show('finisherBox');
|
||||
}
|
||||
@@ -734,9 +790,12 @@
|
||||
await fetch('/lock-game/start-finisher', { method: 'POST' });
|
||||
const r = await fetch('/lock-game/state');
|
||||
_state = await r.json();
|
||||
hide('finisherStart');
|
||||
show('finisherRunning');
|
||||
const timerEl = document.getElementById('finisherTimer');
|
||||
timerEl.classList.add('active');
|
||||
startElapsedTimer(new Date(_state.finisherStartedAt));
|
||||
const btnEl = document.getElementById('finisherBtn');
|
||||
btnEl.textContent = '✓ Erledigt';
|
||||
btnEl.onclick = doEndFinisher;
|
||||
}
|
||||
|
||||
async function doEndFinisher() {
|
||||
@@ -750,12 +809,14 @@
|
||||
function startElapsedTimer(startDate) {
|
||||
clearTimer();
|
||||
const el = document.getElementById('finisherTimer');
|
||||
_timerInt = setInterval(() => {
|
||||
function tick() {
|
||||
const diff = Math.floor((Date.now() - startDate) / 1000);
|
||||
const m = String(Math.floor(diff / 60)).padStart(2, '0');
|
||||
const s = String(diff % 60).padStart(2, '0');
|
||||
el.textContent = m + ':' + s;
|
||||
}, 1000);
|
||||
}
|
||||
tick();
|
||||
_timerInt = setInterval(tick, 1000);
|
||||
}
|
||||
|
||||
function waitForReleaseOk(text) {
|
||||
@@ -773,8 +834,12 @@
|
||||
// ── Level-Bar ─────────────────────────────────────────────────────────────
|
||||
|
||||
function renderLevelBar(level) {
|
||||
const lvl = Math.min(Math.max(level, 1), 5);
|
||||
document.getElementById('levelImg').src = `/img/lvl${lvl}.png`;
|
||||
const isFinisher = level >= 6;
|
||||
document.getElementById('levelImg').style.display = isFinisher ? 'none' : '';
|
||||
document.getElementById('levelTrophy').style.display = isFinisher ? '' : 'none';
|
||||
if (!isFinisher) {
|
||||
document.getElementById('levelImg').src = `/img/lvl${Math.min(Math.max(level, 1), 5)}.png`;
|
||||
}
|
||||
show('levelDisplay');
|
||||
}
|
||||
|
||||
@@ -787,7 +852,7 @@
|
||||
function startTimer(endDate, el) {
|
||||
el.classList.add('active');
|
||||
clearTimer();
|
||||
_timerInt = setInterval(() => {
|
||||
function tick() {
|
||||
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');
|
||||
@@ -799,7 +864,9 @@
|
||||
_gameAction = 'active-done';
|
||||
document.getElementById('gameBtn').textContent = '✓ Erledigt';
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
tick();
|
||||
_timerInt = setInterval(tick, 1000);
|
||||
}
|
||||
|
||||
function clearTimer() {
|
||||
@@ -818,6 +885,25 @@
|
||||
box.style.display = '';
|
||||
}
|
||||
|
||||
// ── Confirm Modal ─────────────────────────────────────────────────────────
|
||||
|
||||
const _confirmModal = document.getElementById('confirmModal');
|
||||
document.getElementById('confirmModalCancel').addEventListener('click', closeConfirmModal);
|
||||
document.getElementById('confirmModalOk').addEventListener('click', closeConfirmModal);
|
||||
_confirmModal.addEventListener('click', e => { if (e.target === _confirmModal) closeConfirmModal(); });
|
||||
document.addEventListener('keydown', e => { if (e.key === 'Escape' && _confirmModal.classList.contains('open')) closeConfirmModal(); });
|
||||
|
||||
function closeConfirmModal() { _confirmModal.classList.remove('open'); }
|
||||
|
||||
function openConfirmModal(text, onConfirm) {
|
||||
document.getElementById('confirmModalText').textContent = text;
|
||||
const okBtn = document.getElementById('confirmModalOk');
|
||||
const newOk = okBtn.cloneNode(true);
|
||||
okBtn.parentNode.replaceChild(newOk, okBtn);
|
||||
newOk.addEventListener('click', () => { closeConfirmModal(); onConfirm(); });
|
||||
_confirmModal.classList.add('open');
|
||||
}
|
||||
|
||||
boot();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user