All demos

Next.js — Introducing Next.js

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/introducing-nextjs

Render it locally

Renders the MP4 on your machine with the Remotion CLI.

$ pnpm dlx remotion render introducing-nextjs out/introducing-nextjs.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.

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,
  Sequence,
  interpolate,
  spring,
  useCurrentFrame,
  useVideoConfig,
} from "remotion";
import {
  TransitionSeries,
  linearTiming,
  type TransitionPresentation,
  type TransitionPresentationComponentProps,
} from "@remotion/transitions";
import { fade } from "@remotion/transitions/fade";

import { ShaderGrainGradient } from "@/components/remocn/shader-grain-gradient";
import { SoftBlurIn } from "@/components/remocn/soft-blur-in";
import { Typewriter } from "@/components/remocn/typewriter";
import { pushThrough } from "@/components/remocn/push-through";
import { whipPan } from "@/components/remocn/whip-pan";
import { focusPull } from "@/components/remocn/focus-pull";
import { grainDissolve } from "@/components/remocn/grain-dissolve";

import {
  BG,
  BRIGHT,
  HAIRLINE,
  INK,
  MONO,
  MONO_FAMILY,
  MUTED,
  SANS,
  SANS_FAMILY,
  clampOpts,
} from "./theme";
import { GridArc, GridLine } from "./decorations";
import { LogotypeDraw } from "./logotype";
import { OptimizationsIllo, RscIllo, StreamingIllo } from "./features";
import { PoweredByScene } from "./powered-by";

const easeOut = Easing.out(Easing.cubic);
const easeSoft = Easing.bezier(0.2, 0.8, 0.2, 1);

// ---------------------------------------------------------------------------
// Scene timings (frames @ 30fps). The flight: the eclipse triangle hooks,
// the camera dives INTO it and falls through the grain-ring tunnel (the
// introducing-videorc dive, in grayscale) onto the logotype writing itself;
// the nextjs.org hero rebuilds around its own construction lines; the
// receipts glide past; three feature illustrations play as stations; the
// Powered By circuit lights up; the numbers land; one grain cover marks the
// install; the blueprint lockup closes the loop. One grayscale grain
// gradient carries the whole video.
// ---------------------------------------------------------------------------
const S_HOOK = 66; // the opening line, slammed in and held before the eclipse
const S_ECLIPSE = 68; // camera dives into the triangle; the logo-approach takes over
// Kept as S_ECLIPSE = DIVE_TO + T_APPROACH so the swallow lands exactly as the
// approach transition begins — the wordmark rushes in ~2s into the video.
const S_LOGOTYPE = 64; // ~22f approach + ~1s hold + 12f fade out
const S_HERO = 176;
const F_STATION = 116; // each feature station
const T_WHIP = 12;
const S_FEATURES = F_STATION * 3 - T_WHIP * 2;
const S_POWERED = 160;
const S_COMMAND = 96;
const S_LOCKUP = 150;

const T_APPROACH = 22; // the wordmark rushes to meet the camera — short, not a long hold
const T_FADE = 12;
const T_PUSH = 22; // Command → Features focus-pull — long enough to read the rack
const T_FEAT = 14;
const T_GRAIN = 34; // Hero → Command grain wash — long enough to ease in, not pop

export const INTRODUCING_NEXTJS_DURATION =
  S_HOOK +
  S_ECLIPSE +
  S_LOGOTYPE +
  S_HERO +
  S_FEATURES +
  S_POWERED +
  S_COMMAND +
  S_LOCKUP -
  (T_FADE + T_APPROACH + T_FADE + T_GRAIN + T_PUSH + T_FEAT + T_FADE);

// ---------------------------------------------------------------------------
// Small shared pieces
// ---------------------------------------------------------------------------

// Barely-there push-in so no frame is ever static.
const Drift: React.FC<{ children: React.ReactNode; grow?: number }> = ({
  children,
  grow = 0.03,
}) => {
  const frame = useCurrentFrame();
  const { durationInFrames } = useVideoConfig();
  const scale = interpolate(frame, [0, durationInFrames], [1, 1 + grow]);
  return (
    <AbsoluteFill style={{ transform: `scale(${scale})` }}>
      {children}
    </AbsoluteFill>
  );
};

// One continuous camera over the whole piece — a slow forward creep plus a
// faint sway, driven by the composition's ABSOLUTE frame, so the "camera"
// never resets at a cut. The transitions handle the per-scene handoffs; this
// is the connective tissue underneath them that keeps the video one take.
const CameraRig: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const frame = useCurrentFrame();
  const { durationInFrames } = useVideoConfig();
  const t = frame / durationInFrames;
  // Base >1 so the sway never reveals a frame edge; the creep is subtle by
  // design — a big global zoom reads as nausea, a small one as intention.
  const scale = 1.006 + t * 0.03 + Math.sin(frame * 0.012) * 0.004;
  const ty = Math.sin(frame * 0.008) * 3;
  return (
    <AbsoluteFill style={{ transform: `scale(${scale}) translateY(${ty}px)` }}>
      {children}
    </AbsoluteFill>
  );
};

