Лувунн находится примерно на 66 градусе северной широты, достаточно близко к Полярному кругу, чтобы летними ночами небо почти не темнело, а зимой большую часть дня съедала темнота. Мне хотелось, чтобы тёмная тема сайта отражала это: настоящее небо, а не обычную россыпь белых точек, которая могла бы быть где угодно. Как только я начал по-настоящему разбираться, как выглядит небо на этой широте, вариант «обычная россыпь точек» перестал казаться мне достаточно хорошим, чтобы его выкладывать.

сначала точки, потом уже небо
Первая версия ночного фона была ровно тем, чем я только что сказал, что не хочу её видеть: пиксельный спрайт со звёздами, разбросанный более или менее равномерно по краям, и только в тёмной теме:
// first version: just scatter dots, no real positions
const stars = Array.from({ length: 40 }, () => ({
x: Math.random() * width,
y: Math.random() * height
}));Неплохо, атмосферно, но совершенно не запоминается. Мнение поменялось, когда я понял: с этой широты, особенно зимой, почти в любую ясную ночь реально можно разглядеть Большую Медведицу и Кассиопею, и они всё время крутятся вокруг Полярной звезды, потому что на 66 градусе они вообще никогда не опускаются за горизонт, это околополярные созвездия. Это реальный, конкретный, проверяемый факт о том, откуда пишется этот сайт, и обычная россыпь звёзд просто выбрасывала его без всякой причины. Так что случайный разброс остался только для фоновых звёзд, а у именованных созвездий появились настоящие координаты.
настоящие звёзды на правильных местах
Я взял реальные опубликованные значения прямого восхождения и склонения для ярких звёзд обоих созвездий, те же координаты, которыми пользуется любое приложение-планетарий, и спроецировал их так, как это делают карты околополярных звёзд: угол каждой звезды вокруг полюса берётся из её прямого восхождения в градусах, а расстояние от полюса, это девяносто минус склонение.
Approximate RA/Dec used (hours/minutes, degrees/minutes):
Polaris 2h31m +89 15 Dubhe 11h03m +61 45 Merak 11h01m +56 23
Phecda 11h53m +53 41 Megrez 12h15m +57 02 Alioth 12h54m +55 57
Mizar 13h23m +54 55 Alkaid 13h47m +49 19Семь звёзд Ковша и пять звёзд Кассиопеи после такой проекции оказываются почти точно по разные стороны от Полярной звезды, что соответствует реальности: их средние прямые восхождения расходятся примерно на 170 градусов. А это заодно означает, что классический любительский трюк, провести линию от Мерака через Дубхе, и она укажет на Полярную звезду, сработал сам собой. Мне не пришлось подгонять ни одну звезду, чтобы эта указующая линия получилась, это просто то, что дают настоящие координаты, если спроецировать их честно.
const DIPPER: Star[] = [
{ x: 1479.6, y: 391.3, r: 1.8, fill: MUTED, opacity: 0.6, twinkle: '-2.1s' }, // Dubhe (pointer star)
{ x: 1475.3, y: 380.3, r: 1.7, fill: MUTED, opacity: 0.6 }, // Merak (pointer star)
{ x: 1490.8, y: 370.6, r: 1.6, fill: SEA, opacity: 0.55 }, // Phecda
{ x: 1498.6, y: 377.5, r: 1.5, fill: SEA, opacity: 0.5 }, // Megrez
{ x: 1511.2, y: 375.9, r: 1.6, fill: MUTED, opacity: 0.55 }, // Alioth
{ x: 1521.1, y: 375.8, r: 1.5, fill: SEA, opacity: 0.5 }, // Mizar
{ x: 1533.4, y: 366.9, r: 1.7, fill: MUTED, opacity: 0.55 } // Alkaid
];Всё, что за пределами этого именованного скопления, остаётся обычными фоновыми звёздами, намеренно нерегулярными по размеру и расстоянию, а не выстроенными по сетке, так что два настоящих созвездия действительно выделяются на фоне окружающего разброса, а не сливаются с ним в одну кашу.
поле медленно вращается
Полярная звезда лежит достаточно близко к настоящему небесному полюсу, чтобы околополярные звёзды заметно вращались вокруг неё за ночь, поэтому я сделал так, чтобы вращалось и всё скопление целиком, один полный оборот за 40 минут, а точкой трансформации служат координаты самой Полярной звезды. При обычном масштабе просмотра, когда кто-то читает пост блога пару минут, всё это выглядит совершенно неподвижным. Но если оставить вкладку открытой в фоне на час, вращение становится заметным, точно так же, как настоящее небо над фьордом, если пробыть на улице достаточно долго, чтобы это заметить. Я дал вращаться только настоящему скоплению, а не фоновым звёздам: у них нет реальных позиций, которые стоило бы сохранять, да и большинство из них слишком далеко от Полярной звезды, чтобы медленное вращение вокруг этой точки вообще читалось как движение.
луна, и умение вовремя остановиться
В сцене есть ещё и едва заметная луна, прозрачная днём, мягкая бледная форма, вставленная в ночное небо над горизонтом в шапке сайта. Собственно, на этом всё, что есть в этой сцене. Ни северного сияния, ни падающих звёзд, ни слоя облаков, который бы пока плыл мимо (облака есть в списке, лежат в той же куче «может быть, потом», что и дальнейшее расширение стаи птиц). Я постоянно порываюсь добавить ещё что-нибудь и постоянно сам себя останавливаю, потому что настоящее небо на этой широте уже само делает всю интересную работу, а моя задача тут ближе к тому, чтобы не мешать ему, чем к тому, чтобы его дальше украшать.

