diff --git a/CLAUDE.md b/CLAUDE.md index ed7a30e0..3fb45724 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -128,7 +128,7 @@ Domains are configured in `config/domains.php` with environment variables: Domain-specific configurations include: - **portal.b2in.test**: Admin backend with Flux UI theme -- **b2in.test**: Anthracite/Dynamic Blue color scheme, Inter/IBM Plex Sans fonts +- **b2in.test**: two color worlds on one theme – Interior (default: Tanne `#202c2b`, Creme `#f7f4ef`, Sand `#d9cbbf`, Ocker `#d39e46`, Clay `#a38f82`, Anthrazit `#2d2926`; Inter/EB Garamond) and Immobilien (`body.world-immobilien`: Anthracite/Dynamic Blue, Inter/IBM Plex Sans). The world comes from `b2in_world()` (app/helpers.php), the logo per world from `ThemeHelper::getLogoPath()` - **b2a.test**: Azur Blue/Liberty Red color scheme, Inter/Merriweather fonts - **stileigentum.test**: Style Blue/Style Sun color scheme, Inter/Ephesis fonts - **style2own.test**: Imperial Blue/Sand Gold color scheme, Inter/EB Garamond fonts diff --git a/app/Helpers/ThemeHelper.php b/app/Helpers/ThemeHelper.php index 92cb95bf..ca403e51 100644 --- a/app/Helpers/ThemeHelper.php +++ b/app/Helpers/ThemeHelper.php @@ -5,12 +5,20 @@ namespace App\Helpers; class ThemeHelper { /** - * Get the logo path based on the current theme + * Logo-Pfad für das aktuelle Theme. + * + * Für b2in hängt die Datei zusätzlich an der Farbwelt: die Interior-Welt trägt das Logo + * mit dem Zusatz „Interior“ und ockerfarbenem Bogen, die Immobilien-Welt das Logo ohne + * Zusatz mit blauem Bogen. Ohne Angabe wird die Welt aus der aktuellen Route gelesen. */ - public static function getLogoPath(string $type = 'positive'): string + public static function getLogoPath(string $type = 'positive', ?string $world = null): string { $theme = config('app.theme', 'b2in'); + if ($theme === 'b2in' && ($world ?? b2in_world()) === 'immobilien') { + return self::getLogoPathForBrand('b2in-immobilien', $type); + } + return self::getLogoPathForBrand($theme, $type); } @@ -26,6 +34,10 @@ class ThemeHelper 'positive' => 'img/logos/b2in-interior-logo-positive.svg', 'negative' => 'img/logos/b2in-interior-logo-negative.svg', ], + 'b2in-immobilien' => [ + 'positive' => 'img/logos/b2in-logo-positive.svg', + 'negative' => 'img/logos/b2in-logo-negative.svg', + ], 'b2a' => [ 'positive' => 'img/logos/b2a-logo-positiv.svg', 'negative' => 'img/logos/b2a-logo-negativ.svg', diff --git a/app/Livewire/Web/Components/Sections/AboutHero.php b/app/Livewire/Web/Components/Sections/AboutHero.php index c0aaa1a7..1cb3233a 100644 --- a/app/Livewire/Web/Components/Sections/AboutHero.php +++ b/app/Livewire/Web/Components/Sections/AboutHero.php @@ -8,9 +8,13 @@ class AboutHero extends Component { public $content = []; - public function mount() + /** Sektionsschluessel – die Immobilien-Welt nutzt eigene Inhalte (immobilien_about_hero). */ + public string $section = 'about_hero'; + + public function mount(string $section = 'about_hero') { - $this->content = cms_theme_section('about_hero'); + $this->section = $section; + $this->content = cms_theme_section($this->section); } public function render() diff --git a/app/Livewire/Web/Components/Sections/CTASection.php b/app/Livewire/Web/Components/Sections/CTASection.php index 6fc5c452..b668225b 100644 --- a/app/Livewire/Web/Components/Sections/CTASection.php +++ b/app/Livewire/Web/Components/Sections/CTASection.php @@ -12,8 +12,13 @@ class CTASection extends Component public $section = ''; - public function mount($bg = 'bg-secondary', $section = 'cta_section') + public function mount($bg = 'cta-band', $section = 'cta_section') { + // Der Standard-Abschluss hat je Welt eigene Texte und fuehrt zur Kontaktseite der Welt. + if ($section === 'cta_section' && b2in_world() === 'immobilien') { + $section = 'cta_section_immobilien'; + } + $this->section = $section; $this->content = cms_theme_section($this->section); $this->bg = $bg; diff --git a/app/Livewire/Web/Components/Sections/MagazinDetail.php b/app/Livewire/Web/Components/Sections/MagazinDetail.php index bd2a5f52..1a1fe7a3 100644 --- a/app/Livewire/Web/Components/Sections/MagazinDetail.php +++ b/app/Livewire/Web/Components/Sections/MagazinDetail.php @@ -15,14 +15,25 @@ class MagazinDetail extends Component public $content; + /** Rueckweg zur Magazinliste der Welt, zu der der Beitrag gehoert. */ + public string $listUrl = ''; + public function mount($slug) { $this->slug = (string) $slug; $this->loadArticle(); + $this->listUrl = $this->articleWorld() === CmsArticle::WORLD_IMMOBILIEN + ? route('immobilien.magazin') + : route('magazin'); $this->loadRelatedArticles(); $this->loadThemeContent(); } + private function articleWorld(): string + { + return (string) ($this->article['world'] ?? CmsArticle::WORLD_INTERIOR); + } + private function loadArticle() { $articles = $this->getArticlesData(); @@ -37,6 +48,7 @@ class MagazinDetail extends Component private function loadRelatedArticles() { $this->relatedArticles = collect($this->getArticlesData()) + ->filter(fn (array $article) => ($article['world'] ?? CmsArticle::WORLD_INTERIOR) === $this->articleWorld()) ->reject(fn (array $article, string $slug) => $slug === $this->slug) ->take(2) ->values() diff --git a/app/Livewire/Web/Components/Sections/MagazinList.php b/app/Livewire/Web/Components/Sections/MagazinList.php index 6b35c4f7..039a63e2 100644 --- a/app/Livewire/Web/Components/Sections/MagazinList.php +++ b/app/Livewire/Web/Components/Sections/MagazinList.php @@ -11,15 +11,19 @@ class MagazinList extends Component public $content = []; - public function mount() + /** Farbwelt der Liste: `interior` (/magazin) oder `immobilien` (/immobilien/magazin). */ + public string $world = CmsArticle::WORLD_INTERIOR; + + public function mount(string $world = CmsArticle::WORLD_INTERIOR) { + $this->world = in_array($world, CmsArticle::WORLDS, true) ? $world : CmsArticle::WORLD_INTERIOR; $this->loadPosts(); $this->loadThemeContent(); } private function loadPosts() { - $this->posts = collect(CmsArticle::forFrontend())->map(function (array $article) { + $this->posts = collect(CmsArticle::forFrontend($this->world))->map(function (array $article) { return [ 'slug' => $article['slug'], 'title' => $article['title'], @@ -33,7 +37,9 @@ class MagazinList extends Component private function loadThemeContent() { - $this->content = cms_theme_section('magazin_list'); + $this->content = cms_theme_section( + $this->world === CmsArticle::WORLD_IMMOBILIEN ? 'magazin_list_immobilien' : 'magazin_list' + ); } public function render() diff --git a/app/Livewire/Web/Components/Sections/OriginSection.php b/app/Livewire/Web/Components/Sections/OriginSection.php new file mode 100644 index 00000000..835e09ed --- /dev/null +++ b/app/Livewire/Web/Components/Sections/OriginSection.php @@ -0,0 +1,83 @@ + */ + public array $content = []; + + public string $bg = ''; + + public function mount(string $bg = ''): void + { + $section = cms_theme_section('origin_section'); + $this->content = is_array($section) ? $section : []; + $this->bg = $bg; + } + + /** + * Standorte mit ausgefüllten Pflichtfeldern, damit ein leerer CMS-Eintrag die Karte nicht zerreißt. + * + * Der Link zur Markenseite kommt nicht aus dem CMS, sondern aus den veröffentlichten Marken: + * eine unveröffentlichte Marke bekommt keinen Link, ihr Standort bleibt aber sichtbar. + * + * @return array> + */ + public function getLocationsProperty(): array + { + $locations = $this->content['locations'] ?? []; + + if (! is_array($locations)) { + return []; + } + + $brands = CmsBrand::forFrontend(); + + $out = []; + + foreach ($locations as $location) { + if (! is_array($location) || empty($location['brand']) || empty($location['city'])) { + continue; + } + + $location['link'] = $this->brandLink($location, $brands); + $out[] = $location; + } + + return $out; + } + + /** + * @param array $location + * @param array> $brands + */ + private function brandLink(array $location, array $brands): ?string + { + $slug = $location['brand_slug'] ?? null; + + if (is_string($slug) && isset($brands[$slug])) { + return '/marken/'.$slug; + } + + foreach ($brands as $brandSlug => $brand) { + if (($brand['name'] ?? null) === $location['brand']) { + return '/marken/'.$brandSlug; + } + } + + return null; + } + + public function render() + { + return view('livewire.web.components.sections.origin-section'); + } +} diff --git a/app/Livewire/Web/Components/Sections/OurStory.php b/app/Livewire/Web/Components/Sections/OurStory.php index 77926785..5086f981 100644 --- a/app/Livewire/Web/Components/Sections/OurStory.php +++ b/app/Livewire/Web/Components/Sections/OurStory.php @@ -8,9 +8,13 @@ class OurStory extends Component { public $content = []; - public function mount() + /** Sektionsschluessel – die Immobilien-Welt nutzt eigene Inhalte (immobilien_our_story). */ + public string $section = 'our_story'; + + public function mount(string $section = 'our_story') { - $this->content = cms_theme_section('our_story'); + $this->section = $section; + $this->content = cms_theme_section($this->section); } public function render() diff --git a/app/Livewire/Web/Components/Sections/OurValues.php b/app/Livewire/Web/Components/Sections/OurValues.php index d6979b66..f13ddabd 100644 --- a/app/Livewire/Web/Components/Sections/OurValues.php +++ b/app/Livewire/Web/Components/Sections/OurValues.php @@ -8,9 +8,13 @@ class OurValues extends Component { public $content = []; - public function mount() + /** Sektionsschluessel – die Immobilien-Welt nutzt eigene Inhalte (immobilien_our_values). */ + public string $section = 'our_values'; + + public function mount(string $section = 'our_values') { - $this->content = cms_theme_section('our_values'); + $this->section = $section; + $this->content = cms_theme_section($this->section); } public function render() diff --git a/app/Livewire/Web/Components/UI/AnnouncementBar.php b/app/Livewire/Web/Components/UI/AnnouncementBar.php index 8d2fd94f..b511e779 100644 --- a/app/Livewire/Web/Components/UI/AnnouncementBar.php +++ b/app/Livewire/Web/Components/UI/AnnouncementBar.php @@ -8,9 +8,16 @@ class AnnouncementBar extends Component { public array $content = []; + /** Farbwelt der Seite – jede Welt hat ihre eigene Meldung. */ + public string $world = 'interior'; + public function mount(): void { - $this->content = cms_theme_section('announcement_bar'); + $this->world = b2in_world(); + + // Interior: global.announcement_bar, Immobilien: global.announcement_bar_immobilien. + $section = cms_theme_section($this->world === 'immobilien' ? 'announcement_bar_immobilien' : 'announcement_bar'); + $this->content = is_array($section) ? $section : []; } /** diff --git a/app/Livewire/Web/Components/UI/AppointmentWidget.php b/app/Livewire/Web/Components/UI/AppointmentWidget.php index 5532fda6..a6362f19 100644 --- a/app/Livewire/Web/Components/UI/AppointmentWidget.php +++ b/app/Livewire/Web/Components/UI/AppointmentWidget.php @@ -20,9 +20,13 @@ class AppointmentWidget extends Component /** Feste Anliegen neben den gepflegten Marken. */ public const EXTRA_TOPICS = [ 'projektausstattung' => 'Projektausstattung', + 'immobilien' => 'Immobilien', 'sonstiges' => 'Sonstiges', ]; + /** Farbwelt der Seite – in der Immobilien-Welt ist das Anliegen vorbelegt. */ + public string $world = 'interior'; + public string $name = ''; public string $email = ''; @@ -52,6 +56,11 @@ class AppointmentWidget extends Component $section = cms_theme_section('appointment_widget'); $this->content = is_array($section) ? $section : []; $this->formLoadedAt = time(); + $this->world = b2in_world(); + + if ($this->world === 'immobilien') { + $this->topic = 'immobilien'; + } $this->applyBrandContext(); } @@ -134,11 +143,13 @@ class AppointmentWidget extends Component $payload['is_spam'] = SpamDetector::fromConfig()->detect($payload, $this->formLoadedAt); - $subject = 'Terminanfrage B2in Interior'.($topicLabel !== '' ? ': '.$topicLabel : ''); + $subject = ($this->world === 'immobilien' ? 'Terminanfrage B2in Immobilien' : 'Terminanfrage B2in Interior') + .($topicLabel !== '' ? ': '.$topicLabel : ''); $service->handle($payload, $subject, 'appointment-widget'); $this->success = true; $this->reset(['name', 'email', 'phone', 'topic', 'preferredDate', 'message', 'privacy', 'website']); + $this->topic = $this->world === 'immobilien' ? 'immobilien' : ''; $this->formLoadedAt = time(); } diff --git a/app/Livewire/Web/Components/UI/ContactForm.php b/app/Livewire/Web/Components/UI/ContactForm.php index b1172057..46b0df84 100644 --- a/app/Livewire/Web/Components/UI/ContactForm.php +++ b/app/Livewire/Web/Components/UI/ContactForm.php @@ -4,6 +4,8 @@ namespace App\Livewire\Web\Components\Ui; use Acme\ContactForm\ContactFormService; use Acme\ContactForm\SpamDetector; +use App\Models\CmsBrand; +use Illuminate\Validation\Rule; use Livewire\Component; class ContactForm extends Component @@ -34,16 +36,109 @@ class ContactForm extends Component /** @var array */ public array $content = []; - public function mount(): void + /** Farbwelt der Seite (`interior` | `immobilien`). */ + public string $world = 'interior'; + + public function mount(string $world = 'interior'): void { - $this->content = cms_theme_section('contact_form'); + $this->world = $world === 'immobilien' ? 'immobilien' : 'interior'; + $this->content = $this->loadContent(); $this->formLoadedAt = time(); } - /** @return array */ + /** + * Inhalte der Welt. Die Immobilien-Welt liegt als Overlay über der Interior-Sektion: + * `contact_form_immobilien` trägt nur, was abweicht (Einstieg, Betreffe) – Adresse, + * Beschriftungen und Social Media erbt sie von `contact_form`. + * + * @return array + */ + private function loadContent(): array + { + $base = cms_theme_section('contact_form'); + $base = is_array($base) ? $base : []; + + if ($this->world !== 'immobilien') { + return $base; + } + + $overlay = cms_theme_section('contact_form_immobilien'); + + if (! is_array($overlay)) { + return $base; + } + + return array_replace_recursive($base, $overlay); + } + + /** + * Betreffe in Gruppen: Interior (mit den gepflegten Kernmarken vorneweg), Immobilien, B2in. + * Die Welt der Seite bestimmt die Reihenfolge – ihre Gruppe steht oben. + * + * Ohne `subject_groups` (aeltere CMS-Staende) faellt das Formular auf die flache Liste zurueck. + * + * @return array}> + */ + public function getSubjectGroupsProperty(): array + { + $groups = $this->content['form']['subject_groups'] ?? null; + + if (! is_array($groups) || $groups === []) { + $flat = $this->content['form']['subjects'] ?? []; + unset($flat['']); + + return $flat === [] ? [] : ['all' => ['label' => '', 'subjects' => $flat]]; + } + + $brands = []; + + foreach (CmsBrand::forFrontend() as $slug => $brand) { + $brands[$slug] = (string) ($brand['name'] ?? $slug); + } + + $out = []; + + foreach ($groups as $key => $group) { + $subjects = is_array($group['subjects'] ?? null) ? $group['subjects'] : []; + + if ($key === 'interior') { + $subjects = $brands + $subjects; + } + + $out[$key] = [ + 'label' => (string) ($group['label'] ?? ''), + 'subjects' => $subjects, + ]; + } + + $first = $this->world === 'immobilien' ? 'immobilien' : 'interior'; + + if (isset($out[$first])) { + $out = [$first => $out[$first]] + $out; + } + + return $out; + } + + /** + * Alle waehlbaren Betreffe als flache Liste (Schluessel → Beschriftung). + * + * @return array + */ public function getSubjectsProperty(): array { - return $this->content['form']['subjects'] ?? []; + $flat = []; + + foreach ($this->getSubjectGroupsProperty() as $group) { + $flat += $group['subjects']; + } + + return $flat; + } + + public function getSubjectPlaceholderProperty(): string + { + return (string) ($this->content['form']['subjects'][''] ?? $this->content['form']['subject_placeholder'] ?? __('ui.contact_form.subject_placeholder')); } /** @return array> */ @@ -64,7 +159,7 @@ class ContactForm extends Component 'firstName' => ['required', 'string', 'max:255'], 'lastName' => ['required', 'string', 'max:255'], 'email' => ['required', 'email:rfc', 'max:255'], - 'subject' => ['required', 'string'], + 'subject' => ['required', 'string', Rule::in(array_keys($this->subjects))], 'message' => ['required', 'string', 'max:2000'], 'company' => ['nullable', 'string', 'max:255'], 'phone' => ['nullable', 'string', 'max:255'], @@ -90,8 +185,9 @@ class ContactForm extends Component $payload['is_spam'] = $spamDetector->detect($payload, $this->formLoadedAt); $subjectLabel = $this->getSubjectsProperty()[$this->subject] ?? $this->subject; + $prefix = $this->world === 'immobilien' ? 'Kontaktanfrage B2in Immobilien' : 'Kontaktanfrage B2in'; $subjectLine = $subjectLabel - ? 'Kontaktanfrage B2in: ' . $subjectLabel + ? $prefix.': '.$subjectLabel : __('contact-form::contact-form.default_subject'); $service->handle($payload, $subjectLine, 'contact-form'); diff --git a/app/Livewire/Web/Components/UI/Footer.php b/app/Livewire/Web/Components/UI/Footer.php index 85a1abac..81d85fc9 100644 --- a/app/Livewire/Web/Components/UI/Footer.php +++ b/app/Livewire/Web/Components/UI/Footer.php @@ -9,10 +9,46 @@ class Footer extends Component /** @var array */ public array $content = []; + /** Farbwelt der Seite (`interior` | `immobilien`), in mount() festgehalten. */ + public string $world = 'interior'; + + /** @var array */ + public array $worlds = []; + + public string $logoPath = ''; + + /** + * Fuer die Immobilien-Welt zeigen die gemeinsamen Footer-Links auf die Seiten dieser Welt. + * + * @var array + */ + private const IMMOBILIEN_LINKS = [ + '/faq' => '/immobilien/faq', + '/about' => '/immobilien/ueber-b2in', + '/magazin' => '/immobilien/magazin', + ]; + public function mount(): void { $section = cms_theme_section('footer'); $this->content = is_array($section) ? $section : []; + $this->world = b2in_world(); + + if ($this->world === 'immobilien' && is_array($this->content['links'] ?? null)) { + $this->content['links'] = array_map(function ($link) { + if (is_array($link) && isset(self::IMMOBILIEN_LINKS[$link['url'] ?? ''])) { + $link['url'] = self::IMMOBILIEN_LINKS[$link['url']]; + } + + return $link; + }, $this->content['links']); + } + $this->worlds = b2in_worlds($this->world); + + // Der Interior-Footer liegt auf Sand (helle Fläche) und trägt deshalb das positive + // 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); } public function render() diff --git a/app/Livewire/Web/Components/UI/Header.php b/app/Livewire/Web/Components/UI/Header.php index e59a0ffe..9dc8d71d 100644 --- a/app/Livewire/Web/Components/UI/Header.php +++ b/app/Livewire/Web/Components/UI/Header.php @@ -16,13 +16,33 @@ class Header extends Component 'en' => 'EN', ]; + /** Farbwelt der Seite (`interior` | `immobilien`), in mount() festgehalten. */ + public string $world = 'interior'; + + /** @var array */ + public array $worlds = []; + + public string $logoPath = ''; + + /** Pfad der Seite, in mount() festgehalten – ein Livewire-Request traegt ihn nicht mehr. */ + public string $currentPath = '/'; + + /** Ziel des Kontakt-Buttons: /contact (Interior) bzw. /immobilien/kontakt (Immobilien). */ + public string $contactUrl = ''; + public function mount(): void { $this->domainName = \App\Helpers\ThemeHelper::getDomainName(); $this->domainUrl = \App\Helpers\ThemeHelper::getDomainUrl(); $this->currentLocale = app()->getLocale(); + $this->world = b2in_world(); + $this->worlds = b2in_worlds($this->world); + $this->logoPath = \App\Helpers\ThemeHelper::getLogoPath('positive', $this->world); + $this->currentPath = '/'.ltrim(request()->path(), '/'); + $this->contactUrl = route($this->world === 'immobilien' ? 'immobilien.contact' : 'contact'); - $this->content = cms_theme_section('header'); + // Jede Welt hat ihre eigene Navigation (CMS: global.header bzw. global.header_immobilien). + $this->content = cms_theme_section($this->world === 'immobilien' ? 'header_immobilien' : 'header'); // Ensure required keys exist if (!isset($this->content['portal_login'])) { @@ -57,27 +77,43 @@ class Header extends Component { $this->isMobileMenuOpen = false; } - public function isActiveRoute($url) + /** + * Aktiver Navigationspunkt: exakter Treffer, sonst der laengste Eintrag, dessen Pfad ein + * Praefix der Seite ist. So markiert /immobilien/magazin nur „Magazin“, nicht auch + * „Immobilien“. Anker (#…) zaehlen nicht zum Pfad. + */ + public function isActiveRoute($url): bool { - $currentPath = request()->path(); - $currentPath = '/' . ltrim($currentPath, '/'); + $path = $this->navPath((string) $url); - // Exact match - if ($currentPath === $url) { + if ($this->currentPath === $path) { return true; } - // Special handling for home route - if ($url === '/' && ($currentPath === '/' || $currentPath === '')) { - return true; + if ($path === '/' || ! str_starts_with($this->currentPath, rtrim($path, '/').'/')) { + return false; } - // Check if current path starts with the navigation URL (for sub-pages) - if ($url !== '/' && str_starts_with($currentPath, rtrim($url, '/'))) { - return true; + foreach ($this->content['navigation'] ?? [] as $navItem) { + $other = $this->navPath((string) ($navItem['url'] ?? '')); + + if ($other === $this->currentPath) { + return false; + } + + if (strlen($other) > strlen($path) && str_starts_with($this->currentPath, rtrim($other, '/').'/')) { + return false; + } } - return false; + return true; + } + + private function navPath(string $url): string + { + $path = (string) (parse_url($url, PHP_URL_PATH) ?? '/'); + + return '/'.ltrim($path, '/'); } public function render() { diff --git a/app/Models/CmsArticle.php b/app/Models/CmsArticle.php index 4a3e9941..f5e1d92c 100644 --- a/app/Models/CmsArticle.php +++ b/app/Models/CmsArticle.php @@ -29,12 +29,31 @@ class CmsArticle extends Model 5 => 'local-for-local-regionaler-moebelhandel', ]; + public const WORLD_INTERIOR = 'interior'; + + public const WORLD_IMMOBILIEN = 'immobilien'; + + /** @var array */ + public const WORLDS = [self::WORLD_INTERIOR, self::WORLD_IMMOBILIEN]; + + /** + * Beiträge aus dem Bestand, die zur Immobilien-Welt gehören (Dubai-Themen). + * + * @var array + */ + public const LEGACY_IMMOBILIEN_SLUGS = [ + 'escrow-system-dubai-investoren', + 'spotlight-al-jaddaf-hotspot', + 'turnkey-investments-moeblierung-rendite', + ]; + protected $fillable = [ 'slug', 'title', 'subtitle', 'image', 'category', + 'world', 'date_label', 'read_time', 'author', @@ -69,6 +88,11 @@ class CmsArticle extends Model return $query->orderBy('order')->orderByDesc('created_at'); } + public function scopeForWorld(Builder $query, string $world): Builder + { + return $query->where('world', $world); + } + /** * Veröffentlichte Artikel fürs Frontend, nach Slug indiziert. * @@ -76,19 +100,32 @@ class CmsArticle extends Model * dieselbe Reihenfolge wie bei cms_theme_section(), damit das Magazin auf einer frischen * Installation nicht leer ist. * + * Mit `$world` nur die Beiträge dieser Farbwelt (Interior oder Immobilien) – die beiden + * Magazinlisten sind getrennt, die Detailseite rendert in der Welt ihres Beitrags. + * * @return array> */ - public static function forFrontend(): array + public static function forFrontend(?string $world = null): array { - $articles = static::query()->published()->ordered()->get(); + $maintained = static::query()->published()->exists(); - if ($articles->isNotEmpty()) { - return $articles + if ($maintained) { + return static::query() + ->published() + ->when($world !== null, fn (Builder $query) => $query->forWorld($world)) + ->ordered() + ->get() ->mapWithKeys(fn (self $article) => [$article->slug => $article->toFrontendArray()]) ->all(); } - return static::fallbackFromLang(); + $articles = static::fallbackFromLang(); + + if ($world === null) { + return $articles; + } + + return array_filter($articles, fn (array $article) => ($article['world'] ?? self::WORLD_INTERIOR) === $world); } /** @@ -143,7 +180,7 @@ class CmsArticle extends Model continue; } - $out[$slug] = $article + ['slug' => $slug]; + $out[$slug] = $article + ['slug' => $slug, 'world' => self::WORLD_INTERIOR]; } return $out; @@ -161,6 +198,7 @@ class CmsArticle extends Model 'subtitle' => $this->subtitle, 'image' => $this->image, 'category' => $this->category, + 'world' => $this->world ?? self::WORLD_INTERIOR, 'date' => $this->date_label, 'readTime' => $this->read_time, 'author' => $this->author ?? [], diff --git a/app/helpers.php b/app/helpers.php index 4e355c45..33f0caba 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -206,3 +206,66 @@ if (! function_exists('media_url')) { return $resolved[$cacheKey]; } } + +if (! function_exists('b2in_world')) { + /** + * Farbwelt der aktuellen Seite: `interior` (Standard) oder `immobilien`. + * + * Die Immobilien-Welt umfasst die Immobilien-Routen samt Detailseiten und den + * Entwicklungs-Vorschauen. Das Layout setzt den Wert als Body-Klasse + * (`world-interior` / `world-immobilien`), theme-b2in.css schaltet daran die Farben um. + * Livewire-Komponenten merken sich die Welt in mount(), weil ein späterer + * Livewire-Request keine Seitenroute mehr trägt. + */ + function b2in_world(): string + { + if (app()->bound('b2in.world')) { + return (string) app('b2in.world'); + } + + $routeName = request()->route()?->getName(); + + if (is_string($routeName) && (str_starts_with($routeName, 'immobilien') || str_starts_with($routeName, 'dev.immobilien'))) { + return 'immobilien'; + } + + return 'interior'; + } +} + +if (! function_exists('b2in_worlds')) { + /** + * Die beiden Welten für den Welten-Wechsler in Kopf- und Fußzeile. + * + * @return array + */ + function b2in_worlds(?string $current = null): array + { + $current = $current ?? b2in_world(); + + return [ + 'interior' => [ + 'label' => __('ui.world_interior'), + 'url' => url('/'), + 'active' => $current === 'interior', + ], + 'immobilien' => [ + 'label' => __('ui.world_immobilien'), + 'url' => route('immobilien'), + 'active' => $current === 'immobilien', + ], + ]; + } +} + +if (! function_exists('b2in_world_set')) { + /** + * Welt für den laufenden Request festlegen, wenn sie nicht an der Route hängt – + * etwa auf der Magazin-Detailseite, die in der Welt ihres Beitrags rendert. + * Muss vor dem Rendern von Kopf- und Fußzeile geschehen (beide halten die Welt in mount() fest). + */ + function b2in_world_set(string $world): void + { + app()->instance('b2in.world', $world); + } +} diff --git a/config/cms_section_map.php b/config/cms_section_map.php index 9b0b6abf..5e6be281 100644 --- a/config/cms_section_map.php +++ b/config/cms_section_map.php @@ -14,15 +14,18 @@ return [ 'sections' => [ 'announcement_bar' => ['global', 'announcement_bar'], + 'announcement_bar_immobilien' => ['global', 'announcement_bar_immobilien'], 'header' => ['global', 'header'], 'footer' => ['global', 'footer'], 'cta_section' => ['global', 'cta_section'], + 'cta_section_immobilien' => ['global', 'cta_section_immobilien'], 'appointment_widget' => ['global', 'appointment_widget'], 'cta_section_portfolio' => ['global', 'cta_section_portfolio'], 'hero' => ['home', 'hero'], 'audience_switch' => ['home', 'audience_switch'], 'core_brands' => ['home', 'core_brands'], + 'origin_section' => ['home', 'origin_section'], 'marken_hero' => ['marken', 'hero'], 'marken_network' => ['marken', 'network'], @@ -68,8 +71,18 @@ return [ 'interior_trust' => ['netzwerk', 'interior_trust'], 'faq' => ['faq', 'faq'], + 'faq_immobilien' => ['faq', 'faq_immobilien'], + + // Immobilien-Welt: eigene Navigation, eigenes Magazin, eigene Ueber-uns-Seite + 'header_immobilien' => ['global', 'header_immobilien'], + 'magazin_list_immobilien' => ['immobilien', 'magazin_list'], + 'immobilien_about_hero' => ['immobilien', 'about_hero'], + 'immobilien_our_story' => ['immobilien', 'our_story'], + 'immobilien_about_image_break' => ['immobilien', 'about_image_break'], + 'immobilien_our_values' => ['immobilien', 'our_values'], 'contact_form' => ['contact', 'contact_form'], + 'contact_form_immobilien' => ['contact', 'contact_form_immobilien'], 'about_hero' => ['about', 'hero'], 'our_story' => ['about', 'our_story'], diff --git a/config/domains.php b/config/domains.php index c7bdac5d..a7a96401 100644 --- a/config/domains.php +++ b/config/domains.php @@ -71,13 +71,29 @@ return [ 'view_prefix' => 'b2in', 'assets_dir' => 'build/b2in', 'description' => 'Haupt-Landingpage mit Standard-Theme', + // Interior-Welt (Standard). Die Immobilien-Welt behält das bisherige Blau, + // siehe `worlds` und resources/css/web/theme-b2in.css. 'color_scheme' => [ - 'primary' => '#2b3f51', // Anthracite - 'secondary' => '#20a0da', // Dynamic Blue + 'primary' => '#202c2b', // Tanne + 'secondary' => '#d39e46', // Ocker ], 'font_family' => [ 'primary' => 'Inter', - 'secondary' => 'IBM Plex Sans', + 'secondary' => 'EB Garamond', + ], + 'worlds' => [ + 'interior' => [ + 'primary' => '#202c2b', // Tanne – Hauptton dunkel, Wortmarke + 'background' => '#f7f4ef', // Creme + 'surface' => '#d9cbbf', // Sand – Flächen, Footer + 'accent' => '#d39e46', // Ocker – Akzent, Logo-Bogen + 'secondary' => '#a38f82', // Clay – Sekundär, Produkte + 'text' => '#2d2926', // Anthrazit + ], + 'immobilien' => [ + 'primary' => '#2b3f51', // Anthracite + 'accent' => '#20a0da', // Dynamic Blue + ], ], ], diff --git a/database/factories/CmsArticleFactory.php b/database/factories/CmsArticleFactory.php index 53bcd566..755d4f92 100644 --- a/database/factories/CmsArticleFactory.php +++ b/database/factories/CmsArticleFactory.php @@ -23,6 +23,7 @@ class CmsArticleFactory extends Factory 'subtitle' => fake()->sentence(12), 'image' => 'b2in/magazin-'.fake()->numberBetween(1, 5).'.jpg', 'category' => fake()->randomElement(['Dubai Investment', 'Rendite & Einrichtung', 'B2B & Partner', 'Einrichtung & Netzwerk']), + 'world' => CmsArticle::WORLD_INTERIOR, 'date_label' => fake()->date('F j, Y'), 'read_time' => fake()->numberBetween(3, 10).' min read', 'author' => [ diff --git a/database/migrations/2026_09_14_120000_add_world_to_cms_articles_table.php b/database/migrations/2026_09_14_120000_add_world_to_cms_articles_table.php new file mode 100644 index 00000000..ce8004e6 --- /dev/null +++ b/database/migrations/2026_09_14_120000_add_world_to_cms_articles_table.php @@ -0,0 +1,34 @@ +string('world', 20)->default(CmsArticle::WORLD_INTERIOR)->index()->after('category'); + }); + + CmsArticle::query() + ->whereIn('slug', CmsArticle::LEGACY_IMMOBILIEN_SLUGS) + ->update(['world' => CmsArticle::WORLD_IMMOBILIEN]); + } + + public function down(): void + { + Schema::table('cms_articles', function (Blueprint $table) { + $table->dropIndex(['world']); + $table->dropColumn('world'); + }); + } +}; diff --git a/database/seeders/CmsArticleSeeder.php b/database/seeders/CmsArticleSeeder.php index 738cc227..6ce7d38d 100644 --- a/database/seeders/CmsArticleSeeder.php +++ b/database/seeders/CmsArticleSeeder.php @@ -70,6 +70,7 @@ class CmsArticleSeeder extends Seeder ], 'image' => (string) ($rowDe['image'] ?? ''), 'category' => (string) ($rowDe['category'] ?? ''), + 'world' => (string) ($rowDe['world'] ?? CmsArticle::WORLD_INTERIOR), 'date_label' => (string) ($rowDe['date'] ?? ''), 'read_time' => (string) ($rowDe['readTime'] ?? ''), 'author' => is_array($rowDe['author'] ?? null) ? $rowDe['author'] : [], diff --git a/database/seeders/CmsBrandSeeder.php b/database/seeders/CmsBrandSeeder.php index 57df1e2a..7c7c6f57 100644 --- a/database/seeders/CmsBrandSeeder.php +++ b/database/seeders/CmsBrandSeeder.php @@ -6,7 +6,13 @@ use App\Models\CmsBrand; use Illuminate\Database\Seeder; /** - * Legt die drei Kernmarken an. Texte aus dev/2026-08-28/umsetzung-inhalte.md §5. + * 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. * * Bestehende Marken werden nicht überschrieben – Marcel pflegt sie im Admin. Für bereits * angelegte Marken setzt {@see BrandLogoSeeder} die Logos nach. @@ -139,6 +145,86 @@ Consultation and planning in our kitchen studio.', 'showroom' => '', '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' => '

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.

', + 'en' => '

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.

', + ], + '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' => '', + 'is_published' => true, + ], ]; } } diff --git a/database/seeders/OriginSectionCmsSeeder.php b/database/seeders/OriginSectionCmsSeeder.php new file mode 100644 index 00000000..f3d7de44 --- /dev/null +++ b/database/seeders/OriginSectionCmsSeeder.php @@ -0,0 +1,29 @@ + */ + private const SECTIONS = ['origin_section']; + + public function run(): void + { + $seeder = new CmsContentSeeder; + $seeder->setCommand($this->command); + $seeder->onlySections = self::SECTIONS; + $seeder->run(); + + $this->command?->info('Herkunfts-Sektion: angelegt, falls sie fehlte.'); + } +} diff --git a/database/seeders/WorldSplitCmsSeeder.php b/database/seeders/WorldSplitCmsSeeder.php new file mode 100644 index 00000000..50eec7e1 --- /dev/null +++ b/database/seeders/WorldSplitCmsSeeder.php @@ -0,0 +1,63 @@ + */ + private const NEW_SECTIONS = [ + 'header_immobilien', + 'announcement_bar_immobilien', + 'magazin_list_immobilien', + 'faq_immobilien', + 'contact_form_immobilien', + 'cta_section_immobilien', + 'immobilien_about_hero', + 'immobilien_our_story', + 'immobilien_about_image_break', + 'immobilien_our_values', + ]; + + /** @var array */ + private const OVERWRITE_SECTIONS = [ + 'header', + 'magazin_list', + // Betreffe in Gruppen (Interior / Immobilien / B2in) statt flacher Liste, 15.09. + 'contact_form', + 'contact_form_immobilien', + ]; + + public function run(): void + { + $create = new CmsContentSeeder; + $create->setCommand($this->command); + $create->onlySections = self::NEW_SECTIONS; + $create->run(); + + $overwrite = new CmsContentSeeder; + $overwrite->setCommand($this->command); + $overwrite->onlySections = self::OVERWRITE_SECTIONS; + $overwrite->overwriteExisting = true; + $overwrite->run(); + + $this->command?->info('Welten-Trennung: '.count(self::NEW_SECTIONS).' Sektionen angelegt (falls fehlend), '.count(self::OVERWRITE_SECTIONS).' Interior-Sektionen aktualisiert.'); + } +} diff --git a/dev/12-01-2026/Moebeldatenliste Stand 4.11.2025.csv b/dev/2026-01-12/Moebeldatenliste Stand 4.11.2025.csv similarity index 100% rename from dev/12-01-2026/Moebeldatenliste Stand 4.11.2025.csv rename to dev/2026-01-12/Moebeldatenliste Stand 4.11.2025.csv diff --git a/dev/12-01-2026/db-backup.sql b/dev/2026-01-12/db-backup.sql similarity index 100% rename from dev/12-01-2026/db-backup.sql rename to dev/2026-01-12/db-backup.sql diff --git a/dev/12-01-2026/entwicklungsplan.md b/dev/2026-01-12/entwicklungsplan.md similarity index 100% rename from dev/12-01-2026/entwicklungsplan.md rename to dev/2026-01-12/entwicklungsplan.md diff --git a/dev/12-01-2026/konzeption.md b/dev/2026-01-12/konzeption.md similarity index 100% rename from dev/12-01-2026/konzeption.md rename to dev/2026-01-12/konzeption.md diff --git a/dev/27-02-2026/B2in Website-Architektur.md b/dev/2026-02-27/B2in Website-Architektur.md similarity index 100% rename from dev/27-02-2026/B2in Website-Architektur.md rename to dev/2026-02-27/B2in Website-Architektur.md diff --git a/dev/27-02-2026/b2in-local-for-local.md b/dev/2026-02-27/b2in-local-for-local.md similarity index 100% rename from dev/27-02-2026/b2in-local-for-local.md rename to dev/2026-02-27/b2in-local-for-local.md diff --git a/dev/27-02-2026/b2in-umsetzung-changelog.md b/dev/2026-02-27/b2in-umsetzung-changelog.md similarity index 100% rename from dev/27-02-2026/b2in-umsetzung-changelog.md rename to dev/2026-02-27/b2in-umsetzung-changelog.md diff --git a/dev/27-02-2026/b2in-website-umsetzungsplan.md b/dev/2026-02-27/b2in-website-umsetzungsplan.md similarity index 100% rename from dev/27-02-2026/b2in-website-umsetzungsplan.md rename to dev/2026-02-27/b2in-website-umsetzungsplan.md diff --git a/dev/27-02-2026/backup-before-relaunch/about.blade.php b/dev/2026-02-27/backup-before-relaunch/about.blade.php similarity index 100% rename from dev/27-02-2026/backup-before-relaunch/about.blade.php rename to dev/2026-02-27/backup-before-relaunch/about.blade.php diff --git a/dev/27-02-2026/backup-before-relaunch/b2in.blade.php b/dev/2026-02-27/backup-before-relaunch/b2in.blade.php similarity index 100% rename from dev/27-02-2026/backup-before-relaunch/b2in.blade.php rename to dev/2026-02-27/backup-before-relaunch/b2in.blade.php diff --git a/dev/27-02-2026/backup-before-relaunch/contact.blade.php b/dev/2026-02-27/backup-before-relaunch/contact.blade.php similarity index 100% rename from dev/27-02-2026/backup-before-relaunch/contact.blade.php rename to dev/2026-02-27/backup-before-relaunch/contact.blade.php diff --git a/dev/27-02-2026/backup-before-relaunch/content.php b/dev/2026-02-27/backup-before-relaunch/content.php similarity index 100% rename from dev/27-02-2026/backup-before-relaunch/content.php rename to dev/2026-02-27/backup-before-relaunch/content.php diff --git a/dev/27-02-2026/backup-before-relaunch/ecosystem.blade.php b/dev/2026-02-27/backup-before-relaunch/ecosystem.blade.php similarity index 100% rename from dev/27-02-2026/backup-before-relaunch/ecosystem.blade.php rename to dev/2026-02-27/backup-before-relaunch/ecosystem.blade.php diff --git a/dev/27-02-2026/backup-before-relaunch/footer.blade.php b/dev/2026-02-27/backup-before-relaunch/footer.blade.php similarity index 100% rename from dev/27-02-2026/backup-before-relaunch/footer.blade.php rename to dev/2026-02-27/backup-before-relaunch/footer.blade.php diff --git a/dev/27-02-2026/backup-before-relaunch/hero.blade.php b/dev/2026-02-27/backup-before-relaunch/hero.blade.php similarity index 100% rename from dev/27-02-2026/backup-before-relaunch/hero.blade.php rename to dev/2026-02-27/backup-before-relaunch/hero.blade.php diff --git a/dev/27-02-2026/backup-before-relaunch/home.blade.php b/dev/2026-02-27/backup-before-relaunch/home.blade.php similarity index 100% rename from dev/27-02-2026/backup-before-relaunch/home.blade.php rename to dev/2026-02-27/backup-before-relaunch/home.blade.php diff --git a/dev/27-02-2026/backup-before-relaunch/magazin.blade.php b/dev/2026-02-27/backup-before-relaunch/magazin.blade.php similarity index 100% rename from dev/27-02-2026/backup-before-relaunch/magazin.blade.php rename to dev/2026-02-27/backup-before-relaunch/magazin.blade.php diff --git a/dev/27-02-2026/backup-before-relaunch/partner.blade.php b/dev/2026-02-27/backup-before-relaunch/partner.blade.php similarity index 100% rename from dev/27-02-2026/backup-before-relaunch/partner.blade.php rename to dev/2026-02-27/backup-before-relaunch/partner.blade.php diff --git a/dev/27-02-2026/backup-before-relaunch/web.php b/dev/2026-02-27/backup-before-relaunch/web.php similarity index 100% rename from dev/27-02-2026/backup-before-relaunch/web.php rename to dev/2026-02-27/backup-before-relaunch/web.php diff --git a/dev/27-02-2026/review-architektur-02-03-2026.md b/dev/2026-02-27/review-architektur-02-03-2026.md similarity index 100% rename from dev/27-02-2026/review-architektur-02-03-2026.md rename to dev/2026-02-27/review-architektur-02-03-2026.md diff --git a/dev/12-03-2026/content.md b/dev/2026-03-12/content.md similarity index 100% rename from dev/12-03-2026/content.md rename to dev/2026-03-12/content.md diff --git a/dev/12-03-2026/magazin.md b/dev/2026-03-12/magazin.md similarity index 100% rename from dev/12-03-2026/magazin.md rename to dev/2026-03-12/magazin.md diff --git a/dev/12-03-2026/tasks.md b/dev/2026-03-12/tasks.md similarity index 100% rename from dev/12-03-2026/tasks.md rename to dev/2026-03-12/tasks.md diff --git a/dev/2026-08-28/entwicklungsplan.md b/dev/2026-08-28/entwicklungsplan.md index 77b20337..60cb250e 100644 --- a/dev/2026-08-28/entwicklungsplan.md +++ b/dev/2026-08-28/entwicklungsplan.md @@ -4,8 +4,9 @@ **Basis:** `B2in_Interior-Konzept.md`, `umsetzungliste.md`, `umsetzung-inhalte.md` **Vorgänger:** `dev/2026-02-27/b2in-website-umsetzungsplan.md` **Rahmen:** 37–39 Std. freigegeben (18 + 8 + 4 + 3 + 4–6) -**Status:** Revision 13 – **AP0–AP6 umgesetzt**, Seite abgestimmt, Markentexte Rotpunkt und Febrü -eingearbeitet. Offen: Bildmaterial, Rechtstexte und die Herstellerfreigaben. +**Status:** Revision 14 – **AP0–AP6 umgesetzt**, Seite abgestimmt, Markentexte Rotpunkt und Febrü +eingearbeitet. **Arbeitsplan 14.09. (`umsetzungen-14-09-2026.md`), Punkt 1.1 Farbwelt und Logo +umgesetzt (Nachtrag 6).** Offen: Bildmaterial, Rechtstexte und die Herstellerfreigaben. **Umgebung:** Dev Container, `php artisan …` (nicht `vendor/bin/sail …`) > **Verbindliche Schreibweisen:** **Interior** (nicht „Interieur"), **Febrü**, **Rotpunkt Küchen**, **CABINET**. @@ -35,6 +36,11 @@ ausführlichen Text. `php artisan test --compact`: **873 grün** (3217 Assertion | Abstimmungsrunde 03.09.: korrigierte Texte, CABINET-Seite, Galerie-Hinweise, Abschluss-CTA | ✅ | | AP4 Display-Spiegelung | ✅ | | AP5 Rubrik-Seite „Referenzen" | ✅ | +| Arbeitsplan 14.09. · 1.1 Farbwelt, Logo je Welt, Welten-Wechsler | ✅ (Nachtrag 6) | +| Welten-Trennung: Navigation, Magazin, Über uns, FAQ, Widget je Welt | ✅ (Nachtrag 7, Erweiterung zu 1.1) | +| Arbeitsplan 14.09. · 1.2 Tischlerei als vierte Marke (Platzhalter) | ✅ (Nachtrag 9) | +| 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, Ablauf CABINET von Marcel bestätigen lassen) | ### Nächste Schritte @@ -1256,6 +1262,17 @@ php artisan db:seed --class=BrandGalleryCaptionNormalizerSeeder --force # hochgeladenes Logo bleibt stehen. Braucht public/img/assets/marken/ aus dem Archiv. php artisan db:seed --class=BrandLogoSeeder --force +# 7. Welten-Trennung (Nachtrag 7): Navigation und Inhalte der Immobilien-Welt anlegen, +# Interior-Navigation ohne „Immobilien“ und Magazin-Einleitung ohne Dubai-Bezug setzen. +# Die Migration ordnet die drei Dubai-Beitraege der Immobilien-Welt zu. +php artisan db:seed --class=WorldSplitCmsSeeder --force + +# 8. Vierte Kernmarke „Tischlerei“ als Platzhalter (Nachtrag 9) – legt nur an, was fehlt. +php artisan db:seed --class=CmsBrandSeeder --force + +# 9. Herkunfts-Sektion „Aus der Region gefertigt“ (Nachtrag 10) – legt die Sektion nur an, wenn sie fehlt. +php artisan db:seed --class=OriginSectionCmsSeeder --force + php artisan config:cache php artisan view:clear @@ -1342,3 +1359,265 @@ genügt ein selektives Zurückspielen von `flux_cms_contents`, `display_versions - [ ] Markengalerien vor dem Livegang durchsehen: jedes Bild muss in der richtigen Rubrik stehen. Ohne gesetzte Kategorie erscheint ein Bild als „Visualisierung" – für ein echtes Projektfoto also bewusst „Realisiertes Projekt" wählen. + +--- + +## Nachtrag 6 · Arbeitsplan 14.09.2026, Punkt 1.1: Farbwelt und Logo (14.09.2026) + +Grundlage: `umsetzungen-14-09-2026.md` (freigegebener Umfang Phase 1) und die Shop-Screenshots +unter `_b2in_interior_shop/screenshots/web/` als optische Richtung: warme Creme-/Sandflächen, +dunkles Tannengrün als Kontrast, Serifen-Überschriften, ruhige Flächen ohne Leuchteffekte. + +### Zwei Welten auf einem Themensystem + +`theme-b2in.css` definiert die **Interior-Welt als Standard** auf `:root` und die +**Immobilien-Welt** als Override unter `.world-immobilien` – dieselben Token-Namen, andere Werte. +Das Layout setzt die Welt als Body-Klasse (`world-interior` / `world-immobilien`), ermittelt +über `b2in_world()` in `app/helpers.php`: Immobilien sind alle Routen `immobilien*` und +`dev.immobilien*`, alles andere ist Interior. Bestehende Sektionen mussten dafür nicht angefasst +werden – sie nutzen bereits die Tokens (`text-secondary`, `bg-accent`, `cta-band` …). + +| Token | Interior (neu) | Immobilien (wie bisher) | +|---|---|---| +| `--background` | Creme `#f7f4ef` | helles Beige `#f5f4f2` | +| `--foreground` | Anthrazit `#2d2926` | `#2a2a2a` | +| `--primary` (Buttons, dunkle Sektionen, Wortmarke) | Tanne `#202c2b` | Anthracite `#2b3f51` | +| `--secondary` (Akzent, Hervorhebungen, Logo-Bogen) | Ocker `#d39e46` | Dynamic Blue `#20a0da` | +| `--accent` (Sektionswechsel) | Sand `#d9cbbf` | helles Grau | +| `--clay` (neu) | Clay `#a38f82` | gedämpftes Blau | +| `--footer-*` (neu) | Sand mit Tanne | Anthracite mit Weiß | +| `--cta-band-end` (neu) | helleres Tannengrün (Ton in Ton, Korrektur 15.09.) | Dynamic Blue | +| `--font-secondary` (Überschriften) | EB Garamond (liegt lokal vor) | IBM Plex Sans | + +**Kontrast-Entscheidungen:** Ocker trägt keinen hellen Text (Creme auf Ocker ≈ 2,5:1). Deshalb +ist Ocker in der Interior-Welt Akzent, kein Flächenton unter Text: der Kontakt-Button im Kopf +und der zweite Hero-Button laufen jetzt auf Tanne (gefüllt bzw. als Kontur, `.world-interior +.btn-secondary-accent`), der Abschluss-CTA der Startseite liegt auf dem Abschlussband +(`cta-band`) mit weißem Button – wie auf den Markenseiten. **Korrektur 15.09.:** Der Verlauf +Grün → Ocker wirkte unharmonisch; das Band läuft jetzt Ton in Ton in ein helleres Tannengrün aus, +Ocker sitzt nur noch in der Haarlinie oben. Hervorgehobene Wörter im Bandtitel („Ihr nächster +Schritt") tragen im CMS `text-primary` und waren auf dem dunklen Band unlesbar – in beiden +Welten. `.cta-band .text-primary` setzt sie auf den Akzent der Welt (Ocker bzw. Blau). Ocker-Text +im Footer auf Sand nutzt die tiefe Stufe `--secondary-deep`. `--muted-foreground` ist ein +abgedunkelter Clay, damit Fließtext auf Creme lesbar bleibt (Clay pur ≈ 2,9:1). + +**Aufgeräumt:** Die Glow- und Schattenklassen in `shared-styles.css` (`glow-*`, `shadow-elegant`, +`shadow-premium`, Button-Hover im Glass-Flow) hatten ein festes Blau (`rgba(0,136,204)`, +`hsla(199 …)`) – jetzt hängen sie am Akzent der Welt. Karten im Glass-Flow nutzen `--card` +statt festem Weiß. + +### Logo je Welt + +Zulieferung 07.09.2026 (`dev/2026-08-28/logos/`): das Interior-Logo in Tanne mit Ocker-Bogen +(positiv) bzw. Creme mit Ocker-Bogen (negativ) und das B2In-Logo ohne Zusatz mit blauem Bogen. +Beide Familien sind auf den Inhalt beschnitten (Interior `viewBox="152.5 94.5 289 228"`, +B2In `viewBox="4.5 2 301 176"`) und liegen als SVG und PNG (240 px hoch) unter +`public/img/logos/`; die Interior-Varianten zusätzlich unter `public/_cabinet/assets/` für das +Display. `ThemeHelper::getLogoPath($type, $world)` liefert die Datei je Welt. + +Header und Footer halten Welt und Logo-Pfad in `mount()` fest, weil ein späterer +Livewire-Request keine Seitenroute mehr trägt. Der Interior-Footer liegt auf Sand und zeigt das +**positive** Logo; der Immobilien-Footer bleibt dunkel mit dem negativen. + +**Favicons** (`public/favicon/`, alle Größen plus `favicon.ico`) neu aus der Wortmarke in +Tanne/Ocker erzeugt; `theme-color` auf Creme. Die bisher tote Referenz +`img/favicons/b2in-favicon.ico` (`ThemeHelper::getFaviconPath()`) existiert jetzt. +Mail-Kopf auf Tanne. + +### Welten-Wechsler + +Kopfzeile (Desktop rechts neben dem Sprachwechsel, mobil als erster Block im Menü) und Footer +tragen einen Umschalter „Interior | Immobilien" (`b2in_worlds()`, Labels in `ui.php` de/en). +Die aktive Welt ist gefüllt und trägt `aria-current="page"`. + +### Tests + +`tests/Feature/ColorWorldsTest.php` (neu, 14 Fälle): Body-Klasse und Logo je Welt für +Startseite, `/immobilien`, Detailseite und weitere Seiten; `ThemeHelper::getLogoPath()` je Welt +und für Fremd-Themes; Dateien und Farbwerte beider Logo-Familien; Wechsler in Kopf und Fuß mit +korrekter Markierung; Tokens im Theme; kein festes Blau mehr in den Effekten. +`HomePageStructureTest` an den neuen Zuschnitt und den Sand-Footer angepasst. + +### Offen nach diesem Nachtrag + +- **Freigabe Marcel:** Farbwerte sind laut Plan „blockierend für 1.1" und wurden wie geliefert + umgesetzt. Die **Serife für Überschriften** (EB Garamond) ist eine Ableitung aus den + Shop-Screenshots, kein Planpunkt – ein Token in `theme-b2in.css`, bei Ablehnung in einer Zeile + zurück auf IBM Plex Sans. +- **Bildwelt:** Die Farbwelt wirkt erst mit warmem Bildmaterial (Hero, Kacheln) vollständig; die + Bestandsbilder aus der Immobilien-Zeit sind kühl. Zulieferung weiterhin offen. +- **Display-Player:** nutzt die neuen Interior-Dateien automatisch; der Theme-Scrim im Player + ist weiter neutral dunkel, keine Anpassung an Tanne vorgenommen. +- Nächste Schritte laut Reihenfolge im Arbeitsplan: 1.2 Tischlerei-Markenseite (blockiert durch + Name, Logo, Bilder), 1.3 Herkunfts-Sektion (blockiert durch CABINET-Klärung), 1.4 Akkordeon + und Widget-Beschriftung. + +--- + +## Nachtrag 7 · Saubere Trennung der beiden Welten (14.09.2026) + +Abgestimmt mit Kevin am 14.09.: Nach der Farbwelt-Umstellung zeigte die Immobilien-Welt noch die +Interior-Navigation, und das Magazin mischte Dubai- und Einrichtungsthemen. Entscheidung: **jede +Welt hat ihre eigene Navigation, der Welten-Wechsler ist die einzige Brücke.** Umfang ist eine +Erweiterung zu 1.1 des Arbeitsplans (nicht im freigegebenen Plan enthalten, Aufwand ca. 6–9 h). + +### Was je Welt getrennt ist + +| Bereich | Interior | Immobilien | +|---|---|---| +| Navigation (CMS) | `global.header`: Home · Marken · Referenzen · Magazin · Über B2in | `global.header_immobilien`: Immobilien · Projekte · Magazin · Über B2in | +| Ankündigungsleiste (CMS) | `global.announcement_bar`: aktuelles Interior-Thema (Rotpunkt) | `global.announcement_bar_immobilien`: aktuelles Projekt (Azizi Creek Views 4, wie vor dem Relaunch), eigener Dismiss-Schlüssel | +| Magazin | `/magazin` – nur Interior-Beiträge | `/immobilien/magazin` – nur Immobilien-Beiträge | +| Über uns | `/about` (unverändert) | `/immobilien/ueber-b2in` – Inhalte aus der Zeit vor dem Relaunch, leicht angepasst, als „B2in Immobilien" gekennzeichnet, Claim „Connecting Design and Property" | +| FAQ | `/faq` (unverändert) | `/immobilien/faq` – Fragen zu Investment, Reisen, Steuern, Einrichtung nach dem Kauf | +| Kontakt | `/contact` (unverändert) | `/immobilien/kontakt` – gleiche Seite, eigener Einstieg und eigene Betreffe (Overlay `contact_form_immobilien` über `contact_form`), Mail-Betreff „Kontaktanfrage B2in Immobilien: …"; Kontakt-Button im Kopf und CTAs der Immobilienseite zeigen dorthin | +| Footer-Links | wie bisher | FAQ, Über uns und Magazin zeigen auf die Immobilien-Seiten | +| Terminanfrage-Widget | Anliegen offen | Anliegen „Immobilien" vorbelegt, Mail-Betreff „Terminanfrage B2in Immobilien" | + +**Gemeinsam bleiben** Rechtstexte und die Cookie-Seiten. (Kontakt war zunächst gemeinsam, seit 15.09. je Welt.) + +### Magazin: Beiträge haben eine Welt + +Neues Feld `world` (`interior` | `immobilien`) an `cms_articles`, im Admin als Auswahl „Welt" +mit Badge in der Liste. Die Migration ordnet die drei Dubai-Beiträge (Escrow, Al Jaddaf, +Turnkey) der Immobilien-Welt zu; der Supply-Chain-Beitrag bleibt laut Abstimmung unter B2B in +Interior. `CmsArticle::forFrontend($world)` filtert, Lang-Fallback inklusive. + +Die **Detailseite behält ihre URL** (`/magazin/{slug}`, SEO und 301-Weiterleitungen bleiben), +rendert aber in der Welt ihres Beitrags: die Route setzt die Welt per `b2in_world_set()` vor dem +Rendern, weil Kopf- und Fußzeile sie in `mount()` festhalten. Verwandte Beiträge und der Rückweg +zur Liste bleiben in derselben Welt. + +### Technik + +- `b2in_world()` prüft zuerst einen expliziten Override (`b2in_world_set()`), dann die Route. +- Header: Navigation je Welt; der aktive Pfad wird in `mount()` festgehalten (vorher ging die + Markierung bei jedem Livewire-Request verloren) und nur der spezifischste Eintrag ist aktiv + (`/immobilien/magazin` markiert „Magazin", nicht „Immobilien"). +- `AboutHero`, `OurStory`, `OurValues` nehmen jetzt wie `ImageBreak` einen Sektionsschlüssel. +- Kontaktformular: Betreffe in drei Gruppen (Interior · Immobilien · B2in, `form.subject_groups`). + Die Kernmarken aus dem CMS stehen automatisch am Anfang der Interior-Gruppe, die Welt der Seite + bestimmt, welche Gruppe oben steht. Betreff wird jetzt gegen die Liste validiert. Ältere + CMS-Stände mit flacher `subjects`-Liste funktionieren weiter (Fallback ohne Gruppen). +- Abschluss-CTA („Ihr nächster Schritt") je Welt: `global.cta_section_immobilien` mit eigenem Text + und Link auf `/immobilien/kontakt`; greift automatisch auf allen Seiten der Immobilien-Welt. +- Neue CMS-Sektionen (alle im Admin pflegbar): `header_immobilien`, `announcement_bar_immobilien`, `contact_form_immobilien`, `cta_section_immobilien`, `magazin_list_immobilien`, + `faq_immobilien`, `immobilien_about_hero`, `immobilien_our_story`, + `immobilien_about_image_break`, `immobilien_our_values`. +- `WorldSplitCmsSeeder` legt die neuen Sektionen an und setzt **zwei** Interior-Sektionen aus + den Lang-Dateien zurück: `header` (ohne „Immobilien") und `magazin_list` (ohne Dubai-Bezug). + Lokal ausgeführt, beim Deployment mit erledigen (Abschnitt 8). + +### Tests + +`tests/Feature/WorldSplitTest.php` (neu, 18 Fälle): Navigation je Welt inkl. aktiver Markierung, +Footer-Links, beide Magazinlisten, Detailseite in der Welt des Beitrags, verwandte Beiträge, +CMS-Beiträge je Welt, Editor-Auswahl und -Validierung, Über-uns- und FAQ-Seite der +Immobilien-Welt, Widget-Vorbelegung. `MagazinPageTest`, `HomePageStructureTest`, +`LiveSiteReviewTest` und `CmsAdminTest` an die Trennung angepasst. + +### Offen + +- Navigationspunkt „Projekte" zeigt auf den Anker `#projekte` der Immobilienseite. +- Die Immobilien-Seite selbst nutzt weiter feste Farben (`bg-zinc-950`, Status-Badges in + emerald/sky/amber) außerhalb des Token-Systems – funktioniert in der blauen Welt, ist aber + nicht themenfähig. +- `LiveSiteReviewTest` prüft die Startseite noch auf das Wort „Netzwerk" (steht im Fließtext); + kein Navigationspunkt. + +--- + +## Nachtrag 8 · Terminanfrage-Buttons öffnen das Widget (15.09.2026) + +Auf der Seite gibt es mehrere Sektionen mit „Termin anfragen"-Buttons (Hero, Weiche, Abschluss-CTA, +`/marken`-Abschluss, Markenseiten, Referenzen, Immobilienseite). Bisher führten sie auf die +Kontaktseite. Jetzt öffnen sie das schwebende Terminanfrage-Widget; von einer Markenseite aus ist +die Marke im Anliegen vorbelegt. Die Anliegen-Auswahl des Widgets bleibt wie sie ist (Marken plus +feste Einträge, keine Gruppen – die gibt es nur auf der Kontaktseite). + +**Wie:** Blade-Komponente ``. Zeigt `href` auf `/contact` oder +`/immobilien/kontakt`, trägt der Link `data-open-appointment` (und `data-appointment-topic`); ein +Klick-Delegat in `resources/js/app.js` fängt ihn ab und feuert `open-appointment`, das Widget +öffnet sich, übernimmt das Anliegen (nur wenn es in der Auswahl existiert) und fokussiert das +Namensfeld. Ohne JavaScript bleibt es ein normaler Link; zeigt der CMS-Link woandershin, bleibt +er ein gewöhnlicher Link. Der Kontakt-Button im Kopf bleibt bewusst ein Link zur Kontaktseite. + +**Formularfelder** in Widget und Kontaktformular tragen jetzt die tokenbasierten Klassen +`.form-field` / `.form-select` (shared-styles.css): Kartenton als Grund, Fokusring im Akzent der +Welt, eigener Chevron und lesbare Gruppenüberschriften in der Betreff-Auswahl – statt +Tailwind-Grau. + +**Tests:** `tests/Feature/AppointmentTriggerTest.php` (9 Fälle). + +--- + +## Nachtrag 9 · Tischlerei als vierte Kernmarke, Platzhalter (15.09.2026) + +Arbeitsplan 14.09., Punkt 1.2. Name, Logo, Basisinfos und Bilder fehlen noch (blockierend laut +Plan), Platzhalter sind ausdrücklich erlaubt. Die Marke ist deshalb als **Denkanstoß** angelegt: + +- **Marke `tischlerei`** im `CmsBrandSeeder` (vierter Eintrag, wird bei bestehenden Marken nicht + doppelt angelegt). Inhalte entlang der Ausrichtung aus dem Plan – Aufbau, Sonderlösungen, + Veredelung, Maßarbeit; ergänzt CABINET und Rotpunkt, steht nicht daneben; Werkstatt in + Ostwestfalen ohne Nennung des Partnerbetriebs. Drei Abschnitte mit Vertiefungen, eigener + Abschlussblock, Widget-Beschriftung „Persönliche Beratung zur Maßarbeit vereinbaren“. +- **Ohne Logo**: der Seitenkopf zeigt den Schriftzug, Kacheln kommen ohne Logo aus. Kein + Stimmungsbild, die Kacheln zeigen den Platzhalter. +- **Kachelraster** der Startseite: `sm:grid-cols-2` – 2×2 auf allen Breiten. Vier nebeneinander + war kurz drin, wurde aber als zu schmal verworfen (Abstimmung 15.09.). „Weitere Marken“ auf den + Detailseiten: drei nebeneinander ab `lg`. +- Automatisch dabei: `/marken`, Anliegen im Widget, Betreff-Gruppe Interior der Kontaktseite. +- Seitentitel von `/marken` nennt die vierte Marke. +- Lang-Fallback (`core_brands.brands`) um die Tischlerei ergänzt. + +**Lokal ausgeführt:** `php artisan db:seed --class=CmsBrandSeeder` (legt nur die fehlende Marke an). +Beim Deployment mit erledigen (Abschnitt 8). + +**Was Marcel nachträgt (alles im Admin):** Name der Marke, Logo, Stimmungsbild, echte Texte – +die Platzhalter sind bewusst so geschrieben, dass sie ohne Änderung nicht live gehen sollten +(„Tischlerei“ ist kein Markenname). Slug `tischlerei` bleibt stabil, auch wenn der Name kommt. + +**Tests:** `tests/Feature/FourthBrandTest.php` (7 Fälle). + +--- + +## Nachtrag 10 · Herkunfts-Sektion „Aus der Region gefertigt“ (15.09.2026) + +Arbeitsplan 14.09., Punkt 1.3. Die Klärung „CABINET Hersteller oder Vertrieb“ kam am 15.09. von +Kevin nach seinem Verständnis: CABINET **fertigt selbst in Köln** und **vertreibt über die +CABINET Stores**; der Store Bielefeld ist einer davon. Dort laufen Beratung und Planung für +Bielefeld und Umkreis, gefertigt wird in Köln, aufgebaut beim Kunden – und hier kommt die +Tischlerei ins Spiel, die den Aufbau übernimmt und Sonderwünsche umsetzt. ⚠️ **Von Marcel +bestätigen lassen** (bietet CABINET selbst Aufbau an? Läuft der Aufbau immer über die eigene +Tischlerei?). Die Sektion ist so formuliert, dass sie diesem Verständnis folgt. + +### Aufbau + +- **Neue Sektion `origin_section`** (`OriginSection`, CMS-Gruppe `home`), eingebunden auf der + Startseite zwischen Kernmarken und „Gebaut auf Expertise“ sowie auf `/marken` zwischen + Markenübersicht und „Mehr als Marken: unser Netzwerk“. Anker `#herkunft`. +- **Schematische Karte** (SVG, ab `md`): Bielefeld als Drehscheibe in Ocker, die vier Standorte + als Punkte in Tanne mit gestrichelten Speichen und Entfernungsangabe, gestrichelte Ellipse + „Ostwestfalen“ um die drei OWL-Standorte. Keine Geografie, nur Richtung und Entfernung – + Positionen (`x`/`y` in der viewBox 640×400) und Beschriftungsseite (`label`) stehen im + Inhalt und sind im Admin änderbar. Unter `md` entfällt die Karte, die Kacheln tragen alles. +- **Vier Standort-Kacheln** (2×2 ab `sm`): Stadt, Marke, Rolle, Entfernung „von Bielefeld“ und + Link „Zur Marke“. Der Link kommt **nicht** aus dem CMS, sondern aus den veröffentlichten + Marken (`brand_slug`, ersatzweise Namensabgleich): eine unveröffentlichte Marke verliert den + Link, ihr Standort bleibt sichtbar. +- **Ablauf-Block „Gefertigt in Köln, geplant in Bielefeld“**: drei nummerierte Schritte am + Beispiel CABINET (Beratung und Planung in Bielefeld → Fertigung in Köln → Aufbau vor Ort mit + Sonderwünschen aus der Tischlerei). Das ist die saubere Trennung „gefertigt“ / „vertrieben“ + aus dem Plan. Entfällt, wenn `steps` im CMS leer ist. +- **Entfernungen** gerundet, Straße ab Bielefeld Mitte: Herford rund 20 km, Bünde rund 30 km, + Rheda-Wiedenbrück rund 35 km, Köln rund 200 km. Bei Bedarf im Admin anpassen. +- Styles `.origin-map-*` in `shared-styles.css` (Token-basiert, in beiden Welten gültig, die + Sektion liegt aber nur in der Interior-Welt). Build `npm run build:b2in` ausgeführt. + +**Lokal ausgeführt:** `php artisan db:seed --class=OriginSectionCmsSeeder` (legt nur an, was +fehlt, überschreibt nichts). Beim Deployment mit erledigen (Abschnitt 8, Schritt 9). + +**Offen:** Bestätigung des CABINET-Ablaufs durch Marcel; Wortlaut der Tischlerei-Kachel folgt +dem Platzhalter aus Nachtrag 9 und wandert mit dem echten Markennamen. + +**Tests:** `tests/Feature/OriginSectionTest.php` (12 Fälle). diff --git a/dev/2026-08-28/logos/B2In-Logo-negativ-2025-09-22_RGB.svg b/dev/2026-08-28/logos/B2In-Logo-negativ-2025-09-22_RGB.svg new file mode 100644 index 00000000..0f6335a4 --- /dev/null +++ b/dev/2026-08-28/logos/B2In-Logo-negativ-2025-09-22_RGB.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + diff --git a/dev/2026-08-28/logos/B2In-Logo-positiv-2025-09-22_RGB.svg b/dev/2026-08-28/logos/B2In-Logo-positiv-2025-09-22_RGB.svg new file mode 100644 index 00000000..6e1e1565 --- /dev/null +++ b/dev/2026-08-28/logos/B2In-Logo-positiv-2025-09-22_RGB.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + diff --git a/dev/2026-08-28/logos/B2In_Interior-Logo-negativ-2025-09-22_CMYK.svg b/dev/2026-08-28/logos/B2In_Interior-Logo-negativ-2025-09-22_CMYK.svg deleted file mode 100644 index 8cb2083a..00000000 --- a/dev/2026-08-28/logos/B2In_Interior-Logo-negativ-2025-09-22_CMYK.svg +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dev/2026-08-28/logos/B2In_Interior-Logo-negativ-2026-09-07_RGB.svg b/dev/2026-08-28/logos/B2In_Interior-Logo-negativ-2026-09-07_RGB.svg new file mode 100644 index 00000000..d7bcd586 --- /dev/null +++ b/dev/2026-08-28/logos/B2In_Interior-Logo-negativ-2026-09-07_RGB.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/2026-08-28/logos/B2In_Interior-Logo-positiv-2025-09-22_CMYK.svg b/dev/2026-08-28/logos/B2In_Interior-Logo-positiv-2025-09-22_CMYK.svg deleted file mode 100644 index bfce235b..00000000 --- a/dev/2026-08-28/logos/B2In_Interior-Logo-positiv-2025-09-22_CMYK.svg +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dev/2026-08-28/logos/B2In_Interior-Logo-positiv-2026-09-07_RGB.svg b/dev/2026-08-28/logos/B2In_Interior-Logo-positiv-2026-09-07_RGB.svg new file mode 100644 index 00000000..dbead3fa --- /dev/null +++ b/dev/2026-08-28/logos/B2In_Interior-Logo-positiv-2026-09-07_RGB.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/2026-08-28/umsetzungen-14-09-2026.md b/dev/2026-08-28/umsetzungen-14-09-2026.md new file mode 100644 index 00000000..0908977c --- /dev/null +++ b/dev/2026-08-28/umsetzungen-14-09-2026.md @@ -0,0 +1,186 @@ +# B2in Interior – Arbeitsplan + +**Freigegebener Umfang, Stand September 2026** + +--- + +## Freigabestatus im Überblick + +| Phase | Status | Aufwand | +|---|---|---:| +| **1 – Website: Layout, Marken, Herkunft** | ✅ freigegeben | 39–65 h | +| **3 – QR-Empfehlungssystem** | ✅ freigegeben | 25–39 h | + +--- + +# Phase 1 – Website + +## 1.1 Farbwelt und Logo + +| Aufgabe | +| Interior-Farbwelt im Themensystem umstellen: +Tanne `#202c2b` Hauptton dunkel, Wortmarke, +Creme `#f7f4ef` Hintergrund, +Sand `#d9cbbf` Flächen, Footer, +Ocker `#d39e46` Akzent, Logo-Bogen +Clay `#a38f82` Sekundär, Produkte +Anthrazit `#2d2926` Text +| Immobilien-Welt als eigene Farbwelt abgrenzen (bestehendes Blau) | +| Logo-Varianten mit farbigem Bogen je Welt, positiv und negativ - normal Immobilien und Interior mit Zusatz +hier liegen die Logos dev/2026-08-28/logos +| Welten-Wechsler in Topleiste und Footer, Immobilien und Interior | + Bei der Umsetzung auf die neue Farbwelt bitte auch diese Shop Screenshots einmal begutachten. In diese Richtung soll es gehen optisch _b2in_interior_shop/screenshots/web + +## 1.2 Tischlerei als vierte Marke + +| Aufgabe | +| Markenseite anlegen, CMS-Eintrag, Einbindung in `/marken` und Navigation | +Als Inhalt erst mal Platzhalter. + +**Inhaltliche Ausrichtung:** Aufbau, Sonderlösungen, Veredelung, Maßarbeit. Ergänzt CABINET und Rotpunkt, steht nicht daneben. + +**Offen:** Name der Marke, Logo, Basis-Infos, Bildmaterial. Zur Namensfrage: Tritt sie unter eigenem Namen auf oder als B2in-Leistung? Der Name des Partnerbetriebs gehört auf die Detailseite, nicht in die Marke. + +## 1.3 Herkunfts-Sektion + +| Aufgabe | +| Eigener Bereich „Aus der Region gefertigt" mit den vier Standorten, visuell aufbereitet | + +**Die Standorte:** Rotpunkt in Bünde, Febrü in Herford, Tischlerei in Rheda-Wiedenbrück, CABINET in Köln. Vier deutsche Manufakturen, drei davon in Ostwestfalen. + +**Zu klären:** Ist CABINET Hersteller oder Vertrieb? Die Formulierung muss sauber trennen zwischen gefertigt und vertrieben. + +## 1.4 Markenseiten und Texte + +| Aufgabe | +| Aufklappbare Detailbereiche auf den Markenseiten | +| Kontextbezogene Beschriftung des Terminanfrage-Widgets je Marke | + +**Texte:** Die finalen Fassungen für Rotpunkt und Febrü Sind bereits eingearbeitet. + +## 1.5 Abschluss + +| Aufgabe | Std. | +|---|---:| +| Tests, Prüfung bestehender Bereiche, Produktivsetzung | + +**Summe Phase 1: 39–65 h** · rund 3.510 – 5.850 € netto + +--- + +# Phase 3 – QR-Empfehlungssystem + +## Wie es funktioniert Linus + +1. Partner im System anlegen (Makler, Architekt, Handwerker, Praxis) +2. System erzeugt eindeutigen Code und QR als Bilddatei +3. Marcel druckt den Aufkleber selbst und bringt ihn auf dem Flyer an +4. Interessent scannt, landet auf der Website, Zugriff wird erfasst +5. Startet er eine Anfrage, ist sie dem Partner zugeordnet +6. Übersicht im Admin: Partner, Scans, Anfragen + +## Die Bausteine + +| Aufgabe | Std. | +|---|---:| +| Datenmodell: Partner, Code, Zuordnung, Status | 3–5 | +| Partnerverwaltung im Admin, Code erzeugen, QR als Bilddatei herunterladen | 7–10 | +| Weiterleitung und Erfassung des Scans | 5–8 | +| Zuordnung der Anfrage zum Partner, angebunden an die bestehende Anfragestrecke | 4–6 | +| Einfache Übersicht im Admin: Partner, Scans, Anfragen | 3–5 | +| Tests | 3–5 | + +**Summe Phase 3: 25–39 h** · rund 2.250 – 3.510 € netto + +## Nicht enthalten + +Provisionsabrechnung und Auszahlung, Partner-Login (Phase 4), Aufkleber-Vorlagen als fertiges PDF, ausgebaute Auswertung mit Diagrammen oder Filtern. + +## Technische Vorgaben für den Flyer + +| | | +|---|---| +| **Aufkleber** | 60 × 80 mm hochkant. QR bei 30 × 30 mm, darunter Partnername, darunter Platz für eine Zeile | +| **Freie Fläche auf dem Flyer** | 70 × 90 mm | +| **Untergrund** | hell, einfarbig, kein Bild, kein Verlauf | +| **Abstand** | mindestens 10 mm zu Rand und Falz | +| **Platzierung** | Rückseite, in der Nähe der Kontaktangaben | + +Die Zielgruppen-Ansprache steckt im Aufkleber, nicht im Flyer. Damit bleibt der Flyer eine Druckfassung. + +--- + +# Phase 2 – Flyer: was enthalten ist + +**2 Stunden.** Eine strukturierte schriftliche Rückmeldung auf einen fertigen Entwurf: Aufbau, Reihenfolge der Inhalte, Wirkung, Markenkonformität, Textschärfe. Einmal, als zusammenhängende Liste. + +**Nicht enthalten:** mehrfache Durchgänge über Zwischenstände, Layoutkorrekturen, Bildauswahl, Umschreiben von Texten, Druckdatenaufbereitung. + +--- + +# Reihenfolge der Umsetzung + +| Schritt | Was | Warum zuerst | +|---|---|---| +| **1** | Farbwelt und Logo | Fundament – alles Weitere baut darauf auf | +| **2** | Welten-Wechsler | gehört technisch zur Farbwelt-Umstellung | +| **3** | Tischlerei-Markenseite | braucht die fertige Farbwelt | +| **4** | Herkunfts-Sektion | braucht die Tischlerei als vierten Standort | +| **5** | Akkordeon und Widget-Beschriftung | Feinschliff auf den fertigen Seiten | +| **6** | Tests und Livegang Phase 1 | | +| **7** | Phase 3 komplett | erst sinnvoll, wenn die Website steht | + +Phase 3 erst nach dem Livegang von Phase 1: Ein QR-Code, der auf eine halbfertige Seite führt, verbrennt den ersten Eindruck beim Partner. + +--- + +# Was ich von Marcel brauche + +## Blockierend für Phase 1 + +| Was | Für | +|---|---| +| Freigabe Farbwerte (Ocker, Tanne) | 1.1 – Start | +| Name der Tischlerei-Marke | 1.2 | +| Logo und Basis-Infos Tischlerei | 1.2 | +| Bildmaterial Tischlerei | 1.2 | +| Klärung: CABINET Hersteller oder Vertrieb | 1.3 | + +## Nicht blockierend, aber offen + +| Was | Anmerkung | +|---|---| +| Logos Rotpunkt und Febrü | für die Markenseiten | +| Bildmaterial Hero und Kernmarken | Platzhalter möglich | +| Neuer Footer-Claim | „Connecting design and property" ist der alte Immobilien-Claim | +| Showroom-Adressen je Markenseite | nennen beide Seiten den richtigen Standort? | +| Höhe der Partnervergütung | vor der Partner-Ansprache, nicht vor dem Bau | + +--- + +# Empfehlungen fürs Protokoll + +**Erste zehn Partner:** Makler, Architekten, Handwerker. Praxen danach. Keine Wertung, sondern Priorisierung auf den Kanal mit der höheren Wirkung. + +**Keine Provisionen an Arztpraxen** im ersten Schritt. Flyer dort über die persönliche Verbindung auslegen, mit eigenem QR-Code. Nach drei Monaten zeigen die Zahlen, ob der Kanal trägt. + +**QR-Feld neutral halten.** Sachliche Zeile wie „Beratungstermin vereinbaren". Keine emotionalen Appelle. + +--- + +# Zurückgestellt + +| Thema | Status | +|---|---| +| Partnerportal | 2027, wenn genug Partner aktiv sind | +| Kundenmappe | Konzept wird überarbeitet | +| Shop (Katalog und Verkauf) | Kalkulation liegt vor: 149–235 h | +| Immobilien-CMS | Kostenplan liegt vor, nicht freigegeben | +| Dubai-Map | setzt Immobilien-CMS voraus | +| Marktplätze | spätere Shop-Stufe | +| Bestandskunden-Anschreiben | Idee vorhanden, noch nicht angeboten | +| Markenrichtlinie Fahrzeug und Print | eigene Design-Aufgabe | + +--- + +*Aufwandsschätzung auf Basis des aktuellen Stands, kein Festpreis. Abrechnung nach tatsächlichen, dokumentierten Stunden. Zusätzliche Anforderungen werden vorab abgestimmt und erst nach Freigabe umgesetzt.* \ No newline at end of file diff --git a/public/_cabinet/assets/b2in-interior-logo-negative.svg b/public/_cabinet/assets/b2in-interior-logo-negative.svg index 36c85f88..838bf931 100644 --- a/public/_cabinet/assets/b2in-interior-logo-negative.svg +++ b/public/_cabinet/assets/b2in-interior-logo-negative.svg @@ -1,54 +1,53 @@ + + viewBox="152.5 94.5 289 228" xml:space="preserve"> - - - - + + - - - - - - - - - - - - - - + + - + + + + + + + + + + + + + diff --git a/public/_cabinet/assets/b2in-interior-logo-positive.svg b/public/_cabinet/assets/b2in-interior-logo-positive.svg index ad309a61..23719451 100644 --- a/public/_cabinet/assets/b2in-interior-logo-positive.svg +++ b/public/_cabinet/assets/b2in-interior-logo-positive.svg @@ -1,54 +1,53 @@ + + viewBox="152.5 94.5 289 228" xml:space="preserve"> - - - - + + - - - - - - - - - - - - - - + + - + + + + + + + + + + + + + diff --git a/public/_cabinet/assets/b2in-logo-positive.svg b/public/_cabinet/assets/b2in-logo-positive.svg index 58aae48e..dabb1b97 100644 --- a/public/_cabinet/assets/b2in-logo-positive.svg +++ b/public/_cabinet/assets/b2in-logo-positive.svg @@ -1,21 +1,36 @@ - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + diff --git a/public/favicon/android-icon-144x144.png b/public/favicon/android-icon-144x144.png index 4d59c540..52126729 100644 Binary files a/public/favicon/android-icon-144x144.png and b/public/favicon/android-icon-144x144.png differ diff --git a/public/favicon/android-icon-192x192.png b/public/favicon/android-icon-192x192.png index 338c769a..5db2bf34 100644 Binary files a/public/favicon/android-icon-192x192.png and b/public/favicon/android-icon-192x192.png differ diff --git a/public/favicon/android-icon-36x36.png b/public/favicon/android-icon-36x36.png index 31171287..9711cc1f 100644 Binary files a/public/favicon/android-icon-36x36.png and b/public/favicon/android-icon-36x36.png differ diff --git a/public/favicon/android-icon-48x48.png b/public/favicon/android-icon-48x48.png index 9fe5826b..855c2c14 100644 Binary files a/public/favicon/android-icon-48x48.png and b/public/favicon/android-icon-48x48.png differ diff --git a/public/favicon/android-icon-72x72.png b/public/favicon/android-icon-72x72.png index 94f262e2..2a321e28 100644 Binary files a/public/favicon/android-icon-72x72.png and b/public/favicon/android-icon-72x72.png differ diff --git a/public/favicon/android-icon-96x96.png b/public/favicon/android-icon-96x96.png index f3afd5d5..68676dd8 100644 Binary files a/public/favicon/android-icon-96x96.png and b/public/favicon/android-icon-96x96.png differ diff --git a/public/favicon/apple-icon-114x114.png b/public/favicon/apple-icon-114x114.png index 0f5c644e..76bfc7aa 100644 Binary files a/public/favicon/apple-icon-114x114.png and b/public/favicon/apple-icon-114x114.png differ diff --git a/public/favicon/apple-icon-120x120.png b/public/favicon/apple-icon-120x120.png index e1e9b4d3..201c1b6b 100644 Binary files a/public/favicon/apple-icon-120x120.png and b/public/favicon/apple-icon-120x120.png differ diff --git a/public/favicon/apple-icon-144x144.png b/public/favicon/apple-icon-144x144.png index f81b3bf3..3019c5e1 100644 Binary files a/public/favicon/apple-icon-144x144.png and b/public/favicon/apple-icon-144x144.png differ diff --git a/public/favicon/apple-icon-152x152.png b/public/favicon/apple-icon-152x152.png index b3ce7178..999c4aa1 100644 Binary files a/public/favicon/apple-icon-152x152.png and b/public/favicon/apple-icon-152x152.png differ diff --git a/public/favicon/apple-icon-180x180.png b/public/favicon/apple-icon-180x180.png index acf55a81..d0e44c56 100644 Binary files a/public/favicon/apple-icon-180x180.png and b/public/favicon/apple-icon-180x180.png differ diff --git a/public/favicon/apple-icon-57x57.png b/public/favicon/apple-icon-57x57.png index bd69e12b..d0812f1f 100644 Binary files a/public/favicon/apple-icon-57x57.png and b/public/favicon/apple-icon-57x57.png differ diff --git a/public/favicon/apple-icon-60x60.png b/public/favicon/apple-icon-60x60.png index 8ab2dba2..5e527983 100644 Binary files a/public/favicon/apple-icon-60x60.png and b/public/favicon/apple-icon-60x60.png differ diff --git a/public/favicon/apple-icon-72x72.png b/public/favicon/apple-icon-72x72.png index 94f262e2..334084ed 100644 Binary files a/public/favicon/apple-icon-72x72.png and b/public/favicon/apple-icon-72x72.png differ diff --git a/public/favicon/apple-icon-76x76.png b/public/favicon/apple-icon-76x76.png index f79c946a..b04b7055 100644 Binary files a/public/favicon/apple-icon-76x76.png and b/public/favicon/apple-icon-76x76.png differ diff --git a/public/favicon/apple-icon-precomposed.png b/public/favicon/apple-icon-precomposed.png index 3b0de1af..87337674 100644 Binary files a/public/favicon/apple-icon-precomposed.png and b/public/favicon/apple-icon-precomposed.png differ diff --git a/public/favicon/apple-icon.png b/public/favicon/apple-icon.png index 3b0de1af..87337674 100644 Binary files a/public/favicon/apple-icon.png and b/public/favicon/apple-icon.png differ diff --git a/public/favicon/favicon-16x16.png b/public/favicon/favicon-16x16.png index 6f20373d..d12ce017 100644 Binary files a/public/favicon/favicon-16x16.png and b/public/favicon/favicon-16x16.png differ diff --git a/public/favicon/favicon-32x32.png b/public/favicon/favicon-32x32.png index 7bc484a1..126c2ab3 100644 Binary files a/public/favicon/favicon-32x32.png and b/public/favicon/favicon-32x32.png differ diff --git a/public/favicon/favicon-96x96.png b/public/favicon/favicon-96x96.png index 2140f245..68676dd8 100644 Binary files a/public/favicon/favicon-96x96.png and b/public/favicon/favicon-96x96.png differ diff --git a/public/favicon/favicon.ico b/public/favicon/favicon.ico index c949cadb..11bdea25 100644 Binary files a/public/favicon/favicon.ico and b/public/favicon/favicon.ico differ diff --git a/public/favicon/ms-icon-144x144.png b/public/favicon/ms-icon-144x144.png index f81b3bf3..52126729 100644 Binary files a/public/favicon/ms-icon-144x144.png and b/public/favicon/ms-icon-144x144.png differ diff --git a/public/favicon/ms-icon-150x150.png b/public/favicon/ms-icon-150x150.png index 5720d210..f0255e1c 100644 Binary files a/public/favicon/ms-icon-150x150.png and b/public/favicon/ms-icon-150x150.png differ diff --git a/public/favicon/ms-icon-310x310.png b/public/favicon/ms-icon-310x310.png index 4ba01825..dd42c76d 100644 Binary files a/public/favicon/ms-icon-310x310.png and b/public/favicon/ms-icon-310x310.png differ diff --git a/public/favicon/ms-icon-70x70.png b/public/favicon/ms-icon-70x70.png index a242a181..13d3fbe0 100644 Binary files a/public/favicon/ms-icon-70x70.png and b/public/favicon/ms-icon-70x70.png differ diff --git a/public/img/favicons/b2in-favicon.ico b/public/img/favicons/b2in-favicon.ico new file mode 100644 index 00000000..11bdea25 Binary files /dev/null and b/public/img/favicons/b2in-favicon.ico differ diff --git a/public/img/logos/b2in-interior-logo-negative.png b/public/img/logos/b2in-interior-logo-negative.png index df56e5be..f047233c 100644 Binary files a/public/img/logos/b2in-interior-logo-negative.png and b/public/img/logos/b2in-interior-logo-negative.png differ diff --git a/public/img/logos/b2in-interior-logo-negative.svg b/public/img/logos/b2in-interior-logo-negative.svg index 36c85f88..838bf931 100644 --- a/public/img/logos/b2in-interior-logo-negative.svg +++ b/public/img/logos/b2in-interior-logo-negative.svg @@ -1,54 +1,53 @@ + + viewBox="152.5 94.5 289 228" xml:space="preserve"> - - - - + + - - - - - - - - - - - - - - + + - + + + + + + + + + + + + + diff --git a/public/img/logos/b2in-interior-logo-positive.png b/public/img/logos/b2in-interior-logo-positive.png index 83c52822..cb6eadb9 100644 Binary files a/public/img/logos/b2in-interior-logo-positive.png and b/public/img/logos/b2in-interior-logo-positive.png differ diff --git a/public/img/logos/b2in-interior-logo-positive.svg b/public/img/logos/b2in-interior-logo-positive.svg index ad309a61..23719451 100644 --- a/public/img/logos/b2in-interior-logo-positive.svg +++ b/public/img/logos/b2in-interior-logo-positive.svg @@ -1,54 +1,53 @@ + + viewBox="152.5 94.5 289 228" xml:space="preserve"> - - - - + + - - - - - - - - - - - - - - + + - + + + + + + + + + + + + + diff --git a/public/img/logos/b2in-logo-negative.png b/public/img/logos/b2in-logo-negative.png index 7646afc3..1b846baa 100644 Binary files a/public/img/logos/b2in-logo-negative.png and b/public/img/logos/b2in-logo-negative.png differ diff --git a/public/img/logos/b2in-logo-negative.svg b/public/img/logos/b2in-logo-negative.svg index 6ef5d925..36ffe00b 100644 --- a/public/img/logos/b2in-logo-negative.svg +++ b/public/img/logos/b2in-logo-negative.svg @@ -1,25 +1,36 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + diff --git a/public/img/logos/b2in-logo-positive.png b/public/img/logos/b2in-logo-positive.png index 20ce0578..731fc145 100644 Binary files a/public/img/logos/b2in-logo-positive.png and b/public/img/logos/b2in-logo-positive.png differ diff --git a/public/img/logos/b2in-logo-positive.svg b/public/img/logos/b2in-logo-positive.svg index fc43ca1e..dabb1b97 100644 --- a/public/img/logos/b2in-logo-positive.svg +++ b/public/img/logos/b2in-logo-positive.svg @@ -1,25 +1,36 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + diff --git a/resources/css/web/shared-styles.css b/resources/css/web/shared-styles.css index 2cf6bccd..62ebd923 100644 --- a/resources/css/web/shared-styles.css +++ b/resources/css/web/shared-styles.css @@ -232,6 +232,61 @@ h1, h2, h3, h4, h5, h6 { box-shadow: var(--shadow-accent-glow); } +/* Formularfelder (Terminanfrage-Widget, Kontaktseite). Tokenbasiert, damit sie in jeder + Welt zum Grund passen; Fokus im Akzent der Welt. `.form-select` ergaenzt einen eigenen + Chevron, weil der Browser-Pfeil neben den uebrigen Feldern fremd wirkt. */ +.form-field { + display: block; + width: 100%; + padding: 0.65rem 0.9rem; + font-size: 0.95rem; + line-height: 1.5; + color: hsl(var(--foreground)); + background-color: hsl(var(--card)); + border: 1px solid hsl(var(--border)); + border-radius: 0.6rem; + box-shadow: inset 0 1px 2px hsl(var(--foreground) / 0.04); + transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease; +} + +.form-field::placeholder { + color: hsl(var(--muted-foreground) / 0.7); +} + +.form-field:hover { + border-color: hsl(var(--primary) / 0.45); +} + +.form-field:focus, +.form-field:focus-visible { + outline: none; + border-color: hsl(var(--secondary)); + box-shadow: 0 0 0 3px hsl(var(--secondary) / 0.22); + background-color: hsl(var(--background)); +} + +.form-select { + appearance: none; + -webkit-appearance: none; + padding-right: 2.6rem; + cursor: pointer; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: right 0.9rem center; + background-size: 1.1rem 1.1rem; +} + +.form-select optgroup { + font-weight: 600; + font-style: normal; + color: hsl(var(--primary)); +} + +.form-select option { + font-weight: 400; + color: hsl(var(--foreground)); +} + /* Card styles */ .card-elevated { background-color: hsl(var(--card)); @@ -258,17 +313,18 @@ h1, h2, h3, h4, h5, h6 { color: hsl(var(--dark-text)); } -/* Abschlussband der Markenseiten. - Ein durchgehender Verlauf ueber die volle Breite: von Anthrazit in das - Dynamic Blue. Bewusst ohne weiche Kreise – die setzten sichtbare Flecken in - die Ecken. Die Tiefe kommt aus dem Verlauf selbst, die Kontur aus zwei - Haarlinien. */ +/* Abschlussband der Marken- und Startseite. + Ein durchgehender Verlauf ueber die volle Breite: vom dunklen Hauptton in den + Akzent. Bewusst ohne weiche Kreise – die setzten sichtbare Flecken in die + Ecken. Die Tiefe kommt aus dem Verlauf selbst, die Kontur aus zwei Haarlinien. + Wo das Band ausläuft, sagt `--cta-band-end` (Interior: tiefes Ocker, + Immobilien: Dynamic Blue); Themes ohne den Token nutzen `--secondary-dark`. */ .cta-band { --cta-band-gradient: linear-gradient( 100deg, hsl(var(--primary-dark)) 0%, hsl(var(--primary)) 42%, - hsl(var(--secondary-dark)) 100% + hsl(var(--cta-band-end, var(--secondary-dark))) 100% ); position: relative; @@ -276,7 +332,7 @@ h1, h2, h3, h4, h5, h6 { background: var(--cta-band-gradient); } -/* Lichtkante oben, die zu beiden Seiten auslaeuft. */ +/* Lichtkante oben, die zu beiden Seiten auslaeuft (Interior: in Ocker, sonst weiss). */ .cta-band::before { content: ''; position: absolute; @@ -287,11 +343,23 @@ h1, h2, h3, h4, h5, h6 { background: linear-gradient( 90deg, transparent 0%, - hsl(0 0% 100% / 0.35) 50%, + hsl(var(--cta-band-line, 0 0% 100%) / 0.6) 50%, transparent 100% ); } +/* Hervorgehobene Woerter im Titel des Bands. Die Inhalte tragen `text-primary` + (dunkler Hauptton) – auf dem dunklen Band unlesbar, deshalb hier der Akzent. */ +.cta-band .text-primary { + color: hsl(var(--secondary)); +} + +/* Umgekehrt auf Flaechen im Akzentton (z. B. „Lernen Sie unsere Marken kennen" auf /marken): + ein `text-secondary`-Wort waere Ocker auf Ocker. Dort traegt es den Hauptton. */ +section.bg-secondary .text-secondary { + color: hsl(var(--primary)); +} + /* Sehr flacher Schimmer von oben links, damit die Flaeche nicht flach wirkt. */ .cta-band::after { content: ''; @@ -309,6 +377,30 @@ h1, h2, h3, h4, h5, h6 { color: hsl(var(--dark-muted)); } +/* Footer. Eigene Tokens, weil der Footer nicht in jeder Welt dunkel ist: + Interior legt ihn auf Sand mit Tanne, Immobilien auf Anthrazit mit Weiss. + Themes ohne Footer-Tokens fallen auf die dunkle Sektion zurueck. */ +.section-footer { + background-color: hsl(var(--footer-bg, var(--dark-bg))); + color: hsl(var(--footer-fg, var(--dark-text))); +} + +.text-footer-muted { + color: hsl(var(--footer-muted, var(--dark-muted))); +} + +.border-footer-line\/30 { + border-color: hsl(var(--footer-line, var(--dark-muted)) / 0.3); +} + +.text-clay { + color: hsl(var(--clay, var(--muted-foreground))); +} + +.hover-bg-primary-light:hover { + background-color: hsl(var(--primary-light)); +} + /* Container styles */ .container-padding { max-width: 80rem; @@ -726,9 +818,9 @@ h1, h2, h3, h4, h5, h6 { } } -/* Shadow Effects - Updated for new primary color */ +/* Schatten und Glow leiten sich aus dem Akzent der jeweiligen Welt ab. */ .shadow-elegant { - box-shadow: 0 10px 30px -10px rgba(0, 136, 204, 0.2); + box-shadow: 0 10px 30px -10px hsl(var(--secondary) / 0.2); } .shadow-elegant-white { @@ -742,34 +834,34 @@ h1, h2, h3, h4, h5, h6 { .elegant-shadow-card { box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.08), - 0 4px 10px 0px rgba(0, 136, 204,1); + 0 4px 10px 0px hsl(var(--secondary)); } .shadow-premium { - box-shadow: 0 20px 60px -15px rgba(0, 136, 204, 0.3); + box-shadow: 0 20px 60px -15px hsl(var(--secondary) / 0.3); } /* Glow Effects - Inspired by Möbius band's luminous edge */ .glow-soft { box-shadow: - 0 0 10px rgba(0, 155, 221, 0.15), - 0 0 20px rgba(0, 155, 221, 0.1), - 0 4px 10px -4px rgba(0, 136, 204, 0.2); + 0 0 10px hsl(var(--secondary) / 0.15), + 0 0 20px hsl(var(--secondary) / 0.1), + 0 4px 10px -4px hsl(var(--secondary) / 0.2); } .glow-medium { box-shadow: - 0 0 15px rgba(0, 155, 221, 0.25), - 0 0 30px rgba(0, 155, 221, 0.15), - 0 5px 10px -5px rgba(0, 136, 204, 0.3); + 0 0 15px hsl(var(--secondary) / 0.25), + 0 0 30px hsl(var(--secondary) / 0.15), + 0 5px 10px -5px hsl(var(--secondary) / 0.3); } .glow-strong { box-shadow: - 0 0 30px rgba(0, 155, 221, 0.35), - 0 0 60px rgba(0, 155, 221, 0.2), - 0 0 90px rgba(0, 113, 168, 0.1), - 0 10px 10px -10px rgba(0, 136, 204, 0.4); + 0 0 30px hsl(var(--secondary) / 0.35), + 0 0 60px hsl(var(--secondary) / 0.2), + 0 0 90px hsl(var(--secondary-dark) / 0.1), + 0 10px 10px -10px hsl(var(--secondary) / 0.4); } .icon-secondary-linear { background-color: hsl(var(--secondary) / 0.1); @@ -836,9 +928,9 @@ h1, h2, h3, h4, h5, h6 { background: linear-gradient( 145deg, - hsl(0 0% 100%) 0%, - hsl(0 0% 99.5%) 40%, - hsl(0 0% 99%) 100% + hsl(var(--card)) 0%, + hsl(var(--card)) 40%, + hsl(var(--card) / 0.92) 100% ); box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.03), @@ -904,7 +996,7 @@ h1, h2, h3, h4, h5, h6 { 0 4px 8px rgba(0, 0, 0, 0.08), 0 8px 16px rgba(0, 0, 0, 0.1), 0 16px 32px rgba(0, 0, 0, 0.08), - 0 0 40px hsla(199, 74%, 49%, 0.3); + 0 0 40px hsl(var(--secondary) / 0.3); transform: translateY(-2px); filter: brightness(1.08); } @@ -925,3 +1017,67 @@ h1, h2, h3, h4, h5, h6 { transition: none; } } + +/* Herkunfts-Sektion: schematische Standortkarte (Bielefeld als Drehscheibe) */ +.origin-map { + width: 100%; + height: auto; + font-family: inherit; + overflow: visible; +} + +.origin-map-region { + fill: hsl(var(--secondary) / 0.06); + stroke: hsl(var(--secondary) / 0.45); + stroke-width: 1.5; + stroke-dasharray: 4 6; +} + +.origin-map-region-label { + fill: hsl(var(--secondary-deep, var(--secondary))); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.origin-map-spoke { + stroke: hsl(var(--secondary)); + stroke-width: 2; + stroke-dasharray: 6 6; + opacity: 0.7; +} + +.origin-map-hub { + fill: hsl(var(--secondary)); +} + +.origin-map-hub-ring { + fill: hsl(var(--secondary) / 0.18); +} + +.origin-map-dot { + fill: hsl(var(--primary)); + stroke: hsl(var(--card)); + stroke-width: 3; +} + +.origin-map-label { + fill: hsl(var(--foreground)); + font-size: 15px; + font-weight: 500; +} + +.origin-map-sublabel { + fill: hsl(var(--muted-foreground)); + font-size: 12px; +} + +.origin-map-distance { + fill: hsl(var(--muted-foreground)); + font-size: 11px; + text-anchor: middle; + stroke: hsl(var(--card)); + stroke-width: 5px; + paint-order: stroke; +} diff --git a/resources/css/web/theme-b2in.css b/resources/css/web/theme-b2in.css index 7cd27735..891f02f7 100644 --- a/resources/css/web/theme-b2in.css +++ b/resources/css/web/theme-b2in.css @@ -1,76 +1,176 @@ @import "tailwindcss"; -/* B2IN Domain Theme - Anthracite & Dynamic Blue */ +/* B2in – zwei Farbwelten auf einem Themensystem (Arbeitsplan September 2026, Punkt 1.1). + + Standard ist die Interior-Welt: Tanne, Creme, Sand, Ocker, Clay, Anthrazit. + Die Immobilien-Welt behält das bestehende Blau; sie greift, sobald das Layout + `world-immobilien` an den Body setzt (siehe b2in_world() in app/helpers.php). + Welche Logo-Variante dazu erscheint, entscheidet ThemeHelper::getLogoPath(). + + Alle Werte sind HSL-Tripel ohne Funktionsaufruf, damit `hsl(var(--x) / 0.5)` + in shared-styles.css und in den Tailwind-Utilities funktioniert. */ @layer base { :root { - /* Base colors */ - --background: 32 20% 97%; /* #f5f4f2 - Light Beige */ - --foreground: 20 14% 16%; /* #2a2a2a - Dark Gray */ + /* ── Interior-Welt ─────────────────────────────────────────────── */ - /* Card colors */ - --card: 0 25% 96%; /* #hsl(0 25% 96%) - Off White */ - --card-foreground: 20 14% 16%; /* #2a2a2a - Dark Gray */ + /* Grundflächen */ + --background: 38 33% 95%; /* #f7f4ef Creme – Hintergrund */ + --foreground: 26 8% 16%; /* #2d2926 Anthrazit – Text */ - --popover: 0 25% 96%; /* #hsl(0 25% 96%)- Off White */ - --popover-foreground: 20 14% 16%; /* #2a2a2a - Dark Gray */ + /* Karten eine Stufe heller als Creme, damit sie sich vom Grund abheben */ + --card: 40 43% 98%; /* #fbf9f5 */ + --card-foreground: 26 8% 16%; + --popover: 40 43% 98%; + --popover-foreground: 26 8% 16%; - /* B2IN Theme Colors */ - --primary: 209 32% 25%; /* #2b3f51 - Anthracite */ - --primary-light: 209 32% 35%; /* #2b3f51 - Anthracite */ - --primary-lighter: 209 32% 85%; /* #2b3f51 - Anthracite */ - --primary-dark: 209 32% 20%; /* #2b3f51 - Anthracite */ - --primary-foreground: 0 25% 96%; /* #hsl(0 25% 96%) - Off White */ + /* Hauptton dunkel: Wortmarke, Buttons, dunkle Sektionen */ + --primary: 175 16% 15%; /* #202c2b Tanne */ + --primary-light: 175 13% 22%; + --primary-lighter: 175 8% 82%; + --primary-dark: 175 18% 11%; + --primary-foreground: 38 33% 95%; /* Creme auf Tanne */ - --secondary: 199 74% 49%; /* #20a0da - Dynamic Blue */ - --secondary-light: 199 74% 56%; /* #20a0da - Dynamic Blue */ - --secondary-lighter: 199 74% 75%; /* #20a0da - Dynamic Blue */ - --secondary-dark: 199 74% 42%; /* #20a0da - Dynamic Blue */ - --secondary-foreground: 0 25% 96%; /* hsl(0 25% 96%) - Off White */ + /* Akzent: Logo-Bogen, Hervorhebungen, Icons, aktive Navigation */ + --secondary: 37 62% 55%; /* #d39e46 Ocker */ + --secondary-light: 37 66% 63%; + --secondary-lighter: 37 60% 80%; + --secondary-dark: 37 55% 45%; + --secondary-deep: 37 50% 36%; /* Ocker als Textfarbe auf hellem Grund */ + --cta-band-line: 37 62% 55%; /* Haarlinie am Abschlussband in Ocker */ + --secondary-foreground: 38 33% 95%; - /* Neutral colors */ - --muted: 0 0% 92%; /* hsl(0 0% 92%) - Light Muted */ - --muted-foreground: 199 50% 35%; /* #4a8bb5 - Muted Blue */ + /* Sekundärton für Produkte und ruhige Beschriftungen */ + --clay: 24 15% 57%; /* #a38f82 Clay */ - --accent: 210 0% 90%; /* hsl(210 5.26% 92.55%) - Light Accent */ - --accent-dark: 210 0% 85%; /* hsl(210 5.26% 92.55%) - Light Accent */ - --accent-foreground: 20 14% 16%; /* #2a2a2a - Dark Gray */ + /* Ruhige Flächen und Fließtext in gedämpfter Farbe */ + --muted: 30 24% 90%; /* helles Sand */ + --muted-foreground: 24 12% 40%; /* abgedunkelter Clay – lesbar auf Creme */ - /* Dark section colors */ - --dark-bg: 209 32% 25%; /* Matching primary */ - --dark-text: 0 0% 100%; /* hsl(0 0% 100%) - Off White */ - --dark-muted: 0 0% 80%; /* hsl(0 0% 80%) - Dark Muted */ + /* Sektionswechsel: Sand als Fläche */ + --accent: 28 25% 80%; /* #d9cbbf Sand */ + --accent-dark: 28 25% 74%; + --accent-foreground: 26 8% 16%; - /* Interactive elements */ - --destructive: 0 84.2% 60.2%; /* #ef4444 - Red */ - --destructive-foreground: 210 40% 98%; /* #f8fafc - Light */ + /* Dunkle Sektionen */ + --dark-bg: 175 16% 15%; /* Tanne */ + --dark-text: 38 33% 95%; + --dark-muted: 38 20% 76%; - --border: 0 0% 80%; /* hsl(0 0% 80%) - Light Border */ - --input: 0 0% 92%; /* hsl(0 0% 92%) - Input Background */ - --ring: 20 14% 16%; /* #2a2a2a - Focus Ring */ + /* Footer: Sand mit Tanne (laut Farbwelt „Sand – Flächen, Footer“) */ + --footer-bg: 28 25% 80%; + --footer-fg: 175 16% 15%; + --footer-muted: 24 12% 40%; + --footer-line: 175 16% 15%; + + /* Abschlussband: Ton in Ton von dunklem Tannengrün in ein helleres, kein Ocker im + Verlauf – Grün zu Ocker wirkte unharmonisch. Ocker bleibt Akzent für Wörter und Linien. */ + --cta-band-end: 175 12% 26%; + + /* Interaktion */ + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + + --border: 28 20% 84%; + --input: 40 40% 98%; + --ring: 175 16% 15%; --radius: 0.75rem; - /* Hero container background */ - --hero-container: 0 0% 93%; /* #e8e8e8 - Light Gray */ - --hero-container-dark: 0 0% 85%; /* #e8e8e8 - Light Gray */ - --hero-container-light: 0 0% 95%; /* #e8e8e8 - Light Gray */ + /* Bühne des Hero-Bereichs */ + --hero-container: 30 28% 89%; + --hero-container-dark: 28 25% 83%; + --hero-container-light: 36 32% 93%; - /* Consistent shadows */ + /* Schatten */ --shadow-warm: 0 10px 30px -15px hsl(var(--foreground) / 0.1); --shadow-card: 0 4px 20px -8px hsl(var(--foreground) / 0.08); --shadow-elevated: 0 20px 40px -20px hsl(var(--foreground) / 0.15); --shadow-accent-glow: 0 0 30px hsl(var(--secondary) / 0.3); - /* Transitions */ + /* Übergänge */ --transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); --transition-bounce: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55); - /* Font families */ + /* Schriften: Inter für Fließtext, eine Serife für Überschriften – der Ton der + Shop-Entwürfe. EB Garamond liegt bereits lokal vor (public/fonts/eb-garamond). */ --font-primary: 'Inter', system-ui, -apple-system, sans-serif; + --font-secondary: 'EB Garamond', 'Georgia', 'Times New Roman', serif; + } + + /* ── Immobilien-Welt: das bisherige Blau ───────────────────────── */ + .world-immobilien { + --background: 32 20% 97%; /* #f5f4f2 helles Beige */ + --foreground: 20 14% 16%; /* #2a2a2a */ + + --card: 0 25% 96%; + --card-foreground: 20 14% 16%; + --popover: 0 25% 96%; + --popover-foreground: 20 14% 16%; + + --primary: 209 32% 25%; /* #2b3f51 Anthracite */ + --primary-light: 209 32% 35%; + --primary-lighter: 209 32% 85%; + --primary-dark: 209 32% 20%; + --primary-foreground: 0 25% 96%; + + --secondary: 199 74% 49%; /* #20a0da Dynamic Blue */ + --secondary-light: 199 74% 56%; + --secondary-lighter: 199 74% 75%; + --secondary-dark: 199 74% 42%; + --secondary-deep: 199 74% 36%; + --secondary-foreground: 0 25% 96%; + + --clay: 199 50% 35%; + + --muted: 0 0% 92%; + --muted-foreground: 199 50% 35%; /* #4a8bb5 */ + + --accent: 210 0% 90%; + --accent-dark: 210 0% 85%; + --accent-foreground: 20 14% 16%; + + --dark-bg: 209 32% 25%; + --dark-text: 0 0% 100%; + --dark-muted: 0 0% 80%; + + --footer-bg: 209 32% 25%; + --footer-fg: 0 0% 100%; + --footer-muted: 0 0% 80%; + --footer-line: 0 0% 80%; + + --cta-band-end: 199 74% 42%; + + --border: 0 0% 80%; + --input: 0 0% 92%; + --ring: 20 14% 16%; + + --hero-container: 0 0% 93%; + --hero-container-dark: 0 0% 85%; + --hero-container-light: 0 0% 95%; + --font-secondary: 'IBM Plex Sans', 'Inter', system-ui, -apple-system, sans-serif; } } +/* Interior: Ocker ist Akzent, kein Flächenton unter Text – Creme auf Ocker erreicht + keinen ausreichenden Kontrast. Der zweite Hero-Button wird deshalb zur Kontur in + Tanne, und Ocker-Text auf Sand (Footer) nutzt die tiefe Stufe. */ +.world-interior .btn-secondary-accent { + background: transparent !important; + color: hsl(var(--primary)); + border: 1px solid hsl(var(--primary) / 0.55); + box-shadow: none; +} + +.world-interior .btn-secondary-accent:hover { + background: hsl(var(--primary)) !important; + color: hsl(var(--primary-foreground)); + border-color: hsl(var(--primary)); +} + +.world-interior .section-footer .text-secondary { + color: hsl(var(--secondary-deep)); +} @import "./shared-styles.css"; diff --git a/resources/js/app.js b/resources/js/app.js index 88d489ef..bfb3b50a 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -75,6 +75,22 @@ globalObserver.observe(el); }); + // Terminanfrage: Links auf die Kontaktseite mit data-open-appointment oeffnen das + // schwebende Widget statt die Seite zu wechseln. Das Anliegen kommt aus + // data-appointment-topic (z. B. der Marken-Slug). Nur einmal registrieren. + if (!window.appointmentLinksInitialized) { + document.addEventListener('click', function(e) { + const trigger = e.target.closest('a[data-open-appointment]'); + if (!trigger) return; + + e.preventDefault(); + window.dispatchEvent(new CustomEvent('open-appointment', { + detail: { topic: trigger.dataset.appointmentTopic || null } + })); + }); + window.appointmentLinksInitialized = true; + } + // Smooth Scroll für Anchor-Links (nur einmal registrieren) if (!window.smoothScrollInitialized) { document.addEventListener('click', function(e) { diff --git a/resources/lang/de/b2in.php b/resources/lang/de/b2in.php index 85c4fadf..cf65503f 100644 --- a/resources/lang/de/b2in.php +++ b/resources/lang/de/b2in.php @@ -11,6 +11,15 @@ return [ 'link_text' => 'Termin anfragen', 'link_url' => '/contact', ], + // Ankündigungsleiste der Immobilien-Welt – eigenes Thema, eigener Dismiss-Schlüssel. + 'announcement_bar_immobilien' => [ + 'enabled' => true, + 'id' => 'azizi-creek-views-4-2026', + 'badge' => 'NEU', + 'text' => 'Azizi Creek Views 4 – Off-Market-Projekt in Dubai ab 1.125.000 AED', + 'link_text' => 'Exposé ansehen', + 'link_url' => '/immobilien/azizi-creek-views-4', + ], 'appointment_widget' => [ 'button_label' => 'Termin anfragen', 'title' => 'Termin anfragen', @@ -56,10 +65,20 @@ return [ ['label' => 'Marken', 'url' => '/marken'], ['label' => 'Referenzen', 'url' => '/referenzen'], ['label' => 'Magazin', 'url' => '/magazin'], - ['label' => 'Immobilien', 'url' => '/immobilien'], ['label' => 'Über B2in', 'url' => '/about'], ], ], + // Navigation der Immobilien-Welt. „Immobilien“ steht nicht in der Interior-Navigation + // und umgekehrt – der Welten-Wechsler in Kopf- und Fußzeile ist die einzige Brücke. + 'header_immobilien' => [ + 'portal_login' => 'Partner-Login', + 'navigation' => [ + ['label' => 'Immobilien', 'url' => '/immobilien'], + ['label' => 'Projekte', 'url' => '/immobilien#projekte'], + ['label' => 'Magazin', 'url' => '/immobilien/magazin'], + ['label' => 'Über B2in', 'url' => '/immobilien/ueber-b2in'], + ], + ], 'hero' => [ 'title' => 'B2in Interior – Premium-Einrichtung von starken Marken. Für Ihr Zuhause. Für Ihr Projekt.', 'subtitle' => 'Von der einzelnen Maßanfertigung bis zur vollständigen Ausstattung – mit CABINET, Rotpunkt Küchen, Febrü und einem Netzwerk regionaler Fachexperten.', @@ -218,6 +237,92 @@ return [ 'logo' => '', 'link_text' => 'Mehr zu Febrü', ], + [ + '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.', + 'image' => '', + 'logo' => '', + 'link_text' => 'Mehr zur Tischlerei', + ], + ], + ], + // Herkunfts-Sektion (Arbeitsplan 14.09., Punkt 1.3). Koordinaten sind Positionen in der + // schematischen Karte (viewBox 640×400), keine Geografie. Entfernungen gerundet, Straße ab + // Bielefeld Mitte. ⚠️ Ablauf CABINET (steps) nach Kevins Verständnis – von Marcel bestätigen lassen. + 'origin_section' => [ + 'title' => 'Aus der Region gefertigt', + 'subtitle' => 'Vier deutsche Manufakturen, drei davon in Ostwestfalen. Kurze Wege, Menschen, die wir kennen, und Qualität, die man besuchen kann.', + 'map_alt' => 'Schematische Karte: Bielefeld als Ausgangspunkt, Fertigung in Bünde, Herford, Rheda-Wiedenbrück und Köln', + 'region_label' => 'Ostwestfalen', + 'distance_suffix' => 'von Bielefeld', + 'hub' => [ + 'city' => 'Bielefeld', + 'role' => 'B2in Interior · Beratung, Planung, Showrooms', + 'x' => 400, + 'y' => 175, + ], + 'locations' => [ + [ + 'city' => 'Bünde', + 'brand' => 'Rotpunkt Küchen', + 'role' => 'Küchen aus eigener Fertigung in Bünde – geplant bei uns im Küchenstudio.', + 'distance' => 'rund 30 km', + 'brand_slug' => 'rotpunkt', + 'link_text' => 'Zur Marke', + 'x' => 330, + 'y' => 55, + 'label' => 'left', + ], + [ + 'city' => 'Herford', + 'brand' => 'Febrü', + 'role' => 'Büro- und Objektmöbel aus eigener Fertigung in Herford.', + 'distance' => 'rund 20 km', + 'brand_slug' => 'februe', + 'link_text' => 'Zur Marke', + 'x' => 480, + 'y' => 90, + 'label' => 'right', + ], + [ + 'city' => 'Rheda-Wiedenbrück', + 'brand' => 'Tischlerei', + 'role' => 'Unsere Werkstatt für Aufbau, Sonderlösungen und Veredelung.', + 'distance' => 'rund 35 km', + 'brand_slug' => 'tischlerei', + 'link_text' => 'Zur Marke', + 'x' => 295, + 'y' => 285, + 'label' => 'left', + ], + [ + 'city' => 'Köln', + 'brand' => 'CABINET', + 'role' => 'Gefertigt in der Kölner Manufaktur, beraten und geplant im CABINET Store Bielefeld.', + 'distance' => 'rund 200 km', + 'brand_slug' => 'cabinet', + 'link_text' => 'Zur Marke', + 'x' => 80, + 'y' => 345, + 'label' => 'right', + ], + ], + 'steps_title' => 'Gefertigt in Köln, geplant in Bielefeld', + 'steps_text' => 'Am Beispiel CABINET: Die Möbel entstehen in der Kölner Fertigung. Beraten, aufgemessen und geplant wird bei uns im CABINET Store Bielefeld. Aufgebaut wird vor Ort – mit unserer Tischlerei, die auf Wunsch auch das umsetzt, was im Katalog nicht steht.', + 'steps' => [ + [ + 'title' => 'Beratung und Planung in Bielefeld', + 'description' => 'Im CABINET Store nehmen wir Maß, planen bis ins Detail und stimmen jede Front und jede Innenaufteilung mit Ihnen ab.', + ], + [ + 'title' => 'Fertigung in Köln', + 'description' => 'CABINET fertigt Ihren Schrank in der eigenen Manufaktur – nach den Maßen aus der Planung.', + ], + [ + '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.', + ], ], ], 'sponsoring' => [ @@ -297,6 +402,13 @@ return [ 'button_text' => 'Termin anfragen', 'button_link' => '/contact', ], + // Abschluss der Immobilien-Welt – fuehrt zur Kontaktseite dieser Welt. + 'cta_section_immobilien' => [ + 'title' => 'Ihr nächster Schritt', + 'subtitle' => 'Ob Exposé, Kaufprozess oder Einrichtung nach dem Kauf – sprechen Sie mit uns. Persönlich, unkompliziert, verbindlich.', + 'button_text' => 'Gespräch anfragen', + 'button_link' => '/immobilien/kontakt', + ], 'immobilien_hero' => [ 'title' => 'Investieren Sie in die Zukunft – Dubai, Lissabon & mehr.', 'subtitle' => 'Exklusive Off-Market-Projekte und High-Yield-Investments. Persönlich kuratiert und begleitet von Marcel Scheibe.', @@ -730,6 +842,135 @@ return [ ], ], ], + // ── Immobilien-Welt: Magazin, Über uns und FAQ ────────────────────────────── + // Inhalte aus der Zeit vor dem Interior-Relaunch (Stand Februar 2026), leicht + // angepasst und als Immobilien-Bereich gekennzeichnet. + 'magazin_list_immobilien' => [ + 'title' => 'B2in Immobilien-Magazin', + 'subtitle' => 'Marktanalysen und Praxiswissen rund um Immobilien-Investments in Dubai – Kaufprozess, Stadtteile und die Rolle der Einrichtung für die Rendite.', + 'read_more' => 'Weiterlesen', + 'load_more' => 'Weitere Artikel laden', + ], + 'immobilien_about_hero' => [ + 'title' => 'B2in Immobilien: Unsere Mission', + 'quote' => '"Meine Mission ist es, zwei Welten zu verbinden, die zusammengehören: internationale Immobilien und hochwertige Einrichtung. B2in begleitet Investoren persönlich beim Kauf – und richtet die Immobilie anschließend aus einer Hand ein.

