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

6.6 KiB
Raw Blame History

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 ladenGET /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:

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