Skip to content
Back to skills

New Template

ASecurity

Find what kind of frame this kit cannot draw yet, then build it — for an agent working inside content-agent-kit. Use when the user says "template mới cho hôm nay", "thêm template", "add a template", "we need a frame for X", or asks what is missing from video-templates/. Also covers pulling one from the upstream HyperFrames registry when that is the cheaper answer.

  • 7 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 11, 2026
content-marketingjavascriptgojavabashnodegit

Works with

  • cli

Security analysis

A100/100

Scanned November 11, 2026

npx -y skills add XuHo-IT/Content-Agent-Kit --skill new-template --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of New Template?

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

Security grade badge for New Template
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/xuho-it-new-template/badge)](https://www.skillsdirectory.com/skills/xuho-it-new-template)

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: new-template
description: Find what kind of frame this kit cannot draw yet, then build it — for an agent working inside content-agent-kit. Use when the user says "template mới cho hôm nay", "thêm template", "add a template", "we need a frame for X", or asks what is missing from video-templates/. Also covers pulling one from the upstream HyperFrames registry when that is the cheaper answer.
---

# Add a template

Two different jobs share this name, and picking the wrong one wastes an hour.

| the user wants | do this |
|---|---|
| an effect, overlay, transition or caption style | **pull it** — one command, §1 |
| a kind of frame the kit cannot draw | **build it** — §2 onward |

## Hướng dẫn từng bước cho người dùng

> Chạy lệnh `node scripts/…` ở **thư mục gốc của kit** — thư mục có `AGENTS.md`, hai cấp trên file này (nếu skill được cài bằng `install-skills.mjs --kit`, đường dẫn nằm trong file `.kit-root` cạnh file này).

Dẫn người dùng theo đúng các bước dưới, bằng lời đơn giản. Trước khi bắt đầu: một câu nói skill sẽ làm gì, mất khoảng bao lâu, cần người dùng chuẩn bị gì. Đầu mỗi bước báo **"Bước n/N — <tên bước>"** kèm một câu đang làm gì. Khi cần người dùng làm gì, chỉ hỏi đúng một việc. Kết thúc: kết quả nằm ở đâu và việc nên làm tiếp.

| Bước | Agent làm gì | Người dùng cần làm |
|---|---|---|
| 1. Tìm chỗ còn thiếu | Liệt kê template có sẵn và chạy `motion-index.mjs` | Nói video của bạn đang thiếu kiểu khung nào |
| 2. Đề xuất | Đưa 2–3 phương án, mỗi phương án lấp một chỗ trống | Chọn một |
| 3. Lấy sẵn hoặc tự dựng | Kéo về bằng `add-template.mjs`, hoặc tự viết hai bản 9:16 và 16:9 | — |
| 4. Đo và ghi danh mục | Chạy `theme-probe.mjs --template <id>`, thêm mục vào `CATALOG.md` | — |
| 5. Xem bằng mắt | Render bằng `template-sheet.mjs <id>` cả hai khung hình (vài phút) | Duyệt ảnh |
| 6. Chạy test | `node --test tests/*.test.mjs` | — |

**Hay vướng:**
- Thiếu `NOTICE.md` hoặc thiếu mục trong `CATALOG.md` → test báo lỗi; thêm vào là hết.
- Chữ viết cứng trong HTML sẽ hiện trên mọi video → mọi chữ phải đi qua slot.
- Font không có dấu tiếng Việt → chạy `font-check.mjs` trước khi render.

## 0. Never start from "what would be cool"

A template with no gap behind it is a twenty-third way to show a headline. Find the gap
first, and be able to say it in one sentence before writing any CSS.

```bash
# what exists, and what each one is FOR
node -e "import('./scripts/video/lib/paths.mjs').then(m=>console.log(m.listTemplateIds().join('\n')))"
grep -n '^\*\*Role:\*\*' video-templates/CATALOG.md

# and how they MOVE — which techniques are well covered, which are nearly absent
node scripts/video/motion-index.mjs
```

The second command answers a question the first cannot. A gap is not only "no frame does this
job" — it is also "no frame moves this way". `mask-sweep` and `clip-reveal` have two samples
each; `draw-on` has thirteen. A new template that fills a thin row is worth more than one that
adds a fourteenth way to draw a line, and the counts tell you which is which.

Then read `templates/VIDEO_GENRES.template.json` and ask which beat has no good frame.

A real gap sounds like: *"`frame-chart-bars` compares magnitudes and `frame-split-compare`
compares states — nothing can strike a claim down."* A fake one sounds like *"we could use
another dark statement frame."*

**Say the gap out loud to the user before building.** They know what they publish; you are
guessing from a folder listing.

### If the user just says "a new template for today"

Do the survey, bring back **two or three candidates with the gap each one fills**, and let
them pick. Do not build five because "new templates" was plural — five templates nobody
needed is worse than one they will use.

### A SIGNATURE FRAME every episode — how to do it without rotting the library

A series can ask for one bespoke frame per episode so no two videos look alike. That is a
legitimate goal, and it is in tension with §0 above. Resolve it this way, not by suspending
the rule:

**The gap for a signature frame is the episode, not the library.** It sounds like *"this
episode turns on 40 days of quarantine against 40 days of fundraising, and nothing in the
library can hold two numbers that mirror each other and mean opposite things."* It does not
sound like *"episode 7 needs its own look."* If you cannot name the one image the episode
turns on, the episode does not need a frame — it needs a better script.

**Build ONE, for the beat that carries the video.** Usually the reveal or the twist. The other
fifteen scenes reuse library frames; that is what makes the signature one land.

**Name it for its job, not its episode.** `frame-mirror-numbers`, not `frame-poveglia-40`.
An episode-specific name guarantees nobody reuses it, which is how you end up with forty
single-use folders.

**Every contract in §2 still applies** — both aspects, both timeline keys, CATALOG entry,
ambient layer, the lot. A signature frame that renders black is not distinctive, it is broken.

**Review the pile every ~10 episodes.** Frames used twice or more graduate into the general
library and get documented as such. Frames used once and never again get deleted — leaving
them costs a CATALOG row, a theme probe, and a slower test suite for nothing. This review is
the whole reason the practice stays affordable; skip it and the library is landfill in a year.

## 1. Pulling from upstream is often the right answer

367 items live in the HyperFrames registry, and `registry-watch.mjs` reports what arrived
since the last check. For code-driven motion prompts and 12 visual style recipes (`productui`,
`explainer`, `collage`, `handdrawn`, `pixel`, `lowpoly`, `generative`, `photoreal`), also check
`opus-55-videos` (`yihui-dev/awesome-opus5-5-videos`) and `opus-video-styles` via
`node scripts/install-skills.mjs --list`.

```bash
node scripts/video/registry-watch.mjs                       # anything new upstream?
node scripts/video/add-template.mjs --list --grep caption   # search
node scripts/video/add-template.mjs <name> <name>           # pull
```

**What you get is building material, not a scene template.** The command says so itself:
blocks and components ship `compositions/` with no `index.html`, so `listTemplateIds()`
skips them and no `script.json` can name them. They are overlays and effects to compose
with. Turning one into a scene template still means writing both compositions by hand — at
which point you are in §2 anyway, just with a head start on the design.

Licence and attribution are handled: the installer writes a `NOTICE.md` with the source URL
and the pinned commit sha. Do not delete it.

## 2. Building one — the five files

Copy the shape of a recent template rather than inventing one. `frame-checklist` and
`frame-timeline` are the plainest.

```
video-templates/<id>/
├── index.html                    16:9 — data-width="1920" data-height="1080"
├── compositions/portrait.html    9:16 — data-width="1080" data-height="1920"
├── meta.json                     { "id": "<id>", "name": "Human Name" }
├── hyperframes.json              copy verbatim from any existing template
└── NOTICE.md                     who wrote it, why it exists, which slots
```

`id` is `frame-<kebab>`. The two compositions must expose the **same slot names** — a test
checks it, because two files drift the moment they are edited separately.

**Generate both from one source.** Every template added since v0.5.0 was emitted by a small
script that takes shared markup, shared JS and per-aspect layout CSS. Writing the same file
twice is how slot parity gets broken, and it is also how a fix lands in one aspect only.

### The rules, all enforced by `tests/templates.test.mjs`

- **Draw from data, never from a hardcoded value.** If a bar length and its printed number
  can disagree, one day they will.
- **No caller-facing text in the markup.** Every visible string arrives through a slot and is
  set in JS. Text sitting in the body renders on *every* video regardless of what the caller
  passed — found three times here, twice after it reached a finished render. Decorative
  punctuation (a quote mark, a tick) may be CSS `content`; a *word* may not.
- **An empty slot removes its element**, it does not render a placeholder. An empty heading
  reads as missing data; an absent one reads as a deliberate omission.
- **No default is someone else's brand.** No real URLs, no channel names in
  `data-composition-variables`.
- **Vietnamese-capable fonts.** Be Vietnam Pro, Lora, Alfa Slab One are known-good. Test with
  `ữ ệ ợ ằ` — a font missing diacritics renders `bước` as `bc`.

### Three traps that pass every test and still look wrong or stall rendering

**1. A delayed animation with no start state shows its ending first.**

```css
.thing { animation: rise 0.5s ease 0.45s forwards; }             /* wrong */
.thing { opacity: 0; animation: rise 0.5s ease 0.45s forwards; } /* base = the start */
.thing { animation: rise 0.5s ease 0.45s both; }                 /* or backwards fill */
```

`forwards` does nothing during the delay — the element renders with its normal style, which
for a `forwards` animation is where it *finishes*. Half a second of the finished state, then
a snap back. Use `both` when the start state differs between aspects.

**2. An animation must travel the same way as the thing it reveals.** A divider sweeping in from
the left while a `clip-path` uncovers from the right reads as two animations that happen to
end together. Both real bugs, both in shipped-looking code, neither catchable by a test.

**3. Missing sub-composition timeline registration (`window.__timelines["portrait"]`).**
Every composition (both `index.html` and `compositions/portrait.html`) MUST register its timeline under both IDs:

```javascript
window.__timelines = window.__timelines || {};
window.__timelines["main"] = window.__timelines["portrait"] = timeline;
```

If `compositions/portrait.html` only registers `"main"` or omits `"portrait"`, Puppeteer will wait **45 seconds** for the sub-composition ready signal, causing a 10x render slowdown. Always register both keys.

## 3. Measure, document, wire up

```bash
node scripts/video/theme-probe.mjs --template <id>   # writes theme-map.json
```

Themes flip lightness only on dark canvases. An unmeasured template is flipped on a guess,
which renders — wrongly, in exactly the way the theme system exists to prevent.

Then:

- a `## <id>` section in `video-templates/CATALOG.md`: role, best-for, and a **slot table with
  real character limits** you found by rendering long text, not by estimating
- raise the count in `.github/workflows/ci.yml` (`listTemplateIds().length < N`)
- add it to `NOTICE.md`'s list of templates original to this repo
- add it to a genre in `templates/VIDEO_GENRES.template.json` **only if it earns a beat there**
  — a template in no genre is fine; a genre with a frame that does not fit is not
- update the counts in README, README.en, `docs/16` and `.github/repo-about.json` — a test
  checks all seven against the folder

## 4. Look at it. The tests cannot.

```bash
node scripts/video/template-sheet.mjs <id> --out /tmp/check.jpg
node scripts/video/template-sheet.mjs <id> --aspect 16:9 --out /tmp/check-l.jpg
```

**Sample the animation, not just the settled frame** — tile three moments and open the image.
Everything visual ever caught in this repo was caught this way: two templates rendering
1920×1080 inside a 9:16 video with correct CSS and a correct viewport; a strike-through that
crossed only the first line of a two-line claim; an eleven-character name wrapping so a
shutter wiped two lines instead of one.

Render with **real content at realistic length**, and check the awkward cases: the longest
string a caller might pass, an empty optional slot, one item in a list that expects four.

> `data-composition-variables` does **not** reach the renderer. Those are the editor's preview
> defaults; `composeTemplate({ inputs: {} })` comes back blank. Pass inputs explicitly, or
> read the defaults out of the HTML and pass those.

## 5. Ship it

```bash
node --test tests/*.test.mjs
```

One PR per template batch, with the strip image in the description so a reviewer sees the
frames without checking out the branch. Say which gap each one fills — if that sentence is
hard to write, the template probably should not exist.

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.

View all in content-marketing →

Comments

Loading comments…

Newsletter

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