Ship Brand
Turn docs/brand.json (brand-as-data: colors + fonts) into a complete, production-ready brand
kit: an expanded palette, a type scale, design tokens, a favicon/app-icon spec, social images,
and an OG share image — all derived deterministically from one file so everything is on-brand.
This is the brand-kit member of the /ship-* pack. It pairs with:
- logo-pack (
ship-logos) — makes the logo; ship-brand turns it into the full asset set.
- user-guide-builder (
ship-guide) and shipping-log (ship-changelog) — read the same
brand.json so docs match the kit.
- screenshot-capture (
ship-screenshots) — uses the same tokens for on-brand callouts.
The split is deliberate: the derivation (palette math, contrast, sizes, manifests) is
deterministic and done by a script. The image generation is generator-agnostic — the skill
writes specs and prompts; you point them at whatever image engine is set up (adapters below).
Workflow
1. Discover what already exists (always first)
Never overwrite blindly. Scan inputs and prior outputs:
node "${CLAUDE_PLUGIN_ROOT}/skills/ship-brand/scripts/scan-assets.mjs" # or --json
It reports: docs/brand.json (name, color count, fonts), docs/VERSION, any prior kit in
docs/brand/, any favicons / app icons / OG images / real web manifests (site.webmanifest,
manifest.webmanifest, bare manifest.json — not the kit's own icon-manifest.json) the app
already ships, and any reusable logos in docs/brand/logos/ (or public/logos/, etc.). It
walks public/, src/app/, and monorepo packages/*/public, then ends with a recommendation:
- No
brand.json → scaffold one (step 2), don't invent a brand.
- Prior kit exists → re-derive to refresh; only regenerate images that actually changed.
- A logo is present → use Adapter A (resize the square mark into every icon size — the
cleanest, brand-preserving path; see step 5). The scan surfaces this, not just the docs.
- App already ships icons → derive the kit, then fill gaps (missing sizes / social / OG)
rather than duplicating what exists.
2. Ensure brand.json exists (honest about missing input)
If scan-assets.mjs found none, scaffold a starter and stop until it's filled in:
node "${CLAUDE_PLUGIN_ROOT}/skills/ship-brand/scripts/brand-kit.mjs" --init
This writes a starter docs/brand.json (no purple by default) with name, tagline, colors,
fonts. Help the user set the real brand color (brand), accent, neutrals, and font names —
then re-run. Do not fabricate brand colors. If a logo exists but no brand.json, run
logo-pack first or read the colors off the logo with the user.
3. Derive the kit (deterministic, dependency-free)
node "${CLAUDE_PLUGIN_ROOT}/skills/ship-brand/scripts/brand-kit.mjs" # human report
node "${CLAUDE_PLUGIN_ROOT}/skills/ship-brand/scripts/brand-kit.mjs" --write # write outputs
--write emits, into docs/brand/:
| File |
What it is |
tokens.css |
:root CSS variables: raw brand colors (incl. alpha overlays, verbatim), a --<name>-50..900 ramp for each opaque color, font vars, --text-* scale |
palette.json |
The palette, the WCAG contrast table, and the type scale — machine-readable |
icon-manifest.json |
The exact icon + social/OG sizes to render, each with its purpose |
site.webmanifest |
A valid PWA web manifest, themed from brand.json |
head-snippet.html |
The <link rel="icon"> / apple-touch / manifest / OG <meta> tags to paste into <head> |
What the script computes (see references/derivation.md for the math):
- Palette — each opaque named color → a 50–900 lightness ramp (hue/sat preserved). Alpha
overlays (e.g.
grid: rgba(...,.07)) keep their raw var only — no ramp, since a tint ladder of
a 7%-opacity scrim is noise.
- Contrast — for every color, the WCAG 2.1 ratio against black and white text, with AA / AAA
verdicts, so you pick text colors with evidence, not by eye.
- Type scale — a modular scale (
display→caption) from --base px and --ratio
(default 16 / 1.25; pass --base 18 --ratio 1.2 to change).
- Icon + social manifest — the standard real-world sizes (favicon 16/32/48,
.ico,
apple-touch 180, PWA 192/512, maskable 512; avatar 512, X banner 1500×500, LinkedIn 1584×396,
OG 1200×630), each tagged with where it's used.
4. Render the brand-kit gallery (self-contained, themeable HTML)
Build a single-file docs/brand/brand-kit.html that shows the kit: palette swatches with their
hex + AA/AAA badges, the type scale rendered live, and an icon spec board (a labeled square
per required size — the spec, with the real images dropped in once generated). Theme it from
tokens.css via :root. Follow references/html-rules.md:
self-contained, no external CSS/JS, themed from brand.json (NEVER purple by default), and
never backdrop-filter or SVG feTurbulence (they hang renderers).
5. Generate the images (generator-agnostic — use an adapter)
The kit specifies what to render; you choose the engine. For each entry in
icon-manifest.json, generate at the listed pixel size onto a transparent (icons) or branded
(social/OG) canvas. Use whichever adapter is available — see
references/generator-adapters.md:
- logo-pack output — the cleanest path: take the winning square mark from
docs/brand/logos/ and resize/pad it to each icon size; compose it onto the OG/banner canvas.
gemini-imagegen skill (Nano Banana Pro) — render icons + OG/social art directly.
using-local-ideogram4 skill — when the OG/banner needs crisp headline text.
- Ad Lab (optional, only if installed) — resolve its path from
$AD_LAB_DIR / PATH / a known
checkout (never a hardcoded drive letter), then node "$AD_LAB_DIR/scripts/generate-image.js" -p "<prompt>" -o docs/brand/icons/<file>. See Adapter D for the discovery snippet.
Save icons to docs/brand/icons/, social to docs/brand/social/. Generate, look at the
output, keep the strongest. For OG/banner, the prompt names the brand colors and reserves a
clear text zone for the product name + tagline.
6. Wire it up + report
Tell the user exactly what to do with the output:
- Copy
docs/brand/icons/* and site.webmanifest to the app's web root (e.g. public/).
- Paste
head-snippet.html into the app's <head> (Next.js: app/layout.tsx metadata or
app/head). Adjust paths if assets live under a sub-path.
- Stamp the kit with
docs/VERSION so it travels with the release.
Report: the derived palette + contrast summary, the type scale, which images were generated vs.
still pending (be honest about gaps), and the wire-up steps. Cross-link ship-logos if the logo
needs work and ship-guide so the docs pick up the new tokens.
Worked example (input → output)
Input — docs/brand.json:
{ "name": "MyFieldTime", "tagline": "Run your jobs. Not your inbox.",
"colors": { "bg": "#0a0a0f", "brand": "#2f7dff", "accent": "#FFDD00", "muted": "#8b9bb4" },
"fonts": { "display": "Bricolage Grotesque", "body": "Hanken Grotesk" } }
Run — node …/brand-kit.mjs --write. Output (excerpt):
brand ramp: 50 #ebf2ff · 200 #9ec2ff · 500 #0060ff · 700 #003fa8 · 900 #001d4d.
- Contrast:
brand #2f7dff → best text black, 5.5:1, AA (so white nav text on a brand button
needs a darker stop like brand-700 for AA body text). accent #FFDD00 → black text,
15.59:1, AAA. muted #8b9bb4 → black, 7.45:1, AAA — safe for secondary text on light.
- Type scale (16 / 1.25):
display 39px · h1 31px · h2 25px · body 16px · caption 10.24px.
tokens.css with all --brand-50..900, --font-display: "Bricolage Grotesque", --text-*.
icon-manifest.json listing 8 icons + 4 social/OG sizes, each with a purpose.
site.webmanifest themed #2f7dff on #0a0a0f; head-snippet.html with the <link>/OG tags.
Then the kit gallery renders those swatches (each with its AA/AAA badge) and an icon spec board,
and the images get generated from the logo-pack mark — a complete kit from one JSON file.
Quality bar
- Derived, not eyeballed. Palette ramps and contrast come from the script's math. Every
text-on-color choice cites a real WCAG ratio + AA/AAA verdict — no "looks fine."
- Honest about inputs. Missing
brand.json → scaffold and stop, never invent a brand.
Unparseable color values are reported and skipped; alpha overlays keep a raw var but no
ramp — both are surfaced in the report, not silently guessed or padded with noise. An
unparseable color that still lands in tokens.css is annotated there too
(/* unparsed — no ramp/contrast, not validated */), so the token file is as honest as the
report and no reader mistakes it for an endorsed token.
- Discovery tells the truth. It distinguishes a shipped web manifest from the kit's own
files: anything under the output dir
docs/brand/ (the site.webmanifest and icons/* that
--write itself produces) is excluded from the shipped-asset scan, so a second run never
re-reports its own output as a pre-existing app asset. It also actually detects a reusable logo
(docs/brand/logos/…) so the recommended Adapter A path is found, not merely described.
- Reuse over churn. Discovery runs first; existing icons are extended, not duplicated. A
re-run refreshes tokens and regenerates only what changed.
- Real sizes, real purposes. The icon manifest is the actual production set (favicon →
.ico
→ apple-touch → PWA/maskable → OG/social), each labeled with where it's used — not a vague list.
- On-brand, never generic. Tokens, manifest theme color, and HTML all source
brand.json.
Never purple unless the brand says so. OG/social art is specific to this product.
- HTML that renders anywhere. Self-contained, themeable via
:root, no backdrop-filter,
no feTurbulence.
- Portable. Nothing hardcoded to one app; image engines are adapters, never assumed installed.
1---2name: ship-brand3description: Generates a complete, production-ready brand kit from docs/brand.json — an expanded color palette (50–900 tints/shades with WCAG-checked text pairings), a modular type scale, a favicon + app-icon spec/manifest at standard sizes, a social avatar + banner, and an Open Graph share image. Discovers existing brand assets first, derives everything deterministically from brand.json with a dependency-free script, and is generator-agnostic for the actual images. Use when the user wants a brand kit, favicon, app icons, social/OG images, a color palette, design tokens, a type scale, a web manifest, or to turn brand.json into real assets.4---56# Ship Brand78Turn `docs/brand.json` (brand-as-data: colors + fonts) into a **complete, production-ready brand9kit**: an expanded palette, a type scale, design tokens, a favicon/app-icon spec, social images,10and an OG share image — all derived deterministically from one file so everything is on-brand.1112This is the **brand-kit** member of the `/ship-*` pack. It pairs with:13- **logo-pack** (`ship-logos`) — makes the *logo*; ship-brand turns it into the full asset set.14- **user-guide-builder** (`ship-guide`) and **shipping-log** (`ship-changelog`) — read the same15 `brand.json` so docs match the kit.16- **screenshot-capture** (`ship-screenshots`) — uses the same tokens for on-brand callouts.1718The split is deliberate: the **derivation** (palette math, contrast, sizes, manifests) is19deterministic and done by a script. The **image generation** is generator-agnostic — the skill20writes specs and prompts; you point them at whatever image engine is set up (adapters below).2122## Workflow2324### 1. Discover what already exists (always first)2526Never overwrite blindly. Scan inputs and prior outputs:2728```bash29node "${CLAUDE_PLUGIN_ROOT}/skills/ship-brand/scripts/scan-assets.mjs" # or --json30```3132It reports: `docs/brand.json` (name, color count, fonts), `docs/VERSION`, any prior kit in33`docs/brand/`, any favicons / app icons / OG images / **real web manifests** (`site.webmanifest`,34`manifest.webmanifest`, bare `manifest.json` — *not* the kit's own `icon-manifest.json`) the app35**already ships**, and any reusable **logos** in `docs/brand/logos/` (or `public/logos/`, etc.). It36walks `public/`, `src/app/`, and monorepo `packages/*/public`, then ends with a recommendation:3738- **No `brand.json`** → scaffold one (step 2), don't invent a brand.39- **Prior kit exists** → re-derive to refresh; only regenerate images that actually changed.40- **A logo is present** → use **Adapter A** (resize the square mark into every icon size — the41 cleanest, brand-preserving path; see step 5). The scan surfaces this, not just the docs.42- **App already ships icons** → derive the kit, then **fill gaps** (missing sizes / social / OG)43 rather than duplicating what exists.4445### 2. Ensure brand.json exists (honest about missing input)4647If `scan-assets.mjs` found none, scaffold a starter and stop until it's filled in:4849```bash50node "${CLAUDE_PLUGIN_ROOT}/skills/ship-brand/scripts/brand-kit.mjs" --init51```5253This writes a starter `docs/brand.json` (no purple by default) with `name`, `tagline`, `colors`,54`fonts`. Help the user set the real brand color (`brand`), accent, neutrals, and font names —55then re-run. **Do not fabricate brand colors.** If a logo exists but no `brand.json`, run56`logo-pack` first or read the colors off the logo with the user.5758### 3. Derive the kit (deterministic, dependency-free)5960```bash61node "${CLAUDE_PLUGIN_ROOT}/skills/ship-brand/scripts/brand-kit.mjs" # human report62node "${CLAUDE_PLUGIN_ROOT}/skills/ship-brand/scripts/brand-kit.mjs" --write # write outputs63```6465`--write` emits, into `docs/brand/`:6667| File | What it is |68|------|-----------|69| `tokens.css` | `:root` CSS variables: raw brand colors (incl. alpha overlays, verbatim), a `--<name>-50..900` ramp for each *opaque* color, font vars, `--text-*` scale |70| `palette.json` | The palette, the WCAG contrast table, and the type scale — machine-readable |71| `icon-manifest.json` | The exact icon + social/OG sizes to render, each with its purpose |72| `site.webmanifest` | A valid PWA web manifest, themed from `brand.json` |73| `head-snippet.html` | The `<link rel="icon">` / apple-touch / manifest / OG `<meta>` tags to paste into `<head>` |7475What the script computes (see **[references/derivation.md](references/derivation.md)** for the math):76- **Palette** — each *opaque* named color → a 50–900 lightness ramp (hue/sat preserved). Alpha77 overlays (e.g. `grid: rgba(...,.07)`) keep their raw var only — no ramp, since a tint ladder of78 a 7%-opacity scrim is noise.79- **Contrast** — for every color, the WCAG 2.1 ratio against black and white text, with AA / AAA80 verdicts, so you pick text colors with evidence, not by eye.81- **Type scale** — a modular scale (`display`→`caption`) from `--base` px and `--ratio`82 (default 16 / 1.25; pass `--base 18 --ratio 1.2` to change).83- **Icon + social manifest** — the standard real-world sizes (favicon 16/32/48, `.ico`,84 apple-touch 180, PWA 192/512, maskable 512; avatar 512, X banner 1500×500, LinkedIn 1584×396,85 OG 1200×630), each tagged with where it's used.8687### 4. Render the brand-kit gallery (self-contained, themeable HTML)8889Build a single-file `docs/brand/brand-kit.html` that shows the kit: palette swatches with their90hex + AA/AAA badges, the type scale rendered live, and an **icon spec board** (a labeled square91per required size — the *spec*, with the real images dropped in once generated). Theme it from92`tokens.css` via `:root`. Follow **[references/html-rules.md](references/html-rules.md)**:93self-contained, no external CSS/JS, themed from `brand.json` (NEVER purple by default), and94**never** `backdrop-filter` or SVG `feTurbulence` (they hang renderers).9596### 5. Generate the images (generator-agnostic — use an adapter)9798The kit specifies *what* to render; you choose the engine. For each entry in99`icon-manifest.json`, generate at the listed pixel size onto a transparent (icons) or branded100(social/OG) canvas. Use whichever adapter is available — see101**[references/generator-adapters.md](references/generator-adapters.md)**:102103- **logo-pack output** — the cleanest path: take the winning square mark from104 `docs/brand/logos/` and resize/pad it to each icon size; compose it onto the OG/banner canvas.105- **`gemini-imagegen`** skill (Nano Banana Pro) — render icons + OG/social art directly.106- **`using-local-ideogram4`** skill — when the OG/banner needs crisp headline text.107- **Ad Lab** (optional, only if installed) — resolve its path from `$AD_LAB_DIR` / PATH / a known108 checkout (never a hardcoded drive letter), then `node "$AD_LAB_DIR/scripts/generate-image.js"109 -p "<prompt>" -o docs/brand/icons/<file>`. See Adapter D for the discovery snippet.110111Save icons to `docs/brand/icons/`, social to `docs/brand/social/`. Generate, **look at the112output**, keep the strongest. For OG/banner, the prompt names the brand colors and reserves a113clear text zone for the product name + tagline.114115### 6. Wire it up + report116117Tell the user exactly what to do with the output:118- Copy `docs/brand/icons/*` and `site.webmanifest` to the app's web root (e.g. `public/`).119- Paste `head-snippet.html` into the app's `<head>` (Next.js: `app/layout.tsx` metadata or120 `app/head`). Adjust paths if assets live under a sub-path.121- Stamp the kit with `docs/VERSION` so it travels with the release.122123Report: the derived palette + contrast summary, the type scale, which images were generated vs.124still pending (be honest about gaps), and the wire-up steps. Cross-link `ship-logos` if the logo125needs work and `ship-guide` so the docs pick up the new tokens.126127## Worked example (input → output)128129**Input** — `docs/brand.json`:130```json131{ "name": "MyFieldTime", "tagline": "Run your jobs. Not your inbox.",132 "colors": { "bg": "#0a0a0f", "brand": "#2f7dff", "accent": "#FFDD00", "muted": "#8b9bb4" },133 "fonts": { "display": "Bricolage Grotesque", "body": "Hanken Grotesk" } }134```135136**Run** — `node …/brand-kit.mjs --write`. **Output** (excerpt):137- `brand` ramp: `50 #ebf2ff · 200 #9ec2ff · 500 #0060ff · 700 #003fa8 · 900 #001d4d`.138- Contrast: `brand #2f7dff` → best text **black, 5.5:1, AA** (so white nav text on a brand button139 needs a *darker* stop like `brand-700` for AA body text). `accent #FFDD00` → black text,140 15.59:1, **AAA**. `muted #8b9bb4` → black, 7.45:1, AAA — safe for secondary text on light.141- Type scale (16 / 1.25): `display 39px · h1 31px · h2 25px · body 16px · caption 10.24px`.142- `tokens.css` with all `--brand-50..900`, `--font-display: "Bricolage Grotesque"`, `--text-*`.143- `icon-manifest.json` listing 8 icons + 4 social/OG sizes, each with a `purpose`.144- `site.webmanifest` themed `#2f7dff` on `#0a0a0f`; `head-snippet.html` with the `<link>`/OG tags.145146Then the kit gallery renders those swatches (each with its AA/AAA badge) and an icon spec board,147and the images get generated from the logo-pack mark — a complete kit from one JSON file.148149## Quality bar150151- **Derived, not eyeballed.** Palette ramps and contrast come from the script's math. Every152 text-on-color choice cites a real WCAG ratio + AA/AAA verdict — no "looks fine."153- **Honest about inputs.** Missing `brand.json` → scaffold and stop, never invent a brand.154 Unparseable color values are *reported and skipped*; alpha overlays keep a raw var but no155 ramp — both are surfaced in the report, not silently guessed or padded with noise. An156 unparseable color that still lands in `tokens.css` is annotated there too157 (`/* unparsed — no ramp/contrast, not validated */`), so the token file is as honest as the158 report and no reader mistakes it for an endorsed token.159- **Discovery tells the truth.** It distinguishes a *shipped* web manifest from the kit's own160 files: anything under the output dir `docs/brand/` (the `site.webmanifest` and `icons/*` that161 `--write` itself produces) is excluded from the shipped-asset scan, so a second run never162 re-reports its own output as a pre-existing app asset. It also actually detects a reusable logo163 (`docs/brand/logos/…`) so the recommended Adapter A path is found, not merely described.164- **Reuse over churn.** Discovery runs first; existing icons are extended, not duplicated. A165 re-run refreshes tokens and regenerates only what changed.166- **Real sizes, real purposes.** The icon manifest is the actual production set (favicon → `.ico`167 → apple-touch → PWA/maskable → OG/social), each labeled with where it's used — not a vague list.168- **On-brand, never generic.** Tokens, manifest theme color, and HTML all source `brand.json`.169 Never purple unless the brand says so. OG/social art is specific to *this* product.170- **HTML that renders anywhere.** Self-contained, themeable via `:root`, no `backdrop-filter`,171 no `feTurbulence`.172- **Portable.** Nothing hardcoded to one app; image engines are adapters, never assumed installed.