- PROJECT.md, README.md, TEST_GUIDE.md: status line refreshed; rate-limiter doc-vs-code drift fixed; HTML export section rewritten for the SvelteKit- static viewer; SSE event names + new events documented; config seed block extended with planned toggles + privacy_note; decision log entries added. - docs/CONCEPT_HTML_VIEWER.md, docs/CONCEPT_MOBILE_UI.md: banner the design intent as shipped; point at the source-of-truth code paths. - docs/CONCEPT_DIASHOW.md: planned-then-shipped design for the live diashow (two-queue policy, pluggable transitions, data-mode aware). - docs/FEATURES.md: capability matrix by role (Guest / Host / Admin) plus prose per area (auth, posting, feed, moderation, admin, export, gestures, data mode, quotas, privacy note, extensibility). - docs/USER_JOURNEYS.md: step-by-step flows for every supported scenario, including PIN reset by host, data mode, privacy note, gestures, and the admin toggles. - docs/IDEAS.md: speculative extensions (global diashow, reactions, multi-tenancy, animation pack, etc.) — explicitly out of v0.16 scope. - backend/migrations/README.md, frontend/src/lib/README.md: codify the "never edit a shipped migration" rule and the lib/ conventions (one store per concern, gestures via actions, sheets via ContextSheet, transitions as drop-in components). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
472 lines
22 KiB
Markdown
472 lines
22 KiB
Markdown
# Mobile-First UI/UX Redesign Concept
|
||
|
||
> **Status: IMPLEMENTED (v0.15).** This document captures the design intent. The redesign
|
||
> has shipped — see [BottomNav.svelte](../frontend/src/lib/components/BottomNav.svelte),
|
||
> [UploadSheet.svelte](../frontend/src/lib/components/UploadSheet.svelte),
|
||
> [CameraCapture.svelte](../frontend/src/lib/components/CameraCapture.svelte),
|
||
> [feed/+page.svelte](../frontend/src/routes/feed/+page.svelte),
|
||
> [account/+page.svelte](../frontend/src/routes/account/+page.svelte),
|
||
> [host/+page.svelte](../frontend/src/routes/host/+page.svelte),
|
||
> [admin/+page.svelte](../frontend/src/routes/admin/+page.svelte). Use this doc as the design
|
||
> reference; treat code as the source of truth for current behaviour.
|
||
|
||
## Overview
|
||
|
||
EventSnap is intended for mobile use at live events. This document describes the full
|
||
mobile-first design covering navigation, the feed/gallery, account page, host dashboard,
|
||
and admin dashboard.
|
||
|
||
---
|
||
|
||
## 1. Navigation: Bottom Tab Bar
|
||
|
||
Replace all per-page top-right icon links with a single **persistent bottom tab bar** present
|
||
on every page. The bar sits at the very bottom with proper `padding-bottom` for iPhone home
|
||
indicator (safe-area-inset-bottom).
|
||
|
||
### Tab Composition by Role
|
||
|
||
| Role | Tabs |
|
||
|-------|------|
|
||
| Guest | 🏠 Feed · [📷+] · 👤 Account |
|
||
| Host | 🏠 Feed · [📷+] · 👤 Account |
|
||
| Admin | 🏠 Feed · [📷+] · 👤 Account |
|
||
|
||
All roles see the same three tabs. Role-specific dashboard links (Host, Admin) live inside
|
||
the Account page — not as separate tabs. This keeps the bar simple and avoids conditional
|
||
tab rendering.
|
||
|
||
### Visual Style
|
||
|
||
- Frosted glass background: `bg-white/85 backdrop-blur-md`
|
||
- Thin top border: `border-t border-gray-200`
|
||
- Subtle shadow upward
|
||
- Active tab: colored icon + small label below
|
||
- Inactive tab: gray icon, small gray label
|
||
|
||
### Upload FAB (Floating Action Button)
|
||
|
||
The center tab is an elevated circular button, not a flat tab icon:
|
||
|
||
- Circle ~56 px diameter, `bg-blue-600`
|
||
- Icon: camera outline with a small `+` badge overlaid at bottom-right
|
||
- Raised above the bar with a drop shadow
|
||
- Press: slight scale-down (`scale-95`) + haptic feedback where available
|
||
- Communicates "capture new or upload existing"
|
||
|
||
---
|
||
|
||
## 2. Feed / Gallery Page
|
||
|
||
### Header
|
||
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ Sommerfest 2025 ≡ ⊞ │
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
- Event name left-aligned
|
||
- List/grid view toggle icons right-aligned (≡ list, ⊞ grid)
|
||
- Header collapses on downward scroll (only toggle remains visible), expands on upward scroll
|
||
|
||
---
|
||
|
||
### View A — Chronological List (default)
|
||
|
||
Full-width post cards, newest at top, infinite scroll.
|
||
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ 👤 MaxMustermann · vor 2 Min │
|
||
│ ┌───────────────────────────────────┐ │
|
||
│ │ │ │
|
||
│ │ [photo / video] │ │
|
||
│ │ │ │
|
||
│ └───────────────────────────────────┘ │
|
||
│ Tolle Stimmung! #party #spaß │
|
||
│ ❤️ 12 💬 3 │
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
- Media: full-width, native aspect ratio, capped at 80 vh
|
||
- Avatar: colored initial circle, no photo
|
||
- Timestamp: relative ("vor 2 Min", "vor 1 Std")
|
||
- Tap media → fullscreen lightbox, swipe left/right navigates feed
|
||
- No search bar in list view
|
||
|
||
---
|
||
|
||
### View B — Grid View
|
||
|
||
Transition animation when toggling: list collapses, grid fades/scales in (~200 ms).
|
||
|
||
#### Search Bar (grid view only)
|
||
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ 🔍 Nutzer oder #Tag suchen… × │
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
- Appears below the header only in grid view
|
||
- Slides in as part of the view transition
|
||
- `×` clears current input
|
||
- Auto-focuses when grid view is activated
|
||
|
||
#### Autocomplete Dropdown
|
||
|
||
Appears immediately on focus and updates on every keystroke. Data source: the already-loaded
|
||
posts in memory — **no extra API calls**.
|
||
|
||
Two suggestion lists are derived at load time:
|
||
- `allTags`: unique hashtags from all post captions, sorted by frequency descending
|
||
- `allUploaders`: unique display names, sorted alphabetically
|
||
|
||
| User input | Suggestions shown |
|
||
|------------|-------------------|
|
||
| (focus, empty) | Top 3 tags by frequency + top 3 uploaders |
|
||
| `#` | All tags, frequency-sorted |
|
||
| `#par` | Tags with prefix "par": `#party`, `#parade` |
|
||
| `Max` | Uploaders matching "max" (case-insensitive) |
|
||
| `a` | Uploaders containing "a" + tags containing "a" |
|
||
|
||
Dropdown layout:
|
||
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ 👤 Nutzer │
|
||
│ MaxMustermann │
|
||
│ AnnaSchulz │
|
||
│ # Tags │
|
||
│ #party #tanz #spaß │
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
Max ~5 total suggestions. Tapping a suggestion adds it as an active filter chip and clears
|
||
the search bar for another entry.
|
||
|
||
#### Active Filter Chips
|
||
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ 👤 MaxMustermann × # party × │
|
||
│ Alle Filter löschen │ ← shown when 2+ chips active
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
Filter combination logic:
|
||
|
||
| Combination | Logic |
|
||
|-------------|-------|
|
||
| Two tags: `#party` + `#tanz` | OR — posts with either tag |
|
||
| Two uploaders: Max + Anna | OR — posts from either |
|
||
| Uploader + tag: Max + `#party` | AND — posts by Max that also have `#party` |
|
||
|
||
#### Grid Layout
|
||
|
||
```
|
||
┌───────┬───────┬───────┐
|
||
│ │ │ │
|
||
│ │ │ │ 3-column, equal square cells
|
||
├───────┼───────┼───────┤ small gap (2 px)
|
||
│ │ ▶ │ │ ← video: small ▶ badge + duration
|
||
│ │ 0:42 │ │
|
||
└───────┴───────┴───────┘
|
||
```
|
||
|
||
- Tap cell → fullscreen lightbox, swipe navigates filtered set only
|
||
- Virtualized grid for performance on large events
|
||
|
||
---
|
||
|
||
## 3. Upload Flow
|
||
|
||
### Step 1 — Source Selection (Bottom Sheet)
|
||
|
||
Tapping the FAB slides up a bottom sheet (~300 ms spring animation).
|
||
Frosted glass, rounded top corners, drag handle at top. Tap outside or swipe down to dismiss.
|
||
|
||
```
|
||
┌──────────────────────────────────┐
|
||
│ ▬ (drag handle) │
|
||
│ │
|
||
│ 📸 Kamera │
|
||
│ Jetzt aufnehmen │
|
||
│ │
|
||
│ 🖼 Galerie │
|
||
│ Foto oder Video wählen │
|
||
│ │
|
||
│ [ Abbrechen ] │
|
||
└──────────────────────────────────┘
|
||
```
|
||
|
||
### Step 2a — Camera
|
||
|
||
Triggers `<input type="file" accept="image/*,video/*" capture="environment">`.
|
||
Native camera opens. After capture → Step 3.
|
||
|
||
### Step 2b — Gallery
|
||
|
||
Triggers `<input type="file" accept="image/*,video/*" multiple>`.
|
||
Native gallery picker with multi-select (up to ~10 items). After selection → Step 3.
|
||
|
||
### Step 3 — Preview & Metadata Screen
|
||
|
||
Full-screen, pushes in from right. Bottom nav hidden (immersive).
|
||
|
||
```
|
||
┌──────────────────────────────────┐
|
||
│ × Abbrechen Hochladen → │
|
||
├──────────────────────────────────┤
|
||
│ │
|
||
│ ┌────┐ ┌────┐ ┌────┐ → │ ← horizontal scroll, tap to preview
|
||
│ │img │ │img │ │ × │ │ × on each thumbnail to remove
|
||
│ └────┘ └────┘ └────┘ │
|
||
│ │
|
||
│ Beschreibung (optional) │
|
||
│ ┌────────────────────────────┐ │
|
||
│ │ │ │ ← auto-focused
|
||
│ └────────────────────────────┘ │
|
||
│ │
|
||
│ # Schnell-Tags │
|
||
│ [#Feier] [#Spaß] [#Party] … │ ← tap to append to caption
|
||
│ │
|
||
├──────────────────────────────────┤
|
||
│ ┌────────────────────────────┐ │
|
||
│ │ 📤 Hochladen │ │ ← sticky, disabled until ≥1 file
|
||
│ └────────────────────────────┘ │
|
||
└──────────────────────────────────┘
|
||
```
|
||
|
||
### Step 4 — Background Upload + Feedback
|
||
|
||
- Tapping "Hochladen" immediately returns to the feed (optimistic UX)
|
||
- Slim progress bar above the bottom tab bar while queue is active
|
||
- FAB gets a small spinning ring badge while uploads are in progress
|
||
- On completion: brief toast near the bottom ("✓ Hochgeladen")
|
||
- Rate-limit countdown banner anchored above the bottom bar (existing behavior)
|
||
|
||
---
|
||
|
||
## 4. Account Page
|
||
|
||
Single entry point for profile info and role-based dashboard navigation.
|
||
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ Mein Account │
|
||
├─────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌───────┐ │
|
||
│ │ M │ MaxMustermann │
|
||
│ └───────┘ 🏷 Gast │
|
||
│ Sommerfest 2025 │
|
||
│ 7 Uploads │
|
||
│ │
|
||
├── Dashboards ───────────────────────────┤ (entire section absent for guests)
|
||
│ │
|
||
│ ⭐ Host-Dashboard → │ (host + admin only)
|
||
│ 🛡 Admin-Dashboard → │ (admin only)
|
||
│ │
|
||
├── Konto ────────────────────────────────┤
|
||
│ │
|
||
│ ✏️ Anzeigename ändern → │
|
||
│ 🔑 PIN ändern → │
|
||
│ 🚪 Event verlassen → │ (red text, confirm sheet)
|
||
│ │
|
||
└─────────────────────────────────────────┘
|
||
│ 🏠 Feed · [📷+] · 👤 Account │
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
- "Dashboards" section is entirely absent in the DOM for plain guests — not just hidden
|
||
- "Event verlassen" triggers a bottom-sheet confirmation before action
|
||
- Avatar: colored circle with initial letter
|
||
|
||
---
|
||
|
||
## 5. Host Dashboard
|
||
|
||
Accessed via Account → ⭐ Host-Dashboard. Full-screen page, bottom nav visible.
|
||
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ ← 🎉 Host-Dashboard │
|
||
├─────────────────────────────────────────┤
|
||
│ │
|
||
│ ── Statistiken ────────────────────── │
|
||
│ ┌──────────┐ ┌──────────┐ │
|
||
│ │ 24 │ │ 156 │ │
|
||
│ │ Nutzer │ │ Uploads │ │
|
||
│ └──────────┘ └──────────┘ │
|
||
│ │
|
||
│ ── Event-Einstellungen ────────────── │ ← collapsible section
|
||
│ │
|
||
│ Neue Uploads sperren │
|
||
│ ○────────────● Gesperrt │ ← toggle
|
||
│ Keine neuen Uploads möglich │
|
||
│ │
|
||
│ ── Nutzerverwaltung ───────────────── │ ← collapsible section
|
||
│ │
|
||
│ 🔍 Nutzer suchen… │
|
||
│ ┌───────────────────────────────────┐ │
|
||
│ │ 👤 MaxMustermann Gast [🚫] │ │
|
||
│ │ 👤 AnnaSchulz Gast [🚫] │ │
|
||
│ │ 👤 GesperrterNutzer [↩] │ │ ← banned: undo icon
|
||
│ └───────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────┘
|
||
│ 🏠 Feed · [📷+] · 👤 Account │
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
- Sections have a chevron toggle to collapse/expand (helps on small phones)
|
||
- Ban/unban: icon tap + bottom sheet confirmation ("Nutzer wirklich sperren?")
|
||
- User list virtualized for large events
|
||
|
||
---
|
||
|
||
## 6. Admin Dashboard
|
||
|
||
Most complex page. Uses an **inner tab bar** directly below the header to divide the four
|
||
functional areas. The inner tabs are independent of the bottom nav.
|
||
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ ← 🛡 Admin-Dashboard │
|
||
├─────────────────────────────────────────┤
|
||
│ [Stats] [Config] [Export] [Nutzer] │ ← inner tab bar (scrollable if needed)
|
||
├─────────────────────────────────────────┤
|
||
│ │
|
||
│ [Tab content] │
|
||
│ │
|
||
└─────────────────────────────────────────┘
|
||
│ 🏠 Feed · [📷+] · 👤 Account │
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
### Stats Tab
|
||
|
||
```
|
||
┌──────────┐ ┌──────────┐
|
||
│ 156 │ │ 24 │
|
||
│ Uploads │ │ Nutzer │
|
||
└──────────┘ └──────────┘
|
||
┌──────────┐ ┌──────────┐
|
||
│ 2.1 GB │ │ 3 │
|
||
│ Speicher │ │ Gesperrt │
|
||
└──────────┘ └──────────┘
|
||
```
|
||
|
||
2×2 metric card grid. Values large and prominent. Optionally expandable to show time-series
|
||
charts on tap.
|
||
|
||
### Config Tab
|
||
|
||
```
|
||
Upload-Limit / Nutzer
|
||
┌────────────────────────────────┐
|
||
│ 10 │
|
||
└────────────────────────────────┘
|
||
|
||
Zeitfenster (Sek.)
|
||
┌────────────────────────────────┐
|
||
│ 60 │
|
||
└────────────────────────────────┘
|
||
|
||
Max. Dateigröße (MB)
|
||
┌────────────────────────────────┐
|
||
│ 50 │
|
||
└────────────────────────────────┘
|
||
|
||
┌────────────────────────────────┐
|
||
│ 💾 Speichern │ ← sticky at bottom of tab scroll area
|
||
└────────────────────────────────┘
|
||
```
|
||
|
||
Each setting: full-width label + input. Save button always reachable without scrolling.
|
||
|
||
### Export Tab
|
||
|
||
```
|
||
── Galerie ──────────────────────────
|
||
[ 🔓 Galerie freigeben ]
|
||
|
||
── Export-Jobs ──────────────────────
|
||
[ 🔄 Aktualisieren ]
|
||
|
||
┌───────────────────────────────────┐
|
||
│ HTML-Viewer ● Fertig [↓ ZIP] │
|
||
│ JSON-Export ⏳ Läuft… │
|
||
│ ZIP-Archiv ✗ Fehler [↺] │
|
||
└───────────────────────────────────┘
|
||
|
||
[ + Neuer Export-Job ]
|
||
```
|
||
|
||
- Status chips: green (Fertig), amber (Läuft), red (Fehler)
|
||
- Download button inline per completed job
|
||
- Only the jobs list refreshes on "Aktualisieren" — no full page re-render
|
||
|
||
### Nutzer Tab
|
||
|
||
Same structure as Host Nutzerverwaltung, with any additional admin-only actions
|
||
(e.g. role assignment) added as extra controls per row.
|
||
|
||
---
|
||
|
||
## Touch gestures vs. desktop buttons (planned extension)
|
||
|
||
Where a gesture is more ergonomic on mobile than a button, EventSnap prefers the gesture
|
||
on touch and mirrors it as an explicit button on desktop. Inspired by Instagram, WhatsApp
|
||
and Telegram — long-press for context, swipe to dismiss, double-tap to react.
|
||
|
||
| Surface | Touch gesture | Desktop equivalent |
|
||
|-----------------------------------------|-------------------------------------|------------------------------------------|
|
||
| Post card | Long-press → context bottom sheet | ⋯ kebab in the card corner |
|
||
| Comment row | Long-press → bottom sheet | ⋯ next to the comment timestamp |
|
||
| User row (Host / Admin dashboards) | Long-press → bottom sheet | Inline buttons (ban, promote, reset PIN) |
|
||
| Lightbox | Swipe left / right | ←/→ arrow keys + on-screen chevrons |
|
||
| Lightbox | Swipe down to close | Esc + ✕ button |
|
||
| Bottom sheet | Swipe down to dismiss | Click backdrop or × in the sheet header |
|
||
| Feed | Pull to refresh | Refresh icon next to the view toggle |
|
||
| Post (any) | Double-tap → like | Click the heart icon |
|
||
|
||
**Discoverability rule:** every gesture must have a visible button equivalent on the same
|
||
page. Gestures are never the *only* path to an action. Helps with stylus users,
|
||
accessibility, and people who don't know the gesture vocabulary.
|
||
|
||
**Context bottom-sheet pattern** (used by every long-press above):
|
||
|
||
```
|
||
┌──────────────────────────────────┐
|
||
│ ▬ (drag handle) │
|
||
│ │
|
||
│ 🗑 Löschen │ ← destructive action red
|
||
│ 📥 Original anzeigen │
|
||
│ 🔗 Teilen │
|
||
│ 🚩 Melden │ (only on others' content)
|
||
│ │
|
||
│ [ Abbrechen ] │
|
||
└──────────────────────────────────┘
|
||
```
|
||
|
||
Each sheet is composed from a shared `<ContextSheet>` component (planned) with a single
|
||
`actions: ContextAction[]` prop. Adding a new gesture context = define the actions array
|
||
where needed. Drop-in, one file.
|
||
|
||
## Design Principles Summary
|
||
|
||
| Principle | Application |
|
||
|-----------|-------------|
|
||
| Thumb zone | All primary actions in bottom ~20% of screen |
|
||
| One-hand operation | FAB centered, bottom sheets dismissable with swipe |
|
||
| Minimal taps to upload | Source → picker → preview → upload: 4 taps |
|
||
| Immediate feedback | Optimistic return to feed, background upload |
|
||
| Progressive disclosure | Caption/tags optional; CTA always reachable |
|
||
| No role clutter in nav | Role links only in Account, bar stays clean |
|
||
| Collapsible sections | Long management pages stay usable on small phones |
|
||
| Inner tabs for complex pages | Admin dashboard split across 4 focused tabs |
|
||
| Gestures over chrome | Long-press for context menus, swipe to dismiss, double-tap to react — always with a button fallback for desktop and accessibility |
|