---
title: "A draggable 3D game cartridge in pure CSS"
description: "No WebGL. A preserve-3d box with six faces, a drag that writes one rotation variable, and a drop-shadow trick that recolors a transparent PNG so one shell works for any accent. Pulled out of basement.fun."
date: "2026-05-30"
tags: ["css", "3d", "transform", "frontend"]
---

One of my favorite bits from [basement.fun](https://basement.fun) is the game
cartridge you can grab and spin. People assume it's WebGL. It's not, it's CSS
3D transforms. Drag it (and swap the cover, accent, and shell):

## It's a box with six faces

The whole thing is one element with `transform-style: preserve-3d` and six
absolutely-positioned children, one per face. The front and back hold the
cartridge shell PNG; the thin left, right, top, and bottom faces are filled
with the accent color so the cartridge has real depth when it turns.

```css
.box {
  transform-style: preserve-3d;
  transform: translateZ(-50px) rotateY(var(--rotation));
}
.box__face--front { transform: rotateY(0deg)   translateZ(20px); }
.box__face--back  { transform: rotateY(180deg) translateZ(20px); }
.box__face--right { transform: rotateY(90deg)  translateZ(183px); }
/* …and so on for left / top / bottom */
```

Dragging just updates `--rotation`. On release it snaps to the nearest half
turn, so you always land on the front or the back, never side-on. A pointer
handler does the math; React only ever writes one CSS variable.

```ts
const dx = clientX - startX;
setRotation(startRotation + dx * 0.5); // 0.5 = drag damping
// on release:
setRotation((r) => Math.round(r / 180) * 180);
```

## The trick I actually love: coloring with a shadow

The cartridge shells are transparent PNGs. You could ship a recolored shell per
game, but there's a slicker way. Take the PNG, give it a `drop-shadow` offset a
thousand pixels straight down in the accent color, then push the layer holding
it a thousand pixels back up:

```css
.cartridge-shadow {
  filter: drop-shadow(0 1000px 0 var(--accent-color));
}
.cartridge-bg {
  mix-blend-mode: overlay;          /* sits over the real shell */
  transform: translateY(-1000px);   /* pull the shadow into frame */
}
```

A drop-shadow is just a blurred, offset silhouette of the source's alpha. With
zero blur and a huge offset, it becomes a perfect solid-color stamp of the
cartridge, in whatever color you want. Blend it over the shell and the body
takes the accent. One PNG, infinite colors, no canvas.

## Why CSS over WebGL here

It's a single object that only really turns on one axis, so a full 3D scene is
overkill. CSS transforms are GPU-composited, there's no runtime to load, it's
crisp at any size, and it degrades gracefully. The only real cost is that you
hand-place the faces in pixels once. Worth it.

Pulled out of [basement.fun](https://basement.fun), a project I work on at B3.
Grab the component (and the shell art) on the [3D Cartridge](/components/cartridge-3d)
page.
