B2in Interior: Relaunch der Website, Kernmarken-CMS und Terminanfrage

Umsetzung von AP0-AP6 aus dev/2026-08-28/entwicklungsplan.md sowie der
vorab gefundenen Befunde T1 und B1-B13.

Befunde
- CmsContentSeeder überschrieb bei jedem Lauf alle redaktionellen Inhalte;
  jetzt Redaktionsschutz plus gezielter Relaunch-Seeder (B1)
- Magazin-CMS war nicht ans Frontend angebunden, Artikel jetzt über Slugs
  mit 301 von den alten numerischen URLs (B2)
- Vier veröffentlichte Display-Playlists zeigten auf b2in.test und wären
  live nicht ladbar gewesen (B13)
- Info-Tablet speicherte geleerte Öffnungszeiten nicht (T1)
- /service und /portfolio lieferten seit Februar 500 (B12)

Website
- Startseite neu strukturiert und umgetextet, eigene Seite /marken mit
  Detailseite je Kernmarke, /netzwerk und Altrouten leiten dorthin
- Galerie je Marke, getrennt nach realisierten Projekten und
  Visualisierungen; ohne gesetzte Kategorie gilt ein Bild als Entwurf
- Seite /referenzen, gespeist aus den Markengalerien
- Showroom-Display im 9:16-Rahmen über eine schreibgeschützte Embed-Route
  ohne Preview-Token
- Terminanfrage-Widget auf allen Seiten, Markenauswahl aus dem CMS,
  WhatsApp per Klick-zum-Chat-Link mit vorbereitetem Text
- FAQ, Über-uns, Kontakt und Magazin inhaltlich auf Interior umgestellt

Sonstiges
- Vorschau-Instanzen liefern noindex und verlinken sich bei unbekanntem
  Host auf sich selbst statt auf eine fremde Domain
- Seeder-Zugangsdaten aus dem Code in die Konfiguration (B8)
- Ablauf für die Vorschau: dev/2026-08-28/preview-deployment.md

833 Tests grün (3084 Assertions), Pint sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-08-28 15:31:19 +00:00
parent 193d37b1ce
commit 6d53243ea6
132 changed files with 8672 additions and 335 deletions

View file

@ -0,0 +1,51 @@
@php
if (!function_exists('renderHeroIcon')) {
function renderHeroIcon($iconName, $style = 'outline', $color = 'text-secondary')
{
$iconPath = public_path("heroicons/optimized/24/{$style}/{$iconName}.svg");
$fallbackPath = public_path('heroicons/optimized/24/outline/sparkles.svg');
if (file_exists($iconPath)) {
return str_replace('<svg', '<svg class="w-10 h-10 ' . $color . '"', file_get_contents($iconPath));
}
if (file_exists($fallbackPath)) {
return str_replace('<svg', '<svg class="w-10 h-10 ' . $color . '"', file_get_contents($fallbackPath));
}
return '';
}
}
@endphp
<section class="section-padding {{ $bg }}" id="wege">
<div class="container-padding">
<div class="text-center mb-16 slide-up delay-300">
<h2 class="text-section-title">{!! $content['title'] ?? '' !!}</h2>
@if (!empty($content['subtitle']))
<p class="text-large text-muted-foreground mt-4 max-w-2xl mx-auto">{{ $content['subtitle'] }}</p>
@endif
</div>
<div class="grid md:grid-cols-2 gap-8 lg:gap-12 slide-up delay-400">
@foreach ($content['cards'] ?? [] as $index => $card)
<div class="card-elevated rounded-2xl p-8 flex flex-col" wire:key="audience-card-{{ $index }}">
<div class="w-20 h-20 icon-secondary-linear glow-soft rounded-2xl flex items-center justify-center mb-6">
{!! renderHeroIcon($card['icon'] ?? 'home', 'outline', 'text-secondary-foreground') !!}
</div>
<h3 class="text-xl font-medium mb-3">{{ $card['title'] ?? '' }}</h3>
<p class="text-muted-foreground leading-relaxed grow">{!! $card['description'] ?? '' !!}</p>
@if (!empty($card['button_text']))
<div class="pt-6">
<a href="{{ $card['button_link'] ?? '/contact' }}" class="btn-primary-accent px-6 py-3 inline-block">
{{ $card['button_text'] }}
</a>
</div>
@endif
</div>
@endforeach
</div>
</div>
</section>

View file

