---
title: "How to add a follow button animation with Claude (tested prompt and script)"
description: "A dark @glowlab profile card pops up under the bottle, a cursor glides in and taps its red \"Follow\" button, and the button flips to a white \"Following\". Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/follow-button-animation
---

# How to add a follow button animation with Claude

A dark @glowlab profile card pops up under the bottle, a cursor glides in and taps its red "Follow" button, and the button flips to a white "Following".

Made by the script on this page, from our own test clip.

## Get this effect

1. ### Switch to Code in your [Claude Desktop App](https://claude.com/download)

2. ### Install the skill (once)

   ```
   Install the Recharm video effects skill: download https://recharm-growth-staging.s3.us-east-2.amazonaws.com/website/downloads/recharm-video-effects.zip and unzip it into ~/.claude/skills
   ```

3. ### Ask for the effect

   ```
   Add a follow button animation to input.mp4
   Handle, bio: @glowlab, Skincare lab
   ```

## Several videos, several effects, one ask

Put your clips in the folder, mention them with @, and ask for everything at once. Claude runs one tested script after another on each clip, then joins them.

Add a follow button animation to **@clip1.mp4**, put a sale badge on **@clip2.mp4**, give **@clip3.mp4** a VHS look, then join all three with crossfades.

## Settings you can change

Ask Claude to change any of these, or edit them at the top of the script.

| Setting           | Default                  | What it does                                |
| ----------------- | ------------------------ | ------------------------------------------- |
| `HANDLE, BIO`     | `@glowlab, Skincare lab` | The name and the line under it on the card. |
| `START`           | `0.8`                    | Second the card pops in.                    |
| `TAP`             | `2.4`                    | Second the cursor taps the button.          |
| `Y`               | `0.80`                   | Card center, 0-1 down the frame.            |
| `FOLLOW_COLOR`    | `(240, 35, 65)`          | Button color before the tap.                |
| `FOLLOWING_COLOR` | `(255, 255, 255)`        | Button color after the tap.                 |

## Without the skill

Don’t want to install the skill? Paste this prompt into Claude Code and Claude writes the script itself.

```
Add a follow button animation to input.mp4. Draw a profile card: a dark (18, 18, 26) rounded rectangle at 84% opacity, 80% of the frame width and 9.5% of its height, centered across and 80% of the way down, with corners rounded to 30% of its height. On its left, a purple (140, 90, 255) circle 68% of the card's height across with a 3-pixel white outline and a white "G" in it; next to it "@glowlab" in white bold text and under that "Skincare lab" in light gray (170, 170, 185). On its right, a pill-shaped button 36% of the card's width and 56% of its height, its right edge 4% of the card's width in from the card's right edge, reading "Follow" in white bold text on red (240, 35, 65). The card pops in at 0.8 seconds, growing from nothing to full size over 0.35 seconds with a slight overshoot. From 1.5 seconds a white arrow cursor with a dark outline, about 4.5% of the frame height tall, glides from the bottom-right corner to the button's center, arriving at 2.3 seconds. At 2.4 seconds it taps: the button squeezes to 90% and springs back over 0.2 seconds, a 4-pixel white ring spreads from the tap and fades over 0.45 seconds, and from 2.48 seconds the button is white (255, 255, 255) reading "Following" in dark (25, 25, 35) bold text. The cursor fades out between 2.9 and 3.2 seconds; the card stays to the end. Nothing shows before 0.8 seconds. Use Python with Pillow to draw it and ffmpeg to put it on the video (don't rely on ffmpeg's drawtext filter), keep the audio, and save it as output.mp4.
```

## Run it without Claude

This is the exact script we tested. Save it as `follow-button-animation.py` and run:

