Skip to content

Commit 4dd313a

Browse files
committed
feat: og-image: Support multiple sizes and resolutions
Generates OG images in 16x9, 4x3, and 1x1 aspect ratios. Updates templates and scripts to handle multiple output sizes.
1 parent e415382 commit 4dd313a

File tree

5 files changed

+147
-81
lines changed

5 files changed

+147
-81
lines changed

assets/og-template/template.html

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,79 +12,96 @@
1212
margin: 0;
1313
padding: 0;
1414
font-family: "Inter", Arial, sans-serif;
15+
width: 100vw;
16+
height: 100vh;
17+
display: flex;
18+
align-items: center;
19+
justify-content: center;
1520
}
1621
.canvas {
1722
position: relative;
18-
width: 1200px;
19-
height: 630px;
23+
width: 100%;
24+
height: 100%;
2025
background-color: #f9fafb;
2126
overflow: hidden;
2227
display: flex;
2328
align-items: center;
2429
justify-content: center;
30+
box-sizing: border-box;
2531
}
2632
.background {
2733
position: absolute;
2834
z-index: 1;
29-
opacity: 0.25;
30-
bottom: -35px;
31-
left: 380px;
32-
width: 900px;
35+
opacity: 0.2;
36+
right: -30px;
37+
bottom: 0;
38+
width: 80vmin;
3339
height: auto;
3440
transform: rotate(-15deg);
3541
}
3642
.content-wrapper {
3743
position: relative;
3844
z-index: 2;
39-
width: 1100px;
40-
height: 570px;
45+
width: 100%;
46+
height: 100%;
4147
display: flex;
4248
flex-direction: column;
4349
justify-content: space-between;
50+
box-sizing: border-box;
51+
padding: 6vmin;
4452
}
4553
.header {
4654
display: flex;
4755
align-items: center;
56+
flex-shrink: 0;
4857
}
4958
.logo {
50-
height: 90px;
59+
height: 12vmin;
5160
width: auto;
52-
margin-right: 30px;
61+
margin-right: 3vmin;
5362
}
5463
.logo-text {
55-
font-size: 56px;
64+
font-size: 6vmin;
65+
line-height: 1.1;
5666
font-weight: 700;
5767
color: #1f2937;
5868
}
5969
.main-text {
70+
flex-grow: 1;
71+
display: flex;
72+
flex-direction: column;
73+
justify-content: center;
74+
padding: 4vmin 0;
6075
}
6176
.title {
62-
font-size: 64px;
77+
font-size: clamp(32px, 8vmin, 80px);
6378
font-weight: 700;
6479
line-height: 1.2;
6580
color: #1f2937;
66-
margin: 0 0 20px 0;
81+
margin: 0 0 2vmin 0;
6782
display: -webkit-box;
68-
-webkit-line-clamp: 3;
83+
-webkit-line-clamp: 4;
6984
-webkit-box-orient: vertical;
7085
overflow: hidden;
86+
word-wrap: break-word;
7187
}
7288
.description {
73-
font-size: 36px;
89+
font-size: clamp(24px, 4.5vmin, 48px);
7490
line-height: 1.4;
7591
color: #4b5563;
7692
margin: 0;
7793
display: -webkit-box;
78-
-webkit-line-clamp: 3;
94+
-webkit-line-clamp: 4;
7995
-webkit-box-orient: vertical;
8096
overflow: hidden;
81-
text-shadow: 0 0 10px white, 0 0 15px white, 0 0 20px white;
97+
word-wrap: break-word;
8298
}
8399
.footer {
84-
font-size: 35px;
100+
font-size: clamp(20px, 4vmin, 40px);
85101
font-weight: 500;
86102
color: #667eea;
87103
text-align: left;
104+
flex-shrink: 0;
88105
}
89106
</style>
90107
</head>

layouts/partials/components/base-seo.html

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@
5555

