---
name: text_message_story_video
description: Write and produce stories told as text message conversations for short vertical video: chat thread arcs with a hook in the first exchange, typing and read timing that builds tension, readable bubble sizing and contrast, a JSON driven renderer, and privacy rules for anything based on real chats. Use when making, scripting, timing, or reviewing a chat story, fake text conversation video, or texting story for TikTok, Instagram Reels, or YouTube Shorts.
---

# Text Message Story Video

## Purpose

Turn a story into a finished chat thread video a viewer can read at feed speed: a structured arc told in message bubbles, typing and hold timing computed from reading speed, bubbles sized and coloured to stay readable on a phone, and a privacy pass that keeps real people's conversations out of published work. Ships with a working renderer, so the output is a video file and a timed plan, not advice about one.

## Workflow

1. **Classify the story first.** Fully fictional, based on a real chat with written permission from every identifiable participant, or based on a real chat without it. The third class gets rebuilt as fiction or dropped. Rules and the transformation checklist are in `references/privacy-and-rights.md`. Never build a published video from screenshots of a real thread.
2. **Pick an arc pattern and a length.** Four patterns with beat maps are in `references/story-arcs.md`: stranger danger at 30 to 60 seconds, the slow reveal at 60 to 120, group chat pile-up for comedy, and the repair at 90 to 180 seconds for drama. The pattern sets the message count before a word is written.
3. **Write the hook as the first exchange.** The opening message states a threat, a deadline, a question the viewer shares, or a detail that does not fit. No greetings, no introductions, no setup messages before it. If the hook sits at message four, cut the three before it.
4. **Write one idea per bubble.** Most messages under 60 characters, none over 200. A bubble with two ideas becomes two bubbles, and the second one carries the typing pause. Characters stay terse under pressure and turn wordy only when stalling or reaching the honest line of a repair story.
5. **Write the story as JSON.** Follow the shape of the bundled examples: characters with sides, a `contact_name` chosen as a story decision, messages with `from`, `text`, and a `beat` label, plus the `fictional`, `based_on_real`, `consent_confirmed`, and `sponsored` flags. Use `typing_s`, `hold_mult`, `pause_after_s`, `gap_after_s`, and `read_receipt` on individual messages to place the tension beats.
6. **Plan before rendering.** Run `python3 scripts/chat_video.py plan assets/example-short.json` on the new story file. Check the hook lands at the start, the twist and payoff holds are the longest on the sheet, and the total runtime fits the pattern. Fix timing in the JSON, not in the render.
7. **Validate.** Run `python3 scripts/chat_video.py validate <story.json>`. It checks bubble length, phone number and email patterns, the consent flags, the sponsored disclosure rule, bubble contrast against WCAG AA, and the YouTube Shorts duration ceiling. Errors stop the render unless forced, and forcing never ships.
8. **Render and verify.** Run `python3 scripts/chat_video.py render <story.json> --out <file>.mp4`, with `--audio <track>` to mux licensed music instead of the silent track. Verify the output with `ffprobe` for 1080 x 1920 and the planned duration, extract one frame at a twist bubble, and read the frame at full size before posting.

## Output Contract

Deliver, at minimum:

- **Story file**: the JSON the video was rendered from, with arc pattern, beat labels, timing overrides, and privacy and sponsorship flags set honestly.
- **Beat sheet**: the plan output showing when each message lands, its typing and hold times, and the total runtime, with the pattern the story follows named.
- **Final video file**: 1080 x 1920, 30 fps, H.264 plus AAC, `yuv420p`, `faststart` set, every bubble readable in a spot checked frame, duration inside the target platform ceiling in `references/timing-and-readability.md`.
- **Caption copy**: title, part number for serials, the fiction or dramatisation label where one applies, and the sponsorship disclosure in the same words used inside the video when the story is sponsored.
- **Privacy note**: one line stating the story class, and for a real based story, where the written permissions are filed.

## Operating Rules

1. **Time bubbles from reading speed, never by feel.** Hold time is characters divided by 17 characters per second with a 0.9 second floor, per `references/timing-and-readability.md`. A bubble that needs more than 7 seconds gets split. Typing time varies on purpose: uniform typing reads as no typing.
2. **Bubble text clears WCAG AA contrast at 4.5:1.** The familiar bright messaging blue with white text computes to 3.82:1 and fails. The renderer palette passes at 5.57:1 and 15.57:1, and validate prints the ratios for every run. Identity is carried by side and name as well as colour, never by colour alone.
3. **Keep story text inside the safe area.** Bubbles stay between the header and y 1470 on the 1920 tall canvas, with a 160 px right margin clearing the platform action rail. Plot never lives below that line or only in a sound cue. The story must work muted.
4. **Real chats stay private by default.** Written permission from every identifiable participant, or a full transformation under the checklist in `references/privacy-and-rights.md`, with an honest fiction or dramatisation label. Extra care classes there, including minors and people in crisis, are hard stops, not judgement calls.
5. **Disclose inside the video.** A sponsored story or a gifted product in the plot gets a clear disclosure inside the video itself, early and long enough to read, plus the same disclosure in the caption. A platform paid partnership label supplements it and does not replace it. A disclosure written as character dialogue does not count.
6. **End on the payoff message.** No goodbye bubble and no reaction message after the last line. The final hold is the ending. For a serial, the payoff answers the current question and opens a bigger one, and names, sides, and colours stay identical across parts.
