# /static-ad-generator

> Full pipeline for generating up to 40 production-ready static ads for a ACME Agency client using Krea.ai Nano Banana 2.


# /static-ad-generator

Full static ad creation pipeline for ACME Agency clients: Brand DNA → Template selection → Krea.ai batch generation. Uses 40 proven ad format templates. Inspects existing client images from Drive for style continuity. Caches Brand DNA on Drive so returning clients skip research.

**vs `/krea-create-images`:** Use `krea-create-images` for a single one-off image. Use this skill for a full batch creative run (8–40 ads at once).

**With `/copywrite`:** For new client campaigns, run `/copywrite meta "ClientName"` first to generate and approve ad copy. Then run `/static-ad-generator "ClientName" --phase 2` — Brand DNA is already cached, so it skips Phase 1 and goes straight to template selection using the approved copy as Mode A input.

---

## Triggers

- `/static-ad-generator ACME Agency` — full pipeline (Phases 1+2+3)
- `/static-ad-generator ACME Agency --phase 2` — re-generate prompts only (uses cached Brand DNA)
- `/static-ad-generator ACME Agency --refresh` — force re-run Brand DNA even if Drive copy exists
- `/static-ad-generator ACME Agency --templates 1,3,11` — generate specific templates only
- `/static-ad-generator ACME Agency --batch 2` — 2 images per template (default: 1)

---

## Preflight (run BEFORE any expensive Krea batch)

A static-ad batch is the most expensive operation in this workspace (8-40 Krea calls × ~48 compute units each). Validate everything BEFORE spending credits. If ANY check fails, abort with the specific failure — do not proceed.

