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

@@ -0,0 +1,146 @@
(function () {
const path = window.location.pathname;
const groups = [
{
label: 'Vanilla Game',
icon: '♡',
items: [
{ href: '/infovanilla.html', icon: '', label: 'Info' },
{ href: '/sessionvanilla.html', icon: '▷', label: 'Neue Session' },
]
},
{
label: 'BDSM Game',
icon: '◆',
items: [
{ href: '/infobdsm.html', icon: '', label: 'Info' },
{ href: '/sessionbdsm.html', icon: '▷', label: 'Neue Session', id: 'navBdsmNeu' },
{ href: '/sessionbdsmingame.html', icon: '▶', label: 'Im Spiel', id: 'navBdsmImSpiel' },
{ href: '/aufgaben.html', icon: '✓', label: 'Aufgaben' },
{ href: '/toys.html', icon: '◈', label: 'Toys' },
{ href: '/entdecken.html', icon: '⊙', label: 'Entdecken' },
]
},
{
label: 'Chastity Game',
icon: '⊗',
items: [
{ href: '/infochastity.html', icon: '', label: 'Info' },
{ href: '/sessionchastity.html', icon: '▷', label: 'Neue Session' },
]
},
];
const homeCls = path === '/userhome.html' ? ' class="active"' : '';
const homeItem = `
<li class="sidebar-mobile-only">
<a href="/userhome.html"${homeCls}><span class="icon">⊞</span> Home</a>
</li>`;
const nav = groups.map(({ label, icon, items }) => {
const isOpen = items.some(item => item.href === path);
const openCls = isOpen ? ' open' : '';
const subItems = items.map(({ href, icon: iIcon, label: iLabel, id: iId }) => {
const cls = path === href ? ' class="active"' : '';
const idAt = iId ? ` id="${iId}"` : '';
return `<li${idAt}><a href="${href}"${cls}><span class="icon">${iIcon}</span> ${iLabel}</a></li>`;
}).join('');
return `
<li class="sidebar-group${openCls}">
<a class="sidebar-group-toggle"><span class="icon">${icon}</span> ${label}<span class="sidebar-arrow">▸</span></a>
<ul class="sidebar-sub">
${subItems}
</ul>
</li>`;
}).join('');
document.body.insertAdjacentHTML('afterbegin', `
<div class="sidebar-overlay" id="sidebarOverlay"></div>
<button class="burger" id="burgerBtn" aria-label="Menü öffnen">
<span class="burger-icon"><span></span><span></span><span></span></span>
</button>
<aside class="sidebar" id="sidebar">
<div class="sidebar-icon-area">
<a href="/userhome.html"><img src="/icon.png" alt="Home"></a>
</div>
<ul>
${homeItem}
${nav}
<li><hr style="border:none; border-top:1px solid var(--color-secondary); margin:0.4rem 1rem;"></li>
<li>
<a href="/login/logout"><span class="icon">⏏</span> Abmelden</a>
</li>
</ul>
</aside>
`);
// Sidebar und .main in einen zentrierten App-Wrapper verschieben
const appWrapper = document.createElement('div');
appWrapper.className = 'app-wrapper';
const sidebarEl = document.getElementById('sidebar');
const mainEl = document.querySelector('.main');
document.body.insertBefore(appWrapper, sidebarEl);
appWrapper.appendChild(sidebarEl);
if (mainEl) appWrapper.appendChild(mainEl);
// Group toggle
document.querySelectorAll('.sidebar-group-toggle').forEach(toggle => {
toggle.addEventListener('click', e => {
e.preventDefault();
toggle.closest('.sidebar-group').classList.toggle('open');
});
});
// "Im Spiel" standardmäßig ausblenden; wird nach Session-Check ggf. wieder eingeblendet
const navNeu = document.getElementById('navBdsmNeu');
const navImSpiel = document.getElementById('navBdsmImSpiel');
if (navImSpiel) navImSpiel.style.display = 'none';
// Session-Status prüfen
fetch('/login/me')
.then(r => r.ok ? r.json() : null)
.then(async user => {
if (!user) return;
// Session-Status prüfen und Menü anpassen
try {
const sessionRes = await fetch(`/session?userId=${user.userId}`);
const hasSession = sessionRes.status === 200;
if (navNeu) navNeu.style.display = hasSession ? 'none' : '';
if (navImSpiel) navImSpiel.style.display = hasSession ? '' : 'none';
} catch (_) { /* Menü bleibt im Standardzustand */ }
})
.catch(() => {});
const sidebar = document.getElementById('sidebar');
const burgerBtn = document.getElementById('burgerBtn');
const overlay = document.getElementById('sidebarOverlay');
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:not([href="/login/logout"]):not(.sidebar-group-toggle)').forEach(l =>
l.addEventListener('click', () => { if (window.innerWidth <= 768) closeMenu(); })
);
// Social sidebar auf allen App-Seiten nachladen
const s = document.createElement('script');
s.src = '/js/social-sidebar.js';
document.head.appendChild(s);
})();

