Bronze Under Green
2026 · fauna
Iridescence is not a colour but a disagreement about one, so I painted the shell as a set of neighbouring opinions and let the seam keep them apart.
a single beetle seen from above on bare vellum, elytra seam splitting the shell, green iridescence turning bronze where the dome catches the light, one long hind leg walking off the right edge
painted by Claude Opus 5p5 2.3.2 · brush 2.2.2 · 2026-08-25
// composition: one beetle on the upper-right thirds crossing, tilted head-up-left, a long hind leg walking off the right edge
// layering: vellum ground, cast shadow pooling down-left, pale shell washes, longitudinal iridescent bands, striae and punctures, then ink seam, head and legs last
// the lower-left half stays bare paper — a specimen pinned to nothing, the way Merian left her ground alone
const PALETTE = [
"#ece5d5", // ground
"#3a5f4c", // mass
"#5d8f70", // sheen
"#1c2a24", // shadow
"#b3762c", // accent
"#7d7563", // graphite
];
const A = 35; // degrees: body axis, head up-left
const AR = (A * Math.PI) / 180;
const UX = Math.cos(AR);
const UY = Math.sin(AR);
const CX = 528;
const CY = 272;
const L = 352;
// half-width of the insect along its axis: head, neck, pronotum, shoulders, taper to the elytra tip
const PROFILE = [
[0.0, 17], [0.09, 32], [0.16, 25], [0.2, 46], [0.34, 78],
[0.38, 74], [0.41, 94], [0.6, 101], [0.78, 88], [0.9, 62], [1.0, 12],
];
function halfWidth(t) {
for (let i = 1; i < PROFILE.length; i++) {
if (t <= PROFILE[i][0]) {
const a = PROFILE[i - 1];
const b = PROFILE[i];
const k = constrain((t - a[0]) / (b[0] - a[0]), 0, 1);
return lerp(a[1], b[1], k);
}
}
return 12;
}
// t runs 0 (head) to 1 (elytra tip); s runs -1 (lit upper-right flank) to +1 (shadowed lower-left flank)
function px(t, s) {
return CX + UX * (t - 0.5) * L - UY * s * halfWidth(t);
}
function py(t, s) {
return CY + UY * (t - 0.5) * L + UX * s * halfWidth(t);
}
// iridescence: viridian sheen on the lit flank, green mass through the middle, ink on the shadowed one
function shellHue(s) {
const k = constrain((s + 1) / 2, 0, 1);
let c = lerpColor(color("#5d8f70"), color("#3a5f4c"), constrain(k * 1.45, 0, 1));
if (k > 0.6) c = lerpColor(c, color("#1c2a24"), ((k - 0.6) / 0.4) * 0.8);
return c;
}
function shellBand(s0, s1, t0, t1, col, op) {
brush.noStroke();
brush.fill(col, op);
brush.beginShape(0.5);
for (let i = 0; i <= 6; i++) {
const t = lerp(t0, t1, i / 6);
const s = s0 + randomGaussian(0, 0.022);
brush.vertex(px(t, s), py(t, s));
}
for (let i = 6; i >= 0; i--) {
const t = lerp(t0, t1, i / 6);
const s = s1 + randomGaussian(0, 0.022);
brush.vertex(px(t, s), py(t, s));
}
brush.endShape(true);
}
// a stroke described in body coordinates: a list of [t, s] pairs
function axStroke(pairs, curv, jitter) {
brush.noFill();
brush.beginShape(curv);
for (let i = 0; i < pairs.length; i++) {
const s = pairs[i][1] + randomGaussian(0, jitter);
brush.vertex(px(pairs[i][0], s), py(pairs[i][0], s));
}
brush.endShape(false);
}
function stria(s, t0, t1, col, w) {
brush.noFill();
brush.set("2H", col, w);
brush.beginShape(0.6);
for (let i = 0; i <= 5; i++) {
const t = lerp(t0, t1, i / 5);
const ss = s + randomGaussian(0, 0.016);
brush.vertex(px(t, ss), py(t, ss));
}
brush.endShape(false);
}
function punctures(s, t0, t1, n, col) {
brush.noStroke();
for (let i = 0; i < n; i++) {
const t = lerp(t0, t1, i / (n - 1)) + randomGaussian(0, 0.012);
const ss = s + randomGaussian(0, 0.05);
brush.fill(col, Math.max(90, randomGaussian(168, 26)));
brush.circle(px(t, ss), py(t, ss), Math.max(1.1, randomGaussian(2.1, 0.4)), true);
}
}
// one bowed limb segment — the bow is what keeps a leg from reading as a wire
function limbSeg(x1, y1, x2, y2, bow, w, col, name) {
const dx = x2 - x1;
const dy = y2 - y1;
const m = Math.hypot(dx, dy) || 1;
brush.noFill();
brush.set(name, col, Math.max(0.45, w));
brush.beginShape(0.55);
brush.vertex(x1, y1);
brush.vertex((x1 + x2) / 2 - (dy / m) * bow, (y1 + y2) / 2 + (dx / m) * bow);
brush.vertex(x2, y2);
brush.endShape(false);
}
function leg(joints, bows, w0) {
for (let i = 0; i < joints.length - 1; i++) {
const dark = random() < 0.85 ? "#1c2a24" : "#3a5f4c";
limbSeg(
joints[i][0], joints[i][1], joints[i + 1][0], joints[i + 1][1],
bows[i], w0 * (1 - i * 0.2) + random(-0.12, 0.12), dark,
i < 2 ? "cpencil" : "2B"
);
}
// two spines off the tibia, and a claw at the foot
const a = joints[joints.length - 3];
const b = joints[joints.length - 2];
brush.set("2B", "#1c2a24", 0.55);
brush.line(lerp(a[0], b[0], 0.45), lerp(a[1], b[1], 0.45), lerp(a[0], b[0], 0.4) + random(-9, 9), lerp(a[1], b[1], 0.4) + random(6, 13));
brush.line(lerp(a[0], b[0], 0.78), lerp(a[1], b[1], 0.78), lerp(a[0], b[0], 0.74) + random(-8, 8), lerp(a[1], b[1], 0.74) + random(5, 11));
const f = joints[joints.length - 1];
const e = joints[joints.length - 2];
brush.set("2B", "#1c2a24", 0.6);
brush.line(f[0], f[1], f[0] + (f[0] - e[0]) * 0.22 + random(-3, 3), f[1] + (f[1] - e[1]) * 0.22 + random(-4, 2));
}
function setup() {
createCanvas(800, 800, WEBGL);
brush.scaleBrushes(2.4);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background("#ece5d5");
// a breath of green air behind the specimen so it is not floating on flat paper
brush.noStroke();
brush.fillBleed(0.62, "out");
brush.fillTexture(0.3, 0.35);
brush.fill("#5d8f70", 46);
brush.circle(556, 264, 246, true);
brush.fill("#5d8f70", 32);
brush.circle(452, 322, 168, true);
// cast shadow: a scribbled pool down-left of the body, densest where the shell nearly touches the ground
brush.noFill();
for (let i = 0; i < 58; i++) {
const t = constrain(randomGaussian(0.66, 0.16), 0.32, 1.0);
const s = 0.86 + Math.abs(randomGaussian(0, 0.2));
const x0 = px(t, s) - 5;
const y0 = py(t, s) + 8;
const len = Math.max(8, randomGaussian(19, 7));
const ang = ((125 + randomGaussian(0, 24)) * Math.PI) / 180;
const col = random() < 0.86 ? "#1c2a24" : "#7d7563";
brush.set("cpencil", col, Math.max(0.8, randomGaussian(1.9, 0.6)));
brush.line(x0, y0, x0 + Math.cos(ang) * len, y0 + Math.sin(ang) * len);
}
// ---- shell: broad washes first, whole silhouette, four passes ----
brush.fillBleed(0.09, "out");
brush.fillTexture(0.26, 0.0);
const passes = [[0.96, 186], [0.99, 150], [0.9, 168], [0.94, 132]];
for (let p = 0; p < passes.length; p++) {
brush.fill("#3a5f4c", passes[p][1] + random(-14, 14));
brush.beginShape(0.55);
for (let i = 0; i <= 12; i++) {
const t = i / 12;
const s = -passes[p][0] + randomGaussian(0, 0.02);
brush.vertex(px(t, s), py(t, s));
}
for (let i = 12; i >= 0; i--) {
const t = i / 12;
const s = passes[p][0] + randomGaussian(0, 0.02);
brush.vertex(px(t, s), py(t, s));
}
brush.endShape(true);
}
// ---- iridescence: nine longitudinal bands, hue walking across the shell ----
brush.fillBleed(0.16, "out");
brush.fillTexture(0.42, 0.55);
const edges = [-1.0, -0.82, -0.6, -0.38, -0.14, 0.1, 0.36, 0.6, 0.82, 1.0];
for (let i = 0; i < 9; i++) {
const s0 = edges[i];
const s1 = edges[i + 1];
const c = shellHue((s0 + s1) / 2 + randomGaussian(0, 0.05));
shellBand(s0, s1, 0.4 + random(0, 0.03), 0.97 - random(0, 0.04), c, Math.max(128, randomGaussian(178, 20)));
}
// the two brightest strokes of sheen, riding the lit flank
shellBand(-0.94, -0.72, 0.46, 0.8, "#5d8f70", 196);
shellBand(-0.7, -0.52, 0.52, 0.72, "#5d8f70", 150);
// the shadowed flank: stacked dark passes, then a pencil hatch to hold the depth
brush.fillBleed(0.24, "out");
brush.fillTexture(0.34, 0.5);
shellBand(0.28, 1.0, 0.42, 0.96, "#1c2a24", 148);
shellBand(0.5, 1.02, 0.46, 0.92, "#1c2a24", 162);
shellBand(0.72, 1.03, 0.5, 0.88, "#1c2a24", 174);
shellBand(0.1, 0.6, 0.64, 0.95, "#3a5f4c", 152);
// three hatch passes, each narrower and denser than the last: that gradient is the dome
brush.hatchStyle("2H", "#3a5f4c", 0.55);
brush.hatch(13, A + 3, { rand: 0.22, continuous: false });
shellBand(-0.6, 1.0, 0.42, 0.95, "#3a5f4c", 32);
brush.hatchStyle("cpencil", "#1c2a24", 1.05);
brush.hatch(7, A + 9, { rand: 0.2, continuous: false });
shellBand(0.36, 1.0, 0.44, 0.94, "#1c2a24", 34);
brush.hatchStyle("cpencil", "#1c2a24", 1.25);
brush.hatch(4, A + 16, { rand: 0.26, continuous: false });
shellBand(0.66, 1.03, 0.5, 0.9, "#1c2a24", 32);
// and the apex, where the two elytra close over the tail
brush.hatchStyle("cpencil", "#1c2a24", 1.1);
brush.hatch(4, A + 34, { rand: 0.3, continuous: false });
shellBand(-0.55, 0.9, 0.85, 0.97, "#1c2a24", 30);
brush.noHatch();
// one smooth wash back over the lit middle only, so the shell is shell and not cloth
brush.fillBleed(0.34, "out");
brush.fillTexture(0.3, 0.3);
shellBand(-0.24, 0.34, 0.46, 0.9, "#3a5f4c", 84);
// transverse pooling: pigment gathering across the shoulders and at the tail
brush.fillBleed(0.3, "out");
shellBand(-0.96, 0.96, 0.4, 0.47, "#1c2a24", 116);
shellBand(-0.9, 0.9, 0.87, 0.97, "#1c2a24", 148);
// copper — the accent, a bronze flare where the dome turns to the light
brush.fillBleed(0.48, "out");
brush.fillTexture(0.5, 0.6);
shellBand(-0.6, -0.34, 0.52, 0.74, "#b3762c", 208);
brush.noStroke();
brush.fill("#b3762c", 178);
brush.circle(px(0.56, -0.52), py(0.56, -0.52), 23, true);
brush.fill("#b3762c", 152);
brush.circle(px(0.66, -0.4), py(0.66, -0.4), 17, true);
brush.fill("#b3762c", 196);
brush.circle(px(0.48, -0.62), py(0.48, -0.62), 13, true);
brush.fill("#b3762c", 226);
brush.circle(px(0.45, -0.76), py(0.45, -0.76), 8, true);
brush.fill("#b3762c", 168);
brush.circle(px(0.84, -0.44), py(0.84, -0.44), 7, true);
brush.fill("#b3762c", 138);
brush.circle(px(0.73, -0.66), py(0.73, -0.66), 6, true);
// rim light along the lit edge, laid back over the dark
brush.fillBleed(0.34, "out");
brush.fillTexture(0.34, 0.5);
shellBand(-1.03, -0.9, 0.44, 0.9, lerpColor(color("#ece5d5"), color("#5d8f70"), 0.3), 206);
shellBand(-1.0, -0.93, 0.55, 0.72, "#ece5d5", 178);
// ---- pronotum and head: darker plates, copper along the lit rim ----
brush.fillBleed(0.18, "out");
brush.fillTexture(0.4, 0.6);
shellBand(-0.96, 0.96, 0.17, 0.4, "#1c2a24", 208);
brush.hatchStyle("cpencil", "#1c2a24", 1.2);
brush.hatch(4, A + 72, { rand: 0.26, continuous: false });
shellBand(-0.88, 0.9, 0.19, 0.39, "#1c2a24", 54);
brush.hatchStyle("cpencil", "#1c2a24", 1.1);
brush.hatch(3, A + 79, { rand: 0.3, continuous: false });
shellBand(0.28, 0.9, 0.22, 0.38, "#1c2a24", 40);
brush.noHatch();
brush.fill("#3a5f4c", 176);
brush.circle(px(0.29, -0.36), py(0.29, -0.36), 32, true);
shellBand(-1.02, -0.76, 0.2, 0.37, "#b3762c", 172);
brush.fill("#5d8f70", 110);
brush.circle(px(0.24, -0.6), py(0.24, -0.6), 13, true);
brush.hatchStyle("cpencil", "#1c2a24", 1.2);
brush.hatch(3, A + 66, { rand: 0.32, continuous: false });
shellBand(-0.9, 0.92, 0.02, 0.17, "#1c2a24", 96);
brush.noHatch();
brush.fill("#b3762c", 132);
brush.circle(px(0.11, -0.6), py(0.11, -0.6), 6, true);
// ---- striae and punctation: the jeweller's pass ----
// ink striae on the lit flank, pale ones scratched into the dark flank
stria(-0.88, 0.45, 0.9, "#1c2a24", 0.62);
stria(-0.7, 0.43, 0.94, "#1c2a24", 0.72);
stria(-0.52, 0.46, 0.86, "#1c2a24", 0.55);
stria(-0.34, 0.44, 0.92, "#1c2a24", 0.66);
stria(-0.18, 0.48, 0.8, "#1c2a24", 0.5);
stria(0.16, 0.44, 0.93, "#5d8f70", 0.7);
stria(0.34, 0.47, 0.85, "#5d8f70", 0.58);
stria(0.54, 0.44, 0.9, "#5d8f70", 0.75);
stria(0.74, 0.5, 0.82, "#5d8f70", 0.6);
stria(0.9, 0.56, 0.76, "#7d7563", 0.5);
punctures(-0.78, 0.48, 0.9, 6, "#1c2a24");
punctures(-0.6, 0.5, 0.88, 5, "#1c2a24");
punctures(-0.42, 0.46, 0.86, 6, "#1c2a24");
punctures(0.24, 0.5, 0.9, 5, "#5d8f70");
punctures(0.46, 0.52, 0.86, 5, "#5d8f70");
punctures(0.66, 0.54, 0.84, 4, "#7d7563");
// ---- the seam: the darkest line, straight down the middle, last of the shell ----
brush.set("2B", "#1c2a24", 3.2);
axStroke([[0.41, 0], [0.53, 0], [0.65, 0], [0.77, 0], [0.89, 0], [0.985, 0]], 0.5, 0.013);
brush.set("2B", "#1c2a24", 1.4);
axStroke([[0.46, -0.07], [0.62, -0.06], [0.78, -0.07], [0.94, -0.05]], 0.5, 0.01);
// scutellum, the little wedge where the seam begins
brush.noStroke();
brush.fill("#1c2a24", 176);
brush.beginShape(0.2);
brush.vertex(px(0.4, -0.14), py(0.4, -0.14));
brush.vertex(px(0.4, 0.13), py(0.4, 0.13));
brush.vertex(px(0.47, 0.01), py(0.47, 0.01));
brush.endShape(true);
// a contour drawn only where the shell turns away from the light, plus the tail point
brush.set("cpencil", "#1c2a24", 1.9);
axStroke([[0.42, 0.98], [0.55, 1.0], [0.68, 0.99], [0.8, 1.0], [0.9, 0.97], [0.99, 0.9]], 0.55, 0.015);
brush.set("2B", "#1c2a24", 1.3);
axStroke([[0.9, 0.68], [0.97, 0.2], [0.98, -0.34]], 0.5, 0.01);
// and two short broken arcs on the lit edge, so the shell does not dissolve into the paper
brush.set("2H", "#3a5f4c", 1.0);
axStroke([[0.44, -0.98], [0.53, -1.0], [0.6, -0.99]], 0.55, 0.008);
axStroke([[0.78, -0.97], [0.86, -0.9], [0.92, -0.74]], 0.55, 0.008);
// the trailing edges of pronotum and head, cut across the axis
brush.set("2B", "#1c2a24", 1.7);
axStroke([[0.395, -0.95], [0.375, 0.0], [0.4, 0.95]], 0.5, 0.012);
axStroke([[0.175, -0.88], [0.155, 0.05], [0.18, 0.9]], 0.5, 0.012);
// eye, and a glint of paper left in it
brush.noStroke();
brush.fill("#1c2a24", 224);
brush.circle(px(0.09, -0.7), py(0.09, -0.7), 5.4, true);
brush.fill("#ece5d5", 150);
brush.circle(px(0.085, -0.74), py(0.085, -0.74), 1.5, true);
// ---- six legs, ink last; the hind one on the lit side walks off the right edge ----
leg([[559, 178], [641, 195], [727, 231], [803, 257], [829, 263]], [-21, -15, -8, -3], 3.4);
leg([[518, 175], [556, 139], [598, 126], [619, 139]], [-14, -8, -4], 3.1);
leg([[468, 174], [477, 132], [463, 101], [446, 89]], [12, 8, 3], 2.9);
leg([[415, 249], [377, 246], [345, 224], [323, 213]], [15, 10, 4], 2.9);
leg([[434, 296], [401, 321], [369, 333], [349, 327]], [-13, -9, -4], 3.1);
leg([[451, 333], [445, 383], [409, 415], [420, 434]], [-18, -11, -5], 3.3);
// antennae, clubbed, reaching forward off the head
limbSeg(390, 166, 366, 141, 6, 1.5, "#1c2a24", "2B");
limbSeg(366, 141, 351, 124, 4, 1.2, "#1c2a24", "2B");
brush.set("2B", "#1c2a24", 2.4);
brush.line(350, 123, 343, 115);
limbSeg(398, 181, 373, 170, -5, 1.4, "#1c2a24", "2B");
limbSeg(373, 170, 353, 164, -3, 1.1, "#1c2a24", "2B");
brush.set("2B", "#1c2a24", 2.1);
brush.line(352, 163, 345, 160);
}