347 lines
21 KiB
PHP
347 lines
21 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">←</span> {{ __('ui.all_brands') }}
|
||
</a>
|
||
|
||
<div class="grid lg:grid-cols-2 gap-12 items-center">
|
||
<div>
|
||
{{-- Liegt ein Logo vor, steht es anstelle des Schriftzugs. Es sitzt im <h1>,
|
||
damit die Ueberschrift erhalten bleibt – den Text liefert das alt-Attribut.
|
||
Feste Box statt fester Hoehe: die Logos haben sehr unterschiedliche
|
||
Seitenverhaeltnisse (Februe breit, Rotpunkt fast quadratisch). --}}
|
||
@if (!empty($brand['logo']))
|
||
<h1>
|
||
<img src="{{ theme_image_url($brand['logo']) }}" alt="{{ $brand['name'] }}"
|
||
class="h-24 sm:h-[7.2rem] max-w-[17rem] w-auto object-contain object-left" />
|
||
</h1>
|
||
@else
|
||
<h1 class="text-section-title">{{ $brand['name'] }}</h1>
|
||
@endif
|
||
|
||
<p class="text-large text-secondary font-medium mt-4">{{ $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['anchor'])) id="{{ $section['anchor'] }}" @endif>
|
||
@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="group flex w-full items-center justify-between gap-4 p-5 text-left cursor-pointer">
|
||
<span class="font-medium transition-colors duration-300 group-hover:text-secondary">{{ $section['detail']['title'] }}</span>
|
||
<svg class="h-5 w-5 shrink-0 text-secondary transition-transform duration-500 ease-[cubic-bezier(0.33,1,0.68,1)]"
|
||
x-bind:class="{ 'rotate-180': open }" 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>
|
||
|
||
{{-- Weiches Auf- und Zuklappen ueber grid-template-rows (0fr → 1fr):
|
||
das animiert die tatsaechliche Hoehe, waehrend x-show den Inhalt
|
||
schlagartig einblendet. Das Alpine-Collapse-Plugin ist in diesem
|
||
Projekt nicht registriert, dies kommt ohne Abhaengigkeit aus.
|
||
Der zugeklappte Zustand steht auch im statischen class-Attribut,
|
||
damit vor dem Start von Alpine nichts aufgeklappt aufblitzt – die
|
||
Objekt-Schreibweise von x-bind:class entfernt ihn beim Oeffnen. --}}
|
||
<div id="detail-{{ $index }}"
|
||
class="grid grid-rows-[0fr] opacity-0 transition-all duration-500 ease-[cubic-bezier(0.33,1,0.68,1)]"
|
||
x-bind:class="{ 'grid-rows-[1fr] opacity-100': open, 'grid-rows-[0fr] opacity-0': !open }"
|
||
x-bind:aria-hidden="(!open).toString()">
|
||
<div class="overflow-hidden">
|
||
<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>
|
||
</div>
|
||
@endif
|
||
|
||
@if (!empty($section['claim']))
|
||
<p class="text-large text-secondary font-medium mt-6">{{ $section['claim'] }}</p>
|
||
@endif
|
||
|
||
@if (!empty($section['link']) && !empty($section['link_text']))
|
||
<a href="{{ $section['link'] }}"
|
||
class="text-secondary font-medium mt-4 inline-flex items-center gap-1 hover:underline">
|
||
{{ $section['link_text'] }}
|
||
<span aria-hidden="true">→</span>
|
||
</a>
|
||
@endif
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
</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, Einblicke in die Werkstatt und Visualisierungen stehen in
|
||
einem Raster, unterschieden nur durch das Badge – der Rubrikwechsel ist ausgesetzt
|
||
(Abstimmung 22.09., Nachtrag 17). Reihenfolge: Projekte, Werkstatt, Visualisierungen.
|
||
Überschrift und Zeile darunter richten sich nach dem, was die Galerie enthält (Nachtrag 19):
|
||
nur Visualisierungen → „Inspirationen und Entwürfe“, nur Projekte → „Referenzen“, Projekte
|
||
und Werkstatt → „Referenzen und Einblicke“, mit Visualisierungen → „Projekte und Entwürfe“. --}}
|
||
@php
|
||
$galleryItems = array_merge($gallery['projects'] ?? [], $gallery['company'] ?? [], $gallery['inspirations'] ?? []);
|
||
|
||
$galleryHasReal = ! empty($gallery['projects']) || ! empty($gallery['company']);
|
||
$galleryHasDrafts = ! empty($gallery['inspirations']);
|
||
$galleryVariant = match (true) {
|
||
! $galleryHasReal => 'inspirations',
|
||
$galleryHasDrafts => 'mixed',
|
||
! empty($gallery['company']) => 'projects_company',
|
||
default => 'projects',
|
||
};
|
||
|
||
$galleryLightbox = collect($galleryItems)->map(fn (array $item) => [
|
||
'src' => theme_image_url($item['image']),
|
||
'caption' => $item['caption'] ?? '',
|
||
'badge' => \App\Models\CmsBrand::galleryBadge($item['category']),
|
||
])->values()->all();
|
||
@endphp
|
||
|
||
@if ($galleryItems)
|
||
<section id="galerie" class="section-padding"
|
||
x-data="{
|
||
items: @js($galleryLightbox),
|
||
lightbox: false,
|
||
current: 0,
|
||
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_'.$galleryVariant) }}</h2>
|
||
<p class="text-muted-foreground leading-relaxed mt-4">{{ __('ui.brand_gallery_intro_'.$galleryVariant) }}</p>
|
||
</div>
|
||
|
||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 mt-12">
|
||
@foreach ($galleryItems as $index => $item)
|
||
<figure wire:key="gallery-{{ $index }}" class="group">
|
||
<button type="button" x-on:click="open({{ $index }})"
|
||
class="relative block w-full overflow-hidden rounded-2xl cursor-pointer">
|
||
<img src="{{ theme_image_url($item['image']) }}"
|
||
alt="{{ $item['caption'] ?: $brand['name'] }}" loading="lazy"
|
||
class="w-full aspect-[4/3] object-cover transition-transform duration-300 group-hover:scale-105" />
|
||
<span class="absolute top-3 left-3 rounded-full bg-black/60 px-3 py-1 text-xs font-medium text-white backdrop-blur-sm">
|
||
{{ \App\Models\CmsBrand::galleryBadge($item['category']) }}
|
||
</span>
|
||
</button>
|
||
@if (!empty($item['caption']))
|
||
<figcaption class="text-sm text-muted-foreground mt-3">{{ $item['caption'] }}</figcaption>
|
||
@endif
|
||
</figure>
|
||
@endforeach
|
||
</div>
|
||
</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. Abschluss. Der Block schliesst die Seite ab und traegt den Claim aus der Zulieferung.
|
||
Der durchgehende Verlauf steckt in `.cta-band` (shared-styles.css) – als Utility-Klassen
|
||
waere er nicht ueber alle Themes hinweg zu halten. --}}
|
||
<section class="section-padding cta-band text-secondary-foreground">
|
||
<div class="container-narrow relative text-center">
|
||
{{-- Haarlinie als Auftakt, laeuft zu beiden Seiten aus. --}}
|
||
<span aria-hidden="true"
|
||
class="mx-auto block h-px w-24 bg-gradient-to-r from-transparent via-white/50 to-transparent"></span>
|
||
|
||
<h2 class="text-section-title text-white mt-8 text-balance">
|
||
{{ $brand['cta_title'] ?: __('ui.brand_cta_title', ['name' => $brand['name']]) }}
|
||
</h2>
|
||
<p class="text-large text-white/85 max-w-2xl mx-auto leading-relaxed mt-5">
|
||
{{ $brand['cta_text'] ?: __('ui.brand_cta_text') }}
|
||
</p>
|
||
|
||
<div class="mt-10 flex flex-col sm:flex-row flex-wrap items-center justify-center gap-4">
|
||
{{-- Oeffnet das Widget mit dieser Marke als Anliegen. --}}
|
||
<x-appointment-link :href="route('contact')" :topic="$brand['slug']"
|
||
class="group inline-flex items-center justify-center gap-2 rounded-lg bg-white px-8 py-4 text-lg font-medium text-secondary shadow-[0_12px_30px_-14px_rgba(0,0,0,0.65)] transition-all duration-300 hover:-translate-y-0.5 hover:shadow-[0_18px_38px_-14px_rgba(0,0,0,0.7)]">
|
||
{{ __('ui.request_appointment') }}
|
||
<svg class="h-5 w-5 transition-transform duration-300 group-hover:translate-x-1" fill="none"
|
||
stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true">
|
||
<path stroke-linecap="round" stroke-linejoin="round" d="M13 7l5 5-5 5M6 12h12" />
|
||
</svg>
|
||
</x-appointment-link>
|
||
|
||
@if (!empty($brand['website_url']))
|
||
<a href="{{ $brand['website_url'] }}" target="_blank" rel="noopener"
|
||
class="inline-flex items-center justify-center gap-2 rounded-lg border border-white/25 px-8 py-4 text-lg text-white/90 transition-colors duration-300 hover:border-white/50 hover:text-white">
|
||
{{ __('ui.brand_website') }}
|
||
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"
|
||
aria-hidden="true">
|
||
<path stroke-linecap="round" stroke-linejoin="round"
|
||
d="M13.5 6H18v4.5M17.5 6.5L10 14M15 14.5V18H6V9h3.5" />
|
||
</svg>
|
||
</a>
|
||
@endif
|
||
</div>
|
||
|
||
@if (!empty($brand['showroom']))
|
||
<p class="mt-10 inline-flex items-center gap-2 text-sm text-white/65">
|
||
<svg class="h-4 w-4" 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>
|
||
</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>
|
||
|
||
{{-- Drei weitere Marken: nebeneinander ab lg, darunter zweispaltig. --}}
|
||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-8 max-w-6xl 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">
|
||
@if (!empty($other['logo']))
|
||
<img src="{{ theme_image_url($other['logo']) }}" alt="{{ $other['name'] }}"
|
||
class="h-[3.3rem] max-w-[10rem] w-auto mb-4 object-contain object-left" loading="lazy" />
|
||
@endif
|
||
|
||
<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>
|