Skip to content

Instantly share code, notes, and snippets.

@jeystaats
Created May 26, 2026 11:09
Show Gist options
  • Select an option

  • Save jeystaats/f47a0f7fd3eba6c62d27a9259e4d3626 to your computer and use it in GitHub Desktop.

Select an option

Save jeystaats/f47a0f7fd3eba6c62d27a9259e4d3626 to your computer and use it in GitHub Desktop.
Brand Shoot skill for Claude Code

Brand Shoot

Scrape a brand, save product references, generate AI photography with those references, build a landing page.

Three steps. No phases. No ceremony.

1. SCRAPE  →  product shots + logo + colors + fonts from website/IG
2. SHOOT   →  4 stills + 2 videos, every image uses saved product ref
3. BUILD   →  /frontend-design with brand tokens + generated assets

Trigger

/brand-shoot <url> — URL can be a website, Instagram profile, or product page.

Prerequisites

  • KREA_API_TOKEN in environment
  • BRANDFETCH_API_KEY in environment
  • Krea scripts at ~/.agents/skills/krea-ai/scripts/
  • uv installed

Output location

~/Projects/coding/krea-ai-experiments/{brand-slug}/


Step 1: SCRAPE

Goal: save everything needed to generate on-brand images. The product reference is the most important asset — without it, every generation is a hallucination.

1a. Brandfetch (name + logo)

curl -s "https://api.brandfetch.io/v2/search/<brand>" \
  -H "Authorization: Bearer $BRANDFETCH_API_KEY"

Pick top result. Download icon to images/logo.webp.

Logo cleanup — MANDATORY

Logos are used as overlays on hero images/videos and in dark footers. The landing page applies filter: brightness(0) invert(1) to make logos white on dark backgrounds. If the SVG contains a background shape (circle, rectangle, or path), that shape also turns white and creates an ugly opaque blob. Every SVG logo must be stripped to wordmark-only before use.

After downloading any logo, run this cleanup:

SVG logos — strip background shapes

Most brand logos from Wikimedia/Brandfetch/logo sites come as "icon + wordmark" SVGs where the first <path>, <circle>, <ellipse>, or <rect> is a large colored background shape (e.g., Coolblue's orange circle, Goossens' dark rectangle). This shape is NOT part of the wordmark — it's the container.

Step 1: Read the SVG and identify the structure.

Read images/logo.svg

Look for this pattern (extremely common):

  • First element: a large shape with a brand color fill (NOT white/black) — this is the background
  • Second element: a <g> group or individual paths with fill="#fff" or fill="#000" — this is the wordmark

Step 2: Remove the background shape.

Open the SVG in an editor and delete the first <path> / <circle> / <rect> / <ellipse> that acts as the background. Keep only the wordmark paths.

Step 3: Re-color the wordmark paths.

After removing the background, the wordmark paths are likely fill="#fff" (white on colored background) or a specific brand color. Change all wordmark path fills to a neutral color that works with the CSS filter:

  • If the hero uses filter: brightness(0) invert(1) (white logo on dark bg): set fills to #000 — the filter inverts black to white
  • Or better: set fills to currentColor so the CSS color property controls the logo color

Step 4: Update the viewBox if needed.

If the background shape defined the visible area and the wordmark is smaller, the viewBox may have excess padding. Tighten it to fit the wordmark.

Step 5: Visually verify.

Read images/logo.svg

Confirm: wordmark visible, no background shape, transparent background.

Example — Coolblue logo fix:

Before: <svg viewBox="0 0 400 400">
          <path d="..." fill="#f60"/>          <- orange circle (REMOVE)
          <g fill="#fff">                       <- white wordmark
            <path d="..."/>                     <- letter paths (KEEP)
          </g>
        </svg>

After:  <svg viewBox="0 0 400 400">
          <g fill="#000">                       <- wordmark, now black (inverts to white via CSS)
            <path d="..."/>
          </g>
        </svg>

PNG/WebP logos — check alpha channel

sips -g hasAlpha images/logo.webp   # macOS
identify -verbose images/logo.webp 2>/dev/null | grep -i "alpha\|channel"

