diff --git a/frontend/src/components/WaterSoftenerDiagram.tsx b/frontend/src/components/WaterSoftenerDiagram.tsx
index 491259d..b31c995 100644
--- a/frontend/src/components/WaterSoftenerDiagram.tsx
+++ b/frontend/src/components/WaterSoftenerDiagram.tsx
@@ -1,4 +1,4 @@
-import { RefreshCw, ChevronsRight, ChevronsLeft } from 'lucide-react'
+import { RefreshCw, ChevronsRight, ChevronsLeft, ChevronsDown } from 'lucide-react'
import type { AssetStatus, TelemetryField } from '../types'
function fieldByKey(latest: TelemetryField[], key: string | null): TelemetryField | undefined {
@@ -44,6 +44,10 @@ function Tank({ label, pct, valueLabel, variant, regenerating }: {
regenerating: boolean
}) {
const clamped = pct === null ? 0 : Math.max(0, Math.min(100, pct))
+ // Water is passing through whichever tank is actually in service right now
+ // (or the single tank, when it's not offline regenerating) — shown as
+ // pulsing down-chevrons under the % reading.
+ const passingWater = !regenerating && variant !== 'standby'
return (
{variant === 'active' && (
@@ -54,7 +58,15 @@ function Tank({ label, pct, valueLabel, variant, regenerating }: {
)}
-
{pct === null ? '—' : `${Math.round(pct)}%`}
+
+
{pct === null ? '—' : `${Math.round(pct)}%`}
+ {passingWater && (
+
+
+
+
+ )}
+
{label}
{valueLabel}
@@ -105,7 +117,6 @@ export default function WaterSoftenerDiagram({ asset }: { asset: AssetStatus })
-
)
}
diff --git a/frontend/src/index.css b/frontend/src/index.css
index f84a2da..0149665 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -249,7 +249,6 @@ html, body, #root { height: 100%; margin: 0; font-size: 14px; }
.softener-flow-left svg { margin-right: -6px; }
.softener-flow-active svg { color: var(--app-primary); animation: softener-flow-pulse 1.1s ease-in-out infinite; }
.softener-flow-active svg:nth-child(2) { animation-delay: .15s; }
-.softener-flow-active svg:nth-child(3) { animation-delay: .3s; }
@keyframes softener-flow-pulse {
0%, 100% { opacity: .25; }
50% { opacity: 1; }
@@ -304,14 +303,29 @@ html, body, #root { height: 100%; margin: 0; font-size: 14px; }
.softener-fill-warning { background: var(--sev-warning); }
.softener-fill-critical { background: var(--sev-critical); }
.softener-fill-unknown { background: var(--card-border); height: 0 !important; }
-.softener-tank-pct {
+.softener-tank-content {
position: relative;
z-index: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 2px;
+}
+.softener-tank-pct {
font-size: 12px;
font-weight: 700;
color: var(--text-dark);
text-shadow: 0 1px 2px rgba(255,255,255,.6);
}
+.softener-tank-water {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ color: var(--app-primary);
+ filter: drop-shadow(0 1px 1px rgba(255,255,255,.6));
+}
+.softener-tank-water svg { margin-top: -4px; animation: softener-flow-pulse 1.1s ease-in-out infinite; }
+.softener-tank-water svg:nth-child(2) { animation-delay: .2s; }
.softener-tank-label { font-size: 11px; font-weight: 600; color: var(--text-dark); margin-top: 6px; }
.softener-tank-value { font-size: 10px; color: var(--text-mid); }