1. **Client exists** in `ACME Agency/clients/clients.json`. Resolve the canonical key (case-insensitive).
2. **`CLIENT.md` exists** at `ACME Agency/clients/<Client>/CLIENT.md`. If missing → recommend `/paradox-onboard` first, do NOT proceed.
3. **`brand-dna.md` exists OR Phase 1 is set to build it.** If `--phase 2+` was passed but no brand-dna.md exists → abort with "run Phase 1 first".
4. **`static_ads_prompts.json` exists** at the client folder if `--phase 3` (skip generation) was passed. Otherwise it'll be built in Phase 2.
5. **`drive_folder_id` is present and reachable.** Test with a `listFiles()` call before starting Phase 3 — Drive auth has expired in production before. Fail-fast beats failing 30 minutes into a batch.
6. **Krea API key set** in `.env`. If missing → abort.
7. **Batch × templates × resolution stays under sane caps**: `batch * templates ≤ 80` and resolution `1K|2K|4K`. If higher → require explicit confirmation in the prompt context.
8. **Reference assets resolve** if `reference_assets` is set in clients.json: every named tag must point to a real Drive URL. If any fail → abort with the missing tag name.
9. **Slack channel resolves** if Slack reporting is enabled. Skip cleanly if bot not invited (don't crash).
10. **Disk space check**: confirm at least 500 MB free in the local client folder. Krea downloads + manifest can blow past quotas on small drives.

If all checks pass, log "preflight: OK (n templates × m batch = X images, ratio Y, est cost Z compute units)" and proceed.

---

## Phase 1 — Brand DNA

**Goal:** Build a visual identity document that informs every prompt. Check Drive for a cached copy first to avoid redundant research.

### Step 1.0 — Check Drive for existing Brand DNA

Before doing any research:

1. Look up the client in `ACME Agency/clients/clients.json` — get `drive_folder_id` and Slack channel
2. Run: `gws drive files list --folder <drive_folder_id>` and look for `brand-dna.md`
3. **If found and `--refresh` was NOT passed:**
   - Download it: `gws drive files download <file_id> --output "ACME Agency/clients/<ClientName>/brand-dna.md"`
   - Tell the user: "Found Brand DNA from [date in file] — skipping research. Use `--refresh` to rebuild it."
   - **Skip directly to Phase 2**
4. **If not found or `--refresh` was passed:** Proceed with Steps 1.1–1.4 below

### Step 1.1 — Load client context

- Read `ACME Agency/clients/<ClientName>/CLIENT.md` — extract: business description, products/services, target audience, brand tone, colors, market, unique selling points
- If CLIENT.md is missing, note it and rely on web research + user's brief

### Step 1.1b — Website scraping with Firecrawl

**Goal:** Get brand copy, colors, messaging, and visual style from the live website.

1. Get the website URL from CLIENT.md or the `notes` field in `clients.json`
2. Scrape with Firecrawl (handles JS-rendered pages, returns clean markdown):
```javascript
node --input-type=module <<'EOF'
import { scrapeWithMeta } from './ACME Agency/scripts/lib/firecrawl.mjs';
const result = await scrapeWithMeta('<WEBSITE_URL>');
console.log('Title:', result.title);
console.log('Description:', result.description);
console.log('OG Image:', result.ogImage);
console.log('Content:\n', result.markdown.slice(0, 3000));
EOF
```
3. Extract from the content:
   - **Brand messaging**: headlines, taglines, value propositions, CTAs
   - **Services/products**: what they offer, how they describe it
   - **Tone of voice**: formal/casual, direct/aspirational, language style
   - **Visual cues mentioned**: any color/style hints in the copy
4. Also take a Playwright screenshot if you need exact hex colors for Brand DNA:
```bash
node --input-type=module <<'EOF'
import { chromium } from 'playwright';
const b = await chromium.launch();
const p = await b.newPage();
await p.setViewportSize({ width: 1280, height: 900 });
try { await p.goto('https://<WEBSITE_URL>', { waitUntil: 'networkidle', timeout: 15000 }); } catch {}
await p.screenshot({ path: 'ACME Agency/clients/<ClientName>/website-screenshot.png' });
await b.close();
EOF
```
   Then Read the screenshot with Claude vision for exact hex colors, logo style, and font weight.

> **If the website is unreachable:** fall back to web research, note "colors approximate — verify before publishing" in Brand DNA.

### Step 1.1c — Logo & reference assets

The logo is **auto-prepared on every Phase 3 run** by `static_ads_generate.mjs`, which calls `ensureClientLogo()` from `lib/logo_prepare.mjs`. Resolution order: cached `clients.json > reference_assets.logo` → local `logo.png` in the client folder → Playwright screenshot from `landing_page` (or website URL in `notes`). The result is uploaded to Drive (public) and written back to `reference_assets.logo` automatically. The logo is then prepended to every template's reference images and the prompt is augmented with a "logo is canonical, reproduce pixel-exact" instruction.

**You only need to intervene** when the Phase 3 log says `[Logo] No logo available`. That means the client has no `landing_page` and no local `logo.png`. Drop a `logo.png` (or jpg/webp) into `ACME Agency/clients/<ClientName>/` and re-run, or run the helper manually:

```bash
node ACME Agency/scripts/lib/logo_prepare.mjs "<ClientName>"          # idempotent (cache hit if already set)
node ACME Agency/scripts/lib/logo_prepare.mjs "<ClientName>" --force  # re-capture (use after a rebrand)
```

Then identify what other named reference assets this client has (interior, product, team, hero shot, etc.) and upload them from `gallery-tmp/` to `clients.json`:

```javascript
node --input-type=module <<'EOF'
import { uploadPublic } from './ACME Agency/scripts/lib/google_drive.mjs';
const folderId = '<drive_folder_id>';
const asset1 = await uploadPublic('ACME Agency/clients/<C>/gallery-tmp/<filename>', folderId);
console.log(asset1);
EOF
```

```json
"reference_assets": {
  "logo":    "https://drive.google.com/uc?id=...",
  "<tag2>":  "https://drive.google.com/uc?id=...",
  "<tag3>":  "https://drive.google.com/uc?id=..."
}
```

Name each asset by what it shows (e.g. `"exterior"`, `"product"`, `"team"`, `"interior"`, `"hero-shot"`) — the tag names are referenced in `reference_tags` in the prompts JSON. Use names that describe the content, not the client-specific subject.

> **Why this matters:** Named assets are passed directly to Krea as reference images per-template instead of a random product-images fallback. Krea sees the real logo and places it naturally where it fits in the design — no forced overlay.

### Step 1.2 — Drive Galerija inspection

Check the client's Drive folder for existing creative images — these become style inspiration for the Brand DNA.

**Uses `google_drive.mjs`** (no gws CLI needed):

```javascript
// Step 1: list all files/subfolders in the client Drive folder
node --input-type=module <<'EOF'
import { listFiles, downloadFile } from './ACME Agency/scripts/lib/google_drive.mjs';
import { mkdir } from 'fs/promises';

const FOLDER_ID = '<drive_folder_id>';  // from clients.json
const CLIENT = '<ClientName>';

// Find Galerija subfolder (also matches 'Galerija', 'Gallery', 'galeria')
const all = await listFiles(FOLDER_ID);
const gallery = all.find(f =>
  f.mimeType.includes('folder') &&
  /galeri|gallery/i.test(f.name)
);

if (!gallery) { console.log('No gallery folder found'); process.exit(0); }
console.log('Gallery folder:', gallery.name, gallery.id);

// List images inside Galerija
const images = await listFiles(gallery.id, { mimeType: 'image' });
console.log(`Found ${images.length} images`);

// Download up to 5 most recent
await mkdir(`ACME Agency/clients/${CLIENT}/gallery-tmp`, { recursive: true });
for (const img of images.slice(0, 5)) {
  const ext = img.name.split('.').pop() || 'jpg';
  const dest = `ACME Agency/clients/${CLIENT}/gallery-tmp/${img.name}`;
  await downloadFile(img.id, dest);
  console.log('Downloaded:', dest);
}
EOF
```

2. **Read each downloaded image** using the Read tool (Claude vision). For each note:
   - Photography style: studio vs lifestyle vs clinical vs UGC
   - Color temperature: warm/cool/neutral, any color grading
   - Text overlay density: minimal / moderate / heavy
   - Composition pattern: centered, split, person-forward, product-hero
   - Mood and energy level

3. Add **"Existing Creative Style"** section to Brand DNA with these observations — this informs the Prompt Modifier with real visual evidence, not assumptions.

4. If no gallery folder or no images found: note it and proceed — Prompt Modifier will rely on screenshot + web research.

### Step 1.3 — Web research

Use web search to fill any gaps not covered by CLIENT.md, screenshot, and gallery:

1. `"[ClientName] [city/country] branding"` — design agency credits, brand guidelines
2. `"[ClientName] Facebook ads"` or Meta Ad Library — current ad creative approaches
3. 1–2 direct competitors for visual differentiation context

**Skip searches where CLIENT.md + screenshot + gallery already provide sufficient detail.**

### Step 1.4 — Write and save Brand DNA

Compile all research into a structured document. Save locally AND upload to Drive via API.

```
# Brand DNA — [ClientName]
Generated: [DATE]
Drive cached: yes

## Brand Overview
- Business: [one sentence]
- Market: [Croatian / regional / other]
- Voice adjectives: [5 descriptors, e.g. "professional, warm, trustworthy, modern, local"]
- Positioning: [what makes them different]

## Visual System
- Primary font: [name or description, e.g. "geometric sans-serif, bold weight"]
- Secondary font: [body/supporting font]
- Primary color: [name + exact hex from screenshot]
- Secondary color: [name + exact hex]
- Accent color: [name + exact hex]
- Background colors: [white, off-white, dark, etc.]
- CTA color: [color + style]

## Logo
- Description: [exact visual description for Krea.ai: font style, text content, icon shape, colors]
- Example: "Bold condensed sans-serif 'ACME Agency' in sky blue (#your-channel), small gold lightning bolt icon to the left"
- Placement in ads: bottom right corner, white version on dark backgrounds

## Photography Direction
- Lighting: [e.g. "soft diffused natural light"]
- Color grading: [warm/cool/neutral, saturation]
- Composition: [e.g. "centered subjects, clean negative space"]
- Subject matter: [people, product, lifestyle, architectural, etc.]
- Props & surfaces: [typical backgrounds, objects in frame]
- Mood: [e.g. "approachable and professional"]

## Product / Service Details
- Core offering: [specific description]
- Visual appearance: [what it looks like in an ad]
- Distinctive elements: [logo marks, key visuals, brand identifiers]

## Existing Creative Style
[Observations from Galerija inspection — or "No gallery found" if empty]
- Photography: [observations]
- Copy density: [observations]
- Composition: [observations]
- Mood: [observations]

## Ad Creative Style
- Formats currently run: [what types they use]
- Text overlay: [minimal / heavy, style]
- Cultural cues: [Croatian market, language preference HR/EN]

## Image Generation Prompt Modifier
[50-75 word paragraph prepended to EVERY generated prompt. Must include: EXACT hex colors from screenshot, precise logo description, font style descriptors, photography direction, lighting, mood, cultural/market cue. End with logo placement instruction: "bottom right corner: [logo description]". This is the most critical output.]
```

**Save locally:** `ACME Agency/clients/<ClientName>/brand-dna.md`

**Upload to Drive via API:**
```javascript
node --input-type=module <<'EOF'
import { uploadFile } from './ACME Agency/scripts/lib/google_drive.mjs';
const fileId = await uploadFile(
  'ACME Agency/clients/<ClientName>/brand-dna.md',
  '<drive_folder_id>',
  'brand-dna.md'
);
console.log('Uploaded brand-dna.md, fileId:', fileId);
EOF
```

Tell the user: "Brand DNA saved locally and uploaded to Drive. Ready for Phase 2."

---

## Phase 2 — Prompt Generation

**Goal:** Select the right templates, write or adapt copy, fill all placeholders → produce `static_ads_prompts.json`.

### Step 2.0 — Detect input mode

Before doing anything else, determine which mode the user is in:

**MODE A — Copy provided:** User has given specific text (headlines, body copy, offers, testimonials).
- Select 5–8 templates that are the best match for the provided copy
- Use the user's copy verbatim where it fits; adapt it for templates that require a different format
- Example: if user provides a testimonial quote, prioritize templates 3, 6, 11, 17

**MODE B — Brief or idea only:** User gives a campaign concept, goal, product focus, or just the client name.
- Claude writes all copy from scratch using Brand DNA voice + audience insights
- Select 8–12 templates that make strategic sense for this client's industry (see guidance below)
- Justify each template selection in 1 line before showing the plan

**In both modes:** Before generating any prompts, show the user:
- Which templates were selected and why
- The copy you plan to use for text-heavy templates (testimonials, ACME Agencyws, headlines, stats)
- Ask for approval or adjustments before proceeding to prompt filling

### Step 2.1 — Template selection by client type

Use these rules to guide template selection:

**Service businesses** (dental, medical, legal, real estate, financial):
- Lead with: 3 (Testimonials), 6 (Social Proof), 17 (Verified ACME Agencyw Card), 4 (Features/Benefits), 10 (Press/Editorial)
- Avoid product-centric templates (35, 22, 13) — adapt these for "service + person" if using them

**E-commerce / product brands:**
- Lead with: 1 (Headline), 2 (Offer/Promotion), 35 (Hero + Stat Bar), 11 (Pull-Quote ACME Agencyw), 25 (Us vs Them Color Split), 13 (Stat Surround Radial)
- UGC-style templates work very well: 8 (Before/After UGC), 29 (UGC + Viral Post Overlay), 32 (UGC Story Callout)

**B2B / agency / professional services:**
- Lead with: 10 (Press/Editorial), 23 (Manifesto/Letter Ad), 26 (Stat Callout), 5 (Bullet-Points), 7 (Us vs Them)
- Avoid heavy UGC/selfie formats — they don't match B2B authority positioning

**Croatian market note:** Lifestyle and testimonial formats convert better in the Croatian market. Heavy number/stat ads perform better in US/UK markets. When in doubt, lead with human faces and clear local context.

### Step 2.2 — Fill templates

For each selected template from `references/template-prompts.md`:

1. **Keep** the opening line "Use the attached images as brand reference." — Krea.ai supports `imageUrls` and the script passes up to 5 product images from the client folder automatically
2. **Prepend** the Image Generation Prompt Modifier from Brand DNA to the start of the prompt
3. Replace ALL `[BRACKETED PLACEHOLDERS]` with brand-specific details
4. Ensure text overlay copy is in the correct language (Croatian for Croatian-market clients unless instructed otherwise)
5. Do not leave any `[BRACKETS]` in the final prompt — every placeholder must be resolved

**Copy writing guidance for text overlays:**
- Headlines: punchy, benefit-driven, max 8 words
- CTAs: action verbs — "Zakažite", "Saznajte više", "Kontaktirajte nas", "Pogledajte ponudu"
- Testimonials and ACME Agencyws: use realistic, specific, credible language matching the industry
- Stats: use real numbers from CLIENT.md if available; otherwise plausible benchmarks marked `[VERIFY]`

### Step 2.3 — Save prompts JSON

Save to `ACME Agency/clients/<ClientName>/static_ads_prompts.json`:

```json
{
  "client": "ClientName",
  "generated_at": "ISO timestamp",
  "brand_dna_version": "YYYY-MM-DD",
  "input_mode": "A | B",
  "prompts": [
    {
      "template_number": 3,
      "template_name": "testimonials",
      "prompt": "Full completed prompt ready for Krea.ai Nano Banana 2...",
      "aspect_ratio": "9:16",
      "reference_tags": ["logo", "interior"],
      "notes": "Copy uses fictional testimonial — replace with real patient quote before publishing"
    }
  ]
}
```

**`reference_tags` rules:**
- Every prompt should have `reference_tags` listing which named assets from `clients.json > reference_assets` to pass as Krea reference images
- `"logo"` is **auto-prepended at runtime** by `static_ads_generate.mjs` — you do NOT need to include it in `reference_tags`. It's still fine if it's there (deduped automatically).
- Add content-specific assets only where relevant: a testimonial ad benefits from an environment shot; a text-only manifesto does not
- If a template uses a real person as a visual reference (team member, professional, spokesperson), add this sentence to the prompt: *"Maintain the exact appearance and likeness of the reference person — do not stylize, idealize, or alter their face, age, or physical characteristics."*
- If `clients.json` has no `reference_assets` yet, omit `reference_tags` — the script falls back to `product-images/` folder automatically
```

Show the user a summary (template name + first 80 chars of prompt) and ask for approval before Phase 3.

---

## Phase 3 — Image Generation

**Goal:** Run batch generation via Krea.ai and deliver organized output.

### Step 3.1 — Confirm count and cost

State before running:
- Number of images: `[templates selected] × [batch]`
- Estimated cost: ~48 Krea compute units per image at 2K
- Ask: "Ready to generate?" unless user has already confirmed

### Step 3.1b — Reference images

The script routes images per-template using `reference_tags` in the prompts JSON:
1. **Named assets** — if `reference_tags` is set, maps each tag to a URL from `clients.json > reference_assets`
2. **Fallback** — if no `reference_tags`, uploads images from `product-images/` (or client root) on-the-fly

Krea receives up to 5 reference images per generation call. It uses them for visual consistency (logo, brand colors, people, environments) — it places them naturally rather than being forced into a fixed position.

**Before running Phase 3:** confirm `clients.json > reference_assets` has at least `"logo"` populated. If not, run `logo_prepare.mjs` first (Step 1.1c).

### Step 3.1c — Drive output folder

Images are automatically uploaded to a structured folder inside the client's Drive:

```
<client drive_folder_id>
└── Social media design/
    └── <client.market or "Croatia">/
        └── <year>/
            └── <month>/
```

Folders are created on demand if missing. To set a non-default market, add `"market": "Germany"` (or whichever) to the client entry in `clients.json`.

### Step 3.2 — Run the script

```bash
node ACME Agency/scripts/static_ads_generate.mjs "ClientName" \
  [--templates 1,3,7] \
  [--batch 1] \
  [--resolution 2K] \
  [--no-slack] \
  [--no-drive] \
  [--no-images]   # skip image reference, pure text-to-image
```

**Default settings:**
- Templates: all in `static_ads_prompts.json`
- Batch: 1 image per template
- Resolution: 2K
- Slack: enabled | Drive: enabled | Image reference: enabled (if images found)

### Step 3.3 — Output

The script saves images to `ACME Agency/clients/<ClientName>/static-ads/<NN-template-name>/`, uploads all to Drive, posts Slack report grouped by template, and appends to `static_ads_manifest.json`.

After completion: tell the user the Drive folder link and ask if they want to iterate on any template.

## Verification (run AFTER Phase 3 — prove it shipped, don't assume)

Rubric: [verify.md](verify.md). Two layers — one enforced, one you run.

**Deterministic (automatic, in `static_ads_generate.mjs`).** Every batch now runs
`validateFiles()` from `shared/verify/assert_output.mjs` BEFORE Drive upload: any 0-byte /
sub-10 KB Krea stub is dropped (never uploaded, never counted as a shipped ad), logged
`[Verify] … dropped`, and recorded as `invalid` in `static_ads_manifest.json`. The summary
line excludes them. So "size > 10 KB" and "no silent stub" are now proven in code, not
hoped. Still confirm from the run output: success count matches selected templates (failed
templates are listed, not hidden), Drive folder created, every valid image has a Drive URL,
Slack post returned a non-null `ts`.

**Vision quality (you run, before the client sees them).** Invoke the `verifier` agent on
a representative sample of the generated PNGs against [verify.md](verify.md) (threshold 8).
Regenerate any template it scores < 8 via `--templates N` (cap 2 passes); only ≥ 8 ships.
This is what catches garbled text, a wrong/redrawn logo, and off-brand color — the failures
the file-size check can't see. If the brand is consistently off, fix `brand-dna.md` and
re-run Phase 2 before regenerating.

Never report "all done" while any layer shows a gap — name the specific failure.

---

## Selective & Re-run Flows

| Goal | Command |
|------|---------|
| Re-run prompts only (new campaign, same brand) | `--phase 2` |
| Force new Brand DNA research | `--refresh` |
| Generate only specific templates | `--templates 3,11,17` |
| A/B testing — 4 variations of winners | `--templates 3,6 --batch 4` |
| Quick test run before full batch | `--templates 1,3,10 --batch 1` |

---

## Key Files

| File | Purpose |
|------|---------|
| `.claude/skills/references/template-prompts.md` | **Primary template library — 40 ad formats** |
| `.claude/skills/static-ad-generator/templates.md` | Quick 12 simplified templates (reference only) |
| `ACME Agency/scripts/static_ads_generate.mjs` | Batch generation script |
| `ACME Agency/scripts/lib/krea.mjs` | Krea.ai API wrapper |
| `ACME Agency/scripts/lib/slack.mjs` | Slack posting |
| `ACME Agency/scripts/lib/logo_prepare.mjs` | Screenshots logo from live website, uploads to Drive, returns public URL |
| `ACME Agency/clients/clients.json` | Client registry (drive_folder_id, slack_channel) |
| `ACME Agency/clients/<Name>/CLIENT.md` | Source brand context |
| `ACME Agency/clients/<Name>/brand-dna.md` | Brand DNA (local copy) |
| `ACME Agency/clients/<Name>/static_ads_prompts.json` | Filled prompts (Phase 2 output) |
| `ACME Agency/clients/<Name>/static-ads/` | Generated images (Phase 3 output) |
| `ACME Agency/clients/<Name>/static_ads_manifest.json` | Generation history |

---

## Tips for Better Output

- **Test before full batch.** Run `--templates 1,3,10` first to validate the Prompt Modifier. If the style is off, adjust Brand DNA and re-run Phase 2 before firing all 40.
- **Richer CLIENT.md = better Brand DNA = better ads.** Ask the user for the website URL and 2–3 "ads we like" examples before Phase 1 if CLIENT.md is sparse.
- **Stats and ACME Agencyws are fictional** — always note this to the user. They should substitute real testimonials and verified numbers before publishing.
- **Croatian copy:** Keep it natural and human, not corporate. Short active sentences. "Zakažite odmah" beats "Iskoristite mogućnost zakazivanja".
- **Mode B quality tip:** Ask the user one question before writing copy — "What's the single most important thing this campaign needs to communicate?" — then make every template answer that question differently.
