Sheen text
A single accent sweep crosses an already-set line.
Customize
import {Scene, Video} from "odori";
import {SheenText} from "../components/sheen-text/sheen-text";
<Video>
<Scene id="sheen-text" duration="3s">
<SheenText
text="Polished by default"
at={20}
sweepFrames={30}
/>
</Scene>
</Video>Installation
pnpm odori add component sheen-textnpx odori add component sheen-textbunx odori add component sheen-textEdit the source in videos/components/sheen-text/. Use odori diff to compare upstream changes and odori update to apply updates.
Copy these files into your project
videos/components/sheen-text/sheen-text.tsx
import {Fill, Easing, interpolate, useBrand, useFrame, useDesignScale} from "odori";
export type SheenTextProps = {
text: string;
/** Frame the sweep begins. */
at?: number;
/** Frames the sweep takes to cross the line. */
sweepFrames?: number;
/** Sheen color. Defaults to the brand accent. */
color?: string;
};
/**
* The line is set immediately; one accent-colored highlight travels across it
* and never comes back. The sheen is a gradient clipped to the glyphs whose
* position is derived from the frame, so it crosses exactly once and holds —
* a looping shimmer reads as a UI affordance, not an edit.
*/
export const SheenText = ({text, at = 20, sweepFrames = 30, color}: SheenTextProps) => {
const frame = useFrame();
const brand = useBrand();
const scale = useDesignScale();
const sheen = color ?? brand.colors.accent;
const fg = brand.colors.foreground;
// The band runs past both edges so the line starts and ends fully settled.
const position = interpolate(frame, [at, at + sweepFrames], [-25, 125], {easing: Easing.standard});
return (
<Fill
style={{
alignItems: "center",
justifyContent: "center",
padding: `${120 * scale}px ${140 * scale}px`,
}}
>
<div
style={{
backgroundImage: `linear-gradient(100deg, ${fg} ${position - 14}%, ${sheen} ${position}%, ${fg} ${position + 14}%)`,
WebkitBackgroundClip: "text",
backgroundClip: "text",
WebkitTextFillColor: "transparent",
color: "transparent",
fontSize: 128 * scale,
fontWeight: 600,
letterSpacing: "-0.045em",
lineHeight: 1.04,
maxWidth: 1560 * scale,
textAlign: "center",
}}
>
{text}
</div>
</Fill>
);
};
videos/components/sheen-text/sheen-text.preview.tsx
import {defineComponentPreview} from "odori/preview";
import {SheenText} from "./sheen-text";
export default defineComponentPreview({
title: "Sheen text",
category: "Typography/Titles",
description: "A single accent sweep crosses an already-set line.",
component: SheenText,
canvas: {width: 1920, height: 1080, duration: "3s"},
controls: {
text: {type: "text", defaultValue: "Polished by default", maxLength: 32},
at: {type: "number", defaultValue: 20, min: 0, max: 90},
sweepFrames: {type: "number", defaultValue: 30, min: 10, max: 90},
},
examples: [
{name: "Default", props: {text: "Polished by default"}},
{name: "Late and slow", props: {text: "Wait for it", at: 45, sweepFrames: 60}},
],
});
Add it to a scene
import {Scene, Video} from "odori";
import {SheenText} from "../components/sheen-text/sheen-text";
<Video>
<Scene id="sheen-text" duration="3s">
<SheenText
text="Polished by default"
at={20}
sweepFrames={30}
/>
</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
- 0 frames
- Exit
- 0 frames
- Reduced motion
- the line is shown in the foreground color with no sweep
- Requires
- sans font
- Content limits
- text ≤ 32
Props
| Prop | Type | Default | Required |
|---|---|---|---|
text | string | — | Yes |
atFrame the sweep begins. | number | 20 | — |
sweepFramesFrames the sweep takes to cross the line. | number | 30 | — |
colorSheen color. Defaults to the brand accent. | 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.