const profits: Promise = (async function () { const response = await fetch('roi.json'); return await response.json(); })(); const lowVolume = document.querySelector('#low-volume') as HTMLInputElement; async function render() { const formatDecimal = new Intl.NumberFormat(undefined, {maximumFractionDigits: 2, maximumSignificantDigits: 6, roundingPriority: 'lessPrecision'}).format; const formatWhole = new Intl.NumberFormat(undefined, {maximumFractionDigits: 0}).format; const tbody = document.querySelector('tbody')!; tbody.innerHTML = ''; for (const p of await profits) { const volumeRatio = p.output_per_day / p.average_traded_7d; if (!lowVolume.checked && volumeRatio > 0.05) { continue; } const tr = document.createElement('tr'); const profit_per_area = p.profit_per_day / p.area; const break_even = p.profit_per_day > 0 ? p.capex / p.profit_per_day : Infinity; tr.innerHTML = ` ${p.output} ${p.expertise} ${formatDecimal(profit_per_area)} ${formatDecimal(break_even)}d ${formatWhole(p.capex)} ${formatWhole(p.cost_per_day)} ${formatDecimal(p.logistics_per_area)} ${formatDecimal(p.output_per_day)}
${formatWhole(p.average_traded_7d)} `; const output = tr.querySelector('td')!; output.dataset.tooltip = p.recipe; tbody.appendChild(tr); } } function color(n: number, low: number, high: number): string { // scale n from low..high to 0..1 clamped const scale = Math.min(Math.max((n - low) / (high - low), 0), 1); return `color-mix(in oklch, #0c8 ${scale * 100}%, #f70)`; } const main = document.querySelector('main')!; const popover = document.querySelector('#popover') as HTMLElement; main.addEventListener('mouseover', (event) => { const target = event.target as HTMLElement; if (target.dataset.tooltip) { popover.textContent = target.dataset.tooltip; const rect = target.getBoundingClientRect(); popover.style.left = `${rect.left}px`; popover.style.top = `${rect.bottom}px`; popover.showPopover(); } }); main.addEventListener('mouseout', (event) => { const target = event.target as HTMLElement; if (target.dataset.tooltip) popover.hidePopover(); }); lowVolume.addEventListener('change', render); render(); interface Profit { output: string recipe: string expertise: string profit_per_day: number area: number capex: number cost_per_day: number logistics_per_area: number output_per_day: number average_traded_7d: number }