All demos

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/tegami

Render 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=95

The prompt

Reconstructed draft

A 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.