WS-3: Recht & Compliance – Rechts-Kern (DSGVO/Persönlichkeitsrecht/Melden + Queue)

Launch-pflichtiger Compliance-Slice: öffentliche Anfrage zu einer PM speist eine
manuelle Admin-Queue (keine KI).

- Migration legal_requests + Model + Enums (Type: dsgvo/personal_rights/report,
  Status: open/in_progress/resolved/rejected) + Factory.
- Öffentliches Formular /release/{slug}/rechtliches (LegalRequestController +
  web/legal-request.blade.php): typ-abhängiger Hinweistext (Alpine), E-Mail bei
  DSGVO/Persönlichkeitsrecht erforderlich, zwei versteckte Honeypot-Felder,
  Rate-Limit + Bremse "1 offene Anfrage pro PM/Typ". Regeltexte als Entwurf mit
  TODO für rechtliche Finalisierung markiert.
- Routen bewusst in eigener routes/legal.php (entkoppelt vom laufenden Web-Umbau),
  host-agnostisch via domains.php eingebunden.
- Admin-Bereich "Recht & Compliance": Sidebar-Nav mit Offen-Zähler, Volt-Queue
  index/show (in Bearbeitung/erledigt/abgelehnt/wieder öffnen + interne Notiz).
- Tests: je Typ, Honeypots (Dataset), Bremse, Admin-Queue + Status-Übergänge.
- Doku: Detailplan WS-3-Status + Deployment-Migrationsreihenfolge ergänzt.

Hinweis: Der "Melden"-/E&F-Button auf der PM-Detailseite (release-detail.blade.php)
wird mit dem separaten Web-Frontend-Commit verdrahtet; Ziel ist legal-request.create.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-06-16 14:20:05 +00:00
parent 2a622044f3
commit 95007da826
16 changed files with 1139 additions and 0 deletions

View file

@ -39,6 +39,9 @@
$reviewCount = $canAdmin
? app(\App\Services\Admin\AdminPerformanceCache::class)->pressReleaseReviewCount()
: 0;
$legalOpenCount = $canAdmin
? \App\Models\LegalRequest::query()->open()->count()
: 0;
@endphp
<flux:navlist variant="outline">
@ -137,6 +140,15 @@
</flux:navlist.item>
</flux:navlist.group>
{{-- Recht & Compliance --}}
<flux:navlist.group :heading="__('Recht & Compliance')" class="grid mb-4">
<flux:navlist.item icon="scale" :href="route('admin.legal-requests.index')"
:current="request()->routeIs('admin.legal-requests.*')"
:badge="$legalOpenCount > 0 ? $legalOpenCount : null" badge-color="red" wire:navigate>
{{ __('Anfragen') }}
</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')"

View file

