HTML5 display banners break in boring ways: the zip is 4 KB over, clickTag was declared with
const so the click goes nowhere, a background glow loops forever and the platform rejects the
whole flight. You find out days later, from a moderator.
This toolkit is two things that stop that: animation templates that already follow the rules, and a script that fails the build when they are broken.
git clone https://github.com/komilovsg/banner-builder.git
cd banner-builder
bash scripts/check.sh examples # see the gate run on a real size set
open examples/preview.html # watch all three banners animateNo npm, no build step, no toolchain. Bash, zip, and a browser.
Build one banner:
mkdir -p out/300x250
cp templates/banner.html out/300x250/index.html
# edit: copy, colors, and the clickTag URL near the bottom of the file
bash scripts/check.sh out/ # must pass before you send anything
bash scripts/backup.sh out/ # renders backup.jpg from the end frame
cd out/300x250 && zip -rX ../300x250.zip . # index.html must sit at the zip rootThat zip is what you upload to Google Ads, Yandex Direct, or MyTarget.
Real output from examples/, rendered end frames:
Each size is re-laid out, not scaled: the rectangle stacks, the leaderboard runs logo → message → CTA horizontally, the skyscraper reads top-down. That difference is the single most common reason a client sends a size set back.
out/
├── 300x250/
│ ├── index.html # markup + CSS + JS inline, self-contained
│ └── backup.jpg # static fallback, exact dimensions
├── 728x90/
└── 160x600/
A banner is not done when it looks good. It is done when it passes:
$ bash scripts/check.sh examples
SIZE ZIPPED CAP RESULT
------------------------------------------------------------
160x600 18K 150K PASS
300x250 20K 150K PASS
728x90 17K 150K PASS
------------------------------------------------------------
3 banner(s), 0 failed, 3 warning(s).check.sh zips every banner directory it finds and exits non-zero on:
| check | why it exists |
|---|---|
| zipped weight over cap | Google Ads and Yandex Direct both reject over 150 KB |
no global var clickTag |
ad servers rewrite that literal — let/const/concatenation break the click |
location.href = clickTag |
navigates the ad iframe instead of opening the landing page |
repeat: -1, animation: infinite |
endless creatives fail automated policy review |
runtime code evaluation, cookies, localStorage |
scanned for and rejected |
ad.size meta missing or mismatched |
the ad server sizes the slot from it |
| missing backup image, no visible border | warnings — both are required for delivery |
Comments are stripped before scanning, so a file that documents a banned pattern does not fail for mentioning it. Point it at any banner folder — it does not care whether these templates produced it:
bash scripts/check.sh ~/clients/acme/creatives 150| template | when |
|---|---|
templates/banner.html |
default. GSAP timeline, 3 scenes, ?frame=end, ~20 KB zipped with the CDN |
templates/banner-nogsap.html |
hard weight caps or a platform that blocks every external host. Pure CSS @keyframes, ~4 KB zipped, zero requests |
Both are one self-contained file: markup, styles, and script inline. Copy, rewrite the copy
blocks, change the CSS variables at the top, set clickTag. Nothing to compile.
Two documents that carry the parts people get wrong:
reference/platforms.md— Google Ads / DV360, Yandex Direct and MyTarget specs, the exact shapeclickTagmust have and why, and a pre-flight checklist.reference/layouts.md— layout recipes and type scale per IAB size family: rectangle, leaderboard, skyscraper, mobile.
Design the master size first — usually 300×250, because that is where the timeline gets settled. Then re-lay-out for the rest of the set, run the gate, capture backups, zip.
The repository doubles as a Claude Code skill: SKILL.md
carries the output contract, the build procedure, and the motion rules, so an agent produces the
same structure the templates and the gate expect.
bash scripts/install.sh # symlink into ~/.claude/skills — git pull keeps it currentRestart Claude Code, then:
/banner-builder 300x250 728x90 160x600
| variant | effect |
|---|---|
bash scripts/install.sh |
symlink into ~/.claude/skills/banner-builder — available in every project |
bash scripts/install.sh --copy |
copy instead of symlink |
bash scripts/install.sh --project |
install into ./.claude/skills of the current project only |
Entirely optional. Everything above works from a plain terminal.
?frame=end. Every template jumps its timeline to the last frame on that query string. Headless Chrome does not reliably advancerequestAnimationFrameunder--virtual-time-budget, so waiting N seconds screenshots scene 1 instead of the end frame. This makes backup capture deterministic.- Reset in
onRepeat, not at the tail of the timeline. Then the timeline's end state is the end frame, which is what the backup image and QA depend on. - Finite ambient tweens. A drifting background glow set to
repeat: -1is still an infinite animation to a policy scanner. - Everything in CSS. Gradients, glows, shine sweeps — no rasters means the weight budget goes to the parts that need it.
- Overlap, never queue.
tl.to(a, {...}).from(b, {...}, '-=0.35'). Sequential fades read as a slideshow; overlapping ones read as motion design.
Bash, zip, and a browser. backup.sh also wants Chrome or Chromium, which you already have.
GSAP loads from a CDN in the templates; vendor gsap.min.js into the bundle (~25 KB zipped)
whenever the platform does not whitelist the host — Yandex effectively does not.
Тулкит для HTML5-баннеров: шаблоны с анимацией и скрипт-гейт, который валит сборку до того, как креатив уедет на модерацию.
git clone https://github.com/komilovsg/banner-builder.git
cd banner-builder
bash scripts/check.sh examples # гейт на реальном сете размеров
open examples/preview.html # три баннера в браузереСобрать свой: скопировать templates/banner.html в out/300x250/index.html, поменять тексты,
цвета в CSS-переменных и clickTag, прогнать check.sh, снять подложку backup.sh, запаковать
в zip так, чтобы index.html лежал в корне архива.
check.sh архивирует каждую папку и валит сборку на: превышении 150 КБ, отсутствии глобального
var clickTag, объявлении через let/const, бесконечных циклах (repeat: -1,
animation: infinite), запрещённых API и несовпадении ad.size с именем папки. Работает на
любых чужих баннерах, не только на этих шаблонах.
Спецификации площадок (Google Ads, Яндекс.Директ, MyTarget) — reference/platforms.md,
раскладки и типошкала по размерам — reference/layouts.md.
Опционально ставится как скилл для Claude Code: bash scripts/install.sh, дальше
/banner-builder 300x250 728x90.
Useful additions, in rough order of value: platform specs that have actually caused a rejection,
layout recipes for sizes not covered yet, additional checks for check.sh. Keep the repository
dependency-free — that constraint is the point.
MIT — see LICENSE.