If hasAlpha: no or the logo has a solid colored background:

  • Re-download from Brandfetch with ?format=png (Brandfetch PNGs usually have transparency)
  • Or use ImageMagick: convert images/logo.png -fuzz 5% -transparent white images/logo.png
  • Or fall back to the site's own SVG logo (often cleaner) and run the SVG cleanup above

Final visual check — REQUIRED before proceeding

Read images/logo.svg

You MUST visually confirm:

  1. No colored background shape visible
  2. Wordmark is visible (not invisible white-on-white)
  3. Logo will work with filter: brightness(0) invert(1) on dark backgrounds

If you skip this check, the logo WILL break in the hero section. This has happened with Coolblue (orange circle blob) and Goossens (dark rectangle blob).

Preferred format priority: SVG (scalable, fixable) > PNG with alpha > WebP with alpha. Never use JPEG for logos.

1b. Product reference images

This is the critical step the old skill skipped.

Find 1-3 clean product shots (front, three-quarter, detail). Save them as reference images:

curl -sL "<product-image-url>" -o reference/product-front.png
curl -sL "<product-image-url>" -o reference/product-angle.png

Source priority (with fallback chain)

  1. WebsiteWebFetch or curl for <img>, <source>, og:image
  2. Instagram — if website is 403/blocked/thin (see below)
  3. WebSearch image results — last resort for isolated product packshots

Website blocked (403 / Cloudflare challenge)?

Many brand sites (especially e-commerce) use Cloudflare bot protection that blocks curl and WebFetch with a 403 or JS challenge page. Do not retry with different headers or user-agents — it won't work.

First: open it in the user's browser so they can see the site themselves:

open "https://the-brand-url.com"   # macOS
# xdg-open on Linux, start on Windows

Tell them: "I can't reach this site automatically — I've opened it in your browser. Can you paste a screenshot or describe the fonts/colors/layout you see?"

If the user provides a screenshot or description, use that to extract colors, fonts, and layout — then continue to 1c. If they can't help or you still need product images, pivot to Instagram:

1. WebSearch: "<brand> instagram"
2. WebFetch the IG profile or recent post pages
3. Look for product shots — IG CDN URLs are usually not bot-protected
4. Download 1-3 clean product images from IG posts
5. Also grab lifestyle shots for mood/color reference

Instagram is often a better source than the website — brands post clean product shots, lifestyle images, and color-rich content that reveals brand personality more than a cookie-walled homepage.

For colors and fonts when the website is blocked:

  • Colors: extract from IG imagery (dominant palette) + Brandfetch data + WebSearch "[brand] brand guidelines" or "[brand] huisstijl"
  • Fonts: WebSearch "[brand] font" or "[brand] typography" — brand pages, Fonts In Use, design case studies often document these
  • Fall back to Google Fonts structural matches when real font files are inaccessible

If you can't find a clean product shot from ANY source, tell the user before generating. Don't proceed with text-only prompts for product photography.

1c. Colors (logo-first, CSS-second)

The logo IS the brand colors. This is the #1 rule. CSS bundles are full of UI noise — focus states, error reds, chrome grays, hover tints. The logo tells you what the brand actually is.

