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

Так появился Колорит — инструмент, который превращает слово или фотографию в цветовую палитру с помощью ИИ. В этой статье расскажу про технические решения, prompt engineering для DeepSeek и пару неочевидных браузерных API.

Стек и архитектура

Принципиально не хотел тащить тяжёлый фреймворк. Итоговый стек:

  • Бэкенд: Node.js + Express, ~150 строк

  • Фронтенд: чистый HTML/CSS/JS без сборщиков

  • ИИ: DeepSeek API (модель deepseek-chat)

  • Деплой: Linux VPS, pm2, nginx reverse proxy

Никакой базы данных — история запросов живёт в sessionStorage браузера. Поднялось за один вечер.

Режим «Слово → Цвет»: prompt engineering

Главная задача — получить от LLM строго структурированный ответ, а не поток текста. Я потратил время на правильный промпт и в итоге пришёл к такому подходу:

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, никакого markdown

  • temperature: 0.8 — достаточно креативно, но предсказуемо по формату

  • Явная просьба «избегать похожих оттенков» — без неё модель иногда возвращала 5 оттенков одного цвета

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

Здесь я принципиально не хотел отправлять фото на сервер — и для приватности, и для скорости. Весь алгоритм работает через Canvas API:

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); }

Для кластеризации использовал k-means в HSL-пространстве — оно лучше отражает перцептивное расстояние между цветами, чем RGB. После нескольких итераций центроиды стабилизируются и дают доминирующие цвета изображения.

Неочевидные детали

iOS: сохранение в Фото, а не в Загрузки

Стандартный download-атрибут на iPhone сохраняет файл в «Загрузки» — пользователи этого не ожидают. Решение — Web Share API:

async function saveOrShare(canvas) { canvas.toBlob(async (blob) => { const file = new File([blob], 'palette.png', { type: 'image/png' }); // iOS поддерживает share файлов — открывает нативный шаринг if (navigator.canShare?.({ files: [file] })) { await navigator.share({ files: [file], title: 'Палитра' }); } else { // Fallback для десктопа 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, скопировать и т.д.

Тёмная тема и восприятие цвета

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

История без базы данных

Последние 10 запросов хранятся в sessionStorage — исчезают при закрытии вкладки, никакой слежки, никакого сервера. Отображаются как кликабельные чипы под строкой поиска.

Что получилось

Инструмент работает на https://konstmax.ru/colorit/. Два режима — «Слово→Цвет» и «Фото→Цвет», переключатель RU/EN, тёмная и светлая темы, история запросов, экспорт PNG с нативным шарингом на iOS.

Основные цифры после запуска:

  • Время ответа DeepSeek: ~1.5–3 сек на запрос

  • Размер фронтенда без зависимостей: ~18 KB CSS + ~22 KB JS

  • Canvas-кластеризация 200×200px: ~40 мс в браузере

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

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