// Production UI: Individual Facility Card (HTML Mount and Fine-grained DOM update) import { getProduct, getNextMilestone, getUnitMilestoneMultiplier, SECTORS } from '../../data/products.js'; import { fmtCurrency, fmtNum } from '../../core/formatters.js'; import { calculateMultiCost, getProductOutputRates, computeProductNetFlow } from './ratesCalculator.js'; export function renderFacilityCardHtml(p, pState, state, buyMode) { const sector = SECTORS.find(s => s.id === p.sector); const isSelected = state.selectedProductId === p.id; if (!pState.unlocked) { const canAffordUnlock = state.cash >= p.unlockCost; return `
${sector?.name || 'Industry'} • Tier ${p.tier}

${p.name}

${p.description}

`; } const nextM = getNextMilestone(pState.lines); const rates = getProductOutputRates(p, pState, state); const milestoneMult = rates.milestoneMult || getUnitMilestoneMultiplier(pState.lines); const calc = calculateMultiCost(p.baseFacilityCost, pState.lines, buyMode); const canAffordBuy = state.cash >= calc.cost; const managerCost = p.baseManagerCost; const canAffordManager = state.cash >= managerCost; const logisticsCost = p.baseLogisticsCost * Math.pow(1.15, pState.logistics || 0); const canAffordLogistics = state.cash >= logisticsCost; const isRaw = !p.inputs || p.inputs.length === 0; let inputsHtml = ''; if (!isRaw) { inputsHtml = `
Inputs per line:
${p.inputs.map(inp => { const inpProduct = getProduct(inp.id); const currentStock = state.products[inp.id]?.stock || 0; const neededPerLine = inp.amount * pState.lines; const isLow = currentStock < neededPerLine; return ` ${inpProduct?.name}: ${fmtNum(currentStock, 0)} / ${fmtNum(neededPerLine, 0)} `; }).join('')}
`; } let statusBadge = ''; if (pState.isBottlenecked) { statusBadge = ` Waiting for Inputs`; } else if (pState.lines > 0) { statusBadge = ` Operating (${milestoneMult}x Speed)`; } else { statusBadge = `Idle (0 Lines)`; } const progressPct = Math.min(100, Math.floor((pState.cycleProgress || 0) * 100)); return `
${sector?.name} • Tier ${p.tier}
${statusBadge}

${p.name}

