Skip to content

FreeIn reviewv1.0.0

Burst

Pleated silk seen from its centre: folds run out of a dark middle like a drawn curtain, with a fine cloth grain.
npx shadcn@latest add @vibebox/burst

Controls

1
1
<Burst />

About

Polar coordinates around a point that leans toward the pointer. The pleat function is four sines of the angle at whole-number multiples of the fold count (a fractional count would leave a seam where the angle wraps), each with its own drift and a small dependence on the radius for the twist. The result is mapped through a smooth step to a lit amount, with a second faint pass stretched along the radius for the streaks; colour goes from the shadow tone to the body to the highlight. The middle is darkened and a two-scale hash grain is multiplied in.

Use it when

  • A fashion, beauty, jewellery or event page wants a rich, tactile hero.
  • A launch or campaign page wants a bold radial focus for the headline.
  • A brand with a single strong colour wants it shown as cloth.

Skip it when

  • The page is dense with copy: use Dot Waves or Studio.
  • The brand is cool and technical: use Strands or Plasma.

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/burst
  3. 3Use it:
    import { Burst } from "@/components/vibebox/burst";
    
    <Burst preset="violet" className="min-h-[560px] rounded-xl">
      <div className="p-12 text-white">
        <h1 className="text-4xl font-semibold">Drawn back, to show the light.</h1>
      </div>
    </Burst>

Code

3 files
components/vibebox/burst/burst.client.tsx
/**
 * VibeBox Motion · Burst
 * 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 { burstPiece, presets, type BurstPreset } from "./burst.shader";

export type BurstColor = "shadow" | "body" | "high" | "hole";
export type BurstValue = "folds" | "twist" | "depth" | "holeSize" | "grain";

export type BurstProps = {
  /** What sits on the scene: a heading, a call to action, a whole section. */
  children?: ReactNode;
  /** A named look: `violet`, `dusk`, `mono`, `garnet`, `jade`. */
  preset?: BurstPreset;
  /** Overrides single palette colours with any CSS colour. */
  colors?: Partial<Record<BurstColor, 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<BurstValue, number>>;

const POSTER: PosterSpec = { type: "radial", base: "shadow", glow: "body", edge: "shadow", at: [50, 50] };

export function Burst({ children, preset, colors, speed, intensity, interactive = true, label, still, className, folds, twist, depth, holeSize, grain }: BurstProps) {
  return (
    <BackdropCanvas
      slug="burst"
      piece={burstPiece}
      presets={presets}
      fallback="violet"
      preset={preset}
      colors={colors}
      values={{ folds, twist, depth, holeSize, grain }}
      speed={speed}
      intensity={intensity}
      poster={POSTER}
      scale={0.5}
      stillTime={3}
      still={still}
      interactive={interactive}
      label={label}
      className={className}
    >
      {children}
    </BackdropCanvas>
  );
}
components/vibebox/burst/burst.shader.ts
/**
 * VibeBox Motion · Burst
 * 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";

/*
 * Pleated silk seen from its centre: folds run out of a dark middle in every direction, like a
 * drawn-back curtain or the inside of a flower. The folds are the angle around the centre fed
 * through a few drifting sines, so the brightness of each pleat is how square-on it faces a soft
 * light; colour runs from the shadow tone up through the body to a highlight. A long streak runs
 * along each pleat (stretched by the radius), a fine grain sits over everything, as on cloth,
 * and the pointer leans the whole thing toward it. `twist` turns the pleats into a spiral.
 */

const SCENE = /* glsl */ `
uniform vec3 uShadow;
uniform vec3 uBody;
uniform vec3 uHigh;
uniform vec3 uHole;
uniform float uFolds;
uniform float uTwist;
uniform float uDepth;
uniform float uHoleSize;
uniform float uGrain;
uniform float uSpeed;
uniform float uIntensity;

float T;

float pleat(float a, float r) {
  // Whole numbers of folds, so the pattern closes on itself where the angle wraps: a fractional
  // count would leave a visible seam along the negative x axis.
  float k = floor(uFolds + 0.5);
  float tw = uTwist;
  float s = sin(a * k + T * 0.12 + r * tw * 3.0);
  s += 0.6 * sin(a * k * 2.0 - T * 0.09 + 1.7 + r * tw * 4.0);
  s += 0.4 * sin(a * (floor(k * 0.5) + 1.0) + T * 0.05 + 0.6);
  s += 0.25 * sin(a * k * 3.0 + T * 0.2 + r * 5.0);
  return s / 2.25;
}

void main() {
  vec2 uv = centred(gl_FragCoord.xy);
  T = uTime * uSpeed;
  float aspect = uResolution.x / uResolution.y;
  // The centre leans toward the pointer.
  vec2 lean = (uPointer - 0.5) * vec2(aspect, 1.0) * (0.12 + 0.2 * uHover);
  vec2 p = uv - lean * 0.6;
  float r = length(p);
  float a = atan(p.y, p.x);

  float f = pleat(a, r);
  // How square-on a pleat faces the light, and a second, softer pass offset along the radius.
  float lit = smoothstep(-0.75, 0.85, f);
  float streak = 0.5 + 0.5 * sin(a * floor(uFolds + 0.5) * 3.0 + r * 9.0 - T * 0.2 + f * 2.0);
  float shade = lit * (0.7 + 0.3 * streak);

  vec3 shadow = lin(uShadow);
  vec3 body = lin(uBody);
  vec3 high = lin(uHigh);
  vec3 col = mix(shadow, body, smoothstep(0.0, 0.6, shade));
  col = mix(col, high, smoothstep(0.62, 1.0, shade) * 0.85);
  // The pleats deepen toward the middle, where they meet, and open at the rim.
  col *= 0.35 + 0.95 * smoothstep(0.02, 0.5 + 0.3 * uDepth, r);
  // The hole: a dark middle with a soft shoulder.
  float hole = smoothstep(uHoleSize + 0.14, uHoleSize * 0.5, r);
  col = mix(col, lin(uHole), hole * 0.92);
  // Cloth grain, in two sizes, multiplied in.
  vec2 g = gl_FragCoord.xy;
  float grain = h2(ivec2(g * 0.9)) * 0.6 + h2(ivec2(g * 0.45) + ivec2(17, 3)) * 0.4;
  col *= 1.0 + (grain - 0.5) * 0.55 * uGrain;

  col *= uIntensity;
  col *= 1.0 - 0.28 * smoothstep(0.55, 1.15, length(uv * vec2(0.75, 1.0)));
  fragColor = vec4(finish(col, gl_FragCoord.xy), 1.0);
}
`;

