Weiter am Chastity Ingame gearbeitet
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:
5
bin/main/db/migration/V4__lock_game_session_nullable.sql
Normal file
5
bin/main/db/migration/V4__lock_game_session_nullable.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- session_id war fälschlicherweise PRIMARY KEY der lock_game-Tabelle.
|
||||
-- Korrekter PK ist game_id (gemäß JPA-Entity @Id).
|
||||
ALTER TABLE lock_game DROP PRIMARY KEY;
|
||||
ALTER TABLE lock_game MODIFY COLUMN session_id VARCHAR(255) NULL;
|
||||
ALTER TABLE lock_game ADD PRIMARY KEY (game_id);
|
||||
5
bin/main/db/migration/V5__lock_game_text_columns.sql
Normal file
5
bin/main/db/migration/V5__lock_game_text_columns.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- task_in_queue und lock_in_queue können serialisierte JSON-Objekte speichern,
|
||||
-- die länger als 255 Zeichen sind – auf TEXT erweitern.
|
||||
ALTER TABLE lock_game
|
||||
MODIFY COLUMN task_in_queue TEXT NULL,
|
||||
MODIFY COLUMN lock_in_queue TEXT NULL;
|
||||
5
bin/main/db/migration/V6__game_active_default.sql
Normal file
5
bin/main/db/migration/V6__game_active_default.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- game_active wurde ohne DEFAULT angelegt; bestehende Zeilen haben NULL –
|
||||
-- auf false setzen und NOT NULL + DEFAULT ergänzen.
|
||||
UPDATE active_lock SET game_active = 0 WHERE game_active IS NULL;
|
||||
ALTER TABLE active_lock
|
||||
MODIFY COLUMN game_active TINYINT(1) NOT NULL DEFAULT 0;
|
||||
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.
18
bin/main/static/css/time-picker.css
Normal file
18
bin/main/static/css/time-picker.css
Normal file
@@ -0,0 +1,18 @@
|
||||
.time-picker { display:flex; align-items:center; gap:0.35rem; flex-wrap:wrap; }
|
||||
.tp-seg { display:flex; flex-direction:column; align-items:center; gap:0; }
|
||||
.tp-seg-row { display:flex; align-items:center; gap:0.2rem; }
|
||||
.tp-seg button {
|
||||
width:24px !important; height:24px !important;
|
||||
background:var(--color-card); border:1px solid var(--color-muted);
|
||||
border-radius:4px; cursor:pointer; font-size:0.9rem; font-weight:700; color:var(--color-text);
|
||||
display:flex; align-items:center; justify-content:center; padding:0; flex-shrink:0;
|
||||
}
|
||||
.tp-seg button:hover { background:var(--color-primary); color:#fff; border-color:var(--color-primary); }
|
||||
.tp-seg .tp-seg-row input {
|
||||
width:28px !important; padding:0.15rem 0 !important;
|
||||
text-align:center; background:var(--color-card);
|
||||
border:1px solid var(--color-muted); border-radius:4px;
|
||||
color:var(--color-text); font-size:0.9rem; font-weight:600;
|
||||
font-family:monospace; box-sizing:border-box;
|
||||
}
|
||||
.tp-seg .tp-label { font-size:0.58rem; color:var(--color-muted); text-transform:uppercase; letter-spacing:0.03em; line-height:1.2; }
|
||||
@@ -532,6 +532,19 @@
|
||||
<label class="werkzeug-check"><span>Anus</span><input type="checkbox" value="ANUS"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="iTempUnlockRow">
|
||||
<label>Temporäre Öffnungen</label>
|
||||
<div style="display:flex; flex-direction:column; gap:0.5rem; margin-top:0.5rem;">
|
||||
<label style="display:flex; align-items:center; gap:0.6rem; font-size:0.85rem; cursor:pointer;">
|
||||
<input type="checkbox" id="iTempUnlockBefore" style="accent-color:var(--color-primary); width:1rem; height:1rem;">
|
||||
Temporäre Öffnung <em>vor</em> der Zeitstrafe erforderlich
|
||||
</label>
|
||||
<label style="display:flex; align-items:center; gap:0.6rem; font-size:0.85rem; cursor:pointer;">
|
||||
<input type="checkbox" id="iTempUnlockAfter" style="accent-color:var(--color-primary); width:1rem; height:1rem;">
|
||||
Temporäre Öffnung <em>nach</em> der Zeitstrafe erforderlich
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="iReleaseTextRow">
|
||||
<label for="iReleaseText">Text bei Aufhebung</label>
|
||||
<textarea id="iReleaseText" rows="2" maxlength="2000" placeholder="Text der angezeigt wird, wenn die Sperre endet…"></textarea>
|
||||
@@ -1411,13 +1424,14 @@
|
||||
document.getElementById('iWerkzeugFinisherPassivRow').style.display = (isFinisher && !isChastity) ? 'block' : 'none';
|
||||
|
||||
// Zeitstrafe rows
|
||||
document.getElementById('iMinutenRow').style.display = isZeit ? 'block' : 'none';
|
||||
document.getElementById('iSperreFuerRow').style.display = isZeit ? 'block' : 'none';
|
||||
document.getElementById('iMinutenRow').style.display = isZeit ? 'block' : 'none';
|
||||
document.getElementById('iSperreFuerRow').style.display = isZeit ? 'block' : 'none';
|
||||
['VAGINA', 'PENIS'].forEach(v => {
|
||||
const lbl = document.querySelector(`#iSperreFuer input[value="${v}"]`)?.closest('label');
|
||||
if (lbl) lbl.style.display = isChastity ? 'none' : '';
|
||||
});
|
||||
document.getElementById('iReleaseTextRow').style.display = isZeit ? 'block' : 'none';
|
||||
document.getElementById('iTempUnlockRow').style.display = (isZeit && isChastity) ? 'block' : 'none';
|
||||
document.getElementById('iReleaseTextRow').style.display = isZeit ? 'block' : 'none';
|
||||
}
|
||||
|
||||
function _resetItemFields() {
|
||||
@@ -1435,6 +1449,8 @@
|
||||
document.querySelectorAll('#iWerkzeugFinisherPassiv input').forEach(cb => cb.checked = false);
|
||||
document.querySelectorAll('#iSperreFuer input').forEach(cb => cb.checked = false);
|
||||
document.querySelectorAll('#iGeschlecht input').forEach(rb => rb.checked = false);
|
||||
document.getElementById('iTempUnlockBefore').checked = false;
|
||||
document.getElementById('iTempUnlockAfter').checked = false;
|
||||
_selectedToys = [];
|
||||
renderSelectedToys();
|
||||
document.getElementById('itemModalError').style.display = 'none';
|
||||
@@ -1485,7 +1501,9 @@
|
||||
document.getElementById('iReleaseText').value = d.releaseText || '';
|
||||
(d.sperreFuer || []).forEach(w => { const cb = document.querySelector(`#iSperreFuer input[value="${w}"]`); if (cb) cb.checked = true; });
|
||||
if (_isChastityMode) {
|
||||
document.getElementById('iLevel').value = d.level != null ? d.level : '';
|
||||
document.getElementById('iLevel').value = d.level != null ? d.level : '';
|
||||
document.getElementById('iTempUnlockBefore').checked = d.tempUnlockBeforeRequired === true;
|
||||
document.getElementById('iTempUnlockAfter').checked = d.tempUnlockAfterRequired === true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1680,6 +1698,8 @@
|
||||
releaseText: document.getElementById('iReleaseText').value.trim() || null,
|
||||
sperreFuer,
|
||||
level: zeitLevel,
|
||||
tempUnlockBeforeRequired: _isChastityMode ? document.getElementById('iTempUnlockBefore').checked : null,
|
||||
tempUnlockAfterRequired: _isChastityMode ? document.getElementById('iTempUnlockAfter').checked : null,
|
||||
benoetigteToys: _selectedToys.map(t => ({ toyId: t.toyId }))
|
||||
};
|
||||
url = isEdit ? `/sperre/${currentItemEditId}` : '/sperre'; // BDSM-only
|
||||
|
||||
@@ -658,6 +658,23 @@
|
||||
<button class="btn-hygiene" id="hygieneBtn" style="display:none;" onclick="openHygieneModal()">🚿 Hygiene-Öffnung</button>
|
||||
</div>
|
||||
|
||||
<!-- Geparkte Spiel-Karte -->
|
||||
<div id="gameCardPanel" class="hygiene-panel" style="display:none;border-color:var(--color-primary);">
|
||||
<div>
|
||||
<div class="hygiene-panel-title" style="color:var(--color-primary);">🎮 Spiel-Karte gezogen</div>
|
||||
<div class="hygiene-countdown" id="gameCardCountdown">–</div>
|
||||
</div>
|
||||
<button class="btn-hygiene" onclick="startParkedGame()">▶ Spiel starten</button>
|
||||
</div>
|
||||
|
||||
<div id="gameActivePanel" class="hygiene-panel" style="display:none;border-color:var(--color-primary);">
|
||||
<div>
|
||||
<div class="hygiene-panel-title" style="color:var(--color-primary);">🎮 Minispiel läuft</div>
|
||||
<div style="font-size:0.82rem;color:var(--color-muted);">Das Lock ist gesperrt, bis das Spiel abgeschlossen ist.</div>
|
||||
</div>
|
||||
<button class="btn-hygiene" onclick="goToActiveGame()">▶ Zum Spiel</button>
|
||||
</div>
|
||||
|
||||
<!-- Speed-Effekt-Panel -->
|
||||
<div id="speedPanel" style="display:none;background:var(--color-card);border:1px solid var(--color-secondary);border-radius:12px;padding:0.85rem 1.1rem;gap:0.35rem;">
|
||||
<div style="font-size:0.75rem;text-transform:uppercase;letter-spacing:0.06em;color:var(--color-muted);" id="speedPanelTitle">Slow Motion aktiv</div>
|
||||
@@ -909,6 +926,8 @@
|
||||
renderKeyholderBar(lock);
|
||||
renderAssignedTasks(lock);
|
||||
renderNextCardPanel(lock);
|
||||
renderGameCardPanel(lock);
|
||||
renderGameActivePanel(lock);
|
||||
renderHygienePanel(lock);
|
||||
renderSpeedPanel(lock);
|
||||
renderVerificationPanel(lock);
|
||||
@@ -1170,6 +1189,9 @@
|
||||
const panel = document.getElementById('nextcardPanel');
|
||||
const cardsDiv = document.getElementById('nextcardCards');
|
||||
const overlay = document.getElementById('nextcardOverlay');
|
||||
|
||||
if (lock.gameActive) { panel.style.display = 'none'; return; }
|
||||
|
||||
panel.style.display = '';
|
||||
panel.classList.remove('drawable');
|
||||
|
||||
@@ -1286,6 +1308,45 @@
|
||||
tickInterval = setInterval(tick, 1000);
|
||||
}
|
||||
|
||||
let gameCardPanelTick = null;
|
||||
|
||||
function renderGameCardPanel(lock) {
|
||||
if (gameCardPanelTick) { clearInterval(gameCardPanelTick); gameCardPanelTick = null; }
|
||||
const panel = document.getElementById('gameCardPanel');
|
||||
if (!lock.gameCardParkedAt || !lock.gameCardDeadline) {
|
||||
panel.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
panel.style.display = '';
|
||||
const deadline = new Date(lock.gameCardDeadline);
|
||||
const cdEl = document.getElementById('gameCardCountdown');
|
||||
function tick() {
|
||||
const diff = deadline - Date.now();
|
||||
if (diff <= 0) { panel.style.display = 'none'; clearInterval(gameCardPanelTick); gameCardPanelTick = null; return; }
|
||||
cdEl.textContent = fmtCountdown(diff);
|
||||
}
|
||||
tick();
|
||||
gameCardPanelTick = setInterval(tick, 1000);
|
||||
}
|
||||
|
||||
async function startParkedGame() {
|
||||
const res = await fetch('/keyholder/cardlock/' + lockId + '/game/start', { method: 'POST' });
|
||||
if (!res.ok) { alert('Fehler beim Starten des Spiels.'); return; }
|
||||
const data = await res.json();
|
||||
const gameSetId = data.gameSetId;
|
||||
const url = '/games/chastity/taskgame.html?lockId=' + lockId
|
||||
+ (gameSetId ? '&gameSetId=' + gameSetId : '');
|
||||
window.location.href = url;
|
||||
}
|
||||
|
||||
function renderGameActivePanel(lock) {
|
||||
document.getElementById('gameActivePanel').style.display = lock.gameActive ? '' : 'none';
|
||||
}
|
||||
|
||||
function goToActiveGame() {
|
||||
window.location.href = '/games/chastity/taskgame.html?lockId=' + lockId;
|
||||
}
|
||||
|
||||
function renderHygienePanel(lock) {
|
||||
if (hygienePanelTick) { clearInterval(hygienePanelTick); hygienePanelTick = null; }
|
||||
if (!lock.hygieneEnabled) return;
|
||||
@@ -1714,10 +1775,8 @@
|
||||
|
||||
if (dto.card === 'GAME_CARD') {
|
||||
const btn = document.getElementById('btnDrawOk');
|
||||
btn.textContent = '▶ Spiel starten';
|
||||
btn.onclick = function() {
|
||||
window.location.href = '/games/chastity/taskgame.html?lockId=' + lockId;
|
||||
};
|
||||
btn.textContent = 'OK';
|
||||
btn.onclick = closeDrawModal;
|
||||
}
|
||||
}, 700);
|
||||
}, 1000);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<title>Keyholder – xXx Sphere</title>
|
||||
<link rel="stylesheet" href="/css/variables.css">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/time-picker.css">
|
||||
<style>
|
||||
.lock-list { display:flex; flex-direction:column; gap:0.5rem; margin-top:0.5rem; }
|
||||
|
||||
@@ -80,27 +81,6 @@
|
||||
.violation-item:last-child { border-bottom:none; }
|
||||
|
||||
/* Zeitpicker (für Freeze-Dialog) */
|
||||
.time-picker { display:flex; align-items:center; gap:0.5rem; }
|
||||
.tp-seg { display:flex; flex-direction:column; align-items:center; gap:0.2rem; }
|
||||
.tp-seg-row { display:flex; align-items:center; gap:0.25rem; }
|
||||
.tp-seg button {
|
||||
width:26px; height:26px;
|
||||
background:var(--color-secondary); border:1px solid var(--color-muted);
|
||||
border-radius:5px; cursor:pointer; font-size:1rem; font-weight:700;
|
||||
color:var(--color-text); display:flex; align-items:center; justify-content:center;
|
||||
padding:0; flex-shrink:0;
|
||||
}
|
||||
.tp-seg button:hover { background:var(--color-primary); color:#fff; border-color:var(--color-primary); }
|
||||
.tp-seg input {
|
||||
width:30px; text-align:center;
|
||||
background:var(--color-secondary); border:1px solid var(--color-muted);
|
||||
border-radius:4px; color:var(--color-text);
|
||||
font-size:0.95rem; font-weight:600; font-family:monospace;
|
||||
padding:0.18rem 0; box-sizing:border-box;
|
||||
}
|
||||
.tp-seg .tp-label { font-size:0.65rem; color:var(--color-muted); text-transform:uppercase; letter-spacing:0.04em; }
|
||||
.tp-colon { font-size:1.1rem; font-weight:700; color:var(--color-muted); margin-bottom:1rem; }
|
||||
|
||||
/* ── Tab-Navigation ── */
|
||||
.kh-tabs { display:flex; gap:0; border-bottom:2px solid var(--color-secondary); margin-bottom:1.25rem; }
|
||||
.kh-tab {
|
||||
@@ -311,32 +291,9 @@
|
||||
<div style="font-size:0.72rem;font-weight:700;color:var(--color-primary);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:0.4rem;">Annahme-Frist</div>
|
||||
|
||||
<div class="time-picker">
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="atTpChange(-1,'d')">−</button>
|
||||
<input type="text" id="at_d" value="0" readonly>
|
||||
<button type="button" onclick="atTpChange(1,'d')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Tage</span>
|
||||
</div>
|
||||
<span class="tp-colon">:</span>
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="atTpChange(-1,'h')">−</button>
|
||||
<input type="text" id="at_h" value="01" readonly>
|
||||
<button type="button" onclick="atTpChange(1,'h')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Stunden</span>
|
||||
</div>
|
||||
<span class="tp-colon">:</span>
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="atTpChange(-1,'m')">−</button>
|
||||
<input type="text" id="at_m" value="00" readonly>
|
||||
<button type="button" onclick="atTpChange(1,'m')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Minuten</span>
|
||||
</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('at',-1,'d')">−</button><input type="text" id="at_d" value="0" readonly><button type="button" onclick="tpChange('at',1,'d')">+</button></div><span class="tp-label">Tage</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('at',-1,'h')">−</button><input type="text" id="at_h" value="01" readonly><button type="button" onclick="tpChange('at',1,'h')">+</button></div><span class="tp-label">Stunden</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('at',-1,'m')">−</button><input type="text" id="at_m" value="00" readonly><button type="button" onclick="tpChange('at',1,'m')">+</button></div><span class="tp-label">Minuten</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -350,32 +307,9 @@
|
||||
</label>
|
||||
<div id="atPenaltyFreezeFields" style="padding-left:1.6rem;opacity:0.4;pointer-events:none;transition:opacity 0.15s;">
|
||||
<div class="time-picker">
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="atFreezeTpChange(-1,'d')">−</button>
|
||||
<input type="text" id="atf_d" value="0" readonly>
|
||||
<button type="button" onclick="atFreezeTpChange(1,'d')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Tage</span>
|
||||
</div>
|
||||
<span class="tp-colon">:</span>
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="atFreezeTpChange(-1,'h')">−</button>
|
||||
<input type="text" id="atf_h" value="04" readonly>
|
||||
<button type="button" onclick="atFreezeTpChange(1,'h')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Stunden</span>
|
||||
</div>
|
||||
<span class="tp-colon">:</span>
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="atFreezeTpChange(-1,'m')">−</button>
|
||||
<input type="text" id="atf_m" value="00" readonly>
|
||||
<button type="button" onclick="atFreezeTpChange(1,'m')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Minuten</span>
|
||||
</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('atf',-1,'d')">−</button><input type="text" id="atf_d" value="0" readonly><button type="button" onclick="tpChange('atf',1,'d')">+</button></div><span class="tp-label">Tage</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('atf',-1,'h')">−</button><input type="text" id="atf_h" value="04" readonly><button type="button" onclick="tpChange('atf',1,'h')">+</button></div><span class="tp-label">Stunden</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('atf',-1,'m')">−</button><input type="text" id="atf_m" value="00" readonly><button type="button" onclick="tpChange('atf',1,'m')">+</button></div><span class="tp-label">Minuten</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<label style="display:flex;align-items:center;gap:0.6rem;font-size:0.88rem;cursor:pointer;">
|
||||
@@ -409,32 +343,9 @@
|
||||
<strong style="color:var(--color-text);">Hinweis:</strong> Während des Einfrierens können keine weiteren Karten gezogen werden.
|
||||
</p>
|
||||
<div class="time-picker">
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="freezeTpChange(-1,'d')">−</button>
|
||||
<input type="text" id="freeze_d" value="0" readonly>
|
||||
<button type="button" onclick="freezeTpChange(1,'d')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Tage</span>
|
||||
</div>
|
||||
<span class="tp-colon">:</span>
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="freezeTpChange(-1,'h')">−</button>
|
||||
<input type="text" id="freeze_h" value="04" readonly>
|
||||
<button type="button" onclick="freezeTpChange(1,'h')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Stunden</span>
|
||||
</div>
|
||||
<span class="tp-colon">:</span>
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="freezeTpChange(-1,'m')">−</button>
|
||||
<input type="text" id="freeze_m" value="00" readonly>
|
||||
<button type="button" onclick="freezeTpChange(1,'m')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Minuten</span>
|
||||
</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('freeze',-1,'d')">−</button><input type="text" id="freeze_d" value="0" readonly><button type="button" onclick="tpChange('freeze',1,'d')">+</button></div><span class="tp-label">Tage</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('freeze',-1,'h')">−</button><input type="text" id="freeze_h" value="04" readonly><button type="button" onclick="tpChange('freeze',1,'h')">+</button></div><span class="tp-label">Stunden</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('freeze',-1,'m')">−</button><input type="text" id="freeze_m" value="00" readonly><button type="button" onclick="tpChange('freeze',1,'m')">+</button></div><span class="tp-label">Minuten</span></div>
|
||||
</div>
|
||||
<div id="freezeModalError" style="display:none;font-size:0.85rem;color:#e74c3c;"></div>
|
||||
<div style="display:flex;gap:0.6rem;justify-content:flex-end;">
|
||||
@@ -462,8 +373,9 @@
|
||||
<script src="/js/card-defs.js"></script>
|
||||
<script src="/js/card-display.js"></script>
|
||||
<script src="/js/icons.js"></script>
|
||||
<script src="/js/nav.js"></script>
|
||||
<script src="/js/nav.js"></script>
|
||||
<script src="/js/social-sidebar.js"></script>
|
||||
<script src="/js/time-picker.js"></script>
|
||||
<script>
|
||||
function esc(s) { const d = document.createElement('div'); d.textContent = s ?? ''; return d.innerHTML; }
|
||||
|
||||
@@ -1095,41 +1007,6 @@
|
||||
let assignTaskLockId = null;
|
||||
let assignTaskSelectedIdx = null;
|
||||
|
||||
function atTpChange(delta, seg) {
|
||||
let d = parseInt(document.getElementById('at_d').value) || 0;
|
||||
let h = parseInt(document.getElementById('at_h').value) || 0;
|
||||
let m = parseInt(document.getElementById('at_m').value) || 0;
|
||||
if (seg === 'm') m += delta; else if (seg === 'h') h += delta; else d += delta;
|
||||
if (m >= 60) { h += Math.floor(m/60); m = m%60; }
|
||||
if (m < 0) { const b = Math.ceil(-m/60); h -= b; m += b*60; }
|
||||
if (h >= 24) { d += Math.floor(h/24); h = h%24; }
|
||||
if (h < 0) { const b = Math.ceil(-h/24); d -= b; h += b*24; }
|
||||
if (d < 0) d = 0;
|
||||
document.getElementById('at_d').value = d;
|
||||
document.getElementById('at_h').value = String(h).padStart(2,'0');
|
||||
document.getElementById('at_m').value = String(m).padStart(2,'0');
|
||||
}
|
||||
|
||||
function atFreezeTpChange(delta, seg) {
|
||||
let d = parseInt(document.getElementById('atf_d').value) || 0;
|
||||
let h = parseInt(document.getElementById('atf_h').value) || 0;
|
||||
let m = parseInt(document.getElementById('atf_m').value) || 0;
|
||||
if (seg === 'm') m += delta; else if (seg === 'h') h += delta; else d += delta;
|
||||
if (m >= 60) { h += Math.floor(m/60); m = m%60; }
|
||||
if (m < 0) { const b = Math.ceil(-m/60); h -= b; m += b*60; }
|
||||
if (h >= 24) { d += Math.floor(h/24); h = h%24; }
|
||||
if (h < 0) { const b = Math.ceil(-h/24); d -= b; h += b*24; }
|
||||
if (d < 0) d = 0;
|
||||
document.getElementById('atf_d').value = d;
|
||||
document.getElementById('atf_h').value = String(h).padStart(2,'0');
|
||||
document.getElementById('atf_m').value = String(m).padStart(2,'0');
|
||||
}
|
||||
|
||||
function atTpToMinutes(prefix) {
|
||||
return (parseInt(document.getElementById(prefix+'_d').value)||0) * 24*60
|
||||
+ (parseInt(document.getElementById(prefix+'_h').value)||0) * 60
|
||||
+ (parseInt(document.getElementById(prefix+'_m').value)||0);
|
||||
}
|
||||
|
||||
function atRedChange(delta) {
|
||||
const inp = document.getElementById('atRedCount');
|
||||
@@ -1273,7 +1150,7 @@
|
||||
errEl.style.display = '';
|
||||
return;
|
||||
}
|
||||
const deadlineMinutes = atTpToMinutes('at');
|
||||
const deadlineMinutes = tpToMinutes('at');
|
||||
if (deadlineMinutes < 1) {
|
||||
errEl.textContent = 'Bitte eine Annahme-Frist angeben.';
|
||||
errEl.style.display = '';
|
||||
@@ -1286,7 +1163,7 @@
|
||||
errEl.style.display = '';
|
||||
return;
|
||||
}
|
||||
const penaltyFreezeMinutes = freezeEnabled ? atTpToMinutes('atf') : null;
|
||||
const penaltyFreezeMinutes = freezeEnabled ? tpToMinutes('atf') : null;
|
||||
const penaltyRedCards = redEnabled ? parseInt(document.getElementById('atRedCount').value) || 1 : null;
|
||||
|
||||
try {
|
||||
@@ -1363,31 +1240,6 @@
|
||||
let freezeTargetLockId = null;
|
||||
let unfreezeTargetLockId = null;
|
||||
|
||||
// Zeitpicker-Logik (Dauer)
|
||||
function freezeTpChange(delta, seg) {
|
||||
let d = parseInt(document.getElementById('freeze_d').value) || 0;
|
||||
let h = parseInt(document.getElementById('freeze_h').value) || 0;
|
||||
let m = parseInt(document.getElementById('freeze_m').value) || 0;
|
||||
if (seg === 'm') m += delta;
|
||||
else if (seg === 'h') h += delta;
|
||||
else if (seg === 'd') d += delta;
|
||||
if (m >= 60) { h += Math.floor(m / 60); m = m % 60; }
|
||||
if (m < 0) { const b = Math.ceil(-m / 60); h -= b; m += b * 60; }
|
||||
if (h >= 24) { d += Math.floor(h / 24); h = h % 24; }
|
||||
if (h < 0) { const b = Math.ceil(-h / 24); d -= b; h += b * 24; }
|
||||
if (d < 0) d = 0;
|
||||
document.getElementById('freeze_d').value = d;
|
||||
document.getElementById('freeze_h').value = String(h).padStart(2, '0');
|
||||
document.getElementById('freeze_m').value = String(m).padStart(2, '0');
|
||||
}
|
||||
|
||||
function freezeTpToMinutes() {
|
||||
const d = parseInt(document.getElementById('freeze_d').value) || 0;
|
||||
const h = parseInt(document.getElementById('freeze_h').value) || 0;
|
||||
const m = parseInt(document.getElementById('freeze_m').value) || 0;
|
||||
return d * 24 * 60 + h * 60 + m;
|
||||
}
|
||||
|
||||
function openFreezeModal(lockId) {
|
||||
freezeTargetLockId = lockId;
|
||||
// Default: 4h
|
||||
@@ -1405,7 +1257,7 @@
|
||||
|
||||
async function submitFreeze() {
|
||||
const lockId = freezeTargetLockId;
|
||||
const minutes = freezeTpToMinutes();
|
||||
const minutes = tpToMinutes('freeze');
|
||||
const errEl = document.getElementById('freezeModalError');
|
||||
if (minutes < 1) {
|
||||
errEl.textContent = 'Bitte eine Dauer von mindestens 1 Minute angeben.';
|
||||
@@ -1485,26 +1337,6 @@
|
||||
let _pendingTaskIndex = null;
|
||||
let _pendingLockId = null;
|
||||
|
||||
function ctpFreezeTpChange(delta, seg) {
|
||||
let d = parseInt(document.getElementById('ctpf_d').value) || 0;
|
||||
let h = parseInt(document.getElementById('ctpf_h').value) || 0;
|
||||
let m = parseInt(document.getElementById('ctpf_m').value) || 0;
|
||||
if (seg === 'm') m += delta; else if (seg === 'h') h += delta; else d += delta;
|
||||
if (m >= 60) { h += Math.floor(m/60); m = m%60; }
|
||||
if (m < 0) { const b = Math.ceil(-m/60); h -= b; m += b*60; }
|
||||
if (h >= 24) { d += Math.floor(h/24); h = h%24; }
|
||||
if (h < 0) { const b = Math.ceil(-h/24); d -= b; h += b*24; }
|
||||
if (d < 0) d = 0;
|
||||
document.getElementById('ctpf_d').value = d;
|
||||
document.getElementById('ctpf_h').value = String(h).padStart(2,'0');
|
||||
document.getElementById('ctpf_m').value = String(m).padStart(2,'0');
|
||||
}
|
||||
|
||||
function ctpFreezeToMinutes() {
|
||||
return (parseInt(document.getElementById('ctpf_d').value)||0) * 24*60
|
||||
+ (parseInt(document.getElementById('ctpf_h').value)||0) * 60
|
||||
+ (parseInt(document.getElementById('ctpf_m').value)||0);
|
||||
}
|
||||
|
||||
function ctpRedChange(delta) {
|
||||
const inp = document.getElementById('ctpRedCount');
|
||||
@@ -1566,7 +1398,7 @@
|
||||
}
|
||||
document.getElementById('ctpPenaltyError').style.display = 'none';
|
||||
|
||||
const freezeVal = freezeEnabled ? (ctpFreezeToMinutes() || null) : null;
|
||||
const freezeVal = freezeEnabled ? (tpToMinutes('ctpf') || null) : null;
|
||||
const redVal = redEnabled ? (parseInt(document.getElementById('ctpRedCount').value) || null) : null;
|
||||
try {
|
||||
const res = await fetch(
|
||||
@@ -1865,32 +1697,9 @@
|
||||
</label>
|
||||
<div id="ctpPenaltyFreezeFields" style="padding-left:1.6rem;opacity:0.4;pointer-events:none;transition:opacity 0.15s;">
|
||||
<div class="time-picker">
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="ctpFreezeTpChange(-1,'d')">−</button>
|
||||
<input type="text" id="ctpf_d" value="0" readonly>
|
||||
<button type="button" onclick="ctpFreezeTpChange(1,'d')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Tage</span>
|
||||
</div>
|
||||
<span class="tp-colon">:</span>
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="ctpFreezeTpChange(-1,'h')">−</button>
|
||||
<input type="text" id="ctpf_h" value="04" readonly>
|
||||
<button type="button" onclick="ctpFreezeTpChange(1,'h')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Stunden</span>
|
||||
</div>
|
||||
<span class="tp-colon">:</span>
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="ctpFreezeTpChange(-1,'m')">−</button>
|
||||
<input type="text" id="ctpf_m" value="00" readonly>
|
||||
<button type="button" onclick="ctpFreezeTpChange(1,'m')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Minuten</span>
|
||||
</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('ctpf',-1,'d')">−</button><input type="text" id="ctpf_d" value="0" readonly><button type="button" onclick="tpChange('ctpf',1,'d')">+</button></div><span class="tp-label">Tage</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('ctpf',-1,'h')">−</button><input type="text" id="ctpf_h" value="04" readonly><button type="button" onclick="tpChange('ctpf',1,'h')">+</button></div><span class="tp-label">Stunden</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('ctpf',-1,'m')">−</button><input type="text" id="ctpf_m" value="00" readonly><button type="button" onclick="tpChange('ctpf',1,'m')">+</button></div><span class="tp-label">Minuten</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<label style="display:flex;align-items:center;gap:0.6rem;font-size:0.88rem;cursor:pointer;">
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<title>Meine Vorlagen – xXx Sphere</title>
|
||||
<link rel="stylesheet" href="/css/variables.css">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/time-picker.css">
|
||||
<style>
|
||||
|
||||
/* ── Liste ── */
|
||||
@@ -133,26 +134,6 @@
|
||||
}
|
||||
.stepper input[type="text"]:focus { outline:none; background:rgba(255,255,255,0.08); }
|
||||
|
||||
/* ── Zeitpicker ── */
|
||||
.time-picker { display:flex; align-items:center; gap:0.4rem; flex-wrap:wrap; }
|
||||
.tp-seg { display:flex; flex-direction:column; align-items:center; gap:0.15rem; }
|
||||
.tp-seg-row { display:flex; align-items:center; gap:0.2rem; }
|
||||
.tp-seg button {
|
||||
width:24px; height:24px; background:var(--color-card);
|
||||
border:1px solid var(--color-muted); border-radius:4px;
|
||||
cursor:pointer; font-size:0.9rem; font-weight:700; color:var(--color-text);
|
||||
display:flex; align-items:center; justify-content:center; padding:0; flex-shrink:0;
|
||||
}
|
||||
.tp-seg button:hover { background:var(--color-primary); color:#fff; border-color:var(--color-primary); }
|
||||
.tp-seg input {
|
||||
width:28px; text-align:center; background:var(--color-card);
|
||||
border:1px solid var(--color-muted); border-radius:4px;
|
||||
color:var(--color-text); font-size:0.9rem; font-weight:600;
|
||||
font-family:monospace; padding:0.15rem 0; box-sizing:border-box;
|
||||
}
|
||||
.tp-seg .tp-label { font-size:0.62rem; color:var(--color-muted); text-transform:uppercase; letter-spacing:0.04em; }
|
||||
.tp-colon { font-size:1rem; font-weight:700; color:var(--color-muted); margin-bottom:0.9rem; }
|
||||
|
||||
/* ── Aufgaben ── */
|
||||
.task-list { display:flex; flex-direction:column; gap:0.5rem; margin-bottom:0.6rem; }
|
||||
.task-item {
|
||||
@@ -260,6 +241,33 @@
|
||||
.radio-group label { display:flex; align-items:center; gap:0.5rem; cursor:pointer; font-size:0.9rem; margin:0; color:var(--color-text); }
|
||||
.radio-group input[type="radio"] { width:auto; padding:0; margin:0; }
|
||||
|
||||
/* ── Spiel-Set Suche ── */
|
||||
.gs-dropdown {
|
||||
display:none; position:absolute; top:100%; left:0; right:0; z-index:200;
|
||||
background:var(--color-card); border:1px solid var(--color-secondary);
|
||||
border-radius:6px; max-height:200px; overflow-y:auto;
|
||||
box-shadow:0 4px 14px rgba(0,0,0,0.35); margin-top:2px;
|
||||
}
|
||||
.gs-dropdown-item {
|
||||
padding:0.55rem 0.9rem; cursor:pointer; font-size:0.9rem;
|
||||
border-bottom:1px solid rgba(255,255,255,0.05);
|
||||
}
|
||||
.gs-dropdown-item:last-child { border-bottom:none; }
|
||||
.gs-dropdown-item:hover { background:rgba(255,255,255,0.07); }
|
||||
.gs-item-name { font-weight:600; color:var(--color-text); }
|
||||
.gs-item-desc { font-size:0.78rem; color:var(--color-muted); margin-top:0.1rem; }
|
||||
.gs-selected {
|
||||
display:flex; align-items:center; gap:0.6rem;
|
||||
background:rgba(255,255,255,0.05); border:1px solid var(--color-secondary);
|
||||
border-radius:6px; padding:0.45rem 0.75rem; margin-top:0.35rem;
|
||||
font-size:0.88rem; color:var(--color-text);
|
||||
}
|
||||
.gs-selected button {
|
||||
background:none; border:none; color:var(--color-muted);
|
||||
cursor:pointer; padding:0; margin:0; font-size:1rem; width:auto; line-height:1;
|
||||
}
|
||||
.gs-selected button:hover { color:#e74c3c; background:none; }
|
||||
|
||||
/* ── Simulation ── */
|
||||
.sim-bar-track { background:var(--color-secondary); border-radius:6px; height:8px; overflow:hidden; margin:0.5rem 0 0.25rem; }
|
||||
.sim-bar-fill { height:100%; background:var(--color-primary); border-radius:6px; transition:width 0.1s linear; width:0%; }
|
||||
@@ -353,32 +361,9 @@
|
||||
<div class="form-row" style="margin-top:0.75rem;">
|
||||
<label>Karte ziehen alle</label>
|
||||
<div class="time-picker">
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="tpChange('pe',-1,'d')">−</button>
|
||||
<input type="text" id="pe_d" value="0" readonly>
|
||||
<button type="button" onclick="tpChange('pe',1,'d')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Tage</span>
|
||||
</div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="tpChange('pe',-1,'h')">−</button>
|
||||
<input type="text" id="pe_h" value="01" readonly>
|
||||
<button type="button" onclick="tpChange('pe',1,'h')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Std</span>
|
||||
</div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="tpChange('pe',-1,'m')">−</button>
|
||||
<input type="text" id="pe_m" value="00" readonly>
|
||||
<button type="button" onclick="tpChange('pe',1,'m')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Min</span>
|
||||
</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('pe',-1,'d')">−</button><input type="text" id="pe_d" value="0" readonly><button type="button" onclick="tpChange('pe',1,'d')">+</button></div><span class="tp-label">Tage</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('pe',-1,'h')">−</button><input type="text" id="pe_h" value="01" readonly><button type="button" onclick="tpChange('pe',1,'h')">+</button></div><span class="tp-label">Stunden</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('pe',-1,'m')">−</button><input type="text" id="pe_m" value="00" readonly><button type="button" onclick="tpChange('pe',1,'m')">+</button></div><span class="tp-label">Minuten</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -391,25 +376,55 @@
|
||||
<label for="fShowRemaining">Art der verbleibenden Karten anzeigen</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Aufgaben (CardLock) -->
|
||||
<div class="form-section">
|
||||
<div class="form-section-title">Aufgaben (optional)</div>
|
||||
<div style="margin-bottom:0.65rem;">
|
||||
<div style="font-size:0.75rem;font-weight:700;text-transform:uppercase;letter-spacing:0.07em;color:var(--color-muted);margin-bottom:0.45rem;">Wer entscheidet über die Aufgabe?</div>
|
||||
<div class="radio-group">
|
||||
<label><input type="radio" name="modalCardTaskMode" value="RANDOM" checked> Zufall</label>
|
||||
<label><input type="radio" name="modalCardTaskMode" value="KEYHOLDER" > Keyholder*In</label>
|
||||
<label><input type="radio" name="modalCardTaskMode" value="COMMUNITY" > Community</label>
|
||||
<!-- Aufgaben-Set – sichtbar wenn TASK > 0 oder GAME_CARD > 0 -->
|
||||
<div id="sectionAufgabenSet" style="display:none;">
|
||||
<div class="form-section">
|
||||
<div class="form-section-title">Aufgaben-Set</div>
|
||||
|
||||
<!-- Task-Karte: Entscheider + internes Set -->
|
||||
<div id="subCardTaskSet" style="display:none;">
|
||||
<div style="margin-bottom:0.65rem;">
|
||||
<div style="font-size:0.75rem;font-weight:700;text-transform:uppercase;letter-spacing:0.07em;color:var(--color-muted);margin-bottom:0.45rem;">Wer entscheidet über die Aufgabe?</div>
|
||||
<div class="radio-group">
|
||||
<label><input type="radio" name="modalCardTaskMode" value="RANDOM" checked> Zufall</label>
|
||||
<label><input type="radio" name="modalCardTaskMode" value="KEYHOLDER" > Keyholder*In</label>
|
||||
<label><input type="radio" name="modalCardTaskMode" value="COMMUNITY" > Community</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0.5rem;">
|
||||
<label>Aufgaben-Set <span class="required-star">*</span></label>
|
||||
<select id="fCardTaskSetId" onchange="onTaskSetChange('card')">
|
||||
<option value="">Kein Aufgaben-Set</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn-add" type="button" onclick="openTaskSetModal(null,'card')">+ Neues Set anlegen</button>
|
||||
<div id="cardTaskSetPreview" class="task-set-preview"></div>
|
||||
</div>
|
||||
|
||||
<!-- Spiel-Karte: Chastity-Set + Spieldauer -->
|
||||
<div id="subGameSet" style="display:none;">
|
||||
<div class="form-row">
|
||||
<label>Aufgaben-Set (Chastity) <span class="required-star">*</span></label>
|
||||
<div style="position:relative;">
|
||||
<input type="text" id="gameSetSearch" placeholder="Name eingeben zum Suchen…"
|
||||
autocomplete="off" oninput="onGameSetSearch(this.value)" onfocus="onGameSetSearchFocus()">
|
||||
<div id="gameSetDropdown" class="gs-dropdown"></div>
|
||||
</div>
|
||||
<div id="gameSetSelected" class="gs-selected" style="display:none;"></div>
|
||||
<input type="hidden" id="fGameSetId">
|
||||
<div class="field-error-msg" id="errGameSet" style="display:none;">Bitte ein Aufgaben-Set für Spiel-Karten auswählen.</div>
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.4rem;">
|
||||
<label for="sldGameSpieldauer" style="margin:0;">Spieldauer</label>
|
||||
<span id="valGameSpieldauer" style="font-size:0.85rem;color:var(--color-muted);">Mittel</span>
|
||||
</div>
|
||||
<input type="range" id="sldGameSpieldauer" min="0" max="4" value="2"
|
||||
oninput="updateGameSpieldauer(this.value)"
|
||||
style="width:100%;accent-color:var(--color-primary);">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0.5rem;">
|
||||
<label>Aufgaben-Set</label>
|
||||
<select id="fCardTaskSetId" onchange="onTaskSetChange('card')">
|
||||
<option value="">Kein Aufgaben-Set</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn-add" type="button" onclick="openTaskSetModal(null,'card')">+ Neues Set anlegen</button>
|
||||
<div id="cardTaskSetPreview" class="task-set-preview"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -423,20 +438,16 @@
|
||||
<label>Mindestdauer (optional)</label>
|
||||
<div class="time-picker">
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('tmin',-1,'d')">−</button><input type="text" id="tmin_d" value="0" readonly><button type="button" onclick="tpChange('tmin',1,'d')">+</button></div><span class="tp-label">Tage</span></div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('tmin',-1,'h')">−</button><input type="text" id="tmin_h" value="00" readonly><button type="button" onclick="tpChange('tmin',1,'h')">+</button></div><span class="tp-label">Std</span></div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('tmin',-1,'m')">−</button><input type="text" id="tmin_m" value="00" readonly><button type="button" onclick="tpChange('tmin',1,'m')">+</button></div><span class="tp-label">Min</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('tmin',-1,'h')">−</button><input type="text" id="tmin_h" value="00" readonly><button type="button" onclick="tpChange('tmin',1,'h')">+</button></div><span class="tp-label">Stunden</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('tmin',-1,'m')">−</button><input type="text" id="tmin_m" value="00" readonly><button type="button" onclick="tpChange('tmin',1,'m')">+</button></div><span class="tp-label">Minuten</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row" id="rowMaxTime">
|
||||
<label>Maximaldauer<span class="required-star">*</span></label>
|
||||
<div class="time-picker">
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('tmax',-1,'d')">−</button><input type="text" id="tmax_d" value="0" readonly><button type="button" onclick="tpChange('tmax',1,'d')">+</button></div><span class="tp-label">Tage</span></div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('tmax',-1,'h')">−</button><input type="text" id="tmax_h" value="01" readonly><button type="button" onclick="tpChange('tmax',1,'h')">+</button></div><span class="tp-label">Std</span></div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('tmax',-1,'m')">−</button><input type="text" id="tmax_m" value="00" readonly><button type="button" onclick="tpChange('tmax',1,'m')">+</button></div><span class="tp-label">Min</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('tmax',-1,'h')">−</button><input type="text" id="tmax_h" value="01" readonly><button type="button" onclick="tpChange('tmax',1,'h')">+</button></div><span class="tp-label">Stunden</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('tmax',-1,'m')">−</button><input type="text" id="tmax_m" value="00" readonly><button type="button" onclick="tpChange('tmax',1,'m')">+</button></div><span class="tp-label">Minuten</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkbox-row">
|
||||
@@ -457,10 +468,8 @@
|
||||
<label>Rad drehen alle</label>
|
||||
<div class="time-picker">
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('se',-1,'d')">−</button><input type="text" id="se_d" value="0" readonly><button type="button" onclick="tpChange('se',1,'d')">+</button></div><span class="tp-label">Tage</span></div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('se',-1,'h')">−</button><input type="text" id="se_h" value="01" readonly><button type="button" onclick="tpChange('se',1,'h')">+</button></div><span class="tp-label">Std</span></div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('se',-1,'m')">−</button><input type="text" id="se_m" value="00" readonly><button type="button" onclick="tpChange('se',1,'m')">+</button></div><span class="tp-label">Min</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('se',-1,'h')">−</button><input type="text" id="se_h" value="01" readonly><button type="button" onclick="tpChange('se',1,'h')">+</button></div><span class="tp-label">Stunden</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('se',-1,'m')">−</button><input type="text" id="se_m" value="00" readonly><button type="button" onclick="tpChange('se',1,'m')">+</button></div><span class="tp-label">Minuten</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -486,10 +495,8 @@
|
||||
<label>Aufgaben alle</label>
|
||||
<div class="time-picker">
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('te',-1,'d')">−</button><input type="text" id="te_d" value="0" readonly><button type="button" onclick="tpChange('te',1,'d')">+</button></div><span class="tp-label">Tage</span></div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('te',-1,'h')">−</button><input type="text" id="te_h" value="08" readonly><button type="button" onclick="tpChange('te',1,'h')">+</button></div><span class="tp-label">Std</span></div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('te',-1,'m')">−</button><input type="text" id="te_m" value="00" readonly><button type="button" onclick="tpChange('te',1,'m')">+</button></div><span class="tp-label">Min</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('te',-1,'h')">−</button><input type="text" id="te_h" value="08" readonly><button type="button" onclick="tpChange('te',1,'h')">+</button></div><span class="tp-label">Stunden</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('te',-1,'m')">−</button><input type="text" id="te_m" value="00" readonly><button type="button" onclick="tpChange('te',1,'m')">+</button></div><span class="tp-label">Minuten</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0;">
|
||||
@@ -533,10 +540,8 @@
|
||||
<label>Dauer</label>
|
||||
<div class="time-picker">
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('pv',-1,'d')">−</button><input type="text" id="pv_d" value="0" readonly><button type="button" onclick="tpChange('pv',1,'d')">+</button></div><span class="tp-label">Tage</span></div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('pv',-1,'h')">−</button><input type="text" id="pv_h" value="01" readonly><button type="button" onclick="tpChange('pv',1,'h')">+</button></div><span class="tp-label">Std</span></div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('pv',-1,'m')">−</button><input type="text" id="pv_m" value="00" readonly><button type="button" onclick="tpChange('pv',1,'m')">+</button></div><span class="tp-label">Min</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('pv',-1,'h')">−</button><input type="text" id="pv_h" value="01" readonly><button type="button" onclick="tpChange('pv',1,'h')">+</button></div><span class="tp-label">Stunden</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('pv',-1,'m')">−</button><input type="text" id="pv_m" value="00" readonly><button type="button" onclick="tpChange('pv',1,'m')">+</button></div><span class="tp-label">Minuten</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -554,20 +559,16 @@
|
||||
<label>Hygiene-Öffnung alle</label>
|
||||
<div class="time-picker">
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('he',-1,'d')">−</button><input type="text" id="he_d" value="1" readonly><button type="button" onclick="tpChange('he',1,'d')">+</button></div><span class="tp-label">Tage</span></div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('he',-1,'h')">−</button><input type="text" id="he_h" value="00" readonly><button type="button" onclick="tpChange('he',1,'h')">+</button></div><span class="tp-label">Std</span></div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('he',-1,'m')">−</button><input type="text" id="he_m" value="00" readonly><button type="button" onclick="tpChange('he',1,'m')">+</button></div><span class="tp-label">Min</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('he',-1,'h')">−</button><input type="text" id="he_h" value="00" readonly><button type="button" onclick="tpChange('he',1,'h')">+</button></div><span class="tp-label">Stunden</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('he',-1,'m')">−</button><input type="text" id="he_m" value="00" readonly><button type="button" onclick="tpChange('he',1,'m')">+</button></div><span class="tp-label">Minuten</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0;">
|
||||
<label>Dauer der Öffnung</label>
|
||||
<div class="time-picker">
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('hd',-1,'d')">−</button><input type="text" id="hd_d" value="0" readonly><button type="button" onclick="tpChange('hd',1,'d')">+</button></div><span class="tp-label">Tage</span></div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('hd',-1,'h')">−</button><input type="text" id="hd_h" value="00" readonly><button type="button" onclick="tpChange('hd',1,'h')">+</button></div><span class="tp-label">Std</span></div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('hd',-1,'m')">−</button><input type="text" id="hd_m" value="30" readonly><button type="button" onclick="tpChange('hd',1,'m')">+</button></div><span class="tp-label">Min</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('hd',-1,'h')">−</button><input type="text" id="hd_h" value="00" readonly><button type="button" onclick="tpChange('hd',1,'h')">+</button></div><span class="tp-label">Stunden</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('hd',-1,'m')">−</button><input type="text" id="hd_m" value="30" readonly><button type="button" onclick="tpChange('hd',1,'m')">+</button></div><span class="tp-label">Minuten</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -652,8 +653,9 @@
|
||||
<script src="/js/card-defs.js"></script>
|
||||
<script src="/js/card-display.js"></script>
|
||||
<script src="/js/icons.js"></script>
|
||||
<script src="/js/nav.js"></script>
|
||||
<script src="/js/nav.js"></script>
|
||||
<script src="/js/social-sidebar.js"></script>
|
||||
<script src="/js/time-picker.js"></script>
|
||||
<script>
|
||||
function esc(s) { const d = document.createElement('div'); d.textContent = s ?? ''; return d.innerHTML; }
|
||||
|
||||
@@ -673,36 +675,6 @@
|
||||
let isLastPage = false;
|
||||
let isLoading = false;
|
||||
|
||||
// ── Zeitpicker ──
|
||||
function tpChange(prefix, delta, seg) {
|
||||
let d = parseInt(document.getElementById(prefix + '_d').value) || 0;
|
||||
let h = parseInt(document.getElementById(prefix + '_h').value) || 0;
|
||||
let m = parseInt(document.getElementById(prefix + '_m').value) || 0;
|
||||
if (seg === 'm') m += delta;
|
||||
else if (seg === 'h') h += delta;
|
||||
else d += delta;
|
||||
if (m >= 60) { h += Math.floor(m/60); m %= 60; }
|
||||
if (m < 0) { const b = Math.ceil(-m/60); h -= b; m += b*60; }
|
||||
if (h >= 24) { d += Math.floor(h/24); h %= 24; }
|
||||
if (h < 0) { const b = Math.ceil(-h/24); d -= b; h += b*24; }
|
||||
if (d < 0) d = 0;
|
||||
document.getElementById(prefix + '_d').value = d;
|
||||
document.getElementById(prefix + '_h').value = String(h).padStart(2,'0');
|
||||
document.getElementById(prefix + '_m').value = String(m).padStart(2,'0');
|
||||
}
|
||||
function tpToMinutes(prefix) {
|
||||
const d = parseInt(document.getElementById(prefix + '_d').value) || 0;
|
||||
const h = parseInt(document.getElementById(prefix + '_h').value) || 0;
|
||||
const m = parseInt(document.getElementById(prefix + '_m').value) || 0;
|
||||
return d*1440 + h*60 + m;
|
||||
}
|
||||
function tpFromMinutes(prefix, total) {
|
||||
total = total || 0;
|
||||
const d = Math.floor(total/1440), h = Math.floor((total%1440)/60), m = total%60;
|
||||
document.getElementById(prefix + '_d').value = d;
|
||||
document.getElementById(prefix + '_h').value = String(h).padStart(2,'0');
|
||||
document.getElementById(prefix + '_m').value = String(m).padStart(2,'0');
|
||||
}
|
||||
function fmtMinutes(min) {
|
||||
if (!min) return '–';
|
||||
const d = Math.floor(min/1440), h = Math.floor((min%1440)/60), m = min%60;
|
||||
@@ -757,8 +729,134 @@
|
||||
function syncMinMax(id, val) {
|
||||
if (id.startsWith('min_')) { const mx = document.getElementById('max_'+id.slice(4)); if (mx && val > (parseInt(mx.value)||0)) mx.value = val; }
|
||||
else if (id.startsWith('max_')) { const mn = document.getElementById('min_'+id.slice(4)); if (mn && val < (parseInt(mn.value)||0)) mn.value = val; }
|
||||
if (id === 'min_GAME_CARD' || id === 'max_GAME_CARD') {
|
||||
if (val > 0) {
|
||||
// Gegenseitiger Ausschluss: Task-Karten nullen
|
||||
const minT = document.getElementById('min_TASK'); if (minT) minT.value = 0;
|
||||
const maxT = document.getElementById('max_TASK'); if (maxT) maxT.value = 0;
|
||||
checkTaskCardSection();
|
||||
}
|
||||
checkGameCardSection();
|
||||
}
|
||||
if (id === 'min_TASK' || id === 'max_TASK') {
|
||||
if (val > 0) {
|
||||
// Gegenseitiger Ausschluss: Game-Karten nullen
|
||||
const minG = document.getElementById('min_GAME_CARD'); if (minG) minG.value = 0;
|
||||
const maxG = document.getElementById('max_GAME_CARD'); if (maxG) maxG.value = 0;
|
||||
checkGameCardSection();
|
||||
}
|
||||
checkTaskCardSection();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Spiel-Karte: Aufgaben-Set + Spieldauer ──
|
||||
const GAME_SPIELDAUER = [
|
||||
{ label: 'Sehr kurz' },
|
||||
{ label: 'Kurz' },
|
||||
{ label: 'Mittel' },
|
||||
{ label: 'Lang' },
|
||||
{ label: 'Sehr lang' },
|
||||
];
|
||||
let _gameSetSearchTimer = null;
|
||||
let _gameSetResults = [];
|
||||
|
||||
function checkGameCardSection() {
|
||||
const minV = parseInt(document.getElementById('min_GAME_CARD')?.value) || 0;
|
||||
const maxV = parseInt(document.getElementById('max_GAME_CARD')?.value) || 0;
|
||||
const hasGame = minV > 0 || maxV > 0;
|
||||
const sub = document.getElementById('subGameSet');
|
||||
if (sub) sub.style.display = hasGame ? '' : 'none';
|
||||
checkAufgabenSetSection();
|
||||
}
|
||||
|
||||
function checkTaskCardSection() {
|
||||
const minV = parseInt(document.getElementById('min_TASK')?.value) || 0;
|
||||
const maxV = parseInt(document.getElementById('max_TASK')?.value) || 0;
|
||||
const hasTask = minV > 0 || maxV > 0;
|
||||
const sub = document.getElementById('subCardTaskSet');
|
||||
if (sub) sub.style.display = hasTask ? '' : 'none';
|
||||
checkAufgabenSetSection();
|
||||
}
|
||||
|
||||
function checkAufgabenSetSection() {
|
||||
const hasTask = (parseInt(document.getElementById('min_TASK')?.value) || 0) > 0
|
||||
|| (parseInt(document.getElementById('max_TASK')?.value) || 0) > 0;
|
||||
const hasGame = (parseInt(document.getElementById('min_GAME_CARD')?.value) || 0) > 0
|
||||
|| (parseInt(document.getElementById('max_GAME_CARD')?.value) || 0) > 0;
|
||||
const sec = document.getElementById('sectionAufgabenSet');
|
||||
if (sec) sec.style.display = (hasTask || hasGame) ? '' : 'none';
|
||||
}
|
||||
|
||||
function updateGameSpieldauer(val) {
|
||||
document.getElementById('valGameSpieldauer').textContent = GAME_SPIELDAUER[+val].label;
|
||||
}
|
||||
|
||||
function onGameSetSearchFocus() {
|
||||
if (!document.getElementById('fGameSetId').value) onGameSetSearch(document.getElementById('gameSetSearch').value);
|
||||
}
|
||||
|
||||
function onGameSetSearch(value) {
|
||||
clearTimeout(_gameSetSearchTimer);
|
||||
if (value.length === 0) {
|
||||
_gameSetSearchTimer = setTimeout(() => doGameSetSearch(''), 0);
|
||||
} else if (value.length < 2) {
|
||||
document.getElementById('gameSetDropdown').style.display = 'none';
|
||||
} else {
|
||||
_gameSetSearchTimer = setTimeout(() => doGameSetSearch(value), 300);
|
||||
}
|
||||
}
|
||||
|
||||
async function doGameSetSearch(search) {
|
||||
try {
|
||||
const url = '/gruppe/chastity' + (search ? '?search=' + encodeURIComponent(search) : '');
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
_gameSetResults = data.gruppen || [];
|
||||
renderGameSetDropdown();
|
||||
} catch(e) { console.error(e); }
|
||||
}
|
||||
|
||||
function renderGameSetDropdown() {
|
||||
const dd = document.getElementById('gameSetDropdown');
|
||||
if (!dd) return;
|
||||
if (!_gameSetResults.length) { dd.style.display = 'none'; return; }
|
||||
dd.innerHTML = _gameSetResults.map(g => `
|
||||
<div class="gs-dropdown-item" onclick="selectGameSet('${esc(g.gruppenId)}','${esc(g.name).replace(/'/g, "\\'")}')">
|
||||
<div class="gs-item-name">${esc(g.name)}</div>
|
||||
${g.beschreibung ? `<div class="gs-item-desc">${esc(g.beschreibung)}</div>` : ''}
|
||||
</div>`).join('');
|
||||
dd.style.display = 'block';
|
||||
}
|
||||
|
||||
function selectGameSet(id, name, suppressDirty = false) {
|
||||
document.getElementById('fGameSetId').value = id;
|
||||
document.getElementById('gameSetSearch').value = '';
|
||||
document.getElementById('gameSetDropdown').style.display = 'none';
|
||||
document.getElementById('gameSetSelected').innerHTML =
|
||||
`<span style="flex:1;">${esc(name)}</span>
|
||||
<button type="button" onclick="clearGameSet()" title="Auswahl entfernen">✕</button>`;
|
||||
document.getElementById('gameSetSelected').style.display = 'flex';
|
||||
document.getElementById('errGameSet').style.display = 'none';
|
||||
if (!suppressDirty) markDirty();
|
||||
}
|
||||
|
||||
function clearGameSet() {
|
||||
document.getElementById('fGameSetId').value = '';
|
||||
document.getElementById('gameSetSearch').value = '';
|
||||
document.getElementById('gameSetSelected').style.display = 'none';
|
||||
document.getElementById('gameSetSelected').innerHTML = '';
|
||||
markDirty();
|
||||
}
|
||||
|
||||
document.addEventListener('click', e => {
|
||||
const search = document.getElementById('gameSetSearch');
|
||||
const dd = document.getElementById('gameSetDropdown');
|
||||
if (dd && search && !search.contains(e.target) && !dd.contains(e.target)) {
|
||||
dd.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// ── Karten-Info ──
|
||||
function openCardInfo(cardId) {
|
||||
const c = CARD_DEFS.find(x => x.id === cardId); if (!c) return;
|
||||
@@ -815,10 +913,8 @@
|
||||
<div id="we-tp-${id}" style="display:none;">
|
||||
<div class="time-picker">
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('wt${id}',-1,'d')">−</button><input type="text" id="wt${id}_d" value="0" readonly><button type="button" onclick="tpChange('wt${id}',1,'d')">+</button></div><span class="tp-label">Tage</span></div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('wt${id}',-1,'h')">−</button><input type="text" id="wt${id}_h" value="01" readonly><button type="button" onclick="tpChange('wt${id}',1,'h')">+</button></div><span class="tp-label">Std</span></div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('wt${id}',-1,'m')">−</button><input type="text" id="wt${id}_m" value="00" readonly><button type="button" onclick="tpChange('wt${id}',1,'m')">+</button></div><span class="tp-label">Min</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('wt${id}',-1,'h')">−</button><input type="text" id="wt${id}_h" value="01" readonly><button type="button" onclick="tpChange('wt${id}',1,'h')">+</button></div><span class="tp-label">Stunden</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('wt${id}',-1,'m')">−</button><input type="text" id="wt${id}_m" value="00" readonly><button type="button" onclick="tpChange('wt${id}',1,'m')">+</button></div><span class="tp-label">Minuten</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" id="we-str-${id}" placeholder="Text…" maxlength="200"
|
||||
@@ -1163,7 +1259,7 @@
|
||||
function alignModalToContent() {
|
||||
const rect = document.querySelector('.content')?.getBoundingClientRect();
|
||||
if (!rect) return;
|
||||
document.getElementById('modalBackdrop').querySelector('.modal-box').style.width = Math.min(rect.width, 720) + 'px';
|
||||
document.getElementById('modalBackdrop').querySelector('.modal-box').style.width = Math.min(rect.width, 800) + 'px';
|
||||
document.getElementById('taskSetModalBackdrop').querySelector('.modal-box').style.width = Math.min(rect.width, 900) + 'px';
|
||||
}
|
||||
|
||||
@@ -1201,6 +1297,20 @@
|
||||
tpFromMinutes('pe', template?.pickEveryMinute || 60);
|
||||
document.getElementById('fAccumulate').checked = template?.accumulatePicks || false;
|
||||
document.getElementById('fShowRemaining').checked = template?.showRemainingCards || false;
|
||||
|
||||
// Task-Karte und Spiel-Karte
|
||||
checkTaskCardSection();
|
||||
clearGameSet();
|
||||
checkGameCardSection();
|
||||
const gsi = template?.gameSpieldauerIdx ?? 2;
|
||||
document.getElementById('sldGameSpieldauer').value = gsi;
|
||||
updateGameSpieldauer(gsi);
|
||||
if (template?.gameSetId) {
|
||||
fetch(`/gruppe/${template.gameSetId}`)
|
||||
.then(r => r.ok ? r.json() : null)
|
||||
.then(g => { if (g?.name) selectGameSet(template.gameSetId, g.name, true); })
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'TIMELOCK') {
|
||||
@@ -1350,7 +1460,15 @@
|
||||
const totalMax = CARD_DEFS.reduce((s,c)=>s+(parseInt(document.getElementById('max_'+c.id).value)||0),0);
|
||||
if (totalMax===0) { showModalError('Das Deck muss mindestens eine Karte enthalten.'); firstError=firstError||document.getElementById('modalError'); }
|
||||
const hasTaskCards = (parseInt(document.getElementById('min_TASK').value)||0)>0 || (parseInt(document.getElementById('max_TASK').value)||0)>0;
|
||||
if (hasTaskCards && !document.getElementById('fCardTaskSetId').value) { showModalError('Aufgaben-Karten konfiguriert, aber kein Aufgaben-Set ausgewählt.'); firstError=firstError||document.getElementById('modalError'); }
|
||||
if (hasTaskCards && !document.getElementById('fCardTaskSetId').value) { showModalError('Bitte ein Aufgaben-Set für die Aufgaben-Karten auswählen.'); firstError=firstError||document.getElementById('modalError'); }
|
||||
const hasGameCards = (parseInt(document.getElementById('min_GAME_CARD').value)||0)>0 || (parseInt(document.getElementById('max_GAME_CARD').value)||0)>0;
|
||||
if (hasGameCards && !document.getElementById('fGameSetId').value) {
|
||||
document.getElementById('errGameSet').style.display = '';
|
||||
showModalError('Spiel-Karten konfiguriert, aber kein Aufgaben-Set ausgewählt.');
|
||||
firstError = firstError || document.getElementById('errGameSet');
|
||||
} else {
|
||||
document.getElementById('errGameSet').style.display = 'none';
|
||||
}
|
||||
if (firstError) { firstError.scrollIntoView({behavior:'smooth',block:'center'}); return; }
|
||||
|
||||
const cardCountsMin={}, cardCountsMax={};
|
||||
@@ -1369,6 +1487,8 @@
|
||||
taskSetId: document.getElementById('fCardTaskSetId').value || null,
|
||||
requiresVerification: document.getElementById('fRequiresVerification').checked,
|
||||
taskMode: document.querySelector('input[name="modalCardTaskMode"]:checked')?.value||'RANDOM',
|
||||
gameSetId: hasGameCards ? (document.getElementById('fGameSetId').value || null) : null,
|
||||
gameSpieldauerIdx: hasGameCards ? (parseInt(document.getElementById('sldGameSpieldauer').value) || 2) : null,
|
||||
};
|
||||
} else {
|
||||
// TimeLock
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<title>Neues Lock – xXx Sphere</title>
|
||||
<link rel="stylesheet" href="/css/variables.css">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/time-picker.css">
|
||||
<style>
|
||||
.form-section {
|
||||
background: var(--color-card);
|
||||
@@ -108,26 +109,6 @@
|
||||
.field-error input { border-color: #e74c3c !important; }
|
||||
.field-error-msg { font-size: 0.78rem; color: #e74c3c; margin-top: 0.15rem; }
|
||||
|
||||
/* Zeitpicker */
|
||||
.time-picker { display:flex; align-items:center; gap:0.4rem; flex-wrap:wrap; }
|
||||
.tp-seg { display:flex; flex-direction:column; align-items:center; gap:0.15rem; }
|
||||
.tp-seg-row { display:flex; align-items:center; gap:0.2rem; }
|
||||
.tp-seg button {
|
||||
width:24px; height:24px; background:var(--color-card);
|
||||
border:1px solid var(--color-muted); border-radius:4px;
|
||||
cursor:pointer; font-size:0.9rem; font-weight:700; color:var(--color-text);
|
||||
display:flex; align-items:center; justify-content:center; padding:0; flex-shrink:0;
|
||||
}
|
||||
.tp-seg button:hover { background:var(--color-primary); color:#fff; border-color:var(--color-primary); }
|
||||
.tp-seg input {
|
||||
width:28px; text-align:center; background:var(--color-card);
|
||||
border:1px solid var(--color-muted); border-radius:4px;
|
||||
color:var(--color-text); font-size:0.9rem; font-weight:600;
|
||||
font-family:monospace; padding:0.15rem 0; box-sizing:border-box;
|
||||
}
|
||||
.tp-seg .tp-label { font-size:0.62rem; color:var(--color-muted); text-transform:uppercase; letter-spacing:0.04em; }
|
||||
.tp-colon { font-size:1rem; font-weight:700; color:var(--color-muted); margin-bottom:0.9rem; }
|
||||
|
||||
/* LockControl-Auswahl */
|
||||
.lockcontrol-options { display: flex; flex-direction: column; gap: 0.6rem; }
|
||||
.lockcontrol-option {
|
||||
@@ -225,23 +206,8 @@
|
||||
<div class="form-row" id="rowMaxDuration">
|
||||
<label>Längste Dauer</label>
|
||||
<div class="time-picker">
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="tpChange('dur',-1,'d')">−</button>
|
||||
<input type="text" id="dur_d" value="0" readonly>
|
||||
<button type="button" onclick="tpChange('dur',1,'d')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Tage</span>
|
||||
</div>
|
||||
<div class="tp-colon">:</div>
|
||||
<div class="tp-seg">
|
||||
<div class="tp-seg-row">
|
||||
<button type="button" onclick="tpChange('dur',-1,'h')">−</button>
|
||||
<input type="text" id="dur_h" value="00" readonly>
|
||||
<button type="button" onclick="tpChange('dur',1,'h')">+</button>
|
||||
</div>
|
||||
<span class="tp-label">Std</span>
|
||||
</div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('dur',-1,'d')">−</button><input type="text" id="dur_d" value="0" readonly><button type="button" onclick="tpChange('dur',1,'d')">+</button></div><span class="tp-label">Tage</span></div>
|
||||
<div class="tp-seg"><div class="tp-seg-row"><button type="button" onclick="tpChange('dur',-1,'h')">−</button><input type="text" id="dur_h" value="00" readonly><button type="button" onclick="tpChange('dur',1,'h')">+</button></div><span class="tp-label">Stunden</span></div>
|
||||
</div>
|
||||
<div class="form-hint">Das Lock öffnet spätestens nach dieser Zeit automatisch. 0 : 00 = keine Begrenzung.</div>
|
||||
</div>
|
||||
@@ -359,8 +325,9 @@
|
||||
<script src="/js/card-defs.js"></script>
|
||||
<script src="/js/shared.js"></script>
|
||||
<script src="/js/icons.js"></script>
|
||||
<script src="/js/nav.js"></script>
|
||||
<script src="/js/nav.js"></script>
|
||||
<script src="/js/social-sidebar.js"></script>
|
||||
<script src="/js/time-picker.js"></script>
|
||||
<script>
|
||||
let myUserId = null;
|
||||
let myUserName = null;
|
||||
@@ -655,20 +622,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ── Zeitpicker ──
|
||||
function tpChange(prefix, delta, seg) {
|
||||
let d = parseInt(document.getElementById(prefix + '_d').value) || 0;
|
||||
let h = parseInt(document.getElementById(prefix + '_h')?.value) || 0;
|
||||
if (seg === 'h') h += delta;
|
||||
else d += delta;
|
||||
if (h >= 24) { d += Math.floor(h / 24); h %= 24; }
|
||||
if (h < 0) { const b = Math.ceil(-h / 24); d -= b; h += b * 24; }
|
||||
if (d < 0) d = 0;
|
||||
document.getElementById(prefix + '_d').value = d;
|
||||
if (document.getElementById(prefix + '_h'))
|
||||
document.getElementById(prefix + '_h').value = String(h).padStart(2, '0');
|
||||
}
|
||||
|
||||
// ── Längste Dauer → LocalDateTime ──
|
||||
function durationToLatestOpening() {
|
||||
const days = parseInt(document.getElementById('dur_d').value) || 0;
|
||||
@@ -843,6 +796,8 @@
|
||||
requiresVerification: t.requiresVerification,
|
||||
testLock: isTestLock,
|
||||
controllType: selectedLockControl,
|
||||
gameSetId: t.gameSetId || null,
|
||||
gameSpieldauerIdx: t.gameSpieldauerIdx ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
<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-secondary" onclick="goBack()" style="margin-top:1.25rem;">Zurück zum Lock</button>
|
||||
<button class="btn-primary" onclick="completeGame()" style="margin-top:1.25rem;">✓ Spiel beenden</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -189,8 +189,9 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const params = new URLSearchParams(location.search);
|
||||
const lockId = params.get('lockId');
|
||||
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;
|
||||
@@ -201,13 +202,23 @@
|
||||
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) {
|
||||
await loadGroups();
|
||||
if (autoGameSetId) {
|
||||
await autoStartGame(autoGameSetId);
|
||||
} else {
|
||||
await loadGroups();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!r.ok) throw new Error('Fehler beim Laden des Spielzustands');
|
||||
@@ -219,6 +230,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
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() : [];
|
||||
|
||||
BIN
bin/main/static/img/icons/bell.png
Normal file
BIN
bin/main/static/img/icons/bell.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
bin/main/static/img/icons/envelope.png
Normal file
BIN
bin/main/static/img/icons/envelope.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
BIN
bin/main/static/img/icons/lock.png
Normal file
BIN
bin/main/static/img/icons/lock.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
BIN
bin/main/static/img/icons/stars.png
Normal file
BIN
bin/main/static/img/icons/stars.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
54
bin/main/static/js/time-picker.js
Normal file
54
bin/main/static/js/time-picker.js
Normal file
@@ -0,0 +1,54 @@
|
||||
function tpChange(prefix, delta, seg) {
|
||||
let d = parseInt(document.getElementById(prefix + '_d').value) || 0;
|
||||
let h = parseInt(document.getElementById(prefix + '_h')?.value) || 0;
|
||||
let m = parseInt(document.getElementById(prefix + '_m')?.value) || 0;
|
||||
if (seg === 'm') m += delta;
|
||||
else if (seg === 'h') h += delta;
|
||||
else d += delta;
|
||||
if (m >= 60) { h += Math.floor(m/60); m %= 60; }
|
||||
if (m < 0) { const b = Math.ceil(-m/60); h -= b; m += b*60; }
|
||||
if (h >= 24) { d += Math.floor(h/24); h %= 24; }
|
||||
if (h < 0) { const b = Math.ceil(-h/24); d -= b; h += b*24; }
|
||||
if (d < 0) d = 0;
|
||||
document.getElementById(prefix + '_d').value = d;
|
||||
if (document.getElementById(prefix + '_h'))
|
||||
document.getElementById(prefix + '_h').value = String(h).padStart(2, '0');
|
||||
if (document.getElementById(prefix + '_m'))
|
||||
document.getElementById(prefix + '_m').value = String(m).padStart(2, '0');
|
||||
}
|
||||
|
||||
function tpToMinutes(prefix) {
|
||||
const d = parseInt(document.getElementById(prefix + '_d').value) || 0;
|
||||
const h = parseInt(document.getElementById(prefix + '_h')?.value) || 0;
|
||||
const m = parseInt(document.getElementById(prefix + '_m')?.value) || 0;
|
||||
return d * 1440 + h * 60 + m;
|
||||
}
|
||||
|
||||
function tpFromMinutes(prefix, total) {
|
||||
total = total || 0;
|
||||
const d = Math.floor(total / 1440);
|
||||
const h = Math.floor((total % 1440) / 60);
|
||||
const m = total % 60;
|
||||
document.getElementById(prefix + '_d').value = d;
|
||||
if (document.getElementById(prefix + '_h'))
|
||||
document.getElementById(prefix + '_h').value = String(h).padStart(2, '0');
|
||||
if (document.getElementById(prefix + '_m'))
|
||||
document.getElementById(prefix + '_m').value = String(m).padStart(2, '0');
|
||||
}
|
||||
|
||||
function tpHtml(prefix, initD, initH, initM) {
|
||||
const d = initD ?? 0, h = initH ?? 0, m = initM ?? 0;
|
||||
return `<div class="time-picker">` +
|
||||
`<div class="tp-seg"><button type="button" onclick="tpChange('${prefix}',-1,'d')">−</button><input type="text" id="${prefix}_d" value="${d}" readonly><button type="button" onclick="tpChange('${prefix}',1,'d')">+</button><span class="tp-label">Tage</span></div>` +
|
||||
`<div class="tp-seg"><button type="button" onclick="tpChange('${prefix}',-1,'h')">−</button><input type="text" id="${prefix}_h" value="${String(h).padStart(2,'0')}" readonly><button type="button" onclick="tpChange('${prefix}',1,'h')">+</button><span class="tp-label">Stunden</span></div>` +
|
||||
`<div class="tp-seg"><button type="button" onclick="tpChange('${prefix}',-1,'m')">−</button><input type="text" id="${prefix}_m" value="${String(m).padStart(2,'0')}" readonly><button type="button" onclick="tpChange('${prefix}',1,'m')">+</button><span class="tp-label">Minuten</span></div>` +
|
||||
`</div>`;
|
||||
}
|
||||
|
||||
function tpHtmlDH(prefix, initD, initH) {
|
||||
const d = initD ?? 0, h = initH ?? 0;
|
||||
return `<div class="time-picker">` +
|
||||
`<div class="tp-seg"><button type="button" onclick="tpChange('${prefix}',-1,'d')">−</button><input type="text" id="${prefix}_d" value="${d}" readonly><button type="button" onclick="tpChange('${prefix}',1,'d')">+</button><span class="tp-label">Tage</span></div>` +
|
||||
`<div class="tp-seg"><button type="button" onclick="tpChange('${prefix}',-1,'h')">−</button><input type="text" id="${prefix}_h" value="${String(h).padStart(2,'0')}" readonly><button type="button" onclick="tpChange('${prefix}',1,'h')">+</button><span class="tp-label">Stunden</span></div>` +
|
||||
`</div>`;
|
||||
}
|
||||
@@ -35,15 +35,15 @@
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<button class="topbar-btn" id="topbarMsgBtn" title="Nachrichten">
|
||||
${IC('MESSAGES')}
|
||||
<img src="/img/icons/envelope.png" style="width:1.875rem;height:1.875rem;object-fit:contain;display:block;" alt="">
|
||||
<span class="topbar-badge" id="topbarMsgBadge"></span>
|
||||
</button>
|
||||
<button class="topbar-btn" id="topbarNotifBtn" title="Benachrichtigungen">
|
||||
${IC('NOTIFICATIONS')}
|
||||
<img src="/img/icons/bell.png" style="width:1.875rem;height:1.875rem;object-fit:contain;display:block;" alt="">
|
||||
<span class="topbar-badge" id="topbarNotifBadge"></span>
|
||||
</button>
|
||||
<button class="topbar-btn" id="topbarInvBtn" title="Einladungen">
|
||||
${IC('INVITATIONS')}
|
||||
<img src="/img/icons/stars.png" style="width:1.875rem;height:1.875rem;object-fit:contain;display:block;" alt="">
|
||||
<span class="topbar-badge" id="topbarInvBadge"></span>
|
||||
</button>
|
||||
<button class="topbar-btn topbar-profile-btn" id="topbarProfileBtn">
|
||||
@@ -57,7 +57,7 @@
|
||||
document.body.insertAdjacentHTML('beforeend', `
|
||||
<div class="topbar-panel" id="topbarMsgPanel">
|
||||
<div class="topbar-panel-header">
|
||||
<span>${IC('MESSAGES')} Nachrichten</span>
|
||||
<span><img src="/img/icons/envelope.png" style="width:1rem;height:1rem;object-fit:contain;vertical-align:middle;margin-right:0.3rem;" alt=""> Nachrichten</span>
|
||||
<button class="topbar-panel-close" onclick="window.__topbarCloseAll()">✕</button>
|
||||
</div>
|
||||
<div class="topbar-panel-body" id="topbarMsgBody"></div>
|
||||
@@ -66,7 +66,7 @@
|
||||
|
||||
<div class="topbar-panel" id="topbarNotifPanel">
|
||||
<div class="topbar-panel-header">
|
||||
<span>${IC('NOTIFICATIONS')} Benachrichtigungen</span>
|
||||
<span><img src="/img/icons/bell.png" style="width:1rem;height:1rem;object-fit:contain;vertical-align:middle;margin-right:0.3rem;" alt=""> Benachrichtigungen</span>
|
||||
<button class="topbar-panel-close" onclick="window.__topbarCloseAll()">✕</button>
|
||||
</div>
|
||||
<div class="topbar-panel-body" id="topbarNotifBody"></div>
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
<div class="topbar-panel" id="topbarInvPanel">
|
||||
<div class="topbar-panel-header">
|
||||
<span>${IC('INVITATIONS')} Einladungen</span>
|
||||
<span><img src="/img/icons/stars.png" style="width:1rem;height:1rem;object-fit:contain;vertical-align:middle;margin-right:0.3rem;" alt=""> Einladungen</span>
|
||||
<button class="topbar-panel-close" onclick="window.__topbarCloseAll()">✕</button>
|
||||
</div>
|
||||
<div class="topbar-panel-body" id="topbarInvBody"></div>
|
||||
|
||||
Reference in New Issue
Block a user