Pool Light
Swimming pool background: caustic light on a pool floor, two drifting sheets of turbulence ridge lines over a teal gradient with a vignette. Slow screen-blended ripples that stop under reduced motion.
pnpm dlx shadcn add @01orbital/pool-lightimport { useId } from "react"
const KEYFRAMES = `
@keyframes pool-light-a {
0%, 100% { transform: translate3d(-1.6%, -1%, 0) scale(1.09); }
50% { transform: translate3d(1.6%, 1%, 0) scale(1.15); }
}
@keyframes pool-light-b {
0%, 100% { transform: translate3d(1.3%, 0.9%, 0) scale(1.14); }
50% { transform: translate3d(-1.3%, -0.9%, 0) scale(1.07); }
}
`
type RidgeProps = {
id: string
seed: number
freq: string
center: number
slope: number
fringe: number
opacity: number
}
function Ridges({
id,
seed,
freq,
center,
slope,
fringe,
opacity,
}: RidgeProps) {
const intercept = (c: number) => 0.5 - slope * c
return (
<svg
aria-hidden="true"
className="absolute inset-0 size-full"
style={{ opacity }}
>
<defs>
<filter
id={id}
x="0"
y="0"
width="100%"
height="100%"
colorInterpolationFilters="sRGB"
>
<feTurbulence
type="fractalNoise"
baseFrequency={freq}
numOctaves={2}
seed={seed}
result="noise"
/>
<feColorMatrix
in="noise"
type="matrix"
values="1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 1"
result="mono"
/>
{/* steep ramp first, then a triangular hat = a thin iso-contour line */}
<feComponentTransfer in="mono" result="steep">
<feFuncR
type="linear"
slope={slope}
intercept={intercept(center - fringe)}
/>
<feFuncG
type="linear"
slope={slope}
intercept={intercept(center)}
/>
<feFuncB
type="linear"
slope={slope}
intercept={intercept(center + fringe)}
/>
</feComponentTransfer>
<feComponentTransfer in="steep" result="band">
<feFuncR type="table" tableValues="0 1 0" />
<feFuncG type="table" tableValues="0 1 0" />
<feFuncB type="table" tableValues="0 1 0" />
</feComponentTransfer>
<feColorMatrix
in="band"
type="matrix"
values="0.86 0 0 0 0 0 0.99 0 0 0 0 0 1 0 0 0 0 0 0 1"
result="tinted"
/>
<feGaussianBlur in="tinted" stdDeviation="0.7" />
</filter>
</defs>
<rect width="100%" height="100%" filter={`url(#${id})`} />
</svg>
)
}
// Two drifting sheets of ridges: noise, contour and drift for each.
const LAYERS = [
{
seed: 7,
freq: "0.009 0.013",
center: 0.5,
slope: 26,
fringe: 0.0016,
opacity: 0.95,
drift: "animate-[pool-light-a_34s_ease-in-out_infinite]",
},
{
seed: 31,
freq: "0.012 0.017",
center: 0.47,
slope: 33,
fringe: 0.0013,
opacity: 0.75,
drift: "animate-[pool-light-b_47s_ease-in-out_infinite]",
},
] as const
// Caustic light on a pool floor: two turbulence ridge layers drifting over a
// teal gradient. Full-bleed art layer; drop inside a `relative` parent.
function PoolLight() {
const id = useId().replace(/[^\w-]/g, "")
return (
<div
data-slot="pool-light"
aria-hidden="true"
className="pointer-events-none absolute inset-0 overflow-hidden"
style={{
background:
"linear-gradient(to bottom, oklch(38% 0.06 200), oklch(26% 0.05 210))",
}}
>
<style href="pool-light" precedence="medium">
{KEYFRAMES}
</style>
{LAYERS.map(({ drift, ...ridge }, index) => (
<div
key={ridge.seed}
className={`absolute inset-0 ${drift} mix-blend-screen will-change-transform motion-reduce:animate-none`}
>
<Ridges id={`${id}-ridge-${index}`} {...ridge} />
</div>
))}
<div
aria-hidden="true"
className="absolute inset-0"
style={{
background:
"radial-gradient(ellipse 92% 74% at 50% 32%, transparent 38%, oklch(20% 0.04 215 / 42%) 100%)",
}}
/>
</div>
)
}
export { PoolLight }
import { PoolLight } from "@/components/scene/pool-light"
// The scene has no height of its own; the frame gives it the whole stage.
export default function PoolLightDemo() {
return <PoolLight />
}
Usage
import { PoolLight } from "@/components/scene/pool-light"
// The scene has no height of its own; the frame gives it the whole stage.
export default function PoolLightDemo() {
return <PoolLight />
}Examples
New
pool-light-reservation
Dependencies
No other registry pieces.