reactbits-mcp-server
Version:
MCP Server for React Bits - Access 99+ React components with animations, backgrounds, and UI elements
146 lines (134 loc) • 6.64 kB
JSX
/* eslint-disable react/no-unknown-property */
'use client';
import { useEffect, useRef, useState } from 'react';
import { Canvas, extend, useFrame } from '@react-three/fiber';
import { useGLTF, useTexture, Environment, Lightformer } from '@react-three/drei';
import { BallCollider, CuboidCollider, Physics, RigidBody, useRopeJoint, useSphericalJoint } from '@react-three/rapier';
import { MeshLineGeometry, MeshLineMaterial } from 'meshline';
// replace with your own imports, see the usage snippet for details
import cardGLB from "./card.glb";
import lanyard from "./lanyard.png";
import * as THREE from 'three';
extend({ MeshLineGeometry, MeshLineMaterial });
export default function Lanyard({ position = [0, 0, 30], gravity = [0, -40, 0], fov = 20, transparent = true }) {
return (
<div className="relative z-0 w-full h-screen flex justify-center items-center transform scale-100 origin-center">
<Canvas
camera={{ position: position, fov: fov }}
gl={{ alpha: transparent }}
onCreated={({ gl }) => gl.setClearColor(new THREE.Color(0x000000), transparent ? 0 : 1)}
>
<ambientLight intensity={Math.PI} />
<Physics gravity={gravity} timeStep={1 / 60}>
<Band />
</Physics>
<Environment blur={0.75}>
<Lightformer intensity={2} color="white" position={[0, -1, 5]} rotation={[0, 0, Math.PI / 3]} scale={[100, 0.1, 1]} />
<Lightformer intensity={3} color="white" position={[-1, -1, 1]} rotation={[0, 0, Math.PI / 3]} scale={[100, 0.1, 1]} />
<Lightformer intensity={3} color="white" position={[1, 1, 1]} rotation={[0, 0, Math.PI / 3]} scale={[100, 0.1, 1]} />
<Lightformer intensity={10} color="white" position={[-10, 0, 14]} rotation={[0, Math.PI / 2, Math.PI / 3]} scale={[100, 10, 1]} />
</Environment>
</Canvas>
</div>
);
}
function Band({ maxSpeed = 50, minSpeed = 0 }) {
const band = useRef(), fixed = useRef(), j1 = useRef(), j2 = useRef(), j3 = useRef(), card = useRef();
const vec = new THREE.Vector3(), ang = new THREE.Vector3(), rot = new THREE.Vector3(), dir = new THREE.Vector3();
const segmentProps = { type: 'dynamic', canSleep: true, colliders: false, angularDamping: 4, linearDamping: 4 };
const { nodes, materials } = useGLTF(cardGLB);
const texture = useTexture(lanyard);
const [curve] = useState(() => new THREE.CatmullRomCurve3([new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()]));
const [dragged, drag] = useState(false);
const [hovered, hover] = useState(false);
const [isSmall, setIsSmall] = useState(() =>
typeof window !== 'undefined' && window.innerWidth < 1024
);
useRopeJoint(fixed, j1, [[0, 0, 0], [0, 0, 0], 1]);
useRopeJoint(j1, j2, [[0, 0, 0], [0, 0, 0], 1]);
useRopeJoint(j2, j3, [[0, 0, 0], [0, 0, 0], 1]);
useSphericalJoint(j3, card, [[0, 0, 0], [0, 1.50, 0]]);
useEffect(() => {
if (hovered) {
document.body.style.cursor = dragged ? 'grabbing' : 'grab';
return () => void (document.body.style.cursor = 'auto');
}
}, [hovered, dragged]);
useEffect(() => {
const handleResize = () => {
setIsSmall(window.innerWidth < 1024);
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
useFrame((state, delta) => {
if (dragged) {
vec.set(state.pointer.x, state.pointer.y, 0.5).unproject(state.camera);
dir.copy(vec).sub(state.camera.position).normalize();
vec.add(dir.multiplyScalar(state.camera.position.length()));
[card, j1, j2, j3, fixed].forEach((ref) => ref.current?.wakeUp());
card.current?.setNextKinematicTranslation({ x: vec.x - dragged.x, y: vec.y - dragged.y, z: vec.z - dragged.z });
}
if (fixed.current) {
[j1, j2].forEach((ref) => {
if (!ref.current.lerped) ref.current.lerped = new THREE.Vector3().copy(ref.current.translation());
const clampedDistance = Math.max(0.1, Math.min(1, ref.current.lerped.distanceTo(ref.current.translation())));
ref.current.lerped.lerp(ref.current.translation(), delta * (minSpeed + clampedDistance * (maxSpeed - minSpeed)));
});
curve.points[0].copy(j3.current.translation());
curve.points[1].copy(j2.current.lerped);
curve.points[2].copy(j1.current.lerped);
curve.points[3].copy(fixed.current.translation());
band.current.geometry.setPoints(curve.getPoints(32));
ang.copy(card.current.angvel());
rot.copy(card.current.rotation());
card.current.setAngvel({ x: ang.x, y: ang.y - rot.y * 0.25, z: ang.z });
}
});
curve.curveType = 'chordal';
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
return (
<>
<group position={[0, 4, 0]}>
<RigidBody ref={fixed} {...segmentProps} type="fixed" />
<RigidBody position={[0.5, 0, 0]} ref={j1} {...segmentProps}>
<BallCollider args={[0.1]} />
</RigidBody>
<RigidBody position={[1, 0, 0]} ref={j2} {...segmentProps}>
<BallCollider args={[0.1]} />
</RigidBody>
<RigidBody position={[1.5, 0, 0]} ref={j3} {...segmentProps}>
<BallCollider args={[0.1]} />
</RigidBody>
<RigidBody position={[2, 0, 0]} ref={card} {...segmentProps} type={dragged ? 'kinematicPosition' : 'dynamic'}>
<CuboidCollider args={[0.8, 1.125, 0.01]} />
<group
scale={2.25}
position={[0, -1.2, -0.05]}
onPointerOver={() => hover(true)}
onPointerOut={() => hover(false)}
onPointerUp={(e) => (e.target.releasePointerCapture(e.pointerId), drag(false))}
onPointerDown={(e) => (e.target.setPointerCapture(e.pointerId), drag(new THREE.Vector3().copy(e.point).sub(vec.copy(card.current.translation()))))}>
<mesh geometry={nodes.card.geometry}>
<meshPhysicalMaterial map={materials.base.map} map-anisotropy={16} clearcoat={1} clearcoatRoughness={0.15} roughness={0.9} metalness={0.8} />
</mesh>
<mesh geometry={nodes.clip.geometry} material={materials.metal} material-roughness={0.3} />
<mesh geometry={nodes.clamp.geometry} material={materials.metal} />
</group>
</RigidBody>
</group>
<mesh ref={band}>
<meshLineGeometry />
<meshLineMaterial
color="white"
depthTest={false}
resolution={isSmall ? [1000, 2000] : [1000, 1000]}
useMap
map={texture}
repeat={[-4, 1]}
lineWidth={1}
/>
</mesh>
</>
);
}