(function () { if (document.querySelector('.topbar')) return; function esc(s) { return String(s ?? '') .replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } // ── Warten bis app-wrapper existiert (sidebar.js läuft synchron davor) ── function init() { const appWrapper = document.querySelector('.app-wrapper'); if (!appWrapper) { setTimeout(init, 30); return; } injectHTML(appWrapper); loadProfile(); setupSearch(); setupOverlayButtons(); loadInitialBadges(); } setTimeout(init, 0); // ── HTML Struktur ── function injectHTML(appWrapper) { const topbar = document.createElement('div'); topbar.className = 'topbar'; topbar.id = 'topbar'; topbar.innerHTML = `
xXx Sphere
${IC('SEARCH')}
`; appWrapper.insertAdjacentElement('beforebegin', topbar); // Panel-Overlays am Ende von body einfügen document.body.insertAdjacentHTML('beforeend', `
${IC('MESSAGES')} Nachrichten
${IC('NOTIFICATIONS')} Benachrichtigungen
${IC('INVITATIONS')} Einladungen
Konto
${IC('PROFILE')}

`); } function IC(key) { return window.IC ? window.IC(key) : (window.ICONS?.[key]?.value || ''); } // ── Profil laden ── function loadProfile() { fetch('/login/me') .then(r => r.ok ? r.json() : null) .then(user => { if (!user) return; const nameEl = document.getElementById('topbarUsername'); if (nameEl) nameEl.textContent = user.name; const avatarWrap = document.getElementById('topbarAvatarWrap'); if (avatarWrap && user.profilePicture) { avatarWrap.innerHTML = ``; } const panelName = document.getElementById('topbarPanelName'); if (panelName) panelName.textContent = user.name; const panelAvatar = document.getElementById('topbarPanelAvatarWrap'); if (panelAvatar && user.profilePicture) { panelAvatar.innerHTML = ``; } const profileLink = document.getElementById('topbarProfileLink'); if (profileLink && user.userId) profileLink.href = '/community/benutzer.html?userId=' + user.userId; }) .catch(() => {}); } // ── Suche ── function setupSearch() { const input = document.getElementById('topbarSearchInput'); const overlay = document.getElementById('topbarSearchOverlay'); if (!input || !overlay) return; let timer; input.addEventListener('input', () => { clearTimeout(timer); const q = input.value.trim(); if (q.length < 2) { overlay.innerHTML = ''; overlay.classList.remove('open'); return; } overlay.innerHTML = '
Suche…
'; overlay.classList.add('open'); timer = setTimeout(() => doSearch(q, overlay), 300); }); document.addEventListener('click', e => { if (!e.target.closest('.topbar-search-wrap')) { overlay.classList.remove('open'); } }); } async function doSearch(q, overlay) { try { const tagQuery = q.startsWith('#') ? q.slice(1) : q; const [searchRes, gruppenRes, hashtagRes] = await Promise.all([ fetch('/search?q=' + encodeURIComponent(q) + '&limit=3'), fetch('/gruppen/search?q=' + encodeURIComponent(q)), fetch('/hashtags/suggest?q=' + encodeURIComponent(tagQuery) + '&limit=4') ]); const data = searchRes.ok ? await searchRes.json() : {}; const gruppen = gruppenRes.ok ? await gruppenRes.json() : []; const hashtags = hashtagRes.ok ? await hashtagRes.json() : []; const { users = [], locations = [], events = [] } = data; const gruppenSlice = gruppen.slice(0, 3); let html = ''; if (!users.length && !locations.length && !events.length && !gruppenSlice.length && !hashtags.length) { html += '
Keine Ergebnisse.
'; } if (users.length) { html += `
Personen
`; html += users.map(u => { const av = u.profilePicture ? `` : `${IC('PROFILE')}`; return ` ${av}${esc(u.name)}`; }).join(''); } if (locations.length) { html += `
Locations
`; html += locations.map(l => { const av = l.profilePicture ? `` : `📍`; return ` ${av}${esc(l.name)}`; }).join(''); } if (events.length) { html += `
Veranstaltungen
`; html += events.map(e => { const av = e.imageData ? `` : `🗓`; return ` ${av}${esc(e.title)}`; }).join(''); } if (gruppenSlice.length) { html += `
Gruppen
`; html += gruppenSlice.map(g => { const av = g.bild ? `` : `👥`; return ` ${av}${esc(g.name)}`; }).join(''); } if (hashtags.length) { html += `
Hashtags
`; html += `
`; html += hashtags.map(tag => `#${esc(tag)}` ).join(''); html += `
`; } html += `Alle Ergebnisse anzeigen →`; overlay.innerHTML = html; } catch (e) { overlay.innerHTML = '
Fehler bei der Suche.
'; } } // ── Panel-Overlays ── let _activePanel = null; function positionPanel(panel, btn) { const topbar = document.getElementById('topbar'); const tRect = topbar ? topbar.getBoundingClientRect() : btn.getBoundingClientRect(); panel.style.top = tRect.bottom + 'px'; panel.style.right = Math.max(4, window.innerWidth - tRect.right) + 'px'; panel.style.left = 'auto'; } function openPanel(panelId, btnId, loadFn) { const panel = document.getElementById(panelId); const btn = document.getElementById(btnId); if (!panel || !btn) return; if (_activePanel === panel && panel.classList.contains('open')) { closeAllPanels(); return; } if (window.__navClose) window.__navClose(); closeAllPanels(); positionPanel(panel, btn); panel.classList.add('open'); _activePanel = panel; if (loadFn) loadFn(); } function closeAllPanels() { document.querySelectorAll('.topbar-panel.open').forEach(p => p.classList.remove('open')); _activePanel = null; } window.__topbarCloseAll = closeAllPanels; document.addEventListener('click', e => { if (!e.target.closest('.topbar-panel') && !e.target.closest('.topbar-btn')) closeAllPanels(); }); document.addEventListener('keydown', e => { if (e.key === 'Escape') closeAllPanels(); }); function setupOverlayButtons() { const msgBtn = document.getElementById('topbarMsgBtn'); const notifBtn = document.getElementById('topbarNotifBtn'); const invBtn = document.getElementById('topbarInvBtn'); const profileBtn = document.getElementById('topbarProfileBtn'); if (msgBtn) msgBtn.addEventListener('click', e => { e.stopPropagation(); openPanel('topbarMsgPanel', 'topbarMsgBtn', loadMessages); }); if (notifBtn) notifBtn.addEventListener('click', e => { e.stopPropagation(); openPanel('topbarNotifPanel', 'topbarNotifBtn', loadNotifications); }); if (invBtn) invBtn.addEventListener('click', e => { e.stopPropagation(); openPanel('topbarInvPanel', 'topbarInvBtn', loadInvitations); }); if (profileBtn) profileBtn.addEventListener('click', e => { e.stopPropagation(); openPanel('topbarProfilePanel', 'topbarProfileBtn', null); }); } // ── Nachrichten ── async function loadMessages() { const body = document.getElementById('topbarMsgBody'); if (!body) return; body.innerHTML = '
Wird geladen…
'; try { const res = await fetch('/social/messages'); if (!res.ok) { body.innerHTML = '
Keine Nachrichten.
'; return; } const convos = await res.json(); if (!convos.length) { body.innerHTML = '
Noch keine Nachrichten.
'; return; } body.innerHTML = convos.slice(0, 7).map(c => { const av = c.partner?.profilePicture ? `` : `${IC('PROFILE')}`; const bold = c.unreadCount > 0 ? 'font-weight:700;' : ''; const badge = c.unreadCount > 0 ? `${c.unreadCount > 99 ? '99+' : c.unreadCount}` : ''; return ` ${av}
${esc(c.partner?.name || '')}
${esc(c.lastMessage?.text || '')}
${badge}
`; }).join(''); } catch (e) { body.innerHTML = '
Fehler beim Laden.
'; } } // ── Benachrichtigungen ── async function loadNotifications() { const body = document.getElementById('topbarNotifBody'); if (!body) return; body.innerHTML = '
Wird geladen…
'; try { const res = await fetch('/notifications'); if (!res.ok) { body.innerHTML = '
Keine Benachrichtigungen.
'; return; } const unread = (await res.json()).filter(n => !n.read); if (!unread.length) { body.innerHTML = '
Keine neuen Benachrichtigungen.
'; return; } body.innerHTML = ''; unread.forEach(n => { const el = document.createElement('div'); const tag = n.targetUrl ? 'a' : 'div'; const href = n.targetUrl ? `href="${esc(n.targetUrl)}"` : ''; const av = n.senderAvatar ? `` : `${IC('PROFILE')}`; el.innerHTML = `<${tag} ${href} class="topbar-panel-item topbar-notif-item${n.read ? '' : ' topbar-notif-item--unread'}"> ${av}
${esc(n.text)}
${n.sentAt ? new Date(n.sentAt).toLocaleString('de-DE',{dateStyle:'short',timeStyle:'short'}) : ''}
`; body.appendChild(el.firstElementChild); }); // Alle als gelesen markieren fetch('/notifications/read-all', { method: 'POST' }).then(() => setTopbarBadge('notif', 0)).catch(() => {}); } catch (e) { body.innerHTML = '
Fehler beim Laden.
'; } } window.__topbarMarkNotifRead = async function (id) { try { await fetch('/notifications/' + id + '/read', { method: 'POST' }); const el = document.querySelector(`.topbar-notif-item--unread[onclick*="${id}"]`); if (el) el.classList.remove('topbar-notif-item--unread'); const r = await fetch('/notifications/unread/count'); if (r.ok) setTopbarBadge('notif', await r.json()); } catch (e) {} }; window.__topbarMarkAllRead = async function () { try { await fetch('/notifications/read-all', { method: 'POST' }); setTopbarBadge('notif', 0); loadNotifications(); } catch (e) {} }; // ── Einladungen ── async function loadInvitations() { const body = document.getElementById('topbarInvBody'); if (!body) return; body.innerHTML = '
Wird geladen…
'; try { const [lr, kr, br, vr] = await Promise.all([ fetch('/lockee/invitations/mine'), fetch('/keyholder/invitations/mine'), fetch('/bdsm/einladung/pending'), fetch('/vanilla/einladung/pending') ]); const lockee = lr.ok ? await lr.json() : []; const kh = kr.ok ? await kr.json() : []; const bdsm = br.ok ? await br.json() : []; const vanilla = vr.ok ? await vr.json() : []; const all = [ ...lockee.map(i => ({ ...i, _type: 'lockee' })), ...kh.map(i => ({ ...i, _type: 'keyholder' })), ...bdsm.map(i => ({ ...i, _type: 'bdsm' })), ...vanilla.map(i => ({ ...i, _type: 'vanilla' })) ]; if (!all.length) { body.innerHTML = '
Keine offenen Einladungen.
'; return; } body.innerHTML = ''; all.forEach(inv => body.appendChild(buildInvCard(inv))); } catch (e) { body.innerHTML = '
Fehler beim Laden.
'; } } function buildInvCard(inv) { let typeIcon, typeName, line; if (inv._type === 'lockee') { typeIcon = IC('LOCK'); typeName = 'Lockee-Einladung'; line = inv.lockName || 'Lock'; } else if (inv._type === 'keyholder') { typeIcon = IC('KEY'); typeName = 'Keyholder-Einladung'; line = inv.lockName || 'Lock'; } else if (inv._type === 'vanilla') { typeIcon = IC('INVITATIONS'); typeName = 'Vanilla Game'; line = inv.inviterName || 'Einladung'; } else { typeIcon = IC('BDSM'); typeName = 'BDSM Game'; line = inv.senderName || 'Einladung'; } const senderPic = inv.senderAvatar || inv.lockOwnerAvatar || inv.inviterAvatar; const av = senderPic ? `` : `${IC('PROFILE')}`; const div = document.createElement('div'); div.className = 'topbar-panel-item topbar-inv-card'; div.style.cursor = 'pointer'; div.innerHTML = `${av}
${typeIcon} ${typeName}
${esc(line)}
`; div.addEventListener('click', () => { window.location.href = '/games/common/einladungen.html'; }); return div; } // ── Badge-Verwaltung ── function setTopbarBadge(type, count) { const map = { msg: 'topbarMsgBadge', notif: 'topbarNotifBadge', inv: 'topbarInvBadge' }; const el = document.getElementById(map[type]); if (!el) return; el.textContent = count > 99 ? '99+' : count; el.style.display = count > 0 ? 'inline-block' : 'none'; } // Für social-sidebar.js zugänglich window.__topbarSetBadge = setTopbarBadge; function reloadInvBadge() { 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]) => setTopbarBadge('inv', l + k + b + v)).catch(() => {}); } window.__topbarReloadInvBadge = reloadInvBadge; function loadInitialBadges() { fetch('/social/messages/unread/count').then(r => r.ok ? r.json() : 0).then(n => setTopbarBadge('msg', n)).catch(() => {}); fetch('/notifications/unread/count').then(r => r.ok ? r.json() : 0).then(n => setTopbarBadge('notif', n)).catch(() => {}); reloadInvBadge(); } })();