← Work

Meta ad pipeline

Shopify product ID in. Finished 9:16 Meta Story ad out.

Two models touch each ad: one paints the gradient, one drafts the headline and then reads the result back. Everything between them is measured or ruled. Type size is fixed. Line fit is measured in the real font. The claim has to be quoted from the product page. The wordmark colour comes from sampled brightness. If a product sits too high for the rules to hold, nothing gets squeezed; the ad goes back.

Generative tools make the imagery. A deterministic renderer sets the type and the mark.

1 · Packshotinput
Transparent packshot of the Amacx Beet Shot bottle
top-100_amacx_7968200130623.png, from the 471-image transparent library. Cutouts escalate from flood-fill to rembg to Photoshop; the hard ones are done by hand.
2 · Gradientmodel
The Beet Shot composited into a generated blue-to-amber gradient
Gemini image generation on palette p1, teal-sky to amber-gold. The gradient pipeline has its own QA gate and keeps the product top below 25% of the frame (y480).
3 · Layoutmeasured
The gradient with the measured product box, the 60px clearance, the type block and the safe zone drawn over it
Drawn from the spec: bbox 406,714 to 692,1468 (u2net), 60px clearance, type block from y260, two lines at 110px each measuring 748px, wordmark 300px.
4 · Finishedrendered
Finished ad: 500MG NITRATE IN A 60ML SHOT, support line Amacx Beet Shot, at The Feed, navy wordmark
Futura PT Condensed ExtraBold at 110px, Helvetica Neue support at 45px, navy on a light field. One attempt, reviewed, rendered 22 Oct 14:08.
The pipeline

Who decides what

Blue marks the steps where a model decides. The other steps are a rule or a person. Three of the line's steps are blue.

A model decidesA rule or a person decides
  1. Product ID
    item_group_id
    7968200130623
  2. Lookup
    manifest · tier
    describers · form
    product page text
  3. Model: gradient image
    Gemini · own QA gate
    ~$0.15 per image
  4. Measure
    product bbox
    u2net segmentation
    share · top · room
  5. Model: headline draft
    gemini-3.6-flash
    + quoted claim
  6. Checks
    lint · fit · balance
    quote · pack word
    one hit fails the row
  7. Model: review pass
    copy read vs image
    2nd reading · vague
  8. Render
    spec → png
  • Nobody hand-writes copy.json. build_spec.py refuses copy with no provenance from write_copy.py.
  • Layout rules from the measurement: 110px fixed · at least 60px clearance · line at most 920px · balance at least 55%.
  • Support line only if the product is under 50% of the frame. Wordmark 300px, navy if luminance is 128 or above, else white.
  • Product top above y480: no squeeze. Routed back for a lower gradient.
  • A failed check goes back to the model with reasons. Three attempts, then human review.

The review pass is a model, so it is blue, and its verdict is still subject to the three-attempt limit. Segmentation is a model, drawn as a rule: it returns a box and a rule reads it.

The gradient pipeline's QA sheet: 18 generated gradients with the product box drawn in green and the safe-zone lines in red and orange
The gradient pipeline's own QA sheet, before any copy. Green is the segmentation box. Red is the Meta Story safe zone the gate tests, 14% top, 6% sides, 35% bottom; orange is the looser 20% bottom line. A product that crosses a line is flagged in the report.
One ad, with its reasons

Every choice is in the spec file

Each rendered ad carries a JSON spec. Nothing on the image is unexplained. These values are copied from specs/poc_7968200130623.json and the run manifest.

The finished Amacx Beet Shot ad
  1. 1

    Headline ← the product page

    "500mg nitrate / in a 60ml shot." The model may only claim what it can quote. The quote has to appear verbatim on thefeed.com.

    copy_provenance.claim_source = "500mg of pure dietary nitrate in a 60ml shot"
    model gemini-3.6-flash · prompt c133edcb · attempts 1 · reviewed true
  2. 2

    110px ← the house ad type scale

    One size for every ad in the batch. Copy has to fit it; nothing auto-shrinks. The 9:16 headline is 10.2% of the short edge, which reads at about 40px on a phone.

    type.headline.size_px_at_1080 = 110 · max_lines 2 · line_height 0.98 · tracking −0.03em
    measured: "500MG NITRATE" 748px · "IN A 60ML SHOT" 748px · limit 920px
  3. 3

    Support line on ← the subhead gate

    The vendor name goes on only when the label is too small to read on a phone and there is room above the product. Here the product is 39.3% of the frame height.

    support_decision.shown = true
    support_decision.reason = "label too small to read on a phone, and there is room"
    product_share 0.393 · product_top 714
  4. 4

    Product box ← segmentation, 60px of air

    The same background-removal model already cached on the Mac finds the product. The type block ends at y560; the product starts at y714; the rule needs 60.

    product_bbox = [406, 714, 692, 1468] · layout.block_top 260 · block_max_width 920
  5. 5

    Navy wordmark ← sampled brightness

    The pixels under the wordmark are averaged. Below 128 the mark is white, above it navy. No lime, by my call on 22 Oct.

    logo.region_luminance = 160.4 → color #09023d · width 300 · bottom 290 · lime_uses 0
