Weiter gebaut
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled

This commit is contained in:
2026-04-25 16:56:35 +02:00
parent e4b762f905
commit 4f2048bdc8
242 changed files with 14108 additions and 1770 deletions

View File

@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<link rel="icon" href="/img/icon.png" type="image/png">
<meta http-equiv="refresh" content="0;url=/games/aufgaben/aufgaben.html">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aufgaben BDSM xXx Sphere</title>
<link rel="stylesheet" href="/css/variables.css">
@@ -96,6 +97,7 @@
.gruppe-badge-private { background: rgba(233,69,96,0.15); color: var(--color-primary); }
.gruppe-badge-public { background: rgba(46,204,113,0.15); color: var(--color-success); }
.gruppe-badge-vanilla { background: #e8f5e9; color: #2e7d32; border: 1px solid #a5d6a7; }
.gruppe-badge-chastity { background: rgba(155,89,182,0.15); color: #9b59b6; border: 1px solid rgba(155,89,182,0.4); }
.gruppe-toggle { font-size: 0.75rem; color: var(--color-muted); flex-shrink: 0; transition: transform 0.2s; }
.gruppe-card.open .gruppe-toggle { transform: rotate(90deg); }
@@ -368,12 +370,14 @@
Gruppe veröffentlichen (für alle sichtbar)
</span>
</label>
<label>
<span class="modal-check">
<input type="checkbox" id="gVanilla">
Auch für Vanilla-Game verfügbar
</span>
</label>
<div style="margin-top:0.5rem;">
<label for="gAvailableIn" style="font-size:0.85rem;display:block;margin-bottom:0.3rem;">Verfügbar in</label>
<select id="gAvailableIn" style="width:100%;padding:0.5rem 0.75rem;border-radius:6px;border:1px solid var(--color-secondary);background:var(--color-secondary);color:var(--color-text);font-size:0.9rem;">
<option value="BDSM_ONLY">Nur BDSM</option>
<option value="BDSM_AND_VANILLA">BDSM &amp; Vanilla</option>
<option value="CHASTITY_ONLY">Nur Chastity</option>
</select>
</div>
<div class="modal-error" id="modalError"></div>
<div class="modal-actions">
<button class="btn-cancel" id="cancelBtn">Abbrechen</button>
@@ -636,8 +640,13 @@
.then(user => { if (!user) return; loadUserGruppen(); loadAboGruppen(); loadSystemGruppen(); })
.catch(() => { window.location.href = '/login.html'; });
// ── Cross-tab notification ──
let _notifyOnLoad = false;
const gruppenBc = new BroadcastChannel('bdsm-gruppen-updated');
// ── Load ──
function loadUserGruppen() {
if (_notifyOnLoad) { _notifyOnLoad = false; try { gruppenBc.postMessage(1); } catch (_) {} }
resetSelection();
document.getElementById('userLoading').style.display = 'block';
fetch(apiUrl(`/gruppe/list/user`) + `?page=${userPage}&size=${PAGE_SIZE}`)
@@ -722,7 +731,8 @@
if (g.privateGruppe) badges.push(`<span class="gruppe-badge gruppe-badge-private">Privat</span>`);
else badges.push(`<span class="gruppe-badge gruppe-badge-public">Öffentlich</span>`);
if (type === 'user' && g.subscriberCount > 0) badges.push(`<span class="gruppe-badge">♥ ${g.subscriberCount} Abo${g.subscriberCount !== 1 ? 's' : ''}</span>`);
if (g.vanillaAvailable) badges.push(`<span class="gruppe-badge gruppe-badge-vanilla">Vanilla</span>`);
if (g.availableIn === 'BDSM_AND_VANILLA') badges.push(`<span class="gruppe-badge gruppe-badge-vanilla">Vanilla</span>`);
if (g.availableIn === 'CHASTITY_ONLY') badges.push(`<span class="gruppe-badge gruppe-badge-chastity">Chastity</span>`);
return `
<div class="gruppe-card" id="gruppe-${esc(g.gruppenId)}">
@@ -936,7 +946,7 @@
openItemId = null;
pendingExpandId = gruppenId;
pendingExpandType = 'user';
loadUserGruppen();
_notifyOnLoad = true; loadUserGruppen();
} else {
document.getElementById('userActionError').textContent = 'Fehler beim Löschen (HTTP ' + r.status + ').';
}
@@ -1077,7 +1087,7 @@
pubCb.checked = !g.privateGruppe;
pubCb.disabled = g.privateGruppe; // Veröffentlichen nur über den Veröffentlichen-Button
document.getElementById('gPublicLabel').style.display = 'block';
document.getElementById('gVanilla').checked = g.vanillaAvailable || false;
document.getElementById('gAvailableIn').value = g.availableIn || 'BDSM_ONLY';
const imgWrap = document.getElementById('gCurrentImgWrap');
if (g.bild) {
document.getElementById('gCurrentImg').src = 'data:image/png;base64,' + g.bild;
@@ -1095,7 +1105,7 @@
document.getElementById('gDesc').value = '';
document.getElementById('gPublic').checked = false;
document.getElementById('gPublicLabel').style.display = 'none';
document.getElementById('gVanilla').checked = false;
document.getElementById('gAvailableIn').value = 'BDSM_ONLY';
document.getElementById('gCurrentImgWrap').style.display = 'none';
gruppeModal.classList.add('open');
document.getElementById('gName').focus();
@@ -1129,7 +1139,7 @@
name,
beschreibung: document.getElementById('gDesc').value.trim() || null,
privateGruppe: isEdit ? !document.getElementById('gPublic').checked : true,
vanillaAvailable: document.getElementById('gVanilla').checked,
availableIn: document.getElementById('gAvailableIn').value,
bild: bildBase64
};
@@ -1146,7 +1156,7 @@
pendingExpandType = 'user';
}
userPage = 0;
loadUserGruppen();
_notifyOnLoad = true; loadUserGruppen();
} else if (r.status === 409) {
showModalError('Limit erreicht: maximal 10 eigene Aufgabengruppen möglich.');
} else {
@@ -1172,7 +1182,7 @@
.then(r => {
if (r.ok || r.status === 202) {
userPage = 0;
loadUserGruppen();
_notifyOnLoad = true; loadUserGruppen();
} else if (r.status === 403) {
document.getElementById('userActionError').textContent = 'Keine Berechtigung.';
btn.disabled = false;
@@ -1194,7 +1204,7 @@
.then(r => {
if (r.ok || r.status === 201) {
userPage = 0;
loadUserGruppen();
_notifyOnLoad = true; loadUserGruppen();
document.getElementById('systemActionError').textContent = '';
} else {
document.getElementById('systemActionError').textContent = 'Fehler beim Kopieren (HTTP ' + r.status + ').';
@@ -1213,7 +1223,7 @@
.then(r => {
if (r.ok || r.status === 201) {
userPage = 0;
loadUserGruppen();
_notifyOnLoad = true; loadUserGruppen();
document.getElementById('aboActionError').textContent = '';
} else if (r.status === 409) {
document.getElementById('aboActionError').textContent = 'Limit erreicht: maximal 10 eigene Aufgabengruppen möglich.';
@@ -1643,7 +1653,7 @@
pendingExpandId = currentItemGruppeId;
pendingExpandType = 'user';
userPage = 0;
loadUserGruppen();
_notifyOnLoad = true; loadUserGruppen();
} else if (r.status === 409) {
showItemError('Limit erreicht: maximal 100 Einträge pro Gruppe möglich.');
} else {
@@ -1739,7 +1749,7 @@
pendingExpandId = selectedGruppeId;
pendingExpandType = 'user';
userPage = 0;
loadUserGruppen();
_notifyOnLoad = true; loadUserGruppen();
} else {
const errEl = document.getElementById('publishError');
errEl.textContent = 'Fehler beim Veröffentlichen (HTTP ' + r.status + ').';

View File

@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<link rel="icon" href="/img/icon.png" type="image/png">
<meta http-equiv="refresh" content="0;url=/games/aufgaben/entdecken.html">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Entdecken xXx Sphere</title>
<link rel="stylesheet" href="/css/variables.css">

View File

@@ -67,13 +67,36 @@
.card-field:last-child { margin-bottom: 0; }
.card-field > label { font-size: 0.8rem; color: #aaa; margin: 0 0 0.5rem 0; display: block; }
.check-group { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.check-group--two-col { display: grid; grid-template-columns: 1fr 1fr; }
.check-item { display: inline-flex; align-items: flex-start; gap: 0.45rem; background: var(--color-secondary); border: 1px solid transparent; border-radius: 6px; padding: 0.4rem 0.7rem; cursor: pointer; transition: border-color 0.15s; user-select: none; }
.check-group--two-col { display: grid; grid-template-columns: repeat(auto-fill, minmax(145px, 1fr)); }
.check-item { display: inline-flex; align-items: flex-start; gap: 0.45rem; background: var(--color-secondary); border: 1px solid transparent; border-radius: 6px; padding: 0.4rem 0.7rem; cursor: pointer; transition: border-color 0.15s; user-select: none; position: relative; }
.check-item.is-checked { border-color: var(--color-primary); }
.check-item.is-disabled { opacity: 0.5; pointer-events: none; cursor: default; }
.check-item input { accent-color: var(--color-primary); width: auto; margin-top: 0.15rem; cursor: pointer; flex-shrink: 0; }
.check-item-label { font-size: 0.88rem; color: var(--color-text); line-height: 1.3; }
.check-item-desc { display: block; font-size: 0.72rem; color: var(--color-muted); margin-top: 0.1rem; }
.check-item-label { font-size: 0.88rem; color: var(--color-text); line-height: 1.3; display: flex; align-items: center; gap: 0.2rem; flex-wrap: wrap; }
.check-item-desc { display: none; }
.check-item-tooltip {
display: none; position: absolute; bottom: calc(100% + 6px); left: 0;
background: var(--color-card); border: 1px solid var(--color-secondary);
border-radius: 6px; padding: 0.4rem 0.65rem;
font-size: 0.78rem; color: var(--color-muted); line-height: 1.4;
width: max-content; max-width: 210px;
z-index: 50; pointer-events: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.35);
}
.check-item:hover .check-item-tooltip { display: block; }
.check-item-info-btn {
display: none; background: none; border: 1px solid var(--color-muted);
border-radius: 50%; width: 1.1rem; height: 1.1rem; font-size: 0.62rem;
color: var(--color-muted); cursor: pointer; padding: 0; line-height: 1;
flex-shrink: 0; font-style: normal; font-weight: normal;
align-items: center; justify-content: center;
}
.check-item-info-btn.active { border-color: var(--color-primary); color: var(--color-primary); }
.check-item-desc-mobile { display: none; font-size: 0.72rem; color: var(--color-muted); margin-top: 0.25rem; line-height: 1.4; }
@media (max-width: 679px) {
.check-item:hover .check-item-tooltip { display: none; }
.check-item-info-btn { display: inline-flex; }
}
.field-error { font-size: 0.78rem; color: var(--color-primary); margin-top: 0.3rem; display: none; }
.add-player-btn { width: 100%; background: transparent; border: 1px dashed var(--color-secondary); color: var(--color-muted); padding: 0.7rem; border-radius: 8px; font-size: 0.88rem; font-weight: normal; cursor: pointer; transition: border-color 0.15s, color 0.15s; margin-top: 0.5rem; }
.add-player-btn:hover { border-color: var(--color-primary); color: var(--color-text); background: transparent; }
@@ -160,8 +183,7 @@
<div class="main" id="setupView" style="display:none;">
<div class="content">
<h1>BDSM Game</h1>
<p id="pageSubtitle" style="margin-bottom:1.5rem;">Session einrichten</p>
<h1>BDSM Game - Session einrichten</h1>
<!-- Accordion 1: Grundeinstellungen -->
<div class="acc-item">
@@ -212,6 +234,9 @@
</button>
<div class="acc-body" id="acc-aufgaben-body">
<div id="guestAufgabenHint" class="guest-hint" style="display:none;">Aufgaben werden vom Host festgelegt nur zur Ansicht.</div>
<p style="font-size:0.85rem;color:var(--color-muted);margin-bottom:0.75rem;">
Gruppen verwalten: <a href="/games/bdsm/aufgaben.html" target="_blank" style="color:var(--color-primary);">Aufgaben-Verwaltung (BDSM)</a>
</p>
<div id="sectionOwn">
<div class="aufgaben-section-label"><label class="select-all-label"><input type="checkbox" class="select-all-cb" data-list="listOwn"> Eigene Gruppen</label></div>
<ul class="gruppe-list" id="listOwn"><li class="empty-hint">Wird geladen…</li></ul>
@@ -281,11 +306,11 @@
DIVERS: ['MUND','ANUS','UMSCHNALLDILDO'],
};
const WERKZEUGE = [
{ value: 'MUND', label: 'Mund', desc: 'Gewillt den Mund einzusetzen' },
{ value: 'VAGINA', label: 'Vagina', desc: 'Verfügt über eine Vagina und setzt sie ein' },
{ value: 'PENIS', label: 'Penis', desc: 'Verfügt über einen Penis und setzt ihn ein' },
{ value: 'ANUS', label: 'Anus', desc: 'Gewillt den Anus einzusetzen' },
{ value: 'VAGINA', label: 'Vagina', desc: 'Verfügt über eine Vagina' },
{ value: 'PENIS', label: 'Penis', desc: 'Verfügt über einen Penis' },
{ value: 'UMSCHNALLDILDO', label: 'Umschnall-Dildo', desc: 'Verfügt über einen Umschnall-Dildo' },
{ value: 'MUND', label: 'Oral', desc: 'Ist für aktiven Oral-Verkehr' },
{ value: 'ANUS', label: 'Anal', desc: 'Ist Bereit passiv Anal-Verkehr zu haben ' },
];
const ROLE_LABELS = {
AUFGABE_AKTIV: 'Aufgabe Aktiv', AUFGABE_PASSIV: 'Aufgabe Passiv',
@@ -417,10 +442,19 @@
return items.map(({ value, label, desc }) => `
<label class="check-item${disabled ? ' is-disabled' : ''}">
<input type="${type}" name="${name}" value="${value}"${disabled ? ' disabled' : ''}>
<span><span class="check-item-label">${label}</span>${desc ? `<span class="check-item-desc">${desc}</span>` : ''}</span>
<span><span class="check-item-label">${label}${desc ? `<button type="button" class="check-item-info-btn" onclick="event.stopPropagation();toggleCheckDesc(this);">ⓘ</button>` : ''}</span>${desc ? `<span class="check-item-tooltip">${desc}</span><span class="check-item-desc-mobile">${desc}</span>` : ''}</span>
</label>`).join('');
}
function toggleCheckDesc(btn) {
const mobile = btn.closest('.check-item')?.querySelector('.check-item-desc-mobile');
if (!mobile) return;
const isVisible = mobile.style.display === 'block';
document.querySelectorAll('.check-item-desc-mobile').forEach(el => { el.style.display = 'none'; });
document.querySelectorAll('.check-item-info-btn').forEach(el => el.classList.remove('active'));
if (!isVisible) { mobile.style.display = 'block'; btn.classList.add('active'); }
}
function buildPlayerBody(id, nameValue, nameReadOnly = false, genderDisabled = false, allDisabled = false) {
const globalDefault = document.getElementById('chkZeitstrafen')?.checked ?? true;
const isCheckedCls = globalDefault ? ' is-checked' : '';
@@ -650,15 +684,15 @@
const selectAllWrap = section?.querySelector('.select-all-label');
if (!gruppen.length) {
ul.innerHTML = '<li class="empty-hint">Keine Gruppen vorhanden.</li>';
if (selectAllWrap) selectAllWrap.style.visibility = 'hidden'; return;
if (selectAllWrap) { const cb = selectAllWrap.querySelector('input'); if (cb) cb.disabled = true; selectAllWrap.style.pointerEvents = 'none'; selectAllWrap.style.opacity = '0.4'; } return;
}
ul.innerHTML = gruppen.map(g => {
const checked = savedGruppen.has(g.gruppenId);
const vanillaBadge = g.vanillaAvailable ? '<span class="vanilla-badge">Vanilla</span>' : '';
const vanillaBadge = g.availableIn === 'BDSM_AND_VANILLA' ? '<span class="vanilla-badge">Vanilla</span>' : '';
return `<li><label class="gruppe-item${checked ? ' is-checked' : ''}">
<input type="checkbox" value="${g.gruppenId}"${checked ? ' checked' : ''}>
<span><span class="gruppe-item-name">${g.name}${vanillaBadge}</span>${g.beschreibung ? `<span class="gruppe-item-desc">${g.beschreibung}</span>` : ''}</span>
${g.bild ? `<img class="item-img" src="data:image/png;base64,${g.bild}" alt="">` : ''}
<span><span class="gruppe-item-name">${g.name}${vanillaBadge}</span>${g.beschreibung ? `<span class="gruppe-item-desc">${g.beschreibung}</span>` : ''}</span>
</label></li>`;
}).join('');
updateSelectAll(ul);
@@ -1033,8 +1067,7 @@
setFieldError(`p${id}-geschlecht-err`, geschlecht.length === 0);
setFieldError(`p${id}-spieltmit-err`, spieltMit.length === 0);
setFieldError(`p${id}-rollen-err`, rollen.length === 0);
setFieldError(`p${id}-werkzeuge-err`, werkzeuge.length === 0);
if (!name || !geschlecht.length || !spieltMit.length || !rollen.length || !werkzeuge.length) valid = false;
if (!name || !geschlecht.length || !spieltMit.length || !rollen.length) valid = false;
const sperre = document.getElementById(`p${id}-sperrenAufloesen`);
return { name, geschlecht: geschlecht[0] || null, spieltMit, rollen, werkzeuge,
userId: inv ? inv.inviteeId : (id === selfPlayerId ? myUserId : null),
@@ -1196,8 +1229,7 @@
setFieldError(`p${id}-geschlecht-err`, geschlecht.length === 0);
setFieldError(`p${id}-spieltmit-err`, spieltMit.length === 0);
setFieldError(`p${id}-rollen-err`, rollen.length === 0);
setFieldError(`p${id}-werkzeuge-err`, werkzeuge.length === 0);
if (!geschlecht.length || !spieltMit.length || !rollen.length || !werkzeuge.length) {
if (!geschlecht.length || !spieltMit.length || !rollen.length) {
showMessage('Bitte alle Felder ausfüllen.', 'error'); return;
}
const sperre = document.getElementById(`p${id}-sperrenAufloesen`);
@@ -1482,6 +1514,13 @@
}
init();
const _sessBc = new BroadcastChannel('bdsm-gruppen-updated');
_sessBc.onmessage = () => {
document.querySelectorAll('.gruppe-list input[type="checkbox"]:checked').forEach(cb => savedGruppen.add(cb.value));
document.querySelectorAll('.gruppe-list input[type="checkbox"]:not(:checked)').forEach(cb => savedGruppen.delete(cb.value));
ladeGruppenListen();
};
</script>
</body>
</html>

View File

@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<link rel="icon" href="/img/icon.png" type="image/png">
<meta http-equiv="refresh" content="0;url=/games/aufgaben/toys.html">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Toys xXx Sphere</title>
<link rel="stylesheet" href="/css/variables.css">