Wir bauen Brücken – zwischen europäischem Design, internationalen Märkten und dem Zuhause der Menschen."', + 'founder_name' => 'Marcel Scheibe', + 'founder_title' => 'Gründer & CEO, B2in', + 'image' => 'b2in/about-hero.jpg', + 'image_alt' => 'Marcel Scheibe, Gründer und CEO von B2in', + 'card_title' => 'B2in Immobilien', + 'card_text' => 'Connecting Design and Property', + ], + 'immobilien_our_story' => [ + 'title' => 'Unsere Geschichte', + 'timeline' => [ + [ + 'title' => 'Die Idee', + 'description' => '2024 erkannten wir eine entscheidende Lücke: Immobilienkäufer suchen nach nahtlosen Einrichtungslösungen, und der Fachhandel vor Ort braucht Sichtbarkeit. Diese beiden Welten gehören zusammen.', + 'icon' => 'light-bulb', + ], + [ + 'title' => 'Das Fundament', + 'description' => 'Wir bauen B2in als Netzwerk auf: Einrichtungsexperten aus der Region, europäische Hersteller und Immobilienprofis – fair, transparent und mit einem persönlichen Ansprechpartner.', + 'icon' => 'rocket-launch', + ], + [ + 'title' => 'Die Erweiterung', + 'description' => '2025/2026 kommen internationale Immobilien dazu: ausgewählte Projekte in Dubai, aus eigener Investorensicht geprüft und persönlich begleitet. Die Einrichtung bleibt der Kern – beide Bereiche ergänzen sich.', + 'icon' => 'globe-alt', + ], + [ + 'title' => 'Die Vision', + 'description' => 'B2in verbindet Design und Immobilien – regional verwurzelt, international vernetzt. In jeder Region der verlässliche Partner für Immobilien-Investments und exklusive Einrichtungskonzepte.', + 'icon' => 'star', + ], + ], + 'summary' => 'Was als Vision begann, den regionalen Handel zu stärken, ist heute ein Netzwerk, das Immobilien und Einrichtung verbindet. B2in schließt die Lücke zwischen internationalen Investments und lokaler Expertise – mit Marcel Scheibe als Gesicht und Ansprechpartner.', + ], + 'immobilien_about_image_break' => [ + 'image' => 'b2in/best-of-two-worlds.jpg', + 'image_alt' => 'B2in – Immobilien und Einrichtung verbinden', + 'quote' => 'Regional verwurzelt, international vernetzt – das ist B2in.', + 'author' => 'Marcel Scheibe', + ], + 'immobilien_our_values' => [ + 'title' => 'Unsere Werte', + 'subtitle' => 'Diese sechs Grundpfeiler leiten unser tägliches Handeln und definieren, wer wir als Unternehmen sind und wofür wir stehen.', + 'values' => [ + [ + 'title' => 'Persönliche Begleitung', + 'description' => 'Vom ersten Gespräch über den Kaufprozess bis zur eingerichteten Immobilie: ein Ansprechpartner, der die Märkte aus eigener Investorensicht kennt.', + 'icon' => 'light-bulb', + ], + [ + 'title' => 'Konnektivität', + 'description' => 'Wir verbinden internationale Immobilienmärkte mit lokaler Expertise und europäische Manufakturen mit den Menschen, die ihre Produkte schätzen.', + 'icon' => 'globe-alt', + ], + [ + 'title' => 'Qualität', + 'description' => 'Kompromisslose Standards – bei der Auswahl der Projekte, der Prüfung der Entwickler und der Kuration der Einrichtung.', + 'icon' => 'check-badge', + ], + [ + 'title' => 'Vertrauen', + 'description' => 'Transparente Modelle und verlässliche Partnerschaften sind unser Fundament. Hinter B2in steht ein Gesicht – und das Versprechen, dass wir liefern, was wir zusagen.', + 'icon' => 'user-group', + ], + [ + 'title' => 'Verlässlichkeit', + 'description' => 'Deutsche Vertragssicherheit trifft auf die Dynamik internationaler Märkte: klare Abläufe, geprüfte Partner, abgesicherte Zahlungswege.', + 'icon' => 'arrow-path', + ], + [ + 'title' => 'Design-Exzellenz', + 'description' => 'Design ist der Kern unserer Wertschöpfung – von internationaler Architektur bis zur kuratierten Einrichtung, die aus einer Immobilie ein Zuhause macht.', + 'icon' => 'cube-transparent', + ], + ], + ], + // Kontaktseite der Immobilien-Welt (/immobilien/kontakt). Overlay über `contact_form`: + // nur Einstieg und Betreffe weichen ab, Adresse und Beschriftungen werden geerbt. + 'contact_form_immobilien' => [ + 'hero' => [ + 'title' => 'Fragen zu einem
Projekt?', + 'subtitle' => 'Ob Exposé, Kaufprozess oder Einrichtung nach dem Kauf – schreiben Sie uns. Marcel Scheibe meldet sich persönlich bei Ihnen.', + ], + ], + 'faq_immobilien' => [ + 'title' => 'Häufige Fragen zu Immobilien', + 'subtitle' => 'Antworten zu Investments in Dubai, zum Kaufprozess und dazu, wie B2in Sie dabei begleitet.', + 'questions' => [ + [ + 'question' => 'Wie funktioniert das Immobilien-Investment über B2in?', + 'answer' => 'Wir vermitteln nicht nur, wir begleiten Sie. Marcel Scheibe ist Ihr persönlicher Berater, der den Markt in Dubai aus eigener Investorensicht kennt. Nach einer Bedarfsanalyse stellen wir Ihnen ausgewählte Projekte vor, etwa von Azizi Developments. Der Kaufprozess selbst ist durch das staatliche Escrow-System in Dubai abgesichert. Ihr Vorteil als B2in-Kunde: Im Anschluss richten wir Ihre Immobilie schlüsselfertig und renditeorientiert ein.', + ], + [ + 'question' => 'Was macht B2in zu einem vertrauenswürdigen Partner?', + 'answer' => 'Vertrauen entsteht durch Transparenz und eigene Markterfahrung. B2in-Gründer Marcel Scheibe investiert selbst in die Märkte, die wir anbieten, und ist regelmäßig vor Ort, um Baufortschritte zu prüfen. Wir verbinden die Dynamik internationaler Märkte mit deutscher Verlässlichkeit und einem persönlichen Ansprechpartner, der Ihre Interessen vertritt.', + ], + ], + 'sections' => [ + [ + 'title' => 'Für Immobilien-Investoren', + 'icon' => 'home-modern', + 'questions' => [ + [ + 'question' => 'Muss ich für den Immobilienkauf persönlich nach Dubai reisen?', + 'answer' => 'Nein, der gesamte Kaufprozess kann vollständig digital und rechtsverbindlich aus der Ferne abgewickelt werden. Das Dubai Land Department bietet hierfür sichere Prozesse. Wir empfehlen unseren Investoren zwar, sich vor Ort ein Bild von der Stadt zu machen – für den rechtssicheren Erwerb ist Ihre Anwesenheit jedoch nicht erforderlich.', + ], + [ + 'question' => 'Was passiert nach dem Immobilienkauf? Helfen Sie bei der Einrichtung?', + 'answer' => 'Genau hier liegt der Vorteil von B2in. Unser Service endet nicht mit dem Kaufvertrag. Wir unterstützen Sie dabei, Ihre Immobilie schlüsselfertig („Turnkey“) und renditeorientiert mit Qualitätsmöbeln auszustatten – die Grundlage für eine erfolgreiche Vermietung.', + ], + [ + 'question' => 'Welche steuerlichen Vorteile bietet ein Investment in Dubai?', + 'answer' => 'Dubai bietet ein besonderes wirtschaftliches Umfeld: Es fallen weder Einkommensteuer auf Mieteinnahmen noch Kapitalertragsteuer beim Verkauf der Immobilie an. In Kombination mit den Mietrenditen macht das den Standort für internationale Anleger interessant. Bitte lassen Sie Ihre persönliche steuerliche Situation von einem Steuerberater prüfen.', + ], + ], + ], + ], + ], 'faq' => [ 'title' => 'Häufig gestellte Fragen', 'subtitle' => 'Antworten auf die Fragen, die uns am häufigsten gestellt werden – zur Beratung, zur Maßanfertigung und zur Ausstattung von Projekten.', @@ -817,17 +1058,37 @@ return [ 'subject' => 'Betreff *', 'message' => 'Ihre Nachricht *', ], - 'subjects' => [ - '' => 'Wählen Sie einen Betreff', - 'beratung' => 'Beratungstermin', - 'massanfertigung' => 'Maßanfertigung für mein Zuhause', - 'kueche' => 'Küchenplanung', - 'supply_chain' => 'Ausstattung für Projekte', - 'partnership' => 'Zusammenarbeit im Netzwerk', - 'immobilien' => 'Internationale Immobilien', - 'general' => 'Allgemeine Anfrage', - 'press' => 'Presse', - 'career' => 'Karriere', + 'subject_placeholder' => 'Wählen Sie einen Betreff', + // Betreffe in Gruppen. Die Kernmarken aus dem CMS stehen automatisch am + // Anfang der Gruppe „Interior“; die Welt der Seite bestimmt die Reihenfolge. + 'subject_groups' => [ + 'interior' => [ + 'label' => 'Interior', + 'subjects' => [ + 'beratung' => 'Beratungstermin', + 'massanfertigung' => 'Maßanfertigung für mein Zuhause', + 'kueche' => 'Küchenplanung', + 'supply_chain' => 'Ausstattung für Projekte', + ], + ], + 'immobilien' => [ + 'label' => 'Immobilien', + 'subjects' => [ + 'immobilien' => 'Beratung zum Investment', + 'expose' => 'Exposé und Verfügbarkeit', + 'kaufprozess' => 'Fragen zum Kaufprozess', + 'einrichtung' => 'Einrichtung nach dem Kauf', + ], + ], + 'b2in' => [ + 'label' => 'B2in', + 'subjects' => [ + 'partnership' => 'Zusammenarbeit im Netzwerk', + 'general' => 'Allgemeine Anfrage', + 'press' => 'Presse', + 'career' => 'Karriere', + ], + ], ], 'placeholders' => [ 'message' => 'Ihre Nachricht...', @@ -1158,7 +1419,7 @@ return [ ], 'magazin_list' => [ 'title' => 'B2in Magazin', - 'subtitle' => 'Insights, Marktanalysen und Praxiswissen rund um Immobilien-Investments in Dubai, Einrichtungskonzepte und das B2in-Ökosystem.', + 'subtitle' => 'Praxiswissen rund ums Einrichten: Maßanfertigung, Küchenplanung, Marken und der Möbelhandel aus der Region.', 'read_more' => 'Weiterlesen', 'load_more' => 'Weitere Artikel laden', ], @@ -1721,6 +1982,7 @@ return [ 'subtitle' => 'Wie das staatlich regulierte Treuhandsystem in Dubai Investorengelder konsequent schützt – und warum es internationalen Standards weit voraus ist.', 'image' => 'b2in/magazin-1.jpg', 'category' => 'Dubai Investment', + 'world' => 'immobilien', 'date' => 'März 10, 2026', 'readTime' => '6 min read', 'author' => [ @@ -1752,6 +2014,7 @@ return [ 'subtitle' => 'Strategische Waterfront-Location, hohe Mietnachfrage und enormes Potenzial für Wertsteigerung – Al Jaddaf ist der Hidden Champion unter Dubais Vierteln.', 'image' => 'b2in/magazin-2.jpg', 'category' => 'Dubai Investment', + 'world' => 'immobilien', 'date' => 'März 5, 2026', 'readTime' => '7 min read', 'author' => [ @@ -1783,6 +2046,7 @@ return [ 'subtitle' => 'Warum schlüsselfertige Einrichtungskonzepte den Unterschied zwischen durchschnittlicher und Premium-Rendite ausmachen.', 'image' => 'b2in/magazin-3.jpg', 'category' => 'Rendite & Einrichtung', + 'world' => 'immobilien', 'date' => 'Februar 25, 2026', 'readTime' => '6 min read', 'author' => [ diff --git a/resources/lang/de/ui.php b/resources/lang/de/ui.php index 48133a3e..f76fff97 100644 --- a/resources/lang/de/ui.php +++ b/resources/lang/de/ui.php @@ -16,6 +16,11 @@ return [ 'menu_close' => 'Menü schließen', 'menu_open' => 'Menü öffnen', + // Welten-Wechsler (Interior / Immobilien) + 'worlds_label' => 'Bereich wechseln', + 'world_interior' => 'Interior', + 'world_immobilien' => 'Immobilien', + 'display_feed_title' => 'Live-Ansicht des Showroom-Displays', 'references_empty' => 'Die ersten Projekte erscheinen hier, sobald wir sie zeigen dürfen.', @@ -85,6 +90,7 @@ return [ // Contact Form 'contact_form' => [ + 'subject_placeholder' => 'Wählen Sie einen Betreff', 'success_title' => 'Vielen Dank!', 'success_message' => 'Ihre Nachricht ist bei uns eingegangen. Wir melden uns schnellstmöglich bei Ihnen.', 'first_name' => 'Vorname *', diff --git a/resources/lang/en/b2in.php b/resources/lang/en/b2in.php index 4b96dfe9..84f17d9d 100644 --- a/resources/lang/en/b2in.php +++ b/resources/lang/en/b2in.php @@ -11,6 +11,14 @@ return [ 'link_text' => 'Request an appointment', 'link_url' => '/contact', ], + 'announcement_bar_immobilien' => [ + 'enabled' => true, + 'id' => 'azizi-creek-views-4-2026', + 'badge' => 'NEW', + 'text' => 'Azizi Creek Views 4 – Off-Market Project in Dubai from AED 1,125,000', + 'link_text' => 'View Exposé', + 'link_url' => '/immobilien/azizi-creek-views-4', + ], 'appointment_widget' => [ 'button_label' => 'Request an appointment', 'title' => 'Request an appointment', @@ -56,10 +64,18 @@ return [ ['label' => 'Brands', 'url' => '/marken'], ['label' => 'References', 'url' => '/referenzen'], ['label' => 'Magazine', 'url' => '/magazin'], - ['label' => 'Real Estate', 'url' => '/immobilien'], ['label' => 'About B2in', 'url' => '/about'], ], ], + 'header_immobilien' => [ + 'portal_login' => 'Partner Login', + 'navigation' => [ + ['label' => 'Real Estate', 'url' => '/immobilien'], + ['label' => 'Projects', 'url' => '/immobilien#projekte'], + ['label' => 'Magazine', 'url' => '/immobilien/magazin'], + ['label' => 'About B2in', 'url' => '/immobilien/ueber-b2in'], + ], + ], 'hero' => [ 'title' => 'B2in Interior – Premium furnishing from strong brands. For your home. For your project.', 'subtitle' => 'From a single bespoke piece to a complete fit-out – with CABINET, Rotpunkt Küchen, Febrü and a network of regional specialists.', @@ -218,6 +234,89 @@ return [ 'logo' => '', 'link_text' => 'More about Febrü', ], + [ + '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.', + 'image' => '', + 'logo' => '', + 'link_text' => 'More about the joinery', + ], + ], + ], + 'origin_section' => [ + 'title' => 'Made in the region', + 'subtitle' => 'Four German manufactories, three of them in East Westphalia. Short distances, people we know, and quality you can go and see.', + 'map_alt' => 'Schematic map: Bielefeld as the starting point, production in Bünde, Herford, Rheda-Wiedenbrück and Cologne', + 'region_label' => 'East Westphalia', + 'distance_suffix' => 'from Bielefeld', + 'hub' => [ + 'city' => 'Bielefeld', + 'role' => 'B2in Interior · consulting, planning, showrooms', + 'x' => 400, + 'y' => 175, + ], + 'locations' => [ + [ + 'city' => 'Bünde', + 'brand' => 'Rotpunkt Küchen', + 'role' => 'Kitchens from their own production in Bünde – planned with us in the kitchen studio.', + 'distance' => 'about 30 km', + 'brand_slug' => 'rotpunkt', + 'link_text' => 'To the brand', + 'x' => 330, + 'y' => 55, + 'label' => 'left', + ], + [ + 'city' => 'Herford', + 'brand' => 'Febrü', + 'role' => 'Office and contract furniture from their own production in Herford.', + 'distance' => 'about 20 km', + 'brand_slug' => 'februe', + 'link_text' => 'To the brand', + 'x' => 480, + 'y' => 90, + 'label' => 'right', + ], + [ + 'city' => 'Rheda-Wiedenbrück', + 'brand' => 'Joinery', + 'role' => 'Our workshop for assembly, custom solutions and finishing.', + 'distance' => 'about 35 km', + 'brand_slug' => 'tischlerei', + 'link_text' => 'To the brand', + 'x' => 295, + 'y' => 285, + 'label' => 'left', + ], + [ + 'city' => 'Cologne', + 'brand' => 'CABINET', + 'role' => 'Made in the Cologne manufactory, advised and planned at the CABINET Store Bielefeld.', + 'distance' => 'about 200 km', + 'brand_slug' => 'cabinet', + 'link_text' => 'To the brand', + 'x' => 80, + 'y' => 345, + 'label' => 'right', + ], + ], + 'steps_title' => 'Made in Cologne, planned in Bielefeld', + 'steps_text' => 'Take CABINET: the furniture is made in Cologne. Consulting, measuring and planning happen with us at the CABINET Store Bielefeld. Assembly takes place on site – with our joinery, which on request also builds what the catalogue does not offer.', + 'steps' => [ + [ + 'title' => 'Consulting and planning in Bielefeld', + 'description' => 'At the CABINET Store we take measurements, plan down to the last detail and agree every front and interior layout with you.', + ], + [ + 'title' => 'Production in Cologne', + 'description' => 'CABINET builds your wardrobe in its own manufactory – to the measurements from the planning.', + ], + [ + 'title' => 'Assembly on site, special requests included', + 'description' => 'Our joinery assembles and adds what the room needs beyond that: panels, connections, custom solutions.', + ], ], ], 'sponsoring' => [ @@ -295,6 +394,12 @@ return [ 'button_text' => 'Request an appointment', 'button_link' => '/contact', ], + 'cta_section_immobilien' => [ + 'title' => 'Your next step', + 'subtitle' => 'Exposé, purchase process or furnishing after the purchase – talk to us. Personal, straightforward, reliable.', + 'button_text' => 'Request a conversation', + 'button_link' => '/immobilien/kontakt', + ], 'immobilien_hero' => [ 'title' => 'Invest in the Future – Dubai, Lisbon & More.', 'subtitle' => 'Exclusive off-market projects and high-yield investments. Personally curated and guided by Marcel Scheibe.', @@ -728,6 +833,130 @@ return [ ], ], ], + 'magazin_list_immobilien' => [ + 'title' => 'B2in Real Estate Magazine', + 'subtitle' => 'Market analyses and practical knowledge about real estate investments in Dubai – purchase process, districts and the role of interiors for the return.', + 'read_more' => 'Read More', + 'load_more' => 'Load More Articles', + ], + 'immobilien_about_hero' => [ + 'title' => 'B2in Real Estate: Our Mission', + 'quote' => '"My mission is to connect two worlds that belong together: international real estate and high-quality interiors. B2in accompanies investors personally through the purchase – and then furnishes the property from a single source.

