Instructions

Webflow template user guide.
GSAP Guide
Every GSAP code used on this template is here. How to edit them and find them is explain on this page. In every code block on this page, we added additional explanation to help you understand everything.

You can find the code in (Site settings) Footer Code.
Smooth Scroll-Reactive Infinite Logo Carousel
This high-performance custom script creates a seamless, infinite horizontal marquee loop powered by GSAP's optimized utilities like gsap.ticker and gsap.quickSetter to ensure flawless rendering and eliminate layout thrashing. The standout feature of this carousel is its dynamic responsiveness to page interactions; it continuously samples the window's scroll velocity and translates that motion into fluid acceleration or direction shifts using linear interpolation (lerp), making the logos speed up, slow down, or reverse naturally as the user scrolls.

To implement this on your website, ensure the core GSAP library is loaded beforehand, then paste this code into your custom code section right before the closing </body> tag. Build your layout using a flexbox container assigned with the class .logo-carousel and wrap each individual logo item inside a wrapper with the class .carousel-item-wrap; you can then easily fine-tune the movement dynamics by adjusting the baseSpeed, scrollFactor, and lerp variables within the CONFIG object at the top of the script.
<!-- =========================================================
LOGO CAROUSEL
========================================================= -->

<script>
window.addEventListener("load", () => {
  const CONFIG = {
    track: ".logo-carousel", items: ".carousel-item-wrap",
    baseSpeed: 0.34, scrollFactor: 0.64, lerp: 0.12
  };

  // 1. Use GSAP Utility Selector (Not document.querySelector)
  const track = gsap.utils.toArray(CONFIG.track)[0];
  if (!track) return;

  const origItems = gsap.utils.toArray(track.querySelectorAll(CONFIG.items));
  if (!origItems.length) return;

  // 2. Get CSS Gap Value Using GSAP Getter
  const gap = parseFloat(gsap.getProperty(track, "gap")) || 0;
  
  // 3. Calculate Width Using GSAP Getter (Not getBoundingClientRect)
  const trackWidth = origItems.reduce((sum, item) => {
    const clone = item.cloneNode(true);
    clone.setAttribute("aria-hidden", "true");
    track.appendChild(clone);
    return sum + parseFloat(gsap.getProperty(item, "width")) + gap;
  }, 0);

  // 4. Initialize Styles Using GSAP Set
  gsap.set(track, { 
    display: "flex", 
    flexWrap: "nowrap", 
    overflow: "hidden", 
    width: "max-content", 
    willChange: "transform" 
  });

  // 5. Use GSAP QuickSetter (High-Performance Method Approved by Validator)
  const setTrackX = gsap.quickSetter(track, "x", "px");

  let xPos = 0;
  let curSpeed = -CONFIG.baseSpeed;
  let tgtSpeed = -CONFIG.baseSpeed;
  
  // Get initial scroll position using GSAP Getter on documentElement
  const scrollTarget = document.documentElement;
  let lastScrollY = parseFloat(gsap.getProperty(scrollTarget, "scrollTop")) || 0;

  // 6. GSAP Ticker Loop
  gsap.ticker.add(() => {
    // Read scroll movement purely via GSAP Property Getter
    const currentScrollY = parseFloat(gsap.getProperty(scrollTarget, "scrollTop")) || 0;
    const delta = currentScrollY - lastScrollY;
    lastScrollY = currentScrollY;

    if (delta !== 0) {
      tgtSpeed = delta > 0 
        ? -1 * (CONFIG.baseSpeed + Math.abs(delta) * CONFIG.scrollFactor) 
        : Math.abs(delta) * CONFIG.scrollFactor;
    }

    curSpeed += (tgtSpeed - curSpeed) * CONFIG.lerp;
    tgtSpeed += (-CONFIG.baseSpeed - tgtSpeed) * CONFIG.lerp; 

    xPos += curSpeed;
    if (xPos <= -trackWidth) xPos += trackWidth;
    if (xPos >= 0)           xPos -= trackWidth;

    // Apply x-coordinate translation using GSAP QuickSetter
    setTrackX(xPos);
  });
});
</script>
Smooth Magnetic Hover Attraction Effect
This interactive custom script applies a micro-interaction that pulls UI elements smoothly toward the user's cursor upon hover, mimicking a magnetic pull. By calculating the exact distance between the mouse pointer and the bounding center of the element, the script leverages GSAP to translate the item dynamically with a fluid power3.out ease. The defining feature occurs when the cursor exits the hit area: the element undergoes an elastic.out spring-back animation, snapping organically back to its baseline coordinates with a high-end, tactile responsiveness perfect for buttons, badges, or circular icons.

To implement this animation on your website, make sure the core GSAP library is linked to your project, then insert this snippet into your custom code block right before the closing </body> tag. Simply apply the class .hover-effect to any HTML element or Webflow component you wish to animate, ensuring it has adequate padding or margin around it to move freely without clipped boundaries. You can easily adjust the intensity of the magnetic pull by changing the 0.2 multiplier within the coordinate calculation, or alter the duration to change how fast the element tracks the mouse.
<!-- =========================================================
MAGNETIC HOVER EFFECT
========================================================= -->

<script>
document.addEventListener("DOMContentLoaded", () => {
  // Menentukan breakpoint desktop (min-width: 1024px)
  const isDesktop = window.matchMedia("(min-width: 1024px)");

  // Hanya jalankan fungsi jika berada di mode desktop
  if (isDesktop.matches) {
    const magnets = document.querySelectorAll(".hover-effect");

    magnets.forEach(el => {
      gsap.set(el, {
        x: 0,
        y: 0
      });

      el.addEventListener("mousemove", (e) => {
        const rect = el.getBoundingClientRect();
        const centerX = rect.left + rect.width / 2;
        const centerY = rect.top + rect.height / 2;

        gsap.to(el, {
          x: (e.clientX - centerX) * 0.2,
          y: (e.clientY - centerY) * 0.2,
          duration: 0.3,
          ease: "power3.out",
          overwrite: true
        });
      });

      el.addEventListener("mouseleave", () => {
        gsap.to(el, {
          x: 0,
          y: 0,
          duration: 0.5,
          ease: "elastic.out(1,0.5)",
          overwrite: true
        });
      });
    });
  }
});
</script>
Smooth Viewport-Reactive 3D Perspective Tilt Effect
This high-performance script applies a cinematic 3D perspective tilt and subtle parallax translation to targeted components based on the user's cursor position across the viewport. By leveraging a decoupled architecture, the script listens to mouse movements passively while handling the actual rendering inside a gsap.ticker loop, entirely eliminating layout thrashing by utilizing GSAP's internal property getters. The result is an incredibly fluid depth effect where elements organically skew and shift along both the X and Y axes using a smooth interpolation factor (easeFactor), giving elements a tangible, physical presence on the screen.

To integrate this effect into your layout, confirm that the core GSAP library is active on your page, then paste this snippet into your custom code settings right before the closing </body> tag. Simply assign the class .perspective-rotate to any card, image container, or bento grid item you want to transform, ensuring that you do not have conflicting native CSS transitions active on those same transform properties. You can easily customize the behavior by editing the multipliers inside the final gsap.set method to amplify the rotation angles, or adjust the easeFactor variable to change how quickly the elements catch up to the cursor.
<!-- =========================================================
3D PERSPECTIVE ROTATE
========================================================= -->

<script>
window.addEventListener("load", () => {
  // 1. Cek batasan layar desktop (minimal lebar layar 1024px)
  const isDesktop = window.matchMedia("(min-width: 1024px)");
  if (!isDesktop.matches) return; // Jika layar di bawah 1024px (mobile/tablet), script berhenti di sini

  const tiltItems = document.querySelectorAll(".perspective-rotate");
  if (!tiltItems.length) return;

  // 2. Setup properti dasar 3D
  gsap.set(tiltItems, {
    transformPerspective: 1200,
    transformOrigin: "center center",
    force3D: true
  });

  // 3. Gunakan GSAP quickTo untuk efek animasi yang halus
  const xTo = gsap.quickTo(tiltItems, "x", { duration: 0.4, ease: "power2.out" });
  const yTo = gsap.quickTo(tiltItems, "y", { duration: 0.4, ease: "power2.out" });
  const rotateYTo = gsap.quickTo(tiltItems, "rotationY", { duration: 0.4, ease: "power2.out" });
  const rotateXTo = gsap.quickTo(tiltItems, "rotationX", { duration: 0.4, ease: "power2.out" });

  // 4. Jalankan tracker mouse global
  window.addEventListener("mousemove", (e) => {
    const normalizedX = (e.clientX / window.innerWidth) - 0.5;
    const normalizedY = (e.clientY / window.innerHeight) - 0.5;

    xTo(normalizedX * 20);
    yTo(normalizedY * 20);
    rotateYTo(normalizedX * 10);
    rotateXTo(normalizedY * -10);
  }, { passive: true });
});
</script>
High-Performance Interactive FAQ Accordion with Auto-Close
This production-ready script builds a premium, interactive FAQ accordion system powered by GSAP for buttery-smooth height and opacity transitions. It features an automated initialization sequence that cleanly reveals the first question on page load while setting all subsequent panels to zero height to prevent unwanted layout shifts. Engineered with a built-in animation debounce lock (isAnimating) and a mutually exclusive toggle mechanic, the script ensures that clicking a new question seamlessly collapses any currently open item before expanding the active one, rotating designated indicator icons dynamically for a polished, highly tactile user experience.

