Chicago
2026 · city
Cloud Gate is nothing but reflection, so I left its upper third as bare paper — that is the sky, the brightest thing in the print — and hung the Michigan Avenue streetwall inside it upside down, squeezed toward the tips. Only the omphalos underside is truly dark.
cloud gate on the at&t plaza, the loop hanging upside down in the polished steel
painted by Claude Opus 5p5 2.3.2 · brush 2.2.2 · 2026-08-30
// composition: Cloud Gate — the Bean — on the AT&T Plaza in Millennium Park, seen standing on the
// granite at eye level with the Michigan Avenue streetwall a block behind. The kidney sits well LEFT
// of the print's centre; the streetwall runs on past it to the right and steps down as it goes, so the
// weight is carried right by the wall and by the crowd bunched at the Bean's right foot. The print runs
// wide and low; the sheet above it is bare paper.
// four facts I will not bend:
// proportion — 20 m long, 13 m high, 10 m deep: half again as wide as it is tall. The omphalos arch
// beneath is 3.7 m, a little over a quarter of the height — high enough to walk through upright.
// ordering — the Bean is in front of everything and OCCLUDES the streetwall; every façade is cut off
// at the steel's contour. The wall then returns inside the steel, inverted and compressed.
// material — seamless polished stainless steel. It has no windows, no seams, no ornament, no plinth:
// it is nothing but reflection. The sky it reflects is the BRIGHTEST thing in the print, so the
// upper third of the Bean is bare paper. Below that the Loop hangs upside down. Only the underside
// at the omphalos is genuinely dark.
// site — it stands directly on open granite paving at plaza level. No water, no railing, no pedestal.
// counts drawn: 11 façades of 11 different widths, heights, inks and window rhythms, one of them the
// notched diamond of the Crain Communications building, one of them stepped back to a mast, with a
// single 12 px gap of bare paper between two blocks; 11 of those façades again inside the steel,
// inverted and squeezed; 1 arch on 2 feet; 10 people, 2 of them under the arch.
// layering: paper flecks, the streetwall clipped to the Bean's silhouette, the plaza, then the steel —
// bare paper sky, the reflected horizon, the inverted city, the mid-tone plaza, the dark underside —
// then the crowd, then the two highlight lines and one gilt nick.
const PALETTE = [
"#efe6d2", // paper — warm cream sheet, and the sky the steel reflects
"#d9cfb6", // stone — Michigan Avenue limestone and the granite plaza
"#2f3a44", // ink — slate-navy line, window slots, the shade under the steel
"#5d7f88", // verdigris — glass curtain wall, and the steel's own grey sheen
"#a8452c", // brick — the older terracotta fronts on the avenue
"#c9a24a", // gilt — one nick on the tallest mast, the only warm accent
];
const BASE = 578; // the plaza line where the Bean's two feet touch
const STREET = 546; // the foot of the streetwall, a block behind
const WL0 = 138, WL1 = 678; // the streetwall's two ends
const CX = 306, AL = 146, AR = 160, BY = 99, ARCH = 56, FOOT = 0.52;
const g = (m, sd, lo, hi) => constrain(randomGaussian(m, sd), lo, hi);
const shift = (pts, dx, dy) => pts.map((p) => [p[0] + dx, p[1] + dy]);
const box = (x, y, w, h) => [[x, y], [x + w, y], [x + w, y + h], [x, y + h]];
// a solid grainy plate — the letterpress solid. noStroke: a plate never carries the last pen's outline.
const plate = (pts, col, w, dist, ang, rnd) => {
brush.noStroke();
brush.noFill();
brush.hatchStyle("charcoal", col, w);
brush.hatch(dist, ang, { rand: rnd, continuous: false, gradient: 0 });
brush.polygon(pts);
brush.noHatch();
};
// a half-tone plate — two crossed cpencil passes, paper breathing through
const halfPlate = (pts, col, w, dist, ang) => {
brush.noStroke();
brush.noFill();
brush.hatchStyle("cpencil", col, w);
brush.hatch(dist, ang, { rand: 0.25, continuous: false, gradient: 0 });
brush.polygon(pts);
brush.hatch(dist * 1.12, ang - 84, { rand: 0.25, continuous: false, gradient: 0 });
brush.polygon(pts);
brush.noHatch();
};
// a fading plate — gradient hatch, dense at one edge, open at the other
const fadePlate = (pts, col, w, dist, ang, grad) => {
brush.noStroke();
brush.noFill();
brush.hatchStyle("charcoal", col, w);
brush.hatch(dist, ang, { rand: 0.3, continuous: false, gradient: grad });
brush.polygon(pts);
brush.noHatch();
};
const ink = (x1, y1, x2, y2, w, name, col) => {
brush.noFill();
brush.set(name || "rotring", col || PALETTE[2], w || 1.0);
brush.line(x1, y1, x2, y2);
};
// a dark window slot
const slot = (x, y, w, h) => plate(box(x, y, w, h), PALETTE[2], 1.1, 1.5, 90, 0.2);
// --- the Bean's section, measured from its own axis. u runs -AL (left tip) to +AR (right tip). ---
const beanTop = (u) => {
const a = u < 0 ? AL : AR;
const t = constrain(u / a, -1, 1);
return BASE - BY - BY * Math.sqrt(Math.max(0, 1 - t * t)) - 4 * t;
};
// the underside: down to a foot at |t| = FOOT, up over the omphalos between the feet, and curling
// clear of the plaza again beyond each foot toward the tips.
const beanBot = (u) => {
const a = u < 0 ? AL : AR;
const t = constrain(u / a, -1, 1);
const s = Math.abs(t);
const y = s <= FOOT
? BASE - ARCH * Math.cos((s / FOOT) * (Math.PI / 2))
: BASE - BY * (1 - Math.cos(((s - FOOT) / (1 - FOOT)) * (Math.PI / 2)));
return y - 4 * t;
};
const beanY = (u, f) => {
const a = beanTop(u), b = beanBot(u);
return a + (b - a) * f;
};
// a band of the steel between two contours — every plate on the Bean follows its curvature
const beanBand = (f0, f1, u0, u1, n) => {
const pts = [];
for (let i = 0; i <= n; i++) { const u = u0 + ((u1 - u0) * i) / n; pts.push([CX + u, beanY(u, f0)]); }
for (let i = n; i >= 0; i--) { const u = u0 + ((u1 - u0) * i) / n; pts.push([CX + u, beanY(u, f1)]); }
return pts;
};
// a ruled contour running along one level of the steel
const along = (f, u0, u1, n, w, name, col, skip) => {
for (let i = 0; i < n; i++) {
if (skip && i % 3 === 2) continue;
const ua = u0 + ((u1 - u0) * i) / n, ub = u0 + ((u1 - u0) * (i + 1)) / n;
ink(CX + ua, beanY(ua, f), CX + ub, beanY(ub, f), w, name, col);
}
};
// the steel occludes the wall: at x, the wall is only visible above this
const occl = (x) => {
const u = x - CX;
if (u <= -AL + 0.6 || u >= AR - 0.6) return STREET;
return Math.min(STREET, beanTop(u));
};
// the x-interval the steel covers at a given height, so a storey ruling can be cut around it
const beanSpan = (y) => {
let xl = 1e9, xr = -1e9;
for (let i = 0; i <= 56; i++) {
const u = -AL + ((AL + AR) * i) / 56;
if (beanTop(u) <= y) { if (CX + u < xl) xl = CX + u; if (CX + u > xr) xr = CX + u; }
}
return [xl - 1.5, xr + 1.5];
};
// a storey ruling, cut where the steel stands in front of it
const ruleTo = (x1, x2, y, w, name, col) => {
if (y < beanTop(0) - 1) { ink(x1, y, x2, y, w, name, col); return; }
const sp = beanSpan(y), xl = sp[0], xr = sp[1];
if (xr <= x1 || xl >= x2) { ink(x1, y, x2, y, w, name, col); return; }
if (x1 < xl - 2) ink(x1, y, Math.min(x2, xl), y, w, name, col);
if (x2 > xr + 2) ink(Math.max(x1, xr), y, x2, y, w, name, col);
};
// a façade's outline as a polygon whose foot is cut by the steel in front of it
const frontOf = (x, w, ty, base) => {
const pts = [[x, ty], [x + w, ty]];
for (let i = 12; i >= 0; i--) {
const xx = x + (w * i) / 12;
pts.push([xx, Math.min(base, occl(xx))]);
}
return pts;
};
// the streetwall folded into the steel: an x on the avenue becomes a u on the Bean's axis,
// bunching hard toward the tips
const uOf = (x) => {
const p = constrain((x - WL0) / (WL1 - WL0), 0, 1);
const s = 2 * p - 1;
return (s < 0 ? AL : AR) * 0.955 * Math.sin((s * Math.PI) / 2);
};
// [ x, width, parapet y, ink, rhythm (0 punched / 1 ribbon / 2 paired), storey pitch, bays, set back ]
const WALL = [
[138, 46, 420, 1, 0, 13, 3, 0], // a low limestone block closing the left end
[184, 34, 366, 4, 2, 11, 2, 0], // a narrow terracotta front, windows in pairs
[218, 64, 336, 3, 1, 8, 0, 0], // a glass slab, ribbon windows storey on storey
[282, 50, 302, 1, 0, 12, 4, 0], // the stepped-back tower — an upper block and a mast
[344, 72, 324, 1, 0, 9, 5, 0], // the wide flat-topped slab, a dense punched grid
[416, 32, 358, 4, 2, 12, 2, 1], // a narrow brick front, set back
[448, 58, 312, 1, 0, 11, 4, 0], // Crain Communications — the notched diamond top
[506, 46, 300, 2, 1, 9, 0, 2], // a dark bronze-glass tower, the deepest ink on the avenue
[552, 38, 400, 1, 0, 11, 3, 1], // a pale lower limestone block, set back
[590, 56, 380, 4, 0, 10, 4, 0], // terracotta, stepping the wall down
[646, 32, 428, 1, 2, 12, 2, 1], // the low end of the wall, set back
];
function setup() {
createCanvas(800, 800, WEBGL);
angleMode(DEGREES);
brush.scaleBrushes(2.4);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background(PALETTE[0]);
// 0 — paper flecks: faint warm specks so the sheet is not dead, never dark
for (let i = 0; i < 220; i++) {
const x = random(10, 790), y = random(10, 790);
ink(x, y, x + g(1.4, 0.5, 0.5, 2.6), y + g(0, 0.4, -1, 1), g(0.45, 0.1, 0.3, 0.6), "2H", random() < 0.7 ? "#bfb39a" : PALETTE[1]);
}
// 1 — the Michigan Avenue streetwall: eleven fronts, each its own width, height, ink and rhythm,
// every one of them cut off where the steel stands in front of it
for (let i = 0; i < WALL.length; i++) {
const x = WALL[i][0], w = WALL[i][1], ty = WALL[i][2], col = PALETTE[WALL[i][3]];
const rhy = WALL[i][4], pitch = WALL[i][5], bays = WALL[i][6], back = WALL[i][7];
const far = back === 1, dark = back === 2;
const foot = far ? STREET - 5 : STREET;
if (dark) plate(frontOf(x, w, ty, foot), col, 1.4, 2.1, 62, 0.3);
else halfPlate(frontOf(x, w, ty, foot), col, far ? 1.0 : 1.2, far ? 3.2 : 2.5, 58 + i * 3);
// the face turning away from the light, and the ink over-plate on it
plate(frontOf(x + w * 0.72, w * 0.28, ty, foot), dark ? PALETTE[2] : col, far ? 1.0 : 1.25, far ? 3.6 : 3.0, 90, 0.3);
if (!far) plate(shift(frontOf(x + w * 0.76, w * 0.24, ty + 2, foot), 1.5, 1), PALETTE[2], 0.95, dark ? 2.6 : 3.4, 92, 0.3);
ink(x - 2, ty, x + w + 2, ty, far ? 0.9 : 1.25); // the parapet
ink(x, ty, x, Math.min(foot, occl(x)), 0.8, "pen");
ink(x + w, ty, x + w, Math.min(foot, occl(x + w)), 0.85, "pen");
let r = 0;
for (let y = ty + pitch; y < foot - 4; y += pitch) {
ruleTo(x + 2.5, x + w - 2.5, y, 0.7, "pen"); // the storey line
if (rhy === 1) {
// ribbon glass: one long shallow slot per storey, the paper left between them
if (y - pitch * 0.62 > occl(x + w / 2) - 2) { r++; continue; }
// on the dark tower the spandrel reads pale against the glass, not dark
if (dark) plate(box(x + 3.5, y - pitch * 0.62, w - 7, pitch * 0.34), PALETTE[1], 1.15, 1.6, 0, 0.2);
else slot(x + 3.5, y - pitch * 0.62, w - 7, pitch * 0.42);
} else if (rhy === 2) {
// paired punched openings with a mullion between them
for (let k = 0; k < bays; k++) {
const bx = x + 5 + k * ((w - 10) / bays);
if (y - pitch * 0.66 > occl(bx) - 2) continue;
slot(bx + g(0, 0.3, -0.7, 0.7), y - pitch * 0.66, 3.0, pitch * 0.5);
slot(bx + 5.4 + g(0, 0.3, -0.7, 0.7), y - pitch * 0.66, 3.0, pitch * 0.5);
}
} else {
for (let k = 0; k < bays; k++) {
const bx = x + 4.5 + k * ((w - 9) / bays);
if (y - pitch * 0.66 > occl(bx) - 2) continue;
if (random() < 0.07) continue; // a dark one, so the grid is not stamped
slot(bx + g(0, 0.35, -0.8, 0.8), y - pitch * 0.66, w > 55 ? 4.0 : 3.4, pitch * 0.52);
}
}
r++;
}
// a shallow cornice ledge on the older fronts
if (rhy === 2) plate(box(x - 3, ty, w + 6, 3.2), PALETTE[2], 1.0, 1.7, 0, 0.2);
}
// the stepped-back tower's upper block, and the two masts on it
halfPlate(box(294, 268, 28, 36), PALETTE[1], 1.2, 2.3, 70);
plate(box(314, 268, 8, 36), PALETTE[2], 1.0, 3.2, 88, 0.3);
ink(292, 268, 324, 268, 1.2); ink(294, 302, 294, 268, 0.8, "pen"); ink(322, 302, 322, 268, 0.8, "pen");
for (let k = 0; k < 3; k++) slot(297 + k * 8, 276, 3.2, 6);
ink(303, 268, 303, 242, 1.6, "charcoal"); ink(315, 268, 315, 252, 1.2, "charcoal");
ink(303, 242, 303, 237, 2.4, "charcoal", PALETTE[5]); // the one gilt mark of the print
// the Crain Communications diamond — two sloped faces with the notch cut down their middle
const CRAIN = [[446, 314], [466, 264], [477, 291], [489, 268], [508, 314]];
halfPlate(CRAIN, PALETTE[1], 1.2, 2.3, 54);
plate([[477, 291], [489, 268], [508, 314], [477, 314]], PALETTE[2], 1.05, 3.3, 72, 0.3);
ink(446, 314, 466, 264, 1.1); ink(466, 264, 477, 291, 0.9);
ink(477, 291, 489, 268, 0.9); ink(489, 268, 508, 314, 1.1);
for (let k = 0; k < 4; k++) ruleTo(452 + k * 2.6, 502 - k * 2.6, 300 + k * 4, 0.6, "pen");
// 2 — the plaza: pale granite, joints ruled in courses that thin forward and break into dashes
fadePlate([[162, STREET + 2], [652, STREET + 2], [664, 594], [152, 594]], PALETTE[1], 1.0, 3.9, 0, 0.8);
ink(154, STREET + 2, 660, STREET + 2, 1.2);
for (let i = 0; i < 4; i++) ink(172 - i * 5, STREET + 9 + i * 10, 640 + i * 5, STREET + 9 + i * 10, 0.65, "pen");
for (let i = 0; i < 9; i++) {
const x = 186 + i * 58;
ink(x, STREET + 6, x + (x - 396) * 0.08, 594, 0.6, "pen");
}
for (let i = 0; i < 26; i++) {
const x0 = 146 + random(0, 470);
const len = constrain(randomGaussian(58, 34), 12, 150);
if (x0 + len > 674) continue;
ink(x0, 594 + g(0, 2.2, -6, 6), x0 + len, 594 + g(0, 2.2, -6, 6), g(1.0, 0.25, 0.6, 1.5), "2B", random() < 0.6 ? PALETTE[1] : PALETTE[2]);
}
// 3 — the steel. The top third is BARE PAPER: that is the sky, and it is the brightest thing here.
// the reflected horizon — the line the inverted city hangs from
along(0.30, -AL * 0.965, AR * 0.965, 34, 0.7, "pen", PALETTE[3], true);
// the streetwall again, upside down, bent along the curvature and squeezed toward the tips
for (let i = 0; i < WALL.length; i++) {
// the gap is opened in the Bean's own u, not on the avenue, so paper survives the compression
const uL = uOf(WALL[i][0]) + 1.5, uR = uOf(WALL[i][0] + WALL[i][1]) - 1.5;
if (uR - uL < 0.9) continue;
const um = (uL + uR) / 2;
const s = um / (um < 0 ? AL : AR);
const hgt = STREET - WALL[i][2];
const frac = 0.22 + 0.76 * constrain((hgt - 106) / 150, 0, 1);
const fB = 0.30 + 0.42 * frac;
const lean = -8.5 * s * Math.sqrt(Math.abs(s));
const cin = (uR - uL) * 0.12;
const cut = uR - uL > 13 ? (uL + uR) / 2 : 0; // a wide front splits, paper between the halves
const seg = cut ? [[uL, cut - 1.4], [cut + 1.4, uR]] : [[uL, uR]];
for (let k = 0; k < seg.length; k++) {
const a = seg[k][0], b = seg[k][1];
plate([
[CX + a, beanY(a, 0.30)], [CX + b, beanY(b, 0.30)],
[CX + b - cin * 0.5 + lean, beanY(b, fB)], [CX + a + cin * 0.5 + lean, beanY(a, fB)],
], PALETTE[2], 1.3, 1.8, 80 + i * 2, 0.25);
}
ink(CX + uL + cin * 0.5 + lean - 1, beanY(uL, fB), CX + uR - cin * 0.5 + lean + 1, beanY(uR, fB), 1.0, "2B"); // the inverted parapet
}
// the plaza coming back up in the metal — a pale mid-tone graded into the shade below it
halfPlate(beanBand(0.70, 0.86, -AL * 0.96, AR * 0.96, 26), PALETTE[3], 1.0, 3.2, 56);
fadePlate(beanBand(0.80, 0.93, -AL * 0.94, AR * 0.94, 26), PALETTE[2], 1.0, 3.6, 6, 0.62);
for (let i = 0; i < 5; i++) {
const u = -70 + i * 38 + g(0, 3, -7, 7);
const y0 = beanY(u, 0.745);
ink(CX + u, y0, CX + u + g(0, 0.6, -1.2, 1.2), y0 + g(6, 1.6, 3.5, 9), 1.3, "charcoal");
}
// the underside: the deepest plate on the sheet, and the only one — it hugs the arch and lets go
plate(beanBand(0.91, 1.0, -AL * 0.88, AR * 0.88, 30), PALETTE[2], 1.4, 2.2, 12, 0.3);
plate(beanBand(0.925, 1.0, -AL * 0.5, AR * 0.5, 22), PALETTE[2], 1.45, 1.9, 100, 0.3); // the crown of the arch
// the omphalos — a shadowed chamber under the steel, open enough to walk into
const archPts = [];
for (let i = 0; i <= 24; i++) {
const u = -AL * FOOT + ((AL + AR) * FOOT * i) / 24;
archPts.push([CX + u, beanBot(u) - 1]);
}
archPts.push([CX + AR * FOOT, BASE], [CX - AL * FOOT, BASE]);
fadePlate(archPts, PALETTE[2], 0.95, 4.8, 0, 0.78);
// the two tips, where the reflection compresses to nothing and the form closes over
halfPlate(beanBand(0.30, 0.99, -AL, -AL * 0.80, 10), PALETTE[3], 1.2, 2.4, 74);
plate(shift(beanBand(0.44, 0.98, -AL, -AL * 0.87, 8), 1.4, 1), PALETTE[2], 1.1, 2.8, 88, 0.3);
halfPlate(beanBand(0.30, 0.99, AR * 0.80, AR, 10), PALETTE[3], 1.2, 2.4, 66);
plate(shift(beanBand(0.44, 0.98, AR * 0.87, AR, 8), -1.4, 1), PALETTE[2], 1.1, 2.8, 92, 0.3);
along(0.16, -AL * 0.94, -AL * 0.74, 6, 0.5, "pen", PALETTE[3]);
along(0.24, -AL * 0.92, -AL * 0.76, 6, 0.45, "pen", PALETTE[3]);
along(0.16, AR * 0.74, AR * 0.94, 6, 0.5, "pen", PALETTE[3]);
along(0.24, AR * 0.76, AR * 0.92, 6, 0.45, "pen", PALETTE[3]);
// 4 — the crowd: ten charcoal ticks, two under the arch, the rest bunched at the Bean's right foot
const CROWD = [
[290, 572, 16], [324, 573, 17],
[392, 590, 25], [407, 597, 27], [425, 587, 23], [446, 595, 26], [468, 583, 21], [436, 578, 19],
[532, 592, 24], [208, 584, 21],
];
for (let i = 0; i < CROWD.length; i++) {
const x = CROWD[i][0], yb = CROWD[i][1], h = CROWD[i][2];
ink(x, yb, x + g(0, 0.5, -1, 1), yb - h * 0.70, 2.8, "charcoal");
ink(x + g(0, 0.4, -0.8, 0.8), yb - h * 0.78, x + g(0, 0.5, -1, 1), yb - h, 2.2, "charcoal");
ink(x - 5, yb + 1.5, x + 5, yb + 1.5, 1.3, "2B"); // the shadow each one casts on the paving
}
// 5 — last edges: the crisp highlight along the top, the thinner one where the surface turns
// under, the silhouette below, and the two contact darks at the feet
along(0.0, -AL, AR, 38, 1.4, "rotring", PALETTE[2]);
along(0.72, -AL * 0.92, AR * 0.92, 26, 0.8, "pen", PALETTE[2]);
along(1.0, -AL, -AL * FOOT, 12, 1.1, "rotring", PALETTE[2]);
along(1.0, AR * FOOT, AR, 12, 1.1, "rotring", PALETTE[2]);
along(1.0, -AL * FOOT, AR * FOOT, 22, 1.25, "rotring", PALETTE[2]); // the arch soffit
fadePlate(box(CX - AL * FOOT - 12, BASE - 2, 25, 7), PALETTE[2], 1.0, 3.0, 0, 0.6);
fadePlate(box(CX + AR * FOOT - 13, BASE - 2, 27, 7), PALETTE[2], 1.0, 3.0, 0, 0.6);
}