Typography

Semantic Headings
Typography
Semantic H1

Progress and possibility

Semantic H2

Progress and possibility

Semantic H3

Progress and possibility

Semantic H4

Progress and possibility

Semantic H5
Progress and possibility
Semantic H6
Progress and possibility
Heading Styles
Typography
heading
heading="display"

Progress and possibility

heading
heading="4xl"

Progress and possibility

heading
heading="3xl"

Progress and possibility

heading
heading="2xl"

Progress and possibility

heading
heading="xl"

Progress and possibility

heading
heading="lg"

Progress and possibility

heading
heading="md"

Progress and possibility

heading
heading="sm"

Progress and possibility

heading
heading="xs"

Progress and possibility

Paragraph Styles
Typography
paragraph
paragraph="lg"

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

paragraph
paragraph="md"

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

paragraph

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

paragraph
paragraph="sm"

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

Rich Text
Typography
rich-text

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
Heading 6

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.

Block quote

Ordered list

  1. Item 1
  2. Item 2
  3. Item 3

Unordered list

  • Item A
  • Item B
  • Item C
Text link

Bold text

Emphasis

Superscript

Subscript

All Block Quotes
Block Quote
All Unordered Lists
All List Items
  • This is some text inside of a div block.
  • This is some text inside of a div block.
  • This is some text inside of a div block.
Text Links
Typography
All Links
Text Link

Color

Primary
Color
Var:
--primary-yellow
Prop:
yellow
Value:
#CBFF54
Var:
--primary-pink
Prop:
pink
Value:
#F784E3
Neutral
Color
Var:
--secondary-dark
Prop:
dark
Value:
#17312F
Var:
--neutral-slate
Prop:
slate
Value:
#6A7A7F
Var:
--neutral-tan
Prop:
tan
Value:
#777D67
Var:
--neutral-light
Prop:
light
Value:
#EEF1EA
Shades
Color
Var:
--shade-dark-50
Prop:
dark-50
Value:
#17312F
Var:
--shade-light-5
Prop:
light-5
Value:
#6A7A7F
Var:
--shade-light-10
Prop:
light-10
Value:
#777D67
Utility
Color
Var:
--utility-error
Prop:
error
Value:
#72F0E9
Var:
--utility-focus
Prop:
focus
Value:
#72F0E9
Var:
--utility-default
Prop:
default
Value:
#72F0E9

Elements

Element / Heading
Elements

Progress and possibility

Heading:
h1
h2
h3
h4
h5
h6
Heading Size:
xs
sm
md
lg
xl
2xl
3xl
4xl
display
Element / Icon
Elements
Icon Select:
#youtube
#twitter
#linkedin
#twitch
#instagram
#facebook
Icon Size:
sm
md
lg
Element / Text Button
Elements
Icon Size:
sm
md
lg
Element / Text Label
Elements
How we work
Icon Size:
sm
md
lg
Element / Button
Elements
Button Variant:
base
small
Button Color:
-
pink
Element / Legacy Button
Elements
apply Now
apply Now
Element / Service Card
Elements

Brand Sprint

A rapid, high-impact sprint to craft your brand’s foundation—strategy, identity, and messaging—so it’s instantly recognizable and built for growth.

Element / Copy Block
Elements
How we work

Your brand is your story our process brings it to life.

A high-impact sprint to craft your brand’s foundation—strategy, identity, and messaging—so you launch with clarity, confidence, and a brand that stands out. Designed for speed and impact, our sprint gets you from zero to a market-ready brand in weeks, not months.

Element / Stat Block
Elements
10x
Top agencies creative output
10x
Top agencies creative output
10x
Top agencies creative output
10x
Top agencies creative output
Element / Field
Elements
Not a valid input
Not a valid input
Not a valid input
Not a valid input
Not a valid input
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Components

Section / Empty
Components
Section / Gallery Masthead
Components
/*-----------------------------------------------------------*/
/* Gallery Masthead                                          */
/*-----------------------------------------------------------*/

