{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "plant-progress",
  "type": "registry:component",
  "title": "Plant Progress",
  "description": "A seedling that grows with your progress value – the stem climbs, a leaf unfurls at each step, and at 100% it flowers.",
  "categories": [
    "illustration",
    "Celebrations"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/devl/plant-progress.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport {\n  AnimatePresence,\n  animate,\n  motion,\n  useMotionValue,\n  useInView,\n  useMotionValueEvent,\n  useReducedMotion,\n  useTransform,\n} from \"motion/react\";\nimport { type ReactNode, useEffect, useId, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type PlantProgressHue = \"sky\" | \"emerald\" | \"amber\" | \"violet\" | \"rose\" | \"orange\" | \"indigo\" | \"teal\";\n\nconst hues: Record<PlantProgressHue, string> = {\n  sky: \"[--pot:var(--color-sky-400)] [--pot-deep:var(--color-sky-600)]\",\n  emerald: \"[--pot:var(--color-emerald-400)] [--pot-deep:var(--color-emerald-600)]\",\n  amber: \"[--pot:var(--color-amber-400)] [--pot-deep:var(--color-amber-600)]\",\n  violet: \"[--pot:var(--color-violet-400)] [--pot-deep:var(--color-violet-600)]\",\n  rose: \"[--pot:var(--color-rose-400)] [--pot-deep:var(--color-rose-600)]\",\n  orange: \"[--pot:var(--color-orange-400)] [--pot-deep:var(--color-orange-600)]\",\n  indigo: \"[--pot:var(--color-indigo-400)] [--pot-deep:var(--color-indigo-600)]\",\n  teal: \"[--pot:var(--color-teal-400)] [--pot-deep:var(--color-teal-600)]\",\n};\n\nexport interface PlantProgressProps {\n  value: number;\n  label?: string;\n  hue?: PlantProgressHue;\n  children?: ReactNode;\n  className?: string;\n}\n\nconst W = 320;\nconst H = 230;\nconst BASE_X = 160;\nconst BASE_Y = 178;\nconst STEM_H = 128;\n\nfunction stemAt(t: number) {\n  return { x: BASE_X + Math.sin(t * Math.PI * 1.6) * 9 * t, y: BASE_Y - t * STEM_H };\n}\nconst STEM_PATH = Array.from({ length: 41 }, (_, i) => {\n  const p = stemAt(i / 40);\n  return `${i ? \"L\" : \"M\"}${p.x.toFixed(2)} ${p.y.toFixed(2)}`;\n}).join(\" \");\n\nconst leaves = [\n  { at: 0.16, side: -1, size: 0.8 },\n  { at: 0.3, side: 1, size: 0.95 },\n  { at: 0.46, side: -1, size: 1.05 },\n  { at: 0.62, side: 1, size: 1 },\n  { at: 0.78, side: -1, size: 0.85 },\n];\n\nconst stages = [\n  { min: 0, name: \"Planted\" },\n  { min: 15, name: \"Sprouting\" },\n  { min: 45, name: \"Growing\" },\n  { min: 75, name: \"Almost there\" },\n  { min: 100, name: \"In bloom\" },\n];\n\nfunction leafPath(size: number) {\n  const s = size * 22;\n  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`;\n}\n\nfunction Leaf({ side, size, shade }: { side: number; size: number; shade: number }) {\n  const s = size * 22;\n  return (\n    <g transform={`scale(${side} 1)`}>\n      <path\n        className={shade ? \"fill-emerald-600\" : \"fill-emerald-500\"}\n        d={leafPath(size)}\n      />\n      <path\n        className=\"fill-none stroke-white/35\"\n        d={`M${s * 0.12} ${-s * 0.06} Q ${s * 0.6} ${-s * 0.3} ${s * 1.05} ${-s * 0.38}`}\n        strokeLinecap=\"round\"\n        strokeWidth={1.25}\n      />\n    </g>\n  );\n}\n\nfunction useCalmMotion() {\n  const prefers = useReducedMotion();\n  const [mounted, setMounted] = useState(false);\n  useEffect(() => setMounted(true), []);\n  return mounted && !!prefers;\n}\n\nexport function PlantProgress({ value, label = \"Profile setup\", hue = \"orange\", children, className }: PlantProgressProps) {\n  const uid = useId().replace(/:/g, \"\");\n  const reduce = useCalmMotion();\n  const root = useRef<HTMLDivElement>(null);\n  const live = useInView(root) && !reduce;\n  const target = Math.min(100, Math.max(0, value));\n  const progress = useMotionValue(target);\n  const [reached, setReached] = useState(target);\n\n  useEffect(() => {\n    if (reduce) {\n      progress.set(target);\n      return;\n    }\n    const c = animate(progress, target, { duration: 0.9, ease: [0.23, 1, 0.32, 1] });\n    return () => c.stop();\n  }, [target, reduce, progress]);\n\n  useMotionValueEvent(progress, \"change\", (v) => setReached(Math.round(v)));\n\n  const length = useTransform(progress, (v) => 0.06 + (v / 100) * 0.94);\n  const tipX = useTransform(length, (l) => stemAt(l).x);\n  const tipY = useTransform(length, (l) => stemAt(l).y);\n  const bloom = target === 100 && reached >= 97;\n  const stage = [...stages].reverse().find((s) => target >= s.min) ?? stages[0];\n  const tipT = 0.06 + (reached / 100) * 0.94;\n\n  return (\n    <div\n      ref={root}\n      className={cn(\n        \"relative w-full max-w-[380px] overflow-hidden rounded-2xl border bg-card p-4 shadow-xs/5 sm:p-6\",\n        \"[--soil:var(--color-amber-900)]\",\n        hues[hue],\n        className,\n      )}\n    >\n      <div\n        aria-hidden\n        className=\"dot-grid pointer-events-none absolute inset-0 opacity-50 [mask-image:radial-gradient(closest-side,black,transparent)]\"\n      />\n\n      <div className=\"relative flex items-baseline justify-between gap-3\">\n        <div className=\"min-w-0\">\n          <p className=\"truncate text-muted-foreground text-xs\">{label}</p>\n          <div className=\"relative mt-0.5 h-5 overflow-hidden\">\n            <AnimatePresence initial={false} mode=\"popLayout\">\n              <motion.p\n                animate={{ opacity: 1, y: 0 }}\n                className=\"font-medium text-foreground text-sm\"\n                exit={{ opacity: 0, y: reduce ? 0 : -10 }}\n                initial={{ opacity: 0, y: reduce ? 0 : 10 }}\n                key={stage.name}\n                transition={{ duration: 0.22, ease: [0.23, 1, 0.32, 1] }}\n              >\n                {stage.name}\n              </motion.p>\n            </AnimatePresence>\n          </div>\n        </div>\n        <span className=\"font-semibold text-2xl text-foreground tabular-nums tracking-tight\">\n          {reached}\n          <span className=\"text-base text-muted-foreground\">%</span>\n        </span>\n      </div>\n\n      <div\n        aria-label={`${label}: ${Math.round(target)}% – ${stage.name}`}\n        aria-valuemax={100}\n        aria-valuemin={0}\n        aria-valuenow={Math.round(target)}\n        className=\"relative mx-auto aspect-[32/23] w-full max-w-[320px]\"\n        role=\"progressbar\"\n      >\n        <svg aria-hidden className=\"absolute inset-0 size-full overflow-visible\" viewBox={`0 0 ${W} ${H}`}>\n          <defs>\n            <linearGradient id={`${uid}-pot`} x1=\"0\" x2=\"1\" y1=\"0\" y2=\"0\">\n              <stop offset=\"0\" stopColor=\"var(--pot)\" />\n              <stop offset=\"1\" stopColor=\"var(--pot-deep)\" />\n            </linearGradient>\n          </defs>\n\n          <motion.circle\n            animate={{ opacity: bloom ? 0.5 : 0, scale: bloom ? 1 : 0.8 }}\n            className=\"fill-amber-300/40\"\n            cx={BASE_X}\n            cy={BASE_Y - STEM_H}\n            initial={false}\n            r={46}\n            style={{ filter: \"blur(14px)\" }}\n            transition={{ duration: 0.6, ease: [0.23, 1, 0.32, 1] }}\n          />\n\n          <ellipse className=\"fill-foreground/8\" cx={BASE_X} cy={H - 8} rx={62} ry={6} />\n\n          <motion.g\n            animate={live ? { rotate: [-1.2, 1.2, -1.2] } : { rotate: 0 }}\n            style={{ originX: `${BASE_X}px`, originY: `${BASE_Y}px`, transformBox: \"view-box\" }}\n            transition={live ? { duration: 5, ease: \"easeInOut\", repeat: Number.POSITIVE_INFINITY } : { duration: 0.6 }}\n          >\n            <g className=\"fill-none stroke-foreground/15\" strokeDasharray=\"2 4\" strokeLinecap=\"round\" strokeWidth={1.5}>\n              <path d={STEM_PATH} />\n              {leaves.map((leaf) => {\n                const p = stemAt(leaf.at);\n                return (\n                  <path\n                    d={leafPath(leaf.size)}\n                    key={leaf.at}\n                    transform={`translate(${p.x.toFixed(2)} ${p.y.toFixed(2)}) scale(${leaf.side} 1)`}\n                  />\n                );\n              })}\n              <circle cx={stemAt(1).x} cy={stemAt(1).y - 2} r={14} />\n            </g>\n            <motion.path\n              className=\"fill-none stroke-emerald-600\"\n              d={STEM_PATH}\n              strokeLinecap=\"round\"\n              strokeWidth={5}\n              style={{ pathLength: length }}\n            />\n\n            {leaves.map((leaf, i) => {\n              const p = stemAt(leaf.at);\n              const open = tipT >= leaf.at + 0.03;\n              return (\n                <g key={leaf.at} transform={`translate(${p.x.toFixed(2)} ${p.y.toFixed(2)})`}>\n                  <motion.g\n                    animate={open ? { scale: 1, rotate: 0, opacity: 1 } : { scale: 0.3, rotate: leaf.side * 40, opacity: 0 }}\n                    initial={false}\n                    style={{ originX: \"0px\", originY: \"0px\", transformBox: \"view-box\" }}\n                    transition={\n                      reduce\n                        ? { duration: 0.15 }\n                        : open\n                          ? { type: \"spring\", stiffness: 260, damping: 15 }\n                          : { duration: 0.2, ease: \"easeIn\" }\n                    }\n                  >\n                    <Leaf shade={i % 2} side={leaf.side} size={leaf.size} />\n                  </motion.g>\n                </g>\n              );\n            })}\n\n            <motion.g style={{ x: tipX, y: tipY }}>\n              <motion.g\n                animate={bloom ? { scale: 0.5, opacity: 0 } : { scale: 1, opacity: 1 }}\n                initial={false}\n                transition={{ duration: 0.2 }}\n              >\n                <ellipse className=\"fill-emerald-400\" cx={0} cy={-3} rx={4.5} ry={6} />\n              </motion.g>\n              <AnimatePresence>\n                {bloom && (\n                  <motion.g\n                    animate={{ scale: 1, rotate: 0, opacity: 1 }}\n                    exit={{ scale: 0.6, opacity: 0, transition: { duration: 0.18 } }}\n                    initial={reduce ? false : { scale: 0.4, rotate: -40, opacity: 0 }}\n                    key=\"flower\"\n                    transition={{ type: \"spring\", stiffness: 220, damping: 14 }}\n                  >\n                    {Array.from({ length: 6 }, (_, i) => (\n                      <ellipse\n                        className={i % 2 ? \"fill-rose-400\" : \"fill-rose-300\"}\n                        cx={0}\n                        cy={-11}\n                        key={i}\n                        rx={7.5}\n                        ry={11}\n                        transform={`rotate(${i * 60})`}\n                      />\n                    ))}\n                    <circle className=\"fill-amber-300\" r={6.5} />\n                    <circle className=\"fill-amber-500\" r={2.5} />\n                  </motion.g>\n                )}\n              </AnimatePresence>\n            </motion.g>\n          </motion.g>\n\n          <path className=\"fill-(--soil)\" d={`M${BASE_X - 50} ${BASE_Y + 2} Q ${BASE_X} ${BASE_Y - 10} ${BASE_X + 50} ${BASE_Y + 2} Z`} />\n          <path d={`M${BASE_X - 46} ${BASE_Y + 10} H${BASE_X + 46} L${BASE_X + 36} ${H - 10} Q ${BASE_X + 35} ${H - 6} ${BASE_X + 30} ${H - 6} H${BASE_X - 30} Q ${BASE_X - 35} ${H - 6} ${BASE_X - 36} ${H - 10} Z`} fill={`url(#${uid}-pot)`} />\n          <rect className=\"fill-(--pot-deep)\" height={14} rx={4} width={112} x={BASE_X - 56} y={BASE_Y} />\n          <rect className=\"fill-white/20\" height={3} rx={1.5} width={92} x={BASE_X - 46} y={BASE_Y + 3} />\n\n          <AnimatePresence>\n            {bloom && !reduce && (\n              <motion.g className=\"fill-amber-400\" exit={{ opacity: 0 }} key=\"sparkles\">\n                {[-1, 1, -0.6, 0.7].map((d, i) => (\n                  <motion.circle\n                    animate={{ opacity: [0, 1, 0], cx: BASE_X + d * 44, cy: BASE_Y - STEM_H - 18 + i * 14 }}\n                    cx={BASE_X}\n                    cy={BASE_Y - STEM_H}\n                    initial={{ opacity: 0 }}\n                    key={d}\n                    r={2.5}\n                    transition={{ delay: 0.1 + i * 0.05, duration: 0.8, ease: \"easeOut\" }}\n                  />\n                ))}\n              </motion.g>\n            )}\n          </AnimatePresence>\n        </svg>\n      </div>\n\n      {children && <div className=\"relative mt-3\">{children}</div>}\n    </div>\n  );\n}\n"
    }
  ]
}