Eclipse LibraryОткрыть эксперимент ↗

CANVAS / ИНТЕРАКТИВНЫЙ ДИЗАЙН

По ту сторону
маски.

Как два портрета превращаются
в живой комикс-постер.

Исходные изображения: Гвен в костюме и портрет без маски
Два предоставленных кадра. Общая геометрия. Никакой генерации лица.
Практический гайд по интерактивному Canvas-постеру: два прозрачных портрета, курсор-слизь, раскрытие костюма и восстановление одним нажатием. Без библиотек и фреймворков.

Этот материал объединяет полный бриф пользователя из «Gwen Stacy.docx» и инженерный разбор реализации. Оригинальный текст сохранён отдельно, без скрытого превращения промпта в правила Codex. Дата подготовки: 5 сентября 2026 года.

Формат: десктопный фан-концепт, один экран без прокрутки. Стек: HTML, CSS, vanilla JavaScript и Canvas 2D. Результат: управляемое раскрытие лица, молочный след на стене, кнопки восстановления, ручного раскрытия и остановки движения.

Локальный проект лежит в web/experiments/gwen-reveal/. Откройте его из ссылки «Gwen: по ту сторону маски» в Animation Lab. Иллюстрированная версия гайда находится рядом со сценой в guide.html; в ней есть исходный бриф и оформленный DOCX. Это локальная реализация, не заявление о публикации.

Модуль 1. От идеи к проверяемому результату

Урок 1. Что делает эффект понятным

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

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

Урок 2. Что нужно подготовить

В предоставленных файлах Suit.png и Sadie.png действительно есть прозрачность. Оба имеют размер 1360×768. Белые поля и чёрный фон в некоторых просмотрщиках не доказывают отсутствие альфа-канала. Проверять нужно данные файла, а не цвет превью.

Модуль 2. Ассеты без потери совмещения

Урок 3. Один бокс для двух изображений

Сначала объединяем альфа-каналы, затем вычисляем общий bounding box. Добавляем 24 пикселя с каждой стороны. Обрезаем оба изображения одним и тем же боксом. Только после этого масштабируем их до ширины 1400 пикселей.

Для этой пары общий бокс с запасом: (247, 5, 1130, 792). Нижний запас выходит за исходный кадр и заполняется прозрачностью. Итоговый размер: 1400×1248. Высота 1230 из примерной CSS-формулы брифа не подменяет фактическую геометрию изображения.

union = ImageChops.lighter(suit.getchannel('A'), face.getchannel('A'))
x0, y0, x1, y1 = union.getbbox()
box = (x0 - 24, y0 - 24, x1 + 24, y1 + 24)
size = (1400, round(1400 * (box[3]-box[1]) / (box[2]-box[0])))
suit = suit.crop(box).resize(size, Image.Resampling.LANCZOS)
face = face.crop(box).resize(size, Image.Resampling.LANCZOS)

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

Урок 4. Matte для стены

Из объединения альфа-каналов делаем маску силуэта. MaxFilter(11) расширяет край примерно на 5 пикселей, GaussianBlur(3) сглаживает переход. Цвет PNG может быть белым: для destination-out важна прозрачность, а не RGB.

alpha = ImageChops.lighter(suit.getchannel('A'), face.getchannel('A'))
alpha = alpha.filter(ImageFilter.MaxFilter(11))
alpha = alpha.filter(ImageFilter.GaussianBlur(3))

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

Результаты подготовки: suit.webp, face.webp, matte.png. WebP сохраняются с quality 90. provenance.json содержит размеры, общий crop и SHA-256 оригиналов. Оригинальные PNG не изменяются.

Модуль 3. Композиция и слои

Урок 5. Порядок важнее количества z-index

СлойНазначениеПоложение
Чёрная стенаСвет, зерно, паутина и комиксная печатьfixed, z-index 0
GWENРисованный SVG-тег, не шрифтовая подменаРаньше героини в DOM
ГероиняПортрет, костюм и край раскрытияОбёртка без z-index и transform
СкримЧитаемость верхнего и нижнего интерфейсаfixed, z-index 20
СлизьМолочное тело и блики вне силуэтаfixed, z-index 30
ИнтерфейсНавигация и действияz-index 40

