{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hero-parallax",
  "title": "Hero Parallax",
  "description": "Hero copy over three card rows that slide apart as a tilted block flattens.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/velora/hero-parallax.tsx",
      "content": "\"use client\";\n\nimport { useRef } from \"react\";\nimport {\n  motion,\n  useMotionValue,\n  useScroll,\n  useSpring,\n  useTransform,\n  type MotionValue,\n} from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface HeroParallaxProduct {\n  /** Caption on the card; also the link's accessible name */\n  title: string;\n  /** Image URL, shown with empty alt text since the title labels it */\n  image: string;\n  /** Turns the card into a link */\n  href?: string;\n}\n\ninterface HeroParallaxProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** Cards, split evenly across three rows */\n  products: HeroParallaxProduct[];\n  /** Hero copy shown above the rows */\n  children?: React.ReactNode;\n  /** How far each row slides sideways over the scroll, in px */\n  drift?: number;\n  /** Extra classes for every card, e.g. to resize them */\n  cardClassName?: string;\n  /** Scrollable element to track instead of the window */\n  container?: React.RefObject<HTMLElement | null>;\n}\n\n/**\n * Hero copy over three rows of cards that slide in opposite directions\n * while the whole block tilts from a 3D angle into flat as you scroll.\n * Heights use `cqh`, so inside a `@container-size` scroll box they measure\n * that box instead of the viewport. Under `prefers-reduced-motion` the block\n * stays flat and the rows wrap so every card is visible.\n */\nexport function HeroParallax({\n  products,\n  children,\n  drift = 480,\n  cardClassName,\n  container,\n  className,\n  ...props\n}: HeroParallaxProps) {\n  const ref = useRef<HTMLDivElement>(null);\n  const { scrollYProgress } = useScroll({\n    container,\n    target: ref,\n    offset: [\"start start\", \"end start\"],\n  });\n  const progress = useSpring(scrollYProgress, { stiffness: 260, damping: 40 });\n\n  const rotateX = useTransform(progress, [0, 0.22], [18, 0]);\n  const rotateZ = useTransform(progress, [0, 0.22], [12, 0]);\n  const y = useTransform(progress, [0, 0.22], [60, 0]);\n  const opacity = useTransform(progress, [0, 0.22], [0.5, 1]);\n  const right = useTransform(progress, [0, 1], [0, drift]);\n  const left = useTransform(progress, [0, 1], [0, -drift]);\n\n  const size = Math.ceil(products.length / 3);\n  const rows = [0, 1, 2].map((i) => products.slice(i * size, (i + 1) * size));\n\n  return (\n    <div\n      ref={ref}\n      data-slot=\"hero-parallax\"\n      className={cn(\"relative overflow-clip pb-[16cqh]\", className)}\n      {...props}\n    >\n      {children && (\n        <div className=\"relative z-10 mx-auto max-w-4xl px-6 pt-[14cqh] pb-[10cqh]\">\n          {children}\n        </div>\n      )}\n      <motion.div\n        style={{ rotateX, rotateZ, y, opacity, transformPerspective: 1000 }}\n        className=\"flex origin-top flex-col gap-5 will-change-transform motion-reduce:transform-none! motion-reduce:opacity-100!\"\n      >\n        {rows.map((row, i) => (\n          <Row\n            key={i}\n            items={row}\n            x={i === 1 ? left : right}\n            root={ref}\n            cardClassName={cardClassName}\n          />\n        ))}\n      </motion.div>\n    </div>\n  );\n}\n\nfunction Row({\n  items,\n  x,\n  root,\n  cardClassName,\n}: {\n  items: HeroParallaxProduct[];\n  x: MotionValue<number>;\n  root: React.RefObject<HTMLDivElement | null>;\n  cardClassName?: string;\n}) {\n  // Extra offset that slides a keyboard-focused card back inside the clip.\n  const nudge = useMotionValue(0);\n  const shifted = useTransform(() => x.get() + nudge.get());\n\n  const reveal = (event: React.FocusEvent) => {\n    const box = root.current?.getBoundingClientRect();\n    const card = (event.target as Element).getBoundingClientRect();\n    if (!box) return;\n    const gap = 24;\n    if (card.left < box.left) nudge.set(nudge.get() + box.left - card.left + gap);\n    else if (card.right > box.right)\n      nudge.set(nudge.get() + box.right - card.right - gap);\n  };\n\n  if (!items.length) return null;\n\n  return (\n    <motion.ul\n      role=\"list\"\n      style={{ x: shifted }}\n      onFocus={reveal}\n      onBlur={(e) => {\n        if (!e.currentTarget.contains(e.relatedTarget)) nudge.set(0);\n      }}\n      className=\"flex justify-center gap-5 will-change-transform motion-reduce:transform-none! motion-reduce:flex-wrap\"\n    >\n      {items.map((item, i) => {\n        const Tag = item.href ? \"a\" : \"div\";\n        return (\n          <li key={`${item.title}-${i}`} className=\"shrink-0\">\n            <Tag\n              href={item.href}\n              className={cn(\n                \"group/hero-card relative block aspect-[4/3] w-60 overflow-hidden rounded-2xl border bg-muted shadow-lg shadow-black/5 outline-none sm:w-72\",\n                item.href &&\n                  \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n                cardClassName\n              )}\n            >\n              {/* eslint-disable-next-line @next/next/no-img-element */}\n              <img\n                src={item.image}\n                alt=\"\"\n                loading=\"lazy\"\n                className=\"size-full object-cover transition-transform duration-500 ease-out motion-safe:group-hover/hero-card:scale-105\"\n              />\n              <span className=\"absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 via-black/30 to-transparent px-4 pt-10 pb-3 text-sm font-medium text-white\">\n                {item.title}\n              </span>\n            </Tag>\n          </li>\n        );\n      })}\n    </motion.ul>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/velora/hero-parallax.tsx"
    }
  ],
  "type": "registry:ui"
}