From c35e45499e319c37df6bbc474f6e3c93a7be158f Mon Sep 17 00:00:00 2001 From: syntaxbullet Date: Mon, 22 Jun 2026 11:30:21 +0200 Subject: [PATCH] feat: manage netzwerk page via payload --- package.json | 1 + src/collections/Pages/index.ts | 19 +- src/collections/Pages/netzwerkFields.ts | 334 +++++++++++++++ src/payload-types.ts | 427 ++++++++++++++++++++ src/scripts/preload-netzwerk-page-cms.ts | 66 +++ src/spa/components/forms/SponsoringForm.tsx | 87 ++-- src/spa/netzwerkContent.ts | 386 ++++++++++++++++++ src/spa/pages/Netzwerk.tsx | 411 ++++++++----------- 8 files changed, 1455 insertions(+), 276 deletions(-) create mode 100644 src/collections/Pages/netzwerkFields.ts create mode 100644 src/scripts/preload-netzwerk-page-cms.ts create mode 100644 src/spa/netzwerkContent.ts diff --git a/package.json b/package.json index 2e4476e..a6dad41 100644 --- a/package.json +++ b/package.json @@ -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: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: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: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", diff --git a/src/collections/Pages/index.ts b/src/collections/Pages/index.ts index 5b94e43..ba5e7e9 100644 --- a/src/collections/Pages/index.ts +++ b/src/collections/Pages/index.ts @@ -14,6 +14,7 @@ import { datenschutzFields } from './datenschutzFields' import { formularUploadFields } from './formularUploadFields' import { homeFields } from './homeFields' import { impressumFields } from './impressumFields' +import { netzwerkFields } from './netzwerkFields' import { participationFields } from './participationFields' import { preistraegerIndexFields } from './preistraegerIndexFields' 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' } +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 }) => isHomePage(undefined, siblingData) || isContactPage(undefined, siblingData) || @@ -111,7 +120,8 @@ const isManagedSpaPage = (_: unknown, siblingData?: { slug?: string; spaPath?: s isDatenschutzPage(undefined, siblingData) || isPreistraegerIndexPage(undefined, siblingData) || isPressIndexPage(undefined, siblingData) || - isFormularUploadPage(undefined, siblingData) + isFormularUploadPage(undefined, siblingData) || + isNetzwerkPage(undefined, siblingData) export const Pages: CollectionConfig<'pages'> = { slug: 'pages', @@ -242,6 +252,13 @@ export const Pages: CollectionConfig<'pages'> = { fields: formularUploadFields, label: 'Formular Upload Page', }, + { + admin: { + condition: (data) => isNetzwerkPage(undefined, data), + }, + fields: netzwerkFields, + label: 'Network Page', + }, { name: 'meta', label: 'SEO', diff --git a/src/collections/Pages/netzwerkFields.ts b/src/collections/Pages/netzwerkFields.ts new file mode 100644 index 0000000..3186458 --- /dev/null +++ b/src/collections/Pages/netzwerkFields.ts @@ -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), + ], + }, + ], + }, +] diff --git a/src/payload-types.ts b/src/payload-types.ts index e62eb43..64d6825 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -1409,6 +1409,226 @@ export interface Page { 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?: { title?: string | null; /** @@ -3440,6 +3660,213 @@ export interface PagesSelect { 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?: | T | { diff --git a/src/scripts/preload-netzwerk-page-cms.ts b/src/scripts/preload-netzwerk-page-cms.ts new file mode 100644 index 0000000..25d1e0d --- /dev/null +++ b/src/scripts/preload-netzwerk-page-cms.ts @@ -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) +}) diff --git a/src/spa/components/forms/SponsoringForm.tsx b/src/spa/components/forms/SponsoringForm.tsx index f99274f..f417cc1 100644 --- a/src/spa/components/forms/SponsoringForm.tsx +++ b/src/spa/components/forms/SponsoringForm.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { AnimatePresence, motion } from 'framer-motion'; import { ArrowRight, ArrowLeft, Check, Layers, Building2, Mail } from 'lucide-react'; import { useIsMobile } from '@/spa/hooks/useIsMobile'; +import { netzwerkContent } from '@/spa/netzwerkContent'; const FF = '"IBM Plex Sans", sans-serif'; @@ -93,17 +94,14 @@ type FormData = { email: string; }; -const STEPS = [ - { label: 'Paket', icon: Layers }, - { label: 'Unternehmen', icon: Building2 }, - { label: 'Kontakt', icon: Mail }, -]; +const STEP_ICONS = [Layers, Building2, Mail]; -const PAKETE = [ - { 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.' }, -]; +type SponsoringFormContent = Partial; +type PackageOption = (typeof netzwerkContent.sponsoringForm.packages)[number]; +type StepOption = (typeof netzwerkContent.sponsoringForm.steps)[number]; + +const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback; +const fallbackArray = (value: unknown, fallback: T[]) => Array.isArray(value) && value.length ? value as T[] : fallback; const variants = { enter: (dir: number) => ({ x: dir > 0 ? 40 : -40, opacity: 0 }), @@ -142,7 +140,7 @@ function StyledInput({ c, ...props }: React.InputHTMLAttributes void }) { +function PaketCard({ c, pkg, selected, onClick }: { c: C; pkg: PackageOption; selected: boolean; onClick: () => void }) { return ( ) :
} - {step < STEPS.length - 1 ? ( + {step < steps.length - 1 ? ( ) : ( )}

- Wir melden uns innerhalb von 48 Stunden. + {fallbackText(copy.footerText, netzwerkContent.sponsoringForm.footerText)}

); diff --git a/src/spa/netzwerkContent.ts b/src/spa/netzwerkContent.ts new file mode 100644 index 0000000..c7aca37 --- /dev/null +++ b/src/spa/netzwerkContent.ts @@ -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.', + }, +} diff --git a/src/spa/pages/Netzwerk.tsx b/src/spa/pages/Netzwerk.tsx index b5e99a7..1fad51f 100644 --- a/src/spa/pages/Netzwerk.tsx +++ b/src/spa/pages/Netzwerk.tsx @@ -4,6 +4,9 @@ import { ChevronRight, X, ArrowRight, Globe, Linkedin, Building2, MapPin, Extern import SponsoringForm from '@/spa/components/forms/SponsoringForm'; import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg'; 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' const FF = '"IBM Plex Sans", sans-serif'; @@ -20,6 +23,43 @@ const ROLE_COLOR: Record = { 'Medienpartner': '#DC2626', }; +type NetzwerkCms = Partial & { + hero?: Partial & { image?: unknown } + sponsoring?: Partial & { 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 = (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 ( + + {i > 0 &&
} + {highlighted ? ( + <> + {line.replace(highlight, '')}{highlight} + + ) : line} +
+ ) + })} + + ) +} + // ── PersonCard (Schirmherrschaft) ───────────────────────────────────────────── interface PersonCardProps { @@ -62,13 +102,6 @@ function PersonCard({ imgSrc, imgAlt, name, titleLine1, titleLine2, institution, // ── JuryCard ────────────────────────────────────────────────────────────────── -interface JuryMember { - name: string; - role: string; - bio: string; - img: string; -} - const JuryCard: React.FC<{ member: JuryMember; idx: number }> = ({ member, idx }) => { const [hovered, setHovered] = useState(false); 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' } : {}) }} >
- {member.name} + {member.name}
@@ -94,7 +127,7 @@ const JuryCard: React.FC<{ member: JuryMember; idx: number }> = ({ member, idx } // ── 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 isMobileCard = useIsMobile(); return ( @@ -112,12 +145,12 @@ const JuryChairCard: React.FC<{ member: JuryMember }> = ({ member }) => { }} >
- {member.name} + {member.name}
- Jury-Vorsitz + {badge}
{member.name}
{member.role}
@@ -130,24 +163,41 @@ const JuryChairCard: React.FC<{ member: JuryMember }> = ({ member }) => { // ── 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) ────────────────────────────────────────────────────── -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 {text.split('\n').map((line, index) => {index > 0 &&
}{line}
)}
+ } + + if (partner.logoKind === 'monogram') { + return {subtext || text.slice(0, 2)}{text} + } + + if (partner.logoKind === 'dot') { + return {text} + } + + if (partner.logoKind === 'leaf') { + return {text} + } + + if (partner.logoKind === 'rsm') { + return {text} {subtext && {subtext}} + } + + if (partner.logoKind === 'deutschebank') { + return {text} + } + + return {text} +} + +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 isMobileCell = useIsMobile(); const lg = variant === 'lg'; @@ -170,15 +220,15 @@ const SponsorCell: React.FC<{ partner: Partner; variant: 'lg' | 'md' | 'sm'; idx }} > {lg && ( - Premium + {premiumLabel} )} -
{partner.logo}
+
{partner.name}
{partner.role}
{!sm &&
{partner.desc}
}
- Details + {detailLabel}
); @@ -186,7 +236,7 @@ const SponsorCell: React.FC<{ partner: Partner; variant: 'lg' | 'md' | 'sm'; idx // ── 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; useEffect(() => { @@ -283,7 +333,7 @@ function PartnerModal({ partner, onClose }: { partner: Partner; onClose: () => v display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 10, zIndex: 2, }}> - {partner.logo} +
@@ -304,9 +354,9 @@ function PartnerModal({ partner, onClose }: { partner: Partner; onClose: () => v {/* 3 info cards */}
{[ - { Icon: Building2, label: 'Branche', value: partner.industry }, - { Icon: MapPin, label: 'Standort', value: partner.location }, - { Icon: Globe, label: 'Web', value: partner.website }, + { Icon: Building2, label: fallbackText(copy.industryLabel, netzwerkContent.partners.modal.industryLabel), value: partner.industry }, + { Icon: MapPin, label: fallbackText(copy.locationLabel, netzwerkContent.partners.modal.locationLabel), value: partner.location }, + { Icon: Globe, label: fallbackText(copy.webLabel, netzwerkContent.partners.modal.webLabel), value: partner.website }, ].map(({ Icon, label, value }) => (
@@ -325,7 +375,7 @@ function PartnerModal({ partner, onClose }: { partner: Partner; onClose: () => v
- Über das Unternehmen + {fallbackText(copy.aboutLabel, netzwerkContent.partners.modal.aboutLabel)}

@@ -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)'; }} onMouseLeave={e => { const el = e.currentTarget as HTMLElement; el.style.background = GOLD; el.style.boxShadow = 'none'; }} > - Website besuchen + {fallbackText(copy.websiteLabel, netzwerkContent.partners.modal.websiteLabel)} )}

@@ -376,10 +426,10 @@ function PartnerModal({ partner, onClose }: { partner: Partner; onClose: () => v borderTop: '1px solid rgba(16,24,40,0.06)', }}> - Bayerischer Mittelstandspreis 2026 + {fallbackText(copy.footerTitle, netzwerkContent.partners.modal.footerTitle)} - Offizieller {partner.role} + {fallbackText(copy.footerRolePrefix, netzwerkContent.partners.modal.footerRolePrefix)} {partner.role}
@@ -392,119 +442,30 @@ function PartnerModal({ partner, onClose }: { partner: Partner; onClose: () => v const Netzwerk: React.FC = () => { const isMobile = useIsMobile(); const [selectedPartner, setSelectedPartner] = useState(null); - - const juryMembers: JuryMember[] = [ - { 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' }, - { 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' }, - { 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' }, - { 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 partners: Partner[] = [ - /* ── TIER 1 · Hauptsponsoren ─────────────────────────────── */ - { - 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.', - logo: (RSM EBNER STOLZ), - 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.', - logo: (WWK), - 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: (Radio
Gong 96.3
), - 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: (münchen.tv), - 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: (METZLER), - 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: (W&P WIESELHUBER), - 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: (FRISTADS), - 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: (NENew Edge), - 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: (moodtalk), - 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: (Deutsche Bank), - 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: (Bionorica®), - 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: (PRIMUS), - 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.' }, - ]; + const cms = (useCmsRoute()?.doc?.netzwerk || {}) as NetzwerkCms; + const hero = { ...netzwerkContent.hero, ...(cms.hero || {}) }; + const patronage = { ...netzwerkContent.patronage, ...(cms.patronage || {}) }; + const juryIntro = { ...netzwerkContent.juryIntro, ...(cms.juryIntro || {}) }; + const jury = { ...netzwerkContent.jury, ...(cms.jury || {}) }; + 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 sponsoring = { ...netzwerkContent.sponsoring, ...(cms.sponsoring || {}) }; + const patronPeople = fallbackArray(patronage.people, netzwerkContent.patronage.people); + const juryMembers = fallbackArray(jury.members, netzwerkContent.jury.members); + const expertiseRows = fallbackArray(expertise.rows, netzwerkContent.expertise.rows); + const partners = fallbackArray(partnerSection.items, netzwerkContent.partners.items as Partner[]); + const partnerTiers = fallbackArray(partnerSection.tiers, netzwerkContent.partners.tiers); + const mobileStats = fallbackArray(juryIntro.stats, netzwerkContent.juryIntro.stats); + const desktopStats = fallbackArray(juryIntro.desktopStats, netzwerkContent.juryIntro.desktopStats); + const sponsorBenefits = fallbackArray(sponsoring.benefits, netzwerkContent.sponsoring.benefits); return (
{/* ── 1. HERO ──────────────────────────────────────────────────────────── */}
- BMP Netzwerk Preisverleihung + {mediaAlt(hero.image,
@@ -512,13 +473,13 @@ const Netzwerk: React.FC = () => {
- Jury & Partner + {fallbackText(hero.eyebrow, netzwerkContent.hero.eyebrow)}

- DAS NETZWERK
HINTER DEM AWARD. +

- 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)}

@@ -527,48 +488,43 @@ const Netzwerk: React.FC = () => { {/* ── 2. SCHIRMHERRSCHAFT ──────────────────────────────────────────────── */}
- Die Schirmherrschaft -

Schirmherrin und Schirmherr

+ {fallbackText(patronage.eyebrow, netzwerkContent.patronage.eyebrow)} +

{fallbackText(patronage.heading, netzwerkContent.patronage.heading)}

-

des Bayerischen Mittelstandspreises

+

{fallbackText(patronage.description, netzwerkContent.patronage.description)}

- - + {patronPeople.map((person, index) => ( + + ))}
{/* Grußwort – Ilse Aigner */}
- Grußwort -
Schirmherrin
-
Ilse Aigner MdL
-
Präsidentin des Bayerischen Landtags
-
Bayerische Staatsministerin für Wirtschaft a.D.
+ {fallbackText(patronage.greetingEyebrow, netzwerkContent.patronage.greetingEyebrow)} +
{fallbackText(patronage.greetingRole, netzwerkContent.patronage.greetingRole)}
+
{fallbackText(patronage.greetingName, netzwerkContent.patronage.greetingName)}
+
{fallbackText(patronage.greetingTitleLine1, netzwerkContent.patronage.greetingTitleLine1)}
+
{fallbackText(patronage.greetingTitleLine2, netzwerkContent.patronage.greetingTitleLine2)}
- „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)}
- – Ilse Aigner MdL, Präsidentin des Bayerischen Landtags + {fallbackText(patronage.greetingAttribution, netzwerkContent.patronage.greetingAttribution)}
@@ -587,24 +543,20 @@ const Netzwerk: React.FC = () => { {isMobile ? ( /* Mobile: one cohesive navy module – copy + compact stat strip */
- Wer entscheidet? + {fallbackText(juryIntro.eyebrow, netzwerkContent.juryIntro.eyebrow)}

- UNABHÄNGIGE EXPERTISE FÜR DEN MITTELSTAND. +

- 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)}

{/* Stat strip */}
- {[ - { v: '11', l: 'Jurymitglieder' }, - { v: 'Mehrstufig', l: 'Bewertungsverfahren' }, - { v: '100%', l: 'Unabhängig' }, - ].map((s, i) => ( + {mobileStats.map((s, i) => (
0 ? 14 : 0, borderLeft: i > 0 ? '1px solid rgba(255,255,255,0.14)' : 'none' }}> -
{s.v}
-
{s.l}
+
{fallbackText(s.value, netzwerkContent.juryIntro.stats[i]?.value || '')}
+
{fallbackText(s.label, netzwerkContent.juryIntro.stats[i]?.label || '')}
))}
@@ -614,29 +566,29 @@ const Netzwerk: React.FC = () => { {/* Left – Cream, stats */}
-
11
-
Unabhängige Jurymitglieder
+
{fallbackText(desktopStats[0]?.value, netzwerkContent.juryIntro.desktopStats[0].value)}
+
{fallbackText(desktopStats[0]?.label, netzwerkContent.juryIntro.desktopStats[0].label)}
-
Mehrstufig
-
Bewertungsverfahren
+
{fallbackText(desktopStats[1]?.value, netzwerkContent.juryIntro.desktopStats[1].value)}
+
{fallbackText(desktopStats[1]?.label, netzwerkContent.juryIntro.desktopStats[1].label)}
-
100%
-
Unabhängig & ehrenamtlich
+
{fallbackText(desktopStats[2]?.value, netzwerkContent.juryIntro.desktopStats[2].value)}
+
{fallbackText(desktopStats[2]?.label, netzwerkContent.juryIntro.desktopStats[2].label)}
{/* Right – Navy, editorial copy */}
- Wer entscheidet? + {fallbackText(juryIntro.eyebrow, netzwerkContent.juryIntro.eyebrow)}

- UNABHÄNGIGE EXPERTISE FÜR DEN MITTELSTAND. +

- 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)}

@@ -648,17 +600,17 @@ const Netzwerk: React.FC = () => {
- Das Gremium -

UNSERE JURY 2026

+ {fallbackText(jury.eyebrow, netzwerkContent.jury.eyebrow)} +

- 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)}

{/* ── Prominenter Vorsitz-Bereich ─────────────────────────────────── */} {/* TODO: echten Jury-Vorsitzenden bestätigen (Name & Foto sind Platzhalter) */}
- +
{/* ── Übrige Jury-Mitglieder ──────────────────────────────────────── */} @@ -671,7 +623,7 @@ const Netzwerk: React.FC = () => { {/* ── Hinweis: Doppelrolle Jury / Partner (#179) ──────────────────── */}

- 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)}

@@ -680,8 +632,8 @@ const Netzwerk: React.FC = () => { {/* ── 5. HINTERGRUND & EXPERTISE ───────────────────────────────────────── */}
- Hintergrund & Expertise -

WIE BEWERTET DIE JURY?

+ {fallbackText(expertise.eyebrow, netzwerkContent.expertise.eyebrow)} +

{expertiseRows.map((row) => ( @@ -698,11 +650,11 @@ const Netzwerk: React.FC = () => {

- 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)}

- Prof. Dr. Klaus Bergmann, Juryvorsitzender + {fallbackText(expertise.quoteAttribution, netzwerkContent.expertise.quoteAttribution)}
@@ -713,26 +665,25 @@ const Netzwerk: React.FC = () => {
- Netzwerkqualität -

PARTNER & SPONSOREN

+ {fallbackText(partnerSection.eyebrow, netzwerkContent.partners.eyebrow)} +

- Partner in drei Kategorien: Hauptsponsoren, Medienpartner und weitere Sponsoren. Klicken für Details. + {fallbackText(partnerSection.description, netzwerkContent.partners.description)}

{/* Tiered grid – continuous, ranks marked by label bars */}
- {([ - { tier: 1 as const, label: 'Hauptsponsoren', note: 'Premium', variant: 'lg' as const, cols: isMobile ? 1 : 2 }, - { tier: 2 as const, label: 'Medienpartner', note: '', variant: 'md' as const, cols: isMobile ? 2 : 2 }, - { tier: 3 as const, label: 'Weitere Sponsoren', note: '', variant: 'sm' as const, cols: isMobile ? 2 : 4 }, - ]).map(t => { - const items = partners.filter(p => p.tier === t.tier); + {partnerTiers.map(t => { + const tier = Number(t.tier) as 1 | 2 | 3; + const variant = tier === 1 ? 'lg' as const : tier === 2 ? 'md' as const : 'sm' as const; + const cols = isMobile ? (tier === 1 ? 1 : 2) : (tier === 3 ? 4 : 2); + const items = partners.filter(p => p.tier === tier); return ( -
+
{items.map((p, i) => ( - setSelectedPartner(p)} /> + setSelectedPartner(p)} /> ))}
); @@ -750,17 +701,13 @@ const Netzwerk: React.FC = () => {
{/* Left – pitch */}
- Wachstum durch Partnerschaft -

WIR FREUEN UNS ÜBER NEUE SPONSOREN.

+ {fallbackText(sponsoring.eyebrow, netzwerkContent.sponsoring.eyebrow)} +

- Positionieren Sie Ihre Marke im exklusivsten Netzwerk des bayerischen Mittelstands – mit maßgeschneiderten Sponsoring-Paketen. + {fallbackText(sponsoring.description, netzwerkContent.sponsoring.description)}

- {[ - { 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) => ( + {sponsorBenefits.map((item) => (
{item.num}
@@ -790,26 +737,26 @@ const Netzwerk: React.FC = () => { {!isMobile && (
- BMP Partnernetzwerk + {mediaAlt(sponsoring.image,
- BMP Partnernetzwerk + {fallbackText(sponsoring.imageLabel, netzwerkContent.sponsoring.imageLabel)}
)}
- Sponsoring 2026 - + {fallbackText(sponsoring.formEyebrow, netzwerkContent.sponsoring.formEyebrow)} +
{/* Footnote */}
- Oder: + {fallbackText(sponsoring.footnotePrefix, netzwerkContent.sponsoring.footnotePrefix)} { (e.currentTarget as HTMLElement).style.borderBottomColor = 'rgba(239,191,4,0.3)'; }} > - Vereinsmitglied werden + {fallbackText(sponsoring.footnoteLabel, netzwerkContent.sponsoring.footnoteLabel)}
@@ -843,7 +790,7 @@ const Netzwerk: React.FC = () => { {/* ── 8. PARTNER MODAL ─────────────────────────────────────────────────── */} {selectedPartner && ( - setSelectedPartner(null)} /> + setSelectedPartner(null)} /> )}