Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

Как я сделал генератор палитр на Node.js + DeepSeek за два вечера - и что из этого вышло

Дата публикации: 23-06-2026 02:44:37

Как-то вечером я поймал себя на том, что трачу по 20 минут на поиск цветовой палитры для каждого нового проекта. Coolors, Adobe Color, случайные пины в Pinterest — и всё равно ощущение «не то». Тогда я задал себе вопрос: а что если вместо колеса оттенков начинать со слова? «Рассвет», «шторм», «лакшери» — у каждого слова есть интуитивный цвет.Так появился Колорит — инструмент, который превращает слово или фотографию в цветовую палитру с помощью ИИ. В этой статье расскажу про технические решения, prompt engineering для DeepSeek и пару неочевидных браузерных API. Читать далее

Основное содержимое страницы с новостью.

Уровень сложностиСредний

Время на прочтение4 мин

Охват и читатели11K

Кейс

Я в очередной раз потратил минут двадцать на подбор палитры для нового проекта - сидел, крутил Coolors, листал Pinterest, смотрел на чужие подборки, которые «почти подходят». В какой‑то момент поймал себя на мысли: почему вообще я начинаю с колеса оттенков? Ведь у меня уже есть образ - слово. «Рассвет». «Шторм». «Лакшери». У каждого слова есть цвет, который чувствуется интуитивно, просто никто не переводит это в HEX за тебя.

Так появился Колорит. В этой статье расскажу, как он устроен внутри - промпты для DeepSeek, кластеризация прямо в браузере, и пара нюансов с iOS, которые я не ожидал.

3e93548cdafc2fd8500a3ab81277fa73.pngСтек: намеренно скучный

Бэкенд - Node.js + Express, около 150 строк. Фронтенд - чистый HTML/CSS/JS, без Vite, без React, без всего. DeepSeek API для генерации палитр по словам. Деплой на Linux VPS через pm2 и nginx. Базы данных нет - история запросов в sessionStorage, исчезает при закрытии вкладки.

Сознательный выбор. Хотелось поднять за вечер, а не тратить день на конфигурацию сборщика.

«Слово → Цвет»: как заставить LLM возвращать только JSON

Главная проблема при работе с языковыми моделями для получения структурированных данных - модель всё равно хочет поговорить. Отправляешь запрос, а в ответ получаешь «Конечно! Вот палитра для слова рассвет:» и потом уже JSON. Или markdown‑блок с ```json. Или пояснения после массива.

Перебрал несколько вариантов. В итоге работает такой подход:

async function generatePalette(query, count) { const res = await fetch('https://api.deepseek.com/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.DEEPSEEK_API_KEY}` }, body: JSON.stringify({ model: 'deepseek-chat', temperature: 0.8, messages: [{ role: 'system', content: `Ты - эксперт по цвету и дизайну. Возвращай ТОЛЬКО валидный JSON-массив, без markdown, без пояснений. Формат каждого элемента: {"hex":"#RRGGBB","name_ru":"Название","name_en":"Name"}` }, { role: 'user', content: `Создай палитру из ${count} цветов для слова/фразы: "${query}". Цвета должны передавать настроение, ассоциации и смысл слова. Избегай слишком похожих оттенков.` }] }) }); const data = await res.json(); return JSON.parse(data.choices[0].message.content); }

Три вещи, которые здесь важны. Первое - system prompt говорит «ТОЛЬКО JSON» заглавными буквами, это не случайно: вежливая просьба срабатывает хуже. Второе - temperature 0.8, не выше: при 1.0 модель иногда начинает фантазировать с форматом. Третье - фраза про «избегать похожих оттенков» обязательна, без неё можно получить пять оттенков бежевого для слова «осень».

Ответ приходит примерно за 1.5–3 секунды - вполне приемлемо для интерактивного инструмента.

Режим «Фото → Цвет»: кластеризация в браузере

Фотографии я принципиально не хотел отправлять на сервер - ни по соображениям приватности, ни по нежеланию платить за трафик и хранение. Весь алгоритм живёт в браузере, через Canvas API.

