Build notes · Study Nº 195
Making the pattern between the patterns
MOIRÉ is an op-art interference study: a fictional gallery sheet whose single job is to make one law legible — two identical gratings plus one invisible rotation equals one unmissable pattern. The audience is anyone who has cursed a scanned halftone or stood too close to a Bridget Riley.
Concept
The page stages three "stations" of the same law — line-on-line, ring-on-ring, and the
dot-lattice twist — cycling on an eight-second clock through one big framed field. One grating
of each pair breathes through a slow, eased rotation (or centre-offset, for the rings), and the
moiré fringes do all the visible moving. A violet dimension bracket rides the field, live-typing
the fringe period D = p / 2·sin(θ/2) as it stretches and collapses: the caption
line for the pattern between the patterns.
Palette
Op-art discipline argues near-monochrome: the fringes are luminance phenomena, and any colour laid under them would fight the interference. Gallery white and grating ink carry the whole field; one violet is reserved for measurement marks and small accents — the instrument's ink, never the artwork's; a cool grey handles captions.
Type
Display & body
Familjen Grotesk
A grotesk with just enough ink-trap character to feel printed rather than rendered — the voice of a 1960s exhibition catalogue set today. Weight 650, tight tracking for display; 400 for body.
Instrument
Space Mono — θ 2.41°
Everything measured speaks Space Mono: readouts, the giant formula, wall labels, the bracket's caption. The moiré is art on one axis and metrology on the other; the mono face is the metrology axis.
Techniques
Pure SVG geometry, no filters, no raster. Each master grating is drawn
once in <defs> and instanced twice with <use>: the line
comb is a single <path> of 266 vertical strokes (one DOM node, not 266),
the bullseye is 120 concentric circles, and the dot lattice is 130 lines wearing
stroke-dasharray: 0 13 with round caps — dots as pure stroke geometry, no
thirteen thousand circle nodes. Total stage weight stays under ~600 elements, so the browser
composites the rotation instead of re-rasterising a pattern tile every frame.
The signature element — fringes sweeping from an invisible cause — is one
transform: the second <use> sits in a group whose rotation breathes through
a shaped sine (sign(s)·|s|^0.75, periods 15–18 s) so it dwells at the extremes
like held breath. The fringe period is computed each frame from the true formula and drives
the violet bracket's height and label, so the annotation is measured, not decorative. Moving
the pointer across the stage adds a ±0.7° bias to the hidden rotation — the page quietly hands
you the grating. Everything pauses on document.hidden, deltas are clamped, and
prefers-reduced-motion renders each station settled mid-sweep at its most
photogenic angle, tabs still working, nothing blank.
Iteration log
Pass 1 · Craft
- Drove the full station cycle headless: asserted θ advancing every frame, the auto-cycle reaching station 2 by t≈9 s, and the bracket label re-typing — all live; found the ring station's readout row mutating text nodes through a redundant double-write and cleaned it to a single labelled path.
- Ring-station fringes read weak at the initial 8.5 px pitch/3.9 stroke — thinned the stroke to 3.6 for a truer 50% duty and the hyperbolas snapped into contrast.
- Formula block overflowed at 375 px (
white-space:nowrapat 6.4vw); rescaled the clamp and let the fraction carry the size instead of the whole line.
Pass 2 · Depth
- Added the pointer bias — a ±0.7° nudge eased at 4.5%/frame, so the fringes lean toward your hand without ever strobing.
- Second-read detail: when the breathing rotation falls under one degree, the caption swaps to "the turn is invisible at any zoom — only the bands remember it", then restores.
- Retimed the three breaths to co-prime periods (18/15/16 s) against the 8 s station clock, so no two visits show the same sequence of rest angles.
Pass 3 · Hardening
- 375 px: verified no horizontal overflow, wordmark and nav inside the viewport; stage
switches to a taller 0.92 aspect with
sliceso fringes stay monumental on a phone. - Reduced-motion: emulated headless — θ holds a fixed 2.4°, two frames hash identical, tabs still switch stations to settled states. CSS media query kills all transitions; the JS gate never starts the rAF loop.
- Chanel rule: cut a planned second violet annotation (a rotating θ protractor arc in the stage corner) — the bracket alone says it better; removed the readout's redundant write path found in Pass 1.