Skip to content

FreeIn reviewv1.0.0

Wire Globe

A sphere drawn as light: glowing wire, bright nodes, a halo of points and a pulse you drive.
npx shadcn@latest add @vibebox/wire-globe

Controls

Look
1
1
<WireGlobe />

About

A lat-long grid is evaluated on the sphere's own direction: meridians and parallels as lines whose width is one screen pixel (from the derivative of the grid coordinate), bunched meridians faded toward the poles, and nodes at the crossings. The same grid is read for the far hemisphere and added dimmer, which is what makes it see-through. A power of the viewing angle gives the rim. A cell-based halo of points on shells around it turns at its own speeds, and noise in polar coordinates forms the smoke. level multiplies the beat's size, brightness and ripple.

Use it when

  • An AI assistant, voice product or monitoring dashboard wants a mark that shows it is listening or working: drive level from the audio, the stream or the load.
  • A network, data or connectivity brand wants a globe without a map.
  • A hero wants a calm, technical centrepiece.

Skip it when

  • A warm, craft or food brand: use Morph Sphere or Planet.
  • The page needs a real map: use a map library.

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/wire-globe
  3. 3Use it:
    import { WireGlobe } from "@/components/vibebox/wire-globe";
    
    <WireGlobe preset="spectrum" className="min-h-[560px] rounded-xl">
      <div className="p-12 text-white">
        <h1 className="text-4xl font-semibold">A mark that listens.</h1>
      </div>
    </WireGlobe>

Code

3 files
components/vibebox/wire-globe/index.tsx
/**
 * VibeBox Motion · Wire Globe
 * 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 { WireGlobe, type WireGlobeProps, type WireGlobeColor, type WireGlobeValue } from "./wire-globe.client";
export { PRESET_NAMES as WIRE_GLOBE_PRESETS, presets as wireGlobePresets, type WireGlobePreset } from "./wire-globe.shader";
components/vibebox/wire-globe/wire-globe.client.tsx
/**
 * VibeBox Motion · Wire Globe
 * 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 { wireGlobePiece, presets, type WireGlobePreset } from "./wire-globe.shader";

export type WireGlobeColor = "body" | "lineA" | "lineB" | "haloC";
export type WireGlobeValue = "level" | "density" | "smoke" | "haloAmt" | "spin";

export type WireGlobeProps = {
  /** What sits on the scene: a heading, a call to action, a whole section. */
  children?: ReactNode;
  /** A named look: `spectrum`, `mint`, `ember`, `mono`. */
  preset?: WireGlobePreset;
  /** Overrides single palette colours with any CSS colour. */
  colors?: Partial<Record<WireGlobeColor, 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<WireGlobeValue, number>>;

const POSTER: PosterSpec = { type: "orb", core: "lineB", edge: "body", halo: "lineA", size: 34 };

export function WireGlobe({ children, preset, colors, speed, intensity, interactive = true, label, still, className, level, density, smoke, haloAmt, spin }: WireGlobeProps) {
  return (
    <BackdropCanvas
      slug="wire-globe"
      piece={wireGlobePiece}
      presets={presets}
      fallback="spectrum"
      preset={preset}
      colors={colors}
      values={{ level, density, smoke, haloAmt, spin }}
      speed={speed}
      intensity={intensity}
      poster={POSTER}
      clearPoster
      scale={0.8}
      stillTime={3}
      still={still}
      interactive={interactive}
      label={label}
      className={className}
    >
      {children}
    </BackdropCanvas>
  );
}
components/vibebox/wire-globe/wire-globe.shader.ts
/**
 * VibeBox Motion · Wire Globe
 * 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 sphere drawn as light: a see-through wireframe of glowing meridians and parallels with bright
 * nodes where they cross, the far side faintly showing through, a rim of light, a halo of loose
 * points orbiting it and a wisp of smoke around. `level` is its pulse: a voice, a heartbeat, a
 * download. It breathes, throws a ripple and brightens with it. The idea follows the talking
 * orb of the studio's Kai app, rebuilt as its own piece.
 */