function initMarqueeScrollDirection() {
  document.querySelectorAll('[data-marquee-scroll-direction-target]').forEach((marquee) => {
    // Query marquee elements
    const marqueeContent = marquee.querySelector('[data-marquee-collection-target]');
    const marqueeScroll = marquee.querySelector('[data-marquee-scroll-target]');
    if (!marqueeContent || !marqueeScroll) return;

    // Get data attributes
    let { marqueeSpeed: speed, marqueeDirection: direction, marqueeDuplicate: duplicate, marqueeScrollSpeed: scrollSpeed } = marquee.dataset;

    // Normalize direction (treat "left" as "up" and "right" as "down")
    if (direction === "left") direction = "up";
    if (direction === "right") direction = "down";

    // Convert data attributes to usable types
    const marqueeSpeedAttr = parseFloat(speed);
    const marqueeDirectionAttr = direction === 'down' ? 1 : -1; // 1 for down, -1 for up
    const duplicateAmount = parseInt(duplicate || 0);
    const scrollSpeedAttr = parseFloat(scrollSpeed);
    const speedMultiplier = window.innerWidth < 479 ? 0.25 : window.innerWidth < 991 ? 0.5 : 1;

    let marqueeSpeed = marqueeSpeedAttr * (marqueeContent.offsetHeight / window.innerHeight) * speedMultiplier;

    // Precompute styles for the scroll container
    marqueeScroll.style.marginTop = `${scrollSpeedAttr * -1}%`;
    marqueeScroll.style.height = `${(scrollSpeedAttr * 2) + 100}%`;

    // Duplicate marquee content for seamless looping
    if (duplicateAmount > 0) {
      const fragment = document.createDocumentFragment();
      for (let i = 0; i < duplicateAmount; i++) {
        fragment.appendChild(marqueeContent.cloneNode(true));
      }
      marqueeScroll.appendChild(fragment);
    }

    // GSAP animation for vertical marquee movement
    const marqueeItems = marquee.querySelectorAll('[data-marquee-collection-target]');
    const animation = gsap.to(marqueeItems, {
      yPercent: -100, // Move completely out of view vertically
      repeat: -1,
      duration: marqueeSpeed,
      ease: 'linear'
    }).totalProgress(0.5);

    // Initialize marquee in the correct direction
    gsap.set(marqueeItems, { yPercent: marqueeDirectionAttr === 1 ? 100 : -100 });
    animation.timeScale(marqueeDirectionAttr); // Set correct direction
    animation.play(); // Start animation immediately

    // Set initial marquee status
    marquee.setAttribute('data-marquee-status', 'normal');

    // ScrollTrigger logic for direction inversion
    ScrollTrigger.create({
      trigger: marquee,
      start: 'top bottom',
      end: 'bottom top',
      onUpdate: (self) => {
        const isInverted = self.direction === 1; // Scrolling down
        const currentDirection = isInverted ? -marqueeDirectionAttr : marqueeDirectionAttr;

        // Update animation direction and marquee status
        animation.timeScale(currentDirection);
        marquee.setAttribute('data-marquee-status', isInverted ? 'normal' : 'inverted');
      }
    });

    // Extra speed effect on scroll
    const tl = gsap.timeline({
      scrollTrigger: {
        trigger: marquee,
        start: '0% 100%',
        end: '100% 0%',
        scrub: 0
      }
    });

    const scrollStart = marqueeDirectionAttr === -1 ? scrollSpeedAttr : -scrollSpeedAttr;
    const scrollEnd = -scrollStart;

    tl.fromTo(marqueeScroll, { y: `${scrollStart}vh` }, { y: `${scrollEnd}vh`, ease: 'none' });
  });
}

initMarqueeScrollDirection();

function cycleMarqueeWords() {
  const track = document.querySelector(".marquee_track");
  const words = ["brands", "products"];
  let index = 0;

  if (!track) return;

  function animateWordChange() {
    const currentWord = track.querySelector(".marquee_item");

    // Clone the current word but don't add it yet
    const newWord = currentWord.cloneNode(true);
    newWord.textContent = words[(index + 1) % words.length]; // Set next word
    gsap.set(newWord, { opacity: 0, x: 50, position: "absolute" });

    // Animate the old word moving out first
    gsap.to(currentWord, {
      x: -50,
      opacity: 0,
      duration: 0.8,
      ease: "power2.inOut",
      onComplete: () => {
        // Once the old word has moved out, add the new word to the DOM
        track.appendChild(newWord);

        // Animate the new word into place
        gsap.to(newWord, {
          x: 0,
          opacity: 1,
          duration: 0.8,
          ease: "power2.inOut",
          onComplete: () => {
            // Remove the old word **only after the new word has animated in**
            track.removeChild(currentWord);
            newWord.style.position = "relative";

            // Update index for the next cycle
            index = (index + 1) % words.length;
          },
        });
      },
    });
  }

  // Run animation every 3 seconds
  setInterval(animateWordChange, 3000);
}

