diff --git a/frontend/css/style.css b/frontend/css/style.css index 7af0fdc..e9ce1d9 100644 --- a/frontend/css/style.css +++ b/frontend/css/style.css @@ -385,10 +385,12 @@ body { } .sector-pill-rate { - font-size: 0.7rem; + font-size: 0.72rem; font-weight: 700; color: var(--emerald-dark); margin-left: 2px; + letter-spacing: -0.02em; + white-space: nowrap; } /* Sector Output Summary Banner */ @@ -577,30 +579,35 @@ body { background: var(--bg-surface); border: 1px solid var(--border-color); border-radius: var(--radius-md); - padding: 8px 12px; + padding: 6px 8px; } .sector-tabs-scroll { display: flex; - gap: 8px; - overflow-x: auto; - padding-bottom: 2px; + gap: 6px; + width: 100%; } .sector-pill { + flex: 1 1 0; + min-width: 0; display: inline-flex; align-items: center; + justify-content: center; gap: 6px; background: var(--bg-surface-sub); border: 1px solid var(--border-color); - padding: 6px 12px; + padding: 8px 6px; border-radius: var(--radius-sm); - font-size: 0.78rem; - font-weight: 600; - color: var(--text-secondary); cursor: pointer; white-space: nowrap; transition: all 0.15s; + box-sizing: border-box; +} + +.sector-pill iconify-icon { + font-size: 1.15rem; + flex-shrink: 0; } .sector-pill:hover { @@ -613,6 +620,14 @@ body { border-color: var(--text-primary); } +.sector-pill.active iconify-icon { + color: #ffffff !important; +} + +.sector-pill.active .sector-pill-rate { + color: #a7f3d0; +} + .production-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); diff --git a/frontend/js/app.js b/frontend/js/app.js index 0b67738..9126b8c 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -256,6 +256,16 @@ class ProductionApp { state.stats.contractsCompleted = (state.stats.contractsCompleted || 0) + 1; state.activeContracts.splice(idx, 1); + state.contractCooldown = 10.0; // 10 second cooldown before new tender RFP appears + + // Recompute readyContractsCount + let readyCount = 0; + for (const actC of state.activeContracts) { + const ps = state.products[actC.productId]; + if (ps && (ps.stock || 0) >= actC.targetAmount) readyCount++; + } + state.readyContractsCount = readyCount; + this.showToast(`Contract Fulfilled! Earned ${fmtCurrency(totalPayout)} + ${c.rewardRep} Rep!`, 'success'); this.sync.queueAction(action); this.mountActiveView(); @@ -342,9 +352,12 @@ class ProductionApp { const elContractsBadge = document.getElementById('contracts-badge'); if (elContractsBadge) { - const count = (state.activeContracts || []).length; - elContractsBadge.textContent = count; - elContractsBadge.style.display = count > 0 ? 'inline-flex' : 'none'; + const readyCount = state.readyContractsCount || 0; + const textVal = String(readyCount); + if (elContractsBadge.textContent !== textVal) { + elContractsBadge.textContent = textVal; + } + elContractsBadge.style.display = readyCount > 0 ? 'inline-flex' : 'none'; } const userBadge = document.getElementById('user-badge'); diff --git a/frontend/js/core/engine.js b/frontend/js/core/engine.js index f761251..6c97f1b 100644 --- a/frontend/js/core/engine.js +++ b/frontend/js/core/engine.js @@ -193,18 +193,35 @@ export class SimulationEngine { updateContracts(dt) { const state = this.sm.state; if (!state.activeContracts) state.activeContracts = []; + if (state.contractCooldown === undefined) state.contractCooldown = 0; for (let i = state.activeContracts.length - 1; i >= 0; i--) { const c = state.activeContracts[i]; c.remainingSeconds -= dt; if (c.remainingSeconds <= 0) { - // Expired contract, remove + // Expired contract, remove and trigger a cooldown before next tender state.activeContracts.splice(i, 1); + state.contractCooldown = Math.max(state.contractCooldown, 10.0); } } - // Replenish up to 3 open contracts - if (state.activeContracts.length < 3) { + // Count how many active tenders are fulfilled and ready to collect + let readyCount = 0; + for (const c of state.activeContracts) { + const pState = state.products[c.productId]; + if (pState && (pState.stock || 0) >= c.targetAmount) { + readyCount++; + } + } + state.readyContractsCount = readyCount; + + // Process replenishment cooldown + if (state.contractCooldown > 0) { + state.contractCooldown = Math.max(0, state.contractCooldown - dt); + } + + // Replenish up to 3 open contracts only when cooldown is over + if (state.activeContracts.length < 3 && state.contractCooldown <= 0) { const unlockedProducts = PRODUCTS.filter(p => state.products[p.id]?.unlocked); if (unlockedProducts.length > 0) { const availableTemplates = CONTRACT_TEMPLATES.filter(tmpl => @@ -226,6 +243,11 @@ export class SimulationEngine { rewardCash: tmpl.rewardCash, rewardRep: tmpl.rewardRep }); + + // If still under 3, stagger next replenish with a slight pause + if (state.activeContracts.length < 3) { + state.contractCooldown = 10.0; + } } } } diff --git a/frontend/js/ui/contractsView.js b/frontend/js/ui/contractsView.js index b22a065..538ebc3 100644 --- a/frontend/js/ui/contractsView.js +++ b/frontend/js/ui/contractsView.js @@ -23,8 +23,10 @@ export function mountContractsBoard(container, state, onAction) {
@@ -125,6 +127,18 @@ export function updateContractsBoard(container, state, onAction) { return; } + const elStatsReady = document.getElementById('contract-stats-ready'); + if (elStatsReady) { + const val = String(state.readyContractsCount || 0); + if (elStatsReady.textContent !== val) elStatsReady.textContent = val; + } + + const elStatsCompleted = document.getElementById('contract-stats-completed'); + if (elStatsCompleted) { + const val = String(state.stats?.contractsCompleted || 0); + if (elStatsCompleted.textContent !== val) elStatsCompleted.textContent = val; + } + for (const c of contracts) { const currentStock = state.products[c.productId]?.stock || 0; const canFulfill = currentStock >= c.targetAmount; diff --git a/frontend/js/ui/productionView.js b/frontend/js/ui/productionView.js index e0a2939..952aaab 100644 --- a/frontend/js/ui/productionView.js +++ b/frontend/js/ui/productionView.js @@ -69,17 +69,16 @@ export function mountProductionFloor(container, state, onAction, engine) { let html = `