Web-Frontend: Umbau Spokes/Hub, Kontakt-Widget (contact-launcher)

Startseiten/Feeds/Kategorie/Rechtsseiten ueberarbeitet, Kontakt-Modal durch
schwebendes contact-launcher-Widget ersetzt, legal-language-notice ergaenzt;
Platzhalter-SVGs + Theme-CSS angepasst.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-09 15:45:58 +00:00
parent fd8e050cb7
commit d1939f6f2a
39 changed files with 1110 additions and 810 deletions

View file

@ -4,13 +4,13 @@
<stop offset="0" stop-color="#1a2540"/>
<stop offset="1" stop-color="#2e3d66"/>
</linearGradient>
<pattern id="waves" width="260" height="120" patternUnits="userSpaceOnUse">
<path d="M-20 70C30 30 80 30 130 70S230 110 280 70" fill="none" stroke="#ffffff" stroke-opacity="0.10" stroke-width="4"/>
<path d="M-20 110C30 70 80 70 130 110S230 150 280 110" fill="none" stroke="#ffffff" stroke-opacity="0.06" stroke-width="3"/>
</pattern>
</defs>
<rect width="1600" height="900" fill="url(#bg)"/>
<rect width="1600" height="900" fill="url(#waves)"/>
<circle cx="1270" cy="260" r="220" fill="#ffffff" fill-opacity="0.05"/>
<circle cx="360" cy="690" r="180" fill="#ffffff" fill-opacity="0.055"/>
<path d="M0 470C133 400 267 400 400 470S667 540 800 470 1067 400 1200 470 1467 540 1600 470" fill="none" stroke="#ffffff" stroke-opacity="0.11" stroke-width="4"/>
<path d="M0 550C133 480 267 480 400 550S667 620 800 550 1067 480 1200 550 1467 620 1600 550" fill="none" stroke="#ffffff" stroke-opacity="0.09" stroke-width="3.5"/>
<path d="M0 630C133 560 267 560 400 630S667 700 800 630 1067 560 1200 630 1467 700 1600 630" fill="none" stroke="#ffffff" stroke-opacity="0.07" stroke-width="3"/>
<path d="M0 710C133 640 267 640 400 710S667 780 800 710 1067 640 1200 710 1467 780 1600 710" fill="none" stroke="#ffffff" stroke-opacity="0.055" stroke-width="3"/>
<path d="M0 790C133 720 267 720 400 790S667 860 800 790 1067 720 1200 790 1467 860 1600 790" fill="none" stroke="#ffffff" stroke-opacity="0.04" stroke-width="2.5"/>
<circle cx="1270" cy="250" r="220" fill="#ffffff" fill-opacity="0.05"/>
<circle cx="340" cy="220" r="140" fill="#ffffff" fill-opacity="0.045"/>
</svg>

Before

Width:  |  Height:  |  Size: 932 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

@ -4,13 +4,13 @@
<stop offset="0" stop-color="#1f2e22"/>
<stop offset="1" stop-color="#3a5a3a"/>
</linearGradient>
<pattern id="waves" width="260" height="120" patternUnits="userSpaceOnUse">
<path d="M-20 70C30 30 80 30 130 70S230 110 280 70" fill="none" stroke="#ffffff" stroke-opacity="0.10" stroke-width="4"/>
<path d="M-20 110C30 70 80 70 130 110S230 150 280 110" fill="none" stroke="#ffffff" stroke-opacity="0.06" stroke-width="3"/>
</pattern>
</defs>
<rect width="1600" height="900" fill="url(#bg)"/>
<rect width="1600" height="900" fill="url(#waves)"/>
<circle cx="1270" cy="260" r="220" fill="#ffffff" fill-opacity="0.05"/>
<circle cx="360" cy="690" r="180" fill="#ffffff" fill-opacity="0.055"/>
<path d="M0 470C133 400 267 400 400 470S667 540 800 470 1067 400 1200 470 1467 540 1600 470" fill="none" stroke="#ffffff" stroke-opacity="0.11" stroke-width="4"/>
<path d="M0 550C133 480 267 480 400 550S667 620 800 550 1067 480 1200 550 1467 620 1600 550" fill="none" stroke="#ffffff" stroke-opacity="0.09" stroke-width="3.5"/>
<path d="M0 630C133 560 267 560 400 630S667 700 800 630 1067 560 1200 630 1467 700 1600 630" fill="none" stroke="#ffffff" stroke-opacity="0.07" stroke-width="3"/>
<path d="M0 710C133 640 267 640 400 710S667 780 800 710 1067 640 1200 710 1467 780 1600 710" fill="none" stroke="#ffffff" stroke-opacity="0.055" stroke-width="3"/>
<path d="M0 790C133 720 267 720 400 790S667 860 800 790 1067 720 1200 790 1467 860 1600 790" fill="none" stroke="#ffffff" stroke-opacity="0.04" stroke-width="2.5"/>
<circle cx="1270" cy="250" r="220" fill="#ffffff" fill-opacity="0.05"/>
<circle cx="340" cy="220" r="140" fill="#ffffff" fill-opacity="0.045"/>
</svg>

Before

Width:  |  Height:  |  Size: 932 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

@ -4,13 +4,13 @@
<stop offset="0" stop-color="#2e2117"/>
<stop offset="1" stop-color="#8a5e27"/>
</linearGradient>
<pattern id="waves" width="260" height="120" patternUnits="userSpaceOnUse">
<path d="M-20 70C30 30 80 30 130 70S230 110 280 70" fill="none" stroke="#ffffff" stroke-opacity="0.10" stroke-width="4"/>
<path d="M-20 110C30 70 80 70 130 110S230 150 280 110" fill="none" stroke="#ffffff" stroke-opacity="0.06" stroke-width="3"/>
</pattern>
</defs>
<rect width="1600" height="900" fill="url(#bg)"/>
<rect width="1600" height="900" fill="url(#waves)"/>
<circle cx="1270" cy="260" r="220" fill="#ffffff" fill-opacity="0.05"/>
<circle cx="360" cy="690" r="180" fill="#ffffff" fill-opacity="0.055"/>
<path d="M0 470C133 400 267 400 400 470S667 540 800 470 1067 400 1200 470 1467 540 1600 470" fill="none" stroke="#ffffff" stroke-opacity="0.11" stroke-width="4"/>
<path d="M0 550C133 480 267 480 400 550S667 620 800 550 1067 480 1200 550 1467 620 1600 550" fill="none" stroke="#ffffff" stroke-opacity="0.09" stroke-width="3.5"/>
<path d="M0 630C133 560 267 560 400 630S667 700 800 630 1067 560 1200 630 1467 700 1600 630" fill="none" stroke="#ffffff" stroke-opacity="0.07" stroke-width="3"/>
<path d="M0 710C133 640 267 640 400 710S667 780 800 710 1067 640 1200 710 1467 780 1600 710" fill="none" stroke="#ffffff" stroke-opacity="0.055" stroke-width="3"/>
<path d="M0 790C133 720 267 720 400 790S667 860 800 790 1067 720 1200 790 1467 860 1600 790" fill="none" stroke="#ffffff" stroke-opacity="0.04" stroke-width="2.5"/>
<circle cx="1270" cy="250" r="220" fill="#ffffff" fill-opacity="0.05"/>
<circle cx="340" cy="220" r="140" fill="#ffffff" fill-opacity="0.045"/>
</svg>

Before

Width:  |  Height:  |  Size: 932 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

@ -4,14 +4,18 @@
<stop offset="0" stop-color="#1a2540"/>
<stop offset="1" stop-color="#2e3d66"/>
</linearGradient>
<pattern id="rules" width="1600" height="56" patternUnits="userSpaceOnUse">
<line x1="0" y1="55" x2="1600" y2="55" stroke="#ffffff" stroke-opacity="0.04" stroke-width="1"/>
</pattern>
</defs>
<rect width="1600" height="900" fill="url(#bg)"/>
<rect x="170" y="190" width="520" height="42" rx="10" fill="#ffffff" fill-opacity="0.12"/>
<rect x="170" y="282" width="820" height="22" rx="8" fill="#ffffff" fill-opacity="0.08"/>
<rect x="170" y="334" width="690" height="22" rx="8" fill="#ffffff" fill-opacity="0.07"/>
<rect x="170" y="386" width="760" height="22" rx="8" fill="#ffffff" fill-opacity="0.06"/>
<rect x="1030" y="250" width="330" height="240" rx="20" fill="#ffffff" fill-opacity="0.055"/>
<path d="M1100 430L1190 340L1260 400L1305 355L1360 430H1100Z" fill="#ffffff" fill-opacity="0.10"/>
<circle cx="1290" cy="315" r="28" fill="#ffffff" fill-opacity="0.11"/>
<circle cx="1280" cy="700" r="180" fill="#ffffff" fill-opacity="0.04"/>
<rect width="1600" height="900" fill="url(#rules)"/>
<rect x="440" y="270" width="96" height="10" rx="5" fill="#ffffff" fill-opacity="0.14"/>
<rect x="440" y="320" width="620" height="40" rx="10" fill="#ffffff" fill-opacity="0.12"/>
<rect x="440" y="384" width="480" height="40" rx="10" fill="#ffffff" fill-opacity="0.10"/>
<rect x="440" y="472" width="720" height="18" rx="9" fill="#ffffff" fill-opacity="0.07"/>
<rect x="440" y="516" width="660" height="18" rx="9" fill="#ffffff" fill-opacity="0.06"/>
<rect x="440" y="560" width="540" height="18" rx="9" fill="#ffffff" fill-opacity="0.05"/>
<circle cx="1280" cy="240" r="200" fill="#ffffff" fill-opacity="0.05"/>
<circle cx="300" cy="700" r="150" fill="#ffffff" fill-opacity="0.055"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

@ -4,14 +4,18 @@
<stop offset="0" stop-color="#1f2e22"/>
<stop offset="1" stop-color="#3a5a3a"/>
</linearGradient>
<pattern id="rules" width="1600" height="56" patternUnits="userSpaceOnUse">
<line x1="0" y1="55" x2="1600" y2="55" stroke="#ffffff" stroke-opacity="0.04" stroke-width="1"/>
</pattern>
</defs>
<rect width="1600" height="900" fill="url(#bg)"/>
<rect x="170" y="190" width="520" height="42" rx="10" fill="#ffffff" fill-opacity="0.12"/>
<rect x="170" y="282" width="820" height="22" rx="8" fill="#ffffff" fill-opacity="0.08"/>
<rect x="170" y="334" width="690" height="22" rx="8" fill="#ffffff" fill-opacity="0.07"/>
<rect x="170" y="386" width="760" height="22" rx="8" fill="#ffffff" fill-opacity="0.06"/>
<rect x="1030" y="250" width="330" height="240" rx="20" fill="#ffffff" fill-opacity="0.055"/>
<path d="M1100 430L1190 340L1260 400L1305 355L1360 430H1100Z" fill="#ffffff" fill-opacity="0.10"/>
<circle cx="1290" cy="315" r="28" fill="#ffffff" fill-opacity="0.11"/>
<circle cx="1280" cy="700" r="180" fill="#ffffff" fill-opacity="0.04"/>
<rect width="1600" height="900" fill="url(#rules)"/>
<rect x="440" y="270" width="96" height="10" rx="5" fill="#ffffff" fill-opacity="0.14"/>
<rect x="440" y="320" width="620" height="40" rx="10" fill="#ffffff" fill-opacity="0.12"/>
<rect x="440" y="384" width="480" height="40" rx="10" fill="#ffffff" fill-opacity="0.10"/>
<rect x="440" y="472" width="720" height="18" rx="9" fill="#ffffff" fill-opacity="0.07"/>
<rect x="440" y="516" width="660" height="18" rx="9" fill="#ffffff" fill-opacity="0.06"/>
<rect x="440" y="560" width="540" height="18" rx="9" fill="#ffffff" fill-opacity="0.05"/>
<circle cx="1280" cy="240" r="200" fill="#ffffff" fill-opacity="0.05"/>
<circle cx="300" cy="700" r="150" fill="#ffffff" fill-opacity="0.055"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

@ -4,14 +4,18 @@
<stop offset="0" stop-color="#2e2117"/>
<stop offset="1" stop-color="#8a5e27"/>
</linearGradient>
<pattern id="rules" width="1600" height="56" patternUnits="userSpaceOnUse">
<line x1="0" y1="55" x2="1600" y2="55" stroke="#ffffff" stroke-opacity="0.04" stroke-width="1"/>
</pattern>
</defs>
<rect width="1600" height="900" fill="url(#bg)"/>
<rect x="170" y="190" width="520" height="42" rx="10" fill="#ffffff" fill-opacity="0.12"/>
<rect x="170" y="282" width="820" height="22" rx="8" fill="#ffffff" fill-opacity="0.08"/>
<rect x="170" y="334" width="690" height="22" rx="8" fill="#ffffff" fill-opacity="0.07"/>
<rect x="170" y="386" width="760" height="22" rx="8" fill="#ffffff" fill-opacity="0.06"/>
<rect x="1030" y="250" width="330" height="240" rx="20" fill="#ffffff" fill-opacity="0.055"/>
<path d="M1100 430L1190 340L1260 400L1305 355L1360 430H1100Z" fill="#ffffff" fill-opacity="0.10"/>
<circle cx="1290" cy="315" r="28" fill="#ffffff" fill-opacity="0.11"/>
<circle cx="1280" cy="700" r="180" fill="#ffffff" fill-opacity="0.04"/>
<rect width="1600" height="900" fill="url(#rules)"/>
<rect x="440" y="270" width="96" height="10" rx="5" fill="#ffffff" fill-opacity="0.14"/>
<rect x="440" y="320" width="620" height="40" rx="10" fill="#ffffff" fill-opacity="0.12"/>
<rect x="440" y="384" width="480" height="40" rx="10" fill="#ffffff" fill-opacity="0.10"/>
<rect x="440" y="472" width="720" height="18" rx="9" fill="#ffffff" fill-opacity="0.07"/>
<rect x="440" y="516" width="660" height="18" rx="9" fill="#ffffff" fill-opacity="0.06"/>
<rect x="440" y="560" width="540" height="18" rx="9" fill="#ffffff" fill-opacity="0.05"/>
<circle cx="1280" cy="240" r="200" fill="#ffffff" fill-opacity="0.05"/>
<circle cx="300" cy="700" r="150" fill="#ffffff" fill-opacity="0.055"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

@ -4,13 +4,15 @@
<stop offset="0" stop-color="#1a2540"/>
<stop offset="1" stop-color="#2e3d66"/>
</linearGradient>
<pattern id="rings" width="180" height="180" patternUnits="userSpaceOnUse">
<circle cx="90" cy="90" r="38" fill="none" stroke="#ffffff" stroke-opacity="0.09" stroke-width="3"/>
<circle cx="90" cy="90" r="4" fill="#ffffff" fill-opacity="0.13"/>
</pattern>
</defs>
<rect width="1600" height="900" fill="url(#bg)"/>
<rect width="1600" height="900" fill="url(#rings)"/>
<path d="M260 680C510 540 710 560 960 410C1140 302 1260 260 1410 288" fill="none" stroke="#ffffff" stroke-opacity="0.12" stroke-width="8" stroke-linecap="round"/>
<circle cx="960" cy="410" r="100" fill="#ffffff" fill-opacity="0.045"/>
<circle cx="1150" cy="450" r="90" fill="none" stroke="#ffffff" stroke-opacity="0.12" stroke-width="3"/>
<circle cx="1150" cy="450" r="180" fill="none" stroke="#ffffff" stroke-opacity="0.09" stroke-width="2.5"/>
<circle cx="1150" cy="450" r="280" fill="none" stroke="#ffffff" stroke-opacity="0.065" stroke-width="2"/>
<circle cx="1150" cy="450" r="390" fill="none" stroke="#ffffff" stroke-opacity="0.045" stroke-width="2"/>
<circle cx="1150" cy="450" r="510" fill="none" stroke="#ffffff" stroke-opacity="0.03" stroke-width="1.5"/>
<circle cx="1150" cy="450" r="10" fill="#ffffff" fill-opacity="0.16"/>
<circle cx="330" cy="450" r="5" fill="#ffffff" fill-opacity="0.12"/>
<line x1="345" y1="450" x2="1130" y2="450" stroke="#ffffff" stroke-opacity="0.08" stroke-width="2" stroke-dasharray="2 14" stroke-linecap="round"/>
<circle cx="320" cy="210" r="130" fill="#ffffff" fill-opacity="0.045"/>
</svg>

Before

Width:  |  Height:  |  Size: 944 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

@ -4,13 +4,15 @@
<stop offset="0" stop-color="#1f2e22"/>
<stop offset="1" stop-color="#3a5a3a"/>
</linearGradient>
<pattern id="rings" width="180" height="180" patternUnits="userSpaceOnUse">
<circle cx="90" cy="90" r="38" fill="none" stroke="#ffffff" stroke-opacity="0.09" stroke-width="3"/>
<circle cx="90" cy="90" r="4" fill="#ffffff" fill-opacity="0.13"/>
</pattern>
</defs>
<rect width="1600" height="900" fill="url(#bg)"/>
<rect width="1600" height="900" fill="url(#rings)"/>
<path d="M260 680C510 540 710 560 960 410C1140 302 1260 260 1410 288" fill="none" stroke="#ffffff" stroke-opacity="0.12" stroke-width="8" stroke-linecap="round"/>
<circle cx="960" cy="410" r="100" fill="#ffffff" fill-opacity="0.045"/>
<circle cx="1150" cy="450" r="90" fill="none" stroke="#ffffff" stroke-opacity="0.12" stroke-width="3"/>
<circle cx="1150" cy="450" r="180" fill="none" stroke="#ffffff" stroke-opacity="0.09" stroke-width="2.5"/>
<circle cx="1150" cy="450" r="280" fill="none" stroke="#ffffff" stroke-opacity="0.065" stroke-width="2"/>
<circle cx="1150" cy="450" r="390" fill="none" stroke="#ffffff" stroke-opacity="0.045" stroke-width="2"/>
<circle cx="1150" cy="450" r="510" fill="none" stroke="#ffffff" stroke-opacity="0.03" stroke-width="1.5"/>
<circle cx="1150" cy="450" r="10" fill="#ffffff" fill-opacity="0.16"/>
<circle cx="330" cy="450" r="5" fill="#ffffff" fill-opacity="0.12"/>
<line x1="345" y1="450" x2="1130" y2="450" stroke="#ffffff" stroke-opacity="0.08" stroke-width="2" stroke-dasharray="2 14" stroke-linecap="round"/>
<circle cx="320" cy="210" r="130" fill="#ffffff" fill-opacity="0.045"/>
</svg>

Before

Width:  |  Height:  |  Size: 944 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

@ -4,13 +4,15 @@
<stop offset="0" stop-color="#2e2117"/>
<stop offset="1" stop-color="#8a5e27"/>
</linearGradient>
<pattern id="rings" width="180" height="180" patternUnits="userSpaceOnUse">
<circle cx="90" cy="90" r="38" fill="none" stroke="#ffffff" stroke-opacity="0.09" stroke-width="3"/>
<circle cx="90" cy="90" r="4" fill="#ffffff" fill-opacity="0.13"/>
</pattern>
</defs>
<rect width="1600" height="900" fill="url(#bg)"/>
<rect width="1600" height="900" fill="url(#rings)"/>
<path d="M260 680C510 540 710 560 960 410C1140 302 1260 260 1410 288" fill="none" stroke="#ffffff" stroke-opacity="0.12" stroke-width="8" stroke-linecap="round"/>
<circle cx="960" cy="410" r="100" fill="#ffffff" fill-opacity="0.045"/>
<circle cx="1150" cy="450" r="90" fill="none" stroke="#ffffff" stroke-opacity="0.12" stroke-width="3"/>
<circle cx="1150" cy="450" r="180" fill="none" stroke="#ffffff" stroke-opacity="0.09" stroke-width="2.5"/>
<circle cx="1150" cy="450" r="280" fill="none" stroke="#ffffff" stroke-opacity="0.065" stroke-width="2"/>
<circle cx="1150" cy="450" r="390" fill="none" stroke="#ffffff" stroke-opacity="0.045" stroke-width="2"/>
<circle cx="1150" cy="450" r="510" fill="none" stroke="#ffffff" stroke-opacity="0.03" stroke-width="1.5"/>
<circle cx="1150" cy="450" r="10" fill="#ffffff" fill-opacity="0.16"/>
<circle cx="330" cy="450" r="5" fill="#ffffff" fill-opacity="0.12"/>
<line x1="345" y1="450" x2="1130" y2="450" stroke="#ffffff" stroke-opacity="0.08" stroke-width="2" stroke-dasharray="2 14" stroke-linecap="round"/>
<circle cx="320" cy="210" r="130" fill="#ffffff" fill-opacity="0.045"/>
</svg>

Before

Width:  |  Height:  |  Size: 944 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

