a kelp forest bending in the surge, light falling through a gap upper-right
painted by Mistral Medium 3.1p5 2.3.2 · brush 2.2.2 · 2026-08-24
// composition: kelp columns rise off the bottom edge, crowded at the left, opening to a gap upper-right where light falls
// layering: soft water washes, wash blades of the front plants, then seabed-field ribbon strokes in three ranks back-to-front, the light shaft last
const PALETTE = [
"#dce8e2", // pale aqua water
"#a9c4b4", // shallow wash
"#6fa08a", // back-rank kelp
"#35735f", // mid kelp teal
"#123c34", // front kelp, deepest
"#efe8cd", // falling light
];
function blade(xb, topY, w, col, op, seed) {
brush.noStroke();
brush.fillBleed(0.45, "out");
brush.fillTexture(0.5, 0.6);
brush.fill(col, op);
const left = [];
const right = [];
for (let i = 0; i <= 6; i++) {
const t = i / 6;
const y = 800 - (800 - topY) * t;
const sway = (noise(seed + t * 2.2) - 0.5) * 88 * t;
const ww = w * (1 - 0.62 * t);
left.push([xb + sway - ww, y]);
right.push([xb + sway + ww, y]);
}
brush.beginShape(0.4);
for (let i = 0; i <= 6; i++) brush.vertex(left[i][0], left[i][1]);
for (let i = 6; i >= 0; i--) brush.vertex(right[i][0], right[i][1]);
brush.endShape(true);
}
function setup() {
createCanvas(800, 800, WEBGL);
angleMode(DEGREES);
brush.scaleBrushes(2.5);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background("#dce8e2");
// water: deeper at the bed, clear above
brush.noStroke();
brush.fillBleed(0.6, "out");
brush.fillTexture(0.4, 0.4);
brush.fill("#a9c4b4", 34);
brush.circle(140, 764, 208, true);
brush.circle(388, 786, 190, true);
brush.circle(636, 792, 168, true);
brush.fill("#a9c4b4", 20);
brush.circle(210, 540, 226, true);
brush.circle(520, 580, 196, true);
// wash blades of the front plants, under their strokes
blade(96, 150, 17, "#35735f", 105, 3.1);
blade(178, 96, 20, "#123c34", 118, 7.7);
blade(292, 210, 15, "#35735f", 96, 12.4);
blade(160, 250, 12, "#123c34", 84, 17.9);
brush.field("seabed");
// back rank — pale, thin, tallest at the left
const backX = [64, 102, 146, 194, 246, 306, 366, 430, 548, 618];
const backTop = [180, 150, 210, 170, 240, 200, 280, 320, 430, 500];
for (let i = 0; i < 10; i++) {
brush.set("marker", "#6fa08a", 2.2 + random(0, 1));
brush.line(backX[i] + random(-3, 3), 800, backX[i] + random(-26, 26), backTop[i] + random(-20, 20));
brush.set("marker", "#6fa08a", 1.8 + random(0, 0.8));
brush.line(backX[i] + 6, 800, backX[i] + random(-20, 30), backTop[i] + random(-10, 40));
}
// mid rank
const midX = [54, 126, 208, 278, 350];
const midTop = [110, 76, 130, 168, 230];
for (let i = 0; i < 5; i++) {
for (let k = 0; k < 3; k++) {
brush.set("marker", "#35735f", 3.4 + random(0, 1.6));
brush.line(midX[i] + k * 5 + random(-2, 2), 800, midX[i] + random(-30, 30), midTop[i] + random(-24, 24));
}
}
// front rank — darkest, thickest ribbons
const frontX = [96, 178, 292];
const frontTop = [140, 88, 200];
for (let i = 0; i < 3; i++) {
for (let k = 0; k < 3; k++) {
brush.set("marker", "#123c34", 5.2 + random(0, 2.2));
brush.line(frontX[i] + k * 7 - 7 + random(-2, 2), 800, frontX[i] + random(-34, 34), frontTop[i] + random(-20, 20));
}
}
brush.noField();
// light falls through the canopy gap, upper-right
brush.noStroke();
brush.fillBleed(0.55, "out");
brush.fillTexture(0.35, 0.4);
brush.fill("#efe8cd", 30);
brush.beginShape(0.3);
brush.vertex(676, 0);
brush.vertex(788, 0);
brush.vertex(628, 368);
brush.vertex(544, 310);
brush.endShape(true);
brush.fill("#efe8cd", 22);
brush.beginShape(0.3);
brush.vertex(596, 0);
brush.vertex(668, 0);
brush.vertex(506, 330);
brush.vertex(452, 288);
brush.endShape(true);
brush.fill("#efe8cd", 40);
brush.beginShape(0.3);
brush.vertex(724, 0);
brush.vertex(772, 0);
brush.vertex(700, 180);
brush.vertex(664, 158);
brush.endShape(true);
// motes hanging in the shaft
brush.fill("#efe8cd", 58);
brush.circle(648, 210, 4, true);
brush.circle(690, 132, 3, true);
brush.circle(600, 292, 5, true);
brush.circle(566, 350, 3, true);
brush.circle(712, 84, 3.5, true);
// drifting fragments in the midwater, left
brush.set("2H", "#35735f", 0.6);
brush.line(420, 380, 428, 386);
brush.line(370, 300, 376, 308);
brush.line(468, 452, 462, 460);
brush.line(330, 430, 338, 434);
}