commit 20260703
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* About Us 2 — achievements stats count-up animation.
|
||||
*/
|
||||
(function () {
|
||||
var SECTION_SELECTOR = "#about-us-2 .au2-stats";
|
||||
var VALUE_SELECTOR = ".au2-stats__value[data-au2-count]";
|
||||
var DURATION_MS = 1800;
|
||||
|
||||
function easeOutCubic(t) {
|
||||
return 1 - Math.pow(1 - t, 3);
|
||||
}
|
||||
|
||||
function formatValue(value, useGrouping) {
|
||||
if (!useGrouping) return String(value);
|
||||
return value.toLocaleString("en-US");
|
||||
}
|
||||
|
||||
function parseCountOptions(el) {
|
||||
var raw = el.getAttribute("data-au2-count");
|
||||
var to = raw ? Number(raw) : NaN;
|
||||
if (!Number.isFinite(to)) return null;
|
||||
|
||||
return {
|
||||
to: to,
|
||||
prefix: el.getAttribute("data-au2-prefix") || "",
|
||||
suffix: el.getAttribute("data-au2-suffix") || "",
|
||||
grouping: el.getAttribute("data-au2-grouping") === "true",
|
||||
};
|
||||
}
|
||||
|
||||
function renderValue(el, value, options) {
|
||||
el.textContent =
|
||||
options.prefix + formatValue(value, options.grouping) + options.suffix;
|
||||
}
|
||||
|
||||
function animateValue(el, options) {
|
||||
if (el.dataset.au2Counted === "true") return;
|
||||
el.dataset.au2Counted = "true";
|
||||
|
||||
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
||||
renderValue(el, options.to, options);
|
||||
return;
|
||||
}
|
||||
|
||||
var start = performance.now();
|
||||
|
||||
function tick(now) {
|
||||
var progress = Math.min((now - start) / DURATION_MS, 1);
|
||||
var current = Math.round(easeOutCubic(progress) * options.to);
|
||||
renderValue(el, current, options);
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(tick);
|
||||
} else {
|
||||
renderValue(el, options.to, options);
|
||||
}
|
||||
}
|
||||
|
||||
renderValue(el, 0, options);
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
function initSection(section) {
|
||||
var values = section.querySelectorAll(VALUE_SELECTOR);
|
||||
if (!values.length) return;
|
||||
|
||||
var observer = new IntersectionObserver(
|
||||
function (entries, obs) {
|
||||
entries.forEach(function (entry) {
|
||||
if (!entry.isIntersecting) return;
|
||||
|
||||
values.forEach(function (el) {
|
||||
var options = parseCountOptions(el);
|
||||
if (options) animateValue(el, options);
|
||||
});
|
||||
|
||||
obs.unobserve(entry.target);
|
||||
});
|
||||
},
|
||||
{ threshold: 0.35, rootMargin: "0px 0px -40px 0px" }
|
||||
);
|
||||
|
||||
observer.observe(section);
|
||||
}
|
||||
|
||||
function init() {
|
||||
var section = document.querySelector(SECTION_SELECTOR);
|
||||
if (section) initSection(section);
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Testimonials marquee — same logic as myfundedfutures.com Reviews section:
|
||||
* - Fisher–Yates shuffle, 3 cards per row
|
||||
* - 3 rows × 3 duplicate tracks
|
||||
* - Row speeds: 125s / 75s reverse / 275s (Framer Motion durations → CSS linear)
|
||||
*/
|
||||
(function () {
|
||||
var ROW_CONFIG = [
|
||||
{ duration: 125, reverse: false },
|
||||
{ duration: 75, reverse: true },
|
||||
{ duration: 275, reverse: false },
|
||||
];
|
||||
var TRACK_COUNT = 3;
|
||||
var CARDS_PER_ROW = 3;
|
||||
|
||||
function shuffle(list) {
|
||||
var arr = list.slice();
|
||||
for (var i = arr.length - 1; i > 0; i--) {
|
||||
var j = Math.floor(Math.random() * (i + 1));
|
||||
var tmp = arr[i];
|
||||
arr[i] = arr[j];
|
||||
arr[j] = tmp;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function pickCards(cards, count) {
|
||||
return shuffle(cards).slice(0, Math.min(count, cards.length));
|
||||
}
|
||||
|
||||
function buildTrack(cards, duration, reverse, trackIndex) {
|
||||
var track = document.createElement('div');
|
||||
track.className =
|
||||
'au2-testimonials__track' + (reverse ? ' au2-testimonials__track--reverse' : '');
|
||||
track.style.setProperty('--duration', duration + 's');
|
||||
if (trackIndex > 0) {
|
||||
track.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
|
||||
cards.forEach(function (card) {
|
||||
track.appendChild(card.cloneNode(true));
|
||||
});
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
function buildRow(cards, config) {
|
||||
var row = document.createElement('div');
|
||||
row.className = 'au2-testimonials__row';
|
||||
var picked = pickCards(cards, CARDS_PER_ROW);
|
||||
|
||||
for (var i = 0; i < TRACK_COUNT; i++) {
|
||||
row.appendChild(buildTrack(picked, config.duration, config.reverse, i));
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
function initAu2TestimonialsMarquee() {
|
||||
var root = document.querySelector('[data-au2-testimonials-marquee]');
|
||||
if (!root) return;
|
||||
|
||||
var source = root.querySelector('.au2-testimonials__source');
|
||||
var rowsEl = root.querySelector('.au2-testimonials__rows');
|
||||
if (!source || !rowsEl) return;
|
||||
|
||||
var cards = Array.prototype.slice.call(source.querySelectorAll('.au2-testimonial'));
|
||||
if (!cards.length) return;
|
||||
|
||||
ROW_CONFIG.forEach(function (config) {
|
||||
rowsEl.appendChild(buildRow(cards, config));
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initAu2TestimonialsMarquee);
|
||||
} else {
|
||||
initAu2TestimonialsMarquee();
|
||||
}
|
||||
})();
|
||||
+183
-1
@@ -1,10 +1,183 @@
|
||||
/**
|
||||
* Home hero carousel — autoplay + dot navigation + mobile swipe
|
||||
* Home hero carousel — autoplay + dot navigation + mobile swipe + layout sync
|
||||
*
|
||||
* Layout contract (extensible per banner):
|
||||
* data-hero-layout-reference — slide that defines carousel height
|
||||
* data-hero-layout-anchor — element whose bottom anchors dot position
|
||||
* --hero-coach-dots-gap — gap between anchor bottom and dots top (default 35px)
|
||||
* Mobile: .is-banner-active fixes dots to viewport bottom while banner is visible
|
||||
*/
|
||||
(function () {
|
||||
var AUTOPLAY_MS = 5000;
|
||||
var SWIPE_THRESHOLD = 48;
|
||||
var MOBILE_MQ = '(max-width: 991.98px)';
|
||||
var DESKTOP_MQ = '(min-width: 992px)';
|
||||
|
||||
function readPx(styles, name, fallback) {
|
||||
var value = parseFloat(styles.getPropertyValue(name));
|
||||
return Number.isFinite(value) ? value : fallback;
|
||||
}
|
||||
|
||||
function getLayoutScope(root) {
|
||||
return root.closest('#homepage') || root;
|
||||
}
|
||||
|
||||
function clearMeasuredLayout(scope, root) {
|
||||
[
|
||||
'--hero-reference-content-bottom',
|
||||
'--hero-reference-viewport-height',
|
||||
'--hero-reference-dots-top',
|
||||
'--hero-mobile-dots-reserve',
|
||||
'--hero-dots-y',
|
||||
].forEach(function (name) {
|
||||
scope.style.removeProperty(name);
|
||||
});
|
||||
|
||||
if (root) {
|
||||
var dots = root.querySelector('.home-hero-carousel__dots');
|
||||
if (dots) {
|
||||
dots.removeAttribute('data-hero-dots-positioned');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function syncHeroLayout(root) {
|
||||
var inner = root.querySelector('.home-hero-carousel__inner');
|
||||
var viewport = root.querySelector('.home-hero-carousel__viewport');
|
||||
if (!inner || !viewport) return;
|
||||
|
||||
var referenceSlide = root.querySelector('[data-hero-layout-reference]')
|
||||
|| root.querySelector('[data-home-hero-slide]');
|
||||
if (!referenceSlide) return;
|
||||
|
||||
var anchor = referenceSlide.querySelector('[data-hero-layout-anchor]')
|
||||
|| referenceSlide.querySelector('.home-hero-carousel__ai-coach')
|
||||
|| referenceSlide.querySelector('.home-hero-carousel__actions :last-child');
|
||||
if (!anchor) return;
|
||||
|
||||
var scope = getLayoutScope(root);
|
||||
var styles = getComputedStyle(scope);
|
||||
var coachDotsGap = readPx(styles, '--hero-coach-dots-gap', 35);
|
||||
var dotsHeight = readPx(styles, '--hero-dots-height', 10);
|
||||
var innerPaddingBottom = readPx(styles, '--hero-inner-padding-bottom', 40);
|
||||
var isDesktop = window.matchMedia(DESKTOP_MQ).matches;
|
||||
|
||||
if (isDesktop) {
|
||||
var innerRect = inner.getBoundingClientRect();
|
||||
var viewportRect = viewport.getBoundingClientRect();
|
||||
var anchorRect = anchor.getBoundingClientRect();
|
||||
|
||||
var contentBottom = Math.ceil(anchorRect.bottom - viewportRect.top);
|
||||
var viewportHeight = contentBottom + coachDotsGap;
|
||||
var dotsTop = Math.ceil(anchorRect.bottom - innerRect.top + coachDotsGap);
|
||||
var dotsBottomY = dotsTop + dotsHeight + innerPaddingBottom;
|
||||
|
||||
scope.style.setProperty('--hero-reference-content-bottom', contentBottom + 'px');
|
||||
scope.style.setProperty('--hero-reference-viewport-height', viewportHeight + 'px');
|
||||
scope.style.setProperty('--hero-reference-dots-top', dotsTop + 'px');
|
||||
scope.style.setProperty('--hero-dots-y', dotsBottomY + 'px');
|
||||
root.setAttribute('data-hero-layout-synced', 'desktop');
|
||||
|
||||
var dots = root.querySelector('.home-hero-carousel__dots');
|
||||
if (dots) {
|
||||
dots.setAttribute('data-hero-dots-positioned', 'true');
|
||||
}
|
||||
root.classList.remove('is-banner-active');
|
||||
return;
|
||||
}
|
||||
|
||||
clearMeasuredLayout(scope, root);
|
||||
|
||||
var viewportRect = viewport.getBoundingClientRect();
|
||||
var slideGrid = referenceSlide.querySelector('.home-hero-carousel__slide-grid');
|
||||
var visual = referenceSlide.querySelector('.home-hero-carousel__visual');
|
||||
var mobileFixedDotsBottom = readPx(styles, '--hero-mobile-fixed-dots-bottom', 16);
|
||||
var imageScale = readPx(styles, '--hero-visual-image-scale', 1.06);
|
||||
|
||||
if (!slideGrid) return;
|
||||
|
||||
var visualBottom = visual
|
||||
? visual.getBoundingClientRect().bottom - viewportRect.top
|
||||
: slideGrid.getBoundingClientRect().bottom - viewportRect.top;
|
||||
|
||||
if (visual) {
|
||||
var img = visual.querySelector('img');
|
||||
if (img && imageScale > 1) {
|
||||
var imgHeight = img.getBoundingClientRect().height;
|
||||
visualBottom += Math.ceil((imgHeight * imageScale - imgHeight) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
var viewportHeight = Math.ceil(visualBottom);
|
||||
var dotsReserve = dotsHeight + mobileFixedDotsBottom;
|
||||
var homepageRect = scope.getBoundingClientRect();
|
||||
var dotsBottomY = Math.ceil(
|
||||
viewportRect.top - homepageRect.top + viewportHeight + dotsReserve + innerPaddingBottom
|
||||
);
|
||||
|
||||
scope.style.setProperty('--hero-reference-viewport-height', viewportHeight + 'px');
|
||||
scope.style.setProperty('--hero-mobile-dots-reserve', dotsReserve + 'px');
|
||||
scope.style.setProperty('--hero-dots-y', dotsBottomY + 'px');
|
||||
|
||||
root.setAttribute('data-hero-layout-synced', 'mobile');
|
||||
}
|
||||
|
||||
function bindMobileFixedDots(root) {
|
||||
if (typeof IntersectionObserver === 'undefined') return;
|
||||
|
||||
var mobileMq = window.matchMedia(MOBILE_MQ);
|
||||
var observer = new IntersectionObserver(
|
||||
function (entries) {
|
||||
if (!mobileMq.matches) {
|
||||
root.classList.remove('is-banner-active');
|
||||
return;
|
||||
}
|
||||
root.classList.toggle('is-banner-active', entries[0].isIntersecting);
|
||||
},
|
||||
{
|
||||
threshold: 0,
|
||||
rootMargin: '0px 0px -8% 0px',
|
||||
}
|
||||
);
|
||||
|
||||
observer.observe(root);
|
||||
|
||||
mobileMq.addEventListener('change', function () {
|
||||
if (!mobileMq.matches) {
|
||||
root.classList.remove('is-banner-active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function bindHeroLayoutSync(root) {
|
||||
var sync = function () {
|
||||
syncHeroLayout(root);
|
||||
};
|
||||
|
||||
sync();
|
||||
|
||||
if (typeof ResizeObserver !== 'undefined') {
|
||||
var observer = new ResizeObserver(sync);
|
||||
observer.observe(root);
|
||||
var inner = root.querySelector('.home-hero-carousel__inner');
|
||||
var referenceSlide = root.querySelector('[data-hero-layout-reference]')
|
||||
|| root.querySelector('[data-home-hero-slide]');
|
||||
if (inner) observer.observe(inner);
|
||||
if (referenceSlide) observer.observe(referenceSlide);
|
||||
var visual = referenceSlide.querySelector('.home-hero-carousel__visual');
|
||||
if (visual) observer.observe(visual);
|
||||
}
|
||||
|
||||
var resizeTimer;
|
||||
window.addEventListener('resize', function () {
|
||||
window.clearTimeout(resizeTimer);
|
||||
resizeTimer = window.setTimeout(sync, 100);
|
||||
});
|
||||
|
||||
if (document.fonts && document.fonts.ready) {
|
||||
document.fonts.ready.then(sync);
|
||||
}
|
||||
}
|
||||
|
||||
function initHomeHeroCarousel(root) {
|
||||
if (root.getAttribute('data-carousel-ready') === 'true') return;
|
||||
@@ -133,6 +306,8 @@
|
||||
});
|
||||
|
||||
bindTouchSwipe();
|
||||
bindHeroLayoutSync(root);
|
||||
bindMobileFixedDots(root);
|
||||
setSlide(0);
|
||||
startAutoplay();
|
||||
}
|
||||
@@ -142,6 +317,13 @@
|
||||
}
|
||||
|
||||
window.AifoHomeHeroCarouselInit = initHomeHeroCarousel;
|
||||
window.AifoHomeHeroCarouselSyncLayout = function (root) {
|
||||
if (root) {
|
||||
syncHeroLayout(root);
|
||||
return;
|
||||
}
|
||||
document.querySelectorAll('[data-home-hero-carousel]').forEach(syncHeroLayout);
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', boot);
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
var NAV_URL = "/components/nav.html";
|
||||
var FOOTER_URL = "/components/footer.html";
|
||||
var FOOT_CSS_ID = "aifo-foot-styles";
|
||||
var NAV_CSS_ID = "aifo-nav-styles";
|
||||
var NAV_CSS_URL = "/assets/css/nav.css";
|
||||
var AIFO_BTN_CSS_ID = "aifo-btn-styles";
|
||||
var AIFO_BTN_CSS_URL = "/assets/css/aifo-btn.css";
|
||||
var ICONFONT_CSS_ID = "aifo-iconfont-styles";
|
||||
var ICONFONT_CSS_URL = "/assets/vendor/icon/iconfont.css";
|
||||
var FLAG_ICONS_CSS_ID = "aifo-flag-icons-styles";
|
||||
var FLAG_ICONS_CSS_URL = "/assets/flagicon/css/flag-icons.min.css";
|
||||
var FOOT_JS_URL = "/js/footer.js";
|
||||
@@ -114,6 +120,30 @@
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
function ensureNavStyles() {
|
||||
if (!document.getElementById(ICONFONT_CSS_ID)) {
|
||||
var iconLink = document.createElement("link");
|
||||
iconLink.id = ICONFONT_CSS_ID;
|
||||
iconLink.rel = "stylesheet";
|
||||
iconLink.href = ICONFONT_CSS_URL;
|
||||
document.head.appendChild(iconLink);
|
||||
}
|
||||
if (!document.getElementById(NAV_CSS_ID)) {
|
||||
var navLink = document.createElement("link");
|
||||
navLink.id = NAV_CSS_ID;
|
||||
navLink.rel = "stylesheet";
|
||||
navLink.href = NAV_CSS_URL;
|
||||
document.head.appendChild(navLink);
|
||||
}
|
||||
if (!document.getElementById(AIFO_BTN_CSS_ID)) {
|
||||
var btnLink = document.createElement("link");
|
||||
btnLink.id = AIFO_BTN_CSS_ID;
|
||||
btnLink.rel = "stylesheet";
|
||||
btnLink.href = AIFO_BTN_CSS_URL;
|
||||
document.head.appendChild(btnLink);
|
||||
}
|
||||
}
|
||||
|
||||
function loadFooterScript(callback) {
|
||||
if (footJsLoaded) {
|
||||
callback();
|
||||
@@ -139,6 +169,25 @@
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
|
||||
var navIconsJsLoading = false;
|
||||
var navIconsJsLoaded = false;
|
||||
|
||||
function loadNavIconsScript() {
|
||||
if (navIconsJsLoaded || navIconsJsLoading) return;
|
||||
navIconsJsLoading = true;
|
||||
var script = document.createElement("script");
|
||||
script.src = "/js/nav-icons.js";
|
||||
script.onload = function () {
|
||||
navIconsJsLoaded = true;
|
||||
navIconsJsLoading = false;
|
||||
};
|
||||
script.onerror = function () {
|
||||
navIconsJsLoading = false;
|
||||
console.error("[layout.js] Failed to load /js/nav-icons.js");
|
||||
};
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
|
||||
function initFooterModule(root) {
|
||||
ensureFooterStyles();
|
||||
updateCopyrightYear(root);
|
||||
@@ -165,9 +214,11 @@
|
||||
var navHtml = parts[0];
|
||||
var footerHtml = parts[1];
|
||||
if (navHtml) {
|
||||
ensureNavStyles();
|
||||
ensureFlagIconsStyles();
|
||||
var navRoot = injectHtml("nav-container", navHtml);
|
||||
if (navRoot) highlightActiveNav(navRoot);
|
||||
loadNavIconsScript();
|
||||
}
|
||||
if (footerHtml) {
|
||||
var footerRoot = injectHtml("footer-container", footerHtml);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Nav submenu iconfont helpers.
|
||||
* Registry: data/nav-icon-registry.json
|
||||
*
|
||||
* After uploading new glyphs to iconfont.cn and replacing iconfont.css:
|
||||
* window.aifoApplyNavIconTargets()
|
||||
* This swaps data-nav-icon-target classes onto each submenu icon.
|
||||
*/
|
||||
(function () {
|
||||
function applyNavIconTargets(root) {
|
||||
if (!root) return 0;
|
||||
var nodes = root.querySelectorAll("[data-nav-icon-target]");
|
||||
var count = 0;
|
||||
nodes.forEach(function (el) {
|
||||
var target = el.getAttribute("data-nav-icon-target");
|
||||
if (!target) return;
|
||||
el.className = "icon iconfont " + target;
|
||||
count += 1;
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
window.aifoApplyNavIconTargets = function () {
|
||||
var nav = document.getElementById("nav");
|
||||
var container = document.getElementById("nav-container");
|
||||
var count = applyNavIconTargets(nav) + applyNavIconTargets(container);
|
||||
console.info("[nav-icons] Applied " + count + " target icon class(es).");
|
||||
return count;
|
||||
};
|
||||
|
||||
document.addEventListener("aifo:layout-ready", function () {
|
||||
if (window.AIFO_NAV_USE_TARGET_ICONS === true) {
|
||||
window.aifoApplyNavIconTargets();
|
||||
}
|
||||
});
|
||||
})();
|
||||
+8
-8
@@ -1,22 +1,22 @@
|
||||
/**
|
||||
* Partnership page — FAQ accordion.
|
||||
* Partnership page — FAQ accordion (Figma export style).
|
||||
*/
|
||||
(function () {
|
||||
function initFaq(root) {
|
||||
if (!root) return;
|
||||
|
||||
root.querySelectorAll(".faq-header").forEach(function (header) {
|
||||
root.querySelectorAll(".partnership-faq__header").forEach(function (header) {
|
||||
header.addEventListener("click", function () {
|
||||
var item = header.closest(".faq-item");
|
||||
var item = header.closest(".partnership-faq__item");
|
||||
if (!item) return;
|
||||
|
||||
var isActive = item.classList.contains("active");
|
||||
root.querySelectorAll(".faq-item").forEach(function (el) {
|
||||
el.classList.remove("active");
|
||||
var isOpen = item.classList.contains("is-open");
|
||||
root.querySelectorAll(".partnership-faq__item").forEach(function (el) {
|
||||
el.classList.remove("is-open");
|
||||
});
|
||||
|
||||
if (!isActive) {
|
||||
item.classList.add("active");
|
||||
if (!isOpen) {
|
||||
item.classList.add("is-open");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Loads support-cta.section.html into #support-cta-module
|
||||
*/
|
||||
(function () {
|
||||
var MODULE_URL = '/pages/modules/support-cta.section.html';
|
||||
|
||||
function loadSupportCtaModule() {
|
||||
var container = document.getElementById('support-cta-module');
|
||||
if (!container) return;
|
||||
|
||||
fetch(MODULE_URL, { credentials: 'same-origin' })
|
||||
.then(function (res) {
|
||||
if (!res.ok) throw new Error('Failed to load ' + MODULE_URL);
|
||||
return res.text();
|
||||
})
|
||||
.then(function (html) {
|
||||
container.innerHTML = html;
|
||||
document.dispatchEvent(new CustomEvent('aifo:support-cta-ready'));
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error('[support-cta-module.js]', err);
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', loadSupportCtaModule);
|
||||
} else {
|
||||
loadSupportCtaModule();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user