На главную
Дизайн-система

Система, по которой собран этот сайт

Токены, правила и компоненты verno/dev — открыто и целиком. Не витрина: те же значения и те же импорты, из которых собирается продакшен-бандл.

Значения сняты с src/styles/_variables.sass. Кнопки, карточки и айброу ниже — не скриншоты и не копии разметки, а те же компоненты, что стоят на остальных страницах.

Принципы

Пять решений, из которых следует всё остальное. Каждое стоило либо измерения, либо переделки.

  1. 01

    Один тон, до конца

    Всё акцентное — вечнозелёное, тон ≈162°. Перекрашены даже ::selection и полоса прокрутки. Исключения ровно два: сигнальные цвета и намеренные «светофоры» macOS в терминале.

  2. 02

    Стекло с физикой

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

  3. 03

    Тёмная тема — основная

    Тёмные значения живут на :root, светлая тема переопределяет их через [data-theme="light"]. Токены, не зависящие от темы, объявлены один раз.

  4. 04

    Контраст измеряют, а не предполагают

    Акцентный текст подобран против худшей реальной подложки — свечения поверх стекла, — а не против чистого фона. Измеренное отношение записано в комментарии рядом с токеном.

  5. 05

    У эффекта есть цена кадра

    Кадр тормозили не backdrop-filter, а размытые тени, повторённые на 15–20 карточках: 35 мс против 17 мс. Повторяющиеся карточки идут по рецепту без размытия, полное стекло осталось у одиночных поверхностей.

Цвет

Один тон на весь интерфейс и две темы. Светлая — не осветлённая копия тёмной, а самостоятельный набор значений.

Вечнозелёный

Бренд, тон ≈162°

  • 50#F4FBF7Текст на акцентной заливке (--accent-ink)
  • 300#4CBE9CНачало градиента заголовков, тёмная тема
  • 400#42AE8EМелкий акцентный текст, тёмная тема (--accent-text)
  • 500#2E8B6FГлавный яркий акцент: иконки, границы, свечения (--accent-bright)
  • 600#2C7E66Начало градиента заголовков, светлая тема
  • 650#1E7358Мета-строки в светлой теме — самый зелёный из проходящих AA
  • 700#256B56Заливка основной кнопки, ::selection, skip-link (--accent-btn)
  • 800#1A5F4CМелкий акцентный текст, светлая тема (--accent-text)
  • 900#1A4D3EГлубокий базовый акцент (--accent)
  • 950#14402DКонец градиента заголовков, светлая тема

Нейтральный

С зелёным подтоном — чистого серого на сайте нет

  • 50#ECF2EFТекст тёмной темы (--ink)
  • 100#E7ECE6Фон светлой темы — намеренно глубже белого, чтобы стекло было видно
  • 200#DCE3DAГлубокий фон светлой темы (--bg-deep)
  • 300#B9C4BEОписания тёмной темы (--ink-soft)
  • 400#8B9891Приглушённый текст тёмной темы (--muted)
  • 600#4E5B54Приглушённый текст светлой темы (--muted)
  • 700#35423BОписания светлой темы (--ink-soft)
  • 900#191D1BФон тёмной темы — зелёный уголь, а не чёрный провал
  • 950#111413Глубокий фон тёмной темы (--bg-deep)
  • 975#0B140FТекст светлой темы (--ink)

Сигнальные

Единственные невечнозелёные цвета интерфейса

  • ok#17C46BУспех: отправленная форма, статус доступности
  • error#FF8A7AОшибка поля — мягкий коралл, читается на тёмном стекле

Семантические токены

То, чем оперируют компоненты. Имя называет роль, а не оттенок: цвет за ним меняется вместе с темой, а у акцентных токенов — ещё и на лету, под платформу открытого проекта.

Акцент

Не зависят от темы: объявлены один раз на :root. Их же переписывает useAccentHover, когда страница перекрашивается под платформу проекта, — поэтому в компонентах допустим только var(--accent-*), но не готовый hex.

ТокенТёмнаяСветлая
--accent#1A4D3E#1A4D3E
--accent-bright#2E8B6F#2E8B6F
--accent-btn#256B56#256B56
--accent-ink#F4FBF7#F4FBF7
--accent-glowrgba(46, 139, 111, 0.28)rgba(46, 139, 111, 0.28)
--ok#17C46B#17C46B
--ok-glowrgba(23, 196, 107, 0.18)rgba(23, 196, 107, 0.18)

Текст и фон

