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 {
|
.sector-pill-rate {
|
||||||
font-size: 0.7rem;
|
font-size: 0.72rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--emerald-dark);
|
color: var(--emerald-dark);
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sector Output Summary Banner */
|
/* Sector Output Summary Banner */
|
||||||
|
|
@ -577,30 +579,35 @@ body {
|
||||||
background: var(--bg-surface);
|
background: var(--bg-surface);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
padding: 8px 12px;
|
padding: 6px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sector-tabs-scroll {
|
.sector-tabs-scroll {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 6px;
|
||||||
overflow-x: auto;
|
width: 100%;
|
||||||
padding-bottom: 2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sector-pill {
|
.sector-pill {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-width: 0;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
background: var(--bg-surface-sub);
|
background: var(--bg-surface-sub);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
padding: 6px 12px;
|
padding: 8px 6px;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
font-size: 0.78rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
transition: all 0.15s;
|
transition: all 0.15s;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sector-pill iconify-icon {
|
||||||
|
font-size: 1.15rem;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sector-pill:hover {
|
.sector-pill:hover {
|
||||||
|
|
@ -613,6 +620,14 @@ body {
|
||||||
border-color: var(--text-primary);
|
border-color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sector-pill.active iconify-icon {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sector-pill.active .sector-pill-rate {
|
||||||
|
color: #a7f3d0;
|
||||||
|
}
|
||||||
|
|
||||||
.production-grid {
|
.production-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
|
||||||
|
|
|
||||||
|
|
@ -256,6 +256,16 @@ class ProductionApp {
|
||||||
state.stats.contractsCompleted = (state.stats.contractsCompleted || 0) + 1;
|
state.stats.contractsCompleted = (state.stats.contractsCompleted || 0) + 1;
|
||||||
|
|
||||||
state.activeContracts.splice(idx, 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.showToast(`Contract Fulfilled! Earned ${fmtCurrency(totalPayout)} + ${c.rewardRep} Rep!`, 'success');
|
||||||
this.sync.queueAction(action);
|
this.sync.queueAction(action);
|
||||||
this.mountActiveView();
|
this.mountActiveView();
|
||||||
|
|
@ -342,9 +352,12 @@ class ProductionApp {
|
||||||
|
|
||||||
const elContractsBadge = document.getElementById('contracts-badge');
|
const elContractsBadge = document.getElementById('contracts-badge');
|
||||||
if (elContractsBadge) {
|
if (elContractsBadge) {
|
||||||
const count = (state.activeContracts || []).length;
|
const readyCount = state.readyContractsCount || 0;
|
||||||
elContractsBadge.textContent = count;
|
const textVal = String(readyCount);
|
||||||
elContractsBadge.style.display = count > 0 ? 'inline-flex' : 'none';
|
if (elContractsBadge.textContent !== textVal) {
|
||||||
|
elContractsBadge.textContent = textVal;
|
||||||
|
}
|
||||||
|
elContractsBadge.style.display = readyCount > 0 ? 'inline-flex' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
const userBadge = document.getElementById('user-badge');
|
const userBadge = document.getElementById('user-badge');
|
||||||
|
|
|
||||||
|
|
@ -193,18 +193,35 @@ export class SimulationEngine {
|
||||||
updateContracts(dt) {
|
updateContracts(dt) {
|
||||||
const state = this.sm.state;
|
const state = this.sm.state;
|
||||||
if (!state.activeContracts) state.activeContracts = [];
|
if (!state.activeContracts) state.activeContracts = [];
|
||||||
|
if (state.contractCooldown === undefined) state.contractCooldown = 0;
|
||||||
|
|
||||||
for (let i = state.activeContracts.length - 1; i >= 0; i--) {
|
for (let i = state.activeContracts.length - 1; i >= 0; i--) {
|
||||||
const c = state.activeContracts[i];
|
const c = state.activeContracts[i];
|
||||||
c.remainingSeconds -= dt;
|
c.remainingSeconds -= dt;
|
||||||
if (c.remainingSeconds <= 0) {
|
if (c.remainingSeconds <= 0) {
|
||||||
// Expired contract, remove
|
// Expired contract, remove and trigger a cooldown before next tender
|
||||||
state.activeContracts.splice(i, 1);
|
state.activeContracts.splice(i, 1);
|
||||||
|
state.contractCooldown = Math.max(state.contractCooldown, 10.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replenish up to 3 open contracts
|
// Count how many active tenders are fulfilled and ready to collect
|
||||||
if (state.activeContracts.length < 3) {
|
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);
|
const unlockedProducts = PRODUCTS.filter(p => state.products[p.id]?.unlocked);
|
||||||
if (unlockedProducts.length > 0) {
|
if (unlockedProducts.length > 0) {
|
||||||
const availableTemplates = CONTRACT_TEMPLATES.filter(tmpl =>
|
const availableTemplates = CONTRACT_TEMPLATES.filter(tmpl =>
|
||||||
|
|
@ -226,6 +243,11 @@ export class SimulationEngine {
|
||||||
rewardCash: tmpl.rewardCash,
|
rewardCash: tmpl.rewardCash,
|
||||||
rewardRep: tmpl.rewardRep
|
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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="contracts-meta-badge">
|
<div class="contracts-meta-badge">
|
||||||
<span>Completed Tenders:</span>
|
<span>Ready to Deliver:</span>
|
||||||
<strong id="contract-stats-completed">${state.stats.contractsCompleted || 0}</strong>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -125,6 +127,18 @@ export function updateContractsBoard(container, state, onAction) {
|
||||||
return;
|
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) {
|
for (const c of contracts) {
|
||||||
const currentStock = state.products[c.productId]?.stock || 0;
|
const currentStock = state.products[c.productId]?.stock || 0;
|
||||||
const canFulfill = currentStock >= c.targetAmount;
|
const canFulfill = currentStock >= c.targetAmount;
|
||||||
|
|
|
||||||
|
|
@ -69,17 +69,16 @@ export function mountProductionFloor(container, state, onAction, engine) {
|
||||||
let html = `
|
let html = `
|
||||||
<div class="production-toolbar">
|
<div class="production-toolbar">
|
||||||
<div class="sector-tabs-scroll">
|
<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>
|
<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>
|
</button>
|
||||||
${SECTORS.map(s => {
|
${SECTORS.map(s => {
|
||||||
const stats = engine?.sectorStats?.[s.id] || { revenueRate: 0 };
|
const stats = engine?.sectorStats?.[s.id] || { revenueRate: 0 };
|
||||||
return `
|
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>
|
<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, 1)}/s</span>
|
||||||
<span class="sector-pill-rate font-mono" id="pill-rate-${s.id}">+$${fmtNum(stats.revenueRate, 2)}/s</span>
|
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
}).join('')}
|
}).join('')}
|
||||||
|
|
@ -373,12 +372,18 @@ export function updateProductionFloor(container, state, engine) {
|
||||||
const selectedSector = state.selectedSector || 'all';
|
const selectedSector = state.selectedSector || 'all';
|
||||||
|
|
||||||
// 1. Update Sector Navigation Pills Output Rates
|
// 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) {
|
for (const s of SECTORS) {
|
||||||
const stats = engine?.sectorStats?.[s.id];
|
const stats = engine?.sectorStats?.[s.id];
|
||||||
if (stats) {
|
if (stats) {
|
||||||
const elPillRate = document.getElementById(`pill-rate-${s.id}`);
|
const elPillRate = document.getElementById(`pill-rate-${s.id}`);
|
||||||
if (elPillRate) {
|
if (elPillRate) {
|
||||||
const text = `+$${fmtNum(stats.revenueRate, 2)}/s`;
|
const text = `+$${fmtNum(stats.revenueRate, 1)}/s`;
|
||||||
if (elPillRate.textContent !== text) elPillRate.textContent = text;
|
if (elPillRate.textContent !== text) elPillRate.textContent = text;
|
||||||
}
|
}
|
||||||
// Update All-Sectors Mini Cards if on 'all'
|
// Update All-Sectors Mini Cards if on 'all'
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue