Login-Dropdown in Nav + Welcome-Overlay mit localStorage-Dismiss

- Login/Logout als Dropdown in der Header-Navigation (rechts)
- Dynamisch: eingeloggt = Profil/Abmelden, nicht eingeloggt = Mini-Login-Formular
- Offcanvas-Nav zeigt ebenfalls dynamisch Profil/Logout oder Registrieren/Login
- Welcome-Overlay beim ersten Besuch mit Erklärung der Seite
- Checkbox 'Nicht mehr anzeigen' speichert in localStorage
- Backdrop-Blur wie bei anderen Overlays
This commit is contained in:
Hermes Agent
2026-06-20 10:35:26 +02:00
parent 1dc63a4eda
commit 76da62af16
+265 -9
View File
@@ -2626,6 +2626,35 @@ def public_frontend():
"""Public frontend showing published submissions — SPIEL Essen inspired.""" """Public frontend showing published submissions — SPIEL Essen inspired."""
import base64 import base64
db = get_db() db = get_db()
user = _get_user_from_session(db) # für Login-Dropdown in Nav
# Hilfsfunktion: Login-Dropdown-HTML bauen
def _build_login_dropdown(u):
"""Baut das Login-Dropdown + Filter-Toggle für den Header."""
h = '<div class="header-right">'
h += '<a href="https://brettspiel-news.de" class="header-bsn-link">brettspiel-news.de</a>'
h += '<button id="filterToggle" class="filter-toggle-btn" aria-label="Filter einblenden"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" style="vertical-align:middle;"><polyline points="6 9 12 15 18 9"/></svg></button>'
if u:
name = (u.get("name") or u.get("email") or "User")[:14]
h += '<div class="login-dropdown">'
h += '<button id="loginToggle" class="login-toggle-btn" aria-label="Benutzermenü"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" style="vertical-align:middle;margin-right:4px;"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>' + name + '</button>'
h += '<div class="login-dropdown-menu" id="loginDropdown">'
h += '<a href="/profile"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" style="vertical-align:middle;margin-right:6px;"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>Profil</a>'
h += '<a href="/logout"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" style="vertical-align:middle;margin-right:6px;"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>Abmelden</a>'
h += '</div></div>'
else:
h += '<div class="login-dropdown">'
h += '<button id="loginToggle" class="login-toggle-btn" aria-label="Login"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" style="vertical-align:middle;"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></button>'
h += '<div class="login-dropdown-menu" id="loginDropdown">'
h += '<form method="post" action="/login" class="login-dropdown-form">'
h += '<input name="login_id" placeholder="E-Mail oder Telefon" required>'
h += '<input name="password" type="password" placeholder="Passwort" required>'
h += '<button type="submit">Einloggen</button>'
h += '</form>'
h += '<a href="/register" class="login-dropdown-register">📋 Neu hier? Registrieren</a>'
h += '</div></div>'
h += '</div>' # close header-right
return h
# Filters # Filters
halle_filter = request.args.get("halle", "").strip() halle_filter = request.args.get("halle", "").strip()
@@ -3119,19 +3148,72 @@ def public_frontend():
.capture-btn svg { display:block; } .capture-btn svg { display:block; }
.capture-btn:active { transform:scale(0.92); background:#f0f0f0; border-color:#20228a; } .capture-btn:active { transform:scale(0.92); background:#f0f0f0; border-color:#20228a; }
.capture-label { font-size:0.62rem; font-weight:600; color:#888; text-transform:uppercase; letter-spacing:0.04em; text-align:center; } .capture-label { font-size:0.62rem; font-weight:600; color:#888; text-transform:uppercase; letter-spacing:0.04em; text-align:center; }
/* Login-Dropdown in der Navigation */
.login-dropdown { position:relative; display:inline-block; }
.login-toggle-btn { background:rgba(255,255,255,0.12); border:1.5px solid rgba(255,255,255,0.25); border-radius:8px; color:#fff; padding:0.35rem 0.7rem; font-size:0.78rem; font-weight:600; cursor:pointer; font-family:inherit; transition:all 0.15s; white-space:nowrap; line-height:1.3; }
.login-toggle-btn:hover { background:rgba(255,255,255,0.22); border-color:rgba(255,255,255,0.5); }
.login-toggle-btn.active { background:rgba(255,255,255,0.25); border-color:#fff; }
.login-dropdown-menu { display:none; position:absolute; right:0; top:110%; background:#fff; border-radius:12px; box-shadow:0 8px 32px rgba(0,0,0,0.18); min-width:240px; z-index:300; overflow:hidden; }
.login-dropdown-menu.open { display:block; animation:fadeDown 0.2s ease; }
@keyframes fadeDown { from{opacity:0;transform:translateY(-8px);} to{opacity:1;transform:translateY(0);} }
.login-dropdown-menu a { display:block; padding:0.65rem 1.1rem; color:#1a1a2e; text-decoration:none; font-size:0.85rem; font-weight:500; transition:background 0.15s; }
.login-dropdown-menu a:hover { background:#f0ede6; }
.login-dropdown-form { padding:0.8rem 1rem 0.5rem; }
.login-dropdown-form input { width:100%; padding:0.5rem 0.7rem; margin:0.3rem 0; border:1.5px solid #ddd; border-radius:8px; font-size:0.82rem; font-family:inherit; outline:none; box-sizing:border-box; transition:border-color 0.15s; }
.login-dropdown-form input:focus { border-color:#20228a; }
.login-dropdown-form button { width:100%; padding:0.55rem; background:#20228a; color:#fff; border:none; border-radius:8px; font-size:0.82rem; font-weight:600; cursor:pointer; font-family:inherit; margin-top:0.4rem; transition:background 0.15s; }
.login-dropdown-form button:hover { background:#161e6e; }
.login-dropdown-register { border-top:1px solid #eee; font-size:0.78rem !important; color:#888 !important; text-align:center; }
.login-dropdown-register:hover { color:#20228a !important; }
.header-bsn-link { color:#aaa; text-decoration:none; font-size:0.8rem; transition:color 0.2s; }
.header-bsn-link:hover { color:#fff; }
/* Welcome Overlay */
.welcome-backdrop { position:fixed; inset:0; z-index:100000; background:rgba(0,0,0,0.4); backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px); display:none; }
.welcome-backdrop.open { display:flex; align-items:center; justify-content:center; padding:1rem; }
.welcome-dialog { background:#fff; border-radius:18px; box-shadow:0 16px 64px rgba(0,0,0,0.25); max-width:480px; width:100%; max-height:90vh; overflow-y:auto; animation:fadeUp 0.3s ease; }
@keyframes fadeUp { from{opacity:0;transform:translateY(20px);} to{opacity:1;transform:translateY(0);} }
.welcome-header { background:#20228a; color:#fff; padding:1.2rem 1.5rem; border-radius:18px 18px 0 0; font-weight:800; font-size:1.1rem; display:flex; align-items:center; gap:0.5rem; }
.welcome-body { padding:1.5rem; color:#333; line-height:1.7; font-size:0.88rem; }
.welcome-body strong { color:#20228a; }
.welcome-body .welcome-feature { display:flex; gap:0.6rem; align-items:flex-start; margin:0.7rem 0; }
.welcome-body .welcome-icon { font-size:1.2rem; flex-shrink:0; margin-top:0.1rem; }
.welcome-footer { padding:0.8rem 1.5rem 1.2rem; border-top:1px solid #f0ede6; display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:0.5rem; }
.welcome-footer label { font-size:0.78rem; color:#888; display:flex; align-items:center; gap:0.4rem; cursor:pointer; user-select:none; }
.welcome-footer label input { width:16px; height:16px; accent-color:#20228a; cursor:pointer; }
.welcome-footer button { background:#20228a; color:#fff; border:none; border-radius:10px; padding:0.55rem 1.4rem; font-size:0.85rem; font-weight:700; cursor:pointer; font-family:inherit; transition:background 0.15s; }
.welcome-footer button:hover { background:#161e6e; }
</style> </style>
</head> </head>
<body> <body>
<!-- Welcome Overlay -->
<div class="welcome-backdrop" id="welcomeBackdrop">
<div class="welcome-dialog">
<div class="welcome-header">🎲 Willkommen in der BSN Community!</div>
<div class="welcome-body">
<p>Hier teilen <strong>Brettspiel-Fans</strong> ihre Eindrücke direkt von der SPIEL Essen und darüber hinaus.</p>
<div class="welcome-feature"><span class="welcome-icon">📸</span><span><strong>Beiträge durchstöbern</strong> Fotos, Videos, Sprachnachrichten und Texte aus der Community, sortiert nach Halle, Kategorie oder Beliebtheit.</span></div>
<div class="welcome-feature"><span class="welcome-icon"></span><span><strong>Selbst einreichen</strong> Über den <span style="background:#20228a;color:#fff;border-radius:50%;display:inline-block;width:22px;height:22px;text-align:center;line-height:22px;font-size:0.9rem;">+</span>-Button rechts unten kannst du eigene Fotos, Videos oder Texte beisteuern.</span></div>
<div class="welcome-feature"><span class="welcome-icon">💬</span><span><strong>BSN Assistent</strong> Der Chat-Button rechts unten beantwortet Fragen zu Spielen und Ständen mit KI.</span></div>
<div class="welcome-feature"><span class="welcome-icon">🔐</span><span><strong>Login</strong> Oben rechts einloggen, um Benachrichtigungen zu erhalten und deine Beiträge zu verwalten.</span></div>
<p style="margin-top:0.8rem;font-size:0.78rem;color:#888;"> Diese Seite ist ein Community-Projekt von <a href="https://brettspiel-news.de" style="color:#20228a;">brettspiel-news.de</a>.</p>
</div>
<div class="welcome-footer">
<label><input type="checkbox" id="welcomeDismiss"> Nicht mehr anzeigen</label>
<button id="welcomeClose">Alles klar!</button>
</div>
</div>
</div>
<header class="site-header"> <header class="site-header">
<button id="navToggle" class="nav-toggle" aria-label="Menü öffnen"></button> <button id="navToggle" class="nav-toggle" aria-label="Menü öffnen"></button>
<a href="/" class="site-logo"> <a href="/" class="site-logo">
<img src="/static/logo.png" alt="Brettspiel News" style="height:38px;width:auto;display:block;"> <img src="/static/logo.png" alt="Brettspiel News" style="height:38px;width:auto;display:block;">
<span class="site-logo-sub">boardgame.network</span> <span class="site-logo-sub">boardgame.network</span>
</a> </a>
<div class="header-right"> """ + _build_login_dropdown(user) + """
<a href="https://brettspiel-news.de">brettspiel-news.de</a>
<button id="filterToggle" class="filter-toggle-btn" aria-label="Filter einblenden"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" style="vertical-align:middle;"><polyline points="6 9 12 15 18 9"/></svg></button>
</div>
</header> </header>
<div id="filterBar" class="filter-collapsed">""" + filter_html + """</div> <div id="filterBar" class="filter-collapsed">""" + filter_html + """</div>
@@ -3613,8 +3695,7 @@ def public_frontend():
<li class="nav-section">Navigation</li> <li class="nav-section">Navigation</li>
<li><a href="/">Startseite</a></li> <li><a href="/">Startseite</a></li>
<li class="nav-section">Community</li> <li class="nav-section">Community</li>
<li><a href="/register">📋 Registrieren</a></li> """ + ("<li><a href=\"/profile\">👤 Profil</a></li><li><a href=\"/logout\">🚪 Abmelden</a></li>" if user else "<li><a href=\"/register\">📋 Registrieren</a></li><li><a href=\"/login\">🔐 Login</a></li>") + """
<li><a href="/login">🔐 Login</a></li>
<li class="nav-section">Rechtliches</li> <li class="nav-section">Rechtliches</li>
<li><a href="/impressum">Impressum</a></li> <li><a href="/impressum">Impressum</a></li>
<li><a href="/datenschutz">Datenschutzerklärung</a></li> <li><a href="/datenschutz">Datenschutzerklärung</a></li>
@@ -3656,6 +3737,49 @@ def public_frontend():
})(); })();
</script> </script>
<script>
// Login-Dropdown Toggle
(function() {
var btn = document.getElementById('loginToggle');
var menu = document.getElementById('loginDropdown');
if (btn && menu) {
btn.addEventListener('click', function(e) {
e.stopPropagation();
menu.classList.toggle('open');
btn.classList.toggle('active');
});
document.addEventListener('click', function(e) {
if (!btn.contains(e.target) && !menu.contains(e.target)) {
menu.classList.remove('open');
btn.classList.remove('active');
}
});
}
})();
// Welcome Overlay
(function() {
var backdrop = document.getElementById('welcomeBackdrop');
var closeBtn = document.getElementById('welcomeClose');
var checkbox = document.getElementById('welcomeDismiss');
if (!backdrop) return;
// Nur anzeigen, wenn nicht per localStorage ausgeblendet
if (localStorage.getItem('welcomeDismissed') !== 'true') {
backdrop.classList.add('open');
}
function dismiss() {
if (checkbox && checkbox.checked) {
localStorage.setItem('welcomeDismissed', 'true');
}
backdrop.classList.remove('open');
}
if (closeBtn) closeBtn.addEventListener('click', dismiss);
backdrop.addEventListener('click', function(e) {
if (e.target === backdrop) dismiss();
});
})();
</script>
</body> </body>
</html>""").replace("{LOGO_B64}", LOGO_B64) </html>""").replace("{LOGO_B64}", LOGO_B64)
@@ -3708,17 +3832,84 @@ def _legal_page(title, content_html):
.legal-container a {{ color:#20228a; text-decoration:underline; }} .legal-container a {{ color:#20228a; text-decoration:underline; }}
.back-link {{ display:inline-block; margin-top:2rem; color:#20228a; text-decoration:none; font-weight:600; font-size:0.9rem; }} .back-link {{ display:inline-block; margin-top:2rem; color:#20228a; text-decoration:none; font-weight:600; font-size:0.9rem; }}
.back-link:hover {{ text-decoration:underline; }} .back-link:hover {{ text-decoration:underline; }}
.nav-toggle {{ background:none; border:none; color:#fff; font-size:1.3rem; cursor:pointer; padding:0.3rem 0.4rem; line-height:1; margin-right:0.5rem; transition:opacity 0.2s; }}
.nav-toggle:hover {{ opacity:0.7; }}
.offcanvas-backdrop {{ position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:200; opacity:0; pointer-events:none; transition:opacity 0.3s ease; }}
.offcanvas-backdrop.open {{ opacity:1; pointer-events:auto; }}
.offcanvas-nav {{ position:fixed; top:0; left:0; width:280px; height:100%; height:100dvh; background:#fff; z-index:201; transform:translateX(-100%); transition:transform 0.3s cubic-bezier(0.4,0,0.2,1); box-shadow:4px 0 24px rgba(0,0,0,0.15); display:flex; flex-direction:column; }}
.offcanvas-nav.open {{ transform:translateX(0); }}
.offcanvas-nav-header {{ background:#20228a; color:#fff; padding:1.2rem 1.5rem; display:flex; align-items:center; justify-content:space-between; }}
.offcanvas-nav-header .nav-logo {{ font-weight:800; font-size:1rem; }}
.offcanvas-nav-header .nav-close {{ background:none; border:none; color:#fff; font-size:1.4rem; cursor:pointer; line-height:1; }}
.offcanvas-nav-links {{ list-style:none; padding:1rem 0; flex:1; }}
.offcanvas-nav-links a {{ display:block; padding:0.75rem 1.5rem; color:#1a1a2e; text-decoration:none; font-size:0.95rem; font-weight:500; transition:background 0.15s; }}
.offcanvas-nav-links a:hover {{ background:#f0ede6; }}
.offcanvas-nav-links .nav-section {{ padding:0.5rem 1.5rem 0.2rem; font-size:0.7rem; font-weight:700; text-transform:uppercase; letter-spacing:0.05em; color:#888; }}
.offcanvas-nav-footer {{ padding:1rem 1.5rem; border-top:1px solid #e0ddd6; font-size:0.75rem; color:#888; }}
.site-header {{ display:flex; align-items:center; gap:0.5rem; }}
@media(max-width:600px) {{ .legal-container {{ margin:1rem; padding:1.2rem; }} }} @media(max-width:600px) {{ .legal-container {{ margin:1rem; padding:1.2rem; }} }}
</style> </style>
</head> </head>
<body> <body>
<header class="site-header"> <header class="site-header">
<a href="/"> BSN Community</a> <button id="navToggle" class="nav-toggle" aria-label="Menü öffnen"></button>
<a href="/">BSN Community</a>
</header> </header>
<main class="legal-container"> <main class="legal-container">
{content_html} {content_html}
<a href="/" class="back-link"> Zurück zur Startseite</a> <a href="/" class="back-link"> Zurück zur Startseite</a>
</main> </main>
<div class="offcanvas-backdrop" id="offcanvasBackdrop"></div>
<nav class="offcanvas-nav" id="offcanvasNav">
<div class="offcanvas-nav-header">
<span class="nav-logo">BSN Community</span>
<button class="nav-close" id="navClose" aria-label="Menü schließen"></button>
</div>
<ul class="offcanvas-nav-links">
<li class="nav-section">Navigation</li>
<li><a href="/">Startseite</a></li>
<li class="nav-section">Community</li>
<li><a href="/register">📋 Registrieren</a></li>
<li><a href="/login">🔐 Login</a></li>
<li class="nav-section">Rechtliches</li>
<li><a href="/impressum">Impressum</a></li>
<li><a href="/datenschutz">Datenschutzerklärung</a></li>
<li><a href="/agb">AGB</a></li>
</ul>
<div class="offcanvas-nav-footer">
© 2026 Brettspiel-News.de
</div>
</nav>
<script>
(function() {{
var toggle = document.getElementById('navToggle');
var close = document.getElementById('navClose');
var nav = document.getElementById('offcanvasNav');
var backdrop = document.getElementById('offcanvasBackdrop');
var main = document.querySelector('main');
var header = document.querySelector('.site-header');
function open() {{
nav.classList.add('open');
backdrop.classList.add('open');
if (main) main.style.filter = 'blur(4px)';
if (header) header.style.filter = 'blur(4px)';
}}
function closeNav() {{
nav.classList.remove('open');
backdrop.classList.remove('open');
if (main) main.style.filter = '';
if (header) header.style.filter = '';
}}
toggle.addEventListener('click', open);
close.addEventListener('click', closeNav);
backdrop.addEventListener('click', closeNav);
}})();
</script>
</body> </body>
</html>""" </html>"""
@@ -4495,7 +4686,21 @@ def public_detail(sub_id: int):
<style> <style>
* {{ margin:0; padding:0; box-sizing:border-box; }} * {{ margin:0; padding:0; box-sizing:border-box; }}
body {{ font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:#F3EDE4; color:#1a1a2e; min-height:100vh; }} body {{ font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:#F3EDE4; color:#1a1a2e; min-height:100vh; }}
.site-header {{ background:#20228a; padding:1rem 2rem; display:flex; align-items:center; gap:1rem; position:sticky; top:0; z-index:100; }} .site-header {{ background:#20228a; padding:1rem 2rem; display:flex; align-items:center; gap:0.5rem; position:sticky; top:0; z-index:100; }}
.nav-toggle {{ background:none; border:none; color:#fff; font-size:1.3rem; cursor:pointer; padding:0.3rem 0.4rem; line-height:1; margin-right:0.5rem; transition:opacity 0.2s; }}
.nav-toggle:hover {{ opacity:0.7; }}
.offcanvas-backdrop {{ position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:200; opacity:0; pointer-events:none; transition:opacity 0.3s ease; }}
.offcanvas-backdrop.open {{ opacity:1; pointer-events:auto; }}
.offcanvas-nav {{ position:fixed; top:0; left:0; width:280px; height:100%; height:100dvh; background:#fff; z-index:201; transform:translateX(-100%); transition:transform 0.3s cubic-bezier(0.4,0,0.2,1); box-shadow:4px 0 24px rgba(0,0,0,0.15); display:flex; flex-direction:column; }}
.offcanvas-nav.open {{ transform:translateX(0); }}
.offcanvas-nav-header {{ background:#20228a; color:#fff; padding:1.2rem 1.5rem; display:flex; align-items:center; justify-content:space-between; }}
.offcanvas-nav-header .nav-logo {{ font-weight:800; font-size:1rem; }}
.offcanvas-nav-header .nav-close {{ background:none; border:none; color:#fff; font-size:1.4rem; cursor:pointer; line-height:1; }}
.offcanvas-nav-links {{ list-style:none; padding:1rem 0; flex:1; }}
.offcanvas-nav-links a {{ display:block; padding:0.75rem 1.5rem; color:#1a1a2e; text-decoration:none; font-size:0.95rem; font-weight:500; transition:background 0.15s; }}
.offcanvas-nav-links a:hover {{ background:#f0ede6; }}
.offcanvas-nav-links .nav-section {{ padding:0.5rem 1.5rem 0.2rem; font-size:0.7rem; font-weight:700; text-transform:uppercase; letter-spacing:0.05em; color:#888; }}
.offcanvas-nav-footer {{ padding:1rem 1.5rem; border-top:1px solid #e0ddd6; font-size:0.75rem; color:#888; }}
.site-logo {{ display:flex; align-items:center; gap:0.6rem; text-decoration:none; color:#fff; }} .site-logo {{ display:flex; align-items:center; gap:0.6rem; text-decoration:none; color:#fff; }}
.site-logo .logo-icon {{ width:40px; height:40px; background:#20228a; border-radius:8px; display:flex; align-items:center; justify-content:center; font-size:1.3rem; }} .site-logo .logo-icon {{ width:40px; height:40px; background:#20228a; border-radius:8px; display:flex; align-items:center; justify-content:center; font-size:1.3rem; }}
.site-logo .logo-text {{ font-weight:800; font-size:1.15rem; color:#fff; }} .site-logo .logo-text {{ font-weight:800; font-size:1.15rem; color:#fff; }}
@@ -4549,6 +4754,7 @@ def public_detail(sub_id: int):
</head> </head>
<body> <body>
<header class="site-header"> <header class="site-header">
<button id="navToggle" class="nav-toggle" aria-label="Menü öffnen"></button>
<a href="/" class="site-logo"> <a href="/" class="site-logo">
<img src="/static/logo.png" alt="Brettspiel News" style="height:30px;width:auto;display:block;" width="160" height="30"> <img src="/static/logo.png" alt="Brettspiel News" style="height:30px;width:auto;display:block;" width="160" height="30">
<span class="site-logo-sub">boardgame.network</span> <span class="site-logo-sub">boardgame.network</span>
@@ -4698,11 +4904,61 @@ function submitComment(event, subId) {{
return false; return false;
}} }}
</script> </script>
<div class="offcanvas-backdrop" id="offcanvasBackdrop"></div>
<nav class="offcanvas-nav" id="offcanvasNav">
<div class="offcanvas-nav-header">
<span class="nav-logo">BSN Community</span>
<button class="nav-close" id="navClose" aria-label="Menü schließen"></button>
</div>
<ul class="offcanvas-nav-links">
<li class="nav-section">Navigation</li>
<li><a href="/">Startseite</a></li>
<li class="nav-section">Community</li>
<li><a href="/register">📋 Registrieren</a></li>
<li><a href="/login">🔐 Login</a></li>
<li class="nav-section">Rechtliches</li>
<li><a href="/impressum">Impressum</a></li>
<li><a href="/datenschutz">Datenschutzerklärung</a></li>
<li><a href="/agb">AGB</a></li>
</ul>
<div class="offcanvas-nav-footer">
© 2026 Brettspiel-News.de
</div>
</nav>
<script>
(function() {{
var toggle = document.getElementById('navToggle');
var close = document.getElementById('navClose');
var nav = document.getElementById('offcanvasNav');
var backdrop = document.getElementById('offcanvasBackdrop');
var main = document.querySelector('main');
var header = document.querySelector('.site-header');
function open() {{
nav.classList.add('open');
backdrop.classList.add('open');
if (main) main.style.filter = 'blur(4px)';
if (header) header.style.filter = 'blur(4px)';
}}
function closeNav() {{
nav.classList.remove('open');
backdrop.classList.remove('open');
if (main) main.style.filter = '';
if (header) header.style.filter = '';
}}
toggle.addEventListener('click', open);
close.addEventListener('click', closeNav);
backdrop.addEventListener('click', closeNav);
}})();
</script>
</body> </body>
</html>""" </html>"""
@app.route("/api/like/<int:sub_id>", methods=["POST"]) @app.route("/api/like/<int:sub_id>", methods=["POST"])
def api_like(sub_id: int): def api_like(sub_id: int):
db = get_db() db = get_db()