feat: manage netzwerk page via payload

This commit is contained in:
syntaxbullet
2026-06-22 11:30:21 +02:00
parent a9283b2e46
commit c35e45499e
8 changed files with 1455 additions and 276 deletions

View File

@@ -23,6 +23,7 @@
"preload:formular-upload-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-formular-upload-page-cms.ts", "preload:formular-upload-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-formular-upload-page-cms.ts",
"preload:home-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-home-page-cms.ts", "preload:home-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-home-page-cms.ts",
"preload:impressum-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-impressum-page-cms.ts", "preload:impressum-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-impressum-page-cms.ts",
"preload:netzwerk-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-netzwerk-page-cms.ts",
"preload:participation-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-participation-page-cms.ts", "preload:participation-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-participation-page-cms.ts",
"preload:preistraeger-index-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-preistraeger-index-page-cms.ts", "preload:preistraeger-index-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-preistraeger-index-page-cms.ts",
"preload:press-index-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-press-index-page-cms.ts", "preload:press-index-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-press-index-page-cms.ts",

View File

@@ -14,6 +14,7 @@ import { datenschutzFields } from './datenschutzFields'
import { formularUploadFields } from './formularUploadFields' import { formularUploadFields } from './formularUploadFields'
import { homeFields } from './homeFields' import { homeFields } from './homeFields'
import { impressumFields } from './impressumFields' import { impressumFields } from './impressumFields'
import { netzwerkFields } from './netzwerkFields'
import { participationFields } from './participationFields' import { participationFields } from './participationFields'
import { preistraegerIndexFields } from './preistraegerIndexFields' import { preistraegerIndexFields } from './preistraegerIndexFields'
import { pressIndexFields } from './pressIndexFields' import { pressIndexFields } from './pressIndexFields'
@@ -102,6 +103,14 @@ const isFormularUploadPage = (_: unknown, siblingData?: { slug?: string; spaPath
return spaPath === '/formular-hochladen' || slug === 'formular-hochladen' || slug === 'upload' || title === 'formular hochladen' return spaPath === '/formular-hochladen' || slug === 'formular-hochladen' || slug === 'upload' || title === 'formular hochladen'
} }
const isNetzwerkPage = (_: unknown, siblingData?: { slug?: string; spaPath?: string; title?: string }) => {
const slug = siblingData?.slug
const spaPath = siblingData?.spaPath
const title = siblingData?.title?.toLowerCase()
return spaPath === '/netzwerk' || slug === 'netzwerk' || slug === 'network' || title === 'netzwerk' || title === 'network'
}
const isManagedSpaPage = (_: unknown, siblingData?: { slug?: string; spaPath?: string; title?: string }) => const isManagedSpaPage = (_: unknown, siblingData?: { slug?: string; spaPath?: string; title?: string }) =>
isHomePage(undefined, siblingData) || isHomePage(undefined, siblingData) ||
isContactPage(undefined, siblingData) || isContactPage(undefined, siblingData) ||
@@ -111,7 +120,8 @@ const isManagedSpaPage = (_: unknown, siblingData?: { slug?: string; spaPath?: s
isDatenschutzPage(undefined, siblingData) || isDatenschutzPage(undefined, siblingData) ||
isPreistraegerIndexPage(undefined, siblingData) || isPreistraegerIndexPage(undefined, siblingData) ||
isPressIndexPage(undefined, siblingData) || isPressIndexPage(undefined, siblingData) ||
isFormularUploadPage(undefined, siblingData) isFormularUploadPage(undefined, siblingData) ||
isNetzwerkPage(undefined, siblingData)
export const Pages: CollectionConfig<'pages'> = { export const Pages: CollectionConfig<'pages'> = {
slug: 'pages', slug: 'pages',
@@ -242,6 +252,13 @@ export const Pages: CollectionConfig<'pages'> = {
fields: formularUploadFields, fields: formularUploadFields,
label: 'Formular Upload Page', label: 'Formular Upload Page',
}, },
{
admin: {
condition: (data) => isNetzwerkPage(undefined, data),
},
fields: netzwerkFields,
label: 'Network Page',
},
{ {
name: 'meta', name: 'meta',
label: 'SEO', label: 'SEO',

View File

@@ -0,0 +1,334 @@
import type { Field } from 'payload'
import { netzwerkContent } from '@/spa/netzwerkContent'
const text = (name: string, label: string, defaultValue?: string): Field => ({
name,
type: 'text',
label,
defaultValue,
})
const textarea = (name: string, label: string, defaultValue?: string): Field => ({
name,
type: 'textarea',
label,
defaultValue,
})
const numberField = (name: string, label: string, defaultValue?: number): Field => ({
name,
type: 'number',
label,
defaultValue,
})
const uploadField = (name: string, label: string, description?: string): Field => ({
name,
type: 'upload',
relationTo: 'media',
label,
admin: description ? { description } : undefined,
})
const sectionAdmin = (description: string) => ({
description,
initCollapsed: true,
})
const statFields: Field[] = [text('value', 'Value'), text('label', 'Label')]
const personFields: Field[] = [
text('image', 'Image URL'),
text('imageAlt', 'Image alt text'),
text('name', 'Name'),
text('titleLine1', 'Title line 1'),
text('titleLine2', 'Title line 2'),
text('institution', 'Institution'),
]
const juryMemberFields: Field[] = [
text('name', 'Name'),
text('role', 'Role'),
textarea('bio', 'Bio'),
text('image', 'Image URL'),
]
const rowFields: Field[] = [text('num', 'Number'), text('label', 'Label'), textarea('body', 'Body')]
const partnerFields: Field[] = [
text('id', 'Stable ID'),
text('name', 'Name'),
text('role', 'Role'),
numberField('tier', 'Tier'),
textarea('desc', 'Short description'),
textarea('fullDesc', 'Full modal description'),
{
name: 'logoKind',
label: 'Logo style',
type: 'select',
defaultValue: 'text',
options: [
{ label: 'Text', value: 'text' },
{ label: 'Serif text', value: 'serif' },
{ label: 'Badge', value: 'badge' },
{ label: 'Monogram', value: 'monogram' },
{ label: 'Dot mark', value: 'dot' },
{ label: 'Leaf mark', value: 'leaf' },
{ label: 'RSM blocks', value: 'rsm' },
{ label: 'Deutsche Bank mark', value: 'deutschebank' },
],
},
text('logoText', 'Logo text'),
text('logoSubtext', 'Logo subtext'),
text('logoColor', 'Logo color'),
text('industry', 'Industry'),
text('location', 'Location'),
text('website', 'Website'),
text('linkedin', 'LinkedIn URL'),
text('heroImg', 'Modal hero image URL'),
]
const benefitFields: Field[] = [text('num', 'Number'), text('label', 'Label'), text('sub', 'Subline')]
const packageFields: Field[] = [text('value', 'Value'), text('title', 'Title'), textarea('desc', 'Description')]
const formStepFields: Field[] = [text('label', 'Label')]
export const netzwerkFields: Field[] = [
{
name: 'netzwerk',
label: 'Network page content',
type: 'group',
admin: {
description: 'Edit the Netzwerk page sections, partner records, modal labels, and sponsoring form copy.',
},
fields: [
{
name: 'hero',
label: '01 · Hero',
type: 'group',
admin: sectionAdmin('Hero image, eyebrow, headline, highlight word, and intro copy.'),
fields: [
uploadField('image', 'Hero image', `Current frontend image: /images/${netzwerkContent.hero.imageFilename}`),
text('imageAlt', 'Image alt text', netzwerkContent.hero.imageAlt),
text('eyebrow', 'Eyebrow', netzwerkContent.hero.eyebrow),
textarea('heading', 'Heading', netzwerkContent.hero.heading),
text('highlight', 'Highlighted heading token', netzwerkContent.hero.highlight),
textarea('description', 'Description', netzwerkContent.hero.description),
],
},
{
name: 'patronage',
label: '02 · Patronage',
type: 'group',
admin: sectionAdmin('Patron cards and greeting quote.'),
fields: [
text('eyebrow', 'Eyebrow', netzwerkContent.patronage.eyebrow),
textarea('heading', 'Heading', netzwerkContent.patronage.heading),
text('description', 'Description', netzwerkContent.patronage.description),
{
name: 'people',
label: 'People',
type: 'array',
dbName: 'network_patrons',
defaultValue: netzwerkContent.patronage.people,
fields: personFields,
},
text('greetingEyebrow', 'Greeting eyebrow', netzwerkContent.patronage.greetingEyebrow),
text('greetingRole', 'Greeting role', netzwerkContent.patronage.greetingRole),
text('greetingName', 'Greeting name', netzwerkContent.patronage.greetingName),
text('greetingTitleLine1', 'Greeting title line 1', netzwerkContent.patronage.greetingTitleLine1),
text('greetingTitleLine2', 'Greeting title line 2', netzwerkContent.patronage.greetingTitleLine2),
textarea('greetingQuote', 'Greeting quote', netzwerkContent.patronage.greetingQuote),
text('greetingAttribution', 'Greeting attribution', netzwerkContent.patronage.greetingAttribution),
],
},
{
name: 'juryIntro',
label: '03 · Jury intro',
type: 'group',
admin: sectionAdmin('Editorial jury intro and stat strips.'),
fields: [
text('eyebrow', 'Eyebrow', netzwerkContent.juryIntro.eyebrow),
textarea('heading', 'Heading', netzwerkContent.juryIntro.heading),
textarea('description', 'Description', netzwerkContent.juryIntro.description),
{
name: 'stats',
label: 'Mobile stats',
type: 'array',
dbName: 'network_jury_stats',
defaultValue: netzwerkContent.juryIntro.stats,
fields: statFields,
},
{
name: 'desktopStats',
label: 'Desktop stats',
type: 'array',
dbName: 'network_jury_d_stats',
defaultValue: netzwerkContent.juryIntro.desktopStats,
fields: statFields,
},
],
},
{
name: 'jury',
label: '04 · Jury members',
type: 'group',
admin: sectionAdmin('Jury section heading, chair badge, members, and note.'),
fields: [
text('eyebrow', 'Eyebrow', netzwerkContent.jury.eyebrow),
textarea('heading', 'Heading', netzwerkContent.jury.heading),
textarea('description', 'Description', netzwerkContent.jury.description),
text('chairBadge', 'Chair badge', netzwerkContent.jury.chairBadge),
textarea('note', 'Note', netzwerkContent.jury.note),
{
name: 'members',
label: 'Members',
type: 'array',
dbName: 'network_jury',
defaultValue: netzwerkContent.jury.members,
fields: juryMemberFields,
},
],
},
{
name: 'expertise',
label: '05 · Expertise',
type: 'group',
admin: sectionAdmin('Evaluation process rows and quote.'),
fields: [
text('eyebrow', 'Eyebrow', netzwerkContent.expertise.eyebrow),
textarea('heading', 'Heading', netzwerkContent.expertise.heading),
textarea('quote', 'Quote', netzwerkContent.expertise.quote),
text('quoteAttribution', 'Quote attribution', netzwerkContent.expertise.quoteAttribution),
{
name: 'rows',
label: 'Process rows',
type: 'array',
dbName: 'network_eval',
defaultValue: netzwerkContent.expertise.rows,
fields: rowFields,
},
],
},
{
name: 'partners',
label: '06 · Partners',
type: 'group',
admin: sectionAdmin('Partner section, tier labels, partner records, and modal labels.'),
fields: [
text('eyebrow', 'Eyebrow', netzwerkContent.partners.eyebrow),
textarea('heading', 'Heading', netzwerkContent.partners.heading),
textarea('description', 'Description', netzwerkContent.partners.description),
text('detailLabel', 'Detail label', netzwerkContent.partners.detailLabel),
text('premiumLabel', 'Premium label', netzwerkContent.partners.premiumLabel),
{
name: 'tiers',
label: 'Tiers',
type: 'array',
dbName: 'network_tiers',
defaultValue: netzwerkContent.partners.tiers,
fields: [numberField('tier', 'Tier'), text('label', 'Label'), text('note', 'Note')],
},
{
name: 'modal',
label: 'Modal labels',
type: 'group',
fields: [
text('industryLabel', 'Industry label', netzwerkContent.partners.modal.industryLabel),
text('locationLabel', 'Location label', netzwerkContent.partners.modal.locationLabel),
text('webLabel', 'Web label', netzwerkContent.partners.modal.webLabel),
text('aboutLabel', 'About label', netzwerkContent.partners.modal.aboutLabel),
text('websiteLabel', 'Website CTA label', netzwerkContent.partners.modal.websiteLabel),
text('closeLabel', 'Close label', netzwerkContent.partners.modal.closeLabel),
text('footerTitle', 'Footer title', netzwerkContent.partners.modal.footerTitle),
text('footerRolePrefix', 'Footer role prefix', netzwerkContent.partners.modal.footerRolePrefix),
],
},
{
name: 'items',
label: 'Partners',
type: 'array',
dbName: 'network_partners',
defaultValue: netzwerkContent.partners.items,
fields: partnerFields,
},
],
},
{
name: 'sponsoring',
label: '07 · Sponsoring section',
type: 'group',
admin: sectionAdmin('Sponsoring pitch, image, benefits, form eyebrow, and membership footnote.'),
fields: [
text('eyebrow', 'Eyebrow', netzwerkContent.sponsoring.eyebrow),
textarea('heading', 'Heading', netzwerkContent.sponsoring.heading),
textarea('description', 'Description', netzwerkContent.sponsoring.description),
uploadField('image', 'Form panel image', `Current frontend image: /images/${netzwerkContent.sponsoring.imageFilename}`),
text('imageAlt', 'Image alt text', netzwerkContent.sponsoring.imageAlt),
text('imageLabel', 'Image label', netzwerkContent.sponsoring.imageLabel),
text('formEyebrow', 'Form eyebrow', netzwerkContent.sponsoring.formEyebrow),
text('footnotePrefix', 'Footnote prefix', netzwerkContent.sponsoring.footnotePrefix),
text('footnoteLabel', 'Footnote link label', netzwerkContent.sponsoring.footnoteLabel),
text('footnoteUrl', 'Footnote link URL', netzwerkContent.sponsoring.footnoteUrl),
{
name: 'benefits',
label: 'Benefits',
type: 'array',
dbName: 'network_sponsor_bens',
defaultValue: netzwerkContent.sponsoring.benefits,
fields: benefitFields,
},
],
},
{
name: 'sponsoringForm',
label: '08 · Sponsoring form',
type: 'group',
admin: sectionAdmin('Wizard labels, package options, validation messages, success copy, and footer note.'),
fields: [
{
name: 'steps',
label: 'Step labels',
type: 'array',
dbName: 'network_form_steps',
defaultValue: netzwerkContent.sponsoringForm.steps,
fields: formStepFields,
},
{
name: 'packages',
label: 'Package options',
type: 'array',
dbName: 'network_form_pkgs',
defaultValue: netzwerkContent.sponsoringForm.packages,
fields: packageFields,
},
text('step1Eyebrow', 'Step 1 eyebrow', netzwerkContent.sponsoringForm.step1Eyebrow),
text('step1Heading', 'Step 1 heading', netzwerkContent.sponsoringForm.step1Heading),
text('step2Eyebrow', 'Step 2 eyebrow', netzwerkContent.sponsoringForm.step2Eyebrow),
text('step2Heading', 'Step 2 heading', netzwerkContent.sponsoringForm.step2Heading),
text('companyLabel', 'Company label', netzwerkContent.sponsoringForm.companyLabel),
text('companyPlaceholder', 'Company placeholder', netzwerkContent.sponsoringForm.companyPlaceholder),
text('industryLabel', 'Industry label', netzwerkContent.sponsoringForm.industryLabel),
text('industryPlaceholder', 'Industry placeholder', netzwerkContent.sponsoringForm.industryPlaceholder),
text('step3Eyebrow', 'Step 3 eyebrow', netzwerkContent.sponsoringForm.step3Eyebrow),
text('step3Heading', 'Step 3 heading', netzwerkContent.sponsoringForm.step3Heading),
text('contactLabel', 'Contact label', netzwerkContent.sponsoringForm.contactLabel),
text('contactPlaceholder', 'Contact placeholder', netzwerkContent.sponsoringForm.contactPlaceholder),
text('emailLabel', 'Email label', netzwerkContent.sponsoringForm.emailLabel),
text('emailPlaceholder', 'Email placeholder', netzwerkContent.sponsoringForm.emailPlaceholder),
text('backLabel', 'Back label', netzwerkContent.sponsoringForm.backLabel),
text('nextLabel', 'Next label', netzwerkContent.sponsoringForm.nextLabel),
text('submitLabel', 'Submit label', netzwerkContent.sponsoringForm.submitLabel),
text('requiredPackageError', 'Required package error', netzwerkContent.sponsoringForm.requiredPackageError),
text('requiredFieldError', 'Required field error', netzwerkContent.sponsoringForm.requiredFieldError),
text('invalidEmailError', 'Invalid email error', netzwerkContent.sponsoringForm.invalidEmailError),
text('doneLabel', 'Done step label', netzwerkContent.sponsoringForm.doneLabel),
text('successHeading', 'Success heading', netzwerkContent.sponsoringForm.successHeading),
text('successMessagePrefix', 'Success message prefix', netzwerkContent.sponsoringForm.successMessagePrefix),
text('successMessageSuffix', 'Success message suffix', netzwerkContent.sponsoringForm.successMessageSuffix),
text('footerText', 'Footer text', netzwerkContent.sponsoringForm.footerText),
],
},
],
},
]

View File

@@ -1409,6 +1409,226 @@ export interface Page {
buttonUrl?: string | null; buttonUrl?: string | null;
}; };
}; };
/**
* Edit the Netzwerk page sections, partner records, modal labels, and sponsoring form copy.
*/
netzwerk?: {
/**
* Hero image, eyebrow, headline, highlight word, and intro copy.
*/
hero?: {
/**
* Current frontend image: /images/netzwerk-hero.jpg
*/
image?: (number | null) | Media;
imageAlt?: string | null;
eyebrow?: string | null;
heading?: string | null;
highlight?: string | null;
description?: string | null;
};
/**
* Patron cards and greeting quote.
*/
patronage?: {
eyebrow?: string | null;
heading?: string | null;
description?: string | null;
people?:
| {
image?: string | null;
imageAlt?: string | null;
name?: string | null;
titleLine1?: string | null;
titleLine2?: string | null;
institution?: string | null;
id?: string | null;
}[]
| null;
greetingEyebrow?: string | null;
greetingRole?: string | null;
greetingName?: string | null;
greetingTitleLine1?: string | null;
greetingTitleLine2?: string | null;
greetingQuote?: string | null;
greetingAttribution?: string | null;
};
/**
* Editorial jury intro and stat strips.
*/
juryIntro?: {
eyebrow?: string | null;
heading?: string | null;
description?: string | null;
stats?:
| {
value?: string | null;
label?: string | null;
id?: string | null;
}[]
| null;
desktopStats?:
| {
value?: string | null;
label?: string | null;
id?: string | null;
}[]
| null;
};
/**
* Jury section heading, chair badge, members, and note.
*/
jury?: {
eyebrow?: string | null;
heading?: string | null;
description?: string | null;
chairBadge?: string | null;
note?: string | null;
members?:
| {
name?: string | null;
role?: string | null;
bio?: string | null;
image?: string | null;
id?: string | null;
}[]
| null;
};
/**
* Evaluation process rows and quote.
*/
expertise?: {
eyebrow?: string | null;
heading?: string | null;
quote?: string | null;
quoteAttribution?: string | null;
rows?:
| {
num?: string | null;
label?: string | null;
body?: string | null;
id?: string | null;
}[]
| null;
};
/**
* Partner section, tier labels, partner records, and modal labels.
*/
partners?: {
eyebrow?: string | null;
heading?: string | null;
description?: string | null;
detailLabel?: string | null;
premiumLabel?: string | null;
tiers?:
| {
tier?: number | null;
label?: string | null;
note?: string | null;
id?: string | null;
}[]
| null;
modal?: {
industryLabel?: string | null;
locationLabel?: string | null;
webLabel?: string | null;
aboutLabel?: string | null;
websiteLabel?: string | null;
closeLabel?: string | null;
footerTitle?: string | null;
footerRolePrefix?: string | null;
};
items?:
| {
id?: string | null;
name?: string | null;
role?: string | null;
tier?: number | null;
desc?: string | null;
fullDesc?: string | null;
logoKind?: ('text' | 'serif' | 'badge' | 'monogram' | 'dot' | 'leaf' | 'rsm' | 'deutschebank') | null;
logoText?: string | null;
logoSubtext?: string | null;
logoColor?: string | null;
industry?: string | null;
location?: string | null;
website?: string | null;
linkedin?: string | null;
heroImg?: string | null;
}[]
| null;
};
/**
* Sponsoring pitch, image, benefits, form eyebrow, and membership footnote.
*/
sponsoring?: {
eyebrow?: string | null;
heading?: string | null;
description?: string | null;
/**
* Current frontend image: /images/networking-innenhof.jpg
*/
image?: (number | null) | Media;
imageAlt?: string | null;
imageLabel?: string | null;
formEyebrow?: string | null;
footnotePrefix?: string | null;
footnoteLabel?: string | null;
footnoteUrl?: string | null;
benefits?:
| {
num?: string | null;
label?: string | null;
sub?: string | null;
id?: string | null;
}[]
| null;
};
/**
* Wizard labels, package options, validation messages, success copy, and footer note.
*/
sponsoringForm?: {
steps?:
| {
label?: string | null;
id?: string | null;
}[]
| null;
packages?:
| {
value?: string | null;
title?: string | null;
desc?: string | null;
id?: string | null;
}[]
| null;
step1Eyebrow?: string | null;
step1Heading?: string | null;
step2Eyebrow?: string | null;
step2Heading?: string | null;
companyLabel?: string | null;
companyPlaceholder?: string | null;
industryLabel?: string | null;
industryPlaceholder?: string | null;
step3Eyebrow?: string | null;
step3Heading?: string | null;
contactLabel?: string | null;
contactPlaceholder?: string | null;
emailLabel?: string | null;
emailPlaceholder?: string | null;
backLabel?: string | null;
nextLabel?: string | null;
submitLabel?: string | null;
requiredPackageError?: string | null;
requiredFieldError?: string | null;
invalidEmailError?: string | null;
doneLabel?: string | null;
successHeading?: string | null;
successMessagePrefix?: string | null;
successMessageSuffix?: string | null;
footerText?: string | null;
};
};
meta?: { meta?: {
title?: string | null; title?: string | null;
/** /**
@@ -3440,6 +3660,213 @@ export interface PagesSelect<T extends boolean = true> {
buttonUrl?: T; buttonUrl?: T;
}; };
}; };
netzwerk?:
| T
| {
hero?:
| T
| {
image?: T;
imageAlt?: T;
eyebrow?: T;
heading?: T;
highlight?: T;
description?: T;
};
patronage?:
| T
| {
eyebrow?: T;
heading?: T;
description?: T;
people?:
| T
| {
image?: T;
imageAlt?: T;
name?: T;
titleLine1?: T;
titleLine2?: T;
institution?: T;
id?: T;
};
greetingEyebrow?: T;
greetingRole?: T;
greetingName?: T;
greetingTitleLine1?: T;
greetingTitleLine2?: T;
greetingQuote?: T;
greetingAttribution?: T;
};
juryIntro?:
| T
| {
eyebrow?: T;
heading?: T;
description?: T;
stats?:
| T
| {
value?: T;
label?: T;
id?: T;
};
desktopStats?:
| T
| {
value?: T;
label?: T;
id?: T;
};
};
jury?:
| T
| {
eyebrow?: T;
heading?: T;
description?: T;
chairBadge?: T;
note?: T;
members?:
| T
| {
name?: T;
role?: T;
bio?: T;
image?: T;
id?: T;
};
};
expertise?:
| T
| {
eyebrow?: T;
heading?: T;
quote?: T;
quoteAttribution?: T;
rows?:
| T
| {
num?: T;
label?: T;
body?: T;
id?: T;
};
};
partners?:
| T
| {
eyebrow?: T;
heading?: T;
description?: T;
detailLabel?: T;
premiumLabel?: T;
tiers?:
| T
| {
tier?: T;
label?: T;
note?: T;
id?: T;
};
modal?:
| T
| {
industryLabel?: T;
locationLabel?: T;
webLabel?: T;
aboutLabel?: T;
websiteLabel?: T;
closeLabel?: T;
footerTitle?: T;
footerRolePrefix?: T;
};
items?:
| T
| {
id?: T;
name?: T;
role?: T;
tier?: T;
desc?: T;
fullDesc?: T;
logoKind?: T;
logoText?: T;
logoSubtext?: T;
logoColor?: T;
industry?: T;
location?: T;
website?: T;
linkedin?: T;
heroImg?: T;
};
};
sponsoring?:
| T
| {
eyebrow?: T;
heading?: T;
description?: T;
image?: T;
imageAlt?: T;
imageLabel?: T;
formEyebrow?: T;
footnotePrefix?: T;
footnoteLabel?: T;
footnoteUrl?: T;
benefits?:
| T
| {
num?: T;
label?: T;
sub?: T;
id?: T;
};
};
sponsoringForm?:
| T
| {
steps?:
| T
| {
label?: T;
id?: T;
};
packages?:
| T
| {
value?: T;
title?: T;
desc?: T;
id?: T;
};
step1Eyebrow?: T;
step1Heading?: T;
step2Eyebrow?: T;
step2Heading?: T;
companyLabel?: T;
companyPlaceholder?: T;
industryLabel?: T;
industryPlaceholder?: T;
step3Eyebrow?: T;
step3Heading?: T;
contactLabel?: T;
contactPlaceholder?: T;
emailLabel?: T;
emailPlaceholder?: T;
backLabel?: T;
nextLabel?: T;
submitLabel?: T;
requiredPackageError?: T;
requiredFieldError?: T;
invalidEmailError?: T;
doneLabel?: T;
successHeading?: T;
successMessagePrefix?: T;
successMessageSuffix?: T;
footerText?: T;
};
};
meta?: meta?:
| T | T
| { | {

View File

@@ -0,0 +1,66 @@
import 'dotenv/config'
import config from '@payload-config'
import { getPayload, type Payload } from 'payload'
import { netzwerkContent } from '@/spa/netzwerkContent'
const mediaByFilename = async (payload: Payload, filename: string) => {
const result = await payload.find({
collection: 'media',
limit: 1,
pagination: false,
where: { filename: { equals: filename } },
})
return result.docs[0]?.id
}
async function main() {
const payload = await getPayload({ config })
const pageResult = await payload.find({
collection: 'pages',
limit: 1,
pagination: false,
draft: true,
overrideAccess: true,
where: {
or: [{ spaPath: { equals: '/netzwerk' } }, { slug: { equals: 'netzwerk' } }, { slug: { equals: 'network' } }],
},
})
const page = pageResult.docs[0]
if (!page) throw new Error('Netzwerk page not found. Expected spaPath=/netzwerk or slug=netzwerk/network.')
const heroImage = await mediaByFilename(payload, netzwerkContent.hero.imageFilename)
const sponsoringImage = await mediaByFilename(payload, netzwerkContent.sponsoring.imageFilename)
const { imageFilename: _heroFilename, ...heroContent } = netzwerkContent.hero
const { imageFilename: _sponsoringFilename, ...sponsoringContent } = netzwerkContent.sponsoring
await payload.update({
collection: 'pages',
id: page.id,
overrideAccess: true,
context: { disableRevalidate: true },
data: {
netzwerk: {
...netzwerkContent,
hero: {
...heroContent,
image: heroImage,
},
sponsoring: {
...sponsoringContent,
image: sponsoringImage,
},
},
} as never,
})
payload.logger.info(`Preloaded Netzwerk CMS fields for page ${page.id}`)
}
main().catch((error) => {
console.error(error)
process.exit(1)
})

View File

@@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { ArrowRight, ArrowLeft, Check, Layers, Building2, Mail } from 'lucide-react'; import { ArrowRight, ArrowLeft, Check, Layers, Building2, Mail } from 'lucide-react';
import { useIsMobile } from '@/spa/hooks/useIsMobile'; import { useIsMobile } from '@/spa/hooks/useIsMobile';
import { netzwerkContent } from '@/spa/netzwerkContent';
const FF = '"IBM Plex Sans", sans-serif'; const FF = '"IBM Plex Sans", sans-serif';
@@ -93,17 +94,14 @@ type FormData = {
email: string; email: string;
}; };
const STEPS = [ const STEP_ICONS = [Layers, Building2, Mail];
{ label: 'Paket', icon: Layers },
{ label: 'Unternehmen', icon: Building2 },
{ label: 'Kontakt', icon: Mail },
];
const PAKETE = [ type SponsoringFormContent = Partial<typeof netzwerkContent.sponsoringForm>;
{ value: 'main', title: 'Haupt-Sponsoring', desc: 'Maximale Sichtbarkeit auf allen Kanälen Bühne, Digital, Print.' }, type PackageOption = (typeof netzwerkContent.sponsoringForm.packages)[number];
{ value: 'category', title: 'Kategorie-Partner', desc: 'Namensgebung einer Auszeichnungskategorie fokussiertes Branding.' }, type StepOption = (typeof netzwerkContent.sponsoringForm.steps)[number];
{ value: 'event', title: 'Event-Partner', desc: 'Präsenz beim exklusiven Gala-Abend in München.' },
]; const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback;
const fallbackArray = <T,>(value: unknown, fallback: T[]) => Array.isArray(value) && value.length ? value as T[] : fallback;
const variants = { const variants = {
enter: (dir: number) => ({ x: dir > 0 ? 40 : -40, opacity: 0 }), enter: (dir: number) => ({ x: dir > 0 ? 40 : -40, opacity: 0 }),
@@ -142,7 +140,7 @@ function StyledInput({ c, ...props }: React.InputHTMLAttributes<HTMLInputElement
); );
} }
function PaketCard({ c, pkg, selected, onClick }: { c: C; pkg: typeof PAKETE[0]; selected: boolean; onClick: () => void }) { function PaketCard({ c, pkg, selected, onClick }: { c: C; pkg: PackageOption; selected: boolean; onClick: () => void }) {
return ( return (
<button type="button" onClick={onClick} style={{ <button type="button" onClick={onClick} style={{
width: '100%', padding: '13px 18px', textAlign: 'left', cursor: 'pointer', width: '100%', padding: '13px 18px', textAlign: 'left', cursor: 'pointer',
@@ -165,9 +163,12 @@ function PaketCard({ c, pkg, selected, onClick }: { c: C; pkg: typeof PAKETE[0];
); );
} }
export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'gold' }) { export default function SponsoringForm({ theme = 'dark', content }: { theme?: 'dark' | 'gold'; content?: SponsoringFormContent }) {
const c = theme === 'gold' ? GOLD : DARK; const c = theme === 'gold' ? GOLD : DARK;
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const copy = { ...netzwerkContent.sponsoringForm, ...(content || {}) };
const steps = fallbackArray<StepOption>(copy.steps, netzwerkContent.sponsoringForm.steps);
const packages = fallbackArray<PackageOption>(copy.packages, netzwerkContent.sponsoringForm.packages);
const [step, setStep] = useState(0); const [step, setStep] = useState(0);
const [dir, setDir] = useState(1); const [dir, setDir] = useState(1);
const [submitted, setSubmitted] = useState(false); const [submitted, setSubmitted] = useState(false);
@@ -181,12 +182,12 @@ export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'g
const validate = () => { const validate = () => {
const e: typeof errors = {}; const e: typeof errors = {};
if (step === 0 && !data.paket) e.paket = 'Bitte wählen Sie ein Paket.'; if (step === 0 && !data.paket) e.paket = fallbackText(copy.requiredPackageError, netzwerkContent.sponsoringForm.requiredPackageError);
if (step === 1 && !data.unternehmen) e.unternehmen = 'Pflichtfeld'; if (step === 1 && !data.unternehmen) e.unternehmen = fallbackText(copy.requiredFieldError, netzwerkContent.sponsoringForm.requiredFieldError);
if (step === 2) { if (step === 2) {
if (!data.kontakt) e.kontakt = 'Pflichtfeld'; if (!data.kontakt) e.kontakt = fallbackText(copy.requiredFieldError, netzwerkContent.sponsoringForm.requiredFieldError);
if (!data.email) e.email = 'Pflichtfeld'; if (!data.email) e.email = fallbackText(copy.requiredFieldError, netzwerkContent.sponsoringForm.requiredFieldError);
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) e.email = 'Ungültige E-Mail'; else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) e.email = fallbackText(copy.invalidEmailError, netzwerkContent.sponsoringForm.invalidEmailError);
} }
setErrors(e); setErrors(e);
return Object.keys(e).length === 0; return Object.keys(e).length === 0;
@@ -196,7 +197,7 @@ export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'g
const prev = () => { setDir(-1); setStep(s => s - 1); }; const prev = () => { setDir(-1); setStep(s => s - 1); };
const submit = () => { if (validate()) setSubmitted(true); }; const submit = () => { if (validate()) setSubmitted(true); };
const progress = (step / (STEPS.length - 1)) * 100; const progress = (step / (steps.length - 1)) * 100;
if (submitted) { if (submitted) {
return ( return (
@@ -205,10 +206,10 @@ export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'g
<Check size={22} color={c.successIconIn} strokeWidth={3} /> <Check size={22} color={c.successIconIn} strokeWidth={3} />
</div> </div>
<h3 style={{ fontSize: 18, fontWeight: 900, color: c.successHeading, textTransform: 'uppercase', letterSpacing: '-0.01em', marginBottom: 10 }}> <h3 style={{ fontSize: 18, fontWeight: 900, color: c.successHeading, textTransform: 'uppercase', letterSpacing: '-0.01em', marginBottom: 10 }}>
Anfrage eingegangen {fallbackText(copy.successHeading, netzwerkContent.sponsoringForm.successHeading)}
</h3> </h3>
<p style={{ fontSize: 12, color: c.successBody, maxWidth: 320, margin: '0 auto', lineHeight: 1.6 }}> <p style={{ fontSize: 12, color: c.successBody, maxWidth: 320, margin: '0 auto', lineHeight: 1.6 }}>
Vielen Dank, <strong style={{ color: c.successHeading }}>{data.kontakt}</strong>. Wir senden Ihnen unser Sponsoring-Exposé innerhalb von 48 Stunden zu. {fallbackText(copy.successMessagePrefix, netzwerkContent.sponsoringForm.successMessagePrefix)} <strong style={{ color: c.successHeading }}>{data.kontakt}</strong>. {fallbackText(copy.successMessageSuffix, netzwerkContent.sponsoringForm.successMessageSuffix)}
</p> </p>
</div> </div>
); );
@@ -223,8 +224,8 @@ export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'g
{/* Step tabs */} {/* Step tabs */}
<div style={{ display: 'flex', borderBottom: `1px solid ${c.border}`, flexShrink: 0 }}> <div style={{ display: 'flex', borderBottom: `1px solid ${c.border}`, flexShrink: 0 }}>
{STEPS.map((s, i) => { {steps.map((s, i) => {
const Icon = s.icon; const Icon = STEP_ICONS[i] || Layers;
const done = i < step, active = i === step; const done = i < step, active = i === step;
return ( return (
<div key={i} style={{ <div key={i} style={{
@@ -237,7 +238,7 @@ export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'g
marginBottom: -1, transition: 'color 0.2s', marginBottom: -1, transition: 'color 0.2s',
}}> }}>
<Icon size={11} strokeWidth={active ? 2 : 1.5} /> <Icon size={11} strokeWidth={active ? 2 : 1.5} />
<span>{done ? '✓' : s.label}</span> <span>{done ? fallbackText(copy.doneLabel, netzwerkContent.sponsoringForm.doneLabel) : fallbackText(s.label, netzwerkContent.sponsoringForm.steps[i]?.label || '')}</span>
</div> </div>
); );
})} })}
@@ -253,11 +254,11 @@ export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'g
{step === 0 && ( {step === 0 && (
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
<div> <div>
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 6 }}>Schritt 1 Paket wählen</p> <p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 6 }}>{fallbackText(copy.step1Eyebrow, netzwerkContent.sponsoringForm.step1Eyebrow)}</p>
<h3 style={{ fontFamily: FF, fontSize: 18, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em', marginBottom: 12 }}>Interesse an</h3> <h3 style={{ fontFamily: FF, fontSize: 18, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em', marginBottom: 12 }}>{fallbackText(copy.step1Heading, netzwerkContent.sponsoringForm.step1Heading)}</h3>
</div> </div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
{PAKETE.map(pkg => ( {packages.map(pkg => (
<PaketCard key={pkg.value} c={c} pkg={pkg} selected={data.paket === pkg.value} onClick={() => set('paket', pkg.value)} /> <PaketCard key={pkg.value} c={c} pkg={pkg} selected={data.paket === pkg.value} onClick={() => set('paket', pkg.value)} />
))} ))}
</div> </div>
@@ -268,14 +269,14 @@ export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'g
{step === 1 && ( {step === 1 && (
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<div> <div>
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 8 }}>Schritt 2 Unternehmen</p> <p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 8 }}>{fallbackText(copy.step2Eyebrow, netzwerkContent.sponsoringForm.step2Eyebrow)}</p>
<h3 style={{ fontFamily: FF, fontSize: 18, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em' }}>Ihr Unternehmen</h3> <h3 style={{ fontFamily: FF, fontSize: 18, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em' }}>{fallbackText(copy.step2Heading, netzwerkContent.sponsoringForm.step2Heading)}</h3>
</div> </div>
<Field c={c} label="Unternehmen *" error={errors.unternehmen}> <Field c={c} label={fallbackText(copy.companyLabel, netzwerkContent.sponsoringForm.companyLabel)} error={errors.unternehmen}>
<StyledInput c={c} value={data.unternehmen} onChange={e => set('unternehmen', e.target.value)} placeholder="Firmenname" /> <StyledInput c={c} value={data.unternehmen} onChange={e => set('unternehmen', e.target.value)} placeholder={fallbackText(copy.companyPlaceholder, netzwerkContent.sponsoringForm.companyPlaceholder)} />
</Field> </Field>
<Field c={c} label="Branche"> <Field c={c} label={fallbackText(copy.industryLabel, netzwerkContent.sponsoringForm.industryLabel)}>
<StyledInput c={c} value={data.branche} onChange={e => set('branche', e.target.value)} placeholder="z. B. Finanzdienstleistungen" /> <StyledInput c={c} value={data.branche} onChange={e => set('branche', e.target.value)} placeholder={fallbackText(copy.industryPlaceholder, netzwerkContent.sponsoringForm.industryPlaceholder)} />
</Field> </Field>
</div> </div>
)} )}
@@ -283,14 +284,14 @@ export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'g
{step === 2 && ( {step === 2 && (
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<div> <div>
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 8 }}>Schritt 3 Kontakt</p> <p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 8 }}>{fallbackText(copy.step3Eyebrow, netzwerkContent.sponsoringForm.step3Eyebrow)}</p>
<h3 style={{ fontFamily: FF, fontSize: 18, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em' }}>Ansprechpartner</h3> <h3 style={{ fontFamily: FF, fontSize: 18, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em' }}>{fallbackText(copy.step3Heading, netzwerkContent.sponsoringForm.step3Heading)}</h3>
</div> </div>
<Field c={c} label="Ansprechpartner *" error={errors.kontakt}> <Field c={c} label={fallbackText(copy.contactLabel, netzwerkContent.sponsoringForm.contactLabel)} error={errors.kontakt}>
<StyledInput c={c} value={data.kontakt} onChange={e => set('kontakt', e.target.value)} placeholder="Vor- und Nachname" /> <StyledInput c={c} value={data.kontakt} onChange={e => set('kontakt', e.target.value)} placeholder={fallbackText(copy.contactPlaceholder, netzwerkContent.sponsoringForm.contactPlaceholder)} />
</Field> </Field>
<Field c={c} label="E-Mail *" error={errors.email}> <Field c={c} label={fallbackText(copy.emailLabel, netzwerkContent.sponsoringForm.emailLabel)} error={errors.email}>
<StyledInput c={c} type="email" value={data.email} onChange={e => set('email', e.target.value)} placeholder="name@unternehmen.de" /> <StyledInput c={c} type="email" value={data.email} onChange={e => set('email', e.target.value)} placeholder={fallbackText(copy.emailPlaceholder, netzwerkContent.sponsoringForm.emailPlaceholder)} />
</Field> </Field>
</div> </div>
)} )}
@@ -311,11 +312,11 @@ export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'g
onMouseEnter={e => (e.currentTarget.style.color = c.backTextHover)} onMouseEnter={e => (e.currentTarget.style.color = c.backTextHover)}
onMouseLeave={e => (e.currentTarget.style.color = c.backText)} onMouseLeave={e => (e.currentTarget.style.color = c.backText)}
> >
<ArrowLeft size={13} /> Zurück <ArrowLeft size={13} /> {fallbackText(copy.backLabel, netzwerkContent.sponsoringForm.backLabel)}
</button> </button>
) : <div />} ) : <div />}
{step < STEPS.length - 1 ? ( {step < steps.length - 1 ? (
<button type="button" onClick={next} style={{ <button type="button" onClick={next} style={{
fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em',
color: c.btnText, background: c.btnBg, border: 'none', cursor: 'pointer', color: c.btnText, background: c.btnBg, border: 'none', cursor: 'pointer',
@@ -325,7 +326,7 @@ export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'g
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = c.btnBgHover; }} onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = c.btnBgHover; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = c.btnBg; }} onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = c.btnBg; }}
> >
Weiter <ArrowRight size={13} /> {fallbackText(copy.nextLabel, netzwerkContent.sponsoringForm.nextLabel)} <ArrowRight size={13} />
</button> </button>
) : ( ) : (
<button type="button" onClick={submit} style={{ <button type="button" onClick={submit} style={{
@@ -337,13 +338,13 @@ export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'g
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = c.btnBgHover; }} onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = c.btnBgHover; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = c.btnBg; }} onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = c.btnBg; }}
> >
Exposé anfordern <ArrowRight size={13} /> {fallbackText(copy.submitLabel, netzwerkContent.sponsoringForm.submitLabel)} <ArrowRight size={13} />
</button> </button>
)} )}
</div> </div>
<p style={{ fontFamily: FF, textAlign: 'center', fontSize: 12, color: c.footerText, paddingTop: 12 }}> <p style={{ fontFamily: FF, textAlign: 'center', fontSize: 12, color: c.footerText, paddingTop: 12 }}>
Wir melden uns innerhalb von 48 Stunden. {fallbackText(copy.footerText, netzwerkContent.sponsoringForm.footerText)}
</p> </p>
</div> </div>
); );

