diff-proof
Before and after source changes with semantic emphasis.
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>The same runtime that renders the export. Scrub to any frame, switch format, and edit the fixture props.
Installation
pnpm odori add @odori/diff-proofnpx odori add @odori/diff-proofbunx odori add @odori/diff-proofSource is copied into videos/components/diff-proof/ and belongs to your repository. odori diff compares it with upstream later; odori update applies the change you accept. It has no registry dependencies.
Copy and paste the following code 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",
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"},
],
},
},
],
});
Use it in 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>Nothing else to install.
The component imports only from odori, so there are no dependencies to add and no import paths to rewrite.
Timing contract
Every registry component declares how it behaves in time.odori test fails a cut that violates these.
- Family
- Developer proof
- Aspect ratios
- 16:9 · 1:1
- 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 | — |