diff --git a/frontend/css/style.css b/frontend/css/style.css index b831cd5..05a50e1 100644 --- a/frontend/css/style.css +++ b/frontend/css/style.css @@ -627,10 +627,13 @@ body { box-shadow: var(--shadow-sm); display: flex; flex-direction: column; + justify-content: space-between; + min-height: 410px; gap: 12px; cursor: pointer; transition: border-color 0.2s, box-shadow 0.2s; position: relative; + box-sizing: border-box; } .facility-card:hover { @@ -652,6 +655,8 @@ body { display: flex; gap: 12px; align-items: flex-start; + min-height: 60px; + box-sizing: border-box; } .facility-icon-wrap { @@ -671,20 +676,31 @@ body { .facility-title-wrap { flex: 1; min-width: 0; + overflow: hidden; } .facility-meta-row { display: flex; justify-content: space-between; align-items: center; + gap: 8px; + min-height: 22px; + height: 22px; + flex-wrap: nowrap; margin-bottom: 2px; + box-sizing: border-box; } .sector-badge { - font-size: 0.72rem; + font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1; + min-width: 0; } .facility-name { @@ -692,11 +708,17 @@ body { font-weight: 700; color: var(--text-primary); line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .facility-type-sub { font-size: 0.75rem; color: var(--text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .facility-desc { @@ -714,6 +736,11 @@ body { font-weight: 700; padding: 2px 8px; border-radius: 12px; + white-space: nowrap; + flex-shrink: 0; + height: 20px; + line-height: 1; + box-sizing: border-box; } .badge-success { @@ -754,27 +781,40 @@ body { /* Facility Stats Grid */ .facility-stats-grid { display: grid; - grid-template-columns: 1fr 1fr 1fr; + grid-template-columns: repeat(3, 1fr); gap: 8px; background: var(--bg-surface-sub); border-radius: var(--radius-sm); padding: 8px 10px; + min-height: 84px; + box-sizing: border-box; } .stat-cell { display: flex; flex-direction: column; + min-width: 0; } .stat-label { - font-size: 0.68rem; + font-size: 0.65rem; font-weight: 600; color: var(--text-muted); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .stat-value { - font-size: 0.8rem; + font-size: 0.78rem; color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.text-warning { + color: var(--amber-dark); } /* Inputs Strip */ diff --git a/frontend/js/ui/productionView.js b/frontend/js/ui/productionView.js index 0fd0544..0812d52 100644 --- a/frontend/js/ui/productionView.js +++ b/frontend/js/ui/productionView.js @@ -25,6 +25,34 @@ export function calculateMultiCost(baseCost, currentCount, multiplier) { return { count: countToBuy, cost: totalCost }; } +/** + * Computes live individual factory output rates (units/s and wholesale revenue $/s) + */ +export function getProductOutputRates(p, pState, state) { + if (!pState || !pState.unlocked || !pState.lines || pState.lines <= 0) { + return { prodRate: 0, revRate: 0, isBottlenecked: false }; + } + + const milestoneMult = getUnitMilestoneMultiplier(pState.lines); + const roboticsMult = state.upgrades?.automated_robotics ? 2.0 : 1.0; + const baseCycleTime = Math.max(0.1, p.baseCycleTime / (milestoneMult * roboticsMult)); + const isBottlenecked = !!pState.isBottlenecked; + + // Output velocity: units per second + const prodRate = isBottlenecked ? 0 : (1 / baseCycleTime) * p.outputAmount * pState.lines; + + // Logistics revenue rate: items sold passively per second * price + const logisticsUnits = pState.logistics || 0; + const logisticsPriceMult = state.upgrades?.global_supply_chain ? 1.5 : 1.0; + const salePrice = p.basePrice * logisticsPriceMult; + const maxSellCapacity = logisticsUnits * 0.75; + // If stockpiled, sells at max fleet capacity; otherwise bottlenecked by production velocity + const effectiveSellRate = Math.min(maxSellCapacity, pState.stock > 0 ? maxSellCapacity : prodRate); + const revRate = effectiveSellRate * salePrice; + + return { prodRate, revRate, isBottlenecked, milestoneMult }; +} + /** * Mounts the production floor DOM structure ONCE */ @@ -165,7 +193,8 @@ export function mountProductionFloor(container, state, onAction, engine) { } const nextM = getNextMilestone(pState.lines); - const milestoneMult = getUnitMilestoneMultiplier(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; @@ -231,12 +260,22 @@ export function mountProductionFloor(container, state, onAction, engine) {
- +