Startseite ruhiger, Otterpohl mit Montage, Marken-Bilder und Farbwelt-Entwurf

- Hinweisleiste per B2IN_ANNOUNCEMENT_BAR ausgeblendet (Standard aus)
- Hero: „Räume aus einer Hand.“ als neue Überschrift, zweiter Button entfernt
- Projekttext mit zentralem deutschen Vertragspartner (Punkt statt Gedankenstrich)
- Otterpohl als vierte Kernmarke: Text v2, Abschnitt „Montage“, Titelbild getauscht
- Markenabschnitte: Absätze mit „- “ als Liste mit fetter Einleitung
- Titelbilder und Galerien für CABINET, Rotpunkt, Febrü und Otterpohl (WebP)
- Feedback 17.09. in den Markentexten, Demobilder entfernt, WhatsApp-Nummer
- Palette „Modern“ fest ohne Schalter, Entwurf /entwurf mit Hero-Slideshow
- Seeder-Liste fürs Deployment im Entwicklungsplan

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-10-01 10:34:21 +00:00
parent 3211569fee
commit 4b07bbdcae
124 changed files with 3618 additions and 496 deletions

4
.gitignore vendored
View file

@ -64,3 +64,7 @@ dev/**/deploy/*.tar.gz
# Referenzmaterial und Projektdokumente ausserhalb der Versionierung
/_b2in_interior_shop/
/docs/
# Original-Bildzulieferungen (mehrere MB je Datei) bleiben ausserhalb der Versionierung;
# die optimierten WebP-Fassungen liegen unter public/img/assets/marken/.
/dev/2026-08-28/images/

View file

@ -19,6 +19,11 @@ class ThemeHelper
return self::getLogoPathForBrand('b2in-immobilien', $type);
}
// Entwurf in der Farbwelt vom 24.09. (Nachtrag 19): Bogen in Gold statt Ocker.
if ($theme === 'b2in' && b2in_palette() === 'greige') {
return "img/logos/b2in-interior-logo-gold-{$type}.svg";
}
return self::getLogoPathForBrand($theme, $type);
}

View file

@ -35,6 +35,51 @@ class BrandDetail extends Component
->reject(fn (array $brand, string $key) => $key === $slug)
->values()
->all();
$craft = $this->craftSection($slug, $brands);
if ($craft !== null) {
$this->brand['sections'][] = $craft;
}
}
/**
* Gemeinsamer Handwerk-Abschluss (Lang-Sektion `brand_craft`, Nachtrag 15): steht als letzter
* Abschnitt auf jeder Markenseite – außer auf der Seite der Handwerksmarke selbst. Der Link
* dorthin erscheint nur, wenn diese Marke veröffentlicht ist und eine Detailseite hat.
*
* @param array<string, array<string, mixed>> $brands
* @return array<string, mixed>|null
*/
private function craftSection(string $slug, array $brands): ?array
{
$content = cms_theme_section('brand_craft');
if (! is_array($content)) {
return null;
}
$craftSlug = (string) ($content['brand_slug'] ?? '');
if ($craftSlug !== '' && $craftSlug === $slug) {
return null;
}
$sections = CmsBrand::normalizeSections([$content]);
if ($sections === []) {
return null;
}
$section = $sections[0];
$section['anchor'] = 'handwerk';
if ($craftSlug !== '' && ! empty($brands[$craftSlug]['has_detail_page'])) {
$section['link'] = route('marken.show', ['slug' => $craftSlug]);
$section['link_text'] = (string) ($content['link_text'] ?? $brands[$craftSlug]['name'] ?? '');
}
return $section;
}
public function render()

View file

@ -2,42 +2,96 @@
namespace App\Livewire\Web\Components\Sections;
use App\Models\CmsBrand;
use Livewire\Component;
/**
* Hero des Startseiten-Entwurfs (Nachtrag 19, Briefing Marcel 24.09.): Rahmen mit Goldkante,
* eine Überschrift, ein Button und statt des festen Bildes eine ruhige Slideshow.
*
* Die Slides kommen aus der Sektion `home/hero_slider` (Liste `slides`), sobald Marcel dort
* welche pflegt. Solange die Liste leer ist, laufen die Titelbilder der veröffentlichten
* Kernmarken in ihrer Reihenfolge – Marken ohne Bild fallen weg.
*/
class HeroSlider extends Component
{
public $content = [];
/** @var array<string, mixed> */
public array $content = [];
public $currentSlide = 0;
/** @var array<int, array{image: string, alt: string, label: string, caption: string}> */
public array $slides = [];
public function mount()
public function mount(): void
{
$this->content = cms_theme_section('hero_slider');
$section = cms_theme_section('hero_slider');
$this->content = is_array($section) ? $section : [];
$this->slides = $this->slidesFromContent() ?: $this->slidesFromBrands();
}
public function nextSlide()
/**
* @return array<int, array{image: string, alt: string, label: string, caption: string}>
*/
private function slidesFromContent(): array
{
if (! empty($this->content['slides'])) {
$this->currentSlide = ($this->currentSlide + 1) % count($this->content['slides']);
$slides = [];
foreach ((array) ($this->content['slides'] ?? []) as $slide) {
if (! is_array($slide) || trim((string) ($slide['image'] ?? '')) === '') {
continue;
}
if (array_key_exists('active', $slide) && ! filter_var($slide['active'], FILTER_VALIDATE_BOOLEAN)) {
continue;
}
$label = trim((string) ($slide['label'] ?? $slide['title'] ?? ''));
$slides[] = [
'image' => (string) $slide['image'],
'alt' => trim((string) ($slide['alt'] ?? $label)),
'label' => $label,
'caption' => trim((string) ($slide['caption'] ?? '')),
];
}
return $slides;
}
public function previousSlide()
/**
* @return array<int, array{image: string, alt: string, label: string, caption: string}>
*/
private function slidesFromBrands(): array
{
if (! empty($this->content['slides'])) {
$totalSlides = count($this->content['slides']);
$this->currentSlide = ($this->currentSlide - 1 + $totalSlides) % $totalSlides;
$slides = [];
foreach (CmsBrand::forFrontend() as $brand) {
$image = trim((string) ($brand['image'] ?? ''));
if ($image === '') {
continue;
}
$name = (string) ($brand['name'] ?? '');
$slides[] = [
'image' => $image,
'alt' => $name.' – '.strip_tags((string) ($brand['tagline'] ?? '')),
'label' => $name,
'caption' => strip_tags((string) ($brand['tagline'] ?? '')),
];
}
return $slides;
}
public function setSlide($index)
public function getIntervalMsProperty(): int
{
if (! empty($this->content['slides']) && $index >= 0 && $index < count($this->content['slides'])) {
$this->currentSlide = $index;
}
$seconds = (float) ($this->content['interval'] ?? 7);
return (int) round(max(3, min(30, $seconds)) * 1000);
}
public function render()
public function render(): \Illuminate\View\View
{
return view('livewire.web.components.sections.hero-slider');
}

View file

@ -17,6 +17,14 @@ class Footer extends Component
public string $logoPath = '';
/**
* Negatives Logo für die Farbvariante „Modern“ (Nachtrag 18): dort liegt der Interior-Footer
* dunkel in Tanne. Die Palette schaltet im Browser, deshalb liegen beide Logos im Markup und
* das Stylesheet blendet das passende ein. Leer in der Immobilien-Welt, die ohnehin das
* negative Logo trägt.
*/
public string $logoPathDark = '';
/**
* Fuer die Immobilien-Welt zeigen die gemeinsamen Footer-Links auf die Seiten dieser Welt.
*
@ -49,6 +57,9 @@ class Footer extends Component
// Logo; der Immobilien-Footer bleibt dunkel und nutzt die negative Variante.
$variant = $this->world === 'immobilien' ? 'negative' : 'positive';
$this->logoPath = \App\Helpers\ThemeHelper::getLogoPath($variant, $this->world);
$this->logoPathDark = $this->world === 'immobilien'
? ''
: \App\Helpers\ThemeHelper::getLogoPath('negative', $this->world);
}
public function render()

View file

@ -22,8 +22,11 @@ class CmsBrand extends Model
/** Entwurf, Rendering oder KI-gestuetzte Darstellung – nicht realisiert. */
public const GALLERY_INSPIRATION = 'inspiration';
/** Einblick in Werkstatt, Fertigung oder Montage des Herstellers – weder Projekt noch Entwurf (Nachtrag 19). */
public const GALLERY_COMPANY = 'company';
/** @var list<string> */
public const GALLERY_CATEGORIES = [self::GALLERY_PROJECT, self::GALLERY_INSPIRATION];
public const GALLERY_CATEGORIES = [self::GALLERY_PROJECT, self::GALLERY_INSPIRATION, self::GALLERY_COMPANY];
protected $table = 'cms_brands';
@ -215,6 +218,45 @@ class CmsBrand extends Model
return $out;
}
/**
* Absätze eines Abschnitts für die Ausgabe gruppieren.
*
* Ein Absatz, der mit „- “ beginnt, ist ein Listenpunkt; aufeinanderfolgende Punkte bilden
* eine Liste. Steht im Punkt ein Doppelpunkt, wird der Teil davor als fette Einleitung
* ausgegeben („- Sichere Prüfung & Terminierung: Wir vereinbaren …“). So lässt sich eine
* Aufzählung im Backend im normalen Textfeld pflegen.
*
* @param list<string> $paragraphs
* @return list<array{type: 'paragraph', text: string}|array{type: 'list', items: list<array{lead: string, text: string}>}>
*/
public static function paragraphBlocks(array $paragraphs): array
{
$blocks = [];
foreach ($paragraphs as $paragraph) {
if (! preg_match('/^[-–•]\s+(.+)$/su', $paragraph, $match)) {
$blocks[] = ['type' => 'paragraph', 'text' => $paragraph];
continue;
}
[$lead, $text] = str_contains($match[1], ': ')
? array_map('trim', explode(': ', $match[1], 2))
: ['', trim($match[1])];
$last = array_key_last($blocks);
if ($last === null || $blocks[$last]['type'] !== 'list') {
$blocks[] = ['type' => 'list', 'items' => []];
$last = array_key_last($blocks);
}
$blocks[$last]['items'][] = ['lead' => $lead, 'text' => $text];
}
return $blocks;
}
/**
* @param mixed $paragraphs
* @return list<string>
@ -275,10 +317,10 @@ class CmsBrand extends Model
}
/**
* Galerie nach realisierten Projekten und Inspirationen getrennt.
* Galerie nach realisierten Projekten, Einblicken in die Werkstatt und Inspirationen getrennt.
*
* @param mixed $gallery
* @return array{projects: list<array{image: string, category: string, caption: string}>, inspirations: list<array{image: string, category: string, caption: string}>}
* @return array{projects: list<array{image: string, category: string, caption: string}>, company: list<array{image: string, category: string, caption: string}>, inspirations: list<array{image: string, category: string, caption: string}>}
*/
public static function groupGallery($gallery): array
{
@ -289,6 +331,10 @@ class CmsBrand extends Model
$items,
fn (array $item) => $item['category'] === self::GALLERY_PROJECT,
)),
'company' => array_values(array_filter(
$items,
fn (array $item) => $item['category'] === self::GALLERY_COMPANY,
)),
'inspirations' => array_values(array_filter(
$items,
fn (array $item) => $item['category'] === self::GALLERY_INSPIRATION,
@ -296,6 +342,18 @@ class CmsBrand extends Model
];
}
/**
* Beschriftung des Badges auf dem Galeriebild („Realisiertes Projekt“, „Aus der Werkstatt“, „Visualisierung“).
*/
public static function galleryBadge(string $category): string
{
return match ($category) {
self::GALLERY_PROJECT => __('ui.brand_gallery_badge_project'),
self::GALLERY_COMPANY => __('ui.brand_gallery_badge_company'),
default => __('ui.brand_gallery_badge_inspiration'),
};
}
/**
* @return array<string, mixed>
*/

View file

@ -233,6 +233,20 @@ if (! function_exists('b2in_world')) {
}
}
if (! function_exists('b2in_palette')) {
/**
* Abweichende Farbpalette der aktuellen Seite – leer im Normalfall.
*
* Der Entwurf der Startseite (Nachtrag 19, /entwurf) bindet „greige“; Logo und Layout
* richten sich danach. Die Wahl „Bisher/Neu“ des Farb-Schalters läuft dagegen im Browser
* und taucht hier nicht auf.
*/
function b2in_palette(): string
{
return app()->bound('b2in.palette') ? (string) app('b2in.palette') : '';
}
}
if (! function_exists('b2in_worlds')) {
/**
* Die beiden Welten für den Welten-Wechsler in Kopf- und Fußzeile.

View file

@ -56,6 +56,58 @@ return [
*/
'indexable' => (bool) env('SITE_INDEXABLE', env('APP_ENV', 'production') === 'production'),
/*
|--------------------------------------------------------------------------
| Farbvarianten-Schalter (B2in Interior)
|--------------------------------------------------------------------------
|
| Blendet auf den Interior-Seiten den schwimmenden Schalter ein, mit dem sich
| die bisherige Creme-Palette und die hellere Variante „Modern“ vergleichen
| lassen (Nachtrag 18). Nur für die Abstimmung gedacht – sobald eine Palette
| entschieden ist, per B2IN_PALETTE_SWITCH=false abschalten.
|
*/
'palette_switch' => (bool) env('B2IN_PALETTE_SWITCH', false),
/*
|--------------------------------------------------------------------------
| Feste Farbpalette der Interior-Welt (ohne Schalter)
|--------------------------------------------------------------------------
|
| „modern“ = die hellere Variante „Neu“ (Nachtrag 18), „classic“ = Creme.
| Gilt, solange der Schalter aus ist; mit Schalter ist es die Vorbelegung.
| Seit der Abstimmung 25.09. steht die Seite auf „modern“.
|
*/
'palette' => env('B2IN_PALETTE', 'modern'),
/*
|--------------------------------------------------------------------------
| Entwurf der Startseite unter /entwurf
|--------------------------------------------------------------------------
|
| Farbwelt „Greige“ mit Hero-Slideshow (Nachtrag 19), nur zur Abstimmung.
| Abschalten, sobald der Entwurf entschieden ist.
|
*/
'home_draft' => (bool) env('B2IN_HOME_DRAFT', true),
/*
|--------------------------------------------------------------------------
| Obere Hinweisleiste (Ankündigungsleiste)
|--------------------------------------------------------------------------
|
| Seit der Abstimmung vom 01.10.2026 ausgeblendet – die Seite wirkt ohne die
| Leiste ruhiger. Inhalt und CMS-Pflege bleiben erhalten; mit
| B2IN_ANNOUNCEMENT_BAR=true ist sie auf allen Seiten wieder da.
|
*/
'announcement_bar' => (bool) env('B2IN_ANNOUNCEMENT_BAR', false),
/*
|--------------------------------------------------------------------------
| Application URL

View file

@ -128,6 +128,9 @@ return [
'sections' => [
['group' => 'home', 'key' => 'hero', 'anchor' => 'hero', 'title' => 'Hero',
'description' => 'Der große Einstieg ganz oben: Überschrift, Text, Buttons und Bild.'],
['group' => 'home', 'key' => 'hero_slider', 'anchor' => 'hero', 'title' => 'Hero mit Slideshow (Entwurf)',
'path' => '/entwurf',
'description' => 'Der Hero des Entwurfs vom 24.09.: Überschrift, Text, Button und die Slideshow. Liste „slides“ leer = Titelbilder der Kernmarken; sonst je Eintrag image, label, caption und active.'],
['group' => 'home', 'key' => 'founder_bar', 'anchor' => 'gruender', 'title' => 'Gründer-Leiste',
'description' => 'Schmale Zeile mit Marcel Scheibe und Zitat. Steht auch auf „Über uns“ und in der Immobilien-Welt.'],
['group' => 'home', 'key' => 'audience_switch', 'anchor' => 'wege', 'title' => 'Zielgruppen-Wahl',
@ -161,6 +164,8 @@ return [
'description' => 'Hinweis, dass weitere Marken folgen.'],
['group' => 'marken', 'key' => 'cta', 'anchor' => 'marken-cta', 'title' => 'Abschluss auf /marken',
'description' => 'Der Abschlussblock „Lernen Sie unsere Marken kennen“ mit Terminanfrage und WhatsApp.'],
['group' => 'marken', 'key' => 'craft', 'anchor' => 'handwerk', 'title' => 'Handwerk-Abschluss auf jeder Markenseite',
'description' => 'Der letzte Abschnitt auf den Seiten von CABINET, Rotpunkt und Febrü: „Planung plus Handwerk“ mit Link zu Otterpohl. Einmal gepflegt, überall gleich.', 'path' => '/marken/cabinet'],
],
],

View file

@ -16,6 +16,7 @@ return [
'announcement_bar' => ['global', 'announcement_bar'],
'announcement_bar_immobilien' => ['global', 'announcement_bar_immobilien'],
'header' => ['global', 'header'],
'hero_slider' => ['home', 'hero_slider'],
'footer' => ['global', 'footer'],
'cta_section' => ['global', 'cta_section'],
'cta_section_immobilien' => ['global', 'cta_section_immobilien'],
@ -31,6 +32,7 @@ return [
'marken_network' => ['marken', 'network'],
'marken_growth' => ['marken', 'growth'],
'marken_cta' => ['marken', 'cta'],
'brand_craft' => ['marken', 'craft'],
'referenzen_hero' => ['referenzen', 'hero'],
'reference_projects' => ['referenzen', 'projects'],

View file

@ -0,0 +1,84 @@
<?php
namespace Database\Seeders;
use FluxCms\Core\Models\CmsContent;
use FluxCms\Core\Services\CmsContentService;
use Illuminate\Database\Seeder;
/**
* Projekttext auf der Startseite (Briefing Marcel 24.09.2026, Punkt 7; Nachtrag 19).
*
* In der Karte „Ausstattung für Ihr Projekt“ (home/audience_switch) wird „Mit deutscher
* Vertragssicherheit …“ durch „Mit einem zentralen deutschen Vertragspartner …“ ersetzt.
* Abstimmung 01.10.2026: Nach „Projektentwicklungen“ steht ein Punkt statt des Gedankenstrichs.
* Der Seeder tauscht nur genau diesen Satz, egal wo er im Sektions-JSON steht; alles andere
* bleibt. Steht der Satz nicht mehr drin, passiert nichts. Mehrfaches Ausführen ist unbedenklich.
*
* php artisan db:seed --class=AudienceSwitchProjectTextSeeder
*/
class AudienceSwitchProjectTextSeeder extends Seeder
{
/** @var array<string, string> alter Satz → neuer Satz */
public const REPLACEMENTS = [
'Mit deutscher Vertragssicherheit und einem eingespielten Herstellernetzwerk.' => 'Mit einem zentralen deutschen Vertragspartner und einem eingespielten Herstellernetzwerk.',
'With German contractual reliability and an established manufacturer network.' => 'With a single German contract partner and an established manufacturer network.',
'Projektentwicklungen – koordiniert aus einer Hand' => 'Projektentwicklungen. Koordiniert aus einer Hand',
'developments – coordinated from a single source' => 'developments. Coordinated from a single source',
];
public function run(): void
{
$row = CmsContent::query()->where('group', 'home')->where('key', 'audience_switch')->first();
if (! $row) {
$this->command?->info('Projekttext: Sektion home/audience_switch nicht im CMS – die Lang-Datei gilt.');
return;
}
$value = $row->getTranslations('value');
$count = 0;
$value = $this->replaceDeep($value, $count);
if ($count === 0) {
$this->command?->info('Projekttext: bereits aktuell, nichts geändert.');
return;
}
$row->value = $value;
$row->save();
app(CmsContentService::class)->clearCache('home');
$this->command?->info("Projekttext ersetzt ({$count} Stelle(n)).");
}
/**
* @param array<mixed> $data
* @return array<mixed>
*/
private function replaceDeep(array $data, int &$count): array
{
foreach ($data as $key => $item) {
if (is_array($item)) {
$data[$key] = $this->replaceDeep($item, $count);
continue;
}
if (! is_string($item)) {
continue;
}
$replaced = strtr($item, self::REPLACEMENTS);
if ($replaced !== $item) {
$data[$key] = $replaced;
$count++;
}
}
return $data;
}
}

View file

@ -0,0 +1,162 @@
<?php
namespace Database\Seeders;
use App\Models\CmsBrand;
use Illuminate\Database\Seeder;
/**
* Trägt Titelbild und Galerie für CABINET, Rotpunkt, Febrü (Nachtrag 16) und Otterpohl (Nachtrag 19) nach.
*
* Grundlage: dev/2026-08-28/images/ (Zulieferungen vom 22.–24.09.2026). Die Originale sind 1–8 MB
* große JPEGs; unter public/img/assets/marken/{cabinet,rotpunkt,februe,otterpohl}/ liegen sie als
* WebP mit maximal 1920 px Kantenlänge (Imagick, Qualität 82, ohne Metadaten). Die mit `_title_`
* markierte Datei ist das Titelbild (Seitenkopf der Markenseite, Kachel auf der Startseite,
* Markenblock auf /marken). Die Galeriebilder der drei Hersteller stehen als **Visualisierung**:
* es sind Herstellerdarstellungen, keine von uns ausgeführten Projekte – unter „Referenzen“
* erscheinen sie damit nicht. Bei Otterpohl sind die Bilder 2–5 Einblicke in Werkstatt und
* Montage (**Aus der Werkstatt**, dazu das frühere Titelbild), alle übrigen ausgeführte Aufträge (**Realisiertes Projekt**);
* letztere erscheinen unter „Referenzen“, solange dort keine eigenen Einträge gepflegt sind.
*
* Verhalten: Das Titelbild wird nur gesetzt, wenn keins gepflegt ist. Galeriebilder werden
* nur ergänzt, wenn die Datei noch nicht in der Galerie steht; vorhandene Einträge bleiben
* samt Kategorie und Bildunterschrift, wie Marcel sie gepflegt hat. Mehrfaches Ausführen ist
* unbedenklich.
*
* php artisan db:seed --class=BrandImageSeeder
*/
class BrandImageSeeder extends Seeder
{
/**
* Galerie: Dateiname → Bildunterschrift (Kategorie Visualisierung) oder
* Dateiname → ['caption' => …, 'category' => …] für eine andere Kategorie.
*
* @var array<string, array{image: string, gallery: array<string, string|array{caption: string, category: string}>}>
*/
private const IMAGES = [
'cabinet' => [
'image' => 'marken/cabinet/ankleide-solitaer-eiche-tano.webp',
'gallery' => [
'marken/cabinet/dachschraegenschrank-filo-cashmere.webp' => 'Dachschrägenschrank in Filo Cashmere',
'marken/cabinet/schrank-gegen-schraege-marmor.webp' => 'Schrank gegen die Schräge in Marmoroptik',
'marken/cabinet/schlafzimmer-eiche-tano.webp' => 'Schlafzimmerschrank in Eiche Tano',
'marken/cabinet/schlafzimmer-velluto-fango.webp' => 'Schlafzimmerschrank in Velluto Fango',
'marken/cabinet/schlafzimmer-wandschrank-filo-cashmere.webp' => 'Wandschrank im Schlafzimmer in Filo Cashmere',
'marken/cabinet/garderobe-seite-offen.webp' => 'Garderobe mit offener Seite',
'marken/cabinet/garderobe-sitznische-filo-cashmere.webp' => 'Garderobe mit Sitznische in Filo Cashmere',
'marken/cabinet/drehtuerenschrank-marmor.webp' => 'Drehtürenschrank in Marmoroptik',
'marken/cabinet/flur-schrank-eiche-tano.webp' => 'Flurschrank in Eiche Tano',
'marken/cabinet/dachschraege-velluto-fango.webp' => 'Schrank unter der Dachschräge in Velluto Fango',
'marken/cabinet/ankleide-solitaer-eiche-tano-seite.webp' => 'Ankleide Solitär in Eiche Tano, Seitenansicht',
],
],
'rotpunkt' => [
'image' => 'marken/rotpunkt/kueche-01.webp',
'gallery' => [
'marken/rotpunkt/kueche-01-b.webp' => 'Küche 01, zweite Ansicht',
'marken/rotpunkt/kueche-02.webp' => 'Küche 02',
'marken/rotpunkt/kueche-03.webp' => 'Küche 03',
'marken/rotpunkt/kueche-04.webp' => 'Küche 04',
'marken/rotpunkt/kueche-05.webp' => 'Küche 05',
'marken/rotpunkt/kueche-06.webp' => 'Küche 06',
'marken/rotpunkt/kueche-06-b.webp' => 'Küche 06, zweite Ansicht',
'marken/rotpunkt/kueche-07.webp' => 'Küche 07',
'marken/rotpunkt/kueche-08.webp' => 'Küche 08',
'marken/rotpunkt/kueche-09.webp' => 'Küche 09',
'marken/rotpunkt/kueche-10.webp' => 'Küche 10',
'marken/rotpunkt/kueche-11.webp' => 'Küche 11',
'marken/rotpunkt/kueche-12.webp' => 'Küche 12',
],
],
// Bildunterschriften nach den Febrü-Produktlinien aus den Dateinamen.
'februe' => [
'image' => 'marken/februe/base-thekenloesung.webp',
'gallery' => [
'marken/februe/active-2-0-bureo.webp' => 'Active 2.0 mit Bureo',
'marken/februe/active-2-0-soul-grossraum.webp' => 'Großraum mit Active 2.0 und Soul',
'marken/februe/adesso.webp' => 'Adesso',
'marken/februe/arena-stage.webp' => 'Arena Stage',
'marken/februe/base.webp' => 'Base',
'marken/februe/base-sideboard.webp' => 'Base Sideboard',
'marken/februe/bobb-bionics-coworker-container-cube.webp' => 'Bobb, Bionics, CoWorker, Container und Cube',
'marken/februe/bobb.webp' => 'Bobb',
'marken/februe/brandon.webp' => 'Brandon',
'marken/februe/coworker.webp' => 'CoWorker',
'marken/februe/corporate.webp' => 'Corporate',
'marken/februe/curved-ben-basic.webp' => 'Curved, Ben und Basic',
'marken/februe/eline-one-silent-garderobenpaneel.webp' => 'E-Line, One, Silent und Garderobenpaneel',
'marken/februe/elements-soul-grossraum.webp' => 'Großraum mit Elements und Soul',
'marken/februe/vio.webp' => 'Vio',
],
],
// Otterpohl (Zulieferung 24.09.): Werkstattbilder und Referenzen. Seit 01.10. ist die Treppe mit
// Lamellenwand (tittle_otter.png) das Titelbild, das frühere Werkstatt-Titelbild steht in der
// Galerie – {@see OtterpohlBrandSeeder::swapTitleImage()} tauscht bestehende Installationen.
// Unterschriften ohne Kundennamen – die stehen nur in den Dateinamen der Zulieferung.
'tischlerei' => [
'image' => OtterpohlBrandSeeder::TITLE_IMAGE,
'gallery' => [
'marken/otterpohl/referenz-dziuballe-treppe.webp' => ['caption' => 'Treppe mit Holzstufen, Stufenbeleuchtung und Eichenhandlauf', 'category' => CmsBrand::GALLERY_PROJECT],
'marken/otterpohl/referenz-luecke-12.webp' => ['caption' => 'Tür und Kommode vor Lamellenwand', 'category' => CmsBrand::GALLERY_PROJECT],
'marken/otterpohl/referenz-neugebauer.webp' => ['caption' => 'Gewendelte Treppe mit Eichenstufen', 'category' => CmsBrand::GALLERY_PROJECT],
'marken/otterpohl/referenz-luecke-15.webp' => ['caption' => 'Beleuchtete Wandregale über Eck', 'category' => CmsBrand::GALLERY_PROJECT],
'marken/otterpohl/referenz-berkemeier-treppe.webp' => ['caption' => 'Treppe mit umlaufender Sockelleiste und Lichtdetail', 'category' => CmsBrand::GALLERY_PROJECT],
'marken/otterpohl/referenz-objektbau-luecke-technik.webp' => ['caption' => 'Objektbau: Verkaufsraum mit Theke und Regalwand', 'category' => CmsBrand::GALLERY_PROJECT],
'marken/otterpohl/referenz-otterpohl-11.webp' => ['caption' => 'Sideboard mit Eichenplatte im Büro', 'category' => CmsBrand::GALLERY_PROJECT],
OtterpohlBrandSeeder::FORMER_TITLE_IMAGE => ['caption' => OtterpohlBrandSeeder::FORMER_TITLE_CAPTION, 'category' => CmsBrand::GALLERY_COMPANY],
'marken/otterpohl/werkstatt-02.webp' => ['caption' => 'CNC-Bearbeitungszentrum in der Fertigung', 'category' => CmsBrand::GALLERY_COMPANY],
'marken/otterpohl/werkstatt-03.webp' => ['caption' => 'Einbau einer Tür vor Ort', 'category' => CmsBrand::GALLERY_COMPANY],
'marken/otterpohl/werkstatt-04.webp' => ['caption' => 'Handwerkzeug in der Werkstatt', 'category' => CmsBrand::GALLERY_COMPANY],
'marken/otterpohl/werkstatt-05-lift.webp' => ['caption' => 'Anlieferung per Möbellift – Montage inklusive', 'category' => CmsBrand::GALLERY_COMPANY],
],
],
];
public function run(): void
{
foreach (self::IMAGES as $slug => $data) {
$brand = CmsBrand::query()->where('slug', $slug)->first();
if (! $brand) {
$this->command?->warn("Marke „{$slug}“ nicht gefunden – übersprungen.");
continue;
}
$titleSet = false;
if (trim((string) $brand->image) === '') {
$brand->image = $data['image'];
$titleSet = true;
}
$gallery = CmsBrand::normalizeGallery($brand->gallery);
$known = array_column($gallery, 'image');
$added = 0;
foreach ($data['gallery'] as $image => $entry) {
if (in_array($image, $known, true)) {
continue;
}
$gallery[] = [
'image' => $image,
'category' => is_array($entry) ? $entry['category'] : CmsBrand::GALLERY_INSPIRATION,
'caption' => is_array($entry) ? $entry['caption'] : $entry,
];
$added++;
}
$brand->gallery = $gallery;
$brand->save();
$this->command?->info(sprintf(
'Marke „%s“: Titelbild %s, %d Galeriebilder ergänzt, %d vorhanden.',
$slug,
$titleSet ? 'gesetzt' : 'behalten',
$added,
count($gallery) - $added,
));
}
}
}

View file

@ -15,6 +15,7 @@ use Illuminate\Database\Seeder;
* Rotpunkt Zeichenflaeche = Inhalt → fuellt die Box, gilt als Bezugsgroesse
* CABINET Zeichenflaeche rechts +18 % → rendert 15 % kleiner als Rotpunkt
* Februe Zeichenflaeche oben/unten +20 % → rendert wie vor der Abstimmung
* Otterpohl knapp beschnitten, weiss freigestellt (Zulieferung 17.09.2026, Nachtrag 15)
*
* Alle drei waren in der Zulieferung sehr unterschiedlich stark umrandet (CABINET nutzte nur
* 29 Prozent der Hoehe) und wirkten dadurch ungleich gross. Die Abstimmung steckt bewusst in den
@ -38,6 +39,7 @@ class BrandLogoSeeder extends Seeder
'cabinet' => 'marken/cabinet.svg',
'rotpunkt' => 'marken/rotpunkt.svg',
'februe' => 'marken/februe.png',
'tischlerei' => 'marken/otterpohl.png',
];
public function run(): void

