Skip to content

FreeIn reviewv1.0.0

Magma

Molten cracks under a drifting crust, or a lava lamp of soft rising blobs.
npx shadcn@latest add @vibebox/magma

Controls

Look
1
1
<Magma />

About

Crust mode: a cellular pattern gives plates; the gap between the nearest and second-nearest points is smallest along the edges, so a smoothstep on it draws the cracks, an exponential of it the glow around them, and a power of the crack a hotter core. A slow noise warp moves the plates. Lamp mode: eight inverse-square metaballs move on slow sines, thresholded into a soft body, a hot rim and a bright core.

Use it when

  • A fitness, grill, forge, hot-sauce or energy brand wants heat.
  • A music or event page wants a lava lamp for the late set.
  • A creative studio wants a warm, abstract field.

Skip it when

  • The brand is calm and cool.
  • The crust cracks sit under a lot of small text.

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/magma
  3. 3Use it:
    import { Magma } from "@/components/vibebox/magma";
    
    <Magma preset="lava" className="min-h-[560px] rounded-xl">
      <div className="p-12 text-white">
        <h1 className="text-4xl font-semibold">Charcoal, salt, patience.</h1>
      </div>
    </Magma>

Code

3 files
components/vibebox/magma/index.tsx
/**
 * VibeBox Motion · Magma
 * 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 { Magma, type MagmaProps, type MagmaColor, type MagmaValue } from "./magma.client";
export { PRESET_NAMES as MAGMA_PRESETS, presets as magmaPresets, type MagmaPreset } from "./magma.shader";
components/vibebox/magma/magma.client.tsx
/**
 * VibeBox Motion · Magma
 * 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 { magmaPiece, presets, type MagmaPreset } from "./magma.shader";

export type MagmaColor = "rock" | "hot" | "core" | "glowM";
export type MagmaValue = "mode" | "scaleM" | "crack";

export type MagmaProps = {
  /** What sits on the scene: a heading, a call to action, a whole section. */
  children?: ReactNode;
  /** A named look: `lava`, `forge`, `lamp`, `candy`. */
  preset?: MagmaPreset;
  /** Overrides single palette colours with any CSS colour. */
  colors?: Partial<Record<MagmaColor, 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<MagmaValue, number>>;

const POSTER: PosterSpec = { type: "linear", from: "rock", to: "rock", angle: 180, blobs: [{ color: "hot", at: [30, 60], size: 48 }, { color: "core", at: [70, 40], size: 24 }] };

export function Magma({ children, preset, colors, speed, intensity, interactive = true, label, still, className, mode, scaleM, crack }: MagmaProps) {
  return (
    <BackdropCanvas
      slug="magma"
      piece={magmaPiece}
      presets={presets}
      fallback="lava"
      preset={preset}
      colors={colors}
      values={{ mode, scaleM, crack }}
      speed={speed}
      intensity={intensity}
      poster={POSTER}
      scale={0.5}
      stillTime={3}
      still={still}
      interactive={interactive}
      label={label}
      className={className}
    >
      {children}
    </BackdropCanvas>
  );
}
components/vibebox/magma/magma.shader.ts
/**
 * VibeBox Motion · Magma
 * 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";

/*
 * Molten rock and warm wax. In `crust` mode a cooling skin of plates splits along cracks that glow
 * from within, the plates drifting on a slow flow. In `lamp` mode the same palette becomes large
 * soft blobs that rise, merge and fall, like a lava lamp. The pointer warms the glass.
 */