ТокенТёмнаяСветлая
--bg#191D1B#E7ECE6
--bg-deep#111413#DCE3DA
--ink#ECF2EF#0B140F
--ink-soft#B9C4BE#35423B
--muted#8B9891#4E5B54
--accent-text#42AE8E#1A5F4C
--heading-grad-a#4CBE9C#2C7E66
--heading-grad-b#2E8B6F#14402D

Поверхности и границы

Альфа-каналы различаются по темам намеренно: тёмное стекло подкрашено белым на 7–12 %, светлое — на 45–68 %, потому что панель чуть темнее белого на почти белой странице попросту не видна.

ТокенТёмнаяСветлая
--surface-glassrgba(255, 255, 255, 0.07)rgba(255, 255, 255, 0.45)
--surface-glass-strongrgba(255, 255, 255, 0.12)rgba(255, 255, 255, 0.68)
--surface-dockrgba(16, 24, 20, 0.94)rgba(252, 253, 252, 0.96)
--border-glassrgba(255, 255, 255, 0.14)rgba(20, 32, 25, 0.16)
--border-glass-strongrgba(255, 255, 255, 0.26)rgba(20, 32, 25, 0.28)
--border-glass-litrgba(255, 255, 255, 0.3)rgba(255, 255, 255, 0.92)

Оптика стекла и атмосфера

ТокенТёмнаяСветлая
--glass-specularrgba(255, 255, 255, 0.09)rgba(255, 255, 255, 0.6)
--glass-grad-argba(255, 255, 255, 0.06)rgba(255, 255, 255, 0.5)
--glass-grad-brgba(46, 139, 111, 0.05)rgba(46, 139, 111, 0.07)
--glass-highlightrgba(255, 255, 255, 0.35)rgba(255, 255, 255, 0.9)
--glass-inner-shadowrgba(0, 0, 0, 0.22)rgba(20, 35, 28, 0.12)
--shadow-colorrgba(1, 4, 3, 0.65)rgba(20, 35, 28, 0.18)
--atmo-glowrgba(46, 139, 111, 0.09)rgba(46, 139, 111, 0.13)
--atmo-lightrgba(255, 255, 255, 0.04)rgba(255, 255, 255, 0.5)

Контраст

Коэффициенты считает сама страница из значений таблицы выше — они не переписаны из отчёта и не могут разойтись с токенами.

ПараГдеТёмнаяСветлая
--ink на --bgОсновной текст15.02:1AAA15.64:1AAA
--ink-soft на --bgОписания9.49:1AAA8.80:1AAA
--muted на --bgПодписи, лид секций5.68:1AA5.95:1AA
--accent-text на --bgМелкий акцентный текст6.23:1AA6.30:1AA
--accent-ink на --accent-btnОсновная кнопка, выделение текста6.03:1AA6.03:1AA
--accent-bright на --bgТолько крупный текст и иконки4.09:1AA3.48:1AA

В таблице только сплошные пары, где коэффициент считается точно. Стеклянные поверхности полупрозрачны, и их стопки — свечение поверх стекла, худший случай на сайте, — измерены вручную; результат записан в комментарии рядом с определением токена.

Типографика

Три начертания, и оба текстовых несут полную кириллицу и латиницу в одном файле — у русской и английской версий не разъезжаются глифы.

  • Заголовки держат страницу$font-displayGolos Text600, letter-spacing −0.01em, line-height 1.1–1.2h1–h4
  • Основной текст, кнопки и поля форм$font-bodyManrope400/600, 16/1.5Текст, кнопки, ввод
  • AIBROW / 01 — MONO 12PX$font-monoJetBrains Mono600, uppercase, letter-spacing 0.06–0.08emАйброу, индексы секций, терминал

Статическая шкала

Кратность 4. После свёртки часть ролей совпала по значению — имена оставлены, чтобы код называл роль, а не пиксель.

  • Съешь ещё этих мягких булок да выпей чаю$text-xs12pxМоносноски, индексы, ошибки полей
  • Съешь ещё этих мягких булок да выпей чаю$text-sm12pxАйброу, моночипы, подписи
  • Съешь ещё этих мягких булок да выпей чаю$text-base-sm16pxЧипы, плотный интерфейс
  • Съешь ещё этих мягких булок да выпей чаю$text-base16pxТекстовые CTA, вторичный текст
  • Съешь ещё этих мягких булок да выпей чаю$text-md16pxОсновной текст — совпадает с body
  • Съешь ещё этих мягких булок да выпей чаю$text-lg20pxЛид секции
  • Съешь ещё этих мягких булок да выпей чаю$text-xl20pxЗаголовки карточек на мобильном
  • Съешь ещё этих мягких булок да выпей чаю$text-2xl28pxЗаголовки колонок
  • Съешь ещё этих мягких булок да выпей чаю$text-3xl32pxАкцентные заголовки колонок