setTimeout(cycleMarqueeWords, 3000);
Section / AI Upskill Masthead
Components
How we work

Your brand is your story our process brings it to life.

A high-impact sprint to craft your brand’s foundation—strategy, identity, and messaging—so you launch with clarity, confidence, and a brand that stands out. Designed for speed and impact, our sprint gets you from zero to a market-ready brand in weeks, not months.

{{Target Audience}} {{Core Problem}} {{Unique Solution}}
/*-----------------------------------------------------------*/
/* AI Upskill Masthead                                       */
/*-----------------------------------------------------------*/

function initAiMasthead() {
    const canvas = document.querySelector('[data-sequence-canvas]');
    if (!canvas) return;

    const ctx = canvas.getContext('2d');
    const baseUrl = canvas.getAttribute('data-sequence-url');
    const totalFrames = parseInt(canvas.getAttribute('data-sequence-frames')) || 278;
    const fps = 30;
    const images = [];
    let frame = 0;
    let loaded = 0;

    const cacheBuster = `v=${Date.now()}`;

    // Setup DPR scaling
    const dpr = window.devicePixelRatio || 1;
    const cssWidth = canvas.clientWidth;
    const cssHeight = canvas.clientHeight;
    canvas.width = cssWidth * dpr;
    canvas.height = cssHeight * dpr;
    ctx.scale(dpr, dpr);

    // 🔥 Load first frame FAST
    const firstFrame = new Image();
    firstFrame.src = `${baseUrl}00000.webp?${cacheBuster}`;
    firstFrame.onload = () => {
        drawCoverFrame(firstFrame);
    };

    function drawCoverFrame(img) {
        const canvasRatio = cssWidth / cssHeight;
        const imgRatio = img.naturalWidth / img.naturalHeight;

        let drawWidth, drawHeight, offsetX, offsetY;

        if (canvasRatio > imgRatio) {
            drawWidth = cssWidth;
            drawHeight = cssWidth / imgRatio;
            offsetX = 0;
            offsetY = -(drawHeight - cssHeight) / 2;
        } else {
            drawWidth = cssHeight * imgRatio;
            drawHeight = cssHeight;
            offsetX = -(drawWidth - cssWidth) / 2;
            offsetY = 0;
        }

        ctx.clearRect(0, 0, cssWidth, cssHeight);
        ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
    }

    // Preload ALL frames (including first one again for consistency)
    for (let i = 0; i <= totalFrames; i++) {
        const frameNumber = String(i).padStart(5, '0');
        const img = new Image();
        img.src = `${baseUrl}${frameNumber}.webp?${cacheBuster}`;
        img.onload = () => {
            loaded++;
            if (loaded === totalFrames + 1) {
                loop();
            }
        };
        images.push(img);
    }

    function loop() {
        ctx.clearRect(0, 0, cssWidth, cssHeight);

        const img = images[frame];
        const canvasRatio = cssWidth / cssHeight;
        const imgRatio = img.naturalWidth / img.naturalHeight;

        let drawWidth, drawHeight, offsetX, offsetY;

        if (canvasRatio > imgRatio) {
            drawWidth = cssWidth;
            drawHeight = cssWidth / imgRatio;
            offsetX = 0;
            offsetY = -(drawHeight - cssHeight) / 2;
        } else {
            drawWidth = cssHeight * imgRatio;
            drawHeight = cssHeight;
            offsetX = -(drawWidth - cssWidth) / 2;
            offsetY = 0;
        }

        ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
        frame = (frame + 1) % (totalFrames + 1);
        setTimeout(loop, 1000 / fps);
    }
      // Hover .ai-upskill_gen-img
    const cards = document.querySelectorAll('.ai-upskill_masthead-wrapper .ai-upskill_gen-img');
    cards.forEach((card) => {
        card.addEventListener('mousemove', (e) => {
            const rect = card.getBoundingClientRect();
            const x = e.clientX - rect.left;
            const y = e.clientY - rect.top;
            const centerX = rect.width / 2;
            const centerY = rect.height / 2;
            const deltaX = (x - centerX) / centerX;
            const deltaY = (y - centerY) / centerY;

            gsap.to(card, {
                duration: 0.4,
                rotateX: deltaY * -10, // adjust intensity here
                rotateY: deltaX * 10,
                transformPerspective: 800,
                ease: "power2.out"
            });
        });

        card.addEventListener('mouseleave', () => {
            gsap.to(card, {
                duration: 0.6,
                rotateX: 0,
                rotateY: 0,
                ease: "power3.out"
            });
        });
    });
}


