presseportale/resources/views/web/newsroom.blade.php
Kevin Adametz ff75159b8f web: RSS-Feed je Newsroom, RSS-Übersichtsseite, „RSS" in der Topleiste (Roadmap 3.1, Nachtrag)
- /{edition}/newsroom/{slug}/feed: Sichtbarkeit wie die Newsroom-Seite
  (aktiv, nicht entfernt, mindestens eine Meldung), Prüfung vor dem Cache,
  301 bei zusammengeführter Firma; Autodiscovery und Link „RSS-Feed" auf
  der Newsroom-Seite; Hub leitet auf das Portal der Firma.
- /{edition}/rss (RssPageController, static.rss DE/EN): Was RSS ist, wie
  ein Reader funktioniert, Hauptfeed, alle Branchen-Feeds mit Adresse,
  Muster des Newsroom-Feeds, Hinweise. Portalseite mit Portal-Kontext,
  Hub → presseecho, in Sitemap und llms.txt.
- Topleiste, Burger-Menü und Footer verlinken RSS neben FAQ/Hilfe/API.
- FeedController in portal/category/newsroom aufgeteilt; Panel
  „Auslieferung" nennt zusätzlich den Newsroom-Feed.
- Tests: FeedTest (+3), RssPageTest (4), StaticPagesTest, VisibilityStatsTest.
- dev/2026-09-01: Nachtrag in Protokoll Teil 11 und Roadmap 3.1.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 08:27:14 +00:00

