// Graphite Ledger theme — shared across all screens
const TH = {
bg: '#13130f',
bg2: '#0d0d0a',
panel: '#1c1c17',
panelHi: '#23231d',
ink: '#e8e4d6',
inkSoft: '#9a9485',
muted: '#5f5a4e',
mutedHi: '#7a7464',
rule: '#2a2a22',
ruleHi: '#363630',
accent: '#d4a84b',
accentDim: '#8a6f2e',
accentHi: '#e8bb5a',
red: '#c86a5a',
green: '#7ba15c',
serif: '"Source Serif 4", "Source Serif Pro", Georgia, serif',
sans: '"Inter", -apple-system, system-ui, sans-serif',
mono: '"JetBrains Mono", ui-monospace, monospace',
};
// ── Logos ───────────────────────────────────────────────────
function Logo({ variant = 'bars', size = 24, color }) {
const c = color || TH.accent;
if (variant === 'bars') {
return (
);
}
if (variant === 'arrow') {
// Forward-stepping arrow — walk-forward metaphor
return (
);
}
if (variant === 'w') {
// Serif W with an underline tick
return (
);
}
return null;
}
// ── Common button ───────────────────────────────────────────
function Btn({ children, onClick, primary, ghost, small, style = {}, type = 'button' }) {
const base = {
padding: small ? '7px 14px' : '12px 20px',
fontFamily: TH.mono,
fontSize: small ? 11 : 12,
fontWeight: primary ? 700 : 500,
textTransform: 'uppercase',
letterSpacing: 1.2,
border: '1px solid',
cursor: 'pointer',
textAlign: 'center',
userSelect: 'none',
transition: 'all .15s',
display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8,
};
const styles = primary
? { ...base, background: TH.accent, color: TH.bg, borderColor: TH.accent, ...style }
: ghost
? { ...base, background: 'transparent', color: TH.inkSoft, borderColor: TH.rule, ...style }
: { ...base, background: 'transparent', color: TH.ink, borderColor: TH.ruleHi, ...style };
return ;
}
// ── Ticker strip ────────────────────────────────────────────
function Ticker() {
const SYMBOLS = ['BTCUSDT','ETHUSDT','SOLUSDT','AVAXUSDT','ARBUSDT','LINKUSDT','DOGEUSDT'];
const LABELS = { BTCUSDT:'BTC', ETHUSDT:'ETH', SOLUSDT:'SOL', AVAXUSDT:'AVAX', ARBUSDT:'ARB', LINKUSDT:'LINK', DOGEUSDT:'DOGE' };
const [ticks, setTicks] = React.useState([]);
const fetchPrices = React.useCallback(() => {
fetch(`https://api.binance.com/api/v3/ticker/24hr?symbols=${JSON.stringify(SYMBOLS)}`)
.then(r => r.json())
.then(data => setTicks(data.map(d => ({
label: LABELS[d.symbol],
price: parseFloat(d.lastPrice),
pct: parseFloat(d.priceChangePercent),
}))))
.catch(() => {});
}, []);
React.useEffect(() => {
fetchPrices();
const id = setInterval(fetchPrices, 10000);
return () => clearInterval(id);
}, [fetchPrices]);
const fmt = (v) => v >= 1000 ? v.toLocaleString(undefined, { maximumFractionDigits: 0 })
: v >= 1 ? v.toFixed(2)
: v.toFixed(4);
if (!ticks.length) return (
LOADING PRICES…
);
return (
{ticks.map((tk, i) => (
{tk.label}
{fmt(tk.price)}
= 0 ? TH.green : TH.red }}>
{tk.pct >= 0 ? '+' : ''}{tk.pct.toFixed(2)}%
))}
);
}
// ── Nav ─────────────────────────────────────────────────────
function Nav({ route, go, user, logoVariant = 'bars' }) {
const [mobileOpen, setMobileOpen] = React.useState(false);
const isWide = useWide();
const items = user
? [['home', 'Dashboard'], ['backtester', 'Backtest'], ['videos', 'Videos'], ['telegram', 'Community']]
: [['landing', 'Home'], ['pricing', 'Pricing'], ['videos', 'Preview']];
return (
go(user ? 'home' : 'landing')}>
Walkforward.
{isWide && (
{items.map(([k, label]) => (
go(k)} style={{
cursor: 'pointer',
color: route === k ? TH.accent : TH.inkSoft,
borderBottom: route === k ? `1px solid ${TH.accent}` : '1px solid transparent',
paddingBottom: 3,
}}>{label}
))}
)}
{user ? (
<>
{isWide &&
go('billing')} style={{ fontSize: 12, color: TH.inkSoft, cursor: 'pointer', fontFamily: TH.mono, textTransform: 'uppercase', letterSpacing: 1 }}>Billing}
go('home')} style={{
width: 32, height: 32, borderRadius: '50%', background: TH.panelHi,
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontFamily: TH.mono, fontSize: 12, color: TH.accent, cursor: 'pointer',
border: `1px solid ${TH.rule}`,
}}>{(user.name || 'U')[0].toUpperCase()}
>
) : (
<>
{isWide &&
go('login')} style={{ fontSize: 13, color: TH.inkSoft, cursor: 'pointer' }}>Log in}
go('signup')}>Join →
>
)}
{!isWide && (
setMobileOpen(v => !v)} style={{ padding: 6, color: TH.ink, cursor: 'pointer' }}>
)}
{!isWide && mobileOpen && (
{items.map(([k, label]) => (
{ go(k); setMobileOpen(false); }} style={{
padding: '12px 0', color: route === k ? TH.accent : TH.inkSoft,
borderBottom: `1px solid ${TH.rule}`, cursor: 'pointer',
}}>{label}
))}
{user && (
{ go('billing'); setMobileOpen(false); }} style={{ padding: '12px 0', color: TH.inkSoft, cursor: 'pointer' }}>Billing
)}
)}
);
}
// ── Footer ──────────────────────────────────────────────────
function Footer() {
const wide = useWide();
return (
A journal & workbench for systematic crypto traders. Built by quants, open to anyone willing to read the data.
{[
['Product', ['Backtester', 'Research memos', 'Video library', 'Telegram']],
['Company', ['Methodology', 'Changelog', 'Press', 'Contact']],
['Legal', ['Terms', 'Privacy', 'Risk disclosure', 'Not advice']],
].map(([title, links]) => (
{title.toUpperCase()}
{links.map(l =>
{l}
)}
))}
© 2026 WALKFORWARD RESEARCH
NOT INVESTMENT ADVICE · TRADE AT YOUR OWN RISK
BILLING VIA WHOP
);
}
// Hook — viewport width >= 820
function useWide() {
const [w, setW] = React.useState(typeof window !== 'undefined' ? window.innerWidth >= 820 : true);
React.useEffect(() => {
const h = () => setW(window.innerWidth >= 820);
window.addEventListener('resize', h);
return () => window.removeEventListener('resize', h);
}, []);
return w;
}
Object.assign(window, { TH, Logo, Btn, Ticker, Nav, Footer, useWide });