Задача: интерактивный портрет моего персонажа с раскрытием лица под маской. Сначала прочитай действующие 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 — отдельные статусы.