@ -0,0 +1,256 @@
{{-- Detailseiten-Vorlage laut umsetzung-inhalte.md §5:
Header (Logo + Claim + Stimmungsbild) → Einleitung → Leistungen → Galerie → CTA --}}
<article>
{{-- 1. Header --}}
<section class="section-padding">
<div class="container-padding">
<a href="{{ route('marken') }}"
class="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors mb-8">
<span aria-hidden="true">&larr;</span> {{ __('ui.all_brands') }}
</a>
<div class="grid lg:grid-cols-2 gap-12 items-center">
<div>
@if (!empty($brand['logo']))
<img src="{{ theme_image_url($brand['logo']) }}" alt="{{ $brand['name'] }}"
class="h-10 w-auto mb-6 object-contain object-left" />
@endif
<h1 class="text-section-title">{{ $brand['name'] }}</h1>
<p class="text-large text-secondary font-medium mt-2">{{ $brand['tagline'] }}</p>
@if (!empty($brand['showroom']))
<p class="text-muted-foreground mt-4 inline-flex items-center gap-2">
<svg class="w-5 h-5 text-secondary" fill="none" stroke="currentColor" stroke-width="1.5"
viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round"
d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z" />
</svg>
{{ $brand['showroom'] }}
</p>
@endif
</div>
<div>
@if (!empty($brand['image']))
<img src="{{ theme_image_url($brand['image']) }}" alt="{{ $brand['name'] }}"
class="w-full rounded-2xl object-cover" />
@else
<div class="w-full aspect-[4/3] rounded-2xl bg-muted flex items-center justify-center">
<span class="text-muted-foreground text-sm">{{ $brand['name'] }}</span>
</div>
@endif
</div>
</div>
</div>
</section>
{{-- 2. Einleitung --}}
@if (!empty($brand['description']))
<section class="pb-16">
<div class="container-narrow">
<div class="prose-content text-large text-muted-foreground leading-relaxed">
{!! $brand['description'] !!}
</div>
</div>
</section>
@endif
{{-- 3. Leistungen --}}
@if (!empty($brand['services']))
<section class="section-padding bg-accent">
<div class="container-padding">
<h2 class="text-section-title text-center mb-12">{{ __('ui.brand_services') }}</h2>
<div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-8">
@foreach ($brand['services'] as $index => $service)
<div class="card-elevated rounded-2xl p-6" wire:key="service-{{ $index }}">
<h3 class="text-lg font-medium">{{ $service['title'] ?? '' }}</h3>
<p class="text-muted-foreground leading-relaxed mt-2">{{ $service['description'] ?? '' }}</p>
</div>
@endforeach
</div>
</div>
</section>
@endif
{{-- 4. Galerie: realisierte Projekte und Inspirationen strikt getrennt --}}
@php
$galleryProjects = $gallery['projects'] ?? [];
$galleryInspirations = $gallery['inspirations'] ?? [];
$hasBothGalleryGroups = $galleryProjects !== [] && $galleryInspirations !== [];
$initialGalleryTab = $galleryProjects !== [] ? 'projects' : 'inspirations';
$galleryLightbox = collect(['projects' => $galleryProjects, 'inspirations' => $galleryInspirations])
->map(fn (array $items) => collect($items)->map(fn (array $item) => [
'src' => theme_image_url($item['image']),
'caption' => $item['caption'] ?? '',
'badge' => $item['category'] === \App\Models\CmsBrand::GALLERY_PROJECT
? __('ui.brand_gallery_badge_project')
: __('ui.brand_gallery_badge_inspiration'),
])->values()->all())
->all();
@endphp
@if ($galleryProjects || $galleryInspirations)
<section id="galerie" class="section-padding"
x-data="{
tab: '{{ $initialGalleryTab }}',
groups: @js($galleryLightbox),
lightbox: false,
current: 0,
get items() { return this.groups[this.tab] ?? [] },
open(index) { this.current = index; this.lightbox = true },
next() { this.current = (this.current + 1) % this.items.length },
prev() { this.current = (this.current - 1 + this.items.length) % this.items.length },
}">
<div class="container-padding">
<div class="text-center max-w-2xl mx-auto">
<h2 class="text-section-title">{{ __('ui.brand_gallery_title') }}</h2>
<p class="text-muted-foreground leading-relaxed mt-4">{{ __('ui.brand_gallery_intro') }}</p>
</div>
@if ($hasBothGalleryGroups)
<div class="flex justify-center mt-10" role="tablist" aria-label="{{ __('ui.brand_gallery_title') }}">
<div class="inline-flex rounded-full bg-accent p-1 gap-1">
@foreach (['projects' => __('ui.brand_gallery_tab_projects'), 'inspirations' => __('ui.brand_gallery_tab_inspirations')] as $tabKey => $tabLabel)
<button type="button" role="tab" wire:key="gallery-tab-{{ $tabKey }}"
x-on:click="tab = '{{ $tabKey }}'"
x-bind:aria-selected="tab === '{{ $tabKey }}' ? 'true' : 'false'"
x-bind:class="tab === '{{ $tabKey }}'
? 'bg-background text-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground'"
class="px-6 py-2.5 rounded-full text-sm font-medium transition-colors cursor-pointer">
{{ $tabLabel }}
</button>
@endforeach
</div>
</div>
@endif
@foreach (['projects' => $galleryProjects, 'inspirations' => $galleryInspirations] as $groupKey => $groupItems)
@if ($groupItems)
<div wire:key="gallery-group-{{ $groupKey }}"
@if ($hasBothGalleryGroups) x-show="tab === '{{ $groupKey }}'" x-cloak @endif
class="mt-10">
@unless ($hasBothGalleryGroups)
<h3 class="text-xl font-medium text-center mb-8">
{{ $groupKey === 'projects' ? __('ui.brand_gallery_tab_projects') : __('ui.brand_gallery_tab_inspirations') }}
</h3>
@endunless
@if ($groupKey === 'inspirations')
<p class="text-sm text-muted-foreground text-center max-w-2xl mx-auto mb-8">
{{ __('ui.brand_gallery_inspiration_note') }}
</p>
@endif
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
@foreach ($groupItems as $index => $item)
<figure wire:key="gallery-{{ $groupKey }}-{{ $index }}" class="group">
<button type="button" x-on:click="open({{ $index }})"
class="relative block w-full overflow-hidden rounded-2xl cursor-pointer">
<img src="{{ theme_image_url($item['image']) }}"
alt="{{ $item['caption'] ?: $brand['name'] }}" loading="lazy"
class="w-full aspect-[4/3] object-cover transition-transform duration-300 group-hover:scale-105" />
<span class="absolute top-3 left-3 rounded-full bg-black/60 px-3 py-1 text-xs font-medium text-white backdrop-blur-sm">
{{ $groupKey === 'projects' ? __('ui.brand_gallery_badge_project') : __('ui.brand_gallery_badge_inspiration') }}
</span>
</button>
@if (!empty($item['caption']))
<figcaption class="text-sm text-muted-foreground mt-3">{{ $item['caption'] }}</figcaption>
@endif
</figure>
@endforeach
</div>
</div>
@endif
@endforeach
</div>
{{-- Lightbox: ins body geteleportet, damit keine Eltern-Constraints stören --}}
<template x-teleport="body">
<div x-show="lightbox" x-cloak class="fixed inset-0 z-50 bg-black/90"
x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0"
x-transition:leave="transition ease-in duration-200" x-transition:leave-end="opacity-0"
x-on:keydown.escape.window="lightbox = false"
x-on:keydown.arrow-right.window="lightbox && next()"
x-on:keydown.arrow-left.window="lightbox && prev()">
<div class="absolute inset-0 flex flex-col items-center justify-center gap-4 p-6">
<img x-bind:src="items[current]?.src" x-bind:alt="items[current]?.caption"
class="max-h-[80vh] max-w-[90vw] rounded-lg object-contain" />
<p class="text-center text-sm text-white/80">
<span class="rounded-full border border-white/30 px-3 py-1" x-text="items[current]?.badge"></span>
<span class="ml-3" x-text="items[current]?.caption"></span>
</p>
</div>
<button type="button" x-on:click="lightbox = false" aria-label="{{ __('ui.close') }}"
class="absolute top-6 right-6 flex h-12 w-12 cursor-pointer items-center justify-center rounded-full border border-white/20 bg-white/10 text-white/90 transition-all hover:bg-white/20 hover:text-white">
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<button type="button" x-show="items.length > 1" x-on:click="prev()" aria-label="{{ __('ui.previous') }}"
class="absolute left-4 top-1/2 flex h-12 w-12 -translate-y-1/2 cursor-pointer items-center justify-center rounded-full border border-white/20 bg-white/10 text-white/90 transition-all hover:bg-white/20">
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
</button>
<button type="button" x-show="items.length > 1" x-on:click="next()" aria-label="{{ __('ui.next') }}"
class="absolute right-4 top-1/2 flex h-12 w-12 -translate-y-1/2 cursor-pointer items-center justify-center rounded-full border border-white/20 bg-white/10 text-white/90 transition-all hover:bg-white/20">
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</template>
</section>
@endif
{{-- 5. CTA --}}
<section class="section-padding bg-secondary text-secondary-foreground">
<div class="container-narrow text-center spacing-section">
<h2 class="text-section-title text-white">{{ __('ui.brand_cta_title', ['name' => $brand['name']]) }}</h2>
<p class="text-large max-w-2xl mx-auto leading-relaxed">{{ __('ui.brand_cta_text') }}</p>
<div class="pt-4 flex flex-wrap items-center justify-center gap-4">
<a href="{{ route('contact') }}" class="btn-primary-accent px-8 py-4 text-lg">
{{ __('ui.request_appointment') }}
</a>
@if (!empty($brand['website_url']))
<a href="{{ $brand['website_url'] }}" target="_blank" rel="noopener"
class="px-8 py-4 text-lg underline underline-offset-4">
{{ __('ui.brand_website') }}
</a>
@endif
</div>
{{-- Der WhatsApp-Button kommt mit AP3, sobald die Nummer bestätigt ist. --}}
</div>
</section>
{{-- Weitere Marken --}}
@if (!empty($otherBrands))
<section class="section-padding bg-accent">
<div class="container-padding">
<h2 class="text-section-title text-center mb-12">{{ __('ui.other_brands') }}</h2>
<div class="grid sm:grid-cols-2 gap-8 max-w-4xl mx-auto">
@foreach ($otherBrands as $other)
<a href="{{ route('marken.show', ['slug' => $other['slug']]) }}"
wire:key="other-{{ $other['slug'] }}"
class="card-elevated rounded-2xl p-8 block hover:shadow-lg transition-shadow duration-300">
<h3 class="text-xl font-medium">{{ $other['name'] }}</h3>
<p class="text-secondary font-medium mt-1">{{ $other['tagline'] }}</p>
<p class="text-muted-foreground leading-relaxed mt-3">{!! $other['teaser'] !!}</p>
</a>
@endforeach
</div>
</div>
</section>
@endif
</article>

