Mathew

NexusOrb

Component

About

NexusOrb is an interactive 3D React component (Three.js) featuring a customizable low-poly sphere and a dynamic, proximity-linked particle network. It includes drag-to-rotate physics, scroll-to-zoom, and Unreal Bloom post-processing.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds NexusOrb to your project and fits it to your theme.

Code

How NexusOrb is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

import { useState } from "react";
import { NexusOrb, NexusOrbProps } from "@/components/ui/nexus-orb";

const DEFAULT_PROPS: NexusOrbProps = {
  showSphere: true,
  sphereDetail: 4,
  dotCount: 300,
  dotSpeed: 0.3,
  autoSpinX: 1.2,
  autoSpinY: 1.4,
  autoSpinZ: 1.2,
  sphereColor: "#7638b7",
  sphereEmissive: "#371669",
  sphereRoughness: 0.5,
  sphereMetalness: 0.16,
  sphereFlatShading: true,
  dotColor: "#ffffff",
  lineColor: "#ffe6ff",
  dotSize: 0.03,
  connectDistance: 1.0,
  bloomStrength: 1.8,
  bloomRadius: 0.6,
  bloomThreshold: 0.5,
};

export default function DemoOne() {
  const [props, setProps] = useState<NexusOrbProps>(DEFAULT_PROPS);
  const [menuOpen, setMenuOpen] = useState(true);

  const handleChange = (key: keyof NexusOrbProps, value: any) => {
    setProps((prev) => ({ ...prev, [key]: value }));
  };

  const handleReset = () => setProps(DEFAULT_PROPS);

  return (
    <div className="relative w-full h-screen overflow-hidden">
      <NexusOrb {...props} />

      {/* Settings Toggle Button - Changed from top-4 to bottom-4 */}
      <button
        onClick={() => setMenuOpen(!menuOpen)}
        className="absolute bottom-4 right-4 z-20 bg-black/50 border border-white/20 text-white px-4 py-2 rounded-lg backdrop-blur-md hover:bg-black/80 transition"
      >
        ⚙️ Controls
      </button>

      {/* Controls Overlay - Changed from top-16 to bottom-20 so it opens upwards */}
      <div
        className={`absolute bottom-20 right-4 z-10 w-72 max-h-[80vh] overflow-y-auto bg-black/60 backdrop-blur-md border border-white/10 rounded-xl p-4 text-white transition-all duration-300 ${
          menuOpen ? "opacity-100 translate-x-0" : "opacity-0 translate-x-8 pointer-events-none"
        }`}
        style={{ scrollbarWidth: "thin", scrollbarColor: "rgba(255,255,255,0.3) transparent" }}
      >
        <div className="flex flex-col gap-4">
          <details open className="bg-black/30 rounded-lg p-2">
            <summary className="cursor-pointer font-semibold text-green-400 outline-none pb-2">Motion & Structure</summary>
            <div className="flex flex-col gap-3 pt-2 border-t border-white/10">
              <label className="flex justify-between items-center text-sm">
                Show Core Sphere <input type="checkbox" checked={props.showSphere} onChange={(e) => handleChange("showSphere", e.target.checked)} className="accent-green-400" />
              </label>
              <label className="flex flex-col text-sm gap-1">Sphere Detail: {props.sphereDetail}
                <input type="range" min="0" max="8" step="1" value={props.sphereDetail} onChange={(e) => handleChange("sphereDetail", parseInt(e.target.value))} className="accent-green-400" />
              </label>
              <label className="flex flex-col text-sm gap-1">Dot Count: {props.dotCount}
                <input type="range" min="10" max="400" step="5" value={props.dotCount} onChange={(e) => handleChange("dotCount", parseInt(e.target.value))} className="accent-green-400" />
              </label>
              <label className="flex flex-col text-sm gap-1">Dot Speed: {props.dotSpeed}x
                <input type="range" min="0" max="5" step="0.1" value={props.dotSpeed} onChange={(e) => handleChange("dotSpeed", parseFloat(e.target.value))} className="accent-green-400" />
              </label>
              <label className="flex flex-col text-sm gap-1">Auto Spin X: {props.autoSpinX}
                <input type="range" min="-10" max="10" step="0.1" value={props.autoSpinX} onChange={(e) => handleChange("autoSpinX", parseFloat(e.target.value))} className="accent-green-400" />
              </label>
              <label className="flex flex-col text-sm gap-1">Auto Spin Y: {props.autoSpinY}
                <input type="range" min="-10" max="10" step="0.1" value={props.autoSpinY} onChange={(e) => handleChange("autoSpinY", parseFloat(e.target.value))} className="accent-green-400" />
              </label>
              <label className="flex flex-col text-sm gap-1">Auto Spin Z: {props.autoSpinZ}
                <input type="range" min="-10" max="10" step="0.1" value={props.autoSpinZ} onChange={(e) => handleChange("autoSpinZ", parseFloat(e.target.value))} className="accent-green-400" />
              </label>
            </div>
          </details>

          <details className="bg-black/30 rounded-lg p-2">
            <summary className="cursor-pointer font-semibold text-green-400 outline-none pb-2">Core Sphere Style</summary>
            <div className="flex flex-col gap-3 pt-2 border-t border-white/10">
              <label className="flex justify-between items-center text-sm">Base Color 
                <input type="color" value={props.sphereColor} onChange={(e) => handleChange("sphereColor", e.target.value)} className="w-8 h-8 rounded bg-transparent border-0 cursor-pointer" />
              </label>
              <label className="flex justify-between items-center text-sm">Emissive 
                <input type="color" value={props.sphereEmissive} onChange={(e) => handleChange("sphereEmissive", e.target.value)} className="w-8 h-8 rounded bg-transparent border-0 cursor-pointer" />
              </label>
              <label className="flex flex-col text-sm gap-1">Roughness: {props.sphereRoughness}
                <input type="range" min="0" max="1" step="0.01" value={props.sphereRoughness} onChange={(e) => handleChange("sphereRoughness", parseFloat(e.target.value))} className="accent-green-400" />
              </label>
              <label className="flex flex-col text-sm gap-1">Metalness: {props.sphereMetalness}
                <input type="range" min="0" max="1" step="0.01" value={props.sphereMetalness} onChange={(e) => handleChange("sphereMetalness", parseFloat(e.target.value))} className="accent-green-400" />
              </label>
              <label className="flex justify-between items-center text-sm">Low Poly <input type="checkbox" checked={props.sphereFlatShading} onChange={(e) => handleChange("sphereFlatShading", e.target.checked)} className="accent-green-400" /></label>
            </div>
          </details>

          <details className="bg-black/30 rounded-lg p-2">
            <summary className="cursor-pointer font-semibold text-green-400 outline-none pb-2">Network & Bloom</summary>
            <div className="flex flex-col gap-3 pt-2 border-t border-white/10">
              <label className="flex justify-between items-center text-sm">Dot Color 
                <input type="color" value={props.dotColor} onChange={(e) => handleChange("dotColor", e.target.value)} className="w-8 h-8 rounded bg-transparent border-0 cursor-pointer" />
              </label>
              <label className="flex justify-between items-center text-sm">Line Color 
                <input type="color" value={props.lineColor} onChange={(e) => handleChange("lineColor", e.target.value)} className="w-8 h-8 rounded bg-transparent border-0 cursor-pointer" />
              </label>
              <label className="flex flex-col text-sm gap-1">Dot Size: {props.dotSize}
                <input type="range" min="0.01" max="0.5" step="0.01" value={props.dotSize} onChange={(e) => handleChange("dotSize", parseFloat(e.target.value))} className="accent-green-400" />
              </label>
              <label className="flex flex-col text-sm gap-1">Connect Distance: {props.connectDistance}
                <input type="range" min="0.1" max="3.0" step="0.1" value={props.connectDistance} onChange={(e) => handleChange("connectDistance", parseFloat(e.target.value))} className="accent-green-400" />
              </label>
              <label className="flex flex-col text-sm gap-1">Bloom Strength: {props.bloomStrength}
                <input type="range" min="0" max="5" step="0.1" value={props.bloomStrength} onChange={(e) => handleChange("bloomStrength", parseFloat(e.target.value))} className="accent-green-400" />
              </label>
              <label className="flex flex-col text-sm gap-1">Bloom Radius: {props.bloomRadius}
                <input type="range" min="0" max="2" step="0.01" value={props.bloomRadius} onChange={(e) => handleChange("bloomRadius", parseFloat(e.target.value))} className="accent-green-400" />
              </label>
              <label className="flex flex-col text-sm gap-1">Bloom Threshold: {props.bloomThreshold}
                <input type="range" min="0" max="1" step="0.01" value={props.bloomThreshold} onChange={(e) => handleChange("bloomThreshold", parseFloat(e.target.value))} className="accent-green-400" />
              </label>
            </div>
          </details>

          <button onClick={handleReset} className="w-full bg-red-500/80 hover:bg-red-500 text-white py-2 rounded-lg font-semibold transition mt-2">
            Reset Defaults
          </button>
        </div>
      </div>
    </div>
  );
}

Dependencies

More by Mathew

View all
  • Trail Grid

    Trail Grid

    100
  • Radial Time Picker

    Radial Time Picker

    84
  • Scrubbable Inputs

    Scrubbable Inputs

    1
  • "Native" Mobile Nav

    "Native" Mobile Nav