import React from 'react';
import { Document, Page, View, Text, StyleSheet, Link, Image } from '@react-pdf/renderer';
// Standard fonts like Helvetica are built-in to PDF and don't require registration
// unless we want to use specific TTF files. Using built-in Helvetica for maximum stability.
// ─── Brand Tokens (matching datasheet) ──────────────────────────────────
const C = {
navy: '#001a4d',
navyDeep: '#000d26',
accent: '#82ed20',
white: '#FFFFFF',
offWhite: '#f8f9fa',
gray100: '#f3f4f6',
gray200: '#e5e7eb',
gray300: '#d1d5db',
gray400: '#9ca3af',
gray600: '#4b5563',
gray900: '#111827',
};
const MARGIN = 72;
const styles = StyleSheet.create({
page: {
color: C.gray900,
lineHeight: 1.6,
backgroundColor: C.white,
paddingTop: 50,
paddingBottom: 80,
fontFamily: 'Helvetica',
},
// Premium Header Layout
hero: {
backgroundColor: C.offWhite,
paddingTop: 24,
paddingBottom: 24,
paddingHorizontal: MARGIN,
marginBottom: 40,
marginTop: -50, // Counters the page padding to achieve full-bleed top
borderBottomWidth: 1,
borderBottomColor: C.gray200,
position: 'relative',
},
headerTop: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 32,
},
logo: {
width: 140, // Increased to fit full logo
height: 45,
objectFit: 'contain',
},
docType: {
fontSize: 9,
fontWeight: 700,
color: C.navy,
letterSpacing: 2,
textTransform: 'uppercase',
opacity: 0.6,
},
headerTitleArea: {
marginTop: 0,
},
pageTitle: {
fontSize: 16,
fontWeight: 700,
color: C.navyDeep,
marginBottom: 4,
textTransform: 'uppercase',
letterSpacing: 0,
},
accentBar: {
width: 40,
height: 4,
backgroundColor: C.accent,
marginTop: 12,
borderRadius: 2,
},
// Content Area
content: {
paddingHorizontal: MARGIN,
},
// Lexical Elements with high-fidelity formatting
paragraph: {
fontSize: 10,
color: C.gray600,
lineHeight: 1.7,
marginBottom: 14,
textAlign: 'justify',
},
heading1: {
fontSize: 14,
fontWeight: 700,
color: C.navyDeep,
textTransform: 'uppercase',
letterSpacing: 0.8,
marginTop: 0,
marginBottom: 0,
},
heading1Wrapper: {
marginTop: 24,
marginBottom: 12,
borderLeftWidth: 3,
borderLeftColor: C.accent,
paddingLeft: 12,
},
heading2: {
fontSize: 11,
fontWeight: 700,
color: C.navyDeep,
marginTop: 18,
marginBottom: 10,
textTransform: 'uppercase',
letterSpacing: 0.5,
},
heading3: {
fontSize: 10,
fontWeight: 700,
color: C.navyDeep,
marginTop: 14,
marginBottom: 8,
},
list: {
marginBottom: 16,
marginLeft: 4,
},
listItem: {
flexDirection: 'row',
marginBottom: 6,
},
listItemBullet: {
width: 20,
fontSize: 10,
color: C.gray400,
fontWeight: 700,
},
listItemContent: {
flex: 1,
fontSize: 10,
color: C.gray600,
lineHeight: 1.7,
textAlign: 'justify',
},
link: {
color: C.navy,
textDecoration: 'underline',
},
textBold: {
fontWeight: 700,
fontFamily: 'Helvetica-Bold',
color: C.navyDeep,
},
textItalic: {
fontStyle: 'italic',
},
// Industrial Footer
footer: {
position: 'absolute',
bottom: 30,
left: MARGIN,
right: MARGIN,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-end',
paddingTop: 16,
borderTopWidth: 0.5,
borderTopColor: C.gray200,
},
footerInfo: {
flexDirection: 'column',
},
footerBrand: {
fontSize: 10,
fontWeight: 700,
color: C.navyDeep,
textTransform: 'uppercase',
letterSpacing: 1,
marginBottom: 4,
},
footerText: {
fontSize: 7,
color: C.gray400,
fontWeight: 500,
textTransform: 'uppercase',
letterSpacing: 0.5,
},
pageNum: {
fontSize: 8,
color: C.gray400,
},
});
// ─── Lexical to React-PDF Renderer ────────────────────────────────
const renderLexicalNode = (node: any, idx: number): React.ReactNode => {
if (!node) return null;
switch (node.type) {
case 'text': {
const format = node.format || 0;
const isBold = (format & 1) !== 0;
const isItalic = (format & 2) !== 0;
let elementStyle: any = {};
if (isBold) elementStyle = { ...elementStyle, ...styles.textBold };
if (isItalic) elementStyle = { ...elementStyle, ...styles.textItalic };
return (
{node.text}
);
}
case 'paragraph': {
if (!node.children || node.children.length === 0) return null;
return (
{node.children?.map((child: any, i: number) => renderLexicalNode(child, i))}
);
}
case 'heading': {
if (!node.children || node.children.length === 0) return null;
let hStyle = styles.heading3;
let isH1 = false;
if (node.tag === 'h1') {
hStyle = styles.heading1;
isH1 = true;
}
if (node.tag === 'h2') hStyle = styles.heading2;
const content = (
{node.children?.map((child: any, i: number) => renderLexicalNode(child, i))}
);
if (isH1) {
return (
{content}
);
}
return content;
}
case 'list': {
if (!node.children || node.children.length === 0) return null;
return (
{node.children?.map((child: any, i: number) => {
if (child.type === 'listitem') {
return (
{node.listType === 'number' ? `${i + 1}.` : '•'}
{child.children?.map((c: any, ci: number) => renderLexicalNode(c, ci))}
);
}
return renderLexicalNode(child, i);
})}
);
}
case 'link': {
const href = node.fields?.url || node.url || '#';
return (
{node.children?.map((child: any, i: number) => renderLexicalNode(child, i))}
);
}
case 'linebreak': {
return {'\n'};
}
// Ignore payload blocks recursively to avoid crashing
case 'block':
return null;
default:
if (node.children) {
return (
{node.children.map((child: any, i: number) => renderLexicalNode(child, i))}
);
}
return null;
}
};
interface PDFPageProps {
page: any;
locale?: string;
}
export const PDFPage: React.FC = ({ page, locale = 'de' }) => {
const dateStr = new Date().toLocaleDateString(locale === 'en' ? 'en-US' : 'de-DE', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
const logoPath = `${process.cwd()}/public/logo-full.png`;
return (
{/* Improved Hero Header - No longer fixed so it doesn't repeat on all pages */}
{page.title}
{page.content?.root?.children?.map((node: any, i: number) =>
renderLexicalNode(node, i),
)}
{/* Industrial footer with page numbers */}
KLZ VERTRIEBS GMBH
RAIFFEISENSTRASSE 22 • 73630 REMSHALDEN • {dateStr}
`${pageNumber} / ${totalPages}`}
/>
);
};