
Banner Creator
- 1.6k installs
- 1.2k repo stars
- Updated July 27, 2026
- resciencelab/opc-skills
banner-creator generates professional banners and headers through iterative AI image workflows.
About
The banner-creator skill produces professional banners through iterative AI image generation. Prerequisites require GEMINI_API_KEY from Google AI Studio and the nanobanana skill for Gemini 3 Pro Image generation. Workflow discusses format and style with the user, generates variations, incorporates feedback loops, and crops to target ratios for GitHub banners, Twitter headers, readme heroes, or cover images. Outputs save under .skill-archive/banner-creator/<yyyy-mm-dd-summaryname>/ for traceability. Agents confirm dimensions, brand constraints, and iteration preferences before generating. Use when users want banners, headers, hero images, or readme graphics rather than manual design tool workflows. Iterative AI banner generation with user feedback on format and style Requires GEMINI_API_KEY and nanobanana image generation skill Crops outputs to target ratios for GitHub, Twitter, or readme headers Saves artifacts under .skill-archive/banner-creator dated folders Supports hero, cover, and social header banner use cases banner-creator generates professional banners and headers through iterative AI image workflows Iterated banner assets cropped to target ratios and archived for reuse U.
- Iterative AI banner generation with user feedback on format and style.
- Requires GEMINI_API_KEY and nanobanana image generation skill.
- Crops outputs to target ratios for GitHub, Twitter, or readme headers.
- Saves artifacts under .skill-archive/banner-creator dated folders.
- Supports hero, cover, and social header banner use cases.
Banner Creator by the numbers
- 1,599 all-time installs (skills.sh)
- +23 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #181 of 1,340 Generative Media skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
banner-creator capabilities & compatibility
- Capabilities
- format discussion · variation generation · feedback iteration · aspect ratio cropping · archive organization
- Works with
- openai
- Use cases
- image generation · marketing
What banner-creator says it does
Discuss format/style, generate variations, iterate with user feedback, crop to target ratio.
npx skills add https://github.com/resciencelab/opc-skills --skill banner-creatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.6k |
|---|---|
| repo stars | ★ 1.2k |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | resciencelab/opc-skills ↗ |
How do I create a banner or hero image with the right aspect ratio and brand style?
Create banners and hero images via AI generation with format discussion, variations, iteration, and crop to target aspect ratios.
Who is it for?
Teams needing GitHub, Twitter, or readme banners without manual design tools.
Skip if: Full brand identity systems or multi-page marketing site design.
When should I use this skill?
User wants a banner, header, hero image, or readme cover graphic.
What you get
Iterated banner assets cropped to target ratios and archived for reuse.
- Cropped banner images
- Hero graphics
- Platform-specific cover assets
By the numbers
- Version 1.0.0
- MIT license from ReScienceLab/opc-skills
- Bundles ./scripts/ command directory
Files
Banner Creator Skill
Create professional banners through AI image generation with an iterative design process.
Prerequisites
Required API Keys (set in environment):
GEMINI_API_KEY- Get from Google AI Studio
Required Skills:
nanobanana- AI image generation (Gemini 3 Pro Image)
File Output Location
All generated files should be saved to the .skill-archive directory:
.skill-archive/banner-creator/<yyyy-mm-dd-summaryname>/Example:
.skill-archive/banner-creator/2026-01-19-opc-banner/
banner-01.png
banner-02.png
...
banner-03-cropped.png
preview.htmlWorkflow
Step 1: Discovery & Requirements
Before generating, gather requirements from user:
Ask about: 1. Purpose - Where will the banner be used?
- GitHub README
- Twitter/X header
- LinkedIn banner
- Website hero
- YouTube channel art
2. Target ratio/size - See references/formats.md:
2:1(1280x640) - GitHub README3:1(1500x500) - Twitter header16:9(1920x1080) - Website hero
3. Style preference:
- Match existing logo/brand?
- Pixel art / 8-bit retro
- Minimalist / flat design
- Gradient / modern
- Illustrated / artistic
4. Content elements:
- Brand name / project name?
- Tagline / slogan?
- Logo character to include?
5. Color preferences:
- Existing brand colors?
- Let AI decide?
Wait for user confirmation before proceeding!
Step 2: Generate Banner Variations
Generate 20 banner variations using the nanobanana skill:
# Generate single banner
python3 <nanobanana_skill_dir>/scripts/generate.py "{style} banner for {brand}, {description}, {text elements}" \
--ratio 21:9 -o .skill-archive/banner-creator/<date-name>/banner-01.png
# Batch generate 20 banners
python3 <nanobanana_skill_dir>/scripts/batch_generate.py "{style} banner for {brand}, {description}, {text elements}" \
-n 20 --ratio 21:9 -d .skill-archive/banner-creator/<date-name> -p bannerGuidelines:
- Generate at
21:9ratio (widest available), crop later to target - Use batch_generate.py for multiple variations (includes auto-delay)
- Use sequential naming:
banner-01.png,banner-02.png, etc.
Image Editing (for incorporating existing logo):
python3 <nanobanana_skill_dir>/scripts/generate.py "add {logo character} to the left side of the banner" \
-i /path/to/existing-logo.png --ratio 21:9 -o banner-with-logo.pngStep 3: Create HTML Preview
Copy the preview template and open in browser:
cp <skill_dir>/templates/preview.html .skill-archive/banner-creator/<yyyy-mm-dd-summaryname>/preview.htmlThen open in default browser:
open .skill-archive/banner-creator/<yyyy-mm-dd-summaryname>/preview.htmlIMPORTANT: Update the HTML to include the correct number of banners generated.
Step 4: Iterate with User
Ask user which banners they prefer:
- "Which banners do you like? (e.g., #3, #7, #15)"
- "What do you like about them?"
- "Any changes you'd want?"
Based on feedback: 1. Generate 10-20 more variations of favorite styles 2. Use naming: banner-{original}-v{n}.png (e.g., banner-03-v1.png) 3. Update HTML preview 4. Repeat until user selects final banner
Step 5: Crop to Target Ratio
Once user approves a banner, crop to target size:
python3 <skill_dir>/scripts/crop_banner.py {input.png} {output.png} --ratio 2:1 --width 1280Common targets:
- GitHub README:
--ratio 2:1 --width 1280→ 1280x640 - Twitter header:
--ratio 3:1 --width 1500→ 1500x500 - Website hero:
--ratio 16:9 --width 1920→ 1920x1080
Step 6: Deliver Final Assets
Present final deliverables:
## Final Banner Assets
| File | Description | Size |
|------|-------------|------|
| banner-03.png | Original (21:9) | 2016x864 |
| banner-03-cropped.png | GitHub README (2:1) | 1280x640 |
All files saved to: `.skill-archive/banner-creator/<yyyy-mm-dd-summaryname>/`
Copy final banner to user's desired location.Quick Reference
Common Prompt Patterns
With Text:
Wide banner for {brand}, {style} style, featuring "{text}" prominently displayed, {colors}, {scene/elements}With Character:
Wide banner featuring {character description}, {style} style, {scene}, text "{brand name}" on {position}, {colors}Abstract/Gradient:
Abstract {style} banner, {colors} gradient, geometric patterns, modern tech feel, text "{brand}" centeredScene-based:
{Style} illustration banner, {scene description}, {character} in {action}, "{brand}" text overlay, {colors}Supported Aspect Ratios
Generate at widest ratio, then crop:
21:9- Ultra-wide (recommended for generation)16:9- Wide3:2- Standard wide
References
- references/formats.md - Common banner sizes by platform
- examples/opc-banner-creation.md - Full example conversation
{
"name": "banner-creator",
"version": "1.0.0",
"description": "Create banners using AI image generation. Discuss format/style, generate variations, iterate with user feedback, crop to target ratio for GitHub, Twitter, LinkedIn, etc.",
"author": {
"name": "ReScienceLab"
},
"homepage": "https://github.com/ReScienceLab/opc-skills/tree/main/skills/banner-creator",
"repository": "https://github.com/ReScienceLab/opc-skills",
"license": "MIT",
"keywords": [
"banner",
"header",
"hero image",
"cover image",
"create banner",
"github banner",
"twitter header",
"readme banner"
],
"skills": [
"./SKILL.md"
],
"commands": [
"./scripts/"
]
}Example: OPC Skills Banner Creation
User: Create a banner for opc.dev GitHub README. Should match the existing pixel art logo style.
Agent: I'll create a banner for OPC Skills. Let me confirm the requirements:
1. Target size: GitHub README banner (typically 1280x640, 2:1 ratio) 2. Style: Pixel art to match existing crowned king logo 3. Text: "opc.dev" and "Agent Skills" 4. Character: Include the pixel art crowned king from logo
User: Yes, that's correct. Please generate some options.
Agent: Generating banner variations at 16:9 ratio first...
Here are the generated banners:






