Wax and Bodies
2026 · fauna
The comb is the argument for order and the bees are the counter-argument, and both are made of the same wax.
bees crowding the upper rim of an open comb, cells capped and open, the top left left as bare paper
painted by Claude Opus 5p5 2.3.2 · brush 2.2.2 · 2026-08-25
// composition: the comb enters low-left and fills the lower-right two thirds on a diagonal; the bees crowd its upper rim, thickest at the right third; the top left stays bare paper
// layering: broad wax washes and a shadow under the rim first, then cell rims, capped lids and dark open mouths, then the bees, whose thoraxes and legs are the last and blackest marks
// the paper above the rim is left alone apart from two bees still in the air
const PALETTE = [
"#efe6d3", // ground — warm paper
"#c9a45c", // mass — wax
"#8a6730", // shadow — cell rims and mouths
"#332a1f", // ink — bee bodies
"#c2632c", // accent — wet honey, rationed
];
const GROUND = PALETTE[0];
const WAX = PALETTE[1];
const SHADE = PALETTE[2];
const INK = PALETTE[3];
const HONEY = PALETTE[4];
// the ragged upper rim of the comb, falling left to right, two octaves of wobble
function combEdge(x) {
return 548 - 0.528 * x + (noise(x * 0.0038) - 0.5) * 130 + (noise(x * 0.013 + 40) - 0.5) * 44;
}
function hexPoints(cx, cy, r, rot) {
const pts = [];
for (let i = 0; i < 6; i++) {
const a = rot + (i * Math.PI) / 3 + random(-0.1, 0.1);
const rr = r * (1 + random(-0.13, 0.13));
pts.push([cx + Math.cos(a) * rr, cy + Math.sin(a) * rr]);
}
return pts;
}
// six rims, one side at a time; cells near the rim lose more walls, as if half built
function cellRims(pts, w, drop) {
brush.noFill();
for (let i = 0; i < 6; i++) {
if (random() < drop) continue;
const a = pts[i];
const b = pts[(i + 1) % 6];
brush.set(random() < 0.36 ? "2B" : "cpencil", random() < 0.36 ? INK : SHADE, w * (1 + random(-0.32, 0.42)));
brush.line(a[0], a[1], b[0], b[1]);
}
}
// a capped cell: a pale lid of wax sitting proud of its rim
function cappedCell(cx, cy, r) {
brush.noStroke();
brush.fillBleed(0.24, "out");
brush.fillTexture(0.55, 0.45);
brush.fill(WAX, 168 + random(-26, 50));
brush.circle(cx + random(-3, 3), cy + random(-3, 3), r * (0.76 + random(-0.07, 0.1)), true);
if (random() < 0.45) {
brush.fill(SHADE, 84 + random(-18, 34));
brush.circle(cx + r * 0.22 + random(-2, 2), cy + r * 0.24 + random(-2, 2), r * 0.34, true);
}
}
// an open cell: the mouth is a hole, and the holes are what make it read as comb
function openCell(cx, cy, r, deep) {
brush.noStroke();
brush.fillBleed(0.14, "out");
brush.fillTexture(0.4, 0.8);
brush.fill(deep ? INK : SHADE, deep ? 132 + random(-32, 74) : 138 + random(-40, 54));
brush.circle(cx + random(-5, 5), cy + random(-5, 5), r * (0.58 + random(-0.11, 0.16)), true);
// strokes inside the mouth carry the real dark, raked at the cell's own angle
if (!deep && random() < 0.45) return;
brush.noFill();
const ang = random(-1.1, 1.1);
const ca = Math.cos(ang);
const sa = Math.sin(ang);
const n = deep ? 3 : 2;
for (let i = 0; i < n; i++) {
const t = -0.34 + i * (0.68 / (n - 0.4));
const hw = r * 0.5 * Math.cos(t * 1.9) * random(0.72, 1);
const ox = cx - sa * t * r * 0.62;
const oy = cy + ca * t * r * 0.62;
brush.set("charcoal", deep ? INK : SHADE, (1.5 + random(-0.4, 0.7)) * (deep ? 1 : 0.66));
brush.line(ox - ca * hw, oy - sa * hw, ox + ca * hw * random(0.7, 1), oy + sa * hw);
}
}
function honeyCell(cx, cy, r) {
brush.noStroke();
brush.fillBleed(0.34, "out");
brush.fillTexture(0.4, 0.55);
brush.fill(HONEY, 176 + random(-24, 44));
brush.circle(cx + random(-2, 2), cy + random(-2, 2), r * (0.64 + random(-0.06, 0.1)), true);
}
// one bee: pale wing, amber abdomen, banded; thorax and head built from heavy strokes so they stay black
function bee(x, y, ang, len, wing) {
const c = Math.cos(ang);
const s = Math.sin(ang);
const nx = -s;
const ny = c;
const w = len * 0.4;
// abdomen — amber, tapering to the sting
brush.noStroke();
brush.fillBleed(0.14, "out");
brush.fillTexture(0.42, 0.55);
brush.fill(random() < 0.24 ? HONEY : WAX, 226 + random(-22, 28));
brush.beginShape(0.7);
brush.vertex(x - c * len * 0.02 + nx * w * 0.54, y - s * len * 0.02 + ny * w * 0.54);
brush.vertex(x - c * len * 0.5 + nx * w * 0.5, y - s * len * 0.5 + ny * w * 0.5);
brush.vertex(x - c * len * 0.98 + nx * w * 0.07, y - s * len * 0.98 + ny * w * 0.07);
brush.vertex(x - c * len * 0.48 - nx * w * 0.52, y - s * len * 0.48 - ny * w * 0.52);
brush.vertex(x - c * len * 0.03 - nx * w * 0.54, y - s * len * 0.03 - ny * w * 0.54);
brush.endShape(true);
// three or four bands — thin enough that the amber stays between them
brush.noFill();
const bands = random() < 0.6 ? 4 : 3;
for (let i = 0; i < bands; i++) {
const t = 0.22 + i * (0.19 + random(-0.03, 0.03));
const wf = w * (0.56 - 0.36 * t);
brush.set("2B", INK, 1.5 + random(-0.3, 0.5));
brush.line(
x - c * len * t + nx * wf,
y - s * len * t + ny * wf,
x - c * len * t - nx * wf,
y - s * len * t - ny * wf,
);
}
// the tip, darkest on the abdomen
brush.set("2B", INK, 1.9);
brush.line(x - c * len * 0.9 + nx * w * 0.1, y - s * len * 0.9 + ny * w * 0.1, x - c * len * 0.99, y - s * len * 0.99);
// thorax — a dense dark blob with two strokes laid over it
brush.noStroke();
brush.fillBleed(0.06, "out");
brush.fillTexture(0.2, 0.9);
brush.fill(INK, 238);
brush.circle(x + c * len * 0.19, y + s * len * 0.19, w * (0.6 + random(-0.05, 0.08)), true);
brush.noFill();
brush.set("charcoal", INK, 2.3 + random(-0.3, 0.5));
brush.line(x + c * len * 0.08 + nx * w * 0.34, y + s * len * 0.08 + ny * w * 0.34, x + c * len * 0.3 - nx * w * 0.3, y + s * len * 0.3 - ny * w * 0.3);
brush.line(x + c * len * 0.08 - nx * w * 0.32, y + s * len * 0.08 - ny * w * 0.32, x + c * len * 0.3 + nx * w * 0.28, y + s * len * 0.3 + ny * w * 0.28);
// head
brush.noStroke();
brush.fill(INK, 232);
brush.circle(x + c * len * 0.47, y + s * len * 0.47, w * (0.3 + random(-0.03, 0.05)), true);
if (wing) {
// one wing laid back over the abdomen: a pale film with a drawn leading edge
brush.noStroke();
brush.fillBleed(0.4, "out");
brush.fillTexture(0.24, 0.35);
brush.fill(GROUND, 104 + random(-14, 20));
brush.beginShape(0.8);
brush.vertex(x + c * len * 0.16 + nx * w * 0.26, y + s * len * 0.16 + ny * w * 0.26);
brush.vertex(x - c * len * 0.34 + nx * w * 1.34, y - s * len * 0.34 + ny * w * 1.34);
brush.vertex(x - c * len * 0.74 + nx * w * 1.16, y - s * len * 0.74 + ny * w * 1.16);
brush.vertex(x - c * len * 0.9 + nx * w * 0.5, y - s * len * 0.9 + ny * w * 0.5);
brush.endShape(true);
brush.noFill();
brush.set("2H", SHADE, 0.7);
brush.beginShape(0.7);
brush.vertex(x + c * len * 0.16 + nx * w * 0.26, y + s * len * 0.16 + ny * w * 0.26);
brush.vertex(x - c * len * 0.34 + nx * w * 1.3, y - s * len * 0.34 + ny * w * 1.3);
brush.vertex(x - c * len * 0.72 + nx * w * 1.12, y - s * len * 0.72 + ny * w * 1.12);
brush.endShape(false);
}
// legs, short and crooked, and two fine antennae — the last marks
for (let i = 0; i < 3; i++) {
const t = 0.02 + i * 0.12;
const sp = random() < 0.5 ? 1 : -1;
const ox = x + c * len * t;
const oy = y + s * len * t;
const lr = w * (0.6 + random(-0.12, 0.22));
const spread = 0.7 + i * 0.5 + random(-0.2, 0.2);
const kx = nx * Math.cos(spread) + c * Math.sin(spread) * 0.8;
const ky = ny * Math.cos(spread) + s * Math.sin(spread) * 0.8;
brush.set("2B", INK, 1.3 + random(-0.25, 0.45));
brush.line(ox, oy, ox + kx * lr * sp, oy + ky * lr * sp);
brush.line(ox - nx * sp * 0.6, oy - ny * sp * 0.6, ox - kx * lr * 0.7 * sp, oy - ky * lr * 0.7 * sp);
}
brush.set("2H", INK, 1);
brush.line(x + c * len * 0.54, y + s * len * 0.54, x + c * len * 0.82 + nx * w * 0.34, y + s * len * 0.82 + ny * w * 0.34);
brush.line(x + c * len * 0.54, y + s * len * 0.54, x + c * len * 0.8 - nx * w * 0.38, y + s * len * 0.8 - ny * w * 0.38);
}
function setup() {
createCanvas(800, 800, WEBGL);
brush.scaleBrushes(2.4);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background(GROUND);
// 1 — the wax mass: broad washes sweeping the lower right, the largest gestures first
brush.noStroke();
brush.fillBleed(0.66, "out");
brush.fillTexture(0.36, 0.36);
brush.fill(WAX, 132);
brush.circle(600, 556, 252, true);
brush.fill(WAX, 118);
brush.circle(352, 668, 214, true);
brush.fill(WAX, 106);
brush.circle(730, 330, 186, true);
brush.fill(WAX, 94);
brush.circle(470, 434, 158, true);
brush.fill(WAX, 80);
brush.circle(716, 302, 120, true);
brush.fill(SHADE, 74);
brush.circle(684, 716, 196, true);
brush.fill(SHADE, 58);
brush.circle(214, 764, 132, true);
brush.fill(SHADE, 50);
brush.circle(800, 470, 150, true);
// 2 — a warm shadow lying just under the rim, so the crowd has something to sit on
brush.fillBleed(0.52, "out");
brush.fill(SHADE, 104);
brush.circle(566, 372, 128, true);
brush.fill(SHADE, 88);
brush.circle(672, 296, 106, true);
brush.fill(SHADE, 66);
brush.circle(378, 470, 96, true);
brush.fill(SHADE, 58);
brush.circle(778, 218, 84, true);
// 3 — the cells: a hex packing thinned by noise, cut off above by the rim
const R = 40;
const DX = R * 1.5;
const DY = R * 1.7320508;
const cells = [];
for (let col = 0; col < 16; col++) {
for (let row = 0; row < 14; row++) {
const cx = 12 + col * DX + randomGaussian(0, 3.6);
const cy = 30 + row * DY + (col % 2 ? DY * 0.5 : 0) + randomGaussian(0, 3.6);
const above = cy - combEdge(cx);
if (above < randomGaussian(24, 18)) continue;
const d = dist(cx, cy, 620, 512) / 600;
let dens = 1.16 - constrain(d, 0, 1) * 0.56;
dens *= 0.56 + noise(cx * 0.0032 + 11, cy * 0.0032 + 7) * 1.0;
if (random() > dens) continue;
cells.push({
x: cx,
y: cy,
r: R * (0.84 + random(-0.09, 0.18)),
rot: random(-0.17, 0.17),
s: noise(cx * 0.0058, cy * 0.0058),
drop: constrain(0.44 - above * 0.0026, 0.09, 0.44),
});
}
}
// rims firm up near the crowd and fade off toward the edges
for (const c of cells) {
const near = constrain(1.35 - dist(c.x, c.y, 596, 400) / 420, 0.5, 1.35);
cellRims(hexPoints(c.x, c.y, c.r, c.rot), 1.7 * near, c.drop);
}
// 4 — states: a dark brood patch under the crowd, capped lids where the noise runs high
let nCap = 0;
let nOpen = 0;
let nHoney = 0;
for (const c of cells) {
// the brood patch: an oval smear of open cells, its border torn by noise
const bx = (c.x - 588) / 168;
const by = (c.y - 392) / 104;
const brood = bx * bx + by * by < 0.62 + (noise(c.x * 0.012, c.y * 0.012) - 0.4) * 0.9;
// a weaker second patch low right, so the eye does not slide off the bottom
const lx = (c.x - 712) / 128;
const ly = (c.y - 660) / 96;
const later = lx * lx + ly * ly < 0.56 + (noise(c.x * 0.014 + 5, c.y * 0.014) - 0.45) * 0.8;
if (brood && nOpen < 36) {
openCell(c.x, c.y, c.r, random() < 0.78);
nOpen++;
} else if (later && nOpen < 36) {
openCell(c.x, c.y, c.r, random() < 0.68);
nOpen++;
} else if (c.s > 0.53 && nCap < 22) {
cappedCell(c.x, c.y, c.r);
nCap++;
} else if (c.s < 0.44 && nOpen < 36) {
openCell(c.x, c.y, c.r, c.y < 520 && random() < 0.45);
nOpen++;
} else if (nHoney < 5 && random() < 0.14) {
honeyCell(c.x, c.y, c.r);
nHoney++;
}
}
// grain in the wax, dragged across the deep corner
brush.noFill();
brush.set("spray", SHADE, 3.4);
brush.line(452, 796, 800, 508);
brush.line(556, 800, 806, 606);
brush.set("spray", SHADE, 2.6);
brush.line(660, 800, 802, 690);
brush.line(268, 748, 470, 636);
// 5 — the crowd: clumps of bees on the rim, thickest at the right third, thinning to the left
bee(300, combEdge(300) + 30, 0.62, 52, false);
bee(336, combEdge(336) + 62, 1.18, 50, false);
bee(434, combEdge(434) + 14, -0.32, 60, false);
bee(456, combEdge(456) + 46, 1.36, 54, true);
bee(478, combEdge(478) + 20, 0.24, 58, false);
bee(524, combEdge(524) + 8, -0.58, 62, false);
bee(546, combEdge(546) + 38, 0.86, 56, false);
bee(566, combEdge(566) + 14, 0.14, 64, true);
bee(588, combEdge(588) + 52, 1.44, 52, false);
bee(606, combEdge(606) + 22, -0.44, 60, false);
bee(628, combEdge(628) + 46, 0.5, 56, false);
bee(646, combEdge(646) + 16, -0.86, 58, true);
bee(700, combEdge(700) + 10, 0.7, 58, false);
bee(722, combEdge(722) + 42, -0.12, 54, false);
bee(792, combEdge(792) + 20, 0.96, 52, false);
// three crawling deeper into the comb, away from the rim
bee(654, 486, 1.62, 54, false);
bee(398, 588, 0.38, 50, true);
bee(748, 668, -1.18, 52, false);
// 6 — two still in the air, the only marks in the empty quarter
bee(250, 184, -0.88, 38, true);
bee(332, 100, 0.44, 28, true);
// 7 — the last darks: three mouths pushed deeper where the bodies press together
brush.noStroke();
brush.fillBleed(0.16, "out");
brush.fillTexture(0.5, 0.76);
brush.fill(INK, 168);
brush.circle(540, combEdge(540) + 54, 16, true);
brush.fill(INK, 148);
brush.circle(654, combEdge(654) + 40, 14, true);
brush.fill(INK, 130);
brush.circle(404, combEdge(404) + 62, 13, true);
brush.noFill();
brush.set("charcoal", INK, 2.4);
brush.line(486, combEdge(486) + 26, 524, combEdge(524) + 22);
brush.line(598, combEdge(598) + 20, 632, combEdge(632) + 18);
brush.line(686, combEdge(686) + 24, 718, combEdge(718) + 20);
brush.line(342, combEdge(342) + 34, 372, combEdge(372) + 30);
brush.line(756, combEdge(756) + 30, 788, combEdge(788) + 26);
}