---
title: "Balance · generative art"
description: "Five colours, each given the room it deserves. Generative art plate #41 by Theodore Vorillas, drawn live in your browser from its code."
url: "https://vorillaz.com/lab/balance"
---

![](https://vorillaz.com/_astro/balance.D8v96orY_ZSOyh0.webp)

fig. #41 · rendered live in your browser from the code below

# #41 balance

Five colours, each given the room it deserves.

- date

  2026.10.13

- tags

  generative-art · javascript

A sibling of [#37 Stokes](https://vorillaz.com/lab/stokes), held in balance. Five bands of the palette, light at both ends, saturated next in, the darkest at the middle. Each band gets the room its colour deserves: a heavy colour, dark or saturated, gets less of the plate and a light one more, so every band carries about the same weight. Neighbours fade into each other through OKLab, where a fade keeps its lightness even and never turns to mud.

Nothing else about the fades is fixed. The boundaries roll and lean on a warp of two scales. Along each one the fade runs from a knife’s edge to a long haze, and in places it is sprayed rather than blended, each pixel tipping over at its own threshold. Drips run both ways, as far up as down, and gather in some stretches and thin out in others. The colours are mottled, as pigment never lies quite flat. Play it and all of it drifts while the bands breathe, one giving what another takes, and the balance holds.

## The weights

```javascript title="balance.js"
// darkness and chroma both make a colour heavy; a heavy colour gets less room
const weight = ([L, a, b]) => 0.25 + (1 - L) * 0.9 + hypot(a, b) * 2.2;
const share = colours.map((c) => weight(c) ** -0.6); // softened, so no band swallows the plate

// breathing: each share swells and shrinks out of phase, and they always come back to one
const breath = share.map((s, i) => s * (1 + 0.22 * sin(t + i * TAU / 5)));
```

## The fades

```javascript title="balance.js"
// slow fields on a coarse grid: a warp that rolls the boundaries, a mottle, and where to spray
const rolled = y + warp(x, y, t);                       // broad swells and smaller billows
const soft = feather * (0.12 + 1.7 * noise(x * 3.5, k, t) ** 2); // a knife's edge to a long haze

let colour = oklab(colours[0]);
for (const [k, edge] of edges.entries()) {
  let q = smoothstep(edge + drip[k][x] - soft, edge + drip[k][x] + soft, rolled);
  // sprayed: each pixel tips over at its own threshold, where the spray field is up
  q = mix(q, q > threshold[pixel] ? 1 : 0, spray(x, y, t));
  colour = mix(colour, oklab(colours[k + 1]), q);
}
pixel = srgb(add(colour, mottle(x, y, t)));
```

## References

- [A perceptual color space for image processing](https://bottosson.github.io/posts/oklab/), Björn Ottosson, 2020
