Build production-ready HTML5 display banners (GSAP + CSS + JS) for Google Ads/Display & Video 360, Yandex Direct, MyTarget, AdRiver and generic DoubleClick-style ad servers. Use when the user asks for an HTML5 banner, rich media creative, animated ad, баннер, креатив, ротацию размеров (300x250, 728x90, 160x600, ...), clickTag wiring, a banner size set, backup images, or a weight/spec check on an existing banner. Trigger: /banner-builder.
Installs into .claude/skills of the current project.
Are you the author of banner-builder?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/komilovsg-banner-builder)
---
name: banner-builder
description: "Build production-ready HTML5 display banners (GSAP + CSS + JS) for Google Ads/Display & Video 360, Yandex Direct, MyTarget, AdRiver and generic DoubleClick-style ad servers. Use when the user asks for an HTML5 banner, rich media creative, animated ad, баннер, креатив, ротацию размеров (300x250, 728x90, 160x600, ...), clickTag wiring, a banner size set, backup images, or a weight/spec check on an existing banner. Trigger: /banner-builder."
---
# banner-builder
One self-contained `index.html` per size. GSAP timeline. clickTag wired. Under the
platform weight cap. Zipped and verified before it is called done.
## Usage
```
/banner-builder # ask for brief, then build
/banner-builder 300x250 728x90 160x600 # build this size set
/banner-builder --check ./out # only verify existing banners (weight, clickTag, loops)
/banner-builder --platform yandex # target-specific rules (default: google)
```
## Non-negotiable output contract
Every banner directory is:
```
out/<W>x<H>/
index.html # markup + CSS + JS inline. Self-contained.
assets/ # only if raster/font files are unavoidable
backup.jpg # static fallback frame, same dimensions
```
and every `index.html`:
1. Declares its size in **one place**, `<meta name="ad.size" content="width=W,height=H">`, and CSS
vars `--w/--h` used by the `#banner` wrapper. No hardcoded pixel duplicates.
2. Exposes `var clickTag = "https://..."` **as a global `var` in the first inline script** —
ad servers rewrite that literal. Not `let`, not `const`, not inside a module or IIFE.
3. Routes every click through one handler on `#banner`, using `window.open(clickTag, '_blank')`.
4. Has a 1px border (`#banner { border: 1px solid rgba(0,0,0,.15) }`). Google rejects
creatives whose edges are indistinguishable from the publisher page.
5. Stops. `repeat: 2` on the master timeline (= 3 plays total), each play ≤ 6s. Google's hard
cap is 30s of animation; IAB LEAN and most RU platforms ask for ≤ 15s — when the brief
targets those, drop to `repeat: 1`. Never `repeat: -1`, not even on an ambient background
tween: automated policy scanners read any infinite tween as an endless creative.
6. Ends on the CTA frame — final state must be readable as a static image.
7. Uses `will-change`/transform+opacity only. No animating `width`, `height`, `top`, `left`,
`box-shadow` — they hit layout/paint every frame and stutter on the publisher's page.
## Build procedure
1. **Collect the brief.** Sizes, click URL, headline / subline / CTA text, brand colors,
logo, target platform, deadline frame ("what must be readable if the user only sees 1 second").
Anything the user did not give: pick a sane default, state the assumption in one line, keep going.
Never block the whole build on a missing hex code.
2. **Design the master size first** — usually `300x250`. Get the timeline right there.
3. **Port to the other sizes.** Do not scale-transform the 300x250 into a 728x90; re-lay-out.
Wide formats (728x90, 970x90, 320x50) go horizontal: logo left, message center, CTA right.
Tall formats (160x600, 300x600) go vertical stack. See `reference/layouts.md`.
4. **Build assets in CSS, not images.** Gradients, shapes, glows, shine sweeps, particles =
CSS/SVG. A raster is justified only for a real photo or a logo that is not available as SVG.
5. **Run the check.** `bash scripts/check.sh out/` — it zips every banner dir and fails on
over-weight, missing clickTag, `repeat: -1`, or a missing backup image. Fix and re-run
until clean. A banner that has not passed `check.sh` is not finished.
6. **Produce backup images.** Screenshot the final frame at exact dimensions
(`scripts/backup.sh` if Playwright/Chrome is available; otherwise say it is outstanding
and tell the user which frame to grab).
7. **Report**: table of size → zipped KB → cap → pass/fail, plus assumptions made.
## Weight
Cap is **150 KB zipped** on Google Ads and Yandex Direct for the whole bundle. Budget it:
| item | budget |
|---|---|
| GSAP (local `gsap.min.js`) | ~70 KB raw, ~25 KB zipped |
| HTML + CSS + JS | < 20 KB |
| images | whatever remains |
GSAP from a CDN keeps the zip tiny but only works where the platform whitelists the host
(Google Ads allows a short list, incl. `ajax.googleapis.com` and `cdn.jsdelivr.net`; Yandex is
stricter). **Verify the current whitelist for the actual placement before shipping.** When
unsure, vendor `gsap.min.js` into the bundle — 25 KB zipped is affordable, a rejected creative
is not. When even that is too much, drop GSAP and use CSS `@keyframes` +
`animation-iteration-count: 3` (see `templates/banner-nogsap.html`).
## Motion that reads as professional
- **One idea per scene.** Three scenes max in 5 seconds. A banner that says three things says nothing.
- **Overlap, never queue.** Outgoing element leaves while the incoming one arrives:
`tl.to(a, {...}).from(b, {...}, '-=0.35')`. Sequential fades look like a slideshow.
- **Ease is the whole personality.** `power3.out` for entrances, `power2.in` for exits,
`back.out(1.6)` for the CTA pop, `none` only for continuous drifts. Default `power1` everywhere
reads as a template.
- **Stagger text**, do not fade whole blocks: `gsap.from('.word', {y: 20, opacity: 0, stagger: 0.06})`.
- **Move ~24–40px, not 300.** Long travel eats time and reads cheap.
- **Hold the end frame ≥ 1.5s** before the loop restarts. The CTA is the point of the banner.
- **`gsap.ticker.lagSmoothing(0)`** and set `force3D: true` on transformed layers — publisher
pages are busy and drop frames.
## Files
- `templates/banner.html` — GSAP master template, 300x250, 3 scenes, clickTag wired. Start here.
- `templates/banner-nogsap.html` — pure CSS `@keyframes` version for hard weight caps.
- `reference/platforms.md` — per-platform specs, clickTag variants, rejection reasons.
- `reference/layouts.md` — layout recipes per IAB size.
- `scripts/check.sh` — weight + compliance gate. Run before declaring done.
- `scripts/backup.sh` — render the final frame to `backup.jpg`.
## Do not
- Ship `repeat: -1` — infinite loops are rejected and burn the viewer's CPU.
- Use legacy synchronous DOM injection, `eval`, cookies, localStorage, or any network call
other than the click-through. Ad servers scan for these.
- Reference the click URL directly in an `<a href>`. It must go through `clickTag`.
- Deliver a size set where every banner is the same layout squeezed — that is the single
most common reason a client sends it back.