View file

@ -51,8 +51,8 @@ class BrandOriginSeeder extends Seeder
'origin_city' => ['de' => 'Rheda-Wiedenbrück', 'en' => 'Rheda-Wiedenbrück'],
'origin_distance' => ['de' => 'rund 35 km', 'en' => 'about 35 km'],
'origin_role' => [
'de' => 'Unsere Werkstatt für Aufbau, Sonderlösungen und Veredelung.',
'en' => 'Our workshop for assembly, custom solutions and finishing.',
'de' => 'Tischlerei und Innenausbau aus Rheda-Wiedenbrück – vom einzelnen Möbel bis zum ganzen Raum, inklusive Montage.',
'en' => 'Joinery and interior fit-out from Rheda-Wiedenbrück – from a single piece to the whole room, installation included.',
],
],
];

View file

@ -22,6 +22,12 @@ use Illuminate\Database\Seeder;
* keine Zahlen, Zertifizierungen oder Herstelleraussagen, die niemand belegen kann. Er braucht
* die Freigabe durch Marcel Scheibe, bevor er live geht.
*
* Feedback 17.09.2026 (dev/2026-08-28/feedback-17-09-2026.md) eingearbeitet: CABINET Einstieg
* „Bild und grobes Maß“ und die Fragen vorab; Febrü „Kunden – keine Schubladen“, „Aus Herford“
* und „Wertschätzung“, doppelter Schlusssatz entfernt; Rotpunkt „Mehr als Küchen“. Die
* markeneigenen Handwerk-Abschnitte sind entfallen: das Thema steht als gemeinsamer Abschluss
* auf jeder Markenseite (Lang-Sektion `brand_craft`, siehe BrandDetail).
*
* ⚠️ Überschreibt bei bestehenden Marken **nur** die Felder aus {@see FIELDS}. Bilder, Galerie,
* Leistungen und Reihenfolge bleiben unangetastet, damit gepflegte Inhalte erhalten bleiben.
*
@ -95,11 +101,14 @@ class BrandPageTextSeeder extends Seeder
],
],
[
'title' => 'Aufmaß vor Ort statt Schätzung am Telefon',
'title' => 'Ein Bild und ein grobes Maß reichen für den Anfang',
'paragraphs' => [
'Jede Planung beginnt bei uns mit dem Aufmaß in Ihren Räumen. Das klingt selbstverständlich, ist es aber nicht: Wände sind selten exakt im Lot, Böden selten vollkommen eben, und alte Häuser halten regelmäßig Überraschungen bereit.',
'Wir messen deshalb vor Ort und planen mit den tatsächlichen Maßen. Das kostet zu Beginn etwas mehr Zeit und erspart am Montagetag die unangenehme Sorte Überraschung.',
'Für den Anfang reicht uns ein Bild und ein grobes Maß völlig aus.',
'Sie müssen also nicht erst alles millimetergenau ausmessen. Ein paar Fotos von der Raumsituation und die ungefähren Maße reichen, damit wir schon eine gute Planung machen und Ihnen ein konkretes Angebot erstellen können.',
'Danach gehen wir gemeinsam ins Detail: Aufteilung, Ausstattung, Materialien, Farben und natürlich der Preis.',
'Wenn alles grundsätzlich passt, kommen wir zum professionellen Aufmaß bei Ihnen vor Ort. Danach machen wir die Feinplanung fertig und geben den Schrank zur Bestellung frei.',
],
'claim' => 'So läuft’s: Foto + grobe Maße → Beratung → Planung & Angebot → Aufmaß vor Ort → Feinplanung → Bestellung',
],
[
'title' => 'Innen entscheidet sich, ob ein Schrank funktioniert',
@ -111,7 +120,7 @@ class BrandPageTextSeeder extends Seeder
'title' => 'Die Fragen, die wir vorher stellen',
'paragraphs' => [
'Wie viel Hängefläche wird wirklich gebraucht und wie viel Fachboden? Wo sollen Schuhe, Koffer, Bettwäsche, Sportsachen oder Weihnachtsdeko hin? Wer nutzt den Schrank – und wie groß ist diese Person?',
'Auch der weniger repräsentative Teil gehört dazu: Wäschekörbe, Bügelbrett, Staubsauger und die Kiste mit den Kabeln, die man aus Prinzip nicht wegwirft. Wird das vorher besprochen, muss es hinterher nicht improvisiert werden.',
'Auch die Dinge, die man nicht jeden Tag braucht, gehören in die Planung: Wäschekörbe, Bügelbrett, Staubsauger, Karnevalskostüme oder besondere Sportsachen. Wenn wir vorher wissen, was untergebracht werden soll, können wir den Platz direkt sinnvoll dafür einplanen.',
],
],
],
@ -153,15 +162,6 @@ class BrandPageTextSeeder extends Seeder
'Sie müssen sich also nicht allein auf unser Verkaufsgespräch verlassen, sondern entscheiden auf Grundlage dessen, was Sie tatsächlich gesehen und angefasst haben.',
],
],
[
'title' => 'Schranksystem plus Handwerk',
'paragraphs' => [
'CABINET deckt bereits eine große planerische Bandbreite ab. Es geht deshalb ausdrücklich nicht um Sonderanfertigungen um jeden Preis.',
'Natürlich gibt es trotzdem diesen einen Fall, bei dem selbst ein hervorragendes Maßsystem an seine Grenze kommt. Dann greift unsere dauerhaft verankerte Schnittstelle zu angeschlossenen Tischlereien – für den Übergang zur Architektur, die besondere Verkleidung oder den letzten Millimeter.',
'Wir wägen gestalterischen Mehrwert, technische Notwendigkeit und wirtschaftliche Sinnhaftigkeit ehrlich miteinander ab. Am Ende entscheiden aber Ihre Wünsche – nicht unser Drang, Ihnen noch eine Sonderlösung zu verkaufen.',
],
'claim' => 'Die Möglichkeiten eines Maßsystems. Die Flexibilität des Handwerks.',
],
],
],
'rotpunkt' => [
@ -235,9 +235,9 @@ class BrandPageTextSeeder extends Seeder
[
'title' => 'Mehr als Küchen',
'paragraphs' => [
'Die Möglichkeiten von Rotpunkt enden nicht bei der klassischen Küche. Das Möbel- und Planungssystem lässt sich auch für Hauswirtschaftsräume, Ankleiden, Badezimmer und weitere Wohnbereiche einsetzen.',
'Materialien, Farben und Gestaltungselemente können über mehrere Räume hinweg aufgegriffen werden. So entsteht nicht in jedem Zimmer eine neue Idee, sondern ein zusammenhängendes Interior-Konzept mit einer gemeinsamen gestalterischen Sprache.',
'Zusammen mit CABINET, Febrü und weiteren ausgewählten Partnern betrachten wir bei Bedarf den gesamten Innenraum – von Möbeln und Beleuchtung bis zu Tapeten, Fußböden und, wenn es sein soll, der letzten Blume.',
'Die Möglichkeiten von Rotpunkt enden nicht bei der klassischen Küche. Das Möbel- und Planungssystem lässt sich auch für Hauswirtschaftsräume, Badezimmer, Sideboards, Wohnmöbel und weitere Bereiche rund um das Wohnen einsetzen.',
'Materialien, Farben und Gestaltungselemente können dabei über mehrere Räume hinweg aufgegriffen werden. So entsteht ein stimmiges Gesamtbild, statt in jedem Raum wieder ganz von vorne anzufangen.',
'Bei Bedarf denken wir den Innenraum deshalb auch über die Küche hinaus – von Möbeln und Beleuchtung bis zu Tapeten, Fußböden und, wenn es sein soll, der letzten Blume.',
],
],
[
@ -257,22 +257,6 @@ class BrandPageTextSeeder extends Seeder
'Wir begleiten Sie dabei, ordnen die unterschiedlichen Möglichkeiten ein und führen Ihre Entscheidungen zu einem stimmigen Gesamtkonzept zusammen.',
],
],
[
'title' => 'Küchenstudio plus Handwerk',
'paragraphs' => [
'Rotpunkt deckt bereits eine enorme planerische und gestalterische Bandbreite ab. Es geht deshalb ausdrücklich nicht um Sonderanfertigungen um jeden Preis.',
'Natürlich gibt es trotzdem immer diesen einen planerischen Sonderfall, bei dem selbst hervorragende Standardsysteme an ihre Grenzen kommen. Dann greift unsere dauerhaft verankerte Schnittstelle zu angeschlossenen Tischlereien.',
'Wir wägen gestalterischen Mehrwert, technische Notwendigkeit und wirtschaftliche Sinnhaftigkeit ehrlich miteinander ab. Am Ende entscheiden aber Ihre Wünsche – nicht unser Drang, Ihnen unbedingt noch eine Sonderlösung zu verkaufen.',
],
'detail' => [
'title' => 'Wenn das Handwerk den letzten Millimeter übernimmt',
'paragraphs' => [
'Möglich sind beispielsweise passgenaue Nischenlösungen, besondere Verkleidungen, raumhohe Einbauten, Übergänge zu angrenzenden Möbeln oder individuelle Anpassungen an die vorhandene Architektur.',
'Bei Bedarf endet das Konzept nicht an der Arbeitsplatte. Auch Tapeten, Fußböden, Wandgestaltung, Beleuchtung und weitere Details können einbezogen werden.',
],
],
'claim' => 'Die Möglichkeiten eines modernen Küchenstudios. Die Flexibilität des Handwerks.',
],
],
],
'februe' => [
@ -299,9 +283,8 @@ class BrandPageTextSeeder extends Seeder
'detail' => [
'title' => 'Bei uns gibt es Kunden – keine Schubladen',
'paragraphs' => [
'B2B und B2C sind Begriffe aus Präsentationen und Vertriebsschulungen. Früher hieß es Privatkunde oder Firmenkunde. Oder, noch schlimmer: A-, B- oder C-Kunde. Bei diesem Schubladendenken machen wir nicht mit.',
'Natürlich beeinflusst die Menge den Preis. Wer fünfzig Arbeitsplätze einrichtet, erhält eine andere Kalkulation als jemand, der einen einzelnen Schreibtisch für sein Homeoffice sucht. Alles andere wäre weder ehrlich noch kaufmännisch sinnvoll.',
'Die Menge verändert aber weder die Qualität des Produktes noch die Sorgfalt der Planung, die persönliche Beratung oder den Service. Ein einzelner Arbeitsplatz im privaten Zuhause verdient dieselbe Aufmerksamkeit wie eine vollständige Büroetage.',
'Ob Privatkunde oder Unternehmen: Für uns gibt es zunächst einmal Kunden mit unterschiedlichen Räumen, Anforderungen und Stückzahlen.',
'Natürlich beeinflusst die Menge den Preis. Fünfzig Arbeitsplätze werden anders kalkuliert als ein einzelner Schreibtisch fürs Homeoffice. Der Anspruch an Produkt, Beratung und Service bleibt trotzdem derselbe.',
'Febrü bleibt Febrü. B2in bleibt B2in. Und jeder Kunde erhält eine Lösung, die zu seinem tatsächlichen Bedarf passt.',
],
],
@ -310,7 +293,7 @@ class BrandPageTextSeeder extends Seeder
[
'title' => 'Aus Herford. Für moderne Arbeitswelten.',
'paragraphs' => [
'Febrü entwickelt und produziert hochwertige Büromöbel direkt im ostwestfälischen Herford – nur wenige Kilometer von Bielefeld entfernt. Eine Fertigungstiefe von 95 Prozent ermöglicht kurze Wege, verlässliche Qualität und hohe Flexibilität bei Materialien, Farben und individuellen Anforderungen.',
'Febrü entwickelt und produziert hochwertige Büromöbel direkt im ostwestfälischen Herford – nur wenige Kilometer von Bielefeld entfernt. Eine sehr hohe Fertigungstiefe ermöglicht kurze Wege, verlässliche Qualität und hohe Flexibilität bei Materialien, Farben und individuellen Anforderungen.',
],
'detail' => [
'title' => 'Von einem Schreibtisch bis zum vollständigen Bürotrakt',
@ -348,7 +331,7 @@ class BrandPageTextSeeder extends Seeder
'title' => 'Wertschätzung ist keine Frage der Stückzahl',
'paragraphs' => [
'Gerade in Zeiten des Fachkräftemangels können Unternehmen es sich nicht leisten, Arbeitsbedingungen als Nebensache zu behandeln. Wer Top-Leistung erwartet, muss die passenden Voraussetzungen schaffen. Ein Obstkorb und kostenloses Wasser sind freundlich – ersetzen aber keinen guten Arbeitsplatz.',
'Besonders deutlich wird das bei teilzeitbeschäftigten Müttern – selbstverständlich genauso bei Vätern und allen anderen Menschen mit familiärer Sorgeverantwortung. Teilzeit bedeutet nicht halbe Leistung. Oft wird in einem kürzeren Zeitfenster hoch konzentriert gearbeitet, während neben dem nächsten Onlinemeeting gleichzeitig Schule, Termine, Betreuung und damit unsere wichtigste gemeinsame Ressource organisiert werden: unser Nachwuchs.',
'Besonders deutlich wird das bei Menschen, die Beruf und Familie miteinander organisieren. Teilzeit bedeutet nicht halbe Leistung. Oft wird in einem kürzeren Zeitfenster hoch konzentriert gearbeitet, während gleichzeitig Schule, Termine, Betreuung und der ganz normale Familienalltag organisiert werden müssen.',
'Gerade hier darf der Arbeitsplatz kein Provisorium aus Küchentisch, Beistelltisch oder Schlafzimmerecke sein. Er muss sofort funktionieren, Bewegung ermöglichen, technisch durchdacht sein und sich trotzdem vernünftig in das Zuhause einfügen. Für den Menschen, der ihn täglich nutzt, ist ein einzelner Arbeitsplatz keine kleine Lösung. Er ist seine vollständige Arbeitswelt.',
],
],
@ -447,7 +430,6 @@ class BrandPageTextSeeder extends Seeder
'Durch die Nähe zwischen Bielefeld und Herford können Febrü-Produkte nach Abstimmung persönlich erlebt werden. Wir organisieren und begleiten den Besuch der Ausstellung – für einen Schreibtisch genauso wie für einen vollständigen Unternehmensstandort.',
'Dort lassen sich Materialien anfassen, Stühle und Tische ausprobieren, Akustiklösungen erleben und unterschiedliche Arbeitswelten vergleichen. Sie müssen sich nicht allein auf ein Verkaufsgespräch verlassen.',
],
'claim' => 'Ob im Kleinen oder im Großen – die Detailtreue entscheidet.',
],
],
],

View file

