cover-image — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cover-image (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Generate blog cover images and Open Graph/social preview images as PNG files. Each cover is a self-contained HTML document with inline CSS, captured as a screenshot via Chrome headless at exactly 1200x630 pixels.
The output is a static PNG suitable for Hugo front matter (image:), HTML <meta property="og:image">, and Twitter card metadata. No external images, no JavaScript, no server-side rendering required at capture time.
Cover images serve as the primary visual identity for a blog post across social platforms, search results, and link previews. Each image is designed from scratch per post, reflecting the topic, tone, and key concepts of the content.
What this skill produces:
When to use this skill:
image: fieldRead the blog post or article to extract the title, key concepts, and overall tone. The cover image must accurately represent the content. Identify:
If the user provides only a title without a file path, ask for the target output location before proceeding.
Select a design approach based on the post topic and tone:
Consult references/cover-design.md for the full color palette and typography specifications for each direction.
Create a complete, self-contained HTML file. All styles must be inline within a <style> block. No external stylesheets, no <link> tags (except Google Fonts via @import), no external images, and no JavaScript.
Hard constraints:
bodyoverflow: hidden on body to prevent scroll artifacts@import url(...) inside the <style> block<img> tags, no background-image: url(...) pointing to external filesSee the HTML boilerplate section below for a complete starting template.
Save the HTML to a temporary location. Use the project's tmp/ directory:
tmp/cover-<slug>.htmlDerive the slug from the post title (lowercase, hyphens, max 30 characters). Create the tmp/ directory if it does not exist. Writing to tmp/ keeps generated HTML separate from source content and makes cleanup straightforward.
Run the screenshot script to render the HTML to PNG:
bash scripts/screenshot.sh tmp/cover-<slug>.html <output-path>/cover.pngThe script handles Chrome headless invocation with the correct flags (--headless=new, --disable-gpu, --no-sandbox, --hide-scrollbars). It outputs the PNG at the specified path.
Important: Run the script from the skill directory so that scripts/screenshot.sh resolves correctly, or use an absolute path to the script.
Read the generated PNG file to visually verify it rendered correctly. Check for:
If the image does not look correct, adjust the HTML and re-run the screenshot. Common fixes are in the troubleshooting section below.
After confirming the PNG renders correctly, delete the temp HTML file:
rm tmp/cover-<slug>.htmlDo not leave temp HTML files in the repository. If the tmp/ directory is now empty, remove it as well.
Add the cover image to the blog post front matter:
---
title: "Post Title"
image: /images/category/cover.png
---Copy or move the PNG to the appropriate static images directory for the blog. The exact path depends on the blog's static file structure.
Use this template as the starting point for every cover image. It includes the correct dimensions, font loading, layout zones, and branding placement.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&family=JetBrains+Mono:wght@400;700&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px;
height: 630px;
overflow: hidden;
font-family: 'Inter', 'Helvetica Neue', Arial, sans-serif;
background: #0f172a;
color: #f8fafc;
position: relative;
}
/* Background decoration */
.bg-circle {
position: absolute;
border-radius: 50%;
opacity: 0.06;
}
.bg-circle-1 {
width: 600px;
height: 600px;
background: linear-gradient(135deg, #6366f1, #8b5cf6);
top: -200px;
right: -100px;
}
.bg-circle-2 {
width: 400px;
height: 400px;
background: linear-gradient(135deg, #06b6d4, #3b82f6);
bottom: -150px;
left: -50px;
}
/* Layout */
.cover {
position: relative;
z-index: 1;
padding: 60px 80px;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
/* Series label */
.series {
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1.5px;
color: #818cf8;
margin-bottom: 20px;
}
/* Title */
.title {
font-size: 56px;
font-weight: 800;
line-height: 1.15;
max-width: 900px;
margin-bottom: 16px;
}
/* Subtitle */
.subtitle {
font-size: 22px;
font-weight: 400;
color: #94a3b8;
max-width: 700px;
line-height: 1.4;
}
/* Branding */
.branding {
position: absolute;
bottom: 24px;
right: 80px;
font-size: 14px;
color: #94a3b8;
font-weight: 500;
}
</style>
</head>
<body>
<div class="bg-circle bg-circle-1"></div>
<div class="bg-circle bg-circle-2"></div>
<div class="cover">
<div class="series">Series Label</div>
<div class="title">Post Title Goes Here</div>
<div class="subtitle">Optional subtitle or tagline</div>
</div>
<div class="branding">widnyana.web.id</div>
</body>
</html>Replace the dark-specific styles:
body {
background: #ffffff;
color: #0f172a;
}
.bg-circle {
opacity: 0.04;
}
.series {
color: #3b82f6;
}
.subtitle {
color: #64748b;
}
.branding {
color: #64748b;
}The structure remains identical. Only background, text, and accent colors change.
| Topic Type | Background | Rationale |
|---|---|---|
| Programming, Rust, Solidity | Dark (#0f172a) | Technical depth |
| Infrastructure, DevOps, CI/CD | Dark (#0f172a) | Systems-level content |
| Security, auditing | Dark (#0f172a) | Seriousness, gravity |
| Blockchain, crypto, DeFi | Dark (#0f172a) | Industry convention |
| Tutorials, guides, how-to | Light (#ffffff) | Approachable, clear |
| Architecture, design patterns | Gradient | Breadth, sophistication |
| Career, soft skills | Light (#ffffff) | Friendly, readable |
| Announcements, launches | Bold gradient | Energy, prominence |
Always load fonts via @import inside the <style> block, not via <link> in the head. This keeps the HTML truly self-contained and avoids rendering issues where Chrome headless may not wait for external <link> resources.
Include only the weights actually used. Loading unused weights slows Chrome headless rendering:
/* Good: only needed weights */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700;800&display=swap');
/* Bad: loading all weights */
@import url('https://fonts.googleapis.com/css2?family=Inter:[email protected]&display=swap');The full canvas is 1200x630px. Social platforms (Twitter, LinkedIn, Slack, Discord) may crop edges. Critical content (title text, series labels) must stay within the safe area:
Each cover must include at least one visual element beyond pure typography. Options:
background-image with linear-gradient for technical posts.All decorative elements must use position: absolute and sit behind text content via z-index. Never let decorations interfere with text legibility.
Symptom: scripts/screenshot.sh exits with error: no Chrome/Chromium binary found in PATH.
Fix: Install Chrome or Chromium. The script searches for google-chrome-stable, google-chrome, chromium-browser, or chromium in that order. On Fedora: sudo dnf install chromium. On Ubuntu: sudo apt install chromium-browser. Verify with which google-chrome-stable.
Symptom: Title renders in Arial or Times New Roman instead of the specified Google Font.
Fixes:
@import URL is correct and the font name matches exactly (case-sensitive).@import is the first rule inside <style>. CSS @import must precede all other rules.font-display: swap parameter: &display=swap in the Google Fonts URL.'Inter', 'Helvetica Neue', Arial, sans-serif.Symptom: Title or subtitle text is cut off at the bottom or right edge.
Fixes:
overflow: hidden is set on body (this is intentional, but confirms clipping is happening).max-width on the title element, but never exceed 1000px to preserve safe margins.Symptom: The output PNG is entirely white or appears empty.
Fixes:
body has an explicit background color set. Chrome headless defaults to white.z-index higher than 0 if background elements are present.Symptom: Gradient or background colors appear washed out or shifted compared to the CSS specification.
Fixes:
color() or lab() CSS color functions. Use hex (#0f172a) or rgba() values only.Symptom: The PNG has a scrollbar or extra whitespace on the right or bottom.
Fixes:
overflow: hidden is set on body.margin: 0; padding: 0 to both html and body via the universal selector * { margin: 0; padding: 0; }.--hide-scrollbars but this may not suppress all scrollbar artifacts in all Chrome versions. The CSS overflow: hidden is the reliable fix.| Property | Value |
|---|---|
| Canvas size | 1200 x 630 px |
| Safe area | 1040 x 510 px (centered) |
| Format | PNG |
| Min margins | 80px horizontal, 60px vertical |
| Max fonts | 2 (display + body) |
| External images | None (CSS only) |
| JavaScript | None (static HTML+CSS) |
| Context | Typical Path |
|---|---|
| Hugo blog | static/images/<category>/cover.png |
| Temp HTML | tmp/cover-<slug>.html |
| Variant | Background | Primary Text | Secondary Text | Accent |
|---|---|---|---|---|
| Dark | #0f172a | #f8fafc | #94a3b8 | #6366f1 to #8b5cf6 |
| Light | #ffffff | #0f172a | #64748b | #3b82f6 |
| Gradient | #1e1b4b to #312e81 | #f8fafc | #a5b4fc | #818cf8 |
tmp/cover-<slug>.htmlbash scripts/screenshot.sh tmp/cover-<slug>.html <output>.pngFor diagram and infographic styles used in other visual-gen skills, consult the architecture-diagram and process-infographic skill references.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.