export const fragment = compose(SCENE);

export const PRESET_NAMES = ["violet", "dusk", "mono", "garnet", "jade"] as const;
export type BurstPreset = (typeof PRESET_NAMES)[number];

export const presets: Record<BurstPreset, ScenePreset> = {
  violet: {
    colors: { shadow: "#120d1f", body: "#5a3fc8", high: "#d8a8ff", hole: "#0b0912" },
    values: { folds: 11, twist: 0.25, depth: 1, holeSize: 0.1, grain: 1, speed: 0.8, intensity: 1.05 },
  },
  dusk: {
    colors: { shadow: "#1a0d1c", body: "#a2417f", high: "#ffc4a0", hole: "#0f0912" },
    values: { folds: 9, twist: 0.4, depth: 1, holeSize: 0.09, grain: 1, speed: 0.7, intensity: 1.05 },
  },
  mono: {
    colors: { shadow: "#0c0d10", body: "#59606e", high: "#f2f5fa", hole: "#07080a" },
    values: { folds: 12, twist: 0.15, depth: 1.1, holeSize: 0.1, grain: 1.1, speed: 0.7, intensity: 1.05 },
  },
  garnet: {
    colors: { shadow: "#14070a", body: "#a3122b", high: "#ff9a8a", hole: "#0a0405" },
    values: { folds: 13, twist: 0.5, depth: 0.9, holeSize: 0.08, grain: 1, speed: 0.8, intensity: 1.05 },
  },
  jade: {
    colors: { shadow: "#071410", body: "#12865f", high: "#b5ffd9", hole: "#040a08" },
    values: { folds: 10, twist: 0.2, depth: 1, holeSize: 0.1, grain: 1, speed: 0.7, intensity: 1.05 },
  },
};

export const burstPiece = createScenePiece("burst", fragment);
components/vibebox/burst/index.tsx
/**
 * VibeBox Motion · Burst
 * 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 { Burst, type BurstProps, type BurstColor, type BurstValue } from "./burst.client";
export { PRESET_NAMES as BURST_PRESETS, presets as burstPresets, type BurstPreset } from "./burst.shader";

Props

PropTypeDefaultDescription
childrenReactNode—What sits on the scene: a heading, a call to action, a whole section.
preset"violet" | "dusk" | "mono" | "garnet" | "jade""violet"The look: violet, dusk, mono, garnet, jade.
colors{ shadow?: string; body?: string; high?: string; hole?: string }—Overrides single palette colours with any CSS colour: shadow (the tone in the deep folds), body (the colour of the cloth facing the light), high (the highlight along the brightest pleats), hole (the dark in the middle).
speednumber1Multiplies the preset's speed; 0 holds the scene still.
intensitynumber1Multiplies the preset's brightness, 0.5 to 1.4.
foldsnumber11How many pleats run round the centre (a whole number) (4 to 16; each look sets its own).
twistnumber0.25How far the pleats turn into a spiral (0 to 1.2; each look sets its own).
depthnumber1How far the shadow reaches out from the middle (0.4 to 1.6; each look sets its own).
holeSizenumber0.1How big the dark middle is (0.02 to 0.3; each look sets its own).
grainnumber1How visible the cloth's grain is (0 to 2; each look sets its own).
interactivebooleantrueThe centre leans toward the pointer.
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 leans the centre toward it like the cursor does; scrolling is never blocked.
Reduced motion
One still frame of the cloth; the pleats do not shift.

Edge cases and limits

  • The pleats are strongly contrasted: keep long text on a card.
  • folds is a whole number; fractional values are rounded.
  • The grain is deliberate: setting it to 0 makes the cloth look like plastic.
  • Many thin cyan, violet and magenta lines of light flowing across a dark background in one wave

    Dozens of thin lines of light riding one slow wave, bunching where they cross, with a glow that follows the pointer.

    Backgrounds · WebGL2

  • 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

↑ ↓ to move↵ to openesc to close