5656
{{ $ogImageNameWithExt := "" }}
5757
{{ if .IsHome }}
58-
{{ $ogImageNameWithExt = "og-image.jpg" }}
59-
{{/* Homepage OG image is expected at static/images/og-image.jpg by the Node script */}}
58+
{{ $ogImageNameWithExt = "og-image-16x9.jpg" }}
59+
{{/* Homepage OG image is expected at static/images/og-image-16x9.jpg by the Node script */}}
6060
{{ $homeOgStaticPath := printf "images/%s" $ogImageNameWithExt }}
6161
{{ if fileExists (printf "static/%s" $homeOgStaticPath) }}
6262
{{ $finalOgImageAbsURL = ($homeOgStaticPath | absURL) }}
@@ -74,18 +74,18 @@
7474
{{ $pageSlug = .Title | urlize }} {{/* Fallback, less likely for actual content pages */}}
7575
{{ warnf "Page %s: .File.Dir not available, falling back to title-based slug '%s' for OG image name." (.RelPermalink | default .Path) $pageSlug }}
7676
{{ end }}
77-
{{ $ogImageNameWithExt = printf "%s-og.jpg" $pageSlug }}
77+
{{ $ogImageNameWithExt = printf "%s-og-16x9.jpg" $pageSlug }}
7878
{{ $ogImageSourceDebug = printf "Content Page: derived OG name '%s' from dir slug '%s'" $ogImageNameWithExt $pageSlug }}
7979
{{ else }}
8080
{{/* Fallback for pages without .File (e.g., some list pages, 404, etc.) */}}
8181
{{ $pageSlug := .Title | urlize }}
82-
{{ $ogImageNameWithExt = printf "%s-og.jpg" $pageSlug }}
82+
{{ $ogImageNameWithExt = printf "%s-og-16x9.jpg" $pageSlug }}
8383
{{ $ogImageSourceDebug = printf "Fallback Page (no .File): derived OG name '%s' from title slug '%s'" $ogImageNameWithExt $pageSlug }}
8484
{{ end }}
8585

8686

8787
{{/* Try to find the image based on $ogImageNameWithExt */}}
88-
{{/* 1. Page Bundle check for [slug]-og.jpg (for content pages) */}}
88+
{{/* 1. Page Bundle check for [slug]-og-16x9.jpg (for content pages) */}}
8989
{{ if not $finalOgImageAbsURL }} {{/* Only if homepage check didn't set it */}}
9090
{{ if and (not .IsHome) .File }} {{/* For content pages, prioritize page bundle */}}
9191
{{ $pageBundleOG := .Resources.GetMatch $ogImageNameWithExt }}
@@ -98,8 +98,7 @@
9898
{{ end }}
9999
{{ end }}
100100

101-
{{/* ------------------------- NEW/MODIFIED SECTION START ------------------------- */}}
102-
{{/* 2. Static /images/[slug]-og.jpg check (if not found above and not homepage) */}}
101+
{{/* 2. Static /images/[slug]-og-16x9.jpg check (if not found above and not homepage) */}}
103102
{{ if and (not $finalOgImageAbsURL) (not .IsHome) }}
104103
{{ $staticOgPathForSlug := printf "images/%s" $ogImageNameWithExt }}
105104
{{ if fileExists (printf "static/%s" $staticOgPathForSlug) }}
@@ -111,7 +110,6 @@
111110
{{ $ogImageSourceDebug = printf "%s (and NOT found in static/images as %s)" $ogImageSourceDebug $staticOgPathForSlug }}
112111
{{ end }}
113112
{{ end }}
114-
{{/* ------------------------- NEW/MODIFIED SECTION END --------------------------- */}}
115113

116114

117115
{{/* Standard fallbacks if specific image not found yet */}}

