import fs from 'fs'; import path from 'path'; import puppeteer from 'puppeteer-core'; const BASE_DIR = '/Users/moritz/src/infrapuzzle/projects/sasch40'; const ARTIFACTS_DIR = '/Users/moritz/.gemini/antigravity/brain/26049ae8-3106-42e0-922f-356dbbfe162a'; // Helper to get base64 data URI function getBase64Image(filePath) { const fullPath = path.isAbsolute(filePath) ? filePath : path.join(BASE_DIR, filePath); if (!fs.existsSync(fullPath)) { console.warn(`File not found: ${fullPath}`); return ''; } const ext = path.extname(fullPath).toLowerCase(); let mime = 'image/jpeg'; if (ext === '.png') mime = 'image/png'; if (ext === '.svg') mime = 'image/svg+xml'; const data = fs.readFileSync(fullPath); return `data:${mime};base64,${data.toString('base64')}`; } async function generatePDF() { console.log('🎨 Generating printable Mittelalter Retro PDF for Saschas Kronkorken Kreuzzug...'); // 1. Assets const saschaPortrait = getBase64Image('frontend/public/assets/characters/sascha/portrait_v2.jpg'); const qrCode = getBase64Image('qrcode_game.png'); // Mini avatars for entourage strip const entourage = [ { name: 'Königin Verena', img: getBase64Image('frontend/public/assets/characters/verena/portrait_v2.jpg') }, { name: 'Saskia', img: getBase64Image('frontend/public/assets/characters/saskia/portrait_v2.jpg') }, { name: 'Maja', img: getBase64Image('frontend/public/assets/characters/maja/portrait_v2.jpg') }, { name: 'Miriam', img: getBase64Image('frontend/public/assets/characters/miriam/portrait_v2.jpg') }, { name: 'Markus (Vater)', img: getBase64Image('frontend/public/assets/characters/markus/portrait.jpg') }, { name: 'NAT (Josef)', img: getBase64Image('frontend/public/assets/characters/nat/portrait_v2.jpg') }, { name: 'Lutz (ZTUL)', img: getBase64Image('frontend/public/assets/characters/lutz/portrait_v2.jpg') }, { name: 'Armend', img: getBase64Image('frontend/public/assets/characters/armend/portrait_v2.jpg') }, { name: 'Amy', img: getBase64Image('frontend/public/assets/characters/amy/portrait.jpg') }, { name: 'Mane', img: getBase64Image('frontend/public/assets/characters/mane/portrait_v2.jpg') }, { name: 'Hansi', img: getBase64Image('frontend/public/assets/characters/hansi/portrait_v2.jpg') }, { name: 'Mario', img: getBase64Image('frontend/public/assets/characters/mario/portrait_v2.jpg') }, { name: 'Flo Maier', img: getBase64Image('frontend/public/assets/characters/flo_maier/portrait_v2.jpg') }, { name: 'Pü', img: getBase64Image('frontend/public/assets/characters/pue/portrait_v2.jpg') }, { name: 'Viktor', img: getBase64Image('frontend/public/assets/characters/viktor/portrait_v2.jpg') } ]; const htmlContent = ` Saschas Kronkorken Kreuzzug – Proklamation zum 40. Geburtstag
👑 KÖNIGLICHE PROKLAMATION ZUM 40. GEBURTSTAG 👑

SASCHAS KRONKORKEN KREUZZUG

40 JAHRE BASS & GERSTENSAFT • HERZOGTUM HAUMDAUCHER
⚜️ ✦ 👑 ✦ 🍻 ✦ ⚔️ ✦ ⚜️
König Sascha I.
KÖNIG SASCHA I.
Der 40-jährige Imperator
📜 DIE CHRONIK DES REICHES ANNO 2026

Hört, hört, wertes Feiervolk und edle Zechkumpanen!
Seit nunmehr 40 stolzen Jahren regiert König Sascha I. das Herzogtum Haumdaucher mit unerschütterlicher Kraft, seiner legendären dunklen Sonnenbrille und wummernden 135-BPM-Techno-Bässen aus den Schlossmauern!

Doch kurz vor dem heutigen Thronjubiläum schlug das finstere Schicksal zu: Die königliche Schatzkammer wurde geplündert! Sämtliche 40 geheiligten Kronkorken des Reiches liegen über ganz Haumdaucher verstreut – und Saschas geliebte Schwestern sind in den Wolkentürmen verschollen!

