All checks were successful
ask-mutual-fund Actions Workflow / test (push) Successful in 16s
651 lines
17 KiB
JavaScript
651 lines
17 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;
|
|
|
|
// ===============================
|
|
// ✅ GSAP Stack Scroll with Section Pinning (Fixed Scroll Lock Only)
|
|
// ===============================
|
|
function initGsapStackScroll() {
|
|
// Check if device is desktop/laptop (min-width: 1024px)
|
|
const isDesktop = window.matchMedia("(min-width: 1024px)").matches;
|
|
|
|
if (!isDesktop) {
|
|
console.log("GSAP stack scroll disabled for mobile/tablet devices");
|
|
return;
|
|
}
|
|
|
|
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;
|
|
},
|
|
});
|
|
|
|
// ✅ Wheel handler with scroll block fix
|
|
window._gsapStackHandler = function (e) {
|
|
if (!isSectionPinned) return;
|
|
|
|
// Completely block scroll while animating
|
|
if (animating) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
return;
|
|
}
|
|
|
|
// Boundaries check
|
|
if (
|
|
(e.deltaY > 0 && currentIndex >= cards.length - 1) ||
|
|
(e.deltaY < 0 && currentIndex <= 0)
|
|
) {
|
|
allowNormalScroll = true;
|
|
return;
|
|
}
|
|
|
|
// Start animation & block scroll during it
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
if (e.deltaY > 0) {
|
|
showNextGsapCard();
|
|
} else {
|
|
showPrevGsapCard();
|
|
}
|
|
};
|
|
|
|
// ✅ Keyboard handler
|
|
window._gsapKeyHandler = function (e) {
|
|
if (!isSectionPinned || animating) return;
|
|
|
|
if (
|
|
(e.key === "ArrowDown" && currentIndex >= cards.length - 1) ||
|
|
(e.key === "ArrowUp" && currentIndex <= 0)
|
|
) {
|
|
return; // Allow normal scroll at boundaries
|
|
}
|
|
|
|
if (e.key === "ArrowDown") {
|
|
e.preventDefault();
|
|
showNextGsapCard();
|
|
} else if (e.key === "ArrowUp") {
|
|
e.preventDefault();
|
|
showPrevGsapCard();
|
|
}
|
|
};
|
|
|
|
window.addEventListener("wheel", window._gsapStackHandler, {
|
|
passive: false,
|
|
});
|
|
window.addEventListener("keydown", window._gsapKeyHandler);
|
|
}
|
|
|
|
// ✅ Reset to first card
|
|
function resetToFirstCard() {
|
|
animating = true;
|
|
allowNormalScroll = false;
|
|
|
|
gsap.set(cards, { opacity: 0, y: "100%", zIndex: 0 });
|
|
|
|
gsap.set(cards[0], {
|
|
opacity: 1,
|
|
y: "60px",
|
|
zIndex: 5,
|
|
onComplete: () => {
|
|
currentIndex = 0;
|
|
animating = false;
|
|
},
|
|
});
|
|
}
|
|
|
|
// ✅ Show next card
|
|
function showNextGsapCard() {
|
|
if (currentIndex >= cards.length - 1 || animating) return;
|
|
|
|
animating = true;
|
|
allowNormalScroll = false;
|
|
|
|
const current = cards[currentIndex];
|
|
const next = cards[currentIndex + 1];
|
|
|
|
gsap.to(current, {
|
|
y: "40px",
|
|
scale: 0.95,
|
|
opacity: 1,
|
|
duration: 0.8,
|
|
ease: "power2.inOut",
|
|
});
|
|
|
|
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();
|
|
animating = false;
|
|
|
|
// ✅ Allow normal scroll only after last card animation completes
|
|
if (currentIndex >= cards.length - 1) {
|
|
allowNormalScroll = true;
|
|
ScrollTrigger.refresh(true);
|
|
} else {
|
|
allowNormalScroll = false;
|
|
}
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
// ✅ Show previous card
|
|
function showPrevGsapCard() {
|
|
if (currentIndex <= 0 || animating) return;
|
|
|
|
animating = true;
|
|
allowNormalScroll = false;
|
|
|
|
const current = cards[currentIndex];
|
|
const prev = cards[currentIndex - 1];
|
|
gsap.to(current, {
|
|
y: "100%",
|
|
opacity: 0,
|
|
duration: 0.8,
|
|
ease: "power2.inOut",
|
|
});
|
|
|
|
gsap.set(prev, { zIndex: 5 });
|
|
gsap.to(prev, {
|
|
y: "60px",
|
|
opacity: 1,
|
|
scale: 1,
|
|
duration: 0.8,
|
|
ease: "power2.inOut",
|
|
onComplete: () => {
|
|
currentIndex--;
|
|
adjustVisibleCards();
|
|
animating = false;
|
|
|
|
if (currentIndex <= 0) {
|
|
allowNormalScroll = false;
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
// ✅ Adjust visible cards stack
|
|
function adjustVisibleCards() {
|
|
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
|
|
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 older cards
|
|
const hideIndex = currentIndex - 3;
|
|
if (hideIndex >= 0 && cards[hideIndex]) {
|
|
gsap.to(cards[hideIndex], {
|
|
opacity: 0,
|
|
y: "100%",
|
|
duration: 0.6,
|
|
ease: "power2.inOut",
|
|
});
|
|
}
|
|
}
|
|
|
|
// ✅ Cleanup function
|
|
function cleanupGsapStackScroll() {
|
|
if (window._gsapStackHandler) {
|
|
window.removeEventListener("wheel", window._gsapStackHandler);
|
|
}
|
|
if (window._gsapKeyHandler) {
|
|
window.removeEventListener("keydown", window._gsapKeyHandler);
|
|
}
|
|
if (scrollTrigger) {
|
|
scrollTrigger.kill();
|
|
}
|
|
}
|
|
|
|
// Initialize after DOM load
|
|
document.addEventListener("DOMContentLoaded", initGsapStackScroll);
|
|
window.addEventListener("beforeunload", cleanupGsapStackScroll);
|
|
|
|
// ===============================
|
|
// ✅ 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
|