@ -1478,6 +1478,60 @@
margin-top: 1px;
}
/* Fehler-Zusammenfassung (bleibt bei den Speichern-Buttons stehen,
Einträge scrollen per Klick zum betroffenen Panel) */
.pr-error-summary {
background: var(--color-err-soft);
border: 1px solid color-mix(in srgb, var(--color-err) 45%, transparent);
border-radius: 4px;
padding: 12px 14px;
margin-bottom: 14px;
}
.pr-error-summary .head {
display: flex;
align-items: center;
gap: 7px;
font-size: 12.5px;
font-weight: 600;
color: var(--color-err);
}
.pr-error-summary ul {
list-style: none;
margin: 8px 0 0;
padding: 0;
display: grid;
gap: 5px;
}
.pr-error-summary li button {
display: block;
width: 100%;
text-align: left;
font-size: 12px;
line-height: 1.45;
color: var(--color-err);
background: transparent;
border: 0;
padding: 0;
cursor: pointer;
text-decoration: underline;
text-decoration-style: dotted;
text-underline-offset: 3px;
}
.pr-error-summary li button:hover {
text-decoration-style: solid;
}
/* Panel mit Validierungsfehler — macht z.B. die Pflicht-Kategorie in
der Sidebar unübersehbar */
.panel.is-invalid {
border-color: var(--color-err);
box-shadow: 0 0 0 1px var(--color-err) inset;
}
.panel.is-invalid > .panel-head {
border-bottom-color: color-mix(in srgb, var(--color-err) 35%, transparent);
background: var(--color-err-soft);
}
/* Boilerplate-Box (Read-only Firmenprofil) */
.pr-boiler {
background: var(--color-bg-elev);

View file

@ -227,6 +227,10 @@
animation: bp-ticker-marquee var(--bp-ticker-duration, 42s) linear infinite;
will-change: transform;
}
/* Anklickbare Meldungen: Lauf anhalten, solange die Maus darüber steht */
.ticker-marquee-track:hover {
animation-play-state: paused;
}
@media (prefers-reduced-motion: reduce) {
.ticker-marquee-track {
animation: none;

View file

@ -232,6 +232,10 @@
animation: bp-ticker-marquee var(--bp-ticker-duration, 42s) linear infinite;
will-change: transform;
}
/* Anklickbare Meldungen: Lauf anhalten, solange die Maus darüber steht */
.ticker-marquee-track:hover {
animation-play-state: paused;
}
@media (prefers-reduced-motion: reduce) {
.ticker-marquee-track {
animation: none;

View file

@ -0,0 +1,199 @@
{{--
Schwebendes Kontakt-Widget (unten rechts, alle Seiten): runder Launcher-
Button, der ein kleines Panel mit Themenauswahl und Kontaktformular
öffnet — Vorbereitung für einen späteren Chatbot. Öffnet auch auf das
Window-Event `open-contact-modal` (bestehende „Kontakt"-Trigger in
Footer/Utility-Bar/Landing) und automatisch nach dem Absenden
(Erfolgs-Flash bzw. Validierungsfehler). Versand über den
host-agnostischen POST-Endpunkt des Kontaktformulars (kontakt.store),
der das Anliegen zusätzlich in der Admin-Queue „Kontakt" persistiert.
Styling: nutzt nur Token-Utilities, die in Web- UND Portal-Build
existieren (bg-bg, text-ink, border-bg-rule …); die Akzentfarbe kommt
per CSS-Variablen-Fallback (Hub-Blau → Brand-Farbe der Spokes).
--}}
@php
$edition = in_array(app()->getLocale(), ['de', 'en'], true) ? app()->getLocale() : 'de';
$launcherAccent = 'background-color: var(--color-hub, var(--color-brand, #1A2540));';
$prefillName = old('name', auth()->user()->name ?? '');
$prefillEmail = old('email', auth()->user()->email ?? '');
$launcherTopics = [
[
'value' => \App\Enums\ContactMessageTopic::Contact->value,
'label' => __('web.launcher.topic_contact'),
'description' => __('web.launcher.topic_contact_desc'),
],
[
'value' => \App\Enums\ContactMessageTopic::BugReport->value,
'label' => __('web.launcher.topic_bug'),
'description' => __('web.launcher.topic_bug_desc'),
],
[
'value' => \App\Enums\ContactMessageTopic::PressReleaseReport->value,
'label' => __('web.launcher.topic_press'),
'description' => __('web.launcher.topic_press_desc'),
],
[
'value' => \App\Enums\ContactMessageTopic::Feedback->value,
'label' => __('web.launcher.topic_feedback'),
'description' => __('web.launcher.topic_feedback_desc'),
],
];
$launcherTopicLabels = collect($launcherTopics)->pluck('label', 'value');
@endphp
<div
x-data="{
open: @js(session()->has('contact-status') || (old('topic') !== null && $errors->any())),
topic: @js(old('topic')),
topicLabels: @js($launcherTopicLabels),
}"
@open-contact-modal.window="open = true"
@keydown.escape.window="open = false"
class="fixed bottom-5 right-5 z-50 flex flex-col items-end gap-3 print:hidden"
>
{{-- Panel --}}
<div x-show="open" x-transition.origin.bottom.right.duration.150ms style="display: none;"
class="w-[calc(100vw-40px)] max-w-[360px] bg-bg-elev border border-bg-rule rounded-[6px] shadow-2xl overflow-hidden"
role="dialog" aria-modal="false" aria-label="{{ __('web.launcher.contact') }}">
{{-- Kopf --}}
<div class="flex items-center justify-between gap-3 px-5 py-4 text-white" style="{{ $launcherAccent }}">
<div>
<div class="text-[10px] font-bold tracking-[0.18em] uppercase opacity-70">{{ __('web.launcher.contact') }}</div>
<div class="text-[15px] font-semibold leading-tight" x-text="topic ? topicLabels[topic] : @js(__('web.launcher.title'))"></div>
</div>
<button type="button" @click="open = false" aria-label="{{ __('web.launcher.close') }}"
class="w-8 h-8 flex items-center justify-center rounded-full text-white/80 hover:text-white hover:bg-white/10 transition-colors cursor-pointer">
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M3 3l10 10M13 3L3 13" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
</svg>
</button>
</div>
@if (session('contact-status'))
{{-- Erfolgsmeldung nach Absenden --}}
<div class="p-5">
<p class="text-[13px] text-ink-2 leading-[1.6] m-0" role="status">
{{ session('contact-status') }}
</p>
<button type="button" @click="open = false"
class="mt-4 inline-flex items-center justify-center px-4 py-2 text-[12.5px] font-semibold text-white rounded-[4px] transition-opacity hover:opacity-90 cursor-pointer"
style="{{ $launcherAccent }}">
{{ __('web.launcher.close') }}
</button>
</div>
@else
{{-- Schritt 1: Themenauswahl --}}
<div x-show="!topic" class="p-3">
<p class="text-[12px] text-ink-3 leading-[1.5] m-0 px-2 pt-1 pb-2">
{{ __('web.launcher.intro') }}
</p>
@foreach ($launcherTopics as $launcherTopic)
<button type="button" @click="topic = '{{ $launcherTopic['value'] }}'"
class="w-full flex items-center justify-between gap-3 px-3 py-2.5 text-left rounded-[4px] hover:bg-bg transition-colors cursor-pointer">
<span>
<span class="block text-[13px] font-semibold text-ink">{{ $launcherTopic['label'] }}</span>
<span class="block text-[11.5px] text-ink-3">{{ $launcherTopic['description'] }}</span>
</span>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" class="text-ink-3 flex-shrink-0" aria-hidden="true">
<path d="M4 2l4 4-4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
@endforeach
</div>
{{-- Schritt 2: Formular --}}
<form x-show="topic" method="POST" action="{{ route('kontakt.store', ['edition' => $edition]) }}"
class="p-5 space-y-3.5" novalidate style="display: none;">
@csrf
<input type="hidden" name="topic" :value="topic" />
<input type="hidden" name="page_url" value="{{ request()->fullUrl() }}" />
<button type="button" @click="topic = null"
class="inline-flex items-center gap-1.5 text-[11.5px] font-medium text-ink-3 hover:text-ink transition-colors cursor-pointer">
<svg width="10" height="10" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="M8 2L4 6l4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
{{ __('web.launcher.back') }}
</button>
<div>
<label for="contact-launcher-name" class="block text-[11.5px] font-semibold text-ink mb-1">{{ __('web.launcher.name') }}</label>
<input id="contact-launcher-name" type="text" name="name" value="{{ $prefillName }}" required
autocomplete="name"
class="w-full border border-bg-rule rounded-[3px] px-3 py-2 text-[13px] bg-bg text-ink focus:outline-none" />
@error('name')
<p class="mt-1 text-[11.5px] text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="contact-launcher-email" class="block text-[11.5px] font-semibold text-ink mb-1">{{ __('web.launcher.email') }}</label>
<input id="contact-launcher-email" type="email" name="email" value="{{ $prefillEmail }}" required
autocomplete="email"
class="w-full border border-bg-rule rounded-[3px] px-3 py-2 text-[13px] bg-bg text-ink focus:outline-none" />
@error('email')
<p class="mt-1 text-[11.5px] text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="contact-launcher-message" class="block text-[11.5px] font-semibold text-ink mb-1">{{ __('web.launcher.message') }}</label>
<textarea id="contact-launcher-message" name="message" rows="4" required
class="w-full border border-bg-rule rounded-[3px] px-3 py-2 text-[13px] bg-bg text-ink focus:outline-none"
placeholder="{{ __('web.launcher.message_placeholder') }}">{{ old('message') }}</textarea>
@error('message')
<p class="mt-1 text-[11.5px] text-red-600">{{ $message }}</p>
@enderror
</div>
{{-- Rechtswidrige Inhalte gehören ins förmliche Meldeverfahren (DSA). --}}
<p x-show="topic === '{{ \App\Enums\ContactMessageTopic::PressReleaseReport->value }}'"
class="text-[11px] text-ink-3 leading-[1.5] m-0">
{{ __('web.launcher.legal_hint') }}
<a href="{{ route('melden', ['edition' => $edition]) }}" class="underline underline-offset-2 hover:text-ink">{{ __('web.launcher.legal_link') }}</a>
</p>
{{-- Bot-Schutz: versteckte Honeypot-Felder (siehe ContactController). --}}
<div class="hidden" aria-hidden="true">
<label for="contact-launcher-website">Website</label>
<input id="contact-launcher-website" type="text" name="website" tabindex="-1" autocomplete="off" value="{{ old('website') }}" />
<label for="contact-launcher-homepage">Homepage</label>
<input id="contact-launcher-homepage" type="text" name="homepage" tabindex="-1" autocomplete="off" value="{{ old('homepage') }}" />
</div>
<div class="flex items-center justify-between gap-3 pt-1">
<p class="text-[10.5px] text-ink-3 leading-[1.5] m-0">
{{ __('web.launcher.privacy_note') }}
(<a href="{{ route('datenschutz', ['edition' => $edition]) }}" class="underline hover:text-ink">{{ __('web.footer.privacy') }}</a>).
</p>
<button type="submit"
class="inline-flex items-center justify-center px-4 py-2 text-[12.5px] font-semibold text-white rounded-[4px] whitespace-nowrap transition-opacity hover:opacity-90 cursor-pointer"
style="{{ $launcherAccent }}">
{{ __('web.launcher.submit') }}
</button>
</div>
</form>
@endif
</div>
{{-- Runder Launcher-Button --}}
<button type="button" @click="open = !open"
class="rounded-full shadow-xl flex items-center justify-center text-white transition-transform hover:scale-105 cursor-pointer"
style="{{ $launcherAccent }} width: 52px; height: 52px;"
:aria-expanded="open.toString()"
aria-haspopup="dialog"
aria-label="{{ __('web.launcher.contact') }}">
{{-- Sprechblasen-Icon (geschlossen) / X (offen) --}}
<svg x-show="!open" width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z"
stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<svg x-show="open" width="18" height="18" viewBox="0 0 16 16" fill="none" aria-hidden="true" style="display: none;">
<path d="M3 3l10 10M13 3L3 13" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
</svg>
</button>
</div>

View file

@ -8,6 +8,7 @@
$categoryName = $category?->name;
$image = $release?->images?->first();
$imageUrl = $image?->variantUrl('card') ?? $image?->url();
$placeholderUrl = $imageUrl ? null : $release?->chosenPlaceholderUrl();
$teaser = (string) \Illuminate\Support\Str::of(strip_tags((string) ($release?->text ?? '')))->squish()->limit(180);
$company = $release?->company?->name;
$title = $release->title;
@ -23,6 +24,8 @@
<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">
@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
<svg width="100%" height="100%" viewBox="0 0 240 160" class="absolute inset-0" aria-hidden="true">
<g opacity="0.5">

View file

@ -12,6 +12,7 @@
$leadCategoryName = $leadCategory?->name;
$leadImage = $leadRelease->images?->first();
$leadImageUrl = $leadImage?->variantUrl('card') ?? $leadImage?->url();
$leadPlaceholderUrl = $leadImageUrl ? null : $leadRelease->chosenPlaceholderUrl();
$leadTitle = $leadRelease->title;
$leadTeaser = (string) \Illuminate\Support\Str::of(strip_tags((string) $leadRelease->text))->squish()->limit(200);
$leadCompany = $leadRelease->company?->name;
@ -42,6 +43,9 @@
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>
@elseif ($leadPlaceholderUrl)
<img src="{{ $leadPlaceholderUrl }}" alt="" aria-hidden="true"
class="absolute inset-0 h-full w-full object-cover" loading="eager">
@else
<x-web.hero-fallback />
@endif

View file

@ -16,6 +16,7 @@
$href = route('release.detail', ['slug' => $release->slug]);
$image = $release?->images?->first();
$imageUrl = $image?->variantUrl('card') ?? $image?->url();
$placeholderUrl = $imageUrl ? null : $release?->chosenPlaceholderUrl();
$gradient = 'linear-gradient(135deg,#3A4A5A,#6B7C95)';
$number = str_pad((string) $index, 2, '0', STR_PAD_LEFT);
@endphp
@ -34,6 +35,8 @@
<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">
@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
<svg width="64" height="64" viewBox="0 0 64 64" class="absolute inset-0" aria-hidden="true">
<circle cx="48" cy="20" r="18" fill="rgba(255,255,255,0.08)" />

View file

@ -1,99 +0,0 @@
{{--
Kontakt-Modal der Hub-Landing: einfaches Formular statt sichtbarer
E-Mail-Adresse (Spam-Schutz). Öffnet auf das Window-Event
`open-contact-modal` (Trigger: `<button x-data @click="$dispatch('open-contact-modal')">`)
und automatisch nach Absenden (Erfolgs-Flash bzw. Validierungsfehler).
Versand über den host-agnostischen POST-Endpunkt des Kontaktformulars.
--}}
<div
x-data="{ open: @js(session()->has('contact-status') || $errors->any()) }"
@open-contact-modal.window="open = true"
@keydown.escape.window="open = false"
>
<div x-show="open" x-cloak class="fixed inset-0 z-50 flex items-center justify-center p-4">
<div class="absolute inset-0 bg-black/50" @click="open = false" aria-hidden="true"></div>
<div class="relative bg-white border border-bg-rule rounded-[4px] w-full max-w-[520px] max-h-[90vh] overflow-y-auto p-7"
role="dialog" aria-modal="true" aria-label="Kontaktanfrage">
<button type="button" @click="open = false" aria-label="Schließen"
class="absolute top-4 right-4 w-8 h-8 flex items-center justify-center text-ink-3 hover:text-ink transition-colors cursor-pointer">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none">
<path d="M3 3l10 10M13 3L3 13" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
</svg>
</button>
@if (session('contact-status'))
<div class="section-eyebrow mb-4">Kontakt</div>
<h3 class="text-[20px] font-bold text-ink tracking-[-0.4px] m-0 mb-3">Nachricht gesendet</h3>
<p class="text-[13.5px] text-ink-2 leading-[1.6] m-0" role="status">
{{ session('contact-status') }}
</p>
<button type="button" @click="open = false"
class="mt-6 inline-flex items-center justify-center px-5 py-2.5 text-[13px] font-semibold text-white bg-hub hover:bg-hub-2 rounded-[4px] transition-colors cursor-pointer">
Schließen
</button>
@else
<div class="section-eyebrow mb-4">Kontakt</div>
<h3 class="text-[20px] font-bold text-ink tracking-[-0.4px] m-0 mb-2">Schreiben Sie uns</h3>
<p class="text-[12.5px] text-ink-3 leading-[1.55] m-0 mb-6">
Ob Enterprise-Anfrage, Support oder Fragen zu Ihren Daten — wir melden uns werktags
schnellstmöglich zurück.
</p>
<form method="POST" action="{{ route('kontakt.store', ['edition' => 'de']) }}" class="space-y-4" novalidate>
@csrf
<div>
<label for="hub-contact-name" class="block text-[12px] font-semibold text-ink mb-1.5">Ihr Name</label>
<input id="hub-contact-name" type="text" name="name" value="{{ old('name') }}" required
autocomplete="name"
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-hub" />
@error('name')
<p class="mt-1 text-[12px] text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="hub-contact-email" class="block text-[12px] font-semibold text-ink mb-1.5">E-Mail-Adresse</label>
<input id="hub-contact-email" type="email" name="email" value="{{ old('email') }}" required
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-hub" />
@error('email')
<p class="mt-1 text-[12px] text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="hub-contact-message" class="block text-[12px] font-semibold text-ink mb-1.5">Ihre Nachricht</label>
<textarea id="hub-contact-message" name="message" rows="5" required
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-hub"
placeholder="Wie können wir Ihnen helfen?">{{ old('message') }}</textarea>
@error('message')
<p class="mt-1 text-[12px] text-red-600">{{ $message }}</p>
@enderror
</div>
{{-- Bot-Schutz: versteckte Honeypot-Felder (siehe ContactController). --}}
<div class="hidden" aria-hidden="true">
<label for="hub-contact-website">Website</label>
<input id="hub-contact-website" type="text" name="website" tabindex="-1" autocomplete="off" value="{{ old('website') }}" />
<label for="hub-contact-homepage">Homepage</label>
<input id="hub-contact-homepage" type="text" name="homepage" tabindex="-1" autocomplete="off" value="{{ old('homepage') }}" />
</div>
<div class="flex items-center justify-between gap-4 pt-1">
<p class="text-[11px] text-ink-3 leading-[1.5] m-0">
Ihre Angaben werden nur zur Bearbeitung der Anfrage verarbeitet
(<a href="{{ route('datenschutz', ['edition' => 'de']) }}" class="underline hover:text-ink">Datenschutz</a>).
</p>
<button type="submit"
class="inline-flex items-center justify-center px-5 py-2.5 text-[13px] font-semibold text-white bg-hub hover:bg-hub-2 rounded-[4px] whitespace-nowrap transition-colors cursor-pointer">
Nachricht senden
</button>
</div>
</form>
@endif
</div>
</div>
</div>

View file

@ -54,6 +54,8 @@
<ul class="space-y-2 text-[13px] text-white/75 list-none p-0 m-0">
<li><a href="{{ route('home') }}#funktion" class="hover:text-white transition-colors">So funktioniert es</a></li>
<li><a href="{{ route('home') }}#familie" class="hover:text-white transition-colors">Die Portale</a></li>
<li><a href="{{ route('faq', ['edition' => 'de']) }}" class="hover:text-white transition-colors">FAQ</a></li>
<li><a href="{{ route('hilfe', ['edition' => 'de']) }}" class="hover:text-white transition-colors">Hilfe-Center</a></li>
<li><a href="{{ route('api', ['edition' => 'de']) }}" class="hover:text-white transition-colors">API-Referenz</a></li>
</ul>
</div>

View file

@ -46,6 +46,8 @@
<span class="flex-1"></span>
<span class="flex items-center gap-4 py-2.5 whitespace-nowrap text-white/65">
<a href="{{ route('faq', ['edition' => 'de']) }}" class="hover:text-white transition-colors">FAQ</a>
<a href="{{ route('hilfe', ['edition' => 'de']) }}" class="hover:text-white transition-colors">Hilfe</a>
<button type="button" x-data @click="$dispatch('open-contact-modal')" class="hover:text-white cursor-pointer">Kontakt</button>
</span>

View file

@ -0,0 +1,18 @@
{{--
Hinweis auf der EN-Ausgabe der rechtsverbindlichen Langtexte (AGB,
Datenschutzerklärung, Richtlinien): englische Fassungen sind bewusst
zurückgestellt (docs/legal/Entscheidung.md O-C, erst nach
fachanwaltlicher Prüfung) — maßgeblich ist die deutsche Fassung.
--}}
@if (app()->getLocale() === 'en')
@php
$legalNoticeEmail = (string) config('legal.contact_email');
@endphp
<div class="max-w-[760px] mb-8 border border-bg-rule border-l-[3px] border-l-brand bg-bg-elev px-5 py-4" role="note">
<div class="text-[12px] font-semibold text-ink mb-1">{{ __('static.legal.german_binding_notice_title') }}</div>
<p class="text-[13px] leading-[1.6] text-ink-2 m-0">
{{ __('static.legal.german_binding_notice', ['email' => $legalNoticeEmail]) }}
</p>
</div>
@endif

View file

@ -21,17 +21,31 @@
<span class="sr-only">
{{ __('web.ticker.sr_intro') }}
@foreach ($items as $item)
{{ $item['time'] }} {{ $item['text'] }}.
@if (! empty($item['href']))
<a href="{{ $item['href'] }}">{{ $item['time'] }} {{ $item['text'] }}.</a>
@else
{{ $item['time'] }} {{ $item['text'] }}.
@endif
@endforeach
</span>
<div class="overflow-hidden min-w-0 flex-1" aria-hidden="true">
<div class="ticker-marquee-track text-[12px] sm:text-[12.5px] whitespace-nowrap">
{{-- tabindex=-1: Duplikate im aria-hidden-Marquee dürfen keinen Fokus erhalten,
die zugängliche Variante steht im sr-only-Block darüber. --}}
@foreach (array_merge($items, $items) as $item)
<span class="inline-flex shrink-0 gap-2 items-baseline">
<span class="font-mono text-[11px] text-ink-on-dark-muted">{{ $item['time'] }}</span>
<span class="text-ink-on-dark">{{ $item['text'] }}</span>
</span>
@if (! empty($item['href']))
<a href="{{ $item['href'] }}" tabindex="-1"
class="inline-flex shrink-0 gap-2 items-baseline cursor-pointer group/ticker">
<span class="font-mono text-[11px] text-ink-on-dark-muted">{{ $item['time'] }}</span>
<span class="text-ink-on-dark group-hover/ticker:text-brand group-hover/ticker:underline underline-offset-2 transition-colors">{{ $item['text'] }}</span>
</a>
@else
<span class="inline-flex shrink-0 gap-2 items-baseline">
<span class="font-mono text-[11px] text-ink-on-dark-muted">{{ $item['time'] }}</span>
<span class="text-ink-on-dark">{{ $item['text'] }}</span>
</span>
@endif
@endforeach
</div>
</div>

View file

@ -15,12 +15,16 @@
$brandName = $brand['name'] ?? 'businessportal';
$brandAccent = $brand['accent'] ?? '';
$brandTagline = $brand['tagline_short'] ?? 'Pressemitteilungen · DACH';
$brandTaglineLong = $brand['tagline_long'] ?? 'Pressemitteilungen aus dem DACH-Raum.';
// Taglines lokalisiert (web.footer.*); Brand-Config bleibt Fallback für
// Themes ohne eigenen Übersetzungsschlüssel.
$brandTagline = __('web.footer.tagline_short');
$brandTaglineLong = \Illuminate\Support\Facades\Lang::has('web.footer.tagline_long_'.$themeKey)
? __('web.footer.tagline_long_'.$themeKey)
: ($brand['tagline_long'] ?? 'Pressemitteilungen aus dem DACH-Raum.');
// Betreiber-Postfach (adametz.media) als Fallback, bis die portal-
// spezifischen CONTACT_EMAIL_*-Adressen gesetzt sind (R2.1).
$contactEmail = $brand['contact_email'] ?? 'info@businessportal24.com';
$legal = str_replace(':year', (string) now()->format('Y'), $brand['footer_legal'] ?? '© :year · Alle Rechte vorbehalten');
$legal = __('web.footer.rights', ['year' => now()->format('Y'), 'brand' => $brandName.$brandAccent]);
@endphp
<footer class="mt-16 bg-topbar-grad text-ink-on-dark">
@ -61,7 +65,7 @@
<div>
<div class="eyebrow mb-3.5">{{ __('web.footer.company') }}</div>
<ul class="space-y-2 text-[13px] text-white/75 list-none p-0 m-0">
<li><a href="mailto:{{ $contactEmail }}" class="cursor-pointer hover:text-white transition-colors">{{ __('web.footer.contact') }}</a></li>
<li><button type="button" x-data @click="$dispatch('open-contact-modal')" class="cursor-pointer hover:text-white transition-colors">{{ __('web.footer.contact') }}</button></li>
<li><a href="{{ route('faq') }}" class="cursor-pointer hover:text-white transition-colors">FAQ</a></li>
<li><a href="{{ route('hilfe') }}" class="cursor-pointer hover:text-white transition-colors">{{ __('web.footer.help') }}</a></li>
<li><a href="{{ route('kuendigung') }}" class="cursor-pointer hover:text-white transition-colors">{{ __('web.footer.cancellation') }}</a></li>

View file

@ -12,7 +12,7 @@
]);
$brandName = $brand['name'] ?? 'businessportal';
$brandAccent = $brand['accent'] ?? '';
$brandTagline = $brand['tagline_short'] ?? 'Pressemitteilungen · DACH';
$brandTagline = __('web.footer.tagline_short');
@endphp
@php
@ -28,17 +28,25 @@
// Navigation = echte Rubriken der aktiven Ausgabe. Es gibt keine separate
// Übersichtsseite; jeder Eintrag führt direkt auf die Kategorieseite.
$navigation ??= collect([
['label' => __('web.nav.home'), 'href' => route('web.home'), 'active' => request()->routeIs('web.home')],
])->concat(
\App\Support\CategoryNavigation::items()->map(fn (array $category): array => [
// Die meistbespielten Rubriken erscheinen als Tabs, der Rest im
// „Mehr“-Dropdown (Desktop) bzw. in der Mobile-Liste.
$moreNavigation = [];
if ($navigation === null) {
$categoryItems = \App\Support\CategoryNavigation::items(null)->map(fn (array $category): array => [
'label' => $category['label'],
'href' => $category['href'],
'active' => request()->routeIs('kategorie') && request()->route('slug') === $category['slug'],
])
)->all();
]);
$activeLabel = collect($navigation)->firstWhere('active', true)['label'] ?? __('web.nav.menu');
$moreNavigation = $categoryItems->slice(9)->values()->all();
$navigation = collect([
['label' => __('web.nav.home'), 'href' => route('web.home'), 'active' => request()->routeIs('web.home')],
])->concat($categoryItems->take(9))->all();
}
$moreActive = collect($moreNavigation)->contains(fn (array $item): bool => $item['active'] ?? false);
$activeLabel = collect($navigation)->concat($moreNavigation)->firstWhere('active', true)['label'] ?? __('web.nav.menu');
@endphp
<header>
@ -89,10 +97,10 @@
</div>
</div>
{{-- Header (Logo + Suche + Buttons + Burger) --}}
{{-- Header (Logo + Suche mittig + Buttons rechtsbündig + Burger) --}}
<div id="header" class="header-normal bg-bg border-b border-bg-rule"
x-data="{ searchOpen: false }"
@keydown.escape.window="searchOpen = false"
x-data="{ searchOpen: false, loginOpen: false }"
@keydown.escape.window="searchOpen = false; loginOpen = false"
@keydown.window.prevent.cmd.k="$refs.desktopSearch ? $refs.desktopSearch.focus() : searchOpen = true"
@keydown.window.prevent.ctrl.k="$refs.desktopSearch ? $refs.desktopSearch.focus() : searchOpen = true">
<div class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 py-3 lg:py-[18px] flex items-center gap-3 sm:gap-4 lg:gap-6">
@ -105,66 +113,127 @@
</div>
</a>
{{-- Suchfeld nur ab md sichtbar --}}
<form action="{{ route('suche') }}" method="get" class="hidden md:block flex-1 max-w-[460px]">
<label class="sr-only" for="site-search">{{ __('web.header.search_label') }}</label>
<div class="flex items-center gap-2.5 px-3.5 py-2.5 border border-bg-rule bg-bg-elev text-[13px] text-ink-3 rounded-[2px] focus-within:border-brand transition-colors">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<circle cx="7" cy="7" r="5" stroke="currentColor" stroke-width="1.5" />
<path d="M11 11l3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
<input id="site-search" type="search" name="q" x-ref="desktopSearch"
placeholder="{{ __('web.header.search_placeholder') }}"
class="flex-1 bg-transparent border-0 p-0 text-[13px] text-ink placeholder:text-ink-3 focus:outline-none focus:ring-0 min-w-0">
<span class="hidden xl:inline-block text-[10.5px] text-ink-4 border border-bg-rule px-1.5 py-0.5 rounded-[2px] font-mono">⌘K</span>
</div>
<div class="hidden lg:flex justify-end mt-1.5">
<a href="{{ route('suche') }}" class="inline-flex items-center gap-1.5 text-[11.5px] text-ink-3 underline underline-offset-2 decoration-[#1C1A17]/40 cursor-pointer hover:text-ink hover:decoration-brand transition-colors">
<svg width="10" height="10" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="M2 3h8M3.5 6h5M5 9h2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" />
{{-- Suchfeld nur ab md sichtbar, mittig im verbleibenden Raum --}}
<div class="hidden md:flex flex-1 justify-center min-w-0">
<form action="{{ route('suche') }}" method="get" class="w-full max-w-[460px]">
<label class="sr-only" for="site-search">{{ __('web.header.search_label') }}</label>
<div class="flex items-center gap-2.5 px-3.5 py-2.5 border border-bg-rule bg-bg-elev text-[13px] text-ink-3 rounded-[2px] focus-within:border-brand transition-colors">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<circle cx="7" cy="7" r="5" stroke="currentColor" stroke-width="1.5" />
<path d="M11 11l3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
{{ __('web.header.advanced_search') }}
</a>
</div>
</form>
<input id="site-search" type="search" name="q" x-ref="desktopSearch"
placeholder="{{ __('web.header.search_placeholder') }}"
class="flex-1 bg-transparent border-0 p-0 text-[13px] text-ink placeholder:text-ink-3 focus:outline-none focus:ring-0 min-w-0">
<span class="hidden xl:inline-block text-[10.5px] text-ink-4 border border-bg-rule px-1.5 py-0.5 rounded-[2px] font-mono">⌘K</span>
</div>
<div class="hidden lg:flex justify-end mt-1.5">
<a href="{{ route('suche') }}" class="inline-flex items-center gap-1.5 text-[11.5px] text-ink-3 underline underline-offset-2 decoration-[#1C1A17]/40 cursor-pointer hover:text-ink hover:decoration-brand transition-colors">
<svg width="10" height="10" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="M2 3h8M3.5 6h5M5 9h2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" />
</svg>
{{ __('web.header.advanced_search') }}
</a>
</div>
</form>
</div>
<span class="flex-1 md:hidden"></span>
{{-- Such-Icon nur auf mobile --}}
<button type="button"
@click="searchOpen = true"
class="md:hidden inline-flex items-center justify-center w-10 h-10 text-ink rounded-[2px] cursor-pointer hover:bg-bg-elev transition-colors"
aria-label="{{ __('web.header.search_open') }}">
<svg width="20" height="20" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<circle cx="7" cy="7" r="5" stroke="currentColor" stroke-width="1.5" />
<path d="M11 11l3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
</button>
{{-- Buttons rechtsbündig --}}
<div class="flex items-center gap-1 sm:gap-2 flex-shrink-0">
{{-- Such-Icon nur auf mobile --}}
<button type="button"
@click="searchOpen = true"
class="md:hidden inline-flex items-center justify-center w-10 h-10 text-ink rounded-[2px] cursor-pointer hover:bg-bg-elev transition-colors"
aria-label="{{ __('web.header.search_open') }}">
<svg width="20" height="20" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<circle cx="7" cy="7" r="5" stroke="currentColor" stroke-width="1.5" />
<path d="M11 11l3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
</button>
{{-- Anmelden: führt in den Hub (Portal-Domain). --}}
<a href="{{ rtrim(config('domains.domain_portal_url', config('app.url')), '/') }}/login"
class="inline-flex items-center gap-2 px-2 sm:px-3 lg:px-4 py-2 lg:py-2.5 text-[13px] font-semibold text-ink rounded-[2px] cursor-pointer hover:text-brand transition-colors"
aria-label="{{ __('web.header.login') }}">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" class="sm:hidden" aria-hidden="true">
<circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="1.8" />
<path d="M4 21c0-4 4-7 8-7s8 3 8 7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
</svg>
<span class="hidden sm:inline">{{ __('web.header.login') }}</span>
</a>
{{-- Anmelden: öffnet Modal mit Pressekonto-Erklärung --}}
<button type="button"
@click="loginOpen = true"
class="inline-flex items-center gap-2 px-2 sm:px-3 lg:px-4 py-2 lg:py-2.5 text-[13px] font-semibold text-ink rounded-[2px] cursor-pointer hover:text-brand transition-colors"
aria-label="{{ __('web.header.login') }}"
aria-haspopup="dialog">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" class="sm:hidden" aria-hidden="true">
<circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="1.8" />
<path d="M4 21c0-4 4-7 8-7s8 3 8 7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
</svg>
<span class="hidden sm:inline">{{ __('web.header.login') }}</span>
</button>
{{-- Veröffentlichen --}}
<a href="{{ route('veroeffentlichen') }}"
class="inline-flex items-center gap-1.5 sm:gap-2 px-3 sm:px-4 lg:px-[18px] py-2 lg:py-2.5 text-[12.5px] sm:text-[13px] font-semibold text-white bg-brand cursor-pointer hover:bg-brand-deep rounded-[2px] transition-colors whitespace-nowrap"
aria-label="{{ __('web.header.publish_aria') }}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" class="sm:hidden" aria-hidden="true">
<path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" />
</svg>
<span class="sm:hidden">PM</span>
<span class="hidden sm:inline">{{ __('web.header.publish') }}</span>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" class="hidden sm:inline" aria-hidden="true">
<path d="M3 6h6M6 3l3 3-3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</a>
{{-- Veröffentlichen --}}
<a href="{{ route('veroeffentlichen') }}"
class="inline-flex items-center gap-1.5 sm:gap-2 px-3 sm:px-4 lg:px-[18px] py-2 lg:py-2.5 text-[12.5px] sm:text-[13px] font-semibold text-white bg-brand cursor-pointer hover:bg-brand-deep rounded-[2px] transition-colors whitespace-nowrap"
aria-label="{{ __('web.header.publish_aria') }}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" class="sm:hidden" aria-hidden="true">
<path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" />
</svg>
<span class="sm:hidden">PM</span>
<span class="hidden sm:inline">{{ __('web.header.publish') }}</span>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" class="hidden sm:inline" aria-hidden="true">
<path d="M3 6h6M6 3l3 3-3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</a>
</div>
</div>
{{-- Anmelde-Modal: erklärt Pressekonto (Hub) und verlinkt dorthin --}}
@php
$portalUrl = rtrim(config('domains.domain_portal_url', config('app.url')), '/');
@endphp
<div x-show="loginOpen"
x-transition.opacity.duration.150ms
class="fixed inset-0 z-50 bg-ink/70 backdrop-blur-sm flex items-start sm:items-center justify-center p-4 sm:p-6 overflow-y-auto"
@click.self="loginOpen = false"
role="dialog"
aria-modal="true"
aria-labelledby="login-modal-title"
style="display: none;">
<div x-show="loginOpen"
x-transition.duration.200ms
class="relative w-full max-w-[520px] bg-bg border border-bg-rule-strong shadow-2xl mt-10 sm:mt-0">
<button type="button"
@click="loginOpen = false"
class="absolute top-3 right-3 inline-flex items-center justify-center w-10 h-10 text-ink-3 rounded-[2px] cursor-pointer hover:text-ink hover:bg-bg-elev transition-colors"
aria-label="{{ __('web.header.login_modal_close') }}">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M6 6l12 12M6 18L18 6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
</svg>
</button>
<div class="px-6 sm:px-8 pt-7 pb-6">
<div class="eyebrow mb-2">{{ __('web.header.login_modal_eyebrow') }}</div>
<h2 id="login-modal-title" class="font-serif text-[24px] sm:text-[26px] font-semibold text-ink tracking-[-0.4px] leading-[1.15] m-0 mb-3.5">
{{ __('web.header.login_modal_title') }}
</h2>
<p class="text-[13.5px] leading-[1.65] text-ink-2 m-0 mb-6">
{{ __('web.header.login_modal_text') }}
</p>
<div class="flex flex-col sm:flex-row gap-2.5">
<a href="{{ $portalUrl }}/login"
class="inline-flex items-center justify-center gap-2 px-[18px] py-2.5 text-[13px] font-semibold text-white bg-brand cursor-pointer hover:bg-brand-deep rounded-[2px] transition-colors">
{{ __('web.header.login_modal_login') }}
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="M3 6h6M6 3l3 3-3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</a>
<a href="{{ $portalUrl }}"
class="inline-flex items-center justify-center gap-2 px-[18px] py-2.5 text-[13px] font-semibold text-ink border border-bg-rule-strong cursor-pointer hover:border-brand hover:text-brand rounded-[2px] transition-colors">
{{ __('web.header.login_modal_hub') }}
</a>
</div>
<p class="text-[11.5px] text-ink-3 m-0 mt-4">
{{ __('web.header.login_modal_note') }}
</p>
</div>
</div>
</div>
{{-- Such-Overlay --}}
@ -245,6 +314,44 @@
{{ $item['label'] }}
</a>
@endforeach
@if (count($moreNavigation) > 0)
{{-- Restliche Rubriken (jenseits der Top 9) im Dropdown --}}
<div class="relative flex items-stretch"
x-data="{ moreOpen: false }"
@keydown.escape.window="moreOpen = false"
@click.outside="moreOpen = false">
<button type="button"
@click="moreOpen = !moreOpen"
class="flex items-center gap-1.5 px-4 py-3.5 text-[13.5px] whitespace-nowrap border-b-2 -mb-px cursor-pointer transition-colors
@if ($moreActive) font-semibold text-ink border-brand
@else font-medium text-ink-2 border-transparent hover:text-ink hover:border-bg-rule-strong/30 @endif"
:aria-expanded="moreOpen.toString()"
aria-controls="desktop-nav-more">
{{ __('web.nav.more') }}
<svg width="10" height="10" viewBox="0 0 12 12" fill="none" class="transition-transform"
:class="{ 'rotate-180': moreOpen }" aria-hidden="true">
<path d="M2 4l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
<div id="desktop-nav-more"
x-show="moreOpen"
x-transition.origin.top
x-cloak
class="absolute top-full right-0 z-40 min-w-[220px] bg-bg-elev border border-bg-rule shadow-lg py-1.5"
style="display: none;">
@foreach ($moreNavigation as $item)
<a href="{{ $item['href'] }}"
class="block px-4 py-2.5 text-[13.5px] whitespace-nowrap cursor-pointer transition-colors
@if ($item['active'] ?? false) font-semibold text-brand bg-brand/[0.04]
@else font-medium text-ink-2 hover:text-ink hover:bg-bg @endif">
{{ $item['label'] }}
</a>
@endforeach
</div>
</div>
@endif
</div>
{{-- Mobile: aktive Kategorie + Burger --}}
@ -282,7 +389,7 @@
class="lg:hidden border-t border-bg-rule bg-bg-elev absolute left-0 right-0 z-40 shadow-lg"
style="display: none;">
<div class="max-w-layout mx-auto px-4 sm:px-6 py-2">
@foreach ($navigation as $item)
@foreach (collect($navigation)->concat($moreNavigation) as $item)
<a href="{{ $item['href'] }}"
class="flex items-center justify-between px-3 py-3 text-[14px] border-b border-bg-rule last:border-b-0 cursor-pointer transition-colors
@if ($item['active'] ?? false) font-semibold text-brand bg-brand/[0.04]

