Design & build guide
How Trefoil was made
The concept: a fictional single-room gallery that treats torus knots as studio ceramics. The audience is anyone who has ever picked at a shoelace; the page's single job is to make one mathematical fact feel physical — a knot's numbers cannot be undone. One vessel on one pedestal, re-thrown live through the T(p,q) family while its museum placard keeps the ledger: crossings, genus, catalogue mark.
Palette
Cold, precise, and argued from the kiln: charcoal for the darkened gallery, oxblood for the classic copper-red reduction glaze, celadon — the other great reduction glaze — demoted to lines and marks only, and a warm plaster-white for everything you actually read.
The room with the lights cut. Every surface starts here.
Lines, rules, eyebrows, the pedestal's trim ring. Never body copy.
The glaze itself — sang-de-boeuf, fired in reduction. Material only; too dark for type on charcoal.
Primary reading ink, ~13:1 on charcoal. A dimmed sibling #A8A49D (≈6.6:1) carries secondary copy.
Type
Marcellus for display — a single-weight face drawn from Roman inscriptional capitals, which is exactly what museum wall text wants to be. It carries the wordmark, the vessel names, and the headings. IBM Plex Mono for everything ledger-like: placard specs, catalogue marks in Alexander–Briggs notation (3₁, 5₁, 7₁, 8₁₉), body copy set narrow and quiet. The bravery moment is the placard itself: a Marcellus artefact name over a mono spec line, exactly as a real gallery sets them.
Techniques
Vanilla three.js (0.160, module CDN). No bloom, no haze, no post-processing — the concept demanded cold studio light, so the whole render budget went into the material and the lamps: MeshPhysicalMaterial with full clearcoat over rough oxblood (wet glaze over fired clay), a PMREM-filtered procedural RoomEnvironment for the ceramic's soft window reflections, and a three-point rig — warm spot key with 2048px soft shadows, cool directional fill, celadon rim from behind.
The signature element — the (p,q) morph: every knot is built by one tube builder with identical topology: 560 rings × 26 vertices, centreline (2+cos qu)·(cos pu, ·, sin pu) with sin qu for height, framed by parallel transport rather than Frenet frames (Frenet normals flip at inflections and would shear the tube mid-morph). The frame mismatch left over when a closed loop's transported normal returns home is measured and distributed as a counter-twist along the full length, so the seam ring is watertight for any (p,q) and indices never change. Morphing does not lerp vertices — blending two circular cross-sections point-by-point degenerates the ring into a flat blade mid-morph. Instead the engine eases the ring skeleton: centreline, transported frame and tube radius are interpolated with smootherstep over 2.6 s, re-orthonormalised, and every cross-section is rebuilt as a true circle each frame, so the clay stays a round tube for the whole re-throw while the placard cross-fades to the next entry.
Everything else is discipline from the collection's quality floor: DPR capped at 2, rAF paused on document.hidden with a clamped delta, resize re-layout with a view offset that keeps the vessel clear of the placard, prefers-reduced-motion honoured in CSS and JS (a settled frame: the trefoil mid-rotation under full light), and a WebGL context-loss fallback that swaps in an SVG trefoil with the collection intact in text.
Iteration log
-
Pass 1Craft
Read the rendered page at 1440 and 375, not just the code. IBM Plex Mono has no "№" glyph — the placard's numero sign fell back to a wrong-looking substitute, so every N-mark became a typed
N°. The fixed header collided with the family cards once the page scrolled; it is now scoped to the hero, where a gallery's signage belongs. Removedaria-livefrom the placard — a self-rotating exhibit announcing itself every eight seconds is screen-reader noise; the verdict line keeps its live region because the visitor asks for it. Hid the hero support note under 640 px width and 680 px height where it collided with the placard, and bumped footer type from 10.9 to 11.5 px. -
Pass 2Depth
Driving the morph and sampling actual canvas pixels caught the wave's classic silent failure: the glaze rendered pure grey-black — every sampled pixel was R≈G≈B. Root cause: the hand-built index buffer wound its triangles opposite to the outward normals, so the camera was seeing the tube's inner wall shaded with flipped normals; diffuse light never registered. Reversing the winding brought the ceramic to life. A numeric seam probe then showed the parallel-transport closure twist was distributed with the wrong sign — doubling the holonomy instead of cancelling it, a 0.30-unit vertex shear at T(2,5)'s seam — flipped to cancel, residual now one step (~0.005 rad, a fiftieth of the tube radius). One pinch survived all of that: mid-morph the tube still looked cut in two places, because lerping vertices between two knots collapses each circular ring into a degenerate ellipse — a flat, black-shaded blade. The fix was to interpolate the ring skeleton (centreline + frame + radius) and rebuild true circles every frame; the clay now stays round for the whole re-throw. Re-tuned the glaze from black through salmon to true oxblood (base #7A2E2E, key dimmed 1050→900 cd, cool fill 0.85→0.68). Also fixed the card-linger flag the morph-end handler was erasing, and added the conservator's tug plus the tricolour dots that only 3₁ earns.
-
Pass 3Hardening
Full headless suite, 33/33: zero console errors on both pages, one h1 each, no horizontal overflow at 375 with nav, wordmark and placard fully inside the viewport. The seam was re-proved two ways — numerically (closure frame error ≈ 0.005 rad on all four knots, sub-pixel) and visually, a sixteen-frame sweep of every knot at four turntable angles plus mid-morph pixel reads, all clean. The signature is asserted live: each
goTomorphs to its vessel, the placard flips to the right catalogue mark, sampled vertex positions move, the card syncs, and the wheel then advances on its own. Two screenshots 1.3 s apart differ while the turntable runs; under reduced motion they are identical, the loop never starts, and a same-task render-then-read probe confirms the settled frame is a lit trefoil, not a blank. Chanel rule: cut the animated scroll-hint planned for the hero's right edge — the vertical turntable caption does that job quieter.