// get a random number between -100 and 100 (no snapping)
gsap.utils.random(-100, 100);

// a random number between 0 and 500 
// snapped to the closest increment of 5
gsap.utils.random(0, 500, 5);

// get a random value from an array of colors
gsap.utils.random(["red", "blue", "green"]); 
// get a reusable function 
// that will randomly choose a value between -10 and 50
var random = gsap.utils.random(-10, 50, true);

// now we can call it anytime:
console.log(random()); 
Let tl - gsap.timeline()

...

tl.to(shapes, {
 keyframes: [
  { y: () => { return gsap.utils.random(-80, -120); },
  { y: 0, scale: 0.3 }
 ],
 ease: this.eases.airtime,
 stagger: 0.15,
 }
)

playTimeline() {
 if (this.playing) return;

 this.tl.invalidate().play(0);
}
Let tl - gsap.timeline()

...

tl.to(shapes, {
 keyframes: [
  { y: () => { return gsap.utils.random(-80, -120); },
  { y: 0, scale: 0.3 }
 ],
 ease: this.eases.airtime,
 stagger: 0.15,
 }
)

playTimeline() {
 if (this.playing) return;

 this.tl.invalidate().play(0);
}
gsap.to(".class", {
  x: "random([0, 100, 200, 500])",
});
gsap.to(".class", {
  x: "random([0, 100, 200, 500])",
  repeat: -1,
  repeatRefresh: true
});
// Set up our coordinate mapping with GSAP utils!
let mapWidth;
let mapHeight;

function setMaps() {
  mapWidth = gsap.utils.mapRange(0, innerWidth, -50, 50);
  mapHeight = gsap.utils.mapRange(0, innerHeight, -50, 50);
}

window.addEventListener('resize', setMaps);

setMaps();
//returns the corresponding value in the array 
// wrapping back to the beginning when necessary)
let color = gsap.utils.wrap(["red", "green", "yellow"], 5); 

// "yellow" (index 5 maps to index 2 in a 3-element Array)

// or use a range
let num = gsap.utils.wrap(5, 10, 12); 
// 7 (12 is two more than the max of 10, 
// so it wraps around to the start and goes two up from there)
// clamp to a range between 0 and 100
var transformer = gsap.utils.pipe(
  // clamp between 0 and 100
  gsap.utils.clamp(0, 100),

  // then map to the corresponding position on the width of the screen
  gsap.utils.mapRange(0, 100, 0, window.innerWidth),

  // then snap to the closest increment of 20
  gsap.utils.snap(20)
);

// now we feed one value in
// it gets run through ALL those transformations!:
transformer(25.874);
gsap.to(el, {
  x: 400,
  yoyo: true,
  repeat: 1
})
// create
let mm = gsap.matchMedia();

// add a media query. When it matches, the associated function will run
mm.add("(min-width: 800px)", () => {

  // this setup code only runs when viewport is at least 800px wide
  gsap.to(...);
  gsap.from(...);
  ScrollTrigger.create({...});

  return () => { // optional
    // custom cleanup code here (runs when it STOPS matching)
  };
});

// later, if we need to revert all the animations/ScrollTriggers...
mm.revert();
let mm = gsap.matchMedia();

mm.add("(min-width: 800px)", () => {
  // desktop setup code here...
});

mm.add("(max-width: 799px)", () => {
  // mobile setup code here...
});
let mm = gsap.matchMedia(),
  breakPoint = 800;

mm.add(
  {
    // set up any number of arbitrarily-named conditions. 
    // The function below will be called when ANY of them match.
    isDesktop: `(min-width: ${breakPoint}px)`,
    isMobile: `(max-width: ${breakPoint - 1}px)`,
    reduceMotion: "(prefers-reduced-motion: reduce)",
  },
  (context) => {
    // context.conditions has a boolean property 
    //  for each condition defined above indicating if it's matched 
    let { isDesktop, isMobile, reduceMotion } = context.conditions;

    gsap.to(".box", {
      rotation: isDesktop ? 360 : 180, 
      // spin further if desktop
      duration: reduceMotion ? 0 : 2,
      // skip to the end if prefers-reduced-motion
    });

    return () => {
    };
  }
);
let mm = gsap.matchMedia(),
  breakPoint = 800;

mm.add(
  {
    // set up any number of arbitrarily-named conditions. 
    // The function below will be called when ANY of them match.
    isDesktop: `(min-width: ${breakPoint}px)`,
    isMobile: `(max-width: ${breakPoint - 1}px)`,
    reduceMotion: "(prefers-reduced-motion: reduce)",
  },
  (context) => {
    // context.conditions has a boolean property 
    //  for each condition defined above indicating if it's matched 
    let { isDesktop, isMobile, reduceMotion } = context.conditions;

    gsap.to(".box", {
      rotation: isDesktop ? 360 : 180, 
      // spin further if desktop
      duration: reduceMotion ? 0 : 2,
      // skip to the end if prefers-reduced-motion
    });

    return () => {
    };
  }
);
let mm = gsap.matchMedia()

// this user has specified "reduced motion"
mm.add("(prefers-reduced-motion: reduce)", (context) => {
  gsap.globalTimeline.timeScale(200);
});
@media screen and (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

el.onmouseover = function () {
    gsap.to(timeline, {
        timeScale: 0.1
    });
}

el.onmouseout = function () {
    gsap.to(timeline, {
        timeScale: 1
    });
}
let scrollTween = gsap.to(".container", {
  xPercent: -100 * (sections.length - 1),
  ease: "none", // <-- IMPORTANT!
  scrollTrigger: {
    trigger: ".container",
    start: "top top",
    end: "+=3000",
    pin: true,
    scrub: 0.5
  }
});


gsap.to(".box", {
  y: -100,
  scrollTrigger: {
    trigger: ".box",
    start: "left center",
    containerAnimation: scrollTween, // <-- NEW!!
  }
});
const tl = gsap.timeline({ paused: true });

let exitTime = 0; // exit time

tl.from(".tooltip", {
  scale: 0.2,
  xPercent: -70,
});
tl.addPause();
exitTime = tl.duration(); // assign the exit time
tl.to(".tooltip", {
  yPercent: 400,
  rotation: "random([-90, 90, -45, 45, -180, 180])",
});

btn.addEventListener("mouseenter", () => {
  if (tl.time() < exitTime) {
    tl.play();
  } else {
    tl.restart();
  }
});

btn.addEventListener("mouseleave", () => {
  if (tl.time() < exitTime) {
    tl.reverse();
  } else {
    tl.invalidate().play();
  }
});