// Shared UI components used across Apex Algo variations.
// Each variation may override tokens (colors, fonts) via props/context,
// but structure + interactivity live here.
window.ApexUI = (function () {
const { useState, useRef, useEffect, useMemo } = React;
// Minimal apply-for-license modal — multi-step, client-side only.
function ApplyModal({ open, onClose, theme }) {
const [step, setStep] = useState(0);
const [form, setForm] = useState({
name: '', email: '', capital: '50k-250k', venues: [], experience: 'retail-systematic', notes: '',
});
const [submitted, setSubmitted] = useState(false);
useEffect(() => {
if (!open) return;
function onKey(e) { if (e.key === 'Escape') onClose(); }
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [open, onClose]);
if (!open) return null;
const t = theme || {};
const bg = t.surface || '#0F1114';
const border = t.border || 'rgba(255,255,255,0.08)';
const fg = t.fg || '#E7E9EC';
const dim = t.dim || 'rgba(231,233,236,0.55)';
const accent = t.accent || '#00D4C8';
const mono = t.mono || 'ui-monospace, SFMono-Regular, Menlo, monospace';
function toggleVenue(v) {
setForm(f => ({ ...f, venues: f.venues.includes(v) ? f.venues.filter(x => x !== v) : [...f.venues, v] }));
}
function submit() {
setSubmitted(true);
}
const steps = ['Contact', 'Profile', 'Review'];
return (
e.stopPropagation()} style={{
width: 'min(560px, 100%)', background: bg, border: `1px solid ${border}`, borderRadius: 4,
color: fg, fontFamily: t.sans || 'system-ui, sans-serif',
}}>
{submitted ? (
Application received
We review applications within 2 business days. You'll hear from us at {form.email || 'your email'} .
Close
) : (
<>
Start 14-day free trial
×
{steps.map((s, i) => (
))}
{step === 0 && (
setForm({ ...form, name: e.target.value })} placeholder="Your full name" style={inputStyle(t)} />
setForm({ ...form, email: e.target.value })} placeholder="name@domain.com" style={inputStyle(t)} />
)}
{step === 1 && (
setForm({ ...form, capital: e.target.value })} style={inputStyle(t)}>
$50k – $250k
$250k – $1M
$1M – $5M
$5M+
{['Hyperliquid (crypto)', 'Interactive Brokers'].map(v => (
toggleVenue(v)} style={{
background: form.venues.includes(v) ? accent : 'transparent',
color: form.venues.includes(v) ? '#0A0B0D' : fg,
border: `1px solid ${form.venues.includes(v) ? accent : border}`,
padding: '7px 12px', fontSize: 12, borderRadius: 2, cursor: 'pointer', fontFamily: 'inherit',
}}>{v}
))}
setForm({ ...form, experience: e.target.value })} style={inputStyle(t)}>
New to systematic trading
Retail, some systematic experience
Professional / small fund
)}
{step === 2 && (
Submitting forwards this to access@theapexalgo.com . Approved trials get dashboard access for 14 days — paper or live, your choice. No payment taken now.
)}
step > 0 ? setStep(step - 1) : onClose()}
style={{ background: 'transparent', color: dim, border: 'none', fontSize: 13, cursor: 'pointer', fontFamily: 'inherit' }}>
{step > 0 ? '← Back' : 'Cancel'}
{step < 2 ? (
setStep(step + 1)}
disabled={step === 0 && (!form.name || !form.email)}
style={{ background: accent, color: '#0A0B0D', border: 'none', padding: '8px 18px', fontSize: 13, borderRadius: 2, cursor: 'pointer', fontWeight: 500, fontFamily: 'inherit', opacity: (step === 0 && (!form.name || !form.email)) ? 0.4 : 1 }}>
Continue
) : (
Request trial access
)}
>
)}
);
}
function inputStyle(t) {
return {
width: '100%', background: 'transparent', color: t.fg || '#E7E9EC',
border: `1px solid ${t.border || 'rgba(255,255,255,0.12)'}`, borderRadius: 2,
padding: '9px 12px', fontSize: 13, outline: 'none', fontFamily: 'inherit',
boxSizing: 'border-box',
};
}
function Field({ label, children, theme }) {
const t = theme || {};
return (
);
}
function Row({ k, v, fg }) {
return (
{k}
{v}
);
}
// FAQ accordion
function FAQ({ items, theme }) {
const [open, setOpen] = useState(-1);
const t = theme || {};
return (
{items.map((it, i) => {
const isOpen = open === i;
return (
setOpen(isOpen ? -1 : i)} style={{
width: '100%', background: 'transparent', color: t.fg, border: 'none',
padding: '18px 0', textAlign: 'left', fontSize: 15, cursor: 'pointer',
display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 20,
fontFamily: 'inherit',
}}>
{it.q}
{isOpen ? '−' : '+'}
{isOpen && (
{it.a}
)}
);
})}
);
}
// Sortable pricing / strategy table
function SortableStrategyTable({ data, theme, columns }) {
const [sort, setSort] = useState({ key: 'sharpe', dir: 'desc' });
const t = theme || {};
const sorted = useMemo(() => {
const out = [...data];
out.sort((a, b) => {
const av = a[sort.key], bv = b[sort.key];
if (typeof av === 'number') return sort.dir === 'asc' ? av - bv : bv - av;
return sort.dir === 'asc' ? String(av).localeCompare(String(bv)) : String(bv).localeCompare(String(av));
});
return out;
}, [data, sort]);
function click(key) {
setSort(s => s.key === key ? { key, dir: s.dir === 'asc' ? 'desc' : 'asc' } : { key, dir: 'desc' });
}
return (
{columns.map(c => (
c.sortable !== false && click(c.key)} style={{
textAlign: c.align || 'left', padding: '10px 12px', color: t.dim,
fontSize: 10, letterSpacing: '0.12em', textTransform: 'uppercase',
cursor: c.sortable !== false ? 'pointer' : 'default', userSelect: 'none', fontWeight: 400,
whiteSpace: 'nowrap',
}}>
{c.label}
{sort.key === c.key && {sort.dir === 'asc' ? '↑' : '↓'} }
))}
{sorted.map((row, i) => (
{columns.map(c => (
{c.render ? c.render(row) : row[c.key]}
))}
))}
);
}
// Comparison table vs alt categories (static data below)
const COMPARISON = {
rows: [
{ label: 'Pricing model', apex: 'One-time $15,000', signals: '$50–500 / month', copy: '$50–300 / month + fees', quant: '$500–5,000 / month', prop: 'Profit split 20–50%' },
{ label: 'Your capital stays yours', apex: true, signals: true, copy: false, quant: true, prop: false },
{ label: 'You hold the API keys', apex: true, signals: false, copy: false, quant: 'Varies', prop: false },
{ label: 'Fully automated execution', apex: true, signals: false, copy: true, quant: true, prop: true },
{ label: 'Live-account track record', apex: true, signals: 'Rare', copy: 'Shared', quant: 'Often paper', prop: 'Internal' },
{ label: 'Multi-asset, multi-TF', apex: true, signals: false, copy: 'Pair-dependent', quant: 'Varies', prop: 'Varies' },
{ label: 'Lifetime updates', apex: true, signals: false, copy: false, quant: false, prop: false },
{ label: 'Lock-up / rules', apex: 'None', signals: 'None', copy: 'Vendor-driven', quant: 'None', prop: 'Strict drawdown rules' },
],
cols: ['apex', 'signals', 'copy', 'quant', 'prop'],
colLabels: {
apex: 'Apex Algo',
signals: 'Signal / Discord',
copy: 'Copy-trading',
quant: 'Quant SaaS',
prop: 'Prop firm',
},
};
function ComparisonTable({ theme }) {
const t = theme || {};
function cell(v) {
if (v === true) return ● ;
if (v === false) return — ;
return {v} ;
}
return (
{COMPARISON.cols.map(k => (
{COMPARISON.colLabels[k]}
))}
{COMPARISON.rows.map((row, i) => (
{row.label}
{COMPARISON.cols.map(k => (
{cell(row[k])}
))}
))}
);
}
return { ApplyModal, FAQ, SortableStrategyTable, ComparisonTable };
})();