Дизайн-система Maia — «стеклянный люкс»
Единственный источник истины по внешнему виду продукта. Кто правит интерфейс — правит сначала здесь, потом в коде.
- Машиночитаемые токены:
brand/tokens.json - Живой брендбук (открыть в браузере):
brand/brandbook.html - Реализация в кабинете:
prototype/src/index.css - Реализация на лендинге: репозиторий
maia24-site, файлsrc/styles/theme.css
1. Что это за язык
Полупрозрачные панели поверх живого цветного фона. Глубина строится не рамками и не заливками, а материалом: размытием подложки (backdrop-filter), светлой верхней кромкой и мягкой тенью. Так выглядит visionOS от Apple — отсюда и название направления.
Пять правил, из которых выводится всё остальное:
- Фон живёт, панели плавают. Фон — три цветных пятна на градиенте, он зафиксирован (
background-attachment: fixed) и не скроллится вместе с содержимым. Панели над ним размывают то, что под ними. - Стекло — материал, а не украшение. Панель, карточка, первичная кнопка, поле ввода — стекло. Разделитель, текст, иконка — нет. Стекло на всём подряд убивает иерархию: если светится каждый элемент, не светится ни один.
- Иерархия держится цветом, а не «плоское против стеклянного». Первичная кнопка — изумрудное стекло, вторичная — нейтральное стекло того же материала. Разница в цвете, а не в наличии материала.
- Две темы равноправны. Ни одна не «основная». Токены одинаковые, значения разные. Тема ставится атрибутом
data-themeна<html>. - Материал отключаем. Пользователь просит систему убрать прозрачность — стекло становится плотной панелью, дизайн не разваливается. Это не «поддержка на будущее», это уже работает.
Почему без оверлея
Вкусовой слой soft из design-ultimate прямо предупреждает: плохо ложится на плотные операционные интерфейсы. Ядро продукта — кабинет агентства, там глубина уходит в точность деталей, а не в пластику. Вместо оверлея действует пол качества impeccable/reference/craft-floor.md.
2. Поверхности
| Поверхность | Что здесь успех | Режим | Реализация |
|---|---|---|---|
Кабинет platform.maia24.ru | выполнить задачу и уйти | operate | React 18 + Vite + Tailwind 4 |
Лендинг maia24.ru | решиться и оставить заявку | persuade | Astro, статика |
| Виджет на сайте клиента | ответ на вопрос | operate | встраиваемый скрипт |
Режим меняет плотность и типографику, но не палитру и не материал. Лендинг берёт ту же систему и достраивает шкалу вверх (герой), кабинет — вниз (плотные таблицы).
3. Цвет
Полный список значений — в brand/tokens.json. Здесь смысл, а не перечисление.
Бренд
| Роль | Тёмная тема | Светлая тема |
|---|---|---|
Акцент (--accent) | #34D399 | #0C7E57 |
Акцент светлее (--accent-2) | #6EE7B7 | #10B981 |
Знак и первичная кнопка (--mark-grad) | #6EE7B7 → #10B981 | #10C98C → #0BB078 |
Акцент инвертируется по яркости: на тёмном фоне светлый изумруд, на светлом — глубокий. Поэтому текст на акценте тоже разный: на тёмной теме почти чёрный #04130D, на светлой белый. Это --on-accent, и брать вместо него #fff вслепую нельзя — на светло-зелёной плашке белый текст нечитаем.
Фон
Три радиальных пятна (изумруд, синий, фиолетовый) поверх диагонального градиента. Пятна одинаковые в обеих темах по позиции, разные по светлоте. Резервный плотный цвет — --solid-bg, включается при prefers-reduced-transparency.
Текст
Три ступени: --txt (основной), --txt-2 (вторичный), --txt-3 (третичный).
--txt-3 раньше не проходил контраст ни в одной теме (3.9 и 3.53 при пороге 4.5), а им набраны плейсхолдеры и подписи — то есть текст, а не декор. Прозрачность поднята до .47 в тёмной теме и .60 в светлой: теперь 4.58 и 4.52.
Иерархия при этом сохранилась — три ступени по-прежнему различимы. Но правило остаётся: --txt-3 для того, что можно не прочитать. Всё, что несёт смысл, — минимум --txt-2.
Статусы
Четыре семейства, каждое из четырёх токенов (фон / рамка / текст / точка): ok — работает, tr — пробный период, pa — на паузе, danger — необратимо. Статус всегда несёт цветную точку рядом с текстом: цвет один не должен быть единственным носителем смысла (дальтонизм).
4. Типографика
Inter — и заголовки, и текст. Начертания 400 / 500 / 600 / 700, самохостинг, кириллические подмножества включены.
Google Fonts не подключается. Проект работает под РФ, внешний CDN может отдать таймаут, и страница молча упадёт на системный шрифт — это другой дизайн, о котором никто не узнает.
Шкала
| Токен | Размер | Где |
|---|---|---|
--t-cap | 13px | подписи, метки статусов, шапки таблиц |
--t-sm | 14px | кнопки, поля, навигация, вторичный текст |
--t-base | 16px | основной текст |
--t-lg | 18px | лид-абзац, имя бренда |
--t-xl | 22px | подзаголовок раздела, h3 |
--t-2xl | 30px | заголовок экрана, h2 на мобильном |
--t-3xl | 42px | h2 на лендинге, h1 на мобильном |
--t-4xl | 56px | герой лендинга |
Кабинет использует --t-cap … --t-2xl. Лендинг — всю шкалу.
Правила
- Трекинг заголовков
-.02em. Крупный Inter без сжатия выглядит рыхло. - Межстрочный: 1.6 в тексте, 1.05–1.2 в заголовках.
- Числа в таблицах и метриках —
.tnum(моноширинные цифры), иначе колонка «дышит» при обновлении. - Ширина строки текста 65–75 символов. Шире — глаз теряет начало следующей.
5. Ритм и форма
Отступы — одна шкала с шагом 4px: --s1 4 · --s2 8 · --s3 12 · --s4 16 · --s5 20 · --s6 24 · --s7 28 · --s8 32 · --s9 36 · --s10 40 · --s12 56 · --s16 80 · --s20 120.
Значений вне шкалы в коде быть не должно. Исключение одно — оптические поправки в 1–2px, и каждая объясняется комментарием в месте применения.
Радиусы: --radius-xs 9px (внутренние чипы, ползунки) · --radius-sm 11px (поля, пункты меню) · --radius 13px (кнопки, сегменты) · --radius-md 16px (панели, контекстное меню) · --radius-lg 20px (карточки, модальные окна) · --radius-pill 999px (метки статусов).
Радиус растёт вместе с элементом. Маленький радиус на большой карточке читается как обрезанный угол, большой на кнопке — как капсула.
Единственное исключение из шкалы — знак M и логотип клиента: их скругление пропорционально стороне (около 30%) и меняется вместе с размером, поэтому там литерал с комментарием, а не шаг шкалы.
Высота управляющих элементов: 42px в кабинете (плотность), 46px на лендинге (палец на телефоне). Это осознанное расхождение, а не рассинхрон.
6. Материал стекла
Рецепт панели — четыре слоя, все обязательны:
- Размытие подложки:
backdrop-filter: blur(--glass-blur) saturate(--glass-sat). Насыщенность выше 100% — иначе размытый фон сереет. - Полупрозрачная заливка
--panel-bgи рамка--panel-border. - Светлая верхняя кромка внутренней тенью:
inset 0 1px 0 --panel-top. Это она даёт ощущение толщины стекла. - Блик — псевдоэлемент
::afterс радиальным градиентом из правого верхнего угла. Имитирует источник света, один и тот же для всей страницы.
Плюс внешняя тень 0 20px 50px — со смещением и мягким размытием. Цветное свечение без смещения — это декорация, а не глубина.
Облегчённый вариант .glass-lite — размытие 16px, без блика и внешней тени. Для вложенных элементов: поля ввода, вторичные кнопки, ячейки.
Где стекло запрещено: контекстное меню и выпадающие списки. Меню всплывает поверх произвольного содержимого, и размытие делает его нечитаемым. Там --solid-panel — плотная заливка.
7. Движение
Одна кривая на весь проект: --ease: cubic-bezier(.22,1,.36,1) — резкий старт, долгий выход. Две длительности: --dur .2s (цвет, фон) и --dur-slow .28s (сдвиг, тень).
Что анимируется: подъём кнопки на 1px при наведении, скольжение индикатора в переключателе тем и сегментном фильтре, появление контекстного меню (.12s), проявление блоков лендинга при скролле.
Что не анимируется: смена экрана, появление данных в таблице, переключение темы. Кабинет открывают ежедневно — анимация, милая на первом запуске, на сотом становится задержкой.
При prefers-reduced-motion всё сокращается до .01ms. Не отключается совсем — нулевая длительность ломает некоторые переходы состояний.
8. Иконки
Контурные SVG, сетка 24×24, viewBox="0 0 24 24", скругления на концах линий. Толщина линии — 1.6.
Эмодзи вместо иконки не используются никогда: они рендерятся системным шрифтом, то есть выглядят по-разному на Windows, Mac и Android, и не подчиняются цвету темы.
Размеры в интерфейсе: 20px в навигации, 18px в кнопках и полях, 17px в контекстном меню.
9. Доступность
- Фокус с клавиатуры — изумрудное кольцо 2px с отступом 2px. Фокус от мыши не показывается (
:focus:not(:focus-visible)). prefers-reduced-transparency— стекло становится плотным, блик убирается.prefers-reduced-motion— переходы обнуляются.color-schemeвыставляется по теме, иначе системные попапы<select>приходят в чужой гамме.- Статус не кодируется одним цветом — рядом всегда точка и текст.
Контраст: было и стало
Четыре провала найдены замером и закрыты 3 августа 2026.
| Место | Было | Стало | Как |
|---|---|---|---|
--txt-3, тёмная | 3.90 | 4.58 | прозрачность .42 → .47 |
--txt-3, светлая | 3.53 | 4.52 | прозрачность .52 → .60 |
| Белый текст на первичной кнопке | 3.54 | 5.49 | стопы градиента затемнены |
| Белый текст на опасной кнопке | ~3.0 | 5.53 | то же |
--danger, светлая | 4.40 | 4.99 | #C4443E → #B93B35 |
Худший случай для кнопок посчитан честно: белый текст на верхнем, самом светлом стопе градиента, поверх самой светлой подложки. Тень под текстом в расчёт не берётся — она не контраст.
10. Расхождения кабинета и лендинга
Лендинг портирован из кабинета. Двенадцать мест разошлись; все сведены 3 августа 2026.
| Что | Было в кабинете | Было на лендинге | Стало |
|---|---|---|---|
--t-base | 15px | 16px | 16px везде, таблицы на --t-table 15px |
| Шкала отступов | s1…s10 | s1…s20 с пропусками | полная сплошная в обоих |
| Токены радиусов | нет, литералы | частично | полная лестница xs…pill в обоих |
| Высота кнопки | 42px | 46px | осознанно оставлено: плотность против пальца |
.pill | метка статуса | акцентная плашка | на лендинге переименовано в .tag |
--divider тёмная | .07 | .09 | .09 |
| Статусные цвета | есть | нет | добавлены на лендинг |
--card-solid, --accent-2 | нет | есть | добавлены в кабинет |
| Inter 700 | не загружался | загружался | загружается в обоих |
| Толщина линии иконок | 1.5 – 2 | 1.8 – 2.5 | 1.6 везде, 30 иконок |
| Полоса прокрутки | стеклянная | системная плоская | стеклянная в обоих |
| Нажатая кнопка | translateY(1px) | scale(.975) | scale(.975) в обоих |
Заодно снялось то, что вскрылось по дороге: цвет первичной кнопки был вписан литералом прямо в правило .btn мимо токена --btn-grad, и правка цвета из одного места не работала. Теперь кнопка красится токеном.
11. Как этим пользоваться
- Новый экран или блок — открыть
brand/brandbook.html, взять готовый компонент. Своего не изобретать. - Значение цвета, отступа, радиуса берётся токеном. Литерал в коде — ошибка.
- Компонент, которого нет в брендбуке, сначала добавляется сюда, потом в код.
- Правка токена — правится
brand/tokens.json, затем оба CSS-файла. Расхождение между ними означает, что продукт распался на два.