Web-Frontend: Umbau Spokes/Hub, Kontakt-Widget (contact-launcher)

Startseiten/Feeds/Kategorie/Rechtsseiten ueberarbeitet, Kontakt-Modal durch
schwebendes contact-launcher-Widget ersetzt, legal-language-notice ergaenzt;
Platzhalter-SVGs + Theme-CSS angepasst.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-09 15:45:58 +00:00
parent fd8e050cb7
commit d1939f6f2a
39 changed files with 1110 additions and 810 deletions

View file

@ -33,15 +33,20 @@
$readTime = max(1, (int) round($wordCount / 200));
$teaser = (string) Str::of(strip_tags((string) $release->text))->squish()->limit(180);
$lead = filled($release->subtitle)
? $release->subtitle
: $teaser;
// Subline nur bei explizit gesetztem Untertitel — kein Fallback auf den Textanfang,
// sonst erscheint der erste Absatz doppelt. Der Teaser dient nur der Meta-Description.
$lead = $release->subtitle;
$image = $release->images->first();
$imageUrl = $image?->variantUrl('detail') ?? $image?->variantUrl('card') ?? $image?->url();
$imageCaption = $image?->description ?: $image?->title;
$imageCredit = $image?->copyright ?: $image?->author;
// Ohne Pressefoto: die im User-Panel gewählte Platzhalter-Variante.
// Null bei Altbeständen ohne Variante — dann generischer Hero-Fallback.
$placeholderUrl = $imageUrl ? null : $release->chosenPlaceholderUrl();
$companyName = $company?->name;
$companyInitial = $companyName ? mb_strtoupper(mb_substr($companyName, 0, 1)) : '·';
$companyHref = $company?->slug ? route('newsroom', ['slug' => $company->slug]) : route('newsrooms');
@ -75,7 +80,7 @@
@endphp
@section('title', $release->title.' – '.$brandLabel)
@section('meta_description', $lead)
@section('meta_description', filled($lead) ? $lead : $teaser)
@section('og_type', 'article')
@if ($imageUrl)
@section('og_image', $imageUrl)
@ -154,6 +159,83 @@
</nav>
</div>
@if (! $imageUrl)
{{-- Ohne Pressefoto: Headline direkt in der Platzhalter-Fläche der PM
(gewählte Variante wie im User-Panel, sonst generischer Fallback). --}}
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 pt-[18px]">
<article class="relative overflow-hidden bg-hero-grad">
@if ($placeholderUrl)
<img src="{{ $placeholderUrl }}" alt="" aria-hidden="true"
class="absolute inset-0 h-full w-full object-cover" loading="eager">
@else
<x-web.hero-fallback />
@endif
@if ($embargoAt)
<div class="absolute top-5 left-5 sm:top-6 sm:left-9 flex items-center gap-2.5 flex-wrap">
<span class="text-[9.5px] font-bold tracking-[0.18em] uppercase text-white px-2.5 py-[3px] border border-white/50 font-mono bg-black/25">
● {{ __('web.detail.embargo_lifted') }}
</span>
<span class="text-[11px] text-white/70 font-mono">{{ $embargoAt->translatedFormat('j. F Y, H:i') }}</span>
</div>
@endif
<div class="relative px-5 sm:px-9 pt-[110px] sm:pt-[150px] pb-7 sm:pb-9"
style="background:linear-gradient(180deg,transparent,rgba(0,0,0,0.55) 55%,rgba(0,0,0,0.82));">
<div class="flex items-center gap-3.5 mb-4 flex-wrap">
@if ($categoryName)
<a href="{{ $categoryHref }}" class="bp-cat cursor-pointer transition-colors" style="color:#FF8B6F;">{{ $categoryName }}</a>
<span class="w-1 h-1 rounded-full bg-white/40" aria-hidden="true"></span>
@endif
<span class="text-[11px] text-white/60 italic font-serif">{{ __('web.detail.audience') }}</span>
</div>
<h1 class="font-serif text-[30px] sm:text-[38px] lg:text-[44px] font-semibold m-0 tracking-[-0.8px] leading-[1.1] max-w-[920px] text-white" style="text-wrap:balance;">
{{ $release->title }}
</h1>
@if (filled($lead))
<p class="font-serif text-[16.5px] sm:text-[19px] leading-[1.45] m-0 mt-5 text-white/85 max-w-[820px] font-normal" style="text-wrap:pretty;">
{{ $lead }}
</p>
@endif
<div class="flex flex-wrap items-center text-[12.5px] text-white/75 mt-6" style="gap:10px 22px;">
@if ($companyName)
<span class="inline-flex items-center gap-2">
<span class="w-7 h-7 bg-white text-ink inline-flex items-center justify-center text-[13px] font-bold font-serif">{{ $companyInitial }}</span>
<span class="font-semibold text-white">{{ $companyName }}</span>
</span>
<span class="w-px h-[14px] bg-white/25" aria-hidden="true"></span>
@endif
@if ($publishedAt)
<time datetime="{{ $publishedAt->toIso8601String() }}" class="font-mono text-[11.5px] text-white/65">
{{ $publishedAt->translatedFormat('j. F Y') }} · {{ __('web.detail.time_at', ['time' => $publishedAt->format('H:i')]) }}
</time>
@endif
<span class="font-mono text-[11.5px] text-white/65">{{ __('web.shared.read_time', ['minutes' => $readTime]) }} · {{ __('web.detail.words', ['count' => number_format($wordCount, 0, ',', '.')]) }}</span>
@if ($companyCountry)
<span class="inline-flex items-center gap-1.5 font-mono text-[11.5px] text-white/65">
<svg width="11" height="11" viewBox="0 0 14 14" fill="none" aria-hidden="true"><path d="M7 1a5 5 0 015 5c0 4-5 7-5 7s-5-3-5-7a5 5 0 015-5z" stroke="currentColor" stroke-width="1.3" fill="none" /><circle cx="7" cy="6" r="1.5" fill="currentColor" /></svg>
{{ $companyCountry }}
</span>
@endif
@if ($isRecommended)
<span class="inline-flex items-center gap-1.5 px-2 py-[3px] text-[9.5px] font-bold tracking-[0.14em] uppercase text-white border border-white/40 font-mono cursor-help"
title="{{ __('web.detail.recommended_hint') }}">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M12 2l9 4v6c0 5-3.5 9-9 10-5.5-1-9-5-9-10V6z" stroke="currentColor" stroke-width="2" fill="none" />
<path d="M8 12l3 3 5-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
{{ __('web.detail.recommended') }}
</span>
@endif
</div>
</div>
</article>
</section>
@else
{{-- Hero Meta Block --}}
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8">
<div class="pt-[18px] pb-8 border-b border-bg-rule">
@ -221,15 +303,9 @@
{{-- Hero Image --}}
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 pt-8">
<div class="relative flex items-end p-[18px] bg-bg-dark hatch-dark overflow-hidden" style="aspect-ratio:21/9;">
@if ($imageUrl)
<img src="{{ $imageUrl }}" alt="{{ $imageCaption ?: $release->title }}"
class="absolute inset-0 h-full w-full object-cover" loading="eager">
@else
<span class="relative text-[10px] font-mono tracking-wider uppercase text-white/50">
{{ $companyName ? __('web.detail.image_placeholder_company', ['company' => $companyName]) : __('web.detail.image_placeholder') }} (16:9)
</span>
@endif
<div class="relative flex items-end p-[18px] bg-bg-dark overflow-hidden" style="aspect-ratio:21/9;">
<img src="{{ $imageUrl }}" alt="{{ $imageCaption ?: $release->title }}"
class="absolute inset-0 h-full w-full object-cover" loading="eager">
</div>
@if ($imageCaption || $imageCredit)
<div class="text-[11.5px] text-ink-3 mt-2.5 italic max-w-[720px]">
@ -237,6 +313,7 @@
</div>
@endif
</section>
@endif
{{-- Body + Sidebar --}}
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 pt-10 grid grid-cols-1 lg:grid-cols-[1fr_280px] gap-10 lg:gap-14">
@ -331,6 +408,20 @@
</a>
<span class="text-ink-4">{{ __('web.detail.reports_free') }}</span>
</div>
{{-- Magic-Link-Hinweis: hinterlegte Firmen-/Pressekontakte verwalten
ihre Mitteilung über den passwortlosen Anmeldelink im pressekonto. --}}
@php
$portalUrl = rtrim(config('domains.domain_portal_url', config('app.url')), '/');
@endphp
<div class="mt-3 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-[11.5px] text-ink-3">
<span>{{ __('web.detail.manage_question') }}</span>
<a href="{{ $portalUrl }}/anmeldelink" class="inline-flex items-center gap-1.5 text-ink-2 underline underline-offset-2 hover:text-ink transition-colors">
<svg width="12" height="12" viewBox="0 0 14 14" class="flex-shrink-0" aria-hidden="true"><path d="M1 3h12v8H1zM1 3l6 4 6-4" stroke="currentColor" stroke-width="1.3" fill="none" /></svg>
{{ __('web.detail.manage_link') }}
</a>
<span class="text-ink-4">{{ __('web.detail.manage_hint') }}</span>
</div>
</div>
{{-- Sidebar --}}
@ -433,10 +524,14 @@
</aside>
</section>
{{-- Empfehlungen --}}
{{-- Empfehlungen: zweispaltig nur, wenn beide Listen gefüllt sind —
eine einzelne Liste nutzt sonst die volle Layout-Breite. --}}
@if ($companyReleases->isNotEmpty() || $relatedReleases->isNotEmpty())
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 mt-14">
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
<div @class([
'grid grid-cols-1 gap-10',
'md:grid-cols-2' => $companyReleases->isNotEmpty() && $relatedReleases->isNotEmpty(),
])>
@if ($companyReleases->isNotEmpty())
<div>
<h3 class="font-serif text-[20px] font-semibold m-0 mb-1 tracking-[-0.2px]">{{ __('web.detail.more_from', ['company' => $companyName]) }}</h3>