// The single light source of the flight. One palette, one mask — the eclipse
// and the logotype ride the SAME halo, so the triangle-portal cut between them
// is a continuous field of light, not two patched-in glows.
const HALO_COLORS = ["#3a3a3a", "#666666", "#9a9a9a"];

const GrainHalo: React.FC<{
  cx: number;
  cy: number;
  r: number;
  opacity?: number;
  breathe?: boolean;
}> = ({ cx, cy, r, opacity = 0.9, breathe = false }) => {
  const frame = useCurrentFrame();
  const b = breathe ? 1 + Math.sin(frame * 0.06) * 0.05 : 1;
  const mask =
    "radial-gradient(circle, rgba(0,0,0,1) 8%, rgba(0,0,0,0.45) 33%, transparent 62%)";
  return (
    <div
      style={{
        position: "absolute",
        left: cx - r,
        top: cy - r,
        width: r * 2,
        height: r * 2,
        opacity,
        WebkitMaskImage: mask,
        maskImage: mask,
        overflow: "hidden",
        pointerEvents: "none",
      }}
    >
      <div
        style={{
          position: "absolute",
          left: "50%",
          top: "50%",
          width: 1280,
          height: 720,
          transform: `translate(-50%, -50%) scale(${b})`,
        }}
      >
        <ShaderGrainGradient
          colors={HALO_COLORS}
          colorBack={BG}
          softness={0.6}
          intensity={0.4}
          noise={0.7}
          speed={0.5}
          scale={1.6}
        />
      </div>
    </div>
  );
};

// Hook → Eclipse: no flat dissolve. The line pushes a touch further in and
// blows out while the eclipse is revealed BEHIND it, settling back from a
// slight over-scale — one camera pulling focus from the words to the image.
type EmptyProps = Record<string, never>;

const SoftDolly: React.FC<
  TransitionPresentationComponentProps<EmptyProps>
> = ({ children, presentationProgress, presentationDirection }) => {
  const p = presentationProgress;
  if (presentationDirection === "exiting") {
    return (
      <AbsoluteFill
        style={{
          transform: `scale(${interpolate(p, [0, 1], [1, 1.12], {
            ...clampOpts,
            easing: Easing.in(Easing.cubic),
          })})`,
          opacity: interpolate(p, [0.15, 0.7], [1, 0], clampOpts),
        }}
      >
        {children}
      </AbsoluteFill>
    );
  }
  if (p >= 1) return <AbsoluteFill>{children}</AbsoluteFill>;
  return (
    <AbsoluteFill
      style={{
        transform: `scale(${interpolate(p, [0, 1], [1.08, 1], {
          ...clampOpts,
          easing: easeSoft,
        })})`,
        opacity: interpolate(p, [0.2, 0.75], [0, 1], clampOpts),
      }}
    >
      {children}
    </AbsoluteFill>
  );
};
const softDolly = (): TransitionPresentation<EmptyProps> => ({
  component: SoftDolly,
  props: {},
});


// Words rise out of blur onto the baseline.
const RiseWords: React.FC<{
  text: string;
  delay?: number;
  stagger?: number;
}> = ({ text, delay = 0, stagger = 2.5 }) => {
  const frame = useCurrentFrame();
  const words = text.split(" ");
  return (
    <>
      {words.map((word, i) => {
        const p = interpolate(frame - delay - i * stagger, [0, 20], [0, 1], {
          ...clampOpts,
          easing: easeSoft,
        });
        // 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(frame - delay - i * stagger, [0, 12], [0, 1], {
          ...clampOpts,
          easing: easeSoft,
        });
        return (
          <span
            key={i}
            style={{
              display: "inline-block",
              whiteSpace: "pre",
              opacity: p,
              transform: `translateY(${(1 - py) * 18}px)`,
              filter: p < 1 ? `blur(${(1 - p) * 7}px)` : undefined,
            }}
          >
            {word}
            {i < words.length - 1 ? " " : ""}
          </span>
        );
      })}
    </>
  );
};

const Triangle: React.FC<{ size: number; color: string }> = ({
  size,
  color,
}) => (
  <svg
    width={size}
    height={size * 0.882}
    viewBox="0 0 100 88.2"
    style={{ display: "block" }}
  >
    <path d="M50 0 L100 88.2 L0 88.2 Z" fill={color} />
  </svg>
);

