All demos

remocn ✕ react-bits — 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-reactbits

Render it locally

Renders the MP4 on your machine with the Remotion CLI.

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

The prompt

Reconstructed draft

A reconstruction of the prompt this video was generated from.

Make a cinematic sponsor announcement for react-bits. Use their real brand colors (the near-black background and that signature violet) inside a warping paper.design dither shader that stays on screen the whole video with a slow camera drift, and set everything in Manrope. Start with "Say hello to my new sponsor" landing word by word like their own split-text effect, then reveal the react-bits wordmark resolving out of depth with the tagline settling under it. Show a quick 130 count-up as proof (their own animation, no plus sign), then cut through the tagline word by word — free, customizable, animations for text, backgrounds, UI. Close with the Remocn x react-bits lockup sliding together from both sides. Use remocn's push-through and focus-pull transitions for the scene changes.

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, Img, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
import { demoAsset } from "@/lib/demo-assets";
import { TransitionSeries, linearTiming } from "@remotion/transitions";
import { loadFont as loadSans } from "@remotion/google-fonts/Manrope";

import { ShaderDithering } from "@/components/remocn/shader-dithering";
import { pushThrough } from "@/components/remocn/push-through";
import { focusPull } from "@/components/remocn/focus-pull";

// react-bits speaks in motion, we speak in Manrope — normal weight only.
const { fontFamily: SANS_FAMILY } = loadSans("normal", {
  subsets: ["latin"],
  weights: ["400"],
});

const SANS = `${SANS_FAMILY}, -apple-system, BlinkMacSystemFont, sans-serif`;

// Palette lifted from reactbits.dev itself: #060010 is the landing body
// background, #5227FF the signature accent used across the whole library.
const BG = "#060010";
const VIOLET = "#5227FF";
const INK = "#fafafa";
const MUTED = "rgba(250,250,250,0.6)";
const FAINT = "rgba(250,250,250,0.45)";

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

// ---------------------------------------------------------------------------
// Scene timings (frames @ 30fps). Transitions overlap.
// ---------------------------------------------------------------------------
const S_HOOK = 70; //    per-word kinetic hook
const S_REVEAL = 95; //  react-bits wordmark reveal + tagline
const S_COUNT = 60; //   130 count-up
const S_LOCKUP = 125; // Remocn ✕ react bits + reactbits.dev

// The tagline, cut into rhythmic beats — each word is its own hard-cut scene.
const BEAT_WORDS = [
  "Free",
  "Customizable",
  "Animations for",
  "Text",
  "Backgrounds",
  "UI",
];
const BEAT_DURS = [14, 14, 16, 12, 12, 22]; // accelerating into the tail
const S_BEATS = BEAT_DURS.reduce((a, b) => a + b, 0);

const T_PT = 18; // push-through
const T_FP = 18; // focus-pull

export const SPONSOR_REACTBITS_DURATION =
  S_HOOK + S_REVEAL + S_COUNT + S_BEATS + S_LOCKUP - (T_PT + T_FP + T_PT);

// ---------------------------------------------------------------------------
// 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 — the react-bits "Split Text" register, rebuilt frame-driven:
// each word resolves out of blur while rising onto the baseline.
// ---------------------------------------------------------------------------
const WordsRise: React.FC<{
  text: string;
  fontSize: number;
  color?: string;
  delay?: number;
  stagger?: number;
}> = ({ text, fontSize, color = INK, delay = 0, stagger = 4 }) => {
  const frame = useCurrentFrame();
  const ease = Easing.bezier(0.2, 0.8, 0.2, 1);
  return (
    <span
      style={{
        fontFamily: SANS,
        fontWeight: 400,
        fontSize,
        color,
        lineHeight: 1.25,
      }}
    >
      {text.split(" ").map((word, i) => {
        const local = frame - delay - i * stagger;
        const p = interpolate(local, [0, 24], [0, 1], {
          ...clampOpts,
          easing: ease,
        });
        return (
          <span
            key={i}
            style={{
              display: "inline-block",
              whiteSpace: "pre",
              opacity: p,
              transform: `translateY(${(1 - p) * 30}px)`,
              filter: p < 1 ? `blur(${(1 - p) * 10}px)` : undefined,
            }}
          >
            {word}
            {i < text.split(" ").length - 1 ? " " : ""}
          </span>
        );
      })}
    </span>
  );
};

