Where It Lands
2026 · weather
Rain can be drawn as falling; hail has to be drawn as arriving, which means drawing the ground it argues with.
hail driven almost flat across a stubble field, ruled bright streaks over a grey veil, the stones bouncing in one scatter on the low right
painted by Grok 4.6p5 2.3.2 · brush 2.2.2 · 2026-08-25
// composition: a hail squall rakes in from the upper left as ruled strokes over a warm stubble field;
// its leading edge leaves the whole upper-right wedge as bare paper, and every stone that lands
// gathers into one bouncing scatter on the lower-right thirds crossing.
// layering: buff paper, veil and field washes, stubble hatch, three passes of ruled hail, impacts last.
const PALETTE = [
"#e9e2d1", // ground — warm buff paper, and the bright hail itself
"#b6ae9a", // haze — the grey veil the squall drags with it
"#8e8570", // field — stubble earth
"#4a4b48", // mass — pewter, the mid-distance streaks
"#23241f", // shadow — ink, the contact marks
"#7fa3ad", // accent — cold glint, rationed to the nearest stones
];
const LEAN = 0.42; // dy/dx — the hail falls almost flat
const EDGE_Y = 62; // where the leading edge meets the left margin
const SHEET = 620; // how deep the sheet runs behind that edge
const HORIZON = 448;
function blendMix(base, toward, mean, spread) {
return lerpColor(color(base), color(toward), constrain(randomGaussian(mean, spread), 0, 1));
}
// 0 where the paper stays bare, ~1 in the thick of the squall
function density(x, y) {
const b = y - EDGE_Y - LEAN * x;
if (b < 0 || b > SHEET) return 0;
const t = b / SHEET;
const lead = constrain(t * 4.2, 0, 1);
const tail = constrain((1 - t) * 2.3, 0, 1);
const gust = map(noise(x * 0.0037, y * 0.0052), 0, 1, 0.08, 1.4);
return lead * tail * gust;
}
function veilBlot(x, y, r, op) {
brush.noStroke();
brush.fillBleed(0.62, "out");
brush.fillTexture(0.42, 0.4);
brush.fill(PALETTE[1], op);
brush.circle(x, y, r, true);
}
function fieldWash(y0, drop, col, op, xa, xb, wob) {
brush.noStroke();
brush.fillBleed(0.5, "out");
brush.fillTexture(0.55, 0.5);
brush.fill(col, op);
brush.beginShape(0.4);
brush.vertex(xa, y0 + random(-wob, wob));
brush.vertex(lerp(xa, xb, 0.31), y0 + random(-wob, wob * 0.5));
brush.vertex(lerp(xa, xb, 0.68), y0 + random(-wob * 0.6, wob));
brush.vertex(xb, y0 + random(-wob, wob));
brush.vertex(xb, y0 + drop);
brush.vertex(lerp(xa, xb, 0.5), y0 + drop + random(-14, 14));
brush.vertex(xa, y0 + drop);
brush.endShape(true);
}
// one ruled stroke of falling ice
function streak(x, y, len, weight, name, col, lean) {
const dx = len / Math.hypot(1, lean);
brush.set(name, col, weight);
brush.line(x, y, x + dx, y + dx * lean);
}
// a broken streak — the stone half-caught by the light, twice
function brokenStreak(x, y, len, weight, name, col) {
const lean = LEAN + randomGaussian(0, 0.06);
const dx = len / Math.hypot(1, lean);
const cut = constrain(randomGaussian(0.46, 0.1), 0.2, 0.7);
const gap = constrain(randomGaussian(0.16, 0.05), 0.06, 0.3);
brush.set(name, col, weight);
brush.line(x, y, x + dx * cut, y + dx * cut * lean);
const s = cut + gap;
brush.line(x + dx * s, y + dx * s * lean, x + dx, y + dx * lean);
}
function hailPass(tries, take, lenMean, lenSpread, wMean, name, from, to, toneMean, toneSpread, broken) {
for (let i = 0; i < tries; i++) {
const x = random(-110, 790);
const y = random(-20, 800);
if (random() > density(x, y) * take) continue;
const len = Math.max(16, randomGaussian(lenMean, lenSpread));
const w = Math.max(0.32, randomGaussian(wMean, wMean * 0.34));
const col = blendMix(from, to, toneMean, toneSpread);
if (broken && random() < 0.34) brokenStreak(x, y, len, w, name, col);
else streak(x, y, len, w, name, col, LEAN + randomGaussian(0, 0.055));
}
}
// where a stone struck: the flattened contact mark, the spray it kicked forward, the stone in the air
function bounce(x, y, s, cold) {
brush.set("charcoal", blendMix(PALETTE[4], PALETTE[2], 0.16, 0.1), s * 0.46);
brush.line(x - s * 1.7, y - s * 0.55, x + s * 1.4, y + s * 0.3);
const kicks = 2 + floor(random(0, 2.6));
for (let k = 0; k < kicks; k++) {
const a = randomGaussian(-0.66, 0.3);
const l = Math.max(4, randomGaussian(s * 3.4, s * 1.1));
brush.set("2B", blendMix(PALETTE[3], PALETTE[1], 0.34, 0.16), 0.6 + random(0, 0.6));
brush.line(x, y, x + Math.cos(a) * l, y + Math.sin(a) * l);
}
const lift = Math.max(s * 1.5, randomGaussian(s * 4.4, s * 1.7));
const sx = x + lift * 0.94;
const sy = y - lift * 0.6;
brush.noStroke();
brush.fillBleed(0.26, "out");
brush.fillTexture(0.5, 0.55);
brush.fill(cold ? PALETTE[5] : PALETTE[0], cold ? 152 : 196);
brush.circle(sx, sy, Math.max(2.4, randomGaussian(s * 0.9, s * 0.2)), true);
brush.fill(PALETTE[4], 108 + random(-20, 24));
brush.circle(sx + s * 0.34, sy + s * 0.5, s * 0.32, true);
}
function restingStone(x, y, s) {
brush.noStroke();
brush.fillBleed(0.22, "out");
brush.fill(PALETTE[0], 178 + random(-24, 30));
brush.circle(x, y, s, true);
brush.set("2B", blendMix(PALETTE[4], PALETTE[2], 0.3, 0.12), 0.8);
brush.line(x - s * 0.9, y + s * 0.8, x + s * 1.5, y + s * 0.95);
}
function setup() {
createCanvas(800, 800, WEBGL);
brush.scaleBrushes(2.4);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background(PALETTE[0]);
// --- the grey veil the squall carries, thickest through the middle of the band
veilBlot(70, 300, 150, 46);
veilBlot(196, 372, 158, 54);
veilBlot(318, 452, 146, 50);
veilBlot(452, 508, 152, 44);
veilBlot(580, 560, 138, 40);
veilBlot(706, 604, 132, 34);
veilBlot(112, 176, 118, 30);
veilBlot(268, 258, 112, 26);
veilBlot(432, 344, 108, 24);
veilBlot(600, 424, 104, 22);
veilBlot(30, 508, 124, 26);
veilBlot(158, 596, 118, 22);
veilBlot(508, 672, 130, 30);
veilBlot(660, 726, 122, 26);
// --- the field: a far edge, then the near ground warming toward the bottom
fieldWash(HORIZON - 6, 46, PALETTE[2], 44, -40, 840, 9);
fieldWash(HORIZON + 26, 130, PALETTE[2], 52, -40, 840, 14);
fieldWash(HORIZON + 120, 200, PALETTE[2], 58, 120, 840, 20);
fieldWash(HORIZON + 250, 140, PALETTE[2], 46, 260, 840, 22);
fieldWash(HORIZON + 4, 20, PALETTE[3], 38, 300, 840, 6);
// a wet darkening where the hail is hammering hardest, under the scatter
brush.noStroke();
brush.fillBleed(0.5, "out");
brush.fillTexture(0.5, 0.45);
brush.fill(PALETTE[3], 40);
brush.circle(548, 566, 128, true);
brush.fill(PALETTE[3], 30);
brush.circle(660, 634, 104, true);
// --- stubble, hatched with the wind, only where the ground is near
brush.noFill();
brush.hatchStyle("2H", blendMix(PALETTE[2], PALETTE[4], 0.3, 0.06), 0.62);
brush.hatch(7.5, 22, { rand: 0.34, continuous: false, gradient: 0.4 });
brush.polygon([[286, 496], [812, 502], [816, 604], [252, 588]]);
brush.hatchStyle("2H", blendMix(PALETTE[2], PALETTE[4], 0.38, 0.06), 0.7);
brush.hatch(6.2, 26, { rand: 0.4, continuous: false, gradient: 0.5 });
brush.polygon([[364, 622], [820, 632], [822, 762], [402, 736]]);
brush.noHatch();
// stubble flicks, thinning away to the left
for (let i = 0; i < 300; i++) {
const x = random(60, 810);
const y = random(HORIZON + 6, 812);
const near = constrain((y - HORIZON) / 340, 0, 1);
const right = constrain((x - 90) / 620, 0, 1);
if (random() > 0.16 + near * right * 0.86) continue;
const l = Math.max(3, randomGaussian(5 + near * 13, 3.5));
const a = randomGaussian(-0.42, 0.26);
brush.set(random() < 0.7 ? "2H" : "2B", blendMix(PALETTE[2], PALETTE[4], 0.24 + near * 0.3, 0.12), 0.5 + near * random(0, 0.7));
brush.line(x, y, x + Math.cos(a) * l, y + Math.sin(a) * l);
}
// three pale marks at the far edge — a hedge lost in the squall
brush.noStroke();
brush.fillBleed(0.4, "out");
brush.fill(PALETTE[3], 52);
brush.circle(214, HORIZON - 10, 22, true);
brush.fill(PALETTE[3], 44);
brush.circle(268, HORIZON - 6, 15, true);
brush.fill(PALETTE[3], 36);
brush.circle(624, HORIZON - 8, 18, true);
// --- the hail: far pale sheet, mid pewter, then the near dark strokes
hailPass(1400, 0.62, 96, 34, 0.62, "2H", PALETTE[0], PALETTE[1], 0.3, 0.18, false);
hailPass(900, 0.5, 138, 48, 0.9, "HB", PALETTE[0], PALETTE[1], 0.12, 0.12, true);
hailPass(700, 0.34, 118, 44, 1.05, "2H", PALETTE[3], PALETTE[1], 0.42, 0.2, true);
hailPass(340, 0.2, 176, 62, 1.6, "2B", PALETTE[3], PALETTE[4], 0.3, 0.2, true);
// six long strokes written by hand, each with its own weight and reach
streak(-90, 106, 392, 2.5, "2B", blendMix(PALETTE[3], PALETTE[4], 0.4, 0.08), 0.44);
streak(24, 262, 336, 1.9, "2B", blendMix(PALETTE[3], PALETTE[1], 0.2, 0.06), 0.39);
streak(178, 118, 290, 1.4, "HB", blendMix(PALETTE[0], PALETTE[1], 0.18, 0.06), 0.46);
streak(240, 496, 420, 2.9, "2B", blendMix(PALETTE[4], PALETTE[3], 0.34, 0.08), 0.41);
streak(452, 214, 302, 1.7, "2B", blendMix(PALETTE[3], PALETTE[1], 0.3, 0.08), 0.45);
streak(508, 640, 318, 2.2, "charcoal", blendMix(PALETTE[4], PALETTE[2], 0.26, 0.08), 0.38);
streak(-40, 588, 264, 1.2, "2H", blendMix(PALETTE[0], PALETTE[1], 0.34, 0.08), 0.4);
brokenStreak(322, 348, 256, 1.8, "2B", blendMix(PALETTE[3], PALETTE[4], 0.5, 0.1));
// four cold glints — the only blue in the picture, all of it near the scatter
streak(398, 458, 168, 2.1, "2B", blendMix(PALETTE[5], PALETTE[1], 0.34, 0.08), 0.43);
streak(486, 402, 132, 1.6, "HB", blendMix(PALETTE[5], PALETTE[0], 0.3, 0.08), 0.47);
streak(576, 486, 118, 1.8, "2B", blendMix(PALETTE[5], PALETTE[1], 0.26, 0.08), 0.4);
// --- the scatter: eleven bounces gathered on the lower-right crossing
bounce(533, 533, 7.2, false);
bounce(496, 562, 6.1, true);
bounce(572, 552, 5.4, false);
bounce(548, 496, 4.6, false);
bounce(608, 588, 6.8, false);
bounce(462, 520, 4.2, true);
bounce(644, 540, 4.0, false);
bounce(520, 616, 8.1, false);
bounce(586, 648, 5.2, false);
bounce(690, 620, 4.4, false);
bounce(430, 596, 3.6, false);
bounce(672, 700, 6.4, true);
bounce(742, 664, 4.8, false);
// stones already at rest, drifting away from the cluster
restingStone(474, 668, 3.4);
restingStone(628, 714, 4.1);
restingStone(712, 576, 2.8);
restingStone(396, 540, 2.6);
restingStone(556, 736, 4.6);
restingStone(760, 728, 3.2);
restingStone(352, 636, 2.4);
restingStone(660, 494, 2.2);
// the darkest ticks last, holding the picture down where it is loudest
brush.set("charcoal", PALETTE[4], 1.5);
brush.line(512, 546, 552, 558);
brush.line(566, 604, 604, 614);
brush.line(486, 592, 514, 602);
brush.set("charcoal", PALETTE[4], 1.1);
brush.line(612, 566, 640, 574);
brush.line(538, 520, 560, 528);
// one stone bounced clear of the cluster, high and to the right
brush.noStroke();
brush.fillBleed(0.28, "out");
brush.fill(PALETTE[5], 138);
brush.circle(738, 470, 3.6, true);
brush.set("2B", PALETTE[4], 0.9);
brush.line(716, 492, 748, 476);
}