function extractColors(imageElement, count) { // Уменьшаем до 200×200 для производительности const canvas = document.createElement('canvas'); canvas.width = canvas.height = 200; const ctx = canvas.getContext('2d'); ctx.drawImage(imageElement, 0, 0, 200, 200); const { data } = ctx.getImageData(0, 0, 200, 200); const pixels = []; // Пропускаем каждый 4-й пиксель для скорости for (let i = 0; i < data.length; i += 16) { const r = data[i], g = data[i+1], b = data[i+2], a = data[i+3]; if (a < 128) continue; // прозрачные - пропускаем pixels.push([r, g, b]); } return kMeansClustering(pixels, count); }

Кластеризация работает в HSL‑пространстве, а не в RGB — перцептивное расстояние между цветами там куда адекватнее. 200×200 пикселей, несколько итераций k‑means — и у нас доминирующие цвета без единого серверного запроса. В браузере это занимает порядка 40 мс, незаметно.

3004cab48bdb98c130ac554a110fa90d.pngДва места, где я неожиданно застрялiOS сохраняет в «Загрузки», а не в «Фото»

Казалось бы, PNG - он и есть PNG. Но стандартный download‑атрибут на iPhone кладёт файл в «Загрузки» в Файлах, откуда его никто не ищет. Пользователь нажимает «скачать», потом удивляется, куда делась палитра.

Решение - Web Share API:

async function saveOrShare(canvas) { canvas.toBlob(async (blob) => { const file = new File([blob], 'palette.png', { type: 'image/png' }); if (navigator.canShare?.({ files: [file] })) { await navigator.share({ files: [file], title: 'Палитра' }); } else { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'palette.png'; a.click(); URL.revokeObjectURL(url); } }); }

На iOS теперь вылезает нативный Share Sheet - можно сохранить в Фото, скинуть в Telegram, скопировать. На десктопе всё по‑старому, через download.

Тёмная тема и то, как цвет «ощущается» по‑разному

Это я не ожидал. Одна и та же палитра на тёмном фоне выглядит теплее, чем на светлом — из‑за симультанного контраста. Тот оттенок, который казался уютным кирпичным на сером, на белом фоне вдруг становится холоднее и резче. Пришлось добавить CSS‑переменные с чуть разными значениями для обеих тем и плавный переход transition: background .35s, чтобы переключение не выглядело как удар по глазам.

Итого

Инструмент живёт на https://konstmax.ru/colorit/ Два режима - «Слово→Цвет» и «Фото→Цвет», переключатель RU/EN, история запросов, экспорт PNG. Фронтенд без зависимостей весит ~40 KB суммарно - CSS плюс JS.

Задачу, с которой всё началось, он решает. Открываю, набираю слово - через пару секунд есть отправная точка. Остальное уже руками.

Что дальше: «Слово‑система»

Колорит - первый модуль в задуманной экосистеме «Слово‑система»: набора инструментов, где точкой входа является обычное слово или образ. Слово→Цвет уже есть. Следующие модули в разработке - Слово→Шрифт, Слово→Форма. Идея в том, чтобы ИИ помогал дизайнеру и разработчику на самом раннем этапе - когда есть только ощущение, но нет ещё ни одного пикселя.

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Как генератор палитр дорос до конструктора бренда: один ИИ-эндпоинт, дырявая ссылка и грабли деплоя08.2627-06-2026
2Чтобы не выглядело как пет-проект»: как я в одиночку сделал премиальный интерфейс кино-сервиса (с кодом)010.3221-05-2026
3Генерируем фотореалистичные изображения09.608-06-2026
4Эволюция токенов цветов в Туту: как навести порядок в дизайне и не уехать в дурку08.5116-06-2026
5Как перенести навайбкоженный проект в Figma через Claude Code и получить дизайн‑систему08.2825-08-2026
6За полчаса и 10 рублей — полноценный сайт для загрузки и редактирования скриншотов на вайбкоде. Пошаговая инструкция07.6629-07-2026
7ContentCombine: как я сделал мультинишевый контент-комбайн и запустил ежедневный SEO-дайджест5728-06-2026
87 месяцев вайбкодинга: как в одиночку делать то, что раньше требовало команду0901-08-2026
9Как я искал себе читалку и придумал новый формат электронных книг для изучающих язык0508-07-2026
10DS MCP: как дать AI-агенту знание о вашей дизайн-системе7808-07-2026

Классификация: Наука. Схожих патентов: 0. Схожих новостей: 10. Тональность: 0. Информативность: 8.89. Источник: habr.com.