update home,about,solutions

This commit is contained in:
2026-06-01 20:20:14 +05:30
parent 8d74f7063e
commit a59a5e79dc
19 changed files with 3543 additions and 1381 deletions

View File

@@ -19,6 +19,11 @@ export default function AnimationProvider({ children }: { children: React.ReactN
duration: 0.8,
});
// Mobile browsers fire `resize` when the address bar shows/hides while
// scrolling. Refreshing ScrollTrigger on every one of those caused jumpy /
// re-hidden animations on phones. Ignore those spurious resizes.
ScrollTrigger.config({ ignoreMobileResize: true });
const initDecorativeBlocks = () => {
// Clean up previous block triggers to avoid duplicates
ScrollTrigger.getAll().forEach((t) => {
@@ -27,6 +32,7 @@ export default function AnimationProvider({ children }: { children: React.ReactN
}
});
const vh = window.innerHeight || document.documentElement.clientHeight;
const decorativeBlocks = document.querySelectorAll(".block-decoration");
decorativeBlocks.forEach((block) => {
ScrollTrigger.create({
@@ -50,6 +56,14 @@ export default function AnimationProvider({ children }: { children: React.ReactN
block.classList.remove("animated");
},
});
// ScrollTrigger does not fire onEnter for blocks already in view at
// creation — on larger / taller screens those stayed un-animated.
// Reveal any block already intersecting the viewport.
const r = block.getBoundingClientRect();
if (r.top < vh && r.bottom > 0) {
block.classList.add("animated");
}
});
};
@@ -83,15 +97,22 @@ export default function AnimationProvider({ children }: { children: React.ReactN
};
window.addEventListener("load", handleLoad);
// Also refresh on window resize
// Refresh on window resize — debounced so dragging the window across
// breakpoints recomputes trigger positions once it settles, instead of
// thrashing on every intermediate pixel.
let resizeTimer: ReturnType<typeof setTimeout>;
const handleResize = () => {
initDecorativeBlocks();
ScrollTrigger.refresh(true);
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
initDecorativeBlocks();
ScrollTrigger.refresh(true);
}, 200);
};
window.addEventListener("resize", handleResize);
return () => {
timeouts.forEach(clearTimeout);
clearTimeout(resizeTimer);
window.removeEventListener("load", handleLoad);
window.removeEventListener("resize", handleResize);
ScrollTrigger.getAll().forEach((t) => t.kill());

View File

@@ -9,6 +9,71 @@ if (typeof window !== "undefined") {
gsap.registerPlugin(ScrollTrigger);
}
/* ============================================================
Shared scroll-reveal trigger factory.
ScrollTrigger does NOT fire `onEnter` for elements that are ALREADY inside
the viewport when the trigger is created. On taller / larger screens more
content sits above the fold on load, so those elements stayed stuck at
opacity:0 (invisible). This was the root cause of "animations work on my
laptop but break on bigger screens / other devices".
This factory fixes it by revealing any element already intersecting the
viewport right after creation, so behaviour is identical on every screen
size. It also honours prefers-reduced-motion by revealing instantly.
============================================================ */
function createRevealTrigger(opts: {
trigger: Element;
start?: string;
show: () => void;
hide: () => void;
triggerOnce?: boolean;
}): () => void {
const { trigger, start = "top 88%", show, hide, triggerOnce = false } = opts;
// Reduced motion: reveal immediately, no scroll dependency.
if (
typeof window !== "undefined" &&
window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
) {
show();
return () => {};
}
const st = ScrollTrigger.create({
trigger,
start,
onEnter: (self) => {
show();
if (triggerOnce) self.kill();
},
onEnterBack: () => {
if (!triggerOnce) show();
},
onLeave: () => {
if (!triggerOnce) hide();
},
onLeaveBack: () => {
if (!triggerOnce) hide();
},
});
// Reveal-if-already-in-view (deferred one frame so layout is measured).
const raf = requestAnimationFrame(() => {
const r = trigger.getBoundingClientRect();
const vh = window.innerHeight || document.documentElement.clientHeight;
if (r.top < vh && r.bottom > 0) {
show();
if (triggerOnce) st.kill();
}
});
return () => {
cancelAnimationFrame(raf);
st.kill();
};
}
/* ============================================================
1. ScrollReveal
Fade-in + slide-up on scroll. The workhorse animation.
@@ -40,47 +105,19 @@ export function ScrollReveal({
gsap.set(el, { y: yOffset, x: xOffset, opacity: 0 });
const trigger = ScrollTrigger.create({
trigger: el,
start: "top 88%",
onEnter: (self) => {
gsap.to(el, {
y: 0,
x: 0,
opacity: 1,
duration,
ease: "power3.out",
delay,
overwrite: "auto",
});
if (triggerOnce) self.kill();
},
onEnterBack: () => {
if (!triggerOnce) {
gsap.to(el, {
y: 0,
x: 0,
opacity: 1,
duration,
ease: "power3.out",
delay,
overwrite: "auto",
});
}
},
onLeave: () => {
if (!triggerOnce) {
gsap.set(el, { y: yOffset, x: xOffset, opacity: 0 });
}
},
onLeaveBack: () => {
if (!triggerOnce) {
gsap.set(el, { y: yOffset, x: xOffset, opacity: 0 });
}
},
});
const show = () =>
gsap.to(el, {
y: 0,
x: 0,
opacity: 1,
duration,
ease: "power3.out",
delay,
overwrite: "auto",
});
const hide = () => gsap.set(el, { y: yOffset, x: xOffset, opacity: 0 });
return () => trigger?.kill();
return createRevealTrigger({ trigger: el, show, hide, triggerOnce });
}, [delay, duration, yOffset, xOffset, triggerOnce]);
return (
@@ -122,47 +159,19 @@ export function RevealText({
gsap.set(items, { y: "110%", opacity: 0 });
const trigger = ScrollTrigger.create({
trigger: el,
start: "top 88%",
onEnter: (self) => {
gsap.to(items, {
y: "0%",
opacity: 1,
duration,
ease: "power4.out",
stagger: type === "chars" ? 0.02 : 0.04,
delay,
overwrite: "auto",
});
if (triggerOnce) self.kill();
},
onEnterBack: () => {
if (!triggerOnce) {
gsap.to(items, {
y: "0%",
opacity: 1,
duration,
ease: "power4.out",
stagger: type === "chars" ? 0.02 : 0.04,
delay,
overwrite: "auto",
});
}
},
onLeave: () => {
if (!triggerOnce) {
gsap.set(items, { y: "110%", opacity: 0 });
}
},
onLeaveBack: () => {
if (!triggerOnce) {
gsap.set(items, { y: "110%", opacity: 0 });
}
},
});
const show = () =>
gsap.to(items, {
y: "0%",
opacity: 1,
duration,
ease: "power4.out",
stagger: type === "chars" ? 0.02 : 0.04,
delay,
overwrite: "auto",
});
const hide = () => gsap.set(items, { y: "110%", opacity: 0 });
return () => trigger?.kill();
return createRevealTrigger({ trigger: el, show, hide, triggerOnce });
}, [children, type, delay, duration, triggerOnce]);
const renderContent = () => {
@@ -290,45 +299,18 @@ export function StaggerChildren({
gsap.set(items, { y: yOffset, opacity: 0 });
const trigger = ScrollTrigger.create({
trigger: el,
start: "top 85%",
onEnter: (self) => {
gsap.to(items, {
y: 0,
opacity: 1,
duration,
ease: "power3.out",
stagger,
overwrite: "auto",
});
if (triggerOnce) self.kill();
},
onEnterBack: () => {
if (!triggerOnce) {
gsap.to(items, {
y: 0,
opacity: 1,
duration,
ease: "power3.out",
stagger,
overwrite: "auto",
});
}
},
onLeave: () => {
if (!triggerOnce) {
gsap.set(items, { y: yOffset, opacity: 0 });
}
},
onLeaveBack: () => {
if (!triggerOnce) {
gsap.set(items, { y: yOffset, opacity: 0 });
}
},
});
const show = () =>
gsap.to(items, {
y: 0,
opacity: 1,
duration,
ease: "power3.out",
stagger,
overwrite: "auto",
});
const hide = () => gsap.set(items, { y: yOffset, opacity: 0 });
return () => trigger?.kill();
return createRevealTrigger({ trigger: el, start: "top 85%", show, hide, triggerOnce });
}, [stagger, duration, yOffset, triggerOnce]);
return (
@@ -365,45 +347,18 @@ export function ScaleReveal({
gsap.set(el, { scale: 0.85, opacity: 0 });
const trigger = ScrollTrigger.create({
trigger: el,
start: "top 88%",
onEnter: (self) => {
gsap.to(el, {
scale: 1,
opacity: 1,
duration,
ease: "power3.out",
delay,
overwrite: "auto",
});
if (triggerOnce) self.kill();
},
onEnterBack: () => {
if (!triggerOnce) {
gsap.to(el, {
scale: 1,
opacity: 1,
duration,
ease: "power3.out",
delay,
overwrite: "auto",
});
}
},
onLeave: () => {
if (!triggerOnce) {
gsap.set(el, { scale: 0.85, opacity: 0 });
}
},
onLeaveBack: () => {
if (!triggerOnce) {
gsap.set(el, { scale: 0.85, opacity: 0 });
}
},
});
const show = () =>
gsap.to(el, {
scale: 1,
opacity: 1,
duration,
ease: "power3.out",
delay,
overwrite: "auto",
});
const hide = () => gsap.set(el, { scale: 0.85, opacity: 0 });
return () => trigger?.kill();
return createRevealTrigger({ trigger: el, show, hide, triggerOnce });
}, [delay, duration, triggerOnce]);
return (
@@ -443,45 +398,18 @@ export function SlideReveal({
const xStart = direction === "left" ? -60 : 60;
gsap.set(el, { x: xStart, opacity: 0 });
const trigger = ScrollTrigger.create({
trigger: el,
start: "top 88%",
onEnter: (self) => {
gsap.to(el, {
x: 0,
opacity: 1,
duration,
ease: "power3.out",
delay,
overwrite: "auto",
});
if (triggerOnce) self.kill();
},
onEnterBack: () => {
if (!triggerOnce) {
gsap.to(el, {
x: 0,
opacity: 1,
duration,
ease: "power3.out",
delay,
overwrite: "auto",
});
}
},
onLeave: () => {
if (!triggerOnce) {
gsap.set(el, { x: xStart, opacity: 0 });
}
},
onLeaveBack: () => {
if (!triggerOnce) {
gsap.set(el, { x: xStart, opacity: 0 });
}
},
});
const show = () =>
gsap.to(el, {
x: 0,
opacity: 1,
duration,
ease: "power3.out",
delay,
overwrite: "auto",
});
const hide = () => gsap.set(el, { x: xStart, opacity: 0 });
return () => trigger?.kill();
return createRevealTrigger({ trigger: el, show, hide, triggerOnce });
}, [direction, delay, duration, triggerOnce]);
return (
@@ -573,43 +501,18 @@ export function CountUp({
const el = elementRef.current;
if (!el) return;
const trigger = ScrollTrigger.create({
trigger: el,
start: "top 90%",
onEnter: (self) => {
const obj = { val: start };
gsap.to(obj, {
val: end,
duration,
ease: "power2.out",
onUpdate: () => setValue(obj.val),
});
if (triggerOnce) self.kill();
},
onEnterBack: () => {
if (!triggerOnce) {
const obj = { val: start };
gsap.to(obj, {
val: end,
duration,
ease: "power2.out",
onUpdate: () => setValue(obj.val),
});
}
},
onLeave: () => {
if (!triggerOnce) {
setValue(start);
}
},
onLeaveBack: () => {
if (!triggerOnce) {
setValue(start);
}
},
});
const show = () => {
const obj = { val: start };
gsap.to(obj, {
val: end,
duration,
ease: "power2.out",
onUpdate: () => setValue(obj.val),
});
};
const hide = () => setValue(start);
return () => trigger?.kill();
return createRevealTrigger({ trigger: el, start: "top 90%", show, hide, triggerOnce });
}, [start, end, duration, triggerOnce]);
return (