web: Transparenzseiten — was kostet, ohne Abo, loeschen/aendern (Roadmap 2.4)

Drei Seiten mit unterschiedlicher Suchintention, DE/EN, Hub + Spokes:
- /was-kostet-eine-pressemitteilung: kurze Antwort im ersten Bildschirm,
  vier Abrechnungsmodelle ohne Anbieternamen und fremde Preise, sechs
  Prueffragen fuer Folgekosten, "Was bei uns gilt" (sieben Zusagen mit
  Einloesung), Rechenbeispiel Einzelabrechnung gegen Starter-Monate mit
  Zeilen fuer beide Seiten.
- /pressemitteilung-veroeffentlichen-ohne-abo: Ablauf, enthalten / nicht
  enthalten, nichts verfaellt, wann sich ein Tarif lohnt.
- /pressemitteilung-loeschen-oder-aendern-lassen: fuenf Wege als
  Tabelle (Korrektur, Aenderung, DSGVO, Depublikation, DSA), Antragsweg
  ueber /melden/pm, Korrektur statt Loeschung, Absatz fuer Meldungen auf
  fremden Portalen ohne Zusagen ueber Dritte.

PricingTransparencyData liefert alle Zahlen aus config/billing, credits,
legal und dem Starter-Tarif; TransparencyPageController leitet die Marke
aus dem Host ab. Tabellen-Typografie fuer .pm-body. Verlinkt aus
Preisuebersicht, Hilfe-Center, Sitemaps und llms.txt.

Test TransparencyPagesTest (9) erzwingt u. a., dass im Rechenbeispiel
beide Modelle Zeilen gewinnen. Protokoll Teil 5, Roadmap 2.4 abgehakt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-17 12:00:28 +00:00
parent 4616bb7aad
commit 106edb1384
18 changed files with 953 additions and 4 deletions

View file