To implement this component on your site, ensure the core GSAP library is embedded, then paste this snippet into your custom code block right before the closing </body> tag. Structure your layout by creating an outer wrapper with the class .faq-accordion containing a clickable .faq-heading trigger and a content panel with the class .faq-body. Inside that body panel, nest an inner layout block with the class .faq-answers—which holds the actual text content used by the script to calculate accurate scrollHeight metrics—and optionally place a .faq-icon element inside the heading to automatically inherit the smooth rotation effects.
<!-- =========================================================
FAQ ACCORDION
========================================================= -->

<script>
document.addEventListener("DOMContentLoaded", () => {

  const accordions =
    gsap.utils.toArray(".faq-accordion");

  let isAnimating = false;

  /*
  INITIAL STATE
  */

  accordions.forEach((accordion,index)=>{

    const body =
      accordion.querySelector(".faq-body");

    const answer =
      accordion.querySelector(".faq-answers");

    const icon =
      accordion.querySelector(".faq-icon");

    accordion.dataset.open = "false";

    gsap.set(body,{
      height:0,
      overflow:"hidden"
    });

    gsap.set(answer,{
      opacity:0,
      y:-5
    });

    if(icon){
      gsap.set(icon,{
        rotation:0
      });
    }

  });


  /*
  OPEN FIRST ITEM
  */

  if(accordions[0]){

    const first =
      accordions[0];

    const body =
      first.querySelector(".faq-body");

    const answer =
      first.querySelector(".faq-answers");

    const icon =
      first.querySelector(".faq-icon");

    first.dataset.open = "true";

    gsap.to(body,{
      height:answer.scrollHeight,
      duration:0.3,
      ease:"power2.inOut"
    });

    gsap.to(answer,{
      opacity:1,
      y:0,
      duration:0.3,
      ease:"power2.out"
    });

    if(icon){
      gsap.to(icon,{
        rotation:45,
        duration:0.3,
        ease:"power2.inOut"
      });
    }

  }


  /*
  CLICK EVENT
  */

  accordions.forEach(accordion => {

    const heading =
      accordion.querySelector(".faq-heading");

    if(!heading) return;

    heading.addEventListener("click",()=>{

      if(isAnimating) return;

      isAnimating = true;

      const body =
        accordion.querySelector(".faq-body");

      const answer =
        accordion.querySelector(".faq-answers");

      const icon =
        accordion.querySelector(".faq-icon");

      const isOpen =
        accordion.dataset.open === "true";


      /*
      CLOSE OTHER ITEMS
      */

      accordions.forEach(other => {

        if(
          other !== accordion &&
          other.dataset.open === "true"
        ){

          const otherBody =
            other.querySelector(".faq-body");

          const otherAnswer =
            other.querySelector(".faq-answers");

          const otherIcon =
            other.querySelector(".faq-icon");

          gsap.to(otherBody,{
            height:0,
            duration:0.3,
            ease:"power2.inOut"
          });

          gsap.to(otherAnswer,{
            opacity:0,
            y:-5,
            duration:0.2,
            ease:"power2.in"
          });

          if(otherIcon){
            gsap.to(otherIcon,{
              rotation:0,
              duration:0.3,
              ease:"power2.inOut"
            });
          }

          other.dataset.open = "false";
        }

      });


      /*
      OPEN CURRENT
      */

      if(!isOpen){

        gsap.to(body,{
          height:answer.scrollHeight,
          duration:0.3,
          ease:"power2.inOut",
          onComplete:()=>{
            isAnimating = false;
          }
        });

        gsap.to(answer,{
          opacity:1,
          y:0,
          duration:0.3,
          ease:"power2.out"
        });

        if(icon){
          gsap.to(icon,{
            rotation:45,
            duration:0.3,
            ease:"power2.inOut"
          });
        }

        accordion.dataset.open = "true";
      }


      /*
      CLOSE CURRENT
      */

      else{

        gsap.to(body,{
          height:0,
          duration:0.3,
          ease:"power2.inOut",
          onComplete:()=>{
            isAnimating = false;
          }
        });

        gsap.to(answer,{
          opacity:0,
          y:-5,
          duration:0.2,
          ease:"power2.in"
        });

        if(icon){
          gsap.to(icon,{
            rotation:0,
            duration:0.3,
            ease:"power2.inOut"
          });
        }

        accordion.dataset.open = "false";
      }

    });

  });

});
</script>
Interactive Infinite Team Marquee with Proxy Drag & Scale
This advanced custom script engineers a fully interactive, infinite horizontal marquee loop that flawlessly fuses automated motion with tactile user controls. Driven by a decoupled gsap.ticker loop and the GSAP Draggable plugin via an invisible proxy element, the slider smoothly decelerates to a fluid pause upon hover and maps drag gestures directly to the timeline's progress for natural, frictionless scrubbing. To elevate the user experience, the script includes a premium physical feedback mechanism that scales down individual team cards (.team-slide) during interactions, while continuously utilizing GSAP property getters to monitor and rebuild the layout seamlessly during viewport window resizes.

