Веб-портфолио с управлением жестами через камеру: полевой дневник. claude code.. claude code. design.. claude code. design. mediapipe.. claude code. design. mediapipe. three.js.. claude code. design. mediapipe. three.js. transformers.js.. claude code. design. mediapipe. three.js. transformers.js. WebGPU.. claude code. design. mediapipe. three.js. transformers.js. WebGPU. вебкамера.. claude code. design. mediapipe. three.js. transformers.js. WebGPU. вебкамера. депт-карта.. claude code. design. mediapipe. three.js. transformers.js. WebGPU. вебкамера. депт-карта. жесты.

Я продуктовый дизайнер. И мне захотелось поэкспериментировать: можно ли уже сейчас сделать полноценное управление сайтом с помощью жестов. Да и ИИшки уже стали достаточно мощными. Так родился этот эксперимент. Для контента взял свое портфолио.

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

start

start

Дальше — грабли по порядку наступания.

Потрогать: https://dimbo-design.github.io/dimbo_heavy-cv/ (десктоп, вебкамера).
Код: https://github.com/dimbo-design/dimbo_heavy-cv

Сразу о разделении труда, без него не объяснить формат статьи. Код писал Claude Code. За мной замысел, дизайн-решения и полевые тесты. Я дизайнер, и в инженерной части проекта понимаю далеко не всё, притворяться не буду. Поэтому договоримся так: как я работал над проектом описываю сам. Где начинается разработка, буду давать объяснение от Клода.

Да, и эта статья написана так же: черновик собирал Клод, окончательный текст и всё, за что тут можно поругать — мое.

Стек, как мне его объяснили

Что я понимаю сам: всё считается на устройстве, ни один кадр с камеры никуда не отправляется, сервера нет вообще. Статика на GitHub Pages, без сборки.
Дальше цитирую Клода:

Глубину кадра считает Depth Anything V2 Small (ONNX) через Transformers.js: WebGPU, с WASM-фолбэком для Safari/Firefox. Инференс живёт в Web Worker и работает с адаптивной частотой: 20 fps, когда человек в кадре, 11 fps, пока он читает главу, 6 fps в пустой комнате. Руки и скелет отслеживает MediaPipe Hand/Pose Landmarker на основном потоке (~5 мс на кадр). Рендер на Three.js: одна геометрия точек, один шейдер на все состояния сайта, один draw call.

Из всего этого мне как владельцу продукта оказалось критично понять одно (а понял это не сразу): рендер практически бесплатный, вся цена в том, чтобы железо потянуло и не перегревалось. Частиц можно насыпать вдвое больше без последствий — подобрал приятное глазу количество. Компромисс с производительностью — в частоте нейросети: она решает, как часто обновляется отражение.

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

Метод: я валидирую — ИИ разбирает

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

Большая часть моей роли свелась к дизайн-ревью: валидации жестов живой рукой. В сайт встроен дебаг-слой (⌥D): журнал всех жестовых событий за сессию плюс запись сырых движений кисти (⌥R). Цикл: я пользуюсь сайтом как обычный человек → ловлю момент «оно меня не поняло» → копирую журнал с записью → отдаю Клоду вместе с описанием чего я на самом деле хотел → он разбирает запись, объясняет механику отказа и предлагает правку → валидируем дальше. Прошли десятки таких итераций.

debug panel

debug panel

Ключевые слова здесь «чего я на самом деле хотел». Самый дорогой урок проекта: логи без описания намерений лгут. Однажды статистика движений показала Клоду, что 70% моих движений вниз — случайный шум. Реальность же: я целенаправленно пытался скроллить, а система читала подготовку к жесту как сам жест. Что показывает: чистые логи без контекста не дают полной картины.

Грабли, по порядку наступания

Ось «к камере» умерла первой

Самое интуитивное движение: «толкнуть» ладонь к экрану. Оно не заработало, и я долго не понимал почему, пока не получил объяснение:

Одна RGB-камера измеряет приближение руки единственным способом: размером кисти в кадре. А он дрожит на ±35% от простого поворота ладони. Фильтры сглаживают дрожь, но вместе с ней и само движение.

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

Воздух — не стекло

Центральная проблема всего жестового ввода. На тачскрине жест дискретен, потому что есть стекло: поднял палец, жест кончился. В воздухе стекла нет, и у каждого взмаха есть возврат. Махнул вверх, рука вернулась вниз, система прочитала «вниз». Как пользователь я ощущал это как лаги: страница дёргается туда-сюда от одного моего движения.