сдержанность не по желанию
И мерцание, и всё вращение на 40 минут подчиняются prefers-reduced-motion, полностью замирая для тех, у кого эта настройка включена, точно так же, как фоновые птицы и рыбы по всему остальному сайту. И весь этот слой в принципе существует только в тёмной теме: в дневном режиме небо прозрачное и без луны вообще, потому что нет такой версии видимого созвездия при полном свете дня, которая читалась бы иначе, чем ошибка. Иногда сдержанность, это просто соответствие тому, как небо выглядит на самом деле, а не поиск повода нарисовать побольше.
почему именно 66 градусов меняют ответ
Большая часть софта, написанного где угодно, по умолчанию читается как ничейная, без привязки к месту, и большинство звёздных фонов, которые я видел в других местах, идут по этому же пути: равномерно разбросанные точки, которые могли бы висеть над любым городом на земле. Для большинства вещей это разумный вариант по умолчанию, но здесь он выбрасывает единственную часть, которая по-настоящему связана с тем, откуда пишется этот сайт. Примерно на 60 градусах северной широты, это где-то широта Осло, Большая Медведица всё ещё восходит и заходит, как большинство созвездий южнее, опускаясь за горизонт на часть ночи или года в зависимости от сезона. А начиная с 66 градусов и выше звёзды, достаточно близкие к небесному полюсу, просто перестают заходить вообще, они там наверху крутятся вокруг Полярной звезды в любое время суток. Это не мелкая эстетическая деталь, это настоящая причина, по которой у этой сцены есть медленное, бесконечное вращение вокруг фиксированной точки, а не созвездие, которое восходит, пересекает небо и заходит, как солнце. Вращение на 40 минут, это не украшение, наложенное поверх идеи, это сама идея, переведённая напрямую из конкретного факта об этой конкретной широте в CSS-трансформацию.
мерцание делает и более тихую работу
У горстки звёзд, и в настоящем скоплении, и в фоновом разбросе, включая саму Полярную, есть собственное независимое мерцание прозрачности по медленному, разному циклу, у каждой своя задержка, так что они никогда не мигают синхронно. Это небольшая деталь, и её легко списать на чистую атмосферность, но на деле она делает ту же работу, что и случайные задержки анимации по всему остальному сайту: не даёт всему выглядеть статичной картинкой с одной CSS-анимацией, натянутой единообразно на каждый элемент. Небо, где каждая звезда мигает в унисон, выглядело бы как индикатор загрузки. Небо, где мигают независимо несколько звёзд вразнобой, читается как что-то куда более похожее на настоящий звёздный свет.