@ -172,6 +172,34 @@
.pm-body strong {
font-weight: 600;
}
/* Tabellen der Info-/Transparenzseiten (Roadmap 2.4). */
.pm-body > table {
width: 100%;
border-collapse: collapse;
margin: 0 0 18px;
font-size: 14.5px;
line-height: 1.5;
color: var(--color-ink);
}
.pm-body > table th,
.pm-body > table td {
padding: 10px 12px 10px 0;
border-bottom: 1px solid var(--color-bg-rule);
text-align: left;
vertical-align: top;
}
.pm-body > table thead th {
font-family: var(--font-mono);
font-size: 10.5px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--color-ink-3);
border-bottom: 1px solid var(--color-bg-rule-strong);
}
.pm-body > table tbody th {
font-weight: 600;
}
/* Button Base Styles */
.btn {

View file

@ -30,12 +30,14 @@
['label' => __('static.help.link_good_release'), 'href' => route('gute-pressemitteilung')],
['label' => __('static.help.link_file_formats'), 'href' => route('faq')],
['label' => __('static.help.link_seo'), 'href' => route('faq')],
['label' => __('static.help.link_corrections'), 'href' => route('faq')],
['label' => __('static.help.link_corrections'), 'href' => route('pressemitteilung-loeschen-oder-aendern-lassen')],
],
],
[
'title' => __('static.help.topic_account'),
'links' => [
['label' => __('static.help.link_cost'), 'href' => route('was-kostet-eine-pressemitteilung')],
['label' => __('static.help.link_no_subscription'), 'href' => route('pressemitteilung-veroeffentlichen-ohne-abo')],
['label' => __('static.help.link_change_plan'), 'href' => route('preise')],
['label' => __('static.help.link_invoices'), 'href' => $publisherUrl.'/login'],
['label' => __('static.help.link_cancel'), 'href' => route('kuendigung')],

View file

@ -278,6 +278,7 @@
@endif
<p class="text-[11.5px] text-ink-3 m-0 mt-4 max-w-[680px]">{{ __('pricing.vat_note') }}</p>
<p class="text-[11.5px] text-ink-3 m-0 mt-2 max-w-[680px] [&_a]:underline [&_a]:underline-offset-2 [&_a:hover]:text-ink">{!! __('pricing.transparency_links', ['cost_url' => route('was-kostet-eine-pressemitteilung'), 'no_sub_url' => route('pressemitteilung-veroeffentlichen-ohne-abo'), 'change_url' => route('pressemitteilung-loeschen-oder-aendern-lassen')]) !!}</p>
{{-- Render-Pflichten der Leistungsbeschreibung (Slots/Credits/Boost/
Partnerportale) — die AGB verweisen auf diese Seite. --}}

View file

@ -0,0 +1,79 @@
@extends('web.layouts.web-master')
@php
// Roadmap 2.4, Transparenzseite 3 (nicht in der Roadmap, aus dem Briefing
// 17.09.2026): das Meldeverfahren als Antwort auf ein akutes Problem.
// Anfragen zu fremden Portalen fängt der letzte Abschnitt auf — ohne
// Zusagen über Dritte (docs/legal/Entscheidung.md).
$params = [
'change' => $changeNet,
'depub' => $depublicationNet,
'pm_url' => route('melden.pm'),
'report_url' => route('melden'),
];
@endphp
@section('title', __('static.change_delete.meta_title', ['brand' => $brandLabel]))
@section('meta_description', __('static.change_delete.meta_description', $params))
@section('content')
<x-web.static-page
:eyebrow="__('static.change_delete.eyebrow')"
:title="__('static.change_delete.title')"
:lead="__('static.change_delete.lead')"
:breadcrumb="__('static.change_delete.breadcrumb')"
>
<div class="pm-body max-w-[760px]">
<h2 id="wege">{{ __('static.change_delete.ways_heading') }}</h2>
<table>
<thead>
<tr>
<th scope="col">{{ __('static.change_delete.ways_col_concern') }}</th>
<th scope="col">{{ __('static.change_delete.ways_col_who') }}</th>
<th scope="col">{{ __('static.change_delete.ways_col_cost') }}</th>
<th scope="col">{{ __('static.change_delete.ways_col_result') }}</th>
</tr>
</thead>
<tbody>
@foreach ([1, 2, 3, 4, 5] as $way)
<tr>
<th scope="row">{{ __('static.change_delete.way_'.$way) }}</th>
<td>{{ __('static.change_delete.way_'.$way.'_who') }}</td>
<td class="whitespace-nowrap">{{ __('static.change_delete.way_'.$way.'_cost', $params) }}</td>
<td>{{ __('static.change_delete.way_'.$way.'_result') }}</td>
</tr>
@endforeach
</tbody>
</table>
<p>{{ __('static.change_delete.ways_note') }}</p>
<h2 id="beantragen">{{ __('static.change_delete.how_heading') }}</h2>
<p>{!! __('static.change_delete.how_p1', $params) !!}</p>
<p>{!! __('static.change_delete.how_p2', $params) !!}</p>
<p>{{ __('static.change_delete.how_p3') }}</p>
<h2 id="warum">{{ __('static.change_delete.why_heading') }}</h2>
<p>{{ __('static.change_delete.why_p1') }}</p>
<p>{{ __('static.change_delete.why_p2') }}</p>
<h2 id="andere-portale">{{ __('static.change_delete.other_heading') }}</h2>
<p>{{ __('static.change_delete.other_p1') }}</p>
<p>{{ __('static.change_delete.other_p2') }}</p>
<p>{{ __('static.change_delete.other_p3') }}</p>
<div class="mt-10 p-6 sm:p-8 bg-bg-elev border border-bg-rule">
<h2 class="font-serif text-[20px] font-semibold m-0 mb-4 text-ink">{{ __('static.change_delete.cta_heading') }}</h2>
<div class="flex flex-wrap gap-3">
<a href="{{ route('melden.pm') }}" class="inline-flex items-center justify-center px-[18px] py-3 text-[14px] font-semibold bg-brand text-white hover:bg-brand-deep transition-colors no-underline">{{ __('static.change_delete.cta_button') }} →</a>
<a href="{{ route('melden') }}" class="inline-flex items-center justify-center px-[18px] py-3 text-[14px] font-semibold text-ink border border-bg-rule-strong bg-white hover:bg-bg-elev transition-colors no-underline">{{ __('static.change_delete.cta_all_ways') }}</a>
</div>
</div>
<h2 id="weiterlesen">{{ __('static.change_delete.related_heading') }}</h2>
<ul>
<li><a href="{{ route('was-kostet-eine-pressemitteilung') }}">{{ __('static.change_delete.related_cost') }}</a></li>
<li><a href="{{ route('pressemitteilung-veroeffentlichen-ohne-abo') }}">{{ __('static.change_delete.related_no_sub') }}</a></li>
</ul>
</div>
</x-web.static-page>
@endsection

View file

@ -0,0 +1,64 @@
@extends('web.layouts.web-master')
@php
// Roadmap 2.4, Transparenzseite 2: Einzelveröffentlichung — was enthalten
// ist, was nicht (ehrlich), wann ein Tarif trotzdem lohnt.
$publisherUrl = rtrim((string) config('domains.domain_portal_url', config('app.url')), '/');
$params = [
'net' => $singleNet,
'starter' => $starterName,
'starter_price' => $starterMonthlyNet,
'quota' => $starterQuota,
'cost_url' => route('was-kostet-eine-pressemitteilung'),
];
@endphp
@section('title', __('static.no_subscription.meta_title', ['brand' => $brandLabel]))
@section('meta_description', __('static.no_subscription.meta_description', ['price' => $singleNet]))
@section('content')
<x-web.static-page
:eyebrow="__('static.no_subscription.eyebrow')"
:title="__('static.no_subscription.title')"
:lead="__('static.no_subscription.lead')"
:breadcrumb="__('static.no_subscription.breadcrumb')"
>
<div class="pm-body max-w-[760px]">
<h2 id="ablauf">{{ __('static.no_subscription.how_heading') }}</h2>
<ol>
@foreach ([1, 2, 3, 4] as $step)
<li>{{ __('static.no_subscription.step_'.$step, $params) }}</li>
@endforeach
</ol>
<h2 id="enthalten">{{ __('static.no_subscription.included_heading') }}</h2>
<ul>
@foreach ([1, 2, 3, 4, 5, 6] as $item)
<li>{{ __('static.no_subscription.included_'.$item) }}</li>
@endforeach
</ul>
<h2 id="nicht-enthalten">{{ __('static.no_subscription.excluded_heading') }}</h2>
<ul>
@foreach ([1, 2, 3] as $item)
<li>{{ __('static.no_subscription.excluded_'.$item) }}</li>
@endforeach
</ul>
<h2 id="verfall">{{ __('static.no_subscription.no_expiry_heading') }}</h2>
<p>{{ __('static.no_subscription.no_expiry_p') }}</p>
<h2 id="tarif">{{ __('static.no_subscription.plan_heading') }}</h2>
<p>{!! __('static.no_subscription.plan_p', $params) !!}</p>
<div class="mt-10 p-6 sm:p-8 bg-ink text-white">
<h2 class="font-serif text-[22px] font-semibold m-0 mb-2 text-white">{{ __('static.no_subscription.cta_heading') }}</h2>
<p class="text-[14px] leading-[1.55] m-0 mb-5 text-white/80">{{ __('static.no_subscription.cta_text') }}</p>
<div class="flex flex-wrap gap-3">
<a href="{{ $publisherUrl }}/register" class="inline-flex items-center justify-center px-[18px] py-3 text-[14px] font-semibold bg-brand text-white hover:bg-brand-deep transition-colors no-underline">{{ __('static.no_subscription.cta_button') }} →</a>
<a href="{{ route('preise') }}" class="inline-flex items-center justify-center px-[18px] py-3 text-[14px] font-semibold text-white border border-white/30 hover:bg-white/10 transition-colors no-underline">{{ __('static.no_subscription.cta_pricing') }}</a>
</div>
</div>
</div>
</x-web.static-page>
@endsection

View file

@ -0,0 +1,114 @@
@extends('web.layouts.web-master')
@php
// Roadmap 2.4, Transparenzseite 1: Modellvergleich ohne Anbieternamen
// und fremde Preise (kein Vergleich im Sinne von § 6 UWG, weil kein
// Wettbewerber erkennbar), Prüfliste, eigene Zusagen, Rechenbeispiel nur
// mit eigenen Optionen. Alle Zahlen aus PricingTransparencyData.
$params = [
'net' => $singleNet,
'gross' => $singleGross,
'vat' => $vatPercent,
'starter' => $starterName,
'starter_price' => $starterMonthlyNet,
'quota' => $starterQuota,
'change' => $changeNet,
'depub' => $depublicationNet,
'extra' => $extraPmStarterNet,
'pricing_url' => route('preise'),
'cancel_url' => route('kuendigung'),
'change_url' => route('pressemitteilung-loeschen-oder-aendern-lassen'),
];
$spreadLabel = fn (array $row): string => __('static.cost.spread_'.$row['spread'], ['months' => $row['months'], 'per' => $starterQuota]);
$cheaperLabel = fn (array $row): string => __('static.cost.cheaper_'.$row['cheaper'], ['starter' => $starterName]);
@endphp
@section('title', __('static.cost.meta_title', ['brand' => $brandLabel]))
@section('meta_description', __('static.cost.meta_description', ['price' => $singleNet]))
@section('content')
<x-web.static-page
:eyebrow="__('static.cost.eyebrow')"
:title="__('static.cost.title')"
:lead="__('static.cost.lead')"
:breadcrumb="__('static.cost.breadcrumb')"
>
<div class="pm-body max-w-[760px]">
<h2 id="kurz">{{ __('static.cost.short_heading') }}</h2>
<p>{!! __('static.cost.short_p1', $params) !!}</p>
<p>{!! __('static.cost.short_p2', $params) !!}</p>
<h2 id="modelle">{{ __('static.cost.models_heading') }}</h2>
<p>{{ __('static.cost.models_intro') }}</p>
<table>
<thead>
<tr>
<th scope="col">{{ __('static.cost.models_col_model') }}</th>
<th scope="col">{{ __('static.cost.models_col_cost') }}</th>
</tr>
</thead>
<tbody>
@foreach ([1, 2, 3, 4] as $model)
<tr>
<th scope="row">{{ __('static.cost.model_'.$model) }}</th>
<td>{{ __('static.cost.model_'.$model.'_cost') }}</td>
</tr>
@endforeach
</tbody>
</table>
<p>{{ __('static.cost.models_note') }}</p>
<h2 id="folgekosten">{{ __('static.cost.check_heading') }}</h2>
<p>{{ __('static.cost.check_intro') }}</p>
<ol>
@foreach ([1, 2, 3, 4, 5, 6] as $question)
<li>{{ __('static.cost.check_'.$question) }}</li>
@endforeach
</ol>
<h2 id="bei-uns">{{ __('static.cost.ours_heading') }}</h2>
<p>{{ __('static.cost.ours_intro') }}</p>
<ol>
@foreach ([1, 2, 3, 4, 5, 6, 7] as $promise)
<li><strong>{{ __('static.cost.ours_'.$promise.'_t') }}</strong> — {!! __('static.cost.ours_'.$promise.'_d', $params) !!}</li>
@endforeach
</ol>
@if ($examples !== [])
<h2 id="rechenbeispiel">{{ __('static.cost.example_heading') }}</h2>
<p>{{ __('static.cost.example_intro', $params) }}</p>
<table>
<thead>
<tr>
<th scope="col">{{ __('static.cost.example_col_count') }}</th>
<th scope="col">{{ __('static.cost.example_col_spread') }}</th>
<th scope="col" class="text-right">{{ __('static.cost.example_col_single') }}</th>
<th scope="col" class="text-right">{{ __('static.cost.example_col_starter', ['starter' => $starterName]) }}</th>
<th scope="col">{{ __('static.cost.example_col_cheaper') }}</th>
</tr>
</thead>
<tbody>
@foreach ($examples as $row)
<tr>
<td class="tabular-nums">{{ $row['count'] }}</td>
<td>{{ $spreadLabel($row) }}</td>
<td class="text-right tabular-nums">{{ $row['single'] }}&nbsp;€</td>
<td class="text-right tabular-nums">{{ $row['starter'] }}&nbsp;€</td>
<td><strong>{{ $cheaperLabel($row) }}</strong></td>
</tr>
@endforeach
</tbody>
</table>
<p>{{ __('static.cost.example_note') }}</p>
<p>{!! __('static.cost.example_extra', $params) !!}</p>
@endif
<h2 id="weiterlesen">{{ __('static.cost.related_heading') }}</h2>
<ul>
<li><a href="{{ route('pressemitteilung-veroeffentlichen-ohne-abo') }}">{{ __('static.cost.related_no_sub') }}</a></li>
<li><a href="{{ route('pressemitteilung-loeschen-oder-aendern-lassen') }}">{{ __('static.cost.related_change') }}</a></li>
<li><a href="{{ route('preise') }}">{{ __('static.cost.related_pricing') }}</a></li>
</ul>
</div>
</x-web.static-page>
@endsection