Three Arms — How Nigeria's Government Works
An interactive 3D explainer of Nigeria's federal government — the three arms, the three tiers, and how a bill becomes law. Each institution is a landmark you can orbit and take apart. Because a wrong fact in civic education is worse than a missing one, every constitutional claim carries the section it comes from, and anything unverified says so rather than printing a confident guess.
Live site
The problem
Most Nigerians can name the President. Far fewer could say what the Senate does that the House of Representatives doesn't, or what actually happens to a bill between "introduced" and "signed." That isn't ignorance — it's a delivery problem. The information exists, but it lives in the Constitution and in civics textbooks, which are written to be correct rather than to be understood.
Government is also, unhelpfully, invisible. It's a set of relationships between institutions you can't see. But in Nigeria those institutions have addresses: the Presidential Villa, the National Assembly and the Supreme Court sit together in Abuja's Three Arms Zone, which is where this project takes its name. The buildings are the one concrete thing about an abstract system.
So I built the thing I wanted when I was learning this: not an article about the government, but the government as an object you can pick up and turn around.
What I built
Three Arms is a browser app. Each institution is presented as a landmark on a plinth — a specimen you orbit, zoom, take apart, and read about. Numbered hotspots sit on the model itself, so "the Red Chamber" stops being a phrase and becomes a place on the eastern side of the atrium that you can tap.

The data models six institutions across all three tiers, of which three are fully built out — the app says "3 of 6 built" rather than hiding the ones that aren't ready. Between them they carry 25 key facts, 10 of which cite a specific section of the 1999 Constitution.
Alongside the specimens, a ten-stage animation follows a single document from introduction to law.
The rule that shaped everything: a wrong fact is worse than a missing one
This is civic education. If someone learns something false here, I've done more damage than if I'd never built it. Two rules fall out of that, and they run through the whole codebase.
Every constitutional claim carries its section. Not a bibliography at the bottom — the section sits inline, next to the fact: s. 48 beside the Senate's 109 seats, s. 64(1) beside the four-year term. A reader can check me rather than trust me.
Anything unverified says so. A fact's value can contain a [TODO: verify …] marker, and the panel handles that in the renderer rather than in a review process:
const TODO = /\s*\[TODO:[^\]]*\]/i;
function Fact({ label, value, source, asOf }) {
const pending = TODO.test(value);
const verified = value.replace(TODO, "").trim();
// renders `verified`, then either the citation or
// "Not yet verified in full" / "Awaiting a verified source"
}
The point is that a half-sourced fact still ships its verified half and visibly withholds the rest. It never rounds up to a confident-sounding guess. A factsNeedingSource() helper lists whatever is outstanding, so the gap is queryable instead of forgotten — it currently returns empty.
Facts about who currently holds an office get separate treatment, because they age in a way constitutional facts don't. Those carry an asOf date, and the footer's "office holders as of August 2026" is read off the data itself:
export const officeHoldersAsOf = () =>
data.flatMap((i) => (i.keyFacts ?? []).map((f) => f.asOf).filter(Boolean))[0] ?? null;
A hardcoded date in the footer would eventually lie about the facts above it. Deriving it means the claim and its date can't drift apart.
The hard part: taking a building apart
"Break open" separates a model into its massing elements — the exploded architectural drawing, not a scatter.

Three things made this harder than pushing children away from a centre.
Space. Box3.setFromObject reports world space, but a child's position is parent-local. Offsetting a local position by a world-space vector puts parts in the wrong place the moment the model is scaled or rotated — which it always is, since the plinth rig transforms the whole group. Every measurement gets brought back into the host's own space first.
Lift. The obvious move is to normalise vertical displacement against the model's bounding radius. That's wrong for this subject: these buildings are far wider than they are tall, so a radius-based lift gives even the ground slab a shove upward. Normalising against the model's real height instead means a part rises in proportion to how high it already sits — the dome travels, the slab doesn't.
dir.y = THREE.MathUtils.clamp((partCentre.y - floor) / height, 0, 1) * LIFT;
Honesty about what can't be done. A GLB exported as a single merged mesh has no separable parts. Rather than animating nothing and looking broken, the control checks for a minimum number of parts, disables itself, and says why.
Paying for 3D only when you need it
A civic tool that takes ten seconds to load is a civic tool nobody reads. Three.js and the GLTF loader are heavy, so the architecture is organised around not shipping them until they're earned.
Every institution's model is its own lazy chunk, so opening the National Assembly never downloads the Supreme Court's geometry. The GLTF loader — with its Draco and meshopt machinery — sits behind lazy() of its own, and only resolves once a probe has confirmed there's actually a GLB to load:
fetch(url, { method: "HEAD" })
// A static host that rewrites unknown paths to index.html answers 200
// with HTML, so the content type is the real test for "a GLB is here".
.then((res) => res.ok && !(res.headers.get("content-type") ?? "").includes("text/html"))
That comment is the whole trick. A single-page app is usually deployed with a catch-all rewrite that answers unknown paths with index.html — which means a missing /models/foo.glb comes back as a cheerful 200 with an HTML body, and res.ok alone would report a model that isn't there. The deploy config for this app excludes models/ from its rewrite so the probe gets a real 404, but the loader shouldn't depend on a hosting config staying correct. Checking the content type makes the probe true anywhere it's deployed.
Two smaller decisions in the same spirit: Draco decoding is self-hosted in public/draco/ rather than pulled from a CDN, so the app has no third-party runtime dependency and works behind a strict network. And institutions.js is deliberately kept free of any Three.js import, because the library screen loads that module and shouldn't pay for a 3D engine it isn't using.
The result is that dropping <institution-id>.glb into public/models/ upgrades a procedural stand-in to a real model with no code change — the probe finds it and swaps it in.
The design: two rivers, one law
The palette is the flag read literally — the page is the white stripe, the specimen well is the green. Archivo carries labels and headings while Source Serif 4 carries body copy, which is the reverse of the usual pairing, so the prose reads like a document rather than like UI chrome.
The mark is the confluence from the national coat of arms, where the Niger and the Benue meet. It isn't just a logo: it is the bill-to-law track. Two chambers run as separate channels, join at harmonisation, and continue as a single line.

The trunk is drawn heavier than the two arms, because two rivers arrive and one leaves.
What's not in it
Quiz mode, full Compare, and animations beyond bill-to-law aren't built. Three of the six institutions — the State House of Assembly, the LGA Secretariat and INEC — are stubs with no content yet, which the rail labels "Coming soon" rather than quietly omitting. There's no backend, database or auth; it's a static app over a JSON file.
The 3D models are schematic on purpose. Marker positions show how parts of a complex relate to one another; they are not a floor plan, and the app says so.
The most important next step isn't a feature — it's the remaining three institutions, because the tiers are the part of Nigerian government people understand least. State and local government are where most citizens actually touch the system, and right now they're the placeholders.
Three Arms is a civic education project. It is not affiliated with the Government of Nigeria.