diff --git a/modules/rflows/components/folk-flow-river.ts b/modules/rflows/components/folk-flow-river.ts
index 828c356..2f24e1a 100644
--- a/modules/rflows/components/folk-flow-river.ts
+++ b/modules/rflows/components/folk-flow-river.ts
@@ -121,6 +121,9 @@ function computeLayout(nodes: FlowNode[]): RiverLayout {
const funnelLayouts: FunnelLayout[] = [];
+ // Find max desiredOutflow to normalize pipe widths
+ const maxOutflow = Math.max(1, ...funnelNodes.map((n) => (n.data as FunnelNodeData).desiredOutflow || (n.data as FunnelNodeData).inflowRate || 1));
+
for (let layer = 0; layer <= maxLayer; layer++) {
const layerNodes = layerGroups.get(layer) || [];
const layerY = funnelStartY + layer * (LAYER_HEIGHT + WATERFALL_HEIGHT + GAP);
@@ -128,10 +131,13 @@ function computeLayout(nodes: FlowNode[]): RiverLayout {
layerNodes.forEach((n, i) => {
const data = n.data as FunnelNodeData;
- const fillRatio = Math.min(1, data.currentValue / (data.maxCapacity || 1));
- // Pipe width = capacity (always full size), inner flow = fillRatio
- const capacityRatio = Math.min(1, (data.maxCapacity || 1) / 90000); // normalize to largest typical capacity
- const riverWidth = MIN_RIVER_WIDTH + capacityRatio * (MAX_RIVER_WIDTH - MIN_RIVER_WIDTH);
+ const outflow = data.desiredOutflow || data.inflowRate || 1;
+ const inflow = data.inflowRate || 0;
+ // Pipe width = desiredOutflow (what they need)
+ const outflowRatio = Math.min(1, outflow / maxOutflow);
+ const riverWidth = MIN_RIVER_WIDTH + outflowRatio * (MAX_RIVER_WIDTH - MIN_RIVER_WIDTH);
+ // Fill ratio = inflowRate / desiredOutflow (how funded they are)
+ const fillRatio = Math.min(1, inflow / (outflow || 1));
const x = -totalWidth / 2 + i * (SEGMENT_LENGTH + GAP * 2);
const status: "healthy" | "overflow" | "critical" =
data.currentValue > data.maxThreshold ? "overflow" :
@@ -335,34 +341,38 @@ function renderFunnel(f: FunnelLayout): string {
const threshold = f.data.sufficientThreshold ?? f.data.maxThreshold;
const isSufficient = f.sufficiency === "sufficient" || f.sufficiency === "abundant";
- // Pipe capacity = full riverWidth (outer boundary)
- // Active flow = inner height proportional to fillRatio
+ // Pipe = desiredOutflow (what they need), Flow = inflowRate (what they get)
+ const outflow = f.data.desiredOutflow || f.data.inflowRate || 1;
+ const inflow = f.data.inflowRate || 0;
const flowHeight = Math.max(2, f.riverWidth * f.fillRatio);
- const flowY = f.y + (f.riverWidth - flowHeight); // flow fills from bottom
+ const flowY = f.y + (f.riverWidth - flowHeight) / 2; // center the flow vertically
+ const fundingPct = Math.round(f.fillRatio * 100);
+ const underfunded = f.fillRatio < 0.95;
+ const flowColor = underfunded ? "#ef4444" : colors[0]; // red tint when underfunded
return `
-
-
-
+
+
+
-
-
-
+
+
+
${isSufficient ? `` : ""}
-
+
-
+
- ${f.fillRatio > 0.02 ? [0, 1, 2].map((i) => ``).join("") : ""}
+ ${f.fillRatio > 0.02 ? [0, 1, 2].map((i) => ``).join("") : ""}
${esc(f.label)}
- $${Math.floor(f.data.currentValue).toLocaleString()} / $${Math.floor(threshold).toLocaleString()} ${isSufficient ? "✨" : ""}
+ $${Math.floor(inflow).toLocaleString()} → $${Math.floor(outflow).toLocaleString()}/mo ${underfunded ? `(${fundingPct}%)` : "✨"}
- `;
+ `;
}
function renderOutcome(o: OutcomeLayout): string {