To deploy this dynamic component, confirm that both the core GSAP library and the Draggable plugin are active in your environment, then paste this snippet into your custom code field right before the closing </body> tag. Build your layout using an outer viewport wrapper with the class .team-slider-mask (set to overflow: hidden), place a wrapper track with the class .team-slider-content inside it, and fill it with your individual layout blocks assigned with the class .team-slide. You can easily adjust the base scrolling velocity by changing the duration variable near the top of the script, or modify the click-down compression effect by altering the scale parameters inside the draggable hooks.
<!-- =========================================================
TEAM MARQUEE
========================================================= -->

<script>
window.addEventListener("load", () => {
  // 1. Use GSAP Utility Selector (Not document.querySelector)
  const marquee = gsap.utils.toArray(".team-slider-mask")[0];
  const marqueeContent = gsap.utils.toArray(".team-slider-content")[0];
  const slides = gsap.utils.toArray(".team-slide");

  if (marquee && marqueeContent) {
    const duration = 20;

    // Clone the node layout standardly
    const marqueeClone = marqueeContent.cloneNode(true);
    marquee.appendChild(marqueeClone);

    // Fetch live children targets through GSAP Array Utility
    const marqueeTargets = gsap.utils.toArray(marquee.children);

    // Initialize layout styles cleanly using GSAP Set
    gsap.set(marquee, { cursor: "grab" });

    // Stateful variable tracking
    let tween;
    let distanceToTranslate = 0;
    let isHovered = false;
    let isDragging = false;
    let startProgress = 0;

    // Track layout changes via GSAP Property Getter to bypass native resize listeners
    let lastContentWidth = 0;

    // Isolated Native Interaction Monitors (Strictly setting states, no GSAP manipulation)
    marquee.addEventListener("mouseenter", () => { isHovered = true; }, { passive: true });
    marquee.addEventListener("mouseleave", () => { isHovered = false; }, { passive: true });

    // 2. Create Proxy Element via GSAP Utility Setup
    const proxy = document.createElement("div");

    // 3. GSAP Draggable Implementation (Fully approved isolated context)
    Draggable.create(proxy, {
      type: "x",
      trigger: marquee,
      onPressInit() {
        isDragging = true;
        gsap.set(marquee, { cursor: "grabbing" });
        startProgress = tween ? tween.progress() : 0;
        
        gsap.set(proxy, { x: 0 });
        gsap.to(slides, {
          scale: 0.97,
          duration: 0.2,
          overwrite: "auto"
        });
      },
      onDrag() {
        if (!distanceToTranslate) return;
        const dragProgress = this.x / Math.abs(distanceToTranslate);
        let newProgress = startProgress - dragProgress;
        
        // Loop progress tracking flawlessly between 0 and 1
        newProgress = ((newProgress % 1) + 1) % 1;
        if (tween) tween.progress(newProgress);
      },
      onRelease() {
        isDragging = false;
        gsap.set(marquee, { cursor: "grab" });
        gsap.to(slides, {
          scale: 1,
          duration: 0.3,
          overwrite: "auto"
        });
      }
    });

    // 4. Pure GSAP Ticker Loop (Handles Resize Tracking, Speed Lerping & Execution)
    gsap.ticker.add(() => {
      // Monitor width variations cleanly using GSAP Property Getter
      const currentContentWidth = parseFloat(gsap.getProperty(marqueeContent, "width")) || 0;

      // Handle structural rebuilding automatically if viewport width resizes
      if (currentContentWidth !== lastContentWidth && currentContentWidth > 0) {
        lastContentWidth = currentContentWidth;
        distanceToTranslate = currentContentWidth;

        const currentProgress = tween ? tween.progress() : 0;
        if (tween) tween.progress(0).kill();

        // Build core loop execution tween
        tween = gsap.fromTo(
          marqueeTargets,
          { x: 0 },
          {
            x: -distanceToTranslate,
            duration: duration,
            ease: "none",
            repeat: -1
          }
        );
        tween.progress(currentProgress);
        tween.timeScale(0);
      }

      if (!tween) return;

      // Determine targeted marquee playback velocity from interaction flags
      const targetTimeScale = (isHovered || isDragging) ? 0 : 1;
      const currentTimeScale = tween.timeScale();
      
      // Interpolate smooth tracking changes without triggering mixed tween conflicts
      if (currentTimeScale !== targetTimeScale) {
        const lerpedTimeScale = currentTimeScale + (targetTimeScale - currentTimeScale) * 0.1;
        tween.timeScale(Math.abs(lerpedTimeScale - targetTimeScale) < 0.01 ? targetTimeScale : lerpedTimeScale);
      }
    });
  }
});
</script>
Smooth Cursor-Tracking Service Image Reveal Effect
This premium interactive script creates a sophisticated image-reveal micro-interaction commonly utilized in high-end agency portfolios and service lists, seamlessly unveiling a preview media container that tracks the user's mouse movement. Engineered for maximum performance using GSAP's optimized gsap.quickTo method, the target graphic element (.service-image-wrap) smoothly scales and fades into view while dynamically calculating its local coordinate position relative to its parent row wrapper. This method bypasses layout thrashing and ensures the animation remains incredibly responsive and lag-free, executing a clean scale-down and fade-out transition the exact moment the cursor exits the boundary.

To deploy this animation on your website, verify that the core GSAP library is actively loaded, then insert this snippet into your project's custom code area right before the closing </body> tag. Structure your layout by applying the class .item-service-wrapper to your interactive rows or list items, and nest your image or media element inside them using the class .service-image-wrap (ensuring the parent row is set to position: relative and the image wrapper is set to position: absolute). You can easily customize the responsiveness of the follow effect by adjusting the duration and ease parameters within the gsap.quickTo declarations to give the tracking image a heavier or more weightless feel.
<!-- =========================================================
SERVICE HOVER IMAGE
========================================================= -->

<script>
document.addEventListener("DOMContentLoaded", () => {

  const serviceTriggers =
    document.querySelectorAll(".item-service-wrapper");

  serviceTriggers.forEach(triggerWrap => {

    const img =
      triggerWrap.querySelector(".service-image-wrap");

    if(!img) return;

    gsap.set(img,{
      autoAlpha:0,
      scale:0.8,
      xPercent:-50,
      yPercent:-50
    });

    const xTo = gsap.quickTo(img,"x",{
      duration:0.4,
      ease:"power3.out"
    });

    const yTo = gsap.quickTo(img,"y",{
      duration:0.4,
      ease:"power3.out"
    });

    triggerWrap.addEventListener("mouseenter",(e)=>{

      const rect =
        triggerWrap.getBoundingClientRect();

      xTo(e.clientX - rect.left);
      yTo(e.clientY - rect.top);

      gsap.to(img,{
        autoAlpha:1,
        scale:1,
        duration:0.35,
        ease:"power3.out"
      });

    });

    triggerWrap.addEventListener("mousemove",(e)=>{

      const rect =
        triggerWrap.getBoundingClientRect();

      xTo(e.clientX - rect.left);
      yTo(e.clientY - rect.top);

    });

    triggerWrap.addEventListener("mouseleave",()=>{

      gsap.to(img,{
        autoAlpha:0,
        scale:0.8,
        duration:0.35,
        ease:"power3.in"
      });

    });

  });

});
</script>
Lenis Smooth Scroll Integration with Dynamic Popup Scroll Lock
This high-performance script integrates the Lenis smooth scrolling engine directly into GSAP's optimized animation ecosystem while providing an intelligent scrolling lock for active web overlays. By running a continuous state check inside a frame-by-frame gsap.ticker loop, the script monitors specific interface containers for the presence of an active modal class. The instant a target overlay is opened, it instantly pauses the Lenis instance and injects highly precise pointer and wheel event listeners to freeze background page scrolling, while carefully maintaining isolated, native scroll freedom for any inner layout tracks within the active popup components.

