feat(dossier): enhance facility detail view with Capitalism II telemetry, product hero showcase, and clean in-universe styling
BIN
frontend/assets/images/facilities/circuit_boards_facility.jpg
Normal file
|
After Width: | Height: | Size: 496 KiB |
BIN
frontend/assets/images/facilities/coal_facility.jpg
Normal file
|
After Width: | Height: | Size: 520 KiB |
BIN
frontend/assets/images/facilities/commercial_trucks_facility.jpg
Normal file
|
After Width: | Height: | Size: 472 KiB |
BIN
frontend/assets/images/facilities/crude_oil_facility.jpg
Normal file
|
After Width: | Height: | Size: 469 KiB |
BIN
frontend/assets/images/facilities/engines_facility.jpg
Normal file
|
After Width: | Height: | Size: 582 KiB |
BIN
frontend/assets/images/facilities/flour_facility.jpg
Normal file
|
After Width: | Height: | Size: 496 KiB |
BIN
frontend/assets/images/facilities/fresh_bread_facility.jpg
Normal file
|
After Width: | Height: | Size: 619 KiB |
BIN
frontend/assets/images/facilities/furniture_facility.jpg
Normal file
|
After Width: | Height: | Size: 476 KiB |
BIN
frontend/assets/images/facilities/iron_ore_facility.jpg
Normal file
|
After Width: | Height: | Size: 562 KiB |
BIN
frontend/assets/images/facilities/metal_frames_facility.jpg
Normal file
|
After Width: | Height: | Size: 502 KiB |
BIN
frontend/assets/images/facilities/microchips_facility.jpg
Normal file
|
After Width: | Height: | Size: 585 KiB |
BIN
frontend/assets/images/facilities/packaged_meals_facility.jpg
Normal file
|
After Width: | Height: | Size: 470 KiB |
BIN
frontend/assets/images/facilities/paper_facility.jpg
Normal file
|
After Width: | Height: | Size: 551 KiB |
BIN
frontend/assets/images/facilities/planks_facility.jpg
Normal file
|
After Width: | Height: | Size: 602 KiB |
BIN
frontend/assets/images/facilities/polymers_facility.jpg
Normal file
|
After Width: | Height: | Size: 591 KiB |
BIN
frontend/assets/images/facilities/refined_sugar_facility.jpg
Normal file
|
After Width: | Height: | Size: 620 KiB |
BIN
frontend/assets/images/facilities/silica_sand_facility.jpg
Normal file
|
After Width: | Height: | Size: 554 KiB |
BIN
frontend/assets/images/facilities/silicon_facility.jpg
Normal file
|
After Width: | Height: | Size: 582 KiB |
BIN
frontend/assets/images/facilities/smartphones_facility.jpg
Normal file
|
After Width: | Height: | Size: 477 KiB |
BIN
frontend/assets/images/facilities/steel_ingots_facility.jpg
Normal file
|
After Width: | Height: | Size: 539 KiB |
BIN
frontend/assets/images/facilities/sugar_beet_facility.jpg
Normal file
|
After Width: | Height: | Size: 629 KiB |
BIN
frontend/assets/images/facilities/timber_facility.jpg
Normal file
|
After Width: | Height: | Size: 556 KiB |
BIN
frontend/assets/images/facilities/wheat_facility.jpg
Normal file
|
After Width: | Height: | Size: 586 KiB |
BIN
frontend/assets/images/products/circuit_boards_product.jpg
Normal file
|
After Width: | Height: | Size: 488 KiB |
BIN
frontend/assets/images/products/coal_product.jpg
Normal file
|
After Width: | Height: | Size: 396 KiB |
BIN
frontend/assets/images/products/commercial_trucks_product.jpg
Normal file
|
After Width: | Height: | Size: 362 KiB |
BIN
frontend/assets/images/products/crude_oil_product.jpg
Normal file
|
After Width: | Height: | Size: 341 KiB |
BIN
frontend/assets/images/products/engines_product.jpg
Normal file
|
After Width: | Height: | Size: 371 KiB |
BIN
frontend/assets/images/products/flour_product.jpg
Normal file
|
After Width: | Height: | Size: 402 KiB |
BIN
frontend/assets/images/products/fresh_bread_product.jpg
Normal file
|
After Width: | Height: | Size: 548 KiB |
BIN
frontend/assets/images/products/furniture_product.jpg
Normal file
|
After Width: | Height: | Size: 282 KiB |
BIN
frontend/assets/images/products/iron_ore_product.jpg
Normal file
|
After Width: | Height: | Size: 519 KiB |
BIN
frontend/assets/images/products/metal_frames_product.jpg
Normal file
|
After Width: | Height: | Size: 317 KiB |
BIN
frontend/assets/images/products/microchips_product.jpg
Normal file
|
After Width: | Height: | Size: 419 KiB |
BIN
frontend/assets/images/products/packaged_meals_product.jpg
Normal file
|
After Width: | Height: | Size: 452 KiB |
BIN
frontend/assets/images/products/paper_product.jpg
Normal file
|
After Width: | Height: | Size: 248 KiB |
BIN
frontend/assets/images/products/planks_product.jpg
Normal file
|
After Width: | Height: | Size: 425 KiB |
BIN
frontend/assets/images/products/polymers_product.jpg
Normal file
|
After Width: | Height: | Size: 334 KiB |
BIN
frontend/assets/images/products/refined_sugar_product.jpg
Normal file
|
After Width: | Height: | Size: 382 KiB |
BIN
frontend/assets/images/products/silica_sand_product.jpg
Normal file
|
After Width: | Height: | Size: 405 KiB |
BIN
frontend/assets/images/products/silicon_product.jpg
Normal file
|
After Width: | Height: | Size: 488 KiB |
BIN
frontend/assets/images/products/smartphones_product.jpg
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
frontend/assets/images/products/steel_ingots_product.jpg
Normal file
|
After Width: | Height: | Size: 418 KiB |
BIN
frontend/assets/images/products/sugar_beet_product.jpg
Normal file
|
After Width: | Height: | Size: 510 KiB |
BIN
frontend/assets/images/products/timber_product.jpg
Normal file
|
After Width: | Height: | Size: 496 KiB |
BIN
frontend/assets/images/products/wheat_product.jpg
Normal file
|
After Width: | Height: | Size: 440 KiB |
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 • ${sector?.name}</div>
|
<div class="dossier-pretitle">${product.facilityName} • ${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');
|
||||||
|
|
|
||||||