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

@ -48,6 +48,8 @@ class LlmsTxtController extends Controller
'- [Was eine gute Pressemitteilung ausmacht]('.$base.'/de/gute-pressemitteilung): Anlass, Aufbau, Sprache, Prüfablauf',
'- [FAQ]('.$base.'/de/faq)',
'- [Richtlinien]('.$base.'/de/richtlinien): Was veröffentlicht wird und was nicht',
'- [Was kostet eine Pressemitteilung?]('.$base.'/de/was-kostet-eine-pressemitteilung): Abrechnungsmodelle, Prüfliste für Folgekosten, Rechenbeispiel',
'- [Pressemitteilung löschen oder ändern lassen]('.$base.'/de/pressemitteilung-loeschen-oder-aendern-lassen)',
'- [API]('.$base.'/de/api): Einlieferung per REST-API; OpenAPI-Spezifikation unter '.$base.'/docs/api/v1',
'',
'## Portale',
@ -106,6 +108,9 @@ class LlmsTxtController extends Controller
'',
'- [Veröffentlichen]('.$base.'/de/veroeffentlichen): Wie eine Meldung auf das Portal kommt',
'- [Preise]('.$base.'/de/preise)',
'- [Was kostet eine Pressemitteilung?]('.$base.'/de/was-kostet-eine-pressemitteilung): Abrechnungsmodelle, Prüfliste für Folgekosten, Rechenbeispiel',
'- [Pressemitteilung veröffentlichen ohne Abo]('.$base.'/de/pressemitteilung-veroeffentlichen-ohne-abo)',
'- [Pressemitteilung löschen oder ändern lassen]('.$base.'/de/pressemitteilung-loeschen-oder-aendern-lassen): Korrektur, Änderung, DSGVO, Depublikation',
'- [Was eine gute Pressemitteilung ausmacht]('.$base.'/de/gute-pressemitteilung)',
'- [Hilfe]('.$base.'/de/hilfe)',
'- [API]('.$base.'/de/api): Einlieferung per REST-API über den Publisher-Bereich '.$hub,

View file

@ -26,12 +26,14 @@ class SitemapController extends Controller
/** @var list<string> */
private const HUB_PAGES = [
'faq', 'hilfe', 'gute-pressemitteilung', 'api', 'kuendigung',
'was-kostet-eine-pressemitteilung', 'pressemitteilung-veroeffentlichen-ohne-abo', 'pressemitteilung-loeschen-oder-aendern-lassen',
'impressum', 'datenschutz', 'agb', 'richtlinien', 'melden',
];
/** @var list<string> */
private const PORTAL_PAGES = [
'preise', 'veroeffentlichen', 'newsrooms', 'faq', 'hilfe', 'gute-pressemitteilung', 'api',
'was-kostet-eine-pressemitteilung', 'pressemitteilung-veroeffentlichen-ohne-abo', 'pressemitteilung-loeschen-oder-aendern-lassen',
'impressum', 'datenschutz', 'agb', 'richtlinien', 'melden',
];

View file

@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers\Web;
use App\Http\Controllers\Controller;
use App\Services\Web\PricingTransparencyData;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
/**
* Transparenzseiten (Roadmap 2.4): drei Seiten mit unterschiedlicher
* Suchintention, alle mit denselben Zahlen aus PricingTransparencyData.
*/
class TransparencyPageController extends Controller
{
public function cost(Request $request, PricingTransparencyData $data): View
{
return view('web.was-kostet-eine-pressemitteilung', $this->pageData($request, $data));
}
public function noSubscription(Request $request, PricingTransparencyData $data): View
{
return view('web.pressemitteilung-veroeffentlichen-ohne-abo', $this->pageData($request, $data));
}
public function changeOrDelete(Request $request, PricingTransparencyData $data): View
{
return view('web.pressemitteilung-loeschen-oder-aendern-lassen', $this->pageData($request, $data));
}
/**
* Markenname aus dem Host (wie /melden) — der Theme-Provider ist in
* Tests noch nicht auf den Request-Host gelaufen.
*
* @return array<string, mixed>
*/
private function pageData(Request $request, PricingTransparencyData $data): array
{
$host = $request->getHost();
$brandKey = str_contains($host, 'pressekonto')
? 'pressekonto'
: (str_contains($host, 'presseecho') ? 'presseecho' : 'businessportal24');
$brand = (array) config('domains.domains.'.$brandKey.'.brand', []);
return [
...$data->build(),
'brandLabel' => ($brand['name'] ?? '').($brand['accent'] ?? ''),
];
}
}

View file

@ -0,0 +1,87 @@
<?php
namespace App\Services\Web;
use App\Enums\Tier;
use App\Models\Plan;
/**
* Zahlen für die Transparenzseiten (Roadmap 2.4): alles aus config/billing,
* config/credits und dem Starter-Tarif, damit die Seiten nie einen
* anderen Preis nennen als die Preisübersicht. Das Rechenbeispiel stellt
* Einzelabrechnung und einen bei Bedarf gebuchten Starter-Monat gegenüber —
* bewusst mit Zeilen, in denen die Einzelabrechnung gewinnt.
*/
class PricingTransparencyData
{
/**
* @return array<string, mixed>
*/
public function build(): array
{
$singleCents = (int) config('billing.single_pm_price_cents');
$starter = Plan::query()->active()->where('slug', Tier::Starter->value)->first();
$starterMonthlyCents = (int) ($starter->monthly_price_cents ?? 0);
$starterQuota = (int) ($starter->press_release_quota ?? 0);
$vatRate = (float) config('billing.vat_rate');
return [
'singleCents' => $singleCents,
'singleNet' => $this->euro($singleCents),
'singleGross' => $this->euro((int) round($singleCents * (1 + $vatRate))),
'vatPercent' => (int) round($vatRate * 100),
'starterName' => $starter->name ?? 'Starter',
'starterMonthlyNet' => $this->euro($starterMonthlyCents),
'starterYearlyNet' => $this->euro((int) ($starter->yearly_price_cents ?? 0)),
'starterQuota' => $starterQuota,
'extraPmStarterNet' => $this->euro((int) config('credits.extra_pm.'.Tier::Starter->value, 0) * 100),
'changeNet' => $this->euro((int) config('billing.change_price_cents')),
'depublicationNet' => $this->euro((int) config('billing.depublication_price_cents')),
'correctionMaxPercent' => (int) config('legal.correction_max_distance_percent'),
'examples' => $starterMonthlyCents > 0 && $starterQuota > 0
? $this->examples($singleCents, $starterMonthlyCents, $starterQuota)
: [],
];
}
/**
* Rechenbeispiel: Meldungen im Jahr, Verteilung, Kosten je Modell.
* Der Starter-Monat wird nur in Monaten gebucht, in denen veröffentlicht
* wird (monatlich kündbar); je Monat höchstens das Kontingent.
*
* @return list<array{count: int, spread: string, months: int, single: string, starter: string, cheaper: string}>
*/
private function examples(int $singleCents, int $starterMonthlyCents, int $starterQuota): array
{
$cases = [
['count' => 1, 'months' => 1, 'spread' => 'one'],
['count' => $starterQuota, 'months' => 1, 'spread' => 'same_month'],
['count' => $starterQuota, 'months' => $starterQuota, 'spread' => 'spread'],
['count' => 12, 'months' => 12, 'spread' => 'monthly'],
['count' => 12, 'months' => (int) ceil(12 / $starterQuota), 'spread' => 'clustered'],
];
$rows = [];
foreach ($cases as $case) {
$single = $case['count'] * $singleCents;
$starter = $case['months'] * $starterMonthlyCents;
$rows[] = [
'count' => $case['count'],
'spread' => $case['spread'],
'months' => $case['months'],
'single' => $this->euro($single),
'starter' => $this->euro($starter),
'cheaper' => $single === $starter ? 'equal' : ($single < $starter ? 'single' : 'starter'),
];
}
return $rows;
}
private function euro(int $cents): string
{
return number_format($cents / 100, $cents % 100 === 0 ? 0 : 2, ',', '.');
}
}

