{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hero-before-after",
  "type": "registry:block",
  "title": "Before and after hero",
  "description": "Draggable comparison slider beside the copy, keyboard operable, with fixed-contrast labels and a results disclaimer.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "hero",
    "beauty",
    "wellness",
    "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": "hero/hero-before-after.tsx",
      "content": "\"use client\"\n\nimport { useCallback, useRef, useState } from \"react\"\nimport { motion, useReducedMotion } from \"motion/react\"\nimport { ChevronsLeftRight } from \"lucide-react\"\nimport { cn } from \"@/components/storekit/lib/cn\"\nimport { useSkBlock } from \"@/components/storekit/lib/sk-analytics\"\n\n/* Before and after hero. Copy on the left, a draggable comparison on the\n   right. The handle is a real slider: drag anywhere on the frame, or focus the\n   handle and use Arrow keys (Shift for 10%), Home and End. The \"after\" image\n   is clipped with clip-path so nothing reflows while dragging. Labels sit on\n   fixed dark chips so they read on any photo. Stacks with the comparison\n   first under lg. */\n\nconst EASE = [0.23, 1, 0.32, 1] as const\n\ntype Cta = { label: string; href: string }\ntype Shot = { src: string; alt: string; label: string; style?: React.CSSProperties }\n\nexport function HeroBeforeAfter({\n  eyebrow = \"Results, unretouched\",\n  headline = \"Eight weeks of Daily Greens, same light, same camera.\",\n  subcopy = \"Drag to compare. Photos are from one customer, shot under identical conditions.\",\n  primaryCta = { label: \"Shop Daily Greens\", href: \"#\" },\n  secondaryCta = { label: \"See more results\", href: \"#\" },\n  before = { src: \"/placeholder.webp\", alt: \"Before\", label: \"Week 0\" },\n  after = { src: \"/placeholder.webp\", alt: \"After\", label: \"Week 8\" },\n  initial = 50,\n  disclaimer = \"Individual results vary.\",\n  className,\n}: {\n  eyebrow?: string\n  headline?: string\n  subcopy?: string\n  primaryCta?: Cta\n  secondaryCta?: Cta\n  /** `style` lets you tone-match a pair (for example a CSS filter on one side). */\n  before?: Shot\n  after?: Shot\n  /** Starting split in percent. */\n  initial?: number\n  disclaimer?: string\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  const sk = useSkBlock(\"hero\", \"hero-before-after\")\n  const [pos, setPos] = useState(initial)\n  const [dragging, setDragging] = useState(false)\n  const frame = useRef<HTMLDivElement>(null)\n  const touched = useRef(false)\n\n  const note = () => {\n    if (touched.current) return\n    touched.current = true\n    sk.emit(\"interact\", \"compare\")\n  }\n\n  const fromPointer = useCallback((clientX: number) => {\n    const r = frame.current?.getBoundingClientRect()\n    if (!r) return\n    setPos(Math.max(0, Math.min(100, ((clientX - r.left) / r.width) * 100)))\n  }, [])\n\n  const onKey = (e: React.KeyboardEvent) => {\n    const step = e.shiftKey ? 10 : 2\n    let next = pos\n    if (e.key === \"ArrowLeft\" || e.key === \"ArrowDown\") next = pos - step\n    else if (e.key === \"ArrowRight\" || e.key === \"ArrowUp\") next = pos + step\n    else if (e.key === \"Home\") next = 0\n    else if (e.key === \"End\") next = 100\n    else return\n    e.preventDefault()\n    note()\n    setPos(Math.max(0, Math.min(100, next)))\n  }\n\n  return (\n    <section ref={sk.ref} {...sk.attrs} className={cn(\"bg-sk-paper text-sk-ink\", className)}>\n      <div className=\"mx-auto grid w-full max-w-6xl items-center gap-10 px-4 py-12 sm:px-8 sm:py-16 lg:grid-cols-[0.9fr_1.1fr] lg:gap-14\">\n        <motion.div\n          initial={{ opacity: 0, y: reduce ? 0 : 8 }}\n          animate={{ opacity: 1, y: 0 }}\n          transition={{ duration: 0.5, ease: EASE }}\n          className=\"order-2 lg:order-1\"\n        >\n          <p className=\"font-sk-mono text-[11px] uppercase tracking-[0.14em] text-sk-muted\">{eyebrow}</p>\n          <h1\n            className=\"mt-4 text-balance font-sk-display font-semibold leading-[1.1] tracking-[-0.02em]\"\n            style={{ fontSize: \"clamp(2rem, 3.8vw, 3rem)\" }}\n          >\n            {headline}\n          </h1>\n          <p className=\"mt-5 max-w-md text-[15px] leading-[1.5] text-sk-muted\">{subcopy}</p>\n          <div className=\"mt-8 flex flex-col gap-3 sm:flex-row\">\n            <a\n              href={primaryCta.href}\n              onClick={() => sk.emit(\"interact\", \"cta\")}\n              className=\"sk-btn inline-flex min-h-12 items-center justify-center py-2.5 text-center leading-tight rounded-sk-btn bg-sk-accent px-7 text-[15px] font-medium text-sk-accent-ink outline-none transition-[filter,transform] duration-200 ease-sk-out hover:brightness-[1.06] focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-paper active:scale-[0.97]\"\n            >\n              {primaryCta.label}\n            </a>\n            <a\n              href={secondaryCta.href}\n              onClick={() => sk.emit(\"interact\", \"cta_secondary\")}\n              className=\"sk-btn inline-flex min-h-12 items-center justify-center py-2.5 text-center leading-tight rounded-sk-btn border border-sk-line px-7 text-[15px] font-medium text-sk-ink outline-none transition-[background-color,transform] duration-200 ease-sk-out hover:bg-sk-ink/[0.04] focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-paper active:scale-[0.97]\"\n            >\n              {secondaryCta.label}\n            </a>\n          </div>\n          {disclaimer ? <p className=\"mt-6 text-sm text-sk-muted\">{disclaimer}</p> : null}\n        </motion.div>\n\n        <div\n          ref={frame}\n          onPointerDown={(e) => {\n            e.currentTarget.setPointerCapture(e.pointerId)\n            setDragging(true)\n            note()\n            fromPointer(e.clientX)\n          }}\n          onPointerMove={(e) => dragging && fromPointer(e.clientX)}\n          onPointerUp={() => setDragging(false)}\n          onPointerCancel={() => setDragging(false)}\n          className={cn(\n            \"relative order-1 aspect-[4/3] w-full touch-pan-y select-none overflow-hidden rounded-sk-card border border-sk-line bg-sk-surface lg:order-2\",\n            dragging ? \"cursor-grabbing\" : \"cursor-ew-resize\"\n          )}\n        >\n          <img src={before.src} alt={before.alt} draggable={false} style={before.style} className=\"absolute inset-0 h-full w-full object-cover\" />\n          <img\n            src={after.src}\n            alt={after.alt}\n            draggable={false}\n            style={{ ...after.style, clipPath: `inset(0 0 0 ${pos}%)` }}\n            className=\"absolute inset-0 h-full w-full object-cover\"\n          />\n          <span className=\"absolute left-3 top-3 rounded-sk-chip bg-[rgb(0_0_0/0.55)] px-2.5 py-1 font-sk-mono text-[11px] uppercase tracking-[0.14em] text-[rgb(255_255_255)]\">\n            {before.label}\n          </span>\n          <span className=\"absolute right-3 top-3 rounded-sk-chip bg-[rgb(0_0_0/0.55)] px-2.5 py-1 font-sk-mono text-[11px] uppercase tracking-[0.14em] text-[rgb(255_255_255)]\">\n            {after.label}\n          </span>\n\n          <div aria-hidden className=\"pointer-events-none absolute inset-y-0 w-px bg-[rgb(255_255_255)]\" style={{ left: `${pos}%` }} />\n          <button\n            type=\"button\"\n            role=\"slider\"\n            aria-label={`Compare ${before.label} and ${after.label}`}\n            aria-valuemin={0}\n            aria-valuemax={100}\n            aria-valuenow={Math.round(pos)}\n            aria-valuetext={`${Math.round(pos)}% ${before.label}`}\n            onKeyDown={onKey}\n            className=\"absolute top-1/2 grid h-11 w-11 -translate-x-1/2 -translate-y-1/2 place-items-center rounded-full border border-[rgb(0_0_0/0.15)] bg-[rgb(255_255_255)] text-[rgb(0_0_0/0.8)] shadow-[0_8px_24px_-8px_rgb(0_0_0/0.5)] outline-none transition-transform duration-200 ease-sk-out focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-[rgb(0_0_0)] active:scale-[0.97]\"\n            style={{ left: `${pos}%` }}\n          >\n            <ChevronsLeftRight size={18} aria-hidden />\n          </button>\n        </div>\n      </div>\n    </section>\n  )\n}\n",
      "type": "registry:block",
      "target": "components/storekit/hero/hero-before-after.tsx"
    }
  ],
  "docs": "Use a real, identically lit photo pair and keep the disclaimer; `style` on a shot can tone-match the pair.",
  "meta": {
    "section": "hero",
    "variant": "before-after",
    "niche": [
      "beauty",
      "wellness",
      "any"
    ],
    "commerce": [
      "cta"
    ],
    "previewHeight": 600
  }
}