{"$schema":"https://ui.logic2b.com/schema/registry-item.json","name":"scroll-reveal","type":"registry:ui","title":"Scroll Reveal","description":"Plays a motion enter recipe when the element scrolls into view instead of on mount. Shares the motion engine's presets (fade, fade-up/down/left/right, scale, blur), timing (duration/delay) and prefers-reduced-motion fallback, with once/amount/margin/root controls from useInView. SSR- and no-JS-safe: renders visible until armed. Supports asChild.","dependencies":["radix-ui"],"registryDependencies":["utils","motion","use-in-view"],"files":[{"path":"ui/scroll-reveal.tsx","type":"registry:ui","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"radix-ui\"\n\nimport { useInView, type UseInViewOptions } from \"@/hooks/use-in-view\"\nimport { cn } from \"@/lib/utils\"\nimport { motionEnter, type MotionPreset } from \"@/components/ui/motion\"\n\n/**\n * Plays a motion enter recipe when the element scrolls into view (rather than on\n * mount like `<Motion>`). Built on the `useInView` IntersectionObserver hook and\n * the motion engine's recipes, so it shares the same presets, timing and\n * prefers-reduced-motion fallback.\n *\n * SSR- and no-JS-safe: it renders visible until it has mounted and armed, so\n * search engines and scriptless clients always see the content.\n */\nexport interface ScrollRevealProps\n  extends React.ComponentProps<\"div\">,\n    Pick<UseInViewOptions, \"once\" | \"amount\" | \"margin\" | \"root\"> {\n  /** Enter recipe to play when revealed. Default `\"fade-up\"`. */\n  preset?: MotionPreset\n  /** Reveal duration in milliseconds. Overrides the 500ms default. */\n  duration?: number\n  /** Reveal delay in milliseconds — the knob for staggering a group. */\n  delay?: number\n  /** Merge props onto the single child instead of rendering a wrapper. */\n  asChild?: boolean\n}\n\nfunction ScrollReveal({\n  className,\n  preset = \"fade-up\",\n  duration,\n  delay,\n  asChild = false,\n  once = true,\n  amount = 0.3,\n  margin = \"0px\",\n  root,\n  style,\n  ...props\n}: ScrollRevealProps) {\n  const [ref, inView] = useInView<HTMLDivElement>({ once, amount, margin, root })\n  const [armed, setArmed] = React.useState(false)\n  React.useEffect(() => setArmed(true), [])\n\n  const timing: React.CSSProperties = {}\n  if (duration != null)\n    (timing as Record<string, string>)[\"--tw-animation-duration\"] = `${duration}ms`\n  if (delay != null)\n    (timing as Record<string, string>)[\"--tw-animation-delay\"] = `${delay}ms`\n\n  const Comp = asChild ? Slot.Root : \"div\"\n\n  return (\n    <Comp\n      ref={ref}\n      data-slot=\"scroll-reveal\"\n      data-in-view={inView ? \"\" : undefined}\n      className={cn(\n        inView ? motionEnter(preset) : armed ? \"opacity-0\" : undefined,\n        className\n      )}\n      style={{ ...timing, ...style }}\n      {...props}\n    />\n  )\n}\n\nexport { ScrollReveal }\n"}],"categories":["motion"],"accessibility":{"support":"consumer","pattern":"scroll-triggered reveal","primitive":"Intersection Observer + Motion","keyboard":[],"aria":["Content remains in the document reading order and preserves the rendered child semantics."],"consumer":["Render meaningful content without requiring scroll activation and disable movement for reduced-motion preferences."],"limitations":["Incorrect initial styles can hide content when scripting or observation is unavailable."]},"api":{"source":"src/ui/scroll-reveal.tsx","exports":[{"name":"ScrollRevealProps","kind":"type","description":"Plays a motion enter recipe when the element scrolls into view (rather than on mount like `<Motion>`). Built on the `useInView` IntersectionObserver hook and the motion engine's recipes, so it shares the same presets, timing and prefers-reduced-motion fallback. SSR- and no-JS-safe: it renders visible until it has mounted and armed, so search engines and scriptless clients always see the content.","definition":"interface ScrollRevealProps extends React.ComponentProps<\"div\">, Pick<UseInViewOptions, \"once\" | \"amount\" | \"margin\" | \"root\"> { /** Enter recipe to play when revealed. Default `\"fade-up\"`. */ preset?: MotionPreset /** Reveal duration in milliseconds. Overrides the 500ms default. */ duration?: number /** Reveal delay in milliseconds — the knob for staggering a group. */ delay?: number /** Merge props onto the single child instead of rendering a wrapper. */ asChild?: boolean }"},{"name":"ScrollReveal","kind":"component","propsType":"ScrollRevealProps","props":[{"name":"preset","type":"MotionPreset","required":false,"default":"\"fade-up\"","description":"Enter recipe to play when revealed. Default `\"fade-up\"`."},{"name":"duration","type":"number","required":false,"description":"Reveal duration in milliseconds. Overrides the 500ms default."},{"name":"delay","type":"number","required":false,"description":"Reveal delay in milliseconds — the knob for staggering a group."},{"name":"asChild","type":"boolean","required":false,"default":"false","description":"Merge props onto the single child instead of rendering a wrapper."},{"name":"once","type":"boolean","required":false,"default":"true","description":"Stop observing after the first time the element enters view. Default `true`."},{"name":"amount","type":"number","required":false,"default":"0.3","description":"Fraction of the element that must be visible to count as in view (0–1). Default `0.3`."},{"name":"margin","type":"string","required":false,"default":"\"0px\"","description":"IntersectionObserver `rootMargin`, e.g. `\"-10% 0px\"`. Default `\"0px\"`."}]}]}}