Diff proof
Before and after source changes with semantic emphasis.
Customize
import {Scene, Video} from "odori";
import {DiffProof} from "../components/diff-proof/diff-proof";
<Video>
<Scene id="diff-proof" duration="5s">
<DiffProof
lines={[
{
text: "<Video>",
kind: "context"
},
{
text: " <Scene duration=\"4s\">",
kind: "removed"
},
{
text: " <Scene duration=\"5s\">",
kind: "added"
},
{
text: " <TitleReveal title={headline} />",
kind: "context"
},
{
text: " </Scene>",
kind: "context"
},
{
text: "</Video>",
kind: "context"
}
]}
title="videos/launch/video.tsx"
stagger={8}
/>
</Scene>
</Video>Installation
pnpm odori add component diff-proofnpx odori add component diff-proofbunx odori add component diff-proofEdit the source in videos/components/diff-proof/. Use odori diff to compare upstream changes and odori update to apply updates.
Copy these files into your project
videos/components/diff-proof/diff-proof.tsx
import {Fill, Easing, interpolate, useBrand, useFrame, useDesignScale} from "odori";
export type DiffLine = {
text: string;
kind?: "added" | "removed" | "context";
};
export type DiffProofProps = {
lines: DiffLine[];
title?: string;
/** Frames between one changed line and the next. */
stagger?: number;
};
/**
* A patch that reveals its changed lines in order. Context lines are present
* from the first frame, so the eye reads the change rather than the file.
*/
export const DiffProof = ({lines, title = "videos/launch/video.tsx", stagger = 8}: DiffProofProps) => {
const frame = useFrame();
const brand = useBrand();
const scale = useDesignScale();
let changed = 0;
return (
<Fill style={{alignItems: "center", justifyContent: "center", padding: 90 * scale}}>
<div
style={{
background: brand.colors.surface,
border: `1px solid ${brand.colors.border}`,
borderRadius: 24 * scale,
minWidth: 1100 * scale,
overflow: "hidden",
}}
>
<div
style={{
borderBottom: `1px solid ${brand.colors.border}`,
color: brand.colors.muted,
fontFamily: brand.typography.mono,
fontSize: 24 * scale,
padding: `${20 * scale}px ${26 * scale}px`,
}}
>
{title}
</div>
<div style={{padding: `${18 * scale}px 0`}}>
{lines.map((line, index) => {
const kind = line.kind ?? "context";
const context = kind === "context";
const delay = context ? 0 : 12 + changed * stagger;
if (!context) changed += 1;
const enter = context ? 1 : interpolate(frame, [delay, delay + 14], [0, 1], {easing: Easing.standard});
const tint =
kind === "added" ? brand.colors.accent : kind === "removed" ? brand.colors.muted : "transparent";
return (
<div
key={`${line.text}-${index}`}
style={{
background: context ? "transparent" : `color-mix(in srgb, ${tint} ${enter * 14}%, transparent)`,
borderLeft: `${4 * scale}px solid ${context ? "transparent" : tint}`,
color: context ? brand.colors.muted : brand.colors.foreground,
fontFamily: brand.typography.mono,
fontSize: 30 * scale,
opacity: context ? 0.7 : enter,
padding: `${8 * scale}px ${26 * scale}px`,
textDecoration: kind === "removed" ? "line-through" : "none",
whiteSpace: "pre",
}}
>
{kind === "added" ? "+ " : kind === "removed" ? "- " : " "}
{line.text}
</div>
);
})}
</div>
</div>
</Fill>
);
};
videos/components/diff-proof/diff-proof.preview.tsx
import {defineComponentPreview} from "odori/preview";
import {DiffProof} from "./diff-proof";
export default defineComponentPreview({
title: "Diff proof",
category: "Developer proof/Code",
description: "Before and after source changes with semantic emphasis.",
component: DiffProof,
canvas: {width: 1920, height: 1080, duration: "5s"},
controls: {
title: {type: "text", defaultValue: "videos/launch/video.tsx"},
stagger: {type: "number", defaultValue: 8, min: 2, max: 24},
},
examples: [
{
name: "Scene retimed",
props: {
lines: [
{text: "<Video>", kind: "context"},
{text: ' <Scene duration="4s">', kind: "removed"},
{text: ' <Scene duration="5s">', kind: "added"},
{text: " <TitleReveal title={headline} />", kind: "context"},
{text: " </Scene>", kind: "context"},
{text: "</Video>", kind: "context"},
],
},
},
],
});
Add it to a scene
import {Scene, Video} from "odori";
import {DiffProof} from "../components/diff-proof/diff-proof";
<Video>
<Scene id="diff-proof" duration="5s">
<DiffProof
lines={[
{
text: "<Video>",
kind: "context"
},
{
text: " <Scene duration=\"4s\">",
kind: "removed"
},
{
text: " <Scene duration=\"5s\">",
kind: "added"
},
{
text: " <TitleReveal title={headline} />",
kind: "context"
},
{
text: " </Scene>",
kind: "context"
},
{
text: "</Video>",
kind: "context"
}
]}
title="videos/launch/video.tsx"
stagger={8}
/>
</Scene>
</Video>Timing contract
Use these durations and content limits when composing a scene. Run odori test to check your video.
- Family
- Developer proof
- Recommended duration
- 150 frames · 5s
- Minimum duration
- 72 frames · 2.4s
- Entrance
- 40 frames
- Exit
- 0 frames
- Reduced motion
- changed lines appear without staggering
- Requires
- mono font
- Content limits
- lines ≤ 12, lineLength ≤ 58
Props
| Prop | Type | Default | Required |
|---|---|---|---|
lines | DiffLine[] | — | Yes |
title | string | "videos/launch/video.tsx" | — |
staggerFrames between one changed line and the next. | number | 8 | — |
Related components
All componentsProduct launchA framed source excerpt that reveals line by line.Code walkthroughProgressive code reveal with authored focal regions.Dependency graphPackages and modules connected as a readable graph.File treeProgressive repository navigation with active file state.Log streamPaced runtime logs with levels and bounded scrolling.TerminalA typed command session with deterministic output reveals.