Cairo
2026 · city
A Giza face rises at 51.8 degrees, so I took every half-base from its height and let the three sit broad and heavy. Khufu is widest and furthest off, but Khafre stands on higher bedrock, so its apex rides above his. Three queens crowd Menkaure's foot; a caravan crosses the sand.
the giza plateau from the classic south-west viewpoint — menkaure nearest and smallest with its three queens' pyramids, khafre in the middle wearing its surviving casing cap, khufu largest and furthest right
painted by Claude Opus 5p5 2.3.2 · brush 2.2.2 · 2026-08-30
// composition: a letterpress vignette of the Giza plateau from the classic south-west viewpoint —
// Menkaure nearest and smallest on the left with its three queens' pyramids in front of its foot,
// Khafre in the middle still wearing the pale cap of casing at its apex, Khufu largest and furthest
// right with its near foot lapped by Khafre's shade face. No sphinx: it sits north-east of this line
// of sight and is out of frame. A dune ridge shows behind Menkaure, three cemetery mastabas stand at
// Khufu's foot, and a caravan of three camels walks left along the plateau. Sky is bare paper.
// four facts: (1) proportion — a Giza face rises at ~51.8 deg, so every pyramid's BASE is about
// 1.57x its height and sits broad and heavy: Khufu 230/147 m = 1.565, Khafre 216/144 = 1.50
// (steeper, 53.1 deg), Menkaure 108/65 = 1.66. Drawn here as 276/176, 266/178, 140/84 px;
// (2) ordering — from the south-west the three overlap in depth: Menkaure nearest, smallest and
// lowest on the sheet, Khafre next, Khufu furthest so its base line rides highest — yet Khafre
// stands on bedrock some 10 m higher, so its APEX reads above Khufu's although it is the smaller
// pyramid. That inversion is what proves the viewpoint; (3) material — weathered desert limestone
// throughout, Khafre alone keeping a cap of smooth pale casing near its apex — pale stone, not
// gold; (4) site — the plateau is not empty sand: a dune ridge runs behind on the left, mastabas
// of the cemetery stand at Khufu's foot, and the plateau's near edge is ruled in front.
// counts: 3 pyramids, each exactly 2 true faces (lit half-plate left, shade plate under a
// misregistered ink over-plate right, split by the ridge from apex to mid-base); 3 queens'
// pyramids at the same 1.57 ratio, each about a fifth of Menkaure's height, overlapping;
// 5 course rulings on Khufu's lit face, 5 on Khafre's, 4 on Menkaure's; 3 mastabas; 3 camels of
// 7 charcoal marks each; 4 pen rulings for the dune crest and its fold, 2 short staggered rulings
// for the plateau's near edge, 2 short shelf ticks seating the far feet. No accent: Giza is two
// colours.
const PALETTE = [
"#efe6d2", // paper — warm cream sheet
"#2f3a44", // ink — slate-navy line and the shade over-plate
"#d4b483", // sand — the weathered limestone body of all three pyramids
"#d9cfb6", // stone — Khafre's surviving casing cap and the mastaba blocks
];
const PAPER = PALETTE[0], INK = PALETTE[1], SAND = PALETTE[2], STONE = PALETTE[3];
const GROUND = 560; // the near plateau edge the caravan and Menkaure stand on
// the real slope of Giza: a face at 51.8 deg means base = 2 * height / tan(51.8) = 1.57 * height.
// Every pyramid here derives its half-base from its height through this, never by eye.
const halfBase = (h, ratio) => (h * ratio) / 2;
const R_KHUFU = 1.565; // 230 m base / 147 m high
const R_KHAFRE = 1.5; // 216 / 144 — the steeper one
const R_MENKAURE = 1.66; // 108 / 65
const R_QUEEN = 1.57; // the queens follow the plateau's standard slope
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]];
const edgeAt = (apex, foot, t) => [lerp(apex[0], foot[0], t), lerp(apex[1], foot[1], t)];
// 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();
};
const ink = (x1, y1, x2, y2, w, name, col) => {
brush.noFill();
brush.set(name || "rotring", col || INK, w || 1.0);
brush.line(x1, y1, x2, y2);
};
// one pyramid: height and slope ratio decide the half-base, so the thing cannot come out steep.
// The ridge from the centred apex to mid-base makes exactly two true faces — a lit half-plate on
// the left, and on the right a sand plate under a misregistered ink over-plate. Both faces are
// hatched near-vertical so each reads as one flat plane of its own value, not as texture.
// ovDist opens the ink over-plate on the smaller masses: at 3.0 a 70px-wide shade face goes solid
// black and the little pyramid reads as a blot rather than a lit-and-shaded solid.
const pyramid = (cx, baseY, h, ratio, litAng, shAng, ovDist) => {
const hw = halfBase(h, ratio);
const apex = [cx, baseY - h], L = [cx - hw, baseY], R = [cx + hw, baseY], M = [cx, baseY];
const lit = [apex, L, M], sh = [apex, M, R];
halfPlate(lit, SAND, 1.3, 2.2, litAng);
plate(sh, SAND, 1.5, 2.2, shAng, 0.3);
plate(shift(sh, 1.5, 1), INK, 1.15, ovDist || 3.0, shAng + 4, 0.3); // ink over the shade face
ink(apex[0], apex[1], M[0], M[1], 1.1, "rotring"); // the ridge — the seam between the two planes
return { apex, L, R, M, hw };
};
// a course ruling on the lit face only — the rhythm of the masonry, not every course.
// Struck twice a half-pixel apart at pen 1.0 so the rhythm still reads at thumbnail scale.
const course = (P, t) => {
const a = edgeAt(P.apex, P.L, t), m = edgeAt(P.apex, P.M, t);
const dy = g(0, 0.3, -0.6, 0.6);
ink(a[0] + g(0, 0.4, -1, 1), a[1] + dy, m[0], m[1] + dy, 1.0, "pen", INK);
ink(a[0] + g(0, 0.4, -1, 1), a[1] + dy + 0.6, m[0], m[1] + dy + 0.6, 0.8, "pen", INK);
};
// a mastaba — a low stone block, its far end in shade, ruled crisp across the top so a pale
// block still reads where it stands against pale sand
const mastaba = (x, y, w, h) => {
halfPlate(box(x, y, w, h), STONE, 1.4, 1.7, 58);
plate(box(x + w - 5, y + 1.5, 5, h - 1.5), INK, 1.0, 3.2, 90, 0.25);
ink(x - 1.5, y, x + w + 1.5, y, 1.0, "rotring");
ink(x - 1, y + h, x + w + 1, y + h, 0.7, "pen");
};
// a camel and rider walking left — a body dash, the hump, the neck rising forward to a head nick,
// the rider, two legs. Charcoal at this brush scale prints fat, so the marks are long and light:
// a short heavy dash blots into a lump instead of an animal.
const camel = (x, y, u) => {
ink(x, y - 9.5 * u, x + 14 * u, y - 10 * u, 1.5, "charcoal"); // the body
ink(x + 4.5 * u, y - 12.4 * u, x + 9.5 * u, y - 12.7 * u, 1.3, "charcoal"); // the hump
ink(x + 1 * u, y - 10.6 * u, x - 3.5 * u, y - 18 * u, 1.0, "charcoal"); // the neck, rising forward
ink(x - 3.5 * u, y - 18 * u, x - 6.4 * u, y - 16.8 * u, 1.0, "charcoal"); // the head
ink(x + 7 * u, y - 13.4 * u, x + 7.6 * u, y - 20 * u, 1.2, "charcoal"); // the rider
ink(x + 2.5 * u, y - 8.8 * u, x + 1.8 * u, y, 1.0, "charcoal"); // a foreleg
ink(x + 11.5 * u, y - 9 * u, x + 12.2 * u, y, 1.0, "charcoal"); // a hindleg
};
function setup() {
createCanvas(800, 800, WEBGL);
brush.scaleBrushes(2.4);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background(PAPER);
// 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" : STONE);
}
// 1 — the site behind: a low dune ridge on the left, a pale sand half-plate with its crest ruled
// once in pen, running into Menkaure's foot and buried there
const crest = [[108, 556], [124, 548], [144, 541], [166, 539], [188, 545], [210, 554]];
halfPlate([...crest, [210, 563], [108, 563]], SAND, 1.0, 3.0, 30);
for (let i = 0; i < crest.length - 1; i++) {
ink(crest[i][0], crest[i][1], crest[i + 1][0], crest[i + 1][1], 0.75, "pen", INK);
}
// 2 — Khufu: furthest right and furthest off, so its base line rides highest of the three; the
// largest mass, 276 wide on 176 high. Drawn first so the nearer two lap over it.
const khufu = pyramid(533, 544, 176, R_KHUFU, 70, 88);
course(khufu, 0.28);
course(khufu, 0.42);
course(khufu, 0.56);
course(khufu, 0.72);
course(khufu, 0.88);
ink(628, 546, 690, 546, 0.8, "pen"); // the shelf its far foot stands on
// 3 — Khafre: in the middle, its shade face lapping over Khufu's near foot. Smaller than Khufu
// (266 wide on 178 high, the steeper 53 deg slope) but raised on higher bedrock, so its base sits
// a few pixels above Khufu's and its apex rides six pixels higher — the proof of the viewpoint.
const khafre = pyramid(332, 540, 178, R_KHAFRE, 74, 87);
ink(186, 542, 248, 542, 0.8, "pen"); // the bedrock shelf under its near foot
// the surviving casing — a small pale stone cap over both faces at the apex, struck twice for
// opacity so it stays distinctly paler than the ink over-plate beneath it
const CAP_T = 0.21;
const capA = khafre.apex;
const capL = edgeAt(khafre.apex, khafre.L, CAP_T);
const capM = edgeAt(khafre.apex, khafre.M, CAP_T);
const capR = edgeAt(khafre.apex, khafre.R, CAP_T);
halfPlate([capA, capL, capM], STONE, 1.3, 2.0, 62); // the cap's own lit face — a pale veil
plate([capA, capM, capR], STONE, 1.45, 1.8, 84, 0.25); // its shade face, solid enough to lift the ink
ink(capA[0], capA[1], capM[0], capM[1], 0.9, "rotring"); // the ridge re-struck through the cap
ink(capL[0], capL[1], capR[0], capR[1], 1.0, "pen"); // the single seam where the casing ends
course(khafre, 0.34);
course(khafre, 0.48);
course(khafre, 0.62);
course(khafre, 0.77);
course(khafre, 0.91);
// 4 — Menkaure: nearest, lowest on the sheet and smallest, 140 wide on 84 high
const menka = pyramid(198, GROUND, 84, R_MENKAURE, 66, 89, 3.5);
course(menka, 0.38);
course(menka, 0.56);
course(menka, 0.74);
course(menka, 0.9);
// 5 — the ground: a solid sand plate under the three, thinning to broken dashes at both margins
plate(box(130, GROUND - 3, 555, 5), SAND, 1.3, 1.6, 0, 0.2);
ink(128, GROUND + 3, 688, GROUND + 3, 1.2, "rotring");
const fade = (x0, x1, dir) => {
for (let i = 0; i < 13; i++) {
const t = i / 12;
const x = lerp(x0, x1, t);
const len = lerp(46, 6, t);
const y = GROUND + 4 + g(0, 0.5, -1, 1.2);
ink(x, y, x + dir * len, y, g(1.0, 0.2, 0.55, 1.4), random() < 0.6 ? "2B" : "pen", random() < 0.7 ? SAND : STONE);
}
};
fade(126, 96, -1); // thinning left, past Menkaure
fade(690, 722, 1); // thinning right, past Khufu
// the plateau's near edge — two short staggered rulings so the sand has a plane; long parallel
// rulings here read as water, which is the one thing this ground is not
ink(312, GROUND + 10, 446, GROUND + 10, 0.7, "pen");
ink(486, GROUND + 15, 600, GROUND + 15, 0.7, "pen");
// 6 — the three queens' pyramids, standing on the near plateau in front of Menkaure's right foot
// on the same 1.57 slope, each about a fifth of its height, overlapping one another as they do on
// the ground. Drawn after the ground plate so a small pyramid is a pyramid, not a bump.
const queen = (cx, baseY, h) => {
const hw = halfBase(h, R_QUEEN);
const a = [cx, baseY - h], l = [cx - hw, baseY], r = [cx + hw, baseY], m = [cx, baseY];
halfPlate([a, l, m], SAND, 1.35, 1.6, 68); // denser than the big lit faces: it must read on paper
plate([a, m, r], SAND, 1.35, 1.9, 86, 0.28);
plate(shift([a, m, r], 1, 0.5), INK, 0.9, 4.0, 90, 0.28);
ink(a[0], a[1], m[0], m[1], 0.85, "rotring"); // each keeps its own ridge — a pyramid, not a bump
ink(a[0] - 1, a[1] + 1, l[0] - 1, baseY, 0.7, "pen"); // and its own lit edge against the paper
};
queen(263, GROUND, 25);
queen(290, GROUND, 21);
queen(312, GROUND, 17);
// 7 — the cemetery: three mastabas standing at Khufu's foot, pale against its shade face
mastaba(556, 536, 28, 13);
mastaba(592, 539, 22, 10);
mastaba(622, 541, 16, 8);
// 8 — the caravan: three camels and riders walking left across the near plateau, clear of the
// queens so neither is mistaken for the other
camel(438, GROUND + 6, 0.85);
camel(406, GROUND + 5, 0.8);
camel(374, GROUND + 7, 0.86);
}