From platform-product-video-harness
Canonical Q&A protocol for collecting brand kit inputs (name, colors, logo, tagline, fonts) and writing brand/brand.json + brand/logo.svg. Used by /init-video-workspace (Step 4.5, first-time setup) AND by /update-brand-kit (any-time update). Single source of truth so both paths use the same prompts, the same defaults, and the same random palette algorithm.
How this skill is triggered — by the user, by Claude, or both
Slash command
/platform-product-video-harness:brand-collectionThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Single source of truth for gathering brand-kit inputs from the user and writing them to `<ProductVideos>/brand/brand.json` + `<ProductVideos>/brand/logo.svg`.
Single source of truth for gathering brand-kit inputs from the user and writing them to <ProductVideos>/brand/brand.json + <ProductVideos>/brand/logo.svg.
This protocol is invoked by:
/init-video-workspace Step 4.5 — first-time setup. No existing brand.json, so "current value" is "(none)"./update-brand-kit — any time after that. Reads existing brand.json, shows current values as defaults, only overwrites what the user changes.<ProductVideos> — absolute path to the ProductVideos repo root<mode> — init (called from init-video-workspace) or update (called from update-brand-kit)<ProductVideos>/brand/brand.json — written from scratch (init) or updated in place (update)<ProductVideos>/brand/logo.svg — replaced if the user provides a new one; left alone otherwise<ProductVideos>/brand/brand.json exists, read it and remember every field as the "current value" (used as defaults in update mode).name: derived from the workspace / repo folder name — split on -/_/space, Title-Case each token (e.g. kawee-kids → "Kawee Kids", nafuu_app → "Nafuu App"). Fall back to "Your Brand" only if the folder name is unusable.tagline: "Your tagline here."Ask via AskUserQuestion (single, 2 options):
What's the brand name?
- Keep current: <current name> ← skip; reuse what's there
- Other ← user types a new name
(In init mode with no current name, replace the first option with Use "<derived-from-folder-name>".)
Same shape:
What's the tagline? (One short sentence — appears on EndCard scenes.)
- Keep current: "<current tagline>"
- Other ← user types
Ask via AskUserQuestion (single, 4 options):
How do you want to set brand colors?
- Keep current palette (primary / accent / ink / paper) ← only shown when current exists
- Use neutral starter defaults (blue / amber / ink) ← always shown in init mode
- Generate a random pleasing palette
- I'll provide my own hex codes
Ask 4 separate AskUserQuestion calls (single, 2 options each: "Keep current: " + "Other"):
colors.primary — main brand accent (default suggestion: #2563EB)colors.accent — call-to-action / highlight (default: #F59E0B)colors.ink — dark background / body text on light (default: #0B1020)colors.paper — light background / body text on dark (default: #F8FAFC)Validate each: must match ^#[0-9A-Fa-f]{6}$ or ^#[0-9A-Fa-f]{3}$. If invalid, re-prompt with the same question and a one-line "Hex format like #2563EB — try again." note.
Derive these from the four answers (don't ask separately):
colors.primaryAlt — primary shifted ~40% lighter (algorithm: convert primary to HSL, set L to min(95, current_L + 30), back to hex).colors.muted — mid gray with ink's hue tint (algorithm: ink hue, saturation 10%, lightness 50%).Algorithm (deterministic so re-runs without input change are stable — seed from brand name hash):
colors.primary = HSL(H, 70%, 45%) → hexcolors.primaryAlt = HSL(H, 80%, 70%) → hexcolors.accent = HSL((H + 120) mod 360, 70%, 55%) → hex (analogous-triadic)colors.ink = HSL(H, 30%, 12%) → hex (near-black with hue tint)colors.paper = HSL(H, 30%, 96%) → hex (near-white with hue tint)colors.muted = HSL(H, 15%, 50%) → hex (mid-gray with hue tint)Tell the user the palette before writing it: "Generated palette: primary #..., accent #..., ink #... — want to regenerate (different hue) or accept?" via AskUserQuestion: Accept / Regenerate (loops back to step 1 with a new hue offset) / Switch to provide my own / Use neutral starter defaults instead.
Ask via AskUserQuestion (single, 4 options):
Where's your logo SVG?
- Keep current: brand/logo.svg ← only in update mode when file exists
- I have a local file path ← user types absolute or repo-relative path
- I have a URL — download it ← user types https:// URL
- Generate a text placeholder using the brand name ← inline SVG with brand name on ink bg
For "local path":
.svg (warn if .png/.jpg/.webp — those work but SVG is preferred for sharpness at any size).Bash cp to <ProductVideos>/brand/logo.svg (preserving extension if non-svg).For "URL":
curl -sfLo <ProductVideos>/brand/logo.svg "<url>" — verify exit 0 and file is non-empty.Content-Type headers indicated non-svg, rename appropriately.For "Generate text placeholder":
<ProductVideos>/brand/logo.svg using this template (substitute {{NAME}} and {{INK}} / {{PRIMARY}} from the collected brand):<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 60" width="240" height="60" role="img" aria-label="{{NAME}}">
<rect width="240" height="60" rx="8" fill="{{INK}}"/>
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle"
font-family="Inter, sans-serif" font-weight="800" font-size="28" fill="{{PRIMARY}}">
{{NAME}}
</text>
</svg>
Ask via AskUserQuestion (single, 2 options):
Use the default font stack (Inter for heading + body, JetBrainsMono for code)?
- Yes (Recommended)
- No — I'll customise ← user types heading/body/mono names
If "No":
fonts.heading (single Q, "Keep Inter" + "Other")fonts.body (single Q, "Keep Inter" + "Other")fonts.mono (single Q, "Keep JetBrainsMono" + "Other")Tell the user: "Note — fonts must be available system-wide OR self-hosted via brand/fonts/. Inter and JetBrainsMono are the safest defaults."
Offer in both modes (the prompt copy differs slightly):
Update personas and tone-of-voice rules?
- Keep current / use neutral starter personas ← skip
- Edit personas (add / remove / change labels) ← prose flow
- Edit tone (banned words, banned phrases, preferred verbs) ← prose flow
- Both
If "Edit personas" or "Both": show the current (or neutral starter) personas, ask which to change, accept new key/label/voice via prose. Write back to brand.json.
If "Edit tone" or "Both": show current banned_words / banned_phrases / preferred_verbs, ask which lists to modify, accept comma-separated additions/removals via prose.
In init mode, the "Keep current" option reads as "Use neutral starter personas" — the generic end_user / prospect / partner set from the Defaults below. This keeps first-init fast while still surfacing the choice (no brand-specific personas are ever applied silently). The user can refine them anytime via /update-brand-kit.
Render the fully-resolved brand.json in the conversation. Show before/after when in update mode (use the old values from Step 0 vs the new values from Steps 1-6).
Ask via AskUserQuestion:
Write this brand kit?
- Yes, write brand/brand.json (and logo if changed)
- Let me edit one more field ← loops back to the relevant step
- Cancel — discard all changes
On "Yes":
<ProductVideos>/brand/brand.json (UTF-8, no BOM, 2-space indent, sorted keys).compositions/<slug>.tsx files exist that reference brand tokens, no changes needed — they import from brand.json so the next render picks up the new values.On "Let me edit one more field": ask which step to re-run, jump back, continue.
On "Cancel": abort. brand.json + logo.svg unchanged.
Print:
✓ Brand kit updated.
Name: <name>
Tagline: "<tagline>"
Primary: #... (was: #... [or "(new)"])
Accent: #... (was: ...)
Ink: #... (was: ...)
Paper: #... (was: ...)
Logo: brand/logo.svg (source: <path|URL|generated|kept>)
Next render will use the new tokens. Re-render any in-flight previews to see them applied.
Used when init mode picks "Use neutral starter defaults" or when fields are missing from the existing brand.json in update mode. name is derived from the workspace folder name (see Step 0) — the value below is only the fallback.
{
"name": "Your Brand",
"tagline": "Your tagline here.",
"colors": {
"primary": "#2563EB",
"primaryAlt": "#60A5FA",
"accent": "#F59E0B",
"ink": "#0B1020",
"paper": "#F8FAFC",
"muted": "#64748B"
},
"fonts": { "heading": "Inter", "body": "Inter", "mono": "JetBrainsMono" },
"logo": { "path": "brand/logo.svg", "minSizePx": 56, "safeAreaPx": 32 },
"lowerThird": { "barHeightPx": 96, "barOpacity": 0.85, "barColor": "ink", "textColor": "paper" },
"captionStrip": {
"fontSizePx": 48, "lineHeightPx": 60, "bgColor": "ink", "bgOpacity": 0.8,
"textColor": "paper", "paddingPx": 16, "maxWordsPerLine": 7, "maxLines": 2
},
"personas": [
{ "key": "end_user", "label": "primary end user of the product", "voice": "warm, plainspoken" },
{ "key": "prospect", "label": "prospective customer evaluating the product", "voice": "curious, value-driven" },
{ "key": "partner", "label": "partner / stakeholder integrated with the product", "voice": "professional, trust-building" }
],
"tone": {
"default": "warm and credible — confident without hype",
"banned_words": ["revolutionary", "game-changing", "disrupt", "synergy", "leverage", "10x"],
"banned_phrases": ["AI-powered", "next-generation", "world-class"],
"preferred_verbs": ["track", "see", "manage", "grow", "save", "send", "get"]
}
}
^#[0-9A-Fa-f]{3,8}$. Loop, don't fail.<BrandIntro> and <EndCard> reference brand.fonts.heading / brand.fonts.body. If the user picks an exotic font that isn't system-installed or bundled in brand/fonts/, the render falls back to sans-serif silently — warn them explicitly.npx claudepluginhub ameenaliu/harness-platform --plugin platform-product-video-harnessProvides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.