HERCULEAN / app.js
Xueqing's picture
HERCULEAN leaderboard site (from xueqingpeng/herculean docs/ @ 76f5121)
c58bef8 verified
Raw History Blame Contribute Delete
88.5 kB
/* 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') => `<span class="${cls}" style="--c:${AC[a]};--fg:${INI_FG[a] || '#0f1528'}">${INI[a]}</span>`;
/* ---------- 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}<span class="sd">±${Math.abs(sd).toFixed(d)}</span>`;
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 `<svg viewBox="0 0 24 24" aria-hidden="true"><polygon class="mane" points="${pts}" fill="#fff"/>
<circle cx="8.1" cy="8.2" r="1.9" fill="${face}"/><circle cx="15.9" cy="8.2" r="1.9" fill="${face}"/>
<circle cx="12" cy="12.9" r="6.4" fill="${face}"/>
<circle cx="9.6" cy="11.6" r=".95" fill="#fff"/><circle cx="14.4" cy="11.6" r=".95" fill="#fff"/>
<ellipse cx="12" cy="15.4" rx="2.6" ry="1.9" fill="#fff" opacity=".22"/>
<path d="M10.6 13.6h2.8L12 15.2z" fill="#fff"/>
<path d="M12 15.2v.8M12 16c-.6.7-1.5.8-2.1.3M12 16c.6.7 1.5.8 2.1.3" stroke="#fff" stroke-width=".75" fill="none" stroke-linecap="round"/></svg>`;
})();
$('#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) => `<span class="${i === 0 ? 'on' : ''}" style="--c:${AC[a]}">${a}</span>`).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]) => `<div${t ? ` data-tip="<b>${l}</b><br>${t}"` : ''}><b data-count="${n}" data-dec="0"${suf ? ` data-suf="${suf}"` : ''}>0</b><span>${l}</span></div>`).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) => `<div class="col${i === 0 ? ' first' : ''}${i < 3 ? ` podium-${i + 1}` : ''}" data-key="${r.key}" style="--h:${r.Overall / top.Overall * 100}%;--d:${i * 90}ms;--c:${AC[r.agent]};--fg:${INI_FG[r.agent] || '#0f1528'}"
data-tip="<b>#${r.rank} ${r.name}</b><br>Overall ${f1(r.Overall)}<span> · floor ${f1(r.Floor)}</span><br>${WF.map(w => `${SHORT[w] || w} ${f1(r[w])}`).join('<span> · </span>')}">
<div class="bar"><span class="val">${f1(r.Overall)}</span><span class="rk-badge">${r.rank}</span></div><span class="nm">${r.agent}<small>${MS[r.model] || r.model}</small></span></div>`).join('');
// ticker: every agent system by Overall rank; the list is doubled so the loop is seamless
const tickItem = r => `<span class="tk${r.rank === 1 ? ' first' : ''}" data-key="${r.key}" data-tip="<b>#${r.rank} ${r.name}</b><br>${WF.map(w => `${SHORT[w] || w} ${f1(r[w])}`).join('<span> · </span>')}"><span class="p">#${String(r.rank).padStart(2, '0')}</span>${ini(r.agent, 'ini sm')}<span class="n">${r.agent} <em>·</em> ${MS[r.model] || r.model}</span><span class="v">${f1(r.Overall)}</span></span>`;
$('#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 => `<span><i style="background:${AC[a]}"></i>${a}</span>`).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) => `<div class="gr" data-kind="${kind}" data-k="${x.k}" data-tip="<b>${kind === 'agent' ? x.k : (MS[x.k] || x.k)}</b> · mean Overall ${f1(x.m)}<br><span>range ${f1(x.lo)}–${f1(x.hi)} over ${kind === 'agent' ? H.models.length + ' backbones' : H.agents.length + ' architectures'}</span><br>best: ${x.best.name} (${f1(x.best.Overall)})">
<span class="gr-p">${i + 1}</span>${kind === 'agent' ? ini(x.k, 'ini sm') : ''}<span class="gr-n">${kind === 'agent' ? x.k : (MS[x.k] || x.k)}</span>
<span class="gr-t"><i style="width:${x.m / mx * 100}%;${kind === 'agent' ? `background:${AC[x.k]}` : ''}"></i></span><b>${f1(x.m)}</b></div>`;
$('#heroLeaders').innerHTML = `<div class="gr-col"><p class="hl-cap">Architectures <span>mean Overall</span></p>${A.map((x, i) => row(x, i, 'agent')).join('')}</div>
<div class="gr-col"><p class="hl-cap">Backbones <span>mean Overall</span></p>${B.map((x, i) => row(x, i, 'model')).join('')}</div>`;
}
$('#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: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5.5" rx="7.5" ry="2.5"/><path d="M4.5 5.5v6c0 1.4 3.4 2.5 7.5 2.5s7.5-1.1 7.5-2.5v-6"/><path d="M4.5 11.5v6c0 1.4 3.4 2.5 7.5 2.5s7.5-1.1 7.5-2.5v-6"/></svg>',
proto: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h12l-3-3M20 17H8l3 3"/></svg>',
eval: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 20h18"/><path d="M5 16l4-5 4 3 6-8"/><circle cx="19" cy="6" r="1.5"/></svg>',
};
// 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 ? `<span>${lead[0].agent} <em>·</em></span><span>${lead[0].model}</span>`
: `<span data-tip="<b>${lead.length}-way tie</b><br>${lead.map(r => r.name).join('<br>')}"><span>${lead[0].agent} <em>·</em></span><span>${lead[0].model} <em>+${lead.length - 1} tied</em></span></span>`;
const item = (j, body) => `<li><span class="ico">${ICONS[PARTS[j][1]]}</span><div><small>${PARTS[j][0]}</small>${body}</div></li>`;
return `<article class="wf spot" data-reveal style="--i:${i};--c:${WC[w]}"><span class="wf-bar"></span>
<div class="wf-top"><span class="lbl"><span class="wdot" style="--c:${WC[w]}"></span>Workflow</span><span class="num">0${i + 1}</span></div>
<h3>${w}</h3>
<p class="purpose">${PURPOSE[w]}</p>
<ul class="recipe">${item(0, `<p>${env}</p>`)}${item(1, `<p>${proto}</p>`)}${item(2, `<div class="chips">${ev.map(m => `<span>${m}</span>`).join('')}</div>`)}</ul>
<div class="wf-best"><div><small>Best agent system</small><div class="who">${who}</div></div><b>${f1(best[w])}</b></div>
</article>`;
}).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. <b>Overall</b> is the mean of the four workflow scores; the <b>workflow floor</b> 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 => `<button type="button" data-a="${a}" aria-pressed="true">${ini(a, 'ini sm')}${a}</button>`).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 => `<button type="button" data-m="${m}" aria-pressed="true">${MS[m] || m}</button>`).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 => `<button type="button" class="sort-h" data-v="${w}" title="Rank by ${w}"><span class="wdot" style="--c:${WC[w]}"></span>${SHORT[w] || w}<i class="arr">↓</i></button>`).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) => `<div class="lb-row" data-i="${i}">
<div class="in" tabindex="0" role="button" aria-expanded="false">
<span class="lb-pos"></span>
<span class="lb-name">${ini(r.agent)}<b>${r.agent}</b><span>${r.model}</span></span>
<span class="lb-score"><span class="lb-track"><span class="lb-bar" style="--c:${AC[r.agent]}"></span></span><span class="lb-val"></span></span>
<span class="cells">${WF.map(w => { const s = shade(r[w]);
return `<span class="cell${isBest(r, w) ? ' best' : ''}" data-w="${w}" style="background:${s.bg};color:${s.fg}" data-tip="<b>${r.name}</b><br>${w}: ${f1(r[w])}<span> / 100${isBest(r, w) ? ' · best in workflow' : ''}</span>">${f1(r[w])}</span>`; }).join('')}</span>
<span class="chev">›</span>
</div></div>`).join('') + '<div class="lb-detail" id="lbDetail"></div>';
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 <span class="chev2">↓</span>` : `Show all ${vis.length} agent systems <em style="font-style:normal;color:var(--muted)">(+${rest})</em> <span class="chev2">↓</span>`;
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 ? '' : `<span class="sd">±${Math.abs(v).toFixed(d)}</span>`;
// 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, '&quot;');
const tag = w => (w === best ? '<span class="d1-tag best" data-tip="this system’s strongest workflow">best</span>' : w === fw ? '<span class="d1-tag floor" data-tip="this system’s weakest workflow (workflow floor)">floor</span>' : '')
+ (liveWf.includes(w) ? `<span class="d1-tag live" data-tip="<b>Run live</b> ${M.liveWindow[0]} → ${M.liveWindow[1]}<br><span>${liveTip}</span>">live</span>` : '');
const rpl = w => (w === 'Trading' || w === 'Hedging') ? `<button type="button" class="d1-replay" data-replay="${k}"><svg class="play-ico" viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="7.5" fill="currentColor"/><path d="M6.4 4.9v6.2l4.9-3.1z" fill="#fff"/></svg>Replay</button>` : '';
const card = (w, sub, body) => `<div class="d1-card" style="--c:${WC[w]}"><h4><span class="wdot" style="--c:${WC[w]}"></span>${w}${tag(w)}<span class="ws" data-tip="${w} workflow score, rank-normalised 0–100"><b>${f1(r[w])}</b><span class="rk" data-tip="rank in ${w} among ${N} agent systems">#${wfRank(r, w)}</span></span></h4><p class="sub">${sub}${rpl(w)}</p>${body}</div>`;
// 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 `<div class="d1-cr" data-tip="<b>${a}</b><br>Agent CR ${pct(v)} <span>±${(sd ?? 0).toFixed(2)}</span><br>SR ${num(sr)} <span>±${(S.Trading[a].SR[k] ?? 0).toFixed(2)}</span><br>MDD ${mdd == null ? '— <span>never held a position</span>' : mdd.toFixed(2) + '% <span>±' + (S.Trading[a].MDD[k] ?? 0).toFixed(2) + '</span>'}<br><span>Buy &amp; Hold CR ${pct(bh)}</span>"><span class="d1-t">${a}</span>
<span class="track"><i class="zero" style="left:${xPos(0)}%"></i><i class="bh" style="left:${xPos(bh)}%"></i>${v == null ? '' : `<i class="ag" style="left:${xPos(v)}%"></i>`}</span>
<span class="val">${pct(v)}${sdS(sd)}</span></div>`; }).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 + `<p class="d1-beat">Beats Buy &amp; Hold on <b>${beats} of ${A.length}</b> tickers</p><div class="d1-legend"><span><i class="l-ag"></i>agent CR</span><span><i class="l-bh"></i>Buy &amp; Hold CR</span><span><i class="l-z"></i>0%</span></div>`);
// 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 ? `<div class="d1-pair" data-tip="LONG_SHORT = long ${pl}, short ${prt}">${pl}<span class="s">/</span>${prt}${pr ? `<span class="runs">${pr.runs}/${pr.of} runs</span>` : ''}</div>` : '<div class="d1-pair na">no pair</div>') +
`<div class="d1-kv"><span>CR</span><span>${pct(hp.CR[k])}${sdS(hs.CR[k])}</span></div><div class="d1-kv"><span>SR</span><span>${num(hp.SR[k])}${sdS(hs.SR[k], 2)}</span></div>
<div class="d1-kv"><span>MDD</span><span>${hp.MDD[k] == null ? '<span data-tip="never held a position">—</span>' : hp.MDD[k].toFixed(2) + '%' + sdS(hs.MDD[k])}</span></div>` +
(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 `<div class="d1-cap">Position each day · run ${pr ? pr.shownRun : 1} of ${M.runs}</div><div class="d1-strip">${days.map(d => `<i class="${d.s}${d.cl ? ' cl' : ''}" data-tip="<b>${H.dates[d.i]}</b><br>${pn[d.s]}<br><span>action: ${d.c === '-' ? 'no committed record (kept as HOLD)' : ACTION[d.c]}</span>"></i>`).join('')}</div>`;
})() : '') +
`<div class="d1-legend"><span><i class="sw L"></i>Long-Short</span><span><i class="sw R"></i>Short-Long</span><span><i class="sw F"></i>Flat</span><span><i class="dia"></i>Close</span></div>`);
// 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`,
`<div class="d1-big">${num(mean(qs))}<small>mean / 10</small></div>` +
`<div class="d1-q hd"><span></span><span></span><span>score</span><span data-tip="CR of the weekly ratings replayed as a strategy">CR</span><span data-tip="Buy &amp; Hold CR">B&amp;H</span></div>` +
A.map((a, i) => { const cr = O['Market Insights'][a].CR[k], bh = H.baseline[`Market Insights|${a}|CR`];
return `<div class="d1-q" data-tip="<b>${a}</b><br>report quality ${num(qs[i])} ±${(qsd[i] ?? 0).toFixed(2)} / 10<br>CR of ratings ${pct(cr)} ±${(S['Market Insights'][a].CR[k] ?? 0).toFixed(2)}<br><span>Buy &amp; Hold CR ${pct(bh)}</span>"><span class="d1-t">${a}</span><span class="q-bar"><i style="width:${(qs[i] || 0) * 10}%"></i></span><span class="val">${f1(qs[i])}</span><span class="val ${cr >= bh ? 'up' : 'dn'}">${pc0(cr)}</span><span class="val bh">${pc0(bh)}</span></div>`; }).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%`,
`<div class="d1-big">${f1(au[0])}<small>% ACC${asd[0] == null ? '' : ' ±' + asd[0].toFixed(1)}</small></div>` +
`<div class="d1-stack">${MS4.map((m, i) => au[i] ? `<i class="s-${m.toLowerCase()}" style="width:${au[i]}%" data-tip="${NAMES[m]} (${m}): ${f1(au[i])}%"></i>` : '').join('')}</div>` +
MS4.map((m, i) => `<div class="d1-kv"><span><i class="s-${m.toLowerCase()} sq"></i>${NAMES[m]} <em class="ab">${m}</em></span><span>${f1(au[i])}%${sdS(asd[i])}</span></div>`).join(''));
return `<div class="d1"><div class="d1-cards">${trading}${hedging}${insights}${auditing}</div><p class="d1-foot">Raw metrics: mean of ${M.runs} runs <span class="sd">±std</span></p></div>`;
}
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': `<b>${w} · top 3</b><br>${[...rows].sort((x, y) => y[w] - x[w] || y.Overall - x.Overall).slice(0, 3).map((r, j) => `${j + 1}. ${r.name}`).join('<br>')}<br><span>click to highlight</span>` }, 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 = `<b>${r.name}</b><br>${WF.map(w => `${SHORT[w] || w} #${lab[r.key][w]}`).join('<span> · </span>')}<br><span>#${r.rank} Overall · click to follow</span>`;
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': `<b>${r.name}</b><br>${w}: ${ordinal(rnum[r.key][w])}${lab[r.key][w][0] === '=' ? ' (tied)' : ''} of ${n}<span> · score ${f1(r[w])}</span>` }, 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' ? '<br><span>ring = the weakest workflow’s dot</span>' : k === 'Overall' ? '<br><span>black dot = Overall</span>' : ''}` }, 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': `<b>#${r.rank} ${r.name}</b><br>Overall ${f1(r.Overall)} · floor ${f1(r.Floor)}<br>${WF.map(w => `${SHORT[w] || w} ${f1(r[w])}`).join('<span> · </span>')}` }, 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': `<b>${r.name}</b><br>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': `<b>${p}</b><br><span>place every system by its ${p} score</span>` }, 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 = `<b>${ag}</b><br>mean ${sc}: ${f1(m)}<br><span>click to highlight its ${H.models.length} systems</span>`;
});
// 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 = `<b>${r.name}</b><br>${sc}: ${f1(val(r))}<span> · #${1 + rows.filter(x => val(x) > val(r) + 1e-9).length} of ${n}</span>`;
});
}
// 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 = '<span></span>' + H.models.map(m => `<button class="pk-col" data-m="${m}" title="All ${H.agents.length} architectures on ${m}">${short(m)}</button>`).join('') +
H.agents.map(ag => `<button class="pk-row" data-a="${ag}" title="All ${H.models.length} ${ag} systems">${ini(ag, 'ini sm')}${ag}</button>` +
H.models.map(m => { const r = rows.find(x => x.agent === ag && x.model === m);
return `<button class="pk-cell" data-k="${r.key}" style="--c:${AC[ag]}" data-tip="<b>${r.name}</b><br>#${r.rank} Overall · ${f1(r.Overall)}">${r.rank}</button>`; }).join('')).join('') +
'<p class="pk-note">Block = one agent system (number = Overall rank) · row = architecture · column = backbone</p>';
/* ---------- 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) => `<div class="tr"><span>${k}</span><b>${v}</b></div>`;
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: `<b>${tk} · ${date(D[i])}</b><br>close $${px[i].toFixed(2)}<br>${ANAME[c]}` })), null, i => `<div class="tt">${tk} · ${date(D[i])}</div>${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 = `<div><b class="${cr >= bh ? 'up' : 'dn'}">${pct(cr)}</b><span>CR · run ${run}</span></div><em class="vs">vs.</em><div><b>${pct(bh)}</b><span>Buy &amp; Hold</span></div><div class="dim"><b>${num(sr)}</b><span>SR</span></div><div class="dim"><b>${mdd == null ? '—' : mdd.toFixed(2) + '%'}</b><span>MDD</span></div>`;
// 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: `<b>${date(D[i])}</b><br>${{ L: 'Long-Short', R: 'Short-Long', F: 'Flat' }[p]}<br><span>action: ${ANAME[c]}</span>` }; });
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 => `<div class="tt">${pl} / ${prt} · ${date(D[i])}</div>${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}<small>chosen in ${same}/${M.runs} runs · Long-Short = long ${pl}</small>`;
} else { $('#rpHedge').innerHTML = '<p class="sub">No pair selected in this run.</p>'; $('#rpPair').textContent = ''; }
const [hcr, hsr, hmdd] = (RN.hedgingMetrics[k] || [])[run - 1] || [];
$('#rpHedgeFoot').innerHTML = `<div><b class="${hcr >= 0 ? 'up' : 'dn'}">${pct(hcr)}</b><span>CR · run ${run}</span></div><div class="dim"><b>${num(hsr)}</b><span>SR</span></div><div class="dim"><b>${hmdd == null ? '—' : hmdd.toFixed(2) + '%'}</b><span>MDD</span></div>`;
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 => `<button type="button" data-a="${ag}">${ini(ag, 'ini sm')}${ag}</button>`).join('');
$('#rpModel').innerHTML = H.models.map(m => `<button type="button" data-m="${m}">${MS[m] || m}</button>`).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) => `<button type="button" data-r="${i + 1}">Run ${i + 1}</button>`).join('');
$('#rpRun').addEventListener('click', e => { const b = e.target.closest('button'); if (b) { run = +b.dataset.r; render(); } });
$('#rpTk').innerHTML = H.tradeAssets.map(t => `<button type="button" data-t="${t}">${t}</button>`).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 => `<span>${n}</span>`).join(' <i>·</i> '); // 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 = `<defs><linearGradient id="lg" gradientUnits="userSpaceOnUse" x1="0" x2="${W}" y1="0" y2="0" spreadMethod="reflect"><stop offset="0" stop-color="#b40028"/><stop offset="1" stop-color="#7800f0"/></linearGradient></defs><g>`;
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(`<text x="${W - 60}" y="${(base - amp / 2 + 3).toFixed(1)}" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="9" fill="#7a1fb4" fill-opacity=".14">${s}</text>`);
const bright = li % 3 === 1;
const segs = segsOf(pts); ridges.push(segs);
out += `<path d="${smooth(segs)}" fill="none" stroke="url(#lg)" stroke-width="${bright ? 1.3 : .9}" opacity="${bright ? .34 : .12 + depth * .1}"/>`;
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 += `<circle cx="${xi}" cy="${y(p[i])}" r="2.2" fill="#7800f0" opacity=".5"/><circle class="node" cx="${xi}" cy="${y(p[i])}" r="2.4" fill="none" stroke="#7800f0" stroke-width=".8" style="animation-delay:${(f * 3).toFixed(2)}s"/>`; });
}
});
out += '</g>' + 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' ? `<path d="M${x},${Y(l) + 3} l3,5 h-6z" fill="#3fd68f"/>` : acts[i] === 'S' ? `<path d="M${x},${Y(h) - 3} l3,-5 h-6z" fill="#ff5a72"/>` : '';
return `<g class="gk" ${d}><line x1="${x}" x2="${x}" y1="${Y(h)}" y2="${Y(l)}" stroke="${col}" stroke-width=".9"/><rect x="${x - w / 2}" y="${Y(Math.max(o, c))}" width="${w}" height="${Math.max(.8, Math.abs(Y(o) - Y(c)))}" fill="${up ? 'none' : col}" stroke="${col}" stroke-width=".8"/>${mk}</g>`;
}).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);
});
})();