legal/web: Zustimmungsgate je Dokumentversion (aus bis AGB 0.7), OG-Variante mit eingebranntem Bildnachweis, Nachweis in Teasern (Bildkennzeichnung Blöcke G und H)
- legal_document_acceptances (insert-only), LegalAcceptanceGate, Pflicht-Schalter im Einreichungs-Modal über den gemeinsamen Trait; API: GET/POST /api/v1/legal/acceptances, submit antwortet 428 mit den geforderten Fassungen; LEGAL_ACCEPTANCE_GATE - ImageService: og-Variante (1200×630) mit halbtransparentem Balken und Inter SemiBold, regenerateOgVariant() für Korrekturen und Bestand; Fokuspunkt/Letterbox getestet - Uploader brennt den Nachweis beim Upload ein, ImageCorrectionService baut die OG-Variante nach einer Korrektur neu; Artikelseite nutzt og für og:image - Teaser: Nachweis in alt/title (Feed, Hero, Kategorie, Newsroom), Unterschrift am Hero-Bild; PressRelease::cardImageCredit() Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
0240c143c8
commit
62723b551a
27 changed files with 866 additions and 15 deletions
|
|
@ -33,6 +33,11 @@
|
|||
{{-- Blockform, nicht Inline-Form: die Inline-Variante würde bis zum Ende des nächsten Blocks als Rohblock gelesen (BladeCompiler::storePhpBlocks, Guard: BladePhpBlockGuardTest). --}}
|
||||
@php
|
||||
$bookingRequired = ! (auth()->user()?->hasActiveBooking() ?? true);
|
||||
// Zustimmungsgate (Bildkennzeichnung Block G): offene Fassungen von AGB
|
||||
// und Richtlinien, die vor dem Einreichen aktiv angenommen werden müssen.
|
||||
$legalGate = app(\App\Services\Legal\LegalAcceptanceGate::class);
|
||||
$legalDocumentsToAccept = $legalGate->requiredFor(auth()->user())->map(fn ($document) => $legalGate->describe($document))->values();
|
||||
$needsLegalAcceptance = $legalDocumentsToAccept->isNotEmpty();
|
||||
@endphp
|
||||
|
||||
<flux:modal :name="$name" class="w-full max-w-xl">
|
||||
|
|
@ -72,7 +77,7 @@
|
|||
@else
|
||||
{{-- Ohne eigenes Titelbild startet `images` auf true: die Checkbox ist
|
||||
ausgeblendet und darf den Bestätigen-Button nicht blockieren. --}}
|
||||
<div class="space-y-5" x-data="{ agb: false, images: {{ $hasTitleImage ? 'false' : 'true' }}, contact: false, waiver: false, ai: false }">
|
||||
<div class="space-y-5" x-data="{ agb: false, images: {{ $hasTitleImage ? 'false' : 'true' }}, contact: false, waiver: false, ai: false, legal: {{ $needsLegalAcceptance ? 'false' : 'true' }} }">
|
||||
<div>
|
||||
<flux:text class="eyebrow muted">{{ __('Veröffentlichung') }}</flux:text>
|
||||
<flux:heading size="lg">{{ __('Pressemitteilung zur Prüfung freigeben') }}</flux:heading>
|
||||
|
|
@ -130,6 +135,28 @@
|
|||
</article>
|
||||
@endif
|
||||
|
||||
@if ($needsLegalAcceptance)
|
||||
{{-- Zustimmungsgate (Block G): aktiver Klick je Fassung, kein
|
||||
„Weiternutzung gilt als Zustimmung". Wire-gebunden, weil die
|
||||
Annahme protokolliert wird (legal_document_acceptances). --}}
|
||||
<article class="panel overflow-hidden" data-legal-acceptance>
|
||||
<div class="panel-head" style="padding:10px 16px;">
|
||||
<span class="section-eyebrow">{{ __('Neue Fassung unserer Bedingungen') }} <span class="text-[color:var(--color-err)]">*</span></span>
|
||||
</div>
|
||||
<div class="p-4 space-y-3 text-[12.5px] leading-[1.6] text-[color:var(--color-ink-2)]">
|
||||
<p class="m-0">{{ __('Vor dem Einreichen nehmen Sie bitte die aktuelle Fassung an:') }}</p>
|
||||
<ul class="m-0 list-disc space-y-1 ps-5">
|
||||
@foreach ($legalDocumentsToAccept as $legalDocument)
|
||||
<li><a href="{{ $legalDocument['url'] }}" target="_blank" rel="noopener" class="underline underline-offset-2">{{ $legalDocument['label'] }}</a> — {{ __('Version :version', ['version' => $legalDocument['version']]) }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
<flux:switch align="left" wire:model="legalAcceptanceConfirmed" x-on:change="legal = $event.target.checked"
|
||||
:label="__('Ich habe die genannten Fassungen gelesen und nehme sie an.')" />
|
||||
<flux:error name="legalAcceptanceConfirmed" />
|
||||
</div>
|
||||
</article>
|
||||
@endif
|
||||
|
||||
{{-- KI-Kennzeichnung (Art. 50 KI-VO, Decision-Update 08.07.2026):
|
||||
Pflicht-Selbstdeklaration in jedem Veröffentlichungsprozess.
|
||||
Die Eltern-Komponente stellt die Properties über den Trait
|
||||
|
|
@ -223,7 +250,7 @@
|
|||
</flux:modal.close>
|
||||
<flux:button variant="primary" wire:click="{{ $action }}"
|
||||
wire:loading.attr="disabled"
|
||||
x-bind:disabled="! (agb && images && contact && waiver && ai)">
|
||||
x-bind:disabled="! (agb && images && contact && waiver && ai && legal)">
|
||||
{{ $confirmLabel ?? __('Zur Prüfung freigeben') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
$teaser = $release?->teaserText(180) ?? '';
|
||||
$company = $release?->company?->name;
|
||||
$title = $release->title;
|
||||
$imageCredit = $imageUrl ? $release?->cardImageCredit() : null;
|
||||
$href = route('release.detail', ['slug' => $release->slug]);
|
||||
$time = $release?->compactPublishedLabel();
|
||||
$city = $release?->company?->city ?? null;
|
||||
|
|
@ -18,7 +19,7 @@
|
|||
<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">
|
||||
<img src="{{ $imageUrl }}" alt="{{ $imageCredit ? $title.' · '.$imageCredit : $title }}" @if ($imageCredit) title="{{ $imageCredit }}" @endif class="absolute inset-0 h-full w-full object-cover transition-transform group-hover:scale-105" loading="lazy">
|
||||
@elseif ($placeholderUrl)
|
||||
<img src="{{ $placeholderUrl }}" alt="" aria-hidden="true" class="absolute inset-0 h-full w-full object-cover transition-transform group-hover:scale-105" loading="lazy">
|
||||
@else
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
$leadHref = route('release.detail', ['slug' => $leadRelease->slug]);
|
||||
$leadReadTime = $leadRelease->readTimeMinutes();
|
||||
$leadPublishedAt = $leadRelease->published_at;
|
||||
$leadImageCredit = $leadImageUrl ? $leadRelease->cardImageCredit() : null;
|
||||
}
|
||||
@endphp
|
||||
|
||||
|
|
@ -37,10 +38,13 @@
|
|||
<a href="{{ $leadHref }}" class="block group cursor-pointer">
|
||||
<div class="relative overflow-hidden bg-hero-grad min-h-[320px] sm:min-h-[420px] lg:min-h-[500px] flex flex-col justify-end">
|
||||
@if ($leadImageUrl)
|
||||
<img src="{{ $leadImageUrl }}" alt="{{ $leadTitle }}"
|
||||
<img src="{{ $leadImageUrl }}" alt="{{ $leadImageCredit ? $leadTitle.' · '.$leadImageCredit : $leadTitle }}" @if ($leadImageCredit) title="{{ $leadImageCredit }}" @endif
|
||||
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>
|
||||
@if ($leadImageCredit)
|
||||
<span class="absolute top-2 right-3 z-10 text-[10.5px] text-white/85 bg-black/35 px-1.5 py-0.5" data-image-credit-teaser>{{ $leadImageCredit }}</span>
|
||||
@endif
|
||||
@elseif ($leadPlaceholderUrl)
|
||||
<img src="{{ $leadPlaceholderUrl }}" alt="" aria-hidden="true"
|
||||
class="absolute inset-0 h-full w-full object-cover" loading="eager">
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
$company = $release?->company?->name;
|
||||
$href = route('release.detail', ['slug' => $release->slug]);
|
||||
$imageUrl = $release?->cardImageUrl();
|
||||
$imageCredit = $imageUrl ? $release?->cardImageCredit() : null;
|
||||
$placeholderUrl = $imageUrl ? null : $release?->chosenPlaceholderUrl();
|
||||
$gradient = 'linear-gradient(135deg,#3A4A5A,#6B7C95)';
|
||||
$number = str_pad((string) $index, 2, '0', STR_PAD_LEFT);
|
||||
|
|
@ -30,7 +31,7 @@
|
|||
|
||||
<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">
|
||||
<img src="{{ $imageUrl }}" alt="" @if ($imageCredit) title="{{ $imageCredit }}" @endif class="absolute inset-0 h-full w-full object-cover transition-transform group-hover:scale-105" loading="lazy">
|
||||
@elseif ($placeholderUrl)
|
||||
<img src="{{ $placeholderUrl }}" alt="" aria-hidden="true" class="absolute inset-0 h-full w-full object-cover transition-transform group-hover:scale-105" loading="lazy">
|
||||
@else
|
||||
|
|
|
|||
|
|
@ -310,6 +310,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
// zuerst prüfen, damit die Fehler direkt im Submit-Modal erscheinen.
|
||||
if ($submitStatus === 'review') {
|
||||
$this->validateAiDeclaration();
|
||||
$this->persistLegalAcceptances();
|
||||
}
|
||||
|
||||
$pr = $this->persistDraft();
|
||||
|
|
|
|||
|
|
@ -300,6 +300,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
|
|||
// zuerst prüfen, damit die Fehler direkt im Submit-Modal erscheinen.
|
||||
if ($submitAfterSave) {
|
||||
$this->validateAiDeclaration();
|
||||
$this->persistLegalAcceptances();
|
||||
}
|
||||
|
||||
$result = $this->persistEdit();
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ new #[Layout('components.layouts.app'), Title('Meine Pressemitteilungen')] class
|
|||
// KI-Kennzeichnung (Art. 50 KI-VO): Pflicht-Deklaration aus dem
|
||||
// Submit-Modal validieren und vor dem Einreichen persistieren.
|
||||
$this->validateAiDeclaration();
|
||||
$this->persistLegalAcceptances();
|
||||
$pr->update($this->aiDeclarationAttributes());
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
// KI-Kennzeichnung (Art. 50 KI-VO): Pflicht-Deklaration aus dem
|
||||
// Submit-Modal validieren und vor dem Einreichen persistieren.
|
||||
$this->validateAiDeclaration();
|
||||
$this->persistLegalAcceptances();
|
||||
$pr->update($this->aiDeclarationAttributes());
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -171,7 +171,13 @@
|
|||
Text im Flow (min-h + flex), damit lange Titel auf Mobile nicht geclippt werden. --}}
|
||||
<div class="relative overflow-hidden bg-hero-grad min-h-[240px] sm:min-h-[300px] lg:min-h-[360px] flex flex-col justify-end">
|
||||
@if ($leadImageUrl)
|
||||
<img src="{{ $leadImageUrl }}" alt="{{ $leadRelease->title }}" class="absolute inset-0 w-full h-full object-cover" loading="lazy">
|
||||
@php
|
||||
$leadImageCredit = $leadRelease->cardImageCredit();
|
||||
@endphp
|
||||
<img src="{{ $leadImageUrl }}" alt="{{ $leadImageCredit ? $leadRelease->title.' · '.$leadImageCredit : $leadRelease->title }}" @if ($leadImageCredit) title="{{ $leadImageCredit }}" @endif class="absolute inset-0 w-full h-full object-cover" loading="lazy">
|
||||
@if ($leadImageCredit)
|
||||
<span class="absolute top-2 right-3 z-10 text-[10.5px] text-white/85 bg-black/35 px-1.5 py-0.5" data-image-credit-teaser>{{ $leadImageCredit }}</span>
|
||||
@endif
|
||||
@elseif ($leadPlaceholderUrl)
|
||||
<img src="{{ $leadPlaceholderUrl }}" alt="" aria-hidden="true" class="absolute inset-0 w-full h-full object-cover" loading="lazy">
|
||||
@else
|
||||
|
|
|
|||
|
|
@ -192,7 +192,13 @@
|
|||
Text im Flow (min-h + flex), damit lange Titel auf Mobile nicht geclippt werden. --}}
|
||||
<div class="relative overflow-hidden bg-hero-grad min-h-[220px] sm:min-h-[280px] lg:min-h-[340px] flex flex-col justify-end">
|
||||
@if ($leadImageUrl)
|
||||
<img src="{{ $leadImageUrl }}" alt="{{ $leadRelease->title }}" class="absolute inset-0 w-full h-full object-cover" loading="lazy">
|
||||
@php
|
||||
$leadImageCredit = $leadRelease->cardImageCredit();
|
||||
@endphp
|
||||
<img src="{{ $leadImageUrl }}" alt="{{ $leadImageCredit ? $leadRelease->title.' · '.$leadImageCredit : $leadRelease->title }}" @if ($leadImageCredit) title="{{ $leadImageCredit }}" @endif class="absolute inset-0 w-full h-full object-cover" loading="lazy">
|
||||
@if ($leadImageCredit)
|
||||
<span class="absolute top-2 right-3 z-10 text-[10.5px] text-white/85 bg-black/35 px-1.5 py-0.5" data-image-credit-teaser>{{ $leadImageCredit }}</span>
|
||||
@endif
|
||||
@elseif ($leadPlaceholderUrl)
|
||||
<img src="{{ $leadPlaceholderUrl }}" alt="" aria-hidden="true" class="absolute inset-0 w-full h-full object-cover" loading="lazy">
|
||||
@else
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@
|
|||
@section('meta_description', filled($lead) ? $lead : $teaser)
|
||||
@section('og_type', 'article')
|
||||
@if ($imageUrl)
|
||||
@section('og_image', $imageUrl)
|
||||
@section('og_image', $image?->variantUrl('og', $signMedia) ?? $imageUrl)
|
||||
@endif
|
||||
@if ($isPreview)
|
||||
@section('robots', 'noindex,nofollow')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue