feat(rflows): richer 4-layer demo with overflow cascading down and back up
Redesign both demo presets with deeper multi-layer funnel networks: - demoNodes: 3 sources → treasury → 3 domains → 5 teams → 11 outcomes - simDemoNodes: 2 sources → treasury → 4 domains → 4 sub-teams → 10 outcomes - Overflow paths create visible bidirectional flow (down and back up) - Wider spacing between nodes for breathing room - River view: larger layout constants (layer height, gaps, segment length) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
26c1e72bb1
commit
ef60e29da3
|
|
@ -29,16 +29,16 @@ interface BranchLayout { sourceId: string; targetId: string; percentage: number;
|
|||
|
||||
// ─── Constants ───────────────────────────────────────────
|
||||
|
||||
const LAYER_HEIGHT = 160;
|
||||
const WATERFALL_HEIGHT = 120;
|
||||
const GAP = 40;
|
||||
const LAYER_HEIGHT = 180;
|
||||
const WATERFALL_HEIGHT = 140;
|
||||
const GAP = 50;
|
||||
const MIN_RIVER_WIDTH = 24;
|
||||
const MAX_RIVER_WIDTH = 100;
|
||||
const MIN_WATERFALL_WIDTH = 4;
|
||||
const SEGMENT_LENGTH = 200;
|
||||
const POOL_WIDTH = 100;
|
||||
const POOL_HEIGHT = 60;
|
||||
const SOURCE_HEIGHT = 40;
|
||||
const SEGMENT_LENGTH = 220;
|
||||
const POOL_WIDTH = 110;
|
||||
const POOL_HEIGHT = 65;
|
||||
const SOURCE_HEIGHT = 45;
|
||||
|
||||
const COLORS = {
|
||||
sourceWaterfall: "#10b981",
|
||||
|
|
@ -520,7 +520,7 @@ class FolkFlowRiver extends HTMLElement {
|
|||
this.shadow.innerHTML = `
|
||||
<style>
|
||||
:host { display: block; }
|
||||
.container { position: relative; overflow: auto; background: ${COLORS.bg}; border-radius: 12px; border: 1px solid var(--rs-bg-surface-raised); max-height: 85vh; cursor: grab; }
|
||||
.container { position: relative; overflow: auto; background: ${COLORS.bg}; border-radius: 12px; border: 1px solid var(--rs-bg-surface-raised); max-height: 90vh; cursor: grab; }
|
||||
.container.dragging { cursor: grabbing; user-select: none; }
|
||||
svg { display: block; }
|
||||
.controls { position: absolute; top: 12px; left: 12px; display: flex; gap: 8px; }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
/**
|
||||
* Demo presets — BCRG Community Flow.
|
||||
*
|
||||
* 2 sources → BCRG central funnel → 5 person funnels (Alice–Eve) → 11 outcomes.
|
||||
* 3 sources → BCRG treasury → 3 domain funnels → 5 team funnels → 12 outcomes.
|
||||
* Overflow paths cascade down AND back up through the network.
|
||||
*/
|
||||
|
||||
import type { FlowNode, FunnelNodeData, OutcomeNodeData, SourceNodeData } from "./types";
|
||||
|
|
@ -9,148 +10,189 @@ import type { FlowNode, FunnelNodeData, OutcomeNodeData, SourceNodeData } from "
|
|||
export const SPENDING_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#06b6d4", "#10b981", "#6366f1"];
|
||||
export const OVERFLOW_COLORS = ["#f59e0b", "#ef4444", "#f97316", "#eab308", "#dc2626", "#ea580c"];
|
||||
|
||||
// Node width is 260px. Spacing: 340px horizontal, 550px vertical between funnel layers.
|
||||
|
||||
export const demoNodes: FlowNode[] = [
|
||||
// ── Sources (Y=-300) ──
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Layer 0 — Sources (Y=-400)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
{
|
||||
id: "source-a", type: "source", position: { x: 480, y: -300 },
|
||||
id: "source-grants", type: "source", position: { x: 200, y: -400 },
|
||||
data: {
|
||||
label: "Grants & Donations", flowRate: 7500, sourceType: "card",
|
||||
label: "Grants & Donations", flowRate: 8000, sourceType: "card",
|
||||
targetAllocations: [{ targetId: "bcrg", percentage: 100, color: "#10b981" }],
|
||||
} as SourceNodeData,
|
||||
},
|
||||
{
|
||||
id: "source-b", type: "source", position: { x: 900, y: -300 },
|
||||
id: "source-membership", type: "source", position: { x: 620, y: -400 },
|
||||
data: {
|
||||
label: "Membership Fees", flowRate: 7500, sourceType: "card",
|
||||
label: "Membership Fees", flowRate: 5000, sourceType: "card",
|
||||
targetAllocations: [{ targetId: "bcrg", percentage: 100, color: "#10b981" }],
|
||||
} as SourceNodeData,
|
||||
},
|
||||
|
||||
// ── BCRG central funnel (Y=0) ──
|
||||
{
|
||||
id: "bcrg", type: "funnel", position: { x: 660, y: 0 },
|
||||
id: "source-services", type: "source", position: { x: 1040, y: -400 },
|
||||
data: {
|
||||
label: "BCRG", currentValue: 0, desiredOutflow: 5000,
|
||||
minThreshold: 5000, sufficientThreshold: 20000, maxThreshold: 30000,
|
||||
maxCapacity: 45000, inflowRate: 15000, dynamicOverflow: true,
|
||||
overflowAllocations: [],
|
||||
label: "Service Revenue", flowRate: 7000, sourceType: "card",
|
||||
targetAllocations: [{ targetId: "bcrg", percentage: 100, color: "#06b6d4" }],
|
||||
} as SourceNodeData,
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Layer 1 — Central Treasury (Y=0)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
{
|
||||
id: "bcrg", type: "funnel", position: { x: 560, y: 0 },
|
||||
data: {
|
||||
label: "BCRG Treasury", currentValue: 0, desiredOutflow: 6000,
|
||||
minThreshold: 6000, sufficientThreshold: 25000, maxThreshold: 35000,
|
||||
maxCapacity: 50000, inflowRate: 20000, dynamicOverflow: true,
|
||||
overflowAllocations: [
|
||||
{ targetId: "growth", percentage: 100, color: OVERFLOW_COLORS[0] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "alice", percentage: 20, color: SPENDING_COLORS[0] },
|
||||
{ targetId: "bob", percentage: 20, color: SPENDING_COLORS[1] },
|
||||
{ targetId: "carol", percentage: 20, color: SPENDING_COLORS[2] },
|
||||
{ targetId: "dave", percentage: 20, color: SPENDING_COLORS[3] },
|
||||
{ targetId: "eve", percentage: 20, color: SPENDING_COLORS[4] },
|
||||
{ targetId: "programs", percentage: 40, color: SPENDING_COLORS[0] },
|
||||
{ targetId: "operations", percentage: 35, color: SPENDING_COLORS[1] },
|
||||
{ targetId: "growth", percentage: 25, color: SPENDING_COLORS[2] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
|
||||
// ── Person funnels (Y=400) ──
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Layer 2 — Domain Funnels (Y=600)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
{
|
||||
id: "alice", type: "funnel", position: { x: 80, y: 400 },
|
||||
id: "programs", type: "funnel", position: { x: 100, y: 600 },
|
||||
data: {
|
||||
label: "Alice", currentValue: 0, desiredOutflow: 1000,
|
||||
minThreshold: 1000, sufficientThreshold: 4000, maxThreshold: 6000,
|
||||
maxCapacity: 9000, inflowRate: 3000,
|
||||
overflowAllocations: [],
|
||||
label: "Programs", currentValue: 0, desiredOutflow: 2500,
|
||||
minThreshold: 2500, sufficientThreshold: 10000, maxThreshold: 15000,
|
||||
maxCapacity: 22000, inflowRate: 0, dynamicOverflow: true,
|
||||
overflowAllocations: [
|
||||
{ targetId: "operations", percentage: 60, color: OVERFLOW_COLORS[1] },
|
||||
{ targetId: "growth", percentage: 40, color: OVERFLOW_COLORS[2] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "alice-comms", percentage: 50, color: SPENDING_COLORS[0] },
|
||||
{ targetId: "alice-events", percentage: 50, color: SPENDING_COLORS[1] },
|
||||
{ targetId: "alice", percentage: 50, color: SPENDING_COLORS[0] },
|
||||
{ targetId: "bob", percentage: 50, color: SPENDING_COLORS[1] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
{
|
||||
id: "bob", type: "funnel", position: { x: 380, y: 400 },
|
||||
id: "operations", type: "funnel", position: { x: 560, y: 600 },
|
||||
data: {
|
||||
label: "Bob", currentValue: 0, desiredOutflow: 800,
|
||||
minThreshold: 800, sufficientThreshold: 3200, maxThreshold: 4800,
|
||||
maxCapacity: 7200, inflowRate: 3000,
|
||||
overflowAllocations: [],
|
||||
label: "Operations", currentValue: 0, desiredOutflow: 2200,
|
||||
minThreshold: 2200, sufficientThreshold: 8800, maxThreshold: 13200,
|
||||
maxCapacity: 20000, inflowRate: 0, dynamicOverflow: true,
|
||||
overflowAllocations: [
|
||||
{ targetId: "growth", percentage: 100, color: OVERFLOW_COLORS[0] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "bob-research", percentage: 60, color: SPENDING_COLORS[0] },
|
||||
{ targetId: "bob-writing", percentage: 40, color: SPENDING_COLORS[1] },
|
||||
{ targetId: "carol", percentage: 55, color: SPENDING_COLORS[2] },
|
||||
{ targetId: "dave", percentage: 45, color: SPENDING_COLORS[3] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
{
|
||||
id: "carol", type: "funnel", position: { x: 680, y: 400 },
|
||||
id: "growth", type: "funnel", position: { x: 1020, y: 600 },
|
||||
data: {
|
||||
label: "Carol", currentValue: 0, desiredOutflow: 1200,
|
||||
label: "Growth", currentValue: 0, desiredOutflow: 1500,
|
||||
minThreshold: 1500, sufficientThreshold: 6000, maxThreshold: 9000,
|
||||
maxCapacity: 14000, inflowRate: 0, dynamicOverflow: true,
|
||||
overflowAllocations: [
|
||||
{ targetId: "bcrg", percentage: 100, color: OVERFLOW_COLORS[3] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "eve", percentage: 100, color: SPENDING_COLORS[4] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Layer 3 — Team Funnels (Y=1250)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
{
|
||||
id: "alice", type: "funnel", position: { x: -100, y: 1250 },
|
||||
data: {
|
||||
label: "Alice — Research", currentValue: 0, desiredOutflow: 1200,
|
||||
minThreshold: 1200, sufficientThreshold: 4800, maxThreshold: 7200,
|
||||
maxCapacity: 10800, inflowRate: 3000,
|
||||
overflowAllocations: [],
|
||||
maxCapacity: 10800, inflowRate: 0,
|
||||
overflowAllocations: [
|
||||
{ targetId: "bob", percentage: 100, color: OVERFLOW_COLORS[4] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "carol-ops", percentage: 50, color: SPENDING_COLORS[0] },
|
||||
{ targetId: "carol-infra", percentage: 50, color: SPENDING_COLORS[1] },
|
||||
{ targetId: "field-research", percentage: 60, color: SPENDING_COLORS[0] },
|
||||
{ targetId: "publications", percentage: 40, color: SPENDING_COLORS[1] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
{
|
||||
id: "dave", type: "funnel", position: { x: 980, y: 400 },
|
||||
id: "bob", type: "funnel", position: { x: 280, y: 1250 },
|
||||
data: {
|
||||
label: "Dave", currentValue: 0, desiredOutflow: 1000,
|
||||
minThreshold: 1000, sufficientThreshold: 4000, maxThreshold: 6000,
|
||||
maxCapacity: 9000, inflowRate: 3000,
|
||||
overflowAllocations: [],
|
||||
label: "Bob — Engineering", currentValue: 0, desiredOutflow: 1300,
|
||||
minThreshold: 1300, sufficientThreshold: 5200, maxThreshold: 7800,
|
||||
maxCapacity: 11700, inflowRate: 0,
|
||||
overflowAllocations: [
|
||||
{ targetId: "programs", percentage: 100, color: OVERFLOW_COLORS[5] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "dave-design", percentage: 45, color: SPENDING_COLORS[0] },
|
||||
{ targetId: "dave-prototypes", percentage: 55, color: SPENDING_COLORS[1] },
|
||||
{ targetId: "infrastructure", percentage: 50, color: SPENDING_COLORS[2] },
|
||||
{ targetId: "prototypes", percentage: 50, color: SPENDING_COLORS[3] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
{
|
||||
id: "eve", type: "funnel", position: { x: 1280, y: 400 },
|
||||
id: "carol", type: "funnel", position: { x: 660, y: 1250 },
|
||||
data: {
|
||||
label: "Eve", currentValue: 0, desiredOutflow: 1000,
|
||||
minThreshold: 1000, sufficientThreshold: 4000, maxThreshold: 6000,
|
||||
maxCapacity: 9000, inflowRate: 3000,
|
||||
overflowAllocations: [],
|
||||
label: "Carol — Comms", currentValue: 0, desiredOutflow: 1100,
|
||||
minThreshold: 1100, sufficientThreshold: 4400, maxThreshold: 6600,
|
||||
maxCapacity: 9900, inflowRate: 0,
|
||||
overflowAllocations: [
|
||||
{ targetId: "dave", percentage: 100, color: OVERFLOW_COLORS[0] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "eve-legal", percentage: 40, color: SPENDING_COLORS[0] },
|
||||
{ targetId: "eve-compliance", percentage: 30, color: SPENDING_COLORS[1] },
|
||||
{ targetId: "eve-governance", percentage: 30, color: SPENDING_COLORS[2] },
|
||||
{ targetId: "comms-strategy", percentage: 50, color: SPENDING_COLORS[0] },
|
||||
{ targetId: "event-series", percentage: 50, color: SPENDING_COLORS[4] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
{
|
||||
id: "dave", type: "funnel", position: { x: 1040, y: 1250 },
|
||||
data: {
|
||||
label: "Dave — Design", currentValue: 0, desiredOutflow: 1000,
|
||||
minThreshold: 1000, sufficientThreshold: 4000, maxThreshold: 6000,
|
||||
maxCapacity: 9000, inflowRate: 0,
|
||||
overflowAllocations: [
|
||||
{ targetId: "operations", percentage: 100, color: OVERFLOW_COLORS[1] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "design-system", percentage: 55, color: SPENDING_COLORS[5] },
|
||||
{ targetId: "user-testing", percentage: 45, color: SPENDING_COLORS[2] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
{
|
||||
id: "eve", type: "funnel", position: { x: 1420, y: 1250 },
|
||||
data: {
|
||||
label: "Eve — Governance", currentValue: 0, desiredOutflow: 900,
|
||||
minThreshold: 900, sufficientThreshold: 3600, maxThreshold: 5400,
|
||||
maxCapacity: 8100, inflowRate: 0,
|
||||
overflowAllocations: [
|
||||
{ targetId: "bcrg", percentage: 100, color: OVERFLOW_COLORS[2] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "legal-framework", percentage: 40, color: SPENDING_COLORS[0] },
|
||||
{ targetId: "compliance", percentage: 30, color: SPENDING_COLORS[3] },
|
||||
{ targetId: "governance-model", percentage: 30, color: SPENDING_COLORS[5] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
|
||||
// ── Outcome nodes (Y=850) — 11 total ──
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Layer 4 — Outcomes (Y=1900)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
// Alice's outcomes
|
||||
{ id: "alice-comms", type: "outcome", position: { x: -20, y: 850 },
|
||||
data: {
|
||||
label: "Comms Strategy", description: "Community communications and outreach",
|
||||
fundingReceived: 0, fundingTarget: 12000, status: "not-started",
|
||||
phases: [
|
||||
{ name: "Planning", fundingThreshold: 4000, tasks: [
|
||||
{ label: "Stakeholder mapping", completed: true },
|
||||
{ label: "Channel audit", completed: true },
|
||||
] },
|
||||
{ name: "Execution", fundingThreshold: 8000, tasks: [
|
||||
{ label: "Newsletter launch", completed: true },
|
||||
{ label: "Social media calendar", completed: true },
|
||||
] },
|
||||
{ name: "Review", fundingThreshold: 12000, tasks: [
|
||||
{ label: "Impact metrics report", completed: true },
|
||||
] },
|
||||
],
|
||||
} as OutcomeNodeData },
|
||||
{ id: "alice-events", type: "outcome", position: { x: 260, y: 850 },
|
||||
data: {
|
||||
label: "Event Series", description: "Quarterly community gatherings",
|
||||
fundingReceived: 0, fundingTarget: 15000, status: "not-started",
|
||||
phases: [
|
||||
{ name: "Venue & Logistics", fundingThreshold: 5000, tasks: [
|
||||
{ label: "Venue scouting", completed: true },
|
||||
{ label: "Catering contracts", completed: true },
|
||||
] },
|
||||
{ name: "Programming", fundingThreshold: 10000, tasks: [
|
||||
{ label: "Speaker invitations", completed: false },
|
||||
{ label: "Workshop facilitation", completed: false },
|
||||
] },
|
||||
],
|
||||
} as OutcomeNodeData },
|
||||
|
||||
// Bob's outcomes
|
||||
{ id: "bob-research", type: "outcome", position: { x: 400, y: 850 },
|
||||
{ id: "field-research", type: "outcome", position: { x: -220, y: 1900 },
|
||||
data: {
|
||||
label: "Field Research", description: "Participatory action research in partner communities",
|
||||
fundingReceived: 0, fundingTarget: 20000, status: "not-started",
|
||||
|
|
@ -165,7 +207,7 @@ export const demoNodes: FlowNode[] = [
|
|||
] },
|
||||
],
|
||||
} as OutcomeNodeData },
|
||||
{ id: "bob-writing", type: "outcome", position: { x: 680, y: 850 },
|
||||
{ id: "publications", type: "outcome", position: { x: 60, y: 1900 },
|
||||
data: {
|
||||
label: "Publications", description: "Research papers and policy briefs",
|
||||
fundingReceived: 0, fundingTarget: 10000, status: "not-started",
|
||||
|
|
@ -179,26 +221,8 @@ export const demoNodes: FlowNode[] = [
|
|||
],
|
||||
} as OutcomeNodeData },
|
||||
|
||||
// Carol's outcomes
|
||||
{ id: "carol-ops", type: "outcome", position: { x: 820, y: 850 },
|
||||
data: {
|
||||
label: "Operations", description: "Day-to-day operational management",
|
||||
fundingReceived: 0, fundingTarget: 18000, status: "not-started",
|
||||
phases: [
|
||||
{ name: "Setup", fundingThreshold: 6000, tasks: [
|
||||
{ label: "Process documentation", completed: true },
|
||||
{ label: "Tool selection", completed: true },
|
||||
] },
|
||||
{ name: "Execution", fundingThreshold: 12000, tasks: [
|
||||
{ label: "Monthly reporting", completed: true },
|
||||
{ label: "Budget tracking", completed: true },
|
||||
] },
|
||||
{ name: "Optimization", fundingThreshold: 18000, tasks: [
|
||||
{ label: "Workflow automation", completed: true },
|
||||
] },
|
||||
],
|
||||
} as OutcomeNodeData },
|
||||
{ id: "carol-infra", type: "outcome", position: { x: 1100, y: 850 },
|
||||
// Bob's outcomes
|
||||
{ id: "infrastructure", type: "outcome", position: { x: 200, y: 1900 },
|
||||
data: {
|
||||
label: "Infrastructure", description: "Shared infrastructure and hosting",
|
||||
fundingReceived: 0, fundingTarget: 20000, status: "not-started",
|
||||
|
|
@ -213,27 +237,7 @@ export const demoNodes: FlowNode[] = [
|
|||
] },
|
||||
],
|
||||
} as OutcomeNodeData },
|
||||
|
||||
// Dave's outcomes
|
||||
{ id: "dave-design", type: "outcome", position: { x: 1240, y: 850 },
|
||||
data: {
|
||||
label: "Design System", description: "Shared UI/UX design system",
|
||||
fundingReceived: 0, fundingTarget: 15000, status: "not-started",
|
||||
phases: [
|
||||
{ name: "Foundations", fundingThreshold: 5000, tasks: [
|
||||
{ label: "Color & type system", completed: true },
|
||||
{ label: "Component library", completed: true },
|
||||
] },
|
||||
{ name: "Documentation", fundingThreshold: 10000, tasks: [
|
||||
{ label: "Storybook setup", completed: true },
|
||||
{ label: "Usage guidelines", completed: true },
|
||||
] },
|
||||
{ name: "Rollout", fundingThreshold: 15000, tasks: [
|
||||
{ label: "Team training", completed: true },
|
||||
] },
|
||||
],
|
||||
} as OutcomeNodeData },
|
||||
{ id: "dave-prototypes", type: "outcome", position: { x: 1520, y: 850 },
|
||||
{ id: "prototypes", type: "outcome", position: { x: 480, y: 1900 },
|
||||
data: {
|
||||
label: "Prototypes", description: "Rapid prototyping of new tools",
|
||||
fundingReceived: 0, fundingTarget: 12000, status: "not-started",
|
||||
|
|
@ -250,8 +254,77 @@ export const demoNodes: FlowNode[] = [
|
|||
],
|
||||
} as OutcomeNodeData },
|
||||
|
||||
// Carol's outcomes
|
||||
{ id: "comms-strategy", type: "outcome", position: { x: 560, y: 1900 },
|
||||
data: {
|
||||
label: "Comms Strategy", description: "Community communications and outreach",
|
||||
fundingReceived: 0, fundingTarget: 12000, status: "not-started",
|
||||
phases: [
|
||||
{ name: "Planning", fundingThreshold: 4000, tasks: [
|
||||
{ label: "Stakeholder mapping", completed: true },
|
||||
{ label: "Channel audit", completed: true },
|
||||
] },
|
||||
{ name: "Execution", fundingThreshold: 8000, tasks: [
|
||||
{ label: "Newsletter launch", completed: true },
|
||||
{ label: "Social media calendar", completed: true },
|
||||
] },
|
||||
{ name: "Review", fundingThreshold: 12000, tasks: [
|
||||
{ label: "Impact metrics report", completed: false },
|
||||
] },
|
||||
],
|
||||
} as OutcomeNodeData },
|
||||
{ id: "event-series", type: "outcome", position: { x: 840, y: 1900 },
|
||||
data: {
|
||||
label: "Event Series", description: "Quarterly community gatherings",
|
||||
fundingReceived: 0, fundingTarget: 15000, status: "not-started",
|
||||
phases: [
|
||||
{ name: "Venue & Logistics", fundingThreshold: 5000, tasks: [
|
||||
{ label: "Venue scouting", completed: true },
|
||||
{ label: "Catering contracts", completed: true },
|
||||
] },
|
||||
{ name: "Programming", fundingThreshold: 10000, tasks: [
|
||||
{ label: "Speaker invitations", completed: false },
|
||||
{ label: "Workshop facilitation", completed: false },
|
||||
] },
|
||||
],
|
||||
} as OutcomeNodeData },
|
||||
|
||||
// Dave's outcomes
|
||||
{ id: "design-system", type: "outcome", position: { x: 940, y: 1900 },
|
||||
data: {
|
||||
label: "Design System", description: "Shared UI/UX design system",
|
||||
fundingReceived: 0, fundingTarget: 15000, status: "not-started",
|
||||
phases: [
|
||||
{ name: "Foundations", fundingThreshold: 5000, tasks: [
|
||||
{ label: "Color & type system", completed: true },
|
||||
{ label: "Component library", completed: true },
|
||||
] },
|
||||
{ name: "Documentation", fundingThreshold: 10000, tasks: [
|
||||
{ label: "Storybook setup", completed: true },
|
||||
{ label: "Usage guidelines", completed: true },
|
||||
] },
|
||||
{ name: "Rollout", fundingThreshold: 15000, tasks: [
|
||||
{ label: "Team training", completed: false },
|
||||
] },
|
||||
],
|
||||
} as OutcomeNodeData },
|
||||
{ id: "user-testing", type: "outcome", position: { x: 1220, y: 1900 },
|
||||
data: {
|
||||
label: "User Testing", description: "Usability research and testing program",
|
||||
fundingReceived: 0, fundingTarget: 8000, status: "not-started",
|
||||
phases: [
|
||||
{ name: "Recruitment", fundingThreshold: 3000, tasks: [
|
||||
{ label: "Participant pool setup", completed: false },
|
||||
] },
|
||||
{ name: "Testing Rounds", fundingThreshold: 8000, tasks: [
|
||||
{ label: "5 usability rounds", completed: false },
|
||||
{ label: "Accessibility audit", completed: false },
|
||||
] },
|
||||
],
|
||||
} as OutcomeNodeData },
|
||||
|
||||
// Eve's outcomes
|
||||
{ id: "eve-legal", type: "outcome", position: { x: 1660, y: 850 },
|
||||
{ id: "legal-framework", type: "outcome", position: { x: 1320, y: 1900 },
|
||||
data: {
|
||||
label: "Legal Framework", description: "Legal structure and agreements",
|
||||
fundingReceived: 0, fundingTarget: 10000, status: "not-started",
|
||||
|
|
@ -261,12 +334,12 @@ export const demoNodes: FlowNode[] = [
|
|||
{ label: "Entity comparison", completed: true },
|
||||
] },
|
||||
{ name: "Formation", fundingThreshold: 10000, tasks: [
|
||||
{ label: "Articles of incorporation", completed: true },
|
||||
{ label: "Operating agreement", completed: true },
|
||||
{ label: "Articles of incorporation", completed: false },
|
||||
{ label: "Operating agreement", completed: false },
|
||||
] },
|
||||
],
|
||||
} as OutcomeNodeData },
|
||||
{ id: "eve-compliance", type: "outcome", position: { x: 1940, y: 850 },
|
||||
{ id: "compliance", type: "outcome", position: { x: 1600, y: 1900 },
|
||||
data: {
|
||||
label: "Compliance", description: "Regulatory compliance and reporting",
|
||||
fundingReceived: 0, fundingTarget: 12000, status: "not-started",
|
||||
|
|
@ -282,7 +355,7 @@ export const demoNodes: FlowNode[] = [
|
|||
] },
|
||||
],
|
||||
} as OutcomeNodeData },
|
||||
{ id: "eve-governance", type: "outcome", position: { x: 2220, y: 850 },
|
||||
{ id: "governance-model", type: "outcome", position: { x: 1600, y: 2100 },
|
||||
data: {
|
||||
label: "Governance Model", description: "Governance framework and voting mechanisms",
|
||||
fundingReceived: 0, fundingTarget: 8000, status: "not-started",
|
||||
|
|
@ -299,94 +372,212 @@ export const demoNodes: FlowNode[] = [
|
|||
];
|
||||
|
||||
/**
|
||||
* Simulation Demo — starts empty so you can watch flow propagate.
|
||||
* Simulation Demo — multi-layer network with overflow cascading down and back up.
|
||||
*
|
||||
* 1 source → central funnel → 3 domain funnels → 6 outcomes.
|
||||
* 2 sources → treasury → 3 domain funnels → 4 sub-funnels → 8 outcomes.
|
||||
* Overflow paths: treasury → reserve, ops → community, research → community,
|
||||
* community → reserve, reserve → treasury (back up!), sub-funnels cross-flow.
|
||||
* All currentValue and fundingReceived start at 0.
|
||||
* Press Play to watch resources flow from source through the entire system.
|
||||
* Press Play to watch resources flow through the entire system.
|
||||
*/
|
||||
export const simDemoNodes: FlowNode[] = [
|
||||
// ── Source ──
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Layer 0 — Sources (Y=-400)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
{
|
||||
id: "src-grants", type: "source", position: { x: 600, y: -300 },
|
||||
id: "src-grants", type: "source", position: { x: 350, y: -400 },
|
||||
data: {
|
||||
label: "Funding Source", flowRate: 12000, sourceType: "card",
|
||||
label: "Grants", flowRate: 10000, sourceType: "card",
|
||||
targetAllocations: [{ targetId: "treasury", percentage: 100, color: "#10b981" }],
|
||||
} as SourceNodeData,
|
||||
},
|
||||
|
||||
// ── Central Treasury ──
|
||||
{
|
||||
id: "treasury", type: "funnel", position: { x: 560, y: 0 },
|
||||
id: "src-earned", type: "source", position: { x: 850, y: -400 },
|
||||
data: {
|
||||
label: "Treasury", currentValue: 0, desiredOutflow: 2000,
|
||||
minThreshold: 2000, sufficientThreshold: 8000, maxThreshold: 12000,
|
||||
maxCapacity: 18000, inflowRate: 12000, dynamicOverflow: true,
|
||||
overflowAllocations: [],
|
||||
label: "Earned Revenue", flowRate: 6000, sourceType: "card",
|
||||
targetAllocations: [{ targetId: "treasury", percentage: 100, color: "#06b6d4" }],
|
||||
} as SourceNodeData,
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Layer 1 — Central Treasury (Y=0)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
{
|
||||
id: "treasury", type: "funnel", position: { x: 500, y: 0 },
|
||||
data: {
|
||||
label: "Treasury", currentValue: 0, desiredOutflow: 4000,
|
||||
minThreshold: 4000, sufficientThreshold: 16000, maxThreshold: 24000,
|
||||
maxCapacity: 36000, inflowRate: 16000, dynamicOverflow: true,
|
||||
overflowAllocations: [
|
||||
{ targetId: "reserve", percentage: 100, color: OVERFLOW_COLORS[0] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "ops", percentage: 40, color: SPENDING_COLORS[0] },
|
||||
{ targetId: "ops", percentage: 35, color: SPENDING_COLORS[0] },
|
||||
{ targetId: "research", percentage: 35, color: SPENDING_COLORS[1] },
|
||||
{ targetId: "community", percentage: 25, color: SPENDING_COLORS[2] },
|
||||
{ targetId: "community", percentage: 30, color: SPENDING_COLORS[2] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
|
||||
// ── Domain Funnels ──
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Layer 2 — Domain Funnels (Y=600)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
{
|
||||
id: "ops", type: "funnel", position: { x: 200, y: 400 },
|
||||
id: "ops", type: "funnel", position: { x: 80, y: 600 },
|
||||
data: {
|
||||
label: "Operations", currentValue: 0, desiredOutflow: 800,
|
||||
minThreshold: 800, sufficientThreshold: 3200, maxThreshold: 4800,
|
||||
maxCapacity: 7200, inflowRate: 0,
|
||||
label: "Operations", currentValue: 0, desiredOutflow: 1500,
|
||||
minThreshold: 1500, sufficientThreshold: 6000, maxThreshold: 9000,
|
||||
maxCapacity: 13500, inflowRate: 0,
|
||||
overflowAllocations: [
|
||||
{ targetId: "community", percentage: 100, color: OVERFLOW_COLORS[0] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "infra", percentage: 50, color: SPENDING_COLORS[0] },
|
||||
{ targetId: "admin", percentage: 50, color: SPENDING_COLORS[1] },
|
||||
{ targetId: "infra-team", percentage: 55, color: SPENDING_COLORS[0] },
|
||||
{ targetId: "admin-team", percentage: 45, color: SPENDING_COLORS[1] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
{
|
||||
id: "research", type: "funnel", position: { x: 560, y: 400 },
|
||||
id: "research", type: "funnel", position: { x: 500, y: 600 },
|
||||
data: {
|
||||
label: "Research", currentValue: 0, desiredOutflow: 700,
|
||||
minThreshold: 700, sufficientThreshold: 2800, maxThreshold: 4200,
|
||||
maxCapacity: 6300, inflowRate: 0,
|
||||
label: "Research", currentValue: 0, desiredOutflow: 1400,
|
||||
minThreshold: 1400, sufficientThreshold: 5600, maxThreshold: 8400,
|
||||
maxCapacity: 12600, inflowRate: 0,
|
||||
overflowAllocations: [
|
||||
{ targetId: "community", percentage: 100, color: OVERFLOW_COLORS[1] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "papers", percentage: 60, color: SPENDING_COLORS[2] },
|
||||
{ targetId: "tools", percentage: 40, color: SPENDING_COLORS[3] },
|
||||
{ targetId: "science-team", percentage: 60, color: SPENDING_COLORS[2] },
|
||||
{ targetId: "tools-team", percentage: 40, color: SPENDING_COLORS[3] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
{
|
||||
id: "community", type: "funnel", position: { x: 920, y: 400 },
|
||||
id: "community", type: "funnel", position: { x: 920, y: 600 },
|
||||
data: {
|
||||
label: "Community", currentValue: 0, desiredOutflow: 500,
|
||||
minThreshold: 500, sufficientThreshold: 2000, maxThreshold: 3000,
|
||||
maxCapacity: 4500, inflowRate: 0,
|
||||
overflowAllocations: [],
|
||||
label: "Community", currentValue: 0, desiredOutflow: 1000,
|
||||
minThreshold: 1000, sufficientThreshold: 4000, maxThreshold: 6000,
|
||||
maxCapacity: 9000, inflowRate: 0,
|
||||
overflowAllocations: [
|
||||
{ targetId: "reserve", percentage: 100, color: OVERFLOW_COLORS[2] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "events", percentage: 50, color: SPENDING_COLORS[4] },
|
||||
{ targetId: "outreach", percentage: 50, color: SPENDING_COLORS[5] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
{
|
||||
id: "reserve", type: "funnel", position: { x: 1340, y: 600 },
|
||||
data: {
|
||||
label: "Reserve Fund", currentValue: 0, desiredOutflow: 500,
|
||||
minThreshold: 500, sufficientThreshold: 5000, maxThreshold: 10000,
|
||||
maxCapacity: 20000, inflowRate: 0,
|
||||
overflowAllocations: [
|
||||
{ targetId: "treasury", percentage: 100, color: OVERFLOW_COLORS[3] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "rainy-day", percentage: 100, color: SPENDING_COLORS[5] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
|
||||
// ── Outcomes ──
|
||||
{ id: "infra", type: "outcome", position: { x: 60, y: 850 },
|
||||
data: { label: "Infrastructure", description: "Servers, hosting, CI/CD", fundingReceived: 0, fundingTarget: 15000, status: "not-started" } as OutcomeNodeData },
|
||||
{ id: "admin", type: "outcome", position: { x: 320, y: 850 },
|
||||
data: { label: "Admin & Legal", description: "Legal, compliance, accounting", fundingReceived: 0, fundingTarget: 12000, status: "not-started" } as OutcomeNodeData },
|
||||
{ id: "papers", type: "outcome", position: { x: 520, y: 850 },
|
||||
data: { label: "Publications", description: "Research papers and reports", fundingReceived: 0, fundingTarget: 18000, status: "not-started" } as OutcomeNodeData },
|
||||
{ id: "tools", type: "outcome", position: { x: 720, y: 850 },
|
||||
data: { label: "R&D Tools", description: "Prototyping and tooling", fundingReceived: 0, fundingTarget: 10000, status: "not-started" } as OutcomeNodeData },
|
||||
{ id: "events", type: "outcome", position: { x: 920, y: 850 },
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Layer 3 — Sub-team Funnels (Y=1250)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
{
|
||||
id: "infra-team", type: "funnel", position: { x: -100, y: 1250 },
|
||||
data: {
|
||||
label: "Infra Team", currentValue: 0, desiredOutflow: 800,
|
||||
minThreshold: 800, sufficientThreshold: 3200, maxThreshold: 4800,
|
||||
maxCapacity: 7200, inflowRate: 0,
|
||||
overflowAllocations: [
|
||||
{ targetId: "admin-team", percentage: 100, color: OVERFLOW_COLORS[4] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "servers", percentage: 60, color: SPENDING_COLORS[0] },
|
||||
{ targetId: "devops", percentage: 40, color: SPENDING_COLORS[1] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
{
|
||||
id: "admin-team", type: "funnel", position: { x: 280, y: 1250 },
|
||||
data: {
|
||||
label: "Admin Team", currentValue: 0, desiredOutflow: 700,
|
||||
minThreshold: 700, sufficientThreshold: 2800, maxThreshold: 4200,
|
||||
maxCapacity: 6300, inflowRate: 0,
|
||||
overflowAllocations: [
|
||||
{ targetId: "ops", percentage: 100, color: OVERFLOW_COLORS[5] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "legal", percentage: 50, color: SPENDING_COLORS[2] },
|
||||
{ targetId: "accounting", percentage: 50, color: SPENDING_COLORS[3] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
{
|
||||
id: "science-team", type: "funnel", position: { x: 660, y: 1250 },
|
||||
data: {
|
||||
label: "Science Team", currentValue: 0, desiredOutflow: 900,
|
||||
minThreshold: 900, sufficientThreshold: 3600, maxThreshold: 5400,
|
||||
maxCapacity: 8100, inflowRate: 0,
|
||||
overflowAllocations: [
|
||||
{ targetId: "tools-team", percentage: 100, color: OVERFLOW_COLORS[0] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "papers", percentage: 60, color: SPENDING_COLORS[4] },
|
||||
{ targetId: "field-work", percentage: 40, color: SPENDING_COLORS[5] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
{
|
||||
id: "tools-team", type: "funnel", position: { x: 1040, y: 1250 },
|
||||
data: {
|
||||
label: "Tools Team", currentValue: 0, desiredOutflow: 600,
|
||||
minThreshold: 600, sufficientThreshold: 2400, maxThreshold: 3600,
|
||||
maxCapacity: 5400, inflowRate: 0,
|
||||
overflowAllocations: [
|
||||
{ targetId: "research", percentage: 100, color: OVERFLOW_COLORS[1] },
|
||||
],
|
||||
spendingAllocations: [
|
||||
{ targetId: "tooling", percentage: 100, color: SPENDING_COLORS[0] },
|
||||
],
|
||||
} as FunnelNodeData,
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Layer 4 — Outcomes (Y=1900)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
// Infra Team outcomes
|
||||
{ id: "servers", type: "outcome", position: { x: -220, y: 1900 },
|
||||
data: { label: "Servers & Hosting", description: "Cloud infrastructure and hosting", fundingReceived: 0, fundingTarget: 15000, status: "not-started" } as OutcomeNodeData },
|
||||
{ id: "devops", type: "outcome", position: { x: 0, y: 1900 },
|
||||
data: { label: "DevOps Pipeline", description: "CI/CD and deployment automation", fundingReceived: 0, fundingTarget: 10000, status: "not-started" } as OutcomeNodeData },
|
||||
|
||||
// Admin Team outcomes
|
||||
{ id: "legal", type: "outcome", position: { x: 200, y: 1900 },
|
||||
data: { label: "Legal & Compliance", description: "Legal structure and regulatory work", fundingReceived: 0, fundingTarget: 12000, status: "not-started" } as OutcomeNodeData },
|
||||
{ id: "accounting", type: "outcome", position: { x: 420, y: 1900 },
|
||||
data: { label: "Accounting", description: "Bookkeeping and financial reporting", fundingReceived: 0, fundingTarget: 8000, status: "not-started" } as OutcomeNodeData },
|
||||
|
||||
// Science Team outcomes
|
||||
{ id: "papers", type: "outcome", position: { x: 560, y: 1900 },
|
||||
data: { label: "Research Papers", description: "Academic publications and reports", fundingReceived: 0, fundingTarget: 18000, status: "not-started" } as OutcomeNodeData },
|
||||
{ id: "field-work", type: "outcome", position: { x: 780, y: 1900 },
|
||||
data: { label: "Field Work", description: "On-site research and data collection", fundingReceived: 0, fundingTarget: 14000, status: "not-started" } as OutcomeNodeData },
|
||||
|
||||
// Tools Team outcome
|
||||
{ id: "tooling", type: "outcome", position: { x: 960, y: 1900 },
|
||||
data: { label: "R&D Tooling", description: "Prototyping and development tools", fundingReceived: 0, fundingTarget: 10000, status: "not-started" } as OutcomeNodeData },
|
||||
|
||||
// Community outcomes (direct from domain funnel)
|
||||
{ id: "events", type: "outcome", position: { x: 1100, y: 1900 },
|
||||
data: { label: "Events", description: "Meetups and conferences", fundingReceived: 0, fundingTarget: 8000, status: "not-started" } as OutcomeNodeData },
|
||||
{ id: "outreach", type: "outcome", position: { x: 1120, y: 850 },
|
||||
data: { label: "Outreach", description: "Marketing and comms", fundingReceived: 0, fundingTarget: 6000, status: "not-started" } as OutcomeNodeData },
|
||||
{ id: "outreach", type: "outcome", position: { x: 1300, y: 1900 },
|
||||
data: { label: "Outreach", description: "Marketing and community building", fundingReceived: 0, fundingTarget: 6000, status: "not-started" } as OutcomeNodeData },
|
||||
|
||||
// Reserve outcome
|
||||
{ id: "rainy-day", type: "outcome", position: { x: 1500, y: 1900 },
|
||||
data: { label: "Rainy Day Fund", description: "Emergency reserves and contingency", fundingReceived: 0, fundingTarget: 25000, status: "not-started" } as OutcomeNodeData },
|
||||
];
|
||||
|
|
|
|||
Loading…
Reference in New Issue