// Generate the social-embed card images (1200×630 JPG — the OG standard size // every unfurler renders, and JPG because webp still fails on iMessage and // LinkedIn). Outputs are committed; re-run only when the art should change. // // card-default.jpg black stage, centered Bigscreen wordmark, one violet // signal bar along the bottom edge (The Single Signal Rule) // card-home.jpg the homepage hero lifestyle shot, center-cropped // // Usage: node scripts/build-og-cards.mjs import fs from "node:fs"; import path from "node:path"; import sharp from "sharp"; const OUT_DIR = "public/landing/og"; const WORDMARK = "public/landing/assets/080-asset.svg"; // white "bigscreen" wordmark, viewBox 392×56 const HOME_SHOT = "public/images/home/hero_transparant2.webp"; // the live "This is Beyond 2" hero const W = 1200; const H = 630; const VIOLET = "#4D1EF7"; fs.mkdirSync(OUT_DIR, { recursive: true }); // --- card-default: wordmark on the black bench ----------------------------- const markWidth = 520; // ~43% of the card; reads at thumbnail size without shouting const markHeight = Math.round((markWidth / 392) * 56); const mark = await sharp(Buffer.from(fs.readFileSync(WORDMARK, "utf8"))) .resize({ width: markWidth }) .png() .toBuffer(); const barH = 12; const base = Buffer.from( ` `, ); await sharp(base) .composite([ { input: mark, left: Math.round((W - markWidth) / 2), // optical center: nudged up so the bottom bar doesn't crowd the mark top: Math.round((H - barH - markHeight) / 2) - 8, }, ]) .jpeg({ quality: 90 }) .toFile(path.join(OUT_DIR, "card-default.jpg")); // --- photo cards: page heroes, center-cropped to 1.91:1 -------------------- const PHOTO_CARDS = [ ["card-home.jpg", HOME_SHOT], ["card-enterprise.jpg", "public/landing/business/assets/slide-transparant-sim.webp"], ["card-press.jpg", "public/landing/press/images/beyond-2.jpg"], ]; for (const [name, src] of PHOTO_CARDS) { await sharp(src) .resize(W, H, { fit: "cover", position: "attention" }) .jpeg({ quality: 84 }) .toFile(path.join(OUT_DIR, name)); } for (const f of ["card-default.jpg", ...PHOTO_CARDS.map(([n]) => n)]) { const kb = (fs.statSync(path.join(OUT_DIR, f)).size / 1024).toFixed(0); console.log(`${OUT_DIR}/${f} (${kb} KB)`); }