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:
@@ -1,13 +1,4 @@
|
||||
Umsetzung des Spiels:
|
||||
Der Lockee hat eine Stunde Zeit das Spiel zu starten, dies geschieht per Knopfdruck
|
||||
Wenn er dies nicht schafft -> bei Keyholder, benachrichtige Keyholder und lass sie/ihn entscheiden, ansonsten freeze wie bei freeze card
|
||||
Übernimm die Logik des Spiels aus dem BDSM Game.
|
||||
Falls eine Zeitstrafe eine temporäre Öffnung vor oder nach der Aufgabe benötigt, öffne das Lock für 5 Minuten. Überzogene Zeit wird addiert und am Ende des Locks gefreezed
|
||||
Selbiges gilt, falls der finisher eine temporäre Öffnung danach erfoldert
|
||||
Benötigt der Finisher eine Öffnung davor, verwende die Logik der Cum Card, und addiere diese Zeit auf die möglicherweise schon vorhandenen Freeze Zeit am Ende des Locks
|
||||
|
||||
|
||||
|
||||
|
||||
wenn ich dates erfasse kann ich diese auch zu einer Verantstaltung machen,
|
||||
hier kann ich die auswählen, zu denen ich "Ich bin dabei" gedrückt habe, das
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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