Rejections

What failed

Two sources, labelled. The first set is a proof of concept from 22 Oct where an assistant wrote the headlines and layout by hand. Those renders are superseded. The second set is what the finished process caught on its own during the real run, straight from copy_log.jsonl.

First pass, before the checks existed

poc/_superseded_handwritten_20261022/ · hand-written on 22 Oct · not to be used
Superseded GU Roctane drink mix render with the headline Fuel and hydration, one bottle
Fuel and hydration, one bottle
pack-word check · review pass

It is a sachet. "bottle" is now only allowed when it matches the form in describers.csv.

Superseded Firefly render with the headline Recovery, strapped on
Recovery, strapped on
double-meaning rule

Flagged in review. The lint regex now lists the phrase by name.

Superseded Garmin HRM 200 render with the headline Chest strap accuracy
Chest strap accuracy
concreteness check

A category claim with nothing to quote. The real run wrote "Real-time heart rate tracking" from the product page.

Superseded Amacx Drink Gel render with the headline A gel you can drink
A gel / you can drink
line-balance rule

The shorter line must be at least 55% the width of the longer, measured in Futura.

Superseded Amacx Turbo Gel render with the headline Carbs and caffeine, one gel
Carbs and caffeine, / one gel
line-balance rule

Same rule. Also the headline was set at a different size from its neighbours.

Superseded Cheribundi render with type sitting on the pouch
Tart cherry / for recovery
segmentation + 60px clearance

Type on the pouch. Position now comes from u2net, with 60px of air required.

Superseded Enervit Magic Cherry render with type sitting on the pouch
Cherry shot, / post-session
segmentation + 60px clearance

Same failure. The edge heuristic missed low-contrast packs; segmentation does not.

Superseded Carbs Fuel render with type over the pouch
Just carbs, / nothing else
product-too-high routing

Type over the product. The real run refused this one entirely; see below.

Contact sheet of the superseded first pass, with headlines at different sizes
The first-pass contact sheet. Headlines at different sizes across the set, and a lime wordmark on four of them. The fix was a fixed 110px and no lime.

Caught by the process during the real run

poc/copy_log.jsonl · 22 Oct 2026 14:08 to 14:13 · 22 attempts for 19 products
Final Firefly ad: Speeds recovery while you rest
Increases blood / flow by 400%
Speeds recovery / while you rest
review pass · double meaning

Attempt 1 was quoted correctly from the product page and still failed. The reviewer flagged a second reading. Attempt 2 passed.

Final Cheribundi ad: Less soreness
Cut inflammation
Less soreness
fit at 110px · 922px against 920

Two pixels over the line limit, as the log recorded it. The rule does not round.

Final Create gummies ad: 1.5g per gummy
Enhance strength
1.5g per gummy
fit at 110px · 922px against 920

Same width, same rule. The rewrite moved from a benefit claim to a spec claim with a quote behind it. All three rejections are in the log at the end of the page.

Carbs Fuel Original Mini gradient with the product top at y426 marked against the y480 rule
product top y426
25% rule · y480

Not rendered. Routed back.

Routed back, automatically

Carbs Fuel Original Mini. Segmentation put the product top at y426. At 110px, not even one headline line fits above it with 60px of air. The gradient pipeline's own placement rule says the product top stays below 25% of the frame, y480, so this is the gradient's error, and the fix belongs there.

The rule is that products that sit too high are not squeezed. The product went to copy_needs_review.json with a note to regenerate the gradient lower. 19 of 20 rendered.

"product top is at y426; no headline fits above it at 110px. Regenerate the gradient with the product lower (the pipeline's own placement rule is top below 25% / y480)."
The set

The nineteen ads

Manifest order. Every headline below was written by the process, from a quote, and checked the same way. Type size is identical on all nineteen.