Нельзя случайно создать stacking context у обёртки героини через transform, opacity, filter или собственный z-index. Перемещение самого canvas внутри обёртки не должно поднимать весь контейнер над интерфейсом. В мобильном упрощённом режиме композиция проверяется отдельно.

Урок 6. Чёрная стена и стикеры

Палитра сохраняет бриф: #08080A, #F6F2FF, #FF2D87, #22E0FF. Основная типографика использует Inter Tight. Латинская UI-обвязка использует Chakra Petch; для русских букв нужен кириллический fallback, поскольку выбранный Chakra Petch не содержит кириллицу. Шрифты и OFL-лицензии лежат в проекте локально.

GWEN нарисован четырьмя непрерывными ломаными path. Несколько use переиспользуют геометрию для призрачных дублей. Сплошные серые цвета позволяют буквам закрывать паутину. Срезанные углы интерфейса задаёт clip-path, а не случайные радиусы карточек.

Гличи и параллакс не должны конкурировать с основным действием. При reduced motion или ручной паузе они выключаются. Кнопка «Смотреть эффект» запускает реальную демонстрацию Canvas, а не обещает отсутствующий видеотрейлер. Галерея открывает два предоставленных кадра. Музыка не подключается без исходного файла и понятных прав.

Модуль 4. Физика молочной капли

Урок 7. След, узлы и независимость от FPS

Четыре масочных буфера работают в масштабе 0.55 от экрана: trail, goo, ring, gloss. Дополнительные буферы нужны для готового внутреннего портрета, костюма и стенового слоя. Это Canvas-рендер, а не обещание «только transform и opacity».

Голова цепочки тянется к указателю с коэффициентом 0.42, следующие восемь узлов тянутся к предыдущему с 0.34. Размер сужается до 38% у хвоста. Базовый радиус равен 6.2% меньшей стороны экрана с ограничением 26-62 px. При зажатой кнопке радиус плавно увеличивается в 1.45 раза.

const frameK = (base, dt) => 1 - Math.pow(1 - base, dt / (1000 / 60));
node.x += (target.x - node.x) * frameK(0.42, dt);
node.y += (target.y - node.y) * frameK(0.42, dt);

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

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

Урок 8. Слипание, блик и раскрытие

