# B2in Display – Frontend-Implementation **Ziel:** Frontend-Webapp für das B2in Schaufenster-Display (9:16 Portrait, 43–55 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 - `` 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