← Perigee THE GUIDE · how it was built
Design showcase · build notes

Making a sky you can steer.

Perigee looks like a native observatory console, but it is one HTML file, one stylesheet and one ES module — no framework, no build step. A 72,000-star WebGL field carries the mood; a small 2D canvas and some real astronomy carry the interaction.

The idea

A telescope network sells one feeling: that the sky is shared — that pointing your scope at a faint smudge lets strangers on three continents watch the same photons land. The site has to make that legible in seconds. So the background isn't a picture of space; it's a real depth field you move through, and the console lets you actually sweep a reticle and lock onto catalogued objects the way an operator would. Everything else — the node grid, the observation stack — is evidence the network is real.

The stack

Signature technique — the deep-space starfield

The field is a single THREE.Points cloud of ~72k vertices scattered through a deep box volume, drawn with additive blending so overlapping stars bloom. Each star carries its own colour, scale and twinkle phase as buffer attributes:

for (let i = 0; i < COUNT; i++) {
  positions[i*3+0] = (rnd()*2-1) * 1700;
  positions[i*3+1] = (rnd()*2-1) * 1300;
  positions[i*3+2] = -1800 + rnd()*2100;      // deep Z → parallax
  scales[i]  = 0.35 + Math.pow(rnd(), 3) * 2.6; // few big, many tiny
  phases[i]  = rnd() * Math.PI * 2;             // twinkle offset
}

A tiny ShaderMaterial does the rest on the GPU. Point size falls off with distance (so the volume reads as depth, not a flat sheet), twinkle is a sine of time plus each star's phase, and the fragment shader turns every square point into a soft round glow:

// vertex
float tw = 0.55 + 0.45 * sin(uTime * 1.7 + aPhase);
float size = uSize * aScale * (0.55 + 0.7*tw) * uPixelRatio
           * (320.0 / max(-mv.z, 1.0));           // perspective falloff
gl_PointSize = clamp(size, 0.0, 28.0 * uPixelRatio);

// fragment — round, soft-edged star
float d = length(gl_PointCoord - vec2(0.5));
float a = pow(smoothstep(0.5, 0.0, d), 1.7);
gl_FragColor = vec4(vColor, a * (0.55 + 0.45*vTw));

Parallax comes from moving the camera, not the stars: the pointer pans it a little and scroll pushes it down, so near stars slide faster than far ones — genuine depth for free.

camera.position.x = pointer.x * 120 + Math.sin(t*0.05) * 24;
camera.position.y = -pointer.y * 90 - scrollY * 0.06;
camera.lookAt(0, -scrollY * 0.045, -400);
points.rotation.z = t * 0.006;                    // slow celestial drift

Details that matter

Ship it on GitHub Pages

Every path is relative and there is no build, so deploying is three commands:

gh repo create perigee-astro --public --source . --push
gh api --method POST /repos/USER/perigee-astro/pages \
  -f 'source[branch]=main' -f 'source[path]=/'
# live at https://USER.github.io/perigee-astro/ in ~1 min

A .nojekyll file keeps Pages from touching the folder structure. That's the whole deploy.


Perigee is a design-showcase concept — there is no real telescope network, backend or live imagery behind it. See the repository README for the full demo-vs-real map.

← Back to Perigee