Videorc — Introducing Videorc
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-videorcRender it locally
Renders the MP4 on your machine with the Remotion CLI.
$ pnpm dlx remotion render introducing-videorc out/introducing-videorc.mp4 --scale=2 --crf=15 --x264-preset=slower --jpeg-quality=95 --gl=angleThe prompt
The prompt this video was generated from.
# Prompt — Introducing Videorc Оригинальный запрос: > нужно сделать introducing демо видео для проекта videorc автор orcdev > мы уже делали спонсорское видео с ним. Вот сайт https://www.videorc.com/ > вот github https://github.com/TheOrcDev/videorc — нужно получить стиль > проекта, суть проекта, фичи проекта > > Используем Manrope шрифт. > Не используем letter spacing, uppercase, installation pills, badges, > text highlight markers. > > Видео должно быть премиальное, использовать нужно динамические переходы, > не нужна нам сильная статика. > > Рефы: src/demos/introducing-shadcn/, src/demos/introducing-remocn/, > src/demos/sponsor-reactbits/index.tsx > > Не использовать swirl переход — уже заезженная тема, нужно что-то новое. > > Проанализировать https://shaders.paper.design/ и подобрать подходящий > по смыслу shader. > > Всё ритмично: без дёрганий, рваных переходов и бессмысленных анимаций; > если одна анимация начинает движение в одну сторону — другая должна > поддержать это движение; никакого бессмысленного fade in / fade out. ## Исследование (июль 2026) - **Суть:** Videorc — open-source Mac-студия: запись экрана, камеры и микрофона в 4K 60fps локально и бесплатно, лайв на YouTube/Twitch/X/RTMP, Premium — 5 направлений одновременно + AI-пайплайн публикации (транскрипт, заголовок, описание, главы, хайлайты). AGPL-3.0, Electron + React (shadcn/ui) + Rust + FFmpeg. Автор — OrcDev. - **Копирайт сайта:** «The future of video starts here», «Create videos. Stream everywhere. Let AI handle the tedious parts», «Everything you need in one window», «From setup to live in seconds», «Built open source. Priced when you need more». - **Стиль:** zinc-токены из собственного CSS сайта (`.dark`: `#18181b` фон, `#f5f5f6` ink, `#a1a1a8` muted), логотип — кибер-орк с красными глазами на чёрной app-иконке; на сайте живут `#ff3b30` / `#ff0033` — красный записи. Акцент видео — record-red `#ff3b30`. - **Shader (paper.design):** grain-gradient — плёночное зерно поверх живого градиента: текстура самого видео. Тот же шейдер (shape wave / ripple) приводит в движение переходы wave-wipe и ripple-zoom — фон и переходы говорят на одном языке. Форма подложки — `corners`: зерно живёт по краям кадра, центр остаётся чистым под типографику.
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, Sequence, interpolate, spring, 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 { ShaderGrainGradient } from "@/components/remocn/shader-grain-gradient";
import { whipPan } from "@/components/remocn/whip-pan";
import { pushThrough } from "@/components/remocn/push-through";
import { focusPull } from "@/components/remocn/focus-pull";
import { KineticCenterBuild } from "@/components/remocn/kinetic-center-build";
import { LineByLineSlide } from "@/components/remocn/line-by-line-slide";
// Videorc speaks in its own dark zinc tokens; we speak in Manrope 400 only.
const { fontFamily: SANS_FAMILY } = loadSans("normal", {
subsets: ["latin"],
weights: ["400", "800"],
});
const { fontFamily: MONO_FAMILY } = loadMono("normal", {
subsets: ["latin"],
weights: ["400"],
});
const SANS = `${SANS_FAMILY}, -apple-system, BlinkMacSystemFont, sans-serif`;
const MONO = `${MONO_FAMILY}, ui-monospace, SFMono-Regular, monospace`;
// Palette lifted from videorc.com's own .dark tokens: #18181b background,
// #f5f5f6 foreground, #a1a1a8 muted — plus ONE accent, the record-red the
// logo's eyes wear and the site keeps for the record dot.
const ZINC = "#18181b";
const INK = "#f5f5f6";
const MUTED = "rgba(245,245,246,0.62)";
const FAINT = "rgba(245,245,246,0.4)";
const RED = "#ff3b30";
const HAIRLINE = "rgba(245,245,246,0.16)";
const SURFACE = "rgba(245,245,246,0.045)";
const clampOpts = {
extrapolateLeft: "clamp" as const,
extrapolateRight: "clamp" as const,
};
// ---------------------------------------------------------------------------
// Scene timings (frames @ 30fps). Transitions overlap.
// The motion score: narrative progress dives INTO the frame (ripple-zoom,
// push-through), enumeration travels LEFT (whip-pans, content arriving from
// the right and decelerating). Nothing moves against the cut before it.
// ---------------------------------------------------------------------------
const S_HOOK = 104; // ● REC blinking, then the dive into the dot
const S_REVEAL = 220; // giant mark out of the tunnel → crisp beat → one descent
const S_VERBS = 88; // Record / Stream / Publish beats
const S_ONE = 96; // "Everything you need in one window"
const F_CAPTURE = 66; // feature station 1
const F_STREAM = 66; // feature station 2
const F_PUBLISH = 74; // feature station 3 (absorbs the wave overlap)
const S_VALUES = 100; // three value lines
const S_OUTRO = 160; // lockup + videorc.com
const T_RIPPLE = 80; // signal dive (grain ripple, brand red)
const T_FP = 18; // focus-pull
const T_WHIP = 14; // whip-pan left
const T_WAVE = 42; // wave-wipe (grain swell)
const T_PUSH = 18; // push-through
const S_MONTAGE = F_CAPTURE + F_STREAM + F_PUBLISH - T_WHIP * 2;
export const INTRODUCING_VIDEORC_DURATION =
S_HOOK +
S_REVEAL +
S_VERBS +
S_ONE +
S_MONTAGE +
S_VALUES +
S_OUTRO -
(T_RIPPLE + T_FP + T_FP + T_WHIP + T_WAVE + T_PUSH);
// Global anchors for the recording chrome.
const REC_START = S_HOOK - T_RIPPLE; // the dive begins — rec is on
const OUTRO_START = INTRODUCING_VIDEORC_DURATION - S_OUTRO;
const REC_STOP = OUTRO_START + 64; // the lockup has landed — cut
// ---------------------------------------------------------------------------
// Local grain-field presentations. TransitionSeries keeps the entering
// presentation mounted for the WHOLE incoming sequence with progress pinned
// at 1, so the stock ripple-zoom / wave-wipe fields would sit behind the
// scene forever. These variants play the same dive / swell but fade the
// field out at the tail — the cut lands back in the calm zinc world.
// ---------------------------------------------------------------------------
type EmptyProps = Record<string, never>;
// Falling through the record dot: the hook scene dives INTO the dot itself
// (the in-scene camera zoom does the swallow — by the time the dot's flat
// red fills the frame, the ripple field has already bloomed beneath it), so
// the outgoing fade happens entirely behind the red and the tunnel is simply
// THERE when the dot dissolves. No visible fade-in for the field.
const SignalDive: React.FC<TransitionPresentationComponentProps<EmptyProps>> = ({
children,
presentationProgress,
presentationDirection,
}) => {
const p = presentationProgress;
if (presentationDirection === "exiting") {
// The hook scene plays its own dive; here we only dissolve the flat red
// once it has swallowed the frame, revealing the tunnel beneath.
return (
<AbsoluteFill
style={{
opacity: interpolate(p, [0.72, 0.84], [1, 0], {
...clampOpts,
easing: Easing.bezier(0.42, 0, 0.58, 1),
}),
}}
>
{children}
</AbsoluteFill>
);
}
// The tunnel blooms under the red swallow, HOLDS readable while it keeps
// zooming, then settles away as the lockup resolves.
const fieldOpacity = interpolate(p, [0.68, 0.78, 0.93, 1], [0, 1, 1, 0], {
...clampOpts,
easing: Easing.bezier(0.42, 0, 0.58, 1),
});
// Rings stay in readable range through the hold, then the zoom punches
// through only at the tail as the lockup lands.
const fieldScale = interpolate(p, [0.68, 0.93, 1], [0.35, 0.9, 4], {
...clampOpts,
easing: Easing.bezier(0.42, 0, 0.58, 1),
});
const childStyle: React.CSSProperties = {
opacity: interpolate(p, [0.86, 0.97], [0, 1], clampOpts),
transform: `scale(${interpolate(p, [0.84, 1], [0.35, 1], {
...clampOpts,
easing: Easing.bezier(0.33, 1, 0.68, 1),
})})`,
filter: `blur(${interpolate(p, [0.84, 0.98], [8, 0], clampOpts)}px)`,
};
return (
<AbsoluteFill>
<AbsoluteFill style={{ opacity: fieldOpacity, pointerEvents: "none" }}>
<ShaderGrainGradient
shape="ripple"
colors={["#2c191c", "#5c2a2c", "#96443c"]}
colorBack={ZINC}
intensity={0.5}
softness={0.55}
noise={0.4}
scale={fieldScale}
/>
</AbsoluteFill>
<AbsoluteFill style={childStyle}>{children}</AbsoluteFill>
</AbsoluteFill>
);
};
const signalDive = (): TransitionPresentation<EmptyProps> => ({
component: SignalDive,
props: {},
});
// A zinc grain swell washes upward over the montage; the values ride in on
// the same rise, and the field settles away once they land.
const GrainSwell: React.FC<TransitionPresentationComponentProps<EmptyProps>> = ({
children,
presentationProgress,
presentationDirection,
}) => {
const p = presentationProgress;
if (presentationDirection === "exiting") {
return (
<AbsoluteFill
style={{
opacity: interpolate(p, [0.3, 0.5], [1, 0], {
...clampOpts,
easing: Easing.bezier(0.42, 0, 0.58, 1),
}),
transform: `translateY(${interpolate(p, [0, 0.7], [0, -70], {
...clampOpts,
easing: Easing.in(Easing.cubic),
})}%)`,
}}
>
{children}
</AbsoluteFill>
);
}
const fieldOpacity = interpolate(p, [0.18, 0.45, 0.8, 1], [0, 1, 1, 0], {
...clampOpts,
easing: Easing.bezier(0.42, 0, 0.58, 1),
});
const drift = interpolate(p, [0, 1], [0, 0.7], {
...clampOpts,
easing: Easing.bezier(0.45, 0, 0.55, 1),
});
const rise = interpolate(p, [0.4, 0.82, 1], [100, -3.5, 0], {
...clampOpts,
easing: Easing.out(Easing.cubic),
});
return (
<AbsoluteFill>
<AbsoluteFill style={{ opacity: fieldOpacity, pointerEvents: "none" }}>
<ShaderGrainGradient
shape="wave"
colors={["#26262b", "#3a3a41", "#5b5b64"]}
colorBack={ZINC}
intensity={0.2}
softness={0.7}
noise={0.4}
scale={1.16}
offsetY={drift}
/>
</AbsoluteFill>
<AbsoluteFill style={{ transform: `translateY(${rise}%)` }}>
{children}
</AbsoluteFill>
</AbsoluteFill>
);
};
const grainSwell = (): TransitionPresentation<EmptyProps> => ({
component: GrainSwell,
props: {},
});
// ---------------------------------------------------------------------------
// 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 — words resolve 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 = 3 }) => {
const frame = useCurrentFrame();
const ease = Easing.bezier(0.2, 0.8, 0.2, 1);
const words = text.split(" ");
return (
<span
style={{
fontFamily: SANS,
fontWeight: 400,
fontSize,
color,
lineHeight: 1.3,
}}
>
{words.map((word, i) => {
const p = interpolate(frame - delay - i * stagger, [0, 22], [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(frame - delay - i * stagger, [0, 13], [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) * 8}px)` : undefined,
}}
>
{word}
{i < words.length - 1 ? " " : ""}
</span>
);
})}
</span>
);
};
// ===========================================================================
// Recording chrome — a quiet mono timecode with a blinking record dot along
// the bottom-left, ticking on the GLOBAL clock (it sits outside the
// TransitionSeries). It appears as the ripple dive begins — the recording
// starts with the brand — and freezes solid at the outro lockup: cut, saved.
// ===========================================================================
const RecChrome: React.FC = () => {
const frame = useCurrentFrame();
const appear = interpolate(frame, [REC_START + 58, REC_START + 80], [0, 1], clampOpts);
const leave = interpolate(frame, [REC_STOP + 10, REC_STOP + 32], [1, 0], clampOpts);
const stopped = frame >= REC_STOP;
const tc = Math.max(0, Math.min(frame, REC_STOP) - REC_START);
const ss = Math.floor(tc / 30) % 60;
const ff = tc % 30;
const pad = (n: number) => String(n).padStart(2, "0");
const blinkOn = stopped || Math.floor(frame / 15) % 2 === 0;
return (
<div
style={{
position: "absolute",
left: 30,
bottom: 24,
display: "flex",
alignItems: "center",
gap: 10,
opacity: appear * leave,
}}
>
<div
style={{
width: 7,
height: 7,
borderRadius: "50%",
background: RED,
opacity: blinkOn ? 1 : 0.2,
}}
/>
<span style={{ fontFamily: MONO, fontSize: 15, color: FAINT }}>
{`00:00:${pad(ss)}:${pad(ff)}`}
</span>
</div>
);
};
// ===========================================================================
// Scene 1 — Hook. The ● REC lockup lands dead-center (the dot blinking on
// the camcorder cycle), one signal ring leaves the dot, and then the camera
// dives INTO the dot: an accelerating zoom whose transform origin is pinned
// to the dot's center, the letters flying past, until the flat red swallows
// the frame and the SignalDive tunnel is revealed beneath it — we fall
// through the record dot.
// ===========================================================================
// The dot's fixed screen position (offsets from center). The REC text hangs
// off the dot's right side so the dot's coordinates — the dive's transform
// origin — never depend on measured text width; -76 optically centers the
// whole ● REC group.
const DOT_X = -76;
const DOT_SIZE = 32;
const DIVE_FROM = 58;
const DIVE_TO = 88;
const HookScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const lockupIn = spring({
frame: frame - 4,
fps,
config: { damping: 12, stiffness: 160, mass: 0.7 },
});
// The dive: accelerating zoom whose origin is the dot's center; the
// translate walks that origin back to screen center so the dot ends up
// dead-center as it swallows the frame.
const dive = interpolate(frame, [DIVE_FROM, DIVE_TO], [0, 1], {
...clampOpts,
easing: Easing.in(Easing.cubic),
});
const diveScale = 1 + dive * 56;
const blinkOn = dive > 0 || Math.floor(frame / 15) % 2 === 0;
return (
<Drift>
<AbsoluteFill
style={{
transform: `translate(${-DOT_X * dive}px, 0px) scale(${diveScale})`,
transformOrigin: `calc(50% + ${DOT_X}px) 50%`,
}}
>
{/* ● REC — the dot is the anchor, REC hangs off its right side. */}
<div
style={{
position: "absolute",
left: `calc(50% + ${DOT_X}px)`,
top: "50%",
width: DOT_SIZE,
height: DOT_SIZE,
marginLeft: -DOT_SIZE / 2,
marginTop: -DOT_SIZE / 2,
borderRadius: "50%",
background: RED,Showing the first 400 of 1160 lines. View the full file on GitHub.