| name | notification-flood-ad |
| description | Produce a 4–6s vertical iOS lockscreen "notification avalanche" clip — N notification banners from one brand cascade onto the lockscreen, faster than the screen can hold them, with a procedural CSS-only tear/glitch overlay at the climax. Output is the avalanche MP4 as a drop-in mechanic — concat it into any ad's payoff. Sibling of create-imessage-video-ad — use this when the punchline is the *volume* of notifications, not a chat thread. |
notification-flood-ad
Use this skill when an ad concept calls for a "lockscreen avalanche" — an iPhone lockscreen filling up with N notifications from one app, sold by sheer volume and pacing. The implicit CTA is the app's icon repeating across every banner.
Scope (read first)
This molecule produces just the lockscreen avalanche MP4 — clips/master-flood.mp4. The deliverable is the notification mechanic by itself, NOT a full ad. The agent stitches it into a larger ad in a downstream pass (with whatever hook clip, end card, or audio mix the brief calls for).
If the orchestrator hands this molecule a hook clip, treat the hook as reference / inspiration, not as material to glue. The molecule never decides the ad's structure — it produces a reusable building block.
The "optional concat" section near the bottom describes how to stitch hook+flood as a follow-up step, but that stitch is the agent's responsibility (or another molecule's), not this one's.
Reference build
Originally validated in content-goose at clients/Yuba/ad-runs/run-02-run2/. Adapt paths to your project.
clients/Yuba/ad-runs/run-02-run2/ — 9s vertical Yubo ad with IRL park hook (couple on a picnic blanket, meme caption) → lockscreen avalanche of ~18 Yubo notifications → blue tear overlay sweeps across as the stack overflows.
Purpose
Stitch a hook clip + an animated lockscreen avalanche + (optional) end card into a single shippable 9:16 master MP4. The lockscreen animation is driven by a TIMELINE inside a single continuous Playwright recording — same architecture as create-imessage-video-ad, but driving the render-ios-lockscreen atom instead of create-imessage-mockup.
When to use
- Ads selling popularity / FOMO / demand ("you'll have so many matches/messages/orders you can't keep up")
- Dating-app / social-app ads where volume = product proof
- Marketplace / job-board ads where the punchline is "they keep coming"
- Any time the brief mentions "phone blowing up", "notification flood", "lockscreen full of"
If the punchline is a single message reveal (FREEPACK code, screenshot of a result), use create-imessage-video-ad instead. If the brief is "person realizes / scrolls / discovers", use a chat thread or hyperframe, not a lockscreen avalanche.
Inputs
brief (required) — what the ad is selling, the app/brand, the volume that makes the joke land (typically 15–25 notifs).
output_folder — <brand>/ad-runs/run-NN-<slug>/ (usually preset by the orchestrator).
brand — { name, app_icon_path }. app_icon_path is the real square PNG of the app icon (see auto-memory: end-card-needs-real-product). For Yubo: clients/Yuba/brand-assets/app-icon.png.
sender_roster — list of { name, avatar_src? }. The molecule shuffles + animates each banner from this list. 15–25 entries is the sweet spot. If avatar_src is omitted, the atom generates a procedural monogram avatar.
wallpaper (optional) — { type: "ios-default" } (recommended — gives the real deep-navy iOS feel), or a custom gradient/image. Defaults to ios-default.
tear (optional) — set decoration.tear: { rotate: -10 } to enable the procedural CSS-only tear/glitch sweep at the climax. No PNG asset needed.
Composed Atoms
skills/atoms/messaging/render-ios-lockscreen — generates the lockscreen HTML (insert-flood mode, light frosted iOS-16 cards, the .arrive width-expand keyframe); this molecule drives it via Playwright. This is the load-bearing atom — most of the visual fidelity comes from it.
skills/atoms/assembly/stitch-videos-ffmpeg — (downstream) concat the flood with a hook clip (+ optional end card) into the full ad in a follow-up pass.
- (optional) iOS notification SFX (CC0). Reuse
skills/molecules/create-imessage-video-ad/assets/sfx/imessage-receive.mp3 — it's already loudness-normalized. The flood needs roughly one cue every 100–250 ms but mixed quiet (~ -16 LUFS) so the cumulative loudness doesn't peak. Set volume=0.40,alimiter=limit=0.95.
Critical knowledge — read before producing your first ad
These are mistakes that have already been made elsewhere in this skill family. Do not re-introduce them.
0. The volume IS the CTA — do not weaken it with copy
Adding "Download Yubo" overlay text or a hard CTA card during the avalanche kills the joke. The avalanche works because the viewer's brain finishes the sentence ("that many people on this thing?"). End card belongs AFTER, not during. The avalanche itself carries no overlay text — only the lockscreen UI.
1. Real app icon, not styled CSS or AI
Per auto-memory feedback_end_card_real_product: pull the actual square PNG from the App Store icon (1024×1024) or the brand press kit. CSS/AI approximations look amateur even at 38×38. The render-ios-lockscreen atom takes the icon path verbatim and uses it as the banner's left tile — point it at the real file.
2. Single continuous Playwright recording
Same rule as create-imessage-video-ad knowledge #3: record the WHOLE lockscreen animation as ONE continuous Playwright session, never per-scene with reloads. Every reload causes a micro-flicker, and faking the "stack overflow" by removing banners between scenes looks janky.
Architecture (scripts/record-master.template.js, copy to the run's clips/):
- Render the lockscreen HTML in
insert-flood mode (atom builds an empty
.notif-pool + a hidden .notif-source pool of all banners), on a 9:16
config.canvas zoomed ×3 (crisp text, fills 1080×1920).
- An embedded driver pre-fills a few banners, then on each
ARRIVALS[t] clones a
source banner into the pool at slot 0 with the .arrive class (width-expand)
and bumps every existing banner's --slot one row down (instant reposition).
ARRIVALS (steady 0.333s) is the single source of truth for the schedule AND
the SFX cue list. Record at 3× then ffmpeg-downscale to 1080×1920; mux the
receive-SFX per cue; clamp the output to the video length (-t) so the last
SFX tail can't leave a frozen frame.
3. Pacing — STEADY ~0.333s cadence (measured from the reference)
This was re-derived frame-by-frame from the reference ad (inter-frame motion
analysis on the actual mp4). The flood is dead steady: one notification every
~0.333s (≈10 frames at 30fps), ~11 of them. It is NOT a burst, NOT
accelerating, NOT decelerating. An accelerating/uneven cadence reads as wrong —
the operator rejected it repeatedly until it was set to a flat 0.333s.
Set ARRIVALS in record-master.js to evenly-spaced 0.333s steps. Verify by
re-measuring the render the same way (tblend=difference → spike timestamps → the
gaps should all be ~0.33s).
4. The stack grows DOWN — newer banners on top, older pushed down
iOS visually stacks the newest notification at the top of the lockscreen's notification area, with older ones sliding down (or condensing). The atom matches this convention: notifications[0] renders at the top. The molecule must prepend each new banner to the DOM stack as it pops in, or pre-render them all and reveal in reverse order. The reference avalanche does the latter (simpler + deterministic).
5. Banner ARRIVAL geometry — WIDTH-EXPANSION, not a slide, not a pop
This is the single hardest detail to get right; it took many iterations. The
correct arrival (verified against reference frames 8→9→10) is:
- The new banner appears at the TOP slot narrow (reduced width, ≈80%) and
expands horizontally to full width as it settles down into place over
~5 frames (~150ms). It's a
scaleX (width) animation + a tiny downward settle.
- It is NOT a uniform scale-pop, and NOT a slide-in from above.
- The existing banners below must reposition INSTANTLY (the gap opens in one
frame). If they slide down over ~90ms, the displaced banner is caught sliding
THROUGH the incoming one and they visibly stack on top of each other — the
operator explicitly rejected this. Only the new banner animates.
This lives in the atom CSS (render-ios-lockscreen/templates/lockscreen.css):
the .insert-flood-mode .notif has transition: none (instant reposition) and
the incoming banner gets the .arrive keyframe (translateY(-9px)→0 +
scaleX(0.80)→1.0). The molecule's driver adds the .arrive class to each
cloned banner on insert and sets every existing banner's --slot one row down.
Implementation note (do not re-trip): driving the scale via a CSS custom property
transition did NOT animate in the headless renderer — the keyframe approach
is required.
6. Visual look — real iOS-16 material; NO tear, NO emoji band by default
The Yubo reference uses none of the decorative overlays that earlier drafts of
this skill recommended. Match the real iOS-16 lockscreen instead:
- Full-bleed 9:16: author the lockscreen on a 9:16 canvas (
config.canvas)
and zoom it ×3 so it fills 1080×1920 — NO pillarbox bars, NO horizontal stretch
(rendering at true iPhone 19.5:9 and padding into 9:16 caused side black bars).
- Cards = light frosted material:
rgba(245,245,245,0.55), backdrop-blur 40px, radius 23px, black text (from the iOS-16 UI Kit Figma). Dark
translucent banners with white text read washed-out over a wallpaper.
- No
decoration.tear and no headerOverlay emoji band unless the brief
explicitly asks. The operator rejected both as unrequested noise. The volume of
the flood is the whole effect (knowledge #0).
- Pure brand flood: drop any filler Mail/Messages rows; use unique sender names.
7. End card is static — no Ken Burns, no zoompan
Same as iMessage molecule knowledge #5. If using an end card, render it as a single PNG with the real wordmark + app icon (or just the wordmark on a colored slate), then ffmpeg with -loop 1 -t 1.8 and crossfade in over 250 ms. Drifting end cards undercut the punch.
8. Hook clip MUST be a real-feeling moment
The hook is the trust-building beat. AI-generated UGC stand-ins for the hook are a known failure mode for this format — see auto-memory feedback_multi_cut_ai_supplement_ads. Either:
- Use real footage the operator provided (IRL, UGC, found content with rights cleared), OR
- Use a single-take talking-head clip from
create-ugc-heygen / lipsync molecules
Do NOT generate the IRL hook scene with image-to-video. The avalanche payoff already pushes the viewer's "is this AI?" detector — making the hook AI too breaks the format.
9. Audio bed: silence under the hook, soft pad under the flood
Keep the hook clip's native audio (ambient + meme caption silence). For the avalanche, layer:
- A low pad (~-22 LUFS) — see
references/audio-recipes.md for a free CC0 pad
- The receive SFX cues at
volume=0.35 (quiet — there are 15–25 of them)
- A single louder "thunk" cue on the tear overlay reveal at
volume=0.7
If the hook clip has dialog, duck the avalanche audio down by 6 dB until the hook ends.
Workflow
State 0 — Brand sanity check
- Confirm the brand wants a volume-as-CTA angle. If they want a clear feature explanation, use a different format.
- Confirm the real app icon PNG exists in the brand-assets folder. If not, pull it before proceeding (App Store
apps.apple.com/.../id<APP_ID> → screenshot of the icon, or the brand press kit).
- No competitor-bashing (auto-memory:
feedback_no_competitor_bashing_in_ads).
State 1 — Lock the hook clip
- If the operator provided one, validate: 2–4s, vertical, meme caption already burned, no other on-screen text.
- If not, brief them: hook needs to land the premise in <3s before the lockscreen reveal. Don't generate a fake one with i2v.
State 2 — Sender roster
- 15–25 names. The molecule shuffles them and assigns a slide-in time per banner via the TIMELINE.
- Avatars are optional; without them, the banners render as plain
Yubo rows (which still works visually).
- Names should fit the app's demo: a dating-app flood needs first names + age-appropriate; a job-board flood needs role keywords ("Recruiter at …").
State 3 — Lockscreen config
- Build
production/lockscreen-base.json matching the render-ios-lockscreen atom's schema.
- Fields the molecule fills in deterministically from inputs:
notifications[], decoration.tear, wallpaper.
- Operator-tunable:
clock.time, clock.date, headerOverlay.text.
State 4 — TIMELINE
- In
scripts/record-master.js, the TIMELINE array is the single source of truth for both the recording and the SFX cue list.
- Use the cadence rules in critical knowledge #3.
- 18–22 banners is the canonical size; below 12 the joke doesn't land, above 28 it stops escalating.
State 5 — Record (this is the molecule's deliverable)
NODE_PATH=<repo>/skills/atoms/messaging/render-ios-lockscreen/node_modules \
node clips/record-master.js
clips/master-flood.mp4 is the molecule's output. Register it in production/asset-manifest.json with role: "active_master".
State 6 — Review
Open the Final tab; play clips/master-flood.mp4 against the brief. Re-time individual events by editing TIMELINE; re-run the recorder; done.
State 7 (downstream, optional) — concat with a hook clip
If the agent wants to glue this flood into a full ad, the scripts/stitch.template.sh reference in this skill folder shows how to concat a hook clip + flood + end card with deterministic SFX. That stitch is NOT part of this molecule's responsibility — it's a separate pass run by whatever skill owns the ad's overall structure.
Decision Rules
| Question | Rule |
|---|
| How many banners? | 15–25. <12 = doesn't escalate; >28 = stops being funny. |
| Avatars on banners or no? | Use avatars when the app is people-first (dating, social, marketplace with humans). Skip avatars when the app is transactional (job notifications, order alerts). |
| Tear overlay or no? | Always for FOMO/popularity angle. Skip only if the brand voice is grounded/serious (rare for this format). |
| End card or no? | Optional. If the brand wordmark already appears on every banner (because the brand IS the app), an end card is redundant. Use only when the avalanche is for a sub-feature (e.g. "Yubo Live") that needs the parent brand named. |
| What goes in the hook clip? | A 2–4s relatable moment that implies the volume payoff before showing it. Couple bickering, single person doom-scrolling, "POV your phone after [event]". |
| Where do SFX go? | Receive cue on every banner pop (quiet), one loud thunk on the tear reveal, ducked under the hook's dialog. |
Output
<output_folder>/
production/
lockscreen-base.json # config for the render-ios-lockscreen atom
sender-roster.json # (optional) the source name list before injection into the config
clips/
record-master.js # copy of this molecule's template (the only script needed)
master-flood.mp4 # ← THE DELIVERABLE. 4–6s, 720×1280, 30fps, h264, no audio.
master-flood.sfx.json # deterministic SFX cue list (for downstream mixing)
Optional downstream artifacts (NOT produced by this molecule):
hook/hook-clip.mp4 # supplied by agent / operator if they want to concat
edits/stitch.sh # follow-up concat — see scripts/stitch.template.sh
edits/master-final.mp4 # output of the downstream concat
Quality Checks
Before declaring the ad shippable:
Failure Modes
| Symptom | Cause | Fix |
|---|
| Banners pop in but stack flat (no escalation feel) | Flat cadence in TIMELINE | Re-time per critical knowledge #3 — first 2 slow, then accelerate |
| Avalanche reads as "just a list of notifications" | No tear overlay, no audio escalation | Add tear overlay sweep + louder thunk SFX at the climax |
| Phone bezel / status bar looks generic / wrong | device: "iphone-15-pro" not set on the lockscreen config, OR bezel is on but molecule was supposed to composite into hand-held phone shot | Match the framing: if the lockscreen is alone, use bezel; if compositing into a hand-held shot, use device: "bare" |
| Notification body wraps to 2 lines | Body text too long | Trim or use truncate: true on the notification |
| App icon renders as broken-image square | Path didn't resolve at render time | Use absolute path OR copy icon into the project folder before generating the config |
| Pinned banner doesn't look pinned | Forgot pinned: true on the notification, OR pinned banner isn't first in the array | Pinned must be notifications[0] AND have pinned: true |
| SFX overpower the bed | amix defaulted to normalize=1, dividing every input by N | Same as iMessage molecule knowledge #6: amix=inputs=N:duration=first:normalize=0 then volume=*,alimiter=limit=0.95 |
| Hook clip → lockscreen cut feels jarring | Hard cut with no audio bridge | Add a 200 ms crossfade and start the lockscreen's audio bed ramping in 400 ms before the visual cut |
| Brand looks like it's begging | Added "Download Yubo" text overlay on the avalanche | Remove it (critical knowledge #0) |
| Lockscreen looks AI / fake | Used CSS-styled app icon instead of real PNG | Replace with the real app-store icon at the real path |
| Avalanche keeps going past the joke | Too many banners | Cap at 22 visible; the last 2–3 can be partially below the safe-bottom edge to imply "more coming" |
Reference run
See clients/Yuba/ad-runs/run-02-run2/ for the canonical Yubo example. Copy its production/lockscreen-base.json + clips/record-master.js and swap brand assets + sender roster as the starting point for a new brand.