#!/usr/bin/env node import sharp from 'sharp'; import { readFileSync } from 'fs'; const SCREENSHOT = 'docs/screenshots/01-web-light.png'; const BG_COLOR = '#1A1A28'; // Social image dimensions const DIMENSIONS = { 'og-image.png': { width: 1200, height: 630 }, 'twitter-image.png': { width: 1200, height: 675 }, 'social-preview.png': { width: 1280, height: 640 } }; async function createSocialImage(filename, width, height) { // Load and resize screenshot to fit right side (60% of width) const screenshotWidth = Math.floor(width * 0.55); const screenshotHeight = Math.floor(height * 0.7); const screenshot = await sharp(SCREENSHOT) .resize(screenshotWidth, screenshotHeight, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }) .toBuffer(); // Create SVG with text and layout const svg = ` SELF-HOSTED ยท OPEN SOURCE Oikos The family planner that respects your privacy. Tasks, calendars, shopping, meals, budget โ€” on your own server. โœ“ Tasks ๐Ÿ“… Calendar ๐Ÿ›’ Shopping ๐Ÿฝ Meals `; // Position screenshot on the right side const screenshotX = width - screenshotWidth - 40; const screenshotY = Math.floor((height - screenshotHeight) / 2); // Composite everything together const image = await sharp({ create: { width, height, channels: 4, background: BG_COLOR } }) .composite([ { input: Buffer.from(svg), top: 0, left: 0 }, { input: screenshot, top: screenshotY, left: screenshotX } ]) .png() .toFile(`docs/${filename}`); console.log(`โœ“ Created docs/${filename} (${width}x${height})`); } async function main() { console.log('Generating social preview images...\n'); for (const [filename, { width, height }] of Object.entries(DIMENSIONS)) { await createSocialImage(filename, width, height); } console.log('\nโœ“ All social images generated successfully!'); } main().catch(err => { console.error('Error:', err.message); process.exit(1); });