Social Card
You are generating a social media card image. Follow the rules below in order. Do not skip steps.
HARD RULES
- OUTPUT: 1200×630 PNG. No exceptions.
- NO PLACEHOLDER TEXT: If the user did not provide content, ask. Never use lorem ipsum or filler.
- CONTRAST: Text must meet WCAG AA minimum (4.5:1 normal, 3:1 large). Default to black
#1A1A1A on white #FFFFFF, or white on near-black #0F172A. Brand colors may be used for text only if they pass WCAG AA against the chosen background. If the user's brand color fails contrast, use it for decorative elements (accent bar, border, background pattern) and keep text in a compliant color.
- LAYOUT: Pick the variant that best fits the content (Step 2). All three variants live as templates under
assets/ — never inline them into prompts or other files.
Pattern Notes
- minimal-precision: Keep the single-card workflow short and exact.
- precise-commands: Use explicit file paths, viewport dimensions, and output checks for rendering.
- progressive-disclosure: Read only the selected template and the checks required by the chosen variant.
Step 1: Extract content
From the user's request, identify:
- Title / headline (required): the primary text on the card.
- Subtitle (optional): secondary text, tagline, URL, or author attribution.
- Brand color (optional): a hex color for the accent bar and Avatar+Quote glyph. If not provided, default to
#1A1A1A.
- Logo / avatar (optional): a URL or local path to a logo (Logo+Title) or author avatar (Avatar+Quote).
If the title is missing, ask: "What should the card say as its main headline?"
Step 2: Choose a layout variant
Pick the variant whose template to render in Step 5:
| Variant |
Use when |
Structure |
Template |
| Centered (default) |
Title only, or title + short subtitle. Clean, minimal. |
Centered title, subtitle bottom-left, accent bar at top. |
assets/centered.html |
| Logo + Title |
Brand card with company/product logo. |
Logo top-left, title centered, subtitle bottom-left, accent bar at top. |
assets/logo-title.html |
| Avatar + Quote |
Author quote, testimonial, personal brand. |
Circular avatar above center, title rendered as a pull-quote, attribution (name + handle) below. |
assets/avatar-quote.html |
Step 3: Escape user text
Before substituting user-provided text into the template, escape: & → &, < → <, > → >, " → ". Do this once, on the title and subtitle separately.
Step 4: Choose font size
Font size depends on title length and character type. CJK, Arabic, and Devanagari characters render at roughly 2× the visual width of Latin characters — adjust accordingly.
| Title length (Latin) |
Title length (CJK / Arabic / Devanagari) |
font-size |
| ≤ 60 chars |
≤ 30 chars |
72px |
| 61–100 chars |
31–50 chars |
56px |
| > 100 chars |
> 50 chars |
44px |
For mixed-script titles, count each CJK / Arabic / Devanagari character as 2 Latin characters when computing length. RTL scripts (Arabic, Hebrew) require no template change at this size — the browser handles direction; verify by visual inspection in Step 7.
Step 5: Build the HTML
- Read the template that matches the variant chosen in Step 2 from
assets/. Do not copy template HTML into your prompt — read the file directly.
- Substitute placeholders with the values from Steps 1, 3, and 4:
{{ESCAPED_TITLE}} → escaped title from Step 3
{{ESCAPED_SUBTITLE}} → escaped subtitle from Step 3 (if any)
{{ACCENT_COLOR}} → brand color from Step 1 (or #1A1A1A default)
{{FONT_SIZE}} → numeric value from Step 4
{{LOGO_SRC}} (Logo+Title only) → logo URL or path
{{AVATAR_SRC}} (Avatar+Quote only) → avatar URL or path
- Strip optional blocks that have no value. The templates use Mustache-style
{{#BLOCK}}…{{/BLOCK}} markers — if SUBTITLE, LOGO, or AVATAR is absent, delete the entire block including the markers. Leaving placeholders in the rendered HTML is a verification failure.
- Write the substituted HTML to
/tmp/bs-social-card.html (macOS / Linux), %TEMP%/bs-social-card.html (Windows), or <project-root>/.claude/tmp/bs-social-card.html (fallback).
Step 6: Screenshot
Use Playwright MCP if available:
1. mcp__playwright__browser_navigate({ url: "file:///tmp/bs-social-card.html" })
2. mcp__playwright__browser_resize({ width: 1200, height: 630 })
3. mcp__playwright__browser_wait_for({ time: 1 })
4. mcp__playwright__browser_take_screenshot({ type: "png", fullPage: true, filename: "<path>" })
If Playwright MCP is unavailable, fall back in this order:
- Playwright CLI:
npx playwright screenshot --viewport-size=1200,630 "file:///tmp/bs-social-card.html" bs-social-card.png (if no browser is installed yet, run npx playwright install chromium first — note this downloads ~100MB).
- Puppeteer script (only if the project already has
puppeteer installed): write a 10-line Node script that launches puppeteer, sets viewport 1200×630, navigates to the file URL, and calls page.screenshot(). Delete the script after use.
- Manual: tell the user — "Open
/tmp/bs-social-card.html in a browser, take a screenshot at 1200×630, and save as PNG."
Step 7: Verify
Run these checks; fix and re-screenshot on any failure. Retry up to 3 times.
- Dimensions (programmatic where possible): use Playwright
page.evaluate(() => ({w: document.body.offsetWidth, h: document.body.offsetHeight})) to confirm 1200×630. Without Playwright, inspect the screenshot file's metadata.
- Text overflow (programmatic): use Playwright
page.evaluate(() => {
const h1 = document.querySelector('h1, .quote');
const rect = h1.getBoundingClientRect();
return {
fits: h1.scrollHeight <= h1.clientHeight * 1.05 // content taller than its box = clipped
&& h1.scrollWidth <= h1.clientWidth * 1.05 // content wider than its box = clipped
&& rect.right <= 1200 && rect.bottom <= 630 // box itself stays inside the card
};
})
If fits is false, drop one tier of font-size (Step 4 table) and regenerate.
- No placeholder leakage (manual): scan the final HTML for any unsubstituted
{{TOKEN}} or unstripped {{#BLOCK}}…{{/BLOCK}} markers. Every visible string must be the user's content.
- Layout-specific (manual):
- Centered: subtitle does not collide with the accent bar at the bottom.
- Logo+Title: logo respects
max-height: 48px and does not overlap the title.
- Avatar+Quote: avatar is circular (border-radius applied), the curly-quote glyph is visible before the title, attribution sits clearly below the quote.
Test prompts
Three prompts that exercise this skill end-to-end, mirrored in evaluation/datasets/batch-1-test-prompts.json.
- Happy path: "Generate an OG image for a blog post titled 'Why Postgres beats MongoDB for OLTP', author Marc Garrison, brand color
#1F6FEB." — expected: Centered or Logo+Title variant, blue accent bar, title fits at 72px or 56px.
- Edge — long CJK title: "我需要一张社交媒体卡片,主标题是'从零搭建一套面向 AI 智能体的可复用技能工具链:架构、模式与评估方法论',副标题写作者名字 '李未然'。" — expected: 56px or 44px font-size; CJK length × 2 rule applied; no clipping.
- Adversarial — testimonial with brand color failing contrast: "Make an avatar+quote card. Quote: 'This refactor cut latency in half.' Author: Priya Subramaniam, @prsubr. Brand color:
#FFD700 (yellow)." — expected: brand yellow rejected for text (fails WCAG AA against white), used only for the accent bar and the curly-quote glyph; quote rendered in #1A1A1A for compliance.
Handoff
After verification passes:
- Return the screenshot file path and a 1-line summary (variant chosen + font-size used).
- If the user wants variants (e.g., dark mode of the same content), iterate by re-running from Step 2.
- If the user wants animation or multi-frame output, hand off to
bs-ui-master — this skill ends at a single 1200×630 PNG.
1---2name: bs-social-card3description: Use when the user asks to produce and validate a single 1200x630 social media card, Open Graph image, Twitter card, link preview, or social share graphic. Do not use for full-page screenshots, multi-slide carousels, animated GIFs, video thumbnails, print-ready graphics, or a general-purpose visual identity system.4---56# Social Card78You are generating a social media card image. Follow the rules below in order. Do not skip steps.910## HARD RULES11121. **OUTPUT**: 1200×630 PNG. No exceptions.132. **NO PLACEHOLDER TEXT**: If the user did not provide content, ask. Never use lorem ipsum or filler.143. **CONTRAST**: Text must meet WCAG AA minimum (4.5:1 normal, 3:1 large). Default to black `#1A1A1A` on white `#FFFFFF`, or white on near-black `#0F172A`. Brand colors may be used for text **only** if they pass WCAG AA against the chosen background. If the user's brand color fails contrast, use it for decorative elements (accent bar, border, background pattern) and keep text in a compliant color.154. **LAYOUT**: Pick the variant that best fits the content (Step 2). All three variants live as templates under `assets/` — never inline them into prompts or other files.1617## Pattern Notes1819- **minimal-precision**: Keep the single-card workflow short and exact.20- **precise-commands**: Use explicit file paths, viewport dimensions, and output checks for rendering.21- **progressive-disclosure**: Read only the selected template and the checks required by the chosen variant.2223## Step 1: Extract content2425From the user's request, identify:2627- **Title / headline** (required): the primary text on the card.28- **Subtitle** (optional): secondary text, tagline, URL, or author attribution.29- **Brand color** (optional): a hex color for the accent bar and Avatar+Quote glyph. If not provided, default to `#1A1A1A`.30- **Logo / avatar** (optional): a URL or local path to a logo (Logo+Title) or author avatar (Avatar+Quote).3132If the title is missing, ask: *"What should the card say as its main headline?"*3334## Step 2: Choose a layout variant3536Pick the variant whose template to render in Step 5:3738| Variant | Use when | Structure | Template |39|---------|----------|-----------|----------|40| **Centered** (default) | Title only, or title + short subtitle. Clean, minimal. | Centered title, subtitle bottom-left, accent bar at top. | [`assets/centered.html`](./assets/centered.html) |41| **Logo + Title** | Brand card with company/product logo. | Logo top-left, title centered, subtitle bottom-left, accent bar at top. | [`assets/logo-title.html`](./assets/logo-title.html) |42| **Avatar + Quote** | Author quote, testimonial, personal brand. | Circular avatar above center, title rendered as a pull-quote, attribution (name + handle) below. | [`assets/avatar-quote.html`](./assets/avatar-quote.html) |4344## Step 3: Escape user text4546Before substituting user-provided text into the template, escape: `&` → `&`, `<` → `<`, `>` → `>`, `"` → `"`. Do this once, on the title and subtitle separately.4748## Step 4: Choose font size4950Font size depends on title length **and** character type. CJK, Arabic, and Devanagari characters render at roughly 2× the visual width of Latin characters — adjust accordingly.5152| Title length (Latin) | Title length (CJK / Arabic / Devanagari) | `font-size` |53|----------------------|------------------------------------------|-------------|54| ≤ 60 chars | ≤ 30 chars | 72px |55| 61–100 chars | 31–50 chars | 56px |56| > 100 chars | > 50 chars | 44px |5758For mixed-script titles, count each CJK / Arabic / Devanagari character as 2 Latin characters when computing length. RTL scripts (Arabic, Hebrew) require no template change at this size — the browser handles direction; verify by visual inspection in Step 7.5960## Step 5: Build the HTML61621. **Read the template** that matches the variant chosen in Step 2 from `assets/`. Do **not** copy template HTML into your prompt — read the file directly.632. **Substitute placeholders** with the values from Steps 1, 3, and 4:64 - `{{ESCAPED_TITLE}}` → escaped title from Step 365 - `{{ESCAPED_SUBTITLE}}` → escaped subtitle from Step 3 (if any)66 - `{{ACCENT_COLOR}}` → brand color from Step 1 (or `#1A1A1A` default)67 - `{{FONT_SIZE}}` → numeric value from Step 468 - `{{LOGO_SRC}}` (Logo+Title only) → logo URL or path69 - `{{AVATAR_SRC}}` (Avatar+Quote only) → avatar URL or path703. **Strip optional blocks** that have no value. The templates use Mustache-style `{{#BLOCK}}…{{/BLOCK}}` markers — if `SUBTITLE`, `LOGO`, or `AVATAR` is absent, **delete the entire block** including the markers. Leaving placeholders in the rendered HTML is a verification failure.714. **Write the substituted HTML** to `/tmp/bs-social-card.html` (macOS / Linux), `%TEMP%/bs-social-card.html` (Windows), or `<project-root>/.claude/tmp/bs-social-card.html` (fallback).7273## Step 6: Screenshot7475Use Playwright MCP if available:7677```781. mcp__playwright__browser_navigate({ url: "file:///tmp/bs-social-card.html" })792. mcp__playwright__browser_resize({ width: 1200, height: 630 })803. mcp__playwright__browser_wait_for({ time: 1 })814. mcp__playwright__browser_take_screenshot({ type: "png", fullPage: true, filename: "<path>" })82```8384If Playwright MCP is unavailable, fall back in this order:85861. **Playwright CLI**: `npx playwright screenshot --viewport-size=1200,630 "file:///tmp/bs-social-card.html" bs-social-card.png` (if no browser is installed yet, run `npx playwright install chromium` first — note this downloads ~100MB).872. **Puppeteer script** (only if the project already has `puppeteer` installed): write a 10-line Node script that launches puppeteer, sets viewport 1200×630, navigates to the file URL, and calls `page.screenshot()`. Delete the script after use.883. **Manual**: tell the user — *"Open `/tmp/bs-social-card.html` in a browser, take a screenshot at 1200×630, and save as PNG."*8990## Step 7: Verify9192Run these checks; fix and re-screenshot on any failure. Retry up to 3 times.9394<HARD-GATE id="verified-before-delivery">95Never return a card that fails verification. If a check still fails after 3 attempts, report the failing check to the user and ask for guidance instead of delivering a broken card.96</HARD-GATE>97981. **Dimensions** (programmatic where possible): use Playwright `page.evaluate(() => ({w: document.body.offsetWidth, h: document.body.offsetHeight}))` to confirm 1200×630. Without Playwright, inspect the screenshot file's metadata.992. **Text overflow** (programmatic): use Playwright100 ```101 page.evaluate(() => {102 const h1 = document.querySelector('h1, .quote');103 const rect = h1.getBoundingClientRect();104 return {105 fits: h1.scrollHeight <= h1.clientHeight * 1.05 // content taller than its box = clipped106 && h1.scrollWidth <= h1.clientWidth * 1.05 // content wider than its box = clipped107 && rect.right <= 1200 && rect.bottom <= 630 // box itself stays inside the card108 };109 })110 ```111 If `fits` is false, drop one tier of font-size (Step 4 table) and regenerate.1123. **No placeholder leakage** (manual): scan the final HTML for any unsubstituted `{{TOKEN}}` or unstripped `{{#BLOCK}}…{{/BLOCK}}` markers. Every visible string must be the user's content.1134. **Layout-specific** (manual):114 - **Centered**: subtitle does not collide with the accent bar at the bottom.115 - **Logo+Title**: logo respects `max-height: 48px` and does not overlap the title.116 - **Avatar+Quote**: avatar is circular (border-radius applied), the curly-quote glyph is visible before the title, attribution sits clearly below the quote.117118## Test prompts119120Three prompts that exercise this skill end-to-end, mirrored in `evaluation/datasets/batch-1-test-prompts.json`.1211221. **Happy path**: *"Generate an OG image for a blog post titled 'Why Postgres beats MongoDB for OLTP', author Marc Garrison, brand color `#1F6FEB`."* — expected: Centered or Logo+Title variant, blue accent bar, title fits at 72px or 56px.1232. **Edge — long CJK title**: *"我需要一张社交媒体卡片,主标题是'从零搭建一套面向 AI 智能体的可复用技能工具链:架构、模式与评估方法论',副标题写作者名字 '李未然'。"* — expected: 56px or 44px font-size; CJK length × 2 rule applied; no clipping.1243. **Adversarial — testimonial with brand color failing contrast**: *"Make an avatar+quote card. Quote: 'This refactor cut latency in half.' Author: Priya Subramaniam, @prsubr. Brand color: `#FFD700` (yellow)."* — expected: brand yellow rejected for text (fails WCAG AA against white), used only for the accent bar and the curly-quote glyph; quote rendered in `#1A1A1A` for compliance.125126## Handoff127128After verification passes:129- Return the screenshot file path and a 1-line summary (variant chosen + font-size used).130- If the user wants variants (e.g., dark mode of the same content), iterate by re-running from Step 2.131- If the user wants animation or multi-frame output, hand off to `bs-ui-master` — this skill ends at a single 1200×630 PNG.