View file

@ -0,0 +1,59 @@
{{-- Übersichtsseite /marken: je Marke Logo, Bild, Claim, Text, drei Merkmale und der Weg
zur Detailseite. Jede Marke steht in einem eigenen Rahmen, damit klar ist, was zusammengehört.
Abwechselnde Bildseite, damit die Liste nicht monoton wird. --}}
<section class="section-padding {{ $bg }}">
<div class="container-padding space-y-10 lg:space-y-14">
@foreach ($brands as $index => $brand)
<article class="card-elevated rounded-3xl p-6 sm:p-8 lg:p-12" wire:key="brand-{{ $brand['slug'] }}">
<div class="grid lg:grid-cols-2 gap-10 lg:gap-16 items-center">
<div class="{{ $loop->even ? '' : 'lg:order-2' }}">
@if (!empty($brand['image']))
<img src="{{ theme_image_url($brand['image']) }}" alt="{{ $brand['name'] }}"
class="w-full rounded-2xl object-cover aspect-[4/3]" loading="lazy" />
@else
{{-- Platzhalter, solange die Bildmotive fehlen (offene Zulieferung). --}}
<div class="w-full aspect-[4/3] rounded-2xl bg-muted flex items-center justify-center">
<span class="text-muted-foreground text-sm">{{ $brand['name'] }}</span>
</div>
@endif
</div>
<div class="{{ $loop->even ? '' : 'lg:order-1' }}">
@if (!empty($brand['logo']))
<img src="{{ theme_image_url($brand['logo']) }}" alt="{{ $brand['name'] }}"
class="h-9 w-auto mb-5 object-contain object-left" loading="lazy" />
@endif
<h2 class="text-2xl lg:text-3xl font-medium">{{ $brand['name'] }}</h2>
<p class="text-large text-secondary font-medium mt-1">{{ $brand['tagline'] }}</p>
@foreach (preg_split('/\R{2,}/', trim((string) ($brand['summary'] ?: $brand['teaser']))) as $i => $paragraph)
<p class="text-muted-foreground leading-relaxed mt-4">{!! nl2br(e($paragraph)) !!}</p>
@endforeach
@if (!empty($brand['highlights']))
<ul class="mt-6 space-y-2 pt-6 border-t border-border/60">
@foreach ($brand['highlights'] as $i => $highlight)
<li class="flex items-start gap-3" wire:key="hl-{{ $brand['slug'] }}-{{ $i }}">
<svg class="w-5 h-5 text-secondary shrink-0 mt-0.5" fill="none" stroke="currentColor"
stroke-width="2" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg>
<span class="text-muted-foreground">{{ is_array($highlight) ? ($highlight['title'] ?? '') : $highlight }}</span>
</li>
@endforeach
</ul>
@endif
@if (!empty($brand['has_detail_page']))
<a href="{{ route('marken.show', ['slug' => $brand['slug']]) }}"
class="btn-primary-accent px-6 py-3 mt-8 inline-block">
{{ __('ui.more_about', ['name' => $brand['name']]) }}
</a>
@endif
</div>
</div>
</article>
@endforeach
</div>
</section>

