Compare commits
3 commits
1242d4bce8
...
3211569fee
| Author | SHA1 | Date | |
|---|---|---|---|
| 3211569fee | |||
| 90aa079cf7 | |||
| 7953a8c9a1 |
4
.gitignore
vendored
|
|
@ -60,3 +60,7 @@ _storage/
|
|||
dev/immobilien 07-05-2026/AZIZI SharePoint
|
||||
# Deployment-Archive werden bei Bedarf neu erzeugt
|
||||
dev/**/deploy/*.tar.gz
|
||||
|
||||
# Referenzmaterial und Projektdokumente ausserhalb der Versionierung
|
||||
/_b2in_interior_shop/
|
||||
/docs/
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ Domains are configured in `config/domains.php` with environment variables:
|
|||
|
||||
Domain-specific configurations include:
|
||||
- **portal.b2in.test**: Admin backend with Flux UI theme
|
||||
- **b2in.test**: Anthracite/Dynamic Blue color scheme, Inter/IBM Plex Sans fonts
|
||||
- **b2in.test**: two color worlds on one theme – Interior (default: Tanne `#202c2b`, Creme `#f7f4ef`, Sand `#d9cbbf`, Ocker `#d39e46`, Clay `#a38f82`, Anthrazit `#2d2926`; Inter/EB Garamond) and Immobilien (`body.world-immobilien`: Anthracite/Dynamic Blue, Inter/IBM Plex Sans). The world comes from `b2in_world()` (app/helpers.php), the logo per world from `ThemeHelper::getLogoPath()`
|
||||
- **b2a.test**: Azur Blue/Liberty Red color scheme, Inter/Merriweather fonts
|
||||
- **stileigentum.test**: Style Blue/Style Sun color scheme, Inter/Ephesis fonts
|
||||
- **style2own.test**: Imperial Blue/Sand Gold color scheme, Inter/EB Garamond fonts
|
||||
|
|
|
|||
|
|
@ -5,12 +5,20 @@ namespace App\Helpers;
|
|||
class ThemeHelper
|
||||
{
|
||||
/**
|
||||
* Get the logo path based on the current theme
|
||||
* Logo-Pfad für das aktuelle Theme.
|
||||
*
|
||||
* Für b2in hängt die Datei zusätzlich an der Farbwelt: die Interior-Welt trägt das Logo
|
||||
* mit dem Zusatz „Interior“ und ockerfarbenem Bogen, die Immobilien-Welt das Logo ohne
|
||||
* Zusatz mit blauem Bogen. Ohne Angabe wird die Welt aus der aktuellen Route gelesen.
|
||||
*/
|
||||
public static function getLogoPath(string $type = 'positive'): string
|
||||
public static function getLogoPath(string $type = 'positive', ?string $world = null): string
|
||||
{
|
||||
$theme = config('app.theme', 'b2in');
|
||||
|
||||
if ($theme === 'b2in' && ($world ?? b2in_world()) === 'immobilien') {
|
||||
return self::getLogoPathForBrand('b2in-immobilien', $type);
|
||||
}
|
||||
|
||||
return self::getLogoPathForBrand($theme, $type);
|
||||
}
|
||||
|
||||
|
|
@ -26,6 +34,10 @@ class ThemeHelper
|
|||
'positive' => 'img/logos/b2in-interior-logo-positive.svg',
|
||||
'negative' => 'img/logos/b2in-interior-logo-negative.svg',
|
||||
],
|
||||
'b2in-immobilien' => [
|
||||
'positive' => 'img/logos/b2in-logo-positive.svg',
|
||||
'negative' => 'img/logos/b2in-logo-negative.svg',
|
||||
],
|
||||
'b2a' => [
|
||||
'positive' => 'img/logos/b2a-logo-positiv.svg',
|
||||
'negative' => 'img/logos/b2a-logo-negativ.svg',
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ class DisplayVersionEditor extends Component
|
|||
|
||||
public string $mediaSubline = '';
|
||||
|
||||
public int $mediaDuration = 10;
|
||||
/** Anzeigedauer eines Bildes in Sekunden als Eingabetext, Komma oder Punkt erlaubt („1,5“). */
|
||||
public string $mediaDuration = '10';
|
||||
|
||||
public bool $mediaIsActive = true;
|
||||
|
||||
|
|
@ -247,6 +248,12 @@ class DisplayVersionEditor extends Component
|
|||
|
||||
public function saveItem(): void
|
||||
{
|
||||
if ($this->itemType === 'media' && $this->mediaType === 'image' && self::parseDuration($this->mediaDuration) === null) {
|
||||
$this->addError('mediaDuration', 'Bitte eine Dauer zwischen 0,5 und 120 Sekunden angeben, z. B. 1,5.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$content = $this->buildItemContent();
|
||||
$isActive = $this->getActiveFlag();
|
||||
|
||||
|
|
@ -279,6 +286,41 @@ class DisplayVersionEditor extends Component
|
|||
$this->refreshModulePreview();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sekunden aus der Eingabe lesen: „1,5“, „1.5“ und „10“ sind gültig, gerundet auf Zehntel.
|
||||
* Erlaubt sind 0,5 bis 120 Sekunden; alles andere ergibt null.
|
||||
*/
|
||||
public static function parseDuration(string|int|float $input): ?float
|
||||
{
|
||||
$normalized = str_replace(',', '.', trim((string) $input));
|
||||
|
||||
if ($normalized === '' || ! is_numeric($normalized)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$seconds = round((float) $normalized, 1);
|
||||
|
||||
if ($seconds < 0.5 || $seconds > 120) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sekunden für die Anzeige: ganze Zahlen ohne Nachkommastelle, sonst mit Komma („1,5“).
|
||||
*/
|
||||
public static function formatDuration(int|float|string|null $seconds): string
|
||||
{
|
||||
$seconds = (float) str_replace(',', '.', (string) ($seconds ?? 10));
|
||||
|
||||
if (floor($seconds) === $seconds) {
|
||||
return (string) (int) $seconds;
|
||||
}
|
||||
|
||||
return str_replace('.', ',', (string) round($seconds, 1));
|
||||
}
|
||||
|
||||
public function deleteItem(int $id): void
|
||||
{
|
||||
DisplayVersionItem::findOrFail($id)->delete();
|
||||
|
|
@ -437,7 +479,7 @@ class DisplayVersionEditor extends Component
|
|||
$this->mediaUrl = $content['media_url'] ?? '';
|
||||
$this->mediaHeadline = $content['headline'] ?? '';
|
||||
$this->mediaSubline = $content['subline'] ?? '';
|
||||
$this->mediaDuration = $content['duration_seconds'] ?? 10;
|
||||
$this->mediaDuration = self::formatDuration($content['duration_seconds'] ?? 10);
|
||||
$this->mediaIsActive = $isActive;
|
||||
}
|
||||
|
||||
|
|
@ -501,7 +543,7 @@ class DisplayVersionEditor extends Component
|
|||
'media_url' => $this->mediaUrl,
|
||||
'headline' => $this->mediaHeadline,
|
||||
'subline' => $this->mediaSubline,
|
||||
'duration_seconds' => $this->mediaDuration,
|
||||
'duration_seconds' => self::parseDuration($this->mediaDuration) ?? 10,
|
||||
],
|
||||
'slide' => [
|
||||
'type' => 'detail',
|
||||
|
|
@ -663,7 +705,7 @@ class DisplayVersionEditor extends Component
|
|||
$this->mediaUrl = '';
|
||||
$this->mediaHeadline = '';
|
||||
$this->mediaSubline = '';
|
||||
$this->mediaDuration = 10;
|
||||
$this->mediaDuration = '10';
|
||||
$this->mediaIsActive = true;
|
||||
$this->slideShowLogo = true;
|
||||
$this->slideLogoUrl = '';
|
||||
|
|
|
|||
|
|
@ -8,9 +8,13 @@ class AboutHero extends Component
|
|||
{
|
||||
public $content = [];
|
||||
|
||||
public function mount()
|
||||
/** Sektionsschluessel – die Immobilien-Welt nutzt eigene Inhalte (immobilien_about_hero). */
|
||||
public string $section = 'about_hero';
|
||||
|
||||
public function mount(string $section = 'about_hero')
|
||||
{
|
||||
$this->content = cms_theme_section('about_hero');
|
||||
$this->section = $section;
|
||||
$this->content = cms_theme_section($this->section);
|
||||
}
|
||||
|
||||
public function render()
|
||||
|
|
|
|||
|
|
@ -12,8 +12,13 @@ class CTASection extends Component
|
|||
|
||||
public $section = '';
|
||||
|
||||
public function mount($bg = 'bg-secondary', $section = 'cta_section')
|
||||
public function mount($bg = 'cta-band', $section = 'cta_section')
|
||||
{
|
||||
// Der Standard-Abschluss hat je Welt eigene Texte und fuehrt zur Kontaktseite der Welt.
|
||||
if ($section === 'cta_section' && b2in_world() === 'immobilien') {
|
||||
$section = 'cta_section_immobilien';
|
||||
}
|
||||
|
||||
$this->section = $section;
|
||||
$this->content = cms_theme_section($this->section);
|
||||
$this->bg = $bg;
|
||||
|
|
|
|||
|
|
@ -15,14 +15,25 @@ class MagazinDetail extends Component
|
|||
|
||||
public $content;
|
||||
|
||||
/** Rueckweg zur Magazinliste der Welt, zu der der Beitrag gehoert. */
|
||||
public string $listUrl = '';
|
||||
|
||||
public function mount($slug)
|
||||
{
|
||||
$this->slug = (string) $slug;
|
||||
$this->loadArticle();
|
||||
$this->listUrl = $this->articleWorld() === CmsArticle::WORLD_IMMOBILIEN
|
||||
? route('immobilien.magazin')
|
||||
: route('magazin');
|
||||
$this->loadRelatedArticles();
|
||||
$this->loadThemeContent();
|
||||
}
|
||||
|
||||
private function articleWorld(): string
|
||||
{
|
||||
return (string) ($this->article['world'] ?? CmsArticle::WORLD_INTERIOR);
|
||||
}
|
||||
|
||||
private function loadArticle()
|
||||
{
|
||||
$articles = $this->getArticlesData();
|
||||
|
|
@ -37,6 +48,7 @@ class MagazinDetail extends Component
|
|||
private function loadRelatedArticles()
|
||||
{
|
||||
$this->relatedArticles = collect($this->getArticlesData())
|
||||
->filter(fn (array $article) => ($article['world'] ?? CmsArticle::WORLD_INTERIOR) === $this->articleWorld())
|
||||
->reject(fn (array $article, string $slug) => $slug === $this->slug)
|
||||
->take(2)
|
||||
->values()
|
||||
|
|
|
|||
|
|
@ -11,15 +11,19 @@ class MagazinList extends Component
|
|||
|
||||
public $content = [];
|
||||
|
||||
public function mount()
|
||||
/** Farbwelt der Liste: `interior` (/magazin) oder `immobilien` (/immobilien/magazin). */
|
||||
public string $world = CmsArticle::WORLD_INTERIOR;
|
||||
|
||||
public function mount(string $world = CmsArticle::WORLD_INTERIOR)
|
||||
{
|
||||
$this->world = in_array($world, CmsArticle::WORLDS, true) ? $world : CmsArticle::WORLD_INTERIOR;
|
||||
$this->loadPosts();
|
||||
$this->loadThemeContent();
|
||||
}
|
||||
|
||||
private function loadPosts()
|
||||
{
|
||||
$this->posts = collect(CmsArticle::forFrontend())->map(function (array $article) {
|
||||
$this->posts = collect(CmsArticle::forFrontend($this->world))->map(function (array $article) {
|
||||
return [
|
||||
'slug' => $article['slug'],
|
||||
'title' => $article['title'],
|
||||
|
|
@ -33,7 +37,9 @@ class MagazinList extends Component
|
|||
|
||||
private function loadThemeContent()
|
||||
{
|
||||
$this->content = cms_theme_section('magazin_list');
|
||||
$this->content = cms_theme_section(
|
||||
$this->world === CmsArticle::WORLD_IMMOBILIEN ? 'magazin_list_immobilien' : 'magazin_list'
|
||||
);
|
||||
}
|
||||
|
||||
public function render()
|
||||
|
|
|
|||
83
app/Livewire/Web/Components/Sections/OriginSection.php
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire\Web\Components\Sections;
|
||||
|
||||
use App\Models\CmsBrand;
|
||||
use Livewire\Component;
|
||||
|
||||
/**
|
||||
* „Aus der Region gefertigt“: die Standorte der Kernmarken rund um Bielefeld
|
||||
* (Arbeitsplan 14.09.2026, Punkt 1.3, Nachtrag 11).
|
||||
*
|
||||
* Überschrift, Karte, Drehscheibe und Ablauf-Block kommen aus dem CMS (Fallback Lang-Datei).
|
||||
* Die Standorte kommen aus den veröffentlichten Marken: Ort, Entfernung und Rolle pflegt
|
||||
* Marcel in der Markenverwaltung. Die Positionen in der schematischen Karte gehören zur
|
||||
* Zeichnung und bleiben hier im Code.
|
||||
*/
|
||||
class OriginSection extends Component
|
||||
{
|
||||
/**
|
||||
* Position je Marke in der Karte (viewBox 640×400) und Seite der Beschriftung.
|
||||
*
|
||||
* Eine Marke ohne Eintrag erscheint in den Kacheln, aber nicht in der Karte.
|
||||
*
|
||||
* @var array<string, array{x: int, y: int, label: string}>
|
||||
*/
|
||||
public const MAP_POSITIONS = [
|
||||
'rotpunkt' => ['x' => 330, 'y' => 55, 'label' => 'left'],
|
||||
'februe' => ['x' => 480, 'y' => 90, 'label' => 'right'],
|
||||
'tischlerei' => ['x' => 295, 'y' => 285, 'label' => 'left'],
|
||||
'cabinet' => ['x' => 80, 'y' => 345, 'label' => 'right'],
|
||||
];
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
public array $content = [];
|
||||
|
||||
public string $bg = '';
|
||||
|
||||
public function mount(string $bg = ''): void
|
||||
{
|
||||
$section = cms_theme_section('origin_section');
|
||||
$this->content = is_array($section) ? $section : [];
|
||||
$this->bg = $bg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Veröffentlichte Marken mit gepflegtem Fertigungsort, in der Reihenfolge der Marken.
|
||||
*
|
||||
* Eine unveröffentlichte Marke fällt heraus, eine Marke ohne Ort ebenfalls. Der Link zur
|
||||
* Markenseite hängt daran, ob die Marke eine Detailseite hat (Lang-Fallback: keine).
|
||||
*
|
||||
* @return list<array{slug: string, city: string, brand: string, role: string, distance: string, link: ?string, map: ?array{x: int, y: int, label: string}}>
|
||||
*/
|
||||
public function getLocationsProperty(): array
|
||||
{
|
||||
$out = [];
|
||||
|
||||
foreach (CmsBrand::forFrontend() as $slug => $brand) {
|
||||
$city = trim((string) ($brand['origin_city'] ?? ''));
|
||||
$name = trim((string) ($brand['name'] ?? ''));
|
||||
|
||||
if ($city === '' || $name === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$out[] = [
|
||||
'slug' => (string) $slug,
|
||||
'city' => $city,
|
||||
'brand' => $name,
|
||||
'role' => trim((string) ($brand['origin_role'] ?? '')),
|
||||
'distance' => trim((string) ($brand['origin_distance'] ?? '')),
|
||||
'link' => ! empty($brand['has_detail_page']) ? '/marken/'.$slug : null,
|
||||
'map' => self::MAP_POSITIONS[$slug] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.web.components.sections.origin-section');
|
||||
}
|
||||
}
|
||||
|
|
@ -8,9 +8,13 @@ class OurStory extends Component
|
|||
{
|
||||
public $content = [];
|
||||
|
||||
public function mount()
|
||||
/** Sektionsschluessel – die Immobilien-Welt nutzt eigene Inhalte (immobilien_our_story). */
|
||||
public string $section = 'our_story';
|
||||
|
||||
public function mount(string $section = 'our_story')
|
||||
{
|
||||
$this->content = cms_theme_section('our_story');
|
||||
$this->section = $section;
|
||||
$this->content = cms_theme_section($this->section);
|
||||
}
|
||||
|
||||
public function render()
|
||||
|
|
|
|||
|
|
@ -8,9 +8,13 @@ class OurValues extends Component
|
|||
{
|
||||
public $content = [];
|
||||
|
||||
public function mount()
|
||||
/** Sektionsschluessel – die Immobilien-Welt nutzt eigene Inhalte (immobilien_our_values). */
|
||||
public string $section = 'our_values';
|
||||
|
||||
public function mount(string $section = 'our_values')
|
||||
{
|
||||
$this->content = cms_theme_section('our_values');
|
||||
$this->section = $section;
|
||||
$this->content = cms_theme_section($this->section);
|
||||
}
|
||||
|
||||
public function render()
|
||||
|
|
|
|||
|
|
@ -3,14 +3,16 @@
|
|||
namespace App\Livewire\Web\Components\Sections;
|
||||
|
||||
use App\Models\CmsBrand;
|
||||
use App\Models\CmsReference;
|
||||
use Livewire\Component;
|
||||
|
||||
/**
|
||||
* Realisierte Projekte aus den Markengalerien, über alle Marken hinweg gesammelt.
|
||||
* Realisierte Projekte auf /referenzen.
|
||||
*
|
||||
* Keine zweite Pflegestelle: Marcel pflegt die Bilder weiterhin je Marke im Kernmarken-Modul.
|
||||
* Hier erscheinen ausschließlich Einträge der Rubrik „Realisiertes Projekt" – Visualisierungen
|
||||
* und Entwürfe bleiben bewusst draußen, damit „Referenzen" hält, was das Wort verspricht.
|
||||
* Seit Nachtrag 13 pflegt Marcel Referenzen als eigene Einträge im Portal (Titelbild, weitere
|
||||
* Bilder, beteiligte Marken). Solange dort nichts veröffentlicht ist, greift der Stand aus AP5:
|
||||
* die als „Realisiertes Projekt“ markierten Bilder aus den Markengalerien, über alle Marken
|
||||
* hinweg gesammelt. Visualisierungen und Entwürfe bleiben in beiden Fällen draußen.
|
||||
*/
|
||||
class ReferenceProjects extends Component
|
||||
{
|
||||
|
|
@ -30,13 +32,56 @@ class ReferenceProjects extends Component
|
|||
$this->content = is_array($section) ? $section : [];
|
||||
$this->bg = $bg;
|
||||
$this->limit = $limit;
|
||||
$this->projects = $this->collectProjects();
|
||||
|
||||
$projects = $this->fromReferences();
|
||||
|
||||
if ($projects === []) {
|
||||
$projects = $this->fromBrandGalleries();
|
||||
}
|
||||
|
||||
$this->projects = $this->limit > 0 ? array_slice($projects, 0, $this->limit) : $projects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function collectProjects(): array
|
||||
private function fromReferences(): array
|
||||
{
|
||||
$references = CmsReference::query()->published()->ordered()->get();
|
||||
|
||||
if ($references->isEmpty()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$brands = CmsBrand::forFrontend();
|
||||
$projects = [];
|
||||
|
||||
foreach ($references as $reference) {
|
||||
$gallery = CmsReference::normalizeGallery($reference->gallery);
|
||||
$image = (string) ($reference->image ?: ($gallery[0]['image'] ?? ''));
|
||||
|
||||
if ($image === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$projects[] = [
|
||||
'key' => 'ref-'.$reference->id,
|
||||
'image' => $image,
|
||||
'caption' => (string) $reference->title,
|
||||
'meta' => implode(' · ', array_filter([$reference->location, $reference->year])),
|
||||
'description' => (string) $reference->description,
|
||||
'gallery' => array_values(array_filter($gallery, fn (array $item) => $item['image'] !== $image)),
|
||||
'brands' => $reference->resolveBrands($brands),
|
||||
];
|
||||
}
|
||||
|
||||
return $projects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function fromBrandGalleries(): array
|
||||
{
|
||||
$projects = [];
|
||||
|
||||
|
|
@ -46,14 +91,19 @@ class ReferenceProjects extends Component
|
|||
'key' => $slug.'-'.$index,
|
||||
'image' => $item['image'],
|
||||
'caption' => $item['caption'],
|
||||
'brand' => (string) ($brand['name'] ?? ''),
|
||||
'brand_slug' => $slug,
|
||||
'has_detail_page' => (bool) ($brand['has_detail_page'] ?? false),
|
||||
'meta' => '',
|
||||
'description' => '',
|
||||
'gallery' => [],
|
||||
'brands' => [[
|
||||
'slug' => $slug,
|
||||
'name' => (string) ($brand['name'] ?? ''),
|
||||
'link' => ! empty($brand['has_detail_page']) ? '/marken/'.$slug : null,
|
||||
]],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->limit > 0 ? array_slice($projects, 0, $this->limit) : $projects;
|
||||
return $projects;
|
||||
}
|
||||
|
||||
public function render()
|
||||
|
|
|
|||
|
|
@ -8,9 +8,16 @@ class AnnouncementBar extends Component
|
|||
{
|
||||
public array $content = [];
|
||||
|
||||
/** Farbwelt der Seite – jede Welt hat ihre eigene Meldung. */
|
||||
public string $world = 'interior';
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->content = cms_theme_section('announcement_bar');
|
||||
$this->world = b2in_world();
|
||||
|
||||
// Interior: global.announcement_bar, Immobilien: global.announcement_bar_immobilien.
|
||||
$section = cms_theme_section($this->world === 'immobilien' ? 'announcement_bar_immobilien' : 'announcement_bar');
|
||||
$this->content = is_array($section) ? $section : [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -20,9 +20,13 @@ class AppointmentWidget extends Component
|
|||
/** Feste Anliegen neben den gepflegten Marken. */
|
||||
public const EXTRA_TOPICS = [
|
||||
'projektausstattung' => 'Projektausstattung',
|
||||
'immobilien' => 'Immobilien',
|
||||
'sonstiges' => 'Sonstiges',
|
||||
];
|
||||
|
||||
/** Farbwelt der Seite – in der Immobilien-Welt ist das Anliegen vorbelegt. */
|
||||
public string $world = 'interior';
|
||||
|
||||
public string $name = '';
|
||||
|
||||
public string $email = '';
|
||||
|
|
@ -52,6 +56,11 @@ class AppointmentWidget extends Component
|
|||
$section = cms_theme_section('appointment_widget');
|
||||
$this->content = is_array($section) ? $section : [];
|
||||
$this->formLoadedAt = time();
|
||||
$this->world = b2in_world();
|
||||
|
||||
if ($this->world === 'immobilien') {
|
||||
$this->topic = 'immobilien';
|
||||
}
|
||||
|
||||
$this->applyBrandContext();
|
||||
}
|
||||
|
|
@ -134,11 +143,13 @@ class AppointmentWidget extends Component
|
|||
|
||||
$payload['is_spam'] = SpamDetector::fromConfig()->detect($payload, $this->formLoadedAt);
|
||||
|
||||
$subject = 'Terminanfrage B2in Interior'.($topicLabel !== '' ? ': '.$topicLabel : '');
|
||||
$subject = ($this->world === 'immobilien' ? 'Terminanfrage B2in Immobilien' : 'Terminanfrage B2in Interior')
|
||||
.($topicLabel !== '' ? ': '.$topicLabel : '');
|
||||
$service->handle($payload, $subject, 'appointment-widget');
|
||||
|
||||
$this->success = true;
|
||||
$this->reset(['name', 'email', 'phone', 'topic', 'preferredDate', 'message', 'privacy', 'website']);
|
||||
$this->topic = $this->world === 'immobilien' ? 'immobilien' : '';
|
||||
$this->formLoadedAt = time();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ namespace App\Livewire\Web\Components\Ui;
|
|||
|
||||
use Acme\ContactForm\ContactFormService;
|
||||
use Acme\ContactForm\SpamDetector;
|
||||
use App\Models\CmsBrand;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Livewire\Component;
|
||||
|
||||
class ContactForm extends Component
|
||||
|
|
@ -34,16 +36,109 @@ class ContactForm extends Component
|
|||
/** @var array<string, mixed> */
|
||||
public array $content = [];
|
||||
|
||||
public function mount(): void
|
||||
/** Farbwelt der Seite (`interior` | `immobilien`). */
|
||||
public string $world = 'interior';
|
||||
|
||||
public function mount(string $world = 'interior'): void
|
||||
{
|
||||
$this->content = cms_theme_section('contact_form');
|
||||
$this->world = $world === 'immobilien' ? 'immobilien' : 'interior';
|
||||
$this->content = $this->loadContent();
|
||||
$this->formLoadedAt = time();
|
||||
}
|
||||
|
||||
/** @return array<string, string> */
|
||||
/**
|
||||
* Inhalte der Welt. Die Immobilien-Welt liegt als Overlay über der Interior-Sektion:
|
||||
* `contact_form_immobilien` trägt nur, was abweicht (Einstieg, Betreffe) – Adresse,
|
||||
* Beschriftungen und Social Media erbt sie von `contact_form`.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function loadContent(): array
|
||||
{
|
||||
$base = cms_theme_section('contact_form');
|
||||
$base = is_array($base) ? $base : [];
|
||||
|
||||
if ($this->world !== 'immobilien') {
|
||||
return $base;
|
||||
}
|
||||
|
||||
$overlay = cms_theme_section('contact_form_immobilien');
|
||||
|
||||
if (! is_array($overlay)) {
|
||||
return $base;
|
||||
}
|
||||
|
||||
return array_replace_recursive($base, $overlay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Betreffe in Gruppen: Interior (mit den gepflegten Kernmarken vorneweg), Immobilien, B2in.
|
||||
* Die Welt der Seite bestimmt die Reihenfolge – ihre Gruppe steht oben.
|
||||
*
|
||||
* Ohne `subject_groups` (aeltere CMS-Staende) faellt das Formular auf die flache Liste zurueck.
|
||||
*
|
||||
* @return array<string, array{label: string, subjects: array<string, string>}>
|
||||
*/
|
||||
public function getSubjectGroupsProperty(): array
|
||||
{
|
||||
$groups = $this->content['form']['subject_groups'] ?? null;
|
||||
|
||||
if (! is_array($groups) || $groups === []) {
|
||||
$flat = $this->content['form']['subjects'] ?? [];
|
||||
unset($flat['']);
|
||||
|
||||
return $flat === [] ? [] : ['all' => ['label' => '', 'subjects' => $flat]];
|
||||
}
|
||||
|
||||
$brands = [];
|
||||
|
||||
foreach (CmsBrand::forFrontend() as $slug => $brand) {
|
||||
$brands[$slug] = (string) ($brand['name'] ?? $slug);
|
||||
}
|
||||
|
||||
$out = [];
|
||||
|
||||
foreach ($groups as $key => $group) {
|
||||
$subjects = is_array($group['subjects'] ?? null) ? $group['subjects'] : [];
|
||||
|
||||
if ($key === 'interior') {
|
||||
$subjects = $brands + $subjects;
|
||||
}
|
||||
|
||||
$out[$key] = [
|
||||
'label' => (string) ($group['label'] ?? ''),
|
||||
'subjects' => $subjects,
|
||||
];
|
||||
}
|
||||
|
||||
$first = $this->world === 'immobilien' ? 'immobilien' : 'interior';
|
||||
|
||||
if (isset($out[$first])) {
|
||||
$out = [$first => $out[$first]] + $out;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alle waehlbaren Betreffe als flache Liste (Schluessel → Beschriftung).
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getSubjectsProperty(): array
|
||||
{
|
||||
return $this->content['form']['subjects'] ?? [];
|
||||
$flat = [];
|
||||
|
||||
foreach ($this->getSubjectGroupsProperty() as $group) {
|
||||
$flat += $group['subjects'];
|
||||
}
|
||||
|
||||
return $flat;
|
||||
}
|
||||
|
||||
public function getSubjectPlaceholderProperty(): string
|
||||
{
|
||||
return (string) ($this->content['form']['subjects'][''] ?? $this->content['form']['subject_placeholder'] ?? __('ui.contact_form.subject_placeholder'));
|
||||
}
|
||||
|
||||
/** @return array<int, array<string, mixed>> */
|
||||
|
|
@ -64,7 +159,7 @@ class ContactForm extends Component
|
|||
'firstName' => ['required', 'string', 'max:255'],
|
||||
'lastName' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'email:rfc', 'max:255'],
|
||||
'subject' => ['required', 'string'],
|
||||
'subject' => ['required', 'string', Rule::in(array_keys($this->subjects))],
|
||||
'message' => ['required', 'string', 'max:2000'],
|
||||
'company' => ['nullable', 'string', 'max:255'],
|
||||
'phone' => ['nullable', 'string', 'max:255'],
|
||||
|
|
@ -90,8 +185,9 @@ class ContactForm extends Component
|
|||
$payload['is_spam'] = $spamDetector->detect($payload, $this->formLoadedAt);
|
||||
|
||||
$subjectLabel = $this->getSubjectsProperty()[$this->subject] ?? $this->subject;
|
||||
$prefix = $this->world === 'immobilien' ? 'Kontaktanfrage B2in Immobilien' : 'Kontaktanfrage B2in';
|
||||
$subjectLine = $subjectLabel
|
||||
? 'Kontaktanfrage B2in: ' . $subjectLabel
|
||||
? $prefix.': '.$subjectLabel
|
||||
: __('contact-form::contact-form.default_subject');
|
||||
$service->handle($payload, $subjectLine, 'contact-form');
|
||||
|
||||
|
|
|
|||
|
|
@ -9,10 +9,46 @@ class Footer extends Component
|
|||
/** @var array<string, mixed> */
|
||||
public array $content = [];
|
||||
|
||||
/** Farbwelt der Seite (`interior` | `immobilien`), in mount() festgehalten. */
|
||||
public string $world = 'interior';
|
||||
|
||||
/** @var array<string, array{label: string, url: string, active: bool}> */
|
||||
public array $worlds = [];
|
||||
|
||||
public string $logoPath = '';
|
||||
|
||||
/**
|
||||
* Fuer die Immobilien-Welt zeigen die gemeinsamen Footer-Links auf die Seiten dieser Welt.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private const IMMOBILIEN_LINKS = [
|
||||
'/faq' => '/immobilien/faq',
|
||||
'/about' => '/immobilien/ueber-b2in',
|
||||
'/magazin' => '/immobilien/magazin',
|
||||
];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$section = cms_theme_section('footer');
|
||||
$this->content = is_array($section) ? $section : [];
|
||||
$this->world = b2in_world();
|
||||
|
||||
if ($this->world === 'immobilien' && is_array($this->content['links'] ?? null)) {
|
||||
$this->content['links'] = array_map(function ($link) {
|
||||
if (is_array($link) && isset(self::IMMOBILIEN_LINKS[$link['url'] ?? ''])) {
|
||||
$link['url'] = self::IMMOBILIEN_LINKS[$link['url']];
|
||||
}
|
||||
|
||||
return $link;
|
||||
}, $this->content['links']);
|
||||
}
|
||||
$this->worlds = b2in_worlds($this->world);
|
||||
|
||||
// Der Interior-Footer liegt auf Sand (helle Fläche) und trägt deshalb das positive
|
||||
// Logo; der Immobilien-Footer bleibt dunkel und nutzt die negative Variante.
|
||||
$variant = $this->world === 'immobilien' ? 'negative' : 'positive';
|
||||
$this->logoPath = \App\Helpers\ThemeHelper::getLogoPath($variant, $this->world);
|
||||
}
|
||||
|
||||
public function render()
|
||||
|
|
|
|||
|
|
@ -16,13 +16,33 @@ class Header extends Component
|
|||
'en' => 'EN',
|
||||
];
|
||||
|
||||
/** Farbwelt der Seite (`interior` | `immobilien`), in mount() festgehalten. */
|
||||
public string $world = 'interior';
|
||||
|
||||
/** @var array<string, array{label: string, url: string, active: bool}> */
|
||||
public array $worlds = [];
|
||||
|
||||
public string $logoPath = '';
|
||||
|
||||
/** Pfad der Seite, in mount() festgehalten – ein Livewire-Request traegt ihn nicht mehr. */
|
||||
public string $currentPath = '/';
|
||||
|
||||
/** Ziel des Kontakt-Buttons: /contact (Interior) bzw. /immobilien/kontakt (Immobilien). */
|
||||
public string $contactUrl = '';
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->domainName = \App\Helpers\ThemeHelper::getDomainName();
|
||||
$this->domainUrl = \App\Helpers\ThemeHelper::getDomainUrl();
|
||||
$this->currentLocale = app()->getLocale();
|
||||
$this->world = b2in_world();
|
||||
$this->worlds = b2in_worlds($this->world);
|
||||
$this->logoPath = \App\Helpers\ThemeHelper::getLogoPath('positive', $this->world);
|
||||
$this->currentPath = '/'.ltrim(request()->path(), '/');
|
||||
$this->contactUrl = route($this->world === 'immobilien' ? 'immobilien.contact' : 'contact');
|
||||
|
||||
$this->content = cms_theme_section('header');
|
||||
// Jede Welt hat ihre eigene Navigation (CMS: global.header bzw. global.header_immobilien).
|
||||
$this->content = cms_theme_section($this->world === 'immobilien' ? 'header_immobilien' : 'header');
|
||||
|
||||
// Ensure required keys exist
|
||||
if (!isset($this->content['portal_login'])) {
|
||||
|
|
@ -57,27 +77,43 @@ class Header extends Component
|
|||
{
|
||||
$this->isMobileMenuOpen = false;
|
||||
}
|
||||
public function isActiveRoute($url)
|
||||
/**
|
||||
* Aktiver Navigationspunkt: exakter Treffer, sonst der laengste Eintrag, dessen Pfad ein
|
||||
* Praefix der Seite ist. So markiert /immobilien/magazin nur „Magazin“, nicht auch
|
||||
* „Immobilien“. Anker (#…) zaehlen nicht zum Pfad.
|
||||
*/
|
||||
public function isActiveRoute($url): bool
|
||||
{
|
||||
$currentPath = request()->path();
|
||||
$currentPath = '/' . ltrim($currentPath, '/');
|
||||
$path = $this->navPath((string) $url);
|
||||
|
||||
// Exact match
|
||||
if ($currentPath === $url) {
|
||||
if ($this->currentPath === $path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Special handling for home route
|
||||
if ($url === '/' && ($currentPath === '/' || $currentPath === '')) {
|
||||
return true;
|
||||
if ($path === '/' || ! str_starts_with($this->currentPath, rtrim($path, '/').'/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if current path starts with the navigation URL (for sub-pages)
|
||||
if ($url !== '/' && str_starts_with($currentPath, rtrim($url, '/'))) {
|
||||
return true;
|
||||
foreach ($this->content['navigation'] ?? [] as $navItem) {
|
||||
$other = $this->navPath((string) ($navItem['url'] ?? ''));
|
||||
|
||||
if ($other === $this->currentPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strlen($other) > strlen($path) && str_starts_with($this->currentPath, rtrim($other, '/').'/')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private function navPath(string $url): string
|
||||
{
|
||||
$path = (string) (parse_url($url, PHP_URL_PATH) ?? '/');
|
||||
|
||||
return '/'.ltrim($path, '/');
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -29,12 +29,31 @@ class CmsArticle extends Model
|
|||
5 => 'local-for-local-regionaler-moebelhandel',
|
||||
];
|
||||
|
||||
public const WORLD_INTERIOR = 'interior';
|
||||
|
||||
public const WORLD_IMMOBILIEN = 'immobilien';
|
||||
|
||||
/** @var array<int, string> */
|
||||
public const WORLDS = [self::WORLD_INTERIOR, self::WORLD_IMMOBILIEN];
|
||||
|
||||
/**
|
||||
* Beiträge aus dem Bestand, die zur Immobilien-Welt gehören (Dubai-Themen).
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
public const LEGACY_IMMOBILIEN_SLUGS = [
|
||||
'escrow-system-dubai-investoren',
|
||||
'spotlight-al-jaddaf-hotspot',
|
||||
'turnkey-investments-moeblierung-rendite',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'slug',
|
||||
'title',
|
||||
'subtitle',
|
||||
'image',
|
||||
'category',
|
||||
'world',
|
||||
'date_label',
|
||||
'read_time',
|
||||
'author',
|
||||
|
|
@ -69,6 +88,11 @@ class CmsArticle extends Model
|
|||
return $query->orderBy('order')->orderByDesc('created_at');
|
||||
}
|
||||
|
||||
public function scopeForWorld(Builder $query, string $world): Builder
|
||||
{
|
||||
return $query->where('world', $world);
|
||||
}
|
||||
|
||||
/**
|
||||
* Veröffentlichte Artikel fürs Frontend, nach Slug indiziert.
|
||||
*
|
||||
|
|
@ -76,19 +100,32 @@ class CmsArticle extends Model
|
|||
* dieselbe Reihenfolge wie bei cms_theme_section(), damit das Magazin auf einer frischen
|
||||
* Installation nicht leer ist.
|
||||
*
|
||||
* Mit `$world` nur die Beiträge dieser Farbwelt (Interior oder Immobilien) – die beiden
|
||||
* Magazinlisten sind getrennt, die Detailseite rendert in der Welt ihres Beitrags.
|
||||
*
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
public static function forFrontend(): array
|
||||
public static function forFrontend(?string $world = null): array
|
||||
{
|
||||
$articles = static::query()->published()->ordered()->get();
|
||||
$maintained = static::query()->published()->exists();
|
||||
|
||||
if ($articles->isNotEmpty()) {
|
||||
return $articles
|
||||
if ($maintained) {
|
||||
return static::query()
|
||||
->published()
|
||||
->when($world !== null, fn (Builder $query) => $query->forWorld($world))
|
||||
->ordered()
|
||||
->get()
|
||||
->mapWithKeys(fn (self $article) => [$article->slug => $article->toFrontendArray()])
|
||||
->all();
|
||||
}
|
||||
|
||||
return static::fallbackFromLang();
|
||||
$articles = static::fallbackFromLang();
|
||||
|
||||
if ($world === null) {
|
||||
return $articles;
|
||||
}
|
||||
|
||||
return array_filter($articles, fn (array $article) => ($article['world'] ?? self::WORLD_INTERIOR) === $world);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -143,7 +180,7 @@ class CmsArticle extends Model
|
|||
continue;
|
||||
}
|
||||
|
||||
$out[$slug] = $article + ['slug' => $slug];
|
||||
$out[$slug] = $article + ['slug' => $slug, 'world' => self::WORLD_INTERIOR];
|
||||
}
|
||||
|
||||
return $out;
|
||||
|
|
@ -161,6 +198,7 @@ class CmsArticle extends Model
|
|||
'subtitle' => $this->subtitle,
|
||||
'image' => $this->image,
|
||||
'category' => $this->category,
|
||||
'world' => $this->world ?? self::WORLD_INTERIOR,
|
||||
'date' => $this->date_label,
|
||||
'readTime' => $this->read_time,
|
||||
'author' => $this->author ?? [],
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ class CmsBrand extends Model
|
|||
'gallery',
|
||||
'website_url',
|
||||
'showroom',
|
||||
'origin_city',
|
||||
'origin_distance',
|
||||
'origin_role',
|
||||
'is_published',
|
||||
'order',
|
||||
];
|
||||
|
|
@ -59,6 +62,9 @@ class CmsBrand extends Model
|
|||
'widget_label',
|
||||
'cta_title',
|
||||
'cta_text',
|
||||
'origin_city',
|
||||
'origin_distance',
|
||||
'origin_role',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
|
|
@ -139,6 +145,9 @@ class CmsBrand extends Model
|
|||
'gallery' => [],
|
||||
'website_url' => '',
|
||||
'showroom' => '',
|
||||
'origin_city' => '',
|
||||
'origin_distance' => '',
|
||||
'origin_role' => '',
|
||||
'has_detail_page' => false,
|
||||
], $brand, ['slug' => $slug, 'has_detail_page' => false]);
|
||||
|
||||
|
|
@ -311,6 +320,9 @@ class CmsBrand extends Model
|
|||
'gallery' => self::normalizeGallery($this->gallery),
|
||||
'website_url' => $this->website_url,
|
||||
'showroom' => $this->showroom,
|
||||
'origin_city' => (string) $this->origin_city,
|
||||
'origin_distance' => (string) $this->origin_distance,
|
||||
'origin_role' => (string) $this->origin_role,
|
||||
'has_detail_page' => true,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
115
app/Models/CmsReference.php
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Spatie\Translatable\HasTranslations;
|
||||
|
||||
/**
|
||||
* Referenzprojekt der Interior-Welt: ein realisierter Auftrag mit Titelbild, weiteren Bildern
|
||||
* und den beteiligten Kernmarken (Nachtrag 13). Erscheint auf /referenzen.
|
||||
*
|
||||
* Nicht zu verwechseln mit {@see CmsProject} – das sind die Immobilienprojekte.
|
||||
*/
|
||||
class CmsReference extends Model
|
||||
{
|
||||
use HasFactory, HasTranslations;
|
||||
|
||||
protected $table = 'cms_references';
|
||||
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'location',
|
||||
'year',
|
||||
'description',
|
||||
'image',
|
||||
'gallery',
|
||||
'brand_slugs',
|
||||
'is_published',
|
||||
'order',
|
||||
];
|
||||
|
||||
/** @var array<string> */
|
||||
public array $translatable = ['title', 'description'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'gallery' => 'array',
|
||||
'brand_slugs' => 'array',
|
||||
'is_published' => 'boolean',
|
||||
'order' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
public function scopePublished(Builder $query): Builder
|
||||
{
|
||||
return $query->where('is_published', true);
|
||||
}
|
||||
|
||||
public function scopeOrdered(Builder $query): Builder
|
||||
{
|
||||
return $query->orderBy('order')->orderBy('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Galerie in eine feste Form bringen: nur Einträge mit Bild, Bildunterschrift getrimmt.
|
||||
*
|
||||
* @param mixed $gallery
|
||||
* @return list<array{image: string, caption: string}>
|
||||
*/
|
||||
public static function normalizeGallery($gallery): array
|
||||
{
|
||||
if (! is_array($gallery)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$out = [];
|
||||
|
||||
foreach ($gallery as $item) {
|
||||
$image = trim((string) (is_array($item) ? ($item['image'] ?? '') : $item));
|
||||
|
||||
if ($image === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$out[] = [
|
||||
'image' => $image,
|
||||
'caption' => is_array($item) ? trim((string) ($item['caption'] ?? '')) : '',
|
||||
];
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Beteiligte Marken als Liste mit Name und Link, aufgelöst gegen die veröffentlichten Marken.
|
||||
*
|
||||
* Eine unveröffentlichte oder gelöschte Marke fällt heraus.
|
||||
*
|
||||
* @param array<string, array<string, mixed>> $brands Ergebnis von CmsBrand::forFrontend()
|
||||
* @return list<array{slug: string, name: string, link: ?string}>
|
||||
*/
|
||||
public function resolveBrands(array $brands): array
|
||||
{
|
||||
$out = [];
|
||||
|
||||
foreach ($this->brand_slugs ?? [] as $slug) {
|
||||
$slug = (string) $slug;
|
||||
|
||||
if (! isset($brands[$slug])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$out[] = [
|
||||
'slug' => $slug,
|
||||
'name' => (string) ($brands[$slug]['name'] ?? $slug),
|
||||
'link' => ! empty($brands[$slug]['has_detail_page']) ? '/marken/'.$slug : null,
|
||||
];
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
187
app/Services/CmsPageTree.php
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use FluxCms\Core\Models\CmsContent;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Seitenbaum für den Inhalte-Editor (config/cms_pages.php, Nachtrag 12).
|
||||
*
|
||||
* Verbindet die Registry mit dem Datenbestand: je Sektion, ob sie in flux_cms_contents liegt
|
||||
* (dann mit `content_id`), und was sonst noch in der Datenbank steht, aber in keiner Seite
|
||||
* eingetragen ist („Nicht mehr verwendet“).
|
||||
*/
|
||||
class CmsPageTree
|
||||
{
|
||||
/** Schlüssel des Sammelpunkts für nicht eingetragene Sektionen. */
|
||||
public const UNLISTED = 'unlisted';
|
||||
|
||||
/**
|
||||
* Alle Seiten mit ihren Sektionen in Website-Reihenfolge.
|
||||
*
|
||||
* @return array<string, array{title: string, path: ?string, sections: list<array<string, mixed>>}>
|
||||
*/
|
||||
public function pages(): array
|
||||
{
|
||||
$rows = CmsContent::query()
|
||||
->get(['id', 'group', 'key'])
|
||||
->keyBy(fn (CmsContent $row) => $row->group.'/'.$row->key);
|
||||
|
||||
$pages = [];
|
||||
|
||||
foreach (config('cms_pages.pages', []) as $pageKey => $page) {
|
||||
$sections = [];
|
||||
|
||||
foreach ($page['sections'] ?? [] as $section) {
|
||||
$id = $section['group'].'/'.$section['key'];
|
||||
|
||||
$sections[] = array_merge($section, [
|
||||
'page' => $pageKey,
|
||||
'path' => $section['path'] ?? $page['path'] ?? null,
|
||||
'anchor' => $section['anchor'] ?? null,
|
||||
'content_id' => $rows[$id]->id ?? null,
|
||||
'theme_key' => $this->themeKey($section['group'], $section['key']),
|
||||
]);
|
||||
}
|
||||
|
||||
$pages[$pageKey] = [
|
||||
'title' => $page['title'],
|
||||
'path' => $page['path'] ?? null,
|
||||
'sections' => $sections,
|
||||
];
|
||||
}
|
||||
|
||||
$unlisted = $this->unlistedSections($rows->keys()->all(), $rows);
|
||||
|
||||
if ($unlisted !== []) {
|
||||
$pages[self::UNLISTED] = [
|
||||
'title' => config('cms_pages.unlisted.title', 'Nicht mehr verwendet'),
|
||||
'path' => null,
|
||||
'sections' => $unlisted,
|
||||
];
|
||||
}
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eine Sektion aus der Registry samt Datenbankbezug, oder null.
|
||||
*
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
public function section(string $group, string $key): ?array
|
||||
{
|
||||
foreach ($this->pages() as $page) {
|
||||
foreach ($page['sections'] as $section) {
|
||||
if ($section['group'] === $group && $section['key'] === $key) {
|
||||
return $section;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Klartext-Beschriftung eines Feldes: Sektions-Override, dann Wörterbuch, sonst lesbar gemacht.
|
||||
*/
|
||||
public function fieldLabel(string $field, ?string $group = null, ?string $key = null): string
|
||||
{
|
||||
if ($group !== null && $key !== null) {
|
||||
foreach (config('cms_pages.pages', []) as $page) {
|
||||
foreach ($page['sections'] ?? [] as $section) {
|
||||
if ($section['group'] === $group && $section['key'] === $key && isset($section['fields'][$field])) {
|
||||
return $section['fields'][$field];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$labels = config('cms_pages.field_labels', []);
|
||||
|
||||
if (isset($labels[$field])) {
|
||||
return $labels[$field];
|
||||
}
|
||||
|
||||
return Str::ucfirst(str_replace('_', ' ', $field));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adresse der Vorschau für eine Sektion: Frontend-Domain, Pfad, Cache-Brecher und Sprungmarke.
|
||||
*/
|
||||
public function previewUrl(?array $section, int $version = 0): ?string
|
||||
{
|
||||
$path = $section['path'] ?? null;
|
||||
|
||||
if ($path === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$url = rtrim(config('domains.protocol', 'https://').config('domains.domain_b2in'), '/')
|
||||
.'/'.ltrim($path, '/')
|
||||
.'?cms-preview='.$version;
|
||||
|
||||
if (! empty($section['anchor'])) {
|
||||
$url .= '#'.$section['anchor'];
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme-Section-Schlüssel (Lang-Datei) zu Gruppe/Key, für „Aus Vorlage anlegen“.
|
||||
*/
|
||||
public function themeKey(string $group, string $key): ?string
|
||||
{
|
||||
foreach (config('cms_section_map.sections', []) as $themeKey => $target) {
|
||||
if ($target === [$group, $key]) {
|
||||
return $themeKey;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $ids
|
||||
* @param \Illuminate\Support\Collection<string, CmsContent> $rows
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
private function unlistedSections(array $ids, $rows): array
|
||||
{
|
||||
$listed = [];
|
||||
|
||||
foreach (config('cms_pages.pages', []) as $page) {
|
||||
foreach ($page['sections'] ?? [] as $section) {
|
||||
$listed[$section['group'].'/'.$section['key']] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$out = [];
|
||||
|
||||
foreach ($ids as $id) {
|
||||
if (isset($listed[$id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$row = $rows[$id];
|
||||
|
||||
$out[] = [
|
||||
'group' => $row->group,
|
||||
'key' => $row->key,
|
||||
'page' => self::UNLISTED,
|
||||
'path' => null,
|
||||
'anchor' => null,
|
||||
'title' => $row->group.' / '.$row->key,
|
||||
'description' => null,
|
||||
'content_id' => $row->id,
|
||||
'theme_key' => null,
|
||||
];
|
||||
}
|
||||
|
||||
usort($out, fn (array $a, array $b) => [$a['group'], $a['key']] <=> [$b['group'], $b['key']]);
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
|
@ -206,3 +206,66 @@ if (! function_exists('media_url')) {
|
|||
return $resolved[$cacheKey];
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('b2in_world')) {
|
||||
/**
|
||||
* Farbwelt der aktuellen Seite: `interior` (Standard) oder `immobilien`.
|
||||
*
|
||||
* Die Immobilien-Welt umfasst die Immobilien-Routen samt Detailseiten und den
|
||||
* Entwicklungs-Vorschauen. Das Layout setzt den Wert als Body-Klasse
|
||||
* (`world-interior` / `world-immobilien`), theme-b2in.css schaltet daran die Farben um.
|
||||
* Livewire-Komponenten merken sich die Welt in mount(), weil ein späterer
|
||||
* Livewire-Request keine Seitenroute mehr trägt.
|
||||
*/
|
||||
function b2in_world(): string
|
||||
{
|
||||
if (app()->bound('b2in.world')) {
|
||||
return (string) app('b2in.world');
|
||||
}
|
||||
|
||||
$routeName = request()->route()?->getName();
|
||||
|
||||
if (is_string($routeName) && (str_starts_with($routeName, 'immobilien') || str_starts_with($routeName, 'dev.immobilien'))) {
|
||||
return 'immobilien';
|
||||
}
|
||||
|
||||
return 'interior';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('b2in_worlds')) {
|
||||
/**
|
||||
* Die beiden Welten für den Welten-Wechsler in Kopf- und Fußzeile.
|
||||
*
|
||||
* @return array<string, array{label: string, url: string, active: bool}>
|
||||
*/
|
||||
function b2in_worlds(?string $current = null): array
|
||||
{
|
||||
$current = $current ?? b2in_world();
|
||||
|
||||
return [
|
||||
'interior' => [
|
||||
'label' => __('ui.world_interior'),
|
||||
'url' => url('/'),
|
||||
'active' => $current === 'interior',
|
||||
],
|
||||
'immobilien' => [
|
||||
'label' => __('ui.world_immobilien'),
|
||||
'url' => route('immobilien'),
|
||||
'active' => $current === 'immobilien',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('b2in_world_set')) {
|
||||
/**
|
||||
* Welt für den laufenden Request festlegen, wenn sie nicht an der Route hängt –
|
||||
* etwa auf der Magazin-Detailseite, die in der Welt ihres Beitrags rendert.
|
||||
* Muss vor dem Rendern von Kopf- und Fußzeile geschehen (beide halten die Welt in mount() fest).
|
||||
*/
|
||||
function b2in_world_set(string $world): void
|
||||
{
|
||||
app()->instance('b2in.world', $world);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
294
config/cms_pages.php
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Seitenbaum für den Inhalte-Editor im Portal (Nachtrag 12).
|
||||
*
|
||||
* Der Editor zeigt die CMS-Sektionen nicht mehr nach Datenbank-Gruppe, sondern so, wie sie auf
|
||||
* der Website stehen: je Seite in der Reihenfolge von oben nach unten, mit Klartext-Titel und
|
||||
* einem Satz, der sagt, was die Sektion ist. `anchor` ist die Sprungmarke in der Vorschau
|
||||
* (das `id` des Abschnitts im Frontend).
|
||||
*
|
||||
* Eine Sektion, die auf mehreren Seiten steht, ist einmal eingetragen – dort, wo sie zuerst
|
||||
* auftaucht – und nennt die anderen Seiten in der Beschreibung.
|
||||
*
|
||||
* `group`/`key` sind Gruppe und Schlüssel in flux_cms_contents (siehe cms_section_map.php).
|
||||
* Sektionen, die in der Datenbank liegen, aber hier nicht eingetragen sind, sammelt der Editor
|
||||
* unter „Nicht mehr verwendet“ – sie gehen also nicht verloren, stehen aber auch nicht im Weg.
|
||||
*
|
||||
* `fields` überschreibt Feldbeschriftungen je Sektion; alles andere kommt aus `field_labels`.
|
||||
*/
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Feldbeschriftungen
|
||||
|--------------------------------------------------------------------------
|
||||
| Gilt für Felder auf Sektionsebene und für Felder in Listeneinträgen.
|
||||
| Unbekannte Schlüssel werden lesbar gemacht (button_text → „Button text“).
|
||||
*/
|
||||
'field_labels' => [
|
||||
'title' => 'Überschrift',
|
||||
'subtitle' => 'Untertitel',
|
||||
'headline' => 'Überschrift',
|
||||
'tagline' => 'Claim',
|
||||
'text' => 'Text',
|
||||
'intro' => 'Einleitung',
|
||||
'description' => 'Beschreibung',
|
||||
'content' => 'Inhalt',
|
||||
'lead' => 'Vorspann',
|
||||
'note' => 'Hinweis',
|
||||
'label' => 'Beschriftung',
|
||||
'badge' => 'Kennzeichen',
|
||||
'eyebrow' => 'Überzeile',
|
||||
'quote' => 'Zitat',
|
||||
'author' => 'Zitatgeber',
|
||||
'name' => 'Name',
|
||||
'role' => 'Rolle / Funktion',
|
||||
'city' => 'Ort',
|
||||
'value' => 'Wert',
|
||||
'number' => 'Zahl',
|
||||
'suffix' => 'Zusatz',
|
||||
'icon' => 'Icon',
|
||||
'image' => 'Bild',
|
||||
'image_alt' => 'Bildbeschreibung',
|
||||
'alt' => 'Bildbeschreibung',
|
||||
'logo' => 'Logo',
|
||||
'link' => 'Link',
|
||||
'url' => 'Adresse (URL)',
|
||||
'href' => 'Link',
|
||||
'link_text' => 'Linktext',
|
||||
'button_text' => 'Button-Beschriftung',
|
||||
'button_link' => 'Button-Link',
|
||||
'primary_button' => 'Erster Button',
|
||||
'secondary_button' => 'Zweiter Button',
|
||||
'cta_title' => 'Überschrift Abschluss',
|
||||
'cta_text' => 'Text Abschluss',
|
||||
'cta_link' => 'Link Abschluss',
|
||||
'items' => 'Einträge',
|
||||
'steps' => 'Schritte',
|
||||
'cards' => 'Karten',
|
||||
'stats' => 'Kennzahlen',
|
||||
'features' => 'Merkmale',
|
||||
'benefits' => 'Vorteile',
|
||||
'values' => 'Werte',
|
||||
'links' => 'Links',
|
||||
'columns' => 'Spalten',
|
||||
'menu' => 'Menüpunkte',
|
||||
'navigation' => 'Navigation',
|
||||
'topics' => 'Themen',
|
||||
'options' => 'Auswahlmöglichkeiten',
|
||||
'placeholder' => 'Platzhaltertext',
|
||||
'meta_title' => 'Seitentitel (Browser, Google)',
|
||||
'meta_description' => 'Beschreibung für Google',
|
||||
'email' => 'E-Mail',
|
||||
'phone' => 'Telefon',
|
||||
'whatsapp' => 'WhatsApp-Nummer',
|
||||
'address' => 'Adresse',
|
||||
'opening_hours' => 'Öffnungszeiten',
|
||||
'copyright' => 'Copyright-Zeile',
|
||||
'map_alt' => 'Beschreibung der Karte',
|
||||
'region_label' => 'Beschriftung der Region',
|
||||
'distance_suffix' => 'Zusatz hinter der Entfernung',
|
||||
'hub' => 'Drehscheibe (Bielefeld)',
|
||||
'steps_title' => 'Überschrift Ablauf-Block',
|
||||
'steps_text' => 'Einleitung Ablauf-Block',
|
||||
'x' => 'Position waagerecht (Entwickler)',
|
||||
'y' => 'Position senkrecht (Entwickler)',
|
||||
'id' => 'Sprungmarke (Entwickler)',
|
||||
'slug' => 'Adresskürzel (Entwickler)',
|
||||
'teaser' => 'Kurztext',
|
||||
'summary' => 'Zusammenfassung',
|
||||
'brands' => 'Marken',
|
||||
'projects' => 'Projekte',
|
||||
'partners' => 'Partner',
|
||||
'questions' => 'Fragen',
|
||||
'question' => 'Frage',
|
||||
'answer' => 'Antwort',
|
||||
'read_more' => 'Weiterlesen-Text',
|
||||
'empty_text' => 'Text, wenn nichts vorhanden ist',
|
||||
'success_text' => 'Text nach dem Absenden',
|
||||
'submit_text' => 'Beschriftung Absenden-Button',
|
||||
'privacy_text' => 'Datenschutz-Hinweis',
|
||||
'subjects' => 'Betreffe',
|
||||
'subject' => 'Betreff',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Seiten in der Reihenfolge des Menüs
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
'pages' => [
|
||||
|
||||
'home' => [
|
||||
'title' => 'Startseite',
|
||||
'path' => '/',
|
||||
'sections' => [
|
||||
['group' => 'home', 'key' => 'hero', 'anchor' => 'hero', 'title' => 'Hero',
|
||||
'description' => 'Der große Einstieg ganz oben: Überschrift, Text, Buttons und Bild.'],
|
||||
['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',
|
||||
'description' => 'Zwei Wege: Privat einrichten oder Projekt ausstatten.'],
|
||||
['group' => 'home', 'key' => 'core_brands', 'anchor' => 'marken', 'title' => 'Kernmarken',
|
||||
'description' => 'Überschrift und Untertitel des Marken-Rasters. Die Kacheln selbst kommen aus der Markenverwaltung.'],
|
||||
['group' => 'home', 'key' => 'origin_section', 'anchor' => 'herkunft', 'title' => 'Aus der Region gefertigt',
|
||||
'description' => 'Karte mit Bielefeld als Drehscheibe und der Ablauf-Block am Beispiel CABINET. Die Standorte pflegst du bei den Marken. Steht auch auf /marken.'],
|
||||
['group' => 'home', 'key' => 'vision_section', 'anchor' => 'vision', 'title' => 'Gebaut auf Expertise',
|
||||
'description' => 'Die Geschichte hinter B2in: Text, Kennzahlen und Bild.'],
|
||||
['group' => 'home', 'key' => 'sponsoring', 'anchor' => 'engagement', 'title' => 'Regional verbunden',
|
||||
'description' => 'Vereine und Organisationen, die B2in unterstützt.'],
|
||||
['group' => 'referenzen', 'key' => 'display_feed', 'anchor' => 'displays', 'title' => 'Aus den Stores',
|
||||
'description' => 'Überschrift und Text des Display-Feeds. Die Inhalte kommen aus den Store Displays. Steht auch auf /referenzen.'],
|
||||
['group' => 'home', 'key' => 'immobilien_teaser', 'anchor' => 'immobilien', 'title' => 'Immobilien-Teaser',
|
||||
'description' => 'Der Übergang in die Immobilien-Welt am Ende der Startseite.'],
|
||||
['group' => 'global', 'key' => 'cta_section', 'anchor' => 'cta', 'title' => 'Abschluss: Termin anfragen',
|
||||
'description' => 'Der letzte Block vor dem Fuß. Steht auf allen Interior-Seiten.'],
|
||||
],
|
||||
],
|
||||
|
||||
'marken' => [
|
||||
'title' => 'Marken',
|
||||
'path' => '/marken',
|
||||
'sections' => [
|
||||
['group' => 'marken', 'key' => 'hero', 'anchor' => 'seitenkopf', 'title' => 'Seitenkopf',
|
||||
'description' => 'Überschrift, Einleitung und Bild oben auf /marken. Die Markenblöcke darunter kommen aus der Markenverwaltung.'],
|
||||
['group' => 'marken', 'key' => 'network', 'anchor' => 'netzwerk', 'title' => 'Mehr als Marken: unser Netzwerk',
|
||||
'description' => 'Partner und Zulieferer jenseits der Kernmarken.'],
|
||||
['group' => 'marken', 'key' => 'growth', 'anchor' => 'wachstum', 'title' => 'Die Markenwelt wächst',
|
||||
'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.'],
|
||||
],
|
||||
],
|
||||
|
||||
'referenzen' => [
|
||||
'title' => 'Referenzen',
|
||||
'path' => '/referenzen',
|
||||
'sections' => [
|
||||
['group' => 'referenzen', 'key' => 'hero', 'anchor' => 'seitenkopf', 'title' => 'Seitenkopf',
|
||||
'description' => 'Überschrift und Einleitung der Referenzen-Seite.'],
|
||||
['group' => 'referenzen', 'key' => 'projects', 'anchor' => 'projekte', 'title' => 'Realisierte Projekte',
|
||||
'description' => 'Überschrift, Text und Leerzustand über den Projekten. Die Projekte selbst pflegst du unter „Referenzen“.'],
|
||||
],
|
||||
],
|
||||
|
||||
'magazin' => [
|
||||
'title' => 'Magazin',
|
||||
'path' => '/magazin',
|
||||
'sections' => [
|
||||
['group' => 'magazin', 'key' => 'list', 'anchor' => 'magazin', 'title' => 'Magazin-Übersicht',
|
||||
'description' => 'Überschrift und Einleitung der Übersicht. Die Beiträge pflegst du unter „Magazin“.'],
|
||||
['group' => 'magazin', 'key' => 'detail', 'anchor' => null, 'title' => 'Beitragsseite',
|
||||
'description' => 'Feste Texte auf jeder Beitragsseite, etwa „Zurück zur Übersicht“ und „Weitere Beiträge“.'],
|
||||
],
|
||||
],
|
||||
|
||||
'about' => [
|
||||
'title' => 'Über uns',
|
||||
'path' => '/about',
|
||||
'sections' => [
|
||||
['group' => 'about', 'key' => 'hero', 'anchor' => 'seitenkopf', 'title' => 'Seitenkopf',
|
||||
'description' => 'Überschrift und Einleitung von „Über uns“.'],
|
||||
['group' => 'about', 'key' => 'our_story', 'anchor' => 'geschichte', 'title' => 'Unsere Geschichte',
|
||||
'description' => 'Der Text zur Entstehung von B2in.'],
|
||||
['group' => 'about', 'key' => 'image_break', 'anchor' => 'bild', 'title' => 'Bildpause',
|
||||
'description' => 'Großes Bild mit kurzer Zeile zwischen Geschichte und Werten.'],
|
||||
['group' => 'about', 'key' => 'our_values', 'anchor' => 'werte', 'title' => 'Unsere Werte',
|
||||
'description' => 'Die Werte-Kacheln.'],
|
||||
['group' => 'netzwerk', 'key' => 'partner_cta', 'anchor' => 'cta', 'title' => 'Abschluss auf „Über uns“',
|
||||
'description' => 'Der Abschlussblock am Ende von „Über uns“.'],
|
||||
],
|
||||
],
|
||||
|
||||
'contact' => [
|
||||
'title' => 'Kontakt',
|
||||
'path' => '/contact',
|
||||
'sections' => [
|
||||
['group' => 'contact', 'key' => 'contact_form', 'anchor' => 'kontakt', 'title' => 'Kontaktformular',
|
||||
'description' => 'Überschrift, Betreffe, Feldbeschriftungen und Datenschutz-Hinweis des Formulars.'],
|
||||
['group' => 'global', 'key' => 'contact', 'anchor' => 'kontakt', 'title' => 'Kontaktdaten',
|
||||
'description' => 'Telefon, E-Mail, Adresse und die WhatsApp-Nummer. Ohne WhatsApp-Nummer erscheint kein WhatsApp-Button.'],
|
||||
],
|
||||
],
|
||||
|
||||
'faq' => [
|
||||
'title' => 'FAQ',
|
||||
'path' => '/faq',
|
||||
'sections' => [
|
||||
['group' => 'faq', 'key' => 'faq', 'anchor' => 'faq', 'title' => 'Fragen und Antworten',
|
||||
'description' => 'Überschrift und die Liste der Fragen der Interior-Welt.'],
|
||||
],
|
||||
],
|
||||
|
||||
'global' => [
|
||||
'title' => 'Auf jeder Seite',
|
||||
'path' => '/',
|
||||
'sections' => [
|
||||
['group' => 'global', 'key' => 'announcement_bar', 'anchor' => 'header', 'title' => 'Hinweisleiste',
|
||||
'description' => 'Schmale Zeile ganz oben über dem Menü. Leer lassen, um sie auszublenden.'],
|
||||
['group' => 'global', 'key' => 'header', 'anchor' => 'header', 'title' => 'Hauptmenü',
|
||||
'description' => 'Menüpunkte und Buttons im Kopf der Interior-Welt.'],
|
||||
['group' => 'global', 'key' => 'appointment_widget', 'anchor' => null, 'title' => 'Terminanfrage-Widget',
|
||||
'description' => 'Das schwebende Widget rechts unten: Texte, Themen und Zeitfenster.'],
|
||||
['group' => 'global', 'key' => 'footer', 'anchor' => 'footer', 'title' => 'Fußbereich',
|
||||
'description' => 'Spalten, Links, Social-Media und Copyright-Zeile.'],
|
||||
],
|
||||
],
|
||||
|
||||
'immobilien' => [
|
||||
'title' => 'Immobilien-Welt',
|
||||
'path' => '/immobilien',
|
||||
'sections' => [
|
||||
['group' => 'global', 'key' => 'announcement_bar_immobilien', 'anchor' => 'header', 'title' => 'Hinweisleiste (Immobilien)',
|
||||
'description' => 'Hinweisleiste über dem Menü der Immobilien-Welt.'],
|
||||
['group' => 'global', 'key' => 'header_immobilien', 'anchor' => 'header', 'title' => 'Hauptmenü (Immobilien)',
|
||||
'description' => 'Menüpunkte und Buttons im Kopf der Immobilien-Welt.'],
|
||||
['group' => 'global', 'key' => 'cta_section_immobilien', 'anchor' => 'cta', 'title' => 'Abschluss (Immobilien)',
|
||||
'description' => 'Der letzte Block vor dem Fuß auf allen Immobilien-Seiten.'],
|
||||
['group' => 'immobilien', 'key' => 'magazin_list', 'anchor' => 'magazin', 'title' => 'Magazin-Übersicht (Immobilien)',
|
||||
'description' => 'Überschrift und Einleitung unter /immobilien/magazin.', 'path' => '/immobilien/magazin'],
|
||||
['group' => 'immobilien', 'key' => 'about_hero', 'anchor' => 'seitenkopf', 'title' => 'Über B2in: Seitenkopf',
|
||||
'description' => 'Überschrift und Einleitung unter /immobilien/ueber-b2in.', 'path' => '/immobilien/ueber-b2in'],
|
||||
['group' => 'immobilien', 'key' => 'our_story', 'anchor' => 'geschichte', 'title' => 'Über B2in: Geschichte',
|
||||
'description' => 'Der Geschichte-Block der Immobilien-Welt.', 'path' => '/immobilien/ueber-b2in'],
|
||||
['group' => 'immobilien', 'key' => 'about_image_break', 'anchor' => 'bild', 'title' => 'Über B2in: Bildpause',
|
||||
'description' => 'Bild zwischen Geschichte und Werten.', 'path' => '/immobilien/ueber-b2in'],
|
||||
['group' => 'immobilien', 'key' => 'our_values', 'anchor' => 'werte', 'title' => 'Über B2in: Werte',
|
||||
'description' => 'Die Werte-Kacheln der Immobilien-Welt.', 'path' => '/immobilien/ueber-b2in'],
|
||||
['group' => 'faq', 'key' => 'faq_immobilien', 'anchor' => 'faq', 'title' => 'FAQ (Immobilien)',
|
||||
'description' => 'Fragen und Antworten unter /immobilien/faq.', 'path' => '/immobilien/faq'],
|
||||
['group' => 'contact', 'key' => 'contact_form_immobilien', 'anchor' => 'kontakt', 'title' => 'Kontaktformular (Immobilien)',
|
||||
'description' => 'Abweichende Texte und Betreffe unter /immobilien/kontakt. Leere Felder übernehmen den Interior-Wert.', 'path' => '/immobilien/kontakt'],
|
||||
],
|
||||
],
|
||||
|
||||
'legal' => [
|
||||
'title' => 'Rechtliches',
|
||||
'path' => '/impressum',
|
||||
'sections' => [
|
||||
['group' => 'legal', 'key' => 'impressum', 'anchor' => null, 'title' => 'Impressum',
|
||||
'description' => 'Pflichtangaben. Der Inhalt ist HTML.', 'path' => '/impressum'],
|
||||
['group' => 'legal', 'key' => 'privacy', 'anchor' => null, 'title' => 'Datenschutz',
|
||||
'description' => 'Datenschutzerklärung. Der Inhalt ist HTML.', 'path' => '/privacy'],
|
||||
['group' => 'legal', 'key' => 'terms', 'anchor' => null, 'title' => 'AGB',
|
||||
'description' => 'Allgemeine Geschäftsbedingungen. Der Inhalt ist HTML.', 'path' => '/terms'],
|
||||
['group' => 'legal', 'key' => 'cookie_policy', 'anchor' => null, 'title' => 'Cookie-Hinweise',
|
||||
'description' => 'Cookie-Richtlinie. Der Inhalt ist HTML.', 'path' => '/cookie-policy'],
|
||||
],
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sammelpunkt für alles, was in der Datenbank liegt, aber oben nicht steht
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
'unlisted' => [
|
||||
'title' => 'Nicht mehr verwendet',
|
||||
'description' => 'Sektionen stillgelegter Seiten (Netzwerk, Partner, alte Immobilien-Seite). Bleiben als Archiv liegen, erscheinen nirgends.',
|
||||
],
|
||||
];
|
||||
|
|
@ -14,15 +14,18 @@ return [
|
|||
|
||||
'sections' => [
|
||||
'announcement_bar' => ['global', 'announcement_bar'],
|
||||
'announcement_bar_immobilien' => ['global', 'announcement_bar_immobilien'],
|
||||
'header' => ['global', 'header'],
|
||||
'footer' => ['global', 'footer'],
|
||||
'cta_section' => ['global', 'cta_section'],
|
||||
'cta_section_immobilien' => ['global', 'cta_section_immobilien'],
|
||||
'appointment_widget' => ['global', 'appointment_widget'],
|
||||
'cta_section_portfolio' => ['global', 'cta_section_portfolio'],
|
||||
|
||||
'hero' => ['home', 'hero'],
|
||||
'audience_switch' => ['home', 'audience_switch'],
|
||||
'core_brands' => ['home', 'core_brands'],
|
||||
'origin_section' => ['home', 'origin_section'],
|
||||
|
||||
'marken_hero' => ['marken', 'hero'],
|
||||
'marken_network' => ['marken', 'network'],
|
||||
|
|
@ -68,8 +71,18 @@ return [
|
|||
'interior_trust' => ['netzwerk', 'interior_trust'],
|
||||
|
||||
'faq' => ['faq', 'faq'],
|
||||
'faq_immobilien' => ['faq', 'faq_immobilien'],
|
||||
|
||||
// Immobilien-Welt: eigene Navigation, eigenes Magazin, eigene Ueber-uns-Seite
|
||||
'header_immobilien' => ['global', 'header_immobilien'],
|
||||
'magazin_list_immobilien' => ['immobilien', 'magazin_list'],
|
||||
'immobilien_about_hero' => ['immobilien', 'about_hero'],
|
||||
'immobilien_our_story' => ['immobilien', 'our_story'],
|
||||
'immobilien_about_image_break' => ['immobilien', 'about_image_break'],
|
||||
'immobilien_our_values' => ['immobilien', 'our_values'],
|
||||
|
||||
'contact_form' => ['contact', 'contact_form'],
|
||||
'contact_form_immobilien' => ['contact', 'contact_form_immobilien'],
|
||||
|
||||
'about_hero' => ['about', 'hero'],
|
||||
'our_story' => ['about', 'our_story'],
|
||||
|
|
|
|||
|
|
@ -71,13 +71,29 @@ return [
|
|||
'view_prefix' => 'b2in',
|
||||
'assets_dir' => 'build/b2in',
|
||||
'description' => 'Haupt-Landingpage mit Standard-Theme',
|
||||
// Interior-Welt (Standard). Die Immobilien-Welt behält das bisherige Blau,
|
||||
// siehe `worlds` und resources/css/web/theme-b2in.css.
|
||||
'color_scheme' => [
|
||||
'primary' => '#2b3f51', // Anthracite
|
||||
'secondary' => '#20a0da', // Dynamic Blue
|
||||
'primary' => '#202c2b', // Tanne
|
||||
'secondary' => '#d39e46', // Ocker
|
||||
],
|
||||
'font_family' => [
|
||||
'primary' => 'Inter',
|
||||
'secondary' => 'IBM Plex Sans',
|
||||
'secondary' => 'EB Garamond',
|
||||
],
|
||||
'worlds' => [
|
||||
'interior' => [
|
||||
'primary' => '#202c2b', // Tanne – Hauptton dunkel, Wortmarke
|
||||
'background' => '#f7f4ef', // Creme
|
||||
'surface' => '#d9cbbf', // Sand – Flächen, Footer
|
||||
'accent' => '#d39e46', // Ocker – Akzent, Logo-Bogen
|
||||
'secondary' => '#a38f82', // Clay – Sekundär, Produkte
|
||||
'text' => '#2d2926', // Anthrazit
|
||||
],
|
||||
'immobilien' => [
|
||||
'primary' => '#2b3f51', // Anthracite
|
||||
'accent' => '#20a0da', // Dynamic Blue
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class CmsArticleFactory extends Factory
|
|||
'subtitle' => fake()->sentence(12),
|
||||
'image' => 'b2in/magazin-'.fake()->numberBetween(1, 5).'.jpg',
|
||||
'category' => fake()->randomElement(['Dubai Investment', 'Rendite & Einrichtung', 'B2B & Partner', 'Einrichtung & Netzwerk']),
|
||||
'world' => CmsArticle::WORLD_INTERIOR,
|
||||
'date_label' => fake()->date('F j, Y'),
|
||||
'read_time' => fake()->numberBetween(3, 10).' min read',
|
||||
'author' => [
|
||||
|
|
|
|||
62
database/factories/CmsReferenceFactory.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\CmsReference;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<CmsReference>
|
||||
*/
|
||||
class CmsReferenceFactory extends Factory
|
||||
{
|
||||
protected $model = CmsReference::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
$title = fake()->unique()->words(3, true);
|
||||
|
||||
return [
|
||||
'title' => ['de' => ucfirst($title), 'en' => ucfirst($title)],
|
||||
'location' => fake()->city(),
|
||||
'year' => (string) fake()->numberBetween(2022, 2026),
|
||||
'description' => ['de' => fake()->paragraph(2), 'en' => fake()->paragraph(2)],
|
||||
'image' => 'referenzen/'.fake()->unique()->slug(2).'.webp',
|
||||
'gallery' => [],
|
||||
'brand_slugs' => [],
|
||||
'is_published' => true,
|
||||
'order' => fake()->numberBetween(1, 20),
|
||||
];
|
||||
}
|
||||
|
||||
public function published(): static
|
||||
{
|
||||
return $this->state(fn () => ['is_published' => true]);
|
||||
}
|
||||
|
||||
public function unpublished(): static
|
||||
{
|
||||
return $this->state(fn () => ['is_published' => false]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $slugs
|
||||
*/
|
||||
public function forBrands(array $slugs): static
|
||||
{
|
||||
return $this->state(fn () => ['brand_slugs' => $slugs]);
|
||||
}
|
||||
|
||||
public function withGallery(int $count = 2): static
|
||||
{
|
||||
return $this->state(function () use ($count) {
|
||||
$items = [];
|
||||
|
||||
for ($i = 1; $i <= $count; $i++) {
|
||||
$items[] = ['image' => 'referenzen/bild-'.$i.'.webp', 'caption' => 'Bild '.$i];
|
||||
}
|
||||
|
||||
return ['gallery' => $items];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use App\Models\CmsArticle;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Magazinbeiträge gehören zu einer Farbwelt (Interior oder Immobilien).
|
||||
*
|
||||
* Die drei Dubai-Beiträge aus dem Bestand werden direkt der Immobilien-Welt zugeordnet,
|
||||
* damit sie nach der Trennung nicht in der Interior-Liste stehen.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('cms_articles', function (Blueprint $table) {
|
||||
$table->string('world', 20)->default(CmsArticle::WORLD_INTERIOR)->index()->after('category');
|
||||
});
|
||||
|
||||
CmsArticle::query()
|
||||
->whereIn('slug', CmsArticle::LEGACY_IMMOBILIEN_SLUGS)
|
||||
->update(['world' => CmsArticle::WORLD_IMMOBILIEN]);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('cms_articles', function (Blueprint $table) {
|
||||
$table->dropIndex(['world']);
|
||||
$table->dropColumn('world');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Fertigungsort je Kernmarke (Nachtrag 11 zu Arbeitsplan 14.09.2026, Punkt 1.3).
|
||||
*
|
||||
* Die Herkunfts-Sektion „Aus der Region gefertigt“ holt ihre Standorte nicht mehr aus einem
|
||||
* JSON-Eintrag im generischen Inhalte-Editor, sondern aus der Markenverwaltung: Marcel pflegt
|
||||
* Ort, Entfernung und Rolle direkt bei der Marke. Die Kartenpositionen bleiben im Code.
|
||||
*
|
||||
* - `origin_city` Fertigungsort, z. B. „Bünde“ – leer bedeutet: Marke erscheint nicht in der Sektion
|
||||
* - `origin_distance` Entfernung ab Bielefeld als Text, z. B. „rund 30 km“
|
||||
* - `origin_role` Ein Satz zur Rolle des Standorts
|
||||
*
|
||||
* Alle drei sind übersetzbar wie die übrigen Textfelder der Marke.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('cms_brands', function (Blueprint $table) {
|
||||
$table->json('origin_city')->nullable()->after('showroom');
|
||||
$table->json('origin_distance')->nullable()->after('origin_city');
|
||||
$table->json('origin_role')->nullable()->after('origin_distance');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('cms_brands', function (Blueprint $table) {
|
||||
$table->dropColumn(['origin_city', 'origin_distance', 'origin_role']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Referenzprojekte der Interior-Welt (Nachtrag 13).
|
||||
*
|
||||
* AP5 hatte die Referenzen aus den Markengalerien zusammengesammelt, um keine zweite Pflegestelle
|
||||
* zu schaffen. Abstimmung 15.09.: Marcel soll Referenzen als eigene Einträge pflegen – ein
|
||||
* realisiertes Projekt hat mehr als ein Bild und mehr als eine Marke. Solange kein Eintrag
|
||||
* existiert, greift das Frontend weiter auf die Markengalerien zurück.
|
||||
*
|
||||
* - `title`, `description` übersetzbar
|
||||
* - `location`, `year` Ort und Jahr als Text („Bielefeld“, „2025“)
|
||||
* - `image` Titelbild (Dateiname aus der Medienbibliothek)
|
||||
* - `gallery` weitere Bilder mit Bildunterschrift
|
||||
* - `brand_slugs` beteiligte Kernmarken (Slugs aus cms_brands), verlinkt auf die Markenseiten
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('cms_references', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->json('title');
|
||||
$table->string('location')->nullable();
|
||||
$table->string('year', 20)->nullable();
|
||||
$table->json('description')->nullable();
|
||||
$table->string('image')->nullable();
|
||||
$table->json('gallery')->nullable();
|
||||
$table->json('brand_slugs')->nullable();
|
||||
$table->boolean('is_published')->default(true);
|
||||
$table->integer('order')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cms_references');
|
||||
}
|
||||
};
|
||||
87
database/seeders/BrandOriginSeeder.php
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\CmsBrand;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Trägt den Fertigungsort der Kernmarken nach (Nachtrag 11 zu Arbeitsplan 14.09.2026, Punkt 1.3).
|
||||
*
|
||||
* Die Herkunfts-Sektion „Aus der Region gefertigt“ liest Ort, Entfernung und Rolle aus der
|
||||
* Marke. {@see CmsBrandSeeder} bringt dieselben Werte für frische Installationen mit,
|
||||
* überspringt aber bereits angelegte Marken. Dieser Seeder fasst nur die drei Felder an und
|
||||
* auch nur bei Marken, deren Fertigungsort noch leer ist – ein im Admin eingetragener Ort
|
||||
* bleibt stehen. Mehrfaches Ausführen ist unbedenklich.
|
||||
*
|
||||
* Entfernungen gerundet, Straße ab Bielefeld Mitte. ⚠️ Rolle CABINET nach Kevins Verständnis
|
||||
* (Fertigung Köln, Beratung im Store Bielefeld) – von Marcel bestätigen lassen.
|
||||
*
|
||||
* php artisan db:seed --class=BrandOriginSeeder
|
||||
*/
|
||||
class BrandOriginSeeder extends Seeder
|
||||
{
|
||||
/** @var array<string, array{origin_city: array<string, string>, origin_distance: array<string, string>, origin_role: array<string, string>}> */
|
||||
private const ORIGINS = [
|
||||
'cabinet' => [
|
||||
'origin_city' => ['de' => 'Köln', 'en' => 'Cologne'],
|
||||
'origin_distance' => ['de' => 'rund 200 km', 'en' => 'about 200 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Gefertigt in der Kölner Manufaktur, beraten und geplant im CABINET Store Bielefeld.',
|
||||
'en' => 'Made in the Cologne manufactory, advised and planned at the CABINET Store Bielefeld.',
|
||||
],
|
||||
],
|
||||
'rotpunkt' => [
|
||||
'origin_city' => ['de' => 'Bünde', 'en' => 'Bünde'],
|
||||
'origin_distance' => ['de' => 'rund 30 km', 'en' => 'about 30 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Küchen aus eigener Fertigung in Bünde – geplant bei uns im Küchenstudio.',
|
||||
'en' => 'Kitchens from their own production in Bünde – planned with us in the kitchen studio.',
|
||||
],
|
||||
],
|
||||
'februe' => [
|
||||
'origin_city' => ['de' => 'Herford', 'en' => 'Herford'],
|
||||
'origin_distance' => ['de' => 'rund 20 km', 'en' => 'about 20 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Büro- und Objektmöbel aus eigener Fertigung in Herford.',
|
||||
'en' => 'Office and contract furniture from their own production in Herford.',
|
||||
],
|
||||
],
|
||||
'tischlerei' => [
|
||||
'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.',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$set = 0;
|
||||
$kept = 0;
|
||||
|
||||
foreach (self::ORIGINS as $slug => $origin) {
|
||||
$brand = CmsBrand::query()->where('slug', $slug)->first();
|
||||
|
||||
if (! $brand) {
|
||||
$this->command?->warn("Marke „{$slug}“ nicht gefunden – übersprungen.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trim((string) $brand->getTranslation('origin_city', 'de', false)) !== '') {
|
||||
$kept++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$brand->fill($origin);
|
||||
$brand->save();
|
||||
$set++;
|
||||
}
|
||||
|
||||
$this->command?->info("Fertigungsorte gesetzt: {$set}, vorhandene behalten: {$kept}.");
|
||||
}
|
||||
}
|
||||
|
|
@ -70,6 +70,7 @@ class CmsArticleSeeder extends Seeder
|
|||
],
|
||||
'image' => (string) ($rowDe['image'] ?? ''),
|
||||
'category' => (string) ($rowDe['category'] ?? ''),
|
||||
'world' => (string) ($rowDe['world'] ?? CmsArticle::WORLD_INTERIOR),
|
||||
'date_label' => (string) ($rowDe['date'] ?? ''),
|
||||
'read_time' => (string) ($rowDe['readTime'] ?? ''),
|
||||
'author' => is_array($rowDe['author'] ?? null) ? $rowDe['author'] : [],
|
||||
|
|
|
|||
|
|
@ -6,10 +6,17 @@ use App\Models\CmsBrand;
|
|||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Legt die drei Kernmarken an. Texte aus dev/2026-08-28/umsetzung-inhalte.md §5.
|
||||
* Legt die Kernmarken an. Texte aus dev/2026-08-28/umsetzung-inhalte.md §5.
|
||||
*
|
||||
* Die vierte Marke „Tischlerei“ (Arbeitsplan 14.09.2026, Punkt 1.2) ist ein **Platzhalter**:
|
||||
* Name, Logo, Basisinfos und Bilder liegen noch nicht vor. Die Texte folgen der inhaltlichen
|
||||
* Ausrichtung aus dem Plan (Aufbau, Sonderlösungen, Veredelung, Maßarbeit – ergänzt CABINET
|
||||
* und Rotpunkt, steht nicht daneben) und sollen als Denkanstoß dienen. Der Name des
|
||||
* Partnerbetriebs gehört laut Plan auf die Detailseite, nicht in die Marke.
|
||||
*
|
||||
* Bestehende Marken werden nicht überschrieben – Marcel pflegt sie im Admin. Für bereits
|
||||
* angelegte Marken setzt {@see BrandLogoSeeder} die Logos nach.
|
||||
* angelegte Marken setzt {@see BrandLogoSeeder} die Logos nach und {@see BrandOriginSeeder}
|
||||
* den Fertigungsort für die Herkunfts-Sektion (Nachtrag 11).
|
||||
*
|
||||
* Stimmungsbilder und Galerien fehlen als Zulieferung und bleiben leer; die Kacheln und
|
||||
* Detailseiten zeigen so lange einen Platzhalter.
|
||||
|
|
@ -71,6 +78,12 @@ Experience the possibilities in our Bielefeld showroom.',
|
|||
['title' => 'Montage durch Fachleute', 'description' => 'Wir liefern und montieren – bis zum letzten Detail.'],
|
||||
],
|
||||
'showroom' => 'Showroom Bielefeld',
|
||||
'origin_city' => ['de' => 'Köln', 'en' => 'Cologne'],
|
||||
'origin_distance' => ['de' => 'rund 200 km', 'en' => 'about 200 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Gefertigt in der Kölner Manufaktur, beraten und geplant im CABINET Store Bielefeld.',
|
||||
'en' => 'Made in the Cologne manufactory, advised and planned at the CABINET Store Bielefeld.',
|
||||
],
|
||||
'is_published' => true,
|
||||
],
|
||||
[
|
||||
|
|
@ -106,6 +119,12 @@ Consultation and planning in our kitchen studio.',
|
|||
['title' => 'Lieferung und Aufbau', 'description' => 'Koordiniert aus einer Hand, inklusive Anschluss.'],
|
||||
],
|
||||
'showroom' => 'Küchenstudio',
|
||||
'origin_city' => ['de' => 'Bünde', 'en' => 'Bünde'],
|
||||
'origin_distance' => ['de' => 'rund 30 km', 'en' => 'about 30 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Küchen aus eigener Fertigung in Bünde – geplant bei uns im Küchenstudio.',
|
||||
'en' => 'Kitchens from their own production in Bünde – planned with us in the kitchen studio.',
|
||||
],
|
||||
'is_published' => true,
|
||||
],
|
||||
[
|
||||
|
|
@ -137,6 +156,98 @@ Consultation and planning in our kitchen studio.',
|
|||
['title' => 'Akustik und Ordnung', 'description' => 'Stauraum und Raumgliederung, die Konzentration ermöglichen.'],
|
||||
],
|
||||
'showroom' => '',
|
||||
'origin_city' => ['de' => 'Herford', 'en' => 'Herford'],
|
||||
'origin_distance' => ['de' => 'rund 20 km', 'en' => 'about 20 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Büro- und Objektmöbel aus eigener Fertigung in Herford.',
|
||||
'en' => 'Office and contract furniture from their own production in Herford.',
|
||||
],
|
||||
'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,
|
||||
],
|
||||
];
|
||||
|
|
|
|||
69
database/seeders/OriginSectionCmsSeeder.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use FluxCms\Core\Models\CmsContent;
|
||||
use FluxCms\Core\Services\CmsContentService;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Herkunfts-Sektion „Aus der Region gefertigt“ ins CMS legen (Arbeitsplan 14.09.2026, Punkt 1.3).
|
||||
*
|
||||
* Legt die Sektion nur an, wenn sie fehlt. Ein bestehender Eintrag bleibt inhaltlich unangetastet,
|
||||
* damit Korrekturen aus dem Admin (Ablauf CABINET) nicht verloren gehen.
|
||||
*
|
||||
* Einzige Ausnahme (Nachtrag 11): Der Schlüssel `locations` stammt aus dem ersten Stand, als die
|
||||
* Standorte noch im Sektions-JSON lagen. Seitdem kommen sie aus den Marken
|
||||
* ({@see BrandOriginSeeder}), das Frontend liest den Schlüssel nicht mehr. Er wird aus einem
|
||||
* bestehenden Eintrag entfernt, damit er im Inhalte-Editor nicht als Pflegefeld auftaucht.
|
||||
* Mehrfaches Ausführen ist unbedenklich.
|
||||
*
|
||||
* php artisan db:seed --class=OriginSectionCmsSeeder
|
||||
*/
|
||||
class OriginSectionCmsSeeder extends Seeder
|
||||
{
|
||||
/** @var array<int, string> */
|
||||
private const SECTIONS = ['origin_section'];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$seeder = new CmsContentSeeder;
|
||||
$seeder->setCommand($this->command);
|
||||
$seeder->onlySections = self::SECTIONS;
|
||||
$seeder->run();
|
||||
|
||||
$this->dropLegacyLocations();
|
||||
|
||||
$this->command?->info('Herkunfts-Sektion: angelegt, falls sie fehlte.');
|
||||
}
|
||||
|
||||
private function dropLegacyLocations(): void
|
||||
{
|
||||
$row = CmsContent::query()->where('group', 'home')->where('key', 'origin_section')->first();
|
||||
|
||||
if (! $row) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $row->getTranslations('value');
|
||||
$changed = false;
|
||||
|
||||
foreach ($value as $locale => $section) {
|
||||
if (is_array($section) && array_key_exists('locations', $section)) {
|
||||
unset($section['locations']);
|
||||
$value[$locale] = $section;
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$row->value = $value;
|
||||
$row->save();
|
||||
app(CmsContentService::class)->clearCache('home');
|
||||
|
||||
$this->command?->info('Herkunfts-Sektion: veralteten Schlüssel „locations“ entfernt – die Standorte kommen aus den Marken.');
|
||||
}
|
||||
}
|
||||
63
database/seeders/WorldSplitCmsSeeder.php
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Trennung der Welten Interior und Immobilien im CMS (Arbeitsplan 14.09.2026, Folge von 1.1).
|
||||
*
|
||||
* Zwei Schritte:
|
||||
*
|
||||
* 1. **Neue Sektionen anlegen**, falls sie fehlen: Navigation, Ankündigungsleiste, Magazinliste,
|
||||
* Über-uns-Seite und FAQ der Immobilien-Welt. Bestehende Einträge bleiben unangetastet.
|
||||
* 2. **Sektionen überschreiben**: die Hauptnavigation (ohne „Immobilien“ – der Welten-Wechsler
|
||||
* übernimmt), die Einleitung der Magazinliste (ohne Dubai-Bezug) und das Kontaktformular
|
||||
* beider Welten (Betreffe in Gruppen).
|
||||
*
|
||||
* ⚠️ Schritt 2 setzt genau diese beiden Sektionen auf den Stand der Lang-Dateien zurück.
|
||||
* Vorher ein DB-Backup ziehen, falls die Navigation im Admin geändert wurde.
|
||||
*
|
||||
* php artisan db:seed --class=WorldSplitCmsSeeder
|
||||
*/
|
||||
class WorldSplitCmsSeeder extends Seeder
|
||||
{
|
||||
/** @var array<int, string> */
|
||||
private const NEW_SECTIONS = [
|
||||
'header_immobilien',
|
||||
'announcement_bar_immobilien',
|
||||
'magazin_list_immobilien',
|
||||
'faq_immobilien',
|
||||
'contact_form_immobilien',
|
||||
'cta_section_immobilien',
|
||||
'immobilien_about_hero',
|
||||
'immobilien_our_story',
|
||||
'immobilien_about_image_break',
|
||||
'immobilien_our_values',
|
||||
];
|
||||
|
||||
/** @var array<int, string> */
|
||||
private const OVERWRITE_SECTIONS = [
|
||||
'header',
|
||||
'magazin_list',
|
||||
// Betreffe in Gruppen (Interior / Immobilien / B2in) statt flacher Liste, 15.09.
|
||||
'contact_form',
|
||||
'contact_form_immobilien',
|
||||
];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$create = new CmsContentSeeder;
|
||||
$create->setCommand($this->command);
|
||||
$create->onlySections = self::NEW_SECTIONS;
|
||||
$create->run();
|
||||
|
||||
$overwrite = new CmsContentSeeder;
|
||||
$overwrite->setCommand($this->command);
|
||||
$overwrite->onlySections = self::OVERWRITE_SECTIONS;
|
||||
$overwrite->overwriteExisting = true;
|
||||
$overwrite->run();
|
||||
|
||||
$this->command?->info('Welten-Trennung: '.count(self::NEW_SECTIONS).' Sektionen angelegt (falls fehlend), '.count(self::OVERWRITE_SECTIONS).' Interior-Sektionen aktualisiert.');
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,9 @@
|
|||
**Basis:** `B2in_Interior-Konzept.md`, `umsetzungliste.md`, `umsetzung-inhalte.md`
|
||||
**Vorgänger:** `dev/2026-02-27/b2in-website-umsetzungsplan.md`
|
||||
**Rahmen:** 37–39 Std. freigegeben (18 + 8 + 4 + 3 + 4–6)
|
||||
**Status:** Revision 13 – **AP0–AP6 umgesetzt**, Seite abgestimmt, Markentexte Rotpunkt und Febrü
|
||||
eingearbeitet. Offen: Bildmaterial, Rechtstexte und die Herstellerfreigaben.
|
||||
**Status:** Revision 14 – **AP0–AP6 umgesetzt**, Seite abgestimmt, Markentexte Rotpunkt und Febrü
|
||||
eingearbeitet. **Arbeitsplan 14.09. (`umsetzungen-14-09-2026.md`), Punkt 1.1 Farbwelt und Logo
|
||||
umgesetzt (Nachtrag 6).** Offen: Bildmaterial, Rechtstexte und die Herstellerfreigaben.
|
||||
**Umgebung:** Dev Container, `php artisan …` (nicht `vendor/bin/sail …`)
|
||||
|
||||
> **Verbindliche Schreibweisen:** **Interior** (nicht „Interieur"), **Febrü**, **Rotpunkt Küchen**, **CABINET**.
|
||||
|
|
@ -35,6 +36,11 @@ ausführlichen Text. `php artisan test --compact`: **873 grün** (3217 Assertion
|
|||
| Abstimmungsrunde 03.09.: korrigierte Texte, CABINET-Seite, Galerie-Hinweise, Abschluss-CTA | ✅ |
|
||||
| AP4 Display-Spiegelung | ✅ |
|
||||
| AP5 Rubrik-Seite „Referenzen" | ✅ |
|
||||
| Arbeitsplan 14.09. · 1.1 Farbwelt, Logo je Welt, Welten-Wechsler | ✅ (Nachtrag 6) |
|
||||
| Welten-Trennung: Navigation, Magazin, Über uns, FAQ, Widget je Welt | ✅ (Nachtrag 7, Erweiterung zu 1.1) |
|
||||
| Arbeitsplan 14.09. · 1.2 Tischlerei als vierte Marke (Platzhalter) | ✅ (Nachtrag 9) |
|
||||
| Arbeitsplan 14.09. · 1.4 Akkordeon und Widget-Beschriftung je Marke | ✅ (bereits seit 03.09., CABINET-Text fehlt im CMS) |
|
||||
| Arbeitsplan 14.09. · 1.3 Herkunfts-Sektion „Aus der Region gefertigt“ | ✅ (Nachtrag 10 + 11: Standorte an der Marke, Ablauf CABINET von Marcel bestätigen lassen) |
|
||||
|
||||
### Nächste Schritte
|
||||
|
||||
|
|
@ -843,7 +849,7 @@ als Datenquelle". → **Einbettung des vorhandenen Players im 9:16-Container.**
|
|||
**Tests:** `DisplayWebFeedTest` (10 Fälle: Embed liefert die Datei, kein Token im Ausgabe-Inhalt,
|
||||
404 bei inaktiv/Entwurf/leer/unbekannt, Einbindung auf der Startseite, CMS-Auswahl und Fallback).
|
||||
|
||||
### AP5 · Rubrik-Seite „Referenzen" — ✅ umgesetzt
|
||||
### AP5 · Rubrik-Seite „Referenzen" — ✅ umgesetzt (Pflege seit Nachtrag 13 als eigenes Modul)
|
||||
|
||||
**Korrektur gegenüber Revision 1:** Revision 1 schlug einen neuen `DisplayVersionType`-Case vor.
|
||||
Die Umsetzungsliste sagt „zweites, separates **Display** im Display-CMS" – das ist ein weiterer
|
||||
|
|
@ -1256,6 +1262,22 @@ php artisan db:seed --class=BrandGalleryCaptionNormalizerSeeder --force
|
|||
# hochgeladenes Logo bleibt stehen. Braucht public/img/assets/marken/ aus dem Archiv.
|
||||
php artisan db:seed --class=BrandLogoSeeder --force
|
||||
|
||||
# 7. Welten-Trennung (Nachtrag 7): Navigation und Inhalte der Immobilien-Welt anlegen,
|
||||
# Interior-Navigation ohne „Immobilien“ und Magazin-Einleitung ohne Dubai-Bezug setzen.
|
||||
# Die Migration ordnet die drei Dubai-Beitraege der Immobilien-Welt zu.
|
||||
php artisan db:seed --class=WorldSplitCmsSeeder --force
|
||||
|
||||
# 8. Vierte Kernmarke „Tischlerei“ als Platzhalter (Nachtrag 9) – legt nur an, was fehlt.
|
||||
php artisan db:seed --class=CmsBrandSeeder --force
|
||||
|
||||
# 9. Herkunfts-Sektion „Aus der Region gefertigt“ (Nachtrag 10) – legt die Sektion nur an, wenn sie
|
||||
# fehlt. Entfernt aus einem bestehenden Eintrag nur den veralteten Schlüssel „locations“ (Nachtrag 11).
|
||||
php artisan db:seed --class=OriginSectionCmsSeeder --force
|
||||
|
||||
# 10. Fertigungsort je Kernmarke (Nachtrag 11) – setzt Ort, Entfernung und Rolle nur bei Marken,
|
||||
# deren Ort noch leer ist. Die Migration davor legt die drei Spalten an.
|
||||
php artisan db:seed --class=BrandOriginSeeder --force
|
||||
|
||||
php artisan config:cache
|
||||
php artisan view:clear
|
||||
|
||||
|
|
@ -1342,3 +1364,448 @@ genügt ein selektives Zurückspielen von `flux_cms_contents`, `display_versions
|
|||
- [ ] Markengalerien vor dem Livegang durchsehen: jedes Bild muss in der richtigen Rubrik stehen.
|
||||
Ohne gesetzte Kategorie erscheint ein Bild als „Visualisierung" – für ein echtes Projektfoto
|
||||
also bewusst „Realisiertes Projekt" wählen.
|
||||
|
||||
---
|
||||
|
||||
## Nachtrag 6 · Arbeitsplan 14.09.2026, Punkt 1.1: Farbwelt und Logo (14.09.2026)
|
||||
|
||||
Grundlage: `umsetzungen-14-09-2026.md` (freigegebener Umfang Phase 1) und die Shop-Screenshots
|
||||
unter `_b2in_interior_shop/screenshots/web/` als optische Richtung: warme Creme-/Sandflächen,
|
||||
dunkles Tannengrün als Kontrast, Serifen-Überschriften, ruhige Flächen ohne Leuchteffekte.
|
||||
|
||||
### Zwei Welten auf einem Themensystem
|
||||
|
||||
`theme-b2in.css` definiert die **Interior-Welt als Standard** auf `:root` und die
|
||||
**Immobilien-Welt** als Override unter `.world-immobilien` – dieselben Token-Namen, andere Werte.
|
||||
Das Layout setzt die Welt als Body-Klasse (`world-interior` / `world-immobilien`), ermittelt
|
||||
über `b2in_world()` in `app/helpers.php`: Immobilien sind alle Routen `immobilien*` und
|
||||
`dev.immobilien*`, alles andere ist Interior. Bestehende Sektionen mussten dafür nicht angefasst
|
||||
werden – sie nutzen bereits die Tokens (`text-secondary`, `bg-accent`, `cta-band` …).
|
||||
|
||||
| Token | Interior (neu) | Immobilien (wie bisher) |
|
||||
|---|---|---|
|
||||
| `--background` | Creme `#f7f4ef` | helles Beige `#f5f4f2` |
|
||||
| `--foreground` | Anthrazit `#2d2926` | `#2a2a2a` |
|
||||
| `--primary` (Buttons, dunkle Sektionen, Wortmarke) | Tanne `#202c2b` | Anthracite `#2b3f51` |
|
||||
| `--secondary` (Akzent, Hervorhebungen, Logo-Bogen) | Ocker `#d39e46` | Dynamic Blue `#20a0da` |
|
||||
| `--accent` (Sektionswechsel) | Sand `#d9cbbf` | helles Grau |
|
||||
| `--clay` (neu) | Clay `#a38f82` | gedämpftes Blau |
|
||||
| `--footer-*` (neu) | Sand mit Tanne | Anthracite mit Weiß |
|
||||
| `--cta-band-end` (neu) | helleres Tannengrün (Ton in Ton, Korrektur 15.09.) | Dynamic Blue |
|
||||
| `--font-secondary` (Überschriften) | EB Garamond (liegt lokal vor) | IBM Plex Sans |
|
||||
|
||||
**Kontrast-Entscheidungen:** Ocker trägt keinen hellen Text (Creme auf Ocker ≈ 2,5:1). Deshalb
|
||||
ist Ocker in der Interior-Welt Akzent, kein Flächenton unter Text: der Kontakt-Button im Kopf
|
||||
und der zweite Hero-Button laufen jetzt auf Tanne (gefüllt bzw. als Kontur, `.world-interior
|
||||
.btn-secondary-accent`), der Abschluss-CTA der Startseite liegt auf dem Abschlussband
|
||||
(`cta-band`) mit weißem Button – wie auf den Markenseiten. **Korrektur 15.09.:** Der Verlauf
|
||||
Grün → Ocker wirkte unharmonisch; das Band läuft jetzt Ton in Ton in ein helleres Tannengrün aus,
|
||||
Ocker sitzt nur noch in der Haarlinie oben. Hervorgehobene Wörter im Bandtitel („Ihr nächster
|
||||
Schritt") tragen im CMS `text-primary` und waren auf dem dunklen Band unlesbar – in beiden
|
||||
Welten. `.cta-band .text-primary` setzt sie auf den Akzent der Welt (Ocker bzw. Blau). Ocker-Text
|
||||
im Footer auf Sand nutzt die tiefe Stufe `--secondary-deep`. `--muted-foreground` ist ein
|
||||
abgedunkelter Clay, damit Fließtext auf Creme lesbar bleibt (Clay pur ≈ 2,9:1).
|
||||
|
||||
**Aufgeräumt:** Die Glow- und Schattenklassen in `shared-styles.css` (`glow-*`, `shadow-elegant`,
|
||||
`shadow-premium`, Button-Hover im Glass-Flow) hatten ein festes Blau (`rgba(0,136,204)`,
|
||||
`hsla(199 …)`) – jetzt hängen sie am Akzent der Welt. Karten im Glass-Flow nutzen `--card`
|
||||
statt festem Weiß.
|
||||
|
||||
### Logo je Welt
|
||||
|
||||
Zulieferung 07.09.2026 (`dev/2026-08-28/logos/`): das Interior-Logo in Tanne mit Ocker-Bogen
|
||||
(positiv) bzw. Creme mit Ocker-Bogen (negativ) und das B2In-Logo ohne Zusatz mit blauem Bogen.
|
||||
Beide Familien sind auf den Inhalt beschnitten (Interior `viewBox="152.5 94.5 289 228"`,
|
||||
B2In `viewBox="4.5 2 301 176"`) und liegen als SVG und PNG (240 px hoch) unter
|
||||
`public/img/logos/`; die Interior-Varianten zusätzlich unter `public/_cabinet/assets/` für das
|
||||
Display. `ThemeHelper::getLogoPath($type, $world)` liefert die Datei je Welt.
|
||||
|
||||
Header und Footer halten Welt und Logo-Pfad in `mount()` fest, weil ein späterer
|
||||
Livewire-Request keine Seitenroute mehr trägt. Der Interior-Footer liegt auf Sand und zeigt das
|
||||
**positive** Logo; der Immobilien-Footer bleibt dunkel mit dem negativen.
|
||||
|
||||
**Favicons** (`public/favicon/`, alle Größen plus `favicon.ico`) neu aus der Wortmarke in
|
||||
Tanne/Ocker erzeugt; `theme-color` auf Creme. Die bisher tote Referenz
|
||||
`img/favicons/b2in-favicon.ico` (`ThemeHelper::getFaviconPath()`) existiert jetzt.
|
||||
Mail-Kopf auf Tanne.
|
||||
|
||||
### Welten-Wechsler
|
||||
|
||||
Kopfzeile (Desktop rechts neben dem Sprachwechsel, mobil als erster Block im Menü) und Footer
|
||||
tragen einen Umschalter „Interior | Immobilien" (`b2in_worlds()`, Labels in `ui.php` de/en).
|
||||
Die aktive Welt ist gefüllt und trägt `aria-current="page"`.
|
||||
|
||||
### Tests
|
||||
|
||||
`tests/Feature/ColorWorldsTest.php` (neu, 14 Fälle): Body-Klasse und Logo je Welt für
|
||||
Startseite, `/immobilien`, Detailseite und weitere Seiten; `ThemeHelper::getLogoPath()` je Welt
|
||||
und für Fremd-Themes; Dateien und Farbwerte beider Logo-Familien; Wechsler in Kopf und Fuß mit
|
||||
korrekter Markierung; Tokens im Theme; kein festes Blau mehr in den Effekten.
|
||||
`HomePageStructureTest` an den neuen Zuschnitt und den Sand-Footer angepasst.
|
||||
|
||||
### Offen nach diesem Nachtrag
|
||||
|
||||
- **Freigabe Marcel:** Farbwerte sind laut Plan „blockierend für 1.1" und wurden wie geliefert
|
||||
umgesetzt. Die **Serife für Überschriften** (EB Garamond) ist eine Ableitung aus den
|
||||
Shop-Screenshots, kein Planpunkt – ein Token in `theme-b2in.css`, bei Ablehnung in einer Zeile
|
||||
zurück auf IBM Plex Sans.
|
||||
- **Bildwelt:** Die Farbwelt wirkt erst mit warmem Bildmaterial (Hero, Kacheln) vollständig; die
|
||||
Bestandsbilder aus der Immobilien-Zeit sind kühl. Zulieferung weiterhin offen.
|
||||
- **Display-Player:** nutzt die neuen Interior-Dateien automatisch; der Theme-Scrim im Player
|
||||
ist weiter neutral dunkel, keine Anpassung an Tanne vorgenommen.
|
||||
- Nächste Schritte laut Reihenfolge im Arbeitsplan: 1.2 Tischlerei-Markenseite (blockiert durch
|
||||
Name, Logo, Bilder), 1.3 Herkunfts-Sektion (blockiert durch CABINET-Klärung), 1.4 Akkordeon
|
||||
und Widget-Beschriftung.
|
||||
|
||||
---
|
||||
|
||||
## Nachtrag 7 · Saubere Trennung der beiden Welten (14.09.2026)
|
||||
|
||||
Abgestimmt mit Kevin am 14.09.: Nach der Farbwelt-Umstellung zeigte die Immobilien-Welt noch die
|
||||
Interior-Navigation, und das Magazin mischte Dubai- und Einrichtungsthemen. Entscheidung: **jede
|
||||
Welt hat ihre eigene Navigation, der Welten-Wechsler ist die einzige Brücke.** Umfang ist eine
|
||||
Erweiterung zu 1.1 des Arbeitsplans (nicht im freigegebenen Plan enthalten, Aufwand ca. 6–9 h).
|
||||
|
||||
### Was je Welt getrennt ist
|
||||
|
||||
| Bereich | Interior | Immobilien |
|
||||
|---|---|---|
|
||||
| Navigation (CMS) | `global.header`: Home · Marken · Referenzen · Magazin · Über B2in | `global.header_immobilien`: Immobilien · Projekte · Magazin · Über B2in |
|
||||
| Ankündigungsleiste (CMS) | `global.announcement_bar`: aktuelles Interior-Thema (Rotpunkt) | `global.announcement_bar_immobilien`: aktuelles Projekt (Azizi Creek Views 4, wie vor dem Relaunch), eigener Dismiss-Schlüssel |
|
||||
| Magazin | `/magazin` – nur Interior-Beiträge | `/immobilien/magazin` – nur Immobilien-Beiträge |
|
||||
| Über uns | `/about` (unverändert) | `/immobilien/ueber-b2in` – Inhalte aus der Zeit vor dem Relaunch, leicht angepasst, als „B2in Immobilien" gekennzeichnet, Claim „Connecting Design and Property" |
|
||||
| FAQ | `/faq` (unverändert) | `/immobilien/faq` – Fragen zu Investment, Reisen, Steuern, Einrichtung nach dem Kauf |
|
||||
| Kontakt | `/contact` (unverändert) | `/immobilien/kontakt` – gleiche Seite, eigener Einstieg und eigene Betreffe (Overlay `contact_form_immobilien` über `contact_form`), Mail-Betreff „Kontaktanfrage B2in Immobilien: …"; Kontakt-Button im Kopf und CTAs der Immobilienseite zeigen dorthin |
|
||||
| Footer-Links | wie bisher | FAQ, Über uns und Magazin zeigen auf die Immobilien-Seiten |
|
||||
| Terminanfrage-Widget | Anliegen offen | Anliegen „Immobilien" vorbelegt, Mail-Betreff „Terminanfrage B2in Immobilien" |
|
||||
|
||||
**Gemeinsam bleiben** Rechtstexte und die Cookie-Seiten. (Kontakt war zunächst gemeinsam, seit 15.09. je Welt.)
|
||||
|
||||
### Magazin: Beiträge haben eine Welt
|
||||
|
||||
Neues Feld `world` (`interior` | `immobilien`) an `cms_articles`, im Admin als Auswahl „Welt"
|
||||
mit Badge in der Liste. Die Migration ordnet die drei Dubai-Beiträge (Escrow, Al Jaddaf,
|
||||
Turnkey) der Immobilien-Welt zu; der Supply-Chain-Beitrag bleibt laut Abstimmung unter B2B in
|
||||
Interior. `CmsArticle::forFrontend($world)` filtert, Lang-Fallback inklusive.
|
||||
|
||||
Die **Detailseite behält ihre URL** (`/magazin/{slug}`, SEO und 301-Weiterleitungen bleiben),
|
||||
rendert aber in der Welt ihres Beitrags: die Route setzt die Welt per `b2in_world_set()` vor dem
|
||||
Rendern, weil Kopf- und Fußzeile sie in `mount()` festhalten. Verwandte Beiträge und der Rückweg
|
||||
zur Liste bleiben in derselben Welt.
|
||||
|
||||
### Technik
|
||||
|
||||
- `b2in_world()` prüft zuerst einen expliziten Override (`b2in_world_set()`), dann die Route.
|
||||
- Header: Navigation je Welt; der aktive Pfad wird in `mount()` festgehalten (vorher ging die
|
||||
Markierung bei jedem Livewire-Request verloren) und nur der spezifischste Eintrag ist aktiv
|
||||
(`/immobilien/magazin` markiert „Magazin", nicht „Immobilien").
|
||||
- `AboutHero`, `OurStory`, `OurValues` nehmen jetzt wie `ImageBreak` einen Sektionsschlüssel.
|
||||
- Kontaktformular: Betreffe in drei Gruppen (Interior · Immobilien · B2in, `form.subject_groups`).
|
||||
Die Kernmarken aus dem CMS stehen automatisch am Anfang der Interior-Gruppe, die Welt der Seite
|
||||
bestimmt, welche Gruppe oben steht. Betreff wird jetzt gegen die Liste validiert. Ältere
|
||||
CMS-Stände mit flacher `subjects`-Liste funktionieren weiter (Fallback ohne Gruppen).
|
||||
- Abschluss-CTA („Ihr nächster Schritt") je Welt: `global.cta_section_immobilien` mit eigenem Text
|
||||
und Link auf `/immobilien/kontakt`; greift automatisch auf allen Seiten der Immobilien-Welt.
|
||||
- Neue CMS-Sektionen (alle im Admin pflegbar): `header_immobilien`, `announcement_bar_immobilien`, `contact_form_immobilien`, `cta_section_immobilien`, `magazin_list_immobilien`,
|
||||
`faq_immobilien`, `immobilien_about_hero`, `immobilien_our_story`,
|
||||
`immobilien_about_image_break`, `immobilien_our_values`.
|
||||
- `WorldSplitCmsSeeder` legt die neuen Sektionen an und setzt **zwei** Interior-Sektionen aus
|
||||
den Lang-Dateien zurück: `header` (ohne „Immobilien") und `magazin_list` (ohne Dubai-Bezug).
|
||||
Lokal ausgeführt, beim Deployment mit erledigen (Abschnitt 8).
|
||||
|
||||
### Tests
|
||||
|
||||
`tests/Feature/WorldSplitTest.php` (neu, 18 Fälle): Navigation je Welt inkl. aktiver Markierung,
|
||||
Footer-Links, beide Magazinlisten, Detailseite in der Welt des Beitrags, verwandte Beiträge,
|
||||
CMS-Beiträge je Welt, Editor-Auswahl und -Validierung, Über-uns- und FAQ-Seite der
|
||||
Immobilien-Welt, Widget-Vorbelegung. `MagazinPageTest`, `HomePageStructureTest`,
|
||||
`LiveSiteReviewTest` und `CmsAdminTest` an die Trennung angepasst.
|
||||
|
||||
### Offen
|
||||
|
||||
- Navigationspunkt „Projekte" zeigt auf den Anker `#projekte` der Immobilienseite.
|
||||
- Die Immobilien-Seite selbst nutzt weiter feste Farben (`bg-zinc-950`, Status-Badges in
|
||||
emerald/sky/amber) außerhalb des Token-Systems – funktioniert in der blauen Welt, ist aber
|
||||
nicht themenfähig.
|
||||
- `LiveSiteReviewTest` prüft die Startseite noch auf das Wort „Netzwerk" (steht im Fließtext);
|
||||
kein Navigationspunkt.
|
||||
|
||||
---
|
||||
|
||||
## Nachtrag 8 · Terminanfrage-Buttons öffnen das Widget (15.09.2026)
|
||||
|
||||
Auf der Seite gibt es mehrere Sektionen mit „Termin anfragen"-Buttons (Hero, Weiche, Abschluss-CTA,
|
||||
`/marken`-Abschluss, Markenseiten, Referenzen, Immobilienseite). Bisher führten sie auf die
|
||||
Kontaktseite. Jetzt öffnen sie das schwebende Terminanfrage-Widget; von einer Markenseite aus ist
|
||||
die Marke im Anliegen vorbelegt. Die Anliegen-Auswahl des Widgets bleibt wie sie ist (Marken plus
|
||||
feste Einträge, keine Gruppen – die gibt es nur auf der Kontaktseite).
|
||||
|
||||
**Wie:** Blade-Komponente `<x-appointment-link :href :topic>`. Zeigt `href` auf `/contact` oder
|
||||
`/immobilien/kontakt`, trägt der Link `data-open-appointment` (und `data-appointment-topic`); ein
|
||||
Klick-Delegat in `resources/js/app.js` fängt ihn ab und feuert `open-appointment`, das Widget
|
||||
öffnet sich, übernimmt das Anliegen (nur wenn es in der Auswahl existiert) und fokussiert das
|
||||
Namensfeld. Ohne JavaScript bleibt es ein normaler Link; zeigt der CMS-Link woandershin, bleibt
|
||||
er ein gewöhnlicher Link. Der Kontakt-Button im Kopf bleibt bewusst ein Link zur Kontaktseite.
|
||||
|
||||
**Formularfelder** in Widget und Kontaktformular tragen jetzt die tokenbasierten Klassen
|
||||
`.form-field` / `.form-select` (shared-styles.css): Kartenton als Grund, Fokusring im Akzent der
|
||||
Welt, eigener Chevron und lesbare Gruppenüberschriften in der Betreff-Auswahl – statt
|
||||
Tailwind-Grau.
|
||||
|
||||
**Tests:** `tests/Feature/AppointmentTriggerTest.php` (9 Fälle).
|
||||
|
||||
---
|
||||
|
||||
## Nachtrag 9 · Tischlerei als vierte Kernmarke, Platzhalter (15.09.2026)
|
||||
|
||||
Arbeitsplan 14.09., Punkt 1.2. Name, Logo, Basisinfos und Bilder fehlen noch (blockierend laut
|
||||
Plan), Platzhalter sind ausdrücklich erlaubt. Die Marke ist deshalb als **Denkanstoß** angelegt:
|
||||
|
||||
- **Marke `tischlerei`** im `CmsBrandSeeder` (vierter Eintrag, wird bei bestehenden Marken nicht
|
||||
doppelt angelegt). Inhalte entlang der Ausrichtung aus dem Plan – Aufbau, Sonderlösungen,
|
||||
Veredelung, Maßarbeit; ergänzt CABINET und Rotpunkt, steht nicht daneben; Werkstatt in
|
||||
Ostwestfalen ohne Nennung des Partnerbetriebs. Drei Abschnitte mit Vertiefungen, eigener
|
||||
Abschlussblock, Widget-Beschriftung „Persönliche Beratung zur Maßarbeit vereinbaren“.
|
||||
- **Ohne Logo**: der Seitenkopf zeigt den Schriftzug, Kacheln kommen ohne Logo aus. Kein
|
||||
Stimmungsbild, die Kacheln zeigen den Platzhalter.
|
||||
- **Kachelraster** der Startseite: `sm:grid-cols-2` – 2×2 auf allen Breiten. Vier nebeneinander
|
||||
war kurz drin, wurde aber als zu schmal verworfen (Abstimmung 15.09.). „Weitere Marken“ auf den
|
||||
Detailseiten: drei nebeneinander ab `lg`.
|
||||
- Automatisch dabei: `/marken`, Anliegen im Widget, Betreff-Gruppe Interior der Kontaktseite.
|
||||
- Seitentitel von `/marken` nennt die vierte Marke.
|
||||
- Lang-Fallback (`core_brands.brands`) um die Tischlerei ergänzt.
|
||||
|
||||
**Lokal ausgeführt:** `php artisan db:seed --class=CmsBrandSeeder` (legt nur die fehlende Marke an).
|
||||
Beim Deployment mit erledigen (Abschnitt 8).
|
||||
|
||||
**Was Marcel nachträgt (alles im Admin):** Name der Marke, Logo, Stimmungsbild, echte Texte –
|
||||
die Platzhalter sind bewusst so geschrieben, dass sie ohne Änderung nicht live gehen sollten
|
||||
(„Tischlerei“ ist kein Markenname). Slug `tischlerei` bleibt stabil, auch wenn der Name kommt.
|
||||
|
||||
**Tests:** `tests/Feature/FourthBrandTest.php` (7 Fälle).
|
||||
|
||||
---
|
||||
|
||||
## Nachtrag 10 · Herkunfts-Sektion „Aus der Region gefertigt“ (15.09.2026)
|
||||
|
||||
Arbeitsplan 14.09., Punkt 1.3. Die Klärung „CABINET Hersteller oder Vertrieb“ kam am 15.09. von
|
||||
Kevin nach seinem Verständnis: CABINET **fertigt selbst in Köln** und **vertreibt über die
|
||||
CABINET Stores**; der Store Bielefeld ist einer davon. Dort laufen Beratung und Planung für
|
||||
Bielefeld und Umkreis, gefertigt wird in Köln, aufgebaut beim Kunden – und hier kommt die
|
||||
Tischlerei ins Spiel, die den Aufbau übernimmt und Sonderwünsche umsetzt. ⚠️ **Von Marcel
|
||||
bestätigen lassen** (bietet CABINET selbst Aufbau an? Läuft der Aufbau immer über die eigene
|
||||
Tischlerei?). Die Sektion ist so formuliert, dass sie diesem Verständnis folgt.
|
||||
|
||||
### Aufbau
|
||||
|
||||
- **Neue Sektion `origin_section`** (`OriginSection`, CMS-Gruppe `home`), eingebunden auf der
|
||||
Startseite zwischen Kernmarken und „Gebaut auf Expertise“ sowie auf `/marken` zwischen
|
||||
Markenübersicht und „Mehr als Marken: unser Netzwerk“. Anker `#herkunft`.
|
||||
- **Schematische Karte** (SVG, ab `md`): Bielefeld als Drehscheibe in Ocker, die vier Standorte
|
||||
als Punkte in Tanne mit gestrichelten Speichen und Entfernungsangabe, gestrichelte Ellipse
|
||||
„Ostwestfalen“ um die drei OWL-Standorte. Keine Geografie, nur Richtung und Entfernung.
|
||||
Positionen und Beschriftungsseite lagen zunächst im Inhalt; seit Nachtrag 11 stehen sie im
|
||||
Code, die Standorte kommen aus den Marken. Unter `md` entfällt die Karte, die Kacheln tragen alles.
|
||||
- **Vier Standort-Kacheln** (2×2 ab `sm`): Stadt, Marke, Rolle, Entfernung „von Bielefeld“ und
|
||||
Link „Zur Marke“. Ursprünglich lagen die Standorte im Sektions-JSON, der Link kam aus den
|
||||
veröffentlichten Marken. Seit Nachtrag 11 gilt: Standort = Marke, siehe dort.
|
||||
- **Ablauf-Block „Gefertigt in Köln, geplant in Bielefeld“**: drei nummerierte Schritte am
|
||||
Beispiel CABINET (Beratung und Planung in Bielefeld → Fertigung in Köln → Aufbau vor Ort mit
|
||||
Sonderwünschen aus der Tischlerei). Das ist die saubere Trennung „gefertigt“ / „vertrieben“
|
||||
aus dem Plan. Entfällt, wenn `steps` im CMS leer ist.
|
||||
- **Entfernungen** gerundet, Straße ab Bielefeld Mitte: Herford rund 20 km, Bünde rund 30 km,
|
||||
Rheda-Wiedenbrück rund 35 km, Köln rund 200 km. Bei Bedarf im Admin anpassen.
|
||||
- Styles `.origin-map-*` in `shared-styles.css` (Token-basiert, in beiden Welten gültig, die
|
||||
Sektion liegt aber nur in der Interior-Welt). Build `npm run build:b2in` ausgeführt.
|
||||
|
||||
**Lokal ausgeführt:** `php artisan db:seed --class=OriginSectionCmsSeeder` (legt nur an, was
|
||||
fehlt, überschreibt nichts). Beim Deployment mit erledigen (Abschnitt 8, Schritt 9).
|
||||
|
||||
**Offen:** Bestätigung des CABINET-Ablaufs durch Marcel; Wortlaut der Tischlerei-Kachel folgt
|
||||
dem Platzhalter aus Nachtrag 9 und wandert mit dem echten Markennamen.
|
||||
|
||||
**Tests:** `tests/Feature/OriginSectionTest.php` (12 Fälle, mit Nachtrag 11 umgeschrieben).
|
||||
|
||||
---
|
||||
|
||||
## Nachtrag 11 · Standorte an der Marke, Marken in der Sidebar (15.09.2026)
|
||||
|
||||
Prüfung der Inhaltspflege im Admin auf Kevins Frage, ob Marken wie das Magazin einen eigenen
|
||||
Punkt brauchen. Ergebnis: **Die Markenverwaltung gab es schon** („Kernmarken verwalten“, AP2),
|
||||
sie war nur nicht in der Sidebar verlinkt – erreichbar allein über die Kachel auf der
|
||||
CMS-Übersicht. Die Herkunfts-Sektion aus Nachtrag 10 lag dagegen komplett im generischen
|
||||
Inhalte-Editor, inklusive der Kartenkoordinaten `x`/`y` und der Beschriftungsseite als nackte
|
||||
Felder. Das war für Marcel nicht pflegbar. Abgestimmt mit Kevin: **Weg 1**, Standort an die Marke.
|
||||
|
||||
### Was sich ändert
|
||||
|
||||
- **Sidebar:** neuer Eintrag „Marken“ unter CMS → Website-Inhalte, zwischen Projekte und
|
||||
Magazin. Die Gruppe klappt auf der Markenseite jetzt auch auf.
|
||||
- **Drei neue Felder je Marke** (Migration `add_origin_to_cms_brands_table`, übersetzbar wie die
|
||||
übrigen Textfelder): `origin_city` (Fertigungsort), `origin_distance` (Entfernung als Text,
|
||||
„rund 30 km“), `origin_role` (ein Satz zur Rolle). Im Admin als Block „Fertigungsort“ unter
|
||||
den Stammdaten, mit Hinweis: ohne Ort erscheint die Marke nicht in der Sektion.
|
||||
- **`OriginSection` liest die Standorte aus `CmsBrand::forFrontend()`**: nur veröffentlichte
|
||||
Marken mit Ort, in der Reihenfolge der Marken. Eine unveröffentlichte Marke fällt damit ganz
|
||||
heraus (vorher blieb ihr Standort ohne Link stehen – jetzt gibt es keinen Standort ohne
|
||||
Marke mehr). Der Link „Zur Marke“ hängt an `has_detail_page`, der Text `link_text` liegt
|
||||
einmal auf Sektionsebene statt je Standort.
|
||||
- **Kartenpositionen im Code:** `OriginSection::MAP_POSITIONS`, je Slug `x`/`y`/`label`. Eine
|
||||
neue Marke mit Ort, aber ohne Position, erscheint in den Kacheln, nicht in der Karte – dafür
|
||||
muss ein Entwickler eine Position ergänzen. Das ist gewollt: die Karte ist Zeichnung, kein
|
||||
Inhalt.
|
||||
- **Sektions-JSON `home/origin_section`** enthält nur noch Überschrift, Untertitel, Alt-Text,
|
||||
Regionslabel, Entfernungs-Suffix, Drehscheibe, Link-Text und Ablauf-Block. Der alte Schlüssel
|
||||
`locations` wird von `OriginSectionCmsSeeder` aus einem bestehenden Eintrag entfernt, damit er
|
||||
im Inhalte-Editor nicht mehr als Pflegefeld auftaucht (idempotent, sonst weiterhin nur
|
||||
anlegen-wenn-fehlt).
|
||||
- **Lang-Fallback:** die vier Einträge in `core_brands.brands` tragen jetzt `slug` und die drei
|
||||
Herkunftsfelder, damit die Sektion auch ohne gepflegte Marke steht – dann ohne Links, wie die
|
||||
Kacheln der Kernmarken.
|
||||
- **Seeder:** `CmsBrandSeeder` bringt die Herkunft für frische Installationen mit; neu
|
||||
`BrandOriginSeeder` trägt sie bei bestehenden Marken nach, nur wenn der Ort leer ist (Muster
|
||||
wie `BrandLogoSeeder`). Keine Dubletten möglich, beide arbeiten über den Slug.
|
||||
- Ohne Marke mit Ort bleiben Überschrift und Ablauf-Block stehen, Karte und Kacheln entfallen.
|
||||
|
||||
**Lokal ausgeführt:** `php artisan migrate`, `db:seed --class=BrandOriginSeeder` (4 gesetzt),
|
||||
`db:seed --class=OriginSectionCmsSeeder` (`locations` entfernt). Beim Deployment: Abschnitt 8,
|
||||
Schritte 9 und 10, die Migration läuft mit `migrate` vorweg.
|
||||
|
||||
**Offen:** wie Nachtrag 10 (Bestätigung des CABINET-Ablaufs durch Marcel). Der generische
|
||||
Inhalte-Editor bleibt für die übrigen Sektionen ohne Schema; wo Listen mit technischen Feldern
|
||||
auftauchen, ist derselbe Weg möglich: Daten an ein Modell hängen, Darstellung in den Code.
|
||||
|
||||
**Tests:** `tests/Feature/OriginSectionTest.php` (15 Fälle), drei neue Fälle in
|
||||
`tests/Feature/Admin/Cms/CmsBrandsIndexTest.php` (Herkunft je Sprache speichern, Ort leeren,
|
||||
Sidebar-Link).
|
||||
|
||||
---
|
||||
|
||||
## Nachtrag 12 · Inhalte-Editor: Seitenbaum und Vorschau (15.09.2026)
|
||||
|
||||
Kevins Rückmeldung nach der Prüfung des CMS: der Punkt „Inhalte“ zeigt Datenbank-Gruppen
|
||||
alphabetisch, ohne Bezug zur Seite, mit JSON-Schlüsseln als Beschriftung – zu abstrakt für
|
||||
Marcel. Abgestimmt: Stufe 1 (Seitenbaum, Klartext) und Stufe 2 (Vorschau im Rahmen) umsetzen,
|
||||
Stufe 3 (Stift-Markierungen in der Vorschau, die den Editor öffnen) erst nach Marcels Feedback.
|
||||
|
||||
### Stufe 1 · Seitenbaum statt Gruppen
|
||||
|
||||
- **Registry `config/cms_pages.php`:** je Seite (Startseite, Marken, Referenzen, Magazin, Über
|
||||
uns, Kontakt, FAQ, „Auf jeder Seite“, Immobilien-Welt, Rechtliches) die Sektionen in der
|
||||
Reihenfolge, wie sie auf der Website stehen, mit Titel, einem Satz Beschreibung, Pfad und
|
||||
Sprungmarke. Dazu ein Wörterbuch `field_labels` (`button_text` → „Button-Beschriftung“,
|
||||
`x`/`y` → „Position (Entwickler)“ …), je Sektion überschreibbar über `fields`.
|
||||
- **`App\Services\CmsPageTree`** verbindet die Registry mit der Datenbank: welche Sektion
|
||||
existiert (`content_id`), welche fehlt, was in der Datenbank liegt, aber auf keiner Seite
|
||||
eingetragen ist. Letzteres landet als Sammelpunkt **„Nicht mehr verwendet“** ganz unten
|
||||
(Netzwerk-, Partner- und alte Immobilien-Sektionen, `home/synergie_section` & Co.) – nichts
|
||||
geht verloren, nichts steht im Weg.
|
||||
- **Editor (`admin.cms.content-index`):** linke Spalte zeigt den Seitenbaum, nummeriert von
|
||||
oben nach unten. Ein Klick wählt genau eine Sektion (`selectSection`), der Kopf zeigt Titel,
|
||||
Beschreibung, Seite und den technischen Schlüssel klein darunter. Felder tragen die
|
||||
Klartext-Beschriftung, der Schlüssel steht als `code` daneben; Typ-Badges heißen „Text“,
|
||||
„Formatierter Text“, „Bild“, „Liste“. Der Listen-Editor (Overlay) beschriftet seine Felder
|
||||
genauso. `selectGroup` bleibt für die Gruppenansicht erhalten.
|
||||
- **Fehlende Sektion anlegen:** steht eine Sektion in der Registry, aber nicht in der
|
||||
Datenbank (heute z. B. das Terminanfrage-Widget), erscheint sie grau mit „+“. Ein Klick
|
||||
legt sie über `CmsContentSeeder` mit den Texten aus `b2in.php` an. Rechtliches und die
|
||||
Kontaktdaten (`global/contact`) haben keine Vorlage, dort erscheint ein Hinweis.
|
||||
|
||||
### Stufe 2 · Vorschau
|
||||
|
||||
- Rechts neben dem Editor ein **iframe mit der echten Website** (Domain aus
|
||||
`config('domains')`, Pfad der Seite, `?cms-preview=<n>` als Cache-Brecher, `#anker`). Beim
|
||||
Wechsel der Sektion springt die Vorschau zur Sprungmarke, nach jedem Speichern lädt sie
|
||||
neu (`previewVersion`). Buttons: neu laden, in neuem Tab öffnen, Vorschau aus/ein.
|
||||
Keine Anmeldung nötig, die Website ist öffentlich; Frame-Header setzt die App keine.
|
||||
- **Sprungmarken nachgezogen:** `id` an den Wurzelelementen der lebenden Sektionen (`hero`,
|
||||
`gruender`, `wege`, `marken`, `herkunft`, `vision`, `engagement`, `displays`, `immobilien`,
|
||||
`cta`, `seitenkopf`, `netzwerk`, `wachstum`, `marken-cta`, `projekte`, `magazin`,
|
||||
`geschichte`, `bild`, `werte`, `kontakt`, `faq`, `header`, `footer`). Beim Display-Feed
|
||||
sitzt der Anker am Wurzel-`div`, weil die Sektion ohne veröffentlichtes Display leer bleibt.
|
||||
- Sektionen ohne Seite (Beitragsseite, Widget, Rechtliches) zeigen die Seite ohne Sprung.
|
||||
|
||||
### Erkenntnisse beim Kartieren
|
||||
|
||||
- `/immobilien` rendert `web/dev/immobilien-azizi.blade.php` mit statischen Texten aus
|
||||
`immobilien-azizi.json`; die CMS-Sektionen `immobilien/hero`, `warum_dubai`, `kaufprozess`
|
||||
usw. gehören zur stillgelegten `web/immobilien.blade.php` und stehen deshalb unter „Nicht
|
||||
mehr verwendet“. Gleiches gilt für `about/leadership_team`.
|
||||
- `display_feed` liegt in der Gruppe `referenzen`, steht aber zuerst auf der Startseite –
|
||||
im Baum unter Startseite mit Hinweis auf /referenzen.
|
||||
|
||||
**Tests:** `tests/Feature/Cms/CmsPageTreeTest.php` (9 Fälle, darunter: jede Sprungmarke der
|
||||
Registry existiert auf der jeweiligen Seite, jede Sektion mit Vorlage ist in `b2in.php`
|
||||
auffindbar) und sechs neue Fälle in `tests/Feature/Cms/CmsAdminTest.php` (Seitenbaum,
|
||||
Sektion wählen mit Klartext-Labels und Vorschau-Adresse, Sammelpunkt, Anlegen aus Vorlage,
|
||||
Anlegen ohne Vorlage, Vorschau ausblenden). Kein Deployment-Schritt nötig: Registry und
|
||||
Editor sind Code, die Datenbank bleibt unverändert.
|
||||
|
||||
**Offen:** Stufe 3 nach Marcels Rückmeldung. Neue Sektionen müssen in `cms_pages.php`
|
||||
eingetragen werden, sonst landen sie unter „Nicht mehr verwendet“ – als Erinnerung steht das
|
||||
im Kopf der Datei.
|
||||
|
||||
---
|
||||
|
||||
## Nachtrag 13 · Referenzen als eigenes Modul, Immobilien-Projekte ausgeblendet (15.09.2026)
|
||||
|
||||
Kevin nach der Abnahme von Nachtrag 12: „Projekte“ im Portal (die Immobilienprojekte aus
|
||||
Dubai) ist nicht freigegeben und kommt raus; an die Stelle gehören die Projekte/Referenzen der
|
||||
Interior-Welt. Das ist eine **Korrektur zu AP5**, das die Referenzen bewusst aus den
|
||||
Markengalerien zusammengesammelt hatte, um keine zweite Pflegestelle zu schaffen. Ein
|
||||
realisiertes Projekt hat aber mehr als ein Bild und oft mehr als eine Marke (CABINET plus
|
||||
Tischlerei), darum jetzt ein eigener Eintrag.
|
||||
|
||||
### Was sich ändert
|
||||
|
||||
- **Neues Modell `CmsReference`** (Tabelle `cms_references`): Titel und Beschreibung
|
||||
übersetzbar, Ort und Jahr als Text, Titelbild, weitere Bilder mit Bildunterschrift,
|
||||
beteiligte Marken als Liste von Slugs aus `cms_brands`, Veröffentlicht, Sortierung.
|
||||
- **Portal → CMS → „Referenzen“** (`admin.cms.references-index`, Route
|
||||
`cms.references.index`): Liste mit Vorschaubild, Marken-Badges und Bildanzahl; Formular
|
||||
nach dem Muster des Magazins mit Sprachumschalter, Marken als Checkboxen, Titelbild und
|
||||
Galerie über den Media-Picker, Galeriebilder sortierbar. Der Kopf der Seite sagt die Regel:
|
||||
nur ausgeführte Aufträge, Entwürfe gehören in die Galerie der Marke.
|
||||
- **Sidebar und CMS-Übersicht:** „Referenzen“ ersetzt „Projekte“. Die Route
|
||||
`cms.projects.index` bleibt bestehen und ist per Adresse erreichbar, nur nicht mehr
|
||||
verlinkt – die Dubai-Daten bleiben unangetastet, falls die Freigabe später kommt.
|
||||
- **`/referenzen`** zeigt die veröffentlichten Referenzen: Titelbild, Titel, „Ort · Jahr“,
|
||||
Beschreibung, weitere Bilder als kleine Reihe, darunter die beteiligten Marken verlinkt auf
|
||||
ihre Markenseiten (unveröffentlichte Marken fallen heraus). Ohne Titelbild dient das erste
|
||||
Galeriebild als Titelbild, ohne jedes Bild wird die Referenz übersprungen.
|
||||
- **Fallback bleibt:** Solange keine Referenz veröffentlicht ist, greift der Stand aus AP5
|
||||
(Bilder der Rubrik „Realisiertes Projekt“ aus den Markengalerien). Der Leerzustand von AP5
|
||||
gilt weiter, wenn beides leer ist. So sieht die Preview beim Deployment keinen Unterschied,
|
||||
bis Marcel die erste Referenz anlegt.
|
||||
- Registry `cms_pages.php`: Beschreibung von „Realisierte Projekte“ verweist auf das neue Modul.
|
||||
|
||||
**Deployment:** nur `php artisan migrate --force` (Abschnitt 8.2, Schritt 1 deckt die neue
|
||||
Tabelle ab). Kein Seeder, keine Daten.
|
||||
|
||||
**Offen:** Marcel legt die ersten Referenzen selbst an; die bisherigen Galeriebilder der Marken
|
||||
bleiben dort, wo sie sind. Ob die Bilder aus den Markengalerien später automatisch als Referenz
|
||||
übernommen werden sollen, klärt sich mit den ersten echten Einträgen.
|
||||
|
||||
**Tests:** `tests/Feature/Admin/Cms/CmsReferencesIndexTest.php` (9 Fälle), vier neue Fälle in
|
||||
`tests/Feature/ReferenzenPageTest.php` (Referenzen vor Galerien, Entwurf ausgeblendet und
|
||||
Fallback, unveröffentlichte Marke und Referenz ohne Bild, Galeriebild als Titelbild),
|
||||
Dashboard-Statistik in `CmsAdminTest` auf Referenzen umgestellt.
|
||||
|
||||
---
|
||||
|
||||
## Nachtrag 14 · Bilddauer im b2in-Display mit Nachkommastelle (17.09.2026)
|
||||
|
||||
Kevins Wunsch: Im Store-Display-Modul „b2in Display“ soll die „Dauer (Sekunden)“ eines Bildes
|
||||
auch 1,5 annehmen. Das Feld war ganzzahlig (`int`, Zahlenfeld mit `min=1`).
|
||||
|
||||
- **Eingabe als Text** mit Dezimal-Tastatur: „10“, „1,5“ und „1.5“ sind gültig, Leerzeichen
|
||||
egal, gerundet auf Zehntel. Erlaubt sind 0,5 bis 120 Sekunden; alles andere bringt beim
|
||||
Speichern eine Fehlermeldung am Feld und speichert nichts. Bei Videos wird das Feld ignoriert.
|
||||
- **Gespeichert wird eine Zahl** (`duration_seconds` = 1.5). Der Player in
|
||||
`public/_cabinet/b2in/index.html` rechnet ohnehin `duration_seconds × 1000`, kommt also ohne
|
||||
Änderung mit 1500 ms klar. Bestehende Einträge mit ganzen Sekunden bleiben unverändert.
|
||||
- Anzeige in der Eintragsliste mit Komma („1,5s“), ganze Zahlen ohne Nachkommastelle.
|
||||
- Doku `public/_cabinet/_docs/b2in-displays.md` ergänzt.
|
||||
|
||||
**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.
|
||||
|
|
|
|||
83
dev/2026-08-28/feedback-17-09-2026.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#1
|
||||
|
||||
Die Richtung passt deutlich besser: locker, verständlich und ohne typischen Werbeprospekt-Ton.
|
||||
|
||||
Besonders gut finde ich den Einstieg „Für den Anfang reicht uns ein Bild und ein grobes Maß völlig aus.“ Der nimmt sofort eine Hürde. Auch „Sie müssen also nicht erst alles millimetergenau ausmessen“ spricht genau die Frage an, die ein Kunde wahrscheinlich im Kopf hat.
|
||||
|
||||
Nur zwei Stellen würde ich noch ändern: „valides Angebot“ klingt im ansonsten lockeren Text etwas technisch. Ich würde „ein wirklich brauchbares Angebot“ oder „ein konkretes Angebot“ schreiben. Und statt „zu Ihnen nach Hause“ lieber „bei Ihnen vor Ort“, weil ihr ja nicht ausschließlich Privatwohnungen plant.
|
||||
|
||||
Dann wäre meine Fassung:
|
||||
|
||||
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.
|
||||
|
||||
So läuft’s:
|
||||
|
||||
Foto + grobe Maße → Beratung → Planung & Angebot → Aufmaß vor Ort → Feinplanung → Bestellung
|
||||
|
||||
Das wäre für mich ziemlich genau die richtige Mischung aus persönlich, unkompliziert und trotzdem hochwertig.
|
||||
|
||||
|
||||
|
||||
#2 Febrü
|
||||
|
||||
Ja. Ich würde wirklich nur die vereinbarten Stellen ändern und den Rest der Seite lassen. Die aktuelle Seite hat diese Passagen an den entsprechenden Stellen.
|
||||
|
||||
Bei uns gibt es Kunden – keine Schubladen
|
||||
|
||||
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.
|
||||
|
||||
Ob im Kleinen oder im Großen – die Detailtreue entscheidet.
|
||||
|
||||
Aus Herford. Für moderne Arbeitswelten.
|
||||
|
||||
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.
|
||||
|
||||
Das ist auch sachlich sauber: Febrü selbst spricht aktuell von einer hohen Fertigungstiefe und nennt auf der Unternehmensseite weiterhin 95 %. Mit „sehr hohe Fertigungstiefe“ seid ihr also auf der sicheren Seite, ohne euch an eine konkrete Zahl zu binden.
|
||||
|
||||
Wertschätzung ist keine Frage der Stückzahl
|
||||
|
||||
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 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.
|
||||
|
||||
Unverändert lassen
|
||||
|
||||
„Nicht drei Marken. Ein durchgängiger Raum.“ würde ich komplett so stehen lassen. Das Zusammenspiel von Febrü, Rotpunkt, CABINET und B2in ist dort sehr gut erklärt.
|
||||
|
||||
„Das Wir und das Ich“ ebenfalls unverändert lassen. Gerade der Einstieg „Ja, ja – das könnte man unter dem üblichen Verkaufs-Blabla abheften“ gibt der Seite Persönlichkeit und passt zum restlichen Ton.
|
||||
|
||||
Beim doppelten Satz „Ob im Kleinen oder im Großen – die Detailtreue entscheidet.“ würde ich die erste Version oben stehen lassen und die zweite Version nach „Erleben statt nur aus dem Katalog auswählen“ löschen. Aktuell steht er tatsächlich zweimal auf der Seite.
|
||||
|
||||
Den mitlaufenden CTA würde ich ebenfalls nicht verändern.
|
||||
|
||||
|
||||
#3 Rotpunk
|
||||
|
||||
#a text erstezen "Mehr als Küchen"
|
||||
Mehr als Küchen
|
||||
|
||||
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.
|
||||
|
||||
#b Thema Handwerk
|
||||
Das Thema Handwerk am Ende von Rotpunkt sollte so formuliert sein, dass es bei jeder Marke als letzte Ergänzung angezeigt wird
|
||||
|
||||
#3 Cabinet
|
||||
"Die Fragen die wir vorher stellen"
|
||||
zweiter Absatz ersetzen:
|
||||
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.
|
||||
36
dev/2026-08-28/logos/B2In-Logo-negativ-2025-09-22_RGB.svg
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 29.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" x="0px" y="0px"
|
||||
viewBox="0 0 310 180" style="enable-background:new 0 0 310 180;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#2C9FDA;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M5,177.5V67.9h43.1c7.9,0,14.6,1.2,20,3.6c5.3,2.4,9.3,5.7,12,9.9c2.6,4.2,4,9.1,4,14.6
|
||||
c0,4.3-0.8,8-2.5,11.3c-1.7,3.2-4,5.8-7,7.8s-6.2,3.5-10.1,4.3v1.1c4.1,0.1,7.9,1.3,11.5,3.5s6.6,5.2,8.8,9.1s3.4,8.6,3.4,14
|
||||
c0,5.8-1.4,10.9-4.2,15.6c-2.9,4.6-7,8.1-12.5,10.8c-5.5,2.6-12.4,3.9-20.6,3.9L5,177.5L5,177.5z M27.2,114h17.6
|
||||
c3.2,0,6-0.5,8.6-1.7c2.5-1.2,4.6-2.8,5.9-5c1.5-2.1,2.1-4.7,2.1-7.6c0-4-1.4-7.2-4.2-9.7c-2.8-2.5-6.8-3.7-12-3.7h-18L27.2,114
|
||||
L27.2,114z M27.2,158.9h19.3c6.6,0,11.3-1.3,14.3-3.8c3-2.5,4.6-5.9,4.6-10.1c0-3.1-0.7-5.8-2.2-8.1c-1.5-2.3-3.6-4.2-6.3-5.6
|
||||
c-2.8-1.4-5.9-2-9.7-2H27.1v29.6H27.2z"/>
|
||||
<path class="st0" d="M103,177.5v-16.2l38.5-36.4c3.3-3.3,6.1-6.1,8.4-8.8s4-5.2,5.2-7.7c1.2-2.5,1.8-5.3,1.8-8.1
|
||||
c0-3.3-0.7-6.1-2.2-8.5s-3.5-4.1-6-5.4c-2.5-1.3-5.4-1.9-8.7-1.9c-3.4,0-6.3,0.6-8.8,2c-2.5,1.4-4.4,3.3-5.8,5.9
|
||||
c-1.4,2.5-2,5.6-2,9.2H102c0-7.1,1.6-13.3,4.9-18.5c3.3-5.3,7.7-9.3,13.4-12.2c5.7-2.9,12.4-4.3,19.9-4.3
|
||||
c7.7,0,14.4,1.4,20.1,4.1c5.7,2.8,10.2,6.6,13.4,11.4c3.2,4.9,4.8,10.5,4.8,16.7c0,4-0.7,8-2.3,12.1c-1.6,4-4.4,8.5-8.5,13.3
|
||||
c-4,5-9.8,10.8-17.2,17.7l-16.4,16.3v0.8H180v18.4L103,177.5L103,177.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M204.2,177.2V98.8h19.3v78.4H204.2z"/>
|
||||
<path class="st0" d="M257.2,177.2h-19.3V98.8h19.3V112h0.7c1.6-4.2,4.1-7.7,7.6-10.6s8.3-4.2,14.4-4.2c8,0,14.2,2.6,18.5,8
|
||||
c4.3,5.4,6.6,12.9,6.6,22.6v49.5h-19.3v-47.6c0-5.6-1-9.8-3-12.6c-2-2.8-5.3-4.2-9.9-4.2c-2.6,0-5.1,0.5-7.5,1.4
|
||||
c-2.4,1-4.3,2.3-5.8,4.2c-1.5,1.9-2.2,4.2-2.2,7v51.7H257.2z"/>
|
||||
</g>
|
||||
<path class="st1" d="M213.8,61c2.6,0,5,0.3,7,0.8C206,27.1,168.9,2.5,125.6,2.5c-43.9,0-81.3,25.3-95.8,60.6H45
|
||||
c9.3-32.6,41.9-56.6,80.5-56.6c38.2,0,70.5,23.5,80.2,55.6C208,61.4,210.6,61,213.8,61z"/>
|
||||
<path class="st0" d="M213.8,88.3c-3.9,0-6.8-1-8.6-2.8c-1.8-1.8-2.8-4.1-2.8-6.9v-3c0-2.8,1-5.1,2.8-6.9c1.8-1.8,4.7-2.8,8.6-2.8
|
||||
s6.8,1,8.6,2.8c1.8,1.8,2.8,4.1,2.8,6.9v3c0,2.8-1,5-2.8,6.9C220.6,87.4,217.7,88.3,213.8,88.3z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
36
dev/2026-08-28/logos/B2In-Logo-positiv-2025-09-22_RGB.svg
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 29.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" x="0px" y="0px"
|
||||
viewBox="0 0 310 180" style="enable-background:new 0 0 310 180;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#314052;}
|
||||
.st1{fill:#2C9FDA;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M5,177.5V67.9h43.1c7.9,0,14.6,1.2,20,3.6c5.3,2.4,9.3,5.7,12,9.9c2.6,4.2,4,9.1,4,14.6
|
||||
c0,4.3-0.8,8-2.5,11.3c-1.7,3.2-4,5.8-7,7.8c-3,2-6.2,3.5-10.1,4.3v1.1c4.1,0.1,7.9,1.3,11.5,3.5s6.6,5.2,8.8,9.1
|
||||
c2.2,3.9,3.4,8.6,3.4,14c0,5.8-1.4,10.9-4.2,15.6c-2.9,4.6-7,8.1-12.5,10.8c-5.5,2.6-12.4,3.9-20.6,3.9L5,177.5L5,177.5z
|
||||
M27.2,114h17.6c3.2,0,6-0.5,8.6-1.7c2.5-1.2,4.6-2.8,5.9-5c1.5-2.1,2.1-4.7,2.1-7.6c0-4-1.4-7.2-4.2-9.7
|
||||
c-2.8-2.5-6.8-3.7-12-3.7h-18L27.2,114L27.2,114z M27.2,158.9h19.3c6.6,0,11.3-1.3,14.3-3.8c3-2.5,4.6-5.9,4.6-10.1
|
||||
c0-3.1-0.7-5.8-2.2-8.1c-1.5-2.3-3.6-4.2-6.3-5.6c-2.8-1.4-5.9-2-9.7-2H27.1v29.6H27.2z"/>
|
||||
<path class="st0" d="M103.1,177.5v-16.2l38.5-36.4c3.3-3.3,6.1-6.1,8.4-8.8c2.2-2.6,4-5.2,5.2-7.7c1.2-2.5,1.8-5.3,1.8-8.1
|
||||
c0-3.3-0.7-6.1-2.2-8.5s-3.5-4.1-6-5.4c-2.5-1.3-5.4-1.9-8.7-1.9c-3.4,0-6.3,0.6-8.8,2c-2.5,1.4-4.4,3.3-5.8,5.9
|
||||
c-1.4,2.5-2,5.6-2,9.2h-21.3c0-7.1,1.6-13.3,4.9-18.5c3.3-5.3,7.7-9.3,13.4-12.2c5.7-2.9,12.4-4.3,19.9-4.3
|
||||
c7.7,0,14.4,1.4,20.1,4.1c5.7,2.8,10.2,6.6,13.4,11.4c3.2,4.9,4.8,10.5,4.8,16.7c0,4-0.7,8-2.3,12.1c-1.6,4-4.4,8.5-8.5,13.3
|
||||
c-4,5-9.8,10.8-17.2,17.7l-16.4,16.3v0.8h45.9v18.4L103.1,177.5L103.1,177.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M204.2,177.2V98.8h19.3v78.4H204.2z"/>
|
||||
<path class="st0" d="M257.2,177.2h-19.3V98.8h19.3V112h0.7c1.6-4.2,4.1-7.7,7.6-10.6s8.3-4.2,14.4-4.2c8,0,14.2,2.6,18.5,8
|
||||
s6.6,12.9,6.6,22.6v49.5h-19.3v-47.6c0-5.6-1-9.8-3-12.6c-2-2.8-5.3-4.2-9.9-4.2c-2.6,0-5.1,0.5-7.5,1.4c-2.4,1-4.3,2.3-5.8,4.2
|
||||
s-2.2,4.2-2.2,7v51.7H257.2z"/>
|
||||
</g>
|
||||
<path class="st1" d="M213.8,61c2.6,0,5,0.3,7,0.8C206,27.1,168.9,2.5,125.6,2.5c-43.9,0-81.3,25.3-95.8,60.6H45
|
||||
c9.3-32.6,41.9-56.6,80.5-56.6c38.2,0,70.5,23.5,80.2,55.6C208.1,61.4,210.7,61,213.8,61z"/>
|
||||
<path class="st0" d="M213.8,88.3c-3.9,0-6.8-1-8.6-2.8c-1.8-1.8-2.8-4.1-2.8-6.9v-3c0-2.8,1-5.1,2.8-6.9c1.8-1.8,4.7-2.8,8.6-2.8
|
||||
c3.9,0,6.8,1,8.6,2.8c1.8,1.8,2.8,4.1,2.8,6.9v3c0,2.8-1,5-2.8,6.9C220.6,87.4,217.7,88.3,213.8,88.3z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -1,55 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 30.8.0, 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" x="0px" y="0px"
|
||||
viewBox="0 0 595.3 419.5" style="enable-background:new 0 0 595.3 419.5;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#2C9FDA;}
|
||||
.st2{fill:none;stroke:#2C9FDA;stroke-width:3;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M156.8,262.4V158.9h40.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.8z M177.8,202.4
|
||||
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-17V202.4
|
||||
z M177.8,244.9H196c6.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.8V244.9z"/>
|
||||
<path class="st0" d="M249.4,262.4v-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.4H249.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M345,262.2v-74.1h18.2v74.1H345z"/>
|
||||
<path class="st0" d="M395.1,262.2h-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.6V262.2z"/>
|
||||
</g>
|
||||
<path class="st1" d="M354.1,152.3c2.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.5C348.6,152.7,351.1,152.3,354.1,152.3z"/>
|
||||
<path class="st0" d="M354.1,178.1c-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.6s6.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
|
||||
C360.5,177.2,357.8,178.1,354.1,178.1z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M156.4,321.9v-5.4h4v-20.5h-4v-5.4h14.9v5.4h-4v20.5h4v5.4H156.4z"/>
|
||||
<path class="st0" d="M196.8,307l-3-6.3h-0.1v21.1h-6.4v-31.4h7.5l8.6,14.8l3,6.3h0.1v-21.1h6.4v31.4h-7.5L196.8,307z"/>
|
||||
<path class="st0" d="M243.1,296.5v25.3h-6.8v-25.3h-8.5v-6.1h23.8v6.1H243.1z"/>
|
||||
<path class="st0" d="M266.4,321.9v-31.4h21.4v6.1h-14.5v6.4h12.5v6h-12.5v6.8h14.5v6.1H266.4z"/>
|
||||
<path class="st0" d="M310.7,321.9h-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.9V321.9z
|
||||
M317.9,304.6c2,0,3.3-1.1,3.3-3.1v-2c0-2-1.3-3.1-3.3-3.1h-7.2v8.2H317.9z"/>
|
||||
<path class="st0" d="M343.2,321.9v-5.4h4v-20.5h-4v-5.4h14.9v5.4h-4v20.5h4v5.4H343.2z"/>
|
||||
<path class="st0" d="M372.8,306.2c0-10.7,5.5-16.2,13.8-16.2c8.3,0,13.8,5.6,13.8,16.2c0,10.7-5.5,16.2-13.8,16.2
|
||||
C378.3,322.4,372.8,316.8,372.8,306.2z M393.2,308.5v-4.7c0-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
|
||||
S393.2,313.5,393.2,308.5z"/>
|
||||
<path class="st0" d="M423.1,321.9h-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.9V321.9z
|
||||
M430.3,304.6c2,0,3.3-1.1,3.3-3.1v-2c0-2-1.3-3.1-3.3-3.1h-7.2v8.2H430.3z"/>
|
||||
</g>
|
||||
<line class="st2" x1="154.1" y1="275.5" x2="440.2" y2="275.5"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.7 KiB |
|
|
@ -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" x="0px" y="0px"
|
||||
viewBox="0 0 595.3 419.5" style="enable-background:new 0 0 595.3 419.5;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F7F4EF;}
|
||||
.st1{fill:#D39E46;}
|
||||
.st2{fill:none;stroke:#D39E46;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.7 KiB |
|
|
@ -1,55 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 30.8.0, 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" x="0px" y="0px"
|
||||
viewBox="0 0 595.3 419.5" style="enable-background:new 0 0 595.3 419.5;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#314052;}
|
||||
.st1{fill:#2C9FDA;}
|
||||
.st2{fill:none;stroke:#2C9FDA;stroke-width:3;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M156.8,262.4V158.9h40.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.8z M177.8,202.4
|
||||
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-17V202.4
|
||||
z M177.8,244.9H196c6.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.8V244.9z"/>
|
||||
<path class="st0" d="M249.4,262.4v-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.4H249.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M345,262.2v-74.1h18.2v74.1H345z"/>
|
||||
<path class="st0" d="M395.1,262.2h-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.6V262.2z"/>
|
||||
</g>
|
||||
<path class="st1" d="M354.1,152.3c2.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.5C348.6,152.7,351.1,152.3,354.1,152.3z"/>
|
||||
<path class="st0" d="M354.1,178.1c-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.6s6.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
|
||||
C360.5,177.2,357.8,178.1,354.1,178.1z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M156.4,321.9v-5.4h4v-20.5h-4v-5.4h14.9v5.4h-4v20.5h4v5.4H156.4z"/>
|
||||
<path class="st0" d="M196.8,307l-3-6.3h-0.1v21.1h-6.4v-31.4h7.5l8.6,14.8l3,6.3h0.1v-21.1h6.4v31.4h-7.5L196.8,307z"/>
|
||||
<path class="st0" d="M243.1,296.5v25.3h-6.8v-25.3h-8.5v-6.1h23.8v6.1H243.1z"/>
|
||||
<path class="st0" d="M266.4,321.9v-31.4h21.4v6.1h-14.5v6.4h12.5v6h-12.5v6.8h14.5v6.1H266.4z"/>
|
||||
<path class="st0" d="M310.7,321.9h-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.9V321.9z
|
||||
M317.9,304.6c2,0,3.3-1.1,3.3-3.1v-2c0-2-1.3-3.1-3.3-3.1h-7.2v8.2H317.9z"/>
|
||||
<path class="st0" d="M343.2,321.9v-5.4h4v-20.5h-4v-5.4h14.9v5.4h-4v20.5h4v5.4H343.2z"/>
|
||||
<path class="st0" d="M372.8,306.2c0-10.7,5.5-16.2,13.8-16.2c8.3,0,13.8,5.6,13.8,16.2c0,10.7-5.5,16.2-13.8,16.2
|
||||
C378.3,322.4,372.8,316.8,372.8,306.2z M393.2,308.5v-4.7c0-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
|
||||
S393.2,313.5,393.2,308.5z"/>
|
||||
<path class="st0" d="M423.1,321.9h-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.9V321.9z
|
||||
M430.3,304.6c2,0,3.3-1.1,3.3-3.1v-2c0-2-1.3-3.1-3.3-3.1h-7.2v8.2H430.3z"/>
|
||||
</g>
|
||||
<line class="st2" x1="154.1" y1="275.5" x2="440.2" y2="275.5"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.7 KiB |
|
|
@ -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" x="0px" y="0px"
|
||||
viewBox="0 0 595.3 419.5" style="enable-background:new 0 0 595.3 419.5;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#202C2B;}
|
||||
.st1{fill:#D39E46;}
|
||||
.st2{fill:none;stroke:#D39E46;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.7 KiB |
BIN
dev/2026-08-28/markenlogos/otterpohl.jpg
Normal file
|
After Width: | Height: | Size: 62 KiB |
131
dev/2026-08-28/otterpohl_Webseitentext_FINAL.md
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
Otterpohl – nicht „easy to click & buy“, aber trotzdem easy to get
|
||||
Vieles kann man heute online finden, konfigurieren und direkt bestellen. Und das ist auch gut so.
|
||||
Aber manchmal möchte man etwas Besonderes. Eine eigene Idee. Ein Möbel, das genau so aussehen soll, wie man es im Kopf hat. Ein ungewöhnliches Detail. Eine besondere Kombination aus Materialien. Oder einfach etwas, das man so auf keiner Webseite und auch nicht im Handel findet.
|
||||
Und manchmal beginnt es nur mit einem Foto und dem Satz:
|
||||
„So ungefähr – aber eben für mich.“
|
||||
Das ist vielleicht nicht „easy to click & buy“.
|
||||
Aber es darf trotzdem easy to get sein.
|
||||
Denn genau dafür sind wir da: Sie bringen die Idee mit – und wir kümmern uns gemeinsam mit Otterpohl um Planung, handwerkliche Umsetzung und Montage.
|
||||
Mit unseren Möbelpartnern können wir bereits sehr individuell planen. Mit Otterpohl kommen zusätzlich Tischlerhandwerk, individuelle Fertigung, besondere Oberflächen und Innenausbau dazu.
|
||||
Und genau diese Kombination erweitert unsere Möglichkeiten noch einmal erheblich.
|
||||
Ziemlich nah an der eierlegenden Wollmilchsau
|
||||
Der Begriff ist vielleicht nicht besonders elegant.
|
||||
Aber ganz falsch ist er nicht.
|
||||
Individuelle Möbel und Sonderanfertigungen. Lackierungen nach Wunsch. Echtholzfurniere. Verkleidungen. Ergänzungen. Anpassungen. Innenausbau. Trockenbauwände. Individuelle Lösungen direkt vor Ort.
|
||||
Und ganz wichtig:
|
||||
inklusive Montage.
|
||||
Damit endet die Arbeit eben nicht am Möbel.
|
||||
Wenn für eine gute Lösung noch eine Trockenbauwand entstehen muss, Technik verschwinden soll, eine Verkleidung gebraucht wird oder verschiedene Elemente sauber miteinander verbunden werden müssen, kann auch das Teil des Projekts sein.
|
||||
Für uns ist genau das der große Vorteil:
|
||||
Planung, Tischlerhandwerk, Innenausbau und Montage greifen ineinander.
|
||||
Nicht erst das Möbel planen, danach einen Trockenbauer suchen, anschließend jemanden für die Anpassung und am Ende noch ein Montageteam koordinieren.
|
||||
Sondern möglichst alles aus einer Hand – und am Ende auch optisch aus einem Guss.
|
||||
Seit 2014 an unserer Seite
|
||||
Otterpohl ist für uns kein Partner, den wir irgendwann für diese Webseite entdeckt haben.
|
||||
Wir arbeiten bereits seit 2014 bei der Montage zusammen.
|
||||
Und das ist für uns wesentlich mehr als eine kleine Fußnote.
|
||||
Denn ein hochwertiges Möbel ist am Ende nur so gut wie seine Montage.
|
||||
Saubere Fugen. Präzise Anschlüsse. Passleisten, die tatsächlich zur Wand passen. Fronten, die vernünftig eingestellt sind. Und Menschen vor Ort, die eine Situation nicht nur montieren, sondern auch handwerklich verstehen.
|
||||
Gerade wenn Möbelbau, Tischlerarbeiten, Trockenbau und Innenausbau zusammenkommen, ist diese Erfahrung besonders wertvoll.
|
||||
Nach so vielen Jahren weiß man ziemlich genau, mit wem man arbeitet.
|
||||
Und bei einem Partner, den wir zu unseren Kunden schicken, finden wir das durchaus wichtig.
|
||||
Farbe darf auch genau die Farbe sein
|
||||
Bei manchen Projekten reichen drei ähnliche Farbtöne eben nicht.
|
||||
Vielleicht soll ein Möbel exakt zu einer vorhandenen Wandfarbe passen. Zu einem anderen Möbel. Zu einem bestimmten Material. Oder Sie haben einfach diesen einen Farbton im Kopf und möchten genau den.
|
||||
Dann ist „fast passend“ manchmal eben nicht passend genug.
|
||||
Mit individuellen Lackierungen entstehen Möglichkeiten, die weit über eine normale Farbpalette hinausgehen.
|
||||
Und genauso ist es bei den Materialien.
|
||||
Manchmal ist eine hochwertige Dekoroberfläche genau richtig.
|
||||
Manchmal soll es Lack sein.
|
||||
Und manchmal möchte man eben echtes Holz – mit echter Maserung, echter Struktur und einem Echtholzfurnier, bei dem kein Möbelstück exakt aussieht wie das andere.
|
||||
Das ist dann eben der echte Charakter eines Möbels. Nicht perfekt gleich – sondern genau deshalb besonders.
|
||||
Manchmal gibt es die Idee schon – nur das Möbel noch nicht
|
||||
Vielleicht haben Sie eine ziemlich genaue Vorstellung.
|
||||
Vielleicht nur ein Foto, eine Skizze oder eine Idee, die Sie irgendwo gesehen haben.
|
||||
Das reicht völlig.
|
||||
Dann überlegen wir gemeinsam: Wie könnte man das umsetzen? Welche Proportionen passen? Welche Materialien? Lack oder Furnier? Welche Funktion soll das Möbel übernehmen? Und wie fügt sich alles in den Raum und in das ein, was bereits vorhanden ist?
|
||||
Nicht jede gute Idee beginnt schließlich mit einer Artikelnummer.
|
||||
Manchmal beginnt sie einfach im Kopf.
|
||||
Aus der Idee wird eine Planung.
|
||||
Aus der Planung ein Möbel.
|
||||
Und irgendwann steht es tatsächlich da.
|
||||
Nicht unbedingt easy to click & buy – aber am Ende trotzdem ziemlich easy to get.
|
||||
Individuelle Planung kann viele Formen haben
|
||||
Wir arbeiten bewusst mit unterschiedlichen Herstellern und Partnern.
|
||||
CABINET, Rotpunkt und Febrü bieten bereits enorme Möglichkeiten, Möbel individuell zu planen. Maße, Farben, Materialien, Ausstattung und Funktionen können auf den Raum und auf die Menschen abgestimmt werden, die ihn später nutzen.
|
||||
Mit Otterpohl erweitern wir diese Möglichkeiten um die handwerkliche Fertigung.
|
||||
Ein Möbel nach eigenem Entwurf. Eine besondere Konstruktion. Eine individuelle Verkleidung. Eine Bank. Ein Tisch. Ein Wandpaneel. Eine spezielle Lackierung. Ein Echtholzfurnier. Oder einfach dieses eine Detail, das sich handwerklich besser lösen lässt.
|
||||
Tischlerhandwerk ist damit für uns keine Alternative zu unserer Planung.
|
||||
Es ist eine zusätzliche Möglichkeit innerhalb unserer Planung.
|
||||
Nicht alles muss individuell gebaut werden
|
||||
Auch das gehört für uns zu einer vernünftigen Beratung.
|
||||
Nur weil ein Tischler etwas bauen kann, muss man nicht automatisch jedes Möbel individuell fertigen lassen.
|
||||
Wenn ein hochwertiges Möbelsystem eine Aufgabe perfekt löst, nutzen wir dieses System.
|
||||
Wenn eine individuelle Ergänzung sinnvoll ist, wird genau diese Ergänzung gebaut.
|
||||
Und wenn die beste Lösung tatsächlich eine komplett freie Anfertigung ist, geht eben auch das.
|
||||
Wir glauben nicht besonders an entweder oder.
|
||||
Wir glauben eher daran, für jede Aufgabe die sinnvollste Lösung zu finden.
|
||||
Ein Raum hört nicht am Möbel auf
|
||||
Ein Interior-Projekt hält sich erstaunlich selten sauber an einzelne Gewerke.
|
||||
Da steht vielleicht irgendwann das perfekte Möbel – aber daneben fehlt noch eine Wand. Eine Nische soll entstehen. Technik muss verschwinden. Eine Verkleidung wird gebraucht. Ein Übergang soll so aussehen, als wäre er von Anfang an genau so geplant gewesen.
|
||||
Dann hilft es wenig, wenn jeder nur bis zur Grenze seines eigenen Gewerks schaut.
|
||||
Wir möchten lieber, dass am Ende alles zusammenpasst.
|
||||
Möbel, Wand, Verkleidung, Oberfläche und Montage sollen nicht wie fünf einzelne Entscheidungen aussehen, sondern wie eine einzige Planung.
|
||||
Oder einfacher:
|
||||
Alles aus einem Guss.
|
||||
Der letzte Zentimeter kann ziemlich wichtig sein
|
||||
Tischlerhandwerk zeigt seine Stärke übrigens nicht nur bei spektakulären Einzelstücken.
|
||||
Manchmal geht es um Dinge, die auf einer Visualisierung kaum auffallen.
|
||||
Eine Wand läuft etwas weg. Ein Boden ist nicht vollkommen eben. Ein vorhandenes Bauteil muss integriert werden. Ein Übergang braucht eine saubere Lösung.
|
||||
Auf dem Bildschirm sieht so etwas meistens ziemlich einfach aus.
|
||||
In einem echten Raum manchmal weniger.
|
||||
Genau dort entscheidet sich häufig, ob etwas am Ende nur irgendwie passt – oder ob es wirklich so aussieht, als wäre es schon immer genau so gedacht gewesen.
|
||||
Unser Anspruch ist deshalb ziemlich einfach:
|
||||
Man soll am Ende nicht sehen, wo es kompliziert war.
|
||||
Gute Planung endet nicht am Bildschirm
|
||||
Wir planen, visualisieren, bemustern und verändern.
|
||||
Aber irgendwann muss jede Idee den Bildschirm verlassen.
|
||||
Dann gibt es echte Wände, echte Böden, echtes Material und meistens mindestens eine Kleinigkeit, die auf keiner Zeichnung so richtig auffallen wollte.
|
||||
Deshalb gehören für uns Planung und Handwerk zusammen.
|
||||
Denn am Ende interessiert niemanden, welche technische Herausforderung wir irgendwo lösen mussten.
|
||||
Es soll funktionieren.
|
||||
Es soll gut aussehen.
|
||||
Und es soll möglichst so wirken, als wäre es genau so immer gedacht gewesen.
|
||||
Handwerk darf trotzdem modern sein
|
||||
Beim Wort Tischlerei denken manche immer noch an dunkles Holz, schwere Möbel und eine Hobelbank.
|
||||
Kann alles schön sein.
|
||||
Ist aber nur ein kleiner Ausschnitt dessen, was heute möglich ist.
|
||||
Modernes Tischlerhandwerk kann genauso geradlinig, reduziert und architektonisch sein.
|
||||
Lackierte Oberflächen. Echtholzfurniere. Moderne Plattenwerkstoffe. Kombinationen verschiedener Materialien. Filigrane Konstruktionen oder ganz bewusst massive Elemente.
|
||||
Am Ende entscheidet nicht die Vorstellung davon, wie ein „Tischlermöbel“ auszusehen hat.
|
||||
Sondern die Idee dahinter.
|
||||
Und genau das finden wir spannend.
|
||||
Eine kleine Idee bleibt trotzdem eine gute Idee
|
||||
Es muss übrigens nicht immer gleich der komplette Innenausbau sein.
|
||||
Vielleicht fehlt nur ein Tisch.
|
||||
Ein Regal.
|
||||
Eine Bank.
|
||||
Eine besondere Ablage.
|
||||
Oder dieses eine Möbel, das man weder online noch im Handel so findet, wie man es eigentlich gerne hätte.
|
||||
Dann sprechen wir darüber.
|
||||
Nicht jede gute Idee muss automatisch ein großes Projekt werden.
|
||||
Manchmal ist ein einziges besonderes Möbel genau das, was einem Raum noch gefehlt hat.
|
||||
Aus Rheda-Wiedenbrück. Und ziemlich nah dran.
|
||||
Otterpohl kommt aus Rheda-Wiedenbrück.
|
||||
Das ist für uns nicht nur geografisch angenehm.
|
||||
Gerade bei individuellen Projekten sind kurze Wege und direkte Kommunikation ziemlich wertvoll.
|
||||
Man kennt sich. Man spricht miteinander. Man kann Situationen gemeinsam beurteilen und Lösungen entwickeln.
|
||||
Und wenn etwas vor Ort anders aussieht als auf dem Papier, braucht es nicht erst fünf E-Mails und drei Weiterleitungen.
|
||||
Dann braucht es jemanden, der sich die Sache anschaut und sagt:
|
||||
„Okay. Kriegen wir hin.“
|
||||
Nach mehr als zehn Jahren gemeinsamer Projekte wissen wir ziemlich gut, was damit gemeint ist.
|
||||
Planung trifft Handwerk
|
||||
Am Ende geht es uns nicht darum, möglichst viele Marken und Partner auf unserer Webseite unterzubringen.
|
||||
Es geht darum, möglichst viele gute Möglichkeiten zu haben.
|
||||
Mit unseren Möbelpartnern können wir sehr individuell planen.
|
||||
Mit Otterpohl kommen Tischlerhandwerk, freie Fertigung, individuelle Lackierungen, Echtholzfurniere, Innenausbau, Trockenbau und die handwerkliche Umsetzung inklusive Montage dazu.
|
||||
Alles aus einer Hand. Und am Ende möglichst alles aus einem Guss.
|
||||
Manchmal gibt es die passende Lösung schon.
|
||||
Manchmal muss man sie planen.
|
||||
Und manchmal muss man sie eben bauen.
|
||||
Nicht immer easy to click & buy. Aber trotzdem easy to get.
|
||||
186
dev/2026-08-28/umsetzungen-14-09-2026.md
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
# B2in Interior – Arbeitsplan
|
||||
|
||||
**Freigegebener Umfang, Stand September 2026**
|
||||
|
||||
---
|
||||
|
||||
## Freigabestatus im Überblick
|
||||
|
||||
| Phase | Status | Aufwand |
|
||||
|---|---|---:|
|
||||
| **1 – Website: Layout, Marken, Herkunft** | ✅ freigegeben | 39–65 h |
|
||||
| **3 – QR-Empfehlungssystem** | ✅ freigegeben | 25–39 h |
|
||||
|
||||
---
|
||||
|
||||
# Phase 1 – Website
|
||||
|
||||
## 1.1 Farbwelt und Logo
|
||||
|
||||
| Aufgabe |
|
||||
| Interior-Farbwelt im Themensystem umstellen:
|
||||
Tanne `#202c2b` Hauptton dunkel, Wortmarke,
|
||||
Creme `#f7f4ef` Hintergrund,
|
||||
Sand `#d9cbbf` Flächen, Footer,
|
||||
Ocker `#d39e46` Akzent, Logo-Bogen
|
||||
Clay `#a38f82` Sekundär, Produkte
|
||||
Anthrazit `#2d2926` Text
|
||||
| Immobilien-Welt als eigene Farbwelt abgrenzen (bestehendes Blau) |
|
||||
| Logo-Varianten mit farbigem Bogen je Welt, positiv und negativ - normal Immobilien und Interior mit Zusatz
|
||||
hier liegen die Logos dev/2026-08-28/logos
|
||||
| Welten-Wechsler in Topleiste und Footer, Immobilien und Interior |
|
||||
Bei der Umsetzung auf die neue Farbwelt bitte auch diese Shop Screenshots einmal begutachten. In diese Richtung soll es gehen optisch _b2in_interior_shop/screenshots/web
|
||||
|
||||
## 1.2 Tischlerei als vierte Marke
|
||||
|
||||
| Aufgabe |
|
||||
| Markenseite anlegen, CMS-Eintrag, Einbindung in `/marken` und Navigation |
|
||||
Als Inhalt erst mal Platzhalter.
|
||||
|
||||
**Inhaltliche Ausrichtung:** Aufbau, Sonderlösungen, Veredelung, Maßarbeit. Ergänzt CABINET und Rotpunkt, steht nicht daneben.
|
||||
|
||||
**Offen:** Name der Marke, Logo, Basis-Infos, Bildmaterial. Zur Namensfrage: Tritt sie unter eigenem Namen auf oder als B2in-Leistung? Der Name des Partnerbetriebs gehört auf die Detailseite, nicht in die Marke.
|
||||
|
||||
## 1.3 Herkunfts-Sektion
|
||||
|
||||
| Aufgabe |
|
||||
| Eigener Bereich „Aus der Region gefertigt" mit den vier Standorten, visuell aufbereitet |
|
||||
|
||||
**Die Standorte:** Rotpunkt in Bünde, Febrü in Herford, Tischlerei in Rheda-Wiedenbrück, CABINET in Köln. Vier deutsche Manufakturen, drei davon in Ostwestfalen.
|
||||
|
||||
**Zu klären:** Ist CABINET Hersteller oder Vertrieb? Die Formulierung muss sauber trennen zwischen gefertigt und vertrieben.
|
||||
|
||||
## 1.4 Markenseiten und Texte
|
||||
|
||||
| Aufgabe |
|
||||
| Aufklappbare Detailbereiche auf den Markenseiten |
|
||||
| Kontextbezogene Beschriftung des Terminanfrage-Widgets je Marke |
|
||||
|
||||
**Texte:** Die finalen Fassungen für Rotpunkt und Febrü Sind bereits eingearbeitet.
|
||||
|
||||
## 1.5 Abschluss
|
||||
|
||||
| Aufgabe | Std. |
|
||||
|---|---:|
|
||||
| Tests, Prüfung bestehender Bereiche, Produktivsetzung |
|
||||
|
||||
**Summe Phase 1: 39–65 h** · rund 3.510 – 5.850 € netto
|
||||
|
||||
---
|
||||
|
||||
# Phase 3 – QR-Empfehlungssystem
|
||||
|
||||
## Wie es funktioniert Linus
|
||||
|
||||
1. Partner im System anlegen (Makler, Architekt, Handwerker, Praxis)
|
||||
2. System erzeugt eindeutigen Code und QR als Bilddatei
|
||||
3. Marcel druckt den Aufkleber selbst und bringt ihn auf dem Flyer an
|
||||
4. Interessent scannt, landet auf der Website, Zugriff wird erfasst
|
||||
5. Startet er eine Anfrage, ist sie dem Partner zugeordnet
|
||||
6. Übersicht im Admin: Partner, Scans, Anfragen
|
||||
|
||||
## Die Bausteine
|
||||
|
||||
| Aufgabe | Std. |
|
||||
|---|---:|
|
||||
| Datenmodell: Partner, Code, Zuordnung, Status | 3–5 |
|
||||
| Partnerverwaltung im Admin, Code erzeugen, QR als Bilddatei herunterladen | 7–10 |
|
||||
| Weiterleitung und Erfassung des Scans | 5–8 |
|
||||
| Zuordnung der Anfrage zum Partner, angebunden an die bestehende Anfragestrecke | 4–6 |
|
||||
| Einfache Übersicht im Admin: Partner, Scans, Anfragen | 3–5 |
|
||||
| Tests | 3–5 |
|
||||
|
||||
**Summe Phase 3: 25–39 h** · rund 2.250 – 3.510 € netto
|
||||
|
||||
## Nicht enthalten
|
||||
|
||||
Provisionsabrechnung und Auszahlung, Partner-Login (Phase 4), Aufkleber-Vorlagen als fertiges PDF, ausgebaute Auswertung mit Diagrammen oder Filtern.
|
||||
|
||||
## Technische Vorgaben für den Flyer
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Aufkleber** | 60 × 80 mm hochkant. QR bei 30 × 30 mm, darunter Partnername, darunter Platz für eine Zeile |
|
||||
| **Freie Fläche auf dem Flyer** | 70 × 90 mm |
|
||||
| **Untergrund** | hell, einfarbig, kein Bild, kein Verlauf |
|
||||
| **Abstand** | mindestens 10 mm zu Rand und Falz |
|
||||
| **Platzierung** | Rückseite, in der Nähe der Kontaktangaben |
|
||||
|
||||
Die Zielgruppen-Ansprache steckt im Aufkleber, nicht im Flyer. Damit bleibt der Flyer eine Druckfassung.
|
||||
|
||||
---
|
||||
|
||||
# Phase 2 – Flyer: was enthalten ist
|
||||
|
||||
**2 Stunden.** Eine strukturierte schriftliche Rückmeldung auf einen fertigen Entwurf: Aufbau, Reihenfolge der Inhalte, Wirkung, Markenkonformität, Textschärfe. Einmal, als zusammenhängende Liste.
|
||||
|
||||
**Nicht enthalten:** mehrfache Durchgänge über Zwischenstände, Layoutkorrekturen, Bildauswahl, Umschreiben von Texten, Druckdatenaufbereitung.
|
||||
|
||||
---
|
||||
|
||||
# Reihenfolge der Umsetzung
|
||||
|
||||
| Schritt | Was | Warum zuerst |
|
||||
|---|---|---|
|
||||
| **1** | Farbwelt und Logo | Fundament – alles Weitere baut darauf auf |
|
||||
| **2** | Welten-Wechsler | gehört technisch zur Farbwelt-Umstellung |
|
||||
| **3** | Tischlerei-Markenseite | braucht die fertige Farbwelt |
|
||||
| **4** | Herkunfts-Sektion | braucht die Tischlerei als vierten Standort |
|
||||
| **5** | Akkordeon und Widget-Beschriftung | Feinschliff auf den fertigen Seiten |
|
||||
| **6** | Tests und Livegang Phase 1 | |
|
||||
| **7** | Phase 3 komplett | erst sinnvoll, wenn die Website steht |
|
||||
|
||||
Phase 3 erst nach dem Livegang von Phase 1: Ein QR-Code, der auf eine halbfertige Seite führt, verbrennt den ersten Eindruck beim Partner.
|
||||
|
||||
---
|
||||
|
||||
# Was ich von Marcel brauche
|
||||
|
||||
## Blockierend für Phase 1
|
||||
|
||||
| Was | Für |
|
||||
|---|---|
|
||||
| Freigabe Farbwerte (Ocker, Tanne) | 1.1 – Start |
|
||||
| Name der Tischlerei-Marke | 1.2 |
|
||||
| Logo und Basis-Infos Tischlerei | 1.2 |
|
||||
| Bildmaterial Tischlerei | 1.2 |
|
||||
| Klärung: CABINET Hersteller oder Vertrieb | 1.3 |
|
||||
|
||||
## Nicht blockierend, aber offen
|
||||
|
||||
| Was | Anmerkung |
|
||||
|---|---|
|
||||
| Logos Rotpunkt und Febrü | für die Markenseiten |
|
||||
| Bildmaterial Hero und Kernmarken | Platzhalter möglich |
|
||||
| Neuer Footer-Claim | „Connecting design and property" ist der alte Immobilien-Claim |
|
||||
| Showroom-Adressen je Markenseite | nennen beide Seiten den richtigen Standort? |
|
||||
| Höhe der Partnervergütung | vor der Partner-Ansprache, nicht vor dem Bau |
|
||||
|
||||
---
|
||||
|
||||
# Empfehlungen fürs Protokoll
|
||||
|
||||
**Erste zehn Partner:** Makler, Architekten, Handwerker. Praxen danach. Keine Wertung, sondern Priorisierung auf den Kanal mit der höheren Wirkung.
|
||||
|
||||
**Keine Provisionen an Arztpraxen** im ersten Schritt. Flyer dort über die persönliche Verbindung auslegen, mit eigenem QR-Code. Nach drei Monaten zeigen die Zahlen, ob der Kanal trägt.
|
||||
|
||||
**QR-Feld neutral halten.** Sachliche Zeile wie „Beratungstermin vereinbaren". Keine emotionalen Appelle.
|
||||
|
||||
---
|
||||
|
||||
# Zurückgestellt
|
||||
|
||||
| Thema | Status |
|
||||
|---|---|
|
||||
| Partnerportal | 2027, wenn genug Partner aktiv sind |
|
||||
| Kundenmappe | Konzept wird überarbeitet |
|
||||
| Shop (Katalog und Verkauf) | Kalkulation liegt vor: 149–235 h |
|
||||
| Immobilien-CMS | Kostenplan liegt vor, nicht freigegeben |
|
||||
| Dubai-Map | setzt Immobilien-CMS voraus |
|
||||
| Marktplätze | spätere Shop-Stufe |
|
||||
| Bestandskunden-Anschreiben | Idee vorhanden, noch nicht angeboten |
|
||||
| Markenrichtlinie Fahrzeug und Print | eigene Design-Aufgabe |
|
||||
|
||||
---
|
||||
|
||||
*Aufwandsschätzung auf Basis des aktuellen Stands, kein Festpreis. Abrechnung nach tatsächlichen, dokumentierten Stunden. Zusätzliche Anforderungen werden vorab abgestimmt und erst nach Freigabe umgesetzt.*
|
||||
|
|
@ -90,7 +90,7 @@ Jedes Playlist-Item hat folgende Eigenschaften:
|
|||
| `media_url` | URL (optional) | Alternative zu Upload: Externe Video-/Bild-URL. Wird bevorzugt wenn vorhanden. |
|
||||
| `headline` | Text | Max. 40 Zeichen. Wird im Textfeld angezeigt. |
|
||||
| `subline` | Text | Max. 80 Zeichen. Erklärender Text unter der Headline. |
|
||||
| `duration_seconds` | Zahl | Anzeigedauer in Sekunden. Für Bilder: empfohlen 8–12 Sek. Für Videos: wird ignoriert, Dauer ergibt sich aus Videolänge. |
|
||||
| `duration_seconds` | Zahl | Anzeigedauer in Sekunden, auch mit Nachkommastelle (z. B. `1.5`). Für Bilder: empfohlen 8–12 Sek. Für Videos: wird ignoriert, Dauer ergibt sich aus Videolänge. |
|
||||
| `sort_order` | Zahl | Reihenfolge in der Playlist. Niedrigste Zahl zuerst. |
|
||||
| `is_active` | Toggle | Aktiv/Inaktiv. Inaktive Items werden übersprungen. |
|
||||
|
||||
|
|
|
|||
|
|
@ -1,54 +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="153 96 289.3 227.5" xml:space="preserve">
|
||||
viewBox="152.5 94.5 289 228" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#2C9FDA;}
|
||||
.st2{fill:none;stroke:#2C9FDA;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st0{fill:#F7F4EF;}
|
||||
.st1{fill:#D39E46;}
|
||||
.st2{fill:none;stroke:#D39E46;stroke-width:3;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M156.8,262.4V158.9h40.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.8z M177.8,202.4
|
||||
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-17V202.4
|
||||
z M177.8,244.9H196c6.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.8V244.9z"/>
|
||||
<path class="st0" d="M249.4,262.4v-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.4H249.4z"/>
|
||||
</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>
|
||||
<path class="st0" d="M345,262.2v-74.1h18.2v74.1H345z"/>
|
||||
<path class="st0" d="M395.1,262.2h-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.6V262.2z"/>
|
||||
</g>
|
||||
<path class="st1" d="M354.1,152.3c2.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.5C348.6,152.7,351.1,152.3,354.1,152.3z"/>
|
||||
<path class="st0" d="M354.1,178.1c-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.6s6.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
|
||||
C360.5,177.2,357.8,178.1,354.1,178.1z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M156.4,321.9v-5.4h4v-20.5h-4v-5.4h14.9v5.4h-4v20.5h4v5.4H156.4z"/>
|
||||
<path class="st0" d="M196.8,307l-3-6.3h-0.1v21.1h-6.4v-31.4h7.5l8.6,14.8l3,6.3h0.1v-21.1h6.4v31.4h-7.5L196.8,307z"/>
|
||||
<path class="st0" d="M243.1,296.5v25.3h-6.8v-25.3h-8.5v-6.1h23.8v6.1H243.1z"/>
|
||||
<path class="st0" d="M266.4,321.9v-31.4h21.4v6.1h-14.5v6.4h12.5v6h-12.5v6.8h14.5v6.1H266.4z"/>
|
||||
<path class="st0" d="M310.7,321.9h-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.9V321.9z
|
||||
M317.9,304.6c2,0,3.3-1.1,3.3-3.1v-2c0-2-1.3-3.1-3.3-3.1h-7.2v8.2H317.9z"/>
|
||||
<path class="st0" d="M343.2,321.9v-5.4h4v-20.5h-4v-5.4h14.9v5.4h-4v20.5h4v5.4H343.2z"/>
|
||||
<path class="st0" d="M372.8,306.2c0-10.7,5.5-16.2,13.8-16.2c8.3,0,13.8,5.6,13.8,16.2c0,10.7-5.5,16.2-13.8,16.2
|
||||
C378.3,322.4,372.8,316.8,372.8,306.2z M393.2,308.5v-4.7c0-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
|
||||
S393.2,313.5,393.2,308.5z"/>
|
||||
<path class="st0" d="M423.1,321.9h-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.9V321.9z
|
||||
M430.3,304.6c2,0,3.3-1.1,3.3-3.1v-2c0-2-1.3-3.1-3.3-3.1h-7.2v8.2H430.3z"/>
|
||||
<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>
|
||||
<line class="st2" x1="154.1" y1="275.5" x2="440.2" y2="275.5"/>
|
||||
<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>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
|
@ -1,54 +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="153 96 289.3 227.5" xml:space="preserve">
|
||||
viewBox="152.5 94.5 289 228" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#314052;}
|
||||
.st1{fill:#2C9FDA;}
|
||||
.st2{fill:none;stroke:#2C9FDA;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st0{fill:#202C2B;}
|
||||
.st1{fill:#D39E46;}
|
||||
.st2{fill:none;stroke:#D39E46;stroke-width:3;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M156.8,262.4V158.9h40.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.8z M177.8,202.4
|
||||
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-17V202.4
|
||||
z M177.8,244.9H196c6.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.8V244.9z"/>
|
||||
<path class="st0" d="M249.4,262.4v-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.4H249.4z"/>
|
||||
</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>
|
||||
<path class="st0" d="M345,262.2v-74.1h18.2v74.1H345z"/>
|
||||
<path class="st0" d="M395.1,262.2h-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.6V262.2z"/>
|
||||
</g>
|
||||
<path class="st1" d="M354.1,152.3c2.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.5C348.6,152.7,351.1,152.3,354.1,152.3z"/>
|
||||
<path class="st0" d="M354.1,178.1c-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.6s6.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
|
||||
C360.5,177.2,357.8,178.1,354.1,178.1z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M156.4,321.9v-5.4h4v-20.5h-4v-5.4h14.9v5.4h-4v20.5h4v5.4H156.4z"/>
|
||||
<path class="st0" d="M196.8,307l-3-6.3h-0.1v21.1h-6.4v-31.4h7.5l8.6,14.8l3,6.3h0.1v-21.1h6.4v31.4h-7.5L196.8,307z"/>
|
||||
<path class="st0" d="M243.1,296.5v25.3h-6.8v-25.3h-8.5v-6.1h23.8v6.1H243.1z"/>
|
||||
<path class="st0" d="M266.4,321.9v-31.4h21.4v6.1h-14.5v6.4h12.5v6h-12.5v6.8h14.5v6.1H266.4z"/>
|
||||
<path class="st0" d="M310.7,321.9h-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.9V321.9z
|
||||
M317.9,304.6c2,0,3.3-1.1,3.3-3.1v-2c0-2-1.3-3.1-3.3-3.1h-7.2v8.2H317.9z"/>
|
||||
<path class="st0" d="M343.2,321.9v-5.4h4v-20.5h-4v-5.4h14.9v5.4h-4v20.5h4v5.4H343.2z"/>
|
||||
<path class="st0" d="M372.8,306.2c0-10.7,5.5-16.2,13.8-16.2c8.3,0,13.8,5.6,13.8,16.2c0,10.7-5.5,16.2-13.8,16.2
|
||||
C378.3,322.4,372.8,316.8,372.8,306.2z M393.2,308.5v-4.7c0-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
|
||||
S393.2,313.5,393.2,308.5z"/>
|
||||
<path class="st0" d="M423.1,321.9h-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.9V321.9z
|
||||
M430.3,304.6c2,0,3.3-1.1,3.3-3.1v-2c0-2-1.3-3.1-3.3-3.1h-7.2v8.2H430.3z"/>
|
||||
<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>
|
||||
<line class="st2" x1="154.1" y1="275.5" x2="440.2" y2="275.5"/>
|
||||
<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>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
|
@ -1,21 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Ebene_1" data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 283.46 160.63">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #2c9fda;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #314052;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="M36.8,55.75C45.39,25.8,75.26,3.7,110.8,3.7s65.41,22.1,74,52.04h13.94C185.49,23.2,151.12,0,110.8,0S36.12,23.2,22.87,55.75h13.94Z"/>
|
||||
<g>
|
||||
<path class="cls-2" d="M0,160.63V60.03h39.61c7.33,0,13.44,1.11,18.33,3.34,4.89,2.23,8.56,5.28,10.99,9.15,2.43,3.87,3.65,8.33,3.65,13.37,0,3.96-.78,7.42-2.34,10.36-1.57,2.95-3.7,5.36-6.4,7.22-2.7,1.87-5.77,3.21-9.22,4.02v1.01c3.75.14,7.29,1.2,10.62,3.21,3.33,2,6.03,4.79,8.11,8.37,2.08,3.58,3.12,7.84,3.12,12.79,0,5.31-1.3,10.06-3.89,14.25-2.59,4.19-6.42,7.48-11.49,9.89-5.07,2.41-11.38,3.61-18.93,3.61H0ZM20.44,102.29h16.15c2.9,0,5.52-.53,7.84-1.59,2.32-1.06,4.14-2.57,5.46-4.52,1.32-1.96,1.98-4.29,1.98-6.99,0-3.65-1.29-6.62-3.85-8.91-2.57-2.3-6.24-3.44-11.02-3.44h-16.55v25.45ZM20.44,143.68h17.69c5.99,0,10.36-1.17,13.14-3.51,2.77-2.34,4.16-5.42,4.16-9.25,0-2.84-.68-5.34-2.04-7.53-1.36-2.18-3.29-3.89-5.8-5.13-2.5-1.24-5.47-1.86-8.91-1.86h-18.23v27.28Z"/>
|
||||
<path class="cls-2" d="M90.07,160.63v-14.85l35.38-33.42c3.04-2.97,5.59-5.66,7.67-8.07,2.08-2.41,3.66-4.77,4.76-7.09,1.09-2.32,1.64-4.83,1.64-7.53,0-3.01-.67-5.6-2.01-7.76s-3.17-3.83-5.49-5c-2.32-1.17-4.98-1.75-7.97-1.75s-5.79.63-8.11,1.89c-2.32,1.26-4.11,3.06-5.36,5.4s-1.88,5.15-1.88,8.44h-19.57c0-6.53,1.48-12.21,4.46-17.05,2.97-4.84,7.09-8.57,12.36-11.21,5.27-2.63,11.35-3.95,18.23-3.95s13.22,1.27,18.5,3.81c5.27,2.54,9.37,6.04,12.3,10.5,2.93,4.46,4.39,9.59,4.39,15.39,0,3.74-.73,7.43-2.18,11.07-1.45,3.65-4.05,7.73-7.77,12.25s-9.01,9.94-15.85,16.24l-15.08,14.99v.74h42.22v16.95h-70.63Z"/>
|
||||
<path class="cls-2" d="M207.81,160.63h-33.41v-12.72h10.4v-71.07h-10.4v-12.72h33.41v12.72h-10.51v71.07h10.51v12.72Z"/>
|
||||
<path class="cls-2" d="M269.53,160.63l-25.22-53.23-8.52-20.6h-.33v73.84h-11.95v-96.51h14.05l25.11,53.23,8.52,20.6h.33v-73.83h11.95v96.51h-13.94Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 29.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="4.5 2 301 176" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#314052;}
|
||||
.st1{fill:#2C9FDA;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M5,177.5V67.9h43.1c7.9,0,14.6,1.2,20,3.6c5.3,2.4,9.3,5.7,12,9.9c2.6,4.2,4,9.1,4,14.6
|
||||
c0,4.3-0.8,8-2.5,11.3c-1.7,3.2-4,5.8-7,7.8c-3,2-6.2,3.5-10.1,4.3v1.1c4.1,0.1,7.9,1.3,11.5,3.5s6.6,5.2,8.8,9.1
|
||||
c2.2,3.9,3.4,8.6,3.4,14c0,5.8-1.4,10.9-4.2,15.6c-2.9,4.6-7,8.1-12.5,10.8c-5.5,2.6-12.4,3.9-20.6,3.9L5,177.5L5,177.5z
|
||||
M27.2,114h17.6c3.2,0,6-0.5,8.6-1.7c2.5-1.2,4.6-2.8,5.9-5c1.5-2.1,2.1-4.7,2.1-7.6c0-4-1.4-7.2-4.2-9.7
|
||||
c-2.8-2.5-6.8-3.7-12-3.7h-18L27.2,114L27.2,114z M27.2,158.9h19.3c6.6,0,11.3-1.3,14.3-3.8c3-2.5,4.6-5.9,4.6-10.1
|
||||
c0-3.1-0.7-5.8-2.2-8.1c-1.5-2.3-3.6-4.2-6.3-5.6c-2.8-1.4-5.9-2-9.7-2H27.1v29.6H27.2z"/>
|
||||
<path class="st0" d="M103.1,177.5v-16.2l38.5-36.4c3.3-3.3,6.1-6.1,8.4-8.8c2.2-2.6,4-5.2,5.2-7.7c1.2-2.5,1.8-5.3,1.8-8.1
|
||||
c0-3.3-0.7-6.1-2.2-8.5s-3.5-4.1-6-5.4c-2.5-1.3-5.4-1.9-8.7-1.9c-3.4,0-6.3,0.6-8.8,2c-2.5,1.4-4.4,3.3-5.8,5.9
|
||||
c-1.4,2.5-2,5.6-2,9.2h-21.3c0-7.1,1.6-13.3,4.9-18.5c3.3-5.3,7.7-9.3,13.4-12.2c5.7-2.9,12.4-4.3,19.9-4.3
|
||||
c7.7,0,14.4,1.4,20.1,4.1c5.7,2.8,10.2,6.6,13.4,11.4c3.2,4.9,4.8,10.5,4.8,16.7c0,4-0.7,8-2.3,12.1c-1.6,4-4.4,8.5-8.5,13.3
|
||||
c-4,5-9.8,10.8-17.2,17.7l-16.4,16.3v0.8h45.9v18.4L103.1,177.5L103.1,177.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M204.2,177.2V98.8h19.3v78.4H204.2z"/>
|
||||
<path class="st0" d="M257.2,177.2h-19.3V98.8h19.3V112h0.7c1.6-4.2,4.1-7.7,7.6-10.6s8.3-4.2,14.4-4.2c8,0,14.2,2.6,18.5,8
|
||||
s6.6,12.9,6.6,22.6v49.5h-19.3v-47.6c0-5.6-1-9.8-3-12.6c-2-2.8-5.3-4.2-9.9-4.2c-2.6,0-5.1,0.5-7.5,1.4c-2.4,1-4.3,2.3-5.8,4.2
|
||||
s-2.2,4.2-2.2,7v51.7H257.2z"/>
|
||||
</g>
|
||||
<path class="st1" d="M213.8,61c2.6,0,5,0.3,7,0.8C206,27.1,168.9,2.5,125.6,2.5c-43.9,0-81.3,25.3-95.8,60.6H45
|
||||
c9.3-32.6,41.9-56.6,80.5-56.6c38.2,0,70.5,23.5,80.2,55.6C208.1,61.4,210.7,61,213.8,61z"/>
|
||||
<path class="st0" d="M213.8,88.3c-3.9,0-6.8-1-8.6-2.8c-1.8-1.8-2.8-4.1-2.8-6.9v-3c0-2.8,1-5.1,2.8-6.9c1.8-1.8,4.7-2.8,8.6-2.8
|
||||
c3.9,0,6.8,1,8.6,2.8c1.8,1.8,2.8,4.1,2.8,6.9v3c0,2.8-1,5-2.8,6.9C220.6,87.4,217.7,88.3,213.8,88.3z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 6 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 6 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 478 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1,023 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2 KiB |
BIN
public/img/favicons/b2in-favicon.ico
Normal file
|
After Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 12 KiB |