${p.facilityName} • ${pState.lines} lines (${nextM - pState.lines} to 2x)
Stockpile: ${fmtNum(pState.stock, 0)} ${p.unit}
Production Output: ${rates.isBottlenecked ? '0.0 (Paused)' : `${fmtNum(rates.prodRate, 1)}/s`}
Net Balance: +0.0/s
Logistics Rev: +$${fmtNum(rates.revRate, 2)}/s
Wholesale Price: ${fmtCurrency(p.basePrice)}
Logistics Auto-Sell: ${pState.logistics || 0} fleets (${(pState.logisticsEnabled !== false) ? `${fmtNum((pState.logistics || 0) * 0.75, 2)}/s` : 'Paused'})
${inputsHtml}
Policy:
${!pState.manager ? ` ` : ` Lead Active (Auto) `}
`; } export function updateFacilityCardDom(p, pState, state, buyMode) { const cash = state.cash; if (!pState.unlocked) { const btnUnlock = document.getElementById(`btn-unlock-${p.id}`); if (btnUnlock) { const canAfford = cash >= p.unlockCost; if (btnUnlock.disabled !== !canAfford) { btnUnlock.disabled = !canAfford; } } return; } // Live Individual Factory Rates & Output const rates = getProductOutputRates(p, pState, state); const elRateUnits = document.getElementById(`rate-units-${p.id}`); if (elRateUnits) { const unitsText = rates.isBottlenecked ? '0.0 (Paused)' : `${fmtNum(rates.prodRate, 1)}/s`; if (elRateUnits.textContent !== unitsText) { elRateUnits.textContent = unitsText; } elRateUnits.className = `stat-value font-mono ${rates.isBottlenecked ? 'text-warning' : 'text-profit'}`; } // Live Net Flow (Production minus Downstream Consumption minus Logistics Sales) const netFlow = computeProductNetFlow(p, state); const elNetFlow = document.getElementById(`net-flow-${p.id}`); if (elNetFlow) { const sign = netFlow.netRate > 0.01 ? '+' : ''; const netText = `${sign}${fmtNum(netFlow.netRate, 1)}/s`; if (elNetFlow.textContent !== netText) { elNetFlow.textContent = netText; } const isDeficit = netFlow.netRate < -0.05; const isSurplus = netFlow.netRate > 0.05; elNetFlow.className = `stat-value font-mono ${isDeficit ? 'text-warning' : (isSurplus ? 'text-profit' : '')}`; } const elRateRev = document.getElementById(`rate-rev-${p.id}`); if (elRateRev) { const revText = `+$${fmtNum(rates.revRate, 2)}/s`; if (elRateRev.textContent !== revText) { elRateRev.textContent = revText; } } const elEff = document.getElementById(`eff-${p.id}`); if (elEff) { const effText = `${rates.milestoneMult}x Speed`; if (elEff.textContent !== effText) { elEff.textContent = effText; } } const elLogistics = document.getElementById(`logistics-${p.id}`); if (elLogistics) { const count = pState.logistics || 0; const isLogActive = pState.logisticsEnabled !== false; const ratePart = isLogActive ? `${fmtNum(count * 0.75, 2)}/s` : 'Paused'; const logText = `${count} fleets (${ratePart})`; if (elLogistics.textContent !== logText) { elLogistics.textContent = logText; } } // Policy Button Toggles DOM State const btnToggleLog = document.getElementById(`btn-toggle-logistics-${p.id}`); if (btnToggleLog) { const isLogActive = pState.logisticsEnabled !== false; btnToggleLog.className = `btn-policy-toggle ${isLogActive ? 'active' : 'inactive'} btn-toggle-logistics`; const textSpan = document.getElementById(`btn-toggle-logistics-text-${p.id}`); if (textSpan) { const tText = `Wholesale Fleet: ${isLogActive ? 'ON' : 'OFF'}`; if (textSpan.textContent !== tText) textSpan.textContent = tText; } const iconEl = btnToggleLog.querySelector('iconify-icon'); if (iconEl) { iconEl.setAttribute('icon', isLogActive ? 'lucide:truck' : 'lucide:truck-x'); } } const btnToggleDown = document.getElementById(`btn-toggle-downstream-${p.id}`); if (btnToggleDown) { const isDownActive = pState.supplyDownstreamEnabled !== false; btnToggleDown.className = `btn-policy-toggle ${isDownActive ? 'active' : 'inactive'} btn-toggle-downstream`; const textSpan = document.getElementById(`btn-toggle-downstream-text-${p.id}`); if (textSpan) { const tText = `Downstream Supply: ${isDownActive ? 'ON' : 'OFF'}`; if (textSpan.textContent !== tText) textSpan.textContent = tText; } const iconEl = btnToggleDown.querySelector('iconify-icon'); if (iconEl) { iconEl.setAttribute('icon', isDownActive ? 'lucide:git-pull-request' : 'lucide:git-pull-request-closed'); } } const elProgress = document.getElementById(`progress-${p.id}`); if (elProgress) { const progressPct = Math.min(100, Math.floor((pState.cycleProgress || 0) * 100)); elProgress.style.width = `${progressPct}%`; } // Stockpile Text const elStock = document.getElementById(`stock-${p.id}`); if (elStock) { const stockText = fmtNum(pState.stock, 0); if (elStock.textContent !== stockText) { elStock.textContent = stockText; } } // Status Badge const elBadge = document.getElementById(`badge-${p.id}`); if (elBadge) { const isBottlenecked = pState.isBottlenecked; const hasWarning = elBadge.classList.contains('badge-warning'); if (isBottlenecked && !hasWarning) { elBadge.className = 'status-badge badge-warning'; elBadge.innerHTML = ` Waiting for Inputs`; } else if (!isBottlenecked && hasWarning) { const milestoneMult = getUnitMilestoneMultiplier(pState.lines); elBadge.className = 'status-badge badge-success'; elBadge.innerHTML = ` Operating (${milestoneMult}x Speed)`; } } // Input Chips Stock if (p.inputs && p.inputs.length > 0) { for (const inp of p.inputs) { const currentStock = state.products[inp.id]?.stock || 0; const elChipVal = document.getElementById(`chip-val-${p.id}-${inp.id}`); if (elChipVal) { const textVal = fmtNum(currentStock, 0); if (elChipVal.textContent !== textVal) { elChipVal.textContent = textVal; } } const elChip = document.getElementById(`chip-${p.id}-${inp.id}`); if (elChip) { const needed = inp.amount * pState.lines; const isLow = currentStock < needed; elChip.classList.toggle('low-stock', isLow); } } } // Expand Button disabled state const calc = calculateMultiCost(p.baseFacilityCost, pState.lines, buyMode); const elBtnExpand = document.getElementById(`btn-expand-${p.id}`); if (elBtnExpand) { const canAfford = cash >= calc.cost; if (elBtnExpand.disabled !== !canAfford) { elBtnExpand.disabled = !canAfford; } } // Hire Manager disabled state const elBtnHire = document.getElementById(`btn-hire-${p.id}`); if (elBtnHire) { const canAfford = cash >= p.baseManagerCost; if (elBtnHire.disabled !== !canAfford) { elBtnHire.disabled = !canAfford; } } // Logistics Button disabled state const logisticsCost = p.baseLogisticsCost * Math.pow(1.15, pState.logistics || 0); const elBtnLogistics = document.getElementById(`btn-logistics-${p.id}`); if (elBtnLogistics) { const canAfford = cash >= logisticsCost; if (elBtnLogistics.disabled !== !canAfford) { elBtnLogistics.disabled = !canAfford; } const elBtnLogisticsText = document.getElementById(`btn-logistics-text-${p.id}`); if (elBtnLogisticsText) { const text = `+Fleet (${fmtCurrency(logisticsCost)})`; if (elBtnLogisticsText.textContent !== text) { elBtnLogisticsText.textContent = text; } } } // Selected Card class const elCard = document.getElementById(`card-${p.id}`); if (elCard) { const isSelected = state.selectedProductId === p.id; elCard.classList.toggle('selected', isSelected); } }