{
    "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:\/\/www.blog.denislykin.ru\/tags\/portfolio\/",
    "feed_url": "https:\/\/www.blog.denislykin.ru\/tags\/portfolio\/json\/",
    "icon": "https:\/\/www.blog.denislykin.ru\/pictures\/userpic\/userpic@2x.jpg?1783580338",
    "authors": [
        {
            "name": "Денис Лыкин",
            "url": "https:\/\/www.blog.denislykin.ru\/",
            "avatar": "https:\/\/www.blog.denislykin.ru\/pictures\/userpic\/userpic@2x.jpg?1783580338"
        }
    ],
    "items": [
        {
            "id": "6",
            "url": "https:\/\/www.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:\/\/www.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:\/\/www.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:\/\/www.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:\/\/www.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:\/\/www.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:\/\/www.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:\/\/www.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:\/\/www.blog.denislykin.ru\/pictures\/Frame-1.png",
                    "https:\/\/www.blog.denislykin.ru\/pictures\/1.0.png",
                    "https:\/\/www.blog.denislykin.ru\/pictures\/1.0-Feed.png",
                    "https:\/\/www.blog.denislykin.ru\/pictures\/Concept-2.png",
                    "https:\/\/www.blog.denislykin.ru\/pictures\/Frame-2.png",
                    "https:\/\/www.blog.denislykin.ru\/pictures\/Frame-3.png"
                ]
            }
        }
    ],
    "_e2_version": 4199,
    "_e2_ua_string": "Aegea 11.5 (v4199)"
}