началось всё с тупиков
На Лувунне одна из крупнейших колоний тупиков в Норвегии, так что, когда гора уже была на месте, показалось неправильным не поставить рядом хотя бы одного тупика. Нарисовал маленького пиксельного тупика, задал ему медленный маршрут полёта через шапку сайта и перезагружал страницу раз тридцать, просто чтобы посмотреть, как он летит.
А потом подумал: ну, одна птица выглядит немного одиноко.
а потом это уже было не остановить
Добавил чайку. Потом ещё одну чайку, которая летает иначе. Потом меня немного понесло: кулик-сорока, шагающий вдоль того, что по сути и есть береговая линия, крачка с быстрыми резкими движениями вместо медленного парения чаек, баклан, сидящий низко над водой, и один орлан, покрупнее и пореже, появляется куда реже остальных.
У каждого вида свой размер, своя скорость и своя частота появления. Орлана я специально сделал довольно редким, показалось правильным для такой крупной птицы.

потолок, потому что стало уже слишком
В какой-то момент птицы появлялись почти без ограничений, и после перезагрузки страницы можно было увидеть штук пятнадцать птиц, дерущихся за воздушное пространство над крошечной шапкой сайта. Больше похоже на фильм Хичкока, чем на блог про побережье. Поставил ограничение прямо в логике отрисовки:
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)];рыбы, и путаница на моей совести
Добавил ещё и рыб под линией воды, маленькие пиксельные силуэты, снующие туда-сюда ниже того места, где летают птицы. На раннем этапе была ошибка: одна из «птиц» на самом деле отрисовывалась ниже слоя воды, и я минут десять сидел в замешательстве, почему птица выглядит так, будто плывёт. Оказалось, я так и не закрепил каждый слой в своей полосе:
// first pass: one shared list, whatever top value felt right per sprite
const sprites = [...birds, ...fish].map((s) => ({ ...s, top: s.top }));Ничто не мешало значению top у птицы случайно оказаться ниже линии воды, потому что все они были просто числами в одной общей куче. Починил это, закрепив каждый слой в собственной полосе, вместо того чтобы полагаться на то, какое значение я туда когда-то вписал:
// 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).Теперь всё исправлено: птицы летают, рыбы плавают, каждый в своей полосе.
Ещё незаметно добавил одного крошечного пиксельного бегуна, трусцой вдоль берега. Наполовину шутка, наполовину я сам, вставленный в сцену, потому что бег, это то, чем я реально занимаюсь здесь, на острове.
Каждая перезагрузка перемешивает, какие птицы показаны, сколько рыб и где находится бегун. Мелочь, но именно она делает шапку сайта живой, а не статичной, в этом и была вся идея.