All demos

remocn ✕ Gramotion — New sponsor

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/sponsor-gramotion

Render it locally

Renders the MP4 on your machine with the Remotion CLI.

$ pnpm dlx remotion render sponsor-gramotion out/sponsor-gramotion.mp4 --scale=2 --crf=15 --x264-preset=slower --jpeg-quality=95 --gl=angle

The prompt

The prompt this video was generated from.

Make a partner-level sponsor video for Gramotion (gramotion.video) — pull their style, logo, and fonts from the site: dark #0a0a0a canvas, #fafafa ink, the orange #ff6825 accent, the lowercase "gramotion" wordmark with its orange dot, Cal Sans headings over Plus Jakarta Sans body with JetBrains Mono details. Keep the family grammar of the other sponsor spots. Backdrop is the paper.design warp shader preset (checks shape, colors 3c1515/944752/ffc085, swirl 0.9 with 6 iterations, scale 2.5) as its own component in this repo, pushed back by a vignette. Open with "Say hello to my new sponsor" in a soft blur-in, reveal the gramotion wordmark with the dot springing in and "Create stunning motion graphics" settling beneath, cut their three-step flow into beats (Design / Animate / Export), each acted out inside a tiny code-drawn fragment of the Gramotion editor — shapes dropping onto a canvas with a cursor landing a handled selection, a timeline where the cursor drags the orange animation bar under a sweeping playhead, an export progress bar resolving into shipped mp4 and gif files — stack four feature lines line by line, count up to 1,200 on the waitlist (plain number, no plus), land "Be there at the first frame", and close on the Remocn ✕ gramotion lockup alone, centered, no URL on screen. A quiet mono timecode ticks along the bottom the whole time. No letter-spacing, no uppercase, no pills, no badges, no pulsing, compact spacing, no trailing periods, no em-dashes. Dynamic text via soft-blur-in and line-by-line-slide. Push-through and focus-pull between scenes.

The code

The exact source the AI wrote — the same files the install command puts in your project.

