Skip to content

Commit a6828af

Browse files
committed
feat: og-image: Improve event template and generation
Refactor event template for better speaker layout and reduce overall generation time by reusing a single page. Reduced padding, adjusted font sizes, and optimized speaker image sizing. Also use domcontentloaded to speed up processing.
1 parent 7089927 commit a6828af

File tree

2 files changed

+73
-59
lines changed

2 files changed

+73
-59
lines changed

assets/og-template/event-template.html

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@
2727
display: flex;
2828
flex-direction: column;
2929
box-sizing: border-box;
30-
padding: 6vmin;
30+
padding: 5vmin;
3131
}
3232
.background {
3333
position: absolute;
3434
z-index: 1;
35-
opacity: 0.1;
35+
opacity: 0.15;
3636
width: 150%;
3737
height: auto;
3838
right: -25%;
@@ -42,6 +42,7 @@
4242
.header {
4343
display: flex;
4444
align-items: center;
45+
justify-content: center;
4546
flex-shrink: 0;
4647
margin-bottom: 4vmin;
4748
}
@@ -65,11 +66,11 @@
6566
text-align: center;
6667
}
6768
.title {
68-
font-size: clamp(30px, 6.5vmin, 65px); /* Further decreased font size */
69+
font-size: clamp(30px, 6.2vmin, 62px);
6970
font-weight: 800;
7071
line-height: 1.2;
7172
color: #1f2937;
72-
margin: 0 0 4vmin 0; /* Reduced bottom margin slightly */
73+
margin: 0 0 4vmin 0;
7374
display: -webkit-box;
7475
-webkit-line-clamp: 5;
7576
-webkit-box-orient: vertical;
@@ -80,15 +81,15 @@
8081
display: flex;
8182
justify-content: center;
8283
align-items: center;
83-
min-height: 25vmin; /* Ensure space is reserved */
84+
min-height: 28vmin;
8485
}
8586
.speakers-container {
8687
display: flex;
8788
justify-content: center;
88-
align-items: flex-start; /* Align items to top */
89+
align-items: flex-start;
8990
gap: 2.5vmin;
9091
flex-wrap: wrap;
91-
max-width: 95%; /* Prevent overflow */
92+
max-width: 95%;
9293
}
9394
.speaker-item {
9495
display: flex;
@@ -104,12 +105,12 @@
104105
flex-shrink: 0;
105106
}
106107
.speaker-name {
107-
font-size: clamp(16px, 2.8vmin, 26px);
108-
font-weight: 500;
109-
color: #374151; /* gray-700 */
110-
line-height: 1.2;
111-
max-width: 100%;
112-
word-wrap: break-word;
108+
font-size: clamp(18px, 3.2vmin, 28px);
109+
font-weight: 500;
110+
color: #374151;
111+
line-height: 1.2;
112+
max-width: 100%;
113+
word-wrap: break-word;
113114
}
114115
.event-details {
115116
font-size: clamp(28px, 5.5vmin, 55px);
@@ -119,8 +120,8 @@
119120
font-weight: 500;
120121
}
121122
.event-date {
122-
font-weight: 700;
123-
color: #667eea; /* Primary color */
123+
font-weight: 700;
124+
color: #667eea;
124125
}
125126
.footer {
126127
font-size: clamp(20px, 4vmin, 40px);
@@ -158,31 +159,35 @@ <h1 class="title">PAGE_TITLE</h1>
158159
document.addEventListener('DOMContentLoaded', () => {
159160
const speakerContainer = document.querySelector('.speakers-container');
160161
if (!speakerContainer) return;
161-
const speakerCount = speakerContainer.children.length;
162+
const speakerItems = speakerContainer.querySelectorAll('.speaker-item');
163+
const speakerCount = speakerItems.length;
162164

163165
if (speakerCount === 0) return;
164166

165-
let size = '20vmin'; // Default
167+
let size = '22vmin';
166168

167169
if (speakerCount === 1) {
168-
size = '40vmin';
170+
size = '42vmin';
169171
} else if (speakerCount === 2) {
170-
size = '30vmin';
172+
size = '32vmin';
171173
} else if (speakerCount === 3) {
172-
size = '25vmin';
174+
size = '27vmin';
173175
} else if (speakerCount >= 4 && speakerCount <= 6) {
174-
size = '22vmin';
176+
size = '24vmin';
175177
} else if (speakerCount > 6) {
176-
size = '18vmin';
178+
size = '20vmin';
177179
}
178180

179-
const speakerImages = document.querySelectorAll('.speaker-img');
180-
speakerImages.forEach(img => {
181-
img.style.width = size;
182-
img.style.height = size;
181+
speakerItems.forEach(item => {
182+
item.style.width = size;
183+
const img = item.querySelector('.speaker-img');
184+
if (img) {
185+
img.style.width = size;
186+
img.style.height = size;
187+
}
183188
});
184189
});
185190
</script>
186191

187192
</body>
188-
</html>
193+
</html>

scripts/generateOgImages.js

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const { readFile, writeFile, unlink, stat } = require('fs/promises');
77
const PROJECT_ROOT = process.cwd();
88
const TMP_DIR = join(PROJECT_ROOT, 'tmp');
99
const TEMPLATE_PATH = join(PROJECT_ROOT, 'assets', 'og-template', 'template.html');
10-
const EVENT_TEMPLATE_PATH = join(PROJECT_ROOT, 'assets', 'og-template', 'event-template.html'); // New
10+
const EVENT_TEMPLATE_PATH = join(PROJECT_ROOT, 'assets', 'og-template', 'event-template.html');
1111
const INPUT_JSON_PATH = join(TMP_DIR, 'ogImageData.json');
1212
const CACHE_MANIFEST_PATH = join(TMP_DIR, 'og-cache-manifest.json');
1313
const JPEG_QUALITY = 90;
@@ -17,6 +17,7 @@ async function pathExists(path) {
1717
}
1818

1919
async function generateImages() {
20+
const startTime = Date.now();
2021
console.log('🖼️ Starting OG Image Generation with Caching...');
2122

2223
// --- Pre-flight checks ---
@@ -31,26 +32,27 @@ async function generateImages() {
3132
const defaultTemplateContent = await readFile(TEMPLATE_PATH, 'utf8');
3233
const eventTemplateContent = await readFile(EVENT_TEMPLATE_PATH, 'utf8');
3334
const jsonData = JSON.parse(await readFile(INPUT_JSON_PATH, 'utf8'));
34-
35+
3536
let oldCache = {};
3637
try {
3738
oldCache = JSON.parse(await readFile(CACHE_MANIFEST_PATH, 'utf8'));
3839
} catch (e) {
3940
console.log('ℹ️ No existing cache manifest found. Will generate all images.');
4041
}
41-
42+
4243
const newCache = {};
4344

4445
if (!jsonData || !jsonData.pages || jsonData.pages.length === 0) {
4546
console.warn('⚠️ No pages to process.');
4647
return;
4748
}
4849

49-
// --- Launch Puppeteer ---
50+
// --- Launch Puppeteer and create ONE reusable page ---
5051
const browser = await puppeteer.launch({
5152
headless: true,
5253
args: ['--no-sandbox', '--disable-setuid-sandbox'],
5354
});
55+
const page = await browser.newPage();
5456

5557
let successCount = 0;
5658
let errorCount = 0;
@@ -78,37 +80,44 @@ async function generateImages() {
7880
newCache[outputPath] = finalHash; // Keep valid entry
7981
continue;
8082
}
81-
82-
const page = await browser.newPage();
83+
84+
// Reuse the single page
8385
await page.setViewport({ width, height });
8486

8587
let htmlContent;
8688
if (template === 'event') {
87-
const speakersHtml = (speakers || [])
88-
.map(speaker => `
89-
<div class="speaker-item">
90-
${speaker.imageUri ? `<img src="${speaker.imageUri}" class="speaker-img" alt="Photo of ${speaker.name}" />` : ''}
89+
const speakersHtml = (speakers || [])
90+
.map(speaker => `
91+
<div class="speaker-item" style="width: ${
92+
speaker.count === 1 ? '42vmin' :
93+
speaker.count === 2 ? '32vmin' :
94+
speaker.count === 3 ? '27vmin' :
95+
speaker.count >= 4 && speaker.count <= 6 ? '24vmin' :
96+
'20vmin'
97+
};">
98+
${speaker.imageUri ? `<img src="${speaker.imageUri}" class="speaker-img" alt="Photo of ${speaker.name}" style="width: 100%; height: 100%;" />` : ''}
9199
<div class="speaker-name">${speaker.name}</div>
92100
</div>
93101
`)
94-
.join('');
95-
96-
htmlContent = eventTemplateContent
97-
.replace('LOGO_SRC', jsonData.logoDataUri)
98-
.replace('BACKGROUND_URL', jsonData.backgroundDataUri || '')
99-
.replace('PAGE_TITLE', title)
100-
.replace('<!-- SPEAKER_IMAGES_HTML will be injected here -->', speakersHtml)
101-
.replace('EVENT_DATE', eventDate || '')
102-
.replace('EVENT_TIME', eventTime || '');
102+
.join('');
103+
104+
htmlContent = eventTemplateContent
105+
.replace('LOGO_SRC', jsonData.logoDataUri)
106+
.replace('BACKGROUND_URL', jsonData.backgroundDataUri || '')
107+
.replace('PAGE_TITLE', title)
108+
.replace('<!-- SPEAKER_IMAGES_HTML will be injected here -->', speakersHtml)
109+
.replace('EVENT_DATE', eventDate || '')
110+
.replace('EVENT_TIME', eventTime || '');
103111
} else {
104-
htmlContent = defaultTemplateContent
105-
.replace('LOGO_SRC', jsonData.logoDataUri)
106-
.replace('BACKGROUND_URL', jsonData.backgroundDataUri || '')
107-
.replace('PAGE_TITLE', title)
108-
.replace('PAGE_DESCRIPTION', description);
112+
htmlContent = defaultTemplateContent
113+
.replace('LOGO_SRC', jsonData.logoDataUri)
114+
.replace('BACKGROUND_URL', jsonData.backgroundDataUri || '')
115+
.replace('PAGE_TITLE', title)
116+
.replace('PAGE_DESCRIPTION', description);
109117
}
110118

111-
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
119+
// Use 'domcontentloaded' for faster processing
120+
await page.setContent(htmlContent, { waitUntil: 'domcontentloaded' });
112121

113122
await page.screenshot({
114123
path: outputPath,
@@ -120,16 +129,15 @@ async function generateImages() {
120129
console.log(`✅ Generated: ${outputPath.replace(PROJECT_ROOT, '')} (${width}x${height}, ${(stats.size / 1024).toFixed(1)} KB)`);
121130
successCount++;
122131
newCache[outputPath] = finalHash; // Add new entry to cache
123-
124-
await page.close();
132+
125133
} catch (err) {
126134
console.error(`❌ Failed generating image for: ${outputPath.replace(PROJECT_ROOT, '')}`);
127135
console.error(err.message || err);
128136
errorCount++;
129137
}
130138
}
131139
}
132-
140+
133141
await browser.close();
134142

135143
// --- Cleanup stale images and cache entries ---
@@ -139,7 +147,7 @@ async function generateImages() {
139147
p.outputs.forEach(o => validOutputPaths.add(o.path));
140148
}
141149
});
142-
150+
143151
let cleanedCount = 0;
144152
for (const oldPath in oldCache) {
145153
if (!validOutputPaths.has(oldPath)) {
@@ -150,17 +158,18 @@ async function generateImages() {
150158
}
151159
}
152160
if (cleanedCount > 0) {
153-
console.log(`🗑️ Cleaned up ${cleanedCount} stale OG image(s).`);
161+
console.log(`🗑️ Cleaned up ${cleanedCount} stale OG image(s).`);
154162
}
155163

156164
// --- Write the new cache manifest ---
157165
await writeFile(CACHE_MANIFEST_PATH, JSON.stringify(newCache, null, 2));
158166

159-
console.log(`\n✨ Generation complete! Succeeded: ${successCount}, Skipped: ${skippedCount}, Failed: ${errorCount}.`);
167+
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
168+
console.log(`\n✨ Generation complete in ${duration}s! Succeeded: ${successCount}, Skipped: ${skippedCount}, Failed: ${errorCount}.`);
160169
if (errorCount > 0) process.exit(1);
161170
}
162171

163172
generateImages().catch(err => {
164173
console.error("🔥 Uncaught error during image generation:", err);
165174
process.exit(1);
166-
});
175+
});

0 commit comments

Comments
 (0)