Weiter an der Oberfläche getüftelt
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
This commit is contained in:
459
bin/main/static/js/mobile-nav.js
Normal file
459
bin/main/static/js/mobile-nav.js
Normal file
@@ -0,0 +1,459 @@
|
||||
(function () {
|
||||
if (window.__mobileNavLoaded) return;
|
||||
window.__mobileNavLoaded = true;
|
||||
|
||||
const path = window.location.pathname;
|
||||
const I = window.IC || function () { return ''; };
|
||||
const TOPBAR_H = '4.875rem';
|
||||
|
||||
// ── CSS ──────────────────────────────────────────────────────────────────
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.mobile-topbar {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0;
|
||||
height: ${TOPBAR_H};
|
||||
background: var(--color-card);
|
||||
border-bottom: 1px solid var(--color-secondary);
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.4);
|
||||
z-index: 500;
|
||||
align-items: center;
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
.mobile-topbar-logo {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.mobile-topbar-logo img {
|
||||
height: 3rem;
|
||||
width: auto;
|
||||
}
|
||||
.mobile-topbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
}
|
||||
.mobile-tb-btn {
|
||||
position: relative;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-text);
|
||||
font-size: 1.725rem;
|
||||
line-height: 1;
|
||||
padding: 0.75rem 0.675rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 12px;
|
||||
text-decoration: none;
|
||||
transition: background 0.12s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.mobile-tb-btn:hover { background: var(--color-secondary); }
|
||||
.mobile-tb-badge {
|
||||
position: absolute;
|
||||
top: 3px; right: 3px;
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
border-radius: 10px;
|
||||
font-size: 0.87rem;
|
||||
font-weight: 700;
|
||||
min-width: 1.65em;
|
||||
padding: 0.15em 0.375em;
|
||||
display: none;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
}
|
||||
.mobile-tb-avatar {
|
||||
width: 2.7rem;
|
||||
height: 2.7rem;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* ── Mobile Menu Backdrop ── */
|
||||
.mob-menu-backdrop {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: ${TOPBAR_H};
|
||||
left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.55);
|
||||
z-index: 998;
|
||||
}
|
||||
.mob-menu-backdrop.open { display: block; }
|
||||
|
||||
/* ── Mobile Menu Panel ── */
|
||||
.mob-menu-panel {
|
||||
position: fixed;
|
||||
top: ${TOPBAR_H};
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: min(80%, 360px);
|
||||
background: var(--color-card);
|
||||
border-left: 1px solid var(--color-secondary);
|
||||
z-index: 999;
|
||||
overflow-y: auto;
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
.mob-menu-panel.open { transform: translateX(0); }
|
||||
|
||||
/* ── Accordion ── */
|
||||
.mnav-section { border-bottom: 1px solid var(--color-secondary); }
|
||||
.mnav-section:last-child { border-bottom: none; }
|
||||
|
||||
.mnav-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.85rem 1.1rem;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
transition: background 0.12s;
|
||||
}
|
||||
.mnav-section-header:hover { background: var(--color-secondary); }
|
||||
|
||||
.mnav-section-arrow {
|
||||
font-size: 0.65rem;
|
||||
transition: transform 0.2s;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.mnav-section.open .mnav-section-arrow { transform: rotate(90deg); }
|
||||
|
||||
.mnav-section-body { display: none; }
|
||||
.mnav-section.open .mnav-section-body { display: block; }
|
||||
|
||||
/* ── Menu Links ── */
|
||||
.mnav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
padding: 0.6rem 1.1rem 0.6rem 1.5rem;
|
||||
text-decoration: none;
|
||||
color: var(--color-text);
|
||||
font-size: 0.9rem;
|
||||
transition: background 0.12s, color 0.12s;
|
||||
}
|
||||
.mnav-link:hover { background: var(--color-secondary); color: var(--color-primary); }
|
||||
.mnav-link.active { color: var(--color-primary); background: rgba(var(--color-primary-rgb,233,69,96),0.08); }
|
||||
.mnav-icon { width: 1.3rem; text-align: center; font-size: 1rem; flex-shrink: 0; }
|
||||
.mnav-badge {
|
||||
margin-left: auto;
|
||||
background: var(--color-primary); color: #fff;
|
||||
border-radius: 10px; font-size: 0.68rem; font-weight: 700;
|
||||
min-width: 1.2em; padding: 0.1em 0.3em; display: none;
|
||||
}
|
||||
.mnav-link--danger { color: var(--color-primary); }
|
||||
.mnav-link--danger:hover { background: rgba(var(--color-primary-rgb,233,69,96),0.1); color: var(--color-primary); }
|
||||
|
||||
/* ── Show only on mobile ── */
|
||||
@media (max-width: 768px) {
|
||||
.mobile-topbar { display: flex; }
|
||||
body.app { padding-top: ${TOPBAR_H}; }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// ── Helper ───────────────────────────────────────────────────────────────
|
||||
function lnk(href, iconKey, label, id, badgeId) {
|
||||
const cls = path === href ? ' active' : '';
|
||||
const idAttr = id ? ` id="${id}"` : '';
|
||||
const badge = badgeId ? `<span class="mnav-badge" id="${badgeId}"></span>` : '';
|
||||
const icon = iconKey ? `<span class="mnav-icon">${I(iconKey) || ''}</span>` : `<span class="mnav-icon"></span>`;
|
||||
return `<a href="${href}" class="mnav-link${cls}"${idAttr}>${icon}<span>${label}</span>${badge}</a>`;
|
||||
}
|
||||
|
||||
// ── Menu-Sektionen ────────────────────────────────────────────────────────
|
||||
const SECTIONS = [
|
||||
{
|
||||
label: 'Allgemein',
|
||||
prefixes: ['/userhome.html', '/search.html', '/community/nachrichten.html',
|
||||
'/community/benachrichtigungen.html', '/games/common/einladungen.html'],
|
||||
html: `
|
||||
${lnk('/userhome.html', 'HOME', 'Home' )}
|
||||
${lnk('/search.html', 'SEARCH', 'Suche' )}
|
||||
<a href="/admin/admin.html" class="mnav-link${path === '/admin/admin.html' ? ' active' : ''}"
|
||||
id="mnavAdminLink" style="display:none">
|
||||
<span class="mnav-icon">${I('ADMIN') || '⚙'}</span><span>Administration</span></a>
|
||||
${lnk('/community/nachrichten.html', 'MESSAGES', 'Nachrichten', null, 'mnavBadgeMsg' )}
|
||||
${lnk('/community/benachrichtigungen.html', 'NOTIFICATIONS', 'Benachrichtigungen', null, 'mnavBadgeNotif' )}
|
||||
${lnk('/games/common/einladungen.html', 'INVITATIONS', 'Einladungen', null, 'mnavBadgeInv' )}
|
||||
`,
|
||||
},
|
||||
{
|
||||
label: 'Community',
|
||||
prefixes: ['/community/'],
|
||||
html: `
|
||||
${lnk('/community/feed.html', 'FEED', 'Feed' )}
|
||||
${lnk('/community/freunde.html', 'FRIENDS', 'Freunde', null, 'mnavBadgeFriends' )}
|
||||
${lnk('/community/gruppen.html', 'GROUPS', 'Gruppen', null, 'mnavBadgeGruppen' )}
|
||||
${lnk('/community/locations.html', 'LOCATION', 'Locations' )}
|
||||
${lnk('/community/events.html', 'EVENT', 'Veranstaltungen' )}
|
||||
`,
|
||||
},
|
||||
{
|
||||
label: 'Dating',
|
||||
prefixes: ['/dating/'],
|
||||
html: `
|
||||
<a href="/dating/dating.html" class="mnav-link${path === '/dating/dating.html' ? ' active' : ''}" id="mnavDatingLink">
|
||||
<span class="mnav-icon">${I('DATING') || '♥'}</span><span>Dating</span></a>
|
||||
${lnk('/dating/besucher.html', '', 'Besucher')}
|
||||
${lnk('/dating/likes.html', '', 'Likes' )}
|
||||
${lnk('/dating/matches.html', '', 'Matches' )}
|
||||
`,
|
||||
},
|
||||
{
|
||||
label: 'Vanilla Game',
|
||||
prefixes: ['/games/vanilla/'],
|
||||
html: `
|
||||
${lnk('/games/vanilla/neuvanilla.html', 'PLAY_NEW', 'Neue Session', 'mnavVanillaNeu' )}
|
||||
<a href="#" class="mnav-link" id="mnavVanillaAktiv" style="display:none">
|
||||
<span class="mnav-icon">${I('WAITING') || ''}</span><span>Aktive Session</span></a>
|
||||
<a href="/games/vanilla/vanillaingame.html" class="mnav-link${path === '/games/vanilla/vanillaingame.html' ? ' active' : ''}"
|
||||
id="mnavVanillaImSpiel" style="display:none">
|
||||
<span class="mnav-icon">${I('PLAY_ACTIVE') || ''}</span><span>Im Spiel</span></a>
|
||||
${lnk('/games/vanilla/aufgaben.html', 'CHECK', 'Aufgaben' )}
|
||||
${lnk('/games/vanilla/toys.html', 'TOYS', 'Toys' )}
|
||||
${lnk('/games/vanilla/entdecken.html', 'DISCOVER', 'Entdecken' )}
|
||||
`,
|
||||
},
|
||||
{
|
||||
label: 'BDSM Game',
|
||||
prefixes: ['/games/bdsm/'],
|
||||
html: `
|
||||
${lnk('/games/bdsm/neubdsm.html', 'PLAY_NEW', 'Neue Session', 'mnavBdsmNeu' )}
|
||||
<a href="#" class="mnav-link" id="mnavBdsmAktiv" style="display:none">
|
||||
<span class="mnav-icon">${I('WAITING') || ''}</span><span>Aktive Session</span></a>
|
||||
<a href="/games/bdsm/bdsmingame.html" class="mnav-link${path === '/games/bdsm/bdsmingame.html' ? ' active' : ''}"
|
||||
id="mnavBdsmImSpiel" style="display:none">
|
||||
<span class="mnav-icon">${I('PLAY_ACTIVE') || ''}</span><span>Im Spiel</span></a>
|
||||
${lnk('/games/bdsm/aufgaben.html', 'CHECK', 'Aufgaben' )}
|
||||
${lnk('/games/bdsm/toys.html', 'TOYS', 'Toys' )}
|
||||
${lnk('/games/bdsm/entdecken.html', 'DISCOVER', 'Entdecken' )}
|
||||
`,
|
||||
},
|
||||
{
|
||||
label: 'Chastity Game',
|
||||
prefixes: ['/games/chastity/'],
|
||||
html: `
|
||||
${lnk('/games/chastity/neulock.html', 'NEW_LOCK', 'Neues Lock', 'mnavChastityNeu' )}
|
||||
<a href="#" class="mnav-link" id="mnavChastityAktiv" style="display:none">
|
||||
<span class="mnav-icon">${I('ACTIVE_LOCK') || ''}</span><span>Aktives Lock</span></a>
|
||||
${lnk('/games/chastity/communityvotes.html', 'VOTES', 'Community Votes' )}
|
||||
${lnk('/games/chastity/meine-locks.html', 'LOCK', 'Meine Vorlagen' )}
|
||||
${lnk('/games/chastity/entdecken-vorlagen.html', 'DISCOVER', 'Entdecken' )}
|
||||
${lnk('/games/chastity/keyholder-finden.html', 'FRIENDS', 'Keyholder finden' )}
|
||||
${lnk('/games/chastity/keyholder.html', 'KEY', 'Keyholder' )}
|
||||
${lnk('/games/chastity/unlock-history.html', 'HISTORY', 'Code-Historie' )}
|
||||
`,
|
||||
},
|
||||
{
|
||||
label: 'Konto',
|
||||
prefixes: ['/konto/', '/help/'],
|
||||
html: `
|
||||
${lnk('/konto/einstellungen.html', 'SETTINGS', 'Einstellungen')}
|
||||
${lnk('/help/overview.html', 'HELP', 'Hilfe' )}
|
||||
<a href="/login/logout" class="mnav-link mnav-link--danger">
|
||||
<span class="mnav-icon">${I('LOGOUT') || ''}</span><span>Abmelden</span></a>
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
// ── Mobile Topbar ─────────────────────────────────────────────────────────
|
||||
const topbarEl = document.createElement('div');
|
||||
topbarEl.className = 'mobile-topbar';
|
||||
topbarEl.id = 'mobileTopbar';
|
||||
topbarEl.innerHTML = `
|
||||
<a href="/userhome.html" class="mobile-topbar-logo">
|
||||
<img src="/img/icon.png" alt="xXx Sphere">
|
||||
</a>
|
||||
<div class="mobile-topbar-actions">
|
||||
<a href="/community/nachrichten.html" class="mobile-tb-btn" title="Nachrichten">
|
||||
${I('MESSAGES')}
|
||||
<span class="mobile-tb-badge" id="mobTbMsgBadge"></span>
|
||||
</a>
|
||||
<a href="/community/benachrichtigungen.html" class="mobile-tb-btn" title="Benachrichtigungen">
|
||||
${I('NOTIFICATIONS')}
|
||||
<span class="mobile-tb-badge" id="mobTbNotifBadge"></span>
|
||||
</a>
|
||||
<a href="/games/common/einladungen.html" class="mobile-tb-btn" title="Einladungen">
|
||||
${I('INVITATIONS')}
|
||||
<span class="mobile-tb-badge" id="mobTbInvBadge"></span>
|
||||
</a>
|
||||
<a href="/community/benutzer.html" class="mobile-tb-btn" id="mobTbProfileBtn" title="Profil">
|
||||
${I('PROFILE')}
|
||||
</a>
|
||||
<button class="mobile-tb-btn" id="mobMenuToggle" aria-label="Menü">
|
||||
${I('MENU') || '☰'}
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
document.body.insertAdjacentElement('afterbegin', topbarEl);
|
||||
|
||||
// ── Overlay ───────────────────────────────────────────────────────────────
|
||||
const backdropEl = document.createElement('div');
|
||||
backdropEl.className = 'mob-menu-backdrop';
|
||||
backdropEl.id = 'mobMenuBackdrop';
|
||||
|
||||
const panelEl = document.createElement('div');
|
||||
panelEl.className = 'mob-menu-panel';
|
||||
panelEl.id = 'mobMenuPanel';
|
||||
panelEl.innerHTML = SECTIONS.map(s => {
|
||||
const isOpen = s.prefixes.some(p => path.startsWith(p) || path === p);
|
||||
return `
|
||||
<div class="mnav-section${isOpen ? ' open' : ''}">
|
||||
<div class="mnav-section-header">
|
||||
<span>${s.label}</span>
|
||||
<span class="mnav-section-arrow">▶</span>
|
||||
</div>
|
||||
<div class="mnav-section-body">${s.html}</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
document.body.appendChild(backdropEl);
|
||||
document.body.appendChild(panelEl);
|
||||
|
||||
// ── Accordion (nur eine Sektion gleichzeitig offen) ──────────────────────
|
||||
panelEl.querySelectorAll('.mnav-section-header').forEach(h => {
|
||||
h.addEventListener('click', () => {
|
||||
const section = h.closest('.mnav-section');
|
||||
const isOpen = section.classList.contains('open');
|
||||
panelEl.querySelectorAll('.mnav-section').forEach(s => s.classList.remove('open'));
|
||||
if (!isOpen) section.classList.add('open');
|
||||
});
|
||||
});
|
||||
|
||||
// ── Open / Close ──────────────────────────────────────────────────────────
|
||||
function openMenu() {
|
||||
panelEl.classList.add('open');
|
||||
backdropEl.classList.add('open');
|
||||
}
|
||||
function closeMenu() {
|
||||
panelEl.classList.remove('open');
|
||||
backdropEl.classList.remove('open');
|
||||
}
|
||||
|
||||
document.getElementById('mobMenuToggle').addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
panelEl.classList.contains('open') ? closeMenu() : openMenu();
|
||||
});
|
||||
backdropEl.addEventListener('click', closeMenu);
|
||||
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeMenu(); });
|
||||
panelEl.querySelectorAll('.mnav-link').forEach(l => {
|
||||
l.addEventListener('click', () => { if (l.getAttribute('href') !== '#') closeMenu(); });
|
||||
});
|
||||
|
||||
// ── Badges ────────────────────────────────────────────────────────────────
|
||||
function setBadge(id, n) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
el.textContent = n > 99 ? '99+' : n;
|
||||
el.style.display = n > 0 ? 'inline-block' : 'none';
|
||||
}
|
||||
|
||||
fetch('/social/messages/unread/count').then(r => r.ok ? r.json() : 0).then(n => {
|
||||
setBadge('mobTbMsgBadge', n); setBadge('mnavBadgeMsg', n);
|
||||
}).catch(() => {});
|
||||
|
||||
fetch('/notifications/unread/count').then(r => r.ok ? r.json() : 0).then(n => {
|
||||
setBadge('mobTbNotifBadge', n); setBadge('mnavBadgeNotif', n);
|
||||
}).catch(() => {});
|
||||
|
||||
Promise.all([
|
||||
fetch('/lockee/invitations/mine/count').then(r => r.ok ? r.json() : 0).catch(() => 0),
|
||||
fetch('/keyholder/invitations/mine/count').then(r => r.ok ? r.json() : 0).catch(() => 0),
|
||||
fetch('/bdsm/einladung/pending/count').then(r => r.ok ? r.json() : 0).catch(() => 0),
|
||||
fetch('/vanilla/einladung/pending/count').then(r => r.ok ? r.json() : 0).catch(() => 0),
|
||||
]).then(([l, k, b, v]) => {
|
||||
const n = l + k + b + v;
|
||||
setBadge('mobTbInvBadge', n); setBadge('mnavBadgeInv', n);
|
||||
}).catch(() => {});
|
||||
|
||||
fetch('/social/friends/pending/count').then(r => r.ok ? r.json() : 0)
|
||||
.then(n => setBadge('mnavBadgeFriends', n)).catch(() => {});
|
||||
|
||||
Promise.all([
|
||||
fetch('/gruppen/requests/pending/count').then(r => r.ok ? r.json() : 0).catch(() => 0),
|
||||
fetch('/gruppen/reports/pending/count').then(r => r.ok ? r.json() : 0).catch(() => 0),
|
||||
]).then(([j, rep]) => setBadge('mnavBadgeGruppen', j + rep)).catch(() => {});
|
||||
|
||||
// ── User / Dynamische Links ───────────────────────────────────────────────
|
||||
fetch('/login/me').then(r => r.ok ? r.json() : null).then(async user => {
|
||||
if (!user) return;
|
||||
|
||||
// Profilbild
|
||||
const profileBtn = document.getElementById('mobTbProfileBtn');
|
||||
if (profileBtn) {
|
||||
profileBtn.href = '/community/benutzer.html?userId=' + user.userId;
|
||||
if (user.profilePicture) {
|
||||
profileBtn.innerHTML =
|
||||
`<img src="data:image/png;base64,${user.profilePicture}" class="mobile-tb-avatar" alt="">`;
|
||||
}
|
||||
}
|
||||
|
||||
// Admin
|
||||
if (user.admin) {
|
||||
const el = document.getElementById('mnavAdminLink');
|
||||
if (el) el.style.display = '';
|
||||
}
|
||||
|
||||
// Dating
|
||||
const datingLink = document.getElementById('mnavDatingLink');
|
||||
if (datingLink) {
|
||||
datingLink.href = user.datingAktiv
|
||||
? '/dating/dating.html'
|
||||
: '/konto/einstellungen.html#sec-dating';
|
||||
}
|
||||
|
||||
const hide = id => { const el = document.getElementById(id); if (el) el.style.display = 'none'; };
|
||||
const show = id => { const el = document.getElementById(id); if (el) el.style.display = ''; };
|
||||
const setHref = (id, h) => { const el = document.getElementById(id); if (el) el.href = h; };
|
||||
|
||||
// BDSM
|
||||
try {
|
||||
const r = await fetch('/bdsm/einladung/meine-aktive');
|
||||
if (r.ok) {
|
||||
const aktiv = await r.json();
|
||||
hide('mnavBdsmNeu'); hide('mnavBdsmImSpiel'); show('mnavBdsmAktiv');
|
||||
setHref('mnavBdsmAktiv', aktiv.sessionId ? '/games/bdsm/bdsmingame.html' : '/games/bdsm/neubdsm.html');
|
||||
} else {
|
||||
const sr = await fetch(`/bdsm?userId=${user.userId}`);
|
||||
if (sr.status === 200) { hide('mnavBdsmNeu'); show('mnavBdsmImSpiel'); }
|
||||
else show('mnavBdsmNeu');
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
// Vanilla
|
||||
try {
|
||||
const r = await fetch('/vanilla/einladung/meine-aktive');
|
||||
if (r.ok) {
|
||||
const aktiv = await r.json();
|
||||
hide('mnavVanillaNeu'); hide('mnavVanillaImSpiel'); show('mnavVanillaAktiv');
|
||||
setHref('mnavVanillaAktiv', aktiv.sessionId ? '/games/vanilla/vanillaingame.html' : '/games/vanilla/neuvanilla.html');
|
||||
} else {
|
||||
const sr = await fetch(`/vanilla?userId=${user.userId}`);
|
||||
if (sr.status === 200) { hide('mnavVanillaNeu'); show('mnavVanillaImSpiel'); }
|
||||
else show('mnavVanillaNeu');
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
// Chastity
|
||||
try {
|
||||
const r = await fetch('/keyholder/mylock');
|
||||
if (r.ok) {
|
||||
const lock = await r.json();
|
||||
show('mnavChastityAktiv');
|
||||
setHref('mnavChastityAktiv', '/games/chastity/activelock.html?lockId=' + lock.lockId);
|
||||
}
|
||||
} catch (_) {}
|
||||
}).catch(() => {});
|
||||
})();
|
||||
@@ -8,23 +8,48 @@
|
||||
/* ── Burger-Button ── */
|
||||
.nav-burger {
|
||||
display: inline-flex; align-items: center; gap: 0.45rem;
|
||||
padding: 0.35rem 0.8rem 0.35rem 0.6rem;
|
||||
padding: 0.46rem 0.8rem 0.46rem 0.6rem;
|
||||
background: none; border: 1px solid var(--color-secondary);
|
||||
border-radius: 8px; cursor: pointer;
|
||||
color: var(--color-text); font-size: 0.88rem; font-weight: 600;
|
||||
flex-shrink: 0; transition: border-color 0.15s, color 0.15s;
|
||||
margin-right: 0.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
.nav-burger:hover { border-color: var(--color-primary); color: var(--color-primary); }
|
||||
.nav-burger-icon { font-size: 1.05rem; line-height: 1; }
|
||||
.nav-burger-icon {
|
||||
font-size: 1.05rem; line-height: 1;
|
||||
position: relative;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
width: 1.2em; height: 1.2em;
|
||||
}
|
||||
.nav-burger-icon-menu,
|
||||
.nav-burger-icon-close {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
transition: opacity 0.15s, transform 0.15s;
|
||||
}
|
||||
.nav-burger-icon-close { opacity: 0; transform: rotate(-45deg); }
|
||||
#topbar.nav-menu-open .nav-burger .nav-burger-icon-menu { opacity: 0; transform: rotate(45deg); }
|
||||
#topbar.nav-menu-open .nav-burger .nav-burger-icon-close { opacity: 1; transform: rotate(0deg); }
|
||||
|
||||
/* ── Backdrop ── */
|
||||
.nav-backdrop {
|
||||
display: none; position: fixed; inset: 0; z-index: 498;
|
||||
position: fixed; inset: 0; z-index: 498;
|
||||
background: rgba(0,0,0,0);
|
||||
pointer-events: none;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
.nav-backdrop.open {
|
||||
background: rgba(0,0,0,0.55);
|
||||
pointer-events: auto;
|
||||
}
|
||||
.nav-backdrop.open { display: block; }
|
||||
|
||||
/* ── Topbar: untere Rundung entfernen wenn Menü offen ── */
|
||||
#topbar {
|
||||
transition: border-radius 0.22s ease, border-bottom-color 0.22s ease;
|
||||
}
|
||||
#topbar.nav-menu-open {
|
||||
border-radius: 12px 12px 0 0;
|
||||
border-bottom-color: transparent;
|
||||
@@ -38,11 +63,21 @@
|
||||
border-top: none;
|
||||
border-radius: 0 0 12px 12px;
|
||||
box-shadow: 0 12px 40px rgba(0,0,0,0.55);
|
||||
display: none;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 80px);
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transform: translateY(-12px);
|
||||
pointer-events: none;
|
||||
transition: opacity 0.22s ease, transform 0.22s ease, visibility 0s linear 0.22s;
|
||||
}
|
||||
.nav-dropdown.open {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
pointer-events: auto;
|
||||
transition: opacity 0.22s ease, transform 0.22s ease, visibility 0s linear 0s;
|
||||
}
|
||||
.nav-dropdown.open { display: block; }
|
||||
|
||||
/* ── 4-Spalten-Layout ── */
|
||||
.nav-columns {
|
||||
@@ -226,17 +261,17 @@
|
||||
{ href: '/games/vanilla/neuvanilla.html', icon: 'PLAY_NEW', label: 'Neue Session', id: 'navVanillaNeu' },
|
||||
{ href: '#', icon: 'WAITING', label: 'Aktive Session', id: 'navVanillaAktiv' },
|
||||
{ href: '/games/vanilla/vanillaingame.html', icon: 'PLAY_ACTIVE', label: 'Im Spiel', id: 'navVanillaImSpiel' },
|
||||
{ href: '/games/common/aufgaben.html', icon: 'CHECK', label: 'Aufgaben' },
|
||||
{ href: '/games/common/toys.html', icon: 'TOYS', label: 'Toys' },
|
||||
{ href: '/games/chastity/entdecken.html', icon: 'DISCOVER', label: 'Entdecken' },
|
||||
{ href: '/games/vanilla/aufgaben.html', icon: 'CHECK', label: 'Aufgaben' },
|
||||
{ href: '/games/vanilla/toys.html', icon: 'TOYS', label: 'Toys' },
|
||||
{ href: '/games/vanilla/entdecken.html', icon: 'DISCOVER', label: 'Entdecken' },
|
||||
])}
|
||||
${gameGroup('BDSM', 'BDSM Game', [
|
||||
{ href: '/games/bdsm/neubdsm.html', icon: 'PLAY_NEW', label: 'Neue Session', id: 'navBdsmNeu' },
|
||||
{ href: '#', icon: 'WAITING', label: 'Aktive Session', id: 'navBdsmAktiv' },
|
||||
{ href: '/games/bdsm/bdsmingame.html', icon: 'PLAY_ACTIVE', label: 'Im Spiel', id: 'navBdsmImSpiel' },
|
||||
{ href: '/games/common/aufgaben.html?mode=bdsm', icon: 'CHECK', label: 'Aufgaben' },
|
||||
{ href: '/games/common/toys.html', icon: 'TOYS', label: 'Toys' },
|
||||
{ href: '/games/chastity/entdecken.html', icon: 'DISCOVER', label: 'Entdecken' },
|
||||
{ href: '/games/bdsm/neubdsm.html', icon: 'PLAY_NEW', label: 'Neue Session', id: 'navBdsmNeu' },
|
||||
{ href: '#', icon: 'WAITING', label: 'Aktive Session', id: 'navBdsmAktiv' },
|
||||
{ href: '/games/bdsm/bdsmingame.html', icon: 'PLAY_ACTIVE', label: 'Im Spiel', id: 'navBdsmImSpiel' },
|
||||
{ href: '/games/bdsm/aufgaben.html', icon: 'CHECK', label: 'Aufgaben' },
|
||||
{ href: '/games/bdsm/toys.html', icon: 'TOYS', label: 'Toys' },
|
||||
{ href: '/games/bdsm/entdecken.html', icon: 'DISCOVER', label: 'Entdecken' },
|
||||
])}
|
||||
${gameGroup('CHASTITY', 'Chastity Game', [
|
||||
{ href: '/games/chastity/neulock.html', icon: 'NEW_LOCK', label: 'Neues Lock', id: 'navChastityNeu' },
|
||||
@@ -330,8 +365,8 @@
|
||||
btn.className = 'nav-burger';
|
||||
btn.id = 'navBurgerBtn';
|
||||
btn.setAttribute('aria-label', 'Menü öffnen');
|
||||
btn.innerHTML = `<span class="nav-burger-icon">${I('MENU') || '☰'}</span><span class="nav-burger-text">Menü</span>`;
|
||||
topbarLeft.prepend(btn);
|
||||
btn.innerHTML = `<span class="nav-burger-icon"><span class="nav-burger-icon-menu">${I('MENU') || '≡'}</span><span class="nav-burger-icon-close">${I('CLOSE') || 'x'}</span></span><span class="nav-burger-text">Menü</span>`;
|
||||
topbarLeft.append(btn);
|
||||
btn.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
const dd = document.getElementById('navDropdown');
|
||||
@@ -460,4 +495,6 @@
|
||||
}
|
||||
loadScript('/js/topbar.js');
|
||||
loadScript('/js/social-sidebar.js');
|
||||
loadScript('/js/section-nav.js');
|
||||
loadScript('/js/mobile-nav.js');
|
||||
})();
|
||||
|
||||
243
bin/main/static/js/section-nav.js
Normal file
243
bin/main/static/js/section-nav.js
Normal file
@@ -0,0 +1,243 @@
|
||||
(function () {
|
||||
const path = window.location.pathname;
|
||||
const search = window.location.search;
|
||||
const I = window.IC || function () { return ''; };
|
||||
|
||||
// ── Bereichs-Definitionen ────────────────────────────────────────────────
|
||||
const SECTIONS = {
|
||||
social: {
|
||||
prefixes: ['/community/'],
|
||||
exclude: [
|
||||
'/community/nachrichten.html',
|
||||
'/community/benachrichtigungen.html',
|
||||
'/community/einladungen.html',
|
||||
],
|
||||
items: [
|
||||
{ href: '/community/feed.html', icon: 'FEED', label: 'Feed' },
|
||||
{ href: '/community/freunde.html', icon: 'FRIENDS', label: 'Freunde' },
|
||||
{ href: '/community/gruppen.html', icon: 'GROUPS', label: 'Gruppen' },
|
||||
{ href: '/community/locations.html', icon: 'LOCATION', label: 'Locations' },
|
||||
{ href: '/community/events.html', icon: 'EVENT', label: 'Veranstaltungen' },
|
||||
],
|
||||
},
|
||||
dating: {
|
||||
prefixes: ['/dating/'],
|
||||
items: [
|
||||
{ href: '/dating/dating.html', icon: 'DATING', label: 'Dating', id: 'snavDatingLink' },
|
||||
{ href: '/dating/besucher.html', icon: '', label: 'Besucher' },
|
||||
{ href: '/dating/likes.html', icon: '', label: 'Likes' },
|
||||
{ href: '/dating/matches.html', icon: '', label: 'Matches' },
|
||||
],
|
||||
},
|
||||
vanilla: {
|
||||
prefixes: ['/games/vanilla/'],
|
||||
items: [
|
||||
{ href: '/games/vanilla/neuvanilla.html', icon: 'PLAY_NEW', label: 'Neue Session', id: 'snavVanillaNeu' },
|
||||
{ href: '#', icon: 'WAITING', label: 'Aktive Session', id: 'snavVanillaAktiv', hidden: true },
|
||||
{ href: '/games/vanilla/vanillaingame.html', icon: 'PLAY_ACTIVE', label: 'Im Spiel', id: 'snavVanillaImSpiel', hidden: true },
|
||||
{ href: '/games/vanilla/aufgaben.html', icon: 'CHECK', label: 'Aufgaben' },
|
||||
{ href: '/games/vanilla/toys.html', icon: 'TOYS', label: 'Toys' },
|
||||
{ href: '/games/vanilla/entdecken.html', icon: 'DISCOVER', label: 'Entdecken' },
|
||||
],
|
||||
},
|
||||
bdsm: {
|
||||
prefixes: ['/games/bdsm/'],
|
||||
items: [
|
||||
{ href: '/games/bdsm/neubdsm.html', icon: 'PLAY_NEW', label: 'Neue Session', id: 'snavBdsmNeu' },
|
||||
{ href: '#', icon: 'WAITING', label: 'Aktive Session', id: 'snavBdsmAktiv', hidden: true },
|
||||
{ href: '/games/bdsm/bdsmingame.html', icon: 'PLAY_ACTIVE', label: 'Im Spiel', id: 'snavBdsmImSpiel', hidden: true },
|
||||
{ href: '/games/bdsm/aufgaben.html', icon: 'CHECK', label: 'Aufgaben' },
|
||||
{ href: '/games/bdsm/toys.html', icon: 'TOYS', label: 'Toys' },
|
||||
{ href: '/games/bdsm/entdecken.html', icon: 'DISCOVER', label: 'Entdecken' },
|
||||
],
|
||||
},
|
||||
chastity: {
|
||||
prefixes: ['/games/chastity/'],
|
||||
items: [
|
||||
{ href: '/games/chastity/neulock.html', icon: 'NEW_LOCK', label: 'Neues Lock', id: 'snavChastityNeu' },
|
||||
{ href: '#', icon: 'ACTIVE_LOCK', label: 'Aktives Lock', id: 'snavChastityAktiv', hidden: true },
|
||||
{ href: '/games/chastity/communityvotes.html', icon: 'VOTES', label: 'Community Votes' },
|
||||
{ href: '/games/chastity/meine-locks.html', icon: 'LOCK', label: 'Meine Vorlagen' },
|
||||
{ href: '/games/chastity/entdecken-vorlagen.html', icon: 'DISCOVER', label: 'Entdecken' },
|
||||
{ href: '/games/chastity/keyholder-finden.html', icon: 'FRIENDS', label: 'Keyholder finden' },
|
||||
{ href: '/games/chastity/keyholder.html', icon: 'KEY', label: 'Keyholder' },
|
||||
{ href: '/games/chastity/unlock-history.html', icon: 'HISTORY', label: 'Code-Historie' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
// ── Aktiven Bereich ermitteln ────────────────────────────────────────────
|
||||
const sectionKey = Object.keys(SECTIONS).find(k => {
|
||||
const s = SECTIONS[k];
|
||||
if (!s.prefixes.some(p => path.startsWith(p))) return false;
|
||||
if (s.exclude && s.exclude.includes(path)) return false;
|
||||
return true;
|
||||
});
|
||||
if (!sectionKey) return;
|
||||
const section = SECTIONS[sectionKey];
|
||||
|
||||
// ── CSS ──────────────────────────────────────────────────────────────────
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.section-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.15rem;
|
||||
flex-wrap: wrap;
|
||||
padding: 0 0 0.6rem 0;
|
||||
}
|
||||
.section-nav-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.3rem 0.75rem;
|
||||
border-radius: 7px;
|
||||
text-decoration: none;
|
||||
color: var(--color-muted);
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
transition: background 0.12s, color 0.12s;
|
||||
}
|
||||
.section-nav-link:hover {
|
||||
background: var(--color-secondary);
|
||||
color: var(--color-text);
|
||||
}
|
||||
.section-nav-link.active {
|
||||
color: var(--color-primary);
|
||||
background: rgba(var(--color-primary-rgb, 233,69,96), 0.09);
|
||||
font-weight: 600;
|
||||
}
|
||||
.section-nav-icon {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.section-nav--icons-only .section-nav-label { display: none; }
|
||||
.section-nav--icons-only .section-nav-link { gap: 0; padding: 0.3rem 0.55rem; }
|
||||
.section-nav-sep {
|
||||
border: none;
|
||||
border-top: 1px solid var(--color-secondary);
|
||||
margin: 0 0 1.25rem 0;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// ── Aktiv-Erkennung ──────────────────────────────────────────────────────
|
||||
function isActive(item) {
|
||||
if (item.href === '#') return false;
|
||||
const [itemPath, itemQuery] = item.href.split('?');
|
||||
if (itemQuery) return path === itemPath && search === '?' + itemQuery;
|
||||
return path === itemPath;
|
||||
}
|
||||
|
||||
// ── Nav bauen ────────────────────────────────────────────────────────────
|
||||
const navEl = document.createElement('nav');
|
||||
navEl.className = 'section-nav';
|
||||
|
||||
section.items.forEach(item => {
|
||||
const a = document.createElement('a');
|
||||
a.href = item.href;
|
||||
a.className = 'section-nav-link' + (isActive(item) ? ' active' : '');
|
||||
if (item.id) a.id = item.id;
|
||||
if (item.hidden) a.style.display = 'none';
|
||||
a.title = item.label;
|
||||
if (item.icon) a.innerHTML += `<span class="section-nav-icon">${I(item.icon) || ''}</span>`;
|
||||
a.innerHTML += `<span class="section-nav-label">${item.label}</span>`;
|
||||
navEl.appendChild(a);
|
||||
});
|
||||
|
||||
const sep = document.createElement('hr');
|
||||
sep.className = 'section-nav-sep';
|
||||
|
||||
// ── Einfügen in .main ────────────────────────────────────────────────────
|
||||
function checkOverflow() {
|
||||
// Immer zuerst Labels einblenden und ohne wrap messen —
|
||||
// so gibt es keine Feedback-Schleife durch Größenänderung nach dem Umschalten
|
||||
navEl.classList.remove('section-nav--icons-only');
|
||||
navEl.style.flexWrap = 'nowrap';
|
||||
const overflows = navEl.scrollWidth > navEl.clientWidth;
|
||||
navEl.style.flexWrap = '';
|
||||
if (overflows) navEl.classList.add('section-nav--icons-only');
|
||||
}
|
||||
|
||||
function inject() {
|
||||
const main = document.querySelector('.main');
|
||||
if (!main) { setTimeout(inject, 30); return; }
|
||||
main.insertBefore(sep, main.firstChild);
|
||||
main.insertBefore(navEl, sep);
|
||||
loadDynamic();
|
||||
// Overflow-Erkennung beim Laden und bei Größenänderung
|
||||
requestAnimationFrame(checkOverflow);
|
||||
new ResizeObserver(checkOverflow).observe(navEl);
|
||||
}
|
||||
inject();
|
||||
|
||||
// ── Dynamische Elemente (analog nav.js) ──────────────────────────────────
|
||||
function hide(id) { const el = document.getElementById(id); if (el) el.style.display = 'none'; }
|
||||
function show(id) { const el = document.getElementById(id); if (el) el.style.display = ''; }
|
||||
function setHref(id, h) { const el = document.getElementById(id); if (el) el.href = h; }
|
||||
|
||||
async function loadDynamic() {
|
||||
try {
|
||||
const res = await fetch('/login/me');
|
||||
if (!res.ok) return;
|
||||
const user = await res.json();
|
||||
if (!user) return;
|
||||
|
||||
// Dating-Link
|
||||
const datingLink = document.getElementById('snavDatingLink');
|
||||
if (datingLink) {
|
||||
datingLink.href = user.datingAktiv
|
||||
? '/dating/dating.html'
|
||||
: '/konto/einstellungen.html#sec-dating';
|
||||
}
|
||||
|
||||
// BDSM
|
||||
if (sectionKey === 'bdsm') {
|
||||
try {
|
||||
const r = await fetch('/bdsm/einladung/meine-aktive');
|
||||
if (r.ok) {
|
||||
const aktiv = await r.json();
|
||||
hide('snavBdsmNeu'); hide('snavBdsmImSpiel');
|
||||
show('snavBdsmAktiv');
|
||||
setHref('snavBdsmAktiv', aktiv.sessionId ? '/games/bdsm/bdsmingame.html' : '/games/bdsm/neubdsm.html');
|
||||
} else {
|
||||
const sr = await fetch(`/bdsm?userId=${user.userId}`);
|
||||
if (sr.status === 200) { hide('snavBdsmNeu'); show('snavBdsmImSpiel'); }
|
||||
else show('snavBdsmNeu');
|
||||
}
|
||||
} catch (_) { show('snavBdsmNeu'); }
|
||||
}
|
||||
|
||||
// Vanilla
|
||||
if (sectionKey === 'vanilla') {
|
||||
try {
|
||||
const r = await fetch('/vanilla/einladung/meine-aktive');
|
||||
if (r.ok) {
|
||||
const aktiv = await r.json();
|
||||
hide('snavVanillaNeu'); hide('snavVanillaImSpiel');
|
||||
show('snavVanillaAktiv');
|
||||
setHref('snavVanillaAktiv', aktiv.sessionId ? '/games/vanilla/vanillaingame.html' : '/games/vanilla/neuvanilla.html');
|
||||
} else {
|
||||
const sr = await fetch(`/vanilla?userId=${user.userId}`);
|
||||
if (sr.status === 200) { hide('snavVanillaNeu'); show('snavVanillaImSpiel'); }
|
||||
else show('snavVanillaNeu');
|
||||
}
|
||||
} catch (_) { show('snavVanillaNeu'); }
|
||||
}
|
||||
|
||||
// Chastity
|
||||
if (sectionKey === 'chastity') {
|
||||
try {
|
||||
const r = await fetch('/keyholder/mylock');
|
||||
if (r.ok) {
|
||||
const lock = await r.json();
|
||||
show('snavChastityAktiv');
|
||||
setHref('snavChastityAktiv', '/games/chastity/activelock.html?lockId=' + lock.lockId);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
})();
|
||||
@@ -10,9 +10,9 @@
|
||||
{ href: '/games/vanilla/neuvanilla.html', icon: I('PLAY_NEW'), label: 'Neue Session', id: 'navVanillaNeu' },
|
||||
{ href: '#', icon: I('WAITING'), label: 'Aktive Session', id: 'navVanillaAktiv' },
|
||||
{ href: '/games/vanilla/vanillaingame.html', icon: I('PLAY_ACTIVE'), label: 'Im Spiel', id: 'navVanillaImSpiel' },
|
||||
{ href: '/games/common/aufgaben.html', icon: I('CHECK'), label: 'Aufgaben' },
|
||||
{ href: '/games/common/toys.html', icon: I('TOYS'), label: 'Toys' },
|
||||
{ href: '/games/chastity/entdecken.html', icon: I('DISCOVER'), label: 'Entdecken' },
|
||||
{ href: '/games/vanilla/aufgaben.html', icon: I('CHECK'), label: 'Aufgaben' },
|
||||
{ href: '/games/vanilla/toys.html', icon: I('TOYS'), label: 'Toys' },
|
||||
{ href: '/games/vanilla/entdecken.html', icon: I('DISCOVER'), label: 'Entdecken' },
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -22,9 +22,9 @@
|
||||
{ href: '/games/bdsm/neubdsm.html', icon: I('PLAY_NEW'), label: 'Neue Session', id: 'navBdsmNeu' },
|
||||
{ href: '#', icon: I('WAITING'), label: 'Aktive Session', id: 'navBdsmAktiv' },
|
||||
{ href: '/games/bdsm/bdsmingame.html', icon: I('PLAY_ACTIVE'), label: 'Im Spiel', id: 'navBdsmImSpiel' },
|
||||
{ href: '/games/common/aufgaben.html?mode=bdsm', icon: I('CHECK'), label: 'Aufgaben' },
|
||||
{ href: '/games/common/toys.html', icon: I('TOYS'), label: 'Toys' },
|
||||
{ href: '/games/chastity/entdecken.html', icon: I('DISCOVER'), label: 'Entdecken' },
|
||||
{ href: '/games/bdsm/aufgaben.html', icon: I('CHECK'), label: 'Aufgaben' },
|
||||
{ href: '/games/bdsm/toys.html', icon: I('TOYS'), label: 'Toys' },
|
||||
{ href: '/games/bdsm/entdecken.html', icon: I('DISCOVER'), label: 'Entdecken' },
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user