// ===========================================================================
// Scene 1 — Hook. The sponsor line lands word by word.
// ===========================================================================
const HookScene: React.FC = () => (
  <Drift>
    <AbsoluteFill
      style={{
        alignItems: "center",
        justifyContent: "center",
        padding: "0 120px",
        textAlign: "center",
      }}
    >
      <WordsRise text="Say hello to my new sponsor" fontSize={58} />
    </AbsoluteFill>
  </Drift>
);

// ===========================================================================
// Scene 2 — Reveal. The react-bits wordmark resolves out of depth, then the
// tagline settles beneath it.
// ===========================================================================
const RevealScene: React.FC = () => {
  const frame = useCurrentFrame();
  const p = interpolate(frame, [4, 34], [0, 1], {
    ...clampOpts,
    easing: Easing.out(Easing.cubic),
  });
  const scale = interpolate(p, [0, 1], [1.14, 1]);
  return (
    <Drift>
      <AbsoluteFill
        style={{
          alignItems: "center",
          justifyContent: "center",
          gap: 36,
        }}
      >
        <Img
          src={demoAsset("reactbits-logo.svg")}
          style={{
            width: 660,
            opacity: p,
            transform: `scale(${scale})`,
            filter: p < 1 ? `blur(${(1 - p) * 22}px)` : undefined,
          }}
        />
        <div style={{ textAlign: "center" }}>
          <WordsRise
            text="The largest & most creative library of animated React components"
            fontSize={25}
            color={MUTED}
            delay={30}
            stagger={2}
          />
        </div>
      </AbsoluteFill>
    </Drift>
  );
};

// ===========================================================================
// Scene 3 — Proof. The count-up react-bits is famous for, turned on itself.
// The plain number, nothing else.
// ===========================================================================
const CountScene: React.FC = () => {
  const frame = useCurrentFrame();
  const count = interpolate(frame, [4, 44], [0, 130], {
    ...clampOpts,
    easing: Easing.out(Easing.poly(3)),
  });
  return (
    <Drift>
      <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
        <span
          style={{
            fontFamily: SANS,
            fontWeight: 400,
            fontSize: 200,
            lineHeight: 1,
            color: INK,
            fontVariantNumeric: "tabular-nums",
          }}
        >
          {Math.round(count)}
        </span>
      </AbsoluteFill>
    </Drift>
  );
};

// ===========================================================================
// Scene 4 — Beats. The tagline cut into hard-cut word slots, switching on a
// rhythm: Free / Customizable / animations for / text / backgrounds / UI.
// Each word snaps in fast and holds until the next cut — no cross-fades.
// ===========================================================================
const BEAT_STARTS = BEAT_DURS.map((_, i) =>
  BEAT_DURS.slice(0, i).reduce((a, b) => a + b, 0),
);

const BeatsScene: React.FC = () => {
  const frame = useCurrentFrame();
  let active = 0;
  for (let i = 0; i < BEAT_STARTS.length; i++) {
    if (frame >= BEAT_STARTS[i]) active = i;
  }
  const local = frame - BEAT_STARTS[active];
  const p = interpolate(local, [0, 8], [0, 1], {
    ...clampOpts,
    easing: Easing.out(Easing.cubic),
  });
  const scale = interpolate(p, [0, 1], [1.14, 1]);
  return (
    <Drift>
      <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
        <span
          style={{
            fontFamily: SANS,
            fontWeight: 400,
            fontSize: 96,
            lineHeight: 1.1,
            color: INK,
            opacity: p,
            transform: `scale(${scale})`,
            filter: p < 1 ? `blur(${(1 - p) * 8}px)` : undefined,
            whiteSpace: "nowrap",
          }}
        >
          {BEAT_WORDS[active]}
        </span>
      </AbsoluteFill>
    </Drift>
  );
};