import React from "react";
import { AbsoluteFill, Easing, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
import { TransitionSeries, linearTiming, type TransitionPresentation, type TransitionPresentationComponentProps } from "@remotion/transitions";
import { loadFont as loadCalSans } from "@remotion/google-fonts/CalSans";
import { loadFont as loadJakarta } from "@remotion/google-fonts/PlusJakartaSans";
import { loadFont as loadJetMono } from "@remotion/google-fonts/JetBrainsMono";

import { GramotionWarp } from "./gramotion-warp";
import { SoftBlurIn } from "@/components/remocn/soft-blur-in";
import { LineByLineSlide } from "@/components/remocn/line-by-line-slide";
import { pushThrough } from "@/components/remocn/push-through";
import { focusPull } from "@/components/remocn/focus-pull";

// Gramotion speaks in three voices, exactly as gramotion.video pairs them:
// Cal Sans for the headings, Plus Jakarta Sans for the body, JetBrains Mono
// for the timeline register. Cal Sans ships one weight only — 400 everywhere.
const { fontFamily: HEADING_FAMILY } = loadCalSans("normal", {
  subsets: ["latin"],
  weights: ["400"],
});
const { fontFamily: BODY_FAMILY } = loadJakarta("normal", {
  subsets: ["latin"],
  weights: ["400", "500"],
});
const { fontFamily: MONO_FAMILY } = loadJetMono("normal", {
  subsets: ["latin"],
  weights: ["400"],
});

const HEADING = `${HEADING_FAMILY}, -apple-system, BlinkMacSystemFont, sans-serif`;
const BODY = `${BODY_FAMILY}, -apple-system, BlinkMacSystemFont, sans-serif`;
const MONO = `${MONO_FAMILY}, ui-monospace, SFMono-Regular, monospace`;

// Palette lifted from gramotion.video itself: #0a0a0a body background,
// #fafafa ink, #a1a1a1 muted copy, #ff6825 the single orange accent that
// also dots the wordmark.
const BG = "#0a0a0a";
const INK = "#fafafa";
const MUTED = "rgba(250,250,250,0.62)";
const FAINT = "rgba(250,250,250,0.4)";
const ORANGE = "#ff6825";

const clampOpts = {
  extrapolateLeft: "clamp" as const,
  extrapolateRight: "clamp" as const,
};

// ---------------------------------------------------------------------------
// Scene timings (frames @ 30fps). Transitions overlap.
// ---------------------------------------------------------------------------
const S_HOOK = 66; //     "Say hello to my new sponsor"
const S_REVEAL = 100; //  gramotion wordmark + orange dot + tagline
const S_FEATURES = 90; // line-by-line value stack
const S_COUNT = 70; //    1,200 waitlist count-up
const S_CTA = 64; //      "Be there at the first frame"
const S_LOCKUP = 120; //  Remocn ✕ gramotion + gramotion.video

// How it works — the site's own three-step flow, cut into hard beats.
const STEPS = [
  {
    name: "Design",
    blurb: "Shapes, text, images and video on one canvas",
  },
  {
    name: "Animate",
    blurb: "Drag a bar on the timeline, no keyframes to wrangle",
  },
  {
    name: "Export",
    blurb: "MP4 or GIF in seconds",
  },
];
const STEP_DURS = [56, 56, 66];
const S_STEPS = STEP_DURS.reduce((a, b) => a + b, 0);

const T_PT = 16; // push-through
const T_FP = 16; // focus-pull
const T_X = 14; //  crossfade

export const SPONSOR_GRAMOTION_DURATION =
  S_HOOK +
  S_REVEAL +
  S_STEPS +
  S_FEATURES +
  S_COUNT +
  S_CTA +
  S_LOCKUP -
  (T_PT + T_FP + T_X + T_FP + T_X + T_PT);

// Global frame where the lockup scene enters — the timecode bows out here.
const LOCKUP_START = SPONSOR_GRAMOTION_DURATION - S_LOCKUP;

// ---------------------------------------------------------------------------
// Slow camera drift — every scene rides a barely-there push-in so no frame
// is ever static. durationInFrames is Sequence-scoped inside TransitionSeries.
// ---------------------------------------------------------------------------
const Drift: React.FC<{ children: React.ReactNode; grow?: number }> = ({
  children,
  grow = 0.035,
}) => {
  const frame = useCurrentFrame();
  const { durationInFrames } = useVideoConfig();
  const scale = interpolate(frame, [0, durationInFrames], [1, 1 + grow]);
  return (
    <AbsoluteFill style={{ transform: `scale(${scale})` }}>
      {children}
    </AbsoluteFill>
  );
};

// ---------------------------------------------------------------------------
// Per-word rise — each word resolves out of blur while rising onto the
// baseline. The body voice of the spot, in Plus Jakarta Sans.
// ---------------------------------------------------------------------------
const WordsRise: React.FC<{
  text: string;
  fontSize: number;
  color?: string;
  delay?: number;
  stagger?: number;
  fontFamily?: string;
}> = ({
  text,
  fontSize,
  color = INK,
  delay = 0,
  stagger = 3,
  fontFamily = BODY,
}) => {
  const frame = useCurrentFrame();
  const ease = Easing.bezier(0.2, 0.8, 0.2, 1);
  const words = text.split(" ");
  return (
    <span
      style={{
        fontFamily,
        fontWeight: 400,
        fontSize,
        color,
        lineHeight: 1.3,
      }}
    >
      {words.map((word, i) => {
        const local = frame - delay - i * stagger;
        const p = interpolate(local, [0, 24], [0, 1], {
          ...clampOpts,
          easing: ease,
        });
        // Travel lands at ~60% — the eased tail clicks the word down the pixel
        // grid one pixel every few frames; opacity/blur keep the full curve.
        const py = interpolate(local, [0, 14], [0, 1], {
          ...clampOpts,
          easing: ease,
        });
        return (
          <span
            key={i}
            style={{
              display: "inline-block",
              whiteSpace: "pre",
              opacity: p,
              transform: `translateY(${(1 - py) * 24}px)`,
              filter: p < 1 ? `blur(${(1 - p) * 9}px)` : undefined,
            }}
          >
            {word}
            {i < words.length - 1 ? " " : ""}
          </span>
        );
      })}
    </span>
  );
};

// ===========================================================================
// Scene 1 — Hook. The family line resolves out of a soft blur.
// ===========================================================================
const HookScene: React.FC = () => (
  <Drift>
    <AbsoluteFill style={{ padding: "0 90px" }}>
      <SoftBlurIn
        text="Say hello to my new sponsor"
        fontSize={54}
        fontWeight={400}
        color={INK}
        blur={14}
      />
    </AbsoluteFill>
  </Drift>
);

// ===========================================================================
// Scene 2 — Reveal. The gramotion wordmark resolves out of depth, its orange
// dot springs in last, and the tagline settles tight beneath.
// ===========================================================================
const RevealScene: React.FC = () => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();
  const p = interpolate(frame, [4, 34], [0, 1], {
    ...clampOpts,
    easing: Easing.out(Easing.cubic),
  });
  const scale = interpolate(p, [0, 1], [1.12, 1]);

  const dot = spring({
    frame: frame - 32,
    fps,
    config: { damping: 12, stiffness: 170, mass: 0.7 },
  });
  const dotOpacity = interpolate(frame, [32, 42], [0, 1], clampOpts);

  return (
    <Drift>
      <AbsoluteFill
        style={{ alignItems: "center", justifyContent: "center", gap: 10 }}
      >
        <div style={{ display: "flex", alignItems: "baseline" }}>
          <span
            style={{
              fontFamily: HEADING,
              fontWeight: 400,
              fontSize: 116,
              lineHeight: 1,
              color: INK,
              opacity: p,
              transform: `scale(${scale})`,
              filter: p < 1 ? `blur(${(1 - p) * 20}px)` : undefined,
              whiteSpace: "nowrap",
            }}
          >
            gramotion
          </span>
          <span
            style={{
              fontFamily: HEADING,
              fontWeight: 400,
              fontSize: 116,
              lineHeight: 1,
              color: ORANGE,
              opacity: dotOpacity,
              display: "inline-block",
              transform: `scale(${interpolate(dot, [0, 1], [0.2, 1])})`,
              transformOrigin: "50% 80%",
            }}
          >
            .
          </span>
        </div>
        <WordsRise
          text="Create stunning motion graphics"
          fontSize={27}
          color={MUTED}
          delay={40}
        />
      </AbsoluteFill>
    </Drift>
  );
};