initAiMasthead();
Section / Split Section
Components
Section / Services Cards
Components
[-100]
[-75]
[-25]
[25]
[75]
[100]

Brand Sprint

A rapid, high-impact sprint to craft your brand’s foundation—strategy, identity, and messaging—so it’s instantly recognizable and built for growth.

Product Sprint

From concept to reality, we help you define, prototype, and validate your product—ensuring perfect market fit before you build.

AI Upskilling

Most teams waste months figuring out AI. Yours won't. Upskilling sprints give your people practical AI skills they'll use every day. No fluff, no theory — just valuable, practical tools.


human x ai


Research


Ideate


Generate


human x ai


Research


Ideate


Generate

Generate
How can I transform my business with AI?
[-100]
[-75]
[-25]
[25]
[75]
[100]
Section / Testimonials
Components

Learn more


/*-----------------------------------------------------------*/
/* Testimonail Slider                                        */
/*-----------------------------------------------------------*/


$(".tst-slider_component").each(function (index) {
  const swiper = new Swiper($(this).find(".swiper")[0], {
    // Default parameters
    slidesPerView: 1.12,
    spaceBetween: 18,
    speed: 800,
    allowTouchMove: true,
    followFinger: true,
    freeMode: false,
    loop: true,
    rewind: false,
    keyboard: {
      enabled: true,
      onlyInViewport: true
    },
    navigation: {
      nextEl: $(this).find(".swiper-next")[0],
      prevEl: $(this).find(".swiper-prev")[0],
      disabledClass: "is-disabled"
    },
    scrollbar: {
      el: $(this).find(".swiper-drag-wrapper")[0],
      draggable: true,
      dragClass: "swiper-drag",
      snapOnRelease: true
    },

    // Breakpoints
    breakpoints: {
      480: {
        slidesPerView: 1.12,
        spaceBetween: 26
      },
      // when window width is >= 768px
      768: {
        slidesPerView: 1.12,
        spaceBetween: 26
      },
      // when window width is >= 992px
      992: {
        slidesPerView: 2.12,
        spaceBetween: 26
      }
    }
  });
});


  let cursorItem = document.querySelector(".cursor");
  let cursorParagraph = cursorItem.querySelector("p");
  let targets = document.querySelectorAll("[data-cursor]");
  let xOffset = 6;
  let yOffset = 140;
  let cursorIsOnRight = false;
  let currentTarget = null;
  let lastText = '';

  // Position cursor relative to actual cursor position on page load
  gsap.set(cursorItem, { xPercent: xOffset, yPercent: yOffset });

  // Use GSAP quick.to for a more performative tween on the cursor
  let xTo = gsap.quickTo(cursorItem, "x", { ease: "power3" });
  let yTo = gsap.quickTo(cursorItem, "y", { ease: "power3" });

  // Function to get the width of the cursor element including a buffer
  const getCursorEdgeThreshold = () => {
    return cursorItem.offsetWidth + 16; // Cursor width + 16px margin
  };

  // On mousemove, call the quickTo functions to the actual cursor position
  window.addEventListener("mousemove", e => {
    let windowWidth = window.innerWidth;
    let windowHeight = window.innerHeight;
    let scrollY = window.scrollY;
    let cursorX = e.clientX;
    let cursorY = e.clientY + scrollY; // Adjust cursorY to account for scroll

    // Default offsets
    let xPercent = xOffset;
    let yPercent = yOffset;

    // Adjust X offset dynamically based on cursor width
    let cursorEdgeThreshold = getCursorEdgeThreshold();
    if (cursorX > windowWidth - cursorEdgeThreshold) {
      cursorIsOnRight = true;
      xPercent = -100;
    } else {
      cursorIsOnRight = false;
    }

    // Adjust Y offset if in the bottom 10% of the current viewport
    if (cursorY > scrollY + windowHeight * 0.9) {
      yPercent = -120;
    }

    if (currentTarget) {
      let newText = currentTarget.getAttribute("data-cursor");
      if (newText !== lastText) { // Only update if the text is different
        cursorParagraph.innerHTML = newText;
        lastText = newText;

        // Recalculate edge awareness whenever the text changes
        cursorEdgeThreshold = getCursorEdgeThreshold();
      }
    }

    gsap.to(cursorItem, { xPercent: xPercent, yPercent: yPercent, duration: 0.9, ease: "power3" });
    xTo(cursorX);
    yTo(cursorY - scrollY);
  });

  // Add a mouse enter listener for each link that has a data-cursor attribute
  targets.forEach(target => {
    target.addEventListener("mouseenter", () => {
      currentTarget = target; // Set the current target

      let newText = target.getAttribute("data-cursor");

      // Update only if the text changes
      if (newText !== lastText) {
        cursorParagraph.innerHTML = newText;
        lastText = newText;

        // Recalculate edge awareness whenever the text changes
        let cursorEdgeThreshold = getCursorEdgeThreshold();
      }
    });
  });
Section / Scramble Text
Components

Organizations that tap into generative AI and workflow automation are seeing a 4x return on every dollar spent

/*-----------------------------------------------------------*/
/* Scramble Text                                             */
/*-----------------------------------------------------------*/

gsap.registerPlugin(ScrambleTextPlugin)
gsap.set('[animate-txt="scramble"]', { visibility: "visible" });

function initScrambleOnScroll(){
  let targets = document.querySelectorAll('[animate-txt="scramble"]')
  
  targets.forEach((target) => {
  	// Used this attribute to showcase a different character scramble, can be replaced with many scenarios
    let isAlternative = target.hasAttribute("data-scramble-alt")
    
    let split = new SplitText(target, {
      type: "words, chars",
      wordsClass: "word",
      charsClass: "char"
    });
    
    gsap.to(split.words, {
      duration: 1.4,
      stagger: 0.015,
      scrambleText: {
        text: "{original}",
       chars: '←©→↓↑®↖℗↗↘ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#',
        speed: 0.45,
      },
      scrollTrigger: {
        trigger: target,
        start: "top 80%",
        once: true
      },
      // Once animation is done, revert the split to reduce DOM size
      onComplete: () => split.revert()
    })   
  }) 
}

initScrambleOnScroll();
Section / Main CTA
Components
Work with us
Find your resonance.

We deliver in weeks, not months. You work directly with experts, not layers of junior staff. We blend world-class design with AI expertise to build brands and products that don’t just exist—they resonate.

/*-----------------------------------------------------------*/
/* Main CTA                                                  */
/*-----------------------------------------------------------*/

