/* ============================================================================ ANAX Capital — Motion Toolkit (no external libs, vanilla) Patterns: smooth-scroll, scroll-reveal, magnetic CTA, counters, tilt, sticky-shrinking nav, hero cursor-gradient, parallax, page transitions, marquees (CSS only — JS just pauses). Respects prefers-reduced-motion. ============================================================================ */ /* ---------------------------------------------------------------------------- UTM / click-id attribution capture — runs site-wide on every page load. Persists last-touch UTM + the landing full_link for 90 days (localStorage) so attribution SURVIVES internal navigation (which drops the query string). form.js reads "anax_attribution_v1" at registration and forwards it to CRM. ---------------------------------------------------------------------------- */ (function () { 'use strict'; try { var KEY = 'anax_attribution_v1'; var TTL = 90 * 24 * 60 * 60 * 1000; // 90 days var qs = new URLSearchParams(location.search); var utm = {}; qs.forEach(function (v, k) { if (k.indexOf('utm_') === 0 && v) utm[k] = v; }); var hasUtm = false; for (var _k in utm) { if (utm.hasOwnProperty(_k)) { hasUtm = true; break; } } var hasClickId = qs.has('gclid') || qs.has('fbclid') || qs.has('bi_source_id') || qs.has('msclkid') || qs.has('ttclid'); var prev = null; try { prev = JSON.parse(localStorage.getItem(KEY) || 'null'); } catch (e) {} if (prev && prev.ts && (Date.now() - prev.ts) > TTL) { // expired (>90d) prev = null; try { localStorage.removeItem(KEY); } catch (e) {} // purge so stale UTM is never re-sent } // Last-touch: (re)write ONLY for a genuinely NEW campaign or a fresh click-id. // Propagated internal links carry the SAME utm_* (no click-id) — detected as // identical, so the original landing ts + full_link (with gclid) are preserved; // plain navigation (no utm at all) never touches the stored value. if (hasUtm || hasClickId) { var sameUtm = false; if (prev && prev.utm) { var pk = Object.keys(prev.utm), nk = Object.keys(utm); sameUtm = pk.length === nk.length && nk.every(function (k) { return prev.utm[k] === utm[k]; }); } if (!prev || !sameUtm || hasClickId) { localStorage.setItem(KEY, JSON.stringify({ utm: utm, // utm_* keys only (CRM accepts utm_* + full_link) full_link: location.href, // landing URL — carries gclid / bi_source_id for per-ad attribution ts: Date.now() })); } } } catch (e) { /* storage blocked (private mode) — fail silent; form falls back to URL */ } })(); /* ---------------------------------------------------------------------------- UTM URL PROPAGATION — keep utm_* VISIBLE in the address bar on every page. (1) self-heals the current URL (history.replaceState) when utm is missing but was captured earlier, and (2) appends utm_* to every same-origin internal link so a click carries it forward. Result: the UTM the visitor arrived with stays in the URL across the whole site. utm_* only (clean URLs); gclid/bi_source_id stay in localStorage full_link for CRM attribution. ---------------------------------------------------------------------------- */ (function () { 'use strict'; try { var TTL = 90 * 24 * 60 * 60 * 1000; var here = new URL(location.href); var active = {}; // utm_* already in the URL win here.searchParams.forEach(function (v, k) { if (k.indexOf('utm_') === 0 && v) active[k] = v; }); var hasActive = false; for (var a in active) { if (active.hasOwnProperty(a)) { hasActive = true; break; } } // else fall back to the captured (non-expired) attribution if (!hasActive) { try { var s = JSON.parse(localStorage.getItem('anax_attribution_v1') || 'null'); if (s && s.ts && (Date.now() - s.ts) <= TTL && s.utm) { for (var k1 in s.utm) { if (s.utm.hasOwnProperty(k1)) active[k1] = s.utm[k1]; } } } catch (e) {} } var keys = []; for (var k2 in active) { if (active.hasOwnProperty(k2)) keys.push(k2); } if (!keys.length) return; // visitor has no UTM — nothing to propagate // (1) self-heal the address bar (no reload, replaces the current history entry) var changed = false; keys.forEach(function (k) { if (!here.searchParams.has(k)) { here.searchParams.set(k, active[k]); changed = true; } }); if (changed && window.history && history.replaceState) { try { history.replaceState(history.state, '', here.toString()); } catch (e) {} } // (2) decorate same-origin internal links so navigation keeps the UTM var decorate = function (el) { try { var href = el.getAttribute('href'); if (!href || href.charAt(0) === '#') return; if (/^(mailto:|tel:|sms:|javascript:|whatsapp:|data:)/i.test(href)) return; var u = new URL(href, location.href); if (u.origin !== location.origin) return; // external link — leave untouched var touched = false; keys.forEach(function (k) { if (!u.searchParams.has(k)) { u.searchParams.set(k, active[k]); touched = true; } }); if (touched) el.setAttribute('href', u.toString()); } catch (e) {} }; var run = function () { var as = document.getElementsByTagName('a'); for (var i = 0; i < as.length; i++) decorate(as[i]); }; if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', run); else run(); // fallback for links injected later (menus / dynamic): decorate on the way to navigation document.addEventListener('click', function (ev) { var t = ev.target; var link = t && t.closest ? t.closest('a[href]') : null; if (link) decorate(link); }, true); } catch (e) {} })(); (() => { 'use strict'; const RM = matchMedia('(prefers-reduced-motion: reduce)').matches; const $ = (s, r = document) => r.querySelector(s); const $$ = (s, r = document) => Array.from(r.querySelectorAll(s)); /* ----- Reveal-on-scroll opt-in via html.js-anim ----- */ // Above-the-fold items get .in BEFORE js-anim flips, so CSS gate never hides them. const ABOVE = () => { const vh = window.innerHeight || 800; document.querySelectorAll('[data-reveal]:not(.in), [data-reveal-chunk]:not(.in)').forEach(el => { const r = el.getBoundingClientRect(); if (r.top < vh * 1.05) el.classList.add('in'); }); }; ABOVE(); document.documentElement.classList.add('js-anim'); // Final safety: reveal anything still hidden after 2.5s setTimeout(() => { document.querySelectorAll('[data-reveal]:not(.in), [data-reveal-chunk]:not(.in)').forEach(el => el.classList.add('in')); }, 2500); /* ----- Smooth scroll for anchor links ----- */ document.addEventListener('click', (e) => { const a = e.target.closest('a[href^="#"]'); if (!a) return; const id = a.getAttribute('href'); if (!id || id === '#') return; const t = document.querySelector(id); if (!t) return; e.preventDefault(); t.scrollIntoView({ behavior: RM ? 'auto' : 'smooth', block: 'start' }); }); /* ----- Scroll reveal ----- */ if (!RM) { const io = new IntersectionObserver((entries) => { entries.forEach(en => { if (en.isIntersecting) { en.target.classList.add('in'); io.unobserve(en.target); } }); }, { threshold: 0.12, rootMargin: '0px 0px -8% 0px' }); $$('[data-reveal]').forEach(el => io.observe(el)); } else { $$('[data-reveal]').forEach(el => el.classList.add('in')); } /* ----- Number counters ----- */ const easeOut = (t) => 1 - Math.pow(1 - t, 3); const animateCounter = (el) => { const target = parseFloat(el.dataset.count); const suffix = el.dataset.suffix || ''; const prefix = el.dataset.prefix || ''; const dec = parseInt(el.dataset.decimals || '0', 10); const dur = 1400; const t0 = performance.now(); const tick = (now) => { const p = Math.min(1, (now - t0) / dur); const v = target * easeOut(p); el.textContent = prefix + v.toFixed(dec) + suffix; if (p < 1) requestAnimationFrame(tick); }; requestAnimationFrame(tick); }; const ioCount = new IntersectionObserver((entries) => { entries.forEach(en => { if (en.isIntersecting) { if (!RM) animateCounter(en.target); else { const t = en.target; t.textContent = (t.dataset.prefix||'') + t.dataset.count + (t.dataset.suffix||''); } ioCount.unobserve(en.target); } }); }, { threshold: 0.5 }); $$('[data-count]').forEach(el => ioCount.observe(el)); /* ----- Magnetic CTAs (subtle pull toward cursor) ----- */ if (!RM && matchMedia('(hover: hover)').matches) { $$('.magnetic').forEach(btn => { btn.addEventListener('mousemove', (e) => { const r = btn.getBoundingClientRect(); const x = e.clientX - r.left - r.width / 2; const y = e.clientY - r.top - r.height / 2; btn.style.transform = `translate(${x * 0.18}px, ${y * 0.22}px)`; }); btn.addEventListener('mouseleave', () => btn.style.transform = ''); }); } /* ----- 3D tilt on .tilt cards ----- */ if (!RM && matchMedia('(hover: hover)').matches) { $$('.tilt').forEach(card => { const inner = card.querySelector('.tilt-inner') || card; card.addEventListener('mousemove', (e) => { const r = card.getBoundingClientRect(); const x = (e.clientX - r.left) / r.width; const y = (e.clientY - r.top) / r.height; const rx = (0.5 - y) * 8; const ry = (x - 0.5) * 10; inner.style.transform = `rotateX(${rx}deg) rotateY(${ry}deg)`; }); card.addEventListener('mouseleave', () => inner.style.transform = ''); }); } /* ----- Hero cursor-follow gradient + arrow parallax ----- */ const hero = $('.hero'); if (hero && !RM) { hero.addEventListener('mousemove', (e) => { const r = hero.getBoundingClientRect(); const mx = ((e.clientX - r.left) / r.width) * 100; const my = ((e.clientY - r.top) / r.height) * 100; hero.style.setProperty('--mx', `${mx}%`); hero.style.setProperty('--my', `${my}%`); }); } if (!RM) { const arrow = $('.hero-art .arrow'); if (arrow) { let raf = 0; window.addEventListener('scroll', () => { if (raf) return; raf = requestAnimationFrame(() => { const y = Math.min(120, window.scrollY * 0.12); arrow.style.transform = `translateY(${y}px) rotate(${y * 0.08}deg)`; raf = 0; }); }, { passive: true }); } } /* ----- Sticky shrinking nav · ALWAYS visible ----- Header stays sticky at top, never hidden. Toggles .scrolled + .shrink only. rAF-throttled + hysteresis to prevent flicker. */ const bar = $('.topbar'); if (bar) { let ticking = false; let scrolledOn = false; let shrinkOn = false; // Ensure header is always visible — remove any hide class bar.classList.remove('hidden-top'); const update = () => { const y = window.scrollY; // Scrolled band: add at >12px, remove at <4px if (!scrolledOn && y > 12) { bar.classList.add('scrolled'); scrolledOn = true; } else if (scrolledOn && y < 4) { bar.classList.remove('scrolled'); scrolledOn = false; } // Shrink band: add at >100px, remove at <70px if (!shrinkOn && y > 100) { bar.classList.add('shrink'); shrinkOn = true; } else if (shrinkOn && y < 70) { bar.classList.remove('shrink'); shrinkOn = false; } ticking = false; }; const onScroll = () => { if (!ticking) { requestAnimationFrame(update); ticking = true; } }; update(); window.addEventListener('scroll', onScroll, { passive: true }); } /* ----- Burger + mobile nav drawer (v3 · 2026-05-18) ----- Drawer structure (after JS): ul#primary-nav.nav (fixed container, .open class toggles slide-from-right) ├─ div.mnav-head (injected: logo + close) ├─ li ... (existing nav items — stay as direct children) └─ div.mnav-foot (injected: Open Account + Login + Call CTA) Sticky head (top) + sticky foot (bottom) + scroll on navUl itself. */ (function setupMobileNav() { const burger = document.getElementById('nav-burger') || document.querySelector('.burger'); const navUl = document.getElementById('primary-nav'); if (!burger || !navUl) return; // Idempotency guard — bail if drawer head already injected (prevents double close X) if (navUl.querySelector(':scope > .mnav-head')) return; // Resolve logo path — keep parity with topbar logo so relative paths work from any page const topbarLogo = document.querySelector('.topbar .brand img'); const logoSrc = topbarLogo ? topbarLogo.getAttribute('src') : 'assets/logo/anax-logo-light.png'; // Defensive: remove any stale drawer fragments (e.g. from double-loaded script) navUl.querySelectorAll(':scope > .mnav-head, :scope > .mnav-foot').forEach(n => n.remove()); // Build drawer header const head = document.createElement('div'); head.className = 'mnav-head'; head.innerHTML = ` ANAX Capital `; // Keep
  • children as direct descendants of navUl so existing desktop CSS keeps working. // Drawer uses sticky head/foot + middle scroll via overflow on navUl itself on mobile. // Build footer with cloned CTAs const foot = document.createElement('div'); foot.className = 'mnav-foot'; const loginHref = document.querySelector('.topbar .nav-cta a[href*="portal.anaxcapital"]')?.getAttribute('href') || 'https://portal.anaxcapital.ae'; const registerHref = document.querySelector('.topbar .nav-cta a[href*="register"]')?.getAttribute('href') || 'register.html'; foot.innerHTML = ` Open Live Account Login to Portal Call 800 MYANAX `; // Assemble: head pinned top, li's flow in middle, foot pinned bottom (via CSS sticky on mobile) navUl.insertBefore(head, navUl.firstChild); navUl.appendChild(foot); // Pure class toggle — CSS display:none/flex handles visibility in mobile-menu.css const closeMenu = () => { navUl.classList.remove('open'); document.body.classList.remove('menu-open'); burger.setAttribute('aria-expanded', 'false'); navUl.querySelectorAll('li.open').forEach(li => { li.classList.remove('open'); li.querySelector('button[aria-haspopup]')?.setAttribute('aria-expanded', 'false'); }); }; const openMenu = () => { navUl.classList.add('open'); document.body.classList.add('menu-open'); burger.setAttribute('aria-expanded', 'true'); }; // Track last-open timestamp to ignore same-tick close events from doc listener let lastOpenAt = 0; burger.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation(); if (navUl.classList.contains('open')) { closeMenu(); } else { openMenu(); lastOpenAt = Date.now(); } }, true); // capture phase so it fires before document listener head.querySelector('.mnav-close').addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); closeMenu(); }); // Accordion submenu toggle — mobile drawer + desktop click toggle navUl.querySelectorAll('li > button[aria-haspopup]').forEach(button => { button.setAttribute('aria-expanded', 'false'); button.addEventListener('click', e => { e.preventDefault(); const li = button.parentElement; // Close any other open submenu navUl.querySelectorAll('li.open').forEach(openLi => { if (openLi !== li) { openLi.classList.remove('open'); openLi.querySelector('button[aria-haspopup]')?.setAttribute('aria-expanded', 'false'); } }); const isOpen = li.classList.toggle('open'); button.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); }); }); // Click outside closes open mega on desktop (mobile drawer has own close logic) document.addEventListener('click', e => { if (!matchMedia('(min-width: 1101px)').matches) return; if (e.target.closest('.topbar .nav > li')) return; navUl.querySelectorAll('li.open').forEach(openLi => { openLi.classList.remove('open'); openLi.querySelector('button[aria-haspopup]')?.setAttribute('aria-expanded', 'false'); }); }); // Close on link click inside the drawer (any link, except the head brand link should still navigate then close) navUl.addEventListener('click', e => { const link = e.target.closest('a'); if (link) closeMenu(); }); // Click outside drawer closes it — but ignore clicks within 350ms of open // (prevents same-tick close from burger tap bubbling on touch devices) document.addEventListener('click', e => { if (!document.body.classList.contains('menu-open')) return; if (Date.now() - lastOpenAt < 350) return; if (navUl.contains(e.target) || burger.contains(e.target)) return; closeMenu(); }); // Escape key closes document.addEventListener('keydown', e => { if (e.key === 'Escape' && document.body.classList.contains('menu-open')) closeMenu(); }); // Close on resize past breakpoint window.addEventListener('resize', () => { if (matchMedia('(min-width: 1101px)').matches && document.body.classList.contains('menu-open')) closeMenu(); }); })(); /* ----- FAQ ----- */ $$('.faq-item').forEach(it => { const q = it.querySelector('.faq-q'); q.addEventListener('click', () => { const open = it.classList.toggle('open'); q.setAttribute('aria-expanded', open ? 'true' : 'false'); }); }); /* ----- Cookie banner ----- */ const cookie = $('#cookie-banner'); if (cookie) { const KEY = 'anax_cookie_v1'; if (!localStorage.getItem(KEY)) setTimeout(() => { cookie.classList.add('show'); document.body.classList.add('cookie-visible'); }, 800); cookie.querySelectorAll('[data-cookie]').forEach(b => { b.addEventListener('click', () => { localStorage.setItem(KEY, b.dataset.cookie); cookie.classList.remove('show'); document.body.classList.remove('cookie-visible'); }); }); } /* ----- Risk bar — DISABLED site-wide (Anax preference). Completely remove the element from DOM so neither the strip nor its dismiss X are rendered. Also kills the "2 close buttons on mobile menu" issue (risk-bar's ✕ used to overlap with the drawer's close X). */ const risk = $('#risk-bar'); if (risk) risk.remove(); /* ----- TradingView main-product cards — auto-hide any card whose widget fails to load (invalid symbol, blocked, or empty). Keeps only cards actually showing live prices, per Anax preference. Used on every /markets/* page (forex, metals, indices, energies, equity, uae-stocks, all-instruments). Runs once after a generous load delay, then re-checks. */ (function tvCardCleanup(){ const tvCards = document.querySelectorAll('.tv-main-card'); if (!tvCards.length) return; function cardLoaded(card){ const widget = card.querySelector('.tradingview-widget-container'); if (!widget) return false; const inner = widget.querySelector('.tradingview-widget-container__widget'); if (!inner) return false; // TradingView injects an iframe (mini-symbol-overview) once loaded const iframe = inner.querySelector('iframe'); if (!iframe) return false; const rect = iframe.getBoundingClientRect(); // Iframe must have a reasonable rendered size if (rect.width < 120 || rect.height < 60) return false; // Iframe must have a src pointing to TradingView const src = iframe.getAttribute('src') || ''; if (!/tradingview/.test(src)) return false; return true; } function hideCard(card){ card.style.display = 'none'; card.setAttribute('aria-hidden', 'true'); card.classList.add('tv-card-hidden'); } function pruneOnce(){ let hidden = 0, total = tvCards.length; tvCards.forEach(c => { if (!cardLoaded(c)) { hideCard(c); hidden++; } }); // If carousel has dots/nav, dispatch event so carousel JS (if any) re-counts const carousel = document.querySelector('.tv-main-carousel'); if (carousel && hidden > 0) { carousel.dispatchEvent(new CustomEvent('tv-cards-pruned', { detail: { hidden, total } })); } // If ALL cards failed, hide the entire section to avoid an empty block if (hidden === total) { const section = document.querySelector('section:has(.tv-main-carousel)'); if (section) section.style.display = 'none'; } } // First check at 8s (TradingView usually loads in 2-5s; this gives slow connections buffer) setTimeout(pruneOnce, 8000); // Safety re-check at 14s to catch late-loaders setTimeout(pruneOnce, 14000); })(); /* ----- Spreads ticker — refresh every 8s with synthetic micro-tick (UI only) ----- */ const ticker = $('.spreads-list'); if (ticker) { const tick = () => { $$('.spread-row', ticker).forEach(row => { const bidEl = row.querySelector('.bid'); const askEl = row.querySelector('.ask'); if (!bidEl || !askEl) return; const bid = parseFloat(bidEl.dataset.base); const ask = parseFloat(askEl.dataset.base); const dp = parseInt(row.dataset.dp || '5', 10); const jitter = () => (Math.random() - 0.5) * (bid * 0.0004); bidEl.textContent = (bid + jitter()).toFixed(dp); askEl.textContent = (ask + jitter()).toFixed(dp); }); }; tick(); if (!RM) setInterval(tick, 4000); } /* ----- Hero tagline rotator + image stage sync ----- Stage 0 (arrow / "Max Possibilities") plays ONLY ONCE at page load. After first cycle reaches end, rotator skips index 0 forever — only stages 1/2/3 (Get.Set.Grow., Compass, Signal) cycle continuously. */ const rotator = $('#tag-rotator'); const stage = $('#hero-stage'); const progress = $('#hero-progress'); if (rotator) { const lines = $$('.tag-line', rotator); const stages = stage ? $$('.stage-img', stage) : []; const dots = progress ? $$('.dot-on', progress) : []; let idx = 0; let firstCycleDone = false; dots.forEach((d, i) => d.classList.toggle('live', i === 0)); const swap = () => { let next = (idx + 1) % lines.length; // After first full cycle, stage 0 (arrow) retires — never returns if (next === 0) firstCycleDone = true; if (firstCycleDone && next === 0) { next = 1 % lines.length; } lines[idx].classList.remove('active'); lines[next].classList.add('active'); if (stages.length) { stages[idx]?.classList.remove('active'); stages[next]?.classList.add('active'); } dots.forEach((d, i) => d.classList.toggle('live', i === next)); idx = next; }; if (!RM) setInterval(swap, 3600); } /* ----- Interactive markets picker ----- */ const mp = $('#markets-pick'); if (mp) { const tabs = $$('.mp-tab', mp); const panels = $$('.mp-panel', mp); const setActive = (key) => { tabs.forEach(t => t.classList.toggle('active', t.dataset.mp === key)); panels.forEach(p => p.classList.toggle('active', p.dataset.mpPanel === key)); }; tabs.forEach(t => t.addEventListener('click', () => setActive(t.dataset.mp))); // Auto-cycle every 5s if user hasn't clicked — desktop only. // On mobile/tablet (≤1100), absolute-positioned inactive panels overlap // visually mid-transition because mp-stage padding collapses; user swipes // horizontally between tabs instead. let userClicked = false; tabs.forEach(t => t.addEventListener('click', () => { userClicked = true; })); let i = 0; if (!RM && matchMedia('(min-width: 1101px)').matches) { setInterval(() => { if (userClicked) return; i = (i + 1) % tabs.length; setActive(tabs[i].dataset.mp); }, 5000); } } /* ----- Scroll-pinned steps cinema (desktop + mobile spec) ----- */ const cinema = document.getElementById('steps-cinema'); if (cinema) { const frames = cinema.querySelectorAll('.sc-frame'); const dots = cinema.querySelectorAll('.sc-dot'); const fill = cinema.querySelector('.sc-progress-fill'); const onCinemaScroll = () => { const rect = cinema.getBoundingClientRect(); const vh = window.innerHeight; const total = cinema.offsetHeight - vh; const scrolled = Math.min(Math.max(-rect.top, 0), total); const p = total > 0 ? scrolled / total : 0; const idx = Math.min(3, Math.floor(p * 4)); frames.forEach((f, i) => f.classList.toggle('active', i === idx)); dots.forEach((d, i) => d.classList.toggle('lit', i <= idx)); if (fill) fill.style.height = Math.min(100, p * 100).toFixed(1) + '%'; }; onCinemaScroll(); window.addEventListener('scroll', onCinemaScroll, { passive: true }); window.addEventListener('resize', onCinemaScroll, { passive: true }); } /* ----- Step-path SVG line draw + cascading reveal on scroll-in ----- */ $$('.steps-anim').forEach(stepsEl => { const svgPath = stepsEl.querySelector('.step-line'); if (svgPath) { const len = svgPath.getTotalLength ? svgPath.getTotalLength() : 1200; svgPath.style.strokeDasharray = len; svgPath.style.strokeDashoffset = len; } const ioStep = new IntersectionObserver((es) => { es.forEach(e => { if (e.isIntersecting) { if (svgPath) { svgPath.style.transition = 'stroke-dashoffset 1.8s var(--ease)'; svgPath.style.strokeDashoffset = '0'; } stepsEl.querySelectorAll('.step').forEach((step, idx) => { step.classList.add('in-view'); setTimeout(() => step.classList.add('lit'), 220 + idx * 320); }); ioStep.unobserve(e.target); } }); }, { threshold: 0.25 }); ioStep.observe(stepsEl); }); /* ----- Tab switcher (used on register page) ----- */ $$('.tab-bar').forEach(bar => { const targetSel = bar.dataset.target; bar.querySelectorAll('button').forEach(btn => { btn.addEventListener('click', () => { bar.querySelectorAll('button').forEach(b => b.classList.toggle('active', b === btn)); const go = btn.dataset.go; if (targetSel) { document.querySelectorAll(`${targetSel} .tab-panel`).forEach(p => { p.classList.toggle('active', p.dataset.panel === go); }); } }); }); }); /* ----- Mobile sticky bottom CTA bar (on conversion-relevant pages) ----- */ if (matchMedia('(max-width: 640px)').matches) { const bb = $('.bottom-bar-mobile'); if (bb) document.body.classList.add('has-bottom-bar'); } /* ----- Timelapse text chunk reveal ----- */ if (!RM) { const io2 = new IntersectionObserver((entries) => { entries.forEach(en => { if (en.isIntersecting) { en.target.classList.add('in'); io2.unobserve(en.target); } }); }, { threshold: 0.18, rootMargin: '0px 0px -6% 0px' }); $$('[data-reveal-chunk]').forEach(el => io2.observe(el)); } else { $$('[data-reveal-chunk]').forEach(el => el.classList.add('in')); } /* ----- Scroll progress bar (vertical, left edge) ----- */ let scrollProgress = document.getElementById('scroll-progress'); if (!scrollProgress) { scrollProgress = document.createElement('div'); scrollProgress.id = 'scroll-progress'; scrollProgress.className = 'scroll-progress'; scrollProgress.setAttribute('aria-hidden', 'true'); document.body.appendChild(scrollProgress); } const updateProgress = () => { const h = document.documentElement; const max = h.scrollHeight - h.clientHeight; const pct = max > 0 ? (window.scrollY / max) * 100 : 0; scrollProgress.style.setProperty('--sp', pct.toFixed(2) + '%'); }; updateProgress(); window.addEventListener('scroll', updateProgress, { passive: true }); window.addEventListener('resize', updateProgress, { passive: true }); /* ----- KPI sparklines: append a mini SVG to each .kpi ----- */ $$('.kpi').forEach((kpi, i) => { if (kpi.querySelector('.spark')) return; const points = []; const n = 14; let v = 50 + Math.random() * 10; for (let j = 0; j < n; j++) { v += (Math.random() - 0.42) * 12; v = Math.max(10, Math.min(90, v)); points.push(`${(j / (n - 1)) * 100},${100 - v}`); } const d = 'M ' + points.join(' L '); const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('class', 'spark'); svg.setAttribute('viewBox', '0 0 100 100'); svg.setAttribute('preserveAspectRatio', 'none'); const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path.setAttribute('d', d); svg.appendChild(path); kpi.appendChild(svg); }); const ioKpi = new IntersectionObserver((es) => { es.forEach(e => { if (e.isIntersecting) { e.target.classList.add('in'); ioKpi.unobserve(e.target); } }); }, { threshold: 0.4 }); $$('.kpi').forEach(k => ioKpi.observe(k)); /* ----- Live-markets strip: poll rates.php every 4s ----- */ const tilesWrap = $('#live-tiles'); if (tilesWrap) { const fmtTime = (t) => { const d = new Date((t || Date.now() / 1000) * 1000); return d.toLocaleTimeString([], { hour12: false }); }; const sparkPath = (history, dp) => { if (!history || history.length < 2) return ''; const min = Math.min(...history); const max = Math.max(...history); const range = (max - min) || 1; return 'M ' + history.map((v, i) => { const x = (i / (history.length - 1)) * 100; const y = 28 - ((v - min) / range) * 24 - 2; return `${x.toFixed(1)} ${y.toFixed(1)}`; }).join(' L '); }; const renderTiles = (items) => { items.forEach(it => { const tile = tilesWrap.querySelector(`[data-sym="${it.sym}"]`); if (!tile) return; const priceEl = tile.querySelector('.price'); const chgEl = tile.querySelector('.chg'); const symEl = tile.querySelector('.sym'); const pathEl = tile.querySelector('.spark2 path'); if (symEl) symEl.textContent = it.name; if (priceEl) priceEl.textContent = it.last.toFixed(it.dp); if (chgEl) { const up = it.chg >= 0; chgEl.textContent = (up ? '+' : '') + it.chg.toFixed(2) + '%'; chgEl.classList.toggle('up', up); chgEl.classList.toggle('dn', !up); chgEl.style.color = up ? 'var(--success)' : 'var(--danger)'; } if (pathEl) pathEl.setAttribute('d', sparkPath(it.spark, it.dp)); if (pathEl) pathEl.setAttribute('stroke', it.chg >= 0 ? 'var(--success)' : 'var(--danger)'); }); }; const updatedEl = $('#rates-updated'); let lastPayload = null; const fetchRates = async () => { try { const res = await fetch('rates.php', { credentials: 'same-origin', cache: 'no-store' }); if (!res.ok) throw new Error('rates ' + res.status); const data = await res.json(); lastPayload = data; renderTiles(data.items.slice(0, 6)); if (updatedEl) updatedEl.textContent = fmtTime(data.ts); } catch (err) { // Quiet fail — keep existing static numbers if proxy unavailable if (updatedEl) updatedEl.textContent = 'sample data'; } }; fetchRates(); if (!RM) setInterval(fetchRates, 4000); } /* Footer ticker (if present) */ const footerTicker = $('#footer-ticker'); if (footerTicker) { setInterval(async () => { try { const r = await fetch('rates.php', { cache: 'no-store' }); const d = await r.json(); footerTicker.querySelectorAll('[data-sym]').forEach(el => { const m = d.items.find(x => x.sym === el.dataset.sym); if (m) el.querySelector('.p').textContent = m.last.toFixed(m.dp); }); } catch (e) {} }, 5000); } /* ----- Platforms tabbed showcase ----- */ const pxSection = document.getElementById('platforms-showcase'); if (pxSection) { const pxTabs = pxSection.querySelectorAll('.px-tab'); const pxDevices = pxSection.querySelectorAll('.px-device'); const pxFeats = pxSection.querySelectorAll('.px-feat-panel'); const setPx = (key) => { pxTabs.forEach(t => { const active = t.dataset.px === key; t.classList.toggle('active', active); t.setAttribute('aria-selected', active ? 'true' : 'false'); }); pxDevices.forEach(d => d.classList.toggle('active', d.dataset.pxPanel === key)); pxFeats.forEach(f => f.classList.toggle('active', f.dataset.pxFeat === key)); }; pxTabs.forEach(t => t.addEventListener('click', () => setPx(t.dataset.px))); // Auto-rotate every 6s if user hasn't clicked let pxUserClicked = false; pxTabs.forEach(t => t.addEventListener('click', () => { pxUserClicked = true; })); if (!RM) { let pxIdx = 0; setInterval(() => { if (pxUserClicked) return; pxIdx = (pxIdx + 1) % pxTabs.length; setPx(pxTabs[pxIdx].dataset.px); }, 6000); } } /* ----- Form: password show/hide ----- */ $$('[data-pwd-toggle]').forEach(btn => { btn.addEventListener('click', () => { const id = btn.dataset.pwdToggle; const inp = document.getElementById(id); if (!inp) return; const show = inp.type === 'password'; inp.type = show ? 'text' : 'password'; btn.querySelector('use')?.setAttribute('href', show ? '/assets/icons.svg#i-eye-off' : '/assets/icons.svg#i-eye'); }); }); /* (Mobile nav submenu logic moved into setupMobileNav above — no duplicate.) */ /* ----- Trading session times indicator ----- */ const sessionsEl = document.getElementById('sessions-bar'); if (sessionsEl) { const SESSIONS = [ { city: 'Sydney', open: 22, close: 7, flag: '🇦🇺' }, { city: 'Tokyo', open: 0, close: 9, flag: '🇯🇵' }, { city: 'London', open: 8, close: 17, flag: '🇬🇧' }, { city: 'New York',open: 13, close: 22, flag: '🇺🇸' }, ]; const pad = n => String(n).padStart(2, '0'); const isOpen = (s, hUtc) => { if (s.open < s.close) return hUtc >= s.open && hUtc < s.close; return hUtc >= s.open || hUtc < s.close; }; const minsTo = (target, nowMin) => { let diff = (target * 60) - nowMin; if (diff <= 0) diff += 24 * 60; return diff; }; const render = () => { const now = new Date(); const hUtc = now.getUTCHours(); const mUtc = now.getUTCMinutes(); const nowMin = hUtc * 60 + mUtc; sessionsEl.innerHTML = SESSIONS.map(s => { const open = isOpen(s, hUtc); const next = open ? minsTo(s.close, nowMin) : minsTo(s.open, nowMin); const h = Math.floor(next / 60); const m = next % 60; const label = open ? `Closes in ${h}h ${pad(m)}m` : `Opens in ${h}h ${pad(m)}m`; return `
    ${s.flag} ${s.city}
    ${pad(s.open)}:00–${pad(s.close)}:00 UTC
    ${label}
    `; }).join(''); }; render(); setInterval(render, 60000); } /* ----- IB commission calculator REMOVED (Anax preference). Commercial rates discussed with the partner desk after KYC. ----- */ /* ----- Risk-suitability quiz ----- */ const quiz = document.getElementById('risk-quiz'); if (quiz) { const steps = quiz.querySelectorAll('.quiz-step'); const fill = quiz.querySelector('.quiz-progress-fill'); const prevBtn = quiz.querySelector('[data-quiz="prev"]'); const nextBtn = quiz.querySelector('[data-quiz="next"]'); let idx = 0; const scores = []; const stepCount = steps.length - 1; // last step = result const update = () => { steps.forEach((s, i) => s.classList.toggle('active', i === idx)); if (fill) fill.style.width = (idx / stepCount * 100).toFixed(1) + '%'; if (prevBtn) prevBtn.style.visibility = idx === 0 ? 'hidden' : 'visible'; if (nextBtn) { if (idx >= stepCount - 1) nextBtn.textContent = 'See result'; else if (idx === stepCount) nextBtn.textContent = 'Restart'; else nextBtn.textContent = 'Next →'; } }; quiz.querySelectorAll('.quiz-opt').forEach(opt => { opt.addEventListener('click', () => { const stepEl = opt.closest('.quiz-step'); stepEl.querySelectorAll('.quiz-opt').forEach(o => o.classList.remove('selected')); opt.classList.add('selected'); const stepIdx = parseInt(stepEl.dataset.step, 10); scores[stepIdx] = parseInt(opt.dataset.score || '0', 10); }); }); const showResult = () => { const sumScore = scores.reduce((a, b) => a + (b || 0), 0); let tier = 'Prime', desc = 'Start with a low-commitment account while you build confidence.', minDeposit = '$100'; if (sumScore >= 14) { tier = 'Elite'; desc = 'Institutional-grade access matches your experience and ambition.'; minDeposit = '$25,000'; } else if (sumScore >= 8) { tier = 'Pro'; desc = 'You are ready for active trading with tighter spreads and a dealing desk.'; minDeposit = '$2,500'; } const result = quiz.querySelector('.quiz-result'); if (result) { result.querySelector('.tier-name').textContent = tier; result.querySelector('.tier-min').textContent = `Min deposit ${minDeposit}`; result.querySelector('.tier-desc').textContent = desc; } }; if (nextBtn) nextBtn.addEventListener('click', () => { if (idx >= stepCount) { // restart idx = 0; scores.length = 0; quiz.querySelectorAll('.quiz-opt.selected').forEach(o => o.classList.remove('selected')); update(); return; } const cur = steps[idx]; if (cur.querySelector('.quiz-opt') && !cur.querySelector('.quiz-opt.selected')) return; idx++; if (idx === stepCount) showResult(); update(); }); if (prevBtn) prevBtn.addEventListener('click', () => { if (idx > 0) { idx--; update(); } }); update(); } /* ----- Academy lessons cookie progress ----- */ const academy = document.getElementById('academy-grid'); if (academy) { const KEY = 'anax_academy_done'; const get = () => { try { return JSON.parse(localStorage.getItem(KEY) || '[]'); } catch { return []; } }; const set = (arr) => localStorage.setItem(KEY, JSON.stringify(arr)); const updateProgress = () => { const done = get(); const cards = academy.querySelectorAll('.lesson-card'); cards.forEach(c => c.classList.toggle('done', done.includes(c.dataset.lesson))); const totalLessons = cards.length; const pct = totalLessons ? Math.round((done.length / totalLessons) * 100) : 0; const apFill = document.querySelector('.academy-progress .ap-fill'); const apNum = document.querySelector('.academy-progress .ap-num'); const apTotal = document.querySelector('.academy-progress .ap-total'); if (apFill) apFill.style.width = pct + '%'; if (apNum) apNum.textContent = done.length; if (apTotal) apTotal.textContent = totalLessons; }; academy.querySelectorAll('.lesson-card').forEach(card => { card.addEventListener('click', () => { const id = card.dataset.lesson; if (!id) return; let done = get(); if (done.includes(id)) done = done.filter(x => x !== id); else done.push(id); set(done); updateProgress(); }); }); updateProgress(); } /* ----- Pull-to-refresh (mobile) ----- */ if (matchMedia('(max-width: 768px)').matches && !RM) { const ptr = document.createElement('div'); ptr.className = 'ptr-indicator'; ptr.innerHTML = ''; document.body.appendChild(ptr); let startY = 0, pulling = false; const THRESHOLD = 80; window.addEventListener('touchstart', (e) => { if (window.scrollY > 0) { pulling = false; return; } startY = e.touches[0].clientY; pulling = true; }, { passive: true }); window.addEventListener('touchmove', (e) => { if (!pulling) return; const d = e.touches[0].clientY - startY; if (d < 0) return; const drag = Math.min(THRESHOLD * 1.4, d * 0.5); ptr.style.setProperty('--ptr', drag + 'px'); ptr.classList.add('pulling'); ptr.querySelector('svg').style.transform = `rotate(${drag * 4}deg)`; }, { passive: true }); window.addEventListener('touchend', () => { if (!pulling) return; const drag = parseFloat(getComputedStyle(ptr).getPropertyValue('--ptr')) || 0; ptr.classList.remove('pulling'); if (drag >= THRESHOLD * 0.7) { ptr.classList.add('refreshing'); setTimeout(() => location.reload(), 500); } else { ptr.style.setProperty('--ptr', '0px'); } pulling = false; }); } /* ----- Mobile zoom lock (iOS Safari ignores user-scalable=no in some versions) ----- */ (function lockZoom() { if (!('ontouchstart' in window)) return; // Block pinch-zoom (multi-touch gesture) document.addEventListener('gesturestart', e => e.preventDefault(), { passive: false }); document.addEventListener('gesturechange', e => e.preventDefault(), { passive: false }); document.addEventListener('gestureend', e => e.preventDefault(), { passive: false }); // Block double-tap zoom let lastTouch = 0; document.addEventListener('touchend', e => { const now = Date.now(); if (now - lastTouch <= 300) e.preventDefault(); lastTouch = now; }, { passive: false }); // Block ctrl+wheel zoom on hybrid touch devices document.addEventListener('wheel', e => { if (e.ctrlKey) e.preventDefault(); }, { passive: false }); })(); })();