b2in/public/_cabinet/_docs/b2in-display-implementation.md
2026-04-10 17:18:17 +02:00

175 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# B2in Display Frontend-Implementation
**Ziel:** Frontend-Webapp für das B2in Schaufenster-Display (9:16 Portrait, 4355 Zoll).
**Pfad:** `public/_cabinet/b2in/index.html`
**Konzept-Basis:** `public/_cabinet/_docs/b2in-displays.md`
---
## Ist-Zustand
Es gibt bereits drei Display-Typen unter `public/_cabinet/`:
| Typ | Pfad | Status | Beschreibung |
|-----|------|--------|-------------|
| **Video-Display** | `index.html` | Live, stabil | CABINET-Branding. Videos + Footer-Rotation. API: `/api/display/config` |
| **Offers** | `offers/index.html` | Entwickelt, nicht live | CABINET-Branding. iFrame-basierte Slide-Rotation mit `config.json` |
| **Info-Tablet** | `info/index.html` | Entwickelt | CABINET-Branding. Store-Status/Öffnungszeiten. API: `/api/cabinet-tablet/status` |
| **B2in Display** | `b2in/` | **Neu wird jetzt gebaut** | B2in-Branding. Playlist mit Videos/Bildern + Text. API: `/api/b2in-display/playlist` |
---
## Architektur-Übersicht
```
public/_cabinet/b2in/
├── index.html ← Haupt-Webapp (Playlist-Engine + UI)
├── b2in-styles.css ← Display-spezifische Styles
└── (assets/) ← Medien werden per API/URL referenziert, nicht lokal
```
Shared CSS aus `public/_cabinet/shared/cabinet-base.css` wird **nicht** importiert das B2in-Display hat ein eigenständiges Branding (dunkel, B2in statt CABINET). Es ist ein komplett eigenes Design-System.
---
## Schritte
### Schritt 1: HTML-Grundgerüst + CSS
**Datei:** `public/_cabinet/b2in/index.html` + `b2in-styles.css`
Layout gemäß Konzept (Abschnitt 2.1):
```
┌─────────────────────────┐
│ HEADER │ ← B2in-Logo (links) + Claim (rechts)
│ B2in · Claim │ Fest, immer sichtbar
├─────────────────────────┤
│ │
│ ┌─────────────────┐ │ ← Gradient oben (dunkel → transparent)
│ │ VIDEO / BILD │ │ 16:9 Content-Bereich
│ │ (16:9 Media) │ │ object-fit: cover
│ └─────────────────┘ │ ← Gradient unten (transparent → dunkel)
│ │
├─────────────────────────┤
│ TEXTFELD │ ← Headline (max 40 Zeichen)
│ Headline + Subline │ Subline (max 80 Zeichen)
├─────────────────────────┤ Wechselt synchron mit Media
│ FOOTER │ ← "Marcel Scheibe" + "b2in.de" + QR-Code
│ Name · URL · QR │ Fest, immer sichtbar
└─────────────────────────┘
```
**Design-Entscheidungen:**
- Dunkler Hintergrund (#0a0a0a) B2in-Branding, nicht CABINET-weiß
- 9:16 Aspect Ratio Container (wie bestehende Displays)
- Gradient-Overlays oben/unten über dem Media-Bereich
- IBM Plex Sans Font (wie alle Cabinet-Displays)
- Akzentfarbe: B2in-Blau (wird aus Branding-Guide übernommen)
- Kein CABINET-Logo, kein Accent #009FE3
### Schritt 2: Playlist-Engine (JavaScript)
**Klasse:** `B2inDisplayApp`
Kernfunktionalität:
1. **API laden**`GET /api/b2in-display/playlist` (wird später gebaut, erstmal Mock/Fallback)
2. **Playlist sortieren** → nach `sort_order`, nur `is_active === true`
3. **Gewichtung anwenden** → 70/30 Immobilien/Möbel-Verteilung berechnen
4. **Rotation starten** → Item für Item durchspielen
Item-Wechsel-Logik:
- **Video:** Abspielen bis Ende → nächstes Item
- **Bild:** `duration_seconds` abwarten → nächstes Item
- **Am Ende der Playlist:** Von vorne beginnen
### Schritt 3: Video-Handling
Bewährtes Pattern aus dem bestehenden `index.html` übernehmen:
- `autoplay muted playsinline` für Browser-Autoplay-Policy
- Memory-Management: `src` leeren + `load()` nach jedem Video
- Preloading: Nächstes Item im Hintergrund vorladen
- Start-Timeout (10s) → bei Timeout überspringen
- Watchdog: Prüft alle 5s ob Video noch läuft
- Error-Handling: Bei Fehler → Item überspringen, nie schwarzer Screen
### Schritt 4: Bild-Handling
- `<img>` Element mit `object-fit: cover`
- Duration aus Item oder globaler `default_image_duration`
- Ken-Burns-Effekt (optionaler langsamer Zoom per CSS)
- Preload via `new Image()` im Hintergrund
### Schritt 5: Transitions
Drei Typen (per CMS-Setting steuerbar):
| Typ | Umsetzung |
|-----|-----------|
| `fade` | Opacity 1→0, dann 0→1 |
| `crossfade` | Neues Element über dem alten einblenden (empfohlen, Standard) |
| `slide` | CSS transform translateX |
Text-Synchronisation:
1. Text fade-out (400ms)
2. Neuen Text setzen
3. Text fade-in (400ms)
4. Startet 200ms vor Media-Wechsel
### Schritt 6: Polling + Stabilität
Gleicher Ansatz wie Info-Tablet, aber mit 60s Intervall:
- **Lightweight Check** alle 60s → `GET /api/b2in-display/check`
- **Full Fetch** nur bei Timestamp-Änderung
- Laufendes Video/Bild wird zu Ende gespielt, dann neue Playlist
- localStorage-Cache als Offline-Fallback
- Auto-Reload alle 6 Stunden
- Connection-Recovery: 3 Fehler → 5 Min Pause → Retry
- 30 Min offline → Page-Reload
### Schritt 7: Standby-Modus
Wenn `display_active === false`:
- Nur B2in-Logo auf dunklem Hintergrund
- Kein Content, kein Textfeld, kein Footer-Text
- Polling läuft weiter (wartet auf Aktivierung)
### Schritt 8: Error-Overlay
Bei kritischen Fehlern (keine Playlist, kein Media):
- Dezentes B2in-Logo auf dunklem Hintergrund
- Niemals Browser-Fehler oder weißer Screen
- Automatischer Retry im Hintergrund
---
## API-Abhängigkeit
Das Frontend wird **zunächst mit Mock-Daten** gebaut. Die API (`/api/b2in-display/playlist` + `/check`) wird als separater Schritt im Backend implementiert. Das Frontend erkennt automatisch, ob die API verfügbar ist, und fällt auf eingebettete Demo-Daten zurück.
**Mock-Playlist für Entwicklung:**
```javascript
const MOCK_PLAYLIST = {
settings: {
display_active: true,
footer_name: "Marcel Scheibe",
footer_url: "b2in.de",
transition: { type: "crossfade", duration_ms: 800 },
default_image_duration: 10
},
items: [
// Demo-Items mit Platzhalter-Medien
],
updated_at: new Date().toISOString()
};
```
---
## Abgrenzung (was NICHT in diesem Schritt passiert)
- Kein CMS-Backend (Model, Migration, Controller, Admin-UI) → separater Schritt
- Kein Media-Upload → Videos/Bilder werden per URL referenziert
- Keine Gewichtungs-Logik im Backend → wird im Frontend berechnet
- Keine Änderung am bestehenden Video-Display (`index.html`) → bleibt unberührt