feat: payload cms
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { defaultJSXConverters, RichText } from '@payloadcms/richtext-lexical/react';
|
||||
import type { JSXConverters } from '@payloadcms/richtext-lexical/react';
|
||||
import Image from 'next/image';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
// Import all custom React components that were previously mapped via MDX
|
||||
import StickyNarrative from '@/components/blog/StickyNarrative';
|
||||
@@ -16,6 +17,24 @@ import Stats from '@/components/blog/Stats';
|
||||
import SplitHeading from '@/components/blog/SplitHeading';
|
||||
import ProductTabs from '@/components/ProductTabs';
|
||||
import ProductTechnicalData from '@/components/ProductTechnicalData';
|
||||
import ContactForm from '@/components/ContactForm';
|
||||
import ContactMap from '@/components/ContactMap';
|
||||
import Gallery from '@/components/team/Gallery';
|
||||
import Reveal from '@/components/Reveal';
|
||||
import { Badge, Container, Heading, Section, Card } from '@/components/ui';
|
||||
import TrackedLink from '@/components/analytics/TrackedLink';
|
||||
import { useLocale } from 'next-intl';
|
||||
|
||||
import HomeHero from '@/components/home/Hero';
|
||||
import ProductCategories from '@/components/home/ProductCategories';
|
||||
import WhatWeDo from '@/components/home/WhatWeDo';
|
||||
import RecentPosts from '@/components/home/RecentPosts';
|
||||
import Experience from '@/components/home/Experience';
|
||||
import WhyChooseUs from '@/components/home/WhyChooseUs';
|
||||
import MeetTheTeam from '@/components/home/MeetTheTeam';
|
||||
import GallerySection from '@/components/home/GallerySection';
|
||||
import VideoSection from '@/components/home/VideoSection';
|
||||
import CTA from '@/components/home/CTA';
|
||||
|
||||
const jsxConverters: JSXConverters = {
|
||||
...defaultJSXConverters,
|
||||
@@ -255,6 +274,372 @@ const jsxConverters: JSXConverters = {
|
||||
{node.fields.content && <RichText data={node.fields.content} converters={jsxConverters} />}
|
||||
</ProductTabs>
|
||||
),
|
||||
// ─── New Page Blocks ───────────────────────────────────────────
|
||||
heroSection: ({ node }: any) => {
|
||||
const f = node.fields;
|
||||
const bgSrc = f.backgroundImage?.sizes?.card?.url || f.backgroundImage?.url;
|
||||
return (
|
||||
<Reveal>
|
||||
<section className={`relative min-h-[50vh] md:min-h-[70vh] flex items-center pt-32 pb-20 md:pt-40 md:pb-32 overflow-hidden bg-primary-dark ${f.alignment === 'center' ? 'justify-center text-center' : ''}`}>
|
||||
{bgSrc && (
|
||||
<>
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
src={bgSrc}
|
||||
alt={f.title}
|
||||
fill
|
||||
className="object-cover opacity-30 md:opacity-40"
|
||||
style={{ objectPosition: `${f.backgroundImage?.focalX ?? 50}% ${f.backgroundImage?.focalY ?? 50}%` }}
|
||||
sizes="100vw"
|
||||
priority
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-primary-dark/80 via-primary-dark/40 to-primary-dark/80" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<Container className={`relative z-10 ${f.alignment === 'center' ? 'max-w-5xl' : ''}`}>
|
||||
<div className={`max-w-4xl ${f.alignment === 'center' ? 'mx-auto' : ''}`}>
|
||||
{f.badge && <Badge variant="saturated" className="mb-4 md:mb-8 shadow-lg">{f.badge}</Badge>}
|
||||
<Heading level={1} className="text-white mb-4 md:mb-8">{f.title}</Heading>
|
||||
{f.subtitle && <p className="text-lg md:text-2xl text-white/70 font-medium leading-relaxed max-w-2xl">{f.subtitle}</p>}
|
||||
{f.ctaLabel && f.ctaHref && (
|
||||
<div className="mt-8">
|
||||
<a href={f.ctaHref} className="inline-flex items-center px-8 py-4 bg-accent text-primary-dark font-bold rounded-full hover:bg-white transition-all duration-300 group">
|
||||
{f.ctaLabel}
|
||||
<span className="ml-3 transition-transform group-hover:translate-x-2">→</span>
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
</Reveal>
|
||||
);
|
||||
},
|
||||
'block-heroSection': ({ node }: any) => {
|
||||
const f = node.fields;
|
||||
const bgSrc = f.backgroundImage?.sizes?.card?.url || f.backgroundImage?.url;
|
||||
return (
|
||||
<Reveal>
|
||||
<section className={`relative min-h-[50vh] md:min-h-[70vh] flex items-center pt-32 pb-20 md:pt-40 md:pb-32 overflow-hidden bg-primary-dark ${f.alignment === 'center' ? 'justify-center text-center' : ''}`}>
|
||||
{bgSrc && (
|
||||
<>
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image src={bgSrc} alt={f.title} fill className="object-cover opacity-30 md:opacity-40" style={{ objectPosition: `${f.backgroundImage?.focalX ?? 50}% ${f.backgroundImage?.focalY ?? 50}%` }} sizes="100vw" priority />
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-primary-dark/80 via-primary-dark/40 to-primary-dark/80" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<Container className={`relative z-10 ${f.alignment === 'center' ? 'max-w-5xl' : ''}`}>
|
||||
<div className={`max-w-4xl ${f.alignment === 'center' ? 'mx-auto' : ''}`}>
|
||||
{f.badge && <Badge variant="saturated" className="mb-4 md:mb-8 shadow-lg">{f.badge}</Badge>}
|
||||
<Heading level={1} className="text-white mb-4 md:mb-8">{f.title}</Heading>
|
||||
{f.subtitle && <p className="text-lg md:text-2xl text-white/70 font-medium leading-relaxed max-w-2xl">{f.subtitle}</p>}
|
||||
{f.ctaLabel && f.ctaHref && (
|
||||
<div className="mt-8">
|
||||
<a href={f.ctaHref} className="inline-flex items-center px-8 py-4 bg-accent text-primary-dark font-bold rounded-full hover:bg-white transition-all duration-300 group">{f.ctaLabel}<span className="ml-3 transition-transform group-hover:translate-x-2">→</span></a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
</Reveal>
|
||||
);
|
||||
},
|
||||
teamProfile: ({ node }: any) => {
|
||||
const f = node.fields;
|
||||
const imgSrc = f.image?.sizes?.card?.url || f.image?.url;
|
||||
const isDark = f.colorScheme === 'dark';
|
||||
const isImageRight = f.layout === 'imageRight';
|
||||
return (
|
||||
<article className="relative bg-white overflow-hidden">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<Reveal className={`w-full lg:w-1/2 p-6 md:p-24 lg:p-32 flex flex-col justify-center relative ${isImageRight ? 'order-2 lg:order-1' : 'order-2'} ${isDark ? 'bg-primary-dark text-white' : 'bg-neutral-light text-saturated'}`}>
|
||||
<div className="relative z-10">
|
||||
<Badge variant={isDark ? 'accent' : 'saturated'} className="mb-4 md:mb-8">{f.role}</Badge>
|
||||
<Heading level={2} className={`${isDark ? 'text-white' : 'text-saturated'} mb-6 md:mb-10 text-3xl md:text-5xl`}>{f.name}</Heading>
|
||||
{f.quote && (
|
||||
<div className="relative mb-6 md:mb-12">
|
||||
<div className={`absolute -left-4 md:-left-8 top-0 bottom-0 w-1 md:w-1.5 ${isDark ? 'bg-accent' : 'bg-saturated'} rounded-full`} />
|
||||
<p className={`text-lg md:text-2xl font-bold italic leading-relaxed pl-5 md:pl-8 ${isDark ? 'text-white/90' : 'text-text-secondary'}`}>{f.quote}</p>
|
||||
</div>
|
||||
)}
|
||||
{f.description && <p className={`text-base md:text-xl leading-relaxed mb-6 md:mb-12 max-w-xl ${isDark ? 'text-white/70' : 'text-text-secondary'}`}>{f.description}</p>}
|
||||
{f.linkedinUrl && (
|
||||
<TrackedLink
|
||||
href={f.linkedinUrl}
|
||||
className={`inline-flex items-center px-8 py-4 font-bold rounded-full transition-all duration-300 group ${isDark ? 'bg-accent text-primary-dark hover:bg-white' : 'bg-saturated text-white hover:bg-primary'}`}
|
||||
eventProperties={{ type: 'social_linkedin', person: f.name, location: 'team_page' }}
|
||||
>
|
||||
{f.linkedinLabel || 'LinkedIn'}
|
||||
<span className="ml-3 transition-transform group-hover:translate-x-2">→</span>
|
||||
</TrackedLink>
|
||||
)}
|
||||
</div>
|
||||
</Reveal>
|
||||
<Reveal className={`w-full lg:w-1/2 relative min-h-[400px] md:min-h-[600px] lg:min-h-screen overflow-hidden ${isImageRight ? 'order-1 lg:order-2' : 'order-1'}`}>
|
||||
{imgSrc && (
|
||||
<>
|
||||
<Image
|
||||
src={imgSrc}
|
||||
alt={f.name}
|
||||
fill
|
||||
className="object-cover scale-105 hover:scale-100 transition-transform duration-1000"
|
||||
style={{ objectPosition: `${f.image?.focalX ?? 50}% ${f.image?.focalY ?? 50}%` }}
|
||||
sizes="(max-width: 1024px) 100vw, 50vw"
|
||||
/>
|
||||
<div className={`absolute inset-0 ${isDark ? 'bg-gradient-to-t from-primary-dark/60 lg:bg-gradient-to-r lg:from-primary-dark/20 to-transparent' : 'bg-gradient-to-t from-white/60 lg:bg-gradient-to-l lg:from-primary-dark/20 to-transparent'}`} />
|
||||
</>
|
||||
)}
|
||||
</Reveal>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
},
|
||||
'block-teamProfile': ({ node }: any) => {
|
||||
const f = node.fields;
|
||||
const imgSrc = f.image?.sizes?.card?.url || f.image?.url;
|
||||
const isDark = f.colorScheme === 'dark';
|
||||
const isImageRight = f.layout === 'imageRight';
|
||||
return (
|
||||
<article className="relative bg-white overflow-hidden">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<Reveal className={`w-full lg:w-1/2 p-6 md:p-24 lg:p-32 flex flex-col justify-center relative ${isImageRight ? 'order-2 lg:order-1' : 'order-2'} ${isDark ? 'bg-primary-dark text-white' : 'bg-neutral-light text-saturated'}`}>
|
||||
<div className="relative z-10">
|
||||
<Badge variant={isDark ? 'accent' : 'saturated'} className="mb-4 md:mb-8">{f.role}</Badge>
|
||||
<Heading level={2} className={`${isDark ? 'text-white' : 'text-saturated'} mb-6 md:mb-10 text-3xl md:text-5xl`}>{f.name}</Heading>
|
||||
{f.quote && (
|
||||
<div className="relative mb-6 md:mb-12">
|
||||
<div className={`absolute -left-4 md:-left-8 top-0 bottom-0 w-1 md:w-1.5 ${isDark ? 'bg-accent' : 'bg-saturated'} rounded-full`} />
|
||||
<p className={`text-lg md:text-2xl font-bold italic leading-relaxed pl-5 md:pl-8 ${isDark ? 'text-white/90' : 'text-text-secondary'}`}>{f.quote}</p>
|
||||
</div>
|
||||
)}
|
||||
{f.description && <p className={`text-base md:text-xl leading-relaxed mb-6 md:mb-12 max-w-xl ${isDark ? 'text-white/70' : 'text-text-secondary'}`}>{f.description}</p>}
|
||||
{f.linkedinUrl && (
|
||||
<TrackedLink href={f.linkedinUrl} className={`inline-flex items-center px-8 py-4 font-bold rounded-full transition-all duration-300 group ${isDark ? 'bg-accent text-primary-dark hover:bg-white' : 'bg-saturated text-white hover:bg-primary'}`} eventProperties={{ type: 'social_linkedin', person: f.name, location: 'team_page' }}>
|
||||
{f.linkedinLabel || 'LinkedIn'}<span className="ml-3 transition-transform group-hover:translate-x-2">→</span>
|
||||
</TrackedLink>
|
||||
)}
|
||||
</div>
|
||||
</Reveal>
|
||||
<Reveal className={`w-full lg:w-1/2 relative min-h-[400px] md:min-h-[600px] lg:min-h-screen overflow-hidden ${isImageRight ? 'order-1 lg:order-2' : 'order-1'}`}>
|
||||
{imgSrc && (<><Image src={imgSrc} alt={f.name} fill className="object-cover scale-105 hover:scale-100 transition-transform duration-1000" style={{ objectPosition: `${f.image?.focalX ?? 50}% ${f.image?.focalY ?? 50}%` }} sizes="(max-width: 1024px) 100vw, 50vw" /><div className={`absolute inset-0 ${isDark ? 'bg-gradient-to-t from-primary-dark/60 lg:bg-gradient-to-r lg:from-primary-dark/20 to-transparent' : 'bg-gradient-to-t from-white/60 lg:bg-gradient-to-l lg:from-primary-dark/20 to-transparent'}`} /></>)}
|
||||
</Reveal>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
},
|
||||
contactSection: ({ node }: any) => {
|
||||
const f = node.fields;
|
||||
return (
|
||||
<Section className="bg-neutral-light py-12 md:py-28">
|
||||
<Container>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 md:gap-16">
|
||||
{f.showForm && (
|
||||
<div className="lg:col-span-7">
|
||||
<Suspense fallback={<div className="animate-pulse bg-neutral-medium h-96 rounded-2xl md:rounded-3xl" />}>
|
||||
<ContactForm />
|
||||
</Suspense>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
{f.showMap && (
|
||||
<section className="mt-12 h-[300px] md:h-[500px] bg-neutral-medium relative overflow-hidden grayscale hover:grayscale-0 transition-all duration-1000">
|
||||
<Suspense fallback={<div className="h-full w-full bg-neutral-medium animate-pulse" />}>
|
||||
<ContactMap address="Raiffeisenstraße 22\n73630 Remshalden" lat={48.8144} lng={9.4144} />
|
||||
</Suspense>
|
||||
</section>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
},
|
||||
'block-contactSection': ({ node }: any) => {
|
||||
const f = node.fields;
|
||||
return (
|
||||
<Section className="bg-neutral-light py-12 md:py-28">
|
||||
<Container>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 md:gap-16">
|
||||
{f.showForm && (
|
||||
<div className="lg:col-span-7">
|
||||
<Suspense fallback={<div className="animate-pulse bg-neutral-medium h-96 rounded-2xl md:rounded-3xl" />}>
|
||||
<ContactForm />
|
||||
</Suspense>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
{f.showMap && (
|
||||
<section className="mt-12 h-[300px] md:h-[500px] bg-neutral-medium relative overflow-hidden grayscale hover:grayscale-0 transition-all duration-1000">
|
||||
<Suspense fallback={<div className="h-full w-full bg-neutral-medium animate-pulse" />}>
|
||||
<ContactMap address="Raiffeisenstraße 22\n73630 Remshalden" lat={48.8144} lng={9.4144} />
|
||||
</Suspense>
|
||||
</section>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
},
|
||||
imageGallery: ({ node }: any) => <Gallery />,
|
||||
'block-imageGallery': ({ node }: any) => <Gallery />,
|
||||
categoryGrid: ({ node }: any) => {
|
||||
const cats = node.fields.categories || [];
|
||||
return (
|
||||
<Section className="bg-neutral-light relative">
|
||||
<Container>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 md:gap-8 lg:gap-12">
|
||||
{cats.map((cat: any, idx: number) => {
|
||||
const imgSrc = cat.image?.sizes?.card?.url || cat.image?.url;
|
||||
const iconSrc = cat.icon?.url;
|
||||
return (
|
||||
<Reveal key={idx} delay={idx * 100}>
|
||||
<a href={cat.href} className="group block">
|
||||
<Card className="h-full border-none shadow-sm hover:shadow-2xl transition-all duration-500 rounded-[24px] md:rounded-[48px] overflow-hidden bg-white active:scale-[0.98]">
|
||||
<div className="relative h-[200px] md:h-[400px] overflow-hidden">
|
||||
{imgSrc && (
|
||||
<Image src={imgSrc} alt={cat.title} fill className="object-cover transition-transform duration-1000 group-hover:scale-105" style={{ objectPosition: `${cat.image?.focalX ?? 50}% ${cat.image?.focalY ?? 50}%` }} sizes="(max-width: 768px) 100vw, 50vw" />
|
||||
)}
|
||||
<div className="absolute inset-0 image-overlay-gradient opacity-80 group-hover:opacity-90 transition-opacity duration-500" />
|
||||
{iconSrc && (
|
||||
<div className="absolute top-3 right-3 md:top-8 md:right-8 w-10 h-10 md:w-20 md:h-20 bg-white/10 backdrop-blur-md rounded-xl md:rounded-[24px] flex items-center justify-center border border-white/20 shadow-2xl transition-all duration-500 group-hover:scale-110 group-hover:bg-white/20">
|
||||
<Image src={iconSrc} alt="" width={24} height={24} className="w-6 h-6 md:w-12 md:h-12 brightness-0 invert opacity-80" />
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute bottom-4 left-4 md:bottom-10 md:left-10 right-4 md:right-10">
|
||||
<h2 className="text-xl md:text-4xl font-bold text-white leading-tight">{cat.title}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-5 md:p-10">
|
||||
{cat.description && <p className="text-text-secondary text-sm md:text-lg leading-relaxed mb-4 md:mb-8 line-clamp-2 md:line-clamp-none">{cat.description}</p>}
|
||||
<div className="flex items-center text-saturated font-bold text-base md:text-lg group-hover:text-accent-dark transition-colors">
|
||||
<span className="border-b-2 border-saturated/10 group-hover:border-accent-dark transition-colors pb-1">{cat.ctaLabel || 'View Products'}</span>
|
||||
<div className="ml-3 md:ml-4 w-8 h-8 md:w-10 md:h-10 rounded-full bg-primary-light flex items-center justify-center text-saturated group-hover:bg-accent group-hover:text-primary-dark transition-all duration-300 shadow-sm">
|
||||
<svg className="w-4 h-4 md:w-5 md:h-5 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</a>
|
||||
</Reveal>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
);
|
||||
},
|
||||
'block-categoryGrid': ({ node }: any) => {
|
||||
const cats = node.fields.categories || [];
|
||||
return (
|
||||
<Section className="bg-neutral-light relative">
|
||||
<Container>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 md:gap-8 lg:gap-12">
|
||||
{cats.map((cat: any, idx: number) => {
|
||||
const imgSrc = cat.image?.sizes?.card?.url || cat.image?.url;
|
||||
const iconSrc = cat.icon?.url;
|
||||
return (
|
||||
<Reveal key={idx} delay={idx * 100}>
|
||||
<a href={cat.href} className="group block">
|
||||
<Card className="h-full border-none shadow-sm hover:shadow-2xl transition-all duration-500 rounded-[24px] md:rounded-[48px] overflow-hidden bg-white active:scale-[0.98]">
|
||||
<div className="relative h-[200px] md:h-[400px] overflow-hidden">
|
||||
{imgSrc && (<Image src={imgSrc} alt={cat.title} fill className="object-cover transition-transform duration-1000 group-hover:scale-105" style={{ objectPosition: `${cat.image?.focalX ?? 50}% ${cat.image?.focalY ?? 50}%` }} sizes="(max-width: 768px) 100vw, 50vw" />)}
|
||||
<div className="absolute inset-0 image-overlay-gradient opacity-80 group-hover:opacity-90 transition-opacity duration-500" />
|
||||
{iconSrc && (<div className="absolute top-3 right-3 md:top-8 md:right-8 w-10 h-10 md:w-20 md:h-20 bg-white/10 backdrop-blur-md rounded-xl md:rounded-[24px] flex items-center justify-center border border-white/20"><Image src={iconSrc} alt="" width={24} height={24} className="w-6 h-6 md:w-12 md:h-12 brightness-0 invert opacity-80" /></div>)}
|
||||
<div className="absolute bottom-4 left-4 md:bottom-10 md:left-10 right-4 md:right-10"><h2 className="text-xl md:text-4xl font-bold text-white leading-tight">{cat.title}</h2></div>
|
||||
</div>
|
||||
<div className="p-5 md:p-10">
|
||||
{cat.description && <p className="text-text-secondary text-sm md:text-lg leading-relaxed mb-4 md:mb-8">{cat.description}</p>}
|
||||
<div className="flex items-center text-saturated font-bold text-base md:text-lg group-hover:text-accent-dark transition-colors"><span>{cat.ctaLabel || 'View Products'}</span></div>
|
||||
</div>
|
||||
</Card>
|
||||
</a>
|
||||
</Reveal>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
);
|
||||
},
|
||||
manifestoGrid: ({ node }: any) => {
|
||||
const f = node.fields;
|
||||
return (
|
||||
<Section className="bg-white text-primary py-16 md:py-28">
|
||||
<Container>
|
||||
<div className="sticky-narrative-container">
|
||||
<div className="sticky-narrative-sidebar mb-8 lg:mb-0">
|
||||
<div className="lg:sticky lg:top-32">
|
||||
{f.title && <Heading level={2} subtitle={f.subtitle}>{f.title}</Heading>}
|
||||
{f.tagline && <p className="text-base md:text-xl text-text-secondary leading-relaxed">{f.tagline}</p>}
|
||||
</div>
|
||||
</div>
|
||||
<ul className="sticky-narrative-content grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-10 list-none p-0 m-0">
|
||||
{(f.items || []).map((item: any, idx: number) => (
|
||||
<li key={idx} className="p-6 md:p-10 bg-neutral-light rounded-2xl md:rounded-[40px] border border-transparent hover:border-accent hover:bg-white hover:shadow-2xl transition-all duration-500 group active:scale-[0.98]">
|
||||
<div className="w-10 h-10 md:w-16 md:h-16 bg-white rounded-xl md:rounded-2xl flex items-center justify-center mb-4 md:mb-8 shadow-sm group-hover:bg-accent transition-colors duration-500">
|
||||
<span className="text-primary font-extrabold text-lg md:text-2xl group-hover:text-primary-dark">0{idx + 1}</span>
|
||||
</div>
|
||||
<h3 className="text-lg md:text-2xl font-bold mb-2 md:mb-4 text-primary">{item.title}</h3>
|
||||
<p className="text-sm md:text-lg text-text-secondary leading-relaxed">{item.description}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
);
|
||||
},
|
||||
'block-manifestoGrid': ({ node }: any) => {
|
||||
const f = node.fields;
|
||||
return (
|
||||
<Section className="bg-white text-primary py-16 md:py-28">
|
||||
<Container>
|
||||
<div className="sticky-narrative-container">
|
||||
<div className="sticky-narrative-sidebar mb-8 lg:mb-0">
|
||||
<div className="lg:sticky lg:top-32">
|
||||
{f.title && <Heading level={2} subtitle={f.subtitle}>{f.title}</Heading>}
|
||||
{f.tagline && <p className="text-base md:text-xl text-text-secondary leading-relaxed">{f.tagline}</p>}
|
||||
</div>
|
||||
</div>
|
||||
<ul className="sticky-narrative-content grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-10 list-none p-0 m-0">
|
||||
{(f.items || []).map((item: any, idx: number) => (
|
||||
<li key={idx} className="p-6 md:p-10 bg-neutral-light rounded-2xl md:rounded-[40px] border border-transparent hover:border-accent hover:bg-white hover:shadow-2xl transition-all duration-500 group active:scale-[0.98]">
|
||||
<div className="w-10 h-10 md:w-16 md:h-16 bg-white rounded-xl md:rounded-2xl flex items-center justify-center mb-4 md:mb-8 shadow-sm group-hover:bg-accent transition-colors duration-500">
|
||||
<span className="text-primary font-extrabold text-lg md:text-2xl group-hover:text-primary-dark">0{idx + 1}</span>
|
||||
</div>
|
||||
<h3 className="text-lg md:text-2xl font-bold mb-2 md:mb-4 text-primary">{item.title}</h3>
|
||||
<p className="text-sm md:text-lg text-text-secondary leading-relaxed">{item.description}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
);
|
||||
},
|
||||
homeHero: ({ node }: any) => {
|
||||
console.log('[PayloadRichText] Rendering homeHero block');
|
||||
return <HomeHero data={node?.fields} />;
|
||||
},
|
||||
'block-homeHero': ({ node }: any) => {
|
||||
console.log('[PayloadRichText] Rendering block-homeHero block');
|
||||
return <HomeHero data={node?.fields} />;
|
||||
},
|
||||
homeProductCategories: ({ node }: any) => <Reveal><ProductCategories data={node?.fields} /></Reveal>,
|
||||
'block-homeProductCategories': ({ node }: any) => <Reveal><ProductCategories data={node?.fields} /></Reveal>,
|
||||
homeWhatWeDo: ({ node }: any) => <Reveal><WhatWeDo data={node?.fields} /></Reveal>,
|
||||
'block-homeWhatWeDo': ({ node }: any) => <Reveal><WhatWeDo data={node?.fields} /></Reveal>,
|
||||
homeExperience: ({ node }: any) => <Reveal><Experience data={node?.fields} /></Reveal>,
|
||||
'block-homeExperience': ({ node }: any) => <Reveal><Experience data={node?.fields} /></Reveal>,
|
||||
homeWhyChooseUs: ({ node }: any) => <Reveal><WhyChooseUs data={node?.fields} /></Reveal>,
|
||||
'block-homeWhyChooseUs': ({ node }: any) => <Reveal><WhyChooseUs data={node?.fields} /></Reveal>,
|
||||
homeMeetTheTeam: ({ node }: any) => <Reveal><MeetTheTeam data={node?.fields} /></Reveal>,
|
||||
'block-homeMeetTheTeam': ({ node }: any) => <Reveal><MeetTheTeam data={node?.fields} /></Reveal>,
|
||||
homeGallery: ({ node }: any) => <Reveal><GallerySection data={node?.fields} /></Reveal>,
|
||||
'block-homeGallery': ({ node }: any) => <Reveal><GallerySection data={node?.fields} /></Reveal>,
|
||||
homeVideo: ({ node }: any) => <Reveal><VideoSection data={node?.fields} /></Reveal>,
|
||||
'block-homeVideo': ({ node }: any) => <Reveal><VideoSection data={node?.fields} /></Reveal>,
|
||||
homeCTA: ({ node }: any) => <Reveal className="content-visibility-auto"><CTA data={node?.fields} /></Reveal>,
|
||||
'block-homeCTA': ({ node }: any) => <Reveal className="content-visibility-auto"><CTA data={node?.fields} /></Reveal>,
|
||||
},
|
||||
// Custom converter for the Payload "upload" Lexical node (Media collection)
|
||||
// This natively reconstructs Next.js <Image /> tags pointing to the focal-point cropped sizes
|
||||
@@ -297,12 +682,29 @@ const jsxConverters: JSXConverters = {
|
||||
},
|
||||
};
|
||||
|
||||
export default function PayloadRichText({ data }: { data: any }) {
|
||||
export default function PayloadRichText({
|
||||
data,
|
||||
className = 'article-content max-w-none',
|
||||
}: {
|
||||
data: any;
|
||||
className?: string;
|
||||
}) {
|
||||
const locale = useLocale();
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
const dynamicConverters: JSXConverters = {
|
||||
...jsxConverters,
|
||||
blocks: {
|
||||
...jsxConverters.blocks,
|
||||
homeRecentPosts: () => <Reveal><RecentPosts locale={locale} /></Reveal>,
|
||||
'block-homeRecentPosts': () => <Reveal><RecentPosts locale={locale} /></Reveal>,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="article-content max-w-none">
|
||||
<RichText data={data} converters={jsxConverters} />
|
||||
<div className={className}>
|
||||
<RichText data={data} converters={dynamicConverters} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user