`;
}).join('');
const tl = $('log'), cur = tl.children[Math.min(S.d, N - 1)];
if (cur) tl.scrollTo({ left: Math.max(0, cur.offsetLeft - tl.clientWidth + cur.offsetWidth * 2.5), behavior: 'auto' });
}
function head(i) {
const b = OH.bars[sym], cur = b[i], prev = b[i - 1], chg = cur[3] / prev[3] - 1, d = Math.min(S.d, N), done = S.d >= N;
const dt = new Date((done ? H.dates[N - 1] : H.dates[S.d]) + 'T12:00:00Z').toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' });
$('head').innerHTML = `${sym}${CO[sym]}${cur[3].toFixed(2)}${pct(chg)}` +
`${OH.dates[i]} · O ${cur[0].toFixed(2)} H ${cur[1].toFixed(2)} L ${cur[2].toFixed(2)} C ${cur[3].toFixed(2)} · Vol ${cur[4].toFixed(1)}M` +
''; void dt; void d; void done;
}
// the standing plot takes whatever height the card has left; redraw when that changes (fonts, resize)
let lastSt = null;
function plotStand() {
const sp = $('stPlot'); if (!sp || !lastSt) return;
sp.innerHTML = ''; sp.innerHTML = strip(lastSt, sp.clientWidth || 328, Math.max(48, Math.min(170, sp.clientHeight)));
}
if (window.ResizeObserver) new ResizeObserver(() => plotStand()).observe($('stand'));
if (document.fonts) { document.fonts.ready.then(() => { plotStand(); S && draw(); }); document.fonts.addEventListener('loadingdone', () => { plotStand(); S && draw(); }); }
let G = null; // geometry for hover
function draw(fresh) {
const svg = $('kline'), W = svg.clientWidth, Hh = svg.clientHeight; if (!W || !Hh) return;
svg.setAttribute('viewBox', `0 0 ${W} ${Hh}`);
const bars = OH.bars[sym], total = HIST + N, L = 6, R = 58, cw = (W - L - R) / total, X = i => L + (i + .5) * cw;
const nShown = HIST + S.d, shown = bars.slice(0, nShown);
const pTop = 20, pH = Hh * .5, aTop = pTop + pH + 10, aH = 13, vTop = aTop + aH + 8, vH = Hh * .08, eTop = vTop + vH + 22, eH = Hh - eTop - 46, tY = Hh - 16;
// price scale from what has been revealed only (no peeking at the future)
let lo = Math.min(...shown.map(b => b[2])), hi = Math.max(...shown.map(b => b[1])); const pad = (hi - lo) * .12;
lo -= pad; hi += pad;
const Y = v => pTop + (1 - (v - lo) / (hi - lo)) * pH, vmax = Math.max(...shown.map(b => b[4]));
let g = '';
for (let t = 0; t <= 4; t++) { const v = lo + t / 4 * (hi - lo); g += `${v.toFixed(0)}`; }
const x0 = X(HIST) - cw / 2, xn = X(nShown) - cw / 2;
g += ``;
if (S.d < N) g += ``;
g += `Jan 2 · window opens`;
// legends inside the panels
g += `MA5` +
`MA20` +
`agent BUYagent SELL`;
// daily decision strip (as in the Trading replay): one cell per window day
g += `ACTION`;
const AF = { B: '#0f9d58', S: '#d6304a', H: '#d9d7e3' };
for (let d = 0; d < N; d++) {
const l = S.log[d], cx0 = X(HIST + d) - cw / 2 + .6, cwi = Math.max(1, cw - 1.2);
if (l) g += ``;
else g += ``;
}
// moving averages
[[5, '#eda100'], [20, '#7800f0']].forEach(([n, c]) => {
let p = ''; for (let i = n - 1; i < nShown; i++) { const v = bars.slice(i - n + 1, i + 1).reduce((s, b) => s + b[3], 0) / n; p += (p ? 'L' : 'M') + X(i).toFixed(1) + ',' + Y(v).toFixed(1); }
g += ``;
});
// candles + volume
const w = Math.max(1.5, cw * .62);
shown.forEach((b, i) => {
const [o, h, l, c, v] = b, up = c >= o, col = up ? '#0f9d58' : '#d6304a', nw = fresh && i === nShown - 1 ? ' class="cnew"' : '';
g += ``;
g += ``;
});
// the agents' calls
S.log.forEach((l, k) => {
const i = HIST + l.d, b = bars[i], nw = fresh && k === S.log.length - 1 ? ' class="mk"' : '';
if (l.a === 'B') g += ``;
if (l.a === 'S') g += ``;
});
const lastC = shown[nShown - 1][3], lastUp = lastC >= shown[nShown - 2][3];
g += `${lastC.toFixed(2)}`;
// equity panel
const all = [...S.eq, ...S.bh], emin = Math.min(...all), emax = Math.max(...all), ep = Math.max((emax - emin) * .15, .01);
const EY = v => eTop + (1 - (v - emin + ep) / (emax - emin + 2 * ep)) * eH;
const ex = k => k === 0 ? x0 : X(HIST + k - 1) + cw / 2; // equity after day k sits at the right edge of that day
g += `EQUITY$100k`;
const line = arr => arr.map((v, k) => (k ? 'L' : 'M') + ex(k).toFixed(1) + ',' + EY(v).toFixed(1)).join('');
if (S.d) {
g += ``;
g += ``;
g += ``;
} else g += `your equity starts here`;
// time bar under the chart: the 61-day window, filled up to today; the Day tag rides on it
const xEnd = X(HIST + N - 1) + cw / 2, cur = Math.min(S.d, N - 1), tx = S.d >= N ? xEnd : X(HIST + S.d);
if (S.d < N) g += ``;
g += `` +
``;
[20, 40].forEach(k => g += ``);
g += `Jan 2Mar 31`;
// date tag riding on the bar: month and day on top, weekday below
const dd = new Date(H.dates[cur] + 'T12:00:00Z'), fmt = o => dd.toLocaleDateString('en-US', { ...o, timeZone: 'UTC' });
const l1 = S.d >= N ? 'Closed' : fmt({ month: 'short', day: 'numeric' }), l2 = S.d >= N ? 'Mar 31' : fmt({ weekday: 'short' });
const tw = Math.max(l1.length * 6.4, l2.length * 5.4) + 16, tcx = Math.min(Math.max(tx, x0 + tw / 2), xEnd - tw / 2);
g += `` +
`${l1}${l2}`;
g += `your equity` +
`Buy & Hold`;
// date axis
g += `${new Date(OH.dates[0] + 'T12:00:00Z').toLocaleDateString('en-US', { month: 'short', day: 'numeric', timeZone: 'UTC' })}`;
g += ``;
svg.innerHTML = `` + g;
G = { X, cw, L, R, W, nShown, pTop, bot: eTop + eH, Y, EY, ex };
head(hover != null && hover < nShown ? hover : nShown - 1);
}
// hover crosshair + readout (chart and log tiles)
function showDay(i, cx, cy) {
if (!G || i < 0 || i >= G.nShown) return leave();
hover = i; head(i);
const day = i - HIST, l = day >= 0 ? S.log[day] : null;
$('hv').innerHTML = `` +
(l ? `` : '');
document.querySelectorAll('.tile.on').forEach(t => t.classList.remove('on'));
if (day >= 0 && $('log').children[day]) $('log').children[day].classList.add('on');
const tip = $('tip');
if (l && cx != null) { tip.innerHTML = `${sysName(l.arch, l.model)} · run ${l.run} → ${ACT[l.a]} · ${pct(l.pnl)}`; tip.style.left = cx + 14 + 'px'; tip.style.top = cy - 34 + 'px'; tip.classList.add('on'); }
else tip.classList.remove('on');
}
$('kline').addEventListener('mousemove', e => {
if (!G) return;
const r = e.currentTarget.getBoundingClientRect();
showDay(Math.round((e.clientX - r.left - G.L) / G.cw - .5), e.clientX, e.clientY);
});
$('log').addEventListener('mouseover', e => { const t = e.target.closest('.tile'); if (t && S.log[+t.dataset.d]) showDay(HIST + +t.dataset.d); });
$('log').addEventListener('mouseleave', () => leave());
const leave = () => { hover = null; if ($('hv')) $('hv').innerHTML = ''; document.querySelectorAll('.tile.on').forEach(t => t.classList.remove('on')); $('tip').classList.remove('on'); if (G) head(G.nShown - 1); };
$('kline').addEventListener('mouseleave', leave);
// ---------- picks & buttons ----------
$('arch').addEventListener('click', e => { const b = e.target.closest('button'); if (b) { arch = b.dataset.a; renderSide(); } });
$('model').addEventListener('click', e => { const b = e.target.closest('button'); if (b) { model = b.dataset.m; renderSide(); } });
$('goBtn').addEventListener('click', () => { stopAuto(); step(); });
$('autoBtn').addEventListener('click', () => {
if (autoT) return stopAuto();
$('autoBtn').classList.add('on'); $('autoBtn').textContent = 'Stop ■'; step();
autoT = setInterval(step, 320);
});
$('newBtn').addEventListener('click', startCard);
document.addEventListener('keydown', e => {
if ($('ov').classList.contains('on')) return;
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); stopAuto(); step(); }
});
addEventListener('resize', () => S && draw());
// ---------- overlays ----------
const spark = tk => { // pre-window history only
const c = OH.bars[tk].slice(0, HIST).map(b => b[3]), lo = Math.min(...c), hi = Math.max(...c), up = c[c.length - 1] >= c[0];
const p = c.map((v, i) => `${(i / (c.length - 1) * 100).toFixed(1)},${(30 - (v - lo) / (hi - lo) * 26 - 2).toFixed(1)}`).join(' ');
return ``;
};
function open(html) { $('ovCard').innerHTML = html; $('ov').classList.add('on'); }
function close() { $('ov').classList.remove('on'); }
function startCard() {
stopAuto();
open(`
HERCULEAN · Trading workflow
Welcome, ${name}
Pick a stock. For each of the 61 trading days (Jan 2 – Mar 31, 2026) you choose which agent system trades; one of its three recorded runs is drawn, and its real decision is executed. Can your picks beat Buy & Hold?