function initImageTrail(config = {}) {

  // config + defaults
  const options = {
    minWidth: config.minWidth ?? 992,
    moveDistance: config.moveDistance ?? 15,
    stopDuration: config.stopDuration ?? 300,
    trailLength: config.trailLength ?? 5
  };

  const wrapper = document.querySelector('[data-trail="wrapper"]');
  
  if (!wrapper || window.innerWidth < options.minWidth) {
    return;
  }
  
  // State management
  const state = {
    trailInterval: null,
    globalIndex: 0,
    last: { x: 0, y: 0 },
    trailImageTimestamps: new Map(),
    trailImages: Array.from(document.querySelectorAll('[data-trail="item"]')),
    isActive: false
  };

  // Utility functions
  const MathUtils = {
    lerp: (a, b, n) => (1 - n) * a + n * b,
    distance: (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1)
  };

  function getRelativeCoordinates(e, rect) {
    return {
      x: e.clientX - rect.left,
      y: e.clientY - rect.top
    };
  }

  function activate(trailImage, x, y) {
    if (!trailImage) return;

    const rect = trailImage.getBoundingClientRect();
    const styles = {
      left: `${x - rect.width / 2}px`,
      top: `${y - rect.height / 2}px`,
      zIndex: state.globalIndex,
      display: 'block'
    };

    Object.assign(trailImage.style, styles);
    state.trailImageTimestamps.set(trailImage, Date.now());

	// Here, animate how the images will appear!
    gsap.fromTo(
      trailImage,
      { autoAlpha: 0, scale: 0.8 },
      {
        scale: 1,
        autoAlpha: 1,
        duration: 0.2,
        overwrite: true
      }
    );

    state.last = { x, y };
  }

  function fadeOutTrailImage(trailImage) {
    if (!trailImage) return;
		
    // Here, animate how the images will disappear!
    gsap.to(trailImage, {
      opacity: 0,
      scale: 0.2,
      duration: 0.8,
      ease: "expo.out",
      onComplete: () => {
        gsap.set(trailImage, { autoAlpha: 0 });
      }
    });
  }

  function handleOnMove(e) {
    if (!state.isActive) return;

    const rectWrapper = wrapper.getBoundingClientRect();
    const { x: relativeX, y: relativeY } = getRelativeCoordinates(e, rectWrapper);
    
    const distanceFromLast = MathUtils.distance(
      relativeX, 
      relativeY, 
      state.last.x, 
      state.last.y
    );

    if (distanceFromLast > window.innerWidth / options.moveDistance) {
      const lead = state.trailImages[state.globalIndex % state.trailImages.length];
      const tail = state.trailImages[(state.globalIndex - options.trailLength) % state.trailImages.length];

      activate(lead, relativeX, relativeY);
      fadeOutTrailImage(tail);
      state.globalIndex++;
    }
  }

  function cleanupTrailImages() {
    const currentTime = Date.now();
    for (const [trailImage, timestamp] of state.trailImageTimestamps.entries()) {
      if (currentTime - timestamp > options.stopDuration) {
        fadeOutTrailImage(trailImage);
        state.trailImageTimestamps.delete(trailImage);
      }
    }
  }

  function startTrail() {
    if (state.isActive) return;
    
    state.isActive = true;
    wrapper.addEventListener("mousemove", handleOnMove);
    state.trailInterval = setInterval(cleanupTrailImages, 100);
  }

  function stopTrail() {
    if (!state.isActive) return;
    
    state.isActive = false;
    wrapper.removeEventListener("mousemove", handleOnMove);
    clearInterval(state.trailInterval);
    state.trailInterval = null;
    
    // Clean up remaining trail images
    state.trailImages.forEach(fadeOutTrailImage);
    state.trailImageTimestamps.clear();
  }

  // Initialize ScrollTrigger
  ScrollTrigger.create({
    trigger: wrapper,
    start: "top bottom",
    end: "bottom top",
    onEnter: startTrail,
    onEnterBack: startTrail,
    onLeave: stopTrail,
    onLeaveBack: stopTrail
  });

  // Clean up on window resize
  const handleResize = () => {
    if (window.innerWidth < options.minWidth && state.isActive) {
      stopTrail();
    } else if (window.innerWidth >= options.minWidth && !state.isActive) {
      startTrail();
    }
  };

  window.addEventListener('resize', handleResize);

  return () => {
    stopTrail();
    window.removeEventListener('resize', handleResize);
  };
}


  const imageTrail = initImageTrail({
    minWidth: 992,
    moveDistance: 15,
    stopDuration: 350,
    trailLength: 8
  });
Section / Full Width Video
Components
[SHOW REEL]
[-75]
[-25]
[25]
[75]
[100]
[-100]
[-75]
[-25]
[25]
[75]
[BRAND]
/*-----------------------------------------------------------*/
/* FW Video                                                  */
/*-----------------------------------------------------------*/
// Initialize Full Width Video Function
function initFullWidthVideo() {
  document.querySelectorAll(".fw-video_wrapper").forEach(wrapper => {
    const playButton = wrapper.querySelector("[data-video-play]");
    const video = wrapper.querySelector(".fw-video");
    
    if (!playButton || !video) return;

    playButton.addEventListener("click", () => {
      if (playButton.classList.contains("is-playing")) {
        playButton.classList.remove("is-playing");
        video.muted = true;
        video.currentTime = 0;
        video.play();
      } else {
        playButton.classList.add("is-playing");
        video.muted = false;
        video.currentTime = 0;
        video.play();
      }
    });
  });
}

