flight-club-lol/components/pricing-chart.tsx

194 lines
5.2 KiB
TypeScript

"use client"
import { useIntersection } from "@/hooks/use-intersection"
import { domesticPricing, internationalPricing } from "@/lib/pricing-data"
// Convert data points to SVG path coordinates
// Chart: X = 0 (60 days) to 400 (0 days), Y = 0 (top, 10x) to 300 (bottom, 0x)
function toPath(
data: { days: number; multiplier: number }[]
): string {
const points = data.map((d) => ({
x: ((60 - d.days) / 60) * 380 + 40,
y: 280 - (d.multiplier / 10) * 260,
}))
// Smooth bezier curve through points
let path = `M ${points[0].x} ${points[0].y}`
for (let i = 1; i < points.length; i++) {
const prev = points[i - 1]
const curr = points[i]
const cpx = (prev.x + curr.x) / 2
path += ` C ${cpx} ${prev.y}, ${cpx} ${curr.y}, ${curr.x} ${curr.y}`
}
return path
}
export function PricingChart() {
const { ref, isVisible } = useIntersection(0.3)
const domesticPath = toPath(domesticPricing)
const internationalPath = toPath(internationalPricing)
// Y position for 2x multiplier (danger threshold)
const dangerY = 280 - (2 / 10) * 260
return (
<div ref={ref as React.RefObject<HTMLDivElement>} className="w-full max-w-3xl mx-auto">
<svg
viewBox="0 0 440 320"
className="w-full h-auto crt-glow"
preserveAspectRatio="xMidYMid meet"
>
{/* Danger zone (above 2x) */}
<rect
x="40"
y="20"
width="380"
height={dangerY - 20}
fill="var(--destructive)"
opacity="0.06"
rx="4"
/>
{/* Grid lines */}
{[0, 2, 4, 6, 8, 10].map((mult) => {
const y = 280 - (mult / 10) * 260
return (
<g key={mult}>
<line
x1="40"
y1={y}
x2="420"
y2={y}
stroke="var(--border)"
strokeWidth="0.5"
strokeDasharray={mult === 2 ? "none" : "4,4"}
opacity={mult === 2 ? 0.6 : 0.3}
/>
<text
x="32"
y={y + 4}
textAnchor="end"
fill="var(--muted-foreground)"
fontSize="10"
fontFamily="var(--font-mono)"
>
{mult}x
</text>
</g>
)
})}
{/* X-axis labels */}
{[60, 45, 30, 21, 14, 7, 3, 0].map((days) => {
const x = ((60 - days) / 60) * 380 + 40
return (
<text
key={days}
x={x}
y="300"
textAnchor="middle"
fill="var(--muted-foreground)"
fontSize="9"
fontFamily="var(--font-mono)"
>
{days}d
</text>
)
})}
{/* 2x danger label */}
<text
x="425"
y={dangerY + 4}
fill="var(--destructive)"
fontSize="9"
fontFamily="var(--font-mono)"
fontWeight="bold"
>
2x
</text>
{/* International line */}
<path
d={internationalPath}
fill="none"
stroke="var(--chart-3)"
strokeWidth="2.5"
strokeLinecap="round"
className="draw-path"
data-visible={isVisible}
style={{ animationDelay: "0.3s" }}
/>
{/* Domestic line */}
<path
d={domesticPath}
fill="none"
stroke="var(--chart-1)"
strokeWidth="2.5"
strokeLinecap="round"
className="draw-path"
data-visible={isVisible}
/>
{/* End point dots */}
{isVisible && (
<>
<circle
cx={((60 - 0) / 60) * 380 + 40}
cy={280 - (10 / 10) * 260}
r="4"
fill="var(--chart-1)"
className="animate-in fade-in duration-1000"
style={{ animationDelay: "2s" }}
/>
<circle
cx={((60 - 0) / 60) * 380 + 40}
cy={280 - (5 / 10) * 260}
r="4"
fill="var(--chart-3)"
className="animate-in fade-in duration-1000"
style={{ animationDelay: "2.3s" }}
/>
</>
)}
{/* Axis labels */}
<text
x="230"
y="316"
textAnchor="middle"
fill="var(--muted-foreground)"
fontSize="10"
>
Days before departure
</text>
<text
x="14"
y="155"
textAnchor="middle"
fill="var(--muted-foreground)"
fontSize="10"
transform="rotate(-90, 14, 155)"
>
Price multiplier
</text>
</svg>
{/* Legend */}
<div className="flex justify-center gap-6 mt-4 text-sm">
<div className="flex items-center gap-2">
<div className="w-4 h-0.5 bg-[var(--chart-1)] rounded" />
<span className="text-muted-foreground">Domestic</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-0.5 bg-[var(--chart-3)] rounded" />
<span className="text-muted-foreground">International</span>
</div>
</div>
</div>
)
}