// Живой прототип Paper Shaders на стенде _lab (без правки прод-кода). // Пакет скопирован локально в /vendor/paper-shaders (zero-dep, ESM). import { ShaderMount, meshGradientFragmentShader, getShaderColorFromString, defaultObjectSizing, ShaderFitOptions, } from '/vendor/paper-shaders/index.js'; // Цвета по бренду (как у .contact-card / .neu-card--shader) const BRAND = { tg: ['#0088cc', '#00bcd4', '#0a66c2'], vk: ['#0077ff', '#4a90ff', '#0033aa'], ya: ['#f8604a', '#ffae00', '#ff3d00'], email: ['#7c5cff', '#b06cff', '#5a3fd6'], demo: ['#7c5cff', '#00c2c7', '#ff5c8a', '#ffb347'], }; // Официальная конвертация hex/HSL/RGB -> [r,g,b,a] (0..1), как ожидает u_colors (vec4[]). const c4 = (arr) => arr.map(getShaderColorFromString); // Sizing uniforms обязательны (vertex-шейдер делит на u_scale — при 0 будет NaN // и весь шейдер чёрный). Берём дефолты из пакета, как в React-обёртке. const sizing = () => ({ u_fit: ShaderFitOptions[defaultObjectSizing.fit], u_scale: defaultObjectSizing.scale, u_rotation: defaultObjectSizing.rotation, u_offsetX: defaultObjectSizing.offsetX, u_offsetY: defaultObjectSizing.offsetY, u_originX: defaultObjectSizing.originX, u_originY: defaultObjectSizing.originY, u_worldWidth: defaultObjectSizing.worldWidth, u_worldHeight: defaultObjectSizing.worldHeight, }); // Единственный используемый шейдер: mesh gradient, анимированный. const MESH = { frag: meshGradientFragmentShader, speed: 1.0, uniforms: (c) => ({ ...sizing(), u_colors: c4(c), u_colorsCount: c.length, u_distortion: 1.2, u_swirl: 0.8, u_grainMixer: 0.08, u_grainOverlay: 0.04, }), }; const mounts = new WeakMap(); function mountShader(host, colors) { const wrap = host.querySelector('.card-shader'); if (!wrap) return; if (mounts.has(host)) { try { mounts.get(host).dispose(); } catch {} mounts.delete(host); } while (wrap.firstChild) wrap.removeChild(wrap.firstChild); try { const m = new ShaderMount(wrap, MESH.frag, MESH.uniforms(colors), { preserveDrawingBuffer: true, antialias: true }, MESH.speed); // отключаем IntersectionObserver: иначе вне вьюпорта ставит currentSpeed=0 // и рендер замирает (у пользователя карточка внизу страницы -> пауза). try { m.intersectionObserver?.disconnect(); } catch {} mounts.set(host, m); } catch (e) { console.error('ShaderMount failed', e); } } function mountAll(root = document) { // любые карточки с шейдером: у .card-shader есть data-shader (бренд), // сама карточка — его родитель (contact-card, neu-card--shader, shader-cell...) root.querySelectorAll('.card-shader[data-shader]').forEach((wrap) => { const host = wrap.parentElement; const brand = wrap.dataset.shader; mountShader(host, BRAND[brand] || BRAND.demo); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', mountAll); } else { mountAll(); } window.__mountLabShaders = mountAll;