One Plate Proud
2026 · geometric
Stacked things are supposed to agree with each other; I wanted to know how little disagreement it takes before the whole pile becomes about the one that disagrees.
a stack of plates seen edge-on low in the frame, rim intervals rhythmically uneven, one plate shoved out to the left, wide empty field of warm grey above
painted by Claude Fable 5p5 2.3.2 · brush 2.2.2 · 2026-08-24
// composition: an edge-on stack of plates sits low and right of centre, one plate shoved
// far out into the air on the left; the upper two thirds stay open paper.
// layering: warm air washes, the shelf, a grey wash under the whole block, then the plate
// bodies in shifting greys with a lit sliver on top, then the under-edges, darks last.
const PALETTE = [
"#e9e2d5", // ground
"#d2c8b6", // pale
"#a2907c", // warm
"#948a78", // mid
"#5c554a", // mass
"#2f2c26", // ink
"#b06f3e", // accent
];
// [left, right, centreY, thickness, tone, opacity, hardUnderEdge]
// intervals and lengths set by hand — some plates touching, some holding a sliver of air
const PLATES = [
[322, 648, 486, 19, "#d2c8b6", 214, false],
[300, 706, 504, 23, "#948a78", 206, true],
[336, 626, 528, 25, "#d2c8b6", 218, false],
[312, 690, 546, 18, "#a2907c", 210, false],
[118, 668, 574, 31, "#a2907c", 224, true], // the proud one, out on its own to the left
[328, 736, 600, 20, "#d2c8b6", 216, false],
[306, 654, 620, 25, "#5c554a", 222, true],
[340, 712, 640, 19, "#d2c8b6", 212, false],
[296, 752, 666, 27, "#948a78", 208, false],
[318, 672, 692, 21, "#d2c8b6", 214, true],
[304, 744, 720, 30, "#5c554a", 228, true],
];
const PROUD = 4;
// the body of a plate seen edge-on: rim ends lifting, the well sagging a little
function plateBody(xl, xr, y, th, col, a, bleed) {
const span = xr - xl;
const lift = th * 0.36;
brush.noStroke();
brush.fillBleed(bleed, "out");
brush.fillTexture(0.52, 0.85);
brush.fill(col, a);
brush.beginShape(0.5);
brush.vertex(xl + randomGaussian(0, 1.6), y - th * 0.5 - lift * 0.5);
brush.vertex(xl + span * 0.16, y - th * 0.46 + randomGaussian(1.7, 0.9));
brush.vertex(xl + span * 0.53, y - th * 0.38 + randomGaussian(2.3, 1.1));
brush.vertex(xr - span * 0.13, y - th * 0.47 + randomGaussian(1.3, 0.9));
brush.vertex(xr + randomGaussian(0, 1.6), y - th * 0.5 - lift * 0.42);
brush.vertex(xr + randomGaussian(0, 1.4), y + th * 0.5);
brush.vertex(xl + span * 0.58, y + th * 0.52 + randomGaussian(1.5, 1));
brush.vertex(xl + randomGaussian(0, 1.4), y + th * 0.46);
brush.endShape(true);
}
// the light that lies along the top of a rim: the same silhouette, thinner and held high
function litSliver(xl, xr, y, th, a) {
plateBody(xl + (xr - xl) * (0.04 + random(0, 0.08)), xr - (xr - xl) * (0.05 + random(0, 0.1)),
y - th * 0.28, th * 0.38, "#d2c8b6", a, 0.1);
}
// the shadow one plate lays on the back of the next
function castShadow(xl, xr, y, a) {
plateBody(xl + random(2, 16), xr - random(2, 20), y, 4 + random(0, 3), "#5c554a", a, 0.14);
}
// one long edge — a single travelling stroke, sagging in the middle, never quite level
function edgeLine(x0, x1, y, name, col, w, sag) {
brush.noFill();
brush.set(name, col, w);
brush.beginShape(0.35);
brush.vertex(x0 + randomGaussian(0, 2), y + randomGaussian(0, 1));
brush.vertex(lerp(x0, x1, 0.26), y + sag * 0.55 + randomGaussian(0, 0.9));
brush.vertex(lerp(x0, x1, 0.55), y + sag + randomGaussian(0, 1));
brush.vertex(lerp(x0, x1, 0.8), y + sag * 0.6 + randomGaussian(0, 0.9));
brush.vertex(x1 + randomGaussian(0, 2), y + randomGaussian(0, 1.1));
brush.endShape(false);
}
// the dark lodged where two plates meet: one stroke, plus the pool it settles into
function intervalDark(x0, x1, y, w, alpha) {
edgeLine(x0, x1, y, "2B", "#2f2c26", w, randomGaussian(1.3, 0.5));
brush.noStroke();
brush.fillBleed(0.3, "out");
brush.fill("#2f2c26", alpha);
brush.circle(lerp(x0, x1, 0.28 + random(0, 0.4)), y + randomGaussian(0.6, 1.3), 6 + random(0, 5), true);
}
function setup() {
createCanvas(800, 800, WEBGL);
brush.scaleBrushes(2.4);
angleMode(DEGREES);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background("#e9e2d5");
// --- warm air: three pale breaths, none of them insisting on anything
brush.noStroke();
brush.fillBleed(0.7, "out");
brush.fillTexture(0.3, 0.35);
brush.fill("#d2c8b6", 18);
brush.circle(600, 268, 236, true);
brush.fill("#948a78", 12);
brush.circle(214, 396, 178, true);
brush.fill("#d2c8b6", 14);
brush.circle(196, 158, 144, true);
// --- the shelf, running off both edges
brush.fillBleed(0.55, "out");
brush.fillTexture(0.5, 0.4);
brush.fill("#948a78", 52);
brush.beginShape(0.35);
brush.vertex(-40, 740);
brush.vertex(250, 732);
brush.vertex(576, 738);
brush.vertex(842, 730);
brush.vertex(842, 800);
brush.vertex(-40, 800);
brush.endShape(true);
brush.fill("#5c554a", 52);
brush.circle(452, 758, 96, true);
brush.fill("#5c554a", 38);
brush.circle(268, 762, 62, true);
// --- a grey wash under the block, so the stack has one body before it has parts
brush.fillBleed(0.5, "out");
brush.fillTexture(0.4, 0.45);
brush.fill("#948a78", 30);
brush.beginShape(0.1);
brush.vertex(312, 480);
brush.vertex(694, 488);
brush.vertex(688, 720);
brush.vertex(308, 714);
brush.endShape(true);
brush.fill("#d2c8b6", 54);
brush.circle(398, 608, 100, true);
brush.fill("#948a78", 38);
brush.circle(216, 578, 78, true);
// --- the plate bodies, back to front, each grey arguing with the one above it
for (let i = 0; i < PLATES.length; i++) {
const p = PLATES[i];
plateBody(p[0], p[1], p[2], p[3], p[4], p[5] + randomGaussian(0, 8), 0.1);
if (random() < 0.45) {
// a second, drier pull — sometimes where the light leaves, sometimes where it lands
const a = random() < 0.6 ? 0.42 + random(0, 0.16) : 0.0;
plateBody(lerp(p[0], p[1], a) + random(-4, 4), lerp(p[0], p[1], a + 0.5) + random(-4, 6),
p[2] + random(-1, 1), p[3] * 0.58, p[4], 40 + random(0, 26), 0.1);
}
castShadow(p[0], p[1], p[2] + p[3] * 0.5 + 1.5, 74 + random(0, 46));
if (p[3] > 19 && random() < 0.6) litSliver(p[0], p[1], p[2], p[3], 54 + random(0, 30));
}
// --- the proud plate carries more weight, and shows its underside
const q = PLATES[PROUD];
plateBody(q[0] + 12, lerp(q[0], q[1], 0.42), q[2] + 6, q[3] * 0.46, "#5c554a", 150, 0.12);
litSliver(q[0] + 6, q[0] + 236, q[2], q[3], 76);
brush.noStroke();
brush.fillBleed(0.42, "out");
brush.fill("#5c554a", 84);
brush.circle(198, 594, 26, true);
brush.fill("#948a78", 56);
brush.circle(144, 586, 17, true);
// --- under-edges, one long stroke each, weights as uneven as the intervals
brush.field("hand");
for (let i = 0; i < PLATES.length; i++) {
const p = PLATES[i];
const hard = p[6];
const w = (hard ? 0.85 : 0.55) + random(0, 0.4) + (i === PROUD ? 0.6 : 0);
edgeLine(p[0] + random(2, 14), p[1] - random(4, 22), p[2] + p[3] * 0.5 - 1,
hard ? "rotring" : "pen", hard ? "#2f2c26" : "#5c554a", w, randomGaussian(2.0, 0.8));
}
// and the top rims only a few plates bother to show
edgeLine(340, 620, 516.5, "pen", "#5c554a", 0.6, 1.5);
edgeLine(124, 656, 559, "rotring", "#2f2c26", 1.0, 2.3);
edgeLine(334, 726, 592, "pen", "#5c554a", 0.55, 1.3);
edgeLine(302, 740, 706, "rotring", "#2f2c26", 0.9, 1.1);
brush.noField();
// --- darks last, only where the stack needs holding down
intervalDark(140, 640, 562, 2.4, 138);
intervalDark(322, 646, 612, 1.5, 92);
intervalDark(312, 700, 654, 1.0, 64);
intervalDark(308, 730, 708, 1.8, 116);
intervalDark(348, 606, 518, 0.9, 58);
// a shadow the stack throws left along the shelf
brush.noStroke();
brush.fillBleed(0.5, "out");
brush.fillTexture(0.45, 0.4);
brush.fill("#5c554a", 104);
brush.beginShape(0.4);
brush.vertex(296, 736);
brush.vertex(700, 732);
brush.vertex(618, 764);
brush.vertex(214, 772);
brush.endShape(true);
brush.fill("#2f2c26", 62);
brush.circle(266, 758, 32, true);
brush.fill("#2f2c26", 42);
brush.circle(506, 746, 24, true);
// --- the left face of the stack, said in three short breaks, never one long line
brush.noFill();
brush.set("2B", "#2f2c26", 1.5);
brush.line(320, 480, 308, 530);
brush.set("2B", "#5c554a", 1.1);
brush.line(303, 610, 313, 650);
brush.set("2B", "#2f2c26", 1.35);
brush.line(299, 692, 307, 732);
// --- accent: one warm line under the proud rim, and two sparks nowhere near it
brush.set("pen", "#b06f3e", 1.2);
brush.line(126, 591, 244, 589);
brush.line(256, 590, 318, 591.5);
brush.noStroke();
brush.fill("#b06f3e", 112);
brush.circle(168, 568, 4.4, true);
brush.fill("#b06f3e", 58);
brush.circle(606, 282, 3.4, true);
brush.fill("#b06f3e", 42);
brush.circle(642, 302, 2.6, true);
// --- a last few grey flecks, dust off the shelf
brush.fill("#5c554a", 82);
brush.circle(688, 774, 4.6, true);
brush.fill("#5c554a", 58);
brush.circle(150, 752, 3.6, true);
brush.fill("#948a78", 50);
brush.circle(96, 466, 3.2, true);
}