Iron Weather
2026 · color field
Rust is not a colour but a rate: three bands agreeing on how fast the iron is leaving, and where the paper is still dry.
three stacked bands of iron oxide, the heaviest low, edges bleeding into one another
painted by Grok 4.6p5 2.3.2 · brush 2.2.2 · 2026-08-25
// composition: three oxide bands — a thin pale one high and short of the left edge, a rust mass above centre that stops
// before the right edge, and the heaviest iron band low, running off both sides; paper breathes at top, right and foot
// layering: pale bleeding hazes first, then sheets and denser cores with corrosion grain, then the seams where one rust
// bleeds into the next, and last the dark pooling where the low band surges up at the lower-right third
const PALETTE = [
"#e7e0d2", // ground — bare paper
"#cbb391", // pale oxide, the thin high band
"#a9552b", // rust, the middle mass
"#7a3520", // red iron, shadow inside the rust
"#42251f", // dark iron oxide, the heavy low band
"#5f6a58", // oxidised green, the accent
];
// two pigments never blendMix the same way twice
function blendMix(a, b, t) {
return lerpColor(color(a), color(b), constrain(t, 0, 1));
}
// a wide translucent sheet of oxide: a closed shape whose top and bottom contours wobble
function sheet(x0, x1, yTop, yBot, col, alpha, wob, bleed) {
brush.noStroke();
brush.fillBleed(bleed, "out");
brush.fillTexture(0.5, 0.42);
brush.fill(col, alpha);
const n = 8;
brush.beginShape(0.55);
for (let i = 0; i <= n; i++) {
const t = i / n;
brush.vertex(lerp(x0, x1, t), yTop + randomGaussian(0, wob) + Math.sin(t * 4.1 + 0.4) * wob * 0.7);
}
for (let i = n; i >= 0; i--) {
const t = i / n;
brush.vertex(lerp(x0, x1, t), yBot + randomGaussian(0, wob) - Math.sin(t * 3.3 + 1.2) * wob * 0.8);
}
brush.endShape(true);
}
// pigment pooling along a horizontal run — the body of a band, or its dissolving end
function pool(x0, x1, yc, r, count, cols, alpha, bleed, sag) {
brush.noStroke();
brush.fillBleed(bleed, "out");
brush.fillTexture(0.55, 0.5);
for (let i = 0; i < count; i++) {
const t = (i + random(-0.34, 0.34)) / (count - 1);
const x = lerp(x0, x1, constrain(t, -0.06, 1.06));
const c = blendMix(cols[0], cols[1], constrain(randomGaussian(0.3, 0.3), 0, 1));
brush.fill(c, constrain(randomGaussian(alpha, alpha * 0.24), 10, 215));
brush.circle(x, yc + randomGaussian(0, sag), max(11, randomGaussian(r, r * 0.2)), true);
}
}
// horizontal grain: the direction rust travels when it runs
function grain(x0, x1, yc, spread, count, cols, w0) {
for (let i = 0; i < count; i++) {
const y = randomGaussian(yc, spread);
const x = randomGaussian((x0 + x1) / 2, (x1 - x0) * 0.29);
const len = max(18, randomGaussian((x1 - x0) * 0.13, (x1 - x0) * 0.07));
const p = random();
const col = blendMix(p < 0.68 ? cols[0] : cols[1], cols[2], random(0, 0.4));
brush.set(p < 0.55 ? "marker" : p < 0.86 ? "cpencil" : "2B", col, max(0.6, randomGaussian(w0, w0 * 0.4)));
brush.line(x - len / 2, y + randomGaussian(0, 1.7), x + len / 2, y + randomGaussian(0, 1.9));
}
}
// pitting: the specks where the metal has given up entirely
function pitting(x0, x1, yc, spread, count, col) {
for (let i = 0; i < count; i++) {
const x = randomGaussian((x0 + x1) / 2, (x1 - x0) * 0.3);
const y = randomGaussian(yc, spread);
brush.set(random() < 0.6 ? "charcoal" : "spray", col, max(0.5, randomGaussian(1.3, 0.5)));
brush.line(x, y, x + randomGaussian(0, 3.4), y + randomGaussian(0, 2.6));
}
}
// a tongue of one band's colour crossing into the paper or into its neighbour
function tongue(x, y, r, col, alpha, reach) {
brush.noStroke();
brush.fillBleed(0.8, "out");
brush.fillTexture(0.4, 0.45);
brush.fill(col, alpha);
brush.circle(x, y, r, true);
brush.fill(col, alpha * 0.6);
brush.circle(x + randomGaussian(0, r * 0.5), y + reach, r * 0.55, true);
}
// rust running down out of a band and stopping where the paper is dry
function drip(x, y, len, col, w) {
brush.noFill();
brush.set(random() < 0.6 ? "2B" : "cpencil", col, w);
brush.beginShape(0.7);
const n = 4;
for (let i = 0; i <= n; i++) {
const t = i / n;
brush.vertex(x + Math.sin(t * 5.4) * 3.4 + randomGaussian(0, 1.6), y + len * t, constrain(1 - t * 0.6, 0.25, 1));
}
brush.endShape(false);
brush.noStroke();
brush.fillBleed(0.6, "out");
brush.fill(col, 96 + random(-24, 34));
brush.circle(x + randomGaussian(0, 2.4), y + len, max(2.4, randomGaussian(4.4, 1.4)), true);
}
function setup() {
createCanvas(800, 800, WEBGL);
angleMode(DEGREES);
brush.scaleBrushes(2.4);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background("#e7e0d2");
// ---- air: the faintest breath of each band, laid down before any band exists
pool(140, 800, 150, 36, 9, ["#cbb391", "#e7e0d2"], 24, 0.78, 7);
pool(-20, 648, 358, 98, 11, ["#a9552b", "#cbb391"], 21, 0.78, 15);
pool(20, 812, 618, 132, 12, ["#42251f", "#7a3520"], 25, 0.76, 17);
// ---- band one: thin, pale, high, leaving the left corner to the paper
sheet(118, 808, 134, 165, "#cbb391", 56, 5, 0.58);
pool(212, 706, 150, 20, 8, ["#cbb391", "#a9552b"], 44, 0.44, 5);
grain(140, 800, 150, 9, 70, ["#cbb391", "#a9552b", "#7a3520"], 1.2);
pitting(300, 780, 152, 11, 26, "#a9552b");
// its left end thins out into nothing before the margin
pool(112, 190, 150, 17, 5, ["#cbb391", "#e7e0d2"], 28, 0.85, 6);
// ---- band two: the rust mass, off-centre high, stopping well short of the right edge
sheet(-26, 598, 301, 419, "#a9552b", 72, 9, 0.5);
sheet(-12, 522, 319, 403, "#7a3520", 44, 11, 0.46);
pool(-10, 566, 352, 62, 12, ["#a9552b", "#7a3520"], 56, 0.42, 12);
pool(44, 434, 374, 40, 8, ["#7a3520", "#a9552b"], 52, 0.36, 10);
grain(-10, 580, 356, 34, 170, ["#a9552b", "#7a3520", "#42251f"], 1.8);
grain(0, 300, 318, 15, 44, ["#a9552b", "#cbb391", "#a9552b"], 1.3);
pitting(20, 560, 366, 40, 44, "#42251f");
// its right end lets go of the colour altogether
pool(524, 646, 356, 33, 7, ["#a9552b", "#e7e0d2"], 30, 0.86, 17);
// ---- band three: the heaviest, low, running off both sides
sheet(30, 812, 523, 705, "#7a3520", 60, 12, 0.5);
sheet(72, 812, 541, 701, "#42251f", 68, 13, 0.46);
pool(60, 802, 612, 84, 14, ["#42251f", "#7a3520"], 60, 0.42, 15);
pool(152, 784, 650, 56, 10, ["#42251f", "#42251f"], 68, 0.36, 12);
grain(40, 806, 616, 52, 190, ["#42251f", "#7a3520", "#42251f"], 2.1);
grain(120, 700, 690, 13, 50, ["#7a3520", "#42251f", "#a9552b"], 1.4);
// corrosion crawling with the field, only here, where the metal is thickest
brush.field("seabed");
for (let i = 0; i < 50; i++) {
const x = randomGaussian(430, 210);
const y = randomGaussian(618, 46);
brush.set(random() < 0.5 ? "cpencil" : "charcoal", blendMix("#42251f", "#7a3520", random(0, 0.55)), max(0.7, randomGaussian(1.7, 0.5)));
brush.flowLine(x, y, max(24, randomGaussian(64, 22)), random() < 0.5 ? random(-9, 9) : random(171, 189));
}
brush.noField();
pitting(80, 780, 626, 56, 64, "#42251f");
// its left end dissolves into the paper margin
pool(18, 132, 600, 44, 6, ["#7a3520", "#e7e0d2"], 32, 0.86, 19);
// ---- the surge: the low band climbs at the lower-right third, where the picture is decided
sheet(452, 640, 497, 562, "#7a3520", 50, 10, 0.62);
pool(468, 624, 522, 33, 7, ["#42251f", "#7a3520"], 64, 0.52, 11);
grain(456, 636, 516, 17, 40, ["#7a3520", "#42251f", "#42251f"], 1.6);
// ---- seams: each band lets a little of itself go into the space below and above
tongue(226, 176, 15, "#cbb391", 48, 13);
tongue(392, 179, 12, "#cbb391", 40, 16);
tongue(604, 174, 18, "#cbb391", 44, 11);
tongue(716, 171, 11, "#cbb391", 34, 18);
tongue(96, 292, 21, "#a9552b", 44, -15);
tongue(268, 287, 16, "#a9552b", 38, -19);
tongue(454, 295, 24, "#7a3520", 40, -13);
tongue(148, 428, 22, "#a9552b", 50, 17);
tongue(322, 433, 18, "#7a3520", 44, 21);
tongue(506, 425, 26, "#a9552b", 46, 15);
tongue(196, 508, 24, "#7a3520", 52, -18);
tongue(346, 514, 19, "#42251f", 46, -14);
tongue(688, 511, 27, "#7a3520", 50, -20);
tongue(768, 518, 17, "#42251f", 42, -12);
tongue(238, 706, 21, "#42251f", 48, 17);
tongue(430, 712, 26, "#7a3520", 42, 14);
tongue(612, 704, 18, "#42251f", 44, 20);
// ---- rust running down out of the two upper bands, leading the eye to the surge
drip(300, 424, 44, "#a9552b", 1.5);
drip(386, 419, 72, "#7a3520", 1.2);
drip(466, 428, 33, "#a9552b", 1.0);
drip(528, 423, 68, "#7a3520", 1.7);
drip(544, 430, 54, "#42251f", 1.1);
drip(214, 166, 26, "#cbb391", 1.0);
drip(656, 168, 38, "#cbb391", 1.3);
drip(112, 703, 41, "#42251f", 1.4);
drip(742, 708, 29, "#42251f", 1.0);
// ---- oxidised green, rationed: a cold flare at the surge and one echo far up the picture
brush.noStroke();
brush.fillBleed(0.5, "out");
brush.fillTexture(0.45, 0.5);
brush.fill("#5f6a58", 128);
brush.circle(548, 512, 6.6, true);
brush.fill("#5f6a58", 104);
brush.circle(566, 527, 4.8, true);
brush.fill("#5f6a58", 116);
brush.circle(519, 503, 5.4, true);
brush.fill("#5f6a58", 84);
brush.circle(592, 540, 7.4, true);
brush.fill("#5f6a58", 92);
brush.circle(714, 148, 4.2, true);
brush.set("2B", "#5f6a58", 1.2);
brush.line(534, 519, 556, 522);
brush.line(572, 508, 583, 515);
// ---- last: fine broken scratches where an edge has hardened against the paper
brush.noFill();
brush.set("rotring", "#42251f", 0.9);
brush.line(214, 522, 288, 519);
brush.line(414, 531, 448, 527);
brush.line(690, 706, 748, 709);
brush.set("rotring", "#7a3520", 0.8);
brush.line(72, 302, 134, 305);
brush.line(318, 415, 366, 418);
brush.set("rotring", "#a9552b", 0.7);
brush.line(486, 165, 528, 163);
}