Coverbilder (bild_url) + responsives Layout für Mobil/Tablet

Teil 1 — Coverbilder:
- Migration 0004_bild_url (neuheiten), 0002_bild_url (planung, archiv
  mit beiden Spiegel-Tabellen); nullable VARCHAR(500), SQLite↔Postgres
  portabel, bestehende Zeilen bleiben erhalten
- BGG-Client extrahiert Cover aus der Thing-Antwort (bevorzugt <image>,
  sonst <thumbnail>; protokoll-relative URLs → https)
- Sync-Upsert pflegt bild_url beim Update; fehlendes Bild löscht keines
- Web-Quellen übernehmen img-URLs aus den Listenelementen (src/data-src,
  relative Pfade gegen die Seiten-URL aufgelöst); SPIEL-Essen nutzt das
  BILDER-Feld des JSON-Endpunkts
- Verschiebung Neuheiten→Planung und Archivierung/Wiederherstellen
  schreiben bild_url mit
- Anzeige: kleine Vorschaubilder (40x56) in den Listen, groß (max 200px)
  in Planung-Bearbeiten/Prüf-Dialog; Platzhalter-Div ohne Bild;
  Hotlinking mit loading=lazy + decoding=async

Teil 2 — Responsives Layout:
- Basislayout: Burger-Menü (Alpine.js) auf Mobil, Desktop-Leiste ab md:
- Listen kollabieren <768px zu Karten-Ansichten (Cover links, Titel +
  Metadaten rechts, Status-Badge oben); Desktop behält Tabellenform
- Touch-Ziele >= 44px auf Mobil, Formulare vollbreit mit Labels oben
- Sync-Übersicht/Audit-Log bleiben horizontal scrollbare Tabellen

Tests: 15 neue Fälle (tests/test_covers.py) — Migration inkl.
Daten-Erhalt, BGG-Parsing gemockt, Quellen-Übernahme, Template-Render;
316 Tests grün. README aktualisiert.
This commit is contained in:
Flo Hartmann
2026-08-24 16:54:56 +00:00
parent 20b84a8e1a
commit 96d6af8163
33 changed files with 1134 additions and 69 deletions

View File

@@ -9,16 +9,32 @@
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.9/dist/cdn.min.js"></script>
</head>
<body class="bg-slate-100 text-slate-800 min-h-screen flex flex-col">
<header class="bg-white border-b border-slate-200">
<nav class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-5" aria-label="Hauptnavigation">
<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">&#127923; {{ app_name }}</a>
{% for eintrag in plugin_nav %}
<a href="{{ eintrag.url }}" class="text-sm text-slate-600 hover:text-emerald-700">{{ eintrag.label }}</a>
{% endfor %}
<div class="ml-auto relative" x-data="{ offen: false }">
<!-- Desktop: horizontale Navigationsleiste -->
<span class="hidden md:flex items-center gap-5 flex-1">
{% for eintrag in plugin_nav %}
<a href="{{ eintrag.url }}" class="text-sm text-slate-600 hover:text-emerald-700">{{ eintrag.label }}</a>
{% endfor %}
</span>
<!-- Mobil: Burger-Button (Alpine.js), Touch-Ziel &ge;&ge; 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">
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 }} &#9662;
</button>
<div x-show="offen" x-cloak @click.outside="offen = false"
@@ -33,10 +49,24 @@
</form>
</div>
{% else %}
<a href="/login" class="text-sm font-medium text-emerald-700">Anmelden</a>
<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 %}
<a href="{{ eintrag.url }}" @click="menue = false"
class="block py-3 text-sm font-medium text-slate-700 hover:text-emerald-700 border-b border-slate-100 last:border-0">
{{ 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 %}

View File

@@ -66,13 +66,13 @@
class="w-full border border-slate-300 rounded-lg px-3 py-2">
</div>
<div class="flex gap-3 pt-2">
<div class="flex flex-col sm:flex-row gap-3 pt-2">
<button type="submit"
class="bg-emerald-600 hover:bg-emerald-700 text-white font-medium rounded-lg px-4 py-2">
class="min-h-[44px] inline-flex items-center justify-center bg-emerald-600 hover:bg-emerald-700 active:bg-emerald-800 text-white font-medium rounded-lg px-4 py-2">
Speichern
</button>
<a href="/admin/benutzer"
class="border border-slate-300 rounded-lg px-4 py-2 text-sm hover:bg-slate-50">Abbrechen</a>
class="min-h-[44px] inline-flex items-center justify-center border border-slate-300 rounded-lg px-4 py-2 text-sm hover:bg-slate-50">Abbrechen</a>
</div>
</form>
{% endblock %}

View File

@@ -4,7 +4,7 @@
<div class="flex items-center justify-between mb-4">
<h1 class="text-2xl font-bold">Benutzerverwaltung</h1>
<a href="/admin/benutzer/neu"
class="bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg px-3 py-2 text-sm font-medium">
class="bg-emerald-600 hover:bg-emerald-700 active:bg-emerald-800 text-white rounded-lg px-3 py-2 text-sm font-medium min-h-[44px] inline-flex items-center">
+ Neuer Benutzer
</a>
</div>

View File

@@ -27,7 +27,7 @@
class="w-full border border-slate-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-emerald-500">
</div>
<button type="submit"
class="w-full bg-emerald-600 hover:bg-emerald-700 text-white font-medium rounded-lg px-3 py-2">
class="w-full min-h-[44px] bg-emerald-600 hover:bg-emerald-700 active:bg-emerald-800 text-white font-medium rounded-lg px-3 py-2">
Anmelden
</button>
</form>