@ -0,0 +1,187 @@
<?php
use App\Enums\LegalRequestStatus;
use App\Enums\LegalRequestType;
use App\Models\LegalRequest;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Attributes\Url;
use Livewire\Volt\Component;
use Livewire\WithPagination;
new #[Layout('components.layouts.app'), Title('Recht & Compliance')] class extends Component
{
use WithPagination;
#[Url(as: 'status', except: 'open')]
public string $statusFilter = 'open';
#[Url(as: 'type', except: 'all')]
public string $typeFilter = 'all';
public function updatedStatusFilter(): void
{
$this->resetPage();
}
public function updatedTypeFilter(): void
{
$this->resetPage();
}
public function with(): array
{
$requests = LegalRequest::query()
->with('pressRelease:id,title,slug')
->when($this->statusFilter === 'open', fn ($query) => $query->open())
->when(! in_array($this->statusFilter, ['open', 'all'], true), fn ($query) => $query->where('status', $this->statusFilter))
->when($this->typeFilter !== 'all', fn ($query) => $query->where('type', $this->typeFilter))
->latest()
->paginate(50);
return [
'requests' => $requests,
'typeOptions' => LegalRequestType::cases(),
'openCount' => LegalRequest::query()->open()->count(),
'totalCount' => LegalRequest::query()->count(),
];
}
public function statusBadgeClass(LegalRequestStatus $status): string
{
return match ($status) {
LegalRequestStatus::Open => 'warn',
LegalRequestStatus::InProgress => 'hub',
LegalRequestStatus::Resolved => 'ok',
LegalRequestStatus::Rejected => 'err',
};
}
}; ?>
<div class="space-y-8">
{{-- ============== PAGE HEADER ============== --}}
<header class="page-header">
<div class="min-w-0">
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
<span class="eyebrow muted">{{ __('Administration · Recht & Compliance') }}</span>
</div>
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
{{ __('Recht & Compliance') }}
</h1>
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
{{ __('DSGVO-Anonymisierung, Persönlichkeitsrechte und Meldungen zu Pressemitteilungen – manuelle Bearbeitung, keine KI.') }}
</p>
</div>
</header>
{{-- ============== KPI-Reihe ============== --}}
<section class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<x-portal.stat-card variant="primary" :label="__('Offen')" :value="number_format($openCount)">
<x-slot:meta>{{ __('zu bearbeiten') }}</x-slot:meta>
<x-slot:trend>{{ __('offen + in Bearbeitung') }}</x-slot:trend>
</x-portal.stat-card>
<x-portal.stat-card variant="muted" :label="__('Gesamt')" :value="number_format($totalCount)">
<x-slot:meta>{{ __('alle Anfragen') }}</x-slot:meta>
<x-slot:trend>{{ __('inkl. erledigt') }}</x-slot:trend>
</x-portal.stat-card>
</section>
{{-- ============== FILTER ============== --}}
<article class="panel">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Filter') }}</span>
</div>
<div class="p-5 flex flex-col gap-4 sm:flex-row">
<flux:select wire:model.live="statusFilter" class="w-full sm:w-56">
<option value="open">{{ __('Offen & in Bearbeitung') }}</option>
<option value="all">{{ __('Alle Status') }}</option>
<option value="resolved">{{ __('Erledigt') }}</option>
<option value="rejected">{{ __('Abgelehnt') }}</option>
</flux:select>
<flux:select wire:model.live="typeFilter" class="w-full sm:w-56">
<option value="all">{{ __('Alle Typen') }}</option>
@foreach ($typeOptions as $typeOption)
<option value="{{ $typeOption->value }}">{{ $typeOption->label() }}</option>
@endforeach
</flux:select>
</div>
</article>
{{-- ============== TABELLE ============== --}}
<article class="panel overflow-hidden">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Anfragen') }}</span>
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
{{ __(':count Einträge', ['count' => $requests->total()]) }}
</span>
</div>
<flux:table>
<flux:table.columns>
<flux:table.column>{{ __('Typ') }}</flux:table.column>
<flux:table.column>{{ __('Status') }}</flux:table.column>
<flux:table.column>{{ __('Pressemitteilung') }}</flux:table.column>
<flux:table.column>{{ __('Absender') }}</flux:table.column>
<flux:table.column>{{ __('Eingegangen') }}</flux:table.column>
<flux:table.column>{{ __('Aktion') }}</flux:table.column>
</flux:table.columns>
<flux:table.rows>
@forelse ($requests as $request)
<flux:table.row :key="$request->id">
<flux:table.cell>
<span class="text-[13px] font-semibold text-[color:var(--color-ink)]">
{{ $request->type->label() }}
</span>
</flux:table.cell>
<flux:table.cell>
<span class="badge dot {{ $this->statusBadgeClass($request->status) }}">
{{ $request->status->label() }}
</span>
</flux:table.cell>
<flux:table.cell>
<span class="text-[12.5px] text-[color:var(--color-ink)]">
{{ \Illuminate\Support\Str::limit($request->pressRelease?->title ?? __('PM gelöscht'), 60) }}
</span>
</flux:table.cell>
<flux:table.cell>
<span class="text-[12.5px] text-[color:var(--color-ink-3)]">
{{ $request->requester_email ?: __('anonym') }}
</span>
</flux:table.cell>
<flux:table.cell>
<span class="text-[12px] text-[color:var(--color-ink-3)]">
{{ $request->created_at?->format('d.m.Y H:i') ?? '-' }}
</span>
</flux:table.cell>
<flux:table.cell>
<flux:button size="sm" variant="filled" icon="eye"
href="{{ route('admin.legal-requests.show', $request->id) }}" wire:navigate>
{{ __('Öffnen') }}
</flux:button>
</flux:table.cell>
</flux:table.row>
@empty
<flux:table.row>
<flux:table.cell colspan="6">
<div class="flex flex-col items-center justify-center px-4 py-10 text-center">
<div class="w-14 h-14 rounded-[6px] flex items-center justify-center mb-3
bg-[color:var(--color-hub-soft)] border border-[color:var(--color-hub-soft-2)] text-[color:var(--color-hub)]">
<flux:icon.scale class="size-6" />
</div>
<div class="text-[14px] font-semibold text-[color:var(--color-ink)] mb-1">
{{ __('Keine Anfragen in dieser Ansicht') }}
</div>
</div>
</flux:table.cell>
</flux:table.row>
@endforelse
</flux:table.rows>
</flux:table>
<div class="border-t border-[color:var(--color-bg-rule)] p-4">
{{ $requests->links('components.portal.pagination') }}
</div>
</article>
</div>