goo получает размытую версию следа и порог альфа-канала через SVG feColorMatrix. Поддержка фильтра проверяется при запуске: непрозрачный центр, прозрачная внешняя область и исчезновение слабого контрольного пятна. Последняя проверка отличает работающий фильтр от браузера, который тихо проигнорировал url(#...).

При неподдерживаемом фильтре используется blur(6px). Такой fallback мягче настоящего metaball-порога; это честное визуальное отличие, а не идентичный результат. Кайма строится как размытие goo минус goo, блик как разность двух слегка сдвинутых копий.

face -> destination-in(suit alpha) -> внутренний портрет
suit -> destination-out(goo) -> костюм с отверстием
внутренний портрет + костюм + край -> canvas героини
молочное goo + кайма + блик -> destination-out(matte) -> стена

Все экранные координаты переводятся в координаты canvas через сохранённый прямоугольник героини. Он пересчитывается при resize, не на каждом кадре. Буфер героини ограничен шириной 1600 px, devicePixelRatio ограничен 2.

Модуль 5. Приёмка без самообмана

Урок 9. Доступность и состояния

Целостность обновляется в DOM не чаще раза в 90 ms. Изменение процентов не озвучивается скринридером каждый кадр: озвучиваются только осмысленные события, например завершение восстановления.

Урок 10. Что проверять руками и измерять

ПроверкаОжидаемый результат
Курсор над глазамиВнутри раскрытия виден исходный портрет, без второго силуэта
Курсор на стенеМолочная капля видна, на героине наружный слой слизи отсутствует
Быстрый длинный жестНепрерывный след и вытянутая цепочка
Зажатая кнопкаРадиус плавно растёт, после отпускания уменьшается
ВосстановлениеСледы и подтёки исчезают, целостность 100%
Диалог и EscapeОткрытие, закрытие и возврат фокуса работают
Reduced motionНет самопроизвольных движений, полезное раскрытие работает
Потеря ассетаСообщение об ошибке и доступный статичный fallback

В DevTools доступна функция gwenDiagnostics(): режим фильтра, число кадров, размер canvas, усреднённое время рендера и недавний FPS. Это диагностический снимок, не инструмент записи пользовательских действий. Значение 60 FPS нельзя обещать по одному только размеру буферов. Проверяйте целевое железо, нагрузку GPU и конкретный браузер; измерения и ограничения фиксируются в design-qa.md проекта.

Модуль 6. Где использовать и как публиковать

Урок 11. Применение в Eclipse

Animation Lab: самостоятельный арт-эксперимент и практический гайд. Не смешиваем fan-concept с production-компонентами форм или оплат.

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

Eclipse Media: тот же технический приём подходит для просмотра «до/после» цветокоррекции, ретуши или монтажа. Для рабочего инструмента лучше заменить декоративную слизь предсказуемой маской/ползунком и сохранить нейтральный интерфейс.

Цифровой компаньон: эффект можно использовать для смены состояния собственного аватара. Он не доказывает наличия 3D-модели, распознавания эмоций или realtime lip-sync. Нужны свои права на персонажа и портрет.

Урок 12. Публикация отдельным решением

Сначала локальная проверка и review прав на исходники. Затем можно разместить проверенную статическую папку в библиотеке. Для независимого демо подходят GitHub Pages и Cloudflare Pages: обе платформы поддерживают статические HTML/CSS/JS-проекты.

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

Перед коммерческим использованием замените персонажа и портрет на собственные или лицензированные материалы. Не называйте фан-концепт официальным проектом Marvel, не выдавайте предоставление файла за разрешение на любое распространение и не переносите OFL-лицензию шрифтов на изображения.

Приложение. Полный авторский бриф и исходники

Полный текст из предоставленного Gwen Stacy.docx сохранён в assets/original-brief.txt рядом с проектом. Он включён в иллюстрированную HTML-версию и в приложение оформленного DOCX. Порядок пунктов и технические значения сохранены. Дополнения реализации описаны выше отдельно: фактическая высота 1248 px, русские подписи, рабочая демонстрация вместо отсутствующего трейлера, обработка ошибок и клавиатурный режим.

Подготовка ассетов выполняется локальным скриптом scripts/prepare-gwen-assets.py, оформление гайда и DOCX скриптом scripts/build-gwen-guide.py. Runtime самого эффекта состоит из трёх файлов. HTML-версия гайда, документ, provenance и инструменты сборки являются сопровождающими материалами, а не зависимостями сцены.

Источники по реализации

Полный исходный промпт

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

Открыть полный текст промпта
Исходники Проекта:


Промт для генерации сайта:
ЗАДАЧА
Собери одностраничный главный экран сайта в эстетике Spider-Gwen (Земля-65) с эффектом
«просвечивания» костюма курсором-слизью. Только десктоп, без скролла (overflow: hidden).
Стек: чистые HTML + CSS + vanilla JS, три файла — index.html, styles.css, script.js.
Никаких библиотек и фреймворков. Все интерактивные эффекты — на canvas.

ИСХОДНИКИ (лежат в папке проекта)
Два PNG-выреза с прозрачным фоном, снятые с одной точки и совпадающие попиксельно:
- suit.png — девушка в костюме Ghost-Spider (белая маска, розовая окантовка, капюшон);
- face.png — та же девушка без маски.
Шаг 0 — подготовка ассетов скриптом (Python/Pillow или canvas):
- обрежь оба одним общим боксом (union их альфа-bbox + запас 24px), приведи к ширине
1400px, сохрани в assets/suit.webp и assets/face.webp (quality 90);
- сгенерируй assets/matte.png: union альфа-каналов, дилатация ~5px, gaussian blur ~3px —
это маска силуэта, по ней слизь будет стираться с героини.

ПАЛИТРА / ШРИФТЫ
--bg:#08080A; --bone:#F6F2FF; --pink:#FF2D87; --cyan:#22E0FF; muted = bone на 55%.
Цветные акценты используются точечно: почти весь экран монохромный, единственное яркое
пятно — сама героиня. Google Fonts: Inter Tight (400–900) — база; Chakra Petch (500–700) —
вся UI-обвязка: uppercase, letter-spacing .14em, размеры 10–12px.

РАСКЛАДКА (сверху вниз по z-порядку, БЕЗ лишних z-index)
1. Фон (fixed, z:0).
2. Граффити-тег GWEN (absolute, без z-index — просто раньше в DOM).
3. Канвас героини (absolute-обёртка на весь экран; ВАЖНО: ни transform, ни z-index на
обёртке — иначе создастся stacking context и сломается порядок слоёв).
4. Скримы: тёмные градиенты сверху/снизу (fixed, z:20).
5. Полноэкранный канвас слизи (fixed, z:30, pointer-events:none).
6. UI (z:40): топбар, подводка, карточка, футбар.

ФОН «ЧЁРНАЯ СТЕНА»
- радиальный графитовый свет за героиней: #232028 → #121014 → прозрачный (центр 55% 36%);
- всполохи: розовый в левом верхнем (radial, blur 70px, opacity .14), циановый в правом
нижнем (.11);
- растровые точки комиксной печати в двух углах: background radial-gradient(circle,
rgba(246,242,255,.5) 0 1.7px, transparent 2.4px) / size 15–17px, mask на затухание к
краям, opacity .07–.09, наклон ±6–8°;
- паутина в углах: инлайн-SVG — 5 спиц из угла + 4 провисающие нити (quadratic Bezier),
stroke 1px, opacity .07, второй экземпляр повёрнут на 180°;
- зерно: SVG feTurbulence (baseFrequency .85), mix-blend-mode:soft-light, opacity .2.

ГРАФФИТИ-ТЕГ «GWEN» (не шрифт! рисованный SVG-wildstyle)
- инлайн-SVG viewBox ~1160×430; каждая буква — ОДИН непрерывный ломаный path;
- стиль штриха: stroke-width 36, stroke-linecap square, stroke-linejoin miter,
stroke-miterlimit 14, fill none;
- геометрия: у букв шипы-засечки на концах; наклоны согласованы — верхние перекладины
идут вверх-вправо, стойки чуть завалены вправо, диагонали W и N параллельны, первый и
последний штрихи W зеркальны; G с открытым просветом и перекладиной внутрь (не Q!);
буквы разделены зазорами; под словом — росчерк на всю ширину с «галочкой» на конце;
вокруг — 6 мелких косых параллелограммов-обломков;
- слои через <defs>+<use> (4 шт.): два призрачных дубля #26262B и #1F1F23, поверх —
ядро #5A5960; цвета СПЛОШНЫЕ, не rgba — тег должен перекрывать паутину, а не
просвечивать её;
- гличи: дубли дёргаются через steps(1) — циклы 2.1s и 2.7s, 5–6 ключевых кадров со
сдвигами от −21 до +19px; раз в 4.2s весь SVG коротко дёргается двумя кадрами
translate+skewX(±2–2.5°);
- посадка: rotate(−3°), строго по центру экрана — проверь фактический bbox рисунка и
сдвинь viewBox так, чтобы визуальный центр совпал с геометрическим;
- параллакс: тег плавно уезжает от курсора (цель: (0.5−x/W)×26px, (0.5−y/H)×14px,
lerp ~0.06/кадр), наклон −3° сохраняется в transform.

ГЕРОИНЯ
Канвас по центру, во весь экран по высоте: width:min(118vw, 100dvh×(1400/1230)).
Рендер в разрешении экрана: ширина = min(1600, max(600, cssWidth×min(dpr,2))).
Композиция каждый кадр:
1) face → drawImage;
2) face обрезать силуэтом костюма: destination-in по suit (чтобы из-под капюшона не
торчал второй кадр);
3) сверху suit, из которого вырезаны дыры каплей: destination-out по goo-маске
(пересчёт координат экран→канвас);
4) по краю разрыва: тень (goo blur 6px, alpha .5, source-atop) и призрачно-белая кайма
(alpha .9).
Появление: opacity 0→1 за 0.9s после загрузки всех трёх картинок.

