BDSM Game umgesetzt, Community Features ergänzt

This commit is contained in:
2026-03-03 23:18:35 +01:00
parent abf85f66e4
commit 21c276e96f
140 changed files with 9552 additions and 2841 deletions

View File

@@ -7,70 +7,6 @@
<link rel="stylesheet" href="/css/variables.css">
<link rel="stylesheet" href="/css/style.css">
<style>
body { display: block; min-height: 100vh; }
.layout { display: flex; min-height: 100vh; }
/* ── Sidebar ── */
.sidebar {
width: 240px; background: var(--color-card);
border-right: 1px solid var(--color-secondary);
display: flex; flex-direction: column;
position: fixed; top: 0; left: 0;
height: 100vh; overflow-y: auto;
z-index: 100; transition: transform 0.25s ease;
}
.sidebar-brand {
color: var(--color-primary); font-size: 1.1rem; font-weight: 700;
padding: 1.25rem 1.25rem 1rem;
border-bottom: 1px solid var(--color-secondary); flex-shrink: 0;
}
.sidebar ul { list-style: none; padding: 0.5rem 0; }
.sidebar ul li a {
display: flex; align-items: center; gap: 0.75rem;
padding: 0.7rem 1.25rem; color: var(--color-text);
text-decoration: none; font-size: 0.95rem;
border-left: 3px solid transparent;
transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.sidebar ul li a:hover, .sidebar ul li a.active {
background: var(--color-secondary); color: var(--color-primary);
border-left-color: var(--color-primary);
}
.sidebar ul li a .icon { font-size: 1rem; width: 1.2rem; text-align: center; flex-shrink: 0; }
/* ── Main ── */
.main { margin-left: 240px; flex: 1; display: flex; flex-direction: column; min-height: 100vh; }
/* ── Topbar ── */
.topbar {
display: flex; align-items: center; gap: 1rem;
padding: 0.9rem 1.5rem; background: var(--color-card);
border-bottom: 1px solid var(--color-secondary);
position: sticky; top: 0; z-index: 50;
}
.topbar h1 { font-size: 1.2rem; font-weight: 600; }
.burger {
display: none; background: none; border: none; cursor: pointer;
color: var(--color-text); padding: 0.25rem 0.4rem;
border-radius: 4px; transition: background 0.15s; flex-shrink: 0;
}
.burger:hover { background: var(--color-secondary); }
.burger-icon { display: flex; flex-direction: column; gap: 5px; width: 22px; }
.burger-icon span {
display: block; height: 2px; background: var(--color-text);
border-radius: 2px; transition: transform 0.25s, opacity 0.25s;
}
.burger.open .burger-icon span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.burger.open .burger-icon span:nth-child(2) { opacity: 0; }
.burger.open .burger-icon span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
.content { padding: 2rem 1.5rem; flex: 1; }
.overlay {
display: none; position: fixed; inset: 0;
background: rgba(0,0,0,0.55); z-index: 90;
}
/* ── Section ── */
.section + .section { margin-top: 2.5rem; }
.section-header {
@@ -272,6 +208,25 @@
.modal-actions .btn-save:disabled { opacity: 0.5; cursor: default; }
.modal-error { color: var(--color-primary); font-size: 0.82rem; margin-top: 0.75rem; display: none; }
/* ── Placeholder-Hint ── */
.label-with-hint { display: flex; align-items: center; gap: 0.4rem; }
.btn-hint {
background: none; border: 1px solid rgba(136,136,136,0.4); border-radius: 50%;
color: var(--color-muted); font-size: 0.7rem; font-style: italic; font-weight: 700;
width: 16px; height: 16px; line-height: 1; padding: 0;
cursor: pointer; flex-shrink: 0; transition: border-color 0.15s, color 0.15s;
}
.btn-hint:hover { border-color: var(--color-primary); color: var(--color-primary); }
.placeholder-hint {
background: rgba(255,255,255,0.04); border: 1px solid rgba(136,136,136,0.25);
border-radius: 6px; padding: 0.5rem 0.7rem; margin-bottom: 0.3rem;
font-size: 0.78rem; color: var(--color-muted); line-height: 1.6;
}
.placeholder-hint code {
background: rgba(233,69,96,0.12); color: var(--color-primary);
border-radius: 3px; padding: 0.05rem 0.3rem; font-size: 0.75rem;
}
/* ── Item-Add-Modal extra ── */
.modal-two-col { display: flex; gap: 0.75rem; }
.modal-two-col > * { flex: 1; }
@@ -326,8 +281,7 @@
}
.btn-toy-add:hover { border-color: var(--color-primary); color: var(--color-primary); }
/* ── Toy-Suchmodal ── */
#toySearchModal .modal-box { max-width: 420px; }
/* ── Toy-Suche (inline) ── */
.toy-search-input {
width: 100%; box-sizing: border-box; padding: 0.45rem 0.7rem;
background: var(--color-secondary); border: 1px solid rgba(136,136,136,0.3);
@@ -368,19 +322,9 @@
flex-shrink: 0; margin-top: 0.15rem;
}
/* ── Mobile ── */
@media (max-width: 768px) {
.sidebar { transform: translateX(-100%); }
.sidebar.open { transform: translateX(0); box-shadow: 4px 0 20px rgba(0,0,0,0.5); }
.main { margin-left: 0; }
.burger { display: flex; }
.overlay.visible { display: block; }
}
</style>
</head>
<body>
<div class="overlay" id="overlay"></div>
<body class="app">
<!-- Gruppe-Modal -->
<div class="modal-backdrop" id="gruppeModal">
@@ -439,9 +383,38 @@
<label for="iKurzText">Kurzbezeichnung *</label>
<input type="text" id="iKurzText" maxlength="200" placeholder="Kurzer Name">
<label for="iText">Beschreibung *</label>
<label class="label-with-hint">
<span>Beschreibung *</span>
<button type="button" class="btn-hint" onclick="togglePlaceholderHint()" title="Platzhalter-Hilfe">i</button>
</label>
<div id="iPlaceholderHint" style="display:none;">
<div class="placeholder-hint">
In Texten können Platzhalter verwendet werden:<br>
<code>{AKTIV}</code> Name des aktiven Parts<br>
<code>{PASSIV}</code> Name des passiven Parts
</div>
</div>
<textarea id="iText" rows="4" maxlength="4000" placeholder="Ausführliche Beschreibung…"></textarea>
<!-- Finisher: Geschlecht -->
<div id="iGeschlechtRow">
<label>Geschlecht der Person die kommt *</label>
<div style="display:flex; gap:1.5rem; margin-top:0.5rem;" id="iGeschlecht">
<label style="display:flex; align-items:center; gap:0.4rem; font-size:0.85rem; cursor:pointer;">
<input type="radio" name="iGeschlechtRadio" value="WEIBLICH" style="accent-color:var(--color-primary);">
Weiblich
</label>
<label style="display:flex; align-items:center; gap:0.4rem; font-size:0.85rem; cursor:pointer;">
<input type="radio" name="iGeschlechtRadio" value="DIVERS" style="accent-color:var(--color-primary);">
Divers
</label>
<label style="display:flex; align-items:center; gap:0.4rem; font-size:0.85rem; cursor:pointer;">
<input type="radio" name="iGeschlechtRadio" value="MAENNLICH" style="accent-color:var(--color-primary);">
Männlich
</label>
</div>
</div>
<!-- Aufgabe / Strafe: Level + Sekunden -->
<div id="iLevelRow">
<label for="iLevel">Level *</label>
@@ -481,6 +454,27 @@
</div>
</div>
<!-- Finisher: Werkzeuge (eigene Labels, kein Umschnalldildo bei aktiv) -->
<div id="iWerkzeugFinisherAktivRow">
<label>Benötigt (Person die kommt)</label>
<div class="werkzeug-checks" id="iWerkzeugFinisherAktiv">
<label class="werkzeug-check"><span>Mund</span><input type="checkbox" value="MUND"></label>
<label class="werkzeug-check"><span>Vagina</span><input type="checkbox" value="VAGINA"></label>
<label class="werkzeug-check"><span>Penis</span><input type="checkbox" value="PENIS"></label>
<label class="werkzeug-check"><span>Anus</span><input type="checkbox" value="ANUS"></label>
</div>
</div>
<div id="iWerkzeugFinisherPassivRow">
<label>Benötigt (Person die zum Kommen bringt)</label>
<div class="werkzeug-checks" id="iWerkzeugFinisherPassiv">
<label class="werkzeug-check"><span>Mund</span><input type="checkbox" value="MUND"></label>
<label class="werkzeug-check"><span>Vagina</span><input type="checkbox" value="VAGINA"></label>
<label class="werkzeug-check"><span>Penis</span><input type="checkbox" value="PENIS"></label>
<label class="werkzeug-check"><span>Anus</span><input type="checkbox" value="ANUS"></label>
<label class="werkzeug-check"><span>Umschnall-Dildo</span><input type="checkbox" value="UMSCHNALLDILDO"></label>
</div>
</div>
<!-- Zeitstrafe: Minuten + SperreFuer + releaseText -->
<div id="iMinutenRow">
<label>Dauer (Minuten) *</label>
@@ -513,6 +507,11 @@
<label>Benötigte Toys (optional)</label>
<div class="selected-toys-row" id="iSelectedToys"></div>
<button class="btn-toy-add" type="button" id="iToyAddBtn">+ Toy hinzufügen</button>
<div id="iToySearchArea" style="display:none; margin-top:0.5rem;">
<input class="toy-search-input" type="text" id="toySearchInput" placeholder="Name filtern…" autocomplete="off">
<div class="toy-search-results" id="toySearchResults"></div>
<div id="toySearchEmpty" style="font-size:0.82rem; color:var(--color-muted); display:none; margin-top:0.4rem;">Keine Toys gefunden.</div>
</div>
<div class="modal-error" id="itemModalError"></div>
<div class="modal-actions">
@@ -522,46 +521,9 @@
</div>
</div>
<!-- ── Toy-Suchmodal ── -->
<div class="modal-overlay" id="toySearchModal">
<div class="modal-box" style="max-width:420px;">
<h2 class="modal-title">Toy auswählen</h2>
<input class="toy-search-input" type="text" id="toySearchInput" placeholder="Name filtern…" autocomplete="off">
<div class="toy-search-results" id="toySearchResults"></div>
<div id="toySearchEmpty" style="font-size:0.82rem; color:var(--color-muted); display:none; margin-top:0.5rem;">Keine Toys gefunden.</div>
<div class="modal-actions" style="margin-top:1rem;">
<button class="btn-save" id="toySearchDoneBtn">Fertig</button>
</div>
</div>
</div>
<div class="layout">
<aside class="sidebar" id="sidebar">
<div class="sidebar-brand">XXX The Game</div>
<ul>
<li><a href="/userhome.html"><span class="icon"></span> Dashboard</a></li>
<li><a href="#"><span class="icon"></span> Meine Session</a></li>
<li><a href="/aufgaben.html" class="active"><span class="icon"></span> Aufgaben</a></li>
<li><a href="/entdecken.html"><span class="icon"></span> Entdecken</a></li>
<li><a href="#"><span class="icon"></span> Strafen</a></li>
<li><a href="/toys.html"><span class="icon"></span> Toys</a></li>
<li><a href="#"><span class="icon"></span> Favoriten</a></li>
<li><a href="#"><span class="icon"></span> Rangliste</a></li>
<li><a href="#"><span class="icon"></span> Nachrichten</a></li>
<li><a href="#"><span class="icon"></span> Einstellungen</a></li>
<li><a href="#" id="logoutLink"><span class="icon"></span> Abmelden</a></li>
</ul>
</aside>
<div class="main">
<header class="topbar">
<button class="burger" id="burgerBtn" aria-label="Menü öffnen">
<span class="burger-icon"><span></span><span></span><span></span></span>
</button>
<h1>Aufgaben</h1>
</header>
<div class="content">
<div class="main">
<div class="content">
<!-- Meine Aufgabengruppen -->
<div class="section">
@@ -621,7 +583,6 @@
</div>
</div>
</div>
</div>
</div>
@@ -708,13 +669,15 @@
}
list.innerHTML = gruppen.map(g => {
_gruppeData[g.gruppenId] = g;
const aufgabenCount = (g.aufgaben || []).length;
const strafeCount = (g.strafen || []).length;
const sperreCount = (g.sperren || []).length;
const aufgabenCount = (g.aufgaben || []).length;
const strafeCount = (g.strafen || []).length;
const sperreCount = (g.sperren || []).length;
const finisherCount = (g.finisher || []).length;
const counts = [
aufgabenCount ? `${aufgabenCount} Aufgabe${aufgabenCount !== 1 ? 'n' : ''}` : '',
strafeCount ? `${strafeCount} Strafe${strafeCount !== 1 ? 'n' : ''}` : '',
sperreCount ? `${sperreCount} Zeitstrafe${sperreCount !== 1 ? 'n' : ''}` : ''
aufgabenCount ? `${aufgabenCount} Aufgabe${aufgabenCount !== 1 ? 'n' : ''}` : '',
strafeCount ? `${strafeCount} Strafe${strafeCount !== 1 ? 'n' : ''}` : '',
sperreCount ? `${sperreCount} Zeitstrafe${sperreCount !== 1 ? 'n' : ''}` : '',
finisherCount ? `${finisherCount} Finisher` : ''
].filter(Boolean).join(' · ');
const badges = [];
@@ -738,9 +701,10 @@
</div>
<div class="gruppe-body" id="body-${esc(g.gruppenId)}" style="display:none;">
${g.beschreibung ? `<div class="gruppe-desc">${esc(g.beschreibung)}</div>` : ''}
${renderSubSection('Aufgaben', sortByLevelThenName(g.aufgaben || []), 'aufgabe', renderAufgabe, g.gruppenId, type)}
${renderSubSection('Strafen', sortByLevelThenName(g.strafen || []), 'strafe', renderStrafe, g.gruppenId, type)}
${renderSubSection('Zeitstrafen',sortByName(g.sperren || []), 'zeitstrafe',renderZeitstrafe, g.gruppenId, type)}
${renderSubSection('Aufgaben', sortByLevelThenName(g.aufgaben || []), 'aufgabe', renderAufgabe, g.gruppenId, type)}
${renderSubSection('Strafen', sortByLevelThenName(g.strafen || []), 'strafe', renderStrafe, g.gruppenId, type)}
${renderSubSection('Zeitstrafen',sortByName(g.sperren || []), 'zeitstrafe',renderZeitstrafe, g.gruppenId, type)}
${renderSubSection('Finisher', sortByName(g.finisher || []), 'finisher', renderFinisher, g.gruppenId, type)}
</div>
</div>`;
}).join('');
@@ -864,6 +828,33 @@
</div>`;
}
const GESCHLECHT_LABEL = { WEIBLICH: 'Weiblich', DIVERS: 'Divers', MAENNLICH: 'Männlich' };
function renderFinisher(f, type, gruppenId) {
_itemData[f.finisherId] = { ...f, _kind: 'finisher', _gruppenId: gruppenId };
const badges = [];
if (f.geschlecht) badges.push(`<span class="badge badge-neutral">${esc(GESCHLECHT_LABEL[f.geschlecht] || f.geschlecht)}</span>`);
const detailRows = [];
if (f.text) detailRows.push(`<div class="item-detail-text">${esc(f.text)}</div>`);
if (f.benoetigtAktiv && f.benoetigtAktiv.length) detailRows.push(`<div class="item-detail-row"><span class="item-detail-label">Aktiv:</span>${werkzeugChips(f.benoetigtAktiv)}</div>`);
if (f.benoetigtPassiv && f.benoetigtPassiv.length) detailRows.push(`<div class="item-detail-row"><span class="item-detail-label">Passiv:</span>${werkzeugChips(f.benoetigtPassiv)}</div>`);
if (f.benoetigteToys && f.benoetigteToys.length) detailRows.push(`<div class="item-detail-row"><span class="item-detail-label">Toys:</span>${toyChips(f.benoetigteToys)}</div>`);
const actionBtns = type === 'user' ? `
<div class="item-action-btns">
<button class="btn-item-edit" onclick="openEditItemModal('${esc(f.finisherId)}',event)">✎ Bearbeiten</button>
<button class="btn-item-delete" onclick="deleteItem('finisher','${esc(f.finisherId)}','${esc(gruppenId)}',event)">✕ Löschen</button>
</div>` : '';
return `<div class="item" id="item-${esc(f.finisherId)}">
<div class="item-row" onclick="toggleItem('${esc(f.finisherId)}')">
<span class="item-text">${esc(f.kurzText)}</span>
${badges.length ? `<span class="item-badges">${badges.join('')}</span>` : ''}
</div>
${(detailRows.length || actionBtns) ? `<div class="item-detail">${detailRows.join('')}${actionBtns}</div>` : ''}
</div>`;
}
// ── Item toggle (detail panel) ──
let openItemId = null;
@@ -883,8 +874,8 @@
}
// ── Item löschen ──
const ITEM_DELETE_URL = { aufgabe: '/aufgabe', strafe: '/strafe', zeitstrafe: '/sperre' };
const ITEM_DELETE_FIELD = { aufgabe: 'aufgabeId', strafe: 'strafeId', zeitstrafe: 'sperreId' };
const ITEM_DELETE_URL = { aufgabe: '/aufgabe', strafe: '/strafe', zeitstrafe: '/sperre', finisher: '/finisher' };
const ITEM_DELETE_FIELD = { aufgabe: 'aufgabeId', strafe: 'strafeId', zeitstrafe: 'sperreId', finisher: 'finisherId' };
function deleteItem(kind, itemId, gruppenId, event) {
event.stopPropagation();
@@ -1225,29 +1216,34 @@
if (chip) chip.classList.remove('selected');
}
// Toy-Suchmodal
const toySearchModal = document.getElementById('toySearchModal');
document.getElementById('iToyAddBtn').addEventListener('click', openToySearch);
document.getElementById('toySearchDoneBtn').addEventListener('click', closeToySearch);
toySearchModal.addEventListener('click', e => { if (e.target === toySearchModal) closeToySearch(); });
// Toy-Suche (inline)
document.getElementById('iToyAddBtn').addEventListener('click', toggleToySearch);
document.getElementById('toySearchInput').addEventListener('input', renderToySearchResults);
function openToySearch() {
document.getElementById('toySearchInput').value = '';
document.getElementById('toySearchResults').innerHTML = '';
document.getElementById('toySearchEmpty').style.display = 'none';
toySearchModal.classList.add('open');
_loadAvailableToys()
.then(() => renderToySearchResults())
.catch(() => {
document.getElementById('toySearchResults').innerHTML =
'<span style="font-size:0.82rem;color:var(--color-muted)">Fehler beim Laden.</span>';
});
document.getElementById('toySearchInput').focus();
function toggleToySearch() {
const area = document.getElementById('iToySearchArea');
if (area.style.display === 'none') {
area.style.display = 'block';
document.getElementById('toySearchInput').value = '';
document.getElementById('toySearchResults').innerHTML = '';
document.getElementById('toySearchEmpty').style.display = 'none';
document.getElementById('iToyAddBtn').textContent = '▲ Suche schließen';
_loadAvailableToys()
.then(() => renderToySearchResults())
.catch(() => {
document.getElementById('toySearchResults').innerHTML =
'<span style="font-size:0.82rem;color:var(--color-muted)">Fehler beim Laden.</span>';
});
document.getElementById('toySearchInput').focus();
} else {
closeToySearch();
}
}
function closeToySearch() { toySearchModal.classList.remove('open'); }
function closeToySearch() {
document.getElementById('iToySearchArea').style.display = 'none';
document.getElementById('iToyAddBtn').textContent = '+ Toy hinzufügen';
}
function renderToySearchResults() {
const query = document.getElementById('toySearchInput').value.trim().toLowerCase();
@@ -1297,17 +1293,29 @@
let currentItemKind = null; // 'aufgabe' | 'strafe' | 'zeitstrafe'
let currentItemEditId = null; // null = neu, sonst ID des zu bearbeitenden Items
const ITEM_TITLES_NEW = { aufgabe: 'Aufgabe hinzufügen', strafe: 'Strafe hinzufügen', zeitstrafe: 'Zeitstrafe hinzufügen' };
const ITEM_TITLES_EDIT = { aufgabe: 'Aufgabe bearbeiten', strafe: 'Strafe bearbeiten', zeitstrafe: 'Zeitstrafe bearbeiten' };
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' };
function _setupItemModal(kind) {
const isZeit = kind === 'zeitstrafe';
document.getElementById('iLevelRow').style.display = isZeit ? 'none' : 'block';
document.getElementById('iWerkzeugAktivRow').style.display = isZeit ? 'none' : 'block';
document.getElementById('iWerkzeugPassivRow').style.display = isZeit ? 'none' : 'block';
document.getElementById('iMinutenRow').style.display = isZeit ? 'block' : 'none';
document.getElementById('iSperreFuerRow').style.display = isZeit ? 'block' : 'none';
document.getElementById('iReleaseTextRow').style.display = isZeit ? 'block' : 'none';
const isZeit = kind === 'zeitstrafe';
const isFinisher = kind === 'finisher';
document.querySelector('#iPlaceholderHint .placeholder-hint').innerHTML =
isFinisher
? 'In Texten können Platzhalter verwendet werden:<br>' +
'<code>{AKTIV}</code> Name der Person die kommt<br>' +
'<code>{PASSIV}</code> Name der Person die zum Kommen bringt'
: 'In Texten können Platzhalter verwendet werden:<br>' +
'<code>{AKTIV}</code> Name des aktiven Parts<br>' +
'<code>{PASSIV}</code> 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';
}
function _resetItemFields() {
@@ -1319,9 +1327,12 @@
document.getElementById('iMinVon').value = '';
document.getElementById('iMinBis').value = '';
document.getElementById('iReleaseText').value = '';
document.querySelectorAll('#iWerkzeugAktiv input').forEach(cb => cb.checked = false);
document.querySelectorAll('#iWerkzeugPassiv input').forEach(cb => cb.checked = false);
document.querySelectorAll('#iSperreFuer input').forEach(cb => cb.checked = false);
document.querySelectorAll('#iWerkzeugAktiv input').forEach(cb => cb.checked = false);
document.querySelectorAll('#iWerkzeugPassiv input').forEach(cb => cb.checked = false);
document.querySelectorAll('#iWerkzeugFinisherAktiv input').forEach(cb => cb.checked = false);
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);
_selectedToys = [];
renderSelectedToys();
document.getElementById('itemModalError').style.display = 'none';
@@ -1359,6 +1370,13 @@
document.getElementById('iSekBis').value = d.sekundenBis != null ? d.sekundenBis : '';
(d.benoetigtAktiv || []).forEach(w => { const cb = document.querySelector(`#iWerkzeugAktiv input[value="${w}"]`); if (cb) cb.checked = true; });
(d.benoetigtPassiv || []).forEach(w => { const cb = document.querySelector(`#iWerkzeugPassiv input[value="${w}"]`); if (cb) cb.checked = true; });
} else if (d._kind === 'finisher') {
(d.benoetigtAktiv || []).forEach(w => { const cb = document.querySelector(`#iWerkzeugFinisherAktiv input[value="${w}"]`); if (cb) cb.checked = true; });
(d.benoetigtPassiv || []).forEach(w => { const cb = document.querySelector(`#iWerkzeugFinisherPassiv input[value="${w}"]`); if (cb) cb.checked = true; });
if (d.geschlecht) {
const rb = document.querySelector(`#iGeschlecht input[value="${d.geschlecht}"]`);
if (rb) rb.checked = true;
}
} else {
document.getElementById('iMinVon').value = d.minutenVon != null ? d.minutenVon : '';
document.getElementById('iMinBis').value = d.minutenBis != null ? d.minutenBis : '';
@@ -1372,7 +1390,11 @@
document.getElementById('iKurzText').focus();
}
function closeItemModal() { itemModal.classList.remove('open'); }
function closeItemModal() { itemModal.classList.remove('open'); closeToySearch(); document.getElementById('iPlaceholderHint').style.display = 'none'; }
function togglePlaceholderHint() {
const el = document.getElementById('iPlaceholderHint');
el.style.display = el.style.display === 'none' ? 'block' : 'none';
}
document.getElementById('itemCancelBtn').addEventListener('click', closeItemModal);
itemModal.addEventListener('click', e => { if (e.target === itemModal) closeItemModal(); });
@@ -1408,6 +1430,19 @@
url = isEdit ? `${base}/${currentItemEditId}` : base;
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; }
payload = {
kurzText, text, geschlecht,
gruppeId: isEdit ? undefined : currentItemGruppeId,
benoetigtAktiv: checkedValues('iWerkzeugFinisherAktiv'),
benoetigtPassiv: checkedValues('iWerkzeugFinisherPassiv'),
benoetigteToys: _selectedToys.map(t => ({ toyId: t.toyId }))
};
url = isEdit ? `/finisher/${currentItemEditId}` : '/finisher';
method = isEdit ? 'PUT' : 'POST';
} else {
const minVon = document.getElementById('iMinVon').value.trim();
if (!minVon) { showItemError('Bitte eine Mindestdauer in Minuten angeben.'); return; }
@@ -1422,7 +1457,7 @@
minutenBis: minBis ? parseInt(minBis, 10) : null,
releaseText: document.getElementById('iReleaseText').value.trim() || null,
sperreFuer,
benoetigteToys: Array.from(_selectedToyIds).map(id => ({ toyId: id }))
benoetigteToys: _selectedToys.map(t => ({ toyId: t.toyId }))
};
url = isEdit ? `/sperre/${currentItemEditId}` : '/sperre';
method = isEdit ? 'PUT' : 'POST';
@@ -1558,27 +1593,12 @@
// ── ESC schließt alle Modals ──
document.addEventListener('keydown', e => {
if (e.key !== 'Escape') return;
if (toySearchModal.classList.contains('open')) { closeToySearch(); return; }
if (publishModal.classList.contains('open')) { closePublishModal(); return; }
if (gruppeModal.classList.contains('open')) { closeGruppeModal(); return; }
if (itemModal.classList.contains('open')) { closeItemModal(); return; }
});
// ── Burger menu ──
const sidebar = document.getElementById('sidebar');
const burgerBtn = document.getElementById('burgerBtn');
const overlay = document.getElementById('overlay');
function openMenu() {
sidebar.classList.add('open'); overlay.classList.add('visible');
burgerBtn.classList.add('open'); burgerBtn.setAttribute('aria-label', 'Menü schließen');
}
function closeMenu() {
sidebar.classList.remove('open'); overlay.classList.remove('visible');
burgerBtn.classList.remove('open'); burgerBtn.setAttribute('aria-label', 'Menü öffnen');
}
burgerBtn.addEventListener('click', () => sidebar.classList.contains('open') ? closeMenu() : openMenu());
overlay.addEventListener('click', closeMenu);
sidebar.querySelectorAll('a').forEach(l => l.addEventListener('click', () => { if (window.innerWidth <= 768) closeMenu(); }));
</script>
<script src="/js/sidebar.js"></script>
</body>
</html>