386
src/spa/netzwerkContent.ts Normal file
View File

@@ -0,0 +1,386 @@
export const netzwerkContent = {
hero: {
imageFilename: 'netzwerk-hero.jpg',
imageAlt: 'BMP Netzwerk Preisverleihung',
eyebrow: 'Jury & Partner',
heading: 'DAS NETZWERK\nHINTER DEM AWARD.',
highlight: 'AWARD.',
description:
'Der Bayerische Mittelstandspreis wird getragen von einem starken Netzwerk aus Schirmherrschaft, unabhängiger Fach-Jury und langjährigen Partnern aus Wirtschaft und Gesellschaft.',
},
patronage: {
eyebrow: 'Die Schirmherrschaft',
heading: 'Schirmherrin und Schirmherr',
description: 'des Bayerischen Mittelstandspreises',
people: [
{
image: 'https://images.unsplash.com/photo-1551836022-d5d88e9218df?auto=format&fit=crop&q=80&w=800',
imageAlt: 'Ilse Aigner',
name: 'Ilse Aigner MdL',
titleLine1: 'Präsidentin des Bayerischen Landtags',
titleLine2: 'Bayerische Staatsministerin für Wirtschaft a.D.',
institution: 'Bayerischer Landtag',
},
{
image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&q=80&w=800',
imageAlt: 'Hubert Aiwanger',
name: 'Hubert Aiwanger MdL',
titleLine1: 'Bayerischer Staatsminister für Wirtschaft,',
titleLine2: 'Landesentwicklung und Energie · Stv. Ministerpräsident',
institution: 'Bayerische Staatsregierung',
},
],
greetingEyebrow: 'Grußwort',
greetingRole: 'Schirmherrin',
greetingName: 'Ilse Aigner MdL',
greetingTitleLine1: 'Präsidentin des Bayerischen Landtags',
greetingTitleLine2: 'Bayerische Staatsministerin für Wirtschaft a.D.',
greetingQuote:
'„Der Bayerische Mittelstandspreis steht für das, was Bayern stark macht: Unternehmergeist, Verantwortung und Qualität. Mit großer Freude übernehme ich erneut die Schirmherrschaft für diesen bedeutenden Preis.“',
greetingAttribution: ' Ilse Aigner MdL, Präsidentin des Bayerischen Landtags',
},
juryIntro: {
eyebrow: 'Wer entscheidet?',
heading: 'UNABHÄNGIGE EXPERTISE FÜR DEN MITTELSTAND.',
description:
'Die Jury des Bayerischen Mittelstandspreises besteht ausschließlich aus ehrenamtlich tätigen Expertinnen und Experten. Kein Mitglied steht in wirtschaftlicher Verbindung zu einem Bewerber Transparenz und Unparteilichkeit sind die Grundpfeiler unseres Verfahrens.',
stats: [
{ value: '11', label: 'Jurymitglieder' },
{ value: 'Mehrstufig', label: 'Bewertungsverfahren' },
{ value: '100%', label: 'Unabhängig' },
],
desktopStats: [
{ value: '11', label: 'Unabhängige Jurymitglieder' },
{ value: 'Mehrstufig', label: 'Bewertungsverfahren' },
{ value: '100%', label: 'Unabhängig & ehrenamtlich' },
],
},
jury: {
eyebrow: 'Das Gremium',
heading: 'UNSERE JURY 2026',
description:
'Unabhängige Expertinnen und Experten aus Wirtschaft, Wissenschaft und Verbänden, für einen vollständig unabhängigen Bewertungsprozess.',
chairBadge: 'Jury-Vorsitz',
note: 'Hinweis: Einzelne Persönlichkeiten engagieren sich sowohl in der Jury als auch als Partner des BMP beide Rollen werden auf dieser Seite getrennt ausgewiesen.',
members: [
{
name: 'Prof. Dr. Klaus Bergmann',
role: 'Vorsitzender / LMU München',
bio: 'Spezialist für KMU-Strategien und Innovations-Ökosysteme.',
image: 'https://images.unsplash.com/photo-1560250097-0b93528c311a?auto=format&fit=crop&q=80&w=400',
},
{
name: 'Dr. Sabine Hofmann',
role: 'IHK Bayern',
bio: 'Expertin für digitale Transformation und regionale Wertschöpfung.',
image: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=400',
},
{
name: 'Maximilian Reiter',
role: 'Unternehmer',
bio: 'CEO der Reiter Group, bringt die wertvolle Unternehmer-Perspektive ein.',
image: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&q=80&w=400',
},
{
name: 'Dr. Elena Fischer',
role: 'TU München',
bio: 'Lehrstuhl für nachhaltige Unternehmensführung.',
image: 'https://images.unsplash.com/photo-1580489944761-15a19d654956?auto=format&fit=crop&q=80&w=400',
},
],
},
expertise: {
eyebrow: 'Hintergrund & Expertise',
heading: 'WIE BEWERTET DIE JURY?',
quote:
'Die Jury des BMP ist vollständig von wirtschaftlichen Interessen unabhängig. Jede Entscheidung wird transparent nachvollzogen das ist unser Versprechen an die Unternehmen Bayerns.',
quoteAttribution: 'Prof. Dr. Klaus Bergmann, Juryvorsitzender',
rows: [
{
num: '01',
label: 'Aktenstudium',
body: 'Alle Einreichungen werden zunächst vollständig gesichtet und nach den festgelegten Kriterien geprüft.',
},
{
num: '02',
label: 'Scoring-Matrix',
body: 'Jedes Jurymitglied bewertet die Einreichungen unabhängig nach den festgelegten Kriterien.',
},
{
num: '03',
label: 'Jury-Plenum',
body: 'In der gemeinsamen Sitzung werden Argumente und Ansichten ausgetauscht, Eigenschaften verglichen und gemeinsam bewertet. Daraus entsteht die Liste der Finalisten.',
},
{
num: '04',
label: 'Ergebnis',
body: 'Die Gewinner des Bayerischen Mittelstandspreises werden nach vorgegebenem Punktesystem bestimmt, erweitert um Jury-Mitglieder aus der Praxis.',
},
],
},
partners: {
eyebrow: 'Netzwerkqualität',
heading: 'PARTNER & SPONSOREN',
description: 'Partner in drei Kategorien: Hauptsponsoren, Medienpartner und weitere Sponsoren. Klicken für Details.',
detailLabel: 'Details',
premiumLabel: 'Premium',
tiers: [
{ tier: 1, label: 'Hauptsponsoren', note: 'Premium' },
{ tier: 2, label: 'Medienpartner', note: '' },
{ tier: 3, label: 'Weitere Sponsoren', note: '' },
],
modal: {
industryLabel: 'Branche',
locationLabel: 'Standort',
webLabel: 'Web',
aboutLabel: 'Über das Unternehmen',
websiteLabel: 'Website besuchen',
closeLabel: 'Schließen',
footerTitle: 'Bayerischer Mittelstandspreis 2026',
footerRolePrefix: 'Offizieller',
},
items: [
{
id: 'rsm',
name: 'RSM Ebner Stolz',
role: 'Hauptsponsor',
tier: 1,
desc: 'Führende mittelständische Wirtschaftsprüfungs- und Steuerberatungsgesellschaft.',
fullDesc:
'RSM Ebner Stolz ist eine der größten unabhängigen Wirtschaftsprüfungs- und Steuerberatungsgesellschaften Deutschlands mit besonderer Expertise im Mittelstand. Als Hauptsponsor des BMP unterstützen sie die Identifikation herausragender Unternehmen und bringen ihre Netzwerke aktiv in die Nominierungsphase ein.',
logoKind: 'rsm',
logoText: 'RSM',
logoSubtext: 'EBNER STOLZ',
logoColor: '#374151',
industry: 'Wirtschaftsprüfung & Beratung',
location: 'München, Bayern',
website: 'rsm-ebner-stolz.de',
linkedin: 'https://linkedin.com/company/rsm-ebner-stolz',
},
{
id: 'wwk',
name: 'WWK',
role: 'Hauptsponsor',
tier: 1,
desc: 'Eine starke Gemeinschaft Lebensversicherungsgruppe mit bayerischen Wurzeln.',
fullDesc:
'Die WWK Lebensversicherung a. G. ist eine der leistungsstärksten deutschen Lebensversicherungsgruppen. Als Münchner Traditionsunternehmen verbindet die WWK tiefe bayerische Verwurzelung mit finanzieller Stärke Werte, die sie mit dem Bayerischen Mittelstandspreis teilt.',
logoKind: 'text',
logoText: 'WWK',
logoColor: '#16a34a',
industry: 'Lebensversicherung',
location: 'München, Bayern',
website: 'wwk.de',
linkedin: 'https://linkedin.com/company/wwk-versicherung',
},
{
id: 'radiogong',
name: 'Radio Gong 96.3',
role: 'Medienpartner',
tier: 2,
desc: 'Der reichweitenstärkste Radiosender Münchens Stimme des Mittelstands.',
fullDesc:
'Radio Gong 96.3 ist der reichweitenstärkste Radiosender Münchens. Als Medienpartner des BMP sorgt er für Aufmerksamkeit für die Nominierten in Sendemitschnitten und redaktionellen Beiträgen.',
logoKind: 'badge',
logoText: 'Radio\nGong 96.3',
logoColor: '#e11d48',
industry: 'Medien',
location: 'München',
website: 'radiogong.com',
},
{
id: 'muenchentv',
name: 'münchen.tv',
role: 'Medienpartner',
tier: 2,
desc: 'Der lokale Fernsehsender der Landeshauptstadt München.',
fullDesc:
'münchen.tv ist der lokale Fernsehsender der Landeshauptstadt München. Als Medienpartner überträgt münchen.tv Highlights der BMP-Gala und produziert Porträts der Nominierten.',
logoKind: 'text',
logoText: 'münchen.tv',
logoColor: '#0ea5e9',
industry: 'Medien',
location: 'München',
website: 'muenchen.tv',
},
{
id: 'metzler',
name: 'METZLER',
role: 'Sponsor',
tier: 3,
desc: 'Älteste Privatbank Deutschlands Unabhängigkeit seit 1674.',
fullDesc:
'B. Metzler seel. Sohn & Co. KGaA ist die älteste deutsche Privatbank in Familienbesitz seit über 350 Jahren unabhängig. Ihr Engagement für den BMP unterstreicht die tiefe Verbundenheit mit dem Mittelstand, dessen Werte wie Verlässlichkeit, Substanz und Langfristigkeit Metzler selbst verkörpert.',
logoKind: 'serif',
logoText: 'METZLER',
logoColor: '#1a1a1a',
industry: 'Privatbankwesen',
location: 'Frankfurt a. M.',
website: 'metzler.com',
linkedin: 'https://linkedin.com/company/metzler',
},
{
id: 'wieselhuber',
name: 'Dr. Wieselhuber & Partner',
role: 'Sponsor',
tier: 3,
desc: 'Unabhängige Top-Management-Beratung für Familienunternehmen und Mittelstand.',
fullDesc:
'Dr. Wieselhuber & Partner ist eine unabhängige, branchenübergreifende Top-Management-Beratung mit besonderem Fokus auf Familienunternehmen und Mittelstand. Als Sponsor bringt das Haus seine Strategie- und Transformationsexpertise in das Netzwerk des BMP ein.',
logoKind: 'text',
logoText: 'W&P WIESELHUBER',
logoColor: '#1a2b4a',
industry: 'Unternehmensberatung',
location: 'München, Bayern',
website: 'wieselhuber.de',
},
{
id: 'fristads',
name: 'Fristads',
role: 'Sponsor',
tier: 3,
desc: 'Premium-Arbeitsbekleidung Funktion, Qualität und Design.',
fullDesc:
'Fristads steht für hochwertige, funktionale Arbeitsbekleidung, die Schutz, Komfort und Design vereint. Als Sponsor des Bayerischen Mittelstandspreises unterstützt das Unternehmen die Auszeichnung herausragender mittelständischer Betriebe.',
logoKind: 'text',
logoText: 'FRISTADS',
logoColor: '#1a1a1a',
industry: 'Workwear & Textil',
location: 'Deutschland',
website: 'fristads.com',
},
{
id: 'newedge',
name: 'New Edge',
role: 'Sponsor',
tier: 3,
desc: 'Digitale Markenführung, Web und Design für den Mittelstand.',
fullDesc:
'New Edge gestaltet Marken, Web-Erlebnisse und digitale Auftritte für mittelständische Unternehmen. Als Sponsor des BMP unterstützt die Agentur die Sichtbarkeit und das digitale Profil ausgezeichneter Unternehmen.',
logoKind: 'monogram',
logoText: 'New Edge',
logoSubtext: 'NE',
logoColor: '#005E67',
industry: 'Branding & Digital',
location: 'Bayern',
website: 'newedgebrand.com',
},
{
id: 'moodtalk',
name: 'Moodtalk',
role: 'Sponsor',
tier: 3,
desc: 'Plattform für Teamkultur und Mitarbeiter-Feedback.',
fullDesc:
'Moodtalk ist eine Plattform für Teamkultur, Stimmung und kontinuierliches Mitarbeiter-Feedback. Als Sponsor des BMP steht das Unternehmen für moderne, wertebasierte Unternehmensführung.',
logoKind: 'dot',
logoText: 'moodtalk',
logoColor: '#7c3aed',
industry: 'HR & Software',
location: 'Bayern',
website: 'moodtalk.io',
},
{
id: 'deutschebank',
name: 'Deutsche Bank',
role: 'Sponsor',
tier: 3,
desc: 'Partnerbank für die Finanzierung und Skalierung von KMU-Wachstum.',
fullDesc:
'Die Deutsche Bank begleitet mittelständische Unternehmen als verlässlicher Finanzpartner durch alle Wachstumsphasen. Im Rahmen des BMP stellt sie ihr deutschlandweites Netzwerk, spezifische Mittelstandsprodukte und direkte Ansprechpartner für die Nominierten bereit.',
logoKind: 'deutschebank',
logoText: 'Deutsche Bank',
logoColor: '#1d1d1b',
industry: 'Bankwesen & Finanzen',
location: 'Frankfurt a. M.',
website: 'db.com',
linkedin: 'https://linkedin.com/company/deutsche-bank',
},
{
id: 'bionorica',
name: 'Bionorica',
role: 'Sponsor',
tier: 3,
desc: 'Weltmarktführer für pflanzliche Arzneimittel aus dem bayerischen Neumarkt.',
fullDesc:
'Bionorica SE ist ein international agierendes Pharmaunternehmen mit Sitz in Neumarkt i. d. OPf. und Weltmarktführer bei pflanzlichen Arzneimitteln. Das Unternehmen selbst ist ein Paradebeispiel eines bayerischen Mittelständlers mit globalem Anspruch und verkörpert die Innovationskraft, die der BMP würdigt.',
logoKind: 'leaf',
logoText: 'Bionorica®',
logoColor: '#16a34a',
industry: 'Pharmaindustrie',
location: 'Neumarkt i.d.OPf., Bayern',
website: 'bionorica.de',
linkedin: 'https://linkedin.com/company/bionorica',
},
{
id: 'primus',
name: 'Primus',
role: 'Sponsor',
tier: 3,
desc: 'Partner des Bayerischen Mittelstandspreises.',
fullDesc: 'Primus unterstützt als Sponsor den Bayerischen Mittelstandspreis und sein Engagement für herausragende Unternehmen des bayerischen Mittelstands.',
logoKind: 'serif',
logoText: 'PRIMUS',
logoColor: '#b8860b',
industry: 'Partner',
location: 'Bayern',
website: '',
},
],
},
sponsoring: {
eyebrow: 'Wachstum durch Partnerschaft',
heading: 'WIR FREUEN UNS ÜBER NEUE SPONSOREN.',
description:
'Positionieren Sie Ihre Marke im exklusivsten Netzwerk des bayerischen Mittelstands mit maßgeschneiderten Sponsoring-Paketen.',
imageFilename: 'networking-innenhof.jpg',
imageAlt: 'BMP Partnernetzwerk',
imageLabel: 'BMP Partnernetzwerk',
formEyebrow: 'Sponsoring 2026',
footnotePrefix: 'Oder:',
footnoteLabel: 'Vereinsmitglied werden',
footnoteUrl: '/mitglied-werden',
benefits: [
{ num: '01', label: 'Markenpräsenz', sub: 'Bühne, Drucksachen, digitale Kanäle' },
{ num: '02', label: 'Top-Entscheider', sub: 'Exklusives Netzwerk-Dinner für Partner' },
{ num: '03', label: 'Multichannel', sub: 'Online, Print, Radio & TV-Partner' },
],
},
sponsoringForm: {
steps: [
{ label: 'Paket' },
{ label: 'Unternehmen' },
{ label: 'Kontakt' },
],
packages: [
{ value: 'main', title: 'Haupt-Sponsoring', desc: 'Maximale Sichtbarkeit auf allen Kanälen Bühne, Digital, Print.' },
{ value: 'category', title: 'Kategorie-Partner', desc: 'Namensgebung einer Auszeichnungskategorie fokussiertes Branding.' },
{ value: 'event', title: 'Event-Partner', desc: 'Präsenz beim exklusiven Gala-Abend in München.' },
],
step1Eyebrow: 'Schritt 1 Paket wählen',
step1Heading: 'Interesse an',
step2Eyebrow: 'Schritt 2 Unternehmen',
step2Heading: 'Ihr Unternehmen',
companyLabel: 'Unternehmen *',
companyPlaceholder: 'Firmenname',
industryLabel: 'Branche',
industryPlaceholder: 'z. B. Finanzdienstleistungen',
step3Eyebrow: 'Schritt 3 Kontakt',
step3Heading: 'Ansprechpartner',
contactLabel: 'Ansprechpartner *',
contactPlaceholder: 'Vor- und Nachname',
emailLabel: 'E-Mail *',
emailPlaceholder: 'name@unternehmen.de',
backLabel: 'Zurück',
nextLabel: 'Weiter',
submitLabel: 'Exposé anfordern',
requiredPackageError: 'Bitte wählen Sie ein Paket.',
requiredFieldError: 'Pflichtfeld',
invalidEmailError: 'Ungültige E-Mail',
doneLabel: '✓',
successHeading: 'Anfrage eingegangen',
successMessagePrefix: 'Vielen Dank,',
successMessageSuffix: 'Wir senden Ihnen unser Sponsoring-Exposé innerhalb von 48 Stunden zu.',
footerText: 'Wir melden uns innerhalb von 48 Stunden.',
},
}