layouts/partials/structured-markup/blog.html

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,44 @@
11
{{ $page_context := . }}
2-
{{ $defaultOgImage := site.Params.metadata.image | default "images/og-image.png" | absLangURL }}
3-
{{ $imagePath := $page_context.Params.image }}
4-
{{ $resolvedImageURL := "" }}
5-
{{ if $imagePath }}
6-
{{ if hasPrefix $imagePath "/" }}
7-
{{ $resolvedImageURL = $imagePath | absLangURL }}
8-
{{ else }}
9-
{{ with $page_context.Resources.GetMatch $imagePath }}
10-
{{ $resolvedImageURL = .Permalink }}
11-
{{ else }}
12-
{{ $resolvedImageURL = printf "%s%s" $page_context.RelPermalink $imagePath | absLangURL }}
2+
{{ $imageUrls := slice }}
3+
4+
{{ if .File }}
5+
{{ $pageSlug := path.Base .File.Dir }}
6+
{{ $suffixes := slice "16x9" "4x3" "1x1" }}
7+
{{ range $suffixes }}
8+
{{ $ogImageName := printf "%s-og-%s.jpg" $pageSlug . }}
9+
{{ with $page_context.Resources.GetMatch $ogImageName }}
10+
{{ $imageUrls = $imageUrls | append .Permalink }}
11+
{{ end }}
12+
{{ end }}
13+
{{ end }}
14+
15+
{{ if not $imageUrls }}
16+
{{ with .Params.image }}
17+
{{ $fmImage := . }}
18+
{{ $imageRes := "" }}
19+
{{ with $page_context.Resources.GetMatch $fmImage }}
20+
{{ $imageRes = . }}
21+
{{ end }}
22+
23+
{{ if $imageRes }}
24+
{{ $imageUrls = $imageUrls | append $imageRes.Permalink }}
25+
{{ else }}
26+
{{ $imageUrls = $imageUrls | append ($fmImage | absURL) }}
27+
{{ end }}
28+
{{ end }}
29+
{{ end }}
30+
31+
{{ if not $imageUrls }}
32+
{{ with site.Params.metadata.image }}
33+
{{ $imageUrls = $imageUrls | append (. | absURL) }}
1334
{{ end }}
14-
{{ end }}
15-
{{ else }}
16-
{{ $resolvedImageURL = $defaultOgImage }}
1735
{{ end }}
1836

1937
{{ $blogLD := dict
2038
"@context" "https://schema.org"
2139
"@type" "BlogPosting"
2240
"headline" $page_context.Title
23-
"image" (slice $resolvedImageURL)
41+
"image" $imageUrls
2442
"datePublished" ($page_context.Date.Format "2006-01-02T15:04:05Z07:00")
2543
}}
2644
{{ if $page_context.Lastmod }}{{ $blogLD = merge $blogLD (dict "dateModified" ($page_context.Lastmod.Format "2006-01-02T15:04:05Z07:00")) }}{{ end }}

scripts/collectOgData.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ const BACKGROUND_IMAGE_PATH_IN_ASSETS = 'images/ONM.png';
1717
const OG_TEMPLATE_PATH = join(PROJECT_ROOT, 'assets', 'og-template', 'template.html');
1818
const HOMEPAGE_TITLE = "Advancing Neuromorphic Computing, Together.";
1919
const HOMEPAGE_DESCRIPTION = "Open Neuromorphic (ONM) is a global community fostering education, research, and open-source collaboration in brain-inspired AI and hardware.";
20+
const SIZES = [
21+
{ width: 1200, height: 630, suffix: '16x9' }, // Standard OG 1.91:1
22+
{ width: 1200, height: 900, suffix: '4x3' },
23+
{ width: 1080, height: 1080, suffix: '1x1' },
24+
];
2025

