Дизайн задаётся входными артефактами, а не выбором модели
Почему «сделай красиво» не работает ни с одной моделью, и что подать на вход вместо этого — разбор метода на примере блога, который вы сейчас читаете.
Этот блог сверстан за одну сессию, и 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,
не оценка. Сколько стоит переверстка блога, который «как-то не так выглядит», я не мерил.
Знаю только, что без выбранного концепта готовую вёрстку не с чем сверять.