diff --git a/docs/CONCEPT_HTML_VIEWER.md b/docs/CONCEPT_HTML_VIEWER.md new file mode 100644 index 0000000..d843e34 --- /dev/null +++ b/docs/CONCEPT_HTML_VIEWER.md @@ -0,0 +1,278 @@ +# HTML Viewer Export Concept + +## Overview + +The HTML Viewer export produces a **self-contained offline ZIP** that is a read-only clone +of the live EventSnap feed. Opening `index.html` in any modern browser shows the full event +gallery — list view, grid view, search, filter, lightbox — with no internet connection or +server required. + +It **replaces the current HTML export job type**. The old HTML export produced a raw +minijinja-rendered template; the new viewer supersedes it entirely. The existing `html` +job variant in the backend is repurposed to run this flow instead of being kept alongside +it. + +It is distinct from the ZIP archive export (which is raw media files). The viewer is a +polished, navigable web app bundled with the event's content. + +--- + +## User-Facing Behavior + +- Download `event_name_viewer.zip`, unzip, open `index.html` +- Full list view (chronological, newest first) and grid view with search/filter +- Likes, comments, and reaction counts shown (static snapshot from export time) +- Read-only: no uploads, no auth, no dashboards +- Works offline, no CDN or external resources + +--- + +## Architecture + +### Separate Static SvelteKit App + +A new mini SvelteKit project lives at `frontend/export-viewer/` within the same monorepo. +It uses `adapter-static` and is kept completely independent of the main app. + +**Why separate rather than a shared route:** +- The viewer must be distributable as a standalone static bundle; the main app uses + `adapter-node` and cannot be mixed +- Keeping it separate avoids auth, store, and routing dependencies leaking in +- Simpler to reason about: the viewer has exactly two concerns (list view, grid view) + +**Why same repo:** +- Shares Tailwind config and design tokens → visual parity with the main app +- Single `pnpm` workspace, no separate CI needed +- Backend can reference the pre-built output by relative path + +### Pre-Built Output Committed to Repo + +The viewer is built once and its output committed to `backend/static/export-viewer/`. +The backend export job **does not run a Node build** at runtime — it just copies the +pre-built assets and injects event data alongside them. + +When the viewer source changes, a developer rebuilds it locally (`pnpm build` in +`frontend/export-viewer/`) and commits the updated `backend/static/export-viewer/` output. + +--- + +## ZIP Structure + +``` +event_name_viewer.zip/ +├── index.html ← entry point; open this in any browser +├── _app/ +│ └── immutable/ +│ ├── viewer.[hash].js ← all Svelte/app logic, single bundle +│ └── viewer.[hash].css ← all styles including Tailwind output +├── data.json ← injected by backend at export time +└── media/ + ├── abc123_thumb.jpg ← ~400 px wide, used in grid cells + ├── abc123_full.jpg ← original or capped (see Media Strategy) + ├── def456_thumb.jpg + └── def456.mp4 ← videos included as-is +``` + +No external font CDN, no Google Fonts, no remote scripts. All assets are local. + +--- + +## data.json Schema + +Generated by the backend export job. The viewer fetches this file on startup via +`fetch('./data.json')` (relative path, works from filesystem). + +```json +{ + "event": { + "name": "Sommerfest 2025", + "exported_at": "2025-07-15T20:00:00Z" + }, + "posts": [ + { + "id": "abc123", + "uploader": "MaxMustermann", + "caption": "Tolle Stimmung! #party #spaß", + "tags": ["party", "spaß"], + "timestamp": "2025-07-15T18:30:00Z", + "likes": 12, + "comments": [ + { + "author": "AnnaSchulz", + "text": "So schön!", + "timestamp": "2025-07-15T18:35:00Z" + } + ], + "media": [ + { + "type": "image", + "thumb": "media/abc123_thumb.jpg", + "full": "media/abc123_full.jpg", + "width": 1920, + "height": 1080 + } + ] + } + ] +} +``` + +All post data (likes, comments, tags) reflects the state at export time. No live updates. + +--- + +## Media Strategy + +### Images + +| Variant | Purpose | Max dimension | Format | +|---------|---------|---------------|--------| +| `_thumb` | Grid cells, list post thumbnail | 400 px wide | JPEG q75 | +| `_full` | Lightbox / full-screen view | Original, or 2000 px cap if >5 MB | JPEG q85 | + +The backend applies compression only when the original exceeds a threshold (e.g. >5 MB for +images). Below that threshold the original is used as `_full` unchanged. + +The full-resolution original is always available via the separate ZIP archive export. + +### Videos + +Included as-is (no server-side transcoding). The viewer uses a standard `