From Blender video to browser physics
Vercel has shared digital event tickets before, but for Vercel Ship 2024 the team wanted something more tangible. Studio Basement created a Blender video showing a virtual badge dropping down, and the idea of making that interactive—and running it entirely in the browser—became the goal.
The result is built on a focused frontend stack: React with React Three Fiber as the declarative Three.js renderer, Drei for helpers, react-three-rapier for physics, and MeshLine for a shader-based thick line that serves as the lanyard. The Blender models were prepared and optimized for the web separately. The core implementation is roughly 80 lines of mostly declarative code, with some math sprinkled in.
Setting up the physics-backed scene
The starting point is the standard React Three Fiber <Canvas>, plus a <Physics> provider from Rapier. Inside that provider, shapes are tied to physics via <RigidBody> components.
MeshLine is vanilla Three.js, so before it can be used as a JSX element it needs to be registered with React Three Fiber's extend function. This adds it to the catalog of known elements, allowing it to be referenced in the scene graph in camelCase, similar to native primitives like <mesh>.
With those pieces in place, the scene structure is straightforward: a fixed <RigidBody type="fixed"> serves as the anchor, three additional <RigidBody> components act as joints for the lanyard, and the extended meshline draws the band itself.
Building the lanyard as a chain of joints
To make the band behave like a physical rope, the implementation uses Rapier's useRopeJoint. A rope joint is a constraint that tells the physics engine how two rigid bodies interact; in this case it enforces a maximum distance between two anchor points. Each joint hangs from the previous one, forming a chain attached to the fixed body.
Connecting the joints is only half the work. The positions of these physics bodies need to be turned into a visible, smooth curve. A THREE.CatmullRomCurve3 is fed the joint positions, and it interpolates a smooth path with 32 points. That path is forwarded to the meshline for rendering.
This update happens every frame—at 60 or 120 fps depending on the display. React Three Fiber provides the useFrame hook for exactly this kind of frame-based animation logic.
Making the card draggable
The card attaches to the end of the last joint with a spherical joint, so it can rotate freely. Making it interactive requires a few additions:
- A reference to the card's rigid body
- Variables for calculating the drag target
- A state flag for whether the card is currently being dragged
Rapier's rigid body types matter here. A fixed body isn't affected by anything, a dynamic body reacts to other bodies, and kinematicPosition means the position is controlled by the user rather than the physics engine. The card switches between kinematic when dragged and dynamic when released, driven by pointer event handlers.
Calculating where a pointer event lands in 3D space requires camera unprojection. Three.js's unproject(state.camera) handles the heavy lifting, converting pointer coordinates into a word-space vector that can be applied as a kinematic translation. The lanyard joints then follow the card naturally.
One deliberate deviation from accurate physics: the card is allowed to rotate, but it always spins back-to-front rather than respecting true momentum. To achieve this, the code reads the current rotational velocity with card.current.angvel() and the rotation with card.current.rotation(), using them to drive the y-axis toward the front. The pointer events capture an offset (e.point minus the card's translation) on the down event, which is required for calculating the correct kinematic position in the frame loop.
Rendering a dynamic name texture
The badge displays the user's name, but it's not baked into the model. Instead, a separate Three.js scene renders the name and badge base, and Drei's <RenderTexture> component captures that scene into a texture. The badge base is laid out first, then the user's name is added using Drei's <Text3D> component.
Because this name scene is entirely separate from the physics scene, its output needs to be attached to the badge as a material map. The rendered texture is plugged directly into mesh.map of the badge's mesh.
Polishing and prototyping
Once the core mechanics work with placeholder geometry, swapping in the Blender models is straightforward. The remaining effort goes into stabilizing the simulation—tweaking values and math to reduce shakiness in the physics chain.
The result is a shareable element that rewards registration with a personalized, physics-driven keepsake rather than a static ticket image.