const SCENE = /* glsl */ `
uniform vec3 uBody;
uniform vec3 uLineA;
uniform vec3 uLineB;
uniform vec3 uHaloC;
uniform float uLevel;
uniform float uDensity;
uniform float uSmoke;
uniform float uHaloAmt;
uniform float uSpin;
uniform float uSpeed;
uniform float uIntensity;

float T;

/* A lat-long grid on a unit direction: x = line coverage, y = node coverage. */
vec2 grid(vec3 q, float scale) {
  vec2 g = vec2((atan(q.z, q.x) / TAU + 0.5) * 36.0 * scale, acos(clamp(q.y, -1.0, 1.0)) / PI * 18.0 * scale);
  vec2 fw = max(fwidth(g), vec2(1e-4));
  vec2 dl = abs(fract(g - 0.5) - 0.5);
  float pole = smoothstep(0.97, 0.8, abs(q.y));
  float line = (1.0 - min(min(dl.x / fw.x * mix(5.0, 1.0, pole), dl.y / fw.y) / 0.75, 1.0)) * mix(0.3, 1.0, pole);
  float node = (1.0 - smoothstep(0.0, 1.5, length(dl / fw) * 0.5)) * pole;
  return vec2(line, node);
}

void main() {
  vec2 uv = centred(gl_FragCoord.xy);
  T = uTime * uSpeed;
  float aspect = uResolution.x / uResolution.y;
  vec2 look = (uPointer - 0.5) * (0.5 + uHover);

  // The beat: a quick swell on a slow clock, scaled by the level.
  float beat = pow(0.5 + 0.5 * sin(T * 3.1), 3.0) * uLevel;
  float R = 0.33 * (1.0 + 0.07 * beat + 0.02 * sin(T * 0.8));
  float r = length(uv);

  vec3 col = vec3(0.0);
  float alpha = 0.0;

  // The wire sphere.
  if (r < R) {
    float z = sqrt(max(R * R - r * r, 0.0));
    vec3 n = vec3(uv, z) / R;
    float cy = cos(T * 0.22 * uSpin + look.x * 0.9), sy = sin(T * 0.22 * uSpin + look.x * 0.9);
    float cp = cos(-0.28 - look.y * 0.5), sp = sin(-0.28 - look.y * 0.5);
    mat3 ry = mat3(cy, 0.0, -sy, 0.0, 1.0, 0.0, sy, 0.0, cy);
    mat3 rx = mat3(1.0, 0.0, 0.0, 0.0, cp, sp, 0.0, -sp, cp);
    vec3 q = ry * rx * n;
    // The surface ripples outward with the level.
    float ripple = 0.5 + 0.5 * sin(length(q - vec3(0.4, 0.3, 0.8)) * 14.0 - T * 4.0);
    vec2 front = grid(q, uDensity);
    // The far side: the same grid, seen through, dimmer.
    vec3 qb = ry * rx * vec3(n.x, n.y, -n.z);
    vec2 back = grid(qb, uDensity);
    float rim = pow(1.0 - n.z, 2.4);
    float hue = 0.5 + 0.5 * dot(q, normalize(vec3(-0.6, 0.55, 0.35)));
    vec3 c = mix(lin(uLineA), lin(uLineB), hue);
    vec3 cb = mix(lin(uLineA), lin(uLineB), 1.0 - hue);
    float lit = 0.55 + 0.9 * rim + 0.5 * beat + 0.15 * ripple * uLevel;
    col = c * (front.x * 0.9 + front.y * 1.4) * lit + cb * (back.x * 0.2 + back.y * 0.5);
    col += mix(c, vec3(1.0), 0.18) * rim * rim * (0.9 + 0.8 * beat);
    col += lin(uBody) * (0.08 + 0.1 * rim);
    alpha = clamp(front.x * 0.8 + front.y + back.x * 0.22 + back.y * 0.4 + rim * 0.9 + 0.1, 0.0, 1.0);
  }

  // The halo: loose points on shells around the sphere, turning at different speeds.
  float rr = r / R;
  if (uHaloAmt > 0.001 && rr > 0.98 && rr < 1.9) {
    float ang = atan(uv.y, uv.x);
    float shell = floor((rr - 0.98) * 7.0);
    float spin = T * (0.08 + 0.05 * shell) * (mod(shell, 2.0) < 1.0 ? 1.0 : -1.0);
    vec2 g = vec2((ang + spin) * 22.0, rr * 24.0);
    ivec2 id = ivec2(floor(g));
    vec2 f = fract(g) - 0.5;
    float rnd = h2(id);
    float on = step(rnd, uHaloAmt * (0.3 - 0.1 * (rr - 1.0)) * smoothstep(1.9, 1.05, rr));
    vec2 o = (h22(id) - 0.5) * 0.5;
    float d = length(f - o);
    float tw = 0.5 + 0.5 * sin(T * (1.0 + rnd * 3.0) + rnd * 30.0);
    float dotm = (1.0 - smoothstep(0.06, 0.16, d)) * on * (0.4 + 0.6 * tw);
    vec3 hc = mix(lin(uHaloC), lin(uLineB), 0.5 + 0.5 * sin(ang + T * 0.2));
    col += hc * dotm * 1.4;
    alpha = max(alpha, dotm);
  }

  // Smoke and the glow it carries: soft wisps in the line colours, brighter with the level.
  if (uSmoke > 0.001) {
    vec2 s = uv * 3.0 + vec2(0.0, -T * 0.12);
    float n = fbm(s + vec2(fbm(s * 0.8 + T * 0.05), fbm(s * 0.8 - T * 0.04 + 4.0)) * 1.3);
    float shellMask = smoothstep(0.7, 1.05, rr) * (1.0 - smoothstep(1.1, 1.9, rr));
    float a = shellMask * smoothstep(0.38, 0.85, n) * (0.28 + 0.2 * uLevel) * uSmoke;
    vec3 sc = mix(lin(uLineA), lin(uLineB), n);
    col += sc * a;
    alpha = max(alpha, a);
    // And a soft shadow under it.
    vec2 sp2 = (uv - vec2(0.0, -R * 1.45)) / vec2(R * 0.85, R * 0.14);
    float sh = exp(-dot(sp2, sp2) * 1.6) * 0.4;
    alpha = max(alpha, sh * uSmoke);
  }

  col *= uIntensity;
  alpha = clamp(alpha, 0.0, 1.0);
  vec3 outc = finish(col, gl_FragCoord.xy);
  fragColor = vec4(outc * alpha, alpha);
}
`;

