← Модули · Тильда · Zero Block

3D-карточка с параллаксом для Zero Block

Бесплатно

Zero Block: сначала проставьте ВСЕ классы на элементы (пока без группы), потом выделите их и сгруппируйте — у группы GROUP = Object. На блок: uc-prlx-block (в редакторе prlx-block). Элементы: paralax-card, paralax-title, paralax-decsription, paralax-pic (Shape, не Image). Скрипт в T123.

Обложка модуля: 3D-карточка с параллаксом для Zero Block

Файлы

tilda-parallax-card.js — конфиг классов + tilt + блик

Инструкция

  1. ВАЖНО: сначала ВСЕ классы на каждый элемент, группу делайте ПОСЛЕДНЕЙ. Если сгруппировать раньше — классы часто слетают / не видны.
  2. Zero Block: соберите карточку (фон + тексты + картинка). Пока НЕ группируйте.
  3. Классы (поле CSS class / Element class у каждого элемента отдельно, ДО группы):
  4. • uc-prlx-block — на сам Zero Block (в редакторе: prlx-block → на сайте uc-prlx-block).
  5. • paralax-card — только на прямоугольник-фон карточки.
  6. • paralax-title — на заголовок.
  7. • paralax-decsription — на описание / цену / второй текст (орфография класса именно такая).
  8. • paralax-pic — на Shape (фигуру/лого), не на Image.
  9. Только после классов: выделите все эти элементы → сгруппируйте. У группы справа: GROUP = Object (не обычная Group).
  10. На группу Object отдельный класс не нужен — скрипт найдёт группу вокруг paralax-card.
  11. Рядом блок HTML-код (T123) → вставьте код модуля целиком (уже с <script>). Опубликуйте.
  12. Проверка: при скролле сначала фон, потом буквы, потом щели на картинке; при наведении — наклон.

Код

<script>
/* ============================================
   YM Parallax Card для Тильды Zero Block
   Буквы 3D + Shape/Image полосками (GSAP)
   https://ymportfolio.ru/modules/tilda-parallax-card/
   ============================================ */

