Skip to content

FreeIn reviewv1.0.0

Cloudscape

A sea of cloud seen from above, or a ceiling of it seen from below, lit by a low sun.
npx shadcn@latest add @vibebox/cloudscape

Controls

1
1
<Cloudscape />

About

Four planes of fractal density at four distances, composited far to near over a sky gradient. Each plane is lit by a second density lookup a short step toward the sun: where the cloud is thicker here than there, the surface faces the light; where it is thinner, it sits in shade. Thick cloud darkens on its underside, thin cloud lets the sky through, and the far planes sink into haze. Looking up uses the same planes mirrored, with the horizon shifted to the bottom of the frame.

Use it when

  • A travel, aviation, drone, airline or weather product wants the view from above the clouds.
  • A kids, education or gifting page wants the candy look; a wellness page wants the sunrise.
  • An insurer or adviser wants a storm front to be sheltered from.

Skip it when

  • Text must sit over a high-contrast, busy area: the cloud edges move.
  • The page already runs another animated background.

Install

  1. 1Add the VibeBox registry to components.json:
    {
      "registries": {
        "@vibebox": "https://motion.vibeboxph.com/r/{name}.json"
      }
    }
  2. 2Add the component with the shadcn CLI:
    npx shadcn@latest add @vibebox/cloudscape
  3. 3Use it:
    import { Cloudscape } from "@/components/vibebox/cloudscape";
    
    <Cloudscape preset="sunrise" className="min-h-[560px] rounded-xl">
      <div className="p-12 text-white">
        <h1 className="text-4xl font-semibold">Above it all, on a calm morning.</h1>
      </div>
    </Cloudscape>

Code

3 files
components/vibebox/cloudscape/cloudscape.client.tsx
/**
 * VibeBox Motion · Cloudscape
 * 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 { cloudscapePiece, presets, type CloudscapePreset } from "./cloudscape.shader";

export type CloudscapeColor = "zenith" | "horizon" | "lit" | "shade" | "sun" | "haze";
export type CloudscapeValue = "coverage" | "scale" | "sunX" | "sunY" | "look" | "horizonY" | "drift" | "flash";

export type CloudscapeProps = {
  /** What sits on the scene: a heading, a call to action, a whole section. */
  children?: ReactNode;
  /** A named look: `sunrise`, `noon`, `candy`, `sunset`, `overcast`, `storm`. */
  preset?: CloudscapePreset;
  /** Overrides single palette colours with any CSS colour. */
  colors?: Partial<Record<CloudscapeColor, 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<CloudscapeValue, number>>;

const POSTER: PosterSpec = { type: "horizon", top: "zenith", horizon: "horizon", groundTop: "haze", groundBottom: "lit", at: 46, glow: "sun", glowAt: [60, 44] };

export function Cloudscape({ children, preset, colors, speed, intensity, interactive = true, label, still, className, coverage, scale, sunX, sunY, look, horizonY, drift, flash }: CloudscapeProps) {
  return (
    <BackdropCanvas
      slug="cloudscape"
      piece={cloudscapePiece}
      presets={presets}
      fallback="sunrise"
      preset={preset}
      colors={colors}
      values={{ coverage, scale, sunX, sunY, look, horizonY, drift, flash }}
      speed={speed}
      intensity={intensity}
      poster={POSTER}
      scale={0.5}
      stillTime={3.2}
      still={still}
      interactive={interactive}
      label={label}
      className={className}
    >
      {children}
    </BackdropCanvas>
  );
}
components/vibebox/cloudscape/cloudscape.shader.ts
/**
 * VibeBox Motion · Cloudscape
 * 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";

/*
 * A sea of cloud seen from above, or a ceiling of it seen from below. Four planes of fractal
 * density sit at different depths and recede toward the horizon; each is lit by finding which way
 * the density thins toward the sun (a second lookup a step toward it), so the lit edges glow and
 * the undersides sink into the shade colour. The pointer parts the cloud a little.
 */

