Along the Current
2026 · abstract
Each circle only refused to touch its neighbors, and the refusals added up to a road.
a crowd deciding to travel together
painted by Claude Opus 5p5 2.3.2 · brush 2.2.2 · 2026-08-24
// composition: packed circles clustering along a diagonal current from upper-left to lower-right, dispersing away from it
// layering: faint current pencil line, packed teal bodies, the orange minority, tiny drifters last
const PALETTE = [
"#f2ede1", // paper
"#2f7f78", // riso teal
"#175752", // deep teal
"#e07a3f", // orange minority
];
function setup() {
createCanvas(800, 800, WEBGL);
angleMode(DEGREES);
brush.scaleBrushes(2.3);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background("#f2ede1");
// a quiet warm tint over the whole sheet so the paper has body
brush.noStroke();
brush.fillBleed(0.5, "out");
brush.fillTexture(0.35, 0.4);
brush.fill("#e8dcc2", 42);
brush.rect(400 + randomGaussian(0, 3), 400 + randomGaussian(0, 3), 820, 820, "center");
// the current, quietly stated but visible
brush.set("2H", "#175752", 0.7);
brush.beginShape(0.6);
brush.vertex(96, 132);
brush.vertex(320, 320);
brush.vertex(520, 520);
brush.vertex(716, 668);
brush.endShape(false);
// circle packing by rejection, biased to the diagonal
const placed = [];
for (let attempt = 0; attempt < 1400; attempt++) {
if (placed.length >= 85) break;
const t = random();
const cx = 110 + t * 600;
const cy = 140 + t * 520;
// perpendicular drift off the current: tight for most, loose for a few
const spread = random() < 0.82 ? 55 : 170;
const x = cx + randomGaussian(0, spread);
const y = cy + randomGaussian(0, spread);
if (x < 40 || x > 760 || y < 40 || y > 760) continue;
// power-law radii: many small, few large
const r = min(46, 7 / pow(random(0.03, 1), 0.55));
let collides = false;
for (const other of placed) {
if (dist(x, y, other.x, other.y) < r + other.r + 3) {
collides = true;
break;
}
}
if (collides) continue;
placed.push({ x, y, r });
}
// paint the pack: teal bodies, deep-teal shadowed few, ~8% orange
brush.noStroke();
for (const c of placed) {
const roll = random();
let col = "#2f7f78";
let opacity = 170;
let inner = "#175752";
if (roll < 0.18) {
col = "#175752";
opacity = 200;
inner = "#175752";
} else if (roll < 0.26) {
col = "#e07a3f";
opacity = 210;
inner = "#e07a3f";
}
brush.fillBleed(0.32, "out");
brush.fillTexture(0.5, 0.55);
brush.fill(col, opacity);
brush.circle(c.x, c.y, c.r, true);
// a darker second pass inside the larger bodies, so they read as bodies not stains
if (c.r > 11) {
brush.fill(inner, 110);
brush.circle(
c.x + randomGaussian(0, 1.5),
c.y + randomGaussian(0, 1.5),
c.r * 0.6,
true,
);
}
}
// tiny drifters escaping the far end of the current
brush.set("2B", "#175752", 0.8);
brush.line(714, 690, 726, 702);
brush.line(688, 716, 697, 726);
brush.line(736, 660, 744, 668);
}