Здравый смысл Дени | Блог

О дизайне, медиа, мире

Сайт-портфолио как отдельный продукт

→Статья переехала в новый блог

Время чтения ±20 минут

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

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

Она больше похожа на карту связей. Один проект ведёт к другому, продуктовые решения опираются на исследования, рядом существуют рабочие эксперименты, результаты, клиенты и контекст самого автора. Мне хотелось показать эту структуру буквально — через пространственный интерфейс, похожий на node-based-системы, Miro и FigJam.

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

Эта статья не столько про необычный вид портфолио, сколько про способ работы над ним. Про решения, которые пришлось принимать между Figma, браузером, структурой данных и редактором WordPress. И про то, почему современному дизайнеру полезно понимать фронтенд достаточно хорошо, чтобы проектировать не только состояния экрана, но и поведение всей системы.

Кому будет полезно?

  • ▸ Продуктовым дизайнерам, которые растут в сторону Lead или Principal Designer и хотят влиять не только на макеты, но и на устройство продукта.
  • ▸ Дизайнерам, которые начинают работать с AI coding agents и ищут более зрелый процесс, чем «написать промпт и принять результат».
  • ▸ Командам, которые проектируют нестандартные интерфейсы с большим количеством состояний, жестов и динамического контента.
  • ▸ Тем, кто использует WordPress как CMS, но не хочет ограничиваться устройством классической темы.

Контекст

У меня уже был работающий сайт на WordPress с кейсами, изображениями и базовыми текстами. Поэтому задача не состояла в том, чтобы сделать ещё одну статичную страницу и вручную перенести в неё контент.

К WordPress я пришёл не сразу, но возвращение к этой «волшебной палочке» стало каким-то грандиозным инсайтом. В нём всё есть: возможность быстро развернуть его и локально, и на хостинге, работа с БД, мобильная наладка процессов и бизнес-логики, headless-режим для своего фронта и прекрасная админка. В нынешних условиях писать для WP визуалы и плагины — одно удовольствие.

Админка WP может сразу показать какой-нибудь нужный самописный виджет

Я хотел сохранить WordPress как канонический источник данных, но полностью заменить способ их представления. Это важное ограничение: новая тема должна была не просто отрисовать существующие записи, а превратить CMS в редактор пространственной системы.

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

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

Визуально эта идея была достаточно ясной уже в Figma. Главная сложность началась дальше: как сделать так, чтобы метафора не рассыпалась при первом же реальном взаимодействии.

Постановка задачи

Я сформулировал несколько обязательных свойств системы.

Во-первых, Canvas должен ощущаться «бесконечным». Пользователь может двигать его рукой, зажатым пробелом или средней кнопкой мыши, менять масштаб колесом и жестами, как в знакомых графических инструментах.

Во-вторых, бесконечность не должна быть буквальной. Если разрешить двигаться без границ, человек легко потеряет и карточки, и точку возврата. Поэтому фактическая область перемещения рассчитывается по геометрии контента и получает дополнительный свободный пояс. Canvas остаётся просторным, но не превращается в пустую вселенную.

В-третьих, карточки должны быть живыми объектами. Их можно перемещать, они не накладываются друг на друга, новые сущности автоматически находят место, а связи перестраиваются вслед за ними.

В-четвёртых, пространственный интерфейс не должен становиться обязательным испытанием для каждого посетителя. В любой момент его можно переключить в Feed — обычную вертикальную ленту с тем же контентом.

Режим ленты был придуман и реализован с самого начала

И наконец, всё это должно управляться из WordPress. Новая карточка, опубликованная в админке, должна появиться на открытом сайте, попасть в нужное пространство, получить исходную позицию, пройти через коллизии и, если требуется, соединиться с родителем. Без правки JavaScript и без ручной сборки страницы.

На этом этапе я сознательно отложил pixel-perfect. Было бессмысленно идеально выравнивать иконку внутри панели, пока не определено, что происходит с камерой при открытии кейса или кто владеет координатами карточки. Сначала нужен был контракт ядра, потом — визуальная точность.

Один из первых концептов портфолио

Canvas — это не большой экран

Первая архитектурная ошибка, которую легко совершить в таком интерфейсе, — воспринимать Canvas как огромный DOM-контейнер, который просто двигается под маской viewport.

На деле это несколько независимых состояний:

  • ▸ активное пространство;
  • ▸ режим Canvas или Feed;
  • ▸ открытый документ;
  • ▸ координаты и масштаб камеры;
  • ▸ расположение карточек;
  • ▸ scroll-позиция ленты или открытого кейса;
  • ▸ выбранный инструмент ввода.

Если эти состояния смешать, любое переключение начинает иметь побочные эффекты. Открытие кейса сбрасывает зум, Feed теряет позицию, а возврат в предыдущее пространство складывает карточки в одну точку.

Поэтому основой стала постоянная оболочка приложения — app shell. Верхняя навигация, Canvas и системные контролы не пересоздаются при переходах. Меняется состояние внутри них: открывается документ, переключается проекция данных или подставляется другое пространство.

Это решение позволило сделать то, что было важно в исходной идее: кейс раскрывается внутри холста, как объект в Miro, а не ведёт на визуально чужую страницу. URL при этом меняется, работают Back и Forward, сохраняется камера, а прямой переход по ссылке всё равно открывает полноценный документ.

Именно здесь макет перестал быть набором кадров и стал моделью приложения.

Три слоя системы

Чтобы не превратить код в набор исключений, я разделил систему на три слоя.

1. Контент

WordPress хранит карточки, кейсы, пространства, типы, подписи, теги, действия, изображения и редакторские настройки. Это единственный источник истины для того, что опубликовано и как контент должен быть изначально организован.

2. Геометрия и состояние

Отдельное ядро рассчитывает координаты, размеры, коллизии, связи, границы движения и положение камеры. Оно не знает, какого цвета карточка и какой easing используется в анимации.

3. Представление и motion

Рендереры превращают одну модель данных в Canvas, Feed или открытый документ. GSAP анимирует переход из текущего состояния в уже рассчитанное конечное, но не принимает геометрические решения.

Это разделение оказалось одним из главных условий устойчивости. Если отдать GSAP владение координатами, визуально плавное движение быстро начинает расходиться с фактической раскладкой. Если хранить камеру в WordPress, редакторский контент смешивается с локальным состоянием посетителя. Если собрать Canvas и Feed из разных запросов, между ними появляются несовпадающие карточки.

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

Карточка как система свойств

Карточки в макете сильно отличались друг от друга. Persona содержит фотографию, город и время. Project — обложку, описание, теги и действие. Contacts состоит из ссылок. Clients — из логотипов. Results — почти полностью текстовый блок.

Можно было сделать один универсальный компонент с десятками условий или, наоборот, собрать каждый вид отдельно. Оба варианта плохо масштабируются: первый превращается в монолит, второй размножает одинаковые элементы и поведение.

Я остановился на общей оболочке и реестре типов. У карточек единые шапка, бордеры, правила фокуса, размещение, теги, действие и коннектор. Внутреннее тело рендерится в зависимости от типа.

Это позволило вынести в общую модель свойства, которые сначала казались локальными:

  • ▸ обложка может быть у карточки любого типа;
  • ▸ SVG-иконка типа всегда помещается в фиксированный монохромный контейнер;
  • ▸ обычные и якорные теги используют один формат;
  • ▸ действие может открыть кейс, перейти в пространство, открыть внешний URL, скачать файл, поставить лайк или быть отключённым;
  • ▸ ссылка независимо определяет, нужно ли открывать её в новой вкладке;
  • ▸ важная карточка получает настраиваемую плашку, иконку и акцентную кнопку, не меняя свою роль в раскладке.

Самые важные уточнения касались не внешнего вида, а независимости свойств.

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

То же произошло с коннекторами. Playground сначала оказался вариантом в одном списке с Dashed. Но Playground — это маршрут связи с другим пространством, а Dashed — её визуальный стиль. Пространственный коннектор может быть и сплошным, и пунктирным. После разделения на route и style модель стала описывать смысл, а не текущее количество макетов.

