feat: convert home page to be cms supported.
This commit is contained in:
292
src/collections/Pages/homeFields.ts
Normal file
292
src/collections/Pages/homeFields.ts
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
import type { Field } from 'payload'
|
||||||
|
|
||||||
|
const uploadField = (name: string, label: string, description?: string): Field => ({
|
||||||
|
name,
|
||||||
|
type: 'upload',
|
||||||
|
relationTo: 'media',
|
||||||
|
label,
|
||||||
|
admin: description ? { description } : undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
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 linkGroup = (labelDefault: string, urlDefault: string): Field[] => [
|
||||||
|
text('label', 'Button text', labelDefault),
|
||||||
|
text('url', 'Link URL', urlDefault),
|
||||||
|
]
|
||||||
|
|
||||||
|
const sectionAdmin = (description: string) => ({
|
||||||
|
description,
|
||||||
|
initCollapsed: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const homeFields: Field[] = [
|
||||||
|
{
|
||||||
|
name: 'home',
|
||||||
|
label: 'Home page layout sections',
|
||||||
|
type: 'group',
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
'Edit the homepage in the same order it appears on the frontend. The generic Hero and Content tabs are hidden for the home page to avoid duplicate controls.',
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'hero',
|
||||||
|
label: '01 · Hero / opening screen',
|
||||||
|
type: 'group',
|
||||||
|
admin: sectionAdmin('Top full-screen gala image, badge, headline, intro text, primary buttons, video trigger, and partner ticker.'),
|
||||||
|
fields: [
|
||||||
|
uploadField('backgroundImage', 'Background image', 'Current frontend image: /images/gala-saal-overview.jpg'),
|
||||||
|
text('imageAlt', 'Image alt text', 'Awards Gala'),
|
||||||
|
text('badge', 'Badge text', 'Bewerbungsphase 2026 geschlossen'),
|
||||||
|
text('headingLine1', 'Headline line 1', 'BAYERNS BESTE'),
|
||||||
|
text('headingAccent', 'Headline accent line', 'MITTELSTÄNDLER.'),
|
||||||
|
textarea(
|
||||||
|
'description',
|
||||||
|
'Intro paragraph',
|
||||||
|
'Der Bayerische Mittelstandspreis würdigt herausragende Leistungen – von Innovation über Nachhaltigkeit bis hin zur Exzellenz.',
|
||||||
|
),
|
||||||
|
{ name: 'primaryCta', label: 'Primary CTA', type: 'group', fields: linkGroup('Jetzt kostenlos bewerben', '/teilnahme') },
|
||||||
|
{ name: 'secondaryCta', label: 'Secondary CTA', type: 'group', fields: linkGroup('Mitglied werden', '/mitglied-werden') },
|
||||||
|
text('videoLabel', 'Video button label', 'Film abspielen'),
|
||||||
|
text('partnersLabel', 'Partner ticker label', 'Unsere Partner'),
|
||||||
|
{
|
||||||
|
name: 'partners',
|
||||||
|
label: 'Partner ticker names',
|
||||||
|
type: 'array',
|
||||||
|
defaultValue: [
|
||||||
|
{ name: 'RSM Ebner Stolz' },
|
||||||
|
{ name: 'WWK' },
|
||||||
|
{ name: 'Deutsche Bank' },
|
||||||
|
{ name: 'Bionorica' },
|
||||||
|
{ name: 'Berchtesgadener Land' },
|
||||||
|
{ name: 'Metzler' },
|
||||||
|
{ name: 'vbw' },
|
||||||
|
{ name: 'Wirtschaftsbeirat Bayern' },
|
||||||
|
{ name: 'Radio Gong 96.3' },
|
||||||
|
{ name: 'münchen.tv' },
|
||||||
|
{ name: 'EWS' },
|
||||||
|
],
|
||||||
|
fields: [text('name', 'Name')],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'quickCheck',
|
||||||
|
label: '02 · Schnell-Check eligibility section',
|
||||||
|
type: 'group',
|
||||||
|
admin: sectionAdmin('Cream text panel plus right-side stage image and five eligibility rows.'),
|
||||||
|
fields: [
|
||||||
|
text('eyebrow', 'Eyebrow', 'Schnell-Check'),
|
||||||
|
textarea('heading', 'Heading', 'SIND SIE\nBERECHTIGT?'),
|
||||||
|
uploadField('image', 'Image', 'Current frontend image: /images/buehne-gewinner.jpg'),
|
||||||
|
text('imageAlt', 'Image alt text', 'Preisverleihung Bühne'),
|
||||||
|
textarea(
|
||||||
|
'body',
|
||||||
|
'Body copy',
|
||||||
|
'Der Bayerische Mittelstandspreis richtet sich an inhabergeführte KMU im Freistaat. Fünf Kriterien entscheiden, erfüllen Sie alle fünf, steht Ihrer Bewerbung nichts im Weg.',
|
||||||
|
),
|
||||||
|
{
|
||||||
|
name: 'criteria',
|
||||||
|
label: 'Criteria rows',
|
||||||
|
type: 'array',
|
||||||
|
defaultValue: [
|
||||||
|
{ label: 'Standort Bayern', desc: 'Sitz oder Betriebsstätte im Freistaat' },
|
||||||
|
{ label: '50–1.500 Mitarbeiter', desc: 'Klassischer Mittelstand' },
|
||||||
|
{ label: 'Min. 3 Geschäftsjahre', desc: 'Markterfahrung und Stabilität' },
|
||||||
|
{ label: 'Privatwirtschaft', desc: 'Kein staatlicher Träger' },
|
||||||
|
{ label: 'Familien- bzw. eigentümergeführt', desc: 'Ihr Unternehmen ist inhabergeführt' },
|
||||||
|
],
|
||||||
|
fields: [text('label', 'Criterion'), text('desc', 'Description')],
|
||||||
|
},
|
||||||
|
textarea(
|
||||||
|
'note',
|
||||||
|
'Small note below criteria',
|
||||||
|
'Auch ein Verbund bzw. eine Gemeinschaft von mittelständischen Unternehmen oder von mittelständischen Unternehmen und Organisationen/Institutionen mit Schwerpunkt in Bayern kann teilnehmen.',
|
||||||
|
),
|
||||||
|
{ name: 'cta', label: 'CTA', type: 'group', fields: linkGroup('Ihr Weg zum Preis', '/teilnahme') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'intro',
|
||||||
|
label: '03 · Award intro / value proposition',
|
||||||
|
type: 'group',
|
||||||
|
admin: sectionAdmin('Jubilation image, stats, editorial quote, body copy, and CTA.'),
|
||||||
|
fields: [
|
||||||
|
text('eyebrow', 'Eyebrow', 'Ein Gewinn für alle'),
|
||||||
|
textarea('heading', 'Heading', 'AUSZEICHNUNG\nFÜR DIE BESTEN.'),
|
||||||
|
uploadField('image', 'Image', 'Current frontend image: /images/preistraeger-jubel.jpg'),
|
||||||
|
text('imageAlt', 'Image alt text', 'BMP Auszeichnung'),
|
||||||
|
{
|
||||||
|
name: 'stats',
|
||||||
|
label: 'Stats',
|
||||||
|
type: 'array',
|
||||||
|
defaultValue: [
|
||||||
|
{ value: '20+', label: 'Jahre Exzellenz' },
|
||||||
|
{ value: '0 €', label: 'Teilnahmegebühr' },
|
||||||
|
{ value: '100%', label: 'Unabhängig' },
|
||||||
|
],
|
||||||
|
fields: [text('value', 'Value'), text('label', 'Label')],
|
||||||
|
},
|
||||||
|
textarea('quote', 'Quote', '„Ein Unternehmen zu gründen erfordert nicht allein spezielle Fähigkeiten. Es erfordert Persönlichkeit!“'),
|
||||||
|
textarea(
|
||||||
|
'body',
|
||||||
|
'Body copy',
|
||||||
|
'Wir suchen Vorbilder, die sich trotz aller Risiken nicht scheuen, Visionen in Businesspläne und Ideen in Unternehmungen zu verwandeln, und das mit großem persönlichen Einsatz und Verantwortung für Ihre Mitarbeiter. Seit vielen Jahren würdigen wir herausragende unternehmerische Leistungen im Freistaat.',
|
||||||
|
),
|
||||||
|
{ name: 'cta', label: 'CTA', type: 'group', fields: linkGroup('Mehr über den Preis', '/der-bmp') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'status',
|
||||||
|
label: '04 · Application phase slider',
|
||||||
|
type: 'group',
|
||||||
|
admin: sectionAdmin('Three-slide status module shown after the award intro.'),
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'activePhase',
|
||||||
|
label: 'Current phase shown on the homepage',
|
||||||
|
type: 'select',
|
||||||
|
defaultValue: '0',
|
||||||
|
options: [
|
||||||
|
{ label: '01 · Bewerbungsphase offen', value: '0' },
|
||||||
|
{ label: '02 · Auswertung / Jury bewertet', value: '1' },
|
||||||
|
{ label: '03 · Preisverleihung abgeschlossen', value: '2' },
|
||||||
|
],
|
||||||
|
admin: { description: 'Only this selected phase is rendered on the frontend; visitors do not see a slider.' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'phases',
|
||||||
|
label: 'Slides',
|
||||||
|
type: 'array',
|
||||||
|
maxRows: 3,
|
||||||
|
defaultValue: [
|
||||||
|
{
|
||||||
|
num: '01', tag: 'JETZT OFFEN', pulse: true, phase: 'Bewerbungsphase 2026', headline: 'BEWERBEN SIE SICH JETZT.',
|
||||||
|
body: 'Die Bewerbungsphase 2026 ist eröffnet – es ist noch nicht zu spät. Reichen Sie Ihre Unterlagen bis zum 30. Juni kostenlos ein und zeigen Sie, was den bayerischen Mittelstand ausmacht.',
|
||||||
|
cta: { label: 'Jetzt kostenlos bewerben', url: '/teilnahme' }, accent: '#EFBF04', bg: '#020A1E', glow: 'rgba(239,191,4,0.14)', meta: 'Deadline: 30. Juni 2026', membershipCta: { label: 'Mitglied werden', url: '/mitglied-werden' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: '02', tag: 'IN AUSWERTUNG', pulse: true, phase: 'Auswertungsphase 2026', headline: 'DIE JURY BEWERTET GERADE.',
|
||||||
|
body: 'Die Anmeldephase 2026 ist geschlossen. Unsere unabhängige Expertenjury aus Wissenschaft und Praxis sichtet und bewertet derzeit alle Einreichungen.',
|
||||||
|
accent: '#4A8FC9', bg: '#060D1C', glow: 'rgba(74,143,201,0.12)', meta: 'Ergebnis: August 2026', membershipCta: { label: 'Mitglied werden', url: '/mitglied-werden' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: '03', tag: 'ABGESCHLOSSEN', pulse: false, phase: 'Preisverleihung · Oktober 2026', headline: 'DER BMP 2026 WAR EIN ERFOLG.',
|
||||||
|
body: 'Wir danken allen 247 Bewerberinnen und Bewerbern für Mut und Exzellenz. Drei außergewöhnliche Unternehmen wurden in München ausgezeichnet – ein unvergesslicher Abend.',
|
||||||
|
cta: { label: 'Alle Preisträger ansehen', url: '/preistraeger' }, accent: '#EFBF04', bg: '#06090F', glow: 'rgba(239,191,4,0.08)', meta: 'Gala · Oktober 2026 · München', successApplications: '247', successWinners: '3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fields: [
|
||||||
|
text('num', 'Slide number'), text('tag', 'Badge'), { name: 'pulse', label: 'Show pulse dot', type: 'checkbox', defaultValue: true }, text('phase', 'Phase label'), text('headline', 'Headline'), textarea('body', 'Body copy'),
|
||||||
|
{ name: 'cta', label: 'Slide CTA', type: 'group', fields: linkGroup('', '') }, text('accent', 'Accent color'), text('bg', 'Background color'), text('glow', 'Glow color'), text('meta', 'Meta label'), text('successApplications', 'Success applications number'), text('successWinners', 'Success winners number'), { name: 'membershipCta', label: 'Membership CTA', type: 'group', fields: linkGroup('Mitglied werden', '/mitglied-werden') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
text('noActionLabel', 'No CTA fallback label', 'Kein Handlungsbedarf'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'winners',
|
||||||
|
label: '05 · Preisträger card grid',
|
||||||
|
type: 'group',
|
||||||
|
admin: sectionAdmin('Blue card grid with featured winner relationships.'),
|
||||||
|
fields: [
|
||||||
|
text('eyebrow', 'Eyebrow', 'Success Stories'),
|
||||||
|
textarea('heading', 'Heading', 'DIE BESTEN UNTERNEHMEN IN BAYERN'),
|
||||||
|
{ name: 'cta', label: 'CTA', type: 'group', fields: linkGroup('Alle Preisträger', '/preistraeger') },
|
||||||
|
{
|
||||||
|
name: 'featured',
|
||||||
|
label: 'Featured winner cards',
|
||||||
|
type: 'relationship',
|
||||||
|
relationTo: 'preistraeger',
|
||||||
|
hasMany: true,
|
||||||
|
admin: { description: 'Choose the winner cards shown on the home page. If empty, the latest 2025 winners are used.' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'testimonials',
|
||||||
|
label: '06 · Testimonials carousel',
|
||||||
|
type: 'group',
|
||||||
|
admin: sectionAdmin('Portrait carousel and quote copy.'),
|
||||||
|
fields: [
|
||||||
|
text('eyebrow', 'Eyebrow', 'Stimmen der Preisträger'),
|
||||||
|
textarea('heading', 'Heading', 'DAS SAGEN UNSERE\nPREISTRÄGER.'),
|
||||||
|
{
|
||||||
|
name: 'items',
|
||||||
|
label: 'Testimonials',
|
||||||
|
type: 'array',
|
||||||
|
defaultValue: [
|
||||||
|
{ imageUrl: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=600', name: 'Dr. Maria Huber', role: 'Geschäftsführerin', company: 'TechVision GmbH', year: '2025', quote: 'Der BMP hat uns neue Türen geöffnet. Die Auszeichnung ist ein wichtiges Qualitätssiegel für unser gesamtes Team.' },
|
||||||
|
{ imageUrl: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&q=80&w=600', name: 'Hannes Weber', role: 'Inhaber & CEO', company: 'Weber Präzision GmbH', year: '2023', quote: 'Der Gewinn war ein Moment des Stolzes für die gesamte Belegschaft. Die höchste Anerkennung für unsere tägliche Arbeit.' },
|
||||||
|
],
|
||||||
|
fields: [uploadField('image', 'Portrait image'), text('imageUrl', 'External image URL'), text('name', 'Name'), text('role', 'Role'), text('company', 'Company'), text('year', 'Year'), textarea('quote', 'Quote')],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'benefits',
|
||||||
|
label: '07 · Participation benefits',
|
||||||
|
type: 'group',
|
||||||
|
admin: sectionAdmin('Cream left panel and networking image.'),
|
||||||
|
fields: [
|
||||||
|
text('eyebrow', 'Eyebrow', 'Mehr als nur ein Preis'),
|
||||||
|
textarea('heading', 'Heading', 'WARUM SICH DIE\nTEILNAHME LOHNT.'),
|
||||||
|
uploadField('image', 'Image', 'Current frontend image: /images/networking-innenhof.jpg'),
|
||||||
|
text('imageAlt', 'Image alt text', 'BMP Gala Netzwerk'),
|
||||||
|
text('imageLabel', 'Desktop image label', 'BMP Gala-Netzwerk'),
|
||||||
|
{
|
||||||
|
name: 'items', label: 'Benefit rows', type: 'array', defaultValue: [
|
||||||
|
{ num: '01', title: 'Netzwerk', desc: 'Direkter Zugang zu Entscheidern aus Staatsregierung, Wirtschaft und Wissenschaft.' },
|
||||||
|
{ num: '02', title: 'Sichtbarkeit', desc: 'Breite mediale Begleitung durch TV-, Radio- und Print-Partner bayernweit.' },
|
||||||
|
{ num: '03', title: 'Bestätigung', desc: 'Ein unabhängiger Qualitätsnachweis – das stärkste Siegel für Ihren Marktauftritt.' },
|
||||||
|
], fields: [text('num', 'Number'), text('title', 'Title'), textarea('desc', 'Description')],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'application',
|
||||||
|
label: '08 · Application form section',
|
||||||
|
type: 'group',
|
||||||
|
admin: sectionAdmin('Final application pitch, award image, embedded form heading, and footnote.'),
|
||||||
|
fields: [
|
||||||
|
text('eyebrow', 'Eyebrow', 'Chance ergreifen'),
|
||||||
|
textarea('heading', 'Heading', 'SCHREIBEN\nAUCH SIE\nGESCHICHTE.'),
|
||||||
|
textarea('body', 'Body copy', 'Werden Sie Teil der Wall of Fame des bayerischen Mittelstands – vollständig kostenfrei.'),
|
||||||
|
{ name: 'benefits', label: 'Benefit rows', type: 'array', defaultValue: [
|
||||||
|
{ num: '01', label: 'Kostenfrei', desc: 'Ohne Teilnahmegebühr' },
|
||||||
|
{ num: '02', label: 'Jury', desc: 'Aus Wissenschaft und Praxis' },
|
||||||
|
{ num: '03', label: 'Sichtbarkeit', desc: 'Medial bayernweit' },
|
||||||
|
{ num: '04', label: 'Gala', desc: 'Verleihung München' },
|
||||||
|
], fields: [text('num', 'Number'), text('label', 'Label'), text('desc', 'Description')] },
|
||||||
|
uploadField('awardImage', 'Award image', 'Current frontend image: /images/preisuebergabe.jpg'),
|
||||||
|
text('awardImageAlt', 'Award image alt text', 'Preisverleihung Bayerischer Mittelstandspreis'),
|
||||||
|
text('awardCaption', 'Award image caption', 'Preisverleihung 2025'),
|
||||||
|
text('formEyebrow', 'Form eyebrow', 'Direktbewerbung 2026'),
|
||||||
|
text('formTitle', 'Form title', 'Kostenlos bewerben'),
|
||||||
|
text('footnote', 'Footnote normal text', 'Kostenlos und unverbindlich –'),
|
||||||
|
text('footnoteStrong', 'Footnote highlighted text', 'keine Teilnahmegebühr'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'videoModal',
|
||||||
|
label: '09 · Video modal placeholder',
|
||||||
|
type: 'group',
|
||||||
|
admin: sectionAdmin('Copy shown when the hero video button is clicked.'),
|
||||||
|
fields: [text('title', 'Title', 'Video-Player Platzhalter'), text('description', 'Description', 'Hier würde das Image-Video des BMP geladen werden.'), text('closeLabel', 'Close button label', 'Schließen')],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -8,6 +8,7 @@ import { Content } from '../../blocks/Content/config'
|
|||||||
import { FormBlock } from '../../blocks/Form/config'
|
import { FormBlock } from '../../blocks/Form/config'
|
||||||
import { MediaBlock } from '../../blocks/MediaBlock/config'
|
import { MediaBlock } from '../../blocks/MediaBlock/config'
|
||||||
import { hero } from '@/heros/config'
|
import { hero } from '@/heros/config'
|
||||||
|
import { homeFields } from './homeFields'
|
||||||
import { slugField } from 'payload'
|
import { slugField } from 'payload'
|
||||||
import { populatePublishedAt } from '../../hooks/populatePublishedAt'
|
import { populatePublishedAt } from '../../hooks/populatePublishedAt'
|
||||||
import { generatePreviewPath } from '../../utilities/generatePreviewPath'
|
import { generatePreviewPath } from '../../utilities/generatePreviewPath'
|
||||||
@@ -21,6 +22,14 @@ import {
|
|||||||
PreviewField,
|
PreviewField,
|
||||||
} from '@payloadcms/plugin-seo/fields'
|
} from '@payloadcms/plugin-seo/fields'
|
||||||
|
|
||||||
|
const isHomePage = (_: unknown, siblingData?: { slug?: string; spaPath?: string; title?: string }) => {
|
||||||
|
const slug = siblingData?.slug
|
||||||
|
const spaPath = siblingData?.spaPath
|
||||||
|
const title = siblingData?.title?.toLowerCase()
|
||||||
|
|
||||||
|
return spaPath === '/' || slug === 'startseite' || slug === 'home' || title === 'home' || title === 'startseite'
|
||||||
|
}
|
||||||
|
|
||||||
export const Pages: CollectionConfig<'pages'> = {
|
export const Pages: CollectionConfig<'pages'> = {
|
||||||
slug: 'pages',
|
slug: 'pages',
|
||||||
access: {
|
access: {
|
||||||
@@ -64,10 +73,16 @@ export const Pages: CollectionConfig<'pages'> = {
|
|||||||
type: 'tabs',
|
type: 'tabs',
|
||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
|
admin: {
|
||||||
|
condition: (data) => !isHomePage(undefined, data),
|
||||||
|
},
|
||||||
fields: [hero],
|
fields: [hero],
|
||||||
label: 'Hero',
|
label: 'Hero',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
admin: {
|
||||||
|
condition: (data) => !isHomePage(undefined, data),
|
||||||
|
},
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: 'layout',
|
name: 'layout',
|
||||||
@@ -81,6 +96,13 @@ export const Pages: CollectionConfig<'pages'> = {
|
|||||||
],
|
],
|
||||||
label: 'Content',
|
label: 'Content',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
admin: {
|
||||||
|
condition: (data) => isHomePage(undefined, data),
|
||||||
|
},
|
||||||
|
fields: homeFields,
|
||||||
|
label: 'Home Page',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'meta',
|
name: 'meta',
|
||||||
label: 'SEO',
|
label: 'SEO',
|
||||||
|
|||||||
@@ -206,6 +206,215 @@ export interface Page {
|
|||||||
media?: (number | null) | Media;
|
media?: (number | null) | Media;
|
||||||
};
|
};
|
||||||
layout: (CallToActionBlock | ContentBlock | MediaBlock | ArchiveBlock | FormBlock)[];
|
layout: (CallToActionBlock | ContentBlock | MediaBlock | ArchiveBlock | FormBlock)[];
|
||||||
|
/**
|
||||||
|
* Edit the homepage in the same order it appears on the frontend. The generic Hero and Content tabs are hidden for the home page to avoid duplicate controls.
|
||||||
|
*/
|
||||||
|
home?: {
|
||||||
|
/**
|
||||||
|
* Top full-screen gala image, badge, headline, intro text, primary buttons, video trigger, and partner ticker.
|
||||||
|
*/
|
||||||
|
hero?: {
|
||||||
|
/**
|
||||||
|
* Current frontend image: /images/gala-saal-overview.jpg
|
||||||
|
*/
|
||||||
|
backgroundImage?: (number | null) | Media;
|
||||||
|
imageAlt?: string | null;
|
||||||
|
badge?: string | null;
|
||||||
|
headingLine1?: string | null;
|
||||||
|
headingAccent?: string | null;
|
||||||
|
description?: string | null;
|
||||||
|
primaryCta?: {
|
||||||
|
label?: string | null;
|
||||||
|
url?: string | null;
|
||||||
|
};
|
||||||
|
secondaryCta?: {
|
||||||
|
label?: string | null;
|
||||||
|
url?: string | null;
|
||||||
|
};
|
||||||
|
videoLabel?: string | null;
|
||||||
|
partnersLabel?: string | null;
|
||||||
|
partners?:
|
||||||
|
| {
|
||||||
|
name?: string | null;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Cream text panel plus right-side stage image and five eligibility rows.
|
||||||
|
*/
|
||||||
|
quickCheck?: {
|
||||||
|
eyebrow?: string | null;
|
||||||
|
heading?: string | null;
|
||||||
|
/**
|
||||||
|
* Current frontend image: /images/buehne-gewinner.jpg
|
||||||
|
*/
|
||||||
|
image?: (number | null) | Media;
|
||||||
|
imageAlt?: string | null;
|
||||||
|
body?: string | null;
|
||||||
|
criteria?:
|
||||||
|
| {
|
||||||
|
label?: string | null;
|
||||||
|
desc?: string | null;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
note?: string | null;
|
||||||
|
cta?: {
|
||||||
|
label?: string | null;
|
||||||
|
url?: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Jubilation image, stats, editorial quote, body copy, and CTA.
|
||||||
|
*/
|
||||||
|
intro?: {
|
||||||
|
eyebrow?: string | null;
|
||||||
|
heading?: string | null;
|
||||||
|
/**
|
||||||
|
* Current frontend image: /images/preistraeger-jubel.jpg
|
||||||
|
*/
|
||||||
|
image?: (number | null) | Media;
|
||||||
|
imageAlt?: string | null;
|
||||||
|
stats?:
|
||||||
|
| {
|
||||||
|
value?: string | null;
|
||||||
|
label?: string | null;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
quote?: string | null;
|
||||||
|
body?: string | null;
|
||||||
|
cta?: {
|
||||||
|
label?: string | null;
|
||||||
|
url?: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Three-slide status module shown after the award intro.
|
||||||
|
*/
|
||||||
|
status?: {
|
||||||
|
/**
|
||||||
|
* Only this selected phase is rendered on the frontend; visitors do not see a slider.
|
||||||
|
*/
|
||||||
|
activePhase?: ('0' | '1' | '2') | null;
|
||||||
|
phases?:
|
||||||
|
| {
|
||||||
|
num?: string | null;
|
||||||
|
tag?: string | null;
|
||||||
|
pulse?: boolean | null;
|
||||||
|
phase?: string | null;
|
||||||
|
headline?: string | null;
|
||||||
|
body?: string | null;
|
||||||
|
cta?: {
|
||||||
|
label?: string | null;
|
||||||
|
url?: string | null;
|
||||||
|
};
|
||||||
|
accent?: string | null;
|
||||||
|
bg?: string | null;
|
||||||
|
glow?: string | null;
|
||||||
|
meta?: string | null;
|
||||||
|
successApplications?: string | null;
|
||||||
|
successWinners?: string | null;
|
||||||
|
membershipCta?: {
|
||||||
|
label?: string | null;
|
||||||
|
url?: string | null;
|
||||||
|
};
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
noActionLabel?: string | null;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Blue card grid with featured winner relationships.
|
||||||
|
*/
|
||||||
|
winners?: {
|
||||||
|
eyebrow?: string | null;
|
||||||
|
heading?: string | null;
|
||||||
|
cta?: {
|
||||||
|
label?: string | null;
|
||||||
|
url?: string | null;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Choose the winner cards shown on the home page. If empty, the latest 2025 winners are used.
|
||||||
|
*/
|
||||||
|
featured?: (number | Preistraeger)[] | null;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Portrait carousel and quote copy.
|
||||||
|
*/
|
||||||
|
testimonials?: {
|
||||||
|
eyebrow?: string | null;
|
||||||
|
heading?: string | null;
|
||||||
|
items?:
|
||||||
|
| {
|
||||||
|
image?: (number | null) | Media;
|
||||||
|
imageUrl?: string | null;
|
||||||
|
name?: string | null;
|
||||||
|
role?: string | null;
|
||||||
|
company?: string | null;
|
||||||
|
year?: string | null;
|
||||||
|
quote?: string | null;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Cream left panel and networking image.
|
||||||
|
*/
|
||||||
|
benefits?: {
|
||||||
|
eyebrow?: string | null;
|
||||||
|
heading?: string | null;
|
||||||
|
/**
|
||||||
|
* Current frontend image: /images/networking-innenhof.jpg
|
||||||
|
*/
|
||||||
|
image?: (number | null) | Media;
|
||||||
|
imageAlt?: string | null;
|
||||||
|
imageLabel?: string | null;
|
||||||
|
items?:
|
||||||
|
| {
|
||||||
|
num?: string | null;
|
||||||
|
title?: string | null;
|
||||||
|
desc?: string | null;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Final application pitch, award image, embedded form heading, and footnote.
|
||||||
|
*/
|
||||||
|
application?: {
|
||||||
|
eyebrow?: string | null;
|
||||||
|
heading?: string | null;
|
||||||
|
body?: string | null;
|
||||||
|
benefits?:
|
||||||
|
| {
|
||||||
|
num?: string | null;
|
||||||
|
label?: string | null;
|
||||||
|
desc?: string | null;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
/**
|
||||||
|
* Current frontend image: /images/preisuebergabe.jpg
|
||||||
|
*/
|
||||||
|
awardImage?: (number | null) | Media;
|
||||||
|
awardImageAlt?: string | null;
|
||||||
|
awardCaption?: string | null;
|
||||||
|
formEyebrow?: string | null;
|
||||||
|
formTitle?: string | null;
|
||||||
|
footnote?: string | null;
|
||||||
|
footnoteStrong?: string | null;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Copy shown when the hero video button is clicked.
|
||||||
|
*/
|
||||||
|
videoModal?: {
|
||||||
|
title?: string | null;
|
||||||
|
description?: string | null;
|
||||||
|
closeLabel?: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
meta?: {
|
meta?: {
|
||||||
title?: string | null;
|
title?: string | null;
|
||||||
/**
|
/**
|
||||||
@@ -818,6 +1027,48 @@ export interface Form {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "preistraeger".
|
||||||
|
*/
|
||||||
|
export interface Preistraeger {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
image?: (number | null) | Media;
|
||||||
|
category?: string | null;
|
||||||
|
year?: number | null;
|
||||||
|
awardType?: string | null;
|
||||||
|
shortDesc?: string | null;
|
||||||
|
longDesc?: string | null;
|
||||||
|
/**
|
||||||
|
* Fallback description. Prefer Kurzbeschreibung / Langbeschreibung for migrated Preisträger content.
|
||||||
|
*/
|
||||||
|
description?: string | null;
|
||||||
|
quote?: string | null;
|
||||||
|
quotePerson?: string | null;
|
||||||
|
quoteRole?: string | null;
|
||||||
|
location?: string | null;
|
||||||
|
industry?: string | null;
|
||||||
|
website?: string | null;
|
||||||
|
hasMedia?: boolean | null;
|
||||||
|
/**
|
||||||
|
* Route in the legacy SPA used for previewing the current migrated Preisträger state.
|
||||||
|
*/
|
||||||
|
spaPath?: string | null;
|
||||||
|
publishedAt?: string | null;
|
||||||
|
meta?: {
|
||||||
|
title?: string | null;
|
||||||
|
description?: string | null;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* When enabled, the slug will auto-generate from the title field on save and autosave.
|
||||||
|
*/
|
||||||
|
generateSlug?: boolean | null;
|
||||||
|
slug: string;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
_status?: ('draft' | 'published') | null;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "events".
|
* via the `definition` "events".
|
||||||
@@ -869,48 +1120,6 @@ export interface Event {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
_status?: ('draft' | 'published') | null;
|
_status?: ('draft' | 'published') | null;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
|
||||||
* via the `definition` "preistraeger".
|
|
||||||
*/
|
|
||||||
export interface Preistraeger {
|
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
image?: (number | null) | Media;
|
|
||||||
category?: string | null;
|
|
||||||
year?: number | null;
|
|
||||||
awardType?: string | null;
|
|
||||||
shortDesc?: string | null;
|
|
||||||
longDesc?: string | null;
|
|
||||||
/**
|
|
||||||
* Fallback description. Prefer Kurzbeschreibung / Langbeschreibung for migrated Preisträger content.
|
|
||||||
*/
|
|
||||||
description?: string | null;
|
|
||||||
quote?: string | null;
|
|
||||||
quotePerson?: string | null;
|
|
||||||
quoteRole?: string | null;
|
|
||||||
location?: string | null;
|
|
||||||
industry?: string | null;
|
|
||||||
website?: string | null;
|
|
||||||
hasMedia?: boolean | null;
|
|
||||||
/**
|
|
||||||
* Route in the legacy SPA used for previewing the current migrated Preisträger state.
|
|
||||||
*/
|
|
||||||
spaPath?: string | null;
|
|
||||||
publishedAt?: string | null;
|
|
||||||
meta?: {
|
|
||||||
title?: string | null;
|
|
||||||
description?: string | null;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* When enabled, the slug will auto-generate from the title field on save and autosave.
|
|
||||||
*/
|
|
||||||
generateSlug?: boolean | null;
|
|
||||||
slug: string;
|
|
||||||
updatedAt: string;
|
|
||||||
createdAt: string;
|
|
||||||
_status?: ('draft' | 'published') | null;
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "redirects".
|
* via the `definition` "redirects".
|
||||||
@@ -1228,6 +1437,198 @@ export interface PagesSelect<T extends boolean = true> {
|
|||||||
archive?: T | ArchiveBlockSelect<T>;
|
archive?: T | ArchiveBlockSelect<T>;
|
||||||
formBlock?: T | FormBlockSelect<T>;
|
formBlock?: T | FormBlockSelect<T>;
|
||||||
};
|
};
|
||||||
|
home?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
hero?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
backgroundImage?: T;
|
||||||
|
imageAlt?: T;
|
||||||
|
badge?: T;
|
||||||
|
headingLine1?: T;
|
||||||
|
headingAccent?: T;
|
||||||
|
description?: T;
|
||||||
|
primaryCta?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
label?: T;
|
||||||
|
url?: T;
|
||||||
|
};
|
||||||
|
secondaryCta?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
label?: T;
|
||||||
|
url?: T;
|
||||||
|
};
|
||||||
|
videoLabel?: T;
|
||||||
|
partnersLabel?: T;
|
||||||
|
partners?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
name?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
quickCheck?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
eyebrow?: T;
|
||||||
|
heading?: T;
|
||||||
|
image?: T;
|
||||||
|
imageAlt?: T;
|
||||||
|
body?: T;
|
||||||
|
criteria?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
label?: T;
|
||||||
|
desc?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
note?: T;
|
||||||
|
cta?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
label?: T;
|
||||||
|
url?: T;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
intro?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
eyebrow?: T;
|
||||||
|
heading?: T;
|
||||||
|
image?: T;
|
||||||
|
imageAlt?: T;
|
||||||
|
stats?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
value?: T;
|
||||||
|
label?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
quote?: T;
|
||||||
|
body?: T;
|
||||||
|
cta?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
label?: T;
|
||||||
|
url?: T;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
status?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
activePhase?: T;
|
||||||
|
phases?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
num?: T;
|
||||||
|
tag?: T;
|
||||||
|
pulse?: T;
|
||||||
|
phase?: T;
|
||||||
|
headline?: T;
|
||||||
|
body?: T;
|
||||||
|
cta?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
label?: T;
|
||||||
|
url?: T;
|
||||||
|
};
|
||||||
|
accent?: T;
|
||||||
|
bg?: T;
|
||||||
|
glow?: T;
|
||||||
|
meta?: T;
|
||||||
|
successApplications?: T;
|
||||||
|
successWinners?: T;
|
||||||
|
membershipCta?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
label?: T;
|
||||||
|
url?: T;
|
||||||
|
};
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
noActionLabel?: T;
|
||||||
|
};
|
||||||
|
winners?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
eyebrow?: T;
|
||||||
|
heading?: T;
|
||||||
|
cta?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
label?: T;
|
||||||
|
url?: T;
|
||||||
|
};
|
||||||
|
featured?: T;
|
||||||
|
};
|
||||||
|
testimonials?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
eyebrow?: T;
|
||||||
|
heading?: T;
|
||||||
|
items?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
image?: T;
|
||||||
|
imageUrl?: T;
|
||||||
|
name?: T;
|
||||||
|
role?: T;
|
||||||
|
company?: T;
|
||||||
|
year?: T;
|
||||||
|
quote?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
benefits?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
eyebrow?: T;
|
||||||
|
heading?: T;
|
||||||
|
image?: T;
|
||||||
|
imageAlt?: T;
|
||||||
|
imageLabel?: T;
|
||||||
|
items?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
num?: T;
|
||||||
|
title?: T;
|
||||||
|
desc?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
application?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
eyebrow?: T;
|
||||||
|
heading?: T;
|
||||||
|
body?: T;
|
||||||
|
benefits?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
num?: T;
|
||||||
|
label?: T;
|
||||||
|
desc?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
awardImage?: T;
|
||||||
|
awardImageAlt?: T;
|
||||||
|
awardCaption?: T;
|
||||||
|
formEyebrow?: T;
|
||||||
|
formTitle?: T;
|
||||||
|
footnote?: T;
|
||||||
|
footnoteStrong?: T;
|
||||||
|
};
|
||||||
|
videoModal?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
title?: T;
|
||||||
|
description?: T;
|
||||||
|
closeLabel?: T;
|
||||||
|
};
|
||||||
|
};
|
||||||
meta?:
|
meta?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
|
|||||||
137
src/scripts/preload-home-page-cms.ts
Normal file
137
src/scripts/preload-home-page-cms.ts
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import 'dotenv/config'
|
||||||
|
|
||||||
|
import config from '@payload-config'
|
||||||
|
import { getPayload, type Payload } from 'payload'
|
||||||
|
|
||||||
|
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: '/' } }, { slug: { equals: 'startseite' } }, { slug: { equals: 'home' } }],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const page = pageResult.docs[0]
|
||||||
|
if (!page) throw new Error('Home page not found. Expected spaPath=/ or slug=startseite/home.')
|
||||||
|
|
||||||
|
const [heroImage, quickImage, introImage, benefitsImage, awardImage] = await Promise.all([
|
||||||
|
mediaByFilename(payload, 'gala-saal-overview.jpg'),
|
||||||
|
mediaByFilename(payload, 'buehne-gewinner.jpg'),
|
||||||
|
mediaByFilename(payload, 'preistraeger-jubel.jpg'),
|
||||||
|
mediaByFilename(payload, 'networking-innenhof.jpg'),
|
||||||
|
mediaByFilename(payload, 'preisuebergabe.jpg'),
|
||||||
|
])
|
||||||
|
|
||||||
|
const winners = await payload.find({
|
||||||
|
collection: 'preistraeger',
|
||||||
|
limit: 8,
|
||||||
|
pagination: false,
|
||||||
|
sort: 'title',
|
||||||
|
where: { year: { equals: 2025 } },
|
||||||
|
})
|
||||||
|
|
||||||
|
await payload.update({
|
||||||
|
collection: 'pages',
|
||||||
|
id: page.id,
|
||||||
|
overrideAccess: true,
|
||||||
|
context: { disableRevalidate: true },
|
||||||
|
data: {
|
||||||
|
home: {
|
||||||
|
hero: {
|
||||||
|
backgroundImage: heroImage,
|
||||||
|
imageAlt: 'Awards Gala',
|
||||||
|
badge: 'Bewerbungsphase 2026 geschlossen',
|
||||||
|
headingLine1: 'BAYERNS BESTE',
|
||||||
|
headingAccent: 'MITTELSTÄNDLER.',
|
||||||
|
description: 'Der Bayerische Mittelstandspreis würdigt herausragende Leistungen – von Innovation über Nachhaltigkeit bis hin zur Exzellenz.',
|
||||||
|
primaryCta: { label: 'Jetzt kostenlos bewerben', url: '/teilnahme' },
|
||||||
|
secondaryCta: { label: 'Mitglied werden', url: '/mitglied-werden' },
|
||||||
|
videoLabel: 'Film abspielen',
|
||||||
|
partnersLabel: 'Unsere Partner',
|
||||||
|
partners: [
|
||||||
|
{ name: 'RSM Ebner Stolz' }, { name: 'WWK' }, { name: 'Deutsche Bank' }, { name: 'Bionorica' },
|
||||||
|
{ name: 'Berchtesgadener Land' }, { name: 'Metzler' }, { name: 'vbw' }, { name: 'Wirtschaftsbeirat Bayern' },
|
||||||
|
{ name: 'Radio Gong 96.3' }, { name: 'münchen.tv' }, { name: 'EWS' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
quickCheck: {
|
||||||
|
eyebrow: 'Schnell-Check', heading: 'SIND SIE\nBERECHTIGT?', image: quickImage, imageAlt: 'Preisverleihung Bühne',
|
||||||
|
body: 'Der Bayerische Mittelstandspreis richtet sich an inhabergeführte KMU im Freistaat. Fünf Kriterien entscheiden, erfüllen Sie alle fünf, steht Ihrer Bewerbung nichts im Weg.',
|
||||||
|
criteria: [
|
||||||
|
{ label: 'Standort Bayern', desc: 'Sitz oder Betriebsstätte im Freistaat' },
|
||||||
|
{ label: '50–1.500 Mitarbeiter', desc: 'Klassischer Mittelstand' },
|
||||||
|
{ label: 'Min. 3 Geschäftsjahre', desc: 'Markterfahrung und Stabilität' },
|
||||||
|
{ label: 'Privatwirtschaft', desc: 'Kein staatlicher Träger' },
|
||||||
|
{ label: 'Familien- bzw. eigentümergeführt', desc: 'Ihr Unternehmen ist inhabergeführt' },
|
||||||
|
],
|
||||||
|
note: 'Auch ein Verbund bzw. eine Gemeinschaft von mittelständischen Unternehmen oder von mittelständischen Unternehmen und Organisationen/Institutionen mit Schwerpunkt in Bayern kann teilnehmen.',
|
||||||
|
cta: { label: 'Ihr Weg zum Preis', url: '/teilnahme' },
|
||||||
|
},
|
||||||
|
intro: {
|
||||||
|
eyebrow: 'Ein Gewinn für alle', heading: 'AUSZEICHNUNG\nFÜR DIE BESTEN.', image: introImage, imageAlt: 'BMP Auszeichnung',
|
||||||
|
stats: [{ value: '20+', label: 'Jahre Exzellenz' }, { value: '0 €', label: 'Teilnahmegebühr' }, { value: '100%', label: 'Unabhängig' }],
|
||||||
|
quote: '„Ein Unternehmen zu gründen erfordert nicht allein spezielle Fähigkeiten. Es erfordert Persönlichkeit!“',
|
||||||
|
body: 'Wir suchen Vorbilder, die sich trotz aller Risiken nicht scheuen, Visionen in Businesspläne und Ideen in Unternehmungen zu verwandeln, und das mit großem persönlichen Einsatz und Verantwortung für Ihre Mitarbeiter. Seit vielen Jahren würdigen wir herausragende unternehmerische Leistungen im Freistaat.',
|
||||||
|
cta: { label: 'Mehr über den Preis', url: '/der-bmp' },
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
activePhase: '0',
|
||||||
|
noActionLabel: 'Kein Handlungsbedarf',
|
||||||
|
phases: [
|
||||||
|
{ num: '01', tag: 'JETZT OFFEN', pulse: true, phase: 'Bewerbungsphase 2026', headline: 'BEWERBEN SIE SICH JETZT.', body: 'Die Bewerbungsphase 2026 ist eröffnet – es ist noch nicht zu spät. Reichen Sie Ihre Unterlagen bis zum 30. Juni kostenlos ein und zeigen Sie, was den bayerischen Mittelstand ausmacht.', cta: { label: 'Jetzt kostenlos bewerben', url: '/teilnahme' }, accent: '#EFBF04', bg: '#020A1E', glow: 'rgba(239,191,4,0.14)', meta: 'Deadline: 30. Juni 2026', membershipCta: { label: 'Mitglied werden', url: '/mitglied-werden' } },
|
||||||
|
{ num: '02', tag: 'IN AUSWERTUNG', pulse: true, phase: 'Auswertungsphase 2026', headline: 'DIE JURY BEWERTET GERADE.', body: 'Die Anmeldephase 2026 ist geschlossen. Unsere unabhängige Expertenjury aus Wissenschaft und Praxis sichtet und bewertet derzeit alle Einreichungen.', accent: '#4A8FC9', bg: '#060D1C', glow: 'rgba(74,143,201,0.12)', meta: 'Ergebnis: August 2026', membershipCta: { label: 'Mitglied werden', url: '/mitglied-werden' } },
|
||||||
|
{ num: '03', tag: 'ABGESCHLOSSEN', pulse: false, phase: 'Preisverleihung · Oktober 2026', headline: 'DER BMP 2026 WAR EIN ERFOLG.', body: 'Wir danken allen 247 Bewerberinnen und Bewerbern für Mut und Exzellenz. Drei außergewöhnliche Unternehmen wurden in München ausgezeichnet – ein unvergesslicher Abend.', cta: { label: 'Alle Preisträger ansehen', url: '/preistraeger' }, accent: '#EFBF04', bg: '#06090F', glow: 'rgba(239,191,4,0.08)', meta: 'Gala · Oktober 2026 · München', successApplications: '247', successWinners: '3' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
winners: { eyebrow: 'Success Stories', heading: 'DIE BESTEN UNTERNEHMEN IN BAYERN', cta: { label: 'Alle Preisträger', url: '/preistraeger' }, featured: winners.docs.map((doc) => doc.id) },
|
||||||
|
testimonials: {
|
||||||
|
eyebrow: 'Stimmen der Preisträger', heading: 'DAS SAGEN UNSERE\nPREISTRÄGER.',
|
||||||
|
items: [
|
||||||
|
{ imageUrl: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=600', name: 'Dr. Maria Huber', role: 'Geschäftsführerin', company: 'TechVision GmbH', year: '2025', quote: 'Der BMP hat uns neue Türen geöffnet. Die Auszeichnung ist ein wichtiges Qualitätssiegel für unser gesamtes Team.' },
|
||||||
|
{ imageUrl: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&q=80&w=600', name: 'Hannes Weber', role: 'Inhaber & CEO', company: 'Weber Präzision GmbH', year: '2023', quote: 'Der Gewinn war ein Moment des Stolzes für die gesamte Belegschaft. Die höchste Anerkennung für unsere tägliche Arbeit.' },
|
||||||
|
{ imageUrl: 'https://images.unsplash.com/photo-1580489944761-15a19d654956?auto=format&fit=crop&q=80&w=600', name: 'Lisa Berger', role: 'Gründerin & CEO', company: 'EcoTech Bavaria', year: '2024', quote: 'Das Netzwerk, das wir durch den BMP gewonnen haben, ist unbezahlbar. Wir fanden Partner, die unsere Werte teilen.' },
|
||||||
|
{ imageUrl: 'https://images.unsplash.com/photo-1560250097-0b93528c311a?auto=format&fit=crop&q=80&w=600', name: 'Dr. Michael Schuh', role: 'Vorstand', company: 'Bayerischer Maschinenbau', year: '2024', quote: 'Der Preis hat unsere Sichtbarkeit weit über die bayerischen Grenzen hinaus gestärkt. Ein echtes Premium-Siegel.' },
|
||||||
|
{ imageUrl: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&q=80&w=600', name: 'Anna Kellner', role: 'Mitgründerin', company: 'AlpinTech Solutions', year: '2024', quote: 'Der BMP hat unsere Employer Brand transformiert. Wir gewinnen heute die besten Talente, weil wir Preisträger sind.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
benefits: {
|
||||||
|
eyebrow: 'Mehr als nur ein Preis', heading: 'WARUM SICH DIE\nTEILNAHME LOHNT.', image: benefitsImage, imageAlt: 'BMP Gala Netzwerk', imageLabel: 'BMP Gala-Netzwerk',
|
||||||
|
items: [
|
||||||
|
{ num: '01', title: 'Netzwerk', desc: 'Direkter Zugang zu Entscheidern aus Staatsregierung, Wirtschaft und Wissenschaft.' },
|
||||||
|
{ num: '02', title: 'Sichtbarkeit', desc: 'Breite mediale Begleitung durch TV-, Radio- und Print-Partner bayernweit.' },
|
||||||
|
{ num: '03', title: 'Bestätigung', desc: 'Ein unabhängiger Qualitätsnachweis – das stärkste Siegel für Ihren Marktauftritt.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
application: {
|
||||||
|
eyebrow: 'Chance ergreifen', heading: 'SCHREIBEN\nAUCH SIE\nGESCHICHTE.', body: 'Werden Sie Teil der Wall of Fame des bayerischen Mittelstands – vollständig kostenfrei.',
|
||||||
|
benefits: [{ num: '01', label: 'Kostenfrei', desc: 'Ohne Teilnahmegebühr' }, { num: '02', label: 'Jury', desc: 'Aus Wissenschaft und Praxis' }, { num: '03', label: 'Sichtbarkeit', desc: 'Medial bayernweit' }, { num: '04', label: 'Gala', desc: 'Verleihung München' }],
|
||||||
|
awardImage, awardImageAlt: 'Preisverleihung Bayerischer Mittelstandspreis', awardCaption: 'Preisverleihung 2025', formEyebrow: 'Direktbewerbung 2026', formTitle: 'Kostenlos bewerben', footnote: 'Kostenlos und unverbindlich –', footnoteStrong: 'keine Teilnahmegebühr',
|
||||||
|
},
|
||||||
|
videoModal: { title: 'Video-Player Platzhalter', description: 'Hier würde das Image-Video des BMP geladen werden.', closeLabel: 'Schließen' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
payload.logger.info(`Preloaded home CMS fields for page ${page.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
@@ -3,6 +3,7 @@ import { ChevronRight } from 'lucide-react';
|
|||||||
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 Image from '@/spa/components/ui/UnoptimizedImage'
|
import Image from '@/spa/components/ui/UnoptimizedImage'
|
||||||
|
import { mediaUrl } from '@/spa/cmsMediaField'
|
||||||
|
|
||||||
const FF = '"IBM Plex Sans", sans-serif';
|
const FF = '"IBM Plex Sans", sans-serif';
|
||||||
|
|
||||||
@@ -14,13 +15,43 @@ export const TESTIMONIALS = [
|
|||||||
{ img: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&q=80&w=600", name: "Anna Kellner", role: "Mitgründerin", company: "AlpinTech Solutions", year: "2024", quote: "Der BMP hat unsere Employer Brand transformiert. Wir gewinnen heute die besten Talente, weil wir Preisträger sind." },
|
{ img: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&q=80&w=600", name: "Anna Kellner", role: "Mitgründerin", company: "AlpinTech Solutions", year: "2024", quote: "Der BMP hat unsere Employer Brand transformiert. Wir gewinnen heute die besten Talente, weil wir Preisträger sind." },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function TestimonialsSection() {
|
type TestimonialItem = {
|
||||||
|
img: string
|
||||||
|
name: string
|
||||||
|
role: string
|
||||||
|
company: string
|
||||||
|
year: string
|
||||||
|
quote: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestimonialsCms = {
|
||||||
|
eyebrow?: string | null
|
||||||
|
heading?: string | null
|
||||||
|
items?: Array<Record<string, any>> | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallbackText = (value: unknown, fallback: string) =>
|
||||||
|
typeof value === 'string' && value.length > 0 ? value : fallback
|
||||||
|
|
||||||
|
function Lines({ text }: { text: string }) {
|
||||||
|
return <>{text.split('\n').map((line, i) => <React.Fragment key={`${line}-${i}`}>{i > 0 && <br />}{line}</React.Fragment>)}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TestimonialsSection({ data }: { data?: TestimonialsCms }) {
|
||||||
const [active, setActive] = useState(0);
|
const [active, setActive] = useState(0);
|
||||||
const t = TESTIMONIALS[active];
|
const testimonials: TestimonialItem[] = data?.items?.length ? data.items.map((item) => ({
|
||||||
|
img: mediaUrl(item.image, item.imageUrl || item.img || ''),
|
||||||
|
name: fallbackText(item.name, ''),
|
||||||
|
role: fallbackText(item.role, ''),
|
||||||
|
company: fallbackText(item.company, ''),
|
||||||
|
year: fallbackText(item.year, ''),
|
||||||
|
quote: fallbackText(item.quote, ''),
|
||||||
|
})) : TESTIMONIALS;
|
||||||
|
const t = testimonials[active] || testimonials[0];
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
const prev = () => setActive(i => (i - 1 + TESTIMONIALS.length) % TESTIMONIALS.length);
|
const prev = () => setActive(i => (i - 1 + testimonials.length) % testimonials.length);
|
||||||
const next = () => setActive(i => (i + 1) % TESTIMONIALS.length);
|
const next = () => setActive(i => (i + 1) % testimonials.length);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section style={{ background: '#fff', overflow: 'hidden', position: 'relative', isolation: 'isolate' }}>
|
<section style={{ background: '#fff', overflow: 'hidden', position: 'relative', isolation: 'isolate' }}>
|
||||||
@@ -29,9 +60,9 @@ export default function TestimonialsSection() {
|
|||||||
{isMobile && (
|
{isMobile && (
|
||||||
<div style={{ padding: '48px 24px 52px', position: 'relative', zIndex: 1 }}>
|
<div style={{ padding: '48px 24px 52px', position: 'relative', zIndex: 1 }}>
|
||||||
{/* Section header */}
|
{/* Section header */}
|
||||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 14 }}>Stimmen der Preisträger</span>
|
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 14 }}>{fallbackText(data?.eyebrow, 'Stimmen der Preisträger')}</span>
|
||||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.8rem, 8vw, 2.3rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.04, margin: '0 0 16px' }}>
|
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.8rem, 8vw, 2.3rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.04, margin: '0 0 16px' }}>
|
||||||
DAS SAGEN UNSERE<br />PREISTRÄGER.
|
<Lines text={fallbackText(data?.heading, 'DAS SAGEN UNSERE\nPREISTRÄGER.')} />
|
||||||
</h2>
|
</h2>
|
||||||
<div style={{ width: 40, height: 2, background: '#EFBF04', marginBottom: 28 }} />
|
<div style={{ width: 40, height: 2, background: '#EFBF04', marginBottom: 28 }} />
|
||||||
|
|
||||||
@@ -62,7 +93,7 @@ export default function TestimonialsSection() {
|
|||||||
<ChevronRight size={16} />
|
<ChevronRight size={16} />
|
||||||
</button>
|
</button>
|
||||||
<div style={{ display: 'flex', gap: 6, marginLeft: 6 }}>
|
<div style={{ display: 'flex', gap: 6, marginLeft: 6 }}>
|
||||||
{TESTIMONIALS.map((_, i) => (
|
{testimonials.map((_, i) => (
|
||||||
<button key={i} onClick={() => setActive(i)} aria-label={`Stimme ${i + 1}`} style={{ width: i === active ? 22 : 7, height: 7, borderRadius: 100, background: i === active ? '#EFBF04' : '#D0D5DD', border: 'none', cursor: 'pointer', padding: 0, transition: 'all 0.2s' }} />
|
<button key={i} onClick={() => setActive(i)} aria-label={`Stimme ${i + 1}`} style={{ width: i === active ? 22 : 7, height: 7, borderRadius: 100, background: i === active ? '#EFBF04' : '#D0D5DD', border: 'none', cursor: 'pointer', padding: 0, transition: 'all 0.2s' }} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -86,10 +117,10 @@ export default function TestimonialsSection() {
|
|||||||
|
|
||||||
{/* Right – quote */}
|
{/* Right – quote */}
|
||||||
<div style={{ padding: isMobile ? '40px 22px 56px' : '80px 72px 80px 56px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
<div style={{ padding: isMobile ? '40px 22px 56px' : '80px 72px 80px 56px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
||||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 14 }}>Stimmen der Preisträger</span>
|
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 14 }}>{fallbackText(data?.eyebrow, 'Stimmen der Preisträger')}</span>
|
||||||
|
|
||||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.8rem, 2.6vw, 2.6rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.04, margin: '0 0 36px' }}>
|
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.8rem, 2.6vw, 2.6rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.04, margin: '0 0 36px' }}>
|
||||||
DAS SAGEN UNSERE PREISTRÄGER.
|
<Lines text={fallbackText(data?.heading, 'DAS SAGEN UNSERE PREISTRÄGER.')} />
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p style={{ fontFamily: FF, fontSize: 'clamp(1.4rem, 2.2vw, 2rem)', fontWeight: 300, color: '#101828', lineHeight: 1.55, margin: '0 0 40px' }}>
|
<p style={{ fontFamily: FF, fontSize: 'clamp(1.4rem, 2.2vw, 2rem)', fontWeight: 300, color: '#101828', lineHeight: 1.55, margin: '0 0 40px' }}>
|
||||||
@@ -117,7 +148,7 @@ export default function TestimonialsSection() {
|
|||||||
<ChevronRight size={16} />
|
<ChevronRight size={16} />
|
||||||
</button>
|
</button>
|
||||||
<div style={{ display: 'flex', gap: 6, marginLeft: 8 }}>
|
<div style={{ display: 'flex', gap: 6, marginLeft: 8 }}>
|
||||||
{TESTIMONIALS.map((_, i) => (
|
{testimonials.map((_, i) => (
|
||||||
<button key={i} onClick={() => setActive(i)} style={{ width: i === active ? 20 : 6, height: 6, background: i === active ? '#EFBF04' : '#D0D5DD', border: 'none', cursor: 'pointer', padding: 0, transition: 'all 0.2s' }} />
|
<button key={i} onClick={() => setActive(i)} style={{ width: i === active ? 20 : 6, height: 6, background: i === active ? '#EFBF04' : '#D0D5DD', border: 'none', cursor: 'pointer', padding: 0, transition: 'all 0.2s' }} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Button } from '@/spa/components/ui/button';
|
import { Button } from '@/spa/components/ui/button';
|
||||||
import { Play, ChevronRight, X, Star } from 'lucide-react';
|
import { Play, ChevronRight, X, Star } from 'lucide-react';
|
||||||
import { Link } from '@/spa/router';
|
import { Link } from '@/spa/router';
|
||||||
@@ -9,6 +9,8 @@ import TestimonialsSection from '@/spa/components/TestimonialsSection';
|
|||||||
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 Image from '@/spa/components/ui/UnoptimizedImage'
|
import Image from '@/spa/components/ui/UnoptimizedImage'
|
||||||
|
import { useCmsCollection, useCmsRoute } from '@/spa/cmsRoute'
|
||||||
|
import { mediaAlt, mediaUrl } from '@/spa/cmsMediaField'
|
||||||
|
|
||||||
const PARTNERS = [
|
const PARTNERS = [
|
||||||
{ id: 'rsm', label: <><span style={{fontWeight:700,fontSize:11,letterSpacing:2}}>RSM</span><br/><span style={{fontWeight:300,fontSize:8,letterSpacing:1,opacity:.7}}>Ebner Stolz</span></> },
|
{ id: 'rsm', label: <><span style={{fontWeight:700,fontSize:11,letterSpacing:2}}>RSM</span><br/><span style={{fontWeight:300,fontSize:8,letterSpacing:1,opacity:.7}}>Ebner Stolz</span></> },
|
||||||
@@ -26,6 +28,41 @@ const PARTNERS = [
|
|||||||
|
|
||||||
const FF = '"IBM Plex Sans", sans-serif';
|
const FF = '"IBM Plex Sans", sans-serif';
|
||||||
|
|
||||||
|
type HomeCms = Record<string, any>;
|
||||||
|
|
||||||
|
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 > 0 ? (value as T[]) : fallback;
|
||||||
|
|
||||||
|
function Lines({ text }: { text: string }) {
|
||||||
|
return <>{text.split('\n').map((line, i) => <React.Fragment key={`${line}-${i}`}>{i > 0 && <br />}{line}</React.Fragment>)}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_QUICK_CRITERIA = [
|
||||||
|
{ label: 'Standort Bayern', desc: 'Sitz oder Betriebsstätte im Freistaat' },
|
||||||
|
{ label: '50–1.500 Mitarbeiter', desc: 'Klassischer Mittelstand' },
|
||||||
|
{ label: 'Min. 3 Geschäftsjahre', desc: 'Markterfahrung und Stabilität' },
|
||||||
|
{ label: 'Privatwirtschaft', desc: 'Kein staatlicher Träger' },
|
||||||
|
{ label: 'Familien- bzw. eigentümergeführt', desc: 'Ihr Unternehmen ist inhabergeführt' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEFAULT_INTRO_STATS = [['20+', 'Jahre Exzellenz'], ['0 €', 'Teilnahmegebühr'], ['100%', 'Unabhängig']];
|
||||||
|
|
||||||
|
const DEFAULT_BENEFITS = [
|
||||||
|
{ num: '01', title: 'Netzwerk', desc: 'Direkter Zugang zu Entscheidern aus Staatsregierung, Wirtschaft und Wissenschaft.' },
|
||||||
|
{ num: '02', title: 'Sichtbarkeit', desc: 'Breite mediale Begleitung durch TV-, Radio- und Print-Partner bayernweit.' },
|
||||||
|
{ num: '03', title: 'Bestätigung', desc: 'Ein unabhängiger Qualitätsnachweis – das stärkste Siegel für Ihren Marktauftritt.' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEFAULT_APPLICATION_BENEFITS = [
|
||||||
|
{ num: '01', label: 'Kostenfrei', desc: 'Ohne Teilnahmegebühr' },
|
||||||
|
{ num: '02', label: 'Jury', desc: 'Aus Wissenschaft und Praxis' },
|
||||||
|
{ num: '03', label: 'Sichtbarkeit', desc: 'Medial bayernweit' },
|
||||||
|
{ num: '04', label: 'Gala', desc: 'Verleihung München' },
|
||||||
|
];
|
||||||
|
|
||||||
// ─── Status Phase Slider ────────────────────────────────────────────────────
|
// ─── Status Phase Slider ────────────────────────────────────────────────────
|
||||||
/**
|
/**
|
||||||
* 🔧 DEPLOYMENT CONFIG
|
* 🔧 DEPLOYMENT CONFIG
|
||||||
@@ -92,27 +129,37 @@ const STATUS_PHASES: StatusPhase[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function HomeWinnerCard({ winner }: { winner: import('@/spa/data/winners').Winner }) {
|
function HomeWinnerCard({ winner }: { winner: any }) {
|
||||||
const [hovered, setHovered] = useState(false);
|
const [hovered, setHovered] = useState(false);
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const staticWinner = WINNERS.find(
|
||||||
|
(item) =>
|
||||||
|
String(item.id) === String(winner?.id) ||
|
||||||
|
item.slug === winner?.slug ||
|
||||||
|
item.name === winner?.title ||
|
||||||
|
item.name === winner?.name,
|
||||||
|
);
|
||||||
|
const title = fallbackText(winner?.title, fallbackText(winner?.name, staticWinner?.name || 'Preisträger'));
|
||||||
|
const imageSrc = mediaUrl(winner?.image, winner?.img || staticWinner?.img || '/images/gala-saal-overview.jpg');
|
||||||
|
const href = winner?.spaPath || (winner?.slug ? `/preistraeger/${winner.slug}` : staticWinner ? `/preistraeger/${staticWinner.slug}` : '/preistraeger');
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
to={`/preistraeger/${winner.slug}`}
|
to={href}
|
||||||
style={{ textDecoration: 'none', display: 'block', position: 'relative', height: isMobile ? 220 : 280, overflow: 'hidden', scrollSnapAlign: isMobile ? 'start' : undefined }}
|
style={{ textDecoration: 'none', display: 'block', position: 'relative', height: isMobile ? 220 : 280, overflow: 'hidden', scrollSnapAlign: isMobile ? 'start' : undefined }}
|
||||||
onMouseEnter={() => setHovered(true)}
|
onMouseEnter={() => setHovered(true)}
|
||||||
onMouseLeave={() => setHovered(false)}
|
onMouseLeave={() => setHovered(false)}
|
||||||
>
|
>
|
||||||
<Image unoptimized src={winner.img} alt={winner.name} style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', transition: 'transform 0.5s ease', transform: hovered ? 'scale(1.07)' : 'scale(1)', filter: 'grayscale(15%)' }} />
|
<Image unoptimized src={imageSrc} alt={title} style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', transition: 'transform 0.5s ease', transform: hovered ? 'scale(1.07)' : 'scale(1)', filter: 'grayscale(15%)' }} />
|
||||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to top, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0.1) 55%, transparent 100%)' }} />
|
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to top, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0.1) 55%, transparent 100%)' }} />
|
||||||
<div style={{ position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.2)', opacity: hovered ? 1 : 0, transition: 'opacity 0.25s' }} />
|
<div style={{ position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.2)', opacity: hovered ? 1 : 0, transition: 'opacity 0.25s' }} />
|
||||||
<div style={{ position: 'absolute', inset: 0, boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.06)' }} />
|
<div style={{ position: 'absolute', inset: 0, boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.06)' }} />
|
||||||
<div style={{ position: 'absolute', bottom: 0, left: 0, padding: '18px 20px' }}>
|
<div style={{ position: 'absolute', bottom: 0, left: 0, padding: '18px 20px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 5, marginBottom: 5 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 5, marginBottom: 5 }}>
|
||||||
<Star size={10} style={{ color: '#EFBF04', flexShrink: 0 }} fill="#EFBF04" />
|
<Star size={10} style={{ color: '#EFBF04', flexShrink: 0 }} fill="#EFBF04" />
|
||||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, fontWeight: 700, color: '#EFBF04', letterSpacing: '0.12em', textTransform: 'uppercase' }}>{winner.year} · {winner.type}</span>
|
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, fontWeight: 700, color: '#EFBF04', letterSpacing: '0.12em', textTransform: 'uppercase' }}>{winner?.year || staticWinner?.year} · {winner?.awardType || winner?.type || staticWinner?.type}</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 16, fontWeight: 700, color: '#fff', margin: '0 0 3px', lineHeight: 1.3 }}>{winner.name}</h3>
|
<h3 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 16, fontWeight: 700, color: '#fff', margin: '0 0 3px', lineHeight: 1.3 }}>{title}</h3>
|
||||||
<p style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 14, color: 'rgba(255,255,255,0.75)', margin: 0 }}>{winner.category}</p>
|
<p style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 14, color: 'rgba(255,255,255,0.75)', margin: 0 }}>{winner?.category || staticWinner?.category}</p>
|
||||||
<div style={{ marginTop: 10, display: 'inline-block', background: '#EFBF04', color: '#101828', padding: '5px 14px', borderRadius: 999, fontSize: 10, fontFamily: '"IBM Plex Sans", sans-serif', fontWeight: 800, letterSpacing: '0.1em', textTransform: 'uppercase', opacity: hovered ? 1 : 0, transform: hovered ? 'translateY(0)' : 'translateY(6px)', transition: 'all 0.25s' }}>
|
<div style={{ marginTop: 10, display: 'inline-block', background: '#EFBF04', color: '#101828', padding: '5px 14px', borderRadius: 999, fontSize: 10, fontFamily: '"IBM Plex Sans", sans-serif', fontWeight: 800, letterSpacing: '0.1em', textTransform: 'uppercase', opacity: hovered ? 1 : 0, transform: hovered ? 'translateY(0)' : 'translateY(6px)', transition: 'all 0.25s' }}>
|
||||||
Detail →
|
Detail →
|
||||||
</div>
|
</div>
|
||||||
@@ -124,6 +171,33 @@ function HomeWinnerCard({ winner }: { winner: import('@/spa/data/winners').Winne
|
|||||||
const Home: React.FC = () => {
|
const Home: React.FC = () => {
|
||||||
const [showVideo, setShowVideo] = useState(false);
|
const [showVideo, setShowVideo] = useState(false);
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const home = (useCmsRoute()?.doc?.home || {}) as HomeCms;
|
||||||
|
const hero = home.hero || {};
|
||||||
|
const quickCheck = home.quickCheck || {};
|
||||||
|
const intro = home.intro || {};
|
||||||
|
const winnersSection = home.winners || {};
|
||||||
|
const benefits = home.benefits || {};
|
||||||
|
const application = home.application || {};
|
||||||
|
const videoModal = home.videoModal || {};
|
||||||
|
const cmsPreistraeger = useCmsCollection('preistraeger');
|
||||||
|
const selectedWinners = fallbackArray(winnersSection.featured, []);
|
||||||
|
const selectedWinnerDocs = selectedWinners
|
||||||
|
.map((selected: any) => {
|
||||||
|
const selectedID = typeof selected === 'object' ? selected.id : selected
|
||||||
|
const listDoc = cmsPreistraeger.find((doc: any) => String(doc.id) === String(selectedID))
|
||||||
|
return listDoc || (typeof selected === 'object' ? selected : undefined)
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
const homeWinners = selectedWinnerDocs.length
|
||||||
|
? selectedWinnerDocs
|
||||||
|
: (cmsPreistraeger.length ? cmsPreistraeger : WINNERS).filter((w: any) => Number(w.year) === 2025).slice(0, 8);
|
||||||
|
const quickCriteria = fallbackArray(quickCheck.criteria, DEFAULT_QUICK_CRITERIA);
|
||||||
|
const introStats = fallbackArray(
|
||||||
|
intro.stats,
|
||||||
|
DEFAULT_INTRO_STATS.map(([value, label]) => ({ value, label })),
|
||||||
|
);
|
||||||
|
const benefitItems = fallbackArray(benefits.items, DEFAULT_BENEFITS);
|
||||||
|
const applicationBenefits = fallbackArray(application.benefits, DEFAULT_APPLICATION_BENEFITS);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="animate-fade-in">
|
<div className="animate-fade-in">
|
||||||
@@ -131,8 +205,8 @@ const Home: React.FC = () => {
|
|||||||
<section className="relative flex items-center overflow-hidden" style={{ background: '#111D55', height: isMobile ? '88svh' : '100vh' }}>
|
<section className="relative flex items-center overflow-hidden" style={{ background: '#111D55', height: isMobile ? '88svh' : '100vh' }}>
|
||||||
<div className="absolute inset-0 z-0">
|
<div className="absolute inset-0 z-0">
|
||||||
<Image unoptimized
|
<Image unoptimized
|
||||||
src="/images/gala-saal-overview.jpg"
|
src={mediaUrl(hero.backgroundImage, '/images/gala-saal-overview.jpg')}
|
||||||
alt="Awards Gala"
|
alt={mediaAlt(hero.backgroundImage, fallbackText(hero.imageAlt, 'Awards Gala'))}
|
||||||
className="w-full h-full object-cover scale-105"
|
className="w-full h-full object-cover scale-105"
|
||||||
style={{ objectPosition: 'center top' }}
|
style={{ objectPosition: 'center top' }}
|
||||||
/>
|
/>
|
||||||
@@ -143,35 +217,35 @@ const Home: React.FC = () => {
|
|||||||
<div style={{ maxWidth: isMobile ? '100%' : '48rem' }}>
|
<div style={{ maxWidth: isMobile ? '100%' : '48rem' }}>
|
||||||
<div className="inline-flex items-center gap-3 bg-accent/20 border border-accent/30 rounded-full px-4 py-1.5 mb-8 backdrop-blur-sm animate-fade-in" style={{ marginBottom: isMobile ? 20 : undefined }}>
|
<div className="inline-flex items-center gap-3 bg-accent/20 border border-accent/30 rounded-full px-4 py-1.5 mb-8 backdrop-blur-sm animate-fade-in" style={{ marginBottom: isMobile ? 20 : undefined }}>
|
||||||
<span className="flex h-2 w-2 rounded-full bg-accent"></span>
|
<span className="flex h-2 w-2 rounded-full bg-accent"></span>
|
||||||
<span className="text-accent text-[10px] uppercase tracking-[0.2em] font-bold">Bewerbungsphase 2026 geschlossen</span>
|
<span className="text-accent text-[10px] uppercase tracking-[0.2em] font-bold">{fallbackText(hero.badge, 'Bewerbungsphase 2026 geschlossen')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="font-display font-bold text-white mb-6 leading-[0.9] tracking-tight" style={{ fontSize: isMobile ? 'clamp(1.8rem, 10vw, 3rem)' : 'clamp(3.5rem, 8vw, 6rem)' }}>
|
<h1 className="font-display font-bold text-white mb-6 leading-[0.9] tracking-tight" style={{ fontSize: isMobile ? 'clamp(1.8rem, 10vw, 3rem)' : 'clamp(3.5rem, 8vw, 6rem)' }}>
|
||||||
BAYERNS BESTE <br />
|
{fallbackText(hero.headingLine1, 'BAYERNS BESTE')} <br />
|
||||||
<span className="text-accent">MITTELSTÄNDLER.</span>
|
<span className="text-accent">{fallbackText(hero.headingAccent, 'MITTELSTÄNDLER.')}</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-white/70 mb-12 font-light leading-relaxed" style={{ fontSize: isMobile ? '1.0625rem' : '1.375rem', maxWidth: isMobile ? '100%' : '36rem', marginBottom: isMobile ? 28 : undefined }}>
|
<p className="text-white/70 mb-12 font-light leading-relaxed" style={{ fontSize: isMobile ? '1.0625rem' : '1.375rem', maxWidth: isMobile ? '100%' : '36rem', marginBottom: isMobile ? 28 : undefined }}>
|
||||||
Der Bayerische Mittelstandspreis würdigt herausragende Leistungen – von Innovation über Nachhaltigkeit bis hin zur Exzellenz.
|
{fallbackText(hero.description, 'Der Bayerische Mittelstandspreis würdigt herausragende Leistungen – von Innovation über Nachhaltigkeit bis hin zur Exzellenz.')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||||
<Link
|
<Link
|
||||||
to="/teilnahme"
|
to={fallbackText(hero.primaryCta?.url, '/teilnahme')}
|
||||||
style={{ fontFamily:'"IBM Plex Sans", sans-serif', fontSize: 16, fontWeight:700, textTransform:'uppercase', letterSpacing:'0.12em', color:'#101828', background:'#EFBF04', padding:'16px 36px', textDecoration:'none', display:'inline-flex', alignItems:'center', justifyContent: isMobile ? 'center' : 'flex-start', gap:10, transition:'background 0.15s, box-shadow 0.2s', width: isMobile ? '100%' : 'auto' }}
|
style={{ fontFamily:'"IBM Plex Sans", sans-serif', fontSize: 16, fontWeight:700, textTransform:'uppercase', letterSpacing:'0.12em', color:'#101828', background:'#EFBF04', padding:'16px 36px', textDecoration:'none', display:'inline-flex', alignItems:'center', justifyContent: isMobile ? 'center' : 'flex-start', gap:10, transition:'background 0.15s, box-shadow 0.2s', width: isMobile ? '100%' : 'auto' }}
|
||||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#FFD130'; (e.currentTarget as HTMLElement).style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#FFD130'; (e.currentTarget as HTMLElement).style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
||||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#EFBF04'; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }}
|
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#EFBF04'; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }}
|
||||||
>
|
>
|
||||||
Jetzt kostenlos bewerben <ChevronRight size={16} />
|
{fallbackText(hero.primaryCta?.label, 'Jetzt kostenlos bewerben')} <ChevronRight size={16} />
|
||||||
</Link>
|
</Link>
|
||||||
<div style={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row', gap: 16, alignItems: isMobile ? 'stretch' : 'center' }}>
|
<div style={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row', gap: 16, alignItems: isMobile ? 'stretch' : 'center' }}>
|
||||||
<Link
|
<Link
|
||||||
to="/mitglied-werden"
|
to={fallbackText(hero.secondaryCta?.url, '/mitglied-werden')}
|
||||||
style={{ fontFamily:'"IBM Plex Sans", sans-serif', fontSize: 16, fontWeight:700, textTransform:'uppercase', letterSpacing:'0.12em', color:'rgba(255,255,255,0.85)', border:'1.5px solid rgba(255,255,255,0.35)', padding:'14px 32px', textDecoration:'none', display:'inline-flex', alignItems:'center', justifyContent: isMobile ? 'center' : 'flex-start', gap:10, transition:'border-color 0.15s, color 0.15s', background:'transparent', width: isMobile ? '100%' : 'auto' }}
|
style={{ fontFamily:'"IBM Plex Sans", sans-serif', fontSize: 16, fontWeight:700, textTransform:'uppercase', letterSpacing:'0.12em', color:'rgba(255,255,255,0.85)', border:'1.5px solid rgba(255,255,255,0.35)', padding:'14px 32px', textDecoration:'none', display:'inline-flex', alignItems:'center', justifyContent: isMobile ? 'center' : 'flex-start', gap:10, transition:'border-color 0.15s, color 0.15s', background:'transparent', width: isMobile ? '100%' : 'auto' }}
|
||||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.borderColor = '#EFBF04'; (e.currentTarget as HTMLElement).style.color = '#EFBF04'; }}
|
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.borderColor = '#EFBF04'; (e.currentTarget as HTMLElement).style.color = '#EFBF04'; }}
|
||||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.borderColor = 'rgba(255,255,255,0.35)'; (e.currentTarget as HTMLElement).style.color = 'rgba(255,255,255,0.85)'; }}
|
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.borderColor = 'rgba(255,255,255,0.35)'; (e.currentTarget as HTMLElement).style.color = 'rgba(255,255,255,0.85)'; }}
|
||||||
>
|
>
|
||||||
Mitglied werden
|
{fallbackText(hero.secondaryCta?.label, 'Mitglied werden')}
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowVideo(true)}
|
onClick={() => setShowVideo(true)}
|
||||||
@@ -181,7 +255,7 @@ const Home: React.FC = () => {
|
|||||||
<div className="w-12 h-12 rounded-full border border-white/30 flex items-center justify-center group-hover:border-accent group-hover:bg-accent/10 transition-all">
|
<div className="w-12 h-12 rounded-full border border-white/30 flex items-center justify-center group-hover:border-accent group-hover:bg-accent/10 transition-all">
|
||||||
<Play size={20} fill="currentColor" />
|
<Play size={20} fill="currentColor" />
|
||||||
</div>
|
</div>
|
||||||
<span className="uppercase text-xs tracking-widest font-bold">Film abspielen</span>
|
<span className="uppercase text-xs tracking-widest font-bold">{fallbackText(hero.videoLabel, 'Film abspielen')}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -192,12 +266,12 @@ const Home: React.FC = () => {
|
|||||||
<div className="absolute bottom-0 left-0 w-full bg-white/5 backdrop-blur-xl border-t border-white/10 hidden lg:block">
|
<div className="absolute bottom-0 left-0 w-full bg-white/5 backdrop-blur-xl border-t border-white/10 hidden lg:block">
|
||||||
<div className="px-8 py-5 flex items-center justify-between opacity-60 hover:opacity-100 transition-opacity duration-500">
|
<div className="px-8 py-5 flex items-center justify-between opacity-60 hover:opacity-100 transition-opacity duration-500">
|
||||||
{/* Left: label – stays put */}
|
{/* Left: label – stays put */}
|
||||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 9, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.5)', whiteSpace: 'nowrap', flexShrink: 0 }}>Unsere Partner</span>
|
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 9, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.5)', whiteSpace: 'nowrap', flexShrink: 0 }}>{fallbackText(hero.partnersLabel, 'Unsere Partner')}</span>
|
||||||
{/* Right: ticker – fixed window, exactly 4 logos wide */}
|
{/* Right: ticker – fixed window, exactly 4 logos wide */}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16, flexShrink: 0 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 16, flexShrink: 0 }}>
|
||||||
<div style={{ width: 1, height: 20, background: 'rgba(255,255,255,0.2)' }} />
|
<div style={{ width: 1, height: 20, background: 'rgba(255,255,255,0.2)' }} />
|
||||||
<div style={{ width: 560, overflow: 'hidden', flexShrink: 0 }}>
|
<div style={{ width: 560, overflow: 'hidden', flexShrink: 0 }}>
|
||||||
<PartnerTicker logos={PARTNERS} />
|
<PartnerTicker logos={fallbackArray(hero.partners, PARTNERS).map((partner: any) => partner.label ? partner : { id: partner.id || partner.name, label: <span style={{fontWeight:700,fontSize:10,letterSpacing:1.5}}>{partner.name}</span> })} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -213,25 +287,19 @@ const Home: React.FC = () => {
|
|||||||
{/* Eyebrow + heading – desktop only (on mobile these are overlaid on the image) */}
|
{/* Eyebrow + heading – desktop only (on mobile these are overlaid on the image) */}
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<>
|
<>
|
||||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 16 }}>Schnell-Check</span>
|
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 16 }}>{fallbackText(quickCheck.eyebrow, 'Schnell-Check')}</span>
|
||||||
<h2 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(1.8rem, 2.8vw, 2.5rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: '0 0 20px' }}>
|
<h2 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(1.8rem, 2.8vw, 2.5rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: '0 0 20px' }}>
|
||||||
SIND SIE<br />BERECHTIGT?
|
<Lines text={fallbackText(quickCheck.heading, 'SIND SIE\nBERECHTIGT?')} />
|
||||||
</h2>
|
</h2>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div style={{ width: 40, height: 2, background: '#EFBF04', marginBottom: 28 }} />
|
<div style={{ width: 40, height: 2, background: '#EFBF04', marginBottom: 28 }} />
|
||||||
<p style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 17, color: 'rgba(16,24,40,0.6)', lineHeight: 1.7, marginBottom: 32 }}>
|
<p style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 17, color: 'rgba(16,24,40,0.6)', lineHeight: 1.7, marginBottom: 32 }}>
|
||||||
Der Bayerische Mittelstandspreis richtet sich an inhabergeführte KMU im Freistaat. Fünf Kriterien entscheiden, erfüllen Sie alle fünf, steht Ihrer Bewerbung nichts im Weg.
|
{fallbackText(quickCheck.body, 'Der Bayerische Mittelstandspreis richtet sich an inhabergeführte KMU im Freistaat. Fünf Kriterien entscheiden, erfüllen Sie alle fünf, steht Ihrer Bewerbung nichts im Weg.')}
|
||||||
</p>
|
</p>
|
||||||
{/* Criteria list */}
|
{/* Criteria list */}
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 0, marginBottom: 36, borderTop: '1px solid #D0D5DD' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 0, marginBottom: 36, borderTop: '1px solid #D0D5DD' }}>
|
||||||
{[
|
{quickCriteria.map((item, i) => (
|
||||||
{ label: 'Standort Bayern', desc: 'Sitz oder Betriebsstätte im Freistaat' },
|
|
||||||
{ label: '50–1.500 Mitarbeiter', desc: 'Klassischer Mittelstand' },
|
|
||||||
{ label: 'Min. 3 Geschäftsjahre', desc: 'Markterfahrung und Stabilität' },
|
|
||||||
{ label: 'Privatwirtschaft', desc: 'Kein staatlicher Träger' },
|
|
||||||
{ label: 'Familien- bzw. eigentümergeführt', desc: 'Ihr Unternehmen ist inhabergeführt' },
|
|
||||||
].map((item, i) => (
|
|
||||||
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '13px 0', borderBottom: '1px solid #D0D5DD' }}>
|
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '13px 0', borderBottom: '1px solid #D0D5DD' }}>
|
||||||
<div style={{ width: 24, height: 24, background: '#111D55', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
<div style={{ width: 24, height: 24, background: '#111D55', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
||||||
<svg width="10" height="10" viewBox="0 0 10 10"><polyline points="1,5 3.5,7.5 9,2" stroke="#EFBF04" strokeWidth="1.8" fill="none" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
<svg width="10" height="10" viewBox="0 0 10 10"><polyline points="1,5 3.5,7.5 9,2" stroke="#EFBF04" strokeWidth="1.8" fill="none" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||||
@@ -244,23 +312,23 @@ const Home: React.FC = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 14, color: 'rgba(16,24,40,0.55)', lineHeight: 1.6, marginTop: 16, marginBottom: 28 }}>
|
<p style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 14, color: 'rgba(16,24,40,0.55)', lineHeight: 1.6, marginTop: 16, marginBottom: 28 }}>
|
||||||
Auch ein Verbund bzw. eine Gemeinschaft von mittelständischen Unternehmen oder von mittelständischen Unternehmen und Organisationen/Institutionen mit Schwerpunkt in Bayern kann teilnehmen.
|
{fallbackText(quickCheck.note, 'Auch ein Verbund bzw. eine Gemeinschaft von mittelständischen Unternehmen oder von mittelständischen Unternehmen und Organisationen/Institutionen mit Schwerpunkt in Bayern kann teilnehmen.')}
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
to="/teilnahme"
|
to={fallbackText(quickCheck.cta?.url, '/teilnahme')}
|
||||||
style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 15, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: '#101828', background: '#EFBF04', padding: '15px 32px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8, alignSelf: isMobile ? 'stretch' : 'flex-start', transition: 'background 0.15s, box-shadow 0.2s' }}
|
style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 15, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: '#101828', background: '#EFBF04', padding: '15px 32px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8, alignSelf: isMobile ? 'stretch' : 'flex-start', transition: 'background 0.15s, box-shadow 0.2s' }}
|
||||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#FFD130'; (e.currentTarget as HTMLElement).style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#FFD130'; (e.currentTarget as HTMLElement).style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
||||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#EFBF04'; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }}
|
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#EFBF04'; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }}
|
||||||
>
|
>
|
||||||
Ihr Weg zum Preis <ChevronRight size={14} />
|
{fallbackText(quickCheck.cta?.label, 'Ihr Weg zum Preis')} <ChevronRight size={14} />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right (desktop) / Top (mobile) – image; on mobile the heading is overlaid here */}
|
{/* Right (desktop) / Top (mobile) – image; on mobile the heading is overlaid here */}
|
||||||
<div style={{ position: 'relative', minHeight: isMobile ? 280 : 560, overflow: 'hidden', order: isMobile ? 1 : 0 }}>
|
<div style={{ position: 'relative', minHeight: isMobile ? 280 : 560, overflow: 'hidden', order: isMobile ? 1 : 0 }}>
|
||||||
<Image unoptimized
|
<Image unoptimized
|
||||||
src="/images/buehne-gewinner.jpg"
|
src={mediaUrl(quickCheck.image, '/images/buehne-gewinner.jpg')}
|
||||||
alt="Preisverleihung Bühne"
|
alt={mediaAlt(quickCheck.image, fallbackText(quickCheck.imageAlt, 'Preisverleihung Bühne'))}
|
||||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: isMobile ? 'center right' : 'center center' }}
|
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: isMobile ? 'center right' : 'center center' }}
|
||||||
/>
|
/>
|
||||||
{/* Overlay: mobile = navy shadow bottom-left (for the heading); desktop = leftward fade into text bg */}
|
{/* Overlay: mobile = navy shadow bottom-left (for the heading); desktop = leftward fade into text bg */}
|
||||||
@@ -270,9 +338,9 @@ const Home: React.FC = () => {
|
|||||||
{/* Heading overlay – mobile only */}
|
{/* Heading overlay – mobile only */}
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, padding: '0 24px 26px', zIndex: 2 }}>
|
<div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, padding: '0 24px 26px', zIndex: 2 }}>
|
||||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, color: '#8FBEEC', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 12 }}>Schnell-Check</span>
|
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, color: '#8FBEEC', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 12 }}>{fallbackText(quickCheck.eyebrow, 'Schnell-Check')}</span>
|
||||||
<h2 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(1.9rem, 8vw, 2.5rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: 0, textShadow: '0 2px 18px rgba(3,9,58,0.5)' }}>
|
<h2 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(1.9rem, 8vw, 2.5rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: 0, textShadow: '0 2px 18px rgba(3,9,58,0.5)' }}>
|
||||||
SIND SIE<br />BERECHTIGT?
|
<Lines text={fallbackText(quickCheck.heading, 'SIND SIE\nBERECHTIGT?')} />
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -289,8 +357,8 @@ const Home: React.FC = () => {
|
|||||||
{/* Left – full-bleed image with stats overlay (+ heading on mobile) */}
|
{/* Left – full-bleed image with stats overlay (+ heading on mobile) */}
|
||||||
<div style={{ position: 'relative', minHeight: isMobile ? 300 : 600, overflow: 'hidden' }}>
|
<div style={{ position: 'relative', minHeight: isMobile ? 300 : 600, overflow: 'hidden' }}>
|
||||||
<Image unoptimized
|
<Image unoptimized
|
||||||
src="/images/preistraeger-jubel.jpg"
|
src={mediaUrl(intro.image, '/images/preistraeger-jubel.jpg')}
|
||||||
alt="BMP Auszeichnung"
|
alt={mediaAlt(intro.image, fallbackText(intro.imageAlt, 'BMP Auszeichnung'))}
|
||||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center top' }}
|
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center top' }}
|
||||||
/>
|
/>
|
||||||
{/* Dark overlay */}
|
{/* Dark overlay */}
|
||||||
@@ -300,16 +368,16 @@ const Home: React.FC = () => {
|
|||||||
{/* Heading overlay – mobile only (bottom-left) */}
|
{/* Heading overlay – mobile only (bottom-left) */}
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, padding: '0 24px 26px', zIndex: 2 }}>
|
<div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, padding: '0 24px 26px', zIndex: 2 }}>
|
||||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, color: '#8FBEEC', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 12 }}>Ein Gewinn für alle</span>
|
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, color: '#8FBEEC', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 12 }}>{fallbackText(intro.eyebrow, 'Ein Gewinn für alle')}</span>
|
||||||
<h2 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(1.9rem, 8vw, 2.5rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: 0, textShadow: '0 2px 18px rgba(3,9,58,0.5)' }}>
|
<h2 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(1.9rem, 8vw, 2.5rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: 0, textShadow: '0 2px 18px rgba(3,9,58,0.5)' }}>
|
||||||
AUSZEICHNUNG<br />FÜR DIE BESTEN.
|
<Lines text={fallbackText(intro.heading, 'AUSZEICHNUNG\nFÜR DIE BESTEN.')} />
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Stats bottom-left – desktop only (mobile shows them below the image) */}
|
{/* Stats bottom-left – desktop only (mobile shows them below the image) */}
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, padding: '32px 40px', background: 'linear-gradient(to top, rgba(3,9,58,0.88) 0%, transparent 100%)', display: 'flex', gap: 36 }}>
|
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, padding: '32px 40px', background: 'linear-gradient(to top, rgba(3,9,58,0.88) 0%, transparent 100%)', display: 'flex', gap: 36 }}>
|
||||||
{[['20+', 'Jahre Exzellenz'], ['0 €', 'Teilnahmegebühr'], ['100%', 'Unabhängig']].map(([v, l]) => (
|
{introStats.map(({ value: v, label: l }) => (
|
||||||
<div key={l}>
|
<div key={l}>
|
||||||
<div style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 28, fontWeight: 900, color: '#EFBF04', lineHeight: 1 }}>{v}</div>
|
<div style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 28, fontWeight: 900, color: '#EFBF04', lineHeight: 1 }}>{v}</div>
|
||||||
<div style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 9, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.6)', marginTop: 4 }}>{l}</div>
|
<div style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 9, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.6)', marginTop: 4 }}>{l}</div>
|
||||||
@@ -323,16 +391,16 @@ const Home: React.FC = () => {
|
|||||||
<div style={{ background: '#fff', padding: isMobile ? '32px 24px 40px' : '80px 72px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
<div style={{ background: '#fff', padding: isMobile ? '32px 24px 40px' : '80px 72px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<>
|
<>
|
||||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 16 }}>Ein Gewinn für alle</span>
|
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 16 }}>{fallbackText(intro.eyebrow, 'Ein Gewinn für alle')}</span>
|
||||||
<h2 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(2rem, 3.2vw, 2.8rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: '0 0 20px' }}>
|
<h2 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(2rem, 3.2vw, 2.8rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: '0 0 20px' }}>
|
||||||
AUSZEICHNUNG<br />FÜR DIE BESTEN.
|
<Lines text={fallbackText(intro.heading, 'AUSZEICHNUNG\nFÜR DIE BESTEN.')} />
|
||||||
</h2>
|
</h2>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{/* Stats row – mobile only, below the image */}
|
{/* Stats row – mobile only, below the image */}
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<div style={{ display: 'flex', gap: 16, marginBottom: 28, paddingBottom: 24, borderBottom: '1px solid #D0D5DD' }}>
|
<div style={{ display: 'flex', gap: 16, marginBottom: 28, paddingBottom: 24, borderBottom: '1px solid #D0D5DD' }}>
|
||||||
{[['20+', 'Jahre Exzellenz'], ['0 €', 'Teilnahmegebühr'], ['100%', 'Unabhängig']].map(([v, l]) => (
|
{introStats.map(({ value: v, label: l }) => (
|
||||||
<div key={l} style={{ flex: 1 }}>
|
<div key={l} style={{ flex: 1 }}>
|
||||||
<div style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 26, fontWeight: 900, color: '#101828', lineHeight: 1 }}>{v}</div>
|
<div style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 26, fontWeight: 900, color: '#101828', lineHeight: 1 }}>{v}</div>
|
||||||
<div style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 9, fontWeight: 700, letterSpacing: '0.16em', textTransform: 'uppercase', color: '#4A8FC9', marginTop: 6 }}>{l}</div>
|
<div style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 9, fontWeight: 700, letterSpacing: '0.16em', textTransform: 'uppercase', color: '#4A8FC9', marginTop: 6 }}>{l}</div>
|
||||||
@@ -342,40 +410,40 @@ const Home: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
<div style={{ width: 40, height: 2, background: '#EFBF04', marginBottom: 28, display: isMobile ? 'none' : 'block' }} />
|
<div style={{ width: 40, height: 2, background: '#EFBF04', marginBottom: 28, display: isMobile ? 'none' : 'block' }} />
|
||||||
<blockquote style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 18, fontStyle: 'italic', color: '#101828', lineHeight: 1.7, borderLeft: '3px solid #EFBF04', paddingLeft: 20, margin: '0 0 24px' }}>
|
<blockquote style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 18, fontStyle: 'italic', color: '#101828', lineHeight: 1.7, borderLeft: '3px solid #EFBF04', paddingLeft: 20, margin: '0 0 24px' }}>
|
||||||
„Ein Unternehmen zu gründen erfordert nicht allein spezielle Fähigkeiten. Es erfordert Persönlichkeit!“
|
{fallbackText(intro.quote, '„Ein Unternehmen zu gründen erfordert nicht allein spezielle Fähigkeiten. Es erfordert Persönlichkeit!“')}
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<p style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 17, color: 'rgba(16,24,40,0.6)', lineHeight: 1.7, margin: '0 0 36px' }}>
|
<p style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 17, color: 'rgba(16,24,40,0.6)', lineHeight: 1.7, margin: '0 0 36px' }}>
|
||||||
Wir suchen Vorbilder, die sich trotz aller Risiken nicht scheuen, Visionen in Businesspläne und Ideen in Unternehmungen zu verwandeln, und das mit großem persönlichen Einsatz und Verantwortung für Ihre Mitarbeiter. Seit vielen Jahren würdigen wir herausragende unternehmerische Leistungen im Freistaat.
|
{fallbackText(intro.body, 'Wir suchen Vorbilder, die sich trotz aller Risiken nicht scheuen, Visionen in Businesspläne und Ideen in Unternehmungen zu verwandeln, und das mit großem persönlichen Einsatz und Verantwortung für Ihre Mitarbeiter. Seit vielen Jahren würdigen wir herausragende unternehmerische Leistungen im Freistaat.')}
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
to="/der-bmp"
|
to={fallbackText(intro.cta?.url, '/der-bmp')}
|
||||||
style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 15, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', color: '#101828', background: '#EFBF04', padding: '14px 28px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8, alignSelf: isMobile ? 'stretch' : 'flex-start', transition: 'background 0.15s, box-shadow 0.2s' }}
|
style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 15, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', color: '#101828', background: '#EFBF04', padding: '14px 28px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8, alignSelf: isMobile ? 'stretch' : 'flex-start', transition: 'background 0.15s, box-shadow 0.2s' }}
|
||||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#FFD130'; (e.currentTarget as HTMLElement).style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#FFD130'; (e.currentTarget as HTMLElement).style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
||||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#EFBF04'; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }}
|
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#EFBF04'; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }}
|
||||||
>
|
>
|
||||||
Mehr über den Preis <ChevronRight size={14} />
|
{fallbackText(intro.cta?.label, 'Mehr über den Preis')} <ChevronRight size={14} />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Status Phase Slider */}
|
{/* Status Phase Slider */}
|
||||||
<StatusSlider />
|
<StatusSlider data={home.status} />
|
||||||
|
|
||||||
{/* Winners Grid – neue Preisträger-Übersicht */}
|
{/* Winners Grid – neue Preisträger-Übersicht */}
|
||||||
<section style={{ background: '#111D55' }}>
|
<section style={{ background: '#111D55' }}>
|
||||||
<div style={{ padding: isMobile ? '48px 24px 32px' : '72px 80px 48px', display: 'flex', flexDirection: isMobile ? 'column' : 'row', alignItems: isMobile ? 'flex-start' : 'flex-end', justifyContent: 'space-between', gap: isMobile ? 16 : 0 }}>
|
<div style={{ padding: isMobile ? '48px 24px 32px' : '72px 80px 48px', display: 'flex', flexDirection: isMobile ? 'column' : 'row', alignItems: isMobile ? 'flex-start' : 'flex-end', justifyContent: 'space-between', gap: isMobile ? 16 : 0 }}>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: '#EFBF04', display: 'block', marginBottom: 12 }}>Success Stories</span>
|
<span style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 10, fontWeight: 700, letterSpacing: '0.3em', textTransform: 'uppercase', color: '#EFBF04', display: 'block', marginBottom: 12 }}>{fallbackText(winnersSection.eyebrow, 'Success Stories')}</span>
|
||||||
<h2 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(2rem, 4vw, 3rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', margin: 0, lineHeight: 1 }}>
|
<h2 style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 'clamp(2rem, 4vw, 3rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', margin: 0, lineHeight: 1 }}>
|
||||||
DIE BESTEN UNTERNEHMEN IN BAYERN
|
<Lines text={fallbackText(winnersSection.heading, 'DIE BESTEN UNTERNEHMEN IN BAYERN')} />
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
to="/preistraeger"
|
to={fallbackText(winnersSection.cta?.url, '/preistraeger')}
|
||||||
style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 15, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#EFBF04', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 6, paddingBottom: 4, borderBottom: '1px solid #EFBF04', whiteSpace: 'nowrap' }}
|
style={{ fontFamily: '"IBM Plex Sans", sans-serif', fontSize: 15, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#EFBF04', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 6, paddingBottom: 4, borderBottom: '1px solid #EFBF04', whiteSpace: 'nowrap' }}
|
||||||
>
|
>
|
||||||
Alle Preisträger <ChevronRight size={14} />
|
{fallbackText(winnersSection.cta?.label, 'Alle Preisträger')} <ChevronRight size={14} />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -392,14 +460,14 @@ const Home: React.FC = () => {
|
|||||||
WebkitOverflowScrolling: 'touch',
|
WebkitOverflowScrolling: 'touch',
|
||||||
scrollbarWidth: 'none',
|
scrollbarWidth: 'none',
|
||||||
}}>
|
}}>
|
||||||
{WINNERS.filter(w => w.year === 2025).slice(0, 8).map(w => (
|
{homeWinners.map((w: any) => (
|
||||||
<HomeWinnerCard key={w.id} winner={w} />
|
<HomeWinnerCard key={w.id || w.slug} winner={w} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Testimonials Section */}
|
{/* Testimonials Section */}
|
||||||
<TestimonialsSection />
|
<TestimonialsSection data={home.testimonials} />
|
||||||
|
|
||||||
|
|
||||||
{/* Benefits Section – editorial split */}
|
{/* Benefits Section – editorial split */}
|
||||||
@@ -411,20 +479,16 @@ const Home: React.FC = () => {
|
|||||||
<div style={{ background: '#E4E2E3', padding: isMobile ? '32px 24px 44px' : '80px 72px', display: 'flex', flexDirection: 'column', justifyContent: 'center', order: isMobile ? 2 : 0 }}>
|
<div style={{ background: '#E4E2E3', padding: isMobile ? '32px 24px 44px' : '80px 72px', display: 'flex', flexDirection: 'column', justifyContent: 'center', order: isMobile ? 2 : 0 }}>
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<>
|
<>
|
||||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 16 }}>Mehr als nur ein Preis</span>
|
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 16 }}>{fallbackText(benefits.eyebrow, 'Mehr als nur ein Preis')}</span>
|
||||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 3vw, 2.6rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: '0 0 48px' }}>
|
<h2 style={{ fontFamily: FF, fontSize: 'clamp(2rem, 3vw, 2.6rem)', fontWeight: 900, color: '#101828', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: '0 0 48px' }}>
|
||||||
WARUM SICH DIE<br />TEILNAHME LOHNT.
|
<Lines text={fallbackText(benefits.heading, 'WARUM SICH DIE\nTEILNAHME LOHNT.')} />
|
||||||
</h2>
|
</h2>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Numbered rows – no cards, no shadows */}
|
{/* Numbered rows – no cards, no shadows */}
|
||||||
<div style={{ borderTop: '1px solid #D0D5DD' }}>
|
<div style={{ borderTop: '1px solid #D0D5DD' }}>
|
||||||
{[
|
{benefitItems.map((item, i) => (
|
||||||
{ num: '01', title: 'Netzwerk', desc: 'Direkter Zugang zu Entscheidern aus Staatsregierung, Wirtschaft und Wissenschaft.' },
|
|
||||||
{ num: '02', title: 'Sichtbarkeit', desc: 'Breite mediale Begleitung durch TV-, Radio- und Print-Partner bayernweit.' },
|
|
||||||
{ num: '03', title: 'Bestätigung', desc: 'Ein unabhängiger Qualitätsnachweis – das stärkste Siegel für Ihren Marktauftritt.' },
|
|
||||||
].map((item, i) => (
|
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
style={{ display: 'grid', gridTemplateColumns: '48px 1fr', gap: '0 20px', padding: '24px 0', borderBottom: '1px solid #D0D5DD', cursor: 'default', transition: 'background 0.15s' }}
|
style={{ display: 'grid', gridTemplateColumns: '48px 1fr', gap: '0 20px', padding: '24px 0', borderBottom: '1px solid #D0D5DD', cursor: 'default', transition: 'background 0.15s' }}
|
||||||
@@ -444,8 +508,8 @@ const Home: React.FC = () => {
|
|||||||
{/* Right – full-bleed photo (+ heading on mobile) */}
|
{/* Right – full-bleed photo (+ heading on mobile) */}
|
||||||
<div style={{ position: 'relative', overflow: 'hidden', minHeight: isMobile ? 280 : 'auto', order: isMobile ? 1 : 0 }}>
|
<div style={{ position: 'relative', overflow: 'hidden', minHeight: isMobile ? 280 : 'auto', order: isMobile ? 1 : 0 }}>
|
||||||
<Image unoptimized
|
<Image unoptimized
|
||||||
src="/images/networking-innenhof.jpg"
|
src={mediaUrl(benefits.image, '/images/networking-innenhof.jpg')}
|
||||||
alt="BMP Gala Netzwerk"
|
alt={mediaAlt(benefits.image, fallbackText(benefits.imageAlt, 'BMP Gala Netzwerk'))}
|
||||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center center', display: 'block' }}
|
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center center', display: 'block' }}
|
||||||
/>
|
/>
|
||||||
<div style={{ position: 'absolute', inset: 0, background: isMobile
|
<div style={{ position: 'absolute', inset: 0, background: isMobile
|
||||||
@@ -454,16 +518,16 @@ const Home: React.FC = () => {
|
|||||||
{/* Heading overlay – mobile only (bottom-left) */}
|
{/* Heading overlay – mobile only (bottom-left) */}
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, padding: '0 24px 26px', zIndex: 2 }}>
|
<div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, padding: '0 24px 26px', zIndex: 2 }}>
|
||||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#8FBEEC', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 12 }}>Mehr als nur ein Preis</span>
|
<span style={{ fontFamily: FF, fontSize: 10, color: '#8FBEEC', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 12 }}>{fallbackText(benefits.eyebrow, 'Mehr als nur ein Preis')}</span>
|
||||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.9rem, 8vw, 2.4rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: 0, textShadow: '0 2px 18px rgba(3,9,58,0.5)' }}>
|
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.9rem, 8vw, 2.4rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.05, margin: 0, textShadow: '0 2px 18px rgba(3,9,58,0.5)' }}>
|
||||||
WARUM SICH DIE<br />TEILNAHME LOHNT.
|
<Lines text={fallbackText(benefits.heading, 'WARUM SICH DIE\nTEILNAHME LOHNT.')} />
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Bottom-left label – desktop only */}
|
{/* Bottom-left label – desktop only */}
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<div style={{ position: 'absolute', bottom: 40, left: 40 }}>
|
<div style={{ position: 'absolute', bottom: 40, left: 40 }}>
|
||||||
<div style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: '#EFBF04', marginBottom: 6 }}>BMP Gala-Netzwerk</div>
|
<div style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: '#EFBF04', marginBottom: 6 }}>{fallbackText(benefits.imageLabel, 'BMP Gala-Netzwerk')}</div>
|
||||||
<div style={{ width: 32, height: 2, background: '#EFBF04' }} />
|
<div style={{ width: 32, height: 2, background: '#EFBF04' }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -480,23 +544,18 @@ const Home: React.FC = () => {
|
|||||||
|
|
||||||
{/* Left – pitch copy */}
|
{/* Left – pitch copy */}
|
||||||
<div style={{ padding: isMobile ? '32px 24px 24px' : '36px 36px 32px 52px', display: 'flex', flexDirection: 'column', justifyContent: 'center', overflow: 'hidden' }}>
|
<div style={{ padding: isMobile ? '32px 24px 24px' : '36px 36px 32px 52px', display: 'flex', flexDirection: 'column', justifyContent: 'center', overflow: 'hidden' }}>
|
||||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#EFBF04', textTransform: 'uppercase', letterSpacing: '0.36em', fontWeight: 700, display: 'block', marginBottom: 16, opacity: 0.8 }}>Chance ergreifen</span>
|
<span style={{ fontFamily: FF, fontSize: 10, color: '#EFBF04', textTransform: 'uppercase', letterSpacing: '0.36em', fontWeight: 700, display: 'block', marginBottom: 16, opacity: 0.8 }}>{fallbackText(application.eyebrow, 'Chance ergreifen')}</span>
|
||||||
<h2 style={{ fontFamily: FF, fontSize: isMobile ? 'clamp(1.8rem, 8vw, 2.6rem)' : 'clamp(1.8rem, 2.8vw, 2.6rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.03em', lineHeight: 1.0, marginBottom: 16 }}>
|
<h2 style={{ fontFamily: FF, fontSize: isMobile ? 'clamp(1.8rem, 8vw, 2.6rem)' : 'clamp(1.8rem, 2.8vw, 2.6rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.03em', lineHeight: 1.0, marginBottom: 16 }}>
|
||||||
SCHREIBEN<br />AUCH SIE<br />GESCHICHTE.
|
<Lines text={fallbackText(application.heading, 'SCHREIBEN\nAUCH SIE\nGESCHICHTE.')} />
|
||||||
</h2>
|
</h2>
|
||||||
<div style={{ width: 32, height: 2, background: '#EFBF04', marginBottom: 20 }} />
|
<div style={{ width: 32, height: 2, background: '#EFBF04', marginBottom: 20 }} />
|
||||||
<p style={{ fontFamily: FF, fontSize: 15, color: 'rgba(255,255,255,0.6)', lineHeight: 1.7, marginBottom: 32 }}>
|
<p style={{ fontFamily: FF, fontSize: 15, color: 'rgba(255,255,255,0.6)', lineHeight: 1.7, marginBottom: 32 }}>
|
||||||
Werden Sie Teil der Wall of Fame des bayerischen Mittelstands – vollständig kostenfrei.
|
{fallbackText(application.body, 'Werden Sie Teil der Wall of Fame des bayerischen Mittelstands – vollständig kostenfrei.')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Benefit rows */}
|
{/* Benefit rows */}
|
||||||
<div style={{ borderTop: '1px solid rgba(255,255,255,0.08)' }}>
|
<div style={{ borderTop: '1px solid rgba(255,255,255,0.08)' }}>
|
||||||
{[
|
{applicationBenefits.map(item => (
|
||||||
{ num: '01', label: 'Kostenfrei', desc: 'Ohne Teilnahmegebühr' },
|
|
||||||
{ num: '02', label: 'Jury', desc: 'Aus Wissenschaft und Praxis' },
|
|
||||||
{ num: '03', label: 'Sichtbarkeit', desc: 'Medial bayernweit' },
|
|
||||||
{ num: '04', label: 'Gala', desc: 'Verleihung München' },
|
|
||||||
].map(item => (
|
|
||||||
<div key={item.num} style={{ display: 'grid', gridTemplateColumns: '24px 1fr', gap: '0 14px', padding: '13px 0', borderBottom: '1px solid rgba(255,255,255,0.06)', alignItems: 'center' }}>
|
<div key={item.num} style={{ display: 'grid', gridTemplateColumns: '24px 1fr', gap: '0 14px', padding: '13px 0', borderBottom: '1px solid rgba(255,255,255,0.06)', alignItems: 'center' }}>
|
||||||
<span style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, color: 'rgba(239,191,4,0.55)', letterSpacing: '0.1em' }}>{item.num}</span>
|
<span style={{ fontFamily: FF, fontSize: 9, fontWeight: 700, color: 'rgba(239,191,4,0.55)', letterSpacing: '0.1em' }}>{item.num}</span>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 8 }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 8 }}>
|
||||||
@@ -531,8 +590,8 @@ const Home: 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
|
<Image unoptimized
|
||||||
src="/images/preisuebergabe.jpg"
|
src={mediaUrl(application.awardImage, '/images/preisuebergabe.jpg')}
|
||||||
alt="Preisverleihung Bayerischer Mittelstandspreis"
|
alt={mediaAlt(application.awardImage, fallbackText(application.awardImageAlt, 'Preisverleihung Bayerischer Mittelstandspreis'))}
|
||||||
style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 25%', filter: 'sepia(0.18) brightness(0.92)' }}
|
style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 25%', filter: 'sepia(0.18) brightness(0.92)' }}
|
||||||
/>
|
/>
|
||||||
{/* Blend photo into gold panel */}
|
{/* Blend photo into gold panel */}
|
||||||
@@ -540,15 +599,15 @@ const Home: React.FC = () => {
|
|||||||
<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' }}>Preisverleihung 2025</span>
|
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: 'rgba(17,29,85,0.75)', textTransform: 'uppercase', letterSpacing: '0.2em' }}>{fallbackText(application.awardCaption, 'Preisverleihung 2025')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Form area */}
|
{/* Form area */}
|
||||||
<div style={{ padding: isMobile ? '32px 24px' : '24px 48px 32px 40px', flex: isMobile ? 'none' : 1, height: isMobile ? '82svh' : undefined, display: 'flex', flexDirection: 'column', minHeight: 0, position: 'relative', zIndex: 1 }}>
|
<div style={{ padding: isMobile ? '32px 24px' : '24px 48px 32px 40px', flex: isMobile ? 'none' : 1, height: isMobile ? '82svh' : undefined, display: 'flex', flexDirection: 'column', minHeight: 0, position: 'relative', zIndex: 1 }}>
|
||||||
<span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(17,29,85,0.5)', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 8 }}>Direktbewerbung 2026</span>
|
<span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(17,29,85,0.5)', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 8 }}>{fallbackText(application.formEyebrow, 'Direktbewerbung 2026')}</span>
|
||||||
<h3 style={{ fontFamily: FF, fontSize: 'clamp(1.2rem, 1.8vw, 1.6rem)', fontWeight: 900, color: '#111D55', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.1, marginBottom: 24 }}>Kostenlos bewerben</h3>
|
<h3 style={{ fontFamily: FF, fontSize: 'clamp(1.2rem, 1.8vw, 1.6rem)', fontWeight: 900, color: '#111D55', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.1, marginBottom: 24 }}>{fallbackText(application.formTitle, 'Kostenlos bewerben')}</h3>
|
||||||
<BewerbungsForm theme="gold" />
|
<BewerbungsForm theme="gold" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -557,8 +616,8 @@ const Home: React.FC = () => {
|
|||||||
|
|
||||||
{/* Footnote row */}
|
{/* Footnote row */}
|
||||||
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '16px 24px 20px' : '14px 56px 18px', display: 'flex', flexWrap: 'wrap', justifyContent: 'center', textAlign: 'center', borderTop: '1px solid rgba(255,255,255,0.06)', flexShrink: 0 }}>
|
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '16px 24px 20px' : '14px 56px 18px', display: 'flex', flexWrap: 'wrap', justifyContent: 'center', textAlign: 'center', borderTop: '1px solid rgba(255,255,255,0.06)', flexShrink: 0 }}>
|
||||||
<span style={{ fontFamily: FF, fontSize: 15, color: 'rgba(255,255,255,0.45)', marginRight: isMobile ? 6 : 10 }}>Kostenlos und unverbindlich –</span>
|
<span style={{ fontFamily: FF, fontSize: 15, color: 'rgba(255,255,255,0.45)', marginRight: isMobile ? 6 : 10 }}>{fallbackText(application.footnote, 'Kostenlos und unverbindlich –')}</span>
|
||||||
<span style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, color: 'rgba(239,191,4,0.8)' }}>keine Teilnahmegebühr</span>
|
<span style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, color: 'rgba(239,191,4,0.8)' }}>{fallbackText(application.footnoteStrong, 'keine Teilnahmegebühr')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ height: 2, background: 'linear-gradient(to right, #EFBF04, rgba(239,191,4,0.25), transparent)', flexShrink: 0 }} />
|
<div style={{ height: 2, background: 'linear-gradient(to right, #EFBF04, rgba(239,191,4,0.25), transparent)', flexShrink: 0 }} />
|
||||||
</section>
|
</section>
|
||||||
@@ -575,9 +634,9 @@ const Home: React.FC = () => {
|
|||||||
<div className="w-full max-w-6xl aspect-video bg-white/5 rounded-3xl overflow-hidden flex items-center justify-center relative">
|
<div className="w-full max-w-6xl aspect-video bg-white/5 rounded-3xl overflow-hidden flex items-center justify-center relative">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Play size={80} className="text-accent mx-auto mb-6 opacity-40" />
|
<Play size={80} className="text-accent mx-auto mb-6 opacity-40" />
|
||||||
<p className="text-white/40 uppercase tracking-widest text-sm">Video-Player Platzhalter</p>
|
<p className="text-white/40 uppercase tracking-widest text-sm">{fallbackText(videoModal.title, 'Video-Player Platzhalter')}</p>
|
||||||
<p className="text-white/60 mt-4 text-xs italic font-body">Hier würde das Image-Video des BMP geladen werden.</p>
|
<p className="text-white/60 mt-4 text-xs italic font-body">{fallbackText(videoModal.description, 'Hier würde das Image-Video des BMP geladen werden.')}</p>
|
||||||
<Button onClick={() => setShowVideo(false)} className="mt-8" variant="outline">Schließen</Button>
|
<Button onClick={() => setShowVideo(false)} className="mt-8" variant="outline">{fallbackText(videoModal.closeLabel, 'Schließen')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -588,41 +647,22 @@ const Home: React.FC = () => {
|
|||||||
|
|
||||||
// ─── StatusSlider ─────────────────────────────────────────────────────────────
|
// ─── StatusSlider ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function StatusSlider() {
|
function StatusSlider({ data }: { data?: HomeCms }) {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const [active, setActive] = useState<number>(ACTIVE_PHASE);
|
const phases = fallbackArray(data?.phases, STATUS_PHASES).map((phase: any, i) => {
|
||||||
const [dragOffset, setDragOffset] = useState(0);
|
const fallbackPhase = STATUS_PHASES[i]
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const cta = phase?.cta?.label
|
||||||
const startX = useRef<number | null>(null);
|
? { label: phase.cta.label, to: phase.cta.to || phase.cta.url || fallbackPhase?.cta?.to || '/' }
|
||||||
const lastDelta = useRef(0);
|
: fallbackPhase?.cta
|
||||||
|
const membershipCta = phase?.membershipCta?.label
|
||||||
|
? { label: phase.membershipCta.label, to: phase.membershipCta.to || phase.membershipCta.url || '/mitglied-werden' }
|
||||||
|
: undefined
|
||||||
|
|
||||||
// Swipe hint on mount: peek next slide briefly, then bounce back
|
return { ...fallbackPhase, ...phase, cta, membershipCta }
|
||||||
useEffect(() => {
|
});
|
||||||
const peek = setTimeout(() => setDragOffset(-Math.round(window.innerWidth * 0.11)), 900);
|
const configuredActive = data?.activePhase !== undefined && data?.activePhase !== null ? Number(data.activePhase) : ACTIVE_PHASE;
|
||||||
const back = setTimeout(() => setDragOffset(0), 1700);
|
const active = Math.max(0, Math.min(phases.length - 1, Number.isFinite(configuredActive) ? configuredActive : ACTIVE_PHASE));
|
||||||
return () => { clearTimeout(peek); clearTimeout(back); };
|
const phase = phases[active];
|
||||||
}, []);
|
|
||||||
|
|
||||||
const goTo = (i: number) => setActive(Math.max(0, Math.min(STATUS_PHASES.length - 1, i)));
|
|
||||||
|
|
||||||
const onStart = (x: number) => { startX.current = x; lastDelta.current = 0; setIsDragging(true); };
|
|
||||||
const onMove = (x: number) => {
|
|
||||||
if (startX.current === null) return;
|
|
||||||
const d = x - startX.current;
|
|
||||||
lastDelta.current = d;
|
|
||||||
setDragOffset(d);
|
|
||||||
};
|
|
||||||
const onEnd = () => {
|
|
||||||
if (startX.current === null) return;
|
|
||||||
const d = lastDelta.current;
|
|
||||||
startX.current = null;
|
|
||||||
setIsDragging(false);
|
|
||||||
setDragOffset(0);
|
|
||||||
if (d < -60 && active < STATUS_PHASES.length - 1) goTo(active + 1);
|
|
||||||
else if (d > 60 && active > 0) goTo(active - 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const curPhase = STATUS_PHASES[active];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
@@ -631,80 +671,16 @@ function StatusSlider() {
|
|||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
height: isMobile ? 'auto' : 480,
|
height: isMobile ? 'auto' : 480,
|
||||||
minHeight: isMobile ? 360 : 'auto',
|
minHeight: isMobile ? 360 : 'auto',
|
||||||
cursor: isDragging ? 'grabbing' : 'grab',
|
|
||||||
userSelect: 'none',
|
|
||||||
}}
|
}}
|
||||||
onMouseDown={e => onStart(e.clientX)}
|
|
||||||
onMouseMove={e => onMove(e.clientX)}
|
|
||||||
onMouseUp={onEnd}
|
|
||||||
onMouseLeave={onEnd}
|
|
||||||
onTouchStart={e => onStart(e.touches[0].clientX)}
|
|
||||||
onTouchMove={e => onMove(e.touches[0].clientX)}
|
|
||||||
onTouchEnd={onEnd}
|
|
||||||
>
|
>
|
||||||
{/* Track */}
|
<StatusSlideCard phase={phase} isMobile={isMobile} noActionLabel={fallbackText(data?.noActionLabel, 'Kein Handlungsbedarf')} />
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
height: '100%',
|
|
||||||
width: `${STATUS_PHASES.length * 100}vw`,
|
|
||||||
transform: `translateX(calc(${-active * 100}vw + ${dragOffset}px))`,
|
|
||||||
transition: isDragging ? 'none' : 'transform 0.62s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
|
|
||||||
willChange: 'transform',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{STATUS_PHASES.map(phase => (
|
|
||||||
<StatusSlideCard key={phase.num} phase={phase} isMobile={isMobile} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Dots */}
|
|
||||||
<div
|
|
||||||
style={{ position: 'absolute', bottom: 28, left: '50%', transform: 'translateX(-50%)', display: 'flex', gap: 6, zIndex: 10 }}
|
|
||||||
onMouseDown={e => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
{STATUS_PHASES.map((_, i) => (
|
|
||||||
<button
|
|
||||||
key={i}
|
|
||||||
onClick={() => goTo(i)}
|
|
||||||
style={{
|
|
||||||
width: i === active ? 32 : 8,
|
|
||||||
height: 8,
|
|
||||||
borderRadius: 4,
|
|
||||||
background: i === active ? curPhase.accent : 'rgba(255,255,255,0.22)',
|
|
||||||
border: 'none',
|
|
||||||
cursor: 'pointer',
|
|
||||||
padding: 0,
|
|
||||||
transition: 'all 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Prev arrow */}
|
|
||||||
{active > 0 && (
|
|
||||||
<button
|
|
||||||
onMouseDown={e => e.stopPropagation()}
|
|
||||||
onClick={() => goTo(active - 1)}
|
|
||||||
style={{ position: 'absolute', left: 28, top: '50%', transform: 'translateY(-50%)', zIndex: 10, background: 'rgba(255,255,255,0.08)', backdropFilter: 'blur(8px)', border: '1px solid rgba(255,255,255,0.15)', borderRadius: '50%', width: 48, height: 48, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', color: '#fff', fontSize: 22, lineHeight: 1, transition: 'background 0.2s' }}
|
|
||||||
>‹</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Next arrow */}
|
|
||||||
{active < STATUS_PHASES.length - 1 && (
|
|
||||||
<button
|
|
||||||
onMouseDown={e => e.stopPropagation()}
|
|
||||||
onClick={() => goTo(active + 1)}
|
|
||||||
style={{ position: 'absolute', right: 28, top: '50%', transform: 'translateY(-50%)', zIndex: 10, background: 'rgba(255,255,255,0.08)', backdropFilter: 'blur(8px)', border: '1px solid rgba(255,255,255,0.15)', borderRadius: '50%', width: 48, height: 48, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', color: '#fff', fontSize: 22, lineHeight: 1, transition: 'background 0.2s' }}
|
|
||||||
>›</button>
|
|
||||||
)}
|
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── StatusSlideCard ──────────────────────────────────────────────────────────
|
// ─── StatusSlideCard ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function StatusSlideCard({ phase, isMobile }: { phase: StatusPhase; isMobile: boolean }) {
|
function StatusSlideCard({ phase, isMobile, noActionLabel }: { phase: StatusPhase & HomeCms; isMobile: boolean; noActionLabel: string }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -752,7 +728,7 @@ function StatusSlideCard({ phase, isMobile }: { phase: StatusPhase; isMobile: bo
|
|||||||
) : (
|
) : (
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||||
<div style={{ width: 20, height: 1, background: 'rgba(255,255,255,0.2)' }} />
|
<div style={{ width: 20, height: 1, background: 'rgba(255,255,255,0.2)' }} />
|
||||||
<span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(255,255,255,0.50)', letterSpacing: '0.2em', textTransform: 'uppercase' }}>Kein Handlungsbedarf</span>
|
<span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(255,255,255,0.50)', letterSpacing: '0.2em', textTransform: 'uppercase' }}>{noActionLabel}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -779,10 +755,10 @@ function StatusSlideCard({ phase, isMobile }: { phase: StatusPhase; isMobile: bo
|
|||||||
) : (
|
) : (
|
||||||
/* Success stats */
|
/* Success stats */
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<div style={{ fontFamily: FF, fontSize: 68, fontWeight: 900, color: phase.accent, lineHeight: 1, opacity: 0.9 }}>247</div>
|
<div style={{ fontFamily: FF, fontSize: 68, fontWeight: 900, color: phase.accent, lineHeight: 1, opacity: 0.9 }}>{fallbackText(phase.successApplications, '247')}</div>
|
||||||
<div style={{ fontFamily: FF, fontSize: 8, fontWeight: 700, letterSpacing: '0.35em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.50)', marginTop: 4, marginBottom: 20 }}>Bewerbungen</div>
|
<div style={{ fontFamily: FF, fontSize: 8, fontWeight: 700, letterSpacing: '0.35em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.50)', marginTop: 4, marginBottom: 20 }}>Bewerbungen</div>
|
||||||
<div style={{ width: 1, height: 28, background: 'rgba(255,255,255,0.08)', margin: '0 auto 20px' }} />
|
<div style={{ width: 1, height: 28, background: 'rgba(255,255,255,0.08)', margin: '0 auto 20px' }} />
|
||||||
<div style={{ fontFamily: FF, fontSize: 44, fontWeight: 900, color: '#fff', lineHeight: 1 }}>3</div>
|
<div style={{ fontFamily: FF, fontSize: 44, fontWeight: 900, color: '#fff', lineHeight: 1 }}>{fallbackText(phase.successWinners, '3')}</div>
|
||||||
<div style={{ fontFamily: FF, fontSize: 8, fontWeight: 700, letterSpacing: '0.35em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.50)', marginTop: 4 }}>Preisträger</div>
|
<div style={{ fontFamily: FF, fontSize: 8, fontWeight: 700, letterSpacing: '0.35em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.50)', marginTop: 4 }}>Preisträger</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -792,7 +768,7 @@ function StatusSlideCard({ phase, isMobile }: { phase: StatusPhase; isMobile: bo
|
|||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<div style={{ width: 1, height: 20, background: 'rgba(255,255,255,0.08)', margin: '0 auto 20px' }} />
|
<div style={{ width: 1, height: 20, background: 'rgba(255,255,255,0.08)', margin: '0 auto 20px' }} />
|
||||||
<Link
|
<Link
|
||||||
to="/mitglied-werden"
|
to={fallbackText(phase.membershipCta?.to || phase.membershipCta?.url, '/mitglied-werden')}
|
||||||
onMouseDown={e => e.stopPropagation()}
|
onMouseDown={e => e.stopPropagation()}
|
||||||
style={{
|
style={{
|
||||||
fontFamily: FF,
|
fontFamily: FF,
|
||||||
@@ -817,7 +793,7 @@ function StatusSlideCard({ phase, isMobile }: { phase: StatusPhase; isMobile: bo
|
|||||||
(e.currentTarget as HTMLElement).style.borderColor = `${phase.accent}55`;
|
(e.currentTarget as HTMLElement).style.borderColor = `${phase.accent}55`;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Mitglied werden <ChevronRight size={13} />
|
{fallbackText(phase.membershipCta?.label, 'Mitglied werden')} <ChevronRight size={13} />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import NextLink from 'next/link'
|
|||||||
import { usePathname, useRouter } from 'next/navigation'
|
import { usePathname, useRouter } from 'next/navigation'
|
||||||
import React, { forwardRef, useEffect, useMemo } from 'react'
|
import React, { forwardRef, useEffect, useMemo } from 'react'
|
||||||
|
|
||||||
type To = string | { pathname?: string; search?: string; hash?: string }
|
type To = string | { pathname?: string; search?: string; hash?: string } | null | undefined
|
||||||
|
|
||||||
type LinkProps = Omit<React.ComponentProps<typeof NextLink>, 'href'> & {
|
type LinkProps = Omit<React.ComponentProps<typeof NextLink>, 'href'> & {
|
||||||
to: To
|
to?: To
|
||||||
}
|
}
|
||||||
|
|
||||||
const toHref = (to: To, pathname = '') => {
|
const toHref = (to: To, pathname = '') => {
|
||||||
|
if (!to) return pathname || '/'
|
||||||
if (typeof to === 'string') {
|
if (typeof to === 'string') {
|
||||||
if (to.startsWith('#')) return `${pathname || '/'}${to}`
|
if (to.startsWith('#')) return `${pathname || '/'}${to}`
|
||||||
return to
|
return to
|
||||||
|
|||||||
1
tsconfig.tsbuildinfo
Normal file
1
tsconfig.tsbuildinfo
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user