{
    "version": "https:\/\/jsonfeed.org\/version\/1.1",
    "title": "Здравый смысл Дени | Блог: заметки с тегом Дизайн",
    "_rss_description": "О дизайне, медиа, мире",
    "_rss_language": "ru",
    "_itunes_email": "",
    "_itunes_categories_xml": "",
    "_itunes_image": "",
    "_itunes_explicit": "",
    "home_page_url": "https:\/\/blog.denislykin.ru\/tags\/dizayn\/",
    "feed_url": "https:\/\/blog.denislykin.ru\/tags\/dizayn\/json\/",
    "icon": "https:\/\/blog.denislykin.ru\/pictures\/userpic\/userpic@2x.jpg?1783580338",
    "authors": [
        {
            "name": "Денис Лыкин",
            "url": "https:\/\/blog.denislykin.ru\/",
            "avatar": "https:\/\/blog.denislykin.ru\/pictures\/userpic\/userpic@2x.jpg?1783580338"
        }
    ],
    "items": [
        {
            "id": "6",
            "url": "https:\/\/blog.denislykin.ru\/all\/sayt-portfolio-kak-otdelny-produkt\/",
            "title": "Сайт-портфолио как отдельный продукт",
            "content_html": "<h3><a href=\"https:\/\/denislykin.ru\/?space=blog\">→Статья переехала в новый блог<\/a><\/h3>\n<p>⏳ <i>Время чтения ±20 минут<\/i><\/p>\n<p>За 8 лет опыта я сменил несколько десятков портфолио. Только за последние три года — пять. Надо понимать, что для дизайнера портфолио — тоже инструмент. Иногда нужно иметь несколько версий для разных целей — рассказать потенциальному заказчику на фрилансе о своём визуале и рассказать нанимающему менеджеру об умении выстраивать процессы — это две разные задачи. Обычно портфолио для меня — это способ рассказать о сделанной работе. В этот раз мне хотелось, чтобы сам сайт тоже стал работой: не декоративной оболочкой вокруг кейсов, а действующим примером того, как я проектирую сложные интерфейсы.<\/p>\n<p>Большинство портфолио устроено одинаково: обложка, короткое представление автора и вертикальная лента проектов. Такой формат понятен, но у него есть ограничение — он показывает результаты последовательно, хотя реальная работа продуктового дизайнера почти никогда не устроена как последовательность независимых экранов.<\/p>\n<p>Она больше похожа на карту связей. Один проект ведёт к другому, продуктовые решения опираются на исследования, рядом существуют рабочие эксперименты, результаты, клиенты и контекст самого автора. Мне хотелось показать эту структуру буквально — через пространственный интерфейс, похожий на node-based-системы, Miro и FigJam.<\/p>\n<p>При этом за визуальной метафорой должна была стоять полноценная продуктовая логика: управляемый Canvas, физика карточек, несколько пространств, мобильная версия, альтернативная лента, бесшовное открытие кейсов и WordPress как единый источник данных.<\/p>\n<p>Эта статья не столько про необычный вид портфолио, сколько про способ работы над ним. Про решения, которые пришлось принимать между Figma, браузером, структурой данных и редактором WordPress. И про то, почему современному дизайнеру полезно понимать фронтенд достаточно хорошо, чтобы проектировать не только состояния экрана, но и поведение всей системы.<\/p>\n<h3>Кому будет полезно?<\/h3>\n<ul>\n<li>▸ Продуктовым дизайнерам, которые растут в сторону Lead или Principal Designer и хотят влиять не только на макеты, но и на устройство продукта.<\/li>\n<li>▸ Дизайнерам, которые начинают работать с AI coding agents и ищут более зрелый процесс, чем «написать промпт и принять результат».<\/li>\n<li>▸ Командам, которые проектируют нестандартные интерфейсы с большим количеством состояний, жестов и динамического контента.<\/li>\n<li>▸ Тем, кто использует WordPress как CMS, но не хочет ограничиваться устройством классической темы.<\/li>\n<\/ul>\n<h3>Контекст<\/h3>\n<p>У меня уже был работающий сайт на WordPress с кейсами, изображениями и базовыми текстами. Поэтому задача не состояла в том, чтобы сделать ещё одну статичную страницу и вручную перенести в неё контент.<\/p>\n<p>К WordPress я пришёл не сразу, но возвращение к этой «волшебной палочке» стало каким-то грандиозным инсайтом. В нём всё есть: возможность быстро развернуть его и локально, и на хостинге, работа с БД, мобильная наладка процессов и бизнес-логики, headless-режим для своего фронта и прекрасная админка. В нынешних условиях писать для WP визуалы и плагины — одно удовольствие.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/blog.denislykin.ru\/pictures\/Frame-1.png\" width=\"2281\" height=\"1236\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Админка WP может сразу показать какой-нибудь нужный самописный виджет<\/div>\n<\/div>\n<p>Я хотел сохранить WordPress как канонический источник данных, но полностью заменить способ их представления. Это важное ограничение: новая тема должна была не просто отрисовать существующие записи, а превратить CMS в редактор пространственной системы.<\/p>\n<p>В центре первого макета находилась карточка Persona — моя цифровая визитка. Слева от неё располагались контакты, клиенты и результаты, справа — проекты и Playground. Все карточки соединялись с центральной точкой, образуя читаемую карту портфолио.<\/p>\n<p>Так появился основной образ интерфейса: не страница с блоками, а рабочее пространство, внутри которого человек сам выбирает направление движения.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/blog.denislykin.ru\/pictures\/1.0.png\" width=\"1920\" height=\"1243\" alt=\"\" \/>\n<\/div>\n<p>Визуально эта идея была достаточно ясной уже в Figma. Главная сложность началась дальше: как сделать так, чтобы метафора не рассыпалась при первом же реальном взаимодействии.<\/p>\n<h3>Постановка задачи<\/h3>\n<p>Я сформулировал несколько обязательных свойств системы.<\/p>\n<p>Во-первых, Canvas должен ощущаться «бесконечным». Пользователь может двигать его рукой, зажатым пробелом или средней кнопкой мыши, менять масштаб колесом и жестами, как в знакомых графических инструментах.<\/p>\n<p>Во-вторых, бесконечность не должна быть буквальной. Если разрешить двигаться без границ, человек легко потеряет и карточки, и точку возврата. Поэтому фактическая область перемещения рассчитывается по геометрии контента и получает дополнительный свободный пояс. Canvas остаётся просторным, но не превращается в пустую вселенную.<\/p>\n<p>В-третьих, карточки должны быть живыми объектами. Их можно перемещать, они не накладываются друг на друга, новые сущности автоматически находят место, а связи перестраиваются вслед за ними.<\/p>\n<p>В-четвёртых, пространственный интерфейс не должен становиться обязательным испытанием для каждого посетителя. В любой момент его можно переключить в Feed — обычную вертикальную ленту с тем же контентом.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/blog.denislykin.ru\/pictures\/1.0-Feed.png\" width=\"1920\" height=\"1080\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Режим ленты был придуман и реализован с самого начала<\/div>\n<\/div>\n<p>И наконец, всё это должно управляться из WordPress. Новая карточка, опубликованная в админке, должна появиться на открытом сайте, попасть в нужное пространство, получить исходную позицию, пройти через коллизии и, если требуется, соединиться с родителем. Без правки JavaScript и без ручной сборки страницы.<\/p>\n<p>На этом этапе я сознательно отложил pixel-perfect. Было бессмысленно идеально выравнивать иконку внутри панели, пока не определено, что происходит с камерой при открытии кейса или кто владеет координатами карточки. Сначала нужен был контракт ядра, потом — визуальная точность.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/blog.denislykin.ru\/pictures\/Concept-2.png\" width=\"1920\" height=\"1395\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Один из первых концептов портфолио<\/div>\n<\/div>\n<h3>Canvas — это не большой экран<\/h3>\n<p>Первая архитектурная ошибка, которую легко совершить в таком интерфейсе, — воспринимать Canvas как огромный DOM-контейнер, который просто двигается под маской viewport.<\/p>\n<p>На деле это несколько независимых состояний:<\/p>\n<ul>\n<li>▸ активное пространство;<\/li>\n<li>▸ режим Canvas или Feed;<\/li>\n<li>▸ открытый документ;<\/li>\n<li>▸ координаты и масштаб камеры;<\/li>\n<li>▸ расположение карточек;<\/li>\n<li>▸ scroll-позиция ленты или открытого кейса;<\/li>\n<li>▸ выбранный инструмент ввода.<\/li>\n<\/ul>\n<p>Если эти состояния смешать, любое переключение начинает иметь побочные эффекты. Открытие кейса сбрасывает зум, Feed теряет позицию, а возврат в предыдущее пространство складывает карточки в одну точку.<\/p>\n<p>Поэтому основой стала постоянная оболочка приложения — app shell. Верхняя навигация, Canvas и системные контролы не пересоздаются при переходах. Меняется состояние внутри них: открывается документ, переключается проекция данных или подставляется другое пространство.<\/p>\n<p>Это решение позволило сделать то, что было важно в исходной идее: кейс раскрывается внутри холста, как объект в Miro, а не ведёт на визуально чужую страницу. URL при этом меняется, работают Back и Forward, сохраняется камера, а прямой переход по ссылке всё равно открывает полноценный документ.<\/p>\n<p>Именно здесь макет перестал быть набором кадров и стал моделью приложения.<\/p>\n<h3>Три слоя системы<\/h3>\n<p>Чтобы не превратить код в набор исключений, я разделил систему на три слоя.<\/p>\n<h3><b>1. Контент<\/b><\/h3>\n<p>WordPress хранит карточки, кейсы, пространства, типы, подписи, теги, действия, изображения и редакторские настройки. Это единственный источник истины для того, что опубликовано и как контент должен быть изначально организован.<\/p>\n<h3><b>2. Геометрия и состояние<\/b><\/h3>\n<p>Отдельное ядро рассчитывает координаты, размеры, коллизии, связи, границы движения и положение камеры. Оно не знает, какого цвета карточка и какой easing используется в анимации.<\/p>\n<h3><b>3. Представление и motion<\/b><\/h3>\n<p>Рендереры превращают одну модель данных в Canvas, Feed или открытый документ. GSAP анимирует переход из текущего состояния в уже рассчитанное конечное, но не принимает геометрические решения.<\/p>\n<p>Это разделение оказалось одним из главных условий устойчивости. Если отдать GSAP владение координатами, визуально плавное движение быстро начинает расходиться с фактической раскладкой. Если хранить камеру в WordPress, редакторский контент смешивается с локальным состоянием посетителя. Если собрать Canvas и Feed из разных запросов, между ними появляются несовпадающие карточки.<\/p>\n<p>В готовой системе у каждого слоя одна ответственность. Это звучит как инженерная формальность, но именно такие границы определяют, можно ли дальше развивать интерфейс без постоянного ремонта.<\/p>\n<h3>Карточка как система свойств<\/h3>\n<p>Карточки в макете сильно отличались друг от друга. Persona содержит фотографию, город и время. Project — обложку, описание, теги и действие. Contacts состоит из ссылок. Clients — из логотипов. Results — почти полностью текстовый блок.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/blog.denislykin.ru\/pictures\/Frame-2.png\" width=\"1854\" height=\"939\" alt=\"\" \/>\n<\/div>\n<p>Можно было сделать один универсальный компонент с десятками условий или, наоборот, собрать каждый вид отдельно. Оба варианта плохо масштабируются: первый превращается в монолит, второй размножает одинаковые элементы и поведение.<\/p>\n<p>Я остановился на общей оболочке и реестре типов. У карточек единые шапка, бордеры, правила фокуса, размещение, теги, действие и коннектор. Внутреннее тело рендерится в зависимости от типа.<\/p>\n<p>Это позволило вынести в общую модель свойства, которые сначала казались локальными:<\/p>\n<ul>\n<li>▸ обложка может быть у карточки любого типа;<\/li>\n<li>▸ SVG-иконка типа всегда помещается в фиксированный монохромный контейнер;<\/li>\n<li>▸ обычные и якорные теги используют один формат;<\/li>\n<li>▸ действие может открыть кейс, перейти в пространство, открыть внешний URL, скачать файл, поставить лайк или быть отключённым;<\/li>\n<li>▸ ссылка независимо определяет, нужно ли открывать её в новой вкладке;<\/li>\n<li>▸ важная карточка получает настраиваемую плашку, иконку и акцентную кнопку, не меняя свою роль в раскладке.<\/li>\n<\/ul>\n<p>Самые важные уточнения касались не внешнего вида, а независимости свойств.<\/p>\n<p>Например, перемещение карточки и pinned сначала оказались связаны: если я задавал новую координату, система автоматически закрепляла объект. Но редакторская позиция не означает запрет физики. Карточка может начинать движение из выбранной точки и всё равно отталкиваться от соседей. Координата и блокировка — два разных свойства.<\/p>\n<p>То же произошло с коннекторами. Playground сначала оказался вариантом в одном списке с Dashed. Но Playground — это маршрут связи с другим пространством, а Dashed — её визуальный стиль. Пространственный коннектор может быть и сплошным, и пунктирным. После разделения на route и style модель стала описывать смысл, а не текущее количество макетов.<\/p>\n<p>Такие исправления легко принять за мелкие баги админки. На самом деле это признаки неправильно выделенных осей системы. Если два свойства могут изменяться независимо, они не должны жить в одном enum.<\/p>\n<h3>Физика без игрового движка<\/h3>\n<p>Карточки остаются обычными HTML-элементами. Это важно для доступности, текста, ссылок, адаптивности и поисковых систем. Поверх них работает небольшое геометрическое ядро.<\/p>\n<p>Для такого интерфейса можно было взять готовую whiteboard-библиотеку или отрисовать всё на HTML Canvas. Но сайт не является редактором схем: его основные объекты — статьи, изображения, ссылки и кнопки. Мне были важны нативный текст, семантика HTML, доступность и нормальная индексация.<\/p>\n<p>Поэтому фронтенд собран на vanilla JavaScript, CSS и WordPress Script Modules, а собственное ядро решает только специфические задачи пространства. Цена этого выбора — необходимость самостоятельно описать жесты, коллизии и камеру. Преимущество — модель взаимодействия не приходится подгонять под ограничения чужого редактора.<\/p>\n<p>При добавлении новой карточки система определяет её логическую сторону. Проекты по умолчанию уходят вправо, информационные блоки — влево. Затем карточка получает начальную позицию и проходит через collision pass: занятые объекты сдвигаются, пока между ними не появится необходимый зазор.<\/p>\n<p>Публичный клиент получает версионированный REST payload и сверяет его ревизию, когда вкладка возвращается в фокус. Если редактор опубликовал новую карточку, интерфейс не пересобирает весь Canvas: он сопоставляет сущности по стабильным ID, сохраняет неизменившиеся DOM-узлы и пользовательские координаты, а новый объект добавляет в существующую раскладку.<\/p>\n<p>Для поиска столкновений используется spatial hash. Вместо проверки каждой карточки с каждой ядро рассматривает только локальных соседей. Это стало особенно важно после появления нескольких пространств и карточек разного размера.<\/p>\n<p>Закреплённые карточки не участвуют в вытеснении, но обычные пользовательские координаты сохраняются отдельно от pinned. Есть и другие независимые признаки: карточку можно исключить из Fit, оставить без стороны и родителя или скрыть в одной из проекций.<\/p>\n<p>Коннекторы строятся по фактическим границам карточек. Точка крепления находится ровно на стыке шапки и тела, а кривая Безье меняется вслед за положением объектов. Во время анимации связь следует не за будущей координатой из модели, а за текущим положением DOM. Иначе карточка двигалась бы плавно, а линия мгновенно перескакивала бы в финальную точку.<\/p>\n<p>Даже точечный фон пришлось включить в координатную систему мира. Сначала карточки двигались, а паттерн оставался приклеенным к экрану. Формально Canvas работал, но пропадал основной навигационный сигнал: пользователь не видел, что перемещает камеру относительно пространства. После привязки позиции и шага сетки к камере фон начал вести себя как в Miro и FigJam.<\/p>\n<h3>Управление — это матрица, а не один drag<\/h3>\n<p>На десктопе один и тот же жест может означать разные вещи. Левая кнопка двигает карточку в режиме Pointer. Средняя кнопка или зажатый пробел перемещают холст. В режиме Hand холст должен двигаться даже тогда, когда курсор находится над карточкой. Правая кнопка не должна случайно запускать drag.<\/p>\n<p>На мобильном к этому добавляются тап, вертикальный scroll и pinch двумя пальцами. А внутри карточек остаются настоящие кнопки: «Открыть», «Скачать», «Перейти», «Лайк». Они должны нажиматься даже при выбранной руке.<\/p>\n<p>Я зафиксировал эти правила как единую матрицу input modality, а не стал исправлять каждый конфликт отдельным обработчиком. Выбранный инструмент запоминается при переключении режимов, первым по умолчанию становится Hand, а Reset восстанавливает не только камеру, но и каноническую раскладку карточек.<\/p>\n<p>Клавиатура получила собственную модель. Карточки используют roving focus: стрелки выбирают ближайший объект по направлению на Canvas и следующий по порядку в Feed, Enter запускает основное действие. Focus ring появляется только при клавиатурной навигации. Обычный клик мышью не оставляет на карточке случайную синюю обводку.<\/p>\n<p>Это хороший пример разницы между состоянием из UI-kit и поведением продукта. В макете можно нарисовать active, hover и focus. Но только в браузере становится понятно, что именно переводит интерфейс в каждое состояние и как разные способы ввода конкурируют между собой.<\/p>\n<h3>Камера и контролируемая бесконечность<\/h3>\n<p>Масштаб Canvas можно менять напрямую колесом или pinch-жестом. Такое управление должно быть мгновенным: если добавить easing между пальцами пользователя и интерфейсом, Canvas начинает ощущаться вязким.<\/p>\n<p>Программные переходы работают иначе. Сброс, Fit, возврат к стартовой карточке или восстановление пространства анимируются коротко и плавно. Пользователь видит, куда переместилась камера, но не ждёт декоративного пролёта.<\/p>\n<p>Для каждого пространства из админки можно задать начальный масштаб от 10 до 200 процентов и выбрать карточку, которая станет центром первого экрана. Это редакторское решение, а не попытка угадать универсальный Fit. Если проектов мало, Persona можно показать крупнее. Если карта выросла — отдалить камеру и дать больше контекста.<\/p>\n<p>На самом Canvas появился отдельный индикатор масштаба с быстрыми значениями. В Feed его нет, потому что там отсутствует сама концепция камеры.<\/p>\n<p>Границы движения рассчитываются по канонической раскладке и расширяются на безопасный запас. Карточка с ignore fit не влияет на начальное кадрирование, но остаётся внутри доступного мира: её можно найти, если сознательно уйти в сторону. За счёт этого пространство остаётся исследуемым, но пользователь не может бесконечно уехать от содержимого.<\/p>\n<h3>Canvas и Feed — две проекции одних данных<\/h3>\n<p>Feed не стал отдельной мобильной страницей или вторым шаблоном WordPress. Это другая проекция того же REST payload.<\/p>\n<p>В Portfolio центральная колонка содержит проекты, а боковые — информационные карточки. Скроллится только лента кейсов. Если боковая колонка выше viewport, она движется вместе с лентой до собственного конца и затем останавливается. Нижняя панель лежит поверх интерфейса, а последний кейс получает достаточный отступ, чтобы не прятаться за контролами.<\/p>\n<p>В остальных пространствах Feed проще: все карточки идут одной лентой без специальных боковых зон.<\/p>\n<p>При переключении Canvas и Feed карточки не исчезают и не возникают заново как несвязанные элементы. Система измеряет их экранную геометрию и морфит одно представление в другое. Панель инструментов тоже меняет форму: в Feed остаётся только переключатель режима, потому что Pointer, Hand и Reset там не нужны.<\/p>\n<p>Для первого мобильного визита Feed включается автоматически. Это не сохранённый выбор, а безопасный старт. Если пользователь явно вернулся на Canvas, его решение запоминается.<\/p>\n<p>Так мобильная версия перестала быть уменьшенным десктопом. Она использует ту же модель данных, но меняет приоритет взаимодействия.<\/p>\n<h3>Несколько пространств<\/h3>\n<p>Portfolio — особое пространство. Здесь карточки образуют связанную композицию вокруг Persona.<\/p>\n<p>Playground и любое новое пространство — свободный Canvas с обычными карточками. Проекты можно публиковать не только в Portfolio, но и в Playground. Переход доступен и из верхнего меню, и через карточку-портал внутри самой карты.<\/p>\n<p>Переключение происходит без перезагрузки app shell. Навигационный island плавно меняет ширину, камера и выбранный режим хранятся отдельно для каждого пространства, а реестр разделов позволяет добавлять новые пункты без переписывания хлебных крошек.<\/p>\n<p>Здесь обнаружился один из характерных асинхронных багов: при быстром переключении ответы нескольких запросов могли приходить в другом порядке, и карточки нового пространства складывались в одну стопку. Исправление было не в увеличении задержки анимации, а в правиле latest request wins и проверке актуальности данных перед применением раскладки.<\/p>\n<p>Это снова не визуальная проблема, хотя проявлялась она визуально.<\/p>\n<h3>Кейс внутри интерфейса<\/h3>\n<p>Карточка проекта открывает кейс внутри текущего Canvas. Остальные карточки и связи плавно уходят в нулевую прозрачность, нижние контролы скрываются, а выбранный объект разворачивается в document layer.<\/p>\n<p>Внешняя страница при этом не скроллится. Шапка кейса и оболочка приложения остаются на месте, прокручивается только контент внутри документа. Короткий кейс обтягивается по высоте, длинный занимает доступную область viewport.<\/p>\n<p>У этого сценария есть две равноправные точки входа:<\/p>\n<ol start=\"1\">\n<li>переход из карточки без перезагрузки;<\/li>\n<li>прямой URL с внешнего сайта, поисковика или социальной сети.<\/li>\n<\/ol>\n<p>Сначала они расходились. По прямой ссылке WordPress показывал самостоятельный шаблон кейса, визуально похожий на документ, но лишённый Canvas shell. Для пользователя это выглядело как сломанная версия сайта. Я привёл обе точки входа к одной оболочке: сервер отдаёт полноценную страницу, а клиент после загрузки восстанавливает пространство и открывает тот же case layer.<\/p>\n<p>Обычные WordPress Pages — например Privacy Policy — используют этот же document layer, но остаются отдельным типом документа. Их тело продолжает редактироваться Gutenberg, а Canvas переиспользует только навигацию, внутренний scroll и lifecycle открытия.<\/p>\n<p>Так получилось сохранить и цельность интерфейса, и нормальную работу URL, истории браузера, индексации и сценария без JavaScript.<\/p>\n<h3>Админка — вторая половина продукта<\/h3>\n<p>В начале проекта часть данных жила в старых настройках темы, часть — в полях записей, часть была захардкожена в рендерере. Persona и Contacts даже присутствовали на Canvas, но не имели очевидной точки редактирования.<\/p>\n<p>Внешне всё работало, но система не была управляемой. Это важное различие: контент на экране ещё не означает, что перед нами CMS.<\/p>\n<p>Я собрал все сущности в раздел Canvas Cards → All. Системные карточки, проекты и обычные записи получили реальные формы редактирования. Отдельно появились:<\/p>\n<ul>\n<li>▸ визуальная карта Canvas для координат и раскладки;<\/li>\n<li>▸ реестр типов карточек с заменяемыми SVG-иконками;<\/li>\n<li>▸ пространства;<\/li>\n<li>▸ настройки стартовой камеры;<\/li>\n<li>▸ единая схема тегов и действий;<\/li>\n<li>▸ формы Persona, Contacts и двух островов верхней навигации.<\/li>\n<\/ul>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/blog.denislykin.ru\/pictures\/Frame-3.png\" width=\"2281\" height=\"1236\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Прямо внутри админки можно расставить карточки на точно таком же холсте<\/div>\n<\/div>\n<p>Для проекта метаданные превью теперь собраны в начале редактора: обложка из Media Library, заголовок, краткое описание, обычные и якорный теги, кнопка действия. Редактору не нужно искать части одной карточки в разных metabox.<\/p>\n<p>Визуальная карта использует то же геометрическое ядро, что и публичный Canvas. Это принципиально: если административный preview рассчитывает коллизии иначе, сохранённая композиция никогда не совпадёт с сайтом.<\/p>\n<h3>Редактор кейса как модель композиции<\/h3>\n<p>Сначала кейс состоял из фиксированного набора полей: задача, решение, результат, картинка, видео. Пустые блоки всё равно оставляли место, а сложная история быстро упиралась в структуру формы.<\/p>\n<p>Я рассматривал возможность полностью отдать кейсы Gutenberg. Для обычных юридических страниц это правильный выбор. Но кейсам требовались контролируемые композиционные блоки, одинаковый рендеринг внутри Canvas и на прямой PHP-странице, особые правила ширины, локализованные подписи и интеграция с lightbox.<\/p>\n<p>Поэтому появился упорядоченный редактор блоков: Text, Callout, Image, Gallery, Visual media, Two columns, Metrics и Wide media. Пустой блок не попадает в результат. Текстовые поля поддерживают безопасный Markdown: жирное и курсивное начертания, ссылки, маркированные, нумерованные и вложенные списки.<\/p>\n<p>Здесь модель тоже уточнялась через реальное использование. Первая версия позволяла поставить текст либо на всю ширину, либо в левую, либо в правую колонку. Формально все три позиции существовали, но нельзя было собрать простой сценарий:<\/p>\n<ol start=\"1\">\n<li>общий заголовок;<\/li>\n<li>текст слева;<\/li>\n<li>его продолжение справа;<\/li>\n<li>изображение на всю ширину ниже.<\/li>\n<\/ol>\n<p>Проблема была не в интерфейсе селекта. Сама схема данных предполагала один текстовый поток. Я заменил её на отдельный layout текста и независимое содержимое правой колонки. На мобильном обе части последовательно складываются в одну колонку.<\/p>\n<p>Такая итерация хорошо показывает разницу между количеством настроек и выразительностью модели. Можно добавить много переключателей, но если структура не соответствует авторскому сценарию, редактор остаётся негибким.<\/p>\n<h3>Изображения, видео и lightbox<\/h3>\n<p>Медиа прошли похожий путь. В ранней версии картинки попадали в заранее заданные рамки, обрезались через object-fit: cover, а слишком крупное изображение внутри кейса приходилось горизонтально скроллить.<\/p>\n<p>Блок Video\/embed содержал поля WEBM URL, MP4 URL, Embed URL и Poster URL. Для разработчика их назначение можно восстановить, но редактор не должен разгадывать внутреннее устройство HTML5 video.<\/p>\n<p>Я заменил технический набор полей на понятный выбор контента:<\/p>\n<ul>\n<li>▸ изображение;<\/li>\n<li>▸ загруженное видео;<\/li>\n<li>▸ внешний плеер;<\/li>\n<li>▸ локализованная подпись;<\/li>\n<li>▸ ширина контентной колонки или всего кейса;<\/li>\n<li>▸ autoplay, muted и loop для видео.<\/li>\n<\/ul>\n<p>Высота всегда определяется пропорциями материала. Никакой скрытой обрезки.<\/p>\n<p>Для изображений внутри кейсов появился отдельный lightbox. Он умеет переключать элементы галереи, увеличивать от 100 до 400 процентов, возвращаться в Fit, масштабироваться колесом, двойным кликом и pinch-жестом, а также ограничивать pan границами изображения.<\/p>\n<p>После первой реализации панель контролов находилась сверху и отнимала часть полезной высоты — низ увеличенной картинки становился недоступен. Панель переехала под изображение, а размеры стали рассчитываться по реальному viewport и safe area мобильного устройства.<\/p>\n<p>Это небольшая деталь, но она отражает общий принцип проекта: интерфейс нельзя оценить только по спокойному состоянию на большом экране. Нужно пройти весь сценарий руками.<\/p>\n<h3>Типографика тоже является поведением<\/h3>\n<p>В кейсах много русского текста, длинных заголовков, чисел, сокращений и ссылок. Одного text-wrap: pretty недостаточно, чтобы убрать висячие предлоги или сохранить осмысленное соединение слов и чисел.<\/p>\n<p>Поэтому текст проходит через Typograf, а CSS отвечает уже за баланс строк и перенос внутри конкретного контейнера. Ручное форматирование автора — жирное начертание, курсив, ссылки и списки — сохраняется из WordPress без попыток найти «важные цифры» регулярным выражением.<\/p>\n<p>Так исчез ещё один ранний хардкод: Results сам выделял проценты и числа, из-за чего перед жирным фрагментом терялись пробелы. Теперь рендерер не интерпретирует смысл текста за автора. Он показывает то, что размечено в редакторе.<\/p>\n<p>То же внимание потребовалось компонентной типографике: подписям рядом с иконками, тексту внутри badge, высоте шапок и вертикальному положению символов. Эти вещи я доводил уже после стабилизации ядра — когда исправление line-height больше не могло замаскировать ошибку структуры.<\/p>\n<h3>Motion объясняет изменение состояния<\/h3>\n<p>GSAP появился в проекте не ради эффектности. Его задача — сделать изменение системы читаемым.<\/p>\n<p>Я использовал короткие анимации для:<\/p>\n<ul>\n<li>▸ вытеснения карточек при коллизиях;<\/li>\n<li>▸ появления новых объектов и skeleton-состояния загрузки;<\/li>\n<li>▸ открытия и закрытия кейса;<\/li>\n<li>▸ перехода Canvas ↔ Feed;<\/li>\n<li>▸ смены пространства;<\/li>\n<li>▸ программного Fit и Reset;<\/li>\n<li>▸ morphing панели инструментов и навигационных islands;<\/li>\n<li>▸ обратной связи для Share и Like.<\/li>\n<\/ul>\n<p>Прямое управление остаётся без tween. Карточка следует за указателем, камера — за колесом или пальцами. Анимация подключается только там, где интерфейс сам меняет состояние и пользователю важно увидеть причинно-следственную связь.<\/p>\n<p>Все вызовы GSAP находятся в отдельном motion-адаптере. Геометрия и REST-слой ничего о нём не знают. Активные tween можно прервать или перенаправить, а prefers-reduced-motion приводит систему сразу в конечное состояние.<\/p>\n<p>Это позволило избежать распространённой ловушки: движения выглядят плавно, но данные уже находятся в другом месте. В моём случае motion визуализирует решение ядра, а не подменяет его.<\/p>\n<h3>Навигация как часть пространства<\/h3>\n<p>Верхняя панель состоит из двух interface islands. Слева — логотип, имя и динамические хлебные крошки. Справа — участники и Share.<\/p>\n<p>Название активного пространства является переключателем. Portfolio и Playground не захардкожены: меню строится из реестра и допускает новые разделы. При открытии кейса цепочка продолжается до его названия.<\/p>\n<p>На мобильном полный маршрут не помещается. Вместо уменьшения шрифта или выхода island за границу я оставил только две смысловые точки: начало «лого + Denis Lykin» и текущую сущность. Длинный конечный заголовок обрезается многоточием внутри доступной ширины.<\/p>\n<p>Даже здесь понадобилось отделить анимацию от верстки. Во время morphing ширина панели на долю секунды становилась меньше содержимого, и «Denis Lykin» с «Knowledge Base» переносились на две строки. Минимальные размеры и запрет переноса стали частью layout-контракта, а не настройкой easing.<\/p>\n<p>Настройки логотипа, аватаров, текста и ссылки Share находятся в WordPress. После копирования ссылка не просто меняет надпись: иконка и ширина кнопки переходят в состояние «Скопировано!» согласованно, без скачка текста.<\/p>\n<h3>Дизайн в паре с AI coding agent<\/h3>\n<p>Значительная часть проекта создавалась в постоянном диалоге с AI coding agent. Но я не воспринимал его как генератор, которому можно показать Figma и получить готовый сайт.<\/p>\n<p>Для меня это был способ сократить расстояние между решением и проверяемым интерфейсом.<\/p>\n<p>Единицей работы стал не экран, а контракт поведения. Я описывал не только желаемый результат, но и ограничения: кто владеет состоянием, что должно сохраниться после перехода, какие свойства независимы, где допустима анимация и что происходит при прямом URL или другом способе ввода.<\/p>\n<p>Рабочий цикл выглядел так:<\/p>\n<ol start=\"1\">\n<li>Сформулировать продуктовый инвариант.<\/li>\n<li>Получить работающую реализацию, а не статичную картинку.<\/li>\n<li>Проверить её в браузере через DevTools и Playwright.<\/li>\n<li>Отличить локальный визуальный дефект от ошибки модели.<\/li>\n<li>Исправить общий контракт и добавить регрессионную проверку.<\/li>\n<\/ol>\n<p>Например, просьба «не закреплять карточку после перемещения» превратилась в разделение редакторской координаты и физического lock. Замечание про Playground и Dashed — в две ортогональные оси коннектора. Невозможность одновременно заполнить левую и правую колонки — в новую схему контентного блока. Сломанный прямой URL — в единый lifecycle документа для серверного и клиентского входа.<\/p>\n<p>AI хорошо ускоряет локальную реализацию. Но он не снимает с дизайнера ответственность за целостность. Если давать только визуальные команды, система быстро покрывается исключениями, потому что каждая следующая правка оптимизирует один кадр. Чем точнее дизайнер понимает браузер и модель данных, тем лучше он может перевести наблюдение в устойчивое правило.<\/p>\n<h3>Зачем дизайнеру разбираться во фронтенде<\/h3>\n<p>Речь не о том, что каждый дизайнер обязан самостоятельно писать production-код. Важнее уметь видеть технические последствия продуктового решения.<\/p>\n<p>Понимание DOM помогло оставить карточки семантическими HTML-элементами, а не рисовать весь интерфейс на Canvas. Знание Pointer Events позволило разделить мышь, touch, pinch и клавиатуру. History API сделал бесшовный кейс совместимым с URL и навигацией браузера. Понимание compositing понадобилось, когда текст и изображения начинали пикселизоваться после масштабирования. Работа с REST и post meta помогла не смешать данные редактора с состоянием посетителя.<\/p>\n<p>Без этого разговор легко сводится к формулировкам «сделай плавнее» или «здесь что-то прыгает». С технической грамотностью дизайнер может сказать: прямой жест должен обновляться синхронно, программная камера — через interruptible tween; background-position должен зависеть от world transform; старый async-response не имеет права перезаписывать активное пространство.<\/p>\n<p>Это не попытка стать разработчиком вместо разработчика, а, скорее, способность проектировать на уровне причин.<\/p>\n<p>На уровне Principal Designer ценность часто находится именно там: не нарисовать ещё один экран, а определить границы между системами, назвать инварианты и сделать так, чтобы разные решения не противоречили друг другу.<\/p>\n<h3>Продакшен — часть дизайна<\/h3>\n<p>Новая тема живёт отдельно от старой и активируется без миграции или удаления контента. WordPress отдаёт первый серверный render, а REST поддерживает обновления внутри постоянной оболочки.<\/p>\n<p>CI\/CD доставляет код темы и плагинов, но не перезаписывает базу, uploads и редакторский контент. Это позволяет развивать интерфейс независимо от production-данных.<\/p>\n<p>Кроме основной темы появились небольшие системные слои:<\/p>\n<ul>\n<li>▸ юридические страницы и управление Cookies;<\/li>\n<li>▸ настраиваемые Open Graph и X\/Twitter previews для сайта и отдельных кейсов;<\/li>\n<li>▸ обезличенная агрегированная аналитика без идентификаторов посетителя и сессии;<\/li>\n<li>▸ локальные SVG-ассеты и GSAP без зависимости от CDN.<\/li>\n<\/ul>\n<p>Playwright проверяет переходы между Canvas, Feed и кейсом, сохранение камеры, клавиатурную навигацию, мобильные размеры, административные контролы и прямые маршруты. Визуальные снимки обновляются отдельно, после ручной проверки diff. Lighthouse desktop baseline достиг 100 баллов по Accessibility, Best Practices и SEO.<\/p>\n<p>Тесты не гарантируют хорошего дизайна. Но они защищают уже принятые дизайнерские решения от случайного разрушения следующей функцией.<\/p>\n<h3>Где система ломалась<\/h3>\n<p>Проект развивался итерациями, и самые полезные выводы появились не в Figma, а в местах, где поведение расходилось с ожиданием.<\/p>\n<h3><b>Внешне похожие свойства оказывались разными<\/b><\/h3>\n<p>Так произошло с маршрутом и стилем коннектора, координатой и pinned, видимостью во время Fit и доступностью карточки в пространстве. После каждого такого случая я проверял, не объединяет ли модель независимые признаки только потому, что сейчас у них по два варианта.<\/p>\n<h3><b>Красивое состояние не гарантировало правильный переход<\/b><\/h3>\n<p>Кейс мог хорошо выглядеть открытым, но зависать на загрузке. Карточки могли плавно двигаться, но резко появляться. Навигация могла быть выровнена, но на один кадр складывать текст в две строки во время morphing.<\/p>\n<p>Поэтому motion стал отдельной областью проектирования со своими loading, interruption, error и reduced-motion состояниями.<\/p>\n<h3><b>Desktop скрывал проблемы модели<\/b><\/h3>\n<p>На большом экране можно было не заметить конкуренцию scroll-контейнеров, слишком широкую колонку кейса или недоступный низ lightbox. Мобильный сценарий заставил определить, что именно скроллится, где находится safe area и какой жест принадлежит браузеру, а какой Canvas.<\/p>\n<h3><b>Админка быстро показывала хардкод<\/b><\/h3>\n<p>Если видимый объект нельзя найти и изменить, значит интерфейс ещё не стал системой. Невозможность отредактировать Contacts или выбрать обложку из Media Library была не «нехваткой поля», а сигналом, что контентная модель не совпадает с публичным рендерером.<\/p>\n<h3>Результат<\/h3>\n<p>В итоге портфолио стало небольшой контентной платформой.<\/p>\n<p>У неё есть пространственный Canvas с физикой, связями, несколькими пространствами и управляемой камерой. Есть Feed как альтернативный способ чтения тех же данных. Кейсы, Persona и обычные страницы открываются внутри постоянной оболочки и сохраняют нормальные URL. WordPress управляет не только текстом, но и типами карточек, композицией, медиа, действиями, тегами и стартовым состоянием Canvas.<\/p>\n<p>Самое важное — система остаётся расширяемой. Новое пространство не требует новой навигации. Новый тип карточки получает общую оболочку. Новый визуальный стиль коннектора не меняет его маршрут. Новая запись не требует пересборки карты вручную.<\/p>\n<p>Портфолио показывает проекты, но одновременно демонстрирует способ мышления: как я работаю с неоднозначностью, раскладываю интерфейс на независимые свойства, проверяю гипотезы в коде и превращаю отдельные правки в правила системы.<\/p>\n<h2><b>Выводы<\/b><\/h2>\n<h3><b>Сильная метафора должна определять поведение<\/b><\/h3>\n<p>Node-based-интерфейс имеет смысл не потому, что выглядит необычно. Он задаёт отношения между объектами, свободу исследования и модель навигации. Если точки не движутся вместе с миром, коннекторы не следуют за карточками, а переход открывает обычную страницу, метафора перестаёт работать.<\/p>\n<h3><b>Состояния важнее экранов<\/b><\/h3>\n<p>Canvas, Feed, кейс и пространство нельзя проектировать только как набор кадров. Важнее определить, что сохраняется между ними, какая зона скроллится и кто владеет камерой, URL и фокусом.<\/p>\n<h3><b>Админка — часть пользовательского опыта<\/b><\/h3>\n<p>CMS является продуктом для редактора. Если создание карточки требует знания внутреннего URL или поиска полей по разным экранам, публичный интерфейс нельзя считать законченным.<\/p>\n<h3><b>Независимые свойства нужно моделировать независимо<\/b><\/h3>\n<p>Большая часть сложных багов возникала там, где два понятия случайно объединялись в одно поле. Хорошая архитектура начинается с правильных различий.<\/p>\n<h3><b>Анимация должна объяснять, а не управлять<\/b><\/h3>\n<p>Motion показывает связь между состояниями, но не хранит истину о них. Прямое взаимодействие остаётся быстрым, программное — коротким и читаемым.<\/p>\n<h3><b>Мобильная версия меняет приоритеты<\/b><\/h3>\n<p>На телефоне я начинаю с Feed, сокращаю навигацию до начала и конечной точки, складываю колонки и сохраняю нативный scroll. Это не компромиссная копия десктопа, а другая точка входа в ту же систему.<\/p>\n<h3><b>AI ускоряет производство решений, но не заменяет дизайн-суждение<\/b><\/h3>\n<p>Чем быстрее появляется код, тем важнее способность заметить неверную модель. Основной навык смещается от производства макетов к формулированию инвариантов, проверке поведения и удержанию целостности продукта.<\/p>\n<h3>Что бы я сделал иначе<\/h3>\n<p>Ретроспективно я бы ещё до первой визуальной реализации описал state machine оболочки: пространство, режим, документ, камера, scroll и input modality. Часть ранних ошибок была следствием того, что эти состояния проявлялись постепенно.<\/p>\n<p>Я бы раньше собрал единую схему карточки и редакторский inventory. Это сократило бы период, когда Contacts, Persona, теги и действия жили в разных местах.<\/p>\n<p>Я бы с самого начала проверял touch на нескольких физических устройствах, а не только через эмуляцию. Браузерный viewport помогает с адаптивностью, но не воспроизводит все конфликты scroll, tap и pinch.<\/p>\n<p>И я бы раньше ввёл правило отмены устаревших запросов при переключении пространств. Асинхронность редко видна в спокойном прототипе, но становится частью дизайна, как только интерфейс начинает жить в реальной сети.<\/p>\n<p>При этом я бы сохранил общий порядок работы: сначала архитектурный контракт, затем функциональный browser QA и только потом pixel-perfect. В сложном интерфейсе визуальная полировка становится устойчивой лишь тогда, когда устойчиво его ядро.<\/p>\n<h3>Вместо заключения<\/h3>\n<p>Этот сайт начинался как новая форма портфолио, а превратился в исследование границы между дизайном и разработкой.<\/p>\n<p>Я всё меньше верю в модель, где дизайнер заканчивает работу после передачи макета. Для систем с живыми данными, множеством состояний и прямым взаимодействием дизайн продолжается в браузере, в структуре CMS, в контрактах API, в поведении при ошибке и в том, что происходит между двумя красивыми кадрами.<\/p>\n<p>Principal Designer не обязан лично реализовывать каждый слой. Но он должен видеть систему целиком: понимать, где визуальное решение становится моделью данных, где анимация конфликтует с состоянием, где редакторский сценарий важнее ещё одного компонента и где локальная правка должна превратиться в общее правило.<\/p>\n<p>В этом смысле портфолио выполнило свою главную задачу. Оно не только рассказывает, как я проектирую продукты, но ещё и само работает тем же способом.<\/p>\n<h3>Связаться со мной<\/h3>\n<p>Если есть желание обсудить этот кейс, другие кейсы или пообщаться по иным вопросам в сфере продуктового дизайна — пишите мне в <a href=\"https:\/\/t.me\/denyalykin\">телеграм<\/a> или на <a href=\"mailto:i@dlykin.ru\">почту<\/a>. Подробнее обо мне и моей работе можно узнать на <a href=\"https:\/\/denislykin.ru\">моём сайте<\/a>. Сейчас я в поисках продуктовой команды, в которой смогу развивать продукт и быть полезным игроком. Пишите, буду рад пообщаться!<\/p>\n",
            "summary": "⏳ Время чтения ±20 минут",
            "date_published": "2026-08-31T18:47:09+03:00",
            "date_modified": "2026-09-04T17:50:41+03:00",
            "tags": [
                "Дизайн",
                "Портфолио",
                "Продукт"
            ],
            "image": "https:\/\/blog.denislykin.ru\/pictures\/Frame-1.png",
            "_date_published_rfc2822": "Mon, 31 Aug 2026 18:47:09 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "6",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/blog.denislykin.ru\/pictures\/Frame-1.png",
                    "https:\/\/blog.denislykin.ru\/pictures\/1.0.png",
                    "https:\/\/blog.denislykin.ru\/pictures\/1.0-Feed.png",
                    "https:\/\/blog.denislykin.ru\/pictures\/Concept-2.png",
                    "https:\/\/blog.denislykin.ru\/pictures\/Frame-2.png",
                    "https:\/\/blog.denislykin.ru\/pictures\/Frame-3.png"
                ]
            }
        },
        {
            "id": "5",
            "url": "https:\/\/blog.denislykin.ru\/all\/contrust-design-system\/",
            "title": "Contrust Design System: как объединить 15+ строительных модулей в одну продуктовую систему",
            "content_html": "<h3><a href=\"https:\/\/denislykin.ru\/?space=blog\">→Статья переехала в новый блог<\/a><\/h3>\n<p>⏳ <i>Время чтения ±15 минут<\/i><\/p>\n<p>Сейчас я старший продуктовый дизайнер с восьмилетним опытом, но для того, чтобы понять, как функционирую сложные системы и процессы, мне понадобилось пройти через множество задач и преобразований. Одной из таких задач стала собственная, «ванильная» дизайн-система, которая была разработана не для работы тысяч клиентов, а для удобной навигации строителей, инженеров и внутренних сотрудников строительных компаний. Как часто это бывает — идея была здравая. Её реализация тоже не подкачала, но сейчас, глядя на весь пройденный путь, я чётко вижу моменты, которые я бы отработал иначе.<\/p>\n<h3>Кому будет полезно?<\/h3>\n<p>В первую очередь дизайнерам, работающим с нестандартными интерфейсами. Нестандартный интерфейс в данном случае тот, который содержит неочевдиные механизмы — 3D пространство, объёмные массивы данных, связи «многие-ко-многим». Ещё дизайнерам, лидам и овнерам, которые размышляют над тем, нужна ли им собственная дизайн-система вообще. Ну и наконец тем, кто уже работает с дизайн-системой и размышляет над её масштабированием.<\/p>\n<h3>Контекст<\/h3>\n<p>В 2020 году один из крупнейших застройщиков Санкт-Петербурга имел свой научно-технический центр, отвечающий за цифровизацию процесса строительства. На тот момент уже широко был развит BIM — система 3D моделирования строительных объектов, хранившая внутри моделей плотный слой информации о будущем здании. Компании требовался веб-дизайнер, так как весь сервис проектировался в вебе. Эту позицию я и занял, начав постепенно разбираться в сути. Сервис на тот момент не существовал как что-то целое и единое: это был набор различных инструментов, нацеленных в первую очередь на пачку прозрачных отчётов.<\/p>\n<p>Первым крупным цифровым инструментом был модуль безопасности. Инженеры обходили строительные площадки, фиксировали нарушения на бумаге, а затем переносили информацию в BIM-модель на рабочем компьютере. Позже процесс частично перевели на планшеты, что позволило отмечать нарушения непосредственно на объекте.<\/p>\n<p>В течение следующих месяцев к модулю безопасности добавились документооборот, пожарная безопасность, передача квартир покупателям, исполнительная документация, технический надзор и другие сервисы. Всего в продуктовом контуре появилось более 15 модулей.<\/p>\n<p>Команда на старте состояла из пяти разработчиков, двух frontend- и трёх backend-инженеров, а также группы специалистов строительного профиля. Проектирование интерфейсов в основном выполняли сами инженеры. Их основной задачей было перенести сложные строительные процессы в цифровую среду. Системное проектирование пользовательского интерфейса в этот процесс не входило.<\/p>\n<h3>Исходное состояние интерфейсов<\/h3>\n<p>Каждый модуль развивался как отдельный инструмент. Новая функция обычно проектировалась под конкретную задачу и сразу передавалась в разработку.<\/p>\n<p>UI-kit отсутствовал. Разработчики использовали базовые элементы frontend-фреймворков, дополняя их локальными компонентами. Большая часть решений создавалась заново.<\/p>\n<p>В интерфейсах накопились типовые проблемы:<\/p>\n<ul>\n<li>▸ одинаковые действия выглядели и работали по-разному;<\/li>\n<li>▸ использовались несвязанные между собой иконки;<\/li>\n<li>▸ не было единой типографической шкалы;<\/li>\n<li>▸ интервалы и размеры элементов задавались локально;<\/li>\n<li>▸ цветовые решения не учитывали требования контрастности;<\/li>\n<li>▸ отсутствовали единые правила для таблиц, форм, модальных окон и состояний;<\/li>\n<li>▸ иерархия данных зависела от конкретного модуля;<\/li>\n<li>▸ компоненты разрабатывались под один сценарий и редко переиспользовались.<\/li>\n<\/ul>\n<p>Например, формирование отчёта, экспорт отчёта и фиксация нарушения могли быть реализованы тремя разными способами. В одном модуле использовалась кнопка, в другом иконка, в третьем действие находилось внутри выпадающего меню.<\/p>\n<p>Для пользователей это увеличивало время освоения новых модулей. Для разработки это означало поддержку большого количества одноразовых решений.<\/p>\n<p>Каждая новая задача порождала новый интерфейс, новую frontend-реализацию и новый слой legacy.<\/p>\n<h3>Постановка задачи<\/h3>\n<p>Изначально задача не формулировалась как создание дизайн-системы.<\/p>\n<p>Необходимо было объединить разрозненные сервисы научно-технического центра в единый продукт. Для этого требовалось одновременно систематизировать несколько уровней:<\/p>\n<ul>\n<li>▸ бренд;<\/li>\n<li>▸ ролевую модель;<\/li>\n<li>▸ интерфейсную систему;<\/li>\n<li>▸ принципы интернационализации;<\/li>\n<li>▸ единую точку входа;<\/li>\n<li>▸ правила масштабирования продукта.<\/li>\n<\/ul>\n<p>Дизайн-система стала инфраструктурной частью этой работы.<\/p>\n<p>Её основные цели:<\/p>\n<ol start=\"1\">\n<li>Снизить количество локальных интерфейсных решений.<\/li>\n<li>Ускорить проектирование новых модулей.<\/li>\n<li>Сократить объём повторной frontend-разработки.<\/li>\n<li>Зафиксировать единые правила для desktop и tablet.<\/li>\n<li>Создать общий язык между внутренней командой, подрядчиками и разработкой.<\/li>\n<li>Обеспечить постепенную миграцию существующих продуктов без полной переработки legacy.<\/li>\n<\/ol>\n<h3>Build or adopt<\/h3>\n<p>На первом этапе мы рассматривали два варианта:<\/p>\n<ul>\n<li>▸ использовать готовую UI-библиотеку;<\/li>\n<li>▸ создать собственную дизайн-систему.<\/li>\n<\/ul>\n<p>Основным аргументом в пользу собственной системы была BIM-модель.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/blog.denislykin.ru\/pictures\/BIM.png.jpg\" width=\"2560\" height=\"1244\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Стандартное отображение BIM-модели<\/div>\n<\/div>\n<p>Продукт содержал большое количество сценариев, связанных с 3D-пространством: навигацию, выбор элементов, фильтрацию, работу со слоями, фиксацию нарушений, назначение статусов и групповые операции.<\/p>\n<p>Мы предположили, что готовая библиотека не сможет покрыть эти сценарии без существенных ограничений.<\/p>\n<p>Дополнительными факторами были:<\/p>\n<ul>\n<li>▸ одновременная поддержка desktop и tablet;<\/li>\n<li>▸ работа инженеров непосредственно на строительных площадках;<\/li>\n<li>▸ высокая плотность данных;<\/li>\n<li>▸ необходимость крупных интерактивных зон для планшета;<\/li>\n<li>▸ нестандартные режимы взаимодействия с BIM-viewer.<\/li>\n<\/ul>\n<p>На основе этих вводных было принято решение создать собственную систему.<\/p>\n<p>При проектировании foundations я анализировал Ant Design, Gravity и Fluent. Из этих систем были взяты устойчивые принципы построения компонентов, состояний, типографики и иерархии. Визуальные и технические параметры адаптировались под Contrust.<\/p>\n<p>Ретроспективно это решение оказалось частично ошибочным. Специфические сценарии действительно требовали собственной систематизации, но большинство базовых компонентов можно было построить поверх существующей библиотеки.<\/p>\n<h3>Архитектура дизайн-системы<\/h3>\n<p>Система состояла из нескольких уровней:<\/p>\n<ol start=\"1\">\n<li>Foundations.<\/li>\n<li>Tokens.<\/li>\n<li>Primitive components.<\/li>\n<li>Composite components.<\/li>\n<li>Product patterns.<\/li>\n<li>Domain patterns.<\/li>\n<li>Product modules.<\/li>\n<\/ol>\n<p>Такое разделение позволяло отделить общие правила интерфейса от специфики строительных сценариев.<\/p>\n<h3><b>Foundations<\/b><\/h3>\n<p>В foundations вошли:<\/p>\n<ul>\n<li>▸ цветовая система;<\/li>\n<li>▸ типографика;<\/li>\n<li>▸ spacing;<\/li>\n<li>▸ radius;<\/li>\n<li>▸ borders;<\/li>\n<li>▸ surface;<\/li>\n<li>▸ elevation;<\/li>\n<li>▸ grid;<\/li>\n<li>▸ iconography;<\/li>\n<li>▸ размеры интерактивных элементов;<\/li>\n<li>▸ правила для desktop и tablet.<\/li>\n<\/ul>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/blog.denislykin.ru\/pictures\/DS-Slide-01.png.jpg\" width=\"2560\" height=\"1881\" alt=\"\" \/>\n<\/div>\n<p>Для типографики использовался PT Root UI. Гарнитура хорошо работала с кириллицей, выдерживала высокую плотность интерфейса и могла свободно использоваться в продукте.<\/p>\n<p>Типографическая шкала строилась с коэффициентом 1,25. Для малых размеров применялся увеличенный интерлиньяж в 140%. По мере роста размера текста относительный интерлиньяж уменьшался пропорционально.<\/p>\n<p>Цветовая модель разделялась на три уровня:<\/p>\n<ul>\n<li>▸ primitive colors;<\/li>\n<li>▸ semantic colors;<\/li>\n<li>▸ component tokens.<\/li>\n<\/ul>\n<p>Например:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">Blue \/ 600\n→ Action \/ Primary\n→ Button \/ Background \/ Primary<\/code><\/pre><p>Это позволяло менять визуальные параметры без ручной переработки каждого компонента.<\/p>\n<h3><b>Tokens<\/b><\/h3>\n<p>Для токенов использовался собственный префикс. Через них описывались:<\/p>\n<ul>\n<li>▸ цвета;<\/li>\n<li>▸ размеры;<\/li>\n<li>▸ отступы;<\/li>\n<li>▸ радиусы;<\/li>\n<li>▸ границы;<\/li>\n<li>▸ типографика;<\/li>\n<li>▸ свойства surface;<\/li>\n<li>▸ отдельные component-level значения.<\/li>\n<\/ul>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/blog.denislykin.ru\/pictures\/DS-Slide-02.png.jpg\" width=\"2560\" height=\"1776\" alt=\"\" \/>\n<\/div>\n<p>Токены использовались как общий источник правил для Figma и фронта.<\/p>\n<p>На раннем этапе часть значений существовала как вручную синхронизируемая система. После выхода Variables в Figma мы перевели активную часть UI-kit на переменные.<\/p>\n<p>Миграция заняла около двух недель периодической работы нескольких дизайнеров.<\/p>\n<p>Мы разделили макеты на три категории:<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: left\"><b>Состояние<\/b><\/td>\n<td style=\"text-align: left\"><b>Решение<\/b><\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: left\">Активная работа<\/td>\n<td style=\"text-align: left\">Перевести на Variables<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: left\">В разработке<\/td>\n<td style=\"text-align: left\">Перевести на Variables<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: left\">Уже выпущенный legacy<\/td>\n<td style=\"text-align: left\">Сверить с токенами, не перестраивать<\/td>\n<\/tr>\n<\/table>\n<p>Полную миграцию старых макетов не проводили. Стоимость такой работы не соответствовала её практической ценности.<\/p>\n<h3>Компонентная архитектура<\/h3>\n<p>Компоненты строились атомарно с использованием nested components и component properties в Figma.<\/p>\n<p>Основной принцип был следующим:<\/p>\n<blockquote>\n<p>Если вложенный элемент мог независимо менять состояние, он оформлялся как отдельный компонент.<\/p>\n<\/blockquote>\n<p>Например, поле ввода могло включать:<\/p>\n<ul>\n<li>▸ label;<\/li>\n<li>▸ input area;<\/li>\n<li>▸ placeholder;<\/li>\n<li>▸ entered value;<\/li>\n<li>▸ leading icon;<\/li>\n<li>▸ trailing action;<\/li>\n<li>▸ helper text;<\/li>\n<li>▸ validation message;<\/li>\n<li>▸ error state.<\/li>\n<\/ul>\n<p>Каждая часть, которая имела собственные варианты или состояния, проектировалась отдельно и затем вкладывалась в основной компонент.<\/p>\n<p>Это позволяло управлять сложным компонентом из одной панели свойств, не разбирая его вручную.<\/p>\n<h3><b>Пример структуры Button<\/b><\/h3>\n<p>Кнопки были представлены примерно в следующих параметрах:<\/p>\n<ul>\n<li>▸ 5 размеров;<\/li>\n<li>▸ несколько визуальных типов;<\/li>\n<li>▸ 4 варианта сочетания текста и иконки;<\/li>\n<li>▸ состояния default, hover, active, disabled, loading;<\/li>\n<li>▸ tablet- и desktop-размерности;<\/li>\n<li>▸ варианты для полноширинного и компактного размещения.<\/li>\n<\/ul>\n<p>Кроме кнопок в систему вошли:<\/p>\n<ul>\n<li>▸ inputs;<\/li>\n<li>▸ dropdowns;<\/li>\n<li>▸ selects;<\/li>\n<li>▸ tooltips;<\/li>\n<li>▸ avatars;<\/li>\n<li>▸ badges;<\/li>\n<li>▸ lists;<\/li>\n<li>▸ tables;<\/li>\n<li>▸ grids;<\/li>\n<li>▸ popovers;<\/li>\n<li>▸ modals;<\/li>\n<li>▸ accordions;<\/li>\n<li>▸ drawers;<\/li>\n<li>▸ rails;<\/li>\n<li>▸ quiz controls;<\/li>\n<li>▸ status components;<\/li>\n<li>▸ navigation controls.<\/li>\n<\/ul>\n<p>При этом количество компонентов не рассматривалось как самостоятельная метрика зрелости системы.<\/p>\n<p>Основным критерием была способность закрывать новые задачи существующими сущностями.<\/p>\n<h3>Компоненты и продуктовые паттерны<\/h3>\n<p>После запуска базовой библиотеки стало понятно, что консистентности на уровне компонентов недостаточно.<\/p>\n<p>Одинаковые элементы могли использоваться в разных последовательностях, с разной логикой и разными состояниями. Поэтому поверх библиотеки компонентов был создан слой продуктовых паттернов.<\/p>\n<p>В него вошли:<\/p>\n<ul>\n<li>▸ сворачивание drawer в navigation rail;<\/li>\n<li>▸ перевод BIM-модели в fullscreen;<\/li>\n<li>▸ режимы модальных окон;<\/li>\n<li>▸ фильтрация;<\/li>\n<li>▸ аккордеоны;<\/li>\n<li>▸ popover-сценарии;<\/li>\n<li>▸ таблицы с bulk actions;<\/li>\n<li>▸ режим выбора элементов модели;<\/li>\n<li>▸ групповые действия;<\/li>\n<li>▸ паттерны статусов;<\/li>\n<li>▸ сценарии работы с планшетом.<\/li>\n<\/ul>\n<h3>BIM Multi Select<\/h3>\n<p>Одним из основных доменных паттернов стал выбор нескольких объектов внутри BIM-модели.<\/p>\n<p>Сценарий состоял из четырёх шагов:<\/p>\n<ol start=\"1\">\n<li>Пользователь открывает модель в обычном режиме навигации.<\/li>\n<li>Включает режим выбора.<\/li>\n<li>Выбирает несколько элементов модели.<\/li>\n<li>Применяет действие ко всей группе.<\/li>\n<\/ol>\n<p>После включения режима кнопка оставалась в активном состоянии. Пользователь мог выбрать 3, 6 или 10 объектов, после чего выполнить групповое действие.<\/p>\n<p>К выбранным элементам могли применяться:<\/p>\n<ul>\n<li>▸ изменение статуса;<\/li>\n<li>▸ создание нарушения;<\/li>\n<li>▸ назначение ответственного;<\/li>\n<li>▸ добавление в проверку;<\/li>\n<li>▸ фильтрация;<\/li>\n<li>▸ включение в отчёт.<\/li>\n<\/ul>\n<p>Для этого паттерна требовалось синхронизировать:<\/p>\n<ul>\n<li>▸ состояние toolbar;<\/li>\n<li>▸ состояние модели;<\/li>\n<li>▸ визуальное выделение объектов;<\/li>\n<li>▸ счётчик выбранных элементов;<\/li>\n<li>▸ доступные действия;<\/li>\n<li>▸ выход из режима;<\/li>\n<li>▸ отмену выбора;<\/li>\n<li>▸ конфликт с native controls viewer.<\/li>\n<\/ul>\n<p>Этот сценарий был специфичен для продукта и действительно требовал собственной системы правил.<\/p>\n<h3>Контроль роста библиотеки<\/h3>\n<p>По мере развития продукта владельцы модулей регулярно предлагали новые элементы.<\/p>\n<p>Одним из примеров была шахматка квартир. Изначально задача выглядела как запрос на новый сложный компонент.<\/p>\n<p>После декомпозиции выяснилось, что интерфейс состоит из трёх уже существующих сущностей:<\/p>\n<ul>\n<li>▸ grid;<\/li>\n<li>▸ badge;<\/li>\n<li>▸ status token.<\/li>\n<\/ul>\n<p>Вместо разработки отдельного компонента мы собрали матрицу квартир из существующих badge-компонентов с заданной сеткой и статусами.<\/p>\n<p>Новый системный компонент не потребовался.<\/p>\n<p>Для принятия подобных решений использовались следующие критерии:<\/p>\n<ol start=\"1\">\n<li>Можно ли решить задачу существующим компонентом.<\/li>\n<li>Можно ли расширить существующий компонент новым variant.<\/li>\n<li>Повторяется ли сценарий в других модулях.<\/li>\n<li>Является ли решение локальным или системным.<\/li>\n<li>Требует ли оно отдельной документации.<\/li>\n<li>Будет ли компонент поддерживаться в Storybook.<\/li>\n<li>Насколько высока стоимость его дальнейшего развития.<\/li>\n<\/ol>\n<h3>Figma, Storybook и документация<\/h3>\n<p>UI-kit существовал в Figma и параллельно переносился в Storybook.<\/p>\n<p>В Storybook фиксировались:<\/p>\n<ul>\n<li>▸ компонент;<\/li>\n<li>▸ его состояния;<\/li>\n<li>▸ варианты;<\/li>\n<li>▸ технические параметры;<\/li>\n<li>▸ правила использования;<\/li>\n<li>▸ ограничения;<\/li>\n<li>▸ примеры.<\/li>\n<\/ul>\n<p>Документация готовилась совместно с frontend-разработкой и техническим писателем.<\/p>\n<p>Таким образом система включала:<\/p>\n<ul>\n<li>▸ Figma UI-kit;<\/li>\n<li>▸ tokens;<\/li>\n<li>▸ Storybook;<\/li>\n<li>▸ текстовую документацию;<\/li>\n<li>▸ brandbook;<\/li>\n<li>▸ правила contribution;<\/li>\n<li>▸ changelog;<\/li>\n<li>▸ governance.<\/li>\n<\/ul>\n<p>Figma не рассматривалась как единственный источник дизайн-системы. Она была рабочим инструментом дизайнеров, но кодовая реализация и документация имели равный статус.<\/p>\n<h3>Работа с распределёнными командами<\/h3>\n<p>К моменту запуска UI-kit к проекту подключились внешние команды дизайна и frontend-разработки.<\/p>\n<p>Из-за ограничений Figma, стоимости лицензий и последующих санкционных ограничений команды работали в разных окружениях.<\/p>\n<p>Централизованное подключение всех дизайнеров к одной библиотеке было невозможно.<\/p>\n<p>Для синхронизации использовался следующий процесс:<\/p>\n<ul>\n<li>▸ головной UI-kit оставался у внутренней команды;<\/li>\n<li>▸ первая страница файла была отведена под changelog;<\/li>\n<li>▸ изменения сопровождались ссылками на конкретные компоненты;<\/li>\n<li>▸ два раза в неделю внешние дизайнеры проверяли обновления;<\/li>\n<li>▸ после синхронизации они оставляли отметку о просмотре;<\/li>\n<li>▸ спорные изменения обсуждались отдельно;<\/li>\n<li>▸ предложения по новым компонентам проходили через внутреннюю дизайн-команду.<\/li>\n<\/ul>\n<p>Такой процесс был частично ручным, но обеспечивал управляемое распространение системы между независимыми командами.<\/p>\n<h3>Где система ломалась<\/h3>\n<p>Основные проблемы возникали не в компонентной архитектуре, а в организации работы.<\/p>\n<p>У каждого модуля был владелец. Обычно это был специалист, хорошо знающий конкретный строительный процесс и связанные с ним юридические, финансовые или операционные риски.<\/p>\n<p>В условиях срочного релиза владелец модуля мог напрямую обратиться к дизайнеру подрядчика и попросить добавить новый элемент.<\/p>\n<p>Типовой сценарий выглядел так:<\/p>\n<ol start=\"1\">\n<li>Владельцу модуля требовалось срочное изменение.<\/li>\n<li>Он передавал задачу внешнему дизайнеру.<\/li>\n<li>Дизайнер создавал новый локальный компонент.<\/li>\n<li>Решение попадало в макет.<\/li>\n<li>Разработчик не находил компонент в UI-kit.<\/li>\n<li>В Storybook и документации его также не было.<\/li>\n<\/ol>\n<p>Такие элементы существовали только внутри одного макета. Для них можно использовать термин orphan components.<\/p>\n<p>Причинами были:<\/p>\n<ul>\n<li>▸ сжатые сроки;<\/li>\n<li>▸ распределённые команды;<\/li>\n<li>▸ локальные договорённости;<\/li>\n<li>▸ скрытые экспертные знания;<\/li>\n<li>▸ отсутствие обязательного review;<\/li>\n<li>▸ давление со стороны владельцев модулей.<\/li>\n<\/ul>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/blog.denislykin.ru\/pictures\/DS-Slide-03.png.jpg\" width=\"2560\" height=\"2304\" alt=\"\" \/>\n<\/div>\n<h3>Governance<\/h3>\n<p>Для контроля новых решений был введён обязательный review на уровне внутренней дизайн-команды.<\/p>\n<p>Любой новый компонент или паттерн должен был пройти следующую проверку:<\/p>\n<h3><b>1. Можно ли использовать существующий компонент?<\/b><\/h3>\n<p>Если да, новая сущность не создавалась.<\/p>\n<h3><b>2. Можно ли расширить существующий компонент?<\/b><\/h3>\n<p>Если задача решалась новым variant или property, обновлялся текущий компонент.<\/p>\n<h3><b>3. Является ли сценарий повторяемым?<\/b><\/h3>\n<p>Если решение использовалось только в одном локальном сценарии, оно могло остаться product-specific pattern.<\/p>\n<h3><b>4. Нужна ли системная сущность?<\/b><\/h3>\n<p>Если решение было повторяемым и применимым в нескольких модулях, оно проходило design review.<\/p>\n<p>После этого выполнялись:<\/p>\n<ul>\n<li>▸ проектирование в Figma;<\/li>\n<li>▸ согласование с frontend;<\/li>\n<li>▸ реализация в Storybook;<\/li>\n<li>▸ подготовка документации;<\/li>\n<li>▸ обновление changelog;<\/li>\n<li>▸ распространение по командам.<\/li>\n<\/ul>\n<p>Governance уменьшил количество несогласованных элементов и зафиксировал единый процесс развития библиотеки.<\/p>\n<h3>Работа с BIM-viewer<\/h3>\n<p>Отдельной проблемой было взаимодействие собственной интерфейсной системы с controls, встроенными в BIM-viewer.<\/p>\n<p>Viewer мог предоставлять собственные инструменты:<\/p>\n<ul>\n<li>▸ навигацию;<\/li>\n<li>▸ работу с камерой;<\/li>\n<li>▸ выбор объектов;<\/li>\n<li>▸ управление слоями;<\/li>\n<li>▸ изоляцию элементов;<\/li>\n<li>▸ скрытие объектов;<\/li>\n<li>▸ измерения.<\/li>\n<\/ul>\n<p>Contrust также содержал controls поверх модели:<\/p>\n<ul>\n<li>▸ фильтрацию;<\/li>\n<li>▸ переключение режимов;<\/li>\n<li>▸ групповой выбор;<\/li>\n<li>▸ назначение статусов;<\/li>\n<li>▸ фиксацию нарушений;<\/li>\n<li>▸ переход к сущности;<\/li>\n<li>▸ работу с карточкой объекта.<\/li>\n<\/ul>\n<p>В результате на одном экране существовали две системы управления.<\/p>\n<p>Необходимо было определить:<\/p>\n<ul>\n<li>▸ какие действия остаются внутри viewer;<\/li>\n<li>▸ какие переносятся в интерфейс Contrust;<\/li>\n<li>▸ какие native controls скрываются;<\/li>\n<li>▸ какие действия перехватываются;<\/li>\n<li>▸ какой режим считается приоритетным;<\/li>\n<li>▸ как избежать конфликта selection states;<\/li>\n<li>▸ как пользователь выходит из модального режима модели.<\/li>\n<\/ul>\n<p>Часть решений пересматривалась несколько раз. Основная сложность заключалась не в визуальном оформлении кнопок, а в согласовании двух interaction models.<\/p>\n<h3>Автоматизация иконок<\/h3>\n<p>Для иконок использовался собственный icon font.<\/p>\n<p>Процесс был частично автоматизирован:<\/p>\n<ol start=\"1\">\n<li>Иконка создавалась в Figma.<\/li>\n<li>Экспортировалась через внутренний плагин.<\/li>\n<li>Добавлялась в шрифт.<\/li>\n<li>Обновление отправлялось в Git.<\/li>\n<li>Frontend-команда получала актуальную версию.<\/li>\n<\/ol>\n<p>После обновления ветки новая иконка становилась доступна в продукте без ручной передачи отдельных SVG-файлов.<\/p>\n<h3>Результат<\/h3>\n<p>В результате была создана полноценная дизайн-система, которая включала:<\/p>\n<ul>\n<li>▸ UI-kit;<\/li>\n<li>▸ foundations;<\/li>\n<li>▸ tokens;<\/li>\n<li>▸ component library;<\/li>\n<li>▸ product patterns;<\/li>\n<li>▸ BIM patterns;<\/li>\n<li>▸ Storybook;<\/li>\n<li>▸ документацию;<\/li>\n<li>▸ changelog;<\/li>\n<li>▸ contribution rules;<\/li>\n<li>▸ brandbook;<\/li>\n<li>▸ governance.<\/li>\n<\/ul>\n<p>Первые три модуля вышли в production с единым визуальным и интерфейсным языком.<\/p>\n<p>Они использовали:<\/p>\n<ul>\n<li>▸ общую точку входа;<\/li>\n<li>▸ единые компоненты;<\/li>\n<li>▸ общую типографику;<\/li>\n<li>▸ стандартные состояния;<\/li>\n<li>▸ одинаковые принципы работы с данными;<\/li>\n<li>▸ согласованную логику desktop и tablet.<\/li>\n<\/ul>\n<p>На этой основе начали разрабатываться следующие модули.<\/p>\n<p>В продуктовом контуре использовалось более 15 сервисов. Дизайн-система позволила перевести их развитие из набора локальных интерфейсных решений в управляемую модель.<\/p>\n<p>Основной качественный результат заключался в том, что новые задачи больше не начинались с пустого файла у дизайнера и новой реализации у frontend-разработчика.<\/p>\n<p>Команды получили общий набор решений, общий процесс согласования и общий источник правил.<\/p>\n<h2><b>Выводы<\/b><\/h2>\n<h3><b>Сначала стандартизировать частое, потом специфичное<\/b><\/h3>\n<p>Большая предметная область не означает, что ей нужна полностью уникальная библиотека компонентов. Большинство базовых задач Contrust закрывались стандартными UI-паттернами. Кастомизация имела наибольшую ценность там, где начиналась специфика продукта: BIM, строительные статусы, проверки и работа инженеров на площадке.<\/p>\n<h3><b>Рост библиотеки сам по себе ничего не доказывает<\/b><\/h3>\n<p>Новый компонент стоит добавлять только тогда, когда существующие компоненты, их свойства и композиции не закрывают задачу. Чем больше сценариев удаётся собрать из уже существующих элементов, тем устойчивее система и дешевле её поддержка.<\/p>\n<h3><b>Governance нужно проектировать вместе с компонентами<\/b><\/h3>\n<p>Даже хорошо организованный UI Kit быстро теряет консистентность, если продуктовые команды могут обходить его при срочных задачах. Changelog, design review, contribution flow и критерии появления новых компонентов оказались не менее важны, чем сами компоненты.<\/p>\n<h3><b>Миграция должна иметь практическую ценность<\/b><\/h3>\n<p>Не каждое изменение дизайн-системы требует обновления всего legacy. Активные макеты имеет смысл поддерживать в актуальном состоянии, а уже выпущенные интерфейсы достаточно проверять на совместимость и обновлять тогда, когда они снова попадают в работу.<\/p>\n<h3>Что не получилось<\/h3>\n<p>Основной ошибкой было решение полностью разрабатывать базовую UI-библиотеку самостоятельно.<\/p>\n<p>Команда вручную проектировала и кодировала:<\/p>\n<ul>\n<li>▸ buttons;<\/li>\n<li>▸ dropdowns;<\/li>\n<li>▸ tooltips;<\/li>\n<li>▸ inputs;<\/li>\n<li>▸ popovers;<\/li>\n<li>▸ базовые состояния;<\/li>\n<li>▸ стандартные controls.<\/li>\n<\/ul>\n<p>При этом большая часть этих компонентов не имела специфики строительного продукта.<\/p>\n<p>Изначальная логика выглядела так:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">Сложный BIM-продукт\n→ нестандартные сценарии\n→ полностью собственная дизайн-система<\/code><\/pre><p>На практике корректная модель была другой:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">Готовая UI-библиотека\n  - ▸ собственные tokens и theme\n  - ▸ строительные product patterns\n  - ▸ BIM interaction layer<\/code><\/pre><p>После того как внутри компании закрепилось представление о полностью собственной дизайн-системе, отказаться от этой модели стало сложно. Собственная разработка воспринималась как технологическое преимущество и стала частью внутренней коммуникации проекта.<\/p>\n<p>В результате часть ресурсов была потрачена на повторную реализацию стандартных компонентов.<\/p>\n<h3>Что я сделал бы иначе<\/h3>\n<p>Если бы аналогичная система создавалась сейчас, я бы разделил её на два уровня.<\/p>\n<h3><b>Стандартный уровень<\/b><\/h3>\n<p>Основа на базе зрелой библиотеки, например Ant Design:<\/p>\n<ul>\n<li>▸ buttons;<\/li>\n<li>▸ inputs;<\/li>\n<li>▸ dropdowns;<\/li>\n<li>▸ tooltips;<\/li>\n<li>▸ modals;<\/li>\n<li>▸ tables;<\/li>\n<li>▸ popovers;<\/li>\n<li>▸ standard states;<\/li>\n<li>▸ accessibility behavior.<\/li>\n<\/ul>\n<h3><b>Доменный уровень<\/b><\/h3>\n<p>Собственные решения для Contrust:<\/p>\n<ul>\n<li>▸ BIM selection mode;<\/li>\n<li>▸ model filtering;<\/li>\n<li>▸ construction statuses;<\/li>\n<li>▸ apartment matrix;<\/li>\n<li>▸ inspection flows;<\/li>\n<li>▸ object violations;<\/li>\n<li>▸ desktop-tablet adaptation;<\/li>\n<li>▸ interaction between viewer and product UI.<\/li>\n<\/ul>\n<p>Такой подход сократил бы стоимость разработки и поддержки базовых компонентов.<\/p>\n<p>Основные ресурсы можно было бы направить на сценарии, в которых продукт действительно отличался от стандартных enterprise-систем.<\/p>\n<p>Главный вывод проекта состоит в следующем:<\/p>\n<p>Сложность предметной области не требует полной уникальности базового интерфейса. В Contrust специфичными были строительные процессы, BIM-сценарии и работа инженеров на площадке. Кнопки, dropdown и tooltip могли оставаться стандартными.<\/p>\n<h3>Связаться со мной<\/h3>\n<p>Если есть желание обсудить этот кейс, другие кейсы или пообщаться по иным вопросам в сфере продуктового дизайна — пишите мне в <a href=\"https:\/\/t.me\/denyalykin\">телеграм<\/a> или на <a href=\"mailto:i@dlykin.ru\">почту<\/a>. Подробнее обо мне и моей работе можно узнать на <a href=\"https:\/\/denislykin.ru\">моём сайте<\/a>. Сейчас я в поисках продуктовой команды, в которой смогу развивать продукт и быть полезным игроком. Пишите, буду рад пообщаться!<\/p>\n",
            "summary": "⏳ Время чтения ±15 минут",
            "date_published": "2026-08-25T00:55:54+03:00",
            "date_modified": "2026-09-04T17:51:06+03:00",
            "tags": [
                "Дизайн",
                "Дизайн-система",
                "Продукт"
            ],
            "image": "https:\/\/blog.denislykin.ru\/pictures\/BIM.png.jpg",
            "_date_published_rfc2822": "Tue, 25 Aug 2026 00:55:54 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "5",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/blog.denislykin.ru\/pictures\/BIM.png.jpg",
                    "https:\/\/blog.denislykin.ru\/pictures\/DS-Slide-01.png.jpg",
                    "https:\/\/blog.denislykin.ru\/pictures\/DS-Slide-02.png.jpg",
                    "https:\/\/blog.denislykin.ru\/pictures\/DS-Slide-03.png.jpg"
                ]
            }
        },
        {
            "id": "4",
            "url": "https:\/\/blog.denislykin.ru\/all\/sobirayu-ekrany-vpn-ogranicheniy\/",
            "title": "Собираю экраны ВПН-ограничений",
            "content_html": "<p>Любой экран в дизайне — это набор усилий, мыслей и задач.<\/p>\n<p>ВПН-ограничения не исключение.<\/p>\n<p>Буду копить тут то, что удалось заметить.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/blog.denislykin.ru\/pictures\/vpn1.png\" width=\"1200\" height=\"700\" alt=\"\" \/>\n<\/div>\n<p>→ У «Самоката» милый котик;<br \/>\n→ «Озон» выдает почти нечеловеческую и очень тяжеловесную ошибку, которая делает вид, что проблема может быть не в ВПН, но при этом дают возможность показать штрихкод для получения товара;<br \/>\n→ «Госуслуги» заботятся о безопасности — так и говорят;<br \/>\n→ «Яндекс-Афиша» загружается, но в момент покупки выдает красный алёрт. (Смотрел сколько билетов на «Колобка» у нас в Питере продано).<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/blog.denislykin.ru\/pictures\/vpn2.png\" width=\"1200\" height=\"700\" alt=\"\" \/>\n<\/div>\n<p>→ «Яндекс GO» говорит «похоже», а потом даёт честный факт: есть ограничения, с ними не зайдём. И даёт пару вариантов, как починить.<br \/>\n→ «Магнит» говорит «кажется», хотя ему не кажется. Сразу даёт код карты лояльности сосканировать — хорошо.<br \/>\n→ Wildberries тоже говорит «возможно», но тоже даёт QR и помимо основных бесполезных кнопок предлагает ещё и перейти в банк. Карты у меня там нет, но полагаю, что ничем хорошим переход не закончится.<br \/>\n→ 2GIS тоже говорит «возможно» и тоже предлагает пару кнопок. Ну тут хотябы картинка щита красивая.<\/p>\n<p>✏ <i>Заметка будет дополняться<\/i><\/p>\n",
            "summary": "Любой экран в дизайне — это набор усилий, мыслей и задач",
            "date_published": "2026-08-08T16:31:04+03:00",
            "date_modified": "2026-08-15T23:57:43+03:00",
            "tags": [
                "VPN",
                "ВПН",
                "Дизайн",
                "Экраны"
            ],
            "image": "https:\/\/blog.denislykin.ru\/pictures\/IMG_4881.png.jpg",
            "_date_published_rfc2822": "Sat, 08 Aug 2026 16:31:04 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "4",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/blog.denislykin.ru\/pictures\/IMG_4881.png.jpg",
                    "https:\/\/blog.denislykin.ru\/pictures\/IMG_4882.png.jpg",
                    "https:\/\/blog.denislykin.ru\/pictures\/IMG_4885.png.jpg",
                    "https:\/\/blog.denislykin.ru\/pictures\/IMG_4886.png.jpg",
                    "https:\/\/blog.denislykin.ru\/pictures\/vpn1.png",
                    "https:\/\/blog.denislykin.ru\/pictures\/vpn2.png"
                ]
            }
        }
    ],
    "_e2_version": 4199,
    "_e2_ua_string": "Aegea 11.5 (v4199)"
}