Здесь я прошёл путь, который другие уже проходили до меня: попытался научить систему понимать обратные движения. Перепробовали умные фильтры: окна инерции, пороги решительности (обратный жест должен быть в полтора раза резче), анализ профиля скорости. Каждый работал на бумаге и разваливался в тесте по одной причине: любой подавленный ввод ощущается как лаг. Пользователь не ведёт себя предсказуемо и не знает, как подстроиться под способ анализа жестов. Он знает только своё намерение: махнул для скролла вниз, а оно не сработало.

Скролл разбился на два решения: безопасное и экспериментальное. В безопасном взял текущую стабильную практику с щипком. О том, что щипок индустриальный стандарт, узнал уже когда дошёл до решения: индустрия прошла тот же путь — от свайпов Kinect до Vision Pro все пришли к явным целям и щипку-скроллу.

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

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

Мёртвое должно быть недетектируемым

Закон, который Клод сформулировал после того, как он трижды не понял, что каждому состоянию экрана нужен свой словарь:

Если жест на данном экране «не работает», его нельзя распознавать и игнорировать. Его нельзя распознавать вовсе. Распознанный-но-отброшенный взмах всё равно взводит кулдауны соседних жестов, и пользователь получает необъяснимые отказы там, где вообще ничего не делал.

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

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

После сложного состояния — тишина

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

Физика как валидатор

Пороги держались, только когда за ними стояло физическое обоснование, а не подобранная константа. Как пример — фокус от статичной части тела. Она «выбирала» пункты меню, пока не появилось правило «выбирает то что двигается»: курсор, не проехавший 60 пикселей за 3,5 секунды, не рука, и его фокус ничего не открывает. Из той же серии: две ладони не могут занимать одну точку пространства, значит «фантомный» зум с нулевой дистанцией режется чистой геометрией, без всяких порогов.

interact

interact

Как это тестировать, если в CI нет рук

В техническую кухню я не вдупляю. Как-то оно само регрессирует — вот описание от ИИ:

Два уровня. Первый — реплей: записанные сырые движения кисти (в том числе реальные сессии владельца с метками намерений) стали фикстурами, и детекторы прогоняются по ним как тесты: «эта запись обязана дать ровно один шаг назад и ноль щепотей». Второй — браузерный: puppeteer поднимает сайт с программно нарисованной депт-картой «человека» и синтетическими руками и проходит 22 проверки, от загрузки модели до открытия главы наведением. Если ваш ввод сенсоры, а не клики, запись и реплей реального ввода — самая дешёвая тестовая инфраструктура из возможных.

regress 22/22

regress 22/22

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

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

Третья правка из поля оказалась не про жесты: у части посетителей из России нейросети просто не загружались — huggingface и CDN из их сетей недостижимы. Теперь все модели и библиотеки (~120 МБ) лежат в самом репозитории и грузятся с того же домена, что и страница, а внешние источники остались тихим резервом.

Что я понял про работу с ИИ

Пара «дизайнер-владелец + ИИ-инженер» рабочая, если у каждого есть своя зона ответственности. Мои: намерение, вкус, дизайн-навыки и полевые тесты. Только я знаю, что имел в виду движением и чувствую лаг взаимодействия. Его: архитектура, код и гипотезы. Каждая правка приходила с объяснением механики, которое я мог оспорить, и оспаривал: половина законов выше родилась из моего «погоди, я же не это имел в виду».

Как это делалось, я не скрываю: история коммитов открыта. Факт что «ИИ может написать сайт» давно не новость. Ценность истории в подходе: как была устроена работа — у кого какая зона, где чьё слово последнее и на чём проверялись гипотезы. По сути, вышло обычное разделение обязанностей, как в продуктовой команде.

Что бы я сказал себе в первый день

Если соберётесь делать что-то сенсорное (жесты, голос, взгляд, любой ввод без «стекла»):

  • личные эксперименты погружают в сферу лучше чем статьи с готовыми ответами (но это дольше)

  • словарь выводится из записей реального использования, а не из текстового описания для ИИ; запись и реплей ввода стройте раньше самого словаря;

  • логи без меток намерений лгут: данные отвечают «что произошло», человек «что имелось в виду», работают только вместе;

  • стройте только на том, что можете мерить надёжно; у каждого направления один хозяин; мёртвый жест недетектируем; после сложного состояния тишина;

  • если работаете в паре с ИИ: определите зоны принятия решений; любую правку проводите с объяснением, которое можно оспорить.

Сайт живёт здесь: https://dimbo-design.github.io/dimbo_heavy-cv/ (нужен десктоп с gpu и вебкамерой; всё считается локально, видео никуда не уходит).
Полный словарь жестов в README репозитория: https://github.com/dimbo-design/dimbo_heavy-cv

А если найдёте в интерфейсе что-то, о чём здесь ни слова, значит нашли пасхалки)


Дмитрий Тогулев — Senior Product Designer, ex-KazanExpress, ex-Uzum.

Автор: DimaDimbo

Источник