Такие исправления легко принять за мелкие баги админки. На самом деле это признаки неправильно выделенных осей системы. Если два свойства могут изменяться независимо, они не должны жить в одном enum.

Физика без игрового движка

Карточки остаются обычными HTML-элементами. Это важно для доступности, текста, ссылок, адаптивности и поисковых систем. Поверх них работает небольшое геометрическое ядро.

Для такого интерфейса можно было взять готовую whiteboard-библиотеку или отрисовать всё на HTML Canvas. Но сайт не является редактором схем: его основные объекты — статьи, изображения, ссылки и кнопки. Мне были важны нативный текст, семантика HTML, доступность и нормальная индексация.

Поэтому фронтенд собран на vanilla JavaScript, CSS и WordPress Script Modules, а собственное ядро решает только специфические задачи пространства. Цена этого выбора — необходимость самостоятельно описать жесты, коллизии и камеру. Преимущество — модель взаимодействия не приходится подгонять под ограничения чужого редактора.

При добавлении новой карточки система определяет её логическую сторону. Проекты по умолчанию уходят вправо, информационные блоки — влево. Затем карточка получает начальную позицию и проходит через collision pass: занятые объекты сдвигаются, пока между ними не появится необходимый зазор.

Публичный клиент получает версионированный REST payload и сверяет его ревизию, когда вкладка возвращается в фокус. Если редактор опубликовал новую карточку, интерфейс не пересобирает весь Canvas: он сопоставляет сущности по стабильным ID, сохраняет неизменившиеся DOM-узлы и пользовательские координаты, а новый объект добавляет в существующую раскладку.

Для поиска столкновений используется spatial hash. Вместо проверки каждой карточки с каждой ядро рассматривает только локальных соседей. Это стало особенно важно после появления нескольких пространств и карточек разного размера.

Закреплённые карточки не участвуют в вытеснении, но обычные пользовательские координаты сохраняются отдельно от pinned. Есть и другие независимые признаки: карточку можно исключить из Fit, оставить без стороны и родителя или скрыть в одной из проекций.

Коннекторы строятся по фактическим границам карточек. Точка крепления находится ровно на стыке шапки и тела, а кривая Безье меняется вслед за положением объектов. Во время анимации связь следует не за будущей координатой из модели, а за текущим положением DOM. Иначе карточка двигалась бы плавно, а линия мгновенно перескакивала бы в финальную точку.

Даже точечный фон пришлось включить в координатную систему мира. Сначала карточки двигались, а паттерн оставался приклеенным к экрану. Формально Canvas работал, но пропадал основной навигационный сигнал: пользователь не видел, что перемещает камеру относительно пространства. После привязки позиции и шага сетки к камере фон начал вести себя как в Miro и FigJam.

Управление — это матрица, а не один drag

На десктопе один и тот же жест может означать разные вещи. Левая кнопка двигает карточку в режиме Pointer. Средняя кнопка или зажатый пробел перемещают холст. В режиме Hand холст должен двигаться даже тогда, когда курсор находится над карточкой. Правая кнопка не должна случайно запускать drag.

На мобильном к этому добавляются тап, вертикальный scroll и pinch двумя пальцами. А внутри карточек остаются настоящие кнопки: «Открыть», «Скачать», «Перейти», «Лайк». Они должны нажиматься даже при выбранной руке.

Я зафиксировал эти правила как единую матрицу input modality, а не стал исправлять каждый конфликт отдельным обработчиком. Выбранный инструмент запоминается при переключении режимов, первым по умолчанию становится Hand, а Reset восстанавливает не только камеру, но и каноническую раскладку карточек.

Клавиатура получила собственную модель. Карточки используют roving focus: стрелки выбирают ближайший объект по направлению на Canvas и следующий по порядку в Feed, Enter запускает основное действие. Focus ring появляется только при клавиатурной навигации. Обычный клик мышью не оставляет на карточке случайную синюю обводку.

Это хороший пример разницы между состоянием из UI-kit и поведением продукта. В макете можно нарисовать active, hover и focus. Но только в браузере становится понятно, что именно переводит интерфейс в каждое состояние и как разные способы ввода конкурируют между собой.

Камера и контролируемая бесконечность

Масштаб Canvas можно менять напрямую колесом или pinch-жестом. Такое управление должно быть мгновенным: если добавить easing между пальцами пользователя и интерфейсом, Canvas начинает ощущаться вязким.

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

Для каждого пространства из админки можно задать начальный масштаб от 10 до 200 процентов и выбрать карточку, которая станет центром первого экрана. Это редакторское решение, а не попытка угадать универсальный Fit. Если проектов мало, Persona можно показать крупнее. Если карта выросла — отдалить камеру и дать больше контекста.

На самом Canvas появился отдельный индикатор масштаба с быстрыми значениями. В Feed его нет, потому что там отсутствует сама концепция камеры.

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

Canvas и Feed — две проекции одних данных

Feed не стал отдельной мобильной страницей или вторым шаблоном WordPress. Это другая проекция того же REST payload.

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

В остальных пространствах Feed проще: все карточки идут одной лентой без специальных боковых зон.

При переключении Canvas и Feed карточки не исчезают и не возникают заново как несвязанные элементы. Система измеряет их экранную геометрию и морфит одно представление в другое. Панель инструментов тоже меняет форму: в Feed остаётся только переключатель режима, потому что Pointer, Hand и Reset там не нужны.

Для первого мобильного визита Feed включается автоматически. Это не сохранённый выбор, а безопасный старт. Если пользователь явно вернулся на Canvas, его решение запоминается.

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

Несколько пространств

Portfolio — особое пространство. Здесь карточки образуют связанную композицию вокруг Persona.

Playground и любое новое пространство — свободный Canvas с обычными карточками. Проекты можно публиковать не только в Portfolio, но и в Playground. Переход доступен и из верхнего меню, и через карточку-портал внутри самой карты.

Переключение происходит без перезагрузки app shell. Навигационный island плавно меняет ширину, камера и выбранный режим хранятся отдельно для каждого пространства, а реестр разделов позволяет добавлять новые пункты без переписывания хлебных крошек.

Здесь обнаружился один из характерных асинхронных багов: при быстром переключении ответы нескольких запросов могли приходить в другом порядке, и карточки нового пространства складывались в одну стопку. Исправление было не в увеличении задержки анимации, а в правиле latest request wins и проверке актуальности данных перед применением раскладки.

Это снова не визуальная проблема, хотя проявлялась она визуально.

Кейс внутри интерфейса

Карточка проекта открывает кейс внутри текущего Canvas. Остальные карточки и связи плавно уходят в нулевую прозрачность, нижние контролы скрываются, а выбранный объект разворачивается в document layer.

Внешняя страница при этом не скроллится. Шапка кейса и оболочка приложения остаются на месте, прокручивается только контент внутри документа. Короткий кейс обтягивается по высоте, длинный занимает доступную область viewport.

У этого сценария есть две равноправные точки входа:

  1. переход из карточки без перезагрузки;
  2. прямой URL с внешнего сайта, поисковика или социальной сети.

Сначала они расходились. По прямой ссылке WordPress показывал самостоятельный шаблон кейса, визуально похожий на документ, но лишённый Canvas shell. Для пользователя это выглядело как сломанная версия сайта. Я привёл обе точки входа к одной оболочке: сервер отдаёт полноценную страницу, а клиент после загрузки восстанавливает пространство и открывает тот же case layer.

Обычные WordPress Pages — например Privacy Policy — используют этот же document layer, но остаются отдельным типом документа. Их тело продолжает редактироваться Gutenberg, а Canvas переиспользует только навигацию, внутренний scroll и lifecycle открытия.

Так получилось сохранить и цельность интерфейса, и нормальную работу URL, истории браузера, индексации и сценария без JavaScript.

Админка — вторая половина продукта

В начале проекта часть данных жила в старых настройках темы, часть — в полях записей, часть была захардкожена в рендерере. Persona и Contacts даже присутствовали на Canvas, но не имели очевидной точки редактирования.

