diff --git a/pumpingStation.html b/pumpingStation.html
index 36e6c05..216f660 100644
--- a/pumpingStation.html
+++ b/pumpingStation.html
@@ -220,31 +220,65 @@
const dryLvl = (refLow != null && dryPct != null) ? refLow * (1 + dryPct / 100) : null;
const ovfLvl = (ovf != null && ovfPct != null) ? ovf * (ovfPct / 100) : null;
- // Build the right-column items. basinHeight is pinned at the
- // tank rim; others are sorted by their proportional y and then
- // pushed apart so every dashed line gets a minimum vertical
- // gap for readability. The diagram is a schematic ordered
- // list, not a strictly to-scale rendering.
+ // Right-column stack. TWO anchors: basinHeight pinned at the
+ // tank rim (top) and outflowLevel pinned at its proportional y
+ // (bottom). Everything between is nudged to maintain a minimum
+ // vertical gap via two passes — top-down from the rim, then
+ // bottom-up from the outlet — so the dashed lines keep their
+ // value-order and outlet stays near the floor where it belongs.
const items = [
- { id: 'basinHeight', yIdeal: DIAG.topY, pinned: true },
+ { id: 'basinHeight', yIdeal: DIAG.topY, pinned: true },
{ id: 'overflowLevel', yIdeal: yForLevel(fNum('overflowLevel'), basinH) },
{ id: 'maxLevel', yIdeal: yForLevel(fNum('maxLevel'), basinH) },
{ id: 'startLevel', yIdeal: yForLevel(fNum('startLevel'), basinH) },
{ id: 'minLevel', yIdeal: yForLevel(fNum('minLevel'), basinH) },
{ id: 'dryRunLevel', yIdeal: yForLevel(dryLvl, basinH) },
- { id: 'outflowLevel', yIdeal: yForLevel(fNum('outflowLevel'), basinH) },
+ { id: 'outflowLevel', yIdeal: yForLevel(fNum('outflowLevel'), basinH), pinned: true },
].filter(it => it.yIdeal != null);
const GAP = 36;
items.sort((a, b) => a.yIdeal - b.yIdeal);
- let prev = -Infinity;
- for (const it of items) {
- if (it.pinned) { it.y = it.yIdeal; prev = it.y; continue; }
- it.y = Math.max(it.yIdeal, prev + GAP);
- prev = it.y;
+ for (const it of items) it.y = it.yIdeal;
+ // Pass 1: top-down — push DOWN to maintain GAP; pinned items don't move
+ for (let i = 1; i < items.length; i++) {
+ if (items[i].pinned) continue;
+ items[i].y = Math.max(items[i].y, items[i - 1].y + GAP);
+ }
+ // Pass 2: bottom-up — push UP so outflow's pin propagates up the stack
+ for (let i = items.length - 2; i >= 0; i--) {
+ if (items[i].pinned) continue;
+ items[i].y = Math.min(items[i].y, items[i + 1].y - GAP);
}
for (const it of items) placeItem(it.id, it.y);
+ // Zone labels between adjacent thresholds (italic, centered).
+ // Hidden if either bracketing threshold is missing, or the gap
+ // is too small to read (< 14 px).
+ const placeZone = (zoneId, topId, botId) => {
+ const el = document.getElementById(`ps-zone-${zoneId}`);
+ if (!el) return;
+ const top = items.find(it => it.id === topId);
+ const bot = items.find(it => it.id === botId);
+ if (!top || !bot || (bot.y - top.y) < 14) {
+ el.setAttribute('visibility', 'hidden'); return;
+ }
+ el.setAttribute('y', (top.y + bot.y) / 2 + 3);
+ el.setAttribute('visibility', 'visible');
+ };
+ placeZone('spare', 'overflowLevel', 'maxLevel');
+ placeZone('sewage', 'maxLevel', 'startLevel');
+ placeZone('buffer1', 'startLevel', 'minLevel');
+ placeZone('buffer2', 'minLevel', 'dryRunLevel');
+ // "Dead volume" sits inside the blue band between outflowLevel and the floor
+ const outflowPinned = items.find(it => it.id === 'outflowLevel');
+ const deadLbl = document.getElementById('ps-zone-dead');
+ if (deadLbl && outflowPinned && (DIAG.botY - outflowPinned.y) > 14) {
+ deadLbl.setAttribute('y', (outflowPinned.y + DIAG.botY) / 2 + 3);
+ deadLbl.setAttribute('visibility', 'visible');
+ } else if (deadLbl) {
+ deadLbl.setAttribute('visibility', 'hidden');
+ }
+
// Inlet arrow — sole item on the left, no stacking concerns
const inflowY = yForLevel(fNum('inflowLevel'), basinH);
if (inflowY != null) {
@@ -385,6 +419,20 @@