Use when someone wants Instagram or LinkedIn carousel slides from a topic, a brief or pasted copy. A fixed brand template sets the text on every slide, and an optional library of hand-drawn illustrations, drawn once in code per brand and reused across decks, supplies the pictures. No image models or stock.
Installs into .claude/skills of the current project.
Sponsors
SSkills DirectorySkills Directory
Know which skills are safe — weekly.
SSkills DirectorySkills Directory
Is that skill safe to install?
Are you the author of Carousel Maker?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/oleg-koval-carousel-maker)
---
name: carousel-maker
description: Use when someone wants Instagram or LinkedIn carousel slides from a topic, a brief or pasted copy. A fixed brand template sets the text on every slide, and an optional library of hand-drawn illustrations, drawn once in code per brand and reused across decks, supplies the pictures. No image models or stock.
license: MIT
allowed-tools: Bash, Read, Write, Edit
compatibility: Codex, Claude Code, Hermes Agent, Cursor, GitHub Copilot, Windsurf, Kiro, and other Agent Skills compatible tools. Needs Node 22.18+ and Chrome; ffmpeg for the contact sheet. The illustration library needs the shorts-animator skill installed alongside.
metadata:
targets: [_source-only]
author: Oleg Koval
tags:
- instagram
- carousel
- social
- design
- slides
---
<!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->
# Carousel Maker
A brand and a few lines of copy in, 1080x1350 carousel slides out. The template places the text, so it
is never garbled and never off-brand. Pictures come from the brand's library: drawings written once as
canvas code, painted by shorts-animator, and reused by every deck after that. Text-only decks need no
library at all.
`CM` = `node <this skill's folder>/scripts/cm.ts`. Write the full command each time. One folder per
brand: `BRAND=./<slug>-carousels`. Decks live in `$BRAND/decks/<deck>.json`.
## 1. Decide the copy
Decide the slides yourself. Ask one question only if the subject or the brand is unclear. A pasted
script is used word for word.
- 5 to 8 slides: a `cover`, then `point` slides, then one `cta`.
- The cover is a promise in 8 words or fewer. It is the only slide most people see.
- One idea per point slide: a title of 7 words or fewer, a body of 22 words or fewer.
- The cta asks for one thing (save, follow, comment) and carries a `follow` line.
- `caption` is the post text: two short lines and one question for the comments.
## 2. Set up the brand
```bash
CM new "$BRAND" --name "Slow Hours" --handle @slowhours --look ink # brand.json, decks/, art/, library/
```
`brand.json`: `name`, `handle`, `colors` (`paper`, `ink`, `accent`, `muted`, all `#rrggbb`), `fonts`
(`head`, `body`, CSS font stacks). Set them from what they told you; the defaults are cream, near-black
and orange. `--look` picks the paint for the library: `ink`, `gouache`, `watercolor`, `pastel`, `oil`.
A deck:
```json
{ "title": "mornings", "caption": "...", "slides": [
{ "kind": "cover", "title": "Slow mornings, in 4 steps", "body": "No 5am club.", "art": "window" },
{ "kind": "point", "title": "Coffee, outside", "body": "Five minutes on the step.", "art": "coffee" },
{ "kind": "point", "title": "Their own photo", "art": "photos/desk.jpg" },
{ "kind": "cta", "title": "Save this for tomorrow", "follow": "Follow @slowhours" } ] }
```
`art` is a library drawing (a bare name), a file (a path, relative to `$BRAND`), or left out for a
text-only slide, which gets larger type.
## 3. Draw the library (once per brand)
Skip this for text-only decks. Every bare `art` name needs a function in `$BRAND/art/scenes.js`:
`window.SCENES = { "window": (k) => { k.sky({...}); ... } }`. Read `../shorts-animator/references/kit.md`
before writing scenes. Draw objects and places, square, one subject, big simple shapes, strong value
contrast; the paint pass adds texture. No people's faces close up.
```bash
CM art "$BRAND" # every drawing the decks use -> library/<name>.jpg
CM art "$BRAND" coffee # only these
```
Look at the sheet it reports once. Redraw only the drawings that are muddy, empty or look alike. The
library is the brand's asset: the next deck reuses it and only new names cost a drawing.
## 4. Check, render, look (budgeted)
```bash
CM check "$BRAND" [deck] # no browser: unknown kinds, missing drawings or files, deck shape, word counts
CM render "$BRAND" [deck] # out/<deck>/slide-N.png, sheet.jpg, caption.txt; measures every slide
```
Fix every `ERROR` from `check` before rendering. `render` also fails a slide whose text spills out of
its frame or whose picture did not load. Then read `out/<deck>/sheet.jpg` once. **Budget: at most two
looks per deck** (the library sheet and the deck sheet). Images are the expensive part of this loop; do
not reopen a sheet you have already judged.
Tell them in two lines what it is (slides, look) and: "Tell me what to change, one sentence is enough."
## 5. One-sentence changes
Smallest change, `CM check`, render, show.
| they say | change |
|---|---|
| shorter, punchier | the slide's `title` or `body` |
| different colours, our brand | `brand.json` `colors` |
| serif, sans, our font | `brand.json` `fonts` |
| a different picture on slide 3 | that slide's `art`, or one new scene plus `CM art "$BRAND" <name>` |
| no pictures | drop `art` from the slides |
| another post on the same brand | a new `decks/<deck>.json`; reuse library names |
## When something goes wrong
| symptom | fix |
|---|---|
| no Chrome | install Chrome, or set `CHROME` to a Chrome or headless-shell binary |
| `CM art` cannot find shorts-animator | install it alongside, or set `SHORTS_ANIMATOR` to its `scripts/sa.ts` |
| "text spills out of the frame" | cut words; `check` warns before this happens |
| no sheet.jpg | ffmpeg is missing; the slides are still in `out/<deck>/` |