View file

@ -0,0 +1,53 @@
<section class="section-padding {{ $bg }}" id="marken">
<div class="container-padding">
<div class="text-center mb-16 slide-up delay-300">
<h2 class="text-section-title">{!! $content['title'] ?? '' !!}</h2>
@if (!empty($content['subtitle']))
<p class="text-large text-muted-foreground mt-4 max-w-2xl mx-auto">{{ $content['subtitle'] }}</p>
@endif
</div>
<div class="grid md:grid-cols-3 gap-8 slide-up delay-400">
@foreach ($brands as $brand)
<div class="card-elevated rounded-2xl overflow-hidden flex flex-col"
wire:key="brand-{{ $brand['slug'] }}">
@if (!empty($brand['image']))
<img src="{{ theme_image_url($brand['image']) }}" alt="{{ $brand['name'] }}"
class="w-full h-56 object-cover" loading="lazy" />
@else
{{-- Platzhalter, solange die Bildmotive fehlen (offene Zulieferung). --}}
<div class="w-full h-56 bg-muted flex items-center justify-center">
<span class="text-muted-foreground text-sm">{{ $brand['name'] }}</span>
</div>
@endif
<div class="p-8 flex flex-col grow">
@if (!empty($brand['logo']))
<img src="{{ theme_image_url($brand['logo']) }}" alt="{{ $brand['name'] }}"
class="h-8 w-auto mb-4 object-contain object-left" loading="lazy" />
@endif
<h3 class="text-xl font-medium">{{ $brand['name'] }}</h3>
<p class="text-secondary font-medium mt-1">{{ $brand['tagline'] }}</p>
<p class="text-muted-foreground leading-relaxed mt-4 grow">{!! $brand['teaser'] !!}</p>
@if (!empty($brand['has_detail_page']))
<a href="{{ route('marken.show', ['slug' => $brand['slug']]) }}"
class="text-secondary font-medium mt-6 inline-flex items-center gap-1 hover:underline">
{{ __('ui.more_about', ['name' => $brand['name']]) }}
<span aria-hidden="true">&rarr;</span>
</a>
@endif
</div>
</div>
@endforeach
</div>
{{-- Die Startseite bleibt ein Teaser – die vollstaendige Darstellung steht auf /marken. --}}
<div class="text-center mt-12 slide-up delay-500">
<a href="{{ route('marken') }}" class="btn-primary-accent px-8 py-4 text-lg inline-block">
{{ __('ui.all_brands_cta') }}
</a>
</div>
</div>
</section>

View file

@ -0,0 +1,37 @@
{{-- Livewire braucht ein Wurzelelement. Solange kein Display veröffentlicht ist, bleibt es leer –
lieber gar keine Sektion als ein Rahmen mit ewigem Ladebalken. --}}
<div>
@if ($displayId !== null)
<section class="section-padding {{ $bg }}">
<div class="container-padding">
<div class="grid lg:grid-cols-2 gap-12 lg:gap-16 items-center">
<div>
<h2 class="text-section-title">{!! $content['title'] ?? '' !!}</h2>
<p class="text-large text-muted-foreground leading-relaxed mt-6">{{ $content['text'] ?? '' }}</p>
@if (!empty($content['note']))
<p class="text-sm text-muted-foreground mt-4">{{ $content['note'] }}</p>
@endif
@if ($showLink && !empty($content['link_text']))
<a href="{{ $content['link_url'] ?? route('referenzen') }}"
class="btn-primary-accent px-6 py-3 mt-8 inline-block">
{{ $content['link_text'] }}
</a>
@endif
</div>
{{-- 9:16 wie das physische Display im Showroom --}}
<div class="flex justify-center">
<div class="w-full max-w-[320px] aspect-[9/16] overflow-hidden rounded-2xl shadow-elevated bg-black">
<iframe src="{{ route('display.embed', ['display' => $displayId]) }}"
title="{{ $content['iframe_title'] ?? __('ui.display_feed_title') }}"
loading="lazy" scrolling="no"
class="h-full w-full border-0"></iframe>
</div>
</div>
</div>
</div>
</section>
@endif
</div>

View file

@ -0,0 +1,17 @@
{{-- Bewusst klein und dezent: wer gezielt sucht, findet den Weg; wer wegen Einrichtung
kommt, wird nicht abgelenkt. Keine Preise, keine Renditeversprechen. --}}
<section class="py-12 {{ $bg }}">
<div class="container-narrow">
<div class="border-t border-border pt-10 text-center">
<h2 class="text-xl font-medium">{!! $content['title'] ?? '' !!}</h2>
<p class="text-muted-foreground leading-relaxed mt-3 max-w-2xl mx-auto">{{ $content['text'] ?? '' }}</p>
@if (!empty($content['link_text']))
<a href="{{ $content['link_url'] ?? '/immobilien' }}"
class="text-secondary font-medium mt-4 inline-flex items-center gap-1 hover:underline">
{{ $content['link_text'] }} <span aria-hidden="true">&rarr;</span>
</a>
@endif
</div>
</div>
</section>

View file