Плавная шкала

Заголовки заданы через clamp: нижняя граница, скорость роста и потолок.

  • Заголовок главной (h1)clamp(40px, 5.2vw, 72px)
  • Заголовок страницыclamp(36px, 5vw, 64px)
  • Заголовок кейсаclamp(28px, 4.4vw, 52px)
  • Заголовок секции (h2)clamp(28px, 3.4vw, 44px)
  • Заголовок блокаclamp(24px, 2.8vw, 36px)
  • Заголовок карточкиclamp(20px, 2.2vw, 28px)

Межстрочный интервал меняется вместе с ролью — 1.1 у крупных заголовков, 1.2 у младших. Единое плотное значение ломается на размере карточки, когда заголовок переносится: у двухстрочного названия выносные элементы почти касаются следующей строки.

Отступы и сетка

Одна шкала на весь сайт. Новые margin и padding берутся только из неё.

  • $space-18px
  • $space-212px
  • $space-320px
  • $space-432px
  • $space-540px
  • $space-660px
  • $space-780px
  • $space-8120px

Раскладка

Контейнер 1200px
Поля 28px, на мобильном 16px. Модификатор --narrow сужает до 760px для сплошного текста
Ритм секций 80 / 60 px
Обычная и .section--tight. Планшет 68px, мобильный 48px
Шапка секции 640px
Отступ снизу 40px, волосяная линейка, моноиндекс /0N в правом нижнем углу
Отступ под шапку 104px
scroll-padding-top на html — единственный механизм. scroll-margin-top на секциях складывается с ним и потому запрещён

Контрольные точки

Все по max-width. Точка меню отделена от планшетной намеренно: «помещается ли главное меню» — это вопрос про 887 пикселей раскладки, а не про класс устройства.

$bp-mobile 620px
Телефон
$bp-nav 930px
«Помещается ли главное меню» — отдельный вопрос от «это планшет»
$bp-tablet 980px
Планшет
$bp-laptop 1080px
Промежуточная ступень, чтобы 621–980 не был одним ведром

Форма и стекло

Три радиуса и четыре рецепта поверхности. Выбор рецепта — это выбор цены кадра, а не вкуса.

  • $radius-sm12pxПоля ввода, чипы, skip-link
  • $radius20pxСтеклянные карточки и панели
  • $radius-pill999pxКнопки, ползунок прокрутки

Рецепты поверхностей

Карточки ниже показывают сами себя: каждая собрана тем рецептом, который описывает.

  • glass-effect

    Одиночные поверхности: шапка, терминал, форма, боковое меню

    Блик + градиент от белого к зелёному + полупрозрачная заливка; светлые верхняя и левая грани; backdrop-filter: blur(20px) saturate(160%); мягкая тень, контактная тень и две внутренние кромки.

  • glass-card--no-blur

    Повторяющиеся карточки в сетках и каруселях

    То же лицо, но без backdrop-filter и без размытой тени — только внутренние кромки. Именно размытая тень, повторённая на 15–20 карточках, стоила 35 мс на кадр против 17 мс.

  • glass-puck

    Контейнеры иконок, повторяющиеся в сетках

    Градиент, граница 1px и одна небольшая тень. Без backdrop-filter — иконка лежит в материале, а не на плоском цветном квадрате.

  • cursor-sheen

    Интерактивные карточки

    Мягкое радиальное пятно акцента ходит за курсором внутри карточки: 7 % в тёмной теме, 10 % в светлой. Координаты пишет useCursorSheen прямо в style, без состояния React. Сама карточка не двигается.

Если backdrop-filter не поддерживается, все рецепты откатываются на непрозрачную --surface-glass-strong — панель остаётся читаемой, просто перестаёт быть стеклом.

Движение

Один закон движения на Sass и на Framer Motion — четыре одинаковых числа в обоих местах.

Криваяcubic-bezier(0.16, 1, 0.3, 1)

Резкий старт и долгое дотягивание: элемент почти доезжает за первую треть времени, остаток уходит на успокоение. Поэтому интерфейс читается как быстрый даже на длительностях в четверть секунды.