271 lines
17 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\Support\StructuredData;
use Illuminate\Support\Str;
\Carbon\Carbon::setLocale(app()->getLocale());
$brandConfig = config('domains.domains.'.config('app.theme').'.brand', []);
$brandName = $brandConfig['name'] ?? 'businessportal';
$brandAccent = $brandConfig['accent'] ?? '24';
$fmt = fn ($value) => number_format((int) $value, 0, ',', '.');
$palette = ['#0098A6', '#21A038', '#0018A8', '#1E1E1E', '#E20074', '#003781', '#C84A1E', '#5A6E2F'];
$logoUrl = $company->logoUrl();
$initial = mb_strtoupper(mb_substr((string) $company->name, 0, 1));
$color = $palette[crc32((string) $company->id) % count($palette)];
$boilerplate = filled($company->boilerplate)
? trim((string) Str::of(strip_tags((string) $company->boilerplate))->squish())
: null;
$websiteHost = filled($company->website) ? preg_replace('#^https?://(www\.)?#', '', rtrim((string) $company->website, '/')) : null;
$leadImage = $leadRelease?->images->first();
$leadImageUrl = $leadImage?->variantUrl('detail') ?? $leadImage?->variantUrl('card') ?? $leadImage?->url();
$leadPlaceholderUrl = $leadImageUrl ? null : $leadRelease?->chosenPlaceholderUrl();
$leadCatName = $leadRelease?->localizedCategoryName();
$leadLead = $leadRelease
? (filled($leadRelease->subtitle) ? $leadRelease->subtitle : $leadRelease->teaserText(220))
: null;
$leadReadTime = $leadRelease?->readTimeMinutes() ?? 1;
// SEO: Der Newsroom ist die kanonische Firmenseite im Portal —
// Organization-Markup verknüpft PMs (author) mit diesem Profil.
// Portal-Basis wie beim Canonical im Layout (unabhängig von forceRootUrl).
$newsroomUrl = StructuredData::newsroomUrl(StructuredData::portalBase($domainUrl ?? null), $company->slug);
// RSS-Feed des Newsrooms (Roadmap 3.1) — Adresse aus der Portal-Basis.
$newsroomFeedUrl = StructuredData::portalBase($domainUrl ?? null).'/'.app()->getLocale().'/newsroom/'.$company->slug.'/feed';
$portalFeedUrl = StructuredData::portalBase($domainUrl ?? null).'/'.app()->getLocale().'/feed';
$jsonLd = [
'@context' => 'https://schema.org',
'@type' => 'Organization',
'@id' => StructuredData::newsroomId($newsroomUrl),
'name' => $company->name,
'url' => $newsroomUrl,
...($company->websiteUrl() ? ['sameAs' => [$company->websiteUrl()]] : []),
...($logoUrl ? ['logo' => $logoUrl] : []),
...($boilerplate ? ['description' => Str::limit($boilerplate, 300)] : []),
];
@endphp
@section('title', $company->name.' – Newsroom · '.$brandName.$brandAccent)
@section('meta_description', Str::limit($boilerplate ?? __('web.newsroom_page.meta_description', ['company' => $company->name]), 155))
@section('og_type', 'profile')
@section('hreflang')
<link rel="alternate" hreflang="de" href="{{ url('/de/newsroom/'.$company->slug) }}">
<link rel="alternate" hreflang="en" href="{{ url('/en/newsroom/'.$company->slug) }}">
<link rel="alternate" hreflang="x-default" href="{{ url('/de/newsroom/'.$company->slug) }}">
@endsection
@section('feed_links')
<link rel="alternate" type="application/rss+xml" title="{{ __('web.rss.newsroom_link_title', ['company' => $company->name, 'brand' => $brandName.$brandAccent]) }}" href="{{ $newsroomFeedUrl }}">
<link rel="alternate" type="application/rss+xml" title="{{ __('web.rss.portal_link_title', ['brand' => $brandName.$brandAccent]) }}" href="{{ $portalFeedUrl }}">
@endsection
@section('structured_data')
<x-web.structured-data :data="$jsonLd" />
@endsection
@section('content')
<x-web.site-header />
<main class="bg-bg text-ink">
{{-- Breadcrumb --}}
<div class="border-b border-bg-rule">
<div class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 py-2.5 flex items-center gap-2 text-[11.5px] text-ink-3">
<a href="{{ route('web.home') }}" class="hover:text-ink transition-colors">{{ __('web.nav.home') }}</a>
<span class="text-ink-4" aria-hidden="true">/</span>
<a href="{{ route('newsrooms') }}" class="hover:text-ink transition-colors">Newsrooms</a>
<span class="text-ink-4" aria-hidden="true">/</span>
<span class="text-ink font-semibold truncate max-w-[55vw]">{{ $company->name }}</span>
</div>
</div>
{{-- Masthead --}}
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 pt-8 lg:pt-9">
<div class="flex items-center justify-between gap-4 mb-4">
<div class="eyebrow">Newsroom</div>
<a href="{{ route('newsrooms') }}" class="inline-flex items-center gap-1.5 text-[12px] font-semibold text-ink-3 hover:text-ink transition-colors whitespace-nowrap" aria-label="{{ __('web.newsroom_page.back_aria') }}">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M15 19l-7-7 7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
{{ __('web.newsroom_page.all_newsrooms') }}
</a>
</div>
{{-- Firmenprofil --}}
<div class="bg-bg-elev border border-bg-rule p-6 sm:p-8">
<div class="flex items-start gap-4 sm:gap-6">
@if ($logoUrl)
<img src="{{ $logoUrl }}" alt="{{ $company->name }}" class="w-16 h-16 sm:w-24 sm:h-24 object-contain bg-white border border-bg-rule flex-shrink-0">
@else
<div class="w-16 h-16 sm:w-24 sm:h-24 flex items-center justify-center text-white text-[30px] sm:text-[44px] font-bold font-serif flex-shrink-0" style="background: {{ $color }};">
{{ $initial }}
</div>
@endif
<div class="min-w-0 flex-1">
<h1 class="font-serif text-[28px] sm:text-[38px] lg:text-[42px] font-semibold m-0 leading-[1.05] tracking-[-1px] text-ink">{{ $company->name }}</h1>
<div class="mt-3 text-[12px] text-ink-3 flex items-center gap-2.5 flex-wrap">
@if ($websiteHost)
<a href="{{ $company->websiteUrl() }}" target="_blank" rel="nofollow noopener" class="inline-flex items-center gap-1.5 font-medium hover:text-ink transition-colors">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true"><circle cx="6" cy="6" r="5" stroke="currentColor" stroke-width="1"/><path d="M1 6h10M6 1c2 1.5 2 8.5 0 10M6 1c-2 1.5-2 8.5 0 10" stroke="currentColor" stroke-width="1"/></svg>
{{ $websiteHost }}
</a>
<span class="text-ink-4" aria-hidden="true">·</span>
@endif
<span class="inline-flex items-center gap-1.5">
<span class="w-1.5 h-1.5 rounded-full bg-brand pulse-dot"></span>
{{ __('web.newsroom_page.recent_stat', ['count' => $fmt($newsroomStats['recent'] ?? 0)]) }}
</span>
@if (! empty($newsroomStats['sinceYear']))
<span class="text-ink-4" aria-hidden="true">·</span>
<span>{{ __('web.newsroom_page.since', ['year' => $newsroomStats['sinceYear']]) }}</span>
@endif
<span class="text-ink-4" aria-hidden="true">·</span>
<a href="{{ $newsroomFeedUrl }}" type="application/rss+xml" class="inline-flex items-center gap-1.5 font-medium hover:text-ink transition-colors" aria-label="{{ __('web.rss.newsroom_link_aria', ['company' => $company->name]) }}">
<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M2 12.5A1.5 1.5 0 1 1 3.5 14 1.5 1.5 0 0 1 2 12.5ZM2 8a6 6 0 0 1 6 6h2a8 8 0 0 0-8-8Zm0-4a10 10 0 0 1 10 10h2A12 12 0 0 0 2 2Z"/></svg>
{{ __('web.newsroom_page.feed_link') }}
</a>
</div>
</div>
</div>
@if ($boilerplate)
<p class="font-serif text-[15.5px] leading-[1.6] m-0 mt-5 pt-5 border-t border-bg-rule text-ink-2">{{ Str::limit($boilerplate, 360) }}</p>
@endif
{{-- Verwaltungszugang (Roadmap 1b.4): ein Hinweis, kein Angebot.
Der Zugang ist kostenlos und führt zur Firmen-E-Mail. --}}
<p class="text-[12px] text-ink-3 m-0 mt-5 pt-4 border-t border-bg-rule">
{{ __('web.newsroom_page.manage_hint', ['company' => $company->name]) }}
<a href="{{ route('newsroom.claim', ['slug' => $company->slug]) }}" rel="nofollow" class="font-medium underline underline-offset-2 hover:text-ink transition-colors">{{ __('web.newsroom_page.manage_link') }}</a>
</p>
@if ($categories->isNotEmpty())
<div class="flex flex-wrap items-center gap-2 mt-5 pt-5 border-t border-bg-rule">
<span class="eyebrow muted text-[10px] mr-1.5">{{ __('web.newsroom_page.industries') }}</span>
@foreach ($categories as $cat)
<a href="{{ $cat['href'] }}" class="inline-flex items-baseline gap-[7px] px-3 py-1.5 text-[12px] font-medium border border-bg-rule-strong bg-bg text-ink hover:bg-white hover:border-brand transition-colors">
{{ $cat['name'] }}
<span class="font-mono text-[10.5px] text-ink-3 font-medium">{{ $fmt($cat['count']) }}</span>
</a>
@endforeach
</div>
@endif
</div>
</section>
{{-- Stat strip --}}
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 mt-6">
<div class="grid grid-cols-2 lg:grid-cols-4 border-y border-bg-rule">
<div class="px-6 py-5 border-r border-bg-rule">
<div class="eyebrow muted text-[10px] mb-2.5">{{ __('web.category.stat_today') }}</div>
<div class="font-serif text-[32px] sm:text-[36px] font-semibold leading-none tracking-[-0.6px] text-ink">{{ $fmt($newsroomStats['today'] ?? 0) }}</div>
<div class="text-[11.5px] text-ink-3 mt-2">{{ __('web.spotlight.stat_window_sub', ['date' => $referenceNow->translatedFormat('j. M Y')]) }}</div>
</div>
<div class="px-6 py-5 lg:border-r border-bg-rule">
<div class="eyebrow muted text-[10px] mb-2.5">{{ __('web.newsroom_page.last_30_days') }}</div>
<div class="font-serif text-[32px] sm:text-[36px] font-semibold leading-none tracking-[-0.6px] text-ink">{{ $fmt($newsroomStats['recent'] ?? 0) }}</div>
<div class="text-[11.5px] text-ink-3 mt-2">{{ __('web.newsroom_page.published_releases') }}</div>
</div>
<div class="px-6 py-5 border-r border-bg-rule border-t lg:border-t-0">
<div class="eyebrow muted text-[10px] mb-2.5">{{ __('web.spotlight.stat_archive') }}</div>
<div class="font-serif text-[32px] sm:text-[36px] font-semibold leading-none tracking-[-0.6px] text-ink">{{ $fmt($newsroomStats['total'] ?? 0) }}</div>
<div class="text-[11.5px] text-ink-3 mt-2">{{ __('web.spotlight.stat_archive_sub') }}</div>
</div>
<div class="px-6 py-5 border-t lg:border-t-0">
<div class="eyebrow muted text-[10px] mb-2.5">{{ __('web.newsroom_page.total_hits') }}</div>
<div class="font-serif text-[32px] sm:text-[36px] font-semibold leading-none tracking-[-0.6px] text-ink">{{ $fmt($newsroomStats['totalHits'] ?? 0) }}</div>
<div class="text-[11.5px] text-ink-3 mt-2">{{ __('web.newsroom_page.total_hits_sub') }}</div>
</div>
</div>
</section>
@if ($leadRelease)
{{-- Top-Meldung --}}
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 mt-10">
<article>
<a href="{{ route('release.detail', ['slug' => $leadRelease->slug]) }}" class="block group">
{{-- Kein hatch-dark: dessen background-image würde den bg-hero-grad-Verlauf überschreiben.
Text im Flow (min-h + flex), damit lange Titel auf Mobile nicht geclippt werden. --}}
<div class="relative overflow-hidden bg-hero-grad min-h-[220px] sm:min-h-[280px] lg:min-h-[340px] flex flex-col justify-end">
@if ($leadImageUrl)
<img src="{{ $leadImageUrl }}" alt="{{ $leadRelease->title }}" class="absolute inset-0 w-full h-full object-cover" loading="lazy">
@elseif ($leadPlaceholderUrl)
<img src="{{ $leadPlaceholderUrl }}" alt="" aria-hidden="true" class="absolute inset-0 w-full h-full object-cover" loading="lazy">
@else
<x-web.hero-fallback />
@endif
<div class="absolute top-4 left-4 sm:top-5 sm:left-5 z-10 flex gap-2">
<span class="bp-tag">{{ __('web.newsroom_page.latest') }}</span>
</div>
<div class="relative z-10 px-4 sm:px-9 pb-6 sm:pb-8 pt-14 sm:pt-[70px] text-white" style="background:linear-gradient(180deg,transparent,rgba(0,0,0,0.92));">
@if ($leadCatName)
<div class="eyebrow mb-3" style="color:#FF8B6F;">{{ $leadCatName }}</div>
@endif
<h2 class="font-serif text-[22px] sm:text-[28px] lg:text-[34px] font-semibold m-0 leading-[1.12] tracking-[-0.6px] max-w-[760px] group-hover:text-white">
{{ $leadRelease->title }}
</h2>
@if ($leadLead)
<p class="font-serif text-[13.5px] sm:text-[14.5px] leading-[1.5] mt-3 sm:mt-3.5 max-w-[640px] text-white/85">{{ $leadLead }}</p>
@endif
<div class="mt-3.5 sm:mt-[18px] flex items-center gap-3.5 text-[12px] text-white/70 flex-wrap">
@if ($leadRelease->published_at)
<span class="font-mono">{{ $leadRelease->published_at->translatedFormat('d.m.Y · H:i') }}</span>
<span aria-hidden="true">·</span>
@endif
<span>{{ __('web.shared.read_time', ['minutes' => $leadReadTime]) }}</span>
</div>
</div>
</div>
</a>
</article>
</section>
@endif
{{-- Meldungen + Sidebar --}}
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 mt-12 mb-16 grid gap-9 lg:grid-cols-[1.7fr_1fr]">
<div>
<header class="flex items-baseline justify-between mb-4 min-h-[34px] flex-wrap gap-3">
<h2 class="font-serif text-[28px] font-semibold m-0 tracking-[-0.3px] text-ink">
{{ __('web.category.all_releases') }} <span class="text-[14px] text-ink-3 font-normal ml-1.5">· {{ $fmt($newsroomStats['total'] ?? 0) }}</span>
</h2>
</header>
<hr class="rule-strong">
@forelse ($releases as $release)
<x-web.feed-item :release="$release" />
@empty
<p class="text-[13px] text-ink-3 py-6">{{ __('web.newsroom_page.empty') }}</p>
@endforelse
<x-web.pagination :paginator="$releases" />
</div>
<aside class="flex flex-col gap-9">
<x-web.most-read :releases="$mostReadReleases ?? []" />
{{-- Eigenen Newsroom starten --}}
<section class="border border-bg-rule-strong bg-bg-elev p-6">
<div class="eyebrow mb-2">{{ __('web.publisher_cta.eyebrow') }}</div>
<h3 class="font-serif text-[20px] font-semibold m-0 mb-2 tracking-[-0.2px] leading-[1.2] text-ink">
{{ __('web.newsroom_page.own_title') }}
</h3>
<p class="text-[12.5px] leading-[1.5] m-0 mb-4 text-ink-2">
{{ __('web.newsroom_page.own_text') }}
</p>
<a href="{{ route('veroeffentlichen') }}"
class="w-full inline-flex items-center justify-center gap-2 px-[18px] py-2.5 text-[13px] font-semibold bg-white border border-bg-rule-strong text-ink rounded-[2px] cursor-pointer hover:border-brand hover:text-brand transition-colors">
{{ __('web.newsroom_page.own_cta') }} →
</a>
</section>
<x-web.publisher-cta />
</aside>
</section>
</main>
<x-web.site-footer />
@endsection