Внешне всё работало, но система не была управляемой. Это важное различие: контент на экране ещё не означает, что перед нами CMS.

Я собрал все сущности в раздел Canvas Cards → All. Системные карточки, проекты и обычные записи получили реальные формы редактирования. Отдельно появились:

  • ▸ визуальная карта Canvas для координат и раскладки;
  • ▸ реестр типов карточек с заменяемыми SVG-иконками;
  • ▸ пространства;
  • ▸ настройки стартовой камеры;
  • ▸ единая схема тегов и действий;
  • ▸ формы Persona, Contacts и двух островов верхней навигации.
Прямо внутри админки можно расставить карточки на точно таком же холсте

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

Визуальная карта использует то же геометрическое ядро, что и публичный Canvas. Это принципиально: если административный preview рассчитывает коллизии иначе, сохранённая композиция никогда не совпадёт с сайтом.

Редактор кейса как модель композиции

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

Я рассматривал возможность полностью отдать кейсы Gutenberg. Для обычных юридических страниц это правильный выбор. Но кейсам требовались контролируемые композиционные блоки, одинаковый рендеринг внутри Canvas и на прямой PHP-странице, особые правила ширины, локализованные подписи и интеграция с lightbox.

Поэтому появился упорядоченный редактор блоков: Text, Callout, Image, Gallery, Visual media, Two columns, Metrics и Wide media. Пустой блок не попадает в результат. Текстовые поля поддерживают безопасный Markdown: жирное и курсивное начертания, ссылки, маркированные, нумерованные и вложенные списки.

Здесь модель тоже уточнялась через реальное использование. Первая версия позволяла поставить текст либо на всю ширину, либо в левую, либо в правую колонку. Формально все три позиции существовали, но нельзя было собрать простой сценарий:

  1. общий заголовок;
  2. текст слева;
  3. его продолжение справа;
  4. изображение на всю ширину ниже.

Проблема была не в интерфейсе селекта. Сама схема данных предполагала один текстовый поток. Я заменил её на отдельный layout текста и независимое содержимое правой колонки. На мобильном обе части последовательно складываются в одну колонку.

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

Изображения, видео и lightbox

Медиа прошли похожий путь. В ранней версии картинки попадали в заранее заданные рамки, обрезались через object-fit: cover, а слишком крупное изображение внутри кейса приходилось горизонтально скроллить.

Блок Video/embed содержал поля WEBM URL, MP4 URL, Embed URL и Poster URL. Для разработчика их назначение можно восстановить, но редактор не должен разгадывать внутреннее устройство HTML5 video.

Я заменил технический набор полей на понятный выбор контента:

  • ▸ изображение;
  • ▸ загруженное видео;
  • ▸ внешний плеер;
  • ▸ локализованная подпись;
  • ▸ ширина контентной колонки или всего кейса;
  • ▸ autoplay, muted и loop для видео.

Высота всегда определяется пропорциями материала. Никакой скрытой обрезки.

Для изображений внутри кейсов появился отдельный lightbox. Он умеет переключать элементы галереи, увеличивать от 100 до 400 процентов, возвращаться в Fit, масштабироваться колесом, двойным кликом и pinch-жестом, а также ограничивать pan границами изображения.

После первой реализации панель контролов находилась сверху и отнимала часть полезной высоты — низ увеличенной картинки становился недоступен. Панель переехала под изображение, а размеры стали рассчитываться по реальному viewport и safe area мобильного устройства.

Это небольшая деталь, но она отражает общий принцип проекта: интерфейс нельзя оценить только по спокойному состоянию на большом экране. Нужно пройти весь сценарий руками.

Типографика тоже является поведением

В кейсах много русского текста, длинных заголовков, чисел, сокращений и ссылок. Одного text-wrap: pretty недостаточно, чтобы убрать висячие предлоги или сохранить осмысленное соединение слов и чисел.

Поэтому текст проходит через Typograf, а CSS отвечает уже за баланс строк и перенос внутри конкретного контейнера. Ручное форматирование автора — жирное начертание, курсив, ссылки и списки — сохраняется из WordPress без попыток найти «важные цифры» регулярным выражением.

Так исчез ещё один ранний хардкод: Results сам выделял проценты и числа, из-за чего перед жирным фрагментом терялись пробелы. Теперь рендерер не интерпретирует смысл текста за автора. Он показывает то, что размечено в редакторе.

То же внимание потребовалось компонентной типографике: подписям рядом с иконками, тексту внутри badge, высоте шапок и вертикальному положению символов. Эти вещи я доводил уже после стабилизации ядра — когда исправление line-height больше не могло замаскировать ошибку структуры.

Motion объясняет изменение состояния

GSAP появился в проекте не ради эффектности. Его задача — сделать изменение системы читаемым.

Я использовал короткие анимации для:

  • ▸ вытеснения карточек при коллизиях;
  • ▸ появления новых объектов и skeleton-состояния загрузки;
  • ▸ открытия и закрытия кейса;
  • ▸ перехода Canvas ↔ Feed;
  • ▸ смены пространства;
  • ▸ программного Fit и Reset;
  • ▸ morphing панели инструментов и навигационных islands;
  • ▸ обратной связи для Share и Like.

Прямое управление остаётся без tween. Карточка следует за указателем, камера — за колесом или пальцами. Анимация подключается только там, где интерфейс сам меняет состояние и пользователю важно увидеть причинно-следственную связь.

Все вызовы GSAP находятся в отдельном motion-адаптере. Геометрия и REST-слой ничего о нём не знают. Активные tween можно прервать или перенаправить, а prefers-reduced-motion приводит систему сразу в конечное состояние.

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

Навигация как часть пространства

Верхняя панель состоит из двух interface islands. Слева — логотип, имя и динамические хлебные крошки. Справа — участники и Share.

Название активного пространства является переключателем. Portfolio и Playground не захардкожены: меню строится из реестра и допускает новые разделы. При открытии кейса цепочка продолжается до его названия.

На мобильном полный маршрут не помещается. Вместо уменьшения шрифта или выхода island за границу я оставил только две смысловые точки: начало «лого + Denis Lykin» и текущую сущность. Длинный конечный заголовок обрезается многоточием внутри доступной ширины.

Даже здесь понадобилось отделить анимацию от верстки. Во время morphing ширина панели на долю секунды становилась меньше содержимого, и «Denis Lykin» с «Knowledge Base» переносились на две строки. Минимальные размеры и запрет переноса стали частью layout-контракта, а не настройкой easing.

Настройки логотипа, аватаров, текста и ссылки Share находятся в WordPress. После копирования ссылка не просто меняет надпись: иконка и ширина кнопки переходят в состояние «Скопировано!» согласованно, без скачка текста.

Дизайн в паре с AI coding agent

Значительная часть проекта создавалась в постоянном диалоге с AI coding agent. Но я не воспринимал его как генератор, которому можно показать Figma и получить готовый сайт.

Для меня это был способ сократить расстояние между решением и проверяемым интерфейсом.

Единицей работы стал не экран, а контракт поведения. Я описывал не только желаемый результат, но и ограничения: кто владеет состоянием, что должно сохраниться после перехода, какие свойства независимы, где допустима анимация и что происходит при прямом URL или другом способе ввода.

Рабочий цикл выглядел так:

  1. Сформулировать продуктовый инвариант.
  2. Получить работающую реализацию, а не статичную картинку.
  3. Проверить её в браузере через DevTools и Playwright.
  4. Отличить локальный визуальный дефект от ошибки модели.
  5. Исправить общий контракт и добавить регрессионную проверку.

Например, просьба «не закреплять карточку после перемещения» превратилась в разделение редакторской координаты и физического lock. Замечание про Playground и Dashed — в две ортогональные оси коннектора. Невозможность одновременно заполнить левую и правую колонки — в новую схему контентного блока. Сломанный прямой URL — в единый lifecycle документа для серверного и клиентского входа.

AI хорошо ускоряет локальную реализацию. Но он не снимает с дизайнера ответственность за целостность. Если давать только визуальные команды, система быстро покрывается исключениями, потому что каждая следующая правка оптимизирует один кадр. Чем точнее дизайнер понимает браузер и модель данных, тем лучше он может перевести наблюдение в устойчивое правило.