@ -32,7 +32,7 @@
</div>
<h3 class="text-xl lg:text-2xl font-semibold text-foreground leading-tight group-hover:text-secondary transition-colors duration-200">
<a href="/magazin/{{ $post['id'] }}" class="stretched-link">
<a href="{{ route('magazin.detail', ['slug' => $post['slug']]) }}" class="stretched-link">
{!! $post['title'] !!}
</a>
</h3>
@ -44,14 +44,14 @@
<div class="flex items-center justify-between mt-6">
<div class="flex items-center text-secondary font-medium group-hover:text-secondary-dark transition-colors duration-200">
<a href="/magazin/{{ $post['id'] }}" class="text-sm">{{ $content['read_more'] }}</a>
<a href="{{ route('magazin.detail', ['slug' => $post['slug']]) }}" class="text-sm">{{ $content['read_more'] }}</a>
<svg class="w-4 h-4 ml-2 transform group-hover:translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</div>
<div class="w-10 h-10 bg-primary rounded-full flex items-center justify-center group-hover:bg-secondary transition-colors duration-200">
<a href="/magazin/{{ $post['id'] }}" aria-label="{{ __('ui.read_article', ['title' => $post['title']]) }}">
<a href="{{ route('magazin.detail', ['slug' => $post['slug']]) }}" aria-label="{{ __('ui.read_article', ['title' => $post['title']]) }}">
<svg class="w-4 h-4 text-white transform group-hover:translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>

View file

@ -0,0 +1,22 @@
<section class="section-padding bg-secondary text-secondary-foreground">
<div class="container-narrow text-center spacing-section">
<h2 class="text-section-title text-white">{!! $content['title'] ?? '' !!}</h2>
<p class="text-large max-w-2xl mx-auto leading-relaxed">{{ $content['text'] ?? '' }}</p>
<div class="pt-4 flex flex-wrap items-center justify-center gap-4">
<a href="{{ $content['button_link'] ?? '/contact' }}" class="btn-primary-accent px-8 py-4 text-lg">
{{ $content['button_text'] ?? __('ui.request_appointment') }}
</a>
{{-- WhatsApp erscheint erst, wenn die Nummer im CMS hinterlegt ist (global.contact.whatsapp).
Das Widget selbst kommt mit AP3. --}}
@php $whatsapp = whatsapp_link(); @endphp
@if ($whatsapp)
<a href="{{ $whatsapp }}" target="_blank" rel="noopener"
class="px-8 py-4 text-lg underline underline-offset-4">
{{ $content['whatsapp_text'] ?? __('ui.whatsapp') }}
</a>
@endif
</div>
</div>
</section>

View file

@ -0,0 +1,9 @@
{{-- Optionaler Block laut Spec – über das CMS-Feld `enabled` abschaltbar. --}}
@if ($content['enabled'] ?? true)
<section class="py-16 {{ $bg }}">
<div class="container-narrow text-center">
<h2 class="text-2xl font-medium">{!! $content['title'] ?? '' !!}</h2>
<p class="text-muted-foreground leading-relaxed mt-4">{{ $content['text'] ?? '' }}</p>
</div>
</section>
@endif

View file

@ -0,0 +1,46 @@
@php
if (!function_exists('renderHeroIcon')) {
function renderHeroIcon($iconName, $style = 'outline', $color = 'text-secondary')
{
$iconPath = public_path("heroicons/optimized/24/{$style}/{$iconName}.svg");
$fallbackPath = public_path('heroicons/optimized/24/outline/sparkles.svg');
if (file_exists($iconPath)) {
return str_replace('<svg', '<svg class="w-10 h-10 ' . $color . '"', file_get_contents($iconPath));
}
if (file_exists($fallbackPath)) {
return str_replace('<svg', '<svg class="w-10 h-10 ' . $color . '"', file_get_contents($fallbackPath));
}
return '';
}
}
@endphp
<section class="section-padding {{ $bg }}">
<div class="container-padding">
<div class="container-narrow text-center">
<h2 class="text-section-title">{!! $content['title'] ?? '' !!}</h2>
@foreach ($content['paragraphs'] ?? [] as $i => $paragraph)
<p class="text-large text-muted-foreground leading-relaxed mt-5" wire:key="p-{{ $i }}">
{!! $paragraph !!}
</p>
@endforeach
</div>
@if (!empty($content['points']))
<div class="grid md:grid-cols-3 gap-8 mt-16">
@foreach ($content['points'] as $index => $point)
<div class="card-elevated rounded-2xl p-8 text-center" wire:key="point-{{ $index }}">
<div class="mx-auto w-20 h-20 icon-secondary-linear glow-soft rounded-2xl flex items-center justify-center mb-5">
{!! renderHeroIcon($point['icon'] ?? 'user', 'outline', 'text-secondary-foreground') !!}
</div>
<h3 class="text-lg font-medium">{{ $point['title'] ?? '' }}</h3>
<p class="text-muted-foreground leading-relaxed mt-2">{{ $point['description'] ?? '' }}</p>
</div>
@endforeach
</div>
@endif
</div>
</section>

View file

@ -0,0 +1,36 @@
{{-- Seitenkopf für Unterseiten – abgesetzte graue Fläche wie der Hero auf Startseite und
Netzwerkseite, damit er sich klar von den Blöcken darunter absetzt. --}}
<section class="section-padding flex items-center relative border-b border-border/30 {{ $bg }}">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 lg:py-20 bg-hero-container rounded-[20px] w-[95%]">
{{-- Breadcrumb: Home → Marken --}}
<nav aria-label="Breadcrumb" class="mb-8">
<ol class="flex items-center gap-2 text-sm text-muted-foreground">
<li><a href="{{ route('home') }}" class="hover:text-foreground transition-colors">Home</a></li>
<li aria-hidden="true">/</li>
<li class="text-foreground" aria-current="page">{{ $breadcrumb }}</li>
</ol>
</nav>
@if (!empty($content['image']))
{{-- 3:2 statt hälftig geteilt – der Text bleibt der Hauptdarsteller, das Bild begleitet ihn nur. --}}
<div class="grid lg:grid-cols-5 gap-10 lg:gap-14 items-center">
<div class="lg:col-span-3 slide-right delay-200">
<h1 class="text-hero-alt">{!! $content['title'] ?? '' !!}</h1>
<p class="text-large text-muted-foreground leading-relaxed mt-6">{{ $content['text'] ?? '' }}</p>
</div>
<div class="lg:col-span-2 relative rounded-2xl overflow-hidden shadow-elevated slide-left delay-300">
<img src="{{ theme_image_url($content['image']) }}" alt="{{ $content['image_alt'] ?? '' }}"
class="w-full aspect-[4/3] object-cover" />
<div class="absolute inset-0 bg-gradient-to-t from-black/15 to-transparent"></div>
</div>
</div>
@else
{{-- Ohne Motiv bleibt der Textblock allein stehen – kein leerer Platzhalterrahmen. --}}
<div class="max-w-3xl slide-right delay-200">
<h1 class="text-hero-alt">{!! $content['title'] ?? '' !!}</h1>
<p class="text-large text-muted-foreground leading-relaxed mt-6">{{ $content['text'] ?? '' }}</p>
</div>
@endif
</div>
</section>

