metric-callout
A single counted number with a supporting label.
import {Scene, Video} from "odori";
import {MetricCallout} from "../components/metric-callout/metric-callout";
<Video>
<Scene id="metric-callout" duration="3s">
<MetricCallout
value={100}
label="deterministic frames"
suffix="%"
/>
</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/metric-calloutnpx odori add @odori/metric-calloutbunx odori add @odori/metric-calloutSource is copied into videos/components/metric-callout/ 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/metric-callout/metric-callout.tsx
import {Fill, Easing, interpolate, useBrand, useFrame, useDesignScale} from "odori";
export type MetricCalloutProps = {
value: number;
label: string;
suffix?: string;
prefix?: string;
decimals?: number;
detail?: string;
};
/** A counted metric. The count is a pure function of the frame. */
export const MetricCallout = ({value, label, suffix = "", prefix = "", decimals = 0, detail}: MetricCalloutProps) => {
const frame = useFrame();
const brand = useBrand();
const scale = useDesignScale();
const counted = interpolate(frame, [0, 44], [0, value], {easing: Easing.out(Easing.cubic)});
const rise = interpolate(frame, [0, 22], [0, 1], {easing: Easing.standard});
return (
<Fill style={{alignItems: "center", gap: 20 * scale, justifyContent: "center", padding: 120 * scale}}>
<div
style={{
backgroundImage: `linear-gradient(180deg, ${brand.colors.foreground} 30%, ${brand.colors.muted} 100%)`,
backgroundClip: "text",
color: "transparent",
fontFamily: brand.typography.sans,
fontSize: 216 * scale,
fontWeight: 560,
letterSpacing: "-0.06em",
lineHeight: 1,
opacity: rise,
transform: `translateY(${(1 - rise) * 40 * scale}px)`,
WebkitBackgroundClip: "text",
}}
>
{prefix}
{counted.toFixed(decimals)}
{suffix}
</div>
<div
style={{
color: brand.colors.foreground,
fontSize: 42 * scale,
letterSpacing: "-0.02em",
opacity: interpolate(frame, [14, 30], [0, 1], {easing: Easing.standard}),
textAlign: "center",
}}
>
{label}
</div>
{detail ? (
<div
style={{
color: brand.colors.muted,
fontSize: 30 * scale,
opacity: interpolate(frame, [22, 38], [0, 1], {easing: Easing.standard}),
textAlign: "center",
}}
>
{detail}
</div>
) : null}
</Fill>
);
};
videos/components/metric-callout/metric-callout.preview.tsx
import {defineComponentPreview} from "odori/preview";
import {MetricCallout} from "./metric-callout";
export default defineComponentPreview({
title: "Metric callout",
category: "Typography",
description: "A single counted number with a supporting label.",
component: MetricCallout,
canvas: {width: 1920, height: 1080, duration: "3s"},
controls: {
value: {type: "number", defaultValue: 100, min: 0, max: 100000},
label: {type: "text", defaultValue: "frames rendered deterministically"},
suffix: {type: "text", defaultValue: "%"},
},
examples: [
{name: "Percentage", props: {value: 100, suffix: "%", label: "deterministic frames"}},
{name: "Duration", props: {value: 0.4, decimals: 1, suffix: "s", label: "preview startup"}},
],
});
Use it in a scene.
import {Scene, Video} from "odori";
import {MetricCallout} from "../components/metric-callout/metric-callout";
<Video>
<Scene id="metric-callout" duration="3s">
<MetricCallout
value={100}
label="deterministic frames"
suffix="%"
/>
</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
- Typography
- Aspect ratios
- 16:9 · 9:16 · 1:1
- Recommended duration
- 90 frames · 3s
- Minimum duration
- 60 frames · 2s
- Entrance
- 40 frames
- Exit
- 8 frames
- Reduced motion
- shows the final value immediately
- Requires
- mono font, sans font
- Content limits
- label ≤ 48
Props
| Prop | Type | Default | Required |
|---|---|---|---|
value | number | — | Yes |
label | string | — | Yes |
suffix | string | "" | — |
prefix | string | "" | — |
decimals | number | 0 | — |
detail | string | — | — |