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:
2026-06-30 15:29:50 +08:00
commit 4d30803b60
1154 changed files with 184340 additions and 0 deletions
+927
View File
@@ -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&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 5%</span><br> Phase 2&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 8%</span>', split: '70%~95%', payout: '14 days', news: 'Allowed' },
'25k': { fee: '$219', daily: ' 3%', max: '8%', target: 'Phase 1&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 5%</span><br> Phase 2&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 8%</span>', split: '70%~95%', payout: '14 days', news: 'Allowed' },
'50k': { fee: '$359', daily: ' 3%', max: '8%', target: 'Phase 1&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 5%</span><br> Phase 2&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 8%</span>', split: '70%~95%', payout: '14 days', news: 'Allowed' },
'100k': { fee: '$589', daily: ' 3%', max: '8%', target: 'Phase 1&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 5%</span><br> Phase 2&nbsp;&nbsp;&nbsp;&nbsp;<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&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 5%</span><br> Phase 2&nbsp;&nbsp;&nbsp;&nbsp;<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&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 3%</span><br>Phase 2&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 3%</span><br>Phase 3&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 5%</span>', split: '70%~95%', payout: '14 days', news: 'Allow' },
'25k': { fee: '$149', daily: ' 3%', max: '5%', target: 'Phase 1&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 3%</span><br>Phase 2&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 3%</span><br>Phase 3&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 5%</span>', split: '70%~95%', payout: '14 days', news: 'Allow' },
'50k': { fee: '$299', daily: ' 3%', max: '5%', target: 'Phase 1&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 3%</span><br>Phase 2&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 3%</span><br>Phase 3&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 5%</span>', split: '70%~95%', payout: '14 days', news: 'Allow' },
'100k': { fee: '$459', daily: ' 3%', max: '5%', target: 'Phase 1&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 3%</span><br>Phase 2&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 3%</span><br>Phase 3&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 5%</span>', split: '70%~95%', payout: '14 days', news: 'Allow' },
'200k': { fee: '$909', daily: ' 3%', max: '5%', target: 'Phase 1&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 3%</span><br>Phase 2&nbsp;&nbsp;&nbsp;&nbsp;<span class="text-orange"> 3%</span><br>Phase 3&nbsp;&nbsp;&nbsp;&nbsp;<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 days 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");
}