Interior-Farbwelt, Welten-Trennung, Tischlerei und Herkunfts-Sektion

Arbeitsplan 14.09.2026, Punkte 1.1 bis 1.4 (Nachträge 6 bis 10 im Entwicklungsplan):

- Farbwelt Interior (Tanne, Creme, Sand, Ocker, Clay) als Standard, Immobilien-Welt
  behält das bisherige Blau über body.world-immobilien; Logo je Welt, Favicons neu,
  Welten-Wechsler in Kopf- und Fußzeile.
- Saubere Trennung der Welten: eigene Navigation, Ankündigungsleiste, Magazinliste,
  Über-uns-Seite, FAQ, Kontaktseite und Abschluss-CTA für Immobilien; Beiträge tragen
  eine Welt (Migration + Admin-Feld). Abschluss-CTA Ton in Ton statt Grün-zu-Ocker.
- Kontakt: Betreffe in Gruppen Interior / Immobilien / B2in, Marken darin.
  Terminanfrage-Buttons öffnen das Widget mit vorgewähltem Anliegen; Select-Felder
  einheitlich gestaltet.
- Tischlerei als vierte Kernmarke (Platzhalter), Kachelraster 2×2.
- Herkunfts-Sektion „Aus der Region gefertigt“ mit schematischer Standortkarte und
  Ablauf CABINET (Beratung Bielefeld, Fertigung Köln, Aufbau mit Tischlerei).

Deployment: migrate, WorldSplitCmsSeeder, CmsBrandSeeder, OriginSectionCmsSeeder
(Abschnitt 8 im Entwicklungsplan).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-15 12:46:41 +00:00
parent 1242d4bce8
commit 7953a8c9a1
137 changed files with 3840 additions and 589 deletions

View file

@ -29,12 +29,31 @@ class CmsArticle extends Model
5 => 'local-for-local-regionaler-moebelhandel',
];
public const WORLD_INTERIOR = 'interior';
public const WORLD_IMMOBILIEN = 'immobilien';
/** @var array<int, string> */
public const WORLDS = [self::WORLD_INTERIOR, self::WORLD_IMMOBILIEN];
/**
* Beiträge aus dem Bestand, die zur Immobilien-Welt gehören (Dubai-Themen).
*
* @var array<int, string>
*/
public const LEGACY_IMMOBILIEN_SLUGS = [
'escrow-system-dubai-investoren',
'spotlight-al-jaddaf-hotspot',
'turnkey-investments-moeblierung-rendite',
];
protected $fillable = [
'slug',
'title',
'subtitle',
'image',
'category',
'world',
'date_label',
'read_time',
'author',
@ -69,6 +88,11 @@ class CmsArticle extends Model
return $query->orderBy('order')->orderByDesc('created_at');
}
public function scopeForWorld(Builder $query, string $world): Builder
{
return $query->where('world', $world);
}
/**
* Veröffentlichte Artikel fürs Frontend, nach Slug indiziert.
*
@ -76,19 +100,32 @@ class CmsArticle extends Model
* dieselbe Reihenfolge wie bei cms_theme_section(), damit das Magazin auf einer frischen
* Installation nicht leer ist.
*
* Mit `$world` nur die Beiträge dieser Farbwelt (Interior oder Immobilien) – die beiden
* Magazinlisten sind getrennt, die Detailseite rendert in der Welt ihres Beitrags.
*
* @return array<string, array<string, mixed>>
*/
public static function forFrontend(): array
public static function forFrontend(?string $world = null): array
{
$articles = static::query()->published()->ordered()->get();
$maintained = static::query()->published()->exists();
if ($articles->isNotEmpty()) {
return $articles
if ($maintained) {
return static::query()
->published()
->when($world !== null, fn (Builder $query) => $query->forWorld($world))
->ordered()
->get()
->mapWithKeys(fn (self $article) => [$article->slug => $article->toFrontendArray()])
->all();
}
return static::fallbackFromLang();
$articles = static::fallbackFromLang();
if ($world === null) {
return $articles;
}
return array_filter($articles, fn (array $article) => ($article['world'] ?? self::WORLD_INTERIOR) === $world);
}
/**
@ -143,7 +180,7 @@ class CmsArticle extends Model
continue;
}
$out[$slug] = $article + ['slug' => $slug];
$out[$slug] = $article + ['slug' => $slug, 'world' => self::WORLD_INTERIOR];
}
return $out;
@ -161,6 +198,7 @@ class CmsArticle extends Model
'subtitle' => $this->subtitle,
'image' => $this->image,
'category' => $this->category,
'world' => $this->world ?? self::WORLD_INTERIOR,
'date' => $this->date_label,
'readTime' => $this->read_time,
'author' => $this->author ?? [],