Команда JavaScript for Devs подготовила перевод истории о том, как маленькая деталь — favicon — может рассказать о продукте больше, чем кажется. От первой раздражённой мысли до полноценной игры, которая проверяет ваш глаз на внимательность к мелочам, автор прошёл путь через данные, ИИ, категории, боль деплоя и удивительное открытие: интернет уже не тот, что раньше. Оцените, насколько хорошо вы знаете любимые сайты — и их крошечные значки.


Признаюсь честно. У меня есть одна маленькая навязчивая придирка. Каждый раз, когда я натыкаюсь на проект, сайт или приложение с дефолтным favicon (favorite icon) — тем самым значком, который идёт по умолчанию в любом фреймворке, инструменте или библиотеке, на которых всё это сделано, — я не могу удержаться от разочарования. Нет, даже не разочарования — чувство скорее приближается к отвращению.

Это ведь такой крошечный, почти бессмысленный штрих. Маленькая картинка, которую на мобильных браузерах порой и не видно. Она появляется в одном, ну максимум в двух местах. Почему это вообще должно иметь значение?

А вот для меня имеет. Причём немалое. Это одна из тех деталей, про которые кажется, что мозг их не замечает — но он замечает. Она показывает отношение к продукту. Внимательность. Скрупулёзность. Мелочи имеют значение.

«Подождите, уважаемый, ведь не все же графические дизайнеры. Стоит ли тратить столько времени на такую мелочь? Уж точно ВОЗМОЖНОСТИ поважнее?» — скажете вы.

В эпоху ИИ это бесит меня особенно сильно, потому что требуется минимум усилий, чтобы вписать в любой prompt, агент или чат, которыми сейчас пользуются: «добавь svg-favicon с моржом или чем-нибудь в этом духе». Серьёзно, это 30 секунд работы. 15, если просто скопировать то, что я сейчас написал.

Но всё это раздражение заставило меня задуматься. А насколько хорошо вообще я — или вы — ЗНАЕМ favicon? Если многие о них даже не догадываются, имеют ли они вообще значение?

Угадай favicon

Итак, вот игра: выбираем 4 сайта из топ-1 000 000, показываем favicon одного из них — и пытаемся угадать, какому домену он принадлежит. Бонус — если можно фильтровать по категории и рейтингу.

Я хотел сделать такую игру уже давно, и сейчас, кажется, самый подходящий момент.

Можете смело потестировать готовое приложение на favvy.fun если хотите сразу перейти к делу, минуя текст. Код лежит на GitHub.

Технический стек здесь будет самым базовым:

  • сервер на Bun + TypeScript

  • база данных SQLite

  • статический клиент

Дальше — данные.

Нет, правда, чей это favicon?

Получить список топовых сайтов можно разными способами. Чтобы сэкономить время, я взял список CrUX, основанный на использовании Chrome. Огромный CSV-файл с доменным именем и диапазоном рейтинга.

Я быстро пробежался по доменам взглядом и заметил несколько закономерностей, которые ухудшили бы игровой опыт.

Во-первых, субдомены. Они создают дубликаты. Поэтому я оставил только www, и список сократился примерно до 560 тысяч доменов. Ух ты!

Следующая проблема в том, что все знают: интернет всегда служил и будет служить одной-единственной цели — порно. Или так думал я. (об этом позже)

Я не был уверен, как лучше справиться с «порно-проблемой». Удалить всё подобное — не так-то просто. Порно-сайты довольно хитро придумывают названия. Femfatal.com может быть модным сайтом, модельным агентством или чем-то совсем не подходящим для рабочего окружения. Последнее, чего мне хотелось бы, — чтобы на экране внезапно появлялся пиксельный непристойный favicon, каким бы забавным это ни казалось.

Поняв, что мои изысканные навыки regex провалились, я решил достать тяжёлую артиллерию. И под тяжёлой артиллерией я имею в виду «ИИ».

Пожалуйста, просто скажи мне, что это за favicon?

