skills/capabilities/create-html-carousel/SKILL.md
Create LinkedIn carousel posts as high-quality PNG images. Design informational multi-slide posts like "5 AI GTM workflows" with consistent styling, then automatically screenshot each slide at LinkedIn's optimal 1080x1080px format.
npx skillsauth add gooseworks-ai/goose-skills create-html-carouselInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Deprecated: This skill is superseded by
goose-graphics. Seeskills/composites/goose-graphics/(install withnpx goose-skills install goose-graphics). The carousel format is one of seven formats in the newer skill and supports 36 style presets plus image sourcing and PNG export. This skill is retained for one release cycle before removal.
Create stunning LinkedIn carousel posts as PNG images. This skill generates styled HTML slides optimized for square format (1080×1080px), then automatically screenshots each slide for direct upload to LinkedIn.
Format: Square (1080×1080px)
Content Structure:
Use for LinkedIn carousel posts like:
NOT for:
1. Content Input → User provides topic/outline
2. Style Selection → Choose visual style (or preview options)
3. HTML Generation → Create 1080×1080px HTML slides
4. Screenshot → Auto-capture each slide as PNG
5. Delivery → Folder of PNG files ready for LinkedIn upload
Ask the user:
Question 1: What's the topic?
Question 2: Content Type
Question 3: Slide Count
Question 4: Branding Handle
Question 5: Content Ready?
If user has content, ask them to share it.
Each slide should be scannable in 2-3 seconds on mobile:
| Slide Type | Max Content | | ---------- | -------------------------------------------------------- | | Cover | Title (1 line) + subtitle (1 line) + branding | | List item | Number/icon + heading (2 lines max) + body (3 lines max) | | Framework | Diagram/visual + 2-4 labels | | Quote/Stat | 1 large stat or quote + context | | CTA | 1 action + visual element |
If content exceeds limits: Break into multiple slides or simplify.
Users can choose styles two ways:
Show preset picker:
Question: Pick a Style
(See STYLE_PRESETS.md for full details on each style)
If user isn't sure, ask:
Question: Audience & Tone
Then generate 2-3 preview slides and let user pick.
All carousel files (HTML source and PNG exports) are saved to the shared assets directory.
[carousel-name]/
├── index.html # Full carousel (all slides)
├── slides/
│ ├── slide-01.html # Individual slide pages
│ ├── slide-02.html
│ └── ...
└── exports/
├── slide-01.png # Screenshots (generated in Phase 4)
├── slide-02.png
└── ...
CRITICAL: LinkedIn carousel slides are SQUARE (1:1 ratio), not widescreen.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Slide 01</title>
<!-- Fonts -->
<link rel="stylesheet" href="https://api.fontshare.com/v2/css?f[]=..." />
<style>
/* ===========================================
LINKEDIN CAROUSEL: SQUARE FORMAT
Fixed 1080×1080px for screenshot
=========================================== */
:root {
/* Fixed size for LinkedIn */
--slide-width: 1080px;
--slide-height: 1080px;
/* Colors (from chosen preset) */
--bg-primary: #0a0f1c;
--text-primary: #ffffff;
--accent: #00ffcc;
/* Typography - scaled for square format */
--title-size: 72px;
--subtitle-size: 36px;
--body-size: 28px;
--small-size: 20px;
/* Spacing */
--slide-padding: 80px;
--content-gap: 40px;
/* Animation */
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
width: var(--slide-width);
height: var(--slide-height);
overflow: hidden;
}
body {
font-family: var(--font-body);
background: var(--bg-primary);
color: var(--text-primary);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: var(--slide-padding);
}
/* Content container */
.slide-content {
width: 100%;
max-width: 100%;
display: flex;
flex-direction: column;
gap: var(--content-gap);
}
/* Typography hierarchy */
h1 {
font-size: var(--title-size);
font-weight: 800;
line-height: 1.1;
margin-bottom: 20px;
}
h2 {
font-size: var(--subtitle-size);
font-weight: 700;
line-height: 1.2;
}
p,
li {
font-size: var(--body-size);
line-height: 1.4;
}
/* List styling */
ul {
list-style: none;
}
li {
padding-left: 40px;
position: relative;
margin-bottom: 20px;
}
li::before {
content: "→";
position: absolute;
left: 0;
color: var(--accent);
font-weight: bold;
}
/* Number badge (for list items) */
.number {
font-size: 120px;
font-weight: 900;
color: var(--accent);
opacity: 0.15;
position: absolute;
top: -40px;
left: -20px;
z-index: 0;
}
/* Branding footer */
.brand {
position: absolute;
bottom: var(--slide-padding);
right: var(--slide-padding);
font-size: var(--small-size);
opacity: 0.7;
}
/* ===========================================
STYLE-SPECIFIC OVERRIDES
Inject preset styles here
=========================================== */
/* ... preset-specific CSS ... */
</style>
</head>
<body>
<div class="slide-content">
<!-- Slide content goes here -->
<h1>Your Title Here</h1>
<p>Your content here</p>
</div>
<div class="brand">@yourbrand</div>
</body>
</html>
Cover Slide:
<div class="slide-content">
<h1>5 AI GTM Workflows<br />You Should Be Using</h1>
<p>Scale your outbound without scaling your team</p>
</div>
<div class="brand">@yourhandle</div>
Numbered Item (e.g., Slide 2/6):
<div class="slide-content">
<div class="number">01</div>
<h2>Signal-Based Outbound</h2>
<p>
Monitor job postings, funding announcements, and tech stack changes to find
companies actively solving your problem.
</p>
</div>
<div class="brand">@yourhandle • 1/5</div>
Framework Slide:
<div class="slide-content">
<h2>The GTM Engineering Stack</h2>
<div class="framework-grid">
<div class="box">Research</div>
<div class="box">Personalization</div>
<div class="box">Outreach</div>
<div class="box">Tracking</div>
</div>
</div>
<div class="brand">@yourhandle • 3/5</div>
CTA Slide:
<div class="slide-content">
<h2>Want more like this?</h2>
<p>Follow me for more tips and workflows.</p>
<div class="cta">Hit that follow button →</div>
</div>
<div class="brand">@yourhandle</div>
After generating HTML, automatically capture screenshots.
Create a Node.js script to screenshot each slide:
// screenshot-slides.js
const { chromium } = require("playwright");
const path = require("path");
const fs = require("fs");
async function screenshotSlides(slidesDir, outputDir) {
const browser = await chromium.launch();
const page = await browser.newPage();
// Set viewport to LinkedIn carousel size
await page.setViewportSize({ width: 1080, height: 1080 });
// Ensure output directory exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Find all HTML files in slides directory
const slideFiles = fs
.readdirSync(slidesDir)
.filter((f) => f.endsWith(".html"))
.sort();
console.log(`Found ${slideFiles.length} slides to screenshot`);
for (const slideFile of slideFiles) {
const slidePath = path.join(slidesDir, slideFile);
const outputName = slideFile.replace(".html", ".png");
const outputPath = path.join(outputDir, outputName);
console.log(`Capturing ${slideFile}...`);
await page.goto(`file://${path.resolve(slidePath)}`);
// Wait for fonts and animations
await page.waitForTimeout(500);
// Take screenshot
await page.screenshot({
path: outputPath,
type: "png",
fullPage: false,
});
console.log(`✓ Saved ${outputName}`);
}
await browser.close();
console.log("\n✨ All slides captured!");
}
// Usage
const carouselName = process.argv[2];
if (!carouselName) {
console.error("Usage: node screenshot-slides.js <carousel-name>");
process.exit(1);
}
const slidesDir = path.join(__dirname, carouselName, "slides");
const outputDir = path.join(__dirname, carouselName, "exports");
screenshotSlides(slidesDir, outputDir);
The skill directory needs these dependencies:
{
"name": "linkedin-carousel-screenshots",
"version": "1.0.0",
"private": true,
"dependencies": {
"playwright": "^1.40.0"
}
}
First time setup:
cd /path/to/skills/create-html-carousel
npm install
After generating HTML slides:
node screenshot-slides.js carousel-name
This will:
[carousel-name]/exports/After screenshots are generated, present to user:
✨ Your LinkedIn carousel is ready!
📁 Location: /assets/carousel-name/
**Slides:**
- 6 HTML slides in slides/ folder
- 6 PNG images in exports/ folder (1080×1080px)
**Preview:**
Open index.html to see all slides with navigation.
**Upload to LinkedIn:**
1. Create new post on LinkedIn
2. Click "Add media"
3. Upload all PNGs from exports/ folder in order
4. Add your post copy
5. Publish!
**File sizes:**
- slide-01.png: 234 KB ✓
- slide-02.png: 198 KB ✓
- slide-03.png: 256 KB ✓
(All under 10MB limit)
Want to make any changes to the slides?
All styles from frontend-slides work for carousels, but require these adjustments:
Square format has less horizontal space, so scale fonts:
| Element | Presentation (16:9) | Carousel (1:1) | | -------- | -------------------------------- | -------------- | | Title | clamp(2rem, 6vw, 5rem) | 72px (fixed) | | Subtitle | clamp(1.25rem, 3vw, 2.5rem) | 36px (fixed) | | Body | clamp(0.875rem, 1.5vw, 1.125rem) | 28px (fixed) | | Small | clamp(0.75rem, 1vw, 0.875rem) | 20px (fixed) |
Why fixed sizes? We're targeting a single export size (1080×1080px), not responsive web viewing.
Vertical space is precious:
Mobile-first mindset:
Strong hooks for LinkedIn carousels:
Each slide should:
Always include a CTA:
Avoid:
Symptom: Screenshots show default system fonts
Solution:
await page.waitForLoadState('networkidle') before screenshotawait page.waitForTimeout(1000)Symptom: Text looks fuzzy or low-res
Solution:
await page.setViewportSize({
width: 1080,
height: 1080,
deviceScaleFactor: 2, // Retina-quality
});
Symptom: Text or elements cut off in screenshot
Solution:
Symptom: PNG colors don't match HTML preview
Solution:
| Preset | Best For | Vibe | | --------------- | ---------------------- | --------------------- | | Bold Signal | Confident, high-impact | Professional | | Dark Botanical | Elegant, premium | Sophisticated | | Notebook Tabs | Editorial, organized | Friendly-professional | | Pastel Geometry | Friendly, approachable | Playful | | Neon Cyber | Tech, innovation | Futuristic | | Split Pastel | Creative, fun | Energetic |
See STYLE_PRESETS.md for complete styling details.
Total time: 5-10 minutes from idea to ready-to-publish carousel.
development
End-to-end skill that turns a single reference image into a fully-installed, example-rendered style preset for the goose-graphics composite. Analyzes the image, writes the slim style spec, registers it in styles/index.json, generates all 7 format examples using the standard brief, renders PNGs via Playwright, and updates examples/manifest.json. Invoke with /goose-graphics-create-style.
development
Evaluate YC batch companies for investment — scrapes the YC directory, researches each company and its founders (work history, LinkedIn, website), assesses founder-company fit, and exports to Google Sheets with priority rankings. Use when asked to evaluate YC companies, research a YC batch, screen startups, or do due diligence on YC companies.
tools
Take screenshots of any website using Notte browser automation. Use when asked to screenshot, capture, or snap a webpage.
development
Search the web, platforms, and datasets. Use when asked to search, find, look up, research, or discover information from the web, YouTube, Amazon, eBay, news, academic sources, or any online platform.