shadscan — Introducing shadscan
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-shadscanRender it locally
Renders the MP4 on your machine with the Remotion CLI.
$ pnpm dlx remotion render introducing-shadscan out/introducing-shadscan.mp4 --scale=2 --crf=15 --x264-preset=slower --jpeg-quality=95The prompt
Reconstructed draftA 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,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
} from "remotion";
import { TransitionSeries, linearTiming } from "@remotion/transitions";
import { asciiDissolve } from "@/components/remocn/ascii-dissolve";
import { pushThrough } from "@/components/remocn/push-through";
import { whipPan } from "@/components/remocn/whip-pan";
import { focusPull } from "@/components/remocn/focus-pull";
import {
BG,
BODY,
FAINT,
GH_BG,
GH_BORDER,
GH_KEY,
GH_MUTED,
GH_RED,
GH_STR,
GH_TEXT,
HAIRLINE_SOFT,
HEADING,
INK,
MONO,
MUTED,
OUTFIT,
RED,
WINDOW,
clampOpts,
} from "./theme";
import { ShadscanMarkDraw } from "./logo";
import { TerminalScene } from "./terminal";
// ---------------------------------------------------------------------------
// Scene timings (frames @ 30fps). Transitions overlap the adjacent scenes.
// ---------------------------------------------------------------------------
const S_HOOK = 166;
const T_ASCII = 70; // ascii-dissolve, text mode — the signature turn
const S_INTRO = 140;
const T_PUSH = 18; // push-through — the dive into the product
const S_TERM = 444; // one continuous terminal session (scan → handoff → rescan)
const T_WHIP = 14; // whip-pan left — enumeration travel
const S_GATE = 205;
const T_FP = 16; // focus-pull — settle into the outro
const S_CTA = 170;
export const INTRODUCING_SHADSCAN_DURATION =
S_HOOK +
S_INTRO +
S_TERM +
S_GATE +
S_CTA -
(T_ASCII + T_PUSH + T_WHIP + T_FP);
// ---------------------------------------------------------------------------
// Shared chrome
// ---------------------------------------------------------------------------
const Vignette: React.FC = () => (
<AbsoluteFill
style={{
background:
"radial-gradient(115% 115% at 50% 44%, rgba(0,0,0,0) 55%, rgba(0,0,0,0.42) 100%)",
pointerEvents: "none",
}}
/>
);
/** Per-word soft blur+rise entrance, measured for Outfit. */
const Words: React.FC<{
text: string;
start: number;
size?: number;
weight?: number;
color?: string;
stagger?: number;
family?: string;
accentWord?: string;
accentColor?: string;
}> = ({
text,
start,
size = 56,
weight = 500,
color = INK,
stagger = 3,
family = HEADING,
accentWord,
accentColor = RED,
}) => {
const frame = useCurrentFrame();
const words = text.split(" ");
return (
<div
style={{
display: "flex",
flexWrap: "wrap",
justifyContent: "center",
gap: "0.28em",
maxWidth: 1100,
padding: "0 40px",
}}
>
{words.map((w, i) => {
const t = interpolate(frame - start - i * stagger, [0, 12], [0, 1], {
...clampOpts,
easing: Easing.out(Easing.cubic),
});
const isAccent = accentWord !== undefined && w.includes(accentWord);
return (
<span
key={`${w}-${i}`}
style={{
fontFamily: family,
fontWeight: weight,
fontSize: size,
color: isAccent ? accentColor : color,
opacity: t,
transform: `translateY(${(1 - t) * 14}px)`,
filter: t < 1 ? `blur(${(1 - t) * 6}px)` : undefined,
}}
>
{w}
</span>
);
})}
</div>
);
};
// ===========================================================================
// Frame 1 — Hook: "It looks right. / It feels done. / It ships broken."
// ===========================================================================
const HOOK_LINES = [
{ text: "It looks right.", start: 6, end: 32 },
{ text: "It feels done.", start: 38, end: 64 },
{ text: "It ships broken.", start: 70, end: Infinity, accent: "broken." },
];
const HookScene: React.FC = () => {
const frame = useCurrentFrame();
return (
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
{HOOK_LINES.map((l, i) => {
const exit =
l.end !== Infinity
? interpolate(frame, [l.end - 6, l.end + 2], [0, 1], clampOpts)
: 0;
return (
<div
key={i}
style={{
position: "absolute",
inset: 0,
display: "flex",
alignItems: "center",
justifyContent: "center",
opacity: 1 - exit,
transform: `translateY(${exit * -12}px)`,
filter: exit > 0 ? `blur(${exit * 6}px)` : undefined,
}}
>
<Words text={l.text} start={l.start} accentWord={l.accent} />
</div>
);
})}
</AbsoluteFill>
);
};
// ===========================================================================
// Frame 2 — Meet shadscan: mark draw-on + wordmark + the site's own H1.
// The headline sits dead-center and static: the ascii-dissolve text-mode
// transition resolves it (its enterText spec mirrors this exactly).
// ===========================================================================
export const INTRO_HEADLINE = "Meet shadscan";
const IntroScene: React.FC = () => {
return (
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, calc(-50% - 148px))",
}}
>
<ShadscanMarkDraw size={84} delay={42} />
</div>
<span
style={{
fontFamily: HEADING,
fontWeight: 400,
fontSize: 76,
color: INK,
}}
>
{INTRO_HEADLINE}
</span>
<div
style={{
position: "absolute",
top: "50%",
left: 0,
right: 0,
transform: "translateY(58px)",
display: "flex",
justifyContent: "center",
}}
>
<Words
text="Deterministic UI audits for shadcn apps"
start={64}
size={27}
weight={400}
color={MUTED}
stagger={3}
/>
</div>
</AbsoluteFill>
);
};
// ===========================================================================
// Frame 5 — The gate: the deterministic score, as a GitHub Action that fails
// the merge. The workflow file you add → the check it enforces on a PR.
// (shadscan.com/docs#github-action)
// ===========================================================================
/** GitHub's red failing-check glyph — a filled circle with an ✕. */
const XMark: React.FC<{ size?: number }> = ({ size = 20 }) => (
<span
style={{
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
flex: "0 0 auto",
width: size,
height: size,
borderRadius: 999,
background: GH_RED,
color: GH_BG,
fontSize: size * 0.56,
fontWeight: 700,
lineHeight: 1,
}}
>
✕
</span>
);
const GateScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const rise = (start: number, dur = 10) =>
interpolate(frame - start, [0, dur], [0, 1], {
...clampOpts,
easing: Easing.out(Easing.cubic),
});
const fileIn = spring({
frame: frame - 20,
fps,
config: { damping: 17, stiffness: 130, mass: 0.85 },
});
const checkIn = spring({
frame: frame - 74,
fps,
config: { damping: 17, stiffness: 130, mass: 0.85 },
});
const failIn = rise(90);
const mergeIn = rise(110);
const closing = rise(134, 12);
// YAML rows, tinted like GitHub's editor.
const yaml: React.ReactNode[] = [
<>
<span style={{ color: GH_KEY }}>steps</span>
<span style={{ color: GH_MUTED }}>:</span>
</>,
<>
<span style={{ color: GH_MUTED }}>{" - "}</span>
<span style={{ color: GH_KEY }}>uses</span>
<span style={{ color: GH_MUTED }}>: </span>
<span style={{ color: INK }}>TheOrcDev/shadscan@main</span>
</>,
<>
<span style={{ color: GH_MUTED }}>{" "}</span>
<span style={{ color: GH_KEY }}>with</span>
<span style={{ color: GH_MUTED }}>:</span>
</>,
<>
<span style={{ color: GH_MUTED }}>{" "}</span>
<span style={{ color: GH_KEY }}>fail-under</span>
<span style={{ color: GH_MUTED }}>: </span>
<span style={{ color: GH_STR }}>"80"</span>
</>,
];
return (
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
<div
style={{
position: "absolute",
top: 88,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
}}
>
<Words
text="Same source. Same score."
start={8}
size={44}
stagger={4}
/>
</div>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 22,
transform: "translateY(24px)",
}}
>
{/* the workflow file */}
<div
style={{
width: 560,
borderRadius: 10,
border: `1px solid ${HAIRLINE_SOFT}`,
background: WINDOW,
overflow: "hidden",
opacity: Math.min(1, fileIn * 1.2),
transform: `translateY(${interpolate(fileIn, [0, 1], [16, 0])}px)`,
}}
>
<div
style={{
padding: "8px 16px",
borderBottom: `1px solid ${HAIRLINE_SOFT}`,
fontFamily: MONO,
fontSize: 12.5,
color: FAINT,
}}
>
.github/workflows/shadscan.yml
</div>
<div
style={{
padding: "12px 18px 14px",
fontFamily: MONO,
fontSize: 14.5,
lineHeight: "25px",
whiteSpace: "pre",
}}
>
{yaml.map((row, i) => (
<div
key={i}
style={{
opacity: rise(34 + i * 5),
transform: `translateY(${(1 - rise(34 + i * 5)) * 5}px)`,
}}
>
{row}
</div>
))}
</div>
</div>
{/* the check it enforces on the PR */}
<div
style={{
width: 560,
borderRadius: 10,
border: `1px solid ${GH_BORDER}`,
background: GH_BG,
overflow: "hidden",
opacity: Math.min(1, checkIn * 1.2),
transform: `translateY(${interpolate(checkIn, [0, 1], [16, 0])}px)`,
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: 12,
padding: "13px 18px",Showing the first 400 of 662 lines. View the full file on GitHub.