WIP-Schnappschuss vor Migration 2026-09-28 (uncommittete Änderungen von feature/b2in-interior)
4
.gitignore
vendored
|
|
@ -64,3 +64,7 @@ dev/**/deploy/*.tar.gz
|
|||
# Referenzmaterial und Projektdokumente ausserhalb der Versionierung
|
||||
/_b2in_interior_shop/
|
||||
/docs/
|
||||
|
||||
# Original-Bildzulieferungen (mehrere MB je Datei) bleiben ausserhalb der Versionierung;
|
||||
# die optimierten WebP-Fassungen liegen unter public/img/assets/marken/.
|
||||
/dev/2026-08-28/images/
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ class ThemeHelper
|
|||
return self::getLogoPathForBrand('b2in-immobilien', $type);
|
||||
}
|
||||
|
||||
// Entwurf in der Farbwelt vom 24.09. (Nachtrag 19): Bogen in Gold statt Ocker.
|
||||
if ($theme === 'b2in' && b2in_palette() === 'greige') {
|
||||
return "img/logos/b2in-interior-logo-gold-{$type}.svg";
|
||||
}
|
||||
|
||||
return self::getLogoPathForBrand($theme, $type);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,51 @@ class BrandDetail extends Component
|
|||
->reject(fn (array $brand, string $key) => $key === $slug)
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$craft = $this->craftSection($slug, $brands);
|
||||
|
||||
if ($craft !== null) {
|
||||
$this->brand['sections'][] = $craft;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gemeinsamer Handwerk-Abschluss (Lang-Sektion `brand_craft`, Nachtrag 15): steht als letzter
|
||||
* Abschnitt auf jeder Markenseite – außer auf der Seite der Handwerksmarke selbst. Der Link
|
||||
* dorthin erscheint nur, wenn diese Marke veröffentlicht ist und eine Detailseite hat.
|
||||
*
|
||||
* @param array<string, array<string, mixed>> $brands
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
private function craftSection(string $slug, array $brands): ?array
|
||||
{
|
||||
$content = cms_theme_section('brand_craft');
|
||||
|
||||
if (! is_array($content)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$craftSlug = (string) ($content['brand_slug'] ?? '');
|
||||
|
||||
if ($craftSlug !== '' && $craftSlug === $slug) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sections = CmsBrand::normalizeSections([$content]);
|
||||
|
||||
if ($sections === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$section = $sections[0];
|
||||
$section['anchor'] = 'handwerk';
|
||||
|
||||
if ($craftSlug !== '' && ! empty($brands[$craftSlug]['has_detail_page'])) {
|
||||
$section['link'] = route('marken.show', ['slug' => $craftSlug]);
|
||||
$section['link_text'] = (string) ($content['link_text'] ?? $brands[$craftSlug]['name'] ?? '');
|
||||
}
|
||||
|
||||
return $section;
|
||||
}
|
||||
|
||||
public function render()
|
||||
|
|
|
|||
|
|
@ -2,42 +2,96 @@
|
|||
|
||||
namespace App\Livewire\Web\Components\Sections;
|
||||
|
||||
use App\Models\CmsBrand;
|
||||
use Livewire\Component;
|
||||
|
||||
/**
|
||||
* Hero des Startseiten-Entwurfs (Nachtrag 19, Briefing Marcel 24.09.): Rahmen mit Goldkante,
|
||||
* eine Überschrift, ein Button und statt des festen Bildes eine ruhige Slideshow.
|
||||
*
|
||||
* Die Slides kommen aus der Sektion `home/hero_slider` (Liste `slides`), sobald Marcel dort
|
||||
* welche pflegt. Solange die Liste leer ist, laufen die Titelbilder der veröffentlichten
|
||||
* Kernmarken in ihrer Reihenfolge – Marken ohne Bild fallen weg.
|
||||
*/
|
||||
class HeroSlider extends Component
|
||||
{
|
||||
public $content = [];
|
||||
/** @var array<string, mixed> */
|
||||
public array $content = [];
|
||||
|
||||
public $currentSlide = 0;
|
||||
/** @var array<int, array{image: string, alt: string, label: string, caption: string}> */
|
||||
public array $slides = [];
|
||||
|
||||
public function mount()
|
||||
public function mount(): void
|
||||
{
|
||||
$this->content = cms_theme_section('hero_slider');
|
||||
$section = cms_theme_section('hero_slider');
|
||||
$this->content = is_array($section) ? $section : [];
|
||||
$this->slides = $this->slidesFromContent() ?: $this->slidesFromBrands();
|
||||
}
|
||||
|
||||
public function nextSlide()
|
||||
/**
|
||||
* @return array<int, array{image: string, alt: string, label: string, caption: string}>
|
||||
*/
|
||||
private function slidesFromContent(): array
|
||||
{
|
||||
if (! empty($this->content['slides'])) {
|
||||
$this->currentSlide = ($this->currentSlide + 1) % count($this->content['slides']);
|
||||
$slides = [];
|
||||
|
||||
foreach ((array) ($this->content['slides'] ?? []) as $slide) {
|
||||
if (! is_array($slide) || trim((string) ($slide['image'] ?? '')) === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (array_key_exists('active', $slide) && ! filter_var($slide['active'], FILTER_VALIDATE_BOOLEAN)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$label = trim((string) ($slide['label'] ?? $slide['title'] ?? ''));
|
||||
|
||||
$slides[] = [
|
||||
'image' => (string) $slide['image'],
|
||||
'alt' => trim((string) ($slide['alt'] ?? $label)),
|
||||
'label' => $label,
|
||||
'caption' => trim((string) ($slide['caption'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
return $slides;
|
||||
}
|
||||
|
||||
public function previousSlide()
|
||||
/**
|
||||
* @return array<int, array{image: string, alt: string, label: string, caption: string}>
|
||||
*/
|
||||
private function slidesFromBrands(): array
|
||||
{
|
||||
if (! empty($this->content['slides'])) {
|
||||
$totalSlides = count($this->content['slides']);
|
||||
$this->currentSlide = ($this->currentSlide - 1 + $totalSlides) % $totalSlides;
|
||||
$slides = [];
|
||||
|
||||
foreach (CmsBrand::forFrontend() as $brand) {
|
||||
$image = trim((string) ($brand['image'] ?? ''));
|
||||
|
||||
if ($image === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = (string) ($brand['name'] ?? '');
|
||||
|
||||
$slides[] = [
|
||||
'image' => $image,
|
||||
'alt' => $name.' – '.strip_tags((string) ($brand['tagline'] ?? '')),
|
||||
'label' => $name,
|
||||
'caption' => strip_tags((string) ($brand['tagline'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
return $slides;
|
||||
}
|
||||
|
||||
public function setSlide($index)
|
||||
public function getIntervalMsProperty(): int
|
||||
{
|
||||
if (! empty($this->content['slides']) && $index >= 0 && $index < count($this->content['slides'])) {
|
||||
$this->currentSlide = $index;
|
||||
}
|
||||
$seconds = (float) ($this->content['interval'] ?? 7);
|
||||
|
||||
return (int) round(max(3, min(30, $seconds)) * 1000);
|
||||
}
|
||||
|
||||
public function render()
|
||||
public function render(): \Illuminate\View\View
|
||||
{
|
||||
return view('livewire.web.components.sections.hero-slider');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@ class Footer extends Component
|
|||
|
||||
public string $logoPath = '';
|
||||
|
||||
/**
|
||||
* Negatives Logo für die Farbvariante „Modern“ (Nachtrag 18): dort liegt der Interior-Footer
|
||||
* dunkel in Tanne. Die Palette schaltet im Browser, deshalb liegen beide Logos im Markup und
|
||||
* das Stylesheet blendet das passende ein. Leer in der Immobilien-Welt, die ohnehin das
|
||||
* negative Logo trägt.
|
||||
*/
|
||||
public string $logoPathDark = '';
|
||||
|
||||
/**
|
||||
* Fuer die Immobilien-Welt zeigen die gemeinsamen Footer-Links auf die Seiten dieser Welt.
|
||||
*
|
||||
|
|
@ -49,6 +57,9 @@ class Footer extends Component
|
|||
// Logo; der Immobilien-Footer bleibt dunkel und nutzt die negative Variante.
|
||||
$variant = $this->world === 'immobilien' ? 'negative' : 'positive';
|
||||
$this->logoPath = \App\Helpers\ThemeHelper::getLogoPath($variant, $this->world);
|
||||
$this->logoPathDark = $this->world === 'immobilien'
|
||||
? ''
|
||||
: \App\Helpers\ThemeHelper::getLogoPath('negative', $this->world);
|
||||
}
|
||||
|
||||
public function render()
|
||||
|
|
|
|||
|
|
@ -22,8 +22,11 @@ class CmsBrand extends Model
|
|||
/** Entwurf, Rendering oder KI-gestuetzte Darstellung – nicht realisiert. */
|
||||
public const GALLERY_INSPIRATION = 'inspiration';
|
||||
|
||||
/** Einblick in Werkstatt, Fertigung oder Montage des Herstellers – weder Projekt noch Entwurf (Nachtrag 19). */
|
||||
public const GALLERY_COMPANY = 'company';
|
||||
|
||||
/** @var list<string> */
|
||||
public const GALLERY_CATEGORIES = [self::GALLERY_PROJECT, self::GALLERY_INSPIRATION];
|
||||
public const GALLERY_CATEGORIES = [self::GALLERY_PROJECT, self::GALLERY_INSPIRATION, self::GALLERY_COMPANY];
|
||||
|
||||
protected $table = 'cms_brands';
|
||||
|
||||
|
|
@ -275,10 +278,10 @@ class CmsBrand extends Model
|
|||
}
|
||||
|
||||
/**
|
||||
* Galerie nach realisierten Projekten und Inspirationen getrennt.
|
||||
* Galerie nach realisierten Projekten, Einblicken in die Werkstatt und Inspirationen getrennt.
|
||||
*
|
||||
* @param mixed $gallery
|
||||
* @return array{projects: list<array{image: string, category: string, caption: string}>, inspirations: list<array{image: string, category: string, caption: string}>}
|
||||
* @return array{projects: list<array{image: string, category: string, caption: string}>, company: list<array{image: string, category: string, caption: string}>, inspirations: list<array{image: string, category: string, caption: string}>}
|
||||
*/
|
||||
public static function groupGallery($gallery): array
|
||||
{
|
||||
|
|
@ -289,6 +292,10 @@ class CmsBrand extends Model
|
|||
$items,
|
||||
fn (array $item) => $item['category'] === self::GALLERY_PROJECT,
|
||||
)),
|
||||
'company' => array_values(array_filter(
|
||||
$items,
|
||||
fn (array $item) => $item['category'] === self::GALLERY_COMPANY,
|
||||
)),
|
||||
'inspirations' => array_values(array_filter(
|
||||
$items,
|
||||
fn (array $item) => $item['category'] === self::GALLERY_INSPIRATION,
|
||||
|
|
@ -296,6 +303,18 @@ class CmsBrand extends Model
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Beschriftung des Badges auf dem Galeriebild („Realisiertes Projekt“, „Aus der Werkstatt“, „Visualisierung“).
|
||||
*/
|
||||
public static function galleryBadge(string $category): string
|
||||
{
|
||||
return match ($category) {
|
||||
self::GALLERY_PROJECT => __('ui.brand_gallery_badge_project'),
|
||||
self::GALLERY_COMPANY => __('ui.brand_gallery_badge_company'),
|
||||
default => __('ui.brand_gallery_badge_inspiration'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -233,6 +233,20 @@ if (! function_exists('b2in_world')) {
|
|||
}
|
||||
}
|
||||
|
||||
if (! function_exists('b2in_palette')) {
|
||||
/**
|
||||
* Abweichende Farbpalette der aktuellen Seite – leer im Normalfall.
|
||||
*
|
||||
* Der Entwurf der Startseite (Nachtrag 19, /entwurf) bindet „greige“; Logo und Layout
|
||||
* richten sich danach. Die Wahl „Bisher/Neu“ des Farb-Schalters läuft dagegen im Browser
|
||||
* und taucht hier nicht auf.
|
||||
*/
|
||||
function b2in_palette(): string
|
||||
{
|
||||
return app()->bound('b2in.palette') ? (string) app('b2in.palette') : '';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('b2in_worlds')) {
|
||||
/**
|
||||
* Die beiden Welten für den Welten-Wechsler in Kopf- und Fußzeile.
|
||||
|
|
|
|||
|
|
@ -56,6 +56,45 @@ return [
|
|||
*/
|
||||
'indexable' => (bool) env('SITE_INDEXABLE', env('APP_ENV', 'production') === 'production'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Farbvarianten-Schalter (B2in Interior)
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Blendet auf den Interior-Seiten den schwimmenden Schalter ein, mit dem sich
|
||||
| die bisherige Creme-Palette und die hellere Variante „Modern“ vergleichen
|
||||
| lassen (Nachtrag 18). Nur für die Abstimmung gedacht – sobald eine Palette
|
||||
| entschieden ist, per B2IN_PALETTE_SWITCH=false abschalten.
|
||||
|
|
||||
*/
|
||||
|
||||
'palette_switch' => (bool) env('B2IN_PALETTE_SWITCH', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Feste Farbpalette der Interior-Welt (ohne Schalter)
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| „modern“ = die hellere Variante „Neu“ (Nachtrag 18), „classic“ = Creme.
|
||||
| Gilt, solange der Schalter aus ist; mit Schalter ist es die Vorbelegung.
|
||||
| Seit der Abstimmung 25.09. steht die Seite auf „modern“.
|
||||
|
|
||||
*/
|
||||
|
||||
'palette' => env('B2IN_PALETTE', 'modern'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Entwurf der Startseite unter /entwurf
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Farbwelt „Greige“ mit Hero-Slideshow (Nachtrag 19), nur zur Abstimmung.
|
||||
| Abschalten, sobald der Entwurf entschieden ist.
|
||||
|
|
||||
*/
|
||||
|
||||
'home_draft' => (bool) env('B2IN_HOME_DRAFT', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application URL
|
||||
|
|
|
|||
|
|
@ -128,6 +128,9 @@ return [
|
|||
'sections' => [
|
||||
['group' => 'home', 'key' => 'hero', 'anchor' => 'hero', 'title' => 'Hero',
|
||||
'description' => 'Der große Einstieg ganz oben: Überschrift, Text, Buttons und Bild.'],
|
||||
['group' => 'home', 'key' => 'hero_slider', 'anchor' => 'hero', 'title' => 'Hero mit Slideshow (Entwurf)',
|
||||
'path' => '/entwurf',
|
||||
'description' => 'Der Hero des Entwurfs vom 24.09.: Überschrift, Text, Button und die Slideshow. Liste „slides“ leer = Titelbilder der Kernmarken; sonst je Eintrag image, label, caption und active.'],
|
||||
['group' => 'home', 'key' => 'founder_bar', 'anchor' => 'gruender', 'title' => 'Gründer-Leiste',
|
||||
'description' => 'Schmale Zeile mit Marcel Scheibe und Zitat. Steht auch auf „Über uns“ und in der Immobilien-Welt.'],
|
||||
['group' => 'home', 'key' => 'audience_switch', 'anchor' => 'wege', 'title' => 'Zielgruppen-Wahl',
|
||||
|
|
@ -161,6 +164,8 @@ return [
|
|||
'description' => 'Hinweis, dass weitere Marken folgen.'],
|
||||
['group' => 'marken', 'key' => 'cta', 'anchor' => 'marken-cta', 'title' => 'Abschluss auf /marken',
|
||||
'description' => 'Der Abschlussblock „Lernen Sie unsere Marken kennen“ mit Terminanfrage und WhatsApp.'],
|
||||
['group' => 'marken', 'key' => 'craft', 'anchor' => 'handwerk', 'title' => 'Handwerk-Abschluss auf jeder Markenseite',
|
||||
'description' => 'Der letzte Abschnitt auf den Seiten von CABINET, Rotpunkt und Febrü: „Planung plus Handwerk“ mit Link zu Otterpohl. Einmal gepflegt, überall gleich.', 'path' => '/marken/cabinet'],
|
||||
],
|
||||
],
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ return [
|
|||
'announcement_bar' => ['global', 'announcement_bar'],
|
||||
'announcement_bar_immobilien' => ['global', 'announcement_bar_immobilien'],
|
||||
'header' => ['global', 'header'],
|
||||
'hero_slider' => ['home', 'hero_slider'],
|
||||
'footer' => ['global', 'footer'],
|
||||
'cta_section' => ['global', 'cta_section'],
|
||||
'cta_section_immobilien' => ['global', 'cta_section_immobilien'],
|
||||
|
|
@ -31,6 +32,7 @@ return [
|
|||
'marken_network' => ['marken', 'network'],
|
||||
'marken_growth' => ['marken', 'growth'],
|
||||
'marken_cta' => ['marken', 'cta'],
|
||||
'brand_craft' => ['marken', 'craft'],
|
||||
|
||||
'referenzen_hero' => ['referenzen', 'hero'],
|
||||
'reference_projects' => ['referenzen', 'projects'],
|
||||
|
|
|
|||
81
database/seeders/AudienceSwitchProjectTextSeeder.php
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use FluxCms\Core\Models\CmsContent;
|
||||
use FluxCms\Core\Services\CmsContentService;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Projekttext auf der Startseite (Briefing Marcel 24.09.2026, Punkt 7; Nachtrag 19).
|
||||
*
|
||||
* In der Karte „Ausstattung für Ihr Projekt“ (home/audience_switch) wird „Mit deutscher
|
||||
* Vertragssicherheit …“ durch „Mit einem zentralen deutschen Vertragspartner …“ ersetzt.
|
||||
* Der Seeder tauscht nur genau diesen Satz, egal wo er im Sektions-JSON steht; alles andere
|
||||
* bleibt. Steht der Satz nicht mehr drin, passiert nichts. Mehrfaches Ausführen ist unbedenklich.
|
||||
*
|
||||
* php artisan db:seed --class=AudienceSwitchProjectTextSeeder
|
||||
*/
|
||||
class AudienceSwitchProjectTextSeeder extends Seeder
|
||||
{
|
||||
/** @var array<string, string> alter Satz → neuer Satz */
|
||||
public const REPLACEMENTS = [
|
||||
'Mit deutscher Vertragssicherheit und einem eingespielten Herstellernetzwerk.' => 'Mit einem zentralen deutschen Vertragspartner und einem eingespielten Herstellernetzwerk.',
|
||||
'With German contractual reliability and an established manufacturer network.' => 'With a single German contract partner and an established manufacturer network.',
|
||||
];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$row = CmsContent::query()->where('group', 'home')->where('key', 'audience_switch')->first();
|
||||
|
||||
if (! $row) {
|
||||
$this->command?->info('Projekttext: Sektion home/audience_switch nicht im CMS – die Lang-Datei gilt.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $row->getTranslations('value');
|
||||
$count = 0;
|
||||
$value = $this->replaceDeep($value, $count);
|
||||
|
||||
if ($count === 0) {
|
||||
$this->command?->info('Projekttext: bereits aktuell, nichts geändert.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$row->value = $value;
|
||||
$row->save();
|
||||
app(CmsContentService::class)->clearCache('home');
|
||||
|
||||
$this->command?->info("Projekttext ersetzt ({$count} Stelle(n)).");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $data
|
||||
* @return array<mixed>
|
||||
*/
|
||||
private function replaceDeep(array $data, int &$count): array
|
||||
{
|
||||
foreach ($data as $key => $item) {
|
||||
if (is_array($item)) {
|
||||
$data[$key] = $this->replaceDeep($item, $count);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! is_string($item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$replaced = strtr($item, self::REPLACEMENTS);
|
||||
|
||||
if ($replaced !== $item) {
|
||||
$data[$key] = $replaced;
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
160
database/seeders/BrandImageSeeder.php
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\CmsBrand;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Trägt Titelbild und Galerie für CABINET, Rotpunkt, Febrü (Nachtrag 16) und Otterpohl (Nachtrag 19) nach.
|
||||
*
|
||||
* Grundlage: dev/2026-08-28/images/ (Zulieferungen vom 22.–24.09.2026). Die Originale sind 1–8 MB
|
||||
* große JPEGs; unter public/img/assets/marken/{cabinet,rotpunkt,februe,otterpohl}/ liegen sie als
|
||||
* WebP mit maximal 1920 px Kantenlänge (Imagick, Qualität 82, ohne Metadaten). Die mit `_title_`
|
||||
* markierte Datei ist das Titelbild (Seitenkopf der Markenseite, Kachel auf der Startseite,
|
||||
* Markenblock auf /marken). Die Galeriebilder der drei Hersteller stehen als **Visualisierung**:
|
||||
* es sind Herstellerdarstellungen, keine von uns ausgeführten Projekte – unter „Referenzen“
|
||||
* erscheinen sie damit nicht. Bei Otterpohl sind die Bilder 2–5 Einblicke in Werkstatt und
|
||||
* Montage (**Aus der Werkstatt**), alle übrigen ausgeführte Aufträge (**Realisiertes Projekt**);
|
||||
* letztere erscheinen unter „Referenzen“, solange dort keine eigenen Einträge gepflegt sind.
|
||||
*
|
||||
* Verhalten: Das Titelbild wird nur gesetzt, wenn keins gepflegt ist. Galeriebilder werden
|
||||
* nur ergänzt, wenn die Datei noch nicht in der Galerie steht; vorhandene Einträge bleiben
|
||||
* samt Kategorie und Bildunterschrift, wie Marcel sie gepflegt hat. Mehrfaches Ausführen ist
|
||||
* unbedenklich.
|
||||
*
|
||||
* php artisan db:seed --class=BrandImageSeeder
|
||||
*/
|
||||
class BrandImageSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Galerie: Dateiname → Bildunterschrift (Kategorie Visualisierung) oder
|
||||
* Dateiname → ['caption' => …, 'category' => …] für eine andere Kategorie.
|
||||
*
|
||||
* @var array<string, array{image: string, gallery: array<string, string|array{caption: string, category: string}>}>
|
||||
*/
|
||||
private const IMAGES = [
|
||||
'cabinet' => [
|
||||
'image' => 'marken/cabinet/ankleide-solitaer-eiche-tano.webp',
|
||||
'gallery' => [
|
||||
'marken/cabinet/dachschraegenschrank-filo-cashmere.webp' => 'Dachschrägenschrank in Filo Cashmere',
|
||||
'marken/cabinet/schrank-gegen-schraege-marmor.webp' => 'Schrank gegen die Schräge in Marmoroptik',
|
||||
'marken/cabinet/schlafzimmer-eiche-tano.webp' => 'Schlafzimmerschrank in Eiche Tano',
|
||||
'marken/cabinet/schlafzimmer-velluto-fango.webp' => 'Schlafzimmerschrank in Velluto Fango',
|
||||
'marken/cabinet/schlafzimmer-wandschrank-filo-cashmere.webp' => 'Wandschrank im Schlafzimmer in Filo Cashmere',
|
||||
'marken/cabinet/garderobe-seite-offen.webp' => 'Garderobe mit offener Seite',
|
||||
'marken/cabinet/garderobe-sitznische-filo-cashmere.webp' => 'Garderobe mit Sitznische in Filo Cashmere',
|
||||
'marken/cabinet/drehtuerenschrank-marmor.webp' => 'Drehtürenschrank in Marmoroptik',
|
||||
'marken/cabinet/flur-schrank-eiche-tano.webp' => 'Flurschrank in Eiche Tano',
|
||||
'marken/cabinet/dachschraege-velluto-fango.webp' => 'Schrank unter der Dachschräge in Velluto Fango',
|
||||
'marken/cabinet/ankleide-solitaer-eiche-tano-seite.webp' => 'Ankleide Solitär in Eiche Tano, Seitenansicht',
|
||||
],
|
||||
],
|
||||
'rotpunkt' => [
|
||||
'image' => 'marken/rotpunkt/kueche-01.webp',
|
||||
'gallery' => [
|
||||
'marken/rotpunkt/kueche-01-b.webp' => 'Küche 01, zweite Ansicht',
|
||||
'marken/rotpunkt/kueche-02.webp' => 'Küche 02',
|
||||
'marken/rotpunkt/kueche-03.webp' => 'Küche 03',
|
||||
'marken/rotpunkt/kueche-04.webp' => 'Küche 04',
|
||||
'marken/rotpunkt/kueche-05.webp' => 'Küche 05',
|
||||
'marken/rotpunkt/kueche-06.webp' => 'Küche 06',
|
||||
'marken/rotpunkt/kueche-06-b.webp' => 'Küche 06, zweite Ansicht',
|
||||
'marken/rotpunkt/kueche-07.webp' => 'Küche 07',
|
||||
'marken/rotpunkt/kueche-08.webp' => 'Küche 08',
|
||||
'marken/rotpunkt/kueche-09.webp' => 'Küche 09',
|
||||
'marken/rotpunkt/kueche-10.webp' => 'Küche 10',
|
||||
'marken/rotpunkt/kueche-11.webp' => 'Küche 11',
|
||||
'marken/rotpunkt/kueche-12.webp' => 'Küche 12',
|
||||
],
|
||||
],
|
||||
// Bildunterschriften nach den Febrü-Produktlinien aus den Dateinamen.
|
||||
'februe' => [
|
||||
'image' => 'marken/februe/base-thekenloesung.webp',
|
||||
'gallery' => [
|
||||
'marken/februe/active-2-0-bureo.webp' => 'Active 2.0 mit Bureo',
|
||||
'marken/februe/active-2-0-soul-grossraum.webp' => 'Großraum mit Active 2.0 und Soul',
|
||||
'marken/februe/adesso.webp' => 'Adesso',
|
||||
'marken/februe/arena-stage.webp' => 'Arena Stage',
|
||||
'marken/februe/base.webp' => 'Base',
|
||||
'marken/februe/base-sideboard.webp' => 'Base Sideboard',
|
||||
'marken/februe/bobb-bionics-coworker-container-cube.webp' => 'Bobb, Bionics, CoWorker, Container und Cube',
|
||||
'marken/februe/bobb.webp' => 'Bobb',
|
||||
'marken/februe/brandon.webp' => 'Brandon',
|
||||
'marken/februe/coworker.webp' => 'CoWorker',
|
||||
'marken/februe/corporate.webp' => 'Corporate',
|
||||
'marken/februe/curved-ben-basic.webp' => 'Curved, Ben und Basic',
|
||||
'marken/februe/eline-one-silent-garderobenpaneel.webp' => 'E-Line, One, Silent und Garderobenpaneel',
|
||||
'marken/februe/elements-soul-grossraum.webp' => 'Großraum mit Elements und Soul',
|
||||
'marken/februe/vio.webp' => 'Vio',
|
||||
],
|
||||
],
|
||||
// Otterpohl (Zulieferung 24.09.): Titelbild und 2–5 aus der Werkstatt, der Rest sind Referenzen.
|
||||
// Unterschriften ohne Kundennamen – die stehen nur in den Dateinamen der Zulieferung.
|
||||
'tischlerei' => [
|
||||
'image' => 'marken/otterpohl/werkstatt-titel.webp',
|
||||
'gallery' => [
|
||||
'marken/otterpohl/referenz-luecke-01.webp' => ['caption' => 'Treppe mit Lamellenwand, Sitzbank und Sideboard', 'category' => CmsBrand::GALLERY_PROJECT],
|
||||
'marken/otterpohl/referenz-dziuballe-treppe.webp' => ['caption' => 'Treppe mit Holzstufen, Stufenbeleuchtung und Eichenhandlauf', 'category' => CmsBrand::GALLERY_PROJECT],
|
||||
'marken/otterpohl/referenz-luecke-12.webp' => ['caption' => 'Tür und Kommode vor Lamellenwand', 'category' => CmsBrand::GALLERY_PROJECT],
|
||||
'marken/otterpohl/referenz-neugebauer.webp' => ['caption' => 'Gewendelte Treppe mit Eichenstufen', 'category' => CmsBrand::GALLERY_PROJECT],
|
||||
'marken/otterpohl/referenz-luecke-15.webp' => ['caption' => 'Beleuchtete Wandregale über Eck', 'category' => CmsBrand::GALLERY_PROJECT],
|
||||
'marken/otterpohl/referenz-berkemeier-treppe.webp' => ['caption' => 'Treppe mit umlaufender Sockelleiste und Lichtdetail', 'category' => CmsBrand::GALLERY_PROJECT],
|
||||
'marken/otterpohl/referenz-objektbau-luecke-technik.webp' => ['caption' => 'Objektbau: Verkaufsraum mit Theke und Regalwand', 'category' => CmsBrand::GALLERY_PROJECT],
|
||||
'marken/otterpohl/referenz-otterpohl-11.webp' => ['caption' => 'Sideboard mit Eichenplatte im Büro', 'category' => CmsBrand::GALLERY_PROJECT],
|
||||
'marken/otterpohl/werkstatt-02.webp' => ['caption' => 'CNC-Bearbeitungszentrum in der Fertigung', 'category' => CmsBrand::GALLERY_COMPANY],
|
||||
'marken/otterpohl/werkstatt-03.webp' => ['caption' => 'Einbau einer Tür vor Ort', 'category' => CmsBrand::GALLERY_COMPANY],
|
||||
'marken/otterpohl/werkstatt-04.webp' => ['caption' => 'Handwerkzeug in der Werkstatt', 'category' => CmsBrand::GALLERY_COMPANY],
|
||||
'marken/otterpohl/werkstatt-05-lift.webp' => ['caption' => 'Anlieferung per Möbellift – Montage inklusive', 'category' => CmsBrand::GALLERY_COMPANY],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
foreach (self::IMAGES as $slug => $data) {
|
||||
$brand = CmsBrand::query()->where('slug', $slug)->first();
|
||||
|
||||
if (! $brand) {
|
||||
$this->command?->warn("Marke „{$slug}“ nicht gefunden – übersprungen.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$titleSet = false;
|
||||
|
||||
if (trim((string) $brand->image) === '') {
|
||||
$brand->image = $data['image'];
|
||||
$titleSet = true;
|
||||
}
|
||||
|
||||
$gallery = CmsBrand::normalizeGallery($brand->gallery);
|
||||
$known = array_column($gallery, 'image');
|
||||
$added = 0;
|
||||
|
||||
foreach ($data['gallery'] as $image => $entry) {
|
||||
if (in_array($image, $known, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$gallery[] = [
|
||||
'image' => $image,
|
||||
'category' => is_array($entry) ? $entry['category'] : CmsBrand::GALLERY_INSPIRATION,
|
||||
'caption' => is_array($entry) ? $entry['caption'] : $entry,
|
||||
];
|
||||
$added++;
|
||||
}
|
||||
|
||||
$brand->gallery = $gallery;
|
||||
$brand->save();
|
||||
|
||||
$this->command?->info(sprintf(
|
||||
'Marke „%s“: Titelbild %s, %d Galeriebilder ergänzt, %d vorhanden.',
|
||||
$slug,
|
||||
$titleSet ? 'gesetzt' : 'behalten',
|
||||
$added,
|
||||
count($gallery) - $added,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ use Illuminate\Database\Seeder;
|
|||
* Rotpunkt Zeichenflaeche = Inhalt → fuellt die Box, gilt als Bezugsgroesse
|
||||
* CABINET Zeichenflaeche rechts +18 % → rendert 15 % kleiner als Rotpunkt
|
||||
* Februe Zeichenflaeche oben/unten +20 % → rendert wie vor der Abstimmung
|
||||
* Otterpohl knapp beschnitten, weiss freigestellt (Zulieferung 17.09.2026, Nachtrag 15)
|
||||
*
|
||||
* Alle drei waren in der Zulieferung sehr unterschiedlich stark umrandet (CABINET nutzte nur
|
||||
* 29 Prozent der Hoehe) und wirkten dadurch ungleich gross. Die Abstimmung steckt bewusst in den
|
||||
|
|
@ -38,6 +39,7 @@ class BrandLogoSeeder extends Seeder
|
|||
'cabinet' => 'marken/cabinet.svg',
|
||||
'rotpunkt' => 'marken/rotpunkt.svg',
|
||||
'februe' => 'marken/februe.png',
|
||||
'tischlerei' => 'marken/otterpohl.png',
|
||||
];
|
||||
|
||||
public function run(): void
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ class BrandOriginSeeder extends Seeder
|
|||
'origin_city' => ['de' => 'Rheda-Wiedenbrück', 'en' => 'Rheda-Wiedenbrück'],
|
||||
'origin_distance' => ['de' => 'rund 35 km', 'en' => 'about 35 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Unsere Werkstatt für Aufbau, Sonderlösungen und Veredelung.',
|
||||
'en' => 'Our workshop for assembly, custom solutions and finishing.',
|
||||
'de' => 'Tischlerei und Innenausbau aus Rheda-Wiedenbrück – vom einzelnen Möbel bis zum ganzen Raum, inklusive Montage.',
|
||||
'en' => 'Joinery and interior fit-out from Rheda-Wiedenbrück – from a single piece to the whole room, installation included.',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -22,6 +22,12 @@ use Illuminate\Database\Seeder;
|
|||
* keine Zahlen, Zertifizierungen oder Herstelleraussagen, die niemand belegen kann. Er braucht
|
||||
* die Freigabe durch Marcel Scheibe, bevor er live geht.
|
||||
*
|
||||
* Feedback 17.09.2026 (dev/2026-08-28/feedback-17-09-2026.md) eingearbeitet: CABINET Einstieg
|
||||
* „Bild und grobes Maß“ und die Fragen vorab; Febrü „Kunden – keine Schubladen“, „Aus Herford“
|
||||
* und „Wertschätzung“, doppelter Schlusssatz entfernt; Rotpunkt „Mehr als Küchen“. Die
|
||||
* markeneigenen Handwerk-Abschnitte sind entfallen: das Thema steht als gemeinsamer Abschluss
|
||||
* auf jeder Markenseite (Lang-Sektion `brand_craft`, siehe BrandDetail).
|
||||
*
|
||||
* ⚠️ Überschreibt bei bestehenden Marken **nur** die Felder aus {@see FIELDS}. Bilder, Galerie,
|
||||
* Leistungen und Reihenfolge bleiben unangetastet, damit gepflegte Inhalte erhalten bleiben.
|
||||
*
|
||||
|
|
@ -95,11 +101,14 @@ class BrandPageTextSeeder extends Seeder
|
|||
],
|
||||
],
|
||||
[
|
||||
'title' => 'Aufmaß vor Ort statt Schätzung am Telefon',
|
||||
'title' => 'Ein Bild und ein grobes Maß reichen für den Anfang',
|
||||
'paragraphs' => [
|
||||
'Jede Planung beginnt bei uns mit dem Aufmaß in Ihren Räumen. Das klingt selbstverständlich, ist es aber nicht: Wände sind selten exakt im Lot, Böden selten vollkommen eben, und alte Häuser halten regelmäßig Überraschungen bereit.',
|
||||
'Wir messen deshalb vor Ort und planen mit den tatsächlichen Maßen. Das kostet zu Beginn etwas mehr Zeit und erspart am Montagetag die unangenehme Sorte Überraschung.',
|
||||
'Für den Anfang reicht uns ein Bild und ein grobes Maß völlig aus.',
|
||||
'Sie müssen also nicht erst alles millimetergenau ausmessen. Ein paar Fotos von der Raumsituation und die ungefähren Maße reichen, damit wir schon eine gute Planung machen und Ihnen ein konkretes Angebot erstellen können.',
|
||||
'Danach gehen wir gemeinsam ins Detail: Aufteilung, Ausstattung, Materialien, Farben und natürlich der Preis.',
|
||||
'Wenn alles grundsätzlich passt, kommen wir zum professionellen Aufmaß bei Ihnen vor Ort. Danach machen wir die Feinplanung fertig und geben den Schrank zur Bestellung frei.',
|
||||
],
|
||||
'claim' => 'So läuft’s: Foto + grobe Maße → Beratung → Planung & Angebot → Aufmaß vor Ort → Feinplanung → Bestellung',
|
||||
],
|
||||
[
|
||||
'title' => 'Innen entscheidet sich, ob ein Schrank funktioniert',
|
||||
|
|
@ -111,7 +120,7 @@ class BrandPageTextSeeder extends Seeder
|
|||
'title' => 'Die Fragen, die wir vorher stellen',
|
||||
'paragraphs' => [
|
||||
'Wie viel Hängefläche wird wirklich gebraucht und wie viel Fachboden? Wo sollen Schuhe, Koffer, Bettwäsche, Sportsachen oder Weihnachtsdeko hin? Wer nutzt den Schrank – und wie groß ist diese Person?',
|
||||
'Auch der weniger repräsentative Teil gehört dazu: Wäschekörbe, Bügelbrett, Staubsauger und die Kiste mit den Kabeln, die man aus Prinzip nicht wegwirft. Wird das vorher besprochen, muss es hinterher nicht improvisiert werden.',
|
||||
'Auch die Dinge, die man nicht jeden Tag braucht, gehören in die Planung: Wäschekörbe, Bügelbrett, Staubsauger, Karnevalskostüme oder besondere Sportsachen. Wenn wir vorher wissen, was untergebracht werden soll, können wir den Platz direkt sinnvoll dafür einplanen.',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
|
@ -153,15 +162,6 @@ class BrandPageTextSeeder extends Seeder
|
|||
'Sie müssen sich also nicht allein auf unser Verkaufsgespräch verlassen, sondern entscheiden auf Grundlage dessen, was Sie tatsächlich gesehen und angefasst haben.',
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => 'Schranksystem plus Handwerk',
|
||||
'paragraphs' => [
|
||||
'CABINET deckt bereits eine große planerische Bandbreite ab. Es geht deshalb ausdrücklich nicht um Sonderanfertigungen um jeden Preis.',
|
||||
'Natürlich gibt es trotzdem diesen einen Fall, bei dem selbst ein hervorragendes Maßsystem an seine Grenze kommt. Dann greift unsere dauerhaft verankerte Schnittstelle zu angeschlossenen Tischlereien – für den Übergang zur Architektur, die besondere Verkleidung oder den letzten Millimeter.',
|
||||
'Wir wägen gestalterischen Mehrwert, technische Notwendigkeit und wirtschaftliche Sinnhaftigkeit ehrlich miteinander ab. Am Ende entscheiden aber Ihre Wünsche – nicht unser Drang, Ihnen noch eine Sonderlösung zu verkaufen.',
|
||||
],
|
||||
'claim' => 'Die Möglichkeiten eines Maßsystems. Die Flexibilität des Handwerks.',
|
||||
],
|
||||
],
|
||||
],
|
||||
'rotpunkt' => [
|
||||
|
|
@ -235,9 +235,9 @@ class BrandPageTextSeeder extends Seeder
|
|||
[
|
||||
'title' => 'Mehr als Küchen',
|
||||
'paragraphs' => [
|
||||
'Die Möglichkeiten von Rotpunkt enden nicht bei der klassischen Küche. Das Möbel- und Planungssystem lässt sich auch für Hauswirtschaftsräume, Ankleiden, Badezimmer und weitere Wohnbereiche einsetzen.',
|
||||
'Materialien, Farben und Gestaltungselemente können über mehrere Räume hinweg aufgegriffen werden. So entsteht nicht in jedem Zimmer eine neue Idee, sondern ein zusammenhängendes Interior-Konzept mit einer gemeinsamen gestalterischen Sprache.',
|
||||
'Zusammen mit CABINET, Febrü und weiteren ausgewählten Partnern betrachten wir bei Bedarf den gesamten Innenraum – von Möbeln und Beleuchtung bis zu Tapeten, Fußböden und, wenn es sein soll, der letzten Blume.',
|
||||
'Die Möglichkeiten von Rotpunkt enden nicht bei der klassischen Küche. Das Möbel- und Planungssystem lässt sich auch für Hauswirtschaftsräume, Badezimmer, Sideboards, Wohnmöbel und weitere Bereiche rund um das Wohnen einsetzen.',
|
||||
'Materialien, Farben und Gestaltungselemente können dabei über mehrere Räume hinweg aufgegriffen werden. So entsteht ein stimmiges Gesamtbild, statt in jedem Raum wieder ganz von vorne anzufangen.',
|
||||
'Bei Bedarf denken wir den Innenraum deshalb auch über die Küche hinaus – von Möbeln und Beleuchtung bis zu Tapeten, Fußböden und, wenn es sein soll, der letzten Blume.',
|
||||
],
|
||||
],
|
||||
[
|
||||
|
|
@ -257,22 +257,6 @@ class BrandPageTextSeeder extends Seeder
|
|||
'Wir begleiten Sie dabei, ordnen die unterschiedlichen Möglichkeiten ein und führen Ihre Entscheidungen zu einem stimmigen Gesamtkonzept zusammen.',
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => 'Küchenstudio plus Handwerk',
|
||||
'paragraphs' => [
|
||||
'Rotpunkt deckt bereits eine enorme planerische und gestalterische Bandbreite ab. Es geht deshalb ausdrücklich nicht um Sonderanfertigungen um jeden Preis.',
|
||||
'Natürlich gibt es trotzdem immer diesen einen planerischen Sonderfall, bei dem selbst hervorragende Standardsysteme an ihre Grenzen kommen. Dann greift unsere dauerhaft verankerte Schnittstelle zu angeschlossenen Tischlereien.',
|
||||
'Wir wägen gestalterischen Mehrwert, technische Notwendigkeit und wirtschaftliche Sinnhaftigkeit ehrlich miteinander ab. Am Ende entscheiden aber Ihre Wünsche – nicht unser Drang, Ihnen unbedingt noch eine Sonderlösung zu verkaufen.',
|
||||
],
|
||||
'detail' => [
|
||||
'title' => 'Wenn das Handwerk den letzten Millimeter übernimmt',
|
||||
'paragraphs' => [
|
||||
'Möglich sind beispielsweise passgenaue Nischenlösungen, besondere Verkleidungen, raumhohe Einbauten, Übergänge zu angrenzenden Möbeln oder individuelle Anpassungen an die vorhandene Architektur.',
|
||||
'Bei Bedarf endet das Konzept nicht an der Arbeitsplatte. Auch Tapeten, Fußböden, Wandgestaltung, Beleuchtung und weitere Details können einbezogen werden.',
|
||||
],
|
||||
],
|
||||
'claim' => 'Die Möglichkeiten eines modernen Küchenstudios. Die Flexibilität des Handwerks.',
|
||||
],
|
||||
],
|
||||
],
|
||||
'februe' => [
|
||||
|
|
@ -299,9 +283,8 @@ class BrandPageTextSeeder extends Seeder
|
|||
'detail' => [
|
||||
'title' => 'Bei uns gibt es Kunden – keine Schubladen',
|
||||
'paragraphs' => [
|
||||
'B2B und B2C sind Begriffe aus Präsentationen und Vertriebsschulungen. Früher hieß es Privatkunde oder Firmenkunde. Oder, noch schlimmer: A-, B- oder C-Kunde. Bei diesem Schubladendenken machen wir nicht mit.',
|
||||
'Natürlich beeinflusst die Menge den Preis. Wer fünfzig Arbeitsplätze einrichtet, erhält eine andere Kalkulation als jemand, der einen einzelnen Schreibtisch für sein Homeoffice sucht. Alles andere wäre weder ehrlich noch kaufmännisch sinnvoll.',
|
||||
'Die Menge verändert aber weder die Qualität des Produktes noch die Sorgfalt der Planung, die persönliche Beratung oder den Service. Ein einzelner Arbeitsplatz im privaten Zuhause verdient dieselbe Aufmerksamkeit wie eine vollständige Büroetage.',
|
||||
'Ob Privatkunde oder Unternehmen: Für uns gibt es zunächst einmal Kunden mit unterschiedlichen Räumen, Anforderungen und Stückzahlen.',
|
||||
'Natürlich beeinflusst die Menge den Preis. Fünfzig Arbeitsplätze werden anders kalkuliert als ein einzelner Schreibtisch fürs Homeoffice. Der Anspruch an Produkt, Beratung und Service bleibt trotzdem derselbe.',
|
||||
'Febrü bleibt Febrü. B2in bleibt B2in. Und jeder Kunde erhält eine Lösung, die zu seinem tatsächlichen Bedarf passt.',
|
||||
],
|
||||
],
|
||||
|
|
@ -310,7 +293,7 @@ class BrandPageTextSeeder extends Seeder
|
|||
[
|
||||
'title' => 'Aus Herford. Für moderne Arbeitswelten.',
|
||||
'paragraphs' => [
|
||||
'Febrü entwickelt und produziert hochwertige Büromöbel direkt im ostwestfälischen Herford – nur wenige Kilometer von Bielefeld entfernt. Eine Fertigungstiefe von 95 Prozent ermöglicht kurze Wege, verlässliche Qualität und hohe Flexibilität bei Materialien, Farben und individuellen Anforderungen.',
|
||||
'Febrü entwickelt und produziert hochwertige Büromöbel direkt im ostwestfälischen Herford – nur wenige Kilometer von Bielefeld entfernt. Eine sehr hohe Fertigungstiefe ermöglicht kurze Wege, verlässliche Qualität und hohe Flexibilität bei Materialien, Farben und individuellen Anforderungen.',
|
||||
],
|
||||
'detail' => [
|
||||
'title' => 'Von einem Schreibtisch bis zum vollständigen Bürotrakt',
|
||||
|
|
@ -348,7 +331,7 @@ class BrandPageTextSeeder extends Seeder
|
|||
'title' => 'Wertschätzung ist keine Frage der Stückzahl',
|
||||
'paragraphs' => [
|
||||
'Gerade in Zeiten des Fachkräftemangels können Unternehmen es sich nicht leisten, Arbeitsbedingungen als Nebensache zu behandeln. Wer Top-Leistung erwartet, muss die passenden Voraussetzungen schaffen. Ein Obstkorb und kostenloses Wasser sind freundlich – ersetzen aber keinen guten Arbeitsplatz.',
|
||||
'Besonders deutlich wird das bei teilzeitbeschäftigten Müttern – selbstverständlich genauso bei Vätern und allen anderen Menschen mit familiärer Sorgeverantwortung. Teilzeit bedeutet nicht halbe Leistung. Oft wird in einem kürzeren Zeitfenster hoch konzentriert gearbeitet, während neben dem nächsten Onlinemeeting gleichzeitig Schule, Termine, Betreuung und damit unsere wichtigste gemeinsame Ressource organisiert werden: unser Nachwuchs.',
|
||||
'Besonders deutlich wird das bei Menschen, die Beruf und Familie miteinander organisieren. Teilzeit bedeutet nicht halbe Leistung. Oft wird in einem kürzeren Zeitfenster hoch konzentriert gearbeitet, während gleichzeitig Schule, Termine, Betreuung und der ganz normale Familienalltag organisiert werden müssen.',
|
||||
'Gerade hier darf der Arbeitsplatz kein Provisorium aus Küchentisch, Beistelltisch oder Schlafzimmerecke sein. Er muss sofort funktionieren, Bewegung ermöglichen, technisch durchdacht sein und sich trotzdem vernünftig in das Zuhause einfügen. Für den Menschen, der ihn täglich nutzt, ist ein einzelner Arbeitsplatz keine kleine Lösung. Er ist seine vollständige Arbeitswelt.',
|
||||
],
|
||||
],
|
||||
|
|
@ -447,7 +430,6 @@ class BrandPageTextSeeder extends Seeder
|
|||
'Durch die Nähe zwischen Bielefeld und Herford können Febrü-Produkte nach Abstimmung persönlich erlebt werden. Wir organisieren und begleiten den Besuch der Ausstellung – für einen Schreibtisch genauso wie für einen vollständigen Unternehmensstandort.',
|
||||
'Dort lassen sich Materialien anfassen, Stühle und Tische ausprobieren, Akustiklösungen erleben und unterschiedliche Arbeitswelten vergleichen. Sie müssen sich nicht allein auf ein Verkaufsgespräch verlassen.',
|
||||
],
|
||||
'claim' => 'Ob im Kleinen oder im Großen – die Detailtreue entscheidet.',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
|
|
|||
|
|
@ -8,11 +8,9 @@ use Illuminate\Database\Seeder;
|
|||
/**
|
||||
* Legt die Kernmarken an. Texte aus dev/2026-08-28/umsetzung-inhalte.md §5.
|
||||
*
|
||||
* Die vierte Marke „Tischlerei“ (Arbeitsplan 14.09.2026, Punkt 1.2) ist ein **Platzhalter**:
|
||||
* Name, Logo, Basisinfos und Bilder liegen noch nicht vor. Die Texte folgen der inhaltlichen
|
||||
* Ausrichtung aus dem Plan (Aufbau, Sonderlösungen, Veredelung, Maßarbeit – ergänzt CABINET
|
||||
* und Rotpunkt, steht nicht daneben) und sollen als Denkanstoß dienen. Der Name des
|
||||
* Partnerbetriebs gehört laut Plan auf die Detailseite, nicht in die Marke.
|
||||
* Die vierte Marke ist Otterpohl aus Rheda-Wiedenbrück (Arbeitsplan 14.09.2026, Punkt 1.2;
|
||||
* Zulieferung 17.09.2026). Ihre Texte liegen in {@see OtterpohlBrandSeeder}, der sie bei
|
||||
* bestehenden Installationen auch nachträgt. Der Slug „tischlerei“ bleibt stabil.
|
||||
*
|
||||
* Bestehende Marken werden nicht überschrieben – Marcel pflegt sie im Admin. Für bereits
|
||||
* angelegte Marken setzt {@see BrandLogoSeeder} die Logos nach und {@see BrandOriginSeeder}
|
||||
|
|
@ -164,92 +162,11 @@ Consultation and planning in our kitchen studio.',
|
|||
],
|
||||
'is_published' => true,
|
||||
],
|
||||
[
|
||||
// ⚠️ Platzhalter – siehe Klassenkommentar. Slug bleibt stabil, auch wenn der Name kommt.
|
||||
'slug' => 'tischlerei',
|
||||
'logo' => '',
|
||||
'name' => ['de' => 'Tischlerei', 'en' => 'Joinery'],
|
||||
'tagline' => ['de' => 'Maßarbeit, die den Raum vollendet.', 'en' => 'Craftsmanship that completes the room.'],
|
||||
'teaser' => [
|
||||
'de' => 'Aufbau, Sonderlösungen und Veredelung aus einer Tischlerei in Ostwestfalen – dort, wo Serienmöbel aufhören und Ihr Raum anfängt.',
|
||||
'en' => 'Assembly, bespoke solutions and finishing from a joinery in East Westphalia – where series furniture ends and your room begins.',
|
||||
],
|
||||
'summary' => [
|
||||
'de' => 'Manche Stellen im Raum passen in keinen Katalog: die Nische neben dem Kamin, der Übergang von der Küche zum Sideboard, das Regal unter der Treppe. Unsere Tischlerei baut, was dort fehlt – und veredelt, was schon da ist.
|
||||
|
||||
Sie ergänzt CABINET und Rotpunkt dort, wo Fertigung nach Zeichnung gefragt ist: Aufbau vor Ort, Sonderlösungen nach Aufmaß, Oberflächen und Anpassungen aus einer Hand.',
|
||||
'en' => 'Some spots in a room fit no catalogue: the alcove next to the fireplace, the transition from kitchen to sideboard, the shelf under the stairs. Our joinery builds what is missing there – and refines what is already in place.
|
||||
|
||||
It complements CABINET and Rotpunkt wherever making to drawing is required: assembly on site, bespoke solutions to measure, surfaces and adaptations from a single source.',
|
||||
],
|
||||
'highlights' => [
|
||||
'Sonderlösungen nach Aufmaß und Zeichnung',
|
||||
'Veredelung und Anpassung bestehender Möbel',
|
||||
'Aufbau und Montage aus einer Hand',
|
||||
],
|
||||
'description' => [
|
||||
'de' => '<p>Eine Küche endet nicht an der letzten Front, ein Einbauschrank nicht an der Wand. Dazwischen liegen die Details, die einen Raum fertig machen: eine Blende, die den Übergang schließt, ein Regal, das genau in die Nische passt, eine Oberfläche, die zum Boden passt. Unsere Tischlerei fertigt diese Details in Ostwestfalen – nach Zeichnung, nach Aufmaß, in Abstimmung mit den Marken, die den Raum tragen.</p>',
|
||||
'en' => '<p>A kitchen does not end at the last front, a fitted wardrobe not at the wall. In between lie the details that finish a room. Our joinery makes these details in East Westphalia – to drawing, to measure, in tune with the brands that carry the room.</p>',
|
||||
],
|
||||
'services' => [
|
||||
['title' => 'Aufbau und Montage', 'description' => 'Fachgerechter Aufbau vor Ort – auch von Möbeln, die nicht von uns stammen.'],
|
||||
['title' => 'Sonderlösungen', 'description' => 'Nischen, Schrägen, Übergänge: gefertigt nach Aufmaß, wo Standard nicht passt.'],
|
||||
['title' => 'Veredelung', 'description' => 'Oberflächen, Fronten und Griffe – bestehende Möbel aufwerten statt ersetzen.'],
|
||||
['title' => 'Maßarbeit', 'description' => 'Einzelstücke nach Zeichnung: Regale, Sideboards, Garderoben, Tische.'],
|
||||
],
|
||||
'widget_label' => ['de' => 'Persönliche Beratung zur Maßarbeit vereinbaren', 'en' => 'Arrange a personal consultation on bespoke work'],
|
||||
'cta_title' => ['de' => 'Ihr Raum. Bis ins Detail gefertigt.', 'en' => 'Your room. Made down to the detail.'],
|
||||
'cta_text' => [
|
||||
'de' => 'Ob ein einzelnes Regal in der Nische oder die Anpassung einer ganzen Einrichtung: Wir schauen uns Ihren Raum an und sagen Ihnen, was sich fertigen lässt – und was sich lohnt. Persönlich, vor Ort und mit einer Werkstatt, die wir kennen.',
|
||||
'en' => 'Whether a single shelf in an alcove or the adaptation of an entire interior: we look at your room and tell you what can be made – and what is worth it.',
|
||||
],
|
||||
'sections' => [
|
||||
[
|
||||
'title' => 'Wo Serienmöbel aufhören.',
|
||||
'paragraphs' => [
|
||||
'Die meisten Räume sind nicht rechtwinklig, nicht genormt und nicht leer. Sie haben Heizkörper, Fensterbänke, Schrägen und Möbel, die bleiben sollen. Genau dort beginnt die Arbeit der Tischlerei: mit einem Aufmaß, einer Zeichnung und der Frage, was der Raum wirklich braucht.',
|
||||
'Das kann ein einzelnes Möbelstück sein, eine Anpassung an ein bestehendes oder ein Detail, das zwei Marken miteinander verbindet.',
|
||||
],
|
||||
'detail' => [
|
||||
'title' => 'Beispiele aus dem Alltag',
|
||||
'paragraphs' => [
|
||||
'Ein Sideboard, das die Flucht der Rotpunkt-Küche aufnimmt und im Wohnzimmer weiterführt. Eine Blende, die den Spalt zwischen CABINET-Schrank und Dachschräge schließt. Ein Regal unter der Treppe, das die Tiefe der Stufen nutzt. Eine Bank am Fenster mit Stauraum darunter.',
|
||||
'Nichts davon steht in einem Katalog. Alles davon lässt sich fertigen.',
|
||||
],
|
||||
],
|
||||
'claim' => 'Gefertigt nach Raum, nicht nach Norm.',
|
||||
],
|
||||
[
|
||||
'title' => 'Ergänzung, nicht Konkurrenz.',
|
||||
'paragraphs' => [
|
||||
'CABINET plant Einbauschränke bis auf den Millimeter, Rotpunkt baut Küchen, Febrü richtet Arbeitsplätze ein. Die Tischlerei steht nicht daneben, sondern dazwischen: Sie fertigt die Übergänge, die Sonderteile und die Einzelstücke, mit denen aus drei Marken ein durchgängiger Raum wird.',
|
||||
],
|
||||
'detail' => [
|
||||
'title' => 'Aus der Region gefertigt',
|
||||
'paragraphs' => [
|
||||
'Die Werkstatt liegt in Ostwestfalen, wenige Kilometer von Bielefeld. Kurze Wege bedeuten kurze Abstimmungen: Aufmaß, Muster und Montage kommen aus einer Hand, und wenn etwas nicht passt, ist die Werkstatt erreichbar – nicht ein Callcenter.',
|
||||
],
|
||||
],
|
||||
'claim' => 'Vier Marken. Ein Raum. Ein Ansprechpartner.',
|
||||
],
|
||||
[
|
||||
'title' => 'Veredeln statt ersetzen.',
|
||||
'paragraphs' => [
|
||||
'Nicht jedes Möbel muss neu sein. Ein guter Korpus mit neuen Fronten, eine geölte statt lackierte Oberfläche, neue Griffe und eine passende Innenaufteilung – oft ist das die nachhaltigere und die schönere Lösung.',
|
||||
'Wir sagen Ihnen ehrlich, wann sich eine Veredelung lohnt und wann nicht.',
|
||||
],
|
||||
'claim' => 'Was gut ist, darf bleiben – und besser werden.',
|
||||
],
|
||||
],
|
||||
'showroom' => '',
|
||||
'origin_city' => ['de' => 'Rheda-Wiedenbrück', 'en' => 'Rheda-Wiedenbrück'],
|
||||
'origin_distance' => ['de' => 'rund 35 km', 'en' => 'about 35 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Unsere Werkstatt für Aufbau, Sonderlösungen und Veredelung.',
|
||||
'en' => 'Our workshop for assembly, custom solutions and finishing.',
|
||||
],
|
||||
'is_published' => true,
|
||||
],
|
||||
// Vierte Kernmarke Otterpohl (Nachtrag 15). Slug bleibt „tischlerei“, Texte aus OtterpohlBrandSeeder.
|
||||
array_merge(OtterpohlBrandSeeder::data(), [
|
||||
'slug' => OtterpohlBrandSeeder::SLUG,
|
||||
'logo' => OtterpohlBrandSeeder::LOGO,
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
52
database/seeders/DemoBrandGalleryCleanupSeeder.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\CmsBrand;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Entfernt die Demobilder aus den Markengalerien (Nachtrag 17).
|
||||
*
|
||||
* {@see DemoBrandGallerySeeder} hatte Stockmotive und KI-Renderings der alten Dubai-Seite als
|
||||
* Beispiele in die Galerien von CABINET, Rotpunkt und Febrü gelegt. Seit den echten Bildern
|
||||
* (Nachtrag 16) sollen sie weg. Erkannt werden sie am Dateinamen aus dem Demo-Seeder – nicht an
|
||||
* der Bildunterschrift, denn den Vermerk „BEISPIEL“ hat {@see BrandGalleryCaptionNormalizerSeeder}
|
||||
* auf der Vorschau bereits entfernt. Alle anderen Galeriebilder bleiben samt Kategorie,
|
||||
* Unterschrift und Reihenfolge. Mehrfaches Ausführen ist unbedenklich.
|
||||
*
|
||||
* php artisan db:seed --class=DemoBrandGalleryCleanupSeeder
|
||||
*/
|
||||
class DemoBrandGalleryCleanupSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$demoImages = [];
|
||||
|
||||
foreach (DemoBrandGallerySeeder::GALLERIES as $items) {
|
||||
foreach ($items as $item) {
|
||||
$demoImages[] = $item[0];
|
||||
}
|
||||
}
|
||||
|
||||
$removed = 0;
|
||||
|
||||
foreach (CmsBrand::query()->whereNotNull('gallery')->get() as $brand) {
|
||||
$gallery = CmsBrand::normalizeGallery($brand->gallery);
|
||||
$kept = array_values(array_filter(
|
||||
$gallery,
|
||||
fn (array $item) => ! in_array($item['image'], $demoImages, true),
|
||||
));
|
||||
|
||||
if (count($kept) === count($gallery)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$removed += count($gallery) - count($kept);
|
||||
$brand->gallery = $kept;
|
||||
$brand->save();
|
||||
}
|
||||
|
||||
$this->command?->info("Demobilder aus den Galerien entfernt: {$removed}.");
|
||||
}
|
||||
}
|
||||
|
|
@ -17,8 +17,8 @@ use Illuminate\Database\Seeder;
|
|||
* bzw. „Visualisierung“) – die Motive der Kategorie `project` muessen deshalb vor dem Livegang
|
||||
* durch echte Referenzen ersetzt werden. Steht als Punkt in der Deployment-Checkliste.
|
||||
*
|
||||
* Wieder entfernen:
|
||||
* php artisan tinker --execute="App\Models\CmsBrand::query()->update(['gallery' => null]);"
|
||||
* Wieder entfernen – nur die Demobilder, gepflegte Bilder bleiben:
|
||||
* php artisan db:seed --class=DemoBrandGalleryCleanupSeeder
|
||||
*/
|
||||
class DemoBrandGallerySeeder extends Seeder
|
||||
{
|
||||
|
|
@ -27,7 +27,7 @@ class DemoBrandGallerySeeder extends Seeder
|
|||
*
|
||||
* @var array<string, list<array{0: string, 1: string, 2: string}>>
|
||||
*/
|
||||
private const GALLERIES = [
|
||||
public const GALLERIES = [
|
||||
'cabinet' => [
|
||||
['b2a.webp', CmsBrand::GALLERY_PROJECT, 'Wohnwand mit Einbauregal, Bielefeld'],
|
||||
['hero-room.webp', CmsBrand::GALLERY_PROJECT, 'Wohnbereich mit Raumteiler'],
|
||||
|
|
|
|||
136
database/seeders/OtterpohlBrandSeeder.php
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\CmsBrand;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Otterpohl Möbelservice, Rheda-Wiedenbrück – die vierte Kernmarke (Nachtrag 15, Text v2 in Nachtrag 19).
|
||||
*
|
||||
* Ersetzt den Platzhalter „Tischlerei“ aus Nachtrag 9 durch die Zulieferung:
|
||||
* dev/2026-08-28/otterpohl_Webseitentext_FINAL_v2.md (Text vom 24.09.2026, Überschriften 1:1 übernommen;
|
||||
* löst die lange Fassung vom 17.09. ab)
|
||||
* dev/2026-08-28/markenlogos/otterpohl.jpg (Logo, freigestellt nach public/img/assets/marken/)
|
||||
*
|
||||
* Der Slug `tischlerei` bleibt stabil (Links, Widget-Thema, Kartenposition der Herkunfts-Sektion).
|
||||
* Überschrieben werden Name, Claim, Texte, Leistungen, Abschnitte, Abschlussblock und
|
||||
* Fertigungsort. Bild, Galerie und Reihenfolge bleiben, das Logo wird nur gesetzt, wenn es leer ist.
|
||||
* {@see CmsBrandSeeder} nutzt dieselben Daten für frische Installationen.
|
||||
*
|
||||
* php artisan db:seed --class=OtterpohlBrandSeeder
|
||||
*/
|
||||
class OtterpohlBrandSeeder extends Seeder
|
||||
{
|
||||
public const SLUG = 'tischlerei';
|
||||
|
||||
public const LOGO = 'marken/otterpohl.png';
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$brand = CmsBrand::query()->where('slug', self::SLUG)->first();
|
||||
|
||||
if (! $brand) {
|
||||
$this->command?->warn('Marke „'.self::SLUG.'“ nicht gefunden – erst CmsBrandSeeder ausführen.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$brand->fill(self::data());
|
||||
|
||||
if (trim((string) $brand->logo) === '') {
|
||||
$brand->logo = self::LOGO;
|
||||
}
|
||||
|
||||
$brand->save();
|
||||
|
||||
$this->command?->info('Otterpohl: Marke „'.self::SLUG.'“ mit den Texten vom 24.09.2026 (v2) aktualisiert.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Alle Textfelder der Marke. Ohne slug, logo, image, gallery, order.
|
||||
*
|
||||
* Reihenfolge wie in der Zulieferung v2: Einleitung „Wollmilchsau“, zwei Abschnitte, die acht
|
||||
* Leistungen, zum Schluss „Das Ganze statt nur das Einzelne“ – der steht als Abschlussblock
|
||||
* (cta_title/cta_text), damit er auf der Seite hinter Leistungen und Galerie bleibt.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function data(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['de' => 'Otterpohl Möbelservice', 'en' => 'Otterpohl Möbelservice'],
|
||||
'tagline' => ['de' => 'Nicht easy click & buy. Aber dennoch easy to get.', 'en' => 'Not easy click & buy. But still easy to get.'],
|
||||
'teaser' => [
|
||||
'de' => 'Individuelle Möbel, Türen, Treppen, Küchen, Wandverkleidungen und Objektbau – bis hin zum kompletten Innenausbau. Tischlerhandwerk aus Rheda-Wiedenbrück.',
|
||||
'en' => 'Bespoke furniture, doors, stairs, kitchens, wall cladding and contract work – up to complete interior fit-outs. Joinery from Rheda-Wiedenbrück.',
|
||||
],
|
||||
'summary' => [
|
||||
'de' => 'Individuelle Möbel. Türen. Treppen. Küchen. Wandverkleidungen. Objektbau. Sonderanfertigungen. Lackierungen und Echtholzfurniere. Bis hin zum kompletten Innenausbau. Mit Otterpohl Möbelservice haben wir einen Partner an unserer Seite, bei dem die Möglichkeiten nicht am einzelnen Möbelstück aufhören.
|
||||
|
||||
Denn manchmal braucht ein Raum mehr als einen Schrank oder ein Sideboard. Vielleicht soll eine Treppe Teil des Gestaltungskonzepts werden. Eine Wand erhält eine individuelle Holzverkleidung. Oder Möbel, Materialien und bauliche Elemente müssen so zusammenspielen, dass am Ende alles wie aus einem Guss wirkt. Genau dort beginnt der individuelle Innenausbau.',
|
||||
'en' => 'Bespoke furniture. Doors. Stairs. Kitchens. Wall cladding. Contract work. Special constructions. Lacquer and real-wood veneer. Up to complete interior fit-outs. With Otterpohl Möbelservice we have a partner whose possibilities do not end at the single piece of furniture.
|
||||
|
||||
Sometimes a room needs more than a wardrobe or a sideboard. A staircase becomes part of the design. A wall gets individual wood cladding. Or furniture, materials and built elements have to work together so that everything looks as if cast from one mould. That is where individual interior fit-out begins.',
|
||||
],
|
||||
'highlights' => [
|
||||
'Individuelle Möbel, Türen, Treppen und Küchen',
|
||||
'Innenausbau, Objektbau, Lack und Echtholzfurnier',
|
||||
'Planung und Handwerk aus einer Hand – inklusive Montage',
|
||||
],
|
||||
'description' => ['de' => '', 'en' => ''],
|
||||
'services' => [
|
||||
['title' => 'Individuelle Möbel & Sonderanfertigungen', 'description' => 'Möbel nach Maß, besondere Konstruktionen und individuelle Lösungen – nach Planung, Idee, Skizze oder konkreter Anforderung.'],
|
||||
['title' => 'Innenausbau', 'description' => 'Von einzelnen Ausbauarbeiten bis zum ganzheitlichen Raumkonzept – inklusive individueller Verkleidungen und maßgefertigter Einbauten.'],
|
||||
['title' => 'Türen & Treppen', 'description' => 'Individuelle Türen, Treppen und konstruktive Lösungen, die sich in Architektur und Einrichtung einfügen.'],
|
||||
['title' => 'Küchen', 'description' => 'Individuelle Küchenlösungen und handwerkliche Ergänzungen auch dort, wo klassische Küchenprogramme an ihre Grenzen kommen.'],
|
||||
['title' => 'Wandverkleidungen & besondere Details', 'description' => 'Holzverkleidungen, Lamellen, Paneele und individuelle Gestaltungselemente, die Architektur und Einrichtung miteinander verbinden.'],
|
||||
['title' => 'Objektbau', 'description' => 'Individuelle Lösungen für Büros, Verkaufsräume und gewerblich genutzte Flächen – vom Einzelmöbel bis zum kompletten Ausbau.'],
|
||||
['title' => 'Lack & Echtholzfurnier', 'description' => 'Individuelle Lackierungen und Furniere für Lösungen, bei denen Farbe, Material und Oberfläche exakt aufeinander abgestimmt werden sollen.'],
|
||||
['title' => 'Montage', 'description' => 'Die Umsetzung endet nicht bei der Fertigung. Entscheidend ist, dass Möbel, Ausbau und Raum am Ende auch vor Ort sauber zusammenkommen.'],
|
||||
],
|
||||
'sections' => [
|
||||
[
|
||||
'title' => 'Ziemlich nah an der eierlegenden Wollmilchsau',
|
||||
'paragraphs' => [
|
||||
'Der Begriff ist vielleicht nicht besonders elegant. Aber ganz falsch ist er nicht.',
|
||||
'Individuelle Möbel. Türen. Treppen. Küchen. Wandverkleidungen. Objektbau. Sonderanfertigungen. Lackierungen und Echtholzfurniere. Bis hin zum kompletten Innenausbau.',
|
||||
'Mit Otterpohl Möbelservice haben wir einen Partner an unserer Seite, bei dem die Möglichkeiten nicht am einzelnen Möbelstück aufhören.',
|
||||
'Denn manchmal braucht ein Raum mehr als einen Schrank oder ein Sideboard. Vielleicht soll eine Treppe Teil des Gestaltungskonzepts werden. Eine Wand erhält eine individuelle Holzverkleidung. Eine Tür soll sich selbstverständlich in den Raum einfügen. Oder Möbel, Materialien und bauliche Elemente müssen so zusammenspielen, dass am Ende alles wie aus einem Guss wirkt.',
|
||||
],
|
||||
'claim' => 'Genau dort beginnt der individuelle Innenausbau.',
|
||||
],
|
||||
[
|
||||
'title' => 'Vom einzelnen Möbel bis zum ganzen Raum',
|
||||
'paragraphs' => [
|
||||
'Wir arbeiten mit Herstellern, die schon sehr viele individuelle Möglichkeiten bieten. CABINET, Rotpunkt und Febrü geben uns dafür eine starke Basis. Aber es gibt Projekte, bei denen die Lösung darüber hinausgeht.',
|
||||
'Mit Otterpohl Möbelservice können wir deshalb nicht nur individuelle Möbel und Sonderanfertigungen realisieren, sondern auch Türen, Treppen, Küchen, Wand- und Deckenverkleidungen sowie komplette Innenausbauten und Lösungen für den Objektbereich einbeziehen.',
|
||||
],
|
||||
'claim' => 'So können wir Räume ganzheitlicher denken – und Details nicht isoliert betrachten.',
|
||||
],
|
||||
[
|
||||
'title' => 'Planung trifft Handwerk',
|
||||
'paragraphs' => [
|
||||
'Manchmal entsteht die beste Lösung aus einem Möbelprogramm. Manchmal braucht es eine individuelle Ergänzung. Und manchmal muss etwas komplett neu gedacht und gebaut werden. Genau diese Kombination macht für uns den Unterschied.',
|
||||
'Wir planen den Raum und verbinden bei Bedarf unterschiedliche Hersteller, Materialien und handwerkliche Lösungen miteinander. Otterpohl Möbelservice ergänzt diese Planung um die Möglichkeiten einer klassischen Tischlerei und eines erfahrenen Innenausbauers.',
|
||||
],
|
||||
'claim' => 'Das Ergebnis kann ein einzelnes Möbel sein. Oder ein kompletter Raum.',
|
||||
],
|
||||
],
|
||||
'widget_label' => ['de' => 'Persönliche Beratung zur Maßarbeit vereinbaren', 'en' => 'Arrange a personal consultation on bespoke work'],
|
||||
'cta_title' => ['de' => 'Das Ganze statt nur das Einzelne', 'en' => 'The whole, not just the single piece'],
|
||||
'cta_text' => [
|
||||
'de' => 'Genau deshalb passt Otterpohl Möbelservice zu unserem Ansatz: Wir möchten Räume nicht in einzelne Gewerke zerlegen, sondern Lösungen schaffen, bei denen Möbel, Materialien, Architektur und handwerkliche Details zusammenpassen. Mal braucht es dafür einen Hersteller. Mal einen Tischler. Und manchmal beides. Nicht easy click & buy. Aber dennoch easy to get.',
|
||||
'en' => 'That is why Otterpohl Möbelservice fits our approach: we do not want to split rooms into single trades, but create solutions where furniture, materials, architecture and craft details belong together. Sometimes that takes a manufacturer. Sometimes a joiner. And sometimes both. Not easy click & buy. But still easy to get.',
|
||||
],
|
||||
'origin_city' => ['de' => 'Rheda-Wiedenbrück', 'en' => 'Rheda-Wiedenbrück'],
|
||||
'origin_distance' => ['de' => 'rund 35 km', 'en' => 'about 35 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Tischlerei und Innenausbau aus Rheda-Wiedenbrück – vom einzelnen Möbel bis zum ganzen Raum, inklusive Montage.',
|
||||
'en' => 'Joinery and interior fit-out from Rheda-Wiedenbrück – from a single piece to the whole room, installation included.',
|
||||
],
|
||||
'showroom' => '',
|
||||
'is_published' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
83
database/seeders/WhatsappNumberSeeder.php
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use FluxCms\Core\Models\CmsContent;
|
||||
use FluxCms\Core\Services\CmsContentService;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Hinterlegt die WhatsApp-Business-Nummer des Stores (Abstimmung 23.09.2026, Nachtrag 18).
|
||||
*
|
||||
* Der WhatsApp-Button im Terminanfrage-Widget liest `global.contact.whatsapp`. Auf der Vorschau
|
||||
* stand dort der Platzhalter „+49 151 00000000“ aus der ersten Einrichtung, danach kurz die
|
||||
* falsche Nummer vom 23.09. Der Seeder trägt die echte Nummer ein, wenn das Feld leer ist oder
|
||||
* einen dieser beiden Werte enthält; eine im Admin gepflegte andere Nummer bleibt stehen. Fehlt der Eintrag `global.contact` ganz, wird er
|
||||
* angelegt. Mehrfaches Ausführen ist unbedenklich.
|
||||
*
|
||||
* php artisan db:seed --class=WhatsappNumberSeeder
|
||||
*/
|
||||
class WhatsappNumberSeeder extends Seeder
|
||||
{
|
||||
/** Business-Nummer laut Marcel (0521-98620100, korrigiert am 24.09.), international geschrieben. */
|
||||
public const NUMBER = '+49 521 98620100';
|
||||
|
||||
/**
|
||||
* Werte, die der Seeder ersetzt: der Platzhalter aus der ersten Einrichtung und die am 23.09.
|
||||
* versehentlich eingetragene falsche Nummer (Zahlendreher in der Zulieferung).
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private const PLACEHOLDERS = ['+49 151 00000000', '+49 521 98610020'];
|
||||
|
||||
/** @var array<int, string> */
|
||||
private const LOCALES = ['de', 'en'];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$row = CmsContent::query()->where('group', 'global')->where('key', 'contact')->first();
|
||||
|
||||
if (! $row) {
|
||||
CmsContent::create([
|
||||
'group' => 'global',
|
||||
'key' => 'contact',
|
||||
'type' => 'json',
|
||||
'value' => array_fill_keys(self::LOCALES, ['whatsapp' => self::NUMBER]),
|
||||
'order' => 0,
|
||||
]);
|
||||
app(CmsContentService::class)->clearCache('global');
|
||||
|
||||
$this->command?->info('WhatsApp-Nummer: Eintrag global.contact angelegt.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $row->getTranslations('value');
|
||||
$changed = 0;
|
||||
|
||||
foreach (self::LOCALES as $locale) {
|
||||
$section = is_array($value[$locale] ?? null) ? $value[$locale] : [];
|
||||
$current = trim((string) ($section['whatsapp'] ?? ''));
|
||||
|
||||
if ($current !== '' && ! in_array($current, self::PLACEHOLDERS, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$section['whatsapp'] = self::NUMBER;
|
||||
$value[$locale] = $section;
|
||||
$changed++;
|
||||
}
|
||||
|
||||
if ($changed === 0) {
|
||||
$this->command?->info('WhatsApp-Nummer: bereits gepflegt, nichts geändert.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$row->value = $value;
|
||||
$row->save();
|
||||
app(CmsContentService::class)->clearCache('global');
|
||||
|
||||
$this->command?->info("WhatsApp-Nummer gesetzt ({$changed} Sprache(n)).");
|
||||
}
|
||||
}
|
||||
BIN
dev/2026-08-28/Bildschirmfoto 2026-09-24 um 17.30.16.png
Normal file
|
After Width: | Height: | Size: 5.5 MiB |
BIN
dev/2026-08-28/Farbwelt und Stil.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
|
|
@ -38,9 +38,12 @@ ausführlichen Text. `php artisan test --compact`: **873 grün** (3217 Assertion
|
|||
| AP5 Rubrik-Seite „Referenzen" | ✅ |
|
||||
| Arbeitsplan 14.09. · 1.1 Farbwelt, Logo je Welt, Welten-Wechsler | ✅ (Nachtrag 6) |
|
||||
| Welten-Trennung: Navigation, Magazin, Über uns, FAQ, Widget je Welt | ✅ (Nachtrag 7, Erweiterung zu 1.1) |
|
||||
| Arbeitsplan 14.09. · 1.2 Tischlerei als vierte Marke (Platzhalter) | ✅ (Nachtrag 9) |
|
||||
| Arbeitsplan 14.09. · 1.2 Tischlerei als vierte Marke | ✅ Otterpohl mit Text und Logo (Nachtrag 9 + 15) |
|
||||
| Arbeitsplan 14.09. · 1.4 Akkordeon und Widget-Beschriftung je Marke | ✅ (bereits seit 03.09., CABINET-Text fehlt im CMS) |
|
||||
| Arbeitsplan 14.09. · 1.3 Herkunfts-Sektion „Aus der Region gefertigt“ | ✅ (Nachtrag 10 + 11: Standorte an der Marke, Ablauf CABINET von Marcel bestätigen lassen) |
|
||||
| Abstimmung 23.09. · WhatsApp zuerst im Widget, Business-Nummer, Farbvariante „Modern“ mit Schalter | ✅ (Nachtrag 18, Palette von Marcel entscheiden lassen) |
|
||||
| Briefing 24.09. · Entwurf Startseite „Greige“ mit Gold-Logo, Hero-Slideshow, Projekttext | ✅ Entwurf unter /entwurf (Nachtrag 19); offen: Foto Marcel, Slides selbst pflegen, Logo-Verläufe |
|
||||
| Zulieferung 24.09. · Otterpohl Text v2, Bilder (Werkstatt + Referenzen) | ✅ (Nachtrag 19, Unterschriften und Referenzen-Fallback von Marcel bestätigen lassen) |
|
||||
|
||||
### Nächste Schritte
|
||||
|
||||
|
|
@ -55,7 +58,7 @@ gebaut. Was fehlt, ist Material, kein Code.
|
|||
| Echte Projektfotos statt der Demobilder (siehe Warnung unten) | Kernmarken → Bildergalerie |
|
||||
| Herstellerseiten-URLs | Kernmarken → Herstellerseite |
|
||||
| Motiv für den Kopf von `/marken` | Inhalte → `marken` → `hero` |
|
||||
| WhatsApp-Nummer | Inhalte → `global` → `contact` → `whatsapp` |
|
||||
| WhatsApp-Nummer – seit Nachtrag 18 per Seeder gesetzt (0521-98620100), Änderung weiter hier | Inhalte → `global` → `contact` → `whatsapp` |
|
||||
| Welches Display auf der Website läuft | Inhalte → `referenzen` → `display_feed` → `display_id` |
|
||||
|
||||
**Beim Deployment mit erledigen (Abschnitt 8):**
|
||||
|
|
@ -1267,7 +1270,7 @@ php artisan db:seed --class=BrandLogoSeeder --force
|
|||
# Die Migration ordnet die drei Dubai-Beitraege der Immobilien-Welt zu.
|
||||
php artisan db:seed --class=WorldSplitCmsSeeder --force
|
||||
|
||||
# 8. Vierte Kernmarke „Tischlerei“ als Platzhalter (Nachtrag 9) – legt nur an, was fehlt.
|
||||
# 8. Vierte Kernmarke Otterpohl, Slug „tischlerei“ (Nachtrag 9 + 15) – legt nur an, was fehlt.
|
||||
php artisan db:seed --class=CmsBrandSeeder --force
|
||||
|
||||
# 9. Herkunfts-Sektion „Aus der Region gefertigt“ (Nachtrag 10) – legt die Sektion nur an, wenn sie
|
||||
|
|
@ -1278,6 +1281,33 @@ php artisan db:seed --class=OriginSectionCmsSeeder --force
|
|||
# deren Ort noch leer ist. Die Migration davor legt die drei Spalten an.
|
||||
php artisan db:seed --class=BrandOriginSeeder --force
|
||||
|
||||
# 11. Otterpohl (Nachtrag 15 + 19): ersetzt den Platzhalter „Tischlerei“ durch Name, Claim, Texte,
|
||||
# Leistungen, Abschnitte, Abschlussblock und Fertigungsort aus der Zulieferung v2 vom 24.09.
|
||||
# Überschreibt auch einen Stand mit dem Text vom 17.09. Bild, Galerie und Reihenfolge
|
||||
# bleiben; das Logo wird nur gesetzt, wenn es leer ist.
|
||||
# Schritt 4 (BrandPageTextSeeder) bringt das Feedback vom 17.09. für CABINET, Rotpunkt
|
||||
# und Febrü mit – er überschreibt dort wie dokumentiert die Textfelder.
|
||||
php artisan db:seed --class=OtterpohlBrandSeeder --force
|
||||
|
||||
# 12. Bilder für CABINET, Rotpunkt, Febrü (Nachtrag 16) und Otterpohl (Nachtrag 19): Titelbild nur,
|
||||
# wenn keins gepflegt ist; Galeriebilder nur, wenn die Datei noch nicht in der Galerie steht.
|
||||
# Die WebP-Dateien liegen unter public/img/assets/marken/{cabinet,rotpunkt,februe,otterpohl}/
|
||||
# im Repository.
|
||||
php artisan db:seed --class=BrandImageSeeder --force
|
||||
|
||||
# 13. Demobilder aus den Galerien entfernen (Nachtrag 17) – erkennt sie am Dateinamen des
|
||||
# Demo-Seeders, echte Bilder bleiben. Nach Schritt 12 ausführen.
|
||||
php artisan db:seed --class=DemoBrandGalleryCleanupSeeder --force
|
||||
|
||||
# 14. WhatsApp-Business-Nummer 0521-98620100 (Nachtrag 18): ersetzt nur den Platzhalter
|
||||
# „+49 151 00000000“, die falsche Nummer vom 23.09. (…98610020) oder ein leeres Feld –
|
||||
# eine im Admin gepflegte andere Nummer bleibt.
|
||||
php artisan db:seed --class=WhatsappNumberSeeder --force
|
||||
|
||||
# 15. Projekttext auf der Startseite (Briefing 24.09., Punkt 7; Nachtrag 19): ersetzt in
|
||||
# home/audience_switch nur den Satz „Mit deutscher Vertragssicherheit …“. Idempotent.
|
||||
php artisan db:seed --class=AudienceSwitchProjectTextSeeder --force
|
||||
|
||||
php artisan config:cache
|
||||
php artisan view:clear
|
||||
|
||||
|
|
@ -1312,6 +1342,15 @@ done
|
|||
- [ ] Logo in Header, Footer **und** in einer Test-Mail
|
||||
- [ ] Alte Magazin-URLs `/magazin/1` … `/magazin/5` leiten per 301 auf den Slug
|
||||
- [ ] `/service` und `/portfolio` leiten um statt 500 zu liefern
|
||||
- [ ] Terminanfrage-Widget: WhatsApp-Button oben, Link führt auf `wa.me/4952198620100`, darunter
|
||||
„oder per Formular“ und das Formular
|
||||
- [ ] Interior-Seiten laufen in „Neu“ (`<html class="palette-modern">`), kein Farb-Schalter;
|
||||
`/immobilien` unverändert blau
|
||||
- [ ] `/entwurf`: Gold-Logo in Kopf und Fuß, Hero mit Goldkante und laufender Slideshow (Marken-
|
||||
Titelbilder), keine Ankündigungsleiste, ein Button im Hero, Termin-Widget unten rechts
|
||||
- [ ] `/marken/tischlerei`: Text v2 (drei Abschnitte, acht Leistungen, Abschluss „Das Ganze statt
|
||||
nur das Einzelne“), Werkstatt-Titelbild, Galerie „Referenzen und Einblicke“ mit den Badges
|
||||
„Realisiertes Projekt“ und „Aus der Werkstatt“
|
||||
- [ ] **Die vier Displays sichten:** „Display 4 – Innen", „Kundenstopper", „Display 2",
|
||||
„Test-Display". Besonders das Modul „Cabinet-Influencerin" – dessen Video lag auf
|
||||
`b2in.test`. Ob die Datei auf dem Live-Server überhaupt existiert, ließ sich lokal nicht
|
||||
|
|
@ -1809,3 +1848,387 @@ auch 1,5 annehmen. Das Feld war ganzzahlig (`int`, Zahlenfeld mit `min=1`).
|
|||
|
||||
**Tests:** vier neue Fälle in `tests/Feature/DisplayVersionTest.php` (Komma-Eingabe,
|
||||
Parser/Formatierer, Ablehnung unter 0,5 s, Video ignoriert das Feld). Kein Deployment-Schritt.
|
||||
|
||||
---
|
||||
|
||||
## Nachtrag 15 · Otterpohl als vierte Marke, Feedback 17.09. eingearbeitet (17.09.2026)
|
||||
|
||||
Zulieferung 17.09.2026: `dev/2026-08-28/otterpohl_Webseitentext_FINAL.md` (Text der
|
||||
Tischlerei-Seite), `dev/2026-08-28/markenlogos/otterpohl.jpg` (Logo) und
|
||||
`dev/2026-08-28/feedback-17-09-2026.md` (Marcels Rückmeldung zu CABINET, Febrü und Rotpunkt).
|
||||
|
||||
### Otterpohl ersetzt den Platzhalter „Tischlerei“
|
||||
|
||||
- **Marke:** Name „Otterpohl“, Claim „Nicht easy to click & buy. Aber easy to get.“, Kacheltext,
|
||||
Text für /marken, drei Merkmale, Einleitung (erster Block des Textes), vier Leistungen, **13
|
||||
Abschnitte** mit den Überschriften der Zulieferung 1:1, die pointierten Schlusssätze als
|
||||
Schlusszeile je Abschnitt, Abschlussblock „Ihre Idee. Handwerklich umgesetzt.“, Fertigungsort
|
||||
Rheda-Wiedenbrück mit Rolle „seit 2014 an unserer Seite“. Slug `tischlerei` bleibt (Links,
|
||||
Widget-Thema, Kartenposition).
|
||||
- **Logo:** `public/img/assets/marken/otterpohl.png`, aus der JPG-Zulieferung freigestellt und
|
||||
knapp beschnitten (Imagick), damit es in der gemeinsamen Logo-Box wie die anderen wirkt.
|
||||
Das Logo trägt „25 Jahre“ – so geliefert, so übernommen.
|
||||
- **Seeder:** neu `OtterpohlBrandSeeder` (überschreibt bei der bestehenden Marke alle Textfelder,
|
||||
lässt Bild, Galerie und Reihenfolge stehen, Logo nur wenn leer); `CmsBrandSeeder` nutzt
|
||||
dieselben Daten für frische Installationen; `BrandLogoSeeder` und `BrandOriginSeeder` kennen
|
||||
die Marke. Lang-Fallback, Seitentitel von /marken und der Ablauf-Block der Herkunfts-Sektion
|
||||
nennen jetzt Otterpohl.
|
||||
|
||||
### Feedback 17.09. in den Markentexten (`BrandPageTextSeeder`)
|
||||
|
||||
- **CABINET:** Der Abschnitt „Aufmaß vor Ort statt Schätzung am Telefon“ widersprach Marcels
|
||||
Ablauf (erst Foto und grobes Maß, Aufmaß erst nach Angebot). Ersetzt durch „Ein Bild und ein
|
||||
grobes Maß reichen für den Anfang“ mit Marcels Fassung wörtlich und der Ablaufzeile
|
||||
„So läuft’s: Foto + grobe Maße → … → Bestellung“ als Schlusszeile. Zweiter Absatz von
|
||||
„Die Fragen, die wir vorher stellen“ ersetzt (Karnevalskostüme, Sportsachen).
|
||||
- **Febrü:** nur die vereinbarten Stellen: „Bei uns gibt es Kunden – keine Schubladen“ (drei
|
||||
Absätze), „Aus Herford“ mit „sehr hohe Fertigungstiefe“ statt 95 Prozent, „Wertschätzung ist
|
||||
keine Frage der Stückzahl“ (Familienalltag statt Mütter-Passage). Der doppelte Schlusssatz
|
||||
„Ob im Kleinen oder im Großen …“ steht nur noch beim ersten Abschnitt. „Nicht drei Marken“,
|
||||
„Das Wir und das Ich“ und der mitlaufende CTA unverändert.
|
||||
- **Rotpunkt:** „Mehr als Küchen“ mit Marcels drei Absätzen ersetzt.
|
||||
- **Handwerk als gemeinsamer Abschluss (Punkt 3b):** Die markeneigenen Abschnitte
|
||||
„Schranksystem plus Handwerk“ (CABINET) und „Küchenstudio plus Handwerk“ (Rotpunkt) sind
|
||||
entfallen. Stattdessen gibt es **eine** Sektion `brand_craft` („Planung plus Handwerk“, Lang-
|
||||
Datei, im Inhalte-Editor unter Marken → „Handwerk-Abschluss auf jeder Markenseite“ anlegbar),
|
||||
die `BrandDetail` als letzten Abschnitt auf jeder Markenseite anhängt – außer auf der
|
||||
Otterpohl-Seite selbst. Der Link „Mehr zu Otterpohl“ erscheint nur, wenn die Marke
|
||||
veröffentlicht ist. Anker `#handwerk`. Einmal gepflegt, überall gleich – genau wie gewünscht.
|
||||
|
||||
**Lokal ausgeführt:** `OtterpohlBrandSeeder`, `BrandPageTextSeeder`, `BrandLogoSeeder`.
|
||||
Beim Deployment: Abschnitt 8.2, Schritt 4 (Feedback) und Schritt 11 (Otterpohl).
|
||||
|
||||
**Offen:** Stimmungsbild und Galerie für Otterpohl (im Admin nachtragen); Nachtrag 10 bleibt
|
||||
offen, ob CABINET selbst Aufbau anbietet. Der Text bestätigt Rheda-Wiedenbrück und die
|
||||
Montage-Partnerschaft seit 2014 – die Herkunfts-Sektion passt damit.
|
||||
|
||||
**Tests:** `tests/Feature/BrandFeedbackTest.php` (10 Fälle: CABINET-Einstieg und Fragen, Febrü-
|
||||
Passagen und einmaliger Schlusssatz, Rotpunkt „Mehr als Küchen“, Handwerk-Abschluss auf drei
|
||||
Seiten mit Link und Position, nicht auf Otterpohl, ohne Link bei unveröffentlichter Marke,
|
||||
CMS-Vorrang, Otterpohl-Seeder behält Logo/Bild/Reihenfolge); `FourthBrandTest`,
|
||||
`MarkenPageTest` und `BrandPageSectionsTest` auf die neuen Texte umgestellt.
|
||||
|
||||
---
|
||||
|
||||
## Nachtrag 16 · Erste Bilder für CABINET und Rotpunkt (22.09.2026)
|
||||
|
||||
Zulieferung 22.09.2026 unter `dev/2026-08-28/images/{cabinet,rotpunkt}/`, das Titelbild je
|
||||
Marke mit `_title_` markiert. 12 Bilder CABINET, 14 Bilder Rotpunkt, alles Visualisierungen des
|
||||
Herstellers (Renderings), 3–6 MB je Datei.
|
||||
|
||||
- **Optimierung:** alle Bilder als WebP mit maximal 1920 px Kantenlänge, Qualität 82, ohne
|
||||
Metadaten (Imagick) nach `public/img/assets/marken/cabinet/` und `…/rotpunkt/`; 55–330 KB je
|
||||
Datei statt mehrerer MB. Sprechende Dateinamen aus den Motiven („dachschraegenschrank-filo-
|
||||
cashmere.webp“, „kueche-07.webp“). Die Originale bleiben per `.gitignore` außerhalb des
|
||||
Repositorys, nur die WebP-Fassungen werden deployt.
|
||||
- **Titelbild** (`image`): CABINET „Ankleide Solitär, Eiche Tano“, Rotpunkt „Küche 01“. Es
|
||||
erscheint im Seitenkopf der Markenseite, auf der Startseiten-Kachel und im Markenblock auf
|
||||
/marken – dort, wo bisher der Platzhalter stand.
|
||||
- **Galerie:** die übrigen Bilder als **Visualisierung** mit Bildunterschrift aus dem Motiv.
|
||||
Die Rubrik passt: Badge „Visualisierung“, Hinweis „Entwürfe und Visualisierungen“. Unter
|
||||
„Referenzen“ erscheinen sie damit bewusst nicht – das sind keine von uns ausgeführten Projekte.
|
||||
Marcel kann jedes Bild im Admin umkategorisieren, sobald ein echtes Projekt dahintersteht.
|
||||
- **`BrandImageSeeder`:** Titelbild nur, wenn keins gepflegt ist; Galeriebilder nur, wenn die
|
||||
Datei noch nicht in der Galerie steht; vorhandene Einträge bleiben samt Kategorie und
|
||||
Unterschrift. Mehrfach ausführbar. Febrü und Otterpohl bleiben unberührt (keine Zulieferung).
|
||||
|
||||
**Lokal ausgeführt:** `php artisan db:seed --class=BrandImageSeeder`. Beim Deployment:
|
||||
Abschnitt 8.2, Schritt 12 – die Dateien kommen mit dem Repository.
|
||||
|
||||
**Ergänzung 23.09.2026:** Febrü nachgeliefert (`dev/2026-08-28/images/februe/`, 16 Bilder,
|
||||
Titelbild „Base Thekenlösung“). Gleiche Behandlung, Bildunterschriften nach den Febrü-
|
||||
Produktlinien aus den Dateinamen („Active 2.0 mit Bureo“, „Arena Stage“, „Vio“ …). Der Seeder
|
||||
und `BrandImageSeederTest` decken jetzt drei Marken ab.
|
||||
|
||||
**Offen:** Bilder für Otterpohl; Freigabe der Bildunterschriften durch Marcel
|
||||
(aus den Dateinamen abgeleitet, z. B. Frontdekore „Filo Cashmere“, „Velluto Fango“, bei Febrü
|
||||
die Produktlinien).
|
||||
|
||||
**Tests:** `tests/Feature/BrandImageSeederTest.php` (4 Fälle: Titelbild und Galerie, jede
|
||||
referenzierte Datei existiert als optimierte Datei unter 400 KB und 1920 px, zweiter Lauf
|
||||
lässt Gepflegtes stehen, Anzeige auf Startseite, /marken, Markenseite und nicht unter Referenzen).
|
||||
|
||||
---
|
||||
|
||||
## Nachtrag 17 · Demobilder raus, Galerie ohne Rubrikwechsel (22.09.2026)
|
||||
|
||||
Abstimmung 22.09. nach den ersten echten Bildern: die Demobilder aus AP2 müssen weg, der
|
||||
Rubrikwechsel „Realisierte Projekte / Inspirationen“ ist ausgesetzt, solange es keine
|
||||
realisierten Projekte gibt, und die zwei Zeilen unter der Galerie-Überschrift werden eine.
|
||||
|
||||
- **`DemoBrandGalleryCleanupSeeder`** entfernt die elf Demobilder (CABINET 4, Rotpunkt 3, Febrü
|
||||
4) aus den Galerien. Erkennung am Dateinamen aus `DemoBrandGallerySeeder`, nicht an der
|
||||
Unterschrift – den Vermerk „BEISPIEL“ hatte Schritt 5 auf der Vorschau bereits entfernt.
|
||||
Alle anderen Bilder bleiben. Idempotent.
|
||||
- **Galerie** auf der Markenseite: ein Raster, realisierte Projekte zuerst, jedes Bild trägt
|
||||
weiter sein Badge („Realisiertes Projekt“ / „Visualisierung“). Kein Tab-Wechsel, keine
|
||||
Gruppenüberschriften, kein separater Hinweistext. Die Kategorie im Admin bleibt bestehen,
|
||||
der Wechsel lässt sich wieder einschalten, sobald echte Projekte da sind.
|
||||
- **Texte:** Überschrift „Inspirationen und Entwürfe“, eine Zeile darunter: „Visualisierungen,
|
||||
die zeigen, was in einem Raum möglich ist – noch bevor das erste Möbel steht.“ (vorher
|
||||
„Referenzen und Inspirationen“ plus Hinweis unter der Rubrik).
|
||||
|
||||
**Lokal ausgeführt:** `php artisan db:seed --class=DemoBrandGalleryCleanupSeeder` (11 entfernt).
|
||||
Beim Deployment: Abschnitt 8.2, Schritt 13, nach Schritt 12.
|
||||
|
||||
**Tests:** `BrandPageTest` umgestellt (ein Raster mit Badges und Projekten zuerst, genau eine
|
||||
Zeile unter der Überschrift, Aufräum-Seeder entfernt nur Demobilder und ist idempotent).
|
||||
|
||||
## Nachtrag 18 · WhatsApp zuerst, Business-Nummer, Farbvariante „Modern“ (23.09.2026)
|
||||
|
||||
Rückmeldung von Marcel über Kevin am 23.09.: Im Termin-Widget soll WhatsApp oben stehen und das
|
||||
Formular darunter; als Nummer gilt die Business-Nummer 0521-98620100 (am 23.09. kam zunächst
|
||||
…98610020, Korrektur von Marcel am 24.09.). Außerdem wirkt ihm die
|
||||
Farbwelt „zu altbacken“ – die beigen Flächen. Gewünscht ist eine klarere, modernere Variante, die
|
||||
farblich am Logo bleibt (Tanne und Ocker unverändert), sowie ein schwimmender Schalter, um beide
|
||||
Stände direkt zu vergleichen.
|
||||
|
||||
### Termin-Widget
|
||||
|
||||
- **Reihenfolge:** WhatsApp-Block direkt unter der Einleitung, darunter eine Trennzeile „oder per
|
||||
Formular“, dann das Formular. Einleitung jetzt „Am schnellsten per WhatsApp – wir bereiten die
|
||||
Nachricht mit Ihren Angaben vor.“ Der Hinweis, dass der Besucher selbst abschickt, bleibt.
|
||||
Der vorbereitete Text liest weiter Anliegen, Wunschtermin und Name aus dem Formular, auch wenn
|
||||
es jetzt unter dem Button steht. Ohne Nummer im CMS erscheint weder Button noch Trennzeile.
|
||||
- **Nummer:** `WhatsappNumberSeeder` trägt `+49 521 98620100` in `global.contact.whatsapp` ein
|
||||
(de und en), aber nur, wenn das Feld leer ist, noch den Platzhalter „+49 151 00000000“ aus der
|
||||
ersten Einrichtung enthält oder die falsche Nummer vom 23.09. (`+49 521 98610020`) – die hatte
|
||||
Marcel versehentlich geschickt, auf der Vorschau lief der Button deshalb ins Leere. Eine im
|
||||
Admin gepflegte andere Nummer bleibt stehen. Fehlt der Eintrag, wird er angelegt. Idempotent.
|
||||
Ergebnis-Link: `https://wa.me/4952198620100`. Auf einer Vorschau ohne Portal reicht
|
||||
`php artisan db:seed --class=WhatsappNumberSeeder --force`.
|
||||
|
||||
### Farbvariante „Modern“
|
||||
|
||||
Eine zweite Interior-Palette als Override in `theme-b2in.css` (`.palette-modern .world-interior`).
|
||||
Sie ändert **nur Flächen**, keine Logofarben:
|
||||
|
||||
| Token | Bisher (Creme) | Modern |
|
||||
|---|---|---|
|
||||
| Hintergrund | Creme `#f7f4ef` | fast weiß, leicht warm `#fbfaf8` |
|
||||
| Karten | `#fbf9f5` | reinweiß |
|
||||
| Sektionswechsel (`bg-accent`) | Sand `#d9cbbf` | helles Grau-Beige `#efece7` |
|
||||
| Ruhige Flächen (`bg-muted`) | helles Sand | `#f2f0ed` |
|
||||
| Linien / Eingabefelder | Sand-Töne | `#e6e2dc` / weiß |
|
||||
| Hero-Bühne | Sand | helles Grau-Beige |
|
||||
| Footer | Sand mit Tanne | Tanne mit Creme, Ocker als Akzent |
|
||||
| Tanne, Ocker, Clay, dunkle Sektionen, CTA-Band | unverändert | unverändert |
|
||||
|
||||
Karten liegen jetzt weiß auf fast weiß, deshalb haben `--shadow-card` und `--shadow-elevated` in
|
||||
der Variante etwas mehr Tiefe. Weil der Footer dunkel ist, trägt er in „Modern“ das **negative
|
||||
Logo** (Rückmeldung Kevin 23.09.): der Footer rendert beide Logos (`footer-logo-light` /
|
||||
`footer-logo-dark`), das Stylesheet blendet je Palette das passende ein – die Umschaltung läuft
|
||||
im Browser, ohne Server-Roundtrip. Die Immobilien-Welt bleibt bei ihrem einen negativen Logo.
|
||||
Zwei Nachbesserungen nach dem ersten Blick (Kevin 23.09.): Abschlussband und Footer sind in
|
||||
„Modern“ beide dunkel, deshalb trennt eine helle Haarlinie (`--footer-muted` bei 35 %) den Footer
|
||||
oben ab. Und der schwebende Termin-Button war als reine Kontur über Bildern und weißen Karten
|
||||
kaum zu sehen – er trägt jetzt in beiden Paletten eine Fläche in Kartenfarbe
|
||||
(`.appointment-toggle`), Hover bleibt Tanne.
|
||||
|
||||
Dritte Nachbesserung (Kevin 23.09.): der **Cookie-Button** unten links kam noch in Blau aus
|
||||
`config/cookie-consent.php` – das ist die Immobilien-Welt. `theme-b2in.css` überschreibt die
|
||||
Paketvariablen (`--cc-*`) für `.world-interior`: Button in Tanne mit dem Verlauf des
|
||||
Akzent-Buttons, Icon, Schalter, Fokusring und Link-Hover in Ocker, „Speichern“ in Tanne,
|
||||
„Akzeptieren“ bleibt grün. Gilt in beiden Paletten; `/immobilien` behält das Blau. Der dunkle Footer ist die eine bewusste Abweichung, die über
|
||||
„heller“ hinausgeht: er gibt der hellen Seite unten einen klaren Abschluss. Wenn Marcel ihn
|
||||
lieber hell hätte, sind es vier Token.
|
||||
|
||||
### Schalter
|
||||
|
||||
- `<x-palette-switch />` schwimmt links in der Mitte (unten links sitzt der Cookie-Button, unten
|
||||
rechts das Termin-Widget): eine senkrechte Pille „Farbwelt · Bisher / Neu“. Nur auf den
|
||||
Interior-Seiten von b2in, nicht auf `/immobilien`.
|
||||
- Die Wahl liegt im `localStorage` (`b2in-palette`) und wird schon im `<head>` gesetzt, bevor das
|
||||
Stylesheet greift – kein Aufblitzen in Creme. `?palette=modern` bzw. `?palette=classic` an einer
|
||||
URL überschreibt die gemerkte Wahl; damit lässt sich Marcel ein Link auf die neue Variante
|
||||
schicken: `https://<vorschau>/?palette=modern`.
|
||||
- Abschalten, sobald entschieden ist: `B2IN_PALETTE_SWITCH=false` in `.env`
|
||||
(`config('app.palette_switch')`). Dann erscheint weder Schalter noch Skript, die Seite läuft in
|
||||
der Standard-Palette. Soll „Modern“ dauerhaft werden, werden die Token in `:root` übernommen und
|
||||
der Override entfernt – ein Nachtrag, kein Umbau.
|
||||
|
||||
**Lokal ausgeführt:** `php artisan db:seed --class=WhatsappNumberSeeder` (2 Sprachen gesetzt),
|
||||
`npm run build:b2in`. Beim Deployment: Abschnitt 8.2, Schritt 14, dazu `npm run build:web`.
|
||||
|
||||
**Tests:** `WhatsappNumberSeederTest` (6: anlegen, Platzhalter ersetzen, falsche Nummer vom 23.09.
|
||||
korrigieren, gepflegte Nummer behalten, idempotent, Link auf der Seite), `AppointmentWidgetTest` (+2: WhatsApp vor dem Formular, keine
|
||||
Trennzeile ohne Nummer), `ColorWorldsTest` (+6: Schalter auf Interior-Seiten, nicht auf
|
||||
Immobilien, per Config abschaltbar, Override ohne Logofarben, negatives Logo im Footer der
|
||||
Variante, Immobilien-Footer unverändert).
|
||||
|
||||
**Offen:** Marcel entscheidet zwischen „Bisher“ und „Neu“ – oder benennt, was an „Neu“ noch
|
||||
fehlt (Footer hell/dunkel, Grad der Aufhellung). Danach Schalter abschalten.
|
||||
|
||||
## Nachtrag 19 · Otterpohl: Text v2 und erste Bilder (24.09.2026)
|
||||
|
||||
Zulieferung vom 24.09.: `dev/2026-08-28/otterpohl_Webseitentext_FINAL_v2.md` löst die lange Fassung
|
||||
vom 17.09. komplett ab, dazu 13 Bilder in `dev/2026-08-28/images/otten/` (Titel plus 2–5 vom
|
||||
Unternehmen, der Rest Referenzen).
|
||||
|
||||
### Text v2
|
||||
|
||||
Deutlich kürzer als v1: eine Einleitung, zwei Abschnitte, acht Leistungen, ein Schlussblock. Die
|
||||
Fassung vom 17.09. mit zwölf Abschnitten (u. a. „Seit 2014 an unserer Seite“, „Farbe darf auch
|
||||
genau die Farbe sein“) ist damit vollständig ersetzt. Zuordnung auf die Markenseite:
|
||||
|
||||
| Zulieferung v2 | Feld | Auf der Seite |
|
||||
|---|---|---|
|
||||
| „Ziemlich nah an der eierlegenden Wollmilchsau“ + 4 Absätze | `sections[0]`, Claim „Genau dort beginnt der individuelle Innenausbau.“ | erster Abschnitt |
|
||||
| „Vom einzelnen Möbel bis zum ganzen Raum“ | `sections[1]`, Claim „So können wir Räume ganzheitlicher denken …“ | zweiter Abschnitt |
|
||||
| „Planung trifft Handwerk“ | `sections[2]`, Claim „Das Ergebnis kann ein einzelnes Möbel sein. Oder ein kompletter Raum.“ | dritter Abschnitt |
|
||||
| „Leistungen und Möglichkeiten“ (8 Punkte) | `services` | Leistungsraster, zwei Reihen à vier |
|
||||
| „Das Ganze statt nur das Einzelne“ | `cta_title` / `cta_text` | Abschlussblock hinter Leistungen und Galerie – so bleibt die Reihenfolge der Zulieferung erhalten |
|
||||
| „Nicht easy click & buy. Aber dennoch easy to get.“ | `tagline` und Ende des `cta_text` | Claim im Seitenkopf und Schlusssatz |
|
||||
|
||||
- **Name** jetzt „Otterpohl Möbelservice“ – so nennt die Zulieferung den Partner durchgängig.
|
||||
Überall dort, wo der Name aus der Marke kommt (Kacheln, Widget-Anliegen, Herkunfts-Kachel).
|
||||
Der Link im Handwerk-Abschnitt der anderen Markenseiten heißt weiter „Mehr zu Otterpohl“.
|
||||
- **Kein „seit 2014“ mehr:** v2 erwähnt die gemeinsame Zeit nicht. Der Hinweis ist aus Teaser,
|
||||
Fertigungsort (`origin_role`), Lang-Fallback und dem Handwerk-Abschnitt (`brand_craft`)
|
||||
entfernt. ⚠️ Falls Marcel die Angabe bewusst behalten wollte, ist sie in drei Sätzen zurück.
|
||||
- `description` bleibt leer, damit der Text in der Reihenfolge der Zulieferung steht; `summary`
|
||||
(Markenübersicht) fasst die Einleitung zusammen; `highlights` sind drei Stichzeilen aus den
|
||||
Leistungen. Englisch: eigene Übersetzung in gleicher Struktur.
|
||||
- `widget_label` „Persönliche Beratung zur Maßarbeit vereinbaren“ bleibt.
|
||||
|
||||
### Bilder
|
||||
|
||||
13 Dateien, konvertiert wie in Nachtrag 16 (WebP, max. 1920 px, unter
|
||||
`public/img/assets/marken/otterpohl/`, zusammen rund 2,4 MB; ein Bild per GD nachverdichtet).
|
||||
|
||||
- **Titelbild** `werkstatt-titel.webp`: zwei Mitarbeiter in der Werkstatt.
|
||||
- **Aus der Werkstatt** (2–5): CNC-Bearbeitungszentrum, Türeinbau vor Ort, Handwerkzeug,
|
||||
Anlieferung per Möbellift. Dafür gibt es eine **dritte Galerie-Kategorie** `company`
|
||||
(`CmsBrand::GALLERY_COMPANY`, Badge „Aus der Werkstatt“). Im Admin: Button „Werkstatt“ und
|
||||
Option „Aus der Werkstatt“ in der Kategorie-Auswahl, Hinweistext ergänzt.
|
||||
- **Referenzen** (8, Kategorie `project`, Badge „Realisiertes Projekt“): Treppen, Lamellenwand mit
|
||||
Sitzbank, Tür und Kommode, Wandregale, Objektbau-Verkaufsraum, Büro-Sideboard. Unterschriften
|
||||
beschreiben das Motiv; die Kundennamen aus den Dateinamen (Lücke, Berkemeier, Neugebauer,
|
||||
Dziuballe) stehen bewusst nicht auf der Seite. ⚠️ `Otterpohl_11.jpg` (Sideboard im Büro) ist
|
||||
nach Kevins Angabe eine Referenz, nicht Werkstatt – falls es das eigene Büro ist, im Admin auf
|
||||
„Aus der Werkstatt“ umstellen.
|
||||
- **Reihenfolge in der Galerie:** Projekte, dann Werkstatt, dann Visualisierungen.
|
||||
- **Überschrift der Galerie** richtet sich jetzt nach dem Inhalt: nur Visualisierungen →
|
||||
„Inspirationen und Entwürfe“ (wie bisher, CABINET/Rotpunkt/Febrü); nur Projekte → „Referenzen“;
|
||||
Projekte und Werkstatt → „Referenzen und Einblicke“ (Otterpohl); mit Visualisierungen →
|
||||
„Projekte und Entwürfe“. Die Zeile darunter passt jeweils.
|
||||
- ⚠️ **Referenzen-Seite:** `/referenzen` zeigt, solange im Modul „Referenzen“ nichts gepflegt ist,
|
||||
die Projekte aus den Markengalerien – jetzt also die acht Otterpohl-Referenzen mit Link auf die
|
||||
Markenseite. Werkstattbilder erscheinen dort nicht. Wenn Marcel das nicht will: Bilder im Admin
|
||||
auf „Aus der Werkstatt“ stellen oder eigene Referenzen anlegen, dann greift der Fallback nicht.
|
||||
|
||||
**Lokal ausgeführt:** `php artisan db:seed --class=OtterpohlBrandSeeder`, `php artisan db:seed
|
||||
--class=BrandImageSeeder`. Beim Deployment: Abschnitt 8.2, Schritte 11 und 12 (Texte
|
||||
unverändert, sie decken v2 mit ab).
|
||||
|
||||
**Tests:** `FourthBrandTest` (v2-Überschriften, acht Leistungen, drei Abschnitte, Schlussblock hinter
|
||||
den Leistungen, kein „Seit 2014“), `BrandFeedbackTest` (Seeder setzt Name, acht Leistungen,
|
||||
Abschluss), `BrandImageSeederTest` (+3: Otterpohl-Galerie 8 Projekte + 4 Werkstatt ohne
|
||||
Kundennamen, Badges und Überschrift „Referenzen und Einblicke“, Referenzen-Fallback zeigt Projekte
|
||||
und keine Werkstattbilder), `BrandPageTest` (gemischte Galerie → „Projekte und Entwürfe“, Aufräum-
|
||||
Seeder lässt Otterpohl 12).
|
||||
|
||||
**Offen:** Marcel prüft Unterschriften, die Zuordnung von `Otterpohl_11`, das gestrichene „seit
|
||||
2014“ und ob die Otterpohl-Referenzen unter `/referenzen` erscheinen sollen.
|
||||
|
||||
## Nachtrag 19 · Entwurf der Startseite in der Farbwelt „Greige“ (24.09.2026)
|
||||
|
||||
Briefing von Marcel (`dev/2026-08-28/farbwelt-kunde-24-09-2026.md`, Visualisierung
|
||||
`Farbwelt und Stil.png`): die Creme-Palette wird nicht weiterverfolgt, die Richtung „Neu“ soll
|
||||
heller, klarer und hochwertiger werden – heller, leicht kühler Grundton (Greige, kein steriles
|
||||
Weiß), deutlich präsenteres Tannengrün, Gold nur als feine Linie. Dazu eine Slideshow im Hero,
|
||||
neue Überschrift, keine Aktionsleiste, ein Button im Hero, neuer Projekttext, Logo mit Goldbogen.
|
||||
Kevins Auftrag: eine **separate Version der Startseite** zum Vergleich, das Logo als Kopie.
|
||||
|
||||
### Was gebaut ist
|
||||
|
||||
- **Entwurfsseite `/entwurf`** (Route `home.draft`, View `web/home-draft.blade.php`): die reguläre
|
||||
Startseite mit neuem Hero, ohne Ankündigungsleiste, Body-Klasse `palette-greige`, `noindex`.
|
||||
Alle übrigen Sektionen sind unverändert eingebunden und färben sich über die Tokens. Die Seite
|
||||
hängt am Farb-Schalter: mit `B2IN_PALETTE_SWITCH=false` liefert sie 404.
|
||||
- **Palette „Greige“** als drittes Token-Set in `theme-b2in.css` (`.world-interior.palette-greige`,
|
||||
am Body, nicht am `<html>` wie der Browser-Schalter – so überlagern sich beide nicht):
|
||||
|
||||
| Token | Wert |
|
||||
|---|---|
|
||||
| Grund | `#f0eeeb` Greige, Off-White (Kopfzeile der Visualisierung) |
|
||||
| Karten / Hero-Bühne | `#f8f7f4` |
|
||||
| Sektionswechsel (`bg-accent`) | `#e8e6e0` |
|
||||
| Tannengrün (`--primary`) | `#14342c`, Buttons, dunkle Sektionen, Footer |
|
||||
| Gold (`--secondary`) | `#aa8341` gedeckt; als Text `--secondary-deep` (dunkler) |
|
||||
| Text | Tannengrau `hsl(166 25% 13%)` |
|
||||
| Footer | Tannengrün mit Greige-Text, Goldlinie oben, negatives Logo |
|
||||
|
||||
- **Logo-Kopien** `public/img/logos/b2in-interior-logo-gold-{positive,negative}.svg`: Bogen
|
||||
`#AA8341` statt Ocker, Wortmarke im neuen Tannengrün `#14342C` (negativ: Schrift `#F0EEEB`).
|
||||
Flächig, ohne Verlauf – die Verläufe liefert Marcel nach; die Dateien lassen sich dann 1:1
|
||||
ersetzen. Die bisherigen Logos sind unangetastet. `ThemeHelper::getLogoPath()` liefert die
|
||||
Gold-Variante, sobald `b2in_palette()` (neuer Helper, Binding `b2in.palette`) „greige“ meldet
|
||||
– das setzt nur die Entwurfsroute. Kein PNG erzeugt (kein SVG-Rasterizer im Container).
|
||||
- **Hero mit Slideshow** (`HeroSlider`, Sektion `home/hero_slider`, im Seitenbaum unter Startseite
|
||||
mit Vorschau auf `/entwurf`): Rahmen mit 1-px-Goldkante und Schatten, tannengrüne Flanken rechts
|
||||
(volle Höhe) und links unten wie in der Visualisierung, Eyebrow „B2IN INTERIOR ——“, Überschrift
|
||||
„*Räume aus einer Hand.* Mit starken Marken und persönlichen Lösungen.“ (erste Zeile in Gold,
|
||||
Serife), Einleitungstext aus der Visualisierung, **ein** Button „Unsere Marken entdecken“ mit
|
||||
Goldkante unten und Pfeil. Kein zweiter Button (Briefing Punkt 5), das Termin-Widget bleibt.
|
||||
- Bild rechts im festen Seitenverhältnis 4:3 → auf großen Bildschirmen nicht mehr unten
|
||||
beschnitten (Briefing Punkt 2).
|
||||
- Slides: Liste `slides` der Sektion (je `image`, `label`, `caption`, `active`), solange leer
|
||||
die **Titelbilder der veröffentlichten Kernmarken** in ihrer Reihenfolge (aktuell CABINET,
|
||||
Rotpunkt, Febrü, Otterpohl sofern Bild). Kleine Marken-Pille unten links im Bild.
|
||||
- Ruhig: Überblendung 1,4 s, Wechsel alle 7 s (`interval`, begrenzt auf 3–30 s), Pause bei
|
||||
Hover/Fokus, Pfeiltasten, Punkte unter dem Rahmen als Tabs; bei „reduzierter Bewegung“ kein
|
||||
Autowechsel. Läuft ohne eigenes JS-Bundle (Alpine im Markup).
|
||||
- **Farb-Schalter** hat jetzt drei Einträge: Bisher / Neu (Browser, wie gehabt) / **Entwurf**
|
||||
(Link auf `/entwurf`). Auf dem Entwurf ist „Entwurf“ aktiv, „Bisher“/„Neu“ führen mit
|
||||
`?palette=…` zurück auf die Startseite.
|
||||
- **Projekttext** (Briefing Punkt 7) global: Lang de/en geändert, `AudienceSwitchProjectTextSeeder`
|
||||
tauscht den Satz in einem vorhandenen CMS-Eintrag (nur diesen Satz, idempotent). Lokal
|
||||
ausgeführt (2 Stellen). Deployment Schritt 15.
|
||||
|
||||
**Nachbesserung nach Kevins Screenshot (24.09., 17:30):** Die Goldkante war auf Greige nicht zu
|
||||
sehen – jetzt ein Verlauf (hell oben links, auslaufend, in der Ecke wieder anziehend) über einen
|
||||
maskierten Pseudo-Rand von 1,5 px. Die Flanken sind gerundet (Radius 2,75 rem rechts, 2 rem
|
||||
links) und laufen hinter das Bild; ihre Goldlinie folgt der Rundung. Das Bild füllt den Rahmen
|
||||
fast ganz (5:4, nur 1,25 rem Luft oben und unten, rechts über der Flanke), die Überschrift ist
|
||||
kleiner (max. 3,3 rem statt 4,1 rem) und der Einleitungstext eine Stufe kleiner.
|
||||
|
||||
**Zweite Nachbesserung (25.09.):** Hinter der Goldkante schien eine helle Linie durch – die
|
||||
Masken-Technik ließ die gerundete Kante des Rahmens außerhalb des Rings sichtbar. Jetzt sind
|
||||
Rahmen und Bild-Rahmen echte Verlaufs-Borders (transparenter Rand, Fläche auf `padding-box`,
|
||||
Goldverlauf auf `border-box`), ohne Pseudo-Element. Der Button trägt den Goldrand rundum, unten
|
||||
3 px, der Pfeil ist Gold. Die grünen Flanken sind **Inline-SVGs**
|
||||
(`components/web/hero-flank-right|left.blade.php`): grüner Verlauf, Goldlinie entlang der
|
||||
Rundung, rechts zusätzlich die kurze Zierlinie aus der Visualisierung. Sie skalieren über die
|
||||
Rahmenhöhe (`height: 100 %` bzw. `46 %`, Breite aus dem viewBox-Verhältnis, Strichstärke bleibt
|
||||
1,5 px dank `vector-effect`), Farben aus den Tokens. Liefert Marcel eigene SVGs, werden die
|
||||
beiden Dateien getauscht – der Platz im Rahmen bleibt.
|
||||
|
||||
**Abstimmung 25.09. (Kevin):** Für die nächste Runde ist der Farb-Schalter **aus** und die Seite
|
||||
steht fest auf „Neu“. Dafür zwei neue Konfigurationswerte in `config/app.php`:
|
||||
`B2IN_PALETTE_SWITCH` (Standard jetzt `false`), `B2IN_PALETTE` (Standard `modern`; ohne Schalter
|
||||
setzt das Layout die Klasse `palette-modern` serverseitig an `<html>`, mit Schalter ist es die
|
||||
Vorbelegung, solange nichts im localStorage liegt) und `B2IN_HOME_DRAFT` (Standard `true`) – der
|
||||
Entwurf `/entwurf` hängt nicht mehr am Schalter, sondern an diesem Wert. Der Schalter selbst
|
||||
bleibt im Code und lässt sich mit `B2IN_PALETTE_SWITCH=true` wieder einblenden.
|
||||
|
||||
### Was aus dem Briefing noch offen ist
|
||||
|
||||
| Punkt | Stand |
|
||||
|---|---|
|
||||
| 1 Slideshow selbst pflegen (Upload, Reihenfolge, an/aus) | Datenmodell steht (Liste `slides` mit `active`), Pflege bisher nur über den JSON-Editor unter Inhalte. Eigene Oberfläche wie bei den Displays: nächster Schritt nach Freigabe des Entwurfs. |
|
||||
| 6 Persönliches Foto | Kein Foto geliefert. Vorschlag: in der Gründer-Leiste („B2in by Marcel Scheibe“), Bildfeld ist dort schon vorhanden (`founder_bar.image`). |
|
||||
| 8 Feinabstimmung der Töne | Werte oben sind aus der Visualisierung gesampelt und leicht aufgehellt („noch etwas heller“). Marcel entscheidet am Entwurf. |
|
||||
| 9 Logo mit Verläufen | Kommt von Marcel; bis dahin flache Gold-Kopie. |
|
||||
| Entwurf → Standard | Wenn freigegeben: Tokens von `.palette-greige` nach `:root`, Gold-Logos umbenennen, `home.blade.php` auf `hero-slider` umstellen, Ankündigungsleiste global aus, Schalter und `/entwurf` abschalten. Ein Nachtrag, kein Umbau. |
|
||||
|
||||
**Lokal ausgeführt:** `php artisan db:seed --class=AudienceSwitchProjectTextSeeder`, `npm run build:b2in`.
|
||||
Beim Deployment: Abschnitt 8.2, Schritt 15, dazu `npm run build:web`; die SVG-Logos liegen im
|
||||
Repository. Link für Marcel: `https://<vorschau>/entwurf`.
|
||||
|
||||
**Tests:** `HomeDraftTest` (16: Palette und Gold-Logos nur auf dem Entwurf, Überschrift/ein
|
||||
Button/keine Leiste, Sektionsreihenfolge, 404 ohne Schalter, Slides aus Marken, ohne Bild,
|
||||
CMS-Slides mit `active`, Intervall-Grenzen, Schalter hin und zurück, Logo-Dateien, Tokens,
|
||||
Projekttext in Lang und per Seeder), `CmsPageTreeTest` (Reihenfolge um `hero_slider` ergänzt).
|
||||
Gesamt: 1049 grün.
|
||||
|
||||
|
|
|
|||
69
dev/2026-08-28/farbwelt-kunde-24-09-2026.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
1. Hero-Bereich / Slideshow
|
||||
Das aktuelle große Hero-Bild würde ich gerne durch eine Slideshow mit unterschiedlichen Herstellern und Themen ersetzen, zum Beispiel:
|
||||
Rotpunkt / Küche
|
||||
CABINET
|
||||
Febrü
|
||||
Tischlerei / individueller Innenausbau
|
||||
komplette Raum- und Einrichtungskonzepte
|
||||
Wichtig wäre mir, dass ich diese Slideshow selbstständig pflegen und steuern kann, ähnlich wie unsere Displays.
|
||||
Ideal wäre eine Möglichkeit im Backend, mit der ich:
|
||||
Bilder selbst hochladen kann
|
||||
die Reihenfolge verändern kann
|
||||
einzelne Slides aktivieren oder deaktivieren kann
|
||||
Bilder später unkompliziert austauschen kann
|
||||
Die Slideshow selbst sollte dabei ruhig und hochwertig wirken – also langsame, dezente Übergänge und kein klassischer Werbebanner-Effekt.
|
||||
|
||||
2. Darstellung und Größe des Hero-Bildes
|
||||
Wichtig wäre mir außerdem, dass das Hero-Bild auf größeren Bildschirmen nicht unten abgeschnitten wird.
|
||||
Auf meinem Monitor – ich meine, es sind 27 Zoll – wird das aktuelle Motiv unten sichtbar beschnitten. Dadurch wirkt der Bildausschnitt aus meiner Sicht nicht ganz stimmig.
|
||||
Bei der mitgeschickten Visualisierung ist das wesentlich besser gelöst: Das Motiv ist nahezu vollständig sichtbar und bekommt dadurch deutlich mehr Wirkung.
|
||||
So würde ich es gerne haben – natürlich responsive für unterschiedliche Bildschirmgrößen.
|
||||
|
||||
3. Headline
|
||||
Die bisherige Headline würde ich gerne ändern zu:
|
||||
„Räume aus einer Hand. Mit starken Marken und persönlichen Lösungen.“
|
||||
Die Formulierung B2in Interior kann dabei gerne, ähnlich wie in der Visualisierung, etwas zurückhaltender oberhalb bzw. im Umfeld der Headline eingesetzt werden.
|
||||
|
||||
4. Obere Aktionsleiste
|
||||
Die derzeitige Aktionsleiste ganz oben würde ich komplett entfernen.
|
||||
Ohne diese Leiste wirkt die Seite aus meiner Sicht ruhiger, klarer und insgesamt hochwertiger.
|
||||
|
||||
5. Buttons im Hero
|
||||
Den zusätzlichen Button „Termin anfragen“ neben „Unsere Marken entdecken“ würde ich entfernen.
|
||||
Der feststehende Button „Termin anfragen“ rechts unten soll dagegen bestehen bleiben.
|
||||
Damit gibt es im eigentlichen Hero einen klaren Hauptbutton:
|
||||
„Unsere Marken entdecken“
|
||||
|
||||
6. Persönliches Foto
|
||||
Ein Foto von mir würde ich gerne dezent und hochwertig auf der Seite integrieren.
|
||||
Nicht im Hero und auch nicht zu dominant, sondern an einer passenden Stelle weiter unten – zum Beispiel in Verbindung mit dem persönlichen B2in-/Marcel-Scheibe-Bereich.
|
||||
|
||||
7. Text Projektgeschäft
|
||||
Die bisherige Formulierung mit „deutscher Vertragssicherheit“ würde ich gerne ersetzen durch:
|
||||
„Vollständige Einrichtung für Objekte, Büros und Projektentwicklungen – koordiniert aus einer Hand, von der Küche bis zur letzten Blume. Mit einem zentralen deutschen Vertragspartner und einem eingespielten Herstellernetzwerk.“
|
||||
|
||||
8. Farbwelt und Stil
|
||||
Die bisherige Farbwelt würde ich nicht weiterverfolgen. Die neue Richtung gefällt mir deutlich besser, ich würde sie allerdings noch etwas heller, klarer und hochwertiger gestalten.
|
||||
Das beigefügte Bild zeigt die Richtung aus meiner Sicht schon sehr gut.
|
||||
Besonders wichtig wären mir dabei:
|
||||
ein heller, leicht kühler Grundton
|
||||
kein reines bzw. steriles Weiß
|
||||
eher ein sehr helles, modernes Greige / Off-White
|
||||
ein deutlich präsenteres und klareres Tannengrün
|
||||
Gold nur als feiner Akzent
|
||||
Gerade die Kombination aus Tannengrün und einer sehr dünnen Goldlinie gefällt mir gut.
|
||||
Im beigefügten Beispiel sieht man das unter anderem:
|
||||
an der feinen goldenen Umrandung des Hero-Bereichs
|
||||
in Verbindung mit den tannengrünen Flächen
|
||||
am Button „Unsere Marken entdecken“, der ebenfalls eine feine Goldkante bzw. einen Goldakzent erhält
|
||||
bei kleinen Linien und Details
|
||||
Genau so würde ich Gold einsetzen: nicht flächig und nicht dekorativ-verspielt, sondern sehr reduziert als hochwertiges Detail.
|
||||
Die Kombination aus hellem Untergrund + kräftigem Tannengrün + sehr feinem Gold trifft für mich die gewünschte Richtung inzwischen ziemlich gut.
|
||||
|
||||
9. B2in-Logo an die neue Farbwelt anpassen
|
||||
Wenn wir die Farbwelt entsprechend weiterentwickeln, sollte sich das aus meiner Sicht konsequenterweise auch im B2in-Logo widerspiegeln.
|
||||
Der Bogen über dem B2in-Schriftzug ist aktuell eher in einem Ocker-/Senfton gehalten. Diesen würde ich gerne auf den neuen Goldton umstellen, den wir auch für die feinen Linien und Akzente auf der Website verwenden.
|
||||
Damit würde sich die Farbsprache durchgängig wiederfinden:
|
||||
Tannengrün + Gold + heller, neutraler Grundton.
|
||||
Ich würde dich daher bitten, die vorhandenen Logo-Varianten entsprechend anzupassen, sodass der bisherige Ockerton konsequent durch den neuen Goldton ersetzt wird und Website und Logo anschließend exakt dieselbe Farbwelt verwenden.
|
||||
Wichtig wäre mir auch hier, dass das Gold edel und zurückhaltend bleibt – also kein gelblicher oder zu glänzender Goldton, sondern eher ein moderner, gedeckter Premium-Goldton wie in der beigefügten Visualisierung.
|
||||
41
dev/2026-08-28/otterpohl_Webseitentext_FINAL_v2.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
Ziemlich nah an der eierlegenden Wollmilchsau
|
||||
Der Begriff ist vielleicht nicht besonders elegant. Aber ganz falsch ist er nicht.
|
||||
Individuelle Möbel. Türen. Treppen. Küchen. Wandverkleidungen. Objektbau. Sonderanfertigungen. Lackierungen und Echtholzfurniere. Bis hin zum kompletten Innenausbau.
|
||||
Mit Otterpohl Möbelservice haben wir einen Partner an unserer Seite, bei dem die Möglichkeiten nicht am einzelnen Möbelstück aufhören.
|
||||
Denn manchmal braucht ein Raum mehr als einen Schrank oder ein Sideboard. Vielleicht soll eine Treppe Teil des Gestaltungskonzepts werden. Eine Wand erhält eine individuelle Holzverkleidung. Eine Tür soll sich selbstverständlich in den Raum einfügen. Oder Möbel, Materialien und bauliche Elemente müssen so zusammenspielen, dass am Ende alles wie aus einem Guss wirkt.
|
||||
Genau dort beginnt der individuelle Innenausbau.
|
||||
Vom einzelnen Möbel bis zum ganzen Raum
|
||||
Wir arbeiten mit Herstellern, die schon sehr viele individuelle Möglichkeiten bieten. CABINET, Rotpunkt und Febrü geben uns dafür eine starke Basis.
|
||||
Aber es gibt Projekte, bei denen die Lösung darüber hinausgeht.
|
||||
Mit Otterpohl Möbelservice können wir deshalb nicht nur individuelle Möbel und Sonderanfertigungen realisieren, sondern auch Türen, Treppen, Küchen, Wand- und Deckenverkleidungen sowie komplette Innenausbauten und Lösungen für den Objektbereich einbeziehen.
|
||||
So können wir Räume ganzheitlicher denken – und Details nicht isoliert betrachten.
|
||||
Planung trifft Handwerk
|
||||
Manchmal entsteht die beste Lösung aus einem Möbelprogramm. Manchmal braucht es eine individuelle Ergänzung. Und manchmal muss etwas komplett neu gedacht und gebaut werden.
|
||||
Genau diese Kombination macht für uns den Unterschied.
|
||||
Wir planen den Raum und verbinden bei Bedarf unterschiedliche Hersteller, Materialien und handwerkliche Lösungen miteinander. Otterpohl Möbelservice ergänzt diese Planung um die Möglichkeiten einer klassischen Tischlerei und eines erfahrenen Innenausbauers.
|
||||
Das Ergebnis kann ein einzelnes Möbel sein.
|
||||
Oder ein kompletter Raum.
|
||||
Leistungen und Möglichkeiten
|
||||
Individuelle Möbel & Sonderanfertigungen
|
||||
Möbel nach Maß, besondere Konstruktionen und individuelle Lösungen – nach Planung, Idee, Skizze oder konkreter Anforderung.
|
||||
Innenausbau
|
||||
Von einzelnen Ausbauarbeiten bis zum ganzheitlichen Raumkonzept – inklusive individueller Verkleidungen und maßgefertigter Einbauten.
|
||||
Türen & Treppen
|
||||
Individuelle Türen, Treppen und konstruktive Lösungen, die sich in Architektur und Einrichtung einfügen.
|
||||
Küchen
|
||||
Individuelle Küchenlösungen und handwerkliche Ergänzungen auch dort, wo klassische Küchenprogramme an ihre Grenzen kommen.
|
||||
Wandverkleidungen & besondere Details
|
||||
Holzverkleidungen, Lamellen, Paneele und individuelle Gestaltungselemente, die Architektur und Einrichtung miteinander verbinden.
|
||||
Objektbau
|
||||
Individuelle Lösungen für Büros, Verkaufsräume und gewerblich genutzte Flächen – vom Einzelmöbel bis zum kompletten Ausbau.
|
||||
Lack & Echtholzfurnier
|
||||
Individuelle Lackierungen und Furniere für Lösungen, bei denen Farbe, Material und Oberfläche exakt aufeinander abgestimmt werden sollen.
|
||||
Montage
|
||||
Die Umsetzung endet nicht bei der Fertigung. Entscheidend ist, dass Möbel, Ausbau und Raum am Ende auch vor Ort sauber zusammenkommen.
|
||||
Das Ganze statt nur das Einzelne
|
||||
Genau deshalb passt Otterpohl Möbelservice zu unserem Ansatz.
|
||||
Wir möchten Räume nicht in einzelne Gewerke zerlegen. Wir möchten Lösungen schaffen, bei denen Möbel, Materialien, Architektur und handwerkliche Details zusammenpassen.
|
||||
Mal braucht es dafür einen Hersteller.
|
||||
Mal einen Tischler.
|
||||
Und manchmal beides.
|
||||
Nicht easy click & buy. Aber dennoch easy to get.
|
||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 332 KiB |
BIN
public/img/assets/marken/cabinet/dachschraege-velluto-fango.webp
Normal file
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 217 KiB |
BIN
public/img/assets/marken/cabinet/drehtuerenschrank-marmor.webp
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
public/img/assets/marken/cabinet/flur-schrank-eiche-tano.webp
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
public/img/assets/marken/cabinet/garderobe-seite-offen.webp
Normal file
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 96 KiB |
BIN
public/img/assets/marken/cabinet/schlafzimmer-eiche-tano.webp
Normal file
|
After Width: | Height: | Size: 181 KiB |
BIN
public/img/assets/marken/cabinet/schlafzimmer-velluto-fango.webp
Normal file
|
After Width: | Height: | Size: 201 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 84 KiB |
BIN
public/img/assets/marken/februe/active-2-0-bureo.webp
Normal file
|
After Width: | Height: | Size: 253 KiB |
BIN
public/img/assets/marken/februe/active-2-0-soul-grossraum.webp
Normal file
|
After Width: | Height: | Size: 271 KiB |
BIN
public/img/assets/marken/februe/adesso.webp
Normal file
|
After Width: | Height: | Size: 325 KiB |
BIN
public/img/assets/marken/februe/arena-stage.webp
Normal file
|
After Width: | Height: | Size: 380 KiB |
BIN
public/img/assets/marken/februe/base-sideboard.webp
Normal file
|
After Width: | Height: | Size: 262 KiB |
BIN
public/img/assets/marken/februe/base-thekenloesung.webp
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
public/img/assets/marken/februe/base.webp
Normal file
|
After Width: | Height: | Size: 197 KiB |
|
After Width: | Height: | Size: 263 KiB |
BIN
public/img/assets/marken/februe/bobb.webp
Normal file
|
After Width: | Height: | Size: 261 KiB |
BIN
public/img/assets/marken/februe/brandon.webp
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
public/img/assets/marken/februe/corporate.webp
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
public/img/assets/marken/februe/coworker.webp
Normal file
|
After Width: | Height: | Size: 226 KiB |
BIN
public/img/assets/marken/februe/curved-ben-basic.webp
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
public/img/assets/marken/februe/elements-soul-grossraum.webp
Normal file
|
After Width: | Height: | Size: 268 KiB |
|
After Width: | Height: | Size: 274 KiB |
BIN
public/img/assets/marken/februe/vio.webp
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
public/img/assets/marken/otterpohl.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 375 KiB |
|
After Width: | Height: | Size: 124 KiB |
BIN
public/img/assets/marken/otterpohl/referenz-luecke-01.webp
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
public/img/assets/marken/otterpohl/referenz-luecke-12.webp
Normal file
|
After Width: | Height: | Size: 231 KiB |
BIN
public/img/assets/marken/otterpohl/referenz-luecke-15.webp
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
public/img/assets/marken/otterpohl/referenz-neugebauer.webp
Normal file
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 181 KiB |
BIN
public/img/assets/marken/otterpohl/referenz-otterpohl-11.webp
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
public/img/assets/marken/otterpohl/werkstatt-02.webp
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
public/img/assets/marken/otterpohl/werkstatt-03.webp
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
public/img/assets/marken/otterpohl/werkstatt-04.webp
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
public/img/assets/marken/otterpohl/werkstatt-05-lift.webp
Normal file
|
After Width: | Height: | Size: 308 KiB |
BIN
public/img/assets/marken/otterpohl/werkstatt-titel.webp
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
public/img/assets/marken/rotpunkt/kueche-01-b.webp
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
public/img/assets/marken/rotpunkt/kueche-01.webp
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
public/img/assets/marken/rotpunkt/kueche-02.webp
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
public/img/assets/marken/rotpunkt/kueche-03.webp
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
public/img/assets/marken/rotpunkt/kueche-04.webp
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
public/img/assets/marken/rotpunkt/kueche-05.webp
Normal file
|
After Width: | Height: | Size: 242 KiB |
BIN
public/img/assets/marken/rotpunkt/kueche-06-b.webp
Normal file
|
After Width: | Height: | Size: 201 KiB |
BIN
public/img/assets/marken/rotpunkt/kueche-06.webp
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
public/img/assets/marken/rotpunkt/kueche-07.webp
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
public/img/assets/marken/rotpunkt/kueche-08.webp
Normal file
|
After Width: | Height: | Size: 203 KiB |
BIN
public/img/assets/marken/rotpunkt/kueche-09.webp
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
public/img/assets/marken/rotpunkt/kueche-10.webp
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
public/img/assets/marken/rotpunkt/kueche-11.webp
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
public/img/assets/marken/rotpunkt/kueche-12.webp
Normal file
|
After Width: | Height: | Size: 170 KiB |
53
public/img/logos/b2in-interior-logo-gold-negative.svg
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 30.8.1, SVG Export Plug-In . SVG Version: 9.03 Build 0) -->
|
||||
<svg version="1.0" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="152.5 94.5 289 228" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F0EEEB;}
|
||||
.st1{fill:#AA8341;}
|
||||
.st2{fill:none;stroke:#AA8341;stroke-width:3;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M156.1,260.6V157.1h40.7c7.5,0,13.8,1.1,18.9,3.4c5,2.3,8.8,5.4,11.3,9.4c2.5,4,3.8,8.6,3.8,13.8
|
||||
c0,4.1-0.8,7.6-2.4,10.7c-1.6,3-3.8,5.5-6.6,7.4c-2.8,1.9-5.9,3.3-9.5,4.1v1c3.9,0.1,7.5,1.2,10.9,3.3c3.4,2.1,6.2,4.9,8.3,8.6
|
||||
c2.1,3.7,3.2,8.1,3.2,13.2c0,5.5-1.3,10.3-4,14.7c-2.7,4.3-6.6,7.7-11.8,10.2c-5.2,2.5-11.7,3.7-19.5,3.7H156.1z M177.1,200.6
|
||||
h16.6c3,0,5.7-0.5,8.1-1.6c2.4-1.1,4.3-2.6,5.6-4.7c1.4-2,2-4.4,2-7.2c0-3.8-1.3-6.8-4-9.2c-2.6-2.4-6.4-3.5-11.3-3.5h-17V200.6z
|
||||
M177.1,243.1h18.2c6.2,0,10.7-1.2,13.5-3.6c2.8-2.4,4.3-5.6,4.3-9.5c0-2.9-0.7-5.5-2.1-7.7c-1.4-2.2-3.4-4-6-5.3
|
||||
c-2.6-1.3-5.6-1.9-9.2-1.9h-18.8V243.1z"/>
|
||||
<path class="st0" d="M248.7,260.6v-15.3l36.4-34.4c3.1-3.1,5.8-5.8,7.9-8.3c2.1-2.5,3.8-4.9,4.9-7.3c1.1-2.4,1.7-5,1.7-7.7
|
||||
c0-3.1-0.7-5.8-2.1-8c-1.4-2.2-3.3-3.9-5.7-5.1c-2.4-1.2-5.1-1.8-8.2-1.8c-3.2,0-6,0.6-8.3,1.9c-2.4,1.3-4.2,3.1-5.5,5.6
|
||||
c-1.3,2.4-1.9,5.3-1.9,8.7h-20.1c0-6.7,1.5-12.6,4.6-17.5c3.1-5,7.3-8.8,12.7-11.5c5.4-2.7,11.7-4.1,18.8-4.1
|
||||
c7.3,0,13.6,1.3,19,3.9c5.4,2.6,9.6,6.2,12.7,10.8c3,4.6,4.5,9.9,4.5,15.8c0,3.8-0.7,7.6-2.2,11.4c-1.5,3.8-4.2,8-8,12.6
|
||||
c-3.8,4.7-9.3,10.2-16.3,16.7l-15.5,15.4v0.8h43.4v17.4H248.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M344.3,260.4v-74.1h18.2v74.1H344.3z"/>
|
||||
<path class="st0" d="M394.4,260.4h-18.2v-74.1h18.2v12.4h0.7c1.5-4,3.9-7.3,7.2-10c3.3-2.7,7.8-4,13.6-4c7.6,0,13.4,2.5,17.5,7.6
|
||||
c4.1,5.1,6.2,12.2,6.2,21.4v46.8h-18.2v-45c0-5.3-0.9-9.3-2.8-11.9c-1.9-2.6-5-4-9.4-4c-2.5,0-4.8,0.5-7.1,1.3
|
||||
c-2.3,0.9-4.1,2.2-5.5,4c-1.4,1.8-2.1,4-2.1,6.6V260.4z"/>
|
||||
</g>
|
||||
<path class="st1" d="M353.4,150.5c2.5,0,4.7,0.3,6.6,0.8c-14-32.8-49-56-89.9-56c-41.5,0-76.8,23.9-90.5,57.3h14.3
|
||||
c8.8-30.8,39.6-53.5,76.1-53.5c36.1,0,66.6,22.2,75.8,52.5C347.9,150.9,350.4,150.5,353.4,150.5z"/>
|
||||
<path class="st0" d="M353.4,176.3c-3.7,0-6.4-0.9-8.1-2.6c-1.7-1.7-2.6-3.9-2.6-6.5v-2.8c0-2.6,0.9-4.8,2.6-6.5
|
||||
c1.7-1.7,4.4-2.6,8.1-2.6c3.7,0,6.4,0.9,8.1,2.6c1.7,1.7,2.6,3.9,2.6,6.5v2.8c0,2.6-0.9,4.7-2.6,6.5
|
||||
C359.7,175.4,357,176.3,353.4,176.3z"/>
|
||||
<g>
|
||||
<path class="st0" d="M155.7,320.1v-5.4h4v-20.5h-4v-5.4h14.9v5.4h-4v20.5h4v5.4H155.7z"/>
|
||||
<path class="st0" d="M196.1,305.2l-3-6.3h-0.1v21.1h-6.4v-31.4h7.5l8.6,14.8l3,6.3h0.1v-21.1h6.4v31.4h-7.5L196.1,305.2z"/>
|
||||
<path class="st0" d="M242.3,294.7v25.3h-6.8v-25.3H227v-6.1h23.8v6.1H242.3z"/>
|
||||
<path class="st0" d="M265.7,320.1v-31.4h21.4v6.1h-14.5v6.4H285v6h-12.5v6.8h14.5v6.1H265.7z"/>
|
||||
<path class="st0" d="M310,320.1h-6.8v-31.4h14.9c5.8,0,9.5,4,9.5,10c0,4.2-1.9,7.6-5.7,9l6.2,12.3h-7.6l-5.5-11.5H310V320.1z
|
||||
M317.2,302.8c2,0,3.3-1.1,3.3-3.1v-2c0-2-1.3-3.1-3.3-3.1H310v8.2H317.2z"/>
|
||||
<path class="st0" d="M342.5,320.1v-5.4h4v-20.5h-4v-5.4h14.9v5.4h-4v20.5h4v5.4H342.5z"/>
|
||||
<path class="st0" d="M372.1,304.4c0-10.7,5.5-16.2,13.8-16.2c8.3,0,13.8,5.6,13.8,16.2s-5.5,16.2-13.8,16.2
|
||||
C377.6,320.6,372.1,315,372.1,304.4z M392.5,306.7V302c0-5-2.5-7.8-6.6-7.8s-6.6,2.8-6.6,7.8v4.7c0,5,2.5,7.8,6.6,7.8
|
||||
S392.5,311.7,392.5,306.7z"/>
|
||||
<path class="st0" d="M422.4,320.1h-6.8v-31.4h14.9c5.8,0,9.5,4,9.5,10c0,4.2-1.9,7.6-5.7,9l6.2,12.3h-7.6l-5.5-11.5h-4.9V320.1z
|
||||
M429.6,302.8c2,0,3.3-1.1,3.3-3.1v-2c0-2-1.3-3.1-3.3-3.1h-7.2v8.2H429.6z"/>
|
||||
</g>
|
||||
<line class="st2" x1="153.4" y1="273.7" x2="439.5" y2="273.7"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
53
public/img/logos/b2in-interior-logo-gold-positive.svg
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 30.8.1, SVG Export Plug-In . SVG Version: 9.03 Build 0) -->
|
||||
<svg version="1.0" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="152.5 94.5 289 228" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#14342C;}
|
||||
.st1{fill:#AA8341;}
|
||||
.st2{fill:none;stroke:#AA8341;stroke-width:3;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M156.1,260.6V157.1h40.7c7.5,0,13.8,1.1,18.9,3.4c5,2.3,8.8,5.4,11.3,9.4c2.5,4,3.8,8.6,3.8,13.8
|
||||
c0,4.1-0.8,7.6-2.4,10.7c-1.6,3-3.8,5.5-6.6,7.4c-2.8,1.9-5.9,3.3-9.5,4.1v1c3.9,0.1,7.5,1.2,10.9,3.3c3.4,2.1,6.2,4.9,8.3,8.6
|
||||
c2.1,3.7,3.2,8.1,3.2,13.2c0,5.5-1.3,10.3-4,14.7c-2.7,4.3-6.6,7.7-11.8,10.2c-5.2,2.5-11.7,3.7-19.5,3.7H156.1z M177.1,200.6
|
||||
h16.6c3,0,5.7-0.5,8.1-1.6c2.4-1.1,4.3-2.6,5.6-4.7c1.4-2,2-4.4,2-7.2c0-3.8-1.3-6.8-4-9.2c-2.6-2.4-6.4-3.5-11.3-3.5h-17V200.6z
|
||||
M177.1,243.1h18.2c6.2,0,10.7-1.2,13.5-3.6c2.8-2.4,4.3-5.6,4.3-9.5c0-2.9-0.7-5.5-2.1-7.7c-1.4-2.2-3.4-4-6-5.3
|
||||
c-2.6-1.3-5.6-1.9-9.2-1.9h-18.8V243.1z"/>
|
||||
<path class="st0" d="M248.7,260.6v-15.3l36.4-34.4c3.1-3.1,5.8-5.8,7.9-8.3c2.1-2.5,3.8-4.9,4.9-7.3c1.1-2.4,1.7-5,1.7-7.7
|
||||
c0-3.1-0.7-5.8-2.1-8c-1.4-2.2-3.3-3.9-5.7-5.1c-2.4-1.2-5.1-1.8-8.2-1.8c-3.2,0-6,0.6-8.3,1.9c-2.4,1.3-4.2,3.1-5.5,5.6
|
||||
c-1.3,2.4-1.9,5.3-1.9,8.7h-20.1c0-6.7,1.5-12.6,4.6-17.5c3.1-5,7.3-8.8,12.7-11.5c5.4-2.7,11.7-4.1,18.8-4.1
|
||||
c7.3,0,13.6,1.3,19,3.9c5.4,2.6,9.6,6.2,12.7,10.8c3,4.6,4.5,9.9,4.5,15.8c0,3.8-0.7,7.6-2.2,11.4c-1.5,3.8-4.2,8-8,12.6
|
||||
c-3.8,4.7-9.3,10.2-16.3,16.7l-15.5,15.4v0.8h43.4v17.4H248.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M344.3,260.4v-74.1h18.2v74.1H344.3z"/>
|
||||
<path class="st0" d="M394.4,260.4h-18.2v-74.1h18.2v12.4h0.7c1.5-4,3.9-7.3,7.2-10c3.3-2.7,7.8-4,13.6-4c7.6,0,13.4,2.5,17.5,7.6
|
||||
c4.1,5.1,6.2,12.2,6.2,21.4v46.8h-18.2v-45c0-5.3-0.9-9.3-2.8-11.9c-1.9-2.6-5-4-9.4-4c-2.5,0-4.8,0.5-7.1,1.3
|
||||
c-2.3,0.9-4.1,2.2-5.5,4c-1.4,1.8-2.1,4-2.1,6.6V260.4z"/>
|
||||
</g>
|
||||
<path class="st1" d="M353.4,150.5c2.5,0,4.7,0.3,6.6,0.8c-14-32.8-49-56-89.9-56c-41.5,0-76.8,23.9-90.5,57.3h14.3
|
||||
c8.8-30.8,39.6-53.5,76.1-53.5c36.1,0,66.6,22.2,75.8,52.5C347.9,150.9,350.4,150.5,353.4,150.5z"/>
|
||||
<path class="st0" d="M353.4,176.3c-3.7,0-6.4-0.9-8.1-2.6c-1.7-1.7-2.6-3.9-2.6-6.5v-2.8c0-2.6,0.9-4.8,2.6-6.5
|
||||
c1.7-1.7,4.4-2.6,8.1-2.6c3.7,0,6.4,0.9,8.1,2.6c1.7,1.7,2.6,3.9,2.6,6.5v2.8c0,2.6-0.9,4.7-2.6,6.5
|
||||
C359.7,175.4,357,176.3,353.4,176.3z"/>
|
||||
<g>
|
||||
<path class="st0" d="M155.7,320.1v-5.4h4v-20.5h-4v-5.4h14.9v5.4h-4v20.5h4v5.4H155.7z"/>
|
||||
<path class="st0" d="M196.1,305.2l-3-6.3h-0.1v21.1h-6.4v-31.4h7.5l8.6,14.8l3,6.3h0.1v-21.1h6.4v31.4h-7.5L196.1,305.2z"/>
|
||||
<path class="st0" d="M242.3,294.7v25.3h-6.8v-25.3H227v-6.1h23.8v6.1H242.3z"/>
|
||||
<path class="st0" d="M265.7,320.1v-31.4h21.4v6.1h-14.5v6.4H285v6h-12.5v6.8h14.5v6.1H265.7z"/>
|
||||
<path class="st0" d="M310,320.1h-6.8v-31.4h14.9c5.8,0,9.5,4,9.5,10c0,4.2-1.9,7.6-5.7,9l6.2,12.3h-7.6l-5.5-11.5H310V320.1z
|
||||
M317.2,302.8c2,0,3.3-1.1,3.3-3.1v-2c0-2-1.3-3.1-3.3-3.1H310v8.2H317.2z"/>
|
||||
<path class="st0" d="M342.5,320.1v-5.4h4v-20.5h-4v-5.4h14.9v5.4h-4v20.5h4v5.4H342.5z"/>
|
||||
<path class="st0" d="M372.1,304.4c0-10.7,5.5-16.2,13.8-16.2c8.3,0,13.8,5.6,13.8,16.2s-5.5,16.2-13.8,16.2
|
||||
C377.6,320.6,372.1,315,372.1,304.4z M392.5,306.7V302c0-5-2.5-7.8-6.6-7.8s-6.6,2.8-6.6,7.8v4.7c0,5,2.5,7.8,6.6,7.8
|
||||
S392.5,311.7,392.5,306.7z"/>
|
||||
<path class="st0" d="M422.4,320.1h-6.8v-31.4h14.9c5.8,0,9.5,4,9.5,10c0,4.2-1.9,7.6-5.7,9l6.2,12.3h-7.6l-5.5-11.5h-4.9V320.1z
|
||||
M429.6,302.8c2,0,3.3-1.1,3.3-3.1v-2c0-2-1.3-3.1-3.3-3.1h-7.2v8.2H429.6z"/>
|
||||
</g>
|
||||
<line class="st2" x1="153.4" y1="273.7" x2="439.5" y2="273.7"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
|
|
@ -151,6 +151,102 @@
|
|||
|
||||
--font-secondary: 'IBM Plex Sans', 'Inter', system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
/* ── Interior, Variante „Modern“ (Nachtrag 18) ──────────────────────
|
||||
Marcels Rückmeldung vom 23.09.: die Creme-Flächen wirken altbacken. Diese
|
||||
Variante lässt Tanne und Ocker aus dem Logo unangetastet und hellt nur die
|
||||
Flächen auf: Grund fast weiß, Karten weiß, Sektionswechsel in hellem Grau-
|
||||
Beige, Footer in Tanne. Aktiv, sobald der Farb-Schalter (palette-switch)
|
||||
`palette-modern` an <html> setzt; die Wahl liegt im localStorage. */
|
||||
.palette-modern .world-interior {
|
||||
--background: 40 25% 98%; /* #fbfaf8 fast weiß, leicht warm */
|
||||
--card: 0 0% 100%; /* Karten reinweiß */
|
||||
--popover: 0 0% 100%;
|
||||
|
||||
--muted: 40 12% 94%; /* #f2f0ed */
|
||||
--muted-foreground: 24 8% 38%;
|
||||
|
||||
--accent: 36 14% 93%; /* #efece7 Sektionswechsel, klar statt Sand */
|
||||
--accent-dark: 36 14% 88%;
|
||||
|
||||
--border: 36 12% 88%; /* #e6e2dc */
|
||||
--input: 0 0% 100%;
|
||||
|
||||
--hero-container: 40 14% 95%;
|
||||
--hero-container-dark: 40 12% 90%;
|
||||
--hero-container-light: 40 25% 98%;
|
||||
|
||||
/* Footer dunkel in Tanne – gibt der hellen Seite unten einen klaren Abschluss */
|
||||
--footer-bg: 175 16% 15%;
|
||||
--footer-fg: 38 33% 95%;
|
||||
--footer-muted: 38 20% 76%;
|
||||
--footer-line: 38 20% 76%;
|
||||
|
||||
/* Karten sind jetzt weiß auf fast weiß: etwas mehr Schatten, damit sie sich lösen */
|
||||
--shadow-card: 0 6px 24px -10px hsl(var(--foreground) / 0.14);
|
||||
--shadow-elevated: 0 24px 48px -20px hsl(var(--foreground) / 0.2);
|
||||
}
|
||||
|
||||
/* ── Interior, Entwurf „Greige“ (Nachtrag 19, Briefing Marcel 24.09.) ─────
|
||||
Heller, leicht kühler Grundton (Greige statt Creme), deutlich präsenteres
|
||||
Tannengrün, Gold nur als feine Linie. Liegt als Klasse am <body> der
|
||||
Entwurfsseite /entwurf – nicht am <html> wie der Browser-Schalter, damit
|
||||
sich beide nicht überlagern. */
|
||||
.world-interior.palette-greige {
|
||||
--background: 45 14% 93%; /* #f0eeeb Greige, Off-White */
|
||||
--foreground: 166 25% 13%; /* Tannengrau für Text */
|
||||
|
||||
--card: 40 20% 97%; /* #f8f7f4 – hell, aber kein steriles Weiß */
|
||||
--card-foreground: 166 25% 13%;
|
||||
--popover: 40 20% 97%;
|
||||
--popover-foreground: 166 25% 13%;
|
||||
|
||||
--primary: 165 44% 14%; /* #14342c Tannengrün, klar und dunkel */
|
||||
--primary-light: 165 32% 22%;
|
||||
--primary-lighter: 165 12% 82%;
|
||||
--primary-dark: 165 50% 10%;
|
||||
--primary-foreground: 45 20% 95%;
|
||||
|
||||
--secondary: 38 45% 46%; /* #aa8341 gedecktes Gold */
|
||||
--secondary-light: 38 45% 56%;
|
||||
--secondary-lighter: 40 40% 80%;
|
||||
--secondary-dark: 38 48% 38%;
|
||||
--secondary-deep: 38 55% 34%; /* Gold als Textfarbe auf hellem Grund */
|
||||
--cta-band-line: 38 45% 46%;
|
||||
--secondary-foreground: 45 20% 95%;
|
||||
|
||||
--clay: 30 8% 50%;
|
||||
|
||||
--muted: 45 12% 90%;
|
||||
--muted-foreground: 160 8% 36%;
|
||||
|
||||
--accent: 45 12% 89%; /* #e8e6e0 Sektionswechsel */
|
||||
--accent-dark: 45 12% 84%;
|
||||
--accent-foreground: 166 25% 13%;
|
||||
|
||||
--dark-bg: 165 44% 14%;
|
||||
--dark-text: 45 20% 95%;
|
||||
--dark-muted: 45 15% 78%;
|
||||
|
||||
/* Footer in Tannengrün, Haarlinie in Gold */
|
||||
--footer-bg: 165 44% 14%;
|
||||
--footer-fg: 45 20% 95%;
|
||||
--footer-muted: 45 15% 78%;
|
||||
--footer-line: 38 45% 46%;
|
||||
|
||||
--cta-band-end: 165 30% 22%;
|
||||
|
||||
--border: 40 10% 84%;
|
||||
--input: 40 20% 98%;
|
||||
--ring: 165 44% 14%;
|
||||
|
||||
--hero-container: 40 20% 97%;
|
||||
--hero-container-dark: 45 12% 90%;
|
||||
--hero-container-light: 40 25% 98%;
|
||||
|
||||
--shadow-card: 0 6px 24px -10px hsl(var(--foreground) / 0.14);
|
||||
--shadow-elevated: 0 30px 60px -28px hsl(var(--primary) / 0.35);
|
||||
}
|
||||
}
|
||||
|
||||
/* Interior: Ocker ist Akzent, kein Flächenton unter Text – Creme auf Ocker erreicht
|
||||
|
|
@ -173,4 +269,202 @@
|
|||
color: hsl(var(--secondary-deep));
|
||||
}
|
||||
|
||||
/* Variante „Modern“: auf dem dunklen Footer trägt Ocker in voller Stärke. */
|
||||
.palette-modern .world-interior .section-footer .text-secondary {
|
||||
color: hsl(var(--secondary));
|
||||
}
|
||||
|
||||
/* Variante „Modern“: Abschlussband und Footer sind beide dunkel – eine helle Haarlinie trennt sie. */
|
||||
.palette-modern .world-interior .section-footer {
|
||||
border-top: 1px solid hsl(var(--footer-muted) / 0.35);
|
||||
}
|
||||
|
||||
/* Der schwebende Termin-Button liegt über Bildern und weißen Karten: er bekommt eine
|
||||
Fläche in Kartenfarbe, damit die Kontur nicht im Hintergrund verschwindet. */
|
||||
.world-interior .appointment-toggle.btn-secondary-accent {
|
||||
background: hsl(var(--card)) !important;
|
||||
}
|
||||
|
||||
.world-interior .appointment-toggle.btn-secondary-accent:hover {
|
||||
background: hsl(var(--primary)) !important;
|
||||
}
|
||||
|
||||
/* Cookie-Manager (acme/cookie-consent) in der Interior-Welt: die Paketfarben aus
|
||||
config/cookie-consent.php sind das Blau der Immobilien-Welt. Hier folgt der schwebende
|
||||
Button dem Akzent-Button der Seite (Tanne, Hover heller), Icon, Schalter und Link-Hover
|
||||
tragen Ocker, „Speichern“ liegt in Tanne. Die Immobilien-Welt behält ihr Blau. */
|
||||
.world-interior .cookie-consent-wrapper {
|
||||
--cc-primary: hsl(var(--secondary));
|
||||
--cc-primary-hover: hsl(var(--secondary-dark));
|
||||
--cc-save: hsl(var(--primary));
|
||||
--cc-save-hover: hsl(var(--primary-light));
|
||||
--cc-button-bg: hsl(var(--primary));
|
||||
--cc-button-hover: hsl(var(--primary-light));
|
||||
}
|
||||
|
||||
.world-interior .cookie-consent-wrapper .cc-floating-btn {
|
||||
--tw-ring-color: hsl(var(--secondary)) !important;
|
||||
color: hsl(var(--primary-foreground));
|
||||
background: linear-gradient(145deg, hsl(var(--primary)) 0%, hsl(var(--primary-light)) 100%);
|
||||
}
|
||||
|
||||
.world-interior .cookie-consent-wrapper .cc-floating-btn:hover {
|
||||
background: linear-gradient(145deg, hsl(var(--primary-light)) 0%, hsl(var(--primary)) 100%);
|
||||
}
|
||||
|
||||
/* Variante „Modern“: der Footer ist dunkel, also das negative Logo statt des positiven. */
|
||||
.footer-logo-dark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.palette-modern .world-interior .footer-logo-light {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.palette-modern .world-interior .footer-logo-dark {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@import "./shared-styles.css";
|
||||
|
||||
/* ── Entwurf „Greige“: Footer dunkel mit Goldlinie und negativem Logo ─────── */
|
||||
.world-interior.palette-greige .section-footer {
|
||||
border-top: 1px solid hsl(var(--secondary) / 0.6);
|
||||
}
|
||||
|
||||
.world-interior.palette-greige .section-footer .text-secondary {
|
||||
color: hsl(var(--secondary));
|
||||
}
|
||||
|
||||
.world-interior.palette-greige .footer-logo-light {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.world-interior.palette-greige .footer-logo-dark {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ── Hero des Entwurfs: Rahmen mit Goldkante, grüne Flanken, Slider ────────
|
||||
Goldkanten als Verlaufs-Border: transparenter Rand, Fläche auf padding-box,
|
||||
Goldverlauf auf border-box. Kein Pseudo-Rand mit Maske mehr – der ließ an
|
||||
der gerundeten Kante eine helle Linie durchscheinen. Die grünen Flanken sind
|
||||
Inline-SVGs (components/web/hero-flank-*), skaliert über die Rahmenhöhe. */
|
||||
.hero-draft .hero-frame {
|
||||
border: 1.5px solid transparent;
|
||||
background:
|
||||
linear-gradient(hsl(var(--hero-container)), hsl(var(--hero-container))) padding-box,
|
||||
linear-gradient(
|
||||
125deg,
|
||||
hsl(var(--secondary-light)) 0%,
|
||||
hsl(var(--secondary)) 28%,
|
||||
hsl(var(--secondary) / 0.35) 62%,
|
||||
hsl(var(--secondary-light)) 100%
|
||||
) border-box;
|
||||
box-shadow: var(--shadow-elevated);
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.hero-draft .hero-flank {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.hero-draft .hero-flank-right {
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hero-draft .hero-flank-left {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: 46%;
|
||||
}
|
||||
|
||||
.hero-draft .hero-image {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Goldkante um das Bild, gleiche Technik wie am Rahmen */
|
||||
.hero-draft .hero-image-frame {
|
||||
border: 1.5px solid transparent;
|
||||
border-radius: 1.25rem;
|
||||
background:
|
||||
linear-gradient(hsl(var(--card)), hsl(var(--card))) padding-box,
|
||||
linear-gradient(
|
||||
135deg,
|
||||
hsl(var(--secondary-light)) 0%,
|
||||
hsl(var(--secondary)) 40%,
|
||||
hsl(var(--secondary) / 0.4) 100%
|
||||
) border-box;
|
||||
box-shadow: var(--shadow-elevated);
|
||||
}
|
||||
|
||||
.hero-draft .hero-eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.28em;
|
||||
text-transform: uppercase;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.hero-draft .hero-eyebrow::after {
|
||||
content: "";
|
||||
width: 3.5rem;
|
||||
height: 1px;
|
||||
background: hsl(var(--secondary));
|
||||
}
|
||||
|
||||
.hero-draft .hero-title {
|
||||
font-family: var(--font-secondary);
|
||||
font-size: clamp(2rem, 3.1vw, 3.3rem);
|
||||
line-height: 1.12;
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.01em;
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.hero-draft .hero-title em {
|
||||
font-style: normal;
|
||||
color: hsl(var(--secondary-deep));
|
||||
}
|
||||
|
||||
/* Button: Goldrand rundum, unten kräftiger, Pfeil in Gold */
|
||||
.hero-draft .btn-hero {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 1rem 1.75rem;
|
||||
border-radius: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: hsl(var(--primary-foreground));
|
||||
background: linear-gradient(145deg, hsl(var(--primary)) 0%, hsl(var(--primary-light)) 100%);
|
||||
border: 1px solid hsl(var(--secondary));
|
||||
border-bottom-width: 3px;
|
||||
box-shadow: 0 14px 30px -14px hsl(var(--primary) / 0.6);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.hero-draft .btn-hero svg {
|
||||
color: hsl(var(--secondary-light));
|
||||
}
|
||||
|
||||
.hero-draft .btn-hero:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 18px 34px -14px hsl(var(--primary) / 0.7);
|
||||
}
|
||||
|
||||
.hero-draft .hero-slide {
|
||||
transition: opacity 1.4s ease-in-out;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hero-draft .hero-slide {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -96,6 +96,15 @@ return [
|
|||
'card_title' => 'B2in Interior',
|
||||
'card_text' => 'Einrichtung, <span class="text-secondary">die bleibt.</span>',
|
||||
],
|
||||
'hero_slider' => [
|
||||
'eyebrow' => 'B2in Interior',
|
||||
'title' => '<em>Räume aus einer Hand.</em> Mit starken Marken und persönlichen Lösungen.',
|
||||
'subtitle' => 'Wir vereinen CABINET, Rotpunkt Küchen, Febrü und ein Netzwerk regionaler Experten, um individuelle Interior-Lösungen für Ihr Zuhause oder Ihr Projekt zu realisieren.',
|
||||
'cta_text' => 'Unsere Marken entdecken',
|
||||
'cta_link' => '/marken',
|
||||
'interval' => 7,
|
||||
'slides' => [],
|
||||
],
|
||||
'founder_bar' => [
|
||||
'image' => 'b2in/marcel-scheibe.jpg',
|
||||
'name' => 'Marcel Scheibe',
|
||||
|
|
@ -203,7 +212,7 @@ return [
|
|||
[
|
||||
'icon' => 'building-office-2',
|
||||
'title' => 'Ausstattung für Ihr Projekt',
|
||||
'description' => 'Vollständige Einrichtung für Objekte, Büros und Projektentwicklungen – koordiniert aus einer Hand, von der Küche bis zur letzten Blume. Mit deutscher Vertragssicherheit und einem eingespielten Herstellernetzwerk.',
|
||||
'description' => 'Vollständige Einrichtung für Objekte, Büros und Projektentwicklungen – koordiniert aus einer Hand, von der Küche bis zur letzten Blume. Mit einem zentralen deutschen Vertragspartner und einem eingespielten Herstellernetzwerk.',
|
||||
'button_text' => 'Projekt besprechen',
|
||||
'button_link' => '/contact',
|
||||
],
|
||||
|
|
@ -251,15 +260,15 @@ return [
|
|||
],
|
||||
[
|
||||
'slug' => 'tischlerei',
|
||||
'name' => 'Tischlerei',
|
||||
'tagline' => 'Maßarbeit, die den Raum vollendet.',
|
||||
'teaser' => 'Aufbau, Sonderlösungen und Veredelung aus einer Tischlerei in Ostwestfalen – dort, wo Serienmöbel aufhören und Ihr Raum anfängt.',
|
||||
'name' => 'Otterpohl Möbelservice',
|
||||
'tagline' => 'Nicht easy click & buy. Aber dennoch easy to get.',
|
||||
'teaser' => 'Individuelle Möbel, Türen, Treppen, Küchen, Wandverkleidungen und Objektbau – bis hin zum kompletten Innenausbau. Tischlerhandwerk aus Rheda-Wiedenbrück.',
|
||||
'image' => '',
|
||||
'logo' => '',
|
||||
'link_text' => 'Mehr zur Tischlerei',
|
||||
'logo' => 'marken/otterpohl.png',
|
||||
'link_text' => 'Mehr zu Otterpohl',
|
||||
'origin_city' => 'Rheda-Wiedenbrück',
|
||||
'origin_distance' => 'rund 35 km',
|
||||
'origin_role' => 'Unsere Werkstatt für Aufbau, Sonderlösungen und Veredelung.',
|
||||
'origin_role' => 'Tischlerei und Innenausbau aus Rheda-Wiedenbrück – vom einzelnen Möbel bis zum ganzen Raum, inklusive Montage.',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
|
@ -292,10 +301,30 @@ return [
|
|||
],
|
||||
[
|
||||
'title' => 'Aufbau vor Ort, Sonderwünsche inklusive',
|
||||
'description' => 'Unsere Tischlerei baut auf und ergänzt, was der Raum zusätzlich braucht: Blenden, Anschlüsse, Sonderlösungen.',
|
||||
'description' => 'Otterpohl baut auf und ergänzt, was der Raum zusätzlich braucht: Blenden, Anschlüsse, Sonderlösungen.',
|
||||
],
|
||||
],
|
||||
],
|
||||
// Gemeinsamer Handwerk-Abschluss jeder Markenseite (Feedback 17.09., Nachtrag 15). Erscheint auf
|
||||
// allen Markenseiten außer bei Otterpohl selbst; der Link führt zur Otterpohl-Seite.
|
||||
'brand_craft' => [
|
||||
'title' => 'Planung plus Handwerk',
|
||||
'paragraphs' => [
|
||||
'CABINET, Rotpunkt und Febrü decken bereits eine enorme planerische und gestalterische Bandbreite ab. Es geht deshalb ausdrücklich nicht um Sonderanfertigungen um jeden Preis.',
|
||||
'Natürlich gibt es trotzdem diesen einen Fall, bei dem selbst ein hervorragendes System an seine Grenze kommt: der Übergang zur Architektur, die besondere Verkleidung, die Wand, die noch fehlt, oder der letzte Millimeter. Dann greift unsere Zusammenarbeit mit Otterpohl aus Rheda-Wiedenbrück – Tischlerhandwerk, Innenausbau und Montage aus einer Hand.',
|
||||
'Wir wägen gestalterischen Mehrwert, technische Notwendigkeit und wirtschaftliche Sinnhaftigkeit ehrlich miteinander ab. Am Ende entscheiden aber Ihre Wünsche – nicht unser Drang, Ihnen noch eine Sonderlösung zu verkaufen.',
|
||||
],
|
||||
'detail' => [
|
||||
'title' => 'Wenn das Handwerk den letzten Millimeter übernimmt',
|
||||
'paragraphs' => [
|
||||
'Möglich sind beispielsweise passgenaue Nischenlösungen, besondere Verkleidungen, raumhohe Einbauten, Übergänge zu angrenzenden Möbeln, individuelle Lackierungen und Echtholzfurniere oder Anpassungen an die vorhandene Architektur.',
|
||||
'Bei Bedarf endet das Konzept nicht am Möbel. Auch Trockenbau, Tapeten, Fußböden, Wandgestaltung, Beleuchtung und weitere Details können einbezogen werden.',
|
||||
],
|
||||
],
|
||||
'claim' => 'Die Möglichkeiten eines Maßsystems. Die Flexibilität des Handwerks.',
|
||||
'link_text' => 'Mehr zu Otterpohl',
|
||||
'brand_slug' => 'tischlerei',
|
||||
],
|
||||
'sponsoring' => [
|
||||
'title' => 'Regional <span class="text-secondary">verbunden</span>',
|
||||
'subtitle' => 'Wir unterstützen Vereine und Organisationen aus der Region – weil Verwurzelung mehr ist als eine Adresse.',
|
||||
|
|
|
|||
|
|
@ -17,6 +17,20 @@ return [
|
|||
'menu_open' => 'Menü öffnen',
|
||||
|
||||
// Welten-Wechsler (Interior / Immobilien)
|
||||
// Farb-Schalter für die Abstimmung der Palette (Nachtrag 18)
|
||||
'palette' => [
|
||||
'label' => 'Farbwelt',
|
||||
'classic' => 'Bisher',
|
||||
'modern' => 'Neu',
|
||||
'draft' => 'Entwurf',
|
||||
],
|
||||
// Slideshow im Hero des Entwurfs (Nachtrag 19)
|
||||
'hero_slider' => [
|
||||
'label' => 'Einblicke in unsere Marken',
|
||||
'dots' => 'Bild wählen',
|
||||
'dot' => 'Bild :n von :total: :label',
|
||||
'empty' => 'Noch keine Bilder hinterlegt.',
|
||||
],
|
||||
'worlds_label' => 'Bereich wechseln',
|
||||
'world_interior' => 'Interior',
|
||||
'world_immobilien' => 'Immobilien',
|
||||
|
|
@ -39,7 +53,8 @@ return [
|
|||
'success' => 'Vielen Dank! Wir melden uns zeitnah bei Ihnen und stimmen den Termin ab.',
|
||||
'new_request' => 'Weitere Anfrage',
|
||||
'date_in_past' => 'Bitte einen Termin ab heute wählen.',
|
||||
'whatsapp_intro' => 'Lieber direkt schreiben? Wir bereiten die Nachricht mit Ihren Angaben vor.',
|
||||
'whatsapp_intro' => 'Am schnellsten per WhatsApp – wir bereiten die Nachricht mit Ihren Angaben vor.',
|
||||
'or_form' => 'oder per Formular',
|
||||
'whatsapp_text' => 'Hallo B2in Interior, ich hätte gern einen Beratungstermin.',
|
||||
'whatsapp_note' => 'Öffnet WhatsApp mit vorbereitetem Text. Abgeschickt wird die Nachricht erst von Ihnen.',
|
||||
],
|
||||
|
|
@ -58,12 +73,22 @@ return [
|
|||
'more_about' => 'Mehr zu :name',
|
||||
|
||||
// Markengalerie – realisierte Projekte vs. Entwuerfe
|
||||
'brand_gallery_title' => 'Referenzen und Inspirationen',
|
||||
'brand_gallery_intro' => 'Ausgeführte Arbeiten aus unseren Aufträgen – und Visualisierungen, die zeigen, was in einem Raum möglich ist.',
|
||||
'brand_gallery_title' => 'Inspirationen und Entwürfe',
|
||||
'brand_gallery_intro' => 'Visualisierungen, die zeigen, was in einem Raum möglich ist – noch bevor das erste Möbel steht.',
|
||||
// Varianten je Galerie-Inhalt (Nachtrag 19): nur Entwürfe / nur Projekte und Werkstatt / gemischt
|
||||
'brand_gallery_title_inspirations' => 'Inspirationen und Entwürfe',
|
||||
'brand_gallery_intro_inspirations' => 'Visualisierungen, die zeigen, was in einem Raum möglich ist – noch bevor das erste Möbel steht.',
|
||||
'brand_gallery_title_projects' => 'Referenzen',
|
||||
'brand_gallery_intro_projects' => 'Realisierte Projekte – so sieht es aus, wenn Planung und Handwerk fertig sind.',
|
||||
'brand_gallery_title_projects_company' => 'Referenzen und Einblicke',
|
||||
'brand_gallery_intro_projects_company' => 'Realisierte Projekte und ein Blick in die Werkstatt, in der sie entstehen.',
|
||||
'brand_gallery_title_mixed' => 'Projekte und Entwürfe',
|
||||
'brand_gallery_intro_mixed' => 'Umgesetzte Projekte und Visualisierungen, die zeigen, was in einem Raum möglich ist.',
|
||||
'brand_gallery_tab_projects' => 'Realisierte Projekte',
|
||||
'brand_gallery_tab_inspirations' => 'Inspirationen und Entwürfe',
|
||||
'brand_gallery_badge_project' => 'Realisiertes Projekt',
|
||||
'brand_gallery_badge_inspiration' => 'Visualisierung',
|
||||
'brand_gallery_badge_company' => 'Aus der Werkstatt',
|
||||
'brand_gallery_inspiration_note' => 'Entwürfe und Visualisierungen. Sie zeigen gestalterische Möglichkeiten, noch bevor das erste Möbel steht.',
|
||||
'previous' => 'Vorheriges Bild',
|
||||
'next' => 'Nächstes Bild',
|
||||
|
|
|
|||
|
|
@ -93,6 +93,15 @@ return [
|
|||
'card_title' => 'B2in Interior',
|
||||
'card_text' => 'Interiors <span class="text-secondary">that last.</span>',
|
||||
],
|
||||
'hero_slider' => [
|
||||
'eyebrow' => 'B2in Interior',
|
||||
'title' => '<em>Rooms from a single source.</em> With strong brands and personal solutions.',
|
||||
'subtitle' => 'We bring together CABINET, Rotpunkt Küchen, Febrü and a network of regional experts to create individual interior solutions for your home or your project.',
|
||||
'cta_text' => 'Discover our brands',
|
||||
'cta_link' => '/marken',
|
||||
'interval' => 7,
|
||||
'slides' => [],
|
||||
],
|
||||
'founder_bar' => [
|
||||
'image' => 'b2in/marcel-scheibe.jpg',
|
||||
'name' => 'Marcel Scheibe',
|
||||
|
|
@ -200,7 +209,7 @@ return [
|
|||
[
|
||||
'icon' => 'building-office-2',
|
||||
'title' => 'Fit-out for your project',
|
||||
'description' => 'Complete furnishing for properties, offices and developments – coordinated from a single source, from the kitchen to the last flower. With German contractual reliability and an established manufacturer network.',
|
||||
'description' => 'Complete furnishing for properties, offices and developments – coordinated from a single source, from the kitchen to the last flower. With a single German contract partner and an established manufacturer network.',
|
||||
'button_text' => 'Discuss your project',
|
||||
'button_link' => '/contact',
|
||||
],
|
||||
|
|
@ -248,15 +257,15 @@ return [
|
|||
],
|
||||
[
|
||||
'slug' => 'tischlerei',
|
||||
'name' => 'Joinery',
|
||||
'tagline' => 'Craftsmanship that completes the room.',
|
||||
'teaser' => 'Assembly, bespoke solutions and finishing from a joinery in East Westphalia – where series furniture ends and your room begins.',
|
||||
'name' => 'Otterpohl Möbelservice',
|
||||
'tagline' => 'Not easy click & buy. But still easy to get.',
|
||||
'teaser' => 'Bespoke furniture, doors, stairs, kitchens, wall cladding and contract work – up to complete interior fit-outs. Joinery from Rheda-Wiedenbrück.',
|
||||
'image' => '',
|
||||
'logo' => '',
|
||||
'link_text' => 'More about the joinery',
|
||||
'logo' => 'marken/otterpohl.png',
|
||||
'link_text' => 'More about Otterpohl',
|
||||
'origin_city' => 'Rheda-Wiedenbrück',
|
||||
'origin_distance' => 'about 35 km',
|
||||
'origin_role' => 'Our workshop for assembly, custom solutions and finishing.',
|
||||
'origin_role' => 'Joinery and interior fit-out from Rheda-Wiedenbrück – from a single piece to the whole room, installation included.',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
|
@ -286,10 +295,28 @@ return [
|
|||
],
|
||||
[
|
||||
'title' => 'Assembly on site, special requests included',
|
||||
'description' => 'Our joinery assembles and adds what the room needs beyond that: panels, connections, custom solutions.',
|
||||
'description' => 'Otterpohl assembles and adds what the room needs beyond that: panels, connections, custom solutions.',
|
||||
],
|
||||
],
|
||||
],
|
||||
'brand_craft' => [
|
||||
'title' => 'Planning plus craftsmanship',
|
||||
'paragraphs' => [
|
||||
'CABINET, Rotpunkt and Febrü already cover an enormous range of planning and design options. So this is explicitly not about custom-made pieces at any price.',
|
||||
'Still, there is always that one case where even an excellent system reaches its limit: the transition to the architecture, a special cladding, the wall that is still missing, or the last millimetre. That is where our collaboration with Otterpohl from Rheda-Wiedenbrück comes in – joinery, interior fit-out and installation from a single source.',
|
||||
'We weigh design value, technical necessity and economic sense honestly. In the end, your wishes decide – not our urge to sell you yet another special solution.',
|
||||
],
|
||||
'detail' => [
|
||||
'title' => 'When craftsmanship takes over the last millimetre',
|
||||
'paragraphs' => [
|
||||
'Possible are, for example, precisely fitted alcove solutions, special claddings, full-height built-ins, transitions to adjoining furniture, individual lacquer finishes and real-wood veneers, or adaptations to the existing architecture.',
|
||||
'If needed, the concept does not end at the furniture. Drywall, wallpaper, flooring, wall design, lighting and further details can be included.',
|
||||
],
|
||||
],
|
||||
'claim' => 'The possibilities of a made-to-measure system. The flexibility of craftsmanship.',
|
||||
'link_text' => 'More about Otterpohl',
|
||||
'brand_slug' => 'tischlerei',
|
||||
],
|
||||
'sponsoring' => [
|
||||
'title' => 'Rooted <span class="text-secondary">in the region</span>',
|
||||
'subtitle' => 'We support clubs and organisations from our region – because being rooted is more than an address.',
|
||||
|
|
|
|||
|
|
@ -17,6 +17,20 @@ return [
|
|||
'menu_open' => 'Open menu',
|
||||
|
||||
// World switcher (Interior / Real estate)
|
||||
// Farb-Schalter für die Abstimmung der Palette (Nachtrag 18)
|
||||
'palette' => [
|
||||
'label' => 'Colour scheme',
|
||||
'classic' => 'Current',
|
||||
'modern' => 'New',
|
||||
'draft' => 'Draft',
|
||||
],
|
||||
// Slideshow in the draft hero (Nachtrag 19)
|
||||
'hero_slider' => [
|
||||
'label' => 'Glimpses of our brands',
|
||||
'dots' => 'Choose image',
|
||||
'dot' => 'Image :n of :total: :label',
|
||||
'empty' => 'No images yet.',
|
||||
],
|
||||
'worlds_label' => 'Switch area',
|
||||
'world_interior' => 'Interior',
|
||||
'world_immobilien' => 'Real estate',
|
||||
|
|
@ -39,7 +53,8 @@ return [
|
|||
'success' => 'Thank you! We will get back to you shortly to arrange the appointment.',
|
||||
'new_request' => 'Another request',
|
||||
'date_in_past' => 'Please choose a date from today onwards.',
|
||||
'whatsapp_intro' => 'Prefer to write directly? We prepare the message with your details.',
|
||||
'whatsapp_intro' => 'Fastest via WhatsApp – we prepare the message with your details.',
|
||||
'or_form' => 'or use the form',
|
||||
'whatsapp_text' => 'Hello B2in Interior, I would like to arrange an appointment.',
|
||||
'whatsapp_note' => 'Opens WhatsApp with a prepared text. You send the message yourself.',
|
||||
],
|
||||
|
|
@ -58,12 +73,21 @@ return [
|
|||
'more_about' => 'More about :name',
|
||||
|
||||
// Brand gallery – realised projects vs. drafts
|
||||
'brand_gallery_title' => 'References and inspiration',
|
||||
'brand_gallery_intro' => 'Completed work from our projects – and visualisations showing what is possible in a room.',
|
||||
'brand_gallery_title' => 'Inspiration and drafts',
|
||||
'brand_gallery_intro' => 'Visualisations that show what is possible in a room – before the first piece of furniture is in place.',
|
||||
'brand_gallery_title_inspirations' => 'Inspiration and drafts',
|
||||
'brand_gallery_intro_inspirations' => 'Visualisations that show what is possible in a room – before the first piece of furniture is in place.',
|
||||
'brand_gallery_title_projects' => 'References',
|
||||
'brand_gallery_intro_projects' => 'Completed projects – this is what it looks like once planning and craft are done.',
|
||||
'brand_gallery_title_projects_company' => 'References and insights',
|
||||
'brand_gallery_intro_projects_company' => 'Completed projects and a look into the workshop where they are made.',
|
||||
'brand_gallery_title_mixed' => 'Projects and drafts',
|
||||
'brand_gallery_intro_mixed' => 'Completed projects and visualisations that show what is possible in a room.',
|
||||
'brand_gallery_tab_projects' => 'Completed projects',
|
||||
'brand_gallery_tab_inspirations' => 'Inspiration and drafts',
|
||||
'brand_gallery_badge_project' => 'Completed project',
|
||||
'brand_gallery_badge_inspiration' => 'Visualisation',
|
||||
'brand_gallery_badge_company' => 'From the workshop',
|
||||
'brand_gallery_inspiration_note' => 'Drafts and visualisations. They show design possibilities before the first piece of furniture is in place.',
|
||||
'previous' => 'Previous image',
|
||||
'next' => 'Next image',
|
||||
|
|
|
|||
55
resources/views/components/palette-switch.blade.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
{{-- Farb-Schalter für die Abstimmung (Nachtrag 18 + 19): bisherige Creme-Palette, die hellere
|
||||
Variante „Neu“ und der Entwurf vom 24.09. Schwimmt am linken Rand in der Mitte – unten links
|
||||
sitzt der Cookie-Button, unten rechts die Terminanfrage.
|
||||
„Bisher“ und „Neu“ schalten im Browser um (localStorage b2in-palette, im <head> gesetzt,
|
||||
?palette=modern|classic per Link). „Entwurf“ ist eine eigene Seite (/entwurf) mit fester
|
||||
Palette; von dort führen die beiden anderen Einträge zurück auf die Startseite. --}}
|
||||
@props(['draft' => false])
|
||||
|
||||
<div class="fixed left-3 top-1/2 z-40 -translate-y-1/2 print:hidden" data-palette-switch
|
||||
@unless ($draft)
|
||||
x-data="{
|
||||
palette: document.documentElement.classList.contains('palette-modern') ? 'modern' : 'classic',
|
||||
choose(value) {
|
||||
this.palette = value;
|
||||
document.documentElement.classList.toggle('palette-modern', value === 'modern');
|
||||
try { localStorage.setItem('b2in-palette', value); } catch (e) {}
|
||||
},
|
||||
}"
|
||||
@endunless>
|
||||
<div class="flex flex-col items-stretch gap-1 rounded-full border border-border bg-card/95 p-1 text-[11px] font-medium shadow-lg backdrop-blur-sm"
|
||||
role="group" aria-label="{{ __('ui.palette.label') }}">
|
||||
<span class="px-2 pt-1 text-center text-[10px] uppercase tracking-wide text-muted-foreground" aria-hidden="true">
|
||||
{{ __('ui.palette.label') }}
|
||||
</span>
|
||||
|
||||
@if ($draft)
|
||||
<a href="{{ url('/?palette=classic') }}"
|
||||
class="rounded-full px-3 py-1.5 text-center text-muted-foreground transition-colors duration-200 hover:text-foreground">
|
||||
{{ __('ui.palette.classic') }}
|
||||
</a>
|
||||
<a href="{{ url('/?palette=modern') }}"
|
||||
class="rounded-full px-3 py-1.5 text-center text-muted-foreground transition-colors duration-200 hover:text-foreground">
|
||||
{{ __('ui.palette.modern') }}
|
||||
</a>
|
||||
<span aria-current="page" class="rounded-full bg-primary px-3 py-1.5 text-center text-primary-foreground">
|
||||
{{ __('ui.palette.draft') }}
|
||||
</span>
|
||||
@else
|
||||
<button type="button" x-on:click="choose('classic')" x-bind:aria-pressed="(palette === 'classic').toString()"
|
||||
x-bind:class="palette === 'classic' ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:text-foreground'"
|
||||
class="cursor-pointer rounded-full px-3 py-1.5 transition-colors duration-200">
|
||||
{{ __('ui.palette.classic') }}
|
||||
</button>
|
||||
<button type="button" x-on:click="choose('modern')" x-bind:aria-pressed="(palette === 'modern').toString()"
|
||||
x-bind:class="palette === 'modern' ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:text-foreground'"
|
||||
class="cursor-pointer rounded-full px-3 py-1.5 transition-colors duration-200">
|
||||
{{ __('ui.palette.modern') }}
|
||||
</button>
|
||||
<a href="{{ route('home.draft') }}"
|
||||
class="rounded-full px-3 py-1.5 text-center text-muted-foreground transition-colors duration-200 hover:text-foreground">
|
||||
{{ __('ui.palette.draft') }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
18
resources/views/components/web/hero-flank-left.blade.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{{-- Linke Flanke des Entwurfs-Heros (Nachtrag 19): schmaler Haken unten links, oben gerundet,
|
||||
mit Goldlinie. Höhe rund 46 % des Rahmens, Breite aus dem viewBox-Verhältnis. --}}
|
||||
<svg class="hero-flank hero-flank-left hidden lg:block" viewBox="0 0 80 480" preserveAspectRatio="xMinYMax meet"
|
||||
aria-hidden="true" focusable="false" data-hero-flank="left">
|
||||
<defs>
|
||||
<linearGradient id="hero-flank-left-green" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" style="stop-color: hsl(var(--primary-light))" />
|
||||
<stop offset="1" style="stop-color: hsl(var(--primary-dark))" />
|
||||
</linearGradient>
|
||||
<linearGradient id="hero-flank-left-gold" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" style="stop-color: hsl(var(--secondary-light))" />
|
||||
<stop offset="1" style="stop-color: hsl(var(--secondary)); stop-opacity: 0.5" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M0 480V56C0 25 25 0 56 0H80V480Z" fill="url(#hero-flank-left-green)" />
|
||||
<path d="M14 480V60C14 35 35 14 60 14H80" fill="none" stroke="url(#hero-flank-left-gold)" stroke-width="1.5"
|
||||
vector-effect="non-scaling-stroke" />
|
||||
</svg>
|
||||
24
resources/views/components/web/hero-flank-right.blade.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{{-- Rechte Flanke des Entwurfs-Heros (Nachtrag 19): tannengrüner Block mit gerundeter Innenkante,
|
||||
Goldlinie mit Verlauf entlang der Rundung und einer kurzen Zierlinie wie in der Visualisierung.
|
||||
Skaliert über die Rahmenhöhe (height: 100 %, Breite aus dem viewBox-Verhältnis). Farben
|
||||
kommen aus den Tokens, damit die Flanke jede Palette mitmacht. Kann später 1:1 gegen eine
|
||||
gelieferte SVG-Datei getauscht werden. --}}
|
||||
<svg class="hero-flank hero-flank-right hidden lg:block" viewBox="0 0 140 1000" preserveAspectRatio="xMaxYMid meet"
|
||||
aria-hidden="true" focusable="false" data-hero-flank="right">
|
||||
<defs>
|
||||
<linearGradient id="hero-flank-right-green" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" style="stop-color: hsl(var(--primary-light))" />
|
||||
<stop offset="1" style="stop-color: hsl(var(--primary-dark))" />
|
||||
</linearGradient>
|
||||
<linearGradient id="hero-flank-right-gold" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" style="stop-color: hsl(var(--secondary-light))" />
|
||||
<stop offset="0.5" style="stop-color: hsl(var(--secondary)); stop-opacity: 0.45" />
|
||||
<stop offset="1" style="stop-color: hsl(var(--secondary-light))" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M140 0H72C32 0 0 32 0 72V928C0 968 32 1000 72 1000H140Z" fill="url(#hero-flank-right-green)" />
|
||||
<path d="M140 14H74C41 14 14 41 14 74V926C14 959 41 986 74 986H140" fill="none"
|
||||
stroke="url(#hero-flank-right-gold)" stroke-width="1.5" vector-effect="non-scaling-stroke" />
|
||||
<path d="M124 330V600" fill="none" stroke="url(#hero-flank-right-gold)" stroke-width="1.5"
|
||||
vector-effect="non-scaling-stroke" />
|
||||
</svg>
|
||||
|
|
@ -565,13 +565,16 @@ $cancelForm = function () {
|
|||
wire:click="addGalleryImage('{{ CmsBrand::GALLERY_PROJECT }}')">Projekt</flux:button>
|
||||
<flux:button size="xs" variant="ghost" icon="plus"
|
||||
wire:click="addGalleryImage('{{ CmsBrand::GALLERY_INSPIRATION }}')">Inspiration</flux:button>
|
||||
<flux:button size="xs" variant="ghost" icon="plus"
|
||||
wire:click="addGalleryImage('{{ CmsBrand::GALLERY_COMPANY }}')">Werkstatt</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 rounded-lg border border-amber-200 bg-amber-50 p-3 text-sm text-amber-800 dark:border-amber-800 dark:bg-amber-900/20 dark:text-amber-200">
|
||||
<strong>Projekt</strong> = tatsächlich ausgeführter Auftrag.
|
||||
<strong>Projekt</strong> = tatsächlich ausgeführter Auftrag (erscheint auch unter „Referenzen“, solange dort nichts gepflegt ist).
|
||||
<strong>Werkstatt</strong> = Einblick in Fertigung oder Montage des Herstellers.
|
||||
<strong>Inspiration</strong> = Entwurf, Rendering oder KI-Darstellung.
|
||||
Auf der Seite werden beide getrennt gezeigt und beschriftet – bitte nichts vermischen.
|
||||
Auf der Seite trägt jedes Bild seine Beschriftung – bitte nichts vermischen.
|
||||
</div>
|
||||
|
||||
@if (empty($gallery))
|
||||
|
|
@ -589,6 +592,7 @@ $cancelForm = function () {
|
|||
<flux:select wire:model="gallery.{{ $i }}.category" size="sm" class="mb-2">
|
||||
<flux:select.option value="{{ CmsBrand::GALLERY_PROJECT }}">Realisiertes Projekt</flux:select.option>
|
||||
<flux:select.option value="{{ CmsBrand::GALLERY_INSPIRATION }}">Inspiration / Entwurf</flux:select.option>
|
||||
<flux:select.option value="{{ CmsBrand::GALLERY_COMPANY }}">Aus der Werkstatt</flux:select.option>
|
||||
</flux:select>
|
||||
|
||||
<flux:input wire:model="gallery.{{ $i }}.caption" size="sm" class="mb-2"
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@
|
|||
<section class="pb-16">
|
||||
<div class="container-narrow space-y-14">
|
||||
@foreach ($brand['sections'] as $index => $section)
|
||||
<div wire:key="section-{{ $index }}">
|
||||
<div wire:key="section-{{ $index }}" @if (!empty($section['anchor'])) id="{{ $section['anchor'] }}" @endif>
|
||||
@if (!empty($section['title']))
|
||||
<h2 class="text-2xl lg:text-3xl font-medium">{{ $section['title'] }}</h2>
|
||||
@endif
|
||||
|
|
@ -129,6 +129,14 @@
|
|||
@if (!empty($section['claim']))
|
||||
<p class="text-large text-secondary font-medium mt-6">{{ $section['claim'] }}</p>
|
||||
@endif
|
||||
|
||||
@if (!empty($section['link']) && !empty($section['link_text']))
|
||||
<a href="{{ $section['link'] }}"
|
||||
class="text-secondary font-medium mt-4 inline-flex items-center gap-1 hover:underline">
|
||||
{{ $section['link_text'] }}
|
||||
<span aria-hidden="true">→</span>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
|
@ -153,98 +161,65 @@
|
|||
</section>
|
||||
@endif
|
||||
|
||||
{{-- 4. Galerie: realisierte Projekte und Inspirationen strikt getrennt --}}
|
||||
{{-- 4. Galerie. Realisierte Projekte, Einblicke in die Werkstatt und Visualisierungen stehen in
|
||||
einem Raster, unterschieden nur durch das Badge – der Rubrikwechsel ist ausgesetzt
|
||||
(Abstimmung 22.09., Nachtrag 17). Reihenfolge: Projekte, Werkstatt, Visualisierungen.
|
||||
Überschrift und Zeile darunter richten sich nach dem, was die Galerie enthält (Nachtrag 19):
|
||||
nur Visualisierungen → „Inspirationen und Entwürfe“, nur Projekte → „Referenzen“, Projekte
|
||||
und Werkstatt → „Referenzen und Einblicke“, mit Visualisierungen → „Projekte und Entwürfe“. --}}
|
||||
@php
|
||||
$galleryProjects = $gallery['projects'] ?? [];
|
||||
$galleryInspirations = $gallery['inspirations'] ?? [];
|
||||
$hasBothGalleryGroups = $galleryProjects !== [] && $galleryInspirations !== [];
|
||||
$initialGalleryTab = $galleryProjects !== [] ? 'projects' : 'inspirations';
|
||||
$galleryItems = array_merge($gallery['projects'] ?? [], $gallery['company'] ?? [], $gallery['inspirations'] ?? []);
|
||||
|
||||
$galleryLightbox = collect(['projects' => $galleryProjects, 'inspirations' => $galleryInspirations])
|
||||
->map(fn (array $items) => collect($items)->map(fn (array $item) => [
|
||||
'src' => theme_image_url($item['image']),
|
||||
'caption' => $item['caption'] ?? '',
|
||||
'badge' => $item['category'] === \App\Models\CmsBrand::GALLERY_PROJECT
|
||||
? __('ui.brand_gallery_badge_project')
|
||||
: __('ui.brand_gallery_badge_inspiration'),
|
||||
])->values()->all())
|
||||
->all();
|
||||
$galleryHasReal = ! empty($gallery['projects']) || ! empty($gallery['company']);
|
||||
$galleryHasDrafts = ! empty($gallery['inspirations']);
|
||||
$galleryVariant = match (true) {
|
||||
! $galleryHasReal => 'inspirations',
|
||||
$galleryHasDrafts => 'mixed',
|
||||
! empty($gallery['company']) => 'projects_company',
|
||||
default => 'projects',
|
||||
};
|
||||
|
||||
$galleryLightbox = collect($galleryItems)->map(fn (array $item) => [
|
||||
'src' => theme_image_url($item['image']),
|
||||
'caption' => $item['caption'] ?? '',
|
||||
'badge' => \App\Models\CmsBrand::galleryBadge($item['category']),
|
||||
])->values()->all();
|
||||
@endphp
|
||||
|
||||
@if ($galleryProjects || $galleryInspirations)
|
||||
@if ($galleryItems)
|
||||
<section id="galerie" class="section-padding"
|
||||
x-data="{
|
||||
tab: '{{ $initialGalleryTab }}',
|
||||
groups: @js($galleryLightbox),
|
||||
items: @js($galleryLightbox),
|
||||
lightbox: false,
|
||||
current: 0,
|
||||
get items() { return this.groups[this.tab] ?? [] },
|
||||
open(index) { this.current = index; this.lightbox = true },
|
||||
next() { this.current = (this.current + 1) % this.items.length },
|
||||
prev() { this.current = (this.current - 1 + this.items.length) % this.items.length },
|
||||
}">
|
||||
<div class="container-padding">
|
||||
<div class="text-center max-w-2xl mx-auto">
|
||||
<h2 class="text-section-title">{{ __('ui.brand_gallery_title') }}</h2>
|
||||
<p class="text-muted-foreground leading-relaxed mt-4">{{ __('ui.brand_gallery_intro') }}</p>
|
||||
<h2 class="text-section-title">{{ __('ui.brand_gallery_title_'.$galleryVariant) }}</h2>
|
||||
<p class="text-muted-foreground leading-relaxed mt-4">{{ __('ui.brand_gallery_intro_'.$galleryVariant) }}</p>
|
||||
</div>
|
||||
|
||||
@if ($hasBothGalleryGroups)
|
||||
<div class="flex justify-center mt-10" role="tablist" aria-label="{{ __('ui.brand_gallery_title') }}">
|
||||
<div class="inline-flex rounded-full bg-accent p-1 gap-1">
|
||||
@foreach (['projects' => __('ui.brand_gallery_tab_projects'), 'inspirations' => __('ui.brand_gallery_tab_inspirations')] as $tabKey => $tabLabel)
|
||||
<button type="button" role="tab" wire:key="gallery-tab-{{ $tabKey }}"
|
||||
x-on:click="tab = '{{ $tabKey }}'"
|
||||
x-bind:aria-selected="tab === '{{ $tabKey }}' ? 'true' : 'false'"
|
||||
x-bind:class="tab === '{{ $tabKey }}'
|
||||
? 'bg-background text-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground'"
|
||||
class="px-6 py-2.5 rounded-full text-sm font-medium transition-colors cursor-pointer">
|
||||
{{ $tabLabel }}
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@foreach (['projects' => $galleryProjects, 'inspirations' => $galleryInspirations] as $groupKey => $groupItems)
|
||||
@if ($groupItems)
|
||||
<div wire:key="gallery-group-{{ $groupKey }}"
|
||||
@if ($hasBothGalleryGroups) x-show="tab === '{{ $groupKey }}'" x-cloak @endif
|
||||
class="mt-10">
|
||||
@unless ($hasBothGalleryGroups)
|
||||
<h3 class="text-xl font-medium text-center mb-8">
|
||||
{{ $groupKey === 'projects' ? __('ui.brand_gallery_tab_projects') : __('ui.brand_gallery_tab_inspirations') }}
|
||||
</h3>
|
||||
@endunless
|
||||
|
||||
@if ($groupKey === 'inspirations')
|
||||
<p class="text-sm text-muted-foreground text-center max-w-2xl mx-auto mb-8">
|
||||
{{ __('ui.brand_gallery_inspiration_note') }}
|
||||
</p>
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 mt-12">
|
||||
@foreach ($galleryItems as $index => $item)
|
||||
<figure wire:key="gallery-{{ $index }}" class="group">
|
||||
<button type="button" x-on:click="open({{ $index }})"
|
||||
class="relative block w-full overflow-hidden rounded-2xl cursor-pointer">
|
||||
<img src="{{ theme_image_url($item['image']) }}"
|
||||
alt="{{ $item['caption'] ?: $brand['name'] }}" loading="lazy"
|
||||
class="w-full aspect-[4/3] object-cover transition-transform duration-300 group-hover:scale-105" />
|
||||
<span class="absolute top-3 left-3 rounded-full bg-black/60 px-3 py-1 text-xs font-medium text-white backdrop-blur-sm">
|
||||
{{ \App\Models\CmsBrand::galleryBadge($item['category']) }}
|
||||
</span>
|
||||
</button>
|
||||
@if (!empty($item['caption']))
|
||||
<figcaption class="text-sm text-muted-foreground mt-3">{{ $item['caption'] }}</figcaption>
|
||||
@endif
|
||||
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
@foreach ($groupItems as $index => $item)
|
||||
<figure wire:key="gallery-{{ $groupKey }}-{{ $index }}" class="group">
|
||||
<button type="button" x-on:click="open({{ $index }})"
|
||||
class="relative block w-full overflow-hidden rounded-2xl cursor-pointer">
|
||||
<img src="{{ theme_image_url($item['image']) }}"
|
||||
alt="{{ $item['caption'] ?: $brand['name'] }}" loading="lazy"
|
||||
class="w-full aspect-[4/3] object-cover transition-transform duration-300 group-hover:scale-105" />
|
||||
<span class="absolute top-3 left-3 rounded-full bg-black/60 px-3 py-1 text-xs font-medium text-white backdrop-blur-sm">
|
||||
{{ $groupKey === 'projects' ? __('ui.brand_gallery_badge_project') : __('ui.brand_gallery_badge_inspiration') }}
|
||||
</span>
|
||||
</button>
|
||||
@if (!empty($item['caption']))
|
||||
<figcaption class="text-sm text-muted-foreground mt-3">{{ $item['caption'] }}</figcaption>
|
||||
@endif
|
||||
</figure>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
</figure>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Lightbox: ins body geteleportet, damit keine Eltern-Constraints stören --}}
|
||||
|
|
|
|||
|
|
@ -1,139 +1,102 @@
|
|||
{{-- Hero des Entwurfs (Nachtrag 19): Rahmen mit feiner Goldkante und grünen Flanken, links
|
||||
Text mit einem Button, rechts die Slideshow. Das Bild hat ein festes Seitenverhältnis –
|
||||
das Motiv wird auf großen Bildschirmen nicht mehr unten beschnitten (Briefing Punkt 2).
|
||||
Der Wechsel läuft langsam (Überblendung 1,4 s, Standard alle 7 s), pausiert bei Hover und
|
||||
bleibt bei „reduzierter Bewegung“ stehen. --}}
|
||||
<section id="hero" class="hero-draft section-padding relative">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"
|
||||
x-data="{
|
||||
active: 0,
|
||||
count: {{ count($slides) }},
|
||||
interval: {{ $this->intervalMs }},
|
||||
timer: null,
|
||||
paused: false,
|
||||
reduced: window.matchMedia('(prefers-reduced-motion: reduce)').matches,
|
||||
start() {
|
||||
if (this.reduced || this.count < 2) { return; }
|
||||
this.stop();
|
||||
this.timer = setInterval(() => { if (!this.paused && !document.hidden) { this.next(); } }, this.interval);
|
||||
},
|
||||
stop() { if (this.timer) { clearInterval(this.timer); this.timer = null; } },
|
||||
go(i) { this.active = (i + this.count) % this.count; this.start(); },
|
||||
next() { this.go(this.active + 1); },
|
||||
prev() { this.go(this.active - 1); },
|
||||
}"
|
||||
x-init="start()" x-on:keydown.arrow-right.prevent="next()" x-on:keydown.arrow-left.prevent="prev()">
|
||||
|
||||
<div x-data="heroSlider()" x-init="init()">
|
||||
<section class="relative mb-10">
|
||||
<!-- Slider Container -->
|
||||
<div class="absolute inset-0 overflow-hidden">
|
||||
@if(!empty($content['slides']))
|
||||
@foreach($content['slides'] as $index => $slide)
|
||||
<div
|
||||
class="absolute inset-0 transition-opacity duration-1000 ease-in-out"
|
||||
:class="currentSlide === {{ $index }} ? 'opacity-100' : 'opacity-0'"
|
||||
>
|
||||
<img
|
||||
src="{{ theme_image_url($slide['image']) }}"
|
||||
alt="{{ $slide['image_alt'] }}"
|
||||
class="size-full object-cover"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-gray-900 opacity-20"></div>
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
<div class="hero-frame relative overflow-hidden rounded-[28px]">
|
||||
<x-web.hero-flank-right />
|
||||
<x-web.hero-flank-left />
|
||||
|
||||
<!-- Content -->
|
||||
<div class="relative mx-auto flex max-w-3xl flex-col items-center px-6 py-32 text-center sm:py-64 lg:px-0">
|
||||
<h1 class="text-hero text-white">
|
||||
{!! $content['title'] !!}
|
||||
</h1>
|
||||
<p class="mt-4 text-xl text-white">{!! $content['subtitle'] !!}</p>
|
||||
<a href="{{ $content['cta1_link'] }}" class="mt-8 inline-block btn-secondary-accent">{{ $content['cta1_text'] }}</a>
|
||||
</div>
|
||||
{{-- Rechts wenig Innenabstand: das Bild liegt über der grünen Flanke, wie in der Visualisierung. --}}
|
||||
<div class="relative grid items-center gap-10 p-6 sm:p-10 lg:grid-cols-12 lg:gap-10 lg:py-5 lg:pl-16 lg:pr-12">
|
||||
{{-- Text --}}
|
||||
<div class="space-y-6 lg:col-span-5 lg:py-10 lg:pr-4">
|
||||
@if (!empty($content['eyebrow']))
|
||||
<p class="hero-eyebrow">{{ $content['eyebrow'] }}</p>
|
||||
@endif
|
||||
|
||||
<!-- Navigation Dots -->
|
||||
@if(!empty($content['slides']) && count($content['slides']) > 1)
|
||||
<div class="absolute bottom-8 left-1/2 transform -translate-x-1/2">
|
||||
<div class="flex space-x-3">
|
||||
@foreach($content['slides'] as $index => $slide)
|
||||
<button
|
||||
@click="setSlide({{ $index }})"
|
||||
class="w-3 h-3 rounded-full border-2 border-white/50 transition-all duration-300"
|
||||
:class="currentSlide === {{ $index }} ? 'bg-white border-white' : 'bg-transparent hover:border-white/80'"
|
||||
aria-label="{{ __('ui.show_slide', ['number' => $index + 1]) }}"
|
||||
></button>
|
||||
@endforeach
|
||||
<h1 class="hero-title">{!! $content['title'] ?? '' !!}</h1>
|
||||
|
||||
@if (!empty($content['subtitle']))
|
||||
<p class="max-w-md text-base leading-relaxed text-muted-foreground">{!! $content['subtitle'] !!}</p>
|
||||
@endif
|
||||
|
||||
<div>
|
||||
<a href="{{ $content['cta_link'] ?? '/marken' }}" class="btn-hero">
|
||||
{{ $content['cta_text'] ?? __('ui.all_brands_cta') }}
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"
|
||||
aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Slideshow --}}
|
||||
<div class="hero-image lg:col-span-7">
|
||||
<div class="hero-image-frame">
|
||||
<div class="relative aspect-[4/3] overflow-hidden rounded-[calc(1.25rem-1.5px)] bg-muted lg:aspect-[5/4]"
|
||||
role="group" aria-roledescription="carousel" aria-label="{{ __('ui.hero_slider.label') }}"
|
||||
x-on:mouseenter="paused = true" x-on:mouseleave="paused = false"
|
||||
x-on:focusin="paused = true" x-on:focusout="paused = false" tabindex="0">
|
||||
@forelse ($slides as $index => $slide)
|
||||
<figure wire:key="hero-slide-{{ $index }}" data-hero-slide="{{ $index }}"
|
||||
class="hero-slide absolute inset-0 m-0 {{ $index === 0 ? 'opacity-100' : 'opacity-0' }}"
|
||||
x-bind:class="active === {{ $index }} ? 'opacity-100' : 'opacity-0 pointer-events-none'"
|
||||
x-bind:aria-hidden="(active !== {{ $index }}).toString()">
|
||||
<img src="{{ theme_image_url($slide['image']) }}" alt="{{ $slide['alt'] }}"
|
||||
class="h-full w-full object-cover" @if ($index > 0) loading="lazy" @endif />
|
||||
@if ($slide['label'] !== '')
|
||||
<figcaption
|
||||
class="absolute bottom-5 left-5 rounded-full bg-card/85 px-3 py-1 text-[10px] font-medium uppercase tracking-[0.18em] text-foreground/80 backdrop-blur-sm">
|
||||
{{ $slide['label'] }}
|
||||
</figcaption>
|
||||
@endif
|
||||
</figure>
|
||||
@empty
|
||||
<div class="flex h-full items-center justify-center text-sm text-muted-foreground">
|
||||
{{ __('ui.hero_slider.empty') }}
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Arrow Navigation (Optional) -->
|
||||
@if(!empty($content['slides']) && count($content['slides']) > 1)
|
||||
<button
|
||||
@click="previousSlide()"
|
||||
class="absolute left-6 top-1/2 transform -translate-y-1/2 text-white/70 hover:text-white transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-white/50 rounded-full p-2"
|
||||
aria-label="{{ __('ui.previous_image') }}"
|
||||
>
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="nextSlide()"
|
||||
class="absolute right-6 top-1/2 transform -translate-y-1/2 text-white/70 hover:text-white transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-white/50 rounded-full p-2"
|
||||
aria-label="{{ __('ui.next_image') }}"
|
||||
>
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
@endif
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function heroSlider() {
|
||||
return {
|
||||
currentSlide: 0,
|
||||
totalSlides: {{ !empty($content['slides']) ? count($content['slides']) : 0 }},
|
||||
autoplayInterval: null,
|
||||
autoplayTimeout: null,
|
||||
|
||||
init() {
|
||||
if (this.totalSlides > 1) {
|
||||
this.startAutoplay();
|
||||
}
|
||||
},
|
||||
|
||||
// Automatischer Wechsel (nur für Intervall)
|
||||
autoNextSlide() {
|
||||
this.currentSlide = (this.currentSlide + 1) % this.totalSlides;
|
||||
},
|
||||
|
||||
// Manuelle Navigation
|
||||
nextSlide() {
|
||||
this.currentSlide = (this.currentSlide + 1) % this.totalSlides;
|
||||
this.resetAutoplay();
|
||||
},
|
||||
|
||||
previousSlide() {
|
||||
this.currentSlide = (this.currentSlide - 1 + this.totalSlides) % this.totalSlides;
|
||||
this.resetAutoplay();
|
||||
},
|
||||
|
||||
setSlide(index) {
|
||||
this.currentSlide = index;
|
||||
this.resetAutoplay();
|
||||
},
|
||||
|
||||
startAutoplay() {
|
||||
// Zuerst alle bestehenden Timer löschen
|
||||
this.stopAutoplay();
|
||||
|
||||
this.autoplayInterval = setInterval(() => {
|
||||
this.autoNextSlide();
|
||||
}, 5000); // Wechsel alle 5 Sekunden
|
||||
},
|
||||
|
||||
stopAutoplay() {
|
||||
if (this.autoplayInterval) {
|
||||
clearInterval(this.autoplayInterval);
|
||||
this.autoplayInterval = null;
|
||||
}
|
||||
if (this.autoplayTimeout) {
|
||||
clearTimeout(this.autoplayTimeout);
|
||||
this.autoplayTimeout = null;
|
||||
}
|
||||
},
|
||||
|
||||
resetAutoplay() {
|
||||
// Alle Timer stoppen
|
||||
this.stopAutoplay();
|
||||
|
||||
// Nach 5 Sekunden Pause wieder starten
|
||||
this.autoplayTimeout = setTimeout(() => {
|
||||
this.startAutoplay();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{{-- Punkte unter dem Rahmen --}}
|
||||
@if (count($slides) > 1)
|
||||
<div class="mt-6 flex items-center justify-center gap-3" role="tablist" aria-label="{{ __('ui.hero_slider.dots') }}">
|
||||
@foreach ($slides as $index => $slide)
|
||||
<button type="button" role="tab" wire:key="hero-dot-{{ $index }}" x-on:click="go({{ $index }})"
|
||||
x-bind:aria-selected="(active === {{ $index }}).toString()"
|
||||
x-bind:class="active === {{ $index }} ? 'bg-primary w-14' : 'bg-border w-10 hover:bg-muted-foreground/40'"
|
||||
class="h-1 cursor-pointer rounded-full transition-all duration-500 {{ $index === 0 ? 'bg-primary w-14' : 'bg-border w-10' }}"
|
||||
aria-label="{{ __('ui.hero_slider.dot', ['n' => $index + 1, 'total' => count($slides), 'label' => $slide['label']]) }}">
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -63,7 +63,32 @@
|
|||
<p class="text-sm text-muted-foreground leading-relaxed mt-2">{{ $content['intro'] }}</p>
|
||||
@endif
|
||||
|
||||
<form wire:submit="submit" class="space-y-4 mt-6">
|
||||
{{-- WhatsApp zuerst (Abstimmung 23.09.): der schnellste Weg steht oben, das Formular
|
||||
darunter. Nur sichtbar, wenn im CMS eine Nummer hinterlegt ist. Der Link ist ein
|
||||
normaler Klick-zum-Chat-Link – auf dem Handy öffnet die installierte App, sonst
|
||||
WhatsApp Web. Der Text ist aus dem Formularstand vorbereitet, abgeschickt wird er
|
||||
vom Besucher selbst. --}}
|
||||
@if ($this->whatsappUrl !== '')
|
||||
<div class="mt-5" data-appointment-whatsapp>
|
||||
<p class="text-sm text-muted-foreground">{{ __('ui.appointment.whatsapp_intro') }}</p>
|
||||
<a x-bind:href="whatsappHref" target="_blank" rel="noopener"
|
||||
class="mt-3 flex w-full items-center justify-center gap-2 rounded-lg bg-[#25D366] px-6 py-3 font-medium text-white transition-transform hover:scale-[1.02]">
|
||||
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.372-.025-.521-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51l-.57-.01c-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.872.118.571-.085 1.758-.719 2.006-1.413.247-.694.247-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884a9.82 9.82 0 016.988 2.896 9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.885-9.885 9.885M20.52 3.449C18.24 1.245 15.24 0 12.045 0 5.463 0 .104 5.359.101 11.945c0 2.096.549 4.142 1.595 5.945L0 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.582 0 11.941-5.359 11.944-11.945a11.87 11.87 0 00-3.417-8.4" />
|
||||
</svg>
|
||||
{{ __('ui.whatsapp') }}
|
||||
</a>
|
||||
<p class="mt-2 text-xs text-muted-foreground">{{ __('ui.appointment.whatsapp_note') }}</p>
|
||||
|
||||
<div class="mt-6 flex items-center gap-3 text-xs uppercase tracking-wide text-muted-foreground" aria-hidden="true">
|
||||
<span class="h-px flex-1 bg-border"></span>
|
||||
<span>{{ __('ui.appointment.or_form') }}</span>
|
||||
<span class="h-px flex-1 bg-border"></span>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form wire:submit="submit" class="space-y-4 mt-5">
|
||||
<div>
|
||||
<label for="appointment-name" class="text-foreground mb-2 block text-sm font-medium">
|
||||
{{ __('ui.appointment.name') }} <span aria-hidden="true">*</span>
|
||||
|
|
@ -150,23 +175,6 @@
|
|||
</button>
|
||||
</form>
|
||||
|
||||
{{-- Alternative: WhatsApp. Nur sichtbar, wenn im CMS eine Nummer hinterlegt ist.
|
||||
Der Link ist ein normaler Klick-zum-Chat-Link – auf dem Handy öffnet die
|
||||
installierte App, sonst WhatsApp Web. Der Text ist vorbereitet, abgeschickt
|
||||
wird er vom Besucher selbst. --}}
|
||||
@if ($this->whatsappUrl !== '')
|
||||
<div class="mt-6 pt-6 border-t border-border/60">
|
||||
<p class="text-sm text-muted-foreground">{{ __('ui.appointment.whatsapp_intro') }}</p>
|
||||
<a x-bind:href="whatsappHref" target="_blank" rel="noopener"
|
||||
class="mt-3 flex w-full items-center justify-center gap-2 rounded-lg bg-[#25D366] px-6 py-3 font-medium text-white transition-transform hover:scale-[1.02]">
|
||||
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.372-.025-.521-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51l-.57-.01c-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.872.118.571-.085 1.758-.719 2.006-1.413.247-.694.247-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884a9.82 9.82 0 016.988 2.896 9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.885-9.885 9.885M20.52 3.449C18.24 1.245 15.24 0 12.045 0 5.463 0 .104 5.359.101 11.945c0 2.096.549 4.142 1.595 5.945L0 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.582 0 11.941-5.359 11.944-11.945a11.87 11.87 0 00-3.417-8.4" />
|
||||
</svg>
|
||||
{{ __('ui.whatsapp') }}
|
||||
</a>
|
||||
<p class="mt-2 text-xs text-muted-foreground">{{ __('ui.appointment.whatsapp_note') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
|
||||
|
|
@ -174,7 +182,7 @@
|
|||
<button type="button" x-on:click="open = !open" x-bind:aria-expanded="open.toString()"
|
||||
aria-controls="appointment-widget-title"
|
||||
class="btn-secondary-accent flex items-center gap-2 rounded-full px-5 py-3 shadow-lg cursor-pointer
|
||||
max-w-[calc(100vw-2rem)] text-left leading-tight">
|
||||
max-w-[calc(100vw-2rem)] text-left leading-tight appointment-toggle">
|
||||
<svg class="h-5 w-5 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"
|
||||
aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{{-- 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()).
|
||||
In der Farbvariante „Modern“ liegt auch der Interior-Footer dunkel und zeigt das negative Logo.
|
||||
pb-28 auf kleinen Schirmen: darunter schwebt das Terminanfrage-Widget, das sonst die
|
||||
letzten Zeilen des Footers verdecken wuerde. --}}
|
||||
<footer id="footer" class="section-footer section-padding pb-28 sm:pb-16">
|
||||
|
|
@ -9,9 +10,16 @@
|
|||
<div class="spacing-section">
|
||||
<div class="flex items-center justify-center">
|
||||
<img src="{{ asset($logoPath) }}"
|
||||
alt="{{ $domainName ?? 'B2in Interior' }} Logo" class="h-16 w-auto"
|
||||
alt="{{ $domainName ?? 'B2in Interior' }} Logo" class="footer-logo-light h-16 w-auto"
|
||||
width="81" height="64"
|
||||
loading="lazy" />
|
||||
@if ($logoPathDark !== '')
|
||||
{{-- Farbvariante „Modern“: dunkler Footer, negatives Logo (Umschaltung per CSS). --}}
|
||||
<img src="{{ asset($logoPathDark) }}"
|
||||
alt="{{ $domainName ?? 'B2in Interior' }} Logo" class="footer-logo-dark h-16 w-auto"
|
||||
width="81" height="64"
|
||||
loading="lazy" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="container-narrow spacing-content">
|
||||
|
|
|
|||
33
resources/views/web/home-draft.blade.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{{-- Entwurf der Startseite in der Farbwelt vom 24.09.2026 (Nachtrag 19). Nur zur Abstimmung:
|
||||
Palette „greige“ am Body, Gold-Logo, neuer Hero mit Slideshow, keine Ankündigungsleiste.
|
||||
Der Rest der Seite ist die reguläre Startseite – die Sektionen färben sich über die Tokens. --}}
|
||||
@extends('web.layouts.web-master')
|
||||
|
||||
@section('title', 'Entwurf – B2in Interior')
|
||||
@section('meta_description', 'Entwurf der Startseite in der neuen Farbwelt – nur zur Abstimmung.')
|
||||
@section('announcement_bar', 'off')
|
||||
|
||||
@push('meta')
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="bg-background">
|
||||
<livewire:web.components.ui.header />
|
||||
|
||||
<main class="variante-glass-flow">
|
||||
<livewire:web.components.sections.hero-slider />
|
||||
<livewire:web.components.sections.founder-bar />
|
||||
<livewire:web.components.sections.audience-switch />
|
||||
<livewire:web.components.sections.core-brands bg="bg-accent" />
|
||||
<livewire:web.components.sections.origin-section />
|
||||
<livewire:web.components.sections.vision-section />
|
||||
<livewire:web.components.sections.sponsoring bg="bg-accent" />
|
||||
<livewire:web.components.sections.display-feed />
|
||||
<livewire:web.components.sections.immobilien-teaser />
|
||||
<livewire:web.components.sections.c-t-a-section />
|
||||
</main>
|
||||
|
||||
<livewire:web.components.ui.footer />
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -1,5 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
@php
|
||||
// Farbpalette der Interior-Welt (Nachtrag 18): ohne Schalter setzt der Server die Klasse aus
|
||||
// config('app.palette'); mit Schalter entscheidet das Skript im <head> anhand des localStorage.
|
||||
$paletteFixed = config('app.theme', 'b2in') === 'b2in'
|
||||
&& b2in_world() === 'interior'
|
||||
&& ! config('app.palette_switch')
|
||||
&& b2in_palette() === ''
|
||||
&& config('app.palette') === 'modern';
|
||||
@endphp
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}"{!! $paletteFixed ? ' class="palette-modern"' : '' !!}>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
|
@ -51,8 +60,39 @@
|
|||
|
||||
@php
|
||||
$theme = config('app.theme', 'b2in');
|
||||
// Farb-Schalter nur auf den Interior-Seiten von b2in, solange die Palette abgestimmt wird.
|
||||
$showPaletteSwitch = $theme === 'b2in' && b2in_world() === 'interior' && config('app.palette_switch');
|
||||
// Entwurf /entwurf (Nachtrag 19): feste Palette „greige“ am Body, kein Browser-Schalter.
|
||||
$paletteDraft = b2in_palette() === 'greige';
|
||||
$hideAnnouncementBar = trim($__env->yieldContent('announcement_bar', 'on')) === 'off';
|
||||
@endphp
|
||||
|
||||
@if ($showPaletteSwitch && ! $paletteDraft)
|
||||
{{-- Läuft vor dem Stylesheet: setzt die gemerkte Palette, bevor die Seite gezeichnet wird.
|
||||
?palette=modern|classic in der URL überschreibt und merkt sich die Wahl; ohne
|
||||
gemerkte Wahl gilt config('app.palette'). --}}
|
||||
<script>
|
||||
(function () {
|
||||
var fromUrl = new URLSearchParams(window.location.search).get('palette');
|
||||
var palette = null;
|
||||
try {
|
||||
if (fromUrl === 'modern' || fromUrl === 'classic') {
|
||||
localStorage.setItem('b2in-palette', fromUrl);
|
||||
}
|
||||
palette = localStorage.getItem('b2in-palette');
|
||||
} catch (e) {
|
||||
palette = fromUrl;
|
||||
}
|
||||
if (palette === null) {
|
||||
palette = @js(config('app.palette'));
|
||||
}
|
||||
if (palette === 'modern') {
|
||||
document.documentElement.classList.add('palette-modern');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@endif
|
||||
|
||||
@vite([
|
||||
\App\Helpers\ThemeHelper::getThemeCssPath(),
|
||||
'resources/js/app.js'
|
||||
|
|
@ -113,13 +153,15 @@
|
|||
@stack('styles')
|
||||
</head>
|
||||
|
||||
<body class="antialiased bg-background text-foreground world-{{ b2in_world() }}">
|
||||
<body class="antialiased bg-background text-foreground world-{{ b2in_world() }}{{ $paletteDraft ? ' palette-greige' : '' }}">
|
||||
|
||||
{{-- GTM noscript (nur bei GTM-Nutzung) --}}
|
||||
<x-cookie-consent::gtm-noscript />
|
||||
|
||||
<!-- Announcement Bar -->
|
||||
<livewire:web.components.ui.announcement-bar />
|
||||
{{-- Ankündigungsleiste – eine Seite kann sie mit @section('announcement_bar', 'off') abschalten --}}
|
||||
@unless ($hideAnnouncementBar)
|
||||
<livewire:web.components.ui.announcement-bar />
|
||||
@endunless
|
||||
|
||||
{{-- TopBar (Backup: Sprachwechsel & Social Icons – folgt später) --}}
|
||||
{{-- <livewire:web.components.ui.top-bar /> --}}
|
||||
|
|
@ -129,6 +171,10 @@
|
|||
{{-- Terminanfrage: schwebend unten rechts, auf allen Seiten --}}
|
||||
<livewire:web.components.ui.appointment-widget />
|
||||
|
||||
@if ($showPaletteSwitch)
|
||||
<x-palette-switch :draft="$paletteDraft" />
|
||||
@endif
|
||||
|
||||
{{-- Cookie Consent Manager (vor Livewire-Scripts) --}}
|
||||
<x-cookie-consent::manager />
|
||||
|
||||
|
|
|
|||