Зачем дизайнеру разбираться во фронтенде

Речь не о том, что каждый дизайнер обязан самостоятельно писать production-код. Важнее уметь видеть технические последствия продуктового решения.

Понимание DOM помогло оставить карточки семантическими HTML-элементами, а не рисовать весь интерфейс на Canvas. Знание Pointer Events позволило разделить мышь, touch, pinch и клавиатуру. History API сделал бесшовный кейс совместимым с URL и навигацией браузера. Понимание compositing понадобилось, когда текст и изображения начинали пикселизоваться после масштабирования. Работа с REST и post meta помогла не смешать данные редактора с состоянием посетителя.

Без этого разговор легко сводится к формулировкам «сделай плавнее» или «здесь что-то прыгает». С технической грамотностью дизайнер может сказать: прямой жест должен обновляться синхронно, программная камера — через interruptible tween; background-position должен зависеть от world transform; старый async-response не имеет права перезаписывать активное пространство.

Это не попытка стать разработчиком вместо разработчика, а, скорее, способность проектировать на уровне причин.

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

Продакшен — часть дизайна

Новая тема живёт отдельно от старой и активируется без миграции или удаления контента. WordPress отдаёт первый серверный render, а REST поддерживает обновления внутри постоянной оболочки.

CI/CD доставляет код темы и плагинов, но не перезаписывает базу, uploads и редакторский контент. Это позволяет развивать интерфейс независимо от production-данных.

Кроме основной темы появились небольшие системные слои:

  • ▸ юридические страницы и управление Cookies;
  • ▸ настраиваемые Open Graph и X/Twitter previews для сайта и отдельных кейсов;
  • ▸ обезличенная агрегированная аналитика без идентификаторов посетителя и сессии;
  • ▸ локальные SVG-ассеты и GSAP без зависимости от CDN.

Playwright проверяет переходы между Canvas, Feed и кейсом, сохранение камеры, клавиатурную навигацию, мобильные размеры, административные контролы и прямые маршруты. Визуальные снимки обновляются отдельно, после ручной проверки diff. Lighthouse desktop baseline достиг 100 баллов по Accessibility, Best Practices и SEO.

Тесты не гарантируют хорошего дизайна. Но они защищают уже принятые дизайнерские решения от случайного разрушения следующей функцией.

Где система ломалась

Проект развивался итерациями, и самые полезные выводы появились не в Figma, а в местах, где поведение расходилось с ожиданием.

Внешне похожие свойства оказывались разными

Так произошло с маршрутом и стилем коннектора, координатой и pinned, видимостью во время Fit и доступностью карточки в пространстве. После каждого такого случая я проверял, не объединяет ли модель независимые признаки только потому, что сейчас у них по два варианта.

Красивое состояние не гарантировало правильный переход

Кейс мог хорошо выглядеть открытым, но зависать на загрузке. Карточки могли плавно двигаться, но резко появляться. Навигация могла быть выровнена, но на один кадр складывать текст в две строки во время morphing.

Поэтому motion стал отдельной областью проектирования со своими loading, interruption, error и reduced-motion состояниями.

Desktop скрывал проблемы модели

На большом экране можно было не заметить конкуренцию scroll-контейнеров, слишком широкую колонку кейса или недоступный низ lightbox. Мобильный сценарий заставил определить, что именно скроллится, где находится safe area и какой жест принадлежит браузеру, а какой Canvas.

Админка быстро показывала хардкод

Если видимый объект нельзя найти и изменить, значит интерфейс ещё не стал системой. Невозможность отредактировать Contacts или выбрать обложку из Media Library была не «нехваткой поля», а сигналом, что контентная модель не совпадает с публичным рендерером.

Результат

В итоге портфолио стало небольшой контентной платформой.

У неё есть пространственный Canvas с физикой, связями, несколькими пространствами и управляемой камерой. Есть Feed как альтернативный способ чтения тех же данных. Кейсы, Persona и обычные страницы открываются внутри постоянной оболочки и сохраняют нормальные URL. WordPress управляет не только текстом, но и типами карточек, композицией, медиа, действиями, тегами и стартовым состоянием Canvas.

Самое важное — система остаётся расширяемой. Новое пространство не требует новой навигации. Новый тип карточки получает общую оболочку. Новый визуальный стиль коннектора не меняет его маршрут. Новая запись не требует пересборки карты вручную.

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

Выводы

Сильная метафора должна определять поведение

Node-based-интерфейс имеет смысл не потому, что выглядит необычно. Он задаёт отношения между объектами, свободу исследования и модель навигации. Если точки не движутся вместе с миром, коннекторы не следуют за карточками, а переход открывает обычную страницу, метафора перестаёт работать.

Состояния важнее экранов

Canvas, Feed, кейс и пространство нельзя проектировать только как набор кадров. Важнее определить, что сохраняется между ними, какая зона скроллится и кто владеет камерой, URL и фокусом.

Админка — часть пользовательского опыта

CMS является продуктом для редактора. Если создание карточки требует знания внутреннего URL или поиска полей по разным экранам, публичный интерфейс нельзя считать законченным.

Независимые свойства нужно моделировать независимо

Большая часть сложных багов возникала там, где два понятия случайно объединялись в одно поле. Хорошая архитектура начинается с правильных различий.

Анимация должна объяснять, а не управлять

Motion показывает связь между состояниями, но не хранит истину о них. Прямое взаимодействие остаётся быстрым, программное — коротким и читаемым.

Мобильная версия меняет приоритеты

На телефоне я начинаю с Feed, сокращаю навигацию до начала и конечной точки, складываю колонки и сохраняю нативный scroll. Это не компромиссная копия десктопа, а другая точка входа в ту же систему.

AI ускоряет производство решений, но не заменяет дизайн-суждение

Чем быстрее появляется код, тем важнее способность заметить неверную модель. Основной навык смещается от производства макетов к формулированию инвариантов, проверке поведения и удержанию целостности продукта.

Что бы я сделал иначе

Ретроспективно я бы ещё до первой визуальной реализации описал state machine оболочки: пространство, режим, документ, камера, scroll и input modality. Часть ранних ошибок была следствием того, что эти состояния проявлялись постепенно.

Я бы раньше собрал единую схему карточки и редакторский inventory. Это сократило бы период, когда Contacts, Persona, теги и действия жили в разных местах.

Я бы с самого начала проверял touch на нескольких физических устройствах, а не только через эмуляцию. Браузерный viewport помогает с адаптивностью, но не воспроизводит все конфликты scroll, tap и pinch.

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

При этом я бы сохранил общий порядок работы: сначала архитектурный контракт, затем функциональный browser QA и только потом pixel-perfect. В сложном интерфейсе визуальная полировка становится устойчивой лишь тогда, когда устойчиво его ядро.

Вместо заключения

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

Я всё меньше верю в модель, где дизайнер заканчивает работу после передачи макета. Для систем с живыми данными, множеством состояний и прямым взаимодействием дизайн продолжается в браузере, в структуре CMS, в контрактах API, в поведении при ошибке и в том, что происходит между двумя красивыми кадрами.

Principal Designer не обязан лично реализовывать каждый слой. Но он должен видеть систему целиком: понимать, где визуальное решение становится моделью данных, где анимация конфликтует с состоянием, где редакторский сценарий важнее ещё одного компонента и где локальная правка должна превратиться в общее правило.

В этом смысле портфолио выполнило свою главную задачу. Оно не только рассказывает, как я проектирую продукты, но ещё и само работает тем же способом.

Связаться со мной

Если есть желание обсудить этот кейс, другие кейсы или пообщаться по иным вопросам в сфере продуктового дизайна — пишите мне в телеграм или на почту. Подробнее обо мне и моей работе можно узнать на моём сайте. Сейчас я в поисках продуктовой команды, в которой смогу развивать продукт и быть полезным игроком. Пишите, буду рад пообщаться!

Contrust Design System: как объединить 15+ строительных модулей в одну продуктовую систему

