const isMobile = window.innerWidth <= 991; const isDesktop = window.innerWidth > 992; $(document).ready(function () { function setCookie(name, value, days) { const date = new Date(); date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); const expires = "expires=" + date.toUTCString(); document.cookie = name + "=" + value + ";" + expires + ";path=/"; } function getCookie(name) { const nameEQ = name + "="; const ca = document.cookie.split(";"); for (let i = 0; i < ca.length; i++) { let c = ca[i]; while (c.charAt(0) === " ") c = c.substring(1); if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length); } return null; } if (!getCookie("popupClosed")) { setTimeout(function () { $(".popup-modal").addClass("active"); }, 10000); } $(document).on("click", ".popup-close", function () { $(".popup-modal").removeClass("active"); setCookie("popupClosed", "true", 7); }); if (isDesktop) { $(".nav-h-cont") .on("mouseenter", function () { $(this).find(".nav-h-arrow").addClass("hovered"); $(this).find(".nav-h").addClass("hovered"); }) .on("mouseleave", function () { $(this).find(".nav-h-arrow").removeClass("hovered"); $(this).find(".nav-h").removeClass("hovered"); }); } }); gsap.registerPlugin(ScrollTrigger, ScrollToPlugin); $(".w-tab-link").removeClass("w--current"); $(".w-tab-pane").removeClass("w--tab-active"); $(".w-tab-link:nth-child(1)").addClass("w--current"); $(".w-tab-pane:nth-child(1)").addClass("w--tab-active"); Webflow.push(function () { $(".copyright-year").text(new Date().getFullYear()); }); $(function () { var path = window.location.pathname.replace(/\/+$/, "") || "/"; if (path === "/") return; var $targets = $( ".nav-buttons, .nav-brand-logo, .nav, .nav-fixed-container, .announcement-bar" ); if (!$targets.length) return; var threshold = 100; function checkNav() { if ($(window).scrollTop() > threshold) { $targets.addClass("scrolled"); } else { $targets.removeClass("scrolled"); } } checkNav(); $(window).on("scroll", checkNav); }); const $navTrigger = $(".nav-hamburger"); const $navFixed = $(".nav-fixed-container"); const $navInner = $(".nav-fixed-inner"); const $navCols = $(".nav-fixed-col"); const $navHElements = $(".nav-h"); const $navLinks = $(".nav-link"); gsap.set($navFixed, { clipPath: "circle(0% at 50% 50%)", opacity: 1, }); gsap.set($navCols, { opacity: 0 }); gsap.set([$navHElements, $navLinks], { opacity: 0, y: 10, transformPerspective: 1000, }); let navOpen = false; let navTimeline = gsap.timeline({ paused: true, defaults: { ease: "expo.out" }, }); navTimeline .to( $navFixed, { clipPath: "circle(100% at 50% 50%)", duration: 1.6, }, 0 ) .to( $navCols, { opacity: 1, duration: 0.5, stagger: 0.1, ease: "back.out(1.4)", }, 0.3 ) .to( $navHElements, { opacity: 1, y: 0, rotationX: 0, duration: 0.4, stagger: 0.08, ease: "back.out(1.2)", }, 0.4 ) .to( $navLinks, { opacity: 1, y: 0, rotationX: 0, duration: 0.35, stagger: { each: 0.05, }, ease: "back.out(1.1)", }, 0.45 ); let closeTimeline = gsap.timeline({ paused: true }); closeTimeline .to([$navHElements, $navLinks], { opacity: 0, y: 10, duration: 0.25, stagger: { each: 0.03, from: "end" }, }) .to( $navCols, { opacity: 0, duration: 0.25, stagger: 0.05, }, "<" ) .to( $navFixed, { clipPath: "circle(0% at 50% 50%)", duration: 0.6, ease: "expo.in", onComplete: () => { gsap.set($navFixed, { display: "none" }); gsap.set("body", { overflow: "auto" }); }, }, "-=0.75" ); navTimeline.reverse(); $navTrigger.on("click", function () { navOpen = !navOpen; if (navOpen) { gsap.set($navFixed, { display: "block" }); gsap.set("body", { overflow: "hidden" }); navTimeline.restart(); } else { closeTimeline.restart().then(() => { if (!navOpen) { gsap.set($navFixed, { display: "none" }); gsap.set("body", { overflow: "auto" }); } }); } }); if ( window.location.pathname.replace(/\/+$/, "") === "/about-pancreatic-cancer" ) { $navLinks.on("click", function (e) { if (navOpen) { e.preventDefault(); const target = this.getAttribute("href"); closeTimeline.restart().then(() => { navOpen = false; gsap.set($navFixed, { display: "none" }); gsap.set("body", { overflow: "auto" }); // Scroll to anchor after menu closes if (target && target.startsWith("#")) { document .querySelector(target) ?.scrollIntoView({ behavior: "smooth" }); } else if (target) { window.location.href = target; } }); } }); } const $searchTrigger = $(".search-trigger"); const $searchModal = $(".nav-search-modal"); const $searchClose = $(".search-modal-close"); $searchTrigger.on("click", function (e) { e.preventDefault(); $searchModal.addClass("active"); setTimeout(() => { $searchModal.find('input[type="search"]').focus(); }, 100); }); $searchClose.on("click", function (e) { e.preventDefault(); $searchModal.removeClass("active"); }); $(document).on("click", function (e) { if ( $(e.target).closest(".nav-search-modal").length === 0 && $(e.target).closest(".search-trigger").length === 0 ) { $searchModal.removeClass("active"); } }); $(document).on("keydown", function (e) { if (e.key === "Escape") { $searchModal.removeClass("active"); } }); if (isDesktop) { document.querySelectorAll(".home-story-section").forEach((section) => { const container = section.querySelector(".home-story-container"); gsap.fromTo( container, { scale: 0.95 }, { scale: 1, ease: "none", scrollTrigger: { trigger: section, start: "20% bottom", end: "top 40%", scrub: true, }, } ); }); } document.querySelectorAll(".cta-section").forEach((section) => { const bg = section.querySelector(".cta-red-bg"); if (!bg) return; ScrollTrigger.create({ trigger: section, start: "top bottom", end: "bottom top", scrub: true, onUpdate: (self) => { gsap.to(bg, { y: `+=${self.direction === 1 ? -15 : 15}`, duration: 0.2, overwrite: "auto", }); }, }); }); function computeDelay(element) { let delay = 0; let parent = element.parentElement; if (parent && parent.hasAttribute("data-stagger")) { delay += 0.2; } let sibling = element.previousElementSibling; while (sibling) { if (sibling.hasAttribute("data-stagger")) { delay += 0.2; } sibling = sibling.previousElementSibling; } return delay; } function initAnimations() { const staggerContainers = document.querySelectorAll( '[data-stagger="true"]' ); staggerContainers.forEach((container) => { const delay = computeDelay(container); const children = container.children; const tl = gsap.timeline({ delay: delay, scrollTrigger: { trigger: container, start: "top bottom", end: "bottom top", toggleActions: "play none none reverse", }, }); gsap.set(children, { opacity: 0, y: 50 }); tl.to(children, { opacity: 1, y: 0, duration: 0.5, stagger: 0.1, }); }); } window.addEventListener("load", initAnimations); document.addEventListener("DOMContentLoaded", function () { let iframes = document.querySelectorAll("iframe[data-src]"); iframes.forEach((iframe) => { let videoUrl = iframe.getAttribute("data-src"); let videoId = new URL(videoUrl).searchParams.get("v"); if (videoId) { iframe.src = `https://www.youtube.com/embed/${videoId}`; } }); }); document.addEventListener("DOMContentLoaded", function () { document .querySelectorAll('a[data-fancybox="true"] > img[fancybox-image="true"]') .forEach((img) => { const anchor = img.closest('a[data-fancybox="true"]'); if (anchor && img.src) { anchor.href = img.src; } }); }); document.addEventListener("DOMContentLoaded", function () { const elements = document.querySelectorAll("[data-countup]"); const observer = new IntersectionObserver( (entries, observer) => { entries.forEach((entry) => { if (entry.isIntersecting) { const el = entry.target; const target = +el.getAttribute("data-countup"); const duration = 2000; const start = 0; let startTime = null; const animate = (timestamp) => { if (!startTime) startTime = timestamp; const progress = timestamp - startTime; const value = Math.min( Math.floor((progress / duration) * target), target ); el.textContent = value.toLocaleString(); if (value < target) { requestAnimationFrame(animate); } }; requestAnimationFrame(animate); observer.unobserve(el); } }); }, { threshold: 0.1, } ); elements.forEach((el) => observer.observe(el)); }); $(document).ready(function () { $(".button").each(function () { const $button = $(this); const $bg = $button.find(".button-gradient-bg"); let timeout; $button.on("mouseenter", function () { clearTimeout(timeout); $bg.removeClass("snap-reset hover-out"); void $bg[0].offsetWidth; $bg.addClass("hovered"); }); $button.on("mouseleave", function () { if (!$bg.hasClass("hover-out")) { $bg.addClass("hover-out"); } timeout = setTimeout(function () { $bg.addClass("snap-reset"); $bg.removeClass("hovered hover-out"); requestAnimationFrame(() => { $bg.removeClass("snap-reset"); }); }, 1000); }); }); }); $(document).ready(function () { $(".tab-section").each(function () { const $section = $(this); const $triggers = $section.find(".tab-trigger"); const $panes = $section.find(".tab-pane"); $panes.each(function (index) { gsap.set(this, { autoAlpha: index === 0 ? 1 : 0, display: index === 0 ? "block" : "none", }); }); $triggers.first().addClass("active"); $triggers.on("click", function (e) { e.preventDefault(); const index = $triggers.index(this); const $targetPane = $panes.eq(index); const $otherPanes = $panes.not($targetPane); $triggers.removeClass("active"); $(this).addClass("active"); gsap.set($otherPanes, { autoAlpha: 0, display: "none", }); gsap.to($targetPane, { autoAlpha: 1, display: "block", duration: 0.3, overwrite: true, }); }); }); }); gsap.registerPlugin(InertiaPlugin); document.addEventListener("DOMContentLoaded", () => { const marquee = document.querySelector(".nav-marquee"); const text = document.querySelectorAll(".nav-marquee-text"); const marqueeSpeed = isDesktop ? 60 : 30; gsap.to(text, { x: "-50%", duration: marqueeSpeed, repeat: -1, ease: "none", }); const footerMarquee = document.querySelector(".footer-marquee"); const footerText = document.querySelectorAll(".footer-marquee-text"); gsap.to(footerText, { x: "-50%", duration: 60, repeat: -1, ease: "none", }); }); function setupAboutNavAccordion() { if (isMobile) { document .querySelectorAll('.about-nav[fs-accordion-element="trigger"]') .forEach((trigger) => { trigger.click(); }); } } window.addEventListener("DOMContentLoaded", setupAboutNavAccordion); gsap.registerPlugin(InertiaPlugin); const marquees = document.querySelectorAll(".marquee"); let loops = {}; // Wait for everything to be fully loaded and settled function initializeMarquees() { // Add a small delay to ensure layout is stable setTimeout(() => { marquees.forEach((marquee, index) => { const images = gsap.utils.toArray(".marquee-image", marquee); // Ensure images are visible and have proper dimensions gsap.set(images, { opacity: 1, x: 0, force3D: true, }); // Force a reflow to ensure layout calculations are accurate marquee.offsetHeight; // Determine direction based on marquee class or index const isReversed = index % 2 === 1; // Odd-indexed marquees go left loops[`loop${index + 1}`] = horizontalLoop(images, { repeat: -1, paddingRight: 24, speed: isReversed ? -0.25 : 0.25, reversed: isReversed, }); }); }, 100); // Small delay to ensure everything is settled } // Use multiple event listeners to ensure proper initialization if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => { // Wait for images to load const images = document.querySelectorAll(".marquee-image img"); let loadedImages = 0; if (images.length === 0) { // No images, initialize immediately initializeMarquees(); return; } function checkAllImagesLoaded() { loadedImages++; if (loadedImages === images.length) { initializeMarquees(); } } images.forEach((img) => { if (img.complete) { checkAllImagesLoaded(); } else { img.addEventListener("load", checkAllImagesLoaded); img.addEventListener("error", checkAllImagesLoaded); // Handle broken images } }); // Fallback timeout in case some images don't load setTimeout(initializeMarquees, 2000); }); } else { // Document already loaded initializeMarquees(); } function horizontalLoop(items, config) { items = gsap.utils.toArray(items); config = config || {}; let onChange = config.onChange, lastIndex = 0, tl = gsap.timeline({ repeat: config.repeat, onUpdate: onChange && function () { let i = tl.closestIndex(); if (lastIndex !== i) { lastIndex = i; onChange(items[i], i); } }, paused: config.paused, defaults: { ease: "none" }, onReverseComplete: () => tl.totalTime(tl.rawTime() + tl.duration() * 100), }), length = items.length, startX = items[0].offsetLeft, times = [], widths = [], spaceBefore = [], xPercents = [], curIndex = 0, indexIsDirty = false, center = config.center, pixelsPerSecond = (Math.abs(config.speed) || 0.25) * 100, snap = config.snap === false ? (v) => v : gsap.utils.snap(0.01), timeOffset = 0, container = center === true ? items[0].parentNode : gsap.utils.toArray(center)[0] || items[0].parentNode, totalWidth, getTotalWidth = () => items[length - 1].offsetLeft + (xPercents[length - 1] / 100) * widths[length - 1] - startX + spaceBefore[0] + items[length - 1].offsetWidth * gsap.getProperty(items[length - 1], "scaleX") + (parseFloat(config.paddingRight) || 0), populateWidths = () => { let b1 = container.getBoundingClientRect(), b2; items.forEach((el, i) => { widths[i] = parseFloat(gsap.getProperty(el, "width", "px")); xPercents[i] = snap( (parseFloat(gsap.getProperty(el, "x", "px")) / widths[i]) * 100 + gsap.getProperty(el, "xPercent") ); b2 = el.getBoundingClientRect(); spaceBefore[i] = b2.left - (i ? b1.right : b1.left); b1 = b2; }); gsap.set(items, { xPercent: (i) => xPercents[i], }); totalWidth = getTotalWidth(); }, timeWrap, populateOffsets = () => { timeOffset = center ? (tl.duration() * (container.offsetWidth / 2)) / totalWidth : 0; center && times.forEach((t, i) => { times[i] = timeWrap( tl.labels["label" + i] + (tl.duration() * widths[i]) / 2 / totalWidth - timeOffset ); }); }, getClosest = (values, value, wrap) => { let i = values.length, closest = 1e10, index = 0, d; while (i--) { d = Math.abs(values[i] - value); if (d > wrap / 2) { d = wrap - d; } if (d < closest) { closest = d; index = i; } } return index; }, populateTimeline = () => { let i, item, curX, distanceToStart, distanceToLoop; tl.clear(); // Determine direction based on speed sign const direction = config.speed < 0 ? -1 : 1; for (i = 0; i < length; i++) { item = items[i]; curX = (xPercents[i] / 100) * widths[i]; distanceToStart = item.offsetLeft + curX - startX + spaceBefore[0]; distanceToLoop = distanceToStart + widths[i] * gsap.getProperty(item, "scaleX"); // Apply direction to the animation tl.to( item, { xPercent: snap( ((curX - distanceToLoop * direction) / widths[i]) * 100 ), duration: distanceToLoop / pixelsPerSecond, }, 0 ) .fromTo( item, { xPercent: snap( ((curX - distanceToLoop * direction + totalWidth * direction) / widths[i]) * 100 ), }, { xPercent: xPercents[i], duration: (curX - distanceToLoop * direction + totalWidth * direction - curX) / pixelsPerSecond, immediateRender: false, }, distanceToLoop / pixelsPerSecond ) .add("label" + i, distanceToStart / pixelsPerSecond); times[i] = distanceToStart / pixelsPerSecond; } timeWrap = gsap.utils.wrap(0, tl.duration()); }, refresh = (deep) => { let progress = tl.progress(); tl.progress(0, true); populateWidths(); deep && populateTimeline(); populateOffsets(); deep ? tl.time(times[curIndex], true) : tl.progress(progress, true); }, proxy; gsap.set(items, { x: 0, force3D: true }); populateWidths(); populateTimeline(); populateOffsets(); // Debounced resize handler let resizeTimeout; window.addEventListener("resize", () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => refresh(true), 250); }); function toIndex(index, vars) { vars = vars || {}; Math.abs(index - curIndex) > length / 2 && (index += index > curIndex ? -length : length); let newIndex = gsap.utils.wrap(0, length, index), time = times[newIndex]; if (time > tl.time() !== index > curIndex && index !== curIndex) { time += tl.duration() * (index > curIndex ? 1 : -1); } if (time < 0 || time > tl.duration()) { vars.modifiers = { time: timeWrap }; } curIndex = newIndex; vars.overwrite = true; gsap.killTweensOf(proxy); return vars.duration === 0 ? tl.time(timeWrap(time)) : tl.tweenTo(time, vars); } tl.toIndex = (index, vars) => toIndex(index, vars); tl.closestIndex = (setCurrent) => { let index = getClosest(times, tl.time(), tl.duration()); if (setCurrent) { curIndex = index; indexIsDirty = false; } return index; }; tl.current = () => (indexIsDirty ? tl.closestIndex(true) : curIndex); tl.next = (vars) => toIndex(tl.current() + 1, vars); tl.previous = (vars) => toIndex(tl.current() - 1, vars); tl.times = times; tl.progress(1, true).progress(0, true); if (config.reversed) { tl.vars.onReverseComplete(); tl.reverse(); } tl.closestIndex(true); lastIndex = curIndex; onChange && onChange(items[curIndex], curIndex); return tl; } gsap.registerPlugin(ScrollTrigger); function initScrollEffect() { gsap.utils.toArray(".splitter-section").forEach((section) => { const img = section.querySelector(".splitter-img"); if (isDesktop) { gsap.to(img, { y: -64, ease: "power1.out", scrollTrigger: { trigger: section, start: "top bottom", end: "bottom top", scrub: 0.8, }, }); } }); } initScrollEffect(); window.addEventListener("resize", function () { ScrollTrigger.refresh(); initScrollEffect(); }); if (document.querySelector(".swiper")) { const swiper = new Swiper(".swiper", { slidesPerView: 1, spaceBetween: 20, breakpoints: { 640: { slidesPerView: 2, spaceBetween: 24, }, 1024: { slidesPerView: 3, spaceBetween: 32, }, }, }); } document.addEventListener("DOMContentLoaded", function () { const hostname = window.location.hostname; const currentLocale = hostname.includes("pancreaticcancernorthamerica.ca") ? "ca" : hostname.includes("pancreaticcancernorthamerica.org") ? "us" : "global"; document.querySelectorAll("[data-locale]").forEach((element) => { const locales = element .getAttribute("data-locale") .split(",") .map((locale) => locale.trim()); if (locales.includes(currentLocale) || locales.includes("global")) { element.style.display = ""; } else { element.style.display = "none"; } }); });