---
name: og-cards
description: Build a 1200×630 social share card (OG image) for an article, post, or launch — in one of six designed styles — by writing HTML and screenshotting it. Use when the user wants an OG image, share card, blog cover card, Twitter/X card, link preview image, or says "make a card for this post". Prefer this over image generation models because type must be set, not sampled.
---

# OG Cards

A share card is 90% typography. Image models still cannot set a headline at the right
optical size, and they cost money per attempt. This skill writes HTML at exactly
1200×630 and screenshots it — pixel-exact, re-editable, zero cost per render.

## When to use

- "Make an OG image / share card / link preview for this post"
- "This article needs a cover for X / LinkedIn / my newsletter"
- Any time the deliverable is a 1200×630 (or 800×418) raster image containing words

**Do not use** when the deliverable is a photo, illustration, or abstract artwork with
no typography — use an image model for that.

## Workflow

### 1. Extract the four inputs

Every card needs exactly four things. Ask only for what you cannot read.

| Input | Rule | Example |
|---|---|---|
| `headline` | ≤ 9 words, one idea, break at a natural pause | "We shipped 47 features. Nobody noticed." |
| `kicker` | ≤ 3 words, all caps, mono | `POST 012` |
| `source` | domain or @handle | `signalnoise.dev` |
| `style` | one of the six below | `editorial` |

If the user gives a URL, fetch it and propose a headline. **Never exceed 9 words** —
if it doesn't fit, the card fails, and a truncated headline looks worse than a short one.

### 2. Write one HTML file

**Start from the shipped template for that style** — `cards/<style>.html` — and change
only the four `:root` tokens plus the copy. Do not hand-write a card from scratch;
the templates already encode the geometry, and freehanding is how margins break.

- Root element: `width:1200px; height:630px; overflow:hidden`
- All internal sizing in `cqw` (container query width units) so the card scales for preview
- One `@font-face`-free stack per style — no webfont downloads, no FOUT
- Exactly one accent color per card. Two accents is the most common failure.

### 3. Screenshot and verify

The Playwright CLI has **no viewport or device-scale flag** — use a 10-line script instead.

```bash
# once per machine
npm i -D playwright && npx playwright install chromium
```

```js
// shot.mjs — node shot.mjs card.html out.png
import { chromium } from 'playwright';
const [, , src, out] = process.argv;
const b = await chromium.launch();
const p = await b.newPage({ viewport: { width: 1200, height: 630 }, deviceScaleFactor: 2 });
await p.goto('file://' + new URL(src, `file://${process.cwd()}/`).pathname);
await p.screenshot({ path: out });
await b.close();
```

Then **read `out.png` back with the Read tool** and check the checklist below.
Do not hand the file over without looking at it.

## The six styles

Token blocks are the contract. Change colors and type, keep the geometry.

### 01 · Editorial
```
bg #F2EEE3   ink #14140F   accent none   type serif
headline 6.1cqw / 1.02 / -0.022em, max 20ch      padding 4.6cqw
kicker + source in mono 1.15cqw, letter-spacing .3em, color #7A7468
one hairline rule at 18% opacity above the footer
```
Best for: essays, newsletters, opinion. The italic on the second clause is what makes it.

### 02 · Terminal
```
bg #0A0C0A   ink #8CFFA6   dim #4E7A5A   type mono
lines 2.15cqw / 1.5                                padding 4.2cqw
4 lines max, left-aligned, centered vertically, blinking cursor at the end
```
Best for: changelogs, dev tools, "I built X". Fake a command, never fake an output.

### 03 · Mesh
```
bg #0D1026   ink #fff   type sans 650
blobs: violet #5B3BFF @22/28%, cyan #00D2C6 @76/22%, pink #FF4D8D @62/88%
blur 6cqw, blend normal, opacity .85        headline 5.2cqw / 1.06 / -.03em
```
Best for: product launches, anything with a waitlist.

### 04 · Swiss
```
bg #E7E4DC   block #E63329   ink #111
grid 38% / 62%                              numeral 17cqw / .8 / -.06em / 800
headline 4.1cqw / 1.06 / -.035em, max 18ch
```
Best for: data, numbers, "we cut X by 40%". The numeral must be the real number.

### 05 · Papercut
```
bg #E9E4D8   sheet #FBF8F1   ink #1A1813
three stacked sheets: -4deg / +2.5deg / 0deg, shadow 0 1.2cqw 3cqw rgba(30,25,15,.18)
headline 3.5cqw serif                        padding 3cqw inside the sheet
```
Best for: personal blogs, letters, anything warm.

### 06 · Data
```
bg #0C0D0F   ink #EDEAE3   accent #FF4D2E
metric 13cqw / .82 / -.055em, tabular-nums   label mono 1.3cqw / .16em / #8A867E
sparkline: inline SVG polyline, 2.5px, no fill
```
Best for: metrics, research, "we measured".

## Verification checklist

Read the rendered PNG and confirm each line. If any fails, fix and re-render.

- [ ] Headline occupies **≤ 3 lines** and no word is hyphenated
- [ ] Nothing touches the edge — minimum margin is 4% of width on all sides
- [ ] Exactly **one** accent color appears
- [ ] Kicker and source are legible at 200px wide (zoom out mentally)
- [ ] The card reads correctly if you only see the top-left third
- [ ] Text contrast ratio ≥ 4.5:1

## Failure modes

| Symptom | Cause | Fix |
|---|---|---|
| Headline wraps to 4+ lines | too many words | cut to ≤ 9 words, never shrink the font below 3.2cqw |
| Feels "AI-generated" | more than one accent, centered everything | pick one accent, left-align |
| Serif looks cheap | fallback font used | pin `Georgia, "Iowan Old Style", serif` |
| Blurry on X | exported at 1× | set `deviceScaleFactor: 2` in `newPage()` — the CLI has no flag for it |
| Screenshot is 1200×630, not 2400×1260 | scale factor ignored | you used the CLI, not the script |

## Output contract

- File: `out/<slug>-<style>.png`
- Size: 2400×1260 (2× of 1200×630)
- Also write `out/<slug>-<style>.html` so the user can edit the copy later
- Report: style used, headline, and the path to both files
