Text bubble stack
Use it in Tella
- Open any video in Tella, then open the AI chat.
- Paste the skill:
tellahq/community-skills/text-bubble-stack - Then ask for what you want, for example:
Add a stack of text bubbles behind me for what I say at the start, in a colour that matches my shirt.
Use it in Claude Code
Add it to Claude Code, or any agent that reads skills:
npx skills add tellahq/community-skills --skill text-bubble-stackInstructions
What the AI chat follows when the skill is pasted, from its SKILL.md.
Text bubble stack
A stack of glossy chat bubbles behind the speaker, one per spoken phrase, over a white gradient wash. references/reference.jpg shows the finished look (violet theme, "No / Nope / Nooooope!") on a 9:16 talking-head clip. scripts/make_bubbles.py renders the bubble images and the wash.
1. Ask first
Read the transcript (get_transcript on the clip, word timings) to find the phrases that could be bubbles, then ask with one ask_form:
- Bubble colour: a choice such as Match the speaker / captions (look at a frame and the video's
captionStyle), Classic iMessage blue, Grey (received), Green (SMS), or a custom colour. Fill in your pick. - How many bubbles: a number (usually 2–4). Fill in the number of phrases you found.
- What they say: one text field, one bubble per line, separated by
/. Prefill it with the spoken words, written expressively when it suits the delivery (e.g. "No / Nope / Nooooope!"). Also confirm, in the same form, which spoken moment each bubble lands on when it isn't obvious.
2. Colour themes
Each theme is a vertical gradient (top light, mid, bottom) plus an outline, shadow and text colour:
- Classic iMessage blue: top #E3F0FD, mid #B4D5F8, bottom #8DBCF1, outline #55779F, shadow #1D3557, text #000000.
- Violet (matched to a lavender shirt and indigo-outlined captions): top #F3ECFF, mid #CDB8F4, bottom #A98AE8, outline #4A45A0, shadow #312E81, text #312E81.
- Any other colour: derive a pale top, mid and saturated bottom from it, a darker outline (slightly lighter than the darkest shade, not near-black), and dark text in the same hue.
Match a speaker or caption colour by sampling a frame and reading
get_videosettings.captionStyle.
3. Render the images
Write a spec JSON (texts, fontSize 88, theme, canvas size from get_timeline, wash {top: 1.0, bottom: 0.1}) and run python3 scripts/make_bubbles.py spec.json out/. It prints each bubble's 1x width W (170 px tall, tail at the bottom right, width fitted to its text) and writes wash.png. View one bubble on a grey background before uploading.
Upload each PNG: create_source (kind image, the printed pixel size), curl -T, wait_for_jobs, then add_library_item (private) to get a hosted url. Image assets in a Tella Motion document need that URL.
4. Build the Tella Motion document
create_motion_document with the videoId, width ~600 (wide enough for the widest bubble + 30), height = 3 slots for 3 bubbles (slot step 188 px: 170 bubble + 18 gap; bottom slot y = height − 20 − 170). One batch:
canvas.setduration = from the first bubble's word to where the stack should end (often the clip end).asset.setkindimagesper bubble:{id: "bubble.N", source: {type: "uri", uri: <library url>}, dimensions}.- Per bubble a
node.insertgroupbNon the canvas, starting at its appear time and lasting to the end,common.originat the tail tip (x = rightEdge − 6, y = 164), motion blur{shutter: 180, samples: 12}, childraster(fit: fill, destination{x: rightEdge − W, y: 0, width: W, height: 170}). The bubbles are right-aligned with the tails on the right, like sent messages. addTitlein each group: the text, fontinter, size 88, weight 400, the theme's text colour,align: center, box{x: rightEdge − W, y: 26, width: W − 16}. Keep the text as live text.- Keys (easing on a key is the segment leaving it). Soft overshoot easing
{x1: 0.3, y1: 1.18, x2: 0.45, y2: 1}, never the spring presets (they land hard):- opacity 0 → 1 over 0.12 s from the appear time.
- scale 0.4 → 1 over 0.45 s (shorter if the document ends sooner), soft overshoot.
- y: starts at the bottom slot. For every later bubble, start moving up one slot 0.1 s before that bubble appears (so the new one never overlaps it), over 0.45 s, soft overshoot.
- rotation (jaunty knock): the new bubble swings in from about ±8–10° (easeOutCubic) to the opposite side (easeInOutSine) and settles at a small rest tilt, alternating left and right between bubbles (−3°, +2.5°, −1.5°). When knocked up, each existing bubble rocks about 4–5° the other way and wobbles back to its rest tilt over ~0.4 s. Hold the rest tilt between knocks. Appear times are the spoken word's start in clip time minus the overlay's start, so the first bubble is at 0.
5. Place in the video
One apply_video_edits batch on the clip:
- The wash:
add_media_overlaywithwash.png, full frame (point 0,0, canvas dimensions), starting at the first bubble's word and lasting as long as the stack,depth: behind_presenter, animation intro{preset: fade, durationMs: 300, easing: {curve: smooth}}, outro null. - The stack:
add_media_overlaywith the document id assourceId, dimensions = the document's canvas size,depth: behind_presenter, positioned above or beside the speaker's head (look at a frame; the bottom bubble may tuck slightly behind the hair). - Restack so the wash is under the stack.
list_overlaysand compare theirzIndex. Setting azIndexmoves the overlay to that position and the others keep their order, so only when the wash'szIndexis higher than the stack's,update_media_overlayon the wash withzIndexequal to the stack's (the wash moves under it). When the wash's is already lower, leave it: setting it to the stack's index would move the wash on top. If you later change the document's canvas size, remove and re-add the overlay: an existing overlay keeps the old aspect and renders stretched.
6. Check
get_filmstrip at each appear time, 0.1 s before each, mid-push and the last frame. Check that no bubble overlaps another, that the text is readable and centred in its bubble, that the wash sits behind the speaker and the bubbles in front of the wash (list_overlays: the stack's zIndex is higher than the wash's; pale, washed-out bubbles, palest at the top, mean the wash is on top), and that nothing important is hidden behind the head. Fix and re-check. Report each bubble's time and the colour used, and say the motion wasn't watched in playback unless you listened/watched a preview.
Files
The other files in the skill, such as reference videos it copies from.