/* HERCULEAN leaderboard.
Reads window.HERC from ./data.js. No libraries. */
(() => {
const H = window.HERC, M = H.meta;
const $ = (s, el = document) => el.querySelector(s);
const $$ = (s, el = document) => [...el.querySelectorAll(s)];
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const WF = H.workflows; // Trading, Hedging, Market Insights, Auditing
const WC = { 'Trading': '#d6304a', 'Hedging': '#8a3ce0', 'Market Insights': '#2f6fd6', 'Auditing': '#11949f' };
const SHORT = { 'Market Insights': 'Insights' };
const AC = { 'ReAct': '#2a78d6', 'Claude Code': '#eb6834', 'Codex': '#1baf7a', 'Hermes': '#eda100', 'OpenClaw': '#e87ba4' };
const INI = { 'ReAct': 'RA', 'Claude Code': 'CC', 'Codex': 'CX', 'Hermes': 'HM', 'OpenClaw': 'OC' };
const INI_FG = { 'ReAct': '#fff' }; // dark text on the lighter badges
const ini = (a, cls = 'ini') => `${INI[a]} `;
/* ---------- agent systems: Overall = mean of the 4 workflow scores, Floor = min ---------- */
const rows = H.scores.map(s => {
const v = WF.map(w => s[w]);
return { ...s, key: `${s.agent}|${s.model}`, name: `${s.agent} · ${s.model}`,
Overall: v.reduce((a, b) => a + b, 0) / v.length, Floor: Math.min(...v) };
}).sort((a, b) => b.Overall - a.Overall);
rows.forEach((r, i) => r.rank = i + 1);
const top = rows[0], N = rows.length, S = H.offlineStd;
// paper rounding (half up), so 96.55 shows as 96.6
const f1 = v => (Math.round(v * 10 + 1e-9) / 10).toFixed(1);
const fmtRho = v => (v < 0 ? '−' : '') + Math.abs(v).toFixed(2);
const pct = v => v == null ? '—' : (v > 0 ? '+' : v < 0 ? '−' : '') + Math.abs(v).toFixed(2) + '%';
const num = (v, d = 2) => v == null ? '—' : (v < 0 ? '−' : '') + Math.abs(v).toFixed(d);
const pm = (txt, sd, d = 2) => sd == null ? txt : `${txt}±${Math.abs(sd).toFixed(d)} `;
const mean = a => { const x = a.filter(v => v != null); return x.length ? x.reduce((p, q) => p + q, 0) / x.length : null; };
/* best score per workflow (ties count for every tied system) */
const best = Object.fromEntries(WF.map(w => [w, Math.max(...rows.map(r => r[w]))]));
const isBest = (r, w) => Math.abs(r[w] - best[w]) < 1e-6;
const leaders = Object.fromEntries(WF.map(w => [w, rows.filter(r => isBest(r, w))]));
const leadCount = {}; WF.forEach(w => leaders[w].forEach(r => leadCount[r.key] = (leadCount[r.key] || 0) + 1));
const maxLeads = Math.max(...Object.values(leadCount));
/* Spearman ρ between workflow rankings (average ranks for ties) */
function ranks(v) {
const idx = v.map((x, i) => [x, i]).sort((a, b) => a[0] - b[0]), r = new Array(v.length);
for (let i = 0; i < idx.length;) { let j = i; while (j + 1 < idx.length && idx[j + 1][0] === idx[i][0]) j++;
for (let k = i; k <= j; k++) r[idx[k][1]] = (i + j) / 2 + 1; i = j + 1; }
return r;
}
function spearman(a, b) {
const ra = ranks(a), rb = ranks(b), n = a.length, m = (n + 1) / 2;
let sab = 0, saa = 0, sbb = 0;
for (let i = 0; i < n; i++) { sab += (ra[i] - m) * (rb[i] - m); saa += (ra[i] - m) ** 2; sbb += (rb[i] - m) ** 2; }
return sab / Math.sqrt(saa * sbb);
}
const rhos = []; WF.forEach((a, i) => WF.slice(i + 1).forEach(b => rhos.push(spearman(rows.map(r => r[a]), rows.map(r => r[b])))));
const meanRho = rhos.reduce((p, q) => p + q, 0) / rhos.length;
/* score magnitude: one purple hue, light → dark */
const RAMP = [[244, 240, 251], [201, 182, 234], [142, 106, 208], [75, 42, 143]];
function shade(v) {
const t = Math.max(0, Math.min(1, v / 100)) * (RAMP.length - 1), i = Math.min(RAMP.length - 2, Math.floor(t)), f = t - i;
const c = RAMP[i].map((x, k) => Math.round(x + (RAMP[i + 1][k] - x) * f));
return { bg: `rgb(${c})`, fg: v > 58 ? '#fff' : '#0f1528' };
}
/* ---------- tooltip ---------- */
const tip = $('#tip');
document.addEventListener('pointerover', e => { const t = e.target.closest('[data-tip]'); if (!t) return; tip.innerHTML = t.dataset.tip; tip.classList.add('on'); });
document.addEventListener('pointermove', e => {
if (!tip.classList.contains('on')) return;
const x = Math.min(innerWidth - tip.offsetWidth - 10, e.clientX + 14), y = e.clientY - tip.offsetHeight - 12;
tip.style.left = x + 'px'; tip.style.top = (y < 6 ? e.clientY + 18 : y) + 'px';
});
document.addEventListener('pointerout', e => { if (e.target.closest('[data-tip]')) tip.classList.remove('on'); });
/* ================= HERO ================= */
// small lion for the "New benchmark" pill: scalloped mane, face, eyes, nose
const LION = (() => {
// spiky mane (16-point star), ears, face, eyes, nose and muzzle
const pts = Array.from({ length: 32 }, (_, i) => { const a = i / 32 * 2 * Math.PI - Math.PI / 2, r = i % 2 ? 8.6 : 11.4;
return `${(12 + r * Math.cos(a)).toFixed(2)},${(12.6 + r * Math.sin(a)).toFixed(2)}`; }).join(' ');
const face = '#6b1590';
return `
`;
})();
$('#lion').innerHTML = LION;
// the same lion beside "Cite HERCULEAN"
if ($('#citeLion')) $('#citeLion').innerHTML = LION;
// hovering the lion or the citation panel speeds the mane up; playbackRate keeps the current angle (no jump)
(() => {
const lion = $('#citeLion'), bib = $('#resources .bib'); if (!lion) return;
const rate = r => { const m = $('.mane', lion); if (m) m.getAnimations().forEach(x => (x.playbackRate = r)); };
[lion, bib].filter(Boolean).forEach(el => { el.addEventListener('pointerenter', () => rate(14 / 3)); el.addEventListener('pointerleave', () => rate(1)); });
})();
// rotating subject: one architecture at a time, in its own colour
(() => {
const r = $('#rotator');
r.innerHTML = H.agents.map((a, i) => `${a} `).join('');
const sp = $$('span', r); let i = 0;
const MS_ROT = 1600; // one name every 1.6 s
r.style.setProperty('--rot', MS_ROT + 'ms');
// width follows the current name; the underline takes its colour
const fit = () => { r.style.width = sp[i].offsetWidth + 'px'; r.style.setProperty('--uc', AC[H.agents[i]]); };
fit(); document.fonts && document.fonts.ready.then(fit);
if (reduce) return;
setInterval(() => {
const prev = sp[i]; prev.classList.remove('on'); prev.classList.add('out');
i = (i + 1) % sp.length; sp[i].classList.remove('out'); sp[i].classList.add('on'); fit();
setTimeout(() => prev.classList.remove('out'), 600);
}, MS_ROT);
})();
$('#awardN').textContent = `${H.agents.length} architectures × ${H.models.length} backbones`;
$('#stats').innerHTML = [[N, 'agent systems', `${H.agents.length} architectures × ${H.models.length} backbones`], [WF.length, 'workflows', WF.join(' · ')], [M.offlineDays, 'offline days', `trading days, ${M.offlineWindow[0]} → ${M.offlineWindow[1]}`], [M.liveDays, 'live days', `trading days, ${M.liveWindow[0]} → ${M.liveWindow[1]}; Trading, Hedging and Market Insights, 3 selected systems per workflow`], [M.runs, 'runs', `every agent system is run ${M.runs} times independently`, '×']]
.map(([n, l, t, suf]) => `
0 ${l}
`).join('');
// "Top 8 · Overall score" card: bars coloured by architecture, initials badge on top
const MS = { 'Claude Sonnet 5': 'Sonnet 5', 'DeepSeek-V3.2': 'DS-V3.2', 'DeepSeek-V4-Pro': 'DS-V4-Pro', 'Qwen3.5-9B': 'Qwen-9B', 'Qwen3.5-4B': 'Qwen-4B' };
// jump into the Rankings section on a given view, optionally with a selection (set by the Rankings block below)
let rkGo = null;
const goRank = (view, sel) => { if (rkGo) rkGo(view, sel); document.getElementById('rankings').scrollIntoView({ behavior: reduce ? 'auto' : 'smooth', block: 'start' }); };
$('#award').addEventListener('click', e => { e.preventDefault(); goRank('flow'); });
const pod = $('#podium'), TOPN = 8;
pod.innerHTML = rows.slice(0, TOPN).map((r, i) => `
${f1(r.Overall)} ${r.rank}
${r.agent}${MS[r.model] || r.model} `).join('');
// ticker: every agent system by Overall rank; the list is doubled so the loop is seamless
const tickItem = r => `#${String(r.rank).padStart(2, '0')} ${ini(r.agent, 'ini sm')}${r.agent} · ${MS[r.model] || r.model} ${f1(r.Overall)} `;
$('#tickerTrack').innerHTML = rows.map(tickItem).join('').repeat(2);
// constant speed (~70 px/s, like a ticker tape) whatever the item widths
const setTickerSpeed = () => { const t = $('#tickerTrack'); t.style.setProperty('--dur', (t.scrollWidth / 2 / 70).toFixed(1) + 's'); };
setTickerSpeed(); document.fonts && document.fonts.ready.then(setTickerSpeed);
$('#ticker').addEventListener('click', e => {
const t = e.target.closest('.tk'); if (!t) return;
const i = rows.findIndex(r => r.key === t.dataset.key);
if (openKey !== rows[i].key) toggle(i);
document.getElementById('leaderboard').scrollIntoView({ behavior: reduce ? 'auto' : 'smooth' });
});
$('#heroLegend').innerHTML = H.agents.map(a => ` ${a} `).join('');
// architecture and backbone rankings: mean Overall over the systems that share the architecture (6 backbones)
// or the backbone (5 architectures) — a page-only aggregate of the leaderboard
{
const agg = (keys, by) => keys.map(k => { const rs = rows.filter(r => r[by] === k), v = rs.map(r => r.Overall);
return { k, m: v.reduce((p, q) => p + q, 0) / v.length, lo: Math.min(...v), hi: Math.max(...v), best: rs[0] }; }).sort((p, q) => q.m - p.m);
const A = agg(H.agents, 'agent'), B = agg(H.models, 'model'), mx = Math.max(...A.map(x => x.m), ...B.map(x => x.m));
const row = (x, i, kind) => `
${i + 1} ${kind === 'agent' ? ini(x.k, 'ini sm') : ''}${kind === 'agent' ? x.k : (MS[x.k] || x.k)}
${f1(x.m)}
`;
$('#heroLeaders').innerHTML = `Architectures mean Overall
${A.map((x, i) => row(x, i, 'agent')).join('')}
Backbones mean Overall
${B.map((x, i) => row(x, i, 'model')).join('')}
`;
}
$('#vizCap').textContent = `Top ${TOPN} · Overall score`;
// clicking a bar opens Rankings on the Floor → Overall view with that system highlighted;
// clicking an architecture / backbone row opens the Field view with that group highlighted
pod.addEventListener('click', e => { const c = e.target.closest('.col'); if (c) goRank('floor', { mode: 'system', key: c.dataset.key }); });
$('#heroLeaders').addEventListener('click', e => { const g = e.target.closest('.gr'); if (g) goRank('field', { mode: g.dataset.kind, key: g.dataset.k }); });
function countUp(el) {
const to = +el.dataset.count, dec = el.dataset.dec != null ? +el.dataset.dec : 1, suf = el.dataset.suf || '', d = 1200, t0 = performance.now();
const show = v => (el.textContent = v.toFixed(dec) + suf);
if (reduce) { show(to); return; }
setTimeout(() => show(to), d + 300); // always land on the value
// rAF time can precede t0 on the first frame; clamp so the count never starts below zero
const step = t => { const p = Math.max(0, Math.min(1, (t - t0) / d)), e = 1 - Math.pow(1 - p, 3);
show(to * e); if (p < 1) requestAnimationFrame(step); };
requestAnimationFrame(step);
}
/* ================= 1 · WORKFLOW CARDS ================= */
const ICONS = {
env: ' ',
proto: ' ',
eval: ' ',
};
// each workflow = Environment + Interaction Protocol + Trajectory Evaluation (short; the paper has the details)
const RECIPE = {
'Trading': [`Daily prices, news and 10-K / 10-Q filings for ${H.tradeAssets.join(', ')}`, `One BUY, SELL or HOLD per day for ${M.offlineDays} trading days`, ['CR', 'SR', 'MDD', 'vs Buy & Hold']],
'Hedging': ['The same data across eight large-cap stocks', 'Pick a pair on day 1, then LONG_SHORT, SHORT_LONG, HOLD or CLOSE daily', ['CR', 'SR', 'MDD', 'dollar-neutral']],
'Market Insights': ['Weekly market context with a fixed peer mapping', 'A weekly eight-section report and rating, about 13 per asset', ['Quality 0–10', 'CR', 'SR', 'MDD']],
'Auditing': [`SEC XBRL filings with the US-GAAP taxonomy, ${M.auditInstances} instances`, 'Commit the reported and the recalculated value of one fact', ['ACC', 'SER', 'EER', 'CER']],
};
// one line on what each workflow is for (directional vs market-neutral; forward- vs backward-looking)
const PURPOSE = {
'Trading': 'Seek profit as the market evolves, one directional decision a day.',
'Hedging': 'Seek profit with market risk hedged out, through a market-neutral pair.',
'Market Insights': 'Look forward: turn evidence into a view on an asset’s future.',
'Auditing': 'Look back: verify what a filing reports against its own numbers.',
};
const PARTS = [['Environment', 'env'], ['Interaction Protocol', 'proto'], ['Trajectory Evaluation', 'eval']];
$('#wfGrid').innerHTML = WF.map((w, i) => {
const lead = leaders[w], [env, proto, ev] = RECIPE[w];
const who = lead.length === 1 ? `${lead[0].agent} · ${lead[0].model} `
: `${lead[0].agent} · ${lead[0].model} +${lead.length - 1} tied `;
const item = (j, body) => `${ICONS[PARTS[j][1]]} ${PARTS[j][0]} ${body}
`;
return `
Workflow0${i + 1}
${w}
${PURPOSE[w]}
`;
}).join('');
/* ================= 2 · LEADERBOARD ================= */
const score = (r, v) => v === 'Overall' ? r.Overall : v === 'Workflow floor' ? r.Floor : r[v];
let view = 'Overall', openKey = null, showAll = false;
const LIMIT = 10; // rows shown before "show all"
const hidden = new Set(), hiddenM = new Set();
$('#lbKicker').textContent = `Leaderboard · offline regime · ${M.offlineWindow[0]} → ${M.offlineWindow[1]}`;
$('#lbSub').textContent = `Click a column to re-rank all ${N} agent systems. Click a row for its details.`;
$('#lbNote').innerHTML = `Each metric (mean of ${M.runs} runs) is rank-normalised across the ${N} agent systems to 0–100. Overall is the mean of the four workflow scores; the workflow floor is the lowest.`;
// ranking is chosen from the column headers (Overall or one workflow); the bar always shows Overall
$('#archChips').innerHTML = H.agents.map(a => `${ini(a, 'ini sm')}${a} `).join('');
$('#archChips').addEventListener('click', e => {
const b = e.target.closest('button'); if (!b) return;
const a = b.dataset.a;
if (hidden.has(a)) hidden.delete(a); else if (hidden.size < H.agents.length - 1) hidden.add(a);
$$('button', $('#archChips')).forEach(x => { x.classList.toggle('off', hidden.has(x.dataset.a)); x.setAttribute('aria-pressed', !hidden.has(x.dataset.a)); });
layout();
});
$('#modelChips').innerHTML = H.models.map(m => `${MS[m] || m} `).join('');
$('#modelChips').addEventListener('click', e => {
const b = e.target.closest('button'); if (!b) return;
const m = b.dataset.m;
if (hiddenM.has(m)) hiddenM.delete(m); else if (hiddenM.size < H.models.length - 1) hiddenM.add(m);
$$('button', $('#modelChips')).forEach(x => { x.classList.toggle('off', hiddenM.has(x.dataset.m)); x.setAttribute('aria-pressed', !hiddenM.has(x.dataset.m)); });
layout();
});
$('#colCells').innerHTML = WF.map(w => ` ${SHORT[w] || w}↓ `).join('');
$('#board .lb-cols').addEventListener('click', e => { const b = e.target.closest('.sort-h'); if (!b || b.dataset.v === view) return; view = b.dataset.v; layout(); });
// one DOM row per system, created once and moved with transforms
const box = $('#lbRows');
box.innerHTML = rows.map((r, i) => `
${ini(r.agent)}${r.agent} ${r.model}
${WF.map(w => { const s = shade(r[w]);
return `${f1(r[w])} `; }).join('')}
›
`).join('') + '
';
const els = $$('.lb-row', box), detail = $('#lbDetail');
function fitNames() {
const w = Math.max(...$$('.lb-name', box).map(n => [...n.children].reduce((s, c) => s + Math.max(c.getBoundingClientRect().width, c.scrollWidth), 0) + 22));
$('#board').style.setProperty('--namew', Math.ceil(w) + 'px');
}
const ROW = 40;
let revealed = false;
function layout() {
const vis = rows.map((r, i) => ({ r, i })).filter(x => !hidden.has(x.r.agent) && !hiddenM.has(x.r.model))
.sort((a, b) => score(b.r, view) - score(a.r, view) || b.r.Overall - a.r.Overall);
if (openKey && (hidden.has(openKey.split('|')[0]) || hiddenM.has(openKey.split('|')[1]))) openKey = null;
// open row beyond the first LIMIT rows (e.g. from the ticker): show everything
if (openKey && !showAll && vis.findIndex(x => x.r.key === openKey) >= LIMIT) showAll = true;
const shown = showAll ? vis : vis.slice(0, LIMIT);
els.forEach(el => el.classList.add('gone'));
let y = 0;
shown.forEach((x, pos) => {
const el = els[x.i], open = openKey === x.r.key;
el.classList.remove('gone'); el.classList.toggle('open', open); el.classList.toggle('top', pos === 0);
$('.in', el).setAttribute('aria-expanded', open);
el.style.transform = `translateY(${y}px)`;
$('.lb-pos', el).textContent = String(pos + 1).padStart(2, '0');
$('.lb-bar', el).style.width = (revealed ? x.r.Overall : 0) + '%';
$('.lb-val', el).textContent = f1(x.r.Overall);
$('.cells', el).classList.toggle('dim', WF.includes(view));
$$('.cell', el).forEach(c => c.classList.toggle('on', c.dataset.w === view));
y += ROW;
if (open) { detail.style.transform = `translateY(${y}px)`; y += detail.offsetHeight; }
});
if (openKey && !shown.some(x => x.r.key === openKey)) openKey = null;
detail.classList.toggle('show', !!openKey);
const more = $('#lbMore'), rest = vis.length - LIMIT;
more.hidden = rest <= 0;
more.setAttribute('aria-expanded', showAll);
more.innerHTML = showAll ? `Show top ${LIMIT} only ↓ ` : `Show all ${vis.length} agent systems (+${rest}) ↓ `;
box.style.height = y + 'px';
$$('#board .sort-h').forEach(b => b.classList.toggle('on', b.dataset.v === view));
}
$('#lbMore').addEventListener('click', () => {
showAll = !showAll;
if (!showAll) openKey = null; // otherwise an open row below #15 would re-expand the list
layout();
if (!showAll) document.getElementById('board').scrollIntoView({ behavior: reduce ? 'auto' : 'smooth', block: 'start' });
});
// expanded row: a one-line summary, then four small cards, one chart each
const wfRank = (r, w) => 1 + rows.filter(x => x[w] > r[w] + 1e-9).length;
const isLive = (w, k) => (M.liveConfigs[w] || []).includes(k);
const pc0 = v => v == null ? '—' : (v > 0 ? '+' : v < 0 ? '−' : '') + Math.abs(v).toFixed(1) + '%';
const sdS = (v, d = 1) => v == null ? '' : `±${Math.abs(v).toFixed(d)} `;
// shared CR axis across every system and Buy & Hold, so trading dots are comparable between rows
const crAll = H.tradeAssets.flatMap(a => [...rows.map(r => H.offline.Trading[a].CR[r.key]), H.baseline[`Trading|${a}|CR`]]).filter(v => v != null);
const crMin = Math.min(0, ...crAll), crMax = Math.max(0, ...crAll), xPos = v => (v - crMin) / (crMax - crMin) * 100;
const ACTION = { B: 'BUY', S: 'SELL', H: 'HOLD', L: 'LONG_SHORT', R: 'SHORT_LONG', C: 'CLOSE', '-': 'no committed record (scored as HOLD)' };
function breakdown(r) {
const k = r.key, O = H.offline, A = H.tradeAssets;
const fw = WF.find(w => r[w] === r.Floor), liveWf = Object.keys(M.liveConfigs).filter(w => isLive(w, k));
const best = WF.reduce((p, w) => r[w] > r[p] ? w : p, WF[0]);
const liveTip = M.liveRule.replace(/"/g, '"');
const tag = w => (w === best ? 'best ' : w === fw ? 'floor ' : '')
+ (liveWf.includes(w) ? `live ` : '');
const rpl = w => (w === 'Trading' || w === 'Hedging') ? ` Replay ` : '';
const card = (w, sub, body) => ` ${w}${tag(w)}${f1(r[w])} #${wfRank(r, w)} ${sub}${rpl(w)}
${body}
`;
// Trading: CR per asset (dot) against Buy & Hold (tick)
const tr = A.map(a => { const v = O.Trading[a].CR[k], sd = S.Trading[a].CR[k], bh = H.baseline[`Trading|${a}|CR`];
const sr = O.Trading[a].SR[k], mdd = O.Trading[a].MDD[k];
return `${a}
${v == null ? '' : ` `}
${pct(v)}${sdS(sd)}
`; }).join('');
const beats = A.filter(t => O.Trading[t].CR[k] >= H.baseline[`Trading|${t}|CR`]).length;
const trading = card('Trading', 'Cumulative return (CR) per asset · hover a row for SR and MDD', tr + `Beats Buy & Hold on ${beats} of ${A.length} tickers
agent CR Buy & Hold CR 0%
`);
// Hedging: pair, CR / SR / MDD, 61-day action strip
const hp = O.Hedging.PAIR, hs = S.Hedging.PAIR, pair = H.pairs[k], pr = H.pairRuns[k], acts = H.hedgingActions[k] || '';
const [pl, prt] = pair ? pair.split('/') : [];
const hedging = card('Hedging', 'Pair chosen on day 1 (most frequent over runs)',
(pair ? `${pl}/ ${prt}${pr ? `${pr.runs}/${pr.of} runs ` : ''}
` : 'no pair
') +
`CR ${pct(hp.CR[k])}${sdS(hs.CR[k])}
SR ${num(hp.SR[k])}${sdS(hs.SR[k], 2)}
MDD ${hp.MDD[k] == null ? '— ' : hp.MDD[k].toFixed(2) + '%' + sdS(hs.MDD[k])}
` +
(acts ? (() => {
// same encoding as the paper's Figure 3b: position state per day (HOLD and days without a committed record keep
// the previous position, as in the evaluator); CLOSE days are marked with a diamond
let p = 'F';
const days = [...acts].map((c, i) => { const cl = c === 'C'; if (c === 'L' || c === 'R') p = c; else if (cl) p = 'F'; return { s: p, cl, c, i }; });
const pn = { L: `Long-Short (long ${pl}, short ${prt})`, R: `Short-Long (short ${pl}, long ${prt})`, F: 'Flat' };
return `Position each day · run ${pr ? pr.shownRun : 1} of ${M.runs}
${days.map(d => ` `).join('')}
`;
})() : '') +
` Long-Short Short-Long Flat Close
`);
// Market Insights: report quality per asset, rating-strategy CR vs Buy & Hold
const qs = A.map(a => O['Market Insights'][a].Score[k]), qsd = A.map(a => S['Market Insights'][a].Score[k]);
const insights = card('Market Insights', `Report quality (0–10), rubric over ${M.insightsRubric.length} dimensions`,
`${num(mean(qs))}mean / 10
` +
`score CR B&H
` +
A.map((a, i) => { const cr = O['Market Insights'][a].CR[k], bh = H.baseline[`Market Insights|${a}|CR`];
return `${a} ${f1(qs[i])} ${pc0(cr)} ${pc0(bh)}
`; }).join(''));
// Auditing: ACC and the error split (ACC + SER + EER + CER = 100%)
const MS4 = ['ACC', 'SER', 'EER', 'CER'], au = MS4.map(m => O.Auditing.AUDIT[m][k]), asd = MS4.map(m => S.Auditing.AUDIT[m][k]);
const NAMES = { ACC: 'Accuracy', SER: 'Schema error', EER: 'Extraction error', CER: 'Calculation error' };
const auditing = card('Auditing', `${M.auditInstances} XBRL instances · ACC + SER + EER + CER = 100%`,
`${f1(au[0])}% ACC${asd[0] == null ? '' : ' ±' + asd[0].toFixed(1)}
` +
`${MS4.map((m, i) => au[i] ? ` ` : '').join('')}
` +
MS4.map((m, i) => ` ${NAMES[m]} ${m} ${f1(au[i])}%${sdS(asd[i])}
`).join(''));
return `${trading}${hedging}${insights}${auditing}
`;
}
function toggle(i) {
const r = rows[i];
openKey = openKey === r.key ? null : r.key;
if (openKey) detail.innerHTML = breakdown(r);
layout();
}
detail.addEventListener('click', e => { const b = e.target.closest('[data-replay]'); if (b) { e.stopPropagation(); replayTo(b.dataset.replay); } });
els.forEach((el, i) => {
const t = $('.in', el);
t.addEventListener('click', () => toggle(i));
t.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggle(i); } });
});
fitNames(); document.fonts && document.fonts.ready.then(fitNames);
layout();
/* ================= 2b · RANKINGS ACROSS WORKFLOWS ================= */
// bump chart with linked highlighting: pick one system, a whole architecture, or a whole backbone
(() => {
const NS = 'http://www.w3.org/2000/svg';
const mk = (tag, attrs = {}, parent) => { const e = document.createElementNS(NS, tag); for (const k in attrs) e.setAttribute(k, attrs[k]); if (parent) parent.appendChild(e); return e; };
const n = rows.length;
// y position = order within a workflow (ties broken by Overall); label = competition rank, "=" marks a tie
const pos = {}, lab = {}, rnum = {};
WF.forEach(w => {
[...rows].sort((x, y) => y[w] - x[w] || y.Overall - x.Overall).forEach((r, i) => {
(pos[r.key] ||= {})[w] = i + 1;
const c = 1 + rows.filter(x => x[w] > r[w] + 1e-9).length, tie = rows.filter(x => Math.abs(x[w] - r[w]) < 1e-9).length > 1;
(lab[r.key] ||= {})[w] = (tie ? '=' : '') + c; (rnum[r.key] ||= {})[w] = c;
});
});
const topIn = w => [...rows].sort((x, y) => y[w] - x[w] || y.Overall - x.Overall);
const topBy = s => s === 'Overall' ? rows : s === 'Floor' ? [...rows].sort((x, y) => y.Floor - x.Floor || y.Overall - x.Overall) : topIn(s);
const ordinal = k => k + (k % 100 >= 11 && k % 100 <= 13 ? 'th' : ({ 1: 'st', 2: 'nd', 3: 'rd' }[k % 10] || 'th'));
const short = m => MS[m] || m;
/* ---------- chart (built once; selection only toggles classes) ---------- */
// side margins just wide enough for the longest labels: rank ticks + architecture on the left, backbone + rank on the right
const textW = (txt, size, weight = 500) => { const c = textW.c || (textW.c = document.createElement('canvas').getContext('2d')); c.font = `${weight} ${size}px Inter, system-ui, sans-serif`; return c.measureText(txt).width; };
const longAgent = Math.max(...H.agents.map(ag => textW(ag, 11.5))), longModel = Math.max(...rows.map(r => textW(`${short(r.model)} #${r.rank}`, 11.5, 600)));
const W = 1116, T = 52, rowH = 18, Hh = T + n * rowH + 10;
const L = Math.ceil(4 + textW('#30', 10.5) + 18 + longAgent + 20 + 6), R = Math.ceil(20 + longModel + 10);
const cx = i => L + i * (W - L - R) / (WF.length - 1), cy = p => T + (p - 1) * rowH + rowH / 2;
const svg = mk('svg', { viewBox: `0 0 ${W} ${Hh}`, role: 'img', 'aria-label': 'Rank of every agent system in each workflow' });
const defs = mk('defs', {}, svg), gr = mk('linearGradient', { id: 'wfGrad', gradientUnits: 'userSpaceOnUse', x1: cx(0), x2: cx(WF.length - 1), y1: 0, y2: 0 }, defs);
WF.forEach((w, i) => mk('stop', { offset: i / (WF.length - 1), 'stop-color': WC[w] }, gr));
WF.forEach((w, i) => {
mk('line', { x1: cx(i), x2: cx(i), y1: T - 10, y2: Hh - 4, class: 'lane' }, svg);
// header = button: follow this workflow's leader
const ld = leaders[w][0], hg = mk('g', { class: 'col-btn', 'data-k': ld.key, 'data-w': w, style: `--c:${WC[w]}`, tabindex: 0, role: 'button',
'data-tip': `${w} · top 3 ${[...rows].sort((x, y) => y[w] - x[w] || y.Overall - x.Overall).slice(0, 3).map((r, j) => `${j + 1}. ${r.name}`).join(' ')}click to highlight ` }, svg);
const tw = w.length * 7.6 + 34;
mk('rect', { x: cx(i) - tw / 2, y: 2, width: tw, height: 26, rx: 13, class: 'cb-bg' }, hg);
mk('circle', { cx: cx(i) - tw / 2 + 13, cy: 15, r: 3.5, class: 'cb-dot' }, hg);
mk('text', { x: cx(i) + 6, y: 19.5, 'text-anchor': 'middle', class: 'col-h' }, hg).textContent = w;
});
[1, 5, 10, 15, 20, 25, 30].filter(p => p <= n).forEach(p => (mk('text', { x: 4, y: cy(p) + 3.5, class: 'tick' }, svg).textContent = `#${p}`));
const lines = {}, layer = mk('g', {}, svg), lbls = mk('g', { class: 'lbls' }, svg);
rows.forEach(r => {
const pts = WF.map((w, i) => [cx(i), cy(pos[r.key][w])]);
let d = `M${pts[0][0]},${pts[0][1]}`;
for (let i = 1; i < pts.length; i++) { const mx = (pts[i - 1][0] + pts[i][0]) / 2; d += ` C${mx},${pts[i - 1][1]} ${mx},${pts[i][1]} ${pts[i][0]},${pts[i][1]}`; }
const g = mk('g', { class: 'ln', 'data-key': r.key }, layer);
const tipAll = `${r.name} ${WF.map(w => `${SHORT[w] || w} #${lab[r.key][w]}`).join(' · ')}#${r.rank} Overall · click to follow `;
mk('path', { d, class: 'line' }, g);
mk('path', { d, class: 'hit', 'data-tip': tipAll }, g);
WF.forEach((w, i) => {
mk('circle', { cx: pts[i][0], cy: pts[i][1], r: 2.6, style: `--wc:${WC[w]}`, 'data-tip': `${r.name} ${w}: ${ordinal(rnum[r.key][w])}${lab[r.key][w][0] === '=' ? ' (tied)' : ''} of ${n} · score ${f1(r[w])} ` }, g);
const t = mk('text', { x: pts[i][0], y: pts[i][1] + 4, class: 'rkl' }, g); t.textContent = lab[r.key][w];
if (lab[r.key][w].length > 2) t.style.fontSize = '9px';
});
lines[r.key] = g;
});
$('#rkChart').appendChild(svg);
svg.classList.add('rk-flow');
/* ---------- second view: workflow floor → Overall ---------- */
// one row per system (Overall order): its four workflow scores as coloured dots, floor (open ring) joined to Overall (filled dot)
const DW = 1116, DT = 58, DH = 17, DL = 250, DR = 70, dH = DT + n * DH + 34;
const dx = v => DL + v / 100 * (DW - DL - DR);
const db = mk('svg', { viewBox: `0 0 ${DW} ${dH}`, class: 'rk-db', role: 'img', 'aria-label': 'Workflow floor, workflow scores and Overall score of every agent system' });
// user-space gradient: a horizontal line has a zero-height bounding box, so a bbox gradient would not paint
const dgr = mk('linearGradient', { id: 'brandGrad', gradientUnits: 'userSpaceOnUse', x1: dx(0), x2: dx(100), y1: 0, y2: 0 }, mk('defs', {}, db));
mk('stop', { offset: 0, 'stop-color': '#b40028' }, dgr); mk('stop', { offset: 1, 'stop-color': '#7800f0' }, dgr);
// sort chips: Overall, workflow floor, then the four workflows (re-orders the rows; the highlight is unchanged)
// same pills as the other two views' headers: centred, tinted, filled when chosen
const DSORT = [['Overall', 'Overall', '#4a1a8c'], ['Floor', 'Workflow floor', '#4a1a8c'], ...WF.map(w => [w, w, WC[w]])];
const dpw = l => l.length * 7.6 + 34, dGap = 10;
let lx = (DW - DSORT.reduce((s, [, l]) => s + dpw(l), 0) - dGap * (DSORT.length - 1)) / 2, dbSort = 'Overall';
DSORT.forEach(([k, lbl, c]) => {
const tw = dpw(lbl), g = mk('g', { class: 'db-sort' + (k === dbSort ? ' on' : ''), 'data-s': k, style: `--c:${c}`, tabindex: 0, role: 'button',
'data-tip': `sort by ${lbl}${k === 'Floor' ? 'ring = the weakest workflow’s dot ' : k === 'Overall' ? 'black dot = Overall ' : ''}` }, db);
mk('rect', { x: lx, y: 2, width: tw, height: 26, rx: 13, class: 'cb-bg' }, g);
if (k === 'Floor') { mk('circle', { cx: lx + 13, cy: 15, r: 2.2, class: 'cb-dot' }, g); mk('circle', { cx: lx + 13, cy: 15, r: 4.6, class: 'cb-ring' }, g); }
else mk('circle', { cx: lx + 13, cy: 15, r: 3.5, class: 'cb-dot' }, g);
mk('text', { x: lx + tw / 2 + 6, y: 19.5, 'text-anchor': 'middle', class: 'col-h' }, g).textContent = lbl;
lx += tw + dGap;
});
[0, 25, 50, 75, 100].forEach(v => {
mk('line', { x1: dx(v), x2: dx(v), y1: DT - 8, y2: DT + n * DH, class: 'lane' }, db);
mk('text', { x: dx(v), y: DT + n * DH + 18, 'text-anchor': 'middle', class: 'tick' }, db).textContent = v;
});
const dbRows = {}, dbLayer = mk('g', {}, db);
rows.forEach((r, i) => {
const y = DT + i * DH + DH / 2, vals = WF.map(w => r[w]), lo = Math.min(...vals), hi = Math.max(...vals);
const g = mk('g', { class: 'db', 'data-key': r.key }, dbLayer);
mk('rect', { x: 0, y: y - DH / 2, width: DW, height: DH, rx: 6, class: 'db-band', 'data-tip': `#${r.rank} ${r.name} Overall ${f1(r.Overall)} · floor ${f1(r.Floor)} ${WF.map(w => `${SHORT[w] || w} ${f1(r[w])}`).join(' · ')}` }, g);
mk('text', { x: DL - 46, y: y + 4, class: 'db-name', 'text-anchor': 'end' }, g).textContent = `${r.agent} · ${short(r.model)}`;
g.dataset.i = i; g.dataset.y = y;
mk('text', { x: DL - 14, y: y + 4, class: 'db-rank', 'text-anchor': 'end' }, g).textContent = `#${r.rank}`;
mk('line', { x1: dx(lo), x2: dx(hi), y1: y, y2: y, class: 'db-range' }, g);
mk('line', { x1: dx(r.Floor), x2: dx(r.Overall), y1: y, y2: y, class: 'db-gap' }, g);
WF.forEach(w => mk('circle', { cx: dx(r[w]), cy: y, r: 3.2, class: 'db-wf', style: `fill:${WC[w]}` }, g));
// floor = a transparent ring drawn around the weakest workflow's own dot (the dot shows through)
const fw = WF.find(w => r[w] === r.Floor);
mk('circle', { cx: dx(r.Floor), cy: y, r: 6, class: 'db-floor', 'data-tip': `${r.name} workflow floor: ${fw} ${f1(r.Floor)}` }, g);
mk('circle', { cx: dx(r.Overall), cy: y, r: 5.5, class: 'db-ov', 'data-dx': dx(r.Floor) - dx(r.Overall) }, g);
mk('text', { x: DW - DR + 18, y: y + 4, class: 'db-val' }, g).textContent = f1(r.Overall);
dbRows[r.key] = g;
});
// re-order rows (slide) by the chosen key; the rank and value columns follow that key
const keyOf = (r, s) => s === 'Overall' ? r.Overall : s === 'Floor' ? r.Floor : r[s];
function sortDb(s) {
dbSort = s;
[...rows].sort((p, q) => keyOf(q, s) - keyOf(p, s) || q.Overall - p.Overall).forEach((r, p) => {
const g = dbRows[r.key];
g.style.transform = `translateY(${(p - +g.dataset.i) * DH}px)`;
$('.db-rank', g).textContent = `#${1 + rows.filter(x => keyOf(x, s) > keyOf(r, s) + 1e-9).length}`;
$('.db-val', g).textContent = f1(keyOf(r, s));
});
$$('.db-sort', db).forEach(b => b.classList.toggle('on', b.dataset.s === s));
}
db.addEventListener('click', e => { const b = e.target.closest('.db-sort'); if (b) { sortDb(b.dataset.s); choose({ mode: 'sort', key: b.dataset.s }, true); } });
db.addEventListener('keydown', e => { const b = e.target.closest('.db-sort'); if (b && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); sortDb(b.dataset.s); choose({ mode: 'sort', key: b.dataset.s }, true); } });
db.style.display = 'none';
$('#rkChart').appendChild(db);
/* ---------- field view ("staff"): one line per architecture, one note per backbone, x = score ---------- */
// score = the chosen workflow (header pills), else Overall; follows the same selection state as the other views
const BB = { 'Claude Sonnet 5': 'Son5', 'GPT-5.4': 'GPT', 'DeepSeek-V3.2': 'DS3.2', 'DeepSeek-V4-Pro': 'DS4P', 'Qwen3.5-9B': 'Q9B', 'Qwen3.5-4B': 'Q4B' };
// same canvas height as the other two views (the card never changes size); the spare height is split above and below
const FW = W, FLANE = 92, FH = Hh, FT = 64 + Math.max(0, (FH - (64 + H.agents.length * FLANE + 70)) / 2), FL = 150, FR = 86, NR = 15.5, KR = 13.5;
const fx = v => FL + v / 100 * (FW - FL - FR);
const fv = mk('svg', { viewBox: `0 0 ${FW} ${FH}`, role: 'img', 'aria-label': 'Every agent system on one score axis, one line per architecture' });
// score pills: Overall + the four workflows (workflow pills reuse the column-header buttons)
const pills = ['Overall', ...WF], pw = p => p.length * 7.6 + 34, gapP = 10, totP = pills.reduce((s, p) => s + pw(p), 0) + gapP * (pills.length - 1);
let px0 = (FW - totP) / 2;
pills.forEach(p => {
const g = mk('g', p === 'Overall' ? { class: 'col-btn ov-btn', style: '--c:#4a1a8c', tabindex: 0, role: 'button', 'data-tip': 'Overall = mean of the four workflow scores' }
: { class: 'col-btn', 'data-w': p, style: `--c:${WC[p]}`, tabindex: 0, role: 'button', 'data-tip': `${p} place every system by its ${p} score ` }, fv);
mk('rect', { x: px0, y: 2, width: pw(p), height: 26, rx: 13, class: 'cb-bg' }, g);
mk('circle', { cx: px0 + 13, cy: 15, r: 3.5, class: 'cb-dot' }, g);
mk('text', { x: px0 + pw(p) / 2 + 6, y: 19.5, 'text-anchor': 'middle', class: 'col-h' }, g).textContent = p;
px0 += pw(p) + gapP;
});
const fGrid = mk('g', {}, fv), fLanes = mk('g', {}, fv), fTraces = mk('g', {}, fv), fNotes = mk('g', {}, fv);
const axY = FT + H.agents.length * FLANE + 6;
[0, 25, 50, 75, 100].forEach(v => { mk('line', { x1: fx(v), x2: fx(v), y1: FT - 8, y2: axY, class: 'f-grid' }, fGrid); mk('text', { x: fx(v), y: axY + 18, class: 'tick', 'text-anchor': 'middle' }, fGrid).textContent = v; });
mk('text', { x: FW - FR + 26, y: FT - 14, class: 'tick' }, fGrid).textContent = 'mean';
// key: note code -> backbone
// key: a small note with the code, then the backbone's full name
// key: a small dark note with the code, then the backbone's full name
const kW = H.models.map(m => 2 * KR + 8 + textW(m, 11.5)), kGap = 26, kTot = kW.reduce((s, w) => s + w, 0) + kGap * (H.models.length - 1);
let kx = (FW - kTot) / 2;
H.models.forEach((m, i) => {
const g = mk('g', { class: 'f-key', transform: `translate(${kx + KR},${axY + 42})` }, fGrid);
mk('circle', { r: KR, class: 'f-kdot' }, g);
mk('text', { y: 3.6, 'text-anchor': 'middle', class: 'f-kbb' }, g).textContent = BB[m];
mk('text', { x: KR + 8, y: 4, class: 'f-kname' }, g).textContent = m;
kx += kW[i] + kGap;
});
const fTrace = Object.fromEntries(H.models.map(m => [m, mk('path', { class: 'f-trace' }, fTraces)]));
const fLane = Object.fromEntries(H.agents.map(ag => {
const g = mk('g', { class: 'f-lane', 'data-a': ag }, fLanes);
mk('line', { x1: FL, x2: FW - FR, y1: 0, y2: 0, class: 'f-line' }, g);
mk('text', { x: FL - 22, y: 5, 'text-anchor': 'end', class: 'f-name', style: `fill:${AC[ag]}` }, g).textContent = ag;
mk('line', { y1: -NR - 8, y2: NR + 8, class: 'f-mean', style: `stroke:${AC[ag]}` }, g);
mk('text', { x: FW - FR + 26, y: 5, class: 'f-mv' }, g);
return [ag, g];
}));
const fNote = Object.fromEntries(rows.map(r => {
const g = mk('g', { class: 'f-note', 'data-key': r.key }, fNotes);
mk('circle', { r: NR, style: `fill:${AC[r.agent]}` }, g);
mk('text', { y: 3.4, 'text-anchor': 'middle', class: 'f-bb' }, g).textContent = BB[r.model];
return [r.key, g];
}));
let fPos = {};
function fieldLayout() {
const sc = st.mode === 'workflow' ? st.key : 'Overall', val = r => sc === 'Overall' ? r.Overall : r[sc];
$$('.col-btn', fv).forEach(b => b.classList.toggle('on', b.classList.contains('ov-btn') ? sc === 'Overall' : b.dataset.w === sc));
const mean = ag => { const v = rows.filter(r => r.agent === ag).map(val); return v.reduce((p, q) => p + q, 0) / v.length; };
const order = H.agents, ly = ag => FT + (order.indexOf(ag) + .5) * FLANE; // fixed: same row order as the picker
order.forEach(ag => {
const g = fLane[ag], m = mean(ag);
g.style.transform = `translateY(${ly(ag)}px)`;
const mk2 = $('.f-mean', g); mk2.setAttribute('x1', fx(m)); mk2.setAttribute('x2', fx(m));
$('.f-mv', g).textContent = f1(m);
g.dataset.tip = `${ag} mean ${sc}: ${f1(m)}click to highlight its ${H.models.length} systems `;
});
// within a lane, notes that would overlap are nudged up / down (small beeswarm)
fPos = {};
H.agents.forEach(ag => {
const placed = [];
rows.filter(r => r.agent === ag).sort((p, q) => val(p) - val(q)).forEach(r => {
const x = fx(val(r)); let dy = 0;
let best = -1; // first free slot; if none is free, the slot with the least overlap
for (const c of [0, -1, 1, -2, 2]) { const md = Math.min(1e9, ...placed.map(p => Math.hypot(p.x - x, p.dy - c * NR * 1.1)));
if (md >= NR * 1.9) { dy = c * NR * 1.1; break; } if (md > best) { best = md; dy = c * NR * 1.1; } }
placed.push({ x, dy }); fPos[r.key] = { x, y: ly(ag) + dy };
});
});
rows.forEach(r => {
const g = fNote[r.key], p = fPos[r.key];
g.style.transform = `translate(${p.x}px,${p.y}px)`;
g.dataset.tip = `${r.name} ${sc}: ${f1(val(r))} · #${1 + rows.filter(x => val(x) > val(r) + 1e-9).length} of ${n} `;
});
}
// classes follow the shared selection; the dashed trace links one backbone across the five lines
function fieldApply(selKey, litKeys) {
fieldLayout();
rows.forEach(r => { const g = fNote[r.key]; g.classList.toggle('sel', r.key === selKey); g.classList.toggle('lit', litKeys.has(r.key)); g.classList.toggle('pv', r.key === pv); });
fv.classList.toggle('all', st.mode === 'none');
// the dashed trace links one backbone across the five lines: the hovered note's, the chosen column's,
// else the highlighted system's (by default the #1 system's)
const one = pv ? rows.find(r => r.key === pv).model : st.mode === 'model' ? st.key : rows.find(r => r.key === selKey).model;
H.models.forEach(m => {
const pts = rows.filter(r => r.model === m).map(r => fPos[r.key]).sort((p, q) => p.y - q.y);
fTrace[m].setAttribute('d', pts.map((p, i) => (i ? 'L' : 'M') + p.x.toFixed(1) + ',' + p.y.toFixed(1)).join(''));
fTrace[m].classList.toggle('on', m === one);
});
}
fNotes.addEventListener('click', e => { const g = e.target.closest('.f-note'); if (g) choose({ mode: 'system', key: g.dataset.key }); });
fNotes.addEventListener('pointerover', e => { const g = e.target.closest('.f-note'); const k = g ? g.dataset.key : null; if (k && k !== pv) { pv = k; apply(); } });
fNotes.addEventListener('pointerleave', () => { if (pv) { pv = null; apply(); } });
fLanes.addEventListener('click', e => { const g = e.target.closest('.f-lane'); if (g) choose({ mode: 'agent', key: g.dataset.a }); });
fv.addEventListener('click', e => { if (e.target.closest('.ov-btn')) choose({ mode: 'none' }, true); });
fv.addEventListener('keydown', e => { if (e.target.closest('.ov-btn') && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); choose({ mode: 'none' }, true); } });
fv.style.display = 'none';
$('#rkChart').appendChild(fv);
function growField() {
rows.forEach((r, i) => { const g = fNote[r.key]; g.animate([{ opacity: 0, transform: `translate(${fx(0)}px,${fPos[r.key].y}px)` }, { opacity: 1, transform: g.style.transform }], { duration: 900, delay: i * 18, easing: 'cubic-bezier(.22,1,.36,1)', fill: 'backwards' }); });
}
let view = 'flow';
/* ---------- picker: architecture rows × backbone columns ---------- */
const pk = $('#picker');
pk.innerHTML = ' ' + H.models.map(m => `${short(m)} `).join('') +
H.agents.map(ag => `${ini(ag, 'ini sm')}${ag} ` +
H.models.map(m => { const r = rows.find(x => x.agent === ag && x.model === m);
return `${r.rank} `; }).join('')).join('') +
'Block = one agent system (number = Overall rank) · row = architecture · column = backbone
';
/* ---------- state ---------- */
let st = { mode: 'none' }, pv = null;
// one highlighted line (sel) plus secondary lines (lit) for every choice:
// none -> #1 Overall highlighted, every other line secondary
// workflow -> its #1 highlighted, its #2 and #3 secondary
// system -> that system highlighted, the same architecture on other backbones secondary
// agent / model -> the group's best Overall system highlighted, the rest of the row / column secondary
// rows are already in Overall order, so the first match is the group's best system
const selOf = s => s.mode === 'system' ? s.key : s.mode === 'workflow' ? topIn(s.key)[0].key : s.mode === 'sort' ? topBy(s.key)[0].key : s.mode === 'none' ? top.key
: rows.find(r => r[s.mode] === s.key).key;
const group = s => {
const k = selOf(s);
if (s.mode === 'agent' || s.mode === 'model') return rows.filter(r => r[s.mode] === s.key && r.key !== k);
if (s.mode === 'workflow') return topIn(s.key).slice(1, 3);
if (s.mode === 'sort') return topBy(s.key).slice(1, 3);
if (s.mode === 'system') { const ag = rows.find(x => x.key === k).agent; return rows.filter(r => r.agent === ag && r.key !== k); }
return [];
};
// labels are static text, the same for every highlighted line: architecture on the left, backbone + Overall rank on the right
function labels(shown) {
lbls.innerHTML = '';
const spread = arr => { arr.sort((p, q) => p.y - q.y); for (let i = 1; i < arr.length; i++) if (arr[i].y - arr[i - 1].y < 13) arr[i].y = arr[i - 1].y + 13; return arr; };
spread(shown.map(r => ({ y: cy(pos[r.key][WF[0]]), r }))).forEach(({ y, r }) =>
(mk('text', { x: cx(0) - 20, y: y + 4, class: 'end end-l', style: `fill:${AC[r.agent]}` }, lbls).textContent = r.agent));
spread(shown.map(r => ({ y: cy(pos[r.key][WF[WF.length - 1]]), r }))).forEach(({ y, r }) => {
const t = mk('text', { x: cx(WF.length - 1) + 20, y: y + 4, class: 'end' }, lbls);
t.textContent = short(r.model) + ' ';
mk('tspan', { class: 'rk-tag' }, t).textContent = `#${r.rank}`;
});
}
function apply() {
const lit = group(st), litKeys = new Set(lit.map(r => r.key)), selKey = selOf(st);
Object.entries(lines).forEach(([k, g]) => { g.classList.toggle('sel', k === selKey); g.classList.toggle('lit', litKeys.has(k)); g.classList.toggle('pv', k === pv); });
// draw order: faint lines, then the lit group, then the preview, then the selected line on top
[...lit.map(r => r.key), pv, selKey].filter(Boolean).forEach(k => layer.appendChild(lines[k]));
labels([...lit, ...(selKey ? [rows.find(x => x.key === selKey)] : [])]);
svg.classList.toggle('all', st.mode === 'none');
$$('.pk-cell', pk).forEach(c => { c.classList.toggle('sel', c.dataset.k === selKey); c.classList.toggle('lit', litKeys.has(c.dataset.k)); c.classList.toggle('pv', c.dataset.k === pv); });
$$('.pk-row', pk).forEach(b => b.classList.toggle('on', st.mode === 'agent' && b.dataset.a === st.key));
$$('.pk-col', pk).forEach(b => b.classList.toggle('on', st.mode === 'model' && b.dataset.m === st.key));
$$('#rkChart .col-btn').forEach(b => b.classList.toggle('on', st.mode === 'workflow' && b.dataset.w === st.key));
Object.entries(dbRows).forEach(([k, g]) => { g.classList.toggle('sel', k === selKey); g.classList.toggle('lit', litKeys.has(k)); g.classList.toggle('pv', k === pv); });
db.classList.toggle('all', st.mode === 'none');
fieldApply(selKey, litKeys);
}
// dots appear as the growing line reaches their column (the ease-out curve gets there early)
const DOT_AT = [0, 70, 160, 300];
const pop = (c, delay) => c.animate([{ opacity: 0, transform: 'scale(.3)' }, { opacity: 1, transform: 'scale(1)' }], { duration: 260, delay, easing: 'cubic-bezier(.34,1.56,.64,1)', fill: 'backwards' });
// the lit and selected lines grow in again after every click (same effect as on first view)
function grow(force) {
if (reduce) return;
if (view === 'floor') return growDb(force);
if (view === 'field') return force && growField();
const keys = force || st.mode === 'none' ? rows.map(r => r.key) : [...group(st).map(r => r.key), selOf(st)].filter(Boolean);
keys.forEach((k, i) => {
const p = $('path.line', lines[k]), l = p.getTotalLength();
p.getAnimations().forEach(x => x.cancel());
p.animate([{ strokeDasharray: l, strokeDashoffset: l }, { strokeDasharray: l, strokeDashoffset: 0 }], { duration: 900, delay: i * 45, easing: 'cubic-bezier(.22,1,.36,1)' });
$$('circle', lines[k]).forEach((c, j) => pop(c, i * 45 + DOT_AT[j]));
});
}
// clicking the current choice again clears it
// floor → Overall view: the Overall dot slides out from the floor, the gap line stretches, workflow dots pop in
function growDb(all) {
const keys = all || st.mode === 'none' ? rows.map(r => r.key) : [...group(st).map(r => r.key), selOf(st)].filter(Boolean);
keys.forEach((k, i) => {
const g = dbRows[k], d = i * (all ? 22 : 50), ov = $('.db-ov', g);
ov.animate([{ transform: `translateX(${ov.dataset.dx}px)` }, { transform: 'none' }], { duration: 800, delay: d, easing: 'cubic-bezier(.22,1,.36,1)', fill: 'backwards' });
$('.db-gap', g).animate([{ transform: 'scaleX(0)' }, { transform: 'none' }], { duration: 800, delay: d, easing: 'cubic-bezier(.22,1,.36,1)', fill: 'backwards' });
$('.db-range', g).animate([{ opacity: 0 }, { opacity: 1 }], { duration: 500, delay: d + 150, fill: 'backwards' });
$$('.db-wf', g).forEach((c, j) => pop(c, d + 120 + j * 60));
});
}
const choose = (s, keep) => { st = (!keep && s.mode === st.mode && s.key === st.key) ? { mode: 'none' } : s; pv = null; apply(); grow(); };
pk.addEventListener('click', e => {
const c = e.target.closest('.pk-cell'), rw = e.target.closest('.pk-row'), cl = e.target.closest('.pk-col');
if (c) choose({ mode: 'system', key: c.dataset.k }); else if (rw) choose({ mode: 'agent', key: rw.dataset.a }); else if (cl) choose({ mode: 'model', key: cl.dataset.m });
});
pk.addEventListener('pointerover', e => { const c = e.target.closest('.pk-cell'); const k = c ? c.dataset.k : null; if (k !== pv) { pv = k; apply(); } });
pk.addEventListener('pointerleave', () => { if (pv) { pv = null; apply(); } });
layer.addEventListener('pointerover', e => { const g = e.target.closest('.ln'); const k = g ? g.dataset.key : null; if (k && k !== pv) { pv = k; apply(); } });
layer.addEventListener('pointerleave', () => { if (pv) { pv = null; apply(); } });
layer.addEventListener('click', e => { const g = e.target.closest('.ln'); if (g) choose({ mode: 'system', key: g.dataset.key }); });
$('#rkChart').addEventListener('click', e => { const b = e.target.closest('.col-btn[data-w]'); if (b) choose({ mode: 'workflow', key: b.dataset.w }); });
dbLayer.addEventListener('click', e => { const g = e.target.closest('.db'); if (g) choose({ mode: 'system', key: g.dataset.key }); });
dbLayer.addEventListener('pointerover', e => { const g = e.target.closest('.db'); const k = g ? g.dataset.key : null; if (k && k !== pv) { pv = k; apply(); } });
dbLayer.addEventListener('pointerleave', () => { if (pv) { pv = null; apply(); } });
// view switch
const vs = $('#rkView'), vPill = $('.seg-pill', vs);
const placeV = () => { const b = $('button[aria-selected=true]', vs); vPill.style.width = b.offsetWidth + 'px'; vPill.style.transform = `translateX(${b.offsetLeft}px)`; };
vs.addEventListener('click', e => {
const b = e.target.closest('button'); if (!b || b.dataset.v === view) return;
view = b.dataset.v; $$('button', vs).forEach(x => x.setAttribute('aria-selected', x === b)); placeV();
svg.style.display = view === 'flow' ? '' : 'none'; db.style.display = view === 'floor' ? '' : 'none'; fv.style.display = view === 'field' ? '' : 'none';
grow(true);
});
requestAnimationFrame(placeV); document.fonts && document.fonts.ready.then(placeV); addEventListener("resize", placeV);
$('#rkChart').addEventListener('keydown', e => { const b = e.target.closest('.col-btn[data-w]'); if (b && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); choose({ mode: 'workflow', key: b.dataset.w }); } });
apply();
rkGo = (v, s) => { const b = $(`button[data-v="${v}"]`, vs); if (b && v !== view) b.click(); if (s) choose(s, true); };
/* ---------- draw-in when the chart scrolls into view ---------- */
if (reduce || !('IntersectionObserver' in window) || /[?&]all\b/.test(location.search)) return;
const paths = rows.map(r => $('path.line', lines[r.key]));
paths.forEach(p => { const l = p.getTotalLength(); p.style.strokeDasharray = l; p.style.strokeDashoffset = l; });
$$('circle', layer).forEach(c => (c.style.opacity = 0));
new IntersectionObserver((es, ob) => es.forEach(e => {
if (!e.isIntersecting) return; ob.disconnect();
paths.forEach((p, i) => {
const l = +p.style.strokeDasharray;
p.animate([{ strokeDashoffset: l }, { strokeDashoffset: 0 }], { duration: 1100, delay: 150 + i * 28, easing: 'cubic-bezier(.22,1,.36,1)', fill: 'forwards' })
.onfinish = () => { p.style.strokeDasharray = ''; p.style.strokeDashoffset = ''; };
});
rows.forEach((r, i) => $$('circle', lines[r.key]).forEach((c, j) => pop(c, 150 + i * 28 + DOT_AT[j]).onfinish = () => (c.style.opacity = '')));
}), { threshold: .2 }).observe($('#rkChart'));
})();
/* ================= 2c · REPLAY: one system's Trading and Hedging trajectories, drawn day by day ================= */
const replayTo = (() => {
const NS = 'http://www.w3.org/2000/svg', D = H.dates, ND = D.length;
const mk = (tag, attrs = {}, parent) => { const e = document.createElementNS(NS, tag); for (const k in attrs) e.setAttribute(k, attrs[k]); if (parent) parent.appendChild(e); return e; };
const ACOL = { B: '#1593b5', S: '#e0475b', H: 'rgba(185,181,207,.6)' }, PCOL = { L: '#3567d8', R: '#e0475b', F: 'rgba(185,181,207,.45)' };
const ANAME = { B: 'BUY', S: 'SELL', H: 'HOLD', L: 'LONG_SHORT', R: 'SHORT_LONG', C: 'CLOSE', '-': 'no committed record (scored as HOLD)' };
const date = d => new Date(d + 'T00:00:00').toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
const W = 560, PH = 150, GAP = 8, SH = 14, L = 40, R = 34, T = 18, Hh = T + PH + GAP + SH + 20;
const X = i => L + (i + .5) * (W - L - R) / ND, cw = (W - L - R) / ND;
let sys = top, tk = H.tradeAssets[0], run = 1, raf = null;
const RN = H.runs;
// one chart: price line(s) with a purple wash underneath, a day-by-day strip, a dot riding the
// first line and a pill that names each day's action / position; returns handles for the animation
function chart(host, series, cells, labels, info) {
host.innerHTML = '';
const svg = mk('svg', { viewBox: `0 0 ${W} ${Hh}` }, host), defs = mk('defs', {}, svg), id = host.id;
const fg = mk('linearGradient', { id: id + 'Fill', x1: 0, x2: 0, y1: 0, y2: 1 }, defs);
mk('stop', { offset: 0, 'stop-color': '#7800f0', 'stop-opacity': .14 }, fg); mk('stop', { offset: 1, 'stop-color': '#7800f0', 'stop-opacity': 0 }, fg);
const all = series.flat(), lo = Math.min(...all), hi = Math.max(...all), pad = (hi - lo) * .08 || 1;
const Y = v => T + (1 - (v - lo + pad) / (hi - lo + 2 * pad)) * PH;
[0, .5, 1].forEach(f => { const v = lo - pad + f * (hi - lo + 2 * pad);
mk('line', { x1: L, x2: W - R, y1: Y(v), y2: Y(v), class: 'rp-gridline' }, svg);
mk('text', { x: L - 6, y: Y(v) + 3, 'text-anchor': 'end', class: 'rp-ax' }, svg).textContent = v.toFixed(0); });
[0, Math.floor(ND / 2), ND - 1].forEach(i => mk('text', { x: X(i), y: Hh - 4, 'text-anchor': 'middle', class: 'rp-ax' }, svg).textContent = date(D[i]));
const cr = mk('rect', { x: 0, y: 0, width: L, height: Hh }, mk('clipPath', { id: id + 'Clip' }, defs));
const g = mk('g', { 'clip-path': `url(#${id}Clip)` }, svg);
const pts = s => series[s].map((v, i) => `${X(i).toFixed(1)},${Y(v).toFixed(1)}`).join(' ');
mk('polygon', { points: `${X(0)},${T + PH} ${pts(0)} ${X(ND - 1)},${T + PH}`, fill: `url(#${id}Fill)` }, g);
series.forEach((p, s) => mk('polyline', { points: pts(s), class: 'rp-line' + (s ? ' b' : '') }, g));
// end labels, nudged apart when the two legs finish close together
const ly = (labels || []).map((t, s) => ({ t, s, y: Y(series[s][ND - 1]) + 3 })).sort((p, q) => p.y - q.y);
for (let k = 1; k < ly.length; k++) if (ly[k].y - ly[k - 1].y < 11) { const m = (ly[k].y + ly[k - 1].y) / 2; ly[k - 1].y = m - 5.5; ly[k].y = m + 5.5; }
ly.forEach(({ t, s, y }) => mk('text', { x: W - R + 4, y, class: 'rp-lab', fill: s ? '#8a93a3' : '#0f1528' }, svg).textContent = t);
const y0 = T + PH + GAP;
const cellEls = cells.map((c, i) => mk('rect', { x: (L + i * cw + .4).toFixed(2), y: y0, width: (cw - .8).toFixed(2), height: SH, rx: 1.5, class: 'rp-cell pending', fill: c.fill, 'data-tip': c.tip }, svg));
const dias = cells.map((c, i) => c.diamond ? mk('rect', { x: L + i * cw + cw / 2 - 3, y: y0 + SH / 2 - 3, width: 6, height: 6, fill: '#8b3fd9', transform: `rotate(45 ${L + i * cw + cw / 2} ${y0 + SH / 2})`, class: 'rp-cell pending' }, svg) : null);
const vl = mk('line', { x1: L, x2: L, y1: T - 4, y2: y0 + SH, stroke: '#0f1528', 'stroke-opacity': .18, 'stroke-dasharray': '2 3' }, svg);
const dot = mk('circle', { r: 4.5, stroke: '#fff', 'stroke-width': 2, fill: '#0f1528', class: 'rp-dot' }, svg);
const status = mk('text', { x: L, y: 10, class: 'rp-status' }, svg);
const pill = mk('rect', { x: 0, y: 0, width: 60, height: 15, rx: 7.5, class: 'rp-pill' }, svg), ptx = mk('text', { x: 0, y: 10.8, 'text-anchor': 'middle', class: 'rp-pilltext' }, svg);
// hover: a vertical line follows the pointer; the tooltip lists that day's values
if (info) {
const hv = mk('line', { y1: T - 4, y2: y0 + SH, class: 'rp-hover', opacity: 0 }, svg);
const hd = series.map((p, s) => mk('circle', { r: 3, class: 'rp-hdot' + (s ? ' b' : ''), opacity: 0 }, svg));
const hit = mk('rect', { x: L, y: 0, width: W - L - R, height: y0 + SH, fill: 'transparent' }, svg);
hit.addEventListener('pointermove', e => {
const b = svg.getBoundingClientRect(), sx = (e.clientX - b.left) / b.width * W;
const i = Math.max(0, Math.min(ND - 1, Math.floor((sx - L) / cw))), x = X(i);
hv.setAttribute('x1', x); hv.setAttribute('x2', x); hv.setAttribute('opacity', 1);
hd.forEach((d, s) => { d.setAttribute('cx', x); d.setAttribute('cy', Y(series[s][i])); d.setAttribute('opacity', 1); });
tip.innerHTML = info(i); tip.classList.add('on');
});
hit.addEventListener('pointerleave', () => { hv.setAttribute('opacity', 0); hd.forEach(d => d.setAttribute('opacity', 0)); tip.classList.remove('on'); });
}
return { svg, cr, cellEls, dias, vl, dot, status, pill, ptx, cells, Y, line: series[0] };
}
const hatch = 'url(#rpHatch)';
const trow = (k, v) => `${k} ${v}
`;
function hatchDef(svg) { const p = mk('pattern', { id: 'rpHatch', width: 4, height: 4, patternUnits: 'userSpaceOnUse', patternTransform: 'rotate(45)' }, mk('defs', {}, svg)); mk('rect', { width: 4, height: 4, fill: '#fff' }, p); mk('line', { x1: 0, y1: 0, x2: 0, y2: 4, stroke: '#d8d6e0', 'stroke-width': 1.6 }, p); }
function render() {
const k = sys.key, O = H.offline, Sd = H.offlineStd;
$$('#rpArch button').forEach(b => b.classList.toggle('sel', b.dataset.a === sys.agent));
$$('#rpModel button').forEach(b => b.classList.toggle('sel', b.dataset.m === sys.model));
$$('#rpRun button').forEach(b => b.classList.toggle('on', +b.dataset.r === run));
$('#rpSub').textContent = `Every committed decision of one agent system over the ${ND} offline trading days. Pick an architecture, a backbone and a run, or open a system from the leaderboard.`;
$$('#rpTk button').forEach(b => b.classList.toggle('on', b.dataset.t === tk));
// Trading: price + BUY / SELL / HOLD strip
const acts = [...((RN.trading[`${k}|${tk}`] || [])[run - 1] || '-'.repeat(ND))], px = H.prices[tk];
const tr = chart($('#rpTrade'), [px], acts.map((c, i) => ({ fill: c === '-' ? hatch : ACOL[c], pill: [c === '-' ? 'NO RECORD' : ANAME[c], c === '-' ? hatch : c === 'H' ? '#d9d7e3' : ACOL[c], c === '-' || c === 'H'], dot: c === 'B' || c === 'S' ? ACOL[c] : '#0f1528', tip: `${tk} · ${date(D[i])} close $${px[i].toFixed(2)} ${ANAME[c]}` })), null, i => `${tk} · ${date(D[i])}
${trow('Close', '$' + px[i].toFixed(2))}${trow('Action', ANAME[acts[i]])}${trow('Run', run + ' of ' + M.runs)}`);
hatchDef(tr.svg);
const [cr, sr, mdd] = (RN.tradingMetrics[`${k}|${tk}`] || [])[run - 1] || [], bh = H.baseline[`Trading|${tk}|CR`];
$('#rpTradeFoot').innerHTML = `${pct(cr)} CR · run ${run}
vs. ${pct(bh)} Buy & Hold
${num(sr)} SR
${mdd == null ? '—' : mdd.toFixed(2) + '%'} MDD
`;
// Hedging: both legs indexed to 100 + position strip (paper Figure 3b encoding)
const pair = (RN.pairs[k] || [])[run - 1], pr = H.pairRuns[k], hs = (RN.hedging[k] || [])[run - 1];
let hc = null;
if (pair && hs) {
const [pl, prt] = pair.split('/'), ix = p => p.map(v => v / p[0] * 100);
let p = 'F';
const cells = [...hs].map((c, i) => { const cl = c === 'C'; if (c === 'L' || c === 'R') p = c; else if (cl) p = 'F';
const pn = cl ? 'CLOSE' : { L: 'LONG-SHORT', R: 'SHORT-LONG', F: 'FLAT' }[p], pc = cl ? '#8b3fd9' : p === 'F' ? '#d9d7e3' : PCOL[p];
return { fill: PCOL[p], diamond: cl, pill: [pn, pc, p === 'F' && !cl], dot: p === 'F' && !cl ? '#0f1528' : pc, tip: `${date(D[i])} ${{ L: 'Long-Short', R: 'Short-Long', F: 'Flat' }[p]}action: ${ANAME[c]} ` }; });
const li = ix(H.prices[pl]), ri = ix(H.prices[prt]), posN = { L: `long ${pl} / short ${prt}`, R: `short ${pl} / long ${prt}`, F: 'flat' };
let q = 'F'; const posArr = [...hs].map(c => { if (c === 'L' || c === 'R') q = c; else if (c === 'C') q = 'F'; return q; });
hc = chart($('#rpHedge'), [li, ri], cells, [pl, prt], i => `${pl} / ${prt} · ${date(D[i])}
${trow(pl + ' (idx)', li[i].toFixed(1))}${trow(prt + ' (idx)', ri[i].toFixed(1))}${trow('Action', ANAME[hs[i]])}${trow('Position', posN[posArr[i]])}`);
const same = (RN.pairs[k] || []).filter(x => x === pair).length;
$('#rpPair').innerHTML = `${pl} / ${prt}chosen in ${same}/${M.runs} runs · Long-Short = long ${pl} `;
} else { $('#rpHedge').innerHTML = 'No pair selected in this run.
'; $('#rpPair').textContent = ''; }
const [hcr, hsr, hmdd] = (RN.hedgingMetrics[k] || [])[run - 1] || [];
$('#rpHedgeFoot').innerHTML = `${pct(hcr)} CR · run ${run}
${num(hsr)} SR
${hmdd == null ? '—' : hmdd.toFixed(2) + '%'} MDD
`;
play([tr, hc].filter(Boolean));
}
// draw both charts left to right over ~4 s (eased); strips light up, the dot rides the line, the pill names the day
let timer = null;
function play(charts) {
cancelAnimationFrame(raf); clearTimeout(timer);
const feet = [$('#rpTradeFoot'), $('#rpHedgeFoot')], shown = charts.map(() => -1);
function frame(p) {
const f = p * (ND - 1), i = Math.min(ND - 1, Math.floor(f + 1e-6)), fr = f - i, x = X(f);
charts.forEach((c, ci) => {
const v = i < ND - 1 ? c.line[i] + (c.line[i + 1] - c.line[i]) * fr : c.line[ND - 1];
c.cr.setAttribute('width', x + 1);
c.dot.setAttribute('cx', x); c.dot.setAttribute('cy', c.Y(v));
c.vl.setAttribute('x1', x); c.vl.setAttribute('x2', x);
if (i === shown[ci]) return;
for (let k = shown[ci] + 1; k <= i; k++) { c.cellEls[k].classList.remove('pending'); if (c.dias[k]) c.dias[k].classList.remove('pending'); }
shown[ci] = i;
const [txt, fill, dark] = c.cells[i].pill;
c.status.textContent = `Day ${String(i + 1).padStart(2, '0')}/${ND} · ${date(D[i])}`;
const bw = c.status.getComputedTextLength(), pw = txt.length * 6.4 + 16;
c.pill.setAttribute('x', L + bw + 10); c.pill.setAttribute('width', pw); c.pill.setAttribute('fill', fill);
c.ptx.setAttribute('x', L + bw + 10 + pw / 2); c.ptx.textContent = txt; c.ptx.classList.toggle('dark', dark);
c.dot.setAttribute('fill', c.cells[i].dot);
});
}
const done = () => { frame(1); charts.forEach(c => c.vl.setAttribute('stroke-opacity', 0)); feet.forEach(f => f.classList.remove('pending')); };
if (reduce || document.hidden) { done(); return; }
feet.forEach(f => f.classList.add('pending'));
const t0 = performance.now(), dur = 4000;
const step = t => { const p = Math.max(0, Math.min(1, (t - t0) / dur)); frame(1 - Math.pow(1 - p, 1.6)); if (p < 1) raf = requestAnimationFrame(step); else done(); };
frame(0); raf = requestAnimationFrame(step);
timer = setTimeout(done, dur + 600); // fallback if rAF is throttled
}
// controls
const pickSys = (ag, m) => { sys = rows.find(r => r.agent === ag && r.model === m); render(); };
$('#rpArch').innerHTML = H.agents.map(ag => `${ini(ag, 'ini sm')}${ag} `).join('');
$('#rpModel').innerHTML = H.models.map(m => `${MS[m] || m} `).join('');
$('#rpArch').addEventListener('click', e => { const b = e.target.closest('button'); if (b) pickSys(b.dataset.a, sys.model); });
$('#rpModel').addEventListener('click', e => { const b = e.target.closest('button'); if (b) pickSys(sys.agent, b.dataset.m); });
$('#rpRun').innerHTML = Array.from({ length: M.runs }, (_, i) => `Run ${i + 1} `).join('');
$('#rpRun').addEventListener('click', e => { const b = e.target.closest('button'); if (b) { run = +b.dataset.r; render(); } });
$('#rpTk').innerHTML = H.tradeAssets.map(t => `${t} `).join('');
$('#rpTk').addEventListener('click', e => { const b = e.target.closest('button'); if (b) { tk = b.dataset.t; render(); } });
$('#rpAgain').addEventListener('click', render);
// first draw when the section scrolls into view
let drawn = false;
const go = () => { if (!drawn) { drawn = true; render(); } };
if (reduce || !('IntersectionObserver' in window) || /[?&]all\b/.test(location.search)) go();
else new IntersectionObserver((es, ob) => es.forEach(e => { if (e.isIntersecting) { ob.disconnect(); go(); } }), { threshold: .3 }).observe($('#replay'));
// entry point used by the leaderboard rows
return key => { sys = rows.find(r => r.key === key); drawn = true; document.getElementById('replay').scrollIntoView({ behavior: reduce ? 'auto' : 'smooth', block: 'start' }); setTimeout(render, reduce ? 0 : 450); };
})();
/* ================= 3 · PARTNERS ================= */
// collaborator institutions from the arXiv paper's affiliations (names only until logos are collected)
const INSTITUTIONS = ['Columbia University', 'Stevens Institute of Technology', 'New York University', 'Georgia Institute of Technology',
'Université de Montréal', 'University of Minnesota', 'University of Massachusetts Boston', 'AIST', 'University of Liverpool',
'Vrije Universiteit Amsterdam', 'National University of Singapore', 'Halmstad University', 'Cardiff University', 'Mila – Quebec AI Institute'];
$('#pNames').innerHTML = INSTITUTIONS.map(n => `${n} `).join(' · '); // spaces = line-break points
$('#copyBib').addEventListener('click', () => {
const b = $('#copyBib');
(navigator.clipboard ? navigator.clipboard.writeText($('#bib').textContent) : Promise.reject()).then(() => b.textContent = 'Copied', () => b.textContent = 'Select & copy');
setTimeout(() => b.textContent = 'Copy BibTeX', 1800);
});
/* ================= hero background: real price paths as thin drifting lines ================= */
// Each line is one stock's 61 offline closes. To loop seamlessly the path runs forward then mirrored
// (period = 2 × width), and the group drifts left by one period every 160 s.
function drawFlow() {
const svg = $('#flow'), W = Math.max(800, svg.clientWidth), Hh = Math.max(500, svg.clientHeight);
svg.setAttribute('viewBox', `0 0 ${W} ${Hh}`);
const syms = ['ADBE', 'META', 'AMZN', 'GOOGL', 'TSLA', 'AAPL', 'NVDA', 'MSFT'].filter(s => H.prices[s]);
// Catmull-Rom through the points, as cubic Bézier segments [start, c1, c2, end] (rounded like the path string)
const r1 = v => Math.round(v * 10) / 10;
const segsOf = pts => pts.slice(0, -1).map((p1, i) => { const p0 = pts[Math.max(0, i - 1)], p2 = pts[i + 1], p3 = pts[Math.min(pts.length - 1, i + 2)];
return [p1, [p1[0] + (p2[0] - p0[0]) / 6, p1[1] + (p2[1] - p0[1]) / 6], [p2[0] - (p3[0] - p1[0]) / 6, p2[1] - (p3[1] - p1[1]) / 6], p2].map(q => [r1(q[0]), r1(q[1])]); });
const f1p = q => `${q[0].toFixed(1)},${q[1].toFixed(1)}`;
const smooth = segs => `M${f1p(segs[0][0])}` + segs.map(([, c1, c2, e]) => ` C${f1p(c1)} ${f1p(c2)} ${f1p(e)}`).join('');
// ridge: the curve's y at every integer x over one 2W period. Same stepping as SVG getPointAtLength every
// 2 units of arc length (the heroes step along it the way they always did), measured on the segments in JS.
const ridgeOf = segs => { const r = new Float32Array(Math.ceil(2 * W) + 1);
let px = segs[0][0][0], py = segs[0][0][1], acc = 0, s = 2, last = 0, ly = py, done = false;
const put = (x, y) => { if (x > 2 * W) { done = true; return; } for (let i = last; i <= Math.floor(x); i++) r[i] = y; last = Math.floor(x) + 1; ly = y; };
put(px, py);
for (const [a, b, c, d] of segs) { if (done) break;
for (let j = 1; j <= 64 && !done; j++) { const t = j / 64, u = 1 - t;
const x = u * u * u * a[0] + 3 * u * u * t * b[0] + 3 * u * t * t * c[0] + t * t * t * d[0], y = u * u * u * a[1] + 3 * u * u * t * b[1] + 3 * u * t * t * c[1] + t * t * t * d[1];
const dl = Math.hypot(x - px, y - py);
while (!done && acc + dl >= s) { const f = (s - acc) / dl; put(px + (x - px) * f, py + (y - py) * f); s += 2; }
acc += dl; px = x; py = y; } }
for (let i = last; i < r.length; i++) r[i] = ly; // the period's last pixels keep the last height
return r; };
const ridges = [];
const labels = [];
let out = ``;
syms.forEach((s, li) => {
const p = H.prices[s], n = p.length, lo = Math.min(...p), hi = Math.max(...p), depth = li / (syms.length - 1);
const base = Hh * (.16 + depth * .74), amp = 60 + depth * 60;
const y = v => base - (v - lo) / (hi - lo) * amp;
const loopv = [...p, ...p.slice(0, -1).reverse()], m = loopv.length; // forward then mirrored, ends where it starts
const pts = [0, 1, 2].flatMap(k => loopv.map((v, i) => [k * 2 * W + i / (m - 1) * 2 * W, y(v)])); // three periods, enough for the drift
// ticker name, fixed at the right edge at the middle of this line's band (does not drift)
labels.push(`${s} `);
const bright = li % 3 === 1;
const segs = segsOf(pts); ridges.push(segs);
out += ` `;
for (let k = 0; k < 3; k++) {
if (bright) [.18, .55, .83].forEach(f => { const i = Math.round(f * (n - 1)), xi = k * 2 * W + i / (m - 1) * 2 * W;
out += ` `; });
}
});
out += ' ' + labels.join('');
svg.innerHTML = out;
flowState = { W, Hh, ridge: ridgeOf(ridges[ridges.length - 1]), ridgeTop: ridgeOf(ridges[0]), g: svg.querySelector('g'), period: 2 * W };
}
let flowState = null, flowT0 = null;
// drift the lines left (one 2W period every 160 s) and stand the pixel hero / lion on the front ridge
function flowTick(ts) {
if (!flowState) return requestAnimationFrame(flowTick);
if (flowT0 === null) flowT0 = ts;
const { W, Hh, ridge, ridgeTop, g, period } = flowState, off = reduce ? 0 : ((ts - flowT0) / 160000 * period) % period;
g.setAttribute('transform', `translate(${-off} 0)`);
const k = $('#flow').getBoundingClientRect().width / W || 1; // svg is stretched to the hero box
const ky = $('#flow').getBoundingClientRect().height / Hh;
const yAt = (r, px) => r[Math.max(0, Math.min(r.length - 1, Math.round(px / k + off) % period))] * ky;
const stand = (sel, r) => $$(sel).forEach(el => {
const sw = el.parentNode.clientWidth, l = el.style.left, w = el.offsetWidth || 18;
const x = l.endsWith('%') ? parseFloat(l) / 100 * sw : parseFloat(l) || 0; // the return walker moves in %
el.style.top = (yAt(r, x + w / 2) - (el.offsetHeight || 24) + 2) + 'px';
});
stand('#herc-top .walker, #herc-top .lion, #herc-top .flash', ridgeTop); // the fight, on the top line (ADBE)
stand('#herc-bot .walker', ridge); // the walk back, on the bottom line (MSFT)
requestAnimationFrame(flowTick);
}
requestAnimationFrame(flowTick);
drawFlow();
/* ================= header: scrolled state, progress bar, sliding nav pill ================= */
const header = $('#header'), prog = $('#progress'), nav = $('#navLinks');
function onScroll() {
const y = scrollY, max = document.documentElement.scrollHeight - innerHeight;
header.classList.toggle('scrolled', y > 10);
prog.style.transform = `scaleX(${max > 0 ? y / max : 0})`;
let cur = null;
for (const a of $$('a', nav)) { const sec = document.getElementById(a.dataset.sec); if (sec && sec.getBoundingClientRect().top < innerHeight * .4) cur = a.dataset.sec; }
$$('a', nav).forEach(a => a.classList.toggle('active', a.dataset.sec === cur));
}
addEventListener('scroll', onScroll, { passive: true }); onScroll();
let rz; addEventListener('resize', () => { clearTimeout(rz); rz = setTimeout(() => { drawFlow(); layout(); }, 200); });
/* ================= micro-interactions (thefin.ai): spotlight cards, button glow, magnetic CTAs, tilt ================= */
let lastBtn = null;
document.addEventListener('pointermove', e => {
const b = e.target.closest && e.target.closest('.btn');
if (lastBtn && lastBtn !== b) lastBtn.style.transform = '';
lastBtn = b;
if (b) {
const r = b.getBoundingClientRect(), x = e.clientX - r.left, y = e.clientY - r.top;
b.style.setProperty('--mx', x + 'px'); b.style.setProperty('--my', y + 'px');
if (!reduce && b.classList.contains('magnetic')) b.style.transform = `translate(${(x - r.width / 2) * .18}px,${(y - r.height / 2) * .3}px)`;
}
const c = e.target.closest && e.target.closest('.spot');
if (c) { const r = c.getBoundingClientRect(); c.style.setProperty('--x', e.clientX - r.left + 'px'); c.style.setProperty('--y', e.clientY - r.top + 'px'); }
}, { passive: true });
if (!reduce && matchMedia('(hover:hover)').matches) $$('.tilt').forEach(card => {
card.addEventListener('pointermove', e => {
const r = card.getBoundingClientRect(), px = (e.clientX - r.left) / r.width, py = (e.clientY - r.top) / r.height;
card.style.transition = 'transform .1s';
card.style.transform = `perspective(1000px) rotateY(${(px - .5) * 8}deg) rotateX(${(.5 - py) * 8}deg)`;
card.style.setProperty('--gx', px * 100 + '%'); card.style.setProperty('--gy', py * 100 + '%');
});
card.addEventListener('pointerleave', () => { card.style.transition = ''; card.style.transform = ''; });
});
/* ================= reveal on scroll ================= */
const io = 'IntersectionObserver' in window ? new IntersectionObserver(es => es.forEach(e => {
if (!e.isIntersecting) return;
e.target.classList.add('in'); io.unobserve(e.target);
if (e.target.id === 'board') { revealed = true; setTimeout(layout, 120); }
}), { threshold: .08 }) : null;
$$('[data-reveal]').forEach(el => io ? io.observe(el) : el.classList.add('in'));
if (!io || reduce || /[?&]all\b/.test(location.search)) { revealed = true; pod.classList.add('in'); $$('[data-reveal]').forEach(el => el.classList.add('in')); layout(); }
requestAnimationFrame(() => { document.body.classList.add('loaded'); setTimeout(() => { $$('[data-count]').forEach(countUp); pod.classList.add('in'); }, 500); });
})();
/* ---------- game card: mini K-line of the real window (TSLA), with one system's calls ---------- */
(() => {
const H = window.HERC, svg = document.getElementById('gameK'); if (!svg || !H.ohlc) return;
const tk = 'TSLA', O = H.ohlc, bars = O.bars[tk].slice(O.start), acts = (H.runs.trading[`Hermes|DeepSeek-V4-Pro|${tk}`] || [''])[0] || '';
const lo = Math.min(...bars.map(b => b[2])), hi = Math.max(...bars.map(b => b[1])), W = svg.clientWidth || 360, Hh = svg.clientHeight || 96, top = 10, ph = Hh - 20, cw = W / bars.length, w = cw * .6;
const Y = v => top + (1 - (v - lo) / (hi - lo)) * ph;
svg.setAttribute('viewBox', `0 0 ${W} ${Hh}`);
svg.innerHTML = bars.map((b, i) => {
const [o, h, l, c] = b, up = c >= o, col = up ? '#3fd68f' : '#ff5a72', x = (i + .5) * cw, d = `style="transition-delay:${(i * 18) | 0}ms"`;
const mk = acts[i] === 'B' ? ` ` : acts[i] === 'S' ? ` ` : '';
return ` ${mk} `;
}).join('');
})();
/* ---------- deep links (play.html → the Leaderboard section): the target travels in the #hash, ?to=, window.name and
localStorage, because some viewers drop or rewrite the URL; scroll once the JS-built sections exist, and again
while the layout settles, unless the reader has started scrolling */
(() => {
let want = location.hash.slice(1) || new URLSearchParams(location.search).get('to') || '';
const wn = /^herc-goto:(\w[\w-]*)$/.exec(window.name || ''); if (wn) { want = want || wn[1]; window.name = ''; }
try { const g = JSON.parse(localStorage.getItem('herc-goto') || 'null'); localStorage.removeItem('herc-goto'); if (!want && g && Date.now() - g.t < 15000) want = g.id; } catch (_) {}
if (!want || !document.getElementById(want)) return;
let user = false; const stop = () => (user = true);
['wheel', 'touchstart', 'keydown', 'mousedown'].forEach(t => addEventListener(t, stop, { once: true, passive: true }));
const go = () => {
if (user) return;
const el = document.getElementById(want), root = document.documentElement, sb = root.style.scrollBehavior;
root.style.scrollBehavior = 'auto'; // html{scroll-behavior:smooth} would animate and can be cut short
scrollTo(0, el.getBoundingClientRect().top + scrollY - (parseFloat(getComputedStyle(root).scrollPaddingTop) || 0));
root.style.scrollBehavior = sb;
};
if ('scrollRestoration' in history) history.scrollRestoration = 'manual';
go(); requestAnimationFrame(go);
[150, 400, 900, 1600, 2600].forEach(t => setTimeout(go, t));
addEventListener('load', go);
addEventListener('pageshow', e => e.persisted && go());
(document.fonts && document.fonts.ready || Promise.resolve()).then(go);
})();
/* ---------- coins popping out of the $ badge on the two "Trade" buttons: slow and faint at rest, fast and bright on hover ---------- */
(() => {
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
const btns = [...document.querySelectorAll('.btn--trade')]; if (!btns.length) return;
const layer = document.createElement('div'); layer.className = 'coin-layer'; document.body.appendChild(layer);
const coin = () => { const c = document.createElement('i'); c.className = 'coin'; c.textContent = '$'; return c; };
btns.forEach(b => {
const host = b.closest('.game-card') || b, dol = b.querySelector('.dol') || b;
let hot = false, vis = true, t = 0, raf = 0;
host.addEventListener('pointerenter', () => (hot = true)); host.addEventListener('pointerleave', () => (hot = false));
if ('IntersectionObserver' in window) new IntersectionObserver(es => es.forEach(e => (vis = e.isIntersecting))).observe(b);
const spawn = () => {
const r = dol.getBoundingClientRect(), x = r.left + r.width / 2 + scrollX, y = r.top + r.height / 2 + scrollY;
const big = hot, c = coin(), dx = (Math.random() - .5) * (big ? 70 : 34), up = (big ? 46 : 26) + Math.random() * (big ? 30 : 14);
const s = big ? .9 + Math.random() * .35 : .7 + Math.random() * .2, op = big ? .95 : .38, dur = big ? 900 + Math.random() * 300 : 1300 + Math.random() * 400;
c.style.left = x + 'px'; c.style.top = y + 'px'; layer.appendChild(c);
// flat coin: a small in-plane turn only
const spin = (Math.random() < .5 ? -1 : 1) * (10 + Math.random() * 20);
c.animate([
{ transform: `translate(-50%,-50%) translate(0,0) scale(${s * .4}) rotate(0deg)`, opacity: 0 },
{ transform: `translate(-50%,-50%) translate(${dx * .5}px,${-up}px) scale(${s}) rotate(${spin * .6}deg)`, opacity: op, offset: .45 },
{ transform: `translate(-50%,-50%) translate(${dx}px,${-up * .55}px) scale(${s * .85}) rotate(${spin}deg)`, opacity: 0 }
], { duration: dur, easing: 'cubic-bezier(.25,.7,.35,1)' }).onfinish = () => c.remove();
};
const tick = now => {
const gap = hot ? 190 : 900;
if (vis && !document.hidden && now - t > gap) { t = now; spawn(); }
raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
});
})();