{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "upsell-policy-strip",
  "type": "registry:component",
  "title": "Pre-CTA policy strip",
  "description": "Delivery window, returns and guarantee in one row above add to cart, each opening a short detail.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "upsell",
    "any"
  ],
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://storekit-eight.vercel.app/r/cn.json",
    "https://storekit-eight.vercel.app/r/sk-analytics.json"
  ],
  "files": [
    {
      "path": "upsell/upsell-policy-strip.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useId, useState } from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport { ChevronDown, RotateCcw, ShieldCheck, Truck } from \"lucide-react\"\nimport { cn } from \"@/components/storekit/lib/cn\"\nimport { useSkBlock } from \"@/components/storekit/lib/sk-analytics\"\n\n/* Pre-CTA policy strip. Delivery, returns and guarantee in one compact row\n   placed directly above the add to cart button, each specific rather than\n   generic. Tapping an item opens its one-paragraph detail underneath. The\n   delivery window is computed from business days after mount, so server and\n   client markup match. */\n\nconst EASE = [0.23, 1, 0.32, 1] as const\nconst RING = \"outline-none focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-paper\"\n\ntype Policy = { id: string; icon: React.ComponentType<{ size?: number; className?: string }>; label: string; value: string; detail: string }\n\nfunction addBusinessDays(from: Date, n: number) {\n  const d = new Date(from)\n  let left = n\n  while (left > 0) {\n    d.setDate(d.getDate() + 1)\n    const day = d.getDay()\n    if (day !== 0 && day !== 6) left--\n  }\n  return d\n}\n\nexport function UpsellPolicyStrip({\n  deliveryDays = [2, 4],\n  returnsDays = 30,\n  guarantee = \"30-day guarantee\",\n  details,\n  className,\n}: {\n  /** Business-day delivery window [min, max] from today. */\n  deliveryDays?: [number, number]\n  returnsDays?: number\n  guarantee?: string\n  /** Override the detail paragraph per policy id: delivery, returns, guarantee. */\n  details?: Partial<Record<\"delivery\" | \"returns\" | \"guarantee\", string>>\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  const uid = useId()\n  const sk = useSkBlock(\"upsell\", \"upsell-policy-strip\")\n  const [window_, setWindow] = useState<string | null>(null)\n  const [open, setOpen] = useState<string | null>(null)\n  const [minDays, maxDays] = deliveryDays\n\n  useEffect(() => {\n    const fmt = (d: Date) => d.toLocaleDateString(undefined, { weekday: \"short\", month: \"short\", day: \"numeric\" }).replace(/ (\\d)/g, \"\\u00a0$1\")\n    const now = new Date()\n    setWindow(`${fmt(addBusinessDays(now, minDays))} to ${fmt(addBusinessDays(now, maxDays))}`)\n  }, [minDays, maxDays])\n\n  const policies: Policy[] = [\n    {\n      id: \"delivery\",\n      icon: Truck,\n      label: \"Delivery\",\n      value: window_ ?? `${deliveryDays[0]} to ${deliveryDays[1]} business days`,\n      detail: details?.delivery ?? `Tracked shipping in ${deliveryDays[0]} to ${deliveryDays[1]} business days. Free on orders over $150, otherwise calculated at checkout.`,\n    },\n    {\n      id: \"returns\",\n      icon: RotateCcw,\n      label: \"Returns\",\n      value: `Free ${returnsDays}-day returns`,\n      detail: details?.returns ?? `Unopened items can be returned within ${returnsDays} days of delivery. We email a prepaid label and refund to your original payment method.`,\n    },\n    {\n      id: \"guarantee\",\n      icon: ShieldCheck,\n      label: \"Guarantee\",\n      value: guarantee,\n      detail: details?.guarantee ?? \"Every batch is third-party tested. Not happy within 30 days? We refund you, opened or not.\",\n    },\n  ]\n  const active = policies.find((p) => p.id === open)\n\n  return (\n    <div {...sk.attrs} ref={sk.ref as React.RefObject<HTMLDivElement>} className={cn(\"w-full overflow-hidden rounded-sk-card border border-sk-line bg-sk-card\", className)}>\n      <ul className=\"grid grid-cols-3 divide-x divide-sk-line\">\n        {policies.map(({ id, icon: Icon, label, value }) => {\n          const on = open === id\n          return (\n            <li key={id} className=\"min-w-0\">\n              <button\n                type=\"button\"\n                aria-expanded={on}\n                aria-controls={`${uid}-detail`}\n                onClick={() => {\n                  if (!on) sk.emit(\"interact\", `policy_${id}`, undefined)\n                  setOpen(on ? null : id)\n                }}\n                className={cn(\n                  \"flex h-full min-h-[72px] w-full flex-col items-start gap-1.5 px-3 py-3 text-left transition-[background-color,transform] duration-200 hover:bg-sk-ink/[0.03] active:scale-[0.97] sm:px-4\",\n                  on && \"bg-sk-surface\",\n                  RING\n                )}\n              >\n                <span className=\"flex w-full items-center gap-1.5 text-sk-muted\">\n                  <Icon size={14} className=\"shrink-0\" />\n                  <span className=\"sr-only truncate font-sk-mono text-[11px] uppercase tracking-[0.14em] sm:not-sr-only\">{label}</span>\n                  <motion.span animate={{ rotate: on ? 180 : 0 }} transition={{ duration: reduce ? 0 : 0.25, ease: EASE }} className=\"ml-auto hidden shrink-0 sm:block\">\n                    <ChevronDown size={14} />\n                  </motion.span>\n                </span>\n                <span className=\"max-w-full break-words text-[13px] font-medium leading-[1.35] text-sk-ink tabular-nums\">{value}</span>\n              </button>\n            </li>\n          )\n        })}\n      </ul>\n      <AnimatePresence initial={false}>\n        {active ? (\n          <motion.div\n            id={`${uid}-detail`}\n            key=\"detail\"\n            initial={{ height: 0, opacity: 0 }}\n            animate={{ height: \"auto\", opacity: 1 }}\n            exit={{ height: 0, opacity: 0 }}\n            transition={{ duration: reduce ? 0 : 0.3, ease: EASE }}\n            className=\"overflow-hidden\"\n          >\n            <AnimatePresence mode=\"wait\" initial={false}>\n              <motion.p\n                key={active.id}\n                initial={{ opacity: 0, y: reduce ? 0 : 6 }}\n                animate={{ opacity: 1, y: 0 }}\n                exit={{ opacity: 0 }}\n                transition={{ duration: reduce ? 0 : 0.25, ease: EASE }}\n                className=\"border-t border-sk-line px-4 py-3 text-[13px] leading-[1.5] text-sk-muted\"\n              >\n                {active.detail}\n              </motion.p>\n            </AnimatePresence>\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/storekit/upsell/upsell-policy-strip.tsx"
    }
  ],
  "meta": {
    "section": "upsell",
    "variant": "policy-strip",
    "niche": [
      "any"
    ],
    "commerce": [
      "trust",
      "shipping"
    ],
    "previewHeight": 260
  }
}