export const fragment = compose(SCENE);

export const PRESET_NAMES = ["spectrum", "mint", "ember", "mono"] as const;
export type WireGlobePreset = (typeof PRESET_NAMES)[number];

export const presets: Record<WireGlobePreset, ScenePreset> = {
  spectrum: {
    colors: { body: "#1b2a6a", lineA: "#38d4ff", lineB: "#ff4fb6", haloC: "#ffd27a" },
    values: { level: 0.7, density: 1, smoke: 0.8, haloAmt: 0.8, spin: 1, speed: 1, intensity: 1.1 },
  },
  mint: {
    colors: { body: "#0a3a30", lineA: "#6dffd0", lineB: "#35a8ff", haloC: "#e8fff6" },
    values: { level: 0.6, density: 1, smoke: 0.7, haloAmt: 0.8, spin: 1, speed: 1, intensity: 1.1 },
  },
  ember: {
    colors: { body: "#4a1208", lineA: "#ff9a3a", lineB: "#ff3d54", haloC: "#ffe2a0" },
    values: { level: 0.8, density: 0.9, smoke: 0.9, haloAmt: 0.9, spin: 1.1, speed: 1, intensity: 1.1 },
  },
  mono: {
    colors: { body: "#1a1f2a", lineA: "#e8ecf5", lineB: "#8f9ab3", haloC: "#ffffff" },
    values: { level: 0.5, density: 1.1, smoke: 0.5, haloAmt: 0.6, spin: 0.8, speed: 1, intensity: 1.05 },
  },
};

export const wireGlobePiece = createScenePiece("wire-globe", fragment);

Props

PropTypeDefaultDescription
childrenReactNode—What sits on the scene: a heading, a call to action, a whole section.
preset"spectrum" | "mint" | "ember" | "mono""spectrum"The look: spectrum, mint, ember, mono.
colors{ body?: string; lineA?: string; lineB?: string; haloC?: string }—Overrides single palette colours with any CSS colour: body (the glass body inside the wire), lineA (the lines on one side of the sphere), lineB (the lines on the other side), haloC (the points of the halo).
speednumber1Multiplies the preset's speed; 0 holds the scene still.
intensitynumber1Multiplies the preset's brightness, 0.5 to 1.4.
levelnumber0.7How strongly it beats and glows (0 is calm, 1 is loud) (0 to 1; each look sets its own).
densitynumber1How fine the wire grid is (0.5 to 1.8; each look sets its own).
smokenumber0.8The wisps around it (0 to 1.4; each look sets its own).
haloAmtnumber0.8How many points orbit it (0 to 1.4; each look sets its own).
spinnumber1How fast it turns (0 to 3; each look sets its own).
interactivebooleantrueThe pointer tilts and turns the globe.
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 globe; it does not turn or pulse.

Edge cases and limits

  • The wire is thin: keep the globe at 240 px or more.
  • It sits on a transparent background; on a light page the mono and mint palettes need a dark card behind.
  • level should change smoothly; jumps read as flicker.
  • A liquid sphere of cyan, violet and magenta melting into a heart

    A liquid sphere that melts into the things you sell: fifteen shapes, four skins, tap for the next.

    3D · WebGL2

  • A planet with blue oceans, green continents and white clouds, lit from one side

    Planet

    Free

    A world: continents, ice, clouds that turn, city lights on the night side and a glowing atmosphere.

    3D · WebGL2

  • A globe made of blue dots with pulsing gold markers and pink arcs between cities

    A globe of dots with pulsing markers and arcs; drag to turn it, choose a place to spin to it.

    3D · three.js

↑ ↓ to move↵ to openesc to close