Lee Slope
2026 · landscape
At this hour a dune is two facts only: the side the sun touches and the side it gave up on.
desert dunes at low sun, hard light from the right, shadows pouring down the lee slopes
painted by Claude Opus 5p5 2.3.2 · brush 2.2.2 · 2026-08-24
// composition: one great S-ridge enters at the left edge, crests near x=300, and dives to the lower-right
// corner; a second shorter ridge crosses it rising from the bottom edge. the top 40% of the sheet stays empty sky.
// layering: pale under-wash to seat the dunes, umber spray on the lee slopes, sparse ochre spray on the lit sides, rust crest lines last.
const PALETTE = [
"#eadfc6", // pale sand paper / sky
"#f2ead4", // lit-slope highlight sand
"#c98d3f", // ochre mid sand
"#4f3a28", // deep shadow umber — the mass
"#b0562e", // rust crest accent
];
const RIDGE_A = [
[-20, 432], [110, 392], [250, 352], [370, 356], [470, 398],
[560, 452], [650, 520], [730, 590], [820, 648],
];
const RIDGE_B = [
[235, 820], [300, 700], [380, 615], [470, 560], [575, 528], [690, 505],
];
function pathPoint(pts, t) {
const ft = t * (pts.length - 1);
const i = constrain(floor(ft), 0, pts.length - 2);
const u = ft - i;
return [lerp(pts[i][0], pts[i + 1][0], u), lerp(pts[i][1], pts[i + 1][1], u)];
}
function crest(pts, w1, w2) {
brush.noFill();
brush.set("2B", PALETTE[4], w1);
brush.beginShape(0.4);
for (let i = 0; i < pts.length; i++) {
brush.vertex(pts[i][0] + random(-3, 3), pts[i][1] + random(-2, 2));
}
brush.endShape(false);
brush.set("pen", PALETTE[4], w2);
brush.beginShape(0.4);
for (let i = 0; i < pts.length; i++) {
brush.vertex(pts[i][0] + random(-2, 2), pts[i][1] - 2 + random(-1.5, 1.5));
}
brush.endShape(false);
}
function setup() {
createCanvas(800, 800, WEBGL);
brush.scaleBrushes(2.3);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background(PALETTE[0]);
// one broad pale under-wash below the main ridge, so the dunes sit in warmth
brush.noStroke();
brush.fillBleed(0.6, "out");
brush.fillTexture(0.4, 0.5);
brush.fill(PALETTE[2], 32);
brush.beginShape(0.4);
brush.vertex(-20, 440);
brush.vertex(250, 366);
brush.vertex(470, 408);
brush.vertex(650, 530);
brush.vertex(820, 656);
brush.vertex(820, 820);
brush.vertex(-20, 820);
brush.endShape(true);
// lee-slope shadow of the main ridge: hard light from the right throws grain down-left
for (let i = 0; i < 240; i++) {
const t = random(0.02, 0.98);
const p = pathPoint(RIDGE_A, t);
const d = abs(randomGaussian(0, 1));
const len = 16 + d * 48;
brush.set("spray", PALETTE[3], max(0.4, random(0.9, 2.1) - d * 0.28));
brush.line(
p[0] + random(-8, 8), p[1] + 2 + d * 5,
p[0] - len * (0.7 + random(0.25)), p[1] + len * (0.5 + random(0.2))
);
}
// crossing ridge: shorter, its shadow also falling left, denser near the crossing
for (let i = 0; i < 150; i++) {
const t = random(0.05, 0.95);
const p = pathPoint(RIDGE_B, t);
const d = abs(randomGaussian(0, 1));
const len = 12 + d * 36;
brush.set("spray", PALETTE[3], max(0.4, random(0.8, 1.8) - d * 0.25));
brush.line(
p[0] + random(-6, 6), p[1] + 2 + d * 4,
p[0] - len * (0.8 + random(0.2)), p[1] + len * (0.35 + random(0.2))
);
}
// lit slopes: sparse ochre grain rising to the right of each crest
for (let i = 0; i < 85; i++) {
const onA = random(1) < 0.62;
const p = pathPoint(onA ? RIDGE_A : RIDGE_B, random(0.05, 0.95));
const len = random(8, 34);
brush.set("spray", PALETTE[2], random(0.6, 1.2));
brush.line(p[0] + random(-5, 5), p[1] + random(1, 8), p[0] + len * 0.75, p[1] + len * 0.35);
}
for (let i = 0; i < 40; i++) {
const p = pathPoint(RIDGE_A, random(0.05, 0.9));
const len = random(6, 20);
brush.set("spray", PALETTE[1], random(0.6, 1.1));
brush.line(p[0] + random(-4, 4), p[1] + random(1, 5), p[0] + len * 0.7, p[1] + len * 0.3);
}
// faint wind ripples in the lower-left flat
brush.noFill();
for (let i = 0; i < 6; i++) {
const rx = random(40, 210);
const ry = random(600, 770);
brush.set("2H", PALETTE[2], random(0.5, 0.9));
brush.beginShape(0.6);
brush.vertex(rx, ry);
brush.vertex(rx + random(40, 80), ry - random(4, 12));
brush.vertex(rx + random(100, 150), ry + random(-4, 8));
brush.endShape(false);
}
// rust crest lines, drawn last so they cut both grain fields
crest(RIDGE_A, 1.5, 0.7);
crest(RIDGE_B, 1.1, 0.6);
}