Hare Pressed Flat
2026 · fauna
Patience is a direction, not a duration: every hair on this animal lies the same way, and that is the only reason it can vanish into a field of nothing.
a hare crouched flat in old snow on the lower-left thirds crossing, ears laid back along its spine, the coat described hair by hair, the rest of the field left as bare paper with two faint drift lines
painted by Mistral Medium 3.1p5 2.3.2 · brush 2.2.2 · 2026-08-25
// composition: the hare presses flat into the lower-left thirds crossing, nose to the left, ears laid back along its own spine
// layering: the cool hollow in the crust first, then the body built as one warm tonal mass, then fur tract by tract, then the eye and the ear tips struck in last
// the paper: the whole upper right stays open snow, crossed only by two faint drift lines
const PALETTE = [
"#eceae3", // ground — old snow, warm off-white paper
"#aab2b7", // shadow — the cool grey pooling in the hollow
"#96795a", // mass — winter ochre, the lit fur
"#57452f", // umber — the dark under the guard hairs
"#2b261f", // ink — eye, ear tips, the last darks
"#a85731", // accent — rust at the nose and in the ear throat, rationed
];
const SNOW = PALETTE[0];
const SHADOW = PALETTE[1];
const OCHRE = PALETTE[2];
const UMBER = PALETTE[3];
const INK = PALETTE[4];
const RUST = PALETTE[5];
// the crouch, disc by disc: nose, muzzle, cheek, nape, shoulder, the low dome of the back, haunch, rump, tail
const SPINE = [
[126, 570, 13], [152, 562, 24], [184, 550, 34], [220, 538, 42], [266, 526, 48],
[318, 520, 50], [370, 528, 48], [412, 548, 36], [438, 566, 17],
];
let MASK = null;
// the silhouette as a chain of overlapping discs — everything else asks this where the fur is
function buildMask(spine) {
const pts = [];
for (let i = 0; i < spine.length - 1; i++) {
for (let k = 0; k < 6; k++) {
const t = k / 6;
pts.push([
lerp(spine[i][0], spine[i + 1][0], t),
lerp(spine[i][1], spine[i + 1][1], t),
lerp(spine[i][2], spine[i + 1][2], t),
]);
}
}
pts.push([spine[spine.length - 1][0], spine[spine.length - 1][1], spine[spine.length - 1][2]]);
return pts;
}
function inBody(x, y, pad) {
for (let i = 0; i < MASK.length; i++) {
if (dist(x, y, MASK[i][0], MASK[i][1]) < MASK[i][2] + pad) return true;
}
return false;
}
// pigment for one hair: ochre mostly, umber often, ink now and then — about half the tips bleached toward the snow
function furInk(shade) {
const r = random();
const base = r < 0.54 ? OCHRE : r < 0.87 ? UMBER : INK;
if (random() < 0.38) {
return lerpColor(color(base), color(SNOW), constrain(randomGaussian(shade * 0.8, 0.1), 0, 0.78));
}
return lerpColor(color(base), color(INK), constrain(randomGaussian(0.5, 0.18), 0, 0.86));
}
// mostly soft graphite, with a few clean pen hairs so the coat reads as hair and not as grain
function pencil() {
const r = random();
if (r < 0.44) return "2B";
if (r < 0.7) return "cpencil";
if (r < 0.87) return "HB";
return r < 0.96 ? "pen" : "2H";
}
// one hair: a stroke that leaves the parting, rakes back, and lifts a little at the tip
function hair(x, y, ang, len, col, weight, curl) {
const mx = x + cos(ang) * len * 0.55;
const my = y + sin(ang) * len * 0.55;
brush.set(pencil(), col, weight);
brush.noFill();
brush.beginShape(0.55);
brush.vertex(x, y);
brush.vertex(mx, my);
brush.vertex(mx + cos(ang + curl) * len * 0.5, my + sin(ang + curl) * len * 0.5);
brush.endShape(false);
}
// a tract of fur: hairs scattered through one soft ellipse, all lying the same way with a little fan
function tract(cx, cy, rx, ry, ang, fan, count, len, shade, weight, free) {
const n = min(200, count);
for (let i = 0; i < n; i++) {
const x = cx + randomGaussian(0, rx * 0.5);
const y = cy + randomGaussian(0, ry * 0.5);
if (!free && !inBody(x, y, -4)) continue;
hair(
x, y,
ang + randomGaussian(0, fan),
max(4, randomGaussian(len, len * 0.32)),
furInk(shade),
weight * (1 + randomGaussian(0, 0.24)),
randomGaussian(0, 9)
);
}
}
// a longer guard hair, placed by hand where the coat breaks the contour
function guard(x, y, ang, len, shade, weight) {
hair(x, y, ang + randomGaussian(0, 4), len * (1 + randomGaussian(0, 0.12)),
furInk(shade), weight, randomGaussian(-5, 7));
}
// a genuinely dark note: watercolour fills stay translucent at this size, so the darks are struck
// in as a tight sheaf of pen strokes all lying one way
function darkNote(x, y, len, wide, ang, col, weight, count) {
brush.noFill();
const n = min(40, count);
for (let i = 0; i < n; i++) {
const off = randomGaussian(0, wide * 0.4);
const px = x + cos(ang + 90) * off + randomGaussian(0, 0.8);
const py = y + sin(ang + 90) * off + randomGaussian(0, 0.8);
const l = max(2.5, randomGaussian(len, len * 0.28));
const a = ang + randomGaussian(0, 10);
brush.set(random() < 0.62 ? "pen" : "rotring", col, weight * (1 + randomGaussian(0, 0.18)));
brush.line(px - cos(a) * l * 0.5, py - sin(a) * l * 0.5, px + cos(a) * l * 0.5, py + sin(a) * l * 0.5);
}
}
// a pool of cold in the snow: one wide pale disc, lifted toward the paper by `lift`
function pool(x, y, r, lift, alpha) {
brush.fill(lerpColor(color(SHADOW), color(SNOW), lift), alpha);
brush.circle(x, y, r, true);
}
// one whisker, and one drift line: both are three points and a weight
function faintCurve(pts, tool, col, weight) {
brush.noFill();
brush.set(tool, col, weight);
brush.beginShape(0.62);
for (let i = 0; i < pts.length; i++) brush.vertex(pts[i][0], pts[i][1]);
brush.endShape(false);
}
// the body built as one tonal mass, back-to-front: each pass tighter and warmer, the underside bleached by the snow it lies on
function wash(scale, alpha, tone, lift) {
brush.noStroke();
brush.fillBleed(0.1, "out");
brush.fillTexture(0.55, 0.5);
for (let i = 1; i < MASK.length; i += 2) {
const p = MASK[i];
const low = constrain((p[1] - 508) / 74, 0, 1);
brush.fill(
lerpColor(color(tone), color(SNOW), constrain(randomGaussian(lift + low * 0.44, 0.08), 0, 0.9)),
max(40, randomGaussian(alpha, 16))
);
brush.circle(p[0] + randomGaussian(0, 3), p[1] + randomGaussian(0, 2.6), p[2] * scale, true);
}
}
// an ear laid flat back along the spine: a tapering blade darker than the coat, fur raked to the tip,
// a pale rim underneath to lift it off the back, and a black tip
function ear(bx, by, tx, ty, wBase, wMid, tone, alpha, rimWeight) {
const ang = atan2(ty - by, tx - bx);
const nx = cos(ang + 90);
const ny = sin(ang + 90);
const up = [];
const down = [];
for (let i = 0; i <= 5; i++) {
const t = i / 5;
const x = lerp(bx, tx, t) + randomGaussian(0, 1.8);
const y = lerp(by, ty, t) + randomGaussian(0, 1.8);
const w = lerp(wBase, wMid, min(1, t * 1.7)) * (1 - pow(t, 3) * 0.8);
up.push([x + nx * w * 0.5, y + ny * w * 0.5]);
down.push([x - nx * w * 0.5, y - ny * w * 0.5]);
}
brush.noStroke();
brush.fillBleed(0.22, "out");
brush.fillTexture(0.5, 0.5);
brush.fill(tone, alpha);
brush.beginShape(0.5);
for (let i = 0; i < up.length; i++) brush.vertex(up[i][0], up[i][1]);
for (let i = down.length - 1; i >= 0; i--) brush.vertex(down[i][0], down[i][1]);
brush.endShape(true);
// the fur of the ear, every hair raking to the tip
tract(lerp(bx, tx, 0.22), lerp(by, ty, 0.22), 32, 9, ang, 7.2, 54, 13, 0.08, 1.3, true);
tract(lerp(bx, tx, 0.52), lerp(by, ty, 0.52), 30, 8, ang, 6, 48, 12, 0.04, 1.25, true);
tract(lerp(bx, tx, 0.8), lerp(by, ty, 0.8), 24, 6, ang, 6, 34, 11, 0.1, 1.15, true);
// the pale underside rim: the one line that lifts the ear off the back
brush.noFill();
brush.set("cpencil", lerpColor(color(SNOW), color(OCHRE), 0.14), rimWeight * 1.6);
brush.beginShape(0.5);
for (let i = 1; i < down.length; i++) brush.vertex(down[i][0] + 1.5, down[i][1] + 2.5);
brush.endShape(false);
faintCurve([up[0], up[1], up[2], up[3]], "2B", lerpColor(color(INK), color(UMBER), 0.44), rimWeight);
faintCurve([up[3], up[4], up[5]], "2B", lerpColor(color(INK), color(UMBER), 0.26), rimWeight * 0.9);
darkNote(tx - cos(ang) * 6, ty - sin(ang) * 6, 14, 9, ang, INK, 1.1, 12);
}
function setup() {
createCanvas(800, 800, WEBGL);
angleMode(DEGREES);
brush.scaleBrushes(2.4);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background(SNOW);
MASK = buildMask(SPINE);
brush.noStroke();
brush.fillBleed(0.6, "out");
brush.fillTexture(0.35, 0.4);
// the cold of the field, laid in wide and pale — heaviest under and behind the hare
pool(286, 652, 238, 0.42, 34);
pool(534, 676, 198, 0.54, 26);
pool(660, 440, 158, 0.66, 17);
// the hollow the hare has pressed into the crust: cool grey, wide and quiet, gathering along the underside
brush.fillBleed(0.62, "out");
brush.fillTexture(0.4, 0.4);
pool(268, 606, 96, 0.16, 82);
pool(364, 612, 102, 0.12, 90);
pool(452, 606, 84, 0.28, 62);
pool(174, 598, 72, 0.32, 54);
pool(330, 674, 128, 0.46, 34);
// the coat as one mass: a pale wash, the warm body, then a tighter umber core along the spine
wash(0.94, 152, lerpColor(color(OCHRE), color(UMBER), 0.3), 0.18);
wash(0.82, 172, lerpColor(color(OCHRE), color(UMBER), 0.52), 0.02);
wash(0.54, 152, UMBER, 0.0);
// the head takes two more washes — a hare's face is the tightest, darkest part of the coat
brush.noStroke();
brush.fillBleed(0.2, "out");
brush.fillTexture(0.5, 0.5);
brush.fill(lerpColor(color(OCHRE), color(UMBER), 0.6), 158);
brush.circle(194, 552, 26, true);
brush.fill(lerpColor(color(OCHRE), color(UMBER), 0.3), 128);
brush.circle(162, 562, 19, true);
// ears back, the far one first — the far blade clears the ridge of the back, the near one lies on the shoulder
ear(224, 526, 384, 452, 27, 33, lerpColor(color(UMBER), color(OCHRE), 0.3), 182, 1.8);
ear(230, 544, 366, 480, 26, 30, lerpColor(color(UMBER), color(OCHRE), 0.52), 164, 1.5);
// fur, tract by tract — every hair lies back from the nose, then fans down the flank to the snow
tract(152, 564, 20, 12, 8, 12, 54, 9, 0.42, 1.32); // muzzle, short and bleached
tract(192, 540, 24, 14, 26, 13, 76, 12, 0.18, 1.5); // brow, raked up over the eye
tract(190, 558, 22, 18, 14, 14, 84, 11, 0.24, 1.5); // cheek, back toward the jaw
tract(214, 580, 20, 14, 62, 16, 62, 10, 0.4, 1.32); // throat, dropping to the crust
tract(230, 540, 28, 20, 8, 12, 96, 13, 0.1, 1.62); // nape, the coat thickening
tract(274, 522, 38, 22, 6, 11, 126, 15, 0.06, 1.68); // shoulder, level and long
tract(324, 504, 44, 15, 14, 10, 140, 16, 0.02, 1.74); // the ridge of the back, darkest
tract(310, 550, 46, 26, 30, 18, 150, 12, 0.14, 1.56); // mid flank, fanning down and back
tract(374, 524, 34, 30, 26, 12, 128, 16, 0.06, 1.68); // haunch, over the hidden thigh
tract(388, 560, 30, 26, 46, 15, 100, 14, 0.14, 1.5); // thigh turning under
tract(418, 556, 22, 24, 54, 17, 80, 13, 0.1, 1.56); // rump, whorled toward the tail
tract(312, 582, 54, 14, 58, 18, 120, 11, 0.12, 1.44); // belly, pressed flat and in its own shade
tract(234, 580, 22, 14, 74, 16, 60, 10, 0.08, 1.44); // chest against the crust
// the seam of the spine, the crease at the haunch, and the light the snow throws back up
tract(330, 508, 40, 8, 15, 7, 80, 13, 0.0, 1.68); // spine seam, darker than anything either side
tract(300, 512, 30, 7, 12, 6, 60, 12, 0.0, 1.62); // and again, a little forward of it
tract(348, 548, 13, 26, 62, 10, 52, 11, 0.02, 1.5); // the crease where the haunch shuts
tract(300, 574, 50, 12, 44, 17, 112, 10, 0.0, 1.56); // the shadow the body casts on itself
tract(316, 590, 48, 7, 52, 16, 76, 7, 0.74, 1.2); // one narrow band of snow-light at the contact
tract(190, 560, 20, 16, 16, 14, 70, 10, 0.18, 1.56); // cheek again, over the wash
tract(164, 576, 16, 10, 32, 14, 48, 8, 0.28, 1.32); // jaw, short hair toward the throat
// the contour: short fur standing off the back and rump so the edge stays fur, not a drawn line
guard(244, 504, -8, 15, 0.1, 1.44);
guard(266, 494, 0, 17, 0.04, 1.56);
guard(290, 484, -6, 18, 0.0, 1.62);
guard(312, 478, 6, 17, 0.06, 1.5);
guard(334, 478, 12, 16, 0.02, 1.56);
guard(356, 484, 20, 17, 0.08, 1.5);
guard(378, 494, 28, 16, 0.04, 1.44);
guard(396, 510, 38, 17, 0.12, 1.5);
guard(414, 528, 48, 15, 0.08, 1.44);
guard(430, 550, 60, 16, 0.16, 1.38);
guard(440, 570, 72, 13, 0.22, 1.32);
guard(226, 516, -16, 13, 0.14, 1.38);
guard(208, 530, 8, 12, 0.2, 1.26);
guard(186, 526, 30, 11, 0.24, 1.2);
// rust in the throat of the near ear — a warm note nobody is meant to notice
brush.noStroke();
brush.fillBleed(0.3, "out");
brush.fillTexture(0.6, 0.6);
brush.fill(RUST, 96);
brush.circle(256, 538, 12, true);
brush.fill(RUST, 66);
brush.circle(286, 528, 9, true);
// the eye: a bruised socket, a ring drawn round it, ink filled in, one grain of paper left dry
brush.noStroke();
brush.fillBleed(0.18, "out");
brush.fill(lerpColor(color(UMBER), color(INK), 0.6), 168);
brush.circle(201, 543, 15, true);
brush.noFill();
brush.set("2B", lerpColor(color(INK), color(UMBER), 0.12), 1.7);
brush.circle(200.5, 542, 8.4, true);
darkNote(200, 542, 9, 11, 26, INK, 1.3, 20);
darkNote(200, 542, 8, 10, 98, INK, 1.15, 12);
darkNote(204, 550, 7, 5, 44, RUST, 0.75, 4);
brush.noStroke();
brush.fill(lerpColor(color(SNOW), color(OCHRE), 0.1), 232);
brush.circle(196.8, 538.4, 2.7, true);
// the nose, the fold of the lid, the line of the jaw, the tucked foreleg
darkNote(129, 568, 10, 8, 34, lerpColor(color(INK), color(RUST), 0.3), 1.2, 10);
faintCurve([[189, 532], [202, 529], [215, 536]], "2B", lerpColor(color(INK), color(UMBER), 0.2), 1.6);
faintCurve([[146, 576], [170, 584], [194, 588]], "2B", lerpColor(color(INK), color(UMBER), 0.4), 1.5);
faintCurve([[226, 566], [232, 578], [228, 588]], "2B", lerpColor(color(INK), color(UMBER), 0.5), 1.4);
// the darks come last and hold the hare down: the coat meeting its own shadow, broken all along
brush.set("2B", lerpColor(color(INK), color(UMBER), 0.3), 1.6);
brush.line(160, 584, 184, 590);
brush.line(196, 592, 218, 594);
brush.set("2B", lerpColor(color(INK), color(UMBER), 0.22), 1.9);
brush.line(230, 594, 270, 597);
brush.line(284, 596, 328, 594);
brush.set("2B", lerpColor(color(INK), color(UMBER), 0.38), 1.7);
brush.line(342, 594, 382, 596);
brush.line(394, 592, 424, 586);
brush.set("2B", lerpColor(color(INK), color(SHADOW), 0.36), 1.3);
brush.line(258, 598, 288, 599);
brush.line(370, 598, 398, 594);
brush.line(428, 580, 442, 570);
// the crust of old snow: short cool dashes, thick around the hollow, thinning away to the right
for (let i = 0; i < 96; i++) {
const x = constrain(randomGaussian(320, 210), 18, 782);
const y = constrain(randomGaussian(650, 122), 400, 786);
if (random() < (x - 60) / 800) continue;
if (inBody(x, y, 12)) continue;
const a = randomGaussian(4, 12);
const len = max(5, randomGaussian(16, 7));
brush.set("2H", lerpColor(color(SHADOW), color(SNOW), constrain(randomGaussian(0.3, 0.2), 0, 0.86)), 1.1);
brush.line(x, y, x + cos(a) * len, y + sin(a) * len);
}
// two drift lines, and nothing else in all that snow
faintCurve([[88, 288], [268, 310], [452, 296], [628, 268], [806, 244]],
"2H", lerpColor(color(SHADOW), color(SNOW), 0.18), 2.0);
faintCurve([[398, 712], [548, 730], [688, 712], [806, 684]],
"2H", lerpColor(color(SHADOW), color(SNOW), 0.34), 1.6);
// three whiskers, out into the empty paper where the eye should travel
faintCurve([[140, 560], [100, 548], [62, 538]], "pen", lerpColor(color(INK), color(SNOW), 0.36), 1.0);
faintCurve([[137, 570], [96, 580], [56, 598]], "pen", lerpColor(color(INK), color(SNOW), 0.5), 0.9);
faintCurve([[144, 554], [112, 532], [88, 516]], "pen", lerpColor(color(INK), color(SNOW), 0.62), 0.85);
}