brand-visuals
Skill0 starsChanged 26 days agoMITView on GitHub
Render on-brand visual assets from the brand kit: featured images and Open Graph cards, LinkedIn and Instagram feed graphics, profile banners, avatars, title slides, LinkedIn carousels, and print-ready PDF reports. Use when the user says "make a featured image," "OG card," "social card," "link preview image," "LinkedIn banner," "make a carousel," "stat card," "quote card," "announcement graphic," "title slide," "render the graphic headline," "turn this into a PDF," "export this report to PDF," "brand this document," "make this look like us," "check our brand colors," "contrast check," or "is our palette accessible." Also use after `social-generator` writes a graphic headline or text overlay, and when an audit's markdown fallback should go out as a branded PDF.
- Installs packages
In this repository9 other files it gives its agents.
- brand-kit-setupSkill
- company-reconSkill
- competitive-white-spaceSkill
- content-writerSkill
- google-ads-auditSkill
- house-styleSkill
- linkedin-ads-auditSkill
- social-generatorSkill
- transcript-to-articleSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
19 rules. About 0 are common, 5 are distinctive, and 14 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
14
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 32
Read these first. They are the source of truth and this skill does not invent around them.
- Very specific: line 43
If brand-kit/ is missing or still has its template placeholders, stop and tell the user to run brand-kit-setup.
- Very specific: line 46
If house-style is installed, run it as the final voice pass over every line of copy before you render.
- Very specific: line 50
Kits written before the visual headings existed: If brand.md has no Logo, Dark Surfaces, Chart Colors, Type Scale, or Spacing and Shape, …
- Very specific: line 75
Every command below runs from the repository root, where brand-kit/ lives.
- Very specific: line 76
If you run from somewhere else, pass --kit <path to brand-kit>.
- Very specific: line 89
Run it once per session and again after any change to brand.md.
- Very specific: line 131
When in doubt: would the reader type something?
- Very specific: line 171
Stats: only from Headline Stats. If the Verified column starts with Unverified or carries an example flag, do not render it without the …
- Very specific: line 182
If social-generator already produced a graphic headline with accent words marked, use it as the title as written.
- Very specific: line 187
Write a JSON file, one object per card.
- Distinctive: line 267
Every card ships with alt text, and the script will not render one without it.
- Distinctive: line 273
Never start with "Image of".
- Very specific: line 301
Use --page Letter for readers in the US and Canada and --page A4 everywhere else.
- Distinctive: line 345
LinkedIn: after replacing a link preview image, run the page URL through LinkedIn's Post Inspector, or the old image keeps appearing on …
- Very specific: line 355
Every color, font, size, and logo comes from brand-kit/brand.md.
- Very specific: line 369
No number without a source, no quote that is not cleared, and no logo you drew.
- Very specific: line 373
Every word follows brand-kit/voice.md.
- Distinctive: line 379
All optional. This skill works alone.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Brand Visuals
Two render pipelines and one brand. Cards: images at the exact sizes the platforms want, from a 400px avatar to a 1920px title slide, plus multi-page LinkedIn carousels. Documents: a component template that renders to a self-contained HTML page and a print-ready PDF.
This skill owns layout, sizing, and checking. It owns no color, font, logo, or
copy. Every one of those comes from brand-kit/, and the scripts refuse to
guess at the ones that matter.
Before you render anything
Read these first. They are the source of truth and this skill does not invent around them.
brand-kit/brand.md:Company,Logo,Palette,Dark Surfaces,Chart Colors,Typography,Type Scale,Spacing and Shape,Document Footer, andCalls to Actionbrand-kit/voice.md: house style for every word that goes on a card or a pagebrand-kit/positioning.md:Named Traps,Headline Stats,External Validation Quotes, andInternal Quotes, which supply most card copybrand-kit/capabilities.md: read before making any product claim
If brand-kit/ is missing or still has its template placeholders, stop and tell
the user to run brand-kit-setup. Do not proceed with invented positioning.
If house-style is installed, run it as the final voice pass over every line of
copy before you render. Short copy is where banned words hide best, and a
rendered image is harder to correct than a draft.
Kits written before the visual headings existed. If brand.md has no
Logo, Dark Surfaces, Chart Colors, Type Scale, or Spacing and Shape,
the scripts derive those values from Palette, keep them above contrast
minimums, and print exactly what they derived. Render anyway, tell the user
which values were derived when you deliver, and offer to add the real ones with
brand-kit-setup. A derived value passes contrast and is still a guess about
their brand.
Setup, once per machine
Node 18 or newer. From this skill's folder:
npm install
npx playwright install chromiumThe scripts also find a Playwright that is installed globally, which is common
in hosted sandboxes, so check before installing: if
node skills/brand-visuals/scripts/check-brand.mjs --fonts runs and reports
on fonts, setup is done. Without --fonts it never starts the browser, so it
proves nothing about setup.
Every command below runs from the repository root, where brand-kit/ lives.
If you run from somewhere else, pass --kit <path to brand-kit>. Output goes to
output/brand-visuals/ under the current folder, which .gitignore already
covers. When the host gives you a folder for files the user downloads, pass
--out pointing at it.
Step 1: check the kit
node skills/brand-visuals/scripts/check-brand.mjs --fontsRun it once per session and again after any change to brand.md. It reports:
- What was derived rather than read, if any heading is missing.
- Contrast for every text and ground pairing the templates use, against
WCAG: 4.5:1 for text, 3:1 for headlines, marks, and chart series. A
FAILprints a passing value next to it. - Color vision: whether status colors and chart series 1 to 4 stay distinguishable under protanopia, deuteranopia, and tritanopia.
- Fonts: whether every family and weight in
Typographyactually loads.
What to do with the result:
- A
FAILis a brand-kit fix, not a render setting. Report the pair and the passing value, and ask the user to updatePaletteorDark Surfaces. Render only if they say to go ahead, and say in the delivery note which pair fails. FILL ONLYis normal. It means a color, usually a bright accent, works as a fill or a highlight but must never carry text on that ground. The templates handle it: accent words on that ground are underlined in the accent instead of colored with it.- A font that does not load means the session cannot reach Google Fonts or
the family is misspelled. Run
node skills/brand-visuals/scripts/fetch-fonts.mjssomewhere with network access to save the fonts intobrand-kit/fonts/, or fix the spelling. Never ship a render that fell back to a system font without telling the user, because the metrics are wrong and the brand is not on it.
Pipeline 1: cards
Choosing the variant
This is the decision that matters. Get it right and the rest is mechanical.
| Variant | Use for | Default ground |
article | An article, essay, audit, report, or anything a reader will read | Background, with a Primary rail and footer band |
tool | Something a reader will type: an install command, a CLI, a repository | Ground from Dark Surfaces, with the command in Terminal |
announcement | A launch, release, or event with one action to take | Primary dark, with the CTA as the accent |
stat | One number from Headline Stats | Background soft |
quote | One sentence from External Validation Quotes or a cleared Internal Quotes row | Background |
mark | The logo mark alone, for avatars | Primary |
When in doubt: would the reader type something? tool. Is the point one
number? stat. One sentence somebody said? quote. Is there one action with a
reason to take it now? announcement. Everything else is article.
Any card can take a different ground with "ground", named by Palette role
(Primary, Primary dark, Accent, Accent soft, Background,
Background soft, Success / positive, Muted text, Body text), or
Dark ground and Dark raised from Dark Surfaces. A tool card only takes a
dark ground, because the terminal color never sits on a light one. The
script picks text, muted, emphasis, and accent colors that pass contrast on
whatever ground you choose. Vary the ground across a series so six posts do not
read as one post seen six times.
Choosing the size
| Size | Pixels | Use for |
og | 1200 x 630 | Link previews on LinkedIn, X, Slack, and Open Graph, and the GitHub social preview |
square | 1080 x 1080 | Feed posts on LinkedIn and Instagram |
portrait | 1080 x 1350 | Taller feed posts, and LinkedIn carousel pages |
banner | 1584 x 396 | LinkedIn personal profile banner. Text sits right, clear of the profile photo |
slide | 1920 x 1080 | 16:9 title slides, video thumbnails, webinar cards |
avatar | 400 x 400 | Profile pictures. The mark variant only |
Writing the copy
The copy matters more than the layout, and it all comes from the kit.
- Eyebrow: two to five words. A trap name from
Named Traps, a series name, or the content type. It sets in capitals automatically, so write it in sentence case. - Title: the claim, not the topic. Aim under 60 characters for
ogandsquare. Past 90 the script warns, because the fitter will shrink it until it is small rather than make it good. Cut words instead. - Kicker: one sentence that pays off the title.
- The accent: wrap exactly one phrase in asterisks,
*like this*. That phrase takes theAccentcolor, or an accent underline when the accent is too light to read on that ground. The script refuses a card with two. - Line breaks:
\nin any field forces a break. Use it to keep a phrase like a product name from splitting across lines. - Stats: only from
Headline Stats. If theVerifiedcolumn starts withUnverifiedor carries an example flag, do not render it without the user's explicit go-ahead, and put that status in the source line. The script refuses a stat card without"source". - Quotes: verbatim, from
External Validation Quotesor fromInternal QuoteswhereCleared for external usesays yes."source"is the name, title, and company as the kit records them. - CTA: the exact words from
Calls to Action, onannouncementcards only. The CTA button is that card's accent, so the text takes no asterisks. - Command: exactly what to type, and you have checked that it works.
If social-generator already produced a graphic headline with accent words
marked, use it as the title as written.
Writing the spec
Write a JSON file, one object per card. Shared fields go in "defaults".
{
"defaults": { "size": "og" },
"cards": [
{
"name": "launch-og",
"variant": "announcement",
"eyebrow": "Now available",
"title": "The claim, with *one phrase* marked",
"kicker": "One sentence that pays it off.",
"cta": "Exact CTA from Calls to Action",
"alt": "What the image says, including every word on it."
}
]
}| Field | Used by | Notes |
name | all | Becomes the file name. No slashes |
variant, size | all | From the tables above |
alt | all | Required. See Alt text below |
eyebrow | all but mark | |
title | article, tool, announcement | Required for those three |
kicker | article, tool, announcement | |
command, path | tool | path is the folder shown in the terminal title bar |
cta | announcement | |
stat, label, source | stat | All three required |
quote, source | quote | Both required |
ground | all | Optional override, by role name |
meta | all | Optional. Replaces the domain in the footer |
Then render:
node skills/brand-visuals/scripts/render-card.mjs cards.jsonEach card becomes a PNG, and manifest.json beside them records the alt text
and every check. The script reports per card:
- fitted: which text it shrank and by how much. A small fit is fine. A big one means the copy is too long.
- ERROR: a card that must not ship. Overflow, a missing source or alt text, two accents, or fonts that fell back. Fix and re-render.
- warning: text in the outer 6% of the frame where platforms crop, or text where the profile photo covers a banner. Fix it or explain it.
- note: something the template handled for you, such as the underlined accent.
Carousels
A LinkedIn carousel is a PDF uploaded as a document post. Give a card a
"slides" array instead of a title, and every slide inherits the card's fields
except alt. The card's alt is one sentence for the carousel as a whole, and
each slide needs its own alt with the words on that page. Carousels render at
portrait unless the card sets its own size, whatever defaults says:
{
"name": "traps-carousel",
"size": "portrait",
"alt": "Five-page carousel. One sentence saying what it argues.",
"slides": [
{ "variant": "article", "title": "The hook, as a claim", "alt": "The words on page one" },
{ "variant": "stat", "stat": "3.2x", "label": "What the number means", "source": "Where it comes from", "alt": "Statistic: 3.2x, what it means" },
{ "variant": "announcement", "title": "The point", "cta": "Exact CTA", "alt": "The point. Call to action: Exact CTA" }
]
}That renders one PNG per page plus traps-carousel.pdf. Pages carry a
01 / 05 counter in the footer, and the last page shows the domain instead.
Five to ten pages. Page one is the hook, written as a claim rather than a cover.
The last page is the one action, from Calls to Action.
Alt text
Every card ships with alt text, and the script will not render one without it.
- Say what the image says, not what it looks like. Include every word on the card, headline first, word for word.
- Name the type when it matters: "Statistic card", "Quote from", "Bar chart".
- Under 250 characters. Most platforms truncate after that.
- Never start with "Image of".
Deliver the alt text with each file, because the user pastes it into the
platform by hand. manifest.json has it for every card.
Pipeline 2: documents and PDFs
For client deliverables, reports, and readouts.
- Copy
assets/document.htmlto a working file. Set<title>, because it becomes the PDF's title in every reader. - Build the document from its components.
references/components.mdlists each one with its markup and when to use it. Delete the components you do not use. Never add a color, a font, or a size inline. If you need one the kit does not have, the kit is missing something. - Brand it:
This writes the fonts, tokens, logo, and footer into the file, which is now one self-contained page that can be shared or published as it is. It follows the reader's light or dark preference. Run it again whenever the kit changes.node skills/brand-visuals/scripts/apply-brand.mjs report.html - Render the PDF:
Usenode skills/brand-visuals/scripts/render-pdf.mjs report.html --page Letter--page Letterfor readers in the US and Canada and--page A4everywhere else. If you cannot tell from the user's location or the kit'sWebsite, ask. Add--datedto use the datedDocument Footervariant when the document carries numbers.
render-pdf.mjs brands a temporary copy, so step 3 is only needed for the HTML
deliverable. It also fixes, every time, the four defects that broke earlier PDF
exports: it pins the light theme so nothing prints dark, opens scroll containers
so wide tables do not lose columns, keeps rows, tiles, and callouts from
splitting across pages with table headers repeating, and forces exact color so
status pills keep their backgrounds. It then warns about off-palette colors,
clipped content, images without alt text, and leftover placeholders.
The HTML is the source. The PDF is a snapshot. Revise the HTML and render again. Never edit a PDF, or the version a client holds stops matching the one that can be updated.
Turning another skill's markdown into a document. company-recon,
competitive-white-space, google-ads-audit, and linkedin-ads-audit fall
back to markdown when no docx skill is present. To send one as a PDF instead,
map it onto components: each ## heading to an h2 section, every table to a
table inside .scroll, headline numbers to .stat tiles, the words KEEP,
WATCH, and CUT (or any good, caution, problem judgment) to verdict pills,
caveats to .note callouts, and worked arithmetic to a .calc block. Keep every
number and every sentence. This is formatting, not editing.
Last step: look at it
The automatic checks catch overflow, contrast, fonts, and crop zones. They do
not catch a headline that breaks in a silly place or a card that is technically
correct and dull. Open every PNG and every PDF page with your file reader before
you deliver, and run the checklist in references/qa.md.
Then check the work against references/donts.md. Misuse is easier to see in a
list of examples than in rules.
After delivering
- GitHub: set the
ogcard as the repository's social preview under the repository settings, so a link to the repo and a link to its page match. - LinkedIn: after replacing a link preview image, run the page URL through LinkedIn's Post Inspector, or the old image keeps appearing on anything already shared.
- Banners: LinkedIn crops banners differently on phones and desktops. Look at the profile on both before calling it done.
Rules
- Every color, font, size, and logo comes from
brand-kit/brand.md. Never type a color value into a spec, a template, or a document.scripts/lint-colors.mjsfinds any that slipped in. - One accent per view, on the single thing to read first.
- Status is never carried by color alone. Pills carry a symbol and a word, and chart series are labeled where they sit.
- On dark grounds, text and emphasis come from
Dark Surfaces. The primary that works on white usually fails on dark. - The
Terminalcolor appears ontoolcards only, and never on a light ground. - Fonts must actually load. A render that fell back to a system font does not ship without the user knowing.
- Nothing marked
ERRORships. Every warning is fixed or explained in the delivery note. - No number without a source, no quote that is not cleared, and no logo you
drew. If
Logois empty, the name is set in the display face, and that is the honest answer. - The HTML is the source. Revise it and re-render rather than editing a PDF.
- Every word follows
brand-kit/voice.md.
Cross-references
All optional. This skill works alone.
brand-kit-setupfills the visual headings this skill reads, and runscheck-brand.mjsas part of its validation when this skill is installed.house-styleruns the voice pass on card copy before rendering.social-generatorwrites graphic headlines and text overlays with the accent words already marked. Render them here assquareorportraitcards.content-writerdrafts the article a featured image goes with.company-recon,competitive-white-space,google-ads-audit, andlinkedin-ads-auditproduce markdown this skill can turn into a branded PDF.
Anything not in skills/ does not exist. Do not reference it.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