Method: logo first, CSS only for exact hex values.

  1. Download and visually inspect the brand logo (from Brandfetch icon URL or the site's own logo asset). The colors in the logo are the brand colors. Period.

    • VanMoof logo = white V on dark charcoal -> brand is monochrome dark, not blue
    • Coca-Cola logo = red + white -> brand is red, not whatever CSS accent is on their site
    • If the logo is monochrome (black/white/dark), the brand identity IS monochrome — don't hunt for a "primary color" that isn't there
  2. Classify from the logo:

    • Brand primary — the dominant color IN THE LOGO (could be a color, could be near-black)
    • Brand secondary — second color in the logo if present
    • Surface — the background tone the logo sits on (dark? light? white?)
  3. CSS is supplementary only — use it to get exact hex values for the colors you already identified from the logo, and to find text/UI colors:

    grep -oiE '#[0-9a-fA-F]{3,8}' bundle.css | sort | uniq -c | sort -rn

    Match the logo colors to the nearest CSS hex values. Saturated colors in CSS that DON'T appear in the logo are UI accents — document them as such, don't promote them to "brand primary."

  4. If CSS has flashy colors the logo doesn't: Those are UI accents (link color, button focus, interactive states). Label them UI accent in the DNA summary. They can appear in the landing page for buttons/links, but they are NOT the brand identity and should NOT drive the overall direction or mood of the page.

Why this matters: CSS accent colors that don't appear in the logo are UI chrome, not brand identity — promoting them to "brand primary" produces a page that looks like a different company.

1d. Fonts (download the actual font files)

Use the brand's real fonts, not "similar" Google Fonts substitutes. Playfair Display is NOT PPEditorialNew. Inter is NOT Unica77. Wrong fonts make the page look like a different brand entirely.

Step 1: Find the font files in CSS

grep -oiE '@font-face\s*\{[^}]*\}' bundle.css | grep -oiE "font-family:\s*[^;]*" | sort -u
grep -oiE "src:\s*url\([^)]*\.woff2[^)]*\)" bundle.css | head -20

Step 2: Download the font files

mkdir -p fonts
# Extract woff2 URLs and download them
# Relative URLs need the domain prefix
curl -sL "https://domain.com/fonts/BrandFont-Regular.woff2" -o fonts/BrandFont-Regular.woff2
curl -sL "https://domain.com/fonts/BrandFont-Bold.woff2" -o fonts/BrandFont-Bold.woff2

Step 3: Use them in the landing page via local @font-face

@font-face {
  font-family: 'BrandFont';
  font-weight: 400;
  font-display: swap;
  src: url('fonts/BrandFont-Regular.woff2') format('woff2');
}

Fallback only if font files are behind auth (403) or not found (404):

  • Pick a Google Fonts match that shares the same structural qualities (x-height, contrast, width, serif style) — not just "another serif" or "another sans"
  • Note in the DNA summary that it's a fallback, not the real font

1e. Present DNA summary + ask direction

Show the user:

[Brand] — [display font] / [body font]
Brand colors: #XXXX (primary), #YYYY (accent)
Text: #2222 body, #FFFF inverse
Fonts: downloaded (or: fallback — couldn't download, using [Google Font])
Product ref: saved (or: couldn't find clean shot)

Recommended direction: [one line based on brand personality]
Go, or tweak?

One question, one answer, move on.


Step 2: SHOOT

Shot list (5 stills + 2 videos)

# Shot Model Dimensions Reference Used in section
1 Hero — product in environment (16:9) nano-banana-pro 1280x720 --image-url reference/product-front.png 1. Hero (video poster)
2 Detail — macro close-up nano-banana-pro 1280x1024 --image-url reference/product-front.png 5. Quote overlay bg
3 Lifestyle — product in use nano-banana-pro 1024x1280 --image-url reference/product-front.png 3. Intro split
4 Texture/environment — architectural (16:9) nano-banana 1280x720 No ref (mood shot) 7. Texture (video poster)
5 Spec — product on clean surface nano-banana 1024x1280 --image-url reference/product-front.png 8. Spec + CTA visual
6 Hero video — product cinematic kling-2.5 16:9 5s --start-image images/01-hero.png 1. Hero bg
7 Section video — different scene/angle kling-2.5 16:9 5s --start-image images/04-texture.png 7. Texture bg

No image reuse — HARD RULE. The 8-section layout has 5 image slots and 2 video slots. Every slot gets its own unique asset. Never reuse an image from one section in another. The shot list is designed 1:1 with the section layout — if a section needs an image, there's a dedicated still for it.

Angle variation — HARD RULE. Every product shot must use a fundamentally different camera angle. Same product, same 3/4 view, different room is NOT enough — a user scrolling the page will see it as the same photo twice. Mandate distinct perspectives across stills:

Still Required angle
01 Hero 3/4 environmental (eye level, product in room)
02 Detail Macro extreme close-up (texture/joint/material)
03 Lifestyle Medium shot with human interaction (hands, in-use)
05 Spec Top-down / direct side profile / flat-lay — must contrast the hero's 3/4 view

If you catch yourself writing two prompts with similar camera positions (both eye-level 3/4, both front-on), stop and change one.

Hero still (01) MUST be 16:9 (1280x720). The hero video uses this as --start-image and plays full-bleed in the viewport. A 4:3 or 5:4 image will leave black bars or get awkwardly cropped.

Texture still (04) MUST also be 16:9 for the same reason — it's the start frame for the second video.

Two distinct videos, two distinct scenes. Never reuse the same video in two sections. The hero video and section video should show different moments/angles/moods.

Every product shot uses --image-url with the saved reference. This is non-negotiable. It's what makes the generated product look like the actual product instead of a hallucination.

The video always uses an approved still as --start-image. Never text-to-video for a hero.

Prompt formula (7 Pillars)

Every prompt follows this structure:

[MOOD] atmosphere,
[SUBJECT: "This exact product from the reference image" + materials, colors, brand details],
[PLACEMENT] on/in [SURFACE/SETTING],
[CAMERA: body + lens + aperture],
[LIGHTING: color temp K + direction + modifier],
[TEXTURE DETAILS],
[STRATEGIC IMPERFECTIONS: film grain, dust, condensation],
NOT CGI, NOT retouched, natural imperfections

Rules:

  • Every prompt opens with a DIFFERENT mood word (Cinematic, Intimate, Moody, Stark, Dawn...)
  • Every prompt has camera body + lens + aperture + color temperature
  • Every prompt ends with anti-AI markers
  • Product shots MUST start with "This exact product from the reference image"
  • Vary lighting across shots (golden hour, overcast, tungsten, blue hour — never same twice)

Camera assignments

Shot type Camera Lens Aperture
Hero product Hasselblad X2D 80mm f/2.8
Detail macro Canon EOS R5 135mm macro f/4
Lifestyle Leica SL2 50mm f/1.4
Environment Phase One IQ4 65mm f/3.5
Spec packshot Fuji GFX 100S 110mm f/5.6

Video prompt (7 video pillars)

1. Subject lock — approved hero still as --start-image
2. Camera motion — one verb: push-in, orbit, dolly, handheld breath
3. Subject motion — one verb: what changes in 5s
4. Lighting evolution — does light shift? Say so or say "static"
5. Mood word — one word: intimate, austere, kinetic
6. Audio — "ambient room tone" or "silent"
7. End state — describe final frame

Execution

mkdir -p logs images video

# All 4 stills in parallel — hero + texture are 16:9 for video start frames
uv run ~/.agents/skills/krea-ai/scripts/generate_image.py \
  --prompt "$(cat prompts/01-hero.txt)" \
  --image-url "reference/product-front.png" \
  --filename "images/01-hero.png" \
  --model nano-banana-pro --width 1280 --height 720 \
  > logs/01.log 2>&1 &

uv run ~/.agents/skills/krea-ai/scripts/generate_image.py \
  --prompt "$(cat prompts/04-texture.txt)" \
  --filename "images/04-texture.png" \
  --model nano-banana --width 1280 --height 720 \
  > logs/04.log 2>&1 &

# ... 02, 03, and 05 in parallel too ...
uv run ~/.agents/skills/krea-ai/scripts/generate_image.py \
  --prompt "$(cat prompts/05-spec.txt)" \
  --image-url "reference/product-front.png" \
  --filename "images/05-spec.png" \
  --model nano-banana --width 1024 --height 1280 \
  > logs/05.log 2>&1 &

wait

# BOTH videos after stills are confirmed — different scenes, different start images
uv run ~/.agents/skills/krea-ai/scripts/generate_video.py \
  --prompt "$(cat prompts/06-video-hero.txt)" \
  --start-image "images/01-hero.png" \
  --filename "video/01-hero.mp4" \
  --model kling-2.5 --duration 5 --aspect-ratio 16:9 \
  > logs/06.log 2>&1 &

uv run ~/.agents/skills/krea-ai/scripts/generate_video.py \
  --prompt "$(cat prompts/07-video-section.txt)" \
  --start-image "images/04-texture.png" \
  --filename "video/02-section.mp4" \
  --model kling-2.5 --duration 5 --aspect-ratio 16:9 \
  > logs/07.log 2>&1 &

wait

Reference params — never mix:

  • Stills: --image-url (generate_image.py)
  • Videos: --start-image / --end-image (generate_video.py)

Step 3: BUILD

Use /frontend-design with the brand DNA + all generated assets.

Brief template for /frontend-design

/frontend-design Build a landing page for [Brand] [Product/Concept].

BRAND DNA:
- Display font: [exact brand font] (loaded via local @font-face from fonts/ folder)
- Body font: [exact brand font] (loaded via local @font-face from fonts/ folder)
- If fonts couldn't be downloaded: [Google Fonts structural match] + note it's a fallback
- Brand primary: [hex — verified against screenshot]
- Brand accent: [hex — verified against screenshot]
- Text: [hex values]
- Personality: [3 adjectives]

FONTS:
- Include @font-face declarations at the top of the CSS pointing to fonts/*.woff2
- The fonts/ folder contains the actual brand font files downloaded from the website
- Font files are served locally — no external font CDN needed

IMAGES (5 unique stills — every section gets its own, NEVER reuse):
- images/01-hero.png (hero product in environment, 16:9) -> section 1 hero poster
- images/02-detail.png (macro close-up) -> section 5 quote bg
- images/03-lifestyle.png (product in use) -> section 3 intro split
- images/04-texture.png (environment/mood, 16:9) -> section 7 texture poster
- images/05-spec.png (product on clean surface) -> section 8 spec visual

VIDEOS (two distinct videos, two distinct scenes):
- video/01-hero.mp4 (hero video, poster: images/01-hero.png) — plays in hero section
- video/02-section.mp4 (section video, poster: images/04-texture.png) — plays in texture/atmosphere section

VIDEO RULES:
- Videos are SECTION BACKGROUNDS, never floating players
- Pattern: `<video autoplay muted loop playsinline poster="images/XX.png">` — the `poster` attribute IS the fallback
- **NEVER add a sibling `<img>` as a poster/fallback.** An `<img>` with `position:absolute; inset:0` will sit on top of the `<video>` and block playback permanently. The browser's native `poster` attribute already shows the still until the video plays — that's all you need.
- Hero still and texture still are both 16:9 so they fill full-bleed sections without cropping
- CSS for video sections: `video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }` — no sibling elements inside the media container

SECTIONS (8):
1. Hero — full-viewport VIDEO background (video/01-hero.mp4, poster images/01-hero.png), brand title, tagline
2. Ticker — brand keywords marquee on brand-primary background
3. Intro split — lifestyle image + editorial copy
4. Feature grid — 3-6 cards with specs/features
5. Full-bleed quote — detail image background + pull quote overlay
6. Editorial break — typography-only section, brand philosophy
7. Texture section — VIDEO background (video/02-section.mp4, poster images/04-texture.png), caption
8. Spec + CTA — specifications list + price + reserve button + images/05-spec.png as visual
+ Footer with "Shot entirely with AI" credit

Save to: ~/Projects/coding/krea-ai-experiments/[brand-slug]/index.html

After build: serve and open

PORT=$(python3 -c "import socket; s=socket.socket(); s.bind(('',0)); print(s.getsockname()[1]); s.close()")
python3 -m http.server $PORT &
sleep 1 && open http://localhost:$PORT

Model registry (verified models in krea_helpers.py)

Use ONLY these names — anything else 404s.

Image: nano-banana-pro (hero, 119 CU) - nano-banana (workhorse, 32 CU) - gpt-image (text/copy, 184 CU) - ideogram-3 (logos, 54 CU) - imagen-4-ultra (photoreal alt, 47 CU) - seedream-4 (materials, 24 CU) - z-image (fastest, 3 CU)

Video: kling-2.5 (default, 300 CU) - veo-3.1 (cinematic, 1017 CU, 8s required) - hailuo-2.3 (fallback, 300 CU) - wan-2.5 (alt, 569 CU)

Do NOT use: nano-banana-2, seedance-*, kling-3.0, veo-3.1-fast — these appear in marketing docs but are NOT in the script's model registry and will 404.


What this skill does NOT do

  • Character sheets / wardrobe builders / product rips (use /ai-product-shoot for multi-person campaigns)
  • Cosmos reference lookups (use /cosmos-inspo separately if needed)
  • Visual DNA agent (the CSS extraction is enough)
  • Font archetype tier research (use /font-hunt separately if needed)
  • Multi-page campaigns (this is a single-page pitch)
  • Polish loops (/typeset, /critique — run those separately after if wanted)
  • WORKFLOW.md reproduction docs

This skill does one thing: brand in, landing page out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment