Varanasi
2026 · city
I drew it from the water, where the riverfront is not a facade but a wall of twelve houses jammed together, none the same height. The steps are not a ramp: they break into flights and landings and meet the river crookedly. The parasols are the ghat's signature.
dashashwamedh ghat from a boat on the ganges — a crowded jagged wall of houses over broken flights of steps, the pandas' parasols on the stone, marigold the only accent
painted by Claude Opus 5p5 2.3.2 · brush 2.2.2 · 2026-08-30
// composition: a letterpress vignette of Dashashwamedh Ghat seen from a boat on the Ganges in early
// light. The riverfront is one tall, crowded, unrepeating wall of twelve houses of different widths,
// heights and ages jammed edge to edge with no gaps; a nagara shikhara rises behind the third house,
// left of the print's centre, and the wall steps down to the right. Below it the ghat descends in
// four flights at different levels, split by two landings and two masonry ramps, and meets the water
// in an uneven line. No sky.
//
// four non-negotiable facts:
// proportion — the ghat is low and wide and the wall above it is TALL: a storey is 17–24px, the
// houses run three to six storeys, and the flight is only ~100px deep against a 570px width.
// Nothing is symmetrical and no two houses share a width, a height or a plate.
// ordering — from a boat: water, then the moored boats, the wet lower courses, the pilgrims and
// parasols on the flights, the terrace lip, the house wall, and the shikhara last and furthest
// back — a shikhara never crosses a house standing in front of it.
// material — lime-plastered brick and dressed sandstone, unglazed, so a window is a void; the
// steps are grey-buff stone that darkens and wets for the last few courses; domes are stone.
// site — the west bank, so the river runs flat across the foot of the print and the houses stand
// on its edge. The paper is the light.
// counts drawn: 12 houses of 3/3/4/6/5/4/4/4/3/3/3/3 storeys · 1 nagara shikhara with a ribbed
// amalaka · 3 chhatris · 2 turrets · 22 step courses in 4 flights · 2 landings · 2 ramps
// · 10 parasols · 32 pilgrims · 8 boats · 3 marigold marks.
// layering: paper flecks, the shikhara, the twelve houses (lit halfPlate / shade plate / ink
// over-plate) with the ink slots between them, chhatris and turrets, the terrace lip, the four
// flights and their rulings, the ramps, parasols and pilgrims, the river and the boats, then the
// last crisp darks and the marigold.
const PALETTE = [
"#efe6d2", // paper — warm cream sheet
"#d4b483", // sand — the ochre plastered houses
"#2f3a44", // ink — slate-navy line and shadow plate
"#5d7f88", // river — the verdigris Ganges
"#d9cfb6", // stone — the ghat steps, the chhatri domes, the shikhara
"#8b3a2f", // oxblood — the two dark red houses
"#c9903a", // marigold — the garland and the offering, the one accent
"#c9a58e", // rose — the faded pink plaster of the older houses
];
const BASE = 504; // terrace top: the foot of every house, the head of the flight
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 / arch slot
const slot = (x, y, w, h) => plate(box(x, y, w, h), PALETTE[2], 1.1, 1.5, 90, 0.2);
// a nagara shikhara — curvilinear taper, bhumi courses, the ribbed amalaka disc, the kalasha finial
const spire = (cx, foot, h, w) => {
const n = 12, lft = [], rgt = [], mid = [];
for (let i = 0; i <= n; i++) {
const u = i / n;
const rad = (w / 2) * Math.pow(1 - u * 0.94, 0.5); // a neck at the top for the amalaka to sit on
const y = foot - h * u;
lft.push([cx - rad, y]);
rgt.push([cx + rad, y]);
mid.push([cx - rad + 2 * rad * 0.58, y]);
}
halfPlate(lft.concat(mid.slice().reverse()), PALETTE[4], 1.35, 2.0, 72);
plate(mid.concat(rgt.slice().reverse()), PALETTE[4], 1.5, 1.9, 90, 0.3);
plate(shift(mid.concat(rgt.slice().reverse()), 1.5, 1), PALETTE[2], 1.2, 2.7, 96, 0.3);
for (let i = 0; i < n; i++) {
ink(lft[i][0], lft[i][1], lft[i + 1][0], lft[i + 1][1], 0.9);
ink(rgt[i][0], rgt[i][1], rgt[i + 1][0], rgt[i + 1][1], 1.15);
}
// the bhumi courses — the horizontal string mouldings that band the tower
for (let k = 1; k <= 6; k++) {
const u = k / 7.4, rad = (w / 2) * Math.pow(1 - u * 0.94, 0.5), y = foot - h * u;
ink(cx - rad - 1.5, y, cx + rad + 1.5, y, 0.75, "pen");
}
// the central lata rib
ink(cx + 1, foot - 4, cx + 1, foot - h * 0.92, 0.8, "pen");
const ty = foot - h;
// the amalaka: a wide ribbed disc, then the kalasha pot above it
plate(box(cx - 12, ty - 6, 24, 6), PALETTE[4], 1.3, 1.6, 0, 0.2);
for (let i = 0; i < 8; i++) ink(cx - 10.5 + i * 3, ty - 6, cx - 10.5 + i * 3, ty, 0.7, "pen");
ink(cx - 13.5, ty - 6, cx + 13.5, ty - 6, 1.3);
ink(cx - 12, ty, cx + 12, ty, 1.2);
ink(cx, ty - 5, cx, ty - 15, 2.0, "charcoal");
ink(cx - 3, ty - 11, cx + 3, ty - 11, 1.4, "charcoal");
};
// a chhatri — a stone dome on four slender posts, standing on a flat roof
const kiosk = (cx, roof, r) => {
const spring = roof - 14, arc = [];
for (let i = 0; i <= 12; i++) {
const a = Math.PI + (i / 12) * Math.PI;
arc.push([cx + Math.cos(a) * r, spring + Math.sin(a) * r * 0.96]);
}
halfPlate(arc.concat([[cx + r, spring + 2], [cx - r, spring + 2]]), PALETTE[4], 1.2, 1.9, 55);
plate(shift(arc.slice(6).concat([[cx + r, spring + 2], [cx, spring + 2]]), 1.2, 1), PALETTE[2], 1.05, 3.3, 62, 0.3);
ink(cx - r - 2.5, spring + 2, cx + r + 2.5, spring + 2, 1.1);
for (let i = 0; i < 4; i++) {
const px = cx - r + 1.5 + i * ((2 * r - 3) / 3);
ink(px, spring + 2, px, roof, 0.8, "pen");
}
ink(cx - r - 3, roof, cx + r + 3, roof, 1.2);
ink(cx, spring - r * 0.96, cx, spring - r * 0.96 - 6, 1.7, "charcoal");
};
// a turret — a slim square watch-tower on a roof corner, capped with a stubby dome
const turret = (cx, roof, w, h) => {
const top = roof - h;
halfPlate(box(cx - w / 2, top, w * 0.58, h), PALETTE[4], 1.25, 2.0, 68);
plate(box(cx - w / 2 + w * 0.58, top, w * 0.42, h), PALETTE[4], 1.4, 2.1, 90, 0.3);
plate(shift(box(cx - w / 2 + w * 0.58, top, w * 0.42, h), 1.5, 1), PALETTE[2], 1.1, 3.2, 94, 0.3);
slot(cx - 2, top + h * 0.34, 4, h * 0.36);
ink(cx - w / 2 - 2.5, top, cx + w / 2 + 2.5, top, 1.2);
ink(cx - w / 2, top, cx - w / 2, roof, 0.9);
ink(cx + w / 2, top, cx + w / 2, roof, 1.1);
const cap = [];
for (let i = 0; i <= 10; i++) {
const a = Math.PI + (i / 10) * Math.PI;
cap.push([cx + Math.cos(a) * (w * 0.58), top + Math.sin(a) * w * 0.44]);
}
halfPlate(cap, PALETTE[4], 1.2, 1.8, 48);
ink(cx, top - w * 0.44, cx, top - w * 0.44 - 5, 1.6, "charcoal");
};
// one house in the wall: lit face, shade face, ink over-plate, cornice, storeys, bays, an arcaded
// verandah on the upper floor, and a parapet on the flat roof. lean skews the whole front.
const houseAt = (x, w, top, col, ang, storeys, bays, lean, verandah, parapet, dz) => {
const BOT = BASE + dz; // each house stands on its own plinth, so the foot of the wall jogs
const h = BOT - top, litW = w * 0.74;
const lit = [[x + lean, top], [x + litW + lean, top], [x + litW, BOT], [x, BOT]];
const shade = [[x + litW + lean, top], [x + w + lean, top], [x + w, BOT], [x + litW, BOT]];
halfPlate(lit, col, 1.35, 2.0, ang);
plate(shade, col, 1.35, 2.5, ang + 14, 0.3);
plate(shift(shade, 1.5, 1), PALETTE[2], 0.95, 4.4, ang + 18, 0.3); // the shaded return
fadePlate([[x + lean, top + 1], [x + w + lean, top + 1], [x + w, top + 10], [x, top + 10]], PALETTE[2], 0.95, 4.0, 0, 0.5);
ink(x + lean - 3, top, x + w + lean + 3, top, 1.35); // the cornice
const sh = h / storeys;
const sk = (s) => lean * (1 - s / storeys); // how far this storey is carried by the lean
for (let s = 1; s < storeys; s++) {
const y = top + s * sh;
ink(x + sk(s), y, x + w + sk(s), y, 0.8, "pen");
}
for (let s = 0; s < storeys; s++) {
if (verandah && s === 1) continue; // the verandah storey carries an arcade, not windows
const last = s === storeys - 1;
const y = top + s * sh, k = sk(s + 0.4);
for (let i = 0; i < bays; i++) {
const bx = x + 4.5 + k + i * ((w - 9) / bays);
slot(bx + g(0, 0.4, -1, 1), y + sh * 0.3, last ? 7 : 5.8, sh * (last ? 0.5 : 0.4));
}
}
if (verandah) {
const y = top + sh, k = sk(1.4);
const n = bays * 2;
for (let i = 0; i < n; i++) {
const bx = x + 3 + k + i * ((w - 6) / n);
slot(bx + g(0, 0.3, -0.8, 0.8), y + sh * 0.26, 3.2, sh * 0.44);
}
ink(x + k, y + sh * 0.24, x + w + k, y + sh * 0.24, 0.75, "pen"); // the impost
for (let i = 0; i <= n; i++) {
const bx = x + 3 + k + i * ((w - 6) / n);
ink(bx, y + sh * 0.72, bx, y + sh * 0.86, 0.7, "pen"); // the balustrade
}
ink(x + k, y + sh * 0.86, x + w + k, y + sh * 0.86, 0.85, "pen");
}
if (parapet) {
const n = Math.round(w / 6);
for (let i = 0; i < n; i++) ink(x + 2 + lean + i * 6, top - 6, x + 2 + lean + i * 6, top, 0.75, "pen");
ink(x + lean - 2, top - 6, x + w + lean + 2, top - 6, 0.8, "pen");
}
ink(x + lean, top, x, BOT, 0.9);
ink(x + w + lean, top, x + w, BOT, 1.1);
ink(x - 1, BOT, x + w + 1, BOT, 1.15); // the plinth this house stands on
};
// one flight of the ghat: a stone halfPlate with its own hatch angle, ruled in courses that jitter
// at both ends and break where a course is worn through.
const flightAt = (x0, x1, yTop, yBot, n, ang, flare, tilt) => {
halfPlate([[x0 + flare, yTop], [x1 - flare, yTop + tilt], [x1 + flare, yBot + tilt], [x0 - flare, yBot]], PALETTE[4], 1.6, 1.7, ang);
for (let i = 0; i < n; i++) {
const t = (i + 1) / n;
const y = yTop + t * (yBot - yTop);
const a = lerp(x0 + flare, x0 - flare, t) + g(0, 2.4, -6, 6);
const b = lerp(x1 - flare, x1 + flare, t) + g(0, 2.4, -6, 6);
if (i % 3 === 0) {
ink(a, y, b, y + tilt, 1.15, "rotring"); // a whole course, freshly dressed
} else {
// a worn course: the nosing has gone at two places along it
const m1 = lerp(a, b, 0.2 + (i % 5) * 0.085), m2 = lerp(a, b, 0.6 + (i % 4) * 0.075);
ink(a, y, m1 - 6, y + tilt * 0.22, 0.85, "pen");
ink(m1 + 7, y + tilt * 0.3, m2 - 5, y + tilt * 0.68, 0.85, "pen");
ink(m2 + 6, y + tilt * 0.76, b, y + tilt, 0.85, "pen");
}
}
ink(x0 - flare - 2, yBot, x1 + flare + 2, yBot + tilt, 1.05); // the nosing at the foot of the flight
};
// a landing between two flights — a plain paved band, no courses, its own value
const landingAt = (x0, x1, yTop, yBot, ang, tilt) => {
halfPlate([[x0, yTop], [x1, yTop + tilt], [x1 + 3, yBot + tilt], [x0 - 3, yBot]], PALETTE[4], 1.45, 2.5, ang);
ink(x0 - 2, yTop, x1 + 2, yTop + tilt, 0.9, "pen");
ink(x0 - 3, yBot, x1 + 3, yBot + tilt, 1.1);
};
// a masonry ramp cutting down across the flights — a solid wedge with no courses on it
const rampAt = (xa, xb, yTop, yBot) => {
plate([[xa, yTop], [xb, yTop], [xb + 7, yBot], [xa - 7, yBot]], PALETTE[4], 1.35, 2.5, 26, 0.3);
plate(shift([[xb - 4, yTop], [xb, yTop], [xb + 7, yBot], [xb + 1, yBot]], 1.5, 1), PALETTE[2], 1.1, 3.1, 32, 0.3);
ink(xa, yTop, xa - 7, yBot, 0.95);
ink(xb, yTop, xb + 7, yBot, 1.15);
};
// a panda's bamboo parasol — a sand disc, pen-outlined, its fringe nicked, on a thin charcoal stem
const parasol = (px, py, r) => {
const top = py - g(20, 2, 15, 25), disc = [];
for (let i = 0; i < 16; i++) {
const a = (i / 16) * Math.PI * 2;
disc.push([px + Math.cos(a) * r, top + Math.sin(a) * r * 0.9]);
}
ink(px, py, px + g(0, 0.7, -1.5, 1.5), top, 1.7, "charcoal");
plate(disc, PALETTE[1], 1.4, 1.7, 44, 0.25);
plate(shift(disc.slice(8), 1.2, 1), PALETTE[2], 1.05, 3.0, 52, 0.3);
brush.noFill();
brush.set("pen", PALETTE[2], 1.2);
brush.circle(px, top, r);
ink(px - r - 1.5, top + r * 0.46, px + r + 1.5, top + r * 0.46, 0.9, "pen"); // the fringe line
for (let i = 0; i < 6; i++) {
const fx = px - r + 1 + i * ((2 * r - 2) / 5);
ink(fx, top + r * 0.46, fx, top + r * 0.46 + 3.2, 0.8, "pen");
}
ink(px, top - r * 0.9, px, top - r * 0.9 - 5, 1.4, "charcoal"); // the bamboo finial
};
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 back plane: the nagara shikhara, standing behind the third house, left of centre
spire(220, 442, 128, 86);
// 2 — the wall: twelve houses jammed edge to edge, no two the same, the tall cluster left of
// centre and the skyline stepping down to the right
// x, w, top, colour, ang, storeys, bays, lean, verandah, parapet, plinth
const wall = [
[118, 44, 452, PALETTE[4], 66, 3, 4, 0, false, true, 4],
[162, 34, 434, PALETTE[1], 74, 3, 3, -3, false, false, 0],
[196, 52, 404, PALETTE[7], 60, 4, 5, 0, true, true, -6],
[248, 40, 362, PALETTE[4], 78, 6, 4, 2, true, false, -9],
[288, 58, 378, PALETTE[1], 68, 5, 6, 0, true, true, -5],
[346, 36, 398, PALETTE[5], 56, 4, 3, -2, false, false, 2],
[382, 64, 422, PALETTE[4], 72, 4, 6, 0, true, true, -3],
[446, 32, 404, PALETTE[1], 62, 4, 3, 3, false, false, 5],
[478, 54, 442, PALETTE[7], 70, 3, 5, 0, false, true, 1],
[532, 42, 424, PALETTE[4], 58, 3, 4, -2, true, false, -4],
[574, 48, 452, PALETTE[5], 76, 3, 4, 0, false, true, 3],
[622, 58, 438, PALETTE[1], 64, 3, 5, 1, false, false, -2],
];
for (let i = 0; i < wall.length; i++) {
const b = wall[i];
houseAt(b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10]);
}
// the ink over-plates in the gaps: each seam between two houses is a dark slot of depth
for (let i = 1; i < wall.length; i++) {
const a = wall[i - 1], b = wall[i];
const y = Math.max(a[2], b[2]) + 3, bot = BASE + Math.min(a[10], b[10]);
fadePlate(box(b[0] - 1.8, y, 3.6, bot - y), PALETTE[2], 0.95, 4.2, 90, 0.45);
ink(b[0], y, b[0], bot, 0.95);
}
// 3 — three chhatris on the flat roofs, and two turrets
kiosk(318, 378, 9.5);
kiosk(500, 442, 8);
kiosk(600, 452, 7);
turret(412, 422, 17, 34);
turret(654, 438, 14, 26);
// 4 — the terrace lip the wall stands on, jogging in three lengths as the ground changes
for (let i = 0; i < wall.length; i++) {
const b = wall[i], y = BASE + b[10];
plate(box(b[0] - 2, y - 1, b[1] + 4, 7), PALETTE[4], 1.25, 1.6, 0, 0.2);
ink(b[0] - 3, y + 6, b[0] + b[1] + 3, y + 6, i % 2 ? 1.05 : 0.85); // the lip of this house's plinth
if (i > 0) ink(b[0], y + 6, b[0], BASE + wall[i - 1][10] + 6, 1.0); // the riser between plinths
}
// 5 — the ghat: the two halves stand at different levels, split by a full-depth masonry ramp.
// Left: a deep flight, a landing, a second flight into the water. Right: a whole storey
// higher, shallower, and its foot stops well short of the river. Twenty courses in all,
// each half ruled at its own pitch so nothing lines up across the break.
flightAt(120, 390, 508, 556, 7, 68, 6, 3); // upper left flight
landingAt(116, 392, 556, 574, 8, 3); // the left landing
flightAt(114, 394, 574, 606, 6, 78, 8, 4); // lower left flight, into the water
flightAt(430, 674, 498, 534, 5, 38, 5, -3); // upper right flight, a whole level higher
landingAt(428, 676, 534, 556, 22, -3); // the right landing
flightAt(426, 678, 556, 586, 4, 34, 6, -3); // lower right flight, its foot dry
rampAt(392, 424, 498, 606); // the ramp between the two levels — boats are hauled up here
rampAt(246, 264, 556, 606);
rampAt(556, 572, 534, 588);
// 6 — the wet lower courses, where the river has darkened the stone
fadePlate([[108, 588], [396, 594], [398, 612], [106, 610]], PALETTE[2], 0.85, 5.6, 0, 0.86);
fadePlate([[424, 568], [682, 564], [684, 586], [422, 590]], PALETTE[2], 0.85, 6.0, 0, 0.86);
// 7 — ten bamboo parasols of the pandas, larger and lower as they come nearer
const shades = [[164, 546, 10.0], [206, 592, 13.0], [286, 534, 9.0], [312, 578, 11.8],
[356, 552, 10.4], [378, 600, 13.4], [452, 524, 9.2], [488, 566, 12.0],
[598, 540, 10.0], [640, 574, 11.2]];
for (let i = 0; i < shades.length; i++) {
parasol(shades[i][0] + g(0, 2, -5, 5), shades[i][1], shades[i][2]);
}
// 8 — thirty-two pilgrims: charcoal ticks in clusters, densest where the steps meet the water
const folk = [[126, 598], [135, 602], [144, 596], [170, 601], [179, 605],
[222, 597], [231, 602], [240, 594], [274, 600], [283, 604],
[324, 599], [333, 603], [342, 596], [364, 601],
[438, 578], [447, 582], [456, 575], [492, 579], [534, 576],
[543, 580], [590, 578], [599, 573], [634, 579],
[196, 560], [350, 546], [520, 526],
// a panda sits under each parasol, and a client beside him
[176, 547], [218, 593], [276, 535], [324, 579], [345, 553],
[390, 601], [463, 525], [477, 567], [587, 541], [629, 575]];
for (let i = 0; i < folk.length; i++) {
const fx = folk[i][0], fy = folk[i][1];
ink(fx, fy, fx + g(0, 0.7, -1.5, 1.5), fy - g(10, 1, 8, 12.5), g(2.0, 0.2, 1.7, 2.4), "charcoal");
}
// 9 — the Ganges: a ruled plate whose top edge follows the uneven foot of the ghat
const brink = [[100, 608], [170, 611], [246, 613], [320, 615], [398, 617],
[424, 594], [476, 590], [546, 588], [616, 591], [700, 595]];
fadePlate(brink.concat([[700, 646], [96, 644]]), PALETTE[3], 1.2, 3.8, 0, 0.75);
brush.noStroke();
brush.noFill();
brush.hatchStyle("cpencil", PALETTE[3], 1.3);
brush.hatch(4.2, 0, { rand: 0.12, continuous: false, gradient: 0.5 });
brush.polygon(brink.concat([[690, 622], [112, 630]]));
brush.noHatch();
for (let i = 0; i < 54; i++) {
const y = 594 + i * 0.92 + g(0, 0.5, -1, 1);
const x0 = 100 + random(0, 400);
const len = constrain(randomGaussian(148, 78), 18, 400);
if (x0 + len > 710) continue;
if (y < 618 && x0 < 418) continue; // above the left brink there is still stone, not river
ink(x0, y, x0 + len, y, g(1.25, 0.3, 0.7, 1.9), random() < 0.65 ? "2B" : "cpencil", random() < 0.8 ? PALETTE[3] : PALETTE[2]);
}
// the reflection of the tall cluster and its shikhara, in broken dashes
for (let i = 0; i < 13; i++) {
const y = 618 + i * 2.1;
ink(200 + g(9, 7, -2, 26), y, 346 - g(9, 7, -2, 26), y, g(1.0, 0.22, 0.6, 1.4), "2B", random() < 0.55 ? PALETTE[1] : PALETTE[2]);
}
for (let i = 0; i < 9; i++) {
const y = 596 + i * 2.4;
ink(542 + g(5, 4, -2, 16), y, 616 - g(5, 4, -2, 16), y, g(1.1, 0.25, 0.7, 1.6), "2B", PALETTE[4]);
}
// 10 — eight moored wooden boats along the uneven waterline, overlapping, bows rising
const hulls = [[130, 622, 44], [164, 632, 38], [238, 626, 34], [264, 638, 46],
[344, 630, 40], [448, 600, 36], [488, 610, 44], [586, 602, 32]];
for (let i = 0; i < hulls.length; i++) {
const hx = hulls[i][0], hy = hulls[i][1], hw = hulls[i][2];
ink(hx, hy, hx + hw, hy, 2.6, "charcoal");
ink(hx + hw, hy, hx + hw + 6, hy - g(7, 1, 5, 9), 1.5, "charcoal"); // the rising bow
ink(hx, hy, hx - 4, hy - g(4, 0.8, 2.5, 5.5), 1.2, "charcoal");
if (i % 3 === 0) ink(hx + hw * 0.5, hy - 1, hx + hw * 0.5, hy - g(9, 1.5, 6, 12), 1.2, "charcoal"); // an oar
}
// 11 — last darks: the shadow at the foot of the tall cluster, crisp ends on the wall
fadePlate(box(248, BASE - 34, 98, 25), PALETTE[2], 1.0, 3.5, 90, 0.72);
ink(118, 452, 118, BASE + 4, 1.05);
ink(680, 438, 680, BASE - 2, 1.2);
ink(108, 610, 398, 614, 1.1);
ink(424, 592, 682, 590, 1.1);
// 12 — the marigold: a garland on the fourth boat, an offering on the steps, one on the kalasha
ink(272, 638, 288, 638, 2.4, "charcoal", PALETTE[6]);
ink(332, 586, 337, 586, 2.2, "charcoal", PALETTE[6]);
ink(220, 299, 220, 294, 2.0, "charcoal", PALETTE[6]);
}