КУРСОР-СЛИЗЬ (главный эффект)
cursor:none на body. Offscreen-канвасы в масштабе 0.55 от экрана: trail, goo, ring, gloss.
1) След: пятна копятся в trail и тают через destination-out ~3.2%/кадр (нормируй на dt).
2) Капля — пружинная цепочка из 9 узлов: узел 0 тянется к курсору (lerp .42/кадр),
каждый следующий — к предыдущему (.34), радиусы убывают до 38%. Базовый радиус
6.2% меньшей стороны экрана (клемп 26–62px), при зажатой кнопке ×1.45 (lerp .14).
Все коэффициенты frame-rate-независимы: k = 1−(1−base)^(dt/16.67).
3) Между позициями узла за кадр — «мазок»: промежуточные штампы через каждые ~r/3,
чтобы след не рвался на быстрой мыши.
4) Неровный край: у каждого штампа 3 пятна-сателлита (радиус ~50%, дрейф по синусоидам
от времени и seed).
5) Подтёки: раз в 240–860ms из хвоста падает капля (vy += 0.075/кадр, радиус ×0.986,
удалить при r<2.5 или за экраном).
6) Слипание (metaball): goo = trail через SVG-фильтр feGaussianBlur stdDeviation 7 +
feColorMatrix (alpha: ×24, −12). ОБЯЗАТЕЛЬНО runtime-тест: нарисуй круг на тестовом
канвасе с ctx.filter='url(#goo)', проверь getImageData (в центре alpha>200, снаружи
<40); если не работает — фолбэк blur(6px).
7) Кайма: blur(5px) от goo минус сам goo (destination-out), закрашено градиентом
#F6F2FF→#D9D2EC→#F6F2FF.
Блик: goo минус goo со сдвигом (−3,−4), source-in rgba(226,210,255,.9).
9) Полноэкранный слой слизи: кайма-свечение (alpha .75) → goo → тело молочным #EFEBF8
(source-atop, alpha .9) → лавандовый градиент-перелив (alpha .18) → блик (blur 2px,
alpha .62) → destination-out по matte.png в позиции героини (слизь живёт на стене,
но обтекает силуэт).
10) Автопилот: пока мышь не двигалась (и первые ~4s), капля сама водит по лицу по
синусоидам; первый pointermove отключает его и прячет подсказку.