// ===========================================================================
// Scene 3 — How it works. The site's three-step flow cut into hard beats,
// each visualized as a tiny live piece of the Gramotion editor itself:
// a canvas being designed, a timeline bar being dragged, an export running.
// gramotion.video ships no illustrations, so the spot draws its own.
// ===========================================================================
const STEP_STARTS = STEP_DURS.map((_, i) =>
  STEP_DURS.slice(0, i).reduce((a, b) => a + b, 0),
);

// A small pointer cursor, shared by the canvas and timeline vignettes.
const Cursor: React.FC<{ x: number; y: number; opacity?: number }> = ({
  x,
  y,
  opacity = 1,
}) => (
  <svg
    width="17"
    height="18"
    viewBox="0 0 17 18"
    style={{ position: "absolute", left: x, top: y, opacity }}
  >
    <path
      d="M1 1 L1 14 L4.8 10.7 L7.2 16 L9.8 14.8 L7.4 9.7 L12.4 9.3 Z"
      fill="#fafafa"
      stroke="rgba(10,10,10,0.65)"
      strokeWidth="1"
    />
  </svg>
);

const PANEL_W = 470;
const PANEL_H = 200;

const panelStyle: React.CSSProperties = {
  position: "relative",
  width: PANEL_W,
  height: PANEL_H,
  borderRadius: 14,
  border: "1px solid rgba(250,250,250,0.12)",
  background: "rgba(22,19,18,0.6)",
  overflow: "hidden",
};

// 01 Design — shapes drop onto a canvas, the cursor flies in and a selection
// frame with corner handles lands around the orange one.
const DesignIllustration: React.FC<{ frame: number }> = ({ frame }) => {
  const { fps } = useVideoConfig();
  const ease = Easing.out(Easing.cubic);

  const rectS = spring({
    frame: frame - 8,
    fps,
    config: { damping: 13, stiffness: 180, mass: 0.7 },
  });
  const rectO = interpolate(frame, [8, 14], [0, 1], clampOpts);
  const circS = spring({
    frame: frame - 15,
    fps,
    config: { damping: 13, stiffness: 180, mass: 0.7 },
  });
  const circO = interpolate(frame, [15, 21], [0, 1], clampOpts);
  const line1 = interpolate(frame, [21, 31], [0, 1], {
    ...clampOpts,
    easing: ease,
  });
  const line2 = interpolate(frame, [24, 34], [0, 1], {
    ...clampOpts,
    easing: ease,
  });

  // The cursor flies to the rectangle; the selection lands when it arrives.
  const cur = interpolate(frame, [18, 30], [0, 1], {
    ...clampOpts,
    easing: ease,
  });
  const curX = interpolate(cur, [0, 1], [416, 168]);
  const curY = interpolate(cur, [0, 1], [176, 130]);
  const sel = interpolate(frame, [30, 37], [0, 1], {
    ...clampOpts,
    easing: ease,
  });

  const handles: Array<[number, number]> = [
    [-4, -4],
    [78, -4],
    [-4, 78],
    [78, 78],
  ];

  return (
    <div style={panelStyle}>
      {/* Orange rounded square */}
      <div
        style={{
          position: "absolute",
          left: 96,
          top: 58,
          width: 64,
          height: 64,
          borderRadius: 12,
          background: ORANGE,
          opacity: rectO,
          transform: `scale(${interpolate(rectS, [0, 1], [0.5, 1])})`,
        }}
      />
      {/* Muted circle */}
      <div
        style={{
          position: "absolute",
          left: 208,
          top: 66,
          width: 50,
          height: 50,
          borderRadius: "50%",
          background: "rgba(250,250,250,0.72)",
          opacity: circO,
          transform: `scale(${interpolate(circS, [0, 1], [0.5, 1])})`,
        }}
      />
      {/* Two text lines */}
      <div
        style={{
          position: "absolute",
          left: 306,
          top: 74,
          width: 108,
          height: 11,
          borderRadius: 5.5,
          background: "rgba(250,250,250,0.4)",
          opacity: line1,
          transform: `translateY(${(1 - line1) * 10}px)`,
        }}
      />
      <div
        style={{
          position: "absolute",
          left: 306,
          top: 95,
          width: 72,
          height: 11,

Showing the first 400 of 1044 lines. View the full file on GitHub.