Created
June 4, 2026 04:11
-
-
Save vapvarun/9f2e9778811cd6dee0f8514d31f6ecdb to your computer and use it in GitHub Desktop.
HTML + Playwright Featured Image Workflow (vapvarun.com)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| width: 1200px; | |
| height: 630px; | |
| overflow: hidden; | |
| font-family: Georgia, serif; | |
| } | |
| .card { | |
| width: 1200px; | |
| height: 630px; | |
| background: linear-gradient(135deg, #FDF6EC 0%, #F5E6C8 40%, #EDD89A 100%); | |
| position: relative; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| padding: 72px 80px; | |
| } | |
| /* Subtle accent bar on left edge */ | |
| .card::before { | |
| content: ''; | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| bottom: 0; | |
| width: 8px; | |
| background: #3B1F0A; | |
| } | |
| /* Decorative large circle, bottom-right */ | |
| .card::after { | |
| content: ''; | |
| position: absolute; | |
| right: -120px; | |
| bottom: -120px; | |
| width: 400px; | |
| height: 400px; | |
| border-radius: 50%; | |
| background: radial-gradient(circle, rgba(59,31,10,0.08) 0%, transparent 70%); | |
| } | |
| .eyebrow { | |
| font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; | |
| font-size: 13px; | |
| font-weight: 600; | |
| letter-spacing: 0.12em; | |
| text-transform: uppercase; | |
| color: #7A5C3A; | |
| margin-bottom: 24px; | |
| } | |
| .title { | |
| font-family: Georgia, serif; | |
| font-size: 52px; | |
| line-height: 1.18; | |
| font-weight: normal; | |
| color: #1A0E05; | |
| max-width: 820px; | |
| margin-bottom: 40px; | |
| } | |
| .pills { | |
| display: flex; | |
| gap: 12px; | |
| flex-wrap: wrap; | |
| margin-top: auto; | |
| } | |
| .pill { | |
| font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; | |
| font-size: 12px; | |
| font-weight: 500; | |
| letter-spacing: 0.06em; | |
| color: #3B1F0A; | |
| background: rgba(59,31,10,0.08); | |
| border: 1px solid rgba(59,31,10,0.18); | |
| border-radius: 20px; | |
| padding: 5px 14px; | |
| } | |
| .branding { | |
| position: absolute; | |
| bottom: 28px; | |
| right: 40px; | |
| font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; | |
| font-size: 12px; | |
| color: rgba(59,31,10,0.4); | |
| letter-spacing: 0.04em; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="card"> | |
| <div class="eyebrow">vapvarun.com</div> | |
| <h1 class="title">{{POST_TITLE}}</h1> | |
| <div class="pills"> | |
| <span class="pill">{{TAG_1}}</span> | |
| <span class="pill">{{TAG_2}}</span> | |
| <span class="pill">{{TAG_3}}</span> | |
| </div> | |
| <div class="branding">vapvarun.com</div> | |
| </div> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| featured_image_screenshot.py | |
| Generates a 1200x630 featured image from an HTML template | |
| using Playwright. Converts to WebP at quality 85. | |
| Requirements: | |
| pip install playwright | |
| playwright install chromium | |
| brew install webp (macOS) | apt install webp (Linux) | |
| Usage: | |
| python3 featured_image_screenshot.py \ | |
| --html /tmp/vapvarun-featured/my-post.html \ | |
| --out /tmp/vapvarun-featured/my-post.webp | |
| """ | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| def screenshot_to_webp(html_path: str, out_path: str, quality: int = 85) -> str: | |
| """ | |
| 1. Serve the HTML from a local HTTP server (avoids file:// restrictions). | |
| 2. Open Playwright at 1200x630, navigate, screenshot. | |
| 3. Convert the PNG to WebP with cwebp. | |
| Returns the final WebP path. | |
| """ | |
| from playwright.sync_api import sync_playwright | |
| import http.server | |
| import threading | |
| import os | |
| html_path = Path(html_path).resolve() | |
| serve_dir = html_path.parent | |
| png_path = html_path.with_suffix(".png") | |
| out_path = Path(out_path) | |
| PORT = 8765 | |
| # Start local HTTP server in background thread | |
| handler = http.server.SimpleHTTPRequestHandler | |
| httpd = http.server.HTTPServer(("", PORT), handler) | |
| thread = threading.Thread(target=httpd.serve_forever, daemon=True) | |
| thread.start() | |
| try: | |
| url = f"http://localhost:{PORT}/{html_path.name}" | |
| with sync_playwright() as p: | |
| browser = p.chromium.launch(headless=True) | |
| page = browser.new_page(viewport={"width": 1200, "height": 630}) | |
| page.goto(url, wait_until="networkidle") | |
| page.screenshot( | |
| path=str(png_path), | |
| full_page=False, # Capture exactly the 1200x630 viewport | |
| type="png", | |
| ) | |
| browser.close() | |
| finally: | |
| httpd.shutdown() | |
| # Convert PNG to WebP | |
| result = subprocess.run( | |
| ["cwebp", "-q", str(quality), str(png_path), "-o", str(out_path)], | |
| capture_output=True, | |
| text=True, | |
| ) | |
| if result.returncode != 0: | |
| raise RuntimeError(f"cwebp failed: {result.stderr}") | |
| png_path.unlink() # Remove intermediate PNG | |
| size_kb = out_path.stat().st_size / 1024 | |
| print(f"Output: {out_path} ({size_kb:.1f} KB)") | |
| return str(out_path) | |
| if __name__ == "__main__": | |
| import argparse | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--html", required=True) | |
| parser.add_argument("--out", required=True) | |
| parser.add_argument("--quality", type=int, default=85) | |
| args = parser.parse_args() | |
| screenshot_to_webp(args.html, args.out, args.quality) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <!-- | |
| Featured Image Starter Template | |
| Dimensions: 1200x630px (Open Graph standard) | |
| Customize: gradient colors, typography, accent color, pill tags | |
| Usage: | |
| 1. Replace {{POST_TITLE}}, {{TAG_1}}, {{TAG_2}}, {{SITE_NAME}} with your values | |
| 2. Adjust CSS variables in :root to match your brand | |
| 3. Screenshot with Playwright at 1200x630 viewport | |
| 4. Convert to WebP: cwebp -q 85 screenshot.png -o image.webp | |
| --> | |
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <style> | |
| /* ============================================ | |
| BRAND VARIABLES -- change these per site | |
| ============================================ */ | |
| :root { | |
| --bg-start: #FDF6EC; /* gradient start (top-left) */ | |
| --bg-end: #EDD89A; /* gradient end (bottom-right) */ | |
| --title-color: #1A0E05; /* main heading */ | |
| --accent-color: #3B1F0A; /* pills, accent bar, branding */ | |
| --eyebrow-color: #7A5C3A; /* site name / category label */ | |
| --font-title: Georgia, serif; | |
| --font-ui: 'Helvetica Neue', Helvetica, Arial, sans-serif; | |
| } | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| width: 1200px; | |
| height: 630px; | |
| overflow: hidden; | |
| font-family: var(--font-title); | |
| } | |
| .card { | |
| width: 1200px; | |
| height: 630px; | |
| background: linear-gradient(135deg, var(--bg-start) 0%, var(--bg-end) 100%); | |
| position: relative; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| padding: 72px 80px; | |
| } | |
| /* Left accent bar */ | |
| .accent-bar { | |
| position: absolute; | |
| left: 0; top: 0; bottom: 0; | |
| width: 8px; | |
| background: var(--accent-color); | |
| } | |
| /* Decorative circle, bottom-right */ | |
| .deco-circle { | |
| position: absolute; | |
| right: -100px; bottom: -100px; | |
| width: 380px; height: 380px; | |
| border-radius: 50%; | |
| background: radial-gradient(circle, rgba(0,0,0,0.06) 0%, transparent 70%); | |
| pointer-events: none; | |
| } | |
| .eyebrow { | |
| font-family: var(--font-ui); | |
| font-size: 13px; | |
| font-weight: 600; | |
| letter-spacing: 0.12em; | |
| text-transform: uppercase; | |
| color: var(--eyebrow-color); | |
| margin-bottom: 24px; | |
| } | |
| .title { | |
| font-family: var(--font-title); | |
| font-size: 54px; | |
| line-height: 1.18; | |
| font-weight: normal; | |
| color: var(--title-color); | |
| max-width: 820px; | |
| margin-bottom: 40px; | |
| } | |
| /* Adjust font-size down for longer titles */ | |
| .title.long { font-size: 42px; } | |
| .pills { | |
| display: flex; | |
| gap: 12px; | |
| flex-wrap: wrap; | |
| } | |
| .pill { | |
| font-family: var(--font-ui); | |
| font-size: 12px; | |
| font-weight: 500; | |
| letter-spacing: 0.06em; | |
| color: var(--accent-color); | |
| background: rgba(0,0,0,0.06); | |
| border: 1px solid rgba(0,0,0,0.14); | |
| border-radius: 20px; | |
| padding: 5px 14px; | |
| } | |
| .branding { | |
| position: absolute; | |
| bottom: 28px; | |
| right: 40px; | |
| font-family: var(--font-ui); | |
| font-size: 12px; | |
| color: rgba(0,0,0,0.3); | |
| letter-spacing: 0.04em; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="card"> | |
| <div class="accent-bar"></div> | |
| <div class="deco-circle"></div> | |
| <div class="eyebrow">{{SITE_NAME}}</div> | |
| <h1 class="title">{{POST_TITLE}}</h1> | |
| <div class="pills"> | |
| <span class="pill">{{TAG_1}}</span> | |
| <span class="pill">{{TAG_2}}</span> | |
| <!-- Add more pills as needed --> | |
| </div> | |
| <div class="branding">{{SITE_DOMAIN}}</div> | |
| </div> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment