Files
ask-mutual-fund/js/main.js
unknown 96dcb4a26d
All checks were successful
ask-mutual-fund Actions Workflow / test (push) Successful in 15s
fixed card stacking animation
2025-10-30 14:44:13 +05:30

673 lines
18 KiB
JavaScript

// ===============================
// ✅ Load Header & Footer
// ===============================
["header", "footer"].forEach((id) => {
const file =
id === "header" ? "components/header.html" : "components/footer.html";
fetch(file)
.then((res) => res.text())
.then((html) => {
const mount = document.getElementById(id);
if (!mount) return;
mount.innerHTML = html;
if (id === "header") {
const script = document.createElement("script");
script.src = "js/navbar.js";
script.defer = true;
document.body.appendChild(script);
}
});
});
// ===============================
// ✅ Routes for SPA Navigation
// ===============================
const routes = {
"#/": "pages/home.html",
"#/our-fund": "pages/our-fund.html",
"#/our-team": "pages/our-team.html",
"#/about": "pages/about.html",
"#/contact": "pages/contact.html",
"#/blog": "pages/blog.html",
};
// ===============================
// ✅ Timeline Data
// ===============================
const timelineData = {
1983: {
icon: "./assests/images/1983-Icon.svg",
heading: "The beginning",
sub1: "Set up by Mr. Asit Koticha and Mr. Sameer Koticha,",
sub2: "ASK Group offers research-based Investment advisory",
sub3: "",
},
1991: {
icon: "./assests/images/1991-Icon.svg",
heading: "ASK Raymond James",
sub1: "ASK Group and Raymond James",
sub2: "Financial enter into a Partnership",
sub3: "",
},
2007: {
icon: "./assests/images/2007-Icon.svg",
heading: "Launch of ASK Wealth Advisors",
sub1: "Raymond James Financial partnership exits.",
sub2: "",
sub3: "",
},
2008: {
icon: "./assests/images/2008-Icon.svg",
heading: "Launch of ASK multi-family office",
sub1: "",
sub2: "",
sub3: "",
},
2013: {
icon: "./assests/images/2013-Icon.svg",
heading: "License from SEBI",
sub1: "ASK Wealth Advisors receives an",
sub2: "Investment Advisor License from SEBI.",
sub3: "",
},
2015: {
icon: "./assests/images/2015-Icon.svg",
heading: "ASK Wealth Advisors adjudged",
sub1: ` "the best Independent Wealth Advisor, 2015" `,
sub2: "by Wealth Briefing, Asia.",
sub3: "",
},
2016: {
icon: "./assests/images/2016-Icon.svg",
mobileIcon: "./assests/images/2016-Mobile-Icon.svg",
heading: "",
sub1: "Advert International acquires minority ",
sub2: "stake in ASK Group.",
sub3: "",
},
2017: {
icon: "./assests/images/2015-Icon.svg",
heading: "ASK Wealth Advisors adjudged",
sub1: `"One to Watch - Wealth Manager - India Domestic" - Distinction, 2017"`,
sub2: "by Asian Private Banker.",
sub3: "",
},
2018: {
icon: "./assests/images/2015-Icon.svg",
heading:
"ASK launches its first offshore fund - ASK Global Strategies Fund",
sub1: "TWICE IN A ROW:",
sub2: " ASK Wealth advisors adjudged",
sub3: `"Best Performing Financial Advisor"-Wealth 2016-17 by UTI MF & CNBC TV18.`,
},
2019: {
icon: "./assests/images/1983-Icon.svg",
heading: "Hall of Fame",
sub1: `ASK Wealth Advisors inducted into the "Hall of Fame"`,
sub2: "at the Financial Advisor Awards for the Years ",
sub3: "2018-19 and 2019-20 by UTI MF & CNBC TV18.",
},
2020: {
icon: "./assests/images/2015-Icon.svg",
heading: "Outstanding Private Bank",
sub1: "Outstanding Private Bank for UNHW clients ",
sub2: "at the Private Banker International Global",
sub3: "Wealth Summit & Awards 2020, Singapore.",
},
2022: {
icon: "./assests/images/2022-Icon.svg",
heading: "Blackstone Acquisition",
sub1: "Blackstone acquires majority stake in ",
sub2: "ASK Group, Advert International exits.",
sub3: "",
},
};
// ===============================
// ✅ Initialize Timeline & Swiper
// ===============================
function initTimelineSwiper() {
const swiperWrapper = document.querySelector(".swiper-wrapper");
const timelineItems = document.querySelectorAll(".timeline-item");
if (!swiperWrapper || !timelineItems.length) return;
// ✅ Check if screen width is 768px or below
const isMobile = window.matchMedia("(max-width: 768px)").matches;
swiperWrapper.innerHTML = "";
Object.keys(timelineData).forEach((year) => {
const content = timelineData[year];
const slide = document.createElement("div");
slide.classList.add("swiper-slide");
// ✅ Use mobile icon if on mobile, else desktop
const iconToUse =
isMobile && content.mobileIcon ? content.mobileIcon : content.icon;
const headingColor = ["2015", "2017", "2018"].includes(year)
? "#ffffff"
: "#b18c4a";
const sub1Color = ["2015", "2017", "2018"].includes(year)
? "#b18c4a"
: "#ffffff";
const customTopPad = ["2018"].includes(year) ? "1rem" : "";
slide.innerHTML = `
${iconToUse ? `<img src="${iconToUse}" alt="icon" />` : ""}
<h2>${year}</h2>
${
content.heading
? `<p class="heading" style="color:${headingColor};">${content.heading}</p>`
: ""
}
<div class="sub-heading-container">
${
content.sub1
? `<p style="color:${sub1Color}; padding-top:${customTopPad}">${content.sub1}</p>`
: ""
}
${content.sub2 ? `<p>${content.sub2}</p>` : ""}
${content.sub3 ? `<p>${content.sub3}</p>` : ""}
</div>
`;
swiperWrapper.appendChild(slide);
});
// Line fill logic - UPDATED for mobile
const lineFill = document.querySelector(".line-fill");
const timeline = document.querySelector(".timeline");
const totalKites = timelineItems.length;
function updateTimeline(index) {
timelineItems.forEach((item, i) => {
item.classList.toggle("active", i === index);
});
// Check if mobile (timeline is scrollable)
const isMobileView = window.matchMedia("(max-width: 768px)").matches;
if (isMobileView && timeline && lineFill) {
// FIXED: Mobile line fill calculation - exact kite to kite
const firstKite = timelineItems[0];
const lastKite = timelineItems[totalKites - 1];
if (firstKite && lastKite) {
// Calculate exact positions of first and last kite centers
const firstKiteCenter =
firstKite.offsetLeft + firstKite.offsetWidth / 2;
const lastKiteCenter = lastKite.offsetLeft + lastKite.offsetWidth / 2;
// Total distance between first and last kite centers
const totalDistance = lastKiteCenter - firstKiteCenter;
// Calculate width based on current index
const progress = index / (totalKites - 1);
const newWidth = totalDistance * progress;
// Set the line fill width
lineFill.style.width = newWidth + "px";
}
} else {
// Desktop line fill calculation (your original code)
const timelineWidth = timeline?.offsetWidth - 20;
const newWidth = (timelineWidth / (totalKites - 1)) * index;
if (lineFill) lineFill.style.width = newWidth + "px";
}
// Auto-scroll for mobile only
if (isMobileView) {
autoScrollTimeline(index);
}
}
// Auto-scroll timeline horizontally - MOBILE ONLY
function autoScrollTimeline(index) {
if (!timeline) return;
const activeItem = timelineItems[index];
if (!activeItem) return;
const itemLeft = activeItem.offsetLeft;
const itemWidth = activeItem.offsetWidth;
const containerWidth = timeline.offsetWidth;
// Calculate the target scroll position to show current year
// completely visible at the far right edge with a small padding
const rightPadding = 40;
const targetScroll = itemLeft + itemWidth - containerWidth + rightPadding;
// Ensure we don't scroll beyond the start or end of the timeline
const maxScroll = timeline.scrollWidth - containerWidth;
const clampedScroll = Math.max(0, Math.min(targetScroll, maxScroll));
// Only scroll if we're not already at the correct position
if (Math.abs(timeline.scrollLeft - clampedScroll) > 4) {
timeline.scrollTo({
left: clampedScroll,
behavior: "smooth",
});
}
}
// Initialize Swiper
const swiper = new Swiper(".mySwiper", {
spaceBetween: 20,
centeredSlides: true,
effect: "fade",
fadeEffect: {
crossFade: true, // 👈 this makes previous slide fade out smoothly
},
speed: 2500, // controls fade speed (in ms)
autoplay: {
delay: 2200,
disableOnInteraction: false,
},
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
},
});
swiper.on("slideChange", () => {
updateTimeline(swiper.realIndex);
});
// Add click events to timeline items
timelineItems.forEach((item, index) => {
item.addEventListener("click", () => {
updateTimeline(index);
swiper.slideTo(index);
});
});
updateTimeline(0);
// Handle window resize - recalculate after a short delay
let resizeTimeout;
window.addEventListener("resize", function () {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(function () {
updateTimeline(swiper.realIndex);
}, 250);
});
}
// ===============================
// ✅ Generic Swiper Init (Other Pages)
// ===============================
function initSwiper() {
if (document.querySelector(".mySwiper2")) {
new Swiper(".mySwiper2", {
slidesPerView: 1.2,
spaceBetween: 20,
loop: true,
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
});
}
}
// ===============================
// ✅ Team Drawer
// ===============================
window.openDrawer = function (name, role, img, desc) {
document.getElementById("drawerName").textContent = name;
document.getElementById("drawerRole").textContent = role;
document.getElementById("drawerImg").src = img;
document.getElementById("drawerDesc").innerHTML = desc;
const drawer = bootstrap.Offcanvas.getOrCreateInstance(
document.getElementById("teamDrawer")
);
drawer.show();
};
let cards = [];
let currentIndex = 0;
let animating = false;
let isSectionPinned = false;
let scrollTrigger;
let allowNormalScroll = false;
let scrollLockPosition = 0;
// ===============================
// ✅ GSAP Stack Scroll with Section Pinning
// ===============================
function initGsapStackScroll() {
const section = document.querySelector(".ask-advantage-scroll");
if (!section) return;
const cardsWrapper = document.querySelector(".cards-wrapper");
if (!cardsWrapper) return;
cards = Array.from(cardsWrapper.querySelectorAll(".card-scroll"));
if (!cards.length) return;
// Initial setup
gsap.set(cards, { opacity: 0, y: "100%", zIndex: 0 });
gsap.set(cards[0], { opacity: 1, y: "60px", zIndex: 5 });
currentIndex = 0;
allowNormalScroll = false;
// Calculate exact pinning distance
const cardHeight = cardsWrapper.offsetHeight;
const totalCards = cards.length;
const pinningDistance = cardHeight * totalCards * 0.4;
// Scroll pinning logic
scrollTrigger = ScrollTrigger.create({
trigger: cardsWrapper,
start: "top top",
end: `+=${pinningDistance}`,
pin: true,
anticipatePin: 1,
pinSpacing: true,
onEnter: () => {
isSectionPinned = true;
allowNormalScroll = false;
if (currentIndex !== 0) {
resetToFirstCard();
}
},
onLeave: () => {
isSectionPinned = false;
allowNormalScroll = true;
},
onEnterBack: () => {
isSectionPinned = true;
allowNormalScroll = false;
},
onLeaveBack: () => {
isSectionPinned = false;
allowNormalScroll = true;
}
});
// deepseek today code: Enhanced scroll prevention with visible scrollbar
window._gsapStackHandler = function (e) {
if (isSectionPinned && !allowNormalScroll) {
e.preventDefault();
e.stopPropagation();
if (animating) {
// If already animating, just prevent scroll completely
return;
}
if (e.deltaY > 0) {
showNextGsapCard();
} else if (e.deltaY < 0) {
showPrevGsapCard();
}
}
};
// Add wheel event listener
window.addEventListener("wheel", window._gsapStackHandler, {
passive: false
});
// deepseek today code: Active scroll position locking during animations
window._scrollLockHandler = function() {
if (isSectionPinned && !allowNormalScroll && animating) {
// Immediately restore to locked position
window.scrollTo(0, scrollLockPosition);
}
};
window.addEventListener("scroll", window._scrollLockHandler, { passive: false });
// Prevent keyboard scrolling during animations
window.addEventListener("keydown", function(e) {
if (isSectionPinned && !allowNormalScroll && (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === " " || e.key === "PageDown" || e.key === "PageUp")) {
e.preventDefault();
if (animating) return;
if (e.key === "ArrowDown" || e.key === "PageDown" || e.key === " ") {
showNextGsapCard();
} else if (e.key === "ArrowUp" || e.key === "PageUp") {
showPrevGsapCard();
}
}
});
}
// deepseek today code: Enhanced scroll locking without hiding scrollbar
function lockScrollDuringAnimation() {
animating = true;
scrollLockPosition = window.scrollY; // Store current scroll position
// Keep scrollbar visible but prevent any scroll movement
document.documentElement.style.overscrollBehavior = 'none';
document.body.style.overscrollBehavior = 'none';
// Use requestAnimationFrame to continuously lock scroll position
function maintainScrollLock() {
if (animating && isSectionPinned && !allowNormalScroll) {
if (window.scrollY !== scrollLockPosition) {
window.scrollTo(0, scrollLockPosition);
}
requestAnimationFrame(maintainScrollLock);
}
}
maintainScrollLock();
}
function unlockScrollAfterAnimation() {
animating = false;
// Restore normal scroll behavior
document.documentElement.style.overscrollBehavior = '';
document.body.style.overscrollBehavior = '';
}
// Reset to first card function
function resetToFirstCard() {
lockScrollDuringAnimation();
// Hide all cards
gsap.set(cards, { opacity: 0, y: "100%", zIndex: 0 });
// Show first card
gsap.set(cards[0], {
opacity: 1,
y: "60px",
zIndex: 5,
onComplete: () => {
currentIndex = 0;
unlockScrollAfterAnimation();
allowNormalScroll = false;
}
});
}
// ✅ Show next card
function showNextGsapCard() {
if (currentIndex >= cards.length - 1) {
allowNormalScroll = true;
unlockScrollAfterAnimation();
const lastCard = cards[currentIndex];
gsap.to(lastCard, {
y: "50px",
duration: 0.3,
ease: "power2.out",
yoyo: true,
repeat: 1
});
return;
}
lockScrollDuringAnimation();
const current = cards[currentIndex];
const next = cards[currentIndex + 1];
// Animate current slightly up/back
gsap.to(current, {
y: "40px",
scale: 0.95,
opacity: 1,
duration: 0.8,
ease: "power2.inOut",
});
// Animate next card into view
gsap.set(next, { zIndex: 5 });
gsap.fromTo(
next,
{ y: "100%", opacity: 0, scale: 1 },
{
y: "60px",
opacity: 1,
scale: 1,
duration: 0.8,
ease: "power2.inOut",
onComplete: () => {
currentIndex++;
adjustVisibleCards();
unlockScrollAfterAnimation();
if (currentIndex >= cards.length - 1) {
allowNormalScroll = true;
}
},
}
);
}
// ✅ Show previous card
function showPrevGsapCard() {
if (currentIndex <= 0) {
allowNormalScroll = true;
unlockScrollAfterAnimation();
return;
}
lockScrollDuringAnimation();
const current = cards[currentIndex];
const prev = cards[currentIndex - 1];
// Hide current card (slide down)
gsap.to(current, {
y: "100%",
opacity: 0,
duration: 0.8,
ease: "power2.inOut",
});
// Bring previous card back as active
gsap.set(prev, { zIndex: 5 });
gsap.to(prev, {
y: "60px",
opacity: 1,
scale: 1,
duration: 0.8,
ease: "power2.inOut",
onComplete: () => {
currentIndex--;
adjustVisibleCards();
unlockScrollAfterAnimation();
if (currentIndex > 0) {
allowNormalScroll = false;
}
},
});
}
// ✅ Maintain only 3 visible cards in stack
function adjustVisibleCards() {
// Reset all first
gsap.set(cards, { opacity: 0, y: "100%", zIndex: 0 });
// Active card
if (cards[currentIndex]) {
gsap.set(cards[currentIndex], {
y: "60px",
scale: 1,
opacity: 1,
zIndex: 5,
});
}
// Show only 2 previous cards (total 3 visible)
for (let i = 1; i <= 2; i++) {
const prevIndex = currentIndex - i;
if (prevIndex >= 0) {
const yOffset = 60 - i * 25;
const scale = 1 - i * 0.05;
const zIndex = 5 - i;
gsap.set(cards[prevIndex], {
y: `${yOffset}px`,
scale: scale,
opacity: 1,
zIndex: zIndex,
});
}
}
// Hide any card older than the last 3 visible
const hideIndex = currentIndex - 3;
if (hideIndex >= 0 && cards[hideIndex]) {
gsap.to(cards[hideIndex], {
opacity: 0,
y: "100%",
duration: 0.6,
ease: "power2.inOut",
});
}
}
// Initialize after DOM load
document.addEventListener("DOMContentLoaded", initGsapStackScroll);
// ===============================
// ✅ Load Page Content Dynamically
// ===============================
function loadPage() {
const hash = location.hash || "#/";
const page = routes[hash] || routes["#/"];
fetch(page)
.then((res) => res.text())
.then((html) => {
document.getElementById("page-content").innerHTML = html;
window.scrollTo({ top: 0, behavior: "smooth" });
// ✅ Hide footer for #/our-fund route
const footer = document.getElementById("footer");
if (footer) {
if (hash === "#/our-fund") {
footer.style.display = "none";
} else {
footer.style.display = "block";
}
}
// Init all components after content load
initSwiper();
initTimelineSwiper();
initGsapStackScroll();
})
.catch(() => {
document.getElementById("page-content").innerHTML =
"<h2>Page not found</h2>";
});
}
// ===============================
// ✅ Router Event Listener
// ===============================
window.addEventListener("hashchange", loadPage);
loadPage(); // Default load