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
/brand-shoot <url> — URL can be a website, Instagram profile, or product page.
KREA_API_TOKENin environmentBRANDFETCH_API_KEYin environment- Krea scripts at
~/.agents/skills/krea-ai/scripts/ uvinstalled
~/Projects/coding/krea-ai-experiments/{brand-slug}/
Goal: save everything needed to generate on-brand images. The product reference is the most important asset — without it, every generation is a hallucination.
curl -s "https://api.brandfetch.io/v2/search/<brand>" \
-H "Authorization: Bearer $BRANDFETCH_API_KEY"Pick top result. Download icon to images/logo.webp.
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:
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 withfill="#fff"orfill="#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
currentColorso the CSScolorproperty 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>
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
Read images/logo.svg
You MUST visually confirm:
- No colored background shape visible
- Wordmark is visible (not invisible white-on-white)
- 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.
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- Website —
WebFetchorcurlfor<img>,<source>,og:image - Instagram — if website is 403/blocked/thin (see below)
- WebSearch image results — last resort for isolated product packshots
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 WindowsTell 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.
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.
-
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
-
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?)
-
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."
-
If CSS has flashy colors the logo doesn't: Those are UI accents (link color, button focus, interactive states). Label them
UI accentin 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.
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 -20Step 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.woff2Step 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
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.
| # | 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.
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)
| 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 |
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
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 &
waitReference params — never mix:
- Stills:
--image-url(generate_image.py) - Videos:
--start-image/--end-image(generate_video.py)
Use /frontend-design with the brand DNA + all generated assets.
/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
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:$PORTUse 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.
- Character sheets / wardrobe builders / product rips (use
/ai-product-shootfor multi-person campaigns) - Cosmos reference lookups (use
/cosmos-insposeparately if needed) - Visual DNA agent (the CSS extraction is enough)
- Font archetype tier research (use
/font-huntseparately 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.