The build · a portfolio note
How the sand
was drawn
Duneline is a fully procedural site. No stock photography, no image files, no libraries. Every dune, grass blade, and aerial plan is math drawn to a canvas. Here is the brief it answered and how it was built.
01 — The brief
A landscape studio, told through pattern at scale
Duneline is the trades-and-studio vertical in a five-site portfolio batch: a landscape and garden design practice that works with native grasses and sand-adapted planting. The brief asked for the differentiator thesis in one page, that is, generative art placed in service of a completely ordinary, sellable client. Nothing on the page is decoration for its own sake; the pattern is the product.
Register chambers: pattern at scale and wabi-sabi. Field family: desert sand, warming to dusk pink as you read down. One loud colour allowed in the room, and only one: brush red.
sand · field
dusk pink · field at rest
shadow brown · ink
brush red · the stroke
sky grey-blue · paths
Type is Instrument Serif for display and Instrument Sans for everything that has to be read, including the label layer set in letterspaced caps. Two faces, no more.
02 — The signature technique
Domain-warped noise, drawn as contour lines
The dune field behind the whole page is a fixed canvas. A value-noise function is folded back through itself twice, a technique called domain warping, so the field stops looking like flat static and starts looking like wind-shaped sand. That scalar field is then traced into fine contour lines with a hand-written marching-squares pass, one open path per iso-level. A slow wind term drifts the warp so the contours comb over time; scrolling turns the season, warming the fill and the lines from dawn sand to dusk pink.
// value noise, folded through itself twice = wind-shaped sand
function field(x, y, wind) {
const qx = fbm(x, y), qy = fbm(x + 5.2, y + 1.3);
const rx = fbm(x + 3.4*qx + wind, y + 3.4*qy);
const ry = fbm(x + 3.4*qx + 1.7, y + 3.4*qy + 9.2);
return fbm(x + 3.6*rx, y + 3.6*ry); // the surface the contours trace
}
// marching squares: one iso-contour, drawn as an open path
let idx = 0;
if (tl > level) idx |= 8; if (tr > level) idx |= 4;
if (br > level) idx |= 2; if (bl > level) idx |= 1;
switch (idx) {
case 1: case 14: seg = [L(), B()]; break; // corner cut, SW
case 3: case 12: seg = [L(), R()]; break; // horizontal band
// ...16 cases, two saddles drawn as double segments
}
Everything else is drawn the same honest way: the seed-packet swatch on each project card is a deterministic run of combed SVG blades; the aerial plate is concentric water rings, radiating paths, and hundreds of planting dots placed by the same noise. The footer splash injects a travelling ripple straight into the contour field so the sand briefly reads as water, the sea-and-desert kinship as one gesture.