View file

@ -0,0 +1,48 @@
<section class="section-padding {{ $bg }}">
<div class="container-padding">
<div class="text-center max-w-2xl mx-auto">
<h2 class="text-section-title">{!! $content['title'] ?? '' !!}</h2>
@if (!empty($content['text']))
<p class="text-large text-muted-foreground leading-relaxed mt-4">{{ $content['text'] }}</p>
@endif
</div>
@if ($projects)
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 mt-12">
@foreach ($projects as $project)
<figure class="card-elevated rounded-2xl overflow-hidden group" wire:key="ref-{{ $project['key'] }}">
<div class="relative overflow-hidden">
<img src="{{ theme_image_url($project['image']) }}"
alt="{{ $project['caption'] ?: $project['brand'] }}" loading="lazy"
class="w-full aspect-[4/3] object-cover transition-transform duration-300 group-hover:scale-105" />
<span class="absolute top-3 left-3 rounded-full bg-black/60 px-3 py-1 text-xs font-medium text-white backdrop-blur-sm">
{{ __('ui.brand_gallery_badge_project') }}
</span>
</div>
<figcaption class="p-5">
@if (!empty($project['caption']))
<p class="font-medium">{{ $project['caption'] }}</p>
@endif
@if ($project['has_detail_page'])
<a href="{{ route('marken.show', ['slug' => $project['brand_slug']]) }}"
class="text-sm text-secondary underline underline-offset-2 hover:no-underline mt-1 inline-block">
{{ $project['brand'] }}
</a>
@else
<p class="text-sm text-muted-foreground mt-1">{{ $project['brand'] }}</p>
@endif
</figcaption>
</figure>
@endforeach
</div>
@else
{{-- Ehrlicher Leerzustand statt einer erfundenen Referenz. --}}
<div class="mt-12 rounded-2xl border border-dashed border-border p-10 text-center">
<p class="text-muted-foreground">{{ $content['empty'] ?? __('ui.references_empty') }}</p>
<a href="{{ route('contact') }}" class="btn-primary-accent px-6 py-3 mt-6 inline-block">
{{ __('ui.request_appointment') }}
</a>
</div>
@endif
</div>
</section>

View file

@ -0,0 +1,33 @@
<section class="section-padding {{ $bg }}">
<div class="container-padding">
<div class="text-center mb-12 slide-up delay-300">
<h2 class="text-section-title">{!! $content['title'] ?? '' !!}</h2>
@if (!empty($content['subtitle']))
<p class="text-large text-muted-foreground mt-4 max-w-2xl mx-auto">{{ $content['subtitle'] }}</p>
@endif
</div>
{{-- Kachel je Verein: Logo oben auf Weiß (die Logos sind für weißen Grund gemacht),
darunter ein kurzer Text. Bewusst ohne Verlinkung – reine Nennung. --}}
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-8 max-w-5xl mx-auto slide-up delay-400">
@foreach ($content['logos'] ?? [] as $index => $logo)
<div class="card-elevated rounded-2xl overflow-hidden" wire:key="sponsor-{{ $index }}">
{{-- Feste Bühnenhöhe und Deckel in beide Richtungen: die Logos haben sehr
unterschiedliche Seitenverhältnisse (1:1 bis 3,3:1) und wirken sonst
unterschiedlich groß. --}}
<div class="bg-white flex items-center justify-center h-36 px-8">
<img src="{{ theme_image_url($logo['image']) }}" alt="{{ $logo['name'] ?? '' }}"
loading="lazy" class="max-h-20 max-w-[85%] w-auto object-contain" />
</div>
<div class="p-6 border-t border-border/60">
<h3 class="font-medium">{{ $logo['name'] ?? '' }}</h3>
@if (!empty($logo['text']))
<p class="text-sm text-muted-foreground leading-relaxed mt-2">{{ $logo['text'] }}</p>
@endif
</div>
</div>
@endforeach
</div>
</div>
</section>

View file

@ -1,5 +1,16 @@
@if (!empty($content) && ($content['enabled'] ?? false))
<div x-data="{ dismissed: localStorage.getItem('announcement_dismissed_{{ $content['id'] ?? 'default' }}') === 'true' }" x-show="!dismissed" x-transition:leave="transition ease-in duration-200"
{{-- Kein x-cloak: Livewire setzt global [x-cloak]{display:none!important}. Mit x-cloak wuerde
die Leiste dauerhaft verschwinden, sobald Alpine das Attribut nicht entfernt. Ohne Alpine
ist sie sichtbar – das ist hier der richtige Standardfall.
Der localStorage-Zugriff steht bewusst im x-data (kein Aufblitzen) und ist abgesichert,
weil er im privaten Modus oder bei blockiertem Storage wirft. --}}
<div x-data="(() => {
const key = @js($this->dismissKey);
let dismissed = false;
try { dismissed = localStorage.getItem(key) === 'true'; } catch (e) {}
return { key, dismissed };
})()"
x-show="!dismissed" x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 max-h-16" x-transition:leave-end="opacity-0 max-h-0"
class="bg-primary text-secondary-foreground overflow-hidden" id="topbar">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
@ -28,7 +39,7 @@
@endif
<button
@click="dismissed = true; localStorage.setItem('announcement_dismissed_{{ $content['id'] ?? 'default' }}', 'true')"
@click="dismissed = true; try { localStorage.setItem(key, 'true') } catch (e) {}"
class="absolute right-0 top-1/2 -translate-y-1/2 p-1.5 text-white/60 hover:text-white transition-colors"
aria-label="{{ __('ui.announcement_close') }}">
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">