View file

@ -68,7 +68,6 @@
</main>
@if ($isHub)
<x-web.hub.contact-modal />
<x-web.hub.site-footer />
@else
<x-web.site-footer />

View file

@ -27,11 +27,13 @@
@section('content')
<x-web.static-page
eyebrow="Rechtliches"
:eyebrow="__('static.legal.eyebrow')"
title="Allgemeine Geschäftsbedingungen"
breadcrumb="AGB"
updated="Stand: {{ $agbStand }} · Version {{ $agbVersion }}"
>
<x-web.legal-language-notice />
<div class="pm-body max-w-[760px]">
<h2>§ 1 Geltungsbereich und Vertragspartner</h2>
<p>
@ -313,12 +315,12 @@
<p>(1) Einzelveröffentlichungen und Credit-Käufe begründen kein Dauerschuldverhältnis.</p>
<p>
(2) Abonnements haben eine Laufzeit von einem Monat (monatliche Zahlweise) bzw. zwölf
Monaten (jährliche Zahlweise). Nach Ablauf der Erstlaufzeit verlängert sich das Abonnement
auf unbestimmte Zeit und ist jederzeit mit einer Frist von einem Monat kündbar.
<strong>Bei jährlicher Zahlweise erfolgt die Abrechnung nach Ablauf der Erstlaufzeit
monatlich zu dem für den jeweiligen Tarif ausgewiesenen Monatspreis, sofern der Kunde nicht
ausdrücklich eine erneute jährliche Zahlweise wählt. Der Jahresrabatt („2 Monate gratis")
gilt nur bei aktiv gewählter jährlicher Zahlweise.</strong>
Monaten (jährliche Zahlweise). Das Abonnement verlängert sich jeweils automatisch um die
gewählte Laufzeit (einen Monat bzw. zwölf Monate), sofern es nicht vor Ablauf der laufenden
Laufzeit gekündigt wird. <strong>Die Kündigung ist jederzeit mit Wirkung zum Ende der
laufenden Laufzeit möglich; bei monatlicher Zahlweise also monatlich, bei jährlicher
Zahlweise zum Ende des jeweiligen Zwölfmonats-Zeitraums. Der Jahresrabatt („2 Monate
gratis") gilt nur bei jährlicher Zahlweise.</strong>
</p>
<p>(3) Die Kündigung ist möglich:</p>
<ul>

View file

@ -1,7 +1,7 @@
@extends('web.layouts.web-master')
@section('title', 'API-Dokumentation')
@section('meta_description', 'REST-API v1: Pressemitteilungen programmatisch erstellen, mit Bildern versehen und zur Prüfung einreichen — Token-basiert, JSON, versioniert.')
@section('title', __('static.api.meta_title'))
@section('meta_description', __('static.api.meta_description'))
@php
// Die API läuft zentral über den Publisher-Hub (pressekonto).
@ -15,62 +15,62 @@
$endpoints = [
[
'group' => 'Pressemitteilungen',
'group' => __('static.api.group_releases'),
'items' => [
['method' => 'GET', 'path' => '/press-releases', 'desc' => 'Eigene Mitteilungen auflisten (paginiert, filterbar über ?status= und ?per_page=, max. 100).', 'ability' => 'press-releases:read'],
['method' => 'POST', 'path' => '/press-releases', 'desc' => 'Neue Mitteilung anlegen — immer als Entwurf (Status draft).', 'ability' => 'press-releases:write'],
['method' => 'GET', 'path' => '/press-releases/{id}', 'desc' => 'Eine Mitteilung inkl. Firma, Rubrik und Bildern abrufen.', 'ability' => 'press-releases:read'],
['method' => 'PATCH', 'path' => '/press-releases/{id}', 'desc' => 'Mitteilung ändern — nur im Status draft oder rejected.', 'ability' => 'press-releases:write'],
['method' => 'POST', 'path' => '/press-releases/{id}/submit', 'desc' => 'Zur Inhaltsprüfung einreichen — der einzige Weg Richtung Veröffentlichung.', 'ability' => 'press-releases:write'],
['method' => 'DELETE', 'path' => '/press-releases/{id}', 'desc' => 'Mitteilung löschen — veröffentlichte Mitteilungen sind ausgenommen.', 'ability' => 'press-releases:write'],
['method' => 'GET', 'path' => '/press-releases', 'desc' => __('static.api.ep_pr_index'), 'ability' => 'press-releases:read'],
['method' => 'POST', 'path' => '/press-releases', 'desc' => __('static.api.ep_pr_store'), 'ability' => 'press-releases:write'],
['method' => 'GET', 'path' => '/press-releases/{id}', 'desc' => __('static.api.ep_pr_show'), 'ability' => 'press-releases:read'],
['method' => 'PATCH', 'path' => '/press-releases/{id}', 'desc' => __('static.api.ep_pr_update'), 'ability' => 'press-releases:write'],
['method' => 'POST', 'path' => '/press-releases/{id}/submit', 'desc' => __('static.api.ep_pr_submit'), 'ability' => 'press-releases:write'],
['method' => 'DELETE', 'path' => '/press-releases/{id}', 'desc' => __('static.api.ep_pr_destroy'), 'ability' => 'press-releases:write'],
],
],
[
'group' => 'Bilder',
'group' => __('static.api.group_images'),
'items' => [
['method' => 'GET', 'path' => '/press-releases/{id}/images', 'desc' => 'Bilder einer Mitteilung auflisten.', 'ability' => 'press-releases:read'],
['method' => 'POST', 'path' => '/press-releases/{id}/images', 'desc' => 'Bild hochladen (multipart/form-data) — nur bei draft/rejected.', 'ability' => 'press-release-images:write'],
['method' => 'DELETE', 'path' => '/press-release-images/{imageId}', 'desc' => 'Bild entfernen — nur bei draft/rejected.', 'ability' => 'press-release-images:write'],
['method' => 'GET', 'path' => '/press-releases/{id}/images', 'desc' => __('static.api.ep_img_index'), 'ability' => 'press-releases:read'],
['method' => 'POST', 'path' => '/press-releases/{id}/images', 'desc' => __('static.api.ep_img_store'), 'ability' => 'press-release-images:write'],
['method' => 'DELETE', 'path' => '/press-release-images/{imageId}', 'desc' => __('static.api.ep_img_destroy'), 'ability' => 'press-release-images:write'],
],
],
[
'group' => 'Stammdaten',
'group' => __('static.api.group_master_data'),
'items' => [
['method' => 'GET', 'path' => '/companies', 'desc' => 'Eigene Firmenprofile (Newsrooms) auflisten.', 'ability' => 'companies:read'],
['method' => 'GET', 'path' => '/companies/{id}', 'desc' => 'Ein Firmenprofil abrufen.', 'ability' => 'companies:read'],
['method' => 'GET', 'path' => '/categories', 'desc' => 'Aktive Rubriken inkl. Übersetzungen — liefert die category_id für neue Mitteilungen.', 'ability' => 'press-releases:read'],
['method' => 'GET', 'path' => '/companies', 'desc' => __('static.api.ep_companies_index'), 'ability' => 'companies:read'],
['method' => 'GET', 'path' => '/companies/{id}', 'desc' => __('static.api.ep_companies_show'), 'ability' => 'companies:read'],
['method' => 'GET', 'path' => '/categories', 'desc' => __('static.api.ep_categories_index'), 'ability' => 'press-releases:read'],
],
],
];
$pressReleaseFields = [
['name' => 'company_id', 'type' => 'integer', 'required' => true, 'desc' => 'ID eines eigenen Firmenprofils — bestimmt Newsroom und Portal.'],
['name' => 'category_id', 'type' => 'integer', 'required' => true, 'desc' => 'Rubrik (siehe GET /categories).'],
['name' => 'language', 'type' => 'string', 'required' => true, 'desc' => 'Sprachcode, zwei Zeichen: de oder en.'],
['name' => 'title', 'type' => 'string', 'required' => true, 'desc' => 'Überschrift, max. 255 Zeichen.'],
['name' => 'text', 'type' => 'string', 'required' => true, 'desc' => 'Inhalt. Erlaubtes HTML: p, br, h2, h3, strong, em, ul, ol, li, blockquote, a — alles andere wird entfernt.'],
['name' => 'backlink_url', 'type' => 'string', 'required' => false, 'desc' => 'Link zur Quelle/Website, max. 255 Zeichen.'],
['name' => 'keywords', 'type' => 'string', 'required' => false, 'desc' => 'Schlagworte, kommagetrennt, max. 255 Zeichen.'],
['name' => 'teaser_begin / teaser_end', 'type' => 'integer', 'required' => false, 'desc' => 'Zeichen-Offsets für den Teaser-Ausschnitt.'],
['name' => 'no_export', 'type' => 'boolean', 'required' => false, 'desc' => 'Mitteilung von Feeds/Exporten ausnehmen.'],
['name' => 'company_id', 'type' => 'integer', 'required' => true, 'desc' => __('static.api.field_company_id')],
['name' => 'category_id', 'type' => 'integer', 'required' => true, 'desc' => __('static.api.field_category_id')],
['name' => 'language', 'type' => 'string', 'required' => true, 'desc' => __('static.api.field_language')],
['name' => 'title', 'type' => 'string', 'required' => true, 'desc' => __('static.api.field_title')],
['name' => 'text', 'type' => 'string', 'required' => true, 'desc' => __('static.api.field_text')],
['name' => 'backlink_url', 'type' => 'string', 'required' => false, 'desc' => __('static.api.field_backlink_url')],
['name' => 'keywords', 'type' => 'string', 'required' => false, 'desc' => __('static.api.field_keywords')],
['name' => 'teaser_begin / teaser_end', 'type' => 'integer', 'required' => false, 'desc' => __('static.api.field_teaser')],
['name' => 'no_export', 'type' => 'boolean', 'required' => false, 'desc' => __('static.api.field_no_export')],
];
$imageFields = [
['name' => 'image', 'type' => 'file', 'required' => true, 'desc' => 'JPG, PNG oder WebP, max. 8 MB.'],
['name' => 'title', 'type' => 'string', 'required' => false, 'desc' => 'Bildtitel, max. 120 Zeichen.'],
['name' => 'description', 'type' => 'string', 'required' => false, 'desc' => 'Beschreibung, max. 500 Zeichen.'],
['name' => 'copyright', 'type' => 'string', 'required' => false, 'desc' => 'Urheberangabe, max. 255 Zeichen.'],
['name' => 'is_preview', 'type' => 'boolean', 'required' => false, 'desc' => 'Als Vorschaubild setzen (ersetzt ein vorhandenes).'],
['name' => 'image', 'type' => 'file', 'required' => true, 'desc' => __('static.api.field_image')],
['name' => 'title', 'type' => 'string', 'required' => false, 'desc' => __('static.api.field_image_title')],
['name' => 'description', 'type' => 'string', 'required' => false, 'desc' => __('static.api.field_image_description')],
['name' => 'copyright', 'type' => 'string', 'required' => false, 'desc' => __('static.api.field_image_copyright')],
['name' => 'is_preview', 'type' => 'boolean', 'required' => false, 'desc' => __('static.api.field_image_is_preview')],
];
$statusCodes = [
['code' => '200 / 201 / 204', 'desc' => 'Erfolg — Abruf, Ressource erstellt bzw. gelöscht.'],
['code' => '401', 'desc' => 'Kein oder ungültiger API-Token.'],
['code' => '402', 'desc' => 'Einreichen erfordert eine aktive Buchung (Tarif oder Einzel-PM).'],
['code' => '403', 'desc' => 'Token-Berechtigung fehlt oder die Ressource gehört nicht zu Ihrem Konto.'],
['code' => '409', 'desc' => 'Statuskonflikt — z. B. Bearbeiten einer bereits veröffentlichten Mitteilung.'],
['code' => '422', 'desc' => 'Validierungsfehler oder Kontingent-/Richtlinienverstoß (Details im Body).'],
['code' => '429', 'desc' => 'Rate-Limit erreicht: 60 Anfragen pro Minute je Token (Header X-RateLimit-*).'],
['code' => '200 / 201 / 204', 'desc' => __('static.api.status_2xx')],
['code' => '401', 'desc' => __('static.api.status_401')],
['code' => '402', 'desc' => __('static.api.status_402')],
['code' => '403', 'desc' => __('static.api.status_403')],
['code' => '409', 'desc' => __('static.api.status_409')],
['code' => '422', 'desc' => __('static.api.status_422')],
['code' => '429', 'desc' => __('static.api.status_429')],
];
$methodColors = [
@ -83,37 +83,21 @@
@section('content')
<x-web.static-page
eyebrow="Entwickler"
title="API-Dokumentation"
lead="Pressemitteilungen programmatisch erstellen, mit Bildern versehen und zur Prüfung einreichen — über die versionierte REST-API des Publisher-Hubs."
breadcrumb="API"
:eyebrow="__('static.api.eyebrow')"
:title="__('static.api.title')"
:lead="__('static.api.lead')"
:breadcrumb="__('static.api.breadcrumb')"
>
{{-- Überblick + Authentifizierung --}}
<div class="pm-body max-w-[760px]">
<h2>Grundlagen</h2>
<p>
Die API v1 ist ein RESTful Web Service mit JSON-Responses. Basis-URL:
<strong>{{ $apiBase }}</strong>. Eine über die API angelegte Mitteilung ist immer ein
Entwurf; veröffentlicht wird sie erst nach dem Einreichen über die Submit-Route und der
anschließenden Inhaltsprüfung — sie erscheint dann wie jede andere Mitteilung auf beiden Portalen.
</p>
<h3>Authentifizierung</h3>
<p>
Alle Anfragen benötigen einen persönlichen API-Token als Bearer-Header:
<code>Authorization: Bearer &lt;token&gt;</code>. Jeder Token trägt Berechtigungen
(z.&nbsp;B. <code>press-releases:write</code>), die den Zugriff auf die jeweiligen Endpunkte steuern.
Tokens erstellen und widerrufen Sie selbst in Ihrem Publisher-Konto unter „API-Tokens" —
Voraussetzung ist eine aktive Buchung (Tarif oder Einzel-Pressemitteilung).
<strong>Bestandskunden der Altportale:</strong> Ihre bisherigen API-Schlüssel wurden
übernommen und funktionieren weiter — sowohl als Bearer-Token als auch in der alten
Form (<code>X-ApiKey</code>-Header) und auf den dokumentierten
<code>/service/…</code>-Pfaden. Für neue Integrationen empfehlen wir die aktuellen
<code>/api/v1/…</code>-Endpunkte mit Bearer-Token.
</p>
<h2>{{ __('static.api.basics_heading') }}</h2>
<p>{!! __('static.api.basics_text', ['base' => e($apiBase)]) !!}</p>
<h3>{{ __('static.api.auth_heading') }}</h3>
<p>{!! __('static.api.auth_text') !!}</p>
</div>
{{-- Endpunkte --}}
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-14 mb-4">Endpunkte</h2>
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-14 mb-4">{{ __('static.api.endpoints_heading') }}</h2>
<div class="max-w-[900px] space-y-8">
@foreach ($endpoints as $group)
<div>
@ -126,7 +110,7 @@
<code class="font-mono text-[13px] text-ink">{{ $endpoint['path'] }}</code>
<p class="text-[12.5px] leading-[1.55] text-ink-2 mt-1 mb-0">
{{ $endpoint['desc'] }}
<span class="text-ink-4">· Berechtigung: <code class="font-mono text-[11.5px]">{{ $endpoint['ability'] }}</code></span>
<span class="text-ink-4">· {{ __('static.api.permission') }}: <code class="font-mono text-[11.5px]">{{ $endpoint['ability'] }}</code></span>
</p>
</div>
</div>
@ -137,15 +121,15 @@
</div>
{{-- Felder --}}
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-14 mb-4">Felder einer Pressemitteilung</h2>
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-14 mb-4">{{ __('static.api.fields_pr_heading') }}</h2>
<div class="max-w-[900px] border border-bg-rule overflow-x-auto">
<table class="w-full text-[13px] border-collapse">
<thead>
<tr class="text-left text-ink-3 bg-bg-elev border-b border-bg-rule">
<th class="font-medium px-4 py-2.5">Feld</th>
<th class="font-medium px-4 py-2.5">Typ</th>
<th class="font-medium px-4 py-2.5">Pflicht</th>
<th class="font-medium px-4 py-2.5">Beschreibung</th>
<th class="font-medium px-4 py-2.5">{{ __('static.api.th_field') }}</th>
<th class="font-medium px-4 py-2.5">{{ __('static.api.th_type') }}</th>
<th class="font-medium px-4 py-2.5">{{ __('static.api.th_required') }}</th>
<th class="font-medium px-4 py-2.5">{{ __('static.api.th_description') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-bg-rule">
@ -153,7 +137,7 @@
<tr>
<td class="px-4 py-2.5 font-mono text-[12.5px] text-ink whitespace-nowrap">{{ $field['name'] }}</td>
<td class="px-4 py-2.5 text-ink-3">{{ $field['type'] }}</td>
<td class="px-4 py-2.5 text-ink-3">{{ $field['required'] ? 'ja' : '—' }}</td>
<td class="px-4 py-2.5 text-ink-3">{{ $field['required'] ? __('static.api.required_yes') : '—' }}</td>
<td class="px-4 py-2.5 text-ink-2">{{ $field['desc'] }}</td>
</tr>
@endforeach
@ -161,15 +145,15 @@
</table>
</div>
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-12 mb-4">Felder eines Bild-Uploads</h2>
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-12 mb-4">{{ __('static.api.fields_img_heading') }}</h2>
<div class="max-w-[900px] border border-bg-rule overflow-x-auto">
<table class="w-full text-[13px] border-collapse">
<thead>
<tr class="text-left text-ink-3 bg-bg-elev border-b border-bg-rule">
<th class="font-medium px-4 py-2.5">Feld</th>
<th class="font-medium px-4 py-2.5">Typ</th>
<th class="font-medium px-4 py-2.5">Pflicht</th>
<th class="font-medium px-4 py-2.5">Beschreibung</th>
<th class="font-medium px-4 py-2.5">{{ __('static.api.th_field') }}</th>
<th class="font-medium px-4 py-2.5">{{ __('static.api.th_type') }}</th>
<th class="font-medium px-4 py-2.5">{{ __('static.api.th_required') }}</th>
<th class="font-medium px-4 py-2.5">{{ __('static.api.th_description') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-bg-rule">
@ -177,7 +161,7 @@
<tr>
<td class="px-4 py-2.5 font-mono text-[12.5px] text-ink whitespace-nowrap">{{ $field['name'] }}</td>
<td class="px-4 py-2.5 text-ink-3">{{ $field['type'] }}</td>
<td class="px-4 py-2.5 text-ink-3">{{ $field['required'] ? 'ja' : '—' }}</td>
<td class="px-4 py-2.5 text-ink-3">{{ $field['required'] ? __('static.api.required_yes') : '—' }}</td>
<td class="px-4 py-2.5 text-ink-2">{{ $field['desc'] }}</td>
</tr>
@endforeach
@ -186,10 +170,10 @@
</div>
{{-- Workflow-Beispiel --}}
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-14 mb-4">Schneller Einstieg</h2>
<p class="text-[13px] text-ink-2 m-0 mb-4 max-w-[640px]">Der typische Ablauf: Entwurf anlegen, Bild hochladen, zur Prüfung einreichen.</p>
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-14 mb-4">{{ __('static.api.quickstart_heading') }}</h2>
<p class="text-[13px] text-ink-2 m-0 mb-4 max-w-[640px]">{{ __('static.api.quickstart_text') }}</p>
<div class="max-w-[900px] bg-[#16140f] border border-bg-rule-strong rounded-[3px] p-5 overflow-x-auto">
<pre class="text-[12.5px] leading-[1.6] font-mono text-[#9ad1a5] m-0"><code># 1. Entwurf anlegen
<pre class="text-[12.5px] leading-[1.6] font-mono text-[#9ad1a5] m-0"><code>{{ __('static.api.quickstart_step_1') }}
curl -X POST {{ $apiBase }}/press-releases \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
@ -201,29 +185,29 @@ curl -X POST {{ $apiBase }}/press-releases \
"text": "&lt;p&gt;Unser neues Produkt …&lt;/p&gt;"
}'
# 2. Bild hochladen (multipart)
{{ __('static.api.quickstart_step_2') }}
curl -X POST {{ $apiBase }}/press-releases/123/images \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-F "image=@pressefoto.jpg" \
-F "copyright=Mustermann GmbH" \
-F "is_preview=1"
# 3. Zur Inhaltsprüfung einreichen
{{ __('static.api.quickstart_step_3') }}
curl -X POST {{ $apiBase }}/press-releases/123/submit \
-H "Authorization: Bearer YOUR_API_TOKEN"</code></pre>
</div>
<p class="text-[12px] text-ink-3 m-0 mt-3">
Maschinenlesbare Referenz: <a href="{{ route('docs.api.v1') }}" class="text-brand hover:underline">OpenAPI-Spezifikation (YAML)</a>.
{{ __('static.api.openapi_note') }} <a href="{{ route('docs.api.v1') }}" class="text-brand hover:underline">{{ __('static.api.openapi_link') }}</a>.
</p>
{{-- Statuscodes --}}
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-14 mb-4">HTTP-Statuscodes</h2>
<h2 class="font-serif text-[26px] font-semibold tracking-[-0.4px] text-ink m-0 mt-14 mb-4">{{ __('static.api.status_heading') }}</h2>
<div class="max-w-[900px] border border-bg-rule overflow-x-auto">
<table class="w-full text-[13px] border-collapse">
<thead>
<tr class="text-left text-ink-3 bg-bg-elev border-b border-bg-rule">
<th class="font-medium px-4 py-2.5">Code</th>
<th class="font-medium px-4 py-2.5">Bedeutung</th>
<th class="font-medium px-4 py-2.5">{{ __('static.api.th_code') }}</th>
<th class="font-medium px-4 py-2.5">{{ __('static.api.th_meaning') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-bg-rule">
@ -240,17 +224,17 @@ curl -X POST {{ $apiBase }}/press-releases/123/submit \
{{-- CTA --}}
<div class="mt-14 border border-bg-rule bg-bg-elev p-6 sm:p-8 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-5 max-w-[900px]">
<div>
<h2 class="font-serif text-[20px] font-semibold tracking-[-0.3px] text-ink m-0">API-Token erstellen</h2>
<p class="text-[13px] text-ink-2 mt-1.5 mb-0">Tokens verwalten Sie selbst im Publisher-Konto. Bei Fragen zur Anbindung hilft der Support: <a href="mailto:{{ $apiContactEmail }}" class="text-brand hover:underline">{{ $apiContactEmail }}</a></p>
<h2 class="font-serif text-[20px] font-semibold tracking-[-0.3px] text-ink m-0">{{ __('static.api.cta_heading') }}</h2>
<p class="text-[13px] text-ink-2 mt-1.5 mb-0">{{ __('static.api.cta_text') }} <a href="mailto:{{ $apiContactEmail }}" class="text-brand hover:underline">{{ $apiContactEmail }}</a></p>
</div>
<div class="flex flex-wrap gap-3 flex-shrink-0">
<a href="{{ rtrim((string) config('domains.domain_portal_url', config('app.url')), '/') }}/login"
class="inline-flex items-center justify-center gap-1.5 px-5 py-2.5 text-[12.5px] font-semibold text-white bg-brand hover:bg-brand-deep rounded-[2px] transition-colors">
Zum Publisher-Konto
{{ __('static.api.cta_account') }}
</a>
<a href="{{ route('preise') }}"
class="inline-flex items-center justify-center gap-1.5 px-5 py-2.5 text-[12.5px] font-semibold text-ink border border-bg-rule-strong hover:bg-bg transition-colors rounded-[2px]">
Preise ansehen
{{ __('static.api.cta_pricing') }}
</a>
</div>
</div>

View file

@ -22,11 +22,13 @@
@section('content')
<x-web.static-page
eyebrow="Rechtliches"
:eyebrow="__('static.legal.eyebrow')"
title="Datenschutzerklärung"
breadcrumb="Datenschutz"
updated="Stand: 3. Juli 2026 · Version 0.3"
>
<x-web.legal-language-notice />
<div class="pm-body max-w-[760px]">
<h2>1. Verantwortlicher</h2>
<p>Verantwortlicher für die Datenverarbeitung auf {{ $host }} ist:</p>

View file

@ -1,7 +1,7 @@
@extends('web.layouts.web-master')
@section('title', 'Häufig gestellte Fragen (FAQ)')
@section('meta_description', 'Antworten auf die wichtigsten Fragen rund um das Veröffentlichen von Pressemitteilungen, Konto, Abrechnung und Newsroom.')
@section('title', __('static.faq.meta_title'))
@section('meta_description', __('static.faq.meta_description'))
@php
// Host-aware Kontaktadresse: Hub (pressekonto) vs. Spokes. Betreiber-
@ -12,42 +12,43 @@
: (str_contains($host, 'presseecho') ? 'presseecho' : 'businessportal24');
$faqContactEmail = config('domains.domains.'.$faqBrandKey.'.brand.contact_email') ?? 'info@businessportal24.com';
// FAQ-Inhalte als Datenstruktur (DRY). Antworten dürfen kontrolliertes
// HTML enthalten (nur eigene Literale, keine Nutzereingaben).
// FAQ-Inhalte als Datenstruktur (DRY), Texte aus lang/{de,en}/static.php.
// Antworten dürfen kontrolliertes HTML enthalten (nur eigene Literale,
// keine Nutzereingaben).
$faqCategories = [
[
'title' => 'Erste Schritte',
'title' => __('static.faq.cat_start'),
'items' => [
['q' => 'Wie veröffentliche ich meine erste Pressemitteilung?', 'a' => 'Registrieren Sie sich im zentralen Publisher-Bereich (pressekonto), legen Sie Ihr Firmenprofil an und verfassen Sie die Mitteilung im Editor — mit Text, Bildern, Anhängen und Pressekontakt. Nach dem Einreichen durchläuft sie unsere Inhaltsprüfung und erscheint anschließend auf presseecho.de und businessportal24.com.'],
['q' => 'An wen richtet sich das Angebot?', 'a' => 'Ausschließlich an Unternehmen, Selbstständige und Agenturen. Die Veröffentlichung erfolgt stets über ein Firmenprofil — ein Angebot für Verbraucher ist es nicht.'],
['q' => 'Muss ich mich registrieren, um Pressemitteilungen zu lesen?', 'a' => 'Nein, alle veröffentlichten Pressemitteilungen sind frei zugänglich. Eine Registrierung ist nur erforderlich, wenn Sie selbst veröffentlichen möchten.'],
['q' => 'Wie lange dauert die Freischaltung meiner Pressemitteilung?', 'a' => 'Die Inhaltsprüfung ist werktags in der Regel am selben Tag abgeschlossen. Nach der Freigabe geht die Mitteilung sofort online — oder automatisch zu Ihrem geplanten Wunschtermin.'],
['q' => __('static.faq.q_first_pm'), 'a' => __('static.faq.a_first_pm')],
['q' => __('static.faq.q_audience'), 'a' => __('static.faq.a_audience')],
['q' => __('static.faq.q_register_to_read'), 'a' => __('static.faq.a_register_to_read')],
['q' => __('static.faq.q_review_time'), 'a' => __('static.faq.a_review_time')],
],
],
[
'title' => 'Konto & Abrechnung',
'title' => __('static.faq.cat_account'),
'items' => [
['q' => 'Wie ändere ich meinen Tarif?', 'a' => 'In Ihrem Konto unter „Buchungen & Add-ons" können Sie jederzeit upgraden oder downgraden. Upgrades gelten sofort, Downgrades zum nächsten Abrechnungszeitraum.'],
['q' => 'Welche Zahlungsmethoden werden akzeptiert?', 'a' => 'Die Abrechnung läuft sicher über Stripe — etwa per Kreditkarte oder SEPA-Lastschrift. Bestandskunden mit laufender Vereinbarung zahlen weiterhin per Rechnung.'],
['q' => 'Kann ich meinen Tarif kündigen?', 'a' => 'Ja, alle Tarife sind monatlich kündbar — direkt im Konto oder ohne Anmeldung über die <a href="'.route('kuendigung').'">Kündigungsseite</a>. Bei jährlicher Zahlweise läuft der bereits bezahlte Zeitraum bis zum Ende der zwölf Monate.'],
['q' => 'Kann ich mein Konto löschen?', 'a' => 'Ja, jederzeit in den Einstellungen Ihres Kontos. Bereits veröffentlichte Pressemitteilungen bleiben im Archiv online, werden aber nicht mehr Ihrem Konto zugeordnet.'],
['q' => __('static.faq.q_change_plan'), 'a' => __('static.faq.a_change_plan')],
['q' => __('static.faq.q_payment_methods'), 'a' => __('static.faq.a_payment_methods')],
['q' => __('static.faq.q_cancel_plan'), 'a' => __('static.faq.a_cancel_plan', ['url' => route('kuendigung')])],
['q' => __('static.faq.q_delete_account'), 'a' => __('static.faq.a_delete_account')],
],
],
[
'title' => 'Pressemitteilungen & Inhalte',
'title' => __('static.faq.cat_content'),
'items' => [
['q' => 'Welche Dateiformate werden unterstützt?', 'a' => 'Bilder: JPG, PNG, WebP (max. 16 MB im Editor; über die API max. 8 MB). Anhänge: PDF, Word, Excel, PowerPoint, ZIP und TXT (max. 25 MB je Datei).'],
['q' => 'Kann ich eine Pressemitteilung nach Veröffentlichung korrigieren?', 'a' => 'Ja — wir korrigieren statt zu löschen: Die URL Ihrer Mitteilung bleibt erhalten, Verweise und Verlinkungen bestehen weiter. Wenden Sie sich mit Korrekturwünschen an unseren Support.'],
['q' => 'Wie optimiere ich meine Pressemitteilung für SEO?', 'a' => 'Nutzen Sie aussagekräftige Headlines, integrieren Sie relevante Keywords natürlich, hinterlegen Sie Bildtitel und strukturieren Sie den Text mit Zwischenüberschriften.'],
['q' => 'Was passiert, wenn eine Mitteilung abgelehnt wird?', 'a' => 'Sie erhalten die Ablehnung mit Begründung und können die Mitteilung überarbeiten und erneut einreichen. Ein abgelehnter Beitrag verbraucht kein Kontingent.'],
['q' => __('static.faq.q_file_formats'), 'a' => __('static.faq.a_file_formats')],
['q' => __('static.faq.q_corrections'), 'a' => __('static.faq.a_corrections')],
['q' => __('static.faq.q_seo'), 'a' => __('static.faq.a_seo')],
['q' => __('static.faq.q_rejection'), 'a' => __('static.faq.a_rejection')],
],
],
[
'title' => 'Newsroom & Funktionen',
'title' => __('static.faq.cat_newsroom'),
'items' => [
['q' => 'Was ist ein Newsroom?', 'a' => 'Ihre Unternehmensseite auf beiden Portalen, auf der alle Ihre Pressemitteilungen gebündelt sind — mit Logo, Kurzprofil und Pressekontakten.'],
['q' => 'Welche Statistiken sehe ich?', 'a' => 'Die Aufrufe jeder Mitteilung über beide Portale hinweg, direkt in Ihrem Konto. Die Zahlen werden laufend aktualisiert.'],
['q' => 'Gibt es eine API?', 'a' => 'Ja — eine Token-basierte REST-API zum Einreichen von Mitteilungen, Statusabfragen und Bild-Upload. Details finden Sie auf der <a href="'.route('api').'">API-Seite</a>; Ihre Tokens erstellen Sie direkt im Konto, sobald eine aktive Buchung vorliegt.'],
['q' => __('static.faq.q_newsroom'), 'a' => __('static.faq.a_newsroom')],
['q' => __('static.faq.q_stats'), 'a' => __('static.faq.a_stats')],
['q' => __('static.faq.q_api'), 'a' => __('static.faq.a_api', ['url' => route('api')])],
],
],
];
@ -55,10 +56,10 @@
@section('content')
<x-web.static-page
eyebrow="Hilfe"
title="Häufig gestellte Fragen"
lead="Finden Sie schnell Antworten auf die wichtigsten Fragen rund um Veröffentlichung, Konto und Newsroom."
breadcrumb="FAQ"
:eyebrow="__('static.faq.eyebrow')"
:title="__('static.faq.title')"
:lead="__('static.faq.lead')"
:breadcrumb="__('static.faq.breadcrumb')"
>
<div class="max-w-[820px] space-y-10">
@foreach ($faqCategories as $category)
@ -86,19 +87,20 @@
{{-- Abschluss-CTA: Frage nicht gefunden? --}}
<div class="max-w-[820px] mt-12 border-t border-bg-rule pt-8">
<h2 class="font-serif text-[22px] font-semibold tracking-[-0.3px] text-ink m-0">Frage nicht gefunden?</h2>
<h2 class="font-serif text-[22px] font-semibold tracking-[-0.3px] text-ink m-0">{{ __('static.faq.not_found') }}</h2>
<p class="text-[13.5px] leading-[1.6] text-ink-2 mt-2 mb-5 max-w-[560px]">
Unser Support-Team hilft Ihnen gerne weiter – per E-Mail an
<a href="mailto:{{ $faqContactEmail }}" class="underline underline-offset-2 hover:text-ink">{{ $faqContactEmail }}</a>.
{{ __('static.faq.support_intro') }}
<a href="mailto:{{ $faqContactEmail }}" class="underline underline-offset-2 hover:text-ink">{{ $faqContactEmail }}</a>
{{ __('static.faq.support_outro') }}
</p>
<div class="flex flex-wrap gap-3">
<a href="mailto:{{ $faqContactEmail }}"
class="inline-flex items-center justify-center gap-1.5 px-5 py-2.5 text-[12.5px] font-semibold text-white bg-brand hover:bg-brand-deep rounded-[2px] transition-colors">
Support anschreiben
</a>
<button type="button" x-data @click="$dispatch('open-contact-modal')"
class="inline-flex items-center justify-center gap-1.5 px-5 py-2.5 text-[12.5px] font-semibold text-white bg-brand hover:bg-brand-deep rounded-[2px] transition-colors cursor-pointer">
{{ __('static.faq.write_support') }}
</button>
<a href="{{ route('hilfe') }}"
class="inline-flex items-center justify-center gap-1.5 px-5 py-2.5 text-[12.5px] font-semibold text-ink border border-bg-rule-strong hover:bg-bg-elev rounded-[2px] transition-colors">
Hilfe-Center besuchen
{{ __('static.faq.visit_help') }}
</a>
</div>
</div>

View file

@ -1,7 +1,7 @@
@extends('web.layouts.web-master')
@section('title', 'Hilfe-Center')
@section('meta_description', 'Hilfe-Center: Antworten, Anleitungen und direkter Kontakt zum Support-Team.')
@section('title', __('static.help.meta_title'))
@section('meta_description', __('static.help.meta_description'))
@php
$publisherUrl = rtrim((string) config('domains.domain_portal_url', config('app.url')), '/');
@ -13,61 +13,62 @@
: (str_contains($helpHost, 'presseecho') ? 'presseecho' : 'businessportal24');
$helpContactEmail = config('domains.domains.'.$helpBrandKey.'.brand.contact_email') ?? 'info@businessportal24.com';
// Hilfe-Themen mit Links auf vorhandene Ziele.
// Hilfe-Themen mit Links auf vorhandene Ziele; Texte aus static.help.*.
$helpTopics = [
[
'title' => 'Erste Schritte',
'title' => __('static.help.topic_start'),
'links' => [
['label' => 'Konto erstellen', 'href' => $publisherUrl.'/register'],
['label' => 'Erste Pressemitteilung veröffentlichen', 'href' => route('veroeffentlichen')],
['label' => 'Newsrooms entdecken', 'href' => route('newsrooms')],
['label' => 'Häufige Fragen zum Einstieg', 'href' => route('faq')],
['label' => __('static.help.link_create_account'), 'href' => $publisherUrl.'/register'],
['label' => __('static.help.link_first_pm'), 'href' => route('veroeffentlichen')],
['label' => __('static.help.link_discover_newsrooms'), 'href' => route('newsrooms')],
['label' => __('static.help.link_faq_start'), 'href' => route('faq')],
],
],
[
'title' => 'Pressemitteilungen',
'title' => __('static.help.topic_releases'),
'links' => [
['label' => 'Dateiformate und Größen', 'href' => route('faq')],
['label' => 'SEO-Optimierung', 'href' => route('faq')],
['label' => 'Korrekturen nach Veröffentlichung', 'href' => route('faq')],
['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')],
],
],
[
'title' => 'Konto & Abrechnung',
'title' => __('static.help.topic_account'),
'links' => [
['label' => 'Tarif wechseln', 'href' => route('preise')],
['label' => 'Rechnungen im Konto abrufen', 'href' => $publisherUrl.'/login'],
['label' => 'Vertrag kündigen', 'href' => route('kuendigung')],
['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')],
],
],
[
'title' => 'Erweiterte Funktionen',
'title' => __('static.help.topic_advanced'),
'links' => [
['label' => 'API-Integration', 'href' => route('api')],
['label' => 'Statistiken zu Aufrufen', 'href' => route('faq')],
['label' => 'Newsroom pflegen', 'href' => $publisherUrl.'/login'],
['label' => __('static.help.link_api'), 'href' => route('api')],
['label' => __('static.help.link_stats'), 'href' => route('faq')],
['label' => __('static.help.link_newsroom'), 'href' => $publisherUrl.'/login'],
],
],
];
$quickLinks = [
['label' => 'FAQ', 'desc' => 'Häufig gestellte Fragen', 'href' => route('faq')],
['label' => 'Veröffentlichen', 'desc' => 'In wenigen Schritten zur Meldung', 'href' => route('veroeffentlichen')],
['label' => 'Kontakt', 'desc' => 'Direkter Draht zum Support', 'href' => 'mailto:'.$helpContactEmail],
['label' => __('static.help.quick_faq'), 'desc' => __('static.help.quick_faq_desc'), 'href' => route('faq')],
['label' => __('static.help.quick_publish'), 'desc' => __('static.help.quick_publish_desc'), 'href' => route('veroeffentlichen')],
['label' => __('static.help.quick_contact'), 'desc' => __('static.help.quick_contact_desc'), 'href' => null, 'contact' => true],
];
@endphp
@section('content')
<x-web.static-page
eyebrow="Hilfe"
title="Hilfe-Center"
lead="Finden Sie Antworten und Anleitungen – oder kontaktieren Sie unser Support-Team direkt."
breadcrumb="Hilfe"
:eyebrow="__('static.help.eyebrow')"
:title="__('static.help.title')"
:lead="__('static.help.lead')"
:breadcrumb="__('static.help.breadcrumb')"
>
{{-- Schnellzugriff --}}
<div class="grid gap-px bg-bg-rule border border-bg-rule grid-cols-1 sm:grid-cols-3">
@foreach ($quickLinks as $link)
<a href="{{ $link['href'] }}" class="group bg-bg p-6 hover:bg-bg-elev transition-colors">
<a @if ($link['contact'] ?? false) href="#" x-data @click.prevent="$dispatch('open-contact-modal')" @else href="{{ $link['href'] }}" @endif
class="group bg-bg p-6 hover:bg-bg-elev transition-colors cursor-pointer">
<div class="flex items-center justify-between gap-2">
<span class="text-[15px] font-semibold text-ink group-hover:text-brand transition-colors">{{ $link['label'] }}</span>
<svg width="13" height="13" viewBox="0 0 12 12" fill="none" aria-hidden="true" class="text-ink-3 group-hover:text-brand transition-colors">
@ -80,7 +81,7 @@
</div>
{{-- Hilfe-Themen --}}
<h2 class="font-serif text-[22px] font-semibold tracking-[-0.3px] text-ink m-0 mt-12 mb-4">Hilfe-Themen</h2>
<h2 class="font-serif text-[22px] font-semibold tracking-[-0.3px] text-ink m-0 mt-12 mb-4">{{ __('static.help.topics_heading') }}</h2>
<div class="grid gap-px bg-bg-rule border border-bg-rule grid-cols-1 md:grid-cols-2">
@foreach ($helpTopics as $topic)
<div class="bg-bg p-6">
@ -102,17 +103,17 @@
{{-- Support-CTA --}}
<div class="mt-12 border border-bg-rule bg-bg-elev p-6 sm:p-8 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-5">
<div>
<h2 class="font-serif text-[20px] font-semibold tracking-[-0.3px] text-ink m-0">Benötigen Sie weitere Hilfe?</h2>
<p class="text-[13px] text-ink-2 mt-1.5 mb-0">Unser Support-Team antwortet werktags per E-Mail.</p>
<h2 class="font-serif text-[20px] font-semibold tracking-[-0.3px] text-ink m-0">{{ __('static.help.cta_heading') }}</h2>
<p class="text-[13px] text-ink-2 mt-1.5 mb-0">{{ __('static.help.cta_text') }}</p>
</div>
<div class="flex flex-wrap gap-3 flex-shrink-0">
<a href="mailto:{{ $helpContactEmail }}"
class="inline-flex items-center justify-center gap-1.5 px-5 py-2.5 text-[12.5px] font-semibold text-white bg-brand hover:bg-brand-deep rounded-[2px] transition-colors">
Support kontaktieren
</a>
<button type="button" x-data @click="$dispatch('open-contact-modal')"
class="inline-flex items-center justify-center gap-1.5 px-5 py-2.5 text-[12.5px] font-semibold text-white bg-brand hover:bg-brand-deep rounded-[2px] transition-colors cursor-pointer">
{{ __('static.help.cta_contact') }}
</button>
<a href="{{ route('faq') }}"
class="inline-flex items-center justify-center gap-1.5 px-5 py-2.5 text-[12.5px] font-semibold text-ink border border-bg-rule-strong hover:bg-bg transition-colors rounded-[2px]">
FAQ durchsuchen
{{ __('static.help.cta_faq') }}
</a>
</div>
</div>

View file

@ -1,7 +1,7 @@
@extends('web.layouts.web-master')
@section('title', 'Impressum')
@section('meta_description', 'Impressum und Anbieterkennzeichnung gemäß § 5 DDG.')
@section('title', __('static.imprint.meta_title'))
@section('meta_description', __('static.imprint.meta_description'))
@php
// Single-Source-Template (Rechtstexte-Architektur E3): ein Inhalt, drei
@ -18,12 +18,12 @@
@section('content')
<x-web.static-page
eyebrow="Rechtliches"
title="Impressum"
breadcrumb="Impressum"
:eyebrow="__('static.legal.eyebrow')"
:title="__('static.imprint.title')"
:breadcrumb="__('static.imprint.breadcrumb')"
>
<div class="pm-body max-w-[760px]">
<h2>Anbieter (Angaben gemäß § 5 DDG)</h2>
<h2>{{ __('static.imprint.provider_heading') }}</h2>
<p>
adametz.media<br>
Inhaber: Kevin Adametz<br>
@ -32,17 +32,17 @@
Deutschland
</p>
<p>
E-Mail: <a href="mailto:{{ $contactEmail }}">{{ $contactEmail }}</a><br>
Telefon: +49 5206 7076721 (kein telefonischer Support)
{{ __('static.imprint.email_label') }}: <a href="mailto:{{ $contactEmail }}">{{ $contactEmail }}</a><br>
{{ __('static.imprint.phone_label') }}: +49 5206 7076721 {{ __('static.imprint.phone_note') }}
</p>
<h3>Umsatzsteuer-Identifikationsnummer</h3>
<h3>{{ __('static.imprint.vat_heading') }}</h3>
<p>
Umsatzsteuer-Identifikationsnummer gemäß § 27a UStG:<br>
{{ __('static.imprint.vat_text') }}<br>
DE298729654
</p>
<h2>Verantwortlich für journalistisch-redaktionelle Inhalte (§ 18 Abs. 2 MStV)</h2>
<h2>{{ __('static.imprint.editorial_heading') }}</h2>
<p>
Kevin Adametz<br>
In der Lake 4<br>
@ -50,53 +50,26 @@
Deutschland
</p>
<h2>Zu diesem Angebot</h2>
<p>
{{ $portalName }} ({{ $host }}) wird betrieben von adametz.media. Zum Verbund gehören die
Presseportale <strong>presseecho.de</strong> und <strong>businessportal24.com</strong> sowie
das zentrale Publisher-Konto <strong>pressekonto</strong>
(<a href="{{ $hubUrl }}">{{ str_replace(['https://', 'http://'], '', $hubUrl) }}</a>),
über das Registrierung, Buchung und Verwaltung erfolgen.
</p>
<h2>{{ __('static.imprint.about_heading') }}</h2>
<p>{!! __('static.imprint.about_text', [
'portal' => e($portalName),
'host' => e($host),
'hub_url' => $hubUrl,
'hub_label' => str_replace(['https://', 'http://'], '', $hubUrl),
]) !!}</p>
<h2>Verantwortlichkeit für Inhalte</h2>
<p>
Die auf den Portalen veröffentlichten Pressemitteilungen sind Inhalte der jeweiligen
einreichenden Unternehmen; diese sind für ihre Inhalte selbst verantwortlich. Der Anbieter
ist Hostingdiensteanbieter im Sinne des Digital Services Act (Verordnung (EU) 2022/2065);
für fremde Inhalte gelten die Haftungsregeln des DSA (Art. 6) und des § 7 DDG. Wenn Sie
einen Inhalt für rechtswidrig halten oder Ihre Rechte verletzt sehen, nutzen Sie bitte unser
<a href="{{ route('melden') }}">Meldeverfahren</a> — Meldungen sind kostenlos.
</p>
<p>
<strong>Kontaktpunkt</strong> gemäß Art. 11 und 12 DSA (für Behörden und Nutzer):<br>
E-Mail: <a href="mailto:{{ $reportEmail }}">{{ $reportEmail }}</a> ·
Kommunikationssprachen: Deutsch, Englisch
</p>
<h2>{{ __('static.imprint.content_heading') }}</h2>
<p>{!! __('static.imprint.content_text', ['report_url' => route('melden')]) !!}</p>
<p>{!! __('static.imprint.contact_point', ['email' => e($reportEmail)]) !!}</p>
<h2>Verbotene Inhalte</h2>
<p>
Inhalte, die gegen geltendes Recht oder unsere
<a href="{{ route('richtlinien') }}">Veröffentlichungsrichtlinien</a> verstoßen, werden
nicht veröffentlicht bzw. nach Kenntnis entfernt. Bei Hinweisen auf Straftaten behalten wir
uns die Erstattung einer Strafanzeige und die Weitergabe an die zuständigen Behörden vor.
</p>
<h2>{{ __('static.imprint.prohibited_heading') }}</h2>
<p>{!! __('static.imprint.prohibited_text', ['guidelines_url' => route('richtlinien')]) !!}</p>
<h2>Urheberrecht</h2>
<p>
Die durch den Anbieter erstellten Inhalte und Werke auf diesen Seiten unterliegen dem
deutschen Urheberrecht. Beiträge Dritter (insbesondere Pressemitteilungen) sind als solche
gekennzeichnet; die Rechte liegen bei den jeweiligen Einreichern. Vervielfältigung,
Bearbeitung und Verwertung außerhalb der Grenzen des Urheberrechts bedürfen der Zustimmung
des jeweiligen Rechteinhabers.
</p>
<h2>{{ __('static.imprint.copyright_heading') }}</h2>
<p>{{ __('static.imprint.copyright_text') }}</p>
<h2>Verbraucherstreitbeilegung</h2>
<p>
Der Anbieter ist nicht bereit und nicht verpflichtet, an Streitbeilegungsverfahren vor einer
Verbraucherschlichtungsstelle teilzunehmen (§ 36 VSBG). Die frühere Plattform der
EU-Kommission zur Online-Streitbeilegung wurde zum 20. Juli 2025 eingestellt.
</p>
<h2>{{ __('static.imprint.dispute_heading') }}</h2>
<p>{{ __('static.imprint.dispute_text') }}</p>
</div>
</x-web.static-page>
@endsection

View file

@ -23,6 +23,7 @@
$leadImage = $leadRelease?->images->first();
$leadImageUrl = $leadImage?->variantUrl('detail') ?? $leadImage?->variantUrl('card') ?? $leadImage?->url();
$leadPlaceholderUrl = $leadImageUrl ? null : $leadRelease?->chosenPlaceholderUrl();
$leadCat = $leadRelease?->category?->translations->firstWhere('locale', app()->getLocale()) ?? $leadRelease?->category?->translations->first();
$leadCompany = $leadRelease?->company?->name;
$leadLead = $leadRelease
@ -125,6 +126,8 @@
<div class="relative overflow-hidden bg-hero-grad" style="aspect-ratio: 16 / 10;">
@if ($leadImageUrl)
<img src="{{ $leadImageUrl }}" alt="{{ $leadRelease->title }}" class="absolute inset-0 w-full h-full object-cover" loading="lazy">
@elseif ($leadPlaceholderUrl)
<img src="{{ $leadPlaceholderUrl }}" alt="" aria-hidden="true" class="absolute inset-0 w-full h-full object-cover" loading="lazy">
@else
<x-web.hero-fallback />
@endif

View file

@ -1,7 +1,7 @@
@extends('web.layouts.web-master')
@section('title', 'Verträge hier kündigen')
@section('meta_description', 'Kündigen Sie Ihren laufenden Vertrag online – ohne Login, über die gesetzliche Bestätigungsseite nach § 312k BGB.')
@section('title', __('static.cancellation.meta_title'))
@section('meta_description', __('static.cancellation.meta_description'))
{{--
Bestätigungsseite des gesetzlichen Kündigungsbuttons (§ 312k BGB).
@ -14,16 +14,16 @@
@section('content')
<x-web.static-page
eyebrow="Rechtliches"
title="Verträge hier kündigen"
breadcrumb="Kündigung"
lead="Hier können Sie Ihren laufenden Vertrag (z. B. Ihr Abo) online und ohne Anmeldung kündigen. Nach dem Absenden erhalten Sie sofort eine automatische Eingangsbestätigung per E-Mail mit Datum und Uhrzeit des Zugangs."
:eyebrow="__('static.legal.eyebrow')"
:title="__('static.cancellation.title')"
:breadcrumb="__('static.cancellation.breadcrumb')"
:lead="__('static.cancellation.lead')"
>
<div class="max-w-[680px]">
@if (session('termination-status'))
<div class="border border-bg-rule bg-bg-elev p-6" role="status">
<div class="section-eyebrow mb-3">Kündigung</div>
<h2 class="text-[19px] font-bold text-ink tracking-[-0.3px] m-0 mb-2">Kündigung eingegangen</h2>
<div class="section-eyebrow mb-3">{{ __('static.cancellation.received_eyebrow') }}</div>
<h2 class="text-[19px] font-bold text-ink tracking-[-0.3px] m-0 mb-2">{{ __('static.cancellation.received_heading') }}</h2>
<p class="text-[13.5px] text-ink-2 leading-[1.6] m-0">
{{ session('termination-status') }}
</p>
@ -38,21 +38,21 @@
{{-- Art der Kündigung --}}
<fieldset class="border border-bg-rule p-5 m-0">
<legend class="text-[12px] font-semibold text-ink px-2">Art der Kündigung</legend>
<legend class="text-[12px] font-semibold text-ink px-2">{{ __('static.cancellation.kind_legend') }}</legend>
<div class="space-y-3">
<label class="flex items-start gap-3 cursor-pointer">
<input type="radio" name="kind" value="ordentlich" x-model="kind" class="mt-0.5">
<span>
<span class="block text-[13.5px] font-semibold text-ink">Ordentliche Kündigung</span>
<span class="block text-[12.5px] text-ink-3 leading-[1.5]">Kündigung zum Ende der Laufzeit bzw. zum nächstmöglichen Zeitpunkt. Ein Grund ist nicht erforderlich.</span>
<span class="block text-[13.5px] font-semibold text-ink">{{ __('static.cancellation.kind_ordinary') }}</span>
<span class="block text-[12.5px] text-ink-3 leading-[1.5]">{{ __('static.cancellation.kind_ordinary_desc') }}</span>
</span>
</label>
<label class="flex items-start gap-3 cursor-pointer">
<input type="radio" name="kind" value="ausserordentlich" x-model="kind" class="mt-0.5">
<span>
<span class="block text-[13.5px] font-semibold text-ink">Außerordentliche Kündigung</span>
<span class="block text-[12.5px] text-ink-3 leading-[1.5]">Kündigung aus wichtigem Grund mit sofortiger Wirkung.</span>
<span class="block text-[13.5px] font-semibold text-ink">{{ __('static.cancellation.kind_extraordinary') }}</span>
<span class="block text-[12.5px] text-ink-3 leading-[1.5]">{{ __('static.cancellation.kind_extraordinary_desc') }}</span>
</span>
</label>
</div>
@ -61,7 +61,7 @@
@enderror
<div x-show="kind === 'ausserordentlich'" x-cloak class="mt-4">
<label for="termination-reason" class="block text-[12px] font-semibold text-ink mb-1.5">Kündigungsgrund</label>
<label for="termination-reason" class="block text-[12px] font-semibold text-ink mb-1.5">{{ __('static.cancellation.label_reason') }}</label>
<textarea id="termination-reason" name="reason" rows="3"
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-hub">{{ old('reason') }}</textarea>
@error('reason', 'termination')
@ -72,11 +72,11 @@
{{-- Zuordnung des Vertrags --}}
<fieldset class="border border-bg-rule p-5 m-0 mt-6">
<legend class="text-[12px] font-semibold text-ink px-2">Wer kündigt?</legend>
<legend class="text-[12px] font-semibold text-ink px-2">{{ __('static.cancellation.who_legend') }}</legend>
<div class="space-y-4">
<div>
<label for="termination-name" class="block text-[12px] font-semibold text-ink mb-1.5">Ihr Name</label>
<label for="termination-name" class="block text-[12px] font-semibold text-ink mb-1.5">{{ __('static.cancellation.label_name') }}</label>
<input id="termination-name" type="text" name="name" value="{{ old('name') }}" required autocomplete="name"
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-hub" />
@error('name', 'termination')
@ -85,7 +85,7 @@
</div>
<div>
<label for="termination-email" class="block text-[12px] font-semibold text-ink mb-1.5">E-Mail-Adresse <span class="text-ink-3 font-normal">(zur Vertragszuordnung und für die Eingangsbestätigung)</span></label>
<label for="termination-email" class="block text-[12px] font-semibold text-ink mb-1.5">{{ __('static.cancellation.label_email') }} <span class="text-ink-3 font-normal">{{ __('static.cancellation.label_email_hint') }}</span></label>
<input id="termination-email" type="email" name="email" value="{{ old('email') }}" required 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-hub" />
@error('email', 'termination')
@ -95,7 +95,7 @@
<div>
<label for="termination-reference" class="block text-[12px] font-semibold text-ink mb-1.5">
Vertrags-/Rechnungsnummer <span class="text-ink-3 font-normal">(falls zur Hand)</span>
{{ __('static.cancellation.label_reference') }} <span class="text-ink-3 font-normal">{{ __('static.cancellation.label_reference_hint') }}</span>
</label>
<input id="termination-reference" type="text" name="contract_reference" value="{{ old('contract_reference') }}"
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-hub" />
@ -108,16 +108,16 @@
{{-- Beendigungszeitpunkt --}}
<fieldset class="border border-bg-rule p-5 m-0 mt-6">
<legend class="text-[12px] font-semibold text-ink px-2">Zu welchem Zeitpunkt?</legend>
<legend class="text-[12px] font-semibold text-ink px-2">{{ __('static.cancellation.when_legend') }}</legend>
<div class="space-y-3">
<label class="flex items-center gap-3 cursor-pointer">
<input type="radio" name="terminate_mode" value="asap" x-model="terminateMode">
<span class="text-[13.5px] text-ink">Zum nächstmöglichen Zeitpunkt</span>
<span class="text-[13.5px] text-ink">{{ __('static.cancellation.mode_asap') }}</span>
</label>
<label class="flex items-center gap-3 cursor-pointer">
<input type="radio" name="terminate_mode" value="date" x-model="terminateMode">
<span class="text-[13.5px] text-ink">Zu einem bestimmten Datum</span>
<span class="text-[13.5px] text-ink">{{ __('static.cancellation.mode_date') }}</span>
</label>
</div>
@error('terminate_mode', 'termination')
@ -125,7 +125,7 @@
@enderror
<div x-show="terminateMode === 'date'" x-cloak class="mt-4">
<label for="termination-date" class="block text-[12px] font-semibold text-ink mb-1.5">Gewünschtes Beendigungsdatum</label>
<label for="termination-date" class="block text-[12px] font-semibold text-ink mb-1.5">{{ __('static.cancellation.label_date') }}</label>
<input id="termination-date" type="date" name="terminate_at" value="{{ old('terminate_at') }}"
class="border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-hub" />
@error('terminate_at', 'termination')
@ -144,21 +144,18 @@
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mt-7">
<p class="text-[11px] text-ink-3 leading-[1.5] m-0 max-w-[380px]">
Ihre Angaben werden nur zur Bearbeitung der Kündigung verarbeitet
(<a href="{{ route('datenschutz', ['edition' => 'de']) }}" class="underline hover:text-ink">Datenschutz</a>).
Mit dem Klick geht Ihre Kündigungserklärung bei uns ein.
{!! __('static.cancellation.privacy_note', ['url' => route('datenschutz', ['edition' => 'de'])]) !!}
</p>
{{-- § 312k BGB: Bestätigungsschaltfläche, Aufschrift „jetzt kündigen". --}}
<button type="submit"
class="inline-flex items-center justify-center px-6 py-3 text-[14px] font-semibold text-white bg-hub hover:bg-hub-2 rounded-[4px] whitespace-nowrap transition-colors cursor-pointer">
jetzt kündigen
{{ __('static.cancellation.submit') }}
</button>
</div>
</form>
<p class="text-[12px] text-ink-3 leading-[1.6] mt-8 mb-0">
Hinweis: Angemeldet geht es noch schneller — in Ihrem Konto unter „Buchungen &amp; Add-ons"
kündigen Sie Ihr Abo mit wenigen Klicks.
{{ __('static.cancellation.account_hint') }}
</p>
@endif
</div>

View file

@ -7,36 +7,27 @@
// "Recht & Compliance" und bestätigen den Eingang per E-Mail.
$isIllegalContent = $way === 'rechtswidrig';
$pageTitle = $isIllegalContent
? 'Rechtswidrige Inhalte melden'
: 'Eigene Kontaktdaten entfernen lassen';
? __('static.report_form.title_illegal')
: __('static.report_form.title_contact_data');
@endphp
@section('title', $pageTitle)
@section('meta_description', $isIllegalContent
? 'Rechtswidrige Inhalte oder Rechteverletzungen in einer Pressemitteilung melden (DSA) — kostenlos, mit Eingangsbestätigung.'
: 'Entfernung eigener personenbezogener Kontaktdaten aus einer Pressemitteilung beantragen (DSGVO) — kostenlos.')
? __('static.report_form.meta_illegal')
: __('static.report_form.meta_contact_data'))
@section('content')
<x-web.static-page
eyebrow="Rechtliches"
:eyebrow="__('static.legal.eyebrow')"
:title="$pageTitle"
breadcrumb="Inhalte melden"
:breadcrumb="__('static.report_form.breadcrumb')"
>
<div class="max-w-[640px]">
<p class="text-[13.5px] text-ink-2 leading-relaxed mb-2">
@if ($isIllegalContent)
Nutzen Sie dieses Formular, wenn Sie eine veröffentlichte Pressemitteilung für
rechtswidrig halten oder Ihre Rechte verletzt sehen. Die Meldung ist kostenlos;
Sie erhalten unverzüglich eine Eingangsbestätigung mit Vorgangsnummer und
anschließend unsere begründete Entscheidung.
@else
Nutzen Sie dieses Formular, wenn Ihre persönlichen Kontaktdaten (z.&nbsp;B. Name,
Telefonnummer oder E-Mail-Adresse) in einer Pressemitteilung stehen. Die Prüfung
erfolgt nach den Vorgaben der DSGVO und ist für Sie kostenlos.
@endif
{{ $isIllegalContent ? __('static.report_form.intro_illegal') : __('static.report_form.intro_contact_data') }}
</p>
<p class="text-[12.5px] text-ink-3 leading-relaxed mb-6">
Alle Wege im Überblick: <a href="{{ route('melden') }}" class="underline underline-offset-2 hover:text-ink">Inhalte melden oder Änderungen beantragen</a>
{{ __('static.report_form.overview') }} <a href="{{ route('melden') }}" class="underline underline-offset-2 hover:text-ink">{{ __('static.report_form.overview_link') }}</a>
</p>
@if (session('report-status'))
@ -49,7 +40,7 @@
@csrf
<div>
<label for="reported_url" class="block text-[12px] font-semibold text-ink mb-1.5">URL der betroffenen Pressemitteilung</label>
<label for="reported_url" class="block text-[12px] font-semibold text-ink mb-1.5">{{ __('static.report_form.label_url') }}</label>
<input id="reported_url" type="url" name="reported_url" value="{{ old('reported_url') }}" required
placeholder="https://…"
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-brand" />
@ -59,7 +50,7 @@
</div>
<div>
<label for="requester_name" class="block text-[12px] font-semibold text-ink mb-1.5">Ihr Name</label>
<label for="requester_name" class="block text-[12px] font-semibold text-ink mb-1.5">{{ __('static.report_form.label_name') }}</label>
<input id="requester_name" type="text" name="requester_name" value="{{ old('requester_name') }}" required
autocomplete="name"
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-brand" />
@ -70,7 +61,7 @@
<div>
<label for="requester_email" class="block text-[12px] font-semibold text-ink mb-1.5">
{{ $isIllegalContent ? 'Ihre E-Mail-Adresse' : 'Ihre E-Mail-Adresse (für Rückmeldung und ggf. Bestätigungslink)' }}
{{ $isIllegalContent ? __('static.report_form.label_email_illegal') : __('static.report_form.label_email_contact_data') }}
</label>
<input id="requester_email" type="email" name="requester_email" value="{{ old('requester_email') }}" required
autocomplete="email"
@ -82,11 +73,11 @@
<div>
<label for="message" class="block text-[12px] font-semibold text-ink mb-1.5">
{{ $isIllegalContent ? 'Begründung: Warum halten Sie den Inhalt für rechtswidrig?' : 'Welche Angaben sind betroffen?' }}
{{ $isIllegalContent ? __('static.report_form.label_message_illegal') : __('static.report_form.label_message_contact_data') }}
</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="{{ $isIllegalContent ? 'Bitte beschreiben Sie konkret, welche Inhalte betroffen sind und warum.' : 'z. B. „Mein Name und meine Telefonnummer stehen im Pressekontakt.“' }}">{{ old('message') }}</textarea>
placeholder="{{ $isIllegalContent ? __('static.report_form.placeholder_message_illegal') : __('static.report_form.placeholder_message_contact_data') }}">{{ old('message') }}</textarea>
@error('message')
<p class="mt-1 text-[12px] text-red-600">{{ $message }}</p>
@enderror
@ -98,8 +89,7 @@
<input type="checkbox" name="truth_affirmed" value="1" @checked(old('truth_affirmed'))
class="mt-0.5 flex-shrink-0" />
<span>
Ich versichere, dass ich diese Meldung nach bestem Wissen und Gewissen
abgebe und die Angaben richtig und vollständig sind.
{{ __('static.report_form.truth_affirmation') }}
</span>
</label>
@error('truth_affirmed')
@ -118,14 +108,12 @@
<button type="submit"
class="bg-brand hover:bg-brand-deep text-white px-5 py-2.5 text-[13px] font-semibold transition-colors">
{{ $isIllegalContent ? 'Meldung absenden' : 'Antrag absenden' }}
{{ $isIllegalContent ? __('static.report_form.submit_illegal') : __('static.report_form.submit_contact_data') }}
</button>
</form>
<p class="mt-6 text-[11.5px] text-ink-3 leading-relaxed">
Ihre Angaben werden ausschließlich zur Bearbeitung Ihrer Meldung verarbeitet —
Details in der <a href="{{ route('datenschutz') }}" class="underline hover:text-ink">Datenschutzerklärung</a>.
Meldungen und die Ausübung gesetzlicher Rechte sind kostenlos.
{!! __('static.report_form.privacy_note', ['url' => route('datenschutz')]) !!}
</p>
</div>
</x-web.static-page>

View file

@ -1,7 +1,7 @@
@extends('web.layouts.web-master')
@section('title', 'Inhalte melden oder Änderungen beantragen')
@section('meta_description', 'Rechtswidrige Inhalte melden (DSA), personenbezogene Daten entfernen lassen (DSGVO) oder Änderungen an eigenen Pressemitteilungen beantragen — Meldungen sind kostenlos.')
@section('title', __('static.report.meta_title'))
@section('meta_description', __('static.report.meta_description'))
@php
// Meldeverfahren v0.1 (Rechtstexte-Architektur, 03.07.2026). Die Wege 1/2
@ -13,147 +13,76 @@
$brandKey = $isHub ? 'pressekonto' : (str_contains($host, 'presseecho') ? 'presseecho' : 'businessportal24');
$brand = config('domains.domains.'.$brandKey.'.brand', []);
$portalName = ($brand['name'] ?? '').($brand['accent'] ?? '');
$contactEmail = (string) config('legal.contact_email');
$reportEmail = $brand['report_email'] ?? $contactEmail;
$loginUrl = rtrim((string) config('domains.domain_portal_url', config('app.url')), '/').'/login';
// Statt der Melde-E-Mail-Adresse verweisen alle Wege auf das schwebende
// Kontakt-Widget (Badge unten rechts) — derselbe Trigger wie im Footer.
$contactTrigger = sprintf(
'<button type="button" x-data @click="$dispatch(\'open-contact-modal\')" class="underline underline-offset-2 cursor-pointer hover:text-ink">%s</button>',
e(__('static.report.contact_trigger_label')),
);
@endphp
@section('content')
<x-web.static-page
eyebrow="Rechtliches"
title="Inhalte melden oder Änderungen beantragen"
lead="Auf {{ $portalName }} veröffentlichen Unternehmen Pressemitteilungen in eigener Verantwortung. Wenn Sie ein Anliegen zu einer veröffentlichten Pressemitteilung haben, wählen Sie bitte den passenden Weg."
breadcrumb="Inhalte melden"
:eyebrow="__('static.legal.eyebrow')"
:title="__('static.report.title')"
:lead="__('static.report.lead', ['portal' => $portalName])"
:breadcrumb="__('static.report.breadcrumb')"
>
<div class="pm-body max-w-[760px]">
<p>
Sie können eine Pressemitteilung direkt über den Link <strong>„Pressemitteilung
melden"</strong> auf der jeweiligen Veröffentlichungsseite melden — dabei wird die
betroffene Pressemitteilung automatisch übernommen.
</p>
<p>
<strong>Die Meldung rechtswidriger Inhalte und die Ausübung gesetzlicher Rechte sind immer
kostenlos.</strong> Kostenpflichtig ist nur die freiwillige Entfernung rechtmäßiger Inhalte
aus der öffentlichen Ansicht.
</p>
<p>{!! __('static.report.intro_direct') !!}</p>
<p>{!! __('static.report.intro_free') !!}</p>
<h2>1. Rechtswidrige Inhalte oder Rechteverletzungen melden</h2>
<p>
Nutzen Sie diesen Weg, wenn Sie der Ansicht sind, dass eine veröffentlichte Pressemitteilung
rechtswidrig ist oder Ihre Rechte verletzt, zum Beispiel bei:
</p>
<h2>{{ __('static.report.way1_heading') }}</h2>
<p>{{ __('static.report.way1_intro') }}</p>
<ul>
<li>falschen Tatsachenbehauptungen,</li>
<li>Verletzung von Persönlichkeitsrechten,</li>
<li>Verletzung von Marken-, Urheber- oder sonstigen Rechten,</li>
<li>Datenschutzverstößen oder unzulässiger Veröffentlichung personenbezogener Daten,</li>
<li>Betrug, Irreführung oder rechtswidriger Werbung,</li>
<li>sonstigen gesetzlichen Verstößen.</li>
<li>{{ __('static.report.way1_item_1') }}</li>
<li>{{ __('static.report.way1_item_2') }}</li>
<li>{{ __('static.report.way1_item_3') }}</li>
<li>{{ __('static.report.way1_item_4') }}</li>
<li>{{ __('static.report.way1_item_5') }}</li>
<li>{{ __('static.report.way1_item_6') }}</li>
</ul>
<p>
<strong>Meldung:</strong> Nutzen Sie unser
<a href="{{ route('melden.form', ['way' => 'rechtswidrig']) }}">Meldeformular für
rechtswidrige Inhalte</a> — Sie erhalten unverzüglich eine Eingangsbestätigung mit
Vorgangsnummer. Alternativ öffnen Sie die betroffene Pressemitteilung und nutzen dort
den Link <em>„Inhalt melden"</em> (die Mitteilung wird dabei automatisch dem Vorgang
zugeordnet), oder Sie senden Ihre Meldung mit der URL der betroffenen Pressemitteilung
an <a href="mailto:{{ $reportEmail }}">{{ $reportEmail }}</a>.
</p>
<p>Damit wir Ihre Meldung prüfen können, benötigen wir:</p>
<p>{!! __('static.report.way1_how', ['form_url' => route('melden.form', ['way' => 'rechtswidrig']), 'contact' => $contactTrigger]) !!}</p>
<p>{{ __('static.report.way1_needs') }}</p>
<ul>
<li>die URL der betroffenen Pressemitteilung,</li>
<li>eine Begründung, warum Sie den Inhalt für rechtswidrig halten,</li>
<li>Ihren Namen und Ihre E-Mail-Adresse, soweit gesetzlich keine Ausnahme gilt,</li>
<li>Ihre Bestätigung, dass Sie die Meldung nach bestem Wissen und Gewissen abgeben und die Angaben richtig und vollständig sind.</li>
<li>{{ __('static.report.way1_need_1') }}</li>
<li>{{ __('static.report.way1_need_2') }}</li>
<li>{{ __('static.report.way1_need_3') }}</li>
<li>{{ __('static.report.way1_need_4') }}</li>
</ul>
<p>
Nach Absenden der Meldung erhalten Sie unverzüglich eine Eingangsbestätigung. Wir prüfen die
Meldung sorgfältig und zeitnah. Anschließend teilen wir Ihnen unsere Entscheidung mit einer
Begründung mit. Ist der Inhalt rechtswidrig oder besteht ein ausreichender Grund für eine
Einschränkung, entfernen oder sperren wir ihn ganz oder teilweise.
</p>
<p>
Der Autor oder Verantwortliche des Inhalts wird über eine Einschränkung des Inhalts
informiert und erhält eine Begründung, soweit dies gesetzlich vorgesehen ist.
</p>
<p>{{ __('static.report.way1_process') }}</p>
<p>{{ __('static.report.way1_author_note') }}</p>
<h2>2. Eigene personenbezogene Daten entfernen lassen</h2>
<p>
Nutzen Sie diesen Weg, wenn Sie eine natürliche Person sind und Ihre persönlichen
Kontaktdaten in einer Pressemitteilung stehen, zum Beispiel Name, Telefonnummer oder
E-Mail-Adresse.
</p>
<p>
<strong>Antrag:</strong> Nutzen Sie unser
<a href="{{ route('melden.form', ['way' => 'kontaktdaten']) }}">Antragsformular zur
Entfernung eigener Kontaktdaten</a>. Alternativ öffnen Sie die betroffene
Pressemitteilung und nutzen dort den Link <em>„Eigene Daten entfernen"</em> — oder Sie
senden Ihren Antrag an <a href="mailto:{{ $reportEmail }}">{{ $reportEmail }}</a>.
</p>
<p>
Wir prüfen Ihren Antrag nach den Vorgaben der Datenschutz-Grundverordnung. Die Prüfung und
Bearbeitung ist für Sie kostenlos.
</p>
<p>
Zur Prüfung senden wir Ihnen in der Regel einen Bestätigungslink an die betroffene
E-Mail-Adresse. Falls diese nicht mehr erreichbar ist, können wir einen geeigneten
Identitäts- oder Berechtigungsnachweis verlangen. Wir verlangen nur die Angaben, die zur
Prüfung Ihrer Berechtigung erforderlich sind.
</p>
<p>
Nach erfolgreicher Prüfung entfernen, ersetzen oder anonymisieren wir Ihre persönlichen
Kontaktdaten in der Pressemitteilung. Die Pressemitteilung selbst bleibt grundsätzlich
bestehen, soweit keine gesetzlichen Ansprüche auf Entfernung, Sperrung oder Änderung
bestehen.
</p>
<p>
Angaben über Unternehmen und Informationen aus öffentlichen Registern prüfen wir im
Einzelfall. Solche Angaben sind nicht automatisch wie private Kontaktdaten zu behandeln.
</p>
<h2>{{ __('static.report.way2_heading') }}</h2>
<p>{{ __('static.report.way2_intro') }}</p>
<p>{!! __('static.report.way2_how', ['form_url' => route('melden.form', ['way' => 'kontaktdaten']), 'contact' => $contactTrigger]) !!}</p>
<p>{{ __('static.report.way2_gdpr') }}</p>
<p>{{ __('static.report.way2_verification') }}</p>
<p>{{ __('static.report.way2_result') }}</p>
<p>{{ __('static.report.way2_companies') }}</p>
<h2>3. Eigene Pressemitteilung ändern oder verwalten</h2>
<p>
Nutzen Sie diesen Weg, wenn Sie der ursprüngliche Auftraggeber, Firmeninhaber oder als
Pressekontakt in der Pressemitteilung genannt sind.
</p>
<p>
Zur Prüfung Ihrer Berechtigung nutzen wir ein Magic-Link-Verfahren: Wir senden einen
einmaligen Zugangslink an die E-Mail-Adresse, die in der Pressemitteilung oder im
Kundenkonto hinterlegt ist. Nach Bestätigung können Sie zulässige Änderungen beantragen,
zum Beispiel:
</p>
<h2>{{ __('static.report.way3_heading') }}</h2>
<p>{{ __('static.report.way3_intro') }}</p>
<p>{{ __('static.report.way3_magic_link') }}</p>
<ul>
<li>Tippfehler korrigieren,</li>
<li>Kontaktdaten aktualisieren,</li>
<li>veraltete Pressekontakte ersetzen,</li>
<li>eine freiwillige Entfernung rechtmäßiger Inhalte aus der öffentlichen Ansicht beauftragen.</li>
<li>{{ __('static.report.way3_item_1') }}</li>
<li>{{ __('static.report.way3_item_2') }}</li>
<li>{{ __('static.report.way3_item_3') }}</li>
<li>{{ __('static.report.way3_item_4') }}</li>
</ul>
<p>
<strong>Änderung beantragen:</strong> Melden Sie sich in Ihrem
<a href="{{ rtrim((string) config('domains.domain_portal_url', config('app.url')), '/') }}/login">Publisher-Konto</a>
an oder fordern Sie den Zugangslink über die Anmeldeseite an. Wenn die hinterlegte
E-Mail-Adresse nicht mehr erreichbar ist, können wir einen geeigneten Berechtigungsnachweis
verlangen — wenden Sie sich dazu an
<a href="mailto:{{ $reportEmail }}">{{ $reportEmail }}</a>.
</p>
<p>{!! __('static.report.way3_how', ['login_url' => $loginUrl, 'contact' => $contactTrigger]) !!}</p>
<h2>4. Freiwillige Entfernung rechtmäßiger Inhalte</h2>
<p>
Rechtmäßige Pressemitteilungen bleiben grundsätzlich Teil unseres dauerhaften Archivs. Eine
spätere Entfernung aus der öffentlichen Ansicht („Depublikation") bieten wir als freiwillige
Serviceleistung an, soweit keine gesetzlichen Ansprüche auf Entfernung, Sperrung oder
Änderung bestehen.
</p>
<p>
Die Beauftragung ist nur durch eine hierzu berechtigte Person möglich, zum Beispiel durch
den ursprünglichen Auftraggeber, den im Inhalt genannten Pressekontakt, einen
vertretungsberechtigten Firmeninhaber oder eine nachweislich bevollmächtigte Person.
</p>
<p>Bei einer Depublikation gilt:</p>
<h2>{{ __('static.report.way4_heading') }}</h2>
<p>{{ __('static.report.way4_intro') }}</p>
<p>{{ __('static.report.way4_authorized') }}</p>
<p>{{ __('static.report.way4_rules_intro') }}</p>
<ul>
<li>Die Pressemitteilung wird aus der öffentlichen Ansicht entfernt.</li>
<li>Die URL bleibt mit einem kurzen Hinweis auf die Entfernung bestehen („Tombstone").</li>
<li>Eine vollständige Löschung der URL erfolgt nicht.</li>
<li>Nicht enthalten ist die Entfernung aus Suchmaschinen-Caches, Archiven Dritter oder Weiterveröffentlichungen auf anderen Portalen, auf die wir keinen Zugriff haben.</li>
<li>{{ __('static.report.way4_rule_1') }}</li>
<li>{{ __('static.report.way4_rule_2') }}</li>
<li>{{ __('static.report.way4_rule_3') }}</li>
<li>{{ __('static.report.way4_rule_4') }}</li>
</ul>
@php
// T12: Preis erscheint erst, wenn er konfiguriert ist (O6) —
@ -161,38 +90,20 @@
$depublicationPriceCents = (int) config('billing.depublication_price_cents');
@endphp
@if ($depublicationPriceCents > 0)
<p>
Der Preis beträgt
<strong>{{ number_format($depublicationPriceCents / 100, 2, ',', '.') }} € zzgl. USt.</strong>
({{ number_format($depublicationPriceCents * (1 + (float) config('billing.vat_rate')) / 100, 2, ',', '.') }} € inkl. 19&nbsp;% USt.).
<strong>Beauftragung:</strong> über Ihr Kundenkonto (Detailseite der
Pressemitteilung) oder per E-Mail an
<a href="mailto:{{ $reportEmail }}">{{ $reportEmail }}</a>.
</p>
<p>{!! __('static.report.way4_price', [
'net' => number_format($depublicationPriceCents / 100, 2, ',', '.'),
'gross' => number_format($depublicationPriceCents * (1 + (float) config('billing.vat_rate')) / 100, 2, ',', '.'),
'contact' => $contactTrigger,
]) !!}</p>
@else
<p>
Der Preis wird Ihnen vor Beauftragung angezeigt.
<strong>Beauftragung:</strong> über Ihr Kundenkonto oder per E-Mail an
<a href="mailto:{{ $reportEmail }}">{{ $reportEmail }}</a>.
</p>
<p>{!! __('static.report.way4_price_fallback', ['contact' => $contactTrigger]) !!}</p>
@endif
<p>
Wichtig: Dieser Service ist keine Voraussetzung für Ihre gesetzlichen Rechte. Die Meldung
rechtswidriger Inhalte und die Ausübung gesetzlicher Rechte sind und bleiben kostenlos —
unabhängig davon, ob Sie diesen Service nutzen.
</p>
<p>{{ __('static.report.way4_note') }}</p>
<h2>Rückfragen zu Meldungen</h2>
<p>
Für Meldungen nutzen Sie bitte vorrangig die Melde-Links direkt bei der jeweiligen
Pressemitteilung.
</p>
<p>
Nach Absenden einer Meldung erhalten Sie eine Eingangsbestätigung. Für Rückfragen zu einer
bestehenden Meldung können Sie auf diese Bestätigung antworten oder uns unter Angabe des
Vorgangs über <a href="mailto:{{ $reportEmail }}">{{ $reportEmail }}</a> kontaktieren.
</p>
<p>Kommunikationssprachen: Deutsch, Englisch</p>
<h2>{{ __('static.report.questions_heading') }}</h2>
<p>{{ __('static.report.questions_links') }}</p>
<p>{!! __('static.report.questions_reply', ['contact' => $contactTrigger]) !!}</p>
<p>{{ __('static.report.languages') }}</p>
</div>
</x-web.static-page>
@endsection

View file

@ -24,6 +24,7 @@
$leadImage = $leadRelease?->images->first();
$leadImageUrl = $leadImage?->variantUrl('detail') ?? $leadImage?->variantUrl('card') ?? $leadImage?->url();
$leadPlaceholderUrl = $leadImageUrl ? null : $leadRelease?->chosenPlaceholderUrl();
$leadCat = $leadRelease?->category?->translations->firstWhere('locale', app()->getLocale()) ?? $leadRelease?->category?->translations->first();
$leadLead = $leadRelease
? (filled($leadRelease->subtitle)
@ -154,6 +155,8 @@
<div class="relative overflow-hidden bg-hero-grad" style="aspect-ratio: 16 / 9;">
@if ($leadImageUrl)
<img src="{{ $leadImageUrl }}" alt="{{ $leadRelease->title }}" class="absolute inset-0 w-full h-full object-cover" loading="lazy">
@elseif ($leadPlaceholderUrl)
<img src="{{ $leadPlaceholderUrl }}" alt="" aria-hidden="true" class="absolute inset-0 w-full h-full object-cover" loading="lazy">
@else
<x-web.hero-fallback />
@endif

View file

@ -92,7 +92,7 @@
<circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.3" />
<path d="M8 4v4l2.5 1.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" />
</svg>
Monatlich kündbar
Monats- oder Jahrestarife
</span>
</div>
</div>
@ -510,7 +510,7 @@
</svg>
</div>
<div class="text-[13px] text-ink-2 leading-[1.55]">
<strong class="text-ink font-semibold">Alle Tarife monatlich kündbar.</strong>
<strong class="text-ink font-semibold">Monatstarife monatlich kündbar, Jahrestarife zum Ende der Laufzeit.</strong>
Preise netto zzgl. USt. Buchung und Verwaltung direkt im Publisher-Konto,
die Abrechnung läuft sicher über Stripe. Auch ohne Abo möglich: eine
Einzel-Pressemitteilung für {{ $fmtEuro($singlePmPriceCents) }}&nbsp;€ netto.
@ -806,8 +806,8 @@
<details class="border-b border-bg-rule group">
<summary class="flex items-center justify-between gap-4 py-5">
<span class="text-[15.5px] font-semibold text-ink tracking-[-0.2px]">Kann ich monatlich
kündigen?</span>
<span class="text-[15.5px] font-semibold text-ink tracking-[-0.2px]">Welche Laufzeiten
haben die Tarife — und wie kündige ich?</span>
<svg class="faq-chev text-ink-3 flex-shrink-0" width="14" height="14"
viewBox="0 0 16 16" fill="none">
<path d="M3 6l5 5 5-5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
@ -815,9 +815,9 @@
</svg>
</summary>
<div class="pb-5 text-[13.5px] text-ink-2 leading-[1.65] max-w-[640px]">
Ja, alle Tarife sind monatlich kündbar. Bei jährlicher Zahlweise läuft der bereits bezahlte
Zeitraum bis zum Ende der zwölf Monate; danach wird monatlich abgerechnet, sofern Sie nicht
erneut die jährliche Zahlweise wählen. Kündigen können Sie direkt im Konto oder ohne
Monatstarife verlängern sich monatlich und sind monatlich kündbar. Jahrestarife
verlängern sich um jeweils zwölf Monate und sind zum Ende der jeweiligen Laufzeit
kündbar. Kündigen können Sie direkt im Konto oder ohne
Anmeldung über <a
class="text-hub underline underline-offset-[3px] decoration-hub/30 hover:decoration-hub"
href="{{ route('kuendigung', ['edition' => 'de']) }}">unsere Kündigungsseite</a>.
@ -910,7 +910,7 @@
In zwei Minuten registriert. Veröffentlichen mit Tarif oder als Einzel-Pressemitteilung.
</h2>
<p class="text-[13.5px] lg:text-[14px] text-ink-on-dark-2 leading-[1.55] mt-4 m-0 max-w-[600px]">
Das Konto ist kostenlos. Tarife monatlich kündbar, Abrechnung sicher über Stripe.
Das Konto ist kostenlos. Tarife mit monatlicher oder jährlicher Laufzeit, Abrechnung sicher über Stripe.
</p>
</div>
<div class="relative flex flex-col gap-2.5">
@ -942,7 +942,5 @@
</section>
</main>
<x-web.hub.contact-modal />
<x-web.hub.site-footer />
@endsection

View file

@ -33,15 +33,20 @@
$readTime = max(1, (int) round($wordCount / 200));
$teaser = (string) Str::of(strip_tags((string) $release->text))->squish()->limit(180);
$lead = filled($release->subtitle)
? $release->subtitle
: $teaser;
// Subline nur bei explizit gesetztem Untertitel — kein Fallback auf den Textanfang,
// sonst erscheint der erste Absatz doppelt. Der Teaser dient nur der Meta-Description.
$lead = $release->subtitle;
$image = $release->images->first();
$imageUrl = $image?->variantUrl('detail') ?? $image?->variantUrl('card') ?? $image?->url();
$imageCaption = $image?->description ?: $image?->title;
$imageCredit = $image?->copyright ?: $image?->author;
// Ohne Pressefoto: die im User-Panel gewählte Platzhalter-Variante.
// Null bei Altbeständen ohne Variante — dann generischer Hero-Fallback.
$placeholderUrl = $imageUrl ? null : $release->chosenPlaceholderUrl();
$companyName = $company?->name;
$companyInitial = $companyName ? mb_strtoupper(mb_substr($companyName, 0, 1)) : '·';
$companyHref = $company?->slug ? route('newsroom', ['slug' => $company->slug]) : route('newsrooms');
@ -75,7 +80,7 @@
@endphp
@section('title', $release->title.' – '.$brandLabel)
@section('meta_description', $lead)
@section('meta_description', filled($lead) ? $lead : $teaser)
@section('og_type', 'article')
@if ($imageUrl)
@section('og_image', $imageUrl)
@ -154,6 +159,83 @@
</nav>
</div>
@if (! $imageUrl)
{{-- Ohne Pressefoto: Headline direkt in der Platzhalter-Fläche der PM
(gewählte Variante wie im User-Panel, sonst generischer Fallback). --}}
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 pt-[18px]">
<article class="relative overflow-hidden bg-hero-grad">
@if ($placeholderUrl)
<img src="{{ $placeholderUrl }}" alt="" aria-hidden="true"
class="absolute inset-0 h-full w-full object-cover" loading="eager">
@else
<x-web.hero-fallback />
@endif
@if ($embargoAt)
<div class="absolute top-5 left-5 sm:top-6 sm:left-9 flex items-center gap-2.5 flex-wrap">
<span class="text-[9.5px] font-bold tracking-[0.18em] uppercase text-white px-2.5 py-[3px] border border-white/50 font-mono bg-black/25">
● {{ __('web.detail.embargo_lifted') }}
</span>
<span class="text-[11px] text-white/70 font-mono">{{ $embargoAt->translatedFormat('j. F Y, H:i') }}</span>
</div>
@endif
<div class="relative px-5 sm:px-9 pt-[110px] sm:pt-[150px] pb-7 sm:pb-9"
style="background:linear-gradient(180deg,transparent,rgba(0,0,0,0.55) 55%,rgba(0,0,0,0.82));">
<div class="flex items-center gap-3.5 mb-4 flex-wrap">
@if ($categoryName)
<a href="{{ $categoryHref }}" class="bp-cat cursor-pointer transition-colors" style="color:#FF8B6F;">{{ $categoryName }}</a>
<span class="w-1 h-1 rounded-full bg-white/40" aria-hidden="true"></span>
@endif
<span class="text-[11px] text-white/60 italic font-serif">{{ __('web.detail.audience') }}</span>
</div>
<h1 class="font-serif text-[30px] sm:text-[38px] lg:text-[44px] font-semibold m-0 tracking-[-0.8px] leading-[1.1] max-w-[920px] text-white" style="text-wrap:balance;">
{{ $release->title }}
</h1>
@if (filled($lead))
<p class="font-serif text-[16.5px] sm:text-[19px] leading-[1.45] m-0 mt-5 text-white/85 max-w-[820px] font-normal" style="text-wrap:pretty;">
{{ $lead }}
</p>
@endif
<div class="flex flex-wrap items-center text-[12.5px] text-white/75 mt-6" style="gap:10px 22px;">
@if ($companyName)
<span class="inline-flex items-center gap-2">
<span class="w-7 h-7 bg-white text-ink inline-flex items-center justify-center text-[13px] font-bold font-serif">{{ $companyInitial }}</span>
<span class="font-semibold text-white">{{ $companyName }}</span>
</span>
<span class="w-px h-[14px] bg-white/25" aria-hidden="true"></span>
@endif
@if ($publishedAt)
<time datetime="{{ $publishedAt->toIso8601String() }}" class="font-mono text-[11.5px] text-white/65">
{{ $publishedAt->translatedFormat('j. F Y') }} · {{ __('web.detail.time_at', ['time' => $publishedAt->format('H:i')]) }}
</time>
@endif
<span class="font-mono text-[11.5px] text-white/65">{{ __('web.shared.read_time', ['minutes' => $readTime]) }} · {{ __('web.detail.words', ['count' => number_format($wordCount, 0, ',', '.')]) }}</span>
@if ($companyCountry)
<span class="inline-flex items-center gap-1.5 font-mono text-[11.5px] text-white/65">
<svg width="11" height="11" viewBox="0 0 14 14" fill="none" aria-hidden="true"><path d="M7 1a5 5 0 015 5c0 4-5 7-5 7s-5-3-5-7a5 5 0 015-5z" stroke="currentColor" stroke-width="1.3" fill="none" /><circle cx="7" cy="6" r="1.5" fill="currentColor" /></svg>
{{ $companyCountry }}
</span>
@endif
@if ($isRecommended)
<span class="inline-flex items-center gap-1.5 px-2 py-[3px] text-[9.5px] font-bold tracking-[0.14em] uppercase text-white border border-white/40 font-mono cursor-help"
title="{{ __('web.detail.recommended_hint') }}">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M12 2l9 4v6c0 5-3.5 9-9 10-5.5-1-9-5-9-10V6z" stroke="currentColor" stroke-width="2" fill="none" />
<path d="M8 12l3 3 5-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
{{ __('web.detail.recommended') }}
</span>
@endif
</div>
</div>
</article>
</section>
@else
{{-- Hero Meta Block --}}
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8">
<div class="pt-[18px] pb-8 border-b border-bg-rule">
@ -221,15 +303,9 @@
{{-- Hero Image --}}
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 pt-8">
<div class="relative flex items-end p-[18px] bg-bg-dark hatch-dark overflow-hidden" style="aspect-ratio:21/9;">
@if ($imageUrl)
<img src="{{ $imageUrl }}" alt="{{ $imageCaption ?: $release->title }}"
class="absolute inset-0 h-full w-full object-cover" loading="eager">
@else
<span class="relative text-[10px] font-mono tracking-wider uppercase text-white/50">
{{ $companyName ? __('web.detail.image_placeholder_company', ['company' => $companyName]) : __('web.detail.image_placeholder') }} (16:9)
</span>
@endif
<div class="relative flex items-end p-[18px] bg-bg-dark overflow-hidden" style="aspect-ratio:21/9;">
<img src="{{ $imageUrl }}" alt="{{ $imageCaption ?: $release->title }}"
class="absolute inset-0 h-full w-full object-cover" loading="eager">
</div>
@if ($imageCaption || $imageCredit)
<div class="text-[11.5px] text-ink-3 mt-2.5 italic max-w-[720px]">
@ -237,6 +313,7 @@
</div>
@endif
</section>
@endif
{{-- Body + Sidebar --}}
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 pt-10 grid grid-cols-1 lg:grid-cols-[1fr_280px] gap-10 lg:gap-14">
@ -331,6 +408,20 @@
</a>
<span class="text-ink-4">{{ __('web.detail.reports_free') }}</span>
</div>
{{-- Magic-Link-Hinweis: hinterlegte Firmen-/Pressekontakte verwalten
ihre Mitteilung über den passwortlosen Anmeldelink im pressekonto. --}}
@php
$portalUrl = rtrim(config('domains.domain_portal_url', config('app.url')), '/');
@endphp
<div class="mt-3 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-[11.5px] text-ink-3">
<span>{{ __('web.detail.manage_question') }}</span>
<a href="{{ $portalUrl }}/anmeldelink" class="inline-flex items-center gap-1.5 text-ink-2 underline underline-offset-2 hover:text-ink transition-colors">
<svg width="12" height="12" viewBox="0 0 14 14" class="flex-shrink-0" aria-hidden="true"><path d="M1 3h12v8H1zM1 3l6 4 6-4" stroke="currentColor" stroke-width="1.3" fill="none" /></svg>
{{ __('web.detail.manage_link') }}
</a>
<span class="text-ink-4">{{ __('web.detail.manage_hint') }}</span>
</div>
</div>
{{-- Sidebar --}}
@ -433,10 +524,14 @@
</aside>
</section>
{{-- Empfehlungen --}}
{{-- Empfehlungen: zweispaltig nur, wenn beide Listen gefüllt sind —
eine einzelne Liste nutzt sonst die volle Layout-Breite. --}}
@if ($companyReleases->isNotEmpty() || $relatedReleases->isNotEmpty())
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 mt-14">
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
<div @class([
'grid grid-cols-1 gap-10',
'md:grid-cols-2' => $companyReleases->isNotEmpty() && $relatedReleases->isNotEmpty(),
])>
@if ($companyReleases->isNotEmpty())
<div>
<h3 class="font-serif text-[20px] font-semibold m-0 mb-1 tracking-[-0.2px]">{{ __('web.detail.more_from', ['company' => $companyName]) }}</h3>

View file

@ -22,11 +22,13 @@
@section('content')
<x-web.static-page
eyebrow="Rechtliches"
:eyebrow="__('static.legal.eyebrow')"
title="Veröffentlichungsrichtlinien"
breadcrumb="Richtlinien"
updated="{{ $guidelinesStand }}"
>
<x-web.legal-language-notice />
<div class="pm-body max-w-[760px]">
<p>
Diese Richtlinien gelten für alle Pressemitteilungen auf presseecho.de und

View file

@ -4,6 +4,7 @@
$brandConfig = config('domains.domains.'.config('app.theme').'.brand', []);
$brandName = $brandConfig['name'] ?? 'businessportal';
$brandAccent = $brandConfig['accent'] ?? '24';
$brandLabel = $brandName.$brandAccent;
$publisherUrl = rtrim((string) config('domains.domain_portal_url', config('app.url')), '/');
$sinceYear = $oldestYear ?? 2012;
@ -22,15 +23,45 @@
$exampleReadMinutes = $example
? max(1, (int) ceil(str_word_count(strip_tags((string) $example->text)) / 200))
: 3;
// Inhalte aus static.publish.* — Reihen als Datenstrukturen (DRY).
$trustPrinciples = [
['k' => __('static.publish.trust_1_k'), 't' => __('static.publish.trust_1_t'), 'd' => __('static.publish.trust_1_d')],
['k' => __('static.publish.trust_2_k'), 't' => __('static.publish.trust_2_t'), 'd' => __('static.publish.trust_2_d')],
['k' => __('static.publish.trust_3_k'), 't' => __('static.publish.trust_3_t'), 'd' => __('static.publish.trust_3_d')],
['k' => __('static.publish.trust_4_k'), 't' => __('static.publish.trust_4_t'), 'd' => __('static.publish.trust_4_d')],
];
$flowSteps = [
['n' => '01', 'meta' => __('static.publish.flow_1_meta'), 't' => __('static.publish.flow_1_t'), 'd' => __('static.publish.flow_1_d')],
['n' => '02', 'meta' => __('static.publish.flow_2_meta'), 't' => __('static.publish.flow_2_t'), 'd' => __('static.publish.flow_2_d')],
['n' => '03', 'meta' => __('static.publish.flow_3_meta'), 't' => __('static.publish.flow_3_t'), 'd' => __('static.publish.flow_3_d')],
];
$occasions = [
['t' => __('static.publish.occasion_1_t'), 'd' => __('static.publish.occasion_1_d')],
['t' => __('static.publish.occasion_2_t'), 'd' => __('static.publish.occasion_2_d')],
['t' => __('static.publish.occasion_3_t'), 'd' => __('static.publish.occasion_3_d')],
['t' => __('static.publish.occasion_4_t'), 'd' => __('static.publish.occasion_4_d')],
['t' => __('static.publish.occasion_5_t'), 'd' => __('static.publish.occasion_5_d')],
['t' => __('static.publish.occasion_6_t'), 'd' => __('static.publish.occasion_6_d')],
];
$publishFaqs = [
['q' => __('static.publish.faq_1_q'), 'a' => __('static.publish.faq_1_a')],
['q' => __('static.publish.faq_2_q'), 'a' => __('static.publish.faq_2_a')],
['q' => __('static.publish.faq_3_q'), 'a' => __('static.publish.faq_3_a')],
['q' => __('static.publish.faq_4_q'), 'a' => __('static.publish.faq_4_a')],
['q' => __('static.publish.faq_5_q'), 'a' => __('static.publish.faq_5_a')],
];
@endphp
@section('title', 'Pressemitteilung veröffentlichen – '.$brandName.$brandAccent)
@section('meta_description', 'Pressemitteilungen geprüft veröffentlichen und dauerhaft auffindbar im Archiv. Faire Preise, keine Vertragsbindung – Einreichung über den zentralen Publisher-Bereich.')
@section('title', __('static.publish.meta_title', ['brand' => $brandLabel]))
@section('meta_description', __('static.publish.meta_description'))
{{-- Kein hreflang="en": Der Seiteninhalt liegt bisher nur auf Deutsch vor.
EN-Übersetzung der Langform-Seiten ist nachgelagert (siehe Release-Plan). --}}
@section('hreflang')
<link rel="alternate" hreflang="de" href="{{ url('/de/veroeffentlichen') }}">
<link rel="alternate" hreflang="en" href="{{ url('/en/veroeffentlichen') }}">
<link rel="alternate" hreflang="x-default" href="{{ url('/de/veroeffentlichen') }}">
@endsection
@ -42,18 +73,18 @@
<div class="border-b border-bg-rule">
<div class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 py-2.5 flex items-center justify-between gap-3 text-[12px] text-ink-3">
<div class="flex items-center gap-2">
<a href="{{ route('web.home') }}" class="hover:text-ink transition-colors">{{ $brandName }}{{ $brandAccent }}</a>
<a href="{{ route('web.home') }}" class="hover:text-ink transition-colors">{{ $brandLabel }}</a>
<span class="text-ink-4" aria-hidden="true">›</span>
<span class="text-ink">Veröffentlichen</span>
<span class="text-ink">{{ __('static.publish.breadcrumb') }}</span>
</div>
<div class="hidden md:flex items-center gap-[18px]">
<span class="inline-flex items-center gap-1.5">
<svg width="11" height="11" viewBox="0 0 11 11" aria-hidden="true"><path d="M2 5.5l2.2 2.2L9 3" stroke="currentColor" class="text-ok" stroke-width="1.6" fill="none" stroke-linecap="round"/></svg>
<span>Qualitätsgeprüft seit {{ $sinceYear }}</span>
<span>{{ __('static.publish.quality_since', ['year' => $sinceYear]) }}</span>
</span>
<span class="text-ink-4">·</span>
<a href="{{ route('hilfe') }}" class="text-ink-2 hover:text-ink transition-colors">Hilfe &amp; Support</a>
<a href="{{ route('preise') }}" class="text-ink-2 hover:text-ink transition-colors">Preise</a>
<a href="{{ route('hilfe') }}" class="text-ink-2 hover:text-ink transition-colors">{{ __('static.publish.help_support') }}</a>
<a href="{{ route('preise') }}" class="text-ink-2 hover:text-ink transition-colors">{{ __('static.publish.pricing') }}</a>
</div>
</div>
</div>
@ -65,104 +96,101 @@
<div>
<div class="flex items-center gap-3 mb-7">
<span class="w-7 h-px bg-brand"></span>
<span class="eyebrow text-[11px]">Pressestelle · Veröffentlichen</span>
<span class="eyebrow text-[11px]">{{ __('static.publish.eyebrow') }}</span>
</div>
<h1 class="font-serif text-[40px] sm:text-[50px] lg:text-[58px] font-semibold m-0 text-ink leading-[1.05] tracking-[-1.2px] text-balance">
Pressemitteilung veröffentlichen.<br class="hidden sm:block" />
Geprüft. Dauerhaft auffindbar.
{{ __('static.publish.h1_line1') }}<br class="hidden sm:block" />
{{ __('static.publish.h1_line2') }}
</h1>
<p class="font-serif mt-7 text-[18px] lg:text-[19px] leading-[1.5] text-ink-2 max-w-[560px]">
{{ $brandName }}{{ $brandAccent }} ist die Presseplattform für mittelständische Unternehmen,
Selbstständige und PR-Agenturen aus Deutschland, Österreich und der Schweiz. Jede Mitteilung
wird auf Qualität geprüft und bleibt dauerhaft im Archiv auffindbar.
{{ __('static.publish.hero_text', ['brand' => $brandLabel]) }}
</p>
{{-- Störer — Hub-CTA --}}
<div class="relative mt-9 grid items-center overflow-hidden text-ink-on-dark bg-topbar-grad gap-6 lg:gap-7 grid-cols-1 lg:grid-cols-[1.4fr_auto] px-7 py-7 lg:px-8">
<span class="absolute left-0 top-0 bottom-0 w-[3px] bg-brand" aria-hidden="true"></span>
<div>
<div class="eyebrow mb-2.5 text-[10.5px] text-brand-soft">Einreichen im Publisher-Bereich</div>
<div class="eyebrow mb-2.5 text-[10.5px] text-brand-soft">{{ __('static.publish.hub_eyebrow') }}</div>
<h2 class="font-serif m-0 text-[21px] lg:text-[23px] font-semibold leading-[1.25] tracking-[-0.3px] text-ink-on-dark">
Die Einreichung läuft über den zentralen Publisher-Bereich.
{{ __('static.publish.hub_heading') }}
</h2>
<p class="mt-2.5 mb-0 text-[13.5px] leading-[1.55] text-ink-on-dark-2 max-w-[540px]">
Dort verwalten Sie Mitteilungen, Credits und Newsroom — einmaliges Konto, beide Portale
nutzbar (businessportal24 &amp; presseecho.de).
{{ __('static.publish.hub_text') }}
</p>
</div>
<div class="flex flex-col items-start lg:items-end gap-2.5">
<a href="{{ $publisherUrl }}/register"
class="inline-flex items-center gap-2 px-[22px] py-3.5 text-[14px] font-semibold text-white bg-brand hover:bg-brand-deep transition-colors">
Zum Publisher-Bereich
{{ __('static.publish.hub_cta') }}
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true"><path d="M4 8L8.5 3.5M8.5 3.5H5M8.5 3.5V7" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<span class="text-[11.5px] text-ink-on-dark-2">Login per Magic-Link · ohne Vertragsbindung</span>
<span class="text-[11.5px] text-ink-on-dark-2">{{ __('static.publish.hub_note') }}</span>
</div>
</div>
{{-- Faktenleiste --}}
<div class="grid grid-cols-2 lg:grid-cols-4 border-t border-bg-rule mt-9">
<div class="py-5 pr-5 border-r border-bg-rule">
<div class="eyebrow muted text-[10px] mb-2">Seit</div>
<div class="eyebrow muted text-[10px] mb-2">{{ __('static.publish.fact_since') }}</div>
<div class="font-serif text-[26px] lg:text-[28px] font-semibold leading-none text-ink tracking-[-0.4px]">{{ $sinceYear }}</div>
<div class="mt-2 text-[12px] leading-[1.4] text-ink-3">im Betrieb · durchgehende Archivierung</div>
<div class="mt-2 text-[12px] leading-[1.4] text-ink-3">{{ __('static.publish.fact_since_sub') }}</div>
</div>
<div class="py-5 px-5 lg:border-r border-bg-rule">
<div class="eyebrow muted text-[10px] mb-2">Archiv</div>
<div class="eyebrow muted text-[10px] mb-2">{{ __('static.publish.fact_archive') }}</div>
<div class="font-serif text-[26px] lg:text-[28px] font-semibold leading-none text-ink tracking-[-0.4px]">{{ $archiveLabel }}</div>
<div class="mt-2 text-[12px] leading-[1.4] text-ink-3">Mitteilungen weiterhin abrufbar</div>
<div class="mt-2 text-[12px] leading-[1.4] text-ink-3">{{ __('static.publish.fact_archive_sub') }}</div>
</div>
<div class="py-5 pr-5 lg:px-5 border-r border-t lg:border-t-0 border-bg-rule">
<div class="eyebrow muted text-[10px] mb-2">Reichweite</div>
<div class="eyebrow muted text-[10px] mb-2">{{ __('static.publish.fact_reach') }}</div>
<div class="font-serif text-[26px] lg:text-[28px] font-semibold leading-none text-ink tracking-[-0.4px]">DACH</div>
<div class="mt-2 text-[12px] leading-[1.4] text-ink-3">Deutschland · Österreich · Schweiz</div>
<div class="mt-2 text-[12px] leading-[1.4] text-ink-3">{{ __('static.publish.fact_reach_sub') }}</div>
</div>
<div class="py-5 pl-5 border-t lg:border-t-0 border-bg-rule">
<div class="eyebrow muted text-[10px] mb-2 inline-flex items-center gap-1.5">
<span class="w-1.5 h-1.5 rounded-full bg-brand pulse-dot"></span>Heute
<span class="w-1.5 h-1.5 rounded-full bg-brand pulse-dot"></span>{{ __('static.publish.fact_today') }}
</div>
<div class="font-serif text-[26px] lg:text-[28px] font-semibold leading-none text-ink tracking-[-0.4px]">{{ (int) $publishedToday }} / {{ (int) $inReview }}</div>
<div class="mt-2 text-[12px] leading-[1.4] text-ink-3">veröffentlicht / in Prüfung — Stand {{ $referenceNow->translatedFormat('j. M') }}</div>
<div class="mt-2 text-[12px] leading-[1.4] text-ink-3">{{ __('static.publish.fact_today_sub', ['date' => $referenceNow->translatedFormat('j. M')]) }}</div>
</div>
</div>
</div>
{{-- RIGHT — Beispiel-Mitteilung --}}
<aside class="lg:sticky lg:top-6">
<div class="text-[11px] tracking-[0.14em] uppercase text-ink-3 font-semibold mb-2.5">So sieht eine veröffentlichte Mitteilung aus</div>
<div class="text-[11px] tracking-[0.14em] uppercase text-ink-3 font-semibold mb-2.5">{{ __('static.publish.example_heading') }}</div>
<article class="bg-bg-elev border border-bg-rule px-6 py-6">
<div class="flex items-center justify-between mb-3.5">
<span class="bp-cat text-[10.5px]">{{ $exampleCategory ?? 'Industrie · Maschinenbau' }}</span>
<span class="bp-cat text-[10.5px]">{{ $exampleCategory ?? __('static.publish.example_category_fallback') }}</span>
<span class="inline-flex items-center gap-1.5 text-[10.5px] text-ok font-semibold">
<svg width="11" height="11" viewBox="0 0 11 11" aria-hidden="true"><path d="M2 5.5l2.2 2.2L9 3" stroke="currentColor" stroke-width="1.7" fill="none" stroke-linecap="round"/></svg>
Geprüft
{{ __('static.publish.example_verified') }}
</span>
</div>
<h3 class="font-serif m-0 text-[22px] leading-[1.2] font-semibold text-ink tracking-[-0.3px]">
@if ($example)
<a href="{{ $example->slug ? route('release.detail', ['slug' => $example->slug]) : '#' }}" class="hover:text-brand transition-colors">{{ $example->title }}</a>
@else
Mittelständischer Fertiger eröffnet Werk und schafft 84 Arbeitsplätze
{{ __('static.publish.example_title_fallback') }}
@endif
</h3>
<p class="font-serif mt-3.5 mb-0 text-[14px] leading-[1.55] text-ink-2">
{{ $example?->subtitle
?? \Illuminate\Support\Str::limit(strip_tags((string) $example?->text), 180)
?: 'Coesfeld · Die Brinkmann Präzisionstechnik GmbH hat einen Erweiterungsbau in Betrieb genommen. Investitionsvolumen: 9,4 Mio. Euro.' }}
?: __('static.publish.example_text_fallback') }}
</p>
<hr class="rule my-4" />
<div class="flex flex-wrap text-[11.5px] text-ink-3 gap-y-2 gap-x-[18px]">
<span>{{ $exampleCompany ?? 'Brinkmann Präzisionstechnik GmbH' }}</span>
<span>{{ $exampleCompany ?? __('static.publish.example_company_fallback') }}</span>
<span class="text-ink-4">·</span>
<span class="font-mono">{{ $examplePublishedAt ? $examplePublishedAt->translatedFormat('j. M Y, H:i') : '14. Mai 2026, 09:30' }}</span>
<span class="text-ink-4">·</span>
<span>{{ $exampleReadMinutes }} Min. Lesezeit</span>
<span>{{ __('static.publish.example_read_time', ['minutes' => $exampleReadMinutes]) }}</span>
</div>
</article>
<div class="mt-3.5 text-[11.5px] text-ink-3 leading-[1.5]">
Beispiel. Jede Mitteilung erhält eine permanente URL, ein strukturiertes Datenblatt und bleibt im durchsuchbaren Archiv.
{{ __('static.publish.example_note') }}
</div>
</aside>
</div>
@ -172,16 +200,16 @@
@if (! empty($newsroomNames))
<section class="bg-bg border-t border-bg-rule">
<div class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 py-6 grid items-baseline gap-4 lg:gap-7 grid-cols-1 lg:grid-cols-[auto_1fr_auto]">
<span class="eyebrow muted text-[10.5px] flex-shrink-0">Aktive Newsrooms zuletzt</span>
<span class="eyebrow muted text-[10.5px] flex-shrink-0">{{ __('static.publish.newsrooms_recent') }}</span>
<p class="m-0 text-[13.5px] leading-[1.6] text-ink-2">
@foreach ($newsroomNames as $name)
<span class="text-ink font-medium">{{ $name }}</span>@if (! $loop->last)<span class="text-ink-4"> · </span>@endif
@endforeach
@if ($newsroomTotalLabel > 0)
<span class="text-ink-3"> — und {{ number_format($newsroomTotalLabel, 0, ',', '.') }} weitere veröffentlichende Unternehmen.</span>
<span class="text-ink-3"> — {{ __('static.publish.newsrooms_more', ['count' => number_format($newsroomTotalLabel, 0, ',', '.')]) }}</span>
@endif
</p>
<a href="{{ route('newsrooms') }}" class="text-[12px] text-brand font-medium border-b border-brand pb-px flex-shrink-0 justify-self-start">Alle Newsrooms →</a>
<a href="{{ route('newsrooms') }}" class="text-[12px] text-brand font-medium border-b border-brand pb-px flex-shrink-0 justify-self-start">{{ __('static.publish.newsrooms_all') }} →</a>
</div>
</section>
@endif
@ -192,18 +220,13 @@
<div class="flex flex-col gap-3.5 mb-9">
<span class="font-mono text-[11px] text-ink-4 tracking-[0.1em]">§&nbsp;01</span>
<div>
<div class="eyebrow text-[10.5px] mb-2.5">Was uns ausmacht</div>
<h2 class="font-serif m-0 text-[28px] lg:text-[36px] font-semibold leading-[1.1] text-ink max-w-[720px] tracking-[-0.6px] text-balance">Vier Prinzipien, die diese Plattform tragen</h2>
<div class="eyebrow text-[10.5px] mb-2.5">{{ __('static.publish.trust_eyebrow') }}</div>
<h2 class="font-serif m-0 text-[28px] lg:text-[36px] font-semibold leading-[1.1] text-ink max-w-[720px] tracking-[-0.6px] text-balance">{{ __('static.publish.trust_heading') }}</h2>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 border-t border-bg-rule-strong">
@foreach ([
['k' => 'Qualität', 't' => 'Geprüfte Mitteilungen', 'd' => 'Jede Mitteilung durchläuft eine Qualitätsprüfung, bevor sie online geht. Keine SEO-Spam-Texte, keine reinen Werbeanzeigen.'],
['k' => 'Auffindbarkeit', 't' => 'Dauerhaft im Archiv', 'd' => 'Pressemitteilungen bleiben im Archiv – auch nach Jahren. Mitteilungen aus mehr als einem Jahrzehnt sind weiterhin abrufbar.'],
['k' => 'Konditionen', 't' => 'Faire Preise, keine Bindung', 'd' => 'Transparente Preise, kein Abo-Zwang, keine Vertragsfallen. Sie zahlen, was Sie einreichen.'],
['k' => 'Umgang mit Fehlern', 't' => 'Korrektur statt Löschung', 'd' => 'Fehler in einer Mitteilung? Wir korrigieren statt zu löschen – damit Verweise und Verlinkungen bestehen bleiben.'],
] as $i => $p)
@foreach ($trustPrinciples as $i => $p)
<div class="py-7 {{ $i % 4 !== 3 ? 'lg:border-r' : '' }} {{ $i % 2 === 0 ? 'sm:border-r lg:border-r' : '' }} border-bg-rule {{ $i > 0 ? 'border-t sm:border-t-0' : '' }} {{ $i >= 2 ? 'lg:border-t-0 sm:border-t' : '' }} px-0 sm:px-6 first:sm:pl-0 lg:first:pl-0 lg:pr-6">
<div class="eyebrow muted text-[10px] mb-3.5">{{ $p['k'] }}</div>
<h3 class="font-serif m-0 text-[22px] leading-[1.2] font-semibold tracking-[-0.3px]">{{ $p['t'] }}</h3>
@ -221,19 +244,15 @@
<div class="flex flex-col gap-3.5">
<span class="font-mono text-[11px] text-ink-4 tracking-[0.1em]">§&nbsp;02</span>
<div>
<div class="eyebrow text-[10.5px] mb-2.5">Ablauf</div>
<h2 class="font-serif m-0 text-[28px] lg:text-[36px] font-semibold leading-[1.1] text-ink max-w-[720px] tracking-[-0.6px] text-balance">So funktioniert's — drei Schritte</h2>
<div class="eyebrow text-[10.5px] mb-2.5">{{ __('static.publish.flow_eyebrow') }}</div>
<h2 class="font-serif m-0 text-[28px] lg:text-[36px] font-semibold leading-[1.1] text-ink max-w-[720px] tracking-[-0.6px] text-balance">{{ __('static.publish.flow_heading') }}</h2>
</div>
</div>
<div class="text-[12.5px] text-ink-3 max-w-[280px] lg:text-right leading-[1.55]">Keine Magic-Sprache. Was hier passiert, passiert sichtbar.</div>
<div class="text-[12.5px] text-ink-3 max-w-[280px] lg:text-right leading-[1.55]">{{ __('static.publish.flow_note') }}</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-px bg-bg-rule border-y border-bg-rule-strong">
@foreach ([
['n' => '01', 'meta' => '≈ 2 Minuten', 't' => 'Konto anlegen', 'd' => 'Mit E-Mail-Adresse und Passwort — Anmeldung wahlweise auch passwortlos per Magic-Link. Konto und Abrechnung liegen im zentralen Publisher-Bereich — ein Konto für beide Portale.'],
['n' => '02', 'meta' => 'werktags · innerhalb 24 h', 't' => 'Mitteilung einreichen', 'd' => 'Text, Bild, Ansprechpartner. Die automatisierte Inhaltsprüfung läuft direkt beim Einreichen mit.'],
['n' => '03', 'meta' => 'permanent · indexiert', 't' => 'Veröffentlichung', 'd' => 'Nach Freigabe online und im Archiv. Bei Bedarf jederzeit korrigierbar — die URL bleibt stabil, Verweise bestehen weiter.'],
] as $step)
@foreach ($flowSteps as $step)
<div class="bg-bg-elev px-7 pt-8 pb-9">
<div class="flex items-baseline justify-between mb-[22px]">
<span class="font-mono text-[13px] text-brand font-semibold">{{ $step['n'] }}</span>
@ -246,8 +265,7 @@
</div>
<p class="mt-[18px] mb-0 text-[12px] text-ink-3">
Die Veröffentlichung erfolgt über den zentralen Publisher-Bereich und erscheint ohne Mehrkosten
auf beiden Portalen — presseecho.de und businessportal24.com.
{{ __('static.publish.flow_footnote') }}
</p>
</div>
</section>
@ -259,23 +277,16 @@
<div class="flex flex-col gap-3.5">
<span class="font-mono text-[11px] text-ink-4 tracking-[0.1em]">§&nbsp;03</span>
<div>
<div class="eyebrow text-[10.5px] mb-2.5">Für wen</div>
<h2 class="font-serif m-0 text-[28px] lg:text-[36px] font-semibold leading-[1.1] text-ink max-w-[720px] tracking-[-0.6px] text-balance">Typische Anlässe einer Veröffentlichung</h2>
<div class="eyebrow text-[10.5px] mb-2.5">{{ __('static.publish.occasions_eyebrow') }}</div>
<h2 class="font-serif m-0 text-[28px] lg:text-[36px] font-semibold leading-[1.1] text-ink max-w-[720px] tracking-[-0.6px] text-balance">{{ __('static.publish.occasions_heading') }}</h2>
</div>
</div>
<div class="text-[12.5px] text-ink-3 max-w-[280px] lg:text-right leading-[1.55]">Anhaltspunkt für die Eignung. Eine Aufzählung, keine abschließende Liste.</div>
<div class="text-[12.5px] text-ink-3 max-w-[280px] lg:text-right leading-[1.55]">{{ __('static.publish.occasions_note') }}</div>
</div>
<div class="grid items-start gap-12 lg:gap-16 grid-cols-1 lg:grid-cols-[1.05fr_1fr]">
<ol class="list-none p-0 m-0">
@foreach ([
['t' => 'Neue Produkte oder Dienstleistungen', 'd' => 'Markteinführungen, Produktupdates, neue Services.'],
['t' => 'Personalien und Geschäftsleitungs-Wechsel', 'd' => 'Berufungen, Bestellungen, Verabschiedungen.'],
['t' => 'Auszeichnungen und Zertifizierungen', 'd' => 'Awards, ISO- und Branchen-Zertifikate, Audits.'],
['t' => 'Standort-Eröffnungen, Expansionen, Aufträge', 'd' => 'Werke, Niederlassungen, Großaufträge, Investitionen.'],
['t' => 'Veranstaltungs-Ankündigungen', 'd' => 'Tage der offenen Tür, Fachtage, Hauptversammlungen.'],
['t' => 'Studien und Marktanalysen', 'd' => 'Eigene Erhebungen, Trendberichte, Branchenkennzahlen.'],
] as $i => $anlass)
@foreach ($occasions as $i => $anlass)
<li class="grid items-start gap-4 py-[18px] {{ $loop->first ? 'border-t border-bg-rule-strong' : '' }} border-b border-bg-rule grid-cols-[44px_1fr]">
<span class="font-mono text-[12px] text-ink-4 pt-1">{{ str_pad((string) ($i + 1), 2, '0', STR_PAD_LEFT) }}</span>
<div>
@ -287,31 +298,30 @@
</ol>
<aside class="border border-bg-rule border-l-[3px] border-l-brand bg-bg-card-warm px-7 py-7 lg:px-8">
<div class="eyebrow text-[10.5px] mb-3.5">Wichtig zur Eingrenzung</div>
<div class="eyebrow text-[10.5px] mb-3.5">{{ __('static.publish.scope_eyebrow') }}</div>
<h3 class="font-serif m-0 text-[20px] lg:text-[22px] leading-[1.25] font-semibold tracking-[-0.3px] text-ink">
Nicht geeignet sind reine Werbeanzeigen, SEO-Linkbuilding-Texte oder Inhalte ohne nachvollziehbaren Pressewert.
{{ __('static.publish.scope_heading') }}
</h3>
<p class="mt-[18px] mb-0 text-[13.5px] leading-[1.6] text-ink-2">
Die Inhaltsprüfung sichert den Charakter der Plattform. Mitteilungen, die diese Schwelle nicht
erreichen, werden mit einer kurzen Begründung zurückgegeben — meist mit einem konkreten Korrekturvorschlag.
{{ __('static.publish.scope_text') }}
</p>
<hr class="rule my-5" />
<div class="grid gap-2.5 text-[13px]">
<div class="flex items-start gap-2.5">
<svg width="14" height="14" viewBox="0 0 14 14" class="mt-[3px] flex-shrink-0 text-ok" aria-hidden="true"><path d="M3 7l3 3 5-6" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="text-ink">Mitteilungen mit klarem Anlass und Quelle</span>
<span class="text-ink">{{ __('static.publish.scope_ok_1') }}</span>
</div>
<div class="flex items-start gap-2.5">
<svg width="14" height="14" viewBox="0 0 14 14" class="mt-[3px] flex-shrink-0 text-ok" aria-hidden="true"><path d="M3 7l3 3 5-6" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="text-ink">Ansprechpartner mit Funktion und Kontakt</span>
<span class="text-ink">{{ __('static.publish.scope_ok_2') }}</span>
</div>
<div class="flex items-start gap-2.5">
<svg width="14" height="14" viewBox="0 0 14 14" class="mt-[3px] flex-shrink-0 text-ink-3" aria-hidden="true"><path d="M3.5 3.5l7 7M10.5 3.5l-7 7" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
<span class="text-ink-3">Reine Verkaufs- und Rabatt-Texte</span>
<span class="text-ink-3">{{ __('static.publish.scope_no_1') }}</span>
</div>
<div class="flex items-start gap-2.5">
<svg width="14" height="14" viewBox="0 0 14 14" class="mt-[3px] flex-shrink-0 text-ink-3" aria-hidden="true"><path d="M3.5 3.5l7 7M10.5 3.5l-7 7" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
<span class="text-ink-3">Anonyme oder Quellen-lose Inhalte</span>
<span class="text-ink-3">{{ __('static.publish.scope_no_2') }}</span>
</div>
</div>
</aside>
@ -325,41 +335,41 @@
<div class="flex flex-col gap-3.5 mb-9">
<span class="font-mono text-[11px] text-ink-4 tracking-[0.1em]">§&nbsp;04</span>
<div>
<div class="eyebrow text-[10.5px] mb-2.5">Preise</div>
<h2 class="font-serif m-0 text-[28px] lg:text-[36px] font-semibold leading-[1.1] text-ink max-w-[720px] tracking-[-0.6px] text-balance">Sie zahlen, was Sie einreichen.</h2>
<div class="eyebrow text-[10.5px] mb-2.5">{{ __('static.publish.pricing') }}</div>
<h2 class="font-serif m-0 text-[28px] lg:text-[36px] font-semibold leading-[1.1] text-ink max-w-[720px] tracking-[-0.6px] text-balance">{{ __('static.publish.pricing_heading') }}</h2>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-[1.3fr_1fr_1fr] border-y border-bg-rule-strong">
<div class="pt-7 pb-8 lg:pr-8 lg:border-r border-bg-rule">
<div class="eyebrow muted text-[10px] mb-3">Logik</div>
<div class="eyebrow muted text-[10px] mb-3">{{ __('static.publish.pricing_logic') }}</div>
<p class="font-serif m-0 text-[18px] lg:text-[19px] leading-[1.5] text-ink tracking-[-0.1px]">
Einzeln ohne Abo veröffentlichen oder im Monats-Tarif — Inhaltsprüfung und beide Portale sind immer enthalten.
{{ __('static.publish.pricing_logic_text') }}
</p>
<ul class="list-none p-0 mt-5 text-[13px] text-ink-2 grid gap-2.5">
<li class="flex items-start gap-2.5"><span class="w-1.5 h-px bg-brand mt-2.5 flex-shrink-0"></span><span>Beide Portale ohne Mehrkosten.</span></li>
<li class="flex items-start gap-2.5"><span class="w-1.5 h-px bg-brand mt-2.5 flex-shrink-0"></span><span>Abgelehnte Mitteilungen kosten kein Kontingent.</span></li>
<li class="flex items-start gap-2.5"><span class="w-1.5 h-px bg-brand mt-2.5 flex-shrink-0"></span><span>Alle Tarife monatlich kündbar.</span></li>
<li class="flex items-start gap-2.5"><span class="w-1.5 h-px bg-brand mt-2.5 flex-shrink-0"></span><span>{{ __('static.publish.pricing_point_1') }}</span></li>
<li class="flex items-start gap-2.5"><span class="w-1.5 h-px bg-brand mt-2.5 flex-shrink-0"></span><span>{{ __('static.publish.pricing_point_2') }}</span></li>
<li class="flex items-start gap-2.5"><span class="w-1.5 h-px bg-brand mt-2.5 flex-shrink-0"></span><span>{{ __('static.publish.pricing_point_3') }}</span></li>
</ul>
</div>
<div class="pt-7 pb-8 lg:px-8 border-t lg:border-t-0 lg:border-r border-bg-rule flex flex-col justify-between">
<div>
<div class="eyebrow muted text-[10px] mb-3">Anker</div>
<div class="font-serif text-[16px] leading-[1.4] text-ink-2">Eine Veröffentlichung ohne Abo</div>
<div class="eyebrow muted text-[10px] mb-3">{{ __('static.publish.pricing_anchor') }}</div>
<div class="font-serif text-[16px] leading-[1.4] text-ink-2">{{ __('static.publish.pricing_single') }}</div>
<div class="mt-2 font-serif text-[40px] lg:text-[48px] font-semibold leading-none text-ink tracking-[-1.5px]">{{ $singlePmLabel }}&nbsp;€</div>
<div class="mt-2.5 text-[12px] text-ink-3">netto · zzgl. USt. · voller Leistungsumfang.</div>
<div class="mt-2.5 text-[12px] text-ink-3">{{ __('static.publish.pricing_single_note') }}</div>
</div>
@if ($cheapestPlanPriceLabel)
<div class="mt-5 text-[11.5px] text-ink-3 leading-[1.5]">Regelmäßig veröffentlichen: Tarife ab {{ $cheapestPlanPriceLabel }}&nbsp;€ / Monat mit {{ $cheapestPlan->press_release_quota }} Mitteilungen.</div>
<div class="mt-5 text-[11.5px] text-ink-3 leading-[1.5]">{{ __('static.publish.pricing_plans_note', ['price' => $cheapestPlanPriceLabel, 'quota' => $cheapestPlan->press_release_quota]) }}</div>
@endif
</div>
<div class="pt-7 pb-8 lg:pl-8 border-t lg:border-t-0 border-bg-rule flex flex-col justify-between">
<div>
<div class="eyebrow muted text-[10px] mb-3">Vollständige Übersicht</div>
<p class="m-0 text-[14px] leading-[1.6] text-ink-2">Alle Tarife, Extra-Kontingente, Boost-Platzierungen und Agentur-Konditionen finden Sie in der Preisübersicht.</p>
<div class="eyebrow muted text-[10px] mb-3">{{ __('static.publish.pricing_overview') }}</div>
<p class="m-0 text-[14px] leading-[1.6] text-ink-2">{{ __('static.publish.pricing_overview_text') }}</p>
</div>
<a href="{{ route('preise') }}" class="mt-5 inline-flex items-center gap-2 text-[13.5px] font-semibold text-brand border-b border-brand self-start pb-0.5">
Zur Preisübersicht
{{ __('static.publish.pricing_overview_cta') }}
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true"><path d="M3 6h6M6 3l3 3-3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
</div>
@ -374,27 +384,21 @@
<div class="flex flex-col gap-3.5">
<span class="font-mono text-[11px] text-ink-4 tracking-[0.1em]">§&nbsp;05</span>
<div>
<div class="eyebrow text-[10.5px] mb-2.5">Häufige Fragen</div>
<h2 class="font-serif m-0 text-[28px] lg:text-[36px] font-semibold leading-[1.1] text-ink max-w-[720px] tracking-[-0.6px] text-balance">Kurz beantwortet</h2>
<div class="eyebrow text-[10.5px] mb-2.5">{{ __('static.publish.faq_eyebrow') }}</div>
<h2 class="font-serif m-0 text-[28px] lg:text-[36px] font-semibold leading-[1.1] text-ink max-w-[720px] tracking-[-0.6px] text-balance">{{ __('static.publish.faq_heading') }}</h2>
</div>
</div>
</div>
<div class="grid items-start gap-10 lg:gap-16 grid-cols-1 lg:grid-cols-[1fr_1.6fr]">
<div class="lg:sticky lg:top-6 pt-2">
<p class="font-serif m-0 text-[16px] leading-[1.55] text-ink-2">Sie finden hier keine Antwort?</p>
<a href="mailto:{{ config('domains.domains.'.config('app.theme').'.brand.contact_email') ?? 'info@businessportal24.com' }}" class="mt-3.5 inline-flex items-center gap-1.5 text-[13.5px] font-medium text-brand border-b border-brand pb-0.5">Direkt Kontakt aufnehmen</a>
<div class="mt-3.5 text-[12px] text-ink-3 leading-[1.55]">Antwort werktags per E-Mail</div>
<p class="font-serif m-0 text-[16px] leading-[1.55] text-ink-2">{{ __('static.publish.faq_no_answer') }}</p>
<button type="button" x-data @click="$dispatch('open-contact-modal')" class="mt-3.5 inline-flex items-center gap-1.5 text-[13.5px] font-medium text-brand border-b border-brand pb-0.5 cursor-pointer">{{ __('static.publish.faq_contact') }}</button>
<div class="mt-3.5 text-[12px] text-ink-3 leading-[1.55]">{{ __('static.publish.faq_response_note') }}</div>
</div>
<div class="border-t border-bg-rule-strong">
@foreach ([
['q' => 'Wie schnell wird meine Mitteilung veröffentlicht?', 'a' => 'Werktags üblicherweise innerhalb von 24 Stunden nach Einreichung. Bei zeitkritischen Mitteilungen ist eine Vorab-Absprache möglich.'],
['q' => 'Bleibt meine Mitteilung dauerhaft online?', 'a' => 'Ja. Mitteilungen werden nicht gelöscht. Korrekturen sind jederzeit möglich — die URL bleibt erhalten.'],
['q' => 'Kann ich auch auf presseecho.de veröffentlichen?', 'a' => 'Ja — jede Mitteilung erscheint automatisch auf beiden Portalen, presseecho.de und businessportal24.com, ohne Mehrkosten und ohne doppelte Eingabe.'],
['q' => 'Brauche ich ein Abo?', 'a' => 'Nein. Eine Einzel-Pressemitteilung buchen Sie ohne Abo zum Festpreis; für regelmäßige Veröffentlichungen lohnen sich die monatlich kündbaren Tarife.'],
['q' => 'Was passiert bei einem Fehler in der Mitteilung?', 'a' => 'Korrektur statt Löschung: Inhalte werden aktualisiert, die URL bleibt erhalten, Verlinkungen und Verweise bestehen weiter.'],
] as $i => $faq)
@foreach ($publishFaqs as $i => $faq)
<details class="group border-b border-bg-rule" {{ $i === 0 ? 'open' : '' }}>
<summary class="grid items-center gap-4 py-[22px] cursor-pointer list-none grid-cols-[44px_1fr_24px] [&::-webkit-details-marker]:hidden">
<span class="font-mono text-[12px] text-ink-4">{{ str_pad((string) ($i + 1), 2, '0', STR_PAD_LEFT) }}</span>
@ -417,21 +421,20 @@
<div class="relative overflow-hidden grid items-end text-ink-on-dark bg-topbar-grad gap-8 lg:gap-14 grid-cols-1 lg:grid-cols-[1.3fr_1fr] px-7 py-10 lg:px-14 lg:py-14">
<span class="absolute left-0 top-0 bottom-0 w-1 bg-brand" aria-hidden="true"></span>
<div>
<div class="eyebrow mb-3.5 text-[10.5px] text-brand-soft">Mitteilung einreichen · Schritt eins</div>
<h2 class="font-serif m-0 text-[34px] lg:text-[48px] font-semibold leading-[1.08] text-ink-on-dark max-w-[700px] tracking-[-0.9px] text-balance">Bereit, Ihre Mitteilung einzureichen?</h2>
<div class="eyebrow mb-3.5 text-[10.5px] text-brand-soft">{{ __('static.publish.cta_eyebrow') }}</div>
<h2 class="font-serif m-0 text-[34px] lg:text-[48px] font-semibold leading-[1.08] text-ink-on-dark max-w-[700px] tracking-[-0.9px] text-balance">{{ __('static.publish.cta_heading') }}</h2>
<p class="font-serif mt-5 mb-0 text-[16px] lg:text-[17px] leading-[1.55] text-ink-on-dark-2 max-w-[580px]">
Konto in zwei Minuten anlegen, Text und Bild einreichen, nach bestandener Inhaltsprüfung veröffentlicht.
Einmaliges Konto — beide Portale (businessportal24 &amp; presseecho.de) nutzbar.
{{ __('static.publish.cta_text') }}
</p>
</div>
<div class="flex flex-col items-start gap-3.5">
<a href="{{ $publisherUrl }}/register"
class="inline-flex items-center gap-2 px-7 py-4 text-[15px] font-semibold text-white bg-brand hover:bg-brand-deep transition-colors">
Zum Publisher-Bereich
{{ __('static.publish.hub_cta') }}
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true"><path d="M4 8L8.5 3.5M8.5 3.5H5M8.5 3.5V7" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<span class="text-[12px] text-ink-on-dark-2">Login per Magic-Link · ohne Vertragsbindung</span>
<a href="{{ route('web.home') }}" class="mt-1.5 text-[13px] text-ink-on-dark border-b border-white/35 pb-0.5">Oder zuerst Beispiele ansehen →</a>
<span class="text-[12px] text-ink-on-dark-2">{{ __('static.publish.hub_note') }}</span>
<a href="{{ route('web.home') }}" class="mt-1.5 text-[13px] text-ink-on-dark border-b border-white/35 pb-0.5">{{ __('static.publish.cta_examples') }}</a>
</div>
</div>
</div>