// ===========================================================================
// Scene 4 — Lockup. Remocn ✕ react bits assembles, reactbits.dev closes.
// ===========================================================================
const LockupScene: React.FC = () => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();
  const ease = Easing.out(Easing.cubic);

  // The two names glide in from opposite sides.
  const sideP = interpolate(frame, [6, 30], [0, 1], {
    ...clampOpts,
    easing: ease,
  });
  const leftX = (1 - sideP) * -70;
  const rightX = (1 - sideP) * 70;
  const sideBlur = (1 - sideP) * 12;

  // The cross springs in once the sides have almost settled.
  const cross = spring({
    frame: frame - 26,
    fps,
    config: { damping: 12, stiffness: 160, mass: 0.8 },
  });
  const crossOpacity = interpolate(frame, [26, 38], [0, 1], clampOpts);

  return (
    <Drift grow={0.05}>
      <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 40 }}>
          <span
            style={{
              fontFamily: SANS,
              fontWeight: 400,
              fontSize: 66,
              color: INK,
              opacity: sideP,
              transform: `translateX(${leftX}px)`,
              filter: sideBlur > 0.2 ? `blur(${sideBlur}px)` : undefined,
              whiteSpace: "nowrap",
            }}
          >
            Remocn
          </span>
          <span
            style={{
              fontFamily: SANS,
              fontWeight: 400,
              fontSize: 44,
              color: FAINT,
              opacity: crossOpacity,
              transform: `scale(${interpolate(cross, [0, 1], [0.4, 1])})`,
            }}
          >

          </span>
          <Img
            src={demoAsset("reactbits-logo.svg")}
            style={{
              height: 58,
              opacity: sideP,
              transform: `translateX(${rightX}px)`,
              filter: sideBlur > 0.2 ? `blur(${sideBlur}px)` : undefined,
            }}
          />
        </div>
      </AbsoluteFill>
    </Drift>
  );
};

// ===========================================================================
// Composition root. One persistent dithering shader carries the whole video —
// the paper.design warp/4x4 preset recolored into the react-bits register
// (#060010 body, #5227FF accent), pushed back by a vignette so it textures
// the dark instead of competing with it.
// ===========================================================================
export const SponsorReactbitsDemo: React.FC = () => {
  return (
    <AbsoluteFill style={{ background: BG }}>
      {/* Persistent shader backdrop — dithering / warp / 4x4. */}
      <ShaderDithering
        speed={0.55}
        colorBack={BG}
        colorFront={VIOLET}
        shape="warp"
        type="4x4"
        size={2.5}
      />
      {/* Vignette scrim — keeps the dither a texture, not a subject. */}
      <AbsoluteFill
        style={{
          background:
            "radial-gradient(120% 120% at 50% 42%, rgba(4,0,12,0.66) 0%, rgba(4,0,12,0.93) 100%)",
        }}
      />

      <TransitionSeries>
        {/* 1 — Hook */}
        <TransitionSeries.Sequence durationInFrames={S_HOOK}>
          <HookScene />
        </TransitionSeries.Sequence>
        <TransitionSeries.Transition
          timing={linearTiming({ durationInFrames: T_PT })}
          presentation={pushThrough()}
        />

        {/* 2 — react-bits reveal */}
        <TransitionSeries.Sequence durationInFrames={S_REVEAL}>
          <RevealScene />
        </TransitionSeries.Sequence>
        <TransitionSeries.Transition
          timing={linearTiming({ durationInFrames: T_FP })}
          presentation={focusPull()}
        />

        {/* 3 — 130 count-up */}
        <TransitionSeries.Sequence durationInFrames={S_COUNT}>
          <CountScene />
        </TransitionSeries.Sequence>

        {/* 4 — Tagline word beats (hard cut in, hard cuts inside) */}
        <TransitionSeries.Sequence durationInFrames={S_BEATS}>
          <BeatsScene />
        </TransitionSeries.Sequence>
        <TransitionSeries.Transition
          timing={linearTiming({ durationInFrames: T_PT })}
          presentation={pushThrough()}
        />

        {/* 5 — Lockup + URL */}
        <TransitionSeries.Sequence durationInFrames={S_LOCKUP}>
          <LockupScene />
        </TransitionSeries.Sequence>
      </TransitionSeries>
    </AbsoluteFill>
  );
};