feat: centralize contact cms defaults

This commit is contained in:
syntaxbullet
2026-06-22 12:18:17 +02:00
parent c21f0d0c8f
commit 1e164d7624
4 changed files with 135 additions and 175 deletions

View File

@@ -1,6 +1,6 @@
import type { Field } from 'payload'
import { contactFormContent } from '@/spa/contactFormContent'
import { contactContent } from '@/spa/contactContent'
const uploadField = (name: string, label: string, description?: string): Field => ({
name,
@@ -45,14 +45,14 @@ export const contactFields: Field[] = [
type: 'group',
admin: sectionAdmin('Top image, eyebrow, headline, and intro copy.'),
fields: [
uploadField('backgroundImage', 'Background image', 'Current frontend image: /images/gala-dinner.jpg'),
text('imageAlt', 'Image alt text', 'BMP Gala'),
text('eyebrow', 'Eyebrow', 'Dialog & Service'),
textarea('heading', 'Headline', 'Wir sind\nfür Sie da.'),
uploadField('backgroundImage', 'Background image', `Current frontend image: /images/${contactContent.hero.backgroundImageFilename}`),
text('imageAlt', 'Image alt text', contactContent.hero.imageAlt),
text('eyebrow', 'Eyebrow', contactContent.hero.eyebrow),
textarea('heading', 'Headline', contactContent.hero.heading),
textarea(
'description',
'Intro paragraph',
'Fragen zur Bewerbung, zur Gala oder zu Partnermöglichkeiten unser Team begleitet Sie persönlich.',
contactContent.hero.description,
),
],
},
@@ -62,18 +62,13 @@ export const contactFields: Field[] = [
type: 'group',
admin: sectionAdmin('Left direct-contact list plus the gold contact form panel.'),
fields: [
text('eyebrow', 'Direct contact eyebrow', 'Direktkontakt'),
textarea('heading', 'Direct contact heading', 'Kontakt­möglichkeiten'),
text('eyebrow', 'Direct contact eyebrow', contactContent.info.eyebrow),
textarea('heading', 'Direct contact heading', contactContent.info.heading),
{
name: 'items',
label: 'Contact methods',
type: 'array',
defaultValue: [
{ icon: 'mapPin', title: 'Geschäftsstelle', lines: 'Maximilianstraße 42\n80538 München' },
{ icon: 'mail', title: 'E-Mail', lines: 'info@bmp-bayern.de' },
{ icon: 'phone', title: 'Telefon', lines: '+49 89 123 456 78' },
{ icon: 'clock', title: 'Bürozeiten', lines: 'Mo Fr: 09:00 17:00 Uhr' },
],
defaultValue: contactContent.info.items,
fields: [
{
name: 'icon',
@@ -91,13 +86,13 @@ export const contactFields: Field[] = [
textarea('lines', 'Lines'),
],
},
text('nextAwardLabel', 'Next award label', 'Nächste Preisverleihung:'),
text('nextAwardDate', 'Next award date', '22. Oktober 2026'),
uploadField('formImage', 'Desktop form image', 'Current frontend image: /images/preistraeger-gruppe.jpg'),
text('formImageAlt', 'Form image alt text', 'BMP Gala 2025'),
text('formImageLabel', 'Form image label', 'Gala 2025 München'),
text('formEyebrow', 'Form eyebrow', 'Kontaktformular'),
text('formTitle', 'Form title', 'Schreiben Sie uns'),
text('nextAwardLabel', 'Next award label', contactContent.info.nextAwardLabel),
text('nextAwardDate', 'Next award date', contactContent.info.nextAwardDate),
uploadField('formImage', 'Desktop form image', `Current frontend image: /images/${contactContent.info.formImageFilename}`),
text('formImageAlt', 'Form image alt text', contactContent.info.formImageAlt),
text('formImageLabel', 'Form image label', contactContent.info.formImageLabel),
text('formEyebrow', 'Form eyebrow', contactContent.info.formEyebrow),
text('formTitle', 'Form title', contactContent.info.formTitle),
{
name: 'formCopy',
label: 'Contact form copy',
@@ -108,14 +103,14 @@ export const contactFields: Field[] = [
name: 'steps',
label: 'Step labels',
type: 'array',
defaultValue: contactFormContent.steps,
defaultValue: contactContent.info.formCopy.steps,
fields: [text('label', 'Label')],
},
{
name: 'subjects',
label: 'Subject choices',
type: 'array',
defaultValue: contactFormContent.subjects,
defaultValue: contactContent.info.formCopy.subjects,
fields: [text('value', 'Value'), text('label', 'Label'), textarea('desc', 'Description')],
},
{
@@ -123,9 +118,9 @@ export const contactFields: Field[] = [
label: 'Prompts',
type: 'group',
fields: [
text('subject', 'Subject step prompt', contactFormContent.prompts.subject),
text('contact', 'Contact step prompt', contactFormContent.prompts.contact),
text('message', 'Message step prompt', contactFormContent.prompts.message),
text('subject', 'Subject step prompt', contactContent.info.formCopy.prompts.subject),
text('contact', 'Contact step prompt', contactContent.info.formCopy.prompts.contact),
text('message', 'Message step prompt', contactContent.info.formCopy.prompts.message),
],
},
{
@@ -133,12 +128,12 @@ export const contactFields: Field[] = [
label: 'Fields',
type: 'group',
fields: [
text('nameLabel', 'Name label', contactFormContent.fields.nameLabel),
text('namePlaceholder', 'Name placeholder', contactFormContent.fields.namePlaceholder),
text('emailLabel', 'Email label', contactFormContent.fields.emailLabel),
text('emailPlaceholder', 'Email placeholder', contactFormContent.fields.emailPlaceholder),
text('messageLabel', 'Message label', contactFormContent.fields.messageLabel),
text('messagePlaceholder', 'Message placeholder', contactFormContent.fields.messagePlaceholder),
text('nameLabel', 'Name label', contactContent.info.formCopy.fields.nameLabel),
text('namePlaceholder', 'Name placeholder', contactContent.info.formCopy.fields.namePlaceholder),
text('emailLabel', 'Email label', contactContent.info.formCopy.fields.emailLabel),
text('emailPlaceholder', 'Email placeholder', contactContent.info.formCopy.fields.emailPlaceholder),
text('messageLabel', 'Message label', contactContent.info.formCopy.fields.messageLabel),
text('messagePlaceholder', 'Message placeholder', contactContent.info.formCopy.fields.messagePlaceholder),
],
},
{
@@ -146,10 +141,10 @@ export const contactFields: Field[] = [
label: 'Validation messages',
type: 'group',
fields: [
text('nameRequired', 'Name required', contactFormContent.validation.nameRequired),
text('emailRequired', 'Email required', contactFormContent.validation.emailRequired),
text('emailInvalid', 'Email invalid', contactFormContent.validation.emailInvalid),
text('messageRequired', 'Message required', contactFormContent.validation.messageRequired),
text('nameRequired', 'Name required', contactContent.info.formCopy.validation.nameRequired),
text('emailRequired', 'Email required', contactContent.info.formCopy.validation.emailRequired),
text('emailInvalid', 'Email invalid', contactContent.info.formCopy.validation.emailInvalid),
text('messageRequired', 'Message required', contactContent.info.formCopy.validation.messageRequired),
],
},
{
@@ -157,9 +152,9 @@ export const contactFields: Field[] = [
label: 'Navigation labels',
type: 'group',
fields: [
text('back', 'Back', contactFormContent.navigation.back),
text('next', 'Next', contactFormContent.navigation.next),
text('submit', 'Submit', contactFormContent.navigation.submit),
text('back', 'Back', contactContent.info.formCopy.navigation.back),
text('next', 'Next', contactContent.info.formCopy.navigation.next),
text('submit', 'Submit', contactContent.info.formCopy.navigation.submit),
],
},
{
@@ -167,9 +162,9 @@ export const contactFields: Field[] = [
label: 'Success copy',
type: 'group',
fields: [
text('heading', 'Heading', contactFormContent.success.heading),
text('prefix', 'Prefix', contactFormContent.success.prefix),
text('suffixBeforeEmail', 'Suffix before email', contactFormContent.success.suffixBeforeEmail),
text('heading', 'Heading', contactContent.info.formCopy.success.heading),
text('prefix', 'Prefix', contactContent.info.formCopy.success.prefix),
text('suffixBeforeEmail', 'Suffix before email', contactContent.info.formCopy.success.suffixBeforeEmail),
],
},
],
@@ -182,17 +177,13 @@ export const contactFields: Field[] = [
type: 'group',
admin: sectionAdmin('Dark 2026 timeline with three deadline columns.'),
fields: [
text('watermark', 'Large background year', '2026'),
text('eyebrow', 'Eyebrow', 'Fahrplan 2026'),
text('watermark', 'Large background year', contactContent.deadlines.watermark),
text('eyebrow', 'Eyebrow', contactContent.deadlines.eyebrow),
{
name: 'items',
label: 'Deadline items',
type: 'array',
defaultValue: [
{ step: '01', label: 'Bewerbungsschluss', date: '30. Juni 2026', desc: 'Letzte Möglichkeit zur Einreichung Ihrer Unterlagen.' },
{ step: '02', label: 'Nominierung', date: '15. August 2026', desc: 'Bekanntgabe der Nominierten nach Jury-Sichtung.' },
{ step: '03', label: 'Preisverleihung', date: '22. Oktober 2026', desc: 'Festliche Gala in München.' },
],
defaultValue: contactContent.deadlines.items,
fields: [text('step', 'Step'), text('label', 'Label'), text('date', 'Date'), textarea('desc', 'Description')],
},
],
@@ -203,30 +194,13 @@ export const contactFields: Field[] = [
type: 'group',
admin: sectionAdmin('FAQ heading and accordion entries.'),
fields: [
text('eyebrow', 'Eyebrow', 'Antworten'),
textarea('heading', 'Heading', 'Häufige\nFragen'),
text('eyebrow', 'Eyebrow', contactContent.faq.eyebrow),
textarea('heading', 'Heading', contactContent.faq.heading),
{
name: 'items',
label: 'FAQ items',
type: 'array',
defaultValue: [
{
q: 'Was kostet die Teilnahme am BMP?',
a: 'Die Teilnahme am Bayerischen Mittelstandspreis ist für alle Unternehmen vollständig kostenfrei. Wir finanzieren uns durch Partner und Sponsoren, um exzellenten Mittelstand ohne finanzielle Hürden zu fördern.',
},
{
q: 'Nach welchen Kriterien wird bewertet?',
a: 'Die Bewertung basiert auf vier Säulen: Zukunftsfähigkeit & Innovation, Nachhaltigkeit & Verantwortung, Unternehmenskultur sowie Regionale Verwurzelung. Jede Säule fließt mit 25 % in das Gesamtergebnis ein.',
},
{
q: 'Wie läuft der Bewerbungsprozess ab?',
a: 'Der Prozess ist mehrstufig: 1. Online-Bewerbung oder Vorschlag, 2. Formale Prüfung & erste Jury-Sichtung, 3. Fach-Audits bei den Finalisten vor Ort, 4. Finale Jury-Sitzung und abschließende Preisverleihung.',
},
{
q: 'Wie lange dauert die Bewerbung?',
a: 'Dank unseres optimierten Bewerbungsformulars benötigen Sie für die erste Einreichung ca. 3045 Minuten. Tiefergehende Unterlagen werden erst in der Audit-Phase angefordert.',
},
],
defaultValue: contactContent.faq.items,
fields: [textarea('q', 'Question'), textarea('a', 'Answer')],
},
],

View File

@@ -3,7 +3,7 @@ import 'dotenv/config'
import config from '@payload-config'
import { getPayload, type Payload } from 'payload'
import { contactFormContent } from '@/spa/contactFormContent'
import { contactContent } from '@/spa/contactContent'
const mediaByFilename = async (payload: Payload, filename: string) => {
const result = await payload.find({
@@ -33,9 +33,11 @@ async function main() {
if (!page) throw new Error('Contact page not found. Expected spaPath=/kontakt or slug=kontakt/contact.')
const [heroImage, formImage] = await Promise.all([
mediaByFilename(payload, 'gala-dinner.jpg'),
mediaByFilename(payload, 'preistraeger-gruppe.jpg'),
mediaByFilename(payload, contactContent.hero.backgroundImageFilename),
mediaByFilename(payload, contactContent.info.formImageFilename),
])
const { backgroundImageFilename: _heroFilename, ...heroContent } = contactContent.hero
const { formImageFilename: _formImageFilename, ...infoContent } = contactContent.info
await payload.update({
collection: 'pages',
@@ -45,63 +47,17 @@ async function main() {
data: {
contact: {
hero: {
...heroContent,
backgroundImage: heroImage,
imageAlt: 'BMP Gala',
eyebrow: 'Dialog & Service',
heading: 'Wir sind\nfür Sie da.',
description: 'Fragen zur Bewerbung, zur Gala oder zu Partnermöglichkeiten unser Team begleitet Sie persönlich.',
},
info: {
eyebrow: 'Direktkontakt',
heading: 'Kontakt­möglichkeiten',
items: [
{ icon: 'mapPin', title: 'Geschäftsstelle', lines: 'Maximilianstraße 42\n80538 München' },
{ icon: 'mail', title: 'E-Mail', lines: 'info@bmp-bayern.de' },
{ icon: 'phone', title: 'Telefon', lines: '+49 89 123 456 78' },
{ icon: 'clock', title: 'Bürozeiten', lines: 'Mo Fr: 09:00 17:00 Uhr' },
],
nextAwardLabel: 'Nächste Preisverleihung:',
nextAwardDate: '22. Oktober 2026',
...infoContent,
formImage,
formImageAlt: 'BMP Gala 2025',
formImageLabel: 'Gala 2025 München',
formEyebrow: 'Kontaktformular',
formTitle: 'Schreiben Sie uns',
formCopy: contactFormContent,
},
deadlines: {
watermark: '2026',
eyebrow: 'Fahrplan 2026',
items: [
{ step: '01', label: 'Bewerbungsschluss', date: '30. Juni 2026', desc: 'Letzte Möglichkeit zur Einreichung Ihrer Unterlagen.' },
{ step: '02', label: 'Nominierung', date: '15. August 2026', desc: 'Bekanntgabe der Nominierten nach Jury-Sichtung.' },
{ step: '03', label: 'Preisverleihung', date: '22. Oktober 2026', desc: 'Festliche Gala in München.' },
],
},
faq: {
eyebrow: 'Antworten',
heading: 'Häufige\nFragen',
items: [
{
q: 'Was kostet die Teilnahme am BMP?',
a: 'Die Teilnahme am Bayerischen Mittelstandspreis ist für alle Unternehmen vollständig kostenfrei. Wir finanzieren uns durch Partner und Sponsoren, um exzellenten Mittelstand ohne finanzielle Hürden zu fördern.',
},
{
q: 'Nach welchen Kriterien wird bewertet?',
a: 'Die Bewertung basiert auf vier Säulen: Zukunftsfähigkeit & Innovation, Nachhaltigkeit & Verantwortung, Unternehmenskultur sowie Regionale Verwurzelung. Jede Säule fließt mit 25 % in das Gesamtergebnis ein.',
},
{
q: 'Wie läuft der Bewerbungsprozess ab?',
a: 'Der Prozess ist mehrstufig: 1. Online-Bewerbung oder Vorschlag, 2. Formale Prüfung & erste Jury-Sichtung, 3. Fach-Audits bei den Finalisten vor Ort, 4. Finale Jury-Sitzung und abschließende Preisverleihung.',
},
{
q: 'Wie lange dauert die Bewerbung?',
a: 'Dank unseres optimierten Bewerbungsformulars benötigen Sie für die erste Einreichung ca. 3045 Minuten. Tiefergehende Unterlagen werden erst in der Audit-Phase angefordert.',
},
],
},
},
deadlines: contactContent.deadlines,
faq: contactContent.faq,
},
} as never,
})
payload.logger.info(`Preloaded contact CMS fields for page ${page.id}`)

61
src/spa/contactContent.ts Normal file
View File

@@ -0,0 +1,61 @@
import { contactFormContent } from '@/spa/contactFormContent'
export const contactContent = {
hero: {
backgroundImageFilename: 'gala-dinner.jpg',
imageAlt: 'BMP Gala',
eyebrow: 'Dialog & Service',
heading: 'Wir sind\nfür Sie da.',
description: 'Fragen zur Bewerbung, zur Gala oder zu Partnermöglichkeiten unser Team begleitet Sie persönlich.',
},
info: {
eyebrow: 'Direktkontakt',
heading: 'Kontakt­möglichkeiten',
items: [
{ icon: 'mapPin', title: 'Geschäftsstelle', lines: 'Maximilianstraße 42\n80538 München' },
{ icon: 'mail', title: 'E-Mail', lines: 'info@bmp-bayern.de' },
{ icon: 'phone', title: 'Telefon', lines: '+49 89 123 456 78' },
{ icon: 'clock', title: 'Bürozeiten', lines: 'Mo Fr: 09:00 17:00 Uhr' },
],
nextAwardLabel: 'Nächste Preisverleihung:',
nextAwardDate: '22. Oktober 2026',
formImageFilename: 'preistraeger-gruppe.jpg',
formImageAlt: 'BMP Gala 2025',
formImageLabel: 'Gala 2025 München',
formEyebrow: 'Kontaktformular',
formTitle: 'Schreiben Sie uns',
formCopy: contactFormContent,
},
deadlines: {
watermark: '2026',
eyebrow: 'Fahrplan 2026',
items: [
{ step: '01', label: 'Bewerbungsschluss', date: '30. Juni 2026', desc: 'Letzte Möglichkeit zur Einreichung Ihrer Unterlagen.' },
{ step: '02', label: 'Nominierung', date: '15. August 2026', desc: 'Bekanntgabe der Nominierten nach Jury-Sichtung.' },
{ step: '03', label: 'Preisverleihung', date: '22. Oktober 2026', desc: 'Festliche Gala in München.' },
],
},
faq: {
eyebrow: 'Antworten',
heading: 'Häufige\nFragen',
items: [
{
q: 'Was kostet die Teilnahme am BMP?',
a: 'Die Teilnahme am Bayerischen Mittelstandspreis ist für alle Unternehmen vollständig kostenfrei. Wir finanzieren uns durch Partner und Sponsoren, um exzellenten Mittelstand ohne finanzielle Hürden zu fördern.',
},
{
q: 'Nach welchen Kriterien wird bewertet?',
a: 'Die Bewertung basiert auf vier Säulen: Zukunftsfähigkeit & Innovation, Nachhaltigkeit & Verantwortung, Unternehmenskultur sowie Regionale Verwurzelung. Jede Säule fließt mit 25 % in das Gesamtergebnis ein.',
},
{
q: 'Wie läuft der Bewerbungsprozess ab?',
a: 'Der Prozess ist mehrstufig: 1. Online-Bewerbung oder Vorschlag, 2. Formale Prüfung & erste Jury-Sichtung, 3. Fach-Audits bei den Finalisten vor Ort, 4. Finale Jury-Sitzung und abschließende Preisverleihung.',
},
{
q: 'Wie lange dauert die Bewerbung?',
a: 'Dank unseres optimierten Bewerbungsformulars benötigen Sie für die erste Einreichung ca. 3045 Minuten. Tiefergehende Unterlagen werden erst in der Audit-Phase angefordert.',
},
],
},
}

View File

@@ -7,6 +7,7 @@ import { mediaAlt, mediaUrl } from '@/spa/cmsMediaField';
import { useCmsRoute } from '@/spa/cmsRoute';
import Image from '@/spa/components/ui/UnoptimizedImage'
import type { Page } from '@/payload-types';
import { contactContent } from '@/spa/contactContent';
const NAVY = '#111D55';
const GOLD = '#EFBF04';
@@ -17,7 +18,7 @@ type ContactCms = NonNullable<Page['contact']>;
type ContactItem = NonNullable<NonNullable<ContactCms['info']>['items']>[number] | {
icon: string;
title: string;
lines: string[];
lines: string | string[];
};
type DeadlineItem = NonNullable<NonNullable<ContactCms['deadlines']>['items']>[number];
type FaqItem = NonNullable<NonNullable<ContactCms['faq']>['items']>[number];
@@ -39,38 +40,6 @@ const ICONS = {
clock: Clock,
};
const DEFAULT_FAQS = [
{
q: 'Was kostet die Teilnahme am BMP?',
a: 'Die Teilnahme am Bayerischen Mittelstandspreis ist für alle Unternehmen vollständig kostenfrei. Wir finanzieren uns durch Partner und Sponsoren, um exzellenten Mittelstand ohne finanzielle Hürden zu fördern.',
},
{
q: 'Nach welchen Kriterien wird bewertet?',
a: 'Die Bewertung basiert auf vier Säulen: Zukunftsfähigkeit & Innovation, Nachhaltigkeit & Verantwortung, Unternehmenskultur sowie Regionale Verwurzelung. Jede Säule fließt mit 25 % in das Gesamtergebnis ein.',
},
{
q: 'Wie läuft der Bewerbungsprozess ab?',
a: 'Der Prozess ist mehrstufig: 1. Online-Bewerbung oder Vorschlag, 2. Formale Prüfung & erste Jury-Sichtung, 3. Fach-Audits bei den Finalisten vor Ort, 4. Finale Jury-Sitzung und abschließende Preisverleihung.',
},
{
q: 'Wie lange dauert die Bewerbung?',
a: 'Dank unseres optimierten Bewerbungsformulars benötigen Sie für die erste Einreichung ca. 3045 Minuten. Tiefergehende Unterlagen werden erst in der Audit-Phase angefordert.',
},
];
const DEFAULT_DEADLINES = [
{ step: '01', label: 'Bewerbungsschluss', date: '30. Juni 2026', desc: 'Letzte Möglichkeit zur Einreichung Ihrer Unterlagen.' },
{ step: '02', label: 'Nominierung', date: '15. August 2026', desc: 'Bekanntgabe der Nominierten nach Jury-Sichtung.' },
{ step: '03', label: 'Preisverleihung', date: '22. Oktober 2026', desc: 'Festliche Gala in München.' },
];
const DEFAULT_CONTACT_ITEMS = [
{ icon: 'mapPin', title: 'Geschäftsstelle', lines: ['Maximilianstraße 42', '80538 München'] },
{ icon: 'mail', title: 'E-Mail', lines: ['info@bmp-bayern.de'] },
{ icon: 'phone', title: 'Telefon', lines: ['+49 89 123 456 78'] },
{ icon: 'clock', title: 'Bürozeiten', lines: ['Mo Fr: 09:00 17:00 Uhr'] },
];
const Contact: React.FC = () => {
const [openFaq, setOpenFaq] = useState<number | null>(0);
const isMobile = useIsMobile();
@@ -79,9 +48,9 @@ const Contact: React.FC = () => {
const info = contact.info || {};
const deadlines = contact.deadlines || {};
const faq = contact.faq || {};
const contactItems = fallbackArray<ContactItem>(info.items, DEFAULT_CONTACT_ITEMS);
const deadlineItems = fallbackArray<DeadlineItem>(deadlines.items, DEFAULT_DEADLINES);
const faqItems = fallbackArray<FaqItem>(faq.items, DEFAULT_FAQS);
const contactItems = fallbackArray<ContactItem>(info.items, contactContent.info.items);
const deadlineItems = fallbackArray<DeadlineItem>(deadlines.items, contactContent.deadlines.items);
const faqItems = fallbackArray<FaqItem>(faq.items, contactContent.faq.items);
return (
<div style={{ background: CREAM }}>
@@ -89,8 +58,8 @@ const Contact: React.FC = () => {
{/* ── Hero ───────────────────────────────────────────────────────── */}
<section style={{ position: 'relative', height: '68vh', minHeight: 520, overflow: 'hidden', display: 'flex', alignItems: 'flex-end' }}>
<Image unoptimized
src={mediaUrl(hero.backgroundImage, '/images/gala-dinner.jpg')}
alt={mediaAlt(hero.backgroundImage, fallbackText(hero.imageAlt, 'BMP Gala'))}
src={mediaUrl(hero.backgroundImage, `/images/${contactContent.hero.backgroundImageFilename}`)}
alt={mediaAlt(hero.backgroundImage, fallbackText(hero.imageAlt, contactContent.hero.imageAlt))}
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 40%' }}
/>
{/* Diagonal overlay */}
@@ -100,13 +69,13 @@ const Contact: React.FC = () => {
<div style={{ position: 'relative', zIndex: 2, width: '100%', paddingBottom: isMobile ? '3rem' : '5rem', paddingLeft: isMobile ? 24 : 80, paddingRight: isMobile ? 24 : 80 }}>
<p style={{ fontSize: 11, fontWeight: 800, letterSpacing: '0.28em', textTransform: 'uppercase', color: GOLD, marginBottom: 16 }}>
{fallbackText(hero.eyebrow, 'Dialog & Service')}
{fallbackText(hero.eyebrow, contactContent.hero.eyebrow)}
</p>
<h1 style={{ fontSize: isMobile ? 'clamp(2rem, 10vw, 3rem)' : 'clamp(3rem, 7vw, 6rem)', fontWeight: 900, lineHeight: 1.0, color: '#fff', letterSpacing: '-0.03em', marginBottom: 24, maxWidth: 640 }}>
<Lines text={fallbackText(hero.heading, 'Wir sind\nfür Sie da.')} />
<Lines text={fallbackText(hero.heading, contactContent.hero.heading)} />
</h1>
<p style={{ fontSize: 18, color: 'rgba(255,255,255,0.72)', maxWidth: 480, lineHeight: 1.65, fontWeight: 400 }}>
{fallbackText(hero.description, 'Fragen zur Bewerbung, zur Gala oder zu Partnermöglichkeiten unser Team begleitet Sie persönlich.')}
{fallbackText(hero.description, contactContent.hero.description)}
</p>
</div>
</section>
@@ -121,11 +90,11 @@ const Contact: React.FC = () => {
{/* Left info panel */}
<div style={{ padding: isMobile ? '48px 24px' : '88px 56px 88px 64px', display: 'flex', flexDirection: 'column', justifyContent: 'center', position: 'relative' }}>
<p style={{ fontSize: 10, fontWeight: 800, letterSpacing: '0.28em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.4)', marginBottom: 20 }}>
{fallbackText(info.eyebrow, 'Direktkontakt')}
{fallbackText(info.eyebrow, contactContent.info.eyebrow)}
</p>
<div style={{ width: 36, height: 2, background: GOLD, marginBottom: 20 }} />
<h2 style={{ fontSize: 'clamp(1.6rem, 3vw, 2.4rem)', fontWeight: 900, color: '#fff', lineHeight: 1.15, letterSpacing: '-0.03em', marginBottom: 52 }}>
<Lines text={fallbackText(info.heading, 'Kontakt­möglichkeiten')} />
<Lines text={fallbackText(info.heading, contactContent.info.heading)} />
</h2>
<div style={{ display: 'flex', flexDirection: 'column', gap: 32 }}>
@@ -152,7 +121,7 @@ const Contact: React.FC = () => {
<div style={{ marginTop: 52, paddingTop: 28, borderTop: '1px solid rgba(255,255,255,0.10)' }}>
<p style={{ fontSize: 15, color: 'rgba(255,255,255,0.40)', lineHeight: 1.65 }}>
{fallbackText(info.nextAwardLabel, 'Nächste Preisverleihung:')} <strong style={{ color: '#fff' }}>{fallbackText(info.nextAwardDate, '22. Oktober 2026')}</strong>
{fallbackText(info.nextAwardLabel, contactContent.info.nextAwardLabel)} <strong style={{ color: '#fff' }}>{fallbackText(info.nextAwardDate, contactContent.info.nextAwardDate)}</strong>
</p>
</div>
</div>
@@ -178,19 +147,19 @@ const Contact: React.FC = () => {
{!isMobile && (
<div style={{ height: 200, position: 'relative', overflow: 'hidden', flexShrink: 0, zIndex: 1 }}>
<Image unoptimized src={mediaUrl(info.formImage, '/images/preistraeger-gruppe.jpg')} alt={mediaAlt(info.formImage, fallbackText(info.formImageAlt, 'BMP Gala 2025'))} style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 30%', filter: 'sepia(0.18) brightness(0.92)' }} />
<Image unoptimized src={mediaUrl(info.formImage, `/images/${contactContent.info.formImageFilename}`)} alt={mediaAlt(info.formImage, fallbackText(info.formImageAlt, contactContent.info.formImageAlt))} style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 30%', filter: 'sepia(0.18) brightness(0.92)' }} />
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to bottom, rgba(168,120,0,0.1) 0%, rgba(168,120,0,0.25) 50%, rgba(168,120,0,0.92) 88%, #A87800 100%)' }} />
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: 'rgba(17,29,85,0.35)' }} />
<div style={{ position: 'absolute', bottom: 14, left: 40, display: 'flex', alignItems: 'center', gap: 8 }}>
<div style={{ width: 5, height: 5, borderRadius: '50%', background: 'rgba(17,29,85,0.7)' }} />
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, fontWeight: 700, color: 'rgba(17,29,85,0.75)', textTransform: 'uppercase', letterSpacing: '0.2em' }}>{fallbackText(info.formImageLabel, 'Gala 2025 München')}</span>
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, fontWeight: 700, color: 'rgba(17,29,85,0.75)', textTransform: 'uppercase', letterSpacing: '0.2em' }}>{fallbackText(info.formImageLabel, contactContent.info.formImageLabel)}</span>
</div>
</div>
)}
<div style={{ padding: isMobile ? '36px 20px' : '32px 48px 32px 40px', flex: isMobile ? 'none' : 1, display: 'flex', flexDirection: 'column', minHeight: isMobile ? 480 : 0, position: 'relative', zIndex: 1 }}>
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, color: 'rgba(17,29,85,0.5)', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 8 }}>{fallbackText(info.formEyebrow, 'Kontaktformular')}</span>
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, color: 'rgba(17,29,85,0.5)', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 8 }}>{fallbackText(info.formEyebrow, contactContent.info.formEyebrow)}</span>
<h3 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(1.2rem, 1.8vw, 1.6rem)', fontWeight: 900, color: '#111D55', letterSpacing: '-0.025em', textTransform: 'uppercase', lineHeight: 1.1, marginBottom: 24 }}>
{fallbackText(info.formTitle, 'Schreiben Sie uns')}
{fallbackText(info.formTitle, contactContent.info.formTitle)}
</h3>
<KontaktForm theme="gold" content={info.formCopy || undefined} />
</div>
@@ -201,12 +170,12 @@ const Contact: React.FC = () => {
{/* ── Deadlines ──────────────────────────────────────────────────── */}
<section style={{ background: '#0D1640', padding: isMobile ? '56px 0' : '88px 0', position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', right: -20, top: '50%', transform: 'translateY(-50%)', fontSize: 'clamp(160px, 22vw, 280px)', fontWeight: 900, color: '#fff', opacity: 0.025, lineHeight: 1, pointerEvents: 'none', userSelect: 'none', letterSpacing: '-0.05em' }}>
{fallbackText(deadlines.watermark, '2026')}
{fallbackText(deadlines.watermark, contactContent.deadlines.watermark)}
</div>
<div className="container mx-auto px-6" style={{ position: 'relative' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 20, marginBottom: isMobile ? 48 : 72 }}>
<span style={{ fontSize: 10, fontWeight: 800, letterSpacing: '0.28em', textTransform: 'uppercase', color: '#EFBF04', whiteSpace: 'nowrap' }}>
{fallbackText(deadlines.eyebrow, 'Fahrplan 2026')}
{fallbackText(deadlines.eyebrow, contactContent.deadlines.eyebrow)}
</span>
<div style={{ flex: 1, height: 1, background: 'rgba(255,255,255,0.10)' }} />
</div>
@@ -248,11 +217,11 @@ const Contact: React.FC = () => {
{/* Left sticky label */}
<div style={isMobile ? undefined : { position: 'sticky', top: 96, alignSelf: 'start' }}>
<p style={{ fontSize: 10, fontWeight: 800, letterSpacing: '0.28em', textTransform: 'uppercase', color: '#4A8FC9', marginBottom: 20 }}>
{fallbackText(faq.eyebrow, 'Antworten')}
{fallbackText(faq.eyebrow, contactContent.faq.eyebrow)}
</p>
<div style={{ width: 28, height: 2, background: GOLD, marginBottom: 20 }} />
<h2 style={{ fontSize: isMobile ? 'clamp(1.8rem, 8vw, 2.4rem)' : 'clamp(2rem, 3.5vw, 2.8rem)', fontWeight: 900, color: NAVY, letterSpacing: '-0.03em', lineHeight: 1.1 }}>
<Lines text={fallbackText(faq.heading, 'Häufige\nFragen')} />
<Lines text={fallbackText(faq.heading, contactContent.faq.heading)} />
</h2>
</div>