→Статья переехала в новый блог

Время чтения ±15 минут

Сейчас я старший продуктовый дизайнер с восьмилетним опытом, но для того, чтобы понять, как функционирую сложные системы и процессы, мне понадобилось пройти через множество задач и преобразований. Одной из таких задач стала собственная, «ванильная» дизайн-система, которая была разработана не для работы тысяч клиентов, а для удобной навигации строителей, инженеров и внутренних сотрудников строительных компаний. Как часто это бывает — идея была здравая. Её реализация тоже не подкачала, но сейчас, глядя на весь пройденный путь, я чётко вижу моменты, которые я бы отработал иначе.

Кому будет полезно?

В первую очередь дизайнерам, работающим с нестандартными интерфейсами. Нестандартный интерфейс в данном случае тот, который содержит неочевдиные механизмы — 3D пространство, объёмные массивы данных, связи «многие-ко-многим». Ещё дизайнерам, лидам и овнерам, которые размышляют над тем, нужна ли им собственная дизайн-система вообще. Ну и наконец тем, кто уже работает с дизайн-системой и размышляет над её масштабированием.

Контекст

В 2020 году один из крупнейших застройщиков Санкт-Петербурга имел свой научно-технический центр, отвечающий за цифровизацию процесса строительства. На тот момент уже широко был развит BIM — система 3D моделирования строительных объектов, хранившая внутри моделей плотный слой информации о будущем здании. Компании требовался веб-дизайнер, так как весь сервис проектировался в вебе. Эту позицию я и занял, начав постепенно разбираться в сути. Сервис на тот момент не существовал как что-то целое и единое: это был набор различных инструментов, нацеленных в первую очередь на пачку прозрачных отчётов.

Первым крупным цифровым инструментом был модуль безопасности. Инженеры обходили строительные площадки, фиксировали нарушения на бумаге, а затем переносили информацию в BIM-модель на рабочем компьютере. Позже процесс частично перевели на планшеты, что позволило отмечать нарушения непосредственно на объекте.

В течение следующих месяцев к модулю безопасности добавились документооборот, пожарная безопасность, передача квартир покупателям, исполнительная документация, технический надзор и другие сервисы. Всего в продуктовом контуре появилось более 15 модулей.

Команда на старте состояла из пяти разработчиков, двух frontend- и трёх backend-инженеров, а также группы специалистов строительного профиля. Проектирование интерфейсов в основном выполняли сами инженеры. Их основной задачей было перенести сложные строительные процессы в цифровую среду. Системное проектирование пользовательского интерфейса в этот процесс не входило.

Исходное состояние интерфейсов

Каждый модуль развивался как отдельный инструмент. Новая функция обычно проектировалась под конкретную задачу и сразу передавалась в разработку.

UI-kit отсутствовал. Разработчики использовали базовые элементы frontend-фреймворков, дополняя их локальными компонентами. Большая часть решений создавалась заново.

В интерфейсах накопились типовые проблемы:

  • ▸ одинаковые действия выглядели и работали по-разному;
  • ▸ использовались несвязанные между собой иконки;
  • ▸ не было единой типографической шкалы;
  • ▸ интервалы и размеры элементов задавались локально;
  • ▸ цветовые решения не учитывали требования контрастности;
  • ▸ отсутствовали единые правила для таблиц, форм, модальных окон и состояний;
  • ▸ иерархия данных зависела от конкретного модуля;
  • ▸ компоненты разрабатывались под один сценарий и редко переиспользовались.

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

Для пользователей это увеличивало время освоения новых модулей. Для разработки это означало поддержку большого количества одноразовых решений.

Каждая новая задача порождала новый интерфейс, новую frontend-реализацию и новый слой legacy.

Постановка задачи

Изначально задача не формулировалась как создание дизайн-системы.

Необходимо было объединить разрозненные сервисы научно-технического центра в единый продукт. Для этого требовалось одновременно систематизировать несколько уровней:

  • ▸ бренд;
  • ▸ ролевую модель;
  • ▸ интерфейсную систему;
  • ▸ принципы интернационализации;
  • ▸ единую точку входа;
  • ▸ правила масштабирования продукта.

Дизайн-система стала инфраструктурной частью этой работы.

Её основные цели:

  1. Снизить количество локальных интерфейсных решений.
  2. Ускорить проектирование новых модулей.
  3. Сократить объём повторной frontend-разработки.
  4. Зафиксировать единые правила для desktop и tablet.
  5. Создать общий язык между внутренней командой, подрядчиками и разработкой.
  6. Обеспечить постепенную миграцию существующих продуктов без полной переработки legacy.

Build or adopt

На первом этапе мы рассматривали два варианта:

  • ▸ использовать готовую UI-библиотеку;
  • ▸ создать собственную дизайн-систему.

Основным аргументом в пользу собственной системы была BIM-модель.

Стандартное отображение BIM-модели

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

Мы предположили, что готовая библиотека не сможет покрыть эти сценарии без существенных ограничений.

Дополнительными факторами были:

  • ▸ одновременная поддержка desktop и tablet;
  • ▸ работа инженеров непосредственно на строительных площадках;
  • ▸ высокая плотность данных;
  • ▸ необходимость крупных интерактивных зон для планшета;
  • ▸ нестандартные режимы взаимодействия с BIM-viewer.

На основе этих вводных было принято решение создать собственную систему.

При проектировании foundations я анализировал Ant Design, Gravity и Fluent. Из этих систем были взяты устойчивые принципы построения компонентов, состояний, типографики и иерархии. Визуальные и технические параметры адаптировались под Contrust.

Ретроспективно это решение оказалось частично ошибочным. Специфические сценарии действительно требовали собственной систематизации, но большинство базовых компонентов можно было построить поверх существующей библиотеки.

Архитектура дизайн-системы

Система состояла из нескольких уровней:

  1. Foundations.
  2. Tokens.
  3. Primitive components.
  4. Composite components.
  5. Product patterns.
  6. Domain patterns.
  7. Product modules.

Такое разделение позволяло отделить общие правила интерфейса от специфики строительных сценариев.

Foundations

В foundations вошли:

  • ▸ цветовая система;
  • ▸ типографика;
  • ▸ spacing;
  • ▸ radius;
  • ▸ borders;
  • ▸ surface;
  • ▸ elevation;
  • ▸ grid;
  • ▸ iconography;
  • ▸ размеры интерактивных элементов;
  • ▸ правила для desktop и tablet.

Для типографики использовался PT Root UI. Гарнитура хорошо работала с кириллицей, выдерживала высокую плотность интерфейса и могла свободно использоваться в продукте.

Типографическая шкала строилась с коэффициентом 1,25. Для малых размеров применялся увеличенный интерлиньяж в 140%. По мере роста размера текста относительный интерлиньяж уменьшался пропорционально.

Цветовая модель разделялась на три уровня:

  • ▸ primitive colors;
  • ▸ semantic colors;
  • ▸ component tokens.

Например:

Blue / 600
→ Action / Primary
→ Button / Background / Primary

Это позволяло менять визуальные параметры без ручной переработки каждого компонента.

Tokens

Для токенов использовался собственный префикс. Через них описывались:

  • ▸ цвета;
  • ▸ размеры;
  • ▸ отступы;
  • ▸ радиусы;
  • ▸ границы;
  • ▸ типографика;
  • ▸ свойства surface;
  • ▸ отдельные component-level значения.

Токены использовались как общий источник правил для Figma и фронта.

На раннем этапе часть значений существовала как вручную синхронизируемая система. После выхода Variables в Figma мы перевели активную часть UI-kit на переменные.

Миграция заняла около двух недель периодической работы нескольких дизайнеров.

Мы разделили макеты на три категории:

Состояние Решение
Активная работа Перевести на Variables
В разработке Перевести на Variables
Уже выпущенный legacy Сверить с токенами, не перестраивать

Полную миграцию старых макетов не проводили. Стоимость такой работы не соответствовала её практической ценности.

Компонентная архитектура

Компоненты строились атомарно с использованием nested components и component properties в Figma.

Основной принцип был следующим:

