One of my favorite projects has been creating a webcam-based head-tracking parallax effect. The idea is simple: your screen becomes a window into a virtual 3D world.
The original prototype used Python, Pygame, and OpenCV. For this portfolio I moved the projection model into TypeScript, added multiple input modes, and connected it to both a canvas-rendered grid and the larger interactive scene.
The projection model
A normal perspective camera assumes the viewer remains centered. Off-axis projection moves that virtual eye point. For each point in the room, I combine its depth with a configurable eye depth, calculate a perspective ratio, and interpolate the point relative to the viewer's horizontal and vertical offset. Points close to the eye plane move more dramatically; distant points remain comparatively stable.
The projected virtual coordinates are then converted to pixels around the center of the viewport. Points behind the eye plane are discarded. The implementation is small, but it produces the important perceptual cue: when the viewer moves left, the nearby edges shift more than the back wall.
Building the wireframe room
The room is an axis-aligned box divided into three groups of lines. Longitudinal lines run into the screen across the floor, ceiling, and side walls. Transversal rectangles mark depth slices. A final grid closes the back wall. Every endpoint passes through the same projection function, which keeps the geometry independent from the rendering layer.
Depth values also control visual emphasis. Nearer frames can be drawn more strongly while distant parts fade, reinforcing the sense of volume without requiring complex lighting.
Mouse, motion, and camera input
The browser chooses a sensible default instead of demanding camera access. Desktop visitors get mouse-based movement. Touch devices attempt to use device orientation, including the permission flow required by iOS. Camera tracking is opt-in.
When enabled, MediaPipe estimates head position from a hidden video stream and reports normalized horizontal and vertical offsets. High-frequency input is stored in refs, then published to React state at roughly 30 frames per second through requestAnimationFrame. That prevents every raw tracking event from forcing a component-tree update.
Why the illusion works
The effect does not reconstruct the room or the viewer in full 3D. It only keeps the perspective cues internally consistent. Human vision is extremely sensitive to relative motion, so a modest shift in the vanishing geometry is enough to make a flat display feel like an opening into a deeper space.
For me, the most useful lesson was that a convincing interaction does not always require a large system. A clear geometric model, carefully normalized input, and a fallback for every permission-sensitive feature can carry most of the experience.