Мне также хочется разнести сайты по категориям — так играть будет интереснее. Возможность сыграть раунд, где участвуют только топ-500 новостных сайтов, сильно добавляет разнообразия. В довесок я могу сделать отдельный NSFW-бакет и просто скинуть туда всё «плохое». Красота.

Я решил использовать Gemini и, как приличный инженер, подумал, что лучше всего подойдёт function calling. Я хотел обрабатывать пачки доменов за раз, потому что у Gemini есть лимит в 10k запросов в день. А 10k — это всё-таки сильно меньше, чем 560k, и мне совсем не улыбается ждать 56 дней. Мне говорили, что функция вызовов там теперь очень крутая, так что ожидания у меня были высокие.

Но мои надежды не оправдались. Gemini работать с этим всем толком не смог. Периодически я получал ошибку, которая по сути означала, что ИИ возвращает некорректные параметры для функции. Хотя сама функция — предельно простая: массив категорий. Попадал он в цель от силы в 50% случаев. Я даже добавил цикл с повторными попытками, но это почти не помогло и только съело ещё больше драгоценных API-вызовов.

В итоге я вернулся к старому доброму one-shot prompt-у со стратегией в духе «ну очень-очень прошу, верни список категорий, разделённых запятыми». Это работало заметно лучше, но всё равно не идеально. Чем больше размер батча, тем выше вероятность, что модель вернёт слишком мало или слишком много категорий. При размере батча 50 я могу разнести все домены по категориям за один день по лимиту API-запросов.

Я вручную проверил несколько батчей, и Gemini в целом неплохо справился с расстановкой категорий — не идеально, но терпимо, — так что я дал этому процессу идти до конца. Примерно через 20 минут у меня была аккуратная база SQlite, заполненная результатами.

Я записал в отдельный файл все батчи, которые не прошли — либо потому что даже после нескольких повторных попыток данные не проходили валидацию (нет некорректных категорий, правильное количество и т.п.), либо из-за срабатывания фильтров безопасности. Таких набралось больше 100 тысяч, в основном из нижней части списка доменов. Это довольно много — почти 25% всего набора данных.

Понимаете, Gemini делает Google, а это компания с довольно «прогрессивными» взглядами. А значит — с очень строгим фильтром «никакого плохого-плохого». Некоторые домены, которые должны были попасть в NSFW-бакет, срабатывали на этот «плохой-плохой» фильтр. У меня размер батча был 50 доменов, и это означало, что множество доменов попадали под раздачу просто из-за одного запрещённого слова. Я в итоге понял, что именно, скорее всего, выводило Gemini из себя, но вдаваться в это сейчас не буду. Однако это снова подчёркивает одну из постоянных, раздражающих проблем современных крупных LLM-провайдеров — их фильтры «Trust and Safety».

Я почти подумывал переключиться на OpenAI с их новым «adult mode» или на Grok… но, знаете, «фу, гадость, мерзость».

На всю категоризацию я потратил примерно доллар. Было бы несложно подправить prompt и прогнать всё заново — или хотя бы переработать только те домены, что провалились.

Но в ито��е я решил, что примерно 400 тысяч доменов, которые удалось разнести по категориям, — это вполне достаточно.

Ты ведь так и не знаешь, что это, да?

У Google есть замечательный маленький API, который возвращает favicon для любого домена. Прекрасно. Игровой API будет подгружать favicon «лениво» и сохранять их в SQLite как blob. Форматы изображений у разных сайтов отличаются, поэтому перед сохранением я приведу их к оптимизированным PNG. Идеально.

Если домен отдаёт 404, я просто удалю его из базы. Порядок — прежде всего.

Я не буду вдаваться в подробности, но получить 4 случайные записи из SQLite с ограничением и при этом сохранить производительность — задача… интересная. Скажем так, удовольствия в этом было немного.

Когда я наконец закончил с единственным API-эндпоинтом, я взялся за клиент.