const SCENE = /* glsl */ `
uniform vec3 uRock;
uniform vec3 uHot;
uniform vec3 uCore;
uniform vec3 uGlowM;
uniform float uMode;
uniform float uScaleM;
uniform float uCrack;
uniform float uSpeed;
uniform float uIntensity;

float T;

float blobs(vec2 p) {
  float f = 0.0;
  for (int i = 0; i < 8; i++) {
    float fi = float(i);
    vec2 c = vec2(0.82 * sin(T * (0.09 + 0.025 * fi) + fi * 2.1), 0.5 * sin(T * (0.14 + 0.02 * fi) + fi * 1.3 + 1.0));
    float r = 0.115 + 0.07 * h1(i + 9);
    f += r * r / (dot(p - c, p - c) + 0.0008);
  }
  return f;
}

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);
  vec3 col;

  if (uMode < 0.5) {
    vec2 p = uv * uScaleM * 3.0;
    // The plates drift on a slow flow.
    p += 0.6 * vec2(fbm3(p * 0.5 + vec2(T * 0.03, 0.0)), fbm3(p * 0.5 + vec2(3.0, -T * 0.025)));
    vec3 v = voronoi(p);
    float gap = v.y - v.x;
    float crack = smoothstep(0.05 + 0.2 * uCrack, 0.0, gap);
    float heat = exp(-gap * (7.0 / max(uCrack, 0.2)));
    // Each plate cools on its own: a little warmth in the middle of some.
    float warm = h1(int(v.z * 977.0)) * 0.35 + 0.25 * fbm3(p * 0.7 + T * 0.04);
    vec3 rock = lin(uRock) * (0.5 + 0.9 * warm);
    col = rock;
    col += lin(uHot) * (crack * 0.9 + heat * 0.6);
    col += lin(uCore) * pow(crack, 3.0) * 1.2;
    col += lin(uGlowM) * heat * 0.25;
    // The pointer warms the plates beneath it.
    float d = length(uv - pu);
    col += lin(uHot) * uHover * exp(-d * d * 6.0) * 0.35;
  } else {
    // Warm wax: the blobs are one field, lit from its gradient so each reads as a soft solid.
    vec2 p = uv * 1.1 * uScaleM;
    float f = blobs(p);
    float e = 0.006;
    vec2 g = vec2(blobs(p + vec2(e, 0.0)) - blobs(p - vec2(e, 0.0)), blobs(p + vec2(0.0, e)) - blobs(p - vec2(0.0, e))) / (2.0 * e);
    vec3 n = normalize(vec3(-g * 0.045, 1.0));
    vec3 L = normalize(vec3(-0.5, 0.65, 0.6));
    float body = smoothstep(0.96, 1.1, f);
    float diff = clamp(dot(n, L), 0.0, 1.0);
    float spec = pow(clamp(dot(reflect(-L, n), vec3(0.0, 0.0, 1.0)), 0.0, 1.0), 22.0);
    float rim = pow(1.0 - n.z, 1.6);
    float depth = smoothstep(1.0, 7.0, f) * 0.55;
    vec3 wax = mix(lin(uHot), lin(uCore), depth) * (0.5 + 0.75 * diff);
    wax += lin(uCore) * spec * 0.55 + lin(uGlowM) * rim * 0.5;
    // The liquid around them is lit from the foot of the lamp, and warms near the blobs.
    float foot = smoothstep(0.55, -0.6, uv.y);
    vec3 bg = lin(uRock) * (0.9 + 1.7 * foot) + lin(uGlowM) * (0.08 + 0.3 * foot) * smoothstep(0.2, 1.0, f);
    col = mix(bg, wax, body);
    col += lin(uCore) * uHover * exp(-length(uv - pu) * 3.0) * 0.12;
  }

  col *= uIntensity;
  col *= 1.0 - 0.3 * smoothstep(0.55, 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 = ["lava", "forge", "lamp", "candy"] as const;
export type MagmaPreset = (typeof PRESET_NAMES)[number];

export const presets: Record<MagmaPreset, ScenePreset> = {
  lava: {
    colors: { rock: "#1a0a08", hot: "#ff5a1a", core: "#ffd27a", glowM: "#ff3a0a" },
    values: { mode: 0, scaleM: 1, crack: 1, speed: 0.8, intensity: 1.05 },
  },
  forge: {
    colors: { rock: "#0c0c10", hot: "#ff8a2a", core: "#fff0c0", glowM: "#ff6a1a" },
    values: { mode: 0, scaleM: 1.5, crack: 0.7, speed: 0.7, intensity: 1.1 },
  },
  lamp: {
    colors: { rock: "#1a0a2a", hot: "#ff4f8a", core: "#ffd27a", glowM: "#b24bff" },
    values: { mode: 1, scaleM: 1, crack: 1, speed: 0.9, intensity: 1.05 },
  },
  candy: {
    colors: { rock: "#0b1a3a", hot: "#35d8ff", core: "#e8fbff", glowM: "#7a5cff" },
    values: { mode: 1, scaleM: 1, crack: 1, speed: 0.8, intensity: 1.05 },
  },
};

export const magmaPiece = createScenePiece("magma", fragment);

Props

PropTypeDefaultDescription
childrenReactNode—What sits on the scene: a heading, a call to action, a whole section.
preset"lava" | "forge" | "lamp" | "candy""lava"The look: lava, forge, lamp, candy.
colors{ rock?: string; hot?: string; core?: string; glowM?: string }—Overrides single palette colours with any CSS colour: rock (the cooled rock or the lamp's liquid), hot (the cracks or the blobs), core (the hottest core), glowM (the glow around the heat).
speednumber1Multiplies the preset's speed; 0 holds the scene still.
intensitynumber1Multiplies the preset's brightness, 0.5 to 1.4.
modenumber00 for a cracked crust, 1 for a lava lamp (0 to 1; each look sets its own).
scaleMnumber1How large the plates or blobs are (0.5 to 2.2; each look sets its own).
cracknumber1How wide the glowing cracks are (0.3 to 1.8; each look sets its own).
interactivebooleantrueThe pointer warms the plates or the glass beneath it.
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; nothing flows or rises.

Edge cases and limits

  • Cracks are bright and thin: keep copy over the plates.
  • Lamp looks are soft and large: copy anywhere reads well.
  • High crack fills the frame with light.
  • 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