presseportale/resources/views/web/release-detail.blade.php
Kevin Adametz d6e49c11c3 web/seo: Änderungsdatum unabhängig von Aufrufen, EN-URLs in Sitemap und Legacy-API, signierte Bounce-Referenz, Merge/Canonical konsistent (Umsetzungsaudit 29.09.: R-02 bis R-11, N17, N18, OPT-1, OPT-2; 02.10.: O01)
- R-02/K-03: Aufrufaggregation und Merge ändern updated_at nicht mehr; neue
  Spalte press_releases.content_changed_at (INSTANT), contentModifiedAt()
  für Sitemap-lastmod, dateModified und article:modified_time
- R-03: Sitemap und Legacy-view_url mit /en/ für englische Meldungen
- R-04/O01: X-Pk-Ref je Empfänger per HMAC signiert; auf eine Sperre zählt —
  hart wie weich — nur ein gültig signierter Rückläufer, unsignierte
  standardmäßig nur zur Sichtung (BOUNCE_UNSIGNED_KIND=policy)
- R-05/R-06: Unmerge setzt nur unveränderte Füllwerte zurück
  (company_merge_moves.merged_value), Canonical-Neubewertung nach Merge und
  Unmerge, assignAll setzt veraltete Verweise zurück
- R-07 bis R-11: Crawler zählen nicht als Aufruf, Mail-Klick gedrosselt und
  verzögert geschrieben, Claim-Zähler erst nach Versand, email_bounces nach
  395 Tagen gelöscht, Canonical-Portal über Kategorie-ID, llms-Cache geleert,
  Feed-Teaser ohne Doppel-Escaping, Merge-Ketten aufgelöst
- N17/N18: kein falsches „gelistet seit", Rollen im Indexbericht wie Sitemap
- OPT-1/OPT-2: topFeeds in SQL, purge-events löscht nach Grenz-ID

Enthält aus PressRelease.php auch die Card-Bild-Helfer (B-01) und
wasEverPublished()/neverPublished() (A-05), weil die Datei ungeteilt
committet ist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 13:09:00 +00:00

