// Renders the 1200x630 social preview (/og.jpg) with headless Chrome: a real screenshot // of the running app (one painting from the Church's Media Library, with its caption) // set in a quiet, warm scene, and every word is live text, so nothing is AI generated. // Writes src/og-image.ts (base64) and /tmp/christ-paintings-og.jpg. // // Usage: node scripts/render-og.mjs [appUrl] // appUrl defaults to https://christ-paintings.kentcdodds.com/ (or a local // `kody dev --app-base-path /` URL such as http://127.0.0.1:4598/). // Needs playwright-core resolvable from here (node_modules is gitignored) and a Chromium; // set CHROME_PATH to use a specific browser. Fonts: Cormorant Garamond and Inter. // The address at the bottom is the app URL's host; set OG_SITE_LABEL to change it. import { chromium } from 'playwright-core' import { writeFileSync } from 'node:fs' const APP_URL = process.argv[2] || 'https://christ-paintings.kentcdodds.com/' const SITE_LABEL = process.env.OG_SITE_LABEL || new URL(APP_URL).host // "Jesus the Christ" by Del Parson. const PAINTING_ID = process.env.OG_PAINTING_ID || 'jesus-christ-74e791b' const browser = await chromium.launch(process.env.CHROME_PATH ? { executablePath: process.env.CHROME_PATH } : {}) // 1. Screenshot the real app on a phone-sized screen, showing one painting and its caption. const phone = await browser.newPage({ viewport: { width: 390, height: 800 }, deviceScaleFactor: 3, isMobile: true, hasTouch: true }) await phone.addInitScript((id) => { try { localStorage.setItem('christ-paintings:current', id) localStorage.setItem('christ-paintings:caption', 'shown') } catch (e) {} }, PAINTING_ID) await phone.goto(APP_URL, { waitUntil: 'load' }) await phone.waitForFunction(() => { const slide = [...document.querySelectorAll('.slide')].find((x) => x.getAttribute('aria-hidden') === 'false') return slide && slide.querySelector('img.ready') && slide.querySelector('img').complete }, null, { timeout: 30000 }) await phone.evaluate(() => document.querySelectorAll('.update-toast').forEach((el) => el.remove())) await phone.waitForTimeout(600) const shot = (await phone.screenshot({ type: 'png' })).toString('base64') await phone.close() // 2. Compose the scene: a soft light from above, a few motes of dust, the phone, the words. let seed = 7 const rnd = () => ((seed = (seed * 16807) % 2147483647) / 2147483647) const motes = Array.from({ length: 70 }, () => ``).join('') const html = `
${motes}
For children at church

Paintings of
Jesus Christ

Official Latter-day Saint art, one painting at a time. Plus coloring pages.

${SITE_LABEL}
` const page = await browser.newPage({ viewport: { width: 1200, height: 630 }, deviceScaleFactor: 1 }) await page.setContent(html, { waitUntil: 'load' }) await page.evaluate(() => document.fonts.ready) const jpeg = await page.screenshot({ type: 'jpeg', quality: 88, clip: { x: 0, y: 0, width: 1200, height: 630 } }) await browser.close() writeFileSync('/tmp/christ-paintings-og.jpg', jpeg) writeFileSync('src/og-image.ts', '// Generated by scripts/render-og.mjs. Do not edit by hand.\nexport const OG_IMAGE_JPEG = \'' + jpeg.toString('base64') + '\'\n') console.log('og.jpg bytes', jpeg.length)