12-05-2026 Frontend dev
This commit is contained in:
parent
405df0a122
commit
5b8bdf4182
779 changed files with 480564 additions and 6241 deletions
|
|
@ -2,5 +2,5 @@
|
|||
<x-app-logo-icon class="size-5 fill-current text-white dark:text-black" />
|
||||
</div>
|
||||
<div class="ms-1 grid flex-1 text-start text-sm">
|
||||
<span class="mb-0.5 truncate leading-none font-semibold">pr-copilot</span>
|
||||
<span class="mb-0.5 truncate leading-none font-semibold">{{ config('app.name') }}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,32 @@
|
|||
<x-layouts.app.sidebar :title="$title ?? null">
|
||||
<flux:main>
|
||||
@php
|
||||
$user = auth()->user();
|
||||
$canCustomer = $user?->canAccessCustomer() ?? false;
|
||||
@endphp
|
||||
|
||||
@if($canCustomer)
|
||||
<div class="mb-6 rounded-xl border border-zinc-200 bg-zinc-50/80 px-4 py-3 shadow-sm ring-1 ring-zinc-950/5 dark:border-zinc-700 dark:bg-zinc-900/60 dark:ring-white/10">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:badge color="zinc" size="sm">{{ __('User Backend') }}</flux:badge>
|
||||
<flux:text class="hidden text-xs text-zinc-500 dark:text-zinc-400 sm:block">
|
||||
{{ __('Firmenkontext') }}
|
||||
</flux:text>
|
||||
</div>
|
||||
<flux:heading size="sm" class="mt-1 truncate">
|
||||
{{ $title ?? __('Mein Bereich') }}
|
||||
</flux:heading>
|
||||
</div>
|
||||
|
||||
<div class="w-full sm:w-auto">
|
||||
<livewire:customer.company-switcher />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{ $slot }}
|
||||
</flux:main>
|
||||
</x-layouts.app.sidebar>
|
||||
|
|
|
|||
|
|
@ -10,34 +10,169 @@
|
|||
<x-app-logo />
|
||||
</a>
|
||||
|
||||
@php
|
||||
$user = auth()->user();
|
||||
$impersonation = app(\App\Actions\Admin\UserImpersonation::class);
|
||||
$impersonator = $impersonation->impersonator();
|
||||
$isImpersonating = $impersonation->isActive();
|
||||
$canAdmin = ($user?->canAccessAdmin() ?? false) && ! $isImpersonating;
|
||||
$canCustomer = $user?->canAccessCustomer() ?? false;
|
||||
$reviewCount = $canAdmin
|
||||
? app(\App\Services\Admin\AdminPerformanceCache::class)->pressReleaseReviewCount()
|
||||
: 0;
|
||||
@endphp
|
||||
|
||||
<flux:navlist variant="outline">
|
||||
<flux:navlist.group :heading="__('Trader')" class="grid mb-4">
|
||||
<flux:navlist.item icon="home" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate>{{ __('Dashboard') }}</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
{{-- Dashboard (Admin/Editor) --}}
|
||||
@if($canAdmin)
|
||||
<flux:navlist.item icon="chart-bar" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate class="mb-4">
|
||||
{{ __('Dashboard') }}
|
||||
</flux:navlist.item>
|
||||
@endif
|
||||
|
||||
<flux:navlist.group :heading="__('Customer')" class="grid mb-4">
|
||||
<flux:navlist.item icon="home" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate>{{ __('Dashboard') }}</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
|
||||
<flux:navlist.group :heading="__('Admin')" class="grid mb-4">
|
||||
<flux:navlist.item icon="user-group" :href="route('admin.users')" :current="request()->routeIs('admin.users')" wire:navigate>{{ __('Users') }}</flux:navlist.item>
|
||||
<flux:navlist.item icon="user-group" :href="route('admin.users.table')" :current="request()->routeIs('admin.users.table')" wire:navigate>{{ __('Users Table') }}</flux:navlist.item>
|
||||
<flux:navlist.group expandable expanded="false" heading="Favorites" class="hidden lg:grid">
|
||||
<flux:navlist.item icon="user-group" :href="route('admin.users')" :current="request()->routeIs('admin.users')" wire:navigate>{{ __('Users') }}</flux:navlist.item>
|
||||
<flux:navlist.item icon="user-group" :href="route('admin.users.table')" :current="request()->routeIs('admin.users.table')" wire:navigate>{{ __('Users Table') }}</flux:navlist.item>
|
||||
{{-- Mein Bereich – sichtbar für alle Panel-User --}}
|
||||
@if($canCustomer)
|
||||
<flux:navlist.group :heading="__('Mein Bereich')" class="grid mb-4">
|
||||
<flux:navlist.item icon="home" :href="route('me.dashboard')" :current="request()->routeIs('me.dashboard')" wire:navigate>
|
||||
{{ __('Übersicht') }}
|
||||
</flux:navlist.item>
|
||||
<flux:navlist.item icon="newspaper" :href="route('me.press-releases.index')" :current="request()->routeIs('me.press-releases.*')" wire:navigate>
|
||||
{{ __('Meine Pressemitteilungen') }}
|
||||
</flux:navlist.item>
|
||||
<flux:navlist.item icon="building-office" :href="route('me.press-kits.index')" :current="request()->routeIs('me.press-kits.*')" wire:navigate>
|
||||
{{ __('Firmen') }}
|
||||
</flux:navlist.item>
|
||||
<flux:navlist.item icon="shopping-bag" :href="route('me.bookings.index')" :current="request()->routeIs('me.bookings.*')" wire:navigate>
|
||||
{{ __('Buchungen & Add-ons') }}
|
||||
</flux:navlist.item>
|
||||
<div class="px-3 py-1.5 text-sm text-zinc-400 dark:text-zinc-500">
|
||||
{{ __('Statistiken') }} <span class="text-xs">{{ __('später') }}</span>
|
||||
</div>
|
||||
</flux:navlist.group>
|
||||
</flux:navlist.group>
|
||||
|
||||
<flux:navlist.group :heading="__('CMS')" class="grid mb-4">
|
||||
<flux:navlist.item icon="home" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate>{{ __('Dashboard') }}</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
<flux:navlist.group :heading="__('Finanzen')" class="grid mb-4">
|
||||
<div class="px-3 py-1.5 text-sm text-zinc-400 dark:text-zinc-500">
|
||||
{{ __('Credits & Tarif') }} <span class="text-xs">{{ __('später') }}</span>
|
||||
</div>
|
||||
<flux:navlist.item icon="document-text" :href="route('me.invoices.index')" :current="request()->routeIs('me.invoices.*')" wire:navigate>
|
||||
{{ __('Rechnungen') }}
|
||||
</flux:navlist.item>
|
||||
<div class="px-3 py-1.5 text-sm text-zinc-400 dark:text-zinc-500">
|
||||
{{ __('Zahlungsarten') }} <span class="text-xs">{{ __('später') }}</span>
|
||||
</div>
|
||||
</flux:navlist.group>
|
||||
|
||||
<flux:navlist.group :heading="__('Konto')" class="grid mb-4">
|
||||
<flux:navlist.item icon="user" :href="route('me.profile')" :current="request()->routeIs('me.profile')" wire:navigate>
|
||||
{{ __('Profil') }}
|
||||
</flux:navlist.item>
|
||||
<flux:navlist.item icon="shield-check" :href="route('me.security')" :current="request()->routeIs('me.security')" wire:navigate>
|
||||
{{ __('Sicherheit') }}
|
||||
</flux:navlist.item>
|
||||
<flux:navlist.item icon="key" :href="route('me.tokens.index')" :current="request()->routeIs('me.tokens.*')" wire:navigate>
|
||||
{{ __('API & Integrationen') }}
|
||||
</flux:navlist.item>
|
||||
<div class="px-3 py-1.5 text-sm text-zinc-400 dark:text-zinc-500">
|
||||
{{ __('Benachrichtigungen') }} <span class="text-xs">{{ __('später') }}</span>
|
||||
</div>
|
||||
</flux:navlist.group>
|
||||
@endif
|
||||
|
||||
{{-- Content Management (Admin/Editor) --}}
|
||||
@if($canAdmin)
|
||||
<flux:navlist.group :heading="__('Content')" class="grid mb-4">
|
||||
<flux:navlist.item
|
||||
icon="newspaper"
|
||||
:href="route('admin.press-releases.index', $reviewCount > 0 ? ['status' => 'review'] : [])"
|
||||
:current="request()->routeIs('admin.press-releases.*')"
|
||||
:badge="$reviewCount > 0 ? $reviewCount : null"
|
||||
badge-color="yellow"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('Pressemitteilungen') }}
|
||||
</flux:navlist.item>
|
||||
<flux:navlist.item icon="folder" :href="route('admin.categories.index')" :current="request()->routeIs('admin.categories.*')" wire:navigate>
|
||||
{{ __('Kategorien') }}
|
||||
</flux:navlist.item>
|
||||
<flux:navlist.item icon="code-bracket-square" :href="route('admin.footer-codes.index')" :current="request()->routeIs('admin.footer-codes.*')" wire:navigate>
|
||||
{{ __('Footer-Codes') }}
|
||||
</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
|
||||
{{-- CRM --}}
|
||||
<flux:navlist.group :heading="__('CRM')" class="grid mb-4">
|
||||
<flux:navlist.item icon="building-office" :href="route('admin.companies.index')" :current="request()->routeIs('admin.companies.*')" wire:navigate>
|
||||
{{ __('Firmen') }}
|
||||
</flux:navlist.item>
|
||||
<flux:navlist.item icon="user-group" :href="route('admin.contacts.index')" :current="request()->routeIs('admin.contacts.*')" wire:navigate>
|
||||
{{ __('Kontakte') }}
|
||||
</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
|
||||
{{-- Billing --}}
|
||||
<flux:navlist.group :heading="__('Billing')" class="grid mb-4">
|
||||
<flux:navlist.item icon="archive-box" :href="route('admin.invoices.index')" :current="request()->routeIs('admin.invoices.*')" wire:navigate>
|
||||
{{ __('Legacy Rechnungen') }}
|
||||
</flux:navlist.item>
|
||||
<flux:navlist.item icon="credit-card" :href="route('admin.payments.index')" :current="request()->routeIs('admin.payments.*')" wire:navigate>
|
||||
{{ __('Zahlungen') }}
|
||||
</flux:navlist.item>
|
||||
<flux:navlist.item icon="ticket" :href="route('admin.coupons.index')" :current="request()->routeIs('admin.coupons.*')" wire:navigate>
|
||||
{{ __('Gutscheine') }}
|
||||
</flux:navlist.item>
|
||||
<flux:navlist.item icon="envelope" :href="route('admin.newsletter.sync')" :current="request()->routeIs('admin.newsletter.sync')" wire:navigate>
|
||||
{{ __('Newsletter Sync') }}
|
||||
</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
|
||||
{{-- Administration --}}
|
||||
<flux:navlist.group :heading="__('Administration')" class="grid mb-4">
|
||||
<flux:navlist.item icon="cog" :href="route('admin.presets.index')" :current="request()->routeIs('admin.presets.*')" wire:navigate>
|
||||
{{ __('Voreinstellungen') }}
|
||||
</flux:navlist.item>
|
||||
<flux:navlist.item icon="users" :href="route('admin.users.index')" :current="request()->routeIs('admin.users.*')" wire:navigate>
|
||||
{{ __('Benutzer') }}
|
||||
</flux:navlist.item>
|
||||
<flux:navlist.item icon="shield-check" :href="route('admin.roles.index')" :current="request()->routeIs('admin.roles.*')" wire:navigate>
|
||||
{{ __('Rollen & Rechte') }}
|
||||
</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
|
||||
{{-- Reports --}}
|
||||
<flux:navlist.group :heading="__('Reports')" class="grid mb-4">
|
||||
<flux:navlist.item icon="chart-bar-square" :href="route('admin.reports.slow-requests')" :current="request()->routeIs('admin.reports.*')" wire:navigate>
|
||||
{{ __('Performance') }}
|
||||
</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
@endif
|
||||
|
||||
<flux:navlist.group :heading="__('Superadmin')" class="grid mb-4">
|
||||
<flux:navlist.item icon="home" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate>{{ __('Dashboard') }}</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
</flux:navlist>
|
||||
|
||||
{{-- Portal-Filter für Admin-Benutzer (P2.6) --}}
|
||||
@auth
|
||||
@if($canAdmin)
|
||||
<div class="border-t border-zinc-200 dark:border-zinc-700 mt-2 pt-2">
|
||||
<livewire:admin.portal-switcher />
|
||||
</div>
|
||||
@endif
|
||||
@endauth
|
||||
|
||||
@if($impersonator)
|
||||
<div class="mt-3 rounded-lg border border-amber-200 bg-amber-50 p-3 text-sm text-amber-950 dark:border-amber-700/60 dark:bg-amber-950/40 dark:text-amber-100">
|
||||
<flux:text weight="semibold">{{ __('Testmodus aktiv') }}</flux:text>
|
||||
<flux:text class="mt-1 text-xs">
|
||||
{{ __('Angemeldet als :user. Admin: :admin.', ['user' => $user?->name, 'admin' => $impersonator->name]) }}
|
||||
</flux:text>
|
||||
|
||||
<form method="POST" action="{{ route('admin.impersonate.leave') }}" class="mt-3">
|
||||
@csrf
|
||||
<flux:button type="submit" size="sm" variant="primary" class="w-full">
|
||||
{{ __('Zurück zum Admin') }}
|
||||
</flux:button>
|
||||
</form>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<flux:spacer />
|
||||
|
||||
<flux:navlist variant="outline">
|
||||
|
|
|
|||
62
resources/views/components/web/active-newsrooms.blade.php
Normal file
62
resources/views/components/web/active-newsrooms.blade.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
@props([
|
||||
'companies' => [],
|
||||
])
|
||||
|
||||
@php
|
||||
$palette = ['#0098A6', '#21A038', '#0018A8', '#1E1E1E', '#E20074', '#003781', '#C84A1E', '#5A6E2F'];
|
||||
|
||||
$companies = collect($companies)->take(6)->values();
|
||||
|
||||
if ($companies->isEmpty()) {
|
||||
$companies = collect([
|
||||
['initial' => 'S', 'name' => 'Siemens AG', 'count' => 18, 'color' => '#0098A6'],
|
||||
['initial' => 'B', 'name' => 'BASF SE', 'count' => 14, 'color' => '#21A038'],
|
||||
['initial' => 'D', 'name' => 'Deutsche Bank', 'count' => 11, 'color' => '#0018A8'],
|
||||
['initial' => 'V', 'name' => 'Volkswagen AG', 'count' => 16, 'color' => '#1E1E1E'],
|
||||
['initial' => 'T', 'name' => 'Deutsche Telekom', 'count' => 9, 'color' => '#E20074'],
|
||||
['initial' => 'A', 'name' => 'Allianz SE', 'count' => 8, 'color' => '#003781'],
|
||||
]);
|
||||
}
|
||||
@endphp
|
||||
|
||||
<section>
|
||||
<header class="flex items-baseline justify-between mb-3.5 min-h-[34px]">
|
||||
<h2 class="font-serif text-[28px] font-semibold m-0 tracking-[-0.3px] leading-[1.2] text-ink">Aktive Newsrooms</h2>
|
||||
<span class="eyebrow muted text-[10px]">Heute</span>
|
||||
</header>
|
||||
<hr class="rule-strong mb-1">
|
||||
|
||||
@foreach ($companies as $index => $company)
|
||||
@php
|
||||
$initial = $company['initial'] ?? strtoupper(mb_substr($company['name'] ?? '?', 0, 1));
|
||||
$name = $company['name'] ?? '';
|
||||
$count = (int) ($company['count'] ?? 0);
|
||||
$color = $company['color'] ?? $palette[$index % count($palette)];
|
||||
$slug = $company['slug'] ?? null;
|
||||
$href = $slug ? url('/newsroom/'.$slug) : route('newsrooms');
|
||||
$isLast = $loop->last;
|
||||
@endphp
|
||||
<a href="{{ $href }}"
|
||||
class="grid items-center gap-3 py-3 {{ $isLast ? '' : 'border-b border-bg-rule' }} cursor-pointer hover:bg-bg-elev transition-colors group"
|
||||
style="grid-template-columns: 32px 1fr auto;">
|
||||
<div class="w-8 h-8 text-white flex items-center justify-center text-[14px] font-bold font-serif"
|
||||
style="background: {{ $color }};">
|
||||
{{ $initial }}
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<div class="text-[13.5px] font-semibold leading-[1.2] text-ink truncate group-hover:text-brand transition-colors">{{ $name }}</div>
|
||||
<div class="text-[11px] text-ink-3 mt-0.5">
|
||||
<span class="font-mono">{{ $count }}</span> Meldungen letzte 7 Tage
|
||||
</div>
|
||||
</div>
|
||||
<span class="inline-flex items-center gap-1.5 text-[10.5px] font-semibold text-ink-2 px-2 py-0.5 bg-brand/[0.06] border border-brand/20 whitespace-nowrap rounded-[2px]">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-brand pulse-dot"></span>
|
||||
heute aktiv
|
||||
</span>
|
||||
</a>
|
||||
@endforeach
|
||||
|
||||
<a href="{{ route('newsrooms') }}" class="block mt-3 text-[12px] text-brand font-semibold cursor-pointer hover:text-brand-deep transition-colors">
|
||||
Alle Newsrooms anzeigen →
|
||||
</a>
|
||||
</section>
|
||||
39
resources/views/components/web/article-card.blade.php
Normal file
39
resources/views/components/web/article-card.blade.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
@props([
|
||||
'article',
|
||||
])
|
||||
|
||||
<article class="card card-hover overflow-hidden group cursor-pointer">
|
||||
<div class="relative h-48 overflow-hidden bg-zinc-100 dark:bg-zinc-800">
|
||||
<img src="{{ $article['image'] }}" alt="{{ $article['title'] }}"
|
||||
class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105">
|
||||
<div class="absolute top-3 left-3">
|
||||
<span class="badge badge-{{ $article['badgeType'] ?? 'primary' }} shadow-md text-xs">
|
||||
{{ $article['badge'] }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<div class="flex items-center gap-2 text-xs text-zinc-600 dark:text-zinc-400 mb-3">
|
||||
<span class="badge badge-{{ $article['categoryBadgeType'] ?? 'secondary' }} text-xs">
|
||||
{{ $article['category'] }}
|
||||
</span>
|
||||
<span>•</span>
|
||||
<time datetime="{{ $article['date'] }}">{{ $article['dateFormatted'] }}</time>
|
||||
</div>
|
||||
<h3 class="text-lg font-bold text-zinc-900 dark:text-zinc-100 mb-2 group-hover:text-[var(--color-primary)] transition-colors line-clamp-2">
|
||||
{{ $article['title'] }}
|
||||
</h3>
|
||||
<p class="text-sm text-zinc-700 dark:text-zinc-300 line-clamp-3 mb-4">
|
||||
{{ $article['teaser'] }}
|
||||
</p>
|
||||
@if(isset($article['author']))
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<div class="w-6 h-6 rounded-full bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-secondary)] flex items-center justify-center text-white font-semibold text-[10px]">
|
||||
{{ $article['authorInitials'] }}
|
||||
</div>
|
||||
<span class="text-zinc-600 dark:text-zinc-400">{{ $article['author'] }}</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</article>
|
||||
|
||||
34
resources/views/components/web/breadcrumb.blade.php
Normal file
34
resources/views/components/web/breadcrumb.blade.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
@props([
|
||||
'items' => [],
|
||||
])
|
||||
|
||||
@if(count($items) > 0)
|
||||
<nav aria-label="Breadcrumb" class="py-4">
|
||||
<ol class="flex items-center gap-2 text-sm text-zinc-600 dark:text-zinc-400">
|
||||
<li>
|
||||
<a href="/" class="hover:text-[var(--color-primary)] transition-colors">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@foreach($items as $index => $item)
|
||||
<li class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
|
||||
@if($index === count($items) - 1)
|
||||
<span class="text-zinc-900 dark:text-zinc-100 font-medium">{{ $item['label'] }}</span>
|
||||
@else
|
||||
<a href="{{ $item['url'] }}" class="hover:text-[var(--color-primary)] transition-colors">
|
||||
{{ $item['label'] }}
|
||||
</a>
|
||||
@endif
|
||||
</li>
|
||||
@endforeach
|
||||
</ol>
|
||||
</nav>
|
||||
@endif
|
||||
|
||||
30
resources/views/components/web/category-strip.blade.php
Normal file
30
resources/views/components/web/category-strip.blade.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
@props([
|
||||
'categories',
|
||||
])
|
||||
|
||||
<section class="rounded-2xl border border-black/10 bg-[#f7f4ef] p-5 dark:border-white/10 dark:bg-zinc-950">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-[#b6332a]">Branchen</p>
|
||||
<h2 class="mt-2 text-lg font-semibold text-zinc-950 dark:text-white">Zugänge nach Themen</h2>
|
||||
</div>
|
||||
<a href="{{ route('kategorien') }}" class="text-sm font-semibold text-[#cf3628] hover:text-[#a92c25]">Alle</a>
|
||||
</div>
|
||||
|
||||
@if ($categories->isNotEmpty())
|
||||
<div class="mt-5 flex flex-col gap-2">
|
||||
@foreach ($categories as $category)
|
||||
<a href="{{ $category['url'] }}" class="group flex items-center justify-between gap-4 rounded-xl border border-black/5 bg-white px-4 py-3 text-sm transition hover:border-[#cf3628]/30 dark:border-white/10 dark:bg-zinc-900">
|
||||
<span class="font-medium text-zinc-800 group-hover:text-[#cf3628] dark:text-zinc-100">{{ $category['name'] }}</span>
|
||||
@if ($category['count'] > 0)
|
||||
<span class="rounded-full bg-[#cf3628]/10 px-2.5 py-1 text-xs font-semibold text-[#a92c25]">{{ $category['count'] }}</span>
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<p class="mt-5 rounded-xl border border-dashed border-black/10 bg-white px-4 py-4 text-sm leading-6 text-zinc-600 dark:border-white/10 dark:bg-zinc-900 dark:text-zinc-300">
|
||||
Branchenzugänge werden angezeigt, sobald aktive Kategorien mit deutscher Übersetzung vorhanden sind.
|
||||
</p>
|
||||
@endif
|
||||
</section>
|
||||
37
resources/views/components/web/content-layout.blade.php
Normal file
37
resources/views/components/web/content-layout.blade.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
@props([
|
||||
'sidebar' => null,
|
||||
'sidebarPosition' => 'right', // left or right
|
||||
])
|
||||
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
@if ($sidebar)
|
||||
<div class="grid lg:grid-cols-12 gap-8">
|
||||
@if ($sidebarPosition === 'left')
|
||||
<!-- Sidebar Left -->
|
||||
<aside class="lg:col-span-4">
|
||||
{{ $sidebar }}
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="lg:col-span-8">
|
||||
{{ $slot }}
|
||||
</main>
|
||||
@else
|
||||
<!-- Main Content -->
|
||||
<main class="lg:col-span-8">
|
||||
{{ $slot }}
|
||||
</main>
|
||||
|
||||
<!-- Sidebar Right -->
|
||||
<aside class="lg:col-span-4">
|
||||
{{ $sidebar }}
|
||||
</aside>
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
<!-- Full Width Content -->
|
||||
<main class="max-w-4xl mx-auto">
|
||||
{{ $slot }}
|
||||
</main>
|
||||
@endif
|
||||
</div>
|
||||
23
resources/views/components/web/dossier-card.blade.php
Normal file
23
resources/views/components/web/dossier-card.blade.php
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
@props([
|
||||
'dossier',
|
||||
])
|
||||
|
||||
<article class="card overflow-hidden group cursor-pointer bg-white dark:bg-zinc-900 shadow-lg hover:shadow-2xl transition-all duration-300">
|
||||
<div class="relative h-56 overflow-hidden">
|
||||
<img src="{{ $dossier['image'] }}" alt="{{ $dossier['title'] }}"
|
||||
class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110">
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent"></div>
|
||||
<div class="absolute bottom-0 left-0 right-0 p-5">
|
||||
<span class="inline-block badge bg-white text-[var(--color-primary)] border-0 mb-3 shadow-md">
|
||||
{{ $dossier['articleCount'] }} Artikel
|
||||
</span>
|
||||
<h3 class="text-xl font-bold text-white mb-2 group-hover:text-[var(--color-secondary)] transition-colors">
|
||||
{{ $dossier['title'] }}
|
||||
</h3>
|
||||
<p class="text-sm text-white/90 line-clamp-2">
|
||||
{{ $dossier['description'] }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
76
resources/views/components/web/events-week.blade.php
Normal file
76
resources/views/components/web/events-week.blade.php
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
@props([
|
||||
'events' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
$events ??= [
|
||||
['day' => 'Heute', 'date' => '06. Mai', 'type' => 'Quartalszahlen', 'company' => 'SAP SE', 'time' => '07:00', 'note' => 'Q1 2026', 'today' => true, 'highlighted' => true],
|
||||
['day' => 'Heute', 'date' => '06. Mai', 'type' => 'Hauptversammlung', 'company' => 'Allianz SE', 'time' => '10:00', 'note' => 'München', 'today' => true],
|
||||
['day' => 'Morgen', 'date' => '07. Mai', 'type' => 'Pressekonferenz', 'company' => 'Volkswagen AG', 'time' => '09:30', 'note' => 'Strategie 2030', 'highlighted' => true],
|
||||
['day' => 'Morgen', 'date' => '07. Mai', 'type' => 'Quartalszahlen', 'company' => 'Deutsche Telekom', 'time' => '07:00', 'note' => 'Q1 2026'],
|
||||
['day' => 'Do', 'date' => '08. Mai', 'type' => 'Konferenz', 'company' => 'Hannover Messe', 'time' => 'Ganztägig', 'note' => 'Industrie 4.0', 'ad' => true],
|
||||
['day' => 'Fr', 'date' => '09. Mai', 'type' => 'Hauptversammlung', 'company' => 'BMW Group', 'time' => '10:00', 'note' => 'München'],
|
||||
['day' => 'Mo', 'date' => '12. Mai', 'type' => 'Quartalszahlen', 'company' => 'Siemens AG', 'time' => '07:00', 'note' => 'Q2 2026', 'highlighted' => true],
|
||||
];
|
||||
@endphp
|
||||
|
||||
<section class="max-w-layout mx-auto px-8 mt-16">
|
||||
<header class="flex items-baseline justify-between mb-4 flex-wrap gap-3">
|
||||
<div class="flex items-baseline gap-3.5 flex-wrap">
|
||||
<h2 class="font-serif text-[28px] font-semibold m-0 tracking-[-0.3px] text-ink">Termine & Events</h2>
|
||||
<span class="eyebrow muted">Diese Woche im Überblick</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 text-[12px] flex-wrap">
|
||||
<button type="button" class="inline-flex items-center gap-1.5 text-ink-2 cursor-pointer hover:text-ink transition-colors">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M3 7h18M3 12h18M3 17h12" stroke="currentColor" stroke-width="2" />
|
||||
</svg>
|
||||
Alle Branchen
|
||||
</button>
|
||||
<a href="#" class="inline-flex items-center gap-1.5 text-ink-2 cursor-pointer hover:text-ink transition-colors">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<rect x="3" y="5" width="18" height="16" rx="2" stroke="currentColor" stroke-width="2" />
|
||||
<path d="M3 9h18M8 3v4M16 3v4" stroke="currentColor" stroke-width="2" />
|
||||
</svg>
|
||||
iCal abonnieren
|
||||
</a>
|
||||
<a href="{{ route('kategorien') }}#termine" class="text-brand font-semibold cursor-pointer hover:text-brand-deep transition-colors">Alle Termine →</a>
|
||||
</div>
|
||||
</header>
|
||||
<hr class="rule-strong">
|
||||
|
||||
<div class="grid border-b border-bg-rule grid-cols-2 md:grid-cols-4 lg:grid-cols-7">
|
||||
@foreach ($events as $idx => $event)
|
||||
@php
|
||||
$isLastInRow = ($idx + 1) % 7 === 0;
|
||||
@endphp
|
||||
<a href="#"
|
||||
class="block p-4 pb-[18px] relative border-b lg:border-b-0 border-bg-rule {{ $isLastInRow ? '' : 'border-r' }} cursor-pointer hover:bg-bg-elev transition-colors group"
|
||||
@if($event['highlighted'] ?? false) style="background:rgba(204,55,51,0.025);" @endif>
|
||||
@if ($event['highlighted'] ?? false)
|
||||
<span class="absolute top-3.5 right-3.5 w-1.5 h-1.5 rounded-full bg-brand pulse-dot"></span>
|
||||
@endif
|
||||
<div class="flex items-baseline gap-1.5 mb-2.5 flex-wrap">
|
||||
@if ($event['today'] ?? false)
|
||||
<span class="eyebrow font-bold">{{ $event['day'] }}</span>
|
||||
@else
|
||||
<span class="text-[11px] font-bold tracking-[0.12em] uppercase text-ink-3">{{ $event['day'] }}</span>
|
||||
@endif
|
||||
<span class="text-[10.5px] text-ink-3 font-mono">{{ $event['date'] }}</span>
|
||||
</div>
|
||||
<div class="text-[10.5px] tracking-[0.06em] uppercase font-bold text-ink-2 mb-1.5">{{ $event['type'] }}</div>
|
||||
<div class="font-serif text-[15px] font-semibold leading-[1.25] mb-2 text-ink group-hover:text-brand transition-colors">{{ $event['company'] }}</div>
|
||||
<div class="flex items-center gap-1.5 text-[11px] text-ink-3 flex-wrap">
|
||||
<span class="font-mono">{{ $event['time'] }}</span>
|
||||
@if ($event['note'] ?? null)
|
||||
<span aria-hidden="true">·</span>
|
||||
<span>{{ $event['note'] }}</span>
|
||||
@endif
|
||||
</div>
|
||||
@if ($event['ad'] ?? false)
|
||||
<div class="mt-2 text-[9.5px] tracking-[0.08em] uppercase font-semibold text-ink-4">Anzeige</div>
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
52
resources/views/components/web/feed-ad.blade.php
Normal file
52
resources/views/components/web/feed-ad.blade.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
@props(['ad'])
|
||||
|
||||
@php
|
||||
$time = $ad['time'] ?? '';
|
||||
$date = $ad['date'] ?? '';
|
||||
$category = $ad['category'] ?? 'Anzeige';
|
||||
$title = $ad['title'] ?? '';
|
||||
$company = $ad['company'] ?? '';
|
||||
$href = $ad['href'] ?? '#';
|
||||
|
||||
$catClass = 'self-start whitespace-nowrap text-[9.5px] font-bold uppercase leading-snug tracking-[0.14em] text-card-warm-cat max-md:mt-1 max-md:text-[9px] max-md:tracking-[0.1em]';
|
||||
@endphp
|
||||
|
||||
<div class="-mx-4 my-2">
|
||||
<div class="flex items-center gap-2.5 px-4">
|
||||
<span class="h-px flex-1 bg-bg-card-warm-rule"></span>
|
||||
<span class="text-[9px] font-bold uppercase tracking-[0.22em] text-ink-on-dark-muted">Anzeige</span>
|
||||
<span class="h-px flex-1 bg-bg-card-warm-rule"></span>
|
||||
</div>
|
||||
|
||||
<a href="{{ $href }}" rel="sponsored nofollow" @class([
|
||||
'grid items-start bg-bg-card-warm py-4 pl-1.5 pr-4 sm:pl-2',
|
||||
'cursor-pointer transition-colors hover:bg-bg-card-warm-hover',
|
||||
'max-md:grid-cols-[auto_minmax(0,1fr)] max-md:gap-x-3',
|
||||
'md:grid-cols-[3.75rem_6.25rem_minmax(0,1fr)] md:gap-x-4',
|
||||
])>
|
||||
<div class="flex min-w-0 shrink-0 flex-col gap-0.5 tabular-nums pt-0.5">
|
||||
@if ($time !== '')
|
||||
<span class="font-mono text-[11px] leading-none text-card-warm-cat sm:text-[12px]">{{ $time }}</span>
|
||||
@endif
|
||||
@if ($date !== '')
|
||||
<span class="text-[9px] leading-tight text-ink-4 sm:text-[10px]">{{ $date }}</span>
|
||||
@endif
|
||||
<span class="{{ $catClass }} md:hidden">{{ $category }}</span>
|
||||
</div>
|
||||
<span class="{{ $catClass }} hidden md:inline">{{ $category }}</span>
|
||||
<div class="min-w-0">
|
||||
<div class="font-serif text-[15.5px] font-medium leading-[1.3] text-card-warm-title">
|
||||
{{ $title }}
|
||||
</div>
|
||||
@if ($company)
|
||||
<div class="mt-1 text-[11px] text-card-warm-cat">{{ $company }}</div>
|
||||
@endif
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="flex items-center gap-2.5 px-4">
|
||||
<span class="h-px flex-1 bg-bg-card-warm-rule"></span>
|
||||
<span class="text-[9px] font-bold uppercase tracking-[0.22em] text-ink-on-dark-muted">Ende Anzeige</span>
|
||||
<span class="h-px flex-1 bg-bg-card-warm-rule"></span>
|
||||
</div>
|
||||
</div>
|
||||
93
resources/views/components/web/feed-item.blade.php
Normal file
93
resources/views/components/web/feed-item.blade.php
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
@props([
|
||||
'release' => null,
|
||||
'mock' => null,
|
||||
'recommended' => false,
|
||||
])
|
||||
|
||||
@php
|
||||
\Carbon\Carbon::setLocale('de');
|
||||
|
||||
if ($release) {
|
||||
$category = $release?->category?->translations?->firstWhere('locale', 'de')
|
||||
?? $release?->category?->translations?->first();
|
||||
$categoryName = $category?->name ?? 'Wirtschaft';
|
||||
$company = $release?->company?->name ?? 'Unternehmen';
|
||||
$city = $release?->company?->city ?? null;
|
||||
$title = $release->title;
|
||||
$href = route('release.detail', ['slug' => $release->slug]);
|
||||
$recommended = $recommended;
|
||||
|
||||
$published = $release->published_at;
|
||||
if ($published) {
|
||||
$timeLine = $published->format('H:i');
|
||||
if ($published->isToday()) {
|
||||
$dateLine = $published->translatedFormat('j. MMM');
|
||||
} elseif ($published->isYesterday()) {
|
||||
$dateLine = 'Gestern';
|
||||
} else {
|
||||
$dateLine = $published->translatedFormat('j. MMM');
|
||||
}
|
||||
$datetimeAttr = $published->toIso8601String();
|
||||
} else {
|
||||
$timeLine = '—';
|
||||
$dateLine = '';
|
||||
$datetimeAttr = null;
|
||||
}
|
||||
} else {
|
||||
$mock ??= [];
|
||||
$categoryName = $mock['category'] ?? 'Wirtschaft';
|
||||
$company = $mock['company'] ?? '';
|
||||
$city = $mock['city'] ?? null;
|
||||
$title = $mock['title'] ?? '';
|
||||
$href = $mock['href'] ?? '#';
|
||||
$timeLine = $mock['time'] ?? '';
|
||||
$dateLine = $mock['date'] ?? '';
|
||||
$datetimeAttr = null;
|
||||
$recommended = $recommended || ($mock['recommended'] ?? false);
|
||||
}
|
||||
|
||||
$bpCatClass = 'bp-cat self-start whitespace-nowrap leading-snug max-md:mt-1 max-md:text-[9px] max-md:tracking-[0.1em]';
|
||||
@endphp
|
||||
|
||||
<a href="{{ $href }}" @class([
|
||||
'grid items-start py-3.5 border-b border-bg-rule cursor-pointer',
|
||||
'hover:bg-bg-elev transition-colors group',
|
||||
'pl-1.5 pr-2 sm:pl-2',
|
||||
// <md: 2 Spalten — links Zeit/Datum/Rubrik, rechts Inhalt
|
||||
'max-md:grid-cols-[auto_minmax(0,1fr)] max-md:gap-x-3',
|
||||
// md+: 3 Spalten wie Desktop-Mockup
|
||||
'md:grid-cols-[3.75rem_6.25rem_minmax(0,1fr)] md:gap-x-4',
|
||||
])>
|
||||
<div class="flex min-w-0 shrink-0 flex-col gap-0.5 tabular-nums pt-0.5">
|
||||
@if ($datetimeAttr)
|
||||
<time datetime="{{ $datetimeAttr }}" class="font-mono text-[11px] leading-none text-ink-3 sm:text-[12px]">{{ $timeLine }}</time>
|
||||
@else
|
||||
<span class="font-mono text-[11px] leading-none text-ink-3 sm:text-[12px]">{{ $timeLine }}</span>
|
||||
@endif
|
||||
@if ($dateLine !== '')
|
||||
<span class="text-[9px] leading-tight text-ink-4 sm:text-[10px]">{{ $dateLine }}</span>
|
||||
@endif
|
||||
{{-- Mobil: Rubrik unter dem Datum in derselben Spalte --}}
|
||||
<span class="{{ $bpCatClass }} md:hidden">{{ $categoryName }}</span>
|
||||
</div>
|
||||
{{-- Desktop: Rubrik in eigener Spalte -- span nur sichtbar ab md, um Tab-Reihenfolge / SR konsistent zu halten --}}
|
||||
<span class="{{ $bpCatClass }} hidden md:inline">{{ $categoryName }}</span>
|
||||
<div class="min-w-0">
|
||||
<div class="font-serif text-[15.5px] leading-[1.3] font-medium text-ink transition-colors group-hover:text-brand flex flex-wrap items-baseline gap-2">
|
||||
<span>{{ $title }}</span>
|
||||
@if ($recommended)
|
||||
<span class="inline-flex items-center gap-1 whitespace-nowrap border border-brand/30 bg-brand/[0.04] px-1.5 py-px font-mono text-[9.5px] font-bold uppercase tracking-[0.1em] text-brand">
|
||||
<svg width="9" height="9" viewBox="0 0 12 12" class="flex-shrink-0" aria-hidden="true">
|
||||
<path d="M6 1l1.5 3.2 3.5.4-2.6 2.4.7 3.5L6 8.8l-3.1 1.7.7-3.5L1 4.6l3.5-.4z" fill="currentColor" />
|
||||
</svg>
|
||||
Empfehlung
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
@if ($company)
|
||||
<div class="mt-1 text-[11px] text-ink-3">
|
||||
{{ $company }}@if ($city) · {{ $city }} @endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</a>
|
||||
92
resources/views/components/web/feed-top-item.blade.php
Normal file
92
resources/views/components/web/feed-top-item.blade.php
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
@props([
|
||||
'release' => null,
|
||||
'mock' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
\Carbon\Carbon::setLocale('de');
|
||||
|
||||
if ($release) {
|
||||
$category = $release?->category?->translations?->firstWhere('locale', 'de')
|
||||
?? $release?->category?->translations?->first();
|
||||
$categoryName = $category?->name ?? 'Wirtschaft';
|
||||
$image = $release?->images?->first();
|
||||
$imageUrl = $image?->variantUrl('card') ?? $image?->url();
|
||||
$teaser = (string) \Illuminate\Support\Str::of(strip_tags((string) ($release?->text ?? '')))->squish()->limit(180);
|
||||
$company = $release?->company?->name ?? 'Unternehmen';
|
||||
$title = $release->title;
|
||||
$href = route('release.detail', ['slug' => $release->slug]);
|
||||
$time = $release?->published_at?->isToday()
|
||||
? $release->published_at->format('H:i')
|
||||
: ($release?->published_at?->isYesterday() ? 'Gestern' : $release?->published_at?->isoFormat('D. MMM'));
|
||||
$city = $release?->company?->city ?? null;
|
||||
$readTime = max(1, (int) round(str_word_count(strip_tags((string) ($release?->text ?? ''))) / 200));
|
||||
} else {
|
||||
$mock ??= [];
|
||||
$categoryName = $mock['category'] ?? 'Bauen · Immobilien';
|
||||
$imageUrl = $mock['image'] ?? null;
|
||||
$teaser = $mock['teaser'] ?? 'Nach 18 Monaten der Konsolidierung deuten Frühindikatoren auf eine Erholung hin. ImmoConsult-Studie analysiert 240 Märkte in DACH.';
|
||||
$company = $mock['company'] ?? 'ImmoConsult Deutschland';
|
||||
$title = $mock['title'] ?? 'Immobilienmarkt 2025: Experten prognostizieren Trendwende bei Kaufpreisen';
|
||||
$href = $mock['href'] ?? '#';
|
||||
$time = $mock['time'] ?? '14:18';
|
||||
$city = $mock['city'] ?? 'München';
|
||||
$readTime = $mock['readTime'] ?? 5;
|
||||
}
|
||||
@endphp
|
||||
|
||||
<article class="py-6 border-b border-bg-rule grid gap-6 grid-cols-1 md:grid-cols-[240px_1fr] group">
|
||||
<a href="{{ $href }}" class="relative overflow-hidden bg-feature-grad block cursor-pointer" style="width:100%; max-width:240px; height:160px;">
|
||||
@if ($imageUrl)
|
||||
<img src="{{ $imageUrl }}" alt="{{ $title }}" class="absolute inset-0 h-full w-full object-cover transition-transform group-hover:scale-105" loading="lazy">
|
||||
@else
|
||||
<svg width="100%" height="100%" viewBox="0 0 240 160" class="absolute inset-0" aria-hidden="true">
|
||||
<g opacity="0.5">
|
||||
<line x1="40" y1="55" x2="40" y2="150" stroke="var(--color-feature-line)" stroke-width="1.8" />
|
||||
<circle cx="40" cy="55" r="3" fill="var(--color-feature-dot)" />
|
||||
</g>
|
||||
<g opacity="0.65">
|
||||
<line x1="100" y1="55" x2="100" y2="150" stroke="var(--color-feature-line)" stroke-width="1.8" />
|
||||
<circle cx="100" cy="55" r="3" fill="var(--color-feature-dot)" />
|
||||
</g>
|
||||
<g opacity="0.8">
|
||||
<line x1="160" y1="55" x2="160" y2="150" stroke="var(--color-feature-line)" stroke-width="1.8" />
|
||||
<circle cx="160" cy="55" r="3" fill="var(--color-feature-dot)" />
|
||||
</g>
|
||||
<g opacity="0.95">
|
||||
<line x1="210" y1="55" x2="210" y2="150" stroke="var(--color-feature-line)" stroke-width="1.8" />
|
||||
<circle cx="210" cy="55" r="3" fill="var(--color-feature-dot)" />
|
||||
</g>
|
||||
</svg>
|
||||
@endif
|
||||
<span class="bp-tag absolute top-2.5 left-2.5">Top-Meldung</span>
|
||||
</a>
|
||||
|
||||
<div>
|
||||
<div class="flex justify-between items-baseline mb-2 flex-wrap gap-2">
|
||||
<span class="bp-cat">{{ $categoryName }}</span>
|
||||
<span class="font-mono text-[11.5px] text-ink-3">{{ $time }}</span>
|
||||
</div>
|
||||
<h3 class="font-serif text-[24px] leading-[1.2] m-0 font-semibold tracking-[-0.3px] text-ink">
|
||||
<a href="{{ $href }}" class="cursor-pointer hover:text-brand transition-colors">{{ $title }}</a>
|
||||
</h3>
|
||||
@if ($teaser)
|
||||
<p class="font-serif text-[14px] text-ink-2 leading-[1.5] mt-2.5">
|
||||
{{ $teaser }}
|
||||
</p>
|
||||
@endif
|
||||
<div class="mt-3 text-[11.5px] text-ink-3 flex items-center gap-1.5 flex-wrap">
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" class="text-ok flex-shrink-0" aria-hidden="true">
|
||||
<circle cx="5.5" cy="5.5" r="5" fill="currentColor" />
|
||||
<path d="M3 5.5l1.8 1.8L8 4" stroke="white" stroke-width="1.4" fill="none" stroke-linecap="round" />
|
||||
</svg>
|
||||
<span>{{ $company }}</span>
|
||||
@if ($city)
|
||||
<span aria-hidden="true">·</span>
|
||||
<span>{{ $city }}</span>
|
||||
@endif
|
||||
<span aria-hidden="true">·</span>
|
||||
<span>{{ $readTime }} Min. Lesezeit</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
50
resources/views/components/web/focus-ad.blade.php
Normal file
50
resources/views/components/web/focus-ad.blade.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
@props(['ad' => null])
|
||||
|
||||
@php
|
||||
$ad ??= [
|
||||
'category' => 'Logistik & Transport',
|
||||
'title' => 'DHL Express erweitert Same-Day-Netz auf 24 deutsche Großstädte',
|
||||
'company' => 'Deutsche Post DHL Group',
|
||||
'gradient' => 'linear-gradient(135deg,#3A2818,#A88466)',
|
||||
'href' => '#',
|
||||
];
|
||||
|
||||
$category = $ad['category'] ?? 'Anzeige';
|
||||
$title = $ad['title'] ?? 'Werbeplatz';
|
||||
$company = $ad['company'] ?? '';
|
||||
$gradient = $ad['gradient'] ?? 'linear-gradient(135deg,#3A2818,#A88466)';
|
||||
$href = $ad['href'] ?? '#';
|
||||
@endphp
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="flex items-center gap-2.5 mb-2.5">
|
||||
<span class="h-px bg-bg-rule-strong flex-1"></span>
|
||||
<span class="text-[9px] font-bold tracking-[0.22em] text-ink-on-dark-muted uppercase">Anzeige</span>
|
||||
<span class="h-px bg-bg-rule-strong flex-1"></span>
|
||||
</div>
|
||||
|
||||
<a href="{{ $href }}" rel="sponsored nofollow" class="block px-3.5 py-4 bg-bg-card-warm border border-bg-card-warm-border cursor-pointer hover:border-brand/50 hover:bg-bg-card-warm-hover transition-colors">
|
||||
<div class="grid items-start gap-3" style="grid-template-columns: 64px 1fr;">
|
||||
<div class="w-16 h-16 flex-shrink-0 relative overflow-hidden opacity-85" style="background: {{ $gradient }};"></div>
|
||||
<div>
|
||||
<div class="text-[9.5px] font-bold tracking-[0.16em] text-card-warm-cat uppercase mb-1">
|
||||
{{ $category }}
|
||||
</div>
|
||||
<h3 class="font-serif text-[15.5px] leading-[1.3] m-0 font-medium text-card-warm-title">
|
||||
{{ $title }}
|
||||
</h3>
|
||||
@if ($company)
|
||||
<div class="text-[11.5px] mt-1.5 text-card-warm-cat">
|
||||
{{ $company }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="flex items-center gap-2.5 mt-2.5">
|
||||
<span class="h-px bg-bg-rule-strong flex-1"></span>
|
||||
<span class="text-[9px] font-bold tracking-[0.22em] text-ink-on-dark-muted uppercase">Ende Anzeige</span>
|
||||
<span class="h-px bg-bg-rule-strong flex-1"></span>
|
||||
</div>
|
||||
</div>
|
||||
129
resources/views/components/web/focus-hero.blade.php
Normal file
129
resources/views/components/web/focus-hero.blade.php
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
@props([
|
||||
'leadRelease' => null,
|
||||
'sideReleases' => [],
|
||||
'advertisement' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
\Carbon\Carbon::setLocale('de');
|
||||
|
||||
$leadCategory = $leadRelease?->category?->translations?->firstWhere('locale', 'de')
|
||||
?? $leadRelease?->category?->translations?->first();
|
||||
$leadCategoryName = $leadCategory?->name ?? 'KI & Innovation';
|
||||
$leadImage = $leadRelease?->images?->first();
|
||||
$leadImageUrl = $leadImage?->variantUrl('card') ?? $leadImage?->url();
|
||||
$leadTitle = $leadRelease?->title ?? 'Die Zukunft der KI im deutschen Mittelstand';
|
||||
$leadTeaser = $leadRelease
|
||||
? (string) \Illuminate\Support\Str::of(strip_tags((string) $leadRelease->text))->squish()->limit(200)
|
||||
: 'Im exklusiven Interview spricht Dr. Maria Schmidt, Leiterin des KI-Instituts München, über Chancen und Herausforderungen der KI für mittelständische Unternehmen.';
|
||||
$leadCompany = $leadRelease?->company?->name ?? 'KI-Institut München';
|
||||
$leadHref = $leadRelease ? route('release.detail', ['slug' => $leadRelease->slug]) : '#';
|
||||
$leadReadTime = max(1, (int) round(str_word_count(strip_tags((string) ($leadRelease?->text ?? ''))) / 200)) ?: 5;
|
||||
$leadPublishedAt = $leadRelease?->published_at;
|
||||
|
||||
$sideReleases = collect($sideReleases)->take(4)->values();
|
||||
@endphp
|
||||
|
||||
<section class="max-w-layout mx-auto px-8 pt-8">
|
||||
<header class="flex items-baseline justify-between mb-4 flex-wrap gap-2">
|
||||
<h2 class="font-serif text-[28px] font-semibold m-0 tracking-[-0.3px] text-ink">
|
||||
Im Fokus
|
||||
<span class="text-[16px] text-ink-3 font-normal ml-2">· 🇩🇪 Deutschland</span>
|
||||
</h2>
|
||||
<div class="eyebrow muted">{{ ucfirst(now()->isoFormat('dddd, HH:mm')) }} Uhr · Auswahl der Redaktion</div>
|
||||
</header>
|
||||
<hr class="rule-strong mb-6">
|
||||
|
||||
<div class="grid gap-9 lg:grid-cols-[1.7fr_1fr] grid-cols-1">
|
||||
{{-- HERO --}}
|
||||
<article>
|
||||
<div class="relative">
|
||||
<a href="{{ $leadHref }}" class="block group cursor-pointer">
|
||||
<div class="relative overflow-hidden bg-hero-grad h-[500px]">
|
||||
@if ($leadImageUrl)
|
||||
<img src="{{ $leadImageUrl }}" alt="{{ $leadTitle }}"
|
||||
class="absolute inset-0 h-full w-full object-cover opacity-90 transition-opacity group-hover:opacity-100"
|
||||
loading="eager">
|
||||
<div class="absolute inset-0 bg-black/10"></div>
|
||||
@else
|
||||
<svg width="100%" height="100%" viewBox="0 0 760 500" class="absolute inset-0 opacity-40" aria-hidden="true">
|
||||
<defs>
|
||||
<pattern id="bp-hero-dots" x="0" y="0" width="40" height="40" patternUnits="userSpaceOnUse">
|
||||
<circle cx="20" cy="20" r="1.5" fill="white" opacity="0.3" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="760" height="500" fill="url(#bp-hero-dots)" />
|
||||
<circle cx="600" cy="180" r="120" fill="white" opacity="0.05" />
|
||||
<circle cx="160" cy="380" r="80" fill="white" opacity="0.06" />
|
||||
</svg>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="absolute top-6 left-6 flex gap-2">
|
||||
<span class="bp-tag">TOP-MELDUNG</span>
|
||||
<span class="bp-tag" style="background:rgba(255,255,255,0.15);border:1px solid rgba(255,255,255,0.3);color:white;backdrop-filter:blur(4px);">
|
||||
Audio · {{ $leadReadTime * 4 }} min
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="absolute left-0 right-0 bottom-0 px-9 pb-8 pt-[60px] text-white"
|
||||
style="background:linear-gradient(180deg,transparent,rgba(0,0,0,0.92));">
|
||||
<div class="eyebrow on-dark mb-3">{{ $leadCategoryName }}</div>
|
||||
<h1 class="font-serif text-[38px] font-semibold m-0 leading-[1.1] tracking-[-0.7px] max-w-[680px]">
|
||||
{{ $leadTitle }}
|
||||
</h1>
|
||||
<p class="font-serif text-[15px] leading-[1.5] mt-3.5 max-w-[580px] text-white/85">
|
||||
{{ $leadTeaser }}
|
||||
</p>
|
||||
<div class="mt-4 flex items-center gap-3.5 text-[12px] text-white/65 flex-wrap">
|
||||
<span>{{ $leadCompany }}</span>
|
||||
<span aria-hidden="true">·</span>
|
||||
@if ($leadPublishedAt)
|
||||
<time datetime="{{ $leadPublishedAt->toIso8601String() }}" class="font-mono">
|
||||
{{ $leadPublishedAt->format('d.m.Y · H:i') }}
|
||||
</time>
|
||||
<span aria-hidden="true">·</span>
|
||||
@endif
|
||||
<span>{{ $leadReadTime }} Min. Lesezeit</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between pt-3.5 px-1 gap-3">
|
||||
<div class="flex gap-2 items-center" aria-label="Top-Meldung Paginierung">
|
||||
<span class="block h-[3px] w-9 bg-bg-rule-strong relative overflow-hidden">
|
||||
<span class="absolute inset-y-0 left-0 bg-brand" style="width:35%;"></span>
|
||||
</span>
|
||||
<span class="block h-[3px] w-[18px] bg-bg-rule-strong"></span>
|
||||
<span class="block h-[3px] w-[18px] bg-bg-rule-strong"></span>
|
||||
</div>
|
||||
<div class="text-[12px] text-ink-3">
|
||||
<span class="text-ink font-semibold">1</span> von 3 Top-Meldungen
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
{{-- SIDEBAR: Was sonst zählt --}}
|
||||
<aside>
|
||||
<div class="eyebrow muted mb-3.5">Was sonst zählt</div>
|
||||
|
||||
@if ($sideReleases->isNotEmpty())
|
||||
@foreach ($sideReleases as $index => $release)
|
||||
<x-web.focus-side-item :release="$release" :index="$index + 1" :first="$loop->first" />
|
||||
@endforeach
|
||||
@else
|
||||
@foreach ([
|
||||
['n' => '01', 'cat' => 'Energie & Umwelt', 'time' => '10:42', 'title' => 'Energiewende beschleunigt sich: Neue Rekorde bei Erneuerbaren', 'company' => 'GreenPower Deutschland', 'gradient' => 'linear-gradient(135deg,#3A5A3A,#7BA876)'],
|
||||
['n' => '02', 'cat' => 'Finanzen', 'time' => '08:15', 'title' => 'FinTech-Startup PaymentFlow sichert sich 45 Mio. Euro', 'company' => 'PaymentFlow GmbH', 'gradient' => 'linear-gradient(135deg,#2A3548,#6B7C95)'],
|
||||
['n' => '03', 'cat' => 'Industrie', 'time' => '07:00', 'title' => 'Deutsche Unternehmen investieren 15 Mrd. in Automatisierung', 'company' => 'TechVision Analytics', 'gradient' => 'linear-gradient(135deg,#3A2A22,#9B6B52)'],
|
||||
['n' => '04', 'cat' => 'Mobilität', 'time' => 'Gestern', 'title' => 'VW eröffnet Batteriewerk in Salzgitter — 4.000 Arbeitsplätze', 'company' => 'Volkswagen AG', 'gradient' => 'linear-gradient(135deg,#1F2A3A,#5B7A99)'],
|
||||
] as $idx => $mock)
|
||||
<x-web.focus-side-item :mock="$mock" :first="$idx === 0" />
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
<x-web.focus-ad :ad="$advertisement" />
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
72
resources/views/components/web/focus-side-item.blade.php
Normal file
72
resources/views/components/web/focus-side-item.blade.php
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
@props([
|
||||
'release' => null,
|
||||
'mock' => null,
|
||||
'index' => 1,
|
||||
'first' => false,
|
||||
])
|
||||
|
||||
@php
|
||||
if ($release) {
|
||||
$category = $release?->category?->translations?->firstWhere('locale', 'de')
|
||||
?? $release?->category?->translations?->first();
|
||||
$categoryName = $category?->name ?? 'Wirtschaft';
|
||||
$time = $release?->published_at?->isToday()
|
||||
? $release->published_at->format('H:i')
|
||||
: ($release?->published_at?->isYesterday() ? 'Gestern' : $release?->published_at?->isoFormat('D. MMM'));
|
||||
$title = $release->title;
|
||||
$company = $release?->company?->name ?? 'Unternehmen';
|
||||
$href = route('release.detail', ['slug' => $release->slug]);
|
||||
$image = $release?->images?->first();
|
||||
$imageUrl = $image?->variantUrl('card') ?? $image?->url();
|
||||
$gradient = 'linear-gradient(135deg,#3A4A5A,#6B7C95)';
|
||||
$number = str_pad((string) $index, 2, '0', STR_PAD_LEFT);
|
||||
} else {
|
||||
$categoryName = $mock['cat'];
|
||||
$time = $mock['time'];
|
||||
$title = $mock['title'];
|
||||
$company = $mock['company'];
|
||||
$href = '#';
|
||||
$imageUrl = null;
|
||||
$gradient = $mock['gradient'] ?? 'linear-gradient(135deg,#3A4A5A,#6B7C95)';
|
||||
$number = $mock['n'];
|
||||
}
|
||||
@endphp
|
||||
|
||||
<article class="px-3.5 py-4 border-b border-bg-rule cursor-pointer transition-colors hover:bg-bg-elev group {{ $first ? 'border-t border-bg-rule-strong' : '' }}">
|
||||
<a href="{{ $href }}" class="block cursor-pointer">
|
||||
<div class="grid items-baseline gap-3 mb-2.5" style="grid-template-columns: 32px 1fr auto;">
|
||||
<div class="font-serif text-[18px] text-brand font-medium leading-none tabular-nums">{{ $number }}</div>
|
||||
<span class="bp-cat">{{ $categoryName }}</span>
|
||||
<span class="font-mono text-[11px] text-ink-3">{{ $time }}</span>
|
||||
</div>
|
||||
|
||||
<div class="grid items-start gap-3" style="grid-template-columns: 32px 64px 1fr;">
|
||||
<div></div>
|
||||
|
||||
<div class="w-16 h-16 flex-shrink-0 relative overflow-hidden" style="background: {{ $gradient }};">
|
||||
@if ($imageUrl)
|
||||
<img src="{{ $imageUrl }}" alt="" class="absolute inset-0 h-full w-full object-cover transition-transform group-hover:scale-105" loading="lazy">
|
||||
@else
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" class="absolute inset-0" aria-hidden="true">
|
||||
<circle cx="48" cy="20" r="18" fill="rgba(255,255,255,0.08)" />
|
||||
<rect x="6" y="40" width="36" height="2" fill="rgba(255,255,255,0.18)" />
|
||||
<rect x="6" y="46" width="24" height="2" fill="rgba(255,255,255,0.12)" />
|
||||
</svg>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-serif text-[15.5px] leading-[1.3] m-0 font-medium tracking-[-0.1px] text-ink group-hover:text-brand transition-colors">
|
||||
{{ $title }}
|
||||
</h3>
|
||||
<div class="text-[11.5px] text-ink-3 mt-1.5 flex items-center gap-1.5">
|
||||
<svg width="10" height="10" viewBox="0 0 11 11" class="text-ok flex-shrink-0" aria-hidden="true">
|
||||
<circle cx="5.5" cy="5.5" r="5" fill="currentColor" />
|
||||
<path d="M3 5.5l1.8 1.8L8 4" stroke="white" stroke-width="1.4" fill="none" stroke-linecap="round" />
|
||||
</svg>
|
||||
<span>{{ $company }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
30
resources/views/components/web/hero-banner.blade.php
Normal file
30
resources/views/components/web/hero-banner.blade.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
@props(['title', 'subtitle', 'theme' => 'businessportal24'])
|
||||
|
||||
<section
|
||||
class="hero-gradient relative overflow-hidden text-white py-6 animate-fade-in border-b border-zinc-100 dark:border-zinc-900">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="mx-auto">
|
||||
<h1
|
||||
class="hero-title text-2xl md:text-3xl lg:text-4xl text-white font-bold mb-4 animate-fade-in-up text-center">
|
||||
{!! $title !!}
|
||||
</h1>
|
||||
<p class="hero-subtitle text-base md:text-lg text-white animate-fade-in-up animation-delay-200 text-center">
|
||||
{{ $subtitle }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($theme === 'businessportal24')
|
||||
<!-- Decorative Pattern -->
|
||||
<div class="absolute top-0 right-0 opacity-10">
|
||||
<svg width="404" height="384" fill="none" viewBox="0 0 404 384">
|
||||
<defs>
|
||||
<pattern id="pattern-hero" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
|
||||
<rect x="0" y="0" width="4" height="4" fill="currentColor" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="404" height="384" fill="url(#pattern-hero)" />
|
||||
</svg>
|
||||
</div>
|
||||
@endif
|
||||
</section>
|
||||
88
resources/views/components/web/highlight-card.blade.php
Normal file
88
resources/views/components/web/highlight-card.blade.php
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
@props(['highlight'])
|
||||
|
||||
@php
|
||||
// Map industry to Heroicon
|
||||
$industryIcons = [
|
||||
'IT & Digitalisierung' => 'cpu-chip',
|
||||
'KI & Innovation' => 'cpu-chip',
|
||||
'Industrie & Technik' => 'cog-6-tooth',
|
||||
'Finanzen & Versicherungen' => 'currency-dollar',
|
||||
'Handel & E-Commerce' => 'shopping-cart',
|
||||
'Bauen & Immobilien' => 'building-office',
|
||||
'Mobilität & Logistik' => 'truck',
|
||||
'Energie & Umwelt' => 'bolt',
|
||||
'Medizin & Gesundheit' => 'heart',
|
||||
'Personal & HR' => 'user-group',
|
||||
'Marketing, PR & Medien' => 'megaphone',
|
||||
'Recht & Steuern' => 'scale',
|
||||
'Politik, Verbände & NGOs' => 'flag',
|
||||
'Wissenschaft & Forschung' => 'beaker',
|
||||
'Lifestyle' => 'sparkles',
|
||||
'Tourismus & Kultur' => 'globe-alt',
|
||||
];
|
||||
|
||||
$iconName = $industryIcons[$highlight['industry']] ?? 'building-office';
|
||||
$iconPath = "/heroicons/optimized/24/outline/{$iconName}.svg";
|
||||
@endphp
|
||||
|
||||
<a href="#" class="highlight-card-link">
|
||||
<article class="highlight-card">
|
||||
<div class="grid md:grid-cols-2">
|
||||
<!-- Image -->
|
||||
<div class="highlight-card-image">
|
||||
<img src="{{ $highlight['image'] }}" alt="{{ $highlight['title'] }}"
|
||||
class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105">
|
||||
<div class="absolute top-4 left-4">
|
||||
<span class="highlight-badge badge badge-{{ $highlight['badgeType'] }}">
|
||||
{{ $highlight['badge'] }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="highlight-card-content">
|
||||
<div>
|
||||
{{-- Meta Info --}}
|
||||
<div class="highlight-meta">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="industry-icon-badge">
|
||||
<img src="{{ $iconPath }}" alt="{{ $highlight['industry'] }}" class="h-4 w-4" />
|
||||
</div>
|
||||
<span
|
||||
class="font-medium text-zinc-700 dark:text-zinc-300">{{ $highlight['industry'] }}</span>
|
||||
</div>
|
||||
<time datetime="{{ $highlight['date'] }}"
|
||||
class="text-zinc-600 dark:text-zinc-400">{{ $highlight['date'] }}</time>
|
||||
</div>
|
||||
|
||||
{{-- Title --}}
|
||||
<h3 class="highlight-title">
|
||||
{{ $highlight['title'] }}
|
||||
</h3>
|
||||
|
||||
{{-- Teaser --}}
|
||||
<p class="highlight-text">
|
||||
{{ $highlight['text'] }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{{-- Footer --}}
|
||||
<div class="highlight-footer">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="h-4 w-4 text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4">
|
||||
</path>
|
||||
</svg>
|
||||
<span
|
||||
class="text-sm font-medium text-zinc-700 dark:text-zinc-300">{{ $highlight['author'] }}</span>
|
||||
</div>
|
||||
<svg class="h-5 w-5 transition-transform group-hover:translate-x-1" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
129
resources/views/components/web/highlights-slider.blade.php
Normal file
129
resources/views/components/web/highlights-slider.blade.php
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
@props([
|
||||
'highlights' => [],
|
||||
'theme' => 'businessportal24',
|
||||
])
|
||||
|
||||
@php
|
||||
$sectionBgClass = match ($theme) {
|
||||
'presseecho' => 'bg-gradient-to-br from-zinc-50 to-zinc-100 dark:from-zinc-800 dark:to-zinc-900',
|
||||
'businessportal24' => 'main-content-section',
|
||||
default => 'bg-white dark:bg-zinc-950',
|
||||
};
|
||||
@endphp
|
||||
|
||||
<section class="relative overflow-hidden {{ $sectionBgClass }} py-12 md:py-16 animate-fade-in">
|
||||
<div class="container mx-auto px-4">
|
||||
<!-- Section Header -->
|
||||
<div class="mb-8 flex items-end justify-between">
|
||||
<div>
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-zinc-900 dark:text-zinc-100 mb-2 flex items-center gap-3">
|
||||
<span class="gradient-indicator"></span>
|
||||
Highlights der Woche
|
||||
</h2>
|
||||
<p class="text-zinc-600 dark:text-zinc-400 ml-5">Exklusive Einblicke und Top-Analysen</p>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Buttons -->
|
||||
<div class="hidden md:flex items-center gap-2">
|
||||
<button id="highlights-prev" class="slider-nav-btn" aria-label="Vorheriges Highlight">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="highlights-next" class="slider-nav-btn" aria-label="Nächstes Highlight">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Slider Container -->
|
||||
<div class="slider-wrapper">
|
||||
<div id="highlights-slider" class="highlights-slider">
|
||||
@foreach ($highlights as $highlight)
|
||||
<x-web.highlight-card :highlight="$highlight" />
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<!-- Pagination Dots -->
|
||||
<div class="slider-dots">
|
||||
@foreach ($highlights as $index => $highlight)
|
||||
<button data-slide="{{ $index }}" class="slider-dot {{ $index === 0 ? 'active' : '' }}"
|
||||
aria-label="Zu Slide {{ $index + 1 }}"></button>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const slider = document.getElementById('highlights-slider');
|
||||
const prevBtn = document.getElementById('highlights-prev');
|
||||
const nextBtn = document.getElementById('highlights-next');
|
||||
const dots = document.querySelectorAll('.slider-dot');
|
||||
|
||||
if (!slider || !prevBtn || !nextBtn) return;
|
||||
|
||||
let currentSlide = 0;
|
||||
const totalSlides = dots.length;
|
||||
|
||||
function updateSlider() {
|
||||
const slideWidth = slider.children[0].offsetWidth;
|
||||
slider.scrollTo({
|
||||
left: currentSlide * (slideWidth + 24), // 24px = gap-6
|
||||
behavior: 'smooth'
|
||||
});
|
||||
updateDots();
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
function updateDots() {
|
||||
dots.forEach((dot, index) => {
|
||||
dot.classList.toggle('active', index === currentSlide);
|
||||
});
|
||||
}
|
||||
|
||||
function updateButtons() {
|
||||
prevBtn.disabled = currentSlide === 0;
|
||||
nextBtn.disabled = currentSlide === totalSlides - 1;
|
||||
}
|
||||
|
||||
prevBtn.addEventListener('click', () => {
|
||||
if (currentSlide > 0) {
|
||||
currentSlide--;
|
||||
updateSlider();
|
||||
}
|
||||
});
|
||||
|
||||
nextBtn.addEventListener('click', () => {
|
||||
if (currentSlide < totalSlides - 1) {
|
||||
currentSlide++;
|
||||
updateSlider();
|
||||
}
|
||||
});
|
||||
|
||||
dots.forEach((dot, index) => {
|
||||
dot.addEventListener('click', () => {
|
||||
currentSlide = index;
|
||||
updateSlider();
|
||||
});
|
||||
});
|
||||
|
||||
// Keyboard navigation
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'ArrowLeft' && currentSlide > 0) {
|
||||
currentSlide--;
|
||||
updateSlider();
|
||||
} else if (e.key === 'ArrowRight' && currentSlide < totalSlides - 1) {
|
||||
currentSlide++;
|
||||
updateSlider();
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
updateButtons();
|
||||
});
|
||||
</script>
|
||||
57
resources/views/components/web/home-hero.blade.php
Normal file
57
resources/views/components/web/home-hero.blade.php
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
@props([
|
||||
'stats' => [],
|
||||
])
|
||||
|
||||
@php
|
||||
$publishedCount = (int) ($stats['publishedCount'] ?? 0);
|
||||
$publishedToday = (int) ($stats['publishedToday'] ?? 0);
|
||||
$archiveSince = $stats['archiveSince'] ?? null;
|
||||
@endphp
|
||||
|
||||
<section class="relative overflow-hidden bg-[#f7f4ef]">
|
||||
<div class="absolute inset-x-0 top-0 h-px bg-black/10 dark:bg-white/10"></div>
|
||||
|
||||
<div class="mx-auto grid max-w-7xl gap-10 px-4 py-12 sm:px-6 md:py-16 lg:grid-cols-[minmax(0,1fr)_24rem] lg:px-8">
|
||||
<div class="max-w-3xl">
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.32em] text-[#b6332a]">Deutschland aktiv</p>
|
||||
<h1 class="mt-5 text-4xl font-semibold tracking-tight text-zinc-950 dark:text-white sm:text-5xl lg:text-6xl">
|
||||
Aktuelle Pressemitteilungen aus der deutschen Wirtschaft
|
||||
</h1>
|
||||
<p class="mt-6 max-w-2xl text-lg leading-8 text-zinc-700 dark:text-zinc-300">
|
||||
Neue Unternehmensmeldungen, Personalien, Produktankündigungen und Wirtschaftsnachrichten aus Mittelstand, Industrie und Dienstleistung.
|
||||
</p>
|
||||
|
||||
<div class="mt-8 flex flex-col gap-3 sm:flex-row">
|
||||
<a href="#aktuell" class="inline-flex items-center justify-center rounded-full bg-[#cf3628] px-5 py-3 text-sm font-semibold text-white transition hover:bg-[#a92c25]">
|
||||
Aktuelle Meldungen lesen
|
||||
</a>
|
||||
<a href="{{ route('veroeffentlichen') }}" class="inline-flex items-center justify-center rounded-full border border-black/15 px-5 py-3 text-sm font-semibold text-zinc-800 transition hover:border-[#cf3628]/50 hover:text-[#cf3628] dark:border-white/15 dark:text-zinc-100">
|
||||
Pressemitteilung veröffentlichen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-black/10 bg-white p-6 shadow-sm dark:border-white/10 dark:bg-zinc-900">
|
||||
<p class="text-sm font-semibold uppercase tracking-[0.24em] text-zinc-500 dark:text-zinc-400">Archiv & Einordnung</p>
|
||||
<dl class="mt-6 grid gap-5">
|
||||
<div class="border-b border-black/10 pb-5 dark:border-white/10">
|
||||
<dt class="text-sm text-zinc-500 dark:text-zinc-400">Veröffentlichte Meldungen</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-zinc-950 dark:text-white">{{ number_format($publishedCount, 0, ',', '.') }}</dd>
|
||||
</div>
|
||||
<div class="border-b border-black/10 pb-5 dark:border-white/10">
|
||||
<dt class="text-sm text-zinc-500 dark:text-zinc-400">Heute neu</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-zinc-950 dark:text-white">{{ number_format($publishedToday, 0, ',', '.') }}</dd>
|
||||
</div>
|
||||
@if ($archiveSince)
|
||||
<div>
|
||||
<dt class="text-sm text-zinc-500 dark:text-zinc-400">Archiv seit</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-zinc-950 dark:text-white">{{ $archiveSince }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
<p class="mt-6 text-sm leading-6 text-zinc-600 dark:text-zinc-300">
|
||||
Die Zahlen stammen aus dem aktuellen Datenbestand. Fehlende Regionaldaten werden nicht ergänzt oder geraten.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
62
resources/views/components/web/industry-index.blade.php
Normal file
62
resources/views/components/web/industry-index.blade.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
@props([
|
||||
'industries' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
$industries = $industries ?? [
|
||||
['name' => 'Technologie', 'count' => 142, 'delta' => 12, 'href' => route('kategorien')],
|
||||
['name' => 'Finanzen', 'count' => 98, 'delta' => 5, 'href' => route('kategorien')],
|
||||
['name' => 'Industrie', 'count' => 87, 'delta' => -2, 'href' => route('kategorien')],
|
||||
['name' => 'Energie', 'count' => 64, 'delta' => 18, 'href' => route('kategorien')],
|
||||
['name' => 'Gesundheit', 'count' => 51, 'delta' => 3, 'href' => route('kategorien')],
|
||||
['name' => 'Mobilität', 'count' => 44, 'delta' => 9, 'href' => route('kategorien')],
|
||||
['name' => 'Handel', 'count' => 38, 'delta' => -1, 'href' => route('kategorien')],
|
||||
['name' => 'Immobilien', 'count' => 32, 'delta' => 4, 'href' => route('kategorien')],
|
||||
];
|
||||
|
||||
$maxCount = max(1, collect($industries)->max('count'));
|
||||
$industries = array_slice($industries, 0, 8);
|
||||
@endphp
|
||||
|
||||
<section class="max-w-layout mx-auto px-8 mt-16">
|
||||
<header class="flex items-baseline justify-between mb-4 flex-wrap gap-3">
|
||||
<h2 class="font-serif text-[28px] font-semibold m-0 tracking-[-0.3px] text-ink">Branchen-Index</h2>
|
||||
<span class="eyebrow muted">Letzte 7 Tage</span>
|
||||
</header>
|
||||
<hr class="rule-strong">
|
||||
|
||||
<div class="grid border-b border-bg-rule grid-cols-2 md:grid-cols-4">
|
||||
@foreach ($industries as $idx => $industry)
|
||||
@php
|
||||
$delta = (int) ($industry['delta'] ?? 0);
|
||||
$direction = $delta > 0 ? 'gain' : ($delta < 0 ? 'loss' : 'flat');
|
||||
$percent = max(20, (int) round(($industry['count'] ?? 0) / $maxCount * 100));
|
||||
$isTopRow = $idx < 4;
|
||||
$isLastInRow = ($idx + 1) % 4 === 0;
|
||||
$barClass = $delta < 0 ? 'bg-brand' : 'bg-gain';
|
||||
@endphp
|
||||
<a href="{{ $industry['href'] ?? route('kategorien') }}"
|
||||
class="block px-5 py-[18px] {{ $isLastInRow ? '' : 'md:border-r border-bg-rule' }} {{ $isTopRow ? '' : 'border-t border-bg-rule' }} cursor-pointer hover:bg-bg-elev transition-colors group">
|
||||
<div class="flex justify-between items-baseline mb-2 gap-2">
|
||||
<span class="text-[14px] font-semibold text-ink group-hover:text-brand transition-colors">{{ $industry['name'] }}</span>
|
||||
<span class="font-mono text-[12px] font-semibold
|
||||
@if ($direction === 'gain') text-gain
|
||||
@elseif ($direction === 'loss') text-loss
|
||||
@else text-ink-3 @endif">
|
||||
@if ($delta > 0) +{{ $delta }}
|
||||
@elseif ($delta < 0) {{ $delta }}
|
||||
@else ±0
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-baseline gap-2 mb-2">
|
||||
<span class="font-serif text-[22px] font-semibold tracking-[-0.3px] text-ink">{{ $industry['count'] }}</span>
|
||||
<span class="text-[11px] text-ink-3">Meldungen</span>
|
||||
</div>
|
||||
<div class="h-[3px] bg-bg-rule">
|
||||
<div class="h-full {{ $barClass }}" style="width: {{ $percent }}%;"></div>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
119
resources/views/components/web/industry-spotlight.blade.php
Normal file
119
resources/views/components/web/industry-spotlight.blade.php
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
@props([
|
||||
'industry' => null,
|
||||
'stats' => null,
|
||||
'releases' => [],
|
||||
'study' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
$industry ??= 'Energie & Klima';
|
||||
$stats ??= [
|
||||
['label' => 'Meldungen heute', 'value' => '47', 'sub' => '+18 ggü. Wochenschnitt'],
|
||||
['label' => 'Investitionsvolumen', 'value' => '€8,4 Mrd.', 'sub' => 'Q4 2025 angekündigt'],
|
||||
['label' => 'Aktive Unternehmen', 'value' => '23', 'sub' => 'in dieser Branche heute'],
|
||||
];
|
||||
|
||||
$releases = collect($releases);
|
||||
if ($releases->isEmpty()) {
|
||||
$releases = collect([
|
||||
['time' => '14:12', 'date' => '12. Mai', 'category' => 'Energie', 'title' => 'RWE startet 1,8 GW Offshore-Windpark vor Helgoland — größtes Projekt in der Nordsee 2025', 'company' => 'RWE AG', 'city' => 'Essen', 'minutes' => 4, 'href' => '#'],
|
||||
['time' => '12:28', 'date' => '12. Mai', 'category' => 'Energie', 'title' => 'EnBW unterzeichnet Wasserstoff-Lieferabkommen mit Air Liquide', 'company' => 'EnBW Energie', 'city' => 'Karlsruhe', 'minutes' => 3, 'href' => '#'],
|
||||
['time' => '10:55', 'date' => '12. Mai', 'category' => 'Klima', 'title' => 'Stadtwerke München erreichen 100 % Ökostrom-Ziel für Privatkunden', 'company' => 'SWM', 'city' => 'München', 'minutes' => 2, 'href' => '#'],
|
||||
]);
|
||||
}
|
||||
|
||||
$study ??= [
|
||||
'kicker' => 'STUDIE · ENERGIE',
|
||||
'title' => 'Energiewende-Monitor 2025: Investitionen, Engpässe, Potenziale im DACH-Raum',
|
||||
'source' => 'Fraunhofer-Institut für Solare Energiesysteme',
|
||||
'meta' => 'PDF · 84 Seiten · Veröffentlicht 11. Nov 2025',
|
||||
'href' => '#',
|
||||
];
|
||||
@endphp
|
||||
|
||||
<section class="max-w-layout mx-auto px-8 mt-16">
|
||||
<header class="flex items-baseline justify-between mb-4 flex-wrap gap-3">
|
||||
<h2 class="font-serif text-[28px] font-semibold m-0 tracking-[-0.3px] text-ink">
|
||||
Heute im Fokus <span class="text-brand">· {{ $industry }}</span>
|
||||
</h2>
|
||||
<div class="eyebrow muted">Aktualisiert {{ now()->format('H:i') }} Uhr · {{ $stats[0]['value'] ?? '47' }} Meldungen heute</div>
|
||||
</header>
|
||||
<hr class="rule-strong">
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 border-b border-bg-rule">
|
||||
@foreach ($stats as $idx => $stat)
|
||||
<div class="px-6 py-5 {{ $idx < count($stats) - 1 ? 'md:border-r border-bg-rule' : '' }}">
|
||||
<div class="eyebrow muted mb-2">{{ $stat['label'] }}</div>
|
||||
<div class="font-serif text-[32px] font-semibold leading-none tracking-[-0.5px] text-ink">{{ $stat['value'] }}</div>
|
||||
<div class="text-[11.5px] text-ink-3 mt-1.5">{{ $stat['sub'] }}</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="grid gap-9 mt-6 grid-cols-1 lg:grid-cols-[1.7fr_1fr]">
|
||||
<div>
|
||||
@foreach ($releases as $release)
|
||||
<a href="{{ $release['href'] }}" @class([
|
||||
'grid items-start border-b border-bg-rule py-4',
|
||||
'cursor-pointer transition-colors group hover:bg-bg-elev',
|
||||
'pl-1.5 pr-2 sm:pl-2',
|
||||
'max-md:grid-cols-[auto_minmax(0,1fr)] max-md:gap-x-3',
|
||||
'md:grid-cols-[3.75rem_6.25rem_minmax(0,1fr)] md:gap-x-4',
|
||||
])>
|
||||
<div class="flex min-w-0 shrink-0 flex-col gap-0.5 tabular-nums pt-0.5">
|
||||
<span class="font-mono text-[11px] leading-none text-ink-3 sm:text-[12px]">{{ $release['time'] }}</span>
|
||||
@if (($release['date'] ?? '') !== '')
|
||||
<span class="text-[9px] leading-tight text-ink-4 sm:text-[10px]">{{ $release['date'] }}</span>
|
||||
@endif
|
||||
<span class="bp-cat self-start whitespace-nowrap leading-snug max-md:mt-1 max-md:text-[9px] max-md:tracking-[0.1em] md:hidden">{{ $release['category'] }}</span>
|
||||
</div>
|
||||
<span class="bp-cat hidden self-start whitespace-nowrap leading-snug md:inline">{{ $release['category'] }}</span>
|
||||
<div class="min-w-0">
|
||||
<div class="font-serif text-[16px] font-medium leading-[1.3] tracking-[-0.1px] text-ink transition-colors group-hover:text-brand">
|
||||
{{ $release['title'] }}
|
||||
</div>
|
||||
<div class="text-[11.5px] text-ink-3 mt-1 flex items-center gap-1.5 flex-wrap">
|
||||
<svg width="10" height="10" viewBox="0 0 11 11" class="text-ok" aria-hidden="true">
|
||||
<circle cx="5.5" cy="5.5" r="5" fill="currentColor" />
|
||||
<path d="M3 5.5l1.8 1.8L8 4" stroke="white" stroke-width="1.4" fill="none" stroke-linecap="round" />
|
||||
</svg>
|
||||
<span>{{ $release['company'] }}</span>
|
||||
@if ($release['city'] ?? null)
|
||||
<span aria-hidden="true">·</span>
|
||||
<span>{{ $release['city'] }}</span>
|
||||
@endif
|
||||
@if ($release['minutes'] ?? null)
|
||||
<span aria-hidden="true">·</span>
|
||||
<span>{{ $release['minutes'] }} Min.</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<aside>
|
||||
<div class="bg-bg-card-warm p-6 border-t-[3px] border-brand">
|
||||
<div class="eyebrow mb-2.5">{{ $study['kicker'] }}</div>
|
||||
<h3 class="font-serif text-[20px] font-semibold m-0 mb-3 tracking-[-0.2px] leading-[1.25] text-ink">
|
||||
{{ $study['title'] }}
|
||||
</h3>
|
||||
<div class="text-[12px] text-ink-3 mb-3.5">
|
||||
{{ $study['source'] }}<br>
|
||||
<span class="font-mono text-[11px]">{{ $study['meta'] }}</span>
|
||||
</div>
|
||||
<a href="{{ $study['href'] }}"
|
||||
class="w-full inline-flex items-center justify-center gap-2 px-[18px] py-2.5 text-[13px] font-semibold bg-transparent text-ink border border-ink rounded-[2px] cursor-pointer hover:bg-ink hover:text-bg transition-colors">
|
||||
↓ Studie herunterladen
|
||||
</a>
|
||||
<div class="mt-2.5 text-[10.5px] leading-[1.45] flex items-center gap-1.5 text-ink-3">
|
||||
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" class="flex-shrink-0 text-ink-4" aria-hidden="true">
|
||||
<rect x="1.5" y="3" width="9" height="6.5" stroke="currentColor" stroke-width="1" />
|
||||
<path d="M1.5 3l4.5 3.5L10.5 3" stroke="currentColor" stroke-width="1" fill="none" />
|
||||
</svg>
|
||||
<span>Kostenlos · Anmeldung per E-Mail erforderlich</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
112
resources/views/components/web/live-ticker.blade.php
Normal file
112
resources/views/components/web/live-ticker.blade.php
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
@props([
|
||||
'items' => [],
|
||||
'marketIndex' => null,
|
||||
'asideSlides' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
$items = collect($items)->all();
|
||||
if (empty($items)) {
|
||||
$items = [
|
||||
['time' => '14:32', 'text' => 'Siemens Energy hebt Jahresprognose an'],
|
||||
['time' => '14:18', 'text' => 'BASF: Restrukturierung in Ludwigshafen abgeschlossen'],
|
||||
['time' => '13:55', 'text' => 'Deutsche Bahn meldet Rekord bei Fernverkehr'],
|
||||
];
|
||||
}
|
||||
|
||||
$marketIndex ??= [
|
||||
'label' => 'DAX',
|
||||
'value' => '18.247',
|
||||
'delta' => '+0,8%',
|
||||
'positive' => true,
|
||||
];
|
||||
|
||||
$asideSlides = collect($asideSlides ?? [
|
||||
$marketIndex,
|
||||
[
|
||||
'label' => 'EUR/USD',
|
||||
'value' => '1,08',
|
||||
'delta' => '+0,2%',
|
||||
'positive' => true,
|
||||
],
|
||||
[
|
||||
'label' => 'Brent Öl',
|
||||
'value' => '78,40 $',
|
||||
'delta' => '-0,9%',
|
||||
'positive' => false,
|
||||
],
|
||||
])->values()->all();
|
||||
@endphp
|
||||
|
||||
<div class="bg-topbar-grad text-ink-on-dark" aria-label="Ad-Hoc-Ticker">
|
||||
<div class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 py-2.5 flex items-center gap-3 sm:gap-5 text-[12.5px]">
|
||||
<div class="flex items-center gap-3 sm:gap-4 overflow-hidden min-w-0 flex-1">
|
||||
<span class="flex items-center gap-1.5 text-accent-warm font-bold text-[10.5px] sm:text-[11px] tracking-[0.15em] flex-shrink-0">
|
||||
<span class="w-[7px] h-[7px] rounded-full bg-accent-warm pulse-dot"></span>
|
||||
AD-HOC
|
||||
</span>
|
||||
|
||||
<span class="sr-only">
|
||||
Ad-hoc-Meldungen:
|
||||
@foreach ($items as $item)
|
||||
{{ $item['time'] }} {{ $item['text'] }}.
|
||||
@endforeach
|
||||
</span>
|
||||
|
||||
<div class="overflow-hidden min-w-0 flex-1" aria-hidden="true">
|
||||
<div class="ticker-marquee-track text-[12px] sm:text-[12.5px] whitespace-nowrap">
|
||||
@foreach (array_merge($items, $items) as $item)
|
||||
<span class="inline-flex shrink-0 gap-2 items-baseline">
|
||||
<span class="font-mono text-[11px] text-ink-on-dark-muted">{{ $item['time'] }}</span>
|
||||
<span class="text-ink-on-dark">{{ $item['text'] }}</span>
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="hidden sm:inline-block w-px h-[18px] bg-ink-on-dark-rule flex-shrink-0"></span>
|
||||
|
||||
{{-- Kein position:absolute: sonst null Breite im Fluss, bis Layout kollabiert --}}
|
||||
<div class="shrink-0 flex justify-end items-center min-w-0 max-w-[min(92vw,280px)] sm:max-w-[min(42vw,280px)]"
|
||||
x-data="{
|
||||
slides: @js($asideSlides),
|
||||
i: 0,
|
||||
init() {
|
||||
if (this.slides.length < 2) {
|
||||
return;
|
||||
}
|
||||
window.setInterval(() => {
|
||||
this.i = (this.i + 1) % this.slides.length;
|
||||
}, 5200);
|
||||
},
|
||||
}"
|
||||
aria-live="polite">
|
||||
<div class="grid min-h-[22px] justify-items-end overflow-hidden [grid-template-columns:minmax(0,max-content)]">
|
||||
<template x-for="(slide, idx) in slides" :key="idx">
|
||||
<div x-show="i === idx"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 -translate-y-full"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-280"
|
||||
x-transition:leave-start="opacity-100 translate-y-0"
|
||||
x-transition:leave-end="opacity-0 translate-y-full"
|
||||
class="col-start-1 row-start-1 flex items-center gap-2 whitespace-nowrap">
|
||||
<span class="text-ink-on-dark-muted font-semibold text-[10.5px] tracking-wider" x-text="slide.label"></span>
|
||||
<span class="text-ink-on-dark font-mono text-[12.5px]" x-text="slide.value"></span>
|
||||
<span class="font-semibold text-[11.5px] font-mono"
|
||||
:class="(slide.positive ?? true) ? 'text-gain' : 'text-loss'"
|
||||
x-text="slide.delta"></span>
|
||||
<svg width="26" height="10" viewBox="0 0 26 10"
|
||||
class="hidden sm:inline-block shrink-0"
|
||||
:class="(slide.positive ?? true) ? 'text-gain' : 'text-loss'"
|
||||
aria-hidden="true">
|
||||
<path d="M0,8 L4,7 L8,5 L12,6 L16,4 L20,3 L24,2"
|
||||
fill="none" stroke="currentColor" stroke-width="1.2" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
39
resources/views/components/web/main-navigation.blade.php
Normal file
39
resources/views/components/web/main-navigation.blade.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
@props([
|
||||
'theme' => 'presseecho',
|
||||
'items' => [],
|
||||
])
|
||||
|
||||
@php
|
||||
$defaultItems = [
|
||||
'presseecho' => [
|
||||
['label' => 'Themendossiers', 'url' => '/themendossiers'],
|
||||
['label' => 'Fachbereiche', 'url' => '/fachbereiche'],
|
||||
['label' => 'Experten', 'url' => '/experten'],
|
||||
],
|
||||
'businessportal24' => [
|
||||
['label' => 'Unternehmen', 'url' => '/unternehmen'],
|
||||
['label' => 'Branchen', 'url' => '/branchen'],
|
||||
['label' => 'Regionen', 'url' => '/regionen'],
|
||||
],
|
||||
];
|
||||
|
||||
$navItems = !empty($items) ? $items : ($defaultItems[$theme] ?? $defaultItems['presseecho']);
|
||||
@endphp
|
||||
|
||||
<nav class="sticky top-0 z-40 bg-white dark:bg-zinc-900 border-b border-zinc-200 dark:border-zinc-800 shadow-sm backdrop-blur-sm transition-colors duration-200">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex items-center justify-center gap-8 h-14">
|
||||
@foreach($navItems as $index => $item)
|
||||
@if($index > 0)
|
||||
<span class="text-zinc-300 dark:text-zinc-700">|</span>
|
||||
@endif
|
||||
<a href="{{ $item['url'] }}"
|
||||
class="main-nav-link text-sm font-medium text-zinc-700 dark:text-zinc-300 hover:text-[var(--color-primary)] dark:hover:text-[var(--color-secondary)] transition-colors relative group py-4">
|
||||
{{ $item['label'] }}
|
||||
<span class="absolute bottom-0 left-0 w-0 h-0.5 bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-secondary)] group-hover:w-full transition-all duration-300"></span>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
48
resources/views/components/web/most-read.blade.php
Normal file
48
resources/views/components/web/most-read.blade.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
@props([
|
||||
'releases' => [],
|
||||
])
|
||||
|
||||
@php
|
||||
$releases = collect($releases)->take(4)->values();
|
||||
|
||||
$fallback = collect([
|
||||
['title' => 'FinTech-Startup PaymentFlow sichert sich 45 Mio. Euro', 'hits' => 12428],
|
||||
['title' => 'Energiewende: Neue Rekorde bei Erneuerbaren', 'hits' => 9831],
|
||||
['title' => 'Immobilienmarkt 2025: Trendwende bei Kaufpreisen', 'hits' => 7104],
|
||||
['title' => 'Telemedizin-Boom: 3 Mio. Online-Sprechstunden', 'hits' => 5298],
|
||||
]);
|
||||
|
||||
$items = $releases->isNotEmpty()
|
||||
? $releases->map(fn ($release) => [
|
||||
'title' => \Illuminate\Support\Str::limit($release->title, 70),
|
||||
'hits' => (int) ($release->hits ?? 0),
|
||||
'href' => route('release.detail', ['slug' => $release->slug]),
|
||||
])
|
||||
: $fallback->map(fn ($mock) => array_merge($mock, ['href' => '#']));
|
||||
|
||||
$maxHits = max(1, $items->max('hits'));
|
||||
@endphp
|
||||
|
||||
<section>
|
||||
<header class="flex items-baseline justify-between mb-4 min-h-[34px]">
|
||||
<h2 class="font-serif text-[28px] font-semibold m-0 tracking-[-0.3px] leading-[1.2] text-ink">Meistgelesen</h2>
|
||||
</header>
|
||||
<hr class="rule-strong mb-1">
|
||||
|
||||
@foreach ($items as $index => $item)
|
||||
@php
|
||||
$percent = max(15, (int) round($item['hits'] / $maxHits * 100));
|
||||
$isLast = $loop->last;
|
||||
@endphp
|
||||
<a href="{{ $item['href'] }}" class="block py-3.5 {{ $isLast ? '' : 'border-b border-bg-rule' }} cursor-pointer hover:bg-bg-elev transition-colors group">
|
||||
<div class="grid gap-2.5 items-baseline mb-1.5" style="grid-template-columns: 32px 1fr auto;">
|
||||
<div class="font-serif text-[18px] text-brand font-semibold leading-none tabular-nums">{{ $index + 1 }}</div>
|
||||
<div class="font-serif text-[14px] leading-[1.3] font-medium text-ink group-hover:text-brand transition-colors">{{ $item['title'] }}</div>
|
||||
<span class="font-mono text-[11px] text-ink-3">{{ number_format((int) $item['hits'], 0, ',', '.') }}</span>
|
||||
</div>
|
||||
<div class="ml-[42px] h-[3px] bg-bg-rule">
|
||||
<div class="h-full bg-brand" style="width: {{ $percent }}%;"></div>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</section>
|
||||
76
resources/views/components/web/newsletter-strip.blade.php
Normal file
76
resources/views/components/web/newsletter-strip.blade.php
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
@props([
|
||||
'topics' => null,
|
||||
'subscribers' => 84000,
|
||||
])
|
||||
|
||||
@php
|
||||
$themeKey = config('app.theme');
|
||||
$topics ??= config('domains.domains.'.$themeKey.'.brand.newsletter_topics', [
|
||||
['name' => 'Tageszusammenfassung', 'meta' => 'Mo–Fr · 07:00 Uhr', 'active' => true],
|
||||
['name' => 'Wochenrückblick', 'meta' => 'Sonntags · 18:00 Uhr', 'active' => false],
|
||||
['name' => 'Energie & Klima', 'meta' => 'ab Content-Score 80 · max. 2× pro Woche', 'active' => true],
|
||||
['name' => 'IPO & M&A', 'meta' => 'ausgewählt von der Redaktion · ca. 3–5 Mails/Monat', 'active' => false],
|
||||
]);
|
||||
@endphp
|
||||
|
||||
<section id="newsletter" class="max-w-layout mx-auto px-8 mt-16">
|
||||
<div class="relative overflow-hidden bg-topbar-grad text-ink-on-dark p-12 px-14">
|
||||
<svg width="280" height="280" viewBox="0 0 280 280" class="absolute -right-10 -top-10 opacity-[0.06]" aria-hidden="true">
|
||||
<circle cx="140" cy="140" r="120" stroke="white" stroke-width="1" fill="none" />
|
||||
<circle cx="140" cy="140" r="80" stroke="white" stroke-width="1" fill="none" />
|
||||
<circle cx="140" cy="140" r="40" stroke="white" stroke-width="1" fill="none" />
|
||||
</svg>
|
||||
|
||||
<div class="relative grid gap-12 grid-cols-1 lg:grid-cols-[1.1fr_1fr]">
|
||||
<div>
|
||||
<div class="eyebrow on-dark mb-3">Bleiben Sie informiert</div>
|
||||
<h2 class="font-serif text-[30px] font-semibold m-0 leading-[1.18] tracking-[-0.4px] mb-3.5 max-w-[460px] text-white">
|
||||
Pressemeldungen, kuratiert in Ihren Posteingang
|
||||
</h2>
|
||||
<p class="text-[14px] leading-[1.55] text-white/70 max-w-[440px] m-0 mb-6">
|
||||
Die wichtigsten Meldungen aus DACH — täglich, wöchentlich oder branchenspezifisch. Keine Werbung, jederzeit kündbar.
|
||||
</p>
|
||||
|
||||
<form action="#newsletter-subscribe" method="post" class="flex mb-3.5 max-w-[460px]">
|
||||
@csrf
|
||||
<label class="sr-only" for="newsletter-email">E-Mail-Adresse</label>
|
||||
<input id="newsletter-email" type="email" name="email" required
|
||||
placeholder="Ihre E-Mail-Adresse"
|
||||
class="flex-1 px-4 py-3.5 text-[14px] bg-white/[0.08] border border-white/[0.18] text-white outline-none rounded-none placeholder-white/40 focus:border-brand focus:ring-0">
|
||||
<button type="submit"
|
||||
class="px-[22px] py-3.5 text-[13px] font-semibold bg-brand text-white whitespace-nowrap rounded-none cursor-pointer hover:bg-brand-deep transition-colors">
|
||||
Kostenlos abonnieren →
|
||||
</button>
|
||||
</form>
|
||||
<div class="text-[11px] text-white/50">
|
||||
Über {{ number_format($subscribers, 0, ',', '.') }} Abonnenten · Datenschutz nach DSGVO
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2.5 self-stretch">
|
||||
@foreach ($topics as $topic)
|
||||
<label class="flex items-start gap-3.5 px-4 py-3.5 cursor-pointer transition-colors
|
||||
{{ ($topic['active'] ?? false) ? 'bg-white/[0.06] border border-brand/40 hover:bg-white/[0.1]' : 'border border-white/10 hover:border-white/30 hover:bg-white/[0.04]' }}">
|
||||
@if ($topic['active'] ?? false)
|
||||
<span class="w-[18px] h-[18px] flex-shrink-0 mt-0.5 bg-brand border border-brand flex items-center justify-center">
|
||||
<svg width="10" height="10" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<path d="M2 6l3 3 5-6" stroke="white" stroke-width="2" />
|
||||
</svg>
|
||||
</span>
|
||||
@else
|
||||
<span class="w-[18px] h-[18px] flex-shrink-0 mt-0.5 border border-white/30"></span>
|
||||
@endif
|
||||
|
||||
<div class="flex-1">
|
||||
<div class="text-[13.5px] font-semibold text-white">{{ $topic['name'] }}</div>
|
||||
<div class="text-[11.5px] text-white/55 mt-0.5">{{ $topic['meta'] }}</div>
|
||||
</div>
|
||||
|
||||
<input type="checkbox" name="topics[]" value="{{ \Illuminate\Support\Str::slug($topic['name']) }}"
|
||||
{{ ($topic['active'] ?? false) ? 'checked' : '' }} class="sr-only">
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
50
resources/views/components/web/page-header.blade.php
Normal file
50
resources/views/components/web/page-header.blade.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
@props([
|
||||
'title',
|
||||
'subtitle' => null,
|
||||
'meta' => null,
|
||||
'image' => null,
|
||||
'compact' => false,
|
||||
])
|
||||
|
||||
<header class="page-header {{ $compact ? 'page-header-compact' : '' }}">
|
||||
<div class="container mx-auto px-4">
|
||||
@if($image)
|
||||
<!-- Header mit Bild -->
|
||||
<div class="grid md:grid-cols-2 gap-8 items-center">
|
||||
<div>
|
||||
@if($meta)
|
||||
<div class="mb-4">
|
||||
{{ $meta }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<h1 class="page-header-title">{{ $title }}</h1>
|
||||
|
||||
@if($subtitle)
|
||||
<p class="page-header-subtitle">{{ $subtitle }}</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="relative h-64 md:h-80 rounded-xl overflow-hidden">
|
||||
<img src="{{ $image }}" alt="{{ $title }}" class="w-full h-full object-cover">
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<!-- Header ohne Bild -->
|
||||
<div class="max-w-4xl">
|
||||
@if($meta)
|
||||
<div class="mb-4">
|
||||
{{ $meta }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<h1 class="page-header-title">{{ $title }}</h1>
|
||||
|
||||
@if($subtitle)
|
||||
<p class="page-header-subtitle">{{ $subtitle }}</p>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</header>
|
||||
|
||||
82
resources/views/components/web/pagination.blade.php
Normal file
82
resources/views/components/web/pagination.blade.php
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
@props([
|
||||
'currentPage' => 1,
|
||||
'totalPages' => 10,
|
||||
'showFirstLast' => true,
|
||||
'maxVisible' => 5,
|
||||
])
|
||||
|
||||
@php
|
||||
$hasPrevious = $currentPage > 1;
|
||||
$hasNext = $currentPage < $totalPages;
|
||||
|
||||
// Berechne sichtbare Seitenzahlen
|
||||
$visiblePages = [];
|
||||
|
||||
if ($totalPages <= $maxVisible) {
|
||||
// Zeige alle Seiten wenn weniger als maxVisible
|
||||
$visiblePages = range(1, $totalPages);
|
||||
} else {
|
||||
// Berechne dynamischen Bereich
|
||||
$start = max(1, $currentPage - floor($maxVisible / 2));
|
||||
$end = min($totalPages, $start + $maxVisible - 1);
|
||||
|
||||
// Korrigiere Start wenn am Ende
|
||||
if ($end - $start < $maxVisible - 1) {
|
||||
$start = max(1, $end - $maxVisible + 1);
|
||||
}
|
||||
|
||||
$visiblePages = range($start, $end);
|
||||
}
|
||||
|
||||
$showStartEllipsis = $visiblePages[0] > 1;
|
||||
$showEndEllipsis = end($visiblePages) < $totalPages;
|
||||
@endphp
|
||||
|
||||
<nav class="flex justify-center mt-12" aria-label="Seitennummerierung">
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Vorherige Seite -->
|
||||
<button
|
||||
class="pagination-btn pagination-nav {{ !$hasPrevious ? 'disabled:opacity-50 disabled:cursor-not-allowed' : '' }}"
|
||||
{{ !$hasPrevious ? 'disabled' : '' }} aria-label="Vorherige Seite"
|
||||
@if ($hasPrevious) onclick="window.location.href='?page={{ $currentPage - 1 }}'" @endif>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Erste Seite + Ellipsis -->
|
||||
@if ($showStartEllipsis && $showFirstLast)
|
||||
<button class="pagination-btn" onclick="window.location.href='?page=1'">
|
||||
1
|
||||
</button>
|
||||
<span class="px-2 text-zinc-600 dark:text-zinc-400">...</span>
|
||||
@endif
|
||||
|
||||
<!-- Sichtbare Seitenzahlen -->
|
||||
@foreach ($visiblePages as $page)
|
||||
<button class="pagination-btn {{ $page === $currentPage ? 'pagination-active' : '' }}"
|
||||
{{ $page === $currentPage ? 'aria-current="page"' : '' }}
|
||||
@if ($page !== $currentPage) onclick="window.location.href='?page={{ $page }}'" @endif>
|
||||
{{ $page }}
|
||||
</button>
|
||||
@endforeach
|
||||
|
||||
<!-- Ellipsis + Letzte Seite -->
|
||||
@if ($showEndEllipsis && $showFirstLast)
|
||||
<span class="px-2 text-zinc-600 dark:text-zinc-400">...</span>
|
||||
<button class="pagination-btn" onclick="window.location.href='?page={{ $totalPages }}'">
|
||||
{{ $totalPages }}
|
||||
</button>
|
||||
@endif
|
||||
|
||||
<!-- Nächste Seite -->
|
||||
<button
|
||||
class="pagination-btn pagination-nav {{ !$hasNext ? 'disabled:opacity-50 disabled:cursor-not-allowed' : '' }}"
|
||||
{{ !$hasNext ? 'disabled' : '' }} aria-label="Nächste Seite"
|
||||
@if ($hasNext) onclick="window.location.href='?page={{ $currentPage + 1 }}'" @endif>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
@ -5,114 +5,155 @@
|
|||
'industry',
|
||||
'region',
|
||||
'date',
|
||||
'hasImage' => false,
|
||||
'hasPdf' => false,
|
||||
'companyLogo' => null,
|
||||
'contentType' => 'FACHMELDUNG', // ANALYSE, INTERVIEW, FACHMELDUNG
|
||||
'slug',
|
||||
'imageUrl' => null,
|
||||
])
|
||||
|
||||
<article class="group bg-white dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-800 overflow-hidden transition-all duration-300 hover:shadow-lg hover:border-[var(--color-primary)]/20 hover:scale-[1.02] shadow-sm">
|
||||
<!-- Image Preview -->
|
||||
<div class="relative h-48 bg-gradient-to-br from-gray-100 to-gray-50 dark:from-gray-800 dark:to-gray-900 overflow-hidden">
|
||||
@if($imageUrl)
|
||||
<img
|
||||
src="{{ $imageUrl }}"
|
||||
alt="{{ $title }}"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
loading="lazy"
|
||||
/>
|
||||
@php
|
||||
// Map industry to Heroicon
|
||||
$industryIcons = [
|
||||
'IT & Digitalisierung' => 'cpu-chip',
|
||||
'Industrie & Technik' => 'cog-6-tooth',
|
||||
'Finanzen & Versicherungen' => 'currency-dollar',
|
||||
'Handel & E-Commerce' => 'shopping-cart',
|
||||
'Bauen & Immobilien' => 'building-office',
|
||||
'Mobilität & Logistik' => 'truck',
|
||||
'Energie & Umwelt' => 'bolt',
|
||||
'Medizin & Gesundheit' => 'heart',
|
||||
'Personal & HR' => 'user-group',
|
||||
'Marketing, PR & Medien' => 'megaphone',
|
||||
'Recht & Steuern' => 'scale',
|
||||
'Politik, Verbände & NGOs' => 'flag',
|
||||
'Wissenschaft & Forschung' => 'beaker',
|
||||
'Lifestyle' => 'sparkles',
|
||||
'Tourismus & Kultur' => 'globe-alt',
|
||||
];
|
||||
|
||||
$iconName = $industryIcons[$industry] ?? 'building-office';
|
||||
$iconPath = "/heroicons/optimized/24/outline/{$iconName}.svg";
|
||||
|
||||
// Content Type Badge Classes
|
||||
$contentTypeClasses = match ($contentType) {
|
||||
'ANALYSE' => 'bg-[var(--color-primary)] text-white',
|
||||
'INTERVIEW' => 'bg-[var(--color-secondary)] text-white',
|
||||
default => 'bg-white/80 dark:bg-zinc-900/90 border border-[var(--color-primary)]/30',
|
||||
};
|
||||
|
||||
$badgeExtraClasses = $imageUrl ? 'shadow-md backdrop-blur-sm' : 'shadow-sm';
|
||||
@endphp
|
||||
|
||||
<a href="/release/{{ $slug }}" class="group block">
|
||||
<article
|
||||
class="bg-gradient-to-br from-[var(--color-white)] to-[var(--color-primary)]/3 dark:from-[var(--color-zinc-800)] dark:to-[var(--color-primary)]/3 rounded-xl border border-[var(--color-primary)]/20 dark:border-[var(--color-primary)]/20 overflow-hidden transition-all duration-300 hover:shadow-xl hover:border-[var(--color-primary)]/30 hover:translate-y-[-2px] shadow-sm h-full flex flex-col">
|
||||
|
||||
@if ($imageUrl)
|
||||
{{-- Card mit Bild --}}
|
||||
<div
|
||||
class="relative h-46 bg-gradient-to-br from-zinc-100 to-zinc-50 dark:from-zinc-800 dark:to-zinc-900 overflow-hidden">
|
||||
<img src="{{ $imageUrl }}" alt="{{ $title }}"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
||||
loading="lazy" />
|
||||
|
||||
{{-- Content Type Label --}}
|
||||
<div class="absolute top-4 left-4">
|
||||
<span
|
||||
class="inline-flex bg-primary/10 items-center gap-2 text-[11px] font-semibold px-3 py-1.5 {{ $contentTypeClasses }} {{ $badgeExtraClasses }} rounded-xl shadow-sm">
|
||||
<div class="industry-icon-badge">
|
||||
<img src="{{ $iconPath }}" alt="{{ $industry }}" class="h-4 w-4" />
|
||||
</div>
|
||||
<span class="font-medium text-zinc-700 dark:text-zinc-300">{{ $industry }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Content --}}
|
||||
<div class="p-7 flex flex-col flex-grow">
|
||||
{{-- Meta Info --}}
|
||||
<div class="flex justify-end left gap-2 mb-4 text-xs text-zinc-500 dark:text-zinc-400">
|
||||
|
||||
<time datetime="{{ $date }}"
|
||||
class="text-zinc-600 dark:text-zinc-400">{{ $date }}</time>
|
||||
</div>
|
||||
|
||||
{{-- Title --}}
|
||||
<h3
|
||||
class="text-xl font-bold text-zinc-900 dark:text-zinc-100 line-clamp-2 mb-4 group-hover:text-[var(--color-primary)] transition-colors leading-tight">
|
||||
{{ $title }}
|
||||
</h3>
|
||||
|
||||
{{-- Teaser --}}
|
||||
<p class="text-[15px] leading-relaxed text-zinc-600 dark:text-zinc-400 line-clamp-3 mb-5 flex-grow">
|
||||
{{ $teaser }}
|
||||
</p>
|
||||
|
||||
{{-- Company Footer --}}
|
||||
<div
|
||||
class="flex items-center justify-between pt-5 border-t border-[var(--color-primary)]/10 group-hover:border-[var(--color-primary)]/20 transition-all duration-300">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="h-4 w-4 text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-zinc-700 dark:text-zinc-300">{{ $company }}</span>
|
||||
</div>
|
||||
<svg class="h-5 w-5 transition-transform group-hover:translate-x-2" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="w-full h-full flex items-center justify-center bg-gradient-to-br from-[var(--color-primary)]/5 to-[var(--color-secondary)]/5">
|
||||
<svg class="h-16 w-16 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
@endif
|
||||
{{-- Card ohne Bild - Textbasiert mit farbigem Hintergrund --}}
|
||||
<div class="relative p-8 flex flex-col h-full">
|
||||
|
||||
<!-- Company Logo Overlay -->
|
||||
@if($companyLogo)
|
||||
<div class="absolute bottom-3 left-3 w-12 h-12 rounded-lg bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm shadow-md flex items-center justify-center border border-gray-200/50 dark:border-gray-700/50">
|
||||
<svg class="h-6 w-6 text-[var(--color-primary)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
|
||||
</svg>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="p-5">
|
||||
<!-- Company Name -->
|
||||
@if(!$companyLogo)
|
||||
<div class="mb-3 flex items-center gap-2">
|
||||
<svg class="h-4 w-4 text-gray-600 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ $company }}</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Title -->
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 line-clamp-2 mb-2 group-hover:text-[var(--color-primary)] transition-colors">
|
||||
{{ $title }}
|
||||
</h3>
|
||||
|
||||
<!-- Teaser -->
|
||||
<p class="text-[15px] leading-relaxed text-gray-600 dark:text-gray-400 line-clamp-3 mb-4">
|
||||
{{ $teaser }}
|
||||
</p>
|
||||
|
||||
<!-- Meta Info -->
|
||||
<div class="flex flex-wrap items-center gap-2 mb-4 text-xs text-gray-600 dark:text-gray-400">
|
||||
<span class="flex items-center gap-1">
|
||||
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
|
||||
</svg>
|
||||
{{ $industry }}
|
||||
</span>
|
||||
<span>•</span>
|
||||
<span class="flex items-center gap-1">
|
||||
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
{{ $region }}
|
||||
</span>
|
||||
<span>•</span>
|
||||
<span class="flex items-center gap-1">
|
||||
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
{{ $date }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Media Badges & CTA -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
@if($hasImage)
|
||||
<span class="inline-flex items-center gap-1 text-xs rounded-full px-3 py-1 bg-[var(--color-primary)]/10 text-[var(--color-primary)] border border-[var(--color-primary)]/20">
|
||||
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
Bild
|
||||
{{-- Content Type Label --}}
|
||||
<div class="mb-0 mt-0">
|
||||
<span
|
||||
class="inline-flex items-center gap-2 text-[11px] font-bold px-3 py-2 {{ $contentTypeClasses }} {{ $badgeExtraClasses }} rounded-xl">
|
||||
<div class="industry-icon-badge">
|
||||
<img src="{{ $iconPath }}" alt="{{ $industry }}" class="h-4 w-4" />
|
||||
</div>
|
||||
<span class="font-medium text-zinc-700 dark:text-zinc-300">{{ $industry }}</span>
|
||||
</span>
|
||||
@endif
|
||||
@if($hasPdf)
|
||||
<span class="inline-flex items-center gap-1 text-xs rounded-full px-3 py-1 bg-[var(--color-secondary)]/10 text-gray-700 border border-[var(--color-secondary)]/20">
|
||||
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
|
||||
</div>
|
||||
|
||||
{{-- Meta Info --}}
|
||||
<div class="flex justify-end left gap-2 mb-10 text-xs text-zinc-500 dark:text-zinc-400">
|
||||
<time datetime="{{ $date }}"
|
||||
class="text-zinc-600 dark:text-zinc-400">{{ $date }}</time>
|
||||
</div>
|
||||
|
||||
{{-- Title - Prominenter ohne Bild --}}
|
||||
<h3
|
||||
class="text-2xl font-bold text-zinc-900 dark:text-zinc-100 mb-5 group-hover:text-[var(--color-primary)] transition-colors leading-tight">
|
||||
{{ $title }}
|
||||
</h3>
|
||||
|
||||
{{-- Teaser as Quote/Highlight --}}
|
||||
<blockquote
|
||||
class="text-base leading-relaxed text-zinc-700 dark:text-zinc-300 line-clamp-5 mb-6 flex-grow italic border-l-4 border-[var(--color-primary)] pl-4">
|
||||
{{ $teaser }}
|
||||
</blockquote>
|
||||
|
||||
{{-- Company Footer --}}
|
||||
<div
|
||||
class="flex items-center justify-between pt-5 border-t border-[var(--color-primary)]/10 group-hover:border-[var(--color-primary)]/20 transition-all duration-300">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="h-4 w-4 text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4">
|
||||
</path>
|
||||
</svg>
|
||||
PDF
|
||||
</span>
|
||||
@endif
|
||||
<span class="text-sm font-medium text-zinc-700 dark:text-zinc-300">{{ $company }}</span>
|
||||
</div>
|
||||
<svg class="h-5 w-5 transition-transform group-hover:translate-x-2" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href="/release/{{ $slug }}"
|
||||
class="text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-[var(--color-primary)] transition-colors group-hover:text-[var(--color-primary)]"
|
||||
>
|
||||
Lesen →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@endif
|
||||
</article>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
@props([
|
||||
'release',
|
||||
])
|
||||
|
||||
@php
|
||||
$categoryTranslation = $release->category?->translations->firstWhere('locale', 'de')
|
||||
?? $release->category?->translations->first();
|
||||
$categoryName = $categoryTranslation?->name;
|
||||
$categoryUrl = $categoryTranslation?->slug ? route('kategorie', ['slug' => $categoryTranslation->slug]) : null;
|
||||
$companyName = $release->company?->name ?: 'Unternehmensmeldung';
|
||||
$teaser = \Illuminate\Support\Str::of(strip_tags((string) $release->text))->squish()->limit(220);
|
||||
$image = $release->images->first();
|
||||
$imageUrl = $image?->variantUrl('card') ?? $image?->url();
|
||||
@endphp
|
||||
|
||||
<article class="group rounded-2xl border border-black/10 bg-white p-5 transition hover:border-[#cf3628]/30 hover:shadow-sm dark:border-white/10 dark:bg-zinc-950">
|
||||
<div class="grid gap-5 sm:grid-cols-[minmax(0,1fr)_11rem]">
|
||||
<div class="min-w-0">
|
||||
<div class="flex flex-wrap items-center gap-2 text-xs font-medium text-zinc-500 dark:text-zinc-400">
|
||||
@if ($categoryName && $categoryUrl)
|
||||
<a href="{{ $categoryUrl }}" class="rounded-full bg-[#cf3628]/10 px-2.5 py-1 text-[#a92c25] hover:bg-[#cf3628]/15">
|
||||
{{ $categoryName }}
|
||||
</a>
|
||||
@elseif ($categoryName)
|
||||
<span class="rounded-full bg-[#cf3628]/10 px-2.5 py-1 text-[#a92c25]">{{ $categoryName }}</span>
|
||||
@endif
|
||||
|
||||
@if ($release->published_at)
|
||||
<time datetime="{{ $release->published_at->toDateString() }}">
|
||||
{{ $release->published_at->format('d.m.Y') }}
|
||||
</time>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<h3 class="mt-3 text-xl font-semibold leading-tight text-zinc-950 group-hover:text-[#cf3628] dark:text-white">
|
||||
<a href="{{ route('release.detail', ['slug' => $release->slug]) }}">
|
||||
{{ $release->title }}
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<p class="mt-3 text-sm leading-6 text-zinc-600 dark:text-zinc-300">
|
||||
{{ $teaser }}
|
||||
</p>
|
||||
|
||||
<div class="mt-4 flex flex-wrap items-center gap-3 text-sm text-zinc-500 dark:text-zinc-400">
|
||||
<span class="font-medium text-zinc-700 dark:text-zinc-200">{{ $companyName }}</span>
|
||||
<span aria-hidden="true">/</span>
|
||||
<a href="{{ route('release.detail', ['slug' => $release->slug]) }}" class="font-semibold text-[#cf3628] hover:text-[#a92c25]">
|
||||
Meldung lesen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($imageUrl)
|
||||
<a href="{{ route('release.detail', ['slug' => $release->slug]) }}" class="block overflow-hidden rounded-xl bg-zinc-100 dark:bg-zinc-900">
|
||||
<img src="{{ $imageUrl }}" alt="{{ $image?->title ?: $release->title }}" class="h-36 w-full object-cover transition duration-300 group-hover:scale-105 sm:h-full" loading="lazy">
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</article>
|
||||
|
|
@ -10,105 +10,152 @@
|
|||
'imageUrl' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
// Map industry to Heroicon
|
||||
$industryIcons = [
|
||||
'IT & Digitalisierung' => 'cpu-chip',
|
||||
'Industrie & Technik' => 'cog-6-tooth',
|
||||
'Finanzen & Versicherungen' => 'currency-dollar',
|
||||
'Handel & E-Commerce' => 'shopping-cart',
|
||||
'Bauen & Immobilien' => 'building-office',
|
||||
'Mobilität & Logistik' => 'truck',
|
||||
'Energie & Umwelt' => 'bolt',
|
||||
'Medizin & Gesundheit' => 'heart',
|
||||
'Personal & HR' => 'user-group',
|
||||
'Marketing, PR & Medien' => 'megaphone',
|
||||
'Recht & Steuern' => 'scale',
|
||||
'Politik, Verbände & NGOs' => 'flag',
|
||||
'Wissenschaft & Forschung' => 'beaker',
|
||||
'Lifestyle' => 'sparkles',
|
||||
'Tourismus & Kultur' => 'globe-alt',
|
||||
];
|
||||
|
||||
$iconName = $industryIcons[$industry] ?? 'building-office';
|
||||
$iconPath = "/heroicons/optimized/24/outline/{$iconName}.svg";
|
||||
|
||||
// Content Type Badge Classes
|
||||
$contentTypeClasses = match ($contentType) {
|
||||
'ANALYSE' => 'bg-[var(--color-primary)] text-white',
|
||||
'INTERVIEW' => 'bg-[var(--color-secondary)] text-white',
|
||||
default => 'bg-white/95 text-[var(--color-primary)] border border-[var(--color-primary)]/20',
|
||||
};
|
||||
|
||||
$badgeExtraClasses = $imageUrl ? 'shadow-md backdrop-blur-sm' : 'shadow-sm';
|
||||
@endphp
|
||||
|
||||
<a href="/release/{{ $slug }}" class="group block">
|
||||
<article class="bg-white dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-800 overflow-hidden transition-all duration-300 hover:shadow-xl hover:border-[var(--color-primary)]/30 shadow-sm h-full flex flex-col">
|
||||
<article
|
||||
class="bg-gradient-to-br from-[var(--color-zinc-100)]/50 to-[var(--color-zinc-100)]/25 dark:from-[var(--color-zinc-800)] dark:to-[var(--color-zinc-800)]/50 rounded-xl border border-[var(--color-primary)]/20 dark:border-zinc-800 overflow-hidden transition-all duration-300 hover:shadow-xl hover:border-[var(--color-primary)]/30 hover:translate-y-[-2px] shadow-sm h-full flex flex-col">
|
||||
|
||||
@if($imageUrl)
|
||||
<!-- Card mit Bild -->
|
||||
<!-- Image Preview -->
|
||||
<div class="relative h-52 bg-gradient-to-br from-gray-100 to-gray-50 dark:from-gray-800 dark:to-gray-900 overflow-hidden">
|
||||
<img
|
||||
src="{{ $imageUrl }}"
|
||||
alt="{{ $title }}"
|
||||
@if ($imageUrl)
|
||||
{{-- Card mit Bild --}}
|
||||
<div
|
||||
class="relative h-46 bg-gradient-to-br from-zinc-100 to-zinc-50 dark:from-zinc-800 dark:to-zinc-900 overflow-hidden">
|
||||
<img src="{{ $imageUrl }}" alt="{{ $title }}"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
||||
loading="lazy"
|
||||
/>
|
||||
loading="lazy" />
|
||||
|
||||
<!-- Content Type Label - Positioned on Image -->
|
||||
{{-- Content Type Label --}}
|
||||
<div class="absolute top-4 left-4">
|
||||
<span class="inline-block text-[10px] font-bold tracking-widest uppercase px-3 py-1.5
|
||||
@if($contentType === 'ANALYSE')
|
||||
bg-[var(--color-primary)] text-white
|
||||
@elseif($contentType === 'INTERVIEW')
|
||||
bg-[var(--color-secondary)] text-white
|
||||
@else
|
||||
bg-white/95 text-[var(--color-primary)] border border-[var(--color-primary)]/20
|
||||
@endif
|
||||
shadow-lg backdrop-blur-sm rounded">
|
||||
|
||||
<span
|
||||
class="inline-block text-[10px] font-bold tracking-widest uppercase px-3 py-1.5 {{ $contentTypeClasses }} {{ $badgeExtraClasses }} rounded">
|
||||
{{ $contentType }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content with increased padding -->
|
||||
{{-- Content --}}
|
||||
<div class="p-7 flex flex-col flex-grow">
|
||||
<!-- Meta Info - More spacing -->
|
||||
<div class="flex items-center gap-3 mb-4 text-xs text-gray-500 dark:text-gray-500">
|
||||
<span class="font-medium text-gray-700 dark:text-gray-300">{{ $industry }}</span>
|
||||
<span class="text-gray-300 dark:text-gray-700">•</span>
|
||||
<time datetime="{{ $date }}" class="text-gray-600 dark:text-gray-400">{{ $date }}</time>
|
||||
{{-- Meta Info --}}
|
||||
<div class="flex items-center justify-between gap-2 mb-4 text-xs text-zinc-500 dark:text-zinc-400">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="industry-icon-badge">
|
||||
<img src="{{ $iconPath }}" alt="{{ $industry }}" class="h-4 w-4" />
|
||||
</div>
|
||||
<span class="font-medium text-zinc-700 dark:text-zinc-300">{{ $industry }}</span>
|
||||
</div>
|
||||
<time datetime="{{ $date }}"
|
||||
class="text-zinc-600 dark:text-zinc-400">{{ $date }}</time>
|
||||
</div>
|
||||
|
||||
<!-- Title - Larger, more space -->
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-gray-100 line-clamp-2 mb-4 group-hover:text-[var(--color-primary)] transition-colors leading-tight">
|
||||
{{-- Title --}}
|
||||
<h3
|
||||
class="text-xl font-bold text-zinc-900 dark:text-zinc-100 line-clamp-2 mb-4 group-hover:text-[var(--color-primary)] transition-colors leading-tight">
|
||||
{{ $title }}
|
||||
</h3>
|
||||
|
||||
<!-- Teaser - More generous line-height -->
|
||||
<p class="text-[15px] leading-relaxed text-gray-600 dark:text-gray-400 line-clamp-3 mb-5 flex-grow">
|
||||
{{-- Teaser --}}
|
||||
<p class="text-[15px] leading-relaxed text-zinc-600 dark:text-zinc-400 line-clamp-3 mb-5 flex-grow">
|
||||
{{ $teaser }}
|
||||
</p>
|
||||
|
||||
<!-- Company at bottom -->
|
||||
<div class="flex items-center gap-2 pt-4 border-t border-gray-100 dark:border-gray-800">
|
||||
<svg class="h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
|
||||
{{-- Company Footer --}}
|
||||
<div class="flex items-center justify-between pt-5 border-t border-[var(--color-primary)]/10">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="h-4 w-4 text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-zinc-700 dark:text-zinc-300">{{ $company }}</span>
|
||||
</div>
|
||||
<svg class="h-5 w-5 transition-transform group-hover:translate-x-1" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ $company }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<!-- Card ohne Bild - Textbasiert mit farbigem Hintergrund -->
|
||||
<div class="relative bg-gradient-to-br from-[var(--color-primary)]/5 via-[var(--color-secondary)]/5 to-[var(--color-primary)]/10 dark:from-[var(--color-primary)]/10 dark:via-[var(--color-secondary)]/10 dark:to-[var(--color-primary)]/20 p-8 flex flex-col min-h-[400px]">
|
||||
{{-- Card ohne Bild - Textbasiert mit farbigem Hintergrund --}}
|
||||
<div class="relative p-8 flex flex-col h-full">
|
||||
|
||||
<!-- Content Type Label - Top -->
|
||||
<div class="mb-6">
|
||||
<span class="inline-block text-[10px] font-bold tracking-widest uppercase px-3 py-1.5
|
||||
@if($contentType === 'ANALYSE')
|
||||
bg-[var(--color-primary)] text-white
|
||||
@elseif($contentType === 'INTERVIEW')
|
||||
bg-[var(--color-secondary)] text-white
|
||||
@else
|
||||
bg-white text-[var(--color-primary)] border border-[var(--color-primary)]/20
|
||||
@endif
|
||||
shadow-sm rounded">
|
||||
{{-- Content Type Label --}}
|
||||
<div class="mb-6 mt-0">
|
||||
<span
|
||||
class="inline-block text-[10px] font-bold tracking-widest uppercase px-3 py-1.5 {{ $contentTypeClasses }} {{ $badgeExtraClasses }} rounded">
|
||||
{{ $contentType }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Meta Info -->
|
||||
<div class="flex items-center gap-3 mb-5 text-xs text-gray-500 dark:text-gray-400">
|
||||
<span class="font-medium text-gray-700 dark:text-gray-300">{{ $industry }}</span>
|
||||
<span class="text-gray-300 dark:text-gray-600">•</span>
|
||||
<time datetime="{{ $date }}" class="text-gray-600 dark:text-gray-400">{{ $date }}</time>
|
||||
{{-- Meta Info --}}
|
||||
<div class="flex items-center justify-between gap-2 mb-5 text-xs text-zinc-500 dark:text-zinc-400">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="industry-icon-badge">
|
||||
<img src="{{ $iconPath }}" alt="{{ $industry }}" class="h-4 w-4" />
|
||||
</div>
|
||||
<span class="font-medium text-zinc-700 dark:text-zinc-300">{{ $industry }}</span>
|
||||
</div>
|
||||
<time datetime="{{ $date }}"
|
||||
class="text-zinc-600 dark:text-zinc-400">{{ $date }}</time>
|
||||
</div>
|
||||
|
||||
<!-- Title - Prominenter ohne Bild -->
|
||||
<h3 class="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-5 group-hover:text-[var(--color-primary)] transition-colors leading-tight">
|
||||
{{-- Title - Prominenter ohne Bild --}}
|
||||
<h3
|
||||
class="text-2xl font-bold text-zinc-900 dark:text-zinc-100 mb-5 group-hover:text-[var(--color-primary)] transition-colors leading-tight">
|
||||
{{ $title }}
|
||||
</h3>
|
||||
|
||||
<!-- Teaser as Quote/Highlight - Mehr Zeilen ohne Bild -->
|
||||
<blockquote class="text-base leading-relaxed text-gray-700 dark:text-gray-300 line-clamp-5 mb-6 flex-grow italic border-l-4 border-[var(--color-primary)] pl-4">
|
||||
{{-- Teaser as Quote/Highlight --}}
|
||||
<blockquote
|
||||
class="text-base leading-relaxed text-zinc-700 dark:text-zinc-300 line-clamp-5 mb-6 flex-grow italic border-l-4 border-[var(--color-primary)] pl-4">
|
||||
{{ $teaser }}
|
||||
</blockquote>
|
||||
|
||||
<!-- Company at bottom -->
|
||||
<div class="flex items-center gap-2 pt-5 border-t border-[var(--color-primary)]/10">
|
||||
<svg class="h-4 w-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
|
||||
{{-- Company Footer --}}
|
||||
<div class="flex items-center justify-between pt-5 border-t border-[var(--color-primary)]/10">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="h-4 w-4 text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-zinc-700 dark:text-zinc-300">{{ $company }}</span>
|
||||
</div>
|
||||
<svg class="h-5 w-5 transition-transform group-hover:translate-x-1" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ $company }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
|
|
|||
32
resources/views/components/web/publisher-cta.blade.php
Normal file
32
resources/views/components/web/publisher-cta.blade.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
@props([
|
||||
'submitHref' => null,
|
||||
'pricingHref' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
$submitHref ??= route('veroeffentlichen');
|
||||
$pricingHref ??= route('preise');
|
||||
@endphp
|
||||
|
||||
<section class="bg-topbar-grad text-ink-on-dark p-6">
|
||||
<div class="eyebrow on-dark mb-2">Für Unternehmen</div>
|
||||
<h3 class="font-serif text-[20px] font-semibold m-0 mb-2 tracking-[-0.2px] leading-[1.2] text-white">
|
||||
Veröffentlichen Sie Ihre Pressemitteilung
|
||||
</h3>
|
||||
<p class="text-[12.5px] leading-[1.5] m-0 mb-4 text-white/85">
|
||||
Reichweite in DACH · Redaktionelle Prüfung · Strukturierte Distribution
|
||||
</p>
|
||||
|
||||
<a href="{{ $submitHref }}"
|
||||
class="w-full inline-flex items-center justify-center gap-2 px-[18px] py-2.5 text-[13px] font-semibold bg-brand text-white rounded-[2px] cursor-pointer hover:bg-brand-deep transition-colors">
|
||||
Jetzt einreichen →
|
||||
</a>
|
||||
|
||||
<a href="{{ $pricingHref }}"
|
||||
class="flex items-center justify-center gap-1.5 w-full mt-3 py-2 text-[12px] font-medium text-white/80 cursor-pointer hover:text-white underline underline-offset-[3px] decoration-white/30 hover:decoration-white/70 transition-colors">
|
||||
Tarife & Pakete ansehen
|
||||
<svg width="10" height="10" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<path d="M2 6h8M6 2l4 4-4 4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</a>
|
||||
</section>
|
||||
25
resources/views/components/web/quality-summary.blade.php
Normal file
25
resources/views/components/web/quality-summary.blade.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
@props(['stats' => []])
|
||||
|
||||
<section class="max-w-layout mx-auto mt-12 px-8">
|
||||
<div class="grid items-center gap-7 p-6 bg-bg-card-warm border border-bg-rule-strong"
|
||||
style="grid-template-columns: auto 1fr auto;">
|
||||
<div class="w-11 h-11 rounded-full flex-shrink-0 bg-white border border-bg-rule-strong flex items-center justify-center text-brand">
|
||||
<svg width="22" height="22" 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="1.6" fill="none" />
|
||||
<path d="M8 12l3 3 5-6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="eyebrow mb-1">Redaktioneller Qualitätsstandard</div>
|
||||
<div class="text-[13.5px] text-ink leading-[1.5] font-medium">
|
||||
Alle Pressemitteilungen werden redaktionell geprüft.
|
||||
<span class="text-ink-2 font-normal">
|
||||
Mindestqualität gewährleistet durch unseren Content-Score — eine Bewertung von Quellenqualität, Verifizierungsstatus und redaktioneller Relevanz.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ route('faq') }}" class="text-[12px] font-semibold text-brand whitespace-nowrap underline underline-offset-[3px] decoration-brand/40 cursor-pointer hover:text-brand-deep hover:decoration-brand transition-colors">
|
||||
Redaktionsrichtlinien →
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
32
resources/views/components/web/section-header.blade.php
Normal file
32
resources/views/components/web/section-header.blade.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
@props([
|
||||
'title',
|
||||
'subtitle' => null,
|
||||
'size' => 'large', // large, medium, small
|
||||
])
|
||||
|
||||
@php
|
||||
$titleClasses = match($size) {
|
||||
'large' => 'text-3xl md:text-4xl',
|
||||
'medium' => 'text-2xl md:text-3xl',
|
||||
'small' => 'text-xl md:text-2xl',
|
||||
default => 'text-2xl md:text-3xl',
|
||||
};
|
||||
|
||||
$indicatorClasses = match($size) {
|
||||
'large' => 'w-2 h-10',
|
||||
'medium' => 'w-1.5 h-8',
|
||||
'small' => 'w-1 h-6',
|
||||
default => 'w-1.5 h-8',
|
||||
};
|
||||
@endphp
|
||||
|
||||
<div class="mb-8">
|
||||
<h2 class="font-bold text-zinc-900 dark:text-zinc-100 mb-2 flex items-center gap-3 {{ $titleClasses }}">
|
||||
<span class="{{ $indicatorClasses }} gradient-indicator"></span>
|
||||
{{ $title }}
|
||||
</h2>
|
||||
@if($subtitle)
|
||||
<p class="text-zinc-600 dark:text-zinc-400 ml-5">{{ $subtitle }}</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
32
resources/views/components/web/sidebar-categories.blade.php
Normal file
32
resources/views/components/web/sidebar-categories.blade.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
@props([
|
||||
'categories' => [],
|
||||
'activeSlug' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
use App\Services\CategoryService;
|
||||
@endphp
|
||||
|
||||
<x-web.sidebar-widget title="Kategorien">
|
||||
<div class="space-y-2">
|
||||
@foreach ($categories as $cat)
|
||||
@php
|
||||
$catIconPath = CategoryService::getIconPath($cat['icon']);
|
||||
$isActive = $cat['slug'] === $activeSlug;
|
||||
@endphp
|
||||
|
||||
<a href="/kategorie/{{ $cat['slug'] }}"
|
||||
class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-all group
|
||||
{{ $isActive
|
||||
? 'bg-[var(--color-primary)] text-white'
|
||||
: 'text-zinc-600 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 hover:text-[var(--color-primary)] hover:translate-x-1' }}">
|
||||
<img src="{{ $catIconPath }}" alt="{{ $cat['name'] }}"
|
||||
class="h-4 w-4 {{ $isActive ? 'brightness-0 invert' : '' }}">
|
||||
<span class="flex-1">{{ $cat['name'] }}</span>
|
||||
<span class="text-xs {{ $isActive ? 'opacity-75' : 'opacity-50' }}">
|
||||
{{ $cat['count'] }}
|
||||
</span>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-web.sidebar-widget>
|
||||
34
resources/views/components/web/sidebar-companies.blade.php
Normal file
34
resources/views/components/web/sidebar-companies.blade.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
@props([
|
||||
'companies' => [],
|
||||
'title' => 'Top Unternehmen',
|
||||
])
|
||||
|
||||
@php
|
||||
// Default companies wenn keine übergeben werden
|
||||
if (empty($companies)) {
|
||||
$companies = [
|
||||
['name' => 'TechVision Analytics', 'initial' => 'T', 'url' => '#'],
|
||||
['name' => 'CloudTech Research', 'initial' => 'C', 'url' => '#'],
|
||||
['name' => 'CyberSafe Europe', 'initial' => 'C', 'url' => '#'],
|
||||
['name' => 'DevTech Insights', 'initial' => 'D', 'url' => '#'],
|
||||
['name' => 'QuantumTech Germany', 'initial' => 'Q', 'url' => '#'],
|
||||
];
|
||||
}
|
||||
@endphp
|
||||
|
||||
<x-web.sidebar-widget title="Top Unternehmen">
|
||||
<div class="space-y-2">
|
||||
@foreach ($companies as $company)
|
||||
<a href="{{ $company['url'] ?? '#' }}"
|
||||
class="flex items-center gap-3 text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800 p-2 rounded-lg transition-colors">
|
||||
<div
|
||||
class="w-8 h-8 bg-gradient-to-br from-[var(--color-primary)]/10 to-[var(--color-secondary)]/10 rounded flex items-center justify-center flex-shrink-0">
|
||||
<span class="text-xs font-bold text-[var(--color-primary)]">{{ $company['initial'] }}</span>
|
||||
</div>
|
||||
<span class="text-zinc-900 dark:text-zinc-100 hover:text-[var(--color-primary)] transition-colors">
|
||||
{{ $company['name'] }}
|
||||
</span>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-web.sidebar-widget>
|
||||
23
resources/views/components/web/sidebar-newsletter.blade.php
Normal file
23
resources/views/components/web/sidebar-newsletter.blade.php
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
@props([
|
||||
'title' => 'Newsletter',
|
||||
'description' => 'Erhalten Sie die neuesten Pressemitteilungen direkt in Ihr Postfach',
|
||||
'buttonText' => 'Jetzt abonnieren',
|
||||
])
|
||||
|
||||
<x-web.sidebar-widget :title="$title">
|
||||
<div class="text-center">
|
||||
<svg class="h-10 w-10 text-[var(--color-primary)] mx-auto mb-3" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z">
|
||||
</path>
|
||||
</svg>
|
||||
<p class="text-sm text-zinc-600 dark:text-zinc-400 mb-4">
|
||||
{{ $description }}
|
||||
</p>
|
||||
<button class="btn-primary w-full py-3 text-sm">
|
||||
{{ $buttonText }}
|
||||
</button>
|
||||
</div>
|
||||
</x-web.sidebar-widget>
|
||||
|
||||
24
resources/views/components/web/sidebar-rss.blade.php
Normal file
24
resources/views/components/web/sidebar-rss.blade.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
@props([
|
||||
'title' => 'RSS-Feed',
|
||||
'description' => 'Bleiben Sie über neue Meldungen auf dem Laufenden',
|
||||
'buttonText' => 'RSS abonnieren',
|
||||
])
|
||||
|
||||
<x-web.sidebar-widget :title="$title">
|
||||
<div class="text-center">
|
||||
<svg class="h-10 w-10 text-[var(--color-secondary)] mx-auto mb-3" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M6 5c7.18 0 13 5.82 13 13M6 11a7 7 0 017 7m-6 0a1 1 0 11-2 0 1 1 0 012 0z">
|
||||
</path>
|
||||
</svg>
|
||||
<p class="text-sm text-zinc-600 dark:text-zinc-400 mb-4">
|
||||
{{ $description }}
|
||||
</p>
|
||||
<button
|
||||
class="w-full px-6 py-3 text-sm font-medium text-zinc-700 dark:text-zinc-300 border border-zinc-300 dark:border-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-800 rounded-lg transition-all">
|
||||
{{ $buttonText }}
|
||||
</button>
|
||||
</div>
|
||||
</x-web.sidebar-widget>
|
||||
|
||||
21
resources/views/components/web/sidebar-widget.blade.php
Normal file
21
resources/views/components/web/sidebar-widget.blade.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
@props([
|
||||
'title' => null,
|
||||
'icon' => null,
|
||||
])
|
||||
|
||||
<div class="sidebar-widget">
|
||||
@if ($title)
|
||||
<div class="sidebar-widget-header">
|
||||
@if ($icon)
|
||||
<div class="w-5 h-5 text-[var(--color-primary)]">
|
||||
{!! $icon !!}
|
||||
</div>
|
||||
@endif
|
||||
<h3 class="sidebar-widget-title">{{ $title }}</h3>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="sidebar-widget-content">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
84
resources/views/components/web/site-footer.blade.php
Normal file
84
resources/views/components/web/site-footer.blade.php
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
@props([
|
||||
'brand' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
$themeKey = config('app.theme');
|
||||
$brand = $brand ?? config('domains.domains.'.$themeKey.'.brand', [
|
||||
'name' => 'businessportal',
|
||||
'accent' => '24',
|
||||
'tagline_short' => 'Pressemitteilungen · DACH',
|
||||
'tagline_long' => 'Veröffentlichungs-Portal für redaktionell geprüfte Pressemitteilungen aus Deutschland, Österreich und der Schweiz.',
|
||||
'footer_legal' => '© :year businessportal24.com · Alle Rechte vorbehalten',
|
||||
'about_label' => 'Über businessportal24',
|
||||
]);
|
||||
|
||||
$brandName = $brand['name'] ?? 'businessportal';
|
||||
$brandAccent = $brand['accent'] ?? '';
|
||||
$brandTagline = $brand['tagline_short'] ?? 'Pressemitteilungen · DACH';
|
||||
$brandTaglineLong = $brand['tagline_long'] ?? 'Pressemitteilungen aus dem DACH-Raum.';
|
||||
$aboutLabel = $brand['about_label'] ?? ('Über '.$brandName.$brandAccent);
|
||||
$legal = str_replace(':year', (string) now()->format('Y'), $brand['footer_legal'] ?? '© :year · Alle Rechte vorbehalten');
|
||||
@endphp
|
||||
|
||||
<footer class="mt-16 bg-topbar-grad text-ink-on-dark">
|
||||
<div class="max-w-layout mx-auto px-8 py-12 grid gap-10 grid-cols-1 md:grid-cols-2 lg:grid-cols-[1.4fr_1fr_1fr_1fr]">
|
||||
<div>
|
||||
<a href="{{ route('home') }}" class="block cursor-pointer group" aria-label="{{ $brandName }}{{ $brandAccent }} Startseite">
|
||||
<div class="font-serif text-[24px] font-semibold leading-none tracking-[-0.5px] text-white group-hover:text-white/80 transition-colors">
|
||||
{{ $brandName }}@if ($brandAccent)<span class="text-brand">{{ $brandAccent }}</span>@endif
|
||||
</div>
|
||||
<div class="eyebrow mt-2 text-[9.5px] tracking-[0.18em] text-ink-on-dark-muted">
|
||||
{{ $brandTagline }}
|
||||
</div>
|
||||
</a>
|
||||
<p class="text-[12.5px] text-white/55 leading-[1.55] mt-4 max-w-[320px]">
|
||||
{{ $brandTaglineLong }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="eyebrow mb-3.5">Pressemitteilungen</div>
|
||||
<ul class="space-y-2 text-[13px] text-white/75 list-none p-0 m-0">
|
||||
<li><a href="{{ route('kategorien') }}" class="cursor-pointer hover:text-white transition-colors">Alle Branchen</a></li>
|
||||
<li><a href="#" class="cursor-pointer hover:text-white transition-colors">Ad-Hoc-Meldungen</a></li>
|
||||
<li><a href="{{ route('kategorien') }}#termine" class="cursor-pointer hover:text-white transition-colors">Termine & Events</a></li>
|
||||
<li><a href="{{ route('newsrooms') }}" class="cursor-pointer hover:text-white transition-colors">Newsrooms</a></li>
|
||||
<li><a href="{{ route('kategorien') }}" class="cursor-pointer hover:text-white transition-colors">Branchen-Index</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="eyebrow mb-3.5">Für Unternehmen</div>
|
||||
<ul class="space-y-2 text-[13px] text-white/75 list-none p-0 m-0">
|
||||
<li><a href="{{ route('veroeffentlichen') }}" class="cursor-pointer hover:text-white transition-colors">Veröffentlichen</a></li>
|
||||
<li><a href="{{ route('preise') }}" class="cursor-pointer hover:text-white transition-colors">Tarife & Pakete</a></li>
|
||||
<li><a href="#" class="cursor-pointer hover:text-white transition-colors">Verifizierter Newsroom</a></li>
|
||||
<li><a href="{{ route('api') }}" class="cursor-pointer hover:text-white transition-colors">API & RSS</a></li>
|
||||
<li><a href="#" class="cursor-pointer hover:text-white transition-colors">Mediendaten</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="eyebrow mb-3.5">Unternehmen</div>
|
||||
<ul class="space-y-2 text-[13px] text-white/75 list-none p-0 m-0">
|
||||
<li><a href="{{ route('ueber-uns') }}" class="cursor-pointer hover:text-white transition-colors">{{ $aboutLabel }}</a></li>
|
||||
<li><a href="#" class="cursor-pointer hover:text-white transition-colors">Redaktion</a></li>
|
||||
<li><a href="{{ route('kontakt') }}" class="cursor-pointer hover:text-white transition-colors">Kontakt</a></li>
|
||||
<li><a href="{{ route('impressum') }}" class="cursor-pointer hover:text-white transition-colors">Impressum</a></li>
|
||||
<li><a href="{{ route('datenschutz') }}" class="cursor-pointer hover:text-white transition-colors">Datenschutz</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-white/10">
|
||||
<div class="max-w-layout mx-auto px-8 py-5 flex items-center justify-between gap-4 text-[11.5px] text-white/50 flex-wrap">
|
||||
<span>{{ $legal }}</span>
|
||||
<span class="flex items-center gap-4 flex-wrap">
|
||||
<a href="{{ route('agb') }}" class="cursor-pointer hover:text-white transition-colors">AGB</a>
|
||||
<a href="#" class="cursor-pointer hover:text-white transition-colors">Cookie-Einstellungen</a>
|
||||
<a href="{{ route('datenschutz') }}" class="cursor-pointer hover:text-white transition-colors">DSGVO</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
312
resources/views/components/web/site-header.blade.php
Normal file
312
resources/views/components/web/site-header.blade.php
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
@props([
|
||||
'activeRegion' => 'DE',
|
||||
'language' => 'Deutsch',
|
||||
'languageShort' => 'DE',
|
||||
'navigation' => null,
|
||||
'ticker' => [],
|
||||
'marketIndex' => null,
|
||||
'asideSlides' => null,
|
||||
'brand' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
$themeKey = config('app.theme');
|
||||
$brand = $brand ?? config('domains.domains.'.$themeKey.'.brand', [
|
||||
'name' => 'businessportal',
|
||||
'accent' => '24',
|
||||
'tagline_short' => 'Pressemitteilungen · DACH',
|
||||
]);
|
||||
$brandName = $brand['name'] ?? 'businessportal';
|
||||
$brandAccent = $brand['accent'] ?? '';
|
||||
$brandTagline = $brand['tagline_short'] ?? 'Pressemitteilungen · DACH';
|
||||
@endphp
|
||||
|
||||
@php
|
||||
\Carbon\Carbon::setLocale('de');
|
||||
$today = now();
|
||||
|
||||
$regions = [
|
||||
['code' => 'DE', 'flag' => '🇩🇪'],
|
||||
['code' => 'AT', 'flag' => '🇦🇹'],
|
||||
['code' => 'CH', 'flag' => '🇨🇭'],
|
||||
['code' => 'EN', 'flag' => '🌐'],
|
||||
];
|
||||
|
||||
$navigation ??= [
|
||||
['label' => 'Startseite', 'href' => route('home'), 'active' => true],
|
||||
['label' => 'Wirtschaft', 'href' => route('kategorien')],
|
||||
['label' => 'Technologie', 'href' => route('kategorien')],
|
||||
['label' => 'Finanzen', 'href' => route('kategorien')],
|
||||
['label' => 'Industrie', 'href' => route('kategorien')],
|
||||
['label' => 'Energie', 'href' => route('kategorien')],
|
||||
['label' => 'Gesundheit', 'href' => route('kategorien')],
|
||||
['label' => 'Handel', 'href' => route('kategorien')],
|
||||
['label' => 'Immobilien', 'href' => route('kategorien')],
|
||||
['label' => 'Mobilität', 'href' => route('kategorien')],
|
||||
['label' => 'Alle Rubriken', 'href' => route('kategorien')],
|
||||
];
|
||||
|
||||
$activeLabel = collect($navigation)->firstWhere('active', true)['label'] ?? 'Menü';
|
||||
@endphp
|
||||
|
||||
<header>
|
||||
{{-- Top Utility Bar (dunkel) --}}
|
||||
<div id="topbar" class="bg-topbar-grad text-ink-on-dark-2 border-b border-black">
|
||||
<div class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 flex items-stretch text-[11.5px] tracking-wide">
|
||||
<span class="flex items-center pr-3 sm:pr-4 py-2 whitespace-nowrap border-r border-ink-on-dark-rule text-[10.5px] sm:text-[11.5px]">
|
||||
<span class="hidden sm:inline">{{ ucfirst($today->isoFormat('dd, D. MMMM YYYY')) }}</span>
|
||||
<span class="sm:hidden">{{ ucfirst($today->isoFormat('dd, D. MMM')) }}</span>
|
||||
</span>
|
||||
|
||||
<span class="hidden md:flex items-center px-3.5 text-[9.5px] uppercase font-semibold tracking-[0.16em] text-ink-3 whitespace-nowrap">
|
||||
Ausgabe
|
||||
</span>
|
||||
|
||||
@foreach ($regions as $region)
|
||||
<button type="button"
|
||||
class="flex items-center gap-1 sm:gap-1.5 px-1.5 sm:px-2.5 py-2 text-[11px] sm:text-[11.5px] whitespace-nowrap -mb-px border-b-2 cursor-pointer transition-colors
|
||||
@if ($region['code'] === $activeRegion) font-semibold text-ink-on-dark bg-white/[0.06] border-brand
|
||||
@else font-medium text-ink-on-dark-2 border-transparent hover:text-ink-on-dark hover:bg-white/[0.04] @endif"
|
||||
aria-label="Region {{ $region['code'] }}"
|
||||
aria-pressed="{{ $region['code'] === $activeRegion ? 'true' : 'false' }}">
|
||||
<span class="text-[11px] sm:text-[12px] leading-none">{{ $region['flag'] }}</span>
|
||||
<span>{{ $region['code'] }}</span>
|
||||
</button>
|
||||
@endforeach
|
||||
|
||||
<span class="flex-1"></span>
|
||||
|
||||
{{-- Sprache: mobil verkürzt; ab lg mit Newsletter/RSS/API --}}
|
||||
<span class="flex items-center gap-3 sm:gap-4 py-2 whitespace-nowrap">
|
||||
<span class="inline-flex items-center gap-1.5">
|
||||
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" class="opacity-60" 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" fill="none" />
|
||||
</svg>
|
||||
<strong class="text-ink-on-dark font-semibold">
|
||||
<span class="hidden lg:inline">{{ $language }}</span>
|
||||
<span class="lg:hidden">{{ $languageShort }}</span>
|
||||
</strong>
|
||||
</span>
|
||||
<span class="hidden lg:inline-block w-px h-[14px] bg-ink-on-dark-rule"></span>
|
||||
<a href="#newsletter" class="hidden lg:inline cursor-pointer hover:text-ink-on-dark transition-colors">Newsletter</a>
|
||||
<a href="#rss" class="hidden lg:inline cursor-pointer hover:text-ink-on-dark transition-colors">RSS</a>
|
||||
<a href="{{ route('api') }}" class="hidden lg:inline cursor-pointer hover:text-ink-on-dark transition-colors">API</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Header (Logo + Suche + Buttons + Burger) --}}
|
||||
<div id="header" class="header-normal bg-bg border-b border-bg-rule"
|
||||
x-data="{ searchOpen: false }"
|
||||
@keydown.escape.window="searchOpen = false">
|
||||
<div class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 py-3 lg:py-[18px] flex items-center gap-3 sm:gap-4 lg:gap-6">
|
||||
<a href="{{ route('home') }}" class="block cursor-pointer group flex-shrink-0" aria-label="{{ $brandName }}{{ $brandAccent }} Startseite">
|
||||
<div class="font-serif text-[22px] sm:text-[24px] lg:text-[28px] font-semibold leading-none tracking-[-0.5px] text-ink group-hover:text-brand transition-colors">
|
||||
{{ $brandName }}@if ($brandAccent)<span class="text-brand">{{ $brandAccent }}</span>@endif
|
||||
</div>
|
||||
<div class="eyebrow muted mt-1 text-[9.5px] tracking-[0.18em] hidden sm:block">
|
||||
{{ $brandTagline }}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{{-- Suchfeld nur ab md sichtbar --}}
|
||||
<form action="{{ route('suche') }}" method="get" class="hidden md:block flex-1 max-w-[460px]">
|
||||
<label class="sr-only" for="site-search">Pressemitteilungen, Unternehmen, Branchen, ISIN durchsuchen</label>
|
||||
<div class="flex items-center gap-2.5 px-3.5 py-2.5 border border-bg-rule bg-bg-elev text-[13px] text-ink-3 rounded-[2px] focus-within:border-brand transition-colors">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<circle cx="7" cy="7" r="5" stroke="currentColor" stroke-width="1.5" />
|
||||
<path d="M11 11l3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
||||
</svg>
|
||||
<input id="site-search" type="search" name="q"
|
||||
placeholder="Pressemitteilungen, Unternehmen, Branchen, ISIN…"
|
||||
class="flex-1 bg-transparent border-0 p-0 text-[13px] text-ink placeholder:text-ink-3 focus:outline-none focus:ring-0 min-w-0">
|
||||
<span class="hidden xl:inline-block text-[10.5px] text-ink-4 border border-bg-rule px-1.5 py-0.5 rounded-[2px] font-mono">⌘K</span>
|
||||
</div>
|
||||
<div class="hidden lg:flex justify-end mt-1.5">
|
||||
<a href="{{ route('suche') }}" class="inline-flex items-center gap-1.5 text-[11.5px] text-ink-3 underline underline-offset-2 decoration-[#1C1A17]/40 cursor-pointer hover:text-ink hover:decoration-brand transition-colors">
|
||||
<svg width="10" height="10" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<path d="M2 3h8M3.5 6h5M5 9h2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" />
|
||||
</svg>
|
||||
Erweiterte Suche
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<span class="flex-1 md:hidden"></span>
|
||||
|
||||
{{-- Such-Icon nur auf mobile --}}
|
||||
<button type="button"
|
||||
@click="searchOpen = true"
|
||||
class="md:hidden inline-flex items-center justify-center w-10 h-10 text-ink rounded-[2px] cursor-pointer hover:bg-bg-elev transition-colors"
|
||||
aria-label="Suche öffnen">
|
||||
<svg width="20" height="20" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<circle cx="7" cy="7" r="5" stroke="currentColor" stroke-width="1.5" />
|
||||
<path d="M11 11l3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{{-- Anmelden --}}
|
||||
<a href="#anmelden"
|
||||
class="inline-flex items-center gap-2 px-2 sm:px-3 lg:px-4 py-2 lg:py-2.5 text-[13px] font-semibold text-ink rounded-[2px] cursor-pointer hover:text-brand transition-colors"
|
||||
aria-label="Anmelden">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" class="sm:hidden" aria-hidden="true">
|
||||
<circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="1.8" />
|
||||
<path d="M4 21c0-4 4-7 8-7s8 3 8 7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
|
||||
</svg>
|
||||
<span class="hidden sm:inline">Anmelden</span>
|
||||
</a>
|
||||
|
||||
{{-- Veröffentlichen --}}
|
||||
<a href="{{ route('veroeffentlichen') }}"
|
||||
class="inline-flex items-center gap-1.5 sm:gap-2 px-3 sm:px-4 lg:px-[18px] py-2 lg:py-2.5 text-[12.5px] sm:text-[13px] font-semibold text-white bg-brand cursor-pointer hover:bg-brand-deep rounded-[2px] transition-colors whitespace-nowrap"
|
||||
aria-label="Pressemitteilung veröffentlichen">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" class="sm:hidden" aria-hidden="true">
|
||||
<path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" />
|
||||
</svg>
|
||||
<span class="sm:hidden">PM</span>
|
||||
<span class="hidden sm:inline">Veröffentlichen</span>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" class="hidden sm:inline" aria-hidden="true">
|
||||
<path d="M3 6h6M6 3l3 3-3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{-- Such-Overlay --}}
|
||||
<div x-show="searchOpen"
|
||||
x-transition.opacity.duration.150ms
|
||||
class="fixed inset-0 z-50 bg-ink/70 backdrop-blur-sm"
|
||||
@click.self="searchOpen = false"
|
||||
style="display: none;">
|
||||
<div x-show="searchOpen"
|
||||
x-transition.duration.200ms
|
||||
class="bg-bg border-b border-bg-rule shadow-xl">
|
||||
<div class="max-w-layout mx-auto px-4 sm:px-6 py-6">
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<div class="eyebrow mb-1">Suche</div>
|
||||
<h2 class="font-serif text-[22px] font-semibold text-ink">Pressemitteilungen durchsuchen</h2>
|
||||
</div>
|
||||
<button type="button"
|
||||
@click="searchOpen = false"
|
||||
class="inline-flex items-center justify-center w-10 h-10 text-ink rounded-[2px] cursor-pointer hover:bg-bg-elev transition-colors"
|
||||
aria-label="Suche schließen">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M6 6l12 12M6 18L18 6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form action="{{ route('suche') }}" method="get">
|
||||
<label class="sr-only" for="site-search-mobile">Pressemitteilungen durchsuchen</label>
|
||||
<div class="flex items-center gap-2.5 px-4 py-3.5 border border-bg-rule-strong bg-bg-elev text-[15px] text-ink-3 rounded-[2px]">
|
||||
<svg width="20" height="20" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<circle cx="7" cy="7" r="5" stroke="currentColor" stroke-width="1.5" />
|
||||
<path d="M11 11l3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
||||
</svg>
|
||||
<input id="site-search-mobile" type="search" name="q"
|
||||
placeholder="Pressemitteilungen, Unternehmen, Branchen, ISIN…"
|
||||
x-ref="searchInput"
|
||||
x-init="$watch('searchOpen', value => value && setTimeout(() => $refs.searchInput.focus(), 50))"
|
||||
class="flex-1 bg-transparent border-0 p-0 text-[15px] text-ink placeholder:text-ink-3 focus:outline-none focus:ring-0 min-w-0">
|
||||
<button type="submit"
|
||||
class="px-3 py-1.5 text-[12px] font-semibold bg-brand text-white rounded-[2px] cursor-pointer hover:bg-brand-deep transition-colors">
|
||||
Suchen
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-3 flex justify-between text-[11.5px] text-ink-3">
|
||||
<a href="{{ route('suche') }}" class="inline-flex items-center gap-1.5 cursor-pointer hover:text-ink transition-colors underline underline-offset-2 decoration-[#1C1A17]/40">
|
||||
<svg width="10" height="10" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<path d="M2 3h8M3.5 6h5M5 9h2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" />
|
||||
</svg>
|
||||
Erweiterte Suche
|
||||
</a>
|
||||
<span class="hidden sm:inline-flex items-center gap-1.5">
|
||||
Drücken Sie
|
||||
<kbd class="font-mono text-[10.5px] text-ink-4 border border-bg-rule px-1.5 py-0.5 rounded-[2px]">Esc</kbd>
|
||||
zum Schließen
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Main Navigation (Underline-Tabs Desktop, Burger Mobile) --}}
|
||||
<nav class="bg-bg border-b border-bg-rule relative"
|
||||
x-data="{ menuOpen: false }"
|
||||
@keydown.escape.window="menuOpen = false"
|
||||
@click.outside="menuOpen = false"
|
||||
aria-label="Hauptnavigation">
|
||||
|
||||
{{-- Desktop: horizontale Tabs --}}
|
||||
<div class="hidden lg:flex max-w-layout mx-auto px-8 items-stretch">
|
||||
@foreach ($navigation as $item)
|
||||
<a href="{{ $item['href'] }}"
|
||||
class="px-4 py-3.5 text-[13.5px] whitespace-nowrap border-b-2 -mb-px cursor-pointer transition-colors
|
||||
@if ($item['active'] ?? false) font-semibold text-ink border-brand
|
||||
@else font-medium text-ink-2 border-transparent hover:text-ink hover:border-bg-rule-strong/30 @endif">
|
||||
{{ $item['label'] }}
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
{{-- Mobile: aktive Kategorie + Burger --}}
|
||||
<div class="lg:hidden max-w-layout mx-auto px-4 sm:px-6">
|
||||
<button type="button"
|
||||
@click="menuOpen = !menuOpen"
|
||||
class="flex items-center justify-between w-full py-3 cursor-pointer group"
|
||||
:aria-expanded="menuOpen.toString()"
|
||||
aria-controls="mobile-nav-menu"
|
||||
aria-label="Hauptmenü öffnen">
|
||||
<span class="flex items-center gap-3">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" class="text-ink"
|
||||
:class="{ 'hidden': menuOpen }" aria-hidden="true">
|
||||
<path d="M3 6h18M3 12h18M3 18h18" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||||
</svg>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" class="text-ink hidden"
|
||||
:class="{ 'hidden': !menuOpen, '!block': menuOpen }" aria-hidden="true">
|
||||
<path d="M6 6l12 12M6 18L18 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||||
</svg>
|
||||
<span class="text-[10px] font-bold tracking-[0.18em] uppercase text-ink-3 group-hover:text-ink transition-colors">Rubrik</span>
|
||||
<span class="font-serif text-[15px] font-semibold text-ink leading-none">{{ $activeLabel }}</span>
|
||||
</span>
|
||||
<svg width="14" height="14" viewBox="0 0 12 12" fill="none" class="text-ink-3 transition-transform"
|
||||
:class="{ 'rotate-180': menuOpen }" aria-hidden="true">
|
||||
<path d="M2 4l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- Mobile Dropdown --}}
|
||||
<div id="mobile-nav-menu"
|
||||
x-show="menuOpen"
|
||||
x-transition.origin.top
|
||||
x-cloak
|
||||
class="lg:hidden border-t border-bg-rule bg-bg-elev absolute left-0 right-0 z-40 shadow-lg"
|
||||
style="display: none;">
|
||||
<div class="max-w-layout mx-auto px-4 sm:px-6 py-2">
|
||||
@foreach ($navigation as $item)
|
||||
<a href="{{ $item['href'] }}"
|
||||
class="flex items-center justify-between px-3 py-3 text-[14px] border-b border-bg-rule last:border-b-0 cursor-pointer transition-colors
|
||||
@if ($item['active'] ?? false) font-semibold text-brand bg-brand/[0.04]
|
||||
@else font-medium text-ink-2 hover:text-ink hover:bg-bg @endif">
|
||||
<span>{{ $item['label'] }}</span>
|
||||
@if ($item['active'] ?? false)
|
||||
<svg width="14" height="14" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<path d="M2 6l3 3 5-6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
@else
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" class="text-ink-3" aria-hidden="true">
|
||||
<path d="M4 2l4 4-4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<x-web.live-ticker :items="$ticker" :market-index="$marketIndex" :aside-slides="$asideSlides" />
|
||||
</header>
|
||||
Loading…
Add table
Add a link
Reference in a new issue