UI — СТИЛЬ «СТИКЕРЫ НА СТЕНЕ»
Все плашки: без border-radius, срезанные под 45° углы через clip-path polygon (срез
9px у мелких, 12–16px у крупных; срезаются верхний левый и нижний правый), фон
rgba(12,12,15,.5) + backdrop-filter blur, бордер rgba(255,255,255,.16).
- Топбар: логотип «GWEN/STC» (слэш розовый, text-shadow-свечение) + сабтайтл
«earth-65 unit» (9px, letter-spacing .34em); навигация-пилюли home/story/suit/band/
gallery/press в общем контейнере, активная — сплошной bone-стикер с naklonom −1.6°;
CTA «play trailer»: розовая рамка, наклон −2°, hover — выравнивается + scale 1.03.
- Паучок: один SVG <symbol> (8 лапок C-кривыми stroke 1.5 round, тело эллипс 2.1×3.1,
голова круг r1.5), переиспользуется через <use> в CTA (15px, розовый,
drop-shadow-свечение) и в кнопке внизу (19px).
- Кнопка «mask integrity» (низ слева, наклон −1.2°): паучок внутри SVG-кольца прогресса
(r 14.5, stroke 2.2, поворот −90°, анимация через stroke-dashoffset, C≈91.1).
Логика: пока капля над центральной третью героини — integrity −0.35/кадр, иначе
+0.6/кадр (клемп 8–100), обновление UI раз в 90ms; ниже 40% дуга розовая → циановая.
Клик = «залатать»: очистить trail-канвас, убить подтёки, integrity 100.
- Низ справа: подсказка «move the cursor across her — the suit melts» + пульсирующая
молочная капелька (border-radius морфится keyframes); прячется при первом движении.
- Справа: карточка-постер (кадр suit.webp, object-position низ — эмблема на груди;
срезанные углы, наклон +1.4°, псевдо-кнопка play, заголовок «Ghost-Spider returns»).
- Слева: подводка «drag to reveal → caught between gwen stacy and ghost-spider».

КАЧЕСТВО / ПРИЁМКА
- prefers-reduced-motion: все CSS-анимации и автопилот выключены.
- Скрытый h1 «Gwen» для скринридеров (sr-only), aria-hidden на декоративных SVG.
- Проверь руками: (1) при движении по костюму открывается лицо с мягким белым краем;
(2) слизь на чёрной стене видна как молочная капля и обтекает силуэт; (3) на быстрой
мыши след не рвётся, капля вытягивается в комету; (4) тег стоит ровно по центру,
дубли дёргаются, героиня перекрывает его середину; (5) счётчик падает/растёт, клик
латает костюм; (6) стабильные 60fps (маски в масштабе 0.55, тяжёлых фильтров на
DOM-элементах нет).