opopa.top

Дизайн задаётся входными артефактами, а не выбором модели

·5 мин чтения·обновлено

разборыдизайн

Почему «сделай красиво» не работает ни с одной моделью, и что подать на вход вместо этого — разбор метода на примере блога, который вы сейчас читаете.

Этот блог сверстан за одну сессию, и grep по src/components не находит ни одного литерального цвета. Не потому что модель попалась хорошая — до первой строки вёрстки уже лежали три файла: таблица токенов, список запретов и десять картинок-концептов.

Почему «сделай красиво» не работает

Просьба «сделай красивый лендинг» оставляет агенту сотню решений: фон, шрифт, отступы, где акцент. Он их принимает — по медиане обучающей выборки. Отсюда одинаковые фиолетовые градиенты и карточки со скруглением 16px на сгенерированных сайтах.

Метод, который описывает @serejaris, переворачивает это: дизайн подаётся на вход как артефакт. Агент получает design.md и подставляет готовые --color-iris, вместо того чтобы выбирать между #6366f1 и #8b5cf6 по внутреннему вкусу.

Что кладут на вход

Артефакт Что фиксирует Без него получится
design.md Токены: цвета с ролями, шкала кеглей, отступы, радиусы Каждый компонент со своим оттенком серого
llms.txt Запреты в исполняемом виде Агент изобретёт «ещё один акцентный цвет»
Концепт-картинки Композицию и ритм до вёрстки Вёрстка «по вкусу», которую потом не с чем сверить
UI-kit (shadcn) Поведение примитивов и состояния Свой велосипед на каждый дропдаун

Порядок обязателен: llms.txt без design.md запрещает изобретать цвета, но не говорит какие брать, а концепты без токенов дают картинку, которую нечем повторить в коде.

Донора нельзя брать как есть

Самая частая ошибка — брать чужой design.md и применять как есть. Мы взяли донором тему Huly с styles.refero.design и первым делом прогнали её палитру через расчёт контраста:

def contrast(fg: str, bg: str) -> float:
    hi, lo = max(lum(fg), lum(bg)), min(lum(fg), lum(bg))
    return (hi + 0.05) / (lo + 0.05)

Результат на донорском холсте #303236:

Пара Контраст Вердикт
ссылка #5683da на холсте 3.46 ниже AA для текста
вторичный текст #95979e 4.40 ниже AA
белый текст на кнопке #5683da 3.71 ниже AA

Донор отлично выглядит на скриншотах SaaS-лендинга и не проходит по контрасту для блога со ссылками внутри абзацев. Опустили холст до #0b0c0e и пересмотрели сочетания текста и акцентов — проверенные текстовые пары прошли AA.

Четыре детали из сборки блога

Tailwind v4 вырезает неиспользуемые токены. Если объявить токены через @theme и обратиться к ним из scoped-стилей Astro как var(--spacing-5), переменной в сборке не окажется: Tailwind эмитит только те, что затронуты утилитами. Лечится одним словом:

@theme static {
  --spacing-5: 24px;
}

static заставляет выпустить все переменные независимо от использования.

Рекурсия в @theme inline. Попытка смапить свои переменные вида --color-canvas: var(--color-canvas) даёт в собранном CSS циклическую ссылку. Спасает каскад слоёв, но объявить токены один раз прямо в @theme проще, чем это отлаживать.

Кириллица режет выбор display-шрифта. Донорский Esbuild кириллицы не имеет, как и большинство «характерных» гротесков из галерей. Мы поставили @fontsource/unbounded 5.3.0; в design.md как запасные записаны Onest и Manrope. Список, из которого выбирали, уместился в четыре имени.

Preflight снимает с блоков их смысл. Tailwind обнуляет браузерные стили, в том числе list-style у всех списков. В Prose.astro для текста статей маркеры не вернули, и списки какое-то время выглядели как обрывки абзацев. Grep такого не ловит: запрещать нечего, стиля просто нет. Поймали это через три недели после сборки, когда добавляли тест встроенного браузера Telegram: он подставляет в статью все блоки, разрешённые автору, и проверяет вычисленные стили — у списка есть маркер, у цитаты черта, длинный код прокручивается. Блок разрешили, а в тест не добавили — красный.

Запрет, который можно прогнать grep-ом

Гайдлайн обычно никто не читает. Но если формулировать запрет так, чтобы его проверял grep, документ становится тестом:

# литеральные цвета и магические px в компонентах — должно быть пусто
grep -rnE '#[0-9a-fA-F]{3,8}|rgb\(|hsl\(|\[[0-9]+px\]' src/components src/layouts src/pages

# классы чужой палитры Tailwind — должно быть пусто
grep -rnE '(bg|text|border)-(slate|gray|zinc|blue|indigo)-[0-9]' src/

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

Из тех же токенов растут и соцкарточки: генератор OG берёт цвета прямо из design.md, поэтому превью в Telegram и сама страница сделаны из одного материала.

Порядок

Сначала пересчёт: прогоняешь палитру донора через формулу контраста под свой контент. У нас не прошли AA ссылка, вторичный текст и белый текст на кнопке.

Дальше design.md с токенами и ролями и механический перенос в tokens.css. Расхождение между ними — баг, а не вкусовщина. Потом llms.txt с запретами и grep-командами.

И только после этого концепты: шесть-десять по разным осям, один выбирается, для остальных записывается причина отказа. Иначе через неделю никто не вспомнит, почему отклонили карточную сетку, и её предложат заново.

Концепт-раунд на десять картинок стоил двадцать центов — это cost_usd в run.json, не оценка. Сколько стоит переверстка блога, который «как-то не так выглядит», я не мерил. Знаю только, что без выбранного концепта готовую вёрстку не с чем сверять.