View file

@ -0,0 +1,201 @@
<?php
use App\Enums\LegalRequestStatus;
use App\Models\LegalRequest;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Title;
use Livewire\Volt\Component;
new #[Layout('components.layouts.app'), Title('Anfrage · Recht & Compliance')] class extends Component
{
#[Locked]
public int $id;
public string $adminNote = '';
public string $notification = '';
public function mount(int $id): void
{
$this->id = $id;
$this->adminNote = (string) ($this->request()->admin_note ?? '');
}
public function markInProgress(): void
{
$this->request()->update(['status' => LegalRequestStatus::InProgress->value]);
$this->notification = __('Status auf „In Bearbeitung" gesetzt.');
}
public function resolve(): void
{
$this->finish(LegalRequestStatus::Resolved, __('Anfrage als erledigt markiert.'));
}
public function reject(): void
{
$this->finish(LegalRequestStatus::Rejected, __('Anfrage abgelehnt.'));
}
public function reopen(): void
{
$this->request()->update([
'status' => LegalRequestStatus::Open->value,
'resolved_by_user_id' => null,
'resolved_at' => null,
]);
$this->notification = __('Anfrage wieder geöffnet.');
}
private function finish(LegalRequestStatus $status, string $message): void
{
$this->request()->update([
'status' => $status->value,
'admin_note' => $this->adminNote !== '' ? $this->adminNote : null,
'resolved_by_user_id' => auth()->id(),
'resolved_at' => now(),
]);
$this->notification = $message;
}
private function request(): LegalRequest
{
return LegalRequest::query()->with(['pressRelease', 'resolver'])->findOrFail($this->id);
}
public function with(): array
{
return ['request' => $this->request()];
}
public function statusBadgeClass(LegalRequestStatus $status): string
{
return match ($status) {
LegalRequestStatus::Open => 'warn',
LegalRequestStatus::InProgress => 'hub',
LegalRequestStatus::Resolved => 'ok',
LegalRequestStatus::Rejected => 'err',
};
}
}; ?>
<div class="space-y-8">
{{-- ============== PAGE HEADER ============== --}}
<header class="page-header">
<div class="min-w-0">
<div class="flex items-center gap-3 mb-3 flex-wrap">
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
<span class="eyebrow muted">{{ __('Recht & Compliance · Anfrage') }}</span>
<span class="badge dot {{ $this->statusBadgeClass($request->status) }}">{{ $request->status->label() }}</span>
</div>
<h1 class="text-[28px] font-bold tracking-[-0.5px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
{{ $request->type->label() }}
</h1>
</div>
<div class="flex flex-wrap items-center gap-2 flex-shrink-0">
<flux:button icon="arrow-left" variant="filled" href="{{ route('admin.legal-requests.index') }}" wire:navigate>
{{ __('Zurück') }}
</flux:button>
</div>
</header>
@if ($notification)
<div x-data="{ show: true }" x-init="setTimeout(() => show = false, 3000)" x-show="show" x-transition
class="px-4 py-3 rounded-[5px] border text-[12.5px] flex items-center gap-2
bg-[color:var(--color-ok-soft)] border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]">
<flux:icon.check-circle class="size-[16px] flex-shrink-0" />
{{ $notification }}
</div>
@endif
<div class="grid gap-6 xl:grid-cols-3">
{{-- ============== ANFRAGE ============== --}}
<article class="panel xl:col-span-2">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Anliegen') }}</span>
</div>
<div class="p-5 space-y-4">
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-4 text-[13px] text-[color:var(--color-ink)] whitespace-pre-line">
{{ $request->message ?: __('Keine Nachricht hinterlegt.') }}
</div>
<dl class="grid gap-3 sm:grid-cols-2 text-[12.5px]">
<div>
<dt class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('Absender') }}</dt>
<dd class="text-[color:var(--color-ink)] break-all">
@if ($request->requester_email)
<a href="mailto:{{ $request->requester_email }}"
class="text-[color:var(--color-hub)] underline underline-offset-2">{{ $request->requester_email }}</a>
@else
{{ __('anonym') }}
@endif
</dd>
</div>
<div>
<dt class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('Eingegangen') }}</dt>
<dd class="text-[color:var(--color-ink)]">{{ $request->created_at?->format('d.m.Y H:i') ?? '-' }}</dd>
</div>
<div class="sm:col-span-2">
<dt class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('Pressemitteilung') }}</dt>
<dd class="text-[color:var(--color-ink)]">
@if ($request->pressRelease)
{{ $request->pressRelease->title }}
@if (\Illuminate\Support\Facades\Route::has('admin.press-releases.show'))
<a href="{{ route('admin.press-releases.show', $request->pressRelease->id) }}" wire:navigate
class="ml-2 text-[12px] text-[color:var(--color-hub)] underline underline-offset-2">{{ __('öffnen') }}</a>
@endif
@else
{{ __('PM wurde gelöscht') }}
@endif
</dd>
</div>
</dl>
</div>
</article>
{{-- ============== BEARBEITUNG ============== --}}
<article class="panel">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Bearbeitung') }}</span>
</div>
<div class="p-5 space-y-4">
<flux:field>
<flux:textarea wire:model="adminNote" :label="__('Interne Notiz')" rows="4"
:placeholder="__('Wie wurde die Anfrage bearbeitet?')" />
</flux:field>
@if ($request->resolver)
<p class="text-[12px] text-[color:var(--color-ink-3)]">
{{ __('Zuletzt bearbeitet von :name am :date', [
'name' => $request->resolver->name,
'date' => $request->resolved_at?->format('d.m.Y H:i') ?? '-',
]) }}
</p>
@endif
<div class="flex flex-col gap-2 pt-2 border-t border-[color:var(--color-bg-rule)]">
@if ($request->status->isOpen())
@if ($request->status !== \App\Enums\LegalRequestStatus::InProgress)
<flux:button variant="filled" icon="clock" wire:click="markInProgress">
{{ __('In Bearbeitung nehmen') }}
</flux:button>
@endif
<flux:button variant="primary" icon="check" wire:click="resolve">
{{ __('Als erledigt markieren') }}
</flux:button>
<flux:button variant="danger" icon="x-mark" wire:click="reject">
{{ __('Ablehnen') }}
</flux:button>
@else
<flux:button variant="filled" icon="arrow-path" wire:click="reopen">
{{ __('Wieder öffnen') }}
</flux:button>
@endif
</div>
</div>
</article>
</div>
</div>