View File

@@ -0,0 +1,111 @@
(function () {
// Verhindert doppelte Ausführung (z.B. wenn sidebar.js nachladen UND direktes <script>-Tag vorhanden)
if (document.querySelector('.social-sidebar')) return;
const path = window.location.pathname;
const links = [
{ href: '/personen-suchen.html', icon: '⊕', label: 'Personen suchen', badgeId: null, mobileBadgeId: null },
{ href: '/freunde.html', icon: '♡', label: 'Freunde', badgeId: 'socialFriendsBadge', mobileBadgeId: 'socialMobileFriendsBadge' },
{ href: '/nachrichten.html', icon: '✉', label: 'Nachrichten', badgeId: 'socialMsgBadge', mobileBadgeId: 'socialMobileMsgBadge' },
];
const profileActive = (path === '/benutzer.html' || path === '/profile.html') ? ' class="active"' : '';
// ── Rechte Desktop-Sidebar (kein Titel) ──
const desktopItems = links.map(({ href, icon, label, badgeId }) => {
const cls = path === href ? ' class="active"' : '';
const badge = badgeId ? `<span class="social-badge" id="${badgeId}" style="display:none;"></span>` : '';
return `<li><a href="${href}"${cls}><span class="icon">${icon}</span> ${label}${badge}</a></li>`;
}).join('');
const aside = document.createElement('aside');
aside.className = 'social-sidebar';
aside.innerHTML = `
<ul>
<li id="socialProfileItem">
<a href="/benutzer.html"${profileActive}>
<span class="icon" id="socialProfileIcon">◉</span>
<span id="socialProfileName">Profil</span>
</a>
</li>
<li><hr style="border:none;border-top:1px solid var(--color-secondary);margin:0.4rem 1rem;"></li>
${desktopItems}
</ul>`;
const appWrapper = document.querySelector('.app-wrapper');
if (appWrapper) appWrapper.appendChild(aside);
// ── Mobile: Links + Profil ins Burger-Menü einhängen ──
const sidebarUl = document.querySelector('.sidebar ul');
if (sidebarUl) {
const mobileLinks = links.map(({ href, icon, label, mobileBadgeId }) => {
const cls = path === href ? ' class="active"' : '';
const badge = mobileBadgeId
? `<span class="social-badge" id="${mobileBadgeId}" style="display:none;"></span>`
: '';
return `<li class="sidebar-mobile-only"><a href="${href}"${cls}><span class="icon">${icon}</span> ${label}${badge}</a></li>`;
}).join('');
const mobileProfileActive = profileActive;
const mobileProfile = `
<li class="sidebar-mobile-only" id="socialMobileProfileItem">
<a href="/benutzer.html"${mobileProfileActive}>
<span class="icon" id="socialMobileProfileIcon">◉</span>
<span id="socialMobileProfileName">Profil</span>
</a>
</li>`;
const sep = '<li class="sidebar-mobile-only"><hr style="border:none;border-top:1px solid var(--color-secondary);margin:0.4rem 1rem;"></li>';
const logoutLi = sidebarUl.querySelector('a[href="/login/logout"]')?.closest('li');
if (logoutLi) {
logoutLi.insertAdjacentHTML('beforebegin', sep + mobileLinks + mobileProfile);
} else {
sidebarUl.insertAdjacentHTML('beforeend', sep + mobileLinks + mobileProfile);
}
}
// ── Profil-Daten nachladen (Name + Avatar) ──
fetch('/login/me')
.then(r => r.ok ? r.json() : null)
.then(user => {
if (!user) return;
function updateProfileEntry(nameId, iconId, itemId) {
const nameEl = document.getElementById(nameId);
if (nameEl) nameEl.textContent = user.name;
const iconEl = document.getElementById(iconId);
if (iconEl && user.profilePicture) {
iconEl.innerHTML = `<img src="data:image/png;base64,${user.profilePicture}" class="sidebar-profile-img" alt="">`;
}
const anchor = document.querySelector('#' + itemId + ' a');
if (anchor) anchor.href = '/benutzer.html?userId=' + user.userId;
}
updateProfileEntry('socialProfileName', 'socialProfileIcon', 'socialProfileItem');
updateProfileEntry('socialMobileProfileName', 'socialMobileProfileIcon', 'socialMobileProfileItem');
})
.catch(() => {});
// ── Badge-Zähler ──
function setBadge(ids, count) {
ids.forEach(id => {
if (!id) return;
const el = document.getElementById(id);
if (!el) return;
el.textContent = count;
el.style.display = count > 0 ? '' : 'none';
});
}
fetch('/social/friends/pending/count')
.then(r => r.ok ? r.json() : 0)
.then(n => setBadge(['socialFriendsBadge', 'socialMobileFriendsBadge'], n))
.catch(() => {});
fetch('/social/messages/unread/count')
.then(r => r.ok ? r.json() : 0)
.then(n => setBadge(['socialMsgBadge', 'socialMobileMsgBadge'], n))
.catch(() => {});
})();