diff --git a/lib/pdf-brochure.tsx b/lib/pdf-brochure.tsx index 175d4540..5a955e7a 100644 --- a/lib/pdf-brochure.tsx +++ b/lib/pdf-brochure.tsx @@ -74,6 +74,7 @@ export interface BrochureProps { }>; galleryImages?: Array; messages?: Record; + directorPhotos?: { michael?: Buffer; klaus?: Buffer }; } // ─── Helpers ──────────────────────────────────────────────────────────────── @@ -341,8 +342,8 @@ const InfoPage: React.FC<{ @@ -378,7 +379,7 @@ const InfoPage: React.FC<{ borderLeftWidth: 3, borderLeftColor: C.green, borderLeftStyle: 'solid', paddingVertical: 12, paddingHorizontal: 12, }}> - {h.value} + {h.value} {h.label} ))} @@ -423,7 +424,8 @@ const AboutPage: React.FC<{ logoBlack?: string | Buffer; image?: string | Buffer; messages?: Record; -}> = ({ locale, companyInfo, logoBlack, image, messages }) => { + directorPhotos?: { michael?: Buffer; klaus?: Buffer }; +}> = ({ locale, companyInfo, logoBlack, image, messages, directorPhotos }) => { const l = labels(locale); // Image at top: 200pt tall (smaller to leave more room for content) @@ -463,18 +465,15 @@ const AboutPage: React.FC<{ {companyInfo.tagline} - {/* Legacy / Heritage section */} - {legacy && ( - - {legacy.title} - - {legacy.p1} - - - {legacy.p2} - - - )} + {/* Company mission — makes immediately clear what KLZ does */} + + + {locale === 'de' + ? 'KLZ Cables ist Ihr Spezialist für Energiekabel von 1 kV bis 220 kV. Wir beliefern Energieversorger, Wind- und Solarparks sowie die Industrie mit VDE-geprüften Kabeln – von der Niederspannung über die Mittelspannung bis zur Hochspannung. Mit einem europaweiten Netzwerk und jahrzehntelanger Erfahrung sorgen wir für zuverlässige Kabelinfrastruktur.' + : 'KLZ Cables is your specialist for power cables from 1 kV to 220 kV. We supply energy providers, wind and solar parks, and industry with VDE-certified cables – from low voltage through medium voltage to high voltage. With a Europe-wide network and decades of experience, we ensure reliable cable infrastructure.' + } + + {/* Directors — two-column */} {(michael || klaus) && ( @@ -483,15 +482,22 @@ const AboutPage: React.FC<{ {locale === 'de' ? 'Die Geschäftsführer' : 'The Directors'} - {[michael, klaus].filter(Boolean).map((person, i) => ( + {[{ data: michael, photo: directorPhotos?.michael }, { data: klaus, photo: directorPhotos?.klaus }].filter(p => p.data).map((p, i) => ( - {person.name} - {person.role} - {person.description} - {person.quote && ( + + {p.photo && ( + + )} + + {p.data.name} + {p.data.role} + + + {p.data.description} + {p.data.quote && ( - „{person.quote}" + „{p.data.quote}“ )} @@ -742,7 +748,7 @@ const BackCover: React.FC<{ export const PDFBrochure: React.FC = ({ products, locale, companyInfo, introContent, - marketingSections, logoBlack, logoWhite, galleryImages, messages, + marketingSections, logoBlack, logoWhite, galleryImages, messages, directorPhotos, }) => { // Cover(1) + About(1) + marketingSections.length + TOC(1) + products + BackCover(1) const numInfoPages = 1 + (marketingSections?.length || 0); @@ -770,7 +776,7 @@ export const PDFBrochure: React.FC = ({ {/* About page — image[1] */} - + {/* Info sections — images[2..] each unique, alternating top/bottom and light/dark */} {marketingSections?.map((section, i) => ( diff --git a/public/brochure/klz-product-catalog-de.pdf b/public/brochure/klz-product-catalog-de.pdf index 3ab5ead8..66ab64af 100644 Binary files a/public/brochure/klz-product-catalog-de.pdf and b/public/brochure/klz-product-catalog-de.pdf differ diff --git a/public/brochure/klz-product-catalog-en.pdf b/public/brochure/klz-product-catalog-en.pdf index 0c36bc16..d36f7c3c 100644 Binary files a/public/brochure/klz-product-catalog-en.pdf and b/public/brochure/klz-product-catalog-en.pdf differ diff --git a/scripts/generate-brochure.ts b/scripts/generate-brochure.ts index 0229773f..c1d73236 100644 --- a/scripts/generate-brochure.ts +++ b/scripts/generate-brochure.ts @@ -254,91 +254,63 @@ async function loadMarketingSections(locale: 'en' | 'de'): Promise = []; - // 1. What we do — short label, long subtitle becomes the description - if (messages.Home?.whatWeDo) { - const label = locale === 'de' ? 'Unser Angebot' : 'Our Services'; - sections.push({ - title: messages.Home.whatWeDo.title, - subtitle: label, - description: messages.Home.whatWeDo.subtitle, - items: messages.Home.whatWeDo.items, - }); - } + // ── 1. Was wir tun + Warum wir — MERGED into one compact section ── + { + const allItems: Array<{ title: string; description: string }> = []; - // 2. Our Legacy — with stats highlight - if (messages.Team?.legacy) { - const label = locale === 'de' ? 'Unsere Geschichte' : 'Our Story'; - sections.push({ - title: messages.Team.legacy.title, - subtitle: label, - description: `${messages.Team.legacy.p1}\n\n${messages.Team.legacy.p2}`, - highlights: [ - { value: messages.Team.legacy.expertise || 'Expertise', label: messages.Team.legacy.expertiseDesc || '' }, - { value: messages.Team.legacy.network || 'Netzwerk', label: messages.Team.legacy.networkDesc || '' }, - ], - }); - } - - // 3. Experience stats section - if (messages.Home?.experience) { - const label = locale === 'de' ? 'Erfahrung' : 'Experience'; - sections.push({ - title: messages.Home.experience.title, - subtitle: label, - description: `${messages.Home.experience.p1 || ''}\n\n${messages.Home.experience.p2 || ''}`.trim(), - highlights: [ - { value: messages.Home.experience.certifiedQuality || (locale === 'de' ? 'Zertifizierte Qualität' : 'Certified Quality'), label: messages.Home.experience.vdeApproved || '' }, - { value: messages.Home.experience.fullSpectrum || (locale === 'de' ? 'Volles Spektrum' : 'Full Spectrum'), label: messages.Home.experience.solutionsRange || '' }, - ], - }); - } - - // 4. Why choose us - if (messages.Home?.whyChooseUs) { - const label = locale === 'de' ? 'Warum KLZ' : 'Why KLZ'; - sections.push({ - title: messages.Home.whyChooseUs.title, - subtitle: label, - description: messages.Home.whyChooseUs.subtitle || '', - items: messages.Home.whyChooseUs.items, - }); - } - - // 5. Team intro + quotes as pull quotes - if (messages.Team?.klaus || messages.Team?.michael) { - const label = locale === 'de' ? 'Die Geschäftsführer' : 'The Directors'; - const title = messages.Home?.meetTheTeam?.title || (locale === 'de' ? 'Das Team' : 'The Team'); - const teamItems: Array<{ title: string; description: string }> = []; - if (messages.Team?.klaus) { - teamItems.push({ - title: `${messages.Team.klaus.name} – ${messages.Team.klaus.role}`, - description: messages.Team.klaus.description, - }); + // WhatWeDo items — truncated to 1 sentence each + if (messages.Home?.whatWeDo?.items) { + for (const item of messages.Home.whatWeDo.items) { + allItems.push({ + title: item.title.split('.')[0], // short title + description: item.description.split('.')[0] + '.', + }); + } } - if (messages.Team?.michael) { - teamItems.push({ - title: `${messages.Team.michael.name} – ${messages.Team.michael.role}`, - description: messages.Team.michael.description, - }); + // WhyChooseUs items — truncated to 1 sentence each + if (messages.Home?.whyChooseUs?.items) { + for (const item of messages.Home.whyChooseUs.items) { + allItems.push({ + title: item.title, + description: item.description.split('.')[0] + '.', + }); + } } - const desc = messages.Home?.meetTheTeam?.description || ''; + sections.push({ - title, - subtitle: label, + title: messages.Home?.whatWeDo?.title || (locale === 'de' ? 'Was wir tun' : 'What We Do'), + subtitle: locale === 'de' ? 'Leistungen & Stärken' : 'Services & Strengths', + description: messages.Home?.whatWeDo?.subtitle || '', + items: allItems, + }); + } + + // ── 2. Experience & Quality — merge Legacy + Experience highlights ── + { + const legacy = messages.Team?.legacy; + const experience = messages.Home?.experience; + const highlights: Array<{ value: string; label: string }> = []; + + if (legacy) { + highlights.push( + { value: legacy.expertise || 'Expertise', label: legacy.expertiseDesc || '' }, + { value: legacy.network || (locale === 'de' ? 'Netzwerk' : 'Network'), label: legacy.networkDesc || '' }, + ); + } + if (experience) { + highlights.push( + { value: experience.certifiedQuality || (locale === 'de' ? 'Zertifiziert' : 'Certified'), label: experience.vdeApproved || '' }, + { value: experience.fullSpectrum || (locale === 'de' ? 'Volles Spektrum' : 'Full Spectrum'), label: experience.solutionsRange || '' }, + ); + } + + const desc = legacy?.p1 || ''; + + sections.push({ + title: legacy?.title || (locale === 'de' ? 'Erfahrung & Qualität' : 'Experience & Quality'), + subtitle: locale === 'de' ? 'Unser Erbe' : 'Our Heritage', description: desc, - items: teamItems, - pullQuote: messages.Team.klaus?.quote || messages.Team.michael?.quote || '', - }); - } - - // 6. Our Values (Manifesto) - if (messages.Team?.manifesto) { - const label = locale === 'de' ? 'Grundprinzipien' : 'Core Principles'; - sections.push({ - title: messages.Team.manifesto.title, - subtitle: label, - description: messages.Team.manifesto.tagline, - items: messages.Team.manifesto.items, + highlights, }); } @@ -395,18 +367,14 @@ async function main(): Promise { console.log(`Logos: white=${!!logoWhite} black=${!!logoBlack}`); - // EXACT image mapping to website sections to prevent "random" images. - // Index map: 0=Cover, 1=About, 2=WhatWeDo, 3=Legacy, 4=Experience, 5=WhyChooseUs, 6=Team, 7=Manifesto, 8=BackCover + // EXACT image mapping — 2 marketing sections now + // Index map: 0=Cover, 1=About, 2=WasWirTun(null), 3=Erfahrung(Legacy image), 4=BackCover const galleryPaths: Array = [ - 'uploads/2024/12/DSC07655-Large.webp', // 0: Cover (Hero) + 'uploads/2024/12/large-rolls-of-wires-against-the-blue-sky-at-sunse-2023-11-27-05-20-33-utc-Large.webp', // 0: Cover (cable drums, no people) 'uploads/2024/12/DSC07460-Large-600x400.webp', // 1: About section - null, // 2: What we do (NO IMAGE) - 'uploads/2024/12/1694273920124-copy.webp', // 3: Legacy (Matching Team page) - 'uploads/2024/12/1694273920124-copy-2.webp', // 4: Experience (Matching Home page) - null, // 5: Why choose us (NO IMAGE) - 'uploads/2024/12/DSC08036-Large.webp', // 6: Team (Matching Team page) - null, // 7: Manifesto (NO IMAGE) - 'uploads/2024/12/DSC07433-Large-600x400.webp', // 8: Back cover + null, // 2: Was wir tun (NO IMAGE — text-heavy) + 'uploads/2024/12/1694273920124-copy.webp', // 3: Erfahrung & Qualität + 'uploads/2024/12/DSC07433-Large-600x400.webp', // 4: Back cover ]; const galleryImages: (string | Buffer | undefined)[] = []; @@ -447,12 +415,35 @@ async function main(): Promise { messages = JSON.parse(fs.readFileSync(messagesPath, 'utf-8')); } catch { /* messages are optional */ } + // Load director portrait photos and crop to circles + const directorPhotos: { michael?: Buffer; klaus?: Buffer } = {}; + const portraitPaths = { + michael: path.join(process.cwd(), 'public/uploads/2024/12/DSC07768-Large.webp'), + klaus: path.join(process.cwd(), 'public/uploads/2024/12/DSC07963-Large.webp'), + }; + const AVATAR_SIZE = 120; // px, will be rendered at 32pt in PDF + const circleMask = Buffer.from( + `` + ); + for (const [key, photoPath] of Object.entries(portraitPaths)) { + if (fs.existsSync(photoPath)) { + try { + const cropped = await sharp(photoPath) + .resize(AVATAR_SIZE, AVATAR_SIZE, { fit: 'cover', position: 'top' }) + .composite([{ input: circleMask, blend: 'dest-in' }]) + .png() + .toBuffer(); + directorPhotos[key as 'michael' | 'klaus'] = cropped; + } catch { /* skip */ } + } + } + try { // Render the React-PDF brochure const buffer = await renderToBuffer( React.createElement(PDFBrochure, { products, locale, companyInfo, introContent, - marketingSections, logoBlack, logoWhite, galleryImages, messages, + marketingSections, logoBlack, logoWhite, galleryImages, messages, directorPhotos, } as any) as any );