Skip to content

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 makingStart from
A product launch or promoProduct launch, the walkthrough, Brand spot
An interview or podcast cutTalking-head cut, Multicam highlights
A vertical shortKinetic type, Captions and key points, Vertical clip from a podcast
A product or screen demoProduct demo with zoom-ins
Charts and numbersData story, Stats with handles
An intro, outro or logo stingLogo reveal
A slideshowSlideshow with motion
One video in many versionsVariants from one file
To seeLook atExplained in
Cuts on transcript words (in="word:12")Product launch, Data story, Talking-head cutTime by meaning
Overlays on the word that names them (anchor + at)Product launch, Kinetic type, CaptionsTime by meaning
Captions from the transcriptKinetic type, Captions, Vertical clipCaptions and sound
three.js (<Three>)Product launch, Logo reveal, Brand spotFree code
Motion (<Motion>)Kinetic type, Data story, An editFree code
Any React in a <Layer>Data story (counters), Stats with handlesFree code
Transitions and keyframesAn edit, SlideshowElements
One prop that restyles everything (themes)Brand spotThemes
Tweakable values (schema, defaults)Every showcase file, VariantsThe file
Music that ducks under speechTalking-head cut, the walkthroughCaptions and sound
Captions from an SRT, VTT or Whisper fileCaptionsGetting a transcript
Google Fonts or your own font filesFonts exampleThemes
Motion blurMotion blur exampleExporting
Your own effects and transitions in GLSLDuotone, circle reveal, colour split on a titleFree code
A flare or a sound over a cutAt a cutTime 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>

tsx
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>

tsx
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>

tsx
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>

tsx
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>

tsx
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

tsx
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>
  );
}

MIT licensed.