{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "segmented-progress",
  "type": "registry:ui",
  "title": "Segmented Progress",
  "description": "Metric card with segmented progress bars, hover value preview, a proximity wave and staggered GSAP fill.",
  "dependencies": [
    "clsx",
    "tailwind-merge",
    "gsap",
    "@gsap/react",
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/ui/segmented-progress.tsx",
      "content": "\"use client\";\n\nimport React, {\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport {\n  AnimatePresence,\n  animate,\n  motion,\n  useMotionValue,\n  useReducedMotion,\n  useTransform,\n  type MotionValue,\n  type Variants,\n} from \"motion/react\";\nimport { useGSAP } from \"@gsap/react\";\nimport gsap from \"gsap\";\nimport { ArrowRight, MoreHorizontal } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\ngsap.registerPlugin(useGSAP);\n\nexport type SegmentedProgressColor =\n  | \"emerald\"\n  | \"blue\"\n  | \"violet\"\n  | \"amber\"\n  | \"rose\"\n  | \"cyan\";\n\nexport interface ColorConfig {\n  active: string;\n  glow: string;\n  badgeBg: string;\n  badgeText: string;\n  badgeBorder: string;\n  inactive: string;\n}\n\nconst COLOR_MAP: Record<SegmentedProgressColor, ColorConfig> = {\n  emerald: {\n    active: \"#4ade80\",\n    glow: \"rgba(74, 222, 128, 0.38)\",\n    badgeBg: \"rgba(16, 185, 129, 0.12)\",\n    badgeText: \"#4ade80\",\n    badgeBorder: \"rgba(74, 222, 128, 0.25)\",\n    inactive: \"#1e1e22\",\n  },\n  blue: {\n    active: \"#60a5fa\",\n    glow: \"rgba(96, 165, 250, 0.38)\",\n    badgeBg: \"rgba(59, 130, 246, 0.12)\",\n    badgeText: \"#60a5fa\",\n    badgeBorder: \"rgba(96, 165, 250, 0.25)\",\n    inactive: \"#1e1e22\",\n  },\n  violet: {\n    active: \"#a78bfa\",\n    glow: \"rgba(167, 139, 250, 0.38)\",\n    badgeBg: \"rgba(139, 92, 246, 0.12)\",\n    badgeText: \"#a78bfa\",\n    badgeBorder: \"rgba(167, 139, 250, 0.25)\",\n    inactive: \"#1e1e22\",\n  },\n  amber: {\n    active: \"#fbbf24\",\n    glow: \"rgba(251, 191, 36, 0.38)\",\n    badgeBg: \"rgba(245, 158, 11, 0.12)\",\n    badgeText: \"#fbbf24\",\n    badgeBorder: \"rgba(251, 191, 36, 0.25)\",\n    inactive: \"#1e1e22\",\n  },\n  rose: {\n    active: \"#fb7185\",\n    glow: \"rgba(251, 113, 133, 0.38)\",\n    badgeBg: \"rgba(244, 63, 94, 0.12)\",\n    badgeText: \"#fb7185\",\n    badgeBorder: \"rgba(251, 113, 133, 0.25)\",\n    inactive: \"#1e1e22\",\n  },\n  cyan: {\n    active: \"#22d3ee\",\n    glow: \"rgba(34, 211, 238, 0.38)\",\n    badgeBg: \"rgba(6, 182, 212, 0.12)\",\n    badgeText: \"#22d3ee\",\n    badgeBorder: \"rgba(34, 211, 238, 0.25)\",\n    inactive: \"#1e1e22\",\n  },\n};\n\nconst COLOR_ALIAS_MAP: Record<string, SegmentedProgressColor> = {\n  green: \"emerald\",\n  purple: \"violet\",\n  red: \"rose\",\n  orange: \"amber\",\n};\n\nfunction resolveColor(color: SegmentedProgressColor | string): ColorConfig {\n  const normalized = COLOR_ALIAS_MAP[color] || color;\n  if (normalized in COLOR_MAP) {\n    return COLOR_MAP[normalized as SegmentedProgressColor];\n  }\n  return {\n    active: color,\n    glow: `color-mix(in srgb, ${color} 40%, transparent)`,\n    badgeBg: `color-mix(in srgb, ${color} 15%, transparent)`,\n    badgeText: color,\n    badgeBorder: `color-mix(in srgb, ${color} 28%, transparent)`,\n    inactive: \"#1e1e22\",\n  };\n}\n\nexport function ProgressPieIcon({\n  className,\n  style,\n}: {\n  className?: string;\n  style?: React.CSSProperties;\n}) {\n  return (\n    <svg\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      style={style}\n      className={cn(\"w-5 h-5 text-white\", className)}\n      aria-hidden=\"true\"\n    >\n      <circle\n        cx=\"12\"\n        cy=\"12\"\n        r=\"9\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        className=\"opacity-40\"\n      />\n      <path\n        d=\"M12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21C16.9706 21 21 16.9706 21 12H12V3Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n}\n\ntype Part = { key: string; digit: number } | { key: string; text: string };\n\nconst DIGITS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];\n\nconst rollMotionTokens = {\n  spring: {\n    snappy: { visualDuration: 0.32, bounce: 0.12 },\n    morph: { visualDuration: 0.4, bounce: 0.08 },\n    smooth: { visualDuration: 0.35, bounce: 0 },\n  },\n  duration: {\n    instant: 0.08,\n    fast: 0.18,\n    normal: 0.3,\n  },\n  blur: {\n    subtle: 1.5,\n    soft: 2.5,\n  },\n  ease: {\n    enter: [0.16, 1, 0.3, 1] as const,\n    standard: [0.2, 0, 0, 1] as const,\n  },\n};\n\nconst enterEase = [...rollMotionTokens.ease.enter] as [\n  number,\n  number,\n  number,\n  number,\n];\nconst exitEase = [...rollMotionTokens.ease.standard] as [\n  number,\n  number,\n  number,\n  number,\n];\n\nconst physicalSpring = ({\n  visualDuration,\n  bounce,\n}: {\n  visualDuration: number;\n  bounce: number;\n}) => {\n  const root = (2 * Math.PI) / (visualDuration * 1.2);\n  return {\n    type: \"spring\" as const,\n    stiffness: root * root,\n    damping: 2 * (1 - bounce) * root,\n    restDelta: 0.002,\n    restSpeed: 0.02,\n  };\n};\n\nconst rollSpring = physicalSpring(rollMotionTokens.spring.snappy);\n\nfunction partsOf(text: string): Part[] {\n  const chars = [...text];\n  const first = chars.findIndex((c) => c >= \"0\" && c <= \"9\");\n  const last =\n    chars.length -\n    1 -\n    [...chars].reverse().findIndex((c) => c >= \"0\" && c <= \"9\");\n  let place = chars.filter((c) => c >= \"0\" && c <= \"9\").length;\n  return chars.map((char, index): Part => {\n    if (first < 0 || index < first)\n      return { key: `p${index}${char}`, text: char };\n    if (index > last)\n      return { key: `s${chars.length - index}${char}`, text: char };\n    if (char >= \"0\" && char <= \"9\")\n      return { key: `d${--place}`, digit: Number(char) };\n    return { key: `g${place}${char}`, text: char };\n  });\n}\n\nconst slotVariants: Variants = {\n  enter: (direction: number) => ({\n    width: 0,\n    scale: 0.6,\n    opacity: 0,\n    y: `${direction * 0.3}em`,\n    filter: `blur(${rollMotionTokens.blur.soft}px)`,\n  }),\n  center: {\n    width: \"auto\",\n    scale: 1,\n    opacity: 1,\n    y: 0,\n    filter: \"blur(0px)\",\n    transitionEnd: { filter: \"none\" },\n    transition: {\n      width: rollMotionTokens.spring.morph,\n      scale: rollMotionTokens.spring.morph,\n      opacity: rollMotionTokens.spring.morph,\n      y: rollMotionTokens.spring.snappy,\n      filter: { duration: 0.22, ease: enterEase },\n    },\n  },\n  exit: (direction: number) => ({\n    width: 0,\n    scale: 0.6,\n    opacity: 0,\n    y: `${direction * -0.3}em`,\n    filter: `blur(${rollMotionTokens.blur.subtle}px)`,\n    transition: {\n      width: rollMotionTokens.spring.smooth,\n      scale: rollMotionTokens.spring.smooth,\n      y: { duration: rollMotionTokens.duration.fast, ease: exitEase },\n      opacity: { duration: rollMotionTokens.duration.instant },\n      filter: { duration: rollMotionTokens.duration.instant },\n    },\n  }),\n};\n\nconst stillVariants: Variants = {\n  enter: { width: \"auto\", scale: 1, opacity: 0, y: 0, filter: \"none\" },\n  center: {\n    width: \"auto\",\n    scale: 1,\n    opacity: 1,\n    y: 0,\n    filter: \"none\",\n    transition: { duration: rollMotionTokens.duration.instant },\n  },\n  exit: { width: 0, opacity: 0, transition: { duration: 0 } },\n};\n\nfunction Glyph({\n  position,\n  digit,\n}: {\n  position: MotionValue<number>;\n  digit: number;\n}) {\n  const offset = (current: number) => {\n    const diff = (((digit - current) % 10) + 10) % 10;\n    return diff > 5 ? diff - 10 : diff;\n  };\n  const y = useTransform(position, (current) => `${offset(current) * 1.05}em`);\n  const opacity = useTransform(position, (current) =>\n    Math.max(0, 1 - Math.abs(offset(current)) ** 1.5 * 1.1),\n  );\n  const visibility = useTransform(position, (current) =>\n    Math.abs(offset(current)) >= 1 ? \"hidden\" : \"visible\",\n  );\n  const filter = useTransform(position, (current) => {\n    const distance = Math.abs(offset(current));\n    return distance < 0.02 || distance >= 1\n      ? \"none\"\n      : `blur(${(distance * rollMotionTokens.blur.soft * 0.75).toFixed(2)}px)`;\n  });\n\n  return (\n    <motion.span\n      className=\"absolute inset-0 flex items-center justify-center pointer-events-none select-none will-change-transform\"\n      style={{ y, opacity, filter, visibility }}\n    >\n      {digit}\n    </motion.span>\n  );\n}\n\nfunction Wheel({ digit, direction }: { digit: number; direction: number }) {\n  const reduced = useReducedMotion();\n  const position = useMotionValue(digit);\n  const wheel = useRef({ digit, target: digit });\n\n  useEffect(() => {\n    const state = wheel.current;\n    if (state.digit === digit) return;\n    state.target +=\n      direction > 0\n        ? (digit - state.digit + 10) % 10\n        : -((state.digit - digit + 10) % 10);\n    state.digit = digit;\n    if (reduced) position.jump(state.target);\n    else animate(position, state.target, rollSpring);\n  }, [digit, direction, position, reduced]);\n\n  return (\n    <>\n      <span className=\"invisible pointer-events-none select-none tabular-nums font-semibold\">\n        0\n      </span>\n      {DIGITS.map((item) => (\n        <Glyph key={item} position={position} digit={item} />\n      ))}\n    </>\n  );\n}\n\nexport function RollingNumber({\n  value,\n  text,\n  className,\n}: {\n  value: number;\n  text: string;\n  className?: string;\n}) {\n  const reduced = useReducedMotion();\n  const [trail, setTrail] = useState({ value, direction: 1 });\n\n  if (trail.value !== value) {\n    setTrail({ value, direction: value >= trail.value ? 1 : -1 });\n  }\n  const direction =\n    trail.value === value ? trail.direction : value >= trail.value ? 1 : -1;\n\n  return (\n    <span\n      className={cn(\n        \"inline-flex items-baseline overflow-hidden tabular-nums leading-none select-none\",\n        className,\n      )}\n    >\n      <AnimatePresence initial={false} custom={direction}>\n        {partsOf(text).map((part) => (\n          <motion.span\n            key={part.key}\n            className={\n              \"digit\" in part\n                ? \"relative inline-flex items-center justify-center overflow-hidden h-[1.15em] align-baseline\"\n                : \"inline-flex items-center justify-center align-baseline\"\n            }\n            custom={direction}\n            variants={reduced ? stillVariants : slotVariants}\n            initial=\"enter\"\n            animate=\"center\"\n            exit=\"exit\"\n          >\n            {\"digit\" in part ? (\n              <Wheel digit={part.digit} direction={direction} />\n            ) : (\n              part.text\n            )}\n          </motion.span>\n        ))}\n      </AnimatePresence>\n    </span>\n  );\n}\n\nexport interface SegmentedProgressBarProps {\n  value: number;\n  segments?: number;\n  color?: SegmentedProgressColor | string;\n  height?: number;\n  interactive?: boolean;\n  onChange?: (value: number) => void;\n  onHoverChange?: (hoveredValue: number | null, index: number | null) => void;\n  className?: string;\n  disabled?: boolean;\n  ariaLabel?: string;\n}\n\nexport function SegmentedProgressBar({\n  value,\n  segments = 32,\n  color = \"emerald\",\n  height = 26,\n  interactive = true,\n  onChange,\n  onHoverChange,\n  className,\n  disabled = false,\n  ariaLabel,\n}: SegmentedProgressBarProps) {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const trackRef = useRef<HTMLDivElement>(null);\n  const segmentRefs = useRef<(HTMLDivElement | null)[]>([]);\n\n  const colorConfig = useMemo(() => resolveColor(color), [color]);\n  const clampedValue = Math.max(0, Math.min(100, Math.round(value)));\n  const baselineCount = Math.round((clampedValue / 100) * segments);\n\n  const isAdjustable = interactive && !disabled && Boolean(onChange);\n\n  const [hoverIndex, setHoverIndex] = useState<number | null>(null);\n  const isDraggingRef = useRef(false);\n\n  const activeSegmentsCount =\n    hoverIndex !== null ? hoverIndex + 1 : baselineCount;\n\n  const isReducedMotion = useRef(false);\n  useEffect(() => {\n    if (typeof window !== \"undefined\") {\n      isReducedMotion.current = window.matchMedia(\n        \"(prefers-reduced-motion: reduce)\",\n      ).matches;\n    }\n  }, []);\n\n  useGSAP(\n    () => {\n      if (isReducedMotion.current) return;\n      const elements = segmentRefs.current.slice(0, baselineCount);\n      if (elements.length === 0) return;\n\n      gsap.fromTo(\n        elements,\n        {\n          scaleY: 0.35,\n          opacity: 0.2,\n        },\n        {\n          scaleY: 1,\n          opacity: 1,\n          duration: 0.45,\n          stagger: {\n            each: 0.012,\n            from: \"start\",\n          },\n          ease: \"power2.out\",\n        },\n      );\n    },\n    {\n      scope: containerRef,\n      dependencies: [baselineCount],\n      revertOnUpdate: false,\n    },\n  );\n\n  const updateHoverEffect = useCallback((targetIndex: number | null) => {\n    if (!segmentRefs.current.length) return;\n\n    segmentRefs.current.forEach((seg, idx) => {\n      if (!seg) return;\n      if (targetIndex === null) {\n        gsap.to(seg, {\n          scaleY: 1,\n          duration: 0.25,\n          ease: \"power2.out\",\n          overwrite: \"auto\",\n        });\n      } else {\n        const dist = Math.abs(idx - targetIndex);\n        if (dist < 4) {\n          const scale = 1 + 0.16 * Math.exp(-(dist * dist) / 3.5);\n          gsap.to(seg, {\n            scaleY: scale,\n            duration: 0.16,\n            ease: \"power1.out\",\n            overwrite: \"auto\",\n          });\n        } else {\n          gsap.to(seg, {\n            scaleY: 1,\n            duration: 0.2,\n            ease: \"power2.out\",\n            overwrite: \"auto\",\n          });\n        }\n      }\n    });\n  }, []);\n\n  const handlePointerMove = useCallback(\n    (e: React.PointerEvent<HTMLDivElement>) => {\n      if (disabled || !trackRef.current) return;\n      const rect = trackRef.current.getBoundingClientRect();\n      const pointerX = Math.max(0, Math.min(rect.width, e.clientX - rect.left));\n      const ratio = pointerX / rect.width;\n      const index = Math.min(segments - 1, Math.floor(ratio * segments));\n      const hoveredVal = Math.round(((index + 1) / segments) * 100);\n\n      setHoverIndex(index);\n      onHoverChange?.(hoveredVal, index);\n      updateHoverEffect(index);\n\n      if (isDraggingRef.current && interactive && onChange) {\n        onChange(hoveredVal);\n      }\n    },\n    [\n      disabled,\n      segments,\n      onHoverChange,\n      updateHoverEffect,\n      interactive,\n      onChange,\n    ],\n  );\n\n  const handlePointerLeave = useCallback(() => {\n    isDraggingRef.current = false;\n    setHoverIndex(null);\n    onHoverChange?.(null, null);\n    updateHoverEffect(null);\n  }, [onHoverChange, updateHoverEffect]);\n\n  const handlePointerDown = useCallback(\n    (e: React.PointerEvent<HTMLDivElement>) => {\n      if (disabled || !interactive) return;\n      isDraggingRef.current = true;\n      e.currentTarget.setPointerCapture(e.pointerId);\n      handlePointerMove(e);\n    },\n    [disabled, interactive, handlePointerMove],\n  );\n\n  const handlePointerUp = useCallback(\n    (e: React.PointerEvent<HTMLDivElement>) => {\n      if (isDraggingRef.current) {\n        isDraggingRef.current = false;\n        try {\n          e.currentTarget.releasePointerCapture(e.pointerId);\n        } catch {}\n      }\n    },\n    [],\n  );\n\n  const handleKeyDown = useCallback(\n    (e: React.KeyboardEvent<HTMLDivElement>) => {\n      if (disabled || !interactive || !onChange) return;\n      if (e.key === \"ArrowRight\" || e.key === \"ArrowUp\") {\n        e.preventDefault();\n        onChange(Math.min(100, clampedValue + 1));\n      } else if (e.key === \"ArrowLeft\" || e.key === \"ArrowDown\") {\n        e.preventDefault();\n        onChange(Math.max(0, clampedValue - 1));\n      } else if (e.key === \"PageUp\") {\n        e.preventDefault();\n        onChange(\n          Math.round((Math.min(segments, baselineCount + 1) / segments) * 100),\n        );\n      } else if (e.key === \"PageDown\") {\n        e.preventDefault();\n        onChange(Math.round((Math.max(0, baselineCount - 1) / segments) * 100));\n      } else if (e.key === \"Home\") {\n        e.preventDefault();\n        onChange(0);\n      } else if (e.key === \"End\") {\n        e.preventDefault();\n        onChange(100);\n      }\n    },\n    [disabled, interactive, onChange, clampedValue, baselineCount, segments],\n  );\n\n  return (\n    <div\n      ref={containerRef}\n      className={cn(\n        \"relative w-full select-none touch-none py-1\",\n        disabled && \"opacity-40 pointer-events-none\",\n        className,\n      )}\n    >\n      <div\n        ref={trackRef}\n        role={isAdjustable ? \"slider\" : \"progressbar\"}\n        tabIndex={isAdjustable ? 0 : -1}\n        aria-valuenow={clampedValue}\n        aria-valuemin={0}\n        aria-valuemax={100}\n        aria-valuetext={`${clampedValue}%`}\n        aria-label={ariaLabel || \"Progress bar\"}\n        onPointerMove={handlePointerMove}\n        onPointerDown={handlePointerDown}\n        onPointerUp={handlePointerUp}\n        onPointerCancel={handlePointerLeave}\n        onPointerLeave={handlePointerLeave}\n        onKeyDown={handleKeyDown}\n        className={cn(\n          \"relative flex w-full items-center justify-between gap-0.75 sm:gap-1 outline-none\",\n          interactive &&\n            \"cursor-pointer focus-visible:ring-2 focus-visible:ring-white/40 focus-visible:ring-offset-2 focus-visible:ring-offset-black rounded-md\",\n        )}\n        style={{ height }}\n      >\n        {Array.from({ length: segments }).map((_, idx) => {\n          const isActive = idx < activeSegmentsCount;\n\n          const activeStyle = isActive\n            ? {\n                backgroundColor: colorConfig.active,\n                boxShadow: `0 0 10px ${colorConfig.glow}`,\n                opacity: 1,\n              }\n            : {\n                backgroundColor: colorConfig.inactive,\n                opacity: 1,\n              };\n\n          return (\n            <div\n              key={idx}\n              ref={(el) => {\n                segmentRefs.current[idx] = el;\n              }}\n              style={{\n                height: \"100%\",\n                ...activeStyle,\n              }}\n              className=\"flex-1 min-w-0.75 rounded-full transition-colors duration-150 will-change-transform\"\n            />\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n\nexport interface SegmentedProgressMetric {\n  id: string;\n  label: string;\n  value: number;\n  trend?: string;\n  trendDirection?: \"up\" | \"down\" | \"neutral\";\n  color?: SegmentedProgressColor | string;\n  unit?: string;\n  formatValue?: (val: number) => string;\n}\n\nexport interface SegmentedProgressItemProps {\n  metric: SegmentedProgressMetric;\n  segments?: number;\n  interactive?: boolean;\n  onChange?: (val: number) => void;\n}\n\nexport function SegmentedProgressItem({\n  metric,\n  segments = 32,\n  interactive = true,\n  onChange,\n}: SegmentedProgressItemProps) {\n  const {\n    label,\n    value,\n    trend,\n    trendDirection = \"up\",\n    color = \"emerald\",\n    unit = \"%\",\n    formatValue,\n  } = metric;\n\n  const colorConfig = useMemo(() => resolveColor(color), [color]);\n  const [hoveredValue, setHoveredValue] = useState<number | null>(null);\n\n  const displayVal = hoveredValue !== null ? hoveredValue : value;\n  const formattedText = formatValue\n    ? formatValue(displayVal)\n    : `${displayVal}${unit}`;\n\n  return (\n    <div className=\"flex flex-col gap-2.5 w-full\">\n      <div className=\"flex items-center justify-between\">\n        <span className=\"text-[13px] sm:text-sm font-medium text-zinc-400\">\n          {label}\n        </span>\n        <div className=\"flex items-center gap-2.5\">\n          {trend && (\n            <div\n              className=\"flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium\"\n              style={{\n                backgroundColor: colorConfig.badgeBg,\n                color: colorConfig.badgeText,\n                border: `1px solid ${colorConfig.badgeBorder}`,\n              }}\n            >\n              <span className=\"text-xs\">\n                {trendDirection === \"up\"\n                  ? \"↑\"\n                  : trendDirection === \"down\"\n                    ? \"↓\"\n                    : \"•\"}\n              </span>\n              <span>{trend}</span>\n            </div>\n          )}\n          <span className=\"text-lg sm:text-xl font-semibold tabular-nums text-white min-w-[3.5ch] text-right inline-flex items-baseline justify-end\">\n            <RollingNumber value={displayVal} text={formattedText} />\n          </span>\n        </div>\n      </div>\n\n      <SegmentedProgressBar\n        value={value}\n        segments={segments}\n        color={color}\n        interactive={interactive}\n        onChange={onChange}\n        onHoverChange={(hoverVal) => setHoveredValue(hoverVal)}\n        ariaLabel={`${label} progress`}\n      />\n    </div>\n  );\n}\n\nexport interface SegmentedProgressProps {\n  title?: string;\n  subtitle?: string;\n  icon?: React.ReactNode;\n  color?: SegmentedProgressColor | string;\n  metrics?: SegmentedProgressMetric[];\n  segments?: number;\n  footerText?: string;\n  footerHref?: string;\n  footerAction?: () => void;\n  onMoreClick?: () => void;\n  interactive?: boolean;\n  onMetricChange?: (metricId: string, newValue: number) => void;\n  className?: string;\n}\n\nconst DEFAULT_METRICS: SegmentedProgressMetric[] = [\n  {\n    id: \"performing-progress\",\n    label: \"Performing Progress\",\n    value: 89,\n  },\n  {\n    id: \"target-sales\",\n    label: \"Target Sales\",\n    value: 67,\n  },\n];\n\nexport function SegmentedProgress({\n  title = \"Project Progress\",\n  subtitle = \"Overall completion rate all projects.\",\n  icon,\n  color = \"emerald\",\n  metrics = DEFAULT_METRICS,\n  segments = 32,\n  footerText = \"Up by 6% compared to last week, great momentum!\",\n  footerHref,\n  footerAction,\n  onMoreClick,\n  interactive = true,\n  onMetricChange,\n  className,\n}: SegmentedProgressProps) {\n  const cardRef = useRef<HTMLDivElement>(null);\n  const [valueOverrides, setValueOverrides] = useState<Record<string, number>>(\n    {},\n  );\n\n  const colorConfig = useMemo(() => resolveColor(color), [color]);\n\n  const activeMetrics = useMemo(() => {\n    return metrics.map((m) => ({\n      ...m,\n      color: m.color || color,\n      value:\n        valueOverrides[m.id] !== undefined ? valueOverrides[m.id] : m.value,\n    }));\n  }, [metrics, valueOverrides, color]);\n\n  const handleMetricChange = useCallback(\n    (id: string, val: number) => {\n      setValueOverrides((prev) => ({ ...prev, [id]: val }));\n      onMetricChange?.(id, val);\n    },\n    [onMetricChange],\n  );\n\n  return (\n    <div\n      ref={cardRef}\n      className={cn(\n        \"relative w-full max-w-105 rounded-2xl bg-[#141416] p-5 sm:p-6 text-white border border-white/8 shadow-[0_24px_50px_-12px_rgba(0,0,0,0.7)] flex flex-col gap-5 overflow-hidden\",\n        className,\n      )}\n    >\n      <div className=\"flex items-start justify-between gap-3\">\n        <div className=\"flex items-center gap-3.5\">\n          <div className=\"relative flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-linear-to-b from-[#2a2a2e] to-[#141416] border border-white/15 shadow-[inset_0_1px_1px_rgba(255,255,255,0.25),0_4px_12px_rgba(0,0,0,0.5)]\">\n            {icon || (\n              <ProgressPieIcon\n                className=\"w-5 h-5 transition-colors duration-200\"\n                style={{ color: colorConfig.active }}\n              />\n            )}\n          </div>\n          <div className=\"flex flex-col\">\n            <h3 className=\"text-[15px] sm:text-base font-semibold text-white tracking-tight leading-snug\">\n              {title}\n            </h3>\n            <p className=\"text-xs sm:text-[13px] text-zinc-400 font-normal leading-normal\">\n              {subtitle}\n            </p>\n          </div>\n        </div>\n\n        <button\n          type=\"button\"\n          onClick={onMoreClick}\n          aria-label=\"More options\"\n          className=\"rounded-lg p-1.5 text-zinc-400 hover:text-white hover:bg-white/6 transition-colors cursor-pointer outline-none focus-visible:ring-1 focus-visible:ring-white/20\"\n        >\n          <MoreHorizontal className=\"w-4 h-4\" />\n        </button>\n      </div>\n\n      <div className=\"flex flex-col gap-4\">\n        {activeMetrics.map((metric) => (\n          <SegmentedProgressItem\n            key={metric.id}\n            metric={metric}\n            segments={segments}\n            interactive={interactive}\n            onChange={(newVal) => handleMetricChange(metric.id, newVal)}\n          />\n        ))}\n      </div>\n\n      {footerText && (\n        <div\n          role={footerAction || footerHref ? \"button\" : undefined}\n          tabIndex={footerAction || footerHref ? 0 : undefined}\n          onClick={footerAction}\n          style={{\n            backgroundColor: colorConfig.badgeBg,\n            borderColor: colorConfig.badgeBorder,\n            color: colorConfig.badgeText,\n          }}\n          className={cn(\n            \"group relative flex items-center justify-between rounded-xl px-4 py-3 border transition-colors duration-200\",\n            (footerAction || footerHref) &&\n              \"cursor-pointer hover:brightness-110\",\n          )}\n        >\n          {footerHref ? (\n            <a\n              href={footerHref}\n              className=\"flex items-center justify-between w-full text-xs sm:text-[13px] font-medium leading-relaxed\"\n            >\n              <span>{footerText}</span>\n              <ArrowRight className=\"w-4 h-4 shrink-0 transition-transform duration-200 group-hover:translate-x-1\" />\n            </a>\n          ) : (\n            <>\n              <span className=\"text-xs sm:text-[13px] font-medium leading-relaxed\">\n                {footerText}\n              </span>\n              <ArrowRight className=\"w-4 h-4 shrink-0 transition-transform duration-200 group-hover:translate-x-1\" />\n            </>\n          )}\n        </div>\n      )}\n    </div>\n  );\n}\n\nexport default SegmentedProgress;\n",
      "type": "registry:ui",
      "target": "@ui/segmented-progress.tsx"
    }
  ]
}