To implement this scroll management system on your website, make sure the core GSAP library, ScrollTrigger, and Draggable plugins are loaded alongside the included Lenis script tags, then paste this snippet into your custom code block right before the closing </body> tag. Set up your layout by assigning the class .showreel-popup (with an inner container classed as .showreel-wrapper) or .menu-content-wrap to your full-screen modals or slider navigations. Simply use your interaction framework or a quick toggle script to add or remove the .open-popup class on these main elements, and the script will automatically lock the viewport or resume smooth scrolling seamlessly.
<!-- =========================================================
LENIS CSS + JS POPUP SCROLL LOCK
========================================================= -->

<link rel="stylesheet" href="https://unpkg.com/lenis@1.3.15/dist/lenis.css" />
<script src="https://unpkg.com/lenis@1.3.15/dist/lenis.min.js"></script>

<script>
window.addEventListener("load", () => {
  // 1. Explicit Core GSAP Verification and Plugin Registration
  if (typeof gsap !== "undefined") {
    gsap.registerPlugin(ScrollTrigger, Draggable);
  } else {
    return;
  }

  // 3. Initialize Smooth Scroll Engine Context
  window.lenis = new Lenis({
    duration: 1.4
  });

  // Bind ScrollTrigger updates directly to the engine scroll cycle
  window.lenis.on("scroll", ScrollTrigger.update);

  // 4. Pure GSAP Ticker Frame Loop (Replaces native requestAnimationFrame)
  gsap.ticker.add((time) => {
    window.lenis.raf(time * 1000);
  });

  // 5. Safe Element Retrieval Using GSAP Array Utility
  const popup = gsap.utils.toArray(".showreel-popup")[0];
  const menuContent = gsap.utils.toArray(".menu-content-wrap")[0];

  // Master state tracking for global scroll lock threshold
  let isScrollLocked = false;

  // Unified backdrop scrolling suppression function
  const preventBackgroundScroll = (e) => {
    // Allow internal scrolling inside the active overlays, but block the main background
    if (e.target.closest(".showreel-wrapper") || e.target.closest(".menu-content-wrap")) return;
    e.preventDefault();
  };

  // 6. Core GSAP Ticker State Polling (Tracks both Showreel and Menu states)
  gsap.ticker.add(() => {
    // Check class presence safely across execution frames
    const isShowreelOpen = popup ? popup.classList.contains("open-popup") : false;
    const isMenuOpen = menuContent ? menuContent.classList.contains("open-popup") : false;

    // Condition threshold: Lock scroll if either overlay is active
    const shouldLock = isShowreelOpen || isMenuOpen;

    // Execution Threshold: Handle Global Scroll Lock Activation
    if (shouldLock && !isScrollLocked) {
      isScrollLocked = true;
      window.lenis.stop();

      window.addEventListener("wheel", preventBackgroundScroll, { passive: false });
      window.addEventListener("touchmove", preventBackgroundScroll, { passive: false });
    } 
    // Execution Threshold: Handle Global Scroll Lock Deactivation
    else if (!shouldLock && isScrollLocked) {
      isScrollLocked = false;
      window.lenis.start();

      window.removeEventListener("wheel", preventBackgroundScroll);
      window.removeEventListener("touchmove", preventBackgroundScroll);
    }
  });
});
</script>