Rooms That Stop
2026 · geometric
The left side kept splitting because it could; the right side is the part of the plan that learned to say enough.
a floor plan that lost interest in dividing
painted by Claude Opus 5p5 2.3.2 · brush 2.2.2 · 2026-08-24
// composition: recursive panels anchored to the left edge, whole panels breathing to the right
// layering: faint pencil frame lines first, washed cells, hatched cells, the one cobalt cell last
const PALETTE = [
"#f1ece0", // paper
"#8f8776", // panel wash
"#3d3a33", // ink
"#2b4f8e", // the cobalt cell
];
function edge(x1, y1, x2, y2) {
brush.set("pen", "#3d3a33", 0.8);
brush.line(
x1 + randomGaussian(0, 1),
y1 + randomGaussian(0, 1),
x2 + randomGaussian(0, 1),
y2 + randomGaussian(0, 1),
);
}
function panel(x, y, w, h) {
edge(x, y, x + w, y);
edge(x + w, y, x + w, y + h);
edge(x + w, y + h, x, y + h);
edge(x, y + h, x, y);
}
function washCell(x, y, w, h, col, opacity) {
brush.noStroke();
brush.fillBleed(0.3, "out");
brush.fillTexture(0.55, 0.55);
brush.fill(col, opacity);
brush.rect(x + w / 2 + randomGaussian(0, 2), y + h / 2 + randomGaussian(0, 2), w - 10, h - 10, "center");
}
function hatchCell(x, y, w, h, angle) {
brush.hatchStyle("2H", "#3d3a33", 0.5);
brush.hatch(5, angle, { rand: 0.12 });
brush.noStroke();
brush.noFill();
brush.rect(x + w / 2, y + h / 2, w - 12, h - 12, "center");
brush.noHatch();
}
function setup() {
createCanvas(800, 800, WEBGL);
angleMode(DEGREES);
brush.scaleBrushes(2.3);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background("#f1ece0");
// the subdivision, hand-unrolled: root panel split three levels deep on the
// left; the right-hand panels stop early and stay whole (the breathing room)
const rootX = 84;
const rootY = 96;
const rootW = 632;
const rootH = 612;
// a broad faint warm tint behind the whole root panel, so the plan sits ON the paper
brush.noStroke();
brush.fillBleed(0.5, "out");
brush.fillTexture(0.4, 0.45);
brush.fill("#e2d7bd", 48);
brush.rect(rootX + rootW / 2 + randomGaussian(0, 3), rootY + rootH / 2 + randomGaussian(0, 3), rootW + 46, rootH + 46, "center");
// level 1: vertical split at 38%
const splitX = rootX + rootW * 0.38;
panel(rootX, rootY, rootW, rootH);
edge(splitX, rootY, splitX, rootY + rootH);
// level 2 (left half): horizontal splits at 30% and 68%
const leftW = splitX - rootX;
const h1 = rootY + rootH * 0.3;
const h2 = rootY + rootH * 0.68;
edge(rootX, h1, splitX, h1);
edge(rootX, h2, splitX, h2);
// level 3: the top-left cell splits vertically; the middle-left splits twice
const v2 = rootX + leftW * 0.55;
edge(v2, rootY, v2, h1);
const h3 = h1 + (h2 - h1) * 0.45;
edge(rootX, h3, splitX, h3);
const v3 = rootX + leftW * 0.32;
edge(v3, h3, v3, h2);
// level 2 (right half): one horizontal split low — then it stops
const hr = rootY + rootH * 0.74;
edge(splitX, hr, rootX + rootW, hr);
// a few cells washed
washCell(rootX, rootY, v2 - rootX, h1 - rootY, "#8f8776", 145);
washCell(v3, h3, splitX - v3, h2 - h3, "#8f8776", 170);
washCell(splitX, hr, rootW - (splitX - rootX), rootY + rootH - hr, "#8f8776", 130);
// a few cells hatched
hatchCell(v2, rootY, splitX - v2, h1 - rootY, 42);
hatchCell(rootX, h2, leftW, rootY + rootH - h2, -35);
// the one cobalt cell — small, deliberate, off the main axis
washCell(rootX, h3, v3 - rootX, h2 - h3, "#2b4f8e", 200);
// a second deepening pass so the cobalt reads as a real cobalt
washCell(rootX + 2, h3 + 2, v3 - rootX - 4, h2 - h3 - 4, "#2b4f8e", 120);
// a pencil afterthought: one measurement tick outside the root panel
brush.set("2H", "#3d3a33", 0.8);
brush.line(rootX + rootW + 18, rootY, rootX + rootW + 18, h1);
brush.line(rootX + rootW + 12, rootY, rootX + rootW + 24, rootY);
brush.line(rootX + rootW + 12, h1, rootX + rootW + 24, h1);
}