{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cart-tier-rewards",
  "type": "registry:component",
  "title": "Tiered rewards bar",
  "description": "One progress track with three milestones at their real thresholds (free shipping, 10% off, free gift). Markers fill as reached.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "cart",
    "any"
  ],
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://storekit-eight.vercel.app/r/cn.json"
  ],
  "files": [
    {
      "path": "cart/cart-tier-rewards.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, useReducedMotion } from \"motion/react\"\nimport { Check, Gift, Percent, Truck, type LucideIcon } from \"lucide-react\"\nimport { cn, money } from \"@/components/storekit/lib/cn\"\n\n/* Multi-tier reward bar. One track, one evenly spaced milestone per tier\n   (so labels always sit under their marker, however close the thresholds\n   are). The fill moves through each segment in proportion to the amount\n   between tiers. Markers fill as the subtotal passes them and the headline\n   always names the next reward and the exact amount left. */\n\nexport type RewardTier = { threshold: number; label: string; icon?: \"shipping\" | \"percent\" | \"gift\" }\n\nconst ICONS: Record<NonNullable<RewardTier[\"icon\"]>, LucideIcon> = { shipping: Truck, percent: Percent, gift: Gift }\n\nconst DEFAULT_TIERS: RewardTier[] = [\n  { threshold: 7500, label: \"Free shipping\", icon: \"shipping\" },\n  { threshold: 12500, label: \"10% off\", icon: \"percent\" },\n  { threshold: 17500, label: \"Free gift\", icon: \"gift\" },\n]\n\nexport function CartTierRewards({ subtotal = 9800, tiers = DEFAULT_TIERS, className }: { subtotal?: number; tiers?: RewardTier[]; className?: string }) {\n  const reduce = useReducedMotion()\n  const sorted = React.useMemo(() => [...tiers].sort((a, b) => a.threshold - b.threshold), [tiers])\n  const max = sorted[sorted.length - 1]?.threshold ?? 1\n  const next = sorted.find((t) => subtotal < t.threshold)\n  const reached = sorted.filter((t) => subtotal >= t.threshold).length\n  const n = Math.max(1, sorted.length)\n  // Piecewise progress: each tier owns an equal share of the track.\n  const pct = (() => {\n    if (!next) return 1\n    const i = sorted.indexOf(next)\n    const prev = i > 0 ? sorted[i - 1].threshold : 0\n    const span = Math.max(1, next.threshold - prev)\n    return Math.min(1, Math.max(0, (i + (subtotal - prev) / span) / n))\n  })()\n\n  return (\n    <div data-sk-section=\"cart\" data-sk-variant=\"cart-tier-rewards\" className={cn(\"w-full\", className)}>\n      <p className=\"text-sm text-sk-ink\" aria-live=\"polite\">\n        {next ? (\n          <>\n            Add <span className=\"font-semibold tabular-nums\">{money(next.threshold - subtotal)}</span> to get <span className=\"font-semibold\">{next.label.toLowerCase()}</span>\n          </>\n        ) : (\n          <span className=\"font-medium text-sk-savings\">All {sorted.length} rewards unlocked</span>\n        )}\n      </p>\n\n      <div className=\"relative mt-4 h-8 px-4\">\n        <div\n          className=\"absolute inset-x-4 top-1/2 h-1.5 -translate-y-1/2 overflow-hidden rounded-sk-chip bg-sk-ink/[0.08]\"\n          role=\"progressbar\"\n          aria-label=\"Progress to cart rewards\"\n          aria-valuemin={0}\n          aria-valuemax={max}\n          aria-valuenow={Math.min(subtotal, max)}\n          aria-valuetext={`${reached} of ${sorted.length} rewards unlocked`}\n        >\n          <motion.div\n            className=\"h-full w-full origin-left rounded-sk-chip bg-sk-accent\"\n            initial={false}\n            animate={{ scaleX: pct }}\n            transition={{ duration: reduce ? 0 : 0.6, ease: [0.16, 1, 0.3, 1] }}\n          />\n        </div>\n        <div className=\"absolute inset-x-4 inset-y-0\">\n          {sorted.map((t, i) => {\n            const on = subtotal >= t.threshold\n            const Icon = t.icon ? ICONS[t.icon] : Gift\n            return (\n              <span\n                key={t.label}\n                className=\"absolute top-1/2 -translate-x-1/2 -translate-y-1/2\"\n                style={{ left: `${((i + 1) / n) * 100}%` }}\n              >\n                <motion.span\n                  initial={false}\n                  animate={{ scale: on ? 1 : 0.9 }}\n                  transition={{ duration: reduce ? 0 : 0.3, ease: [0.16, 1, 0.3, 1] }}\n                  className={cn(\n                    \"grid h-8 w-8 place-items-center rounded-sk-chip border-2 transition-colors duration-300\",\n                    on ? \"border-sk-accent bg-sk-accent text-sk-accent-ink\" : \"border-sk-line bg-sk-card text-sk-muted\"\n                  )}\n                >\n                  {on ? <Check size={14} strokeWidth={2.5} /> : <Icon size={14} />}\n                </motion.span>\n              </span>\n            )\n          })}\n        </div>\n      </div>\n\n      <ol className=\"mt-3 grid gap-2\" style={{ gridTemplateColumns: `repeat(${sorted.length}, minmax(0, 1fr))` }}>\n        {sorted.map((t) => {\n          const on = subtotal >= t.threshold\n          const Icon = t.icon ? ICONS[t.icon] : Gift\n          return (\n            <li key={t.label} className=\"flex min-w-0 flex-col items-end text-right\">\n              <p className={cn(\"flex items-center gap-1 text-[13px] font-medium leading-tight\", on ? \"text-sk-ink\" : \"text-sk-muted\")}>\n                <Icon size={13} aria-hidden className={cn(\"shrink-0\", on ? \"text-sk-accent\" : \"text-sk-muted\")} />\n                <span className=\"min-w-0\">{t.label}</span>\n              </p>\n              <p className=\"mt-0.5 text-xs tabular-nums text-sk-muted\">{on ? \"Unlocked\" : money(t.threshold)}</p>\n            </li>\n          )\n        })}\n      </ol>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/storekit/cart/cart-tier-rewards.tsx"
    }
  ],
  "meta": {
    "section": "cart",
    "variant": "meter",
    "niche": [
      "any"
    ],
    "commerce": [
      "shipping-threshold",
      "discount",
      "gift"
    ],
    "previewHeight": 300
  }
}