**Datum:** 12. Mai 2026 **Status:** Technisches Implementierungs-Konzept **Tech-Stack:** Laravel 12+, Livewire 4 / Volt, Tailwind CSS (v4), Alpine.js (über Livewire) **Bezug:** Konzept-Update 3 (Multi-Brand-Architektur), Konzept-Update 4 (Positionierung), Brand-Landing-Konzept businessportal24 > **IST-Stand 21.05.2026**: Multi-Brand-Architektur ist umgesetzt > (`config/domains.php`, `ThemeServiceProvider`, getrennte Vite-Builds > `portal` + `web`). Die Hub-Migration des User Backends ist als > eigene Roadmap in `dev/frontend/hub-flux/` dokumentiert (Phasen 0–7 > abgeschlossen, Phase 8 in Planung). Der hier beschriebene Brand-Context > wird ueber `View::share()` global aufgeloest. --- ## 1. Leitprinzipien Vier Regeln, an denen sich jede technische Entscheidung in diesem Dokument messen muss: 1. **Ein Codebase, viele Brands.** Kein Branch pro Portal, keine duplizierten Views. Differenzierung über Konfiguration, CSS-Variablen und gezielte View-Overrides. 2. **Brand-Awareness zentral aufgelöst, nicht in Komponenten verteilt.** Eine Komponente fragt nicht „bin ich auf businessportal24?". Sie konsumiert eine `$brand`-Context-Variable und rendert entsprechend. 3. **Livewire/Volt nur wo nötig.** Statische Komponenten bleiben pures Blade. Reaktivität ist ein Kostenfaktor (Server-Roundtrips, Hydration, State-Management) – sie muss verdient werden. 4. **Solo-tauglich heißt: jede Entscheidung muss in 6 Monaten noch verständlich sein.** Lieber explizit als clever. ## 2. Brand-Auflösung (Multi-Tenant-Pattern) ### Brand-Resolution-Pipeline ``` Request → Middleware → BrandResolver → Brand-Context im Container → View-Pfad-Override → Config-Override → Layout-Auswahl ``` **Schritt 1: Domain-Mapping** Die `brands`-Tabelle aus Update 3 enthält pro Brand mindestens: - `slug` (z.B. `businessportal24`, `presseecho`, `hub`) - `primary_domain` (z.B. `businessportal24.com`) - `theme_key` (z.B. `bp24`, `pe`) – Verweis auf CSS-Token-Set - `config_path` (z.B. `brands/businessportal24.php`) - `is_publisher_hub` (boolean) -ist zu prüfen, teils schon im System angelegt! **Schritt 2: Middleware** ```php // app/Http/Middleware/ResolveBrand.php public function handle(Request $request, Closure $next): Response { $brand = Cache::rememberForever( "brand.domain.{$request->getHost()}", fn() => Brand::query() ->where('primary_domain', $request->getHost()) ->orWhereJsonContains('aliases', $request->getHost()) ->firstOrFail() ); app()->instance(Brand::class, $brand); View::share('brand', $brand); Config::set('brand', $brand->config()); return $next($request); } ``` Cache ist hier wichtig – die Domain-zu-Brand-Auflösung passiert bei jedem Request. `rememberForever` mit explizitem Cache-Bust beim Brand-Update. -ist zu prüfen, teils schon im System angelegt! **Schritt 3: Brand im Container** Jede Klasse kann via Dependency Injection auf die aktuelle Brand zugreifen: ```php public function __construct(private Brand $brand) {} ``` In Blade-Templates ist `$brand` durch `View::share()` direkt verfügbar. ### Lokale Entwicklung Lokal arbeiten mit `.test`-Domains in Docker (devserver) auf dem Server via Treafik: - `businessportal24.test` - `presseecho.test` - `pressekonto.test` Alle zeigen auf dieselbe Codebase, die Middleware löst per Hostname auf. Keine Subdomains, keine Port-Tricks – schmerzfreies lokales Multi-Brand-Setup. ## 3. Theming-System (Tailwind v4 + CSS Custom Properties) ### Empfehlung: Tailwind v4 Falls die Migration auf v4 noch offen ist: **jetzt machen**. Die `@theme`-Direktive in v4 macht Multi-Brand-Theming dramatisch einfacher als das v3-Config-Konstrukt. Native CSS-Variablen, keine PostCSS-Akrobatik mehr. ### Token-Architektur Drei Ebenen (Beispiel ): ```css /* Ebene 1: Globale Design-Tokens (markenneutral) */ @theme { --font-serif: 'Source Serif 4', Georgia, serif; --font-sans: 'Inter', system-ui, sans-serif; --spacing-section: 5rem; --spacing-section-tight: 3rem; --radius-card: 2px; /* fast keine Rundungen, editorial */ } /* Ebene 2: Semantische Tokens (markenneutral, aber rollenbasiert) */ :root { --color-text-primary: var(--brand-text); --color-text-muted: var(--brand-text-muted); --color-surface: var(--brand-surface); --color-accent: var(--brand-accent); --color-cta-bg: var(--brand-cta-bg); --color-cta-fg: var(--brand-cta-fg); --color-hub-transition: var(--brand-hub-bg); } /* Ebene 3: Brand-spezifische Werte */ [data-brand="businessportal24"] { --brand-text: #1a1a1a; --brand-text-muted: #6b6b6b; --brand-surface: #fafaf7; /* warmer off-white */ --brand-accent: #d94e1f; /* gedämpftes Orange */ --brand-cta-bg: #d94e1f; --brand-cta-fg: #ffffff; --brand-hub-bg: #1a2540; /* dunkelblau, Störer */ } [data-brand="presseecho"] { --brand-text: #f0f0e8; --brand-text-muted: #a0a098; --brand-surface: #1f2620; /* dunkelgrün-anthrazit */ --brand-accent: #5a8a6b; /* gedämpftes Grün */ --brand-cta-bg: #5a8a6b; --brand-cta-fg: #ffffff; --brand-hub-bg: #1a2540; /* Hub-Farbe bleibt konstant! */ } ``` ### Brand-Aktivierung im Layout ```blade {{-- resources/views/layouts/brand.blade.php --}} @vite(['resources/css/app.css', 'resources/js/app.js']) {{-- Brand-CSS wird im app.css via @import geladen, oder optional separat: --}} @if($brand->has_custom_css) slug}.css") }}"> @endif {{ $slot }} ``` **Wichtig:** Der `data-brand`-Attribut auf `` ist der einzige Hebel, der den gesamten Look umschaltet. Alle Tailwind-Utilities, die brand-spezifische Werte nutzen, greifen über CSS-Variablen darauf zu. ### Pragmatische Tailwind-Nutzung Die Komponenten schreiben **nicht** `bg-orange-600` (das wäre brand-spezifisch im Markup festgenagelt). Stattdessen: ```blade ``` Oder noch sauberer mit eigenen Tailwind-Utility-Klassen, die in `app.css` definiert werden: ```css @layer components { .btn-cta { @apply bg-[var(--color-cta-bg)] text-[var(--color-cta-fg)] px-6 py-3 rounded-sm font-medium hover:opacity-90 transition; } .btn-hub { @apply bg-[var(--color-hub-transition)] text-white px-8 py-6 block; } } ``` So bleibt das Markup brand-agnostisch und die Stilfragen zentralisiert. ## 4. Komponenten-Hierarchie und Engine-Wahl ### Drei Render-Modi, drei Anwendungsbereiche |Modus|Wann verwenden|Performance|Beispiele| |---|---|---|---| |**Blade Component**|Statisches Markup, keine Interaktion|⚡⚡⚡|TopBar, Footer, PressItem, StatsRow| |**Volt (Single-File)**|Lokaler State, einfache Reaktivität, Lifecycle einfach|⚡⚡|AdHocTicker, HeroSlider, Search| |**Klassisches Livewire**|Komplexe Komponenten mit Services, Events, mehrere Methoden|⚡|PressEditor, NewsroomDashboard| ### Volt: konkrete Empfehlung Volt ist für dieses Projekt **die richtige Wahl als Default für reaktive Komponenten** – aber nicht für statische. Die Gründe: **Pro Volt:** - Single-File-Komponenten: PHP-Logik + Blade-Template + Tailwind-Klassen in einer Datei. Solo-Entwickler-Freundlichkeit ist hoch. - Funktionale API ist deutlich weniger Boilerplate als klassische Livewire-Klassen. - Volt-Komponenten lassen sich genau wie Livewire-Komponenten lazy-laden (``), was für Above-the-fold-Performance wichtig ist. **Kontra Volt:** - Für reine Display-Komponenten ist Volt overkill. Eine `` ohne State soll keine Livewire-Komponente sein – Hydration und Wire-Tracking sind unnötige Kosten. - Wenn eine Komponente Services injiziert, ein eigenes Test-Setup braucht oder mehr als ~150 Zeilen wächst, ist eine klassische Livewire-Klasse besser strukturierbar. **Faustregel:** > Renderst du HTML ohne Server-Interaktion? → Blade Component. Brauchst du `wire:model`, `wire:click`, Polling oder reaktiven State? → Volt. Wird die Komponente komplex, hat Services, eigene Tests? → Klassisches Livewire. ### Konkrete Komponenten-Inventur aus den Screens Aufgeschlüsselt nach Engine. Das ist die direkte Übersetzung der Screens in technische Bausteine: #### Blade-Komponenten (statisch, hochfrequent wiederverwendet) ``` -- Wirtschafts-Ticker, Sprachen, Newsletter/RSS -- Logo, Suche, CTAs -- Hauptnavigation (Wirtschaft, Tech, Finanzen...) -- Footer mit Cross-Brand-Hinweis -- Standard-Listen-Eintrag mit Slots für Varianten -- Große Hero-Variante -- Kompakte Sidebar-Variante (mit Nummerierung) -- Quelle · Zeit · Lesezeit (wiederverwendbar) -- "§ 01" + Label + H2 (das Editorial-Pattern) -- Drei-/Vier-Spalten-Statistik -- CTA-Button mit Varianten (primary, secondary, hub) -- Branchen-Marker, "Geprüft"-Label -- DER dunkelblaue Störer (siehe Briefing) -- Inline Hub-CTA (Variante des Störers) -- "Alle Pressemitteilungen werden geprüft..." ``` #### Volt-Komponenten (reaktiv, isolierter State) ``` -- Auto-refresh alle 30s, Polling -- 3 Top-Meldungen, auto-rotate mit Alpine -- Suche im Header -- "Alle · Heute · Diese Woche" Tabs -- Newsroom-Sidebar mit "heute aktiv" Polling -- Live-Werte mit ± Indikatoren -- Termine-Karussell mit Wochen-Navigation ``` #### Klassisches Livewire (komplex, services) ``` PressSubmissionForm -- Mehrstufige Einreichung (auf Hub) NewsroomManager -- Profil-Verwaltung (auf Hub) AdminReviewQueue -- Redaktions-Tool (auf Hub) ``` Auffällig: die **Brand-Portale brauchen kaum klassisches Livewire**. Das ist konsistent zur Architektur aus Update 3 – Brand-Portale sind primär Lese-Oberflächen, der State liegt im Hub. ## 5. Brand-Differenzierung in Komponenten Drei Mechanismen, in aufsteigender Eingriffstiefe: ### 5a. Konfiguration über Brand-Config Der einfachste Fall: eine Komponente verhält sich anders je nach Brand-Konfiguration. (Beispiel) siehe: config/domains.php ```php return [ 'name' => 'businessportal24', 'tagline' => 'Pressemitteilungen · DACH', 'press_item_layout' => 'timeline', // vs. 'topic' 'show_market_ticker' => true, 'show_branchen_index' => true, 'hero_variant' => 'top-meldung', // vs. 'topic-cluster' 'rubriken' => ['Wirtschaft', 'Technologie', /* ... */], ]; return [ 'name' => 'presseecho', 'tagline' => 'Branchen-Pressearchiv', 'press_item_layout' => 'topic', 'show_market_ticker' => false, 'show_branchen_index' => false, 'hero_variant' => 'topic-cluster', 'rubriken' => [/* andere Reihenfolge, andere Schwerpunkte */], ]; ``` Komponenten lesen daraus: ```blade @if($brand->config('show_market_ticker')) @endif ``` **Das ist die häufigste Form der Differenzierung** – und sie reicht für ~80 % aller Fälle. ### 5b. Slots und Defaults in Komponenten Wenn eine Komponente strukturell gleich ist, aber Inhalte/Sprache abweichen: ```blade {{-- resources/views/components/hub/transition-block.blade.php --}} @props([ 'title' => $brand->config('hub_cta.title') ?? 'Pressemitteilung einreichen', 'description' => $brand->config('hub_cta.description'), 'buttonText' => $brand->config('hub_cta.button') ?? 'Zum Publisher-Bereich', ]) ``` Brand-Texte stehen in Config, Komponente bleibt eine. ### 5c. View-Override pro Brand (Eskalations-Pfad) Für die seltenen Fälle, in denen eine Brand wirklich ein anderes Markup braucht: Laravel kann View-Pfade brand-spezifisch erweitern. ```php // app/Providers/BrandServiceProvider.php public function boot(): void { $this->app['view']->prependLocation( resource_path("views/themes/{$brand->slug}") ); } ``` Dann sucht Laravel View-Dateien zuerst unter `resources/views/themes/presseecho/components/press/item.blade.php`, dann unter dem Standard-Pfad. **Nur** für die Komponenten, die wirklich anders sein müssen, wird eine Override-Datei angelegt. > **Disziplin-Regel:** View-Overrides sind die letzte Eskalationsstufe. Erst versuchen, mit Config + Slots auszukommen. Override-Dateien verdoppeln Wartungsaufwand – jeder Bugfix muss mehrfach gemacht werden. ## 6. Datei-Struktur (Beispiel, siehe akutelle Struktur und optimiere falls nötig ) ``` app/ ├── Brand/ │ ├── Brand.php # Eloquent Model │ ├── BrandManager.php # Service, im Container │ └── BrandResolver.php # Domain → Brand ├── Http/ │ └── Middleware/ │ └── ResolveBrand.php ├── Livewire/ │ ├── Brand/ # Brand-Portal-spezifisch │ │ ├── AdHocTicker.php │ │ ├── HeroSlider.php │ │ └── PressSearch.php │ └── Hub/ # Hub-spezifisch │ ├── PressSubmissionForm.php │ └── NewsroomManager.php ├── View/ │ └── Components/ │ ├── Brand/ │ ├── Press/ │ ├── Hub/ │ ├── Ui/ │ └── Quality/ └── Providers/ └── BrandServiceProvider.php config/ └── brands/ ├── businessportal24.php ├── presseecho.php └── hub.php resources/ ├── css/ │ ├── app.css # Tailwind base + semantische Tokens │ └── themes/ │ ├── businessportal24.css # Brand-Tokens (optional separat) │ └── presseecho.css ├── js/ │ └── app.js └── views/ ├── layouts/ │ ├── brand.blade.php # Brand-Portal-Layout │ └── hub.blade.php # Hub-Layout ├── components/ # Standard-Komponenten │ ├── brand/ │ ├── press/ │ ├── hub/ │ ├── ui/ │ └── quality/ ├── livewire/ # Volt-Komponenten │ ├── ad-hoc-ticker.blade.php │ ├── hero-slider.blade.php │ └── press-search.blade.php ├── pages/ # Konkrete Seiten-Templates │ ├── home.blade.php │ └── veroeffentlichen.blade.php └── themes/ # NUR Brand-Overrides └── presseecho/ └── components/ └── ... # nur was wirklich anders ist ``` ## 7. Performance-Strategie Vier konkrete Hebel, die in dieser Reihenfolge ausgeschöpft werden: **1. Aggressive View-Caching für statisches Markup.** Press-Listen, Newsroom-Sidebars, Statistik-Zeilen können mit Tag-basiertem Cache gepuffert werden. Neue Mitteilung → relevante Tags invalidieren. ```php Cache::tags(['press_list', "brand.{$brand->slug}"]) ->remember('home.aktuelle-meldungen', now()->addMinutes(5), fn() => /* ... */); ``` **2. Volt-Komponenten lazy laden, wo sinnvoll.** Below-the-fold-Komponenten (Branchen-Index, Termine, Newsroom-Liste) als `lazy`: ```blade ``` Sie laden erst beim Scroll, blockieren nicht das initiale Render. **3. Asset-Pipeline: ein Bundle, alle Brands.** Über die CSS-Variablen-Strategie ist kein Per-Brand-Build nötig. Ein Vite-Build, der für alle Brands gilt. Spart Komplexität und Cache-Invalidierung. **4. Brand-Resolution cachen.** Die Domain-zu-Brand-Auflösung ist `rememberForever` (siehe Middleware). Cache-Bust nur beim Brand-Update über Model-Observer. ## 8. Migration der Bestands-Inhalte Quer zu allem oben: die ~100.000 Bestandsmitteilungen sind im neuem System migriert! Drei Punkte, die das Komponenten-Design beeinflussen: - **`` muss tolerant gegenüber unvollständigen Daten sein.** Alte Mitteilungen haben evtl. keine Lesezeit-Schätzung, keine Branchen-Zuordnung, keine sauberen Bilder. Komponente rendert auch dann sauber. - **Permalink-Stabilität.** Die alte URL-Struktur muss erhalten bleiben (Strategie-Dokument: Tombstone-Modell). Das ist ein Routing-Thema, kein Komponenten-Thema – aber die Komponenten dürfen keine URLs hardcoden, sondern nur `route()`-Helpers nutzen. - **Brand-Zuordnung der Bestände.** Wie in Update 3 festgelegt: am Start ist jede Mitteilung beiden Brands zugewiesen. Komponenten brauchen dafür keine Sonderlogik – sie filtern nach Brand-Kontext, und der Pool ist eben (am Anfang) für beide Brands derselbe. ## 9. Entwicklungs-Reihenfolge (Empfehlung) Konkrete Bauplan-Sequenz, die früh nutzbare Ergebnisse liefert: **Sprint 1 – Fundament** - Brand-Model, Middleware, Resolver - Theming-Setup (Tailwind v4, CSS-Variablen, zwei Brand-Themes) - Layout `brand.blade.php` - Grundlegende UI-Komponenten (`button`, `badge`, `section-header`) **Sprint 2 – Statisches Markup für businessportal24** - TopBar, Header, RubrikenNav, Footer - `` und seine Varianten - StatsRow, QualityStandardFooter - Statische Version der Veröffentlichen-Landing (ohne Reaktivität) **Sprint 3 – Reaktive Komponenten** - AdHocTicker (Volt + Polling) - HeroSlider (Volt + Alpine) - PressList mit Filter-Tabs - Hub-Transition-Block mit Cross-Domain-Auth-Übergabe **Sprint 4 – Hub-Anbindung** - Sanctum-Setup für Cross-Domain - Hub-Routing für `?brand=businessportal24`-Parameter - Einreichungs-Flow im Hub (klassisches Livewire) **Sprint 5 – Zweite Brand aufschalten** - `presseecho.test` lokal - Brand-Config für presseecho - Erste Override-Komponente: `topic-cluster`-Hero - Testen: was funktioniert ohne Override, was braucht eines? Ab Sprint 5 wird die eigentliche Stresstest-Frage beantwortet: **Hält die Architektur, wenn die zweite Brand wirklich anders aussehen soll?** Wenn an Sprint 5 viele Overrides nötig werden, ist die Config-Schicht zu dünn – dann iterieren. ## 10. technische Punkte - **Tailwind v4:** wenn das Projekt noch nicht migriert ist, sollte das _vor_ Sprint 1 entschieden werden. v4 macht das CSS-Variablen-Setup deutlich eleganter. - **Sanctum-Cookie-Domain für Cross-Domain-Auth:** Detail aus Update 3, muss vor Sprint 4 final geklärt sein. Same-Site-Strategie, SPA-Mode oder klassischer Token-Flow? - **CDN/Asset-Hosting:** Brand-Bilder, Press-Item-Fotos – kommen vom Hub - **Translations:** DACH-Sprachschalter auf der Startseite –(de (ohne parameter) / de-at / de-ch / en) ist eine reine Inhalts-Filterung. Bei Mehrsprachigkeit de/en: i18n-Setup - **Polling-Frequenzen:** AdHocTicker, Newsroom-Liste – wie oft refreshen, ohne dass die Server-Last bei wachsendem Traffic problematisch wird? Anfangswerte: Ticker 30s, Newsroom 60s, Branchen-Index 5 min. --- _Dieses Konzept ist die technische Brücke zwischen Architektur (Update 3), Positionierung (Update 4) und Implementation. Es legt fest, wie Komponenten strukturiert werden, damit die Brand-Differenzierung skaliert – ohne in eine Codebase-Duplikation zu kippen. Anpassungen sollten dokumentiert und mit den Update-Dokumenten abgeglichen werden._