diff --git a/bin/main/db/migration/V3__migrate_chastity_game_set.sql b/bin/main/db/migration/V3__migrate_chastity_game_set.sql
new file mode 100644
index 0000000..81f4c42
--- /dev/null
+++ b/bin/main/db/migration/V3__migrate_chastity_game_set.sql
@@ -0,0 +1,215 @@
+-- Migration: Inhalte aus chastity_game_set in die normalen Aufgabengruppen-Tabellen übernehmen.
+--
+-- Jedes chastity_game_set eines Users wird zu einer privaten CHASTITY_ONLY-Aufgabengruppe:
+-- aufgaben (JSON) → aufgabe + aufgabe_benoetigt_passiv
+-- zeitstrafen (JSON) → sperre + sperre_sperre_fuer
+-- finisher (JSON) → finisher
+--
+-- Die Prozedur prüft zuerst, ob chastity_game_set existiert – auf leeren Datenbanken
+-- ist sie dadurch ein No-op.
+
+DROP PROCEDURE IF EXISTS proc_migrate_chastity_game_set;
+
+CREATE PROCEDURE proc_migrate_chastity_game_set()
+BEGIN
+ DECLARE v_set_id VARCHAR(36);
+ DECLARE v_owner_id VARCHAR(36);
+ DECLARE v_set_name VARCHAR(255);
+ DECLARE v_user_name VARCHAR(255);
+ DECLARE v_aufgaben_json TEXT;
+ DECLARE v_zeitstr_json TEXT;
+ DECLARE v_finisher_json TEXT;
+
+ DECLARE v_gruppe_id VARCHAR(36);
+ DECLARE v_aufgabe_id VARCHAR(36);
+ DECLARE v_sperre_id VARCHAR(36);
+ DECLARE v_finisher_id VARCHAR(36);
+
+ DECLARE v_outer_count INT;
+ DECLARE v_inner_count INT;
+ DECLARE i INT;
+ DECLARE j INT;
+
+ DECLARE tbl_exists INT DEFAULT 0;
+ DECLARE done INT DEFAULT FALSE;
+
+ DECLARE cur CURSOR FOR
+ SELECT id, owner_id, name, aufgaben, zeitstrafen, finisher
+ FROM chastity_game_set;
+ DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
+
+ -- Add columns that may not yet exist (Hibernate ddl-auto runs after Flyway)
+ IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'aufgabe' AND COLUMN_NAME = 'level') THEN
+ ALTER TABLE aufgabe ADD COLUMN level INT NULL;
+ END IF;
+ IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'sperre' AND COLUMN_NAME = 'level') THEN
+ ALTER TABLE sperre ADD COLUMN level INT NULL;
+ END IF;
+ IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'sperre' AND COLUMN_NAME = 'temp_unlock_before_required') THEN
+ ALTER TABLE sperre ADD COLUMN temp_unlock_before_required TINYINT(1) NULL;
+ END IF;
+ IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'sperre' AND COLUMN_NAME = 'temp_unlock_after_required') THEN
+ ALTER TABLE sperre ADD COLUMN temp_unlock_after_required TINYINT(1) NULL;
+ END IF;
+
+ SELECT COUNT(*) INTO tbl_exists
+ FROM information_schema.TABLES
+ WHERE TABLE_SCHEMA = DATABASE()
+ AND TABLE_NAME = 'chastity_game_set';
+
+ IF tbl_exists > 0 THEN
+
+ OPEN cur;
+
+ set_loop: LOOP
+ FETCH cur INTO v_set_id, v_owner_id, v_set_name,
+ v_aufgaben_json, v_zeitstr_json, v_finisher_json;
+ IF done THEN LEAVE set_loop; END IF;
+
+ SELECT name INTO v_user_name
+ FROM user
+ WHERE user_id = v_owner_id
+ LIMIT 1;
+
+ SET v_gruppe_id = UUID();
+
+ -- ── Aufgabengruppe anlegen ───────────────────────────────────────
+ INSERT INTO aufgaben_gruppe
+ (gruppen_id, name, beschreibung, user_id, private_gruppe, bild, von, available_in)
+ VALUES
+ (v_gruppe_id,
+ v_set_name,
+ 'Importiert aus Chastity Game Set',
+ v_owner_id,
+ TRUE,
+ NULL,
+ v_user_name,
+ 'CHASTITY_ONLY');
+
+ -- ── Aufgaben ────────────────────────────────────────────────────
+ -- GameSetAufgabe.minutes → sekunden_von / sekunden_bis (× 60)
+ -- GameSetAufgabe.benoetigt → aufgabe_benoetigt_passiv
+ SET v_outer_count = IFNULL(JSON_LENGTH(v_aufgaben_json), 0);
+ SET i = 0;
+ WHILE i < v_outer_count DO
+ SET v_aufgabe_id = UUID();
+
+ INSERT INTO aufgabe
+ (aufgabe_id, kurz_text, text, level, sekunden_von, sekunden_bis, gruppe_id)
+ VALUES (
+ v_aufgabe_id,
+ JSON_UNQUOTE(JSON_EXTRACT(v_aufgaben_json, CONCAT('$[', i, '].title'))),
+ JSON_UNQUOTE(JSON_EXTRACT(v_aufgaben_json, CONCAT('$[', i, '].description'))),
+ CAST(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(v_aufgaben_json, CONCAT('$[', i, '].level'))), 'null') AS SIGNED),
+ CASE
+ WHEN JSON_EXTRACT(v_aufgaben_json, CONCAT('$[', i, '].minutes')) IS NOT NULL
+ THEN CAST(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(v_aufgaben_json, CONCAT('$[', i, '].minutes'))), 'null') AS SIGNED) * 60
+ ELSE NULL
+ END,
+ CASE
+ WHEN JSON_EXTRACT(v_aufgaben_json, CONCAT('$[', i, '].minutes')) IS NOT NULL
+ THEN CAST(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(v_aufgaben_json, CONCAT('$[', i, '].minutes'))), 'null') AS SIGNED) * 60
+ ELSE NULL
+ END,
+ v_gruppe_id
+ );
+
+ SET v_inner_count = IFNULL(
+ JSON_LENGTH(JSON_EXTRACT(v_aufgaben_json, CONCAT('$[', i, '].benoetigt'))),
+ 0);
+ SET j = 0;
+ WHILE j < v_inner_count DO
+ SET @wz = NULLIF(JSON_UNQUOTE(JSON_EXTRACT(v_aufgaben_json,
+ CONCAT('$[', i, '].benoetigt[', j, ']'))), 'null');
+ IF @wz IS NOT NULL AND @wz != '' THEN
+ INSERT IGNORE INTO aufgabe_benoetigt_passiv (aufgabe_id, werkzeug)
+ VALUES (v_aufgabe_id, @wz);
+ END IF;
+ SET j = j + 1;
+ END WHILE;
+
+ SET i = i + 1;
+ END WHILE;
+
+ -- ── Zeitstrafen → Sperren ────────────────────────────────────────
+ -- GameSetZeitstrafe.minMinutes / maxMinutes → minuten_von / minuten_bis
+ -- GameSetZeitstrafe.level / tempUnlock* → level / temp_unlock_before/after_required
+ -- GameSetZeitstrafe.sperrt → sperre_sperre_fuer
+ SET v_outer_count = IFNULL(JSON_LENGTH(v_zeitstr_json), 0);
+ SET i = 0;
+ WHILE i < v_outer_count DO
+ SET v_sperre_id = UUID();
+
+ INSERT INTO sperre
+ (sperre_id, kurz_text, text, release_text, minuten_von, minuten_bis,
+ level, temp_unlock_before_required, temp_unlock_after_required, gruppe_id)
+ VALUES (
+ v_sperre_id,
+ JSON_UNQUOTE(JSON_EXTRACT(v_zeitstr_json, CONCAT('$[', i, '].title'))),
+ JSON_UNQUOTE(JSON_EXTRACT(v_zeitstr_json, CONCAT('$[', i, '].description'))),
+ IF(JSON_EXTRACT(v_zeitstr_json, CONCAT('$[', i, '].releaseText')) IS NOT NULL,
+ JSON_UNQUOTE(JSON_EXTRACT(v_zeitstr_json, CONCAT('$[', i, '].releaseText'))),
+ NULL),
+ CAST(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(v_zeitstr_json, CONCAT('$[', i, '].minMinutes'))), 'null') AS SIGNED),
+ CAST(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(v_zeitstr_json, CONCAT('$[', i, '].maxMinutes'))), 'null') AS SIGNED),
+ CAST(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(v_zeitstr_json, CONCAT('$[', i, '].level'))), 'null') AS SIGNED),
+ CASE JSON_UNQUOTE(JSON_EXTRACT(v_zeitstr_json, CONCAT('$[', i, '].tempUnlockBeforeRequired')))
+ WHEN 'true' THEN 1 WHEN 'false' THEN 0 ELSE NULL END,
+ CASE JSON_UNQUOTE(JSON_EXTRACT(v_zeitstr_json, CONCAT('$[', i, '].tempUnlockAfterRequired')))
+ WHEN 'true' THEN 1 WHEN 'false' THEN 0 ELSE NULL END,
+ v_gruppe_id
+ );
+
+ SET v_inner_count = IFNULL(
+ JSON_LENGTH(JSON_EXTRACT(v_zeitstr_json, CONCAT('$[', i, '].sperrt'))),
+ 0);
+ SET j = 0;
+ WHILE j < v_inner_count DO
+ SET @wz = NULLIF(JSON_UNQUOTE(JSON_EXTRACT(v_zeitstr_json,
+ CONCAT('$[', i, '].sperrt[', j, ']'))), 'null');
+ IF @wz IS NOT NULL AND @wz != '' THEN
+ INSERT IGNORE INTO sperre_sperre_fuer (sperre_id, werkzeug)
+ VALUES (v_sperre_id, @wz);
+ END IF;
+ SET j = j + 1;
+ END WHILE;
+
+ SET i = i + 1;
+ END WHILE;
+
+ -- ── Finisher ─────────────────────────────────────────────────────
+ -- GameSetFinisher hat kein geschlecht und keine benoetigt-Listen
+ -- tempUnlockBeforeRequired / tempUnlockAfterRequired haben kein Gegenstück in finisher
+ SET v_outer_count = IFNULL(JSON_LENGTH(v_finisher_json), 0);
+ SET i = 0;
+ WHILE i < v_outer_count DO
+ SET v_finisher_id = UUID();
+
+ INSERT INTO finisher
+ (finisher_id, kurz_text, text, geschlecht, gruppe_id)
+ VALUES (
+ v_finisher_id,
+ JSON_UNQUOTE(JSON_EXTRACT(v_finisher_json, CONCAT('$[', i, '].title'))),
+ JSON_UNQUOTE(JSON_EXTRACT(v_finisher_json, CONCAT('$[', i, '].description'))),
+ NULL,
+ v_gruppe_id
+ );
+
+ SET i = i + 1;
+ END WHILE;
+
+ END LOOP;
+
+ CLOSE cur;
+
+ END IF;
+
+END;
+
+CALL proc_migrate_chastity_game_set();
+
+DROP PROCEDURE IF EXISTS proc_migrate_chastity_game_set;
diff --git a/bin/main/de/oaa/xxx/games/bdsm/AufgabeAnzeige.class b/bin/main/de/oaa/xxx/games/bdsm/AufgabeAnzeige.class
index 2c6c444..819da98 100644
Binary files a/bin/main/de/oaa/xxx/games/bdsm/AufgabeAnzeige.class and b/bin/main/de/oaa/xxx/games/bdsm/AufgabeAnzeige.class differ
diff --git a/bin/main/de/oaa/xxx/games/bdsm/controller/FinisherController.class b/bin/main/de/oaa/xxx/games/bdsm/controller/FinisherController.class
index 41de71e..fb0681c 100644
Binary files a/bin/main/de/oaa/xxx/games/bdsm/controller/FinisherController.class and b/bin/main/de/oaa/xxx/games/bdsm/controller/FinisherController.class differ
diff --git a/bin/main/de/oaa/xxx/games/bdsm/controller/SperreController.class b/bin/main/de/oaa/xxx/games/bdsm/controller/SperreController.class
index fb60e89..b81ca8a 100644
Binary files a/bin/main/de/oaa/xxx/games/bdsm/controller/SperreController.class and b/bin/main/de/oaa/xxx/games/bdsm/controller/SperreController.class differ
diff --git a/bin/main/de/oaa/xxx/games/chastity/common/GameState.class b/bin/main/de/oaa/xxx/games/chastity/common/GameState.class
new file mode 100644
index 0000000..4b3c8ba
Binary files /dev/null and b/bin/main/de/oaa/xxx/games/chastity/common/GameState.class differ
diff --git a/bin/main/de/oaa/xxx/games/chastity/common/LockGameController.class b/bin/main/de/oaa/xxx/games/chastity/common/LockGameController.class
new file mode 100644
index 0000000..85bfb11
Binary files /dev/null and b/bin/main/de/oaa/xxx/games/chastity/common/LockGameController.class differ
diff --git a/bin/main/de/oaa/xxx/games/chastity/common/LockGameEntity.class b/bin/main/de/oaa/xxx/games/chastity/common/LockGameEntity.class
index 7ffadf8..56bf323 100644
Binary files a/bin/main/de/oaa/xxx/games/chastity/common/LockGameEntity.class and b/bin/main/de/oaa/xxx/games/chastity/common/LockGameEntity.class differ
diff --git a/bin/main/de/oaa/xxx/games/chastity/common/LockGameLockEntity.class b/bin/main/de/oaa/xxx/games/chastity/common/LockGameLockEntity.class
index d88eab1..70bb24a 100644
Binary files a/bin/main/de/oaa/xxx/games/chastity/common/LockGameLockEntity.class and b/bin/main/de/oaa/xxx/games/chastity/common/LockGameLockEntity.class differ
diff --git a/bin/main/de/oaa/xxx/games/chastity/common/LockGameLockRepository.class b/bin/main/de/oaa/xxx/games/chastity/common/LockGameLockRepository.class
new file mode 100644
index 0000000..384c216
Binary files /dev/null and b/bin/main/de/oaa/xxx/games/chastity/common/LockGameLockRepository.class differ
diff --git a/bin/main/de/oaa/xxx/games/chastity/common/LockGameRepository.class b/bin/main/de/oaa/xxx/games/chastity/common/LockGameRepository.class
new file mode 100644
index 0000000..1d57c2a
Binary files /dev/null and b/bin/main/de/oaa/xxx/games/chastity/common/LockGameRepository.class differ
diff --git a/bin/main/de/oaa/xxx/games/chastity/common/LockGameService.class b/bin/main/de/oaa/xxx/games/chastity/common/LockGameService.class
index 71ebaae..841c367 100644
Binary files a/bin/main/de/oaa/xxx/games/chastity/common/LockGameService.class and b/bin/main/de/oaa/xxx/games/chastity/common/LockGameService.class differ
diff --git a/bin/main/de/oaa/xxx/games/common/aufgaben/Sperre.class b/bin/main/de/oaa/xxx/games/common/aufgaben/Sperre.class
index 0d02203..b92ef14 100644
Binary files a/bin/main/de/oaa/xxx/games/common/aufgaben/Sperre.class and b/bin/main/de/oaa/xxx/games/common/aufgaben/Sperre.class differ
diff --git a/bin/main/de/oaa/xxx/games/common/entity/SperreEntity.class b/bin/main/de/oaa/xxx/games/common/entity/SperreEntity.class
index 9df1c31..765dca3 100644
Binary files a/bin/main/de/oaa/xxx/games/common/entity/SperreEntity.class and b/bin/main/de/oaa/xxx/games/common/entity/SperreEntity.class differ
diff --git a/bin/main/de/oaa/xxx/games/common/repository/FinisherRepository.class b/bin/main/de/oaa/xxx/games/common/repository/FinisherRepository.class
index b2a20d8..fcc61d5 100644
Binary files a/bin/main/de/oaa/xxx/games/common/repository/FinisherRepository.class and b/bin/main/de/oaa/xxx/games/common/repository/FinisherRepository.class differ
diff --git a/bin/main/static/games/aufgaben/aufgaben.html b/bin/main/static/games/aufgaben/aufgaben.html
index 5085716..d7e7641 100644
--- a/bin/main/static/games/aufgaben/aufgaben.html
+++ b/bin/main/static/games/aufgaben/aufgaben.html
@@ -416,7 +416,7 @@
@@ -451,15 +451,17 @@
-
-
-
-
-
-
-
@@ -749,7 +751,7 @@
${g.beschreibung ? `
${esc(g.beschreibung)}
` : ''}
${renderSubSection('Aufgaben', sortByLevelThenName(g.aufgaben || []), 'aufgabe', renderAufgabe, g.gruppenId, type)}
- ${renderSubSection('Strafen', sortByLevelThenName(g.strafen || []), 'strafe', renderStrafe, g.gruppenId, type)}
+ ${g.availableIn !== 'CHASTITY_ONLY' ? renderSubSection('Strafen', sortByLevelThenName(g.strafen || []), 'strafe', renderStrafe, g.gruppenId, type) : ''}
${renderSubSection('Zeitstrafen',sortByName(g.sperren || []), 'zeitstrafe',renderZeitstrafe, g.gruppenId, type)}
${renderSubSection('Finisher', sortByGeschlecht(g.finisher || []), 'finisher', renderFinisher, g.gruppenId, type)}
@@ -1086,7 +1088,9 @@
pubCb.checked = !g.privateGruppe;
pubCb.disabled = g.privateGruppe; // Veröffentlichen nur über den Veröffentlichen-Button
document.getElementById('gPublicLabel').style.display = 'block';
- document.getElementById('gAvailableIn').value = g.availableIn || 'BDSM_ONLY';
+ const avSel = document.getElementById('gAvailableIn');
+ avSel.value = g.availableIn || 'BDSM_ONLY';
+ avSel.disabled = true;
const imgWrap = document.getElementById('gCurrentImgWrap');
if (g.bild) {
document.getElementById('gCurrentImg').src = 'data:image/png;base64,' + g.bild;
@@ -1104,7 +1108,8 @@
document.getElementById('gDesc').value = '';
document.getElementById('gPublic').checked = false;
document.getElementById('gPublicLabel').style.display = 'none';
- document.getElementById('gAvailableIn').value = 'BDSM_ONLY';
+ document.getElementById('gAvailableIn').value = 'BDSM_ONLY';
+ document.getElementById('gAvailableIn').disabled = false;
document.getElementById('gCurrentImgWrap').style.display = 'none';
gruppeModal.classList.add('open');
document.getElementById('gName').focus();
@@ -1113,7 +1118,8 @@
function closeGruppeModal() {
gruppeModal.classList.remove('open');
- document.getElementById('gPublic').disabled = false;
+ document.getElementById('gPublic').disabled = false;
+ document.getElementById('gAvailableIn').disabled = false;
}
document.getElementById('openCreateBtn').addEventListener('click', () => openModal(null));
@@ -1359,6 +1365,7 @@
let currentItemGruppeId = null;
let currentItemKind = null; // 'aufgabe' | 'strafe' | 'zeitstrafe'
let currentItemEditId = null; // null = neu, sonst ID des zu bearbeitenden Items
+ let _isChastityMode = false;
const ITEM_TITLES_NEW = { aufgabe: 'Aufgabe hinzufügen', strafe: 'Strafe hinzufügen', zeitstrafe: 'Zeitstrafe hinzufügen', finisher: 'Finisher hinzufügen' };
const ITEM_TITLES_EDIT = { aufgabe: 'Aufgabe bearbeiten', strafe: 'Strafe bearbeiten', zeitstrafe: 'Zeitstrafe bearbeiten', finisher: 'Finisher bearbeiten' };
@@ -1366,6 +1373,11 @@
function _setupItemModal(kind) {
const isZeit = kind === 'zeitstrafe';
const isFinisher = kind === 'finisher';
+ const isChastity = (_gruppeData[currentItemGruppeId]?.availableIn === 'CHASTITY_ONLY');
+ _isChastityMode = isChastity;
+
+ // Placeholder hint button + text
+ document.getElementById('iPlaceholderHintBtn').style.display = isChastity ? 'none' : '';
document.querySelector('#iPlaceholderHint .placeholder-hint').innerHTML =
isFinisher
? 'In Texten können Platzhalter verwendet werden:
' +
@@ -1374,15 +1386,38 @@
: 'In Texten können Platzhalter verwendet werden:
' +
'
{AKTIV} – Name des aktiven Parts
' +
'
{PASSIV} – Name des passiven Parts';
- document.getElementById('iGeschlechtRow').style.display = isFinisher ? 'block' : 'none';
- document.getElementById('iLevelRow').style.display = (!isZeit && !isFinisher) ? 'block' : 'none';
- document.getElementById('iWerkzeugAktivRow').style.display = (!isZeit && !isFinisher) ? 'block' : 'none';
- document.getElementById('iWerkzeugPassivRow').style.display = (!isZeit && !isFinisher) ? 'block' : 'none';
- document.getElementById('iWerkzeugFinisherAktivRow').style.display = isFinisher ? 'block' : 'none';
- document.getElementById('iWerkzeugFinisherPassivRow').style.display = isFinisher ? 'block' : 'none';
- document.getElementById('iMinutenRow').style.display = isZeit ? 'block' : 'none';
- document.getElementById('iSperreFuerRow').style.display = isZeit ? 'block' : 'none';
- document.getElementById('iReleaseTextRow').style.display = isZeit ? 'block' : 'none';
+
+ // Geschlecht: finisher only, and not in chastity
+ document.getElementById('iGeschlechtRow').style.display = (isFinisher && !isChastity) ? 'block' : 'none';
+
+ // Level row: aufgabe/strafe always; zeitstrafe only in chastity
+ document.getElementById('iLevelRow').style.display = (!isZeit && !isFinisher) || (isZeit && isChastity) ? 'block' : 'none';
+ // Sekunden sub-section: not for chastity zeitstrafe
+ document.getElementById('iSekRow').style.display = (!isZeit && !isFinisher) ? 'block' : 'none';
+
+ // Aktiv Werkzeuge: aufgabe/strafe only
+ document.getElementById('iWerkzeugAktivRow').style.display = (!isZeit && !isFinisher) ? 'block' : 'none';
+ document.querySelector('#iWerkzeugAktivRow label').textContent = isChastity ? 'Benötigt' : 'Benötigt (aktiv)';
+ ['VAGINA', 'PENIS', 'UMSCHNALLDILDO'].forEach(v => {
+ const lbl = document.querySelector(`#iWerkzeugAktiv input[value="${v}"]`)?.closest('label');
+ if (lbl) lbl.style.display = isChastity ? 'none' : '';
+ });
+
+ // Passiv Werkzeuge: aufgabe/strafe, not in chastity
+ document.getElementById('iWerkzeugPassivRow').style.display = (!isZeit && !isFinisher && !isChastity) ? 'block' : 'none';
+
+ // Finisher Werkzeuge: not in chastity
+ document.getElementById('iWerkzeugFinisherAktivRow').style.display = (isFinisher && !isChastity) ? 'block' : 'none';
+ 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';
+ ['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';
}
function _resetItemFields() {
@@ -1449,6 +1484,9 @@
document.getElementById('iMinBis').value = d.minutenBis != null ? d.minutenBis : '';
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 : '';
+ }
}
const preSelected = (d.benoetigteToys || []).filter(t => t.toyId);
@@ -1480,7 +1518,7 @@
}
function buildItems(filter) {
const f = filter || '';
- _items = STATIC.filter(s => !f || s.toLowerCase().includes(f)).map(s => ({ label: s, insert: s }));
+ _items = (_isChastityMode ? [] : STATIC.filter(s => !f || s.toLowerCase().includes(f))).map(s => ({ label: s, insert: s }));
const toys = _allToys.filter(t => !f || t.name.toLowerCase().includes(f));
if (toys.length) {
_items.push({ separator: true, label: 'Toys' });
@@ -1606,13 +1644,14 @@
method = isEdit ? 'PUT' : 'POST';
} else if (kind === 'finisher') {
- const geschlecht = document.querySelector('#iGeschlecht input:checked')?.value;
- if (!geschlecht) { showItemError('Bitte ein Geschlecht auswählen.'); return; }
+ const geschlecht = _isChastityMode ? null : document.querySelector('#iGeschlecht input:checked')?.value;
+ if (!_isChastityMode && !geschlecht) { showItemError('Bitte ein Geschlecht auswählen.'); return; }
payload = {
- kurzText, text, geschlecht,
+ kurzText, text,
+ geschlecht: geschlecht || null,
gruppeId: isEdit ? undefined : currentItemGruppeId,
- benoetigtAktiv: checkedValues('iWerkzeugFinisherAktiv'),
- benoetigtPassiv: checkedValues('iWerkzeugFinisherPassiv'),
+ benoetigtAktiv: _isChastityMode ? [] : checkedValues('iWerkzeugFinisherAktiv'),
+ benoetigtPassiv: _isChastityMode ? [] : checkedValues('iWerkzeugFinisherPassiv'),
benoetigteToys: _selectedToys.map(t => ({ toyId: t.toyId }))
};
url = isEdit ? apiUrl(`/finisher/${currentItemEditId}`) : apiUrl('/finisher');
@@ -1624,6 +1663,14 @@
const sperreFuer = checkedValues('iSperreFuer');
if (sperreFuer.length === 0) { showItemError('Bitte mindestens ein Werkzeug für die Sperre auswählen.'); return; }
+ let zeitLevel = null;
+ if (_isChastityMode) {
+ const lv = document.getElementById('iLevel').value.trim();
+ if (!lv) { showItemError('Bitte ein Level angeben.'); return; }
+ zeitLevel = parseInt(lv, 10);
+ if (isNaN(zeitLevel) || zeitLevel < 1 || zeitLevel > 5) { showItemError('Level muss zwischen 1 und 5 liegen.'); return; }
+ }
+
const minBis = document.getElementById('iMinBis').value.trim();
payload = {
kurzText, text,
@@ -1632,6 +1679,7 @@
minutenBis: minBis ? parseInt(minBis, 10) : null,
releaseText: document.getElementById('iReleaseText').value.trim() || null,
sperreFuer,
+ level: zeitLevel,
benoetigteToys: _selectedToys.map(t => ({ toyId: t.toyId }))
};
url = isEdit ? `/sperre/${currentItemEditId}` : '/sperre'; // BDSM-only
diff --git a/bin/main/static/games/chastity/activelock.html b/bin/main/static/games/chastity/activelock.html
index 89626ad..f1a6896 100644
--- a/bin/main/static/games/chastity/activelock.html
+++ b/bin/main/static/games/chastity/activelock.html
@@ -1711,6 +1711,14 @@
document.getElementById('btnDrawOk').style.display = 'none';
document.getElementById('btnSpeedConfirm').style.display = '';
}
+
+ 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;
+ };
+ }
}, 700);
}, 1000);
})
diff --git a/bin/main/static/games/chastity/meine-locks.html b/bin/main/static/games/chastity/meine-locks.html
index a6b3328..1136b67 100644
--- a/bin/main/static/games/chastity/meine-locks.html
+++ b/bin/main/static/games/chastity/meine-locks.html
@@ -268,47 +268,6 @@
.sim-stat-val { font-size:1rem; font-weight:700; color:var(--color-text); }
.sim-stat-lbl { font-size:0.7rem; color:var(--color-muted); margin-top:0.15rem; text-transform:uppercase; letter-spacing:0.05em; }
- /* ── Spiel-Sets (gruppe-card style) ── */
- .gs-card { background:var(--color-card); border:1px solid var(--color-secondary); border-radius:10px; overflow:hidden; cursor:pointer; transition:border-color 0.15s; }
- .gs-card:hover { border-color:var(--color-primary); }
- .gs-card-header { display:flex; align-items:center; gap:0.75rem; padding:0.85rem 1rem; user-select:none; }
- .gs-card-meta { flex:1; min-width:0; }
- .gs-card-name { font-size:0.95rem; font-weight:600; color:var(--color-text); }
- .gs-card-header-actions { display:flex; gap:0.4rem; flex-shrink:0; }
-
- .gs-sub { margin-bottom:0.85rem; }
- .gs-sub:last-child { margin-bottom:0; }
- .gs-sub-header { display:flex; align-items:center; justify-content:space-between; margin-bottom:0.35rem; }
- .gs-sub-title { font-size:0.7rem; font-weight:700; letter-spacing:0.06em; text-transform:uppercase; color:var(--color-primary); }
- .gs-sub-warn { color:#e74c3c !important; }
-
- .gs-item-list { display:flex; flex-direction:column; gap:0.25rem; }
- .gs-list-item { border-radius:6px; background:var(--color-secondary); overflow:hidden; }
- .gs-list-item-row { display:flex; align-items:center; gap:0.5rem; padding:0.3rem 0.5rem 0.3rem 0.75rem; cursor:pointer; user-select:none; transition:background 0.12s; }
- .gs-list-item-row:hover { background:rgba(255,255,255,0.04); }
- .gs-list-item.open .gs-list-item-row { background:rgba(233,69,96,0.08); }
- .gs-list-item-text { color:var(--color-text); flex:1; min-width:0; font-size:0.83rem; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
- .gs-list-item-badges { display:flex; gap:0.3rem; flex-shrink:0; align-items:center; }
- .gs-badge { font-size:0.68rem; padding:0.1rem 0.4rem; border-radius:20px; background:rgba(233,69,96,0.15); color:var(--color-primary); white-space:nowrap; }
- .gs-badge-neutral { background:rgba(255,255,255,0.07); color:var(--color-muted); }
- .gs-list-item-detail { display:none; padding:0.4rem 0.75rem 0.5rem; border-top:1px solid rgba(255,255,255,0.06); font-size:0.79rem; color:var(--color-muted); line-height:1.5; }
- .gs-list-item.open .gs-list-item-detail { display:block; }
- .gs-detail-text { color:var(--color-text); white-space:pre-wrap; margin-bottom:0.3rem; }
- .gs-detail-actions { display:flex; gap:0.35rem; margin-top:0.4rem; justify-content:flex-end; }
- .gs-btn-sub-add { background:none; border:1px solid var(--color-secondary); border-radius:5px; color:var(--color-muted); font-size:0.73rem; padding:0.15rem 0.5rem; cursor:pointer; transition:border-color 0.15s,color 0.15s; width:auto; }
- .gs-btn-sub-add:hover { border-color:var(--color-primary); color:var(--color-primary); }
- .gs-btn-item-edit { background:none; border:1px solid rgba(136,136,136,0.45); border-radius:5px; color:var(--color-muted); font-size:0.73rem; padding:0.18rem 0.55rem; cursor:pointer; transition:border-color 0.15s,color 0.15s; width:auto; }
- .gs-btn-item-edit:hover { border-color:var(--color-text); color:var(--color-text); }
- .gs-btn-item-delete { background:none; border:1px solid rgba(233,69,96,0.4); border-radius:5px; color:var(--color-primary); font-size:0.73rem; padding:0.18rem 0.55rem; cursor:pointer; transition:background 0.15s; width:auto; }
- .gs-btn-item-delete:hover { background:rgba(233,69,96,0.15); }
- .gs-sub-empty { font-size:0.78rem; color:var(--color-muted); padding:0.15rem 0; }
-
- #gsSetModal, #gsItemModal { z-index:600; }
-
- .gs-check-group { display:flex; flex-wrap:wrap; gap:0.4rem; }
- .gs-check-chip { display:inline-flex; align-items:center; gap:0.4rem; background:var(--color-secondary); border:1px solid rgba(255,255,255,0.1); border-radius:20px; padding:0.25rem 0.7rem; cursor:pointer; font-size:0.82rem; color:var(--color-text); transition:border-color 0.15s; user-select:none; }
- .gs-check-chip:has(input:checked) { border-color:var(--color-primary); }
- .gs-check-chip input { accent-color:var(--color-primary); width:auto; cursor:pointer; margin:0; flex-shrink:0; }
@@ -329,17 +288,6 @@
Noch keine Aufgaben-Sets vorhanden.
-
-
-
-
Spiel-Sets
-
Aufgaben-Sets für die Spiel-Karte im Karten-Lock · max. 5 Sets
-
-
-
-
-
Noch keine Spiel-Sets vorhanden.
-
Abonnierte Vorlagen
Keine abonnierten Vorlagen vorhanden.
@@ -443,24 +391,6 @@
-
-
-
-
-
-
Neues Spiel-Set
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Aufgabe
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- –
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -1223,51 +1045,6 @@
if (res.ok || res.status === 204) await loadTaskSets();
}
- const GS_TOOLS = [
- { value: 'UMSCHNALLDILDO', label: 'Strap-on' },
- { value: 'MUND', label: 'Oral' },
- { value: 'ANUS', label: 'Anal' },
- ];
- function gsGetChecked(prefix) {
- return GS_TOOLS.filter(t => document.getElementById(prefix + t.value)?.checked).map(t => t.value);
- }
- function gsSetChecked(prefix, values) {
- GS_TOOLS.forEach(t => {
- const el = document.getElementById(prefix + t.value);
- if (el) el.checked = (values || []).includes(t.value);
- });
- }
-
- const GAME_SPIELDAUER = [
- { label: 'Sehr kurz' },
- { label: 'Kurz' },
- { label: 'Mittel' },
- { label: 'Lang' },
- { label: 'Sehr lang' },
- ];
-
- function populateGameSetSelect() {
- const sel = document.getElementById('fGameSetId');
- if (!sel) return;
- const cur = sel.value;
- sel.innerHTML = '
';
- _gameSets.forEach(s => {
- const opt = document.createElement('option');
- opt.value = s.id; opt.textContent = s.name;
- sel.appendChild(opt);
- });
- sel.value = cur;
- }
-
- function onGameSetChange() {
- const val = document.getElementById('fGameSetId')?.value;
- document.getElementById('gameSetSpieldauerRow').style.display = val ? '' : 'none';
- }
-
- function updateGameSpieldauer(val) {
- document.getElementById('valGameSpieldauer').textContent = GAME_SPIELDAUER[val]?.label || '';
- }
-
function populateTaskSetSelects() {
for (const selId of ['fCardTaskSetId', 'fTimelockTaskSetId']) {
const sel = document.getElementById(selId);
@@ -1476,14 +1253,6 @@
onTaskSetChange('card');
onTaskSetChange('timelock');
- // Spiel-Set
- populateGameSetSelect();
- document.getElementById('fGameSetId').value = template?.gameSetId || '';
- onGameSetChange();
- const sdIdx = template?.gameSpieldauerIdx ?? 2;
- document.getElementById('sldGameSpieldauer').value = sdIdx;
- updateGameSpieldauer(sdIdx);
-
alignModalToContent();
document.getElementById('modalBackdrop').classList.add('open');
document.getElementById('modalDiscardConfirm').style.display = 'none';
@@ -1527,13 +1296,7 @@
document.getElementById('modalBackdrop').addEventListener('click', e => { if (e.target===e.currentTarget) tryCloseModal(); });
document.addEventListener('keydown', e => {
if (e.key !== 'Escape') return;
- if (document.getElementById('gsItemModal').classList.contains('open')) {
- e.preventDefault(); closeGsItemModal();
- } else if (document.getElementById('gsSetModal').classList.contains('open')) {
- e.preventDefault(); closeGsSetModal();
- } else if (document.getElementById('gsEditModal').classList.contains('open')) {
- e.preventDefault(); closeGsEditModal();
- } else if (document.getElementById('taskSetModalBackdrop').classList.contains('open')) {
+ if (document.getElementById('taskSetModalBackdrop').classList.contains('open')) {
e.preventDefault(); tryCloseTaskSetModal();
} else if (document.getElementById('modalBackdrop').classList.contains('open')) {
e.preventDefault(); tryCloseModal();
@@ -1588,9 +1351,6 @@
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'); }
- 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) { showModalError('Spiel-Karten konfiguriert, aber kein Spiel-Set ausgewählt.'); firstError=firstError||document.getElementById('modalError'); }
-
if (firstError) { firstError.scrollIntoView({behavior:'smooth',block:'center'}); return; }
const cardCountsMin={}, cardCountsMax={};
@@ -1609,8 +1369,6 @@
taskSetId: document.getElementById('fCardTaskSetId').value || null,
requiresVerification: document.getElementById('fRequiresVerification').checked,
taskMode: document.querySelector('input[name="modalCardTaskMode"]:checked')?.value||'RANDOM',
- gameSetId: document.getElementById('fGameSetId').value || null,
- gameSpieldauerIdx: parseInt(document.getElementById('sldGameSpieldauer').value) || 2,
};
} else {
// TimeLock
@@ -1895,426 +1653,8 @@
observer.observe(document.getElementById('scrollSentinel'));
resetList();
- loadGameSets();
- // ════════════════════════════════════════════════
- // Spiel-Sets
- // ════════════════════════════════════════════════
- let _gameSets = [];
- let _gsEditSetId = null; // set being renamed
- let _gsSetCaller = null; // 'template' when opened from the template modal
- let _gsOpenSetId = null; // set currently open in the content popup
- let _gsItemType = null; // 'aufgabe' | 'zeitstrafe' | 'finisher'
- let _gsItemSetId = null;
- let _gsItemIdx = null; // null = new, number = editing
-
- async function loadGameSets() {
- try {
- const res = await fetch('/chastity/game-sets');
- if (!res.ok) return;
- _gameSets = await res.json();
- renderGameSetList();
- populateGameSetSelect();
- if (_gsOpenSetId) renderGsEditModalContent(_gsOpenSetId);
- } catch (e) { console.error(e); }
- }
-
- function renderGameSetList() {
- const list = document.getElementById('gameSetList');
- list.innerHTML = '';
- document.getElementById('gameSetEmpty').style.display = _gameSets.length ? 'none' : '';
- document.getElementById('btnNewGameSet').disabled = _gameSets.length >= 5;
-
- _gameSets.forEach(s => {
- const aufgaben = s.aufgaben || [];
- const zeitstrafen = s.zeitstrafen || [];
- const finisher = s.finisher || [];
- const levelCounts = [1,2,3,4,5].map(l => aufgaben.filter(a => a.level === l).length);
-
- const lvlBadges = levelCounts.map((c, i) => {
- const cls = c >= 3 ? 'gs-badge gs-badge-neutral' : 'gs-badge';
- return `
L${i+1}: ${c}`;
- }).join('');
- const finBadgeCls = finisher.length >= 1 ? 'gs-badge gs-badge-neutral' : 'gs-badge';
-
- const card = document.createElement('div');
- card.className = 'gs-card';
- card.id = 'gscard_' + s.id;
- card.addEventListener('click', () => openGsEditModal(s.id));
- card.innerHTML = `
- `;
- list.appendChild(card);
- });
- }
-
- function toggleGsListItem(id) {
- document.getElementById(id)?.classList.toggle('open');
- }
-
- // ── Set content popup ──────────────────────────
-
- function openGsEditModal(setId) {
- _gsOpenSetId = setId;
- renderGsEditModalContent(setId);
- document.getElementById('gsEditModal').classList.add('open');
- }
-
- function closeGsEditModal() {
- document.getElementById('gsEditModal').classList.remove('open');
- _gsOpenSetId = null;
- }
-
- function renderGsEditModalContent(setId) {
- const container = document.getElementById('gsEditModalContent');
- if (!container) return;
- const s = _gameSets.find(x => x.id === setId);
- if (!s) { closeGsEditModal(); return; }
- document.getElementById('gsEditModalTitle').textContent = s.name;
- const aufgaben = s.aufgaben || [];
- const zeitstrafen = s.zeitstrafen || [];
- const finisher = s.finisher || [];
- let html = '';
- for (let l = 1; l <= 5; l++) {
- const items = aufgaben.map((a, i) => ({...a, _gi: i})).filter(a => a.level === l);
- const warnCls = items.length < 3 ? ' gs-sub-warn' : '';
- const itemsHtml = items.map(a => gsAufgabeRowHtml(s.id, a._gi, a)).join('') ||
- '
–
';
- html += `
`;
- }
- const zeitHtml = zeitstrafen.map((z, i) => gsZeitstrafeRowHtml(s.id, i, z)).join('') ||
- '
–
';
- html += `
`;
- const finWarnCls = finisher.length < 1 ? ' gs-sub-warn' : '';
- const finHtml = finisher.map((f, i) => gsFinisherRowHtml(s.id, i, f)).join('') ||
- '
–
';
- html += `
`;
- container.innerHTML = html;
- }
-
- // ── Row HTML helpers ───────────────────────────
-
- function gsAufgabeRowHtml(setId, gi, a) {
- const toolLabels = (a.benoetigt || []).map(v => GS_TOOLS.find(t => t.value === v)?.label).filter(Boolean);
- const badges = [
- a.minutes ? `
${a.minutes} Min.` : '',
- ...toolLabels.map(l => `
${l}`),
- ].join('');
- const desc = a.description ? `
${esc(a.description)}
` : '';
- return `
-
-
${esc(a.title)}
-
${badges}
-
-
${desc}
-
-
-
-
-
-
`;
- }
-
- function gsZeitstrafeRowHtml(setId, idx, z) {
- const timeStr = (z.minMinutes != null ? z.minMinutes : '?') + '–' + (z.maxMinutes != null ? z.maxMinutes : '?') + ' Min.';
- const sperrtLabels = (z.sperrt || []).map(v => GS_TOOLS.find(t => t.value === v)?.label).filter(Boolean);
- const badges = [
- z.level ? `
L${z.level}` : '',
- `
${timeStr}`,
- ...sperrtLabels.map(l => `
🔒 ${l}`),
- z.releaseText ? `
📝 Aufhebung` : '',
- z.tempUnlockBeforeRequired ? `
🔓 Vorher` : '',
- z.tempUnlockAfterRequired ? `
🔓 Nachher` : '',
- ].join('');
- const releaseRow = z.releaseText ? `
Bei Aufhebung:
${esc(z.releaseText)}
` : '';
- const desc = z.description ? `
${esc(z.description)}
` : '';
- return `
-
-
${esc(z.title)}
-
${badges}
-
-
${desc}${releaseRow}
-
-
-
-
-
-
`;
- }
-
- function gsFinisherRowHtml(setId, idx, f) {
- const badges = [
- f.tempUnlockBeforeRequired ? `
🔓 Vorher` : '',
- f.tempUnlockAfterRequired ? `
🔓 Nachher` : '',
- ].join('');
- const desc = f.description ? `
${esc(f.description)}
` : '';
- return `
-
-
${esc(f.title)}
-
${badges}
-
-
${desc}
-
-
-
-
-
-
`;
- }
-
- // ── Set create / rename modal ──────────────────
-
- function openGsSetModal(id, caller) {
- _gsEditSetId = id || null;
- _gsSetCaller = caller || null;
- document.getElementById('gsSetModalTitle').textContent = id ? 'Spiel-Set umbenennen' : 'Neues Spiel-Set';
- document.getElementById('gsSetName').value = id ? (_gameSets.find(s => s.id === id)?.name || '') : '';
- document.getElementById('gsSetError').style.display = 'none';
- document.getElementById('gsSetModal').classList.add('open');
- setTimeout(() => document.getElementById('gsSetName').focus(), 50);
- }
-
- function closeGsSetModal() {
- document.getElementById('gsSetModal').classList.remove('open');
- _gsEditSetId = _gsSetCaller = null;
- }
-
- async function saveGsSet() {
- const name = document.getElementById('gsSetName').value.trim();
- const errEl = document.getElementById('gsSetError');
- if (!name) { errEl.textContent = 'Name ist ein Pflichtfeld.'; errEl.style.display = ''; return; }
- errEl.style.display = 'none';
- const set = _gsEditSetId ? _gameSets.find(s => s.id === _gsEditSetId) : null;
- const url = _gsEditSetId ? `/chastity/game-sets/${_gsEditSetId}` : '/chastity/game-sets';
- const method = _gsEditSetId ? 'PUT' : 'POST';
- const body = { name,
- aufgaben: set?.aufgaben || [],
- zeitstrafen: set?.zeitstrafen || [],
- finisher: set?.finisher || [] };
- try {
- const res = await fetch(url, { method, headers: {'Content-Type':'application/json'}, body: JSON.stringify(body) });
- if (res.ok) {
- const saved = await res.json().catch(() => null);
- const caller = _gsSetCaller;
- closeGsSetModal();
- await loadGameSets();
- if (caller === 'template' && saved?.id) {
- document.getElementById('fGameSetId').value = saved.id;
- onGameSetChange();
- markDirty();
- }
- return;
- }
- const b = await res.json().catch(() => ({}));
- errEl.textContent = b.error || 'Fehler.'; errEl.style.display = '';
- } catch (e) { errEl.textContent = 'Netzwerkfehler.'; errEl.style.display = ''; }
- }
-
- async function deleteGameSet(id, name) {
- if (!confirm(`Spiel-Set „${name}" wirklich löschen?`)) return;
- const res = await fetch(`/chastity/game-sets/${id}`, { method: 'DELETE' });
- if (res.ok || res.status === 204) loadGameSets();
- }
-
- // ── Item modal ─────────────────────────────────
-
- function openGsItemModal(type, setId, itemIdx, contextLevel) {
- _gsItemType = type;
- _gsItemSetId = setId;
- _gsItemIdx = itemIdx !== null && itemIdx !== undefined ? itemIdx : null;
-
- const titles = { aufgabe: 'Aufgabe', zeitstrafe: 'Zeitstrafe', finisher: 'Finisher' };
- document.getElementById('gsItemModalTitle').textContent =
- (_gsItemIdx !== null ? 'Bearbeiten: ' : 'Neu: ') + titles[type];
-
- // Reset fields
- document.getElementById('gsItemTitle').value = '';
- document.getElementById('gsItemDesc').value = '';
- document.getElementById('gsItemMinutes').value = '';
- document.getElementById('gsItemMinMin').value = '';
- document.getElementById('gsItemMaxMin').value = '';
- document.getElementById('gsItemReleaseText').value = '';
- document.getElementById('gsItemBefore').checked = false;
- document.getElementById('gsItemAfter').checked = false;
- document.getElementById('gsItemAufgabeLevel').value = contextLevel || 1;
- document.getElementById('gsItemZeitstrafeLevel').value = 1;
- document.getElementById('gsItemError').style.display = 'none';
- gsSetChecked('gsItemBen_', []);
- gsSetChecked('gsItemSperr_', []);
-
- // Show/hide type-specific rows
- document.getElementById('gsItemAufgabeRow').style.display = type === 'aufgabe' ? '' : 'none';
- document.getElementById('gsItemBenoetigtRow').style.display = type === 'aufgabe' ? '' : 'none';
- document.getElementById('gsItemZeitstrafeRow').style.display = type === 'zeitstrafe' ? '' : 'none';
- document.getElementById('gsItemSperrtRow').style.display = type === 'zeitstrafe' ? '' : 'none';
- document.getElementById('gsItemUnlockRow').style.display = (type === 'zeitstrafe' || type === 'finisher') ? '' : 'none';
-
- // Pre-fill when editing
- if (_gsItemIdx !== null) {
- const set = _gameSets.find(s => s.id === setId);
- if (set) {
- let item;
- if (type === 'aufgabe') item = set.aufgaben[_gsItemIdx];
- if (type === 'zeitstrafe') item = set.zeitstrafen[_gsItemIdx];
- if (type === 'finisher') item = set.finisher[_gsItemIdx];
- if (item) {
- document.getElementById('gsItemTitle').value = item.title || '';
- document.getElementById('gsItemDesc').value = item.description || '';
- if (type === 'aufgabe') {
- document.getElementById('gsItemAufgabeLevel').value = item.level || 1;
- document.getElementById('gsItemMinutes').value = item.minutes || '';
- gsSetChecked('gsItemBen_', item.benoetigt || []);
- }
- if (type === 'zeitstrafe') {
- document.getElementById('gsItemZeitstrafeLevel').value = item.level || 1;
- document.getElementById('gsItemMinMin').value = item.minMinutes ?? '';
- document.getElementById('gsItemMaxMin').value = item.maxMinutes ?? '';
- document.getElementById('gsItemReleaseText').value = item.releaseText || '';
- gsSetChecked('gsItemSperr_', item.sperrt || []);
- }
- if (type === 'zeitstrafe' || type === 'finisher') {
- document.getElementById('gsItemBefore').checked = !!item.tempUnlockBeforeRequired;
- document.getElementById('gsItemAfter').checked = !!item.tempUnlockAfterRequired;
- }
- }
- }
- }
-
- document.getElementById('gsItemModal').classList.add('open');
- setTimeout(() => document.getElementById('gsItemTitle').focus(), 50);
- }
-
- function closeGsItemModal() {
- document.getElementById('gsItemModal').classList.remove('open');
- _gsItemType = _gsItemSetId = _gsItemIdx = null;
- }
-
- async function saveGsItem() {
- const title = document.getElementById('gsItemTitle').value.trim();
- const errEl = document.getElementById('gsItemError');
- if (!title) { errEl.textContent = 'Titel ist ein Pflichtfeld.'; errEl.style.display = ''; return; }
- errEl.style.display = 'none';
-
- const set = _gameSets.find(s => s.id === _gsItemSetId);
- if (!set) return;
- const updated = {
- name: set.name,
- aufgaben: JSON.parse(JSON.stringify(set.aufgaben || [])),
- zeitstrafen: JSON.parse(JSON.stringify(set.zeitstrafen || [])),
- finisher: JSON.parse(JSON.stringify(set.finisher || [])),
- };
-
- const desc = document.getElementById('gsItemDesc').value.trim() || null;
- let item;
- if (_gsItemType === 'aufgabe') {
- const min = parseInt(document.getElementById('gsItemMinutes').value);
- const ben = gsGetChecked('gsItemBen_');
- item = { title, description: desc,
- level: parseInt(document.getElementById('gsItemAufgabeLevel').value) || 1,
- minutes: isNaN(min) ? null : min,
- benoetigt: ben.length ? ben : null };
- if (_gsItemIdx !== null) updated.aufgaben[_gsItemIdx] = item;
- else updated.aufgaben.push(item);
- } else if (_gsItemType === 'zeitstrafe') {
- const minMin = parseInt(document.getElementById('gsItemMinMin').value);
- const maxMin = parseInt(document.getElementById('gsItemMaxMin').value);
- const sperrt = gsGetChecked('gsItemSperr_');
- const releaseText = document.getElementById('gsItemReleaseText').value.trim() || null;
- item = { title, description: desc,
- level: parseInt(document.getElementById('gsItemZeitstrafeLevel').value) || 1,
- minMinutes: isNaN(minMin) ? null : minMin,
- maxMinutes: isNaN(maxMin) ? null : maxMin,
- releaseText,
- tempUnlockBeforeRequired: document.getElementById('gsItemBefore').checked,
- tempUnlockAfterRequired: document.getElementById('gsItemAfter').checked,
- sperrt: sperrt.length ? sperrt : null };
- if (_gsItemIdx !== null) updated.zeitstrafen[_gsItemIdx] = item;
- else updated.zeitstrafen.push(item);
- } else if (_gsItemType === 'finisher') {
- item = { title, description: desc,
- tempUnlockBeforeRequired: document.getElementById('gsItemBefore').checked,
- tempUnlockAfterRequired: document.getElementById('gsItemAfter').checked };
- if (_gsItemIdx !== null) updated.finisher[_gsItemIdx] = item;
- else updated.finisher.push(item);
- }
-
- try {
- const res = await fetch(`/chastity/game-sets/${_gsItemSetId}`, {
- method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify(updated)
- });
- if (res.ok) { closeGsItemModal(); await loadGameSets(); }
- else { const b = await res.json().catch(()=>({})); errEl.textContent = b.error||'Fehler.'; errEl.style.display = ''; }
- } catch (e) { errEl.textContent = 'Netzwerkfehler.'; errEl.style.display = ''; }
- }
-
- async function deleteGsItem(type, setId, idx) {
- if (!confirm('Eintrag wirklich löschen?')) return;
- const set = _gameSets.find(s => s.id === setId);
- if (!set) return;
- const updated = {
- name: set.name,
- aufgaben: JSON.parse(JSON.stringify(set.aufgaben || [])),
- zeitstrafen: JSON.parse(JSON.stringify(set.zeitstrafen || [])),
- finisher: JSON.parse(JSON.stringify(set.finisher || [])),
- };
- if (type === 'aufgabe') updated.aufgaben.splice(idx, 1);
- if (type === 'zeitstrafe') updated.zeitstrafen.splice(idx, 1);
- if (type === 'finisher') updated.finisher.splice(idx, 1);
- const res = await fetch(`/chastity/game-sets/${setId}`, {
- method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify(updated)
- });
- if (res.ok) loadGameSets();
- }
-
- async function duplicateGsItem(type, setId, idx) {
- const set = _gameSets.find(s => s.id === setId);
- if (!set) return;
- const updated = {
- name: set.name,
- aufgaben: JSON.parse(JSON.stringify(set.aufgaben || [])),
- zeitstrafen: JSON.parse(JSON.stringify(set.zeitstrafen || [])),
- finisher: JSON.parse(JSON.stringify(set.finisher || [])),
- };
- if (type === 'aufgabe') updated.aufgaben.splice(idx + 1, 0, JSON.parse(JSON.stringify(set.aufgaben[idx])));
- if (type === 'zeitstrafe') updated.zeitstrafen.splice(idx + 1, 0, JSON.parse(JSON.stringify(set.zeitstrafen[idx])));
- if (type === 'finisher') updated.finisher.splice(idx + 1, 0, JSON.parse(JSON.stringify(set.finisher[idx])));
- const res = await fetch(`/chastity/game-sets/${setId}`, {
- method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify(updated)
- });
- if (res.ok) loadGameSets();
- }
-
- document.getElementById('gsSetModal').addEventListener('click', e => { if (e.target === e.currentTarget) closeGsSetModal(); });
- document.getElementById('gsItemModal').addEventListener('click', e => { if (e.target === e.currentTarget) closeGsItemModal(); });
- document.getElementById('gsEditModal').addEventListener('click', e => { if (e.target === e.currentTarget) closeGsEditModal(); });