// ts/popover.ts function setupPopover() { const main = document.querySelector("main"); const popover = document.querySelector("#popover"); main.addEventListener("mouseover", (event) => { const target = event.target; 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; if (target.dataset.tooltip) popover.hidePopover(); }); } // ts/roi.ts var roiCache = {}; async function getROI(cx) { const response = await fetch(`/roi_${cx.toLowerCase()}.json`); const lastModified = new Date(response.headers.get("last-modified")); const profits = await response.json(); return { lastModified, profits }; } var lowVolume = document.querySelector("input#low-volume"); var cxSelect = document.querySelector("select#cx"); var expertise = { AGRICULTURE: "agri", CHEMISTRY: "chem", CONSTRUCTION: "const", ELECTRONICS: "elec", FOOD_INDUSTRIES: "food ind", FUEL_REFINING: "fuel", MANUFACTURING: "mfg", METALLURGY: "metal", RESOURCE_EXTRACTION: "res ext" }; var expertiseSelect = document.querySelector("select#expertise"); for (const key of Object.keys(expertise)) { const option = document.createElement("option"); option.value = key; option.textContent = key.replace("_", " ").toLowerCase(); expertiseSelect.appendChild(option); } var buildingSelect = document.querySelector("select#building"); var formatDecimal = new Intl.NumberFormat(undefined, { maximumFractionDigits: 2, maximumSignificantDigits: 6, roundingPriority: "lessPrecision" }).format; var formatWhole = new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 }).format; async function render() { const tbody = document.querySelector("tbody"); tbody.innerHTML = ""; const cx = cxSelect.value; if (!roiCache[cx]) roiCache[cx] = getROI(cx); const { lastModified, profits } = await roiCache[cx]; const buildingTickers = new Set(profits.map((p) => p.building)); const buildings = Array.from(buildingTickers).map((building) => ({ ticker: building, expertise: profits.find((p) => p.building === building).expertise })).sort((a, b) => a.ticker.localeCompare(b.ticker)); let selectedBuilding = buildingSelect.value; let buildingFound = false; buildingSelect.innerHTML = ''; for (const building of buildings) if (expertiseSelect.value === "" || expertiseSelect.value === building.expertise) { const option = document.createElement("option"); option.value = building.ticker; option.textContent = building.ticker; if (building.ticker === selectedBuilding) { buildingFound = true; option.selected = true; } buildingSelect.appendChild(option); } if (!buildingFound) selectedBuilding = ""; for (const p of profits) { const volumeRatio = p.output_per_day / p.average_traded_7d; if (!lowVolume.checked && volumeRatio > 0.05) continue; if (expertiseSelect.value !== "" && p.expertise !== expertiseSelect.value) continue; if (selectedBuilding !== "" && p.building !== selectedBuilding) continue; const tr = document.createElement("tr"); const profitPerArea = p.profit_per_day / p.area; const breakEven = p.profit_per_day > 0 ? (p.capex + 3 * p.cost_per_day) / p.profit_per_day : Infinity; tr.innerHTML = `