View File

@@ -4,6 +4,9 @@ import { ChevronRight, X, ArrowRight, Globe, Linkedin, Building2, MapPin, Extern
import SponsoringForm from '@/spa/components/forms/SponsoringForm'; import SponsoringForm from '@/spa/components/forms/SponsoringForm';
import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg'; import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg';
import { useIsMobile } from '@/spa/hooks/useIsMobile'; import { useIsMobile } from '@/spa/hooks/useIsMobile';
import { useCmsRoute } from '@/spa/cmsRoute';
import { mediaAlt, mediaUrl } from '@/spa/cmsMediaField';
import { netzwerkContent } from '@/spa/netzwerkContent';
import Image from '@/spa/components/ui/UnoptimizedImage' import Image from '@/spa/components/ui/UnoptimizedImage'
const FF = '"IBM Plex Sans", sans-serif'; const FF = '"IBM Plex Sans", sans-serif';
@@ -20,6 +23,43 @@ const ROLE_COLOR: Record<string, string> = {
'Medienpartner': '#DC2626', 'Medienpartner': '#DC2626',
}; };
type NetzwerkCms = Partial<typeof netzwerkContent> & {
hero?: Partial<typeof netzwerkContent.hero> & { image?: unknown }
sponsoring?: Partial<typeof netzwerkContent.sponsoring> & { image?: unknown }
}
type PatronPerson = (typeof netzwerkContent.patronage.people)[number]
type JuryMember = (typeof netzwerkContent.jury.members)[number]
type Partner = (typeof netzwerkContent.partners.items)[number] & { tier: 1 | 2 | 3; heroImg?: string; linkedin?: string; logoSubtext?: string }
type PartnerTier = (typeof netzwerkContent.partners.tiers)[number]
type ExpertiseRow = (typeof netzwerkContent.expertise.rows)[number]
type StatItem = (typeof netzwerkContent.juryIntro.stats)[number]
type BenefitItem = (typeof netzwerkContent.sponsoring.benefits)[number]
type PartnerModalCopy = typeof netzwerkContent.partners.modal
const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback;
const fallbackArray = <T,>(value: unknown, fallback: T[]) => Array.isArray(value) && value.length ? value as T[] : fallback;
function Lines({ text, highlight }: { text: string; highlight?: string }) {
return (
<>
{text.split('\n').map((line, i) => {
const highlighted = highlight && line.includes(highlight)
return (
<React.Fragment key={`${line}-${i}`}>
{i > 0 && <br />}
{highlighted ? (
<>
{line.replace(highlight, '')}<span style={{ color: GOLD }}>{highlight}</span>
</>
) : line}
</React.Fragment>
)
})}
</>
)
}
// ── PersonCard (Schirmherrschaft) ───────────────────────────────────────────── // ── PersonCard (Schirmherrschaft) ─────────────────────────────────────────────
interface PersonCardProps { interface PersonCardProps {
@@ -62,13 +102,6 @@ function PersonCard({ imgSrc, imgAlt, name, titleLine1, titleLine2, institution,
// ── JuryCard ────────────────────────────────────────────────────────────────── // ── JuryCard ──────────────────────────────────────────────────────────────────
interface JuryMember {
name: string;
role: string;
bio: string;
img: string;
}
const JuryCard: React.FC<{ member: JuryMember; idx: number }> = ({ member, idx }) => { const JuryCard: React.FC<{ member: JuryMember; idx: number }> = ({ member, idx }) => {
const [hovered, setHovered] = useState(false); const [hovered, setHovered] = useState(false);
const isMobileCard = useIsMobile(); const isMobileCard = useIsMobile();
@@ -79,7 +112,7 @@ const JuryCard: React.FC<{ member: JuryMember; idx: number }> = ({ member, idx }
style={{ borderLeft: !isMobileCard && idx > 0 ? '1px solid rgba(255,255,255,0.07)' : 'none', display: 'flex', flexDirection: 'column', cursor: 'default', background: hovered ? 'rgba(255,255,255,0.03)' : 'transparent', transition: 'background 0.2s', ...(isMobileCard ? { minWidth: '74vw', maxWidth: '74vw', flexShrink: 0, scrollSnapAlign: 'start', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 16, overflow: 'hidden' } : {}) }} style={{ borderLeft: !isMobileCard && idx > 0 ? '1px solid rgba(255,255,255,0.07)' : 'none', display: 'flex', flexDirection: 'column', cursor: 'default', background: hovered ? 'rgba(255,255,255,0.03)' : 'transparent', transition: 'background 0.2s', ...(isMobileCard ? { minWidth: '74vw', maxWidth: '74vw', flexShrink: 0, scrollSnapAlign: 'start', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 16, overflow: 'hidden' } : {}) }}
> >
<div style={{ position: 'relative', overflow: 'hidden', aspectRatio: '3/4' }}> <div style={{ position: 'relative', overflow: 'hidden', aspectRatio: '3/4' }}>
<Image unoptimized src={member.img} alt={member.name} style={{ width: '100%', height: '100%', objectFit: 'cover', filter: hovered ? 'none' : 'grayscale(100%)', transition: 'filter 0.5s', display: 'block' }} /> <Image unoptimized src={member.image} alt={member.name} style={{ width: '100%', height: '100%', objectFit: 'cover', filter: hovered ? 'none' : 'grayscale(100%)', transition: 'filter 0.5s', display: 'block' }} />
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: '40%', background: 'linear-gradient(to top, rgba(3,9,58,0.65), transparent)' }} /> <div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: '40%', background: 'linear-gradient(to top, rgba(3,9,58,0.65), transparent)' }} />
</div> </div>
<div style={{ padding: isMobileCard ? '28px 28px 32px' : '32px 32px 36px' }}> <div style={{ padding: isMobileCard ? '28px 28px 32px' : '32px 32px 36px' }}>
@@ -94,7 +127,7 @@ const JuryCard: React.FC<{ member: JuryMember; idx: number }> = ({ member, idx }
// ── JuryChairCard (Vorsitz prominent) ─────────────────────────────────────── // ── JuryChairCard (Vorsitz prominent) ───────────────────────────────────────
const JuryChairCard: React.FC<{ member: JuryMember }> = ({ member }) => { const JuryChairCard: React.FC<{ member: JuryMember; badge: string }> = ({ member, badge }) => {
const [hovered, setHovered] = useState(false); const [hovered, setHovered] = useState(false);
const isMobileCard = useIsMobile(); const isMobileCard = useIsMobile();
return ( return (
@@ -112,12 +145,12 @@ const JuryChairCard: React.FC<{ member: JuryMember }> = ({ member }) => {
}} }}
> >
<div style={{ position: 'relative', overflow: 'hidden', aspectRatio: isMobileCard ? '16/10' : '3/4', minHeight: isMobileCard ? 240 : 'auto' }}> <div style={{ position: 'relative', overflow: 'hidden', aspectRatio: isMobileCard ? '16/10' : '3/4', minHeight: isMobileCard ? 240 : 'auto' }}>
<Image unoptimized src={member.img} alt={member.name} style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center top', filter: hovered ? 'none' : 'grayscale(100%)', transition: 'filter 0.5s', display: 'block' }} /> <Image unoptimized src={member.image} alt={member.name} style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center top', filter: hovered ? 'none' : 'grayscale(100%)', transition: 'filter 0.5s', display: 'block' }} />
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: '45%', background: 'linear-gradient(to top, rgba(3,9,58,0.7), transparent)' }} /> <div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: '45%', background: 'linear-gradient(to top, rgba(3,9,58,0.7), transparent)' }} />
</div> </div>
<div style={{ padding: isMobileCard ? '32px 28px 36px' : '56px 56px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}> <div style={{ padding: isMobileCard ? '32px 28px 36px' : '56px 56px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
<span style={{ alignSelf: 'flex-start', fontFamily: FF, fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.18em', color: NAVY, background: GOLD, padding: '7px 16px', borderRadius: 999, marginBottom: 24 }}> <span style={{ alignSelf: 'flex-start', fontFamily: FF, fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.18em', color: NAVY, background: GOLD, padding: '7px 16px', borderRadius: 999, marginBottom: 24 }}>
Jury-Vorsitz {badge}
</span> </span>
<div style={{ fontFamily: FF, fontSize: isMobileCard ? 26 : 34, fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, marginBottom: 10 }}>{member.name}</div> <div style={{ fontFamily: FF, fontSize: isMobileCard ? 26 : 34, fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, marginBottom: 10 }}>{member.name}</div>
<div style={{ fontFamily: FF, fontSize: 13, color: GOLD, textTransform: 'uppercase', letterSpacing: '0.18em', fontWeight: 700, marginBottom: 24 }}>{member.role}</div> <div style={{ fontFamily: FF, fontSize: 13, color: GOLD, textTransform: 'uppercase', letterSpacing: '0.18em', fontWeight: 700, marginBottom: 24 }}>{member.role}</div>
@@ -130,24 +163,41 @@ const JuryChairCard: React.FC<{ member: JuryMember }> = ({ member }) => {
// ── PartnerCell ─────────────────────────────────────────────────────────────── // ── PartnerCell ───────────────────────────────────────────────────────────────
interface Partner {
id: string;
name: string;
role: string;
tier: 1 | 2 | 3;
desc: string;
fullDesc: string;
logo: React.ReactNode;
industry: string;
location: string;
website: string;
heroImg?: string;
linkedin?: string;
}
// ── SponsorCell (tiered) ────────────────────────────────────────────────────── // ── SponsorCell (tiered) ──────────────────────────────────────────────────────
const SponsorCell: React.FC<{ partner: Partner; variant: 'lg' | 'md' | 'sm'; idx: number; cols: number; onClick: () => void }> = ({ partner, variant, idx, cols, onClick }) => { function PartnerLogo({ partner }: { partner: Partner }) {
const color = fallbackText(partner.logoColor, '#111D55');
const text = fallbackText(partner.logoText, partner.name);
const subtext = fallbackText(partner.logoSubtext, '');
if (partner.logoKind === 'badge') {
return <span style={{ background: color, color: '#fff', fontWeight: 900, fontSize: 14, padding: '4px 10px', lineHeight: 1.25, display: 'inline-block', textAlign: 'center' as const, fontFamily: FF }}>{text.split('\n').map((line, index) => <React.Fragment key={`${line}-${index}`}>{index > 0 && <br />}{line}</React.Fragment>)}</span>
}
if (partner.logoKind === 'monogram') {
return <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}><span style={{ width: 22, height: 22, background: '#3a3a3a', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 900, fontSize: 11, fontFamily: FF }}>{subtext || text.slice(0, 2)}</span><span style={{ fontWeight: 700, fontSize: 15, color, fontFamily: FF }}>{text}</span></span>
}
if (partner.logoKind === 'dot') {
return <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}><span style={{ width: 20, height: 20, borderRadius: '50%', background: color, display: 'block' }} /><span style={{ fontWeight: 700, fontSize: 16, color, fontFamily: FF }}>{text}</span></span>
}
if (partner.logoKind === 'leaf') {
return <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}><svg width="20" height="20" viewBox="0 0 20 20"><circle cx="10" cy="10" r="9" fill={color} /><path d="M10 3 Q14 7 14 10 Q14 14 10 17 Q6 14 6 10 Q6 7 10 3Z" fill="white" opacity="0.8" /></svg><span style={{ fontWeight: 700, fontSize: 15, color, fontFamily: FB }}>{text}</span></span>
}
if (partner.logoKind === 'rsm') {
return <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}><span style={{ display: 'flex', gap: 2 }}><span style={{ width: 10, height: 10, background: '#6b7280', display: 'block' }} /><span style={{ width: 10, height: 10, background: '#22c55e', display: 'block' }} /><span style={{ width: 10, height: 10, background: '#3b82f6', display: 'block' }} /></span><span style={{ fontWeight: 900, fontSize: 15, letterSpacing: '-0.02em', color, fontFamily: FF }}>{text} {subtext && <span style={{ color: '#6b7280', fontWeight: 600 }}>{subtext}</span>}</span></span>
}
if (partner.logoKind === 'deutschebank') {
return <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}><svg width="20" height="20" viewBox="0 0 20 20"><rect x="1" y="1" width="18" height="18" fill="none" stroke={color} strokeWidth="1.5" /><line x1="5" y1="15" x2="15" y2="5" stroke={color} strokeWidth="2" /></svg><span style={{ fontWeight: 600, fontSize: 15, color, fontFamily: FB }}>{text}</span></span>
}
return <span style={{ fontWeight: 900, fontSize: partner.logoKind === 'serif' ? 21 : 16, letterSpacing: partner.logoKind === 'serif' ? '0.12em' : '-0.01em', color, fontFamily: partner.logoKind === 'serif' ? 'Georgia, serif' : FF }}>{text}</span>
}
const SponsorCell: React.FC<{ partner: Partner; variant: 'lg' | 'md' | 'sm'; idx: number; cols: number; detailLabel: string; premiumLabel: string; onClick: () => void }> = ({ partner, variant, idx, cols, detailLabel, premiumLabel, onClick }) => {
const [hovered, setHovered] = useState(false); const [hovered, setHovered] = useState(false);
const isMobileCell = useIsMobile(); const isMobileCell = useIsMobile();
const lg = variant === 'lg'; const lg = variant === 'lg';
@@ -170,15 +220,15 @@ const SponsorCell: React.FC<{ partner: Partner; variant: 'lg' | 'md' | 'sm'; idx
}} }}
> >
{lg && ( {lg && (
<span style={{ position: 'absolute', top: 18, right: 18, fontFamily: FF, fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.18em', color: '#101828', background: GOLD, padding: '4px 10px', borderRadius: 100, zIndex: 1 }}>Premium</span> <span style={{ position: 'absolute', top: 18, right: 18, fontFamily: FF, fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.18em', color: '#101828', background: GOLD, padding: '4px 10px', borderRadius: 100, zIndex: 1 }}>{premiumLabel}</span>
)} )}
<div style={{ height: lg ? 52 : 44, display: 'flex', alignItems: 'center' }}>{partner.logo}</div> <div style={{ height: lg ? 52 : 44, display: 'flex', alignItems: 'center' }}><PartnerLogo partner={partner} /></div>
<div style={{ width: hovered ? 48 : 20, height: 1, background: GOLD, transition: 'width 0.3s ease' }} /> <div style={{ width: hovered ? 48 : 20, height: 1, background: GOLD, transition: 'width 0.3s ease' }} />
<div style={{ fontFamily: FF, fontSize: lg ? 22 : 18, fontWeight: 700, color: '#101828', letterSpacing: '-0.01em', lineHeight: 1.15 }}>{partner.name}</div> <div style={{ fontFamily: FF, fontSize: lg ? 22 : 18, fontWeight: 700, color: '#101828', letterSpacing: '-0.01em', lineHeight: 1.15 }}>{partner.name}</div>
<div style={{ fontFamily: FF, fontSize: sm ? 10 : 11, color: GOLD, textTransform: 'uppercase', letterSpacing: '0.2em', fontWeight: 700 }}>{partner.role}</div> <div style={{ fontFamily: FF, fontSize: sm ? 10 : 11, color: GOLD, textTransform: 'uppercase', letterSpacing: '0.2em', fontWeight: 700 }}>{partner.role}</div>
{!sm && <div style={{ fontFamily: FB, fontSize: lg ? 17 : 16, color: 'rgba(16,24,40,0.45)', lineHeight: 1.6, flexGrow: 1 }}>{partner.desc}</div>} {!sm && <div style={{ fontFamily: FB, fontSize: lg ? 17 : 16, color: 'rgba(16,24,40,0.45)', lineHeight: 1.6, flexGrow: 1 }}>{partner.desc}</div>}
<div style={{ fontFamily: FF, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.12em', color: GOLD, fontWeight: 700, display: 'flex', alignItems: 'center', gap: 4 }}> <div style={{ fontFamily: FF, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.12em', color: GOLD, fontWeight: 700, display: 'flex', alignItems: 'center', gap: 4 }}>
Details <ArrowRight size={10} /> {detailLabel} <ArrowRight size={10} />
</div> </div>
</div> </div>
); );
@@ -186,7 +236,7 @@ const SponsorCell: React.FC<{ partner: Partner; variant: 'lg' | 'md' | 'sm'; idx
// ── PartnerModal ───────────────────────────────────────────────────────────── // ── PartnerModal ─────────────────────────────────────────────────────────────
function PartnerModal({ partner, onClose }: { partner: Partner; onClose: () => void }) { function PartnerModal({ partner, copy, onClose }: { partner: Partner; copy: PartnerModalCopy; onClose: () => void }) {
const accentColor = ROLE_COLOR[partner.role] ?? GOLD; const accentColor = ROLE_COLOR[partner.role] ?? GOLD;
useEffect(() => { useEffect(() => {
@@ -283,7 +333,7 @@ function PartnerModal({ partner, onClose }: { partner: Partner; onClose: () => v
display: 'flex', alignItems: 'center', justifyContent: 'center', display: 'flex', alignItems: 'center', justifyContent: 'center',
padding: 10, zIndex: 2, padding: 10, zIndex: 2,
}}> }}>
{partner.logo} <PartnerLogo partner={partner} />
</div> </div>
</div> </div>
@@ -304,9 +354,9 @@ function PartnerModal({ partner, onClose }: { partner: Partner; onClose: () => v
{/* 3 info cards */} {/* 3 info cards */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 10, marginBottom: 28 }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 10, marginBottom: 28 }}>
{[ {[
{ Icon: Building2, label: 'Branche', value: partner.industry }, { Icon: Building2, label: fallbackText(copy.industryLabel, netzwerkContent.partners.modal.industryLabel), value: partner.industry },
{ Icon: MapPin, label: 'Standort', value: partner.location }, { Icon: MapPin, label: fallbackText(copy.locationLabel, netzwerkContent.partners.modal.locationLabel), value: partner.location },
{ Icon: Globe, label: 'Web', value: partner.website }, { Icon: Globe, label: fallbackText(copy.webLabel, netzwerkContent.partners.modal.webLabel), value: partner.website },
].map(({ Icon, label, value }) => ( ].map(({ Icon, label, value }) => (
<div key={label} style={{ background: 'hsl(220,40%,97%)', padding: '14px 16px', borderRadius: 8 }}> <div key={label} style={{ background: 'hsl(220,40%,97%)', padding: '14px 16px', borderRadius: 8 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 6 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 6 }}>
@@ -325,7 +375,7 @@ function PartnerModal({ partner, onClose }: { partner: Partner; onClose: () => v
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14 }}>
<div style={{ width: 4, height: 20, background: accentColor, borderRadius: 2, flexShrink: 0 }} /> <div style={{ width: 4, height: 20, background: accentColor, borderRadius: 2, flexShrink: 0 }} />
<span style={{ fontFamily: FF, fontSize: 17, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: '#101828' }}> <span style={{ fontFamily: FF, fontSize: 17, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: '#101828' }}>
Über das Unternehmen {fallbackText(copy.aboutLabel, netzwerkContent.partners.modal.aboutLabel)}
</span> </span>
</div> </div>
<p style={{ fontFamily: FB, fontSize: 19, color: 'rgba(16,24,40,0.6)', lineHeight: 1.82, marginBottom: 36 }}> <p style={{ fontFamily: FB, fontSize: 19, color: 'rgba(16,24,40,0.6)', lineHeight: 1.82, marginBottom: 36 }}>
@@ -349,7 +399,7 @@ function PartnerModal({ partner, onClose }: { partner: Partner; onClose: () => v
onMouseEnter={e => { const el = e.currentTarget as HTMLElement; el.style.background = '#FFD130'; el.style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }} onMouseEnter={e => { const el = e.currentTarget as HTMLElement; el.style.background = '#FFD130'; el.style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
onMouseLeave={e => { const el = e.currentTarget as HTMLElement; el.style.background = GOLD; el.style.boxShadow = 'none'; }} onMouseLeave={e => { const el = e.currentTarget as HTMLElement; el.style.background = GOLD; el.style.boxShadow = 'none'; }}
> >
Website besuchen <ExternalLink size={12} /> {fallbackText(copy.websiteLabel, netzwerkContent.partners.modal.websiteLabel)} <ExternalLink size={12} />
</a> </a>
)} )}
<button <button
@@ -364,7 +414,7 @@ function PartnerModal({ partner, onClose }: { partner: Partner; onClose: () => v
onMouseEnter={e => { const el = e.currentTarget as HTMLElement; el.style.background = NAVY; el.style.color = '#fff'; el.style.borderColor = NAVY; }} onMouseEnter={e => { const el = e.currentTarget as HTMLElement; el.style.background = NAVY; el.style.color = '#fff'; el.style.borderColor = NAVY; }}
onMouseLeave={e => { const el = e.currentTarget as HTMLElement; el.style.background = 'transparent'; el.style.color = 'rgba(16,24,40,0.45)'; el.style.borderColor = 'rgba(16,24,40,0.12)'; }} onMouseLeave={e => { const el = e.currentTarget as HTMLElement; el.style.background = 'transparent'; el.style.color = 'rgba(16,24,40,0.45)'; el.style.borderColor = 'rgba(16,24,40,0.12)'; }}
> >
Schließen {fallbackText(copy.closeLabel, netzwerkContent.partners.modal.closeLabel)}
</button> </button>
</div> </div>
</div> </div>
@@ -376,10 +426,10 @@ function PartnerModal({ partner, onClose }: { partner: Partner; onClose: () => v
borderTop: '1px solid rgba(16,24,40,0.06)', borderTop: '1px solid rgba(16,24,40,0.06)',
}}> }}>
<span style={{ fontFamily: FF, fontSize: 9, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.12em', color: 'rgba(16,24,40,0.3)' }}> <span style={{ fontFamily: FF, fontSize: 9, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.12em', color: 'rgba(16,24,40,0.3)' }}>
Bayerischer Mittelstandspreis 2026 {fallbackText(copy.footerTitle, netzwerkContent.partners.modal.footerTitle)}
</span> </span>
<span style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: accentColor }}> <span style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: accentColor }}>
Offizieller {partner.role} {fallbackText(copy.footerRolePrefix, netzwerkContent.partners.modal.footerRolePrefix)} {partner.role}
</span> </span>
</div> </div>
</div> </div>
@@ -392,119 +442,30 @@ function PartnerModal({ partner, onClose }: { partner: Partner; onClose: () => v
const Netzwerk: React.FC = () => { const Netzwerk: React.FC = () => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [selectedPartner, setSelectedPartner] = useState<Partner | null>(null); const [selectedPartner, setSelectedPartner] = useState<Partner | null>(null);
const cms = (useCmsRoute()?.doc?.netzwerk || {}) as NetzwerkCms;
const juryMembers: JuryMember[] = [ const hero = { ...netzwerkContent.hero, ...(cms.hero || {}) };
{ name: 'Prof. Dr. Klaus Bergmann', role: 'Vorsitzender / LMU München', bio: 'Spezialist für KMU-Strategien und Innovations-Ökosysteme.', img: 'https://images.unsplash.com/photo-1560250097-0b93528c311a?auto=format&fit=crop&q=80&w=400' }, const patronage = { ...netzwerkContent.patronage, ...(cms.patronage || {}) };
{ name: 'Dr. Sabine Hofmann', role: 'IHK Bayern', bio: 'Expertin für digitale Transformation und regionale Wertschöpfung.', img: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=400' }, const juryIntro = { ...netzwerkContent.juryIntro, ...(cms.juryIntro || {}) };
{ name: 'Maximilian Reiter', role: 'Unternehmer', bio: 'CEO der Reiter Group, bringt die wertvolle Unternehmer-Perspektive ein.', img: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&q=80&w=400' }, const jury = { ...netzwerkContent.jury, ...(cms.jury || {}) };
{ name: 'Dr. Elena Fischer', role: 'TU München', bio: 'Lehrstuhl für nachhaltige Unternehmensführung.', img: 'https://images.unsplash.com/photo-1580489944761-15a19d654956?auto=format&fit=crop&q=80&w=400' }, const expertise = { ...netzwerkContent.expertise, ...(cms.expertise || {}) };
]; const partnerSection = { ...netzwerkContent.partners, ...(cms.partners || {}) };
const partnerModal = { ...netzwerkContent.partners.modal, ...((cms.partners as typeof netzwerkContent.partners | undefined)?.modal || {}) };
const partners: Partner[] = [ const sponsoring = { ...netzwerkContent.sponsoring, ...(cms.sponsoring || {}) };
/* ── TIER 1 · Hauptsponsoren ─────────────────────────────── */ const patronPeople = fallbackArray<PatronPerson>(patronage.people, netzwerkContent.patronage.people);
{ const juryMembers = fallbackArray<JuryMember>(jury.members, netzwerkContent.jury.members);
id: 'rsm', name: 'RSM Ebner Stolz', role: 'Hauptsponsor', tier: 1, const expertiseRows = fallbackArray<ExpertiseRow>(expertise.rows, netzwerkContent.expertise.rows);
desc: 'Führende mittelständische Wirtschaftsprüfungs- und Steuerberatungsgesellschaft.', const partners = fallbackArray<Partner>(partnerSection.items, netzwerkContent.partners.items as Partner[]);
fullDesc: 'RSM Ebner Stolz ist eine der größten unabhängigen Wirtschaftsprüfungs- und Steuerberatungsgesellschaften Deutschlands mit besonderer Expertise im Mittelstand. Als Hauptsponsor des BMP unterstützen sie die Identifikation herausragender Unternehmen und bringen ihre Netzwerke aktiv in die Nominierungsphase ein.', const partnerTiers = fallbackArray<PartnerTier>(partnerSection.tiers, netzwerkContent.partners.tiers);
logo: (<span style={{ display: 'flex', alignItems: 'center', gap: 6 }}><span style={{ display: 'flex', gap: 2 }}><span style={{ width: 10, height: 10, background: '#6b7280', display: 'block' }} /><span style={{ width: 10, height: 10, background: '#22c55e', display: 'block' }} /><span style={{ width: 10, height: 10, background: '#3b82f6', display: 'block' }} /></span><span style={{ fontWeight: 900, fontSize: 15, letterSpacing: '-0.02em', color: '#374151', fontFamily: FF }}>RSM <span style={{ color: '#6b7280', fontWeight: 600 }}>EBNER STOLZ</span></span></span>), const mobileStats = fallbackArray<StatItem>(juryIntro.stats, netzwerkContent.juryIntro.stats);
industry: 'Wirtschaftsprüfung & Beratung', location: 'München, Bayern', website: 'rsm-ebner-stolz.de', linkedin: 'https://linkedin.com/company/rsm-ebner-stolz', const desktopStats = fallbackArray<StatItem>(juryIntro.desktopStats, netzwerkContent.juryIntro.desktopStats);
}, const sponsorBenefits = fallbackArray<BenefitItem>(sponsoring.benefits, netzwerkContent.sponsoring.benefits);
{
id: 'wwk', name: 'WWK', role: 'Hauptsponsor', tier: 1,
desc: 'Eine starke Gemeinschaft Lebensversicherungsgruppe mit bayerischen Wurzeln.',
fullDesc: 'Die WWK Lebensversicherung a. G. ist eine der leistungsstärksten deutschen Lebensversicherungsgruppen. Als Münchner Traditionsunternehmen verbindet die WWK tiefe bayerische Verwurzelung mit finanzieller Stärke Werte, die sie mit dem Bayerischen Mittelstandspreis teilt.',
logo: (<span style={{ fontWeight: 900, fontSize: 24, letterSpacing: '-0.02em', color: '#16a34a', fontFamily: 'Arial Black, sans-serif' }}>WWK</span>),
industry: 'Lebensversicherung', location: 'München, Bayern', website: 'wwk.de', linkedin: 'https://linkedin.com/company/wwk-versicherung',
},
/* ── TIER 2 · Medienpartner ──────────────────────────────── */
{
id: 'radiogong', name: 'Radio Gong 96.3', role: 'Medienpartner', tier: 2,
desc: 'Der reichweitenstärkste Radiosender Münchens Stimme des Mittelstands.',
fullDesc: 'Radio Gong 96.3 ist der reichweitenstärkste Radiosender Münchens. Als Medienpartner des BMP sorgt er für Aufmerksamkeit für die Nominierten in Sendemitschnitten und redaktionellen Beiträgen.',
logo: (<span style={{ background: '#e11d48', color: '#fff', fontWeight: 900, fontSize: 14, padding: '4px 10px', lineHeight: 1.25, display: 'inline-block', textAlign: 'center' as const, fontFamily: '"IBM Plex Sans", sans-serif' }}>Radio<br/>Gong <span style={{ color: '#fde047' }}>96.3</span></span>),
industry: 'Medien', location: 'München', website: 'radiogong.com',
},
{
id: 'muenchentv', name: 'münchen.tv', role: 'Medienpartner', tier: 2,
desc: 'Der lokale Fernsehsender der Landeshauptstadt München.',
fullDesc: 'münchen.tv ist der lokale Fernsehsender der Landeshauptstadt München. Als Medienpartner überträgt münchen.tv Highlights der BMP-Gala und produziert Porträts der Nominierten.',
logo: (<span style={{ fontWeight: 900, fontSize: 15, color: '#0ea5e9', fontFamily: '"Inter", sans-serif' }}>münchen<span style={{ color: '#1e293b' }}>.tv</span></span>),
industry: 'Medien', location: 'München', website: 'muenchen.tv',
},
/* ── TIER 3 · Weitere Sponsoren ──────────────────────────── */
{
id: 'metzler', name: 'METZLER', role: 'Sponsor', tier: 3,
desc: 'Älteste Privatbank Deutschlands Unabhängigkeit seit 1674.',
fullDesc: 'B. Metzler seel. Sohn & Co. KGaA ist die älteste deutsche Privatbank in Familienbesitz seit über 350 Jahren unabhängig. Ihr Engagement für den BMP unterstreicht die tiefe Verbundenheit mit dem Mittelstand, dessen Werte wie Verlässlichkeit, Substanz und Langfristigkeit Metzler selbst verkörpert.',
logo: (<span style={{ fontWeight: 900, fontSize: 21, letterSpacing: '0.15em', color: '#1a1a1a', fontFamily: 'Georgia, serif' }}>METZLER</span>),
industry: 'Privatbankwesen', location: 'Frankfurt a. M.', website: 'metzler.com', linkedin: 'https://linkedin.com/company/metzler',
},
{
id: 'wieselhuber', name: 'Dr. Wieselhuber & Partner', role: 'Sponsor', tier: 3,
desc: 'Unabhängige Top-Management-Beratung für Familienunternehmen und Mittelstand.',
fullDesc: 'Dr. Wieselhuber & Partner ist eine unabhängige, branchenübergreifende Top-Management-Beratung mit besonderem Fokus auf Familienunternehmen und Mittelstand. Als Sponsor bringt das Haus seine Strategie- und Transformationsexpertise in das Netzwerk des BMP ein.',
logo: (<span style={{ fontWeight: 900, fontSize: 16, letterSpacing: '-0.01em', color: '#1a2b4a', fontFamily: FF }}>W&amp;P <span style={{ fontWeight: 600, color: '#5a6b85' }}>WIESELHUBER</span></span>),
industry: 'Unternehmensberatung', location: 'München, Bayern', website: 'wieselhuber.de',
},
{
id: 'fristads', name: 'Fristads', role: 'Sponsor', tier: 3,
desc: 'Premium-Arbeitsbekleidung Funktion, Qualität und Design.',
fullDesc: 'Fristads steht für hochwertige, funktionale Arbeitsbekleidung, die Schutz, Komfort und Design vereint. Als Sponsor des Bayerischen Mittelstandspreises unterstützt das Unternehmen die Auszeichnung herausragender mittelständischer Betriebe.',
logo: (<span style={{ fontWeight: 900, fontSize: 19, letterSpacing: '0.02em', color: '#1a1a1a', fontFamily: 'Arial Black, sans-serif' }}>FRISTADS</span>),
industry: 'Workwear & Textil', location: 'Deutschland', website: 'fristads.com',
},
{
id: 'newedge', name: 'New Edge', role: 'Sponsor', tier: 3,
desc: 'Digitale Markenführung, Web und Design für den Mittelstand.',
fullDesc: 'New Edge gestaltet Marken, Web-Erlebnisse und digitale Auftritte für mittelständische Unternehmen. Als Sponsor des BMP unterstützt die Agentur die Sichtbarkeit und das digitale Profil ausgezeichneter Unternehmen.',
logo: (<span style={{ display: 'flex', alignItems: 'center', gap: 6 }}><span style={{ width: 22, height: 22, background: '#3a3a3a', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 900, fontSize: 11, fontFamily: FF }}>NE</span><span style={{ fontWeight: 700, fontSize: 15, color: '#005E67', fontFamily: FF }}>New Edge</span></span>),
industry: 'Branding & Digital', location: 'Bayern', website: 'newedgebrand.com',
},
{
id: 'moodtalk', name: 'Moodtalk', role: 'Sponsor', tier: 3,
desc: 'Plattform für Teamkultur und Mitarbeiter-Feedback.',
fullDesc: 'Moodtalk ist eine Plattform für Teamkultur, Stimmung und kontinuierliches Mitarbeiter-Feedback. Als Sponsor des BMP steht das Unternehmen für moderne, wertebasierte Unternehmensführung.',
logo: (<span style={{ display: 'flex', alignItems: 'center', gap: 6 }}><span style={{ width: 20, height: 20, borderRadius: '50%', background: '#7c3aed', display: 'block' }} /><span style={{ fontWeight: 700, fontSize: 16, color: '#7c3aed', fontFamily: FF }}>moodtalk</span></span>),
industry: 'HR & Software', location: 'Bayern', website: 'moodtalk.io',
},
{
id: 'deutschebank', name: 'Deutsche Bank', role: 'Sponsor', tier: 3,
desc: 'Partnerbank für die Finanzierung und Skalierung von KMU-Wachstum.',
fullDesc: 'Die Deutsche Bank begleitet mittelständische Unternehmen als verlässlicher Finanzpartner durch alle Wachstumsphasen. Im Rahmen des BMP stellt sie ihr deutschlandweites Netzwerk, spezifische Mittelstandsprodukte und direkte Ansprechpartner für die Nominierten bereit.',
logo: (<span style={{ display: 'flex', alignItems: 'center', gap: 8 }}><svg width="20" height="20" viewBox="0 0 20 20"><rect x="1" y="1" width="18" height="18" fill="none" stroke="#1d1d1b" strokeWidth="1.5" /><line x1="5" y1="15" x2="15" y2="5" stroke="#1d1d1b" strokeWidth="2" /></svg><span style={{ fontWeight: 600, fontSize: 15, color: '#1d1d1b', fontFamily: FB }}>Deutsche Bank</span></span>),
industry: 'Bankwesen & Finanzen', location: 'Frankfurt a. M.', website: 'db.com', linkedin: 'https://linkedin.com/company/deutsche-bank',
},
{
id: 'bionorica', name: 'Bionorica', role: 'Sponsor', tier: 3,
desc: 'Weltmarktführer für pflanzliche Arzneimittel aus dem bayerischen Neumarkt.',
fullDesc: 'Bionorica SE ist ein international agierendes Pharmaunternehmen mit Sitz in Neumarkt i. d. OPf. und Weltmarktführer bei pflanzlichen Arzneimitteln. Das Unternehmen selbst ist ein Paradebeispiel eines bayerischen Mittelständlers mit globalem Anspruch und verkörpert die Innovationskraft, die der BMP würdigt.',
logo: (<span style={{ display: 'flex', alignItems: 'center', gap: 6 }}><svg width="20" height="20" viewBox="0 0 20 20"><circle cx="10" cy="10" r="9" fill="#16a34a" /><path d="M10 3 Q14 7 14 10 Q14 14 10 17 Q6 14 6 10 Q6 7 10 3Z" fill="white" opacity="0.8" /></svg><span style={{ fontWeight: 700, fontSize: 15, color: '#16a34a', fontFamily: FB }}>Bionorica<sup style={{ fontSize: 8 }}>®</sup></span></span>),
industry: 'Pharmaindustrie', location: 'Neumarkt i.d.OPf., Bayern', website: 'bionorica.de', linkedin: 'https://linkedin.com/company/bionorica',
},
{
id: 'primus', name: 'Primus', role: 'Sponsor', tier: 3,
desc: 'Partner des Bayerischen Mittelstandspreises.',
fullDesc: 'Primus unterstützt als Sponsor den Bayerischen Mittelstandspreis und sein Engagement für herausragende Unternehmen des bayerischen Mittelstands.',
logo: (<span style={{ fontWeight: 900, fontSize: 18, letterSpacing: '0.04em', color: '#b8860b', fontFamily: 'Georgia, serif' }}>PRIMUS</span>),
industry: 'Partner', location: 'Bayern', website: '',
},
];
const expertiseRows = [
{ num: '01', label: 'Aktenstudium', body: 'Alle Einreichungen werden zunächst vollständig gesichtet und nach den festgelegten Kriterien geprüft.' },
{ num: '02', label: 'Scoring-Matrix', body: 'Jedes Jurymitglied bewertet die Einreichungen unabhängig nach den festgelegten Kriterien.' },
{ num: '03', label: 'Jury-Plenum', body: 'In der gemeinsamen Sitzung werden Argumente und Ansichten ausgetauscht, Eigenschaften verglichen und gemeinsam bewertet. Daraus entsteht die Liste der Finalisten.' },
{ num: '04', label: 'Ergebnis', body: 'Die Gewinner des Bayerischen Mittelstandspreises werden nach vorgegebenem Punktesystem bestimmt, erweitert um Jury-Mitglieder aus der Praxis.' },
];
return ( return (
<div className="animate-fade-in"> <div className="animate-fade-in">
{/* ── 1. HERO ──────────────────────────────────────────────────────────── */} {/* ── 1. HERO ──────────────────────────────────────────────────────────── */}
<section style={{ position: 'relative', minHeight: '72vh', display: 'flex', alignItems: 'flex-end', overflow: 'hidden', background: '#060C14' }}> <section style={{ position: 'relative', minHeight: '72vh', display: 'flex', alignItems: 'flex-end', overflow: 'hidden', background: '#060C14' }}>
<Image unoptimized src="/images/netzwerk-hero.jpg" alt="BMP Netzwerk Preisverleihung" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 30%' }} /> <Image unoptimized src={mediaUrl(hero.image, `/images/${netzwerkContent.hero.imageFilename}`)} alt={mediaAlt(hero.image, fallbackText(hero.imageAlt, netzwerkContent.hero.imageAlt))} style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 30%' }} />
<div style={{ position: 'absolute', inset: 0, background: isMobile ? 'linear-gradient(to top, rgba(2,9,48,0.95) 0%, rgba(2,9,48,0.72) 38%, rgba(2,9,48,0.22) 72%, transparent 100%)' : 'linear-gradient(to right, #020930 0%, rgba(2,9,48,0.90) 38%, rgba(2,9,48,0.18) 65%, transparent 100%)' }} /> <div style={{ position: 'absolute', inset: 0, background: isMobile ? 'linear-gradient(to top, rgba(2,9,48,0.95) 0%, rgba(2,9,48,0.72) 38%, rgba(2,9,48,0.22) 72%, transparent 100%)' : 'linear-gradient(to right, #020930 0%, rgba(2,9,48,0.90) 38%, rgba(2,9,48,0.18) 65%, transparent 100%)' }} />
<div style={{ position: 'absolute', top: 0, left: 80, width: 2, height: '100%', background: `linear-gradient(to bottom, transparent, ${GOLD}, transparent)`, opacity: 0.4 }} /> <div style={{ position: 'absolute', top: 0, left: 80, width: 2, height: '100%', background: `linear-gradient(to bottom, transparent, ${GOLD}, transparent)`, opacity: 0.4 }} />
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: 'linear-gradient(to right, #EFBF04, rgba(239,191,4,0.3), transparent)', zIndex: 2 }} /> <div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: 'linear-gradient(to right, #EFBF04, rgba(239,191,4,0.3), transparent)', zIndex: 2 }} />
@@ -512,13 +473,13 @@ const Netzwerk: React.FC = () => {
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '0 24px 48px' : '0 80px 88px', maxWidth: 860 }}> <div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '0 24px 48px' : '0 80px 88px', maxWidth: 860 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 28 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 28 }}>
<div style={{ width: 40, height: 2, background: GOLD }} /> <div style={{ width: 40, height: 2, background: GOLD }} />
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: GOLD }}>Jury & Partner</span> <span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: GOLD }}>{fallbackText(hero.eyebrow, netzwerkContent.hero.eyebrow)}</span>
</div> </div>
<h1 style={{ fontFamily: FF, fontSize: isMobile ? 'clamp(1.6rem, 6vw, 5rem)' : 'clamp(2.8rem, 6vw, 5rem)', fontWeight: 900, color: '#fff', lineHeight: 0.95, letterSpacing: '-0.03em', textTransform: 'uppercase', margin: '0 0 28px' }}> <h1 style={{ fontFamily: FF, fontSize: isMobile ? 'clamp(1.6rem, 6vw, 5rem)' : 'clamp(2.8rem, 6vw, 5rem)', fontWeight: 900, color: '#fff', lineHeight: 0.95, letterSpacing: '-0.03em', textTransform: 'uppercase', margin: '0 0 28px' }}>
DAS NETZWERK<br />HINTER DEM <span style={{ color: GOLD }}>AWARD.</span> <Lines text={fallbackText(hero.heading, netzwerkContent.hero.heading)} highlight={fallbackText(hero.highlight, netzwerkContent.hero.highlight)} />
</h1> </h1>
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(255,255,255,0.65)', lineHeight: 1.7, maxWidth: 560, margin: 0, fontWeight: 300 }}> <p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(255,255,255,0.65)', lineHeight: 1.7, maxWidth: 560, margin: 0, fontWeight: 300 }}>
Der Bayerische Mittelstandspreis wird getragen von einem starken Netzwerk aus Schirmherrschaft, unabhängiger Fach-Jury und langjährigen Partnern aus Wirtschaft und Gesellschaft. {fallbackText(hero.description, netzwerkContent.hero.description)}
</p> </p>
</div> </div>
</section> </section>
@@ -527,48 +488,43 @@ const Netzwerk: React.FC = () => {
{/* ── 2. SCHIRMHERRSCHAFT ──────────────────────────────────────────────── */} {/* ── 2. SCHIRMHERRSCHAFT ──────────────────────────────────────────────── */}
<section id="schirmherrschaft" style={{ background: NAVY }}> <section id="schirmherrschaft" style={{ background: NAVY }}>
<div style={{ padding: isMobile ? '48px 24px 32px' : '80px 80px 56px' }}> <div style={{ padding: isMobile ? '48px 24px 32px' : '80px 80px 56px' }}>
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: GOLD, display: 'block', marginBottom: 16 }}>Die Schirmherrschaft</span> <span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: GOLD, display: 'block', marginBottom: 16 }}>{fallbackText(patronage.eyebrow, netzwerkContent.patronage.eyebrow)}</span>
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 4vw, 3rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1, margin: '0 0 20px' }}>Schirmherrin und Schirmherr</h2> <h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 4vw, 3rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1, margin: '0 0 20px' }}>{fallbackText(patronage.heading, netzwerkContent.patronage.heading)}</h2>
<div style={{ width: 40, height: 2, background: GOLD, marginBottom: 14 }} /> <div style={{ width: 40, height: 2, background: GOLD, marginBottom: 14 }} />
<p style={{ fontFamily: FF, fontSize: 18, color: 'rgba(255,255,255,0.5)', margin: 0 }}>des Bayerischen Mittelstandspreises</p> <p style={{ fontFamily: FF, fontSize: 18, color: 'rgba(255,255,255,0.5)', margin: 0 }}>{fallbackText(patronage.description, netzwerkContent.patronage.description)}</p>
</div> </div>
<div style={{ display: isMobile ? 'flex' : 'grid', gridTemplateColumns: isMobile ? undefined : '1fr 1fr', overflowX: isMobile ? 'auto' : undefined, scrollSnapType: isMobile ? 'x mandatory' : undefined, gap: isMobile ? 14 : 0, padding: isMobile ? '0 24px 28px' : 0, WebkitOverflowScrolling: 'touch', scrollbarWidth: 'none' }}> <div style={{ display: isMobile ? 'flex' : 'grid', gridTemplateColumns: isMobile ? undefined : '1fr 1fr', overflowX: isMobile ? 'auto' : undefined, scrollSnapType: isMobile ? 'x mandatory' : undefined, gap: isMobile ? 14 : 0, padding: isMobile ? '0 24px 28px' : 0, WebkitOverflowScrolling: 'touch', scrollbarWidth: 'none' }}>
<PersonCard {patronPeople.map((person, index) => (
imgSrc="https://images.unsplash.com/photo-1551836022-d5d88e9218df?auto=format&fit=crop&q=80&w=800" <PersonCard
imgAlt="Ilse Aigner" key={`${person.name}-${index}`}
name="Ilse Aigner MdL" imgSrc={fallbackText(person.image, netzwerkContent.patronage.people[index]?.image || '')}
titleLine1="Präsidentin des Bayerischen Landtags" imgAlt={fallbackText(person.imageAlt, person.name)}
titleLine2="Bayerische Staatsministerin für Wirtschaft a.D." name={fallbackText(person.name, netzwerkContent.patronage.people[index]?.name || '')}
institution="Bayerischer Landtag" titleLine1={fallbackText(person.titleLine1, netzwerkContent.patronage.people[index]?.titleLine1 || '')}
borderRight titleLine2={fallbackText(person.titleLine2, netzwerkContent.patronage.people[index]?.titleLine2 || '')}
/> institution={fallbackText(person.institution, netzwerkContent.patronage.people[index]?.institution || '')}
<PersonCard borderRight={index === 0}
imgSrc="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&q=80&w=800" />
imgAlt="Hubert Aiwanger" ))}
name="Hubert Aiwanger MdL"
titleLine1="Bayerischer Staatsminister für Wirtschaft,"
titleLine2="Landesentwicklung und Energie · Stv. Ministerpräsident"
institution="Bayerische Staatsregierung"
/>
</div> </div>
{/* Grußwort Ilse Aigner */} {/* Grußwort Ilse Aigner */}
<div style={{ background: 'rgba(255,255,255,0.03)', borderTop: '1px solid rgba(255,255,255,0.07)' }}> <div style={{ background: 'rgba(255,255,255,0.03)', borderTop: '1px solid rgba(255,255,255,0.07)' }}>
<div style={{ padding: isMobile ? '32px 24px' : '64px 80px', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '40% 60%', gap: isMobile ? '24px 0' : '0 64px', alignItems: 'flex-start' }}> <div style={{ padding: isMobile ? '32px 24px' : '64px 80px', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '40% 60%', gap: isMobile ? '24px 0' : '0 64px', alignItems: 'flex-start' }}>
<div> <div>
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: GOLD, display: 'block', marginBottom: 8 }}>Grußwort</span> <span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: GOLD, display: 'block', marginBottom: 8 }}>{fallbackText(patronage.greetingEyebrow, netzwerkContent.patronage.greetingEyebrow)}</span>
<div style={{ fontFamily: FF, fontSize: 11, fontWeight: 400, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'rgba(255,255,255,0.3)', marginBottom: 16 }}>Schirmherrin</div> <div style={{ fontFamily: FF, fontSize: 11, fontWeight: 400, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'rgba(255,255,255,0.3)', marginBottom: 16 }}>{fallbackText(patronage.greetingRole, netzwerkContent.patronage.greetingRole)}</div>
<div style={{ fontFamily: FF, fontSize: 18, fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.15, marginBottom: 10 }}>Ilse Aigner MdL</div> <div style={{ fontFamily: FF, fontSize: 18, fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.15, marginBottom: 10 }}>{fallbackText(patronage.greetingName, netzwerkContent.patronage.greetingName)}</div>
<div style={{ fontFamily: FF, fontSize: 18, color: 'rgba(255,255,255,0.5)', lineHeight: 1.6, marginBottom: 4 }}>Präsidentin des Bayerischen Landtags</div> <div style={{ fontFamily: FF, fontSize: 18, color: 'rgba(255,255,255,0.5)', lineHeight: 1.6, marginBottom: 4 }}>{fallbackText(patronage.greetingTitleLine1, netzwerkContent.patronage.greetingTitleLine1)}</div>
<div style={{ fontFamily: FF, fontSize: 17, color: 'rgba(255,255,255,0.4)', lineHeight: 1.6 }}>Bayerische Staatsministerin für Wirtschaft a.D.</div> <div style={{ fontFamily: FF, fontSize: 17, color: 'rgba(255,255,255,0.4)', lineHeight: 1.6 }}>{fallbackText(patronage.greetingTitleLine2, netzwerkContent.patronage.greetingTitleLine2)}</div>
</div> </div>
<div> <div>
<blockquote style={{ fontFamily: FF, fontSize: 18, fontStyle: 'italic', color: 'rgba(255,255,255,0.8)', lineHeight: 1.75, margin: 0, paddingLeft: 24, borderLeft: `3px solid ${GOLD}` }}> <blockquote style={{ fontFamily: FF, fontSize: 18, fontStyle: 'italic', color: 'rgba(255,255,255,0.8)', lineHeight: 1.75, margin: 0, paddingLeft: 24, borderLeft: `3px solid ${GOLD}` }}>
Der Bayerische Mittelstandspreis steht für das, was Bayern stark macht: Unternehmergeist, Verantwortung und Qualität. Mit großer Freude übernehme ich erneut die Schirmherrschaft für diesen bedeutenden Preis. {fallbackText(patronage.greetingQuote, netzwerkContent.patronage.greetingQuote)}
</blockquote> </blockquote>
<div style={{ fontFamily: FF, fontSize: 11, color: 'rgba(255,255,255,0.35)', marginTop: 20, paddingLeft: 24 }}> <div style={{ fontFamily: FF, fontSize: 11, color: 'rgba(255,255,255,0.35)', marginTop: 20, paddingLeft: 24 }}>
Ilse Aigner MdL, Präsidentin des Bayerischen Landtags {fallbackText(patronage.greetingAttribution, netzwerkContent.patronage.greetingAttribution)}
</div> </div>
</div> </div>
</div> </div>
@@ -587,24 +543,20 @@ const Netzwerk: React.FC = () => {
{isMobile ? ( {isMobile ? (
/* Mobile: one cohesive navy module copy + compact stat strip */ /* Mobile: one cohesive navy module copy + compact stat strip */
<div style={{ padding: '48px 24px', position: 'relative', zIndex: 1 }}> <div style={{ padding: '48px 24px', position: 'relative', zIndex: 1 }}>
<span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: GOLD, display: 'block', marginBottom: 18 }}>Wer entscheidet?</span> <span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: GOLD, display: 'block', marginBottom: 18 }}>{fallbackText(juryIntro.eyebrow, netzwerkContent.juryIntro.eyebrow)}</span>
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.7rem, 8vw, 2.2rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.08, margin: '0 0 22px' }}> <h2 style={{ fontFamily: FF, fontSize: 'clamp(1.7rem, 8vw, 2.2rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.08, margin: '0 0 22px' }}>
UNABHÄNGIGE EXPERTISE FÜR DEN MITTELSTAND. <Lines text={fallbackText(juryIntro.heading, netzwerkContent.juryIntro.heading)} />
</h2> </h2>
<div style={{ width: 40, height: 2, background: GOLD, marginBottom: 22 }} /> <div style={{ width: 40, height: 2, background: GOLD, marginBottom: 22 }} />
<p style={{ fontFamily: FB, fontSize: 16, color: 'rgba(255,255,255,0.55)', lineHeight: 1.75, margin: 0 }}> <p style={{ fontFamily: FB, fontSize: 16, color: 'rgba(255,255,255,0.55)', lineHeight: 1.75, margin: 0 }}>
Die Jury des Bayerischen Mittelstandspreises besteht ausschließlich aus ehrenamtlich tätigen Expertinnen und Experten. Kein Mitglied steht in wirtschaftlicher Verbindung zu einem Bewerber Transparenz und Unparteilichkeit sind die Grundpfeiler unseres Verfahrens. {fallbackText(juryIntro.description, netzwerkContent.juryIntro.description)}
</p> </p>
{/* Stat strip */} {/* Stat strip */}
<div style={{ display: 'flex', marginTop: 32, paddingTop: 26, borderTop: '1px solid rgba(255,255,255,0.14)' }}> <div style={{ display: 'flex', marginTop: 32, paddingTop: 26, borderTop: '1px solid rgba(255,255,255,0.14)' }}>
{[ {mobileStats.map((s, i) => (
{ v: '11', l: 'Jurymitglieder' },
{ v: 'Mehrstufig', l: 'Bewertungsverfahren' },
{ v: '100%', l: 'Unabhängig' },
].map((s, i) => (
<div key={i} style={{ flex: 1, minWidth: 0, paddingLeft: i > 0 ? 14 : 0, borderLeft: i > 0 ? '1px solid rgba(255,255,255,0.14)' : 'none' }}> <div key={i} style={{ flex: 1, minWidth: 0, paddingLeft: i > 0 ? 14 : 0, borderLeft: i > 0 ? '1px solid rgba(255,255,255,0.14)' : 'none' }}>
<div style={{ fontFamily: FF, fontSize: 'clamp(2.2rem, 9vw, 3rem)', fontWeight: 900, color: GOLD, letterSpacing: '-0.03em', lineHeight: 1 }}>{s.v}</div> <div style={{ fontFamily: FF, fontSize: 'clamp(2.2rem, 9vw, 3rem)', fontWeight: 900, color: GOLD, letterSpacing: '-0.03em', lineHeight: 1 }}>{fallbackText(s.value, netzwerkContent.juryIntro.stats[i]?.value || '')}</div>
<div style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: 'rgba(255,255,255,0.5)', marginTop: 8, lineHeight: 1.3 }}>{s.l}</div> <div style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: 'rgba(255,255,255,0.5)', marginTop: 8, lineHeight: 1.3 }}>{fallbackText(s.label, netzwerkContent.juryIntro.stats[i]?.label || '')}</div>
</div> </div>
))} ))}
</div> </div>
@@ -614,29 +566,29 @@ const Netzwerk: React.FC = () => {
{/* Left Cream, stats */} {/* Left Cream, stats */}
<div style={{ background: CREAM, padding: '72px 64px', display: 'flex', flexDirection: 'column', justifyContent: 'center', position: 'relative', zIndex: 1 }}> <div style={{ background: CREAM, padding: '72px 64px', display: 'flex', flexDirection: 'column', justifyContent: 'center', position: 'relative', zIndex: 1 }}>
<div> <div>
<div style={{ fontFamily: FF, fontSize: 'clamp(5rem, 8.5vw, 7.5rem)', fontWeight: 900, color: '#101828', letterSpacing: '-0.04em', lineHeight: 1 }}>11</div> <div style={{ fontFamily: FF, fontSize: 'clamp(5rem, 8.5vw, 7.5rem)', fontWeight: 900, color: '#101828', letterSpacing: '-0.04em', lineHeight: 1 }}>{fallbackText(desktopStats[0]?.value, netzwerkContent.juryIntro.desktopStats[0].value)}</div>
<div style={{ fontFamily: FF, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.15em', color: 'rgba(16,24,40,0.4)', marginTop: 4 }}>Unabhängige Jurymitglieder</div> <div style={{ fontFamily: FF, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.15em', color: 'rgba(16,24,40,0.4)', marginTop: 4 }}>{fallbackText(desktopStats[0]?.label, netzwerkContent.juryIntro.desktopStats[0].label)}</div>
</div> </div>
<div style={{ width: '100%', height: 1, background: 'rgba(3,9,58,0.1)', margin: '32px 0' }} /> <div style={{ width: '100%', height: 1, background: 'rgba(3,9,58,0.1)', margin: '32px 0' }} />
<div> <div>
<div style={{ fontFamily: FF, fontSize: 'clamp(2.4rem, 4vw, 3.4rem)', fontWeight: 900, color: '#101828', letterSpacing: '-0.03em', lineHeight: 1 }}>Mehrstufig</div> <div style={{ fontFamily: FF, fontSize: 'clamp(2.4rem, 4vw, 3.4rem)', fontWeight: 900, color: '#101828', letterSpacing: '-0.03em', lineHeight: 1 }}>{fallbackText(desktopStats[1]?.value, netzwerkContent.juryIntro.desktopStats[1].value)}</div>
<div style={{ fontFamily: FF, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.15em', color: 'rgba(16,24,40,0.4)', marginTop: 8 }}>Bewertungsverfahren</div> <div style={{ fontFamily: FF, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.15em', color: 'rgba(16,24,40,0.4)', marginTop: 8 }}>{fallbackText(desktopStats[1]?.label, netzwerkContent.juryIntro.desktopStats[1].label)}</div>
</div> </div>
<div style={{ width: '100%', height: 1, background: 'rgba(3,9,58,0.1)', margin: '32px 0' }} /> <div style={{ width: '100%', height: 1, background: 'rgba(3,9,58,0.1)', margin: '32px 0' }} />
<div> <div>
<div style={{ fontFamily: FF, fontSize: 'clamp(5rem, 8.5vw, 7.5rem)', fontWeight: 900, color: '#101828', letterSpacing: '-0.04em', lineHeight: 1 }}>100%</div> <div style={{ fontFamily: FF, fontSize: 'clamp(5rem, 8.5vw, 7.5rem)', fontWeight: 900, color: '#101828', letterSpacing: '-0.04em', lineHeight: 1 }}>{fallbackText(desktopStats[2]?.value, netzwerkContent.juryIntro.desktopStats[2].value)}</div>
<div style={{ fontFamily: FF, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.15em', color: 'rgba(16,24,40,0.4)', marginTop: 4 }}>Unabhängig &amp; ehrenamtlich</div> <div style={{ fontFamily: FF, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.15em', color: 'rgba(16,24,40,0.4)', marginTop: 4 }}>{fallbackText(desktopStats[2]?.label, netzwerkContent.juryIntro.desktopStats[2].label)}</div>
</div> </div>
</div> </div>
{/* Right Navy, editorial copy */} {/* Right Navy, editorial copy */}
<div style={{ background: NAVY, padding: '72px 64px', display: 'flex', flexDirection: 'column', justifyContent: 'center', position: 'relative', zIndex: 1 }}> <div style={{ background: NAVY, padding: '72px 64px', display: 'flex', flexDirection: 'column', justifyContent: 'center', position: 'relative', zIndex: 1 }}>
<span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: GOLD, marginBottom: 20 }}>Wer entscheidet?</span> <span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: GOLD, marginBottom: 20 }}>{fallbackText(juryIntro.eyebrow, netzwerkContent.juryIntro.eyebrow)}</span>
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.6rem, 2.5vw, 2.2rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.08, margin: '0 0 24px' }}> <h2 style={{ fontFamily: FF, fontSize: 'clamp(1.6rem, 2.5vw, 2.2rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.08, margin: '0 0 24px' }}>
UNABHÄNGIGE EXPERTISE FÜR DEN MITTELSTAND. <Lines text={fallbackText(juryIntro.heading, netzwerkContent.juryIntro.heading)} />
</h2> </h2>
<div style={{ width: 40, height: 2, background: GOLD }} /> <div style={{ width: 40, height: 2, background: GOLD }} />
<p style={{ marginTop: 24, fontFamily: FB, fontSize: 19, color: 'rgba(255,255,255,0.45)', lineHeight: 1.85 }}> <p style={{ marginTop: 24, fontFamily: FB, fontSize: 19, color: 'rgba(255,255,255,0.45)', lineHeight: 1.85 }}>
Die Jury des Bayerischen Mittelstandspreises besteht ausschließlich aus ehrenamtlich tätigen Expertinnen und Experten. Kein Mitglied steht in wirtschaftlicher Verbindung zu einem Bewerber Transparenz und Unparteilichkeit sind die Grundpfeiler unseres Verfahrens. {fallbackText(juryIntro.description, netzwerkContent.juryIntro.description)}
</p> </p>
</div> </div>
</> </>
@@ -648,17 +600,17 @@ const Netzwerk: React.FC = () => {
<section id="jury" style={{ background: NAVY, overflow: 'hidden', position: 'relative' }}> <section id="jury" style={{ background: NAVY, overflow: 'hidden', position: 'relative' }}>
<div style={{ padding: isMobile ? '48px 24px 32px' : '80px 80px 56px', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: isMobile ? 16 : 48, alignItems: 'flex-end', borderBottom: '1px solid rgba(255,255,255,0.07)' }}> <div style={{ padding: isMobile ? '48px 24px 32px' : '80px 80px 56px', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: isMobile ? 16 : 48, alignItems: 'flex-end', borderBottom: '1px solid rgba(255,255,255,0.07)' }}>
<div> <div>
<span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: GOLD, display: 'block', marginBottom: 16 }}>Das Gremium</span> <span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: GOLD, display: 'block', marginBottom: 16 }}>{fallbackText(jury.eyebrow, netzwerkContent.jury.eyebrow)}</span>
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 3.5vw, 3rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 0.95, margin: 0 }}>UNSERE JURY 2026</h2> <h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 3.5vw, 3rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 0.95, margin: 0 }}><Lines text={fallbackText(jury.heading, netzwerkContent.jury.heading)} /></h2>
</div> </div>
<p style={{ fontFamily: FB, fontSize: 19, color: 'rgba(255,255,255,0.4)', lineHeight: 1.8, margin: 0 }}> <p style={{ fontFamily: FB, fontSize: 19, color: 'rgba(255,255,255,0.4)', lineHeight: 1.8, margin: 0 }}>
Unabhängige Expertinnen und Experten aus Wirtschaft, Wissenschaft und Verbänden, für einen vollständig unabhängigen Bewertungsprozess. {fallbackText(jury.description, netzwerkContent.jury.description)}
</p> </p>
</div> </div>
{/* ── Prominenter Vorsitz-Bereich ─────────────────────────────────── */} {/* ── Prominenter Vorsitz-Bereich ─────────────────────────────────── */}
{/* TODO: echten Jury-Vorsitzenden bestätigen (Name & Foto sind Platzhalter) */} {/* TODO: echten Jury-Vorsitzenden bestätigen (Name & Foto sind Platzhalter) */}
<div style={{ padding: isMobile ? '28px 24px 8px' : '64px 80px 16px' }}> <div style={{ padding: isMobile ? '28px 24px 8px' : '64px 80px 16px' }}>
<JuryChairCard member={juryMembers[0]} /> <JuryChairCard member={juryMembers[0]} badge={fallbackText(jury.chairBadge, netzwerkContent.jury.chairBadge)} />
</div> </div>
{/* ── Übrige Jury-Mitglieder ──────────────────────────────────────── */} {/* ── Übrige Jury-Mitglieder ──────────────────────────────────────── */}
@@ -671,7 +623,7 @@ const Netzwerk: React.FC = () => {
{/* ── Hinweis: Doppelrolle Jury / Partner (#179) ──────────────────── */} {/* ── Hinweis: Doppelrolle Jury / Partner (#179) ──────────────────── */}
<div style={{ padding: isMobile ? '4px 24px 36px' : '8px 80px 56px' }}> <div style={{ padding: isMobile ? '4px 24px 36px' : '8px 80px 56px' }}>
<p style={{ fontFamily: FB, fontSize: isMobile ? 13 : 14, color: 'rgba(255,255,255,0.35)', lineHeight: 1.6, margin: 0, maxWidth: 720 }}> <p style={{ fontFamily: FB, fontSize: isMobile ? 13 : 14, color: 'rgba(255,255,255,0.35)', lineHeight: 1.6, margin: 0, maxWidth: 720 }}>
Hinweis: Einzelne Persönlichkeiten engagieren sich sowohl in der Jury als auch als Partner des BMP beide Rollen werden auf dieser Seite getrennt ausgewiesen. {fallbackText(jury.note, netzwerkContent.jury.note)}
</p> </p>
</div> </div>
</section> </section>
@@ -680,8 +632,8 @@ const Netzwerk: React.FC = () => {
{/* ── 5. HINTERGRUND & EXPERTISE ───────────────────────────────────────── */} {/* ── 5. HINTERGRUND & EXPERTISE ───────────────────────────────────────── */}
<section style={{ background: '#101828', overflow: 'hidden', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '55% 45%', borderTop: '1px solid rgba(255,255,255,0.07)' }}> <section style={{ background: '#101828', overflow: 'hidden', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '55% 45%', borderTop: '1px solid rgba(255,255,255,0.07)' }}>
<div style={{ padding: isMobile ? '48px 24px' : 80, display: 'flex', flexDirection: 'column', justifyContent: 'center', background: '#101828' }}> <div style={{ padding: isMobile ? '48px 24px' : 80, display: 'flex', flexDirection: 'column', justifyContent: 'center', background: '#101828' }}>
<span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: GOLD, display: 'block', marginBottom: 16 }}>Hintergrund &amp; Expertise</span> <span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: GOLD, display: 'block', marginBottom: 16 }}>{fallbackText(expertise.eyebrow, netzwerkContent.expertise.eyebrow)}</span>
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.8rem, 3vw, 2.6rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.0, margin: '0 0 24px' }}>WIE BEWERTET DIE JURY?</h2> <h2 style={{ fontFamily: FF, fontSize: 'clamp(1.8rem, 3vw, 2.6rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.0, margin: '0 0 24px' }}><Lines text={fallbackText(expertise.heading, netzwerkContent.expertise.heading)} /></h2>
<div style={{ width: 40, height: 2, background: GOLD, marginBottom: 40 }} /> <div style={{ width: 40, height: 2, background: GOLD, marginBottom: 40 }} />
<div> <div>
{expertiseRows.map((row) => ( {expertiseRows.map((row) => (
@@ -698,11 +650,11 @@ const Netzwerk: React.FC = () => {
<div style={{ padding: isMobile ? '48px 24px' : '80px 64px', display: 'flex', flexDirection: 'column', justifyContent: 'center', borderLeft: isMobile ? 'none' : '1px solid rgba(255,255,255,0.07)', borderTop: isMobile ? '1px solid rgba(255,255,255,0.07)' : 'none', overflow: 'hidden', background: '#101828' }}> <div style={{ padding: isMobile ? '48px 24px' : '80px 64px', display: 'flex', flexDirection: 'column', justifyContent: 'center', borderLeft: isMobile ? 'none' : '1px solid rgba(255,255,255,0.07)', borderTop: isMobile ? '1px solid rgba(255,255,255,0.07)' : 'none', overflow: 'hidden', background: '#101828' }}>
<span style={{ fontFamily: 'Georgia, serif', fontSize: 'clamp(60px, 8vw, 120px)', color: 'rgba(239,191,4,0.1)', lineHeight: 0.8, display: 'block', marginBottom: -20 }}>&ldquo;</span> <span style={{ fontFamily: 'Georgia, serif', fontSize: 'clamp(60px, 8vw, 120px)', color: 'rgba(239,191,4,0.1)', lineHeight: 0.8, display: 'block', marginBottom: -20 }}>&ldquo;</span>
<p style={{ fontFamily: FF, fontSize: 'clamp(1.1rem, 1.8vw, 1.4rem)', fontWeight: 300, color: 'rgba(255,255,255,0.75)', lineHeight: 1.65, margin: '0 0 40px' }}> <p style={{ fontFamily: FF, fontSize: 'clamp(1.1rem, 1.8vw, 1.4rem)', fontWeight: 300, color: 'rgba(255,255,255,0.75)', lineHeight: 1.65, margin: '0 0 40px' }}>
Die Jury des BMP ist vollständig von wirtschaftlichen Interessen unabhängig. Jede Entscheidung wird transparent nachvollzogen das ist unser Versprechen an die Unternehmen Bayerns. {fallbackText(expertise.quote, netzwerkContent.expertise.quote)}
</p> </p>
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<div style={{ width: 40, height: 2, background: GOLD, flexShrink: 0 }} /> <div style={{ width: 40, height: 2, background: GOLD, flexShrink: 0 }} />
<span style={{ fontFamily: FF, fontSize: 17, color: 'rgba(255,255,255,0.4)' }}>Prof. Dr. Klaus Bergmann, Juryvorsitzender</span> <span style={{ fontFamily: FF, fontSize: 17, color: 'rgba(255,255,255,0.4)' }}>{fallbackText(expertise.quoteAttribution, netzwerkContent.expertise.quoteAttribution)}</span>
</div> </div>
</div> </div>
</section> </section>
@@ -713,26 +665,25 @@ const Netzwerk: React.FC = () => {
<MunichSkylineBg /> <MunichSkylineBg />
<div style={{ padding: isMobile ? '48px 24px 32px' : '80px 80px 56px', display: 'flex', flexDirection: isMobile ? 'column' : 'row', gap: isMobile ? 12 : 0, justifyContent: 'space-between', alignItems: isMobile ? 'flex-start' : 'flex-end', borderBottom: '1px solid rgba(3,9,58,0.1)' }}> <div style={{ padding: isMobile ? '48px 24px 32px' : '80px 80px 56px', display: 'flex', flexDirection: isMobile ? 'column' : 'row', gap: isMobile ? 12 : 0, justifyContent: 'space-between', alignItems: isMobile ? 'flex-start' : 'flex-end', borderBottom: '1px solid rgba(3,9,58,0.1)' }}>
<div> <div>
<span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: '#4A8FC9', display: 'block', marginBottom: 16 }}>Netzwerkqualität</span> <span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: '#4A8FC9', display: 'block', marginBottom: 16 }}>{fallbackText(partnerSection.eyebrow, netzwerkContent.partners.eyebrow)}</span>
<h2 style={{ fontFamily: FF, fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.025em', fontSize: 'clamp(2rem, 3.5vw, 3rem)', lineHeight: 0.95, margin: 0 }}>PARTNER &amp; SPONSOREN</h2> <h2 style={{ fontFamily: FF, fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.025em', fontSize: 'clamp(2rem, 3.5vw, 3rem)', lineHeight: 0.95, margin: 0 }}><Lines text={fallbackText(partnerSection.heading, netzwerkContent.partners.heading)} /></h2>
</div> </div>
<p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.4)', maxWidth: isMobile ? '100%' : 220, textAlign: isMobile ? 'left' : 'right', lineHeight: 1.6, margin: 0 }}> <p style={{ fontFamily: FB, fontSize: 18, color: 'rgba(16,24,40,0.4)', maxWidth: isMobile ? '100%' : 220, textAlign: isMobile ? 'left' : 'right', lineHeight: 1.6, margin: 0 }}>
Partner in drei Kategorien: Hauptsponsoren, Medienpartner und weitere Sponsoren. Klicken für Details. {fallbackText(partnerSection.description, netzwerkContent.partners.description)}
</p> </p>
</div> </div>
{/* Tiered grid continuous, ranks marked by label bars */} {/* Tiered grid continuous, ranks marked by label bars */}
<div style={{ position: 'relative', zIndex: 1 }}> <div style={{ position: 'relative', zIndex: 1 }}>
{([ {partnerTiers.map(t => {
{ tier: 1 as const, label: 'Hauptsponsoren', note: 'Premium', variant: 'lg' as const, cols: isMobile ? 1 : 2 }, const tier = Number(t.tier) as 1 | 2 | 3;
{ tier: 2 as const, label: 'Medienpartner', note: '', variant: 'md' as const, cols: isMobile ? 2 : 2 }, const variant = tier === 1 ? 'lg' as const : tier === 2 ? 'md' as const : 'sm' as const;
{ tier: 3 as const, label: 'Weitere Sponsoren', note: '', variant: 'sm' as const, cols: isMobile ? 2 : 4 }, const cols = isMobile ? (tier === 1 ? 1 : 2) : (tier === 3 ? 4 : 2);
]).map(t => { const items = partners.filter(p => p.tier === tier);
const items = partners.filter(p => p.tier === t.tier);
return ( return (
<div key={t.tier} style={{ display: 'grid', gridTemplateColumns: `repeat(${t.cols}, 1fr)` }}> <div key={t.tier} style={{ display: 'grid', gridTemplateColumns: `repeat(${cols}, 1fr)` }}>
{items.map((p, i) => ( {items.map((p, i) => (
<SponsorCell key={p.id} partner={p} variant={t.variant} idx={i} cols={t.cols} onClick={() => setSelectedPartner(p)} /> <SponsorCell key={p.id} partner={p} variant={variant} idx={i} cols={cols} detailLabel={fallbackText(partnerSection.detailLabel, netzwerkContent.partners.detailLabel)} premiumLabel={fallbackText(partnerSection.premiumLabel, netzwerkContent.partners.premiumLabel)} onClick={() => setSelectedPartner(p)} />
))} ))}
</div> </div>
); );
@@ -750,17 +701,13 @@ const Netzwerk: React.FC = () => {
<div style={{ flex: 1, position: 'relative', zIndex: 1, display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '4fr 1px 8fr', minHeight: 0, overflow: isMobile ? 'visible' : 'visible' }}> <div style={{ flex: 1, position: 'relative', zIndex: 1, display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '4fr 1px 8fr', minHeight: 0, overflow: isMobile ? 'visible' : 'visible' }}>
{/* Left pitch */} {/* Left pitch */}
<div style={{ padding: isMobile ? '32px 24px 24px' : '36px 36px 32px 52px', display: 'flex', flexDirection: 'column', justifyContent: 'center', overflow: 'visible' }}> <div style={{ padding: isMobile ? '32px 24px 24px' : '36px 36px 32px 52px', display: 'flex', flexDirection: 'column', justifyContent: 'center', overflow: 'visible' }}>
<span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: GOLD, display: 'block', marginBottom: 8 }}>Wachstum durch Partnerschaft</span> <span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: GOLD, display: 'block', marginBottom: 8 }}>{fallbackText(sponsoring.eyebrow, netzwerkContent.sponsoring.eyebrow)}</span>
<h2 style={{ fontFamily: FF, fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', fontSize: 'clamp(1.6rem, 2.4vw, 2.4rem)', lineHeight: 1.0, margin: '0 0 12px' }}>WIR FREUEN UNS ÜBER NEUE SPONSOREN.</h2> <h2 style={{ fontFamily: FF, fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', fontSize: 'clamp(1.6rem, 2.4vw, 2.4rem)', lineHeight: 1.0, margin: '0 0 12px' }}><Lines text={fallbackText(sponsoring.heading, netzwerkContent.sponsoring.heading)} /></h2>
<div style={{ width: 36, height: 2, background: GOLD, margin: '0 0 14px', flexShrink: 0 }} /> <div style={{ width: 36, height: 2, background: GOLD, margin: '0 0 14px', flexShrink: 0 }} />
<p style={{ fontFamily: FB, fontSize: 'clamp(15px, 1.2vw, 17px)', color: 'rgba(255,255,255,0.75)', lineHeight: 1.65, marginBottom: 16 }}> <p style={{ fontFamily: FB, fontSize: 'clamp(15px, 1.2vw, 17px)', color: 'rgba(255,255,255,0.75)', lineHeight: 1.65, marginBottom: 16 }}>
Positionieren Sie Ihre Marke im exklusivsten Netzwerk des bayerischen Mittelstands mit maßgeschneiderten Sponsoring-Paketen. {fallbackText(sponsoring.description, netzwerkContent.sponsoring.description)}
</p> </p>
{[ {sponsorBenefits.map((item) => (
{ num: '01', label: 'Markenpräsenz', sub: 'Bühne, Drucksachen, digitale Kanäle' },
{ num: '02', label: 'Top-Entscheider', sub: 'Exklusives Netzwerk-Dinner für Partner' },
{ num: '03', label: 'Multichannel', sub: 'Online, Print, Radio & TV-Partner' },
].map((item) => (
<div key={item.num} style={{ display: 'grid', gridTemplateColumns: '32px 1fr', gap: '0 12px', padding: '10px 0', borderBottom: '1px solid rgba(255,255,255,0.06)', alignItems: 'center' }}> <div key={item.num} style={{ display: 'grid', gridTemplateColumns: '32px 1fr', gap: '0 12px', padding: '10px 0', borderBottom: '1px solid rgba(255,255,255,0.06)', alignItems: 'center' }}>
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: 'rgba(239,191,4,0.5)' }}>{item.num}</span> <span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: 'rgba(239,191,4,0.5)' }}>{item.num}</span>
<div> <div>
@@ -790,26 +737,26 @@ const Netzwerk: React.FC = () => {
{!isMobile && ( {!isMobile && (
<div style={{ height: 220, position: 'relative', overflow: 'hidden', flexShrink: 0, zIndex: 1 }}> <div style={{ height: 220, position: 'relative', overflow: 'hidden', flexShrink: 0, zIndex: 1 }}>
<Image unoptimized src="/images/networking-innenhof.jpg" alt="BMP Partnernetzwerk" style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 40%', filter: 'sepia(0.18) brightness(0.92)' }} /> <Image unoptimized src={mediaUrl(sponsoring.image, `/images/${netzwerkContent.sponsoring.imageFilename}`)} alt={mediaAlt(sponsoring.image, fallbackText(sponsoring.imageAlt, netzwerkContent.sponsoring.imageAlt))} style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 40%', 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', 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: 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={{ 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)' }} /> <div style={{ width: 5, height: 5, borderRadius: '50%', background: 'rgba(17,29,85,0.7)' }} />
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: 'rgba(17,29,85,0.75)', textTransform: 'uppercase', letterSpacing: '0.2em' }}>BMP Partnernetzwerk</span> <span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: 'rgba(17,29,85,0.75)', textTransform: 'uppercase', letterSpacing: '0.2em' }}>{fallbackText(sponsoring.imageLabel, netzwerkContent.sponsoring.imageLabel)}</span>
</div> </div>
</div> </div>
)} )}
<div style={{ padding: isMobile ? '28px 20px' : '24px 48px 32px 40px', flex: 1, display: 'flex', flexDirection: 'column', minHeight: isMobile ? 560 : 0, position: 'relative', zIndex: 1 }}> <div style={{ padding: isMobile ? '28px 20px' : '24px 48px 32px 40px', flex: 1, display: 'flex', flexDirection: 'column', minHeight: isMobile ? 560 : 0, position: 'relative', zIndex: 1 }}>
<span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: 'rgba(17,29,85,0.5)', display: 'block', marginBottom: 8 }}>Sponsoring 2026</span> <span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: 'rgba(17,29,85,0.5)', display: 'block', marginBottom: 8 }}>{fallbackText(sponsoring.formEyebrow, netzwerkContent.sponsoring.formEyebrow)}</span>
<SponsoringForm theme="gold" /> <SponsoringForm theme="gold" content={cms.sponsoringForm} />
</div> </div>
</div> </div>
</div> </div>
{/* Footnote */} {/* Footnote */}
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '16px 20px 20px' : '14px 56px 18px', display: 'flex', flexWrap: 'wrap', justifyContent: 'center', alignItems: 'center', borderTop: '1px solid rgba(255,255,255,0.06)', flexShrink: 0 }}> <div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '16px 20px 20px' : '14px 56px 18px', display: 'flex', flexWrap: 'wrap', justifyContent: 'center', alignItems: 'center', borderTop: '1px solid rgba(255,255,255,0.06)', flexShrink: 0 }}>
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 15, color: 'rgba(255,255,255,0.45)', marginRight: 10 }}>Oder:</span> <span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 15, color: 'rgba(255,255,255,0.45)', marginRight: 10 }}>{fallbackText(sponsoring.footnotePrefix, netzwerkContent.sponsoring.footnotePrefix)}</span>
<Link <Link
to="/mitglied-werden" to={fallbackText(sponsoring.footnoteUrl, netzwerkContent.sponsoring.footnoteUrl)}
style={{ style={{
fontFamily: '"IBM Plex Sans", sans-serif', fontFamily: '"IBM Plex Sans", sans-serif',
fontSize: 15, fontSize: 15,
@@ -834,7 +781,7 @@ const Netzwerk: React.FC = () => {
(e.currentTarget as HTMLElement).style.borderBottomColor = 'rgba(239,191,4,0.3)'; (e.currentTarget as HTMLElement).style.borderBottomColor = 'rgba(239,191,4,0.3)';
}} }}
> >
Vereinsmitglied werden <ChevronRight size={11} /> {fallbackText(sponsoring.footnoteLabel, netzwerkContent.sponsoring.footnoteLabel)} <ChevronRight size={11} />
</Link> </Link>
</div> </div>
<div style={{ position: 'relative', zIndex: 1, height: 2, background: `linear-gradient(to right, ${GOLD}, rgba(239,191,4,0.3), transparent)`, flexShrink: 0 }} /> <div style={{ position: 'relative', zIndex: 1, height: 2, background: `linear-gradient(to right, ${GOLD}, rgba(239,191,4,0.3), transparent)`, flexShrink: 0 }} />
@@ -843,7 +790,7 @@ const Netzwerk: React.FC = () => {
{/* ── 8. PARTNER MODAL ─────────────────────────────────────────────────── */} {/* ── 8. PARTNER MODAL ─────────────────────────────────────────────────── */}
{selectedPartner && ( {selectedPartner && (
<PartnerModal partner={selectedPartner} onClose={() => setSelectedPartner(null)} /> <PartnerModal partner={selectedPartner} copy={partnerModal} onClose={() => setSelectedPartner(null)} />
)} )}
</div> </div>