two koi circling in dark pond water, their bodies left as bare paper
painted by Grok 4.6p5 2.3.2 · brush 2.2.2 · 2026-08-24
// composition: two koi circle each other left-of-centre, kept as bare paper reserved out of layered dark pond washes
// layering: base pond pass, deeper patchy pass, edge-tightening washes hugging each body, persimmon patches, ghost fins and whiskers last
const PALETTE = [
"#e8e2d2", // paper — the koi themselves
"#22392e", // pond, base pass
"#12241c", // pond, deep pass
"#d4642a", // persimmon patches, the accent
"#8f8873", // pale pen for ripples and whiskers
];
const SPINE_A = [
[338, 288], [392, 318], [430, 376], [428, 442], [390, 492],
];
const RAD_A = [24, 31, 34, 27, 11];
const SPINE_B = [
[452, 554], [398, 546], [346, 512], [322, 452], [332, 392],
];
const RAD_B = [20, 26, 27, 21, 9];
let FISH = null;
function buildFish(spine, rads) {
const xs = [];
const ys = [];
const rs = [];
for (let i = 0; i < spine.length - 1; i++) {
for (let k = 0; k < 7; k++) {
const t = k / 7;
xs.push(spine[i][0] + (spine[i + 1][0] - spine[i][0]) * t);
ys.push(spine[i][1] + (spine[i + 1][1] - spine[i][1]) * t);
rs.push(rads[i] + (rads[i + 1] - rads[i]) * t);
}
}
xs.push(spine[spine.length - 1][0]);
ys.push(spine[spine.length - 1][1]);
rs.push(rads[rads.length - 1]);
return { xs, ys, rs };
}
function clearOf(x, y, pad) {
for (const f of FISH) {
for (let i = 0; i < f.xs.length; i++) {
const dx = x - f.xs[i];
const dy = y - f.ys[i];
if (dx * dx + dy * dy < (f.rs[i] + pad) * (f.rs[i] + pad)) return false;
}
}
return true;
}
function setup() {
createCanvas(800, 800, WEBGL);
brush.scaleBrushes(2.4);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background("#e8e2d2");
FISH = [buildFish(SPINE_A, RAD_A), buildFish(SPINE_B, RAD_B)];
brush.noStroke();
// base pond pass — big soft blobs everywhere the fish are not
brush.fillBleed(0.45, "out");
brush.fillTexture(0.55, 0.5);
for (let gy = 0; gy < 8; gy++) {
for (let gx = 0; gx < 8; gx++) {
const x = 50 + gx * 100 + random(-24, 24);
const y = 50 + gy * 100 + random(-24, 24);
const r = Math.max(40, randomGaussian(62, 8));
if (!clearOf(x, y, r * 0.35)) continue;
brush.fill("#22392e", Math.max(70, randomGaussian(108, 13)));
brush.circle(x, y, r, true);
}
}
// deeper, patchier second pass
brush.fillBleed(0.4, "out");
for (let gy = 0; gy < 6; gy++) {
for (let gx = 0; gx < 6; gx++) {
if (random() < 0.3) continue;
const x = 80 + gx * 130 + random(-30, 30);
const y = 80 + gy * 130 + random(-30, 30);
const r = Math.max(32, randomGaussian(48, 7));
if (!clearOf(x, y, r * 0.4)) continue;
brush.fill("#12241c", Math.max(55, randomGaussian(86, 12)));
brush.circle(x, y, r, true);
}
}
// tighten the water against each body so the koi read as reserved paper
brush.fillBleed(0.28, "out");
for (const f of FISH) {
for (let i = 2; i < f.xs.length - 1; i += 3) {
const tx = f.xs[i + 1] - f.xs[i - 1];
const ty = f.ys[i + 1] - f.ys[i - 1];
const m = Math.sqrt(tx * tx + ty * ty) || 1;
const nx = -ty / m;
const ny = tx / m;
for (const side of [-1, 1]) {
if (random() < 0.25) continue;
const d = f.rs[i] + 19 + random(0, 8);
brush.fill("#12241c", 118 + random(-14, 14));
brush.circle(f.xs[i] + nx * d * side, f.ys[i] + ny * d * side, 17 + random(0, 7), true);
}
}
}
// persimmon patches riding each back
brush.fillBleed(0.35, "out");
brush.fillTexture(0.6, 0.6);
const fa = FISH[0];
const patchA = [5, 12, 19];
for (let k = 0; k < 3; k++) {
const i = patchA[k];
brush.fill("#d4642a", 150 + random(-10, 15));
brush.circle(fa.xs[i] + random(-4, 4), fa.ys[i] + random(-4, 4), fa.rs[i] * 0.68, true);
}
brush.fill("#d4642a", 88);
brush.circle(fa.xs[12] + 5, fa.ys[12] - 4, fa.rs[12] * 0.4, true);
const fb = FISH[1];
const patchB = [6, 15];
for (let k = 0; k < 2; k++) {
const i = patchB[k];
brush.fill("#d4642a", 148 + random(-10, 15));
brush.circle(fb.xs[i] + random(-3, 3), fb.ys[i] + random(-3, 3), fb.rs[i] * 0.66, true);
}
// eyes
brush.fill("#12241c", 215);
brush.circle(346, 282, 3.6, true);
brush.circle(446, 546, 3.2, true);
// ghost fins and tails, translucent paper laid back over the dark
brush.fillBleed(0.5, "out");
brush.fillTexture(0.45, 0.5);
brush.fill("#e8e2d2", 112);
brush.beginShape(0.5);
brush.vertex(374, 338);
brush.vertex(338, 382);
brush.vertex(364, 388);
brush.endShape(true);
brush.fill("#e8e2d2", 120);
brush.beginShape(0.5);
brush.vertex(392, 488);
brush.vertex(368, 542);
brush.vertex(406, 548);
brush.endShape(true);
brush.fill("#e8e2d2", 92);
brush.beginShape(0.5);
brush.vertex(388, 494);
brush.vertex(418, 540);
brush.vertex(430, 516);
brush.endShape(true);
brush.fill("#e8e2d2", 116);
brush.beginShape(0.5);
brush.vertex(334, 396);
brush.vertex(316, 350);
brush.vertex(350, 356);
brush.endShape(true);
brush.fill("#e8e2d2", 108);
brush.beginShape(0.5);
brush.vertex(456, 548);
brush.vertex(490, 584);
brush.vertex(462, 594);
brush.endShape(true);
// two slow ripples, and whisker hints at each mouth
brush.noFill();
brush.set("pen", "#8f8873", 0.85);
brush.beginShape(0.6);
brush.vertex(206, 330);
brush.vertex(178, 420);
brush.vertex(202, 516);
brush.endShape(false);
brush.beginShape(0.6);
brush.vertex(538, 268);
brush.vertex(576, 366);
brush.vertex(566, 470);
brush.endShape(false);
brush.line(332, 292, 320, 300);
brush.line(340, 278, 330, 268);
brush.line(458, 560, 470, 570);
// one persimmon leaf adrift, far from the fish
brush.noStroke();
brush.fill("#d4642a", 130);
brush.circle(654, 212, 5.5, true);
}