User: I like the style. But GitHub banners work best at 2:1 ratio. Can you generate some at that ratio?
Agent: Generating at 2:1 ratio for GitHub:



User: Perfect! Let's use the third one.
Agent: Here's the final banner:

The banner is ready to use in your README!
Banner Formats Reference
Platform-Specific Sizes
| Platform | Size (px) | Ratio | Notes |
|---|---|---|---|
| GitHub README | 1280×640 | 2:1 | Most common for project banners |
| Twitter/X Header | 1500×500 | 3:1 | Profile header image |
| LinkedIn Banner | 1584×396 | 4:1 | Personal profile background |
| LinkedIn Company | 1128×191 | ~6:1 | Company page banner |
| YouTube Channel | 2560×1440 | 16:9 | Channel art (safe area: 1546×423) |
| Facebook Cover | 820×312 | ~2.6:1 | Personal profile |
| Discord Server | 960×540 | 16:9 | Server banner |
| Website Hero | 1920×1080 | 16:9 | Full-width hero section |
| Website Hero (tall) | 1920×800 | 2.4:1 | Shorter hero section |
| Email Header | 600×200 | 3:1 | Newsletter headers |
| Product Hunt | 1270×760 | ~1.67:1 | Gallery images |
Generation Strategy
Since nano-banana-pro supports these ratios:
1:1,2:3,3:2,3:4,4:3,4:5,5:4,9:16,16:9,21:9
Recommended approach: 1. Generate at 21:9 (widest available) 2. Crop to target ratio using crop_banner.py
This ensures:
- Maximum flexibility for different platforms
- Content centered properly
- No stretching or distortion
Ratio Quick Reference
21:9 ████████████████████████████████ (ultra-wide, cinematic)
16:9 ████████████████████████ (widescreen)
3:1 ████████████████████████████████ (Twitter header)
2:1 ████████████████ (GitHub README)
4:1 ████████████████████████████████████████████ (LinkedIn)File Size Guidelines
- GitHub: < 10MB, PNG or JPG
- Twitter: < 5MB, PNG, JPG, or GIF
- LinkedIn: < 8MB, PNG or JPG
- Website: Optimize for web (< 500KB ideally)
Tips
1. Safe zones: Keep important content (text, logo) in center 60% for platforms that crop on mobile 2. Text legibility: Use high contrast, avoid small text 3. Brand consistency: Match colors and style with existing logo 4. Mobile preview: Check how banner looks on mobile (often cropped)
#!/usr/bin/env python3
"""
Crop banner to target aspect ratio.
Usage:
python crop_banner.py input.png output.png --ratio 2:1 --width 1280
python crop_banner.py input.png output.png --ratio 16:9
python crop_banner.py input.png output.png --size 1500x500
"""
import argparse
import sys
from PIL import Image
def parse_ratio(ratio_str: str) -> tuple[int, int]:
"""Parse ratio string like '2:1' or '16:9' into (width, height) parts."""
parts = ratio_str.split(':')
if len(parts) != 2:
raise ValueError(f"Invalid ratio format: {ratio_str}. Use format like '2:1' or '16:9'")
return int(parts[0]), int(parts[1])
def parse_size(size_str: str) -> tuple[int, int]:
"""Parse size string like '1280x640' into (width, height)."""
parts = size_str.lower().split('x')
if len(parts) != 2:
raise ValueError(f"Invalid size format: {size_str}. Use format like '1280x640'")
return int(parts[0]), int(parts[1])
def crop_to_ratio(img: Image.Image, target_ratio: tuple[int, int]) -> Image.Image:
"""Crop image to target aspect ratio, centered."""
orig_width, orig_height = img.size
target_w, target_h = target_ratio
target_aspect = target_w / target_h
orig_aspect = orig_width / orig_height
if orig_aspect > target_aspect:
# Image is wider than target, crop width
new_width = int(orig_height * target_aspect)
new_height = orig_height
left = (orig_width - new_width) // 2
top = 0
else:
# Image is taller than target, crop height
new_width = orig_width
new_height = int(orig_width / target_aspect)
left = 0
top = (orig_height - new_height) // 2
right = left + new_width
bottom = top + new_height
return img.crop((left, top, right, bottom))
def main():
parser = argparse.ArgumentParser(description='Crop banner to target aspect ratio')
parser.add_argument('input', help='Input image path')
parser.add_argument('output', help='Output image path')
parser.add_argument('--ratio', '-r', help='Target aspect ratio (e.g., 2:1, 16:9)')
parser.add_argument('--size', '-s', help='Target size (e.g., 1280x640)')
parser.add_argument('--width', '-w', type=int, help='Target width (maintains ratio)')
parser.add_argument('--height', type=int, help='Target height (maintains ratio)')
args = parser.parse_args()
if not args.ratio and not args.size:
print("Error: Must specify --ratio or --size", file=sys.stderr)
sys.exit(1)
try:
img = Image.open(args.input)
print(f"Input: {img.size[0]}x{img.size[1]}")
if args.size:
target_w, target_h = parse_size(args.size)
target_ratio = (target_w, target_h)
else:
target_ratio = parse_ratio(args.ratio)
target_w, target_h = None, None
# Crop to ratio
cropped = crop_to_ratio(img, target_ratio)
print(f"Cropped: {cropped.size[0]}x{cropped.size[1]}")
# Resize if target dimensions specified
if args.size:
cropped = cropped.resize((target_w, target_h), Image.Resampling.LANCZOS)
print(f"Resized: {cropped.size[0]}x{cropped.size[1]}")
elif args.width:
ratio = args.width / cropped.size[0]
new_height = int(cropped.size[1] * ratio)
cropped = cropped.resize((args.width, new_height), Image.Resampling.LANCZOS)
print(f"Resized: {cropped.size[0]}x{cropped.size[1]}")
elif args.height:
ratio = args.height / cropped.size[1]
new_width = int(cropped.size[0] * ratio)
cropped = cropped.resize((new_width, args.height), Image.Resampling.LANCZOS)
print(f"Resized: {cropped.size[0]}x{cropped.size[1]}")
cropped.save(args.output)
print(f"Saved: {args.output}")
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Banner Preview</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #1a1a1a;
color: #fff;
padding: 20px;
}
h1 {
text-align: center;
margin-bottom: 20px;
font-weight: 300;
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
max-width: 1600px;
margin: 0 auto;
}
.card {
background: #2a2a2a;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
}
.card img {
width: 100%;
height: auto;
display: block;
}
.card-info {
padding: 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-number {
font-size: 14px;
font-weight: 600;
color: #888;
}
.card-fav {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
opacity: 0.3;
transition: opacity 0.2s;
}
.card-fav:hover, .card-fav.active {
opacity: 1;
}
/* Modal */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.95);
z-index: 1000;
justify-content: center;
align-items: center;
padding: 40px;
}
.modal.active { display: flex; }
.modal img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.modal-close {
position: absolute;
top: 20px;
right: 30px;
font-size: 40px;
color: #fff;
cursor: pointer;
opacity: 0.7;
}
.modal-close:hover { opacity: 1; }
.modal-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
font-size: 60px;
color: #fff;
cursor: pointer;
opacity: 0.5;
user-select: none;
}
.modal-nav:hover { opacity: 1; }
.modal-prev { left: 20px; }
.modal-next { right: 20px; }
.modal-info {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.8);
padding: 10px 20px;
border-radius: 8px;
font-size: 14px;
}
/* Favorites summary */
.favorites {
position: fixed;
bottom: 20px;
right: 20px;
background: #333;
padding: 16px 20px;
border-radius: 8px;
max-width: 300px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
.favorites h3 {
font-size: 14px;
margin-bottom: 8px;
color: #888;
}
.favorites-list {
font-size: 16px;
font-weight: 600;
}
.favorites:empty { display: none; }
@media (max-width: 1200px) {
.grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) {
.grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<h1>Banner Variations</h1>
<div class="grid" id="grid"></div>
<div class="modal" id="modal">
<span class="modal-close" onclick="closeModal()">×</span>
<span class="modal-nav modal-prev" onclick="navModal(-1)">‹</span>
<img id="modal-img" src="" alt="">
<span class="modal-nav modal-next" onclick="navModal(1)">›</span>
<div class="modal-info" id="modal-info"></div>
</div>
<div class="favorites" id="favorites"></div>
<script>
// Configuration - UPDATE THIS
const BANNER_COUNT = 20;
const BANNER_PREFIX = 'banner-';
const BANNER_EXT = '.png';
let currentIndex = 0;
let favorites = new Set();
function init() {
const grid = document.getElementById('grid');
for (let i = 1; i <= BANNER_COUNT; i++) {
const num = String(i).padStart(2, '0');
const filename = `${BANNER_PREFIX}${num}${BANNER_EXT}`;
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = `
<img src="${filename}" alt="Banner ${num}" onclick="openModal(${i-1})">
<div class="card-info">
<span class="card-number">#${num}</span>
<button class="card-fav" onclick="toggleFav(${i}, event)">★</button>
</div>
`;
grid.appendChild(card);
}
}
function openModal(index) {
currentIndex = index;
updateModal();
document.getElementById('modal').classList.add('active');
document.body.style.overflow = 'hidden';
}
function closeModal() {
document.getElementById('modal').classList.remove('active');
document.body.style.overflow = '';
}
function navModal(dir) {
currentIndex = (currentIndex + dir + BANNER_COUNT) % BANNER_COUNT;
updateModal();
}
function updateModal() {
const num = String(currentIndex + 1).padStart(2, '0');
const filename = `${BANNER_PREFIX}${num}${BANNER_EXT}`;
document.getElementById('modal-img').src = filename;
document.getElementById('modal-info').textContent = `Banner #${num}`;
}
function toggleFav(num, event) {
event.stopPropagation();
const btn = event.target;
if (favorites.has(num)) {
favorites.delete(num);
btn.classList.remove('active');
} else {
favorites.add(num);
btn.classList.add('active');
}
updateFavorites();
}
function updateFavorites() {
const el = document.getElementById('favorites');
if (favorites.size === 0) {
el.innerHTML = '';
} else {
const nums = Array.from(favorites).sort((a,b) => a-b).map(n => `#${String(n).padStart(2,'0')}`);
el.innerHTML = `<h3>Favorites</h3><div class="favorites-list">${nums.join(', ')}</div>`;
}
}
document.addEventListener('keydown', (e) => {
if (!document.getElementById('modal').classList.contains('active')) return;
if (e.key === 'Escape') closeModal();
if (e.key === 'ArrowLeft') navModal(-1);
if (e.key === 'ArrowRight') navModal(1);
});
init();
</script>
</body>
</html>
Related skills
How it compares
Choose banner-creator for quick platform-specific social and README headers rather than full poster or print design workflows.
FAQ
What API key is required?
GEMINI_API_KEY from Google AI Studio plus the nanobanana image skill.
Where are files saved?
Under .skill-archive/banner-creator/<yyyy-mm-dd-summaryname>/.
Can it match specific aspect ratios?
Yes. The workflow discusses format and crops to the target ratio.
Is Banner Creator safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.