| name | create-concept-loop-video |
| description | Produce a 10-25s seamlessly looping concept video — a single character/object/idea cycling through N emotional states (e.g. days-of-the-week mood loop), built so the last frame matches the first for infinite-scroll replay. Music-only, in-world text, color-as-emotion. Opens on the emotional payoff, not the logical start. Use for brand-identity loops, mood reels, and character animation ads where the hook is the vibe, not a claim. Orchestrates extract-beat-grid + generate-edit-decision-list (archetype concept-loop, grammar.loop=true) + Nano Banana per-state frames + motion-graphics rendering + render-edl. |
create-concept-loop-video
Purpose
Produce a shippable 10-25s looping concept video using the concept-loop
archetype from CREATOR_GRAMMAR.md §8. The archetype's defining pattern:
- Seamless loop — the last frame must visually match the first. The video
is built to play on infinite repeat without a visible seam.
- Color-as-emotion — each state in the cycle has its own background color
palette. The color is the mood. No explanatory caption.
- In-world text — any text on screen is part of the visual design (e.g.
a day label in the character's hand, a status badge floating over the scene),
not a platform-layer subtitle burn.
- Opens on emotional payoff — the first state shown is the most resonant,
not the logical start of the sequence (e.g. a "Friday" state opens the
Monday-through-Friday loop).
- Music-only, beat-synced cuts — no VO, no karaoke captions. Cut density
is lower than the silent-product-demo archetype (2 cuts/10s in the body);
the rhythm is in the beat-snapped transitions between states.
- Rapid cut burst near the loop point — a small burst (3-5 cuts) signals
the loop is resetting, giving infinite-scroll replay a satisfying rhythm.
The molecule accepts a concept brief describing the loop idea and N states,
generates one visual frame per state, renders each state clip, and assembles
them through the EDL renderer with grammar.loop: true.
Inputs
| Input | Required | Notes |
|---|
loop_concept | yes | One-sentence description of the loop idea (e.g. "a character cycling through the seven moods of a work week"). |
states | yes | Ordered list of N state definitions. Each entry: { "label": "<day or state name>", "color": "<hex>", "description": "<visual description of the state>", "in_world_text": "<optional text rendered in-world>" }. Min 3 states, max 8. |
open_state | yes | The label of the state that opens the video (the emotional payoff). Must NOT be the logical first state (e.g. for a Mon-Fri loop, open on Friday, not Monday). |
music_path | no | Path to an existing music track. If omitted, find-music is run. |
character_reference | no | Path to a reference image of the character or object. Drives Nano Banana consistency across states. |
target_duration | no, default 15 | Target runtime in seconds (10-25). |
output_dir | yes | Project directory (canonical 5-folder layout). |
Composed Atoms
atoms/audio-editing/extract-beat-grid — analyse the music track and
produce beat-grid.json. Required because concept-loop is
beat_sync: true.
atoms/planning/generate-edit-decision-list — build the edl.json with
archetype concept-loop, grammar.loop: true, beat-snapped cut points,
and the loop-closure clip pairing (first state clip referenced again as
the final clip).
atoms/assembly/render-edl — execute the validated edl.json into
master.mp4. Handles hard-cut concat, music bed, and in-world text captions
(rendered as static-title-card cues inside the EDL).
atoms/music/find-music — source a royalty-free music track when none is
provided. Prefer ambient/loopable tracks that do not have an obvious
intro or outro (they'll be trimmed to loop).
atoms/image-generation/create-image-nano-banana-fal — render one keyframe
per state from the concept description + color + optional character
reference. This is the primary visual generation step.
atoms/motion-graphics/create-motion-graphics-remotion — optional: if a
state requires animated text or kinetic elements (not just a static
still-to-motion clip), author the state frame as a Remotion component
instead of a Nano Banana still.
atoms/motion-graphics/create-kinetic-typography — optional: if in-world
text needs kinetic treatment (day label bouncing in, status bar animating),
use this atom to author the text animation for that state.
atoms/editing/add-zoom-cuts — apply a subtle zoom-punch on the rapid cut
burst at the loop point to signal the reset.
atoms/review/watch — frame-grounded QC of the final master, including
loop-seam check (first frame vs last frame).
Workflow
Phase 0 — Intake and concept lock
- Read
loop_concept, states, and open_state. Validate:
- Minimum 3, maximum 8 states.
open_state is a label present in states.
open_state is NOT the logical first state (if it is, warn the user and
ask them to pick a different opening state).
- Each state has a distinct
color hex value — no two adjacent states in
the play order may share a color family (e.g. two blues in a row).
- Derive the play order: move
open_state to position 0, then continue
in the original state sequence, wrapping around. Example: if states are
[Mon, Tue, Wed, Thu, Fri] and open_state is Fri, the play order is
[Fri, Mon, Tue, Wed, Thu, (Fri)].
- Write the play order to
working/play-order.json.
- Create
output_dir with canonical 5-folder structure.
Phase 1 — Music + beat grid
- Music selection:
- If
music_path is provided, skip to step 2.
- Run
find-music with descriptors matching the loop concept (e.g.
"loopable ambient, 100-120 BPM, chill, no long intro"). Prefer tracks
that sustain energy without a dramatic drop (drops disrupt the loop seam).
- Write to
assets/music/track.mp3.
- Run
extract-beat-grid:
python3 skills/atoms/audio-editing/extract-beat-grid/scripts/extract_beats.py \
--source assets/music/track.mp3 \
--output-dir working/beat-grid/
- Verify
bpm is positive and beats is non-empty.
- Determine the per-state clip duration:
target_duration / N_states.
Adjust each state's duration to the nearest bar boundary in the beat grid
(±150ms tolerance) so cuts land on bars.
Phase 2 — Generate per-state frames
For each state in the play order:
- Build a generation prompt:
"<state.description>, background color <state.color>,
in-world text '<state.in_world_text>' visible,
matching the character/object from <character_reference>"
- Run
create-image-nano-banana-fal with the prompt and
character_reference (if provided). Save to
assets/keyframes/<state_label>.png.
- If the state needs animated in-world text (kinetic label, floating status),
run
create-kinetic-typography or create-motion-graphics-remotion to
render a short motion clip (assets/clips/<state_label>-anim.mp4) for that
state instead of a static still.
- Loop closure: after generating all N states, verify that the
open_state keyframe (assets/keyframes/<open_state_label>.png) can
serve as the first AND last frame (visually, the last rendered frame of
the sequence should be close to the first). Do not re-generate — the
EDL will reference the first state's clip at both ends.
Phase 3 — Generate edit decision list
- Build the scene table at
working/scene-table.json:
[
{ "scene_id": "loop-open", "description": "<open_state.description>",
"role": "hook", "color": "<open_state.color>",
"in_world_text": "<open_state.in_world_text>" },
{ "scene_id": "state-02", "description": "<state[1].description>",
"role": "feature", "color": "<state[1].color>",
"in_world_text": "<state[1].in_world_text>" },
...
{ "scene_id": "loop-close", "description": "<open_state.description>",
The entry references the clip again (not a
new generation). This is what creates the seamless loop.
Phase 4 — Assemble scene clips
For each state clip in the play order:
- If a motion clip was generated in Phase 2 (
assets/clips/<state_label>-anim.mp4),
use it directly as the EDL clip source.
- If only a still keyframe exists, the EDL records
source.mode: "generate", spec.keyframe: "assets/keyframes/<label>.png".
render-edl will hold the still for the clip's duration (still-image
clip handling).
- Apply
add-zoom-cuts to the 3-5 clips nearest the loop point (the
rapid cut burst): set motion: { "type": "zoom-punch", "from": 1.0, "to": 1.08 }.
Phase 5 — Render master
Run render-edl:
python3 skills/atoms/assembly/render-edl/scripts/render_edl.py \
--edl working/edl.json \
--output finals/master.mp4 \
--project-dir <output_dir> \
--crf 18
Phase 6 — Loop seam verification
-
Extract the first and last frames:
ffmpeg -ss 0.1 -i finals/master.mp4 -frames:v 1 -q:v 2 working/frame-first.png
ffmpeg -sseof -0.1 -i finals/master.mp4 -frames:v 1 -q:v 2 working/frame-last.png
-
Run atoms/review/watch on finals/master.mp4 with the QC prompt:
"Check: (1) does the first frame closely match the last frame — same color,
same character state, no jarring jump? (2) do cuts land on beats?
(3) is the opening state the most emotionally resonant one? (4) are there
no VO tracks, no karaoke captions? (5) is in-world text legible and
integrated into the visual design? (6) is there a small burst of rapid
cuts near the loop point?"
-
Report findings as a timestamp punch list. If the loop seam is visible
(significant color or composition mismatch between first/last frames),
fix the loop-close clip and re-render Phase 5.
Decision Rules
DR-1 — Emotional-payoff-first opening
The first state in the play order must be the emotional peak, not the logical
start. For a Mon-Fri loop, "Friday" (relief/energy) opens — not "Monday"
(dread). For a product-benefit loop, the "after" state opens — not the
"before."
If the user provides states in chronological order and does not specify
open_state, ask rather than guessing. An incorrectly ordered loop opens
on the most forgettable state and loses the scroll-stop hook.
DR-2 — Loop closure (first frame = last frame)
The loop-close entry in the scene table must reference the open_state
clip — not a newly generated frame. Even a visually identical regenerated
frame will have slight differences that break the loop seam. Use the exact
same source: source.spec.keyframe: "assets/keyframes/<open_state>.png".
If the open_state is a motion clip (animated in-world text), the loop-close
clip must use the first frame of that animation held static, not a looped
replay. This prevents audio/visual drift when the platform loops the video.
DR-3 — Color adjacency rule
Adjacent states in the play order must have visibly distinct colors. Do not
place two similar-hue states next to each other (e.g. two blues, two greens).
The color change IS the editorial cut signal. If the user's state list has
adjacent same-hue states, reorder or ask for a color substitution.
DR-4 — No payoff-hold clip
The concept-loop archetype does not use a payoff hold. Do not add a
payoff-hold role to any clip — the EDL validator will pass (this archetype
marks payoff as not required). The loop's "reward" is the return to the
opening state, not a held single shot.
DR-5 — In-world text vs platform caption
All text visible in the video must be rendered as part of the visual frame
(via Nano Banana in-world text, Remotion component, or kinetic typography).
Never use the burn-in-captions atom or ASS captions to display state labels.
The in-world text is part of the art direction; a burned-in subtitle is a
platform UI element, which breaks the aesthetic register.
DR-6 — Loop burst placement
The rapid cut burst (3-5 quick cuts) should occur in the last 15% of the
video (the tail segment), signaling the loop reset. This matches the
concept-loop archetype's cuts_per_10s.tail: 5 target. Set these clips'
transition_in to beat-synced-cut and add motion.zoom-punch via
add-zoom-cuts.
Output
<output_dir>/
├── source/
│ └── character-reference.png ← optional input reference
├── assets/
│ ├── music/
│ │ └── track.mp3
│ └── keyframes/
│ └── <state_label>.png ← one Nano Banana frame per state
├── working/
│ ├── play-order.json ← state sequence starting from open_state
│ ├── beat-grid/
│ │ ├── beat-grid.json
│ │ └── manifest.json
│ ├── scene-table.json
│ ├── edl.json
│ ├── frame-first.png ← Phase 6 loop-seam check frame
│ ├── frame-last.png ← Phase 6 loop-seam check frame
│ └── planning-notes.md
└── finals/
├── master.mp4 ← the delivered looping Reel
└── manifest.json
The finals/master.mp4 is the shippable loop: 9:16, 1080×1920, h264 + AAC,
30fps, 10-25s, with in-world text baked into the visuals and no VO track.
Quality Checks
finals/master.mp4 exists and is non-zero; ffprobe reports both video
and audio streams.
- Duration within ±0.5s of
target_duration.
- Resolution is 1080×1920 (9:16), 30fps.
edl.json has grammar.loop: true and the first clip's source spec
matches the last clip's source spec (same keyframe path).
working/frame-first.png and working/frame-last.png are visually
consistent — same dominant color, same subject in the same position.
meta.audio_mode is "music-only"; no VO track in edl.json.
grammar.caption_family is "static-title-card"; no karaoke-pop cues.
- Exactly N+1 clips in the timeline (N states + the loop-close clip that
mirrors the first state).
- All
beat-synced-cut clip boundaries land within ±50ms of a bar boundary
in beat-grid.json.
- Self-QC punch list from Phase 6 has zero critical loop-seam or beat-sync
issues.
manifest.json exists with status: pass.
Failure Modes
- Loop seam visible — the last frame has a different color or character
pose than the first frame. The
loop-close clip was generated anew instead
of referencing the open_state keyframe. Fix: point the EDL loop-close
clip to assets/keyframes/<open_state>.png explicitly.
- Opening state is the logical first, not the emotional payoff — the loop
lacks a scroll-stop hook. Re-select
open_state per Decision Rule 1.
- Adjacent same-hue states — two states with similar colors are adjacent
in the play order; the cut is invisible and the loop feels monotonous.
Reorder or replace colors per Decision Rule 3.
- In-world text illegible — small text rendered inside the Nano Banana
frame at 9:16 is often too small. If
in_world_text is ≥5 words, consider
rendering the text as a create-motion-graphics-remotion component with
larger, styled typography, and composite it over the Nano Banana frame.
- Music has an obvious intro/outro — the loop seam is hidden audibly, but
a long intro before the first beat makes the loop feel wrong on repeat. Trim
the music track to start on the first downbeat using
audio.music with
a start-offset in the EDL, or use find-music with "loopable, no long
intro" as the criterion.
generate-edit-decision-list reports beat-grid missing for a
beat_sync: true archetype — extract-beat-grid was skipped or the file
path is wrong. Run Phase 1 before Phase 3.
- More than 8 states — the per-state clip duration at 15s becomes too
short for any motion treatment (< 1.5s). Reduce to ≤8 states or increase
target_duration.
render-edl cannot find generate-mode clips — assets/clips/ or
assets/keyframes/ paths in the EDL do not exist. Verify Phase 2 completed
successfully and all paths in edl.json are relative to project_dir.