Если вложенный элемент мог независимо менять состояние, он оформлялся как отдельный компонент.

Например, поле ввода могло включать:

  • ▸ label;
  • ▸ input area;
  • ▸ placeholder;
  • ▸ entered value;
  • ▸ leading icon;
  • ▸ trailing action;
  • ▸ helper text;
  • ▸ validation message;
  • ▸ error state.

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

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

Пример структуры Button

Кнопки были представлены примерно в следующих параметрах:

  • ▸ 5 размеров;
  • ▸ несколько визуальных типов;
  • ▸ 4 варианта сочетания текста и иконки;
  • ▸ состояния default, hover, active, disabled, loading;
  • ▸ tablet- и desktop-размерности;
  • ▸ варианты для полноширинного и компактного размещения.

Кроме кнопок в систему вошли:

  • ▸ inputs;
  • ▸ dropdowns;
  • ▸ selects;
  • ▸ tooltips;
  • ▸ avatars;
  • ▸ badges;
  • ▸ lists;
  • ▸ tables;
  • ▸ grids;
  • ▸ popovers;
  • ▸ modals;
  • ▸ accordions;
  • ▸ drawers;
  • ▸ rails;
  • ▸ quiz controls;
  • ▸ status components;
  • ▸ navigation controls.

При этом количество компонентов не рассматривалось как самостоятельная метрика зрелости системы.

Основным критерием была способность закрывать новые задачи существующими сущностями.

Компоненты и продуктовые паттерны

После запуска базовой библиотеки стало понятно, что консистентности на уровне компонентов недостаточно.

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

В него вошли:

  • ▸ сворачивание drawer в navigation rail;
  • ▸ перевод BIM-модели в fullscreen;
  • ▸ режимы модальных окон;
  • ▸ фильтрация;
  • ▸ аккордеоны;
  • ▸ popover-сценарии;
  • ▸ таблицы с bulk actions;
  • ▸ режим выбора элементов модели;
  • ▸ групповые действия;
  • ▸ паттерны статусов;
  • ▸ сценарии работы с планшетом.

BIM Multi Select

Одним из основных доменных паттернов стал выбор нескольких объектов внутри BIM-модели.

Сценарий состоял из четырёх шагов:

  1. Пользователь открывает модель в обычном режиме навигации.
  2. Включает режим выбора.
  3. Выбирает несколько элементов модели.
  4. Применяет действие ко всей группе.

После включения режима кнопка оставалась в активном состоянии. Пользователь мог выбрать 3, 6 или 10 объектов, после чего выполнить групповое действие.

К выбранным элементам могли применяться:

  • ▸ изменение статуса;
  • ▸ создание нарушения;
  • ▸ назначение ответственного;
  • ▸ добавление в проверку;
  • ▸ фильтрация;
  • ▸ включение в отчёт.

Для этого паттерна требовалось синхронизировать:

  • ▸ состояние toolbar;
  • ▸ состояние модели;
  • ▸ визуальное выделение объектов;
  • ▸ счётчик выбранных элементов;
  • ▸ доступные действия;
  • ▸ выход из режима;
  • ▸ отмену выбора;
  • ▸ конфликт с native controls viewer.

Этот сценарий был специфичен для продукта и действительно требовал собственной системы правил.

Контроль роста библиотеки

По мере развития продукта владельцы модулей регулярно предлагали новые элементы.

Одним из примеров была шахматка квартир. Изначально задача выглядела как запрос на новый сложный компонент.

После декомпозиции выяснилось, что интерфейс состоит из трёх уже существующих сущностей:

  • ▸ grid;
  • ▸ badge;
  • ▸ status token.

Вместо разработки отдельного компонента мы собрали матрицу квартир из существующих badge-компонентов с заданной сеткой и статусами.

Новый системный компонент не потребовался.

Для принятия подобных решений использовались следующие критерии:

  1. Можно ли решить задачу существующим компонентом.
  2. Можно ли расширить существующий компонент новым variant.
  3. Повторяется ли сценарий в других модулях.
  4. Является ли решение локальным или системным.
  5. Требует ли оно отдельной документации.
  6. Будет ли компонент поддерживаться в Storybook.
  7. Насколько высока стоимость его дальнейшего развития.

Figma, Storybook и документация

UI-kit существовал в Figma и параллельно переносился в Storybook.

В Storybook фиксировались:

  • ▸ компонент;
  • ▸ его состояния;
  • ▸ варианты;
  • ▸ технические параметры;
  • ▸ правила использования;
  • ▸ ограничения;
  • ▸ примеры.

Документация готовилась совместно с frontend-разработкой и техническим писателем.

Таким образом система включала:

  • ▸ Figma UI-kit;
  • ▸ tokens;
  • ▸ Storybook;
  • ▸ текстовую документацию;
  • ▸ brandbook;
  • ▸ правила contribution;
  • ▸ changelog;
  • ▸ governance.

Figma не рассматривалась как единственный источник дизайн-системы. Она была рабочим инструментом дизайнеров, но кодовая реализация и документация имели равный статус.

Работа с распределёнными командами

К моменту запуска UI-kit к проекту подключились внешние команды дизайна и frontend-разработки.

Из-за ограничений Figma, стоимости лицензий и последующих санкционных ограничений команды работали в разных окружениях.

Централизованное подключение всех дизайнеров к одной библиотеке было невозможно.

Для синхронизации использовался следующий процесс:

  • ▸ головной UI-kit оставался у внутренней команды;
  • ▸ первая страница файла была отведена под changelog;
  • ▸ изменения сопровождались ссылками на конкретные компоненты;
  • ▸ два раза в неделю внешние дизайнеры проверяли обновления;
  • ▸ после синхронизации они оставляли отметку о просмотре;
  • ▸ спорные изменения обсуждались отдельно;
  • ▸ предложения по новым компонентам проходили через внутреннюю дизайн-команду.

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

Где система ломалась

Основные проблемы возникали не в компонентной архитектуре, а в организации работы.

У каждого модуля был владелец. Обычно это был специалист, хорошо знающий конкретный строительный процесс и связанные с ним юридические, финансовые или операционные риски.

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

Типовой сценарий выглядел так:

  1. Владельцу модуля требовалось срочное изменение.
  2. Он передавал задачу внешнему дизайнеру.
  3. Дизайнер создавал новый локальный компонент.
  4. Решение попадало в макет.
  5. Разработчик не находил компонент в UI-kit.
  6. В Storybook и документации его также не было.

Такие элементы существовали только внутри одного макета. Для них можно использовать термин orphan components.

Причинами были:

  • ▸ сжатые сроки;
  • ▸ распределённые команды;
  • ▸ локальные договорённости;
  • ▸ скрытые экспертные знания;
  • ▸ отсутствие обязательного review;
  • ▸ давление со стороны владельцев модулей.

Governance

Для контроля новых решений был введён обязательный review на уровне внутренней дизайн-команды.

Любой новый компонент или паттерн должен был пройти следующую проверку:

1. Можно ли использовать существующий компонент?

Если да, новая сущность не создавалась.

2. Можно ли расширить существующий компонент?

Если задача решалась новым variant или property, обновлялся текущий компонент.

3. Является ли сценарий повторяемым?

Если решение использовалось только в одном локальном сценарии, оно могло остаться product-specific pattern.

4. Нужна ли системная сущность?

Если решение было повторяемым и применимым в нескольких модулях, оно проходило design review.

После этого выполнялись:

  • ▸ проектирование в Figma;
  • ▸ согласование с frontend;
  • ▸ реализация в Storybook;
  • ▸ подготовка документации;
  • ▸ обновление changelog;
  • ▸ распространение по командам.

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

Работа с BIM-viewer

Отдельной проблемой было взаимодействие собственной интерфейсной системы с controls, встроенными в BIM-viewer.

Viewer мог предоставлять собственные инструменты:

  • ▸ навигацию;
  • ▸ работу с камерой;
  • ▸ выбор объектов;
  • ▸ управление слоями;
  • ▸ изоляцию элементов;
  • ▸ скрытие объектов;
  • ▸ измерения.

