Nach dem Build stand der Abschluss hellgrau mit weisser Schrift da.
`.variante-glass-flow section` faerbt jede Sektion in diesem Wrapper hell ein
und ist mit (0,1,1) spezifischer als `.cta-band` mit (0,1,0). Fuer
`.bg-secondary` und `.bg-accent` gibt es dort laengst Ausnahmen – die neue
Klasse brauchte dieselbe. /marken/{slug} haengt in genau diesem Wrapper.
Der Verlauf steht jetzt in --cta-band-gradient und wird von beiden Regeln
benutzt, damit er nicht doppelt gepflegt werden muss. Ein Test haelt die
Ausnahme fest.
Nebenbei dokumentiert: `npm run build:web` baut nur die vier Frontend-Themes,
nicht das Portal. Ohne `npm run build:portal` liefert das Admin-Portal 500 –
in Deployment-Checkliste und Vorschau-Runbook ergaenzt, samt Pruefschleife
ueber alle fuenf Manifeste.
Tests: 863 gruen (3194 Assertions), Pint sauber, alle Themes gebaut.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1269 lines
73 KiB
Markdown
1269 lines
73 KiB
Markdown
# 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 13 – **AP0–AP6 umgesetzt**, Seite abgestimmt, Markentexte Rotpunkt und Febrü
|
||
eingearbeitet. 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`: **863 grün** (3194 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" | ✅ |
|
||
|
||
### 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 |
|
||
|---|---|
|
||
| Logos und Stimmungsbilder der drei Marken | 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 | 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
|
||
|
||
**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.
|
||
|
||
#### 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.
|
||
|
||
`php artisan test --compact`: **863 grün** (3194 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 19 in AP1 geänderten Sektionen,
|
||
# 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
|
||
|
||
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
|
||
ausführlichem Text, aufklappbaren Vertiefungen und eigenem Abschlussblock
|
||
- [ ] 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
|
||
- [ ] **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.
|