// The official circle-N icon, kept for the lockup.
const N_DIAGONAL =
  "M149.508 157.52L69.142 54H54V125.97H66.114V69.384L139.999 164.845C143.333 162.614 146.509 160.165 149.508 157.52Z";
const N_BAR = "M115 54H127V126H115V54Z";

const NextMark: React.FC<{ size: number; idPrefix: string }> = ({
  size,
  idPrefix,
}) => (
  <svg width={size} height={size} viewBox="0 0 180 180">
    <defs>
      <linearGradient
        id={`${idPrefix}-d`}
        x1="109"
        y1="116.5"
        x2="144.5"
        y2="160.5"
        gradientUnits="userSpaceOnUse"
      >
        <stop stopColor="white" />
        <stop offset="1" stopColor="white" stopOpacity="0" />
      </linearGradient>
      <linearGradient
        id={`${idPrefix}-b`}
        x1="121"
        y1="54"
        x2="120.8"
        y2="106.9"
        gradientUnits="userSpaceOnUse"
      >
        <stop stopColor="white" />
        <stop offset="1" stopColor="white" stopOpacity="0" />
      </linearGradient>
    </defs>
    <circle cx="90" cy="90" r="88" fill="#000000" />
    <circle
      cx="90"
      cy="90"
      r="88"
      fill="none"
      stroke="rgba(237,237,237,0.3)"
      strokeWidth="1.5"
    />
    <path d={N_BAR} fill={`url(#${idPrefix}-b)`} />
    <path d={N_DIAGONAL} fill={`url(#${idPrefix}-d)`} />
  </svg>
);

// ===========================================================================
// Scene 1 — The eclipse. The backlit triangle over the grain field, the hook
// beneath it, and then the camera dives INTO the triangle — its flat black
// swallows the frame and the ring tunnel is already there beneath.
// ===========================================================================
const ECLIPSE_CX = 640;
const ECLIPSE_CY = 322;
// The camera flies INTO the ▲: the eclipse scales up from the triangle center
// so its flat-black interior swallows the frame, finishing exactly as the
// logo-approach transition takes over (DIVE_TO === S_ECLIPSE − T_APPROACH).
const DIVE_FROM = 28;
const DIVE_TO = 46;

// ===========================================================================
// Scene 0 — The opening line. Alone on black, it SLAMS in: crashing down out
// of an over-scale and heavy blur to a hard settle, then a slow, pompous
// push-in holds the claim before the eclipse.
// ===========================================================================
const HookScene: React.FC = () => {
  const frame = useCurrentFrame();
  // Brutal impact — over-scale + heavy blur crash into place, a hard settle.
  const slam = interpolate(frame, [0, 9, 15], [1.42, 0.985, 1], {
    ...clampOpts,
    easing: Easing.out(Easing.exp),
  });
  const op = interpolate(frame, [0, 5], [0, 1], clampOpts);
  // Pompous hold — a slow, majestic push-in after the impact lands.
  const grand = interpolate(frame, [15, S_HOOK], [1, 1.05], clampOpts);
  // A short decaying shake sells the crash.
  const shake = Math.sin(frame * 2.1) * Math.max(0, 1 - frame / 9) * 4;
  // Brutal exit — the line blasts outward and dissolves before the eclipse,
  // so it never lingers over the triangle during the cut.
  const exit = interpolate(frame, [S_HOOK - 16, S_HOOK - 2], [0, 1], {
    ...clampOpts,
    easing: Easing.in(Easing.cubic),
  });
  const blur = interpolate(frame, [0, 11], [24, 0], clampOpts) + exit * 26;
  return (
    <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
      <div
        style={{
          fontFamily: SANS,
          fontWeight: 600,
          fontSize: 64,
          color: BRIGHT,
          textAlign: "center",
          padding: "0 90px",
          opacity: op * (1 - exit),
          transform: `translateY(${shake}px) scale(${slam * grand * (1 + exit * 0.2)})`,
          filter: blur > 0.4 ? `blur(${blur}px)` : undefined,
        }}
      >
        You've already used Next.js today
      </div>
    </AbsoluteFill>
  );
};

const EclipseScene: React.FC = () => {
  const frame = useCurrentFrame();

  const glowIn = interpolate(frame, [0, 14], [0, 1], {
    ...clampOpts,
    easing: easeOut,
  });
  // Fly INTO the triangle — the flat black interior grows to swallow the frame,
  // completing right as the approach transition hands over to the logotype.
  const dive = interpolate(frame, [DIVE_FROM, DIVE_TO], [0, 1], {
    ...clampOpts,
    easing: Easing.in(Easing.cubic),
  });

  return (

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