feat(ui): evenly distribute sector pills with icons and compact rates, add 10s tender cooldown, and sync ready tender counter
This commit is contained in:
parent
e5232c2af3
commit
da5435afbc
5 changed files with 92 additions and 23 deletions
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,10 @@ export function mountContractsBoard(container, state, onAction) {
|
|||
</p>
|
||||
</div>
|
||||
<div class="contracts-meta-badge">
|
||||
<span>Completed Tenders:</span>
|
||||
<strong id="contract-stats-completed">${state.stats.contractsCompleted || 0}</strong>
|
||||
<span>Ready to Deliver:</span>
|
||||
<strong id="contract-stats-ready" class="font-mono text-profit">${state.readyContractsCount || 0}</strong>
|
||||
<span style="margin-left: 8px; border-left: 1px solid var(--border-color); padding-left: 8px;">Completed:</span>
|
||||
<strong id="contract-stats-completed" class="font-mono">${state.stats.contractsCompleted || 0}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -69,17 +69,16 @@ export function mountProductionFloor(container, state, onAction, engine) {
|
|||
let html = `
|
||||
<div class="production-toolbar">
|
||||
<div class="sector-tabs-scroll">
|
||||
<button class="sector-pill ${selectedSector === 'all' ? 'active' : ''}" data-sector="all">
|
||||
<button class="sector-pill ${selectedSector === 'all' ? 'active' : ''}" data-sector="all" title="All Sectors">
|
||||
<iconify-icon icon="lucide:layout-grid"></iconify-icon>
|
||||
<span>All Sectors</span>
|
||||
<span class="sector-pill-rate font-mono" id="pill-rate-all">+$${fmtNum(engine?.cashRate || 0, 1)}/s</span>
|
||||
</button>
|
||||
${SECTORS.map(s => {
|
||||
const stats = engine?.sectorStats?.[s.id] || { revenueRate: 0 };
|
||||
return `
|
||||
<button class="sector-pill ${selectedSector === s.id ? 'active' : ''}" data-sector="${s.id}">
|
||||
<button class="sector-pill ${selectedSector === s.id ? 'active' : ''}" data-sector="${s.id}" title="${s.name}">
|
||||
<iconify-icon icon="${s.icon}" style="color: ${s.color};"></iconify-icon>
|
||||
<span>${s.name}</span>
|
||||
<span class="sector-pill-rate font-mono" id="pill-rate-${s.id}">+$${fmtNum(stats.revenueRate, 2)}/s</span>
|
||||
<span class="sector-pill-rate font-mono" id="pill-rate-${s.id}">+$${fmtNum(stats.revenueRate, 1)}/s</span>
|
||||
</button>
|
||||
`;
|
||||
}).join('')}
|
||||
|
|
@ -373,12 +372,18 @@ export function updateProductionFloor(container, state, engine) {
|
|||
const selectedSector = state.selectedSector || 'all';
|
||||
|
||||
// 1. Update Sector Navigation Pills Output Rates
|
||||
const elPillRateAll = document.getElementById('pill-rate-all');
|
||||
if (elPillRateAll) {
|
||||
const text = `+$${fmtNum(engine?.cashRate || 0, 1)}/s`;
|
||||
if (elPillRateAll.textContent !== text) elPillRateAll.textContent = text;
|
||||
}
|
||||
|
||||
for (const s of SECTORS) {
|
||||
const stats = engine?.sectorStats?.[s.id];
|
||||
if (stats) {
|
||||
const elPillRate = document.getElementById(`pill-rate-${s.id}`);
|
||||
if (elPillRate) {
|
||||
const text = `+$${fmtNum(stats.revenueRate, 2)}/s`;
|
||||
const text = `+$${fmtNum(stats.revenueRate, 1)}/s`;
|
||||
if (elPillRate.textContent !== text) elPillRate.textContent = text;
|
||||
}
|
||||
// Update All-Sectors Mini Cards if on 'all'
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue