b2in/resources/views/livewire/web/components/sections/brand-detail.blade.php
Kevin Adametz 4ef3a4d8f1 Markenseiten Rotpunkt und Febrü aus der Zulieferung vom 31.08.
Umsetzung der beiden finalen Webseitentexte aus dev/2026-08-28/.
Die angekündigte Aufgabenliste umsetzung-03-09-2026.md war leer, Grundlage
sind daher die Umsetzungshinweise in den PDFs selbst.

- Neue Felder in cms_brands: sections, widget_label, cta_title, cta_text.
  Einleitung und Leistungskacheln reichten für diese Texte nicht aus.
- Die als Vertiefung gelieferten Blöcke werden aufklappbar dargestellt,
  mit aria-expanded und aria-controls. Bewusst x-transition statt
  x-collapse, weil das Collapse-Plugin hier nicht registriert ist.
- Kein zweiter CTA-Button: das schwebende Terminanfrage-Widget trägt auf
  der Markenseite die Beschriftung der Marke und liest den Kontext aus
  der Route, da es im Layout und nicht je Seite eingebunden ist.
- Footer bekommt unten Platz und lange Beschriftungen brechen um, damit
  das Widget auf dem Smartphone nichts verdeckt.
- Abschnitte sind im Admin vollständig pflegbar, inklusive Sortierung.
- Interne Quellenhinweise der PDFs sind nicht übernommen; ein Test prüft,
  dass sie nicht im Ausgabe-HTML stehen.
- BrandPageTextSeeder fasst nur die Textfelder an, Bilder, Galerie und
  Leistungen bleiben unangetastet.

Vor der Veröffentlichung: Herstellerfreigabe einholen und die Aussage zur
erhaltenen Waldfläche nur mit ausdrücklicher Bestätigung verwenden. Beides
steht in der Deployment-Checkliste.

854 Tests grün (3150 Assertions), Pint sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 08:52:36 +00:00

324 lines
19 KiB
PHP

{{-- 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
{{-- 2b. Abschnitte der Markenseite. Die als Vertiefung gelieferten Blöcke sind aufklappbar,
damit die Seite schnell erfassbar bleibt, ohne dass Inhalt verloren geht. --}}
@if (!empty($brand['sections']))
<section class="pb-16">
<div class="container-narrow space-y-14">
@foreach ($brand['sections'] as $index => $section)
<div wire:key="section-{{ $index }}">
@if (!empty($section['title']))
<h2 class="text-2xl lg:text-3xl font-medium">{{ $section['title'] }}</h2>
@endif
@foreach ($section['paragraphs'] as $i => $paragraph)
<p class="text-muted-foreground leading-relaxed mt-4">{{ $paragraph }}</p>
@endforeach
@if (!empty($section['quote']))
<figure class="mt-8 border-l-4 border-secondary pl-6">
<blockquote class="text-large leading-relaxed italic">„{{ $section['quote'] }}"</blockquote>
@if (!empty($section['quote_author']))
<figcaption class="text-sm text-muted-foreground mt-3">{{ $section['quote_author'] }}</figcaption>
@endif
</figure>
@endif
@if (!empty($section['detail']))
<div class="mt-6 card-elevated rounded-2xl overflow-hidden"
x-data="{ open: false }">
<button type="button" x-on:click="open = !open"
x-bind:aria-expanded="open.toString()"
aria-controls="detail-{{ $index }}"
class="flex w-full items-center justify-between gap-4 p-5 text-left cursor-pointer hover:bg-muted/30 transition-colors">
<span class="font-medium">{{ $section['detail']['title'] }}</span>
<svg class="h-5 w-5 shrink-0 text-secondary transition-transform duration-200"
x-bind:class="open ? 'rotate-180' : ''" fill="none" stroke="currentColor"
stroke-width="2" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
</svg>
</button>
{{-- x-transition statt x-collapse: das Collapse-Plugin ist in diesem
Projekt nicht registriert, dieselbe Bewegung wie im FAQ-Akkordeon. --}}
<div id="detail-{{ $index }}" x-show="open" x-cloak
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 -translate-y-2"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-end="opacity-0 -translate-y-2">
<div class="px-5 pb-5 border-t border-border/60 pt-4">
@foreach ($section['detail']['paragraphs'] as $paragraph)
<p class="text-muted-foreground leading-relaxed mt-3 first:mt-0">{{ $paragraph }}</p>
@endforeach
</div>
</div>
</div>
@endif
@if (!empty($section['claim']))
<p class="text-large text-secondary font-medium mt-6">{{ $section['claim'] }}</p>
@endif
</div>
@endforeach
</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">
{{ $brand['cta_title'] ?: __('ui.brand_cta_title', ['name' => $brand['name']]) }}
</h2>
<p class="text-large max-w-2xl mx-auto leading-relaxed">
{{ $brand['cta_text'] ?: __('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>
</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>