@ -8,11 +8,9 @@ use Illuminate\Database\Seeder;
/**
* Legt die Kernmarken an. Texte aus dev/2026-08-28/umsetzung-inhalte.md §5.
*
* Die vierte Marke „Tischlerei“ (Arbeitsplan 14.09.2026, Punkt 1.2) ist ein **Platzhalter**:
* Name, Logo, Basisinfos und Bilder liegen noch nicht vor. Die Texte folgen der inhaltlichen
* Ausrichtung aus dem Plan (Aufbau, Sonderlösungen, Veredelung, Maßarbeit – ergänzt CABINET
* und Rotpunkt, steht nicht daneben) und sollen als Denkanstoß dienen. Der Name des
* Partnerbetriebs gehört laut Plan auf die Detailseite, nicht in die Marke.
* Die vierte Marke ist Otterpohl aus Rheda-Wiedenbrück (Arbeitsplan 14.09.2026, Punkt 1.2;
* Zulieferung 17.09.2026). Ihre Texte liegen in {@see OtterpohlBrandSeeder}, der sie bei
* bestehenden Installationen auch nachträgt. Der Slug „tischlerei“ bleibt stabil.
*
* Bestehende Marken werden nicht überschrieben – Marcel pflegt sie im Admin. Für bereits
* angelegte Marken setzt {@see BrandLogoSeeder} die Logos nach und {@see BrandOriginSeeder}
@ -164,92 +162,11 @@ Consultation and planning in our kitchen studio.',
],
'is_published' => true,
],
[
// ⚠️ Platzhalter – siehe Klassenkommentar. Slug bleibt stabil, auch wenn der Name kommt.
'slug' => 'tischlerei',
'logo' => '',
'name' => ['de' => 'Tischlerei', 'en' => 'Joinery'],
'tagline' => ['de' => 'Maßarbeit, die den Raum vollendet.', 'en' => 'Craftsmanship that completes the room.'],
'teaser' => [
'de' => 'Aufbau, Sonderlösungen und Veredelung aus einer Tischlerei in Ostwestfalen – dort, wo Serienmöbel aufhören und Ihr Raum anfängt.',
'en' => 'Assembly, bespoke solutions and finishing from a joinery in East Westphalia – where series furniture ends and your room begins.',
],
'summary' => [
'de' => 'Manche Stellen im Raum passen in keinen Katalog: die Nische neben dem Kamin, der Übergang von der Küche zum Sideboard, das Regal unter der Treppe. Unsere Tischlerei baut, was dort fehlt – und veredelt, was schon da ist.
Sie ergänzt CABINET und Rotpunkt dort, wo Fertigung nach Zeichnung gefragt ist: Aufbau vor Ort, Sonderlösungen nach Aufmaß, Oberflächen und Anpassungen aus einer Hand.',
'en' => 'Some spots in a room fit no catalogue: the alcove next to the fireplace, the transition from kitchen to sideboard, the shelf under the stairs. Our joinery builds what is missing there – and refines what is already in place.
It complements CABINET and Rotpunkt wherever making to drawing is required: assembly on site, bespoke solutions to measure, surfaces and adaptations from a single source.',
],
'highlights' => [
'Sonderlösungen nach Aufmaß und Zeichnung',
'Veredelung und Anpassung bestehender Möbel',
'Aufbau und Montage aus einer Hand',
],
'description' => [
'de' => '<p>Eine Küche endet nicht an der letzten Front, ein Einbauschrank nicht an der Wand. Dazwischen liegen die Details, die einen Raum fertig machen: eine Blende, die den Übergang schließt, ein Regal, das genau in die Nische passt, eine Oberfläche, die zum Boden passt. Unsere Tischlerei fertigt diese Details in Ostwestfalen – nach Zeichnung, nach Aufmaß, in Abstimmung mit den Marken, die den Raum tragen.</p>',
'en' => '<p>A kitchen does not end at the last front, a fitted wardrobe not at the wall. In between lie the details that finish a room. Our joinery makes these details in East Westphalia – to drawing, to measure, in tune with the brands that carry the room.</p>',
],
'services' => [
['title' => 'Aufbau und Montage', 'description' => 'Fachgerechter Aufbau vor Ort – auch von Möbeln, die nicht von uns stammen.'],
['title' => 'Sonderlösungen', 'description' => 'Nischen, Schrägen, Übergänge: gefertigt nach Aufmaß, wo Standard nicht passt.'],
['title' => 'Veredelung', 'description' => 'Oberflächen, Fronten und Griffe – bestehende Möbel aufwerten statt ersetzen.'],
['title' => 'Maßarbeit', 'description' => 'Einzelstücke nach Zeichnung: Regale, Sideboards, Garderoben, Tische.'],
],
'widget_label' => ['de' => 'Persönliche Beratung zur Maßarbeit vereinbaren', 'en' => 'Arrange a personal consultation on bespoke work'],
'cta_title' => ['de' => 'Ihr Raum. Bis ins Detail gefertigt.', 'en' => 'Your room. Made down to the detail.'],
'cta_text' => [
'de' => 'Ob ein einzelnes Regal in der Nische oder die Anpassung einer ganzen Einrichtung: Wir schauen uns Ihren Raum an und sagen Ihnen, was sich fertigen lässt – und was sich lohnt. Persönlich, vor Ort und mit einer Werkstatt, die wir kennen.',
'en' => 'Whether a single shelf in an alcove or the adaptation of an entire interior: we look at your room and tell you what can be made – and what is worth it.',
],
'sections' => [
[
'title' => 'Wo Serienmöbel aufhören.',
'paragraphs' => [
'Die meisten Räume sind nicht rechtwinklig, nicht genormt und nicht leer. Sie haben Heizkörper, Fensterbänke, Schrägen und Möbel, die bleiben sollen. Genau dort beginnt die Arbeit der Tischlerei: mit einem Aufmaß, einer Zeichnung und der Frage, was der Raum wirklich braucht.',
'Das kann ein einzelnes Möbelstück sein, eine Anpassung an ein bestehendes oder ein Detail, das zwei Marken miteinander verbindet.',
],
'detail' => [
'title' => 'Beispiele aus dem Alltag',
'paragraphs' => [
'Ein Sideboard, das die Flucht der Rotpunkt-Küche aufnimmt und im Wohnzimmer weiterführt. Eine Blende, die den Spalt zwischen CABINET-Schrank und Dachschräge schließt. Ein Regal unter der Treppe, das die Tiefe der Stufen nutzt. Eine Bank am Fenster mit Stauraum darunter.',
'Nichts davon steht in einem Katalog. Alles davon lässt sich fertigen.',
],
],
'claim' => 'Gefertigt nach Raum, nicht nach Norm.',
],
[
'title' => 'Ergänzung, nicht Konkurrenz.',
'paragraphs' => [
'CABINET plant Einbauschränke bis auf den Millimeter, Rotpunkt baut Küchen, Febrü richtet Arbeitsplätze ein. Die Tischlerei steht nicht daneben, sondern dazwischen: Sie fertigt die Übergänge, die Sonderteile und die Einzelstücke, mit denen aus drei Marken ein durchgängiger Raum wird.',
],
'detail' => [
'title' => 'Aus der Region gefertigt',
'paragraphs' => [
'Die Werkstatt liegt in Ostwestfalen, wenige Kilometer von Bielefeld. Kurze Wege bedeuten kurze Abstimmungen: Aufmaß, Muster und Montage kommen aus einer Hand, und wenn etwas nicht passt, ist die Werkstatt erreichbar – nicht ein Callcenter.',
],
],
'claim' => 'Vier Marken. Ein Raum. Ein Ansprechpartner.',
],
[
'title' => 'Veredeln statt ersetzen.',
'paragraphs' => [
'Nicht jedes Möbel muss neu sein. Ein guter Korpus mit neuen Fronten, eine geölte statt lackierte Oberfläche, neue Griffe und eine passende Innenaufteilung – oft ist das die nachhaltigere und die schönere Lösung.',
'Wir sagen Ihnen ehrlich, wann sich eine Veredelung lohnt und wann nicht.',
],
'claim' => 'Was gut ist, darf bleiben – und besser werden.',
],
],
'showroom' => '',
'origin_city' => ['de' => 'Rheda-Wiedenbrück', 'en' => 'Rheda-Wiedenbrück'],
'origin_distance' => ['de' => 'rund 35 km', 'en' => 'about 35 km'],
'origin_role' => [
'de' => 'Unsere Werkstatt für Aufbau, Sonderlösungen und Veredelung.',
'en' => 'Our workshop for assembly, custom solutions and finishing.',
],
'is_published' => true,
],
// Vierte Kernmarke Otterpohl (Nachtrag 15). Slug bleibt „tischlerei“, Texte aus OtterpohlBrandSeeder.
array_merge(OtterpohlBrandSeeder::data(), [
'slug' => OtterpohlBrandSeeder::SLUG,
'logo' => OtterpohlBrandSeeder::LOGO,
]),
];
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace Database\Seeders;
use App\Models\CmsBrand;
use Illuminate\Database\Seeder;
/**
* Entfernt die Demobilder aus den Markengalerien (Nachtrag 17).
*
* {@see DemoBrandGallerySeeder} hatte Stockmotive und KI-Renderings der alten Dubai-Seite als
* Beispiele in die Galerien von CABINET, Rotpunkt und Febrü gelegt. Seit den echten Bildern
* (Nachtrag 16) sollen sie weg. Erkannt werden sie am Dateinamen aus dem Demo-Seeder – nicht an
* der Bildunterschrift, denn den Vermerk „BEISPIEL“ hat {@see BrandGalleryCaptionNormalizerSeeder}
* auf der Vorschau bereits entfernt. Alle anderen Galeriebilder bleiben samt Kategorie,
* Unterschrift und Reihenfolge. Mehrfaches Ausführen ist unbedenklich.
*
* php artisan db:seed --class=DemoBrandGalleryCleanupSeeder
*/
class DemoBrandGalleryCleanupSeeder extends Seeder
{
public function run(): void
{
$demoImages = [];
foreach (DemoBrandGallerySeeder::GALLERIES as $items) {
foreach ($items as $item) {
$demoImages[] = $item[0];
}
}
$removed = 0;
foreach (CmsBrand::query()->whereNotNull('gallery')->get() as $brand) {
$gallery = CmsBrand::normalizeGallery($brand->gallery);
$kept = array_values(array_filter(
$gallery,
fn (array $item) => ! in_array($item['image'], $demoImages, true),
));
if (count($kept) === count($gallery)) {
continue;
}
$removed += count($gallery) - count($kept);
$brand->gallery = $kept;
$brand->save();
}
$this->command?->info("Demobilder aus den Galerien entfernt: {$removed}.");
}
}

View file