View file

@ -0,0 +1,144 @@
@extends('web.layouts.web-master')
@section('title', 'Recht & Compliance – '.$release->title)
@section('meta_description', 'Datenschutz-, Persönlichkeitsrechts- oder Meldeanfrage zu einer Pressemitteilung.')
@section('content')
{{--
TODO (Inhalt/Recht): Die untenstehenden Hinweistexte je Anfragetyp sind ein
erster ENTWURF und müssen noch fachlich/rechtlich finalisiert werden. Hier
gehört für jeden Typ konkret hinein, WIE der jeweilige Vorgang funktioniert:
- DSGVO (E): Rechtsgrundlage (Art. 17 DSGVO), welche Daten betroffen sein
können, ob/wie ein Identitätsnachweis nötig ist, Bearbeitungs-
frist (i.d.R. 30 Tage), was nach Einreichung passiert.
- Persönlichkeitsrecht (F): welche Inhalte beanstandet werden können
(unwahre Tatsachen, Bildrechte …), Nachweise, Ablauf der Prüfung,
mögliche Folgen (Anpassung/Entfernung).
- Meldung: welche Gründe sinnvoll sind (Spam, irreführend, rechtswidrig),
dass jede Meldung manuell geprüft wird, E-Mail optional.
Diese Regeln mit Recht/Legal abstimmen, bevor sie live gehen.
--}}
<section
x-data="{ type: @js(old('type', $type->value)), emailRequired() { return ['dsgvo', 'personal_rights'].includes(this.type); } }"
class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-10"
>
<a href="{{ route('release.detail', ['slug' => $release->slug]) }}"
class="text-[12px] text-ink-3 hover:text-ink transition-colors">← Zurück zur Pressemitteilung</a>
<div class="eyebrow muted text-[10px] mt-4 mb-2">Recht &amp; Compliance</div>
<h1 class="font-serif text-[26px] font-semibold tracking-[-0.3px] m-0 mb-2 text-ink">
Anfrage zu dieser Pressemitteilung
</h1>
<p class="text-[13px] text-ink-3 mb-1">
Betrifft: <span class="text-ink-2 font-medium">{{ $release->title }}</span>
</p>
<p class="text-[12.5px] text-ink-3 leading-relaxed mb-6">
Datenschutz (DSGVO), Persönlichkeitsrechte und Meldungen werden von unserem
Team manuell geprüft. Bitte wählen Sie die Art Ihrer Anfrage – dazu erscheint
ein Hinweis mit den genauen Regeln.
</p>
@if (session('legal-status'))
<div class="mb-6 px-4 py-3 border border-bg-rule bg-bg-elev text-[13px] text-ink-2" role="status">
{{ session('legal-status') }}
</div>
@endif
<form method="POST" action="{{ route('legal-request.store', ['slug' => $release->slug]) }}" class="space-y-5">
@csrf
<div>
<label for="type" class="block text-[12px] font-semibold text-ink mb-1.5">Art der Anfrage</label>
<select id="type" name="type" x-model="type"
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-brand">
@foreach ($types as $case)
<option value="{{ $case->value }}" @selected(old('type', $type->value) === $case->value)>
{{ $case->publicLabel() }}
</option>
@endforeach
</select>
@error('type')
<p class="mt-1 text-[12px] text-red-600">{{ $message }}</p>
@enderror
</div>
{{-- Hinweistext je Anfragetyp (ENTWURF – siehe TODO oben). --}}
<div x-cloak x-show="type === 'report'"
class="px-4 py-3 border border-bg-rule bg-bg-elev text-[12.5px] text-ink-2 leading-relaxed">
<strong class="text-ink">Meldung:</strong>
Melden Sie diese Pressemitteilung, wenn Sie sie für Spam, irreführend oder
rechtswidrig halten. Bitte beschreiben Sie den Grund konkret. Jede Meldung
wird manuell geprüft. Eine E-Mail-Adresse ist optional, hilft aber bei
Rückfragen.
</div>
<div x-cloak x-show="type === 'dsgvo'"
class="px-4 py-3 border border-bg-rule bg-bg-elev text-[12.5px] text-ink-2 leading-relaxed">
<strong class="text-ink">Datenschutz / DSGVO:</strong>
Sie können die Löschung oder Anonymisierung Ihrer personenbezogenen Daten
verlangen (Art. 17 DSGVO). Bitte geben Sie an, welche Angaben Sie betreffen.
Zur Rückmeldung und ggf. Identitätsprüfung benötigen wir Ihre E-Mail-Adresse.
Die Bearbeitung erfolgt in der Regel innerhalb von 30 Tagen.
</div>
<div x-cloak x-show="type === 'personal_rights'"
class="px-4 py-3 border border-bg-rule bg-bg-elev text-[12.5px] text-ink-2 leading-relaxed">
<strong class="text-ink">Persönlichkeitsrecht:</strong>
Wenn diese Mitteilung Ihre Persönlichkeitsrechte verletzt (z.&nbsp;B. unwahre
Tatsachenbehauptungen, Bildrechte), schildern Sie bitte konkret, welche
Inhalte betroffen sind. Für Rückfragen benötigen wir Ihre E-Mail-Adresse.
Bei berechtigten Ansprüchen wird der Inhalt geprüft und ggf. angepasst oder
entfernt.
</div>
<div>
<label for="requester_email" class="block text-[12px] font-semibold text-ink mb-1.5">
E-Mail-Adresse
<span class="text-ink-3 font-normal" x-show="emailRequired()">(erforderlich)</span>
<span class="text-ink-3 font-normal" x-show="!emailRequired()" x-cloak>(optional)</span>
</label>
<input id="requester_email" type="email" name="requester_email" value="{{ old('requester_email') }}"
autocomplete="email"
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-brand" />
@error('requester_email')
<p class="mt-1 text-[12px] text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="message" class="block text-[12px] font-semibold text-ink mb-1.5">Ihr Anliegen</label>
<textarea id="message" name="message" rows="6" required
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-brand"
placeholder="Bitte beschreiben Sie Ihr Anliegen.">{{ old('message') }}</textarea>
@error('message')
<p class="mt-1 text-[12px] text-red-600">{{ $message }}</p>
@enderror
</div>
{{--
Bot-Schutz: versteckte Honeypot-Felder. Für Menschen unsichtbar
(display:none via .hidden), für Bots aber im DOM. Werden sie ausgefüllt,
verwirft der Controller die Anfrage still. Die Namen ('website',
'homepage') sind bewusst „attraktiv" für Auto-Filler. Zusätzlich greift
serverseitig ein Rate-Limit pro IP/PM.
Mögliche Erweiterung später: zeitbasierte Falle (signierter Render-
Zeitstempel, Ablehnung bei Absenden < 2s) oder Captcha bei Missbrauch.
--}}
<div class="hidden" aria-hidden="true">
<label for="website">Website</label>
<input id="website" type="text" name="website" tabindex="-1" autocomplete="off" value="{{ old('website') }}" />
<label for="homepage">Homepage</label>
<input id="homepage" type="text" name="homepage" tabindex="-1" autocomplete="off" value="{{ old('homepage') }}" />
</div>
<button type="submit"
class="bg-brand hover:bg-brand-deep text-white px-5 py-2.5 text-[13px] font-semibold transition-colors">
Anfrage absenden
</button>
</form>
<p class="mt-6 text-[11.5px] text-ink-3 leading-relaxed">
Hinweis: Diese Angaben werden ausschließlich zur Bearbeitung Ihrer Anfrage
verarbeitet. Weitere Informationen in der
<a href="{{ route('datenschutz') }}" class="underline hover:text-ink">Datenschutzerklärung</a>.
</p>
</section>
@endsection