| name | create-end-card-from-reference |
| description | Builds a 3s end-card mp4 from an external reference image (Pinterest, magazine spread, competitor ad) by generating the product/background layer via Nano Banana 2 (no text) and overlaying typography via an HTML hyperframe (Playwright-rendered at design dimensions, ffmpeg-scaled to output). Validated 2026-05-20 on Touchland (AMINU skincare ad as reference). Use when standard designed end-cards feel generic; reference-image-driven cards are dramatically more on-brand for lifestyle/Pinterest-coded brands. |
create-end-card-from-reference
Purpose
Generative image models (Nano Banana 2 included) are great at product flat-lays but unreliable at typography — ad-headline text gets mangled. The fix is to split the end-card into two layers:
- Layer 1 (NB2): product flat-lay on a gradient background, NO text.
- Layer 2 (HTML hyperframe): all typography, headline, labels, arrows, wordmark.
Both layers compose via Playwright Chromium render → ffmpeg scale-down. The visual language of the resulting end-card matches the supplied reference image (e.g. AMINU skincare's "massive sans headline + outlined word + handwritten micro-labels + product flat-lay" pattern).
This atom is the proven alternative to:
create-end-card-html — pure HTML, no AI imagery. Good for type-only cards; weak for lifestyle.
create-end-card-branded — auto-selected lifestyle / plain-hero / generated-bg modes. Less precise control over typography placement vs product arrangement.
When to use
- Brand is lifestyle/beauty/CPG/fragrance with a Pinterest-coded aesthetic.
- User provides (or you can source) a reference ad image with strong typographic + product-flatlay composition.
- The end-card needs both: hero product imagery + bold ad typography that AI can't render reliably.
- Output: 9:16 vertical, 3s mp4 with subtle animation (label/arrow staggered reveals, no zoom).
Skip if:
- The end-card is type-only (no products) → use
create-end-card-html.
- The end-card needs photoreal motion/CGI → use
create-end-card-branded with mode=generated-bg.
- You don't have a reference image and no time to source one → use
create-end-card-html or create-end-card-branded.
Inputs
Required:
- N product PNGs (transparent or solid background). Touchland-style flat-button atomizer or any consumer SKU.
- Reference image (jpg/png) showing the target aesthetic. Stored at
<project>/source/references/<ref>.jpg.
- Brand wordmark PNG (high-res, transparent). If not provided, fetch from brand's CDN (e.g.
touchland.com/cdn/shop/files/Touchland_Logo__1.png).
- Headline copy (string, ≤5 words). E.g. "WHICH ONE ARE YOU?", "MATCH YOUR MOOD".
- Outlined-word index — which word in the headline gets the hollow/outlined treatment (AMINU's signature). Usually the second word or the pivot word.
- Micro-label per product — handwritten-style labels, one per SKU. E.g. "main character" / "soft girl" / "after dark".
- Background gradient — two-stop vertical gradient (top color → bottom color). Default: warm blush-cream (
#FFF1ED → #FCDDD8) for warm-leaning brands; soft sky-blue (#E8F0FA → #D6E5F5) for cool-leaning.
- Output target dimensions (default 720×1280 to match MS clips; design at 1080×1920).
- Output duration (default 3s for end-card overlay; can extend to 5-6s for standalone use).
Workflow
Step 1 — Source / verify reference image
If user-supplied, save to <project>/source/references/<ref>.jpg.
If sourcing fresh:
- Pinterest search "skincare ad layout" / "GenZ product flat-lay" / "scent wardrobe"
- Brand's competitor ads
- Magazine ad scans (i-D, Glossier, Cosmetics Insider)
Look for: massive sans headline + product arrangement + handwritten/script micro-labels. The reference is the visual contract — your output should match its grammar.
Step 2 — Extract aesthetic specs from the reference
Document in <project>/working/endcard/notes.md:
- Headline font weight + size relative to canvas (e.g. "Inter 900 at ~210px on 1920-tall canvas").
- Outline treatment (stroke width, transparent fill — common:
-webkit-text-stroke: 5px <ink>; color: transparent;).
- Label font + size (handwritten: Caveat 500 ~84px is the AMINU-equivalent).
- Background palette (sample with a color picker).
- Product layout pattern (diagonal cascade, top-down flat-lay, horizontal row).
- Wordmark placement (centered footer, top-left, bottom-right).
Step 3 — Generate Layer 1 (NB2 product flat-lay)
higgsfield generate create nano_banana_2 \
--prompt "<product flat-lay description per template below>" \
--aspect_ratio "9:16" \
--resolution "2k" \
--image <product1.jpg> --image <product2.jpg> ... \
--wait --wait-timeout 5m \
--json > layer1_result.json
Prompt template (no text):
Editorial top-down flat-lay product photograph of <N> <brand> <product family>
arranged casually on a <background description>. Bottle 1 with <color1 description>
tilted at a 20-degree angle in the <position1>, bottle 2 with <color2 description>
standing upright in the <position2>, ... [continue per product]. The <N> bottles
overlap slightly but each is clearly visible, with soft diffused shadows pooling
beneath them on the <surface>. All bottles have <shared physical features> and the
'<wordmark>' plus '<sub-line>' text on each label exactly matching the imported
product references. Pinterest-style flat-lay, editorial product photography,
soft natural overhead daylight, clean and uncluttered, lots of negative space at
the top of the frame. No text overlays, no typography, no captions, no logos
other than what is printed on the bottles.
Critical phrasing:
Pinterest-style flat-lay, editorial product photography — invokes the visual grammar.
clean and uncluttered, lots of negative space at the top of the frame — leaves room for the typography overlay.
No text overlays, no typography, no captions — keeps NB2 out of the text business.
Cost: ~$0.08 per attempt. Budget 2-3 rerolls if first attempt has wrong angles or label drift. Validated on Touchland Trio 1: NB2 nailed the 3-bottle composite on the first try.
Download the resulting PNG to <project>/working/endcard/layer1.png.
Step 4 — Build Layer 2 (HTML hyperframe)
Create <project>/working/endcard/endcard.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;800;900&family=Caveat:wght@500;700&display=swap" rel="stylesheet">
<style>
:root {
--ink: #1a1a1a;
--ink-soft: rgba(26,26,26,0.65);
--bg-top: #FFF1ED;
--bg-bot: #FCDDD8;
}
* { box-sizing: border-box; }
html, body {
margin: 0; padding: 0;
width: 1080px; height: 1920px;
background: linear-gradient(180deg, var(--bg-top) 0%, var(--bg-bot) 100%);
font-family: 'Inter', sans-serif;
: (--ink);
: hidden;
}
{
: absolute; : ;
: ;
: ();
: cover;
: center;
}
{ : absolute; : ; : ; }
{ : absolute; : ; : ; : none; }
{
: absolute;
: ; : ; : ;
: ; : ;
: ; : -;
: uppercase;
: ;
: ();
: headlineIn ease-out forwards;
}
{
-webkit--: (--ink);
: transparent;
}
{
: absolute;
: , cursive;
: ; : ;
: (--ink);
: nowrap;
: ;
: fadeIn ease-out forwards;
}
{
: none;
: (--ink);
: ;
: round;
: round;
: ;
: ;
}
{
: absolute;
: ; : ; : ;
: center;
: ;
: ();
: footerIn ease-out forwards;
}
{
: block; : auto ;
: ; : auto;
}
{
: ; : ;
: ;
: (--ink-soft);
: uppercase;
}
headlineIn { { : ; : (); } }
fadeIn { { : ; } }
draw { { : ; } }
footerIn { { : ; : (); } }
WORD1
WORD2 WORD3
WORD4?
brand.com
Animation timeline (3s total)
| t | event |
|---|
| 0.0–0.2s | Static base (product layer visible) |
| 0.2–0.7s | Headline rises into place |
| 0.75 / 1.05 / 1.35s | Labels fade in (staggered ~0.3s apart) |
| 0.95 / 1.25 / 1.55s | Arrows draw via stroke-dashoffset (one per label) |
| 1.85–2.35s | Footer (wordmark + URL) rises into place |
| 2.35–3.0s | Hold final state |
Arrow positioning (critical detail)
Each arrow's <path d="..."> is a Q-curve from "just after the label text" to "just outside the bottle's edge":
M <label_end_x> <label_end_y> Q <control_x> <control_y> <bottle_edge_x> <bottle_edge_y>
The arrowhead (marker-end) renders AT the path's endpoint. To avoid the arrowhead overriding the bottle:
- For a left-side label pointing UP-RIGHT to a top-right bottle: endpoint should be ~15-30px LEFT of the bottle's left edge.
- For a right-side label pointing DOWN-LEFT to a middle bottle: endpoint should be ~15-30px RIGHT of the bottle's right edge.
Validate by rendering the final frame and inspecting each arrow visually. If an arrowhead sits ON or IN a bottle, shorten the path's endpoint by 30-50px.
Step 5 — Render Layer 1+2 composite
CRITICAL: Render at DESIGN dimensions (1080×1920), then scale down. Do NOT render at output dimensions — Playwright will clip the right 33% of the canvas.
python3 skills/atoms/end-cards/create-end-card-html/scripts/render_hyperframe.py \
endcard.html endcard_3s_1080.mp4 3 \
--fps 24 --width 1080 --height 1920
ffmpeg -y -i endcard_3s_1080.mp4 \
-vf "scale=720:1280:flags=lanczos" \
-c:v libx264 -pix_fmt yuv420p -crf 18 -preset medium -an \
endcard_3s.mp4
Step 6 — Verify the final frame
ffmpeg -y -i endcard_3s.mp4 -ss 2.7 -frames:v 1 endcard_final.jpg
Read endcard_final.jpg and check:
If any check fails, fix the HTML/CSS and re-render. Do NOT re-render NB2 unless the product layer itself is wrong.
Output
<project>/working/endcard/
├── endcard.html ← HTML source
├── layer1.png ← NB2 product flat-lay
├── brand_logo.png ← official brand wordmark
├── reference.jpg ← (optional) reference image used
├── endcard_3s_1080.mp4 ← intermediate 1080×1920 render
├── endcard_3s.mp4 ← final 720×1280 mp4 (use in master)
└── endcard_final.jpg ← QC still
Quality Checks
endcard_3s.mp4 exists, has duration ~3.0s ±0.05s, dimensions match target.
endcard_final.jpg shows all required elements (headline, N labels, N arrows, wordmark, URL).
- No element clipped by the canvas edge.
- No arrow tip inside any product geometry.
- Animation timeline plays smoothly (no z-fighting, no janky transforms).
- Brand wordmark is the OFFICIAL wordmark (sourced from brand's CDN), not text-rendered approximation.
Failure Modes
- Headline clipped on right edge. Root cause: rendered at output dimensions instead of design dimensions. Fix: render at 1080×1920, scale down via ffmpeg.
- Arrowhead inside bottle. Path endpoint too far into the canvas. Shorten endpoint by 30-50px.
- Label overlapping bottle. Reposition the label outside the bottle's bounding box.
- NB2 output has text on the bottles. Prompt is ambiguous — ensure "No text overlays, no typography, no captions" is in the prompt.
- NB2 bottles in wrong positions. Re-roll NB2 with more explicit per-bottle position instructions.
- Font not loading. Google Fonts is the standard source — ensure the
<link> tag is present and Playwright has network access at render time. Wait 500-800ms after page load for fonts to settle.
- Animation not firing in screencast.
render_hyperframe.py walks document.timeline.currentTime. CSS keyframe animations with explicit animation-delay should work. If not firing, capture in real-time mode (page.wait_for_timeout(<total_anim_duration_ms>)).
- Brand wordmark looks wrong / pixelated. Source from brand's CDN at highest available resolution; ensure transparent PNG.
Cost + time
| Item | Cost | Wall time |
|---|
| NB2 product layer | ~$0.08 (1-3 rerolls) | 2-9 min |
| HTML scaffolding | free | 15-30 min (first time); 5 min (template reuse) |
| Render at 1080×1920 + scale | free | 30 sec |
| Frame QC | free | 1-2 min |
| Total | ~$0.08-0.24 | ~25 min first time, ~10 min subsequent |
References
- Validated 2026-05-20 on Touchland Trio 1 (AMINU skincare ad as reference, 3-bottle composite, "WHICH ONE ARE YOU?" headline with "ONE" outlined, handwritten labels "main character" / "soft girl" / "after dark").
- Reference image saved:
touchland/Could skincare BE any simpler_.jpeg.
- Per-project case study:
touchland/video-03-third-place-tour/working/endcard/.
- Related atoms:
create-end-card-html (the underlying renderer script)
create-end-card-branded (alternative for non-reference workflows)
create-end-card-hyperframes (motion-graphics variant)
- Related memories:
feedback_endcard_reference_pattern.md
feedback_render_at_design_dim.md