810 lines
59 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@extends('web.layouts.web-master')
@php
use App\Enums\Portal;
use App\Services\PressRelease\CanonicalAssigner;
use App\Support\ImageCredit;
use App\Support\StructuredData;
use Illuminate\Support\Str;
// Private Vorschau über Magic-Link (/pm-vorschau/{token}): identisches
// Layout wie die spätere öffentliche Seite, plus Hinweis-Banner und noindex.
$isPreview = ($isPreview ?? false) === true;
$previewExpiresAt = $previewExpiresAt ?? null;
$previewPortalOptions = $previewPortalOptions ?? null;
$company = $release->company;
$categoryTranslation = $release->category?->translations?->firstWhere('locale', app()->getLocale())
?? $release->category?->translations?->first();
$categoryName = $categoryTranslation?->name;
$categorySlug = $categoryTranslation?->slug;
$categoryHref = $categorySlug ? route('kategorie', ['slug' => $categorySlug]) : route('web.home');
$parentTranslation = $release->category?->parent?->translations?->firstWhere('locale', app()->getLocale())
?? $release->category?->parent?->translations?->first();
$parentName = $parentTranslation?->name;
$parentSlug = $parentTranslation?->slug;
$parentHref = $parentSlug ? route('kategorie', ['slug' => $parentSlug]) : route('web.home');
// In der Vorschau ersatzweise der geplante Termin, damit die Meta-Zeile
// der späteren Veröffentlichung entspricht.
$publishedAt = $release->published_at ?? ($isPreview ? $release->scheduledAtLocal() : null);
$embargoAt = $release->embargoAtLocal();
$bodyHtml = $release->renderedText();
$wordCount = $release->wordCount();
$readTime = $release->readTimeMinutes();
$teaser = $release->teaserText(180);
// 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();
// In der anonymen Token-Vorschau ist die PM noch nicht öffentlich; die
// Media-Route braucht daher eine signierte URL, um die Bilder auszuliefern.
$signMedia = $isPreview ?? false;
$imageUrl = $image?->variantUrl('detail', $signMedia) ?? $image?->variantUrl('card', $signMedia) ?? $image?->url($signMedia);
$imageCaption = $image?->description ?: $image?->title;
// Bildnachweis, KI-Kennzeichnung und Verantwortlich-Zeile aus einer
// Auflösung (Bildkennzeichnung D.1, Decision-Update 3.4). Die sichtbare
// Zeile unter dem Bild ist die Nennung, das Overlay die Erklärung.
$imageCreditInfo = $image ? ImageCredit::for($image, $company) : null;
$imageCredit = $imageCreditInfo?->credit;
$imageAiBadge = $imageCreditInfo?->aiLabel;
$imageReportUrl = route('melden', ['edition' => app()->getLocale()]);
// 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');
$countryNames = ['DE' => __('web.countries.de'), 'AT' => __('web.countries.at'), 'CH' => __('web.countries.ch')];
$companyCountry = $company?->country_code ? ($countryNames[strtoupper($company->country_code)] ?? null) : null;
$boilerplate = filled($release->boilerplate_override) ? $release->boilerplate_override : $company?->boilerplate;
$keywords = collect(preg_split('/[,;\n]+/', (string) $release->keywords))
->map(fn ($keyword) => trim($keyword))
->filter()
->unique()
->take(8);
$contact = $release->contacts->first();
$contactName = $contact ? trim(implode(' ', array_filter([$contact->title, $contact->first_name, $contact->last_name]))) : null;
// Legacy-Bestände ohne Kontakt-Datensatz: der Block erscheint trotzdem,
// solange die Firma erreichbare Angaben hat (wie im Altsystem).
$contactEmail = $contact?->email ?: $company?->email;
$hasContactBlock = $contact || filled($contactEmail) || filled($company?->address) || filled($company?->website);
// Attributions-Zeile wie im Legacy-System: Urheber (Firma der PM) und
// Übermittler (Profilfeld agency_name des einliefernden Accounts, z. B.
// "connektar.de Presseverteiler" bei API-Einlieferungen).
$agencyName = $release->user?->agency_name;
$publisherVerified = (bool) ($company?->is_active && filled($company?->website));
$isRecommended = $release->content_score !== null && $release->content_score >= 80;
// In der Vorschau wird der Vorschau-Link selbst geteilt — die öffentliche
// URL existiert vor der Veröffentlichung noch nicht.
$shareUrl = $isPreview ? request()->fullUrl() : route('release.detail', ['slug' => $release->slug]);
$brandLabel = config('app.theme') === 'presseecho' ? 'presseecho' : 'businessportal24';
// Doppelveröffentlichung (Anhang 2, 1c.8): rel=canonical zeigt auf die
// Erstquelle — die eigene Adresse, oder bei zweiter Fassung/portal=both
// das Portal, das laut Kategorie-Zuordnung führt. Geteilt wird weiterhin
// die Adresse, auf der der Leser gerade ist.
$canonicalUrl = $isPreview
? $shareUrl
: app(CanonicalAssigner::class)->canonicalUrlFor($release, $brandLabel === 'presseecho' ? Portal::Presseecho : Portal::Businessportal24);
// dateModified/article:modified_time = letzte inhaltliche Änderung, nie
// vor datePublished (PressRelease::contentModifiedAt()); updated_at
// bewegen auch Aufrufzähler und Merge (Umsetzungsaudit 29.09.2026, R-02).
$modifiedAt = $release->contentModifiedAt() ?? $publishedAt;
// SEO: NewsArticle + BreadcrumbList als JSON-LD — nur für die öffentliche
// Seite (die Vorschau ist noindex und hat keine stabile URL).
$jsonLd = null;
if (! $isPreview) {
$portalBase = StructuredData::portalBase($domainUrl ?? null);
$authorNewsroomUrl = $company?->slug ? StructuredData::newsroomUrl($portalBase, $company->slug) : null;
// URLs aus der Portal-Basis (wie der Canonical) statt route(), damit
// Host und Canonical auch bei forceRootUrl-Abweichungen übereinstimmen.
$breadcrumbs = collect([
['name' => $brandLabel, 'item' => $portalBase.'/'.app()->getLocale()],
$categoryName && $categorySlug ? ['name' => $categoryName, 'item' => $portalBase.'/'.app()->getLocale().'/category/'.$categorySlug] : null,
['name' => $release->title, 'item' => $portalBase.'/'.app()->getLocale().'/press-release/'.$release->slug],
])->filter()->values();
$jsonLd = [
'@context' => 'https://schema.org',
'@graph' => [
array_filter([
'@type' => 'NewsArticle',
'mainEntityOfPage' => ['@type' => 'WebPage', '@id' => $canonicalUrl],
'headline' => Str::limit($release->title, 110),
'description' => filled($lead) ? $lead : $teaser,
'image' => $imageUrl ? [$imageUrl] : null,
'datePublished' => $publishedAt?->toIso8601String(),
'dateModified' => $modifiedAt?->toIso8601String(),
'articleSection' => $categoryName,
'keywords' => $keywords->isNotEmpty() ? $keywords->implode(', ') : null,
'wordCount' => $wordCount ?: null,
'inLanguage' => app()->getLocale(),
'isAccessibleForFree' => true,
// author trägt die @id der Newsroom-Organization (2.1): Meldung
// und Newsroom-Seite beschreiben dieselbe Entität.
'author' => $companyName ? array_filter([
'@type' => 'Organization',
'@id' => $authorNewsroomUrl ? StructuredData::newsroomId($authorNewsroomUrl) : null,
'name' => $companyName,
'url' => $authorNewsroomUrl,
]) : null,
'publisher' => StructuredData::publisher($portalBase),
], fn ($value) => $value !== null),
[
'@type' => 'BreadcrumbList',
'itemListElement' => $breadcrumbs->map(fn (array $crumb, int $index) => [
'@type' => 'ListItem',
'position' => $index + 1,
'name' => $crumb['name'],
'item' => $crumb['item'],
])->all(),
],
],
];
}
@endphp
@section('title', $release->title.' – '.$brandLabel)
@section('meta_description', filled($lead) ? $lead : $teaser)
@section('og_type', 'article')
@if ($imageUrl)
@section('og_image', $image?->variantUrl('og', $signMedia) ?? $imageUrl)
@endif
@if ($isPreview)
@section('robots', 'noindex,nofollow')
@else
@section('canonical', $canonicalUrl)
@section('meta_extra')
@if ($publishedAt)
<meta property="article:published_time" content="{{ $publishedAt->toIso8601String() }}">
@endif
@if ($publishedAt && $modifiedAt?->greaterThan($publishedAt))
<meta property="article:modified_time" content="{{ $modifiedAt->toIso8601String() }}">
@endif
@if ($categoryName)
<meta property="article:section" content="{{ $categoryName }}">
@endif
@foreach ($keywords as $keyword)
<meta property="article:tag" content="{{ $keyword }}">
@endforeach
@if ($imageUrl && ($imageCaption || $release->title))
<meta property="og:image:alt" content="{{ $imageCaption ?: $release->title }}">
@endif
@endsection
@section('structured_data')
<x-web.structured-data :data="$jsonLd" />
@endsection
@endif
@section('content')
{{-- Lese-Fortschrittsbalken --}}
<div class="fixed top-0 left-0 right-0 h-0.5 bg-bg-rule z-[60]"
x-data="{ progress: 0 }"
x-init="const update = () => { const h = document.body.scrollHeight - window.innerHeight; progress = h > 0 ? Math.min(100, (window.scrollY / h) * 100) : 0; }; update(); window.addEventListener('scroll', update, { passive: true }); window.addEventListener('resize', update);"
aria-hidden="true">
<div class="h-full bg-brand transition-[width] duration-150 ease-out" :style="`width:${progress}%`"></div>
</div>
<x-web.site-header />
@if ($isPreview)
{{-- Hinweis-Banner: private Vorschau, nicht öffentlich erreichbar --}}
<div class="bg-amber-50 border-b border-amber-200 text-amber-900">
<div class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 py-2.5 flex flex-wrap items-center gap-x-4 gap-y-1 text-[12.5px]">
<span class="text-[9.5px] font-bold tracking-[0.18em] uppercase px-2.5 py-[3px] border border-amber-400 font-mono whitespace-nowrap">
● {{ __('web.detail.preview_label') }}
</span>
<span>{{ __('web.detail.preview_text') }}</span>
<span class="font-mono text-[11.5px] text-amber-800">
{{ __('web.detail.preview_status', ['status' => $release->status->label()]) }}
@if ($previewExpiresAt)
· {{ __('web.detail.preview_valid_until', ['date' => $previewExpiresAt->translatedFormat('j. F Y, H:i')]) }}
@endif
</span>
@if (! empty($previewPortalOptions))
{{-- PM erscheint auf beiden Portalen: Ansicht umschalten --}}
<span class="sm:ml-auto inline-flex items-center gap-2">
<span class="text-[10px] font-semibold tracking-[0.14em] uppercase text-amber-800 font-mono">{{ __('web.detail.preview_portal_switch') }}</span>
<span class="inline-flex items-stretch border border-amber-400 divide-x divide-amber-400 font-mono text-[10.5px]">
@foreach ($previewPortalOptions as $option)
<a href="{{ $option['url'] }}"
@class([
'px-2.5 py-[3px] whitespace-nowrap transition-colors cursor-pointer',
'bg-amber-400/40 font-bold text-amber-950' => $option['active'],
'hover:bg-amber-100 text-amber-900' => ! $option['active'],
])
@if ($option['active']) aria-current="true" @endif>
{{ $option['label'] }}
</a>
@endforeach
</span>
</span>
@endif
</div>
</div>
@endif
<main class="bg-bg text-ink">
<article>
{{-- Breadcrumbs --}}
<div class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 pt-7">
<nav class="flex flex-wrap gap-2 text-[11.5px] text-ink-3 mb-2 font-mono tracking-wide" aria-label="{{ __('web.detail.breadcrumbs') }}">
<a href="{{ route('web.home') }}" class="cursor-pointer hover:text-ink transition-colors">{{ __('web.nav.home') }}</a>
<span aria-hidden="true">/</span>
@if ($parentName)
<a href="{{ $parentHref }}" class="cursor-pointer hover:text-ink transition-colors">{{ $parentName }}</a>
<span aria-hidden="true">/</span>
@endif
@if ($categoryName)
<a href="{{ $categoryHref }}" class="cursor-pointer text-brand hover:text-brand-deep transition-colors">{{ $categoryName }}</a>
<span aria-hidden="true">/</span>
@endif
<span class="text-ink-2">{{ __('web.detail.press_release') }}</span>
</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, ',', '.')]) }}@unless ($isPreview) · {{ __('web.detail.views', ['count' => number_format((int) $release->hits, 0, ',', '.')]) }}@endunless</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">
@if ($embargoAt)
<div class="flex items-center gap-2.5 mb-3.5 flex-wrap">
<span class="text-[9.5px] font-bold tracking-[0.18em] uppercase text-brand px-2.5 py-[3px] border border-brand font-mono">
● {{ __('web.detail.embargo_lifted') }}
</span>
<span class="text-[11px] text-ink-3 font-mono">{{ $embargoAt->translatedFormat('j. F Y, H:i') }}</span>
</div>
@endif
<div class="flex items-center gap-3.5 mb-4 flex-wrap">
@if ($categoryName)
<a href="{{ $categoryHref }}" class="bp-cat cursor-pointer hover:text-brand-deep transition-colors">{{ $categoryName }}</a>
<span class="w-1 h-1 rounded-full bg-ink-4" aria-hidden="true"></span>
@endif
<span class="text-[11px] text-ink-3 italic font-serif">{{ __('web.detail.audience') }}</span>
</div>
<h1 class="font-serif text-[32px] sm:text-[40px] lg:text-[48px] font-semibold m-0 mb-[22px] tracking-[-0.8px] leading-[1.1] max-w-[980px]" style="text-wrap:balance;">
{{ $release->title }}
</h1>
@if (filled($lead))
<p class="font-serif text-[18px] sm:text-[21px] leading-[1.45] m-0 mb-7 text-ink-2 max-w-[880px] font-normal" style="text-wrap:pretty;">
{{ $lead }}
</p>
@endif
<div class="flex flex-wrap items-center text-[12.5px] text-ink-2" style="gap:10px 22px;">
@if ($companyName)
<span class="inline-flex items-center gap-2">
<span class="w-7 h-7 bg-black text-white inline-flex items-center justify-center text-[13px] font-bold font-serif">{{ $companyInitial }}</span>
<span class="font-semibold">{{ $companyName }}</span>
</span>
<span class="w-px h-[14px] bg-bg-rule-strong" aria-hidden="true"></span>
@endif
@if ($publishedAt)
<time datetime="{{ $publishedAt->toIso8601String() }}" class="font-mono text-[11.5px] text-ink-3">
{{ $publishedAt->translatedFormat('j. F Y') }} · {{ __('web.detail.time_at', ['time' => $publishedAt->format('H:i')]) }}
</time>
@endif
<span class="font-mono text-[11.5px] text-ink-3">{{ __('web.shared.read_time', ['minutes' => $readTime]) }} · {{ __('web.detail.words', ['count' => number_format($wordCount, 0, ',', '.')]) }}@unless ($isPreview) · {{ __('web.detail.views', ['count' => number_format((int) $release->hits, 0, ',', '.')]) }}@endunless</span>
@if ($companyCountry)
<span class="inline-flex items-center gap-1.5 font-mono text-[11.5px] text-ink-3">
<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-brand bg-brand/[0.04] border border-brand/30 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>
</section>
{{-- 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 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">
@if ($imageAiBadge)
{{-- KI-Badge am Bild (Art. 50 KI-VO): kräftig abgedunkelter
Hintergrund, damit es auch auf hellen Bildern lesbar bleibt.
Rechts unten, damit es das „i"-Zeichen links nicht überlappt. --}}
<span class="relative ml-auto text-[9.5px] font-bold tracking-[0.18em] uppercase text-white px-2.5 py-[3px] border border-white/60 font-mono bg-black/75 shadow-sm">
{{ $imageAiBadge }}
</span>
@endif
@if ($imageCreditInfo?->hasDetails())
{{-- Detailfenster zum Bildnachweis (Bildkennzeichnung D.2, E-B13):
eigenes „i"-Zeichen, kein „cr"-Logo. Alpine ohne Plugin: Fokus
wandert in den Dialog und beim Schließen zurück; Escape schließt.
Auf Mobil ein Bottom-Sheet, ab sm ein Popover am Icon. --}}
<div x-data="{ open: false, show() { this.open = true; this.$nextTick(() => this.$refs.close.focus()) }, hide() { this.open = false; this.$nextTick(() => this.$refs.trigger.focus()) } }"
@keydown.escape.window="if (open) hide()"
class="absolute bottom-3 left-3 z-10" data-image-credit>
<button type="button" x-ref="trigger" @click="open ? hide() : show()" :aria-expanded="open ? 'true' : 'false'"
aria-haspopup="dialog" aria-controls="image-credit-dialog"
class="flex size-7 items-center justify-center rounded-full bg-black/60 text-white ring-1 ring-white/50 backdrop-blur-[2px] hover:bg-black/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-white"
title="{{ __('web.detail.image_credit_open') }}">
<span class="sr-only">{{ __('web.detail.image_credit_open') }}</span>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<circle cx="12" cy="12" r="9.5" stroke="currentColor" stroke-width="1.6" />
<path d="M12 10.5v6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
<circle cx="12" cy="7.6" r="1.15" fill="currentColor" />
</svg>
</button>
<div x-show="open" x-cloak @click.outside="hide()" id="image-credit-dialog" role="dialog" aria-modal="true"
aria-labelledby="image-credit-title"
class="fixed inset-x-0 bottom-0 z-40 max-h-[80vh] overflow-y-auto border-t border-bg-rule-strong bg-bg p-5 text-[13px] text-ink shadow-2xl sm:absolute sm:inset-auto sm:bottom-10 sm:left-0 sm:w-[22rem] sm:border sm:shadow-lg">
<div class="flex items-start justify-between gap-3">
<h2 id="image-credit-title" class="m-0 text-[11px] font-semibold uppercase tracking-[0.14em] text-ink-3">{{ __('web.detail.image_credit_title') }}</h2>
<button type="button" x-ref="close" @click="hide()" class="text-[12px] text-ink-3 underline-offset-2 hover:text-ink hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-ink">
{{ __('web.detail.image_credit_close') }}
</button>
</div>
<ul class="m-0 mt-3 list-none space-y-2 p-0 leading-[1.5]">
@if ($imageCreditInfo->credit)
<li>{{ $imageCreditInfo->creditLabel }}: <span class="font-medium">{{ $imageCreditInfo->credit }}</span></li>
@endif
@if ($imageCreditInfo->licenseUrl)
<li>{{ __('web.detail.image_license') }}: <a href="{{ $imageCreditInfo->licenseUrl }}" rel="nofollow noopener" target="_blank" class="underline underline-offset-2">{{ \Illuminate\Support\Str::limit($imageCreditInfo->licenseUrl, 60) }}</a></li>
@endif
@if ($imageCreditInfo->sourceUrl)
<li>{{ __('web.detail.image_source_link') }}: <a href="{{ $imageCreditInfo->sourceUrl }}" rel="nofollow noopener" target="_blank" class="underline underline-offset-2">{{ \Illuminate\Support\Str::limit($imageCreditInfo->sourceUrl, 60) }}</a></li>
@endif
@if ($imageCreditInfo->aiLabel)
<li>
{{ $image->ai_declaration?->value === 'ai_edited' ? __('web.detail.image_ai_hint_edited') : __('web.detail.image_ai_hint_generated') }}
@if ($imageCreditInfo->aiSourceLabel)
<span class="block text-ink-3">{{ __('web.detail.image_ai_source', ['source' => $imageCreditInfo->aiSourceLabel]) }}</span>
@endif
</li>
@endif
@if ($imageCreditInfo->responsibleLine)
<li class="text-ink-3">{{ $imageCreditInfo->responsibleLine }}</li>
@endif
</ul>
<a href="{{ $imageReportUrl }}" class="mt-4 inline-block text-[12px] text-ink-3 underline underline-offset-2 hover:text-ink">{{ __('web.detail.image_report') }}</a>
</div>
</div>
@endif
</div>
@if ($imageCaption || $imageCreditInfo?->line())
<div class="text-[11.5px] text-ink-3 mt-2.5 italic max-w-[720px]" data-image-credit-line>
{{ $imageCaption }}@if ($imageCaption && $imageCreditInfo?->line()) · @endif@if ($imageCreditInfo?->line())<span class="not-italic">{{ $imageCreditInfo->line() }}</span>@endif
</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">
{{-- Body --}}
<div>
{{-- KI-Label (Art. 50 KI-VO) in Einreicher-Stimme am Anfang
des Inhalts; neutrales ℹ-Icon, kein Flux auf Web-Domains. --}}
@if ($aiTextLabel = $release->aiTextPublicLabel())
<div class="flex items-start gap-2 border border-bg-rule-strong bg-bg-elev px-3 py-2.5 mb-6 max-w-[680px] text-[12px] leading-[1.5] text-ink-3">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true" class="shrink-0 mt-0.5">
<circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="1.8" fill="none" />
<path d="M12 11v5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
<circle cx="12" cy="8" r="1.1" fill="currentColor" />
</svg>
<span>{{ $aiTextLabel }}</span>
</div>
@endif
<div class="pm-body max-w-[680px]">
{!! $bodyHtml !!}
</div>
@if (filled($boilerplate))
<div class="mt-9 pt-5 border-t border-bg-rule-strong max-w-[680px]">
<div class="eyebrow muted text-[9.5px] mb-2.5">{{ __('web.detail.about_company') }}</div>
<p class="text-[13px] leading-[1.6] text-ink-3 m-0 max-w-[620px]">{{ $boilerplate }}</p>
</div>
@endif
{{-- Inline-Werbeplatz (Position der früheren Inline-Ad); in der privaten Vorschau ausgeblendet --}}
@unless ($isPreview)
<x-web.ad-slot variant="inline" />
@endunless
{{-- Pressekontakt (Fallback ohne Kontakt-Datensatz: Firmendaten) --}}
@if ($hasContactBlock)
<div class="mt-8 px-5 sm:px-7 py-6 bg-white border border-bg-rule-strong max-w-[680px]">
<div class="eyebrow mb-3 text-[10.5px]">{{ __('web.detail.press_contact') }}</div>
<div class="flex gap-1.5 items-center mb-1 flex-wrap">
<strong class="font-serif text-[19px] font-semibold tracking-[-0.2px]">{{ $contactName ?: $companyName }}</strong>
@if ($publisherVerified)
<span title="{{ __('web.detail.verified_hint') }}" class="inline-flex items-center text-verify-border flex-shrink-0">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M12 1l2.5 3 3.8-.4-.5 3.8L21 9l-2.6 2.5L19 15l-3.8.4L12 19l-2.5-3.6L5.7 15 5.6 11.4 3 9l2.5-1.6L4.7 4 8.5 4z" fill="currentColor" />
<path d="M7.5 12l3 3 6-6" stroke="white" stroke-width="2.2" fill="none" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</span>
@endif
</div>
@if ($contact?->responsibility || $companyName)
<div class="text-[12.5px] text-ink-3 mb-3.5 leading-[1.5]">
@if ($contact?->responsibility){{ $contact->responsibility }}<br />@endif
{{ $companyName }}
</div>
@endif
<div class="flex flex-col gap-2 text-[12.5px] font-mono">
@if ($company?->address)
<span class="flex items-center gap-2">
<svg width="14" height="14" viewBox="0 0 14 14" class="text-ink-3 flex-shrink-0" aria-hidden="true"><path d="M7 1a5 5 0 015 5c0 4-5 7-5 7s-5-3-5-7a5 5 0 015-5zm0 3.5a1.5 1.5 0 100 3 1.5 1.5 0 000-3z" fill="currentColor" /></svg>
<span class="text-ink-2">{{ $company->address }}</span>
</span>
@endif
@if ($contact?->phone)
<span class="flex items-center gap-2">
<svg width="14" height="14" viewBox="0 0 14 14" class="text-ink-3 flex-shrink-0" aria-hidden="true"><path d="M2 3l3-1 1.5 3-1.5 1c.5 2 2 3.5 4 4l1-1.5L13 10l-1 3c-5 0-10-5-10-10z" fill="currentColor" /></svg>
<a href="tel:{{ preg_replace('/[^0-9+]/', '', $contact->phone) }}" class="text-ink-2 hover:text-ink transition-colors">{{ $contact->phone }}</a>
</span>
@endif
@if ($contactEmail)
<span class="flex items-center gap-2">
<svg width="14" height="14" viewBox="0 0 14 14" class="text-ink-3 flex-shrink-0" aria-hidden="true"><path d="M1 3h12v8H1zM1 3l6 4 6-4" stroke="currentColor" stroke-width="1.2" fill="none" /></svg>
<a href="mailto:{{ $contactEmail }}" class="text-ink underline underline-offset-2 hover:text-brand transition-colors">{{ $contactEmail }}</a>
</span>
@endif
@if ($company?->website)
<span class="flex items-center gap-2">
<svg width="14" height="14" viewBox="0 0 14 14" class="text-ink-3 flex-shrink-0" aria-hidden="true"><circle cx="7" cy="7" r="5.5" stroke="currentColor" stroke-width="1.2" fill="none" /><path d="M1.5 7h11M7 1.5c2 1.5 2 9.5 0 11M7 1.5c-2 1.5-2 9.5 0 11" stroke="currentColor" stroke-width="1" fill="none" /></svg>
<a href="{{ $company->websiteUrl() }}" rel="nofollow noopener" target="_blank" class="text-ink underline underline-offset-2 hover:text-brand transition-colors">{{ Str::of($company->website)->after('//')->rtrim('/') }}</a>
</span>
@endif
</div>
</div>
@endif
{{-- Attribution: Urheber & übermittelnder Verteiler (Legacy-Konvention) --}}
@if ($companyName)
<p class="mt-6 text-[12px] text-ink-3 italic max-w-[680px]">
@if ($agencyName && $agencyName !== $companyName)
{{ __('web.detail.attribution_via', ['company' => $companyName, 'agency' => $agencyName]) }}
@elseif ($agencyName)
{{ __('web.detail.attribution_agency_only', ['agency' => $agencyName]) }}
@else
{{ __('web.detail.attribution', ['company' => $companyName]) }}
@endif
</p>
@endif
{{-- Zwei Handlungsfelder, klar getrennt und mit Button-Charakter:
rechtliches Anliegen (T13) und Verwaltung über die hinterlegte
Adresse (Roadmap 1b.2/1b.4). Vorher eine Textzeile in 11,5 px,
die überlesen wurde. Die Empfängeradresse steht maskiert dabei,
damit klar ist, wohin der Link geht — ohne sie preiszugeben. --}}
@php
$portalUrl = rtrim(config('domains.domain_portal_url', config('app.url')), '/');
$claimRecipients = ($company && ! $company->isGone() && $company->slug)
? app(\App\Services\Company\NewsroomClaimService::class)->maskedRecipients($company)
: collect();
@endphp
<div class="mt-8 grid gap-4 sm:grid-cols-2">
<div class="border border-bg-rule-strong bg-bg-elev p-5">
<div class="eyebrow muted text-[10px] mb-2">{{ __('web.detail.legal_eyebrow') }}</div>
<p class="text-[13px] leading-[1.5] text-ink-2 m-0 mb-3.5">{{ __('web.detail.legal_question') }} {{ __('web.detail.reports_free') }}</p>
<div class="flex flex-wrap gap-2">
<a href="{{ route('legal-request.create', ['slug' => $release->slug, 'type' => 'report']) }}" class="inline-flex items-center gap-1.5 px-3.5 py-2 text-[12.5px] font-semibold bg-white border border-bg-rule-strong text-ink hover:border-brand hover:text-brand transition-colors">
<svg width="12" height="12" viewBox="0 0 14 14" class="flex-shrink-0" aria-hidden="true"><circle cx="7" cy="7" r="5.5" stroke="currentColor" stroke-width="1.3" fill="none" /><path d="M7 4v3.5M7 9.5v.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" /></svg>
{{ __('web.detail.report_content') }}
</a>
<a href="{{ route('legal-request.create', ['slug' => $release->slug, 'type' => 'dsgvo']) }}" class="inline-flex items-center gap-1.5 px-3.5 py-2 text-[12.5px] font-semibold bg-white border border-bg-rule-strong text-ink hover:border-brand hover:text-brand transition-colors">
<svg width="12" height="12" viewBox="0 0 14 14" class="flex-shrink-0" aria-hidden="true"><path d="M7 1l5 2v4c0 3-2.2 5.2-5 6-2.8-.8-5-3-5-6V3z" stroke="currentColor" stroke-width="1.3" fill="none" stroke-linejoin="round" /></svg>
{{ __('web.detail.remove_data') }}
</a>
</div>
</div>
<div class="border border-bg-rule-strong bg-bg-elev p-5">
<div class="eyebrow muted text-[10px] mb-2">{{ __('web.detail.manage_eyebrow') }}</div>
<p class="text-[13px] leading-[1.5] text-ink-2 m-0 mb-2">{{ __('web.detail.manage_question') }}</p>
@if ($claimRecipients->isNotEmpty())
<p class="text-[12px] text-ink-3 m-0 mb-1">{{ __('web.detail.manage_recipient') }}</p>
<ul class="m-0 mb-3.5 p-0 list-none font-mono text-[13px] text-ink">
@foreach ($claimRecipients as $recipient)
<li>{{ $recipient }}</li>
@endforeach
</ul>
<a href="{{ route('newsroom.claim', ['slug' => $company->slug]) }}" rel="nofollow" class="inline-flex items-center gap-1.5 px-3.5 py-2 text-[12.5px] font-semibold bg-brand text-white hover:opacity-90 transition-opacity">
<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_cta') }}
</a>
<p class="text-[11.5px] text-ink-3 m-0 mt-3">
{{ __('web.detail.manage_hint') }}
<a href="{{ $portalUrl }}/anmeldelink" class="underline underline-offset-2 hover:text-ink transition-colors">{{ __('web.detail.manage_other_address') }}</a>
</p>
@else
<a href="{{ $portalUrl }}/anmeldelink" class="inline-flex items-center gap-1.5 px-3.5 py-2 text-[12.5px] font-semibold bg-brand text-white hover:opacity-90 transition-opacity">
<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>
<p class="text-[11.5px] text-ink-3 m-0 mt-3">{{ __('web.detail.manage_hint') }}</p>
@endif
</div>
</div>
</div>
{{-- Sidebar --}}
<aside class="flex flex-col gap-7">
{{-- Publisher --}}
@if ($companyName)
<div class="p-4 bg-white border border-bg-rule-strong">
<div class="eyebrow muted text-[9.5px] mb-3">{{ __('web.detail.published_by') }}</div>
<div class="flex gap-3 items-start mb-3">
<span class="w-11 h-11 bg-black text-white inline-flex items-center justify-center text-[20px] font-bold font-serif flex-shrink-0">{{ $companyInitial }}</span>
<div class="min-w-0">
<strong class="block font-serif text-[14px] font-semibold leading-[1.25] tracking-[-0.1px] mb-0.5">{{ $companyName }}</strong>
@if ($companyCountry)
<div class="text-[10.5px] text-ink-4 mt-1 font-mono">{{ $companyCountry }}</div>
@endif
</div>
</div>
@if ($publisherVerified)
<div class="flex items-center gap-2 px-2.5 py-2 mb-3 bg-verify-bg border border-verify-border text-verify-ink cursor-help"
title="{{ __('web.detail.verified_by', ['brand' => $brandLabel]) }}">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" class="text-verify-border flex-shrink-0" aria-hidden="true">
<path d="M12 1l2.5 3 3.8-.4-.5 3.8L21 9l-2.6 2.5L19 15l-3.8.4L12 19l-2.5-3.6L5.7 15 5.6 11.4 3 9l2.5-1.6L4.7 4 8.5 4z" fill="currentColor" />
<path d="M7.5 12l3 3 6-6" stroke="white" stroke-width="2.2" fill="none" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<span class="text-[11.5px] font-semibold">{{ __('web.detail.verified_publisher') }}</span>
</div>
@endif
<div class="pt-3 border-t border-bg-rule flex flex-col gap-2">
<a href="{{ $companyHref }}" class="px-2.5 py-2 text-center text-[11.5px] font-semibold bg-ink text-white hover:bg-black transition-colors cursor-pointer">{{ __('web.detail.to_newsroom') }} →</a>
</div>
</div>
@endif
{{-- Teilen --}}
<div x-data="{
url: @js($shareUrl),
title: @js($release->title),
copied: false,
{{-- Share-Klicks als Ereignis loggen (ohne Personenbezug); in der Vorschau kein Tracking. --}}
trackUrl: @js($isPreview ? null : route('release.share', ['slug' => $release->slug])),
track(channel) {
if (!this.trackUrl) return;
fetch(this.trackUrl, {
method: 'POST',
keepalive: true,
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]')?.content ?? '' },
body: JSON.stringify({ channel })
}).catch(() => {});
},
copy() { navigator.clipboard?.writeText(this.url).then(() => { this.copied = true; this.track('copy'); setTimeout(() => this.copied = false, 2000); }); }
}">
<div class="eyebrow muted text-[9.5px] mb-2.5">{{ __('web.detail.share') }}</div>
<div class="grid grid-cols-2 gap-1.5">
<a :href="`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}`" @click="track('linkedin')" target="_blank" rel="noopener" class="inline-flex items-center gap-[7px] px-2.5 py-2 text-[11.5px] font-medium bg-white border border-bg-rule-strong text-ink hover:bg-bg-elev transition-colors cursor-pointer">
<svg width="13" height="13" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 6h2.5v8H3V6zm1.25-3a1.25 1.25 0 110 2.5 1.25 1.25 0 010-2.5zM7 6h2.4v1.1c.4-.7 1.3-1.3 2.6-1.3 2.7 0 3 1.7 3 3.9V14h-2.5v-3.7c0-.9 0-2-1.3-2s-1.5 1-1.5 2V14H7V6z" fill="currentColor" /></svg>
LinkedIn
</a>
<a :href="`https://twitter.com/intent/tweet?url=${encodeURIComponent(url)}&text=${encodeURIComponent(title)}`" @click="track('x')" target="_blank" rel="noopener" class="inline-flex items-center gap-[7px] px-2.5 py-2 text-[11.5px] font-medium bg-white border border-bg-rule-strong text-ink hover:bg-bg-elev transition-colors cursor-pointer">
<svg width="13" height="13" viewBox="0 0 16 16" aria-hidden="true"><path d="M2 2l5.5 7.2L2 14h1.6l4.7-3.7L11.5 14H14L8.2 6.4 13.6 2H12L7.5 5.5 4.5 2H2z" fill="currentColor" /></svg>
X
</a>
<a :href="`mailto:?subject=${encodeURIComponent(title)}&body=${encodeURIComponent(url)}`" @click="track('email')" class="inline-flex items-center gap-[7px] px-2.5 py-2 text-[11.5px] font-medium bg-white border border-bg-rule-strong text-ink hover:bg-bg-elev transition-colors cursor-pointer">
<svg width="13" height="13" viewBox="0 0 16 16" aria-hidden="true"><rect x="2" y="3" width="12" height="10" stroke="currentColor" stroke-width="1.3" fill="none" /><path d="M2 4l6 4 6-4" stroke="currentColor" stroke-width="1.3" fill="none" /></svg>
{{ __('web.detail.email') }}
</a>
<button type="button" @click="copy()" class="inline-flex items-center gap-[7px] px-2.5 py-2 text-[11.5px] font-medium bg-white border border-bg-rule-strong text-ink hover:bg-bg-elev transition-colors cursor-pointer">
<svg width="13" height="13" viewBox="0 0 16 16" aria-hidden="true"><path d="M6 9l4-4M5 4h2a3 3 0 010 6h-1M11 12H9a3 3 0 010-6h1" stroke="currentColor" stroke-width="1.3" fill="none" stroke-linecap="round" /></svg>
<span x-text="copied ? @js(__('web.detail.copied')) : @js(__('web.detail.copy_link'))">{{ __('web.detail.copy_link') }}</span>
</button>
</div>
</div>
{{-- Schlagwörter --}}
@if ($keywords->isNotEmpty())
<div>
<div class="eyebrow muted text-[9.5px] mb-2.5">{{ __('web.detail.keywords') }}</div>
<div class="flex flex-wrap gap-[5px]">
@foreach ($keywords as $keyword)
<a href="{{ route('suche', ['q' => $keyword]) }}" class="px-2.5 py-1 text-[11px] font-medium bg-white border border-bg-rule-strong text-ink-2 hover:text-ink transition-colors cursor-pointer">{{ $keyword }}</a>
@endforeach
</div>
</div>
@endif
{{-- Newsletter-Box bewusst entfernt: kommt nachgelagert mit echtem
Double-Opt-in (siehe dev/release/entwicklungsplan-release.md). --}}
{{-- Weiterführend --}}
<div>
<div class="eyebrow muted text-[9.5px] mb-2.5">{{ __('web.detail.further') }}</div>
<div class="flex flex-col border border-bg-rule-strong bg-white">
<button type="button" onclick="window.print()" class="flex items-center gap-2.5 px-3 py-2.5 text-[12px] text-ink-2 hover:bg-bg-elev transition-colors cursor-pointer text-left">
<svg width="13" height="13" viewBox="0 0 14 14" class="text-ink-3 flex-shrink-0" aria-hidden="true"><path d="M2 4h10v8H2zM2 4V2h10v2" stroke="currentColor" stroke-width="1.3" fill="none" /></svg>
<span class="flex-1">{{ __('web.detail.print') }}</span>
<svg width="9" height="9" viewBox="0 0 12 12" fill="none" class="text-ink-4" aria-hidden="true"><path d="M4 2l4 4-4 4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" /></svg>
</button>
<a href="{{ route('legal-request.create', ['slug' => $release->slug, 'type' => 'report']) }}" class="flex items-center gap-2.5 px-3 py-2.5 text-[12px] text-ink-2 border-t border-bg-rule hover:bg-bg-elev transition-colors cursor-pointer">
<svg width="13" height="13" viewBox="0 0 14 14" class="text-ink-3 flex-shrink-0" aria-hidden="true"><circle cx="7" cy="7" r="5.5" stroke="currentColor" stroke-width="1.3" fill="none" /><path d="M7 4v3.5M7 9.5v.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" /></svg>
<span class="flex-1">{{ __('web.detail.report_release') }}</span>
<svg width="9" height="9" viewBox="0 0 12 12" fill="none" class="text-ink-4" aria-hidden="true"><path d="M4 2l4 4-4 4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" /></svg>
</a>
<a href="{{ route('legal-request.create', ['slug' => $release->slug, 'type' => 'dsgvo']) }}" class="flex items-center gap-2.5 px-3 py-2.5 text-[12px] text-ink-2 border-t border-bg-rule hover:bg-bg-elev transition-colors cursor-pointer">
<svg width="13" height="13" viewBox="0 0 14 14" class="text-ink-3 flex-shrink-0" aria-hidden="true"><path d="M7 1l5 2v4c0 3-2.2 5.2-5 6-2.8-.8-5-3-5-6V3z" stroke="currentColor" stroke-width="1.3" fill="none" stroke-linejoin="round" /></svg>
<span class="flex-1">{{ __('web.detail.privacy_rights') }}</span>
<svg width="9" height="9" viewBox="0 0 12 12" fill="none" class="text-ink-4" aria-hidden="true"><path d="M4 2l4 4-4 4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" /></svg>
</a>
</div>
</div>
{{-- Verwaltungszugang in der Seitenleiste (Roadmap 1b.4): derselbe
Hinweis wie in der Veröffentlichungsmail — wer nur die Leiste
liest, soll ihn nicht verpassen. --}}
<div class="border border-bg-rule-strong bg-bg-elev p-4">
<div class="eyebrow muted text-[9.5px] mb-2">{{ __('web.detail.manage_eyebrow') }}</div>
<p class="text-[12px] leading-[1.5] text-ink-2 m-0 mb-3">{{ __('web.detail.manage_sidebar_text') }}</p>
@if ($claimRecipients->isNotEmpty())
<a href="{{ route('newsroom.claim', ['slug' => $company->slug]) }}" rel="nofollow" class="w-full inline-flex items-center justify-center gap-2 px-3 py-2 text-[12px] font-semibold bg-white border border-bg-rule-strong text-ink hover:border-brand hover:text-brand transition-colors">
{{ __('web.detail.manage_cta') }} →
</a>
@else
<a href="{{ $portalUrl }}/anmeldelink" class="w-full inline-flex items-center justify-center gap-2 px-3 py-2 text-[12px] font-semibold bg-white border border-bg-rule-strong text-ink hover:border-brand hover:text-brand transition-colors">
{{ __('web.detail.manage_link_short') }} →
</a>
@endif
</div>
</aside>
</section>
{{-- 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 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>
<div class="text-[11.5px] text-ink-3 mb-3.5">{{ __('web.detail.more_from_sub') }}</div>
<hr class="rule-strong mb-1" />
@foreach ($companyReleases as $companyRelease)
<x-web.feed-item :release="$companyRelease" />
@endforeach
<a href="{{ $companyHref }}" class="block mt-3.5 text-[12.5px] font-semibold text-brand hover:text-brand-deep transition-colors cursor-pointer">{{ __('web.detail.all_from', ['company' => $companyName]) }} →</a>
</div>
@endif
@if ($relatedReleases->isNotEmpty())
<div>
<h3 class="font-serif text-[20px] font-semibold m-0 mb-1 tracking-[-0.2px]">{{ __('web.detail.related') }}</h3>
<div class="text-[11.5px] text-ink-3 mb-3.5">{{ __('web.detail.related_sub') }}</div>
<hr class="rule-strong mb-1" />
@foreach ($relatedReleases as $relatedRelease)
<x-web.feed-item :release="$relatedRelease" />
@endforeach
@if ($categoryName)
<a href="{{ $categoryHref }}" class="block mt-3.5 text-[12.5px] font-semibold text-brand hover:text-brand-deep transition-colors cursor-pointer">{{ __('web.detail.all_from_category', ['category' => $categoryName]) }} →</a>
@endif
</div>
@endif
</div>
</section>
@endif
</article>
</main>
<x-web.site-footer />
@endsection