Gel wash
Blurred bands of color over a dark background.
Customize
import {Scene, Video} from "odori";
import {GelWash} from "../components/gel-wash/gel-wash";
<Video>
<Scene id="gel-wash" duration="6s">
<GelWash
intensity={0.55}
speed={1}
curtains={3}
/>
</Scene>
</Video>Installation
pnpm odori add component gel-washnpx odori add component gel-washbunx odori add component gel-washEdit the source in videos/components/gel-wash/. Use odori diff to compare upstream changes and odori update to apply updates.
Copy these files into your project
videos/components/gel-wash/gel-wash.tsx
import {Fill, random, randomBetween, useBrand, useDesignScale, useFrame, useVideo} from "odori";
/**
* Two or three blurred colour curtains hanging across the frame, each drifting
* on its own slow sine path.
*
* The aurora look is skew: a straight gradient bar, tilted and heavily blurred,
* reads as a sheet of light seen edge-on. The curtains never share a rate or a
* phase — each takes its own from a seed — so the motion never synchronizes
* into an obvious loop, and never disagrees between preview and export either.
*
* Intensity is a ceiling, not a glow: this sits under titles at full opacity,
* so the default keeps every curtain translucent and the ground dark.
*/
export type GelWashProps = {
/** One colour per curtain, cycled if short. Defaults to the brand. */
colors?: string[];
/** Opacity ceiling for the brightest curtain. */
intensity?: number;
/** How fast the curtains drift. */
speed?: number;
/** How many curtains hang, 2 to 4. */
curtains?: number;
};
export const GelWash = ({colors, intensity = 0.55, speed = 1, curtains = 3}: GelWashProps) => {
const frame = useFrame();
const brand = useBrand();
const scale = useDesignScale();
const {fps, width, height} = useVideo();
const palette = colors?.length ? colors : [brand.colors.accent, brand.colors.muted, brand.colors.surface];
const count = Math.max(2, Math.min(4, Math.round(curtains)));
const t = (frame / fps) * speed;
return (
<Fill style={{background: brand.colors.background, overflow: "hidden"}}>
{Array.from({length: count}, (_, index) => {
// Everything a curtain does differently, it takes from its seed, so
// the arrangement is fixed however many times this renders.
const rate = randomBetween(`curtain-${index}-rate`, 0.05, 0.11);
const phase = random(`curtain-${index}-phase`) * Math.PI * 2;
const tilt = randomBetween(`curtain-${index}-tilt`, -24, 24);
const anchor = randomBetween(`curtain-${index}-x`, 15, 85);
const sway = Math.sin(t * rate * Math.PI * 2 + phase) * width * 0.12;
const rise = Math.cos(t * rate * 0.7 * Math.PI * 2 + phase * 1.7) * height * 0.06;
const colour = palette[index % palette.length];
return (
<div
key={index}
aria-hidden
style={{
background: `linear-gradient(180deg, transparent 0%, ${colour} 45%, transparent 100%)`,
filter: `blur(${90 * scale}px)`,
height: height * 1.6,
left: `calc(${anchor}% - ${180 * scale}px)`,
opacity: intensity * (1 - index * 0.18),
position: "absolute",
top: -height * 0.3,
transform: `translate(${sway}px, ${rise}px) rotate(${tilt}deg) skewX(${tilt * 0.5}deg)`,
width: 360 * scale,
}}
/>
);
})}
</Fill>
);
};
videos/components/gel-wash/gel-wash.preview.tsx
import {defineComponentPreview} from "odori/preview";
import {GelWash} from "./gel-wash";
export default defineComponentPreview({
title: "Gel wash",
category: "Backgrounds",
description: "Blurred bands of color over a dark background.",
component: GelWash,
canvas: {width: 1920, height: 1080, duration: "6s"},
controls: {
intensity: {type: "number", defaultValue: 0.55, min: 0, max: 1, step: 0.05},
speed: {type: "number", defaultValue: 1, min: 0, max: 3, step: 0.1},
curtains: {type: "number", defaultValue: 3, min: 2, max: 4, step: 1},
},
examples: [
{name: "Brand", props: {}},
{name: "Polar", props: {colors: ["#3ddc97", "#4f8fdd", "#7a5fd0"], intensity: 0.45}},
{name: "Faint", props: {curtains: 2, intensity: 0.15, speed: 0.6}},
],
});
Add it to a scene
import {Scene, Video} from "odori";
import {GelWash} from "../components/gel-wash/gel-wash";
<Video>
<Scene id="gel-wash" duration="6s">
<GelWash
intensity={0.55}
speed={1}
curtains={3}
/>
</Scene>
</Video>Timing contract
Use these durations and content limits when composing a scene. Run odori test to check your video.
- Family
- Backgrounds
- Recommended duration
- 180 frames · 6s
- Minimum duration
- 30 frames · 1s
- Entrance
- 0 frames
- Exit
- 0 frames
- Reduced motion
- set speed to 0 and the curtains hang still in their seeded positions
- Requires
- Odori
- Content limits
- none
Props
| Prop | Type | Default | Required |
|---|---|---|---|
colorsOne colour per curtain, cycled if short. Defaults to the brand. | string[] | — | — |
intensityOpacity ceiling for the brightest curtain. | number | 0.55 | — |
speedHow fast the curtains drift. | number | 1 | — |
curtainsHow many curtains hang, 2 to 4. | number | 3 | — |
Related components
All componentsAurora veilFlowing curtains of colored light.Bead fieldA fixed grid of dots that swell as two drifting ripples cross them.Deep swellOcean waves beneath a sunlit horizon.Dither fieldA moving gradient with pixelated shading.Dust motesSoft particles drifting across the frame.Glass causticsShifting patterns of light beneath water.