soft-blur-in
A gentle blur and opacity reveal for premium openings.
import {Scene, Video} from "odori";
import {SoftBlurIn} from "../components/soft-blur-in/soft-blur-in";
<Video>
<Scene id="soft-blur-in" duration="4s">
<SoftBlurIn
title="Built for focus."
detail="Everything you need, nothing you do not."
blur={26}
/>
</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/soft-blur-innpx odori add @odori/soft-blur-inbunx odori add @odori/soft-blur-inSource is copied into videos/components/soft-blur-in/ 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/soft-blur-in/soft-blur-in.tsx
import {Fill, Easing, interpolate, useBrand, useFrame, useDesignScale} from "odori";
export type SoftBlurInProps = {
title: string;
detail?: string;
/** Blur radius at the start of the reveal, in design pixels. */
blur?: number;
};
/**
* A restrained opening: the headline resolves out of a blur while it settles
* a few pixels upward. Useful when a hard word stagger would feel too busy.
*/
export const SoftBlurIn = ({title, detail, blur = 26}: SoftBlurInProps) => {
const frame = useFrame();
const brand = useBrand();
const scale = useDesignScale();
const progress = interpolate(frame, [0, 34], [0, 1], {easing: Easing.standard});
const detailProgress = interpolate(frame, [18, 46], [0, 1], {easing: Easing.standard});
return (
<Fill
style={{
alignItems: "center",
gap: 32 * scale,
justifyContent: "center",
padding: `${120 * scale}px ${160 * scale}px`,
textAlign: "center",
}}
>
<div
style={{
filter: `blur(${(1 - progress) * blur * scale}px)`,
fontSize: 124 * scale,
fontWeight: 560,
letterSpacing: "-0.05em",
lineHeight: 1.04,
maxWidth: 1500 * scale,
opacity: progress,
transform: `translateY(${(1 - progress) * 26 * scale}px) scale(${0.98 + progress * 0.02})`,
}}
>
{title}
</div>
{detail ? (
<div
style={{
color: brand.colors.muted,
filter: `blur(${(1 - detailProgress) * 10 * scale}px)`,
fontSize: 36 * scale,
maxWidth: 1100 * scale,
opacity: detailProgress,
}}
>
{detail}
</div>
) : null}
</Fill>
);
};
videos/components/soft-blur-in/soft-blur-in.preview.tsx
import {defineComponentPreview} from "odori/preview";
import {SoftBlurIn} from "./soft-blur-in";
export default defineComponentPreview({
title: "Soft blur in",
category: "Typography",
description: "A gentle blur and opacity reveal for premium openings.",
component: SoftBlurIn,
canvas: {width: 1920, height: 1080, duration: "4s"},
controls: {
title: {type: "text", defaultValue: "Built for focus.", maxLength: 64},
detail: {type: "text", defaultValue: "Everything you need, nothing you do not."},
blur: {type: "number", defaultValue: 26, min: 0, max: 60},
},
examples: [
{name: "Default", props: {title: "Built for focus."}},
{name: "Subtle", props: {title: "Quietly precise.", blur: 10}},
],
});
Use it in a scene.
import {Scene, Video} from "odori";
import {SoftBlurIn} from "../components/soft-blur-in/soft-blur-in";
<Video>
<Scene id="soft-blur-in" duration="4s">
<SoftBlurIn
title="Built for focus."
detail="Everything you need, nothing you do not."
blur={26}
/>
</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
- 120 frames · 4s
- Minimum duration
- 45 frames · 1.5s
- Entrance
- 34 frames
- Exit
- 10 frames
- Reduced motion
- fades without blur
- Requires
- sans font
- Content limits
- title ≤ 64, detail ≤ 96
Props
| Prop | Type | Default | Required |
|---|---|---|---|
title | string | — | Yes |
detail | string | — | — |
blurBlur radius at the start of the reveal, in design pixels. | number | 26 | — |