Bengaluru
2026 · city
I stood on the Cubbon lawn and gave the granite the left of the sheet so the dome would be the first thing you see. The colonnade had to count itself, each shaft a little different, and the stairs had to fan. The garden is only there so the stone has green to sit on; the right sky I left as paper.
the granite dome and colonnade of vidhana soudha over cubbon garden green, neo-dravidian, large and unmistakable
painted by Grok 4.6p5 2.3.2 · brush 2.2.2 · 2026-08-30
// composition: Vidhana Soudha from Ambedkar Veedhi, a step LEFT of the axis, so the long front
// foreshortens to the right — the near left wing runs wide, the far right wing crowds its bays up,
// and the twelve-column porch with the dome over it therefore sits a little right of the print's
// own centre. The print is wide and low, x 124-676, y 378-658; the dome is the only thing that
// climbs, and everything above and around the print is bare paper. No sky.
// four facts: (1) proportion — one very long four-storey symmetrical facade (~215 m) against a
// dome that tops out near 46 m: the block reads several times wider than tall, and the dome's drum
// is a fifth of the porch's width, never a half. (2) ordering — porch in FRONT of the central
// pavilion; drum and dome BEHIND and above the porch; the wings behind both; the rain trees in
// front of the wings' lowest storey. (3) material — Bangalore granite as a warm grey-ochre plate,
// with an ink over-plate only on the returns that face left, away from the afternoon light, and
// under the deep eaves. (4) site — the building stands on a plinth over lawn; the paper is the sky.
// counts drawn: 12 porch columns, each with capital and base; 1 central dome on a drum, 9 ribs, one
// finial; 4 smaller domes (2 on the central pavilion's front corners, 2 on the wing-end pavilions);
// 4 storeys of arched windows in 31 bays = 124 slots, the bays tightening rightward; 10 chhatri
// ornaments on the cornice; 51 parapet ticks; 5 rain trees; 4 people; 1 auto-rickshaw.
// layering: paper flecks, the two wings, the eave shadow, the counted slots, the corner pavilions and
// their domes, the central pavilion, the drum, the dome and its ribs, the porch entablature, the
// dark recess and the twelve columns, the plinth, the lawn, the broad stair, the trees, then the
// last darks — left returns, mass ends — and one gilt finial.
const PALETTE = [
"#efe6d2", // paper — warm cream sheet
"#c9b994", // granite — warm grey-ochre Bangalore stone, the lit plate
"#2f3a44", // ink — slate-navy line, slot and shadow plate
"#7a8c6a", // moss — the lawns and the rain trees
"#8c8168", // granite, shade — returns, eaves, the receding wing
"#c9a24a", // gilt — the dome's finial, the one warm accent
];
const FL = 124, FR = 676; // the whole front, left and right
const CB_L = 340, CB_R = 520; // the central pavilion
const PX_L = 348, PX_R = 512; // the porch of twelve columns
const PCX = 430; // porch / drum / dome centre
const SPRING = 436; // the dome springs here, on top of the drum
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]];
// the cornice drops and the base lifts toward the right: the front is receding that way
const topAt = (x) => 492 + ((x - FL) / (FR - FL)) * 20;
const botAt = (x) => 604 - ((x - FL) / (FR - FL)) * 8;
const wallOf = (x0, x1) => [[x0, topAt(x0)], [x1, topAt(x1)], [x1, botAt(x1)], [x0, botAt(x0)]];
// 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 — 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 arched-window slot
const slot = (x, y, w, h) => plate(box(x, y, w, h), PALETTE[2], 1.1, 1.5, 90, 0.2);
// one bay of the front: four storeys of arched windows, the storeys shortening as the wall recedes
const bayOf = (x, w) => {
const t = topAt(x), b = botAt(x), H = b - t, st = (H - 18) / 4;
for (let s = 0; s < 4; s++) {
slot(x + g(0, 0.35, -0.9, 0.9), t + 16 + st * s, w, st * 0.62);
}
ink(x - 1, t + 14, x + w + 1, t + 14.5, 0.7, "pen"); // the impost of the piano-nobile arcade
};
// a dome: a slight neo-Dravidian bulge, not a bare hemisphere, sitting on its springing line
const domeOf = (cx, base, rx, ry) => {
const pts = [];
for (let i = 0; i <= 18; i++) {
const a = Math.PI + (i / 18) * Math.PI;
const k = 1 + 0.07 * Math.sin((i / 18) * Math.PI);
pts.push([cx + Math.cos(a) * rx * k, base + Math.sin(a) * ry * k]);
}
pts.push([cx + rx, base + 5], [cx - rx, base + 5]);
return pts;
};
// a rain tree: a wide flat crown, the underside squashed
const crownOf = (cx, cy, rw, rh) => {
const pts = [];
for (let i = 0; i < 13; i++) {
const a = (i / 13) * Math.PI * 2;
const k = 0.74 + noise(i * 0.8 + cx * 0.013) * 0.5;
const sy = Math.sin(a);
pts.push([cx + Math.cos(a) * rw * k, cy + sy * rh * k * (sy < 0 ? 1 : 0.45)]);
}
return pts;
};
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[4]);
}
// 1 — the two wings of the long front, the right one greying as it recedes
const LW = wallOf(FL, CB_L), RW = wallOf(CB_R, FR);
halfPlate(LW, PALETTE[1], 1.35, 2.2, 68);
halfPlate(RW, PALETTE[1], 1.3, 2.3, 74);
plate(shift(RW, 1, 0.5), PALETTE[4], 1.05, 3.6, 78, 0.3);
// the deep eaves — the shadow the chajja throws along the whole front
const eaveOf = (x0, x1) => [
[x0, topAt(x0) + 2], [x1, topAt(x1) + 2], [x1, topAt(x1) + 14], [x0, topAt(x0) + 14],
];
fadePlate(eaveOf(FL, CB_L), PALETTE[2], 1.05, 3.2, 0, 0.68);
fadePlate(eaveOf(CB_R, FR), PALETTE[2], 1.0, 3.0, 0, 0.72);
ink(FL - 6, topAt(FL) - 1, CB_L, topAt(CB_L) - 1, 1.4);
ink(CB_R, topAt(CB_R) - 1, FR + 6, topAt(FR) - 1, 1.3);
ink(FL - 3, topAt(FL) + 14, CB_L, topAt(CB_L) + 14, 0.85, "pen");
ink(CB_R, topAt(CB_R) + 14, FR + 3, topAt(FR) + 14, 0.8, "pen");
// the string course between every storey
for (let s = 1; s < 4; s++) {
const sy = (x) => topAt(x) + 16 + ((botAt(x) - topAt(x) - 18) * s) / 4;
ink(FL, sy(FL), CB_L, sy(CB_L), 0.8, "pen");
ink(CB_R, sy(CB_R), FR, sy(FR), 0.75, "pen");
}
// the Dravidian cornice run: a close file of parapet ticks over both wings
for (let i = 0; i < 27; i++) {
const x = FL + 3 + i * 8;
if (x > CB_L - 3) continue;
ink(x, topAt(x) - 1, x + g(0, 0.3, -0.7, 0.7), topAt(x) - 6, 0.75, "pen");
}
for (let i = 0; i < 24; i++) {
const x = CB_R + 3 + i * 6.4;
if (x > FR - 2) continue;
ink(x, topAt(x) - 1, x + g(0, 0.3, -0.7, 0.7), topAt(x) - 5, 0.7, "pen");
}
// 2 — the counted fabric: 31 bays, four storeys each, the right-hand bays tightening
for (let i = 0; i < 13; i++) bayOf(182 + i * 12, 5.4);
for (let i = 0; i < 12; i++) bayOf(528 + i * 8.2, 4);
for (let i = 0; i < 3; i++) bayOf(134 + i * 13, 6);
for (let i = 0; i < 3; i++) bayOf(641 + i * 10.5, 4.4);
// 3 — the wing-end pavilions, each capped by one of the four smaller domes
const LP_T = topAt(149) - 10, RP_T = topAt(656) - 9;
halfPlate([[124, LP_T], [176, LP_T + 1], [176, botAt(176)], [124, botAt(124)]], PALETTE[1], 1.35, 2.1, 60);
plate(box(124, LP_T + 2, 8, botAt(124) - LP_T - 2), PALETTE[1], 1.4, 2.2, 92, 0.3); // its lit right return, seen edge-on
ink(120, LP_T, 178, LP_T + 1, 1.35);
ink(176, LP_T + 1, 176, botAt(176), 0.9, "pen");
halfPlate([[634, RP_T], [676, RP_T + 1], [676, botAt(676)], [634, botAt(634)]], PALETTE[1], 1.3, 2.2, 64);
plate(box(634, RP_T + 2, 7, botAt(634) - RP_T - 2), PALETTE[4], 1.35, 2.3, 92, 0.3); // its shaded left return
plate(shift(box(634, RP_T + 3, 7, botAt(634) - RP_T - 3), 1.4, 1), PALETTE[2], 1.05, 3.1, 94, 0.3);
ink(631, RP_T, 680, RP_T + 1, 1.3);
const smallDome = (cx, base, rx, ry, dw) => {
plate(box(cx - dw, base, dw * 2, 12), PALETTE[1], 1.4, 2.1, 88, 0.3);
plate(shift(box(cx + dw * 0.3, base + 1, dw * 0.7, 11), 1, 0.5), PALETTE[4], 1.1, 3.2, 90, 0.3);
halfPlate(domeOf(cx, base, rx, ry), PALETTE[1], 1.3, 2.0, 46);
plate([[cx, base], [cx - rx, base], [cx - rx * 0.72, base - ry * 0.72], [cx - rx * 0.2, base - ry]],
PALETTE[4], 1.2, 2.6, 52, 0.3); // the shaded left cheek
ink(cx, base - ry * 1.07, cx, base - ry * 1.07 - 7, 1.6, "charcoal");
ink(cx - rx, base + 1, cx + rx, base + 1, 1.0);
};
smallDome(150, LP_T - 2, 13, 14, 9);
smallDome(655, RP_T - 2, 12, 13, 8);
smallDome(356, 474, 14, 15, 10);
smallDome(504, 474, 14, 15, 10);
// 4 — the central pavilion, rising a storey clear of the wings
const CP = [[CB_L, 474], [CB_R, 476], [CB_R, botAt(CB_R)], [CB_L, botAt(CB_L)]];
halfPlate(CP, PALETTE[1], 1.4, 2.1, 76);
plate(box(CB_L, 476, 9, botAt(CB_L) - 476), PALETTE[4], 1.35, 2.3, 90, 0.3); // its shaded left return
plate(shift(box(CB_L, 477, 9, botAt(CB_L) - 477), 1.5, 1), PALETTE[2], 1.05, 3.0, 92, 0.3);
fadePlate(box(CB_L + 8, 476, CB_R - CB_L - 8, 13), PALETTE[2], 1.0, 3.2, 0, 0.66);
ink(CB_L - 5, 474, CB_R + 5, 476, 1.4);
ink(CB_L - 3, 488, CB_R + 3, 490, 0.85, "pen");
for (let i = 0; i < 15; i++) ink(CB_L + 6 + i * 11.8, 473, CB_L + 6 + i * 11.8, 467, 0.75, "pen");
// 5 — the drum, then the dome: ribbed, on its own cornice ring, the finial the highest mark
plate(box(390, SPRING, 80, 474 - SPRING), PALETTE[1], 1.45, 2.1, 88, 0.3);
plate(shift(box(390, SPRING + 1, 22, 474 - SPRING - 1), 1.5, 1), PALETTE[4], 1.15, 3.0, 90, 0.3);
for (let i = 0; i < 5; i++) slot(398 + i * 16, SPRING + 8, 5, 20); // the drum's arched lights
ink(384, SPRING - 1, 476, SPRING - 1, 1.35);
ink(386, 474, 474, 474, 1.2);
const DOME = domeOf(PCX, SPRING, 42, 44);
halfPlate(DOME, PALETTE[1], 1.4, 2.0, 54);
plate([[PCX, SPRING], [PCX - 42, SPRING], [PCX - 33, SPRING - 30], [PCX - 12, SPRING - 45]],
PALETTE[4], 1.3, 2.4, 58, 0.3); // the shaded left cheek of the dome
plate(shift([[PCX - 4, SPRING], [PCX - 42, SPRING], [PCX - 34, SPRING - 28], [PCX - 14, SPRING - 43]], 1.5, 1),
PALETTE[2], 1.05, 3.4, 62, 0.3);
brush.noFill();
for (let i = 0; i < 9; i++) {
const a = Math.PI + ((i + 0.5) / 9) * Math.PI;
ink(PCX, SPRING - 47, PCX + Math.cos(a) * 41, SPRING + Math.sin(a) * 8 + 0, 0.8, "pen");
}
ink(PCX - 43, SPRING + 1, PCX + 43, SPRING + 1, 1.3);
// the finial — the only gilt in the print
ink(PCX, SPRING - 45, PCX, 380, 2.4, "charcoal", PALETTE[5]);
ink(PCX - 4, 386, PCX + 4, 386, 1.6, "charcoal", PALETTE[5]);
// 6 — the porch: entablature, the dark recess behind, twelve granite columns
plate([[342, 486], [518, 488], [518, 500], [342, 498]], PALETTE[1], 1.45, 2.0, 6, 0.3);
ink(338, 485, 522, 487, 1.4);
ink(340, 500, 520, 502, 1.0);
plate(box(PX_L, 500, PX_R - PX_L, 96), PALETTE[2], 1.35, 2.0, 84, 0.3); // the recess the porch stands in
plate(shift(box(PX_L, 501, PX_R - PX_L, 95), 1, 1), PALETTE[4], 1.0, 4.2, 12, 0.3);
for (let i = 0; i < 12; i++) {
const cx = PX_L + 7.6 + i * 13.55 + g(0, 0.3, -0.7, 0.7);
plate(box(cx - 3.5, 500, 7, 89), PALETTE[1], 1.5, 1.9, i % 2 ? 88 : 92, 0.28);
plate(box(cx - 5.5, 494, 11, 6), PALETTE[1], 1.4, 1.8, 4, 0.25); // capital
plate(box(cx - 5, 589, 10, 6), PALETTE[1], 1.4, 1.8, 8, 0.25); // base
ink(cx + 3.4, 501, cx + 3.4, 588, 0.7, "pen"); // the shaded right edge of each shaft
}
ink(340, 596, 520, 596, 1.2);
// 7 — the plinth the whole front stands on
plate([[FL, botAt(FL)], [FR, botAt(FR)], [FR, botAt(FR) + 7], [FL, botAt(FL) + 7]], PALETTE[1], 1.2, 1.7, 0, 0.25);
ink(FL - 4, botAt(FL) + 7, FR + 4, botAt(FR) + 7, 1.3);
// 8 — the lawns of the forecourt, a moss half-plate that breaks into dashes at its foot
halfPlate([[112, 626], [690, 620], [696, 650], [104, 656]], PALETTE[3], 1.3, 2.4, 34);
plate([[430, 624], [690, 620], [696, 648], [436, 652]], PALETTE[2], 1.0, 4.0, 44, 0.3);
for (let i = 0; i < 34; i++) {
const y = 630 + i * 0.85 + g(0, 0.6, -1.4, 1.4);
const x0 = 108 + random(0, 380);
const len = constrain(randomGaussian(120, 66), 16, 330);
if (x0 + len > 700) continue;
ink(x0, y, x0 + len, y, g(1.2, 0.3, 0.7, 1.8), random() < 0.6 ? "2B" : "cpencil",
random() < 0.75 ? PALETTE[3] : PALETTE[2]);
}
// 9 — the broad flight of steps up to the porch
halfPlate([[336, 596], [524, 596], [552, 628], [310, 628]], PALETTE[1], 1.35, 2.2, 4);
fadePlate([[336, 597], [524, 597], [552, 628], [310, 628]], PALETTE[2], 1.0, 3.6, 0, 0.62);
for (let i = 0; i < 8; i++) {
const t = i / 7;
ink(lerp(336, 310, t), 597 + i * 4.4, lerp(524, 552, t), 597 + i * 4.4, g(0.9, 0.15, 0.6, 1.2));
}
ink(336, 596, 310, 628, 1.1);
ink(524, 596, 552, 628, 1.1);
// 10 — the rain trees along the lawn, flat dark crowns on thin trunks
const trees = [[150, 588, 42, 15], [214, 594, 34, 13], [268, 598, 28, 11], [598, 592, 36, 14], [652, 598, 30, 12]];
for (let i = 0; i < trees.length; i++) {
const t = trees[i], cr = crownOf(t[0], t[1], t[2], t[3]);
halfPlate(cr, PALETTE[3], 1.35, 2.2, 30 + i * 3);
plate(shift(cr.slice(6, 12).concat([[t[0], t[1]]]), 2, 2), PALETTE[2], 1.15, 3.2, 44, 0.35);
ink(t[0] + g(0, 1, -2, 2), t[1] + t[3] * 0.4, t[0] + g(0, 1, -2, 2), 628, 1.9, "charcoal");
ink(t[0] - t[2] * 0.4, t[1] + t[3] * 0.3, t[0] + g(0, 1, -2, 2), 626, 1.2, "charcoal");
}
// 11 — last darks: the ends of the mass, the deep shade at the foot of the porch, then the scale
ink(FL, topAt(FL), FL, botAt(FL) + 6, 1.2);
ink(FR, topAt(FR), FR, botAt(FR) + 6, 1.4);
ink(CB_L, 476, CB_L, botAt(CB_L), 1.0);
ink(CB_R + 1, 477, CB_R + 1, botAt(CB_R), 0.9);
fadePlate(box(PX_L, 566, PX_R - PX_L, 30), PALETTE[2], 1.1, 3.0, 90, 0.7);
const folk = [[300, 8], [311, 7], [489, 8], [546, 7]];
for (let i = 0; i < folk.length; i++) {
ink(folk[i][0], 634, folk[i][0] + g(0, 0.6, -1.2, 1.2), 634 - folk[i][1], 1.6, "charcoal");
}
// one auto-rickshaw on the near edge of the road: an ink hood with a gilt tick on its canopy
plate([[228, 638], [248, 636], [250, 646], [226, 646]], PALETTE[2], 1.4, 1.8, 24, 0.25);
ink(230, 636, 246, 634, 1.8, "charcoal", PALETTE[5]);
ink(231, 646, 231, 650, 2.2, "charcoal");
ink(246, 646, 246, 650, 2.2, "charcoal");
}