Generate, preview, and apply a brand color palette (plus typography, gradients, and tone/voice) to a frontend project. Use when a user says "pick brand colors", "choose a color palette", "brand design", "generate a palette", "theme this project", "what colors should I use", "brand identity", "design my brand", "set up brand colors", "time to build the frontend", "let's start the UI", "make this look branded", or any time a project is about to start frontend work and has no brand.md yet. Presents 6 candidate palettes as a visual HTML preview opened in the user's browser, supports an infinite regenerate loop until the user is satisfied, then writes the chosen palette to shadcn CSS variables (light + dark), wires up typography via next/font, derives brand gradients, and writes brand.md for future reference.
_TEL_TIER=$(cat ~/.superstack/config.json 2>/dev/null | grep -o '"telemetryTier": *"[^"]*"' | head -1 | sed 's/.*"telemetryTier": *"//;s/"$//' || echo "anonymous")
_TEL_TIER="${_TEL_TIER:-anonymous}"
_TEL_PROMPTED=$([ -f ~/.superstack/.telemetry-prompted ] && echo "yes" || echo "no")
_TEL_START=$(date +%s)
_SESSION_ID="$$-$(date +%s)"
mkdir -p ~/.superstack
echo "TELEMETRY: $_TEL_TIER"
echo "TEL_PROMPTED: $_TEL_PROMPTED"
if [ "$_TEL_TIER" != "off" ]; then
_TEL_EVENT='{"skill":"brand-design","phase":"build","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}'
echo "$_TEL_EVENT" >> ~/.superstack/telemetry.jsonl 2>/dev/null || true
_CONVEX_URL=$(cat ~/.superstack/config.json 2>/dev/null | grep -o '"convexUrl":"[^"]*"' | head -1 | cut -d'"' -f4 || echo "")
[ -n "$_CONVEX_URL" ] && curl -s -X POST "$_CONVEX_URL/api/mutation" -H "Content-Type: application/json" -d '{"path":"telemetry:track","args":{"skill":"brand-design","phase":"build","status":"success","version":"0.2.0","platform":"'$(uname -s)-$(uname -m)'","timestamp":'$(date +%s)000'}}' >/dev/null 2>&1 &
true
fi
If TEL_PROMPTED is no: Before starting the skill workflow, ask the user about telemetry.
Use AskUserQuestion:
Help superstack get better! We track which skills get used and how long they take — no code, no file paths, no PII. Change anytime in
~/.superstack/config.json.
Options:
If A: run this bash:
echo '{"telemetryTier":"anonymous"}' > ~/.superstack/config.json
_TEL_TIER="anonymous"
touch ~/.superstack/.telemetry-prompted
If B: run this bash:
echo '{"telemetryTier":"off"}' > ~/.superstack/config.json
_TEL_TIER="off"
touch ~/.superstack/.telemetry-prompted
This only happens once. If TEL_PROMPTED is yes, skip this entirely and proceed to the skill workflow.
Turn a project from "unthemed shadcn default" into something with real identity. This skill runs the full brand pass: palette, typography, gradients, tone/voice. Pairs with scaffold-project (runs after it) and frontend-design-guidelines (which reads the brand.md this skill writes).
Apply this skill any time the user is:
brand.mdIf the user is already deep in component work, do not interrupt to run this skill unless they ask. Offer it once at the start of the frontend phase, then drop it.
Before starting, quickly read the project to ground the interview:
.superstack/idea-context.md if present — gives you the product name, category, and audience
.superstack/build-context.md if present — tells you the stack
package.json — confirms Next.js + Tailwind + shadcn
app/globals.css — current theme (usually shadcn default)
brand.md in project root — three cases:
Case A — brand.md does not exist: first-time setup. Proceed normally.
Case B — brand.md exists with Status: deferred: the user previously deferred brand setup via frontend-design-guidelines's Step 0. Treat this as first-time setup — do NOT ask "keep or replace?" The deferred file is a placeholder, not a real palette. Tell the user:
Picking up where we left off. You deferred brand setup earlier — I'll run the full interview now and replace the deferred placeholder with your choices.
Then proceed to Step 1 normally. When Step 8 writes the real brand.md, it overwrites the deferred file — no backup needed because the deferred file has no real content to preserve.
Case C — brand.md exists with a real palette (any status other than deferred): show the user the current palette name and ask before overwriting:
You already have a brand palette set up: {{CURRENT_NAME}}. Want to:
- Keep it and exit — I won't touch anything
- Replace it — I'll back up the current
brand.mdtobrand.md.bakand run a fresh setup- Tweak it — tell me what you want to change and I'll regenerate variations around the current palette
Wait for the user's response. Never overwrite a real palette without explicit confirmation.
If the project is not a Next.js + Tailwind project, the skill still works but the "apply" step adapts — it writes the palette to brand.md only and explains how the user can map it into their stack manually.
Ask four short questions. Do not skip. Do not combine. Wait for answers between questions.
Product name + one line of what it does (e.g., "SolFeed — a realtime Solana activity feed"). If you already have this from idea-context.md, confirm: "I see this is {name} — {description}. Correct?"
Category — pick one, or the closest match:
defi · infra/data · consumer/social · memecoin/playful · ai/tech · nft/creative · tooling/dev
Mood — pick 1 or 2 from this list. If user picks more than 2, push back: "Two is the max — which two matter most?"
minimal · bold · warm · calm · playful · serious · technical · premium
Reference brands — "Any existing apps or sites you like the feel of? (Optional — 1 to 3 is ideal.)" Accept "no" as a valid answer.
Using the answers from step 1:
references/palette-recipes.md and select 3 curated palettes that match the category × mood intersection. If fewer than 3 exact matches exist, fall back to adjacent moods (e.g., "calm" is adjacent to "serious" and "minimal").references/palette-generation.md and generate 3 algorithmic variants — one that tightly matches the mood, one that pushes slightly bolder, one that pushes slightly softer. Vary hue/saturation/lightness across the three so the user sees range.references/shadcn-integration.md.references/contrast-rules.md. If any token pair fails WCAG AA, auto-adjust lightness until it passes. If it can't be made to pass, drop the palette and generate a replacement.Each candidate palette has:
bg-base, bg-elevated, primary, primary-soft, fg-baseRead references/html-preview.md for the exact HTML template and the contextual mini-UI templates. The palette preview shows the SAME mini-UI rendered 6 times with 6 different palette tokens — only the colors change across the 6 sections. This is the whole point: the user sees how their specific product looks across 6 brand options.
Pick the contextual mini-UI template based on the interview's category answer and product description. The options are in html-preview.md's "Contextual mini-UI templates" section:
wallet — for wallet apps, portfolio trackers, custody (balance + send/receive/swap)swap — for DEXes, AMMs, swap UIs, bridges (token in → token out)staking — for staking dashboards, lending vaults, yield (supplied + APR)nft — for NFT marketplaces, collections, drops (floor + buy)dashboard — for analytics, data tools, observability (KPI card)social — for feeds, chat, community (post card)generic — fallback for anything unclear (welcome + search)Pick by category first; fall back to keyword matches in the product description and reference brands. Never use generic when a specific template clearly fits — a wallet app should see wallet UI, not "Welcome back."
Write the filled template to:
<project-root>/.brand-preview/index.html
Each of the 6 palettes is rendered as a stacked section with the chosen contextual mini-UI. All six sections share a single light/dark toggle at the top.
After writing the file, open it automatically:
# macOS
open .brand-preview/index.html
# linux
xdg-open .brand-preview/index.html 2>/dev/null
# windows
start .brand-preview/index.html
Detect the OS (uname -s) and run the right command. If open/xdg-open/start is unavailable, print the absolute file:// path and tell the user to open it manually.
Tell the user in the conversation:
I've opened a preview in your browser with 6 candidates. Take a look and tell me:
- Pick one: "I'll take 3"
- Ask for variations: "more like 2 but cooler" or "more minimal"
- Reject all: "none" or "nothing fits"
Parse the user's next message. Match against these intents, in order:
| Pattern | Intent | Action |
|---|---|---|
<number> (1-6), or "take N", or "I like N", or "the Nth one" | Pick | Go to step 5 with the chosen palette |
| "more like N", "variations of N", "N but {adjective}", "tweak N" | Vary around N | Regenerate 6 new palettes all derived from palette N per palette-generation.md variation rules (±15% on saturation, ±10 hue, re-roll secondary). Back to step 3. |
| "more {adjective}", "cooler", "warmer", "more minimal", "brighter", "darker", "softer" | Adjust mood | Keep the original category but add or replace the adjective in the mood. Back to step 2. |
| "none", "nothing fits", "all no", "start over", "different direction" | Reset | Go back to step 1 question 3 (mood) and ask "what didn't work about these?" Then regenerate from scratch. |
| "more" / "show me more" | More of same | Regenerate 6 fresh candidates with the same mood/category. Back to step 2. |
| Ambiguous | Clarify | Ask: "Do you want to pick one of these, see variations of a specific one, or try a different direction?" |
There is no iteration limit. The user stays in the loop until they explicitly pick or quit. If they quit ("nevermind", "let's skip this"), stop the skill cleanly — do not force a palette on them.
Only after the user has picked:
app/globals.css to app/globals.css.bak. Never skip this.app/globals.css under :root (light) and .dark (dark). Use the exact CSS block from references/shadcn-integration.md. Preserve everything else in the file (Tailwind imports, custom layers, etc.) — only replace the token block.--background, --foreground, --primary, --primary-foreground.Typography gets the same HTML preview treatment as the palette. Never present font options as plain text in conversation — always render them visually in the browser.
Read references/typography-pairings.md and look up the 6 candidate font pairs for the chosen palette's mood (the "Mood → 6 candidates mapping" table). If the user picked two moods, blend the two mood rows per the instructions in that file.
Read references/typography-preview.md for the exact HTML template. Fill it with:
--pair-sans, --pair-mono, and --pair-serif valuestypography-preview.md)<link> URL covering all 6 pairs — every font in the file is on Google Fonts, served freeWrite to <project-root>/.brand-preview/typography.html (same folder as the palette preview).
Open it with the same OS-detection command used in step 3 (open / xdg-open / start).
Tell the user in conversation:
I've opened a typography preview with 6 font pairs, each rendered on your chosen palette so you see exactly how they look on your brand. Pick 1-6, or say "skip" to default to Inter + JetBrains Mono.
Wait for the user's response and parse the intent per the "User response parsing" table in typography-preview.md:
typography-pairings.mdWire the chosen pair (or the default):
app/layout.tsx (or wherever the root layout lives) with next/font/google imports for the chosen pairtailwind.config.* fontFamily section if the project uses font-sans / font-mono utilities--font-sans, --font-mono, and --font-serif if the pair includes a serif) to globals.css so they work with shadcn's token system@theme block; for v3, extend theme.fontFamily in the configTell the user what was applied, including whether it was their explicit pick or the default fallback:
Applied Inter + JetBrains Mono (your pick). Wired via
next/font/googleinapp/layout.tsx. CSS variables--font-sansand--font-monoare now available on every shadcn token.
Default handling — always safe: if the user picks "neither", "skip", "no", or gives an unclear/missing answer, default to Inter + JetBrains Mono (Pair A). Never end this step with zero typography configured. See typography-pairings.md's "The default — Inter + JetBrains Mono" section.
Ask: "Want brand gradients too? I'll derive 2 — a subtle background gradient and a brand accent gradient. (y/n)"
If yes, read references/gradient-recipes.md and derive both. Write them as CSS variables (--gradient-bg, --gradient-accent) in globals.css and export them as Tailwind utilities via the config.
Read references/brand-md-template.md and write the filled template to the project root as brand.md. This file contains:
This is the artifact future sessions (and other skills) can read to understand the brand.
Tell the user in conversation:
app/globals.css.bak)pnpm dev and view the result.brand-preview/ once they're happy: rm -rf .brand-preview/brand.md has been written and will be read by frontend-design-guidelines on future component workreferences/contrast-rules.md.app/globals.css → app/globals.css.bak before writing the new tokens.brand.md. Show the current values first.brand.md only and tell the user how to map it.Before marking the skill complete, confirm:
app/globals.css.bak exists (backup was made)app/globals.css updated with new :root and .dark token blocksnext/font (if user picked a pair)brand.md written to project root.brand-preview/ still exists (user may want to re-check before deleting)pnpm dev) and how to clean up (rm -rf .brand-preview/)frontend-design-guidelines sessions will read brand.mdAfter the skill workflow completes (success, error, or abort), log the telemetry event.
Determine the outcome from the workflow result: success if completed normally, error
if it failed, abort if the user interrupted.
Run this bash:
_TEL_END=$(date +%s)
_TEL_DUR=$(( _TEL_END - ${_TEL_START:-$_TEL_END} ))
_TEL_TIER=$(cat ~/.superstack/config.json 2>/dev/null | grep -o '"telemetryTier": *"[^"]*"' | head -1 | sed 's/.*"telemetryTier": *"//;s/"$//' || echo "anonymous")
if [ "$_TEL_TIER" != "off" ]; then
echo '{"skill":"brand-design","phase":"build","event":"completed","outcome":"OUTCOME","duration_s":"'"$_TEL_DUR"'","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","platform":"'$(uname -s)-$(uname -m)'"}' >> ~/.superstack/telemetry.jsonl 2>/dev/null || true
true
fi
Replace OUTCOME with success/error/abort based on the workflow result.