@ -17,8 +17,8 @@ use Illuminate\Database\Seeder;
* bzw. „Visualisierung“) – die Motive der Kategorie `project` muessen deshalb vor dem Livegang
* durch echte Referenzen ersetzt werden. Steht als Punkt in der Deployment-Checkliste.
*
* Wieder entfernen:
* php artisan tinker --execute="App\Models\CmsBrand::query()->update(['gallery' => null]);"
* Wieder entfernen – nur die Demobilder, gepflegte Bilder bleiben:
* php artisan db:seed --class=DemoBrandGalleryCleanupSeeder
*/
class DemoBrandGallerySeeder extends Seeder
{
@ -27,7 +27,7 @@ class DemoBrandGallerySeeder extends Seeder
*
* @var array<string, list<array{0: string, 1: string, 2: string}>>
*/
private const GALLERIES = [
public const GALLERIES = [
'cabinet' => [
['b2a.webp', CmsBrand::GALLERY_PROJECT, 'Wohnwand mit Einbauregal, Bielefeld'],
['hero-room.webp', CmsBrand::GALLERY_PROJECT, 'Wohnbereich mit Raumteiler'],

View file

@ -0,0 +1,79 @@
<?php
namespace Database\Seeders;
use FluxCms\Core\Models\CmsContent;
use FluxCms\Core\Services\CmsContentService;
use Illuminate\Database\Seeder;
/**
* Hero der Startseite (Abstimmung 01.10.2026, Briefing Marcel 24.09.2026 Punkte 3 und 5).
*
* Setzt im CMS-Eintrag home/hero die neue Überschrift „Räume aus einer Hand. Mit starken Marken
* und persönlichen Lösungen.“ und entfernt den zweiten Button „Termin anfragen“ (der feste
* Termin-Button unten rechts bleibt). Die Überschrift wird nur ersetzt, solange noch die alte
* „B2in Interior – Premium-Einrichtung …“ drinsteht – eine eigene Formulierung im CMS bleibt.
* Mehrfaches Ausführen ist unbedenklich.
*
* php artisan db:seed --class=HomeHeroTextSeeder
*/
class HomeHeroTextSeeder extends Seeder
{
/** @var array<string, array{old: string, new: string}> */
public const TITLES = [
'de' => [
'old' => 'Premium-Einrichtung von starken Marken',
'new' => '<span class="text-secondary">Räume aus einer Hand.</span><br>Mit starken Marken und persönlichen Lösungen.',
],
'en' => [
'old' => 'Premium furnishing from strong brands',
'new' => '<span class="text-secondary">Spaces from a single source.</span><br>With strong brands and personal solutions.',
],
];
public function run(): void
{
$row = CmsContent::query()->where('group', 'home')->where('key', 'hero')->first();
if (! $row) {
$this->command?->info('Hero: Sektion home/hero nicht im CMS – die Lang-Datei gilt.');
return;
}
$value = $row->getTranslations('value');
$changed = false;
foreach ($value as $locale => $content) {
if (! is_array($content)) {
continue;
}
$title = self::TITLES[$locale] ?? null;
if ($title && str_contains((string) ($content['title'] ?? ''), $title['old'])) {
$content['title'] = $title['new'];
$changed = true;
}
if (array_key_exists('cta2_text', $content) || array_key_exists('cta2_link', $content)) {
unset($content['cta2_text'], $content['cta2_link']);
$changed = true;
}
$value[$locale] = $content;
}
if (! $changed) {
$this->command?->info('Hero: bereits aktuell, nichts geändert.');
return;
}
$row->value = $value;
$row->save();
app(CmsContentService::class)->clearCache('home');
$this->command?->info('Hero: Überschrift gesetzt, zweiter Button entfernt.');
}
}

View file

@ -0,0 +1,193 @@
<?php
namespace Database\Seeders;
use App\Models\CmsBrand;
use Illuminate\Database\Seeder;
/**
* Otterpohl Möbelservice, Rheda-Wiedenbrück – die vierte Kernmarke (Nachtrag 15, Text v2 in Nachtrag 19).
*
* Ersetzt den Platzhalter „Tischlerei“ aus Nachtrag 9 durch die Zulieferung:
* dev/2026-08-28/otterpohl_Webseitentext_FINAL_v2.md (Text vom 24.09.2026, Überschriften 1:1 übernommen;
* löst die lange Fassung vom 17.09. ab)
* dev/2026-08-28/markenlogos/otterpohl.jpg (Logo, freigestellt nach public/img/assets/marken/)
*
* Der Slug `tischlerei` bleibt stabil (Links, Widget-Thema, Kartenposition der Herkunfts-Sektion).
* Überschrieben werden Name, Claim, Texte, Leistungen, Abschnitte, Abschlussblock und
* Fertigungsort. Bild, Galerie und Reihenfolge bleiben, das Logo wird nur gesetzt, wenn es leer ist.
* Ausnahme seit 01.10.2026: Steht noch das alte Werkstatt-Titelbild drin, tauscht es mit der
* Treppe aus der Galerie ({@see swapTitleImage()}). Dazu kam der Abschnitt „Montage“.
* {@see CmsBrandSeeder} nutzt dieselben Daten für frische Installationen.
*
* php artisan db:seed --class=OtterpohlBrandSeeder
*/
class OtterpohlBrandSeeder extends Seeder
{
public const SLUG = 'tischlerei';
public const LOGO = 'marken/otterpohl.png';
/** Titelbild seit 01.10.2026: dev/2026-08-28/images/otten/tittle_otter.png als WebP. */
public const TITLE_IMAGE = 'marken/otterpohl/titel-treppe.webp';
/** Titelbild bis 01.10.2026 – rückt in die Galerie. */
public const FORMER_TITLE_IMAGE = 'marken/otterpohl/werkstatt-titel.webp';
public const FORMER_TITLE_CAPTION = 'Fertigung in der Werkstatt in Rheda-Wiedenbrück';
/** Galeriebild mit demselben Motiv wie das neue Titelbild – fällt beim Tausch heraus. */
public const TITLE_IMAGE_IN_GALLERY = 'marken/otterpohl/referenz-luecke-01.webp';
public function run(): void
{
$brand = CmsBrand::query()->where('slug', self::SLUG)->first();
if (! $brand) {
$this->command?->warn('Marke „'.self::SLUG.'“ nicht gefunden – erst CmsBrandSeeder ausführen.');
return;
}
$brand->fill(self::data());
if (trim((string) $brand->logo) === '') {
$brand->logo = self::LOGO;
}
$swapped = self::swapTitleImage($brand);
$brand->save();
$this->command?->info('Otterpohl: Marke „'.self::SLUG.'“ mit den Texten vom 24.09.2026 (v2) und dem Abschnitt „Montage“ aktualisiert'
.($swapped ? ', Titelbild getauscht.' : '.'));
}
/**
* Tauscht Titelbild und Produktbild (Abstimmung 01.10.2026): Die Treppe mit Lamellenwand wird
* Titelbild, das bisherige Werkstattbild nimmt ihren Platz in der Galerie ein (Aus der Werkstatt).
*
* Nur solange noch das alte Titelbild gesetzt ist – ein im Backend gewähltes Bild bleibt.
*/
public static function swapTitleImage(CmsBrand $brand): bool
{
if ($brand->image !== self::FORMER_TITLE_IMAGE) {
return false;
}
$brand->image = self::TITLE_IMAGE;
$gallery = CmsBrand::normalizeGallery($brand->gallery);
$former = ['image' => self::FORMER_TITLE_IMAGE, 'category' => CmsBrand::GALLERY_COMPANY, 'caption' => self::FORMER_TITLE_CAPTION];
$position = array_search(self::TITLE_IMAGE_IN_GALLERY, array_column($gallery, 'image'), true);
if ($position !== false) {
$gallery[$position] = $former;
} elseif (! in_array(self::FORMER_TITLE_IMAGE, array_column($gallery, 'image'), true)) {
$gallery[] = $former;
}
$brand->gallery = array_values($gallery);
return true;
}
/**
* Alle Textfelder der Marke. Ohne slug, logo, image, gallery, order.
*
* Reihenfolge wie in der Zulieferung v2: Einleitung „Wollmilchsau“, zwei Abschnitte, die acht
* Leistungen, zum Schluss „Das Ganze statt nur das Einzelne“ – der steht als Abschlussblock
* (cta_title/cta_text), damit er auf der Seite hinter Leistungen und Galerie bleibt.
*
* @return array<string, mixed>
*/
public static function data(): array
{
return [
'name' => ['de' => 'Otterpohl Möbelservice', 'en' => 'Otterpohl Möbelservice'],
'tagline' => ['de' => 'Nicht easy click & buy. Aber dennoch easy to get.', 'en' => 'Not easy click & buy. But still easy to get.'],
'teaser' => [
'de' => 'Individuelle Möbel, Türen, Treppen, Küchen, Wandverkleidungen und Objektbau – bis hin zum kompletten Innenausbau. Tischlerhandwerk aus Rheda-Wiedenbrück.',
'en' => 'Bespoke furniture, doors, stairs, kitchens, wall cladding and contract work – up to complete interior fit-outs. Joinery from Rheda-Wiedenbrück.',
],
'summary' => [
'de' => 'Individuelle Möbel. Türen. Treppen. Küchen. Wandverkleidungen. Objektbau. Sonderanfertigungen. Lackierungen und Echtholzfurniere. Bis hin zum kompletten Innenausbau. Mit Otterpohl Möbelservice haben wir einen Partner an unserer Seite, bei dem die Möglichkeiten nicht am einzelnen Möbelstück aufhören.
Denn manchmal braucht ein Raum mehr als einen Schrank oder ein Sideboard. Vielleicht soll eine Treppe Teil des Gestaltungskonzepts werden. Eine Wand erhält eine individuelle Holzverkleidung. Oder Möbel, Materialien und bauliche Elemente müssen so zusammenspielen, dass am Ende alles wie aus einem Guss wirkt. Genau dort beginnt der individuelle Innenausbau.',
'en' => 'Bespoke furniture. Doors. Stairs. Kitchens. Wall cladding. Contract work. Special constructions. Lacquer and real-wood veneer. Up to complete interior fit-outs. With Otterpohl Möbelservice we have a partner whose possibilities do not end at the single piece of furniture.
Sometimes a room needs more than a wardrobe or a sideboard. A staircase becomes part of the design. A wall gets individual wood cladding. Or furniture, materials and built elements have to work together so that everything looks as if cast from one mould. That is where individual interior fit-out begins.',
],
'highlights' => [
'Individuelle Möbel, Türen, Treppen und Küchen',
'Innenausbau, Objektbau, Lack und Echtholzfurnier',
'Planung und Handwerk aus einer Hand – inklusive Montage',
],
'description' => ['de' => '', 'en' => ''],
'services' => [
['title' => 'Individuelle Möbel & Sonderanfertigungen', 'description' => 'Möbel nach Maß, besondere Konstruktionen und individuelle Lösungen – nach Planung, Idee, Skizze oder konkreter Anforderung.'],
['title' => 'Innenausbau', 'description' => 'Von einzelnen Ausbauarbeiten bis zum ganzheitlichen Raumkonzept – inklusive individueller Verkleidungen und maßgefertigter Einbauten.'],
['title' => 'Türen & Treppen', 'description' => 'Individuelle Türen, Treppen und konstruktive Lösungen, die sich in Architektur und Einrichtung einfügen.'],
['title' => 'Küchen', 'description' => 'Individuelle Küchenlösungen und handwerkliche Ergänzungen auch dort, wo klassische Küchenprogramme an ihre Grenzen kommen.'],
['title' => 'Wandverkleidungen & besondere Details', 'description' => 'Holzverkleidungen, Lamellen, Paneele und individuelle Gestaltungselemente, die Architektur und Einrichtung miteinander verbinden.'],
['title' => 'Objektbau', 'description' => 'Individuelle Lösungen für Büros, Verkaufsräume und gewerblich genutzte Flächen – vom Einzelmöbel bis zum kompletten Ausbau.'],
['title' => 'Lack & Echtholzfurnier', 'description' => 'Individuelle Lackierungen und Furniere für Lösungen, bei denen Farbe, Material und Oberfläche exakt aufeinander abgestimmt werden sollen.'],
['title' => 'Montage', 'description' => 'Die Umsetzung endet nicht bei der Fertigung. Entscheidend ist, dass Möbel, Ausbau und Raum am Ende auch vor Ort sauber zusammenkommen.'],
],
'sections' => [
[
'title' => 'Ziemlich nah an der eierlegenden Wollmilchsau',
'paragraphs' => [
'Der Begriff ist vielleicht nicht besonders elegant. Aber ganz falsch ist er nicht.',
'Individuelle Möbel. Türen. Treppen. Küchen. Wandverkleidungen. Objektbau. Sonderanfertigungen. Lackierungen und Echtholzfurniere. Bis hin zum kompletten Innenausbau.',
'Mit Otterpohl Möbelservice haben wir einen Partner an unserer Seite, bei dem die Möglichkeiten nicht am einzelnen Möbelstück aufhören.',
'Denn manchmal braucht ein Raum mehr als einen Schrank oder ein Sideboard. Vielleicht soll eine Treppe Teil des Gestaltungskonzepts werden. Eine Wand erhält eine individuelle Holzverkleidung. Eine Tür soll sich selbstverständlich in den Raum einfügen. Oder Möbel, Materialien und bauliche Elemente müssen so zusammenspielen, dass am Ende alles wie aus einem Guss wirkt.',
],
'claim' => 'Genau dort beginnt der individuelle Innenausbau.',
],
[
'title' => 'Vom einzelnen Möbel bis zum ganzen Raum',
'paragraphs' => [
'Wir arbeiten mit Herstellern, die schon sehr viele individuelle Möglichkeiten bieten. CABINET, Rotpunkt und Febrü geben uns dafür eine starke Basis. Aber es gibt Projekte, bei denen die Lösung darüber hinausgeht.',
'Mit Otterpohl Möbelservice können wir deshalb nicht nur individuelle Möbel und Sonderanfertigungen realisieren, sondern auch Türen, Treppen, Küchen, Wand- und Deckenverkleidungen sowie komplette Innenausbauten und Lösungen für den Objektbereich einbeziehen.',
],
'claim' => 'So können wir Räume ganzheitlicher denken – und Details nicht isoliert betrachten.',
],
[
'title' => 'Planung trifft Handwerk',
'paragraphs' => [
'Manchmal entsteht die beste Lösung aus einem Möbelprogramm. Manchmal braucht es eine individuelle Ergänzung. Und manchmal muss etwas komplett neu gedacht und gebaut werden. Genau diese Kombination macht für uns den Unterschied.',
'Wir planen den Raum und verbinden bei Bedarf unterschiedliche Hersteller, Materialien und handwerkliche Lösungen miteinander. Otterpohl Möbelservice ergänzt diese Planung um die Möglichkeiten einer klassischen Tischlerei und eines erfahrenen Innenausbauers.',
],
'claim' => 'Das Ergebnis kann ein einzelnes Möbel sein. Oder ein kompletter Raum.',
],
// Ergänzung vom 01.10.2026. Absätze mit „- “ erscheinen als Liste, der Teil vor dem
// ersten Doppelpunkt fett ({@see CmsBrand::paragraphBlocks()}).
[
'title' => 'Montage',
'paragraphs' => [
'Eine perfekte Montage ist keine Glückssache, sondern das Ergebnis starker Logistik. Wir sorgen dafür, dass alles Hand in Hand geht:',
'- Sichere Prüfung & Terminierung: Wir vereinbaren Ihren Montagetermin erst, wenn Ihre Lieferung vollständig und unversehrt bei uns geprüft wurde. Das garantiert Ihnen absolute Planungssicherheit.',
'- Perfekt vorbereitete Lieferung: Wir prüfen vorab die Anliefersituation bei Ihnen vor Ort. Die Lieferung erfolgt mit unserem modernen Fuhrpark, optimal geschützt durch Spezialfolien und Decken.',
'- Flexibler Renovierungsschutz: Ihr Bauprojekt verzögert sich? Kein Problem. Wir lagern Ihre Möbel fachgerecht und sicher ein.',
'- Erweiterter Komfort: Auf Wunsch liefern wir Umzugskartons, lagern Möbel zwischen oder passen vorhandene Möbelstücke direkt an.',
],
],
],
'widget_label' => ['de' => 'Persönliche Beratung zur Maßarbeit vereinbaren', 'en' => 'Arrange a personal consultation on bespoke work'],
'cta_title' => ['de' => 'Das Ganze statt nur das Einzelne', 'en' => 'The whole, not just the single piece'],
'cta_text' => [
'de' => 'Genau deshalb passt Otterpohl Möbelservice zu unserem Ansatz: Wir möchten Räume nicht in einzelne Gewerke zerlegen, sondern Lösungen schaffen, bei denen Möbel, Materialien, Architektur und handwerkliche Details zusammenpassen. Mal braucht es dafür einen Hersteller. Mal einen Tischler. Und manchmal beides. Nicht easy click & buy. Aber dennoch easy to get.',
'en' => 'That is why Otterpohl Möbelservice fits our approach: we do not want to split rooms into single trades, but create solutions where furniture, materials, architecture and craft details belong together. Sometimes that takes a manufacturer. Sometimes a joiner. And sometimes both. Not easy click & buy. But still easy to get.',
],
'origin_city' => ['de' => 'Rheda-Wiedenbrück', 'en' => 'Rheda-Wiedenbrück'],
'origin_distance' => ['de' => 'rund 35 km', 'en' => 'about 35 km'],
'origin_role' => [
'de' => 'Tischlerei und Innenausbau aus Rheda-Wiedenbrück – vom einzelnen Möbel bis zum ganzen Raum, inklusive Montage.',
'en' => 'Joinery and interior fit-out from Rheda-Wiedenbrück – from a single piece to the whole room, installation included.',
],
'showroom' => '',
'is_published' => true,
];
}
}

View file

@ -0,0 +1,83 @@
<?php
namespace Database\Seeders;
use FluxCms\Core\Models\CmsContent;
use FluxCms\Core\Services\CmsContentService;
use Illuminate\Database\Seeder;
/**
* Hinterlegt die WhatsApp-Business-Nummer des Stores (Abstimmung 23.09.2026, Nachtrag 18).
*
* Der WhatsApp-Button im Terminanfrage-Widget liest `global.contact.whatsapp`. Auf der Vorschau
* stand dort der Platzhalter „+49 151 00000000“ aus der ersten Einrichtung, danach kurz die
* falsche Nummer vom 23.09. Der Seeder trägt die echte Nummer ein, wenn das Feld leer ist oder
* einen dieser beiden Werte enthält; eine im Admin gepflegte andere Nummer bleibt stehen. Fehlt der Eintrag `global.contact` ganz, wird er
* angelegt. Mehrfaches Ausführen ist unbedenklich.
*
* php artisan db:seed --class=WhatsappNumberSeeder
*/
class WhatsappNumberSeeder extends Seeder
{
/** Business-Nummer laut Marcel (0521-98620100, korrigiert am 24.09.), international geschrieben. */
public const NUMBER = '+49 521 98620100';
/**
* Werte, die der Seeder ersetzt: der Platzhalter aus der ersten Einrichtung und die am 23.09.
* versehentlich eingetragene falsche Nummer (Zahlendreher in der Zulieferung).
*
* @var array<int, string>
*/
private const PLACEHOLDERS = ['+49 151 00000000', '+49 521 98610020'];
/** @var array<int, string> */
private const LOCALES = ['de', 'en'];
public function run(): void
{
$row = CmsContent::query()->where('group', 'global')->where('key', 'contact')->first();
if (! $row) {
CmsContent::create([
'group' => 'global',
'key' => 'contact',
'type' => 'json',
'value' => array_fill_keys(self::LOCALES, ['whatsapp' => self::NUMBER]),
'order' => 0,
]);
app(CmsContentService::class)->clearCache('global');
$this->command?->info('WhatsApp-Nummer: Eintrag global.contact angelegt.');
return;
}
$value = $row->getTranslations('value');
$changed = 0;
foreach (self::LOCALES as $locale) {
$section = is_array($value[$locale] ?? null) ? $value[$locale] : [];
$current = trim((string) ($section['whatsapp'] ?? ''));
if ($current !== '' && ! in_array($current, self::PLACEHOLDERS, true)) {
continue;
}
$section['whatsapp'] = self::NUMBER;
$value[$locale] = $section;
$changed++;
}
if ($changed === 0) {
$this->command?->info('WhatsApp-Nummer: bereits gepflegt, nichts geändert.');
return;
}
$row->value = $value;
$row->save();
app(CmsContentService::class)->clearCache('global');
$this->command?->info("WhatsApp-Nummer gesetzt ({$changed} Sprache(n)).");
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View file

@ -38,9 +38,12 @@ ausführlichen Text. `php artisan test --compact`: **873 grün** (3217 Assertion
| AP5 Rubrik-Seite „Referenzen" | ✅ |
| Arbeitsplan 14.09. · 1.1 Farbwelt, Logo je Welt, Welten-Wechsler | ✅ (Nachtrag 6) |
| Welten-Trennung: Navigation, Magazin, Über uns, FAQ, Widget je Welt | ✅ (Nachtrag 7, Erweiterung zu 1.1) |
| Arbeitsplan 14.09. · 1.2 Tischlerei als vierte Marke (Platzhalter) | ✅ (Nachtrag 9) |
| Arbeitsplan 14.09. · 1.2 Tischlerei als vierte Marke | ✅ Otterpohl mit Text und Logo (Nachtrag 9 + 15) |
| Arbeitsplan 14.09. · 1.4 Akkordeon und Widget-Beschriftung je Marke | ✅ (bereits seit 03.09., CABINET-Text fehlt im CMS) |
| Arbeitsplan 14.09. · 1.3 Herkunfts-Sektion „Aus der Region gefertigt“ | ✅ (Nachtrag 10 + 11: Standorte an der Marke, Ablauf CABINET von Marcel bestätigen lassen) |
| Abstimmung 23.09. · WhatsApp zuerst im Widget, Business-Nummer, Farbvariante „Modern“ mit Schalter | ✅ (Nachtrag 18, Palette von Marcel entscheiden lassen) |
| Briefing 24.09. · Entwurf Startseite „Greige“ mit Gold-Logo, Hero-Slideshow, Projekttext | ✅ Entwurf unter /entwurf (Nachtrag 19); offen: Foto Marcel, Slides selbst pflegen, Logo-Verläufe |
| Zulieferung 24.09. · Otterpohl Text v2, Bilder (Werkstatt + Referenzen) | ✅ (Nachtrag 19, Unterschriften und Referenzen-Fallback von Marcel bestätigen lassen) |
### Nächste Schritte
@ -55,7 +58,7 @@ gebaut. Was fehlt, ist Material, kein Code.
| 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` |
| WhatsApp-Nummer – seit Nachtrag 18 per Seeder gesetzt (0521-98620100), Änderung weiter hier | Inhalte → `global` → `contact` → `whatsapp` |
| Welches Display auf der Website läuft | Inhalte → `referenzen` → `display_feed` → `display_id` |
**Beim Deployment mit erledigen (Abschnitt 8):**
@ -1267,7 +1270,7 @@ php artisan db:seed --class=BrandLogoSeeder --force
# Die Migration ordnet die drei Dubai-Beitraege der Immobilien-Welt zu.
php artisan db:seed --class=WorldSplitCmsSeeder --force
# 8. Vierte Kernmarke „Tischlerei“ als Platzhalter (Nachtrag 9) – legt nur an, was fehlt.
# 8. Vierte Kernmarke Otterpohl, Slug „tischlerei“ (Nachtrag 9 + 15) – legt nur an, was fehlt.
php artisan db:seed --class=CmsBrandSeeder --force
# 9. Herkunfts-Sektion „Aus der Region gefertigt“ (Nachtrag 10) – legt die Sektion nur an, wenn sie
@ -1278,6 +1281,41 @@ php artisan db:seed --class=OriginSectionCmsSeeder --force
# deren Ort noch leer ist. Die Migration davor legt die drei Spalten an.
php artisan db:seed --class=BrandOriginSeeder --force
# 11. Otterpohl (Nachtrag 15 + 19): ersetzt den Platzhalter „Tischlerei“ durch Name, Claim, Texte,
# Leistungen, Abschnitte, Abschlussblock und Fertigungsort aus der Zulieferung v2 vom 24.09.
# Überschreibt auch einen Stand mit dem Text vom 17.09. Bild, Galerie und Reihenfolge
# bleiben; das Logo wird nur gesetzt, wenn es leer ist.
# Schritt 4 (BrandPageTextSeeder) bringt das Feedback vom 17.09. für CABINET, Rotpunkt
# und Febrü mit – er überschreibt dort wie dokumentiert die Textfelder.
php artisan db:seed --class=OtterpohlBrandSeeder --force
# 12. Bilder für CABINET, Rotpunkt, Febrü (Nachtrag 16) und Otterpohl (Nachtrag 19): Titelbild nur,
# wenn keins gepflegt ist; Galeriebilder nur, wenn die Datei noch nicht in der Galerie steht.
# Die WebP-Dateien liegen unter public/img/assets/marken/{cabinet,rotpunkt,februe,otterpohl}/
# im Repository.
php artisan db:seed --class=BrandImageSeeder --force
# 13. Demobilder aus den Galerien entfernen (Nachtrag 17) – erkennt sie am Dateinamen des
# Demo-Seeders, echte Bilder bleiben. Nach Schritt 12 ausführen.
php artisan db:seed --class=DemoBrandGalleryCleanupSeeder --force
# 14. WhatsApp-Business-Nummer 0521-98620100 (Nachtrag 18): ersetzt nur den Platzhalter
# „+49 151 00000000“, die falsche Nummer vom 23.09. (…98610020) oder ein leeres Feld –
# eine im Admin gepflegte andere Nummer bleibt.
php artisan db:seed --class=WhatsappNumberSeeder --force
# 15. Projekttext auf der Startseite (Briefing 24.09., Punkt 7; Nachtrag 19): ersetzt in
# home/audience_switch nur den Satz „Mit deutscher Vertragssicherheit …“. Idempotent.
php artisan db:seed --class=AudienceSwitchProjectTextSeeder --force
# 16. Abstimmung 01.10. (Nachtrag 20): Hero-Überschrift „Räume aus einer Hand.“ und zweiten
# Hero-Button entfernen (nur wenn noch die alte Überschrift im CMS steht), Projekttext mit
# Punkt (Schritt 15 erneut, idempotent), Otterpohl: Titelbild tauschen + Abschnitt „Montage“.
# Die Hinweisleiste ist per Standard aus (B2IN_ANNOUNCEMENT_BAR, nichts in die .env nötig).
php artisan db:seed --class=HomeHeroTextSeeder --force
php artisan db:seed --class=AudienceSwitchProjectTextSeeder --force
php artisan db:seed --class=OtterpohlBrandSeeder --force
php artisan config:cache
php artisan view:clear
@ -1312,6 +1350,15 @@ done
- [ ] Logo in Header, Footer **und** in einer Test-Mail
- [ ] Alte Magazin-URLs `/magazin/1` … `/magazin/5` leiten per 301 auf den Slug
- [ ] `/service` und `/portfolio` leiten um statt 500 zu liefern
- [ ] Terminanfrage-Widget: WhatsApp-Button oben, Link führt auf `wa.me/4952198620100`, darunter
„oder per Formular“ und das Formular
- [ ] Interior-Seiten laufen in „Neu“ (`<html class="palette-modern">`), kein Farb-Schalter;
`/immobilien` unverändert blau
- [ ] `/entwurf`: Gold-Logo in Kopf und Fuß, Hero mit Goldkante und laufender Slideshow (Marken-
Titelbilder), keine Ankündigungsleiste, ein Button im Hero, Termin-Widget unten rechts
- [ ] `/marken/tischlerei`: Text v2 (drei Abschnitte, acht Leistungen, Abschluss „Das Ganze statt
nur das Einzelne“), Werkstatt-Titelbild, Galerie „Referenzen und Einblicke“ mit den Badges
„Realisiertes Projekt“ und „Aus der Werkstatt“
- [ ] **Die vier Displays sichten:** „Display 4 – Innen", „Kundenstopper", „Display 2",
„Test-Display". Besonders das Modul „Cabinet-Influencerin" – dessen Video lag auf
`b2in.test`. Ob die Datei auf dem Live-Server überhaupt existiert, ließ sich lokal nicht
@ -1809,3 +1856,446 @@ auch 1,5 annehmen. Das Feld war ganzzahlig (`int`, Zahlenfeld mit `min=1`).
**Tests:** vier neue Fälle in `tests/Feature/DisplayVersionTest.php` (Komma-Eingabe,
Parser/Formatierer, Ablehnung unter 0,5 s, Video ignoriert das Feld). Kein Deployment-Schritt.
---
## Nachtrag 15 · Otterpohl als vierte Marke, Feedback 17.09. eingearbeitet (17.09.2026)
Zulieferung 17.09.2026: `dev/2026-08-28/otterpohl_Webseitentext_FINAL.md` (Text der
Tischlerei-Seite), `dev/2026-08-28/markenlogos/otterpohl.jpg` (Logo) und
`dev/2026-08-28/feedback-17-09-2026.md` (Marcels Rückmeldung zu CABINET, Febrü und Rotpunkt).
### Otterpohl ersetzt den Platzhalter „Tischlerei“
- **Marke:** Name „Otterpohl“, Claim „Nicht easy to click & buy. Aber easy to get.“, Kacheltext,
Text für /marken, drei Merkmale, Einleitung (erster Block des Textes), vier Leistungen, **13
Abschnitte** mit den Überschriften der Zulieferung 1:1, die pointierten Schlusssätze als
Schlusszeile je Abschnitt, Abschlussblock „Ihre Idee. Handwerklich umgesetzt.“, Fertigungsort
Rheda-Wiedenbrück mit Rolle „seit 2014 an unserer Seite“. Slug `tischlerei` bleibt (Links,
Widget-Thema, Kartenposition).
- **Logo:** `public/img/assets/marken/otterpohl.png`, aus der JPG-Zulieferung freigestellt und
knapp beschnitten (Imagick), damit es in der gemeinsamen Logo-Box wie die anderen wirkt.
Das Logo trägt „25 Jahre“ – so geliefert, so übernommen.
- **Seeder:** neu `OtterpohlBrandSeeder` (überschreibt bei der bestehenden Marke alle Textfelder,
lässt Bild, Galerie und Reihenfolge stehen, Logo nur wenn leer); `CmsBrandSeeder` nutzt
dieselben Daten für frische Installationen; `BrandLogoSeeder` und `BrandOriginSeeder` kennen
die Marke. Lang-Fallback, Seitentitel von /marken und der Ablauf-Block der Herkunfts-Sektion
nennen jetzt Otterpohl.
### Feedback 17.09. in den Markentexten (`BrandPageTextSeeder`)
- **CABINET:** Der Abschnitt „Aufmaß vor Ort statt Schätzung am Telefon“ widersprach Marcels
Ablauf (erst Foto und grobes Maß, Aufmaß erst nach Angebot). Ersetzt durch „Ein Bild und ein
grobes Maß reichen für den Anfang“ mit Marcels Fassung wörtlich und der Ablaufzeile
„So läuft’s: Foto + grobe Maße → … → Bestellung“ als Schlusszeile. Zweiter Absatz von
„Die Fragen, die wir vorher stellen“ ersetzt (Karnevalskostüme, Sportsachen).
- **Febrü:** nur die vereinbarten Stellen: „Bei uns gibt es Kunden – keine Schubladen“ (drei
Absätze), „Aus Herford“ mit „sehr hohe Fertigungstiefe“ statt 95 Prozent, „Wertschätzung ist
keine Frage der Stückzahl“ (Familienalltag statt Mütter-Passage). Der doppelte Schlusssatz
„Ob im Kleinen oder im Großen …“ steht nur noch beim ersten Abschnitt. „Nicht drei Marken“,
„Das Wir und das Ich“ und der mitlaufende CTA unverändert.
- **Rotpunkt:** „Mehr als Küchen“ mit Marcels drei Absätzen ersetzt.
- **Handwerk als gemeinsamer Abschluss (Punkt 3b):** Die markeneigenen Abschnitte
„Schranksystem plus Handwerk“ (CABINET) und „Küchenstudio plus Handwerk“ (Rotpunkt) sind
entfallen. Stattdessen gibt es **eine** Sektion `brand_craft` („Planung plus Handwerk“, Lang-
Datei, im Inhalte-Editor unter Marken → „Handwerk-Abschluss auf jeder Markenseite“ anlegbar),
die `BrandDetail` als letzten Abschnitt auf jeder Markenseite anhängt – außer auf der
Otterpohl-Seite selbst. Der Link „Mehr zu Otterpohl“ erscheint nur, wenn die Marke
veröffentlicht ist. Anker `#handwerk`. Einmal gepflegt, überall gleich – genau wie gewünscht.
**Lokal ausgeführt:** `OtterpohlBrandSeeder`, `BrandPageTextSeeder`, `BrandLogoSeeder`.
Beim Deployment: Abschnitt 8.2, Schritt 4 (Feedback) und Schritt 11 (Otterpohl).
**Offen:** Stimmungsbild und Galerie für Otterpohl (im Admin nachtragen); Nachtrag 10 bleibt
offen, ob CABINET selbst Aufbau anbietet. Der Text bestätigt Rheda-Wiedenbrück und die
Montage-Partnerschaft seit 2014 – die Herkunfts-Sektion passt damit.
**Tests:** `tests/Feature/BrandFeedbackTest.php` (10 Fälle: CABINET-Einstieg und Fragen, Febrü-
Passagen und einmaliger Schlusssatz, Rotpunkt „Mehr als Küchen“, Handwerk-Abschluss auf drei
Seiten mit Link und Position, nicht auf Otterpohl, ohne Link bei unveröffentlichter Marke,
CMS-Vorrang, Otterpohl-Seeder behält Logo/Bild/Reihenfolge); `FourthBrandTest`,
`MarkenPageTest` und `BrandPageSectionsTest` auf die neuen Texte umgestellt.
---
## Nachtrag 16 · Erste Bilder für CABINET und Rotpunkt (22.09.2026)
Zulieferung 22.09.2026 unter `dev/2026-08-28/images/{cabinet,rotpunkt}/`, das Titelbild je
Marke mit `_title_` markiert. 12 Bilder CABINET, 14 Bilder Rotpunkt, alles Visualisierungen des
Herstellers (Renderings), 3–6 MB je Datei.
- **Optimierung:** alle Bilder als WebP mit maximal 1920 px Kantenlänge, Qualität 82, ohne
Metadaten (Imagick) nach `public/img/assets/marken/cabinet/` und `…/rotpunkt/`; 55–330 KB je
Datei statt mehrerer MB. Sprechende Dateinamen aus den Motiven („dachschraegenschrank-filo-
cashmere.webp“, „kueche-07.webp“). Die Originale bleiben per `.gitignore` außerhalb des
Repositorys, nur die WebP-Fassungen werden deployt.
- **Titelbild** (`image`): CABINET „Ankleide Solitär, Eiche Tano“, Rotpunkt „Küche 01“. Es
erscheint im Seitenkopf der Markenseite, auf der Startseiten-Kachel und im Markenblock auf
/marken – dort, wo bisher der Platzhalter stand.
- **Galerie:** die übrigen Bilder als **Visualisierung** mit Bildunterschrift aus dem Motiv.
Die Rubrik passt: Badge „Visualisierung“, Hinweis „Entwürfe und Visualisierungen“. Unter
„Referenzen“ erscheinen sie damit bewusst nicht – das sind keine von uns ausgeführten Projekte.
Marcel kann jedes Bild im Admin umkategorisieren, sobald ein echtes Projekt dahintersteht.
- **`BrandImageSeeder`:** Titelbild nur, wenn keins gepflegt ist; Galeriebilder nur, wenn die
Datei noch nicht in der Galerie steht; vorhandene Einträge bleiben samt Kategorie und
Unterschrift. Mehrfach ausführbar. Febrü und Otterpohl bleiben unberührt (keine Zulieferung).
**Lokal ausgeführt:** `php artisan db:seed --class=BrandImageSeeder`. Beim Deployment:
Abschnitt 8.2, Schritt 12 – die Dateien kommen mit dem Repository.
**Ergänzung 23.09.2026:** Febrü nachgeliefert (`dev/2026-08-28/images/februe/`, 16 Bilder,
Titelbild „Base Thekenlösung“). Gleiche Behandlung, Bildunterschriften nach den Febrü-
Produktlinien aus den Dateinamen („Active 2.0 mit Bureo“, „Arena Stage“, „Vio“ …). Der Seeder
und `BrandImageSeederTest` decken jetzt drei Marken ab.
**Offen:** Bilder für Otterpohl; Freigabe der Bildunterschriften durch Marcel
(aus den Dateinamen abgeleitet, z. B. Frontdekore „Filo Cashmere“, „Velluto Fango“, bei Febrü
die Produktlinien).
**Tests:** `tests/Feature/BrandImageSeederTest.php` (4 Fälle: Titelbild und Galerie, jede
referenzierte Datei existiert als optimierte Datei unter 400 KB und 1920 px, zweiter Lauf
lässt Gepflegtes stehen, Anzeige auf Startseite, /marken, Markenseite und nicht unter Referenzen).
---
## Nachtrag 17 · Demobilder raus, Galerie ohne Rubrikwechsel (22.09.2026)
Abstimmung 22.09. nach den ersten echten Bildern: die Demobilder aus AP2 müssen weg, der
Rubrikwechsel „Realisierte Projekte / Inspirationen“ ist ausgesetzt, solange es keine
realisierten Projekte gibt, und die zwei Zeilen unter der Galerie-Überschrift werden eine.
- **`DemoBrandGalleryCleanupSeeder`** entfernt die elf Demobilder (CABINET 4, Rotpunkt 3, Febrü
4) aus den Galerien. Erkennung am Dateinamen aus `DemoBrandGallerySeeder`, nicht an der
Unterschrift – den Vermerk „BEISPIEL“ hatte Schritt 5 auf der Vorschau bereits entfernt.
Alle anderen Bilder bleiben. Idempotent.
- **Galerie** auf der Markenseite: ein Raster, realisierte Projekte zuerst, jedes Bild trägt
weiter sein Badge („Realisiertes Projekt“ / „Visualisierung“). Kein Tab-Wechsel, keine
Gruppenüberschriften, kein separater Hinweistext. Die Kategorie im Admin bleibt bestehen,
der Wechsel lässt sich wieder einschalten, sobald echte Projekte da sind.
- **Texte:** Überschrift „Inspirationen und Entwürfe“, eine Zeile darunter: „Visualisierungen,
die zeigen, was in einem Raum möglich ist – noch bevor das erste Möbel steht.“ (vorher
„Referenzen und Inspirationen“ plus Hinweis unter der Rubrik).
**Lokal ausgeführt:** `php artisan db:seed --class=DemoBrandGalleryCleanupSeeder` (11 entfernt).
Beim Deployment: Abschnitt 8.2, Schritt 13, nach Schritt 12.
**Tests:** `BrandPageTest` umgestellt (ein Raster mit Badges und Projekten zuerst, genau eine
Zeile unter der Überschrift, Aufräum-Seeder entfernt nur Demobilder und ist idempotent).
## Nachtrag 18 · WhatsApp zuerst, Business-Nummer, Farbvariante „Modern“ (23.09.2026)
Rückmeldung von Marcel über Kevin am 23.09.: Im Termin-Widget soll WhatsApp oben stehen und das
Formular darunter; als Nummer gilt die Business-Nummer 0521-98620100 (am 23.09. kam zunächst
…98610020, Korrektur von Marcel am 24.09.). Außerdem wirkt ihm die
Farbwelt „zu altbacken“ – die beigen Flächen. Gewünscht ist eine klarere, modernere Variante, die
farblich am Logo bleibt (Tanne und Ocker unverändert), sowie ein schwimmender Schalter, um beide
Stände direkt zu vergleichen.
### Termin-Widget
- **Reihenfolge:** WhatsApp-Block direkt unter der Einleitung, darunter eine Trennzeile „oder per
Formular“, dann das Formular. Einleitung jetzt „Am schnellsten per WhatsApp – wir bereiten die
Nachricht mit Ihren Angaben vor.“ Der Hinweis, dass der Besucher selbst abschickt, bleibt.
Der vorbereitete Text liest weiter Anliegen, Wunschtermin und Name aus dem Formular, auch wenn
es jetzt unter dem Button steht. Ohne Nummer im CMS erscheint weder Button noch Trennzeile.
- **Nummer:** `WhatsappNumberSeeder` trägt `+49 521 98620100` in `global.contact.whatsapp` ein
(de und en), aber nur, wenn das Feld leer ist, noch den Platzhalter „+49 151 00000000“ aus der
ersten Einrichtung enthält oder die falsche Nummer vom 23.09. (`+49 521 98610020`) – die hatte
Marcel versehentlich geschickt, auf der Vorschau lief der Button deshalb ins Leere. Eine im
Admin gepflegte andere Nummer bleibt stehen. Fehlt der Eintrag, wird er angelegt. Idempotent.
Ergebnis-Link: `https://wa.me/4952198620100`. Auf einer Vorschau ohne Portal reicht
`php artisan db:seed --class=WhatsappNumberSeeder --force`.
### Farbvariante „Modern“
Eine zweite Interior-Palette als Override in `theme-b2in.css` (`.palette-modern .world-interior`).
Sie ändert **nur Flächen**, keine Logofarben:
| Token | Bisher (Creme) | Modern |
|---|---|---|
| Hintergrund | Creme `#f7f4ef` | fast weiß, leicht warm `#fbfaf8` |
| Karten | `#fbf9f5` | reinweiß |
| Sektionswechsel (`bg-accent`) | Sand `#d9cbbf` | helles Grau-Beige `#efece7` |
| Ruhige Flächen (`bg-muted`) | helles Sand | `#f2f0ed` |
| Linien / Eingabefelder | Sand-Töne | `#e6e2dc` / weiß |
| Hero-Bühne | Sand | helles Grau-Beige |
| Footer | Sand mit Tanne | Tanne mit Creme, Ocker als Akzent |
| Tanne, Ocker, Clay, dunkle Sektionen, CTA-Band | unverändert | unverändert |
Karten liegen jetzt weiß auf fast weiß, deshalb haben `--shadow-card` und `--shadow-elevated` in
der Variante etwas mehr Tiefe. Weil der Footer dunkel ist, trägt er in „Modern“ das **negative
Logo** (Rückmeldung Kevin 23.09.): der Footer rendert beide Logos (`footer-logo-light` /
`footer-logo-dark`), das Stylesheet blendet je Palette das passende ein – die Umschaltung läuft
im Browser, ohne Server-Roundtrip. Die Immobilien-Welt bleibt bei ihrem einen negativen Logo.
Zwei Nachbesserungen nach dem ersten Blick (Kevin 23.09.): Abschlussband und Footer sind in
„Modern“ beide dunkel, deshalb trennt eine helle Haarlinie (`--footer-muted` bei 35 %) den Footer
oben ab. Und der schwebende Termin-Button war als reine Kontur über Bildern und weißen Karten
kaum zu sehen – er trägt jetzt in beiden Paletten eine Fläche in Kartenfarbe
(`.appointment-toggle`), Hover bleibt Tanne.
Dritte Nachbesserung (Kevin 23.09.): der **Cookie-Button** unten links kam noch in Blau aus
`config/cookie-consent.php` – das ist die Immobilien-Welt. `theme-b2in.css` überschreibt die
Paketvariablen (`--cc-*`) für `.world-interior`: Button in Tanne mit dem Verlauf des
Akzent-Buttons, Icon, Schalter, Fokusring und Link-Hover in Ocker, „Speichern“ in Tanne,
„Akzeptieren“ bleibt grün. Gilt in beiden Paletten; `/immobilien` behält das Blau. Der dunkle Footer ist die eine bewusste Abweichung, die über
„heller“ hinausgeht: er gibt der hellen Seite unten einen klaren Abschluss. Wenn Marcel ihn
lieber hell hätte, sind es vier Token.
### Schalter
- `<x-palette-switch />` schwimmt links in der Mitte (unten links sitzt der Cookie-Button, unten
rechts das Termin-Widget): eine senkrechte Pille „Farbwelt · Bisher / Neu“. Nur auf den
Interior-Seiten von b2in, nicht auf `/immobilien`.
- Die Wahl liegt im `localStorage` (`b2in-palette`) und wird schon im `<head>` gesetzt, bevor das
Stylesheet greift – kein Aufblitzen in Creme. `?palette=modern` bzw. `?palette=classic` an einer
URL überschreibt die gemerkte Wahl; damit lässt sich Marcel ein Link auf die neue Variante
schicken: `https://<vorschau>/?palette=modern`.
- Abschalten, sobald entschieden ist: `B2IN_PALETTE_SWITCH=false` in `.env`
(`config('app.palette_switch')`). Dann erscheint weder Schalter noch Skript, die Seite läuft in
der Standard-Palette. Soll „Modern“ dauerhaft werden, werden die Token in `:root` übernommen und
der Override entfernt – ein Nachtrag, kein Umbau.
**Lokal ausgeführt:** `php artisan db:seed --class=WhatsappNumberSeeder` (2 Sprachen gesetzt),
`npm run build:b2in`. Beim Deployment: Abschnitt 8.2, Schritt 14, dazu `npm run build:web`.
**Tests:** `WhatsappNumberSeederTest` (6: anlegen, Platzhalter ersetzen, falsche Nummer vom 23.09.
korrigieren, gepflegte Nummer behalten, idempotent, Link auf der Seite), `AppointmentWidgetTest` (+2: WhatsApp vor dem Formular, keine
Trennzeile ohne Nummer), `ColorWorldsTest` (+6: Schalter auf Interior-Seiten, nicht auf
Immobilien, per Config abschaltbar, Override ohne Logofarben, negatives Logo im Footer der
Variante, Immobilien-Footer unverändert).
**Offen:** Marcel entscheidet zwischen „Bisher“ und „Neu“ – oder benennt, was an „Neu“ noch
fehlt (Footer hell/dunkel, Grad der Aufhellung). Danach Schalter abschalten.
## Nachtrag 19 · Otterpohl: Text v2 und erste Bilder (24.09.2026)
Zulieferung vom 24.09.: `dev/2026-08-28/otterpohl_Webseitentext_FINAL_v2.md` löst die lange Fassung
vom 17.09. komplett ab, dazu 13 Bilder in `dev/2026-08-28/images/otten/` (Titel plus 2–5 vom
Unternehmen, der Rest Referenzen).
### Text v2
Deutlich kürzer als v1: eine Einleitung, zwei Abschnitte, acht Leistungen, ein Schlussblock. Die
Fassung vom 17.09. mit zwölf Abschnitten (u. a. „Seit 2014 an unserer Seite“, „Farbe darf auch
genau die Farbe sein“) ist damit vollständig ersetzt. Zuordnung auf die Markenseite:
| Zulieferung v2 | Feld | Auf der Seite |
|---|---|---|
| „Ziemlich nah an der eierlegenden Wollmilchsau“ + 4 Absätze | `sections[0]`, Claim „Genau dort beginnt der individuelle Innenausbau.“ | erster Abschnitt |
| „Vom einzelnen Möbel bis zum ganzen Raum“ | `sections[1]`, Claim „So können wir Räume ganzheitlicher denken …“ | zweiter Abschnitt |
| „Planung trifft Handwerk“ | `sections[2]`, Claim „Das Ergebnis kann ein einzelnes Möbel sein. Oder ein kompletter Raum.“ | dritter Abschnitt |
| „Leistungen und Möglichkeiten“ (8 Punkte) | `services` | Leistungsraster, zwei Reihen à vier |
| „Das Ganze statt nur das Einzelne“ | `cta_title` / `cta_text` | Abschlussblock hinter Leistungen und Galerie – so bleibt die Reihenfolge der Zulieferung erhalten |
| „Nicht easy click & buy. Aber dennoch easy to get.“ | `tagline` und Ende des `cta_text` | Claim im Seitenkopf und Schlusssatz |
- **Name** jetzt „Otterpohl Möbelservice“ – so nennt die Zulieferung den Partner durchgängig.
Überall dort, wo der Name aus der Marke kommt (Kacheln, Widget-Anliegen, Herkunfts-Kachel).
Der Link im Handwerk-Abschnitt der anderen Markenseiten heißt weiter „Mehr zu Otterpohl“.
- **Kein „seit 2014“ mehr:** v2 erwähnt die gemeinsame Zeit nicht. Der Hinweis ist aus Teaser,
Fertigungsort (`origin_role`), Lang-Fallback und dem Handwerk-Abschnitt (`brand_craft`)
entfernt. ⚠️ Falls Marcel die Angabe bewusst behalten wollte, ist sie in drei Sätzen zurück.
- `description` bleibt leer, damit der Text in der Reihenfolge der Zulieferung steht; `summary`
(Markenübersicht) fasst die Einleitung zusammen; `highlights` sind drei Stichzeilen aus den
Leistungen. Englisch: eigene Übersetzung in gleicher Struktur.
- `widget_label` „Persönliche Beratung zur Maßarbeit vereinbaren“ bleibt.
### Bilder
13 Dateien, konvertiert wie in Nachtrag 16 (WebP, max. 1920 px, unter
`public/img/assets/marken/otterpohl/`, zusammen rund 2,4 MB; ein Bild per GD nachverdichtet).
- **Titelbild** `werkstatt-titel.webp`: zwei Mitarbeiter in der Werkstatt.
- **Aus der Werkstatt** (2–5): CNC-Bearbeitungszentrum, Türeinbau vor Ort, Handwerkzeug,
Anlieferung per Möbellift. Dafür gibt es eine **dritte Galerie-Kategorie** `company`
(`CmsBrand::GALLERY_COMPANY`, Badge „Aus der Werkstatt“). Im Admin: Button „Werkstatt“ und
Option „Aus der Werkstatt“ in der Kategorie-Auswahl, Hinweistext ergänzt.
- **Referenzen** (8, Kategorie `project`, Badge „Realisiertes Projekt“): Treppen, Lamellenwand mit
Sitzbank, Tür und Kommode, Wandregale, Objektbau-Verkaufsraum, Büro-Sideboard. Unterschriften
beschreiben das Motiv; die Kundennamen aus den Dateinamen (Lücke, Berkemeier, Neugebauer,
Dziuballe) stehen bewusst nicht auf der Seite. ⚠️ `Otterpohl_11.jpg` (Sideboard im Büro) ist
nach Kevins Angabe eine Referenz, nicht Werkstatt – falls es das eigene Büro ist, im Admin auf
„Aus der Werkstatt“ umstellen.
- **Reihenfolge in der Galerie:** Projekte, dann Werkstatt, dann Visualisierungen.
- **Überschrift der Galerie** richtet sich jetzt nach dem Inhalt: nur Visualisierungen →
„Inspirationen und Entwürfe“ (wie bisher, CABINET/Rotpunkt/Febrü); nur Projekte → „Referenzen“;
Projekte und Werkstatt → „Referenzen und Einblicke“ (Otterpohl); mit Visualisierungen →
„Projekte und Entwürfe“. Die Zeile darunter passt jeweils.
- ⚠️ **Referenzen-Seite:** `/referenzen` zeigt, solange im Modul „Referenzen“ nichts gepflegt ist,
die Projekte aus den Markengalerien – jetzt also die acht Otterpohl-Referenzen mit Link auf die
Markenseite. Werkstattbilder erscheinen dort nicht. Wenn Marcel das nicht will: Bilder im Admin
auf „Aus der Werkstatt“ stellen oder eigene Referenzen anlegen, dann greift der Fallback nicht.
**Lokal ausgeführt:** `php artisan db:seed --class=OtterpohlBrandSeeder`, `php artisan db:seed
--class=BrandImageSeeder`. Beim Deployment: Abschnitt 8.2, Schritte 11 und 12 (Texte
unverändert, sie decken v2 mit ab).
**Tests:** `FourthBrandTest` (v2-Überschriften, acht Leistungen, drei Abschnitte, Schlussblock hinter
den Leistungen, kein „Seit 2014“), `BrandFeedbackTest` (Seeder setzt Name, acht Leistungen,
Abschluss), `BrandImageSeederTest` (+3: Otterpohl-Galerie 8 Projekte + 4 Werkstatt ohne
Kundennamen, Badges und Überschrift „Referenzen und Einblicke“, Referenzen-Fallback zeigt Projekte
und keine Werkstattbilder), `BrandPageTest` (gemischte Galerie → „Projekte und Entwürfe“, Aufräum-
Seeder lässt Otterpohl 12).
**Offen:** Marcel prüft Unterschriften, die Zuordnung von `Otterpohl_11`, das gestrichene „seit
2014“ und ob die Otterpohl-Referenzen unter `/referenzen` erscheinen sollen.
## Nachtrag 19 · Entwurf der Startseite in der Farbwelt „Greige“ (24.09.2026)
Briefing von Marcel (`dev/2026-08-28/farbwelt-kunde-24-09-2026.md`, Visualisierung
`Farbwelt und Stil.png`): die Creme-Palette wird nicht weiterverfolgt, die Richtung „Neu“ soll
heller, klarer und hochwertiger werden – heller, leicht kühler Grundton (Greige, kein steriles
Weiß), deutlich präsenteres Tannengrün, Gold nur als feine Linie. Dazu eine Slideshow im Hero,
neue Überschrift, keine Aktionsleiste, ein Button im Hero, neuer Projekttext, Logo mit Goldbogen.
Kevins Auftrag: eine **separate Version der Startseite** zum Vergleich, das Logo als Kopie.
### Was gebaut ist
- **Entwurfsseite `/entwurf`** (Route `home.draft`, View `web/home-draft.blade.php`): die reguläre
Startseite mit neuem Hero, ohne Ankündigungsleiste, Body-Klasse `palette-greige`, `noindex`.
Alle übrigen Sektionen sind unverändert eingebunden und färben sich über die Tokens. Die Seite
hängt am Farb-Schalter: mit `B2IN_PALETTE_SWITCH=false` liefert sie 404.
- **Palette „Greige“** als drittes Token-Set in `theme-b2in.css` (`.world-interior.palette-greige`,
am Body, nicht am `<html>` wie der Browser-Schalter – so überlagern sich beide nicht):
| Token | Wert |
|---|---|
| Grund | `#f0eeeb` Greige, Off-White (Kopfzeile der Visualisierung) |
| Karten / Hero-Bühne | `#f8f7f4` |
| Sektionswechsel (`bg-accent`) | `#e8e6e0` |
| Tannengrün (`--primary`) | `#14342c`, Buttons, dunkle Sektionen, Footer |
| Gold (`--secondary`) | `#aa8341` gedeckt; als Text `--secondary-deep` (dunkler) |
| Text | Tannengrau `hsl(166 25% 13%)` |
| Footer | Tannengrün mit Greige-Text, Goldlinie oben, negatives Logo |
- **Logo-Kopien** `public/img/logos/b2in-interior-logo-gold-{positive,negative}.svg`: Bogen
`#AA8341` statt Ocker, Wortmarke im neuen Tannengrün `#14342C` (negativ: Schrift `#F0EEEB`).
Flächig, ohne Verlauf – die Verläufe liefert Marcel nach; die Dateien lassen sich dann 1:1
ersetzen. Die bisherigen Logos sind unangetastet. `ThemeHelper::getLogoPath()` liefert die
Gold-Variante, sobald `b2in_palette()` (neuer Helper, Binding `b2in.palette`) „greige“ meldet
– das setzt nur die Entwurfsroute. Kein PNG erzeugt (kein SVG-Rasterizer im Container).
- **Hero mit Slideshow** (`HeroSlider`, Sektion `home/hero_slider`, im Seitenbaum unter Startseite
mit Vorschau auf `/entwurf`): Rahmen mit 1-px-Goldkante und Schatten, tannengrüne Flanken rechts
(volle Höhe) und links unten wie in der Visualisierung, Eyebrow „B2IN INTERIOR ——“, Überschrift
„*Räume aus einer Hand.* Mit starken Marken und persönlichen Lösungen.“ (erste Zeile in Gold,
Serife), Einleitungstext aus der Visualisierung, **ein** Button „Unsere Marken entdecken“ mit
Goldkante unten und Pfeil. Kein zweiter Button (Briefing Punkt 5), das Termin-Widget bleibt.
- Bild rechts im festen Seitenverhältnis 4:3 → auf großen Bildschirmen nicht mehr unten
beschnitten (Briefing Punkt 2).
- Slides: Liste `slides` der Sektion (je `image`, `label`, `caption`, `active`), solange leer
die **Titelbilder der veröffentlichten Kernmarken** in ihrer Reihenfolge (aktuell CABINET,
Rotpunkt, Febrü, Otterpohl sofern Bild). Kleine Marken-Pille unten links im Bild.
- Ruhig: Überblendung 1,4 s, Wechsel alle 7 s (`interval`, begrenzt auf 3–30 s), Pause bei
Hover/Fokus, Pfeiltasten, Punkte unter dem Rahmen als Tabs; bei „reduzierter Bewegung“ kein
Autowechsel. Läuft ohne eigenes JS-Bundle (Alpine im Markup).
- **Farb-Schalter** hat jetzt drei Einträge: Bisher / Neu (Browser, wie gehabt) / **Entwurf**
(Link auf `/entwurf`). Auf dem Entwurf ist „Entwurf“ aktiv, „Bisher“/„Neu“ führen mit
`?palette=…` zurück auf die Startseite.
- **Projekttext** (Briefing Punkt 7) global: Lang de/en geändert, `AudienceSwitchProjectTextSeeder`
tauscht den Satz in einem vorhandenen CMS-Eintrag (nur diesen Satz, idempotent). Lokal
ausgeführt (2 Stellen). Deployment Schritt 15.
**Nachbesserung nach Kevins Screenshot (24.09., 17:30):** Die Goldkante war auf Greige nicht zu
sehen – jetzt ein Verlauf (hell oben links, auslaufend, in der Ecke wieder anziehend) über einen
maskierten Pseudo-Rand von 1,5 px. Die Flanken sind gerundet (Radius 2,75 rem rechts, 2 rem
links) und laufen hinter das Bild; ihre Goldlinie folgt der Rundung. Das Bild füllt den Rahmen
fast ganz (5:4, nur 1,25 rem Luft oben und unten, rechts über der Flanke), die Überschrift ist
kleiner (max. 3,3 rem statt 4,1 rem) und der Einleitungstext eine Stufe kleiner.
**Zweite Nachbesserung (25.09.):** Hinter der Goldkante schien eine helle Linie durch – die
Masken-Technik ließ die gerundete Kante des Rahmens außerhalb des Rings sichtbar. Jetzt sind
Rahmen und Bild-Rahmen echte Verlaufs-Borders (transparenter Rand, Fläche auf `padding-box`,
Goldverlauf auf `border-box`), ohne Pseudo-Element. Der Button trägt den Goldrand rundum, unten
3 px, der Pfeil ist Gold. Die grünen Flanken sind **Inline-SVGs**
(`components/web/hero-flank-right|left.blade.php`): grüner Verlauf, Goldlinie entlang der
Rundung, rechts zusätzlich die kurze Zierlinie aus der Visualisierung. Sie skalieren über die
Rahmenhöhe (`height: 100 %` bzw. `46 %`, Breite aus dem viewBox-Verhältnis, Strichstärke bleibt
1,5 px dank `vector-effect`), Farben aus den Tokens. Liefert Marcel eigene SVGs, werden die
beiden Dateien getauscht – der Platz im Rahmen bleibt.
**Abstimmung 25.09. (Kevin):** Für die nächste Runde ist der Farb-Schalter **aus** und die Seite
steht fest auf „Neu“. Dafür zwei neue Konfigurationswerte in `config/app.php`:
`B2IN_PALETTE_SWITCH` (Standard jetzt `false`), `B2IN_PALETTE` (Standard `modern`; ohne Schalter
setzt das Layout die Klasse `palette-modern` serverseitig an `<html>`, mit Schalter ist es die
Vorbelegung, solange nichts im localStorage liegt) und `B2IN_HOME_DRAFT` (Standard `true`) – der
Entwurf `/entwurf` hängt nicht mehr am Schalter, sondern an diesem Wert. Der Schalter selbst
bleibt im Code und lässt sich mit `B2IN_PALETTE_SWITCH=true` wieder einblenden.
### Was aus dem Briefing noch offen ist
| Punkt | Stand |
|---|---|
| 1 Slideshow selbst pflegen (Upload, Reihenfolge, an/aus) | Datenmodell steht (Liste `slides` mit `active`), Pflege bisher nur über den JSON-Editor unter Inhalte. Eigene Oberfläche wie bei den Displays: nächster Schritt nach Freigabe des Entwurfs. |
| 6 Persönliches Foto | Kein Foto geliefert. Vorschlag: in der Gründer-Leiste („B2in by Marcel Scheibe“), Bildfeld ist dort schon vorhanden (`founder_bar.image`). |
| 8 Feinabstimmung der Töne | Werte oben sind aus der Visualisierung gesampelt und leicht aufgehellt („noch etwas heller“). Marcel entscheidet am Entwurf. |
| 9 Logo mit Verläufen | Kommt von Marcel; bis dahin flache Gold-Kopie. |
| Entwurf → Standard | Wenn freigegeben: Tokens von `.palette-greige` nach `:root`, Gold-Logos umbenennen, `home.blade.php` auf `hero-slider` umstellen, Ankündigungsleiste global aus, Schalter und `/entwurf` abschalten. Ein Nachtrag, kein Umbau. |
**Lokal ausgeführt:** `php artisan db:seed --class=AudienceSwitchProjectTextSeeder`, `npm run build:b2in`.
Beim Deployment: Abschnitt 8.2, Schritt 15, dazu `npm run build:web`; die SVG-Logos liegen im
Repository. Link für Marcel: `https://<vorschau>/entwurf`.
**Tests:** `HomeDraftTest` (16: Palette und Gold-Logos nur auf dem Entwurf, Überschrift/ein
Button/keine Leiste, Sektionsreihenfolge, 404 ohne Schalter, Slides aus Marken, ohne Bild,
CMS-Slides mit `active`, Intervall-Grenzen, Schalter hin und zurück, Logo-Dateien, Tokens,
Projekttext in Lang und per Seeder), `CmsPageTreeTest` (Reihenfolge um `hero_slider` ergänzt).
Gesamt: 1049 grün.
---
## Nachtrag 20 · Korrekturen Startseite und Otterpohl (01.10.2026)
Farbwelt bleibt vorerst „Modern“ ohne Schalter. Umgesetzt auf der regulären Startseite `/`:
- **Hinweisleiste oben** aus über die Flag `B2IN_ANNOUNCEMENT_BAR` (`config('app.announcement_bar')`,
Standard `false`, gilt für beide Welten). Inhalt und CMS-Pflege bleiben, `=true` blendet sie wieder ein.
- **Hero-Überschrift** „<span class="text-secondary">Räume aus einer Hand.</span><br>Mit starken Marken
und persönlichen Lösungen.“ (erste Zeile in `.text-secondary`). Lang de/en und `HomeHeroTextSeeder`
für den CMS-Eintrag `home/hero` (ersetzt nur die alte Überschrift).
- **Zweiter Hero-Button** „Termin anfragen“ entfernt: aus Lang und CMS gelöscht; das Blade zeigt ihn nur
noch, wenn `cta2_text` gepflegt ist. Der feste Termin-Button unten rechts bleibt.
- **Projekttext**: „… Projektentwicklungen. Koordiniert aus einer Hand …“ (Punkt statt Gedankenstrich),
Lang de/en und `AudienceSwitchProjectTextSeeder` erweitert.
- **Otterpohl Titelbild**: `tittle_otter.png` → `marken/otterpohl/titel-treppe.webp` (1920 px, WebP 82).
Gleiches Motiv wie das Galeriebild `referenz-luecke-01` – daher echter Tausch: die Treppe wird Titelbild,
das bisherige Werkstattbild nimmt ihren Galerieplatz ein (Aus der Werkstatt). Nur wenn noch das alte
Titelbild gesetzt ist (`OtterpohlBrandSeeder::swapTitleImage()`).
- **Otterpohl Abschnitt „Montage“** als vierter Abschnitt. Neu für alle Markenabschnitte: Absätze mit
„- “ am Anfang werden zur Liste, der Teil vor dem ersten Doppelpunkt fett (`CmsBrand::paragraphBlocks()`)
– pflegbar im normalen Textfeld im Admin.
Lokal ausgeführt: die drei Seeder aus Schritt 16. Tests: `HomeCorrectionsTest` (neu), angepasst
`AnnouncementBarTest`, `HomePageStructureTest`, `HomeDraftTest`, `BrandImageSeederTest`,
`FourthBrandTest`, `AppointmentTriggerTest`, `CmsContentSeederTest`. Gesamt 1071 grün
(mit `B2IN_HOME_DRAFT=true`; die lokale `.env` hat den Entwurf aus, dann schlagen die `/entwurf`-Tests fehl).
---
## Seeder für das nächste Deployment (Stand 01.10.2026)
Alles, was seit dem letzten Commit (`3211569f`, Bilddauer im Display) neu ist: Nachträge 15–20.
Annahme: Der Stand bis zu diesem Commit ist live. Genau diese Seeder, in dieser Reihenfolge:
```bash
php artisan db:seed --class=CmsBrandSeeder --force # legt Otterpohl („tischlerei“) an, falls die Marke fehlt – überschreibt nichts
php artisan db:seed --class=BrandPageTextSeeder --force # Feedback 17.09. für CABINET, Rotpunkt, Febrü (überschreibt deren Textfelder)
php artisan db:seed --class=OtterpohlBrandSeeder --force # Otterpohl-Texte v2, Abschnitt „Montage“, Titelbild-Tausch
php artisan db:seed --class=BrandImageSeeder --force # Titelbilder und Galerien aller vier Marken
php artisan db:seed --class=DemoBrandGalleryCleanupSeeder --force # Demobilder raus – nach BrandImageSeeder
php artisan db:seed --class=WhatsappNumberSeeder --force # WhatsApp-Business-Nummer 0521-98620100
php artisan db:seed --class=AudienceSwitchProjectTextSeeder --force # Projekttext „… Projektentwicklungen. Koordiniert …“
php artisan db:seed --class=HomeHeroTextSeeder --force # Hero „Räume aus einer Hand.“, zweiter Button weg
```
Danach:
```bash
php artisan config:cache
php artisan view:clear
npm ci && npm run build:web && npm run build:portal
```
- Alle Seeder sind idempotent, ein zweiter Lauf schadet nicht.
- In der `.env` muss nichts ergänzt werden: Hinweisleiste aus und Palette „Modern“ ohne Schalter sind
die Standardwerte. Den Entwurf `/entwurf` live abschalten: `B2IN_HOME_DRAFT=false`.
- Nicht nötig: `BrandLogoSeeder` und `BrandOriginSeeder` – Logo und Fertigungsort von Otterpohl setzt
bereits der `OtterpohlBrandSeeder`.

View file

@ -0,0 +1,69 @@
1. Hero-Bereich / Slideshow
Das aktuelle große Hero-Bild würde ich gerne durch eine Slideshow mit unterschiedlichen Herstellern und Themen ersetzen, zum Beispiel:
Rotpunkt / Küche
CABINET
Febrü
Tischlerei / individueller Innenausbau
komplette Raum- und Einrichtungskonzepte
Wichtig wäre mir, dass ich diese Slideshow selbstständig pflegen und steuern kann, ähnlich wie unsere Displays.
Ideal wäre eine Möglichkeit im Backend, mit der ich:
Bilder selbst hochladen kann
die Reihenfolge verändern kann
einzelne Slides aktivieren oder deaktivieren kann
Bilder später unkompliziert austauschen kann
Die Slideshow selbst sollte dabei ruhig und hochwertig wirken – also langsame, dezente Übergänge und kein klassischer Werbebanner-Effekt.
2. Darstellung und Größe des Hero-Bildes
Wichtig wäre mir außerdem, dass das Hero-Bild auf größeren Bildschirmen nicht unten abgeschnitten wird.
Auf meinem Monitor – ich meine, es sind 27 Zoll – wird das aktuelle Motiv unten sichtbar beschnitten. Dadurch wirkt der Bildausschnitt aus meiner Sicht nicht ganz stimmig.
Bei der mitgeschickten Visualisierung ist das wesentlich besser gelöst: Das Motiv ist nahezu vollständig sichtbar und bekommt dadurch deutlich mehr Wirkung.
So würde ich es gerne haben – natürlich responsive für unterschiedliche Bildschirmgrößen.
3. Headline
Die bisherige Headline würde ich gerne ändern zu:
„Räume aus einer Hand. Mit starken Marken und persönlichen Lösungen.“
Die Formulierung B2in Interior kann dabei gerne, ähnlich wie in der Visualisierung, etwas zurückhaltender oberhalb bzw. im Umfeld der Headline eingesetzt werden.
4. Obere Aktionsleiste
Die derzeitige Aktionsleiste ganz oben würde ich komplett entfernen.
Ohne diese Leiste wirkt die Seite aus meiner Sicht ruhiger, klarer und insgesamt hochwertiger.
5. Buttons im Hero
Den zusätzlichen Button „Termin anfragen“ neben „Unsere Marken entdecken“ würde ich entfernen.
Der feststehende Button „Termin anfragen“ rechts unten soll dagegen bestehen bleiben.
Damit gibt es im eigentlichen Hero einen klaren Hauptbutton:
„Unsere Marken entdecken“
6. Persönliches Foto
Ein Foto von mir würde ich gerne dezent und hochwertig auf der Seite integrieren.
Nicht im Hero und auch nicht zu dominant, sondern an einer passenden Stelle weiter unten – zum Beispiel in Verbindung mit dem persönlichen B2in-/Marcel-Scheibe-Bereich.
7. Text Projektgeschäft
Die bisherige Formulierung mit „deutscher Vertragssicherheit“ würde ich gerne ersetzen durch:
„Vollständige Einrichtung für Objekte, Büros und Projektentwicklungen – koordiniert aus einer Hand, von der Küche bis zur letzten Blume. Mit einem zentralen deutschen Vertragspartner und einem eingespielten Herstellernetzwerk.“
8. Farbwelt und Stil
Die bisherige Farbwelt würde ich nicht weiterverfolgen. Die neue Richtung gefällt mir deutlich besser, ich würde sie allerdings noch etwas heller, klarer und hochwertiger gestalten.
Das beigefügte Bild zeigt die Richtung aus meiner Sicht schon sehr gut.
Besonders wichtig wären mir dabei:
ein heller, leicht kühler Grundton
kein reines bzw. steriles Weiß
eher ein sehr helles, modernes Greige / Off-White
ein deutlich präsenteres und klareres Tannengrün
Gold nur als feiner Akzent
Gerade die Kombination aus Tannengrün und einer sehr dünnen Goldlinie gefällt mir gut.
Im beigefügten Beispiel sieht man das unter anderem:
an der feinen goldenen Umrandung des Hero-Bereichs
in Verbindung mit den tannengrünen Flächen
am Button „Unsere Marken entdecken“, der ebenfalls eine feine Goldkante bzw. einen Goldakzent erhält
bei kleinen Linien und Details
Genau so würde ich Gold einsetzen: nicht flächig und nicht dekorativ-verspielt, sondern sehr reduziert als hochwertiges Detail.
Die Kombination aus hellem Untergrund + kräftigem Tannengrün + sehr feinem Gold trifft für mich die gewünschte Richtung inzwischen ziemlich gut.
9. B2in-Logo an die neue Farbwelt anpassen
Wenn wir die Farbwelt entsprechend weiterentwickeln, sollte sich das aus meiner Sicht konsequenterweise auch im B2in-Logo widerspiegeln.
Der Bogen über dem B2in-Schriftzug ist aktuell eher in einem Ocker-/Senfton gehalten. Diesen würde ich gerne auf den neuen Goldton umstellen, den wir auch für die feinen Linien und Akzente auf der Website verwenden.
Damit würde sich die Farbsprache durchgängig wiederfinden:
Tannengrün + Gold + heller, neutraler Grundton.
Ich würde dich daher bitten, die vorhandenen Logo-Varianten entsprechend anzupassen, sodass der bisherige Ockerton konsequent durch den neuen Goldton ersetzt wird und Website und Logo anschließend exakt dieselbe Farbwelt verwenden.
Wichtig wäre mir auch hier, dass das Gold edel und zurückhaltend bleibt – also kein gelblicher oder zu glänzender Goldton, sondern eher ein moderner, gedeckter Premium-Goldton wie in der beigefügten Visualisierung.

View file

@ -0,0 +1,41 @@
Ziemlich nah an der eierlegenden Wollmilchsau
Der Begriff ist vielleicht nicht besonders elegant. Aber ganz falsch ist er nicht.
Individuelle Möbel. Türen. Treppen. Küchen. Wandverkleidungen. Objektbau. Sonderanfertigungen. Lackierungen und Echtholzfurniere. Bis hin zum kompletten Innenausbau.
Mit Otterpohl Möbelservice haben wir einen Partner an unserer Seite, bei dem die Möglichkeiten nicht am einzelnen Möbelstück aufhören.
Denn manchmal braucht ein Raum mehr als einen Schrank oder ein Sideboard. Vielleicht soll eine Treppe Teil des Gestaltungskonzepts werden. Eine Wand erhält eine individuelle Holzverkleidung. Eine Tür soll sich selbstverständlich in den Raum einfügen. Oder Möbel, Materialien und bauliche Elemente müssen so zusammenspielen, dass am Ende alles wie aus einem Guss wirkt.
Genau dort beginnt der individuelle Innenausbau.
Vom einzelnen Möbel bis zum ganzen Raum
Wir arbeiten mit Herstellern, die schon sehr viele individuelle Möglichkeiten bieten. CABINET, Rotpunkt und Febrü geben uns dafür eine starke Basis.
Aber es gibt Projekte, bei denen die Lösung darüber hinausgeht.
Mit Otterpohl Möbelservice können wir deshalb nicht nur individuelle Möbel und Sonderanfertigungen realisieren, sondern auch Türen, Treppen, Küchen, Wand- und Deckenverkleidungen sowie komplette Innenausbauten und Lösungen für den Objektbereich einbeziehen.
So können wir Räume ganzheitlicher denken – und Details nicht isoliert betrachten.
Planung trifft Handwerk
Manchmal entsteht die beste Lösung aus einem Möbelprogramm. Manchmal braucht es eine individuelle Ergänzung. Und manchmal muss etwas komplett neu gedacht und gebaut werden.
Genau diese Kombination macht für uns den Unterschied.
Wir planen den Raum und verbinden bei Bedarf unterschiedliche Hersteller, Materialien und handwerkliche Lösungen miteinander. Otterpohl Möbelservice ergänzt diese Planung um die Möglichkeiten einer klassischen Tischlerei und eines erfahrenen Innenausbauers.
Das Ergebnis kann ein einzelnes Möbel sein.
Oder ein kompletter Raum.
Leistungen und Möglichkeiten
Individuelle Möbel & Sonderanfertigungen
Möbel nach Maß, besondere Konstruktionen und individuelle Lösungen – nach Planung, Idee, Skizze oder konkreter Anforderung.
Innenausbau
Von einzelnen Ausbauarbeiten bis zum ganzheitlichen Raumkonzept – inklusive individueller Verkleidungen und maßgefertigter Einbauten.
Türen & Treppen
Individuelle Türen, Treppen und konstruktive Lösungen, die sich in Architektur und Einrichtung einfügen.
Küchen
Individuelle Küchenlösungen und handwerkliche Ergänzungen auch dort, wo klassische Küchenprogramme an ihre Grenzen kommen.
Wandverkleidungen & besondere Details
Holzverkleidungen, Lamellen, Paneele und individuelle Gestaltungselemente, die Architektur und Einrichtung miteinander verbinden.
Objektbau
Individuelle Lösungen für Büros, Verkaufsräume und gewerblich genutzte Flächen – vom Einzelmöbel bis zum kompletten Ausbau.
Lack & Echtholzfurnier
Individuelle Lackierungen und Furniere für Lösungen, bei denen Farbe, Material und Oberfläche exakt aufeinander abgestimmt werden sollen.
Montage
Die Umsetzung endet nicht bei der Fertigung. Entscheidend ist, dass Möbel, Ausbau und Raum am Ende auch vor Ort sauber zusammenkommen.
Das Ganze statt nur das Einzelne
Genau deshalb passt Otterpohl Möbelservice zu unserem Ansatz.
Wir möchten Räume nicht in einzelne Gewerke zerlegen. Wir möchten Lösungen schaffen, bei denen Möbel, Materialien, Architektur und handwerkliche Details zusammenpassen.
Mal braucht es dafür einen Hersteller.
Mal einen Tischler.
Und manchmal beides.
Nicht easy click & buy. Aber dennoch easy to get.

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 30.8.1, SVG Export Plug-In . SVG Version: 9.03 Build 0) -->
<svg version="1.0" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="152.5 94.5 289 228" xml:space="preserve">
<style type="text/css">
.st0{fill:#F0EEEB;}
.st1{fill:#AA8341;}
.st2{fill:none;stroke:#AA8341;stroke-width:3;stroke-miterlimit:10;}
</style>
<g>
<g>
<g>
<path class="st0" d="M156.1,260.6V157.1h40.7c7.5,0,13.8,1.1,18.9,3.4c5,2.3,8.8,5.4,11.3,9.4c2.5,4,3.8,8.6,3.8,13.8
c0,4.1-0.8,7.6-2.4,10.7c-1.6,3-3.8,5.5-6.6,7.4c-2.8,1.9-5.9,3.3-9.5,4.1v1c3.9,0.1,7.5,1.2,10.9,3.3c3.4,2.1,6.2,4.9,8.3,8.6
c2.1,3.7,3.2,8.1,3.2,13.2c0,5.5-1.3,10.3-4,14.7c-2.7,4.3-6.6,7.7-11.8,10.2c-5.2,2.5-11.7,3.7-19.5,3.7H156.1z M177.1,200.6
h16.6c3,0,5.7-0.5,8.1-1.6c2.4-1.1,4.3-2.6,5.6-4.7c1.4-2,2-4.4,2-7.2c0-3.8-1.3-6.8-4-9.2c-2.6-2.4-6.4-3.5-11.3-3.5h-17V200.6z
M177.1,243.1h18.2c6.2,0,10.7-1.2,13.5-3.6c2.8-2.4,4.3-5.6,4.3-9.5c0-2.9-0.7-5.5-2.1-7.7c-1.4-2.2-3.4-4-6-5.3
c-2.6-1.3-5.6-1.9-9.2-1.9h-18.8V243.1z"/>
<path class="st0" d="M248.7,260.6v-15.3l36.4-34.4c3.1-3.1,5.8-5.8,7.9-8.3c2.1-2.5,3.8-4.9,4.9-7.3c1.1-2.4,1.7-5,1.7-7.7
c0-3.1-0.7-5.8-2.1-8c-1.4-2.2-3.3-3.9-5.7-5.1c-2.4-1.2-5.1-1.8-8.2-1.8c-3.2,0-6,0.6-8.3,1.9c-2.4,1.3-4.2,3.1-5.5,5.6
c-1.3,2.4-1.9,5.3-1.9,8.7h-20.1c0-6.7,1.5-12.6,4.6-17.5c3.1-5,7.3-8.8,12.7-11.5c5.4-2.7,11.7-4.1,18.8-4.1
c7.3,0,13.6,1.3,19,3.9c5.4,2.6,9.6,6.2,12.7,10.8c3,4.6,4.5,9.9,4.5,15.8c0,3.8-0.7,7.6-2.2,11.4c-1.5,3.8-4.2,8-8,12.6
c-3.8,4.7-9.3,10.2-16.3,16.7l-15.5,15.4v0.8h43.4v17.4H248.7z"/>
</g>
</g>
<g>
<path class="st0" d="M344.3,260.4v-74.1h18.2v74.1H344.3z"/>
<path class="st0" d="M394.4,260.4h-18.2v-74.1h18.2v12.4h0.7c1.5-4,3.9-7.3,7.2-10c3.3-2.7,7.8-4,13.6-4c7.6,0,13.4,2.5,17.5,7.6
c4.1,5.1,6.2,12.2,6.2,21.4v46.8h-18.2v-45c0-5.3-0.9-9.3-2.8-11.9c-1.9-2.6-5-4-9.4-4c-2.5,0-4.8,0.5-7.1,1.3
c-2.3,0.9-4.1,2.2-5.5,4c-1.4,1.8-2.1,4-2.1,6.6V260.4z"/>
</g>
<path class="st1" d="M353.4,150.5c2.5,0,4.7,0.3,6.6,0.8c-14-32.8-49-56-89.9-56c-41.5,0-76.8,23.9-90.5,57.3h14.3
c8.8-30.8,39.6-53.5,76.1-53.5c36.1,0,66.6,22.2,75.8,52.5C347.9,150.9,350.4,150.5,353.4,150.5z"/>
<path class="st0" d="M353.4,176.3c-3.7,0-6.4-0.9-8.1-2.6c-1.7-1.7-2.6-3.9-2.6-6.5v-2.8c0-2.6,0.9-4.8,2.6-6.5
c1.7-1.7,4.4-2.6,8.1-2.6c3.7,0,6.4,0.9,8.1,2.6c1.7,1.7,2.6,3.9,2.6,6.5v2.8c0,2.6-0.9,4.7-2.6,6.5
C359.7,175.4,357,176.3,353.4,176.3z"/>
<g>
<path class="st0" d="M155.7,320.1v-5.4h4v-20.5h-4v-5.4h14.9v5.4h-4v20.5h4v5.4H155.7z"/>
<path class="st0" d="M196.1,305.2l-3-6.3h-0.1v21.1h-6.4v-31.4h7.5l8.6,14.8l3,6.3h0.1v-21.1h6.4v31.4h-7.5L196.1,305.2z"/>
<path class="st0" d="M242.3,294.7v25.3h-6.8v-25.3H227v-6.1h23.8v6.1H242.3z"/>
<path class="st0" d="M265.7,320.1v-31.4h21.4v6.1h-14.5v6.4H285v6h-12.5v6.8h14.5v6.1H265.7z"/>
<path class="st0" d="M310,320.1h-6.8v-31.4h14.9c5.8,0,9.5,4,9.5,10c0,4.2-1.9,7.6-5.7,9l6.2,12.3h-7.6l-5.5-11.5H310V320.1z
M317.2,302.8c2,0,3.3-1.1,3.3-3.1v-2c0-2-1.3-3.1-3.3-3.1H310v8.2H317.2z"/>
<path class="st0" d="M342.5,320.1v-5.4h4v-20.5h-4v-5.4h14.9v5.4h-4v20.5h4v5.4H342.5z"/>
<path class="st0" d="M372.1,304.4c0-10.7,5.5-16.2,13.8-16.2c8.3,0,13.8,5.6,13.8,16.2s-5.5,16.2-13.8,16.2
C377.6,320.6,372.1,315,372.1,304.4z M392.5,306.7V302c0-5-2.5-7.8-6.6-7.8s-6.6,2.8-6.6,7.8v4.7c0,5,2.5,7.8,6.6,7.8
S392.5,311.7,392.5,306.7z"/>
<path class="st0" d="M422.4,320.1h-6.8v-31.4h14.9c5.8,0,9.5,4,9.5,10c0,4.2-1.9,7.6-5.7,9l6.2,12.3h-7.6l-5.5-11.5h-4.9V320.1z
M429.6,302.8c2,0,3.3-1.1,3.3-3.1v-2c0-2-1.3-3.1-3.3-3.1h-7.2v8.2H429.6z"/>
</g>
<line class="st2" x1="153.4" y1="273.7" x2="439.5" y2="273.7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 30.8.1, SVG Export Plug-In . SVG Version: 9.03 Build 0) -->
<svg version="1.0" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="152.5 94.5 289 228" xml:space="preserve">
<style type="text/css">
.st0{fill:#14342C;}
.st1{fill:#AA8341;}
.st2{fill:none;stroke:#AA8341;stroke-width:3;stroke-miterlimit:10;}
</style>
<g>
<g>
<g>
<path class="st0" d="M156.1,260.6V157.1h40.7c7.5,0,13.8,1.1,18.9,3.4c5,2.3,8.8,5.4,11.3,9.4c2.5,4,3.8,8.6,3.8,13.8
c0,4.1-0.8,7.6-2.4,10.7c-1.6,3-3.8,5.5-6.6,7.4c-2.8,1.9-5.9,3.3-9.5,4.1v1c3.9,0.1,7.5,1.2,10.9,3.3c3.4,2.1,6.2,4.9,8.3,8.6
c2.1,3.7,3.2,8.1,3.2,13.2c0,5.5-1.3,10.3-4,14.7c-2.7,4.3-6.6,7.7-11.8,10.2c-5.2,2.5-11.7,3.7-19.5,3.7H156.1z M177.1,200.6
h16.6c3,0,5.7-0.5,8.1-1.6c2.4-1.1,4.3-2.6,5.6-4.7c1.4-2,2-4.4,2-7.2c0-3.8-1.3-6.8-4-9.2c-2.6-2.4-6.4-3.5-11.3-3.5h-17V200.6z
M177.1,243.1h18.2c6.2,0,10.7-1.2,13.5-3.6c2.8-2.4,4.3-5.6,4.3-9.5c0-2.9-0.7-5.5-2.1-7.7c-1.4-2.2-3.4-4-6-5.3
c-2.6-1.3-5.6-1.9-9.2-1.9h-18.8V243.1z"/>
<path class="st0" d="M248.7,260.6v-15.3l36.4-34.4c3.1-3.1,5.8-5.8,7.9-8.3c2.1-2.5,3.8-4.9,4.9-7.3c1.1-2.4,1.7-5,1.7-7.7
c0-3.1-0.7-5.8-2.1-8c-1.4-2.2-3.3-3.9-5.7-5.1c-2.4-1.2-5.1-1.8-8.2-1.8c-3.2,0-6,0.6-8.3,1.9c-2.4,1.3-4.2,3.1-5.5,5.6
c-1.3,2.4-1.9,5.3-1.9,8.7h-20.1c0-6.7,1.5-12.6,4.6-17.5c3.1-5,7.3-8.8,12.7-11.5c5.4-2.7,11.7-4.1,18.8-4.1
c7.3,0,13.6,1.3,19,3.9c5.4,2.6,9.6,6.2,12.7,10.8c3,4.6,4.5,9.9,4.5,15.8c0,3.8-0.7,7.6-2.2,11.4c-1.5,3.8-4.2,8-8,12.6
c-3.8,4.7-9.3,10.2-16.3,16.7l-15.5,15.4v0.8h43.4v17.4H248.7z"/>
</g>
</g>
<g>
<path class="st0" d="M344.3,260.4v-74.1h18.2v74.1H344.3z"/>
<path class="st0" d="M394.4,260.4h-18.2v-74.1h18.2v12.4h0.7c1.5-4,3.9-7.3,7.2-10c3.3-2.7,7.8-4,13.6-4c7.6,0,13.4,2.5,17.5,7.6
c4.1,5.1,6.2,12.2,6.2,21.4v46.8h-18.2v-45c0-5.3-0.9-9.3-2.8-11.9c-1.9-2.6-5-4-9.4-4c-2.5,0-4.8,0.5-7.1,1.3
c-2.3,0.9-4.1,2.2-5.5,4c-1.4,1.8-2.1,4-2.1,6.6V260.4z"/>
</g>
<path class="st1" d="M353.4,150.5c2.5,0,4.7,0.3,6.6,0.8c-14-32.8-49-56-89.9-56c-41.5,0-76.8,23.9-90.5,57.3h14.3
c8.8-30.8,39.6-53.5,76.1-53.5c36.1,0,66.6,22.2,75.8,52.5C347.9,150.9,350.4,150.5,353.4,150.5z"/>
<path class="st0" d="M353.4,176.3c-3.7,0-6.4-0.9-8.1-2.6c-1.7-1.7-2.6-3.9-2.6-6.5v-2.8c0-2.6,0.9-4.8,2.6-6.5
c1.7-1.7,4.4-2.6,8.1-2.6c3.7,0,6.4,0.9,8.1,2.6c1.7,1.7,2.6,3.9,2.6,6.5v2.8c0,2.6-0.9,4.7-2.6,6.5
C359.7,175.4,357,176.3,353.4,176.3z"/>
<g>
<path class="st0" d="M155.7,320.1v-5.4h4v-20.5h-4v-5.4h14.9v5.4h-4v20.5h4v5.4H155.7z"/>
<path class="st0" d="M196.1,305.2l-3-6.3h-0.1v21.1h-6.4v-31.4h7.5l8.6,14.8l3,6.3h0.1v-21.1h6.4v31.4h-7.5L196.1,305.2z"/>
<path class="st0" d="M242.3,294.7v25.3h-6.8v-25.3H227v-6.1h23.8v6.1H242.3z"/>
<path class="st0" d="M265.7,320.1v-31.4h21.4v6.1h-14.5v6.4H285v6h-12.5v6.8h14.5v6.1H265.7z"/>
<path class="st0" d="M310,320.1h-6.8v-31.4h14.9c5.8,0,9.5,4,9.5,10c0,4.2-1.9,7.6-5.7,9l6.2,12.3h-7.6l-5.5-11.5H310V320.1z
M317.2,302.8c2,0,3.3-1.1,3.3-3.1v-2c0-2-1.3-3.1-3.3-3.1H310v8.2H317.2z"/>
<path class="st0" d="M342.5,320.1v-5.4h4v-20.5h-4v-5.4h14.9v5.4h-4v20.5h4v5.4H342.5z"/>
<path class="st0" d="M372.1,304.4c0-10.7,5.5-16.2,13.8-16.2c8.3,0,13.8,5.6,13.8,16.2s-5.5,16.2-13.8,16.2
C377.6,320.6,372.1,315,372.1,304.4z M392.5,306.7V302c0-5-2.5-7.8-6.6-7.8s-6.6,2.8-6.6,7.8v4.7c0,5,2.5,7.8,6.6,7.8
S392.5,311.7,392.5,306.7z"/>
<path class="st0" d="M422.4,320.1h-6.8v-31.4h14.9c5.8,0,9.5,4,9.5,10c0,4.2-1.9,7.6-5.7,9l6.2,12.3h-7.6l-5.5-11.5h-4.9V320.1z
M429.6,302.8c2,0,3.3-1.1,3.3-3.1v-2c0-2-1.3-3.1-3.3-3.1h-7.2v8.2H429.6z"/>
</g>
<line class="st2" x1="153.4" y1="273.7" x2="439.5" y2="273.7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -151,6 +151,102 @@
--font-secondary: 'IBM Plex Sans', 'Inter', system-ui, -apple-system, sans-serif;
}
/* ── Interior, Variante „Modern“ (Nachtrag 18) ──────────────────────
Marcels Rückmeldung vom 23.09.: die Creme-Flächen wirken altbacken. Diese
Variante lässt Tanne und Ocker aus dem Logo unangetastet und hellt nur die
Flächen auf: Grund fast weiß, Karten weiß, Sektionswechsel in hellem Grau-
Beige, Footer in Tanne. Aktiv, sobald der Farb-Schalter (palette-switch)
`palette-modern` an <html> setzt; die Wahl liegt im localStorage. */
.palette-modern .world-interior {
--background: 40 25% 98%; /* #fbfaf8 fast weiß, leicht warm */
--card: 0 0% 100%; /* Karten reinweiß */
--popover: 0 0% 100%;
--muted: 40 12% 94%; /* #f2f0ed */
--muted-foreground: 24 8% 38%;
--accent: 36 14% 93%; /* #efece7 Sektionswechsel, klar statt Sand */
--accent-dark: 36 14% 88%;
--border: 36 12% 88%; /* #e6e2dc */
--input: 0 0% 100%;
--hero-container: 40 14% 95%;
--hero-container-dark: 40 12% 90%;
--hero-container-light: 40 25% 98%;
/* Footer dunkel in Tanne – gibt der hellen Seite unten einen klaren Abschluss */
--footer-bg: 175 16% 15%;
--footer-fg: 38 33% 95%;
--footer-muted: 38 20% 76%;
--footer-line: 38 20% 76%;
/* Karten sind jetzt weiß auf fast weiß: etwas mehr Schatten, damit sie sich lösen */
--shadow-card: 0 6px 24px -10px hsl(var(--foreground) / 0.14);
--shadow-elevated: 0 24px 48px -20px hsl(var(--foreground) / 0.2);
}
/* ── Interior, Entwurf „Greige“ (Nachtrag 19, Briefing Marcel 24.09.) ─────
Heller, leicht kühler Grundton (Greige statt Creme), deutlich präsenteres
Tannengrün, Gold nur als feine Linie. Liegt als Klasse am <body> der
Entwurfsseite /entwurf – nicht am <html> wie der Browser-Schalter, damit
sich beide nicht überlagern. */
.world-interior.palette-greige {
--background: 45 14% 93%; /* #f0eeeb Greige, Off-White */
--foreground: 166 25% 13%; /* Tannengrau für Text */
--card: 40 20% 97%; /* #f8f7f4 – hell, aber kein steriles Weiß */
--card-foreground: 166 25% 13%;
--popover: 40 20% 97%;
--popover-foreground: 166 25% 13%;
--primary: 165 44% 14%; /* #14342c Tannengrün, klar und dunkel */
--primary-light: 165 32% 22%;
--primary-lighter: 165 12% 82%;
--primary-dark: 165 50% 10%;
--primary-foreground: 45 20% 95%;
--secondary: 38 45% 46%; /* #aa8341 gedecktes Gold */
--secondary-light: 38 45% 56%;
--secondary-lighter: 40 40% 80%;
--secondary-dark: 38 48% 38%;
--secondary-deep: 38 55% 34%; /* Gold als Textfarbe auf hellem Grund */
--cta-band-line: 38 45% 46%;
--secondary-foreground: 45 20% 95%;
--clay: 30 8% 50%;
--muted: 45 12% 90%;
--muted-foreground: 160 8% 36%;
--accent: 45 12% 89%; /* #e8e6e0 Sektionswechsel */
--accent-dark: 45 12% 84%;
--accent-foreground: 166 25% 13%;
--dark-bg: 165 44% 14%;
--dark-text: 45 20% 95%;
--dark-muted: 45 15% 78%;
/* Footer in Tannengrün, Haarlinie in Gold */
--footer-bg: 165 44% 14%;
--footer-fg: 45 20% 95%;
--footer-muted: 45 15% 78%;
--footer-line: 38 45% 46%;
--cta-band-end: 165 30% 22%;
--border: 40 10% 84%;
--input: 40 20% 98%;
--ring: 165 44% 14%;
--hero-container: 40 20% 97%;
--hero-container-dark: 45 12% 90%;
--hero-container-light: 40 25% 98%;
--shadow-card: 0 6px 24px -10px hsl(var(--foreground) / 0.14);
--shadow-elevated: 0 30px 60px -28px hsl(var(--primary) / 0.35);
}
}
/* Interior: Ocker ist Akzent, kein Flächenton unter Text – Creme auf Ocker erreicht
@ -173,4 +269,202 @@
color: hsl(var(--secondary-deep));
}
/* Variante „Modern“: auf dem dunklen Footer trägt Ocker in voller Stärke. */
.palette-modern .world-interior .section-footer .text-secondary {
color: hsl(var(--secondary));
}
/* Variante „Modern“: Abschlussband und Footer sind beide dunkel – eine helle Haarlinie trennt sie. */
.palette-modern .world-interior .section-footer {
border-top: 1px solid hsl(var(--footer-muted) / 0.35);
}
/* Der schwebende Termin-Button liegt über Bildern und weißen Karten: er bekommt eine
Fläche in Kartenfarbe, damit die Kontur nicht im Hintergrund verschwindet. */
.world-interior .appointment-toggle.btn-secondary-accent {
background: hsl(var(--card)) !important;
}
.world-interior .appointment-toggle.btn-secondary-accent:hover {
background: hsl(var(--primary)) !important;
}
/* Cookie-Manager (acme/cookie-consent) in der Interior-Welt: die Paketfarben aus
config/cookie-consent.php sind das Blau der Immobilien-Welt. Hier folgt der schwebende
Button dem Akzent-Button der Seite (Tanne, Hover heller), Icon, Schalter und Link-Hover
tragen Ocker, „Speichern“ liegt in Tanne. Die Immobilien-Welt behält ihr Blau. */
.world-interior .cookie-consent-wrapper {
--cc-primary: hsl(var(--secondary));
--cc-primary-hover: hsl(var(--secondary-dark));
--cc-save: hsl(var(--primary));
--cc-save-hover: hsl(var(--primary-light));
--cc-button-bg: hsl(var(--primary));
--cc-button-hover: hsl(var(--primary-light));
}
.world-interior .cookie-consent-wrapper .cc-floating-btn {
--tw-ring-color: hsl(var(--secondary)) !important;
color: hsl(var(--primary-foreground));
background: linear-gradient(145deg, hsl(var(--primary)) 0%, hsl(var(--primary-light)) 100%);
}
.world-interior .cookie-consent-wrapper .cc-floating-btn:hover {
background: linear-gradient(145deg, hsl(var(--primary-light)) 0%, hsl(var(--primary)) 100%);
}
/* Variante „Modern“: der Footer ist dunkel, also das negative Logo statt des positiven. */
.footer-logo-dark {
display: none;
}
.palette-modern .world-interior .footer-logo-light {
display: none;
}
.palette-modern .world-interior .footer-logo-dark {
display: block;
}
@import "./shared-styles.css";
/* ── Entwurf „Greige“: Footer dunkel mit Goldlinie und negativem Logo ─────── */
.world-interior.palette-greige .section-footer {
border-top: 1px solid hsl(var(--secondary) / 0.6);
}
.world-interior.palette-greige .section-footer .text-secondary {
color: hsl(var(--secondary));
}
.world-interior.palette-greige .footer-logo-light {
display: none;
}
.world-interior.palette-greige .footer-logo-dark {
display: block;
}
/* ── Hero des Entwurfs: Rahmen mit Goldkante, grüne Flanken, Slider ────────
Goldkanten als Verlaufs-Border: transparenter Rand, Fläche auf padding-box,
Goldverlauf auf border-box. Kein Pseudo-Rand mit Maske mehr – der ließ an
der gerundeten Kante eine helle Linie durchscheinen. Die grünen Flanken sind
Inline-SVGs (components/web/hero-flank-*), skaliert über die Rahmenhöhe. */
.hero-draft .hero-frame {
border: 1.5px solid transparent;
background:
linear-gradient(hsl(var(--hero-container)), hsl(var(--hero-container))) padding-box,
linear-gradient(
125deg,
hsl(var(--secondary-light)) 0%,
hsl(var(--secondary)) 28%,
hsl(var(--secondary) / 0.35) 62%,
hsl(var(--secondary-light)) 100%
) border-box;
box-shadow: var(--shadow-elevated);
isolation: isolate;
}
.hero-draft .hero-flank {
position: absolute;
z-index: 0;
width: auto;
}
.hero-draft .hero-flank-right {
top: 0;
right: 0;
height: 100%;
}
.hero-draft .hero-flank-left {
left: 0;
bottom: 0;
height: 46%;
}
.hero-draft .hero-image {
position: relative;
z-index: 1;
}
/* Goldkante um das Bild, gleiche Technik wie am Rahmen */
.hero-draft .hero-image-frame {
border: 1.5px solid transparent;
border-radius: 1.25rem;
background:
linear-gradient(hsl(var(--card)), hsl(var(--card))) padding-box,
linear-gradient(
135deg,
hsl(var(--secondary-light)) 0%,
hsl(var(--secondary)) 40%,
hsl(var(--secondary) / 0.4) 100%
) border-box;
box-shadow: var(--shadow-elevated);
}
.hero-draft .hero-eyebrow {
display: inline-flex;
align-items: center;
gap: 1rem;
font-size: 0.75rem;
letter-spacing: 0.28em;
text-transform: uppercase;
color: hsl(var(--muted-foreground));
}
.hero-draft .hero-eyebrow::after {
content: "";
width: 3.5rem;
height: 1px;
background: hsl(var(--secondary));
}
.hero-draft .hero-title {
font-family: var(--font-secondary);
font-size: clamp(2rem, 3.1vw, 3.3rem);
line-height: 1.12;
font-weight: 400;
letter-spacing: -0.01em;
color: hsl(var(--primary));
}
.hero-draft .hero-title em {
font-style: normal;
color: hsl(var(--secondary-deep));
}
/* Button: Goldrand rundum, unten kräftiger, Pfeil in Gold */
.hero-draft .btn-hero {
display: inline-flex;
align-items: center;
gap: 0.75rem;
padding: 1rem 1.75rem;
border-radius: 0.75rem;
font-weight: 500;
color: hsl(var(--primary-foreground));
background: linear-gradient(145deg, hsl(var(--primary)) 0%, hsl(var(--primary-light)) 100%);
border: 1px solid hsl(var(--secondary));
border-bottom-width: 3px;
box-shadow: 0 14px 30px -14px hsl(var(--primary) / 0.6);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.hero-draft .btn-hero svg {
color: hsl(var(--secondary-light));
}
.hero-draft .btn-hero:hover {
transform: translateY(-1px);
box-shadow: 0 18px 34px -14px hsl(var(--primary) / 0.7);
}
.hero-draft .hero-slide {
transition: opacity 1.4s ease-in-out;
}
@media (prefers-reduced-motion: reduce) {
.hero-draft .hero-slide {
transition: none;
}
}

View file

@ -80,14 +80,12 @@ return [
],
],
'hero' => [
'title' => '<span class="text-secondary">B2in Interior</span> – Premium-Einrichtung von starken Marken. Für Ihr Zuhause. Für Ihr Projekt.',
'title' => '<span class="text-secondary">Räume aus einer Hand.</span><br>Mit starken Marken und persönlichen Lösungen.',
'subtitle' => 'Von der einzelnen Maßanfertigung bis zur vollständigen Ausstattung – mit CABINET, Rotpunkt Küchen, Febrü und einem Netzwerk regionaler Fachexperten.',
'image' => 'b2in/hero-room.jpg',
'image_alt' => 'B2in Interior – Premium-Einrichtung von starken Marken',
'cta1_text' => 'Unsere Marken entdecken',
'cta1_link' => '/marken',
'cta2_text' => 'Termin anfragen',
'cta2_link' => '/contact',
'stats' => [
'Premium-Marken',
'Maßgefertigte Lösungen',
@ -96,6 +94,15 @@ return [
'card_title' => 'B2in Interior',
'card_text' => 'Einrichtung, <span class="text-secondary">die bleibt.</span>',
],
'hero_slider' => [
'eyebrow' => 'B2in Interior',
'title' => '<em>Räume aus einer Hand.</em> Mit starken Marken und persönlichen Lösungen.',
'subtitle' => 'Wir vereinen CABINET, Rotpunkt Küchen, Febrü und ein Netzwerk regionaler Experten, um individuelle Interior-Lösungen für Ihr Zuhause oder Ihr Projekt zu realisieren.',
'cta_text' => 'Unsere Marken entdecken',
'cta_link' => '/marken',
'interval' => 7,
'slides' => [],
],
'founder_bar' => [
'image' => 'b2in/marcel-scheibe.jpg',
'name' => 'Marcel Scheibe',
@ -203,7 +210,7 @@ return [
[
'icon' => 'building-office-2',
'title' => 'Ausstattung für Ihr Projekt',
'description' => 'Vollständige Einrichtung für Objekte, Büros und Projektentwicklungen – koordiniert aus einer Hand, von der Küche bis zur letzten Blume. Mit deutscher Vertragssicherheit und einem eingespielten Herstellernetzwerk.',
'description' => 'Vollständige Einrichtung für Objekte, Büros und Projektentwicklungen. Koordiniert aus einer Hand, von der Küche bis zur letzten Blume. Mit einem zentralen deutschen Vertragspartner und einem eingespielten Herstellernetzwerk.',
'button_text' => 'Projekt besprechen',
'button_link' => '/contact',
],
@ -251,15 +258,15 @@ return [
],
[
'slug' => 'tischlerei',
'name' => 'Tischlerei',
'tagline' => 'Maßarbeit, die den Raum vollendet.',
'teaser' => 'Aufbau, Sonderlösungen und Veredelung aus einer Tischlerei in Ostwestfalen – dort, wo Serienmöbel aufhören und Ihr Raum anfängt.',
'name' => 'Otterpohl Möbelservice',
'tagline' => 'Nicht easy click & buy. Aber dennoch easy to get.',
'teaser' => 'Individuelle Möbel, Türen, Treppen, Küchen, Wandverkleidungen und Objektbau – bis hin zum kompletten Innenausbau. Tischlerhandwerk aus Rheda-Wiedenbrück.',
'image' => '',
'logo' => '',
'link_text' => 'Mehr zur Tischlerei',
'logo' => 'marken/otterpohl.png',
'link_text' => 'Mehr zu Otterpohl',
'origin_city' => 'Rheda-Wiedenbrück',
'origin_distance' => 'rund 35 km',
'origin_role' => 'Unsere Werkstatt für Aufbau, Sonderlösungen und Veredelung.',
'origin_role' => 'Tischlerei und Innenausbau aus Rheda-Wiedenbrück – vom einzelnen Möbel bis zum ganzen Raum, inklusive Montage.',
],
],
],
@ -292,10 +299,30 @@ return [
],
[
'title' => 'Aufbau vor Ort, Sonderwünsche inklusive',
'description' => 'Unsere Tischlerei baut auf und ergänzt, was der Raum zusätzlich braucht: Blenden, Anschlüsse, Sonderlösungen.',
'description' => 'Otterpohl baut auf und ergänzt, was der Raum zusätzlich braucht: Blenden, Anschlüsse, Sonderlösungen.',
],
],
],
// Gemeinsamer Handwerk-Abschluss jeder Markenseite (Feedback 17.09., Nachtrag 15). Erscheint auf
// allen Markenseiten außer bei Otterpohl selbst; der Link führt zur Otterpohl-Seite.
'brand_craft' => [
'title' => 'Planung plus Handwerk',
'paragraphs' => [
'CABINET, Rotpunkt und Febrü decken bereits eine enorme planerische und gestalterische Bandbreite ab. Es geht deshalb ausdrücklich nicht um Sonderanfertigungen um jeden Preis.',
'Natürlich gibt es trotzdem diesen einen Fall, bei dem selbst ein hervorragendes System an seine Grenze kommt: der Übergang zur Architektur, die besondere Verkleidung, die Wand, die noch fehlt, oder der letzte Millimeter. Dann greift unsere Zusammenarbeit mit Otterpohl aus Rheda-Wiedenbrück – Tischlerhandwerk, Innenausbau und Montage aus einer Hand.',
'Wir wägen gestalterischen Mehrwert, technische Notwendigkeit und wirtschaftliche Sinnhaftigkeit ehrlich miteinander ab. Am Ende entscheiden aber Ihre Wünsche – nicht unser Drang, Ihnen noch eine Sonderlösung zu verkaufen.',
],
'detail' => [
'title' => 'Wenn das Handwerk den letzten Millimeter übernimmt',
'paragraphs' => [
'Möglich sind beispielsweise passgenaue Nischenlösungen, besondere Verkleidungen, raumhohe Einbauten, Übergänge zu angrenzenden Möbeln, individuelle Lackierungen und Echtholzfurniere oder Anpassungen an die vorhandene Architektur.',
'Bei Bedarf endet das Konzept nicht am Möbel. Auch Trockenbau, Tapeten, Fußböden, Wandgestaltung, Beleuchtung und weitere Details können einbezogen werden.',
],
],
'claim' => 'Die Möglichkeiten eines Maßsystems. Die Flexibilität des Handwerks.',
'link_text' => 'Mehr zu Otterpohl',
'brand_slug' => 'tischlerei',
],
'sponsoring' => [
'title' => 'Regional <span class="text-secondary">verbunden</span>',
'subtitle' => 'Wir unterstützen Vereine und Organisationen aus der Region – weil Verwurzelung mehr ist als eine Adresse.',

View file

@ -17,6 +17,20 @@ return [
'menu_open' => 'Menü öffnen',
// Welten-Wechsler (Interior / Immobilien)
// Farb-Schalter für die Abstimmung der Palette (Nachtrag 18)
'palette' => [
'label' => 'Farbwelt',
'classic' => 'Bisher',
'modern' => 'Neu',
'draft' => 'Entwurf',
],
// Slideshow im Hero des Entwurfs (Nachtrag 19)
'hero_slider' => [
'label' => 'Einblicke in unsere Marken',
'dots' => 'Bild wählen',
'dot' => 'Bild :n von :total: :label',
'empty' => 'Noch keine Bilder hinterlegt.',
],
'worlds_label' => 'Bereich wechseln',
'world_interior' => 'Interior',
'world_immobilien' => 'Immobilien',
@ -39,7 +53,8 @@ return [
'success' => 'Vielen Dank! Wir melden uns zeitnah bei Ihnen und stimmen den Termin ab.',
'new_request' => 'Weitere Anfrage',
'date_in_past' => 'Bitte einen Termin ab heute wählen.',
'whatsapp_intro' => 'Lieber direkt schreiben? Wir bereiten die Nachricht mit Ihren Angaben vor.',
'whatsapp_intro' => 'Am schnellsten per WhatsApp – wir bereiten die Nachricht mit Ihren Angaben vor.',
'or_form' => 'oder per Formular',
'whatsapp_text' => 'Hallo B2in Interior, ich hätte gern einen Beratungstermin.',
'whatsapp_note' => 'Öffnet WhatsApp mit vorbereitetem Text. Abgeschickt wird die Nachricht erst von Ihnen.',
],
@ -58,12 +73,22 @@ return [
'more_about' => 'Mehr zu :name',
// Markengalerie – realisierte Projekte vs. Entwuerfe
'brand_gallery_title' => 'Referenzen und Inspirationen',
'brand_gallery_intro' => 'Ausgeführte Arbeiten aus unseren Aufträgen – und Visualisierungen, die zeigen, was in einem Raum möglich ist.',
'brand_gallery_title' => 'Inspirationen und Entwürfe',
'brand_gallery_intro' => 'Visualisierungen, die zeigen, was in einem Raum möglich ist – noch bevor das erste Möbel steht.',
// Varianten je Galerie-Inhalt (Nachtrag 19): nur Entwürfe / nur Projekte und Werkstatt / gemischt
'brand_gallery_title_inspirations' => 'Inspirationen und Entwürfe',
'brand_gallery_intro_inspirations' => 'Visualisierungen, die zeigen, was in einem Raum möglich ist – noch bevor das erste Möbel steht.',
'brand_gallery_title_projects' => 'Referenzen',
'brand_gallery_intro_projects' => 'Realisierte Projekte – so sieht es aus, wenn Planung und Handwerk fertig sind.',
'brand_gallery_title_projects_company' => 'Referenzen und Einblicke',
'brand_gallery_intro_projects_company' => 'Realisierte Projekte und ein Blick in die Werkstatt, in der sie entstehen.',
'brand_gallery_title_mixed' => 'Projekte und Entwürfe',
'brand_gallery_intro_mixed' => 'Umgesetzte Projekte und Visualisierungen, die zeigen, was in einem Raum möglich ist.',
'brand_gallery_tab_projects' => 'Realisierte Projekte',
'brand_gallery_tab_inspirations' => 'Inspirationen und Entwürfe',
'brand_gallery_badge_project' => 'Realisiertes Projekt',
'brand_gallery_badge_inspiration' => 'Visualisierung',
'brand_gallery_badge_company' => 'Aus der Werkstatt',
'brand_gallery_inspiration_note' => 'Entwürfe und Visualisierungen. Sie zeigen gestalterische Möglichkeiten, noch bevor das erste Möbel steht.',
'previous' => 'Vorheriges Bild',
'next' => 'Nächstes Bild',

View file

@ -77,14 +77,12 @@ return [
],
],
'hero' => [
'title' => '<span class="text-secondary">B2in Interior</span> – Premium furnishing from strong brands. For your home. For your project.',
'title' => '<span class="text-secondary">Spaces from a single source.</span><br>With strong brands and personal solutions.',
'subtitle' => 'From a single bespoke piece to a complete fit-out – with CABINET, Rotpunkt Küchen, Febrü and a network of regional specialists.',
'image' => 'b2in/hero-room.jpg',
'image_alt' => 'B2in Interior – Premium furnishing from strong brands',
'cta1_text' => 'Discover our brands',
'cta1_link' => '/marken',
'cta2_text' => 'Request an appointment',
'cta2_link' => '/contact',
'stats' => [
'Premium brands',
'Bespoke solutions',
@ -93,6 +91,15 @@ return [
'card_title' => 'B2in Interior',
'card_text' => 'Interiors <span class="text-secondary">that last.</span>',
],
'hero_slider' => [
'eyebrow' => 'B2in Interior',
'title' => '<em>Rooms from a single source.</em> With strong brands and personal solutions.',
'subtitle' => 'We bring together CABINET, Rotpunkt Küchen, Febrü and a network of regional experts to create individual interior solutions for your home or your project.',
'cta_text' => 'Discover our brands',
'cta_link' => '/marken',
'interval' => 7,
'slides' => [],
],
'founder_bar' => [
'image' => 'b2in/marcel-scheibe.jpg',
'name' => 'Marcel Scheibe',
@ -200,7 +207,7 @@ return [
[
'icon' => 'building-office-2',
'title' => 'Fit-out for your project',
'description' => 'Complete furnishing for properties, offices and developments – coordinated from a single source, from the kitchen to the last flower. With German contractual reliability and an established manufacturer network.',
'description' => 'Complete furnishing for properties, offices and developments. Coordinated from a single source, from the kitchen to the last flower. With a single German contract partner and an established manufacturer network.',
'button_text' => 'Discuss your project',
'button_link' => '/contact',
],
@ -248,15 +255,15 @@ return [
],
[
'slug' => 'tischlerei',
'name' => 'Joinery',
'tagline' => 'Craftsmanship that completes the room.',
'teaser' => 'Assembly, bespoke solutions and finishing from a joinery in East Westphalia – where series furniture ends and your room begins.',
'name' => 'Otterpohl Möbelservice',
'tagline' => 'Not easy click & buy. But still easy to get.',
'teaser' => 'Bespoke furniture, doors, stairs, kitchens, wall cladding and contract work – up to complete interior fit-outs. Joinery from Rheda-Wiedenbrück.',
'image' => '',
'logo' => '',
'link_text' => 'More about the joinery',
'logo' => 'marken/otterpohl.png',
'link_text' => 'More about Otterpohl',
'origin_city' => 'Rheda-Wiedenbrück',
'origin_distance' => 'about 35 km',
'origin_role' => 'Our workshop for assembly, custom solutions and finishing.',
'origin_role' => 'Joinery and interior fit-out from Rheda-Wiedenbrück – from a single piece to the whole room, installation included.',
],
],
],
@ -286,10 +293,28 @@ return [
],
[
'title' => 'Assembly on site, special requests included',
'description' => 'Our joinery assembles and adds what the room needs beyond that: panels, connections, custom solutions.',
'description' => 'Otterpohl assembles and adds what the room needs beyond that: panels, connections, custom solutions.',
],
],
],
'brand_craft' => [
'title' => 'Planning plus craftsmanship',
'paragraphs' => [
'CABINET, Rotpunkt and Febrü already cover an enormous range of planning and design options. So this is explicitly not about custom-made pieces at any price.',
'Still, there is always that one case where even an excellent system reaches its limit: the transition to the architecture, a special cladding, the wall that is still missing, or the last millimetre. That is where our collaboration with Otterpohl from Rheda-Wiedenbrück comes in – joinery, interior fit-out and installation from a single source.',
'We weigh design value, technical necessity and economic sense honestly. In the end, your wishes decide – not our urge to sell you yet another special solution.',
],
'detail' => [
'title' => 'When craftsmanship takes over the last millimetre',
'paragraphs' => [
'Possible are, for example, precisely fitted alcove solutions, special claddings, full-height built-ins, transitions to adjoining furniture, individual lacquer finishes and real-wood veneers, or adaptations to the existing architecture.',
'If needed, the concept does not end at the furniture. Drywall, wallpaper, flooring, wall design, lighting and further details can be included.',
],
],
'claim' => 'The possibilities of a made-to-measure system. The flexibility of craftsmanship.',
'link_text' => 'More about Otterpohl',
'brand_slug' => 'tischlerei',
],
'sponsoring' => [
'title' => 'Rooted <span class="text-secondary">in the region</span>',
'subtitle' => 'We support clubs and organisations from our region – because being rooted is more than an address.',

View file

@ -17,6 +17,20 @@ return [
'menu_open' => 'Open menu',
// World switcher (Interior / Real estate)
// Farb-Schalter für die Abstimmung der Palette (Nachtrag 18)
'palette' => [
'label' => 'Colour scheme',
'classic' => 'Current',
'modern' => 'New',
'draft' => 'Draft',
],
// Slideshow in the draft hero (Nachtrag 19)
'hero_slider' => [
'label' => 'Glimpses of our brands',
'dots' => 'Choose image',
'dot' => 'Image :n of :total: :label',
'empty' => 'No images yet.',
],
'worlds_label' => 'Switch area',
'world_interior' => 'Interior',
'world_immobilien' => 'Real estate',
@ -39,7 +53,8 @@ return [
'success' => 'Thank you! We will get back to you shortly to arrange the appointment.',
'new_request' => 'Another request',
'date_in_past' => 'Please choose a date from today onwards.',
'whatsapp_intro' => 'Prefer to write directly? We prepare the message with your details.',
'whatsapp_intro' => 'Fastest via WhatsApp – we prepare the message with your details.',
'or_form' => 'or use the form',
'whatsapp_text' => 'Hello B2in Interior, I would like to arrange an appointment.',
'whatsapp_note' => 'Opens WhatsApp with a prepared text. You send the message yourself.',
],
@ -58,12 +73,21 @@ return [
'more_about' => 'More about :name',
// Brand gallery – realised projects vs. drafts
'brand_gallery_title' => 'References and inspiration',
'brand_gallery_intro' => 'Completed work from our projects – and visualisations showing what is possible in a room.',
'brand_gallery_title' => 'Inspiration and drafts',
'brand_gallery_intro' => 'Visualisations that show what is possible in a room – before the first piece of furniture is in place.',
'brand_gallery_title_inspirations' => 'Inspiration and drafts',
'brand_gallery_intro_inspirations' => 'Visualisations that show what is possible in a room – before the first piece of furniture is in place.',
'brand_gallery_title_projects' => 'References',
'brand_gallery_intro_projects' => 'Completed projects – this is what it looks like once planning and craft are done.',
'brand_gallery_title_projects_company' => 'References and insights',
'brand_gallery_intro_projects_company' => 'Completed projects and a look into the workshop where they are made.',
'brand_gallery_title_mixed' => 'Projects and drafts',
'brand_gallery_intro_mixed' => 'Completed projects and visualisations that show what is possible in a room.',
'brand_gallery_tab_projects' => 'Completed projects',
'brand_gallery_tab_inspirations' => 'Inspiration and drafts',
'brand_gallery_badge_project' => 'Completed project',
'brand_gallery_badge_inspiration' => 'Visualisation',
'brand_gallery_badge_company' => 'From the workshop',
'brand_gallery_inspiration_note' => 'Drafts and visualisations. They show design possibilities before the first piece of furniture is in place.',
'previous' => 'Previous image',
'next' => 'Next image',

View file

@ -0,0 +1,55 @@
{{-- Farb-Schalter für die Abstimmung (Nachtrag 18 + 19): bisherige Creme-Palette, die hellere
Variante „Neu“ und der Entwurf vom 24.09. Schwimmt am linken Rand in der Mitte – unten links
sitzt der Cookie-Button, unten rechts die Terminanfrage.
„Bisher“ und „Neu“ schalten im Browser um (localStorage b2in-palette, im <head> gesetzt,
?palette=modern|classic per Link). „Entwurf“ ist eine eigene Seite (/entwurf) mit fester
Palette; von dort führen die beiden anderen Einträge zurück auf die Startseite. --}}
@props(['draft' => false])
<div class="fixed left-3 top-1/2 z-40 -translate-y-1/2 print:hidden" data-palette-switch
@unless ($draft)
x-data="{
palette: document.documentElement.classList.contains('palette-modern') ? 'modern' : 'classic',
choose(value) {
this.palette = value;
document.documentElement.classList.toggle('palette-modern', value === 'modern');
try { localStorage.setItem('b2in-palette', value); } catch (e) {}
},
}"
@endunless>
<div class="flex flex-col items-stretch gap-1 rounded-full border border-border bg-card/95 p-1 text-[11px] font-medium shadow-lg backdrop-blur-sm"
role="group" aria-label="{{ __('ui.palette.label') }}">
<span class="px-2 pt-1 text-center text-[10px] uppercase tracking-wide text-muted-foreground" aria-hidden="true">
{{ __('ui.palette.label') }}
</span>
@if ($draft)
<a href="{{ url('/?palette=classic') }}"
class="rounded-full px-3 py-1.5 text-center text-muted-foreground transition-colors duration-200 hover:text-foreground">
{{ __('ui.palette.classic') }}
</a>
<a href="{{ url('/?palette=modern') }}"
class="rounded-full px-3 py-1.5 text-center text-muted-foreground transition-colors duration-200 hover:text-foreground">
{{ __('ui.palette.modern') }}
</a>
<span aria-current="page" class="rounded-full bg-primary px-3 py-1.5 text-center text-primary-foreground">
{{ __('ui.palette.draft') }}
</span>
@else
<button type="button" x-on:click="choose('classic')" x-bind:aria-pressed="(palette === 'classic').toString()"
x-bind:class="palette === 'classic' ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:text-foreground'"
class="cursor-pointer rounded-full px-3 py-1.5 transition-colors duration-200">
{{ __('ui.palette.classic') }}
</button>
<button type="button" x-on:click="choose('modern')" x-bind:aria-pressed="(palette === 'modern').toString()"
x-bind:class="palette === 'modern' ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:text-foreground'"
class="cursor-pointer rounded-full px-3 py-1.5 transition-colors duration-200">
{{ __('ui.palette.modern') }}
</button>
<a href="{{ route('home.draft') }}"
class="rounded-full px-3 py-1.5 text-center text-muted-foreground transition-colors duration-200 hover:text-foreground">
{{ __('ui.palette.draft') }}
</a>
@endif
</div>
</div>

View file

@ -0,0 +1,18 @@
{{-- Linke Flanke des Entwurfs-Heros (Nachtrag 19): schmaler Haken unten links, oben gerundet,
mit Goldlinie. Höhe rund 46 % des Rahmens, Breite aus dem viewBox-Verhältnis. --}}
<svg class="hero-flank hero-flank-left hidden lg:block" viewBox="0 0 80 480" preserveAspectRatio="xMinYMax meet"
aria-hidden="true" focusable="false" data-hero-flank="left">
<defs>
<linearGradient id="hero-flank-left-green" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" style="stop-color: hsl(var(--primary-light))" />
<stop offset="1" style="stop-color: hsl(var(--primary-dark))" />
</linearGradient>
<linearGradient id="hero-flank-left-gold" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" style="stop-color: hsl(var(--secondary-light))" />
<stop offset="1" style="stop-color: hsl(var(--secondary)); stop-opacity: 0.5" />
</linearGradient>
</defs>
<path d="M0 480V56C0 25 25 0 56 0H80V480Z" fill="url(#hero-flank-left-green)" />
<path d="M14 480V60C14 35 35 14 60 14H80" fill="none" stroke="url(#hero-flank-left-gold)" stroke-width="1.5"
vector-effect="non-scaling-stroke" />
</svg>

View file

@ -0,0 +1,24 @@
{{-- Rechte Flanke des Entwurfs-Heros (Nachtrag 19): tannengrüner Block mit gerundeter Innenkante,
Goldlinie mit Verlauf entlang der Rundung und einer kurzen Zierlinie wie in der Visualisierung.
Skaliert über die Rahmenhöhe (height: 100 %, Breite aus dem viewBox-Verhältnis). Farben
kommen aus den Tokens, damit die Flanke jede Palette mitmacht. Kann später 1:1 gegen eine
gelieferte SVG-Datei getauscht werden. --}}
<svg class="hero-flank hero-flank-right hidden lg:block" viewBox="0 0 140 1000" preserveAspectRatio="xMaxYMid meet"
aria-hidden="true" focusable="false" data-hero-flank="right">
<defs>
<linearGradient id="hero-flank-right-green" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" style="stop-color: hsl(var(--primary-light))" />
<stop offset="1" style="stop-color: hsl(var(--primary-dark))" />
</linearGradient>
<linearGradient id="hero-flank-right-gold" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" style="stop-color: hsl(var(--secondary-light))" />
<stop offset="0.5" style="stop-color: hsl(var(--secondary)); stop-opacity: 0.45" />
<stop offset="1" style="stop-color: hsl(var(--secondary-light))" />
</linearGradient>
</defs>
<path d="M140 0H72C32 0 0 32 0 72V928C0 968 32 1000 72 1000H140Z" fill="url(#hero-flank-right-green)" />
<path d="M140 14H74C41 14 14 41 14 74V926C14 959 41 986 74 986H140" fill="none"
stroke="url(#hero-flank-right-gold)" stroke-width="1.5" vector-effect="non-scaling-stroke" />
<path d="M124 330V600" fill="none" stroke="url(#hero-flank-right-gold)" stroke-width="1.5"
vector-effect="non-scaling-stroke" />
</svg>

View file

@ -565,13 +565,16 @@ $cancelForm = function () {
wire:click="addGalleryImage('{{ CmsBrand::GALLERY_PROJECT }}')">Projekt</flux:button>
<flux:button size="xs" variant="ghost" icon="plus"
wire:click="addGalleryImage('{{ CmsBrand::GALLERY_INSPIRATION }}')">Inspiration</flux:button>
<flux:button size="xs" variant="ghost" icon="plus"
wire:click="addGalleryImage('{{ CmsBrand::GALLERY_COMPANY }}')">Werkstatt</flux:button>
</div>
</div>
<div class="mb-3 rounded-lg border border-amber-200 bg-amber-50 p-3 text-sm text-amber-800 dark:border-amber-800 dark:bg-amber-900/20 dark:text-amber-200">
<strong>Projekt</strong> = tatsächlich ausgeführter Auftrag.
<strong>Projekt</strong> = tatsächlich ausgeführter Auftrag (erscheint auch unter „Referenzen“, solange dort nichts gepflegt ist).
<strong>Werkstatt</strong> = Einblick in Fertigung oder Montage des Herstellers.
<strong>Inspiration</strong> = Entwurf, Rendering oder KI-Darstellung.
Auf der Seite werden beide getrennt gezeigt und beschriftet – bitte nichts vermischen.
Auf der Seite trägt jedes Bild seine Beschriftung – bitte nichts vermischen.
</div>
@if (empty($gallery))
@ -589,6 +592,7 @@ $cancelForm = function () {
<flux:select wire:model="gallery.{{ $i }}.category" size="sm" class="mb-2">
<flux:select.option value="{{ CmsBrand::GALLERY_PROJECT }}">Realisiertes Projekt</flux:select.option>
<flux:select.option value="{{ CmsBrand::GALLERY_INSPIRATION }}">Inspiration / Entwurf</flux:select.option>
<flux:select.option value="{{ CmsBrand::GALLERY_COMPANY }}">Aus der Werkstatt</flux:select.option>
</flux:select>
<flux:input wire:model="gallery.{{ $i }}.caption" size="sm" class="mb-2"

View file

@ -71,13 +71,29 @@
<section class="pb-16">
<div class="container-narrow space-y-14">
@foreach ($brand['sections'] as $index => $section)
<div wire:key="section-{{ $index }}">
<div wire:key="section-{{ $index }}" @if (!empty($section['anchor'])) id="{{ $section['anchor'] }}" @endif>
@if (!empty($section['title']))
<h2 class="text-2xl lg:text-3xl font-medium">{{ $section['title'] }}</h2>
@endif
@foreach ($section['paragraphs'] as $i => $paragraph)
<p class="text-muted-foreground leading-relaxed mt-4">{{ $paragraph }}</p>
@foreach (\App\Models\CmsBrand::paragraphBlocks($section['paragraphs']) as $block)
@if ($block['type'] === 'list')
<ul class="mt-6 space-y-4">
@foreach ($block['items'] as $item)
<li class="flex gap-3 text-muted-foreground leading-relaxed">
<span class="mt-2.5 h-1.5 w-1.5 shrink-0 rounded-full bg-secondary" aria-hidden="true"></span>
<span>
@if ($item['lead'] !== '')
<strong class="font-medium text-foreground">{{ $item['lead'] }}:</strong>
@endif
{{ $item['text'] }}
</span>
</li>
@endforeach
</ul>
@else
<p class="text-muted-foreground leading-relaxed mt-4">{{ $block['text'] }}</p>
@endif
@endforeach
@if (!empty($section['quote']))
@ -129,6 +145,14 @@
@if (!empty($section['claim']))
<p class="text-large text-secondary font-medium mt-6">{{ $section['claim'] }}</p>
@endif
@if (!empty($section['link']) && !empty($section['link_text']))
<a href="{{ $section['link'] }}"
class="text-secondary font-medium mt-4 inline-flex items-center gap-1 hover:underline">
{{ $section['link_text'] }}
<span aria-hidden="true">&rarr;</span>
</a>
@endif
</div>
@endforeach
</div>
@ -153,98 +177,65 @@
</section>
@endif
{{-- 4. Galerie: realisierte Projekte und Inspirationen strikt getrennt --}}
{{-- 4. Galerie. Realisierte Projekte, Einblicke in die Werkstatt und Visualisierungen stehen in
einem Raster, unterschieden nur durch das Badge – der Rubrikwechsel ist ausgesetzt
(Abstimmung 22.09., Nachtrag 17). Reihenfolge: Projekte, Werkstatt, Visualisierungen.
Überschrift und Zeile darunter richten sich nach dem, was die Galerie enthält (Nachtrag 19):
nur Visualisierungen → „Inspirationen und Entwürfe“, nur Projekte → „Referenzen“, Projekte
und Werkstatt → „Referenzen und Einblicke“, mit Visualisierungen → „Projekte und Entwürfe“. --}}
@php
$galleryProjects = $gallery['projects'] ?? [];
$galleryInspirations = $gallery['inspirations'] ?? [];
$hasBothGalleryGroups = $galleryProjects !== [] && $galleryInspirations !== [];
$initialGalleryTab = $galleryProjects !== [] ? 'projects' : 'inspirations';
$galleryItems = array_merge($gallery['projects'] ?? [], $gallery['company'] ?? [], $gallery['inspirations'] ?? []);
$galleryLightbox = collect(['projects' => $galleryProjects, 'inspirations' => $galleryInspirations])
->map(fn (array $items) => collect($items)->map(fn (array $item) => [
'src' => theme_image_url($item['image']),
'caption' => $item['caption'] ?? '',
'badge' => $item['category'] === \App\Models\CmsBrand::GALLERY_PROJECT
? __('ui.brand_gallery_badge_project')
: __('ui.brand_gallery_badge_inspiration'),
])->values()->all())
->all();
$galleryHasReal = ! empty($gallery['projects']) || ! empty($gallery['company']);
$galleryHasDrafts = ! empty($gallery['inspirations']);
$galleryVariant = match (true) {
! $galleryHasReal => 'inspirations',
$galleryHasDrafts => 'mixed',
! empty($gallery['company']) => 'projects_company',
default => 'projects',
};
$galleryLightbox = collect($galleryItems)->map(fn (array $item) => [
'src' => theme_image_url($item['image']),
'caption' => $item['caption'] ?? '',
'badge' => \App\Models\CmsBrand::galleryBadge($item['category']),
])->values()->all();
@endphp
@if ($galleryProjects || $galleryInspirations)
@if ($galleryItems)
<section id="galerie" class="section-padding"
x-data="{
tab: '{{ $initialGalleryTab }}',
groups: @js($galleryLightbox),
items: @js($galleryLightbox),
lightbox: false,
current: 0,
get items() { return this.groups[this.tab] ?? [] },
open(index) { this.current = index; this.lightbox = true },
next() { this.current = (this.current + 1) % this.items.length },
prev() { this.current = (this.current - 1 + this.items.length) % this.items.length },
}">
<div class="container-padding">
<div class="text-center max-w-2xl mx-auto">
<h2 class="text-section-title">{{ __('ui.brand_gallery_title') }}</h2>
<p class="text-muted-foreground leading-relaxed mt-4">{{ __('ui.brand_gallery_intro') }}</p>
<h2 class="text-section-title">{{ __('ui.brand_gallery_title_'.$galleryVariant) }}</h2>
<p class="text-muted-foreground leading-relaxed mt-4">{{ __('ui.brand_gallery_intro_'.$galleryVariant) }}</p>
</div>
@if ($hasBothGalleryGroups)
<div class="flex justify-center mt-10" role="tablist" aria-label="{{ __('ui.brand_gallery_title') }}">
<div class="inline-flex rounded-full bg-accent p-1 gap-1">
@foreach (['projects' => __('ui.brand_gallery_tab_projects'), 'inspirations' => __('ui.brand_gallery_tab_inspirations')] as $tabKey => $tabLabel)
<button type="button" role="tab" wire:key="gallery-tab-{{ $tabKey }}"
x-on:click="tab = '{{ $tabKey }}'"
x-bind:aria-selected="tab === '{{ $tabKey }}' ? 'true' : 'false'"
x-bind:class="tab === '{{ $tabKey }}'
? 'bg-background text-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground'"
class="px-6 py-2.5 rounded-full text-sm font-medium transition-colors cursor-pointer">
{{ $tabLabel }}
</button>
@endforeach
</div>
</div>
@endif
@foreach (['projects' => $galleryProjects, 'inspirations' => $galleryInspirations] as $groupKey => $groupItems)
@if ($groupItems)
<div wire:key="gallery-group-{{ $groupKey }}"
@if ($hasBothGalleryGroups) x-show="tab === '{{ $groupKey }}'" x-cloak @endif
class="mt-10">
@unless ($hasBothGalleryGroups)
<h3 class="text-xl font-medium text-center mb-8">
{{ $groupKey === 'projects' ? __('ui.brand_gallery_tab_projects') : __('ui.brand_gallery_tab_inspirations') }}
</h3>
@endunless
@if ($groupKey === 'inspirations')
<p class="text-sm text-muted-foreground text-center max-w-2xl mx-auto mb-8">
{{ __('ui.brand_gallery_inspiration_note') }}
</p>
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 mt-12">
@foreach ($galleryItems as $index => $item)
<figure wire:key="gallery-{{ $index }}" class="group">
<button type="button" x-on:click="open({{ $index }})"
class="relative block w-full overflow-hidden rounded-2xl cursor-pointer">
<img src="{{ theme_image_url($item['image']) }}"
alt="{{ $item['caption'] ?: $brand['name'] }}" loading="lazy"
class="w-full aspect-[4/3] object-cover transition-transform duration-300 group-hover:scale-105" />
<span class="absolute top-3 left-3 rounded-full bg-black/60 px-3 py-1 text-xs font-medium text-white backdrop-blur-sm">
{{ \App\Models\CmsBrand::galleryBadge($item['category']) }}
</span>
</button>
@if (!empty($item['caption']))
<figcaption class="text-sm text-muted-foreground mt-3">{{ $item['caption'] }}</figcaption>
@endif
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
@foreach ($groupItems as $index => $item)
<figure wire:key="gallery-{{ $groupKey }}-{{ $index }}" class="group">
<button type="button" x-on:click="open({{ $index }})"
class="relative block w-full overflow-hidden rounded-2xl cursor-pointer">
<img src="{{ theme_image_url($item['image']) }}"
alt="{{ $item['caption'] ?: $brand['name'] }}" loading="lazy"
class="w-full aspect-[4/3] object-cover transition-transform duration-300 group-hover:scale-105" />
<span class="absolute top-3 left-3 rounded-full bg-black/60 px-3 py-1 text-xs font-medium text-white backdrop-blur-sm">
{{ $groupKey === 'projects' ? __('ui.brand_gallery_badge_project') : __('ui.brand_gallery_badge_inspiration') }}
</span>
</button>
@if (!empty($item['caption']))
<figcaption class="text-sm text-muted-foreground mt-3">{{ $item['caption'] }}</figcaption>
@endif
</figure>
@endforeach
</div>
</div>
@endif
@endforeach
</figure>
@endforeach
</div>
</div>
{{-- Lightbox: ins body geteleportet, damit keine Eltern-Constraints stören --}}

View file

@ -1,139 +1,102 @@
{{-- Hero des Entwurfs (Nachtrag 19): Rahmen mit feiner Goldkante und grünen Flanken, links
Text mit einem Button, rechts die Slideshow. Das Bild hat ein festes Seitenverhältnis –
das Motiv wird auf großen Bildschirmen nicht mehr unten beschnitten (Briefing Punkt 2).
Der Wechsel läuft langsam (Überblendung 1,4 s, Standard alle 7 s), pausiert bei Hover und
bleibt bei „reduzierter Bewegung“ stehen. --}}
<section id="hero" class="hero-draft section-padding relative">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"
x-data="{
active: 0,
count: {{ count($slides) }},
interval: {{ $this->intervalMs }},
timer: null,
paused: false,
reduced: window.matchMedia('(prefers-reduced-motion: reduce)').matches,
start() {
if (this.reduced || this.count < 2) { return; }
this.stop();
this.timer = setInterval(() => { if (!this.paused && !document.hidden) { this.next(); } }, this.interval);
},
stop() { if (this.timer) { clearInterval(this.timer); this.timer = null; } },
go(i) { this.active = (i + this.count) % this.count; this.start(); },
next() { this.go(this.active + 1); },
prev() { this.go(this.active - 1); },
}"
x-init="start()" x-on:keydown.arrow-right.prevent="next()" x-on:keydown.arrow-left.prevent="prev()">
<div x-data="heroSlider()" x-init="init()">
<section class="relative mb-10">
<!-- Slider Container -->
<div class="absolute inset-0 overflow-hidden">
@if(!empty($content['slides']))
@foreach($content['slides'] as $index => $slide)
<div
class="absolute inset-0 transition-opacity duration-1000 ease-in-out"
:class="currentSlide === {{ $index }} ? 'opacity-100' : 'opacity-0'"
>
<img
src="{{ theme_image_url($slide['image']) }}"
alt="{{ $slide['image_alt'] }}"
class="size-full object-cover"
/>
<div class="absolute inset-0 bg-gray-900 opacity-20"></div>
</div>
@endforeach
@endif
</div>
<div class="hero-frame relative overflow-hidden rounded-[28px]">
<x-web.hero-flank-right />
<x-web.hero-flank-left />
<!-- Content -->
<div class="relative mx-auto flex max-w-3xl flex-col items-center px-6 py-32 text-center sm:py-64 lg:px-0">
<h1 class="text-hero text-white">
{!! $content['title'] !!}
</h1>
<p class="mt-4 text-xl text-white">{!! $content['subtitle'] !!}</p>
<a href="{{ $content['cta1_link'] }}" class="mt-8 inline-block btn-secondary-accent">{{ $content['cta1_text'] }}</a>
</div>
{{-- Rechts wenig Innenabstand: das Bild liegt über der grünen Flanke, wie in der Visualisierung. --}}
<div class="relative grid items-center gap-10 p-6 sm:p-10 lg:grid-cols-12 lg:gap-10 lg:py-5 lg:pl-16 lg:pr-12">
{{-- Text --}}
<div class="space-y-6 lg:col-span-5 lg:py-10 lg:pr-4">
@if (!empty($content['eyebrow']))
<p class="hero-eyebrow">{{ $content['eyebrow'] }}</p>
@endif
<!-- Navigation Dots -->
@if(!empty($content['slides']) && count($content['slides']) > 1)
<div class="absolute bottom-8 left-1/2 transform -translate-x-1/2">
<div class="flex space-x-3">
@foreach($content['slides'] as $index => $slide)
<button
@click="setSlide({{ $index }})"
class="w-3 h-3 rounded-full border-2 border-white/50 transition-all duration-300"
:class="currentSlide === {{ $index }} ? 'bg-white border-white' : 'bg-transparent hover:border-white/80'"
aria-label="{{ __('ui.show_slide', ['number' => $index + 1]) }}"
></button>
@endforeach
<h1 class="hero-title">{!! $content['title'] ?? '' !!}</h1>
@if (!empty($content['subtitle']))
<p class="max-w-md text-base leading-relaxed text-muted-foreground">{!! $content['subtitle'] !!}</p>
@endif
<div>
<a href="{{ $content['cta_link'] ?? '/marken' }}" class="btn-hero">
{{ $content['cta_text'] ?? __('ui.all_brands_cta') }}
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"
aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" />
</svg>
</a>
</div>
</div>
{{-- Slideshow --}}
<div class="hero-image lg:col-span-7">
<div class="hero-image-frame">
<div class="relative aspect-[4/3] overflow-hidden rounded-[calc(1.25rem-1.5px)] bg-muted lg:aspect-[5/4]"
role="group" aria-roledescription="carousel" aria-label="{{ __('ui.hero_slider.label') }}"
x-on:mouseenter="paused = true" x-on:mouseleave="paused = false"
x-on:focusin="paused = true" x-on:focusout="paused = false" tabindex="0">
@forelse ($slides as $index => $slide)
<figure wire:key="hero-slide-{{ $index }}" data-hero-slide="{{ $index }}"
class="hero-slide absolute inset-0 m-0 {{ $index === 0 ? 'opacity-100' : 'opacity-0' }}"
x-bind:class="active === {{ $index }} ? 'opacity-100' : 'opacity-0 pointer-events-none'"
x-bind:aria-hidden="(active !== {{ $index }}).toString()">
<img src="{{ theme_image_url($slide['image']) }}" alt="{{ $slide['alt'] }}"
class="h-full w-full object-cover" @if ($index > 0) loading="lazy" @endif />
@if ($slide['label'] !== '')
<figcaption
class="absolute bottom-5 left-5 rounded-full bg-card/85 px-3 py-1 text-[10px] font-medium uppercase tracking-[0.18em] text-foreground/80 backdrop-blur-sm">
{{ $slide['label'] }}
</figcaption>
@endif
</figure>
@empty
<div class="flex h-full items-center justify-center text-sm text-muted-foreground">
{{ __('ui.hero_slider.empty') }}
</div>
@endforelse
</div>
</div>
</div>
</div>
</div>
</div>
@endif
<!-- Arrow Navigation (Optional) -->
@if(!empty($content['slides']) && count($content['slides']) > 1)
<button
@click="previousSlide()"
class="absolute left-6 top-1/2 transform -translate-y-1/2 text-white/70 hover:text-white transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-white/50 rounded-full p-2"
aria-label="{{ __('ui.previous_image') }}"
>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
</svg>
</button>
<button
@click="nextSlide()"
class="absolute right-6 top-1/2 transform -translate-y-1/2 text-white/70 hover:text-white transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-white/50 rounded-full p-2"
aria-label="{{ __('ui.next_image') }}"
>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</button>
@endif
</section>
</div>
<script>
function heroSlider() {
return {
currentSlide: 0,
totalSlides: {{ !empty($content['slides']) ? count($content['slides']) : 0 }},
autoplayInterval: null,
autoplayTimeout: null,
init() {
if (this.totalSlides > 1) {
this.startAutoplay();
}
},
// Automatischer Wechsel (nur für Intervall)
autoNextSlide() {
this.currentSlide = (this.currentSlide + 1) % this.totalSlides;
},
// Manuelle Navigation
nextSlide() {
this.currentSlide = (this.currentSlide + 1) % this.totalSlides;
this.resetAutoplay();
},
previousSlide() {
this.currentSlide = (this.currentSlide - 1 + this.totalSlides) % this.totalSlides;
this.resetAutoplay();
},
setSlide(index) {
this.currentSlide = index;
this.resetAutoplay();
},
startAutoplay() {
// Zuerst alle bestehenden Timer löschen
this.stopAutoplay();
this.autoplayInterval = setInterval(() => {
this.autoNextSlide();
}, 5000); // Wechsel alle 5 Sekunden
},
stopAutoplay() {
if (this.autoplayInterval) {
clearInterval(this.autoplayInterval);
this.autoplayInterval = null;
}
if (this.autoplayTimeout) {
clearTimeout(this.autoplayTimeout);
this.autoplayTimeout = null;
}
},
resetAutoplay() {
// Alle Timer stoppen
this.stopAutoplay();
// Nach 5 Sekunden Pause wieder starten
this.autoplayTimeout = setTimeout(() => {
this.startAutoplay();
}, 1000);
}
}
}
</script>
{{-- Punkte unter dem Rahmen --}}
@if (count($slides) > 1)
<div class="mt-6 flex items-center justify-center gap-3" role="tablist" aria-label="{{ __('ui.hero_slider.dots') }}">
@foreach ($slides as $index => $slide)
<button type="button" role="tab" wire:key="hero-dot-{{ $index }}" x-on:click="go({{ $index }})"
x-bind:aria-selected="(active === {{ $index }}).toString()"
x-bind:class="active === {{ $index }} ? 'bg-primary w-14' : 'bg-border w-10 hover:bg-muted-foreground/40'"
class="h-1 cursor-pointer rounded-full transition-all duration-500 {{ $index === 0 ? 'bg-primary w-14' : 'bg-border w-10' }}"
aria-label="{{ __('ui.hero_slider.dot', ['n' => $index + 1, 'total' => count($slides), 'label' => $slide['label']]) }}">
</button>
@endforeach
</div>
@endif
</div>
</section>

Some files were not shown because too many files have changed in this diff Show more