About
Depth is the distance from the top of the frame; the water mixes from the surface colour to the abyss colour along it. Shafts are three sine bands over a slanted coordinate, sharpened with a power and broken by noise, fading with depth, and cut near the pointer. Caustics are the cellular edge pattern, strongest in the top third. Flecks are three layers of cell-based points drifting upward past.
Use it when
- A dive, aquarium, pool, fisheries or marine brand wants the water.
- A spa or resort page wants calm blue.
- A science or education page wants the ocean.
Skip it when
- The brand is dry, warm or urban.
- A lot of body text sits in the top third where the caustics run.
Install
- 1Add the VibeBox registry to
components.json:{ "registries": { "@vibebox": "https://motion.vibeboxph.com/r/{name}.json" } } - 2Add the component with the shadcn CLI:
npx shadcn@latest add @vibebox/deep-sea - 3Use it:
import { DeepSea } from "@/components/vibebox/deep-sea"; <DeepSea preset="aquarium" className="min-h-[560px] rounded-xl"> <div className="p-12 text-white"> <h1 className="text-4xl font-semibold">Twenty metres down, nothing but quiet.</h1> </div> </DeepSea>
Code
3 filescomponents/vibebox/deep-sea/deep-sea.client.tsx
/**
* VibeBox Motion · Deep Sea
* Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
* https://motion.vibeboxph.com/license
* Use it in unlimited personal, commercial and client projects.
* Do not sell or redistribute it as a component, kit, template or builder.
*/
"use client";
import type { ReactNode } from "react";
import { BackdropCanvas, type PosterSpec } from "@/lib/vibebox/backdrop-canvas";
import { deepSeaPiece, presets, type DeepSeaPreset } from "./deep-sea.shader";
export type DeepSeaColor = "surface" | "abyss" | "ray" | "fleck";
export type DeepSeaValue = "rays" | "caustics" | "flecks";
export type DeepSeaProps = {
/** What sits on the scene: a heading, a call to action, a whole section. */
children?: ReactNode;
/** A named look: `aquarium`, `abyss`, `lagoon`, `arctic`. */
preset?: DeepSeaPreset;
/** Overrides single palette colours with any CSS colour. */
colors?: Partial<Record<DeepSeaColor, string>>;
/** Multiplies the preset's speed (1 keeps it, 0 holds the scene still). */
speed?: number;
/** Multiplies the preset's brightness. */
intensity?: number;
/** The pointer and touch move the scene. */
interactive?: boolean;
/** Accessible name; leave it out when the scene is only a background. */
label?: string;
/** Hold one still frame until this is false (for a gallery card). */
still?: boolean;
className?: string;
} & Partial<Record<DeepSeaValue, number>>;
const POSTER: PosterSpec = { type: "linear", from: "surface", to: "abyss", angle: 180 };
export function DeepSea({ children, preset, colors, speed, intensity, interactive = true, label, still, className, rays, caustics, flecks }: DeepSeaProps) {
return (
<BackdropCanvas
slug="deep-sea"
piece={deepSeaPiece}
presets={presets}
fallback="aquarium"
preset={preset}
colors={colors}
values={{ rays, caustics, flecks }}
speed={speed}
intensity={intensity}
poster={POSTER}
scale={0.5}
stillTime={3}
still={still}
interactive={interactive}
label={label}
className={className}
>
{children}
</BackdropCanvas>
);
}components/vibebox/deep-sea/deep-sea.shader.ts
/**
* VibeBox Motion · Deep Sea
* Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
* https://motion.vibeboxph.com/license
* Use it in unlimited personal, commercial and client projects.
* Do not sell or redistribute it as a component, kit, template or builder.
*/
import { compose, createScenePiece, type ScenePreset } from "@/lib/vibebox/engine/backdrops";
/*
* Under the surface: water that darkens with depth, shafts of light slanting down from above that
* sway and break up, caustics dancing near the top, and flecks of marine snow drifting through.
* The pointer parts the shafts and stirs the flecks.
*/
const SCENE = /* glsl */ `
uniform vec3 uSurface;
uniform vec3 uAbyss;
uniform vec3 uRay;
uniform vec3 uFleck;
uniform float uRays;
uniform float uCaustics;
uniform float uFlecks;
uniform float uSpeed;
uniform float uIntensity;
float T;
float caustic(vec2 p) {
vec2 q = p + 0.25 * vec2(sin(p.y * 1.7 + T * 0.5), cos(p.x * 1.5 - T * 0.4));
vec3 v = voronoi(q + vec2(T * 0.08, 0.0));
return pow(1.0 - smoothstep(0.0, 0.12, v.y - v.x), 3.0);
}
void main() {
vec2 uv = centred(gl_FragCoord.xy);
T = uTime * uSpeed;
float aspect = uResolution.x / uResolution.y;
vec2 pu = vec2((uPointer.x - 0.5) * aspect, uPointer.y - 0.5);
float depth = clamp(0.5 - uv.y, 0.0, 1.0);
vec3 col = mix(lin(uSurface), lin(uAbyss), pow(depth, 0.75));
// Shafts: soft slanted bands, brighter near the surface, drifting and shimmering.
float slant = uv.x + uv.y * 0.42;
float parted = uHover * exp(-pow(slant - (pu.x + pu.y * 0.42), 2.0) * 18.0) * 0.5;
float shafts = 0.0;
for (int i = 0; i < 3; i++) {
float fi = float(i);
float s = slant * (3.0 + fi * 2.4) + T * (0.05 + 0.02 * fi) + fi * 4.0;
float band = 0.5 + 0.5 * sin(s + 1.6 * sin(s * 0.37 + T * 0.1));
band = pow(band, 3.0 + fi);
shafts += band * (0.55 - 0.12 * fi);
}
shafts *= (0.4 + 0.6 * smoothstep(0.0, 0.7, fbm3(vec2(slant * 4.0, T * 0.06 + uv.y))));
shafts *= smoothstep(0.95, 0.0, depth) * (1.0 - parted);
col += lin(uRay) * shafts * uRays * 0.5;
// Caustics, strongest in the top third.
float c = caustic(uv * 3.0 + 4.0) * 0.6 + caustic(uv * 5.2 - 2.0) * 0.4;
col += lin(uRay) * c * uCaustics * smoothstep(0.65, 0.0, depth) * 0.5;
// Marine snow: three depths of drifting specks.
for (int l = 0; l < 3; l++) {
float fl = float(l);
float scale = 18.0 + fl * 22.0;
vec2 p = uv * scale;
p.y += T * (0.6 + fl * 0.4);
p.x += sin(T * 0.1 + fl) * 2.0 + (pu.x - uv.x) * uHover * 1.2;
ivec2 id = ivec2(floor(p)) + ivec2(int(fl) * 37, 11);
if (h2(id) < uFlecks * 0.18) {
vec2 o = h22(id + ivec2(3, 3)) * 0.6 + 0.2;
float d = length(fract(p) - o);
col += lin(uFleck) * smoothstep(0.14, 0.0, d) * (0.4 + 0.3 * (1.0 - fl * 0.3));
}
}
col *= uIntensity;
col *= 1.0 - 0.34 * smoothstep(0.5, 1.2, length(uv * vec2(0.85, 1.1)));
fragColor = vec4(finish(col, gl_FragCoord.xy), 1.0);
}
`;
export const fragment = compose(SCENE);
export const PRESET_NAMES = ["aquarium", "abyss", "lagoon", "arctic"] as const;
export type DeepSeaPreset = (typeof PRESET_NAMES)[number];
export const presets: Record<DeepSeaPreset, ScenePreset> = {
aquarium: {
colors: { surface: "#0f7fb0", abyss: "#03203a", ray: "#bff3ff", fleck: "#d8f6ff" },
values: { rays: 1, caustics: 1, flecks: 1, speed: 0.8, intensity: 1.05 },
},
abyss: {
colors: { surface: "#0a2e5a", abyss: "#010510", ray: "#6fb7ff", fleck: "#9fd0ff" },
values: { rays: 0.7, caustics: 0.5, flecks: 1.2, speed: 0.6, intensity: 1.05 },
},
lagoon: {
colors: { surface: "#20c9c0", abyss: "#046a7a", ray: "#e8fffb", fleck: "#ffffff" },
values: { rays: 1.1, caustics: 1.2, flecks: 0.7, speed: 0.8, intensity: 1.05 },
},
arctic: {
colors: { surface: "#6fc4ff", abyss: "#0a2a5a", ray: "#ffffff", fleck: "#e8f6ff" },
values: { rays: 1, caustics: 0.8, flecks: 0.9, speed: 0.6, intensity: 1.05 },
},
};
export const deepSeaPiece = createScenePiece("deep-sea", fragment);components/vibebox/deep-sea/index.tsx
/**
* VibeBox Motion · Deep Sea
* Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
* https://motion.vibeboxph.com/license
* Use it in unlimited personal, commercial and client projects.
* Do not sell or redistribute it as a component, kit, template or builder.
*/
export { DeepSea, type DeepSeaProps, type DeepSeaColor, type DeepSeaValue } from "./deep-sea.client";
export { PRESET_NAMES as DEEP_SEA_PRESETS, presets as deepSeaPresets, type DeepSeaPreset } from "./deep-sea.shader";Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | What sits on the scene: a heading, a call to action, a whole section. |
| preset | "aquarium" | "abyss" | "lagoon" | "arctic" | "aquarium" | The look: aquarium, abyss, lagoon, arctic. |
| colors | { surface?: string; abyss?: string; ray?: string; fleck?: string } | — | Overrides single palette colours with any CSS colour: surface (the water near the surface), abyss (the water at depth), ray (the light shafts and caustics), fleck (the marine snow). |
| speed | number | 1 | Multiplies the preset's speed; 0 holds the scene still. |
| intensity | number | 1 | Multiplies the preset's brightness, 0.5 to 1.4. |
| rays | number | 1 | How bright the shafts are (0 to 1.6; each look sets its own). |
| caustics | number | 1 | How bright the caustic light is (0 to 1.6; each look sets its own). |
| flecks | number | 1 | How much marine snow drifts through (0 to 1.8; each look sets its own). |
| interactive | boolean | true | The pointer parts the light shafts and stirs the flecks. |
| label | string | — | Accessible name; leave it out when the scene is only a background. |
| still | boolean | false | Hold one composed frame until false (a gallery card that plays on hover). |
| className | string | — | Merged with cn(); give it a size and a radius. |
Accessibility and motion
- Keyboard
- Not focusable; content on it keeps its own focus order.
- Screen readers
- Decorative by default; with
labelit is an image with that name. - Touch
- A finger moves the scene like the cursor does; scrolling is never blocked.
- Reduced motion
- One still frame of the water; the light does not move.
Edge cases and limits
- Caustics are busy in the top third; keep the heading lower.
- Arctic is light: use dark text.
- Many flecks on a phone look like noise; keep
flecksunder 1.
Related

Ocean
FreeA sea from a low viewpoint: a swell, a sun or moon on the horizon, and a glitter path the pointer ripples.
Backgrounds · WebGL2

Aurora
FreeCurtains of northern light over a night sky and a mountain ridge, drawn toward the pointer.
Backgrounds · WebGL2

Bokeh
FreeOut-of-focus lights in three depths, brighter at the rim and drifting with the pointer.
Backgrounds · WebGL2