{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "text-reveal",
  "title": "Text Reveal",
  "description": "GSAP SplitText reveals by character, word or line.",
  "dependencies": [
    "gsap"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.cuelark.com/r/theme.json",
    "https://ui.cuelark.com/r/motion.json"
  ],
  "files": [
    {
      "path": "src/registry/cuelark/ui/text-reveal.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { SplitText } from \"gsap/SplitText\";\nimport { ScrollTrigger } from \"gsap/ScrollTrigger\";\n\nimport { gsap, motionScale, useGSAP } from \"@/lib/motion\";\nimport { cn } from \"@/lib/utils\";\n\nif (typeof window !== \"undefined\") gsap.registerPlugin(SplitText, ScrollTrigger);\n\ntype TextRevealProps = React.HTMLAttributes<HTMLElement> & {\n  /** The element to render. Use a real heading (`\"h1\"`, `\"h2\"`) for SEO: the full text is in the server HTML. */\n  as?: \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"p\" | \"span\" | \"div\";\n  /** What animates as a unit. */\n  split?: \"chars\" | \"words\" | \"lines\";\n  /** `rise` slides each piece up out of a mask; `blur` focuses it in; `fade` just fades. */\n  effect?: \"rise\" | \"blur\" | \"fade\";\n  /** Animate on mount or when scrolled into view. */\n  trigger?: \"mount\" | \"inView\";\n  delay?: number;\n  /** Seconds between pieces. */\n  stagger?: number;\n};\n\n/**\n * Splits text with GSAP SplitText (re-splits on resize and font load, keeps an accessible label) and\n * reveals it piece by piece. Without JavaScript the text is simply there.\n */\nfunction TextReveal({ as: Tag = \"p\", split = \"words\", effect = \"rise\", trigger = \"inView\", delay = 0, stagger, className, children, ...props }: TextRevealProps) {\n  const ref = React.useRef<HTMLElement>(null);\n\n  useGSAP(\n    () => {\n      const el = ref.current;\n      if (!el) return;\n      const scale = motionScale(el);\n      if (!scale) return;\n      SplitText.create(el, {\n        type: split === \"chars\" ? \"words,chars\" : split,\n        mask: effect === \"rise\" ? split : undefined,\n        // aria-label is prohibited on generic elements (span, div): there, leave the word spans readable instead.\n        aria: (Tag === \"span\" || Tag === \"div\") && split !== \"chars\" ? \"none\" : \"auto\",\n        autoSplit: true,\n        onSplit(self) {\n          const targets = self[split];\n          const from =\n            effect === \"rise\"\n              ? { yPercent: 110 }\n              : effect === \"blur\"\n                ? { opacity: 0, filter: `blur(${8 * scale}px)`, y: 6 * scale }\n                : { opacity: 0 };\n          return gsap.from(targets, {\n            ...from,\n            duration: effect === \"rise\" ? 0.7 : 0.8,\n            ease: \"cue\",\n            delay,\n            stagger: stagger ?? { chars: 0.015, words: 0.04, lines: 0.1 }[split],\n            scrollTrigger: trigger === \"inView\" ? { trigger: el, start: \"top 85%\", once: true } : undefined,\n          });\n        },\n      });\n    },\n    { scope: ref, dependencies: [split, effect, trigger, delay, stagger] },\n  );\n\n  return React.createElement(Tag, { ref, \"data-slot\": \"text-reveal\", className: cn(\"[&_.line]:pb-[0.08em]\", className), ...props }, children);\n}\n\nexport { TextReveal };\nexport type { TextRevealProps };\n",
      "type": "registry:ui"
    }
  ],
  "docs": "Docs and examples: https://ui.cuelark.com/components/text-reveal",
  "categories": [
    "motion"
  ],
  "type": "registry:ui"
}