birds

September 3, 2026 meta devlogpixelart

it started with puffins

Lovund has one of the biggest puffin colonies in Norway, so once I had the mountain in place it felt wrong not to put a puffin somewhere near it. Drew one small pixel puffin, gave it a slow flight path across the header, and reloaded the page about thirty times just watching it go.

Then I thought, well, one bird looks a little lonely.

and then it didn't stop

Added a gull. Then another gull that flies differently. Then I got a bit carried away: an oystercatcher walking along what's basically the shoreline, a tern doing quick darting movement instead of the slow glide the gulls get, a cormorant sitting low over the water, and one sea eagle that's bigger and rarer, shows up way less often than the rest.

Each species got its own size, speed, and how often it actually spawns in. The sea eagle in particular I set to be pretty uncommon, felt right for a bird that big.

The footer strip: perched puffins and an oystercatcher along the shoreline

the cap, because it got silly

At one point I had it spawning birds pretty freely and reloading the page gave me something like fifteen birds all fighting for airspace over one small header. Looked more like a Hitchcock movie than a coastal blog. Capped it with a small guard baked into the draw itself:

const guaranteed = CANDIDATES.find((c) => c.guaranteed)!;
const rest = CANDIDATES.filter((c) => !c.guaranteed);
const shuffled = [...rest].sort(() => rand() - 0.5);
const extraCount = 2 + Math.floor(rand() * 3); // 2-4
const chosen = [guaranteed, ...shuffled.slice(0, extraCount)];

fish, and a mixup that was on me

Added fish under the waterline too, little pixel shapes swimming back and forth below where the birds fly. At one point early on I had a bug where one of the "birds" was actually rendering below the waterline layer, and I spent a good ten minutes confused why a bird kept looking like it was swimming. Turned out I'd never actually pinned each layer to its own band:

// first pass: one shared list, whatever top value felt right per sprite
const sprites = [...birds, ...fish].map((s) => ({ ...s, top: s.top }));

Nothing stopped a bird's top from landing below the waterline by accident since they were all just numbers in the same pile. Fixed it by pinning each layer to its own band instead of trusting whatever value I'd typed in:

// row 3's top edge, 60% down the strip's own height, is literally
// "the waterline" in this layer's own geometry. Humans stand in the
// land band (top 0-60%); fish swim in the sea band (top 60-100%).
//
// Caps: fish <= 3 (here: 3), humans <= 2 (here: 2 -- one standing, one
// running).

Fixed now, birds fly, fish swim, they stay in their own lanes.

Also snuck in one tiny pixel runner jogging along the shore. Half joke, half just me putting myself in the scene, since running is a thing I actually do here on the island.

Every reload mixes up which birds show, how many fish, where the runner is. Small thing but it makes the header feel alive instead of static, which was the whole point.

1 comment

Log in to comment.

anna_k September 5, 2026

A puffin crossed the screen while I was reading and I grinned like an idiot. This is lovely.

Log in

No account?