- .focus-ring Utility via Tailwind-Play-CDN (@apply) in base.html - Globaler :focus-visible-Fallback (emerald Outline) für alle Bedienelemente ohne explizite Fokus-Klassen - Benutzer-Formular und Audit-Log-Filter nutzen .focus-ring
108 lines
6.0 KiB
HTML
108 lines
6.0 KiB
HTML
<!doctype html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{% block titel %}{{ app_name }}{% endblock %}</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.9/dist/cdn.min.js"></script>
|
|
<style>[x-cloak] { display: none !important; }</style>
|
|
<style type="text/tailwindcss">
|
|
/* Einheitlicher Fokus-Ring für Formularelemente und Button-Links */
|
|
.focus-ring { @apply focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500; }
|
|
</style>
|
|
<style>
|
|
/* Fallback ohne Klassen-Anpassung: sichtbarer Fokus auf allen
|
|
Bedienelementen (Tailwind-Ring überlagert diesen, wo vorhanden). */
|
|
input:focus-visible, select:focus-visible, textarea:focus-visible,
|
|
button:focus-visible, summary:focus-visible, a:focus-visible {
|
|
outline: 2px solid #10b981;
|
|
outline-offset: 1px;
|
|
}
|
|
input:focus-visible, select:focus-visible, textarea:focus-visible {
|
|
border-color: #10b981;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-slate-100 text-slate-800 min-h-screen flex flex-col">
|
|
<header class="bg-white border-b border-slate-200" x-data="{ menue: false }">
|
|
<nav class="max-w-6xl mx-auto px-4 py-2 md:py-3 flex items-center gap-5" aria-label="Hauptnavigation">
|
|
<a href="/" class="font-bold text-emerald-700 whitespace-nowrap inline-flex items-center gap-1.5">
|
|
{% from "icons.html" import icon_wuerfelbecher %}{{ icon_wuerfelbecher("h-5 w-5") }} {{ app_name }}
|
|
</a>
|
|
<!-- Desktop: horizontale Navigationsleiste -->
|
|
<span class="hidden md:flex items-center gap-5 flex-1">
|
|
{% for eintrag in plugin_nav %}
|
|
{% set aktiv = request.url.path == eintrag.url or request.url.path.startswith(eintrag.url ~ '/') %}
|
|
<a href="{{ eintrag.url }}" {% if aktiv %}aria-current="page"{% endif %}
|
|
class="text-sm pb-0.5 border-b-2 {% if aktiv %}text-emerald-700 font-semibold border-emerald-600{% else %}text-slate-600 border-transparent hover:text-emerald-700 hover:border-emerald-300{% endif %}">{{ eintrag.label }}</a>
|
|
{% endfor %}
|
|
</span>
|
|
<!-- Mobil: Burger-Button (Alpine.js), Touch-Ziel ≥≥ 44px -->
|
|
<button type="button" @click="menue = !menue" :aria-expanded="menue.toString()"
|
|
aria-controls="hauptmenue-mobil" aria-label="Navigation umschalten"
|
|
class="md:hidden ml-auto inline-flex items-center justify-center min-h-[44px] min-w-[44px] rounded-lg text-slate-600 hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-emerald-500">
|
|
<svg x-show="!menue" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none"
|
|
viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16"/>
|
|
</svg>
|
|
<svg x-show="menue" x-cloak xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none"
|
|
viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
|
|
</svg>
|
|
</button>
|
|
<div class="relative" x-data="{ offen: false }">
|
|
{% if user %}
|
|
<button type="button" @click="offen = !offen"
|
|
class="text-sm font-medium px-2 py-1 rounded hover:bg-slate-100 inline-flex items-center min-h-[44px] md:min-h-0">
|
|
{{ user.display_name }}
|
|
{% from "icons.html" import icon_chevron_unten %}{{ icon_chevron_unten("h-3.5 w-3.5") }}
|
|
</button>
|
|
<div x-show="offen" x-cloak @click.outside="offen = false"
|
|
class="absolute right-0 mt-2 w-56 bg-white border border-slate-200 rounded-lg shadow-lg p-3 text-sm z-10">
|
|
<p class="text-slate-500 mb-2">Angemeldet als<br><span class="font-medium text-slate-800">{{ user.username }}</span></p>
|
|
<p class="text-slate-500 mb-3">Rolle: <span class="font-medium text-slate-800">{{ rollen_anzeige[user.role] or user.role }}</span></p>
|
|
{% if user.role == 'admin' %}
|
|
<a href="/admin/benutzer" class="block px-2 py-1 rounded hover:bg-slate-100 mb-1">Benutzerverwaltung</a>
|
|
{% endif %}
|
|
<form method="post" action="/logout">
|
|
<button type="submit" class="w-full text-left px-2 py-1 rounded hover:bg-red-50 text-red-600">Abmelden</button>
|
|
</form>
|
|
</div>
|
|
{% else %}
|
|
<a href="/login" class="text-sm font-medium text-emerald-700 inline-flex items-center min-h-[44px] md:min-h-0 px-2">Anmelden</a>
|
|
{% endif %}
|
|
</div>
|
|
</nav>
|
|
<!-- Mobil: ausklappbare Navigation (Burger-Menü) -->
|
|
<div id="hauptmenue-mobil" x-show="menue" x-cloak @click.outside="menue = false"
|
|
class="md:hidden border-t border-slate-200 bg-white px-4 pb-3 pt-2">
|
|
{% for eintrag in plugin_nav %}
|
|
{% set aktiv = request.url.path == eintrag.url or request.url.path.startswith(eintrag.url ~ '/') %}
|
|
<a href="{{ eintrag.url }}" @click="menue = false" {% if aktiv %}aria-current="page"{% endif %}
|
|
class="block py-3 text-sm border-b border-slate-100 last:border-0 {% if aktiv %}font-semibold text-emerald-700{% else %}font-medium text-slate-700 hover:text-emerald-700{% endif %}">
|
|
{{ eintrag.label }}
|
|
</a>
|
|
{% endfor %}
|
|
{% if not user %}
|
|
<a href="/login" @click="menue = false"
|
|
class="block py-3 text-sm font-medium text-emerald-700 border-t border-slate-100 mt-1">Anmelden</a>
|
|
{% endif %}
|
|
</div>
|
|
</header>
|
|
<main class="flex-1 max-w-6xl w-full mx-auto px-4 py-6">
|
|
{% block inhalt %}{% endblock %}
|
|
</main>
|
|
<footer class="text-center text-xs text-slate-400 py-4">
|
|
{{ app_name }} · Plugin-Anzahl: {{ geladene_plugins|length }}
|
|
<div class="mt-2">
|
|
<a href="https://boardgamegeek.com" target="_blank" rel="noopener noreferrer">
|
|
<img src="/static/powered-by-bgg.png" alt="Powered by BoardGameGeek"
|
|
width="120" height="35" class="inline-block opacity-80 hover:opacity-100">
|
|
</a>
|
|
</div>
|
|
</footer>
|
|
</body>
|
|
</html>
|