Metric callout
A single counted number with a supporting label.
Customize
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>Installation
pnpm odori add component metric-calloutnpx odori add component metric-calloutbunx odori add component metric-calloutEdit the source in videos/components/metric-callout/. Use odori diff to compare upstream changes and odori update to apply updates.
Copy these files 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/Figures",
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"}},
],
});
Add it to 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>Timing contract
Use these durations and content limits when composing a scene. Run odori test to check your video.
- Family
- Typography
- 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 | — | — |
Related components
All componentsCaptionsFrame-accurate caption cues for sound-off playback.Character riseCharacters rise with a crisp per-letter stagger.Draw titleAn outline draws across the line, then fills solid.Flap boardA split-flap board clatters through to the target string.Focus inWords settle from a soft blur into a crisp line.Glitch textA title that arrives through brief seeded interference bursts.