We build bridges – between European design, international markets, and people\'s homes."', + 'founder_name' => 'Marcel Scheibe', + 'founder_title' => 'Founder & CEO, B2in', + 'image' => 'b2in/about-hero.jpg', + 'image_alt' => 'Marcel Scheibe, Founder and CEO of B2in', + 'card_title' => 'B2in Real Estate', + 'card_text' => 'Connecting Design and Property', + ], + 'immobilien_our_story' => [ + 'title' => 'Our Story', + 'timeline' => [ + [ + 'title' => 'The Idea', + 'description' => 'In 2024, we identified a crucial gap: property buyers are looking for seamless furnishing solutions, and local retailers need visibility. These two worlds belong together.', + 'icon' => 'light-bulb', + ], + [ + 'title' => 'The Foundation', + 'description' => 'We build B2in as a network: regional interior specialists, European manufacturers and real estate professionals – fair, transparent and with a personal point of contact.', + 'icon' => 'rocket-launch', + ], + [ + 'title' => 'The Expansion', + 'description' => '2025/2026 adds international real estate: selected projects in Dubai, reviewed from an investor\'s perspective and accompanied personally. Interiors remain the core – both areas complement each other.', + 'icon' => 'globe-alt', + ], + [ + 'title' => 'The Vision', + 'description' => 'B2in connects design and property – locally rooted, internationally connected. In every region, the reliable partner for real estate investments and exclusive interior concepts.', + 'icon' => 'star', + ], + ], + 'summary' => 'What began as a vision to strengthen regional trade is today a network that connects real estate and interiors. B2in bridges the gap between international investments and local expertise – with Marcel Scheibe as the face and point of contact.', + ], + 'immobilien_about_image_break' => [ + 'image' => 'b2in/best-of-two-worlds.jpg', + 'image_alt' => 'B2in – Connecting Real Estate and Interior', + 'quote' => 'Locally rooted, internationally connected – that is B2in.', + 'author' => 'Marcel Scheibe', + ], + 'immobilien_our_values' => [ + 'title' => 'Our Values', + 'subtitle' => 'These six pillars guide our daily actions and define who we are as a company and what we stand for.', + 'values' => [ + [ + 'title' => 'Personal Guidance', + 'description' => 'From the first conversation through the purchase process to the furnished property: one point of contact who knows the markets from an investor\'s perspective.', + 'icon' => 'light-bulb', + ], + [ + 'title' => 'Connectivity', + 'description' => 'We connect international real estate markets with local expertise and European manufacturers with the people who value their products.', + 'icon' => 'globe-alt', + ], + [ + 'title' => 'Quality', + 'description' => 'Uncompromising standards – in the selection of projects, the review of developers and the curation of interiors.', + 'icon' => 'check-badge', + ], + [ + 'title' => 'Trust', + 'description' => 'Transparent models and reliable partnerships are our foundation. Behind B2in stands a face – and the promise that we deliver what we commit to.', + 'icon' => 'user-group', + ], + [ + 'title' => 'Reliability', + 'description' => 'German contractual certainty meets the dynamics of international markets: clear processes, vetted partners, secured payment routes.', + 'icon' => 'arrow-path', + ], + [ + 'title' => 'Design Excellence', + 'description' => 'Design is the core of our value creation – from international architecture to curated interiors that turn a property into a home.', + 'icon' => 'cube-transparent', + ], + ], + ], + 'contact_form_immobilien' => [ + 'hero' => [ + 'title' => 'Questions about a
project?', + 'subtitle' => 'Exposé, purchase process or furnishing after the purchase – write to us. Marcel Scheibe will get back to you personally.', + ], + ], + 'faq_immobilien' => [ + 'title' => 'Frequently Asked Questions about Real Estate', + 'subtitle' => 'Answers about investments in Dubai, the purchase process and how B2in accompanies you.', + 'questions' => [ + [ + 'question' => 'How does real estate investment through B2in work?', + 'answer' => 'We do not just broker, we accompany you. Marcel Scheibe is your personal advisor who knows the Dubai market from his own investor perspective. After a needs analysis, we present selected projects, for example by Azizi Developments. The purchase process itself is secured by the government escrow system in Dubai. Your advantage as a B2in client: afterwards we furnish your property turnkey and with the return in mind.', + ], + [ + 'question' => 'What makes B2in a trustworthy partner?', + 'answer' => 'Trust comes from transparency and first-hand market experience. B2in founder Marcel Scheibe invests in the markets we offer himself and is regularly on site to check construction progress. We combine the dynamics of international markets with German reliability and a personal contact who represents your interests.', + ], + ], + 'sections' => [ + [ + 'title' => 'For Real Estate Investors', + 'icon' => 'home-modern', + 'questions' => [ + [ + 'question' => 'Do I need to travel to Dubai personally for the property purchase?', + 'answer' => 'No, the entire purchase process can be completed digitally and legally binding from a distance. The Dubai Land Department provides secure processes for this. We do recommend that investors get a picture of the city on site – but your presence is not required for a legally secure purchase.', + ], + [ + 'question' => 'What happens after the property purchase? Do you help with furnishing?', + 'answer' => 'This is exactly where the B2in advantage lies. Our service does not end with the purchase contract. We help you furnish your property turnkey and with the return in mind using quality furniture – the basis for a successful rental.', + ], + [ + 'question' => 'What tax advantages does an investment in Dubai offer?', + 'answer' => 'Dubai offers a special economic environment: there is neither income tax on rental income nor capital gains tax on the sale of the property. Combined with the rental yields, this makes the location attractive for international investors. Please have your personal tax situation checked by a tax advisor.', + ], + ], + ], + ], + ], 'faq' => [ 'title' => 'Frequently Asked Questions', 'subtitle' => 'Answers to the questions we are asked most often – about advice, bespoke furniture and fitting out projects.', @@ -815,17 +1044,35 @@ return [ 'subject' => 'Subject *', 'message' => 'Your Message *', ], - 'subjects' => [ - '' => 'Select a subject', - 'beratung' => 'Consultation appointment', - 'massanfertigung' => 'Bespoke furniture for my home', - 'kueche' => 'Kitchen planning', - 'supply_chain' => 'Fitting out a project', - 'partnership' => 'Working together in the network', - 'immobilien' => 'International Real Estate', - 'general' => 'General Inquiry', - 'press' => 'Press', - 'career' => 'Career', + 'subject_placeholder' => 'Select a subject', + 'subject_groups' => [ + 'interior' => [ + 'label' => 'Interior', + 'subjects' => [ + 'beratung' => 'Consultation appointment', + 'massanfertigung' => 'Bespoke furniture for my home', + 'kueche' => 'Kitchen planning', + 'supply_chain' => 'Fitting out a project', + ], + ], + 'immobilien' => [ + 'label' => 'Real Estate', + 'subjects' => [ + 'immobilien' => 'Investment consultation', + 'expose' => 'Exposé and availability', + 'kaufprozess' => 'Questions about the purchase process', + 'einrichtung' => 'Furnishing after the purchase', + ], + ], + 'b2in' => [ + 'label' => 'B2in', + 'subjects' => [ + 'partnership' => 'Working together in the network', + 'general' => 'General Inquiry', + 'press' => 'Press', + 'career' => 'Career', + ], + ], ], 'placeholders' => [ 'message' => 'Your message...', @@ -1156,7 +1403,7 @@ return [ ], 'magazin_list' => [ 'title' => 'B2in Magazine', - 'subtitle' => 'Insights, market analyses, and practical knowledge about real estate investments in Dubai, interior concepts, and the B2in ecosystem.', + 'subtitle' => 'Practical knowledge about interiors: custom-made furniture, kitchen planning, brands and the regional furniture trade.', 'read_more' => 'Read More', 'load_more' => 'Load More Articles', ], @@ -1712,6 +1959,7 @@ return [ 'subtitle' => 'How the government-regulated trust system in Dubai consistently protects investor funds – and why it is far ahead of international standards.', 'image' => 'b2in/magazin-1.jpg', 'category' => 'Dubai Investment', + 'world' => 'immobilien', 'date' => 'March 10, 2026', 'readTime' => '6 min read', 'author' => [ @@ -1743,6 +1991,7 @@ return [ 'subtitle' => 'Strategic waterfront location, high rental demand, and enormous potential for capital appreciation – Al Jaddaf is the hidden champion among Dubai\'s districts.', 'image' => 'b2in/magazin-2.jpg', 'category' => 'Dubai Investment', + 'world' => 'immobilien', 'date' => 'March 5, 2026', 'readTime' => '7 min read', 'author' => [ @@ -1774,6 +2023,7 @@ return [ 'subtitle' => 'Why turnkey interior concepts make the difference between average and premium returns.', 'image' => 'b2in/magazin-3.jpg', 'category' => 'Returns & Interior', + 'world' => 'immobilien', 'date' => 'February 25, 2026', 'readTime' => '6 min read', 'author' => [ diff --git a/resources/lang/en/ui.php b/resources/lang/en/ui.php index 3b6e04cc..4de6b892 100644 --- a/resources/lang/en/ui.php +++ b/resources/lang/en/ui.php @@ -16,6 +16,11 @@ return [ 'menu_close' => 'Close menu', 'menu_open' => 'Open menu', + // World switcher (Interior / Real estate) + 'worlds_label' => 'Switch area', + 'world_interior' => 'Interior', + 'world_immobilien' => 'Real estate', + 'display_feed_title' => 'Live view of the showroom display', 'references_empty' => 'The first projects will appear here as soon as we are allowed to show them.', @@ -85,6 +90,7 @@ return [ // Contact Form 'contact_form' => [ + 'subject_placeholder' => 'Select a subject', 'success_title' => 'Thank you!', 'success_message' => 'Your message has been received. We will get back to you as soon as possible.', 'first_name' => 'First Name *', diff --git a/resources/views/components/appointment-link.blade.php b/resources/views/components/appointment-link.blade.php new file mode 100644 index 00000000..6e5f1f06 --- /dev/null +++ b/resources/views/components/appointment-link.blade.php @@ -0,0 +1,17 @@ +{{-- Link zu einer Terminanfrage. + + Zeigt der Link auf eine Kontaktseite (/contact oder /immobilien/kontakt), oeffnet ein Klick + das schwebende Terminanfrage-Widget statt die Seite zu wechseln – `topic` belegt dabei das + Anliegen vor (z. B. der Marken-Slug auf einer Markenseite). Ohne JavaScript bleibt es ein + normaler Link. Zeigt der Link woandershin (vom CMS geaendert), ist es ein gewoehnlicher Link. + Das Abfangen steckt in resources/js/app.js, das Oeffnen im Widget selbst. --}} +@props(['href', 'topic' => null]) + +@php + $path = (string) (parse_url((string) $href, PHP_URL_PATH) ?? ''); + $opensWidget = in_array(rtrim($path, '/'), ['/contact', '/immobilien/kontakt'], true); +@endphp + +{{ $slot }} diff --git a/resources/views/livewire/admin/cms/articles-index.blade.php b/resources/views/livewire/admin/cms/articles-index.blade.php index ab2e74c9..9b908b22 100644 --- a/resources/views/livewire/admin/cms/articles-index.blade.php +++ b/resources/views/livewire/admin/cms/articles-index.blade.php @@ -17,6 +17,7 @@ state([ 'subtitle' => '', 'image' => '', 'category' => '', + 'world' => 'interior', 'date_label' => '', 'read_time' => '', 'authorName' => '', @@ -48,9 +49,10 @@ $articles = computed( $openCreate = function () { $this->editingId = null; - $this->reset(['slug', 'articleTitle', 'subtitle', 'image', 'category', 'date_label', 'read_time', 'authorName', 'authorBio', 'authorAvatar', 'intro', 'sections']); + $this->reset(['slug', 'articleTitle', 'subtitle', 'image', 'category', 'world', 'date_label', 'read_time', 'authorName', 'authorBio', 'authorAvatar', 'intro', 'sections']); $this->is_published = true; $this->order = 0; + $this->world = 'interior'; $this->sections = [['title' => '', 'content' => '']]; $this->showForm = true; }; @@ -65,6 +67,7 @@ $openEdit = function (int $id) { $this->slug = $article->slug; $this->image = $article->image ?? ''; $this->category = $article->category ?? ''; + $this->world = $article->world ?? 'interior'; $this->date_label = $article->date_label ?? ''; $this->read_time = $article->read_time ?? ''; $this->is_published = $article->is_published; @@ -114,6 +117,7 @@ $save = function () { 'subtitle' => $this->subtitle, 'image' => $this->image, 'category' => $this->category, + 'world' => $this->world, 'date_label' => $this->date_label, 'read_time' => $this->read_time, 'authorName' => $this->authorName, @@ -126,6 +130,7 @@ $save = function () { 'subtitle' => 'nullable|string|max:1000', 'image' => 'nullable|string|max:500', 'category' => 'nullable|string|max:255', + 'world' => 'required|in:interior,immobilien', 'date_label' => 'nullable|string|max:100', 'read_time' => 'nullable|string|max:50', 'authorName' => 'nullable|string|max:255', @@ -153,6 +158,7 @@ $save = function () { $article->image = $validated['image'] ?? null; $article->category = $validated['category'] ?? null; + $article->world = $validated['world']; $article->date_label = $validated['date_label'] ?? null; $article->read_time = $validated['read_time'] ?? null; $article->author = [ @@ -225,6 +231,12 @@ $cancelForm = function () { + {{-- Welt: entscheidet, in welcher Magazinliste der Beitrag steht (/magazin oder + /immobilien/magazin) und in welcher Farbwelt die Detailseite rendert. --}} + + Interior + Immobilien + @@ -337,6 +349,9 @@ $cancelForm = function () { @if ($article->category) {{ $article->category }} @endif + @if ($article->world === 'immobilien') + Immobilien + @endif @unless ($article->is_published) Entwurf @endunless diff --git a/resources/views/livewire/web/components/sections/audience-switch.blade.php b/resources/views/livewire/web/components/sections/audience-switch.blade.php index 8df6ab13..d89f5dfb 100644 --- a/resources/views/livewire/web/components/sections/audience-switch.blade.php +++ b/resources/views/livewire/web/components/sections/audience-switch.blade.php @@ -39,9 +39,9 @@ @if (!empty($card['button_text'])) @endif diff --git a/resources/views/livewire/web/components/sections/brand-detail.blade.php b/resources/views/livewire/web/components/sections/brand-detail.blade.php index 25a86324..afe2504f 100644 --- a/resources/views/livewire/web/components/sections/brand-detail.blade.php +++ b/resources/views/livewire/web/components/sections/brand-detail.blade.php @@ -306,14 +306,15 @@

- {{ __('ui.request_appointment') }} - + @if (!empty($brand['website_url']))

{{ __('ui.other_brands') }}

-
diff --git a/resources/views/livewire/web/components/sections/core-brands.blade.php b/resources/views/livewire/web/components/sections/core-brands.blade.php index 3cd698fe..675261ee 100644 --- a/resources/views/livewire/web/components/sections/core-brands.blade.php +++ b/resources/views/livewire/web/components/sections/core-brands.blade.php @@ -7,7 +7,8 @@ @endif
-
+ {{-- Vier Kernmarken als 2×2-Raster; vier nebeneinander wäre zu schmal (Abstimmung 15.09.). --}} +
@foreach ($brands as $brand)
diff --git a/resources/views/livewire/web/components/sections/hero.blade.php b/resources/views/livewire/web/components/sections/hero.blade.php index 4262e446..0cdd7703 100644 --- a/resources/views/livewire/web/components/sections/hero.blade.php +++ b/resources/views/livewire/web/components/sections/hero.blade.php @@ -16,9 +16,9 @@ {{ $content['cta1_text'] }} - + {{ $content['cta2_text'] }} - +
@if(isset($content['stats'])) diff --git a/resources/views/livewire/web/components/sections/magazin-detail.blade.php b/resources/views/livewire/web/components/sections/magazin-detail.blade.php index 747f4c09..8f82c2bc 100644 --- a/resources/views/livewire/web/components/sections/magazin-detail.blade.php +++ b/resources/views/livewire/web/components/sections/magazin-detail.blade.php @@ -2,7 +2,7 @@
diff --git a/resources/views/livewire/web/components/sections/marken-cta.blade.php b/resources/views/livewire/web/components/sections/marken-cta.blade.php index f8fdfffb..4626b4ba 100644 --- a/resources/views/livewire/web/components/sections/marken-cta.blade.php +++ b/resources/views/livewire/web/components/sections/marken-cta.blade.php @@ -4,9 +4,9 @@

{{ $content['text'] ?? '' }}

- + {{ $content['button_text'] ?? __('ui.request_appointment') }} - + {{-- WhatsApp erscheint erst, wenn die Nummer im CMS hinterlegt ist (global.contact.whatsapp). Das Widget selbst kommt mit AP3. --}} diff --git a/resources/views/livewire/web/components/sections/origin-section.blade.php b/resources/views/livewire/web/components/sections/origin-section.blade.php new file mode 100644 index 00000000..a0598d4c --- /dev/null +++ b/resources/views/livewire/web/components/sections/origin-section.blade.php @@ -0,0 +1,126 @@ +@php + $hub = $content['hub'] ?? []; + $hubX = (int) ($hub['x'] ?? 400); + $hubY = (int) ($hub['y'] ?? 175); + $steps = array_values(array_filter($content['steps'] ?? [], fn ($step) => is_array($step) && ! empty($step['title']))); +@endphp + +
+
+
+

{!! $content['title'] ?? '' !!}

+ @if (!empty($content['subtitle'])) +

{{ $content['subtitle'] }}

+ @endif +
+ +
+ {{-- Schematische Karte: Bielefeld als Drehscheibe, die vier Standorte als Speichen. + Keine Geografie, nur Richtung und Entfernung. Unter md tragen die Kacheln alles. --}} + + + {{-- Die vier Standorte --}} +
+ @foreach ($this->locations as $index => $location) +
+ + + {{ $location['city'] }} + +

{{ $location['brand'] }}

+ @if (!empty($location['role'])) +

{{ $location['role'] }}

+ @endif +
+ @if (!empty($location['distance'])) + {{ $location['distance'] }} {{ $content['distance_suffix'] ?? '' }} + @endif + @if (!empty($location['link']) && !empty($location['link_text'])) + + {{ $location['link_text'] }} + + @endif +
+
+ @endforeach +
+
+ + {{-- Gefertigt oder vertrieben? Der Weg eines Maßmöbels am Beispiel CABINET. --}} + @if ($steps !== []) +
+
+

{!! $content['steps_title'] ?? '' !!}

+ @if (!empty($content['steps_text'])) +

{{ $content['steps_text'] }}

+ @endif +
+ +
    + @foreach ($steps as $index => $step) +
  1. + + {{ $index + 1 }} + +
    +

    {{ $step['title'] }}

    + @if (!empty($step['description'])) +

    {{ $step['description'] }}

    + @endif +
    +
  2. + @endforeach +
+
+ @endif +
+
diff --git a/resources/views/livewire/web/components/sections/reference-projects.blade.php b/resources/views/livewire/web/components/sections/reference-projects.blade.php index afe2a00c..1342bd1c 100644 --- a/resources/views/livewire/web/components/sections/reference-projects.blade.php +++ b/resources/views/livewire/web/components/sections/reference-projects.blade.php @@ -39,9 +39,9 @@ {{-- Ehrlicher Leerzustand statt einer erfundenen Referenz. --}}

{{ $content['empty'] ?? __('ui.references_empty') }}

- + {{ __('ui.request_appointment') }} - +
@endif
diff --git a/resources/views/livewire/web/components/ui/appointment-widget.blade.php b/resources/views/livewire/web/components/ui/appointment-widget.blade.php index 9d5c965f..76ff891f 100644 --- a/resources/views/livewire/web/components/ui/appointment-widget.blade.php +++ b/resources/views/livewire/web/components/ui/appointment-widget.blade.php @@ -17,7 +17,18 @@ if ($wire.name) { lines.push('Mein Name: ' + $wire.name) } return this.whatsappBase + '?text=' + encodeURIComponent(lines.join('\n')); }, - }" x-on:keydown.escape.window="open = false"> + /* Von einem „Termin anfragen“-Button irgendwo auf der Seite geoeffnet (Blade-Komponente + appointment-link, Klick-Delegat in app.js). Ein mitgegebenes Anliegen wird nur + uebernommen, wenn es in der Auswahl existiert – sonst bleibt die bisherige Wahl stehen. */ + openWith(topic) { + this.open = true; + if (topic && Object.prototype.hasOwnProperty.call(this.topicLabels, topic)) { + $wire.set('topic', topic); + } + this.$nextTick(() => { this.$refs.name?.focus(); }); + }, + }" x-on:keydown.escape.window="open = false" + x-on:open-appointment.window="openWith($event.detail?.topic)"> {{-- Panel --}}
{{ __('ui.appointment.name') }} - + @error('name') {{ $message }} @enderror
@@ -67,7 +78,7 @@ {{ __('ui.appointment.email') }} + class="form-field" /> @error('email') {{ $message }} @enderror
@@ -76,7 +87,7 @@ {{ __('ui.appointment.phone') }} + class="form-field" /> @error('phone') {{ $message }} @enderror
@@ -85,7 +96,7 @@ {{ __('ui.appointment.topic') }} + class="form-field" /> @error('preferredDate') {{ $message }} @enderror
@@ -110,7 +121,7 @@ + class="form-field resize-none"> @error('message') {{ $message }} @enderror diff --git a/resources/views/livewire/web/components/ui/contact-form.blade.php b/resources/views/livewire/web/components/ui/contact-form.blade.php index 66aa4340..cfbda0ab 100644 --- a/resources/views/livewire/web/components/ui/contact-form.blade.php +++ b/resources/views/livewire/web/components/ui/contact-form.blade.php @@ -40,7 +40,7 @@ wire:model="firstName" id="firstName" type="text" - class="w-full px-3 py-2 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-gray-600 sm:text-sm/6" + class="form-field" required /> @error('firstName') {{ $message }} @enderror @@ -53,7 +53,7 @@ wire:model="lastName" id="lastName" type="text" - class="w-full px-3 py-2 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-gray-600 sm:text-sm/6" + class="form-field" required /> @error('lastName') {{ $message }} @enderror @@ -68,7 +68,7 @@ wire:model="company" id="company" type="text" - class="w-full px-3 py-2 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-gray-600 sm:text-sm/6" + class="form-field" /> @error('company') {{ $message }} @enderror @@ -82,7 +82,7 @@ wire:model="email" id="email" type="email" - class="w-full px-3 py-2 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-gray-600 sm:text-sm/6" + class="form-field" required /> @error('email') {{ $message }} @enderror @@ -95,7 +95,7 @@ wire:model="phone" id="phone" type="tel" - class="w-full px-3 py-2 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-gray-600 sm:text-sm/6" + class="form-field" /> @error('phone') {{ $message }} @enderror @@ -105,18 +105,24 @@ -
+
-
@error('subject') {{ $message }} @enderror
@@ -131,7 +137,7 @@ wire:model="message" id="message" rows="5" - class="w-full px-3 py-2 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-gray-600 sm:text-sm/6 resize-none" + class="form-field resize-none" placeholder="{{ $content['form']['placeholders']['message'] ?? __('ui.contact_form.message_placeholder') }}" required > diff --git a/resources/views/livewire/web/components/ui/footer.blade.php b/resources/views/livewire/web/components/ui/footer.blade.php index 2fefaff1..6941af67 100644 --- a/resources/views/livewire/web/components/ui/footer.blade.php +++ b/resources/views/livewire/web/components/ui/footer.blade.php @@ -1,25 +1,40 @@ -{{-- pb-28 auf kleinen Schirmen: darunter schwebt das Terminanfrage-Widget, das sonst die +{{-- Der Footer folgt der Farbwelt der Seite: Interior auf Sand mit Tanne und positivem Logo, + Immobilien dunkel mit negativem Logo (Tokens `--footer-*` in theme-b2in.css, Logo in mount()). + pb-28 auf kleinen Schirmen: darunter schwebt das Terminanfrage-Widget, das sonst die letzten Zeilen des Footers verdecken wuerde. --}} -