Design consistency audit fixes: alerts, cards, dark mode, utilities

Alert sweep (113 alerts, 79 files):
  All persistent static banners now carry alert-permanent so the
  layout's 5-second auto-dismiss cannot swallow guidance, warnings,
  or validation errors. Transient dismissible toasts left untouched.

CSS fixes (site.css):
  .card.shadow-sm      — strips rogue border from ~40 drifted cards
  .card-header.bg-white — rebinds to var(--bs-body-bg) so card
                          headers follow dark/light theme correctly
  Typography utilities  — .text-2xs (.68rem), .text-xs (.73rem)
  Token color classes   — .text-ember, .text-ok, .text-bad,
                          .text-warn, .text-cool, .bg-paper-2
  Layout utilities      — .mw-xs/sm/md/lg replace inline max-width
  Comment              — documents text-ember vs text-primary intent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 18:05:29 -04:00
parent f6d457fe0e
commit 328b195127
80 changed files with 603 additions and 561 deletions
@@ -1,4 +1,4 @@
@model PowderCoating.Application.DTOs.Powder.PowderInsightsDashboardDto
@model PowderCoating.Application.DTOs.Powder.PowderInsightsDashboardDto
@{
ViewData["Title"] = "Powder Insights";
ViewData["PageIcon"] = "bi-graph-up";
@@ -11,7 +11,7 @@
</a>
</div>
@* ── KPI cards ── *@
@* ── KPI cards ── *@
<div class="row g-3 mb-4">
<div class="col-6 col-md-3">
<div class="card border-0 shadow-sm h-100">
@@ -49,7 +49,7 @@
</div>
</div>
@* ── Tabs ── *@
@* ── Tabs ── *@
<ul class="nav nav-tabs mb-3" id="insightsTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="forecast-tab" data-bs-toggle="tab" data-bs-target="#forecast" type="button">
@@ -74,7 +74,7 @@
<div class="tab-content">
@* ── Tab 1: Stock Forecast (Layer 2, immediate value) ── *@
@* ── Tab 1: Stock Forecast (Layer 2, immediate value) ── *@
<div class="tab-pane fade show active" id="forecast" role="tabpanel">
@if (!Model.LowStockAlerts.Any())
{
@@ -87,7 +87,7 @@
{
<div class="card border-0 shadow-sm">
<div class="card-header bg-transparent">
<h6 class="mb-0"><i class="bi bi-box-seam me-2"></i>Powder Demand vs. Stock Active Jobs</h6>
<h6 class="mb-0"><i class="bi bi-box-seam me-2"></i>Powder Demand vs. Stock — Active Jobs</h6>
</div>
<div class="table-responsive">
<table class="table table-hover mb-0">
@@ -115,7 +115,7 @@
<td class="text-end fw-semibold">@item.CurrentStockLbs.ToString("0.##") lbs</td>
<td class="text-end">@item.ScheduledDemandLbs.ToString("0.##") lbs</td>
<td class="text-end @(item.ShortfallLbs > 0 ? "text-danger fw-bold" : "text-muted")">
@(item.ShortfallLbs > 0 ? $"{item.ShortfallLbs:0.##} lbs" : "")
@(item.ShortfallLbs > 0 ? $"{item.ShortfallLbs:0.##} lbs" : "—")
</td>
<td class="text-center">@item.ActiveJobCount</td>
<td class="text-center">
@@ -141,7 +141,7 @@
}
</div>
@* ── Tab 2: Coverage Efficiency (Layer 2) ── *@
@* ── Tab 2: Coverage Efficiency (Layer 2) ── *@
<div class="tab-pane fade" id="efficiency" role="tabpanel">
@if (!readiness.IsLayer2Ready)
{
@@ -157,7 +157,7 @@
else if (!Model.EfficiencyBySku.Any())
{
<div class="text-center py-5 text-muted">
<p>No efficiency data yet record actual powder usage on completed jobs to see this chart.</p>
<p>No efficiency data yet — record actual powder usage on completed jobs to see this chart.</p>
</div>
}
else
@@ -188,11 +188,11 @@
<strong>@eff.Name</strong>
@if (!string.IsNullOrEmpty(eff.ColorName))
{
<br /><small class="text-muted">@eff.ColorName · @eff.Manufacturer</small>
<br /><small class="text-muted">@eff.ColorName · @eff.Manufacturer</small>
}
@if (!eff.HasEnoughData)
{
<br /><small class="text-muted fst-italic">Low confidence need 5+ samples</small>
<br /><small class="text-muted fst-italic">Low confidence — need 5+ samples</small>
}
</td>
<td class="text-end">@eff.CatalogCoverageSqFtPerLb.ToString("0.#") sq ft/lb</td>
@@ -214,7 +214,7 @@
}
</div>
@* ── Tab 3: Predictive (Layer 3, gated) ── *@
@* ── Tab 3: Predictive (Layer 3, gated) ── *@
<div class="tab-pane fade" id="predictive" role="tabpanel">
@if (!readiness.IsLayer3Ready)
{
@@ -235,12 +235,12 @@
<strong>@readiness.JobsWithActualData</strong> of <strong>@readiness.Layer3MinJobs</strong> jobs recorded
(@readiness.Layer3ProgressPercent%)
</p>
<div class="alert alert-info text-start">
<div class="alert alert-info alert-permanent text-start">
<h6 class="alert-heading"><i class="bi bi-lightbulb me-2"></i>What unlocks here</h6>
<ul class="mb-0 small">
<li><strong>Smart reorder suggestions</strong> quantity recommendations based on your actual usage history + scheduled job pipeline</li>
<li><strong>Waste pattern detection</strong> identifies jobs and powder types that consistently over-consume</li>
<li><strong>Per-powder efficiency corrections</strong> suggests updating coverage defaults based on real data</li>
<li><strong>Smart reorder suggestions</strong> — quantity recommendations based on your actual usage history + scheduled job pipeline</li>
<li><strong>Waste pattern detection</strong> — identifies jobs and powder types that consistently over-consume</li>
<li><strong>Per-powder efficiency corrections</strong> — suggests updating coverage defaults based on real data</li>
</ul>
</div>
</div>
@@ -258,7 +258,7 @@
</div>
@if (!Model.ReorderSuggestions.Any())
{
<div class="card-body text-muted text-center py-4">No reorder suggestions stock levels look good for upcoming pipeline.</div>
<div class="card-body text-muted text-center py-4">No reorder suggestions — stock levels look good for upcoming pipeline.</div>
}
else
{
@@ -284,7 +284,7 @@
<strong>@s.Name</strong>
@if (!string.IsNullOrEmpty(s.ColorName))
{
<br /><small class="text-muted">@s.ColorName · @s.Manufacturer</small>
<br /><small class="text-muted">@s.ColorName · @s.Manufacturer</small>
}
</td>
<td class="text-end">@s.CurrentStockLbs.ToString("0.#") lbs</td>
@@ -311,7 +311,7 @@
@* Waste Patterns *@
<div class="card border-0 shadow-sm">
<div class="card-header bg-transparent">
<h6 class="mb-0"><i class="bi bi-exclamation-triangle me-2 text-warning"></i>Waste Patterns <small class="text-muted fw-normal"> coats that used &gt;20% more than estimated</small></h6>
<h6 class="mb-0"><i class="bi bi-exclamation-triangle me-2 text-warning"></i>Waste Patterns <small class="text-muted fw-normal">— coats that used &gt;20% more than estimated</small></h6>
</div>
@if (!Model.WastePatterns.Any())
{
@@ -344,7 +344,7 @@
<br /><small class="text-muted">@w.CoatName</small>
</td>
<td class="text-muted small">@(w.InventoryItemName ?? "Custom")</td>
<td>@(w.Complexity ?? "")</td>
<td>@(w.Complexity ?? "—")</td>
<td class="text-end">@w.EstimatedLbs.ToString("0.##") lbs</td>
<td class="text-end">@w.ActualLbs.ToString("0.##") lbs</td>
<td class="text-end text-danger fw-bold">+@w.OveragePct.ToString("0.#")%</td>