Contrust также содержал controls поверх модели:

  • ▸ фильтрацию;
  • ▸ переключение режимов;
  • ▸ групповой выбор;
  • ▸ назначение статусов;
  • ▸ фиксацию нарушений;
  • ▸ переход к сущности;
  • ▸ работу с карточкой объекта.

В результате на одном экране существовали две системы управления.

Необходимо было определить:

  • ▸ какие действия остаются внутри viewer;
  • ▸ какие переносятся в интерфейс Contrust;
  • ▸ какие native controls скрываются;
  • ▸ какие действия перехватываются;
  • ▸ какой режим считается приоритетным;
  • ▸ как избежать конфликта selection states;
  • ▸ как пользователь выходит из модального режима модели.

Часть решений пересматривалась несколько раз. Основная сложность заключалась не в визуальном оформлении кнопок, а в согласовании двух interaction models.

Автоматизация иконок

Для иконок использовался собственный icon font.

Процесс был частично автоматизирован:

  1. Иконка создавалась в Figma.
  2. Экспортировалась через внутренний плагин.
  3. Добавлялась в шрифт.
  4. Обновление отправлялось в Git.
  5. Frontend-команда получала актуальную версию.

После обновления ветки новая иконка становилась доступна в продукте без ручной передачи отдельных SVG-файлов.

Результат

В результате была создана полноценная дизайн-система, которая включала:

  • ▸ UI-kit;
  • ▸ foundations;
  • ▸ tokens;
  • ▸ component library;
  • ▸ product patterns;
  • ▸ BIM patterns;
  • ▸ Storybook;
  • ▸ документацию;
  • ▸ changelog;
  • ▸ contribution rules;
  • ▸ brandbook;
  • ▸ governance.

Первые три модуля вышли в production с единым визуальным и интерфейсным языком.

Они использовали:

  • ▸ общую точку входа;
  • ▸ единые компоненты;
  • ▸ общую типографику;
  • ▸ стандартные состояния;
  • ▸ одинаковые принципы работы с данными;
  • ▸ согласованную логику desktop и tablet.

На этой основе начали разрабатываться следующие модули.

В продуктовом контуре использовалось более 15 сервисов. Дизайн-система позволила перевести их развитие из набора локальных интерфейсных решений в управляемую модель.

Основной качественный результат заключался в том, что новые задачи больше не начинались с пустого файла у дизайнера и новой реализации у frontend-разработчика.

Команды получили общий набор решений, общий процесс согласования и общий источник правил.

Выводы

Сначала стандартизировать частое, потом специфичное

Большая предметная область не означает, что ей нужна полностью уникальная библиотека компонентов. Большинство базовых задач Contrust закрывались стандартными UI-паттернами. Кастомизация имела наибольшую ценность там, где начиналась специфика продукта: BIM, строительные статусы, проверки и работа инженеров на площадке.

Рост библиотеки сам по себе ничего не доказывает

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

Governance нужно проектировать вместе с компонентами

Даже хорошо организованный UI Kit быстро теряет консистентность, если продуктовые команды могут обходить его при срочных задачах. Changelog, design review, contribution flow и критерии появления новых компонентов оказались не менее важны, чем сами компоненты.

Миграция должна иметь практическую ценность

Не каждое изменение дизайн-системы требует обновления всего legacy. Активные макеты имеет смысл поддерживать в актуальном состоянии, а уже выпущенные интерфейсы достаточно проверять на совместимость и обновлять тогда, когда они снова попадают в работу.

Что не получилось

Основной ошибкой было решение полностью разрабатывать базовую UI-библиотеку самостоятельно.

Команда вручную проектировала и кодировала:

  • ▸ buttons;
  • ▸ dropdowns;
  • ▸ tooltips;
  • ▸ inputs;
  • ▸ popovers;
  • ▸ базовые состояния;
  • ▸ стандартные controls.

При этом большая часть этих компонентов не имела специфики строительного продукта.

Изначальная логика выглядела так:

Сложный BIM-продукт
→ нестандартные сценарии
→ полностью собственная дизайн-система

На практике корректная модель была другой:

Готовая UI-библиотека
  - ▸ собственные tokens и theme
  - ▸ строительные product patterns
  - ▸ BIM interaction layer

После того как внутри компании закрепилось представление о полностью собственной дизайн-системе, отказаться от этой модели стало сложно. Собственная разработка воспринималась как технологическое преимущество и стала частью внутренней коммуникации проекта.

В результате часть ресурсов была потрачена на повторную реализацию стандартных компонентов.

Что я сделал бы иначе

Если бы аналогичная система создавалась сейчас, я бы разделил её на два уровня.

Стандартный уровень

Основа на базе зрелой библиотеки, например Ant Design:

  • ▸ buttons;
  • ▸ inputs;
  • ▸ dropdowns;
  • ▸ tooltips;
  • ▸ modals;
  • ▸ tables;
  • ▸ popovers;
  • ▸ standard states;
  • ▸ accessibility behavior.

Доменный уровень

Собственные решения для Contrust:

  • ▸ BIM selection mode;
  • ▸ model filtering;
  • ▸ construction statuses;
  • ▸ apartment matrix;
  • ▸ inspection flows;
  • ▸ object violations;
  • ▸ desktop-tablet adaptation;
  • ▸ interaction between viewer and product UI.

Такой подход сократил бы стоимость разработки и поддержки базовых компонентов.

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

Главный вывод проекта состоит в следующем:

Сложность предметной области не требует полной уникальности базового интерфейса. В Contrust специфичными были строительные процессы, BIM-сценарии и работа инженеров на площадке. Кнопки, dropdown и tooltip могли оставаться стандартными.

Связаться со мной

Если есть желание обсудить этот кейс, другие кейсы или пообщаться по иным вопросам в сфере продуктового дизайна — пишите мне в телеграм или на почту. Подробнее обо мне и моей работе можно узнать на моём сайте. Сейчас я в поисках продуктовой команды, в которой смогу развивать продукт и быть полезным игроком. Пишите, буду рад пообщаться!

Собираю экраны ВПН-ограничений

Любой экран в дизайне — это набор усилий, мыслей и задач.

ВПН-ограничения не исключение.

Буду копить тут то, что удалось заметить.

→ У «Самоката» милый котик;
→ «Озон» выдает почти нечеловеческую и очень тяжеловесную ошибку, которая делает вид, что проблема может быть не в ВПН, но при этом дают возможность показать штрихкод для получения товара;
→ «Госуслуги» заботятся о безопасности — так и говорят;
→ «Яндекс-Афиша» загружается, но в момент покупки выдает красный алёрт. (Смотрел сколько билетов на «Колобка» у нас в Питере продано).

→ «Яндекс GO» говорит «похоже», а потом даёт честный факт: есть ограничения, с ними не зайдём. И даёт пару вариантов, как починить.
→ «Магнит» говорит «кажется», хотя ему не кажется. Сразу даёт код карты лояльности сосканировать — хорошо.
→ Wildberries тоже говорит «возможно», но тоже даёт QR и помимо основных бесполезных кнопок предлагает ещё и перейти в банк. Карты у меня там нет, но полагаю, что ничем хорошим переход не закончится.
→ 2GIS тоже говорит «возможно» и тоже предлагает пару кнопок. Ну тут хотябы картинка щита красивая.

Заметка будет дополняться

«Да, и» — главный принцип импровизации, который помог мне воспринимать мир спокойнее

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

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

Одно из самых главных правил звучит как «да, и». Смысл простой: нам необходимо всегда соглашаться с тем, что предоставляет нам на сцене наш партнёр, сокомандник, тот, кто играет вместе с нами.

Вот пример:

Мы выходим вдвоём на сцену, и мой партнёр говорит мне:

— Представляешь, у меня опять сгорели волосы на спине. Ночью снова прилетали феи с огнемётами и сожгли мне всю волосню.

Нормальная реакция обычного человека будет примерно такой:

— Так, ты вообще вменяемый? Таблетки сегодня принимал? У тебя всё нормально? Может, в дурку позвонить?

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

Отсюда берутся все эти присказки про таблетки, больницы, сумасшествие и прочее.

