10-04-2026

This commit is contained in:
Kevin Adametz 2026-04-10 17:18:17 +02:00
parent 4d6b4930b2
commit 4bb89aad8c
836 changed files with 52961 additions and 5950 deletions

View file

@ -0,0 +1,175 @@
# 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