Amacx Beet Shot
Amacx Beet Shot
500mg nitrate / in a 60ml shot
Amacx Drink Gel
Amacx Drink Gel
Absorb up to / 90g of carbs
Amacx Turbo Gel
Amacx Turbo Gel
40 g carbs / no water needed
Ascent Whey Protein
Ascent Whey Protein
Speed up / your recovery
Cheribundi Tart Cherry
Cheribundi Tart Cherry
Less soreness
Clif Bloks
Clif Bloks
24g carbs / per serving
COROS Heart Rate Monitor
COROS HRM
38 hours of / battery life
Create Creatine Gummies
Create Creatine Gummies
1.5g per gummy
Enervit C2:1PRO Carbo Gel
Enervit C2:1PRO
40g of carbs / in one gel
Enervit Magic Cherry
Enervit Magic Cherry
40x more / concentrated
Enervit Secret Sodium Gel
Enervit Secret Sodium
40g carbs and / 200mg sodium
Firefly Recovery Device
Firefly Recovery
Speeds recovery / while you rest
Garmin Forerunner 265
Garmin Forerunner 265
Lightweight / at just 47g
Garmin HRM 200
Garmin HRM 200
Real-time heart / rate tracking
GU Energy Gel
GU Energy Gel
Protect muscles / from breakdown
GU Roctane Drink Mix
GU Roctane Drink Mix
Fuel long days / with 60g carbs
GU Roctane Energy Gel
GU Roctane Gel
3x the sodium / of original GU
Hammer Gel
Hammer Gel
A consistent / flow of energy
hDrop Gen 2
hDrop Gen 2
120 hours of / active tracking
Numbers and limits

Counts, cost and limits

19of 20
produced in one run
1
routed back automatically
3
headlines rejected and rewritten
5min 31 s
copy for 19 products, first log line to last

Cost

About $0.15 per gradient image, plus a few cents of copy per product. Copy ran one attempt for 16 products and two for 3. Carbs Fuel never reached the copy step.

What stays human, and why

  • Product clipping. Automated cutouts were tested on 22 Oct and rejected. The edges were not good enough. Clipping stays in Photoshop or with a vendor.
  • Health claims sign-off. The quote check proves the claim exists on the product page. It does not prove the claim should run.
  • Contact-sheet approval. Every run ends on a contact sheet and a person looks at it.

Known copy weaknesses from this run

  • It ran on gemini-3.6-flash because Pro was not available on the key.
  • Several headlines lift the vendor's phrasing almost whole. "Lightweight at just 47g" is the product page's "Lightweight: Just a mere 47g."
  • Some line breaks end on "of" or "to": "38 hours of / battery life", "120 hours of / active tracking", "Absorb up to / 90g of carbs".

Fixes for all three are specced in the prompt and lint rules and have not yet been run. The manifest records model, prompt version and attempts for every ad, so the next run can be compared to this one line by line.

Not yet wired

  • 4:5 and 1:1 cuts. The gradient pipeline outputs 9:16 only.
  • A real sales ranking. top20.txt is the first 20 top-100 SKUs with a QA-passed gradient, in manifest order.
The run

One command

cd ~/Desktop/renderer
poc/run_batch.sh --ids-file poc/top20.txt
The log
copy_log.jsonl · 22 attempts for 19 products · 14:08:10 to 14:13:41
14:08:10 7968200130623 1 500mg nitrate / in a 60ml shot ok
14:08:29 7225043648575 1 Absorb up to / 90g of carbs ok
14:08:43 7225036439615 1 40 g carbs / no water needed ok
14:08:58 7891959368 1 Speed up / your recovery ok
14:09:14 7017666641983 1 Cut inflammation 922px at 110px; max 920
14:09:29 7017666641983 2 Less soreness ok
14:09:42 8177272520 1 24g carbs / per serving ok
14:09:56 7864700796991 1 38 hours of / battery life ok
14:10:06 7218430804031 1 Enhance strength 922px at 110px; max 920
14:10:29 7218430804031 2 1.5g per gummy ok
14:10:45 7189920776255 1 40g of carbs / in one gel ok
14:10:58 7790147928127 1 40x more / concentrated ok
14:11:16 8149804449855 1 40g carbs and / 200mg sodium ok
14:11:30 4508986671167 1 Increases blood / flow by 400% reviewer: double_meaning
14:11:46 4508986671167 2 Speeds recovery / while you rest ok
14:12:01 7644072640575 1 Lightweight / at just 47g ok
14:12:17 8061508517951 1 Real-time heart / rate tracking ok
14:12:30 7892115592 1 Protect muscles / from breakdown ok
14:12:46 7892078024 1 Fuel long days / with 60g carbs ok
14:13:09 7892111176 1 3x the sodium / of original GU ok
14:13:31 7892062472 1 A consistent / flow of energy ok
14:13:41 7981501317183 1 120 hours of / active tracking ok

Carbs Fuel 7265399603263 never reached the copy step; it is in copy_needs_review.json.

Contact sheet of the 19 finished ads from the 22 Oct run
poc/out/20261022_1406/contact.png. The run ends here. A person reviews the sheet.