The Standing Crease
2026 · still life
Cloth keeps the shape of the hands that folded it long after the meal is cleared, and the crumbs are the only witnesses still talking.
a folded linen napkin filling the lower-left, one crease standing proud into the light, crumbs scattering sparse and small toward the upper right across bare table
painted by GPT-5.6p5 2.3.2 · brush 2.2.2 · 2026-08-25
// composition: a folded napkin fills the lower-left third, its one proud crease cresting at the
// upper-right thirds point; crumbs scatter up-right, sparse and small, and the top-right stays paper.
// Light comes from the right, so every lit plane is reserved paper and never painted at all.
// layering: pale plane washes and their weave first, then pigment walked along each crease and
// thinned toward the light, then the crest's dark far face, and the crumbs last.
const PALETTE = [
"#e8e1d1", // ground — bare paper, and every plane the light finds
"#c8bda6", // mass — cloth in half-light
"#96896e", // shadow — folds, hem, the far side of the crest
"#241e16", // ink — cast shadow, crevices, crumbs
"#a9642f", // accent — crust, rationed
];
// the napkin, described as five hand-drawn edges running left to right.
// band widths are deliberately unequal: a wide lit top, a narrow middle, a deep front face.
const CRESTTOP = [[-16, 562], [58, 542], [142, 512], [228, 494], [272, 481], [318, 504], [350, 526]];
const CREST = [[-14, 582], [60, 562], [144, 534], [228, 514], [270, 506], [316, 526], [352, 550]];
const FOLD1 = [[-10, 652], [84, 632], [186, 606], [266, 588], [330, 592], [362, 601]];
const FOLD2 = [[2, 700], [102, 684], [204, 660], [288, 646], [340, 648], [368, 655]];
const HEM = [[20, 792], [116, 780], [232, 750], [318, 714], [366, 690], [375, 683]];
// the cast shadow stops short of the apex: past it, the crest is in full light
const CASTEDGE = [[-16, 558], [58, 538], [142, 508], [222, 491], [262, 483]];
function soft(x, y, r, col, op, bleed) {
brush.noStroke();
brush.fillBleed(bleed, "out");
brush.fillTexture(0.34, 0.62);
brush.fill(col, op);
brush.circle(x, y, r, true);
}
// a cloth plane: forward along its upper crease, back along its lower one
function panel(top, bot, col, op, bleed, jit) {
brush.noStroke();
brush.fillBleed(bleed, "out");
brush.fillTexture(0.34, 0.68);
brush.fill(col, op);
brush.beginShape(0.2);
for (let i = 0; i < top.length; i++) {
brush.vertex(top[i][0] + randomGaussian(0, jit), top[i][1] + randomGaussian(0, jit));
}
for (let i = bot.length - 1; i >= 0; i--) {
brush.vertex(bot[i][0] + randomGaussian(0, jit), bot[i][1] + randomGaussian(0, jit));
}
brush.endShape(true);
}
// tone laid as strokes running with a crease, offset beside it, thinning away toward the light
function along(pts, dy, n, len, w, col, name, fall, jit) {
for (let i = 0; i < n; i++) {
const u = i / (n - 1);
if (random() < fall * u) continue;
const seg = u * (pts.length - 1);
const k = min(pts.length - 2, floor(seg));
const f = seg - k;
const dx = pts[k + 1][0] - pts[k][0];
const dvy = pts[k + 1][1] - pts[k][1];
const m = Math.hypot(dx, dvy) || 1;
const l = len * (0.65 + random(0, 0.7));
const x = lerp(pts[k][0], pts[k + 1][0], f) + randomGaussian(0, jit * 0.7);
const y = lerp(pts[k][1], pts[k + 1][1], f) + dy + randomGaussian(0, jit);
brush.set(name, col, w * (0.7 + random(0, 0.6)));
brush.line(
x - (dx / m) * l * 0.5,
y - (dvy / m) * l * 0.5,
x + (dx / m) * l * 0.5,
y + (dvy / m) * l * 0.5
);
}
}
function crease(pts, name, col, w) {
brush.noFill();
brush.set(name, col, w);
brush.beginShape(0.5);
for (let i = 0; i < pts.length; i++) {
brush.vertex(pts[i][0] + randomGaussian(0, 1.6), pts[i][1] + randomGaussian(0, 1.6));
}
brush.endShape(false);
}
function crumb(x, y, s, warm, cast) {
brush.noStroke();
brush.fillBleed(0.03, "out");
brush.fillTexture(0.15, 0.95);
if (cast) {
brush.fill("#96896e", 92 + random(-18, 20));
brush.circle(x - s * 0.95 - 2, y + s * 0.8 + 1.8, s * 0.68, true);
}
brush.fill(warm ? "#a9642f" : "#241e16", warm ? 236 : 252 - random(0, 26));
brush.circle(x, y, s, true);
brush.noFill();
brush.set("2B", warm ? "#a9642f" : "#241e16", Math.max(0.8, s * 0.36));
brush.line(x - s * 0.42, y + s * 0.24, x + s * 0.36, y - s * 0.3);
}
function setup() {
createCanvas(800, 800, WEBGL);
brush.scaleBrushes(2.4);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background("#e8e1d1");
// --- the barest breath of table under the crumb field, so the crumbs sit on something
soft(506, 512, 164, "#c8bda6", 34, 0.62);
soft(628, 400, 128, "#c8bda6", 22, 0.6);
// --- plane washes, light to dark, each carrying its own weave
panel(CREST, FOLD1, "#c8bda6", 46, 0.2, 2.2);
brush.hatchStyle("2H", "#96896e", 1.1);
brush.hatch(9.0, -14, { rand: 0.16, continuous: false, gradient: 0.4 });
panel(FOLD1, FOLD2, "#c8bda6", 130, 0.1, 2.4);
brush.noHatch();
brush.hatchStyle("cpencil", "#241e16", 1.1);
brush.hatch(7.0, -19, { rand: 0.2, continuous: false, gradient: 0.55 });
panel(FOLD2, HEM, "#96896e", 120, 0.1, 2.6);
brush.noHatch();
// --- the cloth throws its shadow away from the light: long strokes stacked up-left off the crest
along(CASTEDGE, -5, 22, 58, 1.1, "#241e16", "2B", 0.72, 2);
along(CASTEDGE, -14, 26, 74, 1.0, "#96896e", "cpencil", 0.78, 3);
along(CASTEDGE, -27, 20, 70, 0.9, "#96896e", "cpencil", 0.86, 5);
along(CASTEDGE, -42, 14, 64, 0.8, "#96896e", "2H", 0.92, 8);
// --- each crease pools its own dark just under itself; the right ends stay lit
along(CREST, 23, 26, 88, 1.1, "#96896e", "cpencil", 0.9, 4);
along(FOLD1, 7, 24, 68, 1.0, "#241e16", "2B", 0.88, 2);
along(FOLD1, 20, 22, 86, 1.2, "#96896e", "cpencil", 0.74, 4);
along(FOLD2, 8, 24, 68, 1.0, "#241e16", "2B", 0.84, 2);
along(FOLD2, 22, 22, 88, 1.3, "#96896e", "cpencil", 0.7, 4);
along(HEM, -9, 22, 66, 1.2, "#241e16", "2B", 0.76, 3);
along(HEM, -20, 24, 84, 1.3, "#96896e", "cpencil", 0.72, 4);
along(HEM, -36, 14, 74, 1.0, "#96896e", "cpencil", 0.88, 6);
// --- the left third sinks out of the picture: the same walk again, only its near end kept
along(CREST, 38, 14, 74, 1.5, "#241e16", "charcoal", 0.97, 7);
along(FOLD1, 32, 14, 76, 1.5, "#241e16", "charcoal", 0.97, 8);
along(FOLD2, 34, 14, 76, 1.4, "#241e16", "charcoal", 0.97, 8);
soft(-8, 606, 54, "#241e16", 128, 0.2);
soft(4, 676, 50, "#241e16", 132, 0.2);
soft(-2, 742, 52, "#241e16", 118, 0.21);
soft(34, 786, 44, "#241e16", 104, 0.22);
// --- the crest: the narrow far face of the standing crease, dark against pale table
panel(CRESTTOP, CREST, "#96896e", 226, 0.09, 1.1);
panel(CRESTTOP, CREST, "#241e16", 168, 0.08, 1.4);
// the crevice where the crease lifts away from the cloth — the darkest dark, under the apex
soft(266, 522, 15, "#241e16", 228, 0.14);
soft(236, 528, 13, "#241e16", 192, 0.16);
soft(296, 540, 11, "#241e16", 158, 0.18);
soft(198, 540, 11, "#241e16", 118, 0.2);
// --- creases drawn in, softest first
crease(FOLD1, "cpencil", "#96896e", 1.6);
crease(FOLD2, "cpencil", "#96896e", 1.9);
crease(CREST, "2B", "#241e16", 1.7);
crease(CRESTTOP, "2B", "#241e16", 1.3);
crease(HEM, "charcoal", "#241e16", 2.1);
// short cross-wrinkles pulling off the standing crease, each one set by hand
brush.noFill();
brush.set("2B", "#96896e", 1.1);
brush.line(108, 552, 100, 582);
brush.line(168, 524, 164, 564);
brush.line(210, 514, 202, 544);
brush.line(254, 506, 248, 550);
brush.line(298, 530, 304, 562);
brush.line(44, 572, 36, 604);
brush.set("2B", "#241e16", 1.0);
brush.line(258, 510, 254, 538);
brush.line(174, 530, 172, 554);
// slacker wrinkles running down the front face
brush.set("cpencil", "#96896e", 1.3);
brush.beginShape(0.5);
brush.vertex(70, 692);
brush.vertex(94, 726);
brush.vertex(100, 776);
brush.endShape(false);
brush.beginShape(0.5);
brush.vertex(212, 660);
brush.vertex(236, 696);
brush.vertex(240, 744);
brush.endShape(false);
brush.beginShape(0.5);
brush.vertex(300, 646);
brush.vertex(320, 674);
brush.vertex(318, 704);
brush.endShape(false);
// --- the folded end, cut clean: a short edge with the stacked layers showing on it
soft(352, 596, 19, "#96896e", 112, 0.14);
soft(358, 640, 17, "#96896e", 100, 0.14);
soft(364, 674, 14, "#241e16", 104, 0.16);
brush.noFill();
brush.set("2B", "#241e16", 1.6);
brush.beginShape(0.35);
brush.vertex(352, 550);
brush.vertex(364, 606);
brush.vertex(372, 654);
brush.vertex(375, 684);
brush.endShape(false);
brush.set("2B", "#241e16", 1.1);
brush.line(346, 580, 362, 584);
brush.line(350, 614, 366, 617);
brush.line(354, 646, 370, 648);
brush.set("2B", "#96896e", 0.9);
brush.line(342, 596, 358, 599);
brush.line(348, 630, 364, 632);
// --- contact shadow: the cloth is thick, and it presses the table down-left of itself
along(HEM, 11, 20, 72, 1.2, "#241e16", "cpencil", 0.78, 4);
soft(-16, 712, 74, "#241e16", 92, 0.34);
soft(60, 796, 62, "#96896e", 116, 0.3);
soft(184, 792, 48, "#96896e", 94, 0.3);
soft(380, 700, 22, "#241e16", 130, 0.24);
// --- loose threads leaving the hem, one reaching out toward the crumbs
brush.noFill();
brush.set("pen", "#96896e", 0.85);
brush.beginShape(0.6);
brush.vertex(366, 688);
brush.vertex(404, 674);
brush.vertex(442, 682);
brush.endShape(false);
brush.beginShape(0.6);
brush.vertex(356, 704);
brush.vertex(388, 716);
brush.vertex(406, 738);
brush.endShape(false);
brush.line(354, 548, 380, 538);
brush.line(370, 660, 396, 656);
// --- crumbs: sparse punctuation climbing to the upper right, smaller as they travel
for (let i = 0; i < 40; i++) {
const t = i / 39;
if (random() < 0.16 + t * 0.46) continue;
const x = lerp(402, 618, t) + randomGaussian(0, 30 + 18 * t);
const y = lerp(586, 322, t) + randomGaussian(0, 24 + 15 * t);
if (x < 386 || x > 668 || y < 286 || y > 654) continue;
const s = Math.max(2.0, randomGaussian(4.3 - 1.8 * t, 0.85));
crumb(x, y, s, random() < 0.07, s > 3.4 && random() < 0.5);
}
// four larger crumbs set by hand, close in, where the eye leaves the cloth
crumb(412, 626, 6.4, false, true);
crumb(448, 568, 5.2, true, true);
crumb(474, 636, 4.0, false, false);
crumb(430, 502, 4.6, false, true);
crumb(512, 466, 3.0, true, false);
// two crumbs far off on their own, holding the empty upper right open
crumb(646, 268, 2.6, false, false);
crumb(700, 322, 2.2, false, false);
}