"use client"; import { useEffect, useState } from "react"; import { motion } from "framer-motion"; interface HealthScoreRingProps { score: number; size?: number; strokeWidth?: number; previousScore?: number; label?: string; } function scoreColor(score: number): string { if (score >= 80) return "#34d399"; // emerald-400 if (score >= 60) return "#fbbf24"; // amber-400 return "#f87171"; // red-400 } function scoreColorClass(score: number): string { if (score >= 80) return "text-emerald-400"; if (score >= 60) return "text-amber-400"; return "text-red-400"; } function scoreGlow(score: number): string { if (score >= 80) return "rgba(52,211,153,0.2)"; if (score >= 60) return "rgba(251,191,36,0.15)"; return "rgba(248,113,113,0.2)"; } export default function HealthScoreRing({ score, size = 180, strokeWidth = 10, previousScore, label, }: HealthScoreRingProps) { const [animatedScore, setAnimatedScore] = useState(0); useEffect(() => { let raf: number; const start = performance.now(); const duration = 1200; function tick(now: number) { const elapsed = now - start; const progress = Math.min(elapsed / duration, 1); const ease = 1 - Math.pow(1 - progress, 4); setAnimatedScore(Math.round(score * ease)); if (progress < 1) raf = requestAnimationFrame(tick); } raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [score]); const radius = (size - strokeWidth) / 2; const circumference = 2 * Math.PI * radius; const dashOffset = circumference - (animatedScore / 100) * circumference; const color = scoreColor(animatedScore); const delta = previousScore !== undefined ? score - previousScore : undefined; return (
{/* background track */} {/* gradient arc */} {/* centered text */}
{animatedScore} {delta !== undefined && ( 0 ? "text-emerald-400" : delta < 0 ? "text-red-400" : "text-zinc-600" }`} > {delta > 0 ? "+" : ""} {delta} pts )}
{label && ( {label} )}
); }