Animated Website Pipeline
End-to-end - URL in, deployed Netlify URL out. Four steps, no prompts in between. Outputs are namespaced under $PWD/sites/<slug>/ so multiple sites coexist in one parent project.
When triggered
- Extract the URL from the user's message or
$1argument. If somehow no URL is present, ask once and stop. Otherwise proceed silently. - Derive the slug and site name from the URL host:
- host
acmegrowthpartners.com→ slugacme-growth, site nameAcme Growth Partners(title case) - Strip
www., drop TLD, replace dots with hyphens, lowercase. For human-friendly site names use a sensible title case based on brand recognition (e.g.acmegrowthpartners.com→Acme Growth Partners, notAcmegrowthpartners). Use the slug for filenames and URLs only.
- host
- Confirm prerequisites in parallel:
npx playwright --version(brand-book extract dep)grep -l KIE_AI_API_KEY "$PWD/.env" || grep -l KIE_AI_API_KEY "$HOME/<your-project-dir>/.env"(KIE.ai API key)command -v netlify(Netlify CLI for deploy step)mkdir -p "$PWD/sites/<slug>"
If any prereq fails, surface the exact problem and stop. Don't try to install Playwright, Netlify, or guess at API keys.
Step 1 — Brand Book (skippable)
Skip condition: all of $PWD/sites/<slug>/brand/typography.md, colors.md, tailwind.snippet.js, reference.png exist AND reference.png was modified within the last 24h. If skipped, log "→ brand book reused from cache" and move on.
- Run the brand-book extractor:
node ~/.claude/skills/brand-book/extract.js "<URL>" "$PWD/sites/<slug>/brand" - Read
sites/<slug>/brand/type-styles.json,sites/<slug>/brand/css-variables.json, and viewsites/<slug>/brand/reference.png. - Write
sites/<slug>/brand/typography.md,colors.md,tailwind.snippet.js,README.mdfollowing the brand-book skill's format - inconsistencies-first, type-scale table, palette groups, semantic-named tailwind tokens. Apply the rationalize-don't-codify rule. - Auto-publish to GitHub without asking (this creates a public repo under your GitHub account — set
<your-github-username>below, or remove this step if you don't want brand books published):- Repo name:
Brand-Book-<TitleCaseSlug>(e.g.Brand-Book-Acme-Growth-Partners). Hyphens between words. - Use
mcp__github__create_repository(owner:<your-github-username>, public, autoInit: true), thenmcp__github__push_fileswith all 6 files (skipreference.png- too large for the tool arg limit). - If the repo already exists (HTTP 422 from create), continue - call
mcp__github__push_filesdirectly to update.
- Repo name:
Capture: BRAND_REPO_URL, plus the rationalized primary, accent, neutral, surface-dark hex values from colors.md (you'll re-read these in step 3).
Step 2 — Hero Loop Video (skippable)
Skip condition: $PWD/sites/<slug>/hero-loop-7s.mp4 exists. Log "→ hero video reused from cache" and move on.
- Derive the visual concept from the brand book artifacts you just wrote:
- Inspect the rationalized palette in
colors.md→ pick the dominant brand emotion (e.g., trajectory + unlock for growth-oriented brands; warmth + craftsmanship for warm-leaning palettes; restraint + precision for minimal monochrome). - Eyeball
reference.pngfor visual motifs (gradient direction, line work, particle/grain treatment, geometric shapes). - Synthesize a one-sentence video metaphor that maps the brand's verbal positioning to motion.
- This is creative work - take it seriously, don't fall back on a generic template. Look at the screenshot.
- Inspect the rationalized palette in
- Build the Nano Banana 2 image prompt as a 1:1 still that captures the "before" beat of the loop. Reference exact brand hex codes. End with "no text, no logos, cinematic, premium B2B aesthetic."
- Submit to Nano Banana 2:
Capturesource "$PWD/.env" 2>/dev/null || source "$HOME/<your-project-dir>/.env" curl -s -X POST "https://api.kie.ai/api/v1/playground/createTask" \ -H "Authorization: Bearer $KIE_AI_API_KEY" \ -H "Content-Type: application/json" \ --data-binary @- <<EOF { "model": "nano-banana-2", "input": { "prompt": "<IMAGE_PROMPT>", "output_format": "png", "image_size": "1:1" } } EOFtaskId. Pollhttps://api.kie.ai/api/v1/playground/recordInfo?taskId=<ID>untilstate == "success". Parsedata.resultJson(it's a JSON string) for the URL. Do not download - keep the cloud URL. - Build the Seedance prompt as time-coded beats over 7 seconds. Always include:
- 0-2s: establish (camera drift, atmosphere)
- 2-4s: build (motion accelerates / converges)
- 4-5s: release (the brand "unlock" moment - usually the accent color blooming)
- 5-7s: resolve (settle back to a loopable state)
- Tail: "Slow controlled camera, no jitter. Realistic light bloom physics. No text, no logos."
- Submit to Seedance 2 with the cloud image URL as
image_url:Capture taskId. Run a polling loop in the background (cat > /tmp/seedance-payload.json <<'EOF' { "model": "bytedance/seedance-2", "input": { "prompt": "<VIDEO_PROMPT>", "image_url": "<CLOUD_IMAGE_URL>", "duration": 7, "resolution": "480p", "aspect_ratio": "16:9" } } EOF curl -s -X POST "https://api.kie.ai/api/v1/playground/createTask" \ -H "Authorization: Bearer $KIE_AI_API_KEY" \ -H "Content-Type: application/json" \ --data-binary @/tmp/seedance-payload.jsonrun_in_background: true) - typical generation 3-5 min. When notified done, hit therecordInfoendpoint directly to readstate(the python-in-bash polling parser is brittle - prefer one direct curl after the background notification). - Download the final video to
$PWD/sites/<slug>/hero-loop-7s.mp4.
Capture: VIDEO_PATH.
Step 3 — Frontend Design Build (skippable)
Skip condition: $PWD/sites/<slug>/index.html exists. Log "→ site reused from cache" and move on. (If the user wants a rebuild, they'll delete the file or pass --force in their request.)
This is the work product the user is comparing against claude.ai's Artifacts. Quality matters.
- Load context for the frontend-design subskill:
- Read
sites/<slug>/brand/tailwind.snippet.js(full content) - Read
sites/<slug>/brand/typography.mdandcolors.md(just the rationalization summaries) - Note the local video path:
./hero-loop-7s.mp4(sibling of index.html — IMPORTANT: don't use../references/...like the old structure) - Note the source URL (for content/copy reference - the page should be a revision, not a clone)
- Read
- Invoke the
frontend-design:frontend-designskill via the Skill tool. Pass a single self-contained prompt with all of:- Output path -
$PWD/sites/<slug>/index.html(single file, self-contained). The video is at./hero-loop-7s.mp4next to it. - Brand tokens - paste the full Tailwind theme.extend block from
sites/<slug>/brand/tailwind.snippet.js - Hero video - reference with src
./hero-loop-7s.mp4as<video autoplay loop muted playsinline>background in the hero, with a dark gradient scrim for text legibility. - Source URL for content - tell the design subskill to draft a revision of
<URL>'s landing page: keep the value prop and key sections (hero, social proof, services/solutions, CTA, footer) but elevate the design quality, typography hierarchy, and motion polish. - Quality bar - "production-grade, distinctive, avoid generic AI aesthetic. The user is comparing this against claude.ai Artifacts output - aim higher."
- Tech stack - vanilla HTML + Tailwind via CDN (
https://cdn.tailwindcss.com) + lucide icons + Google Fonts. Inline the Tailwind config to consume the brand tokens. - Content authenticity - if the live site has named products/modules/services, use them verbatim. If unsure, hit WebFetch on the URL to enumerate real modules before writing copy. Do not invent products that don't exist.
- Output path -
- After the subskill finishes, verify
$PWD/sites/<slug>/index.htmlexists and references./hero-loop-7s.mp4.
Capture: SITE_PATH.
Step 4 — Netlify Deploy
- Create or reuse a Netlify site for this slug. Use a site name pattern that's clear and stable:
animated-<slug>(e.g.animated-acme-growth). - From
$PWD/sites/<slug>/, deploy to production:cd "$PWD/sites/<slug>" && netlify deploy --prod --dir=. --site=animated-<slug> 2>&1- On first run for a slug, Netlify will say the site doesn't exist. In that case, create it explicitly:
netlify sites:create --name=animated-<slug>then re-run the deploy. - Capture the
Website URL:from the deploy output (the live*.netlify.appURL).
- On first run for a slug, Netlify will say the site doesn't exist. In that case, create it explicitly:
- Quick smoke check:
curl -sI <LIVE_URL> | head -1should returnHTTP/2 200. If not, surface the failure but still report the local path.
Capture: LIVE_URL.
Final output
Once all four steps succeed, send the user one short message - four links and nothing else:
Live - <LIVE_URL>
Brand book - <BRAND_REPO_URL>
Hero video - <VIDEO_PATH>
Local site - <SITE_PATH>
Don't summarize what was built. Don't recap. Don't auto-open the local file - the live URL is the deliverable now.
Pitfalls
- Don't ask the user mid-run. The whole point is zero handoffs. If something is genuinely missing (no URL, no API key, no Netlify CLI), surface the blocker and stop - don't ask for creative input.
- Skip steps that already ran. All three artifact steps are idempotent by file existence. Re-doing them wastes credits.
- Video path must be relative to its sibling HTML. Use
./hero-loop-7s.mp4, not../references/.... Each site is fully self-contained insites/<slug>/. - Each site is its own Netlify site, not a subpath. Deploys with
--site=animated-<slug>so you get its own*.netlify.appURL. Don't try to mount five sites into one Netlify project. - The KIE-hosted image URL is temporary - submit the Seedance task in the same session.
data.resultJsonis a JSON string, not an object. Parse it.- GitHub repo already exists? Don't fail - just push the new files to update.
- Don't auto-install netlify-cli. If
command -v netlifyfails, tell the user to runnpm install -g netlify-cli && netlify loginand stop. - Brand fidelity > creative liberty for product names. Always use the actual product/module names from the live site. Run WebFetch to verify before writing the page copy.
- Brand-book screenshot quality matters for concept derivation. If the screenshot is mostly empty (cookie banner overlay, lazy-loaded sections didn't paint), still proceed - use the type-styles JSON + color palette + your knowledge of the brand's positioning to derive the concept.