Длительности

$dur-fast 0.2s
Микровзаимодействия, ховер кнопки во Framer
$dur-base 0.25s
Цвета кнопок, границы полей, блик курсора
$dur-slow 0.6s
Крупные появления

Правила

Нажатие scale(0.97)
Единственная обратная связь на касании: нативная подсветка тапа отключена глобально, потому что рисовала прямоугольник поверх круглых кнопок
Ховер кнопки y: −2
Framer Motion; нажатие — scale 0.96
Карточки без сдвига
Подъём и скос сняты 13.09.2026. Ховер карточки — это свечение и блик за курсором, ничего больше
Смена темы 0.3s
Переезжает только фон, цвет текста переключается мгновенно: на середине кросс-фейда оба оказываются серыми, ~1.1:1, и текст на миг исчезает
Пульс айброу 2.6s
Кольцо расходится и гаснет по бесконечному циклу
Переход страниц 0.35s
Появление секций — 0.7s «осадкой в плоскость»

Компоненты

Живые, а не снимки: всё ниже импортировано из тех же файлов, что и на остальных страницах. Наведите и нажмите — состояния настоящие.

Button

Три варианта, четыре состояния. Пилюля, Manrope 600, 16px, отступы 15×28.

primary
ghost
on-accent
ВариантОбычноеНаведениеНажатиеЗаблокировано
primary--accent-btn / --accent-inkКольцо свечения 6px + мягкий ореолscale(0.97)opacity 0.6, cursor: progress
ghostБелый 4% + blur(20px), граница --border-glass-strongГраница и текст --accent-brightscale(0.97)opacity 0.6, cursor: progress
on-accent--bg / --ink--bg-deepscale(0.97)opacity 0.6, cursor: progress

Нажатие — scale(0.97) у всех трёх вариантов: нативная подсветка тапа отключена глобально, потому что рисовала прямоугольник поверх круглой кнопки, а форму подсветки задать нельзя. Значит, обратную связь на касании обязан давать сам компонент.

GlassCard

Одна поверхность, три режима. Отличаются ценой кадра, а не лицом.

  • <GlassCard />

    Полное стекло: backdrop-filter и мягкие тени. Для одиночных поверхностей.

  • noBlur

    Без размытия и без размытой тени, только внутренние кромки. Для сеток и каруселей.

  • hoverLift

    Акцентная граница и свечение на наведении. Карточка при этом не двигается.

Айброу и шапка секции

Два сквозных паттерна страницы. Индекс шапки — счётчик CSS: разделы нумеруются сами, порядком в документе.

.eyebrow Доступен для проектов
.section__head

Заголовок секции

Подзаголовок на 640 пикселей, с волосяной линейкой и моноиндексом справа.

Поле формы

Радиус 12, стеклянная заливка, акцентная рамка на фокусе.

Размер текста 16px не декоративный: на меньшем значении Safari в iOS увеличивает страницу при фокусе. Фокусное кольцо остаётся отдельно от акцентной рамки — гасить outline вслепую нельзя.

Доступность

Правила, которые нельзя нарушить, не сломав систему. Первые четыре — про цвет, последние два — про фокус и движение.

  1. Мелкий акцентный текст — только --accent-text. --accent-bright никогда не несёт текст меньше крупного: 3.97:1 под --accent-ink и 3.48:1 на светлом фоне.
  2. Новое сочетание акцента с поверхностью измеряется на худшей стопке, на которой окажется, — свечение поверх стекла, — а не на чистом --bg.
  3. Заголовки берут более яркие остановки градиента только пока нижняя граница clamp и вес 600 удерживают их в категории крупного текста (≥18.66px bold → порог 3:1).
  4. Измеренное отношение записывается в комментарий рядом с определением токена — так устроен весь _variables.sass.
  5. Кольцо фокуса — 2px var(--accent-text) с отступом 3px и без переопределения радиуса: заданный радиус перекраивал сам элемент, пока тот держал фокус, и круглые точки карусели становились квадратными.
  6. Каждый эффект движения обязан попадать под глобальный выключатель prefers-reduced-motion — он гасит и анимации, и переходы, и плавную прокрутку, а MotionConfig reducedMotion="user" останавливает то, что Framer ведёт инлайновыми стилями.

Нужна такая же система?

Если у проекта уже есть макет или живой сайт, систему можно собрать из того, что есть, и дальше вести проект по ней.

Получить смету и срок