Components
Loading preview...
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.
npx shadcn@latest add https://21st.dev/r/mathewsaju210/nexus-orbimport { 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>
);
}