(function () {
  var CFG = {
    block: "prlx-block",
    card: "paralax-card",
    title: "paralax-title",
    description: "paralax-decsription",
    pic: "paralax-pic",
    maxTilt: 18,
    glare: true,
    depth: {
      card: 0,
      title: 28,
      description: 16,
      pic: 42
    },
    z: {
      card: 0,
      title: 56,
      description: 32,
      pic: 90
    },
    scale: {
      card: 1,
      title: 1,
      description: 1,
      pic: 1
    },
    assemble: {
      enabled: true,
      hold: 0.35,
      charDuration: 0.42,
      charStagger: 0.032,
      /* картинка: щели через SVG-mask (Shape или Image) */
      slits: 26,
      slitDuration: 0.55,
      slitStagger: 0.038,
      ease: "power3.inOut",
      gsapCdn: "https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"
    }
  };

  function norm(name) {
    return String(name || "").replace(/^\./, "").trim();
  }

  function cfgNum(obj, key, fallback) {
    if (!obj || obj[key] == null || obj[key] === "") return fallback;
    var n = Number(obj[key]);
    return isNaN(n) ? fallback : n;
  }

  function earlyHideCss() {
    if (document.getElementById("ym-prlx-early-hide")) return;
    var t = norm(CFG.title);
    var d = norm(CFG.description);
    var p = norm(CFG.pic);
    var s = document.createElement("style");
    s.id = "ym-prlx-early-hide";
    s.textContent =
      "." + t + ":not(.ym-prlx-done)," +
      "." + d + ":not(.ym-prlx-done)," +
      "." + p + ":not(.ym-prlx-done){opacity:0!important;visibility:hidden!important;}" +
      "." + t + ".ym-prlx-revealing," +
      "." + d + ".ym-prlx-revealing," +
      "." + p + ".ym-prlx-revealing{opacity:1!important;visibility:visible!important;}";
    document.head.appendChild(s);
  }

  earlyHideCss();

  var css = [
    ".ym-prlx-3d{perspective:800px;overflow:visible!important;}",
    ".ym-prlx-3d.tn-group,.ym-prlx-3d .tn-molecule{overflow:visible!important;}",
    ".ym-prlx-stage{position:absolute;left:0;top:0;width:100%;height:100%;",
    "  transform-origin:center center;transform-style:preserve-3d;will-change:transform;",
    "  overflow:visible!important;}",
    ".ym-prlx-stage .tn-elem{transform-style:preserve-3d!important;will-change:transform;",
    "  overflow:visible!important;}",
    ".ym-prlx-stage .tn-atom{transform-style:preserve-3d;}",
    ".ym-prlx-glare{pointer-events:none;position:absolute;inset:0;z-index:3;",
    "  border-radius:inherit;overflow:hidden;opacity:.55;}",
    ".ym-prlx-glare::before{content:\"\";position:absolute;width:140%;height:140%;",
    "  left:var(--gx,-20%);top:var(--gy,-30%);",
    "  background:radial-gradient(ellipse at center,",
    "  rgba(255,255,255,.2) 0%,rgba(255,255,255,.07) 28%,rgba(255,255,255,0) 58%);",
    "  mix-blend-mode:screen;}",
    ".ym-prlx-glare::after{content:\"\";position:absolute;inset:-40%;",
    "  background:linear-gradient(125deg,transparent 42%,rgba(255,255,255,.04) 48%,",
    "  rgba(255,255,255,.14) 50%,rgba(255,255,255,.04) 52%,transparent 58%);",
    "  transform:translateX(var(--sx,-18%)) rotate(12deg);mix-blend-mode:soft-light;}",
    ".ym-prlx-float .tn-atom{filter:drop-shadow(0 10px 18px rgba(0,0,0,.35));}",
    ".ym-prlx-stage.is-hover .ym-prlx-float .tn-atom{",
    "  filter:drop-shadow(0 18px 28px rgba(0,0,0,.5));}",
    /* буквы */
    ".ym-prlx-ch{display:inline-block;will-change:transform,opacity,filter;",
    "  opacity:0;transform:translate3d(0,.7em,0) rotateX(-70deg);filter:blur(8px);",
    "  transform-origin:50% 80%;}",
    ".ym-prlx-word{display:inline-block;white-space:nowrap;perspective:400px;}",
    ".ym-prlx-space{display:inline-block;width:.33em;}",
    ".ym-prlx-pic-build{pointer-events:none;}",
    ".ym-prlx-pic-strip{transform-origin:50% 50%;}"
  ].join("");

  function qsa(root, name) {
    var cls = norm(name);
    if (!cls || !root) return [];
    return Array.prototype.slice.call(root.querySelectorAll("." + cls));
  }

  function atomOf(el) {
    return (el && (el.querySelector(".tn-atom") || el)) || null;
  }

  function findBlocks() {
    var name = norm(CFG.block);
    var list = document.querySelectorAll("." + name + ", .uc-" + name);
    if (list.length) return Array.prototype.slice.call(list);
    return qsa(document, CFG.card).map(function (card) {
      return card.closest(".t-rec") || card.closest(".t396") || card;
    });
  }

  function injectCss() {
    if (document.getElementById("ym-prlx-card-css")) return;
    var s = document.createElement("style");
    s.id = "ym-prlx-card-css";
    s.textContent = css;
    document.head.appendChild(s);
  }

  function loadGsap(done) {
    if (window.gsap) {
      done(window.gsap);
      return;
    }
    var existing = document.querySelector("script[data-ym-gsap]");
    if (existing) {
      existing.addEventListener("load", function () {
        done(window.gsap);
      });
      return;
    }
    var s = document.createElement("script");
    s.src =
      (CFG.assemble && CFG.assemble.gsapCdn) ||
      "https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js";
    s.async = true;
    s.setAttribute("data-ym-gsap", "1");
    s.onload = function () {
      done(window.gsap);
    };
    s.onerror = function () {
      done(null);
    };
    document.head.appendChild(s);
  }

  function wrapStage(card) {
    var molecule =
      card.closest(".tn-molecule") ||
      card.closest(".tn-group") ||
      card.parentNode;
    if (!molecule) return null;

    var kids = molecule.children;
    var i;
    for (i = 0; i < kids.length; i++) {
      if (kids[i].classList && kids[i].classList.contains("ym-prlx-stage")) {
        return kids[i];
      }
    }

    var wrap = document.createElement("div");
    wrap.className = "ym-prlx-stage";
    while (molecule.firstChild) wrap.appendChild(molecule.firstChild);
    molecule.appendChild(wrap);
    molecule.classList.add("ym-prlx-3d");
    var group = molecule.closest(".tn-group");
    if (group) group.classList.add("ym-prlx-3d");
    return wrap;
  }

  function collectLayers(stage, card) {
    var d = CFG.depth || {};
    var z = CFG.z || {};
    var sc = CFG.scale || {};
    var layers = [];
    var seen = {};

    function add(el, depth, zz, scale, float) {
      if (!el || seen[el]) return;
      seen[el] = 1;
      if (float) el.classList.add("ym-prlx-float");
      layers.push({ el: el, depth: depth, z: zz, scale: scale });
    }

    add(card, cfgNum(d, "card", 0), cfgNum(z, "card", 0), cfgNum(sc, "card", 1), false);
    qsa(stage, CFG.title).forEach(function (el) {
      add(el, cfgNum(d, "title", 28), cfgNum(z, "title", 56), cfgNum(sc, "title", 1), true);
    });
    qsa(stage, CFG.description).forEach(function (el) {
      add(
        el,
        cfgNum(d, "description", 16),
        cfgNum(z, "description", 32),
        cfgNum(sc, "description", 1),
        true
      );
    });
    qsa(stage, CFG.pic).forEach(function (el) {
      add(el, cfgNum(d, "pic", 42), cfgNum(z, "pic", 90), cfgNum(sc, "pic", 1), true);
    });
    return layers;
  }

  function addGlare(card) {
    if (!CFG.glare) return null;
    var atom = atomOf(card);
    if (!atom) return null;
    var old = atom.querySelector(".ym-prlx-glare");
    if (old) return old;
    var st = window.getComputedStyle(atom);
    if (st.position === "static") atom.style.position = "relative";
    atom.style.overflow = "hidden";
    var g = document.createElement("div");
    g.className = "ym-prlx-glare";
    g.setAttribute("aria-hidden", "true");
    g.style.setProperty("--gx", "-20%");
    g.style.setProperty("--gy", "-30%");
    g.style.setProperty("--sx", "-18%");
    atom.appendChild(g);
    return g;
  }

  function isPicEl(el) {
    return el && el.classList && el.classList.contains(norm(CFG.pic));
  }

  function splitTextToChars(atom) {
    if (!atom || atom.getAttribute("data-ym-split") === "1") {
      return atom ? Array.prototype.slice.call(atom.querySelectorAll(".ym-prlx-ch")) : [];
    }

    var text = (atom.innerText || atom.textContent || "").replace(/\r/g, "");
    if (!text.trim()) return [];

    atom.setAttribute("data-ym-split", "1");
    atom.setAttribute("data-ym-orig", atom.innerHTML);
    atom.innerHTML = "";

    var chars = [];
    var lines = text.split("\n");
    lines.forEach(function (line, li) {
      if (li > 0) atom.appendChild(document.createElement("br"));
      var words = line.split(/(\s+)/);
      words.forEach(function (part) {
        if (!part) return;
        if (/^\s+$/.test(part)) {
          var sp = document.createElement("span");
          sp.className = "ym-prlx-space";
          sp.innerHTML = " ";
          atom.appendChild(sp);
          return;
        }
        var word = document.createElement("span");
        word.className = "ym-prlx-word";
        for (var i = 0; i < part.length; i++) {
          var ch = document.createElement("span");
          ch.className = "ym-prlx-ch";
          ch.textContent = part.charAt(i);
          word.appendChild(ch);
          chars.push(ch);
        }
        atom.appendChild(word);
      });
    });
    return chars;
  }

  function picTarget(el) {
    var atom = atomOf(el);
    if (!atom) return el;
    return atom;
  }

  function extractUrlsFromCssBg(bg) {
    var urls = [];
    var re = /url\(\s*(['"]?)(.*?)\1\s*\)/gi;
    var m;
    bg = String(bg || "");
    while ((m = re.exec(bg))) {
      var u = (m[2] || "").trim();
      if (u && u !== "none" && urls.indexOf(u) < 0) urls.push(u);
    }
    return urls;
  }

  function collectPicUrls(atom, el) {
    var urls = [];
    function add(u) {
      if (!u || u === "none") return;
      if (urls.indexOf(u) < 0) urls.push(u);
    }
    if (!atom) return urls;

    Array.prototype.forEach.call(atom.querySelectorAll("img"), function (img) {
      try {
        img.loading = "eager";
        if (img.getAttribute("data-original")) add(img.getAttribute("data-original"));
        if (img.getAttribute("data-src")) add(img.getAttribute("data-src"));
        add(img.currentSrc || img.src);
      } catch (e) {}
    });

    addAllBg(atom);
    if (el && el !== atom) addAllBg(el);

    function addAllBg(node) {
      var cs = window.getComputedStyle(node);
      extractUrlsFromCssBg(cs.backgroundImage).forEach(add);
      /* Тильда иногда кладёт картинку в ::before */
      try {
        extractUrlsFromCssBg(window.getComputedStyle(node, "::before").backgroundImage).forEach(add);
      } catch (e2) {}
    }

    return urls;
  }

  function loadOneUrl(url) {
    return new Promise(function (resolve) {
      if (!url) {
        resolve();
        return;
      }
      var img = new Image();
      var done = false;
      function finish() {
        if (done) return;
        done = true;
        resolve();
      }
      img.onload = finish;
      img.onerror = finish;
      img.src = url;
      if (img.complete) finish();
    });
  }

  function waitPicsReady(picEls) {
    return new Promise(function (resolve) {
      var tries = 0;

      function gather() {
        var urls = [];
        picEls.forEach(function (el) {
          var atom = picTarget(el);
          collectPicUrls(atom, el).forEach(function (u) {
            if (urls.indexOf(u) < 0) urls.push(u);
          });
          if (atom) {
            Array.prototype.forEach.call(atom.querySelectorAll("img"), function (img) {
              var src = img.getAttribute("data-original") || img.getAttribute("data-src");
              if (src && !img.getAttribute("src")) img.setAttribute("src", src);
            });
          }
        });
        return urls;
      }

      function attempt() {
        var urls = gather();
        /* Тильда иногда проставляет background позже — подождать */
        if (!urls.length && tries < 30) {
          tries += 1;
          setTimeout(attempt, 100);
          return;
        }
        if (!urls.length) {
          resolve();
          return;
        }
        Promise.all(urls.map(loadOneUrl)).then(resolve);
      }

      attempt();
    });
  }

  function snapshotVisual(atom) {
    var cs = window.getComputedStyle(atom);
    return {
      backgroundColor: cs.backgroundColor,
      backgroundImage: cs.backgroundImage,
      backgroundSize: cs.backgroundSize,
      backgroundPosition: cs.backgroundPosition,
      backgroundRepeat: cs.backgroundRepeat,
      borderRadius: cs.borderRadius,
      boxShadow: cs.boxShadow,
      border: cs.border
    };
  }

  function hideOriginal(atom) {
    if (!atom) return;
    atom.setAttribute("data-ym-pic-prev", atom.style.cssText || "");
    atom.style.visibility = "hidden";
    atom.style.opacity = "0";
  }

  function showOriginal(atom) {
    if (!atom) return;
    var prev = atom.getAttribute("data-ym-pic-prev");
    atom.removeAttribute("data-ym-pic-prev");
    if (prev != null) atom.style.cssText = prev;
    else {
      atom.style.visibility = "";
      atom.style.opacity = "";
    }
  }

  /* Надёжная сборка: ждём картинку, оригинал скрыт, полоски с явным background */
  function buildPicStrips(el, atom, stripsN) {
    var w = Math.round(el.offsetWidth || atom.offsetWidth || 0);
    var h = Math.round(el.offsetHeight || atom.offsetHeight || 0);
    if (w < 4 || h < 4) {
      var box = el.getBoundingClientRect();
      w = Math.round(box.width) || w;
      h = Math.round(box.height) || h;
    }
    if (w < 4 || h < 4) return null;

    var st = window.getComputedStyle(el);
    if (st.position === "static") el.style.position = "relative";

    var old = el.querySelector(".ym-prlx-pic-build");
    if (old) old.remove();

    var snap = snapshotVisual(atom);
    hideOriginal(atom);

    var n = Math.max(14, Math.round(stripsN) || 26);
    var stripW = w / n;
    var build = document.createElement("div");
    build.className = "ym-prlx-pic-build";
    build.setAttribute("aria-hidden", "true");
    build.style.cssText =
      "position:absolute;left:0;top:0;width:" +
      w +
      "px;height:" +
      h +
      "px;z-index:6;pointer-events:none;overflow:visible;";

    var strips = [];
    var i;
    for (i = 0; i < n; i++) {
      var strip = document.createElement("div");
      strip.className = "ym-prlx-pic-strip";
      strip.style.cssText =
        "position:absolute;top:0;left:" +
        i * stripW +
        "px;width:" +
        (Math.ceil(stripW) + 1.25) +
        "px;height:100%;overflow:hidden;will-change:clip-path,opacity,transform;";

      var clone = atom.cloneNode(true);
      clone.removeAttribute("data-ym-pic-prev");
      clone.style.cssText =
        "position:absolute;left:" +
        -i * stripW +
        "px;top:0;width:" +
        w +
        "px;height:" +
        h +
        "px;margin:0;visibility:visible!important;opacity:1!important;transform:none;filter:none;" +
        "background-color:" +
        snap.backgroundColor +
        ";" +
        "background-image:" +
        snap.backgroundImage +
        ";" +
        "background-size:" +
        snap.backgroundSize +
        ";" +
        "background-position:" +
        snap.backgroundPosition +
        ";" +
        "background-repeat:" +
        snap.backgroundRepeat +
        ";" +
        "border-radius:" +
        snap.borderRadius +
        ";";

      /* img внутри клона — тоже дождаться уже загруженного src */
      Array.prototype.forEach.call(clone.querySelectorAll("img"), function (img) {
        img.loading = "eager";
        img.style.width = "100%";
        img.style.height = "100%";
        img.style.objectFit = "cover";
        img.style.visibility = "visible";
        img.style.opacity = "1";
      });

      strip.appendChild(clone);
      build.appendChild(strip);
      strips.push(strip);
    }

    el.appendChild(build);
    return { build: build, strips: strips, atom: atom, el: el };
  }

  function clearPicStrips(pack) {
    if (!pack) return;
    if (pack.build && pack.build.parentNode) pack.build.parentNode.removeChild(pack.build);
    showOriginal(pack.atom);
  }

  function markDone(el) {
    el.classList.remove("ym-prlx-revealing");
    el.classList.add("ym-prlx-done");
  }

  function playAssembleGSAP(gsap, stage) {
    var a = CFG.assemble || {};
    var titles = qsa(stage, CFG.title);
    var descs = qsa(stage, CFG.description);
    var pics = qsa(stage, CFG.pic);

    var master = gsap.timeline();
    var charDur = cfgNum(a, "charDuration", 0.42);
    var charStagger = cfgNum(a, "charStagger", 0.032);
    var slitDur = cfgNum(a, "slitDuration", 0.55);
    var slitStagger = cfgNum(a, "slitStagger", 0.038);
    var slits = Math.round(cfgNum(a, "slits", 26));
    var ease = a.ease || "power3.inOut";

    function addTextBlock(els) {
      els.forEach(function (el) {
        var atom = atomOf(el);
        if (!atom) return;
        el.classList.add("ym-prlx-revealing");
        var chars = splitTextToChars(atom);
        if (!chars.length) {
          markDone(el);
          return;
        }
        master.fromTo(
          chars,
          {
            opacity: 0,
            y: 18,
            rotateX: -70,
            filter: "blur(8px)"
          },
          {
            opacity: 1,
            y: 0,
            rotateX: 0,
            filter: "blur(0px)",
            duration: charDur,
            stagger: charStagger,
            ease: "back.out(1.4)",
            onComplete: function () {
              markDone(el);
            }
          },
          master.duration() === 0 ? 0 : ">-0.08"
        );
      });
    }

    function addPicBlocks() {
      pics.forEach(function (el) {
        var atom = picTarget(el);
        if (!atom) {
          markDone(el);
          return;
        }

        el.classList.add("ym-prlx-revealing");
        var pack = buildPicStrips(el, atom, slits);
        if (!pack) {
          showOriginal(atom);
          markDone(el);
          return;
        }

        var tl = gsap.timeline({
          onComplete: function () {
            clearPicStrips(pack);
            markDone(el);
          }
        });

        tl.fromTo(
          pack.strips,
          {
            opacity: 0,
            scaleY: 0.08,
            clipPath: "inset(0 46% 0 46%)",
            webkitClipPath: "inset(0 46% 0 46%)"
          },
          {
            opacity: 1,
            scaleY: 1,
            clipPath: "inset(0 46% 0 46%)",
            webkitClipPath: "inset(0 46% 0 46%)",
            duration: slitDur * 0.45,
            stagger: slitStagger,
            ease: "power2.out"
          }
        );

        tl.to(pack.strips, {
          clipPath: "inset(0 0% 0 0%)",
          webkitClipPath: "inset(0 0% 0 0%)",
          duration: slitDur,
          stagger: slitStagger * 0.75,
          ease: ease
        });

        master.add(tl, ">-0.05");
      });
    }

    /* сначала ждём фоны/img — иначе анимируется белый Shape */
    waitPicsReady(pics).then(function () {
      addTextBlock(titles);
      addTextBlock(descs);
      addPicBlocks();
    });
  }

  function playAssembleFallback(stage) {
    var all = []
      .concat(qsa(stage, CFG.title))
      .concat(qsa(stage, CFG.description))
      .concat(qsa(stage, CFG.pic));
    all.forEach(function (el, i) {
      el.classList.add("ym-prlx-revealing");
      el.style.transition = "opacity .8s ease " + i * 0.35 + "s";
      requestAnimationFrame(function () {
        el.style.opacity = "1";
        setTimeout(function () {
          markDone(el);
        }, 800 + i * 350);
      });
    });
  }

  function runAssemble(stage) {
    var a = CFG.assemble || {};
    if (a.enabled === false) {
      contentTargets(stage).forEach(markDone);
      return;
    }

    var picsEarly = qsa(stage, CFG.pic);
    /* прогрев картинок сразу, до скролла */
    waitPicsReady(picsEarly);

    function play() {
      var holdMs = Math.max(0, cfgNum(a, "hold", 0.4) * 1000);
      setTimeout(function () {
        loadGsap(function (gsap) {
          if (gsap) playAssembleGSAP(gsap, stage);
          else playAssembleFallback(stage);
        });
      }, holdMs);
    }

    if (!("IntersectionObserver" in window)) {
      play();
      return;
    }

    var started = false;
    var io = new IntersectionObserver(
      function (entries) {
        entries.forEach(function (entry) {
          if (entry.isIntersecting && !started) {
            started = true;
            play();
            io.disconnect();
          }
        });
      },
      { threshold: 0.3 }
    );
    io.observe(stage);
  }

  function contentTargets(stage) {
    return []
      .concat(qsa(stage, CFG.title))
      .concat(qsa(stage, CFG.description))
      .concat(qsa(stage, CFG.pic));
  }

  function bind(stage, layers, glare) {
    var max = CFG.maxTilt || 18;
    var tx = 0;
    var ty = 0;
    var cx = 0;
    var cy = 0;
    var raf = 0;
    var active = false;
    var canTilt = !window.matchMedia("(hover:none)").matches;

    function applyLayers(nx, ny) {
      layers.forEach(function (item) {
        var dx = (nx * item.depth).toFixed(2);
        var dy = (ny * item.depth).toFixed(2);
        var sc = item.scale || 1;
        item.el.style.transform =
          "translate3d(" + dx + "px," + dy + "px," + item.z + "px) scale(" + sc + ")";
      });
    }

    applyLayers(0, 0);
    if (!canTilt) return;

    function paint() {
      cx += (tx - cx) * 0.16;
      cy += (ty - cy) * 0.16;
      var rx = -cy * max;
      var ry = cx * max;
      stage.style.transform =
        "rotateX(" + rx.toFixed(2) + "deg) rotateY(" + ry.toFixed(2) + "deg)";
      applyLayers(cx, cy);
      if (glare) {
        glare.style.setProperty("--gx", (-20 + ry * 2.2).toFixed(1) + "%");
        glare.style.setProperty("--gy", (-30 - rx * 2.2).toFixed(1) + "%");
        glare.style.setProperty("--sx", (-18 + ry * 1.6).toFixed(1) + "%");
      }
      if (Math.abs(tx - cx) >= 0.002 || Math.abs(ty - cy) >= 0.002 || active) {
        raf = requestAnimationFrame(paint);
      } else {
        raf = 0;
        cx = tx;
        cy = ty;
      }
    }

    function kick() {
      if (!raf) raf = requestAnimationFrame(paint);
    }

    stage.addEventListener("pointermove", function (e) {
      var r = stage.getBoundingClientRect();
      if (!r.width || !r.height) return;
      tx = ((e.clientX - r.left) / r.width) * 2 - 1;
      ty = ((e.clientY - r.top) / r.height) * 2 - 1;
      active = true;
      stage.classList.add("is-hover");
      kick();
    });
    stage.addEventListener("pointerleave", function () {
      tx = 0;
      ty = 0;
      active = false;
      stage.classList.remove("is-hover");
      kick();
    });
  }

  function setupCard(card) {
    var stage = wrapStage(card);
    if (!stage || stage.getAttribute("data-ym-prlx") === "1") return;
    stage.setAttribute("data-ym-prlx", "1");
    bind(stage, collectLayers(stage, card), addGlare(card));
    runAssemble(stage);
  }

  function init() {
    injectCss();
    findBlocks().forEach(function (block) {
      var cards = qsa(block, CFG.card);
      if (!cards.length && block.classList && block.classList.contains(norm(CFG.card))) {
        cards = [block];
      }
      cards.forEach(setupCard);
    });
  }

  var tries = 0;
  function boot() {
    var ready = findBlocks().some(function (block) {
      return (
        qsa(block, CFG.card).length ||
        (block.classList && block.classList.contains(norm(CFG.card)))
      );
    });
    if (!ready && tries < 40) {
      tries += 1;
      setTimeout(boot, 120);
      return;
    }
    init();
  }

  function start() {
    if (typeof t_onFuncLoad === "function") {
      t_onFuncLoad("t396_init", function () {
        setTimeout(boot, 80);
      });
    } else {
      boot();
    }
  }

  if (typeof t_onReady === "function") {
    t_onReady(start);
  } else if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", start);
  } else {
    start();
  }

  window.YM_PARALLAX_CARD = CFG;
})();
</script>