Examples
There are three kinds of example, each a complete file:
- Showcase videos (this page): short finished videos, each rendered next to the file that made it. Start here to see what CutCraft can do.
- Recipes: files for the videos people most often make (an interview cut, a product demo, a vertical clip, variants). Each passes the lint and has a test; copy one as a starting point.
- The walkthrough: one launch video a model wrote from a brief, explained line by line.
- Feature examples in the guide: short files that each show one feature, rendered next to their code on the page that explains it (fonts, motion blur, your own effects and transitions, something over a cut).
Where to start
| You're making | Start from |
|---|---|
| A product launch or promo | Product launch, the walkthrough, Brand spot |
| An interview or podcast cut | Talking-head cut, Multicam highlights |
| A vertical short | Kinetic type, Captions and key points, Vertical clip from a podcast |
| A product or screen demo | Product demo with zoom-ins |
| Charts and numbers | Data story, Stats with handles |
| An intro, outro or logo sting | Logo reveal |
| A slideshow | Slideshow with motion |
| One video in many versions | Variants from one file |
| To see | Look at | Explained in |
|---|---|---|
Cuts on transcript words (in="word:12") | Product launch, Data story, Talking-head cut | Time by meaning |
Overlays on the word that names them (anchor + at) | Product launch, Kinetic type, Captions | Time by meaning |
| Captions from the transcript | Kinetic type, Captions, Vertical clip | Captions and sound |
three.js (<Three>) | Product launch, Logo reveal, Brand spot | Free code |
Motion (<Motion>) | Kinetic type, Data story, An edit | Free code |
Any React in a <Layer> | Data story (counters), Stats with handles | Free code |
| Transitions and keyframes | An edit, Slideshow | Elements |
| One prop that restyles everything (themes) | Brand spot | Themes |
Tweakable values (schema, defaults) | Every showcase file, Variants | The file |
| Music that ducks under speech | Talking-head cut, the walkthrough | Captions and sound |
| Captions from an SRT, VTT or Whisper file | Captions | Getting a transcript |
| Google Fonts or your own font files | Fonts example | Themes |
| Motion blur | Motion blur example | Exporting |
| Your own effects and transitions in GLSL | Duotone, circle reveal, colour split on a title | Free code |
| A flare or a sound over a cut | At a cut | Time by meaning |
Rendering them yourself
Each video is one file in examples/showcase, rendered with pnpm --filter @cutcraft/example-showcase render; the guide's feature examples, in examples/showcase/guide, with pnpm --filter @cutcraft/example-showcase docs. The footage is made by ffmpeg's own sources (pnpm --filter @cutcraft/example-showcase gen), and every file renders through the player in headless Chrome. The people on camera are stand-ins: their footage is a gradient, and the manifest holds the words they would say.
A product launch
The founder on camera, cut on her words, with b-roll in the middle. A <Three> layer turns a phone in on the word "Shed" and keeps it turning; its screen is the product's name drawn on a canvas. A Motion lower third names her, and each feature appears as a pill on the word that names it (anchor="talk" at="word:8"), so recutting the talk retimes every callout.
Shows: cuts on words, overlays on words, <Three>, <Motion>, a crossfade, tweakable props. Time by meaning · Free code · <Three> · <Motion>
import { z } from "zod";
import { Clip, Composition, Motion, Three, Track, Transition } from "@cutcraft/kit";
export const schema = z.object({ product: z.string(), founder: z.string(), accent: z.string() });
export const defaults = { product: "Shed", founder: "Maya Chen", accent: "#e8a33d" };
const pill = {
position: "absolute",
left: 56,
padding: "14px 22px",
borderRadius: 999,
background: "rgba(16, 20, 33, 0.82)",
color: "#f4efe6",
font: "600 30px 'Space Grotesk', sans-serif",
letterSpacing: "-0.01em",
} as const;
/**
* A product launch: the founder on camera, a three.js device turning beside them, and each feature called out on the
* word that names it. Trim the founder's clip and every callout moves with its word.
*/
export default function Video({ product, founder, accent }: z.infer<typeof schema>) {
return (
<Composition fps={30} width={960} height={540} theme={{ extends: "midnight", palette: { primary: accent } }}>
<Track id="main" name="Founder">
<Clip id="talk" src="asset:founder" in="word:0" out="word:11.end" fit="cover" />
<Transition id="x1" type="crossfade" duration={0.4} />
<Clip id="broll" src="asset:product" in={0} out={1.6} fit="cover" />
<Transition id="x2" type="crossfade" duration={0.4} />
<Clip id="close" src="asset:founder" in="word:12" out="word:25.end" fit="cover" />
</Track>
<Track id="device" name="Device" layout="free">
{/* The device turns in as the founder says the product's name, and keeps turning to the end. */}
<Three
id="phone"
anchor="talk"
at="word:1"
duration={12}
camera={{ position: [0, 0, 6], fov: 35 }}
setup={({ scene, THREE }) => {
const body = new THREE.Mesh(
new THREE.BoxGeometry(1.5, 3, 0.16),
new THREE.MeshStandardMaterial({ color: "#1b2a4a", metalness: 0.5, roughness: 0.3 }),
);
// The screen: the product's name drawn on a canvas, shown on the front face.
const art = document.createElement("canvas");
art.width = 300;
art.height = 600;
const g = art.getContext("2d") as CanvasRenderingContext2D;
const grad = g.createLinearGradient(0, 0, 0, 600);
grad.addColorStop(0, accent);
grad.addColorStop(1, "#c8402a");
g.fillStyle = grad;
g.fillRect(0, 0, 300, 600);
g.fillStyle = "#101421";
g.font = "700 64px 'Space Grotesk', sans-serif";
g.textAlign = "center";
g.fillText(product, 150, 320);
const texture = new THREE.CanvasTexture(art);
texture.colorSpace = THREE.SRGBColorSpace;
const screen = new THREE.Mesh(
new THREE.PlaneGeometry(1.36, 2.8),
new THREE.MeshBasicMaterial({ map: texture }),
);
screen.position.z = 0.082;
const phone = new THREE.Group();
phone.add(body, screen);
phone.position.x = 1.7;
scene.add(phone);
scene.add(new THREE.AmbientLight("#ffffff", 0.6));
const key = new THREE.DirectionalLight("#ffffff", 2.2);
key.position.set(-3, 4, 5);
scene.add(key);
return ({ time }) => {
const enter = Math.min(1, time / 1.2);
const ease = 1 - (1 - enter) ** 3;
phone.scale.setScalar(0.6 + 0.4 * ease);
phone.rotation.y = (1 - ease) * -Math.PI + Math.sin(time * 0.7) * 0.45;
phone.rotation.x = Math.sin(time * 0.5) * 0.08;
};
}}
/>
</Track>
<Track id="text" name="Text" layout="free">
<Motion
id="who"
anchor="talk"
offset={0.3}
duration={3}
sequence={[
[".bar", { scaleX: [0, 1] }, { duration: 0.45, ease: "easeOut" }],
[".name", { opacity: [0, 1], x: [-24, 0] }, { at: 0.15, duration: 0.4 }],
[".role", { opacity: [0, 1], x: [-24, 0] }, { at: 0.3, duration: 0.4 }],
]}
>
<div
className="bar"
style={{
position: "absolute",
left: 56,
top: 400,
width: 8,
height: 78,
background: accent,
transformOrigin: "left",
}}
/>
<div
className="name"
style={{
position: "absolute",
left: 80,
top: 398,
color: "#f4efe6",
font: "700 38px 'Space Grotesk', sans-serif",
}}
>
{founder}
</div>
<div
className="role"
style={{ position: "absolute", left: 80, top: 446, color: "#c9c2b8", font: "500 22px Inter, sans-serif" }}
>
Founder, {product}
</div>
</Motion>
<Motion
id="builds"
anchor="talk"
at="word:8"
duration={2.6}
sequence={[[".pill", { opacity: [0, 1], x: [-40, 0] }, { duration: 0.4, ease: "easeOut" }]]}
>
<div className="pill" style={{ ...pill, top: 200 }}>
Builds from your repo
</div>
</Motion>
<Motion
id="sandbox"
anchor="close"
at="word:17"
duration={2.4}
sequence={[[".pill", { opacity: [0, 1], x: [-40, 0] }, { duration: 0.4, ease: "easeOut" }]]}
>
<div className="pill" style={{ ...pill, top: 200 }}>
Every app in a sandbox
</div>
</Motion>
<Motion
id="zero"
anchor="close"
at="word:21"
duration={2}
sequence={[
[".pill", { opacity: [0, 1], scale: [0.8, 1] }, { duration: 0.35, ease: "easeOut" }],
[".pill", { backgroundColor: ["rgba(16, 20, 33, 0.82)", accent] }, { at: 0.35, duration: 0.3 }],
]}
>
<div className="pill" style={{ ...pill, top: 200 }}>
Scales to zero
</div>
</Motion>
</Track>
</Composition>
);
}Kinetic type
A vertical short. Each phrase is a <Motion> sequence anchored to the word that starts it: one slams in, one stacks word by word, one slides across. Karaoke captions run underneath.
Shows: <Motion> sequences anchored to words, karaoke captions, a vertical composition. Time by meaning · Captions and sound · <Motion>
import { z } from "zod";
import { Captions, Clip, Composition, Layer, Motion, Track } from "@cutcraft/kit";
export const schema = z.object({ accent: z.string() });
export const defaults = { accent: "#e8a33d" };
const big = {
position: "absolute",
left: 0,
right: 0,
textAlign: "center",
color: "#f4efe6",
font: "700 92px 'Space Grotesk', sans-serif",
letterSpacing: "-0.03em",
lineHeight: 1,
} as const;
const word = { display: "inline-block", marginRight: "0.22em" } as const;
/** One sequence step: a selector, its keyframes, its options. */
const slam: [string, Record<string, unknown>, Record<string, unknown>] = [
".k",
{ scale: [2.4, 1], opacity: [0, 1] },
{ duration: 0.35, ease: [0.2, 0.9, 0.2, 1] },
];
/**
* Kinetic type for a vertical short: each phrase slams or stacks in on the word that starts it, over the speaker and
* karaoke captions. Every layer is anchored to a word, so recutting the talk retimes the type.
*/
export default function Video({ accent }: z.infer<typeof schema>) {
return (
<Composition fps={30} width={540} height={960} theme={{ extends: "midnight", palette: { primary: accent } }}>
<Track id="main" name="Speaker">
<Clip id="talk" src="asset:speaker" in="word:0" out="word:24.end" fit="cover" />
</Track>
<Track id="shade" name="Shade" layout="free">
<Layer id="dim" start={0} duration={11.1}>
<div style={{ position: "absolute", inset: 0, background: "rgba(10, 12, 20, 0.45)" }} />
</Layer>
</Track>
<Track id="type" name="Type" layout="free">
<Motion id="every-cut" anchor="talk" at="word:0" duration={1.5} sequence={[slam]}>
<div className="k" style={{ ...big, top: 300 }}>
Every
<br />
<span style={{ color: accent }}>cut</span>
</div>
</Motion>
<Motion
id="line-of-code"
anchor="talk"
at="word:4"
duration={1.5}
sequence={[
[".a", { y: [80, 0], opacity: [0, 1] }, { duration: 0.3, ease: "easeOut" }],
[".b", { y: [80, 0], opacity: [0, 1] }, { at: 0.12, duration: 0.3, ease: "easeOut" }],
[
".c",
{ y: [80, 0], opacity: [0, 1], color: ["#f4efe6", accent] },
{ at: 0.24, duration: 0.3, ease: "easeOut" },
],
]}
>
<div style={{ ...big, top: 320, fontSize: 80 }}>
<span className="a" style={word}>
a line
</span>
<span className="b" style={word}>
of
</span>
<br />
<span className="c" style={word}>
code.
</span>
</div>
</Motion>
<Motion
id="move-a-clip"
anchor="talk"
at="word:7"
duration={1.6}
sequence={[
[".k", { x: [-540, 0] }, { duration: 0.4, ease: [0.2, 0.9, 0.2, 1] }],
[".k", { x: [0, 30] }, { at: 0.5, duration: 1, ease: "linear" }],
]}
>
<div className="k" style={{ ...big, top: 340, fontSize: 84 }}>
Move a clip
</div>
</Motion>
<Motion
id="words-move"
anchor="talk"
at="word:11"
duration={1.9}
sequence={[
[".a", { opacity: [0, 1], rotate: [-8, 0] }, { duration: 0.3 }],
[".b", { opacity: [0, 1], rotate: [8, 0] }, { at: 0.15, duration: 0.3 }],
[".c", { opacity: [0, 1], scale: [0.6, 1] }, { at: 0.3, duration: 0.3 }],
]}
>
<div style={{ ...big, top: 300, fontSize: 80 }}>
<span className="a" style={word}>
the words
</span>
<br />
<span className="b" style={{ ...word, color: accent }}>
move
</span>
<br />
<span className="c" style={word}>
with it.
</span>
</div>
</Motion>
<Motion id="point-lands" anchor="talk" at="word:16" duration={2.1} sequence={[slam]}>
<div className="k" style={{ ...big, top: 320, fontSize: 84 }}>
The point
<br />
lands
</div>
</Motion>
<Motion
id="names-it"
anchor="talk"
at="word:21"
duration={1.6}
sequence={[
[".k", { opacity: [0, 1], y: [40, 0] }, { duration: 0.3 }],
[".u", { scaleX: [0, 1] }, { at: 0.25, duration: 0.35, ease: "easeOut" }],
]}
>
<div className="k" style={{ ...big, top: 330, fontSize: 84 }}>
on the word.
</div>
<div
className="u"
style={{
position: "absolute",
left: 90,
right: 90,
top: 430,
height: 10,
background: accent,
transformOrigin: "left",
}}
/>
</Motion>
</Track>
<Captions id="captions" src="asset:speaker" style={{ size: 30 }} animation="karaoke" />
</Composition>
);
}A data story
A chart written as plain SVG and animated by Motion: each bar grows on the word that says its number. The counters are a few lines of React in a <Layer>, counting up on the layer's clock.
Shows: SVG animated by <Motion>, React counters in a <Layer> reading useLayer(), numbers on the words that say them. Free code · <Layer> · <Motion>
import { z } from "zod";
import { Clip, Composition, Layer, Motion, Track, useLayer } from "@cutcraft/kit";
export const schema = z.object({ accent: z.string() });
export const defaults = { accent: "#e8a33d" };
const label = { fill: "#c9c2b8", font: "500 18px Inter, sans-serif" } as const;
const value = { fill: "#f4efe6", font: "700 26px 'Space Grotesk', sans-serif" } as const;
const card = { position: "absolute", left: 520, width: 380, height: 260, top: 60 } as const;
/** A number that counts up over its layer's first second: any React, on the video's clock. */
function Count({
to,
prefix = "",
suffix = "",
label,
color,
}: {
to: number;
prefix?: string;
suffix?: string;
label: string;
color: string;
}) {
const { elapsed } = useLayer();
const t = Math.min(1, elapsed / 0.9);
const shown = Math.round(to * (1 - (1 - t) ** 3));
return (
<div style={{ position: "absolute", left: 72, top: 150, opacity: Math.min(1, elapsed / 0.2) }}>
<div style={{ color, font: "700 150px 'Space Grotesk', sans-serif", letterSpacing: "-0.05em", lineHeight: 1 }}>
{prefix}
{shown}
{suffix}
</div>
<div style={{ color: "#f4efe6", font: "500 30px Inter, sans-serif", marginTop: 12 }}>{label}</div>
</div>
);
}
/**
* A data story: each bar grows, and each number lands, on the word that says it. The chart is plain SVG animated by
* Motion; the counters are a few lines of React reading the layer's clock.
*/
export default function Video({ accent }: z.infer<typeof schema>) {
return (
<Composition fps={30} width={960} height={540} theme={{ extends: "midnight", palette: { primary: accent } }}>
<Track id="main" name="Analyst">
<Clip id="talk" src="asset:analyst" in="word:0" out="word:23.end" fit="cover" />
</Track>
<Track id="chart" name="Chart" layout="free">
{/* The frame draws in as the analyst starts. */}
<Motion
id="frame"
anchor="talk"
at="word:0"
duration={7.4}
sequence={[
[".panel", { opacity: [0, 1] }, { duration: 0.4 }],
[".axis", { pathLength: [0, 1] }, { at: 0.2, duration: 0.6, ease: "easeOut" }],
[".title", { opacity: [0, 1], y: [10, 0] }, { at: 0.4, duration: 0.4 }],
]}
>
<div className="panel" style={{ ...card, borderRadius: 18, background: "rgba(16, 20, 33, 0.78)" }} />
<svg width="960" height="540" style={{ position: "absolute", inset: 0 }}>
<title>Release time</title>
<text className="title" x="548" y="100" style={{ ...label, font: "600 20px Inter, sans-serif" }}>
Hours per release
</text>
<path className="axis" d="M 560 290 L 860 290" stroke="#5a6478" strokeWidth="2" fill="none" />
</svg>
</Motion>
{/* Six hours: the tall bar, on "six". */}
<Motion
id="before"
anchor="talk"
at="word:5"
duration={5.1}
sequence={[
[".bar", { scaleY: [0, 1] }, { duration: 0.6, ease: [0.2, 0.9, 0.2, 1] }],
[".num", { opacity: [0, 1] }, { at: 0.4, duration: 0.3 }],
]}
>
<svg width="960" height="540" style={{ position: "absolute", inset: 0 }}>
<title>Last year: six hours</title>
<rect
className="bar"
x="600"
y="160"
width="90"
height="130"
fill="#5a6478"
style={{ transformOrigin: "645px 290px" }}
/>
<text className="num" x="645" y="150" textAnchor="middle" style={value}>
6 h
</text>
<text x="645" y="312" textAnchor="middle" style={label}>
last year
</text>
</svg>
</Motion>
{/* Ninety minutes: the short bar, on "ninety". */}
<Motion
id="after"
anchor="talk"
at="word:10"
duration={2.6}
sequence={[
[".bar", { scaleY: [0, 1] }, { duration: 0.6, ease: [0.2, 0.9, 0.2, 1] }],
[".num", { opacity: [0, 1] }, { at: 0.4, duration: 0.3 }],
]}
>
<svg width="960" height="540" style={{ position: "absolute", inset: 0 }}>
<title>Today: ninety minutes</title>
<rect
className="bar"
x="730"
y="258"
width="90"
height="32"
fill={accent}
style={{ transformOrigin: "775px 290px" }}
/>
<text className="num" x="775" y="248" textAnchor="middle" style={value}>
1.5 h
</text>
<text x="775" y="312" textAnchor="middle" style={label}>
today
</text>
</svg>
</Motion>
</Track>
<Track id="numbers" name="Numbers" layout="free">
<Layer id="four" anchor="talk" at="word:14" duration={2.4}>
<Count to={4} suffix="×" label="as many releases" color={accent} />
</Layer>
<Layer id="forty" anchor="talk" at="word:22" duration={0.9}>
<Count to={40} prefix="−" suffix="%" label="fewer incidents" color="#7fd1b9" />
</Layer>
</Track>
</Composition>
);
}A logo reveal
Three thousand three.js particles, scattered by a seeded random, settle into the name sampled from a canvas, then a Motion tagline slides in under it. Every particle's place is a function of time, so a frame looks the same however it is reached.
Shows: a three.js particle system written as a function of time, a seeded random, a <Motion> hand-off. Free code · <Three>
import { z } from "zod";
import { Composition, Motion, Three, Track } from "@cutcraft/kit";
export const schema = z.object({ name: z.string(), tagline: z.string(), accent: z.string() });
export const defaults = { name: "Shed", tagline: "A cloud for small software", accent: "#e8a33d" };
/**
* A logo reveal for an intro or outro: three.js particles drift in and settle into the name, which then hands off
* to a lower third. Every particle's place is a function of time, so the export draws exactly what the preview shows.
*/
export default function Video({ name, tagline, accent }: z.infer<typeof schema>) {
return (
<Composition fps={30} width={960} height={540} theme={{ extends: "midnight", palette: { primary: accent } }}>
<Track id="scene" name="Scene" layout="free">
<Three
id="particles"
start={0}
duration={6}
background="#101421"
camera={{ position: [0, 0, 10], fov: 40 }}
setup={({ scene, THREE }) => {
// Where the particles end: points sampled from the name drawn on a canvas.
const W = 480;
const H = 160;
const art = document.createElement("canvas");
art.width = W;
art.height = H;
const g = art.getContext("2d") as CanvasRenderingContext2D;
g.fillStyle = "#fff";
g.font = "700 132px 'Space Grotesk', sans-serif";
g.textAlign = "center";
g.textBaseline = "middle";
g.fillText(name, W / 2, H / 2);
const pixels = g.getImageData(0, 0, W, H).data;
const targets: number[] = [];
for (let y = 0; y < H; y += 3)
for (let x = 0; x < W; x += 3)
if ((pixels[(y * W + x) * 4 + 3] as number) > 128) targets.push((x - W / 2) / 48, (H / 2 - y) / 48, 0);
// Seeded, so every run (and every export) scatters the same way.
let seed = 7;
const random = () => {
seed = (seed * 16807) % 2147483647;
return seed / 2147483647;
};
const count = 3000;
const from = new Float32Array(count * 3);
const to = new Float32Array(count * 3);
const delay = new Float32Array(count);
for (let i = 0; i < count; i++) {
const k = (i % (targets.length / 3)) * 3;
to.set([targets[k] as number, targets[k + 1] as number, targets[k + 2] as number], i * 3);
const r = 6 + random() * 6;
const a = random() * Math.PI * 2;
from.set([Math.cos(a) * r, Math.sin(a) * r * 0.6, (random() - 0.5) * 8], i * 3);
delay[i] = random() * 0.8;
}
const positions = new Float32Array(count * 3);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
const points = new THREE.Points(geometry, new THREE.PointsMaterial({ color: accent, size: 0.07 }));
scene.add(points);
return ({ time }) => {
for (let i = 0; i < count; i++) {
const p = Math.min(1, Math.max(0, (time - (delay[i] as number)) / 1.8));
const e = 1 - (1 - p) ** 4;
for (let c = 0; c < 3; c++) {
const j = i * 3 + c;
positions[j] = (from[j] as number) + ((to[j] as number) - (from[j] as number)) * e;
}
}
geometry.attributes.position.needsUpdate = true;
points.rotation.y = Math.max(0, 2.6 - time) * 0.12;
// Once settled, the name lifts to make room for the lower third.
points.position.y = Math.min(1, Math.max(0, (time - 3) / 0.6)) * 0.8;
};
}}
/>
</Track>
<Track id="text" name="Text" layout="free">
<Motion
id="tagline"
start={3.2}
duration={2.8}
sequence={[
[".rule", { scaleX: [0, 1] }, { duration: 0.5, ease: "easeOut" }],
[".line", { opacity: [0, 1], y: [16, 0] }, { at: 0.2, duration: 0.45, ease: "easeOut" }],
]}
>
<div
className="rule"
style={{ position: "absolute", left: 330, width: 300, top: 352, height: 4, background: accent }}
/>
<div
className="line"
style={{
position: "absolute",
left: 0,
right: 0,
top: 372,
textAlign: "center",
color: "#f4efe6",
font: "500 28px Inter, sans-serif",
}}
>
{tagline}
</div>
</Motion>
</Track>
</Composition>
);
}An edit
Three shots cut back to back on a sequence track: a wipe and a slide sit between them as siblings, the middle shot pushes in with four keyframes, a title builds word by word, and a Motion lower third comes in over the second shot. The overlays anchor to the shots, so they move when a cut moves.
Shows: a sequence track, transitions as siblings, keyframes, titles anchored to shots, a <Motion> lower third. Time by meaning · <Transition> · <Keyframe>
import { Clip, Composition, Keyframe, Motion, Title, Track, Transition } from "@cutcraft/kit";
/** An edit: three shots cut back to back, a wipe and a slide between them, a slow push-in, a title and a Motion lower third. */
export default function Video() {
return (
<Composition fps={30} width={960} height={540} theme={{ extends: "midnight", palette: { primary: "#c8402a" } }}>
<Track id="main" name="Story">
<Clip id="open" src="asset:aurora" in={0} out={3} fit="cover" />
<Transition id="x1" type="wipe" duration={0.6} params={{ direction: "left" }} />
<Clip id="zoom" src="asset:spiral" in={2} out={5.5} fit="cover">
{/* A slow push-in: two keys on each scale axis. */}
<Keyframe id="zoom-sx-0" property="transform.scale.x" at={0} value={1} />
<Keyframe id="zoom-sx-1" property="transform.scale.x" at={3.5} value={1.2} easing="easeInOutSine" />
<Keyframe id="zoom-sy-0" property="transform.scale.y" at={0} value={1} />
<Keyframe id="zoom-sy-1" property="transform.scale.y" at={3.5} value={1.2} easing="easeInOutSine" />
</Clip>
<Transition id="x2" type="slide" duration={0.6} params={{ direction: "up" }} />
<Clip id="close" src="asset:cells" in={0} out={2.6} fit="cover" />
</Track>
<Track id="text" name="Text">
<Title
id="title"
anchor="open"
offset={0.3}
duration={2.2}
style="h1"
size={64}
animation={{ preset: "word-by-word" }}
>
Cut by words
</Title>
<Motion
id="who"
anchor="zoom"
offset={0.4}
duration={2.6}
sequence={[
[".bar", { scaleX: [0, 1] }, { duration: 0.4, ease: "easeOut" }],
[".name", { opacity: [0, 1], x: [-20, 0] }, { at: 0.15, duration: 0.35 }],
[".role", { opacity: [0, 1], x: [-20, 0] }, { at: 0.3, duration: 0.35 }],
]}
>
<div
className="bar"
style={{
position: "absolute",
left: 48,
top: 404,
width: 6,
height: 70,
background: "#c8402a",
transformOrigin: "left",
}}
/>
<div
className="name"
style={{
position: "absolute",
left: 68,
top: 402,
color: "#f4efe6",
font: "700 32px 'Space Grotesk', sans-serif",
}}
>
Every cut is a line
</div>
<div
className="role"
style={{ position: "absolute", left: 68, top: 444, color: "#c9c2b8", font: "500 20px Inter, sans-serif" }}
>
and every line is editable
</div>
</Motion>
<Title id="end" anchor="close" offset={0.2} duration={2.2} style="h2" animation={{ preset: "slide-up" }}>
The file is the video.
</Title>
</Track>
</Composition>
);
}Captions and key points
A vertical short. The captions are generated from the words the clip plays and highlight word by word; the key point is anchored to the clip and timed to the word that names it (at="word:16"), so trimming the clip keeps it on that word.
Shows: cuts on words, generated karaoke captions, a title on the word that names it. Captions and sound · Time by meaning
import { Captions, Clip, Composition, Title, Track } from "@cutcraft/kit";
/** A vertical short: captions come from the words the clip plays, and the key point starts on the word that names it. */
export default function Video() {
return (
<Composition fps={30} width={540} height={960} theme={{ extends: "midnight", palette: { primary: "#c8402a" } }}>
<Track id="main" name="Speaker">
<Clip id="talk" src="asset:speaker" in="word:0" out="word:24.end" fit="cover" />
</Track>
<Track id="points" name="Key points">
<Title
id="point"
anchor="talk"
at="word:16"
duration={3.4}
style="note"
width={420}
height={84}
size={34}
transform={{ position: { x: 0, y: 115 }, borderRadius: 18 }}
>
The point lands here
</Title>
</Track>
<Captions id="captions" src="asset:speaker" style={{ size: 34 }} animation="karaoke" />
</Composition>
);
}