$npx -y skills add gooseworks-ai/goose-skills --skill render-airdrop-carouselAssemble a viral iOS "AirDrop" notification-carousel video ad (≈6–8s, 9:16) from a brand line plus 6–16 real product photos — a native AirDrop share-sheet card ("Brand would like to share a ___ · Decline / Accept") springs up and its preview window CYCLES through the products, la
| 1 | # render-airdrop-carousel |
| 2 | |
| 3 | The free, deterministic renderer for the **airdrop-notification-carousel** video ad |
| 4 | format — the viral iOS "AirDrop" trend where a native share-sheet card |
| 5 | ("**Brand** would like to share a *candle* · Decline / Accept") springs up on the |
| 6 | phone and its preview window flips through a **carousel of real product photos**, |
| 7 | landing on a range/lineup payoff with an Accept tap. |
| 8 | |
| 9 | This is a DETERMINISTIC composite — **no generative video, no AI-rendered product**. |
| 10 | A real-DOM AirDrop card is rendered once to PNG (headless Chrome), chroma-keyed, and |
| 11 | its preview window is refilled per-product in PIL, then animated + audio-synthed with |
| 12 | FFmpeg. The whole point of the format is crisp system-UI text and **real** product |
| 13 | photography, both of which a video model would smear. This capability makes **no paid |
| 14 | calls**; the recipe gates the only optional paid step — generating a hero product shot |
| 15 | when the brand has NO usable photo at all (→ `create-image-fal`). |
| 16 | |
| 17 | Default output ≈ **6–8s**, 1080×1920, h264 + aac. Duration is DERIVED, not trimmed — |
| 18 | `first_hold + (N-1)·per + final_hold`. Add images or raise `per` to lengthen. |
| 19 | |
| 20 | ## Scripts (free) |
| 21 | |
| 22 | - `scripts/build_card.py` — brand params → `chrome.html` + `chrome-pressed.html`: the |
| 23 | AirDrop card on a **green page** (`#00e000`) with a **magenta preview window** |
| 24 | (`#ff00ff`) and a solid brand band (wordmark SVG or text + tagline). Real DOM text — |
| 25 | `AirDrop`, `Decline`, `Accept`, and the brand line are DOM/SVG, never AI-rendered. |
| 26 | - `scripts/one_shot.py` — glue: `build_card` → headless-Chrome (Playwright) fullPage |
| 27 | screenshot of both card states → `compose_carousel`. One `--config`, one MP4. |
| 28 | - `scripts/compose_carousel.py` — the render engine: green-key the card → detect the |
| 29 | magenta window → fill it per-product (cover-crop) → blurred per-product backdrop + |
| 30 | push-in → card spring-up (iOS ease-out-back) + carousel + Accept tap → synth audio |
| 31 | (whoosh on entry, chime on land, a tick per swap, a pop on the tap) → encode h264+aac. |
| 32 | - `scripts/config.example.json` — the shape of the brand `config` the recipe binds |
| 33 | (brand-neutral worked defaults; replace every `/abs/path/...` placeholder). |
| 34 | |
| 35 | ## Chroma contract (load-bearing — build_card.py and compose_carousel.py MUST share it) |
| 36 | |
| 37 | `#00e000` **green** = page background, keyed to the card's alpha. `#ff00ff` **magenta** |
| 38 | = preview window, replaced per product. **Neither color may appear in the card art or |
| 39 | any product photo** — a product image containing near-pure green or magenta gets |
| 40 | keyed/misread. Swap the image or tighten the `window_mask` thresholds if it bleeds. |
| 41 | |
| 42 | ## Craft rules (faithful to the source molecule) |
| 43 | |
| 44 | - **Real product photos only.** The format's credibility is that these look like real |
| 45 | AirDropped items — no AI-generated products in the carousel. |
| 46 | - **Never AI-render text** — the card UI, wordmark, and band are DOM/SVG composited so |
| 47 | they stay pixel-crisp. Cover-crop the window; white-bg PDP shots reading as "product |
| 48 | in a white tile" is on-brand and fine. |
| 49 | - **6–16 images**, ordered; **end on the range** — the payoff (`final_image`) should |
| 50 | read as "the whole line" (lineup / family / all-shades). |
| 51 | - **Hard cuts on rhythm, a tick per swap** — do NOT crossfade. |
| 52 | - **Never invent proof** — a `1M+ sold` tagline must be the brand's OWN stated figure, |
| 53 | not filled from a brand-kit proof section. |
| 54 | - **Duration is derived, not trimmed** — add images or raise `per`; never cut the audio. |
| 55 | |
| 56 | ## Requires |
| 57 | |
| 58 | - **Python 3** with `numpy` + `Pillow`, and **ffmpeg/ffprobe** on PATH. |
| 59 | - **Playwright chromium** for the screenshot step (`pip install playwright && playwright |
| 60 | install chromium`). If Playwright is unavailable, run `build_card.py`, screenshot |
| 61 | `chrome.html` / `chrome-pressed.html` (fullPage) → `chrome-green.png` / |
| 62 | `chrome-green-pressed.png` via the chrome-devtools MCP, then call `compose_carousel.py` |
| 63 | directly. |
| 64 | - `watch` (QC the final master). The recipe gates `create-image-fal` (optional hero shot |
| 65 | when the brand has NO usable product photo) — the only paid, proxy-routed step. |