b2in/dev/2026-08-28/entwicklungsplan.md
Kevin Adametz b5ed756d67 Git-Deploy per Tag, Betriebsüberwachung und Release-Seeder für den Livegang
- dev/betrieb/deploy.sh: Deploy nur per Tag, mit --seed (Seeder im Wartungsmodus)
  und --initial (Vorschau/Live einmalig an Ort und Stelle auf Git umstellen)
- dev/betrieb/vorschau-auffrischen.sh: Live-DB und Live-Medien in die Vorschau (Generalprobe)
- InteriorReleaseSeeder: alle Inhaltsschritte des Relaunchs in einem Lauf, idempotent;
  Displays, Playlists und Mediathek bleiben unberührt
- DisplayInteriorBrandingSeeder überschreibt keinen inzwischen gepflegten Claim mehr
- Monitoring: GlitchTip (Server + Browser), /health mit Token, Scheduler-Herzschlag,
  Alarme per Mail und ntfy, Version in about und /health
- public/build versioniert; .gitignore: macOS-Muster Icon traf resources/views/flux/icon/
- Runbook: dev/betrieb/README.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:56:23 +00:00

2519 lines
160 KiB
Markdown
Raw 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.

# Entwicklungsplan: B2in Interior – Neuausrichtung Website & CMS
**Erstellt:** 28.08.2026 · zuletzt aktualisiert 28.08.2026
**Basis:** `B2in_Interior-Konzept.md`, `umsetzungliste.md`, `umsetzung-inhalte.md`
**Vorgänger:** `dev/2026-02-27/b2in-website-umsetzungsplan.md`
**Rahmen:** 37–39 Std. freigegeben (18 + 8 + 4 + 3 + 4–6)
**Status:** Revision 14 – **AP0–AP6 umgesetzt**, Seite abgestimmt, Markentexte Rotpunkt und Febrü
eingearbeitet. **Arbeitsplan 14.09. (`umsetzungen-14-09-2026.md`), Punkt 1.1 Farbwelt und Logo
umgesetzt (Nachtrag 6).** Offen: Bildmaterial, Rechtstexte und die Herstellerfreigaben.
**Umgebung:** Dev Container, `php artisan …` (nicht `vendor/bin/sail …`)
> **Verbindliche Schreibweisen:** **Interior** (nicht „Interieur"), **Febrü**, **Rotpunkt Küchen**, **CABINET**.
> Das Konzeptpapier schreibt durchgehend „Interieur" – die Umsetzungsliste überschreibt das. Gilt für Code,
> Texte, Commit-Messages und Datenbank-Inhalte.
---
## 0. Stand & nächste Schritte
**Stand 03.09.2026:** T1 und B1–B13 abgearbeitet, **alle Arbeitspakete AP0–AP6 umgesetzt**, die
Bestandsinhalte auf den Interior-Auftritt umgestellt, die Seite abgestimmt und alle vier Punkte
der Abstimmungsrunde vom 03.09. umgesetzt (Nachtrag 5). Alle drei Markenseiten haben jetzt einen
ausführlichen Text. `php artisan test --compact`: **873 grün** (3217 Assertions), Pint sauber,
`npm run build:b2in` gebaut.
**Die Seite ist testbereit** – die ausstehenden Zulieferungen trägt Marcel selbst im CMS nach.
| Paket | Stand |
|---|---|
| AP0 Vorbereitung | ✅ |
| AP1 Umstrukturierung & Umtextung | ✅ |
| AP2 Kernmarken-Modul + `/marken` + Galerie | ✅ |
| AP3 Terminanfrage-Widget + WhatsApp-Link | ✅ (Nummer fehlt noch) |
| AP6 Sponsoring | ✅ |
| Bestandsinhalte FAQ / About / Kontakt / Magazin | ✅ |
| Markenseiten Rotpunkt und Febrü (Zulieferung 31.08.) | ✅ |
| Abstimmungsrunde 03.09.: korrigierte Texte, CABINET-Seite, Galerie-Hinweise, Abschluss-CTA | ✅ |
| AP4 Display-Spiegelung | ✅ |
| AP5 Rubrik-Seite „Referenzen" | ✅ |
| Arbeitsplan 14.09. · 1.1 Farbwelt, Logo je Welt, Welten-Wechsler | ✅ (Nachtrag 6) |
| Welten-Trennung: Navigation, Magazin, Über uns, FAQ, Widget je Welt | ✅ (Nachtrag 7, Erweiterung zu 1.1) |
| Arbeitsplan 14.09. · 1.2 Tischlerei als vierte Marke | ✅ Otterpohl mit Text und Logo (Nachtrag 9 + 15) |
| Arbeitsplan 14.09. · 1.4 Akkordeon und Widget-Beschriftung je Marke | ✅ (bereits seit 03.09., CABINET-Text fehlt im CMS) |
| Arbeitsplan 14.09. · 1.3 Herkunfts-Sektion „Aus der Region gefertigt“ | ✅ (Nachtrag 10 + 11: Standorte an der Marke, Ablauf CABINET von Marcel bestätigen lassen) |
| Abstimmung 23.09. · WhatsApp zuerst im Widget, Business-Nummer, Farbvariante „Modern“ mit Schalter | ✅ (Nachtrag 18, Palette von Marcel entscheiden lassen) |
| Briefing 24.09. · Entwurf Startseite „Greige“ mit Gold-Logo, Hero-Slideshow, Projekttext | ✅ Entwurf unter /entwurf (Nachtrag 19); offen: Foto Marcel, Slides selbst pflegen, Logo-Verläufe |
| Zulieferung 24.09. · Otterpohl Text v2, Bilder (Werkstatt + Referenzen) | ✅ (Nachtrag 19, Unterschriften und Referenzen-Fallback von Marcel bestätigen lassen) |
### Nächste Schritte
**Vor dem Versand des Testlinks — nichts.** Alle Seiten laden, die Suite ist grün, die Assets sind
gebaut. Was fehlt, ist Material, kein Code.
**Was Marcel selbst nachträgt (alles im CMS, ohne Codeänderung):**
| Was | Wo im Admin |
|---|---|
| Stimmungsbilder der drei Marken (Logos liegen seit 03.09. vor) | Kernmarken → Marke bearbeiten |
| Echte Projektfotos statt der Demobilder (siehe Warnung unten) | Kernmarken → Bildergalerie |
| Herstellerseiten-URLs | Kernmarken → Herstellerseite |
| Motiv für den Kopf von `/marken` | Inhalte → `marken` → `hero` |
| WhatsApp-Nummer – seit Nachtrag 18 per Seeder gesetzt (0521-98620100), Änderung weiter hier | Inhalte → `global` → `contact` → `whatsapp` |
| Welches Display auf der Website läuft | Inhalte → `referenzen` → `display_feed` → `display_id` |
**Beim Deployment mit erledigen (Abschnitt 8):**
1. **Rechtstexte** – AGB und Datenschutzerklärung beschreiben weiter das alte Geschäft
(„Connecting Design and Property", Dubai/Lissabon, Supply-Chain-Management,
`resources/lang/de/b2in_legal.php` Zeilen 117 und 221). Marcel lässt die Formulierung prüfen,
danach eintragen. Der WhatsApp-Absatz kommt im selben Zug dazu.
2. **OG-Bild** – das Layout verweist auf `img/assets/b2in-og-default.jpg`, die Datei existiert
nicht. Bis dahin zeigt jede geteilte Seite eine Vorschau ohne Bild.
3. **Seeder-Passwort rotieren** (B8) – steht weiter in der Git-Historie.
4. Die zwei Magazin-Beiträge und die Sponsoren-Kurztexte gegenlesen lassen.
5. **CABINET-Seitentext freigeben lassen** – für CABINET lag keine Zulieferung vor, der Text ist
von uns nach dem Muster der beiden abgestimmten Seiten verfasst (Nachtrag 5).
6. **Demobilder der Rubrik „Realisiertes Projekt" ersetzen.** Seit dem Entfallen der
„BEISPIEL"-Kennzeichnung geben sie sich als echte Referenzen aus.
**Danach offen, aber nicht Teil dieses Auftrags:** YouTube-Einbindung mit Consent-Gate auf der
Referenzen-Seite (Abschnitt 4). Sinnvoll, sobald es Videos gibt – vorher wäre es ein Consent-Gate
ohne Inhalt.
---
## 1. Ist-Architektur (relevanter Ausschnitt)
### 1.1 Content-Pipeline
```
resources/lang/de/b2in.php (themes.b2in.<section>) ← Quelltexte / Fallback
├─ config/cms_section_map.php section → [group, key]
├─ database/seeders/CmsContentSeeder.php
▼
flux_cms_contents (group, key, type, value:JSON, translatable de/en)
├─ FluxCms\Core\Services\CmsContentService (Cache: flux_cms.content.__all__)
▼
cms_theme_section('hero') → CMS gewinnt, sonst trans('b2in.themes.b2in.hero')
```
Redaktion unter `admin/flux-cms/content` → [`content-index.blade.php`](../../resources/views/livewire/admin/cms/content-index.blade.php)
(Volt, 782 Z.): Group/Key-Editor mit JSON-Repeater-Modal, Medien-Picker, Flux-Editor für HTML.
**Kann Werte ändern, aber keine Sektion neu anlegen.**
### 1.2 Seiten & Sektionen
- Startseite: `routes/web.php` → `resources/views/web/home.blade.php`
- Sektionen = Livewire-Komponenten unter `app/Livewire/Web/Components/Sections/`,
Muster `mount() { $this->content = cms_theme_section('<key>'); }`
- Ist-Reihenfolge: `header → hero → founder-bar → content-section(synergie) → vision-section → ecosystem-core → cta-section → footer`
- Navigation und Ankündigungsleiste liegen **bereits im CMS** (`global.header.navigation`, `global.announcement_bar`)
→ Umbau ist Redaktion, kein Code.
- SEO: `@section('title')` / `@section('meta_description')` je View; OG-Tags leiten sich
in `web-master.blade.php` automatisch daraus ab → **OG kostet keinen Extra-Aufwand** (aber siehe B6).
- Immobilien rendern statisch aus `resources/lang/de/immobilien-azizi.json` – wie gefordert.
### 1.3 Display-CMS
`Display` (Gerät) → `DisplayPlaylist` (draft/published) → `DisplayVersion` („Modul", Enum
`DisplayVersionType`: `video-display` | `b2in` | `offers`) → `DisplayVersionItem`.
[`DisplayPlaylistConfigBuilder`](../../app/Services/DisplayPlaylistConfigBuilder.php) erzeugt das
Player-JSON. Player: `public/_cabinet/display/index.html`, festes 1080×1920-Raster (9:16), per Scale eingepasst.
API vorhanden: `/api/display/{display}/config`, `/api/display/preview/{token}`,
`/api/display/module/{module}/preview` – registriert für Portal-Domain **und** als Fallback für alle Domains.
### 1.4 Sonstiges
- Kontaktformular: `packages/acme/contact-form` – **unterstützt bereits `customFields` + Presets,
Honeypot, Rate-Limit und `SpamDetector`.** Das Terminanfrage-Widget kann darauf aufsetzen.
- Tests: Pest, SQLite in-memory, `php artisan test --compact`.
---
## 2. Gefundene Bugs & Optimierungen
Beim Abgleich der Umsetzungsliste mit dem Code aufgefallen.
**Stand 28.08.2026: alle Befunde behoben, Suite grün (699 Tests, 2715 Assertions).**
| # | Befund | Status |
|---|---|---|
| T1 | Info-Tablet: Leeren per X wird nicht gespeichert | ✅ behoben |
| B1 | CMS-Seeder überschreibt redaktionelle Inhalte | ✅ behoben |
| B2 | Magazin-CMS nicht ans Frontend angeschlossen | ✅ behoben (Variante A: Slug-Routen + 301) |
| B3 | Ankündigungsleiste bleibt nach Umtexten unsichtbar | ✅ behoben |
| B4 | Footer hartkodiert, nicht pflegbar | ✅ behoben |
| B5 | Englische Footer-Labels auf deutscher Seite | ✅ behoben |
| B6 | 19 Seiten erben alte Dubai-Beschreibung | ✅ behoben (Fallback + canonical) |
| B7 | Content-Seeder nicht registriert | ✅ behoben |
| B8 | Klartext-Passwort im Seeder | ✅ behoben |
| B9 | Medien-Picker greift bei leeren Logo-Feldern nicht | ✅ behoben |
| B10 | Alt-Begriffe im Seiteninhalt | ✅ behoben in AP1 |
| B12 | `/service` und `/portfolio` lieferten 500 | ✅ behoben (301) |
| B11 | `/magazin/{unbekannt}` liefert 200 statt 404 | ✅ behoben |
---
### T1 · Info-Tablet: Leeren der Öffnungszeiten wird nicht gespeichert ✅
Gemeldet am 28.08.2026: „Wenn ich den Inhalt weg-X, kommt nicht Geschlossen. Der Wert kommt
wieder, wenn ich in ein anderes Feld klicke. Setze ich Öffnung und Schluss auf X und speichere,
ist Öffnung wieder der alte Wert."
**Drei Ursachen, alle in `cabinet-info-tablet.blade.php` bzw. `CabinetInfoTablet.php`:**
1. **Zwei Submit-Handler auf demselben Formular.** `wire:submit.prevent="save"` **und**
`x-on:submit.prevent="… $wire.save(hours)"` waren gleichzeitig gebunden – `save()` lief pro
Klick zweimal, einmal mit den DOM-Werten und einmal ohne. Welcher Aufruf zuletzt schrieb,
war nicht deterministisch. Das erklärt „speichert nur den letzten".
2. **`wire:model.live` auf den Zeit-Pickern.** Jede Änderung löste einen Roundtrip aus; beim
erneuten Rendern hat Livewire ein zuvor geleertes Feld mit dem unveränderten Serverwert
überschrieben. Das erklärt „der Wert kommt wieder, wenn ich in ein anderes Feld klicke".
3. **Geöffnet/Geschlossen wurde serverseitig gerechnet** und konnte deshalb erst nach einem
Roundtrip umspringen. Das erklärt „es kommt nicht Geschlossen".
**Behoben:** nur noch ein Submit-Pfad (das DOM ist die Quelle der Wahrheit), `.live` entfernt,
die Geöffnet/Geschlossen-Anzeige rechnet clientseitig per Alpine und reagiert damit sofort auf das X.
**Zusätzlich:** `save(array $hours)` hat über `property_exists()` **jede** öffentliche Property
aus dem Client-Payload beschreibbar gemacht – auch `storeStatus` und `contactEmail`. Jetzt greift
eine Allowlist auf die 14 Zeitfelder.
**Tests:** 5 neue Fälle in `tests/Feature/CabinetInfoTabletTest.php` (15 grün) – u. a. mehrere Tage
in einem Submit leeren, nur die Öffnung leeren, Fallback ohne DOM-Payload, Allowlist.
**Noch am Gerät zu prüfen:** die drei Ursachen liegen im Browser-Verhalten; die Tests sichern den
Serverpfad ab. Bitte im Portal einmal gegenprüfen: mehrere Tage leeren → speichern → neu laden.
### B1 · Der CMS-Seeder überschreibt alles, was Marcel pflegt ✅
`database/seeders/CmsContentSeeder.php:76-90` – bestehende Einträge werden bedingungslos
per `$content->update()` mit den Werten aus der Lang-Datei überschrieben. Geschützt sind nur
die zwei Sektionen in `$skipSections`.
**Wirkung:** Jeder erneute Seeder-Lauf – auch der geplante nach der Umtextung – setzt sämtliche
redaktionellen Änderungen zurück. Die Zusage „von Marcel selbst pflegbar" hält so nicht.
**Behoben:** Default ist jetzt „nur anlegen, wenn nicht vorhanden". Überschreiben nur noch über
`$seeder->overwriteExisting = true`. Der Lauf meldet, wie viele Einträge unangetastet blieben, und
leert den CMS-Cache, wenn er etwas geändert hat.
**Tests:** 3 neue Fälle in `tests/Feature/Cms/CmsContentSeederTest.php` – gepflegter Inhalt bleibt,
fehlende Sektionen werden trotzdem angelegt, bewusstes Überschreiben funktioniert.
### B2 · Das Magazin-CMS ist nicht mit dem Frontend verbunden ✅
- Admin `admin/flux-cms/articles` schreibt nach `cms_articles` (Model `CmsArticle`).
- Frontend liest `trans('b2in.articles')`:
`app/Livewire/Web/Components/Sections/MagazinList.php:20` und `MagazinDetail.php:48`.
- `CmsArticle` wird **außerhalb des Admin-Bereichs nirgends verwendet** (verifiziert per Grep).
**Wirkung 1:** Was Marcel im Magazin-CMS bearbeitet, erscheint nicht auf der Website.
**Wirkung 2 (für dieses Projekt entscheidend):** Die Umsetzungsliste sagt für die Kernmarken
„adaptiert vom **Magazin-Muster**". Das Admin-CRUD ist als Vorlage brauchbar, die
Frontend-Anbindung fehlt dort aber – **dieser Fehler darf nicht mitkopiert werden.**
**Bei der Umsetzung aufgefallen – das ist kein reiner Verdrahtungsfehler:**
Die Artikel liegen in beiden Quellen (5 in `cms_articles`, 5 in der Lang-Datei, inhaltsgleich),
**aber unter verschiedenen Schlüsseln**: die Lang-Datei nutzt 1–5, die Datenbank die IDs 8–12.
Die öffentlichen URLs lauten heute `/magazin/1` … `/magazin/5`. Eine Umstellung auf das Model
**ändert damit die öffentlichen URLs** und braucht eine Slug-Route plus Weiterleitungen von den
alten numerischen IDs – sonst brechen bestehende Links und die Google-Indexierung.
**Deshalb bewusst nicht einfach umgestellt.** Entscheidung nötig:
| Variante | Aufwand | Wirkung |
|---|---|---|
| A · Slug-Routen `/magazin/{slug}` + 301 von den alten IDs | ≈3–4 Std. | sauber, CMS wird wirksam, URLs werden sprechend |
| B · Numerische Route beibehalten, Auflösung über die DB-ID | ≈2 Std. | CMS wird wirksam, aber `/magazin/1` … `/magazin/5` werden zu 404 |
| C · Vorerst so lassen | 0 | Magazin funktioniert, das Magazin-CMS bleibt aber wirkungslos |
**Umgesetzt: Variante A.**
- `CmsArticle::forFrontend()` liefert die veröffentlichten Artikel nach Slug indiziert.
Das CMS gewinnt; solange keine Artikel gepflegt sind, greift die Lang-Datei als Fallback –
dieselbe Reihenfolge wie bei `cms_theme_section()`, damit das Magazin auf einer frischen
Installation nicht leer ist.
- Die ID→Slug-Zuordnung lag als private Konstante im `CmsArticleSeeder` und ist jetzt
`CmsArticle::LEGACY_ID_TO_SLUG` – eine Quelle für Seeder, Fallback und Weiterleitung.
- `MagazinList` und `MagazinDetail` lesen aus dem Model statt aus `trans('b2in.articles')`;
die Übersicht verlinkt über `route('magazin.detail')` statt über die numerische ID.
- Neue Route `/magazin/{slug}`. Die alten `/magazin/1` … `/magazin/5` leiten dauerhaft (301)
auf den Slug um; sie stehen per `whereNumber()` **vor** der Slug-Route, sonst würde diese
die Ziffern selbst schlucken. Numerische IDs ohne Zuordnung liefern 404.
- Die Detailseite fragte die Artikel zweimal ab (Artikel + verwandte Artikel) – jetzt gemerkt,
eine Abfrage pro Seitenaufruf.
**Tests:** `tests/Feature/MagazinPageTest.php` auf 13 Fälle erweitert – Slug-Auslieferung,
301 von jeder alten ID, 404 bei unbekanntem Slug, CMS schlägt Lang-Datei, unveröffentlichte
Artikel sind unsichtbar, Fallback bei leerer Tabelle. `LiveSiteReviewTest` prüft für jede alte
ID die Weiterleitung **und** dass das Ziel lädt.
⚠️ **Beim Deployment:** `/magazin/1` … `/magazin/5` sind indexiert. Die 301 fängt sie ab, aber
nach dem Livegang bitte einmal in der Search Console gegenprüfen. Externe Links, die noch auf
die alten URLs zeigen, bleiben funktionsfähig.
**Nebenbefund, nicht angefasst:** `MagazinDetail::$relatedArticles` wird berechnet, aber von
keiner View gerendert – entweder die Sektion „verwandte Artikel" nachziehen oder die Property
entfernen. Deine Entscheidung.
### B3 · Ankündigungsleiste: alte Wegklicker sehen die neue Leiste nie ✅
`announcement-bar.blade.php:2` merkt sich das Wegklicken in
`localStorage['announcement_dismissed_<id>']`. Bleibt die `id` beim Umtexten gleich, bleibt die
Leiste für alle Wiederkehrer unsichtbar.
**Behoben:** Der Dismiss-Schlüssel wird jetzt aus Inhalt **und** `id` abgeleitet
(`AnnouncementBar::getDismissKeyProperty()`). Wird die Leiste im CMS neu belegt, sehen auch
Wiederkehrer die neue Meldung – ohne dass jemand daran denken muss, die `id` mitzuändern.
**Tests:** 2 neue Fälle in `tests/Feature/AnnouncementBarTest.php`.
### B4 · Der Footer ist vollständig hartkodiert ✅
`footer.blade.php` – Claim „Connecting Design and Property" steht in Zeile 15 im Markup,
`Footer.php` lädt überhaupt keinen Content. Es gibt keine `footer`-Sektion in `cms_section_map.php`.
**Wirkung:** Sowohl „Footer-Claim ersetzen" als auch „FAQ in den Footer" aus der Umsetzungsliste
erfordern einen kleinen Umbau; Marcel kann den Claim danach ohne CMS-Anbindung weiterhin nicht ändern.
**Behoben:** Neue CMS-Sektion `global.footer` (Claim, Byline, Linkliste) in `cms_section_map.php`
und in beiden Lang-Dateien. `Footer.php` lädt sie über `cms_theme_section('footer')`, die View
rendert Claim und Links daraus und fällt ohne CMS-Eintrag auf sinnvolle Defaults zurück.
Der Claim lautet jetzt „Premium-Einrichtung. Persönlich gemacht.", FAQ ist als erster Link drin.
**Tests:** `tests/Feature/FooterTest.php` (4 Fälle).
### B5 · Englische Footer-Labels auf der deutschen Seite ✅
„Privacy Policy", „Terms of Service", „Cookie Policy" waren hartkodiert englisch, während
„Impressum" korrekt über `__('ui.legal_notice')` lief.
**Behoben:** neue Keys `privacy_policy`, `terms_of_service`, `cookie_policy`, `faq` in
`resources/lang/{de,en}/ui.php`; die deutsche Seite zeigt jetzt Datenschutz / AGB /
Cookie-Richtlinie / Häufige Fragen / Impressum.
### B6 · 19 Seiten tragen die alte Dubai-Beschreibung ✅
`web-master.blade.php:11` und `:18` haben als Fallback fest verdrahtet:
„B2in – Connecting Design and Property. Exklusive Immobilien in Dubai & europäisches Einrichtungsnetzwerk."
19 Views unter `resources/views/web/` setzen **kein** `@section('meta_description')` und erben damit
diesen Text – darunter die Live-Seite `magazin-detail.blade.php`.
**Behoben:** Der Fallback in `web-master.blade.php` (Description **und** OG-Description) trägt
jetzt den Interior-Text aus `umsetzung-inhalte.md` §0. Zusätzlich `<link rel="canonical">` ergänzt,
das fehlte komplett.
**Offen als B10:** Der Alt-Begriff-Bestand im Seiteninhalt („Supply-Chain", „Local-for-Local",
„Ökosystem", „Investment", „Connecting Design") – **90 Treffer in 14 Dateien**, u. a. der
Hero-`card_text`. Das ist Content-Arbeit mit den freigegebenen Texten und gehört in AP1, nicht
in einen Bugfix.
### B7 · Content-Seeder sind nirgends registriert ✅
`DatabaseSeeder::run()` ruft nur `TestDisplaySeeder`. `CmsContentSeeder`, `CmsArticleSeeder`,
`CmsProjectSeeder`, `CmsLegalSeeder`, `NetzwerkPageCmsSeeder` müssen von Hand aufgerufen werden;
ein frisch aufgesetztes System hat keine CMS-Inhalte.
**Behoben:** `CmsContentSeeder`, `CmsLegalSeeder`, `NetzwerkPageCmsSeeder`, `CmsArticleSeeder`
und `CmsProjectSeeder` sind in `DatabaseSeeder::run()` registriert – nach dem B1-Fix gefahrlos,
weil bestehende Inhalte unangetastet bleiben.
### B8 · Klartext-Passwort im Seeder ✅
`database/seeders/DatabaseSeeder.php` enthielt ein echtes Passwort im Repo.
**Behoben:** Zugangsdaten kommen jetzt aus `config/seeding.php` bzw. `SEED_ADMIN_*` in der `.env`;
`.env.example` ist ergänzt.
⚠️ **Das alte Passwort steht weiterhin in der Git-Historie – bitte rotieren, falls es irgendwo
sonst benutzt wird.**
### B9 · Medien-Picker greift bei leeren Logo-Feldern nicht ✅
`content-index.blade.php` → `_cmsFieldLooksLikeImage()` erkennt Bildfelder an einer festen
Feldnamen-Liste **oder** an der Dateiendung **im Wert**. `logo` steht nicht in der Liste, und ein
noch leeres Feld hat keinen Wert → wird als reines Textfeld gerendert.
**Wirkung:** Marcel kann bei einer neu angelegten Marke bzw. bei den Sponsoring-Logos das Bild
nicht per Picker setzen, sondern müsste den Dateinamen abtippen. Betrifft AP2 und AP6 direkt.
**Behoben:** `logo` in die Feldnamen-Liste und `_logo$` in die Suffix-Regex aufgenommen. Zusätzlich
prüft `_cmsFieldLooksLikeImage()` den Feldnamen jetzt **vor** dem Wert – ein noch leeres Bildfeld
wird dadurch als Bild erkannt und bekommt den Picker. (`logo_width` bleibt korrekt ein Textfeld.)
### B11 · `/magazin/{unbekannte-id}` liefert 200 statt 404 ✅
`MagazinDetail::loadArticle()` fiel bei unbekannten IDs still auf Artikel 1 zurück
(`$articles[$this->articleId] ?? $articles[1]`). `/magazin/999` lieferte damit Status 200 mit dem
Inhalt von Artikel 1 – beliebig viele URLs mit identischem Inhalt, schlecht für die Indexierung,
und bei fehlendem Artikel 1 ein Fehler statt einer 404.
**Behoben:** `abort_unless(isset($articles[$this->articleId]), 404)`.
**Tests:** 2 neue Fälle in `tests/Feature/MagazinPageTest.php`.
---
## 3. Arbeitspakete
Gliederung folgt der Umsetzungsliste. Reihenfolge nach dem Vorschlag aus `umsetzung-inhalte.md`.
### AP0 · Vorbereitung (im Rahmen von AP1)
Branch `feature/b2in-interior`, DB-Backup, Backup der Startseite (Muster
`dev/2026-02-27/backup-before-relaunch/`), `php artisan test --compact` als Referenzstand.
### AP1 · Website-Umstrukturierung & Umtextung — ✅ umgesetzt
**Global**
- Meta-Title und Description auf Interior umgestellt; fünf Live-Seiten hatten gar keine eigene
Description und erbten den Fallback – jetzt gesetzt (`magazin-detail`, `impressum`, `privacy`,
`terms`, `cookie-policy`). OG-Tags leiten sich automatisch ab.
- Navigation: `Home · Marken · Magazin · Immobilien · Über B2in`. „Netzwerk" ist raus (Seite bleibt
erreichbar), FAQ steht im Footer. „Referenzen" kommt mit AP5 dazu.
- Footer-Claim „Premium-Einrichtung. Persönlich gemacht." (siehe B4).
**Startseite** – neue Reihenfolge in `resources/views/web/home.blade.php`:
`header → hero → marcel-badge → weiche → kernmarken → über-marcel → sponsoring → immobilien-teaser → abschluss-cta → footer`
- Ankündigungsleiste neu belegt (Rotpunkt Küchen), inkl. neuer `id`.
- Hero: neue Headline, Subline, zwei Buttons, drei Checkmarks, Bild-Badge „Einrichtung, die bleibt."
- Marcel-Badge umgetextet.
- **Neu:** `Sections/AudienceSwitch` (Weiche Privat/Geschäft) – ersetzt „Zwei Welten. Ein Netzwerk."
- **Neu:** `Sections/CoreBrands` mit CABINET, Rotpunkt Küchen, Febrü (Anker `#marken`).
Febrü bewusst mit Home-Office zuerst. **Datenquelle ist vorerst das CMS-Sektionsfeld –
in AP2 wird sie durch das Model `CmsBrand` samt Detailseiten ersetzt.** Das Markup bleibt.
- **Neu:** `Sections/Sponsoring` (AP6) und `Sections/ImmobilienTeaser`.
- „Ein Ökosystem, drei Säulen" ist von der Startseite genommen, die Komponente bleibt als Archiv.
**Sponsoring-Logos:** je Verein eine Datei gewählt, auf max. 400 px Breite herunterskaliert
(326 KB → 130 KB) und nach `public/img/assets/sponsoring/` übernommen. Die drei Seitenverhältnisse
sind sehr unterschiedlich (500×262, 689×689, 2443×741), deshalb eine feste Box je Logo.
`tc-bad-waldliesborn-horn` liegt nur als JPG vor und hat damit keinen transparenten Hintergrund.
**Durchgang Alt-Begriffe:** auf allen Live-Seiten bereinigt – Startseite, `/about`, `/contact`,
`/immobilien` sind sauber; `/netzwerk` und `/faq` ebenfalls. Nicht angefasst: die
**Magazin-Artikel**, die inhaltlich über Supply-Chain und Investments handeln – das sind
redaktionelle Beiträge, keine Seitentexte.
**Tests:** `tests/Feature/HomePageStructureTest.php` (11 Fälle: Reihenfolge, Hero-Buttons und
-Checkmarks, Kernmarken samt Anker, Febrü-Gewichtung, Navigation, SEO, Sponsoring-Logos);
`AnnouncementBarTest` und `LiveSiteReviewTest` auf die neuen Inhalte gezogen.
---
#### ⚠️ Wichtig fürs Deployment: der Relaunch-Seeder
`cms_theme_section()` bevorzugt das CMS gegenüber der Lang-Datei. 14 der neu getexteten Sektionen
lagen bereits mit den **alten** Texten im CMS und hätten die neuen Inhalte überdeckt – der reguläre
`CmsContentSeeder` fasst bestehende Einträge seit B1 bewusst nicht mehr an.
Deshalb gibt es `InteriorRelaunchCmsSeeder`: er überschreibt **genau die 19 in AP1 geänderten
Sektionen** aus den Lang-Dateien und lässt alles andere in Ruhe.
```
php artisan db:seed --class=InteriorRelaunchCmsSeeder
```
Auf der lokalen Datenbank bereits gelaufen (5 angelegt, 14 aktualisiert). **Auf dem Live-System
vor dem Lauf ein DB-Backup ziehen** – er überschreibt in diesen Sektionen auch redaktionelle
Änderungen. Danach `npm run build:web`.
---
#### Nebenbefund B12 · `/service` und `/portfolio` lieferten 500 ✅
Beide Seiten sind öffentlich erreichbar, aber ihre Content-Sektionen (`hero_image`,
`content_section_left/right`, `portfolio`) existieren in keiner Lang-Datei mehr – vermutlich seit
dem Februar-Relaunch. Sie warfen seitdem einen 500er; da sie in keinem Test vorkamen, ist es
niemandem aufgefallen. **Verifiziert: älter als AP1, keine Regression.**
**Behoben** wie bei den anderen stillgelegten Seiten per 301 (`/service` → `/netzwerk`,
`/portfolio` → `/immobilien`); die Views bleiben als Archiv liegen. Beide Ziele sind eine
Zeile in `routes/web.php`, falls Du woandershin umleiten möchtest.
---
#### Was in AP1 offen bleibt
| Punkt | Grund |
|---|---|
| Hero-Bildmotiv, Kachelbilder, OG-Image | Zulieferung offen, Bestandsbild bleibt |
| Navigationspunkt „Referenzen" | kommt mit der Seite in AP5 |
| Teaser „Aktuelles aus unseren Showrooms" | kommt mit AP5, sonst zeigt er ins Leere |
---
#### Logo mit Zusatz „Interior" ✅ (Zulieferung eingetroffen)
Marcel hat die Dateien geliefert (`dev/2026-08-28/logos/`) – die typografische Eigenlösung
entfällt damit. Beide Varianten sind identisch aufgebaut und unterscheiden sich nur im Füllwert:
positiv `#314052`, negativ `#FFFFFF`; der blaue Akzent `#2C9FDA` bleibt in beiden. Trotz „CMYK"
im Dateinamen enthalten die SVGs RGB-Hexwerte, die Schrift liegt in Pfaden.
**Zuschnitt war nötig:** Die Lieferung lag auf einem A4-Artboard (595,3 × 419,5) – gemessen
**74 % leerer Weißraum**. Ungeschnitten hätte das Logo bei gleicher CSS-Höhe rund halb so groß
gerendert wie das bisherige. Der Inhaltsrahmen wurde durch Rendern ermittelt und das viewBox auf
`153 96 289.3 227.5` gezogen (98 % Füllgrad, gleichmäßige Randluft, Verhältnis 1,27:1).
Abgelegt als `public/img/logos/b2in-interior-logo-{positive,negative}.{svg,png}`; die alten
`b2in-logo-*`-Dateien bleiben als Archiv liegen.
**Angepasst:** `ThemeHelper` (b2in → neue Dateien) · Header `h-10` → `h-12` und Footer `h-14` →
`h-16`, weil das neue Lockup gestapelt ist und die Wortmarke bei gleicher Höhe kleiner wirkt ·
Footer- und Mail-`width`/`height`-Attribute auf das neue Verhältnis korrigiert (standen auf
2,14:1 bzw. 3,5:1 und passten schon vorher nicht) · Mail-Header nutzt das neue PNG.
**Display ebenfalls umgestellt** (Marcel möchte es laut Mail einheitlich):
- Beide Varianten liegen jetzt auch unter `public/_cabinet/assets/`.
- `DisplayModuleSettings`: Default-Logo und Claim getauscht. Der Claim stand dort noch auf
„Connecting Design & Property".
- **Variante nach Theme:** Das Standard-Theme ist `dark`, dort liegt das Logo auf einem dunklen
Scrim – bislang stand da die **dunkle** Variante (deshalb der `drop-shadow` im Player). Jetzt
dunkles Theme → negativ/weiß, helles Theme → positiv.
- Logo-Höhe im Player von `3.5vh` auf `4.6vh`, weil das neue Lockup gestapelt ist.
- `DisplayInteriorBrandingSeeder` vereinheitlicht die bestehenden Module. Vorher liefen dort
**vier verschiedene Logo-Quellen** nebeneinander (zwei Dateien aus der Medienbibliothek, ein
relativer und ein absoluter Asset-Pfad) und zwei Module trugen den Claim „Hello".
---
#### B13 · Live-Displays zeigten auf eine lokale Entwicklungsdomain ⛔→✅
Beim Vereinheitlichen aufgefallen: Medien-URLs lagen als **absolute URLs mit fest verdrahtetem
Host** in den Moduleinstellungen und Item-Inhalten – teils `b2in.eu`, teils **`b2in.test`**.
Der Player gibt absolute URLs unverändert zurück, ein auf `b2in.test` verweisendes Medium kann auf
dem Live-Display also nicht laden.
**Betroffen waren vier veröffentlichte Playlists:** „Display 4 – Innen", „Kundenstopper",
„Display 2" und „Test-Display" – unter anderem über das Modul „Cabinet-Influencerin", das auf
drei der vier Displays läuft.
**Behoben:** `DisplayMediaUrlNormalizerSeeder` schreibt Schema + Host auf einen reinen Pfad um.
Relative Pfade löst der Player über `resolveDisplayUrl()` gegen die Portal-Basis auf und
funktionieren damit auf Test- **und** Live-System. 23 Verweise umgeschrieben, 0 verblieben.
Ziel-Links (Footer-URL, QR-Ziel wie `cabinet-bielefeld.de`) bleiben bewusst absolut – der Filter
greift nur bei einem B2in-Host **und** einem Pfad unterhalb von `/storage/`. Nachgeprüft: die
fünf Ziel-Links stehen unverändert.
**Tests:** `DisplayInteriorBrandingTest` (6) und `DisplayMediaUrlNormalizerTest` (6).
⚠️ **Wichtig:** Ob die betroffenen Mediendateien auf dem Live-Server überhaupt vorhanden sind,
kann ich von hier nicht prüfen. Wurden sie nur lokal hochgeladen, fehlen sie dort weiterhin –
der relative Pfad ist dann trotzdem richtig, aber die Datei muss nachgezogen werden.
Nach dem Deployment bitte die vier Displays einmal sichten.
**Deployment-Reihenfolge:**
```
php artisan db:seed --class=InteriorRelaunchCmsSeeder
php artisan db:seed --class=DisplayInteriorBrandingSeeder
php artisan db:seed --class=DisplayMediaUrlNormalizerSeeder
npm run build:web
```
### AP2 · Kernmarken-Modul im CMS — ✅ umgesetzt
**Model + Migration:** `CmsBrand` / Tabelle `cms_brands`.
⚠️ Bewusst **nicht** `Brand` – das Model existiert bereits im Produkt-/Partner-Kontext
(`Partner::brand()`, `Product::brand()`) und bleibt unangetastet. Ein Test hält das fest.
Felder: `slug`, `name`, `tagline`, `teaser`, `description` (HTML), `logo`, `image`, `services`,
`gallery`, `website_url`, `showroom`, `is_published`, `order`.
Übersetzt werden `name`, `tagline`, `teaser`, `description`.
**Die Lehre aus B2 ist eingebaut:** Das Frontend liest direkt aus dem Model, nicht aus einer
Lang-Datei. `CmsBrand::forFrontend()` liefert die veröffentlichten Marken nach Slug indiziert;
solange keine Marke gepflegt ist, greift die Lang-Sektion `core_brands` als Fallback. Die
Fallback-Einträge tragen `has_detail_page = false`, damit die Kachel dann nicht auf eine
Detailseite verlinkt, die es noch nicht gibt.
**Admin:** `admin/flux-cms/brands` → `brands-index.blade.php` (Volt), aufgebaut nach dem
Magazin-Muster. Medien über den bestehenden `MediaPicker` (Logo, Stimmungsbild, Galerie), die
Einleitung über den Flux-Editor mit `CmsFluxEditorHtmlTransformer`. Slug wird auf Format und
Eindeutigkeit geprüft. Eigene Karte im CMS-Dashboard.
**Frontend:**
- `Sections/CoreBrands` rendert die Kacheln aus dem Model (Anker `#marken`).
- Neue Route `/marken/{slug}` mit `Sections/BrandDetail` und der Vorlage-Struktur aus
`umsetzung-inhalte.md` §5: Header (Logo + Claim + Stimmungsbild) → Einleitung → Leistungen →
Galerie → CTA, dazu eine Leiste mit den übrigen Marken.
- Eigener Title und eigene Description je Marke.
- Unveröffentlichte Marken liefern 404 und verschwinden von der Startseite.
**Seeder:** `CmsBrandSeeder` legt CABINET, Rotpunkt Küchen und Febrü mit den Texten aus
`umsetzung-inhalte.md` §5 sowie je vier Leistungen an – Febrü mit Home-Office zuerst.
Bestehende Marken werden **nicht** überschrieben, Marcel pflegt sie im Admin.
**Tests:** `Cms/CmsBrandTest` (8), `BrandPageTest` (11), `Admin/Cms/CmsBrandsIndexTest` (11) –
darunter die Gegenprobe zu B2: eine im CMS neu angelegte Marke erscheint ohne Codeänderung auf
der Startseite **und** bekommt ihre Detailseite.
#### Nachtrag: eigene Unterseite `/marken`
Auf Wunsch nach der Sichtung – Grundlage `dev/2026-08-28/unterseite_marke.md`. Die Startseite
bleibt wie sie ist, die Kernmarken-Sektion dort ist jetzt ein **Teaser**.
- **Neue Seite `/marken`** mit dem Aufbau aus der Spec: Seitenkopf mit Breadcrumb (Home → Marken)
→ Kernmarken → Partnernetzwerk → „Markenwelt wächst" → Abschluss-CTA. Eigener Title und eigene
Description.
- **Zwei Textebenen je Marke.** Dafür kamen zwei Felder dazu (`summary`, `highlights`):
| Feld | Wo |
|---|---|
| `teaser` | Kachel auf der Startseite (gekürzt) |
| `summary` | Text auf `/marken` (zwei Absätze) |
| `highlights` | die drei Merkmale auf `/marken` |
| `description` | Einleitung der Detailseite |
| `services` | Leistungen der Detailseite |
- **Startseite:** Kacheln verlinken direkt auf die Detailseite, darunter der Button
„Alle Marken ansehen" → `/marken`.
- **Navigation:** „Marken" zeigt auf `/marken` statt auf den Anker `/#marken`; der Hero-Button
ebenfalls. Der Anker `#marken` bleibt für Direktsprünge erhalten.
- **Slug korrigiert:** Die Spec nennt `/marken/rotpunkt`, geseedet war `rotpunkt-kuechen`.
Angepasst, solange nichts live ist – später wäre das ein Redirect gewesen.
- **`/netzwerk` stillgelegt.** Der Inhalt geht im Abschnitt „Mehr als Marken: unser Netzwerk" auf
`/marken` auf. `/netzwerk`, `/interior`, `/partner` und `/service` leiten jetzt **direkt** auf
`/marken` – vorher liefen `/interior` und `/partner` über `/netzwerk`, also eine Redirect-Kette.
Die View bleibt als Archiv liegen.
- **WhatsApp-Button:** erscheint nur, wenn im CMS unter `global.contact.whatsapp` eine Nummer
hinterlegt ist (`whatsapp_link()`). Solange die Nummer nicht bestätigt ist, zeigt der CTA nur
„Termin anfragen" – kein Button auf eine erfundene Nummer.
**Tests:** `MarkenPageTest` (9), `InteriorPageTest` neu geschnitten (4), dazu zwei Fälle in
`Admin/Cms/CmsBrandsIndexTest` – darunter die Gegenprobe, dass eine im Admin angelegte Marke
mitsamt Text und Merkmalen auf `/marken` erscheint.
> **Notiz für spätere Reihenfolge-Tests:** `wire:snapshot` serialisiert die Komponentendaten als
> JSON ins Attribut und escaped Umlaute zu `\uXXXX`. Ein `strpos`-Vergleich auf dem rohen HTML
> misst dann den Snapshot statt des sichtbaren Textes. In `MarkenPageTest` werden die Snapshots
> vorher entfernt.
**Offen:** Logos, Stimmungsbilder und Galerien fehlen als Zulieferung – Kacheln und Detailseiten
zeigen so lange einen Platzhalter. Der WhatsApp-Button im CTA-Block kommt mit AP3, sobald die
Nummer bestätigt ist.
---
#### Nachtrag 2: Galerie je Marke – realisierte Projekte und Inspirationen getrennt
Aus Marcels Mail: „Der Bereich Referenzen ist mir wichtig … Entscheidend ist für mich, dass dieser
Bereich langfristig mitwachsen und von mir selbst gepflegt werden kann. Für den Start können wir
ergänzend mit hochwertigen Visualisierungen und KI-unterstützten Darstellungen arbeiten. Dabei
sollte allerdings klar zwischen tatsächlich realisierten Referenzen und Entwürfen beziehungsweise
Inspirationen unterschieden werden."
Umgesetzt als Galerie **auf der Detailseite jeder Kernmarke**, mit zwei Rubriken.
- **Datenform.** Das bestehende JSON-Feld `cms_brands.gallery` bekommt je Eintrag
`image`, `category` (`project` | `inspiration`) und `caption`. **Keine Migration nötig.**
- **Sicherheitsnetz gegen die schädlichere Verwechslung:** `CmsBrand::normalizeGallery()` stuft
jeden Eintrag ohne oder mit unbekannter Kategorie als **Inspiration** ein – nie als Projekt.
Ein Entwurf, der versehentlich als Referenz durchgeht, wäre der teurere Fehler. Die
Normalisierung greift beim Laden, beim Speichern und beim Ausspielen.
- **Frontend** (`brand-detail.blade.php`): Abschnitt „Referenzen und Inspirationen" mit Umschalter,
sobald beide Rubriken gefüllt sind; ist nur eine gefüllt, steht sie mit eigener Überschrift da.
Jede Kachel trägt ein sichtbares Label („Realisiertes Projekt" / „Visualisierung"), über den
Inspirationen zusätzlich der Satz „Diese Darstellungen sind Entwürfe und Visualisierungen –
keine ausgeführten Projekte." Lightbox mit Pfeiltasten und Esc, Muster übernommen aus
`immobilien-show.blade.php`.
- **Admin** (`brands-index.blade.php`): zwei getrennte Buttons „+ Projekt" und „+ Inspiration", je
Eintrag ein Auswahlfeld und eine Bildunterschrift, dazu ein Hinweis, was in welche Rubrik gehört.
Die Kategorie wird beim Anlegen entschieden, nicht per stillem Vorgabewert.
- **Abgrenzung zu AP5:** AP5 bleibt die Rubrik-Seite „Referenzen" mit dem zweiten Display. Die
markenbezogene Galerie hier ist die pflegbare Grundlage dafür und ersetzt AP5 nicht.
**Tests:** 5 Fälle in `BrandPageTest` (keine Galerie → kein Abschnitt; beide Rubriken beschriftet;
kein Umschalter bei nur einer Rubrik; Eintrag ohne Kategorie erscheint als Visualisierung; Hinweis
sichtbar), 5 in `Cms/CmsBrandTest` (Normalisierung, Gruppierung), 4 in
`Admin/Cms/CmsBrandsIndexTest` (Anlegen je Rubrik, Speichern, unbekannte Kategorie, Löschen).
**Offen:** Bildmaterial. Marcel setzt Projektfotos und Visualisierungen selbst ein.
---
### AP3 · Terminanfrage-Widget + WhatsApp — ✅ umgesetzt
**Korrektur gegenüber Revision 1:** kein Inline-Abschnitt, sondern ein **schwebendes Element
unten rechts auf allen Seiten** → gehört ins Layout `web-master.blade.php`, nicht in `home.blade.php`.
- Aufsetzen auf `Acme\ContactForm\Livewire\ContactForm` per `customFields` – Honeypot,
Rate-Limit und `SpamDetector` kommen damit geschenkt.
- Felder: Name*, E-Mail*, Telefon, Anliegen/Marke (Auswahl), Wunschtermin, Nachricht.
- **Markenauswahl speist sich aus `CmsBrand`** → Abhängigkeit zu AP2 (Reihenfolge einhalten),
plus feste Einträge „Projektausstattung" und „Sonstiges".
- WhatsApp-Button daneben, Nummer als CMS-Wert (`global.contact.whatsapp`), Link `https://wa.me/<nummer>`.
- Cookie-Consent beachten (`packages/acme/CookieConsent`).
**Tests:** `AppointmentWidgetTest` (17 Fälle: Einbindung auf allen Seiten, Markenauswahl aus
`CmsBrand`, Validierung inkl. Wunschtermin in der Vergangenheit, Übergabe an den
`ContactFormService`, Honeypot, WhatsApp sichtbar/unsichtbar); `ContactFormTest` bleibt grün.
#### Umsetzungsnotizen
- **Abweichung vom Plan:** nicht auf `Acme\ContactForm\Livewire\ContactForm` per `customFields`
aufgesetzt. Dessen mitgelieferte View bringt ein eigenes, blaues Layout mit, das nicht zum
Auftritt passt. Stattdessen eine eigene Komponente wie `ContactForm` und `ImmobilienContactForm`
der Seite, die `ContactFormService` und `SpamDetector` aus dem Paket nutzt – Honeypot,
Rate-Limit und Spam-Erkennung sind damit trotzdem drin.
- **Platzierung:** `web-master.blade.php`, unten rechts, `z-40`. Der Cookie-Button des
Consent-Managers sitzt unten links (ebenfalls `z-40`), sein Dialog auf `z-50` – die Einwilligung
bleibt also immer erreichbar und wird nie verdeckt.
- **Kein eigener Consent nötig:** Das Widget setzt nichts im Browser, der WhatsApp-Link ist ein
ausgehender Link ohne Einbettung.
- Die `[x-cloak]`-Regel liegt jetzt einmal zentral im Layout – die Seiten brachten sie bisher nur
teilweise selbst mit, das Widget braucht sie überall.
- Felder: Name*, E-Mail*, Telefon, Anliegen (Auswahl), Wunschtermin, Nachricht, Einwilligung*.
Der Wunschtermin ist auf heute oder später begrenzt, im Browser über `min` und serverseitig
über `after_or_equal:today`.
- Betreff der Mail: `Terminanfrage B2in Interior: <Anliegen>`, Log-Kontext `appointment-widget` –
so lassen sich Widget-Anfragen von den übrigen Formularen unterscheiden. Die Seite, von der die
Anfrage kam, steht als `source` in der Mail.
- **WhatsApp** erscheint weiterhin nur, wenn unter `global.contact.whatsapp` eine Nummer im CMS
steht. Ohne Nummer zeigt das Widget allein den Termin-Button.
#### WhatsApp: was technisch geht und was nicht
Eine echte WhatsApp-Anbindung (Nachrichten empfangen, im CMS beantworten, Verlauf speichern)
setzt die WhatsApp Business Platform voraus: Meta-Verifizierung des Unternehmens, ein Anbieter
für den API-Zugang, laufende Kosten je Konversation. Das ist nicht Teil dieses Auftrags.
Umgesetzt ist die dafür übliche Alternative, ein **Klick-zum-Chat-Link** (`wa.me`):
- Der Button sitzt im Terminanfrage-Panel unter dem Absenden-Button.
- Auf dem Handy öffnet die installierte WhatsApp-App, am Rechner WhatsApp Web bzw. die
Desktop-App. Ist WhatsApp nirgends vorhanden, landet man auf der Hinweisseite von `wa.me`.
- Der Nachrichtentext ist **vorbereitet** und wird im Browser aus dem zusammengesetzt, was im
Formular schon steht (Anliegen, Wunschtermin, Name). Der Einstiegssatz ist im CMS pflegbar
(`global.appointment_widget.whatsapp_text`).
- **Abgeschickt wird die Nachricht vom Besucher selbst** – das steht auch so unter dem Button.
Was das bedeutet, bewusst festgehalten:
| | |
|---|---|
| Wo landet die Nachricht | in Marcels WhatsApp, nicht im CMS und nicht per Mail |
| Was sehen wir davon | nichts – kein Eintrag, keine Statistik, kein Spamschutz |
| Voraussetzung | eine Mobilnummer, die WhatsApp empfangen kann (sinnvoll: WhatsApp Business) |
| Datenschutz | ausgehender Link zu Meta. In die Datenschutzerklärung gehört ein Absatz dazu; ein Consent-Banner braucht es nicht, weil nichts eingebettet wird und nichts gesetzt wird |
Wer eine Anfrage im System dokumentiert haben will, nutzt weiterhin das Formular – der
WhatsApp-Weg ist die schnelle, aber unprotokollierte Abkürzung.
### Nachtrag 3 · Bestandsinhalte auf den Interior-Auftritt umgestellt ✅
Nach der Sichtung: Drei verlinkte Seiten erzählten noch die Dubai-/Plattform-Geschichte.
`/immobilien` bleibt bewusst unverändert – die Seite ist der „Außerdem"-Bereich.
**`/faq`** – die beiden Rubriken „Fokus: Immobilien-Investoren (B2C)" und „Fokus: B2B-Partner"
sind ersetzt durch **„Für Ihr Zuhause"** und **„Für Projekte und Objekte"** (je vier Fragen:
Einstieg mit einem einzelnen Möbelstück, Kosten der Maßanfertigung, Montage, Showroom –
bzw. Projektgröße, Termintreue, Architekten, Netzwerk). Von den allgemeinen Fragen waren zwei
noch Dubai-lastig, die sind jetzt Beratungsablauf und Verlässlichkeit. Die englische Fassung war
noch komplett auf dem alten Stand und ist mitgezogen.
**`/about`** – das Mission-Zitat sprach von „digitalen Werkzeugen" und „Brücken zwischen Märkten".
Neu: wenige ausgewählte Hersteller, Fachbetriebe aus der Region, ein Ansprechpartner. Die sechs
Werte waren Plattform-Vokabular (Innovation, Konnektivität, Design-Exzellenz) und heißen jetzt
Beratung statt Verkauf, Maßarbeit, Ausgewählte Marken, Regional verwurzelt, Ein Ansprechpartner,
Verlässlichkeit. In der Zeitleiste sind „Die Idee", „Das Fundament" und „Die Vision" umgeschrieben.
**`/contact`** – die Betreffliste beginnt jetzt mit Beratungstermin, Maßanfertigung und
Küchenplanung; „Internationale Immobilien" rutscht nach hinten, bleibt aber erhalten.
**`/magazin`** – zwei neue Beiträge, die **an erster Stelle** stehen:
`massanfertigung-warum-der-millimeter-zaehlt` und `kueche-planen-vom-wunsch-zum-grundriss`.
Die vier Dubai-Beiträge bleiben, sie gehören zum Immobilienbereich.
> ⚠️ Die beiden Beiträge sind **Platzhalter**. Bewusst ohne Referenzen, Zahlen oder Namen
> geschrieben, damit nichts behauptet wird, was nicht belegt ist – vor dem Livegang von Marcel
> gegenlesen lassen. Als Bilder dienen vorhandene Motive aus dem Altbestand.
**Zwei technische Punkte dabei:**
- Neue Lang-Artikel bringen ihren Slug jetzt selbst mit (`'slug' => …`, aufgelöst über
`CmsArticle::slugForLangArticle()`). `LEGACY_ID_TO_SLUG` bleibt damit das, was der Name sagt:
die Karte für die alten `/magazin/{id}`-URLs. `/magazin/6` ist korrekt ein 404, weil es diese
URL nie gab.
- `CmsArticleSeeder` sortiert nicht mehr per `ksort`. Die Reihenfolge in der Lang-Datei bestimmt
jetzt die Reihenfolge im Magazin – im CMS über `order`, im Lang-Fallback über die
Array-Reihenfolge. Vorher konnten beide Wege auseinanderlaufen.
**Tests:** vier Fälle in `MagazinPageTest` (Reihenfolge, beide Slugs, kein erfundener Legacy-Link,
Slug-Auflösung), drei in `LiveSiteReviewTest` (FAQ ohne Dubai mit neuen Rubriken, About mit den
neuen Werten, Kontakt mit den neuen Betreffen).
**Nachkontrolle:** Gegen die Live-Seiten geprüft, nicht nur gegen die Sprachdatei – dabei fiel in
der FAQ eine dritte Frage auf, die noch vom „Local for Local"-Netzwerk sprach. Ebenfalls korrigiert.
**Noch offen auf diesen Seiten:** eigene Beitragsbilder für die zwei neuen Artikel; die vier
Dubai-Beiträge bleiben inhaltlich unverändert stehen.
---
### Nachtrag 4 · Markenseiten Rotpunkt und Febrü aus der Zulieferung ✅
**Grundlage:** zunächst `Rotpunkt_Kuechen_Webseitentext_B2in_FINAL.pdf` und
`Februe_Webseitentext_B2in_FINAL.pdf` (Stand 31.08.2026, im selben Ordner). Beide enthalten neben
dem Seitentext einen Abschnitt „UMSETZUNGSHINWEIS FÜR KEVIN – WEBSTRUKTUR UND CTA“.
> Die als Aufgabenliste angekündigte Datei `umsetzung-03-09-2026.md` war zu diesem Zeitpunkt leer
> (0 Byte). Sie wurde nachgereicht – **die Auswertung steht in Nachtrag 5**, der auch die
> korrigierten Textfassungen (`*_FINAL.md`) einarbeitet. Für den Textstand gilt Nachtrag 5.
**Was die Zulieferung verlangt:**
1. Der normale Seitentext bleibt direkt sichtbar.
2. Die mit „AUFKLAPPBARER DETAILBEREICH“ gekennzeichneten Vertiefungen werden aufklappbar.
3. **Kein zweiter CTA-Button.** Stattdessen bekommt das vorhandene schwebende
Terminanfrage-Widget auf der jeweiligen Markenseite eine kontextbezogene Beschriftung.
4. Das Widget bleibt beim Scrollen sichtbar und darf auf dem Smartphone nichts verdecken.
5. Die internen Quellenhinweise am Ende der PDFs werden **nicht** veröffentlicht.
#### Umsetzung
**Datenstruktur.** `description` (Einleitung) und `services` (Kacheln) reichten für diese Texte
nicht aus. Neu in `cms_brands`:
| Feld | Inhalt |
|---|---|
| `sections` | Abschnitte der Seite: Titel, Absätze, optional Schlusszeile, Zitat und Vertiefung |
| `widget_label` | Beschriftung des Terminanfrage-Widgets auf dieser Markenseite |
| `cta_title` / `cta_text` | Abschlussblock der Seite |
`sections` ist – wie `services`, `highlights` und `gallery` – bewusst **nicht** übersetzbar; die
Texte liegen nur auf Deutsch vor. Die drei Textfelder folgen den übrigen Textfeldern und sind
übersetzbar. `CmsBrand::normalizeSections()` bringt jeden Eintrag in eine feste Form und nimmt
Absätze wahlweise als Liste oder als Textblock entgegen (Leerzeile trennt) – dadurch lässt sich im
Admin einfach in ein Textfeld schreiben.
**Darstellung.** Die Abschnitte stehen zwischen Einleitung und Leistungen. Vertiefungen sind
aufklappbar, mit `aria-expanded` und `aria-controls`. Bewusst `x-transition` statt `x-collapse`:
Das Collapse-Plugin ist in diesem Projekt nicht registriert, die Bewegung entspricht jetzt dem
FAQ-Akkordeon. Zitate stehen abgesetzt mit Nennung des Sprechers, Schlusszeilen hervorgehoben.
**Widget-Beschriftung.** Das Widget steckt im Layout, nicht in der Seite – es liest den Kontext
deshalb aus der Route: Auf `marken.show` greift `widget_label` der Marke, sonst die allgemeine
Beschriftung. Ein Test prüft gegen, dass dabei **kein zweiter Button** entsteht.
**Platz auf dem Smartphone.** Der Footer bekommt unten zusätzlichen Abstand (`pb-28 sm:pb-16`),
und der Button bricht lange Beschriftungen um, statt über den Bildschirmrand zu laufen – „Persönliche
Arbeitsplatzberatung vereinbaren“ ist deutlich länger als das vorherige „Termin anfragen“.
**Pflegbarkeit.** Die Abschnitte sind vollständig im Admin pflegbar: hinzufügen, löschen, per Pfeil
sortieren, je Abschnitt Überschrift, Text, Schlusszeile, Zitat und Vertiefung. Dazu die drei neuen
Felder für Abschluss und Widget-Beschriftung.
**Einspielen.** `BrandPageTextSeeder` schreibt die Texte. Er fasst **nur** `tagline`,
`description`, `sections`, `widget_label`, `cta_title` und `cta_text` an – Bilder, Galerie,
Leistungen und Reihenfolge bleiben unangetastet. Ein Test prüft genau das.
**Nebenbei korrigiert:** Febrüs Claim hieß „Arbeiten, wo man gerne ist.“, die Zulieferung schreibt
„Arbeiten, wo man gerne **bleibt**.“
**Tests:** `BrandPageSectionsTest` (17 Fälle: Abschnitte beider Marken, Anzahl der aufklappbaren
Bereiche, Zitat, Schlusszeilen, keine Quellenhinweise im Ausgabe-HTML, markenspezifischer
Abschlussblock mit Rückfall auf den allgemeinen, Widget-Beschriftung je Marke, kein zweiter Button,
Normalisierung, Redaktionsschutz des Seeders) und vier neue Fälle in `Admin/Cms/CmsBrandsIndexTest`.
> ⚠️ **Vor der Veröffentlichung freigeben lassen.** Die PDFs halten fest: Aussagen nur im belegten
> Umfang veröffentlichen, Herstellerfreigabe einholen, und die Rotpunkt-Aussage zur jährlich
> erhaltenen **Waldfläche von ungefähr 50 Fußballfeldern** nur mit ausdrücklicher Bestätigung
> verwenden. Sie steht als eigener Absatz in der Vertiefung „Zahlen, Zertifizierungen und
> Raumklima“ und lässt sich dort einzeln entfernen. Als Punkt in Abschnitt 8 aufgenommen.
**Offen:** CABINET hat noch keinen ausführlichen Seitentext – die Seite läuft weiter über
Einleitung und Leistungskacheln. Bilder fehlen weiterhin für alle drei Marken.
---
### AP4 · Display-Spiegelung auf der Website — ✅ umgesetzt
**Korrektur gegenüber Revision 1:** Revision 1 empfahl, die Slides nativ zu rendern. Bei 3 Std.
ist das nicht zu halten, und die Umsetzungsliste schreibt „nutzt die vorhandene Display-/Player-API
als Datenquelle". → **Einbettung des vorhandenen Players im 9:16-Container.**
- Sektion `Sections/DisplayFeed` mit `aspect-ratio: 9/16`, darin der Player.
- **Vorher zu klären:** Die `/preview/…`-Routen sind token- bzw. modulbasiert und für den internen
Gebrauch gedacht. Für die öffentliche Domain braucht es eine schmale, read-only Route
(z. B. `/embed/display/{display}`), die keine Preview-Tokens nach außen gibt.
- Keine doppelte Pflege: Marcel pflegt weiterhin nur im Display-CMS.
#### Umsetzung
- **Route `GET /embed/display/{display}`** (`DisplayEmbedController`), bewusst getrennt von den
Preview-Routen: Die arbeiten mit `preview_token` und zeigen den **Entwurf**. Nach außen darf
weder ein Token noch ein unveröffentlichter Stand gelangen. Die Embed-Route kennt nur die
numerische ID, liefert 404 bei inaktivem Display, bei reinem Entwurf und bei leerer Playlist,
und setzt `X-Robots-Tag: noindex`.
- **Kein Proxy, keine CORS-Frage:** Der Player liest die ID aus dem Pfad (`/display/{id}` steckt in
`/embed/display/{id}`) und holt seine Konfiguration über die bereits öffentliche Route
`/api/display/{display}/config` – dieselbe Quelle wie das physische Display. Da die API-Routen
für alle Domains registriert sind, läuft der Abruf gleichursprünglich über `b2in.test`.
Möglich wurde das durch **B13**: Vorher standen absolute `b2in.test`-URLs in den Playlists.
- **Sektion `Sections/DisplayFeed`** im 9:16-Rahmen. Welches Display gezeigt wird, steht im CMS
(`referenzen.display_feed.display_id`); ohne Eintrag greift das erste aktive Display mit
veröffentlichter Playlist. Gibt es keins, rendert die Sektion **nichts** – lieber keine Sektion
als ein Rahmen mit ewigem Ladebalken. Auch die Texte bleiben dann leer, sonst stünden sie
im Livewire-Snapshot der Seite.
- Eingebunden auf der Startseite (an der dafür vorgesehenen Stelle) und auf `/referenzen`.
**Tests:** `DisplayWebFeedTest` (10 Fälle: Embed liefert die Datei, kein Token im Ausgabe-Inhalt,
404 bei inaktiv/Entwurf/leer/unbekannt, Einbindung auf der Startseite, CMS-Auswahl und Fallback).
### AP5 · Rubrik-Seite „Referenzen" — ✅ umgesetzt (Pflege seit Nachtrag 13 als eigenes Modul)
**Korrektur gegenüber Revision 1:** Revision 1 schlug einen neuen `DisplayVersionType`-Case vor.
Die Umsetzungsliste sagt „zweites, separates **Display** im Display-CMS" – das ist ein weiterer
`Display`-Datensatz und braucht **keine Enum-Änderung**. Damit entfallen die Folgeänderungen an
`DisplayPlaylistConfigBuilder::moduleData()`, `DisplayModuleSettings` und den
`version-editor-*`-Partials. Deutlich risikoärmer und billiger.
- Zweites `Display` anlegen, Route `/referenzen`, View + Sektion, Aufnahme in die Navigation.
- Startseiten-Teaser „Aktuelles aus unseren Showrooms" mit Button „Alle Referenzen ansehen →".
- 2–3 Platzhalter-Einträge.
- YouTube-Verlinkungen mit Consent-Gate.
- Inhaltliche Regel dokumentieren: unter „Referenzen" nur realisierte Projekte; Visualisierungen
und Entwürfe gehören in die Kernmarken-Detailseiten.
- **Bereits vorhanden:** die markenbezogene Galerie mit den Rubriken „Realisierte Projekte" und
„Inspirationen und Entwürfe" (siehe Nachtrag 2 zu AP2). AP5 baut darauf auf, statt eine zweite
Pflegestelle für dieselben Bilder zu schaffen.
#### Umsetzung
- Seite `/referenzen`: Seitenkopf → realisierte Projekte → Showroom-Display → Abschluss-CTA.
Navigationspunkt zwischen „Marken" und „Magazin".
- **Kein zweites Display nötig, keine zweite Pflegestelle:** Die Projekte kommen aus den
Markengalerien (AP2) und werden über alle Marken hinweg gesammelt. Es erscheinen ausschließlich
Einträge der Rubrik **„Realisiertes Projekt"** – Visualisierungen bleiben draußen, damit
„Referenzen" hält, was das Wort verspricht. Jede Kachel verlinkt zurück auf ihre Markenseite.
- **Ehrlicher Leerzustand:** Solange keine Projekte gepflegt sind, steht dort ein Satz dazu und ein
Terminlink – keine erfundene Referenz und keine leere Fläche.
- Der Seitenkopf ist jetzt eine wiederverwendbare Sektion (`PageHero` mit `section` und
`breadcrumb`), vorher war er auf `/marken` festgelegt.
**Tests:** `ReferenzenPageTest` (6 Fälle: Seite lädt mit eigenem Titel, Navigationspunkt,
Projekte aus allen Marken, Visualisierungen bleiben draußen, Rückverlinkung, Leerzustand).
### AP6 · Engagement / Sponsoring — ✅ umgesetzt (im Rahmen von AP1)
Schlanke Sektion „Regional verbunden" nach der Marcel-Sektion: ein Einleitungssatz plus drei
Logos in ruhiger Reihe (Graustufen/gedeckt).
Logos liegen bereits unter `dev/2026-08-28/sponsoren-logos/` – **7 Dateien für 3 Vereine**
(Arminia, TCBWH, TCH in mehreren Varianten und Formaten). Vor der Umsetzung je Verein **eine**
Datei auswählen, nach `public/img/assets/` bzw. in die Medienbibliothek übernehmen und
vereinheitlichen (gleiche Höhe, transparenter Hintergrund – die `.jpg`-Varianten haben keinen).
Setzt **B9** voraus, damit die Logos per Picker pflegbar sind.
---
---
### Nachtrag 5 · Abstimmungsrunde 03.09.2026 ✅
**Grundlage:** `dev/2026-08-28/umsetzung-03-09-2026.md`. Die Datei war beim ersten Zugriff leer
und wurde nachgereicht. Dazu kamen die korrigierten Textfassungen als Markdown:
`Rotpunkt_Webseitentext_FINAL.md` und `Februe_Webseitentext_FINAL.md`. **Sie lösen die PDFs vom
31.08. ab** – die PDFs bleiben nur als Beleg im Ordner liegen.
Vier Punkte, alle umgesetzt:
| # | Punkt | Stand |
|---|---|---|
| 1 | Markenseiten umfangreicher: Rotpunkt und Febrü korrigiert, CABINET neu | ✅ |
| 2 | „BEISPIEL"-Kennzeichnung bei Visualisierungen entfernen | ✅ |
| 3 | Hinweis zu Visualisierungen eleganter formulieren | ✅ |
| 4 | Abschluss-CTA hochwertiger | ✅ |
#### 1a · Korrigierte Fassungen Rotpunkt und Febrü
Die beiden Markdown-Dateien führen ihre Änderungen gegenüber dem 31.08. selbst auf. Übernommen:
- **Rotpunkt:** „Nachhaltigkeit mit konkreter Substanz" und „Greenline hält dem Vergleich stand"
sind **zusammengeführt**; der doppelte Satz „Nachhaltigkeit braucht keine großen Versprechen…"
steht nur noch einmal. Rotpunkt hat damit **8 statt 9 Abschnitte**, weiterhin 4 Vertiefungen.
- **Febrü:** Der erklärende Satz zum Blauen Engel („Die Aussage bezieht sich ausdrücklich auf die
offiziell gelisteten Produktgruppen…") ist aus dem Kundentext **entfernt** – der Produktbezug
steckt jetzt im Satz selbst („Ausgewählte Febrü-Tischsysteme und Stauraummöbel"). Ein Test
hält diese Formulierung fest, damit sie beim Redigieren nicht pauschal wird.
- Die Schlusszeile „Nachhaltig ist nicht das Möbelstück mit dem längsten Werbetext…" steht laut
korrigierter Fassung **innerhalb** der Vertiefung, nicht als Abschnitts-Claim.
Ein Prüfskript hat jeden veröffentlichten Satz des Seeders gegen die Markdown-Datei gehalten:
0 Abweichungen, alle Überschriften der Vorlage vorhanden. Einzige bewusste Abweichung sind
typografische Anführungszeichen („…“ statt „…").
#### 1b · CABINET nach demselben Muster
Für CABINET **lag keine Zulieferung vor**. Die Seite ist nach dem Vorbild der beiden abgestimmten
Texte von uns verfasst: Einleitung mit Claim, 8 Abschnitte, 3 davon mit Vertiefung, eigener
Abschlussblock „Ihr Raum. Persönlich geplant." und die Widget-Beschriftung „Persönliche
Schrankplanung vereinbaren".
> ⚠️ **Bewusste Zurückhaltung:** Der Text beschreibt ausschließlich die eigene Arbeitsweise –
> Aufmaß, Planung, Innenausstattung, Fronten, Montage, Schnittstelle zur Tischlerei. Er enthält
> **keine Zahlen, Zertifizierungen oder Herstelleraussagen**, weil es dafür keine Quelle gibt.
> Die Rotpunkt- und Febrü-Seiten stützen solche Aussagen auf die Quellenhinweise der Zulieferung;
> für CABINET wäre das erfunden. **Der Text braucht Marcels Freigabe** (Punkt in Abschnitt 8).
**Nebenbei korrigiert:** Febrüs Claim stand in `CmsBrandSeeder` und `b2in.php` weiter als
„Arbeiten, wo man gerne ist." – die Zulieferung schreibt „…gerne **bleibt**."
#### 2 · „BEISPIEL"-Kennzeichnung entfernt
Die Demobilder trugen den Vermerk „BEISPIEL – " in der Bildunterschrift, damit ein Stockmotiv
nicht versehentlich als echte Referenz durchgeht. Der Vermerk ist entfallen.
Zwei Stellen waren nötig, weil die Bilder auf Vorschau und Live **bereits in der Datenbank
stehen**:
- `DemoBrandGallerySeeder` – Bildunterschriften ohne Vermerk (für frische Installationen).
- `BrandGalleryCaptionNormalizerSeeder` **(neu)** – entfernt den Vermerk in vorhandenen Daten.
Fasst **ausschließlich den Text** an; Bild, Kategorie und Reihenfolge bleiben. Mehrfaches
Ausführen ist unbedenklich.
> ⚠️ Die Unterscheidung hängt damit allein an der **Kategorie** (Badge „Realisiertes Projekt" /
> „Visualisierung"). Die Demobilder der Kategorie `project` sind Stockmotive und KI-Renderings –
> sie behaupten jetzt ohne den Vermerk, echte Referenzen zu sein. **Vor dem Livegang ersetzen
> oder auf „Inspiration" umstellen** (Punkt in Abschnitt 8).
#### 3 · Hinweis zu Visualisierungen
| Wo | Vorher | Jetzt |
|---|---|---|
| Markengalerie, Rubrik Inspirationen | „Diese Darstellungen sind Entwürfe und Visualisierungen – keine ausgeführten Projekte." | „Entwürfe und Visualisierungen. Sie zeigen gestalterische Möglichkeiten, noch bevor das erste Möbel steht." |
| Markengalerie, Einleitung | „…und Visualisierungen, die zeigen, was möglich ist." | „…und Visualisierungen, die zeigen, was in einem Raum möglich ist." |
| `/referenzen`, Seitenkopf | „…keine Entwürfe und keine Visualisierungen." | „Hier zeigen wir ausschließlich Arbeiten, die wir tatsächlich ausgeführt haben." |
| `/referenzen`, Projektliste | „…finden Sie bewusst getrennt davon auf den Markenseiten." | „…zeigen wir bewusst getrennt davon – auf den Markenseiten." |
Die Trennung bleibt inhaltlich unverändert, sie wird nur nicht mehr über eine Verneinung
transportiert. Der Badge am Bild („Visualisierung") trägt die Unterscheidung ohnehin.
#### 4 · Abschluss-CTA
Der Block war eine einfarbige Fläche mit Überschrift, Text, Button und einem unterstrichenen
Textlink. Neu:
- Eine schmale Linie über der Überschrift setzt den Abschluss ab.
- Hauptbutton weiß auf Markenfarbe, mit Pfeil, der beim Hovern mitgeht.
- Die Herstellerseite ist jetzt ein gleichwertiger Outline-Button statt eines Textlinks.
- Der Showroom steht als kleine Zeile mit Pin-Symbol darunter.
**Nachgebessert (dritte Rückmeldung):** Nach dem Build stand der Abschluss hellgrau mit weißer
Schrift da. Ursache: `.variante-glass-flow section` in `shared-styles.css` färbt **jede** Sektion
in diesem Wrapper hell ein und ist mit `(0,1,1)` spezifischer als `.cta-band` mit `(0,1,0)`. Für
`.bg-secondary` und `.bg-accent` gibt es dort längst Ausnahmen – die neue Klasse brauchte
dieselbe. `/marken/{slug}` hängt in genau diesem Wrapper, deshalb fiel es erst dort auf. Der
Verlauf steht jetzt in `--cta-band-gradient` und wird von beiden Regeln benutzt, damit er nicht
doppelt gepflegt werden muss. Ein Test hält die Ausnahme fest.
**Nachgebessert (zweite Rückmeldung):** Der erste Versuch legte zwei weiche Kreise (`blur-3xl`)
über das Hellblau – die wirkten als sichtbare Flecken, besonders der dunkle rechts unten. Ersetzt
durch einen durchgehenden Verlauf über die volle Breite: `.cta-band` in `shared-styles.css`, von
Anthrazit (`--primary-dark`) diagonal in das Dynamic Blue (`--secondary-dark`). Die Tiefe kommt
aus dem Verlauf selbst, die Kontur aus zwei Haarlinien – eine oben über die Breite, eine als
Auftakt über der Überschrift, beide zu den Seiten auslaufend. Buttons und Fußzeile dabei
zurückhaltender: dünnerer Rahmen, weicherer Schatten.
> Der Verlauf liegt bewusst als CSS-Klasse vor und nicht als Utility-Kette im Blade: er arbeitet
> mit den Theme-Variablen und bleibt so über alle Themes hinweg stimmig.
Die Textinhalte kommen unverändert aus dem CMS (`cta_title` / `cta_text`) – Punkt 4 der Liste war
inhaltlich bereits durch die neuen Texte erledigt („Ihre Küche. Persönlich geplant.").
#### 5 · Akkordeons weicher (Nachreichung aus derselben Runde)
Rückmeldung: Die Klappelemente springen zu schnell auf und zu, der Hover-Effekt ist zu stark.
Ursache war `x-show` mit einer Opacity-Bewegung: Das Element nimmt sofort seine volle Höhe ein
und blendet nur den Inhalt ein – es sieht aus wie ein Sprung. Ersetzt durch eine Animation von
`grid-template-rows: 0fr → 1fr` über 500 ms mit `cubic-bezier(0.33, 1, 0.68, 1)`, die die
tatsächliche Höhe bewegt. Das Alpine-Collapse-Plugin ist in diesem Projekt nicht registriert;
diese Lösung kommt ohne neue Abhängigkeit aus.
- Der zugeklappte Zustand steht **auch statisch im `class`-Attribut**, damit vor dem Start von
Alpine nichts aufgeklappt aufblitzt. Die Objekt-Schreibweise von `x-bind:class` entfernt ihn
beim Öffnen wieder.
- `aria-hidden` hängt am Zustand, weil der Inhalt jetzt dauerhaft im DOM steht.
- **Hover:** Die Flächenaufhellung (`hover:bg-muted/30`) ist raus. Geblieben ist ein feiner
Farbwechsel der Überschrift auf die Sekundärfarbe. Im FAQ entfällt zusätzlich der
Hintergrundwechsel am Icon-Kreis.
- Der Pfeil dreht in derselben Dauer und Kurve wie das Aufklappen, damit beides zusammenläuft.
Betrifft **beide** Akkordeons – die Vertiefungen auf den Markenseiten und das FAQ; sie hatten
dasselbe Verhalten.
#### 6 · Markenlogos (Zulieferung 03.09.2026)
Grundlage: `dev/2026-08-28/markenlogos/` – CABINET und Rotpunkt als SVG, Febrü als PNG. Sie
liegen jetzt unter `public/img/assets/marken/` und hängen als `logo` an der jeweiligen Marke.
| Wo | Vorher | Jetzt |
|---|---|---|
| Kopf der Markenseite | Schriftzug als `<h1>` | Logo **anstelle** des Schriftzugs, im `<h1>` |
| Kachel auf der Startseite | ohne Logo | Logo über Name und Claim |
| Block auf `/marken` | ohne Logo | Logo über Name und Claim |
| „Weitere Marken" am Seitenfuß | ohne Logo | Logo über Name und Claim |
Das Logo sitzt **im** `<h1>`, nicht daneben: So bleibt die Seitenüberschrift erhalten, den Text
liefert das `alt`-Attribut. Fehlt ein Logo, erscheint weiterhin der Schriftzug – ein Test hält
beide Wege fest.
**Feste Box statt fester Höhe.** Die drei Logos haben sehr unterschiedliche Seitenverhältnisse
(Febrü 2,3:1, Rotpunkt 1,2:1). Mit einer reinen Höhenangabe hätte Febrü doppelt so breit gewirkt
wie Rotpunkt. Jedes Logo bekommt deshalb Höhe **und** Maximalbreite plus `object-contain` – die
Konvention aus der Sponsoring-Sektion.
**Alle drei Dateien auf ihren Inhalt beschnitten.** Jede Zulieferung brachte leeren Rand mit,
und zwar unterschiedlich viel – dadurch wirkten die Logos trotz gleicher Box unterschiedlich
groß:
| Logo | genutzte Höhe der Datei | nach dem Beschnitt |
|---|---|---|
| CABINET | **29 %** | 98 % |
| Rotpunkt | 70 % | 98 % |
| Febrü | 27 % | 100 % |
CABINET ist ein 4:1-Schriftzug, der in einer nahezu quadratischen Fläche saß – im Seitenkopf
blieben davon rund 28 px sichtbare Höhe. Nach dem Beschnitt sind es 69 px bei voller Boxbreite,
also gut zweieinhalbmal so groß. Rotpunkt wächst um gut das Anderthalbfache und steht jetzt auf
derselben Höhe wie Febrü.
Bei den beiden SVGs genügte dafür der `viewBox` (Ursprung und Maße auf die Tuschekante gesetzt,
plus 1 % Luft); die Pfade selbst sind unverändert. Der Febrü-PNG wurde tatsächlich beschnitten
und auf 760 px Breite gerechnet – 53 KB → 15 KB. Die Originale liegen unverändert in
`dev/2026-08-28/markenlogos/`.
**Zweite Feinabstimmung nach Sichtung:** CABINET wirkte noch zu groß, Rotpunkt zu klein –
gewünscht waren −15 % und +20 % bei unverändertem Febrü. Rotpunkt füllte die Box bereits ganz
aus, konnte also nur über eine größere Box wachsen. Deshalb:
- Alle vier Logo-Boxen **20 % höher** (Seitenkopf 96 → 115 px, Kacheln 44 → 53 px, `/marken`
56 → 67 px). Rotpunkt füllt sie weiter aus und wächst damit um genau 20 %.
- **Febrü** gleicht das über 20 % Luft oben und unten in der Datei aus und rendert unverändert.
- **CABINET** bekommt 18 % Luft rechts – die Box begrenzt den 4:1-Schriftzug über die *Breite*,
nicht über die Höhe. Die Luft sitzt nur rechts, damit das Logo links bündig zum Text darunter
bleibt.
Nachgerechnet an allen drei Stellen: CABINET 85 %, Rotpunkt 120 %, Febrü 100 %.
> Die optische Abstimmung steckt bewusst in den **Zeichenflächen der Dateien** und nicht im
> Blade. So gilt sie an allen vier Stellen gleichzeitig, und die Boxgrößen bleiben eine reine
> Layoutentscheidung. Der Merksatz für neue Logos steht im Kopf von `BrandLogoSeeder`: knapp
> beschnitten rendert ein Logo in Rotpunkt-Größe, mit Rand entsprechend kleiner.
**Nachziehen auf bestehenden Installationen:** `CmsBrandSeeder` überspringt vorhandene Marken,
trägt die Logos also nur bei einer frischen Installation ein. `BrandLogoSeeder` **(neu)** setzt
sie auf bereits angelegten Marken nach – nur das Feld `logo` und nur, wenn es leer ist. Ein
selbst hochgeladenes Logo bleibt damit stehen; mehrfaches Ausführen ist unbedenklich.
#### Tests
`BrandPageSectionsTest` und `BrandPageTest` erweitert:
- CABINET rendert seine Abschnitte und seinen eigenen Abschlussblock.
- Widget-Beschriftung jetzt für alle drei Marken (Datensatz-Test).
- Rotpunkt: der zusammengeführte Abschnitt existiert, der alte ist weg, der doppelte Satz steht
genau einmal auf der Seite.
- Febrü: der Blaue-Engel-Satz behält seinen Produktbezug.
- Der Rückfall auf den generischen Abschlussblock funktioniert weiterhin – **beide Sprachen
müssen dafür leer sein**, sonst greift die Übersetzungs-Fallbackkette und der englische Text
erscheint auf der deutschen Seite.
- Demogalerien tragen keinen „BEISPIEL"-Vermerk mehr, dafür durchgehend eine Kategorie.
- Der Normalizer entfernt den Vermerk, ohne Bild, Kategorie oder Reihenfolge zu verändern.
- Jede Vertiefung rendert zugeklappt und mit der Höhen-Animation; die alte Sprung-Bewegung ist
weg.
- Weder Markenseite noch FAQ verwenden noch den Flächen-Hover.
- Der Abschlussblock trägt das Verlaufsband; die weichen Kreise sind weg.
- Die Ausnahme für `.variante-glass-flow` ist vorhanden – ohne sie steht der Abschluss hellgrau da.
- Das Logo steht im `<h1>` der Markenseite; ohne Logo erscheint weiter der Schriftzug.
- Alle drei Logos erscheinen auf Startseite, `/marken` und den „Weitere Marken"-Kacheln.
- Die Dateien, auf die der Seeder zeigt, liegen tatsächlich unter `public/`.
- `BrandLogoSeeder` überschreibt kein selbst hochgeladenes Logo.
- Die SVGs unter `public/` sind die beschnittenen Fassungen, nicht die Zulieferungen (erkennbar
am `viewBox`-Ursprung, der nach dem Beschnitt nicht mehr `0 0` ist).
`php artisan test --compact`: **873 grün** (3217 Assertions), Pint sauber, `npm run build:b2in`
gebaut.
---
## 4. Nicht Bestandteil dieses Auftrags ⏸
Referenzbereich voll ausgebaut · 16:9-Display-Modul · Immobilien-CMS · Dubai-Map ·
Markenrichtlinie Fahrzeug/Print · weitere frei anlegbare Rubriken · eigene Videolösung.
**Zusätzlich neu zu bewerten:** B2 (Magazin-CMS ans Frontend anschließen, ≈2–3 Std.)
und B8 (Passwort aus dem Seeder).
---
## 5. Abhängigkeiten
```
AP1 (Struktur, Umtextung, B1/B3/B4/B5/B6/B7)
├─ AP2 (Kernmarken, B9) ──┐
├─ AP4 (Display-Spiegelung)│
├─ AP5 (Referenzen) │
├─ AP6 (Sponsoring) ◄──────┤ braucht B9
└─ AP3 (Termin-Widget) ◄───┘ Markenauswahl braucht CmsBrand
```
**B1 zuerst.** Solange der Seeder überschreibt, ist jede redaktionelle Arbeit flüchtig.
---
## 6. Restrisiken & offene Punkte
| Punkt | Stand | Umgang |
|---|---|---|
| **Rechtstexte** nennen weiter „Connecting Design and Property", Dubai/Lissabon, Supply-Chain | ⚠️ offen | Formulierung freigeben lassen, dann eintragen – nicht selbst getextet |
| **OG-Bild fehlt komplett** (`b2in-og-default.jpg` existiert nicht) | ⚠️ offen | Motiv wählen, ablegen |
| WhatsApp-Nummer (+49 151 51002992?) | offen | bestätigen lassen; ohne Nummer bleibt der Button unsichtbar |
| Logos, Stimmungsbilder, Galeriefotos der drei Marken | offen | Platzhalter stehen, Marcel trägt im CMS nach |
| Zwei neue Magazin-Beiträge sind Platzhalter | offen | gegenlesen lassen |
| Sponsoren-Kurztexte sind Entwürfe | offen | gegenlesen lassen |
| Vier Dubai-Beiträge bleiben im Magazin | bewusst | gehören zum Immobilienbereich, der bestehen bleibt |
| `ecosystem_stats` behauptet „1,7K+ Partner", „510+ Projekte", „98 %" | ruhend | Sektion wird auf keiner erreichbaren Seite gerendert – vor einer Wiederverwendung prüfen |
| B1/B2 Entscheidung | ✅ erledigt | Redaktionsschutz im Seeder, Magazin am Frontend |
| Logo mit Zusatz „Interior" | ✅ erledigt | Zulieferung eingetroffen und zugeschnitten |
| Sponsoring-Logos uneinheitlich | ✅ erledigt | Kacheln mit weißer Bühne, JPG ohne Transparenz fällt nicht mehr auf |
**Spielregel aus der Umsetzungsliste:** eine Korrekturschleife je Bereich ist enthalten.
Weitere Runden vorher melden und beziffern.
---
## 7. Definition of Done
- [x] Alle Live-Seiten laden (`LiveSiteReviewTest` grün)
- [x] Marcel kann Marke anlegen, Logo per Picker setzen, Galerie pflegen – und die Änderung
erscheint auf der Website (Gegenprobe zu B2, getestet in `CmsBrandsIndexTest`)
- [x] Erneuter `CmsContentSeeder`-Lauf löscht keine redaktionellen Inhalte (Gegenprobe zu B1)
- [x] `./vendor/bin/pint`
- [x] `npm run build:b2in` (Build-Verzeichnis `build/b2in`)
- [x] `php artisan test --compact` vollständig grün — 810 Tests, 3037 Assertions
- [ ] Keine Treffer mehr für „Supply-Chain", „Local for Local", „Ökosystem", „Connecting Design"
auf den Live-Seiten. **Stand: 8 statt ursprünglich 90.** Davon
5 in `/magazin` (die vier Dubai-Beiträge, bleiben bewusst),
1 in `/privacy` und 2 in `/terms` (Rechtstexte, siehe Abschnitt 0, Schritt 2).
Erst abhakbar, wenn die Rechtstexte freigegeben sind.
- [ ] OG-Bild vorhanden
- [ ] Rechtstexte freigegeben und eingetragen
---
## 7a. Vorschau-Instanz für die Abstimmung
Für die erste Abstimmung läuft der neue Stand als **eigenständige Vorschau** unter
`preview.b2in.eu` – eigenes Verzeichnis, eigene `.env`, eigene Datenbank. Live bleibt unangetastet.
**Ablauf Schritt für Schritt: `dev/2026-08-28/preview-deployment.md`.**
Dateien dafür: `dev/2026-08-28/deploy/b2in-interior-update.tar.gz` (geänderte Dateien plus
gebaute Assets) und `deploy/dateiliste.txt`.
Zwei Punkte, die dabei leicht übersehen werden:
- **Die Vorschau braucht `DOMAIN_B2IN=preview.b2in.eu` in ihrer `.env`.** Der
`ThemeServiceProvider` setzt `URL::forceRootUrl()` auf die konfigurierte Domain; ein unbekannter
Host fällt auf `APP_URL` zurück. Ohne diesen Eintrag rendert die Vorschau zwar, aber jeder
interne Link springt auf die Live-Seite.
- **Eigene Datenbank, keine geteilte.** `InteriorRelaunchCmsSeeder` überschreibt 23 CMS-Sektionen –
auf der Live-DB ausgeführt wäre der alte Stand weg.
Neu dafür im Code: `config('app.indexable')`. Außerhalb von `APP_ENV=production` liefert jede Seite
`noindex, nofollow`, damit eine Vorschau nicht mit dem Original um Suchergebnisse konkurriert.
Über `SITE_INDEXABLE` einzeln steuerbar.
---
## 8. Deployment-Checkliste (Livegang)
> **Diese Liste beim Livegang von oben nach unten abarbeiten.** Sie wird mit jedem Arbeitspaket
> ergänzt – zuletzt aktualisiert nach AP1 und der Display-Umstellung.
### 8.1 Vorher
- [ ] **DB-Backup ziehen.** Die Seeder unter 8.2 schreiben in `flux_cms_contents`,
`display_versions` und `display_version_items`.
- [ ] `.env` prüfen: `SEED_ADMIN_NAME`, `SEED_ADMIN_EMAIL`, `SEED_ADMIN_PASSWORD` gesetzt
(neu seit B8 – vorher stand das Passwort im Repo).
- [ ] Wartungsfenster mit Marcel abstimmen: die Displays holen ihre Konfiguration zyklisch,
ein Neustart ist nicht nötig, aber ein sichtbarer Wechsel mitten im Ladengeschäft schon.
### 8.2 Deployment
```bash
php artisan migrate --force # legt u. a. cms_brands an (AP2)
# 0. Kernmarken anlegen (CABINET, Rotpunkt Küchen, Febrü).
# Überschreibt nichts – bereits gepflegte Marken bleiben unberührt.
php artisan db:seed --class=CmsBrandSeeder --force
# 1. Neue Texte ins CMS. Überschreibt GENAU die in AP1 geänderten Sektionen (seit 06.10. 15, die vier Netzwerk-Sektionen sind entfernt),
# alles andere bleibt unangetastet.
php artisan db:seed --class=InteriorRelaunchCmsSeeder --force
# 2. Display-Branding auf das Interior-Logo vereinheitlichen (Logo je Theme + neuer Claim).
php artisan db:seed --class=DisplayInteriorBrandingSeeder --force
# 3. Medien-URLs host-unabhängig machen. Ohne diesen Schritt zeigen vier veröffentlichte
# Playlists weiterhin auf b2in.test und laden auf dem Live-Display nicht.
php artisan db:seed --class=DisplayMediaUrlNormalizerSeeder --force
# 4. Ausführliche Markenseiten (CABINET, Rotpunkt, Febrü). Schreibt NUR tagline, description,
# sections, widget_label, cta_title und cta_text – Bilder, Galerie, Leistungen und
# Reihenfolge bleiben unberührt.
php artisan db:seed --class=BrandPageTextSeeder --force
# 5. Entfernt den Vermerk „BEISPIEL – “ aus vorhandenen Bildunterschriften (Nachtrag 5).
# Fasst nur den Text an, mehrfaches Ausführen ist unbedenklich.
php artisan db:seed --class=BrandGalleryCaptionNormalizerSeeder --force
# 6. Markenlogos nachtragen. Setzt `logo` nur, wenn das Feld leer ist – ein im Admin
# hochgeladenes Logo bleibt stehen. Braucht public/img/assets/marken/ aus dem Archiv.
php artisan db:seed --class=BrandLogoSeeder --force
# 7. Welten-Trennung (Nachtrag 7): Navigation und Inhalte der Immobilien-Welt anlegen,
# Interior-Navigation ohne „Immobilien“ und Magazin-Einleitung ohne Dubai-Bezug setzen.
# Die Migration ordnet die drei Dubai-Beitraege der Immobilien-Welt zu.
php artisan db:seed --class=WorldSplitCmsSeeder --force
# 8. Vierte Kernmarke Otterpohl, Slug „tischlerei“ (Nachtrag 9 + 15) – legt nur an, was fehlt.
php artisan db:seed --class=CmsBrandSeeder --force
# 9. Herkunfts-Sektion „Aus der Region gefertigt“ (Nachtrag 10) – legt die Sektion nur an, wenn sie
# fehlt. Entfernt aus einem bestehenden Eintrag nur den veralteten Schlüssel „locations“ (Nachtrag 11).
php artisan db:seed --class=OriginSectionCmsSeeder --force
# 10. Fertigungsort je Kernmarke (Nachtrag 11) – setzt Ort, Entfernung und Rolle nur bei Marken,
# deren Ort noch leer ist. Die Migration davor legt die drei Spalten an.
php artisan db:seed --class=BrandOriginSeeder --force
# 11. Otterpohl (Nachtrag 15 + 19): ersetzt den Platzhalter „Tischlerei“ durch Name, Claim, Texte,
# Leistungen, Abschnitte, Abschlussblock und Fertigungsort aus der Zulieferung v2 vom 24.09.
# Überschreibt auch einen Stand mit dem Text vom 17.09. Bild, Galerie und Reihenfolge
# bleiben; das Logo wird nur gesetzt, wenn es leer ist.
# Schritt 4 (BrandPageTextSeeder) bringt das Feedback vom 17.09. für CABINET, Rotpunkt
# und Febrü mit – er überschreibt dort wie dokumentiert die Textfelder.
php artisan db:seed --class=OtterpohlBrandSeeder --force
# 12. Bilder für CABINET, Rotpunkt, Febrü (Nachtrag 16) und Otterpohl (Nachtrag 19): Titelbild nur,
# wenn keins gepflegt ist; Galeriebilder nur, wenn die Datei noch nicht in der Galerie steht.
# Die WebP-Dateien liegen unter public/img/assets/marken/{cabinet,rotpunkt,februe,otterpohl}/
# im Repository.
php artisan db:seed --class=BrandImageSeeder --force
# 13. Demobilder aus den Galerien entfernen (Nachtrag 17) – erkennt sie am Dateinamen des
# Demo-Seeders, echte Bilder bleiben. Nach Schritt 12 ausführen.
php artisan db:seed --class=DemoBrandGalleryCleanupSeeder --force
# 14. WhatsApp-Business-Nummer 0521-98620100 (Nachtrag 18): ersetzt nur den Platzhalter
# „+49 151 00000000“, die falsche Nummer vom 23.09. (…98610020) oder ein leeres Feld –
# eine im Admin gepflegte andere Nummer bleibt.
php artisan db:seed --class=WhatsappNumberSeeder --force
# 15. Projekttext auf der Startseite (Briefing 24.09., Punkt 7; Nachtrag 19): ersetzt in
# home/audience_switch nur den Satz „Mit deutscher Vertragssicherheit …“. Idempotent.
php artisan db:seed --class=AudienceSwitchProjectTextSeeder --force
# 16. Abstimmung 01.10. (Nachtrag 20): Hero-Überschrift „Räume aus einer Hand.“ und zweiten
# Hero-Button entfernen (nur wenn noch die alte Überschrift im CMS steht), Projekttext mit
# Punkt (Schritt 15 erneut, idempotent), Otterpohl: Titelbild tauschen + Abschnitt „Montage“.
# Die Hinweisleiste ist per Standard aus (B2IN_ANNOUNCEMENT_BAR, nichts in die .env nötig).
php artisan db:seed --class=HomeHeroTextSeeder --force
php artisan db:seed --class=AudienceSwitchProjectTextSeeder --force
php artisan db:seed --class=OtterpohlBrandSeeder --force
php artisan config:cache
php artisan view:clear
# Assets. WICHTIG: `build:web` baut nur die vier Frontend-Themes, NICHT das Portal.
# Fehlt public/build/portal/manifest.json, liefert das Admin-Portal 500.
npm ci
npm run build:web
npm run build:portal
```
Danach prüfen, dass alle fünf Manifeste liegen:
```bash
for d in b2in b2a stileigentum style2own portal; do
test -f "public/build/$d/manifest.json" && echo "$d ok" || echo "$d FEHLT"
done
```
### 8.3 Nachher prüfen
- [ ] Startseite: Reihenfolge, Kernmarken-Anker `#marken`, Sponsoring-Logos
- [ ] Markenseite `/marken` – Aufbau, Merkmale, „Alle Marken ansehen" von der Startseite
- [ ] Kernmarken-Detailseiten `/marken/cabinet`, `/marken/rotpunkt`, `/marken/februe` – je mit
Logo im Kopf, ausführlichem Text, aufklappbaren Vertiefungen und eigenem Abschlussblock
- [ ] Logos erscheinen auf der Startseite, auf `/marken` und in den „Weitere Marken"-Kacheln
- [ ] Auf dem Smartphone prüfen: Das Terminanfrage-Widget trägt auf der Markenseite die längere
Beschriftung und darf keine Inhalte verdecken
- [ ] `/netzwerk`, `/interior`, `/partner`, `/service` leiten auf `/marken`
- [ ] Im Admin unter „Kernmarken" eine Marke ändern und prüfen, dass es auf der Seite ankommt
- [ ] Ankündigungsleiste erscheint (Dismiss-Key hängt seit B3 am Inhalt – wer die alte Leiste
weggeklickt hatte, sieht die neue wieder)
- [ ] Logo in Header, Footer **und** in einer Test-Mail
- [ ] Alte Magazin-URLs `/magazin/1` … `/magazin/5` leiten per 301 auf den Slug
- [ ] `/service` und `/portfolio` leiten um statt 500 zu liefern
- [ ] Terminanfrage-Widget: WhatsApp-Button oben, Link führt auf `wa.me/4952198620100`, darunter
„oder per Formular“ und das Formular
- [ ] Interior-Seiten laufen in „Neu“ (`<html class="palette-modern">`), kein Farb-Schalter;
`/immobilien` unverändert blau
- [ ] `/entwurf`: Gold-Logo in Kopf und Fuß, Hero mit Goldkante und laufender Slideshow (Marken-
Titelbilder), keine Ankündigungsleiste, ein Button im Hero, Termin-Widget unten rechts
- [ ] `/marken/tischlerei`: Text v2 (drei Abschnitte, acht Leistungen, Abschluss „Das Ganze statt
nur das Einzelne“), Werkstatt-Titelbild, Galerie „Referenzen und Einblicke“ mit den Badges
„Realisiertes Projekt“ und „Aus der Werkstatt“
- [ ] **Die vier Displays sichten:** „Display 4 – Innen", „Kundenstopper", „Display 2",
„Test-Display". Besonders das Modul „Cabinet-Influencerin" – dessen Video lag auf
`b2in.test`. Ob die Datei auf dem Live-Server überhaupt existiert, ließ sich lokal nicht
prüfen; fehlt sie, muss sie nachgeladen werden.
- [ ] Search Console: die alten Magazin-URLs waren indexiert, die 301 fängt sie ab
- [ ] Search Console: `/netzwerk` war indexiert und leitet jetzt auf `/marken`
### 8.4 Zurückrollen
Die Seeder sind nicht umkehrbar. Rückweg ist das Backup aus 8.1 – für die betroffenen Tabellen
genügt ein selektives Zurückspielen von `flux_cms_contents`, `display_versions` und
`display_version_items`.
### 8.5 Nicht vergessen
- [ ] Altes Seeder-Passwort rotieren, falls es anderswo benutzt wird – es steht weiterhin in der
Git-Historie (B8).
- [ ] WhatsApp-Nummer bestätigen lassen – erst dann erscheint der Button im Terminanfrage-Widget
und in den CTA-Blöcken (CMS: `global.contact.whatsapp`).
- [ ] `CONTACT_FORM_RECIPIENT` in der Live-`.env` prüfen: dorthin gehen auch die Terminanfragen
aus dem Widget.
- [ ] Datenschutzerklärung um einen Absatz zum WhatsApp-Link ergänzen, sobald die Nummer steht.
- [ ] **Rechtstexte:** AGB (`b2in_legal.php` Zeile 117) und Datenschutzerklärung (Zeile 221)
nennen weiter „Connecting Design and Property", Dubai/Lissabon und Supply-Chain-Management.
Von Marcel prüfen lassen, dann eintragen.
- [ ] Prüfen, welches Display auf der Website laufen soll (`referenzen.display_feed.display_id`).
Ohne Eintrag zeigt die Seite das erste aktive Display mit veröffentlichter Playlist.
- [ ] Die zwei neuen Magazin-Beiträge von Marcel gegenlesen lassen – sie sind Platzhalter.
- [ ] **Herstellerfreigabe für die Markentexte** (Rotpunkt, Febrü) einholen – so verlangt es die
Zulieferung selbst. Beide Fassungen nennen zusätzlich die EmpCo-Regeln (ab 27.09.2026):
die Umweltaussagen vorher rechtlich prüfen lassen.
- [ ] **CABINET-Seitentext von Marcel freigeben lassen.** Für CABINET lag keine Zulieferung vor;
der Text ist von uns nach dem Muster der beiden abgestimmten Seiten verfasst und beschreibt
bewusst nur die eigene Arbeitsweise (Nachtrag 5).
- [ ] **Febrü, Vertiefung „Konkrete Standards statt allgemeiner Versprechen“:** Der Blaue Engel
gilt nur für ausgewählte Tischsysteme und Stauraummöbel. Beim Redigieren darf daraus keine
pauschale Aussage über das Gesamtsortiment werden.
- [ ] **Demobilder der Rubrik „Realisiertes Projekt" ersetzen.** Der Vermerk „BEISPIEL" ist auf
Kundenwunsch entfallen (Nachtrag 5); die Stockmotive und KI-Renderings aus
`DemoBrandGallerySeeder` geben sich damit als echte Referenzen aus. Entweder durch echte
Projektfotos ersetzen oder auf „Inspiration" umstellen.
- [ ] **Rotpunkt, Vertiefung „Zahlen, Zertifizierungen und Raumklima“:** Die Aussage zur jährlich
erhaltenen Waldfläche von ungefähr 50 Fußballfeldern darf laut Zulieferung nur mit
ausdrücklicher Bestätigung veröffentlicht werden. Ohne Bestätigung den Absatz entfernen
(Admin: Kernmarken → Rotpunkt → Abschnitte → Vertiefung).
- [ ] OG-Bild anlegen: das Layout verweist auf `img/assets/b2in-og-default.jpg`, die Datei
existiert nicht. Bis dahin zeigt jede geteilte Seite eine Vorschau ohne Bild.
- [ ] Sponsoring-Logo `tc-bad-waldliesborn-horn` liegt nur als JPG vor, also ohne transparenten
Hintergrund. Falls Marcel eine PNG-Variante hat, austauschen.
- [ ] Markengalerien vor dem Livegang durchsehen: jedes Bild muss in der richtigen Rubrik stehen.
Ohne gesetzte Kategorie erscheint ein Bild als „Visualisierung" – für ein echtes Projektfoto
also bewusst „Realisiertes Projekt" wählen.
---
## Nachtrag 6 · Arbeitsplan 14.09.2026, Punkt 1.1: Farbwelt und Logo (14.09.2026)
Grundlage: `umsetzungen-14-09-2026.md` (freigegebener Umfang Phase 1) und die Shop-Screenshots
unter `_b2in_interior_shop/screenshots/web/` als optische Richtung: warme Creme-/Sandflächen,
dunkles Tannengrün als Kontrast, Serifen-Überschriften, ruhige Flächen ohne Leuchteffekte.
### Zwei Welten auf einem Themensystem
`theme-b2in.css` definiert die **Interior-Welt als Standard** auf `:root` und die
**Immobilien-Welt** als Override unter `.world-immobilien` – dieselben Token-Namen, andere Werte.
Das Layout setzt die Welt als Body-Klasse (`world-interior` / `world-immobilien`), ermittelt
über `b2in_world()` in `app/helpers.php`: Immobilien sind alle Routen `immobilien*` und
`dev.immobilien*`, alles andere ist Interior. Bestehende Sektionen mussten dafür nicht angefasst
werden – sie nutzen bereits die Tokens (`text-secondary`, `bg-accent`, `cta-band` …).
| Token | Interior (neu) | Immobilien (wie bisher) |
|---|---|---|
| `--background` | Creme `#f7f4ef` | helles Beige `#f5f4f2` |
| `--foreground` | Anthrazit `#2d2926` | `#2a2a2a` |
| `--primary` (Buttons, dunkle Sektionen, Wortmarke) | Tanne `#202c2b` | Anthracite `#2b3f51` |
| `--secondary` (Akzent, Hervorhebungen, Logo-Bogen) | Ocker `#d39e46` | Dynamic Blue `#20a0da` |
| `--accent` (Sektionswechsel) | Sand `#d9cbbf` | helles Grau |
| `--clay` (neu) | Clay `#a38f82` | gedämpftes Blau |
| `--footer-*` (neu) | Sand mit Tanne | Anthracite mit Weiß |
| `--cta-band-end` (neu) | helleres Tannengrün (Ton in Ton, Korrektur 15.09.) | Dynamic Blue |
| `--font-secondary` (Überschriften) | EB Garamond (liegt lokal vor) | IBM Plex Sans |
**Kontrast-Entscheidungen:** Ocker trägt keinen hellen Text (Creme auf Ocker ≈ 2,5:1). Deshalb
ist Ocker in der Interior-Welt Akzent, kein Flächenton unter Text: der Kontakt-Button im Kopf
und der zweite Hero-Button laufen jetzt auf Tanne (gefüllt bzw. als Kontur, `.world-interior
.btn-secondary-accent`), der Abschluss-CTA der Startseite liegt auf dem Abschlussband
(`cta-band`) mit weißem Button – wie auf den Markenseiten. **Korrektur 15.09.:** Der Verlauf
Grün → Ocker wirkte unharmonisch; das Band läuft jetzt Ton in Ton in ein helleres Tannengrün aus,
Ocker sitzt nur noch in der Haarlinie oben. Hervorgehobene Wörter im Bandtitel („Ihr nächster
Schritt") tragen im CMS `text-primary` und waren auf dem dunklen Band unlesbar – in beiden
Welten. `.cta-band .text-primary` setzt sie auf den Akzent der Welt (Ocker bzw. Blau). Ocker-Text
im Footer auf Sand nutzt die tiefe Stufe `--secondary-deep`. `--muted-foreground` ist ein
abgedunkelter Clay, damit Fließtext auf Creme lesbar bleibt (Clay pur ≈ 2,9:1).
**Aufgeräumt:** Die Glow- und Schattenklassen in `shared-styles.css` (`glow-*`, `shadow-elegant`,
`shadow-premium`, Button-Hover im Glass-Flow) hatten ein festes Blau (`rgba(0,136,204)`,
`hsla(199 …)`) – jetzt hängen sie am Akzent der Welt. Karten im Glass-Flow nutzen `--card`
statt festem Weiß.
### Logo je Welt
Zulieferung 07.09.2026 (`dev/2026-08-28/logos/`): das Interior-Logo in Tanne mit Ocker-Bogen
(positiv) bzw. Creme mit Ocker-Bogen (negativ) und das B2In-Logo ohne Zusatz mit blauem Bogen.
Beide Familien sind auf den Inhalt beschnitten (Interior `viewBox="152.5 94.5 289 228"`,
B2In `viewBox="4.5 2 301 176"`) und liegen als SVG und PNG (240 px hoch) unter
`public/img/logos/`; die Interior-Varianten zusätzlich unter `public/_cabinet/assets/` für das
Display. `ThemeHelper::getLogoPath($type, $world)` liefert die Datei je Welt.
Header und Footer halten Welt und Logo-Pfad in `mount()` fest, weil ein späterer
Livewire-Request keine Seitenroute mehr trägt. Der Interior-Footer liegt auf Sand und zeigt das
**positive** Logo; der Immobilien-Footer bleibt dunkel mit dem negativen.
**Favicons** (`public/favicon/`, alle Größen plus `favicon.ico`) neu aus der Wortmarke in
Tanne/Ocker erzeugt; `theme-color` auf Creme. Die bisher tote Referenz
`img/favicons/b2in-favicon.ico` (`ThemeHelper::getFaviconPath()`) existiert jetzt.
Mail-Kopf auf Tanne.
### Welten-Wechsler
Kopfzeile (Desktop rechts neben dem Sprachwechsel, mobil als erster Block im Menü) und Footer
tragen einen Umschalter „Interior | Immobilien" (`b2in_worlds()`, Labels in `ui.php` de/en).
Die aktive Welt ist gefüllt und trägt `aria-current="page"`.
### Tests
`tests/Feature/ColorWorldsTest.php` (neu, 14 Fälle): Body-Klasse und Logo je Welt für
Startseite, `/immobilien`, Detailseite und weitere Seiten; `ThemeHelper::getLogoPath()` je Welt
und für Fremd-Themes; Dateien und Farbwerte beider Logo-Familien; Wechsler in Kopf und Fuß mit
korrekter Markierung; Tokens im Theme; kein festes Blau mehr in den Effekten.
`HomePageStructureTest` an den neuen Zuschnitt und den Sand-Footer angepasst.
### Offen nach diesem Nachtrag
- **Freigabe Marcel:** Farbwerte sind laut Plan „blockierend für 1.1" und wurden wie geliefert
umgesetzt. Die **Serife für Überschriften** (EB Garamond) ist eine Ableitung aus den
Shop-Screenshots, kein Planpunkt – ein Token in `theme-b2in.css`, bei Ablehnung in einer Zeile
zurück auf IBM Plex Sans.
- **Bildwelt:** Die Farbwelt wirkt erst mit warmem Bildmaterial (Hero, Kacheln) vollständig; die
Bestandsbilder aus der Immobilien-Zeit sind kühl. Zulieferung weiterhin offen.
- **Display-Player:** nutzt die neuen Interior-Dateien automatisch; der Theme-Scrim im Player
ist weiter neutral dunkel, keine Anpassung an Tanne vorgenommen.
- Nächste Schritte laut Reihenfolge im Arbeitsplan: 1.2 Tischlerei-Markenseite (blockiert durch
Name, Logo, Bilder), 1.3 Herkunfts-Sektion (blockiert durch CABINET-Klärung), 1.4 Akkordeon
und Widget-Beschriftung.
---
## Nachtrag 7 · Saubere Trennung der beiden Welten (14.09.2026)
Abgestimmt mit Kevin am 14.09.: Nach der Farbwelt-Umstellung zeigte die Immobilien-Welt noch die
Interior-Navigation, und das Magazin mischte Dubai- und Einrichtungsthemen. Entscheidung: **jede
Welt hat ihre eigene Navigation, der Welten-Wechsler ist die einzige Brücke.** Umfang ist eine
Erweiterung zu 1.1 des Arbeitsplans (nicht im freigegebenen Plan enthalten, Aufwand ca. 6–9 h).
### Was je Welt getrennt ist
| Bereich | Interior | Immobilien |
|---|---|---|
| Navigation (CMS) | `global.header`: Home · Marken · Referenzen · Magazin · Über B2in | `global.header_immobilien`: Immobilien · Projekte · Magazin · Über B2in |
| Ankündigungsleiste (CMS) | `global.announcement_bar`: aktuelles Interior-Thema (Rotpunkt) | `global.announcement_bar_immobilien`: aktuelles Projekt (Azizi Creek Views 4, wie vor dem Relaunch), eigener Dismiss-Schlüssel |
| Magazin | `/magazin` – nur Interior-Beiträge | `/immobilien/magazin` – nur Immobilien-Beiträge |
| Über uns | `/about` (unverändert) | `/immobilien/ueber-b2in` – Inhalte aus der Zeit vor dem Relaunch, leicht angepasst, als „B2in Immobilien" gekennzeichnet, Claim „Connecting Design and Property" |
| FAQ | `/faq` (unverändert) | `/immobilien/faq` – Fragen zu Investment, Reisen, Steuern, Einrichtung nach dem Kauf |
| Kontakt | `/contact` (unverändert) | `/immobilien/kontakt` – gleiche Seite, eigener Einstieg und eigene Betreffe (Overlay `contact_form_immobilien` über `contact_form`), Mail-Betreff „Kontaktanfrage B2in Immobilien: …"; Kontakt-Button im Kopf und CTAs der Immobilienseite zeigen dorthin |
| Footer-Links | wie bisher | FAQ, Über uns und Magazin zeigen auf die Immobilien-Seiten |
| Terminanfrage-Widget | Anliegen offen | Anliegen „Immobilien" vorbelegt, Mail-Betreff „Terminanfrage B2in Immobilien" |
**Gemeinsam bleiben** Rechtstexte und die Cookie-Seiten. (Kontakt war zunächst gemeinsam, seit 15.09. je Welt.)
### Magazin: Beiträge haben eine Welt
Neues Feld `world` (`interior` | `immobilien`) an `cms_articles`, im Admin als Auswahl „Welt"
mit Badge in der Liste. Die Migration ordnet die drei Dubai-Beiträge (Escrow, Al Jaddaf,
Turnkey) der Immobilien-Welt zu; der Supply-Chain-Beitrag bleibt laut Abstimmung unter B2B in
Interior. `CmsArticle::forFrontend($world)` filtert, Lang-Fallback inklusive.
Die **Detailseite behält ihre URL** (`/magazin/{slug}`, SEO und 301-Weiterleitungen bleiben),
rendert aber in der Welt ihres Beitrags: die Route setzt die Welt per `b2in_world_set()` vor dem
Rendern, weil Kopf- und Fußzeile sie in `mount()` festhalten. Verwandte Beiträge und der Rückweg
zur Liste bleiben in derselben Welt.
### Technik
- `b2in_world()` prüft zuerst einen expliziten Override (`b2in_world_set()`), dann die Route.
- Header: Navigation je Welt; der aktive Pfad wird in `mount()` festgehalten (vorher ging die
Markierung bei jedem Livewire-Request verloren) und nur der spezifischste Eintrag ist aktiv
(`/immobilien/magazin` markiert „Magazin", nicht „Immobilien").
- `AboutHero`, `OurStory`, `OurValues` nehmen jetzt wie `ImageBreak` einen Sektionsschlüssel.
- Kontaktformular: Betreffe in drei Gruppen (Interior · Immobilien · B2in, `form.subject_groups`).
Die Kernmarken aus dem CMS stehen automatisch am Anfang der Interior-Gruppe, die Welt der Seite
bestimmt, welche Gruppe oben steht. Betreff wird jetzt gegen die Liste validiert. Ältere
CMS-Stände mit flacher `subjects`-Liste funktionieren weiter (Fallback ohne Gruppen).
- Abschluss-CTA („Ihr nächster Schritt") je Welt: `global.cta_section_immobilien` mit eigenem Text
und Link auf `/immobilien/kontakt`; greift automatisch auf allen Seiten der Immobilien-Welt.
- Neue CMS-Sektionen (alle im Admin pflegbar): `header_immobilien`, `announcement_bar_immobilien`, `contact_form_immobilien`, `cta_section_immobilien`, `magazin_list_immobilien`,
`faq_immobilien`, `immobilien_about_hero`, `immobilien_our_story`,
`immobilien_about_image_break`, `immobilien_our_values`.
- `WorldSplitCmsSeeder` legt die neuen Sektionen an und setzt **zwei** Interior-Sektionen aus
den Lang-Dateien zurück: `header` (ohne „Immobilien") und `magazin_list` (ohne Dubai-Bezug).
Lokal ausgeführt, beim Deployment mit erledigen (Abschnitt 8).
### Tests
`tests/Feature/WorldSplitTest.php` (neu, 18 Fälle): Navigation je Welt inkl. aktiver Markierung,
Footer-Links, beide Magazinlisten, Detailseite in der Welt des Beitrags, verwandte Beiträge,
CMS-Beiträge je Welt, Editor-Auswahl und -Validierung, Über-uns- und FAQ-Seite der
Immobilien-Welt, Widget-Vorbelegung. `MagazinPageTest`, `HomePageStructureTest`,
`LiveSiteReviewTest` und `CmsAdminTest` an die Trennung angepasst.
### Offen
- Navigationspunkt „Projekte" zeigt auf den Anker `#projekte` der Immobilienseite.
- Die Immobilien-Seite selbst nutzt weiter feste Farben (`bg-zinc-950`, Status-Badges in
emerald/sky/amber) außerhalb des Token-Systems – funktioniert in der blauen Welt, ist aber
nicht themenfähig.
- `LiveSiteReviewTest` prüft die Startseite noch auf das Wort „Netzwerk" (steht im Fließtext);
kein Navigationspunkt.
---
## Nachtrag 8 · Terminanfrage-Buttons öffnen das Widget (15.09.2026)
Auf der Seite gibt es mehrere Sektionen mit „Termin anfragen"-Buttons (Hero, Weiche, Abschluss-CTA,
`/marken`-Abschluss, Markenseiten, Referenzen, Immobilienseite). Bisher führten sie auf die
Kontaktseite. Jetzt öffnen sie das schwebende Terminanfrage-Widget; von einer Markenseite aus ist
die Marke im Anliegen vorbelegt. Die Anliegen-Auswahl des Widgets bleibt wie sie ist (Marken plus
feste Einträge, keine Gruppen – die gibt es nur auf der Kontaktseite).
**Wie:** Blade-Komponente `<x-appointment-link :href :topic>`. Zeigt `href` auf `/contact` oder
`/immobilien/kontakt`, trägt der Link `data-open-appointment` (und `data-appointment-topic`); ein
Klick-Delegat in `resources/js/app.js` fängt ihn ab und feuert `open-appointment`, das Widget
öffnet sich, übernimmt das Anliegen (nur wenn es in der Auswahl existiert) und fokussiert das
Namensfeld. Ohne JavaScript bleibt es ein normaler Link; zeigt der CMS-Link woandershin, bleibt
er ein gewöhnlicher Link. Der Kontakt-Button im Kopf bleibt bewusst ein Link zur Kontaktseite.
**Formularfelder** in Widget und Kontaktformular tragen jetzt die tokenbasierten Klassen
`.form-field` / `.form-select` (shared-styles.css): Kartenton als Grund, Fokusring im Akzent der
Welt, eigener Chevron und lesbare Gruppenüberschriften in der Betreff-Auswahl – statt
Tailwind-Grau.
**Tests:** `tests/Feature/AppointmentTriggerTest.php` (9 Fälle).
---
## Nachtrag 9 · Tischlerei als vierte Kernmarke, Platzhalter (15.09.2026)
Arbeitsplan 14.09., Punkt 1.2. Name, Logo, Basisinfos und Bilder fehlen noch (blockierend laut
Plan), Platzhalter sind ausdrücklich erlaubt. Die Marke ist deshalb als **Denkanstoß** angelegt:
- **Marke `tischlerei`** im `CmsBrandSeeder` (vierter Eintrag, wird bei bestehenden Marken nicht
doppelt angelegt). Inhalte entlang der Ausrichtung aus dem Plan – Aufbau, Sonderlösungen,
Veredelung, Maßarbeit; ergänzt CABINET und Rotpunkt, steht nicht daneben; Werkstatt in
Ostwestfalen ohne Nennung des Partnerbetriebs. Drei Abschnitte mit Vertiefungen, eigener
Abschlussblock, Widget-Beschriftung „Persönliche Beratung zur Maßarbeit vereinbaren“.
- **Ohne Logo**: der Seitenkopf zeigt den Schriftzug, Kacheln kommen ohne Logo aus. Kein
Stimmungsbild, die Kacheln zeigen den Platzhalter.
- **Kachelraster** der Startseite: `sm:grid-cols-2` – 2×2 auf allen Breiten. Vier nebeneinander
war kurz drin, wurde aber als zu schmal verworfen (Abstimmung 15.09.). „Weitere Marken“ auf den
Detailseiten: drei nebeneinander ab `lg`.
- Automatisch dabei: `/marken`, Anliegen im Widget, Betreff-Gruppe Interior der Kontaktseite.
- Seitentitel von `/marken` nennt die vierte Marke.
- Lang-Fallback (`core_brands.brands`) um die Tischlerei ergänzt.
**Lokal ausgeführt:** `php artisan db:seed --class=CmsBrandSeeder` (legt nur die fehlende Marke an).
Beim Deployment mit erledigen (Abschnitt 8).
**Was Marcel nachträgt (alles im Admin):** Name der Marke, Logo, Stimmungsbild, echte Texte –
die Platzhalter sind bewusst so geschrieben, dass sie ohne Änderung nicht live gehen sollten
(„Tischlerei“ ist kein Markenname). Slug `tischlerei` bleibt stabil, auch wenn der Name kommt.
**Tests:** `tests/Feature/FourthBrandTest.php` (7 Fälle).
---
## Nachtrag 10 · Herkunfts-Sektion „Aus der Region gefertigt“ (15.09.2026)
Arbeitsplan 14.09., Punkt 1.3. Die Klärung „CABINET Hersteller oder Vertrieb“ kam am 15.09. von
Kevin nach seinem Verständnis: CABINET **fertigt selbst in Köln** und **vertreibt über die
CABINET Stores**; der Store Bielefeld ist einer davon. Dort laufen Beratung und Planung für
Bielefeld und Umkreis, gefertigt wird in Köln, aufgebaut beim Kunden – und hier kommt die
Tischlerei ins Spiel, die den Aufbau übernimmt und Sonderwünsche umsetzt. ⚠️ **Von Marcel
bestätigen lassen** (bietet CABINET selbst Aufbau an? Läuft der Aufbau immer über die eigene
Tischlerei?). Die Sektion ist so formuliert, dass sie diesem Verständnis folgt.
### Aufbau
- **Neue Sektion `origin_section`** (`OriginSection`, CMS-Gruppe `home`), eingebunden auf der
Startseite zwischen Kernmarken und „Gebaut auf Expertise“ sowie auf `/marken` zwischen
Markenübersicht und „Mehr als Marken: unser Netzwerk“. Anker `#herkunft`.
- **Schematische Karte** (SVG, ab `md`): Bielefeld als Drehscheibe in Ocker, die vier Standorte
als Punkte in Tanne mit gestrichelten Speichen und Entfernungsangabe, gestrichelte Ellipse
„Ostwestfalen“ um die drei OWL-Standorte. Keine Geografie, nur Richtung und Entfernung.
Positionen und Beschriftungsseite lagen zunächst im Inhalt; seit Nachtrag 11 stehen sie im
Code, die Standorte kommen aus den Marken. Unter `md` entfällt die Karte, die Kacheln tragen alles.
- **Vier Standort-Kacheln** (2×2 ab `sm`): Stadt, Marke, Rolle, Entfernung „von Bielefeld“ und
Link „Zur Marke“. Ursprünglich lagen die Standorte im Sektions-JSON, der Link kam aus den
veröffentlichten Marken. Seit Nachtrag 11 gilt: Standort = Marke, siehe dort.
- **Ablauf-Block „Gefertigt in Köln, geplant in Bielefeld“**: drei nummerierte Schritte am
Beispiel CABINET (Beratung und Planung in Bielefeld → Fertigung in Köln → Aufbau vor Ort mit
Sonderwünschen aus der Tischlerei). Das ist die saubere Trennung „gefertigt“ / „vertrieben“
aus dem Plan. Entfällt, wenn `steps` im CMS leer ist.
- **Entfernungen** gerundet, Straße ab Bielefeld Mitte: Herford rund 20 km, Bünde rund 30 km,
Rheda-Wiedenbrück rund 35 km, Köln rund 200 km. Bei Bedarf im Admin anpassen.
- Styles `.origin-map-*` in `shared-styles.css` (Token-basiert, in beiden Welten gültig, die
Sektion liegt aber nur in der Interior-Welt). Build `npm run build:b2in` ausgeführt.
**Lokal ausgeführt:** `php artisan db:seed --class=OriginSectionCmsSeeder` (legt nur an, was
fehlt, überschreibt nichts). Beim Deployment mit erledigen (Abschnitt 8, Schritt 9).
**Offen:** Bestätigung des CABINET-Ablaufs durch Marcel; Wortlaut der Tischlerei-Kachel folgt
dem Platzhalter aus Nachtrag 9 und wandert mit dem echten Markennamen.
**Tests:** `tests/Feature/OriginSectionTest.php` (12 Fälle, mit Nachtrag 11 umgeschrieben).
---
## Nachtrag 11 · Standorte an der Marke, Marken in der Sidebar (15.09.2026)
Prüfung der Inhaltspflege im Admin auf Kevins Frage, ob Marken wie das Magazin einen eigenen
Punkt brauchen. Ergebnis: **Die Markenverwaltung gab es schon** („Kernmarken verwalten“, AP2),
sie war nur nicht in der Sidebar verlinkt – erreichbar allein über die Kachel auf der
CMS-Übersicht. Die Herkunfts-Sektion aus Nachtrag 10 lag dagegen komplett im generischen
Inhalte-Editor, inklusive der Kartenkoordinaten `x`/`y` und der Beschriftungsseite als nackte
Felder. Das war für Marcel nicht pflegbar. Abgestimmt mit Kevin: **Weg 1**, Standort an die Marke.
### Was sich ändert
- **Sidebar:** neuer Eintrag „Marken“ unter CMS → Website-Inhalte, zwischen Projekte und
Magazin. Die Gruppe klappt auf der Markenseite jetzt auch auf.
- **Drei neue Felder je Marke** (Migration `add_origin_to_cms_brands_table`, übersetzbar wie die
übrigen Textfelder): `origin_city` (Fertigungsort), `origin_distance` (Entfernung als Text,
„rund 30 km“), `origin_role` (ein Satz zur Rolle). Im Admin als Block „Fertigungsort“ unter
den Stammdaten, mit Hinweis: ohne Ort erscheint die Marke nicht in der Sektion.
- **`OriginSection` liest die Standorte aus `CmsBrand::forFrontend()`**: nur veröffentlichte
Marken mit Ort, in der Reihenfolge der Marken. Eine unveröffentlichte Marke fällt damit ganz
heraus (vorher blieb ihr Standort ohne Link stehen – jetzt gibt es keinen Standort ohne
Marke mehr). Der Link „Zur Marke“ hängt an `has_detail_page`, der Text `link_text` liegt
einmal auf Sektionsebene statt je Standort.
- **Kartenpositionen im Code:** `OriginSection::MAP_POSITIONS`, je Slug `x`/`y`/`label`. Eine
neue Marke mit Ort, aber ohne Position, erscheint in den Kacheln, nicht in der Karte – dafür
muss ein Entwickler eine Position ergänzen. Das ist gewollt: die Karte ist Zeichnung, kein
Inhalt.
- **Sektions-JSON `home/origin_section`** enthält nur noch Überschrift, Untertitel, Alt-Text,
Regionslabel, Entfernungs-Suffix, Drehscheibe, Link-Text und Ablauf-Block. Der alte Schlüssel
`locations` wird von `OriginSectionCmsSeeder` aus einem bestehenden Eintrag entfernt, damit er
im Inhalte-Editor nicht mehr als Pflegefeld auftaucht (idempotent, sonst weiterhin nur
anlegen-wenn-fehlt).
- **Lang-Fallback:** die vier Einträge in `core_brands.brands` tragen jetzt `slug` und die drei
Herkunftsfelder, damit die Sektion auch ohne gepflegte Marke steht – dann ohne Links, wie die
Kacheln der Kernmarken.
- **Seeder:** `CmsBrandSeeder` bringt die Herkunft für frische Installationen mit; neu
`BrandOriginSeeder` trägt sie bei bestehenden Marken nach, nur wenn der Ort leer ist (Muster
wie `BrandLogoSeeder`). Keine Dubletten möglich, beide arbeiten über den Slug.
- Ohne Marke mit Ort bleiben Überschrift und Ablauf-Block stehen, Karte und Kacheln entfallen.
**Lokal ausgeführt:** `php artisan migrate`, `db:seed --class=BrandOriginSeeder` (4 gesetzt),
`db:seed --class=OriginSectionCmsSeeder` (`locations` entfernt). Beim Deployment: Abschnitt 8,
Schritte 9 und 10, die Migration läuft mit `migrate` vorweg.
**Offen:** wie Nachtrag 10 (Bestätigung des CABINET-Ablaufs durch Marcel). Der generische
Inhalte-Editor bleibt für die übrigen Sektionen ohne Schema; wo Listen mit technischen Feldern
auftauchen, ist derselbe Weg möglich: Daten an ein Modell hängen, Darstellung in den Code.
**Tests:** `tests/Feature/OriginSectionTest.php` (15 Fälle), drei neue Fälle in
`tests/Feature/Admin/Cms/CmsBrandsIndexTest.php` (Herkunft je Sprache speichern, Ort leeren,
Sidebar-Link).
---
## Nachtrag 12 · Inhalte-Editor: Seitenbaum und Vorschau (15.09.2026)
Kevins Rückmeldung nach der Prüfung des CMS: der Punkt „Inhalte“ zeigt Datenbank-Gruppen
alphabetisch, ohne Bezug zur Seite, mit JSON-Schlüsseln als Beschriftung – zu abstrakt für
Marcel. Abgestimmt: Stufe 1 (Seitenbaum, Klartext) und Stufe 2 (Vorschau im Rahmen) umsetzen,
Stufe 3 (Stift-Markierungen in der Vorschau, die den Editor öffnen) erst nach Marcels Feedback.
### Stufe 1 · Seitenbaum statt Gruppen
- **Registry `config/cms_pages.php`:** je Seite (Startseite, Marken, Referenzen, Magazin, Über
uns, Kontakt, FAQ, „Auf jeder Seite“, Immobilien-Welt, Rechtliches) die Sektionen in der
Reihenfolge, wie sie auf der Website stehen, mit Titel, einem Satz Beschreibung, Pfad und
Sprungmarke. Dazu ein Wörterbuch `field_labels` (`button_text` → „Button-Beschriftung“,
`x`/`y` → „Position (Entwickler)“ …), je Sektion überschreibbar über `fields`.
- **`App\Services\CmsPageTree`** verbindet die Registry mit der Datenbank: welche Sektion
existiert (`content_id`), welche fehlt, was in der Datenbank liegt, aber auf keiner Seite
eingetragen ist. Letzteres landet als Sammelpunkt **„Nicht mehr verwendet“** ganz unten
(Netzwerk-, Partner- und alte Immobilien-Sektionen, `home/synergie_section` & Co.) – nichts
geht verloren, nichts steht im Weg.
- **Editor (`admin.cms.content-index`):** linke Spalte zeigt den Seitenbaum, nummeriert von
oben nach unten. Ein Klick wählt genau eine Sektion (`selectSection`), der Kopf zeigt Titel,
Beschreibung, Seite und den technischen Schlüssel klein darunter. Felder tragen die
Klartext-Beschriftung, der Schlüssel steht als `code` daneben; Typ-Badges heißen „Text“,
„Formatierter Text“, „Bild“, „Liste“. Der Listen-Editor (Overlay) beschriftet seine Felder
genauso. `selectGroup` bleibt für die Gruppenansicht erhalten.
- **Fehlende Sektion anlegen:** steht eine Sektion in der Registry, aber nicht in der
Datenbank (heute z. B. das Terminanfrage-Widget), erscheint sie grau mit „+“. Ein Klick
legt sie über `CmsContentSeeder` mit den Texten aus `b2in.php` an. Rechtliches und die
Kontaktdaten (`global/contact`) haben keine Vorlage, dort erscheint ein Hinweis.
### Stufe 2 · Vorschau
- Rechts neben dem Editor ein **iframe mit der echten Website** (Domain aus
`config('domains')`, Pfad der Seite, `?cms-preview=<n>` als Cache-Brecher, `#anker`). Beim
Wechsel der Sektion springt die Vorschau zur Sprungmarke, nach jedem Speichern lädt sie
neu (`previewVersion`). Buttons: neu laden, in neuem Tab öffnen, Vorschau aus/ein.
Keine Anmeldung nötig, die Website ist öffentlich; Frame-Header setzt die App keine.
- **Sprungmarken nachgezogen:** `id` an den Wurzelelementen der lebenden Sektionen (`hero`,
`gruender`, `wege`, `marken`, `herkunft`, `vision`, `engagement`, `displays`, `immobilien`,
`cta`, `seitenkopf`, `netzwerk`, `wachstum`, `marken-cta`, `projekte`, `magazin`,
`geschichte`, `bild`, `werte`, `kontakt`, `faq`, `header`, `footer`). Beim Display-Feed
sitzt der Anker am Wurzel-`div`, weil die Sektion ohne veröffentlichtes Display leer bleibt.
- Sektionen ohne Seite (Beitragsseite, Widget, Rechtliches) zeigen die Seite ohne Sprung.
### Erkenntnisse beim Kartieren
- `/immobilien` rendert `web/dev/immobilien-azizi.blade.php` mit statischen Texten aus
`immobilien-azizi.json`; die CMS-Sektionen `immobilien/hero`, `warum_dubai`, `kaufprozess`
usw. gehören zur stillgelegten `web/immobilien.blade.php` und stehen deshalb unter „Nicht
mehr verwendet“. Gleiches gilt für `about/leadership_team`.
- `display_feed` liegt in der Gruppe `referenzen`, steht aber zuerst auf der Startseite –
im Baum unter Startseite mit Hinweis auf /referenzen.
**Tests:** `tests/Feature/Cms/CmsPageTreeTest.php` (9 Fälle, darunter: jede Sprungmarke der
Registry existiert auf der jeweiligen Seite, jede Sektion mit Vorlage ist in `b2in.php`
auffindbar) und sechs neue Fälle in `tests/Feature/Cms/CmsAdminTest.php` (Seitenbaum,
Sektion wählen mit Klartext-Labels und Vorschau-Adresse, Sammelpunkt, Anlegen aus Vorlage,
Anlegen ohne Vorlage, Vorschau ausblenden). Kein Deployment-Schritt nötig: Registry und
Editor sind Code, die Datenbank bleibt unverändert.
**Offen:** Stufe 3 nach Marcels Rückmeldung. Neue Sektionen müssen in `cms_pages.php`
eingetragen werden, sonst landen sie unter „Nicht mehr verwendet“ – als Erinnerung steht das
im Kopf der Datei.
---
## Nachtrag 13 · Referenzen als eigenes Modul, Immobilien-Projekte ausgeblendet (15.09.2026)
Kevin nach der Abnahme von Nachtrag 12: „Projekte“ im Portal (die Immobilienprojekte aus
Dubai) ist nicht freigegeben und kommt raus; an die Stelle gehören die Projekte/Referenzen der
Interior-Welt. Das ist eine **Korrektur zu AP5**, das die Referenzen bewusst aus den
Markengalerien zusammengesammelt hatte, um keine zweite Pflegestelle zu schaffen. Ein
realisiertes Projekt hat aber mehr als ein Bild und oft mehr als eine Marke (CABINET plus
Tischlerei), darum jetzt ein eigener Eintrag.
### Was sich ändert
- **Neues Modell `CmsReference`** (Tabelle `cms_references`): Titel und Beschreibung
übersetzbar, Ort und Jahr als Text, Titelbild, weitere Bilder mit Bildunterschrift,
beteiligte Marken als Liste von Slugs aus `cms_brands`, Veröffentlicht, Sortierung.
- **Portal → CMS → „Referenzen“** (`admin.cms.references-index`, Route
`cms.references.index`): Liste mit Vorschaubild, Marken-Badges und Bildanzahl; Formular
nach dem Muster des Magazins mit Sprachumschalter, Marken als Checkboxen, Titelbild und
Galerie über den Media-Picker, Galeriebilder sortierbar. Der Kopf der Seite sagt die Regel:
nur ausgeführte Aufträge, Entwürfe gehören in die Galerie der Marke.
- **Sidebar und CMS-Übersicht:** „Referenzen“ ersetzt „Projekte“. Die Route
`cms.projects.index` bleibt bestehen und ist per Adresse erreichbar, nur nicht mehr
verlinkt – die Dubai-Daten bleiben unangetastet, falls die Freigabe später kommt.
- **`/referenzen`** zeigt die veröffentlichten Referenzen: Titelbild, Titel, „Ort · Jahr“,
Beschreibung, weitere Bilder als kleine Reihe, darunter die beteiligten Marken verlinkt auf
ihre Markenseiten (unveröffentlichte Marken fallen heraus). Ohne Titelbild dient das erste
Galeriebild als Titelbild, ohne jedes Bild wird die Referenz übersprungen.
- **Fallback bleibt:** Solange keine Referenz veröffentlicht ist, greift der Stand aus AP5
(Bilder der Rubrik „Realisiertes Projekt“ aus den Markengalerien). Der Leerzustand von AP5
gilt weiter, wenn beides leer ist. So sieht die Preview beim Deployment keinen Unterschied,
bis Marcel die erste Referenz anlegt.
- Registry `cms_pages.php`: Beschreibung von „Realisierte Projekte“ verweist auf das neue Modul.
**Deployment:** nur `php artisan migrate --force` (Abschnitt 8.2, Schritt 1 deckt die neue
Tabelle ab). Kein Seeder, keine Daten.
**Offen:** Marcel legt die ersten Referenzen selbst an; die bisherigen Galeriebilder der Marken
bleiben dort, wo sie sind. Ob die Bilder aus den Markengalerien später automatisch als Referenz
übernommen werden sollen, klärt sich mit den ersten echten Einträgen.
**Tests:** `tests/Feature/Admin/Cms/CmsReferencesIndexTest.php` (9 Fälle), vier neue Fälle in
`tests/Feature/ReferenzenPageTest.php` (Referenzen vor Galerien, Entwurf ausgeblendet und
Fallback, unveröffentlichte Marke und Referenz ohne Bild, Galeriebild als Titelbild),
Dashboard-Statistik in `CmsAdminTest` auf Referenzen umgestellt.
---
## Nachtrag 14 · Bilddauer im b2in-Display mit Nachkommastelle (17.09.2026)
Kevins Wunsch: Im Store-Display-Modul „b2in Display“ soll die „Dauer (Sekunden)“ eines Bildes
auch 1,5 annehmen. Das Feld war ganzzahlig (`int`, Zahlenfeld mit `min=1`).
- **Eingabe als Text** mit Dezimal-Tastatur: „10“, „1,5“ und „1.5“ sind gültig, Leerzeichen
egal, gerundet auf Zehntel. Erlaubt sind 0,5 bis 120 Sekunden; alles andere bringt beim
Speichern eine Fehlermeldung am Feld und speichert nichts. Bei Videos wird das Feld ignoriert.
- **Gespeichert wird eine Zahl** (`duration_seconds` = 1.5). Der Player in
`public/_cabinet/b2in/index.html` rechnet ohnehin `duration_seconds × 1000`, kommt also ohne
Änderung mit 1500 ms klar. Bestehende Einträge mit ganzen Sekunden bleiben unverändert.
- Anzeige in der Eintragsliste mit Komma („1,5s“), ganze Zahlen ohne Nachkommastelle.
- Doku `public/_cabinet/_docs/b2in-displays.md` ergänzt.
**Tests:** vier neue Fälle in `tests/Feature/DisplayVersionTest.php` (Komma-Eingabe,
Parser/Formatierer, Ablehnung unter 0,5 s, Video ignoriert das Feld). Kein Deployment-Schritt.
---
## Nachtrag 15 · Otterpohl als vierte Marke, Feedback 17.09. eingearbeitet (17.09.2026)
Zulieferung 17.09.2026: `dev/2026-08-28/otterpohl_Webseitentext_FINAL.md` (Text der
Tischlerei-Seite), `dev/2026-08-28/markenlogos/otterpohl.jpg` (Logo) und
`dev/2026-08-28/feedback-17-09-2026.md` (Marcels Rückmeldung zu CABINET, Febrü und Rotpunkt).
### Otterpohl ersetzt den Platzhalter „Tischlerei“
- **Marke:** Name „Otterpohl“, Claim „Nicht easy to click & buy. Aber easy to get.“, Kacheltext,
Text für /marken, drei Merkmale, Einleitung (erster Block des Textes), vier Leistungen, **13
Abschnitte** mit den Überschriften der Zulieferung 1:1, die pointierten Schlusssätze als
Schlusszeile je Abschnitt, Abschlussblock „Ihre Idee. Handwerklich umgesetzt.“, Fertigungsort
Rheda-Wiedenbrück mit Rolle „seit 2014 an unserer Seite“. Slug `tischlerei` bleibt (Links,
Widget-Thema, Kartenposition).
- **Logo:** `public/img/assets/marken/otterpohl.png`, aus der JPG-Zulieferung freigestellt und
knapp beschnitten (Imagick), damit es in der gemeinsamen Logo-Box wie die anderen wirkt.
Das Logo trägt „25 Jahre“ – so geliefert, so übernommen.
- **Seeder:** neu `OtterpohlBrandSeeder` (überschreibt bei der bestehenden Marke alle Textfelder,
lässt Bild, Galerie und Reihenfolge stehen, Logo nur wenn leer); `CmsBrandSeeder` nutzt
dieselben Daten für frische Installationen; `BrandLogoSeeder` und `BrandOriginSeeder` kennen
die Marke. Lang-Fallback, Seitentitel von /marken und der Ablauf-Block der Herkunfts-Sektion
nennen jetzt Otterpohl.
### Feedback 17.09. in den Markentexten (`BrandPageTextSeeder`)
- **CABINET:** Der Abschnitt „Aufmaß vor Ort statt Schätzung am Telefon“ widersprach Marcels
Ablauf (erst Foto und grobes Maß, Aufmaß erst nach Angebot). Ersetzt durch „Ein Bild und ein
grobes Maß reichen für den Anfang“ mit Marcels Fassung wörtlich und der Ablaufzeile
„So läuft’s: Foto + grobe Maße → … → Bestellung“ als Schlusszeile. Zweiter Absatz von
„Die Fragen, die wir vorher stellen“ ersetzt (Karnevalskostüme, Sportsachen).
- **Febrü:** nur die vereinbarten Stellen: „Bei uns gibt es Kunden – keine Schubladen“ (drei
Absätze), „Aus Herford“ mit „sehr hohe Fertigungstiefe“ statt 95 Prozent, „Wertschätzung ist
keine Frage der Stückzahl“ (Familienalltag statt Mütter-Passage). Der doppelte Schlusssatz
„Ob im Kleinen oder im Großen …“ steht nur noch beim ersten Abschnitt. „Nicht drei Marken“,
„Das Wir und das Ich“ und der mitlaufende CTA unverändert.
- **Rotpunkt:** „Mehr als Küchen“ mit Marcels drei Absätzen ersetzt.
- **Handwerk als gemeinsamer Abschluss (Punkt 3b):** Die markeneigenen Abschnitte
„Schranksystem plus Handwerk“ (CABINET) und „Küchenstudio plus Handwerk“ (Rotpunkt) sind
entfallen. Stattdessen gibt es **eine** Sektion `brand_craft` („Planung plus Handwerk“, Lang-
Datei, im Inhalte-Editor unter Marken → „Handwerk-Abschluss auf jeder Markenseite“ anlegbar),
die `BrandDetail` als letzten Abschnitt auf jeder Markenseite anhängt – außer auf der
Otterpohl-Seite selbst. Der Link „Mehr zu Otterpohl“ erscheint nur, wenn die Marke
veröffentlicht ist. Anker `#handwerk`. Einmal gepflegt, überall gleich – genau wie gewünscht.
**Lokal ausgeführt:** `OtterpohlBrandSeeder`, `BrandPageTextSeeder`, `BrandLogoSeeder`.
Beim Deployment: Abschnitt 8.2, Schritt 4 (Feedback) und Schritt 11 (Otterpohl).
**Offen:** Stimmungsbild und Galerie für Otterpohl (im Admin nachtragen); Nachtrag 10 bleibt
offen, ob CABINET selbst Aufbau anbietet. Der Text bestätigt Rheda-Wiedenbrück und die
Montage-Partnerschaft seit 2014 – die Herkunfts-Sektion passt damit.
**Tests:** `tests/Feature/BrandFeedbackTest.php` (10 Fälle: CABINET-Einstieg und Fragen, Febrü-
Passagen und einmaliger Schlusssatz, Rotpunkt „Mehr als Küchen“, Handwerk-Abschluss auf drei
Seiten mit Link und Position, nicht auf Otterpohl, ohne Link bei unveröffentlichter Marke,
CMS-Vorrang, Otterpohl-Seeder behält Logo/Bild/Reihenfolge); `FourthBrandTest`,
`MarkenPageTest` und `BrandPageSectionsTest` auf die neuen Texte umgestellt.
---
## Nachtrag 16 · Erste Bilder für CABINET und Rotpunkt (22.09.2026)
Zulieferung 22.09.2026 unter `dev/2026-08-28/images/{cabinet,rotpunkt}/`, das Titelbild je
Marke mit `_title_` markiert. 12 Bilder CABINET, 14 Bilder Rotpunkt, alles Visualisierungen des
Herstellers (Renderings), 3–6 MB je Datei.
- **Optimierung:** alle Bilder als WebP mit maximal 1920 px Kantenlänge, Qualität 82, ohne
Metadaten (Imagick) nach `public/img/assets/marken/cabinet/` und `…/rotpunkt/`; 55–330 KB je
Datei statt mehrerer MB. Sprechende Dateinamen aus den Motiven („dachschraegenschrank-filo-
cashmere.webp“, „kueche-07.webp“). Die Originale bleiben per `.gitignore` außerhalb des
Repositorys, nur die WebP-Fassungen werden deployt.
- **Titelbild** (`image`): CABINET „Ankleide Solitär, Eiche Tano“, Rotpunkt „Küche 01“. Es
erscheint im Seitenkopf der Markenseite, auf der Startseiten-Kachel und im Markenblock auf
/marken – dort, wo bisher der Platzhalter stand.
- **Galerie:** die übrigen Bilder als **Visualisierung** mit Bildunterschrift aus dem Motiv.
Die Rubrik passt: Badge „Visualisierung“, Hinweis „Entwürfe und Visualisierungen“. Unter
„Referenzen“ erscheinen sie damit bewusst nicht – das sind keine von uns ausgeführten Projekte.
Marcel kann jedes Bild im Admin umkategorisieren, sobald ein echtes Projekt dahintersteht.
- **`BrandImageSeeder`:** Titelbild nur, wenn keins gepflegt ist; Galeriebilder nur, wenn die
Datei noch nicht in der Galerie steht; vorhandene Einträge bleiben samt Kategorie und
Unterschrift. Mehrfach ausführbar. Febrü und Otterpohl bleiben unberührt (keine Zulieferung).
**Lokal ausgeführt:** `php artisan db:seed --class=BrandImageSeeder`. Beim Deployment:
Abschnitt 8.2, Schritt 12 – die Dateien kommen mit dem Repository.
**Ergänzung 23.09.2026:** Febrü nachgeliefert (`dev/2026-08-28/images/februe/`, 16 Bilder,
Titelbild „Base Thekenlösung“). Gleiche Behandlung, Bildunterschriften nach den Febrü-
Produktlinien aus den Dateinamen („Active 2.0 mit Bureo“, „Arena Stage“, „Vio“ …). Der Seeder
und `BrandImageSeederTest` decken jetzt drei Marken ab.
**Offen:** Bilder für Otterpohl; Freigabe der Bildunterschriften durch Marcel
(aus den Dateinamen abgeleitet, z. B. Frontdekore „Filo Cashmere“, „Velluto Fango“, bei Febrü
die Produktlinien).
**Tests:** `tests/Feature/BrandImageSeederTest.php` (4 Fälle: Titelbild und Galerie, jede
referenzierte Datei existiert als optimierte Datei unter 400 KB und 1920 px, zweiter Lauf
lässt Gepflegtes stehen, Anzeige auf Startseite, /marken, Markenseite und nicht unter Referenzen).
---
## Nachtrag 17 · Demobilder raus, Galerie ohne Rubrikwechsel (22.09.2026)
Abstimmung 22.09. nach den ersten echten Bildern: die Demobilder aus AP2 müssen weg, der
Rubrikwechsel „Realisierte Projekte / Inspirationen“ ist ausgesetzt, solange es keine
realisierten Projekte gibt, und die zwei Zeilen unter der Galerie-Überschrift werden eine.
- **`DemoBrandGalleryCleanupSeeder`** entfernt die elf Demobilder (CABINET 4, Rotpunkt 3, Febrü
4) aus den Galerien. Erkennung am Dateinamen aus `DemoBrandGallerySeeder`, nicht an der
Unterschrift – den Vermerk „BEISPIEL“ hatte Schritt 5 auf der Vorschau bereits entfernt.
Alle anderen Bilder bleiben. Idempotent.
- **Galerie** auf der Markenseite: ein Raster, realisierte Projekte zuerst, jedes Bild trägt
weiter sein Badge („Realisiertes Projekt“ / „Visualisierung“). Kein Tab-Wechsel, keine
Gruppenüberschriften, kein separater Hinweistext. Die Kategorie im Admin bleibt bestehen,
der Wechsel lässt sich wieder einschalten, sobald echte Projekte da sind.
- **Texte:** Überschrift „Inspirationen und Entwürfe“, eine Zeile darunter: „Visualisierungen,
die zeigen, was in einem Raum möglich ist – noch bevor das erste Möbel steht.“ (vorher
„Referenzen und Inspirationen“ plus Hinweis unter der Rubrik).
**Lokal ausgeführt:** `php artisan db:seed --class=DemoBrandGalleryCleanupSeeder` (11 entfernt).
Beim Deployment: Abschnitt 8.2, Schritt 13, nach Schritt 12.
**Tests:** `BrandPageTest` umgestellt (ein Raster mit Badges und Projekten zuerst, genau eine
Zeile unter der Überschrift, Aufräum-Seeder entfernt nur Demobilder und ist idempotent).
## Nachtrag 18 · WhatsApp zuerst, Business-Nummer, Farbvariante „Modern“ (23.09.2026)
Rückmeldung von Marcel über Kevin am 23.09.: Im Termin-Widget soll WhatsApp oben stehen und das
Formular darunter; als Nummer gilt die Business-Nummer 0521-98620100 (am 23.09. kam zunächst
…98610020, Korrektur von Marcel am 24.09.). Außerdem wirkt ihm die
Farbwelt „zu altbacken“ – die beigen Flächen. Gewünscht ist eine klarere, modernere Variante, die
farblich am Logo bleibt (Tanne und Ocker unverändert), sowie ein schwimmender Schalter, um beide
Stände direkt zu vergleichen.
### Termin-Widget
- **Reihenfolge:** WhatsApp-Block direkt unter der Einleitung, darunter eine Trennzeile „oder per
Formular“, dann das Formular. Einleitung jetzt „Am schnellsten per WhatsApp – wir bereiten die
Nachricht mit Ihren Angaben vor.“ Der Hinweis, dass der Besucher selbst abschickt, bleibt.
Der vorbereitete Text liest weiter Anliegen, Wunschtermin und Name aus dem Formular, auch wenn
es jetzt unter dem Button steht. Ohne Nummer im CMS erscheint weder Button noch Trennzeile.
- **Nummer:** `WhatsappNumberSeeder` trägt `+49 521 98620100` in `global.contact.whatsapp` ein
(de und en), aber nur, wenn das Feld leer ist, noch den Platzhalter „+49 151 00000000“ aus der
ersten Einrichtung enthält oder die falsche Nummer vom 23.09. (`+49 521 98610020`) – die hatte
Marcel versehentlich geschickt, auf der Vorschau lief der Button deshalb ins Leere. Eine im
Admin gepflegte andere Nummer bleibt stehen. Fehlt der Eintrag, wird er angelegt. Idempotent.
Ergebnis-Link: `https://wa.me/4952198620100`. Auf einer Vorschau ohne Portal reicht
`php artisan db:seed --class=WhatsappNumberSeeder --force`.
### Farbvariante „Modern“
Eine zweite Interior-Palette als Override in `theme-b2in.css` (`.palette-modern .world-interior`).
Sie ändert **nur Flächen**, keine Logofarben:
| Token | Bisher (Creme) | Modern |
|---|---|---|
| Hintergrund | Creme `#f7f4ef` | fast weiß, leicht warm `#fbfaf8` |
| Karten | `#fbf9f5` | reinweiß |
| Sektionswechsel (`bg-accent`) | Sand `#d9cbbf` | helles Grau-Beige `#efece7` |
| Ruhige Flächen (`bg-muted`) | helles Sand | `#f2f0ed` |
| Linien / Eingabefelder | Sand-Töne | `#e6e2dc` / weiß |
| Hero-Bühne | Sand | helles Grau-Beige |
| Footer | Sand mit Tanne | Tanne mit Creme, Ocker als Akzent |
| Tanne, Ocker, Clay, dunkle Sektionen, CTA-Band | unverändert | unverändert |
Karten liegen jetzt weiß auf fast weiß, deshalb haben `--shadow-card` und `--shadow-elevated` in
der Variante etwas mehr Tiefe. Weil der Footer dunkel ist, trägt er in „Modern“ das **negative
Logo** (Rückmeldung Kevin 23.09.): der Footer rendert beide Logos (`footer-logo-light` /
`footer-logo-dark`), das Stylesheet blendet je Palette das passende ein – die Umschaltung läuft
im Browser, ohne Server-Roundtrip. Die Immobilien-Welt bleibt bei ihrem einen negativen Logo.
Zwei Nachbesserungen nach dem ersten Blick (Kevin 23.09.): Abschlussband und Footer sind in
„Modern“ beide dunkel, deshalb trennt eine helle Haarlinie (`--footer-muted` bei 35 %) den Footer
oben ab. Und der schwebende Termin-Button war als reine Kontur über Bildern und weißen Karten
kaum zu sehen – er trägt jetzt in beiden Paletten eine Fläche in Kartenfarbe
(`.appointment-toggle`), Hover bleibt Tanne.
Dritte Nachbesserung (Kevin 23.09.): der **Cookie-Button** unten links kam noch in Blau aus
`config/cookie-consent.php` – das ist die Immobilien-Welt. `theme-b2in.css` überschreibt die
Paketvariablen (`--cc-*`) für `.world-interior`: Button in Tanne mit dem Verlauf des
Akzent-Buttons, Icon, Schalter, Fokusring und Link-Hover in Ocker, „Speichern“ in Tanne,
„Akzeptieren“ bleibt grün. Gilt in beiden Paletten; `/immobilien` behält das Blau. Der dunkle Footer ist die eine bewusste Abweichung, die über
„heller“ hinausgeht: er gibt der hellen Seite unten einen klaren Abschluss. Wenn Marcel ihn
lieber hell hätte, sind es vier Token.
### Schalter
- `<x-palette-switch />` schwimmt links in der Mitte (unten links sitzt der Cookie-Button, unten
rechts das Termin-Widget): eine senkrechte Pille „Farbwelt · Bisher / Neu“. Nur auf den
Interior-Seiten von b2in, nicht auf `/immobilien`.
- Die Wahl liegt im `localStorage` (`b2in-palette`) und wird schon im `<head>` gesetzt, bevor das
Stylesheet greift – kein Aufblitzen in Creme. `?palette=modern` bzw. `?palette=classic` an einer
URL überschreibt die gemerkte Wahl; damit lässt sich Marcel ein Link auf die neue Variante
schicken: `https://<vorschau>/?palette=modern`.
- Abschalten, sobald entschieden ist: `B2IN_PALETTE_SWITCH=false` in `.env`
(`config('app.palette_switch')`). Dann erscheint weder Schalter noch Skript, die Seite läuft in
der Standard-Palette. Soll „Modern“ dauerhaft werden, werden die Token in `:root` übernommen und
der Override entfernt – ein Nachtrag, kein Umbau.
**Lokal ausgeführt:** `php artisan db:seed --class=WhatsappNumberSeeder` (2 Sprachen gesetzt),
`npm run build:b2in`. Beim Deployment: Abschnitt 8.2, Schritt 14, dazu `npm run build:web`.
**Tests:** `WhatsappNumberSeederTest` (6: anlegen, Platzhalter ersetzen, falsche Nummer vom 23.09.
korrigieren, gepflegte Nummer behalten, idempotent, Link auf der Seite), `AppointmentWidgetTest` (+2: WhatsApp vor dem Formular, keine
Trennzeile ohne Nummer), `ColorWorldsTest` (+6: Schalter auf Interior-Seiten, nicht auf
Immobilien, per Config abschaltbar, Override ohne Logofarben, negatives Logo im Footer der
Variante, Immobilien-Footer unverändert).
**Offen:** Marcel entscheidet zwischen „Bisher“ und „Neu“ – oder benennt, was an „Neu“ noch
fehlt (Footer hell/dunkel, Grad der Aufhellung). Danach Schalter abschalten.
## Nachtrag 19 · Otterpohl: Text v2 und erste Bilder (24.09.2026)
Zulieferung vom 24.09.: `dev/2026-08-28/otterpohl_Webseitentext_FINAL_v2.md` löst die lange Fassung
vom 17.09. komplett ab, dazu 13 Bilder in `dev/2026-08-28/images/otten/` (Titel plus 2–5 vom
Unternehmen, der Rest Referenzen).
### Text v2
Deutlich kürzer als v1: eine Einleitung, zwei Abschnitte, acht Leistungen, ein Schlussblock. Die
Fassung vom 17.09. mit zwölf Abschnitten (u. a. „Seit 2014 an unserer Seite“, „Farbe darf auch
genau die Farbe sein“) ist damit vollständig ersetzt. Zuordnung auf die Markenseite:
| Zulieferung v2 | Feld | Auf der Seite |
|---|---|---|
| „Ziemlich nah an der eierlegenden Wollmilchsau“ + 4 Absätze | `sections[0]`, Claim „Genau dort beginnt der individuelle Innenausbau.“ | erster Abschnitt |
| „Vom einzelnen Möbel bis zum ganzen Raum“ | `sections[1]`, Claim „So können wir Räume ganzheitlicher denken …“ | zweiter Abschnitt |
| „Planung trifft Handwerk“ | `sections[2]`, Claim „Das Ergebnis kann ein einzelnes Möbel sein. Oder ein kompletter Raum.“ | dritter Abschnitt |
| „Leistungen und Möglichkeiten“ (8 Punkte) | `services` | Leistungsraster, zwei Reihen à vier |
| „Das Ganze statt nur das Einzelne“ | `cta_title` / `cta_text` | Abschlussblock hinter Leistungen und Galerie – so bleibt die Reihenfolge der Zulieferung erhalten |
| „Nicht easy click & buy. Aber dennoch easy to get.“ | `tagline` und Ende des `cta_text` | Claim im Seitenkopf und Schlusssatz |
- **Name** jetzt „Otterpohl Möbelservice“ – so nennt die Zulieferung den Partner durchgängig.
Überall dort, wo der Name aus der Marke kommt (Kacheln, Widget-Anliegen, Herkunfts-Kachel).
Der Link im Handwerk-Abschnitt der anderen Markenseiten heißt weiter „Mehr zu Otterpohl“.
- **Kein „seit 2014“ mehr:** v2 erwähnt die gemeinsame Zeit nicht. Der Hinweis ist aus Teaser,
Fertigungsort (`origin_role`), Lang-Fallback und dem Handwerk-Abschnitt (`brand_craft`)
entfernt. ⚠️ Falls Marcel die Angabe bewusst behalten wollte, ist sie in drei Sätzen zurück.
- `description` bleibt leer, damit der Text in der Reihenfolge der Zulieferung steht; `summary`
(Markenübersicht) fasst die Einleitung zusammen; `highlights` sind drei Stichzeilen aus den
Leistungen. Englisch: eigene Übersetzung in gleicher Struktur.
- `widget_label` „Persönliche Beratung zur Maßarbeit vereinbaren“ bleibt.
### Bilder
13 Dateien, konvertiert wie in Nachtrag 16 (WebP, max. 1920 px, unter
`public/img/assets/marken/otterpohl/`, zusammen rund 2,4 MB; ein Bild per GD nachverdichtet).
- **Titelbild** `werkstatt-titel.webp`: zwei Mitarbeiter in der Werkstatt.
- **Aus der Werkstatt** (2–5): CNC-Bearbeitungszentrum, Türeinbau vor Ort, Handwerkzeug,
Anlieferung per Möbellift. Dafür gibt es eine **dritte Galerie-Kategorie** `company`
(`CmsBrand::GALLERY_COMPANY`, Badge „Aus der Werkstatt“). Im Admin: Button „Werkstatt“ und
Option „Aus der Werkstatt“ in der Kategorie-Auswahl, Hinweistext ergänzt.
- **Referenzen** (8, Kategorie `project`, Badge „Realisiertes Projekt“): Treppen, Lamellenwand mit
Sitzbank, Tür und Kommode, Wandregale, Objektbau-Verkaufsraum, Büro-Sideboard. Unterschriften
beschreiben das Motiv; die Kundennamen aus den Dateinamen (Lücke, Berkemeier, Neugebauer,
Dziuballe) stehen bewusst nicht auf der Seite. ⚠️ `Otterpohl_11.jpg` (Sideboard im Büro) ist
nach Kevins Angabe eine Referenz, nicht Werkstatt – falls es das eigene Büro ist, im Admin auf
„Aus der Werkstatt“ umstellen.
- **Reihenfolge in der Galerie:** Projekte, dann Werkstatt, dann Visualisierungen.
- **Überschrift der Galerie** richtet sich jetzt nach dem Inhalt: nur Visualisierungen →
„Inspirationen und Entwürfe“ (wie bisher, CABINET/Rotpunkt/Febrü); nur Projekte → „Referenzen“;
Projekte und Werkstatt → „Referenzen und Einblicke“ (Otterpohl); mit Visualisierungen →
„Projekte und Entwürfe“. Die Zeile darunter passt jeweils.
- ⚠️ **Referenzen-Seite:** `/referenzen` zeigt, solange im Modul „Referenzen“ nichts gepflegt ist,
die Projekte aus den Markengalerien – jetzt also die acht Otterpohl-Referenzen mit Link auf die
Markenseite. Werkstattbilder erscheinen dort nicht. Wenn Marcel das nicht will: Bilder im Admin
auf „Aus der Werkstatt“ stellen oder eigene Referenzen anlegen, dann greift der Fallback nicht.
**Lokal ausgeführt:** `php artisan db:seed --class=OtterpohlBrandSeeder`, `php artisan db:seed
--class=BrandImageSeeder`. Beim Deployment: Abschnitt 8.2, Schritte 11 und 12 (Texte
unverändert, sie decken v2 mit ab).
**Tests:** `FourthBrandTest` (v2-Überschriften, acht Leistungen, drei Abschnitte, Schlussblock hinter
den Leistungen, kein „Seit 2014“), `BrandFeedbackTest` (Seeder setzt Name, acht Leistungen,
Abschluss), `BrandImageSeederTest` (+3: Otterpohl-Galerie 8 Projekte + 4 Werkstatt ohne
Kundennamen, Badges und Überschrift „Referenzen und Einblicke“, Referenzen-Fallback zeigt Projekte
und keine Werkstattbilder), `BrandPageTest` (gemischte Galerie → „Projekte und Entwürfe“, Aufräum-
Seeder lässt Otterpohl 12).
**Offen:** Marcel prüft Unterschriften, die Zuordnung von `Otterpohl_11`, das gestrichene „seit
2014“ und ob die Otterpohl-Referenzen unter `/referenzen` erscheinen sollen.
## Nachtrag 19 · Entwurf der Startseite in der Farbwelt „Greige“ (24.09.2026)
Briefing von Marcel (`dev/2026-08-28/farbwelt-kunde-24-09-2026.md`, Visualisierung
`Farbwelt und Stil.png`): die Creme-Palette wird nicht weiterverfolgt, die Richtung „Neu“ soll
heller, klarer und hochwertiger werden – heller, leicht kühler Grundton (Greige, kein steriles
Weiß), deutlich präsenteres Tannengrün, Gold nur als feine Linie. Dazu eine Slideshow im Hero,
neue Überschrift, keine Aktionsleiste, ein Button im Hero, neuer Projekttext, Logo mit Goldbogen.
Kevins Auftrag: eine **separate Version der Startseite** zum Vergleich, das Logo als Kopie.
### Was gebaut ist
- **Entwurfsseite `/entwurf`** (Route `home.draft`, View `web/home-draft.blade.php`): die reguläre
Startseite mit neuem Hero, ohne Ankündigungsleiste, Body-Klasse `palette-greige`, `noindex`.
Alle übrigen Sektionen sind unverändert eingebunden und färben sich über die Tokens. Die Seite
hängt am Farb-Schalter: mit `B2IN_PALETTE_SWITCH=false` liefert sie 404.
- **Palette „Greige“** als drittes Token-Set in `theme-b2in.css` (`.world-interior.palette-greige`,
am Body, nicht am `<html>` wie der Browser-Schalter – so überlagern sich beide nicht):
| Token | Wert |
|---|---|
| Grund | `#f0eeeb` Greige, Off-White (Kopfzeile der Visualisierung) |
| Karten / Hero-Bühne | `#f8f7f4` |
| Sektionswechsel (`bg-accent`) | `#e8e6e0` |
| Tannengrün (`--primary`) | `#14342c`, Buttons, dunkle Sektionen, Footer |
| Gold (`--secondary`) | `#aa8341` gedeckt; als Text `--secondary-deep` (dunkler) |
| Text | Tannengrau `hsl(166 25% 13%)` |
| Footer | Tannengrün mit Greige-Text, Goldlinie oben, negatives Logo |
- **Logo-Kopien** `public/img/logos/b2in-interior-logo-gold-{positive,negative}.svg`: Bogen
`#AA8341` statt Ocker, Wortmarke im neuen Tannengrün `#14342C` (negativ: Schrift `#F0EEEB`).
Flächig, ohne Verlauf – die Verläufe liefert Marcel nach; die Dateien lassen sich dann 1:1
ersetzen. Die bisherigen Logos sind unangetastet. `ThemeHelper::getLogoPath()` liefert die
Gold-Variante, sobald `b2in_palette()` (neuer Helper, Binding `b2in.palette`) „greige“ meldet
– das setzt nur die Entwurfsroute. Kein PNG erzeugt (kein SVG-Rasterizer im Container).
- **Hero mit Slideshow** (`HeroSlider`, Sektion `home/hero_slider`, im Seitenbaum unter Startseite
mit Vorschau auf `/entwurf`): Rahmen mit 1-px-Goldkante und Schatten, tannengrüne Flanken rechts
(volle Höhe) und links unten wie in der Visualisierung, Eyebrow „B2IN INTERIOR ——“, Überschrift
„*Räume aus einer Hand.* Mit starken Marken und persönlichen Lösungen.“ (erste Zeile in Gold,
Serife), Einleitungstext aus der Visualisierung, **ein** Button „Unsere Marken entdecken“ mit
Goldkante unten und Pfeil. Kein zweiter Button (Briefing Punkt 5), das Termin-Widget bleibt.
- Bild rechts im festen Seitenverhältnis 4:3 → auf großen Bildschirmen nicht mehr unten
beschnitten (Briefing Punkt 2).
- Slides: Liste `slides` der Sektion (je `image`, `label`, `caption`, `active`), solange leer
die **Titelbilder der veröffentlichten Kernmarken** in ihrer Reihenfolge (aktuell CABINET,
Rotpunkt, Febrü, Otterpohl sofern Bild). Kleine Marken-Pille unten links im Bild.
- Ruhig: Überblendung 1,4 s, Wechsel alle 7 s (`interval`, begrenzt auf 3–30 s), Pause bei
Hover/Fokus, Pfeiltasten, Punkte unter dem Rahmen als Tabs; bei „reduzierter Bewegung“ kein
Autowechsel. Läuft ohne eigenes JS-Bundle (Alpine im Markup).
- **Farb-Schalter** hat jetzt drei Einträge: Bisher / Neu (Browser, wie gehabt) / **Entwurf**
(Link auf `/entwurf`). Auf dem Entwurf ist „Entwurf“ aktiv, „Bisher“/„Neu“ führen mit
`?palette=…` zurück auf die Startseite.
- **Projekttext** (Briefing Punkt 7) global: Lang de/en geändert, `AudienceSwitchProjectTextSeeder`
tauscht den Satz in einem vorhandenen CMS-Eintrag (nur diesen Satz, idempotent). Lokal
ausgeführt (2 Stellen). Deployment Schritt 15.
**Nachbesserung nach Kevins Screenshot (24.09., 17:30):** Die Goldkante war auf Greige nicht zu
sehen – jetzt ein Verlauf (hell oben links, auslaufend, in der Ecke wieder anziehend) über einen
maskierten Pseudo-Rand von 1,5 px. Die Flanken sind gerundet (Radius 2,75 rem rechts, 2 rem
links) und laufen hinter das Bild; ihre Goldlinie folgt der Rundung. Das Bild füllt den Rahmen
fast ganz (5:4, nur 1,25 rem Luft oben und unten, rechts über der Flanke), die Überschrift ist
kleiner (max. 3,3 rem statt 4,1 rem) und der Einleitungstext eine Stufe kleiner.
**Zweite Nachbesserung (25.09.):** Hinter der Goldkante schien eine helle Linie durch – die
Masken-Technik ließ die gerundete Kante des Rahmens außerhalb des Rings sichtbar. Jetzt sind
Rahmen und Bild-Rahmen echte Verlaufs-Borders (transparenter Rand, Fläche auf `padding-box`,
Goldverlauf auf `border-box`), ohne Pseudo-Element. Der Button trägt den Goldrand rundum, unten
3 px, der Pfeil ist Gold. Die grünen Flanken sind **Inline-SVGs**
(`components/web/hero-flank-right|left.blade.php`): grüner Verlauf, Goldlinie entlang der
Rundung, rechts zusätzlich die kurze Zierlinie aus der Visualisierung. Sie skalieren über die
Rahmenhöhe (`height: 100 %` bzw. `46 %`, Breite aus dem viewBox-Verhältnis, Strichstärke bleibt
1,5 px dank `vector-effect`), Farben aus den Tokens. Liefert Marcel eigene SVGs, werden die
beiden Dateien getauscht – der Platz im Rahmen bleibt.
**Abstimmung 25.09. (Kevin):** Für die nächste Runde ist der Farb-Schalter **aus** und die Seite
steht fest auf „Neu“. Dafür zwei neue Konfigurationswerte in `config/app.php`:
`B2IN_PALETTE_SWITCH` (Standard jetzt `false`), `B2IN_PALETTE` (Standard `modern`; ohne Schalter
setzt das Layout die Klasse `palette-modern` serverseitig an `<html>`, mit Schalter ist es die
Vorbelegung, solange nichts im localStorage liegt) und `B2IN_HOME_DRAFT` (Standard `true`) – der
Entwurf `/entwurf` hängt nicht mehr am Schalter, sondern an diesem Wert. Der Schalter selbst
bleibt im Code und lässt sich mit `B2IN_PALETTE_SWITCH=true` wieder einblenden.
### Was aus dem Briefing noch offen ist
| Punkt | Stand |
|---|---|
| 1 Slideshow selbst pflegen (Upload, Reihenfolge, an/aus) | Datenmodell steht (Liste `slides` mit `active`), Pflege bisher nur über den JSON-Editor unter Inhalte. Eigene Oberfläche wie bei den Displays: nächster Schritt nach Freigabe des Entwurfs. |
| 6 Persönliches Foto | Kein Foto geliefert. Vorschlag: in der Gründer-Leiste („B2in by Marcel Scheibe“), Bildfeld ist dort schon vorhanden (`founder_bar.image`). |
| 8 Feinabstimmung der Töne | Werte oben sind aus der Visualisierung gesampelt und leicht aufgehellt („noch etwas heller“). Marcel entscheidet am Entwurf. |
| 9 Logo mit Verläufen | Kommt von Marcel; bis dahin flache Gold-Kopie. |
| Entwurf → Standard | Wenn freigegeben: Tokens von `.palette-greige` nach `:root`, Gold-Logos umbenennen, `home.blade.php` auf `hero-slider` umstellen, Ankündigungsleiste global aus, Schalter und `/entwurf` abschalten. Ein Nachtrag, kein Umbau. |
**Lokal ausgeführt:** `php artisan db:seed --class=AudienceSwitchProjectTextSeeder`, `npm run build:b2in`.
Beim Deployment: Abschnitt 8.2, Schritt 15, dazu `npm run build:web`; die SVG-Logos liegen im
Repository. Link für Marcel: `https://<vorschau>/entwurf`.
**Tests:** `HomeDraftTest` (16: Palette und Gold-Logos nur auf dem Entwurf, Überschrift/ein
Button/keine Leiste, Sektionsreihenfolge, 404 ohne Schalter, Slides aus Marken, ohne Bild,
CMS-Slides mit `active`, Intervall-Grenzen, Schalter hin und zurück, Logo-Dateien, Tokens,
Projekttext in Lang und per Seeder), `CmsPageTreeTest` (Reihenfolge um `hero_slider` ergänzt).
Gesamt: 1049 grün.
---
## Nachtrag 20 · Korrekturen Startseite und Otterpohl (01.10.2026)
Farbwelt bleibt vorerst „Modern“ ohne Schalter. Umgesetzt auf der regulären Startseite `/`:
- **Hinweisleiste oben** aus über die Flag `B2IN_ANNOUNCEMENT_BAR` (`config('app.announcement_bar')`,
Standard `false`, gilt für beide Welten). Inhalt und CMS-Pflege bleiben, `=true` blendet sie wieder ein.
- **Hero-Überschrift** „<span class="text-secondary">Räume aus einer Hand.</span><br>Mit starken Marken
und persönlichen Lösungen.“ (erste Zeile in `.text-secondary`). Lang de/en und `HomeHeroTextSeeder`
für den CMS-Eintrag `home/hero` (ersetzt nur die alte Überschrift).
- **Zweiter Hero-Button** „Termin anfragen“ entfernt: aus Lang und CMS gelöscht; das Blade zeigt ihn nur
noch, wenn `cta2_text` gepflegt ist. Der feste Termin-Button unten rechts bleibt.
- **Projekttext**: „… Projektentwicklungen. Koordiniert aus einer Hand …“ (Punkt statt Gedankenstrich),
Lang de/en und `AudienceSwitchProjectTextSeeder` erweitert.
- **Otterpohl Titelbild**: `tittle_otter.png` → `marken/otterpohl/titel-treppe.webp` (1920 px, WebP 82).
Gleiches Motiv wie das Galeriebild `referenz-luecke-01` – daher echter Tausch: die Treppe wird Titelbild,
das bisherige Werkstattbild nimmt ihren Galerieplatz ein (Aus der Werkstatt). Nur wenn noch das alte
Titelbild gesetzt ist (`OtterpohlBrandSeeder::swapTitleImage()`).
- **Otterpohl Abschnitt „Montage“** als vierter Abschnitt. Neu für alle Markenabschnitte: Absätze mit
„- “ am Anfang werden zur Liste, der Teil vor dem ersten Doppelpunkt fett (`CmsBrand::paragraphBlocks()`)
– pflegbar im normalen Textfeld im Admin.
Lokal ausgeführt: die drei Seeder aus Schritt 16. Tests: `HomeCorrectionsTest` (neu), angepasst
`AnnouncementBarTest`, `HomePageStructureTest`, `HomeDraftTest`, `BrandImageSeederTest`,
`FourthBrandTest`, `AppointmentTriggerTest`, `CmsContentSeederTest`. Gesamt 1071 grün
(mit `B2IN_HOME_DRAFT=true`; die lokale `.env` hat den Entwurf aus, dann schlagen die `/entwurf`-Tests fehl).
---
## Seeder für das nächste Deployment (Stand 01.10.2026)
Alles, was seit dem letzten Commit (`3211569f`, Bilddauer im Display) neu ist: Nachträge 15–20.
Annahme: Der Stand bis zu diesem Commit ist live. Genau diese Seeder, in dieser Reihenfolge:
```bash
php artisan db:seed --class=CmsBrandSeeder --force # legt Otterpohl („tischlerei“) an, falls die Marke fehlt – überschreibt nichts
php artisan db:seed --class=BrandPageTextSeeder --force # Feedback 17.09. für CABINET, Rotpunkt, Febrü (überschreibt deren Textfelder)
php artisan db:seed --class=OtterpohlBrandSeeder --force # Otterpohl-Texte v2, Abschnitt „Montage“, Titelbild-Tausch
php artisan db:seed --class=BrandImageSeeder --force # Titelbilder und Galerien aller vier Marken
php artisan db:seed --class=DemoBrandGalleryCleanupSeeder --force # Demobilder raus – nach BrandImageSeeder
php artisan db:seed --class=WhatsappNumberSeeder --force # WhatsApp-Business-Nummer 0521-98620100
php artisan db:seed --class=AudienceSwitchProjectTextSeeder --force # Projekttext „… Projektentwicklungen. Koordiniert …“
php artisan db:seed --class=HomeHeroTextSeeder --force # Hero „Räume aus einer Hand.“, zweiter Button weg; 07.10.: Zeile „B2in Interior“, Untertitel „Wir vereinen …“, erste Slide „B2in Interior“
php artisan db:seed --class=UnusedCmsContentCleanupSeeder --force # tote CMS-Sektionen (inkl. Entwurfs-Hero home.hero_slider) + ungenutzte Felder (u. a. Markenliste in home.core_brands) löschen (sichert vorher nach storage/app/private/cms-archiv/)
php artisan db:seed --class=AboutPageTextSeeder --force # /about: „auch dafür geradesteht“ und Textkorrekturen (06.10.)
php artisan db:seed --class=ProofreadingCorrectionsSeeder --force # Lektorat aller angezeigten Texte (06.10.)
php artisan db:seed --class=LegalTextUpdateSeeder --force # Rechtstexte: DDG/MStV/TDDDG, GA4, Leistungsbeschreibung (06.10.)
php artisan db:seed --class=ReferencesPageSwitchSeeder --force # Referenzen ausblenden (Seite + Menüpunkt), nur wenn noch nicht geschaltet
php artisan db:seed --class=HomeHeroSlidesSeeder --force # Slideshow im Hero der Startseite (9 Bilder), nur wenn noch keine Slides gepflegt
php artisan db:seed --class=PageHeroEyebrowSeeder --force # Zeile über der Hero-Überschrift auf Marken, Über uns, Kontakt (07.10.); „Über B2in:“ aus der h1 auf /about
php artisan db:seed --class=HeroTrustItemsSeeder --force # Vertrauenspunkte im Hero: Startseite neu, Marken ergänzt (07.10.)
php artisan db:seed --class=HomeVisionTitleSeeder --force # „Gebaut auf Expertise und Vertrauen“: Hervorhebung in Ocker (07.10.)
php artisan db:seed --class=ContentCorrections20261008Seeder --force # Korrekturen 08.10.: Febrü-Slides, Vereine, Ablauf Maßanfertigung, Magazin, FAQ, Otterpohl
php artisan db:seed --class=CmsSectionFieldsSeeder --force # zuletzt: fehlende Felder aus der Lang-Datei in CMS-Einträgen ergänzen (überschreibt nichts)
php artisan db:seed --class=AssetMediaImportSeeder --force # ganz zuletzt: verwendete Bilder aus public/img/assets in die Mediathek (braucht php artisan storage:link)
```
Danach:
```bash
php artisan config:cache
php artisan view:clear
npm ci && npm run build:web && npm run build:portal
```
- Alle Seeder sind idempotent, ein zweiter Lauf schadet nicht.
- In der `.env` muss nichts ergänzt werden: Die Hinweisleiste ist standardmäßig aus. `B2IN_PALETTE_SWITCH`,
`B2IN_PALETTE` und `B2IN_HOME_DRAFT` werden nicht mehr gelesen (Nachtrag 24) und können aus der `.env` raus.
- Nicht nötig: `BrandLogoSeeder` und `BrandOriginSeeder` – Logo und Fertigungsort von Otterpohl setzt
bereits der `OtterpohlBrandSeeder`.
---
## Nachtrag 22 · Tote Inhalte entfernt, Lektorat aller angezeigten Texte (06.10.2026)
- **Aufgeräumt:** 48 Sektionen, die keine erreichbare Seite mehr zeigt (Netzwerk, Partner, Ecosystem,
Interior, alte Immobilien-Seite, Startseiten-Blöcke vor dem Relaunch, Führungsteam), aus Lang-Dateien,
`cms_section_map`, CMS-Datenbank (`UnusedCmsContentCleanupSeeder`, sichert vorher nach
`storage/app/private/cms-archiv/`) und Seedern. Dazu 15 alte Views, die Dev-Archivseiten
`/dev/{immobilien,interior,partner}-v1`, 19 Livewire-Sektionen, `NetzwerkPageCmsSeeder`,
`config/content.php` und ungenutzte Felder (`stats`, `social_media`, `load_more`, `share_article`) samt
auskommentiertem Markup. `partner_cta` liegt jetzt in der Gruppe `about`, die Gruppe `netzwerk` gibt es
nicht mehr. Lang-Sektionen und Section-Map decken sich jetzt 1:1 (Test).
- **Archiv:** `dev/2026-08-28/archiv-2026-10-06/` (README dort).
- **Lektorat:** `ProofreadingCorrectionsSeeder` (40 Stellen in CMS, Marken, Magazin, Rechtstexten),
dieselben Korrekturen in Lang-Dateien und Marken-Seedern, rund 50 Stellen in `immobilien-azizi.json`.
Offene inhaltliche Punkte: `dev/2026-08-28/lektorat-rueckfragen-06-10-2026.md`.
- **/about:** Kennzahlen im Abschluss entfernt (wurden nie angezeigt), Alternativtext der Bildpause
beschreibt jetzt das Bild.
Deployment: siehe Seeder-Liste oben (`UnusedCmsContentCleanupSeeder`, `AboutPageTextSeeder`,
`ProofreadingCorrectionsSeeder`). Tests: 1072 grün (mit `B2IN_HOME_DRAFT=true`).
**Nachgang 06.10. (Kevin):** Kachel „Datenstand“ samt `confidence_labels`/`data_confidence` und die
internen Prüfnotizen auf /immobilien entfernt; „unsere Tischlerei“ → „die Tischlerei Otterpohl“
(Herkunfts-Sektion, über `ProofreadingCorrectionsSeeder`); Rechtstexte aktualisiert
(`LegalTextUpdateSeeder` + `resources/lang/de/b2in_legal.php`, Banner-Texte des Cookie-Pakets per
`resources/lang/vendor/cookie-consent/de/`). Immobilien-Daten bleiben wie sie sind.
**Referenzen-Schalter (06.10.):** Im CMS-Modul „Referenzen“ gibt es oben den Schalter „Seite ‚Referenzen‘
auf der Website anzeigen“ (Einstellung `settings` → `pages.referenzen_enabled`, Helper
`references_page_enabled()`). Aus: Menüpunkt weg, /referenzen liefert 404, der Button unter dem
Showroom-Feed entfällt, sofern er auf /referenzen zeigt. Die Referenzen selbst bleiben gepflegt.
`ReferencesPageSwitchSeeder` schaltet beim Deployment aus (nur ohne bestehenden Eintrag).
---
## Nachtrag 23 · Slideshow im Hero der Startseite (07.10.2026)
Die Slideshow aus dem Entwurf (`/entwurf`) läuft jetzt im Hero der aktiven Startseite („Modern“). Das
Layout bleibt: Text links, rechts statt des festen Bildes die Slideshow, Infokarte unten links, kleine
Marken-Pille oben links, Punkte unter dem Bild.
- **Bilder:** `dev/2026-08-28/images/slideshow/` (9 JPEGs, 1,7–5,7 MB) → `public/img/assets/b2in/slideshow/*.webp`
(1920 px, Qualität 82, ohne Metadaten, 86–265 KB). Reihenfolge wie geliefert: Rotpunkt Fenix House,
CABINET Ankleide, Rotpunkt Küche, CABINET Schlafzimmer, Rotpunkt Küche, CABINET Flur, Rotpunkt Küche,
CABINET Dachschräge, Rotpunkt Bad.
- **Pflege im CMS:** Seiten → Startseite → Hero, Liste `slides` (je `image`, `label`, `alt`, `active`), Reihenfolge
der Liste = Reihenfolge der Slideshow, `interval` in Sekunden (3–30, Standard 7). Ohne aktive Slides
steht wieder das einzelne Bild `image`.
- **Verhalten:** Überblendung 1,4 s, Pause bei Hover/Fokus, Pfeiltasten, bei „reduzierter Bewegung“ kein
Autowechsel; erstes Bild sofort, die übrigen lazy. Festes Seitenverhältnis 4:3, damit nichts unten
abgeschnitten wird.
- **Code:** `Hero` (Livewire) mit `activeSlides()`, Blade `sections/hero.blade.php`, Lang de/en
`hero.slides`/`hero.interval`, `HomeHeroSlidesSeeder` für bestehende Installationen. Tests:
`HomeHeroSlideshowTest`. `npm run build:b2in` (neue Utility-Klassen).
**Nachbesserung 07.10. (nach „Farbwelt und Stil.png“):** Die Slideshow ist jetzt das Hauptelement des
Rahmens: zweispaltig (Text 5/12, Bild 7/12), das Bild reicht oben, unten und rechts bis auf 16 px an den
Rand, auf großen Bildschirmen mindestens 560 px hoch (mobil 4:3 unter dem Text). Die Punkte stehen mittig
unter dem ganzen Block. Die Infokarte über dem Bild bleibt und wechselt mit: Marke (`label`) und Motiv
(neues Feld `caption`), ohne Angaben die festen `card_title`/`card_text`. Die Marken-Pille oben links
entfällt. `HomeHeroSlidesSeeder` ergänzt fehlende `caption` bei bereits eingetragenen Slides.
**Nachbesserung 07.10. (2):** Rahmenschatten `rgba(0, 0, 0, 0.1) 0px 10px 20px 5px`. Die Infokarte
saß oben im Bild, weil die Glass-Flow-Regel `[class*="card"]` ihr `position: relative` gab – jetzt
`absolute!`. Der Kartentext überblendet nicht mehr gleichzeitig: der alte blendet in 0,3 s aus, der neue
danach in 0,5 s ein.
---
## Nachtrag 24 · Alte Entwürfe entfernt (07.10.2026)
Mit der Slideshow im Hero und der festen Palette „Modern“ sind die Abstimmungsstände erledigt. Entfernt,
Kopien unter `dev/2026-08-28/archiv-2026-10-07-entwurf/`:
- **Entwurf `/entwurf`** (Nachtrag 19): Route, View `home-draft`, Farbwelt „Greige“, Gold-Logos,
Hero mit Goldkante und Flanken (`hero-flank-*`), CSS `.hero-draft`, CMS-Sektion `home.hero_slider`
(Seitenbaum, Section-Map, Lang de/en; auf dem Server löscht sie der `UnusedCmsContentCleanupSeeder`).
- **Farb-Schalter und Palette „Bisher“ (Creme)** (Nachtrag 18): Komponente `palette-switch`, Skript im
`<head>`, Klasse `palette-modern` am `<html>`, `b2in_palette()`, Config `app.palette_switch`,
`app.palette`, `app.home_draft`. Die hellen Flächen hängen jetzt fest an `.world-interior`.
- **HeroSlider** bleibt, weil b2a, Stileigentum und Style2own ihn einbinden – zurück auf den Stand vor
dem Entwurf. Ohne gepflegte Sektion zeigt er nichts statt eines Fehlers (deren Inhalte lagen in
`config/content.php`, siehe Archiv vom 06.10.).
Tests laufen jetzt ohne `B2IN_HOME_DRAFT=true`.
**Nachbesserung 07.10. (3):** Kartentexte überlagerten sich – Alpine ließ die fest gesetzten Klassen
stehen; der Zustand hängt jetzt an `data-active`. Über der Überschrift steht „B2IN INTERIOR“ mit
Ockerlinie (neues Feld `eyebrow`), Untertitel „Wir vereinen CABINET, Rotpunkt Küchen, Febrü und ein
Netzwerk regionaler Experten …“, das erste Bild trägt in der Karte „B2in Interior“. Auf dem Server
setzt das der `HomeHeroTextSeeder`.
**Nachbesserung 07.10. (4):** Gründer-Leiste auf der Startseite auf `bg-accent` (Verlauf Accent/Accent-dark
50 %). Der Hero-Stil der Startseite gilt jetzt auch für Marken, Über uns und Kontakt: Klasse `hero-frame`
(Goldverlauf, Hintergrund `linear-gradient(to bottom right, hsl(40, 5.65%, 95.65%), hsl(40, 15.95%, 93.9%))`,
nur in der Interior-Welt), weißer 1-px-Rand an den Bildern (`border-white!` – die globale Regel
`* { border-color }` in shared-styles.css schlägt sonst die Utility), Zeile über der Überschrift als
Komponente `x-web.hero-eyebrow` (Felder `eyebrow`; Kontakt: `hero.eyebrow`), im CMS ergänzt durch
`PageHeroEyebrowSeeder`.
**Nachbesserung 07.10. (5):** Breadcrumb im Seitenkopf entfernt, Heroes oben/unten mit weniger
Abstand (lg 48 px). Vertrauenspunkte als Komponente `x-web.hero-trust` (Feld `stats`): Startseite
„Deutsche Manufakturen“, „Planung bis Montage“, „Zwei Showrooms in Bielefeld“; Marken „Maßarbeit vom
Handwerk“, „Werk und Ausstellung erlebbar“, „Küche, Einbau, Arbeitsplatz“ (`HeroTrustItemsSeeder`).
**Nachbesserung 07.10. (6):** Gründer-Leiste auch unter dem Hero von /marken, auf /about und /marken
wie auf der Startseite mit `bg-accent`.
**Nachtrag 25 · Aufräumen (07.10.2026):** Sicherungskopien (`sections_bak/`, `setup-wizard_bak`), doppelte
`AboutHero`-Klasse, ungenutzte Layouts und Starter-Vorlagen, Bildkopien, macOS-Metadaten, Bild
„Integriertes Modell“ und die Dubai-Karte nach `dev/2026-08-28/archiv-2026-10-07-aufraeumen/`. Dazu `/welcome` (Demo-Seite) und 118 ungenutzte
Bilder der ruhenden Domains b2a, Stileigentum und Style2own; die Domains selbst bleiben, `/theme-demo` bleibt.
**Nachtrag 26 · Test-Routen nur lokal (07.10.2026):** `routes/test.php` (E-Mail-Vorschauen mit echten Namen
und Adressen, Login-Tests) lädt nur noch bei `APP_ENV=local`. Live liefern `/test/emails/…`, `/test-login`,
`/login-simple`, `/debug-login` und `/simple-test` im Portal 404. Auf dem Server `php artisan route:cache` neu bauen,
falls Routen gecacht sind.
**Nachbesserung 07.10. (7):** /marken: Bildpause `marken.image_break` (Section `id="bild"` wie auf /about)
zwischen Markenblöcken und „Aus der Region gefertigt“, Bild = letztes Slideshow-Bild (Rotpunkt Bad), Zitat
Marcel Scheibe „Hinter jeder unserer Marken stehe ich persönlich. Ich kenne die Werke und die Menschen, die dort arbeiten.“
Kein Seeder nötig – ohne CMS-Eintrag gilt die Lang-Datei; im Seitenbaum unter Marken → Bildpause pflegbar.
**Nachbesserung 07.10. (8):** Startseite „Gebaut auf Expertise und Vertrauen“: Fläche `bg-secondary`
(Verlauf Ocker → Ocker dunkel), Inhalt in weißer Box mit Schatten, Porträt klein (4/12, 4:5) – Parameter
`boxed` an `vision-section`, die anderen Themes bleiben offen. Bildunterschrift mit `absolute!` (Glass-Flow-Regel).
**Nachbesserung 07.10. (9):** „Expertise und Vertrauen“ in Ocker (`HomeVisionTitleSeeder`; in der weißen Box
gilt wieder Ocker statt der Tanne-Regel für Ockerflächen). „Aktuelles aus unserem Showroom“ in weißer Box;
„Größer ansehen“ öffnet das Display als Overlay über die volle Fensterhöhe (9:16), dort „Vollbild“ per
Fullscreen-API, wo der Browser es kann (nicht auf dem iPhone). Das große Display lädt erst beim Öffnen.
**Nachbesserung 07.10. (10):** Hero-Slideshow: feine Laufzeit-Linie unten im Bild (füllt sich bis zum
Wechsel, hält bei Hover/Fokus an; Zähler über requestAnimationFrame statt setInterval). Punkte mit
unsichtbarer Klickfläche (py-4) um den Balken, ohne Glass-Flow-Button-Schatten.
**Nachtrag 27 · CMS-Durchsicht (07.10.2026):** Section-Map, Seitenbaum, Datenbank und Lang-Datei decken sich
(41 Sektionen; Rechtstexte und Kontaktdaten laufen über eigene Helfer). Sections ohne DB-Eintrag (Termin-Widget,
Marken-Bildpause, Handwerk-Abschluss) lassen sich im Seitenbaum mit „+“ anlegen. Korrigiert: Markenliste
`brands` in „Kernmarken“ war im CMS ohne Wirkung (Kacheln aus der Markenverwaltung) – entfernt, Fallback liest
die Lang-Datei direkt. „Zur Marke“ in „Aus der Region gefertigt“ fehlte im CMS-Eintrag – `CmsSectionFieldsSeeder`
ergänzt fehlende Felder generell. Beschreibungen im Seitenbaum aktualisiert (eyebrow, stats, Bildpause, Vision).
Marken, Magazin, Projekte und Referenzen: alle Felder im Admin bearbeitbar. Alle 135 Bildpfade vorhanden.
**Nachtrag 28 · Admin-Bildvorschauen (07.10.2026):** `media_url()` verwies für Bilder außerhalb der Mediathek
auf das nicht existierende `assets/images/` (404 in Marken, Magazin, Projekten, Referenzen) – jetzt
`img/assets/` wie `theme_image_url()`. Ungenutzte `cms_media_url()` entfernt. Alle 80 Bilder der
Admin-Ansichten lokal geprüft.
**Nachtrag 29 · Bilder in der Mediathek (07.10.2026):** `AssetMediaImportSeeder` übernimmt die 82 Bilder, die
Marken, Magazin und CMS aus `public/img/assets/` verwenden, in die Mediathek (Sammlungen Marken, Magazin,
Website, mit Vorschaubild). Name = bisheriger Pfad, daher bleiben alle Verweise gültig. Die Website liefert weiter aus
`public/img/assets/` (WebP-Fassungen), der Admin zeigt die Mediathek-Kopie. Auf dem Server muss `storage:link` bestehen (sonst liefern Mediathek-Bilder 404).
**Nachtrag 30 · Beschreibungen im Seitenbaum (07.10.2026):** alle Beschreibungen gegen die Seiten geprüft und
korrigiert – u. a. Hinweisleiste (über B2IN_ANNOUNCEMENT_BAR aus, Feld „enabled“), Abschluss „Termin anfragen“
(nur Startseite und /referenzen), Fußbereich (Claim, byline, Links – kein Social Media), Showroom-Display
(display_id, „Größer ansehen“), Hero (Ersatzkarte/-bild), Geschichte (Zeitleiste), „AGB“ → Nutzungsbedingungen.
## Nachtrag 31 · Korrekturen vom 08.10.2026 (`Korrekturen-2026-10-08.md`)
- **Slideshow:** Febrü „Active 2.0 Büro“ an Position 4, „Elements Soul Großraum“ an Position 9 (WebP 1920 px).
- **Regional verbunden:** Arminia, TC Bad Salzuflen, TC Hiddenhausen (Logo war schon da, hieß „TC Hövelhof“),
TC Blau Weiss Halle – einheitliche Kurztexte, Kachel verlinkt auf die Vereinsseite, wo eine URL gepflegt ist
(neues Feld `url`, neuer Tab). TC Bad Waldliesborn-Horn raus, alte Logos im Archiv `archiv-2026-10-08/`.
- **Ablauf Maßanfertigung überall gleich:** Foto / Grundriss + grobe Maße → Beratung → Planung & Angebot →
Angebotsannahme → Anzahlung → professionelles Aufmaß → Feinplanung / Fertigungsfreigabe → Bestellung / Fertigung.
Angepasst: CABINET (Ablauf, „Aufmaß vor Ort“), Herkunft (Ablauf am Beispiel CABINET), Marken-Netzwerk
(„Planung & Aufmaß“), Über uns (Zitat), FAQ (Beratung, Kosten).
- **Magazin:** Maßanfertigungs-Beitrag neu (Text von Marcel); Beitragsseite kann jetzt Absätze (Leerzeile im Text);
Abschluss „Weitere Einblicke rund um Einrichtung, Planung und Projekte …“; „Supply-Chain“ und „Local for Local“
offline; Datum und Lesezeit deutsch („26. August 2026 · 5 Min. Lesezeit“).
- **FAQ Projektausstattung** nach heutiger Positionierung (ein Vertragspartner, Koordination, Netzwerk) ohne
Garantie-/Eskalationssprache. **Otterpohl:** „Das erhöht die Planungssicherheit deutlich.“
- Gemeinsamer Ersetzungs-Baustein `Database\Seeders\Concerns\ReplacesCmsTexts` (auch vom Lektorats-Seeder genutzt).
**Nachbesserung 08.10.:** Hero mobil – die Infokarte steht unter dem Bild (Bild 4:3 oben, Karte als weißer
Streifen darunter), ab lg schwebt sie wie bisher unten links. Karte weiß statt `bg-card`, damit die
Glass-Flow-Regel `[class*="card"]` nicht greift; `absolute!` entfällt.
---
## Nachtrag 32 · Git-Deploy, Monitoring und Ablauf für den Livegang (08.10.2026)
- **Git-Deploy per Tag:** `dev/betrieb/deploy.sh` (aus `dev/git-deploy-monitoring/`) liefert nur Tags aus,
`origin` = forge.adametz.media, `NAS` = Sicherung. `public/build` ist jetzt versioniert. Neu:
`--seed=<Klasse>` (Seeder im Wartungsmodus) und `--initial` (Vorschau und Live einmalig an Ort und
Stelle auf Git umstellen, mit Code-Sicherung und Parken der Nur-Server-Dateien).
`.gitignore`: Das macOS-Muster `Icon` traf `resources/views/flux/icon/`, die vier Icons wären mit
Git-Deploy auf Live verloren gegangen; das Muster heißt jetzt `Icon[\r]`.
- **Monitoring:** GlitchTip (Server und Browser, Website und Portal), `/health` mit Token (jede Domain,
ohne Basic Auth), Scheduler-Herzschlag, Alarme bei fehlgeschlagenen Tasks per Mail und ntfy,
Version in `php artisan about` und `/health`. Pakete: `sentry/sentry-laravel`, `@sentry/browser`.
- **Livegang ohne Datenmigration:** Die Live-Datenbank bleibt die Grundlage (Displays, Playlists,
Mediathek, Benutzer), `InteriorReleaseSeeder` bringt die Website-Inhalte in einem Lauf auf den neuen
Stand. Er fasst alle Seeder aus 8.2 und „Seeder für das nächste Deployment“ zusammen, ist idempotent
(Test) und lässt Display-Daten unberührt. Ausnahme: Branding, nur bei Modulen ohne Interior-Logo;
`DisplayInteriorBrandingSeeder` überschreibt seit heute keinen inzwischen gepflegten Claim mehr.
Generalprobe: `dev/betrieb/vorschau-auffrischen.sh` kopiert Live-DB und Live-Medien in die Vorschau.
- **Ablauf:** `dev/betrieb/README.md`. Er ersetzt für den Livegang die Einzelbefehle aus 8.2 und die
Seeder-Liste oben. Die Prüfliste 8.3 gilt weiter.
- **Offen:** Datenschutzerklärung um die Fehlerprotokollierung ergänzen, bevor die DSNs auf Live gesetzt
werden (Textvorschlag in `dev/betrieb/README.md`, Abschnitt 6).