View file

@ -0,0 +1,175 @@
{{-- Schwebende Terminanfrage, unten rechts. Der Cookie-Button des Consent-Managers sitzt unten
links (z-40), sein Dialog auf z-50 – dieses Widget bleibt darunter, damit die Einwilligung
immer erreichbar bleibt. --}}
<div class="fixed bottom-4 right-4 z-40 flex flex-col items-end gap-3 print:hidden"
x-data="{
open: false,
whatsappBase: @js($this->whatsappUrl),
topicLabels: @js($this->topics),
greeting: @js($content['whatsapp_text'] ?? __('ui.appointment.whatsapp_text')),
/* Baut den Nachrichtentext aus dem, was schon im Formular steht. Der Besucher sieht ihn
in WhatsApp und schickt ihn selbst ab – wir verschicken nichts automatisch. */
get whatsappHref() {
const lines = [this.greeting];
const topic = this.topicLabels[$wire.topic];
if (topic) { lines.push('Anliegen: ' + topic) }
if ($wire.preferredDate) { lines.push('Wunschtermin: ' + $wire.preferredDate) }
if ($wire.name) { lines.push('Mein Name: ' + $wire.name) }
return this.whatsappBase + '?text=' + encodeURIComponent(lines.join('\n'));
},
}" x-on:keydown.escape.window="open = false">
{{-- Panel --}}
<div x-show="open" x-cloak x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 translate-y-2" x-transition:leave="transition ease-in duration-150"
x-transition:leave-end="opacity-0 translate-y-2" x-on:click.outside="open = false"
role="dialog" aria-modal="true" aria-labelledby="appointment-widget-title"
class="card-elevated w-[calc(100vw-2rem)] sm:w-[26rem] max-h-[80vh] overflow-y-auto rounded-2xl p-6">
<div class="flex items-start justify-between gap-4">
<h2 id="appointment-widget-title" class="text-xl font-medium">
{{ $content['title'] ?? __('ui.request_appointment') }}
</h2>
<button type="button" x-on:click="open = false" aria-label="{{ __('ui.close') }}"
class="shrink-0 text-muted-foreground hover:text-foreground transition-colors cursor-pointer">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"
aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
@if ($success)
<div class="mt-6 rounded-lg border border-green-200 bg-green-50 p-4 text-sm text-green-800">
{{ $content['success'] ?? __('ui.appointment.success') }}
</div>
<button type="button" wire:click="startOver"
class="btn-secondary-accent w-full mt-4 px-6 py-3 cursor-pointer">
{{ __('ui.appointment.new_request') }}
</button>
@else
@if (!empty($content['intro']))
<p class="text-sm text-muted-foreground leading-relaxed mt-2">{{ $content['intro'] }}</p>
@endif
<form wire:submit="submit" class="space-y-4 mt-6">
<div>
<label for="appointment-name" class="text-foreground mb-2 block text-sm font-medium">
{{ __('ui.appointment.name') }} <span aria-hidden="true">*</span>
</label>
<input wire:model="name" id="appointment-name" type="text" required
class="w-full px-3 py-2 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-gray-600 sm:text-sm/6" />
@error('name') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div>
<label for="appointment-email" class="text-foreground mb-2 block text-sm font-medium">
{{ __('ui.appointment.email') }} <span aria-hidden="true">*</span>
</label>
<input wire:model="email" id="appointment-email" type="email" required
class="w-full px-3 py-2 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-gray-600 sm:text-sm/6" />
@error('email') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div>
<label for="appointment-phone" class="text-foreground mb-2 block text-sm font-medium">
{{ __('ui.appointment.phone') }}
</label>
<input wire:model="phone" id="appointment-phone" type="tel"
class="w-full px-3 py-2 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-gray-600 sm:text-sm/6" />
@error('phone') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div>
<label for="appointment-topic" class="text-foreground mb-2 block text-sm font-medium">
{{ __('ui.appointment.topic') }}
</label>
<select wire:model="topic" id="appointment-topic"
class="w-full px-3 py-2 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-gray-600 sm:text-sm/6">
<option value="">{{ __('ui.appointment.topic_placeholder') }}</option>
@foreach ($this->topics as $value => $label)
<option value="{{ $value }}">{{ $label }}</option>
@endforeach
</select>
@error('topic') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div>
<label for="appointment-date" class="text-foreground mb-2 block text-sm font-medium">
{{ __('ui.appointment.preferred_date') }}
</label>
<input wire:model="preferredDate" id="appointment-date" type="date"
min="{{ now()->toDateString() }}"
class="w-full px-3 py-2 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-gray-600 sm:text-sm/6" />
@error('preferredDate') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div>
<label for="appointment-message" class="text-foreground mb-2 block text-sm font-medium">
{{ __('ui.appointment.message') }}
</label>
<textarea wire:model="message" id="appointment-message" rows="3"
placeholder="{{ __('ui.appointment.message_placeholder') }}"
class="w-full px-3 py-2 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-gray-600 sm:text-sm/6 resize-none"></textarea>
@error('message') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
{{-- Honeypot --}}
<div class="opacity-0 absolute top-0 left-0 h-0 w-0 -z-10 overflow-hidden" aria-hidden="true">
<label for="appointment-website">Website</label>
<input type="text" id="appointment-website" wire:model="website" tabindex="-1" autocomplete="off" />
</div>
<label class="flex items-start gap-3 cursor-pointer">
<input wire:model="privacy" id="appointment-privacy" type="checkbox"
class="mt-1 shrink-0 rounded border-border" />
<span class="text-sm text-muted-foreground">
{{ __('ui.contact_form.privacy_prefix') }}
<a href="{{ route('privacy') }}" target="_blank"
class="text-secondary underline underline-offset-2 hover:no-underline">Datenschutzerklärung</a>
{{ __('ui.contact_form.privacy_suffix') }}
</span>
</label>
@error('privacy') <span class="text-red-500 text-xs block">{{ $message }}</span> @enderror
<button type="submit" wire:loading.attr="disabled"
class="btn-primary-accent w-full px-6 py-3 cursor-pointer disabled:opacity-70">
<span wire:loading.remove wire:target="submit">{{ __('ui.appointment.submit') }}</span>
<span wire:loading wire:target="submit">{{ __('ui.appointment.sending') }}</span>
</button>
</form>
{{-- Alternative: WhatsApp. Nur sichtbar, wenn im CMS eine Nummer hinterlegt ist.
Der Link ist ein normaler Klick-zum-Chat-Link – auf dem Handy öffnet die
installierte App, sonst WhatsApp Web. Der Text ist vorbereitet, abgeschickt
wird er vom Besucher selbst. --}}
@if ($this->whatsappUrl !== '')
<div class="mt-6 pt-6 border-t border-border/60">
<p class="text-sm text-muted-foreground">{{ __('ui.appointment.whatsapp_intro') }}</p>
<a x-bind:href="whatsappHref" target="_blank" rel="noopener"
class="mt-3 flex w-full items-center justify-center gap-2 rounded-lg bg-[#25D366] px-6 py-3 font-medium text-white transition-transform hover:scale-[1.02]">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.372-.025-.521-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51l-.57-.01c-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.872.118.571-.085 1.758-.719 2.006-1.413.247-.694.247-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884a9.82 9.82 0 016.988 2.896 9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.885-9.885 9.885M20.52 3.449C18.24 1.245 15.24 0 12.045 0 5.463 0 .104 5.359.101 11.945c0 2.096.549 4.142 1.595 5.945L0 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.582 0 11.941-5.359 11.944-11.945a11.87 11.87 0 00-3.417-8.4" />
</svg>
{{ __('ui.whatsapp') }}
</a>
<p class="mt-2 text-xs text-muted-foreground">{{ __('ui.appointment.whatsapp_note') }}</p>
</div>
@endif
@endif
</div>
<div class="flex items-center gap-3">
<button type="button" x-on:click="open = !open" x-bind:aria-expanded="open.toString()"
aria-controls="appointment-widget-title"
class="btn-secondary-accent flex items-center gap-2 rounded-full px-5 py-3 shadow-lg cursor-pointer">
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"
aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" />
</svg>
<span x-show="!open">{{ $content['button_label'] ?? __('ui.request_appointment') }}</span>
<span x-show="open" x-cloak>{{ __('ui.close') }}</span>
</button>
</div>
</div>