const SCENE = /* glsl */ `
uniform vec3 uZenith;
uniform vec3 uHorizon;
uniform vec3 uLit;
uniform vec3 uShade;
uniform vec3 uSun;
uniform vec3 uHaze;
uniform float uCoverage;
uniform float uScale;
uniform float uSunX;
uniform float uSunY;
uniform float uLook;
uniform float uHorizonY;
uniform float uDrift;
uniform float uFlash;
uniform float uSpeed;
uniform float uIntensity;

float T;
vec2 part;

float density(vec2 p, float layer) {
  vec2 q = p + vec2(layer * 7.3, layer * 3.1);
  float base = fbm6(q * uScale);
  // A second, slower scale shapes the cloud banks.
  float bank = vnoise(q * uScale * 0.33 + 5.0);
  return base * 0.85 + bank * 0.32;
}

void main() {
  vec2 uv = centred(gl_FragCoord.xy);
  T = uTime * uSpeed;
  float aspect = uResolution.x / uResolution.y;
  bool below = uLook > 0.5;
  vec3 rd = normalize(vec3(uv.x, uv.y - uHorizonY, -1.0));
  // Looking up, the clouds are the mirror of looking down at them: dy < 0 means "toward the cloud".
  float dy = below ? -rd.y : rd.y;

  vec3 zen = lin(uZenith), hor = lin(uHorizon), lit = lin(uLit), shade = lin(uShade), sun = lin(uSun), haze = lin(uHaze);
  vec3 L = normalize(vec3(uSunX * 0.9, 0.04 + uSunY * 0.6, -1.0));
  float sd = max(dot(rd, L), 0.0);

  // The backdrop: sky from the horizon to the zenith, with the sun and its glow.
  float skyY = max(rd.y, 0.0);
  vec3 col = lin(mix(uHorizon, uZenith, smoothstep(0.0, 0.65, pow(skyY, 0.72))));
  col += sun * (pow(sd, 10.0) * 0.18 + pow(sd, 90.0) * 0.4 + 4.0 * smoothstep(0.9993, 0.9998, sd));

  // Where the pointer is, the cloud parts a little.
  vec2 pu = vec2((uPointer.x - 0.5) * aspect, uPointer.y - 0.5);
  vec2 pdir = vec2(0.0);

  if (dy < -0.002) {
    float far = 1.0;
    vec3 acc = vec3(0.0);
    float cover = 0.0;
    // Four planes, far to near: each composites over what lies behind it.
    for (int i = 0; i < 4; i++) {
      float fi = float(i);
      float depth = mix(6.0, 0.8, fi / 3.0);          // plane distance below the camera
      float t = depth / -dy;
      vec2 p = rd.xz * t;
      p.x += T * uDrift * (0.15 + 0.12 * fi) + 3.0 * fi;
      p.y += T * uDrift * 0.05;
      // The pointer's wake: density is pushed away from it.
      vec2 world = rd.xz * t;
      float pd = length(uv - pu);
      float push = uHover * 0.35 * exp(-pd * pd * 14.0);
      float d = density(p, fi) - push;
      float thresh = 0.9 - uCoverage * (0.42 + 0.04 * fi);
      float a = smoothstep(thresh, thresh + 0.16, d);
      // Light: how much denser the cloud is here than a step toward the sun.
      vec2 toSun = normalize(vec2(L.x, L.z) + vec2(0.0001, -0.0001));
      float d2 = density(p + toSun * 0.12, fi) - push;
      float facing = clamp(0.5 + (d - d2) * 7.0, 0.0, 1.0);
      // Thick cloud is bright on top and dark beneath; thin edges let the sky through.
      float thick = smoothstep(thresh, thresh + 0.45, d);
      vec3 body = mix(shade, lit, clamp(facing * 0.75 + thick * 0.4 - 0.05, 0.0, 1.0));
      body *= mix(1.0, 0.72, smoothstep(0.55, 1.0, thick) * (1.0 - facing));
      // The sun's silver on thin edges.
      body += sun * pow(sd, 6.0) * (1.0 - thick) * 0.55 * a;
      // The far planes sink into the haze.
      float fogAmt = clamp(1.0 - exp(-t * 0.028), 0.0, 1.0);
      body = mix(body, haze, fogAmt * 0.7);
      a *= smoothstep(0.0, 0.06, -dy) * (1.0 - fogAmt * 0.15);
      col = mix(col, body, a);
    }
    // Haze where the clouds meet the horizon.
    float hz = exp(-(-dy) * 18.0);
    col = mix(col, haze, hz * 0.6);
  } else if (below) {
    // Beneath the ceiling's far edge: haze, deepening away from the horizon.
    col = mix(haze, shade * 0.55, smoothstep(0.0, 0.5, max(dy, 0.0)));
  }

  col += vec3(0.7, 0.75, 0.95) * uFlash * 0.0;
  col *= uIntensity;
  col *= 1.0 - 0.22 * smoothstep(0.6, 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 = ["sunrise", "noon", "candy", "sunset", "overcast", "storm"] as const;
export type CloudscapePreset = (typeof PRESET_NAMES)[number];

export const presets: Record<CloudscapePreset, ScenePreset> = {
  sunrise: {
    colors: { zenith: "#5b6fc9", horizon: "#ffcfb0", lit: "#fff1e2", shade: "#a58bc4", sun: "#fff0cc", haze: "#ffd9c4" },
    values: { coverage: 0.85, scale: 0.9, sunX: 0.35, sunY: 0.04, look: 0, horizonY: 0.06, drift: 0.5, flash: 0, speed: 0.6, intensity: 1 },
  },
  noon: {
    colors: { zenith: "#2c78d6", horizon: "#bfe2f7", lit: "#ffffff", shade: "#8fb0d6", sun: "#ffffff", haze: "#d8eefa" },
    values: { coverage: 0.78, scale: 0.85, sunX: -0.3, sunY: 0.55, look: 0, horizonY: 0.06, drift: 0.5, flash: 0, speed: 0.6, intensity: 1 },
  },
  candy: {
    colors: { zenith: "#8fa8ff", horizon: "#ffd6ec", lit: "#fffafd", shade: "#d9a9e8", sun: "#fff6e0", haze: "#ffe3f1" },
    values: { coverage: 0.9, scale: 0.95, sunX: 0.2, sunY: 0.35, look: 0, horizonY: 0.06, drift: 0.45, flash: 0, speed: 0.55, intensity: 1.02 },
  },
  sunset: {
    colors: { zenith: "#2b2766", horizon: "#ff8a5e", lit: "#ffd1a1", shade: "#6b3d86", sun: "#ffd9a0", haze: "#ff9d78" },
    values: { coverage: 0.85, scale: 0.9, sunX: 0.1, sunY: 0.03, look: 0, horizonY: 0.06, drift: 0.45, flash: 0, speed: 0.5, intensity: 1 },
  },
  overcast: {
    colors: { zenith: "#7d8794", horizon: "#c8ced6", lit: "#e9edf1", shade: "#6c7684", sun: "#ffffff", haze: "#cfd5dc" },
    values: { coverage: 1, scale: 1.0, sunX: 0, sunY: 0.5, look: 1, horizonY: -0.42, drift: 0.6, flash: 0, speed: 0.55, intensity: 1 },
  },
  storm: {
    colors: { zenith: "#10131b", horizon: "#3b4252", lit: "#7e8aa3", shade: "#1a1e29", sun: "#b4bfd6", haze: "#2c3340" },
    values: { coverage: 1, scale: 1.1, sunX: -0.2, sunY: 0.35, look: 1, horizonY: -0.42, drift: 0.9, flash: 1, speed: 0.9, intensity: 1 },
  },
};

export const cloudscapePiece = createScenePiece("cloudscape", fragment);
components/vibebox/cloudscape/index.tsx
/**
 * VibeBox Motion · Cloudscape
 * 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 { Cloudscape, type CloudscapeProps, type CloudscapeColor, type CloudscapeValue } from "./cloudscape.client";
export { PRESET_NAMES as CLOUDSCAPE_PRESETS, presets as cloudscapePresets, type CloudscapePreset } from "./cloudscape.shader";

Props

PropTypeDefaultDescription
childrenReactNode—What sits on the scene: a heading, a call to action, a whole section.
preset"sunrise" | "noon" | "candy" | "sunset" | "overcast" | "storm""sunrise"The look: sunrise, noon, candy, sunset, overcast, storm.
colors{ zenith?: string; horizon?: string; lit?: string; shade?: string; sun?: string; haze?: string }—Overrides single palette colours with any CSS colour: zenith (the sky overhead), horizon (the sky at the horizon), lit (cloud tops in the sun), shade (cloud undersides in shadow), sun (the sun and the silver on thin edges), haze (distance haze).
speednumber1Multiplies the preset's speed; 0 holds the scene still.
intensitynumber1Multiplies the preset's brightness, 0.5 to 1.4.
coveragenumber0.85How much of the sky the cloud fills (0.3 to 1; each look sets its own).
scalenumber0.9How large the cloud forms are (larger is finer) (0.4 to 1.6; each look sets its own).
sunXnumber0.35Where the sun sits across the frame (-1 to 1; each look sets its own).
sunYnumber0.04How high the sun stands (0 to 1; each look sets its own).
looknumber00 looks down at a cloud sea, 1 looks up at a cloud ceiling (0 to 1; each look sets its own).
horizonYnumber0.06Where the horizon falls in the frame; 0 is the middle (-0.5 to 0.3; each look sets its own).
driftnumber0.5How fast the cloud moves across (0 to 1.5; each look sets its own).
flashnumber0Reserved for a storm's flicker (0 to 1; each look sets its own).
interactivebooleantrueThe pointer parts the cloud a little.
labelstring—Accessible name; leave it out when the scene is only a background.
stillbooleanfalseHold one composed frame until false (a gallery card that plays on hover).
classNamestring—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 label it 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 sky; the cloud does not drift.

Edge cases and limits

  • Pastel looks are bright: use dark text or a card, not white text.
  • On the look-up presets the horizon sits at the bottom, so the ceiling fills the frame; give it room.
  • Very high coverage on a small phone looks flat; lower coverage for more form.
  • Green and violet aurora curtains over a mountain ridge and stars

    Aurora

    Free

    Curtains of northern light over a night sky and a mountain ridge, drawn toward the pointer.

    Backgrounds · WebGL2

  • Warm amber bokeh lights of different sizes on a dark background

    Bokeh

    Free

    Out-of-focus lights in three depths, brighter at the rim and drifting with the pointer.

    Backgrounds · WebGL2

  • A dusk skyline of lit towers under an orange and violet sky

    A skyline of lit windows against a glowing sky, with a road of light trails in front.

    Backgrounds · WebGL2

↑ ↓ to move↵ to openesc to close