Lagrange · Guide

How it was made

How the sheet was made to bend.

LAGRANGE is a fictional field-optics studio's showpiece: a spacetime rubber sheet rendered in three.js, built to make one hard-to-hold idea legible — that gravity is not a pull across empty space but a shape the space itself takes. Its single job is to let you drag a mass and watch the geometry answer: wells deepen, orbits re-route, and the five Lagrange points slide to their new balance.

The concept 01

Two masses sit on a grid. Each one dents the sheet by the depth of its gravitational potential, Φ = −Σ GMᵢ / r, and the grid you see is that potential drawn as height. Test particles are released with orbital velocities and simply fall along the curvature — no orbit is scripted, each is the sum of two inverse-square pulls integrated every frame. Where those pulls (and, in the rotating three-body picture, the centrifugal term) cancel, the five Lagrange points mark the places a third small body could sit still: L1–L3 strung along the axis between and beyond the masses, L4 and L5 at the apex of the two equilateral triangles the masses make.

The audience is anyone who has seen the "bowling ball on a trampoline" picture and wanted to push the ball. There is no product and no funnel here — only the field, a live readout, and one instruction: grab a mass. The whole geometry is its own explanation.

Palette 02

Every colour is argued from a gravity well seen in the dark. The concept's grid accent, #5F7FE0, is a bright line colour — it measures well under 4.5:1 as small text on the void, so it is reserved for field lines and marks only, and a pale star-ink carries all body copy.

Void
#080A12
The ground the sheet floats in — a near-black with a trace of blue, darkest at the rim.
Grid line
#5F7FE0
Lines & marks only. The rubber sheet, brightening as it bends into the wells. Never text.
Mass glow
#E8C24A
The two bodies and the Lagrange rings — warm mass warping the cold field.
Glow-hi
#F6DA86
Mass cores, fast particles, key figures — the hottest note, used sparingly.
Star-ink
#C7CFE6
Body copy. 12.6:1 on the void — legible over the animated field with no scrim.
Star-dim
#8B96BC
Secondary labels and captions. 6.8:1 — quiet, still comfortably above the floor.

Type 03

Display
Sora — gravity is just a dented sheet

Sora is a low-contrast geometric sans with a faintly technical, almost orbital roundness — the right voice for physics rendered as an instrument. Set light (300) at large sizes so the headline reads as calm statement, not marketing shout, with the two key words lifted in glow-hi.

Readout
μ 0.23 · 1.04 R · L1–L5

Space Mono for every value, ratio and label. Tabular figures hold their place as you drag, and the machined mono is a deliberate counterpoint to Sora's soft geometry — data against diagram.

Technique 04

The scene is three.js r160 (WebGL). The sheet is not a solid mesh but a lattice of LineSegments — 25 lines each way, finely sampled — whose every vertex is displaced in Y by the summed potential and coloured by depth in a custom shader (faint at the rim, bright in the wells, with a distance fog so the plane recedes cleanly). The two masses are additive-blended spheres sitting at the bottom of their own wells; the five Lagrange markers are glowing rings with projected HTML labels.

The signature is that everything derives from the same field function. One call, heightAt(x,z), sets the grid height, seats each mass in its dent, lifts every particle onto the surface it rolls across, and places the Lagrange rings. So when you drag a mass, a single flag re-runs it and the whole geometry re-knits in one frame — no piece can fall out of sync with another. The 190 test particles are integrated with a two-substep symplectic step against both masses (softened to avoid singularities), leaving short comet trails so their orbits — and the way those orbits re-route around a moved mass — read at a glance. The Lagrange points are computed geometrically each frame: L1/L2 at the Hill radius R·∛(μ/3) either side of the secondary, L3 just beyond the primary, L4/L5 as the equilateral apexes.

Dragging is a screen-space pick (project both masses, grab the nearer) then a ray cast onto the ground plane; a minimum-separation clamp keeps the two bodies from merging. A secondary-mass slider gives a second way to reshape the field. If the WebGL context fails, a static CSS grid-and-wells scene stands in. Reduced-motion settles the particles onto their orbits and renders a single frame — dented, orbiting, never blank. DPR is capped at 2, the canvas reframes on resize, and the loop pauses when the tab is hidden.

Iteration log 05

Pass 1 — Craft

Set the ink/line split first: #5F7FE0 is a line colour, so body copy moved to #C7CFE6 (12.6:1) and captions to #8B96BC (6.8:1). The wells read too shallow from a near-top-down camera, so I deepened the potential and lowered the camera to a more oblique angle — the sheet now reads as a genuine 3D funnel, not a tinted plane. Trimmed the oversized mass glow discs and set a deliberate Sora scale.

Pass 2 — Depth

Orbits were a scatter of dots — you couldn't see them orbit. Added comet trails: a short history buffer per particle, sampled every few frames so each tail spans about half a second of arc, brightening toward the head. This is what makes the signature legible — drag a mass and you watch the trails bend into new paths. Warmed the fast-particle colour and pre-rolled the simulation at boot so the resting thumbnail already shows full, swirling orbits.

Pass 3 — Hardening

Confirmed the drag actually re-curves the grid and re-routes the trails via a screenshot-hash diff, and the passive two-frame liveness check for the resting orbit. Verified the reduced-motion static frame renders a settled, dented, orbiting scene (not blank), focus-visible rings, DPR cap, resize reframe, rAF pause on hidden, and zero console errors. Stacked the console under the hero at 375px with no overflow, and applied the Chanel rule — removed the outer third mass-halo so each body reads as one clean glow.