Смотрите, я сделал всё на статичных HTML и JS-файлах. Это вышло… грязно, очень грязно. Bun до сих пор не даёт того UX, который мне нужен для статичных JS-сайтов, а React — как бы я его ни любил — совершенно не стоил того, чтобы возиться со всяким бандлинг-дерьмом.

И нет, никакого htmx. Хватит пытаться его везде притупить. Не взлетит. У меня нет времени объяснять почему. Если хотите доказать, что я не прав, можете посмотреть код на GitHub.

Деплой оказался болезненнее, чем на Vercel, но всё же легче, чем на AWS. Быстрый апдейт конфига nginx, покупка домена — и всё в онлайне.

Это утка.

Встречайте — favvy.fun.

Одна вещь, которую я заметил: ландшафт интернета изменился. Похоже, NSFW больше не держит корону главной «силы», движущей Сеть. Что же заняло её место? Я выяснил это, когда попробовал категорию «Gaming».

Я надеялся увидеть сайты по World of Warcraft, Fortnite и LoL. Но вместо этого там было… азартные игры. Да-да, азартные игры сверху, снизу, слева, справа. Даже по диагонали и по всем немыслимым осям. Столько гемблинга. Такое ощущение, что почти в любой категории рано или поздно попадётся какой-нибудь сайт про ставки. Shopping? Скины CS:GO. News? «Лучшее время, чтобы сделать пуллы в какой-нибудь гача-игре».Travel? Казино.

Спорт? Хех, о наивное создание. Спорт — это просто азартные игры. DraftKings — лишь вершина айсберга.

Возможно, я позже сделаю ещё один проход и выделю отдельный бакет «gambling», но пока и так сойдёт. Очень забавно брать топ-100 или топ-500 какой-нибудь категории и проверять, насколько хорошо у тебя получается угадывать.

Зато я обнаружил новую раздражающую мелочь. Если у вас, чёрт побери, название компании стоит favicon — который, напомню, обычно не больше 40 пикселей — пожалуйста, остановитесь и обратитесь за помощью. Серьёзно, зачем вы суёте название в иконку? Что вы вообще делаете? Только скажите, что вы не заплатили агентству за это.

В иконографии нормально использовать первую букву домена — как это делают Amazon или Google, добавляя немного стиля. Для favicon это работает отлично. Можно использовать и несколько букв, как CNN, но очень быстро начинает казаться тесновато.

В моём приложении favicon — это FAV, «сложенные» друг на друга, что экономит место, но, если честно, выглядит всё равно так себе.

Важно понимать, что favicon поддерживают множество форматов, включая SVG с прозрачностью. И размер необязательно должен быть маленьким — большинство браузеров сами его подгоняют. У Apple есть свои специальные иконки, которые можно задать, но Apple любит менять правила их отображения.

Лучшие favicon (да и иконки в целом) — это простые формы, которые мгновенно вызывают нужную ассоциацию, в идеале связанную с сайтом, продуктом или смыслом бренда. Буквы — это короткий путь к узнаваемости и особенно полезны, когда бренд новый. На ранних этапах в этом нет ничего плохого.

Apple — образцовый пример отличного дизайна, но Xbox почти так же узнаваем.

У меня, правда, есть ещё один favicon.

Не могу подобрать правильные слова, но когда я думаю о JavaScript-рантайме под названием Bun, зная всё, что знаю о разработчиках, инженерах и экосистеме JS, — ровно ЭТО и всплывает у меня в голове. Пока что ему не хватает ощущения «иконичности» бренда, но он ещё молодой. Дайте ему время.

Именно маленькие вещи, те самые мелочи, которые все видят, но почти никто не замечает, — придают миру фактуру и делают жизнь приятнее.

Русскоязычное JavaScript сообщество

Друзья! Эту статью перевела команда «JavaScript for Devs» — сообщества, где мы делимся практическими кейсами, инструментами для разработчиков и свежими новостями из мира Frontend. Подписывайтесь, чтобы быть в курсе и ничего не упустить!