View file

@ -5,38 +5,38 @@
<div class="spacing-section">
<div class="flex items-center justify-center">
<img src="{{ asset(\App\Helpers\ThemeHelper::getLogoPath('negative')) }}"
alt="{{ $domainName ?? 'B2in' }} Logo" class="h-14 w-auto"
width="120" height="56"
alt="{{ $domainName ?? 'B2in Interior' }} Logo" class="h-16 w-auto"
width="81" height="64"
loading="lazy" />
</div>
<div class="container-narrow spacing-content">
<h2 class="text-section-title text-dark-text leading-tight">
Connecting Design and <span class="text-secondary">Property</span>
{!! $content['claim'] ?? 'Premium-Einrichtung. <span class="text-secondary">Persönlich gemacht.</span>' !!}
</h2>
<p class="text-dark-muted text-sm mt-4">Marcel Scheibe – {{ __('ui.founder_ceo') }}</p>
<p class="text-dark-muted text-sm mt-4">{{ $content['byline'] ?? 'Marcel Scheibe' }} – {{ __('ui.founder_ceo') }}</p>
</div>
<hr class="border-t border-dark-muted/30 mt-12 pt-4">
</div>
{{-- Links --}}
<div class="grid grid-cols-2 md:grid-cols-4 gap-8 text-left max-w-4xl mx-auto">
<div class="spacing-small text-center">
<a href="{{ route('privacy') }}" class="block hover-text-secondary transition-colors">Privacy Policy</a>
</div>
<div class="spacing-small text-center">
<a href="{{ route('terms') }}" class="block hover-text-secondary transition-colors">Terms of Service</a>
</div>
<div class="spacing-small text-center">
<a href="{{ route('cookie-policy') }}" class="block hover-text-secondary transition-colors">Cookie Policy</a>
</div>
<div class="spacing-small text-center">
<a href="{{ route('impressum') }}" class="block hover-text-secondary transition-colors">{{ __('ui.legal_notice') }}</a>
</div>
@php
$footerLinks = $content['links'] ?? [
['label' => __('ui.faq'), 'url' => '/faq'],
['label' => __('ui.privacy_policy'), 'url' => '/privacy'],
['label' => __('ui.terms_of_service'), 'url' => '/terms'],
['label' => __('ui.cookie_policy'), 'url' => '/cookie-policy'],
['label' => __('ui.legal_notice'), 'url' => '/impressum'],
];
@endphp
<div class="grid grid-cols-2 md:grid-cols-5 gap-8 text-left max-w-4xl mx-auto">
@foreach ($footerLinks as $footerLink)
<div class="spacing-small text-center" wire:key="footer-link-{{ $loop->index }}">
<a href="{{ $footerLink['url'] }}"
class="block hover-text-secondary transition-colors">{{ $footerLink['label'] }}</a>
</div>
@endforeach
</div>
</div>

View file

@ -3,7 +3,7 @@
<div class="flex items-center justify-between h-16">
<a href="{{ $domainUrl }}" class="flex items-center">
<img src="{{ asset(\App\Helpers\ThemeHelper::getLogoPath('positive')) }}"
alt="{{ $domainName ?? 'B2IN' }} Logo" class="h-10 w-auto" />
alt="{{ $domainName ?? 'B2in Interior' }} Logo" class="h-12 w-auto" />
</a>
<nav class="hidden md:flex items-center space-x-8" aria-label="{{ __('ui.main_navigation') }}">