{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "rating-badge",
  "title": "Rating Badge",
  "description": "Editorial rating with sequentially drawn stars, review count, and a soft luminous edge.",
  "dependencies": [
    "framer-motion"
  ],
  "files": [
    {
      "path": "components/trust/RatingBadge.tsx",
      "content": "'use client';\n\nimport { motion } from 'framer-motion';\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n//\n// rating     Score from 0–5 (supports halves)\n// count      Review count shown beside the score\n// source     Attribution label (e.g. \"Product Hunt\", \"G2\")\n// variant    visual | editorial — glass pill vs hairline rule\n// compact    Hide source / tighten spacing\n// className  Styles for the outer shell\n//\ntype RatingBadgeProps = {\n  rating?: number;\n  count?: number;\n  source?: string;\n  variant?: 'visual' | 'editorial';\n  compact?: boolean;\n  className?: string;\n};\n\nfunction Star({\n  fill,\n  index,\n}: {\n  fill: number;\n  index: number;\n}) {\n  const clip = Math.max(0, Math.min(1, fill)) * 100;\n\n  return (\n    <motion.span\n      className='relative inline-block h-3.5 w-3.5'\n      initial={{ opacity: 0, scale: 0.6, y: 4 }}\n      animate={{ opacity: 1, scale: 1, y: 0 }}\n      transition={{\n        duration: 0.35,\n        delay: 0.08 + index * 0.07,\n        ease: [0.22, 1, 0.36, 1],\n      }}\n      aria-hidden\n    >\n      <svg viewBox='0 0 20 20' className='absolute inset-0 h-full w-full text-gray-200'>\n        <path\n          fill='currentColor'\n          d='M10 1.5l2.35 5.1 5.55.7-4.1 3.85.1 5.55L10 14.3l-4.9 2.4.1-5.55-4.1-3.85 5.55-.7L10 1.5z'\n        />\n      </svg>\n      <span\n        className='absolute inset-0 overflow-hidden'\n        style={{ width: `${clip}%` }}\n      >\n        <svg viewBox='0 0 20 20' className='h-3.5 w-3.5 text-amber-400'>\n          <path\n            fill='currentColor'\n            d='M10 1.5l2.35 5.1 5.55.7-4.1 3.85.1 5.55L10 14.3l-4.9 2.4.1-5.55-4.1-3.85 5.55-.7L10 1.5z'\n          />\n        </svg>\n      </span>\n    </motion.span>\n  );\n}\n\nfunction formatCount(n: number) {\n  if (n >= 1000) return `${(n / 1000).toFixed(n >= 10000 ? 0 : 1)}k`;\n  return n.toLocaleString();\n}\n\nexport default function RatingBadge({\n  rating = 4.9,\n  count = 1280,\n  source = 'G2',\n  variant = 'visual',\n  compact = false,\n  className = '',\n}: RatingBadgeProps) {\n  const clamped = Math.max(0, Math.min(5, rating));\n  const stars = Array.from({ length: 5 }, (_, i) => clamped - i);\n\n  const shell =\n    variant === 'editorial'\n      ? 'inline-flex items-center gap-3 border-y border-[color:var(--color-border,#e5e7eb)] py-2'\n      : 'group inline-flex items-center gap-3 rounded-full border border-[color:var(--color-border,#e5e7eb)] bg-[var(--color-background,#fff)] px-3.5 py-2 shadow-[0_1px_2px_rgba(0,0,0,0.04)] backdrop-blur-md transition-[box-shadow,border-color] duration-300 hover:shadow-[0_0_0_1px_rgba(251,191,36,0.25),0_8px_24px_rgba(0,0,0,0.06)]';\n\n  return (\n    <motion.div\n      className={[shell, className].filter(Boolean).join(' ')}\n      initial={{ opacity: 0, y: 6 }}\n      animate={{ opacity: 1, y: 0 }}\n      transition={{ duration: 0.45, ease: [0.22, 1, 0.36, 1] }}\n    >\n      <div className='flex items-center gap-1' aria-hidden>\n        {stars.map((fill, i) => (\n          <Star key={i} fill={fill} index={i} />\n        ))}\n      </div>\n\n      <div className='flex items-baseline gap-1.5'>\n        <span className='text-sm font-semibold tracking-tight text-[color:var(--color-foreground,#111827)]'>\n          {clamped.toFixed(1)}\n        </span>\n        {!compact ? (\n          <span className='text-xs tracking-wide text-[color:var(--color-muted-foreground,#9ca3af)]'>\n            · {formatCount(count)} reviews\n          </span>\n        ) : (\n          <span className='text-xs text-[color:var(--color-muted-foreground,#9ca3af)]'>({formatCount(count)})</span>\n        )}\n      </div>\n\n      {!compact && source ? (\n        <span className='hidden text-[10px] font-medium uppercase tracking-[0.14em] text-[color:var(--color-muted-foreground,#9ca3af)] sm:inline'>\n          {source}\n        </span>\n      ) : null}\n\n      <span className='sr-only'>\n        Rated {clamped.toFixed(1)} out of 5 from {count} reviews on {source}\n      </span>\n    </motion.div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/trust/RatingBadge.tsx"
    }
  ],
  "categories": [
    "trust"
  ],
  "type": "registry:component"
}