🚨 Die Lage ist dramatisch: Ohne Kronkorken kein kühles Helles, ohne Helles kein Bass – und ohne Bass keine Jubiläumsfeier! Das Schicksal der Regentschaft liegt in deinen Händen!
⚔️ DEINE KÖNIGLICHEN MISSIONEN IM SPIEL ⚔️
☁️ AKT II: SCHWESTERN-RETTUNG

Hüpfe im Arcade-Doodle-Jump von Wolke zu Wolke hinauf und befreie Saschas Schwestern Saskia, Maja & Miriam aus ihren Wolkentürmen!

🍻 AKT III: DER SCHANKRAUM

Erkunde das Wirtshaus: Triff Königin Verena auf der Galerie, Lutz im Holzbottich, Armend am Schmiede-Amboss & öffne die geheime Luke zum VIP-Bunker!

🐗 HOF-DUELLE: NAT & MARKUS

Besiege Hof-Barde NAT im Wildschwein-Schnick-Schnack-Schnuck und bluffe Markus (Vater) beim taiwanesischen Liar's Dice (吹牛) ab!

🎸 AKT IV & V: KONZERT & URKUNDE

Rocke an der Minnesänger-Laute (Mittelalter Guitar Hero) mit Ritter Hansi die Schlossbühne und erhalte deine persönliche Highscore-Thronurkunde!

QR Code zum Spiel 👑 SCAN & PLAY 👑
📱 JETZT SCANNEN & DIREKT MITFEIERN!
  • 1 Kamera-App des Smartphones auf den QR-Code richten
  • 2 Tippe auf den Link oder öffne direkt im Browser
  • 3 Lautstärke aufdrehen für 135-BPM-Mittelalter-Soundtrack!
  • 4 Kronkorken sammeln & den Party-Highscore knacken!
🌐 https://sasch40.haumdaucher.de
⚜️ DAS HAUMDAUCHER GEFOLGE – ALLE CHARAKTERE IM SPIEL ⚜️
${entourage.map(c => `
${c.name} ${c.name.split(' ')[0]}
`).join('')}
Besiegelt durch das königliche Hofamt Haumdaucher zum 40. Geburtstag von König Sascha I. • Anno 2026 • Code & Pixel von Mop
`; const htmlPath = path.join(BASE_DIR, 'Saschas_Kronkorken_Kreuzzug_Jubilaeum.html'); fs.writeFileSync(htmlPath, htmlContent, 'utf-8'); console.log(`✅ Saved HTML template to ${htmlPath}`); // Launch Puppeteer to render pixel-perfect A4 PDF const browser = await puppeteer.launch({ executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox'] }); const page = await browser.newPage(); await page.setContent(htmlContent, { waitUntil: 'load', timeout: 25000 }); try { await page.evaluateHandle('document.fonts.ready'); } catch (e) {} await new Promise(r => setTimeout(r, 1000)); // Output paths const pdfFileName = 'Saschas_Kronkorken_Kreuzzug_Jubilaeum.pdf'; const outPdf1 = path.join(BASE_DIR, pdfFileName); const outPdf2 = path.join(BASE_DIR, 'frontend/public', pdfFileName); const outPdf3 = path.join(ARTIFACTS_DIR, pdfFileName); console.log('🖨️ Rendering A4 PDF...'); const pdfBuffer = await page.pdf({ format: 'A4', printBackground: true, margin: { top: '0mm', right: '0mm', bottom: '0mm', left: '0mm' }, preferCSSPageSize: true }); fs.writeFileSync(outPdf1, pdfBuffer); fs.writeFileSync(outPdf2, pdfBuffer); fs.writeFileSync(outPdf3, pdfBuffer); console.log(`✅ PDF written to:\n 1. ${outPdf1}\n 2. ${outPdf2}\n 3. ${outPdf3}`); // Also take a high-res PNG preview of the printed page for visual review! await page.setViewport({ width: 1240, height: 1754, deviceScaleFactor: 2 }); const previewPng = path.join(ARTIFACTS_DIR, 'Saschas_Kronkorken_Kreuzzug_Druck_Vorschau.png'); await page.screenshot({ path: previewPng, fullPage: true }); console.log(`📸 Rendered high-res preview screenshot to ${previewPng}`); await browser.close(); console.log('🎉 PDF generation complete!'); } generatePDF().catch(err => { console.error('❌ Error generating PDF:', err); process.exit(1); });