View file

@ -187,7 +187,7 @@ Verbreitert den Trichter oben. Sinnvoll erst, wenn Block 1 unten geschlossen ist
| ~~2.1~~ | NewsArticle-Schema auf allen Meldungen, Organization-Schema auf Newsrooms — **Grundgerüst seit Juli (379688f), 17.09.2026 ergänzt:** gemeinsame `@id` zwischen `NewsArticle.author` und Newsroom-Organization, Publisher mit `@id` und Logo (`App\Support\StructuredData`) | M | 1b.1 |
| 2.2 | Aufnahme in Google News und Publisher Center prüfen und beantragen — **technischer Teil 17.09.2026:** News-Sitemap `/sitemap-news.xml` je Spoke (48 h, ≤ 1.000), in `robots.txt`; Publisher-Center-Anmeldung liegt beim Betreiber (Protokoll Teil 5) | M | 2.1 |
| ~~2.3~~ | Branchen- und Kategorie-Landingpages aus dem Archivbestand — **17.09.2026:** Jahresarchiv je Branche `/category/{slug}/archiv/{jahr}` (34.600 Meldungen intern verlinkt, in der Sitemap), Jahresleiste auf der Branchenseite, Beschreibungen für alle 14 Kategorien (`categories:seed-descriptions`), Portalname im Titel | M | keine |
| 2.4 | Transparenzseiten: Preisvergleich, ohne Abo veröffentlichen, ohne Mindestlaufzeit | S | keine |
| ~~2.4~~ | Transparenzseiten — **17.09.2026 nach Briefing, drei Seiten:** `was-kostet-eine-pressemitteilung` (Modellvergleich ohne Namen, Prüfliste Folgekosten, eigene Zusagen, Rechenbeispiel mit gemischtem Ausgang), `pressemitteilung-veroeffentlichen-ohne-abo`, `pressemitteilung-loeschen-oder-aendern-lassen` (Meldeverfahren + Absatz für fremde Portale); keine fremden Zahlen | S | keine |
| 2.5 | Konsolidierung der bestehenden Ratgeberseiten unter `/de/presse/` gegen 1d.1 und 2.4 prüfen | S | 1d.1 |
| 2.6 | Outreach an Betreiber von Presseportal-Listen und Vergleichsartikeln | M | 2.4 |
| ~~2.7~~ | Auffindbarkeit für KI-Systeme: strukturierte Daten, klare Entitätszuordnung, `llms.txt` — **17.09.2026:** `/llms.txt` je Host (Hub verweist auf beide Portale), Entitätszuordnung über 2.1 | M | 2.1 |

View file

@ -1660,6 +1660,67 @@ Flag. Bewusst nicht gebaut: Landingpages unterhalb der Kategorien
(„Branchen" im engeren Sinn) — dafür gibt es keine Unterkategorien und
keine Daten; die Meldungen tragen nur die 14 Hauptkategorien.
### 2.4 — Transparenzseiten (17.09.2026, nach Briefing)
**Zuschnitt aus dem Briefing:** kein Preisvergleich Anbieter gegen
Anbieter, sondern (a) Modellvergleich ohne Namen und fremde Preise und
(b) ein internes Rechenbeispiel mit den eigenen Optionen. Aus dem einen
Roadmap-Punkt wurden drei Seiten mit unterschiedlicher Suchintention,
alle auf Hub und Spokes, DE und EN:
| URL | Sucht danach |
|---|---|
| `/{edition}/was-kostet-eine-pressemitteilung` | Preis, Vergleich, Budget |
| `/{edition}/pressemitteilung-veroeffentlichen-ohne-abo` | Ablehnung eines Modells |
| `/{edition}/pressemitteilung-loeschen-oder-aendern-lassen` | akutes Problem, oft bei einem anderen Portal |
**Umsetzung:** `TransparencyPageController` + `PricingTransparencyData`
— alle Zahlen aus `config/billing`, `config/credits`, `config/legal` und
dem Starter-Tarif (19 € Einzel, 29 €/3 Meldungen Starter, 15 € Extra-PM,
10 € Änderung, 35 € Depublikation, USt.), damit die Seiten nie einen
anderen Preis nennen als die Preisübersicht. Texte in
`lang/{de,en}/static.php` (`cost`, `no_subscription`, `change_delete`).
Tabellen-Typografie für `.pm-body` ergänzt (Asset-Build nötig).
Seite 1, Aufbau wie im Briefing: kurze Antwort im ersten Bildschirm →
vier Abrechnungsmodelle und was sie bei seltener Nutzung kosten → sechs
Prüffragen für Folgekosten → „Was bei uns gilt" (dieselben sechs Fragen
beantwortet plus eine siebte: abgelehnte Meldungen kosten nichts) →
Rechenbeispiel Einzelabrechnung gegen bei Bedarf gebuchte Starter-Monate:
1 Meldung 19 vs. 29 (Einzel), 3 im selben Monat 57 vs. 29 (Starter),
3 verteilt 57 vs. 87 (Einzel), 12 monatlich 228 vs. 348 (Einzel), 12 in
4 Monaten 228 vs. 116 (Starter). Der Test `TransparencyPagesTest`
erzwingt, dass beide Modelle Zeilen gewinnen.
Seite 2: Ablauf in vier Schritten, was enthalten ist, **was nicht** (kein
Versand an Redaktionen, keine Platzierungsgarantie, keine Zusage über
Rankings oder KI-Nennungen — Formulierungsgrenze 2.7), nichts verfällt,
wann sich ein Tarif trotzdem lohnt.
Seite 3: fünf Wege als Tabelle (Anliegen / Wer / Kosten / Ergebnis),
Antragsweg über `/melden/pm` mit Bestätigungslink an die hinterlegte
Pressekontaktadresse, „Korrektur statt Löschung", und der Absatz für
fremde Portale: Impressum des Betreibers, DSA-Meldeweg und DSGVO-Anspruch
gelten überall und sind kostenlos, Dienstleister ist Ansprechpartner für
weitere Kopien — ohne Namen, ohne Zusagen über Dritte
(`docs/legal/Entscheidung.md`).
**Verlinkung:** Preisübersicht (Zeile unter dem USt-Hinweis), Hilfe-Center
(„Korrekturen nach Veröffentlichung" zeigt jetzt auf Seite 3 statt FAQ),
Sitemaps Hub + Spokes, `llms.txt`, untereinander.
**Bitte gegenlesen — drei Tatsachenbehauptungen, die ich aus Code und
Konzept abgeleitet habe:** (1) „Kein Versand an Redaktionen" (Seite 2),
(2) „Konto anlegen ohne Zahlungsdaten" (Seite 2), (3) „Kunden mit Konto
erledigen kleine Korrekturen direkt im Publisher-Bereich" (Seite 3,
Selbstbedienungskorrektur `legal.corrections_per_week`). Die „sieben
Zusagen" haben keine kanonische Liste in den Konzepten; die Fassung auf
Seite 1 ist aus Preisseite, AGB-Hinweisen und Decision-Update
Preisstruktur zusammengesetzt.
Tests: `TransparencyPagesTest` (9). Nicht gebaut: fremde Zahlen, auch
nicht als datierte Erhebung (Empfehlung im Briefing: lassen).
### Befund zu 2.5 / E5
Die alte Ratgeber-Strecke `/de/presse/*.html` leitet seit dem Rückbau
@ -1671,8 +1732,9 @@ Empfehlung: ja für Slugs, die erkennbar das Schreiben betreffen, sonst
FAQ. Braucht die Liste der Alt-Slugs aus dem Webserver-Log oder der
Search Console; ohne die bleibt es bei FAQ.
**Deploy (2.1/2.2/2.3/2.7):** kein `migrate`, `view:cache`/`route:cache`
wie üblich, dann `categories:seed-descriptions`. Danach
**Deploy (2.1–2.4/2.7):** kein `migrate`, Asset-Build (`npm run build`,
neue Tabellen-Styles), `view:cache`/`route:cache` wie üblich, dann
`categories:seed-descriptions`. Danach
`/sitemap-news.xml`, `/llms.txt`, `/de/category/handel/archiv/2019` und
eine Meldung im Rich-Results-Test (search.google.com/test/rich-results)
prüfen.

