"use client"; import { AnimatePresence, animate, motion, useMotionValue, useInView, useMotionValueEvent, useReducedMotion, useTransform, } from "motion/react"; import { type ReactNode, useEffect, useId, useRef, useState } from "react"; import { cn } from "@/lib/utils"; export type PlantProgressHue = "sky" | "emerald" | "amber" | "violet" | "rose" | "orange" | "indigo" | "teal"; const hues: Record = { sky: "[--pot:var(--color-sky-400)] [--pot-deep:var(--color-sky-600)]", emerald: "[--pot:var(--color-emerald-400)] [--pot-deep:var(--color-emerald-600)]", amber: "[--pot:var(--color-amber-400)] [--pot-deep:var(--color-amber-600)]", violet: "[--pot:var(--color-violet-400)] [--pot-deep:var(--color-violet-600)]", rose: "[--pot:var(--color-rose-400)] [--pot-deep:var(--color-rose-600)]", orange: "[--pot:var(--color-orange-400)] [--pot-deep:var(--color-orange-600)]", indigo: "[--pot:var(--color-indigo-400)] [--pot-deep:var(--color-indigo-600)]", teal: "[--pot:var(--color-teal-400)] [--pot-deep:var(--color-teal-600)]", }; export interface PlantProgressProps { value: number; label?: string; hue?: PlantProgressHue; children?: ReactNode; className?: string; } const W = 320; const H = 230; const BASE_X = 160; const BASE_Y = 178; const STEM_H = 128; function stemAt(t: number) { return { x: BASE_X + Math.sin(t * Math.PI * 1.6) * 9 * t, y: BASE_Y - t * STEM_H }; } const STEM_PATH = Array.from({ length: 41 }, (_, i) => { const p = stemAt(i / 40); return `${i ? "L" : "M"}${p.x.toFixed(2)} ${p.y.toFixed(2)}`; }).join(" "); const leaves = [ { at: 0.16, side: -1, size: 0.8 }, { at: 0.3, side: 1, size: 0.95 }, { at: 0.46, side: -1, size: 1.05 }, { at: 0.62, side: 1, size: 1 }, { at: 0.78, side: -1, size: 0.85 }, ]; const stages = [ { min: 0, name: "Planted" }, { min: 15, name: "Sprouting" }, { min: 45, name: "Growing" }, { min: 75, name: "Almost there" }, { min: 100, name: "In bloom" }, ]; function leafPath(size: number) { const s = size * 22; return `M0 0 C ${s * 0.3} ${-s * 0.55} ${s * 0.9} ${-s * 0.62} ${s * 1.25} ${-s * 0.42} C ${s * 0.95} ${-s * 0.05} ${s * 0.45} ${s * 0.12} 0 0 Z`; } function Leaf({ side, size, shade }: { side: number; size: number; shade: number }) { const s = size * 22; return ( ); } function useCalmMotion() { const prefers = useReducedMotion(); const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); return mounted && !!prefers; } export function PlantProgress({ value, label = "Profile setup", hue = "orange", children, className }: PlantProgressProps) { const uid = useId().replace(/:/g, ""); const reduce = useCalmMotion(); const root = useRef(null); const live = useInView(root) && !reduce; const target = Math.min(100, Math.max(0, value)); const progress = useMotionValue(target); const [reached, setReached] = useState(target); useEffect(() => { if (reduce) { progress.set(target); return; } const c = animate(progress, target, { duration: 0.9, ease: [0.23, 1, 0.32, 1] }); return () => c.stop(); }, [target, reduce, progress]); useMotionValueEvent(progress, "change", (v) => setReached(Math.round(v))); const length = useTransform(progress, (v) => 0.06 + (v / 100) * 0.94); const tipX = useTransform(length, (l) => stemAt(l).x); const tipY = useTransform(length, (l) => stemAt(l).y); const bloom = target === 100 && reached >= 97; const stage = [...stages].reverse().find((s) => target >= s.min) ?? stages[0]; const tipT = 0.06 + (reached / 100) * 0.94; return (

{label}

{stage.name}
{reached} %
{leaves.map((leaf) => { const p = stemAt(leaf.at); return ( ); })} {leaves.map((leaf, i) => { const p = stemAt(leaf.at); const open = tipT >= leaf.at + 0.03; return ( ); })} {bloom && ( {Array.from({ length: 6 }, (_, i) => ( ))} )} {bloom && !reduce && ( {[-1, 1, -0.6, 0.7].map((d, i) => ( ))} )}
{children &&
{children}
}
); }