An interactive desktop-PC configurator in the browser. Pick a case, motherboard, CPU, cooler, memory, graphics card, storage, power supply and fans, and watch the machine assemble itself on a workbench in 3D — with real clearance checking, RGB lighting, an exploded view and shareable builds.
Every part is procedural geometry: there are no model files anywhere in this repo. A 240mm radiator is a length variable, a 3-slot graphics card is a slot-width variable, and the whole catalog is plain data.
Built with React Three Fiber, three.js and Vite.
Requires Node ^20.19 or >=22.12 (Vite 8's floor).
git clone https://github.com/codegobrrrr9/therig.gitcd therig && npm installnpm run devThen open http://localhost:5173.
| Script | What it does |
|---|---|
npm run dev |
Dev server with hot reload |
npm run build |
Production bundle into dist/ |
npm run preview |
Serve the production build locally |
npm test |
Fit rules, world-space layout and mount-placement checks |
- Six chassis, from a 260mm small-form-factor cube to a 640mm super tower
- Physical fit checking — form factor, GPU length and slot width, cooler height, radiator size, DIMM count, RAM-versus-cooler clearance, PSU depth and form factor, drive bays and fan mounts. Incompatible parts grey out with the reason.
- Mesh-level collision detection as an independent second pass: it measures the actual geometry rather than trusting the catalog, and marks offending parts in red
- Addressable RGB — static, rainbow, breathing, per-zone hue offsets, bloom, and aggregate interior lights so the chassis actually glows
- Exploded view, a turntable that rotates the machine independently of the camera, a removable side panel, and five camera presets that adapt to the fitted case
- Shareable builds — the whole configuration round-trips through the URL hash, so the link is the build. No backend, no storage.
- Screenshot export straight from the canvas
Every dimension in the catalog is authored in real millimetres. src/data/units.js
holds the single conversion to scene units (1 unit = 100mm) via mm(). Nothing else
in the codebase is allowed to cross that boundary.
+X out of the motherboard, toward the tempered-glass side panel
+Y up
+Z toward the FRONT of the case (so -Z is the rear I/O panel)
A motherboard's two in-plane dimensions are slotSpan along Y — the axis PCIe
brackets stack down — and depth along Z. That assignment is not arbitrary: ATX
(305mm) minus mATX (244mm) is exactly 61mm, which is three 20.32mm expansion slots.
The dimension that differs between the two form factors is the slot-stacking axis.
No part knows an absolute position. Each part publishes named mount points in its own local space, and children attach to them:
Case
├─ slots.motherboard ──→ Motherboard
│ ├─ socket ──→ CPU ──→ Cooler
│ ├─ dimm[n] ──→ RAM sticks
│ ├─ pcie[n] ──→ GPU
│ └─ m2[n] ──→ NVMe drives
├─ slots.psu ──→ Power supply
├─ slots.drives[] ──→ 2.5" / 3.5" drives
├─ slots.fans[] ──→ Case fans (position, normal, accepted sizes)
└─ slots.radiators──→ AIO radiator (panel run, not a fixed point)
Swapping the case moves the entire build for free. Exploded view falls out of the same structure — each part just runs its install axis backwards.
A motherboard anchors at its rear-top corner, which is what real standoffs do: ATX, mATX and ITX all share that corner, which is why they put the I/O shield in the same place.
Radiator mounts publish the panel run rather than a point, because a 240 and a 360 do not centre in the same place. Fan mounts are centred banks pitched at 145mm — above the largest fan — and the count is clamped to what the panel physically holds, so a case cannot over-declare its mounts.
src/logic/fit.js has exactly one evaluator. The parts picker and the issues panel are
both views onto it, so a rule can never disagree with itself between the two.
src/logic/collision.js is a deliberately independent net. It only tests pairs that
could never legitimately touch — RAM is supposed to intersect its DIMM slot, so a
naive all-pairs sweep would be useless — and measures in the rig's own frame, so
rotating the machine cannot invent overlaps.
Adding a part is a data edit, never a code edit. Drop an entry into the relevant
module under src/data/parts/ and it appears in the picker, gets fit-checked, priced
and rendered:
{
id: 'gpu-example-5070',
name: 'Example 5070',
brand: 'Acme',
price: 549,
category: 'gpu',
length: 290, // mm, front-to-back — checked against the case
height: 128, // mm, out from the PCIe slot
slotWidth: 2.5, // rear-panel brackets consumed
fanCount: 3,
tdp: 250,
powerConnectors: ['8-pin'],
palette: { shroud: '#252830', backplate: '#191b20', fan: '#33363f', accent: '#7a8494' },
leds: [{ id: 'gpu-bar', kind: 'strip', axis: 'z', edge: 'top' }],
}Cases are built by a makeCase() factory that derives every mount point from the outer
dimensions plus a few clearance figures, so a new chassis is about fifteen lines of
numbers and no geometry code.
src/
data/
units.js mm conversion, ATX/PCI constants, board layout
catalog.js merges and indexes every category
parts/ one module per category — the entire catalog
logic/
fit.js physical fit rules (one evaluator, two consumers)
collision.js mesh-level overlap pass
share.js build <-> URL hash
three/
Scene.jsx canvas, lighting, camera framing, post-processing
Rig.jsx the assembly — slots resolved into a scene graph
Room.jsx workbench, pegboard, tools, lamp
Part.jsx selection, hover, exploded offset, collision registration
primitives.jsx Panel, Fins, Fan, Vents, Cable, Led
materials.js shared material registry
leds.js RGB channel pool and its frame driver
parts/ one component per part type
ui/ sidebar, inspector, issues, lighting, toolbar
store/buildStore.js zustand state
test/ fit, layout and mount-placement checks
npm test runs three suites in plain Node — no test framework, no browser:
fit.test.mjs— every physical rule, plus share round-trip and pricinglayout.test.mjs— resolves each part's world-space extent from the slot data and asserts it lands inside the chassis, across E-ATX, ATX, mATX and ITX buildsmounts.test.mjs— sweeps every fan and radiator mount in every case, at both fan sizes, against the chassis interior
The layout suite exists because these are exactly the bugs that are invisible until you
look at the render: memory that intersected the graphics card, a radiator that pushed
its fan bank through the rear panel. It also cross-checks the geometry against the
declarative rule — an air cooler's fin stack and its stated ramClearance have to
agree about whether a given memory kit collides, so the mesh and the rule cannot drift
apart silently.
The app mounts Vercel Web Analytics (<Analytics />
in src/App.jsx), which records anonymous page views. It is inert unless the app is
deployed to Vercel with Web Analytics enabled — in local development it only logs to the
console and sends nothing. Delete that one component to remove it entirely.
Note this is a Vite SPA, so it uses the @vercel/analytics/react entrypoint; the
/next entrypoint depends on next/navigation and will not resolve here.
Fit checking is physical only — what you could verify with a tape measure. The following are deliberately not enforced, though the schema has room for each:
- CPU socket compatibility (the
socketfield is carried but unused) - Memory generation and speed compatibility
- PSU wattage headroom
- Cable-connection requirements
Part names, brands and prices are fictional and are not a buying guide.
MIT — see LICENSE.