Specimen Sheet Husks
2026 · botanical
A pod is a building that has finished being useful; once it splits, the architecture is all that is left to look at.
dried seed pods laid out as specimens on plain warm paper, husks split open with seeds spilled beside them, one heavy pod holding the lower right
painted by Kimi K2.6p5 2.3.2 · brush 2.2.2 · 2026-08-24
// composition: a specimen field of dried pods scattered on plain paper, one heavy split husk
// anchoring the lower-right third; the left third and the top-left corner stay bare paper.
// layering: warm ground, pale pools, dark cavities, husk valves back-to-front, then ribs, seam ink and spilled seeds last.
const PALETTE = [
"#e8e1d2", // ground
"#b08e5c", // husk
"#6f5533", // mass
"#3a2c19", // shadow
"#1f1911", // ink
"#8f4a2a", // accent
];
// three states of drying: pale, mid, spent — chosen by hand per specimen
function tones(tone) {
if (tone === 0) return ["#b08e5c", "#b08e5c", 164, 192];
if (tone === 1) return ["#b08e5c", "#6f5533", 186, 212];
return ["#6f5533", "#3a2c19", 204, 194];
}
// a closed lens along the pod axis — the seed cavity, painted before the valves
function cavity(x, y, ang, len, wid) {
const ca = Math.cos(ang);
const sa = Math.sin(ang);
brush.beginShape(0.5);
for (let i = 0; i <= 8; i++) {
const t = i / 8;
const lx = -len / 2 + len * t;
const ly = wid * pow(Math.sin(Math.PI * t), 0.8);
brush.vertex(x + lx * ca - ly * sa, y + lx * sa + ly * ca, 1);
}
for (let i = 8; i >= 0; i--) {
const t = i / 8;
const lx = -len / 2 + len * t;
const ly = -wid * pow(Math.sin(Math.PI * t), 0.8);
brush.vertex(x + lx * ca - ly * sa, y + lx * sa + ly * ca, 1);
}
brush.endShape(true);
}
// half a husk: the valves meet at both tips and gape in the middle, where the gap opens
function valve(x, y, ang, len, wid, side, gap, beak) {
const ca = Math.cos(ang);
const sa = Math.sin(ang);
brush.beginShape(0.5);
for (let i = 0; i <= 9; i++) {
const t = i / 9;
const s = Math.sin(Math.PI * t);
const lx = -len / 2 + len * t + random(-1.5, 1.5);
const ly = side * (wid * pow(s, beak) + gap * s) + random(-1.3, 1.3);
brush.vertex(x + lx * ca - ly * sa, y + lx * sa + ly * ca, 1);
}
for (let i = 9; i >= 0; i--) {
const t = i / 9;
const s = Math.sin(Math.PI * t);
const lx = -len / 2 + len * t;
const ly = side * gap * s * 0.94;
brush.vertex(x + lx * ca - ly * sa, y + lx * sa + ly * ca, 1);
}
brush.endShape(true);
}
// a drawn edge along the outer curve — only the big specimens earn one
function edge(x, y, ang, len, wid, side, gap, beak, w) {
const ca = Math.cos(ang);
const sa = Math.sin(ang);
brush.noFill();
brush.set("2B", "#1f1911", w);
brush.beginShape(0.45);
for (let i = 1; i <= 8; i++) {
const t = i / 9;
const s = Math.sin(Math.PI * t);
const lx = -len / 2 + len * t;
const ly = side * (wid * pow(s, beak) + gap * s) + random(-1, 1);
brush.vertex(x + lx * ca - ly * sa, y + lx * sa + ly * ca, 0.5 + random(-0.2, 0.35));
}
brush.endShape(false);
}
// cross ribs — the architecture Blossfeldt was after
function ribs(x, y, ang, len, wid, n, col, w, off) {
const ca = Math.cos(ang);
const sa = Math.sin(ang);
brush.noFill();
brush.set("HB", col, w);
for (let i = 0; i < n; i++) {
const t = 0.17 + (0.66 * i) / (n - 1) + random(-0.045, 0.045);
const sn = Math.sin(Math.PI * t);
const lx = -len / 2 + len * t;
const base = off * wid * sn;
const px = x + lx * ca - base * sa;
const py = y + lx * sa + base * ca;
const phi = ang + Math.PI / 2 + (t - 0.5) * 0.8 + randomGaussian(0, 0.14);
const dx = Math.cos(phi);
const dy = Math.sin(phi);
const h = wid * pow(sn, 0.7) * (0.3 + random(-0.08, 0.1));
brush.line(px - dx * h, py - dy * h, px + dx * h * (0.8 + random(-0.2, 0.2)), py + dy * h * 0.85);
}
}
// the dark seam where the husk let go
function seam(x, y, ang, len, weight) {
const ca = Math.cos(ang);
const sa = Math.sin(ang);
brush.noFill();
brush.set("rotring", "#1f1911", weight);
brush.beginShape(0.4);
for (let i = 0; i <= 6; i++) {
const t = i / 6;
const lx = -len * 0.45 + len * 0.9 * t;
const ly = randomGaussian(0, 1.7);
brush.vertex(x + lx * ca - ly * sa, y + lx * sa + ly * ca, 0.8 + random(-0.18, 0.2));
}
brush.endShape(false);
}
// a dry stalk trailing off the base tip
function stalk(x, y, ang, len, curl) {
const ca = Math.cos(ang);
const sa = Math.sin(ang);
brush.noFill();
brush.set("rotring", random() < 0.5 ? "#3a2c19" : "#1f1911", 1.15);
brush.beginShape(0.45);
for (let i = 0; i <= 4; i++) {
const t = i / 4;
const lx = -len * t;
const ly = curl * len * t * t;
brush.vertex(x + lx * ca - ly * sa + random(-1.2, 1.2), y + lx * sa + ly * ca + random(-1.2, 1.2), 1);
}
brush.endShape(false);
}
function pod(x, y, ang, len, ratio, split, nribs, tone, stem) {
const wid = len * ratio * (1 + random(-0.08, 0.1));
const gap = wid * split;
const beak = 0.62 + random(0, 0.32);
const [lit, shade, opA, opB] = tones(tone);
brush.noStroke();
if (split > 0.1) {
brush.fillBleed(0.24, "out");
brush.fillTexture(0.6, 0.5);
brush.fill(random() < 0.92 ? (tone === 2 ? "#1f1911" : "#3a2c19") : "#8f4a2a", 172 + random(-26, 34));
cavity(x, y, ang, len * 0.8, wid * (0.26 + split * 0.5));
}
brush.fillBleed(0.36, "out");
brush.fillTexture(0.55, 0.62);
brush.fill(random() < 0.97 ? lit : "#8f4a2a", opA + random(-20, 26));
valve(x, y, ang, len, wid * (1 + random(-0.05, 0.05)), -1, gap, beak);
brush.fill(shade, opB + random(-22, 24));
valve(x - 2, y + 2, ang, len * (0.97 + random(-0.03, 0.04)), wid * (0.93 + random(-0.07, 0.09)), 1, gap, beak);
if (len > 55) {
brush.fill(tone === 0 ? "#6f5533" : "#3a2c19", 104 + random(-18, 26));
valve(x - 3, y + 5, ang, len * 0.86, wid * (0.62 + random(-0.08, 0.1)), 1, gap * 1.1, beak + 0.16);
}
if (len > 90) {
brush.fill(tone === 0 ? "#b08e5c" : "#6f5533", 96 + random(-16, 24));
valve(x + 3, y - 4, ang, len * 0.9, wid * (0.7 + random(-0.08, 0.1)), -1, gap * 0.9, beak + 0.1);
}
if (nribs > 0) {
const col = random() < 0.5 ? "#3a2c19" : "#1f1911";
ribs(x, y, ang, len, wid, nribs, col, 0.9, -0.52 + random(-0.06, 0.06));
if (nribs > 4) ribs(x, y, ang, len * 0.94, wid * 0.9, nribs - 2, col, 0.85, 0.5 + random(-0.06, 0.06));
}
if (stem > 0) stalk(x - Math.cos(ang) * len * 0.48, y - Math.sin(ang) * len * 0.48, ang + random(-0.2, 0.2), stem, random(-0.3, 0.3));
if (len > 80) edge(x, y, ang, len, wid, -1, gap, beak, 1.15);
if (split > 0.1) seam(x, y, ang, len, len > 90 ? 1.25 : 0.95);
}
// a seed: a small warm body with one dark side, crisp against the paper
function seed(x, y, r) {
const a = random(0, 6.2832);
const dx = Math.cos(a);
const dy = Math.sin(a);
brush.noFill();
brush.set(random() < 0.72 ? "2B" : "charcoal", random() < 0.58 ? "#3a2c19" : "#6f5533", r * 0.62);
brush.line(x - dx * r * 0.62, y - dy * r * 0.62, x + dx * r * 0.66, y + dy * r * 0.66);
brush.set("2B", "#1f1911", r * 0.3);
brush.line(x - dx * r * 0.34, y - dy * r * 0.34, x + dx * r * 0.3, y + dy * r * 0.3);
}
// seeds thrown out of a split husk — tight near the pod, straggling away from it
function spill(cx, cy, n, spread, r0, dirX, dirY) {
for (let i = 0; i < n; i++) {
const d = 8 + abs(randomGaussian(0, spread));
const a = random(0, 6.2832);
const x = cx + Math.cos(a) * d * 1.35 + dirX * d * 0.9;
const y = cy + Math.sin(a) * d * 0.8 + dirY * d * 0.9;
seed(x, y, max(4.2, randomGaussian(r0, r0 * 0.24)));
}
}
function setup() {
createCanvas(800, 800, WEBGL);
brush.scaleBrushes(2.4);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background("#e8e1d2");
// plain ground, but breathing — pale pools weighted to the right of centre
brush.noStroke();
brush.fillBleed(0.7, "out");
brush.fillTexture(0.34, 0.35);
brush.fill("#6f5533", 17);
brush.circle(566, 506, 258, true);
brush.fill("#6f5533", 13);
brush.circle(436, 302, 198, true);
brush.fill("#3a2c19", 10);
brush.circle(668, 636, 162, true);
brush.fill("#8f4a2a", 9);
brush.circle(512, 462, 88, true);
// the faintest weight under the heavy husk — even light, almost no shadow
brush.fillBleed(0.5, "out");
brush.fill("#3a2c19", 44);
brush.circle(574, 526, 108, true);
// --- the scattered field: smallest specimens first, back-to-front ---
pod(322, 306, 1.19, 18, 0.42, 0.18, 0, 2, 0);
pod(214, 372, 0.42, 21, 0.32, 0.05, 0, 1, 0);
pod(742, 342, -0.88, 26, 0.36, 0.24, 0, 1, 0);
pod(410, 450, 0.66, 20, 0.46, 0.08, 0, 2, 0);
pod(618, 360, -0.34, 24, 0.3, 0.28, 0, 1, 0);
pod(684, 556, 1.02, 22, 0.4, 0.06, 0, 0, 0);
pod(494, 566, -1.24, 23, 0.34, 0.22, 0, 2, 0);
pod(288, 668, 0.28, 17, 0.44, 0.04, 0, 1, 0);
pod(552, 244, 0.58, 19, 0.32, 0.2, 0, 2, 0);
pod(346, 168, 0.94, 48, 0.26, 0.1, 3, 1, 0);
pod(238, 528, -0.52, 46, 0.38, 0.34, 3, 0, 22);
pod(560, 152, -0.24, 60, 0.32, 0.2, 4, 2, 0);
pod(438, 726, 0.66, 50, 0.42, 0.08, 3, 1, 0);
pod(706, 428, 1.34, 64, 0.24, 0.38, 4, 1, 20);
pod(650, 692, -0.62, 70, 0.36, 0.12, 4, 2, 0);
pod(788, 566, 0.36, 76, 0.32, 0.3, 4, 1, 0);
pod(392, 370, -0.86, 52, 0.28, 0.14, 3, 0, 18);
pod(668, 118, 0.44, 44, 0.4, 0.16, 3, 1, 0);
pod(206, 212, -0.42, 42, 0.34, 0.26, 3, 2, 26);
// medium specimens ringing the focal husk at uneven intervals
pod(266, 404, 1.16, 98, 0.22, 0.09, 5, 1, 34);
pod(672, 252, 0.86, 110, 0.34, 0.42, 5, 2, 30);
pod(438, 258, -0.63, 124, 0.25, 0.14, 5, 1, 40);
pod(342, 588, 0.44, 138, 0.3, 0.44, 6, 1, 38);
pod(772, 138, -1.08, 116, 0.19, 0.22, 5, 2, 28);
pod(628, 386, 0.24, 86, 0.44, 0.34, 4, 0, 0);
// --- the heavy husk, off-centre right, hand-weighted ---
const HX = 552;
const HY = 498;
const HA = -0.32;
const HL = 282;
const HW = 78;
brush.noStroke();
brush.fillBleed(0.28, "out");
brush.fillTexture(0.62, 0.5);
brush.fill("#3a2c19", 192);
cavity(HX, HY, HA, HL * 0.82, HW * 0.56);
brush.fill("#1f1911", 138);
cavity(HX - 12, HY + 4, HA, HL * 0.6, HW * 0.34);
brush.fill("#8f4a2a", 82);
cavity(HX + 34, HY - 12, HA, HL * 0.4, HW * 0.24);
brush.fillBleed(0.34, "out");
brush.fillTexture(0.5, 0.62);
brush.fill("#b08e5c", 196);
valve(HX, HY - 4, HA, HL, HW, -1, HW * 0.44, 0.68);
brush.fill("#6f5533", 152);
valve(HX + 2, HY - 7, HA, HL * 0.92, HW * 0.66, -1, HW * 0.46, 0.86);
brush.fill("#b08e5c", 132);
valve(HX - 4, HY - 2, HA, HL * 0.96, HW * 0.86, -1, HW * 0.42, 0.74);
brush.fill("#6f5533", 214);
valve(HX - 6, HY + 3, HA, HL * 0.98, HW * 0.9, 1, HW * 0.4, 0.72);
brush.fill("#3a2c19", 148);
valve(HX - 10, HY + 14, HA, HL * 0.84, HW * 0.62, 1, HW * 0.44, 0.88);
brush.fill("#1f1911", 84);
valve(HX - 16, HY + 22, HA, HL * 0.62, HW * 0.4, 1, HW * 0.42, 0.95);
// a third valve prised loose and laid just below the pair
brush.fillBleed(0.32, "out");
brush.fill("#b08e5c", 194);
valve(482, 626, -0.05, 156, 50, 1, 9, 0.62);
brush.fill("#6f5533", 128);
valve(486, 620, -0.05, 136, 32, 1, 7, 0.72);
ribs(HX, HY - 10, HA, HL * 0.9, HW * 0.95, 6, "#3a2c19", 1.5, -0.6);
ribs(HX - 8, HY + 12, HA, HL * 0.92, HW * 0.84, 5, "#1f1911", 1.4, 0.56);
ribs(482, 634, -0.05, 154, 48, 5, "#3a2c19", 0.9, 0.44);
edge(HX, HY - 4, HA, HL, HW, -1, HW * 0.44, 0.68, 1.6);
edge(HX - 6, HY + 3, HA, HL * 0.98, HW * 0.9, 1, HW * 0.4, 0.72, 1.3);
edge(482, 626, -0.05, 156, 50, 1, 9, 0.62, 1.15);
// six seeds still sitting in the open gape, along the axis
seed(HX - 74, HY + 21, 6.2);
seed(HX - 44, HY + 13, 5.6);
seed(HX - 12, HY + 6, 6.6);
seed(HX + 24, HY - 3, 5.8);
seed(HX + 60, HY - 13, 5.1);
seed(HX + 94, HY - 21, 4.5);
// --- darks land last: seams, stalks, spilled seed ---
seam(HX, HY, HA, HL, 2);
seam(672, 252, 0.86, 112, 1.35);
seam(342, 588, 0.44, 134, 1.4);
seam(706, 428, 1.34, 62, 1.05);
seam(628, 386, 0.24, 84, 1.1);
seam(438, 258, -0.63, 118, 1.1);
stalk(HX - Math.cos(HA) * HL * 0.5, HY - Math.sin(HA) * HL * 0.5, HA + 0.36, 104, 0.2);
spill(448, 596, 12, 32, 7.4, -0.5, 0.4);
spill(658, 460, 8, 26, 6.4, 0.6, 0.3);
spill(360, 654, 7, 22, 5.8, -0.3, 0.5);
spill(700, 300, 5, 18, 5.2, 0.4, 0.4);
spill(288, 464, 4, 16, 4.8, -0.4, 0.3);
// three stragglers that have rolled well out into the bare paper
seed(148, 300, 4.8);
seed(176, 322, 3.9);
seed(258, 732, 5.4);
seed(156, 608, 4.6);
seed(108, 472, 4);
}