feat(dossier): enhance facility detail view with Capitalism II telemetry, product hero showcase, and clean in-universe styling

This commit is contained in:
Christian 2026-10-16 16:04:10 +02:00
parent b8ac21d373
commit 98fb1147b3
48 changed files with 245 additions and 65 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 KiB

View file

@ -141,14 +141,15 @@
font-size: 0.88rem; font-size: 0.88rem;
} }
/* Dual Photographic Cards Grid */ /* Asymmetric Visual Showcase (Product Primacy & Architectural Reference) */
.dossier-photo-grid { .dossier-photo-grid {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1.45fr 1fr;
gap: 16px; gap: 16px;
align-items: stretch;
} }
@media (max-width: 900px) { @media (max-width: 960px) {
.dossier-photo-grid { .dossier-photo-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
@ -161,8 +162,16 @@
padding: 16px; padding: 16px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between;
gap: 12px; gap: 12px;
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
position: relative;
overflow: hidden;
}
.dossier-photo-card.hero-product-card {
border-color: var(--border-subtle);
box-shadow: var(--shadow-md);
} }
.photo-card-header { .photo-card-header {
@ -178,20 +187,22 @@
color: var(--text-secondary); color: var(--text-secondary);
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 5px; gap: 6px;
letter-spacing: 0.03em;
} }
.photo-spec { .photo-spec {
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 700;
color: var(--primary-blue); color: var(--primary-blue);
background: var(--primary-blue-light); background: var(--primary-blue-light);
padding: 2px 8px; padding: 3px 8px;
border-radius: 4px; border-radius: 4px;
} }
.photo-wrapper { .photo-wrapper {
width: 100%; width: 100%;
height: 220px; height: 240px;
border-radius: var(--radius-md); border-radius: var(--radius-md);
background: var(--bg-surface-sub); background: var(--bg-surface-sub);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
@ -202,6 +213,10 @@
justify-content: center; justify-content: center;
} }
.photo-wrapper.secondary-wrapper {
height: 200px;
}
.dossier-photo-img { .dossier-photo-img {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -213,33 +228,69 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 8px; gap: 10px;
text-align: center; text-align: center;
padding: 20px; padding: 24px;
} }
.photo-fallback-icon { .photo-fallback-icon {
font-size: 3rem; font-size: 3.2rem;
opacity: 0.9;
} }
.photo-fallback-title { .photo-fallback-title {
font-size: 1rem; font-size: 1.05rem;
font-weight: 700; font-weight: 800;
color: var(--text-primary); color: var(--text-primary);
} }
.photo-fallback-sub { .photo-fallback-sub {
font-size: 0.72rem; font-size: 0.75rem;
color: var(--text-secondary);
max-width: 320px;
line-height: 1.4;
}
.product-quick-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
background: var(--bg-surface-sub);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
padding: 10px 14px;
}
.p-stat-item {
display: flex;
flex-direction: column;
}
.p-stat-label {
font-size: 0.68rem;
font-weight: 700;
color: var(--text-muted); color: var(--text-muted);
font-family: var(--font-mono); text-transform: uppercase;
letter-spacing: 0.03em;
}
.p-stat-val {
font-size: 0.95rem;
font-weight: 800;
color: var(--text-primary);
margin-top: 2px;
} }
.photo-caption { .photo-caption {
font-size: 0.78rem; font-size: 0.78rem;
color: var(--text-secondary); color: var(--text-secondary);
font-style: italic; border-left: 2px solid var(--primary-blue);
border-left: 2px solid var(--border-color); padding-left: 10px;
padding-left: 8px; line-height: 1.4;
}
.photo-caption.secondary-caption {
border-left-color: var(--border-subtle);
} }
/* Deep Statistical Matrix */ /* Deep Statistical Matrix */
@ -323,6 +374,38 @@
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
} }
.risk-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.70rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.risk-badge.risk-optimal {
background: var(--emerald-light);
color: var(--emerald-dark);
}
.risk-badge.risk-low {
background: var(--amber-light);
color: var(--amber-dark);
}
.risk-badge.risk-bottleneck {
background: #fee2e2;
color: #b91c1c;
}
[data-theme="dark"] .risk-badge.risk-bottleneck {
background: #450a0a;
color: #f87171;
}
/* Strategic Controls Grid */ /* Strategic Controls Grid */
.dossier-control-grid { .dossier-control-grid {
display: grid; display: grid;

View file

@ -60,17 +60,43 @@ export function mountUnitDetailView(container, state, onAction, engine) {
const revPerSec = rates.revRate || 0; const revPerSec = rates.revRate || 0;
const revPerHour = revPerSec * 3600; const revPerHour = revPerSec * 3600;
// Valuation contribution // Valuation and Revenue Contribution
const facilityValuation = (pState.lines || 0) * product.baseFacilityCost; const facilityValuation = (pState.lines || 0) * product.baseFacilityCost;
const inventoryValuation = (pState.stock || 0) * product.basePrice; const inventoryValuation = (pState.stock || 0) * product.basePrice;
const totalCompanyCashRate = engine?.cashRate || 0;
const companyRevShare = totalCompanyCashRate > 0.001
? Math.min(100, Math.max(0, (rates.revRate / totalCompanyCashRate) * 100))
: 0;
// Dual Photographic URLs (with standardized paths) // Actual Cycle Time adjusted for milestone speed multiplier & throttle
const effectiveCycleTime = product.baseCycleTime / (milestoneMult * Math.max(0.01, currentThrottle));
const effectiveLines = (pState.lines || 0) * currentThrottle;
// Upstream Stock & Bottleneck Risk Assessment
let bottleneckStatus = 'optimal';
let bottleneckLabel = 'Optimal Input Reserves';
if (product.inputs && product.inputs.length > 0) {
for (const inp of product.inputs) {
const parentState = state.products[inp.id];
const neededPerBatch = inp.amount * (pState.lines || 1);
if (!parentState || (parentState.stock || 0) < 1) {
bottleneckStatus = 'bottleneck';
bottleneckLabel = 'Starved: Zero Raw Materials';
break;
} else if ((parentState.stock || 0) < neededPerBatch) {
bottleneckStatus = 'low';
bottleneckLabel = 'Low Upstream Reserves';
}
}
}
// Photographic Asset Paths
const facilityImgPath = `assets/images/facilities/${product.id}_facility.jpg`; const facilityImgPath = `assets/images/facilities/${product.id}_facility.jpg`;
const productImgPath = `assets/images/products/${product.id}_product.jpg`; const productImgPath = `assets/images/products/${product.id}_product.jpg`;
let html = ` let html = `
<div class="unit-detail-canvas"> <div class="unit-detail-canvas">
<!-- Navigation Breadcrumb & Header Controls --> <!-- Navigation Breadcrumb & Header Controls (Clean in-universe badges, NO SYS-ID) -->
<div class="dossier-top-bar"> <div class="dossier-top-bar">
<button class="btn-dossier-back" id="btn-back-to-floor" title="Return to Production Floor (Esc)"> <button class="btn-dossier-back" id="btn-back-to-floor" title="Return to Production Floor (Esc)">
<iconify-icon icon="lucide:arrow-left"></iconify-icon> <iconify-icon icon="lucide:arrow-left"></iconify-icon>
@ -81,8 +107,11 @@ export function mountUnitDetailView(container, state, onAction, engine) {
<span class="sector-tag" style="background: ${product.color}15; color: ${product.color};"> <span class="sector-tag" style="background: ${product.color}15; color: ${product.color};">
<iconify-icon icon="${product.icon}"></iconify-icon> ${sector?.name} <iconify-icon icon="${product.icon}"></iconify-icon> ${sector?.name}
</span> </span>
<span class="tier-tag">Tier ${product.tier}</span> <span class="tier-tag">Tier ${product.tier} Industry</span>
<span class="dossier-id-tag font-mono">SYS-ID: ${product.id.toUpperCase()}</span> <span class="risk-badge risk-${bottleneckStatus}" id="det-bottleneck-badge">
<iconify-icon icon="${bottleneckStatus === 'optimal' ? 'lucide:shield-check' : 'lucide:alert-triangle'}"></iconify-icon>
<span id="det-bottleneck-text">${bottleneckLabel}</span>
</span>
</div> </div>
</div> </div>
@ -93,7 +122,7 @@ export function mountUnitDetailView(container, state, onAction, engine) {
<iconify-icon icon="${product.icon}" class="icon-hero"></iconify-icon> <iconify-icon icon="${product.icon}" class="icon-hero"></iconify-icon>
</div> </div>
<div class="dossier-hero-text"> <div class="dossier-hero-text">
<div class="dossier-pretitle">${product.facilityName} Blueprint &bull; ${sector?.name}</div> <div class="dossier-pretitle">${product.facilityName} &bull; ${sector?.name}</div>
<h1 class="dossier-title">${product.name}</h1> <h1 class="dossier-title">${product.name}</h1>
<p class="dossier-desc">${product.description}</p> <p class="dossier-desc">${product.description}</p>
</div> </div>
@ -123,31 +152,12 @@ export function mountUnitDetailView(container, state, onAction, engine) {
</div> </div>
</div> </div>
<!-- Dual Photographic Assets Section --> <!-- Visual Showcase: Product Primacy (Hero Showcase) + Architectural Facility Reference (Clean Placeholders, NO file paths) -->
<div class="dossier-photo-grid"> <div class="dossier-photo-grid">
<!-- 1. Facility Architecture Photo Card --> <!-- 1. Primary Commodity Specimen Showpiece -->
<div class="dossier-photo-card"> <div class="dossier-photo-card hero-product-card">
<div class="photo-card-header"> <div class="photo-card-header">
<span class="photo-tag"><iconify-icon icon="lucide:building-2"></iconify-icon> Industrial Facility Rendering</span> <span class="photo-tag"><iconify-icon icon="lucide:box"></iconify-icon> Primary Commodity Specimen</span>
<span class="photo-spec font-mono">${product.facilityName}</span>
</div>
<div class="photo-wrapper">
<img src="${facilityImgPath}" alt="${product.facilityName}" class="dossier-photo-img" onerror="this.onerror=null; this.parentElement.classList.add('photo-fallback'); this.style.display='none';">
<div class="photo-placeholder-box">
<iconify-icon icon="${sector?.icon || 'lucide:factory'}" class="photo-fallback-icon" style="color: ${product.color};"></iconify-icon>
<span class="photo-fallback-title">${product.facilityName}</span>
<span class="photo-fallback-sub">Blueprint asset pending in /assets/images/facilities/${product.id}_facility.jpg</span>
</div>
</div>
<div class="photo-caption">
<span>High-throughput architectural layout optimized for Tier ${product.tier} processing.</span>
</div>
</div>
<!-- 2. Commodity Specimen Photo Card -->
<div class="dossier-photo-card">
<div class="photo-card-header">
<span class="photo-tag"><iconify-icon icon="lucide:box"></iconify-icon> Material / Commodity Specimen</span>
<span class="photo-spec font-mono">${product.unit}</span> <span class="photo-spec font-mono">${product.unit}</span>
</div> </div>
<div class="photo-wrapper"> <div class="photo-wrapper">
@ -155,25 +165,58 @@ export function mountUnitDetailView(container, state, onAction, engine) {
<div class="photo-placeholder-box"> <div class="photo-placeholder-box">
<iconify-icon icon="${product.icon}" class="photo-fallback-icon" style="color: ${product.color};"></iconify-icon> <iconify-icon icon="${product.icon}" class="photo-fallback-icon" style="color: ${product.color};"></iconify-icon>
<span class="photo-fallback-title">${product.name}</span> <span class="photo-fallback-title">${product.name}</span>
<span class="photo-fallback-sub">Specimen asset pending in /assets/images/products/${product.id}_product.jpg</span> <span class="photo-fallback-sub">Archival material specimen registered in industrial catalog.</span>
</div>
</div>
<div class="product-quick-stats">
<div class="p-stat-item">
<span class="p-stat-label">Market Spot Price:</span>
<span class="p-stat-val font-mono">${fmtCurrency(product.basePrice)}</span>
</div>
<div class="p-stat-item">
<span class="p-stat-label">Warehouse Stock:</span>
<span class="p-stat-val font-mono" id="det-hero-stock">${fmtNum(pState.stock || 0, 0)} ${product.unit}</span>
</div>
<div class="p-stat-item">
<span class="p-stat-label">Inventory Value:</span>
<span class="p-stat-val font-mono text-profit" id="det-hero-stock-val">${fmtCurrency(inventoryValuation)}</span>
</div> </div>
</div> </div>
<div class="photo-caption"> <div class="photo-caption">
<span>Commercial wholesale specimen traded at ${fmtCurrency(product.basePrice)} per ${product.unit}.</span> <span>Commercial grade commodity wholesale specimen traded at ${fmtCurrency(product.basePrice)} per ${product.unit}.</span>
</div>
</div>
<!-- 2. Facility Architecture Reference Card -->
<div class="dossier-photo-card">
<div class="photo-card-header">
<span class="photo-tag"><iconify-icon icon="lucide:building-2"></iconify-icon> Operating Facility Layout</span>
<span class="photo-spec font-mono">${product.facilityName}</span>
</div>
<div class="photo-wrapper secondary-wrapper">
<img src="${facilityImgPath}" alt="${product.facilityName}" class="dossier-photo-img" onerror="this.onerror=null; this.parentElement.classList.add('photo-fallback'); this.style.display='none';">
<div class="photo-placeholder-box">
<iconify-icon icon="${sector?.icon || 'lucide:factory'}" class="photo-fallback-icon" style="color: ${product.color};"></iconify-icon>
<span class="photo-fallback-title">${product.facilityName}</span>
<span class="photo-fallback-sub">Architectural production plant schematic.</span>
</div>
</div>
<div class="photo-caption secondary-caption">
<span>High-throughput architectural layout optimized for Tier ${product.tier} commercial processing.</span>
</div> </div>
</div> </div>
</div> </div>
<!-- Deep Statistical & Velocity Telemetry (Data Nerd Grid) --> <!-- Deep Statistical & Velocity Telemetry (Capitalism II Style 3-Column Matrix) -->
<div class="dossier-section-title"> <div class="dossier-section-title">
<iconify-icon icon="lucide:activity"></iconify-icon> Operational & Financial Telemetry <iconify-icon icon="lucide:activity"></iconify-icon> Executive Economics & Production Telemetry
</div> </div>
<div class="dossier-stats-matrix"> <div class="dossier-stats-matrix">
<!-- Financial Block --> <!-- 1. Financial Performance Block -->
<div class="matrix-card"> <div class="matrix-card">
<div class="matrix-header"> <div class="matrix-header">
<iconify-icon icon="lucide:dollar-sign"></iconify-icon> Financial Performance <iconify-icon icon="lucide:dollar-sign"></iconify-icon> Financial Performance & Share
</div> </div>
<div class="matrix-rows"> <div class="matrix-rows">
<div class="matrix-row"> <div class="matrix-row">
@ -188,34 +231,39 @@ export function mountUnitDetailView(container, state, onAction, engine) {
<span class="m-label">Projected Hourly Revenue:</span> <span class="m-label">Projected Hourly Revenue:</span>
<span class="m-val font-mono text-profit" id="det-rev-hour">${fmtCurrency(revPerHour)}/h</span> <span class="m-val font-mono text-profit" id="det-rev-hour">${fmtCurrency(revPerHour)}/h</span>
</div> </div>
<div class="matrix-row">
<span class="m-label">Enterprise Revenue Share:</span>
<span class="m-val font-mono text-equity" id="det-rev-share">${fmtNum(companyRevShare, 1)}%</span>
</div>
<div class="matrix-divider"></div>
<div class="matrix-row"> <div class="matrix-row">
<span class="m-label">Inventory Warehouse Value:</span> <span class="m-label">Inventory Warehouse Value:</span>
<span class="m-val font-mono" id="det-stock-val">${fmtCurrency(inventoryValuation)}</span> <span class="m-val font-mono" id="det-stock-val">${fmtCurrency(inventoryValuation)}</span>
</div> </div>
<div class="matrix-row"> <div class="matrix-row">
<span class="m-label">Facility Asset Valuation:</span> <span class="m-label">Facility Plant Capital Value:</span>
<span class="m-val font-mono">${fmtCurrency(facilityValuation)}</span> <span class="m-val font-mono">${fmtCurrency(facilityValuation)}</span>
</div> </div>
</div> </div>
</div> </div>
<!-- Engineering & Velocity Block --> <!-- 2. Engineering & Production Dynamics Block -->
<div class="matrix-card"> <div class="matrix-card">
<div class="matrix-header"> <div class="matrix-header">
<iconify-icon icon="lucide:gauge"></iconify-icon> Production Dynamics <iconify-icon icon="lucide:gauge"></iconify-icon> Industrial Dynamics & Velocity
</div> </div>
<div class="matrix-rows"> <div class="matrix-rows">
<div class="matrix-row"> <div class="matrix-row">
<span class="m-label">Active Industrial Lines:</span> <span class="m-label">Operating Industrial Lines:</span>
<span class="m-val font-mono" id="det-lines">${pState.lines} lines (${nextM - pState.lines} to 2x)</span> <span class="m-val font-mono" id="det-lines">${pState.lines} lines (${nextM - pState.lines} to 2x)</span>
</div> </div>
<div class="matrix-row"> <div class="matrix-row">
<span class="m-label">Base Cycle Duration:</span> <span class="m-label">Effective Cycle Time:</span>
<span class="m-val font-mono">${product.baseCycleTime.toFixed(1)}s (raw)</span> <span class="m-val font-mono" id="det-cycle-time">${effectiveCycleTime.toFixed(2)}s <span class="text-muted">(${product.baseCycleTime.toFixed(1)}s raw)</span></span>
</div> </div>
<div class="matrix-row"> <div class="matrix-row">
<span class="m-label">Milestone Speed Multiplier:</span> <span class="m-label">Milestone Speed Multiplier:</span>
<span class="m-val font-mono text-profit">${milestoneMult}x Speed</span> <span class="m-val font-mono text-profit">${milestoneMult}x Speed Boost</span>
</div> </div>
<div class="matrix-row"> <div class="matrix-row">
<span class="m-label">Batches Processed / Sec:</span> <span class="m-label">Batches Processed / Sec:</span>
@ -226,37 +274,41 @@ export function mountUnitDetailView(container, state, onAction, engine) {
<span class="m-val font-mono text-profit" id="det-prod-rate">${fmtNum(unitsPerSec, 1)} units/s</span> <span class="m-val font-mono text-profit" id="det-prod-rate">${fmtNum(unitsPerSec, 1)} units/s</span>
</div> </div>
<div class="matrix-row"> <div class="matrix-row">
<span class="m-label">Hourly Volume Output:</span> <span class="m-label">Projected Hourly Output:</span>
<span class="m-val font-mono" id="det-prod-hour">${fmtNum(unitsPerHour, 0)} ${product.unit}/h</span> <span class="m-val font-mono" id="det-prod-hour">${fmtNum(unitsPerHour, 0)} ${product.unit}/h</span>
</div> </div>
</div> </div>
</div> </div>
<!-- Net Supply Chain Flow Block --> <!-- 3. Supply Chain Flow Balance Block -->
<div class="matrix-card"> <div class="matrix-card">
<div class="matrix-header"> <div class="matrix-header">
<iconify-icon icon="lucide:git-pull-request"></iconify-icon> Supply Chain Balance Formula <iconify-icon icon="lucide:git-pull-request"></iconify-icon> Supply Chain Flow Balance
</div> </div>
<div class="matrix-rows"> <div class="matrix-rows">
<div class="matrix-row"> <div class="matrix-row">
<span class="m-label">Factory Production Rate:</span> <span class="m-label">Plant Production Rate:</span>
<span class="m-val font-mono text-profit" id="det-flow-prod">+${fmtNum(unitsPerSec, 1)}/s</span> <span class="m-val font-mono text-profit" id="det-flow-prod">+${fmtNum(unitsPerSec, 1)}/s</span>
</div> </div>
<div class="matrix-row"> <div class="matrix-row">
<span class="m-label">Downstream Consumption:</span> <span class="m-label">Downstream Consumption Pull:</span>
<span class="m-val font-mono text-warning" id="det-flow-consumed">-${fmtNum(netFlow.consumedRate, 1)}/s</span> <span class="m-val font-mono text-warning" id="det-flow-consumed">-${fmtNum(netFlow.consumedRate, 1)}/s</span>
</div> </div>
<div class="matrix-row"> <div class="matrix-row">
<span class="m-label">Logistics Wholesale Sales:</span> <span class="m-label">Wholesale Logistics Sales:</span>
<span class="m-val font-mono text-warning" id="det-flow-sold">-${fmtNum(netFlow.soldRate, 1)}/s</span> <span class="m-val font-mono text-warning" id="det-flow-sold">-${fmtNum(netFlow.soldRate, 1)}/s</span>
</div> </div>
<div class="matrix-divider"></div> <div class="matrix-divider"></div>
<div class="matrix-row highlight-row"> <div class="matrix-row highlight-row">
<span class="m-label">Net Inventory Flow (Δ):</span> <span class="m-label">Net Inventory Drift (Δ/s):</span>
<span class="m-val font-mono ${netFlow.netRate < -0.05 ? 'text-warning' : 'text-profit'}" id="det-flow-net"> <span class="m-val font-mono ${netFlow.netRate < -0.05 ? 'text-warning' : 'text-profit'}" id="det-flow-net">
${netFlow.netRate > 0.01 ? '+' : ''}${fmtNum(netFlow.netRate, 1)}/s ${netFlow.netRate > 0.01 ? '+' : ''}${fmtNum(netFlow.netRate, 1)}/s
</span> </span>
</div> </div>
<div class="matrix-row">
<span class="m-label">Current Operating Throttle:</span>
<span class="m-val font-mono" id="det-throttle-stat">${Math.round(currentThrottle * 100)}% (${fmtNum(effectiveLines, 1)} lines active)</span>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -553,6 +605,51 @@ export function updateUnitDetailView(container, state, engine) {
elFlowNet.className = `m-val font-mono ${netFlow.netRate < -0.05 ? 'text-warning' : 'text-profit'}`; elFlowNet.className = `m-val font-mono ${netFlow.netRate < -0.05 ? 'text-warning' : 'text-profit'}`;
} }
// Hero Product Stock and Value
const elHeroStock = document.getElementById('det-hero-stock');
if (elHeroStock) {
elHeroStock.textContent = `${fmtNum(pState.stock || 0, 0)} ${product.unit}`;
}
const elHeroStockVal = document.getElementById('det-hero-stock-val');
if (elHeroStockVal) {
elHeroStockVal.textContent = fmtCurrency((pState.stock || 0) * product.basePrice);
}
// Enterprise Revenue Share
const totalCompanyCashRate = engine?.cashRate || 0;
const companyRevShare = totalCompanyCashRate > 0.001
? Math.min(100, Math.max(0, (rates.revRate / totalCompanyCashRate) * 100))
: 0;
const elRevShare = document.getElementById('det-rev-share');
if (elRevShare) {
elRevShare.textContent = `${fmtNum(companyRevShare, 1)}%`;
}
// Bottleneck & Raw Material Reserves Assessment
let bottleneckStatus = 'optimal';
let bottleneckLabel = 'Optimal Input Reserves';
if (product.inputs && product.inputs.length > 0) {
for (const inp of product.inputs) {
const parentState = state.products[inp.id];
const neededPerBatch = inp.amount * (pState.lines || 1);
if (!parentState || (parentState.stock || 0) < 1) {
bottleneckStatus = 'bottleneck';
bottleneckLabel = 'Starved: Zero Raw Materials';
break;
} else if ((parentState.stock || 0) < neededPerBatch) {
bottleneckStatus = 'low';
bottleneckLabel = 'Low Upstream Reserves';
}
}
}
const elBottleneckBadge = document.getElementById('det-bottleneck-badge');
const elBottleneckText = document.getElementById('det-bottleneck-text');
if (elBottleneckBadge && elBottleneckText) {
elBottleneckBadge.className = `risk-badge risk-${bottleneckStatus}`;
elBottleneckText.textContent = bottleneckLabel;
}
// Liquidation Spot Values // Liquidation Spot Values
const totalLiq = (pState.stock || 0) * product.basePrice; const totalLiq = (pState.stock || 0) * product.basePrice;
const elLiqAll = document.getElementById('det-liq-all-val'); const elLiqAll = document.getElementById('det-liq-all-val');