diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index 533c64709f..267fec016f 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -63,6 +63,20 @@

Redirecting…

{{ .Page.Title | default "Git" }}{{ if (isset .Params "subtitle") }} - {{ .Params.subtitle }}{{ end }} + + + + + + + + + + + + + + {{ $style := resources.Get "sass/application.scss" | resources.ExecuteAsTemplate "application.scss" . | css.Sass | resources.Minify | fingerprint }} diff --git a/script/take-og-image.js b/script/take-og-image.js new file mode 100644 index 0000000000..2127b4d799 --- /dev/null +++ b/script/take-og-image.js @@ -0,0 +1,114 @@ +#!/usr/bin/env node +// @ts-check + +/** + * Generate a 1200x630 Open Graph image for git-scm.com by taking a real + * screenshot of the built site, so the card reflects the site's actual look + * and feel. + * + * Usage: + * node script/take-og-image.js [worktree-path] + * + * The optional WORKTREE-PATH points at a git-scm.com worktree (default: the + * current directory). The script builds the site with Hugo, serves the built + * site with script/serve-public.js, captures the homepage in light mode at a + * 1200x630 clip, and writes the result to static/images/og-image.png in that + * worktree. + * + * This mirrors the repo's existing screenshot tooling + * (script/compare-screenshots.js) — same build/serve helpers, same Playwright + * stack — so the OG image is a genuine snapshot of the site rather than a + * hand-drawn card that would look nothing like git-scm.com. + */ + +const { chromium } = require('@playwright/test'); +const { spawn, execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const WORKTREE = path.resolve(process.argv[2] || '.'); +const PORT = 5100; +const VIEWPORT = { width: 1280, height: 720 }; +// 1200x630 social card: header + hero + the first two rows of the feature +// grid. The homepage's bottom half (release box and lower cards) is hidden so +// the card never shows a stale release version and nothing is cut off at the +// bottom edge. +const CLIP = { x: 40, y: 0, width: 1200, height: 630 }; +const OUTPUT = path.join(WORKTREE, 'static', 'images', 'og-image.png'); + +/** + * Compose the screenshot region: hide the ever-changing release box (which + * hard-codes the latest version number) plus lower-page content so the clip + * reads as a complete, timeless view of the site. + */ +async function composeForScreenshot(page) { + await page.evaluate(() => { + const hide = (sel) => document.querySelectorAll(sel).forEach((el) => { el.style.display = 'none'; }); + hide('#front-downloads'); // "Latest source release" box with version number + hide('#front-book'); // Pro Git promo row below the feature grid + // Keep only the first two feature-grid rows so the 1200x630 clip ends on a + // clean boundary instead of slicing the "Install"/"Community" row. + const lis = document.querySelectorAll('#front-nav ul li'); + for (let i = 4; i < lis.length; i++) lis[i].style.display = 'none'; + const ul = document.querySelector('#front-nav ul'); + if (ul) ul.style.paddingBottom = '24px'; + }); +} + +async function startServer() { + console.error(`Building Hugo site in ${WORKTREE}...`); + execSync('hugo', { cwd: WORKTREE, stdio: 'inherit' }); + + const serverScript = path.join(WORKTREE, 'script', 'serve-public.js'); + const server = spawn('node', [serverScript], { + cwd: WORKTREE, + env: { ...process.env, PORT: String(PORT) }, + stdio: ['ignore', 'pipe', 'inherit'], + }); + + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject(new Error('Server startup timeout')), 30000); + server.stdout.on('data', (data) => { + if (data.toString().includes('Now listening')) { + clearTimeout(timeout); + resolve(); + } + }); + server.on('error', (err) => { + clearTimeout(timeout); + reject(err); + }); + }); + + return server; +} + +async function main() { + const server = await startServer(); + const browser = await chromium.launch(); + + try { + const context = await browser.newContext({ + viewport: VIEWPORT, + colorScheme: 'light', + }); + const page = await context.newPage(); + + console.error(`Navigating to: http://localhost:${PORT}/`); + await page.goto(`http://localhost:${PORT}/`, { waitUntil: 'networkidle' }); + + await composeForScreenshot(page); + + fs.mkdirSync(path.dirname(OUTPUT), { recursive: true }); + await page.screenshot({ path: OUTPUT, clip: CLIP }); + console.error(`Saved: ${OUTPUT} (${CLIP.width}x${CLIP.height} clip of the homepage)`); + } finally { + await browser.close(); + server.kill(); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); \ No newline at end of file diff --git a/static/images/og-image.png b/static/images/og-image.png new file mode 100644 index 0000000000..ef60f4c1bb Binary files /dev/null and b/static/images/og-image.png differ