Originally validated in content-goose at
clients/som-sleep/video-01-value-prop-vp-swap/. Adapt paths to your project.
Validated on clients/som-sleep/video-01-value-prop-vp-swap/. Read that project's
HOW_TO_MAKE_THIS_VIDEO.md and LEARNINGS.md for the canonical reference run.
Purpose
Produce a 9:16 vertical Value Prop ad where 3-5 short noun-phrase claims are
revealed sequentially over per-SKU product visuals. The text + product carry the
spot; sound-off legibility is required. Default music bed (instrumental, -14dB)
per user preference; format DNA says silent but cinematic/product short-form
defaults to music per feedback_music_with_vocals_default.md.
When to use
- Brand has ≥3 SKU variants (flavors / scents / styles) with clean product photos
- Brand has 3-5 stackable noun-phrase claims (≤4 words each)
- Target placement: Reels / Stories / TikTok / Meta vertical
- Tier 1/5 complexity (simplest paid-social format)
Don't use if:
- 1 SKU only → use VP-ACCUM (anatomy diagram approach; decision rule below)
- Strong UGC available → use VP-LIVE (labels over live action; decision rule below)
- Needs VO narration → use
create-vo-anchored-beat-videomolecule - Lifestyle/UGC-led → use
recreate-ugc-ad-from-source
Inputs
Required:
brand_url— homepage URL, used for autonomous scrape
Optional (autonomous defaults if omitted):
aspect_ratio—9:16(default) |1:1|4:5duration_s— default17(range 10-20)sub_archetype—auto|VP-SWAP|VP-ACCUM|VP-LIVEvalue_props[]— 3-5 items{label: "≤4 words", benefit_sentence?: "≤12 words"}. If omitted, scraped autonomously.hook_line— opening tagline (default: scraped from brand H1)tagline— end-card line (default: scraped, or reused as hook)brand_color_hex,brand_font— scraped if omittedmusic_brief— text prompt for ElevenLabs Music API. Default: brand-appropriate ambient instrumental at -14dB. Passnullto ship silent.vo_script— if provided, generates VO via ElevenLabs (eleven_v3+ atempo=1.20). Ducks music to -22dB. Default: no VO.animation_preset— brand-register-mapped preset name (sleep_wellness,sport_active,energy_cpg,luxury_editorial,tech_active,premium_clinical) ORauto(default — molecule infers from scraped tokens) OR explicit per-layer overrides (seereferences/animation-library.md).
Composed Atoms
atoms/source/source-brand-assets— logo + hero photos at full CDN resolution (scrape)atoms/source/source-company-existing-ads— Meta Ad Library for prop copy inspiration (optional, scrape)atoms/asset-preparation/remove-backgrounds— clean product cutoutatoms/product-images/create-product-images-higgsfield-product-photoshoot— hero if scrape fails (optional)atoms/end-cards/create-end-card-from-reference— Pinterest + NB2-edit lifestyle composite (Phase 6)atoms/motion-graphics/create-motion-graphics-hyperframes— beat HTML + Playwright renderatoms/music/create-music-elevenlabs— ambient instrumental bedatoms/voiceover/create-voiceover-elevenlabs— only ifvo_scriptprovided (optional)atoms/audio-editing/mix-master— VO + music + duckingatoms/assembly/stitch-videos-ffmpeg— concat beat clips + mux audioatoms/review/watch— self-QC midframe extract + audio levels
Do NOT use: Klap captions (text is burned in, not subtitled), Soul ID, UGC-creator atoms.
Workflow
Phase 0 — Autonomous brand intake
Scrape from brand_url:
- Logo —
header img[alt*="logo"]→ fetch without?width=Nquery param (Shopify default-resolution is1644×939-ish; with width param caps at served thumbnail size). Verify via PIL. - Brand colors —
getComputedStyle(body)forbackground-color+color;:rootfor--color-primary,--color-button, etc. - Brand font —
getComputedStyle(h1).fontFamily+ body font - Tagline — homepage
<h1>or hero copy (verbatim) - Value props (5-source merge):
- Homepage meta description
- Homepage feature row
- PDP "Features" / "What's Inside"
- Footer trust strip (NSF, USDA Organic, etc.)
- Stick-pack / package label copy
- Per-SKU PNGs — find variety-pack image on PDPs (4-pack composite is common), then PIL bounding-box detect each SKU column:
arr = np.array(img.convert('RGB'))
is_non_white = np.any(arr < 240, axis=2)
col_nonwhite = is_non_white.sum(axis=0)
in_pack = col_nonwhite > 50
# Find contiguous runs → pack bounding boxes → crop with ~12px padding
Output <project>/brief.md with provenance + recommended 5-prop set + sub-archetype recommendation + estimated spend.
🚦 GATE 1 — Brief + asset inventory + animation choice
User can edit:
- Prop list / hook / tagline / sub-archetype
- Animation preset —
brief.mdMUST present the auto-picked brand-register default + the 5 alternative presets with one-sentence descriptions, so user knows what's possible. Render a preview thumbnail per preset on a single prop beat (one-time-rendered ships with skill atshared/animations/_previews/). User picks: keep default, swap preset, or per-layer override (e.g. keep sachet entrance, change text entrance).
See references/animation-library.md for full preset catalog.
Phase 1 — Storyboard + preview hyperframes
- Pick sub-archetype (see Decision Rule 2)
- Author
shot-list.yml(7 beats: hook + 5 props + endcard) - Render preview hyperframes at 540×960 (50% res)
- Compile
storyboard.htmlgallery
🚦 GATE 2 — Storyboard approval
User can request copy / layout / accent changes.
Phase 2 — Render + audio + upload (autonomous)
- Render each beat at 1080×1920 @ 30fps via
render_hyperframe.py - Concat with ffmpeg + silent stereo audio placeholder
- Generate music bed via ElevenLabs Music API (
fal-ai/elevenlabs/music,force_instrumental: true, $0.04) - Mix music in at -14dB with 0.4s fade-in + 0.6s fade-out
- (Optional) Generate end-card composite via Pinterest + NB2-edit (Phase 6 of HOW_TO doc)
- Self-QC: ffprobe + volumedetect + midframe extract
- Upload to gooseworks for shareable URL
Decision Rules
-
Format is silent by default (per format DNA — GEKKS/GREATS/INCREDIWEAR all -91dB). User-preference override: add music bed at -14dB. Reject VO unless
vo_scriptexplicitly provided. -
Sub-archetype selection:
- VP-SWAP if brand has ≥3 SKUs (Som, GEKKS-style). Per-SKU PNGs cropped, hero+supporters layouts.
- VP-ACCUM if 1 SKU + anatomy story (GREATS-style "anatomy of X" diagram with accumulating callout labels).
- VP-LIVE if good product-in-use UGC available OR brand approves Seedance i2v generation (~$0.48 for 10s).
-
Prop count: 3-5. Never <3, never >7. If brand has more, pick the most differentiating.
-
Prop word cap: ≤4 words. Compress aggressively (e.g. "NSF Certified for Sport" stays, "Independently Tested by Pro Athletes" → "Pro-Team Tested"). Reject if compression loses meaning.
-
Benefit sentence (VP-SWAP only): ≤12 words. Optional. Skip if compression awkward.
-
Uniform pacing. Hook 3.0s, props 2.0-2.5s each (uniform within a run), endcard 2.0s. No acceleration curve.
-
No human face is focus. If hero image has a model's face, auto-crop to torso-down or product-only.
-
Display headline cap: ~100-120px for 9:16. Bristle reference is 96px. For supplement/wellness register, 84-112px. Don't exceed 130px — reads as "magazine confident" not "calm authority."
-
Per-flavor color accents on RULES, not headlines. Yellow/light-color HEADLINES on white = low contrast. Use the per-flavor color as the 4px×72px accent rule under the eyebrow. Headlines stay in
--inknavy. -
Brand wordmark threading: optional. GEKKS threads "yekks" through every frame; Som doesn't need to (per-pack wordmarks carry attribution). Audit fit per brand. Default: skip wordmark threading on prop beats, lean into the per-SKU wordmark on the canvas instead.
-
End card: brand wordmark sourced from CDN without
?width=Nparam. Verify resolution ≥ 1200×600 via PIL after download. No chromatic aberration / RGB-shift effects — they read as pixelated noise on a high-contrast wordmark. -
Animation architecture:
initRenderer(duration, renderFn)from_shared.js. Pure function of beat-local timet. Never CSS keyframes, never setTimeout. Helpers:tw,tw_lin,easeOut,springScale,revealWords. (Architecture proven on Bristle + Everself v4.) -
Per-sachet PNGs when ≥3 SKUs. PIL bounding-box detection on variety-pack source. Crop with ~12px padding. Don't reuse flat 4-pack image as canvas for every beat — reads as same-image-shifting.
-
Layout variation per beat: alternate hook → row → hero → hero → hero → row → endcard. Rotate which sachet is the hero (Berry → Cherry → Mango → Tangerine). Eye anchor must shift.
-
Silent audio track via
anullsrcmono is mandatory even if no music — some platforms reject videos with no audio stream. -
Music mix:
-14dBdefault,normalize=0(perfeedback_ffmpeg_amix_normalize), explicit-map 0:v -map 1:a(perfeedback_ffmpeg_map_directive). Verify final audio bit_rate ≥ 100kbps via ffprobe. -
NB2-edit (end-card optional layer):
- Pre-crop source to target aspect before upload (NB2-edit preserves source aspect)
- Explicit photoreal-compositing language in prompt: "PHYSICAL OBJECTS IN THIS SCENE, not pasted overlays"
- Pass
aspect_ratio: "9:16"explicitly - Test ONE first before batch firing (pipeline gate per
feedback_media_pipeline_gates)
-
Pipeline gates (per
feedback_media_pipeline_gates):- Prompt-review before EVERY paid model call
- Still-review before video gen
- Clip-review before composite
- User can override case-by-case ("fire all 3 anyway") but must do so explicitly.
-
Animation library is registry-pattern, with user choice surfaced at Gate 1. Animations live in
shared/animations/{sachet-entrance,text-entrance,transitions}/<id>.jsas JS modules matching the(t, elements, params) → voidcontract.- Default: brand-register-matched preset (see
references/animation-library.md). Inferred from scraped tokens (font genre, color saturation, claim word energy, category). - Gate 1 surfaces choices:
brief.mdMUST list the auto-pick + 5 alternative presets with one-sentence descriptions + preview thumbnails. User must KNOW what's possible. - Per-layer override: user can swap one layer (e.g. keep sachet, change text) without forcing a full preset swap.
- v1 ships layers 1+2 (per-beat entrance + inter-beat transition).
- Layer 3 (continuous sachet state for carousel/conveyor patterns) is documented
as v2 — requires unified-timeline rendering. See
animation-library.md§v2. - Validated presets in v1:
sleep_wellness(Som v5),sport_active(Som v6). Others scaffolded but need brand-validation.
- Default: brand-register-matched preset (see
Output
<project>/
├── brief.md ← Gate 1 review
├── shot-list.yml ← beat manifest
├── storyboard.html ← Gate 2 review (gallery of previews)
├── HOW_TO_MAKE_THIS_VIDEO.md ← per-project recipe
├── LEARNINGS.md ← run-specific notes
├── source/
│ ├── logo-<brand>.png ← from CDN, no width param
│ ├── sachets/{sku-1,sku-2,…}.png ← PIL-cropped
│ ├── scraped-props.yml
│ ├── brand-identity.yml
│ └── end-card-references/ ← Pinterest sourcing (optional)
├── assets/
│ └── hyperframes/ ← previews + final renders
├── working/
│ ├── hyperframes-v1/{_shared.css, _shared.js, beat-*.html}
│ ├── beat-clips-v1/
│ └── audio/music-raw.mp3
├── finals/
│ ├── master-v1-clean.mp4 ← silent
│ ├── master-v1-with-music.mp4 ← shippable
│ └── master-v1-with-vo.mp4 ← if VO requested
└── archive/ ← prior versions
Quality Checks
- Length within 10-20s range (default 17s)
- Prop count 3-5
- Every prop label ≤4 words
- Optional benefit sentence (VP-SWAP) ≤12 words
- Every prop displayed ≥2.0s
- Per-SKU PNG canvas (≥3 SKUs) — no flat composite reused as canvas
- No human face is focus
- Audio track exists, bit_rate ≥100kbps (verify with
ffprobe) - Music mean volume between -25dB and -34dB (background level)
- End card wordmark resolution ≥1200×600
- No chromatic aberration on end card
- No CSS keyframes /
setTimeoutin beat HTMLs (animation only viarenderAt(t)) - Per-flavor accent rule color matches packaging
- Headline color =
--ink(not flavor color) - Aspect ratio matches target
- Gooseworks share URL generated and tested
Failure Modes
| Failure | Cause | Recovery |
|---|---|---|
| Brand homepage has no value props | Storytelling brand without claim-stack marketing | Pivot to PDP bullets / Amazon listing / Meta Ad Library. Surface at Gate 1 if all sources fail. |
| Props all >4 words after compression | Brand uses sentence-form marketing | Best-effort LLM compression with strict ≤4-word rule. Surface at Gate 1 if compression loses meaning. |
| Variety-pack image not found | Brand has no composite SKU image | Source per-SKU hero PNGs individually from PDPs. Fall back to higgsfield-product-photoshoot only if grounding rules approved. |
| Per-SKU PIL crop produces wrong boxes | Multi-pack source has unusual layout (overlapping, diagonal) | Manual crop coordinates. Surface intermediate PNGs at Gate 1. |
| Headline overlaps hero sachet (9:16 + hero layout) | Hero sachet sized too tall | Cap hero width at 280px (≈1090px tall on 1620:416 aspect). Push down with bottom: 170px. |
| End-card wordmark pixelated | Logo source served at thumbnail size by Shopify CDN | Re-fetch without ?width=N parameter. Verify ≥1200×600 via PIL. |
| Audio bit_rate 1 kbps after mux | Missing -map 0:v -map 1:a in ffmpeg call |
Re-mux with explicit map directives. Per feedback_ffmpeg_map_directive. |
| Music too loud / overpowers visuals | Default normalize=1 in amix ate the volume filter |
Set normalize=0 in amix. Per feedback_ffmpeg_amix_normalize. |
| ElevenLabs music tapers in 2nd half | Known API behavior (feedback_elevenlabs_music_decay) |
For >15s clips, generate 10s + loop+flatten via acrossfade + acompressor + loudnorm. |
| NB2 returns 2-panel composition | Prompt too generic | Add "Output a single image, not a comparison panel" + explicit aspect_ratio param. Re-roll. |
| NB2 wordmarks drifting | Reference image not pre-cropped to target aspect | Pre-crop source via ffmpeg crop+scale to 1080×1920 before upload. |
| Whisper mishears VO ("Bristle" → "Bristol") | atempo'd / cloned voice with brand-specific vocab | Pass initial_prompt with brand terms to Whisper (per feedback_whisper_asmr_sped_audio). |
References
references/format-dna.md— extracted DNA from GEKKS/GREATS/INCREDIWEAR (the 3 corpus refs)references/gekks-design-teardown.md— frame-by-frame design language (the actual creative brief)references/typography-rules.md— display ladder + Fontshare options + brand-register match criteriareferences/layout-variation-rules.md— per-beat composition rotationshared/_shared.css+shared/_shared.js— Bristle architecture (validated)shared/beat-templates/{hook-sticker,prop-row,prop-hero,endcard-wordmark}.html- Validated run:
clients/som-sleep/video-01-value-prop-vp-swap/ - LEARNINGS:
clients/som-sleep/video-01-value-prop-vp-swap/LEARNINGS.md - Recipe:
clients/som-sleep/video-01-value-prop-vp-swap/HOW_TO_MAKE_THIS_VIDEO.md
Related memories
feedback_use_skill_library.md— scan atoms before writing custom scriptsfeedback_image_grounding_rules.md— NB2 may only edit real images; never gen productsfeedback_media_pipeline_gates.md— prompt-review before EVERY paid callfeedback_render_hyperframe_paths.md— Playwright + relative asset pathsfeedback_explainer_no_css_keyframes.md— initRenderer architecture mandatoryfeedback_ffmpeg_map_directive.md+feedback_ffmpeg_amix_normalize.md— audio mux hygienefeedback_music_with_vocals_default.md— user preference: music default for product short-formfeedback_elevenlabs_v3_naturalness.md+feedback_v3_audio_tags_color_read.md— VO settings stackfeedback_elevenlabs_music_decay.md— loop+flatten for longer music bedsfeedback_video_review_pattern.md— present local + gooseworks URL togetherreference_fal_elevenlabs.md— FAL endpoint for ElevenLabs music