Задача: интерактивный портрет моего персонажа с раскрытием лица под маской.

Сначала прочитай действующие AGENTS.md и инструкции текущего проекта.
Подтверди checkout, текущую ветку, Git state и существующий frontend.
Сохрани чужие изменения. Новую ветку, commit, push и deploy выполняй
только по отдельному прямому поручению пользователя.

Результат: отдельный hero-блок с портретом моего взрослого вымышленного
персонажа. Имя и внешность задаются моими материалами. Посетитель проводит по маске мягкой
кистью, открывая то же лицо без маски. Затем маска восстанавливается.
Кнопка полностью открывает лицо и возвращает маску.

Материалы:
- face.webp — утверждённый портрет без маски;
- masked.webp — его отредактированный вариант с маской;
- face-small.webp / masked-small.webp — меньшая совмещённая пара.
Используй предоставленное лицо. Новую identity не генерируй.
Проверь одинаковые размеры, положение головы, глаза, crop и фон.
Если совпадение недостаточно, сообщи конкретное расхождение.
Исходники в сотни мегабайт сохрани без изменения локально;
для сайта создай лёгкие WebP-производные и измерь их размер.
Не публикуй огромные оригиналы автоматически.
Используй только мои собственные или лицензированные изображения.
Не загружай и не копируй портреты Эриды из демонстрации Eclipse Library.
Скачиваемый код примера намеренно не содержит изображений персонажа.

Используй существующий framework и дизайн-систему. Для отдельного
примера достаточно HTML/CSS/JavaScript и Canvas 2D. Не добавляй
библиотеки, сервисы и AI-вызовы без технической необходимости.

Механика:
1. Нижний слой — портрет без маски; передний Canvas — портрет в маске.
2. Отдельный offscreen Canvas хранит мягкие белые отпечатки кисти.
3. Интерполируй путь с шагом не больше четверти радиуса кисти.
4. Передний Canvas каждый кадр вырезает trail через destination-out.
5. Ограничь раскрытие областью лица. Волосы и фон должны оставаться
   стабильными. Все слои используют общие координаты и размеры.
6. Настрой исчезновение 95% следа за 1100 мс:
   tau = 1100 / Math.log(20);
   eraseAlpha = 1 - Math.exp(-dt / tau);
   dt и tau в миллисекундах. Через 1800 мс без движения очищай остаток
   и останавливай цикл. Скорость не должна зависеть от частоты кадров.
7. Свечение — необязательный отдельный слой: trail окрашивается через
   source-in, область лица вычитается destination-out. Умеренный blur
   и screen. Предусмотри выключение декоративного слоя.

Пример стартовых параметров; проверь их по моему портрету:
face = { x: 0.502, y: 0.444, rx: 0.205, ry: 0.16 };
brushRadius = canvasWidth * 0.062;
accent = '#b96f85'.
Проценты относятся к полному изображению. Для нового кадра проверь их
заново. Установи общий aspect ratio из фактического исходника.

Управление:
- мышь рисует при движении по портрету;
- getCoalescedEvents применять только при наличии API, иначе [event];
- кнопки, ссылки, поля ввода и навигация не запускают кисть;
- touch: активный pointerId от pointerdown, завершение при pointerup,
  pointercancel и lostpointercapture; сохраняй вертикальную прокрутку
  и pinch zoom. Для этого примера допустим pan-y pinch-zoom и
  горизонтальное рисование, а полное раскрытие доступно кнопкой;
- обычный button «Показать лицо / Вернуть маску» с aria-pressed;
- отдельный «Сбросить» и Escape восстанавливают маску;
- необязательная виртуальная кисть стрелками на сфокусированном
  портрете с ограничением координат. Не обозначай 2D-кисть slider;
- видимый фокус, понятное описание портрета, редкие объявления
  состояния. Не отправляй screen reader сообщения каждый кадр.

Состояния:
Статичный портрет сразу доступен. До загрузки и проверки обеих картинок
интерактивные кнопки disabled. Предусмотри loading, ошибку, тайм-аут
и реальную повторную загрузку. Устаревший ответ не должен менять новую
попытку. Если JavaScript/Canvas недоступен, оставь изображение и ссылку
на портрет без маски. Не сообщай успех до реальной готовности.

Доступность и ресурсы:
Учитывай prefers-reduced-motion через matchMedia и change. В этом режиме
используй статичное переключение кнопкой, без кисти и свечения.
Короткая прозрачность допустима. Ограничь devicePixelRatio до 2 и
ширину backing Canvas до 1440. Используй ResizeObserver и
IntersectionObserver. Один rAF-цикл работает только во время следа.
Останавливай его при скрытии страницы, потере фокуса и уходе портрета
за экран. Уничтожение компонента отменяет rAF, слушатели, наблюдатели
и загрузочные ответы. При возврате из history/BFCache восстанови
работоспособность. Не добавляй телеметрию и внешние сетевые запросы.

Для повторяемой визуальной проверки добавь t=<секунды> с фиксированным
состоянием следа без rAF и reduce=1 для спокойного режима.
Публичная основная ссылка не должна принудительно включать QA-режим.

Проверка:
Проверь реальный путь в браузере, а не только сборку. Мышь, быстрый
мазок, восстановление, idle, кнопка, Enter/Space/стрелки/Escape,
фокус, desktop/mobile, DPR 2, resize, touch cancel и прокрутка,
reduced-motion при загрузке и переключении, ошибка картинки и retry,
статичный fallback без JavaScript/Canvas, остановка вне экрана.
Осмотри screenshots normal/reduced и desktop/mobile. Проверь консоль,
внешние запросы, контраст и CSP. Для inline-кода примера предпочти
точные CSP-хеши. Укажи браузер, реально пройденные сценарии и ограничения.
Не выдавай эмуляцию телефона за проверку на физическом устройстве.

Перед публикацией подготовь подробное руководство с этим полным
промптом, описанием подготовки изображений, параметрами, ошибками,
источниками, результатами проверки и ZIP кода примера.
Не включай изображения персонажа в скачиваемый ZIP без отдельного
разрешения владельца. Добавь README с инструкцией подставить свои файлы.
Сначала практическая реализация и проверка, затем руководство.
Встраивание в продукт, commit/push и production — отдельные статусы.
