Skip to content
Back to skills

Ui Morph Spec

ASecurity

Write a six-part XML state spec for a one-shape UI morph film (inputs, direction, structure, build, gotchas, start): states on downbeats, cursor-driven changes, seamless loop, approval gate. Use for 'UI morph', 'state list'.

  • 2 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added October 7, 2026
designgobashnodespringapi

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Pro scans all 5 files and shows the line behind each finding

Scanned October 7, 2026

npx -y skills add yazzang-homelab/motion-studio --skill ui-morph-spec --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ui Morph Spec?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Ui Morph Spec
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/yazzang-homelab-ui-morph-spec/badge)](https://www.skillsdirectory.com/skills/yazzang-homelab-ui-morph-spec)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: ui-morph-spec
description: "Write a six-part XML state spec for a one-shape UI morph film (inputs, direction, structure, build, gotchas, start): states on downbeats, cursor-driven changes, seamless loop, approval gate. Use for 'UI morph', 'state list'."
argument-hint: "[product or URL] [number of states] [bpm]"
allowed-tools: Bash(node "${CLAUDE_SKILL_DIR}/scripts/statelist.mjs" *)
effort: xhigh
---

# UI morph spec (course step 06, level L3)

Write the state list, not the vibe. The most-bookmarked prompt of the Opus 5.5 launch week was not
a one-liner: @twoclipping's UI morph loop and @verbove's MakerMap film both used an XML spec with
inputs to ask for, a direction, a beat-by-beat state list, build rules and gotchas. The idea behind
them: one shape that never cuts. A single element changes size, radius and color from state to
state (button, loader, player, chart, command palette), a cursor causes each change with a real
click, and the last frame equals the first so the film loops.

Request arguments (may be empty), shown once:

<request>
$ARGUMENTS
</request>

Credit: the six-section layout follows @twoclipping's public template
(https://x.com/twoclipping/status/2103273003555402193; the XML is in the main post, not in a thread
reply as the course says) and @verbove's MakerMap spec
(https://x.com/verbove/status/2103483957266268381). The template in this skill is written in
motion-studio's own words: [assets/spec-template.xml](assets/spec-template.xml).

## 1. Ask for the inputs

Ask in one AskUserQuestion round (subagents cannot ask; you do):

| Input | Default | Rule |
|---|---|---|
| Product + URL | required | capture real UI and copy with `/motion-studio:product-reel` stage 3 when there is a URL |
| 8 to 12 states in story order | logo, CTA, email field, loader, check, card, chart, command palette | one per downbeat |
| Real data per state | ask | labels, numbers, names from the product; no placeholders |
| Brand colors, fonts, one accent | from capture or `studio.json` | one accent |
| Track | synthesize `minimal` at 120 BPM | a supplied track must allow this use and start on a downbeat |
| Formats | `9x16`, `1x1`, `16x9` | same timeline, reframed |
| Loop | yes | the last state morphs back into the first |

## 2. Fill the spec

Copy the template into the project as `docs/spec.xml` and fill every `[...]` with the answers.
Keep all six sections: `<inputs>`, `<direction>`, `<structure>`, `<build>`, `<gotchas>`,
`<start>`. The motion-director subagent (`motion-studio:motion-director`) can draft it from your
brief; it writes only under `docs/`.

## 3. Put the states on the beat grid

1. Project: scaffold with `--loop` if needed
   (`node "${CLAUDE_PLUGIN_ROOT}/skills/studio-init/scripts/init.mjs" <dir> --title "<Product> morph" --loop --duration <S> --install`;
   in a skills-only install use `node "${CLAUDE_SKILL_DIR}/../studio-init/scripts/init.mjs" ...` with the same
   flags, or invoke the `studio-init` skill).
2. Grid: `node tools/score.mjs --style minimal --bpm 120 --dur <S>`, or measure the supplied track
   with `node tools/beats.mjs audio/<track>`.
3. State list with exact downbeat times, loop checks and warnings:

   ```
   node "${CLAUDE_SKILL_DIR}/scripts/statelist.mjs" --states "logo,CTA button,email field,loader,check,card,chart,palette" --out docs/statelist.md
   ```

   | Flag | Use |
   |---|---|
   | `--states "a,b:2,c"` or `--file list.txt` | states in order; `:2` holds a state for two bars |
   | `--beats audio/beats.json` | measured grid (default when the file exists); `--beats none` ignores it |
   | `--bpm N`, `--beats-per-bar N` | override the grid |
   | `--no-loop` | a film that ends instead of looping |
   | `--json` | one JSON line with every state's start, end and content timing |

   It warns when the loop length differs from `studio.json` `duration`, when the first state is not
   at 0 s (a pickup in the track), or when a state holds under 1 s. Fix the duration or trim the
   track before continuing.
4. Fill the cursor action, data and sound columns of `docs/statelist.md`, and copy the table into
   `docs/shotlist.md`.

## 4. The gate

Show the state list on the beat grid and wait for the user's OK before writing any film code (the
`<start>` section says so, and so does the course). A rejected list is rewritten, not coded around.
Record the decision on the `Status:` line of `docs/shotlist.md`.

## 5. Build from the verified example

Copy [assets/morph-loop.film.js](assets/morph-loop.film.js) to `film/film.js` and adapt it: it is a
working 16 s, 8-state loop at 120 BPM that renders in every format. Replace `STATES` (sizes in
1080 px units, fill token), `COPY` and the `content` painters with the approved list and the real
data; keep the mechanics:

| Mechanic | API | Rule |
|---|---|---|
| state times | `grid.bar(i)` | never typed seconds |
| container size, radius, color channels, camera zoom | `loopTrack(t, keys, dur, spring)` | keys in `[0, duration]`; the seam change once, as a key at the first downbeat (this example) or at t = duration |
| content in and out | `swapAlpha(t, start, nextStart)` | in after the morph starts, out before the next |
| cursor | `loopTrack` on x and y, `cursor(g, x, y, { pressed })` | clicks the current element on the downbeat |
| tabs and list highlights | `indicator(t, stops, { width })` | edges on different springs |
| sounds | `cues` from `grid.bar` and `grid.beat` | click on presses, swoosh under morphs |
| grain and other frame-indexed noise | `grain(g, W, H, c.frame % LOOP_FRAMES, ...)` with `LOOP_FRAMES = Math.round(dur * fps)` | the critique compares `hash(0)` with `hash(duration, { wrap: false })`; noise seeded by the raw frame index makes the end differ from frame 0 and fails the loop check |

Every pattern with its reasoning: [references/morph-recipes.md](references/morph-recipes.md).
Set `studio.json` `"loop": true` and `duration` to the loop length, then `npm run lint`.

## 6. Critique and deliver

Run the critique loop from `/motion-studio:motion-reel` (at least 3 rounds, all axes >= 8, no open P0; each round
builds the mixed render and both evidence passes first, see its pipeline runbook).
For morph loops also check `out/review/<fmt>/loop.png` (live pass only; the `video/` pass has no loop pane) and the "loop seam" metric
(`hash(0)` equals `hash(duration, { wrap: false })`, continuity ratio <= 1.5; the `|end - first|` pane must be black,
which frame-indexed grain breaks unless its index wraps), labels during every swap in `strip.png`, and the
cursor position at each downbeat in `contact.png`. A seam jump is a P0. Then `npm run build`.

## Rules for morph films

- One container, never cut. States differ by size, radius, fill and content only.
- A cursor causes every change; no state changes by itself.
- Something moves on every beat: a press, typing, a counter, a line drawing, a highlight step.
- Real product data only. Springs with at most a hint of overshoot; none on type.
- Banned: bouncy easing, glows, gradients on UI chrome, particle bursts, dead beats.
- Never `will-change` (or any rasterizing trick) on anything the camera scales.

Next: `/motion-studio:springs` to tune the feel; `/motion-studio:ship-formats` for per-format
reframing and delivery.

Files in this skill

  • SKILL.md7.1 KB
  • assets/morph-loop.film.js10.6 KB
  • assets/spec-template.xml4 KB
  • references/morph-recipes.md7.9 KB
  • scripts/statelist.mjs11.3 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.