kindness-fund-website/components/flow-visual.tsx

105 lines
3.7 KiB
TypeScript

"use client"
import { useEffect, useRef, useState } from "react"
import type { JSX } from "react/jsx-runtime" // Import JSX to fix the undeclared variable error
interface FlowVisualProps {
allocations: { id: string; amount: number; color: string }[]
containerHeight: number
}
export function FlowVisual({ allocations, containerHeight }: FlowVisualProps) {
const [paths, setPaths] = useState<JSX.Element[]>([])
const containerRef = useRef<HTMLDivElement>(null)
// Calculate paths whenever allocations change or window resizes
useEffect(() => {
const updatePaths = () => {
if (!containerRef.current) return
const containerWidth = containerRef.current.clientWidth
const sourceX = containerWidth / 2
const sourceY = 0 // Top center
const newPaths = allocations.map((alloc, index) => {
// Calculate target position based on index and total items
// We assume the target cards are distributed evenly at the bottom
// This is a visual approximation to match the grid layout
const totalItems = allocations.length
const sectionWidth = containerWidth / totalItems
const targetX = sectionWidth * index + sectionWidth / 2
const targetY = containerHeight - 20 // Bottom, slightly offset
// Bezier curve control points
const cp1x = sourceX
const cp1y = containerHeight * 0.5
const cp2x = targetX
const cp2y = containerHeight * 0.5
// Stroke width based on allocation amount (normalized)
const maxAllocation = Math.max(...allocations.map((a) => a.amount))
const strokeWidth = Math.max(2, (alloc.amount / maxAllocation) * 20)
return (
<g key={alloc.id}>
{/* Glow Effect Path */}
<path
d={`M ${sourceX} ${sourceY} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${targetX} ${targetY}`}
fill="none"
stroke={alloc.color}
strokeWidth={strokeWidth + 8}
strokeOpacity="0.1"
strokeLinecap="round"
/>
{/* Main Flow Path */}
<path
d={`M ${sourceX} ${sourceY} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${targetX} ${targetY}`}
fill="none"
stroke={alloc.color}
strokeWidth={strokeWidth}
strokeLinecap="round"
className="opacity-80"
>
<animate
attributeName="stroke-dasharray"
from={`0, ${strokeWidth * 20}`}
to={`${strokeWidth * 20}, 0`}
dur={`${30000 / (alloc.amount + 100)}s`}
repeatCount="indefinite"
/>
</path>
{/* Moving Particle */}
<circle r={strokeWidth / 2 + 2} fill="white">
<animateMotion
dur={`${20000 / (alloc.amount + 100)}s`}
repeatCount="indefinite"
path={`M ${sourceX} ${sourceY} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${targetX} ${targetY}`}
/>
</circle>
</g>
)
})
setPaths(newPaths)
}
updatePaths()
window.addEventListener("resize", updatePaths)
return () => window.removeEventListener("resize", updatePaths)
}, [allocations, containerHeight])
return (
<div ref={containerRef} className="absolute inset-0 pointer-events-none z-0">
<svg width="100%" height="100%" className="overflow-visible">
<defs>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="5" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
</defs>
{paths}
</svg>
</div>
)
}