// Call the function to initialize the functionality
initFullWidthVideo();
Section / Grid Grow
Components
/*-----------------------------------------------------------*/
/* Grid Grow                                                 */
/*-----------------------------------------------------------*/
function initGridGrow() {
    const grids = document.querySelectorAll('.bg-grid');

    grids.forEach((grid) => {
        const growItems = grid.querySelectorAll('[data-grid-grow]');

        growItems.forEach((item) => {
            const growValue = parseFloat(item.getAttribute('data-grid-grow')) || 1;

            // Initial height to 0
            gsap.set(item, { height: '0rem' });

            // Create ScrollTrigger
            gsap.to(item, {
                height: `${growValue}rem`,
                ease: 'power2.out',
                scrollTrigger: {
                    trigger: grid,
                    start: 'top 80%',
                    end: 'bottom 70%',
                    scrub: true
                }
            });
        });
    });
}

initGridGrow();
Section / Logo Wall Marquee
Components
/*-----------------------------------------------------------*/
/* Logo Wall                                                 */
/*-----------------------------------------------------------*/

function initLogoWall() {
  document.querySelectorAll('[data-marquee-scroll-direction-target]').forEach((marquee) => {
    // Query marquee elements
    const marqueeContent = marquee.querySelector('[data-marquee-collection-target]');
    const marqueeScroll = marquee.querySelector('[data-marquee-scroll-target]');
    if (!marqueeContent || !marqueeScroll) return;

    // Get data attributes
    const { marqueeSpeed: speed, marqueeDirection: direction, marqueeDuplicate: duplicate, marqueeScrollSpeed: scrollSpeed } = marquee.dataset;

    // Convert data attributes to usable types
    const marqueeSpeedAttr = parseFloat(speed);
    const marqueeDirectionAttr = direction === 'right' ? 1 : -1; // 1 for right, -1 for left
    const duplicateAmount = parseInt(duplicate || 0);
    const scrollSpeedAttr = parseFloat(scrollSpeed);
    const speedMultiplier = window.innerWidth < 479 ? 0.25 : window.innerWidth < 991 ? 0.5 : 1;

    let marqueeSpeed = marqueeSpeedAttr * (marqueeContent.offsetWidth / window.innerWidth) * speedMultiplier;

    // Precompute styles for the scroll container
    marqueeScroll.style.marginLeft = `${scrollSpeedAttr * -1}%`;
    marqueeScroll.style.width = `${(scrollSpeedAttr * 2) + 100}%`;

    // Duplicate marquee content
    if (duplicateAmount > 0) {
      const fragment = document.createDocumentFragment();
      for (let i = 0; i < duplicateAmount; i++) {
        fragment.appendChild(marqueeContent.cloneNode(true));
      }
      marqueeScroll.appendChild(fragment);
    }

    // GSAP animation for marquee content
    const marqueeItems = marquee.querySelectorAll('[data-marquee-collection-target]');
    const animation = gsap.to(marqueeItems, {
      xPercent: -100, // Move completely out of view
      repeat: -1,
      duration: marqueeSpeed,
      ease: 'linear'
    }).totalProgress(0.5);

    // Initialize marquee in the correct direction
    gsap.set(marqueeItems, { xPercent: marqueeDirectionAttr === 1 ? 100 : -100 });
    animation.timeScale(marqueeDirectionAttr); // Set correct direction
    animation.play(); // Start animation immediately

    // Set initial marquee status
    marquee.setAttribute('data-marquee-status', 'normal');

    // ScrollTrigger logic for direction inversion
    ScrollTrigger.create({
      trigger: marquee,
      start: 'top bottom',
      end: 'bottom top',
      onUpdate: (self) => {
        const isInverted = self.direction === 1; // Scrolling down
        const currentDirection = isInverted ? -marqueeDirectionAttr : marqueeDirectionAttr;

        // Update animation direction and marquee status
        animation.timeScale(currentDirection);
        marquee.setAttribute('data-marquee-status', isInverted ? 'normal' : 'inverted');
      }
    });

    // Extra speed effect on scroll
    const tl = gsap.timeline({
      scrollTrigger: {
        trigger: marquee,
        start: '0% 100%',
        end: '100% 0%',
        scrub: 0
      }
    });

    const scrollStart = marqueeDirectionAttr === -1 ? scrollSpeedAttr : -scrollSpeedAttr;
    const scrollEnd = -scrollStart;

    tl.fromTo(marqueeScroll, { x: `${scrollStart}vw` }, { x: `${scrollEnd}vw`, ease: 'none' });
  });
}