View file

@ -104,6 +104,7 @@ return [
],
'vat_note' => 'Alle Preise sind Nettopreise und verstehen sich zzgl. der gesetzlichen Umsatzsteuer. Die Abrechnung erfolgt sicher über unseren Zahlungsdienstleister.',
'transparency_links' => 'Ausführlich: <a href=":cost_url">Was kostet eine Pressemitteilung?</a> · <a href=":no_sub_url">Veröffentlichen ohne Abo</a> · <a href=":change_url">Meldung löschen oder ändern lassen</a>',
// Render-Pflichten aus der Leistungsbeschreibung v0.2 (Slots, Credits,
// Partnerportale) — die AGB verweisen auf diese Seite als Preisquelle.

View file

@ -170,6 +170,184 @@ return [
'partners_text' => 'An dieser Stelle stellen wir künftig Partner vor, die bei Text, Bild und Verbreitung unterstützen. Bis dahin steht hier bewusst nichts.',
],
/*
| Transparenzseiten (Roadmap 2.4): Modellvergleich ohne Anbieternamen und
| fremde Preise; Rechenbeispiel nur mit eigenen Optionen. Zahlen kommen
| aus PricingTransparencyData, nie aus dem Text.
*/
'cost' => [
'meta_title' => 'Was kostet eine Pressemitteilung? Preis, Modelle, Rechenbeispiel – :brand',
'meta_description' => 'Eine Veröffentlichung kostet :price € netto — ohne Abo, ohne Mindestlaufzeit. Warum Preise am Markt so weit auseinandergehen, woran man Folgekosten erkennt, und ein Rechenbeispiel für 1 bis 12 Meldungen im Jahr.',
'eyebrow' => 'Preise · Transparenz',
'breadcrumb' => 'Was kostet eine Pressemitteilung',
'title' => 'Was kostet eine Pressemitteilung?',
'lead' => 'Die kurze Antwort, die Abrechnungsmodelle dahinter und ein Rechenbeispiel mit unseren eigenen Optionen — auch dort, wo das günstigere Modell nicht das Abo ist.',
'short_heading' => 'Die kurze Antwort',
'short_p1' => 'Eine Veröffentlichung kostet bei uns <strong>:net € netto</strong> (:gross € inkl. :vat % USt.) — auf beiden Portalen, presseecho und businessportal24, mit Inhaltsprüfung, eigenem Newsroom und dauerhafter Archivierung. Kein Abo, keine Mindestlaufzeit, kein Einzelkauf, der verfällt.',
'short_p2' => 'Wer selten veröffentlicht, zahlt pro Meldung. Wer regelmäßig veröffentlicht, bucht einen Monatstarif ab :starter € netto für :quota Meldungen im Monat — monatlich kündbar. Alle Tarife stehen in der <a href=":pricing_url">Preisübersicht</a>.',
'models_heading' => 'Warum die Angaben im Markt so weit auseinandergehen',
'models_intro' => 'Presseportale rechnen nach vier Grundmodellen ab. Der Preis je Meldung sagt allein wenig; entscheidend ist, was das Modell kostet, wenn man es selten nutzt.',
'models_col_model' => 'Modell',
'models_col_cost' => 'Was es kostet, wenn man es selten nutzt',
'model_1' => 'Monats- oder Jahresabo',
'model_1_cost' => 'läuft weiter, auch in Monaten ohne Meldung',
'model_2' => 'Guthabenpakete mit Verfallsdatum',
'model_2_cost' => 'nicht genutztes Guthaben verfällt am Stichtag',
'model_3' => 'Einzelabrechnung',
'model_3_cost' => 'nur die tatsächliche Veröffentlichung',
'model_4' => 'Kostenlos mit Auflagen',
'model_4_cost' => 'keine Gebühr, dafür Einschränkungen bei Links, Bildern oder Sichtbarkeit',
'models_note' => 'Jedes Modell hat seinen Platz: Ein Abo lohnt sich bei planbarem Volumen, die Einzelabrechnung bei unregelmäßigem. Problematisch ist nicht das Modell, sondern wenn es sich erst nach der Anmeldung zeigt.',
'check_heading' => 'Woran man Folgekosten erkennt',
'check_intro' => 'Sechs Fragen, die sich auf jedes Angebot anwenden lassen — auch auf unseres. Die Antworten gehören auf die Preisseite; stehen sie nur in den AGB, ist das bereits eine Antwort.',
'check_1' => 'Steht der Preis öffentlich, oder erst nach der Anmeldung?',
'check_2' => 'Verlängert sich der Vertrag automatisch, und mit welcher Frist?',
'check_3' => 'Verfällt ungenutztes Guthaben?',
'check_4' => 'Was kostet eine Korrektur nach der Veröffentlichung?',
'check_5' => 'Was kostet die Löschung?',
'check_6' => 'Bleibt die Meldung dauerhaft online, oder nur für die Vertragslaufzeit?',
'ours_heading' => 'Was bei uns gilt',
'ours_intro' => 'Dieselben sechs Fragen, beantwortet für presseecho und businessportal24 — und eine siebte, die wir uns selbst stellen.',
'ours_1_t' => 'Preis öffentlich',
'ours_1_d' => ':net € netto je Veröffentlichung, Tarife ab :starter € netto im Monat. Alle Preise stehen vor der Anmeldung in der <a href=":pricing_url">Preisübersicht</a>, netto zzgl. USt.',
'ours_2_t' => 'Verlängerung',
'ours_2_d' => 'Eine Einzelveröffentlichung verlängert nichts. Monatstarife verlängern sich monatlich und sind monatlich kündbar, Jahrestarife zum Ende der Laufzeit — auch ohne Anmeldung über die <a href=":cancel_url">Kündigungsseite</a>.',
'ours_3_t' => 'Guthaben',
'ours_3_d' => 'Eine gekaufte Einzelveröffentlichung verfällt nicht; sie wird erst mit der Veröffentlichung verbraucht. Das Monatskontingent eines Tarifs gilt für den jeweiligen Monat. Credits für Zusatzleistungen sind mindestens drei Jahre gültig, mit Erinnerung vor Ablauf.',
'ours_4_t' => 'Korrektur',
'ours_4_d' => 'Kleine Korrekturen — Tippfehler, Kontaktdaten, ein neuer Pressekontakt — sind kostenlos. Eine inhaltliche Änderung mit erneuter Prüfung kostet :change € netto.',
'ours_5_t' => 'Löschung',
'ours_5_d' => 'Rechtswidrige Inhalte melden und gesetzliche Rechte ausüben ist immer kostenlos. Die freiwillige Entfernung einer rechtmäßigen Meldung aus der öffentlichen Ansicht kostet :depub € netto; die URL bleibt mit einem Hinweis bestehen. Details: <a href=":change_url">Pressemitteilung löschen oder ändern lassen</a>.',
'ours_6_t' => 'Dauer',
'ours_6_d' => 'Veröffentlichte Meldungen bleiben online — unabhängig davon, ob ein Tarif noch läuft. Eine Kündigung nimmt nichts offline.',
'ours_7_t' => 'Abgelehnte Meldungen',
'ours_7_d' => 'Eine Meldung, die die Inhaltsprüfung nicht besteht, verbraucht weder Kontingent noch Einzelkauf. Bezahlt wird nur, was erscheint.',
'example_heading' => 'Rechenbeispiel: unsere eigenen Optionen im Vergleich',
'example_intro' => 'Einzelabrechnung (:net € je Meldung) gegen den :starter-Tarif (:starter_price € im Monat für bis zu :quota Meldungen), der nur in den Monaten gebucht wird, in denen veröffentlicht wird. Alle Beträge netto.',
'example_col_count' => 'Meldungen im Jahr',
'example_col_spread' => 'Verteilung',
'example_col_single' => 'Einzelabrechnung',
'example_col_starter' => ':starter-Monate',
'example_col_cheaper' => 'günstiger ist',
'spread_one' => 'eine Meldung',
'spread_same_month' => 'alle im selben Monat',
'spread_spread' => 'je eine in :months Monaten',
'spread_monthly' => 'eine je Monat',
'spread_clustered' => 'je :per in :months Monaten',
'cheaper_single' => 'Einzelabrechnung',
'cheaper_starter' => ':starter',
'cheaper_equal' => 'gleich',
'example_note' => 'Der Tarif lohnt sich, wenn mehrere Meldungen in denselben Monat fallen. Wer über das Jahr verteilt eine Meldung nach der anderen veröffentlicht, fährt mit der Einzelabrechnung günstiger — das steht hier absichtlich.',
'example_extra' => 'Über das Kontingent hinaus kostet eine zusätzliche Meldung im :starter-Tarif :extra € netto; größere Tarife senken diesen Preis. Alle Stufen in der <a href=":pricing_url">Preisübersicht</a>.',
'related_heading' => 'Weiterlesen',
'related_no_sub' => 'Pressemitteilung veröffentlichen ohne Abo',
'related_change' => 'Pressemitteilung löschen oder ändern lassen',
'related_pricing' => 'Preisübersicht mit allen Tarifen',
],
'no_subscription' => [
'meta_title' => 'Pressemitteilung veröffentlichen ohne Abo – :brand',
'meta_description' => 'Eine einzelne Pressemitteilung für :price € netto veröffentlichen — ohne Abo, ohne Mindestlaufzeit, ohne Kündigung. Was enthalten ist, was nicht, und wann sich ein Tarif trotzdem lohnt.',
'eyebrow' => 'Veröffentlichen · Ohne Abo',
'breadcrumb' => 'Ohne Abo veröffentlichen',
'title' => 'Pressemitteilung veröffentlichen ohne Abo',
'lead' => 'Eine Meldung, ein Preis, kein Vertrag. So funktioniert die Einzelveröffentlichung auf presseecho und businessportal24.',
'how_heading' => 'So läuft es',
'step_1' => 'Konto im Publisher-Bereich anlegen — kostenlos, ohne Zahlungsdaten.',
'step_2' => 'Einzelveröffentlichung für :net € netto kaufen. Einmalige Zahlung, keine Laufzeit.',
'step_3' => 'Meldung schreiben oder einfügen, Bild und Pressekontakt ergänzen, zur Prüfung freigeben.',
'step_4' => 'Nach bestandener Inhaltsprüfung erscheint die Meldung auf beiden Portalen — dauerhaft, mit stabiler URL.',
'included_heading' => 'Was enthalten ist',
'included_1' => 'Veröffentlichung auf presseecho und businessportal24, ohne Mehrkosten',
'included_2' => 'Automatisierte Inhaltsprüfung, bei Bedarf persönliche Nachprüfung',
'included_3' => 'Eigener Newsroom für Ihr Unternehmen mit allen Meldungen',
'included_4' => 'Dauerhafte Archivierung mit stabiler URL',
'included_5' => 'Kleine Korrekturen nach der Veröffentlichung kostenlos',
'included_6' => 'Strukturierte Daten (NewsArticle), Aufnahme in Sitemap und News-Sitemap',
'excluded_heading' => 'Was nicht enthalten ist',
'excluded_1' => 'Kein Versand an Redaktionen: Die Meldung wird veröffentlicht und auffindbar gemacht, nicht per E-Mail an Journalisten verteilt.',
'excluded_2' => 'Keine Platzierungsgarantie auf der Startseite — die Hervorhebung ist ein optionaler, separat ausgewiesener Zukauf.',
'excluded_3' => 'Keine Zusage über Suchmaschinen-Rankings oder Nennungen in KI-Antworten. Wir liefern die technischen Voraussetzungen, nicht das Ergebnis.',
'no_expiry_heading' => 'Nichts verfällt, nichts verlängert sich',
'no_expiry_p' => 'Der Einzelkauf wird erst mit der Veröffentlichung verbraucht. Besteht die Meldung die Inhaltsprüfung nicht, bleibt der Kauf für die überarbeitete Fassung erhalten. Es gibt keine Laufzeit, keine automatische Verlängerung und nichts zu kündigen.',
'plan_heading' => 'Wann sich ein Tarif trotzdem lohnt',
'plan_p' => 'Ab :quota Meldungen im selben Monat ist der :starter-Tarif (:starter_price € netto, monatlich kündbar) günstiger als :quota Einzelkäufe. Wer über das Jahr verteilt veröffentlicht, bleibt mit der Einzelabrechnung günstiger. Das Rechenbeispiel steht unter <a href=":cost_url">Was kostet eine Pressemitteilung</a>.',
'cta_heading' => 'Einzelveröffentlichung starten',
'cta_text' => 'Konto anlegen, Einzelveröffentlichung kaufen, Meldung einreichen. Ein Konto für beide Portale.',
'cta_button' => 'Zum Publisher-Bereich',
'cta_pricing' => 'Preisübersicht',
],
'change_delete' => [
'meta_title' => 'Pressemitteilung löschen oder ändern lassen – :brand',
'meta_description' => 'Korrektur, inhaltliche Änderung, Entfernung persönlicher Daten oder Depublikation einer veröffentlichten Pressemitteilung: Was kostenlos ist, was :change € oder :depub € kostet — und wie der Weg bei anderen Portalen aussieht.',
'eyebrow' => 'Veröffentlichte Meldungen',
'breadcrumb' => 'Löschen oder ändern',
'title' => 'Pressemitteilung löschen oder ändern lassen',
'lead' => 'Was möglich ist, was es kostet und wer es beantragen kann — für Meldungen auf presseecho und businessportal24. Und was gilt, wenn die Meldung woanders steht.',
'ways_heading' => 'Die fünf Wege',
'ways_col_concern' => 'Anliegen',
'ways_col_who' => 'Wer',
'ways_col_cost' => 'Kosten',
'ways_col_result' => 'Ergebnis',
'way_1' => 'Kleine Korrektur — Tippfehler, Kontaktdaten, veralteter Pressekontakt',
'way_1_who' => 'Auftraggeber oder hinterlegter Pressekontakt',
'way_1_cost' => 'kostenlos',
'way_1_result' => 'Text wird korrigiert, URL bleibt',
'way_2' => 'Inhaltliche Änderung — neuer oder umgeschriebener Text, Bildtausch',
'way_2_who' => 'Auftraggeber oder Pressekontakt',
'way_2_cost' => ':change € netto',
'way_2_result' => 'Neue Fassung nach erneuter Prüfung, URL bleibt',
'way_3' => 'Eigene personenbezogene Daten entfernen (DSGVO)',
'way_3_who' => 'die betroffene Person',
'way_3_cost' => 'kostenlos',
'way_3_result' => 'Name und Kontaktdaten werden geschwärzt, die Meldung bleibt online',
'way_4' => 'Meldung offline nehmen (Depublikation)',
'way_4_who' => 'Auftraggeber, Pressekontakt oder nachweislich Bevollmächtigte',
'way_4_cost' => ':depub € netto',
'way_4_result' => 'Meldung aus der öffentlichen Ansicht entfernt, URL bleibt mit Hinweis',
'way_5' => 'Rechtswidrigen Inhalt melden (DSA)',
'way_5_who' => 'jede Person',
'way_5_cost' => 'kostenlos',
'way_5_result' => 'Prüfung mit begründeter Entscheidung; Entfernung oder Sperrung bei Rechtsverstoß',
'ways_note' => 'Kostenlose Anliegen setzen wir nach Prüfung direkt um. Bei kostenpflichtigen Anliegen erhalten Sie vor der Umsetzung eine Zahlungsaufforderung — ohne Ihre Bestätigung entstehen keine Kosten.',
'how_heading' => 'So beantragen Sie es',
'how_p1' => 'Öffnen Sie <a href=":pm_url">Pressemitteilung melden oder ändern</a> und fügen Sie die URL der Meldung ein. Wir senden einen Bestätigungslink an die in der Meldung hinterlegte Pressekontaktadresse — nicht an eine frei eingegebene Adresse. So kann nur der echte Kontakt Änderungen anstoßen.',
'how_p2' => 'Ist diese Adresse nicht mehr erreichbar, prüfen wir Ihre Berechtigung anhand eines Nachweises über das Formular für die manuelle Prüfung. Rechtswidrige Inhalte und die Entfernung eigener Daten melden Sie ohne Konto über die Links direkt an der Meldung oder über die <a href=":report_url">Meldeseite</a>.',
'how_p3' => 'Kunden mit Konto erledigen kleine Korrekturen direkt im Publisher-Bereich an der Meldung.',
'why_heading' => 'Warum wir korrigieren statt löschen',
'why_p1' => 'Eine Pressemitteilung ist ein datiertes Dokument; Verweise, Verlinkungen und Suchergebnisse zeigen auf ihre URL. Deshalb bleibt die URL auch bei einer Depublikation bestehen und trägt dann nur noch einen kurzen Hinweis auf die Entfernung. Eine vollständige Löschung der URL nehmen wir nicht vor.',
'why_p2' => 'Kopien in Suchmaschinen-Caches oder Archiven Dritter können zeitweise weiter abrufbar sein — darauf haben wir keinen Zugriff. Suchmaschinen bieten für veraltete Inhalte eigene Formulare zur Aktualisierung an.',
'other_heading' => 'Die Meldung steht auf einem anderen Portal?',
'other_p1' => 'Wir können nur ändern, was auf presseecho oder businessportal24 veröffentlicht ist. Für eine Meldung auf einem anderen Portal wenden Sie sich an dessen Betreiber — die Kontaktdaten stehen im Impressum der Seite; viele Portale haben zusätzlich eine eigene Melde- oder Kontaktseite.',
'other_p2' => 'Zwei Grundlagen gelten überall in der EU: Nach dem Digital Services Act muss jeder Hosting-Anbieter einen Weg anbieten, rechtswidrige Inhalte zu melden, und die Datenschutz-Grundverordnung gibt betroffenen Personen das Recht, die Entfernung eigener personenbezogener Daten zu verlangen. Beides ist kostenlos. Für die freiwillige Entfernung einer rechtmäßigen Meldung gelten die Bedingungen des jeweiligen Portals.',
'other_p3' => 'Wurde Ihre Meldung über einen Dienstleister auf mehreren Portalen eingestellt, ist dieser Dienstleister für die anderen Kopien der richtige Ansprechpartner.',
'cta_heading' => 'Anliegen zu einer Meldung auf presseecho oder businessportal24',
'cta_button' => 'Pressemitteilung melden oder ändern',
'cta_all_ways' => 'Alle Wege im Detail',
'related_heading' => 'Weiterlesen',
'related_cost' => 'Was kostet eine Pressemitteilung',
'related_no_sub' => 'Pressemitteilung veröffentlichen ohne Abo',
],
'help' => [
'meta_title' => 'Hilfe-Center',
'meta_description' => 'Hilfe-Center: Antworten, Anleitungen und direkter Kontakt zum Support-Team.',
@ -197,6 +375,8 @@ return [
'link_seo' => 'SEO-Optimierung',
'link_corrections' => 'Korrekturen nach Veröffentlichung',
'topic_account' => 'Konto & Abrechnung',
'link_cost' => 'Was kostet eine Pressemitteilung?',
'link_no_subscription' => 'Veröffentlichen ohne Abo',
'link_change_plan' => 'Tarif wechseln',
'link_invoices' => 'Rechnungen im Konto abrufen',
'link_cancel' => 'Vertrag kündigen',

View file

@ -97,6 +97,7 @@ return [
],
'vat_note' => 'All prices are net and exclude statutory VAT. Billing is handled securely via our payment provider.',
'transparency_links' => 'In detail: <a href=":cost_url">What does a press release cost?</a> · <a href=":no_sub_url">Publish without a subscription</a> · <a href=":change_url">Have a release deleted or changed</a>',
// Rendering obligations from the service description (slots/credits/
// boost/partner portals) — the T&Cs refer to this page.

View file

@ -169,6 +169,183 @@ return [
'partners_text' => 'This is where we will introduce partners who help with text, images and distribution. Until then this space is deliberately empty.',
],
/*
| Transparency pages (Roadmap 2.4): model comparison without vendor names
| or third-party prices; worked example with our own options only.
*/
'cost' => [
'meta_title' => 'What does a press release cost? Price, models, worked example – :brand',
'meta_description' => 'One publication costs :price € net — no subscription, no minimum term. Why prices differ so widely, how to spot follow-up costs, and a worked example for 1 to 12 releases a year.',
'eyebrow' => 'Pricing · Transparency',
'breadcrumb' => 'What a press release costs',
'title' => 'What does a press release cost?',
'lead' => 'The short answer, the billing models behind it, and a worked example with our own options — including the rows where the subscription is not the cheaper choice.',
'short_heading' => 'The short answer',
'short_p1' => 'One publication costs <strong>:net € net</strong> (:gross € incl. :vat % VAT) — on both portals, presseecho and businessportal24, with content review, your own newsroom and permanent archiving. No subscription, no minimum term, no single purchase that expires.',
'short_p2' => 'If you publish rarely, you pay per release. If you publish regularly, a monthly plan starts at :starter € net for :quota releases a month — cancellable monthly. All plans are listed on the <a href=":pricing_url">pricing page</a>.',
'models_heading' => 'Why prices in the market differ so widely',
'models_intro' => 'Press portals bill in four basic models. The price per release alone says little; what matters is what the model costs when you use it rarely.',
'models_col_model' => 'Model',
'models_col_cost' => 'What it costs when used rarely',
'model_1' => 'Monthly or yearly subscription',
'model_1_cost' => 'keeps running, even in months without a release',
'model_2' => 'Credit packages with an expiry date',
'model_2_cost' => 'unused credit expires on the cut-off date',
'model_3' => 'Pay per release',
'model_3_cost' => 'only the actual publication',
'model_4' => 'Free with conditions',
'model_4_cost' => 'no fee, but restrictions on links, images or visibility',
'models_note' => 'Each model has its place: a subscription pays off with predictable volume, pay-per-release with irregular volume. The problem is not the model but a model that only shows itself after sign-up.',
'check_heading' => 'How to spot follow-up costs',
'check_intro' => 'Six questions you can apply to any offer — including ours. The answers belong on the pricing page; if they only appear in the terms, that is already an answer.',
'check_1' => 'Is the price public, or only visible after sign-up?',
'check_2' => 'Does the contract renew automatically, and with what notice period?',
'check_3' => 'Does unused credit expire?',
'check_4' => 'What does a correction after publication cost?',
'check_5' => 'What does deletion cost?',
'check_6' => 'Does the release stay online permanently, or only for the contract term?',
'ours_heading' => 'What applies here',
'ours_intro' => 'The same six questions, answered for presseecho and businessportal24 — plus a seventh we ask ourselves.',
'ours_1_t' => 'Public price',
'ours_1_d' => ':net € net per publication, plans from :starter € net a month. All prices are on the <a href=":pricing_url">pricing page</a> before sign-up, net plus VAT.',
'ours_2_t' => 'Renewal',
'ours_2_d' => 'A single publication renews nothing. Monthly plans renew monthly and can be cancelled monthly, yearly plans at the end of the term — also without logging in, via the <a href=":cancel_url">cancellation page</a>.',
'ours_3_t' => 'Credit',
'ours_3_d' => 'A purchased single publication does not expire; it is only used up when the release is published. A plan\'s monthly quota applies to that month. Credits for add-ons are valid for at least three years, with a reminder before expiry.',
'ours_4_t' => 'Corrections',
'ours_4_d' => 'Small corrections — typos, contact details, a new press contact — are free. A content change with a new review costs :change € net.',
'ours_5_t' => 'Deletion',
'ours_5_d' => 'Reporting unlawful content and exercising legal rights is always free. Voluntary removal of a lawful release from public view costs :depub € net; the URL remains with a notice. Details: <a href=":change_url">have a press release deleted or changed</a>.',
'ours_6_t' => 'Duration',
'ours_6_d' => 'Published releases stay online — whether or not a plan is still running. Cancelling takes nothing offline.',
'ours_7_t' => 'Rejected releases',
'ours_7_d' => 'A release that fails the content review uses neither quota nor a single purchase. You only pay for what appears.',
'example_heading' => 'Worked example: our own options compared',
'example_intro' => 'Pay per release (:net € each) versus the :starter plan (:starter_price € a month for up to :quota releases), booked only in months with a publication. All amounts net.',
'example_col_count' => 'Releases a year',
'example_col_spread' => 'Distribution',
'example_col_single' => 'Pay per release',
'example_col_starter' => ':starter months',
'example_col_cheaper' => 'cheaper',
'spread_one' => 'one release',
'spread_same_month' => 'all in the same month',
'spread_spread' => 'one each in :months months',
'spread_monthly' => 'one a month',
'spread_clustered' => ':per each in :months months',
'cheaper_single' => 'pay per release',
'cheaper_starter' => ':starter',
'cheaper_equal' => 'equal',
'example_note' => 'The plan pays off when several releases fall into the same month. If you publish one release after another across the year, pay-per-release is cheaper — that is here on purpose.',
'example_extra' => 'Beyond the quota, an additional release on the :starter plan costs :extra € net; larger plans lower that price. All tiers on the <a href=":pricing_url">pricing page</a>.',
'related_heading' => 'Read on',
'related_no_sub' => 'Publish a press release without a subscription',
'related_change' => 'Have a press release deleted or changed',
'related_pricing' => 'Pricing page with all plans',
],
'no_subscription' => [
'meta_title' => 'Publish a press release without a subscription – :brand',
'meta_description' => 'Publish a single press release for :price € net — no subscription, no minimum term, nothing to cancel. What is included, what is not, and when a plan still pays off.',
'eyebrow' => 'Publish · No subscription',
'breadcrumb' => 'Publish without subscription',
'title' => 'Publish a press release without a subscription',
'lead' => 'One release, one price, no contract. How single publication works on presseecho and businessportal24.',
'how_heading' => 'How it works',
'step_1' => 'Create an account in the publisher area — free, no payment details.',
'step_2' => 'Buy a single publication for :net € net. One-off payment, no term.',
'step_3' => 'Write or paste the release, add an image and a press contact, submit for review.',
'step_4' => 'After passing the content review, the release appears on both portals — permanently, with a stable URL.',
'included_heading' => 'What is included',
'included_1' => 'Publication on presseecho and businessportal24 at no extra cost',
'included_2' => 'Automated content review, with a personal check where needed',
'included_3' => 'Your own company newsroom with all releases',
'included_4' => 'Permanent archiving with a stable URL',
'included_5' => 'Small corrections after publication free of charge',
'included_6' => 'Structured data (NewsArticle), inclusion in sitemap and news sitemap',
'excluded_heading' => 'What is not included',
'excluded_1' => 'No distribution to editorial desks: the release is published and made findable, not e-mailed to journalists.',
'excluded_2' => 'No guaranteed homepage placement — highlighting is an optional, separately priced add-on.',
'excluded_3' => 'No promise about search rankings or mentions in AI answers. We provide the technical prerequisites, not the outcome.',
'no_expiry_heading' => 'Nothing expires, nothing renews',
'no_expiry_p' => 'The single purchase is only used up when the release is published. If the release fails the content review, the purchase remains for the revised version. There is no term, no automatic renewal and nothing to cancel.',
'plan_heading' => 'When a plan still pays off',
'plan_p' => 'From :quota releases in the same month, the :starter plan (:starter_price € net, cancellable monthly) is cheaper than :quota single purchases. If you publish spread across the year, pay-per-release stays cheaper. The worked example is on <a href=":cost_url">What does a press release cost</a>.',
'cta_heading' => 'Start a single publication',
'cta_text' => 'Create an account, buy a single publication, submit the release. One account for both portals.',
'cta_button' => 'To the publisher area',
'cta_pricing' => 'Pricing page',
],
'change_delete' => [
'meta_title' => 'Have a press release deleted or changed – :brand',
'meta_description' => 'Correction, content change, removal of personal data or depublication of a published press release: what is free, what costs :change € or :depub € — and what to do when the release is on another portal.',
'eyebrow' => 'Published releases',
'breadcrumb' => 'Delete or change',
'title' => 'Have a press release deleted or changed',
'lead' => 'What is possible, what it costs and who can request it — for releases on presseecho and businessportal24. And what applies when the release is elsewhere.',
'ways_heading' => 'The five ways',
'ways_col_concern' => 'Request',
'ways_col_who' => 'Who',
'ways_col_cost' => 'Cost',
'ways_col_result' => 'Result',
'way_1' => 'Small correction — typos, contact details, outdated press contact',
'way_1_who' => 'client or the press contact on file',
'way_1_cost' => 'free',
'way_1_result' => 'text corrected, URL unchanged',
'way_2' => 'Content change — new or rewritten text, image swap',
'way_2_who' => 'client or press contact',
'way_2_cost' => ':change € net',
'way_2_result' => 'new version after a fresh review, URL unchanged',
'way_3' => 'Remove your own personal data (GDPR)',
'way_3_who' => 'the person concerned',
'way_3_cost' => 'free',
'way_3_result' => 'name and contact details redacted, release stays online',
'way_4' => 'Take the release offline (depublication)',
'way_4_who' => 'client, press contact or a person with proof of authority',
'way_4_cost' => ':depub € net',
'way_4_result' => 'release removed from public view, URL remains with a notice',
'way_5' => 'Report unlawful content (DSA)',
'way_5_who' => 'anyone',
'way_5_cost' => 'free',
'way_5_result' => 'review with a reasoned decision; removal or blocking if unlawful',
'ways_note' => 'Free requests are carried out after review. For paid requests you receive a payment request before anything is done — no costs arise without your confirmation.',
'how_heading' => 'How to request it',
'how_p1' => 'Open <a href=":pm_url">report or change a press release</a> and paste the URL of the release. We send a confirmation link to the press contact address stored in the release — not to an address entered freely. That way only the genuine contact can trigger changes.',
'how_p2' => 'If that address is no longer reachable, we verify your authority with proof via the manual review form. Unlawful content and removal of your own data can be reported without an account via the links on the release itself or the <a href=":report_url">reporting page</a>.',
'how_p3' => 'Customers with an account make small corrections directly in the publisher area on the release.',
'why_heading' => 'Why we correct instead of delete',
'why_p1' => 'A press release is a dated document; references, links and search results point to its URL. That is why the URL remains even after depublication and then only carries a short notice of removal. We do not delete the URL entirely.',
'why_p2' => 'Copies in search engine caches or third-party archives may remain available for a while — we have no access to those. Search engines offer their own forms for outdated content.',
'other_heading' => 'Is the release on another portal?',
'other_p1' => 'We can only change what is published on presseecho or businessportal24. For a release on another portal, contact its operator — the contact details are in the site\'s legal notice; many portals also have a dedicated reporting or contact page.',
'other_p2' => 'Two rules apply throughout the EU: under the Digital Services Act every hosting provider must offer a way to report unlawful content, and the GDPR gives data subjects the right to request removal of their own personal data. Both are free. Voluntary removal of a lawful release is subject to the respective portal\'s terms.',
'other_p3' => 'If your release was placed on several portals by a service provider, that provider is the right contact for the other copies.',
'cta_heading' => 'A request about a release on presseecho or businessportal24',
'cta_button' => 'Report or change a press release',
'cta_all_ways' => 'All ways in detail',
'related_heading' => 'Read on',
'related_cost' => 'What does a press release cost',
'related_no_sub' => 'Publish a press release without a subscription',
],
'help' => [
'meta_title' => 'Help center',
'meta_description' => 'Help center: answers, guides and direct contact with the support team.',
@ -196,6 +373,8 @@ return [
'link_seo' => 'SEO optimisation',
'link_corrections' => 'Corrections after publication',
'topic_account' => 'Account & billing',
'link_cost' => 'What does a press release cost?',
'link_no_subscription' => 'Publish without a subscription',
'link_change_plan' => 'Change plan',
'link_invoices' => 'Access invoices in your account',
'link_cancel' => 'Cancel contract',

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

View file

@ -22,6 +22,7 @@ use App\Http\Controllers\Web\ReportFormController;
use App\Http\Controllers\Web\SitemapController;
use App\Http\Controllers\Web\SucheController;
use App\Http\Controllers\Web\TerminationController;
use App\Http\Controllers\Web\TransparencyPageController;
use App\Http\Controllers\Web\VeroeffentlichenController;
use App\Http\Middleware\SetEdition;
use App\Models\Category;
@ -161,6 +162,7 @@ $htmlRedirect = static function (string $edition, string $path) {
'preise', 'faq', 'veroeffentlichen',
'suche', 'newsrooms', 'api',
'hilfe', 'gute-pressemitteilung', 'impressum', 'datenschutz', 'agb',
'was-kostet-eine-pressemitteilung', 'pressemitteilung-veroeffentlichen-ohne-abo', 'pressemitteilung-loeschen-oder-aendern-lassen',
];
if (isset($staticAliases[$clean])) {
@ -262,6 +264,11 @@ Route::prefix('{edition}')->whereIn('edition', SetEdition::EDITIONS)->group(func
// Roadmap 1d.1: Hilfeseite ohne kommerziellen Anteil — Ziel des
// Textbausteins in der Ablehnungsmail (1d.2).
Route::view('gute-pressemitteilung', 'web.gute-pressemitteilung')->name('gute-pressemitteilung');
// Transparenzseiten (Roadmap 2.4): Preis, ohne Abo, löschen/ändern.
Route::get('was-kostet-eine-pressemitteilung', [TransparencyPageController::class, 'cost'])->name('was-kostet-eine-pressemitteilung');
Route::get('pressemitteilung-veroeffentlichen-ohne-abo', [TransparencyPageController::class, 'noSubscription'])->name('pressemitteilung-veroeffentlichen-ohne-abo');
Route::get('pressemitteilung-loeschen-oder-aendern-lassen', [TransparencyPageController::class, 'changeOrDelete'])->name('pressemitteilung-loeschen-oder-aendern-lassen');
Route::view('impressum', 'web.impressum')->name('impressum');
// Gesetzlicher Kündigungsbutton (§ 312k BGB): öffentliche Bestätigungsseite

View file

@ -0,0 +1,87 @@
<?php
use App\Models\Plan;
use App\Services\Web\PricingTransparencyData;
use Tests\TestCase;
/**
* Transparenzseiten (Roadmap 2.4): Zahlen aus der Konfiguration, kein
* Anbietername, Rechenbeispiel mit gemischtem Ausgang.
*/
beforeEach(function (): void {
config()->set('billing.single_pm_price_cents', 1900);
config()->set('billing.change_price_cents', 1000);
config()->set('billing.depublication_price_cents', 3500);
config()->set('billing.vat_rate', 0.19);
config()->set('credits.extra_pm.starter', 15);
Plan::factory()->create(['slug' => 'starter', 'name' => 'Starter', 'monthly_price_cents' => 2900, 'yearly_price_cents' => 29000, 'press_release_quota' => 3, 'is_active' => true]);
});
test('the worked example lets pay-per-release win in some rows and the plan in others', function () {
$data = app(PricingTransparencyData::class)->build();
expect($data['singleNet'])->toBe('19')
->and($data['singleGross'])->toBe('22,61')
->and($data['starterMonthlyNet'])->toBe('29')
->and(collect($data['examples'])->pluck('cheaper')->unique()->sort()->values()->all())->toBe(['single', 'starter']);
});
test('the cost page shows the short answer with the configured price and no competitor names', function () {
/** @var TestCase $this */
$response = $this->get('https://presseecho.test/de/was-kostet-eine-pressemitteilung')->assertSuccessful();
$response
->assertSee('Was kostet eine Pressemitteilung?')
->assertSee('<strong>19 € netto</strong> (22,61 € inkl. 19 % USt.)', false)
->assertSee('Woran man Folgekosten erkennt')
->assertSee('Rechenbeispiel')
->assertSee('228&nbsp;€', false)
->assertSee('10 € netto')
->assertSee('35 € netto')
->assertSee('/de/pressemitteilung-loeschen-oder-aendern-lassen', false)
->assertSee('<title>Was kostet eine Pressemitteilung? Preis, Modelle, Rechenbeispiel – presseecho</title>', false);
// Kein Anbietername, keine fremden Preise (Briefing 17.09.2026).
$this->assertStringNotContainsStringIgnoringCase('presseportal.de', (string) $response->getContent());
$this->assertStringNotContainsStringIgnoringCase('openpr', (string) $response->getContent());
});
test('the no-subscription page and the change page render on every host in both editions', function (string $url) {
/** @var TestCase $this */
$this->get($url)->assertSuccessful();
})->with([
'https://businessportal24.test/de/pressemitteilung-veroeffentlichen-ohne-abo',
'https://businessportal24.test/en/pressemitteilung-veroeffentlichen-ohne-abo',
'https://pressekonto.test/de/pressemitteilung-loeschen-oder-aendern-lassen',
'https://presseecho.test/en/pressemitteilung-loeschen-oder-aendern-lassen',
'https://pressekonto.test/en/was-kostet-eine-pressemitteilung',
]);
test('the change page names the five ways with their prices and points to the self-service entry', function () {
/** @var TestCase $this */
$this->get('https://businessportal24.test/de/pressemitteilung-loeschen-oder-aendern-lassen')
->assertSuccessful()
->assertSee('Die fünf Wege')
->assertSee('kostenlos')
->assertSee('10 € netto')
->assertSee('35 € netto')
->assertSee('/de/melden/pm', false)
->assertSee('Die Meldung steht auf einem anderen Portal?');
});
test('the pages are linked from the pricing page, the help centre and the sitemap', function () {
/** @var TestCase $this */
$this->get('https://presseecho.test/de/preise')->assertSuccessful()
->assertSee('/de/was-kostet-eine-pressemitteilung', false);
$this->get('https://presseecho.test/de/hilfe')->assertSuccessful()
->assertSee('/de/pressemitteilung-loeschen-oder-aendern-lassen', false)
->assertSee('/de/pressemitteilung-veroeffentlichen-ohne-abo', false);
$this->get('https://presseecho.test/sitemap.xml')->assertSuccessful()
->assertSee('<loc>https://presseecho.test/de/was-kostet-eine-pressemitteilung</loc>', false);
$this->get('https://pressekonto.test/sitemap.xml')->assertSuccessful()
->assertSee('<loc>https://pressekonto.test/de/pressemitteilung-loeschen-oder-aendern-lassen</loc>', false);
});