Но в импровизации я отвечу по-другому.

— Представляешь, у меня опять сгорели все волосы на спине. Ночью снова прилетали феи с огнемётами и сожгли мне всю волосню.

Я отвечу:

— Да, у тебя тоже?! И я страдаю от того же! Я вообще волос на спине уже несколько месяцев не видел. Эти феи совсем задолбали. У меня ещё проблема с гномиками, которые грызут мне ногти на ногах. Падлы, до крови разгрызают — ходить не могу.

В этом и заключается ключевой принцип. Благодаря ему строятся целые истории. Двумя фразами мы с сокомандником создали мир, внутри которого странные феи с огнемётами и гномики, грызущие ногти на ногах, — это норма. Это ключевая особенность этого мира. Мы все в нём живём.

Следующий, кто зайдёт в сцену, уже не побоится сказать, что существуют единороги со страпонами вместо рогов или что-то ещё похуже. Потому что мы уже обозначили границы нормальности.

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

Принцип «да, и…» стал сильно помогать мне в последнее время. Я начал проще относиться к сюжетам сериалов, книг и игр. Мне стало проще принимать идеи людей, потому что стало ещё легче ставить себя на их место.

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

Мне становится легче понимать сюжетные повороты сериалов. Легче понимать, что автор книги хочет до меня донести. Самое главное — я перестал предвзято относиться ко всему. Ушла какая-то тупая зажатость, невосприимчивость.

У меня перестали проскакивать мысли из разряда:

«Господи, что же это за бред они несут? Как овцы в фильме могут разгадывать убийство собственного пастуха?»

Вместо этого я задаю себе вопрос:
«Если в этом мире овцы, общающиеся между собой и разгадывающие преступления, — это обыденная реальность, то что ещё реально в этом сумасшедшем мире? В этом классном сумасшедшем мире».

Или, например, сериал «Извне», который поначалу показался мне уж больно нестандартным и похожим на собирательный образ нагромождённого бреда. Но потом, каждый раз задавая себе вопрос:

«А что ещё в этом мире может быть реальным?»,

я начал получать искреннее удовольствие от шоу и его нестандартности.

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

Принимать правила другого мира стало легче и, например, в изучении языка. Я ненавижу английский из-за того, что не могу заключить его в понятную для себя систему — система разваливается сразу же, просто потому что английский любит вносить коррективы и исключения из правил. Я на стену лез первое время, потому что не мог просто жёстко проставить причинно-следственные связи, постоянно орал

«Да какого хера тооооо?!».

Но потом я просто начал говорить «Да, и…», и правила стали учиться легче, потому что к чёрту логику — просто принимай.

Казалось бы, это всего лишь три буквы: «да, и». Но сколько лёгкости и кайфа я стал благодаря им получать — не передать словами. Наверное, надо пересмотреть «Всегда говори „Да“».

Всем лёгкости и спокойствия, которые может дать простая фраза «Да, и…».

Новая сторона гениальности Шерлока, которой мы не замечали, или как показать силу разума через самый сложный экзамен в мире

Сериал «Шерлок» вышел 16 лет назад, в июле 2010 года.
С первой серии разорвал рейтинги и стал любимцем зрителей, а Кэмбербетч, сыгравший Холмса-младшего, взлетел на свой пьедестал популярности.
Первая серия действительно была полна эффектных сцен, в которых Шерлок демонстрирует невероятную мощь дедукции — метода мышления, при котором частный вывод логически выводится из общих положений или фактов. Холмс сразу же определяет, что Ватсон — военный врач, устанавливает род деятельности жертвы по её кольцу и каплям грязи на колготках, рассказывает Джону об алкоголизме сестры, просто подержав в руках его телефон. Эффектно, но просто.

Новая сторона гениальности Шерлока открылась для меня совсем недавно, когда я узнал про экзамен, который называется The Knowledge.

Что такое The Knowledge

The Knowledge (англ. «Знание») — экзамен, который сдают таксисты в Лондоне. Не все таксисты — только те, которые хотят водить классический чёрный кэб, он же Hackney carriage, вот такой:

Экзамен считается одним из самых сложных в мире — сдают его в течение 4—6 лет, в зависимости от успехов и умственных способностей сдающего.

В чём, собственно, принцип: если ты хочешь водить кэб, то ты просто обязан ориентироваться в городе без карты и навигатора. Кандидат в кэбмены учит более 25 000 улиц и 20 000 достопримечательностей в радиусе 10 километров от центра Лондона. Это не всё, просто знать улицы недостаточно: надо уметь в них ориентироваться, прокладывать маршруты, помнить где какое движение (одностороннее, двустороннее), где сейчас проводятся ремонтные работы, где чаще всего случаются аварии, где дети перебегают дорогу, какие аптеки по пути и в каких выгоднее купить аспирин, какое расписание на Кингс-Кросс, можно ли попасть в тот самый музей в это время года и так до бесконечности. Само собой, кэбмен должен уметь прокладывать маршрут из точки А в точку Б, везти наиболее удобным путем, удовлетворять прихоти пассажира (в рамках законов и социальных норм).

Knowledge boys and girls — так называют мужчин и женщин, готовящихся к экзамену. Их легко узнать: обычно это человек на мопеде с прикрепленной к рулю картой и заметками, кружащий по улицам города и запоминающий улицы, перекрестки, развязки, достопримечательности.

Knowledge boy or girl

Экзамен сдают в несколько заходов, обычно их 12—20, называются они appearances — «явки». Нельзя просто сдать и получить лицензию, или завалить и пойти домой навсегда. Каждая ступень, начиная с третьей (первые две ступени — письменные тесты, ступени 3, 4, и 5 — устные собеседования) даёт студенту баллы, сумма которых пропускает его на следующую ступень, либо, если попытки всё хуже и хуже, откатывает на предыдущую. Устная явка — это диалог с экзаменатором. Обычно диалог строится на 3—5 вопросах следующего вида:

«Увезите меня от Ресторана Гордона Рамзи на Ройал-Хоспитал-Роуд к Музею Шерлока Холмса на Бейкер-стрит».

Экзаменаторы никогда не выбирают простые прямые проспекты. Они подбирают точки на противоположных берегах или в разных частях центра, где движение сильно ограничено. Кандидат должен мгновенно «нарисовать» карту в голове и учесть множество факторов:

  1. Точное знание точек: Нужно до метра помнить, где находятся входы. Ресторан Рамзи находится на юге Челси, а Музей Шерлока — на севере, у Риджентс-парка.
  2. Сложная топография: Между этими точками лежит Гайд-парк. Это огромный барьер. Его нельзя пересечь насквозь в любом месте — нужно точно знать разрешенные сквозные дороги (например, Парк-Лейн или Вест-Карридж-Драйв).
  3. Запреты и одностороннее движение: На Бейкер-стрит и вокруг нее действует строгая система одностороннего движения. Ошибка в один поворот — это автоматический провал (явка не сдана).

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

А при чём тут Шерлок?

В середине первого эпизода Холмс и Ватсон сидят и ужинают в каком-то кафе в Сохо, когда замечают кэб, который, возможно, увозит преступника, попавшегося на их уловку. Кэб стартует быстро, и герои пускаются в погоню, происходит такой диалог:

— Я запомнил номер!
— Молодец! (закрывает глаза) направо, только прямо, переход, одностороннее, направо, пешеходный, нашёл!

Шерлок «поднимает» в голове карту Сохо

Т. е. Холмс буквально повторяет ментальный процесс, который является основным инструментом кэбменов. Только в отличие от водителей, которые годами учат улицы и сдают по 12—20 явок, Шерлок делает это как бы невзначай, мимоходом, будто бы это — лишь один из его повседневных инструментов.

Мне очень нравится думать, что этот приём — чисто британский фарс, который очень сложно понять неподготовленному зрителю. Эту сцену поймут люди, живущие в Лондоне или Англии, и то, не все. А она лишь усиливает ощущение того, что перед нами — гений, для которого сложнейшие в мире мнемонические практики — всего лишь повседневная игрушка.