initLogoWall();
Section / Nav Padding
Components
Global / Footer
Components
/*-----------------------------------------------------------*/
/* Footer                                                    */
/*-----------------------------------------------------------*/

// Footer Logo
gsap.from('.footer-logo path', {
  y: 100,
  duration: 1,
  stagger: {
    amount: 0.5,
    from: "start"
  },
  ease: "main",
  scrollTrigger: {
    trigger: ".footer_container",
    start: "bottom+=-20% bottom",
    toggleActions: "play none none reverse"
  }
});

//Footer Current Year
var spanYear = document.querySelector('[data-get-year]');

if (spanYear) {
  var currentYear = new Date().getFullYear();
  spanYear.textContent = currentYear;
};
Global / Nav
Components
/*-----------------------------------------------------------*/
/* Navigation (Legacy)                                       */
/*-----------------------------------------------------------*/

function initializeNavMenu() {
  var nav = document.querySelector('.nav');
  if (!nav) return; // Stop execution if nav is not found

  // Get the button
  var menuButton = document.querySelector('.nav-menu_btn');
  
  if (menuButton) {
    menuButton.addEventListener('click', function () {
      var navMenu = menuButton.parentElement.querySelector('.nav-menu');
      if (!navMenu) return;
      
      navMenu.classList.toggle('is-open');
      
      var menuLines = menuButton.querySelectorAll('.menu-line');
      menuLines.forEach(function (line) {
        line.classList.toggle('is-open');
      });
    });
  }
  
  // Dropdown button 
  let dropdowns = document.querySelectorAll('.nav-dropdown');
  
  // Loop through each .nav-dropdown element
  dropdowns.forEach(function (dropdown) {
    let wrapper = dropdown.querySelector('.nav-dropdown_wrapper');
    if (!wrapper) return;
    
    dropdown.addEventListener('mouseover', function () {
      dropdown.classList.add('is-open');
      wrapper.classList.add('is-open');
    });

    dropdown.addEventListener('mouseout', function () {
      dropdown.classList.remove('is-open');
      wrapper.classList.remove('is-open');
    });
  });
  
  // Get navigation height
  var navHeight = nav.offsetHeight;
  document.documentElement.style.setProperty('--nav-height', navHeight + 'px');
}

// Run the function to initialize the navigation menu
initializeNavMenu();
Global / Nav (Legacy)
Components
/*-----------------------------------------------------------*/
/* Navigation (Legacy)                                       */
/*-----------------------------------------------------------*/

function initializeNavMenu() {
  var nav = document.querySelector('.nav');
  if (!nav) return; // Stop execution if nav is not found

  // Get the button
  var menuButton = document.querySelector('.nav-menu_btn');
  
  if (menuButton) {
    menuButton.addEventListener('click', function () {
      var navMenu = menuButton.parentElement.querySelector('.nav-menu');
      if (!navMenu) return;
      
      navMenu.classList.toggle('is-open');
      
      var menuLines = menuButton.querySelectorAll('.menu-line');
      menuLines.forEach(function (line) {
        line.classList.toggle('is-open');
      });
    });
  }
  
  // Dropdown button 
  let dropdowns = document.querySelectorAll('.nav-dropdown');
  
  // Loop through each .nav-dropdown element
  dropdowns.forEach(function (dropdown) {
    let wrapper = dropdown.querySelector('.nav-dropdown_wrapper');
    if (!wrapper) return;
    
    dropdown.addEventListener('mouseover', function () {
      dropdown.classList.add('is-open');
      wrapper.classList.add('is-open');
    });

    dropdown.addEventListener('mouseout', function () {
      dropdown.classList.remove('is-open');
      wrapper.classList.remove('is-open');
    });
  });
  
  // Get navigation height
  var navHeight = nav.offsetHeight;
  document.documentElement.style.setProperty('--nav-height', navHeight + 'px');
}

// Run the function to initialize the navigation menu
initializeNavMenu();