Changelog — llms.txt for AI agents
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/llms-txtRender it locally
Renders the MP4 on your machine with the Remotion CLI.
$ pnpm dlx remotion render llms-txt out/llms-txt.mp4 --scale=2 --crf=15 --x264-preset=slower --jpeg-quality=95 --gl=angleThe 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,
Sequence,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
} from "remotion";
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 { ScaleDownFade } from "@/components/remocn/scale-down-fade";
import { ShortSlideRight } from "@/components/remocn/short-slide-right";
import { LineByLineSlide } from "@/components/remocn/line-by-line-slide";
import { ShaderSimplexNoise } from "@/components/remocn/shader-simplex-noise";
import { ShaderSmokeRing } from "@/components/remocn/shader-smoke-ring";
import { asciiDissolve } from "@/components/remocn/ascii-dissolve";
// Manrope carries every spoken headline; Geist Mono carries the URLs, the
// markup noise, the index/corpus text, and the .md suffix.
const { fontFamily: SANS_FAMILY } = loadSans("normal", {
subsets: ["latin"],
weights: ["400", "500", "600"],
});
const { fontFamily: MONO_FAMILY } = loadMono("normal", {
subsets: ["latin"],
weights: ["400", "500"],
});
const SANS =
"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif";
const MONO = `${MONO_FAMILY}, ui-monospace, SFMono-Regular, monospace`;
// The shipped remocn.dev brand: warm obsidian + one lime accent.
const OBSIDIAN = "#141318";
const INK = "#f2f2f2";
const MUTED = "rgba(242,242,242,0.62)";
const FAINT = "rgba(242,242,242,0.4)";
const LIME = "#C3E88D";
const clampOpts = {
extrapolateLeft: "clamp" as const,
extrapolateRight: "clamp" as const,
};
// ---------------------------------------------------------------------------
// Scene timings (frames @ 30fps). Transitions overlap.
// ---------------------------------------------------------------------------
const S_HOOK = 66; // "Your agent reads the docs too"
const S_SCRAPE = 196; // the sentence buried in markup + the verdict
const S_TURN = 134; // "Now the docs are plain text" (self-exits)
const S_ADDR = 72; // "At the addresses agents already look for"
const S_ROUTES = 344; // the three routes on one mono surface
const S_VALUE = 96; // three value lines
const S_OUTRO = 150; // smoke ring → R mark → wordmark
const T_X = 14; // crossfade
const T_ASCII_TEXT = 84; // ascii-dissolve in text mode: the verdict decays
// into the field, the turn headline condenses out
// and holds READABLE before solidifying
const T_ASCII = 56; // ascii-dissolve cover, generic mode
const T_BLUR = 16; // blur crossfade
// ===========================================================================
// The remocn mark — the R letterform from remocn.dev/logo-mark.svg, inherited
// from the introducing-remocn outro so the changelog series closes in one
// voice: `draw` runs the outline, `fill` lets the solid letter catch up.
// ===========================================================================
const MARK_VIEWBOX = "0 0 124.06 134.26";
const MARK_RATIO = 124.06 / 134.26;
const MARK_PATH =
"M 0.01 0.81 C 0.01 1.73, 0.36 2.79, 1.09 4.13 C 4.91 11.04, 13.45 17.16, 21.7 18.9 C 22.94 19.16, 23.18 19.16, 51.39 19.27 C 76.07 19.36, 80.02 19.4, 81.32 19.57 C 89.89 20.69, 96.2 24.68, 99.38 31.01 C 103.19 38.56, 102.53 50.1, 97.91 57.07 C 94.66 61.96, 89.68 64.99, 83.26 66 C 82.81 66.07, 70.18 66.83, 55.2 67.69 C 24.82 69.43, 27.03 69.23, 24.18 70.4 C 19.9 72.15, 14.84 75.7, 10.65 79.89 C 4.86 85.68, 1.3 91.91, 0.25 98.13 C 0.12 98.85, 0.08 103.13, 0.04 116.66 L 0 134.26 9.5 134.26 L 19 134.26 19.05 119.41 C 19.1 103.27, 19.08 103.82, 19.82 101.04 C 21.79 93.65, 27.86 88.75, 36.45 87.63 C 37.23 87.53, 39.41 87.5, 43.57 87.53 C 50.12 87.59, 50.75 87.65, 53.22 88.45 C 56.61 89.56, 59.67 91.86, 62.02 95.07 C 62.52 95.76, 69.35 103.85, 77.2 113.07 C 85.04 122.28, 91.63 130.04, 91.85 130.32 C 92.07 130.59, 92.5 131.34, 92.82 131.97 C 93.52 133.35, 94.11 133.93, 95.06 134.13 C 95.5 134.23, 98.97 134.26, 106.36 134.23 L 117.01 134.19 100.82 113.07 C 91.91 101.45, 84.52 91.78, 84.39 91.57 C 83.36 89.89, 83.66 87.53, 85.09 86 C 85.79 85.25, 86.36 84.94, 88.07 84.38 C 96.18 81.72, 104.15 76.62, 109.97 70.36 C 120.59 58.93, 124.06 44.32, 119.44 30.43 C 114.59 15.81, 101.93 4.02, 87.64 0.83 C 83.93 0.01, 88.09 0.08, 41.85 0.04 L 0.01 0 0.01 0.81 Z";
const RemocnMark: React.FC<{ size: number; draw?: number; fill?: number }> = ({
size,
draw = 1,
fill = 1,
}) => (
<svg
viewBox={MARK_VIEWBOX}
width={size * MARK_RATIO}
height={size}
style={{ display: "block", color: INK, overflow: "visible" }}
>
<path
d={MARK_PATH}
pathLength={1}
fill="currentColor"
fillOpacity={fill}
stroke="currentColor"
strokeWidth={2}
strokeLinejoin="round"
strokeDasharray={1}
strokeDashoffset={1 - draw}
/>
</svg>
);
// Readability scrim over the backdrop shader.
const Scrim: React.FC<{ strength?: number }> = ({ strength = 1 }) => (
<AbsoluteFill
style={{
background: `radial-gradient(120% 120% at 50% 42%, rgba(20,19,24,${
0.3 * strength
}) 0%, rgba(20,19,24,${0.78 * strength}) 100%)`,
}}
/>
);
// ===========================================================================
// Scene 1 — Hook. One line lands solo: the reader the viewer didn't picture.
// ===========================================================================
const HookScene: React.FC = () => (
<AbsoluteFill>
<ScaleDownFade
text="Your agent reads the docs too"
fontSize={54}
fontWeight={400}
color={INK}
/>
</AbsoluteFill>
);
// ===========================================================================
// Scene 2 — The scrape. A clean docs sentence gets buried by mono markup
// crowding in from all four edges; then everything dims and the entry's own
// verdict lands over the held noise.
// ===========================================================================
type TagFragment = {
text: string;
x: number; // offset from center, px
y: number;
size: number;
dx: number; // slide-in direction (unit-ish vector, px of travel)
dy: number;
o: number; // settled opacity
};
const TAG_FRAGMENTS: TagFragment[] = [
{ text: '<div class="prose prose-invert max-w-none">', x: -320, y: -236, size: 19, dx: -60, dy: -20, o: 0.6 },
{ text: '<nav aria-label="On this page">', x: 330, y: -262, size: 17, dx: 60, dy: -24, o: 0.5 },
{ text: '<span data-state="open">', x: -430, y: -120, size: 18, dx: -70, dy: 0, o: 0.55 },
{ text: '<script type="application/ld+json">', x: 350, y: -140, size: 16, dx: 70, dy: 0, o: 0.45 },
{ text: '<svg viewBox="0 0 24 24" fill="none">', x: -350, y: 160, size: 17, dx: -60, dy: 20, o: 0.5 },
{ text: "data-radix-collection-item", x: 380, y: 130, size: 18, dx: 66, dy: 16, o: 0.55 },
{ text: 'class="flex items-center gap-2"', x: -260, y: 262, size: 18, dx: -40, dy: 50, o: 0.55 },
{ text: '<button data-slot="trigger" tabindex="-1">', x: 250, y: 236, size: 16, dx: 40, dy: 46, o: 0.45 },
{ text: "</div></div></div></div>", x: 30, y: 300, size: 20, dx: 0, dy: 60, o: 0.6 },
{ text: 'style="--tw-prose-body: #a1a1aa"', x: -60, y: -300, size: 17, dx: 0, dy: -56, o: 0.5 },
{ text: 'aria-hidden="true"', x: -470, y: 40, size: 16, dx: -66, dy: 8, o: 0.4 },
{ text: '<a class="text-sm underline-offset-4">', x: 430, y: -20, size: 16, dx: 66, dy: -6, o: 0.42 },
// The last wave lands ON the sentence — the burial.
{ text: '<li data-sidebar="menu-item">', x: -150, y: -52, size: 19, dx: -30, dy: -30, o: 0.66 },
{ text: '<meta property="og:type" content="article">', x: 130, y: 44, size: 18, dx: 30, dy: 30, o: 0.66 },
{ text: '<span class="sr-only">Copy</span>', x: 60, y: -110, size: 18, dx: 24, dy: -30, o: 0.62 },
{ text: '<code class="relative rounded px-1">', x: -110, y: 96, size: 18, dx: -26, dy: 32, o: 0.64 },
];
const TAGS_START = 26;
const TAGS_PER = 4;
const DIM_AT = 96;
const ScrapeScene: React.FC = () => {
const frame = useCurrentFrame();
const sentenceIn = interpolate(frame, [0, 14], [0, 1], {
...clampOpts,
easing: Easing.out(Easing.cubic),
});
// Once the soup has buried the sentence, the whole pile dims and holds —
// the verdict lands over it.
const dim = interpolate(frame, [DIM_AT, DIM_AT + 16], [1, 0.3], {
...clampOpts,
easing: Easing.inOut(Easing.cubic),
});
const dimBlur = interpolate(frame, [DIM_AT, DIM_AT + 16], [0, 2.5], clampOpts);
return (
<AbsoluteFill>
<AbsoluteFill
style={{
alignItems: "center",
justifyContent: "center",
opacity: dim,
filter: dimBlur > 0.01 ? `blur(${dimBlur}px)` : undefined,
}}
>
<span
style={{
fontFamily: SANS,
fontWeight: 400,
fontSize: 30,
lineHeight: 1.5,
color: INK,
maxWidth: 680,
textAlign: "center",
opacity: sentenceIn,
transform: `translateY(${(1 - sentenceIn) * 10}px)`,
}}
>
Wrap two scenes in a TransitionSeries and pass a presentation.
</span>
{TAG_FRAGMENTS.map((tag, i) => {
const start = TAGS_START + i * TAGS_PER;
const enter = interpolate(frame, [start, start + 12], [0, 1], {
...clampOpts,
easing: Easing.out(Easing.cubic),
});
if (enter <= 0.002) return null;
return (
<span
key={tag.text}
style={{
position: "absolute",
left: 640 + tag.x - 200,
top: 360 + tag.y - 14,
width: 400,
textAlign: "center",
fontFamily: MONO,
fontSize: tag.size,
whiteSpace: "nowrap",
color: MUTED,
opacity: enter * tag.o,
transform: `translate(${(1 - enter) * tag.dx}px, ${
(1 - enter) * tag.dy
}px)`,
}}
>
{tag.text}
</span>
);
})}
</AbsoluteFill>
<Sequence from={96}>
<ScaleDownFade
text="A bad way to learn an API"
fontSize={58}
fontWeight={400}
color={INK}
/>
</Sequence>
</AbsoluteFill>
);
};
// ===========================================================================
// Scene 3 — The turn. Static on entry (the ascii-dissolve cover resolves it
// in), then it plays its own exit so the next beat gets an empty canvas.
// ===========================================================================
const TurnScene: React.FC = () => {
const frame = useCurrentFrame();
const { durationInFrames } = useVideoConfig();
const exitP = interpolate(
frame,
[durationInFrames - 18, durationInFrames - 2],
[0, 1],
{ ...clampOpts, easing: Easing.in(Easing.cubic) },
);
return (
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
<span
style={{
fontFamily: SANS,
fontWeight: 400,
fontSize: 72,
color: INK,
opacity: 1 - exitP,
transform: `translateY(${exitP * -10}px) scale(${1 - exitP * 0.05})`,
filter: `blur(${exitP * 6}px)`,
}}
>
Now the docs are plain text
</span>
</AbsoluteFill>
);
};
// ===========================================================================
// Scene 3b — The addresses. The promise in the entry's own words.
// ===========================================================================
const AddressesScene: React.FC = () => (
<AbsoluteFill>
<ShortSlideRight
text="At the addresses agents already look for"
fontSize={42}
fontWeight={400}
color={INK}
/>
</AbsoluteFill>
);
// ===========================================================================
// Scene 4 — The three routes, one mono surface. The address line is the
// hero: it types /llms.txt (the index pours in beneath), the filename flips
// in place to /llms-full.txt (the whole corpus streams past), then a real
// docs URL takes the slot and .md types itself — the rendered card flips to
// raw Markdown on the last keystroke.
// ===========================================================================
const RT_URL_START = 8; // "remocn.dev/llms.txt" types
const RT_INDEX_START = 40; // index lines pour in
const RT_FLIP = 116; // llms.txt → llms-full.txt flips in place
const RT_STREAM_START = 128; // corpus stream begins
const RT_STREAM_END = 204; // stream fades
const RT_C_START = 212; // phase C — the address line hands over
const RT_URL2_START = 218; // docs URL types
const RT_MD_KEYS = [246, 253, 260] as const; // ".md" keystrokes
const RT_CARD_FLIP = 264; // rendered → raw flip
const URL_PREFIX = "remocn.dev/";
const FILE_A = "llms.txt";
const FILE_B = "llms-full.txt";
type IndexLine =
| { kind: "h"; text: string }
| { kind: "q"; text: string }
| { kind: "blank" }
| { kind: "item"; title: string; path: string; desc: string };
const INDEX_LINES: IndexLine[] = [
{ kind: "h", text: "# remocn" },
{ kind: "q", text: "> Remotion components as a shadcn registry" },
{ kind: "blank" },
{ kind: "item", title: "Installation", path: "/docs/installation.md", desc: "registry setup for a Remotion project" },
{ kind: "item", title: "Typography", path: "/docs/typography.md", desc: "31 kinetic text animations" },
{ kind: "item", title: "Transitions", path: "/docs/transitions.md", desc: "scene-to-scene presentations" },
{ kind: "item", title: "Shaders", path: "/docs/shaders.md", desc: "18 frame-driven WebGL backdrops" },
{ kind: "item", title: "UI", path: "/docs/ui.md", desc: "timeline-driven primitives and flows" },
{ kind: "item", title: "Icons", path: "/docs/icons.md", desc: "100 animated icons, draw and action" },
];
// The corpus stream — plausible slices of the full docs as one document.
const CORPUS_LINES: string[] = [
"# remocn",
"Production-ready Remotion components, installed as code you own.",
"",
"## Installation",
"Add the registry to components.json, then pull components by name.",
"```bash",
"npx shadcn add @remocn/typewriter",
"```",
"",
"## typewriter",
"Types text character by character behind a blinking block caret.",
"| prop | type | default |",
"| --- | --- | --- |",
"| text | string | — |",
"| speed | number | 1 |",
"",
"## rolling-number",
"Odometer digits that wheel into place, tabular by default.",
"",
"## grain-dissolve",
"A film-grain burst that carries one scene into the next.",
"```tsx",
"<TransitionSeries.Transition",
" presentation={grainDissolve()}",
" timing={linearTiming({ durationInFrames: 18 })}",
"/>",
"```",
"",
"## shader-mesh-gradient",
"Four-color animated mesh, frame-driven for deterministic renders.",
"",
"## command-menu",
"A timeline-driven command palette: open, filter, land on cue.",
"| prop | type | default |",
"| items | CommandMenuEntry[] | — |",
"| revealCount | number | 0 |",
"",
"## use-button-transition",
"Describe steps as { at, state } — the frame renders the rest.",
"",
"## backdrop",
"Full-bleed framing layer with safe-area padding for scenes.",
"",
"## confetti",
"A one-shot deterministic burst, seeded once per render.",
"",
"## shader-god-rays",
"Volumetric light shafts from a movable source point.",
"",
"## chat-flow",
"One message API across iMessage, Telegram, and plain skins.",
];
const corpusColor = (line: string): string => {
if (line.startsWith("# ") || line.startsWith("## ")) return INK;
if (line.startsWith("```") || line.startsWith("| ")) return FAINT;Showing the first 400 of 1205 lines. View the full file on GitHub.