From 9c4c8e28e9eac2424f83769be79876bd6791b695 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Thu, 5 Mar 2026 22:56:53 +0100 Subject: [PATCH] style: align PDF Page component with KLZ brand Design System --- lib/pdf-page.tsx | 180 +++++++++++++++++++++++++++++------------------ 1 file changed, 110 insertions(+), 70 deletions(-) diff --git a/lib/pdf-page.tsx b/lib/pdf-page.tsx index b46627fd..4235462f 100644 --- a/lib/pdf-page.tsx +++ b/lib/pdf-page.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Document, Page, View, Text, Image, StyleSheet, Font, Link } from '@react-pdf/renderer'; +import { Document, Page, View, Text, StyleSheet, Font, Link } from '@react-pdf/renderer'; // Register fonts (using system fonts for now, can be customized) Font.register({ @@ -10,120 +10,120 @@ Font.register({ ], }); -// ─── Brand Tokens ──────────────────────────────────────── - +// ─── Brand Tokens (matching datasheet) ────────────────────────────────── const C = { navy: '#001a4d', navyDeep: '#000d26', green: '#4da612', + greenLight: '#e8f5d8', white: '#FFFFFF', offWhite: '#f8f9fa', + gray100: '#f3f4f6', gray200: '#e5e7eb', + gray300: '#d1d5db', gray400: '#9ca3af', gray600: '#4b5563', gray900: '#111827', }; const MARGIN = 56; -const HEADER_H = 52; -const FOOTER_H = 48; -const BODY_TOP = HEADER_H + 20; const styles = StyleSheet.create({ page: { color: C.gray900, lineHeight: 1.5, backgroundColor: C.white, - paddingTop: BODY_TOP, - paddingBottom: FOOTER_H + 40, - paddingHorizontal: MARGIN, + paddingTop: 0, + paddingBottom: 80, fontFamily: 'Helvetica', }, - header: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - height: HEADER_H, - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'flex-end', + + // Hero-style header + hero: { + backgroundColor: C.white, + paddingTop: 24, + paddingBottom: 0, paddingHorizontal: MARGIN, - paddingBottom: 12, + marginBottom: 20, + position: 'relative', borderBottomWidth: 0, }, - logoText: { - fontSize: 16, - fontWeight: 700, - color: C.navy, - }, - docTitleLabel: { - fontSize: 7, - fontWeight: 700, - color: C.gray400, - letterSpacing: 1.2, - textTransform: 'uppercase', - }, - footer: { - position: 'absolute', - bottom: 20, - left: MARGIN, - right: MARGIN, + + header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - borderTopWidth: 0.5, - borderTopColor: C.gray200, - paddingTop: 8, + marginBottom: 16, }, - footerText: { - fontSize: 7, - color: C.gray400, - letterSpacing: 0.8, + + logoText: { + fontSize: 22, + fontWeight: 700, + color: C.navyDeep, + letterSpacing: 2, textTransform: 'uppercase', }, + + docTitle: { + fontSize: 8, + fontWeight: 700, + color: C.green, + letterSpacing: 2, + textTransform: 'uppercase', + }, + + // Content Area + content: { + paddingHorizontal: MARGIN, + }, + pageTitle: { fontSize: 24, fontWeight: 700, color: C.navyDeep, - marginBottom: 16, + marginBottom: 8, + marginTop: 10, textTransform: 'uppercase', letterSpacing: -0.5, }, + accentBar: { - width: 40, - height: 3, + width: 30, + height: 2, backgroundColor: C.green, - marginBottom: 24, + marginBottom: 20, + borderRadius: 1, }, // Lexical Elements paragraph: { fontSize: 10, color: C.gray600, - lineHeight: 1.6, + lineHeight: 1.7, marginBottom: 12, }, heading1: { - fontSize: 18, + fontSize: 16, + fontWeight: 700, + color: C.navyDeep, + marginTop: 20, + marginBottom: 10, + textTransform: 'uppercase', + letterSpacing: 0.5, + }, + heading2: { + fontSize: 12, fontWeight: 700, color: C.navyDeep, marginTop: 16, marginBottom: 8, }, - heading2: { - fontSize: 14, - fontWeight: 700, - color: C.navyDeep, - marginTop: 14, - marginBottom: 6, - }, heading3: { - fontSize: 12, + fontSize: 10, fontWeight: 700, color: C.navyDeep, marginTop: 12, - marginBottom: 4, + marginBottom: 6, }, list: { marginBottom: 12, @@ -137,12 +137,13 @@ const styles = StyleSheet.create({ width: 12, fontSize: 10, color: C.green, + fontWeight: 700, }, listItemContent: { flex: 1, fontSize: 10, color: C.gray600, - lineHeight: 1.6, + lineHeight: 1.7, }, link: { color: C.green, @@ -154,7 +155,37 @@ const styles = StyleSheet.create({ color: C.navyDeep, }, textItalic: { - fontStyle: 'italic', // Not actually working without proper font set, but we will fallback + fontStyle: 'italic', + }, + + // Footer — matches brochure style + footer: { + position: 'absolute', + bottom: 28, + left: MARGIN, + right: MARGIN, + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingTop: 12, + borderTopWidth: 2, + borderTopColor: C.green, + }, + + footerText: { + fontSize: 7, + color: C.gray400, + fontWeight: 400, + textTransform: 'uppercase', + letterSpacing: 0.8, + }, + + footerBrand: { + fontSize: 9, + fontWeight: 700, + color: C.navyDeep, + textTransform: 'uppercase', + letterSpacing: 1.5, }, }); @@ -232,11 +263,10 @@ const renderLexicalNode = (node: any, idx: number): React.ReactNode => { } case 'linebreak': { - // React-PDF doesn't handle `
`, but a newline char usually works inside ``. return {'\n'}; } - // Ignore payload blocks recursively to avoid crashing, as pages should mainly use rich text + // Ignore payload blocks recursively to avoid crashing case 'block': return null; @@ -267,20 +297,30 @@ export const PDFPage: React.FC = ({ page, locale = 'de' }) => { return ( - - KLZ - {locale === 'en' ? 'Document' : 'Dokument'} + {/* Hero Header */} + + + + KLZ + + {locale === 'en' ? 'Document' : 'Dokument'} + - {page.title} - + + {page.title} + - - {page.content?.root?.children?.map((node: any, i: number) => renderLexicalNode(node, i))} + + {page.content?.root?.children?.map((node: any, i: number) => + renderLexicalNode(node, i), + )} + + {/* Minimal footer */} - KLZ CABLES + KLZ CABLES {dateStr}