Tegami — Releasing, as simple as writing a note
Install this video
Adds the full composition, its remocn dependencies, and the prompt to your Remotion project via the shadcn registry.
$ pnpm dlx shadcn@latest add kapishdima/remocn-demo/tegamiRender it locally
Renders the MP4 on your machine with the Remotion CLI.
$ pnpm dlx remotion render tegami out/tegami.mp4 --scale=2 --crf=15 --x264-preset=slower --jpeg-quality=95The prompt
Reconstructed draftA reconstruction of the prompt this video was generated from.
Make an introducing video for Tegami, a tool for managing changelogs, versioning, and publishing in monorepos. Lean fully into its hand-drawn brand — black ink on white paper, sketchy doodles, the てがみ mascot, handwritten Caveat-style annotations, nothing digital-looking. Do a before/after arc: release day chores piling up, the old fixes breaking down (a brittle script, fighting Changesets), then "Meet Tegami" — a script you actually own, writing a change like a little note in .tegami/, tegami version computing the bumps, merging the Version Packages PR and it ships with GitHub releases automatically. Cover that it's one pipeline across npm, Cargo, and PyPI, retry-safe and pluggable, and that an AI agent can write the changelog for you. Mention migrating from Changesets, and close with "record your changes!" and tegami.fuma-nama.dev. Build the paper/ink motion with remocn.
The code
The exact source the AI wrote — the same files the install command puts in your project.
import React, { type CSSProperties, type ReactNode } from "react";
import { AbsoluteFill, Easing, Img, interpolate, useCurrentFrame, useVideoConfig } from "remotion";
import { demoAsset } from "@/lib/demo-assets";
import {
TransitionSeries,
linearTiming,
type TransitionPresentation,
type TransitionPresentationComponentProps,
} from "@remotion/transitions";
import { loadFont as loadSans } from "@remotion/google-fonts/Manrope";
import { loadFont as loadMono } from "@remotion/google-fonts/GeistMono";
import { loadFont as loadHand } from "@remotion/google-fonts/Caveat";
import { CheckIcon, CopyIcon } from "lucide-react";
import { RemocnUIProvider } from "@/lib/remocn-ui";
import { BlurIn } from "@/components/remocn/blur-in";
import { useBlurInTransition } from "@/components/remocn/use-blur-in-transition";
import { StrikethroughReplace } from "@/components/remocn/strikethrough-replace";
// ---------------------------------------------------------------------------
// Fonts — the Tegami brand: a bold geometric sans (Manrope) for the wordmark
// and headlines, a handwritten script (Caveat) for the margin notes / "record
// your changes!" annotations, and GeistMono for code. Bound to the CSS vars
// the remocn text components read.
// ---------------------------------------------------------------------------
const { fontFamily: SANS_FAMILY } = loadSans("normal", {
subsets: ["latin"],
weights: ["400", "500", "600", "700", "800"],
});
const { fontFamily: MONO_FAMILY } = loadMono("normal", {
subsets: ["latin"],
weights: ["400", "500", "700"],
});
const { fontFamily: HAND_FAMILY } = loadHand("normal", {
subsets: ["latin"],
weights: ["400", "500", "600", "700"],
});
const SANS = "var(--font-geist-sans), -apple-system, sans-serif";
const MONO = "var(--font-geist-mono), ui-monospace, monospace";
const HAND = "var(--font-hand), cursive";
// Monochrome ink-on-paper palette. Pure black ink on warm white paper — the
// brand uses no color accent at all, and neither do we.
const PAPER = "#FFFFFF";
const INK = "#161616";
const FAINT = "rgba(22,22,22,0.52)";
const HAIR = "rgba(22,22,22,0.16)";
const DOT = "#EEEEEE";
const BANNER = demoAsset("tegami-banner.png");
const LOGO = demoAsset("tegami-logo.png");
// ---------------------------------------------------------------------------
// Scene timings (frames @ 30fps). Transitions overlap and are subtracted.
// ---------------------------------------------------------------------------
const S_AVALANCHE = 150; // hook — release chores pile up
const S_BREAKS = 120; // the old fixes fail
const S_MEET = 110; // Meet Tegami (banner reveal)
const S_SCRIPT = 150; // a script you own (paper code)
const S_NOTE = 165; // write the change like a note
const S_VERSION = 150; // version & lock
const S_SHIP = 175; // merge -> ship
const S_REGISTRY = 130; // not just npm (constellation)
const S_STACK = 150; // retry-safe / plugins / programmable
const S_AGENT = 120; // agent writes the changelog
const S_MIGRATE = 110; // coming from Changesets
const S_CTA = 130; // record your changes
const T_X = 14; // crossfade (same visual world)
const T_Z1 = 18; // problem -> solution (zoom-through)
const T_Z2 = 16; // intro -> demo
const T_P = 14; // push-slide between demo steps
const T_Z3 = 16; // demo -> benefits
const T_Z4 = 18; // -> CTA
export const TEGAMI_DURATION =
S_AVALANCHE +
S_BREAKS +
S_MEET +
S_SCRIPT +
S_NOTE +
S_VERSION +
S_SHIP +
S_REGISTRY +
S_STACK +
S_AGENT +
S_MIGRATE +
S_CTA -
(T_X + T_Z1 + T_Z2 + T_X + T_P + T_P + T_Z3 + T_X + T_X + T_X + T_Z4);
// ===========================================================================
// Shared primitives
// ===========================================================================
// Blur-in reveal, driven by the remocn-ui timeline hook.
const Reveal: React.FC<{
children: ReactNode;
delay?: number;
distance?: number;
blur?: number;
duration?: number;
direction?: "up" | "down" | "left" | "right";
display?: CSSProperties["display"];
}> = ({
children,
delay = 0,
distance = 16,
blur = 10,
duration = 20,
direction = "up",
display = "block",
}) => {
const style = useBlurInTransition(
[{ at: delay, state: "revealed", duration }],
{ direction, distance, blur },
);
return (
<BlurIn style={style} display={display}>
{children}
</BlurIn>
);
};
// Handwritten annotation (Caveat) — the brand's margin-note voice.
const Hand: React.FC<{
children: ReactNode;
size?: number;
color?: string;
style?: CSSProperties;
}> = ({ children, size = 34, color = INK, style }) => (
<span
style={{
fontFamily: HAND,
fontSize: size,
fontWeight: 600,
color,
lineHeight: 1.1,
display: "inline-block",
...style,
}}
>
{children}
</span>
);
// Inline, normal-flow typewriter (mono, left-aligned) — deterministic char
// reveal with a blinking block caret. Replaces the scene-centered Typewriter
// component so it can sit inside a card / prompt line.
const MonoType: React.FC<{
text: string;
delay?: number;
cps?: number;
fontSize?: number;
color?: string;
weight?: number;
}> = ({ text, delay = 0, cps = 16, fontSize = 20, color = INK, weight = 500 }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const chars = Math.floor(
interpolate(frame, [delay, delay + (text.length / cps) * fps], [0, text.length], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
);
const shown = text.slice(0, Math.max(0, chars));
const done = chars >= text.length;
const blinkOn = Math.floor((frame / fps) * 2) % 2 === 0;
const caretVisible = frame >= delay && (!done || blinkOn);
return (
<span
style={{
fontFamily: MONO,
fontSize,
fontWeight: weight,
color,
whiteSpace: "pre",
display: "inline-flex",
alignItems: "center",
}}
>
{shown}
<span
style={{
display: "inline-block",
width: fontSize * 0.5,
height: fontSize,
background: color,
marginLeft: 2,
opacity: caretVisible ? 1 : 0,
transform: "translateY(1px)",
}}
/>
</span>
);
};
const Heading: React.FC<{
children: ReactNode;
size?: number;
weight?: number;
style?: CSSProperties;
}> = ({ children, size = 44, weight = 700, style }) => (
<h2
style={{
margin: 0,
fontFamily: SANS,
fontWeight: weight,
fontSize: size,
letterSpacing: "-0.02em",
color: INK,
textAlign: "center",
lineHeight: 1.12,
...style,
}}
>
{children}
</h2>
);
// Stroke-draw hook — pairs with pathLength={1} strokeDasharray={1}.
const useDrawOffset = (delay: number, dur: number) => {
const frame = useCurrentFrame();
return interpolate(frame, [delay, delay + dur], [1, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.inOut(Easing.cubic),
});
};
// A single inked path that draws itself on.
const Stroke: React.FC<{
d: string;
delay?: number;
dur?: number;
width?: number;
color?: string;
}> = ({ d, delay = 0, dur = 24, width = 2.5, color = INK }) => {
const off = useDrawOffset(delay, dur);
return (
<path
d={d}
pathLength={1}
strokeDasharray={1}
strokeDashoffset={off}
fill="none"
stroke={color}
strokeWidth={width}
strokeLinecap="round"
strokeLinejoin="round"
/>
);
};
// A marker swipe that draws left-to-right — the brand's brush stroke.
const BrushUnderline: React.FC<{
width?: number;
delay?: number;
dur?: number;
thickness?: number;
}> = ({ width = 320, delay = 6, dur = 18, thickness = 9 }) => {
const off = useDrawOffset(delay, dur);
// A slightly wavy stroke so it reads as a hand swipe, not a ruler line.
const d = `M6 ${thickness + 6} C ${width * 0.28} ${thickness - 2}, ${
width * 0.6
} ${thickness + 11}, ${width - 6} ${thickness + 2}`;
return (
<svg width={width} height={thickness + 18} style={{ display: "block" }}>
<path
d={d}
pathLength={1}
strokeDasharray={1}
strokeDashoffset={off}
fill="none"
stroke={INK}
strokeWidth={thickness}
strokeLinecap="round"
/>
</svg>
);
};
// Empty sketch checkbox that ticks at `checkAt`.
const SketchCheck: React.FC<{
size?: number;
checkAt?: number | null;
}> = ({ size = 26, checkAt = null }) => {
const boxOff = useDrawOffset(0, 12);
const tickOff = useDrawOffset(checkAt ?? 1e9, 9);
return (
<svg width={size} height={size} viewBox="0 0 26 26">
<path
d="M4 5 Q3 4 5 4 L21 3.5 Q23 4 22.5 6 L22 21 Q22 23 20 22.5 L5 23 Q3 22.5 3.5 20 Z"
pathLength={1}
strokeDasharray={1}
strokeDashoffset={boxOff}
fill="none"
stroke={INK}
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
/>
{checkAt !== null && (
<path
d="M7 13 L11.5 18 L20 7"
pathLength={1}
strokeDasharray={1}
strokeDashoffset={tickOff}
fill="none"
stroke={INK}
strokeWidth={2.6}
strokeLinecap="round"
strokeLinejoin="round"
/>
)}
</svg>
);
};
// A paper card with a hand-inked border and a small tilt.
const PaperCard: React.FC<{
children: ReactNode;
width?: number | string;
tilt?: number;
padding?: number | string;
style?: CSSProperties;
}> = ({ children, width = 360, tilt = 0, padding = "26px 28px", style }) => (
<div
style={{
width,
padding,
background: PAPER,
border: `2px solid ${INK}`,
borderRadius: 16,
transform: `rotate(${tilt}deg)`,
boxShadow: `5px 6px 0 ${INK}`,
...style,
}}
>
{children}
</div>
);
// A light, ink-on-paper code card with line-by-line reveal.
const PaperCode: React.FC<{
title: string;
code: string;
width?: number;
fontSize?: number;
startDelay?: number;
stagger?: number;
tilt?: number;
}> = ({
title,
code,
width = 560,
fontSize = 18,
startDelay = 0,
stagger = 6,
tilt = -1,
}) => {
const lines = code.split("\n");
return (
<div
style={{
width,
background: PAPER,
border: `2px solid ${INK}`,
borderRadius: 14,
overflow: "hidden",
transform: `rotate(${tilt}deg)`,
boxShadow: `6px 7px 0 ${INK}`,
fontFamily: MONO,
}}
>
<div
style={{
height: 38,
display: "flex",
alignItems: "center",
gap: 8,
padding: "0 16px",
borderBottom: `2px solid ${INK}`,
}}
>
<Dot /> <Dot /> <Dot />
<span
style={{
flex: 1,
textAlign: "center",
fontFamily: MONO,
fontSize: 14,
color: FAINT,
}}
>
{title}
</span>
</div>
<div
style={{Showing the first 400 of 1678 lines. View the full file on GitHub.