{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"enter-view","type":"registry:ui","title":"Enter View","description":"Slides, scales and fades its children into place when they scroll into view.","dependencies":[],"registryDependencies":[],"files":[{"path":"registry/ui/enter-view.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { useEffect, useRef, useState, type ReactNode, type RefObject } from \"react\";\n\nconst EASES = {\n  smooth: \"cubic-bezier(0.16, 1, 0.3, 1)\",\n  spring: \"cubic-bezier(0.34, 1.56, 0.64, 1)\",\n  snappy: \"cubic-bezier(0.65, 0, 0.35, 1)\",\n  linear: \"linear\",\n} as const;\n\nconst OFFSET = { up: [0, 1], down: [0, -1], left: [1, 0], right: [-1, 0] } as const;\n\nexport interface EnterViewProps {\n  children: ReactNode;\n  /** Direction the content travels as it enters. */\n  direction?: keyof typeof OFFSET;\n  /** Px travelled on entry. */\n  distance?: number;\n  /** Starting scale; 1 disables scaling. */\n  scale?: number;\n  /** Starting opacity. */\n  initialOpacity?: number;\n  /** Ms. */\n  duration?: number;\n  /** Ms to wait once in view. */\n  delay?: number;\n  /** A preset name or any CSS timing function. */\n  ease?: keyof typeof EASES | (string & {});\n  /** Fraction of the element that must be visible to trigger. */\n  threshold?: number;\n  /** Animate only the first time; otherwise it replays on every entry. */\n  once?: boolean;\n  /** Scrolling ancestor to observe. Defaults to the viewport. */\n  scrollContainerRef?: RefObject<HTMLElement | null>;\n  className?: string;\n}\n\nexport function EnterView({\n  children,\n  direction = \"up\",\n  distance = 80,\n  scale = 0.96,\n  initialOpacity = 0,\n  duration = 900,\n  delay = 0,\n  ease = \"smooth\",\n  threshold = 0.15,\n  once = true,\n  scrollContainerRef,\n  className,\n}: EnterViewProps) {\n  const ref = useRef<HTMLDivElement>(null);\n  const [shown, setShown] = useState(false);\n\n  useEffect(() => {\n    const el = ref.current!;\n    const io = new IntersectionObserver(\n      ([entry]) => {\n        if (entry.isIntersecting) {\n          setShown(true);\n          if (once) io.disconnect();\n        } else if (!once) setShown(false);\n      },\n      { threshold, root: scrollContainerRef?.current ?? null },\n    );\n    io.observe(el);\n    return () => io.disconnect();\n  }, [threshold, once, scrollContainerRef]);\n\n  const [dx, dy] = OFFSET[direction] ?? OFFSET.up;\n  const curve = EASES[ease as keyof typeof EASES] ?? ease;\n  const timing = `${duration}ms ${curve} ${delay}ms`;\n\n  return (\n    <div\n      ref={ref}\n      data-animated-content=\"\"\n      className={className}\n      style={{\n        opacity: shown ? 1 : initialOpacity,\n        transform: shown ? \"none\" : `translate3d(${dx * distance}px, ${dy * distance}px, 0) scale(${scale})`,\n        transition: `opacity ${timing}, transform ${timing}`,\n        willChange: \"transform, opacity\",\n      }}\n    >\n      <style href=\"enter-view\" precedence=\"default\">\n        {`@media (prefers-reduced-motion: reduce){[data-animated-content]{transform:none!important;transition:opacity .2s!important}}`}\n      </style>\n      {children}\n    </div>\n  );\n}\n"}]}