2126
// --- Helper Functions ---
2227
async function pathExists(path) {
@@ -119,10 +124,17 @@ async function collectData() {
119124
const homepageFinalHash = createHash(homepageContentHash + globalHash);
120125
const homepageOgDir = join(STATIC_DIR, 'images');
121126
await ensureDir(homepageOgDir);
127+
128+
const homepageOutputs = SIZES.map(size => ({
129+
path: join(homepageOgDir, `og-image-${size.suffix}.${OUTPUT_FORMAT}`),
130+
width: size.width,
131+
height: size.height,
132+
}));
133+
122134
outputData.pages.push({
123135
title: HOMEPAGE_TITLE,
124136
description: HOMEPAGE_DESCRIPTION,
125-
outputPath: join(homepageOgDir, `og-image.${OUTPUT_FORMAT}`),
137+
outputs: homepageOutputs,
126138
finalHash: homepageFinalHash
127139
});
128140

@@ -142,12 +154,17 @@ async function collectData() {
142154
const finalHash = createHash(contentHash + globalHash);
143155

144156
const parentDirName = basename(pageDirectory);
145-
const ogImageFilename = `${parentDirName}-og.${OUTPUT_FORMAT}`;
157+
158+
const pageOutputs = SIZES.map(size => ({
159+
path: join(pageDirectory, `${parentDirName}-og-${size.suffix}.${OUTPUT_FORMAT}`),
160+
width: size.width,
161+
height: size.height,
162+
}));
146163

147164
outputData.pages.push({
148165
title,
149166
description,
150-
outputPath: join(pageDirectory, ogImageFilename),
167+
outputs: pageOutputs,
151168
finalHash
152169
});
153170
} catch (err) {

scripts/generateOgImages.js

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -56,52 +56,68 @@ async function generateImages() {
5656

5757
// --- Process each page ---
5858
for (const pageData of jsonData.pages) {
59-
const { title, description, outputPath, finalHash } = pageData;
59+
const { title, description, outputs, finalHash } = pageData;
6060

61-
try {
62-
const fileAlreadyExists = await pathExists(outputPath);
63-
const isCacheValid = oldCache[outputPath] === finalHash;
61+
if (!outputs || outputs.length === 0) {
62+
console.warn(`⚠️ No outputs defined for page: ${title}`);
63+
continue;
64+
}
6465

65-
if (fileAlreadyExists && isCacheValid) {
66-
skippedCount++;
67-
newCache[outputPath] = finalHash; // Keep valid entry in new cache
68-
continue;
66+
// Process each output size for the current page
67+
for (const output of outputs) {
68+
const { path: outputPath, width, height } = output;
69+
70+
try {
71+
const fileAlreadyExists = await pathExists(outputPath);
72+
const isCacheValid = oldCache[outputPath] === finalHash;
73+
74+
if (fileAlreadyExists && isCacheValid) {
75+
skippedCount++;
76+
newCache[outputPath] = finalHash; // Keep valid entry
77+
continue;
78+
}
79+
80+
const page = await browser.newPage();
81+
await page.setViewport({ width, height });
82+
83+
const htmlContent = templateContent
84+
.replace('LOGO_SRC', jsonData.logoDataUri)
85+
.replace('BACKGROUND_URL', jsonData.backgroundDataUri || '')
86+
.replace('PAGE_TITLE', title)
87+
.replace('PAGE_DESCRIPTION', description);
88+
89+
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
90+
91+
await page.screenshot({
92+
path: outputPath,
93+
type: 'jpeg',
94+
quality: JPEG_QUALITY,
95+
});
96+
97+
const stats = await stat(outputPath);
98+
console.log(`✅ Generated: ${outputPath.replace(PROJECT_ROOT, '')} (${width}x${height}, ${(stats.size / 1024).toFixed(1)} KB)`);
99+
successCount++;
100+
newCache[outputPath] = finalHash; // Add new entry to cache
101+
102+
await page.close();
103+
} catch (err) {
104+
console.error(`❌ Failed generating image for: ${outputPath.replace(PROJECT_ROOT, '')}`);
105+
console.error(err.message || err);
106+
errorCount++;
69107
}
70-
71-
const page = await browser.newPage();
72-
await page.setViewport({ width: 1200, height: 630 });
73-
74-
const htmlContent = templateContent
75-
.replace('LOGO_SRC', jsonData.logoDataUri)
76-
.replace('BACKGROUND_URL', jsonData.backgroundDataUri || '')
77-
.replace('PAGE_TITLE', title)
78-
.replace('PAGE_DESCRIPTION', description);
79-
80-
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
81-
82-
await page.screenshot({
83-
path: outputPath,
84-
type: 'jpeg',
85-
quality: JPEG_QUALITY,
86-
});
87-
88-
const stats = await stat(outputPath);
89-
console.log(`✅ Generated: ${outputPath.replace(PROJECT_ROOT, '')} (${(stats.size / 1024).toFixed(1)} KB)`);
90-
successCount++;
91-
newCache[outputPath] = finalHash; // Add new entry to cache
92-
93-
await page.close();
94-
} catch (err) {
95-
console.error(`❌ Failed generating image for: ${outputPath.replace(PROJECT_ROOT, '')}`);
96-
console.error(err.message || err);
97-
errorCount++;
98108
}
99109
}
100110

101111
await browser.close();
102112

103113
// --- Cleanup stale images and cache entries ---
104-
const validOutputPaths = new Set(jsonData.pages.map(p => p.outputPath));
114+
const validOutputPaths = new Set();
115+
jsonData.pages.forEach(p => {
116+
if (p.outputs) {
117+
p.outputs.forEach(o => validOutputPaths.add(o.path));
118+
}
119+
});
120+
105121
let cleanedCount = 0;
106122
for (const oldPath in oldCache) {
107123
if (!validOutputPaths.has(oldPath)) {

0 commit comments

Comments
 (0)