chore: remove legacy mdx artifacts and dependencies
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 7s
Build & Deploy / 🧪 QA (push) Failing after 55s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / ⚡ Performance & Accessibility (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s

This commit is contained in:
2026-02-26 01:47:30 +01:00
parent 7d65237ee9
commit 3de13b4fb3
132 changed files with 521 additions and 223914 deletions

View File

@@ -3,7 +3,7 @@ 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 all custom React components that were previously mapped via Markdown
import StickyNarrative from '@/components/blog/StickyNarrative';
import ComparisonGrid from '@/components/blog/ComparisonGrid';
import VisualLinkPreview from '@/components/blog/VisualLinkPreview';
@@ -42,7 +42,7 @@ const jsxConverters: JSXConverters = {
// If the text node contains raw HTML (from messy migrations), render it as HTML instead of escaping it
text: ({ node }: any) => {
const text = node.text;
// Handle markdown-style lists embedded in text nodes from MDX migration
// Handle markdown-style lists embedded in text nodes from Markdown migration
if (text && text.includes('\n- ')) {
const parts = text.split('\n- ').filter((p: string) => p.trim() !== '');
// If first part doesn't start with "- ", it's a prefix paragraph
@@ -72,7 +72,7 @@ const jsxConverters: JSXConverters = {
return <span dangerouslySetInnerHTML={{ __html: text }} />;
}
// Handle markdown-style links [text](url) from MDX migration
// Handle markdown-style links [text](url) from Markdown migration
if (text && /\[([^\]]+)\]\(([^)]+)\)/.test(text)) {
const parts: React.ReactNode[] = [];
const remaining = text;
@@ -280,7 +280,9 @@ const jsxConverters: JSXConverters = {
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' : ''}`}>
<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">
@@ -289,7 +291,9 @@ const jsxConverters: JSXConverters = {
alt={f.title}
fill
className="object-cover opacity-30 md:opacity-40"
style={{ objectPosition: `${f.backgroundImage?.focalX ?? 50}% ${f.backgroundImage?.focalY ?? 50}%` }}
style={{
objectPosition: `${f.backgroundImage?.focalX ?? 50}% ${f.backgroundImage?.focalY ?? 50}%`,
}}
sizes="100vw"
priority
/>
@@ -299,14 +303,29 @@ const jsxConverters: JSXConverters = {
)}
<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.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">
<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">&rarr;</span>
<span className="ml-3 transition-transform group-hover:translate-x-2">
&rarr;
</span>
</a>
</div>
)}
@@ -321,23 +340,53 @@ const jsxConverters: JSXConverters = {
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' : ''}`}>
<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 />
<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.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">&rarr;</span></a>
<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">
&rarr;
</span>
</a>
</div>
)}
</div>
@@ -354,30 +403,59 @@ const jsxConverters: JSXConverters = {
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'}`}>
<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>
<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
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.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' }}
eventProperties={{
type: 'social_linkedin',
person: f.name,
location: 'team_page',
}}
>
{f.linkedinLabel || 'LinkedIn'}
<span className="ml-3 transition-transform group-hover:translate-x-2">&rarr;</span>
<span className="ml-3 transition-transform group-hover:translate-x-2">
&rarr;
</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'}`}>
<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
@@ -385,10 +463,14 @@ const jsxConverters: JSXConverters = {
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}%` }}
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'}`} />
<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>
@@ -404,26 +486,76 @@ const jsxConverters: JSXConverters = {
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'}`}>
<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>
<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
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.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">&rarr;</span>
<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">
&rarr;
</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
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>
@@ -437,7 +569,11 @@ const jsxConverters: JSXConverters = {
<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" />}>
<Suspense
fallback={
<div className="animate-pulse bg-neutral-medium h-96 rounded-2xl md:rounded-3xl" />
}
>
<ContactForm />
</Suspense>
</div>
@@ -446,8 +582,14 @@ const jsxConverters: JSXConverters = {
</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
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>
)}
@@ -462,7 +604,11 @@ const jsxConverters: JSXConverters = {
<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" />}>
<Suspense
fallback={
<div className="animate-pulse bg-neutral-medium h-96 rounded-2xl md:rounded-3xl" />
}
>
<ContactForm />
</Suspense>
</div>
@@ -471,8 +617,14 @@ const jsxConverters: JSXConverters = {
</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
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>
)}
@@ -496,24 +648,59 @@ const jsxConverters: JSXConverters = {
<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" />
<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" />
<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>
<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>}
{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>
<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>
<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>
@@ -541,14 +728,45 @@ const jsxConverters: JSXConverters = {
<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" />)}
{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>
{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>
{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>
@@ -568,18 +786,35 @@ const jsxConverters: JSXConverters = {
<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>}
{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]">
<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>
<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>
<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>
@@ -596,18 +831,35 @@ const jsxConverters: JSXConverters = {
<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>}
{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]">
<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>
<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>
<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>
@@ -624,22 +876,86 @@ const jsxConverters: JSXConverters = {
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>,
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
@@ -697,8 +1013,16 @@ export default function PayloadRichText({
...jsxConverters,
blocks: {
...jsxConverters.blocks,
homeRecentPosts: () => <Reveal><RecentPosts locale={locale} /></Reveal>,
'block-homeRecentPosts': () => <Reveal><RecentPosts locale={locale} /></Reveal>,
homeRecentPosts: () => (
<Reveal>
<RecentPosts locale={locale} />
</Reveal>
),
'block-homeRecentPosts': () => (
<Reveal>
<RecentPosts locale={locale} />
</Reveal>
),
},
};

View File

@@ -1,197 +0,0 @@
import Link from 'next/link';
import VisualLinkPreview from '@/components/blog/VisualLinkPreview';
import { Callout } from '@/components/ui';
import HighlightBox from '@/components/blog/HighlightBox';
import Stats from '@/components/blog/Stats';
import AnimatedImage from '@/components/blog/AnimatedImage';
import ChatBubble from '@/components/blog/ChatBubble';
import SplitHeading from '@/components/blog/SplitHeading';
import PowerCTA from '@/components/blog/PowerCTA';
import StickyNarrative from '@/components/blog/StickyNarrative';
import TechnicalGrid from '@/components/blog/TechnicalGrid';
import ComparisonGrid from '@/components/blog/ComparisonGrid';
import { generateHeadingId, getTextContent } from '@/lib/blog';
export const mdxComponents = {
VisualLinkPreview,
Callout,
HighlightBox,
Stats,
AnimatedImage,
ChatBubble,
PowerCTA,
SplitHeading,
StickyNarrative,
TechnicalGrid,
ComparisonGrid,
h1: ({ children, ...props }: any) => {
const id = props.id || generateHeadingId(getTextContent(children));
return (
<SplitHeading {...props} id={id} className="mt-16 mb-6 pb-3 border-b-2 border-primary/20">
{children}
</SplitHeading>
);
},
a: ({ href, children, ...props }: any) => {
// Special handling for PDF downloads to make them prominent
if (href?.endsWith('.pdf')) {
return (
<a
href={href}
{...props}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-3 px-6 py-3 bg-primary text-white font-bold rounded-xl hover:bg-accent hover:text-primary-dark transition-all duration-300 no-underline my-8 group shadow-lg hover:shadow-xl hover:-translate-y-1"
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
<span>{children}</span>
<span className="text-xs opacity-50 font-normal group-hover:opacity-100 transition-opacity">
(PDF)
</span>
</a>
);
}
if (href?.startsWith('/')) {
return (
<Link
href={href}
{...props}
className="text-primary font-medium hover:underline decoration-2 underline-offset-2 transition-all"
>
{children}
</Link>
);
}
return (
<a
href={href}
{...props}
target="_blank"
rel="noopener noreferrer"
className="text-primary font-medium hover:underline decoration-2 underline-offset-2 transition-all inline-flex items-center gap-1"
>
{children}
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
/>
</svg>
</a>
);
},
img: (props: any) => <AnimatedImage src={props.src} alt={props.alt} />,
h2: ({ children, ...props }: any) => {
const id = props.id || generateHeadingId(getTextContent(children));
return (
<h3 {...props} id={id} className="text-2xl font-bold text-text-primary mt-12 mb-4">
{children}
</h3>
);
},
h3: ({ children, ...props }: any) => {
const id = props.id || generateHeadingId(getTextContent(children));
return (
<h4 {...props} id={id} className="text-xl font-bold text-text-primary mt-10 mb-3">
{children}
</h4>
);
},
p: ({ children, ...props }: any) => (
<div {...props} className="text-lg text-text-secondary leading-relaxed mb-6">
{children}
</div>
),
ul: ({ children, ...props }: any) => (
<ul {...props} className="my-8 space-y-3">
{children}
</ul>
),
ol: ({ children, ...props }: any) => (
<ol {...props} className="my-8 space-y-3 list-decimal list-inside">
{children}
</ol>
),
li: ({ children, ...props }: any) => (
<li {...props} className="text-lg text-text-secondary flex items-start gap-3">
<span className="text-primary mt-1.5 flex-shrink-0">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clipRule="evenodd"
/>
</svg>
</span>
<span className="flex-1">{children}</span>
</li>
),
blockquote: ({ children, ...props }: any) => (
<blockquote
{...props}
className="my-8 pl-6 border-l-4 border-primary bg-neutral-light/30 py-4 pr-6 rounded-r-lg"
>
<div className="text-lg text-text-primary italic">{children}</div>
</blockquote>
),
strong: ({ children, ...props }: any) => (
<strong {...props} className="font-bold text-primary">
{children}
</strong>
),
code: ({ children, ...props }: any) => (
<code {...props} className="px-2 py-1 bg-neutral-light text-primary rounded font-mono text-sm">
{children}
</code>
),
pre: ({ children, ...props }: any) => (
<pre {...props} className="my-8 p-6 bg-neutral-dark/5 rounded-xl overflow-x-auto">
{children}
</pre>
),
table: ({ children, ...props }: any) => (
<div className="my-8 overflow-x-auto rounded-lg border border-neutral-200 shadow-sm">
<table {...props} className="w-full text-left text-sm text-text-secondary">
{children}
</table>
</div>
),
thead: ({ children, ...props }: any) => (
<thead
{...props}
className="bg-neutral-50 text-text-primary font-semibold border-b border-neutral-200"
>
{children}
</thead>
),
tbody: ({ children, ...props }: any) => (
<tbody {...props} className="divide-y divide-neutral-200 bg-white">
{children}
</tbody>
),
tr: ({ children, ...props }: any) => (
<tr {...props} className="hover:bg-neutral-50/50 transition-colors">
{children}
</tr>
),
th: ({ children, ...props }: any) => (
<th {...props} className="px-6 py-4 whitespace-nowrap">
{children}
</th>
),
td: ({ children, ...props }: any) => (
<td {...props} className="px-6 py-4">
{children}
</td>
),
};

View File

@@ -1,10 +1,10 @@
import React from 'react';
import Link from 'next/link';
import { PostMdx } from '@/lib/blog';
import { PostData } from '@/lib/blog';
interface PostNavigationProps {
prev: PostMdx | null;
next: PostMdx | null;
prev: PostData | null;
next: PostData | null;
isPrevRandom?: boolean;
isNextRandom?: boolean;
locale: string;