9 Views: Gate Screen · Galerie · Artikel-Modal · Admin Login · Admin Inventar · Artikel hinzufügen/bearbeiten · Codes · Reservierungen · Übersicht. Alle Mockups auf ~55 % skaliert. Impl-Ref-Tabellen enthalten exakte Tailwind-Klassen und Pixel-Werte.
Einzige öffentlich sichtbare Seite. Kein Inhalt ohne gültigen Code. Der ?code=-URL-Parameter wird serverseitig in +page.server.ts abgefangen — Cookie setzen → redirect /galerie. Manuelle Eingabe als Fallback.
| Element | Tailwind-Klassen | Px | Notiz |
|---|---|---|---|
| Seite | min-h-screen bg-canvas flex items-center justify-center p-6 | — | Canvas-Hintergrund, vertikal zentriert |
| Karte | bg-surface border border-line rounded-xl p-7 w-full max-w-sm text-center shadow-sm | 28px pad | max-width 384px |
| Icon | w-[52px] h-[52px] rounded-full bg-[#DFF0E6] flex items-center justify-center text-2xl mx-auto mb-3.5 | 52px | Dekorativ — aria-hidden |
| Titel | font-serif text-xl font-bold text-ink mb-1.5 | 20px/700 | Lora |
| Subtext | text-sm text-ink-muted leading-relaxed mb-5 | 14px | Inter |
| Code-Input | font-mono text-base font-semibold tracking-[4px] uppercase text-center w-full h-12 border-1.5 border-line rounded-lg bg-[#FAFAF7] mb-3 focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/30 | 48px | inputmode="text" autocomplete="off" spellcheck="false" |
| Weiter-Button | bg-primary hover:bg-primary-dark text-white w-full min-h-[48px] rounded-lg font-bold text-sm | 48px | SvelteKit Form Action — funktioniert ohne JS |
| Fehler-Text | text-xs text-status-taken mt-2 flex items-center gap-1.5 | 12px | ⚠ Icon + Text — kein Farbe allein |
| Hinweis | text-[10px] text-ink-muted leading-relaxed mt-3 | 10px | — |
/?code=AB3K7MN2 → Server liest url.searchParams.get('code'), prüft gegen DB, setzt HTTP-only Cookie family_code, leitet zu /galerie/galerie ohne Gate Screen zu zeigen (hooks.server.ts)Hauptansicht für Familienmitglieder. Telefon: 1-Spalte-Liste (horizontale Karten, Foto 72 px links). Tablet/Desktop: 2-Spalten-Grid (quadratische Karten). Kategorie-Filter als scrollbare Pill-Leiste. Kein Artikel-Titel in der Galerie — nur Kategorie + Status.
| Element | Tailwind-Klassen | Px | Notiz |
|---|---|---|---|
| Nav | h-11 bg-primary sticky top-0 z-10 flex items-center px-3.5 | 44px | Sticky — immer sichtbar |
| App-Logo | font-serif text-sm font-bold text-white | 14px/700 | Lora |
| Nutzername | ml-auto text-[10px] font-semibold text-white/75 | 10px | „Angemeldet als: Markus" — Display-Name aus Cookie |
| Filter-Leiste | bg-canvas border-b border-line px-2.5 py-2 flex gap-1.5 overflow-x-auto scrollbar-hide sticky top-11 z-10 | — | Sticky unter Nav |
| Pill aktiv | bg-primary text-white border-primary text-[10px] font-bold px-2.5 py-1.5 rounded-full min-h-[30px] | 30px min. | — |
| Pill inaktiv | bg-transparent text-ink-muted border-1.5 border-line text-[10px] font-semibold px-2.5 py-1.5 rounded-full min-h-[30px] | — | — |
| Grid (Telefon) | flex flex-col gap-2 p-2.5 | ≤ 767px | 1-Spalte-Liste |
| Grid (Tablet+) | grid grid-cols-2 gap-2 p-2.5 via md:grid md:grid-cols-2 | ≥ 768px | 2-Spalten-Grid |
| Karte (Liste) | bg-surface border border-line rounded-lg overflow-hidden flex cursor-pointer hover:shadow-sm transition-shadow | — | Klick öffnet Modal |
| Karte Foto (Liste) | w-[72px] h-[72px] object-cover flex-shrink-0 | 72×72 | alt="" für dekorative Fotos; aria-describedby auf Karte für SR |
| Karte Foto (Grid) | aspect-square w-full object-cover | quadratisch | — |
| Kategorie-Label | text-[9px] font-extrabold uppercase tracking-wider text-ink-muted | 9px | — |
| Status Frei | text-[10px] font-bold text-status-free | 10px | „✓ Frei" |
| Status Reserviert | text-[10px] font-bold text-status-taken | 10px | „● [Display-Name]" |
| Status Meins | text-[9px] font-bold bg-[#E8F5EC] text-[#2E6645] px-2 py-0.5 rounded-full border border-[#A8D5B8] mt-0.5 inline-block | Badge | „✓ Meine Reservierung" |
Bottom Sheet öffnet sich beim Antippen einer Galerie-Karte. Foto-Galerie mit Wisch-Geste (touch-action: pan-x) und Seitenzähler. Reservierungsbereich passt sich an Status an. Schließen durch Tippen auf Overlay oder Handle-Swipe nach unten.
| Element | Tailwind-Klassen | Px | Notiz |
|---|---|---|---|
| Overlay | fixed inset-0 bg-black/45 flex items-end z-50 | — | Klick schließt Modal · role="dialog" aria-modal="true" |
| Sheet | bg-surface rounded-t-xl w-full max-h-[88dvh] overflow-y-auto | 88dvh max | SvelteKit slide-Transition von unten |
| Handle | w-9 h-1 bg-line rounded mx-auto mt-2.5 mb-3.5 | 4px | Swipe-Down schließt Modal |
| Foto-Galerie | aspect-[4/3] w-full object-cover bg-canvas touch-pan-x overflow-hidden | 4:3 | Einfaches Swipe — kein JS-Framework nötig |
| Foto-Zähler | absolute bottom-2 right-2.5 bg-black/45 text-white text-[9px] font-bold px-1.5 py-0.5 rounded-full | — | „1 / 3 →" — ausblenden bei nur 1 Foto |
| Modal-Body | p-4 | 16px | — |
| Kategorie-Badge | inline-block text-[9px] font-extrabold uppercase tracking-wider bg-[#DFF0E6] text-[#2E6645] px-2.5 py-0.5 rounded-full border border-[#A8D5B8] mb-1.5 | — | — |
| Artikel-Titel | font-serif text-[18px] font-bold text-ink leading-snug mb-1.5 | 18px/700 | Lora |
| Notiz | text-sm text-ink-muted italic leading-relaxed mb-3 | 14px | Nur rendern wenn artikel.notiz vorhanden |
| Reservieren (A) | bg-primary hover:bg-primary-dark text-white w-full min-h-[48px] rounded-lg font-bold text-sm | 48px | Form Action POST |
| Aufheben (B) | bg-[#FBF0F0] border-1.5 border-status-taken text-status-taken w-full min-h-[44px] rounded-lg font-bold text-sm | 44px | Nur wenn reservierung.code_id === locals.familyCode.id |
| Gesperrt (C) | bg-canvas border-1.5 border-line text-[#AAA] w-full min-h-[44px] rounded-lg font-bold text-sm cursor-not-allowed | 44px | disabled-Attribut setzen |
reservierungen-Row existiert? → Taken. code_id === locals.familyCode.id? → Mine.reservierungen.artikel_id verhindert Doppel-Reservierung atomarEigenständige Seite mit dunklem Hintergrund (Admin-BG). Vollständig getrennt von der Familien-Ansicht — kein Code-Zugang. Benutzername (Marcel / Renate / Berit) + Passwort. Kein User-Enumeration bei falschen Credentials.
| Element | Tailwind-Klassen | Px | Notiz |
|---|---|---|---|
| Seite | min-h-screen bg-admin flex items-center justify-center p-6 | — | Admin-Dunkelgrün als Background |
| Karte | bg-surface border border-line rounded-xl p-[22px] w-full max-w-xs | 22px pad | Kein Shadow — wirkt geerdet auf dunklem BG |
| Form-Label | text-[9px] font-extrabold uppercase tracking-wide text-ink-muted mb-1 block | 9px | Immer <label for="…"> verknüpfen |
| Input | w-full h-10 border-1.5 border-line rounded-md bg-[#FAFAF7] px-2.5 text-[13px] focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/30 | 40px | — |
| Anmelden-Button | bg-admin hover:bg-[#1E2C24] text-white w-full min-h-[44px] rounded-md font-bold text-[13px] | 44px | bcrypt.compare → Cookie admin_session |
| Fehler | text-xs text-status-taken mt-2 | 12px | Identische Meldung für falschen User + falsches Passwort — kein User-Enumeration |
Gemeinsames Layout-Gerüst für alle vier Admin-Bereiche. Desktop: feste Sidebar (176 px) links, Content-Bereich rechts. Mobil: Sidebar klappt zu Hamburger-Drawer. Amber-Linie markiert den aktiven Bereich.
| Artikel | Kategorie | Status | ||
|---|---|---|---|---|
| Schreibtisch Eiche | Möbel | ✓ Frei | ||
| Goldbrosche | Schmuck | ● Renate | ||
| Goethe Gesamtausgabe | Bücher | ✓ Markus |
| Name | Code | Res. | |
|---|---|---|---|
| Markus | AB3K7MN2 | 3 | |
| Renate | XP9QRT4W | 1 | |
| Berit | LM2J6VH8 | 0 |
| Kategorie | Gesamt | Reserviert | Frei |
|---|---|---|---|
| Möbel | 14 | 5 | 9 |
| Bücher | 18 | 4 | 14 |
| Schmuck | 7 | 2 | 5 |
| Kunstwerke | 4 | 1 | 3 |
| Küche | 4 | 0 | 4 |
| Element | Tailwind-Klassen | Px | Notiz |
|---|---|---|---|
| Outer Layout | flex min-h-screen | — | +layout.svelte unter /admin/ |
| Sidebar Desktop | w-44 bg-admin flex-shrink-0 hidden md:flex flex-col py-3 | 176px | Dunkelgrün, ab md: sichtbar |
| Sidebar Mobil | Overlay-Drawer — Toggle via Svelte $state(false) | — | Hamburger-Icon in Mobile-TopBar |
| Sidebar-Link aktiv | flex items-center gap-2 px-3 py-1.5 text-[10px] font-semibold text-white bg-white/10 border-l-[3px] border-accent | — | Amber-Linie links |
| Sidebar-Link inaktiv | flex items-center gap-2 px-3 py-1.5 text-[10px] font-semibold text-white/45 border-l-[3px] border-transparent hover:text-white/75 | — | — |
| Content-Bereich | flex-1 bg-canvas p-3.5 overflow-auto | 14px | — |
| Stat-Karten-Grid | grid grid-cols-3 gap-1.5 mb-3 | — | — |
| Kamera-Button | font-serif text-sm font-bold bg-primary text-white w-full min-h-[60px] rounded-xl flex items-center justify-center gap-2.5 | 60px | Triggert <input capture="environment"> |
| Thumbnail-Strip | flex gap-1.5 overflow-x-auto pb-0.5 | — | Drag & Drop für Reihenfolge |
| Thumbnail | w-[68px] h-[68px] rounded-lg object-cover flex-shrink-0 relative cursor-grab | 68×68 | — |
| Thumbnail Badge | absolute top-1 left-1 bg-accent text-white text-[7px] font-extrabold px-1.5 py-0.5 rounded-sm | — | Text: „Thumbnail" — nur auf Index 0 |
| Kategorie (native select) | w-full h-[46px] border-1.5 border-primary rounded-lg px-3 text-[13px] text-ink bg-surface appearance-none | 46px | Native <select> für Mobile-Kompatibilität |
| Sticky Save Bar | bg-surface border-t border-line p-2.5 flex gap-2 shadow-[0_-2px_10px_rgba(0,0,0,.06)] flex-shrink-0 | — | Sticky unten — immer erreichbar |
| Code löschen (Bestätigung) | Native confirm() oder Svelte Dialog-Komponente | — | Warnung: „Alle Reservierungen werden gelöscht" |
+layout.svelte unter src/routes/admin/ — rendert Sidebar + <slot />+layout.server.ts prüft locals.admin → redirect zu /admin/login bei fehlendem Cookie<input type="file" accept="image/*" capture="environment" multiple> — Kamera-Button triggert via .click()createObjectURL()-Preview im Strip, Upload erst beim Speichern via Multipart Form Action → sharp → WebPnavigator.clipboard.writeText(url) mit visueller Bestätigung (Button-Text → „Kopiert ✓")