Initial commit: AIFO official portal web.
Add static site, shared layout, and Node dev server with standard .gitignore. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,927 @@
|
||||
"use strict";
|
||||
|
||||
(function($){
|
||||
|
||||
$(window).on("load", function() {
|
||||
|
||||
}),
|
||||
|
||||
$(window).on('load resize', function() {
|
||||
|
||||
// Background image holder - Static hero with fullscreen autosize
|
||||
if ($('.spotlight').length) {
|
||||
$('.spotlight').each(function() {
|
||||
|
||||
var $this = $(this);
|
||||
var holderHeight;
|
||||
|
||||
if ($this.data('spotlight') == 'fullscreen') {
|
||||
if ($this.data('spotlight-offset')) {
|
||||
var offsetHeight = $('body').find($this.data('spotlight-offset')).height();
|
||||
holderHeight = $(window).height() - offsetHeight;
|
||||
}
|
||||
else {
|
||||
holderHeight = $(window).height();
|
||||
}
|
||||
|
||||
|
||||
if ($(window).width() > 991) {
|
||||
$this.find('.spotlight-holder').css({
|
||||
'height': holderHeight + 'px'
|
||||
});
|
||||
}
|
||||
else {
|
||||
$this.find('.spotlight-holder').css({
|
||||
'height': 'auto'
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
// Plugins init
|
||||
$(".scrollbar-inner")[0] && $(".scrollbar-inner").scrollbar().scrollLock();
|
||||
$('[data-stick-in-parent="true"]')[0] && $('[data-stick-in-parent="true"]').stick_in_parent();
|
||||
$('.selectpicker')[0] && $('.selectpicker').selectpicker();
|
||||
$('.textarea-autosize')[0] && autosize($('.textarea-autosize'));
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('[data-toggle="popover"]').each(function() {
|
||||
var popoverClass = '';
|
||||
if($(this).data('color')) {
|
||||
popoverClass = 'popover-'+$(this).data('color');
|
||||
}
|
||||
$(this).popover({
|
||||
trigger: 'focus',
|
||||
template: '<div class="popover '+ popoverClass +'" role="tooltip"><div class="arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>'
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
// Floating label
|
||||
$('.form-control').on('focus blur', function(e) {
|
||||
$(this).parents('.form-group').toggleClass('focused', (e.type === 'focus' || this.value.length > 0));
|
||||
}).trigger('blur');
|
||||
|
||||
|
||||
// Custom input file
|
||||
$('.custom-input-file').each(function() {
|
||||
var $input = $(this),
|
||||
$label = $input.next('label'),
|
||||
labelVal = $label.html();
|
||||
|
||||
$input.on('change', function(e) {
|
||||
var fileName = '';
|
||||
|
||||
if (this.files && this.files.length > 1)
|
||||
fileName = (this.getAttribute('data-multiple-caption') || '').replace('{count}', this.files.length);
|
||||
else if (e.target.value)
|
||||
fileName = e.target.value.split('\\').pop();
|
||||
|
||||
if (fileName)
|
||||
$label.find('span').html(fileName);
|
||||
else
|
||||
$label.html(labelVal);
|
||||
});
|
||||
|
||||
|
||||
// Firefox bug fix
|
||||
$input.on('focus', function() {
|
||||
$input.addClass('has-focus');
|
||||
})
|
||||
.on('blur', function() {
|
||||
$input.removeClass('has-focus');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// NoUI Slider
|
||||
if ($(".input-slider-container")[0]) {
|
||||
$('.input-slider-container').each(function() {
|
||||
|
||||
var slider = $(this).find('.input-slider');
|
||||
var sliderId = slider.attr('id');
|
||||
var minValue = slider.data('range-value-min');
|
||||
var maxValue = slider.data('range-value-max');
|
||||
|
||||
var sliderValue = $(this).find('.range-slider-value');
|
||||
var sliderValueId = sliderValue.attr('id');
|
||||
var startValue = sliderValue.data('range-value-low');
|
||||
|
||||
var c = document.getElementById(sliderId),
|
||||
d = document.getElementById(sliderValueId);
|
||||
|
||||
noUiSlider.create(c, {
|
||||
start: [parseInt(startValue)],
|
||||
connect: [true, false],
|
||||
//step: 1000,
|
||||
range: {
|
||||
'min': [parseInt(minValue)],
|
||||
'max': [parseInt(maxValue)]
|
||||
}
|
||||
});
|
||||
|
||||
c.noUiSlider.on('update', function(a, b) {
|
||||
d.textContent = a[b];
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
if ($("#input-slider-range")[0]) {
|
||||
var c = document.getElementById("input-slider-range"),
|
||||
d = document.getElementById("input-slider-range-value-low"),
|
||||
e = document.getElementById("input-slider-range-value-high"),
|
||||
f = [d, e];
|
||||
|
||||
noUiSlider.create(c, {
|
||||
start: [parseInt(d.getAttribute('data-range-value-low')), parseInt(e.getAttribute('data-range-value-high'))],
|
||||
connect: !0,
|
||||
range: {
|
||||
min: parseInt(c.getAttribute('data-range-value-min')),
|
||||
max: parseInt(c.getAttribute('data-range-value-max'))
|
||||
}
|
||||
}), c.noUiSlider.on("update", function(a, b) {
|
||||
f[b].textContent = a[b]
|
||||
})
|
||||
}
|
||||
|
||||
// Scroll to anchor with animation
|
||||
$('.scroll-me, .toc-entry a').on('click', function(event) {
|
||||
var hash = $(this).attr('href');
|
||||
var offset = $(this).data('scroll-to-offset') ? $(this).data('scroll-to-offset') : 0;
|
||||
|
||||
// Animate scroll to the selected section
|
||||
$('html, body').stop(true, true).animate({
|
||||
scrollTop: $(hash).offset().top - offset
|
||||
}, 600);
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
$(document).ready(function() {
|
||||
$("body").on("click", "[data-action]", function(e) {
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
var $this = $(this);
|
||||
var action = $this.data('action');
|
||||
var target = '';
|
||||
|
||||
switch (action) {
|
||||
case "offcanvas-open":
|
||||
target = $this.data("target"), $(target).addClass("open"), $("body").append('<div class="body-backdrop" data-action="offcanvas-close" data-target=' + target + " />");
|
||||
break;
|
||||
case "offcanvas-close":
|
||||
target = $this.data("target"), $(target).removeClass("open"), $("body").find(".body-backdrop").remove();
|
||||
break;
|
||||
|
||||
case 'aside-open':
|
||||
target = $this.data('target');
|
||||
$this.data('action', 'aside-close');
|
||||
$this.addClass('toggled');
|
||||
$(target).addClass('toggled');
|
||||
$('.content').append('<div class="body-backdrop" data-action="aside-close" data-target='+target+' />');
|
||||
break;
|
||||
|
||||
|
||||
case 'aside-close':
|
||||
target = $this.data('target');
|
||||
$this.data('action', 'aside-open');
|
||||
$('[data-action="aside-open"], '+target).removeClass('toggled');
|
||||
$('.content, .header').find('.body-backdrop').remove();
|
||||
break;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
// nav
|
||||
|
||||
// Check if top code bar is visible
|
||||
function isTopCodeBarVisible() {
|
||||
const $topCodeBar = $('.top-code-bar');
|
||||
|
||||
// If element doesn't exist
|
||||
if (!$topCodeBar.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use jQuery method to check if visible
|
||||
return $topCodeBar.is(':visible') && $topCodeBar.css('visibility') !== 'hidden' && $topCodeBar.height() > 0;
|
||||
}
|
||||
|
||||
function isMobileNavViewport() {
|
||||
if (window.matchMedia("(max-width: 991.98px)").matches) {
|
||||
return true;
|
||||
}
|
||||
if (window.matchMedia("(min-width: 992px)").matches) {
|
||||
return false;
|
||||
}
|
||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i
|
||||
.test(navigator.userAgent);
|
||||
}
|
||||
|
||||
function initDesktopFixedNav() {
|
||||
if (isMobileNavViewport()) return;
|
||||
|
||||
const $nav = $('#nav');
|
||||
if (!$nav.length) return;
|
||||
|
||||
const threshold = 35;
|
||||
const defaultTop = isTopCodeBarVisible() ? '35px' : '0';
|
||||
const fixedTop = '0';
|
||||
|
||||
$nav.css({
|
||||
'position': 'fixed',
|
||||
'top': defaultTop,
|
||||
'left': '0',
|
||||
'width': '100%',
|
||||
'z-index': '1500',
|
||||
'background-color': 'rgba(0, 0, 0, 0.4)',
|
||||
'box-shadow': '0 2px 10px rgba(0,0,0,0.1)',
|
||||
'transition': 'none'
|
||||
});
|
||||
|
||||
function updateNavPosition() {
|
||||
const isdefaultTop = isTopCodeBarVisible();
|
||||
if (!isdefaultTop) {
|
||||
$nav.css('top', fixedTop);
|
||||
return;
|
||||
}
|
||||
const scrollTop = $(window).scrollTop();
|
||||
$nav.css('top', scrollTop > threshold ? fixedTop : defaultTop);
|
||||
}
|
||||
|
||||
$(window).off('scroll.aifoNav resize.aifoNav');
|
||||
$(window).on('scroll.aifoNav resize.aifoNav', updateNavPosition);
|
||||
updateNavPosition();
|
||||
}
|
||||
|
||||
$(document).ready(initDesktopFixedNav);
|
||||
document.addEventListener('aifo:layout-ready', initDesktopFixedNav);
|
||||
|
||||
// Seaction-2
|
||||
$(function() {
|
||||
// Check if jQuery UI is loaded (for easing functions)
|
||||
const hasJQueryUI = typeof $.easing !== 'undefined';
|
||||
|
||||
const $section2 = $('#section-2');
|
||||
|
||||
// Counter data
|
||||
const counters = [
|
||||
{ index: 0, start: 1000, end: 50000, suffix: '+' },
|
||||
{ index: 1, start: 1, end: 20, suffix: 'M+' },
|
||||
{ index: 2, start: 1000, end: 70000, suffix: '+' }
|
||||
];
|
||||
|
||||
// Listen to elements entering viewport
|
||||
const observer = new IntersectionObserver(function(entries) {
|
||||
entries.forEach(function(entry) {
|
||||
if (entry.isIntersecting) {
|
||||
// Add fade-in class (if not already)
|
||||
if (!$section2.hasClass('fade-in')) {
|
||||
$section2.addClass('fade-in');
|
||||
}
|
||||
|
||||
triggerCounters();
|
||||
// Listen to animation end
|
||||
// $section2.one('animationend webkitAnimationEnd oanimationend MSAnimationEnd', function() {
|
||||
|
||||
// });
|
||||
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.2 });
|
||||
|
||||
// Check if element exists
|
||||
if ($section2.length) {
|
||||
observer.observe($section2[0]);
|
||||
}
|
||||
|
||||
function triggerCounters() {
|
||||
setTimeout(function() {
|
||||
counters.forEach(function(counter) {
|
||||
const $counter = $('#section-2 .counter').eq(counter.index);
|
||||
|
||||
if ($counter.length) {
|
||||
animateValue(
|
||||
$counter,
|
||||
counter.start,
|
||||
counter.end,
|
||||
counter.suffix,
|
||||
2000,
|
||||
hasJQueryUI ? 'easeOutElastic' : 'easeOutCubic'
|
||||
);
|
||||
}
|
||||
});
|
||||
}, 600);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Animation function
|
||||
function animateValue($element, start, end, suffix, duration, easing) {
|
||||
let startTimestamp = null;
|
||||
|
||||
// Easing function
|
||||
function getEasing(progress) {
|
||||
switch(easing) {
|
||||
case 'easeOutCubic':
|
||||
return 1 - Math.pow(1 - progress, 3);
|
||||
case 'easeOutElastic':
|
||||
const c4 = (2 * Math.PI) / 3;
|
||||
return progress === 0 ? 0 :
|
||||
progress === 1 ? 1 :
|
||||
Math.pow(2, -10 * progress) * Math.sin((progress * 10 - 0.75) * c4) + 1;
|
||||
default:
|
||||
return progress;
|
||||
}
|
||||
}
|
||||
|
||||
// Format number
|
||||
function formatNumber(num) {
|
||||
return num.toLocaleString('en-US');
|
||||
}
|
||||
|
||||
// Animation step
|
||||
function step(timestamp) {
|
||||
if (!startTimestamp) startTimestamp = timestamp;
|
||||
|
||||
const elapsed = timestamp - startTimestamp;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
const easedProgress = getEasing(progress);
|
||||
|
||||
const current = Math.floor(start + (end - start) * easedProgress);
|
||||
|
||||
// Update display
|
||||
if (suffix === 'M+') {
|
||||
$element.text('$'+current + suffix);
|
||||
} else {
|
||||
$element.text(formatNumber(current) + suffix);
|
||||
}
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/* =========================
|
||||
Intersection fade-in (pixel-precise)
|
||||
and lazy loaders for floor loading
|
||||
========================= */
|
||||
|
||||
const io = new IntersectionObserver((entries, obs) => {
|
||||
entries.forEach(e=>{
|
||||
if(e.isIntersecting){
|
||||
$(e.target).addClass('show');
|
||||
obs.unobserve(e.target);
|
||||
}
|
||||
});
|
||||
},{threshold:0.12});
|
||||
|
||||
$(function(){
|
||||
$('.fade-in').each(function(){ io.observe(this); });
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
// ============================================
|
||||
// challenge
|
||||
// ============================================
|
||||
|
||||
const appState = {
|
||||
programType: '1step',
|
||||
instantMode: 'pro',
|
||||
accountSize: '10k',
|
||||
expandedParam: null
|
||||
};
|
||||
|
||||
const programData = {
|
||||
'1step': {
|
||||
defaultSize: '10k',
|
||||
availableSizes: ['10k', '25k', '50k', '100k', '200k'],
|
||||
data: {
|
||||
'10k': { fee: '$89', daily: '3%', max: '5%', target: '8%', split: '70%~95%', payout: '14 days', news: 'Allowed' },
|
||||
'25k': { fee: '$179', daily: '3%', max: '5%', target: '8%', split: '70%~95%', payout: '14 days', news: 'Allowed' },
|
||||
'50k': { fee: '$359', daily: '3%', max: '5%', target: '8%', split: '70%~95%', payout: '14 days', news: 'Allowed' },
|
||||
'100k': { fee: '$579', daily: '3%', max: '5%', target: '8%', split: '70%~95%', payout: '14 days', news: 'Allowed' },
|
||||
'200k': { fee: '$1,239', daily: '3%', max: '5%', target: '8%', split: '70%~95%', payout: '14 days', news: 'Allowed' }
|
||||
}
|
||||
},
|
||||
'2step': {
|
||||
defaultSize: '10k',
|
||||
availableSizes: ['10k', '25k', '50k', '100k', '200k'],
|
||||
data: {
|
||||
'10k': { fee: '$109', daily: ' 3%', max: '8%', target: 'Phase 1 <span class="text-orange"> 5%</span><br> Phase 2 <span class="text-orange"> 8%</span>', split: '70%~95%', payout: '14 days', news: 'Allowed' },
|
||||
'25k': { fee: '$219', daily: ' 3%', max: '8%', target: 'Phase 1 <span class="text-orange"> 5%</span><br> Phase 2 <span class="text-orange"> 8%</span>', split: '70%~95%', payout: '14 days', news: 'Allowed' },
|
||||
'50k': { fee: '$359', daily: ' 3%', max: '8%', target: 'Phase 1 <span class="text-orange"> 5%</span><br> Phase 2 <span class="text-orange"> 8%</span>', split: '70%~95%', payout: '14 days', news: 'Allowed' },
|
||||
'100k': { fee: '$589', daily: ' 3%', max: '8%', target: 'Phase 1 <span class="text-orange"> 5%</span><br> Phase 2 <span class="text-orange"> 8%</span>', split: '70%~95%', payout: '14 days', news: 'Allowed' },
|
||||
'200k': { fee: '$1,249', daily: ' 3%', max: '8%', target: 'Phase 1 <span class="text-orange"> 5%</span><br> Phase 2 <span class="text-orange"> 8%</span>', split: '70%~95%', payout: '14 days', news: 'Allowed' }
|
||||
}
|
||||
},
|
||||
'3step': {
|
||||
defaultSize: '10k',
|
||||
availableSizes: ['10k', '25k', '50k', '100k', '200k'],
|
||||
data: {
|
||||
'10k': { fee: '$69', daily: ' 3%', max: '5%', target: 'Phase 1 <span class="text-orange"> 3%</span><br>Phase 2 <span class="text-orange"> 3%</span><br>Phase 3 <span class="text-orange"> 5%</span>', split: '70%~95%', payout: '14 days', news: 'Allow' },
|
||||
'25k': { fee: '$149', daily: ' 3%', max: '5%', target: 'Phase 1 <span class="text-orange"> 3%</span><br>Phase 2 <span class="text-orange"> 3%</span><br>Phase 3 <span class="text-orange"> 5%</span>', split: '70%~95%', payout: '14 days', news: 'Allow' },
|
||||
'50k': { fee: '$299', daily: ' 3%', max: '5%', target: 'Phase 1 <span class="text-orange"> 3%</span><br>Phase 2 <span class="text-orange"> 3%</span><br>Phase 3 <span class="text-orange"> 5%</span>', split: '70%~95%', payout: '14 days', news: 'Allow' },
|
||||
'100k': { fee: '$459', daily: ' 3%', max: '5%', target: 'Phase 1 <span class="text-orange"> 3%</span><br>Phase 2 <span class="text-orange"> 3%</span><br>Phase 3 <span class="text-orange"> 5%</span>', split: '70%~95%', payout: '14 days', news: 'Allow' },
|
||||
'200k': { fee: '$909', daily: ' 3%', max: '5%', target: 'Phase 1 <span class="text-orange"> 3%</span><br>Phase 2 <span class="text-orange"> 3%</span><br>Phase 3 <span class="text-orange"> 5%</span>', split: '70%~95%', payout: '14 days', news: 'Allow' }
|
||||
}
|
||||
},
|
||||
'instant': {
|
||||
pro: {
|
||||
defaultSize: '10k',
|
||||
availableSizes: ['10k', '25k', '50k'],
|
||||
data: {
|
||||
'10k': { fee: '$1091', daily: '2%', max: '5%', target: '4%', split: '70%~95%', payout: 'payout on demand', news: 'Allow' },
|
||||
'25k': { fee: '$219', daily: '2%', max: '5%', target: '4%', split: '70%~95%', payout: 'payout on demand', news: 'Allow' },
|
||||
'50k': { fee: '$399', daily: '2%', max: '5%', target: '4%', split: '70%~95%', payout: 'payout on demand', news: 'Allow' },
|
||||
}
|
||||
},
|
||||
aifo: {
|
||||
defaultSize: '100k',
|
||||
availableSizes: ['100k', '200k'],
|
||||
data: {
|
||||
'100k': { fee: '$749', daily: '2%', max: '4%', target: '4%', split: '70%~95%', payout: 'payout on demand', news: 'Allow' },
|
||||
'200k': { fee: '$1,199', daily: '2%', max: '4%', target: '4%', split: '70%~95%', payout: 'payout on demand', news: 'Allow' }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const parameterDescriptions = {
|
||||
'Challenge Fee': 'One-time fee to participate in the challenge program',
|
||||
'Daily Drawdown': 'Maximum percentage loss allowed in a single trading day based on the previous day’s closing balance or equity.',
|
||||
'Max Drawdown': 'The Maximum Loss Limit is the maximum allowable loss calculated based on the highest equity value your account reaches during the evaluation or trading phase.',
|
||||
'Profit Target': 'The percentage gain required to successfully complete each phase of the challenge.',
|
||||
'withdrawable profit': 'The Profit Target refers to the required percentage of withdrawable profit that the account must achieve before a payout can be requested.',
|
||||
'Profit split': 'Percentage of profits you keep. The firm keeps the remainder.',
|
||||
'Payout period': 'Minimum time required before you can request a withdrawal of profits.',
|
||||
'News Trading': ''
|
||||
};
|
||||
|
||||
// const parameterNames = ['Challenge Fee', 'Daily Drawdown', 'Max Drawdown', 'Profit Target', 'Profit split', 'Payout period', 'News Trading'];
|
||||
const parameterNames = ['Challenge Fee', 'Daily Drawdown', 'Max Drawdown', 'Profit Target', 'Profit split', 'Payout period'];
|
||||
const parameterNames_instant = ['Challenge Fee', 'Daily Drawdown', 'Max Drawdown', 'withdrawable profit', 'Profit split', 'Payout period'];
|
||||
|
||||
// ============================================
|
||||
// 工具函数
|
||||
// ============================================
|
||||
|
||||
function getIconSVG(paramName) {
|
||||
const icons = {
|
||||
'Challenge Fee': '<img src="./assets/images/backgrounds/svg/Component 26.svg" alt="Funded Trading" class="chall-icon">',
|
||||
'Daily Drawdown': '<img src="./assets/images/backgrounds/svg/Component 27.svg" alt="Funded Trading" class="chall-icon">',
|
||||
'Max Drawdown': '<img src="./assets/images/backgrounds/svg/Component 28.svg" alt="Funded Trading" class="chall-icon">',
|
||||
'Profit Target': '<img src="./assets/images/backgrounds/svg/Component 29.svg" alt="Funded Trading" class="chall-icon">',
|
||||
'withdrawable profit': '<img src="./assets/images/backgrounds/svg/Component 29.svg" alt="Funded Trading" class="chall-icon">',
|
||||
'Profit split': '<img src="./assets/images/backgrounds/svg/Component 30.svg" alt="Funded Trading" class="chall-icon">',
|
||||
'Payout period': '<img src="./assets/images/backgrounds/svg/Component 31.svg" alt="Funded Trading" class="chall-icon">',
|
||||
'News Trading': '<img src="./assets/images/backgrounds/svg/Component 32.svg" alt="Funded Trading" class="chall-icon">'
|
||||
};
|
||||
return icons[paramName] || '';
|
||||
}
|
||||
|
||||
function getCurrentProgramConfig() {
|
||||
if (appState.programType === 'instant') {
|
||||
return programData.instant[appState.instantMode];
|
||||
}
|
||||
return programData[appState.programType];
|
||||
}
|
||||
|
||||
function getDataForSize(size) {
|
||||
const config = getCurrentProgramConfig();
|
||||
return config.data[size];
|
||||
}
|
||||
|
||||
function formatAccountSize(size) {
|
||||
const formats = {
|
||||
'10k': '$10K',
|
||||
'25k': '$25K',
|
||||
'50k': '$50K',
|
||||
'100k': '$100K',
|
||||
'200k': '$200K'
|
||||
};
|
||||
return formats[size] || size;
|
||||
}
|
||||
|
||||
function getParamKey(paramName) {
|
||||
const keyMap = {
|
||||
'Challenge Fee': 'fee',
|
||||
'Daily Drawdown': 'daily',
|
||||
'Max Drawdown': 'max',
|
||||
'Profit Target': 'target',
|
||||
'withdrawable profit':'target',
|
||||
'Profit split': 'split',
|
||||
'Payout period': 'payout',
|
||||
'News Trading': 'news'
|
||||
};
|
||||
return keyMap[paramName];
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 桌面版渲染
|
||||
// ============================================
|
||||
|
||||
function renderDesktopTable(type) {
|
||||
const config = getCurrentProgramConfig();
|
||||
const sizes = config.availableSizes;
|
||||
|
||||
let html = '';
|
||||
|
||||
var param = parameterNames;
|
||||
if(type=="aifo" || type=="pro" || type=="instant"){
|
||||
param = parameterNames_instant;
|
||||
}
|
||||
|
||||
param.forEach(paramName => {
|
||||
const paramKey = getParamKey(paramName);
|
||||
const rowId = paramName.replace(/\s+/g, '-').toLowerCase();
|
||||
|
||||
// 参数行
|
||||
html += `<tr class="param-data-row" data-param="${paramName}">`;
|
||||
html += `<td>
|
||||
<div class="param-name-cell">
|
||||
<div class="param-icon">${getIconSVG(paramName)}</div>
|
||||
<div class="param-name">${paramName}</div>
|
||||
<div class="expand-btn ${appState.expandedParam === paramName ? 'expanded' : ''}">
|
||||
<img src="./assets/images/backgrounds/arraw-icon.png" alt="Funded Trading" class="arraw-icon-table">
|
||||
</div>
|
||||
</div>
|
||||
</td>`;
|
||||
|
||||
// 各账户大小的数值
|
||||
sizes.forEach(size => {
|
||||
const data = getDataForSize(size);
|
||||
html += `<td>${data[paramKey]}</td>`;
|
||||
});
|
||||
|
||||
html += `</tr>`;
|
||||
|
||||
// 展开说明行
|
||||
html += `<tr class="challenge-row-expand ${appState.expandedParam === paramName ? 'show' : ''}" data-expand="${paramName}">`;
|
||||
html += `<td colspan="6">${parameterDescriptions[paramName]}</td>`;
|
||||
html += `</tr>`;
|
||||
});
|
||||
|
||||
$('#desktopTableBody').html(html);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 移动版渲染
|
||||
// ============================================
|
||||
|
||||
function renderMobileAccountSizes() {
|
||||
const config = getCurrentProgramConfig();
|
||||
const sizes = config.availableSizes;
|
||||
|
||||
const buttons = sizes.map(size => {
|
||||
const label = formatAccountSize(size);
|
||||
const activeClass = size === appState.accountSize ? 'active' : '';
|
||||
return `<button class="mobile-size-btn ${activeClass}" data-size="${size}">${label}</button>`;
|
||||
}).join('');
|
||||
|
||||
$('#mobileAccountSizes').html(buttons);
|
||||
|
||||
if (!sizes.includes(appState.accountSize)) {
|
||||
appState.accountSize = config.defaultSize;
|
||||
}
|
||||
}
|
||||
|
||||
function renderMobileParams(type) {
|
||||
const data = getDataForSize(appState.accountSize);
|
||||
|
||||
let html = '';
|
||||
|
||||
var param = parameterNames;
|
||||
if(type=="aifo" || type=="pro" || type=="instant"){
|
||||
param = parameterNames_instant;
|
||||
}
|
||||
|
||||
param.forEach(paramName => {
|
||||
const paramKey = getParamKey(paramName);
|
||||
|
||||
html += `<div>
|
||||
<div class="mobile-param-row" data-param="${paramName}">
|
||||
<div class="mobile-param-left">
|
||||
<div class="param-icon">${getIconSVG(paramName)}</div>
|
||||
<div class="param-name">${paramName}</div>
|
||||
<div class="expand-btn ${appState.expandedParam === paramName ? 'expanded' : ''}">
|
||||
<img src="./assets/images/backgrounds/arraw-icon.png" alt="Funded Trading" class="arraw-icon-table">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-param-value">${data[paramKey]}</div>
|
||||
</div>
|
||||
<div class="mobile-param-description ${appState.expandedParam === paramName ? 'show' : ''}" data-desc="${paramName}">
|
||||
${parameterDescriptions[paramName]}
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
$('#mobileParamsList').html(html);
|
||||
}
|
||||
|
||||
function updateMobileAmount() {
|
||||
$('#mobileAmount').text(formatAccountSize(appState.accountSize));
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 状态切换
|
||||
// ============================================
|
||||
|
||||
function changeProgramType(newType) {
|
||||
appState.programType = newType;
|
||||
const config = getCurrentProgramConfig();
|
||||
appState.accountSize = config.defaultSize;
|
||||
appState.expandedParam = null;
|
||||
|
||||
renderDesktopTable(newType);
|
||||
renderMobileAccountSizes();
|
||||
renderMobileParams(newType);
|
||||
updateMobileAmount();
|
||||
}
|
||||
|
||||
function changeInstantMode(newMode) {
|
||||
appState.instantMode = newMode;
|
||||
const config = getCurrentProgramConfig();
|
||||
|
||||
if (!config.availableSizes.includes(appState.accountSize)) {
|
||||
appState.accountSize = config.defaultSize;
|
||||
}
|
||||
|
||||
appState.expandedParam = null;
|
||||
|
||||
renderDesktopTable(newMode);
|
||||
renderMobileAccountSizes();
|
||||
renderMobileParams(newMode);
|
||||
updateMobileAmount();
|
||||
}
|
||||
|
||||
function changeMobileAccountSize(newSize) {
|
||||
appState.accountSize = newSize;
|
||||
appState.expandedParam = null;
|
||||
|
||||
updateMobileAmount();
|
||||
$('#mobileParamsList').fadeOut(150, function() {
|
||||
|
||||
const tabs = document.querySelectorAll('.challenge-tab');
|
||||
var type = "step";
|
||||
tabs.forEach(tab => {
|
||||
if (tab.classList.contains('active')) {
|
||||
const program = tab.dataset.program;
|
||||
if(program == "instant"){
|
||||
type = "instant";
|
||||
}
|
||||
}
|
||||
});
|
||||
renderMobileParams(type);
|
||||
|
||||
|
||||
$(this).fadeIn(150);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 事件监听
|
||||
// ============================================
|
||||
|
||||
$(document).on('click', '.challenge-tab', function() {
|
||||
$('.challenge-tab').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
|
||||
const newProgram = $(this).data('program');
|
||||
const addons = document.querySelector('.challenge-addons');
|
||||
|
||||
if (newProgram === 'instant') {
|
||||
$('#instantSubTabs').addClass('show');
|
||||
document.querySelectorAll('.ch-pro')
|
||||
.forEach(el => el.classList.remove('d-none'));
|
||||
|
||||
document.querySelectorAll('.ch-aifo')
|
||||
.forEach(el => el.classList.add('d-none'));
|
||||
|
||||
if (addons) {
|
||||
addons.classList.add('is-hidden');
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
|
||||
document.querySelectorAll('.ch-pro')
|
||||
.forEach(el => el.classList.remove('d-none'));
|
||||
|
||||
document.querySelectorAll('.ch-aifo')
|
||||
.forEach(el => el.classList.remove('d-none'));
|
||||
|
||||
$('#instantSubTabs').removeClass('show');
|
||||
if (addons) {
|
||||
addons.classList.remove('is-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
changeProgramType(newProgram);
|
||||
});
|
||||
|
||||
$(document).on('click', '.instant-tab', function() {
|
||||
$('.instant-tab').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
|
||||
const newMode = $(this).data('instant-type');
|
||||
|
||||
if (newMode === "pro") {
|
||||
document.querySelectorAll('.ch-pro')
|
||||
.forEach(el => el.classList.remove('d-none'));
|
||||
|
||||
document.querySelectorAll('.ch-aifo')
|
||||
.forEach(el => el.classList.add('d-none'));
|
||||
} else {
|
||||
document.querySelectorAll('.ch-pro')
|
||||
.forEach(el => el.classList.add('d-none'));
|
||||
|
||||
document.querySelectorAll('.ch-aifo')
|
||||
.forEach(el => el.classList.remove('d-none'));
|
||||
}
|
||||
|
||||
changeInstantMode(newMode);
|
||||
});
|
||||
|
||||
$(document).on('click', '.mobile-size-btn', function() {
|
||||
$('.mobile-size-btn').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
|
||||
const newSize = $(this).data('size');
|
||||
changeMobileAccountSize(newSize);
|
||||
});
|
||||
|
||||
$(document).on('click', '.param-data-row', function() {
|
||||
const paramName = $(this).data('param');
|
||||
if(paramName=="News Trading"){
|
||||
return;
|
||||
}
|
||||
|
||||
if (appState.expandedParam === paramName) {
|
||||
appState.expandedParam = null;
|
||||
} else {
|
||||
appState.expandedParam = paramName;
|
||||
}
|
||||
|
||||
$('.challenge-row-expand').removeClass('show');
|
||||
$('.param-data-row .expand-btn').removeClass('expanded');
|
||||
|
||||
if (appState.expandedParam) {
|
||||
$(`.challenge-row-expand[data-expand="${appState.expandedParam}"]`).addClass('show');
|
||||
$(this).find('.expand-btn').addClass('expanded');
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.mobile-param-row', function() {
|
||||
const paramName = $(this).data('param');
|
||||
|
||||
if (appState.expandedParam === paramName) {
|
||||
appState.expandedParam = null;
|
||||
} else {
|
||||
appState.expandedParam = paramName;
|
||||
}
|
||||
|
||||
$('.mobile-param-description').removeClass('show');
|
||||
$('.mobile-param-row .expand-btn').removeClass('expanded');
|
||||
|
||||
if (appState.expandedParam) {
|
||||
$(`.mobile-param-description[data-desc="${appState.expandedParam}"]`).addClass('show');
|
||||
$(this).find('.expand-btn').addClass('expanded');
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.start-challenge-btn', function() {
|
||||
const size = $(this).data('size');
|
||||
console.log('Start Challenge:', {
|
||||
programType: appState.programType,
|
||||
instantMode: appState.instantMode,
|
||||
accountSize: size
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.mobile-start-btn', function() {
|
||||
console.log('Start Challenge:', {
|
||||
programType: appState.programType,
|
||||
instantMode: appState.instantMode,
|
||||
accountSize: appState.accountSize
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// 初始化
|
||||
// ============================================
|
||||
|
||||
$(document).ready(function() {
|
||||
renderDesktopTable();
|
||||
renderMobileAccountSizes();
|
||||
renderMobileParams();
|
||||
updateMobileAmount();
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
})(jQuery);
|
||||
|
||||
|
||||
/* ===========================================================
|
||||
Reusable Carousel Class
|
||||
=========================================================== */
|
||||
class Carousel {
|
||||
constructor(opts) {
|
||||
this.container = document.querySelector(opts.container);
|
||||
this.track = this.container.querySelector(".carousel-track");
|
||||
this.items = [...this.track.children];
|
||||
this.prevBtn = document.getElementById(opts.prev);
|
||||
this.nextBtn = document.getElementById(opts.next);
|
||||
this.dotsContainer = document.querySelector(opts.dots);
|
||||
|
||||
this.index = 0;
|
||||
this.pageWidth = this.items[0].offsetWidth + 40; // including margin
|
||||
|
||||
this.setupDots();
|
||||
this.update();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
setupDots() {
|
||||
this.dotsContainer.innerHTML = "";
|
||||
this.items.forEach((_, i) => {
|
||||
const dot = document.createElement("div");
|
||||
dot.className = "dot";
|
||||
dot.addEventListener("click", () => this.goTo(i));
|
||||
this.dotsContainer.appendChild(dot);
|
||||
});
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.prevBtn.addEventListener("click", () => this.prev());
|
||||
this.nextBtn.addEventListener("click", () => this.next());
|
||||
window.addEventListener("resize", () => {
|
||||
this.pageWidth = this.items[0].offsetWidth + 40;
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
|
||||
prev() {
|
||||
this.index = Math.max(0, this.index - 1);
|
||||
this.update();
|
||||
}
|
||||
|
||||
next() {
|
||||
this.index = Math.min(this.items.length - 1, this.index + 1);
|
||||
this.update();
|
||||
}
|
||||
|
||||
goTo(i) {
|
||||
this.index = i;
|
||||
this.update();
|
||||
}
|
||||
|
||||
update() {
|
||||
const centerOffset = (window.innerWidth / 2) - (this.pageWidth / 2);
|
||||
let x = this.index * this.pageWidth * -1 + centerOffset;
|
||||
|
||||
if (this.index === 0) x = 0;
|
||||
if (this.index === this.items.length - 1) {
|
||||
x = (this.items.length - 1) * this.pageWidth * -1 + (window.innerWidth - this.pageWidth);
|
||||
}
|
||||
|
||||
this.track.style.transform = `translateX(${x}px)`;
|
||||
|
||||
[...this.dotsContainer.children].forEach((dot, i) => {
|
||||
dot.classList.toggle("active", i === this.index);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function showInfoAlert(){
|
||||
alert("New features are about to be launched");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user