```
python3 follow-button-animation.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Follow button animation: a profile card with a red "Follow" button pops in, a cursor taps it and it turns to a white "Following".

Usage: python3 follow-button-animation.py input.mp4 output.mp4
Needs ffmpeg and Pillow (pip install pillow).
"""
import json
import math
import subprocess
import sys
from pathlib import Path

from PIL import Image, ImageDraw, ImageFont

HANDLE, BIO = "@glowlab", "Skincare lab"   # name and line under it on the card
START = 0.8              # second the card pops in
POP = 0.35               # seconds the pop takes
TAP = 2.4                # second the cursor taps the button
WIDTH, HEIGHT = 0.80, 0.095  # card size as a share of the frame width and height
Y = 0.80                 # card center, 0-1 down the frame (centered across)
FOLLOW_COLOR = (240, 35, 65)       # button before the tap (white text)
FOLLOWING_COLOR = (255, 255, 255)  # button after the tap (dark text)
FONTS = ["/System/Library/Fonts/Supplemental/Arial Bold.ttf",     # macOS
         "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",  # Linux
         "C:/Windows/Fonts/arialbd.ttf"]                          # Windows
FONT_FILE = next((f for f in FONTS if Path(f).exists()), None) or sys.exit("No bold font found: add one to FONTS")

def card():
    """The dark card with the avatar, handle and bio; the button is added per frame."""
    cw, ch = round(w * WIDTH), round(h * HEIGHT)
    img = Image.new("RGBA", (cw, ch))
    draw = ImageDraw.Draw(img)
    draw.rounded_rectangle([0, 0, cw - 1, ch - 1], ch * 0.3, fill=(18, 18, 26, 215))
    d, pad = ch * 0.68, ch * 0.16
    draw.ellipse([pad, (ch - d) / 2, pad + d, (ch + d) / 2], fill=(140, 90, 255), outline="white", width=3)
    draw.text((pad + d / 2, ch / 2), HANDLE[1].upper(), font=ImageFont.truetype(FONT_FILE, round(d * 0.5)), anchor="mm", fill="white")
    draw.text((pad * 2 + d, ch * 0.40), HANDLE, font=ImageFont.truetype(FONT_FILE, round(ch * 0.25)), anchor="lm", fill="white")
    draw.text((pad * 2 + d, ch * 0.68), BIO, font=ImageFont.truetype(FONT_FILE, round(ch * 0.17)), anchor="lm", fill=(170, 170, 185))
    return img

def button(label, fill, text_fill):
    bw, bh = round(w * WIDTH * 0.36), round(h * HEIGHT * 0.56)
    img = Image.new("RGBA", (bw, bh))
    draw = ImageDraw.Draw(img)
    draw.rounded_rectangle([0, 0, bw - 1, bh - 1], bh / 2, fill=fill)
    draw.text((bw / 2, bh / 2), label, font=ImageFont.truetype(FONT_FILE, round(bh * 0.46)), anchor="mm", fill=text_fill)
    return img

def button_center():
    """Where the button sits, in frame pixels, once the card has landed."""
    return w / 2 + w * WIDTH * (0.5 - 0.04 - 0.18), h * Y

def cursor(alpha):
    """A white arrow pointer with a dark outline, its tip at (2, 2)."""
    s = h * 0.045
    pts = [(0, 0), (0, 1), (0.27, 0.76), (0.44, 1.12), (0.58, 1.05), (0.42, 0.7), (0.75, 0.7)]
    img = Image.new("RGBA", (round(s * 0.9), round(s * 1.25)))
    ImageDraw.Draw(img).polygon([(2 + x * s * 0.95, 2 + y * s * 0.95) for x, y in pts],
                                fill=(255, 255, 255, alpha), outline=(20, 20, 20, alpha), width=2)
    return img

def overlay(t):
    img = Image.new("RGBA", (w, h))
    s = ease_out_back((t - START) / POP) if t >= START else 0
    if s < 0.02:
        return img
    c = base.copy()
    b = following if t >= TAP + 0.08 else follow
    squeeze = 1 - 0.1 * math.sin(math.pi * min(max((t - TAP) / 0.2, 0), 1))  # press in and spring back
    b = b.resize((round(b.width * squeeze), round(b.height * squeeze)), Image.LANCZOS)
    bx, by = button_center()
    c.alpha_composite(b, (round(bx - (w - c.width) / 2 - b.width / 2), round(by - (h * Y - c.height / 2) - b.height / 2)))
    c = c.resize((max(1, round(c.width * s)), max(1, round(c.height * s))), Image.LANCZOS)
    img.alpha_composite(c, (round(w / 2 - c.width / 2), round(h * Y - c.height / 2)))
    if 0 <= t - TAP < 0.45:  # a ring spreading from the tap
        p = (t - TAP) / 0.45
        r = h * HEIGHT * (0.2 + 0.6 * p)
        ImageDraw.Draw(img).ellipse([bx - r, by - r, bx + r, by + r], outline=(255, 255, 255, round(220 * (1 - p))), width=4)
    if TAP - 0.9 <= t < TAP + 0.8:  # the cursor glides in from the bottom right, taps, then fades
        p = min((t - TAP + 0.9) / 0.8, 1)
        p = p * p * (3 - 2 * p)
        cx, cy = w * 0.97 + (bx - w * 0.97) * p, h * 0.99 + (by - h * 0.99) * p
        fade = min(1, (TAP + 0.8 - t) / 0.3)
        img.alpha_composite(cursor(round(255 * fade)), (round(cx - 2), round(cy - 2)))
    return img

def ease_out_back(p):
    """0 -> 1 with a small overshoot past 1 before settling, which reads as a 'pop'."""
    p = min(max(p, 0), 1)
    return 1 + 2.70158 * (p - 1) ** 3 + 1.70158 * (p - 1) ** 2

def probe(path):
    info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
                                               "stream=width,height,r_frame_rate:format=duration", "-of", "json", path]))
    s = info["streams"][0]
    num, den = s["r_frame_rate"].split("/")
    return s["width"], s["height"], s["r_frame_rate"], float(num) / float(den), float(info["format"]["duration"])

def render(src, out):
    """Draw overlay(t) on every frame of src and save to out, keeping the audio."""
    ff = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-i", src,
                           "-f", "rawvideo", "-pix_fmt", "rgba", "-s", f"{w}x{h}", "-r", rate, "-i", "-",
                           "-filter_complex", "[0:v][1:v]overlay=shortest=1,format=yuv420p[v]",
                           "-map", "[v]", "-map", "0:a?", "-c:v", "libx264", "-crf", "18", "-c:a", "copy", out],
                          stdin=subprocess.PIPE)
    for i in range(math.ceil(duration * fps)):
        ff.stdin.write(overlay(i / fps).tobytes())
    ff.stdin.close()
    if ff.wait():
        sys.exit("ffmpeg failed")

src, out = sys.argv[1:3]
w, h, rate, fps, duration = probe(src)
base = card()
follow = button("Follow", FOLLOW_COLOR, "white")
following = button("Following", FOLLOWING_COLOR, (25, 25, 35))
render(src, out)
```

## More ugc and social proof

- [Comment reply overlay **Comment reply overlay**](https://www.recharm.com/effects/comment-reply-overlay)
- [Day counter **Day counter**](https://www.recharm.com/effects/day-counter)
- [Emoji rain **Emoji rain**](https://www.recharm.com/effects/emoji-rain)
- [Floating hearts **Floating hearts**](https://www.recharm.com/effects/floating-hearts)
- [Live stream overlay **Live stream overlay**](https://www.recharm.com/effects/live-stream-overlay)

## Find 3x _winning ads_

Recharm cuts your ad footage into tagged, searchable clips: hooks, testimonials, product shots. Find the shots that win, add the effect with Claude, and ship 3x more ad edits.

[Start 14-day free trial](https://www.recharm.com/get-started)
